Moving Beyond Two Dimensions
Paper and screens are two-dimensional.
We live in a three-dimensional world.
For visualizing three-dimensional data we can take advantage of our
visual system’s ability to reconstruct three-dimensional scenes from
two-dimensional images using:
perspective rendering, lighting, and shading;
motion with animation and interaction;
stereo viewing methods.
Most of us have no intuition for four and more dimensions.
Some techniques that work in three dimensions but can also be used in
higher dimensions:
Higher dimensions maybe up to ten; the curse
of dimensionality is a limiting factor.
The lattice package provides some facilities not easily
available in ggplot so I will use lattice in a
few examples.
Scatterplot Matrices
A scatterplot matrix is a useful overview that shows all
pairwise scatterplots.
There are many options for creating scatterplot matrices in R; a few
are:
pairs in base graphics;
splom in package lattice
ggpairs in GGally.
Some examples using the mpg data:
library(lattice)
splom(select(mpg, cty, hwy, displ),
cex = 0.5, pch = 19)
library(GGally)
ggpairs(select(mpg, cty, hwy, displ),
lower = list(continuous =
wrap("points",
size = 1)))
Some variations:
diagonal left-top to right-bottom or left-bottom to
right-top;
how to use the panels in the two triangles;
how to use the panels on the diagonal.
Some things to look for in the panels:
clusters or separation of groups;
strong relationships;
outliers, rounding, clumping.
Notes:
Scatterplot matrices were popularized by Cleveland and co-workers
at Bell Laboratories in the 1980s.
Cleveland recommends using the full version displaying both
triangles of plots to facilitate visual linking .
If you do use only one triangle, and one variable is a response,
then it is a good idea to arrange for that variable to be shown on the
vertical axis against all other variables.
The symmetry in the plot with the diagonal running from
bottom-left to top-tight as produced by splom is simpler
than the symmetry in the plot with the diagonal running from top-left t
bottom-right produced by pairs and
ggpairs.
Three Data Sets
Thee useful data sets to explore:
The ethanol data frame in the lattice
package.
Soil resistivity data from from Cleveland’s Visualizing
Data book.
The quakes data frame in the datasets
package.
Ethanol Data
The ethanol data frame in the lattice
package contains data from an experiment on efficiency and emissions in
small one-cylinder engines.
The data frame contains 88 observations on three variables:
NOx: Concentration of nitrogen oxides (NO and NO2)
in micrograms.
C Compression ratio of the engine.
E Equivalence ratio, a measure of the richness of
the air and ethanol fuel mixture.
A scatterplot matrix:
library(lattice)
splom(ethanol, ced = 0.5, pch = 19)
A goal is to understand the relationship between the pollutant
NOx and the controllable variables E and
C.
Soil Resistivity Data
Data from
Cleveland’s Visualizing Data book contains measurements of soil
resistivity of an agricultural field along a roughly rectangular
grid.
A scatterplot matrix of the resistivity,
northing and easting variables:
if (! file.exists("soil.dat"))
download.file("http://www.stat.uiowa.edu/~luke/data/soil.dat",
"soil.dat")
soil <- read.table("soil.dat")
splom(soil[1 : 3], cex = 0.1, pch = 19)
The data is quite noisy but there is some structure.
A goal is to understand how resistivity varies across the field.
Earth Quake Locations and Magnitudes
The quakes data frame contains data on locations of
seismic events of magnitude 4.0 or larger in a region near Fiji.
The time frame is from 1964 to perhaps 2000.
More recent data is available from a number of sources on the
web.
A scatter plot matrix:
library(lattice)
splom(quakes, cex = 0.1, pch = 19)
Quake locations:
md <- map_data("world2", c("Fiji", "Tonga", "New Zealand"))
ggplot(quakes, aes(long, lat)) +
geom_polygon(aes(group = group), data = md, color = "black", fill = NA) +
geom_point(size = 0.5, color = "red") +
coord_map() +
ggthemes::theme_map()
Some goals:
Grouping for Conditioning
For the ethanol data there are only a small number of
distinct levels for C.
This suggests considering a plot mapping the level to color.
ggplot(ethanol,
aes(E, NOx,
color = C)) +
geom_point(size = 2)
A qualitative scheme can help distinguish the levels.
ggplot(ethanol,
aes(E, NOx,
color = ordered(C))) +
geom_point(size = 2)
Adding smooths further helps the visual grouping:
ggplot(ethanol,
aes(E, NOx,
color = ordered(C))) +
geom_point(size = 2) +
geom_smooth(se = FALSE)
Some observations:
At each level of C there is a strong non-linear
relation between NOx and E.
At levels of E above 1 the value of C
has little effect.
For lower levels of E the NOx level
increases with C.
For the quakes data, breaking the depth
values into thirds gives some insights:
quakes2 <-
mutate(quakes,
depth_cut = cut_number(depth, 3))
ggplot(quakes2, aes(x = long,
y = lat,
color = depth_cut)) +
geom_point() +
theme_bw() +
coord_map()
Conditioning Plots (Coplots)
One way to try to get a handle on higher dimensional data is to try
to fix values of some variables and visualize the values of others in
2D.
This can be done with
A conditioning plot, or coplot :
Shows a collection of plots of two variables for different
settings of one or more additional variables, the conditioning
variables.
For ordered conditioning variables the plots are arranged in a
way that reflects the order.
When a conditioning variable is numeric, or ordered categorical
with many levels, the values of the conditioning variable are grouped
into bins.
For the soil resistivity data, a coplot of resistivity
against easting, conditioning on northing with
bins of size 0.5:
p1 <- ggplot(soil,
aes(easting, resistivity)) +
geom_point(size = 0.5) +
facet_wrap(~ cut_width(northing,
width = 0.5,
center = 0))
p1
Adding a smooth is often helpful.
With a large amount of data the smooth is hard to see.
Some options:
Omit the data and only show the smooth.
Show the data in a less intense color, such as light
gray.
Use a contrasting color for the smooth curves.
Show the data using alpha blending.
This uses a muted representation of the data:
p2 <- ggplot(soil,
aes(easting, resistivity)) +
geom_point(size = 0.5,
color = "lightgrey") +
facet_wrap(~ cut_width(northing,
width = 0.5,
center = 0)) +
geom_smooth()
p2
The conditioning bins are quite wide.
Using rounding and keeping only points within 0.05 of the rounded
values reduces the variability:
soil_trm <-
mutate(soil,
nrnd = round(northing * 2) / 2) |>
filter(abs(northing - nrnd) < 0.05)
p1 + soil_trm +
facet_wrap(~ cut_width(northing,
width = 0.1,
center = 0))
p2 + soil_trm +
facet_wrap(~ cut_width(northing,
width = 0.1,
center = 0))
For the quakes data a plot of latitude against longitude conditioned
on three depth levels:
qthm <- theme(panel.border = element_rect(color = "grey30", fill = NA))
ggplot(quakes2, aes(x = long, y = lat)) +
geom_point(color = scales::muted("blue"),
size = 0.5) +
facet_wrap(~ depth_cut,
nrow = 1) +
coord_map() +
qthm
The relative positions of the depth groups are much harder to see
than in the grouped conditioning plot.
Adding the full data for background context, and using a more intense
color for the panel subset, helps a lot:
## quakes does not contain the depth_cut
## variable used in the facet
ggplot(quakes2, aes(x = long, y = lat)) +
geom_point(data = quakes,
color = "gray", size = 0.5) +
geom_point(color = "blue", size = 0.5) +
facet_wrap(~ depth_cut, nrow = 1) +
coord_map() +
qthm
Switching latitude and depth shows another aspect:
quakes3 <-
mutate(quakes,
lat_cut = cut_width(lat,
width = 5,
boundary = 0))
ggplot(quakes3, aes(x = long, y = depth)) +
geom_point(data = quakes,
color = "gray", size = 0.5) +
geom_point(color = "blue", size = 0.5) +
scale_y_reverse() +
facet_wrap(~ lat_cut) +
qthm
Coplot for the ethanol data:
ggplot(ethanol, aes(x = E, y = NOx)) +
geom_point() +
facet_wrap(~ C)
Adding muted full data for context:
ggplot(ethanol, aes(x = E, y = NOx)) +
geom_point(color = "grey",
data = mutate(ethanol, C = NULL)) +
geom_point() +
facet_wrap(~ C)
Contour and Level Plots for Surfaces
A number of methods can be used to estimate a smooth signal surface
as a function of the two location variables.
One option is the loess local polynomial smoother;
another is gam from package mgcv.
The estimated surface level can be computed on a grid of points using
the predict method of the fit.
These estimated surfaces can be visualized using contour plots or
level plots.
m <- loess(resistivity ~ easting * northing, span = 0.25,
degree = 2, data = soil)
eastseq <- seq(.15, 1.410, by = .015)
northseq <- seq(.150, 3.645, by = .015)
soi.grid <- expand.grid(easting = eastseq, northing = northseq)
soi.fit <- predict(m, soi.grid)
soi.fit.df <- mutate(soi.grid, fit = as.numeric(soi.fit))
Contour Plots
Contour plots compute contours, or level curves, as polygons
at a set of levels.
Contour plots draw the level curves, often with a level
annotation.
Contour plots can also have their polygons filled in with colors
representing the levels.
A basic contour plot of the fit soil resistivity surface in
ggplot using geom_contour:
p <- ggplot(soi.fit.df,
aes(x = easting,
y = northing,
z = fit)) +
coord_fixed()
p + geom_contour()
Neither lattice nor ggplot seem to make it
easy to fill in the contours.
The base function filled.contour is available for
this:
cm.rev <- function(...) rev(cm.colors(...))
filled.contour(eastseq, northseq, soi.fit,
asp = 1,
color.palette = cm.rev)
Level Plots
A level plot colors a grid spanned by two variables by the
color of a third variable.
Level plots are also called image plots
The term heat map is also used, in particular with a
specific color scheme.
But heat map often means a more complex visualization with an image
plot at its core.
ggplot provides geom_tile that can be used
for a level plot:
p + geom_tile(aes(fill = fit)) +
scale_fill_gradientn(
colors = rev(cm.colors(100)))
Superimposing contours on a level plot is often helpful.
p + geom_tile(aes(fill = fit)) +
geom_contour() +
scale_fill_gradientn(
colors = rev(cm.colors(100)))
Level plots do not require computing contours, but are not as smooth
as filled contour plots.
Visually, image plots and filled contour plots are very similar for
fine grids, but image plots are less smooth for coarse ones.
Lack of smoothness is less of an issue when the data values
themselves are noisy.
The grid for the volcano
data set is coarser and illustrates the lack of smoothness.
vd <- expand.grid(x = seq_len(nrow(volcano)), y = seq_len(ncol(volcano)))
vd$z <- as.numeric(volcano)
ggplot(vd, aes(x, y, fill = z)) +
geom_tile() +
scale_fill_gradientn(colors = rev(cm.colors(100))) +
coord_equal()
A filled.contour plot looks like this:
filled.contour(seq_len(nrow(volcano)),
seq_len(ncol(volcano)),
volcano,
nlevels = 10,
color.palette = cm.rev,
asp = 1)
A coarse grid can be interpolated to a finer grid.
Irregularly spaced data can also be interpolated to a grid.
The interp function in the akima
or interp
packages is useful for this kind of interpolation.
(interp has a more permissive license.)
Three-Dimensional Views
There are several options for viewing surfaces or collections of
points as three-dimensional objects:
Fixed Views
The lattice function cloud() shows a
projection of a rotated point cloud in three dimensions.
For the soil resistivity data:
cloud(resistivity ~ easting * northing,
pch = 19, cex = 0.1, data = soil)
For the quakes data:
cloud(-depth ~ long * lat,
data = quakes)
A surface can also be visualized using a wire frame plot
showing a 3D view of the surface from a particular viewpoint.
A simple wire frame plot is often sufficient.
Lighting and shading can be used to enhance the 3D effect.
A basic wire frame plot for the volcano data:
wireframe(z ~ x * y,
data = vd,
aspect = c(61 / 89, 0.3))
Wire frame is a bit of a misnomer since surface panels in
front occlude lines behind them.
For a fine grid, as in the soil surface, the lines are too dense.
The use of shading for the surfaces can help.
wfpal <- makeShadePalette(cm.colors(10), pref = 0.2)
wireframe(z ~ x * y,
data = vd,
aspect = c(61 / 89, 0.3),
shade = TRUE,
shade.colors.palette = wfpal)
A wire frame plot with shading for the fit soil resistivity
surface:
asp <- with(soi.grid,
diff(range(northing)) /
diff(range(easting)))
wf <- wireframe(
soi.fit ~
soi.grid$easting * soi.grid$northing,
aspect = asp, shade = TRUE,
screen = list(z = -50, x = -30),
xlab = "Easting (km",
ylab = "Northing (km)",
shade.colors.palette = wfpal)
wf
Both ways of looking at a surface are useful:
lv <-
levelplot(soi.fit ~
soi.grid$easting *
soi.grid$northing,
cuts = 9,
aspect = asp,
contour = TRUE,
xlab = "Easting (km)",
ylab = "Northing (km)",
col.regions = cm.colors)
print(lv, split = c(1, 1, 2, 1),
more = TRUE)
print(wf, split = c(2, 1, 2, 1))
The level plot/contour representation is useful for recognizing
locations of key features.
The wire frame view helps build a mental model of the 3D
structure.
Being able to interactively adjust the viewing position for a
wire frame model greatly enhances our ability to understand the 3D
structure.
Interactive 3D Plots Using OpenGL
OpenGL is a standardized
framework for high performance graphics.
The rgl package provides an R interface to some of
OpenGL’s capabilities.
WebGL is a JavaScript
framework for using OpenGL within a browser window.
Most desktop browsers support WebGL; some mobile browsers do as
well.
In some cases support may be available but not enabled by default.
You may be able to get help at https://get.webgl.org/ .
knitr and rgl provide support for embedding
OpenGL images in web pages.
It is also possible to embed OpenGL images in PDF files, but not all
PDF viewers support this.
Start by creating the fit surface data frame.
if (! file.exists("soil.dat"))
download.file("http://www.stat.uiowa.edu/~luke/data/soil.dat",
"soil.dat")
soil <- read.table("soil.dat")
library(dplyr)
soil <- read.table("http://www.stat.uiowa.edu/~luke/data/soil.dat")
m <- loess(resistivity ~ easting * northing, span = 0.25,
degree = 2, data = soil)
eastseq <- seq(.15, 1.410, by = .015)
northseq <- seq(.150, 3.645, by = .015)
soi.grid <- expand.grid(easting = eastseq, northing = northseq)
soi.fit <- predict(m, soi.grid)
soi.fit.df <- mutate(soi.grid, fit = as.numeric(soi.fit))
This code run in R will open a new window containing an interactive
3D scene (but this may not work on FastX and is not available on the
RStudio server):
library(rgl)
bg3d(color = "white")
clear3d()
par3d(mouseMode = "trackball")
surface3d(eastseq, northseq,
soi.fit / 100,
color = rep("red", length(soi.fit)))
This will work in the RStudio notebook server:
options(rgl.useNULL = TRUE)
library(rgl)
bg3d(color = "white")
clear3d()
par3d(mouseMode = "trackball")
surface3d(eastseq, northseq,
soi.fit / 100,
color = rep("red", length(soi.fit)))
rglwidget()
To embed an image in an HTML document, first set the
webgl hook with a code chunk like this:
knitr::knit_hooks$set(webgl = rgl::hook_webgl)
options(rgl.useNULL = TRUE)
Then a chunk with the option
webgl = TRUE
can produce an embedded OpenGL image:
library(rgl)
bg3d(color = "white")
clear3d()
par3d(mouseMode = "trackball")
surface3d(eastseq, northseq,
soi.fit / 100,
color = rep("red", length(soi.fit)))
A view that includes the points and uses alpha blending to make the
surface translucent:
library(rgl)
clear3d()
points3d(soil$easting,
soil$northing,
soil$resistivity / 100,
col = rep("black", nrow(soil)))
surface3d(eastseq, northseq,
soi.fit / 100,
col = rep("red", length(soi.fit)),
alpha = 0.9,
front = "fill",
back = "fill")
A view of the volcano surface:
library(rgl)
knitr::knit_hooks$set(webgl = rgl::hook_webgl)
options(rgl.useNULL = TRUE)
clear3d()
surface3d(x = 10 * seq_len(nrow(volcano)),
y = 10 * seq_len(ncol(volcano)),
z = 2 * volcano, color = "red")
The WebGL
vignette in the rgl package shows some more
examples.
The embedded graphs show properly for me on most browser/OS
combinations I have tried.
To include a static view of an rgl scene you can use the
rgl.snapshot function.
To use the view found interactively with rgl for creating a
lattice wireframe view, you can use the
rglToLattice function:
wireframe(soi.fit ~ soi.grid$easting * soi.grid$northing,
aspect = c(asp, 0.7), shade = TRUE,
xlab = "Easting (km)",
ylab = "Northing (km)", screen = rglToLattice())
Ethanol data:
library(rgl)
clear3d()
with(ethanol, points3d(E, C / 5, NOx / 5,
size = 4, col = rep("black", nrow(ethanol))))
Quakes data:
library(rgl)
clear3d()
with(quakes, points3d(long, lat, -depth / 50, size = 2,
col = rep("black", nrow(quakes))))
Other Animation Technologies
Animated GIF or PNG images can be used to show a fly around
of a surface.
A fly-around can also be recorded as a movie.
A movie can be paused and replayed; animated images typically
cannot.
Alternatives and Variations
Interactive or animated views rely on our visual system’s ability to
extract depth
queues from motion.
This is called motion parallax .
When the motion stops, the 3D illusion is lost.
Other options for viewing a 3D scene that do not rquire motion:
A stereogram
This approach is or was used
You can create your own viewer with paper or cardboard tubes, for
example.
A stereo image for the soil data surface:
tp <- trellis.par.get()
trellis.par.set(theme = col.whitebg())
ocol <- trellis.par.get("axis.line")$col
oclip <- trellis.par.get("clip")$panel
trellis.par.set(list(axis.line = list(col = "transparent"),
clip = list(panel = FALSE)))
print(wireframe(soi.fit ~ soi.grid$easting * soi.grid$northing,
cuts = 9,
aspect = diff(range(soi.grid$n)) / diff(range(soi.grid$e)),
xlab = "Easting (km)",
ylab = "Northing (km)", shade = TRUE,
screen = list(z = 40, x = -70, y = 3),
shade.colors.palette = wfpal),
split = c(1, 1, 2, 1), more = TRUE)
print(wireframe(soi.fit ~ soi.grid$easting * soi.grid$northing,
cuts = 9,
aspect = diff(range(soi.grid$n)) / diff(range(soi.grid$e)),
xlab = "Easting (km)",
ylab = "Northing (km)", shade = TRUE,
screen = list(z = 40, x = -70, y = 0),
shade.colors.palette = wfpal),
split = c(2, 1, 2, 1))
A stereo view of the volcano surface:
library(lattice)
v1 <- wireframe(volcano, shade = TRUE,
aspect = c(61 / 87, 0.4),
light.source = c(10, 0, 10),
screen = list(z = 40, x = -60, y = 0),
shade.colors.palette = wfpal)
v2 <- wireframe(volcano, shade = TRUE,
aspect = c(61 / 87, 0.4),
light.source = c(10, 0, 10),
screen = list(z = 40, x = -60, y = 3),
shade.colors.palette = wfpal)
print(v1, split = c(1, 1, 2, 1), more = TRUE)
print(v2, split = c(2, 1, 2, 1))
A stereo view of the quakes data:
q1 <- cloud(-depth ~ long * lat, data = quakes, pch = 19, cex = 0.3,
screen = list(z = 40, x = -60, y = 0))
q2 <- cloud(-depth ~ long * lat, data = quakes, pch = 19, cex = 0.3,
screen = list(z = 40, x = -60, y = 3))
print(q1, split = c(1, 1, 2, 1), more = TRUE)
print(q2, split = c(2, 1, 2, 1))
Coplots for Surfaces
We can also use the idea of a coplot for examining a surface a few
slices at a time.
For the soil resistivity surface:
wireframe(
soi.fit ~
soi.grid$easting * soi.grid$northing,
cuts = 9,
aspect = diff(range(soi.grid$n)) /
diff(range(soi.grid$e)),
xlab = "Easting (km)",
ylab = "Northing (km)", shade = TRUE,
screen = list(z = 40, x = -70, y = 0),
shade.colors.palette = wfpal)
Choosing 12 approximately equally spaced slices along
easting:
sf <- soi.grid
sf$fit <- as.numeric(soi.fit)
sube <- eastseq[seq_len(length(eastseq)) %% 7 == 0]
sube <- eastseq[round(seq(1, length(eastseq), length.out = 12))]
ssf <- filter(sf, easting %in% sube)
ggplot(ssf, aes(x = northing, y = fit)) +
geom_line() +
scale_x_reverse() +
facet_wrap(~ easting)
For examining a surface this way we fix one variable at a specific
value.
For examining data it is also sometimes useful to choose a narrow
window.
Conditioning with a Single Plot
It is possible to show conditioning in a single plot using an
identity channel to distinguish the conditions.
sf <- soi.grid
sf$fit <- as.numeric(soi.fit)
sube4 <- eastseq[round(seq(1, length(eastseq), length.out = 4))]
ssf4 <- filter(sf, easting %in% sube4)
ggplot(mutate(ssf4, easting = factor(easting)),
aes(x = northing, y = fit,
color = easting,
group = easting)) +
geom_line()
This is most useful when the effect of the conditioning variable is a
level shift.
The number of different levels that can be used effectively is
lower.
Over-plotting becomes an issue when used with data points.
Example: Ozone Levels
From Cleveland, William S. (1993) Visualizing Data :
data(environmental, package = "lattice")
Daily measurements of ozone concentration, wind speed, temperature
and solar radiation in New York City from May to September of 1973.
A data frame with 111 observations on the following 4 variables.
ozone: Average ozone concentration
radiation: Solar radiation
temperature: Maximum daily emperature
wind: Average wind speed
pairs(environmental)
Cleveland suggests a cube root transformation for
ozone:
env2 <- mutate(environmental, ozone = ozone ^ (1 / 3))
pairs(env2)
There is monotone marginal relationship between the transformed
ozone and temperature.
It might be worth looking at this relationship for different levels
of wind and radiation.
Two levels each might be a good start:
env2 <- mutate(env2,
wind_cut = cut_number(wind, 2,
labels = c("low wind", "high wind")),
rad_cut = cut_number(radiation, 2,
labels = c("low rad", "high rad")))
ggplot(env2, aes(x = temperature, y = ozone)) +
geom_point() +
geom_smooth(method = "gam") +
facet_grid(rad_cut ~ wind_cut)
Faceting on wind:
ggplot(env2, aes(x = temperature, y = ozone, color = rad_cut)) +
geom_point() +
geom_smooth(method = "gam") +
facet_wrap(~ wind_cut)
Faceting on radiation:
ggplot(env2, aes(x = temperature, y = ozone, color = wind_cut)) +
geom_point() +
geom_smooth(method = "gam") +
facet_wrap(~ rad_cut)
Fit model; red at 1st quartile of wind, green at 3rd quartile:
library(dplyr)
library(ggplot2)
data(environmental, package = "lattice")
env2 <- mutate(environmental, ozone = ozone ^ (1 / 3))
tempseq <- seq(57, 97, length.out = 20)
radseq <- seq(7, 334, length.out = 20)
fit2 <- loess(ozone ~ wind * temperature * radiation,
span = 1.0, degree = 2, data = env2)
grid1 <- expand.grid(temperature = tempseq, radiation = radseq, wind = 7.5)
grid2 <- expand.grid(temperature = tempseq, radiation = radseq, wind = 11.5)
s1 <- predict(fit2, newdata = grid1)
s2 <- predict(fit2, newdata = grid2)
library(rgl)
clear3d()
surface3d(as.numeric(scale(tempseq)), as.numeric(scale(radseq)), s1,
color = rep("red", length(s1)))
surface3d(as.numeric(scale(tempseq)), as.numeric(scale(radseq)), s2,
color = rep("green", length(s2)))
axes3d()
title3d(xlab = "temp", ylab = "rad", zlab = "ozone")
3D Density Estimates
Density estimates can be used with 3D data as well.
The density is a function of three variables, so the density surface
is in 4D.
The points in 3D with a common density level form a contour
surface .
The contour surface for a higher density level will be inside the
surface for a lower level.
Some artificial data:
library(rgl)
library(misc3d)
x <- matrix(rnorm(9000), ncol = 3)
x[1001 : 2000, 2] <- x[1001 : 2000, 2] + 4
x[2001 : 3000, 3] <- x[2001 : 3000, 3] + 4
clear3d()
bg3d(col = "white")
points3d(x = x[, 1], y = x[, 2], z = x[, 3],
size = 2, color = "black")
3D plot of one contour surface:
g <- expand.grid(x = seq(-4, 8.5, len = 30),
y = seq(-4, 8.5, len = 30),
z = seq(-4, 8.5, len = 30))
d <- kde3d(x[, 1], x[, 2], x[, 3],
0.5, 30, c(-4, 8.5))$d
clear3d()
xv <- seq(-4, 8.5, len = 30)
contour3d(d, 20 / 3000, xv, xv, xv,
color = "red")
Adding the data:
d2 <- kde3d(x[, 1], x[, 2], x[, 3],
0.5, 30, c(-4, 8.5))$d
clear3d()
points3d(x = x[, 1], y = x[, 2], z = x[, 3],
size = 2, color = "black")
contour3d(d2, 20 / 3000, xv, xv, xv,
color = "red", alpha = 0.5,
add = TRUE)
3D plot of several contours for bw = 0.5:
clear3d()
contour3d(d2, c(10, 25, 40) / 3000, xv, xv, xv,
color = c("red", "green", "blue"),
alpha = c(0.2, 0.5, 0.7),
add = TRUE)
Density contour for the quakes data:
bg3d(col = "white")
clear3d()
d <- kde3d(quakes$long, quakes$lat, -quakes$depth, n = 40)
contour3d(d$d, level = exp(-12),
x = d$x / 22, y = d$y / 28, z = d$z / 640,
color = "green", ## color2 = "gray",
scale = FALSE, add = TRUE)
box3d(col = "gray")
Adding data and second contour:
clear3d()
points3d(quakes$long / 22, quakes$lat / 28, -quakes$depth / 640,
size = 2, col = rep("black", nrow(quakes)))
box3d(col = "gray")
d <- kde3d(quakes$long, quakes$lat, -quakes$depth, n = 40)
contour3d(d$d, level = exp(c(-10, -12)),
x = d$x / 22, y = d$y / 28, z = d$z / 640,
color = c("red", "green"), alpha = 0.1,
scale = FALSE, add = TRUE)
Parallel Coordinates Plots
The same idea as a slope graph, but usually with more variables.
Some references:
A post by
Robert Kosara.
Wikipedia
entry .
Paper
on recognizing mathematical objects in parallel coordinate plots.
Some R implementations include parallelplot() in
lattice and ggparcoord() in
GGally
A parallel coordinate plot of a data set on the chemical composition
of coffee samples:
library(gridExtra)
library(GGally)
data(coffee, package = "pgmm")
coffee <- mutate(coffee,
Type = ifelse(Variety == 1,
"Arabica",
"Robusta"))
ggparcoord(coffee[order(coffee$Type), ],
columns = 3 : 14,
groupColumn = "Type",
scale = "uniminmax") +
xlab("") +
ylab("") +
scale_colour_manual(
values = c("grey", "red")) +
theme(axis.ticks.y = element_blank(),
axis.text.y = element_blank(),
axis.text.x =
element_text(angle = 45,
vjust = 1,
hjust = 1),
legend.position = "top")
There are 43 samples from 29 countries and two varieties,
Arabica or Robusta .
Examining the plot shows that the varieties are distinguished by
their fat and caffeine contents:
ggplot(coffee,
aes(x = Fat,
y = Caffine,
colour = Type)) +
geom_point(size = 3) +
scale_colour_manual(
values = c("grey", "red"))
Some useful adjustments:
Interactive implementations that support these and more are
available.
Australian Crabs
The data frame crabs in the MASS package
contains measurement on crabs of a species that has been split into two
based on color, orange or blue.
Preserved specimens lose their color (and possibly ability to
identify sex).
Data were collected to help classify preserved specimens.
It would be useful to have a fairly simple classification rule.
A scatterplot matrix view:
data(crabs, package = "MASS")
## splom(~ crabs[4 : 8],
## group = paste(sex, sp),
## data = crabs,
## auto.key = TRUE,
## pscale = 0)
ggpairs(crabs,
aes(color = interaction(sp, sex)),
columns = 4 : 8,
upper = list(continuous = "points"),
legend = 1)
The variables shown are:
FL frontal lobe size (mm);
RW rear width (mm);
CL carapace length (mm);
CW carapace width (mm);
BD body depth (mm).
The variables are highly correlated, reflecting overall size and
age.
The RW * CL or RW * CW plots separate males
and females well, at least for larger crabs.
A parallel coordinates view:
ggparcoord(crabs,
columns = 4 : 8,
groupColumn = "sp") +
scale_color_manual(
values = c(B = "blue", O = "orange"))
A possible next step: Reduce the correlation by a scaling by one of
the variables.
cr <- mutate(crabs,
FLCL = FL / CL,
RWCL = RW / CL,
CWCL = CW / CL,
BDCL = BD / CL)
## splom(~ cr[9 : 12], group = sp,
## data = cr,
## auto.key = TRUE, pscale = 0)
ggpairs(cr, aes(color = sp),
columns = 9 : 12,
upper = list(continuous = "points"),
legend = 1) +
scale_color_manual(
values = c(B = "blue", O = "orange")) +
scale_fill_manual(
values = c(B = "blue", O = "orange"))
## Warning: No shared levels found between `names(values)` of the manual scale and the
## data's colour values.
## No shared levels found between `names(values)` of the manual scale and the
## data's colour values.
## Warning: No shared levels found between `names(values)` of the manual scale and the
## data's fill values.
## No shared levels found between `names(values)` of the manual scale and the
## data's fill values.
## No shared levels found between `names(values)` of the manual scale and the
## data's fill values.
## No shared levels found between `names(values)` of the manual scale and the
## data's fill values.
## Warning: No shared levels found between `names(values)` of the manual scale and the
## data's colour values.
## Warning: No shared levels found between `names(values)` of the manual scale and the
## data's fill values.
## No shared levels found between `names(values)` of the manual scale and the
## data's fill values.
## No shared levels found between `names(values)` of the manual scale and the
## data's fill values.
## No shared levels found between `names(values)` of the manual scale and the
## data's fill values.
## Warning: No shared levels found between `names(values)` of the manual scale and the
## data's colour values.
## Warning: No shared levels found between `names(values)` of the manual scale and the
## data's fill values.
## No shared levels found between `names(values)` of the manual scale and the
## data's fill values.
## No shared levels found between `names(values)` of the manual scale and the
## data's fill values.
## No shared levels found between `names(values)` of the manual scale and the
## data's fill values.
## Warning: No shared levels found between `names(values)` of the manual scale and the
## data's colour values.
The CWCL * FLCL plot shows good species separation by a
line.
A parallel coordinates plot after scaling by CL:
ggparcoord(cr,
columns = 9 : 12,
groupColumn = "sp") +
scale_color_manual(
values = c(B = "blue", O = "orange"))
Reorder the variables:
ggparcoord(cr,
columns = c(10, 9, 11, 12),
groupColumn = "sp") +
scale_color_manual(
values = c(B = "blue", O = "orange"))
Reorder again:
ggparcoord(cr,
columns = c(10, 9, 12, 11),
groupColumn = "sp") +
scale_color_manual(
values = c(B = "blue", O = "orange"))
Reverse the CWCL variable:
ggparcoord(mutate(cr, CWCL = -CWCL),
columns = c(10, 9, 12, 11),
groupColumn = "sp") +
scale_color_manual(
values = c(B = "blue", O = "orange"))
The patterns for FLCL, CWLC, and
BDCL for the two species differ.
Dimension Reduction by PCA
A number of methods are available for extracting a lower dimensional
representation of a data set that captures most important features.
One approach is principal component analysis , or
PCA .
Principal component analysis identifies a rotation of the data
that produces uncorrelated scores.
Components are ordered by the amount they contribute to the
overall variation in the data.
Sometimes the first few principal components capture most of the
interesting variation.
The components are linear combinations of the original variables;
they may not be easy to interpret.
The first two principal components for the scaled crabs data:
fit <- select(cr, 9 : 12) |>
mutate(across(everything(), scale)) |>
prcomp()
cr_PC <- cbind(cr, fit$x)
p <- ggplot(cr_PC, aes(PC1, PC2, color = sp)) +
geom_point() +
scale_color_manual(values = c(O = "orange", B = "blue"))
library(ggrepel)
p + geom_segment(aes(x = 0, y = 0, xend = PC1, yend = PC2),
data = as.data.frame(fit$rotation),
color = "black",
arrow = arrow(length = unit(0.03, "npc"))) +
geom_text_repel(aes(x = PC1, y = PC2,
label = rownames(fit$rotation)),
data = as.data.frame(fit$rotation),
color = "black")
The two species are separated quite well by the first principal
component alone.
For the coffee data the first principal component also separates the
varieties well:
fit <- select(coffee, 4:14) |>
mutate(across(everything(), scale)) |>
prcomp()
coffee_PC <- cbind(coffee, fit$x)
ggplot(coffee_PC, aes(x = PC1, y = PC2, color = Type)) +
geom_point(size = 3) +
scale_colour_manual(values = c("grey", "red"))
The first principal component is a weighted combination of the
original variables; there is some weight, positive or negative, on
almost every variable, which makes the result hard to interpret.
data.frame(variable = rownames(fit$rotation),
weight = as.vector(fit$rotation[, 1])) |>
ggplot(aes(x = weight, y = variable)) +
geom_col(width = 0.2)
Grand Tours
The grand tour can be viewed as carrying out a sequence of rotations
in high dimensional space and showing images of some of the
coordinates.
This can be useful for discovering groups, outliers, and some
lower-dimensional structures.
The rotations can be random or selected to optimize some criterion
(guided tours).
The tourr package provides one implementation.
Good interactive interfaces do not seem to be readily available at
the moment.
LS0tCnRpdGxlOiAiVmlzdWFsaXppbmcgVGhyZWUgb3IgTW9yZSBOdW1lcmljIFZhcmlhYmxlcyIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCi0tLQoKPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJzdGF0NDU4MC5jc3MiIHR5cGU9InRleHQvY3NzIiAvPgo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPiAucmVtYXJrLWNvZGUgeyBmb250LXNpemU6IDg1JTsgfSA8L3N0eWxlPgo8IS0tIHRpdGxlIGJhc2VkIG9uIFdpbGtlJ3MgY2hhcHRlciAtLT4KCmBgYHtyIHNldHVwLCBpbmNsdWRlID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0Kc291cmNlKGhlcmU6OmhlcmUoInNldHVwLlIiKSkKa25pdHI6Om9wdHNfY2h1bmskc2V0KGNvbGxhcHNlID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgZmlnLmhlaWdodCA9IDUsIGZpZy53aWR0aCA9IDYsIGZpZy5hbGlnbiA9ICJjZW50ZXIiKQoKc2V0LnNlZWQoMTIzNDUpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShsYXR0aWNlKQpsaWJyYXJ5KGdyaWRFeHRyYSkKbGlicmFyeShwYXRjaHdvcmspCnNvdXJjZShoZXJlOjpoZXJlKCJkYXRhc2V0cy5SIikpCnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKCkgKwogICAgICAgICAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYpLAogICAgICAgICAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG9yID0gImdyZXkzMCIsIGZpbGwgPSBOQSkpKQpgYGAKCgojIyBNb3ZpbmcgQmV5b25kIFR3byBEaW1lbnNpb25zCgpQYXBlciBhbmQgc2NyZWVucyBhcmUgdHdvLWRpbWVuc2lvbmFsLgoKV2UgbGl2ZSBpbiBhIHRocmVlLWRpbWVuc2lvbmFsIHdvcmxkLgoKRm9yIHZpc3VhbGl6aW5nIHRocmVlLWRpbWVuc2lvbmFsIGRhdGEgd2UgY2FuIHRha2UgYWR2YW50YWdlIG9mIG91cgp2aXN1YWwgc3lzdGVtJ3MgYWJpbGl0eSB0byByZWNvbnN0cnVjdCB0aHJlZS1kaW1lbnNpb25hbCBzY2VuZXMgZnJvbQp0d28tZGltZW5zaW9uYWwgaW1hZ2VzIHVzaW5nOgoKKiBwZXJzcGVjdGl2ZSByZW5kZXJpbmcsIGxpZ2h0aW5nLCBhbmQgc2hhZGluZzsKCiogbW90aW9uIHdpdGggYW5pbWF0aW9uIGFuZCBpbnRlcmFjdGlvbjsKCiogc3RlcmVvIHZpZXdpbmcgbWV0aG9kcy4KCk1vc3Qgb2YgdXMgaGF2ZSBubyBpbnR1aXRpb24gZm9yIGZvdXIgYW5kIG1vcmUgZGltZW5zaW9ucy4KClNvbWUgdGVjaG5pcXVlcyB0aGF0IHdvcmsgaW4gdGhyZWUgZGltZW5zaW9ucyBidXQgY2FuIGFsc28gYmUgdXNlZCBpbgpoaWdoZXIgZGltZW5zaW9uczoKCiogX0dyb3VwaW5nXyBieSBlbmNvZGluZyBhZGRpdGlvbmFsIHZhcmlhYmxlcyBpbiBjb2xvciBvciBzaGFwZSBjaGFubmVscy4KCiogX0NvbmRpdGlvbmluZ18gYnkgdXNpbmcgc21hbGwgbXVsdGlwbGVzIGZvciBkaWZmZXJlbnQgbGV2ZWxzIG9mCiAgYWRkaXRpb25hbCB2YXJpYWJsZXMuCgpIaWdoZXIgZGltZW5zaW9ucyBtYXliZSB1cCB0byB0ZW47IHRoZSBbX2N1cnNlIG9mCmRpbWVuc2lvbmFsaXR5X10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvQ3Vyc2Vfb2ZfZGltZW5zaW9uYWxpdHkpCmlzIGEgbGltaXRpbmcgZmFjdG9yLgoKVGhlIGBsYXR0aWNlYCBwYWNrYWdlIHByb3ZpZGVzIHNvbWUgZmFjaWxpdGllcyBub3QgZWFzaWx5IGF2YWlsYWJsZSBpbgpgZ2dwbG90YCBzbyBJIHdpbGwgdXNlIGBsYXR0aWNlYCBpbiBhIGZldyBleGFtcGxlcy4KCgojIyBTY2F0dGVycGxvdCBNYXRyaWNlcwoKQSBfc2NhdHRlcnBsb3QgbWF0cml4XyBpcyBhIHVzZWZ1bCBvdmVydmlldyB0aGF0IHNob3dzIGFsbCBwYWlyd2lzZQpzY2F0dGVycGxvdHMuCgpUaGVyZSBhcmUgbWFueSBvcHRpb25zIGZvciBjcmVhdGluZyBzY2F0dGVycGxvdCBtYXRyaWNlcyBpbiBSOyBhIGZldyBhcmU6CgoqIGBwYWlyc2AgaW4gYmFzZSBncmFwaGljczsKCiogYHNwbG9tYCBpbiBwYWNrYWdlIGBsYXR0aWNlYAoKKiBgZ2dwYWlyc2AgaW4gYEdHYWxseWAuCgpTb21lIGV4YW1wbGVzIHVzaW5nIHRoZSBgbXBnYCBkYXRhOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpsaWJyYXJ5KGxhdHRpY2UpCnNwbG9tKHNlbGVjdChtcGcsIGN0eSwgaHd5LCBkaXNwbCksCiAgICAgIGNleCA9IDAuNSwgcGNoID0gMTkpCmBgYAoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpsaWJyYXJ5KEdHYWxseSkKZ2dwYWlycyhzZWxlY3QobXBnLCBjdHksIGh3eSwgZGlzcGwpLAogICAgICAgIGxvd2VyID0gbGlzdChjb250aW51b3VzID0KICAgICAgICAgICAgICAgICAgICAgICAgIHdyYXAoInBvaW50cyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSAxKSkpCmBgYAoKU29tZSB2YXJpYXRpb25zOgoKKiBkaWFnb25hbCBsZWZ0LXRvcCB0byByaWdodC1ib3R0b20gb3IgbGVmdC1ib3R0b20gdG8gcmlnaHQtdG9wOwoKKiBob3cgdG8gdXNlIHRoZSBwYW5lbHMgaW4gdGhlIHR3byB0cmlhbmdsZXM7CgoqIGhvdyB0byB1c2UgdGhlIHBhbmVscyBvbiB0aGUgZGlhZ29uYWwuCgpTb21lIHRoaW5ncyB0byBsb29rIGZvciBpbiB0aGUgcGFuZWxzOgoKKiBjbHVzdGVycyBvciBzZXBhcmF0aW9uIG9mIGdyb3VwczsKCiogc3Ryb25nIHJlbGF0aW9uc2hpcHM7CgoqIG91dGxpZXJzLCByb3VuZGluZywgY2x1bXBpbmcuCgpOb3RlczoKCiogU2NhdHRlcnBsb3QgbWF0cmljZXMgd2VyZSBwb3B1bGFyaXplZCBieSBDbGV2ZWxhbmQgYW5kIGNvLXdvcmtlcnMgYXQKICBCZWxsIExhYm9yYXRvcmllcyBpbiB0aGUgMTk4MHMuCgoqIENsZXZlbGFuZCByZWNvbW1lbmRzIHVzaW5nIHRoZSBmdWxsIHZlcnNpb24gZGlzcGxheWluZyBib3RoIHRyaWFuZ2xlcyBvZgogIHBsb3RzIHRvIGZhY2lsaXRhdGUgX3Zpc3VhbCBsaW5raW5nXy4KCiogSWYgeW91IGRvIHVzZSBvbmx5IG9uZSB0cmlhbmdsZSwgYW5kIG9uZSB2YXJpYWJsZSBpcyBhIHJlc3BvbnNlLAogIHRoZW4gaXQgaXMgYSBnb29kIGlkZWEgdG8gYXJyYW5nZSBmb3IgdGhhdCB2YXJpYWJsZSB0byBiZSBzaG93biBvbgogIHRoZSB2ZXJ0aWNhbCBheGlzIGFnYWluc3QgYWxsIG90aGVyIHZhcmlhYmxlcy4KCiogVGhlIHN5bW1ldHJ5IGluIHRoZSBwbG90IHdpdGggdGhlIGRpYWdvbmFsIHJ1bm5pbmcgZnJvbSBib3R0b20tbGVmdAogIHRvIHRvcC10aWdodCBhcyBwcm9kdWNlZCBieSBgc3Bsb21gIGlzIHNpbXBsZXIgdGhhbiB0aGUgc3ltbWV0cnkgaW4KICB0aGUgcGxvdCB3aXRoIHRoZSBkaWFnb25hbCBydW5uaW5nIGZyb20gdG9wLWxlZnQgdCBib3R0b20tcmlnaHQKICBwcm9kdWNlZCBieSBgcGFpcnNgIGFuZCBgZ2dwYWlyc2AuCgoKIyMgVGhyZWUgRGF0YSBTZXRzCgpUaGVlIHVzZWZ1bCBkYXRhIHNldHMgdG8gZXhwbG9yZToKCiogVGhlIGBldGhhbm9sYCBkYXRhIGZyYW1lIGluIHRoZSBgbGF0dGljZWAgcGFja2FnZS4KCiogU29pbCByZXNpc3Rpdml0eSBkYXRhIGZyb20gZnJvbSBDbGV2ZWxhbmQncyBfVmlzdWFsaXppbmcgRGF0YV8gYm9vay4KCiogVGhlIGBxdWFrZXNgIGRhdGEgZnJhbWUgaW4gdGhlIGBkYXRhc2V0c2AgcGFja2FnZS4KCgojIyMgRXRoYW5vbCBEYXRhCgpUaGUgYGV0aGFub2xgIGRhdGEgZnJhbWUgaW4gdGhlIGBsYXR0aWNlYCBwYWNrYWdlIGNvbnRhaW5zIGRhdGEgZnJvbQphbiBleHBlcmltZW50IG9uIGVmZmljaWVuY3kgYW5kIGVtaXNzaW9ucyBpbiBzbWFsbCBvbmUtY3lsaW5kZXIKZW5naW5lcy4KClRoZSBkYXRhIGZyYW1lIGNvbnRhaW5zIDg4IG9ic2VydmF0aW9ucyBvbiB0aHJlZSB2YXJpYWJsZXM6CgoqIGBOT3hgOiBDb25jZW50cmF0aW9uIG9mIG5pdHJvZ2VuIG94aWRlcyAoTk8gYW5kIE5PMikgaW4gbWljcm9ncmFtcy4KCiogYENgIENvbXByZXNzaW9uIHJhdGlvIG9mIHRoZSBlbmdpbmUuCgoqIGBFYCBFcXVpdmFsZW5jZSByYXRpbywgYSBtZWFzdXJlIG9mIHRoZSByaWNobmVzcyBvZiB0aGUgYWlyIGFuZAogIGV0aGFub2wgZnVlbCBtaXh0dXJlLgoKQSBzY2F0dGVycGxvdCBtYXRyaXg6CgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmxpYnJhcnkobGF0dGljZSkKc3Bsb20oZXRoYW5vbCwgY2VkID0gMC41LCBwY2ggPSAxOSkKYGBgCgpBIGdvYWwgaXMgdG8gdW5kZXJzdGFuZCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIHBvbGx1dGFudCBgTk94YCBhbmQgdGhlCmNvbnRyb2xsYWJsZSB2YXJpYWJsZXMgYEVgIGFuZCBgQ2AuCgoKIyMjIFNvaWwgUmVzaXN0aXZpdHkgRGF0YQoKW0RhdGFdKGh0dHBzOi8vc3RhdC51aW93YS5lZHUvfmx1a2UvZGF0YS9zb2lsLmRhdCkgZnJvbSBDbGV2ZWxhbmQncwpfVmlzdWFsaXppbmcgRGF0YV8gYm9vayBjb250YWlucyBtZWFzdXJlbWVudHMgb2Ygc29pbCByZXNpc3Rpdml0eSBvZgphbiBhZ3JpY3VsdHVyYWwgZmllbGQgYWxvbmcgYSByb3VnaGx5IHJlY3Rhbmd1bGFyIGdyaWQuCgo8IS0tClNvbWV0aGluZyBpbnRlcmVzdGluZyBpcyBnb2luZyBvbiB3aXRoIHRoZSBgdHJhY2tgIHZhcmlhYmxlLCBidXQKSSdtIG5vdCBzdXJlIHdoYXQuCi0tPgoKQSBzY2F0dGVycGxvdCBtYXRyaXggb2YgdGhlIGByZXNpc3Rpdml0eWAsIGBub3J0aGluZ2AgYW5kIGBlYXN0aW5nYAp2YXJpYWJsZXM6CgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmlmICghIGZpbGUuZXhpc3RzKCJzb2lsLmRhdCIpKQogICAgZG93bmxvYWQuZmlsZSgiaHR0cDovL3d3dy5zdGF0LnVpb3dhLmVkdS9+bHVrZS9kYXRhL3NvaWwuZGF0IiwKICAgICAgICAgICAgICAgICAgInNvaWwuZGF0IikKc29pbCA8LSByZWFkLnRhYmxlKCJzb2lsLmRhdCIpCnNwbG9tKHNvaWxbMSA6IDNdLCBjZXggPSAwLjEsIHBjaCA9IDE5KQpgYGAKClRoZSBkYXRhIGlzIHF1aXRlIG5vaXN5IGJ1dCB0aGVyZSBpcyBzb21lIHN0cnVjdHVyZS4KCkEgZ29hbCBpcyB0byB1bmRlcnN0YW5kIGhvdyByZXNpc3Rpdml0eSB2YXJpZXMgYWNyb3NzIHRoZQpmaWVsZC4KCgojIyMgRWFydGggUXVha2UgTG9jYXRpb25zIGFuZCBNYWduaXR1ZGVzCgpUaGUgYHF1YWtlc2AgZGF0YSBmcmFtZSBjb250YWlucyBkYXRhIG9uIGxvY2F0aW9ucyBvZiBzZWlzbWljIGV2ZW50cwpvZiBtYWduaXR1ZGUgNC4wIG9yIGxhcmdlciBpbiBhIHJlZ2lvbiBuZWFyIEZpamkuCgpUaGUgdGltZSBmcmFtZSBpcyBmcm9tIDE5NjQgdG8gcGVyaGFwcyAyMDAwLgoKTW9yZSByZWNlbnQgZGF0YSBpcyBhdmFpbGFibGUgZnJvbSBhIG51bWJlciBvZiBzb3VyY2VzIG9uIHRoZSB3ZWIuCgpBIHNjYXR0ZXIgcGxvdCBtYXRyaXg6CgpgYGB7ciwgZmlnLmhlaWdodCA9IDcsIGZpZy5hbGlnbiA9ICJjZW50ZXIiLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KbGlicmFyeShsYXR0aWNlKQpzcGxvbShxdWFrZXMsIGNleCA9IDAuMSwgcGNoID0gMTkpCmBgYAoKUXVha2UgbG9jYXRpb25zOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQptZCA8LSBtYXBfZGF0YSgid29ybGQyIiwgYygiRmlqaSIsICJUb25nYSIsICJOZXcgWmVhbGFuZCIpKQpnZ3Bsb3QocXVha2VzLCBhZXMobG9uZywgbGF0KSkgKwogICAgZ2VvbV9wb2x5Z29uKGFlcyhncm91cCA9IGdyb3VwKSwgZGF0YSA9IG1kLCBjb2xvciA9ICJibGFjayIsIGZpbGwgPSBOQSkgKwogICAgZ2VvbV9wb2ludChzaXplID0gMC41LCBjb2xvciA9ICJyZWQiKSArCiAgICBjb29yZF9tYXAoKSArCiAgICBnZ3RoZW1lczo6dGhlbWVfbWFwKCkKYGBgCgpTb21lIGdvYWxzOgoKKiB1bmRlcnN0YW5kIHRoZSB0aHJlZS1kaW1lbnNpb25hbCBsb2NhdGlvbiBvZiB0aGUgcXVha2VzOwoKKiBzZWUgaWYgdGhlcmUgaXMgYW55IGFzc29jaWF0aW9uIGJldHdlZW4gbG9jYXRpb24gYW5kIG1hZ25pdHVkZS4KCgojIyBHcm91cGluZyBmb3IgQ29uZGl0aW9uaW5nCgpGb3IgdGhlIGBldGhhbm9sYCBkYXRhIHRoZXJlIGFyZSBvbmx5IGEgc21hbGwgbnVtYmVyIG9mIGRpc3RpbmN0CmxldmVscyBmb3IgYENgLgoKVGhpcyBzdWdnZXN0cyBjb25zaWRlcmluZyBhIHBsb3QgbWFwcGluZyB0aGUgbGV2ZWwgdG8gY29sb3IuCgpgYGB7ciBldGhhbm9sLWdyYWRpZW50LCBldmFsID0gRkFMU0V9CmdncGxvdChldGhhbm9sLAogICAgICAgYWVzKEUsIE5PeCwKICAgICAgICAgICBjb2xvciA9IEMpKSArCiAgICBnZW9tX3BvaW50KHNpemUgPSAyKQpgYGAKCmBgYHtyIGV0aGFub2wtZ3JhZGllbnQsIGVjaG8gPSBGQUxTRX0KYGBgCgpBIHF1YWxpdGF0aXZlIHNjaGVtZSBjYW4gaGVscCBkaXN0aW5ndWlzaCB0aGUgbGV2ZWxzLgoKYGBge3IgZXRoYW5vbC1mYWN0b3IsIGV2YWwgPSBGQUxTRX0KZ2dwbG90KGV0aGFub2wsCiAgICAgICBhZXMoRSwgTk94LAogICAgICAgICAgIGNvbG9yID0gb3JkZXJlZChDKSkpICsKICAgIGdlb21fcG9pbnQoc2l6ZSA9IDIpCmBgYAoKYGBge3IgZXRoYW5vbC1mYWN0b3IsIGVjaG8gPSBGQUxTRX0KYGBgCgpBZGRpbmcgc21vb3RocyBmdXJ0aGVyIGhlbHBzIHRoZSB2aXN1YWwgZ3JvdXBpbmc6CgpgYGB7ciBldGhhbm9sLCBldmFsID0gRkFMU0V9CmdncGxvdChldGhhbm9sLAogICAgICAgYWVzKEUsIE5PeCwKICAgICAgICAgICBjb2xvciA9IG9yZGVyZWQoQykpKSArCiAgICBnZW9tX3BvaW50KHNpemUgPSAyKSArCiAgICBnZW9tX3Ntb290aChzZSA9IEZBTFNFKQpgYGAKCmBgYHtyIGV0aGFub2wsIGVjaG8gPSBGQUxTRX0KYGBgClNvbWUgb2JzZXJ2YXRpb25zOgoKKiBBdCBlYWNoIGxldmVsIG9mIGBDYCB0aGVyZSBpcyBhIHN0cm9uZyBub24tbGluZWFyIHJlbGF0aW9uIGJldHdlZW4KICBgTk94YCBhbmQgYEVgLgoKKiBBdCBsZXZlbHMgb2YgYEVgIGFib3ZlIDEgdGhlIHZhbHVlIG9mIGBDYCBoYXMgbGl0dGxlIGVmZmVjdC4KCiogRm9yIGxvd2VyIGxldmVscyBvZiBgRWAgdGhlIGBOT3hgIGxldmVsIGluY3JlYXNlcyB3aXRoIGBDYC4KCkZvciB0aGUgYHF1YWtlc2AgZGF0YSwgYnJlYWtpbmcgdGhlIGBkZXB0aGAgdmFsdWVzIGludG8gdGhpcmRzIGdpdmVzCnNvbWUgaW5zaWdodHM6CgpgYGB7ciBxdWFrZXMtdGhpcmRzLCBldmFsID0gRkFMU0V9CnF1YWtlczIgPC0KICAgIG11dGF0ZShxdWFrZXMsCiAgICAgICAgICAgZGVwdGhfY3V0ID0gY3V0X251bWJlcihkZXB0aCwgMykpCmdncGxvdChxdWFrZXMyLCBhZXMoeCA9IGxvbmcsCiAgICAgICAgICAgICAgICAgICAgeSA9IGxhdCwKICAgICAgICAgICAgICAgICAgICBjb2xvciA9IGRlcHRoX2N1dCkpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICB0aGVtZV9idygpICsKICAgIGNvb3JkX21hcCgpCmBgYAoKYGBge3IgcXVha2VzLXRoaXJkcywgZWNobyA9IEZBTFNFfQpgYGAKCgojIyBDb25kaXRpb25pbmcgUGxvdHMgKENvcGxvdHMpCgpPbmUgd2F5IHRvIHRyeSB0byBnZXQgYSBoYW5kbGUgb24gaGlnaGVyIGRpbWVuc2lvbmFsIGRhdGEgaXMgdG8gdHJ5IHRvCmZpeCB2YWx1ZXMgb2Ygc29tZSB2YXJpYWJsZXMgYW5kIHZpc3VhbGl6ZSB0aGUgdmFsdWVzIG9mIG90aGVycyBpbiAyRC4KClRoaXMgY2FuIGJlIGRvbmUgd2l0aAoKKiBpbnRlcmFjdGl2ZSB0b29sczsKCiogc21hbGwgbXVsdGlwbGVzIHdpdGggbGF0dGljZS90cmVsbGlzIGRpc3BsYXlzIG9yIGZhY2V0aW5nLgoKQSBjb25kaXRpb25pbmcgcGxvdCwgb3IgX2NvcGxvdF86CgoqIFNob3dzIGEgY29sbGVjdGlvbiBvZiBwbG90cyBvZiB0d28gdmFyaWFibGVzIGZvciBkaWZmZXJlbnQKICBzZXR0aW5ncyBvZiBvbmUgb3IgbW9yZSBhZGRpdGlvbmFsIHZhcmlhYmxlcywgdGhlIGNvbmRpdGlvbmluZwogIHZhcmlhYmxlcy4KCiogRm9yIG9yZGVyZWQgY29uZGl0aW9uaW5nIHZhcmlhYmxlcyB0aGUgcGxvdHMgYXJlIGFycmFuZ2VkIGluIGEgd2F5CiAgdGhhdCByZWZsZWN0cyB0aGUgb3JkZXIuCgoqIFdoZW4gYSBjb25kaXRpb25pbmcgdmFyaWFibGUgaXMgbnVtZXJpYywgb3Igb3JkZXJlZCBjYXRlZ29yaWNhbCB3aXRoCiAgbWFueSBsZXZlbHMsIHRoZSB2YWx1ZXMgb2YgdGhlIGNvbmRpdGlvbmluZyB2YXJpYWJsZSBhcmUgZ3JvdXBlZAogIGludG8gYmlucy4KCkZvciB0aGUgc29pbCByZXNpc3Rpdml0eSBkYXRhLCBhIGNvcGxvdCBvZiBgcmVzaXN0aXZpdHlgIGFnYWluc3QKYGVhc3RpbmdgLCBjb25kaXRpb25pbmcgb24gYG5vcnRoaW5nYCB3aXRoIGJpbnMgb2Ygc2l6ZSAwLjU6CgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CnAxIDwtIGdncGxvdChzb2lsLAogICAgICAgICAgICAgYWVzKGVhc3RpbmcsIHJlc2lzdGl2aXR5KSkgKwogICAgZ2VvbV9wb2ludChzaXplID0gMC41KSArCiAgICBmYWNldF93cmFwKH4gY3V0X3dpZHRoKG5vcnRoaW5nLAogICAgICAgICAgICAgICAgICAgICAgICAgICB3aWR0aCA9IDAuNSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgY2VudGVyID0gMCkpCnAxCmBgYAoKQWRkaW5nIGEgc21vb3RoIGlzIG9mdGVuIGhlbHBmdWwuCgpXaXRoIGEgbGFyZ2UgYW1vdW50IG9mIGRhdGEgdGhlIHNtb290aCBpcyBoYXJkIHRvIHNlZS4KCiBTb21lIG9wdGlvbnM6CgoqIE9taXQgdGhlIGRhdGEgYW5kIG9ubHkgc2hvdyB0aGUgc21vb3RoLgoKKiBTaG93IHRoZSBkYXRhIGluIGEgbGVzcyBpbnRlbnNlIGNvbG9yLCBzdWNoIGFzIGxpZ2h0IGdyYXkuCgoqIFVzZSBhIGNvbnRyYXN0aW5nIGNvbG9yIGZvciB0aGUgc21vb3RoIGN1cnZlcy4KCiogU2hvdyB0aGUgZGF0YSB1c2luZyBhbHBoYSBibGVuZGluZy4KClRoaXMgdXNlcyBhIG11dGVkIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBkYXRhOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpwMiA8LSBnZ3Bsb3Qoc29pbCwKICAgICAgICAgICAgIGFlcyhlYXN0aW5nLCByZXNpc3Rpdml0eSkpICsKICAgIGdlb21fcG9pbnQoc2l6ZSA9IDAuNSwKICAgICAgICAgICAgICAgY29sb3IgPSAibGlnaHRncmV5IikgKwogICAgZmFjZXRfd3JhcCh+IGN1dF93aWR0aChub3J0aGluZywKICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lkdGggPSAwLjUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbnRlciA9IDApKSArCiAgICBnZW9tX3Ntb290aCgpCnAyCmBgYAoKVGhlIGNvbmRpdGlvbmluZyBiaW5zIGFyZSBxdWl0ZSB3aWRlLgoKVXNpbmcgcm91bmRpbmcgYW5kIGtlZXBpbmcgb25seSBwb2ludHMgd2l0aGluIDAuMDUgb2YgdGhlIHJvdW5kZWQKdmFsdWVzIHJlZHVjZXMgdGhlIHZhcmlhYmlsaXR5OgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpzb2lsX3RybSA8LQogICAgbXV0YXRlKHNvaWwsCiAgICAgICAgICAgbnJuZCA9IHJvdW5kKG5vcnRoaW5nICogMikgLyAyKSB8PgogICAgZmlsdGVyKGFicyhub3J0aGluZyAtIG5ybmQpIDwgMC4wNSkKcDEgKyBzb2lsX3RybSArCiAgICBmYWNldF93cmFwKH4gY3V0X3dpZHRoKG5vcnRoaW5nLAogICAgICAgICAgICAgICAgICAgICAgICAgICB3aWR0aCA9IDAuMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgY2VudGVyID0gMCkpCmBgYAoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpwMiArIHNvaWxfdHJtICsKICAgIGZhY2V0X3dyYXAofiBjdXRfd2lkdGgobm9ydGhpbmcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHdpZHRoID0gMC4xLAogICAgICAgICAgICAgICAgICAgICAgICAgICBjZW50ZXIgPSAwKSkKYGBgCgpGb3IgdGhlIHF1YWtlcyBkYXRhIGEgcGxvdCBvZiBsYXRpdHVkZSBhZ2FpbnN0IGxvbmdpdHVkZSBjb25kaXRpb25lZApvbiB0aHJlZSBkZXB0aCBsZXZlbHM6CgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CnF0aG0gPC0gdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG9yID0gImdyZXkzMCIsIGZpbGwgPSBOQSkpCmdncGxvdChxdWFrZXMyLCBhZXMoeCA9IGxvbmcsIHkgPSBsYXQpKSArCiAgICBnZW9tX3BvaW50KGNvbG9yID0gc2NhbGVzOjptdXRlZCgiYmx1ZSIpLAogICAgICAgICAgICAgICBzaXplID0gMC41KSArCiAgICBmYWNldF93cmFwKH4gZGVwdGhfY3V0LAogICAgICAgICAgICAgICBucm93ID0gMSkgKwogICAgY29vcmRfbWFwKCkgKwogICAgcXRobQpgYGAKClRoZSByZWxhdGl2ZSBwb3NpdGlvbnMgb2YgdGhlIGRlcHRoIGdyb3VwcyBhcmUgbXVjaCBoYXJkZXIgdG8gc2VlIHRoYW4KaW4gdGhlIGdyb3VwZWQgY29uZGl0aW9uaW5nIHBsb3QuCgpBZGRpbmcgdGhlIGZ1bGwgZGF0YSBmb3IgYmFja2dyb3VuZCBjb250ZXh0LCBhbmQgdXNpbmcgYSBtb3JlIGludGVuc2UKY29sb3IgZm9yIHRoZSBwYW5lbCBzdWJzZXQsIGhlbHBzIGEgbG90OgoKYGBge3IsIGV2YWwgPSBUUlVFLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KIyMgcXVha2VzIGRvZXMgbm90IGNvbnRhaW4gdGhlIGRlcHRoX2N1dAojIyB2YXJpYWJsZSB1c2VkIGluIHRoZSBmYWNldApnZ3Bsb3QocXVha2VzMiwgYWVzKHggPSBsb25nLCB5ID0gbGF0KSkgKwogICAgZ2VvbV9wb2ludChkYXRhID0gcXVha2VzLAogICAgICAgICAgICAgICBjb2xvciA9ICJncmF5Iiwgc2l6ZSA9IDAuNSkgKwogICAgZ2VvbV9wb2ludChjb2xvciA9ICJibHVlIiwgc2l6ZSA9IDAuNSkgKwogICAgZmFjZXRfd3JhcCh+IGRlcHRoX2N1dCwgbnJvdyA9IDEpICsKICAgIGNvb3JkX21hcCgpICsKICAgIHF0aG0KYGBgCgpTd2l0Y2hpbmcgbGF0aXR1ZGUgYW5kIGRlcHRoIHNob3dzIGFub3RoZXIgYXNwZWN0OgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpxdWFrZXMzIDwtCiAgICBtdXRhdGUocXVha2VzLAogICAgICAgICAgIGxhdF9jdXQgPSBjdXRfd2lkdGgobGF0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lkdGggPSA1LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm91bmRhcnkgPSAwKSkKZ2dwbG90KHF1YWtlczMsIGFlcyh4ID0gbG9uZywgeSA9IGRlcHRoKSkgKwogICAgZ2VvbV9wb2ludChkYXRhID0gcXVha2VzLAogICAgICAgICAgICAgICBjb2xvciA9ICJncmF5Iiwgc2l6ZSA9IDAuNSkgKwogICAgZ2VvbV9wb2ludChjb2xvciA9ICJibHVlIiwgc2l6ZSA9IDAuNSkgKwogICAgc2NhbGVfeV9yZXZlcnNlKCkgKwogICAgZmFjZXRfd3JhcCh+IGxhdF9jdXQpICsKICAgIHF0aG0KYGBgCgpDb3Bsb3QgZm9yIHRoZSBgZXRoYW5vbGAgZGF0YToKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpnZ3Bsb3QoZXRoYW5vbCwgYWVzKHggPSBFLCB5ID0gTk94KSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGZhY2V0X3dyYXAofiBDKQpgYGAKCkFkZGluZyBtdXRlZCBmdWxsIGRhdGEgZm9yIGNvbnRleHQ6CmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZ2dwbG90KGV0aGFub2wsIGFlcyh4ID0gRSwgeSA9IE5PeCkpICsKICAgIGdlb21fcG9pbnQoY29sb3IgPSAiZ3JleSIsCiAgICAgICAgICAgICAgIGRhdGEgPSBtdXRhdGUoZXRoYW5vbCwgQyA9IE5VTEwpKSArCiAgICBnZW9tX3BvaW50KCkgKwogICAgZmFjZXRfd3JhcCh+IEMpCmBgYAoKCiMjIENvbnRvdXIgYW5kIExldmVsIFBsb3RzIGZvciBTdXJmYWNlcwoKQSBudW1iZXIgb2YgbWV0aG9kcyBjYW4gYmUgdXNlZCB0byBlc3RpbWF0ZSBhIHNtb290aCBzaWduYWwgc3VyZmFjZSBhcwphIGZ1bmN0aW9uIG9mIHRoZSB0d28gbG9jYXRpb24gdmFyaWFibGVzLgoKT25lIG9wdGlvbiBpcyB0aGUgYGxvZXNzYCBsb2NhbCBwb2x5bm9taWFsIHNtb290aGVyOyBhbm90aGVyIGlzIGBnYW1gCmZyb20gcGFja2FnZSBgbWdjdmAuCgpUaGUgZXN0aW1hdGVkIHN1cmZhY2UgbGV2ZWwgY2FuIGJlIGNvbXB1dGVkIG9uIGEgZ3JpZCBvZiBwb2ludHMgdXNpbmcKdGhlIGBwcmVkaWN0YCBtZXRob2Qgb2YgdGhlIGZpdC4KClRoZXNlIGVzdGltYXRlZCBzdXJmYWNlcyBjYW4gYmUgdmlzdWFsaXplZCB1c2luZyBjb250b3VyIHBsb3RzIG9yCmxldmVsIHBsb3RzLgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQptIDwtIGxvZXNzKHJlc2lzdGl2aXR5IH4gZWFzdGluZyAqIG5vcnRoaW5nLCBzcGFuID0gMC4yNSwKICAgICAgICAgICBkZWdyZWUgPSAyLCBkYXRhID0gc29pbCkKZWFzdHNlcSA8LSBzZXEoLjE1LCAxLjQxMCwgYnkgPSAuMDE1KQpub3J0aHNlcSA8LSBzZXEoLjE1MCwgMy42NDUsIGJ5ID0gLjAxNSkKc29pLmdyaWQgPC0gZXhwYW5kLmdyaWQoZWFzdGluZyA9IGVhc3RzZXEsIG5vcnRoaW5nID0gbm9ydGhzZXEpCnNvaS5maXQgPC0gcHJlZGljdChtLCBzb2kuZ3JpZCkKc29pLmZpdC5kZiA8LSBtdXRhdGUoc29pLmdyaWQsIGZpdCA9IGFzLm51bWVyaWMoc29pLmZpdCkpCmBgYAoKCiMjIyBDb250b3VyIFBsb3RzCgpfQ29udG91ciBwbG90c18gY29tcHV0ZSBjb250b3Vycywgb3IgbGV2ZWwgY3VydmVzLCBhcyBwb2x5Z29ucyBhdCBhCnNldCBvZiBsZXZlbHMuCgpDb250b3VyIHBsb3RzIGRyYXcgdGhlIGxldmVsIGN1cnZlcywgb2Z0ZW4gd2l0aCBhIGxldmVsIGFubm90YXRpb24uCgpDb250b3VyIHBsb3RzIGNhbiBhbHNvIGhhdmUgdGhlaXIgcG9seWdvbnMgZmlsbGVkIGluIHdpdGggY29sb3JzCnJlcHJlc2VudGluZyB0aGUgbGV2ZWxzLgoKQSBiYXNpYyBjb250b3VyIHBsb3Qgb2YgdGhlIGZpdCBzb2lsIHJlc2lzdGl2aXR5IHN1cmZhY2UgaW4gYGdncGxvdGAKdXNpbmcgYGdlb21fY29udG91cmA6CgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CnAgPC0gZ2dwbG90KHNvaS5maXQuZGYsCiAgICAgICAgICAgIGFlcyh4ID0gZWFzdGluZywKICAgICAgICAgICAgICAgIHkgPSBub3J0aGluZywKICAgICAgICAgICAgICAgIHogPSBmaXQpKSArCiAgICBjb29yZF9maXhlZCgpCnAgKyBnZW9tX2NvbnRvdXIoKQpgYGAKCk5laXRoZXIgYGxhdHRpY2VgIG5vciBgZ2dwbG90YCBzZWVtIHRvIG1ha2UgaXQgZWFzeSB0byBmaWxsIGluIHRoZQpjb250b3Vycy4KClRoZSBiYXNlIGZ1bmN0aW9uIGBmaWxsZWQuY29udG91cmAgaXMgYXZhaWxhYmxlIGZvciB0aGlzOgoKYGBge3IsIGZpZy5hc3AgPSAxLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KY20ucmV2IDwtIGZ1bmN0aW9uKC4uLikgcmV2KGNtLmNvbG9ycyguLi4pKQpmaWxsZWQuY29udG91cihlYXN0c2VxLCBub3J0aHNlcSwgc29pLmZpdCwKICAgICAgICAgICAgICAgYXNwID0gMSwKICAgICAgICAgICAgICAgY29sb3IucGFsZXR0ZSA9IGNtLnJldikKYGBgCgoKIyMjIExldmVsIFBsb3RzCgpBIF9sZXZlbCBwbG90XyBjb2xvcnMgYSBncmlkIHNwYW5uZWQgYnkgdHdvIHZhcmlhYmxlcyBieSB0aGUgY29sb3Igb2YgYQp0aGlyZCB2YXJpYWJsZS4KCkxldmVsIHBsb3RzIGFyZSBhbHNvIGNhbGxlZCBfaW1hZ2UgcGxvdHNfCgpUaGUgdGVybSBfaGVhdCBtYXBfIGlzIGFsc28gdXNlZCwgaW4gcGFydGljdWxhciB3aXRoIGEgc3BlY2lmaWMgY29sb3IKc2NoZW1lLgoKQnV0IGhlYXQgbWFwIG9mdGVuIG1lYW5zIGEgbW9yZSBjb21wbGV4IHZpc3VhbGl6YXRpb24gd2l0aAphbiBpbWFnZSBwbG90IGF0IGl0cyBjb3JlLgoKYGdncGxvdGAgcHJvdmlkZXMgYGdlb21fdGlsZWAgdGhhdCBjYW4gYmUgdXNlZCBmb3IgYSBsZXZlbCBwbG90OgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpwICsgZ2VvbV90aWxlKGFlcyhmaWxsID0gZml0KSkgKwogICAgc2NhbGVfZmlsbF9ncmFkaWVudG4oCiAgICAgICAgY29sb3JzID0gcmV2KGNtLmNvbG9ycygxMDApKSkKYGBgCgpTdXBlcmltcG9zaW5nIGNvbnRvdXJzIG9uIGEgbGV2ZWwgcGxvdCBpcyBvZnRlbiBoZWxwZnVsLgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpwICsgZ2VvbV90aWxlKGFlcyhmaWxsID0gZml0KSkgKwogICAgZ2VvbV9jb250b3VyKCkgKwogICAgc2NhbGVfZmlsbF9ncmFkaWVudG4oCiAgICAgICAgY29sb3JzID0gcmV2KGNtLmNvbG9ycygxMDApKSkKYGBgCgpMZXZlbCBwbG90cyBkbyBub3QgcmVxdWlyZSBjb21wdXRpbmcgY29udG91cnMsIGJ1dCBhcmUgbm90IGFzCnNtb290aCBhcyBmaWxsZWQgY29udG91ciBwbG90cy4KClZpc3VhbGx5LCBpbWFnZSBwbG90cyBhbmQgZmlsbGVkIGNvbnRvdXIgcGxvdHMgYXJlIHZlcnkgc2ltaWxhciBmb3IKZmluZSBncmlkcywgYnV0IGltYWdlIHBsb3RzIGFyZSBsZXNzIHNtb290aCBmb3IgY29hcnNlIG9uZXMuCgpMYWNrIG9mIHNtb290aG5lc3MgaXMgbGVzcyBvZiBhbiBpc3N1ZSB3aGVuIHRoZSBkYXRhIHZhbHVlcwp0aGVtc2VsdmVzIGFyZSBub2lzeS4KClRoZSBncmlkIGZvciB0aGUKW2B2b2xjYW5vYCBkYXRhXShodHRwczovL3RlYXJhLmdvdnQubnovZW4vcGhvdG9ncmFwaC8zOTIwL21hdW5nYXdoYXUtbXQtZWRlbikKc2V0IGlzIGNvYXJzZXIgYW5kIGlsbHVzdHJhdGVzIHRoZSBsYWNrIG9mIHNtb290aG5lc3MuCgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CnZkIDwtIGV4cGFuZC5ncmlkKHggPSBzZXFfbGVuKG5yb3codm9sY2FubykpLCB5ID0gc2VxX2xlbihuY29sKHZvbGNhbm8pKSkKdmQkeiA8LSBhcy5udW1lcmljKHZvbGNhbm8pCmdncGxvdCh2ZCwgYWVzKHgsIHksIGZpbGwgPSB6KSkgKwogICAgZ2VvbV90aWxlKCkgKwogICAgc2NhbGVfZmlsbF9ncmFkaWVudG4oY29sb3JzID0gcmV2KGNtLmNvbG9ycygxMDApKSkgKwogICAgY29vcmRfZXF1YWwoKQpgYGAKCkEgYGZpbGxlZC5jb250b3VyYCBwbG90IGxvb2tzIGxpa2UgdGhpczoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZmlsbGVkLmNvbnRvdXIoc2VxX2xlbihucm93KHZvbGNhbm8pKSwKICAgICAgICAgICAgICAgc2VxX2xlbihuY29sKHZvbGNhbm8pKSwKICAgICAgICAgICAgICAgdm9sY2FubywKICAgICAgICAgICAgICAgbmxldmVscyA9IDEwLAogICAgICAgICAgICAgICBjb2xvci5wYWxldHRlID0gY20ucmV2LAogICAgICAgICAgICAgICBhc3AgPSAxKQpgYGAKCkEgY29hcnNlIGdyaWQgY2FuIGJlIGludGVycG9sYXRlZCB0byBhIGZpbmVyIGdyaWQuCgpJcnJlZ3VsYXJseSBzcGFjZWQgZGF0YSBjYW4gYWxzbyBiZSBpbnRlcnBvbGF0ZWQgdG8gYSBncmlkLgoKVGhlIGBpbnRlcnBgIGZ1bmN0aW9uIGluIHRoZQpbYGFraW1hYF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvcGFja2FnZT1pbnRlcnApIG9yCltgaW50ZXJwYF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvcGFja2FnZT1pbnRlcnApIHBhY2thZ2VzIGlzCnVzZWZ1bCBmb3IgdGhpcyBraW5kIG9mIGludGVycG9sYXRpb24uCgooYGludGVycGAgaGFzIGEgbW9yZSBwZXJtaXNzaXZlIGxpY2Vuc2UuKQoKCiMjIFRocmVlLURpbWVuc2lvbmFsIFZpZXdzCgpUaGVyZSBhcmUgc2V2ZXJhbCBvcHRpb25zIGZvciB2aWV3aW5nIHN1cmZhY2VzIG9yIGNvbGxlY3Rpb25zIG9mCnBvaW50cyBhcyB0aHJlZS1kaW1lbnNpb25hbCBvYmplY3RzOgoKKiBGaXhlZCB2aWV3cyBvZiByb3RhdGVkIHByb2plY3Rpb25zLgoKKiBBbmltYXRlZCBvciBpbnRlcmFjdGl2ZSB2aWV3cyBzaG93aW5nIGEgc2VxdWVuY2Ugb2Ygcm90YXRlZCBwcm9qZWN0aW9ucy4KCgojIyMgRml4ZWQgVmlld3MKClRoZSBgbGF0dGljZWAgZnVuY3Rpb24gYGNsb3VkKClgIHNob3dzIGEgcHJvamVjdGlvbiBvZiBhIHJvdGF0ZWQgcG9pbnQKY2xvdWQgaW4gdGhyZWUgZGltZW5zaW9ucy4KCkZvciB0aGUgc29pbCByZXNpc3Rpdml0eSBkYXRhOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpjbG91ZChyZXNpc3Rpdml0eSB+IGVhc3RpbmcgKiBub3J0aGluZywKICAgICAgcGNoID0gMTksIGNleCA9IDAuMSwgZGF0YSA9IHNvaWwpCmBgYAoKRm9yIHRoZSBgcXVha2VzYCBkYXRhOgoKYGBge3J9CmNsb3VkKC1kZXB0aCB+IGxvbmcgKiBsYXQsCiAgICAgIGRhdGEgPSBxdWFrZXMpCmBgYAoKQSBzdXJmYWNlIGNhbiBhbHNvIGJlIHZpc3VhbGl6ZWQgdXNpbmcgYSBfd2lyZSBmcmFtZV8gcGxvdCBzaG93aW5nIGEKM0QgdmlldyBvZiB0aGUgc3VyZmFjZSBmcm9tIGEgcGFydGljdWxhciB2aWV3cG9pbnQuCgpBIHNpbXBsZSB3aXJlIGZyYW1lIHBsb3QgaXMgb2Z0ZW4gc3VmZmljaWVudC4KCkxpZ2h0aW5nIGFuZCBzaGFkaW5nIGNhbiBiZSB1c2VkIHRvIGVuaGFuY2UgdGhlIDNEIGVmZmVjdC4KCkEgYmFzaWMgd2lyZSBmcmFtZSBwbG90IGZvciB0aGUgdm9sY2FubyBkYXRhOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQp3aXJlZnJhbWUoeiB+IHggKiB5LAogICAgICAgICAgZGF0YSA9IHZkLAogICAgICAgICAgYXNwZWN0ID0gYyg2MSAvIDg5LCAwLjMpKQpgYGAKCl9XaXJlIGZyYW1lXyBpcyBhIGJpdCBvZiBhIG1pc25vbWVyIHNpbmNlIHN1cmZhY2UgcGFuZWxzIGluIGZyb250Cm9jY2x1ZGUgbGluZXMgYmVoaW5kIHRoZW0uCgpGb3IgYSBmaW5lIGdyaWQsIGFzIGluIHRoZSBzb2lsIHN1cmZhY2UsIHRoZSBsaW5lcyBhcmUgdG9vIGRlbnNlLgoKVGhlIHVzZSBvZiBzaGFkaW5nIGZvciB0aGUgc3VyZmFjZXMgY2FuIGhlbHAuCgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CndmcGFsIDwtIG1ha2VTaGFkZVBhbGV0dGUoY20uY29sb3JzKDEwKSwgcHJlZiA9IDAuMikKd2lyZWZyYW1lKHogfiB4ICogeSwKICAgICAgICAgIGRhdGEgPSB2ZCwKICAgICAgICAgIGFzcGVjdCA9IGMoNjEgLyA4OSwgMC4zKSwKICAgICAgICAgIHNoYWRlID0gVFJVRSwKCSAgc2hhZGUuY29sb3JzLnBhbGV0dGUgPSB3ZnBhbCkKYGBgCgpBIHdpcmUgZnJhbWUgcGxvdCB3aXRoIHNoYWRpbmcgZm9yIHRoZSBmaXQgc29pbCByZXNpc3Rpdml0eSBzdXJmYWNlOgoKYGBge3J9CmFzcCA8LSB3aXRoKHNvaS5ncmlkLAogICAgICAgICAgICBkaWZmKHJhbmdlKG5vcnRoaW5nKSkgLwogICAgICAgICAgICBkaWZmKHJhbmdlKGVhc3RpbmcpKSkKd2YgPC0gd2lyZWZyYW1lKAogICAgc29pLmZpdCB+CiAgICAgICAgc29pLmdyaWQkZWFzdGluZyAqIHNvaS5ncmlkJG5vcnRoaW5nLAogICAgYXNwZWN0ID0gYXNwLCBzaGFkZSA9IFRSVUUsCiAgICBzY3JlZW4gPSBsaXN0KHogPSAtNTAsIHggPSAtMzApLAogICAgeGxhYiA9ICJFYXN0aW5nIChrbSIsCiAgICB5bGFiID0gIk5vcnRoaW5nIChrbSkiLAogICAgc2hhZGUuY29sb3JzLnBhbGV0dGUgPSB3ZnBhbCkKd2YKYGBgCgpCb3RoIHdheXMgb2YgbG9va2luZyBhdCBhIHN1cmZhY2UgYXJlIHVzZWZ1bDoKCmBgYHtyLCBmaWcuaGVpZ2h0ID0gNiwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9Cmx2IDwtCiAgICBsZXZlbHBsb3Qoc29pLmZpdCB+CiAgICAgICAgICAgICAgICAgIHNvaS5ncmlkJGVhc3RpbmcgKgogICAgICAgICAgICAgICAgICBzb2kuZ3JpZCRub3J0aGluZywKICAgICAgICAgICAgICBjdXRzID0gOSwKICAgICAgICAgICAgICBhc3BlY3QgPSBhc3AsCiAgICAgICAgICAgICAgY29udG91ciA9IFRSVUUsCiAgICAgICAgICAgICAgeGxhYiA9ICJFYXN0aW5nIChrbSkiLAogICAgICAgICAgICAgIHlsYWIgPSAiTm9ydGhpbmcgKGttKSIsCgkgICAgICBjb2wucmVnaW9ucyA9IGNtLmNvbG9ycykKcHJpbnQobHYsIHNwbGl0ID0gYygxLCAxLCAyLCAxKSwKICAgICAgbW9yZSA9IFRSVUUpCnByaW50KHdmLCBzcGxpdCA9IGMoMiwgMSwgMiwgMSkpCmBgYAoKKiBUaGUgbGV2ZWwgcGxvdC9jb250b3VyIHJlcHJlc2VudGF0aW9uIGlzIHVzZWZ1bCBmb3IgcmVjb2duaXppbmcKICBsb2NhdGlvbnMgb2Yga2V5IGZlYXR1cmVzLgoKKiBUaGUgd2lyZSBmcmFtZSB2aWV3IGhlbHBzIGJ1aWxkIGEgbWVudGFsIG1vZGVsIG9mIHRoZSAzRCBzdHJ1Y3R1cmUuCgoqIEJlaW5nIGFibGUgdG8gaW50ZXJhY3RpdmVseSBhZGp1c3QgdGhlIHZpZXdpbmcgcG9zaXRpb24gZm9yIGEgd2lyZQogIGZyYW1lIG1vZGVsIGdyZWF0bHkgZW5oYW5jZXMgb3VyIGFiaWxpdHkgdG8gdW5kZXJzdGFuZCB0aGUgM0QKICBzdHJ1Y3R1cmUuCgoKIyMjIEludGVyYWN0aXZlIDNEIFBsb3RzIFVzaW5nIE9wZW5HTAoKW09wZW5HTF0oaHR0cHM6Ly93d3cub3BlbmdsLm9yZy8pIGlzIGEgc3RhbmRhcmRpemVkIGZyYW1ld29yayBmb3IgaGlnaApwZXJmb3JtYW5jZSBncmFwaGljcy4KClRoZSBgcmdsYCBwYWNrYWdlIHByb3ZpZGVzIGFuIFIgaW50ZXJmYWNlIHRvIHNvbWUgb2YgT3BlbkdMJ3MKY2FwYWJpbGl0aWVzLgoKW1dlYkdMXShodHRwczovL3d3dy5raHJvbm9zLm9yZy93ZWJnbC8pIGlzIGEgSmF2YVNjcmlwdCBmcmFtZXdvcmsKZm9yIHVzaW5nIE9wZW5HTCB3aXRoaW4gYSBicm93c2VyIHdpbmRvdy4KCk1vc3QgZGVza3RvcCBicm93c2VycyBzdXBwb3J0IFdlYkdMOyBzb21lIG1vYmlsZSBicm93c2VycyBkbyBhcyB3ZWxsLgoKSW4gc29tZSBjYXNlcyBzdXBwb3J0IG1heSBiZSBhdmFpbGFibGUgYnV0IG5vdCBlbmFibGVkIGJ5IGRlZmF1bHQuCllvdSBtYXkgYmUgYWJsZSB0byBnZXQgaGVscCBhdCA8aHR0cHM6Ly9nZXQud2ViZ2wub3JnLz4uCgpga25pdHJgIGFuZCBgcmdsYCBwcm92aWRlIHN1cHBvcnQgZm9yIGVtYmVkZGluZyBPcGVuR0wgaW1hZ2VzIGluIHdlYgpwYWdlcy4KCkl0IGlzIGFsc28gcG9zc2libGUgdG8gZW1iZWQgT3BlbkdMIGltYWdlcyBpbiBQREYgZmlsZXMsIGJ1dCBub3QgYWxsClBERiB2aWV3ZXJzIHN1cHBvcnQgdGhpcy4KClN0YXJ0IGJ5IGNyZWF0aW5nIHRoZSBmaXQgc3VyZmFjZSBkYXRhIGZyYW1lLgoKYGBge3IsIGV2YWwgPSBGQUxTRSwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmlmICghIGZpbGUuZXhpc3RzKCJzb2lsLmRhdCIpKQogICAgZG93bmxvYWQuZmlsZSgiaHR0cDovL3d3dy5zdGF0LnVpb3dhLmVkdS9+bHVrZS9kYXRhL3NvaWwuZGF0IiwKICAgICAgICAgICAgICAgICAgInNvaWwuZGF0IikKc29pbCA8LSByZWFkLnRhYmxlKCJzb2lsLmRhdCIpCgpsaWJyYXJ5KGRwbHlyKQpzb2lsIDwtIHJlYWQudGFibGUoImh0dHA6Ly93d3cuc3RhdC51aW93YS5lZHUvfmx1a2UvZGF0YS9zb2lsLmRhdCIpCm0gPC0gbG9lc3MocmVzaXN0aXZpdHkgfiBlYXN0aW5nICogbm9ydGhpbmcsIHNwYW4gPSAwLjI1LAogICAgICAgICAgIGRlZ3JlZSA9IDIsIGRhdGEgPSBzb2lsKQplYXN0c2VxIDwtIHNlcSguMTUsIDEuNDEwLCBieSA9IC4wMTUpCm5vcnRoc2VxIDwtIHNlcSguMTUwLCAzLjY0NSwgYnkgPSAuMDE1KQpzb2kuZ3JpZCA8LSBleHBhbmQuZ3JpZChlYXN0aW5nID0gZWFzdHNlcSwgbm9ydGhpbmcgPSBub3J0aHNlcSkKc29pLmZpdCA8LSBwcmVkaWN0KG0sIHNvaS5ncmlkKQpzb2kuZml0LmRmIDwtIG11dGF0ZShzb2kuZ3JpZCwgZml0ID0gYXMubnVtZXJpYyhzb2kuZml0KSkKYGBgCgpUaGlzIGNvZGUgcnVuIGluIFIgd2lsbCBvcGVuIGEgbmV3IHdpbmRvdyBjb250YWluaW5nIGFuIGludGVyYWN0aXZlIDNECnNjZW5lIChidXQgdGhpcyBtYXkgbm90IHdvcmsgb24gRmFzdFggYW5kIGlzIG5vdCBhdmFpbGFibGUgb24gdGhlIFJTdHVkaW8Kc2VydmVyKToKCmBgYHtyIHNvaS5maXRfcmdsLCBldmFsID0gRkFMU0V9CmxpYnJhcnkocmdsKQpiZzNkKGNvbG9yID0gIndoaXRlIikKY2xlYXIzZCgpCnBhcjNkKG1vdXNlTW9kZSA9ICJ0cmFja2JhbGwiKQpzdXJmYWNlM2QoZWFzdHNlcSwgbm9ydGhzZXEsCiAgICAgICAgICBzb2kuZml0IC8gMTAwLAogICAgICAgICAgY29sb3IgPSByZXAoInJlZCIsIGxlbmd0aChzb2kuZml0KSkpCmBgYAoKVGhpcyB3aWxsIHdvcmsgaW4gdGhlIFJTdHVkaW8gbm90ZWJvb2sgc2VydmVyOgoKPCEtLSAjIyBub2xpbnQgc3RhcnQgLS0+CmBgYHtyLCBldmFsID0gRkFMU0V9Cm9wdGlvbnMocmdsLnVzZU5VTEwgPSBUUlVFKQo8PHNvaS5maXRfcmdsPj4Kcmdsd2lkZ2V0KCkKYGBgCjwhLS0gIyMgbm9saW50IGVuZCAtLT4KClRvIGVtYmVkIGFuIGltYWdlIGluIGFuIEhUTUwgZG9jdW1lbnQsIGZpcnN0IHNldCB0aGUgYHdlYmdsYCBob29rIHdpdGgKYSBjb2RlIGNodW5rIGxpa2UgdGhpczoKCmBgYHtyfQprbml0cjo6a25pdF9ob29rcyRzZXQod2ViZ2wgPSByZ2w6Omhvb2tfd2ViZ2wpCm9wdGlvbnMocmdsLnVzZU5VTEwgPSBUUlVFKQpgYGAKClRoZW4gYSBjaHVuayB3aXRoIHRoZSBvcHRpb24KCmBgYAp3ZWJnbCA9IFRSVUUKYGBgCgpjYW4gcHJvZHVjZSBhbiBlbWJlZGRlZCBPcGVuR0wgaW1hZ2U6CgpgYGB7ciwgcmVmLmxhYmVsID0gInNvaS5maXRfcmdsIiwgd2ViZ2wgPSBUUlVFLCBldmFsID0gRkFMU0V9CmBgYAoKYGBge3IsIHJlZi5sYWJlbCA9ICJzb2kuZml0X3JnbCIsIHdlYmdsID0gVFJVRSwgZWNobyA9IEZBTFNFfQpgYGAKCkEgdmlldyB0aGF0IGluY2x1ZGVzIHRoZSBwb2ludHMgYW5kIHVzZXMgYWxwaGEgYmxlbmRpbmcgdG8gbWFrZSB0aGUKc3VyZmFjZSB0cmFuc2x1Y2VudDoKCmBgYHtyLCBmaWcuaGVpZ2h0ID0gNywgZmlnLndpZHRoID0gOCwgd2ViZ2wgPSBUUlVFLCBmaWcuYWxpZ24gPSAiY2VudGVyIiwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmxpYnJhcnkocmdsKQpjbGVhcjNkKCkKcG9pbnRzM2Qoc29pbCRlYXN0aW5nLAogICAgICAgICBzb2lsJG5vcnRoaW5nLAogICAgICAgICBzb2lsJHJlc2lzdGl2aXR5IC8gMTAwLAogICAgICAgICBjb2wgPSByZXAoImJsYWNrIiwgbnJvdyhzb2lsKSkpCnN1cmZhY2UzZChlYXN0c2VxLCBub3J0aHNlcSwKICAgICAgICAgIHNvaS5maXQgLyAxMDAsCiAgICAgICAgICBjb2wgPSByZXAoInJlZCIsIGxlbmd0aChzb2kuZml0KSksCiAgICAgICAgICBhbHBoYSA9IDAuOSwKICAgICAgICAgIGZyb250ID0gImZpbGwiLAogICAgICAgICAgYmFjayA9ICJmaWxsIikKYGBgCgpBIHZpZXcgb2YgdGhlIGB2b2xjYW5vYCBzdXJmYWNlOgoKYGBge3IsIHdlYmdsID0gVFJVRX0KbGlicmFyeShyZ2wpCmtuaXRyOjprbml0X2hvb2tzJHNldCh3ZWJnbCA9IHJnbDo6aG9va193ZWJnbCkKb3B0aW9ucyhyZ2wudXNlTlVMTCA9IFRSVUUpCmNsZWFyM2QoKQpzdXJmYWNlM2QoeCA9IDEwICogc2VxX2xlbihucm93KHZvbGNhbm8pKSwKICAgICAgICAgIHkgPSAxMCAqIHNlcV9sZW4obmNvbCh2b2xjYW5vKSksCiAgICAgICAgICB6ID0gMiAqIHZvbGNhbm8sIGNvbG9yID0gInJlZCIpCmBgYAoKVGhlCltXZWJHTCB2aWduZXR0ZV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvcGFja2FnZT1yZ2wvdmlnbmV0dGVzL1dlYkdMLmh0bWwpCmluIHRoZSBgcmdsYCBwYWNrYWdlIHNob3dzIHNvbWUgbW9yZSBleGFtcGxlcy4KClRoZSBlbWJlZGRlZCBncmFwaHMgc2hvdyBwcm9wZXJseSBmb3IgbWUgb24gbW9zdCBicm93c2VyL09TCmNvbWJpbmF0aW9ucyBJIGhhdmUgdHJpZWQuCgpUbyBpbmNsdWRlIGEgc3RhdGljIHZpZXcgb2YgYW4gcmdsIHNjZW5lIHlvdSBjYW4gdXNlIHRoZQpgcmdsLnNuYXBzaG90YCBmdW5jdGlvbi4KClRvIHVzZSB0aGUgdmlldyBmb3VuZCBpbnRlcmFjdGl2ZWx5IHdpdGggcmdsIGZvciBjcmVhdGluZyBhIGBsYXR0aWNlYApgd2lyZWZyYW1lYCB2aWV3LCB5b3UgY2FuIHVzZSB0aGUgYHJnbFRvTGF0dGljZWAgZnVuY3Rpb246CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQp3aXJlZnJhbWUoc29pLmZpdCB+IHNvaS5ncmlkJGVhc3RpbmcgKiBzb2kuZ3JpZCRub3J0aGluZywKICAgICAgICAgIGFzcGVjdCA9IGMoYXNwLCAwLjcpLCBzaGFkZSA9IFRSVUUsCiAgICAgICAgICB4bGFiID0gIkVhc3RpbmcgKGttKSIsCiAgICAgICAgICB5bGFiID0gIk5vcnRoaW5nIChrbSkiLCBzY3JlZW4gPSByZ2xUb0xhdHRpY2UoKSkKYGBgCgpFdGhhbm9sIGRhdGE6CgpgYGB7ciwgd2ViZ2wgPSBUUlVFLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KbGlicmFyeShyZ2wpCmNsZWFyM2QoKQp3aXRoKGV0aGFub2wsIHBvaW50czNkKEUsIEMgLyA1LCBOT3ggLyA1LAogICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSA0LCBjb2wgPSByZXAoImJsYWNrIiwgbnJvdyhldGhhbm9sKSkpKQpgYGAKClF1YWtlcyBkYXRhOgoKYGBge3IsIHdlYmdsID0gVFJVRSwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmxpYnJhcnkocmdsKQpjbGVhcjNkKCkKd2l0aChxdWFrZXMsIHBvaW50czNkKGxvbmcsIGxhdCwgLWRlcHRoIC8gNTAsIHNpemUgPSAyLAogICAgICAgICAgICAgICAgICAgICAgY29sID0gcmVwKCJibGFjayIsIG5yb3cocXVha2VzKSkpKQpgYGAKCgojIyMgT3RoZXIgQW5pbWF0aW9uIFRlY2hub2xvZ2llcwoKQW5pbWF0ZWQgR0lGIG9yIFBORyBpbWFnZXMgY2FuIGJlIHVzZWQgdG8gc2hvdyBhIF9mbHkgYXJvdW5kXyBvZiBhCnN1cmZhY2UuCgo8IS0tICMjIG5vbGludCBzdGFydCAtLT4KYGBge3IsIGVjaG8gPSBGQUxTRSwgZXZhbCA9IEZBTFNFfQojIyBJIGNvdWxkIG5vdCBmaWd1cmUgb3V0IGhvdyB0byBnZXQgZWl0aGVyIHdpcmVmcmFtZSBvciBwZXJzcCB0byB1c2UKIyMgYSBmaXhlZCB0YXJnZXQgYm94LCBzbyBhdHRlbXB0aW5nIHRvIGFuaW1hdGUgd291bGQgYXBwZWFyIHRvIG1vdmUKIyMgdGhlIHZpZXdlciBjbG9zZXIgb3IgZmFydGhlciBkZXBlbmRpbmcgb24gdGhlIHJvdGF0aW9uLiBIYWNraW5nIG9uZQojIyBvZiB0aGUgbWlyYzNkIGZ1bmN0aW9ucyB3YXMgdGhlIGJlc3Qgb3B0aW9uIEkgY291bGQgZmluZC4KbGlicmFyeShtaXNjM2QpCmRzIDwtIGZ1bmN0aW9uKHNjZW5lLCBsaWdodCA9IGMoMCwgMCwgMSksIHNjcmVlbiA9IGxpc3QoeiA9IDQwLCB4ID0gLTYwKSwKICAgICAgICAgICAgICAgc2NhbGUgPSBUUlVFLCBSLm1hdCA9IGRpYWcoNCksIHBlcnNwZWN0aXZlID0gRkFMU0UsCiAgICAgICAgICAgICAgIGRpc3RhbmNlID0gaWYgKHBlcnNwZWN0aXZlKSAwLjIgZWxzZSAwLAogICAgICAgICAgICAgICBmaWxsID0gVFJVRSwgeGxpbSA9IE5VTEwsIHlsaW0gPSBOVUxMLCB6bGltID0gTlVMTCwKICAgICAgICAgICAgICAgYXNwZWN0ID0gYygxLCAxKSwKICAgICAgICAgICAgICAgY29sLm1lc2ggPSBpZiAoZmlsbCkgTkEgZWxzZSAiYmxhY2siLCBwb2x5bnVtID0gMTAwLAogICAgICAgICAgICAgICBsaWdodGluZyA9IHBob25nTGlnaHRpbmcsIGFkZCA9IEZBTFNFLCBlbmdpbmUgPSAic3RhbmRhcmQiLAogICAgICAgICAgICAgICBjb2wuYmcgPSAidHJhbnNwYXJlbnQiLCBkZXB0aCA9IDAsIG5ld3BhZ2UgPSBUUlVFLAogICAgICAgICAgICAgICBmaXhib3gpCnsKICAgIHNjZW5lIDwtIGNvbG9yU2NlbmUoc2NlbmUpCiAgICBzciA8LSBzY2VuZVJhbmdlcyhzY2VuZSwgeGxpbSwgeWxpbSwgemxpbSkKICAgIGlmIChhZGQpCiAgICAgICAgcm90Lm1hdCA8LSBSLm1hdAogICAgZWxzZSByb3QubWF0IDwtIG1ha2VWaWV3VHJhbnNmb3JtKHNyLCBzY2FsZSwgYXNwZWN0LCBzY3JlZW4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUi5tYXQpCiAgICBzY2VuZSA8LSB0cmFuc2Zvcm1TY2VuZShzY2VuZSwgcm90Lm1hdCkKICAgIHNjZW5lIDwtIGxpZ2h0U2NlbmUoc2NlbmUsIGxpZ2h0aW5nLCBsaWdodCkKICAgIGlmIChkaXN0YW5jZSA+IDApIHsKICAgICAgICBzY2VuZSA8LSBhZGRQZXJzcGVjdGl2ZShzY2VuZSwgZGlzdGFuY2UpCiAgICAgICAgcm90Lm1hdCA8LSBtYWtlUGVyc3BNYXRyaXgoZGlzdGFuY2UpICUqJSByb3QubWF0CiAgICB9CiAgICBpZiAobWlzc2luZyhmaXhib3gpKSB7CiAgICAgICAgYm94IDwtIGFzLm1hdHJpeChleHBhbmQuZ3JpZChzciR4bGltLCBzciR5bGltLCBzciR6bGltKSkKICAgICAgICBib3ggPC0gdHJhbnMzZHRvM2QoYm94LCByb3QubWF0KQogICAgfQogICAgZWxzZSB7CiAgICAgICAgbGltcyA8LSBmaXhib3ggKiBjKC0xLCAxKQogICAgICAgIGJveCA8LSBhcy5tYXRyaXgoZXhwYW5kLmdyaWQobGltcywgbGltcywgbGltcykpCiAgICB9CiAgICByZW5kZXJTY2VuZShzY2VuZSwgYm94LCBmaWxsLCBjb2wubWVzaCwgYWRkLCBlbmdpbmUsIHBvbHludW0sCiAgICAgICAgICAgICAgICBjb2wuYmcsIGRlcHRoLCBuZXdwYWdlKQogICAgaW52aXNpYmxlKHQocm90Lm1hdCkpCn0KZW52aXJvbm1lbnQoZHMpIDwtIGVudmlyb25tZW50KGRyYXdTY2VuZSkKCmYgPC0gZnVuY3Rpb24oeikgewogICAgZHModnRyaSwgbGlnaHQgPSBjKDEsIDEuNSwgMCksIGxpc3QoeiA9IHosIHggPSAtNjAsIHkgPSAzKSwKICAgICAgIHNjYWxlID0gRkFMU0UsIGZpeGJveCA9IDEuMikKfQoKbGlicmFyeShhbmltYXRpb24pCmFuaS5vcHRpb25zKGludGVydmFsID0gMC4xLCBubWF4ID0gMTAwKQpzYXZlR0lGKGZvciAoaSBpbiBzZXEoMCwgMzYwLCBieSA9IDUpKSBmKGkgKyA0MCkpCmBgYAo8IS0tICMjIG5vbGludCBlbmQgLS0+CgpgYGB7ciwgZWNobyA9IEZBTFNFfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhJTUcoImFuaW12b2xjYW5vLmdpZiIpKQpgYGAKCkEgZmx5LWFyb3VuZCBjYW4gYWxzbyBiZSByZWNvcmRlZCBhcyBhIG1vdmllLgoKPGNlbnRlcj4KICA8dmlkZW8gd2lkdGg9IjY0MCIgaGVpZ2h0PSI0ODAiIGNvbnRyb2xzPgogICAgPHNvdXJjZSBzcmM9ImltZy9hbmltdm9sY2Fuby5tcDQiIHR5cGU9InZpZGVvL21wNCI+CiAgPC92aWRlbz4KPC9jZW50ZXI+CgpBIG1vdmllIGNhbiBiZSBwYXVzZWQgYW5kIHJlcGxheWVkOyBhbmltYXRlZCBpbWFnZXMgdHlwaWNhbGx5IGNhbm5vdC4KCgojIyMgQWx0ZXJuYXRpdmVzIGFuZCBWYXJpYXRpb25zCgpJbnRlcmFjdGl2ZSBvciBhbmltYXRlZCB2aWV3cyByZWx5IG9uIG91ciB2aXN1YWwgc3lzdGVtJ3MgYWJpbGl0eSB0bwpleHRyYWN0IFtfZGVwdGgKcXVldWVzX10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvRGVwdGhfcGVyY2VwdGlvbikgZnJvbSBtb3Rpb24uCgpUaGlzIGlzIGNhbGxlZCBfbW90aW9uIHBhcmFsbGF4Xy4KCldoZW4gdGhlIG1vdGlvbiBzdG9wcywgdGhlIDNEIGlsbHVzaW9uIGlzIGxvc3QuCgpPdGhlciBvcHRpb25zIGZvciB2aWV3aW5nIGEgM0Qgc2NlbmUgdGhhdCBkbyBub3QgcnF1aXJlIG1vdGlvbjoKCiogW0FuYWdseXBoIDNEXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9BbmFnbHlwaF8zRCkgdXNpbmcKICByZWQvY3lhbiBnbGFzc2VzLgoKKiBbUG9sYXJpemVkIDNEXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Qb2xhcml6ZWRfM0Rfc3lzdGVtKSBhcwogIGN1cnJlbnRseSB1c2VkIGluIG1hbnkgM0QgbW92aWVzLgoKKiBbU3RlcmVvZ3JhbXMsIHN0ZXJlb3Njb3B5XShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9TdGVyZW9zY29weSkKCkEgc3RlcmVvZ3JhbQoKKiBwcmVzZW50cyBlYWNoIGV5ZSB3aXRoIGFuIGltYWdlIGZyb20gYSBzbGlnaHRseSBkaWZmZXJlbnQgdmlld2luZyBhbmdsZTsKCiogdGhlIGJyYWluIGZ1c2VzIHRoZSBpbWFnZXMgaW50byBhIDNEIHZpZXcgaW4gYSBwcm9jZXNzIGNhbGxlZAogIFtfc3RlcmVvcHNpc19dKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1N0ZXJlb3BzaXMpLgoKVGhpcyBhcHByb2FjaCBpcyBvciB3YXMgdXNlZAoKKiB3aXRoIHZpcnR1YWwgcmVhbGl0eSBkaXNwbGF5cyBsaWtlCiAgW09jdWx1cyBSaWZ0XShodHRwczovL3d3dy5tZXRhLmNvbS9xdWVzdC9wcm9kdWN0cy9xdWVzdC0yLykgb3IKICBbQXBwbGUgVmlzaW9uIFByb10oaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlLXZpc2lvbi1wcm8vKTsKCiogaW4gdGhlIFtWaWV3LU1hc3RlciB0b3ldKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1ZpZXctTWFzdGVyKTsKCiogaW4gYWVyaWFsIHBob3RvZ3JhcGh5IHVzaW5nIHZpZXdlcnMgbGlrZSB0aGlzOgoKPCEtLSBJbWFnZToKImh0dHBzOi8vdXBsb2FkLndpa2ltZWRpYS5vcmcvd2lraXBlZGlhL2NvbW1vbnMvMy8zMS9Qb2NrZXRfc3RlcmVvc2NvcGUuanBnIgotLT4KYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoID0gIjgwJSJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKElNRygiUG9ja2V0X3N0ZXJlb3Njb3BlLmpwZyIpKQpgYGAKCllvdSBjYW4gY3JlYXRlIHlvdXIgb3duIHZpZXdlciB3aXRoIHBhcGVyIG9yIGNhcmRib2FyZCB0dWJlcywgZm9yIGV4YW1wbGUuCgpBIHN0ZXJlbyBpbWFnZSBmb3IgdGhlIHNvaWwgZGF0YSBzdXJmYWNlOgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgZmlnLndpZHRoID0gMTAsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQp0cCA8LSB0cmVsbGlzLnBhci5nZXQoKQp0cmVsbGlzLnBhci5zZXQodGhlbWUgPSBjb2wud2hpdGViZygpKQpvY29sIDwtIHRyZWxsaXMucGFyLmdldCgiYXhpcy5saW5lIikkY29sCm9jbGlwIDwtIHRyZWxsaXMucGFyLmdldCgiY2xpcCIpJHBhbmVsCnRyZWxsaXMucGFyLnNldChsaXN0KGF4aXMubGluZSA9IGxpc3QoY29sID0gInRyYW5zcGFyZW50IiksCiAgICAgICAgICAgICAgICAgICAgIGNsaXAgPSBsaXN0KHBhbmVsID0gRkFMU0UpKSkKcHJpbnQod2lyZWZyYW1lKHNvaS5maXQgfiBzb2kuZ3JpZCRlYXN0aW5nICogc29pLmdyaWQkbm9ydGhpbmcsCiAgICAgICAgICAgICAgICBjdXRzID0gOSwKICAgICAgICAgICAgICAgIGFzcGVjdCA9IGRpZmYocmFuZ2Uoc29pLmdyaWQkbikpIC8gZGlmZihyYW5nZShzb2kuZ3JpZCRlKSksCiAgICAgICAgICAgICAgICB4bGFiID0gIkVhc3RpbmcgKGttKSIsCiAgICAgICAgICAgICAgICB5bGFiID0gIk5vcnRoaW5nIChrbSkiLCBzaGFkZSA9IFRSVUUsCiAgICAgICAgICAgICAgICBzY3JlZW4gPSBsaXN0KHogPSA0MCwgeCA9IC03MCwgeSA9IDMpLAoJCXNoYWRlLmNvbG9ycy5wYWxldHRlID0gd2ZwYWwpLAogICAgICBzcGxpdCA9IGMoMSwgMSwgMiwgMSksIG1vcmUgPSBUUlVFKQoKcHJpbnQod2lyZWZyYW1lKHNvaS5maXQgfiBzb2kuZ3JpZCRlYXN0aW5nICogc29pLmdyaWQkbm9ydGhpbmcsCiAgICAgICAgICAgICAgICBjdXRzID0gOSwKICAgICAgICAgICAgICAgIGFzcGVjdCA9IGRpZmYocmFuZ2Uoc29pLmdyaWQkbikpIC8gZGlmZihyYW5nZShzb2kuZ3JpZCRlKSksCiAgICAgICAgICAgICAgICB4bGFiID0gIkVhc3RpbmcgKGttKSIsCiAgICAgICAgICAgICAgICB5bGFiID0gIk5vcnRoaW5nIChrbSkiLCBzaGFkZSA9IFRSVUUsCiAgICAgICAgICAgICAgICBzY3JlZW4gPSBsaXN0KHogPSA0MCwgeCA9IC03MCwgeSA9IDApLAoJCXNoYWRlLmNvbG9ycy5wYWxldHRlID0gd2ZwYWwpLAogICAgICBzcGxpdCA9IGMoMiwgMSwgMiwgMSkpCmBgYApgYGB7ciwgZWNobyA9IEZBTFNFfQp0cmVsbGlzLnBhci5zZXQobGlzdChheGlzLmxpbmUgPSBsaXN0KGNvbCA9IG9jb2wpLCBjbGlwID0gbGlzdChwYW5lbCA9IG9jbGlwKSkpCnRyZWxsaXMucGFyLnNldCh0cCkKYGBgCgpBIHN0ZXJlbyB2aWV3IG9mIHRoZSBgdm9sY2Fub2Agc3VyZmFjZToKCmBgYHtyLCBmaWcud2lkdGggPSAxMCwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmxpYnJhcnkobGF0dGljZSkKdjEgPC0gd2lyZWZyYW1lKHZvbGNhbm8sIHNoYWRlID0gVFJVRSwKICAgICAgICAgICAgICAgIGFzcGVjdCA9IGMoNjEgLyA4NywgMC40KSwKICAgICAgICAgICAgICAgIGxpZ2h0LnNvdXJjZSA9IGMoMTAsIDAsIDEwKSwKICAgICAgICAgICAgICAgIHNjcmVlbiA9IGxpc3QoeiA9IDQwLCB4ID0gLTYwLCB5ID0gMCksCgkJc2hhZGUuY29sb3JzLnBhbGV0dGUgPSB3ZnBhbCkKdjIgPC0gd2lyZWZyYW1lKHZvbGNhbm8sIHNoYWRlID0gVFJVRSwKICAgICAgICAgICAgICAgIGFzcGVjdCA9IGMoNjEgLyA4NywgMC40KSwKICAgICAgICAgICAgICAgIGxpZ2h0LnNvdXJjZSA9IGMoMTAsIDAsIDEwKSwKICAgICAgICAgICAgICAgIHNjcmVlbiA9IGxpc3QoeiA9IDQwLCB4ID0gLTYwLCB5ID0gMyksCgkJc2hhZGUuY29sb3JzLnBhbGV0dGUgPSB3ZnBhbCkKCnByaW50KHYxLCBzcGxpdCA9IGMoMSwgMSwgMiwgMSksIG1vcmUgPSBUUlVFKQpwcmludCh2Miwgc3BsaXQgPSBjKDIsIDEsIDIsIDEpKQpgYGAKCkEgc3RlcmVvIHZpZXcgb2YgdGhlIGBxdWFrZXNgIGRhdGE6CgpgYGB7ciwgZmlnLndpZHRoID0gMTAsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpxMSA8LSBjbG91ZCgtZGVwdGggfiBsb25nICogbGF0LCBkYXRhID0gcXVha2VzLCBwY2ggPSAxOSwgY2V4ID0gMC4zLAogICAgICAgICAgICBzY3JlZW4gPSBsaXN0KHogPSA0MCwgeCA9IC02MCwgeSA9IDApKQpxMiA8LSBjbG91ZCgtZGVwdGggfiBsb25nICogbGF0LCBkYXRhID0gcXVha2VzLCBwY2ggPSAxOSwgY2V4ID0gMC4zLAogICAgICAgICAgICBzY3JlZW4gPSBsaXN0KHogPSA0MCwgeCA9IC02MCwgeSA9IDMpKQpwcmludChxMSwgc3BsaXQgPSBjKDEsIDEsIDIsIDEpLCBtb3JlID0gVFJVRSkKcHJpbnQocTIsIHNwbGl0ID0gYygyLCAxLCAyLCAxKSkKYGBgCgoKIyMgQ29wbG90cyBmb3IgU3VyZmFjZXMKCldlIGNhbiBhbHNvIHVzZSB0aGUgaWRlYSBvZiBhIGNvcGxvdCBmb3IgZXhhbWluaW5nIGEgc3VyZmFjZSBhIGZldwpzbGljZXMgYXQgYSB0aW1lLgoKRm9yIHRoZSBzb2lsIHJlc2lzdGl2aXR5IHN1cmZhY2U6CgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CndpcmVmcmFtZSgKICAgIHNvaS5maXQgfgogICAgICAgIHNvaS5ncmlkJGVhc3RpbmcgKiBzb2kuZ3JpZCRub3J0aGluZywKICAgIGN1dHMgPSA5LAogICAgYXNwZWN0ID0gZGlmZihyYW5nZShzb2kuZ3JpZCRuKSkgLwogICAgICAgIGRpZmYocmFuZ2Uoc29pLmdyaWQkZSkpLAogICAgeGxhYiA9ICJFYXN0aW5nIChrbSkiLAogICAgeWxhYiA9ICJOb3J0aGluZyAoa20pIiwgc2hhZGUgPSBUUlVFLAogICAgc2NyZWVuID0gbGlzdCh6ID0gNDAsIHggPSAtNzAsIHkgPSAwKSwKICAgIHNoYWRlLmNvbG9ycy5wYWxldHRlID0gd2ZwYWwpCmBgYAoKQ2hvb3NpbmcgMTIgYXBwcm94aW1hdGVseSBlcXVhbGx5IHNwYWNlZCBzbGljZXMgYWxvbmcgYGVhc3RpbmdgOgoKPCEtLSAqKioqIHN3aXRjaCB0byBjb25kaXRpb25pbmcgb24gbm9ydGhpbmc/IG5vdCB3b3J0aCBpdCBmb3Igbm93IC0tPgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpzZiA8LSBzb2kuZ3JpZApzZiRmaXQgPC0gYXMubnVtZXJpYyhzb2kuZml0KQoKc3ViZSA8LSBlYXN0c2VxW3NlcV9sZW4obGVuZ3RoKGVhc3RzZXEpKSAlJSA3ID09IDBdCnN1YmUgPC0gZWFzdHNlcVtyb3VuZChzZXEoMSwgbGVuZ3RoKGVhc3RzZXEpLCBsZW5ndGgub3V0ID0gMTIpKV0KCnNzZiA8LSBmaWx0ZXIoc2YsIGVhc3RpbmcgJWluJSBzdWJlKQoKZ2dwbG90KHNzZiwgYWVzKHggPSBub3J0aGluZywgeSA9IGZpdCkpICsKICAgIGdlb21fbGluZSgpICsKICAgIHNjYWxlX3hfcmV2ZXJzZSgpICsKICAgIGZhY2V0X3dyYXAofiBlYXN0aW5nKQpgYGAKCkZvciBleGFtaW5pbmcgYSBzdXJmYWNlIHRoaXMgd2F5IHdlIGZpeCBvbmUgdmFyaWFibGUgYXQgYSBzcGVjaWZpYyB2YWx1ZS4KCkZvciBleGFtaW5pbmcgZGF0YSBpdCBpcyBhbHNvIHNvbWV0aW1lcyB1c2VmdWwgdG8gY2hvb3NlIGEgbmFycm93IHdpbmRvdy4KCiogQSBuYXJyb3cgd2luZG93IG1pbmltaXplcyB0aGUgdmFyaWF0aW9uIHdpdGhpbiB0aGUgdmFyaWFibGUgd2UKICBhcmUgY29uZGl0aW9uaW5nIG9uLgoKKiBUb28gbmFycm93IGEgd2luZG93IGNvbnRhaW5zIHRvIGZldyBvYnNlcnZhdGlvbnMgdG8gc2VlIGEgc2lnbmFsLgoKCiMjIENvbmRpdGlvbmluZyB3aXRoIGEgU2luZ2xlIFBsb3QKCkl0IGlzIHBvc3NpYmxlIHRvIHNob3cgY29uZGl0aW9uaW5nIGluIGEgc2luZ2xlIHBsb3QgdXNpbmcgYW4gaWRlbnRpdHkKY2hhbm5lbCB0byBkaXN0aW5ndWlzaCB0aGUgY29uZGl0aW9ucy4KCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0Kc2YgPC0gc29pLmdyaWQKc2YkZml0IDwtIGFzLm51bWVyaWMoc29pLmZpdCkKCnN1YmU0IDwtIGVhc3RzZXFbcm91bmQoc2VxKDEsIGxlbmd0aChlYXN0c2VxKSwgbGVuZ3RoLm91dCA9IDQpKV0KCnNzZjQgPC0gZmlsdGVyKHNmLCBlYXN0aW5nICVpbiUgc3ViZTQpCgpnZ3Bsb3QobXV0YXRlKHNzZjQsIGVhc3RpbmcgPSBmYWN0b3IoZWFzdGluZykpLAogICAgICAgYWVzKHggPSBub3J0aGluZywgeSA9IGZpdCwKICAgICAgICAgICBjb2xvciA9IGVhc3RpbmcsCiAgICAgICAgICAgZ3JvdXAgPSBlYXN0aW5nKSkgKwogICAgZ2VvbV9saW5lKCkKYGBgCgpUaGlzIGlzIG1vc3QgdXNlZnVsIHdoZW4gdGhlIGVmZmVjdCBvZiB0aGUgY29uZGl0aW9uaW5nIHZhcmlhYmxlIGlzCmEgbGV2ZWwgc2hpZnQuCgpUaGUgbnVtYmVyIG9mIGRpZmZlcmVudCBsZXZlbHMgdGhhdCBjYW4gYmUgdXNlZCBlZmZlY3RpdmVseSBpcyBsb3dlci4KCk92ZXItcGxvdHRpbmcgYmVjb21lcyBhbiBpc3N1ZSB3aGVuIHVzZWQgd2l0aCBkYXRhIHBvaW50cy4KCjwhLS0gKioqKiBsb29rIGludG8gcXVha2VzIGFuZCBtYWduaXR1ZGVzIGRvZXNuJ3QgbG9vayB3b3J0aCBpdCAtLT4KCgojIyBFeGFtcGxlOiBPem9uZSBMZXZlbHMKCkZyb20gQ2xldmVsYW5kLCBXaWxsaWFtIFMuICgxOTkzKSBfVmlzdWFsaXppbmcgRGF0YV86CgpgYGB7cn0KZGF0YShlbnZpcm9ubWVudGFsLCBwYWNrYWdlID0gImxhdHRpY2UiKQpgYGAKRGFpbHkgbWVhc3VyZW1lbnRzIG9mIG96b25lIGNvbmNlbnRyYXRpb24sIHdpbmQgc3BlZWQsIHRlbXBlcmF0dXJlCmFuZCBzb2xhciByYWRpYXRpb24gaW4gTmV3IFlvcmsgQ2l0eSBmcm9tIE1heSB0byBTZXB0ZW1iZXIgb2YKMTk3My4KCkEgZGF0YSBmcmFtZSB3aXRoIDExMSBvYnNlcnZhdGlvbnMgb24gdGhlIGZvbGxvd2luZyA0IHZhcmlhYmxlcy4KCi0gb3pvbmU6IEF2ZXJhZ2Ugb3pvbmUgY29uY2VudHJhdGlvbgotIHJhZGlhdGlvbjogU29sYXIgcmFkaWF0aW9uCi0gdGVtcGVyYXR1cmU6IE1heGltdW0gZGFpbHkgZW1wZXJhdHVyZQotIHdpbmQ6IEF2ZXJhZ2Ugd2luZCBzcGVlZAoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpwYWlycyhlbnZpcm9ubWVudGFsKQpgYGAKCkNsZXZlbGFuZCBzdWdnZXN0cyBhIGN1YmUgcm9vdCB0cmFuc2Zvcm1hdGlvbiBmb3IgYG96b25lYDoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZW52MiA8LSBtdXRhdGUoZW52aXJvbm1lbnRhbCwgb3pvbmUgPSBvem9uZSBeICgxIC8gMykpCnBhaXJzKGVudjIpCmBgYAoKVGhlcmUgaXMgbW9ub3RvbmUgbWFyZ2luYWwgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIHRyYW5zZm9ybWVkCmBvem9uZWAgYW5kIGB0ZW1wZXJhdHVyZWAuCgpJdCBtaWdodCBiZSB3b3J0aCBsb29raW5nIGF0IHRoaXMgcmVsYXRpb25zaGlwIGZvciBkaWZmZXJlbnQgbGV2ZWxzIG9mCmB3aW5kYCBhbmQgYHJhZGlhdGlvbmAuCgpUd28gbGV2ZWxzIGVhY2ggbWlnaHQgYmUgYSBnb29kIHN0YXJ0OgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQplbnYyIDwtIG11dGF0ZShlbnYyLAogICAgICAgICAgICAgICB3aW5kX2N1dCA9IGN1dF9udW1iZXIod2luZCwgMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoImxvdyB3aW5kIiwgImhpZ2ggd2luZCIpKSwKICAgICAgICAgICAgICAgcmFkX2N1dCA9IGN1dF9udW1iZXIocmFkaWF0aW9uLCAyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJsb3cgcmFkIiwgImhpZ2ggcmFkIikpKQoKZ2dwbG90KGVudjIsIGFlcyh4ID0gdGVtcGVyYXR1cmUsIHkgPSBvem9uZSkpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBnZW9tX3Ntb290aChtZXRob2QgPSAiZ2FtIikgKwogICAgZmFjZXRfZ3JpZChyYWRfY3V0IH4gd2luZF9jdXQpCmBgYAoKRmFjZXRpbmcgb24gYHdpbmRgOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpnZ3Bsb3QoZW52MiwgYWVzKHggPSB0ZW1wZXJhdHVyZSwgeSA9IG96b25lLCBjb2xvciA9IHJhZF9jdXQpKSArCiAgICBnZW9tX3BvaW50KCkgKwogICAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImdhbSIpICsKICAgIGZhY2V0X3dyYXAofiB3aW5kX2N1dCkKYGBgCgpGYWNldGluZyBvbiBgcmFkaWF0aW9uYDoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZ2dwbG90KGVudjIsIGFlcyh4ID0gdGVtcGVyYXR1cmUsIHkgPSBvem9uZSwgY29sb3IgPSB3aW5kX2N1dCkpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBnZW9tX3Ntb290aChtZXRob2QgPSAiZ2FtIikgKwogICAgZmFjZXRfd3JhcCh+IHJhZF9jdXQpCmBgYAoKRml0IG1vZGVsOyByZWQgYXQgMXN0IHF1YXJ0aWxlIG9mIHdpbmQsIGdyZWVuIGF0IDNyZCBxdWFydGlsZToKCmBgYHtyLCB3ZWJnbCA9IFRSVUUsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGdncGxvdDIpCmRhdGEoZW52aXJvbm1lbnRhbCwgcGFja2FnZSA9ICJsYXR0aWNlIikKZW52MiA8LSBtdXRhdGUoZW52aXJvbm1lbnRhbCwgb3pvbmUgPSBvem9uZSBeICgxIC8gMykpCnRlbXBzZXEgPC0gc2VxKDU3LCA5NywgbGVuZ3RoLm91dCA9IDIwKQpyYWRzZXEgPC0gc2VxKDcsIDMzNCwgbGVuZ3RoLm91dCA9IDIwKQpmaXQyIDwtIGxvZXNzKG96b25lIH4gd2luZCAqIHRlbXBlcmF0dXJlICogcmFkaWF0aW9uLAogICAgICAgICAgICAgIHNwYW4gPSAxLjAsIGRlZ3JlZSA9IDIsIGRhdGEgPSBlbnYyKQpncmlkMSA8LSBleHBhbmQuZ3JpZCh0ZW1wZXJhdHVyZSA9IHRlbXBzZXEsIHJhZGlhdGlvbiA9IHJhZHNlcSwgd2luZCA9IDcuNSkKZ3JpZDIgPC0gZXhwYW5kLmdyaWQodGVtcGVyYXR1cmUgPSB0ZW1wc2VxLCByYWRpYXRpb24gPSByYWRzZXEsIHdpbmQgPSAxMS41KQpzMSA8LSBwcmVkaWN0KGZpdDIsIG5ld2RhdGEgPSBncmlkMSkKczIgPC0gcHJlZGljdChmaXQyLCBuZXdkYXRhID0gZ3JpZDIpCgpsaWJyYXJ5KHJnbCkKY2xlYXIzZCgpCnN1cmZhY2UzZChhcy5udW1lcmljKHNjYWxlKHRlbXBzZXEpKSwgYXMubnVtZXJpYyhzY2FsZShyYWRzZXEpKSwgczEsCiAgICAgICAgICBjb2xvciA9IHJlcCgicmVkIiwgbGVuZ3RoKHMxKSkpCnN1cmZhY2UzZChhcy5udW1lcmljKHNjYWxlKHRlbXBzZXEpKSwgYXMubnVtZXJpYyhzY2FsZShyYWRzZXEpKSwgczIsCiAgICAgICAgICBjb2xvciA9IHJlcCgiZ3JlZW4iLCBsZW5ndGgoczIpKSkKYXhlczNkKCkKdGl0bGUzZCh4bGFiID0gInRlbXAiLCB5bGFiID0gInJhZCIsIHpsYWIgPSAib3pvbmUiKQpgYGAKCgojIyAzRCBEZW5zaXR5IEVzdGltYXRlcwoKRGVuc2l0eSBlc3RpbWF0ZXMgY2FuIGJlIHVzZWQgd2l0aCAzRCBkYXRhIGFzIHdlbGwuCgpUaGUgZGVuc2l0eSBpcyBhIGZ1bmN0aW9uIG9mIHRocmVlIHZhcmlhYmxlcywgc28gdGhlIGRlbnNpdHkgc3VyZmFjZQppcyBpbiA0RC4KClRoZSBwb2ludHMgaW4gM0Qgd2l0aCBhIGNvbW1vbiBkZW5zaXR5IGxldmVsIGZvcm0gYSBfY29udG91ciBzdXJmYWNlXy4KClRoZSBjb250b3VyIHN1cmZhY2UgZm9yIGEgaGlnaGVyIGRlbnNpdHkgbGV2ZWwgd2lsbCBiZSBpbnNpZGUgdGhlCnN1cmZhY2UgZm9yIGEgbG93ZXIgbGV2ZWwuCgpTb21lIGFydGlmaWNpYWwgZGF0YToKCmBgYHtyLCB3ZWJnbCA9IFRSVUUsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpsaWJyYXJ5KHJnbCkKbGlicmFyeShtaXNjM2QpCgp4IDwtIG1hdHJpeChybm9ybSg5MDAwKSwgbmNvbCA9IDMpCnhbMTAwMSA6IDIwMDAsIDJdIDwtIHhbMTAwMSA6IDIwMDAsIDJdICsgNAp4WzIwMDEgOiAzMDAwLCAzXSA8LSB4WzIwMDEgOiAzMDAwLCAzXSArIDQKCmNsZWFyM2QoKQpiZzNkKGNvbCA9ICJ3aGl0ZSIpCnBvaW50czNkKHggPSB4WywgMV0sIHkgPSB4WywgMl0sIHogPSB4WywgM10sCiAgICAgICAgIHNpemUgPSAyLCBjb2xvciA9ICJibGFjayIpCmBgYAoKM0QgcGxvdCBvZiBvbmUgY29udG91ciBzdXJmYWNlOgpgYGB7ciwgd2ViZ2wgPSBUUlVFLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZyA8LSBleHBhbmQuZ3JpZCh4ID0gc2VxKC00LCA4LjUsIGxlbiA9IDMwKSwKICAgICAgICAgICAgICAgICB5ID0gc2VxKC00LCA4LjUsIGxlbiA9IDMwKSwKICAgICAgICAgICAgICAgICB6ID0gc2VxKC00LCA4LjUsIGxlbiA9IDMwKSkKZCA8LSBrZGUzZCh4WywgMV0sIHhbLCAyXSwgeFssIDNdLAogICAgICAgICAgIDAuNSwgMzAsIGMoLTQsIDguNSkpJGQKCmNsZWFyM2QoKQp4diA8LSBzZXEoLTQsIDguNSwgbGVuID0gMzApCmNvbnRvdXIzZChkLCAyMCAvIDMwMDAsIHh2LCB4diwgeHYsCiAgICAgICAgICBjb2xvciA9ICJyZWQiKQpgYGAKCkFkZGluZyB0aGUgZGF0YToKCmBgYHtyLCB3ZWJnbCA9IFRSVUUsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpkMiA8LSBrZGUzZCh4WywgMV0sIHhbLCAyXSwgeFssIDNdLAogICAgICAgICAgICAwLjUsIDMwLCBjKC00LCA4LjUpKSRkCmNsZWFyM2QoKQpwb2ludHMzZCh4ID0geFssIDFdLCB5ID0geFssIDJdLCB6ID0geFssIDNdLAogICAgICAgICBzaXplID0gMiwgY29sb3IgPSAiYmxhY2siKQpjb250b3VyM2QoZDIsIDIwIC8gMzAwMCwgeHYsIHh2LCB4diwKICAgICAgICAgIGNvbG9yID0gInJlZCIsIGFscGhhID0gMC41LAogICAgICAgICAgYWRkID0gVFJVRSkKYGBgCgozRCBwbG90IG9mIHNldmVyYWwgY29udG91cnMgZm9yIGJ3ID0gMC41OgoKYGBge3IsIHdlYmdsID0gVFJVRSwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmNsZWFyM2QoKQpjb250b3VyM2QoZDIsIGMoMTAsIDI1LCA0MCkgLyAzMDAwLCB4diwgeHYsIHh2LAogICAgICAgICAgY29sb3IgPSBjKCJyZWQiLCAiZ3JlZW4iLCAiYmx1ZSIpLAogICAgICAgICAgYWxwaGEgPSBjKDAuMiwgMC41LCAwLjcpLAogICAgICAgICAgYWRkID0gVFJVRSkKYGBgCgpEZW5zaXR5IGNvbnRvdXIgZm9yIHRoZSBgcXVha2VzYCBkYXRhOgoKYGBge3IsIHdlYmdsID0gVFJVRSwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmJnM2QoY29sID0gIndoaXRlIikKY2xlYXIzZCgpCmQgPC0ga2RlM2QocXVha2VzJGxvbmcsIHF1YWtlcyRsYXQsIC1xdWFrZXMkZGVwdGgsIG4gPSA0MCkKY29udG91cjNkKGQkZCwgbGV2ZWwgPSBleHAoLTEyKSwKICAgICAgICAgIHggPSBkJHggLyAyMiwgeSA9IGQkeSAvIDI4LCB6ID0gZCR6IC8gNjQwLAogICAgICAgICAgY29sb3IgPSAiZ3JlZW4iLCAjIyBjb2xvcjIgPSAiZ3JheSIsCiAgICAgICAgICBzY2FsZSA9IEZBTFNFLCBhZGQgPSBUUlVFKQpib3gzZChjb2wgPSAiZ3JheSIpCmBgYAoKQWRkaW5nIGRhdGEgYW5kIHNlY29uZCBjb250b3VyOgoKYGBge3IsIHdlYmdsID0gVFJVRSwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmNsZWFyM2QoKQpwb2ludHMzZChxdWFrZXMkbG9uZyAvIDIyLCBxdWFrZXMkbGF0IC8gMjgsIC1xdWFrZXMkZGVwdGggLyA2NDAsCiAgICAgICAgIHNpemUgPSAyLCBjb2wgPSByZXAoImJsYWNrIiwgbnJvdyhxdWFrZXMpKSkKYm94M2QoY29sID0gImdyYXkiKQpkIDwtIGtkZTNkKHF1YWtlcyRsb25nLCBxdWFrZXMkbGF0LCAtcXVha2VzJGRlcHRoLCBuID0gNDApCmNvbnRvdXIzZChkJGQsIGxldmVsID0gZXhwKGMoLTEwLCAtMTIpKSwKICAgICAgICAgIHggPSBkJHggLyAyMiwgeSA9IGQkeSAvIDI4LCB6ID0gZCR6IC8gNjQwLAogICAgICAgICAgY29sb3IgPSBjKCJyZWQiLCAiZ3JlZW4iKSwgYWxwaGEgPSAwLjEsCiAgICAgICAgICBzY2FsZSA9IEZBTFNFLCBhZGQgPSBUUlVFKQpgYGAKCgojIyBQYXJhbGxlbCBDb29yZGluYXRlcyBQbG90cwoKVGhlIHNhbWUgaWRlYSBhcyBhIHNsb3BlIGdyYXBoLCBidXQgdXN1YWxseSB3aXRoIG1vcmUgdmFyaWFibGVzLgoKU29tZSByZWZlcmVuY2VzOgoKPiBBIFtwb3N0XShodHRwczovL2VhZ2VyZXllcy5vcmcvdGVjaG5pcXVlcy9wYXJhbGxlbC1jb29yZGluYXRlcykgYnkKPiBSb2JlcnQgS29zYXJhLgoKPiBbV2lraXBlZGlhIGVudHJ5XShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9QYXJhbGxlbF9jb29yZGluYXRlcykuCgo+IFtQYXBlcl0oaHR0cDovL3dlYi5hcmNoaXZlLm9yZy93ZWIvMjAyNDA4MTUxMDMzNTAvaHR0cDovL3d3dy5pZnMudHV3aWVuLmFjLmF0L35tbGFuemVuYmVyZ2VyL3RlYWNoaW5nL3BzL3dzMDcvc3R1ZmYvMDAxNDY0MDIucGRmKQo+IG9uIHJlY29nbml6aW5nIG1hdGhlbWF0aWNhbCBvYmplY3RzIGluIHBhcmFsbGVsIGNvb3JkaW5hdGUgcGxvdHMuCgpTb21lIFIgaW1wbGVtZW50YXRpb25zIGluY2x1ZGUgYHBhcmFsbGVscGxvdCgpYCBpbiBgbGF0dGljZWAgYW5kCmBnZ3BhcmNvb3JkKClgIGluIGBHR2FsbHlgCgpBIHBhcmFsbGVsIGNvb3JkaW5hdGUgcGxvdCBvZiBhIGRhdGEgc2V0IG9uIHRoZSBjaGVtaWNhbCBjb21wb3NpdGlvbiBvZgpjb2ZmZWUgc2FtcGxlczoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KbGlicmFyeShncmlkRXh0cmEpCmxpYnJhcnkoR0dhbGx5KQoKZGF0YShjb2ZmZWUsIHBhY2thZ2UgPSAicGdtbSIpCmNvZmZlZSA8LSBtdXRhdGUoY29mZmVlLAogICAgICAgICAgICAgICAgIFR5cGUgPSBpZmVsc2UoVmFyaWV0eSA9PSAxLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkFyYWJpY2EiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlJvYnVzdGEiKSkKZ2dwYXJjb29yZChjb2ZmZWVbb3JkZXIoY29mZmVlJFR5cGUpLCBdLAogICAgICAgICAgIGNvbHVtbnMgPSAzIDogMTQsCiAgICAgICAgICAgZ3JvdXBDb2x1bW4gPSAiVHlwZSIsCiAgICAgICAgICAgc2NhbGUgPSAidW5pbWlubWF4IikgKwogICAgeGxhYigiIikgKwogICAgeWxhYigiIikgKwogICAgc2NhbGVfY29sb3VyX21hbnVhbCgKICAgICAgICB2YWx1ZXMgPSBjKCJncmV5IiwgInJlZCIpKSArCiAgICB0aGVtZShheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGV4dC54ID0KICAgICAgICAgICAgICBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgdmp1c3QgPSAxLAogICAgICAgICAgICAgICAgICAgICAgICAgICBoanVzdCA9IDEpLAogICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInRvcCIpCmBgYAoKVGhlcmUgYXJlIDQzIHNhbXBsZXMgZnJvbSAyOSBjb3VudHJpZXMgYW5kIHR3byB2YXJpZXRpZXMsIF9BcmFiaWNhXyBvcgpfUm9idXN0YV8uCgpFeGFtaW5pbmcgdGhlIHBsb3Qgc2hvd3MgdGhhdCB0aGUgdmFyaWV0aWVzIGFyZSBkaXN0aW5ndWlzaGVkIGJ5IHRoZWlyCmZhdCBhbmQgY2FmZmVpbmUgY29udGVudHM6CgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmdncGxvdChjb2ZmZWUsCiAgICAgICBhZXMoeCA9IEZhdCwKICAgICAgICAgICB5ID0gQ2FmZmluZSwKICAgICAgICAgICBjb2xvdXIgPSBUeXBlKSkgKwogICAgZ2VvbV9wb2ludChzaXplID0gMykgKwogICAgc2NhbGVfY29sb3VyX21hbnVhbCgKICAgICAgICB2YWx1ZXMgPSBjKCJncmV5IiwgInJlZCIpKQpgYGAKClNvbWUgdXNlZnVsIGFkanVzdG1lbnRzOgoKKiBhbHBoYSBibGVuZGluZyBmb3IgbGFyZ2VyIGRhdGEgc2V0czsKCiogdmFyeSBheGlzIHNjYWxpbmc7CgoqIHJlb3JkZXIgYXhlczsKCiogcmV2ZXJzZSBheGVzOwoKSW50ZXJhY3RpdmUgaW1wbGVtZW50YXRpb25zIHRoYXQgc3VwcG9ydCB0aGVzZSBhbmQgbW9yZSBhcmUgYXZhaWxhYmxlLgoKCiMjIEF1c3RyYWxpYW4gQ3JhYnMKClRoZSBkYXRhIGZyYW1lIGBjcmFic2AgaW4gdGhlIGBNQVNTYCBwYWNrYWdlCmNvbnRhaW5zIG1lYXN1cmVtZW50IG9uIGNyYWJzIG9mIGEgc3BlY2llcyB0aGF0IGhhcyBiZWVuIHNwbGl0IGludG8KdHdvIGJhc2VkIG9uIGNvbG9yLCBvcmFuZ2Ugb3IgYmx1ZS4KClByZXNlcnZlZCBzcGVjaW1lbnMgbG9zZSB0aGVpciBjb2xvciAoYW5kIHBvc3NpYmx5IGFiaWxpdHkgdG8gaWRlbnRpZnkKc2V4KS4KCkRhdGEgd2VyZSBjb2xsZWN0ZWQgdG8gaGVscCBjbGFzc2lmeSBwcmVzZXJ2ZWQgc3BlY2ltZW5zLgoKSXQgd291bGQgYmUgdXNlZnVsIHRvIGhhdmUgYSBmYWlybHkgc2ltcGxlIGNsYXNzaWZpY2F0aW9uIHJ1bGUuCgpBIHNjYXR0ZXJwbG90IG1hdHJpeCB2aWV3OgoKYGBge3IsIGZpZy5oZWlnaHQgPSA3LCBmaWcud2lkdGggPSAxMCwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmRhdGEoY3JhYnMsIHBhY2thZ2UgPSAiTUFTUyIpCiMjIHNwbG9tKH4gY3JhYnNbNCA6IDhdLAojIyAgICAgICBncm91cCA9IHBhc3RlKHNleCwgc3ApLAojIyAgICAgICBkYXRhID0gY3JhYnMsCiMjICAgICAgIGF1dG8ua2V5ID0gVFJVRSwKIyMgICAgICAgcHNjYWxlID0gMCkKZ2dwYWlycyhjcmFicywKICAgICAgICBhZXMoY29sb3IgPSBpbnRlcmFjdGlvbihzcCwgc2V4KSksCiAgICAgICAgY29sdW1ucyA9IDQgOiA4LAogICAgICAgIHVwcGVyID0gbGlzdChjb250aW51b3VzID0gInBvaW50cyIpLAogICAgICAgIGxlZ2VuZCA9IDEpCmBgYAoKVGhlIHZhcmlhYmxlcyBzaG93biBhcmU6CgoqIGBGTGAgZnJvbnRhbCBsb2JlIHNpemUgKG1tKTsKKiBgUldgIHJlYXIgd2lkdGggKG1tKTsKKiBgQ0xgIGNhcmFwYWNlIGxlbmd0aCAobW0pOwoqIGBDV2AgY2FyYXBhY2Ugd2lkdGggKG1tKTsKKiBgQkRgIGJvZHkgZGVwdGggKG1tKS4KClRoZSB2YXJpYWJsZXMgYXJlIGhpZ2hseSBjb3JyZWxhdGVkLCByZWZsZWN0aW5nIG92ZXJhbGwgc2l6ZSBhbmQgYWdlLgoKVGhlIGBSVyAqIENMYCBvciBgUlcgKiBDV2AgcGxvdHMgc2VwYXJhdGUgbWFsZXMgYW5kIGZlbWFsZXMgd2VsbCwgYXQKbGVhc3QgZm9yIGxhcmdlciBjcmFicy4KCkEgcGFyYWxsZWwgY29vcmRpbmF0ZXMgdmlldzoKCmBgYHtyLCBldmFsID0gVFJVRSwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmdncGFyY29vcmQoY3JhYnMsCiAgICAgICAgICAgY29sdW1ucyA9IDQgOiA4LAogICAgICAgICAgIGdyb3VwQ29sdW1uID0gInNwIikgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKAogICAgICAgIHZhbHVlcyA9IGMoQiA9ICJibHVlIiwgTyA9ICJvcmFuZ2UiKSkKYGBgCgpBIHBvc3NpYmxlIG5leHQgc3RlcDogUmVkdWNlIHRoZSBjb3JyZWxhdGlvbiBieSBhIHNjYWxpbmcgYnkgb25lIG9mCnRoZSB2YXJpYWJsZXMuCgpgYGB7cn0KY3IgPC0gbXV0YXRlKGNyYWJzLAogICAgICAgICAgICAgRkxDTCA9IEZMIC8gQ0wsCiAgICAgICAgICAgICBSV0NMID0gUlcgLyBDTCwKICAgICAgICAgICAgIENXQ0wgPSBDVyAvIENMLAogICAgICAgICAgICAgQkRDTCA9IEJEIC8gQ0wpCmBgYAoKYGBge3IsIGZpZy5oZWlnaHQgPSA3LCBmaWcud2lkdGggPSAxMCwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CiMjIHNwbG9tKH4gY3JbOSA6IDEyXSwgZ3JvdXAgPSBzcCwKIyMgICAgICAgIGRhdGEgPSBjciwKIyMgICAgICAgIGF1dG8ua2V5ID0gVFJVRSwgcHNjYWxlID0gMCkKZ2dwYWlycyhjciwgYWVzKGNvbG9yID0gc3ApLAogICAgICAgIGNvbHVtbnMgPSA5IDogMTIsCiAgICAgICAgdXBwZXIgPSBsaXN0KGNvbnRpbnVvdXMgPSAicG9pbnRzIiksCiAgICAgICAgbGVnZW5kID0gMSkgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKAogICAgICAgIHZhbHVlcyA9IGMoQiA9ICJibHVlIiwgTyA9ICJvcmFuZ2UiKSkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwoCiAgICAgICAgdmFsdWVzID0gYyhCID0gImJsdWUiLCBPID0gIm9yYW5nZSIpKQpgYGAKClRoZSBgQ1dDTCAqIEZMQ0xgIHBsb3Qgc2hvd3MgZ29vZCBzcGVjaWVzIHNlcGFyYXRpb24gYnkgYSBsaW5lLgoKQSBwYXJhbGxlbCBjb29yZGluYXRlcyBwbG90IGFmdGVyIHNjYWxpbmcgYnkgYENMYDoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZ2dwYXJjb29yZChjciwKICAgICAgICAgICBjb2x1bW5zID0gOSA6IDEyLAogICAgICAgICAgIGdyb3VwQ29sdW1uID0gInNwIikgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKAogICAgICAgIHZhbHVlcyA9IGMoQiA9ICJibHVlIiwgTyA9ICJvcmFuZ2UiKSkKYGBgCgpSZW9yZGVyIHRoZSB2YXJpYWJsZXM6CgpgYGB7cn0KZ2dwYXJjb29yZChjciwKICAgICAgICAgICBjb2x1bW5zID0gYygxMCwgOSwgMTEsIDEyKSwKICAgICAgICAgICBncm91cENvbHVtbiA9ICJzcCIpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCgKICAgICAgICB2YWx1ZXMgPSBjKEIgPSAiYmx1ZSIsIE8gPSAib3JhbmdlIikpCmBgYAoKUmVvcmRlciBhZ2FpbjoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZ2dwYXJjb29yZChjciwKICAgICAgICAgICBjb2x1bW5zID0gYygxMCwgOSwgMTIsIDExKSwKICAgICAgICAgICBncm91cENvbHVtbiA9ICJzcCIpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCgKICAgICAgICB2YWx1ZXMgPSBjKEIgPSAiYmx1ZSIsIE8gPSAib3JhbmdlIikpCmBgYAoKUmV2ZXJzZSB0aGUgYENXQ0xgIHZhcmlhYmxlOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpnZ3BhcmNvb3JkKG11dGF0ZShjciwgQ1dDTCA9IC1DV0NMKSwKICAgICAgICAgICBjb2x1bW5zID0gYygxMCwgOSwgMTIsIDExKSwKICAgICAgICAgICBncm91cENvbHVtbiA9ICJzcCIpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCgKICAgICAgICB2YWx1ZXMgPSBjKEIgPSAiYmx1ZSIsIE8gPSAib3JhbmdlIikpCmBgYAoKVGhlIHBhdHRlcm5zIGZvciBgRkxDTGAsIGBDV0xDYCwgYW5kIGBCRENMYCBmb3IgdGhlIHR3byBzcGVjaWVzIGRpZmZlci4KCgojIyBEaW1lbnNpb24gUmVkdWN0aW9uIGJ5IFBDQQoKQSBudW1iZXIgb2YgbWV0aG9kcyBhcmUgYXZhaWxhYmxlIGZvciBleHRyYWN0aW5nIGEgbG93ZXIgZGltZW5zaW9uYWwKcmVwcmVzZW50YXRpb24gb2YgYSBkYXRhIHNldCB0aGF0IGNhcHR1cmVzIG1vc3QgaW1wb3J0YW50IGZlYXR1cmVzLgoKT25lIGFwcHJvYWNoIGlzIF9wcmluY2lwYWwgY29tcG9uZW50IGFuYWx5c2lzXywgb3IgX1BDQV8uCgoqIFByaW5jaXBhbCBjb21wb25lbnQgYW5hbHlzaXMgaWRlbnRpZmllcyBhIHJvdGF0aW9uIG9mIHRoZSBkYXRhIHRoYXQKICBwcm9kdWNlcyB1bmNvcnJlbGF0ZWQgc2NvcmVzLgoKKiBDb21wb25lbnRzIGFyZSBvcmRlcmVkIGJ5IHRoZSBhbW91bnQgdGhleSBjb250cmlidXRlIHRvIHRoZSBvdmVyYWxsCiAgdmFyaWF0aW9uIGluIHRoZSBkYXRhLgoKKiBTb21ldGltZXMgdGhlIGZpcnN0IGZldyBwcmluY2lwYWwgY29tcG9uZW50cyBjYXB0dXJlIG1vc3Qgb2YgdGhlCiAgaW50ZXJlc3RpbmcgdmFyaWF0aW9uLgoKKiBUaGUgY29tcG9uZW50cyBhcmUgbGluZWFyIGNvbWJpbmF0aW9ucyBvZiB0aGUgb3JpZ2luYWwgdmFyaWFibGVzOwogIHRoZXkgbWF5IG5vdCBiZSBlYXN5IHRvIGludGVycHJldC4KClRoZSBmaXJzdCB0d28gcHJpbmNpcGFsIGNvbXBvbmVudHMgZm9yIHRoZSBzY2FsZWQgY3JhYnMgZGF0YToKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZml0IDwtIHNlbGVjdChjciwgOSA6IDEyKSB8PgogICAgbXV0YXRlKGFjcm9zcyhldmVyeXRoaW5nKCksIHNjYWxlKSkgfD4KICAgIHByY29tcCgpCmNyX1BDIDwtIGNiaW5kKGNyLCBmaXQkeCkKCnAgPC0gZ2dwbG90KGNyX1BDLCBhZXMoUEMxLCBQQzIsIGNvbG9yID0gc3ApKSArCiAgICBnZW9tX3BvaW50KCkgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoTyA9ICJvcmFuZ2UiLCBCID0gImJsdWUiKSkKCmxpYnJhcnkoZ2dyZXBlbCkKcCArIGdlb21fc2VnbWVudChhZXMoeCA9IDAsIHkgPSAwLCB4ZW5kID0gUEMxLCB5ZW5kID0gUEMyKSwKICAgICAgICAgICAgICAgICBkYXRhID0gYXMuZGF0YS5mcmFtZShmaXQkcm90YXRpb24pLAogICAgICAgICAgICAgICAgIGNvbG9yID0gImJsYWNrIiwKICAgICAgICAgICAgICAgICBhcnJvdyA9IGFycm93KGxlbmd0aCA9IHVuaXQoMC4wMywgIm5wYyIpKSkgKwogICAgZ2VvbV90ZXh0X3JlcGVsKGFlcyh4ID0gUEMxLCB5ID0gUEMyLAogICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IHJvd25hbWVzKGZpdCRyb3RhdGlvbikpLAogICAgICAgICAgICAgICAgICAgIGRhdGEgPSBhcy5kYXRhLmZyYW1lKGZpdCRyb3RhdGlvbiksCiAgICAgICAgICAgICAgICAgICAgY29sb3IgPSAiYmxhY2siKQpgYGAKClRoZSB0d28gc3BlY2llcyBhcmUgc2VwYXJhdGVkIHF1aXRlIHdlbGwgYnkgdGhlIGZpcnN0IHByaW5jaXBhbApjb21wb25lbnQgYWxvbmUuCgpGb3IgdGhlIGNvZmZlZSBkYXRhIHRoZSBmaXJzdCBwcmluY2lwYWwgY29tcG9uZW50IGFsc28gc2VwYXJhdGVzIHRoZQp2YXJpZXRpZXMgd2VsbDoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZml0IDwtIHNlbGVjdChjb2ZmZWUsIDQ6MTQpIHw+CiAgICBtdXRhdGUoYWNyb3NzKGV2ZXJ5dGhpbmcoKSwgc2NhbGUpKSB8PgogICAgcHJjb21wKCkKY29mZmVlX1BDIDwtIGNiaW5kKGNvZmZlZSwgZml0JHgpCgpnZ3Bsb3QoY29mZmVlX1BDLCBhZXMoeCA9IFBDMSwgeSA9IFBDMiwgY29sb3IgPSBUeXBlKSkgKwogICAgZ2VvbV9wb2ludChzaXplID0gMykgKwogICAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCJncmV5IiwgInJlZCIpKQpgYGAKClRoZSBmaXJzdCBwcmluY2lwYWwgY29tcG9uZW50IGlzIGEgd2VpZ2h0ZWQgY29tYmluYXRpb24gb2YgdGhlCm9yaWdpbmFsIHZhcmlhYmxlczsgdGhlcmUgaXMgc29tZSB3ZWlnaHQsIHBvc2l0aXZlIG9yIG5lZ2F0aXZlLCBvbgphbG1vc3QgZXZlcnkgdmFyaWFibGUsIHdoaWNoIG1ha2VzIHRoZSByZXN1bHQgaGFyZCB0byBpbnRlcnByZXQuCgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmRhdGEuZnJhbWUodmFyaWFibGUgPSByb3duYW1lcyhmaXQkcm90YXRpb24pLAogICAgICAgICAgIHdlaWdodCA9IGFzLnZlY3RvcihmaXQkcm90YXRpb25bLCAxXSkpIHw+CiAgICBnZ3Bsb3QoYWVzKHggPSB3ZWlnaHQsIHkgPSB2YXJpYWJsZSkpICsKICAgIGdlb21fY29sKHdpZHRoID0gMC4yKQpgYGAKCgojIyBHcmFuZCBUb3VycwoKYGBge3IsIGVjaG8gPSBGQUxTRX0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoSU1HKCJmbGVhdG91ci5naWYiKSkKYGBgCgpUaGUgZ3JhbmQgdG91ciBjYW4gYmUgdmlld2VkIGFzIGNhcnJ5aW5nIG91dCBhIHNlcXVlbmNlIG9mIHJvdGF0aW9ucwppbiBoaWdoIGRpbWVuc2lvbmFsIHNwYWNlIGFuZCBzaG93aW5nIGltYWdlcyBvZiBzb21lIG9mIHRoZQpjb29yZGluYXRlcy4KClRoaXMgY2FuIGJlIHVzZWZ1bCBmb3IgZGlzY292ZXJpbmcgZ3JvdXBzLCBvdXRsaWVycywgYW5kIHNvbWUKbG93ZXItZGltZW5zaW9uYWwgc3RydWN0dXJlcy4KClRoZSByb3RhdGlvbnMgY2FuIGJlIHJhbmRvbSBvciBzZWxlY3RlZCB0byBvcHRpbWl6ZSBzb21lIGNyaXRlcmlvbgooZ3VpZGVkIHRvdXJzKS4KClRoZSBgdG91cnJgIHBhY2thZ2UgcHJvdmlkZXMgb25lIGltcGxlbWVudGF0aW9uLgoKR29vZCBpbnRlcmFjdGl2ZSBpbnRlcmZhY2VzIGRvIG5vdCBzZWVtIHRvIGJlIHJlYWRpbHkgYXZhaWxhYmxlIGF0IHRoZQptb21lbnQuCgoKIyMgUmVhZGluZwoKQ2hhcHRlciBbX1Zpc3VhbGl6aW5nIGFzc29jaWF0aW9ucyBhbW9uZyB0d28gb3IgbW9yZSBxdWFudGl0YXRpdmUKICB2YXJpYWJsZXNfXShodHRwczovL2NsYXVzd2lsa2UuY29tL2RhdGF2aXovdmlzdWFsaXppbmctYXNzb2NpYXRpb25zLmh0bWwpCiAgaW4gW19GdW5kYW1lbnRhbHMgb2YgRGF0YQogIFZpc3VhbGl6YXRpb25fXShodHRwczovL2NsYXVzd2lsa2UuY29tL2RhdGF2aXovKS4KCgo=