Color is very effective when used well.
But using color well is not easy.
Some of the issues:
Perception depends on context.
Simple color assignments may not separate equally well.
Effectiveness may vary with the medium (screen, projector,
print).
Some people do not perceive the full spectrum of colors.
Grey scale printing.
Some colors have cultural significance.
Cultural significance may vary among cultures and with
time.
An internet “controversy” in 2015: The Dress (and a
follow-up article )
Color Spaces
RGB and HSV Color Spaces
Computer monitors and projectors work in terms of red, green, and
blue light.
Amounts of red green and blue (and alpha level) are stored as
integers in the range between 0 and 255 (8-bit bytes).
cols <- c("red", "green", "blue", "yellow", "cyan", "magenta")
rgbcols <- col2rgb(cols); colnames(rgbcols) <- cols
rgbcols
## red green blue yellow cyan magenta
## red 255 0 0 255 0 255
## green 0 255 0 255 255 0
## blue 0 0 255 0 255 255
Colors are often encoded in hexadecimal form (base 16).
rgb(1, 0, 0) ## pure red
## [1] "#FF0000"
rgb(0, 0, 1) ## pure blue
## [1] "#0000FF"
rgb(255, 0, 0, maxColorValue = 255)
## [1] "#FF0000"
rgb(0, 0, 255, maxColorValue = 255)
## [1] "#0000FF"
Hue, saturation, value (HSV) is a simple transformation of RGB.
rgb2hsv(rgbcols)
## red green blue yellow cyan magenta
## h 0 0.3333333 0.6666667 0.1666667 0.5 0.8333333
## s 1 1.0000000 1.0000000 1.0000000 1.0 1.0000000
## v 1 1.0000000 1.0000000 1.0000000 1.0 1.0000000
HSV is a little more convenient since it allows the hue to be
controlled separately.
A simple color picker: https://www.google.com/search?q=color+picker
But saturation and value attributes are not particularly useful for
specifying colors that work well perceptually.
A color wheel of fully saturated colors:
wheel <- function(col, radius = 1, ...)
pie(rep(1, length(col)),
col = col, radius = radius, ...)
wheel(rainbow(6))
Removing saturation:
library(colorspace)
wheel(desaturate(rainbow(6)))
Fully saturated yellow is brighter than red, which is brighter than
blue.
HCL Color Space
The rainbow palette of the color wheel is often a default in
visualization systems.
A blog post
illustrates why this is a bad idea.
The rainbow hues are evenly spaced in the color spectrum, but chroma
and luminance are not.
Luminance in particular is not monotone across the palette.
rgb2hcl <- function(col) {
## ignores alpha
col <- RGB(t(col[1 : 3, ]) / 255)
col <- as(col, "polarLUV")
col <- t(col@coords[, 3 : 1, drop = FALSE])
rownames(col) <- tolower(rownames(col))
col
}
col2hcl <- function(col) rgb2hcl(col2rgb(col))
pal <- function(col, border = "light gray", ...) {
n <- length(col)
plot(0, 0, type = "n", xlim = c(0, 1), ylim = c(0, 1),
axes = FALSE, xlab = "", ylab = "", ...)
rect((0 : (n - 1)) / n, 0, (1 : n) / n, 1, col = col, border = border)
}
par(mfrow = c(1, 2))
pal(rainbow(6), main = "Saturated Rainbow")
pal(desaturate(rainbow(6)), main = "Desaturated")
specplot(rainbow(6), ldw = 4)
The hue, chroma, luminance (HCL ) space
allows separate control of:
Hue , the color.
Chroma , the amount of the color.
Luminance , or perceived brightness.
HCL makes it easier to create perceptually uniform color
palettes.
A palette with constant chroma, evenly spaced hues and evenly spaced
luminance values:
rain6 <- hcl(seq(0, 360 * 5 / 6, len = 6), 50, seq(60, 80, len = 6))
par(mfrow = c(1, 2))
pal(rain6, main = "Uniform Rainbow")
pal(desaturate(rain6), main = "Desaturated")
specplot(rain6, lwd = 4)
For a fully saturated red, varying only chroma to reduce the amount
of color:
red_hcl <- list(h = 12.17395, c = 179.04076, l = 53.24059)
specplot(hcl(red_hcl$h, red_hcl$c * seq(0, 1, len = 10), red_hcl$l), lwd = 4)
For a given hue, not all combinations of chroma and luminance are
possible.
In particular, for low luminance values the available chroma range is
limited.
The ggplot book
contains this visualization of the HCL space.
Hue is mapped to angle.
Chroma is mapped to radius.
Luminance is mapped to facets.
The origins with zero chroma are shades of grey.
HCL color
picker.
HCL is a transformation of the CIEluv color space
designed for perceptual uniformity.
The definition of the luminance takes into account the light
sensitivity of a standard human observer at various wave lengths.
Light sensitivity for different wave lengths in daylight conditions
(photopic vision) and under dark adapted conditions (scotopic
vision):
Munsell Color Space
Another color space, similar to HCL, is the Munsell
system developed in the early 1900s.
This system uses a Hue, Value, Chroma encoding.
The munsell
package provides an R interface and is used in
ggplot.
Munsell specifications are of the form "H V/C", such as
5R 5/10.
Possible hues are
library(munsell, exclude = "desaturate")
mnsl_hues()
## [1] "2.5R" "5R" "7.5R" "10R" "2.5YR" "5YR" "7.5YR" "10YR" "2.5Y"
## [10] "5Y" "7.5Y" "10Y" "2.5GY" "5GY" "7.5GY" "10GY" "2.5G" "5G"
## [19] "7.5G" "10G" "2.5BG" "5BG" "7.5BG" "10BG" "2.5B" "5B" "7.5B"
## [28] "10B" "2.5PB" "5PB" "7.5PB" "10PB" "2.5P" "5P" "7.5P" "10P"
## [37] "2.5RP" "5RP" "7.5RP" "10RP"
V should be an integer between 0 and 10.
C should be an even integer less than 24, but not all
combinations are possible.
Adjusting colors in the value, chroma, and hue dimensions:
my_blue <- "5PB 5/8"
plot_mnsl(c(
lighter(my_blue, 2), my_blue, darker(my_blue, 2),
munsell::desaturate(my_blue, 2), my_blue, saturate(my_blue, 2),
rygbp(my_blue, 2), my_blue, pbgyr(my_blue, 2)))
Creating scales:
plot_mnsl(sapply(0 : 6, darker, col = "5PB 7/4")) + facet_wrap(~ num, nrow = 1)
Examining available colors:
hue_slice("5R")
value_slice(5)
Complementary colors:
complement_slice("5R")
Opponent Process Theory
The Opponent Process Model of vision says that the brain
divides the visual signal among three opposing contrast pairs:
black and white;
red and green;
yellow and blue.
The black/white pair corresponds to luminance in HCL
Hue and chroma in HCL span the two chromatic axes.
The luminance axis has higher resolution than the two chromatic
axes.
The major form of color vision deficiency reflects an inability to
distinguish differences along the red/green axis.
Impairment along the yellow/blue axis does occur as well but is much
rarer.
Contrast and Comparisons
Vision reacts to differences, not absolutes.
Small differences in shading or hue can be recognized when objects
are contiguous but be much harder to see when they are separated.
Simultaneous brightness contrast : a grey patch on a dark
background looks lighter than the same grey patch on a light
background.
plot(0, 0, type = "n", xlim = c(0, 1), ylim = c(0, 1),
axes = FALSE, xlab = "", ylab = "")
rect(0, 0, 0.5, 1, col = "lightgrey", border = NA)
rect(0.5, 0, 1, 1, col = "darkgrey", border = NA)
rect(0.2, 0.3, 0.3, 0.7, col = "grey", border = NA)
rect(0.7, 0.3, 0.8, 0.7, col = "grey", border = NA)
An example we saw earlier:
Some more are available here ,
including:
Using luminance or grey scale alone does not work well for encoding
categorical variables against a key.
Grey scale can be effective for showing continuous transitions in
pseudo-color images.
filled.contour(volcano, color.palette = grey.colors)
Grey scale is less effective for segmented maps, or choropleth maps;
only a few levels can be accurately decoded.
Interactions with Size, Background and Proximity
For small items more contrast and more saturated colors are
needed:
x <- runif(6, 0.1, 0.9)
y <- runif(6, 0.1, 0.9)
cols <- c("red", "green", "blue", "yellow", "cyan", "magenta")
f <- function(size = 1, black = FALSE) {
plot(x, y, type = "n", xlim = c(0, 1), ylim = c(0, 1))
if (black) rect(0, 0, 1, 1, col = "black")
text(x, y, cols, col = cols, cex = size)
}
opar <- par(mfrow = c(2, 2))
f(1)
f(4)
f(1, TRUE)
f(4, TRUE)
par(opar)
Variations in luminance are particularly helpful for seeing fine
structure, such as small text or small symbols:
plot(0, type = "n", xlim = c(0, 1), ylim = c(0, 1),
axes = FALSE, xlab = "", ylab = "")
rect(0, 0, 1, 1, col = hcl(0)) ## defaults: c = 35, l = 85
qbf <- "The quick brown fox jumps ..."
text(0.5, 0.3, label = qbf, col = hcl(180)) ## hue
text(0.5, 0.5, label = qbf, col = hcl(0, c = 70)) ## chroma
text(0.5, 0.7, label = qbf, col = hcl(0, l = 50)) ## luminance
Chrominance (hue and chroma) differences alone are not sufficient for
small items.
Ware recommends a luminance contrast of at least 3:1 for small text;
10:1 is preferable.
Small areas also need variation in more than hue:
Contrasting borders can help for larger areas with similar
luminance:
Color Specification in R
A large number of named colors are available (currently
657).
Some examples:
col2rgb("red")
## [,1]
## red 255
## green 0
## blue 0
col2rgb("forestgreen")
## [,1]
## red 34
## green 139
## blue 34
col2rgb("deepskyblue")
## [,1]
## red 0
## green 191
## blue 255
col2rgb("firebrick")
## [,1]
## red 178
## green 34
## blue 34
These will show some details:
colors()
demo(colors)
The available named colors follow a widely used standard .
These colors include the 140 web colors
supported on modern browsers.
Individual colors can also be specified using rgb() or
hcl() or as hexadecimal specifications.
library(colorspace)
hex2RGB("#FF0000")
## R G B
## [1,] 1 0 0
Using color spaces:
rgb(1, 0, 0)
## [1] "#FF0000"
rgb(255, 0, 0, max = 255)
## [1] "#FF0000"
rgb2hsv(col2rgb("red"))
## [,1]
## h 0
## s 1
## v 1
Converting to HCL:
rgb2hcl <- function(col) {
## ignores alpha
col <- RGB(t(col[1 : 3, ]) / 255)
col <- as(col, "polarLUV")
col <- t(col@coords[, 3 : 1, drop = FALSE])
rownames(col) <- tolower(rownames(col))
col
}
col2hcl <- function(col) rgb2hcl(col2rgb(col))
col2hcl("red")
## [,1]
## h 12.17395
## c 179.04076
## l 53.24059
col2hcl("green")
## [,1]
## h 127.7235
## c 135.7811
## l 87.7351
col2hcl("blue")
## [,1]
## h 265.87278
## c 130.67593
## l 32.29567
col2hcl("yellow")
## [,1]
## h 85.87351
## c 107.06462
## l 97.13951
col2hcl("cyan")
## [,1]
## h 192.16714
## c 72.09794
## l 91.11330
col2hcl("magenta")
## [,1]
## h 307.72618
## c 137.40166
## l 60.32351
hcl(12.17, 179.04, 53.24)
## [1] "#FF0000"
Color pickers can help:
When a set of colors is needed to encode variable values it is
usually best to use a suitable palette .
Color Palettes
Color palettes are collections of colors that work well
together.
It is useful to distinguish three kinds of palettes:
Tools for selecting palettes include:
A blog
post with some further options.
Some current US
government work on color palettes; more
extensive notes and code .
R color palette functions:
rainbow()
heat.colors()
terrain.colors()
topo.colors()
cm.colors()
grey.colors()
gray.colors()
These all take the number of colors as an argument, as well as some
additional optional arguments.
The hcl.color() function provides access to the palettes
defined in the colorspace package.
colorRampPalette() can be used to create a palette
function that interpolates between a set of colors using
rwb <- colorRampPalette(
c("red", "white", "blue"))
rwb(5)
## [1] "#FF0000" "#FF7F7F" "#FFFFFF" "#7F7FFF" "#0000FF"
filled.contour(volcano,
color.palette = rwb,
asp = 1)
With more perceptually comparable extremes (from the Blue-Red palette
of HCL Wizard):
rwb1 <- colorRampPalette(
c("#8E063B", "white", "#023FA5"))
filled.contour(volcano,
color.palette = rwb1,
asp = 1)
An alternative uses the muted function from package
scales:
rwb2 <- colorRampPalette(
c(scales::muted("red"),
"white",
scales::muted("blue")))
filled.contour(volcano,
color.palette = rwb2,
asp = 1)
Most base and lattice functions allow a vector of colors
to be specified.
Some, like filled.contour() and levelplot()
allow a palette function to be provided.
ggplot provides a framework for specifying palette
functions to use with scale_color_xyz() and
scale_fill_xyz() functions.
Packages like colorspace and viridis
provide additional scale_color_xyz() and
scale_fill_xyz() functions.
RColorBrewer Palettes
The available palettes:
library(RColorBrewer)
display.brewer.all()
Palettes in the first group are sequential .
The second group are qualitative .
The third group are diverging .
The "Blues" palette:
display.brewer.pal(9, "Blues")
As RGB values:
brewer.pal(9, "Blues")
## [1] "#F7FBFF" "#DEEBF7" "#C6DBEF" "#9ECAE1" "#6BAED6" "#4292C6" "#2171B5"
## [8] "#08519C" "#08306B"
The palettes are limited to a maximum number of levels.
To obtain more levels you can interpolate.
brewer.pal(10, "Blues")
## Warning in brewer.pal(10, "Blues"): n too large, allowed maximum for palette Blues is 9
## Returning the palette you asked for with that many colors
## [1] "#F7FBFF" "#DEEBF7" "#C6DBEF" "#9ECAE1" "#6BAED6" "#4292C6" "#2171B5"
## [8] "#08519C" "#08306B"
pbrbl <- colorRampPalette(brewer.pal(9, "Blues"), interpolate = "spline")
pbrbl
## function (n)
## {
## x <- ramp(seq.int(0, 1, length.out = n))
## if (ncol(x) == 4L)
## rgb(x[, 1L], x[, 2L], x[, 3L], x[, 4L], maxColorValue = 255)
## else rgb(x[, 1L], x[, 2L], x[, 3L], maxColorValue = 255)
## }
## <bytecode: 0x59e9da5e7628>
## <environment: 0x59e9d9124d20>
pbrbl(10)
## [1] "#F7FBFF" "#E0ECF7" "#CCDEF1" "#ADD0E5" "#81BBDA" "#57A1CF" "#3687C0"
## [8] "#1A69B0" "#064D98" "#08306B"
Colorspace Palettes
The colorspace package provides a wide range of
pre-defined palettes:
library(colorspace)
hcl_palettes(plot = TRUE)
A particular number of colors from one of these palettes can be
obtained with
qualitative_hcl(4, palette = "Dark 3")
## [1] "#E16A86" "#909800" "#00AD9A" "#9183E6"
The functions sequential_hcl() and
diverging_hcl() are analogous.
For use with ggplot2 the package provides scale
functions like scale_fill_discrete_qualitative() and
scale_color_continuous_sequential().
A package
vignette provides more details and background.
Viridis Palettes
These are provided in package viridisLite.
Palette functions are viridis(), mako(),
etc..
They are also available via the hcl.colors()
function.
For use in ggplot they can be specified in the viridis
color scale functions.
Palettes in R Graphics
ggplot uses scale_color_xyz() or
scale_fill_xyz().
For discrete scales the choices for xyz include
hue varies the hue (default for unordered
factors);
grey uses grey scale;
brewer uses ColorBrewer palettes;
manual allows explicit specification.
viridis_d for an alternative palette family (default
for orderer factors).
For continuous scales the choices for xyz include
gradient interpolates between two colors, low and
high;
gradient2 interpolates between three colors, low,
medium, high;
gradientn interpolates between a vector of colors;
distiller for ColorBrewer palettes.
viridis_c for an alternative palette family.
Others are available in packages such as colorspace.
The default qualitative and sequential discrete palettes:
library(gapminder)
gap_2007 <- filter(gapminder, year == 2007) |> slice_max(pop, n = 20)
p <- mutate(gap_2007, country = reorder(country, pop)) |>
ggplot(aes(x = gdpPercap, y = lifeExp, fill = continent)) +
scale_size_area(max_size = 10) +
scale_x_log10() +
geom_point(size = 4, shape = 21) +
guides(fill = guide_legend(override.aes = list(size = 4)))
p1 <- p + ggtitle("Hue")
p2 <- p + scale_fill_viridis_d() + ggtitle("Viridis")
library(patchwork)
p1 + p2
Discrete examples for brewer, colorspace
and manual:
p1 <- p + scale_fill_brewer(palette = "Set1") +
ggtitle("Brewer Set1")
p2 <- p + scale_fill_brewer(palette = "Set2") +
ggtitle("Brewer Set2")
p3 <- p + scale_fill_discrete_qualitative("Dark 3") +
ggtitle("Colorspace Dark 3")
p4 <- p + scale_fill_manual(values = c(Africa = "red", Asia = "blue",
Americas = "green", Europe = "grey")) +
ggtitle("Manual")
(p1 + p2) / (p3 + p4)
The default for continuous scales is gradient from a
dark blue to a light blue:
V <- data.frame(x = rep(seq_len(nrow(volcano)), ncol(volcano)),
y = rep(seq_len(ncol(volcano)), each = nrow(volcano)),
z = as.vector(volcano))
p <- ggplot(V, aes(x, y, fill = z)) + geom_raster() + coord_fixed()
p
Some alternatives:
p1 <- p + scale_fill_gradient2(
low = "red", mid = "white", high = "blue",
midpoint = median(volcano)) +
ggtitle("Red-White-Blue Gradient")
p2 <- p + scale_fill_viridis_c() +
ggtitle("Viridis")
p3 <- p + scale_fill_gradientn(
colors = terrain.colors(8)) +
ggtitle("Terrain")
vbins <- seq(80, by = 20, length.out = 7)
nc <- length(vbins) - 1
p4 <- ggplot(mutate(V, z = fct_rev(cut(z, vbins))),
aes(x, y, fill = z)) +
geom_raster() +
scale_fill_manual(values = rev(terrain.colors(nc))) +
ggtitle("Discretized Terrain")
(p1 + p2) / (p3 + p4)
Discretizing a continuous range to a modest number of levels can make
decoding values from a legend easier.
Reduced Color Vision
Color vision deficiency affects about 10% of males, a smaller
percentage of females.
The most common form is reduced ability to distinguish red and
green.
Some web sites provide tools to simulate how a visualization would
look to a color vision deficient viewer.
The R packages dichromat, colorspace, and
colorblindr provide tools for simulating how colors would
look to a color vision deficient viewer for three major types of color
vision deficiency:
deuteranomaly (green cone cells defective);
protanomaly (red cone cells defective);
tritanomaly (blue cone cells defective).
An article explaining the color vision impairment simulation is
available here
Using some tools from packages colorspace and
colorblinder we can simulate what a plot would look like in
grey scale and to someone with some of the major types of color
impairment.
A plot with the default discrete color palette:
p <- ggplot(gap_2007, aes(gdpPercap, lifeExp, color = continent)) +
geom_point(size = 4) +
scale_x_log10() +
guides(color = guide_legend(override.aes = list(size = 4)))
p
library(colorblindr)
library(colorspace)
library(grid)
color_check <- function(p) {
p1 <- edit_colors(p + ggtitle("Desaturated"), desaturate)
p2 <- edit_colors(p + ggtitle("deutan"), deutan)
p3 <- edit_colors(p + ggtitle("protan"), protan)
p4 <- edit_colors(p + ggtitle("tritan"), tritan)
gridExtra::grid.arrange(p1, p2, p3, p4, nrow = 2)
}
color_check(p)
For the Viridis palette:
pv <- p + scale_color_viridis_d()
pv
color_check(pv)
The swatchplot() function in the colorspace
package can be used with the cvd = TRUE argument to
simulate how specific palettes work for different color vision
deficiencies:
colorspace::swatchplot(rainbow(6), cvd = TRUE)
colorspace::swatchplot(hcl.colors(6), cvd = TRUE)
Two Issues to Watch Out For
Missing Values
It is common for default settings to not assign a color for missing
values.
In a choropleth map with (made-up) data where one state’s value is
missing this might not be noticed.
m <- map_data("state")
d <- data.frame(region = unique(m$region),
val = ordered(sample(1 : 4, 49, replace = TRUE)))
m <- left_join(m, d, "region")
pm <- ggplot(m) +
geom_polygon(aes(long, lat, group = group, fill = val)) +
coord_map() +
ggthemes::theme_map()
dnm <- mutate(m, val = replace(val, region == "michigan", NA))
pm %+% dnm
## Warning: <ggplot> %+% x was deprecated in ggplot2 4.0.0.
## ℹ Please use <ggplot> + x instead.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
Unless the viewer is very familiar with US geography.
Or is from Michigan.
In a scatterplot there are even fewer cues:
gnc <- mutate(gap_2007, continent = replace(continent, country == "China", NA))
pv +
pv %+% gnc
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_point()`).
Specifying na.value = "red", or some other color, will
make sure NA values are visible:
(pm %+% dnm +
scale_fill_viridis_d(na.value = "red") + theme(legend.position = "top")) +
(pv %+% gnc + scale_color_viridis_d(na.value = "red"))
## Scale for colour is already present.
## Adding another scale for colour, which will replace the existing scale.
Using outlines can also help:
p1 <- pm %+% dnm +
geom_polygon(aes(long, lat, group = group),
fill = NA, color = "black", linewidth = 0.1) +
theme(legend.position = "top")
p2 <- pv %+% gnc +
geom_point(shape = 21, fill = NA, color = "black", size = 4)
p1 + p2
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_point()`).
A final plot might handle missing values differently, but for initial
explorations it is a good idea to make sure they are clearly
visible.
Aligning Diverging Palettes
Diverging palettes are very useful for showing deviations above or
below a baseline.
par(mfrow = c(1, 2))
RColorBrewer::display.brewer.pal(7, "PRGn")
RColorBrewer::display.brewer.pal(6, "PRGn")
For a diverging palette to work properly, the palette base line needs
to be aligned with the data baseline.
How to do this will depend on the palette, but you do need to keep
this in mind when using a diverging palette.
Just using scale_fill_brewer is not enough when the
value range is not symmetric around the baseline:
m <- map_data("state")
d <- data.frame(region = unique(m$region),
val = ordered(sample((1 : 6) - 3, 49, replace = TRUE)))
m <- left_join(m, d, "region")
p <- ggplot(m) +
geom_polygon(aes(long, lat, group = group, fill = val)) +
coord_map() +
ggthemes::theme_map() +
theme(legend.position = "right")
p + scale_fill_brewer(palette = "PRGn")
Setting the scale limits explicitly forces a 7-category symmetric
scale that aligns the zero value with the middle color:
lims <- as.character(-3 : 3)
p + scale_fill_brewer(palette = "PRGn",
limits = lims)
This shows a category in the legend for -3 that does not appear in
the map.
This is often what you want.
But if you want to drop the -3 category, one option is to use a
manual scale:
vals <- RColorBrewer::brewer.pal(7, "PRGn")
names(vals) <- lims
p + scale_fill_manual(values = vals[-1])
Bivariate Palettes
It is possible to encode two variables in a palette.
Some sample palettes:
Bivariate palettes are sometimes used in bivariate
choropleth maps .
Some recommendations from Cynthia Brewer are available here .
A discussion
of a recent example.
Unless one variable is binary, and the palette is very well chosen,
it is hard to decode a visualization using a binary palette without
constantly referring to the key.
Culture, Tradition, and Conventions
Colors can have different meanings in different cultures and at
different times.
Conventions can also give colors particular meanings:
red/green in traffic lights;
red/green colors in microarray heatmaps;
red states and blue states;
pink for breast cancer;
pink for girls, blue for boys;
black for mourning.
Traffic Lights
Traffic
lights use red/green, even though this is a major axis of color
vision deficiency.
The convention comes from railroads.
The red used generally contains some orange and the green contains
blue to help with red/green color vision deficiency.
Position provides an alternate encoding. Orientations do vary.
Microarray Heatmaps
Microarrays are
used for the analysis of gene-level changes and differences in
bio-medical research.
Dyes are used that result in genes with a high response appearing
red and genes with a low response appearing green.
In keeping with this physical characteristic of microarrays, a
common visualization of the data is as a red/green heat map.
Red States and Blue States
It is now standard in the US to refer to Republican-leaning states as
red states and Democrat-leaning states as blue states.
This is a fairly recent convention, dating back to the 2000
presidential election.
Prior to 1980 it was somewhat more traditional to use red for more
left-leaning Democrats.
A map of the 1960 election results uses these more traditional
colors.
In 1996 the New York Times used blue for Democrat, red for
Republican, but the Washington Post used the opposite color
scheme.
The long, drawn out process of the 2000 election may have contributed
to fixing the color schema at the current convention.
Notes
Points need more saturation, luminance than areas.
False color images may benefit from discretizing.
Bivariate encodings (e.g. x = hue, y = luminance) are
possible but tricky and not often a good idea. Best if at least one is
binary.
Providing a second encoding, e.g. shape, position can help for color
vision deficient viewers and photocopying.
In area plots and maps it is important to distinguish between base
line values and missing values.
If observed values only cover part of a possible range, it is
sometimes appropriate to use a color coding that applies to the entire
possible range.
For diverging palettes, some care may be needed to make sure the
neutral color and the neutral value are properly aligned.
Using a well-designed palette is usually better than creating your
own.
Choosing a palette can consider many factors, including appearance
and branding.
A UI Diverging Palette?
UI color guidelines are
spelled out in the brand
manual .
UIgold <- rgb(255, 205, 0, maxColorValue=255)
pdUI <- colorRampPalette(c("black", "white", UIgold))
colorspace::swatchplot(pdUI(5))
colorspace::swatchplot(pdUI(5), cvd = TRUE)
References
Few, Stephen. “Practical rules for using color in charts.” Visual
Business Intelligence Newsletter 11 (2008): 25. (PDF )
Harrower, M. A. and Brewer, C. M. (2003). ColorBrewer.org: An online
tool for selecting color schemes for maps. The Cartographic
Journal , 40, 27–37. ColorBrewer
web site . The RColopBrewer package provides an R
interface.
Ihaka, R. (2003). Colour for presentation graphics, in K. Hornik, F.
Leisch, and A. Zeileis (eds.), Proceedings
of the 3rd International Workshop on Distributed
Statistical Computing , Vienna, Austria. PDF .
See also the colorspace package and vignette .
Lumley, T. (2006). Color coding and color blindness in statistical
graphics. ASA Statistical Computing & Graphics Newsletter ,
17(2), 4–7. PDF .
Munzner, T. (2014), Visualization Analysis and Design ,
Chapter 10.
Lisa Charlotte Muth (2021). 4-part series of blog posts on choosing
color scales. Part
1 ; Part
2 ; Part
3 ; Part
4 .
Lisa Charlotte Muth (2022). A detailed guide to colors in data vis
style guides. Blog
post .
Treinish, Lloyd A. “Why Should Engineers and Scientists Be Worried
About Color?.” IBM Thomas J. Watson Research Center, Yorktown Heights,
NY (2009): 46. (pdf )
Ware, C. (2012), Information Visualization: Perception for
Design , 3rd ed, Chapters 3 & 4.
Zeileis, A., Murrell, P. and Hornik, K. (2009). Escaping RGBland:
Selecting colors for statistical graphics, Computational Statistics
& Data Analysis , 53(9), 3259-–3270 (PDF ).
Achim Zeileis, Paul Murrell (2019). HCL-Based Color Palettes in
grDevices. R
Blog post .
Achim Zeileis et al. (2020). “colorspace: A Toolbox for Manipulating
and Assessing Colors and Palettes.” Journal of Statistical Software,
96(1), 1-49. doi:10.18637/jss.v096.i01 .
Coloring Political Statements
POLITIFACT reviews the accuracy of statements by politicians and
publishes summaries
of the results .
A 2016
post on Daily Kos included a
visualization
of the results for a number of politicians.
Kaiser Fung posted a critique
at JunkCharts and
proposed an alternative.
I scraped the data as of April 11, 2017, from POLITIFACT; they are
available here .
if (! file.exists("polfac.dat"))
download.file("https://stat.uiowa.edu/~luke/data/polfac.dat",
"polfac.dat")
pft <- read.table("polfac.dat")
vcp <- prop.table(as.matrix(pft), 1)[, 6 : 1]
colnames(vcp) <- gsub("\\.", " ", colnames(vcp))
head(vcp)
## Pants on Fire False Mostly False Half True Mostly True True
## Trump 0.16279070 0.3281654 0.1989664 0.14470284 0.12403101 0.04134367
## Bachmann 0.26229508 0.3606557 0.1311475 0.09836066 0.06557377 0.08196721
## Cruz 0.06779661 0.2796610 0.3050847 0.12711864 0.16101695 0.05932203
## Gingrich 0.13924051 0.1898734 0.2025316 0.25316456 0.12658228 0.08860759
## Palin 0.09523810 0.3015873 0.1428571 0.14285714 0.09523810 0.22222222
## Santorum 0.08333333 0.2833333 0.2000000 0.21666667 0.11666667 0.10000000
The Daily Kos chart is ordered by the percentage of statements that
are more false than true. A function to produce a bar chart with a
specified color palette:
## lattice version
polbars <- function(col = cm.colors(6)) {
barchart(vcp[order(rowSums(vcp[, 1 : 3])), ], auto.key = TRUE,
par.settings = list(superpose.polygon = list(col = col)))
}
## ggplot version
gvcp <- as.data.frame(vcp) |>
rownames_to_column("Name") |>
pivot_longer(-1, names_to = "Grade", values_to = "prop") |>
mutate(Grade = fct_rev(fct_inorder(Grade)))
nm <- mutate(gvcp, Grade = ordered(Grade)) |>
filter(Grade <= "Half True") |>
group_by(Name) |>
summarize(prop = sum(prop)) |>
arrange(desc(prop)) |>
pull(Name)
gvcp <- mutate(gvcp, Name = factor(Name, nm))
pvcp <- ggplot(gvcp, aes(Name, prop, fill = Grade)) +
geom_col(position = "fill", width = 0.7) +
coord_flip() +
theme(legend.position = "top",
plot.margin = margin(r = 50),
legend.text = element_text(size = 10)) +
scale_y_continuous(labels = scales::percent, expand = c(0, 0)) +
guides(fill = guide_legend(title = NULL, nrow = 1, reverse = TRUE)) +
labs(x = "", y = "")
polbars <- function(col = cm.colors(6))
pvcp + scale_fill_manual(values = rev(col))
polbars()
The original Daily Kos chart seems to use a slightly modified version
of the Color Brewer Spectral palette, a diverging
palette.
polbars(brewer.pal(6, "Spectral"))
dkcols <- brewer.pal(6, "Spectral")
dkcols[4] <- "lightgrey"
polbars(dkcols)
The JunkCharts plot uses another diverging palette, close to the
Blue-Red palette available in hclwizard.
rwbcols <- c("#4A6FE3", "#8595E1", "#B5BBE3", "#E2E2E2",
"#E6AFB9", "#E07B91", "#D33F6A")
polbars(rwbcols)
polbars(rev(rwbcols))
Another diverging palette:
polbars(brewer.pal(7, "PiYG"))
A sequential palette:
polbars(rev(brewer.pal(6, "Oranges")))
Exercises
A color can be specified in hexadecimal notation. Given such a
color specification you can find out what it looks like by using it in a
simple plot, or by using the Google color picker. Which of the following
best describe the color #B22222?
a shade of green
a shade of blue
orange
a shade of red
The following shows how to view the colors in the
RColorBrewer palette named Reds with 7
colors:
library(RColorBrewer)
display.brewer.pal(7, "Reds")
Which of the following RColorBrewer palettes is
diverging?
Blues
PuRd
Set1
RdGy
LS0tCnRpdGxlOiAiQ29sb3IgSXNzdWVzIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKLS0tCgo8bGluayByZWw9InN0eWxlc2hlZXQiIGhyZWY9InN0YXQ0NTgwLmNzcyIgdHlwZT0idGV4dC9jc3MiIC8+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+IC5yZW1hcmstY29kZSB7IGZvbnQtc2l6ZTogODUlOyB9IDwvc3R5bGU+CgpgYGB7ciBzZXR1cCwgaW5jbHVkZSA9IEZBTFNFfQpzb3VyY2UoaGVyZTo6aGVyZSgic2V0dXAuUiIpKQpvcHRpb25zKGh0bWx0b29scy5kaXIudmVyc2lvbiA9IEZBTFNFKQpvcHRpb25zKGNvbmZsaWN0cy5wb2xpY3kgPSAiZGVwZW5kcy5vayIpCmtuaXRyOjpvcHRzX2NodW5rJHNldChjb2xsYXBzZSA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICBmaWcuaGVpZ2h0ID0gNSwgZmlnLndpZHRoID0gNiwgZmlnLmFsaWduID0gImNlbnRlciIpCmxpYnJhcnkobGF0dGljZSkKbGlicmFyeSh0aWR5dmVyc2UpCnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKCkgKwogICAgICAgICAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYpKSArCiAgICAgICAgICB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3IgPSAiZ3JleTMwIiwgZmlsbCA9IE5BKSkpCnNldC5zZWVkKDEyMzQ1KQpgYGAKCkNvbG9yIGlzIHZlcnkgZWZmZWN0aXZlIHdoZW4gdXNlZCB3ZWxsLgoKQnV0IHVzaW5nIGNvbG9yIHdlbGwgaXMgbm90IGVhc3kuCgpTb21lIG9mIHRoZSBpc3N1ZXM6CgoqIFBlcmNlcHRpb24gZGVwZW5kcyBvbiBjb250ZXh0LgoKKiBTaW1wbGUgY29sb3IgYXNzaWdubWVudHMgbWF5IG5vdCBzZXBhcmF0ZSBlcXVhbGx5IHdlbGwuCgoqIEVmZmVjdGl2ZW5lc3MgbWF5IHZhcnkgd2l0aCB0aGUgbWVkaXVtIChzY3JlZW4sIHByb2plY3RvciwgcHJpbnQpLgoKKiBTb21lIHBlb3BsZSBkbyBub3QgcGVyY2VpdmUgdGhlIGZ1bGwgc3BlY3RydW0gb2YgY29sb3JzLgoKKiBHcmV5IHNjYWxlIHByaW50aW5nLgoKKiBTb21lIGNvbG9ycyBoYXZlIGN1bHR1cmFsIHNpZ25pZmljYW5jZS4KCiogQ3VsdHVyYWwgc2lnbmlmaWNhbmNlIG1heSB2YXJ5IGFtb25nIGN1bHR1cmVzIGFuZCB3aXRoIHRpbWUuCgoKQW4gaW50ZXJuZXQgImNvbnRyb3ZlcnN5IiBpbiAyMDE1OgpbVGhlIERyZXNzXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9UaGVfZHJlc3MpIChhbmQgYSBmb2xsb3ctdXAKW2FydGljbGVdKGh0dHBzOi8vd3d3LmluZGVwZW5kZW50LmNvLnVrL2xpZmUtc3R5bGUvZmFzaGlvbi9uZXdzL3RoZS1kcmVzcy1hY3R1YWwtY29sb3VyLWJyYW5kLWFuZC1wcmljZS1kZXRhaWxzLXJldmVhbGVkLTEwMDc0Njg2Lmh0bWwpKQoKCiMjIENvbG9yIFNwYWNlcwoKIyMjIFJHQiBhbmQgSFNWIENvbG9yIFNwYWNlcwoKQ29tcHV0ZXIgbW9uaXRvcnMgYW5kIHByb2plY3RvcnMgd29yayBpbiB0ZXJtcyBvZiByZWQsIGdyZWVuLCBhbmQgYmx1ZQpsaWdodC4KCkFtb3VudHMgb2YgcmVkIGdyZWVuIGFuZCBibHVlIChhbmQgYWxwaGEgbGV2ZWwpIGFyZSBzdG9yZWQgYXMgaW50ZWdlcnMKaW4gdGhlIHJhbmdlIGJldHdlZW4gMCBhbmQgMjU1ICg4LWJpdCBieXRlcykuCgpgYGB7cn0KY29scyA8LSBjKCJyZWQiLCAiZ3JlZW4iLCAiYmx1ZSIsICJ5ZWxsb3ciLCAiY3lhbiIsICJtYWdlbnRhIikKcmdiY29scyA8LSBjb2wycmdiKGNvbHMpOyBjb2xuYW1lcyhyZ2Jjb2xzKSA8LSBjb2xzCnJnYmNvbHMKYGBgCgpDb2xvcnMgYXJlIG9mdGVuIGVuY29kZWQgaW4gX2hleGFkZWNpbWFsXyBmb3JtIChiYXNlIDE2KS4KCmBgYHtyfQpyZ2IoMSwgMCwgMCkgICAjIyBwdXJlIHJlZApyZ2IoMCwgMCwgMSkgICAjIyBwdXJlIGJsdWUKYGBgCgpgYGB7cn0KcmdiKDI1NSwgMCwgMCwgbWF4Q29sb3JWYWx1ZSA9IDI1NSkKcmdiKDAsIDAsIDI1NSwgbWF4Q29sb3JWYWx1ZSA9IDI1NSkKYGBgCgpIdWUsIHNhdHVyYXRpb24sIHZhbHVlIChIU1YpIGlzIGEgc2ltcGxlIHRyYW5zZm9ybWF0aW9uIG9mIFJHQi4KCmBgYHtyfQpyZ2IyaHN2KHJnYmNvbHMpCmBgYAoKSFNWIGlzIGEgbGl0dGxlIG1vcmUgY29udmVuaWVudCBzaW5jZSBpdCBhbGxvd3MgdGhlIGh1ZSB0byBiZQpjb250cm9sbGVkIHNlcGFyYXRlbHkuCgpBIHNpbXBsZSBjb2xvciBwaWNrZXI6IDxodHRwczovL3d3dy5nb29nbGUuY29tL3NlYXJjaD9xPWNvbG9yK3BpY2tlcj4KCkJ1dCBzYXR1cmF0aW9uIGFuZCB2YWx1ZSBhdHRyaWJ1dGVzIGFyZSBub3QgcGFydGljdWxhcmx5IHVzZWZ1bCBmb3IKc3BlY2lmeWluZyBjb2xvcnMgdGhhdCB3b3JrIHdlbGwgcGVyY2VwdHVhbGx5LgoKQSBjb2xvciB3aGVlbCBvZiBmdWxseSBzYXR1cmF0ZWQgY29sb3JzOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQp3aGVlbCA8LSBmdW5jdGlvbihjb2wsIHJhZGl1cyA9IDEsIC4uLikKICAgIHBpZShyZXAoMSwgbGVuZ3RoKGNvbCkpLAogICAgICAgIGNvbCA9IGNvbCwgcmFkaXVzID0gcmFkaXVzLCAuLi4pCndoZWVsKHJhaW5ib3coNikpCmBgYAoKUmVtb3Zpbmcgc2F0dXJhdGlvbjoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KbGlicmFyeShjb2xvcnNwYWNlKQp3aGVlbChkZXNhdHVyYXRlKHJhaW5ib3coNikpKQpgYGAKCkZ1bGx5IHNhdHVyYXRlZCB5ZWxsb3cgaXMgYnJpZ2h0ZXIgdGhhbiByZWQsIHdoaWNoIGlzIGJyaWdodGVyIHRoYW4KYmx1ZS4KCgojIyMgSENMIENvbG9yIFNwYWNlCgpUaGUgX3JhaW5ib3dfIHBhbGV0dGUgb2YgdGhlIGNvbG9yIHdoZWVsIGlzIG9mdGVuIGEgZGVmYXVsdCBpbgp2aXN1YWxpemF0aW9uIHN5c3RlbXMuCgpBIFtibG9nIHBvc3RdKGh0dHBzOi8vd3d3LnplaWxlaXMub3JnL25ld3MvZW5kcmFpbmJvdy8pCmlsbHVzdHJhdGVzIHdoeSB0aGlzIGlzIGEgYmFkIGlkZWEuCgpUaGUgcmFpbmJvdyBodWVzIGFyZSBldmVubHkgc3BhY2VkIGluIHRoZSBjb2xvciBzcGVjdHJ1bSwgYnV0IGNocm9tYQphbmQgbHVtaW5hbmNlIGFyZSBub3QuCgpMdW1pbmFuY2UgaW4gcGFydGljdWxhciBpcyBub3QgbW9ub3RvbmUgYWNyb3NzIHRoZSBwYWxldHRlLgoKYGBge3IsIGZpZy53aWR0aCA9IDEwLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KcmdiMmhjbCA8LSBmdW5jdGlvbihjb2wpIHsKICAgICMjIGlnbm9yZXMgYWxwaGEKICAgIGNvbCA8LSBSR0IodChjb2xbMSA6IDMsIF0pIC8gMjU1KQogICAgY29sIDwtIGFzKGNvbCwgInBvbGFyTFVWIikKICAgIGNvbCA8LSB0KGNvbEBjb29yZHNbLCAzIDogMSwgZHJvcCA9IEZBTFNFXSkKICAgIHJvd25hbWVzKGNvbCkgPC0gdG9sb3dlcihyb3duYW1lcyhjb2wpKQogICAgY29sCn0KY29sMmhjbCA8LSBmdW5jdGlvbihjb2wpIHJnYjJoY2woY29sMnJnYihjb2wpKQpwYWwgPC0gZnVuY3Rpb24oY29sLCBib3JkZXIgPSAibGlnaHQgZ3JheSIsIC4uLikgewogICAgbiA8LSBsZW5ndGgoY29sKQogICAgcGxvdCgwLCAwLCB0eXBlID0gIm4iLCB4bGltID0gYygwLCAxKSwgeWxpbSA9IGMoMCwgMSksCiAgICAgICAgIGF4ZXMgPSBGQUxTRSwgeGxhYiA9ICIiLCB5bGFiID0gIiIsIC4uLikKICAgIHJlY3QoKDAgOiAobiAtIDEpKSAvIG4sIDAsICgxIDogbikgLyBuLCAxLCBjb2wgPSBjb2wsIGJvcmRlciA9IGJvcmRlcikKfQoKcGFyKG1mcm93ID0gYygxLCAyKSkKcGFsKHJhaW5ib3coNiksIG1haW4gPSAiU2F0dXJhdGVkIFJhaW5ib3ciKQpwYWwoZGVzYXR1cmF0ZShyYWluYm93KDYpKSwgbWFpbiA9ICJEZXNhdHVyYXRlZCIpCmBgYApgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CnNwZWNwbG90KHJhaW5ib3coNiksIGxkdyA9IDQpCmBgYAoKVGhlIGh1ZSwgY2hyb21hLCBsdW1pbmFuY2UKKFtIQ0xdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0hDTF9jb2xvcl9zcGFjZSkpIHNwYWNlIGFsbG93cwpzZXBhcmF0ZSBjb250cm9sIG9mOgoKKiBfSHVlXywgdGhlIGNvbG9yLgoKKiBfQ2hyb21hXywgdGhlIGFtb3VudCBvZiB0aGUgY29sb3IuCgoqIF9MdW1pbmFuY2VfLCBvciBwZXJjZWl2ZWQgYnJpZ2h0bmVzcy4KCkhDTCBtYWtlcyBpdCBlYXNpZXIgdG8gY3JlYXRlIHBlcmNlcHR1YWxseSB1bmlmb3JtIGNvbG9yIHBhbGV0dGVzLgoKQSBwYWxldHRlIHdpdGggY29uc3RhbnQgY2hyb21hLCBldmVubHkgc3BhY2VkIGh1ZXMgYW5kIGV2ZW5seSBzcGFjZWQKbHVtaW5hbmNlIHZhbHVlczoKCmBgYHtyLCBmaWcud2lkdGggPSAxMCwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CnJhaW42IDwtIGhjbChzZXEoMCwgMzYwICogNSAvIDYsIGxlbiA9IDYpLCA1MCwgc2VxKDYwLCA4MCwgbGVuID0gNikpCnBhcihtZnJvdyA9IGMoMSwgMikpCnBhbChyYWluNiwgbWFpbiA9ICJVbmlmb3JtIFJhaW5ib3ciKQpwYWwoZGVzYXR1cmF0ZShyYWluNiksIG1haW4gPSAiRGVzYXR1cmF0ZWQiKQpgYGAKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0Kc3BlY3Bsb3QocmFpbjYsIGx3ZCA9IDQpCmBgYAoKRm9yIGEgZnVsbHkgc2F0dXJhdGVkIHJlZCwgdmFyeWluZyBvbmx5IGNocm9tYSB0byByZWR1Y2UgdGhlIGFtb3VudCBvZgpjb2xvcjoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KcmVkX2hjbCA8LSBsaXN0KGggPSAxMi4xNzM5NSwgYyA9IDE3OS4wNDA3NiwgbCA9IDUzLjI0MDU5KQpzcGVjcGxvdChoY2wocmVkX2hjbCRoLCByZWRfaGNsJGMgKiBzZXEoMCwgMSwgbGVuID0gMTApLCByZWRfaGNsJGwpLCBsd2QgPSA0KQpgYGAKCkZvciBhIGdpdmVuIGh1ZSwgbm90IGFsbCBjb21iaW5hdGlvbnMgb2YgY2hyb21hIGFuZCBsdW1pbmFuY2UgYXJlCnBvc3NpYmxlLgoKSW4gcGFydGljdWxhciwgZm9yIGxvdyBsdW1pbmFuY2UgdmFsdWVzIHRoZSBhdmFpbGFibGUgY2hyb21hIHJhbmdlIGlzCmxpbWl0ZWQuCgpUaGUgW2BnZ3Bsb3RgIGJvb2tdKGh0dHBzOi8vZ2dwbG90Mi1ib29rLm9yZy8pIGNvbnRhaW5zIHRoaXMKdmlzdWFsaXphdGlvbiBvZiB0aGUgSENMIHNwYWNlLgoKKiBIdWUgaXMgbWFwcGVkIHRvIGFuZ2xlLgoqIENocm9tYSBpcyBtYXBwZWQgdG8gcmFkaXVzLgoqIEx1bWluYW5jZSBpcyBtYXBwZWQgdG8gZmFjZXRzLgoKVGhlIG9yaWdpbnMgd2l0aCB6ZXJvIGNocm9tYSBhcmUgc2hhZGVzIG9mIGdyZXkuCgpbSENMIGNvbG9yIHBpY2tlci5dKGh0dHA6Ly9oY2x3aXphcmQub3JnOjMwMDAvaGNsY29sb3JwaWNrZXIvKQoKYGBge3IsIG91dC53aWR0aCA9IDUwMCwgZWNobyA9IEZBTFNFfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhJTUcoImdncGxvdDJfaGNsc3BhY2UucG5nIikpCmBgYAoKSENMIGlzIGEgdHJhbnNmb3JtYXRpb24gb2YgdGhlCltDSUVsdXZdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0NJRUxVVikgY29sb3Igc3BhY2UgZGVzaWduZWQKZm9yIHBlcmNlcHR1YWwgdW5pZm9ybWl0eS4KClRoZSBkZWZpbml0aW9uIG9mIHRoZSBsdW1pbmFuY2UgdGFrZXMgaW50byBhY2NvdW50IHRoZSBsaWdodApzZW5zaXRpdml0eSBvZiBhIHN0YW5kYXJkIGh1bWFuIG9ic2VydmVyIGF0IHZhcmlvdXMgd2F2ZSBsZW5ndGhzLgoKTGlnaHQgc2Vuc2l0aXZpdHkgZm9yIGRpZmZlcmVudCB3YXZlIGxlbmd0aHMgaW4gZGF5bGlnaHQgY29uZGl0aW9ucwoocGhvdG9waWMgdmlzaW9uKSBhbmQgdW5kZXIgZGFyayBhZGFwdGVkIGNvbmRpdGlvbnMgKHNjb3RvcGljIHZpc2lvbik6Cgo8IS0tCmh0dHA6Ly9saWdodC1tZWFzdXJlbWVudC5jb20vaW1hZ2VzL3NwZWN0cmFsLWx1bWlub3VzLWVmZmljaWVuY3ktZnVuY3Rpb24uanBnCi0tPgoKYGBge3IsIGVjaG8gPSBGQUxTRX0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoSU1HKCJsaWdodC1zZW5zaXRpdml0eS5qcGciKSkKYGBgCgo8IS0tCmh0dHBzOi8vd3d3LmhhbmRwcmludC5jb20vSFAvV0NML2NvbG9yMS5odG1sCmh0dHA6Ly9saWdodC1tZWFzdXJlbWVudC5jb20vc3BlY3RyYWwtc2Vuc2l0aXZpdHktb2YtZXllLyAtLT4KCgojIyMgTXVuc2VsbCBDb2xvciBTcGFjZQoKQW5vdGhlciBjb2xvciBzcGFjZSwgc2ltaWxhciB0byBIQ0wsIGlzIHRoZQpbTXVuc2VsbCBzeXN0ZW1dKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL011bnNlbGxfY29sb3Jfc3lzdGVtKQpkZXZlbG9wZWQgaW4gdGhlIGVhcmx5IDE5MDBzLgoKVGhpcyBzeXN0ZW0gdXNlcyBhIEh1ZSwgVmFsdWUsIENocm9tYSBlbmNvZGluZy4KClRoZSBbYG11bnNlbGxgIHBhY2thZ2VdKGh0dHBzOi8vZ2l0aHViLmNvbS9jd2lja2hhbS9tdW5zZWxsKSBwcm92aWRlcwphbiBSIGludGVyZmFjZSBhbmQgaXMgdXNlZCBpbiBgZ2dwbG90YC4KCk11bnNlbGwgc3BlY2lmaWNhdGlvbnMgYXJlIG9mIHRoZSBmb3JtIGAiSCBWL0MiYCwgc3VjaCBhcwpgNVIgNS8xMGAuCgpQb3NzaWJsZSBodWVzIGFyZQoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRX0KbGlicmFyeShtdW5zZWxsLCBleGNsdWRlID0gImRlc2F0dXJhdGUiKQptbnNsX2h1ZXMoKQpgYGAKCmBWYCBzaG91bGQgYmUgYW4gaW50ZWdlciBiZXR3ZWVuIDAgYW5kIDEwLgoKYENgIHNob3VsZCBiZSBhbiBldmVuIGludGVnZXIgbGVzcyB0aGFuIDI0LCBidXQgbm90IGFsbCBjb21iaW5hdGlvbnMKICBhcmUgcG9zc2libGUuCgpBZGp1c3RpbmcgY29sb3JzIGluIHRoZSB2YWx1ZSwgY2hyb21hLCBhbmQgaHVlIGRpbWVuc2lvbnM6CgpgYGB7ciBtdW5zZWxsLWJsdWVzLCBlY2hvID0gRkFMU0V9Cm15X2JsdWUgPC0gIjVQQiA1LzgiCnBsb3RfbW5zbChjKAogICAgbGlnaHRlcihteV9ibHVlLCAyKSwgICAgICAgICAgICAgIG15X2JsdWUsICBkYXJrZXIobXlfYmx1ZSwgMiksCiAgICBtdW5zZWxsOjpkZXNhdHVyYXRlKG15X2JsdWUsIDIpLCAgbXlfYmx1ZSwgIHNhdHVyYXRlKG15X2JsdWUsIDIpLAogICAgcnlnYnAobXlfYmx1ZSwgMiksICAgICAgICAgICAgICAgIG15X2JsdWUsICBwYmd5cihteV9ibHVlLCAyKSkpCmBgYApgYGB7ciBtdW5zZWxsLWJsdWVzLCBldmFsID0gRkFMU0V9CmBgYAoKQ3JlYXRpbmcgc2NhbGVzOgoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA0fQpwbG90X21uc2woc2FwcGx5KDAgOiA2LCBkYXJrZXIsIGNvbCA9ICI1UEIgNy80IikpICsgZmFjZXRfd3JhcCh+IG51bSwgbnJvdyA9IDEpCmBgYAoKRXhhbWluaW5nIGF2YWlsYWJsZSBjb2xvcnM6CgpgYGB7ciwgd2FybmluZyA9IEZBTFNFfQpodWVfc2xpY2UoIjVSIikKYGBgCgpgYGB7cn0KdmFsdWVfc2xpY2UoNSkKYGBgCgpDb21wbGVtZW50YXJ5IGNvbG9yczoKCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gNH0KY29tcGxlbWVudF9zbGljZSgiNVIiKQpgYGAKCiMjIE9wcG9uZW50IFByb2Nlc3MgVGhlb3J5CgpUaGUgX09wcG9uZW50IFByb2Nlc3MgTW9kZWxfIG9mIHZpc2lvbiBzYXlzIHRoYXQgdGhlIGJyYWluIGRpdmlkZXMgdGhlCnZpc3VhbCBzaWduYWwgYW1vbmcgdGhyZWUgb3Bwb3NpbmcgY29udHJhc3QgcGFpcnM6CgoqIGJsYWNrIGFuZCB3aGl0ZTsKCiogcmVkIGFuZCBncmVlbjsKCiogeWVsbG93IGFuZCBibHVlLgoKVGhlIGJsYWNrL3doaXRlIHBhaXIgY29ycmVzcG9uZHMgdG8gbHVtaW5hbmNlIGluIEhDTAoKSHVlIGFuZCBjaHJvbWEgaW4gSENMIHNwYW4gdGhlIHR3byBjaHJvbWF0aWMgYXhlcy4KClRoZSBsdW1pbmFuY2UgYXhpcyBoYXMgaGlnaGVyIHJlc29sdXRpb24gdGhhbiB0aGUgdHdvIGNocm9tYXRpYyBheGVzLgoKVGhlIG1ham9yIGZvcm0gb2YgY29sb3IgdmlzaW9uIGRlZmljaWVuY3kgcmVmbGVjdHMgYW4gaW5hYmlsaXR5IHRvCmRpc3Rpbmd1aXNoIGRpZmZlcmVuY2VzIGFsb25nIHRoZSByZWQvZ3JlZW4gYXhpcy4KCkltcGFpcm1lbnQgYWxvbmcgdGhlIHllbGxvdy9ibHVlIGF4aXMgZG9lcyBvY2N1ciBhcyB3ZWxsIGJ1dCBpcyBtdWNoCnJhcmVyLgoKCiMjIENvbnRyYXN0IGFuZCBDb21wYXJpc29ucwoKVmlzaW9uIHJlYWN0cyB0byBkaWZmZXJlbmNlcywgbm90IGFic29sdXRlcy4KCiogQ29udHJhc3QgaXMgdmVyeSBpbXBvcnRhbnQuCgoqIENvbnRleHQgaXMgdmVyeSBpbXBvcnRhbnQuCgpTbWFsbCBkaWZmZXJlbmNlcyBpbiBzaGFkaW5nIG9yIGh1ZSBjYW4gYmUgcmVjb2duaXplZCB3aGVuIG9iamVjdHMgYXJlCmNvbnRpZ3VvdXMgYnV0IGJlIG11Y2ggaGFyZGVyIHRvIHNlZSB3aGVuIHRoZXkgYXJlIHNlcGFyYXRlZC4KCiFbXShodHRwczovL29wZW5pLm5sbS5uaWguZ292L2ltZ3MvNTEyLzM5OS8zODUyMDA0L1BNQzM4NTIwMDRfMTQ3MS0yMTA1LTE0LVMxNS1TMTItMi5wbmcpCgpfU2ltdWx0YW5lb3VzIGJyaWdodG5lc3MgY29udHJhc3RfOiBhIGdyZXkgcGF0Y2ggb24gYSBkYXJrIGJhY2tncm91bmQKbG9va3MgbGlnaHRlciB0aGFuIHRoZSBzYW1lIGdyZXkgcGF0Y2ggb24gYSBsaWdodCBiYWNrZ3JvdW5kLgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpwbG90KDAsIDAsIHR5cGUgPSAibiIsIHhsaW0gPSBjKDAsIDEpLCB5bGltID0gYygwLCAxKSwKICAgICBheGVzID0gRkFMU0UsIHhsYWIgPSAiIiwgeWxhYiA9ICIiKQpyZWN0KDAsIDAsIDAuNSwgMSwgY29sID0gImxpZ2h0Z3JleSIsIGJvcmRlciA9IE5BKQpyZWN0KDAuNSwgMCwgMSwgMSwgY29sID0gImRhcmtncmV5IiwgYm9yZGVyID0gTkEpCnJlY3QoMC4yLCAwLjMsIDAuMywgMC43LCBjb2wgPSAiZ3JleSIsIGJvcmRlciA9IE5BKQpyZWN0KDAuNywgMC4zLCAwLjgsIDAuNywgY29sID0gImdyZXkiLCBib3JkZXIgPSBOQSkKYGBgCgpBbiBleGFtcGxlIHdlIHNhdyBlYXJsaWVyOgoKIVtdKGByIElNRygiY2hlc3MxLnBuZyIpYCkgIVtdKGByIElNRygiY2hlc3MyLnBuZyIpYCkKClNvbWUgbW9yZSBhcmUgYXZhaWxhYmxlCltoZXJlXShodHRwczovL3dlYi5hcmNoaXZlLm9yZy93ZWIvMjAxOTAxMDYxODQ0NTcvaHR0cHM6Ly9ibG9nLnJldm9sdXRpb25hbmFseXRpY3MuY29tLzIwMTgvMDgvbHVtaW5hbmNlLWlsbHVzaW9uLmh0bWwpLAppbmNsdWRpbmc6CgpgYGB7ciwgZWNobyA9IEZBTFNFfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhJTUcoImx1bWluYW5pbS5naWYiKSkKYGBgCgo8IS0tIFRyeWluZyBmb3IgY29sb3IgY29udHJhc3QgYXMgaW4gV2FyZSBGaWcgNC4xNSBvbiBwIDExNSBidXQgbm90CmdldHRpbmcgdGhlcmUgLS0+CgpgYGB7ciwgZXZhbCA9IEZBTFNFLCBlY2hvID0gRkFMU0V9CnBsb3QoMCwgMCwgdHlwZSA9ICJuIiwgeGxpbSA9IGMoMCwgMSksIHlsaW0gPSBjKDAsIDEpLAogICAgIGF4ZXMgPSBGQUxTRSwgeGxhYiA9ICIiLCB5bGFiID0gIiIpCnJlY3QoMCwgMCwgMC41LCAxLCBjb2wgPSBoY2woMCksIGJvcmRlciA9IE5BKQpyZWN0KDAuNSwgMCwgMSwgMSwgY29sID0gaGNsKDEyMCksIGJvcmRlciA9IE5BKQpwaW5rMSA8LSByZ2IoMSwgMTkyIC8gMjU1LCAyMjAgLyAyNTUpCnJlY3QoMC4yLCAwLjMsIDAuMywgMC43LCBjb2wgPSBwaW5rMSwgYm9yZGVyID0gTkEpCnJlY3QoMC43LCAwLjMsIDAuOCwgMC43LCBjb2wgPSBwaW5rMSwgYm9yZGVyID0gTkEpCmBgYAoKVXNpbmcgbHVtaW5hbmNlIG9yIGdyZXkgc2NhbGUgYWxvbmUgZG9lcyBub3Qgd29yayB3ZWxsIGZvciBlbmNvZGluZwpjYXRlZ29yaWNhbCB2YXJpYWJsZXMgYWdhaW5zdCBhIGtleS4KCkdyZXkgc2NhbGUgY2FuIGJlIGVmZmVjdGl2ZSBmb3Igc2hvd2luZyBjb250aW51b3VzIHRyYW5zaXRpb25zIGluCnBzZXVkby1jb2xvciBpbWFnZXMuCgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmZpbGxlZC5jb250b3VyKHZvbGNhbm8sIGNvbG9yLnBhbGV0dGUgPSBncmV5LmNvbG9ycykKYGBgCgpHcmV5IHNjYWxlIGlzIGxlc3MgZWZmZWN0aXZlIGZvciBzZWdtZW50ZWQgbWFwcywgb3IgY2hvcm9wbGV0aCBtYXBzOwpvbmx5IGEgZmV3IGxldmVscyBjYW4gYmUgYWNjdXJhdGVseSBkZWNvZGVkLgoKCiMjIEludGVyYWN0aW9ucyB3aXRoIFNpemUsIEJhY2tncm91bmQgYW5kIFByb3hpbWl0eQoKYGBge3IsIGVjaG8gPSBGQUxTRSwgZXZhbCA9IEZBTFNFfQpsaWJyYXJ5KGxhdHRpY2VFeHRyYSkKbGlicmFyeShtYXBwcm9qKQpkYXRhKFVTQ2FuY2VyUmF0ZXMpCm1hcHBsb3Qocm93bmFtZXMoVVNDYW5jZXJSYXRlcykgfiBsb2cocmF0ZS5tYWxlKSwgZGF0YSA9IFVTQ2FuY2VyUmF0ZXMsCiAgICAgICAgIyNjb2xyYW1wID0gZ3JleS5jb2xvcnMsCiAgICAgICAgYm9yZGVyID0gTkEsCiAgICAgICAgbWFwID0gbWFwKCJjb3VudHkiLCBwbG90ID0gRkFMU0UsIGZpbGwgPSBUUlVFLAogICAgICAgICAgICAgICAgICBwcm9qZWN0aW9uID0gIm1lcmNhdG9yIikpCmBgYAoKRm9yIHNtYWxsIGl0ZW1zIG1vcmUgY29udHJhc3QgYW5kIG1vcmUgc2F0dXJhdGVkIGNvbG9ycyBhcmUgbmVlZGVkOgoKYGBge3IsIGZpZy5oZWlnaHQgPSA3LCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KeCA8LSBydW5pZig2LCAwLjEsIDAuOSkKeSA8LSBydW5pZig2LCAwLjEsIDAuOSkKY29scyA8LSBjKCJyZWQiLCAiZ3JlZW4iLCAiYmx1ZSIsICJ5ZWxsb3ciLCAiY3lhbiIsICJtYWdlbnRhIikKZiA8LSBmdW5jdGlvbihzaXplID0gMSwgYmxhY2sgPSBGQUxTRSkgewogICAgcGxvdCh4LCB5LCB0eXBlID0gIm4iLCB4bGltID0gYygwLCAxKSwgeWxpbSA9IGMoMCwgMSkpCiAgICBpZiAoYmxhY2spIHJlY3QoMCwgMCwgMSwgMSwgY29sID0gImJsYWNrIikKICAgIHRleHQoeCwgeSwgY29scywgY29sID0gY29scywgY2V4ID0gc2l6ZSkKfQpvcGFyIDwtIHBhcihtZnJvdyA9IGMoMiwgMikpCmYoMSkKZig0KQpmKDEsIFRSVUUpCmYoNCwgVFJVRSkKcGFyKG9wYXIpCmBgYAoKVmFyaWF0aW9ucyBpbiBsdW1pbmFuY2UgYXJlIHBhcnRpY3VsYXJseSBoZWxwZnVsIGZvciBzZWVpbmcgZmluZQpzdHJ1Y3R1cmUsIHN1Y2ggYXMgc21hbGwgdGV4dCBvciBzbWFsbCBzeW1ib2xzOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpwbG90KDAsIHR5cGUgPSAibiIsIHhsaW0gPSBjKDAsIDEpLCB5bGltID0gYygwLCAxKSwKICAgICBheGVzID0gRkFMU0UsIHhsYWIgPSAiIiwgeWxhYiA9ICIiKQpyZWN0KDAsIDAsIDEsIDEsIGNvbCA9IGhjbCgwKSkgIyMgZGVmYXVsdHM6IGMgPSAzNSwgbCA9IDg1CnFiZiA8LSAiVGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyAuLi4iCnRleHQoMC41LCAwLjMsIGxhYmVsID0gcWJmLCBjb2wgPSBoY2woMTgwKSkgICAgICAgIyMgaHVlCnRleHQoMC41LCAwLjUsIGxhYmVsID0gcWJmLCBjb2wgPSBoY2woMCwgYyA9IDcwKSkgIyMgY2hyb21hCnRleHQoMC41LCAwLjcsIGxhYmVsID0gcWJmLCBjb2wgPSBoY2woMCwgbCA9IDUwKSkgIyMgbHVtaW5hbmNlCmBgYAoKQ2hyb21pbmFuY2UgKGh1ZSBhbmQgY2hyb21hKSBkaWZmZXJlbmNlcyBhbG9uZSBhcmUgbm90IHN1ZmZpY2llbnQgZm9yCnNtYWxsIGl0ZW1zLgoKV2FyZSByZWNvbW1lbmRzIGEgbHVtaW5hbmNlIGNvbnRyYXN0IG9mIGF0IGxlYXN0IDM6MSBmb3Igc21hbGwgdGV4dDsKMTA6MSBpcyBwcmVmZXJhYmxlLgoKU21hbGwgYXJlYXMgYWxzbyBuZWVkIHZhcmlhdGlvbiBpbiBtb3JlIHRoYW4gaHVlOgoKYGBge3IsIGVjaG8gPSBGQUxTRX0KcGxvdCgwLCAwLCB0eXBlID0gIm4iLCB4bGltID0gYygwLCAxKSwgeWxpbSA9IGMoMCwgMSksCiAgICAgYXhlcyA9IEZBTFNFLCB4bGFiID0gIiIsIHlsYWIgPSAiIikKCnBhbDAgPC0gZnVuY3Rpb24oY29sLCBib3R0b20gPSAwLCB0b3AgPSAxKSB7CiAgICBuIDwtIGxlbmd0aChjb2wpCiAgICByZWN0KCgwIDogKG4gLSAxKSkgLyBuLCBib3R0b20sCiAgICAgICAgICgxIDogbikgLyBuLCB0b3AsIGNvbCA9IGNvbCwgYm9yZGVyID0gImxpZ2h0Z3JleSIpCn0KCnBhbDAocmFpbmJvd19oY2woOCksIDAsIDAuMikKcGFsMChyYWluYm93X2hjbCg4KSwgMC4zLCAwLjMxKQpwYWwwKHJhaW5ib3coOCksIDAuNSwgMC43KQpwYWwwKHJhaW5ib3coOCksIDAuOCwgMC44MSkKYGBgCgpDb250cmFzdGluZyBib3JkZXJzIGNhbiBoZWxwIGZvciBsYXJnZXIgYXJlYXMgd2l0aCBzaW1pbGFyIGx1bWluYW5jZToKCiogb3V0bGluZXMgb24gdGV4dDsKCiogYm9yZGVycyBvbiBzeW1ib2xzOwoKKiBib3JkZXJzIG9uIHJlZ2lvbnMsIGUuZyBpbiBbbWFwc10oaHR0cHM6Ly9jb2xvcmJyZXdlcjIub3JnKQoKYGBge3IsIGZpZy53aWR0aCA9IDEwLCBlY2hvID0gRkFMU0V9Ck4gPC0gbnJvdyh0cmVlcykKb3AgPC0gcGFsZXR0ZShyYWluYm93KE4sIGVuZCA9IDAuOSkpCmYgPC0gZnVuY3Rpb24oZmcsIG1haW4gPSAiIikKICAgIHdpdGgodHJlZXMsCiAgICAgICAgIHN5bWJvbHMoSGVpZ2h0LCBWb2x1bWUsIGNpcmNsZXMgPSBHaXJ0aCAvIDE2LAogICAgICAgICAgICAgICAgIGluY2hlcyA9IEZBTFNFLCBiZyA9IDEgOiBOLAogICAgICAgICAgICAgICAgIGZnID0gZmcsIG1haW4gPSBtYWluKSkKcGFyKG1mcm93ID0gYygxLCAyKSkKZihOQSwgIk5vIEJvcmRlcnMiKQpmKCJncmF5MzAiLCAiR3JleSBCb3JkZXJzIikKcGFsZXR0ZShvcCkKYGBgCgoKIyMgQ29sb3IgU3BlY2lmaWNhdGlvbiBpbiBSCgpgYGB7ciwgaW5jbHVkZSA9IEZBTFNFfQpuY29scyA8LSBsZW5ndGgoY29sb3JzKCkpCmBgYAoKQSBsYXJnZSBudW1iZXIgb2YgX25hbWVkIGNvbG9yc18gYXJlIGF2YWlsYWJsZSAoY3VycmVudGx5IGByIG5jb2xzYCkuCgpTb21lIGV4YW1wbGVzOgpgYGB7cn0KY29sMnJnYigicmVkIikKY29sMnJnYigiZm9yZXN0Z3JlZW4iKQpjb2wycmdiKCJkZWVwc2t5Ymx1ZSIpCmNvbDJyZ2IoImZpcmVicmljayIpCmBgYAoKVGhlc2Ugd2lsbCBzaG93IHNvbWUgZGV0YWlsczoKCmBgYHtyLCBldmFsID0gRkFMU0V9CmNvbG9ycygpCmRlbW8oY29sb3JzKQpgYGAKClRoZSBhdmFpbGFibGUgbmFtZWQgY29sb3JzIGZvbGxvdyBhIHdpZGVseSB1c2VkCltzdGFuZGFyZF0oaHR0cHM6Ly93d3cudzNzY2hvb2xzLmNvbS9jb2xvcnMvY29sb3JzX3gxMS5hc3ApLgoKVGhlc2UgY29sb3JzIGluY2x1ZGUgdGhlIDE0MCBbX3dlYgpjb2xvcnNfXShodHRwczovL2h0bWxjb2xvcmNvZGVzLmNvbS9jb2xvci1uYW1lcy8pIHN1cHBvcnRlZCBvbiBtb2Rlcm4KYnJvd3NlcnMuCgpJbmRpdmlkdWFsIGNvbG9ycyBjYW4gYWxzbyBiZSBzcGVjaWZpZWQgdXNpbmcgYHJnYigpYCBvciBgaGNsKClgIG9yIGFzCmhleGFkZWNpbWFsIHNwZWNpZmljYXRpb25zLgoKYGBge3J9CmxpYnJhcnkoY29sb3JzcGFjZSkKaGV4MlJHQigiI0ZGMDAwMCIpCmBgYAoKVXNpbmcgY29sb3Igc3BhY2VzOgoKYGBge3J9CnJnYigxLCAwLCAwKQpyZ2IoMjU1LCAwLCAwLCBtYXggPSAyNTUpCnJnYjJoc3YoY29sMnJnYigicmVkIikpCmBgYAoKQ29udmVydGluZyB0byBIQ0w6CmBgYHtyfQpyZ2IyaGNsIDwtIGZ1bmN0aW9uKGNvbCkgewogICAgIyMgaWdub3JlcyBhbHBoYQogICAgY29sIDwtIFJHQih0KGNvbFsxIDogMywgXSkgLyAyNTUpCiAgICBjb2wgPC0gYXMoY29sLCAicG9sYXJMVVYiKQogICAgY29sIDwtIHQoY29sQGNvb3Jkc1ssIDMgOiAxLCBkcm9wID0gRkFMU0VdKQogICAgcm93bmFtZXMoY29sKSA8LSB0b2xvd2VyKHJvd25hbWVzKGNvbCkpCiAgICBjb2wKfQpjb2wyaGNsIDwtIGZ1bmN0aW9uKGNvbCkgcmdiMmhjbChjb2wycmdiKGNvbCkpCgpjb2wyaGNsKCJyZWQiKQpjb2wyaGNsKCJncmVlbiIpCmNvbDJoY2woImJsdWUiKQpjb2wyaGNsKCJ5ZWxsb3ciKQpjb2wyaGNsKCJjeWFuIikKY29sMmhjbCgibWFnZW50YSIpCgpoY2woMTIuMTcsIDE3OS4wNCwgNTMuMjQpCmBgYAoKQ29sb3IgcGlja2VycyBjYW4gaGVscDoKCiogR29vZ2xlIHNlYXJjaDogaHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS9zZWFyY2g/cT1jb2xvcitwaWNrZXIKCiogYGNvbG91clBpY2tlcigpYCBpbiB0aGUgIGBjb2xvdXJwaWNrZXJgIHBhY2thZ2UuCgpXaGVuIGEgc2V0IG9mIGNvbG9ycyBpcyBuZWVkZWQgdG8gZW5jb2RlIHZhcmlhYmxlIHZhbHVlcyBpdCBpcyB1c3VhbGx5CmJlc3QgdG8gdXNlIGEgc3VpdGFibGUgX3BhbGV0dGVfLgoKCiMjIENvbG9yIFBhbGV0dGVzCgpfQ29sb3IgcGFsZXR0ZXNfIGFyZSBjb2xsZWN0aW9ucyBvZiBjb2xvcnMgdGhhdCB3b3JrIHdlbGwgdG9nZXRoZXIuCgpJdCBpcyB1c2VmdWwgdG8gZGlzdGluZ3Vpc2ggdGhyZWUga2luZHMgb2YgcGFsZXR0ZXM6CgoqIHF1YWxpdGF0aXZlL2NhdGVnb3JpY2FsIHBhbGV0dGVzOwoKKiBzZXF1ZW50aWFsIHBhbGV0dGVzOwoKKiBkaXZlcmdpbmcgcGFsZXR0ZXMuCgpUb29scyBmb3Igc2VsZWN0aW5nIHBhbGV0dGVzIGluY2x1ZGU6CgoqIFtDb2xvckJyZXdlcl0oaHR0cHM6Ly9jb2xvcmJyZXdlcjIub3JnKTsgYXZhaWxhYmxlIGluIHRoZQogIGBSQ29sb3JCcmV3ZXJgIHBhY2thZ2UuCgoqIFtIQ0wgV2l6YXJkXShodHRwczovL2hjbHdpemFyZC5vcmcvKTsgYWxzbyBhdmFpbGFibGUgYXMgYGhjbHdpemFyZGAKICBpbiB0aGUgYGNvbG9yc3BhY2VgIHBhY2thZ2UuCgpBCiAgW2Jsb2cgcG9zdF0oaHR0cHM6Ly9mbG93aW5nZGF0YS5jb20vMjAxOC8wNC8xMi92aXN1YWxpemF0aW9uLWNvbG9yLXBpY2tlci1iYXNlZC1vbi1wZXJjZXB0aW9uLXJlc2VhcmNoLykKICB3aXRoIHNvbWUgZnVydGhlciBvcHRpb25zLgoKU29tZSBbY3VycmVudCBVUyBnb3Zlcm5tZW50CiAgd29ya10oaHR0cHM6Ly9kZXNpZ25zeXN0ZW0uZGlnaXRhbC5nb3YvY29tcG9uZW50cy9jb2xvcnMvKSBvbiBjb2xvcgogIHBhbGV0dGVzOyBbbW9yZSBleHRlbnNpdmUKICBub3Rlc10oaHR0cHM6Ly94ZGdvdi5naXRodWIuaW8vZGF0YS1kZXNpZ24tc3RhbmRhcmRzL2NvbXBvbmVudHMvY29sb3JzKQogIGFuZCBbY29kZV0oaHR0cHM6Ly9naXRodWIuY29tL3Vzd2RzL3Vzd2RzKS4KClIgY29sb3IgcGFsZXR0ZSBmdW5jdGlvbnM6CgoqIGByYWluYm93KClgCiogYGhlYXQuY29sb3JzKClgCiogYHRlcnJhaW4uY29sb3JzKClgCiogYHRvcG8uY29sb3JzKClgCiogYGNtLmNvbG9ycygpYAoqIGBncmV5LmNvbG9ycygpYAoqIGBncmF5LmNvbG9ycygpYAoKVGhlc2UgYWxsIHRha2UgdGhlIG51bWJlciBvZiBjb2xvcnMgYXMgYW4gYXJndW1lbnQsIGFzIHdlbGwgYXMgc29tZQphZGRpdGlvbmFsIG9wdGlvbmFsIGFyZ3VtZW50cy4KClRoZSBgaGNsLmNvbG9yKClgIGZ1bmN0aW9uIHByb3ZpZGVzIGFjY2VzcyB0byB0aGUgcGFsZXR0ZXMgZGVmaW5lZCBpbgp0aGUgYGNvbG9yc3BhY2VgIHBhY2thZ2UuCgpgY29sb3JSYW1wUGFsZXR0ZSgpYCBjYW4gYmUgdXNlZCB0byBjcmVhdGUgYSBfcGFsZXR0ZSBmdW5jdGlvbl8gdGhhdAppbnRlcnBvbGF0ZXMgYmV0d2VlbiBhIHNldCBvZiBjb2xvcnMgdXNpbmcKCiogUkdCIHNwYWNlIG9yIExhYiAoc2ltaWxhciB0byBIQ0wpIHNwYWNlOwoKKiBsaW5lYXIgb3Igc3BsaW5lIGludGVycG9sYXRpb24uCgpgYGB7cn0KcndiIDwtIGNvbG9yUmFtcFBhbGV0dGUoCiAgICBjKCJyZWQiLCAid2hpdGUiLCAiYmx1ZSIpKQpyd2IoNSkKYGBgCmBgYHtyIHBhbGV0dGUtZnVuY3Rpb24tZXhhbXBsZSwgZXZhbCA9IEZBTFNFfQpmaWxsZWQuY29udG91cih2b2xjYW5vLAogICAgICAgICAgICAgICBjb2xvci5wYWxldHRlID0gcndiLAogICAgICAgICAgICAgICBhc3AgPSAxKQpgYGAKYGBge3IgcGFsZXR0ZS1mdW5jdGlvbi1leGFtcGxlLCBlY2hvID0gRkFMU0V9CmBgYAoKV2l0aCBtb3JlIHBlcmNlcHR1YWxseSBjb21wYXJhYmxlIGV4dHJlbWVzIChmcm9tIHRoZSBCbHVlLVJlZCBwYWxldHRlCm9mIEhDTCBXaXphcmQpOgoKYGBge3IgcGFsZXR0ZS1mdW5jdGlvbi1tdXRlZDEsIGV2YWwgPSBGQUxTRX0KcndiMSA8LSBjb2xvclJhbXBQYWxldHRlKAogICAgYygiIzhFMDYzQiIsICJ3aGl0ZSIsICIjMDIzRkE1IikpCmZpbGxlZC5jb250b3VyKHZvbGNhbm8sCiAgICAgICAgICAgICAgIGNvbG9yLnBhbGV0dGUgPSByd2IxLAogICAgICAgICAgICAgICBhc3AgPSAxKQpgYGAKYGBge3IgcGFsZXR0ZS1mdW5jdGlvbi1tdXRlZDEsIGVjaG8gPSBGQUxTRX0KYGBgCgpBbiBhbHRlcm5hdGl2ZSB1c2VzIHRoZSBgbXV0ZWRgIGZ1bmN0aW9uIGZyb20gcGFja2FnZSBgc2NhbGVzYDoKCmBgYHtyIHBhbGV0dGUtZnVuY3Rpb24tbXV0ZWQyLCBldmFsID0gRkFMU0V9CnJ3YjIgPC0gY29sb3JSYW1wUGFsZXR0ZSgKICAgIGMoc2NhbGVzOjptdXRlZCgicmVkIiksCiAgICAgICJ3aGl0ZSIsCiAgICAgIHNjYWxlczo6bXV0ZWQoImJsdWUiKSkpCmZpbGxlZC5jb250b3VyKHZvbGNhbm8sCiAgICAgICAgICAgICAgIGNvbG9yLnBhbGV0dGUgPSByd2IyLAogICAgICAgICAgICAgICBhc3AgPSAxKQpgYGAKYGBge3IgcGFsZXR0ZS1mdW5jdGlvbi1tdXRlZDIsIGVjaG8gPSBGQUxTRX0KYGBgCgpNb3N0IGJhc2UgYW5kIGBsYXR0aWNlYCBmdW5jdGlvbnMgYWxsb3cgYSB2ZWN0b3Igb2YgY29sb3JzIHRvIGJlCnNwZWNpZmllZC4KClNvbWUsIGxpa2UgYGZpbGxlZC5jb250b3VyKClgIGFuZCBgbGV2ZWxwbG90KClgIGFsbG93IGEgcGFsZXR0ZSBmdW5jdGlvbgp0byBiZSBwcm92aWRlZC4KCmBnZ3Bsb3RgIHByb3ZpZGVzIGEgZnJhbWV3b3JrIGZvciBzcGVjaWZ5aW5nIHBhbGV0dGUgZnVuY3Rpb25zIHRvIHVzZQp3aXRoIGBzY2FsZV9jb2xvcl94eXooKWAgYW5kIGBzY2FsZV9maWxsX3h5eigpYCBmdW5jdGlvbnMuCgpQYWNrYWdlcyBsaWtlIGBjb2xvcnNwYWNlYCBhbmQgYHZpcmlkaXNgIHByb3ZpZGUgYWRkaXRpb25hbApgc2NhbGVfY29sb3JfeHl6KClgIGFuZCBgc2NhbGVfZmlsbF94eXooKWAgZnVuY3Rpb25zLgoKCiMjIFJDb2xvckJyZXdlciBQYWxldHRlcwoKVGhlIGF2YWlsYWJsZSBwYWxldHRlczoKCmBgYHtyIGJyZXdlci1wYWxsZXRlcywgZXZhbCA9IEZBTFNFfQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKZGlzcGxheS5icmV3ZXIuYWxsKCkKYGBgClBhbGV0dGVzIGluIHRoZSBmaXJzdCBncm91cCBhcmUgX3NlcXVlbnRpYWxfLgoKVGhlIHNlY29uZCBncm91cCBhcmUgX3F1YWxpdGF0aXZlXy4KClRoZSB0aGlyZCBncm91cCBhcmUgX2RpdmVyZ2luZ18uCmBgYHtyIGJyZXdlci1wYWxsZXRlcywgZWNobyA9IEZBTFNFLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gOH0KYGBgCgpUaGUgYCJCbHVlcyJgICBwYWxldHRlOgoKYGBge3IgYmx1ZXMtcGFsZXR0ZSwgZXZhbCA9IEZBTFNFfQpkaXNwbGF5LmJyZXdlci5wYWwoOSwgIkJsdWVzIikKYGBgCmBgYHtyIGJsdWVzLXBhbGV0dGUsIGVjaG8gPSBGQUxTRX0KYGBgCgpBcyBSR0IgdmFsdWVzOgoKYGBge3J9CmJyZXdlci5wYWwoOSwgIkJsdWVzIikKYGBgCgpUaGUgcGFsZXR0ZXMgYXJlIGxpbWl0ZWQgdG8gYSBtYXhpbXVtIG51bWJlciBvZiBsZXZlbHMuCgpUbyBvYnRhaW4gbW9yZSBsZXZlbHMgeW91IGNhbiBpbnRlcnBvbGF0ZS4KCmBgYHtyfQpicmV3ZXIucGFsKDEwLCAiQmx1ZXMiKQoKcGJyYmwgPC0gY29sb3JSYW1wUGFsZXR0ZShicmV3ZXIucGFsKDksICJCbHVlcyIpLCBpbnRlcnBvbGF0ZSA9ICJzcGxpbmUiKQpwYnJibApwYnJibCgxMCkKYGBgCgoKIyMgQ29sb3JzcGFjZSBQYWxldHRlcwoKVGhlIGBjb2xvcnNwYWNlYCBwYWNrYWdlIHByb3ZpZGVzIGEgd2lkZSByYW5nZSBvZiBwcmUtZGVmaW5lZCBwYWxldHRlczoKCmBgYHtyLCBmaWcud2lkdGggPSAxMH0KbGlicmFyeShjb2xvcnNwYWNlKQpoY2xfcGFsZXR0ZXMocGxvdCA9IFRSVUUpCmBgYAoKQSBwYXJ0aWN1bGFyIG51bWJlciBvZiBjb2xvcnMgZnJvbSBvbmUgb2YgdGhlc2UgcGFsZXR0ZXMgY2FuIGJlCm9idGFpbmVkIHdpdGgKCmBgYHtyfQpxdWFsaXRhdGl2ZV9oY2woNCwgcGFsZXR0ZSA9ICJEYXJrIDMiKQpgYGAKClRoZSBmdW5jdGlvbnMgYHNlcXVlbnRpYWxfaGNsKClgIGFuZCBgZGl2ZXJnaW5nX2hjbCgpYCBhcmUgYW5hbG9nb3VzLgoKRm9yIHVzZSB3aXRoIGBnZ3Bsb3QyYCB0aGUgcGFja2FnZSBwcm92aWRlcyBzY2FsZSBmdW5jdGlvbnMgbGlrZQpgc2NhbGVfZmlsbF9kaXNjcmV0ZV9xdWFsaXRhdGl2ZSgpYCBhbmQKYHNjYWxlX2NvbG9yX2NvbnRpbnVvdXNfc2VxdWVudGlhbCgpYC4KCkEgW3BhY2thZ2UKdmlnbmV0dGVdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3BhY2thZ2U9Y29sb3JzcGFjZS92aWduZXR0ZXMvY29sb3JzcGFjZS5odG1sKQpwcm92aWRlcyBtb3JlIGRldGFpbHMgYW5kIGJhY2tncm91bmQuCgoKIyMgVmlyaWRpcyBQYWxldHRlcwoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgZWNobyA9IEZBTFNFfQpuX2NvbCA8LSAxMjgKaW1nIDwtIGZ1bmN0aW9uKG9iaiwgbmFtKSB7CiAgaW1hZ2Uoc2VxX2Fsb25nKG9iaiksIDEsIGFzLm1hdHJpeChzZXFfYWxvbmcob2JqKSksIGNvbCA9IG9iaiwKICAgICAgICBtYWluID0gbmFtLCB5bGFiID0gIiIsIHhheHQgPSAibiIsIHlheHQgPSAibiIsICBidHkgPSAibiIpCn0KCmxpYnJhcnkodmlyaWRpcykKCnBhcihtZnJvdyA9IGMoOCwgMSksIG1hciA9IHJlcCgxLCA0KSkKaW1nKHJldih2aXJpZGlzKG5fY29sKSksICJ2aXJpZGlzIikKaW1nKHJldihtYWdtYShuX2NvbCkpLCAibWFnbWEiKQppbWcocmV2KHBsYXNtYShuX2NvbCkpLCAicGxhc21hIikKaW1nKHJldihpbmZlcm5vKG5fY29sKSksICJpbmZlcm5vIikKaW1nKHJldihjaXZpZGlzKG5fY29sKSksICJjaXZpZGlzIikKaW1nKHJldihtYWtvKG5fY29sKSksICJtYWtvIikKaW1nKHJldihyb2NrZXQobl9jb2wpKSwgInJvY2tldCIpCmltZyhyZXYodHVyYm8obl9jb2wpKSwgInR1cmJvIikKYGBgCgpUaGVzZSBhcmUgcHJvdmlkZWQgaW4gcGFja2FnZSBgdmlyaWRpc0xpdGVgLgoKUGFsZXR0ZSBmdW5jdGlvbnMgYXJlIGB2aXJpZGlzKClgLCBgbWFrbygpYCwgZXRjLi4KClRoZXkgYXJlIGFsc28gYXZhaWxhYmxlIHZpYSB0aGUgYGhjbC5jb2xvcnMoKWAgZnVuY3Rpb24uCgpGb3IgdXNlIGluIGBnZ3Bsb3RgIHRoZXkgY2FuIGJlIHNwZWNpZmllZCBpbiB0aGUgdmlyaWRpcyBjb2xvciBzY2FsZQpmdW5jdGlvbnMuCgoKIyMgUGFsZXR0ZXMgaW4gUiBHcmFwaGljcwoKYGBge3IsIGVjaG8gPSBGQUxTRX0KaWYgKCEgZmlsZS5leGlzdHMoIlBsYXlmYWlyLmRhdCIpKQogICAgZG93bmxvYWQuZmlsZSgiaHR0cDovL3d3dy5zdGF0LnVpb3dhLmVkdS9+bHVrZS9kYXRhL1BsYXlmYWlyIiwKICAgICAgICAgICAgICAgICAgIlBsYXlmYWlyLmRhdCIpCgpQbGF5ZmFpciA8LSByZWFkLnRhYmxlKCJQbGF5ZmFpci5kYXQiKQpQbGF5ZmFpciRjaXR5IDwtIHJvd25hbWVzKFBsYXlmYWlyKQpyb3duYW1lcyhQbGF5ZmFpcikgPC0gTlVMTApgYGAKCjwhLS0KU3BlY2lmeWluZyBhIHZlY3RvciBvZiBjb2xvcnMgaW4gYmFzZSBhbmQgYGxhdHRpY2VgIGdyYXBoaWNzOgoKYGBge3IsIGV2YWwgPSBGQUxTRX0Kd2l0aChQbGF5ZmFpciwgcGxvdChwb3B1bGF0aW9uLCBjb2wgPSByYWluYm93KGxlbmd0aChwb3B1bGF0aW9uKSkpKQp3aXRoKFBsYXlmYWlyLCBwbG90KHBvcHVsYXRpb24sIGNvbCA9IHJhaW5ib3coMikpKQp3aXRoKFBsYXlmYWlyLCBiYXJwbG90KHBvcHVsYXRpb24sIGNvbCA9IHJhaW5ib3cobGVuZ3RoKHBvcHVsYXRpb24pKSkpCndpdGgoUGxheWZhaXIsIGJhcnBsb3QocG9wdWxhdGlvbiwgY29sID0gcmFpbmJvdygyKSkpCgp3aXRoKFBsYXlmYWlyLAogICAgIGJhcmNoYXJ0KHJlb3JkZXIoY2l0eSwgcG9wdWxhdGlvbikgfiBwb3B1bGF0aW9uLAogICAgICAgICAgICAgIGNvbCA9IHJhaW5ib3cobGVuZ3RoKHBvcHVsYXRpb24pKSkpCndpdGgoUGxheWZhaXIsCiAgICAgYmFyY2hhcnQocmVvcmRlcihjaXR5LCBwb3B1bGF0aW9uKSB+IHBvcHVsYXRpb24sIGNvbCA9IHJhaW5ib3coMikpKQpgYGAKCkEgYmV0dGVyIGFwcHJvYWNoIGZvciBgbGF0dGljZWAgaXMgdG8gdXNlIGBwYXIuc2V0dGluZ3NgOyB0aGlzIHdpbGwKbWFrZSBzdXJlIGEgbGVnZW5kIGlzIGNvbnNpc3RlbnQgd2l0aCB0aGUgcGxvdCBjb2xvcnMuCgpgZmlsbGVkLmNvbnRvdXJgIGFuZCBgbGV2ZWxwbG90YCBhbHNvIGFjY2VwdCBhIHBhbGV0dGUgZnVuY3Rpb246CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpmaWxsZWQuY29udG91cih2b2xjYW5vLCBjb2xvci5wYWxldHRlID0gdGVycmFpbi5jb2xvcnMpCmxldmVscGxvdCh2b2xjYW5vLCBjb2wucmVnaW9ucyA9IHRlcnJhaW4uY29sb3JzKQpgYGAKLS0+CgpgZ2dwbG90YCB1c2VzIGBzY2FsZV9jb2xvcl94eXooKWAgb3IgYHNjYWxlX2ZpbGxfeHl6KClgLgoKRm9yIGRpc2NyZXRlIHNjYWxlcyB0aGUgY2hvaWNlcyBmb3IgYHh5emAgaW5jbHVkZQoKKiBgaHVlYCB2YXJpZXMgdGhlIGh1ZSAoZGVmYXVsdCBmb3IgdW5vcmRlcmVkIGZhY3RvcnMpOwoqIGBncmV5YCB1c2VzIGdyZXkgc2NhbGU7CiogYGJyZXdlcmAgdXNlcyBDb2xvckJyZXdlciBwYWxldHRlczsKKiBgbWFudWFsYCBhbGxvd3MgZXhwbGljaXQgc3BlY2lmaWNhdGlvbi4KKiBgdmlyaWRpc19kYCBmb3IgYW4gYWx0ZXJuYXRpdmUgcGFsZXR0ZSBmYW1pbHkgKGRlZmF1bHQgZm9yIG9yZGVyZXIgZmFjdG9ycykuCgpGb3IgY29udGludW91cyBzY2FsZXMgdGhlIGNob2ljZXMgZm9yIGB4eXpgIGluY2x1ZGUKCiogYGdyYWRpZW50YCBpbnRlcnBvbGF0ZXMgYmV0d2VlbiB0d28gY29sb3JzLCBsb3cgYW5kIGhpZ2g7CiogYGdyYWRpZW50MmAgaW50ZXJwb2xhdGVzIGJldHdlZW4gdGhyZWUgY29sb3JzLCBsb3csIG1lZGl1bSwgaGlnaDsKKiBgZ3JhZGllbnRuYCBpbnRlcnBvbGF0ZXMgYmV0d2VlbiBhIHZlY3RvciBvZiBjb2xvcnM7CiogYGRpc3RpbGxlcmAgZm9yIENvbG9yQnJld2VyIHBhbGV0dGVzLgoqIGB2aXJpZGlzX2NgICBmb3IgYW4gYWx0ZXJuYXRpdmUgcGFsZXR0ZSBmYW1pbHkuCgpPdGhlcnMgYXJlIGF2YWlsYWJsZSBpbiBwYWNrYWdlcyBzdWNoIGFzIGBjb2xvcnNwYWNlYC4KClRoZSBkZWZhdWx0IHF1YWxpdGF0aXZlIGFuZCBzZXF1ZW50aWFsIGRpc2NyZXRlIHBhbGV0dGVzOgoKYGBge3IsIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gNCwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmxpYnJhcnkoZ2FwbWluZGVyKQpnYXBfMjAwNyA8LSBmaWx0ZXIoZ2FwbWluZGVyLCB5ZWFyID09IDIwMDcpIHw+IHNsaWNlX21heChwb3AsIG4gPSAyMCkKcCA8LSBtdXRhdGUoZ2FwXzIwMDcsIGNvdW50cnkgPSByZW9yZGVyKGNvdW50cnksIHBvcCkpIHw+CiAgICBnZ3Bsb3QoYWVzKHggPSBnZHBQZXJjYXAsIHkgPSBsaWZlRXhwLCBmaWxsID0gY29udGluZW50KSkgKwogICAgc2NhbGVfc2l6ZV9hcmVhKG1heF9zaXplID0gMTApICsKICAgIHNjYWxlX3hfbG9nMTAoKSArCiAgICBnZW9tX3BvaW50KHNpemUgPSA0LCBzaGFwZSA9IDIxKSArCiAgICBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSA0KSkpCnAxIDwtIHAgKyBnZ3RpdGxlKCJIdWUiKQpwMiA8LSBwICsgc2NhbGVfZmlsbF92aXJpZGlzX2QoKSArIGdndGl0bGUoIlZpcmlkaXMiKQpsaWJyYXJ5KHBhdGNod29yaykKcDEgKyBwMgpgYGAKCkRpc2NyZXRlIGV4YW1wbGVzIGZvciBgYnJld2VyYCwgYGNvbG9yc3BhY2VgIGFuZCBgbWFudWFsYDoKCmBgYHtyLCBmaWcud2lkdGggPSA5LCBmaWcuaGVpZ2h0ID0gNiwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CnAxIDwtIHAgKyBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlNldDEiKSArCiAgICBnZ3RpdGxlKCJCcmV3ZXIgU2V0MSIpCnAyIDwtIHAgKyBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlNldDIiKSArCiAgICBnZ3RpdGxlKCJCcmV3ZXIgU2V0MiIpCnAzIDwtIHAgKyBzY2FsZV9maWxsX2Rpc2NyZXRlX3F1YWxpdGF0aXZlKCJEYXJrIDMiKSArCiAgICBnZ3RpdGxlKCJDb2xvcnNwYWNlIERhcmsgMyIpCnA0IDwtIHAgKyBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKEFmcmljYSA9ICJyZWQiLCBBc2lhID0gImJsdWUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBbWVyaWNhcyA9ICJncmVlbiIsIEV1cm9wZSA9ICJncmV5IikpICsKICAgIGdndGl0bGUoIk1hbnVhbCIpCihwMSArIHAyKSAvIChwMyArIHA0KQpgYGAKClRoZSBkZWZhdWx0IGZvciBjb250aW51b3VzIHNjYWxlcyBpcyBgZ3JhZGllbnRgIGZyb20gYSBkYXJrIGJsdWUgdG8gYQpsaWdodCBibHVlOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpWIDwtIGRhdGEuZnJhbWUoeCA9IHJlcChzZXFfbGVuKG5yb3codm9sY2FubykpLCBuY29sKHZvbGNhbm8pKSwKICAgICAgICAgICAgICAgIHkgPSByZXAoc2VxX2xlbihuY29sKHZvbGNhbm8pKSwgZWFjaCA9IG5yb3codm9sY2FubykpLAogICAgICAgICAgICAgICAgeiA9IGFzLnZlY3Rvcih2b2xjYW5vKSkKcCA8LSBnZ3Bsb3QoViwgYWVzKHgsIHksIGZpbGwgPSB6KSkgKyBnZW9tX3Jhc3RlcigpICsgY29vcmRfZml4ZWQoKQpwCmBgYAoKU29tZSBhbHRlcm5hdGl2ZXM6CgpgYGB7ciBhbHQtY29udGludW91cywgZWNobyA9IEZBTFNFLCBmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodCA9IDl9CmBgYApgYGB7ciBhbHQtY29udGludW91cywgZXZhbCA9IEZBTFNFLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KcDEgPC0gcCArIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKAogICAgICAgICAgICAgIGxvdyA9ICJyZWQiLCBtaWQgPSAid2hpdGUiLCBoaWdoID0gImJsdWUiLAogICAgICAgICAgICAgIG1pZHBvaW50ID0gbWVkaWFuKHZvbGNhbm8pKSArCiAgICBnZ3RpdGxlKCJSZWQtV2hpdGUtQmx1ZSBHcmFkaWVudCIpCnAyIDwtIHAgKyBzY2FsZV9maWxsX3ZpcmlkaXNfYygpICsKICAgIGdndGl0bGUoIlZpcmlkaXMiKQpwMyA8LSBwICsgc2NhbGVfZmlsbF9ncmFkaWVudG4oCiAgICAgICAgICAgICAgY29sb3JzID0gdGVycmFpbi5jb2xvcnMoOCkpICsKICAgIGdndGl0bGUoIlRlcnJhaW4iKQoKdmJpbnMgPC0gc2VxKDgwLCBieSA9IDIwLCBsZW5ndGgub3V0ID0gNykKbmMgPC0gbGVuZ3RoKHZiaW5zKSAtIDEKcDQgPC0gZ2dwbG90KG11dGF0ZShWLCB6ID0gZmN0X3JldihjdXQoeiwgdmJpbnMpKSksCiAgICAgICAgICAgICBhZXMoeCwgeSwgZmlsbCA9IHopKSArCiAgICBnZW9tX3Jhc3RlcigpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHJldih0ZXJyYWluLmNvbG9ycyhuYykpKSArCiAgICBnZ3RpdGxlKCJEaXNjcmV0aXplZCBUZXJyYWluIikKKHAxICsgcDIpIC8gKHAzICsgcDQpCmBgYAoKRGlzY3JldGl6aW5nIGEgY29udGludW91cyByYW5nZSB0byBhIG1vZGVzdCBudW1iZXIgb2YgbGV2ZWxzIGNhbiBtYWtlCmRlY29kaW5nIHZhbHVlcyBmcm9tIGEgbGVnZW5kIGVhc2llci4KCjwhLS0KIyMgZmlsbGVkLmNvbnRvdXIodm9sY2FubywgY29sb3IucGFsZXR0ZSA9IHRlcnJhaW4uY29sb3JzLCBubGV2ZWxzID0gbmMpCi0tPgoKCiMjIFJlZHVjZWQgQ29sb3IgVmlzaW9uCgpDb2xvciB2aXNpb24gZGVmaWNpZW5jeSBhZmZlY3RzIGFib3V0IDEwJSBvZiBtYWxlcywgYSBzbWFsbGVyCnBlcmNlbnRhZ2Ugb2YgZmVtYWxlcy4KClRoZSBtb3N0IGNvbW1vbiBmb3JtIGlzIHJlZHVjZWQgYWJpbGl0eSB0byBkaXN0aW5ndWlzaCByZWQgYW5kIGdyZWVuLgoKU29tZSB3ZWIgc2l0ZXMgcHJvdmlkZSB0b29scyB0byBzaW11bGF0ZSBob3cgYSB2aXN1YWxpemF0aW9uIHdvdWxkCmxvb2sgdG8gYSBjb2xvciB2aXNpb24gZGVmaWNpZW50IHZpZXdlci4KClRoZSBSIHBhY2thZ2VzIGBkaWNocm9tYXRgLCBgY29sb3JzcGFjZWAsIGFuZCBgY29sb3JibGluZHJgIHByb3ZpZGUKdG9vbHMgZm9yIHNpbXVsYXRpbmcgaG93IGNvbG9ycyB3b3VsZCBsb29rIHRvIGEgY29sb3IgdmlzaW9uIGRlZmljaWVudAp2aWV3ZXIgZm9yIHRocmVlIG1ham9yIHR5cGVzIG9mIGNvbG9yIHZpc2lvbiBkZWZpY2llbmN5OgoKKiBkZXV0ZXJhbm9tYWx5IChncmVlbiBjb25lIGNlbGxzIGRlZmVjdGl2ZSk7CiogcHJvdGFub21hbHkgKHJlZCBjb25lIGNlbGxzIGRlZmVjdGl2ZSk7CiogdHJpdGFub21hbHkgKGJsdWUgY29uZSBjZWxscyBkZWZlY3RpdmUpLgoKQW4gYXJ0aWNsZSBleHBsYWluaW5nIHRoZSBjb2xvciB2aXNpb24gaW1wYWlybWVudCBzaW11bGF0aW9uIGlzCmF2YWlsYWJsZQpbaGVyZV0oaHR0cDovL2NvbG9yc3BhY2Uuci1mb3JnZS5yLXByb2plY3Qub3JnL2FydGljbGVzL2NvbG9yX3Zpc2lvbl9kZWZpY2llbmN5Lmh0bWwpCjwhLS0gaHR0cHM6Ly9hcnhpdi5vcmcvYWJzLzE5MDMuMDY0OTAgLS0+CgpVc2luZyBzb21lIHRvb2xzIGZyb20gcGFja2FnZXMgYGNvbG9yc3BhY2VgIGFuZCBgY29sb3JibGluZGVyYCB3ZSBjYW4Kc2ltdWxhdGUgd2hhdCBhIHBsb3Qgd291bGQgbG9vayBsaWtlIGluIGdyZXkgc2NhbGUgYW5kIHRvIHNvbWVvbmUKd2l0aCBzb21lIG9mIHRoZSBtYWpvciB0eXBlcyBvZiBjb2xvciBpbXBhaXJtZW50LgoKQSBwbG90IHdpdGggdGhlIGRlZmF1bHQgZGlzY3JldGUgY29sb3IgcGFsZXR0ZToKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KcCA8LSBnZ3Bsb3QoZ2FwXzIwMDcsIGFlcyhnZHBQZXJjYXAsIGxpZmVFeHAsIGNvbG9yID0gY29udGluZW50KSkgKwogICAgZ2VvbV9wb2ludChzaXplID0gNCkgKwogICAgc2NhbGVfeF9sb2cxMCgpICsKICAgIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSA0KSkpCnAKYGBgCgo8IS0tICMjIG5vbGludCBzdGFydDogb2JqZWN0X3VzYWdlIC0tPgpgYGB7ciBjdmQtZXhhbXBsZXMsIGZpZy53aWR0aCA9IDkuNSwgZmlnLmhlaWdodCA9IDYuNSwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmxpYnJhcnkoY29sb3JibGluZHIpCmxpYnJhcnkoY29sb3JzcGFjZSkKbGlicmFyeShncmlkKQpjb2xvcl9jaGVjayA8LSBmdW5jdGlvbihwKSB7CiAgICBwMSA8LSBlZGl0X2NvbG9ycyhwICsgZ2d0aXRsZSgiRGVzYXR1cmF0ZWQiKSwgZGVzYXR1cmF0ZSkKICAgIHAyIDwtIGVkaXRfY29sb3JzKHAgKyBnZ3RpdGxlKCJkZXV0YW4iKSwgZGV1dGFuKQogICAgcDMgPC0gZWRpdF9jb2xvcnMocCArIGdndGl0bGUoInByb3RhbiIpLCBwcm90YW4pCiAgICBwNCA8LSBlZGl0X2NvbG9ycyhwICsgZ2d0aXRsZSgidHJpdGFuIiksIHRyaXRhbikKICAgIGdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKHAxLCBwMiwgcDMsIHA0LCBucm93ID0gMikKfQpjb2xvcl9jaGVjayhwKQpgYGAKPCEtLSAjIyBub2xpbnQgZW5kIC0tPgoKRm9yIHRoZSBWaXJpZGlzIHBhbGV0dGU6CgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CnB2IDwtIHAgKyBzY2FsZV9jb2xvcl92aXJpZGlzX2QoKQpwdgpgYGAKCmBgYHtyLCBmaWcud2lkdGggPSA5LjUsIGZpZy5oZWlnaHQgPSA2LjUsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpjb2xvcl9jaGVjayhwdikKYGBgCgpUaGUgYHN3YXRjaHBsb3QoKWAgZnVuY3Rpb24gaW4gdGhlIGBjb2xvcnNwYWNlYCBwYWNrYWdlIGNhbiBiZSB1c2VkCndpdGggdGhlIGBjdmQgPSBUUlVFYCBhcmd1bWVudCB0byBzaW11bGF0ZSBob3cgc3BlY2lmaWMgcGFsZXR0ZXMgd29yawpmb3IgZGlmZmVyZW50IGNvbG9yIHZpc2lvbiBkZWZpY2llbmNpZXM6CgpgYGB7cn0KY29sb3JzcGFjZTo6c3dhdGNocGxvdChyYWluYm93KDYpLCBjdmQgPSBUUlVFKQpgYGAKCmBgYHtyfQpjb2xvcnNwYWNlOjpzd2F0Y2hwbG90KGhjbC5jb2xvcnMoNiksIGN2ZCA9IFRSVUUpCmBgYAoKCiMjIFR3byBJc3N1ZXMgdG8gV2F0Y2ggT3V0IEZvcgoKIyMjIE1pc3NpbmcgVmFsdWVzCgpJdCBpcyBjb21tb24gZm9yIGRlZmF1bHQgc2V0dGluZ3MgdG8gbm90IGFzc2lnbiBhIGNvbG9yIGZvciBtaXNzaW5nIHZhbHVlcy4KCkluIGEgY2hvcm9wbGV0aCBtYXAgd2l0aCAobWFkZS11cCkgZGF0YSB3aGVyZSBvbmUgc3RhdGUncyB2YWx1ZSBpcwptaXNzaW5nIHRoaXMgbWlnaHQgbm90IGJlIG5vdGljZWQuCgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9Cm0gPC0gbWFwX2RhdGEoInN0YXRlIikKZCA8LSBkYXRhLmZyYW1lKHJlZ2lvbiA9IHVuaXF1ZShtJHJlZ2lvbiksCiAgICAgICAgICAgICAgICB2YWwgPSBvcmRlcmVkKHNhbXBsZSgxIDogNCwgNDksIHJlcGxhY2UgPSBUUlVFKSkpCm0gPC0gbGVmdF9qb2luKG0sIGQsICJyZWdpb24iKQpwbSA8LSBnZ3Bsb3QobSkgKwogICAgZ2VvbV9wb2x5Z29uKGFlcyhsb25nLCBsYXQsIGdyb3VwID0gZ3JvdXAsIGZpbGwgPSB2YWwpKSArCiAgICBjb29yZF9tYXAoKSArCiAgICBnZ3RoZW1lczo6dGhlbWVfbWFwKCkKCmRubSA8LSBtdXRhdGUobSwgdmFsID0gcmVwbGFjZSh2YWwsIHJlZ2lvbiA9PSAibWljaGlnYW4iLCBOQSkpCnBtICUrJSBkbm0KYGBgClVubGVzcyB0aGUgdmlld2VyIGlzIHZlcnkgZmFtaWxpYXIgd2l0aCBVUyBnZW9ncmFwaHkuCgpPciBpcyBmcm9tIE1pY2hpZ2FuLgoKSW4gYSBzY2F0dGVycGxvdCB0aGVyZSBhcmUgZXZlbiBmZXdlciBjdWVzOgoKYGBge3IsIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gNCwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmduYyA8LSBtdXRhdGUoZ2FwXzIwMDcsIGNvbnRpbmVudCA9IHJlcGxhY2UoY29udGluZW50LCBjb3VudHJ5ID09ICJDaGluYSIsIE5BKSkKcHYgKwogICAgcHYgJSslIGduYwpgYGAKClNwZWNpZnlpbmcgYG5hLnZhbHVlID0gInJlZCJgLCBvciBzb21lIG90aGVyIGNvbG9yLCB3aWxsIG1ha2Ugc3VyZQpgTkFgIHZhbHVlcyBhcmUgdmlzaWJsZToKCmBgYHtyLCBmaWcud2lkdGggPSAxNCwgZmlnLmhlaWdodCA9IDUsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQoocG0gJSslIGRubSArCiBzY2FsZV9maWxsX3ZpcmlkaXNfZChuYS52YWx1ZSA9ICJyZWQiKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKSkgKwogICAgKHB2ICUrJSBnbmMgKyBzY2FsZV9jb2xvcl92aXJpZGlzX2QobmEudmFsdWUgPSAicmVkIikpCmBgYAoKVXNpbmcgb3V0bGluZXMgY2FuIGFsc28gaGVscDoKCmBgYHtyLCBmaWcud2lkdGggPSAxNCwgZmlnLmhlaWdodCA9IDUsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpwMSA8LSBwbSAlKyUgZG5tICsKICAgIGdlb21fcG9seWdvbihhZXMobG9uZywgbGF0LCBncm91cCA9IGdyb3VwKSwKICAgICAgICAgICAgICAgICBmaWxsID0gTkEsIGNvbG9yID0gImJsYWNrIiwgbGluZXdpZHRoID0gMC4xKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikKcDIgPC0gcHYgJSslIGduYyArCiAgICBnZW9tX3BvaW50KHNoYXBlID0gMjEsIGZpbGwgPSBOQSwgY29sb3IgPSAiYmxhY2siLCBzaXplID0gNCkKcDEgKyBwMgpgYGAKCkEgZmluYWwgcGxvdCBtaWdodCBoYW5kbGUgbWlzc2luZyB2YWx1ZXMgZGlmZmVyZW50bHksIGJ1dCBmb3IgaW5pdGlhbApleHBsb3JhdGlvbnMgaXQgaXMgYSBnb29kIGlkZWEgdG8gbWFrZSBzdXJlIHRoZXkgYXJlIGNsZWFybHkgdmlzaWJsZS4KCgojIyMgQWxpZ25pbmcgRGl2ZXJnaW5nIFBhbGV0dGVzCgpEaXZlcmdpbmcgcGFsZXR0ZXMgYXJlIHZlcnkgdXNlZnVsIGZvciBzaG93aW5nIGRldmlhdGlvbnMgYWJvdmUgb3IKYmVsb3cgYSBiYXNlbGluZS4KCmBgYHtyLCBmaWcuaGVpZ2h0ID0gMywgZmlnLndpZHRoID0gNywgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CnBhcihtZnJvdyA9IGMoMSwgMikpClJDb2xvckJyZXdlcjo6ZGlzcGxheS5icmV3ZXIucGFsKDcsICJQUkduIikKUkNvbG9yQnJld2VyOjpkaXNwbGF5LmJyZXdlci5wYWwoNiwgIlBSR24iKQpgYGAKCkZvciBhIGRpdmVyZ2luZyBwYWxldHRlIHRvIHdvcmsgcHJvcGVybHksIHRoZSBwYWxldHRlIGJhc2UgbGluZSBuZWVkcwp0byBiZSBhbGlnbmVkIHdpdGggdGhlIGRhdGEgYmFzZWxpbmUuCgpIb3cgdG8gZG8gdGhpcyB3aWxsIGRlcGVuZCBvbiB0aGUgcGFsZXR0ZSwgYnV0IHlvdSBkbyBuZWVkIHRvIGtlZXAKdGhpcyBpbiBtaW5kIHdoZW4gdXNpbmcgYSBkaXZlcmdpbmcgcGFsZXR0ZS4KCkp1c3QgdXNpbmcgYHNjYWxlX2ZpbGxfYnJld2VyYCBpcyBub3QgZW5vdWdoIHdoZW4gdGhlIHZhbHVlIHJhbmdlIGlzCm5vdCBzeW1tZXRyaWMgYXJvdW5kIHRoZSBiYXNlbGluZToKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KbSA8LSBtYXBfZGF0YSgic3RhdGUiKQpkIDwtIGRhdGEuZnJhbWUocmVnaW9uID0gdW5pcXVlKG0kcmVnaW9uKSwKICAgICAgICAgICAgICAgIHZhbCA9IG9yZGVyZWQoc2FtcGxlKCgxIDogNikgLSAzLCA0OSwgcmVwbGFjZSA9IFRSVUUpKSkKbSA8LSBsZWZ0X2pvaW4obSwgZCwgInJlZ2lvbiIpCnAgPC0gZ2dwbG90KG0pICsKICAgIGdlb21fcG9seWdvbihhZXMobG9uZywgbGF0LCBncm91cCA9IGdyb3VwLCBmaWxsID0gdmFsKSkgKwogICAgY29vcmRfbWFwKCkgKwogICAgZ2d0aGVtZXM6OnRoZW1lX21hcCgpICsKICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpCnAgKyBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlBSR24iKQpgYGAKClNldHRpbmcgdGhlIHNjYWxlIGxpbWl0cyBleHBsaWNpdGx5IGZvcmNlcyBhIDctY2F0ZWdvcnkgc3ltbWV0cmljIHNjYWxlCnRoYXQgYWxpZ25zIHRoZSB6ZXJvIHZhbHVlIHdpdGggdGhlIG1pZGRsZSBjb2xvcjoKCmBgYHtyLCB3YXJuaW5nID0gRkFMU0V9CmxpbXMgPC0gYXMuY2hhcmFjdGVyKC0zIDogMykKcCArIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUFJHbiIsCiAgICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBsaW1zKQpgYGAKClRoaXMgc2hvd3MgYSBjYXRlZ29yeSBpbiB0aGUgbGVnZW5kIGZvciAtMyB0aGF0IGRvZXMgbm90IGFwcGVhciBpbiB0aGUgbWFwLgoKVGhpcyBpcyBvZnRlbiB3aGF0IHlvdSB3YW50LgoKQnV0IGlmIHlvdSB3YW50IHRvIGRyb3AgdGhlIC0zIGNhdGVnb3J5LCBvbmUgb3B0aW9uIGlzIHRvIHVzZSBhIG1hbnVhbApzY2FsZToKCmBgYHtyfQp2YWxzIDwtIFJDb2xvckJyZXdlcjo6YnJld2VyLnBhbCg3LCAiUFJHbiIpCm5hbWVzKHZhbHMpIDwtIGxpbXMKcCArIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHZhbHNbLTFdKQpgYGAKCgojIyBCaXZhcmlhdGUgUGFsZXR0ZXMKCkl0IGlzIHBvc3NpYmxlIHRvIGVuY29kZSB0d28gdmFyaWFibGVzIGluIGEgcGFsZXR0ZS4KClNvbWUgc2FtcGxlIHBhbGV0dGVzOgoKYGBge3IsIGVjaG8gPSBGQUxTRX0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoSU1HKCJiaXZwYWwucG5nIikpCmBgYAoKQml2YXJpYXRlIHBhbGV0dGVzIGFyZSBzb21ldGltZXMgdXNlZCBpbiBbYml2YXJpYXRlIGNob3JvcGxldGgKbWFwc10oaHR0cHM6Ly93d3cuam9zaHVhc3RldmVucy5uZXQvY2FydG9ncmFwaHkvbWFrZS1hLWJpdmFyaWF0ZS1jaG9yb3BsZXRoLW1hcC8pLgoKU29tZSByZWNvbW1lbmRhdGlvbnMgZnJvbSBDeW50aGlhIEJyZXdlciBhcmUgYXZhaWxhYmxlCltoZXJlXShodHRwczovL3dlYi5hcmNoaXZlLm9yZy93ZWIvMjAyMTA0MTUxNjEwNDkvaHR0cDovL3d3dy5wZXJzb25hbC5wc3UuZWR1L2NhYjM4L0NvbG9yU2NoL1NjaGVtZXMuaHRtbCkuCgpBCltkaXNjdXNzaW9uXShodHRwczovL3dlYi5hcmNoaXZlLm9yZy93ZWIvMjAyMzA2MDcxOTExMzAvaHR0cHM6Ly9qdW5rY2hhcnRzLnR5cGVwYWQuY29tL2p1bmtfY2hhcnRzLzIwMjMvMDQvYml2YXJpYXRlLWNob3JvcGxldGhzLmh0bWwpCm9mIGEgcmVjZW50IGV4YW1wbGUuCgpVbmxlc3Mgb25lIHZhcmlhYmxlIGlzIGJpbmFyeSwgYW5kIHRoZSBwYWxldHRlIGlzIHZlcnkgd2VsbCBjaG9zZW4sIGl0CmlzIGhhcmQgdG8gZGVjb2RlIGEgdmlzdWFsaXphdGlvbiB1c2luZyBhIGJpbmFyeSBwYWxldHRlIHdpdGhvdXQKY29uc3RhbnRseSByZWZlcnJpbmcgdG8gdGhlIGtleS4KCgojIyBDdWx0dXJlLCBUcmFkaXRpb24sIGFuZCBDb252ZW50aW9ucwoKQ29sb3JzIGNhbiBoYXZlIGRpZmZlcmVudCBtZWFuaW5ncyBpbiBkaWZmZXJlbnQgY3VsdHVyZXMgYW5kIGF0CmRpZmZlcmVudCB0aW1lcy4KCiogQSBbdmlzdWFsCiAgcmVwcmVzZW50YXRpb25dKGh0dHBzOi8vd3d3LmluZm9ybWF0aW9uaXNiZWF1dGlmdWwubmV0L3Zpc3VhbGl6YXRpb25zL2NvbG91cnMtaW4tY3VsdHVyZXMvKQoKKiBBIFtibG9nCiAgcG9zdF0oaHR0cHM6Ly93d3cuaHVmZnBvc3QuY29tL2VudHJ5L3doYXQtY29sb3JzLW1lYW4taW4tb3RoZXJfYl85MDc4Njc0KQogIGF0IHRoZSBIdWZmaW5ndG9uIFBvc3QuCgoqIEEgW3NpbWlsYXIKICBwb3N0XShodHRwczovL3d3dy5zaHV0dGVyc3RvY2suY29tL2Jsb2cvY29sb3Itc3ltYm9saXNtLWFuZC1tZWFuaW5ncy1hcm91bmQtdGhlLXdvcmxkKQogIGF0IFNodXR0ZXJzdG9jay4KCkNvbnZlbnRpb25zIGNhbiBhbHNvIGdpdmUgY29sb3JzIHBhcnRpY3VsYXIgbWVhbmluZ3M6CgoqIHJlZC9ncmVlbiBpbiB0cmFmZmljIGxpZ2h0czsKCiogcmVkL2dyZWVuIGNvbG9ycyBpbiBtaWNyb2FycmF5IGhlYXRtYXBzOwoKKiByZWQgc3RhdGVzIGFuZCBibHVlIHN0YXRlczsKCiogcGluayBmb3IgYnJlYXN0IGNhbmNlcjsKCiogcGluayBmb3IgZ2lybHMsIGJsdWUgZm9yIGJveXM7CgoqIGJsYWNrIGZvciBtb3VybmluZy4KCgojIyMgVHJhZmZpYyBMaWdodHMKCltUcmFmZmljCiAgbGlnaHRzXShodHRwczovL3d3dy5hdXRvZXZvbHV0aW9uLmNvbS9uZXdzL2F1dG9tb3RpdmUtd2lraS13aHktYXJlLXRyYWZmaWMtbGlnaHRzLXJlZC15ZWxsb3ctYW5kLWdyZWVuLTQyNTU3Lmh0bWwpCiAgdXNlIHJlZC9ncmVlbiwgZXZlbiB0aG91Z2ggdGhpcyBpcyBhIG1ham9yIGF4aXMgb2YgY29sb3IgdmlzaW9uCiAgZGVmaWNpZW5jeS4KClRoZSBjb252ZW50aW9uIGNvbWVzIGZyb20gcmFpbHJvYWRzLgoKVGhlIHJlZCB1c2VkIGdlbmVyYWxseSBjb250YWlucyBzb21lIG9yYW5nZSBhbmQgdGhlIGdyZWVuIGNvbnRhaW5zCmJsdWUgdG8gaGVscCB3aXRoIHJlZC9ncmVlbiBjb2xvciB2aXNpb24gZGVmaWNpZW5jeS4KClBvc2l0aW9uIHByb3ZpZGVzIGFuIGFsdGVybmF0ZSBlbmNvZGluZy4gT3JpZW50YXRpb25zIGRvIHZhcnkuCgo8IS0tCmh0dHA6Ly9yZ2IudG8va2V5d29yZC8xNzc3OS8xL3RyYWZmaWMtbGlnaHQtZ3JlZW4KaHR0cDovL2VuY3ljb2xvcnBlZGlhLmNvbS9jYzA2MDUKCmh0dHBzOi8vd3d3LmF1dG9ldm9sdXRpb24uY29tL25ld3MvYXV0b21vdGl2ZS13aWtpLXdoeS1hcmUtdHJhZmZpYy1saWdodHMtcmVkLXllbGxvdy1hbmQtZ3JlZW4tNDI1NTcuaHRtbApodHRwczovL3d3dy50aHJpbGxpc3QuY29tL2NhcnMvbmF0aW9uL3RyYWZmaWMtbGlnaHQtY29sb3JzLWhpc3RvcnkKLS0+CgojIyMgTWljcm9hcnJheSBIZWF0bWFwcwoKLSBbTWljcm9hcnJheXNdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0ROQV9taWNyb2FycmF5KSBhcmUgdXNlZAogIGZvciB0aGUgYW5hbHlzaXMgb2YgZ2VuZS1sZXZlbCBjaGFuZ2VzIGFuZCBkaWZmZXJlbmNlcyBpbgogIGJpby1tZWRpY2FsIHJlc2VhcmNoLgotIER5ZXMgYXJlIHVzZWQgdGhhdCByZXN1bHQgaW4gZ2VuZXMgd2l0aCBhIGhpZ2ggcmVzcG9uc2UgYXBwZWFyaW5nIHJlZAogIGFuZCBnZW5lcyB3aXRoIGEgbG93IHJlc3BvbnNlIGFwcGVhcmluZyBncmVlbi4KLSBJbiBrZWVwaW5nIHdpdGggdGhpcyBwaHlzaWNhbCBjaGFyYWN0ZXJpc3RpYyBvZiBtaWNyb2FycmF5cywgYQogIGNvbW1vbiB2aXN1YWxpemF0aW9uIG9mIHRoZSBkYXRhIGlzIGFzIGEgcmVkL2dyZWVuIGhlYXQgbWFwLgoKPCEtLQpodHRwczovL3d3dy10Yy5wYnMub3JnL3dnYmgvbm92YS9lZHVjYXRpb24vYWN0aXZpdGllcy9wZGYvMzQxM19nZW5lc18wMi5wZGYKaHR0cHM6Ly9iaW9yYW1ibGUud29yZHByZXNzLmNvbS8yMDE1LzA4LzAzL2hlYXRtYXBzLXBhcnQtMy1ob3ctdG8tY3JlYXRlLWEtbWljcm9hcnJheS1oZWF0bWFwLXdpdGgtci8KLS0+CgoKIyMjIFJlZCBTdGF0ZXMgYW5kIEJsdWUgU3RhdGVzCgpJdCBpcyBub3cgc3RhbmRhcmQgaW4gdGhlIFVTIHRvIHJlZmVyIHRvIFJlcHVibGljYW4tbGVhbmluZyBzdGF0ZXMgYXMKcmVkIHN0YXRlcyBhbmQgRGVtb2NyYXQtbGVhbmluZyBzdGF0ZXMgYXMgYmx1ZSBzdGF0ZXMuCgpUaGlzIGlzIGEgZmFpcmx5IHJlY2VudCBjb252ZW50aW9uLCBkYXRpbmcgYmFjayB0byB0aGUgMjAwMApwcmVzaWRlbnRpYWwgZWxlY3Rpb24uCgpQcmlvciB0byAxOTgwIGl0IHdhcyBzb21ld2hhdCBtb3JlIHRyYWRpdGlvbmFsIHRvIHVzZSByZWQgZm9yIG1vcmUKbGVmdC1sZWFuaW5nIERlbW9jcmF0cy4KCkEgbWFwIG9mIHRoZSAxOTYwIGVsZWN0aW9uIHJlc3VsdHMgdXNlcyB0aGVzZSBtb3JlIHRyYWRpdGlvbmFsIGNvbG9ycy4KCmBgYHtyLCBlY2hvID0gRkFMU0V9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKElNRygiZTE5NjBfZWNtYXAuR0lGIikpCmBgYAoKSW4gMTk5NiB0aGUgX05ldyBZb3JrIFRpbWVzXyB1c2VkIGJsdWUgZm9yIERlbW9jcmF0LCByZWQgZm9yClJlcHVibGljYW4sIGJ1dCB0aGUgX1dhc2hpbmd0b24gUG9zdF8gdXNlZCB0aGUgb3Bwb3NpdGUgY29sb3Igc2NoZW1lLgoKVGhlIGxvbmcsIGRyYXduIG91dCBwcm9jZXNzIG9mIHRoZSAyMDAwIGVsZWN0aW9uIG1heSBoYXZlIGNvbnRyaWJ1dGVkCnRvIGZpeGluZyB0aGUgY29sb3Igc2NoZW1hIGF0IHRoZSBjdXJyZW50IGNvbnZlbnRpb24uCgoKIyMgTm90ZXMKClBvaW50cyBuZWVkIG1vcmUgc2F0dXJhdGlvbiwgbHVtaW5hbmNlIHRoYW4gYXJlYXMuCgpGYWxzZSBjb2xvciBpbWFnZXMgbWF5IGJlbmVmaXQgZnJvbSBkaXNjcmV0aXppbmcuCgpCaXZhcmlhdGUgZW5jb2RpbmdzIChlLmcuIGB4ID0gaHVlLCB5ID0gbHVtaW5hbmNlYCkgYXJlIHBvc3NpYmxlIGJ1dAp0cmlja3kgYW5kIG5vdCBvZnRlbiBhIGdvb2QgaWRlYS4gQmVzdCBpZiBhdCBsZWFzdCBvbmUgaXMgYmluYXJ5LgoKUHJvdmlkaW5nIGEgc2Vjb25kIGVuY29kaW5nLCBlLmcuIHNoYXBlLCBwb3NpdGlvbiBjYW4gaGVscCBmb3IgY29sb3IKdmlzaW9uIGRlZmljaWVudCB2aWV3ZXJzIGFuZCBwaG90b2NvcHlpbmcuCgpJbiBhcmVhIHBsb3RzIGFuZCBtYXBzIGl0IGlzIGltcG9ydGFudCB0byBkaXN0aW5ndWlzaCBiZXR3ZWVuIGJhc2UKbGluZSB2YWx1ZXMgYW5kIG1pc3NpbmcgdmFsdWVzLgoKSWYgb2JzZXJ2ZWQgdmFsdWVzIG9ubHkgY292ZXIgcGFydCBvZiBhIHBvc3NpYmxlIHJhbmdlLCBpdCBpcwpzb21ldGltZXMgYXBwcm9wcmlhdGUgdG8gdXNlIGEgY29sb3IgY29kaW5nIHRoYXQgYXBwbGllcyB0byB0aGUgZW50aXJlCnBvc3NpYmxlIHJhbmdlLgoKRm9yIGRpdmVyZ2luZyBwYWxldHRlcywgc29tZSBjYXJlIG1heSBiZSBuZWVkZWQgdG8gbWFrZSBzdXJlIHRoZQpuZXV0cmFsIGNvbG9yIGFuZCB0aGUgbmV1dHJhbCB2YWx1ZSBhcmUgcHJvcGVybHkgYWxpZ25lZC4KClVzaW5nIGEgd2VsbC1kZXNpZ25lZCBwYWxldHRlIGlzIHVzdWFsbHkgYmV0dGVyIHRoYW4gY3JlYXRpbmcgeW91ciBvd24uCgpDaG9vc2luZyBhIHBhbGV0dGUgY2FuIGNvbnNpZGVyIG1hbnkgZmFjdG9ycywgaW5jbHVkaW5nIGFwcGVhcmFuY2UgYW5kIGJyYW5kaW5nLgoKCiMjIEEgVUkgRGl2ZXJnaW5nIFBhbGV0dGU/CgpbVUkgY29sb3IgZ3VpZGVsaW5lc10oaHR0cHM6Ly9icmFuZC51aW93YS5lZHUvY29sb3IpIGFyZSBzcGVsbGVkIG91dCBpbiB0aGUgW2JyYW5kIG1hbnVhbF0oaHR0cHM6Ly9icmFuZC51aW93YS5lZHUvKS4KCmBgYHtyfQpVSWdvbGQgPC0gcmdiKDI1NSwgMjA1LCAwLCBtYXhDb2xvclZhbHVlPTI1NSkKcGRVSSA8LSBjb2xvclJhbXBQYWxldHRlKGMoImJsYWNrIiwgIndoaXRlIiwgVUlnb2xkKSkKY29sb3JzcGFjZTo6c3dhdGNocGxvdChwZFVJKDUpKQpgYGAKCmBgYHtyfQpjb2xvcnNwYWNlOjpzd2F0Y2hwbG90KHBkVUkoNSksIGN2ZCA9IFRSVUUpCmBgYAoKCiMjIFJlZmVyZW5jZXMKCj4gRmV3LCBTdGVwaGVuLiAiUHJhY3RpY2FsIHJ1bGVzIGZvciB1c2luZyBjb2xvciBpbiBjaGFydHMuIiBWaXN1YWwKPiBCdXNpbmVzcyBJbnRlbGxpZ2VuY2UgTmV3c2xldHRlciAxMQo+ICgyMDA4KTogMjUuIChbUERGXShodHRwOi8vd3d3LnBlcmNlcHR1YWxlZGdlLmNvbS9hcnRpY2xlcy92aXN1YWxfYnVzaW5lc3NfaW50ZWxsaWdlbmNlL3J1bGVzX2Zvcl91c2luZ19jb2xvci5wZGYpKQoKPiBIYXJyb3dlciwgTS4gQS4gYW5kIEJyZXdlciwgQy4gTS4gKDIwMDMpLiBDb2xvckJyZXdlci5vcmc6IEFuIG9ubGluZQo+IHRvb2wgZm9yIHNlbGVjdGluZyBjb2xvciBzY2hlbWVzIGZvciBtYXBzLgo+IF9UaGUgQ2FydG9ncmFwaGljIEpvdXJuYWxfLCA0MCwgMjctLTM3LiBbQ29sb3JCcmV3ZXIgd2ViCj4gc2l0ZV0oaHR0cHM6Ly9jb2xvcmJyZXdlcjIub3JnKS4gIFRoZSBgUkNvbG9wQnJld2VyYCBwYWNrYWdlIHByb3ZpZGVzCj4gYW4gUiBpbnRlcmZhY2UuCgo+IEloYWthLCBSLiAoMjAwMykuIENvbG91ciBmb3IgcHJlc2VudGF0aW9uIGdyYXBoaWNzLCBpbiBLLiBIb3JuaWssCj4gRi4gTGVpc2NoLCBhbmQgQS4gWmVpbGVpcyAoZWRzLiksIFtfUHJvY2VlZGluZ3Mgb2YgdGhlIDNyZF8KPiBfSW50ZXJuYXRpb25hbCBXb3Jrc2hvcCBvbiBEaXN0cmlidXRlZCBTdGF0aXN0aWNhbF8KPiBfQ29tcHV0aW5nX10oaHR0cHM6Ly93d3cuci1wcm9qZWN0Lm9yZy9jb25mZXJlbmNlcy9EU0MtMjAwMy9Qcm9jZWVkaW5ncy8pLAo+IFZpZW5uYSwKPiBBdXN0cmlhLiBbUERGXShodHRwczovL3d3dy5yLXByb2plY3Qub3JnL2NvbmZlcmVuY2VzL0RTQy0yMDAzL1Byb2NlZWRpbmdzL0loYWthLnBkZikuCj4gU2VlIGFsc28gdGhlIGBjb2xvcnNwYWNlYCBwYWNrYWdlIGFuZAo+IFt2aWduZXR0ZV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvcGFja2FnZT1jb2xvcnNwYWNlL3ZpZ25ldHRlcy9oY2wtY29sb3JzLnBkZikuCgo+IEx1bWxleSwgVC4gKDIwMDYpLiBDb2xvciBjb2RpbmcgYW5kIGNvbG9yIGJsaW5kbmVzcyBpbiBzdGF0aXN0aWNhbAo+IGdyYXBoaWNzLiAgX0FTQSBTdGF0aXN0aWNhbCBDb21wdXRpbmcgJiBHcmFwaGljcyBOZXdzbGV0dGVyXywgMTcoMiksCj4gNC0tNy4gW1BERl0oaHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL29wZW4/aWQ9MWNlam12eUdqUG5uS3NORVNHdU5RVHNaaGw0X3ZCbmRFKS4KCj4gTXVuem5lciwgVC4gKDIwMTQpLCBfVmlzdWFsaXphdGlvbiBBbmFseXNpcyBhbmQgRGVzaWduXywgQ2hhcHRlciAxMC4KCj4gTGlzYSBDaGFybG90dGUgTXV0aCAoMjAyMSkuIDQtcGFydCBzZXJpZXMgb2YgYmxvZyBwb3N0cyBvbiBjaG9vc2luZwo+IGNvbG9yIHNjYWxlcy4gW1BhcnQKPiAxXShodHRwczovL2Jsb2cuZGF0YXdyYXBwZXIuZGUvd2hpY2gtY29sb3Itc2NhbGUtdG8tdXNlLWluLWRhdGEtdmlzLyk7Cj4gW1BhcnQKPiAyXShodHRwczovL2Jsb2cuZGF0YXdyYXBwZXIuZGUvcXVhbnRpdGF0aXZlLXZzLXF1YWxpdGF0aXZlLWNvbG9yLXNjYWxlcy8pOwo+IFtQYXJ0Cj4gM10oaHR0cHM6Ly9ibG9nLmRhdGF3cmFwcGVyLmRlL2RpdmVyZ2luZy12cy1zZXF1ZW50aWFsLWNvbG9yLXNjYWxlcy8pOwo+IFtQYXJ0Cj4gNF0oaHR0cHM6Ly9ibG9nLmRhdGF3cmFwcGVyLmRlL2NsYXNzZWQtdnMtdW5jbGFzc2VkLWNvbG9yLXNjYWxlcy8pLgoKPiBMaXNhIENoYXJsb3R0ZSBNdXRoICgyMDIyKS4gQSBkZXRhaWxlZCBndWlkZSB0byBjb2xvcnMgaW4gZGF0YSB2aXMKPiBzdHlsZSBndWlkZXMuICBbQmxvZwo+IHBvc3RdKGh0dHBzOi8vYmxvZy5kYXRhd3JhcHBlci5kZS9jb2xvcnMtZm9yLWRhdGEtdmlzLXN0eWxlLWd1aWRlcy8pLgoKPiBUcmVpbmlzaCwgTGxveWQgQS4gIldoeSBTaG91bGQgRW5naW5lZXJzIGFuZCBTY2llbnRpc3RzIEJlIFdvcnJpZWQKPiBBYm91dCBDb2xvcj8uIiBJQk0gVGhvbWFzIEouIFdhdHNvbiBSZXNlYXJjaCBDZW50ZXIsIFlvcmt0b3duCj4gSGVpZ2h0cywgTlkgKDIwMDkpOiA0Ni4gIChbcGRmXShodHRwczovL3d3dy5yZXNlYXJjaGdhdGUubmV0L3Byb2ZpbGUvQWhtZWRfRWxoYXR0YWIyL3Bvc3QvUGxlYXNlX3N1Z2dlc3Rfc29tZV9nb29kXzNEX3Bsb3RfdG9vbF9Tb2Z0d2FyZV9mb3Jfc3VyZmFjZV9wbG90L2F0dGFjaG1lbnQvNWMwNWJhMzVjZmU0YTc2NDU1MDY5NDhlL0FTJTNBNjk5ODk0MzM1NTU3NjQ0JTQwMTU0Mzg3OTIyMTcyNS9kb3dubG9hZC9XaHkrU2hvdWxkK0VuZ2luZWVycythbmQrU2NpZW50aXN0cytCZStXb3JyaWVkK0Fib3V0K0NvbG9yXy5wZGYpKQoKPiBXYXJlLCBDLiAoMjAxMiksIF9JbmZvcm1hdGlvbiBWaXN1YWxpemF0aW9uOiBQZXJjZXB0aW9uIGZvciBEZXNpZ25fLAo+IDNyZCBlZCwgQ2hhcHRlcnMgMwo+ICYgNC4KCj4gWmVpbGVpcywgQS4sIE11cnJlbGwsIFAuIGFuZCBIb3JuaWssIEsuICgyMDA5KS4gRXNjYXBpbmcgUkdCbGFuZDoKPiBTZWxlY3RpbmcgY29sb3JzIGZvciBzdGF0aXN0aWNhbCBncmFwaGljcywgX0NvbXB1dGF0aW9uYWwgU3RhdGlzdGljcwo+ICYgRGF0YSBBbmFseXNpc18sIDUzKDkpLCAzMjU5LeKAkzMyNzAKPiAoW1BERl0oaHR0cHM6Ly93d3cuemVpbGVpcy5vcmcvcGFwZXJzL1plaWxlaXMrSG9ybmlrK011cnJlbGwtMjAwOS5wZGYpKS4KCj4gQWNoaW0gWmVpbGVpcywgUGF1bCBNdXJyZWxsICgyMDE5KS4gSENMLUJhc2VkIENvbG9yIFBhbGV0dGVzIGluCj4gYGdyRGV2aWNlc2AuICBbUiBCbG9nCj4gcG9zdF0oaHR0cHM6Ly9kZXZlbG9wZXIuci1wcm9qZWN0Lm9yZy9CbG9nL3B1YmxpYy8yMDE5LzA0LzAxL2hjbC1iYXNlZC1jb2xvci1wYWxldHRlcy1pbi1ncmRldmljZXMvaW5kZXguaHRtbCkuCgo+IEFjaGltIFplaWxlaXMgZXQgYWwuICgyMDIwKS4g4oCcY29sb3JzcGFjZTogQSBUb29sYm94IGZvciBNYW5pcHVsYXRpbmcKPiBhbmQgQXNzZXNzaW5nIENvbG9ycyBhbmQgUGFsZXR0ZXMu4oCdIEpvdXJuYWwgb2YgU3RhdGlzdGljYWwgU29mdHdhcmUsCj4gOTYoMSksCj4gMS00OS4gW2RvaToxMC4xODYzNy9qc3MudjA5Ni5pMDFdKGh0dHBzOi8vZG9pLm9yZy8xMC4xODYzNy9qc3MudjA5Ni5pMDEpLgoKCiMjIENvbG9yaW5nIFBvbGl0aWNhbCBTdGF0ZW1lbnRzCgpQT0xJVElGQUNUIHJldmlld3MgdGhlIGFjY3VyYWN5IG9mIHN0YXRlbWVudHMgYnkgcG9saXRpY2lhbnMgYW5kCnB1Ymxpc2hlcyBbc3VtbWFyaWVzIG9mIHRoZQpyZXN1bHRzXShodHRwczovL3d3dy5wb2xpdGlmYWN0LmNvbS9wZXJzb25hbGl0aWVzL21pY2hlbGUtYmFjaG1hbm4vKS4KCkEgWzIwMTYKcG9zdF0oaHR0cHM6Ly93d3cuZGFpbHlrb3MuY29tL3N0b3JpZXMvMjAxNi84LzcvMTU1NjY2Ni8tVGhyZWUtbGVzc29ucy1mcm9tLXRoZS1yaXNlLW9mLURvbmFsZC1UcnVtcCkKb24gW0RhaWx5IEtvc10oaHR0cHM6Ly93d3cuZGFpbHlrb3MuY29tLykgaW5jbHVkZWQgYQpbdmlzdWFsaXphdGlvbl0oaHR0cHM6Ly9pbWFnZXMuZGFpbHlrb3MuY29tL2ltYWdlcy8yODMxNTIvbGFyZ2UvZGF0YXZpel9yb2JlcnRfbWFubi5wbmc/MTQ3MDMyMzUxNCkKb2YgdGhlIHJlc3VsdHMgZm9yIGEgbnVtYmVyIG9mIHBvbGl0aWNpYW5zLgoKS2Fpc2VyIEZ1bmcgcG9zdGVkIGEKW2NyaXRpcXVlXShodHRwczovL3dlYi5hcmNoaXZlLm9yZy93ZWIvMjAxODAyMDEwNzA3MDMvaHR0cHM6Ly9qdW5rY2hhcnRzLnR5cGVwYWQuY29tL2p1bmtfY2hhcnRzLzIwMTcvMDQvd2hhdC1kb2VzLWx5aW5nLXBvbGl0aWNpYW5zLWhhdmUtaW4tY29tbW9uLXdpdGgtcmFpbmJvdy1jb2xvcnMuaHRtbCkKYXQgW0p1bmtDaGFydHNdKGh0dHBzOi8vd3d3Lmp1bmtjaGFydHMuY29tL3RhZy9ibG9ncy8pIGFuZCBwcm9wb3NlZCBhbgphbHRlcm5hdGl2ZS4KCjwhLS0KIyMgU2NyYXBpbmcgdGhlIGRhdGE6CgpwZyA8LSByZWFkX2h0bWwoImh0dHBzOi8vd3d3LnBvbGl0aWZhY3QuY29tL3BlcnNvbmFsaXRpZXMvbWljaGVsZS1iYWNobWFubi8iKQpsYWJzIDwtIGh0bWxfdGV4dChodG1sX25vZGVzKHBnLCB4cGF0aD0iLy9zcGFuW0BjbGFzcz0nY2hhcnRsaXN0X19sYWJlbCddIikpCnZub2RlcyA8LSBodG1sX25vZGVzKHBnLCB4cGF0aD0iLy9zcGFuW0BjbGFzcz0nY2hhcnRsaXN0X19pbmRleCddIikKdnR4dCA8LSBodG1sX3RleHQodm5vZGVzKQphcy5udW1lcmljKGdzdWIoIltcXChcXCldIiwgIiIsIHZ0eHQpKQp2YXR0ciA8LSBodG1sX2F0dHIodm5vZGVzLCAic3R5bGUiKQphcy5udW1lcmljKHN1YigiLio6KFtbOmRpZ2l0Ol1dKykuKiIsICJcXDEiLCB2YXR0cikpCgpwb2xmYWMgPC0gZnVuY3Rpb24ocGlkKSB7CiAgICB1cmwgPC0gc3ByaW50ZigiaHR0cDovL3d3dy5wb2xpdGlmYWN0LmNvbS9wZXJzb25hbGl0aWVzLyVzLyIsIHBpZCkKICAgIHBnIDwtIHJlYWRfaHRtbCh1cmwpCiAgICBsYWJzIDwtIGh0bWxfdGV4dChodG1sX25vZGVzKHBnLCB4cGF0aD0iLy9zcGFuW0BjbGFzcz0nY2hhcnRsaXN0X19sYWJlbCddIikpCiAgICB2bm9kZXMgPC0gaHRtbF9ub2RlcyhwZywgeHBhdGg9Ii8vc3BhbltAY2xhc3M9J2NoYXJ0bGlzdF9faW5kZXgnXSIpCiAgICB2dHh0IDwtIGh0bWxfdGV4dCh2bm9kZXMpCiAgICBjb3VudHMgPC0gYXMubnVtZXJpYyhnc3ViKCJbXFwoXFwpXSIsICIiLCB2dHh0KSkKICAgIHZhdHRyIDwtIGh0bWxfYXR0cih2bm9kZXMsICJzdHlsZSIpCiAgICBwY3QgPC0gYXMubnVtZXJpYyhzdWIoIi4qOihbWzpkaWdpdDpdXSspLioiLCAiXFwxIiwgdmF0dHIpKQogICAgZGF0YS5mcmFtZShjbGFzcyA9IGxhYnMsIGNvdW50cyA9IGNvdW50cywgcGN0ID0gcGN0KQp9Cgpwb2xzIDwtICIKVHJ1bXAsIGRvbmFsZC10cnVtcApCYWNobWFubiwgbWljaGVsZS1iYWNobWFubgpDcnV6LCB0ZWQtY3J1egpHaW5ncmljaCwgbmV3dC1naW5ncmljaApQYWxpbiwgc2FyYWgtcGFsaW4KU2FudG9ydW0sIHJpY2stc2FudG9ydW0KV2Fsa2VyLCBzY290dC13YWxrZXIKUGVycnksIHJpY2stcGVycnkKUnlhbiwgcGF1bC1yeWFuCk1jQ2Fpbiwgam9obi1tY2NhaW4KUnViaW8sIG1hcmNvLXJ1YmlvClJvbW5leSwgbWl0dC1yb21uZXkKUmFuZCBQYXVsLCByYW5kLXBhdWwKQ2hyaXN0aWUsIGNocmlzLWNocmlzdGllCkhpbGxhcnkgQ2xpbnRvbiwgaGlsbGFyeS1jbGludG9uCk9iYW1hLCBiYXJhY2stb2JhbWEKS2FzaWNoLCBqb2huLWthc2ljaApCaWRlbiwgam9lLWJpZGVuCkplYiBCdXNoLCBqZWItYnVzaApTYW5kZXJzLCBiZXJuaWUtcwoiCgpwcCA8LSByZWFkLmNzdih0ZXh0Q29ubmVjdGlvbihwb2xzKSwgaGVhZCA9IEZBTFNFLAogICAgICAgICAgICAgICBzdHJpcC53aGl0ZSA9IFRSVUUpCgp2YyA8LSBzYXBwbHkocHBbWzJdXSwgZnVuY3Rpb24obikgeyBwcmludChuKTsgcG9sZmFjKG4pfVtbMl1dKQpyb3duYW1lcyh2YykgPC0gbGFicwp2cCA8LSBzYXBwbHkocHBbWzJdXSwgZnVuY3Rpb24obikgeyBwcmludChuKTsgcG9sZmFjKG4pfVtbM11dKQpyb3duYW1lcyh2cCkgPC0gbGFicwoKdmNwIDwtIHByb3AudGFibGUodCh2YyksIDEpWywgNiA6IDFdCgojIGNvbG5hbWVzKHZjKSA8LSBwcFtbMV1dCiMgd3JpdGUudGFibGUodCh2YyksIGZpbGUgPSAicG9sZmFjLmRhdCIpCi0tPgoKSSBzY3JhcGVkIHRoZSBkYXRhIGFzIG9mIEFwcmlsIDExLCAyMDE3LCBmcm9tIFBPTElUSUZBQ1Q7IHRoZXkgYXJlCmF2YWlsYWJsZSBbaGVyZV0oaHR0cHM6Ly9zdGF0LnVpb3dhLmVkdS9+bHVrZS9kYXRhL3BvbGZhYy5kYXQpLgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQppZiAoISBmaWxlLmV4aXN0cygicG9sZmFjLmRhdCIpKQogICAgZG93bmxvYWQuZmlsZSgiaHR0cHM6Ly9zdGF0LnVpb3dhLmVkdS9+bHVrZS9kYXRhL3BvbGZhYy5kYXQiLAogICAgICAgICAgICAgICAgICAicG9sZmFjLmRhdCIpCnBmdCA8LSByZWFkLnRhYmxlKCJwb2xmYWMuZGF0IikKdmNwIDwtIHByb3AudGFibGUoYXMubWF0cml4KHBmdCksIDEpWywgNiA6IDFdCmNvbG5hbWVzKHZjcCkgPC0gZ3N1YigiXFwuIiwgIiAiLCBjb2xuYW1lcyh2Y3ApKQoKaGVhZCh2Y3ApCgpgYGAKClRoZSBEYWlseSBLb3MgY2hhcnQgaXMgb3JkZXJlZCBieSB0aGUgcGVyY2VudGFnZSBvZiBzdGF0ZW1lbnRzIHRoYXQKYXJlIG1vcmUgZmFsc2UgdGhhbiB0cnVlLiBBIGZ1bmN0aW9uIHRvIHByb2R1Y2UgYSBiYXIgY2hhcnQgd2l0aCBhCnNwZWNpZmllZCBjb2xvciBwYWxldHRlOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQojIyBsYXR0aWNlIHZlcnNpb24KcG9sYmFycyA8LSBmdW5jdGlvbihjb2wgPSBjbS5jb2xvcnMoNikpIHsKICAgIGJhcmNoYXJ0KHZjcFtvcmRlcihyb3dTdW1zKHZjcFssIDEgOiAzXSkpLCBdLCBhdXRvLmtleSA9IFRSVUUsCiAgICAgICAgICAgICBwYXIuc2V0dGluZ3MgPSBsaXN0KHN1cGVycG9zZS5wb2x5Z29uID0gbGlzdChjb2wgPSBjb2wpKSkKfQoKIyMgZ2dwbG90IHZlcnNpb24KCmd2Y3AgPC0gYXMuZGF0YS5mcmFtZSh2Y3ApIHw+CiAgICByb3duYW1lc190b19jb2x1bW4oIk5hbWUiKSB8PgogICAgcGl2b3RfbG9uZ2VyKC0xLCBuYW1lc190byA9ICJHcmFkZSIsIHZhbHVlc190byA9ICJwcm9wIikgfD4KICAgIG11dGF0ZShHcmFkZSA9IGZjdF9yZXYoZmN0X2lub3JkZXIoR3JhZGUpKSkKbm0gPC0gbXV0YXRlKGd2Y3AsIEdyYWRlID0gb3JkZXJlZChHcmFkZSkpIHw+CiAgICBmaWx0ZXIoR3JhZGUgPD0gIkhhbGYgVHJ1ZSIpIHw+CiAgICBncm91cF9ieShOYW1lKSB8PgogICAgc3VtbWFyaXplKHByb3AgPSBzdW0ocHJvcCkpIHw+CiAgICBhcnJhbmdlKGRlc2MocHJvcCkpIHw+CiAgICBwdWxsKE5hbWUpCmd2Y3AgPC0gbXV0YXRlKGd2Y3AsIE5hbWUgPSBmYWN0b3IoTmFtZSwgbm0pKQoKcHZjcCA8LSBnZ3Bsb3QoZ3ZjcCwgYWVzKE5hbWUsIHByb3AsIGZpbGwgPSBHcmFkZSkpICsKICAgIGdlb21fY29sKHBvc2l0aW9uID0gImZpbGwiLCB3aWR0aCA9IDAuNykgKwogICAgY29vcmRfZmxpcCgpICsKICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiLAogICAgICAgICAgcGxvdC5tYXJnaW4gPSBtYXJnaW4ociA9IDUwKSwKICAgICAgICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsKICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQsIGV4cGFuZCA9IGMoMCwgMCkpICsKICAgIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKHRpdGxlID0gTlVMTCwgbnJvdyA9IDEsIHJldmVyc2UgPSBUUlVFKSkgKwogICAgbGFicyh4ID0gIiIsIHkgPSAiIikKCnBvbGJhcnMgPC0gZnVuY3Rpb24oY29sID0gY20uY29sb3JzKDYpKQogICAgcHZjcCArIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHJldihjb2wpKQoKcG9sYmFycygpCmBgYAoKVGhlIG9yaWdpbmFsIERhaWx5IEtvcyBjaGFydCBzZWVtcyB0byB1c2UgYSBzbGlnaHRseSBtb2RpZmllZCB2ZXJzaW9uCm9mIHRoZSBDb2xvciBCcmV3ZXIgYFNwZWN0cmFsYCBwYWxldHRlLCBhIGRpdmVyZ2luZyBwYWxldHRlLgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpwb2xiYXJzKGJyZXdlci5wYWwoNiwgIlNwZWN0cmFsIikpCmRrY29scyA8LSBicmV3ZXIucGFsKDYsICJTcGVjdHJhbCIpCmRrY29sc1s0XSA8LSAibGlnaHRncmV5Igpwb2xiYXJzKGRrY29scykKYGBgCgpUaGUgSnVua0NoYXJ0cyBwbG90IHVzZXMgYW5vdGhlciBkaXZlcmdpbmcgcGFsZXR0ZSwgY2xvc2UgdG8gdGhlCmBCbHVlLVJlZCBgIHBhbGV0dGUgYXZhaWxhYmxlIGluIGBoY2x3aXphcmRgLgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpyd2Jjb2xzIDwtIGMoIiM0QTZGRTMiLCAiIzg1OTVFMSIsICIjQjVCQkUzIiwgIiNFMkUyRTIiLAogICAgICAgICAgICAgIiNFNkFGQjkiLCAiI0UwN0I5MSIsICIjRDMzRjZBIikKcG9sYmFycyhyd2Jjb2xzKQpwb2xiYXJzKHJldihyd2Jjb2xzKSkKYGBgCgpBbm90aGVyIGRpdmVyZ2luZyBwYWxldHRlOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpwb2xiYXJzKGJyZXdlci5wYWwoNywgIlBpWUciKSkKYGBgCgpBIHNlcXVlbnRpYWwgcGFsZXR0ZToKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KcG9sYmFycyhyZXYoYnJld2VyLnBhbCg2LCAiT3JhbmdlcyIpKSkKYGBgCgo8IS0tCgpzdXBwcmVzc1dhcm5pbmdzKHByaW50KAptYXBwbG90KHJvd25hbWVzKFVTQ2FuY2VyUmF0ZXMpIH4gbG9nKHJhdGUubWFsZSkgKyBsb2cocmF0ZS5mZW1hbGUpLAogICAgICAgIGRhdGEgPSBVU0NhbmNlclJhdGVzLAogICAgICAgIG1hcCA9IG1hcCgiY291bnR5IiwgcGxvdCA9IEZBTFNFLCBmaWxsID0gVFJVRSwKICAgICAgICAgICAgICAgICAgcHJvamVjdGlvbiA9ICJtZXJjYXRvciIpKQopKQpzdXBwcmVzc1dhcm5pbmdzKHByaW50KAptYXBwbG90KHJvd25hbWVzKFVTQ2FuY2VyUmF0ZXMpIH4gbG9nKHJhdGUubWFsZSkgKyBsb2cocmF0ZS5mZW1hbGUpLAogICAgICAgIGRhdGEgPSBVU0NhbmNlclJhdGVzLAogICAgY29scmFtcCA9IGNvbG9yUmFtcFBhbGV0dGUoYnJld2VyLnBhbChuID0gOSwgbmFtZSA9ICJSZWRzIikpLAogICAgYm9yZGVyID0gTkEsCiAgICAgICAgbWFwID0gbWFwKCJjb3VudHkiLCBwbG90ID0gRkFMU0UsIGZpbGwgPSBUUlVFLAogICAgICAgICAgICAgICAgICBwcm9qZWN0aW9uID0gIm1lcmNhdG9yIikpCikpCgpodHRwOi8vZGF0YS5saWJyYXJ5LnZpcmdpbmlhLmVkdS9zZXR0aW5nLXVwLWNvbG9yLXBhbGV0dGVzLWluLXIvCmh0dHA6Ly9zdGF0NTQ1LmNvbS9ibG9jazAxOF9jb2xvcnMuaHRtbAoKaHR0cHM6Ly95b3V0dS5iZS92Skc2OThVMk12bwoKCmxpYnJhcnkoZ2dwbG90MikKZ2dIQ0wgPC0gZnVuY3Rpb24oTCkgewpyICA8LSBzZXEoMCwxLGxlbmd0aD0yMDEpCnRoIDwtIHNlcSgwLDIqcGksIGxlbmd0aD0yMDEpCmQgIDwtIGV4cGFuZC5ncmlkKHI9cix0aD10aCkKZ2cgPC0gd2l0aChkLGRhdGEuZnJhbWUoZCx4PXIqc2luKHRoKSx5PXIqY29zKHRoKSwKICAgICAgICAgICAgICAgICAgICAgICAgej1oY2woaD0zNjAqdGgvKDIqcGkpLGM9MTAwKnIsIGw9TCkpKQpnZ3Bsb3QoZ2cpICsKICBnZW9tX3BvaW50KGFlcyh4LHksIGNvbG9yPXopLCBzaXplPTMpKwogIHNjYWxlX2NvbG9yX2lkZW50aXR5KCkrbGFicyh4PSIiLHk9IiIpICsKICBjb29yZF9maXhlZCgpCgp9CgotLT4KCgojIyBSZWFkaW5nCgpTZWN0aW9uIFtfUGVyY2VwdGlvbiBhbmQgRGF0YQpWaXN1YWxpemF0aW9uX10oaHR0cHM6Ly9zb2N2aXouY28vbG9va2F0ZGF0YS5odG1sI3BlcmNlcHRpb24tYW5kLWRhdGEtdmlzdWFsaXphdGlvbikKaW4gW19EYXRhIFZpc3VhbGl6YXRpb25fXShodHRwczovL3NvY3Zpei5jby8pLgoKQ2hhcHRlciBbX0NvbG9yIHNjYWxlc19dKGh0dHBzOi8vY2xhdXN3aWxrZS5jb20vZGF0YXZpei9jb2xvci1iYXNpY3MuaHRtbCkKICBpbiBbX0Z1bmRhbWVudGFscyBvZiBEYXRhCiAgVmlzdWFsaXphdGlvbl9dKGh0dHBzOi8vY2xhdXN3aWxrZS5jb20vZGF0YXZpei8pLgoKCiMjIEV4ZXJjaXNlcwoKMS4gQSBjb2xvciBjYW4gYmUgc3BlY2lmaWVkIGluIGhleGFkZWNpbWFsIG5vdGF0aW9uLiBHaXZlbiBzdWNoIGEKICAgIGNvbG9yIHNwZWNpZmljYXRpb24geW91IGNhbiBmaW5kIG91dCB3aGF0IGl0IGxvb2tzIGxpa2UgYnkgdXNpbmcKICAgIGl0IGluIGEgc2ltcGxlIHBsb3QsIG9yIGJ5IHVzaW5nIHRoZSBHb29nbGUgY29sb3IgcGlja2VyLiAgV2hpY2gKICAgIG9mIHRoZSBmb2xsb3dpbmcgYmVzdCBkZXNjcmliZSB0aGUgY29sb3IgYCNCMjIyMjJgPwoKICAgIGEuIGEgc2hhZGUgb2YgZ3JlZW4KICAgIGIuIGEgc2hhZGUgb2YgYmx1ZQogICAgYy4gb3JhbmdlCiAgICBkLiBhIHNoYWRlIG9mIHJlZAoKMi4gVGhlIGZvbGxvd2luZyBzaG93cyBob3cgdG8gdmlldyB0aGUgY29sb3JzIGluIHRoZQogICAgYFJDb2xvckJyZXdlcmAgcGFsZXR0ZSBuYW1lZCBgUmVkc2Agd2l0aCA3IGNvbG9yczoKCiAgICBgYGB7ciwgZmlnLmNhcCA9ICIifQogICAgbGlicmFyeShSQ29sb3JCcmV3ZXIpCiAgICBkaXNwbGF5LmJyZXdlci5wYWwoNywgIlJlZHMiKQogICAgYGBgCgogICAgV2hpY2ggb2YgdGhlIGZvbGxvd2luZyBgUkNvbG9yQnJld2VyYCBwYWxldHRlcyBpcyBkaXZlcmdpbmc/CgogICAgYS4gYEJsdWVzYAogICAgYi4gYFB1UmRgCiAgICBjLiBgU2V0MWAKICAgIGQuIGBSZEd5YAo=