Categorical Data
Categorical data can be
nominal, qualitative
ordinal
For visualization, the main difference is that ordinal data suggests
a particular display order.
Purely categorical data can come in a range of formats.
The most common are
Raw Data
Raw data for a survey of individuals that records hair color, eye
color, and gender of 592 individuals might look like this:
head(raw)
## Hair Eye Sex
## 1 Brown Blue Male
## 2 Brown Brown Male
## 3 Brown Hazel Male
## 4 Blond Green Female
## 5 Brown Brown Female
## 6 Brown Hazel Male
Aggregated Data
One way to aggregate raw categorical data is to use
count from dplyr:
library(dplyr)
agg <- count(raw, Hair, Eye, Sex) |>
as_tibble()
agg
## # A tibble: 32 × 4
## Hair Eye Sex n
## <fct> <fct> <fct> <int>
## 1 Black Brown Male 32
## 2 Black Brown Female 36
## 3 Black Blue Male 11
## 4 Black Blue Female 9
## 5 Black Hazel Male 10
## 6 Black Hazel Female 5
## 7 Black Green Male 3
## 8 Black Green Female 2
## 9 Brown Brown Male 53
## 10 Brown Brown Female 66
## # ℹ 22 more rows
Cross-Tabulated Data
Cross-tabulated data can be produced from aggregate data using
xtabs:
xtabs(n ~ Hair + Eye + Sex, data = agg)
## , , Sex = Male
##
## Eye
## Hair Brown Blue Hazel Green
## Black 32 11 10 3
## Brown 53 50 25 15
## Red 10 10 7 7
## Blond 3 30 5 8
##
## , , Sex = Female
##
## Eye
## Hair Brown Blue Hazel Green
## Black 36 9 5 2
## Brown 66 34 29 14
## Red 16 7 7 7
## Blond 4 64 5 8
Cross-tabulated data can be produced from raw data using
table:
xtb <- table(raw)
xtb
## , , Sex = Male
##
## Eye
## Hair Brown Blue Hazel Green
## Black 32 11 10 3
## Brown 53 50 25 15
## Red 10 10 7 7
## Blond 3 30 5 8
##
## , , Sex = Female
##
## Eye
## Hair Brown Blue Hazel Green
## Black 36 9 5 2
## Brown 66 34 29 14
## Red 16 7 7 7
## Blond 4 64 5 8
Both raw and aggregate data in this example are in tidy
form; the cross-tabulated data is not.
Cross-tabulated data on \(p\)
variables is arranged in a \(p\) -way
array.
The cross-tabulated data can be converted to the tidy aggregate form
using as.data.frame:
class(xtb)
## [1] "table"
head(as.data.frame(xtb))
## Hair Eye Sex Freq
## 1 Black Brown Male 32
## 2 Brown Brown Male 53
## 3 Red Brown Male 10
## 4 Blond Brown Male 3
## 5 Black Blue Male 11
## 6 Brown Blue Male 50
The variable xtb corresponds to the data set
HairEyeColor in the datasets package,
Working With Categorical Variables
Categorical variables are usually represented as:
character vectors
factors.
Some advantages of factors:
more control over ordering of levels
levels are preserved when forming subsets
levels can reflect possible values not present in the
data
Most plotting and modeling functions will convert character vectors
to factors with levels ordered alphabetically.
Some standard R functions for working with factors include
factor creates a factor from another type of
variable
levels returns the levels of a factor
reorder changes level order to match another
variable
relevel moves a particular level to the first position
as a base line
droplevels removes levels not in the variable.
The tidyverse package forcats adds some
more tools, including
fct_inorder creates a factor with levels ordered by
first appearance
fct_infreq orders levels by decreasing frequency
fct_rev reverses the levels
fct_recode changes factor levels
fct_relevel moves one or more levels
fct_c merges two or more factors
fct_collapse merge some factor levels
Bar Charts For Frequencies
Basics
A bar chart is often used to show the frequencies of a categorical
variable.
By default, geom_bar uses stat = "count"
and maps its result to the y aesthetic.
This is suitable for raw data:
thm <- theme_minimal() +
theme(text = element_text(size = 16))
ggplot(raw) +
geom_bar(aes(x = Hair),
fill = "deepskyblue3") +
thm
For a nominal variable it is often better to order the bars by
decreasing frequency:
library(forcats)
ggplot(mutate(raw,
Hair = fct_infreq(Hair))) +
geom_bar(aes(x = Hair),
fill = "deepskyblue3") +
thm
If the data have already been aggregated, then you need to either
ggplot(agg) +
geom_col(aes(x = Hair,
y = n),
fill = "deepskyblue3") +
thm
For aggregated data, reordering can be based on the computed counts
using
agg_ord <-
mutate(agg,
Hair = reorder(Hair, -n, sum))
ggplot(agg_ord) +
geom_col(aes(x = Hair,
y = n),
fill = "deepskyblue3") +
thm
Adding a Grouping Variable
Mapping the Eye variable to fill in
ggplot produces a stacked bar chart .
An alternative, specified with position = "dodge", is a
side by side bar chart, or a clustered bar chart.
For the side by side chart in particular it may be useful to also
reorder the Eye color levels.
ecols <- c(Brown = "brown4",
Blue = "blue2",
Hazel = "darkgoldenrod3",
Green = "green4")
agg_ord <-
mutate(agg,
Hair = reorder(Hair, -n, sum),
Eye = reorder(Eye, -n, sum))
p1 <- ggplot(agg_ord) +
geom_col(aes(x = Hair,
y = n,
fill = Eye)) +
scale_fill_manual(values = ecols) +
thm
p2 <- ggplot(agg_ord) +
geom_col(aes(x = Hair,
y = n,
fill = Eye),
position = "dodge") +
scale_fill_manual(values = ecols) +
thm
(p1 + guides(fill = "none")) | p2
Faceting can be used to bring in additional variables:
p1 + facet_wrap(~ Sex)
The counts shown here may not be the most relevant features for
understanding the joint distributions of these variables.
Pie Charts and Doughnut Charts
Pie charts go by many different names (from a Twitter
thread ):
Pie charts can be viewed as stacked bar charts in polar
coordinates:
hcols <- c(Black = "black", Brown = "brown4",
Red = "brown1", Blond = "lightgoldenrod1")
p1 <- ggplot(agg_ord) +
geom_col(aes(x = 1, y = n, fill = Hair), position = "fill") +
coord_polar(theta = "y") +
scale_fill_manual(values = hcols) +
thm
p2 <- ggplot(agg_ord) +
geom_col(aes(x = Hair, y = n, fill = Hair)) +
scale_fill_manual(values = hcols) +
thm
(p1 + guides(fill = "none")) | p2
The axes and grid lines are not helpful for the pie chart and can be
removed with some theme settings.
Using faceting we can also separately show the distributions for men
and women:
pie_thm <- thm +
theme(axis.title = element_blank(),
axis.text = element_blank(),
axis.ticks = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank())
p3 <- p1 + facet_wrap(~ Sex) + pie_thm
p3
Doughnut charts are a variant that has recently become
popular in the media:
p4 <- p3 + xlim(0, 1.5)
p4
The center is often used for annotation:
p4 + geom_text(aes(x = 0, y = 0, label = Sex), size = 5) +
theme(strip.background = element_blank(),
strip.text = element_blank())
An alternative to the polar coordinates approach uses
geom_arc_bar and stat_pie from package
ggforce:
library(ggforce)
arrange(agg_ord, desc(Hair)) |>
ggplot(aes(x0 = 0, y0 = 0, r0 = 0, r = 1, amount = n, fill = Hair)) +
geom_arc_bar(stat = "pie", color = NA) +
coord_fixed() +
scale_fill_manual(values = hcols) +
pie_thm +
facet_wrap(~ Sex)
For doughnut charts:
arrange(agg_ord, desc(Hair)) |>
ggplot(aes(x0 = 0, y0 = 0, r0 = 0.4, r = 1, amount = n, fill = Hair)) +
geom_arc_bar(stat = "pie", color = NA) +
geom_text(aes(x = 0, y = 0, label = Sex), size = 5) +
coord_fixed() +
scale_fill_manual(values = hcols) +
pie_thm +
theme(strip.background = element_blank(),
strip.text = element_blank()) +
facet_wrap(~ Sex)
Some Notes
Pie charts are effective for judging part/whole relationships.
Pie charts can be effective for comparing proportions to
Pie charts are not very effective for comparing proportions to each
other.
3D pie charts are popular and a very bad idea. An example (Fig.
6.61 ) from Andy Kirk’s book (2016), Data Visualization: A
Handbook for Data Driven Design :
Pie charts are widely used for political data.
With the right ordering, pie charts are very good at showing
which coalitions of parties can form a majority.
When no one candidate earns a majority of the votes, pie charts
do not show which candidate has earned a plurality very well.
Good orientation and factor ordering can help.
elect <- geofacet::election |>
group_by(candidate) |>
summarize(votes = sum(votes))
p1 <- ggplot(elect) +
geom_col(aes(x = 1, y = votes, fill = candidate), position = "fill") +
coord_polar(theta = "y", start = -1) +
xlim(c(-0.5, 1.5)) +
scale_fill_manual(values = c(Trump = scales::muted("red", 50, 80),
Clinton = scales::muted("blue", 50, 70),
Other = "grey")) +
pie_thm
p2 <- mutate(elect,
candidate = factor(candidate,
c("Clinton", "Other", "Trump"))) |>
ggplot() +
geom_col(aes(x = 1, y = votes, fill = candidate), position = "fill") +
coord_polar(theta = "y") +
xlim(c(-0.5, 1.5)) +
scale_fill_manual(values = c(Trump = scales::muted("red", 50, 80),
Clinton = scales::muted("blue", 50, 70),
Other = "grey")) +
pie_thm
p3 <- ggplot(elect) +
geom_col(aes(x = candidate,
y = 100 * (votes / sum(votes)),
fill = candidate)) +
scale_fill_manual(values = c(Trump = scales::muted("red", 50, 80),
Clinton = scales::muted("blue", 50, 70),
Other = "grey")) +
labs(y = "percent") +
thm +
theme(axis.text.x = element_blank(),
axis.title.x = element_blank())
(p1 + guides(fill = "none")) + (p2 + guides(fill = "none")) + p3
Some Alternatives
Stacked Bar Charts
Stacked bar charts with equal heights, or filled bar charts, are an
alternative for representing part-whole relationships.
ggplot(agg) +
geom_col(aes(x = Sex, y = n, fill = Hair), position = "fill") +
scale_fill_manual(values = hcols) +
thm
Waffle Charts
Another alternative is a waffle chart , sometimes also called
a square pie chart .
The waffle
package is one R implementation of this idea.
Currently the development version on GitHub is needed for the
following examples.
Showing the counts:
library(waffle)
stopifnot(packageVersion("waffle") >= "1.0.1")
ggplot(arrange(agg, Hair), aes(values = n, fill = Hair)) +
geom_waffle(n_rows = 18, flip = TRUE, color = "white", size = 0.33,
na.rm = FALSE) +
coord_equal() +
facet_wrap(~ Sex) +
scale_fill_manual(values = hcols) +
theme_minimal() +
theme_enhance_waffle()
Showing the proportions:
round_pct <- function(n) {
pct <- 100 * (n / sum(n))
nn <- floor(pct)
if (sum(nn) < 100) {
rem <- pct - nn
idx <- sort(order(rem), decreasing = TRUE)[seq_len(100 - sum(nn))]
nn[idx] <- nn[idx] + 1
}
nn
}
group_by(agg, Sex) |>
mutate(pct = round_pct(n)) |>
ungroup() |>
ggplot(aes(values = pct, fill = Hair)) +
geom_waffle(n_rows = 10, flip = TRUE, color = "white", size = 0.33,
na.rm = FALSE) +
coord_equal() +
facet_wrap(~ Sex) +
scale_fill_manual(values = hcols) +
theme_minimal() +
theme_enhance_waffle()
Population Pyramids
Bar charts for two groups can be shown back to back.
mutate(agg, Hair = reorder(Hair, n, sum)) |>
ggplot(aes(x = ifelse(Sex == "Male", n, -n),
y = Hair,
fill = Sex)) +
geom_col() +
xlab("Count") +
thm
This is often used for showing age distributions by sex for
populations; the result is called a population
pyramid .
Age distribution data for many countries and years is available from
a Census Bureau
website .
Data files for 2020 for Germany
and Nigeria
are available locally.
if (! file.exists("germany-2020.csv"))
download.file("https://stat.uiowa.edu/~luke/data/germany-2020.csv",
"germany-2020.csv")
if (! file.exists("nigeria-2020.csv"))
download.file("https://stat.uiowa.edu/~luke/data/nigeria-2020.csv",
"nigeria-2020.csv")
gm_pop <- read.csv("germany-2020.csv", skip = 1) |>
filter(Age != "Total") |>
mutate(Age = fct_inorder(Age))
ni_pop <- read.csv("nigeria-2020.csv", skip = 1) |>
filter(Age != "Total") |>
mutate(Age = fct_inorder(Age))
Combining the data sets allows a side by side comparison of the
counts:
library(tidyr)
pop2 <-
bind_rows(mutate(gm_pop, Country = "Germany"),
mutate(ni_pop, Country = "Nigeria")) |>
select(Age,
Male = Male.Population,
Female = Female.Population, Country) |>
pivot_longer(Male : Female,
names_to = "Sex",
values_to = "n")
ggplot(pop2) +
geom_col(aes(x = ifelse(Sex == "Male", n, -n),
y = Age,
fill = Sex)) +
facet_wrap(~ Country) +
scale_x_continuous(
labels = function(n) scales::comma(abs(n))) +
xlab("Count") +
thm +
theme(legend.position = "top")
The different shapes are evident, but are harder to see than they
could be because of the difference in total population:
group_by(pop2, Country) |>
summarize(Population = sum(n)) |>
ungroup() |>
mutate(Population = scales::comma(Population)) |>
knitr::kable(format = "html", align = "lr") |>
kableExtra::kable_styling(full_width = FALSE)
Country
Population
Germany
80,159,662
Nigeria
214,028,302
Using a group mutate we can compute sex/age group percentages within
each country:
group_by(pop2, Country) |>
mutate(pct = 100 * n / sum(n)) |>
ungroup() |>
ggplot() +
geom_col(aes(x = ifelse(Sex == "Male", pct, -pct),
y = Age,
fill = Sex)) +
facet_wrap(~ Country) +
scale_x_continuous(
labels = function(x) scales::percent(abs(x / 100))) +
xlab("Percent") +
thm +
theme(legend.position = "top")
Multiple Categorical Variables
Visualizing the distribution of multiple categorical variables
involves visualizing counts and proportions.
Distributions can be viewed as
When one variable (or several) can be viewed as a response and others
as predictors then it is common to focus on the conditional distribution
of the response given the predictors.
The most common approaches use variants of bar and area charts.
The resulting plots are often called mosaic
plots .
Two Data Sets
Hair and Eye Color
HairEyeColorDF <-
as.data.frame(HairEyeColor)
head(HairEyeColorDF)
## Hair Eye Sex Freq
## 1 Black Brown Male 32
## 2 Brown Brown Male 53
## 3 Red Brown Male 10
## 4 Blond Brown Male 3
## 5 Black Blue Male 11
## 6 Brown Blue Male 50
Marginal distributions of the variables:
p1 <- ggplot(HairEyeColorDF) +
geom_col(aes(Sex, Freq), fill = "deepskyblue3") +
thm
p2 <- mutate(HairEyeColorDF, Hair = reorder(Hair, -Freq, sum)) |>
ggplot() +
geom_col(aes(Hair, Freq), fill = "deepskyblue3") +
thm
p3 <- ggplot(HairEyeColorDF) +
geom_col(aes(Eye, Freq), fill = "deepskyblue3") +
thm
p1 | p2 | p3
Arthritis Data
The vcd package includes the data frame
Arthritis with several variables for 84 patients in a
clinical trial for a treatment for rheumatoid arthritis.
data(Arthritis, package = "vcd")
head(Arthritis)
## ID Treatment Sex Age Improved
## 1 57 Treated Male 27 Some
## 2 46 Treated Male 29 None
## 3 77 Treated Male 30 None
## 4 17 Treated Male 32 Marked
## 5 36 Treated Male 46 Marked
## 6 23 Treated Male 58 Marked
The Improved variable is the response.
The predictors are Treatment, Sex, and
Age.
Counts for the categorical predictors:
xtabs(~ Sex, Arthritis)
## Sex
## Female Male
## 59 25
xtabs(~ Treatment, Arthritis)
## Treatment
## Placebo Treated
## 43 41
xtabs(~ Treatment + Sex, data = Arthritis)
## Sex
## Treatment Female Male
## Placebo 32 11
## Treated 27 14
Joint distribution of the predictors:
ggplot(Arthritis) +
geom_histogram(aes(x = Age),
binwidth = 10,
fill = "deepskyblue3",
color = "black") +
facet_grid(Treatment ~ Sex) +
thm
Conditional distribuiton of age, given sex and treatment:
ggplot(Arthritis) +
geom_histogram(aes(x = Age,
y = after_stat(density)),
binwidth = 10,
fill = "deepskyblue3",
color = "black") +
facet_grid(Treatment ~ Sex) +
thm
Bar Charts
Hair and Eye Color
Default bar charts show the individual count or joint
proportions.
For the hair-eye color aggregated data counts:
ggplot(HairEyeColorDF) +
geom_col(aes(x = Eye, y = Freq, fill = Sex)) +
facet_wrap(~ Hair) +
thm
Joint proportions:
ggplot(mutate(HairEyeColorDF, Prop = Freq / sum(Freq))) +
geom_col(aes(x = Eye, y = Prop, fill = Sex)) +
facet_wrap(~ Hair) +
thm
Showing conditional distributions requires computing proportions
within groups.
For the joint conditional distribution of sex and eye color given
hair color:
group_by(HairEyeColorDF, Hair) |>
mutate(Prop = Freq / sum(Freq)) |>
ungroup() |>
ggplot() +
geom_col(aes(x = Eye, y = Prop, fill = Sex)) +
facet_wrap(~ Hair) +
thm
It is easier to compare the skewness of the eye color
distributions for black, brown, and red hair.
Assessing the proportion of females or males withing the
different groups is possible but challenging since it requires relative
length comparisons.
To more clearly see the that the proportion of females among subjects
with blond hair and blue eyes is higher than for other hair/eye color
combinations we can look at the conditional distribution of sex given
hair and eye color.
group_by(HairEyeColorDF, Hair, Eye) |>
mutate(Prop = Freq / sum(Freq)) |>
ungroup() |>
ggplot() +
geom_col(aes(x = Eye,
y = Prop,
fill = Sex)) +
facet_wrap(~ Hair, nrow = 1) +
thm +
theme(axis.text.x =
element_text(angle = 45,
hjust = 1))
This plot can also be obtained using
position = "fill".
ggplot(HairEyeColorDF) +
geom_col(aes(x = Eye,
y = Freq,
fill = Sex),
position = "fill") +
facet_wrap(~ Hair, nrow = 1) +
thm +
theme(axis.text.x =
element_text(angle = 45,
hjust = 1))
One drawback: This visualization no longer shows that some of the
hair/eye color combinations are more common than others.
Arthritis Data
For the raw arthritis data, geom_bar computes the
aggregate counts and produces a stacked bar chart by default:
p <- ggplot(Arthritis, aes(x = Sex,
fill = Improved)) +
facet_wrap(~ Treatment)
p + geom_bar() +
scale_fill_brewer(palette = "Blues") +
thm
Specifying position = "dodge" produces a side-by-side
plot:
p + geom_bar(position = "dodge") +
scale_fill_brewer(palette = "Blues") +
thm
There are no cases of male patients on placebo reporting
Some improvement, resulting in wider bars for the other
options.
One way to produce a zero height bar:
library(tidyr)
comp_counts <-
count(Arthritis,
Treatment, Sex, Improved) |>
complete(Treatment, Sex, Improved,
fill = list(n = 0))
ggplot(comp_counts,
aes(x = Sex, y = n, fill = Improved)) +
geom_col(position = "dodge") +
facet_wrap(~ Treatment) +
scale_fill_brewer(palette = "Blues") +
thm
Another option is to use the preserve = "single" option
with position_dodge.
p + geom_bar(position =
position_dodge(
preserve = "single")) +
scale_fill_brewer(palette = "Blues") +
thm
Showing conditional distributions of Improved given
different levels of Treatment and Sex:
group_by(comp_counts, Treatment, Sex) |>
mutate(prop = n / sum(n)) |>
ungroup() |>
ggplot() +
geom_col(aes(x = Sex,
y = prop,
fill = Improved),
position = "dodge") +
facet_wrap(~ Treatment) +
scale_fill_brewer(palette = "Blues") +
thm
Stacked bar charts with height one are another option to make these
conditional distributions easier to compare:
p + geom_bar(position = "fill") +
scale_fill_brewer(palette = "Blues") +
thm
Ordering of variables affects which comparisons are easier.
ggplot(Arthritis, aes(x = Treatment, fill = Improved)) +
geom_bar(position = "fill") +
scale_fill_brewer(palette = "Blues") +
thm +
facet_wrap(~ Sex)
Some notes:
The stacked bar chart is effective for two categories, and a few
more if they are ordered.
Providing a visual indication of uncertainty in the estimates is
a challenge. The standard errors in this case are around 0.1.
The proportions of each treatment group that are male or female
could be encoded in the bar widths.
The resulting plot is called a spine plot .
Basic ggplot2 does not seem to make this
easy.
Spine Plots
Spine plots are a special case of mosaic
plots , and can be seen as a generalization of stacked bar
plots.
For a spine plot the proportions for the categories of a predictor
variable are encoded in the bar widths.
The ggmosaic package provides support for mosaic plots
in the ggplot framework. (It can be a little rough around
the edges.)
Spine plots are provided by the base graphics function
spineplot and the vcd function
spine.
vcd plots are built on the grid graphics
system, like lattice and ggplot2 graphics.
A spine plot for the distribution of Improved given
Sex in the Treated group:
library(ggmosaic)
filter(Arthritis, Treatment == "Treated") |>
mutate(Improved = fct_rev(Improved)) |>
ggplot() +
geom_mosaic(aes(x = product(Sex),
fill = Improved)) +
scale_fill_brewer(palette = "Blues",
direction = -1,
guide = guide_legend(reverse = TRUE)) +
facet_wrap(~ Treatment) +
thm + labs(x = "", y = "Improved")
Spine plots for Treatment groups using faceting:
library(ggmosaic)
mutate(Arthritis,
Improved = fct_rev(Improved)) |>
ggplot() +
geom_mosaic(aes(x = product(Sex),
fill = Improved)) +
scale_fill_brewer(palette = "Blues",
direction = -1,
guide = guide_legend(reverse = TRUE)) +
facet_wrap(~ Treatment) +
thm + labs(x = "", y = "Improved")
Spine plots for the arthritis data, faceted on Sex:
library(ggmosaic)
mutate(Arthritis,
Improved = fct_rev(Improved)) |>
ggplot() +
geom_mosaic(aes(x = product(Treatment),
fill = Improved)) +
scale_fill_brewer(palette = "Blues",
direction = -1,
guide = guide_legend(reverse = TRUE)) +
facet_wrap(~ Sex) +
thm + labs(x = "", y = "Improved")
This no longer shows the Female/Male imbalance.
For aggregate counts use the weight aesthetic:
mutate(HairEyeColorDF, Sex = fct_rev(Sex)) |>
ggplot() +
geom_mosaic(aes(weight = Freq,
x = product(Hair),
fill = Sex)) +
thm + labs(x = "Hair", y = "")
Spine plots of Sex within Eye color,
faceted on Hair color:
mutate(HairEyeColorDF, Sex = fct_rev(Sex)) |>
ggplot() +
geom_mosaic(aes(weight = Freq,
x = product(Eye),
fill = Sex)) +
thm + labs(x = "Eye", y = "") +
facet_wrap(~ Hair,
nrow = 1,
scales = "free_x") +
theme(legend.position = "top",
axis.text.y = element_blank(),
axis.text.x =
element_text(angle = 45,
hjust = 1)) +
scale_y_continuous(expand = c(0, 0))
The relative sizes of the groups on the x (eye color)
axis are shown within the facets.
The sizes of the faceted variable (hair color) groups are not
reflected.
Double decker plots try to address this.
Doubledecker Plots
Doubledecker plots can be viewed as a generalization of
spine plots to multiple predictors.
Package vcd provides the doubledecker
function.
This function can use a formula interface.
arth_pal <-
RColorBrewer::brewer.pal(3, "Blues")
arth_gp <- grid::gpar(fill = arth_pal)
vcd::doubledecker(Improved ~ Treatment + Sex,
data = Arthritis,
gp = arth_gp,
margins = c(2, 5, 4, 2))
vcd::doubledecker(Improved ~ Sex + Treatment,
data = Arthritis,
gp = arth_gp,
margins = c(2, 5, 4, 2))
Using ggmosaic:
mutate(Arthritis,
Improved = fct_rev(Improved)) |>
ggplot() +
geom_mosaic(
aes(x = product(Sex, Treatment),
fill = Improved),
divider = ddecker()) +
scale_fill_brewer(palette = "Blues",
direction = -1,
guide = guide_legend(reverse = TRUE)) +
thm +
theme(axis.text.x =
element_text(angle = 15,
hjust = 1)) +
labs(x = "", y = "")
mutate(Arthritis,
Improved = fct_rev(Improved)) |>
ggplot() +
geom_mosaic(
aes(x = product(Treatment, Sex),
fill = Improved),
divider = ddecker()) +
scale_fill_brewer(palette = "Blues",
direction = -1,
guide = guide_legend(reverse = TRUE)) +
thm +
theme(axis.text.x =
element_text(angle = 15,
hjust = 1)) +
labs(x = "", y = "")
Another approach using the ggh4x
package:
library(dplyr)
library(ggplot2)
library(ggh4x)
data(Arthritis, package = "vcd")
ggplot(Arthritis, aes(x = 0, fill = Improved)) +
geom_bar(position = "fill") +
scale_fill_brewer(palette = "Blues") +
facet_nested(~ Treatment + Sex) +
force_panelsizes(cols = count(Arthritis, Treatment, Sex)$n) +
labs(x = element_blank(), y = element_blank()) +
scale_x_continuous(expand = c(0, 0), breaks = NULL, labels = NULL) +
scale_y_continuous(expand = c(0, 0), breaks = NULL, labels = NULL)
## Warning: `label` cannot be a <ggplot2::element_blank> object.
## `label` cannot be a <ggplot2::element_blank> object.
library(dplyr)
library(ggplot2)
library(ggh4x)
data(Arthritis, package = "vcd")
ggplot(Arthritis, aes(x = 0, fill = Improved)) +
geom_bar(position = "fill") +
scale_fill_brewer(palette = "Blues") +
facet_nested(~ Sex + Treatment) +
force_panelsizes(cols = count(Arthritis, Sex, Treatment)$n) +
labs(x = element_blank(), y = element_blank()) +
scale_x_continuous(expand = c(0, 0), breaks = NULL, labels = NULL) +
scale_y_continuous(expand = c(0, 0), breaks = NULL, labels = NULL)
## Warning: `label` cannot be a <ggplot2::element_blank> object.
## `label` cannot be a <ggplot2::element_blank> object.
Mosaic Plots
Mosaic plots recursively partition the axes to represent
counts of categorical variables as rectangles.
Both support a formula interface.
A Mosaic plot for the predictors Sex and
Treatment:
vcd::mosaic(~ Sex + Treatment,
data = Arthritis)
Adding Improved to the joint distribution:
vcd::mosaic(~ Sex + Treatment + Improved,
data = Arthritis)
Identifying Improved as the response:
vcd::mosaic(Improved ~ Sex + Treatment,
data = Arthritis)
Matching the doubledecker plots:
vcd::mosaic(
Improved ~ Treatment + Sex,
data = Arthritis,
split_vertical = c(TRUE, TRUE, FALSE))
vcd::mosaic(
Improved ~ Sex + Treatment,
data = Arthritis,
split_vertical = c(TRUE, TRUE, FALSE))
Some variants using ggmosaic:
ggplot(mutate(Arthritis, Sex = fct_rev(Sex))) +
geom_mosaic(
aes(x = product(Treatment,
Sex))) +
coord_flip() +
labs(x = "", y = "")
ggplot(mutate(Arthritis,
Sex = fct_rev(Sex),
Improved = fct_rev(Improved))) +
geom_mosaic(aes(x = product(Improved,
Treatment,
Sex))) +
coord_flip()
A mosaic plot for all bivariate marginals:
pairs(xtabs(~ Sex + Treatment + Improved, data = Arthritis))
Spinograms and CD Plots
Spinograms and CD plots show the conditional
distribution of a categorical variable given the value of a numeric
variable.
A spinogram for Improved against Age:
ArthT <- filter(Arthritis,
Treatment == "Treated") |>
mutate(Improved = fct_rev(Improved))
arthT_gp <-
grid::gpar(fill = rev(arth_gp$fill))
vcd::spine(Improved ~ Age,
data = ArthT,
gp = arthT_gp,
breaks = 5)
An analogous plot created with ggmosaic by binning the
Age variable:
Arth <-
mutate(Arthritis,
AgeBin = cut(Arthritis$Age,
seq(20, by = 10,
len = 7)),
Improved = fct_rev(Improved))
filter(Arth, Treatment == "Treated") |>
count(Improved, AgeBin) |>
ggplot() +
geom_mosaic(aes(weight = n,
x = product(AgeBin),
fill = Improved)) +
scale_fill_brewer(palette = "Blues",
direction = -1,
guide = guide_legend(reverse = TRUE)) +
theme_minimal() +
theme(axis.title = element_blank())
A facet grid can be used to create spinograms for each of the
Sex/Treatment combinations:
ggplot(count(Arth, Improved, Sex, Treatment, AgeBin)) +
geom_mosaic(aes(weight = n,
x = product(AgeBin),
fill = Improved)) +
scale_fill_brewer(palette = "Blues",
direction = -1,
guide = guide_legend(reverse = TRUE)) +
theme_minimal() +
facet_grid(Treatment ~ Sex) +
theme(axis.title = element_blank()) +
theme(axis.text.x = element_text(angle = 35,
hjust = 1),
axis.text.y = element_blank())
A spinogram
in the media (NYT, August 2021):
Some plots in a Twitter
thread :
CD plots estimate the conditional density of the x
variable given the levels of y, weighted by the marginal
proportions of y and use these to estimate cumulative
probabilities.
The slice at a particular x level visualizes the
conditional distribution of y given x at that
level.
geom_density with position = stack is
one way to create a CD plot.
The cd_plot function from the vcd
package produces a CD plot using grid graphics.
The cdplot function from the base
graphics package provides the same plots using base
graphics.
CD plots for the Treated group:
filter(Arthritis, Treatment == "Treated") |>
ggplot(aes(x = Age, fill = Improved)) +
geom_density(position = "fill", bw = 5) +
scale_fill_brewer(palette = "Blues") +
facet_wrap(~ Sex, ncol = 1) +
thm
CD plots for all combinations end up with one group of size one and
one of size zero, which produces a non-useful plot for one
combination:
count(Arthritis, Treatment, Sex, Improved) |>
complete(Treatment, Sex, Improved,
fill = list(n = 0)) |>
filter(n < 2)
## # A tibble: 2 × 4
## Treatment Sex Improved n
## <fct> <fct> <ord> <int>
## 1 Placebo Male Some 0
## 2 Placebo Male Marked 1
ggplot(Arthritis,
aes(x = Age, fill = Improved)) +
geom_density(position = "fill", bw = 5) +
scale_fill_brewer(palette = "Blues") +
facet_grid(Treatment ~ Sex) +
thm
## Warning: Groups with fewer than two data points have been dropped.
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_density()`).
Uncertainty Representation
Categorical data are often analyzed by fitting models representing
conditional independence structures.
For the Arthritis data, observed counts and expected
counts under an independence model assuming Treatment and
Improved are independent can be visualized as mosaic
plots:
## there are easier ways do do this ...
v <- count(Arthritis, Treatment, Improved)
pT <- group_by(v, Treatment) |>
summarize(n = sum(n)) |>
mutate(pT = n / sum(n)) |>
select(-n)
pI <- group_by(v, Improved) |>
summarize(n = sum(n)) |>
mutate(pI = n / sum(n)) |>
select(-n)
v <- left_join(v, pT, "Treatment") |>
left_join(pI, "Improved") |>
mutate(p = pT * pI,
Treatment = fct_rev(Treatment))
po <- ggplot(v) +
geom_mosaic(aes(weight = n, x = product(Improved, Treatment),
fill = Improved)) +
scale_fill_brewer(palette = "Blues") +
guides(fill = "none") +
labs(title = "Observed Proportions") +
thm +
coord_flip() +
theme(axis.text.y = element_text(angle = 90, hjust = 0))
pe <- ggplot(v) +
geom_mosaic(aes(weight = p, x = product(Improved, Treatment),
fill = Improved)) +
scale_fill_brewer(palette = "Blues") +
guides(fill = "none") +
labs(title = "Expected Proportions") +
thm +
coord_flip() +
theme(axis.text.y = element_text(angle = 90, hjust = 0))
po + pe
A plot for assessing the fit of the residuals between the observed
and expected data under a model assuming independence of
Treatment and Improved produces:
vcd::mosaic(~ Treatment + Improved,
data = Arthritis,
gp = vcd::shading_max)
Another visualization of the residuals is the association
plot produced by assoc:
vcd::assoc(~ Treatment + Improved,
data = Arthritis,
gp = vcd::shading_max)
Some Other Visualizations
Tree Maps
Tree maps show hierarchically structured (or tree-tructured)
data.
Each branch is represented by a rectangle.
Leaf node tiles have areas proportional to the value of a
variable.
Tiles are often colored to reflect the value of another
variable.
The package treemapify provides a
ggplot-based implementation.
The data set G20 includes some variables on the G-20
member countries:
library(treemapify)
select(G20, region,
country, gdp_mil_usd, hdi) |>
knitr::kable(format = "html") |>
kableExtra::kable_styling(
full_width = FALSE)
region
country
gdp_mil_usd
hdi
Africa
South Africa
384315
0.629
North America
United States
15684750
0.937
North America
Canada
1819081
0.911
North America
Mexico
1177116
0.775
South America
Brazil
2395968
0.730
South America
Argentina
474954
0.811
Asia
China
8227037
0.699
Asia
Japan
5963969
0.912
Asia
South Korea
1155872
0.909
Asia
India
1824832
0.554
Asia
Indonesia
878198
0.629
Eurasia
Russia
2021960
0.788
Eurasia
Turkey
794468
0.722
Europe
European Union
16414483
0.876
Europe
Germany
3400579
0.920
Europe
France
2608699
0.893
Europe
United Kingdom
2440505
0.875
Europe
Italy
2014079
0.881
Middle East
Saudi Arabia
727307
0.782
Oceania
Australia
1541797
0.938
A simple tree with only one level, the individual countries:
A corresponding tree map based on gdp_mil_usd:
ggplot(G20, aes(area = gdp_mil_usd)) +
geom_treemap() +
geom_treemap_text(aes(label = country),
color = "white")
A tree grouping by region:
A corresponding tree map:
ggplot(G20, aes(area = gdp_mil_usd,
subgroup = region)) +
geom_treemap() +
geom_treemap_text(aes(label = country),
color = "white") +
geom_treemap_subgroup_border(
color = "red") +
geom_treemap_subgroup_text(color = "red")
A tree map showing GDP values for the G-20 members, grouped by
region, with fill mapped to the country’s Human Development Index:
ggplot(G20, aes(area = gdp_mil_usd,
fill = hdi,
subgroup = region)) +
geom_treemap() +
geom_treemap_text(aes(label = country),
color = "white") +
geom_treemap_subgroup_border() +
geom_treemap_subgroup_text(
color = "lightgrey")
A treemap representing the distribution of eye color within hair
color:
group_by(agg, Eye, Hair) |>
summarize(n = sum(n)) |>
ungroup() |>
ggplot(aes(area = n,
subgroup = Hair)) +
geom_treemap(aes(fill = Eye),
color = "white") +
geom_treemap_subgroup_text() +
geom_treemap_subgroup_border(
color = "black", size = 6) +
geom_treemap_text(aes(label = Eye),
color = "grey90") +
scale_fill_manual(values = ecols) +
guides(fill = "none")
A treemap representing proportions for Improved within
Treatment within Sex for the Arthritis
data:
count(Arth, Treatment, Improved, Sex) |>
ggplot(aes(area = n,
subgroup = Sex, fill = Improved,
subgroup2 = Treatment)) +
geom_treemap() +
geom_treemap_subgroup_text() +
scale_fill_brewer(palette = "Blues",
direction = -1) +
geom_treemap_subgroup_border() +
geom_treemap_subgroup2_text(place = "top",
size = 20)
Alluvial plots
These are also known as
parallel sets , or
Sankey diagrams .
They can be viewed as a parallel coordinates plot for categorical
data.
Several implementations are available, including:
geom_parallel_sets from
ggforce;
geom_sankey from ggsankey ;
geom_alluvium from ggalluvial.
Hair/Eye color using the ggforce package:
pal <- RColorBrewer::brewer.pal(3, "Set1")
HDF <- mutate(HairEyeColorDF,
Sex = fct_rev(Sex))
library(ggforce)
sHDF <- gather_set_data(HDF, 3 : 1)
sHDF <- mutate(sHDF, x = fct_inorder(as.factor(x))) #**** simplify this?
ggplot(sHDF, aes(x, id = id,
split = y,
value = Freq)) +
geom_parallel_sets(aes(fill = Sex),
alpha = 0.5,
axis.width = 0.1) +
geom_parallel_sets_axes(
axis.width = 0.1) +
geom_parallel_sets_labels(
colour = 'white') +
scale_fill_manual(
values = c(Male = pal[2],
Female = pal[1])) +
theme_void() + guides(fill = "none")
Arthritis data with ggforce:
sArth <- mutate(Arth,
Improved = factor(Improved,
ordered = FALSE)) |>
count(Improved, Treatment, Sex) |>
gather_set_data(3 : 1)
sArth <- mutate(sArth,
x = fct_inorder(factor(x)),
Sex = fct_rev(Sex))
ggplot(sArth, aes(x,
id = id,
split = y,
value = n)) +
geom_parallel_sets(aes(fill = Sex),
alpha = 0.5,
axis.width = 0.1) +
geom_parallel_sets_axes(axis.width = 0.1) +
geom_parallel_sets_labels(
colour = 'white') +
scale_fill_manual(
values = c(Male = pal[2],
Female = pal[1])) +
theme_void() + guides(fill = "none")
Stream Graphs
Stream
graphs are a generalization of stacked bar charts plotted against a
numeric variable.
In some cases the origins of the bars are shifted to improve some
aspect of the overall visualization.
An early example is the Baby Name Voyager .
(A more recent variant is also available .)
A NY
Times visualization of movie box office results is another example.
(Blog
post with a static version ).
Some R implementations on GitHub:
A stream graph for movie genres (these are not mutually
exclusive):
## install with: remotes::install_github("hrbrmstr/streamgraph")
library(streamgraph)
library(tidyverse)
genres <- c("Action", "Animation", "Comedy",
"Drama", "Documentary", "Romance")
mymovies <- select(ggplot2movies::movies,
year, one_of(genres))
mymovies_long <- pivot_longer(
mymovies, -year,
names_to = "genre",
values_to = "value")
movie_counts <- count(mymovies_long,
year, genre)
streamgraph(movie_counts, "genre", "n", "year")
Interactive Tutorial
An interactive learnr
tutorial for these notes is available .
You can run the tutorial with
STAT4580::runTutorial("proportions")
You can install the current version of the STAT4580
package with
remotes::install_gitlab("luke-tierney/STAT4580")
You may need to install the remotes package from CRAN
first.
Exercises
Figure A shows a bar char of the flights leaving NYC airports in
2013 for each day of the week. Figure B shows the market share of five
major internet browsers in 2015.
For which of these bar charts would it be better to reorder the
categories so the bars are ordered from largest to smallest?
Yes for Figure A. No for Figure B.
No for Figure A. Yes for Figure B.
Yes for both.
No for both.
Consider the stacked bar chart p1 and the spine plot
p2 for the hair and eye color data produced by the
following code:
library(dplyr)
library(ggplot2)
library(ggmosaic)
ecols <- c(Brown = "brown4", Blue = "blue2",
Hazel = "darkgoldenrod3", Green = "green4")
HairEyeColorDF <- as.data.frame(HairEyeColor)
p0 <- ggplot(HairEyeColorDF) +
scale_fill_manual(values = ecols) +
theme_minimal()
p1 <- p0 + geom_col(aes(x = Hair, y = Freq / sum(Freq), fill = Eye))
p2 <- p0 + geom_mosaic(aes(x = product(Hair), fill = Eye, weight = Freq))
Use the two plots to answer: Which hair color has the highest
proportion of individuals with green eyes?
Black
Brown
Red
Blond
Which plot makes it easiest to answer this question?
Use the plots of the previous question to answer: The proportion
of individuals with red hair is closest to:
5%
8%
12%
20%
Which plot makes it easiest to answer this question?
LS0tCnRpdGxlOiAiVmlzdWFsaXppbmcgUHJvcG9ydGlvbnMiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCjxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0ic3RhdDQ1ODAuY3NzIiB0eXBlPSJ0ZXh0L2NzcyIgLz4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4gLnJlbWFyay1jb2RlIHsgZm9udC1zaXplOiA4NSU7IH0gPC9zdHlsZT4KPCEtLSB0aXRsZSBiYXNlZCBvbiBXaWxrZSdzIGNoYXB0ZXIgLS0+CgpgYGB7ciBzZXR1cCwgaW5jbHVkZSA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CnNvdXJjZShoZXJlOjpoZXJlKCJzZXR1cC5SIikpCmtuaXRyOjpvcHRzX2NodW5rJHNldChjb2xsYXBzZSA9IFRSVUUsIG1lc3NhZ2UgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgIGZpZy5oZWlnaHQgPSA1LCBmaWcud2lkdGggPSA2LCBmaWcuYWxpZ24gPSAiY2VudGVyIikKCnNldC5zZWVkKDEyMzQ1KQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkobGF0dGljZSkKbGlicmFyeShncmlkRXh0cmEpCmxpYnJhcnkocGF0Y2h3b3JrKQpzb3VyY2UoaGVyZTo6aGVyZSgiZGF0YXNldHMuUiIpKQpgYGAKCgojIyBDYXRlZ29yaWNhbCBEYXRhCgpDYXRlZ29yaWNhbCBkYXRhIGNhbiBiZQoKKiBub21pbmFsLCBxdWFsaXRhdGl2ZQoKKiBvcmRpbmFsCgpGb3IgdmlzdWFsaXphdGlvbiwgdGhlIG1haW4gZGlmZmVyZW5jZSBpcyB0aGF0IG9yZGluYWwgZGF0YSBzdWdnZXN0cyBhCnBhcnRpY3VsYXIgZGlzcGxheSBvcmRlci4KClB1cmVseSBjYXRlZ29yaWNhbCBkYXRhIGNhbiBjb21lIGluIGEgcmFuZ2Ugb2YgZm9ybWF0cy4KClRoZSBtb3N0IGNvbW1vbiBhcmUKCiogcmF3IGRhdGE6IGluZGl2aWR1YWwgb2JzZXJ2YXRpb25zOwoKKiBhZ2dyZWdhdGVkIGRhdGE6IGNvdW50cyBmb3IgZWFjaCB1bmlxdWUgY29tYmluYXRpb24gb2YgbGV2ZWxzOwoKKiBjcm9zcy10YWJ1bGF0ZWQgZGF0YS4KCgojIyMgUmF3IERhdGEKCmBgYHtyLCBlY2hvID0gRkFMU0V9CmFoIDwtIGFzLmRhdGEuZnJhbWUoSGFpckV5ZUNvbG9yKQpyYXcgPC0gYWhbcmVwKHNlcV9sZW4obnJvdyhhaCkpLCB0aW1lcyA9IGFoJEZyZXEpLCBdWy00XQpyYXcgPC0gcmF3W3NhbXBsZShzZXFfbGVuKG5yb3cocmF3KSkpLCBdCnJvdy5uYW1lcyhyYXcpIDwtIE5VTEwKYGBgCgpSYXcgZGF0YSBmb3IgYSBzdXJ2ZXkgb2YgaW5kaXZpZHVhbHMgdGhhdCByZWNvcmRzIGhhaXIgY29sb3IsIGV5ZQpjb2xvciwgYW5kIGdlbmRlciBvZiBgciBucm93KHJhdylgIGluZGl2aWR1YWxzIG1pZ2h0IGxvb2sgbGlrZSB0aGlzOgoKYGBge3J9CmhlYWQocmF3KQpgYGAKCgojIyMgQWdncmVnYXRlZCBEYXRhCgpPbmUgd2F5IHRvIGFnZ3JlZ2F0ZSByYXcgY2F0ZWdvcmljYWwgZGF0YSBpcyB0byB1c2UgYGNvdW50YCBmcm9tIGBkcGx5cmA6CgpgYGB7cn0KbGlicmFyeShkcGx5cikKYWdnIDwtIGNvdW50KHJhdywgSGFpciwgRXllLCBTZXgpIHw+CiAgICBhc190aWJibGUoKQphZ2cKYGBgCgo8IS0tClRoZSBgY291bnRfYCBmdW5jdGlvbiBmcm9tIGBkcGx5cmAgYWxsb3dzIHRoZSB2YXJpYWJsZXMgdG8gdXNlIHRvIGJlCnJlYWQgZnJvbSB0aGUgZGF0YToKCmBgYHtyLCBldmFsID0gRkFMU0V9CmFnZyA8LSBjb3VudF8ocmF3LCBuYW1lcyhyYXcpKQpoZWFkKGFnZykKYGBgCgpBcHBhcmVudGx5IHRoZSAibW9kZXJuIiB3YXkgdG8gZG8gdGhpcyBpcwoKYGBge3J9CmNvdW50KHJhdywgISEhIHN5bXMobmFtZXMocmF3KSkpCmBgYAotLT4KCgojIyMgQ3Jvc3MtVGFidWxhdGVkIERhdGEKCkNyb3NzLXRhYnVsYXRlZCBkYXRhIGNhbiBiZSBwcm9kdWNlZCBmcm9tIGFnZ3JlZ2F0ZSBkYXRhIHVzaW5nIGB4dGFic2A6CgpgYGB7cn0KeHRhYnMobiB+IEhhaXIgKyBFeWUgKyBTZXgsIGRhdGEgPSBhZ2cpCmBgYAoKQ3Jvc3MtdGFidWxhdGVkIGRhdGEgY2FuIGJlIHByb2R1Y2VkIGZyb20gcmF3IGRhdGEgdXNpbmcgYHRhYmxlYDoKCmBgYHtyfQp4dGIgPC0gdGFibGUocmF3KQp4dGIKYGBgCgpCb3RoIHJhdyBhbmQgYWdncmVnYXRlIGRhdGEgaW4gdGhpcyBleGFtcGxlIGFyZSBpbiBfdGlkeV8gZm9ybTsgdGhlCmNyb3NzLXRhYnVsYXRlZCBkYXRhIGlzIG5vdC4KCkNyb3NzLXRhYnVsYXRlZCBkYXRhIG9uICRwJCB2YXJpYWJsZXMgaXMgYXJyYW5nZWQgaW4gYSAkcCQtd2F5IGFycmF5LgoKVGhlIGNyb3NzLXRhYnVsYXRlZCBkYXRhIGNhbiBiZSBjb252ZXJ0ZWQgdG8gdGhlIHRpZHkgYWdncmVnYXRlIGZvcm0KdXNpbmcgYGFzLmRhdGEuZnJhbWVgOgoKYGBge3J9CmNsYXNzKHh0YikKaGVhZChhcy5kYXRhLmZyYW1lKHh0YikpCmBgYAoKVGhlIHZhcmlhYmxlIGB4dGJgIGNvcnJlc3BvbmRzIHRvIHRoZSBkYXRhIHNldCBgSGFpckV5ZUNvbG9yYCBpbiB0aGUKYGRhdGFzZXRzYCBwYWNrYWdlLAoKCiMjIyBXb3JraW5nIFdpdGggQ2F0ZWdvcmljYWwgVmFyaWFibGVzCgpDYXRlZ29yaWNhbCB2YXJpYWJsZXMgYXJlIHVzdWFsbHkgcmVwcmVzZW50ZWQgYXM6CgoqIGNoYXJhY3RlciB2ZWN0b3JzCgoqIGZhY3RvcnMuCgpTb21lIGFkdmFudGFnZXMgb2YgZmFjdG9yczoKCiogbW9yZSBjb250cm9sIG92ZXIgb3JkZXJpbmcgb2YgbGV2ZWxzCgoqIGxldmVscyBhcmUgcHJlc2VydmVkIHdoZW4gZm9ybWluZyBzdWJzZXRzCgoqIGxldmVscyBjYW4gcmVmbGVjdCBwb3NzaWJsZSB2YWx1ZXMgbm90IHByZXNlbnQgaW4gdGhlIGRhdGEKCk1vc3QgcGxvdHRpbmcgYW5kIG1vZGVsaW5nIGZ1bmN0aW9ucyB3aWxsIGNvbnZlcnQgY2hhcmFjdGVyIHZlY3RvcnMgdG8KZmFjdG9ycyB3aXRoIGxldmVscyBvcmRlcmVkIGFscGhhYmV0aWNhbGx5LgoKU29tZSBzdGFuZGFyZCBSIGZ1bmN0aW9ucyBmb3Igd29ya2luZyB3aXRoIGZhY3RvcnMgaW5jbHVkZQoKKiBgZmFjdG9yYCBjcmVhdGVzIGEgZmFjdG9yIGZyb20gYW5vdGhlciB0eXBlIG9mIHZhcmlhYmxlCiogYGxldmVsc2AgcmV0dXJucyB0aGUgbGV2ZWxzIG9mIGEgZmFjdG9yCiogYHJlb3JkZXJgIGNoYW5nZXMgbGV2ZWwgb3JkZXIgdG8gbWF0Y2ggYW5vdGhlciB2YXJpYWJsZQoqIGByZWxldmVsYCBtb3ZlcyBhIHBhcnRpY3VsYXIgbGV2ZWwgdG8gdGhlIGZpcnN0IHBvc2l0aW9uIGFzIGEgYmFzZSBsaW5lCiogYGRyb3BsZXZlbHNgIHJlbW92ZXMgbGV2ZWxzIG5vdCBpbiB0aGUgdmFyaWFibGUuCgpUaGUgYHRpZHl2ZXJzZWAgcGFja2FnZSBgZm9yY2F0c2AgYWRkcyBzb21lIG1vcmUgdG9vbHMsIGluY2x1ZGluZwoKKiBgZmN0X2lub3JkZXJgIGNyZWF0ZXMgYSBmYWN0b3Igd2l0aCBsZXZlbHMgb3JkZXJlZCBieSBmaXJzdCBhcHBlYXJhbmNlCiogYGZjdF9pbmZyZXFgIG9yZGVycyBsZXZlbHMgYnkgZGVjcmVhc2luZyBmcmVxdWVuY3kKKiBgZmN0X3JldmAgcmV2ZXJzZXMgdGhlIGxldmVscwoqIGBmY3RfcmVjb2RlYCBjaGFuZ2VzIGZhY3RvciBsZXZlbHMKKiBgZmN0X3JlbGV2ZWxgIG1vdmVzIG9uZSBvciBtb3JlIGxldmVscwoqIGBmY3RfY2AgbWVyZ2VzIHR3byBvciBtb3JlIGZhY3RvcnMKKiBgZmN0X2NvbGxhcHNlYCBtZXJnZSBzb21lIGZhY3RvciBsZXZlbHMKCgojIyBCYXIgQ2hhcnRzIEZvciBGcmVxdWVuY2llcwoKCiMjIyBCYXNpY3MKCkEgYmFyIGNoYXJ0IGlzIG9mdGVuIHVzZWQgdG8gc2hvdyB0aGUgZnJlcXVlbmNpZXMgb2YgYSBjYXRlZ29yaWNhbAp2YXJpYWJsZS4KCkJ5IGRlZmF1bHQsIGBnZW9tX2JhcmAgdXNlcyBgc3RhdCA9ICJjb3VudCJgIGFuZCBtYXBzIGl0cyByZXN1bHQgdG8KdGhlIGB5YCBhZXN0aGV0aWMuCgpUaGlzIGlzIHN1aXRhYmxlIGZvciByYXcgZGF0YToKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KdGhtIDwtIHRoZW1lX21pbmltYWwoKSArCiAgICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNikpCmdncGxvdChyYXcpICsKICAgIGdlb21fYmFyKGFlcyh4ID0gSGFpciksCiAgICAgICAgICAgICBmaWxsID0gImRlZXBza3libHVlMyIpICsKICAgIHRobQpgYGAKCkZvciBhIG5vbWluYWwgdmFyaWFibGUgaXQgaXMgb2Z0ZW4gYmV0dGVyIHRvIG9yZGVyIHRoZSBiYXJzIGJ5CmRlY3JlYXNpbmcgZnJlcXVlbmN5OgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpsaWJyYXJ5KGZvcmNhdHMpCmdncGxvdChtdXRhdGUocmF3LAogICAgICAgICAgICAgIEhhaXIgPSBmY3RfaW5mcmVxKEhhaXIpKSkgKwogICAgZ2VvbV9iYXIoYWVzKHggPSBIYWlyKSwKICAgICAgICAgICAgIGZpbGwgPSAiZGVlcHNreWJsdWUzIikgKwogICAgdGhtCmBgYAoKSWYgdGhlIGRhdGEgaGF2ZSBhbHJlYWR5IGJlZW4gYWdncmVnYXRlZCwgdGhlbiB5b3UgbmVlZCB0byBlaXRoZXIKCiogc3BlY2lmeSBgc3RhdCA9ICJpZGVudGl0eSJgIGFzIHdlbGwgYXMgdGhlIHZhcmlhYmxlIGNvbnRhaW5pbmcgdGhlCiAgY291bnRzIGFzIHRoZSBgeWAgYWVzdGhldGljLCBvcgoKKiB1c2UgYGdlb21fY29sYDoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZ2dwbG90KGFnZykgKwogICAgZ2VvbV9jb2woYWVzKHggPSBIYWlyLAogICAgICAgICAgICAgICAgIHkgPSBuKSwKICAgICAgICAgICAgIGZpbGwgPSAiZGVlcHNreWJsdWUzIikgKwogICAgdGhtCmBgYAoKRm9yIGFnZ3JlZ2F0ZWQgZGF0YSwgcmVvcmRlcmluZyBjYW4gYmUgYmFzZWQgb24gdGhlIGNvbXB1dGVkIGNvdW50cwp1c2luZwoKYGBge3J9CmFnZ19vcmQgPC0KICAgIG11dGF0ZShhZ2csCiAgICAgICAgICAgSGFpciA9IHJlb3JkZXIoSGFpciwgLW4sIHN1bSkpCmBgYAoKKiBgLW5gIGlzIHVzZWQgdG8gb3JkZXIgbGFyZ2VzdCB0byBzbWFsbGVzdDsKCiogdGhlIGRlZmF1bHQgc3VtbWFyeSB1c2VkIGJ5IGByZW9yZGVyYCBpcyBgbWVhbmA7IGBzdW1gIGlzIGJldHRlciBoZXJlLgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpnZ3Bsb3QoYWdnX29yZCkgKwogICAgZ2VvbV9jb2woYWVzKHggPSBIYWlyLAogICAgICAgICAgICAgICAgIHkgPSBuKSwKICAgICAgICAgICAgIGZpbGwgPSAiZGVlcHNreWJsdWUzIikgKwogICAgdGhtCmBgYAoKCiMjIyBBZGRpbmcgYSBHcm91cGluZyBWYXJpYWJsZQoKTWFwcGluZyB0aGUgYEV5ZWAgdmFyaWFibGUgdG8gYGZpbGxgIGluIGBnZ3Bsb3RgIHByb2R1Y2VzIGEgX3N0YWNrZWQKYmFyIGNoYXJ0Xy4KCkFuIGFsdGVybmF0aXZlLCBzcGVjaWZpZWQgd2l0aCBgcG9zaXRpb24gPSAiZG9kZ2UiYCwgaXMgYSBfc2lkZSBieQpzaWRlXyBiYXIgY2hhcnQsIG9yIGEgX2NsdXN0ZXJlZF8gYmFyIGNoYXJ0LgoKRm9yIHRoZSBzaWRlIGJ5IHNpZGUgY2hhcnQgaW4gcGFydGljdWxhciBpdCBtYXkgYmUgdXNlZnVsIHRvIGFsc28KcmVvcmRlciB0aGUgYEV5ZWAgY29sb3IgbGV2ZWxzLgoKYGBge3IsIGZpZy53aWR0aCA9IDgsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQplY29scyA8LSBjKEJyb3duID0gImJyb3duNCIsCiAgICAgICAgICAgQmx1ZSA9ICJibHVlMiIsCiAgICAgICAgICAgSGF6ZWwgPSAiZGFya2dvbGRlbnJvZDMiLAogICAgICAgICAgIEdyZWVuID0gImdyZWVuNCIpCmFnZ19vcmQgPC0KICAgIG11dGF0ZShhZ2csCiAgICAgICAgICAgSGFpciA9IHJlb3JkZXIoSGFpciwgLW4sIHN1bSksCiAgICAgICAgICAgRXllID0gcmVvcmRlcihFeWUsIC1uLCBzdW0pKQpwMSA8LSBnZ3Bsb3QoYWdnX29yZCkgKwogICAgZ2VvbV9jb2woYWVzKHggPSBIYWlyLAogICAgICAgICAgICAgICAgIHkgPSBuLAogICAgICAgICAgICAgICAgIGZpbGwgPSBFeWUpKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBlY29scykgKwogICAgdGhtCnAyIDwtIGdncGxvdChhZ2dfb3JkKSArCiAgICBnZW9tX2NvbChhZXMoeCA9IEhhaXIsCiAgICAgICAgICAgICAgICAgeSA9IG4sCiAgICAgICAgICAgICAgICAgZmlsbCA9IEV5ZSksCiAgICAgICAgICAgICBwb3NpdGlvbiA9ICJkb2RnZSIpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGVjb2xzKSArCiAgICB0aG0KKHAxICsgZ3VpZGVzKGZpbGwgPSAibm9uZSIpKSB8IHAyCmBgYAoKRmFjZXRpbmcgY2FuIGJlIHVzZWQgdG8gYnJpbmcgaW4gYWRkaXRpb25hbCB2YXJpYWJsZXM6CgpgYGB7ciwgZmlnLndpZHRoID0gOCwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CnAxICsgZmFjZXRfd3JhcCh+IFNleCkKYGBgCgpUaGUgY291bnRzIHNob3duIGhlcmUgbWF5IG5vdCBiZSB0aGUgbW9zdCByZWxldmFudCBmZWF0dXJlcyBmb3IKdW5kZXJzdGFuZGluZyB0aGUgam9pbnQgZGlzdHJpYnV0aW9ucyBvZiB0aGVzZSB2YXJpYWJsZXMuCgoKIyMgUGllIENoYXJ0cyBhbmQgRG91Z2hudXQgQ2hhcnRzCgpfUGllIGNoYXJ0c18gZ28gYnkgbWFueSBkaWZmZXJlbnQgbmFtZXMgKGZyb20gYSBbVHdpdHRlcgp0aHJlYWRdKGh0dHBzOi8vdHdpdHRlci5jb20vRWxlcGhhbnRFYXRpbmcvc3RhdHVzLzEzNjEwMzk3NzE0MTQzMTkxMDYpKToKCmBgYHtyLCBlY2hvID0gRkFMU0UsIG91dC53aWR0aCA9ICI4MCUifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhJTUcoInBpZW5hbWVzLmpwZWciKSkKYGBgCgpQaWUgY2hhcnRzIGNhbiBiZSB2aWV3ZWQgYXMgc3RhY2tlZCBiYXIgY2hhcnRzIGluIHBvbGFyIGNvb3JkaW5hdGVzOgoKYGBge3IsIGZpZy53aWR0aCA9IDgsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpoY29scyA8LSBjKEJsYWNrID0gImJsYWNrIiwgQnJvd24gPSAiYnJvd240IiwKICAgICAgICAgICBSZWQgPSAiYnJvd24xIiwgQmxvbmQgPSAibGlnaHRnb2xkZW5yb2QxIikKcDEgPC0gZ2dwbG90KGFnZ19vcmQpICsKICAgIGdlb21fY29sKGFlcyh4ID0gMSwgeSA9IG4sIGZpbGwgPSBIYWlyKSwgcG9zaXRpb24gPSAiZmlsbCIpICsKICAgIGNvb3JkX3BvbGFyKHRoZXRhID0gInkiKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBoY29scykgKwogICAgdGhtCnAyIDwtIGdncGxvdChhZ2dfb3JkKSArCiAgICBnZW9tX2NvbChhZXMoeCA9IEhhaXIsIHkgPSBuLCBmaWxsID0gSGFpcikpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGhjb2xzKSArCiAgICB0aG0KKHAxICsgZ3VpZGVzKGZpbGwgPSAibm9uZSIpKSB8IHAyCmBgYAoKVGhlIGF4ZXMgYW5kIGdyaWQgbGluZXMgYXJlIG5vdCBoZWxwZnVsIGZvciB0aGUgcGllIGNoYXJ0IGFuZCBjYW4gYmUKcmVtb3ZlZCB3aXRoIHNvbWUgX3RoZW1lXyBzZXR0aW5ncy4KClVzaW5nIGZhY2V0aW5nIHdlIGNhbiBhbHNvIHNlcGFyYXRlbHkgc2hvdyB0aGUgZGlzdHJpYnV0aW9ucyBmb3IgbWVuCmFuZCB3b21lbjoKCmBgYHtyLCBmaWcud2lkdGggPSA4LCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KcGllX3RobSA8LSB0aG0gKwogICAgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSkKCnAzIDwtIHAxICsgZmFjZXRfd3JhcCh+IFNleCkgKyBwaWVfdGhtCnAzCmBgYAoKX0RvdWdobnV0IGNoYXJ0c18gYXJlIGEgdmFyaWFudCB0aGF0IGhhcyByZWNlbnRseSBiZWNvbWUgcG9wdWxhciBpbgp0aGUgbWVkaWE6CgpgYGB7ciwgZmlnLndpZHRoID0gOCwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CnA0IDwtIHAzICsgeGxpbSgwLCAxLjUpCnA0CmBgYAoKVGhlIGNlbnRlciBpcyBvZnRlbiB1c2VkIGZvciBhbm5vdGF0aW9uOgoKYGBge3IsIGZpZy53aWR0aCA9IDgsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpwNCArIGdlb21fdGV4dChhZXMoeCA9IDAsIHkgPSAwLCBsYWJlbCA9IFNleCksIHNpemUgPSA1KSArCiAgICB0aGVtZShzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgc3RyaXAudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgpBbiBhbHRlcm5hdGl2ZSB0byB0aGUgcG9sYXIgY29vcmRpbmF0ZXMgYXBwcm9hY2ggdXNlcwpgZ2VvbV9hcmNfYmFyYCBhbmQgYHN0YXRfcGllYCBmcm9tIHBhY2thZ2UgYGdnZm9yY2VgOgoKYGBge3IsIGZpZy53aWR0aCA9IDgsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQojfCB3YXJuaW5nOiBmYWxzZQpsaWJyYXJ5KGdnZm9yY2UpCmFycmFuZ2UoYWdnX29yZCwgZGVzYyhIYWlyKSkgfD4KICAgIGdncGxvdChhZXMoeDAgPSAwLCB5MCA9IDAsIHIwID0gMCwgciA9IDEsIGFtb3VudCA9IG4sIGZpbGwgPSBIYWlyKSkgKwogICAgZ2VvbV9hcmNfYmFyKHN0YXQgPSAicGllIiwgY29sb3IgPSBOQSkgKwogICAgY29vcmRfZml4ZWQoKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBoY29scykgKwogICAgcGllX3RobSArCiAgICBmYWNldF93cmFwKH4gU2V4KQpgYGAKCkZvciBkb3VnaG51dCBjaGFydHM6CgpgYGB7ciwgZmlnLndpZHRoID0gOCwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmFycmFuZ2UoYWdnX29yZCwgZGVzYyhIYWlyKSkgfD4KICAgIGdncGxvdChhZXMoeDAgPSAwLCB5MCA9IDAsIHIwID0gMC40LCByID0gMSwgYW1vdW50ID0gbiwgZmlsbCA9IEhhaXIpKSArCiAgICBnZW9tX2FyY19iYXIoc3RhdCA9ICJwaWUiLCBjb2xvciA9IE5BKSArCiAgICBnZW9tX3RleHQoYWVzKHggPSAwLCB5ID0gMCwgbGFiZWwgPSBTZXgpLCBzaXplID0gNSkgKwogICAgY29vcmRfZml4ZWQoKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBoY29scykgKwogICAgcGllX3RobSArCiAgICB0aGVtZShzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgc3RyaXAudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgZmFjZXRfd3JhcCh+IFNleCkKYGBgCgoKIyMgU29tZSBOb3RlcwoKUGllIGNoYXJ0cyBhcmUgZWZmZWN0aXZlIGZvciBqdWRnaW5nIHBhcnQvd2hvbGUgcmVsYXRpb25zaGlwcy4KClBpZSBjaGFydHMgY2FuIGJlIGVmZmVjdGl2ZSBmb3IgY29tcGFyaW5nIHByb3BvcnRpb25zIHRvCgoqIG9uZSBoYWxmCiogb25lIHF1YXJ0ZXIKClBpZSBjaGFydHMgYXJlIG5vdCB2ZXJ5IGVmZmVjdGl2ZSBmb3IgY29tcGFyaW5nIHByb3BvcnRpb25zIHRvIGVhY2ggb3RoZXIuCgozRCBwaWUgY2hhcnRzIGFyZSBwb3B1bGFyIGFuZCBhIHZlcnkgYmFkIGlkZWEuIEFuIGV4YW1wbGUKKFtGaWcuIDYuNjFdKGh0dHBzOi8vd3d3LmRyb3Bib3guY29tL3MvdGxlaHppM2tiNmlrYnN6LzYuNjEuM0RJbGx1c3RyYXRpb24ucG5nP2RsPTApKQpmcm9tIEFuZHkgS2lyaydzIGJvb2sgKDIwMTYpLApbX0RhdGEgVmlzdWFsaXphdGlvbjogQSBIYW5kYm9vayBmb3IgRGF0YSBEcml2ZW4gRGVzaWduX10oaHR0cDovL2Jvb2sudmlzdWFsaXNpbmdkYXRhLmNvbS9ob21lKToKCmBgYHtyLCBlY2hvID0gRkFMU0UsIG91dC53aWR0aCA9ICI1MCUifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhJTUcoImJhZHBpZS5wbmciKSkKYGBgCgpQaWUgY2hhcnRzIGFyZSB3aWRlbHkgdXNlZCBmb3IgcG9saXRpY2FsIGRhdGEuCgoqIFdpdGggdGhlIHJpZ2h0IG9yZGVyaW5nLCBwaWUgY2hhcnRzIGFyZSB2ZXJ5IGdvb2QgYXQgc2hvd2luZwogIHdoaWNoIGNvYWxpdGlvbnMgb2YgcGFydGllcyBjYW4gZm9ybSBhIG1ham9yaXR5LgoKKiBXaGVuIG5vIG9uZSBjYW5kaWRhdGUgZWFybnMgYSBtYWpvcml0eSBvZiB0aGUgdm90ZXMsIHBpZSBjaGFydHMKICBkbyBub3Qgc2hvdyB3aGljaCBjYW5kaWRhdGUgaGFzIGVhcm5lZCBhIHBsdXJhbGl0eSB2ZXJ5IHdlbGwuCgoqIEdvb2Qgb3JpZW50YXRpb24gYW5kIGZhY3RvciBvcmRlcmluZyBjYW4gaGVscC4KCmBgYHtyLCBmaWcud2lkdGggPSA4LCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZWxlY3QgPC0gZ2VvZmFjZXQ6OmVsZWN0aW9uIHw+CiAgICBncm91cF9ieShjYW5kaWRhdGUpIHw+CiAgICBzdW1tYXJpemUodm90ZXMgPSBzdW0odm90ZXMpKQoKcDEgPC0gZ2dwbG90KGVsZWN0KSArCiAgICBnZW9tX2NvbChhZXMoeCA9IDEsIHkgPSB2b3RlcywgZmlsbCA9IGNhbmRpZGF0ZSksIHBvc2l0aW9uID0gImZpbGwiKSArCiAgICBjb29yZF9wb2xhcih0aGV0YSA9ICJ5Iiwgc3RhcnQgPSAtMSkgKwogICAgeGxpbShjKC0wLjUsIDEuNSkpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoVHJ1bXAgPSBzY2FsZXM6Om11dGVkKCJyZWQiLCA1MCwgODApLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDbGludG9uID0gc2NhbGVzOjptdXRlZCgiYmx1ZSIsIDUwLCA3MCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE90aGVyID0gImdyZXkiKSkgKwogICAgcGllX3RobQoKcDIgPC0gbXV0YXRlKGVsZWN0LAogICAgICAgICAgICAgY2FuZGlkYXRlID0gZmFjdG9yKGNhbmRpZGF0ZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjKCJDbGludG9uIiwgIk90aGVyIiwgIlRydW1wIikpKSB8PgogICAgZ2dwbG90KCkgKwogICAgZ2VvbV9jb2woYWVzKHggPSAxLCB5ID0gdm90ZXMsIGZpbGwgPSBjYW5kaWRhdGUpLCBwb3NpdGlvbiA9ICJmaWxsIikgKwogICAgY29vcmRfcG9sYXIodGhldGEgPSAieSIpICsKICAgIHhsaW0oYygtMC41LCAxLjUpKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKFRydW1wID0gc2NhbGVzOjptdXRlZCgicmVkIiwgNTAsIDgwKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2xpbnRvbiA9IHNjYWxlczo6bXV0ZWQoImJsdWUiLCA1MCwgNzApLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPdGhlciA9ICJncmV5IikpICsKICAgIHBpZV90aG0KCnAzIDwtIGdncGxvdChlbGVjdCkgKwogICAgZ2VvbV9jb2woYWVzKHggPSBjYW5kaWRhdGUsCiAgICAgICAgICAgICAgICAgeSA9IDEwMCAqICh2b3RlcyAvIHN1bSh2b3RlcykpLAogICAgICAgICAgICAgICAgIGZpbGwgPSBjYW5kaWRhdGUpKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKFRydW1wID0gc2NhbGVzOjptdXRlZCgicmVkIiwgNTAsIDgwKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2xpbnRvbiA9IHNjYWxlczo6bXV0ZWQoImJsdWUiLCA1MCwgNzApLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPdGhlciA9ICJncmV5IikpICsKICAgIGxhYnMoeSA9ICJwZXJjZW50IikgKwogICAgdGhtICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKQoKKHAxICsgZ3VpZGVzKGZpbGwgPSAibm9uZSIpKSArIChwMiArIGd1aWRlcyhmaWxsID0gIm5vbmUiKSkgKyBwMwpgYGAKCgojIyBTb21lIEFsdGVybmF0aXZlcwoKCiMjIyBTdGFja2VkIEJhciBDaGFydHMKClN0YWNrZWQgYmFyIGNoYXJ0cyB3aXRoIGVxdWFsIGhlaWdodHMsIG9yIGZpbGxlZCBiYXIgY2hhcnRzLCBhcmUgYW4KYWx0ZXJuYXRpdmUgZm9yIHJlcHJlc2VudGluZyBwYXJ0LXdob2xlIHJlbGF0aW9uc2hpcHMuCgoqIFRvcCBhbmQgYm90dG9tIHByb3BvcnRpb25zIGFyZSBlYXN5IHRvIGNvbXBhcmUuCgoqIENvbXBhcmluZyBwcm9wb3J0aW9ucyB0byBvbmUgaGFsZiBhbmQgb25lIHF1YXJ0ZXIgaXMgaGFyZGVyLgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpnZ3Bsb3QoYWdnKSArCiAgICBnZW9tX2NvbChhZXMoeCA9IFNleCwgeSA9IG4sIGZpbGwgPSBIYWlyKSwgcG9zaXRpb24gPSAiZmlsbCIpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGhjb2xzKSArCiAgICB0aG0KYGBgCgoKIyMjIFdhZmZsZSBDaGFydHMKCkFub3RoZXIgYWx0ZXJuYXRpdmUgaXMgYSBfd2FmZmxlIGNoYXJ0Xywgc29tZXRpbWVzIGFsc28gY2FsbGVkIGEKX3NxdWFyZSBwaWUgY2hhcnRfLgoKYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoID0gIjUwJSJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKElNRygid2FmZmxlLnBuZyIpKQpgYGAKClRoZSBbYHdhZmZsZWBdKGh0dHBzOi8vZ2l0aHViLmNvbS9ocmJybXN0ci93YWZmbGUpIHBhY2thZ2UgaXMgb25lIFIKaW1wbGVtZW50YXRpb24gb2YgdGhpcyBpZGVhLgoKQ3VycmVudGx5IHRoZSBkZXZlbG9wbWVudCB2ZXJzaW9uIG9uIEdpdEh1YiBpcyBuZWVkZWQgZm9yIHRoZQpmb2xsb3dpbmcgZXhhbXBsZXMuCgpTaG93aW5nIHRoZSBjb3VudHM6CgpgYGB7ciwgZmlnLndpZHRoID0gNywgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmxpYnJhcnkod2FmZmxlKQpzdG9waWZub3QocGFja2FnZVZlcnNpb24oIndhZmZsZSIpID49ICIxLjAuMSIpCmdncGxvdChhcnJhbmdlKGFnZywgSGFpciksIGFlcyh2YWx1ZXMgPSBuLCBmaWxsID0gSGFpcikpICsKICAgIGdlb21fd2FmZmxlKG5fcm93cyA9IDE4LCBmbGlwID0gVFJVRSwgY29sb3IgPSAid2hpdGUiLCBzaXplID0gMC4zMywKICAgICAgICAgICAgICAgIG5hLnJtID0gRkFMU0UpICsKICAgIGNvb3JkX2VxdWFsKCkgKwogICAgZmFjZXRfd3JhcCh+IFNleCkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gaGNvbHMpICsKICAgIHRoZW1lX21pbmltYWwoKSArCiAgICB0aGVtZV9lbmhhbmNlX3dhZmZsZSgpCmBgYAoKYGBge3IsIGV2YWwgPSBUUlVFLCBlY2hvID0gRkFMU0V9CmdncGxvdChhcnJhbmdlKGFnZywgSGFpciksIGFlcyh2YWx1ZXMgPSBuLCBmaWxsID0gSGFpcikpICsKICAgIGdlb21fd2FmZmxlKG5fcm93cyA9IDEwLCBmbGlwID0gVFJVRSwgY29sb3IgPSAid2hpdGUiLCBzaXplID0gMC4zMywKICAgICAgICAgICAgICAgIG5hLnJtID0gRkFMU0UpICsKICAgIGNvb3JkX2VxdWFsKCkgKwogICAgZmFjZXRfZ3JpZChTZXggfiBFeWUpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGhjb2xzKSArCiAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgdGhlbWVfZW5oYW5jZV93YWZmbGUoKQpgYGAKClNob3dpbmcgdGhlIHByb3BvcnRpb25zOgoKYGBge3IsIGZpZy53aWR0aCA9IDcsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpyb3VuZF9wY3QgPC0gZnVuY3Rpb24obikgewogICAgcGN0IDwtIDEwMCAqIChuIC8gc3VtKG4pKQogICAgbm4gPC0gZmxvb3IocGN0KQogICAgaWYgKHN1bShubikgPCAxMDApIHsKICAgICAgICByZW0gPC0gcGN0IC0gbm4KICAgICAgICBpZHggPC0gc29ydChvcmRlcihyZW0pLCBkZWNyZWFzaW5nID0gVFJVRSlbc2VxX2xlbigxMDAgLSBzdW0obm4pKV0KICAgICAgICBubltpZHhdIDwtIG5uW2lkeF0gKyAxCiAgICB9CiAgICBubgp9Cgpncm91cF9ieShhZ2csIFNleCkgfD4KICAgIG11dGF0ZShwY3QgPSByb3VuZF9wY3QobikpIHw+CiAgICB1bmdyb3VwKCkgfD4KICAgIGdncGxvdChhZXModmFsdWVzID0gcGN0LCBmaWxsID0gSGFpcikpICsKICAgIGdlb21fd2FmZmxlKG5fcm93cyA9IDEwLCBmbGlwID0gVFJVRSwgY29sb3IgPSAid2hpdGUiLCBzaXplID0gMC4zMywKICAgICAgICAgICAgICAgIG5hLnJtID0gRkFMU0UpICsKICAgIGNvb3JkX2VxdWFsKCkgKwogICAgZmFjZXRfd3JhcCh+IFNleCkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gaGNvbHMpICsKICAgIHRoZW1lX21pbmltYWwoKSArCiAgICB0aGVtZV9lbmhhbmNlX3dhZmZsZSgpCmBgYAoKYGBge3IsIGV2YWwgPSBGQUxTRSwgZWNobyA9IEZBTFNFfQpncm91cF9ieShhZ2csIFNleCwgRXllKSB8PgogICAgbXV0YXRlKHBjdCA9IHJvdW5kX3BjdChuKSkgfD4KICAgIHVuZ3JvdXAoKSB8PgogICAgYXJyYW5nZShwY3QsIEhhaXIpIHw+CiAgICBnZ3Bsb3QoYWVzKHZhbHVlcyA9IHBjdCwgZmlsbCA9IEhhaXIpKSArCiAgICBnZW9tX3dhZmZsZShuX3Jvd3MgPSAxMCwgZmxpcCA9IFRSVUUsIGNvbG9yID0gIndoaXRlIiwgc2l6ZSA9IDAuMzMsCiAgICAgICAgICAgICAgICBuYS5ybSA9IEZBTFNFKSArCiAgICBjb29yZF9lcXVhbCgpICsKICAgIGZhY2V0X2dyaWQoU2V4IH4gRXllKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBoY29scykgKwogICAgdGhlbWVfbWluaW1hbCgpICsKICAgIHRoZW1lX2VuaGFuY2Vfd2FmZmxlKCkKYGBgCgoKIyMgUG9wdWxhdGlvbiBQeXJhbWlkcwoKQmFyIGNoYXJ0cyBmb3IgdHdvIGdyb3VwcyBjYW4gYmUgc2hvd24gYmFjayB0byBiYWNrLgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQptdXRhdGUoYWdnLCBIYWlyID0gcmVvcmRlcihIYWlyLCBuLCBzdW0pKSB8PgogICAgZ2dwbG90KGFlcyh4ID0gaWZlbHNlKFNleCA9PSAiTWFsZSIsIG4sIC1uKSwKICAgICAgICAgICAgICAgeSA9IEhhaXIsCiAgICAgICAgICAgICAgIGZpbGwgPSBTZXgpKSArCiAgICBnZW9tX2NvbCgpICsKICAgIHhsYWIoIkNvdW50IikgKwogICAgdGhtCmBgYAoKVGhpcyBpcyBvZnRlbiB1c2VkIGZvciBzaG93aW5nIGFnZSBkaXN0cmlidXRpb25zIGJ5IHNleCBmb3IKcG9wdWxhdGlvbnM7IHRoZSByZXN1bHQgaXMgY2FsbGVkIGEgW19wb3B1bGF0aW9uCnB5cmFtaWRfXShodHRwczovL3d3dy52aXN1YWxjYXBpdGFsaXN0LmNvbS91cy1wb3B1bGF0aW9uLXB5cmFtaWQtMTk4MC0yMDUwLykuCgpBZ2UgZGlzdHJpYnV0aW9uIGRhdGEgZm9yIG1hbnkgY291bnRyaWVzIGFuZCB5ZWFycyBpcyBhdmFpbGFibGUgZnJvbSBhCltDZW5zdXMgQnVyZWF1CndlYnNpdGVdKGh0dHBzOi8vd3d3LmNlbnN1cy5nb3YvZGF0YS10b29scy9kZW1vL2lkYi8pLgoKRGF0YSBmaWxlcyBmb3IgMjAyMCBmb3IKW0dlcm1hbnldKGh0dHBzOi8vc3RhdC51aW93YS5lZHUvfmx1a2UvZGF0YS9nZXJtYW55LTIwMjAuY3N2KSBhbmQKW05pZ2VyaWFdKGh0dHBzOi8vc3RhdC51aW93YS5lZHUvfmx1a2UvZGF0YS9uaWdlcmlhLTIwMjAuY3N2KSBhcmUKYXZhaWxhYmxlIGxvY2FsbHkuCgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmlmICghIGZpbGUuZXhpc3RzKCJnZXJtYW55LTIwMjAuY3N2IikpCiAgICBkb3dubG9hZC5maWxlKCJodHRwczovL3N0YXQudWlvd2EuZWR1L35sdWtlL2RhdGEvZ2VybWFueS0yMDIwLmNzdiIsCiAgICAgICAgICAgICAgICAgICJnZXJtYW55LTIwMjAuY3N2IikKaWYgKCEgZmlsZS5leGlzdHMoIm5pZ2VyaWEtMjAyMC5jc3YiKSkKICAgIGRvd25sb2FkLmZpbGUoImh0dHBzOi8vc3RhdC51aW93YS5lZHUvfmx1a2UvZGF0YS9uaWdlcmlhLTIwMjAuY3N2IiwKICAgICAgICAgICAgICAgICAgIm5pZ2VyaWEtMjAyMC5jc3YiKQoKZ21fcG9wIDwtIHJlYWQuY3N2KCJnZXJtYW55LTIwMjAuY3N2Iiwgc2tpcCA9IDEpIHw+CiAgICBmaWx0ZXIoQWdlICE9ICJUb3RhbCIpIHw+CiAgICBtdXRhdGUoQWdlID0gZmN0X2lub3JkZXIoQWdlKSkKCm5pX3BvcCA8LSByZWFkLmNzdigibmlnZXJpYS0yMDIwLmNzdiIsIHNraXAgPSAxKSB8PgogICAgZmlsdGVyKEFnZSAhPSAiVG90YWwiKSB8PgogICAgbXV0YXRlKEFnZSA9IGZjdF9pbm9yZGVyKEFnZSkpCmBgYAoKQ29tYmluaW5nIHRoZSBkYXRhIHNldHMgYWxsb3dzIGEgc2lkZSBieSBzaWRlIGNvbXBhcmlzb24gb2YgdGhlIGNvdW50czoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KbGlicmFyeSh0aWR5cikKcG9wMiA8LQogICAgYmluZF9yb3dzKG11dGF0ZShnbV9wb3AsIENvdW50cnkgPSAiR2VybWFueSIpLAogICAgICAgICAgICAgIG11dGF0ZShuaV9wb3AsIENvdW50cnkgPSAiTmlnZXJpYSIpKSB8PgogICAgc2VsZWN0KEFnZSwKICAgICAgICAgICBNYWxlID0gTWFsZS5Qb3B1bGF0aW9uLAogICAgICAgICAgIEZlbWFsZSA9IEZlbWFsZS5Qb3B1bGF0aW9uLCBDb3VudHJ5KSB8PgogICAgcGl2b3RfbG9uZ2VyKE1hbGUgOiBGZW1hbGUsCiAgICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAiU2V4IiwKICAgICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAibiIpCgpnZ3Bsb3QocG9wMikgKwogICAgZ2VvbV9jb2woYWVzKHggPSBpZmVsc2UoU2V4ID09ICJNYWxlIiwgbiwgLW4pLAogICAgICAgICAgICAgICAgIHkgPSBBZ2UsCiAgICAgICAgICAgICAgICAgZmlsbCA9IFNleCkpICsKICAgIGZhY2V0X3dyYXAofiBDb3VudHJ5KSArCiAgICBzY2FsZV94X2NvbnRpbnVvdXMoCiAgICAgICAgbGFiZWxzID0gZnVuY3Rpb24obikgc2NhbGVzOjpjb21tYShhYnMobikpKSArCiAgICB4bGFiKCJDb3VudCIpICsKICAgIHRobSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikKYGBgCgpUaGUgZGlmZmVyZW50IHNoYXBlcyBhcmUgZXZpZGVudCwgYnV0IGFyZSBoYXJkZXIgdG8gc2VlIHRoYW4KdGhleSBjb3VsZCBiZSBiZWNhdXNlIG9mIHRoZSBkaWZmZXJlbmNlIGluIHRvdGFsIHBvcHVsYXRpb246CgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9Cmdyb3VwX2J5KHBvcDIsIENvdW50cnkpIHw+CiAgICBzdW1tYXJpemUoUG9wdWxhdGlvbiA9IHN1bShuKSkgfD4KICAgIHVuZ3JvdXAoKSB8PgogICAgbXV0YXRlKFBvcHVsYXRpb24gPSBzY2FsZXM6OmNvbW1hKFBvcHVsYXRpb24pKSB8PgogICAga25pdHI6OmthYmxlKGZvcm1hdCA9ICJodG1sIiwgYWxpZ24gPSAibHIiKSB8PgogICAga2FibGVFeHRyYTo6a2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UpCmBgYAoKVXNpbmcgYSBncm91cCBtdXRhdGUgd2UgY2FuIGNvbXB1dGUgc2V4L2FnZSBncm91cCBwZXJjZW50YWdlcyB3aXRoaW4KZWFjaCBjb3VudHJ5OgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpncm91cF9ieShwb3AyLCBDb3VudHJ5KSB8PgogICAgbXV0YXRlKHBjdCA9IDEwMCAqIG4gLyBzdW0obikpIHw+CiAgICB1bmdyb3VwKCkgfD4KICAgIGdncGxvdCgpICsKICAgIGdlb21fY29sKGFlcyh4ID0gaWZlbHNlKFNleCA9PSAiTWFsZSIsIHBjdCwgLXBjdCksCiAgICAgICAgICAgICAgICAgeSA9IEFnZSwKICAgICAgICAgICAgICAgICBmaWxsID0gU2V4KSkgKwogICAgZmFjZXRfd3JhcCh+IENvdW50cnkpICsKICAgIHNjYWxlX3hfY29udGludW91cygKICAgICAgICBsYWJlbHMgPSBmdW5jdGlvbih4KSBzY2FsZXM6OnBlcmNlbnQoYWJzKHggLyAxMDApKSkgKwogICAgeGxhYigiUGVyY2VudCIpICsKICAgIHRobSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikKYGBgCgpgYGB7ciwgZXZhbCA9IEZBTFNFLCBlY2hvID0gRkFMU0V9CnAgPC0gZ2dwbG90KGdtX3BvcCkgKwogICAgZ2VvbV9jb2woYWVzKHggPSBNYWxlLlBvcHVsYXRpb24sIHkgPSBBZ2UsIGZpbGwgPSAiTWFsZSIpKSArCiAgICBnZW9tX2NvbChhZXMoeCA9IC1GZW1hbGUuUG9wdWxhdGlvbiwgeSA9IEFnZSwgZmlsbCA9ICJGZW1hbGUiKSkgKwogICAgdGhtCgpwIHwgKHAgKyBuaV9wb3ApCmBgYAoKCiMjIE11bHRpcGxlIENhdGVnb3JpY2FsIFZhcmlhYmxlcwoKVmlzdWFsaXppbmcgdGhlIGRpc3RyaWJ1dGlvbiBvZiBtdWx0aXBsZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMKaW52b2x2ZXMgdmlzdWFsaXppbmcgY291bnRzIGFuZCBwcm9wb3J0aW9ucy4KCkRpc3RyaWJ1dGlvbnMgY2FuIGJlIHZpZXdlZCBhcwoKKiBqb2ludCBkaXN0cmlidXRpb25zOwoKKiBjb25kaXRpb25hbCBkaXN0cmlidXRpb25zLgoKV2hlbiBvbmUgdmFyaWFibGUgKG9yIHNldmVyYWwpIGNhbiBiZSB2aWV3ZWQgYXMgYSByZXNwb25zZSBhbmQgb3RoZXJzCmFzIHByZWRpY3RvcnMgdGhlbiBpdCBpcyBjb21tb24gdG8gZm9jdXMgb24gdGhlIGNvbmRpdGlvbmFsCmRpc3RyaWJ1dGlvbiBvZiB0aGUgcmVzcG9uc2UgZ2l2ZW4gdGhlIHByZWRpY3RvcnMuCgpUaGUgbW9zdCBjb21tb24gYXBwcm9hY2hlcyB1c2UgdmFyaWFudHMgb2YgYmFyIGFuZCBhcmVhIGNoYXJ0cy4KClRoZSByZXN1bHRpbmcgcGxvdHMgYXJlIG9mdGVuIGNhbGxlZCBbX21vc2FpYwpwbG90c19dKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL01vc2FpY19wbG90KS4KCgojIyBUd28gRGF0YSBTZXRzCgoKIyMjIEhhaXIgYW5kIEV5ZSBDb2xvcgoKYGBge3J9CkhhaXJFeWVDb2xvckRGIDwtCiAgICBhcy5kYXRhLmZyYW1lKEhhaXJFeWVDb2xvcikKaGVhZChIYWlyRXllQ29sb3JERikKYGBgCgpNYXJnaW5hbCBkaXN0cmlidXRpb25zIG9mIHRoZSB2YXJpYWJsZXM6CgpgYGB7ciwgZmlnLmhlaWdodCA9IDMsIGZpZy53aWR0aCA9IDksIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpwMSA8LSBnZ3Bsb3QoSGFpckV5ZUNvbG9yREYpICsKICAgIGdlb21fY29sKGFlcyhTZXgsIEZyZXEpLCBmaWxsID0gImRlZXBza3libHVlMyIpICsKICAgIHRobQpwMiA8LSBtdXRhdGUoSGFpckV5ZUNvbG9yREYsIEhhaXIgPSByZW9yZGVyKEhhaXIsIC1GcmVxLCBzdW0pKSB8PgogICAgZ2dwbG90KCkgKwogICAgZ2VvbV9jb2woYWVzKEhhaXIsIEZyZXEpLCBmaWxsID0gImRlZXBza3libHVlMyIpICsKICAgIHRobQpwMyA8LSBnZ3Bsb3QoSGFpckV5ZUNvbG9yREYpICsKICAgIGdlb21fY29sKGFlcyhFeWUsIEZyZXEpLCBmaWxsID0gImRlZXBza3libHVlMyIpICsKICAgIHRobQpwMSB8IHAyIHwgcDMKYGBgCgoKIyMjIEFydGhyaXRpcyBEYXRhCgpUaGUgYHZjZGAgcGFja2FnZSBpbmNsdWRlcyB0aGUgZGF0YSBmcmFtZSBgQXJ0aHJpdGlzYCB3aXRoIHNldmVyYWwKdmFyaWFibGVzIGZvciA4NCBwYXRpZW50cyBpbiBhIGNsaW5pY2FsIHRyaWFsIGZvciBhIHRyZWF0bWVudCBmb3IKcmhldW1hdG9pZCBhcnRocml0aXMuCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQpkYXRhKEFydGhyaXRpcywgcGFja2FnZSA9ICJ2Y2QiKQpoZWFkKEFydGhyaXRpcykKYGBgCgoqIFRoZSBgSW1wcm92ZWRgIHZhcmlhYmxlIGlzIHRoZSByZXNwb25zZS4KCiogVGhlIHByZWRpY3RvcnMgYXJlIGBUcmVhdG1lbnRgLCBgU2V4YCwgYW5kIGBBZ2VgLgoKQ291bnRzIGZvciB0aGUgY2F0ZWdvcmljYWwgcHJlZGljdG9yczoKYGBge3J9Cnh0YWJzKH4gU2V4LCBBcnRocml0aXMpCmBgYAoKYGBge3J9Cnh0YWJzKH4gVHJlYXRtZW50LCBBcnRocml0aXMpCmBgYAoKYGBge3J9Cnh0YWJzKH4gVHJlYXRtZW50ICsgU2V4LCBkYXRhID0gQXJ0aHJpdGlzKQpgYGAKCkpvaW50IGRpc3RyaWJ1dGlvbiBvZiB0aGUgcHJlZGljdG9yczoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZ2dwbG90KEFydGhyaXRpcykgKwogICAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBBZ2UpLAogICAgICAgICAgICAgICAgICAgYmlud2lkdGggPSAxMCwKICAgICAgICAgICAgICAgICAgIGZpbGwgPSAiZGVlcHNreWJsdWUzIiwKICAgICAgICAgICAgICAgICAgIGNvbG9yID0gImJsYWNrIikgKwogICAgZmFjZXRfZ3JpZChUcmVhdG1lbnQgfiBTZXgpICsKICAgIHRobQpgYGAKCkNvbmRpdGlvbmFsIGRpc3RyaWJ1aXRvbiBvZiBhZ2UsIGdpdmVuIHNleCBhbmQgdHJlYXRtZW50OgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpnZ3Bsb3QoQXJ0aHJpdGlzKSArCiAgICBnZW9tX2hpc3RvZ3JhbShhZXMoeCA9IEFnZSwKICAgICAgICAgICAgICAgICAgICAgICB5ID0gYWZ0ZXJfc3RhdChkZW5zaXR5KSksCiAgICAgICAgICAgICAgICAgICBiaW53aWR0aCA9IDEwLAogICAgICAgICAgICAgICAgICAgZmlsbCA9ICJkZWVwc2t5Ymx1ZTMiLAogICAgICAgICAgICAgICAgICAgY29sb3IgPSAiYmxhY2siKSArCiAgICBmYWNldF9ncmlkKFRyZWF0bWVudCB+IFNleCkgKwogICAgdGhtCmBgYAoKCiMjIEJhciBDaGFydHMKCgojIyMgSGFpciBhbmQgRXllIENvbG9yCgpEZWZhdWx0IGJhciBjaGFydHMgc2hvdyB0aGUgaW5kaXZpZHVhbCBjb3VudCBvciBqb2ludCBwcm9wb3J0aW9ucy4KCkZvciB0aGUgaGFpci1leWUgY29sb3IgYWdncmVnYXRlZCBkYXRhIGNvdW50czoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZ2dwbG90KEhhaXJFeWVDb2xvckRGKSArCiAgICBnZW9tX2NvbChhZXMoeCA9IEV5ZSwgeSA9IEZyZXEsIGZpbGwgPSBTZXgpKSArCiAgICBmYWNldF93cmFwKH4gSGFpcikgKwogICAgdGhtCmBgYAoKSm9pbnQgcHJvcG9ydGlvbnM6CgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmdncGxvdChtdXRhdGUoSGFpckV5ZUNvbG9yREYsIFByb3AgPSBGcmVxIC8gc3VtKEZyZXEpKSkgKwogICAgZ2VvbV9jb2woYWVzKHggPSBFeWUsIHkgPSBQcm9wLCBmaWxsID0gU2V4KSkgKwogICAgZmFjZXRfd3JhcCh+IEhhaXIpICsKICAgIHRobQpgYGAKCiogRGlmZmVyaW5nIGZyZXF1ZW5jaWVzIG9mIHRoZSBoYWlyIGNvbG9ycyBhcmUgdmlzaWJsZS4KCiogQ29uZGl0aW9uYWwgZGlzdHJpYnV0aW9ucyBvZiBleWUgY29sb3Igd2l0aGluIGhhaXIgY29sb3IgYXJlCiAgaGFyZGVyIHRvIGNvbXBhcmUuCgpTaG93aW5nIGNvbmRpdGlvbmFsIGRpc3RyaWJ1dGlvbnMgcmVxdWlyZXMgY29tcHV0aW5nIHByb3BvcnRpb25zCndpdGhpbiBncm91cHMuCgpGb3IgdGhlIGpvaW50IGNvbmRpdGlvbmFsIGRpc3RyaWJ1dGlvbiBvZiBzZXggYW5kIGV5ZSBjb2xvciBnaXZlbiBoYWlyIGNvbG9yOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpncm91cF9ieShIYWlyRXllQ29sb3JERiwgSGFpcikgfD4KICAgIG11dGF0ZShQcm9wID0gRnJlcSAvIHN1bShGcmVxKSkgfD4KICAgIHVuZ3JvdXAoKSB8PgogICAgZ2dwbG90KCkgKwogICAgZ2VvbV9jb2woYWVzKHggPSBFeWUsIHkgPSBQcm9wLCBmaWxsID0gU2V4KSkgKwogICAgZmFjZXRfd3JhcCh+IEhhaXIpICsKICAgIHRobQpgYGAKCiogSXQgaXMgZWFzaWVyIHRvIGNvbXBhcmUgdGhlIHNrZXduZXNzIG9mIHRoZSBleWUgY29sb3IKICBkaXN0cmlidXRpb25zIGZvciBibGFjaywgYnJvd24sIGFuZCByZWQgaGFpci4KCiogQXNzZXNzaW5nIHRoZSBwcm9wb3J0aW9uIG9mIGZlbWFsZXMgb3IgbWFsZXMgd2l0aGluZyB0aGUgZGlmZmVyZW50CiAgZ3JvdXBzIGlzIHBvc3NpYmxlIGJ1dCBjaGFsbGVuZ2luZyBzaW5jZSBpdCByZXF1aXJlcyByZWxhdGl2ZQogIGxlbmd0aCBjb21wYXJpc29ucy4KClRvIG1vcmUgY2xlYXJseSBzZWUgdGhlIHRoYXQgdGhlIHByb3BvcnRpb24gb2YgZmVtYWxlcyBhbW9uZyBzdWJqZWN0cwp3aXRoIGJsb25kIGhhaXIgYW5kIGJsdWUgZXllcyBpcyBoaWdoZXIgdGhhbiBmb3Igb3RoZXIgaGFpci9leWUgY29sb3IKY29tYmluYXRpb25zIHdlIGNhbiBsb29rIGF0IHRoZSBjb25kaXRpb25hbCBkaXN0cmlidXRpb24gb2Ygc2V4IGdpdmVuCmhhaXIgYW5kIGV5ZSBjb2xvci4KCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZ3JvdXBfYnkoSGFpckV5ZUNvbG9yREYsIEhhaXIsIEV5ZSkgfD4KICAgIG11dGF0ZShQcm9wID0gRnJlcSAvIHN1bShGcmVxKSkgfD4KICAgIHVuZ3JvdXAoKSB8PgogICAgZ2dwbG90KCkgKwogICAgZ2VvbV9jb2woYWVzKHggPSBFeWUsCiAgICAgICAgICAgICAgICAgeSA9IFByb3AsCiAgICAgICAgICAgICAgICAgZmlsbCA9IFNleCkpICsKICAgIGZhY2V0X3dyYXAofiBIYWlyLCBucm93ID0gMSkgKwogICAgdGhtICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0KICAgICAgICAgICAgICBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgaGp1c3QgPSAxKSkKYGBgCgpUaGlzIHBsb3QgY2FuIGFsc28gYmUgb2J0YWluZWQgdXNpbmcgYHBvc2l0aW9uID0gImZpbGwiYC4KCmBgYHtyLCBldmFsID0gRkFMU0UsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpnZ3Bsb3QoSGFpckV5ZUNvbG9yREYpICsKICAgIGdlb21fY29sKGFlcyh4ID0gRXllLAogICAgICAgICAgICAgICAgIHkgPSBGcmVxLAogICAgICAgICAgICAgICAgIGZpbGwgPSBTZXgpLAogICAgICAgICAgICAgcG9zaXRpb24gPSAiZmlsbCIpICsKICAgIGZhY2V0X3dyYXAofiBIYWlyLCBucm93ID0gMSkgKwogICAgdGhtICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0KICAgICAgICAgICAgICBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgaGp1c3QgPSAxKSkKYGBgCgpPbmUgZHJhd2JhY2s6IFRoaXMgdmlzdWFsaXphdGlvbiBubyBsb25nZXIgc2hvd3MgdGhhdCBzb21lIG9mIHRoZQpoYWlyL2V5ZSBjb2xvciBjb21iaW5hdGlvbnMgYXJlIG1vcmUgY29tbW9uIHRoYW4gb3RoZXJzLgoKCiMjIyBBcnRocml0aXMgRGF0YQoKRm9yIHRoZSByYXcgYXJ0aHJpdGlzIGRhdGEsIGBnZW9tX2JhcmAgY29tcHV0ZXMgdGhlIGFnZ3JlZ2F0ZSBjb3VudHMKYW5kIHByb2R1Y2VzIGEgc3RhY2tlZCBiYXIgY2hhcnQgYnkgZGVmYXVsdDoKCmBgYHtyfQpwIDwtIGdncGxvdChBcnRocml0aXMsIGFlcyh4ID0gU2V4LAogICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gSW1wcm92ZWQpKSArCiAgICBmYWNldF93cmFwKH4gVHJlYXRtZW50KQpwICsgZ2VvbV9iYXIoKSArCiAgICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIkJsdWVzIikgKwogICAgdGhtCgpgYGAKClNwZWNpZnlpbmcgYHBvc2l0aW9uID0gImRvZGdlImAgcHJvZHVjZXMgYSBzaWRlLWJ5LXNpZGUgcGxvdDoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KcCArIGdlb21fYmFyKHBvc2l0aW9uID0gImRvZGdlIikgKwogICAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJCbHVlcyIpICsKICAgIHRobQpgYGAKClRoZXJlIGFyZSBubyBjYXNlcyBvZiBtYWxlIHBhdGllbnRzIG9uIHBsYWNlYm8gcmVwb3J0aW5nIGBTb21lYAppbXByb3ZlbWVudCwgcmVzdWx0aW5nIGluIHdpZGVyIGJhcnMgZm9yIHRoZSBvdGhlciBvcHRpb25zLgoKT25lIHdheSB0byBwcm9kdWNlIGEgemVybyBoZWlnaHQgYmFyOgoKKiBhZ2dyZWdhdGUgd2l0aCBgY291bnRgLCBhbmQKCiogdXNlIGBjb21wbGV0ZWAgZnJvbSBgdGlkeXJgCgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmxpYnJhcnkodGlkeXIpCmNvbXBfY291bnRzIDwtCiAgICBjb3VudChBcnRocml0aXMsCiAgICAgICAgICBUcmVhdG1lbnQsIFNleCwgSW1wcm92ZWQpIHw+CiAgICBjb21wbGV0ZShUcmVhdG1lbnQsIFNleCwgSW1wcm92ZWQsCiAgICAgICAgICAgICBmaWxsID0gbGlzdChuID0gMCkpCmdncGxvdChjb21wX2NvdW50cywKICAgICAgIGFlcyh4ID0gU2V4LCB5ID0gbiwgZmlsbCA9IEltcHJvdmVkKSkgKwogICAgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgICBmYWNldF93cmFwKH4gVHJlYXRtZW50KSArCiAgICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIkJsdWVzIikgKwogICAgdGhtCmBgYAoKQW5vdGhlciBvcHRpb24gaXMgdG8gdXNlIHRoZSBgcHJlc2VydmUgPSAic2luZ2xlImAgb3B0aW9uIHdpdGgKYHBvc2l0aW9uX2RvZGdlYC4KCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KcCArIGdlb21fYmFyKHBvc2l0aW9uID0KICAgICAgICAgICAgICAgICBwb3NpdGlvbl9kb2RnZSgKICAgICAgICAgICAgICAgICAgICAgcHJlc2VydmUgPSAic2luZ2xlIikpICsKICAgIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiQmx1ZXMiKSArCiAgICB0aG0KYGBgCgpTaG93aW5nIGNvbmRpdGlvbmFsIGRpc3RyaWJ1dGlvbnMgb2YgYEltcHJvdmVkYCBnaXZlbiBkaWZmZXJlbnQgbGV2ZWxzCm9mIGBUcmVhdG1lbnRgIGFuZCBgU2V4YDoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZ3JvdXBfYnkoY29tcF9jb3VudHMsIFRyZWF0bWVudCwgU2V4KSB8PgogICAgbXV0YXRlKHByb3AgPSBuIC8gc3VtKG4pKSB8PgogICAgdW5ncm91cCgpIHw+CiAgICBnZ3Bsb3QoKSArCiAgICBnZW9tX2NvbChhZXMoeCA9IFNleCwKICAgICAgICAgICAgICAgICB5ID0gcHJvcCwKICAgICAgICAgICAgICAgICBmaWxsID0gSW1wcm92ZWQpLAogICAgICAgICAgICAgcG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgICBmYWNldF93cmFwKH4gVHJlYXRtZW50KSArCiAgICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIkJsdWVzIikgKwogICAgdGhtCmBgYAoKU3RhY2tlZCBiYXIgY2hhcnRzIHdpdGggaGVpZ2h0IG9uZSBhcmUgYW5vdGhlciBvcHRpb24gdG8gbWFrZQp0aGVzZSBjb25kaXRpb25hbCBkaXN0cmlidXRpb25zIGVhc2llciB0byBjb21wYXJlOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpwICsgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpICsKICAgIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiQmx1ZXMiKSArCiAgICB0aG0KYGBgCgpPcmRlcmluZyBvZiB2YXJpYWJsZXMgYWZmZWN0cyB3aGljaCBjb21wYXJpc29ucyBhcmUgZWFzaWVyLgoKKiBBIHJlc2VhcmNoZXIgbWlnaHQgd2FudCB0byBlbXBoYXNpemUgdGhlIGRpZmZlcmVudGlhbCByZXNwb25zZSBhbW9uZwogIG1hbGVzIGFuZCBmZW1hbGVzLgoKKiBBIHBhdGllbnQgbWlnaHQgcHJlZmVyIHRvIGJlIGFibGUgdG8gZm9jdXMgb24gd2hldGhlciB0aGUgdHJlYXRtZW50CiAgaXMgZWZmZWN0aXZlIGZvciB0aGVtOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpnZ3Bsb3QoQXJ0aHJpdGlzLCBhZXMoeCA9IFRyZWF0bWVudCwgZmlsbCA9IEltcHJvdmVkKSkgKwogICAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpICsKICAgIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiQmx1ZXMiKSArCiAgICB0aG0gKwogICAgZmFjZXRfd3JhcCh+IFNleCkKYGBgCgpTb21lIG5vdGVzOgoKKiBUaGUgc3RhY2tlZCBiYXIgY2hhcnQgaXMgZWZmZWN0aXZlIGZvciB0d28gY2F0ZWdvcmllcywgYW5kIGEgZmV3CiAgbW9yZSBpZiB0aGV5IGFyZSBvcmRlcmVkLgoKKiBQcm92aWRpbmcgYSB2aXN1YWwgaW5kaWNhdGlvbiBvZiB1bmNlcnRhaW50eSBpbiB0aGUgZXN0aW1hdGVzIGlzIGEKICBjaGFsbGVuZ2UuIFRoZSBzdGFuZGFyZCBlcnJvcnMgaW4gdGhpcyBjYXNlIGFyZSBhcm91bmQgMC4xLgoKKiBUaGUgcHJvcG9ydGlvbnMgb2YgZWFjaCB0cmVhdG1lbnQgZ3JvdXAgdGhhdCBhcmUgbWFsZSBvciBmZW1hbGUKICBjb3VsZCBiZSBlbmNvZGVkIGluIHRoZSBiYXIgd2lkdGhzLgoKKiBUaGUgcmVzdWx0aW5nIHBsb3QgaXMgY2FsbGVkIGEgX3NwaW5lIHBsb3RfLgoKKiBCYXNpYyBgZ2dwbG90MmAgZG9lcyBub3Qgc2VlbSB0byBtYWtlIHRoaXMgZWFzeS4KCgojIyBTcGluZSBQbG90cwoKX1NwaW5lIHBsb3RzXyBhcmUgYSBzcGVjaWFsIGNhc2Ugb2YgW19tb3NhaWMKcGxvdHNfXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Nb3NhaWNfcGxvdCksIGFuZCBjYW4gYmUgc2VlbiBhcwphIGdlbmVyYWxpemF0aW9uIG9mIHN0YWNrZWQgYmFyIHBsb3RzLgoKRm9yIGEgc3BpbmUgcGxvdCB0aGUgcHJvcG9ydGlvbnMgZm9yIHRoZSBjYXRlZ29yaWVzIG9mIGEgcHJlZGljdG9yCnZhcmlhYmxlIGFyZSBlbmNvZGVkIGluIHRoZSBiYXIgd2lkdGhzLgoKVGhlIGBnZ21vc2FpY2AgcGFja2FnZSBwcm92aWRlcyBzdXBwb3J0IGZvciBtb3NhaWMgcGxvdHMgaW4gdGhlCmBnZ3Bsb3RgIGZyYW1ld29yay4gKEl0IGNhbiBiZSBhIGxpdHRsZSByb3VnaCBhcm91bmQgdGhlIGVkZ2VzLikKClNwaW5lIHBsb3RzIGFyZSBwcm92aWRlZCBieSB0aGUgYmFzZSBncmFwaGljcyBmdW5jdGlvbiBgc3BpbmVwbG90YCBhbmQKdGhlIGB2Y2RgIGZ1bmN0aW9uIGBzcGluZWAuCgpgdmNkYCBwbG90cyBhcmUgYnVpbHQgb24gdGhlIGBncmlkYCBncmFwaGljcyBzeXN0ZW0sIGxpa2UgYGxhdHRpY2VgCmFuZCBgZ2dwbG90MmAgZ3JhcGhpY3MuCgo8IS0tIHNwaW5lIHBsb3QgaW4gdGhlIHdpbGQ6Cmh0dHBzOi8vdC5jby85MXhxa1dYSWZEOwppbiBpbWcvZW5lcmd5LXNwaW5lLnBuZyAtLT4KCkEgc3BpbmUgcGxvdCBmb3IgdGhlIGRpc3RyaWJ1dGlvbiBvZiBgSW1wcm92ZWRgIGdpdmVuIGBTZXhgIGluIHRoZQpgVHJlYXRlZGAgZ3JvdXA6CgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KbGlicmFyeShnZ21vc2FpYykKZmlsdGVyKEFydGhyaXRpcywgVHJlYXRtZW50ID09ICJUcmVhdGVkIikgfD4KICAgIG11dGF0ZShJbXByb3ZlZCA9IGZjdF9yZXYoSW1wcm92ZWQpKSB8PgogICAgZ2dwbG90KCkgKwogICAgZ2VvbV9tb3NhaWMoYWVzKHggPSBwcm9kdWN0KFNleCksCiAgICAgICAgICAgICAgICAgICAgZmlsbCA9IEltcHJvdmVkKSkgKwogICAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJCbHVlcyIsCiAgICAgICAgICAgICAgICAgICAgICBkaXJlY3Rpb24gPSAtMSwKICAgICAgICAgICAgICAgICAgICAgIGd1aWRlID0gZ3VpZGVfbGVnZW5kKHJldmVyc2UgPSBUUlVFKSkgKwogICAgZmFjZXRfd3JhcCh+IFRyZWF0bWVudCkgKwogICAgdGhtICsgbGFicyh4ID0gIiIsIHkgPSAiSW1wcm92ZWQiKQpgYGAKClNwaW5lIHBsb3RzIGZvciBgVHJlYXRtZW50YCBncm91cHMgdXNpbmcgZmFjZXRpbmc6CgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmxpYnJhcnkoZ2dtb3NhaWMpCm11dGF0ZShBcnRocml0aXMsCiAgICAgICBJbXByb3ZlZCA9IGZjdF9yZXYoSW1wcm92ZWQpKSB8PgogICAgZ2dwbG90KCkgKwogICAgZ2VvbV9tb3NhaWMoYWVzKHggPSBwcm9kdWN0KFNleCksCiAgICAgICAgICAgICAgICAgICAgZmlsbCA9IEltcHJvdmVkKSkgKwogICAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJCbHVlcyIsCiAgICAgICAgICAgICAgICAgICAgICBkaXJlY3Rpb24gPSAtMSwKICAgICAgICAgICAgICAgICAgICAgIGd1aWRlID0gZ3VpZGVfbGVnZW5kKHJldmVyc2UgPSBUUlVFKSkgKwogICAgZmFjZXRfd3JhcCh+IFRyZWF0bWVudCkgKwogICAgdGhtICsgbGFicyh4ID0gIiIsIHkgPSAiSW1wcm92ZWQiKQpgYGAKClNwaW5lIHBsb3RzIGZvciB0aGUgYXJ0aHJpdGlzIGRhdGEsIGZhY2V0ZWQgb24gYFNleGA6CgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmxpYnJhcnkoZ2dtb3NhaWMpCm11dGF0ZShBcnRocml0aXMsCiAgICAgICBJbXByb3ZlZCA9IGZjdF9yZXYoSW1wcm92ZWQpKSB8PgogICAgZ2dwbG90KCkgKwogICAgZ2VvbV9tb3NhaWMoYWVzKHggPSBwcm9kdWN0KFRyZWF0bWVudCksCiAgICAgICAgICAgICAgICAgICAgZmlsbCA9IEltcHJvdmVkKSkgKwogICAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJCbHVlcyIsCiAgICAgICAgICAgICAgICAgICAgICBkaXJlY3Rpb24gPSAtMSwKICAgICAgICAgICAgICAgICAgICAgIGd1aWRlID0gZ3VpZGVfbGVnZW5kKHJldmVyc2UgPSBUUlVFKSkgKwogICAgZmFjZXRfd3JhcCh+IFNleCkgKwogICAgdGhtICsgbGFicyh4ID0gIiIsIHkgPSAiSW1wcm92ZWQiKQpgYGAKVGhpcyBubyBsb25nZXIgc2hvd3MgdGhlIEZlbWFsZS9NYWxlIGltYmFsYW5jZS4KCkZvciBhZ2dyZWdhdGUgY291bnRzIHVzZSB0aGUgd2VpZ2h0IGFlc3RoZXRpYzoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KbXV0YXRlKEhhaXJFeWVDb2xvckRGLCBTZXggPSBmY3RfcmV2KFNleCkpIHw+CiAgICBnZ3Bsb3QoKSArCiAgICBnZW9tX21vc2FpYyhhZXMod2VpZ2h0ID0gRnJlcSwKICAgICAgICAgICAgICAgICAgICB4ID0gcHJvZHVjdChIYWlyKSwKICAgICAgICAgICAgICAgICAgICBmaWxsID0gU2V4KSkgKwogICAgdGhtICsgbGFicyh4ID0gIkhhaXIiLCB5ID0gIiIpCmBgYAoKU3BpbmUgcGxvdHMgb2YgYFNleGAgd2l0aGluIGBFeWVgIGNvbG9yLCBmYWNldGVkIG9uIGBIYWlyYCBjb2xvcjoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KbXV0YXRlKEhhaXJFeWVDb2xvckRGLCBTZXggPSBmY3RfcmV2KFNleCkpIHw+CiAgICBnZ3Bsb3QoKSArCiAgICBnZW9tX21vc2FpYyhhZXMod2VpZ2h0ID0gRnJlcSwKICAgICAgICAgICAgICAgICAgICB4ID0gcHJvZHVjdChFeWUpLAogICAgICAgICAgICAgICAgICAgIGZpbGwgPSBTZXgpKSArCiAgICB0aG0gKyBsYWJzKHggPSAiRXllIiwgeSA9ICIiKSArCiAgICBmYWNldF93cmFwKH4gSGFpciwKICAgICAgICAgICAgICAgbnJvdyA9IDEsCiAgICAgICAgICAgICAgIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIiwKICAgICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0LnggPQogICAgICAgICAgICAgIGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LAogICAgICAgICAgICAgICAgICAgICAgICAgICBoanVzdCA9IDEpKSArCiAgICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkKYGBgCgpUaGUgcmVsYXRpdmUgc2l6ZXMgb2YgdGhlIGdyb3VwcyBvbiB0aGUgYHhgIChleWUgY29sb3IpIGF4aXMgYXJlIHNob3duCndpdGhpbiB0aGUgZmFjZXRzLgoKVGhlIHNpemVzIG9mIHRoZSBmYWNldGVkIHZhcmlhYmxlIChoYWlyIGNvbG9yKSBncm91cHMgYXJlIG5vdCByZWZsZWN0ZWQuCgpfRG91YmxlIGRlY2tlciBwbG90c18gdHJ5IHRvIGFkZHJlc3MgdGhpcy4KCgojIyBEb3VibGVkZWNrZXIgUGxvdHMKCl9Eb3VibGVkZWNrZXIgcGxvdHNfIGNhbiBiZSB2aWV3ZWQgYXMgYSBnZW5lcmFsaXphdGlvbiBvZiBzcGluZSBwbG90cwp0byBtdWx0aXBsZSBwcmVkaWN0b3JzLgoKUGFja2FnZSBgdmNkYCBwcm92aWRlcyB0aGUgYGRvdWJsZWRlY2tlcmAgZnVuY3Rpb24uCgpUaGlzIGZ1bmN0aW9uIGNhbiB1c2UgYSBmb3JtdWxhIGludGVyZmFjZS4KCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KYXJ0aF9wYWwgPC0KICAgIFJDb2xvckJyZXdlcjo6YnJld2VyLnBhbCgzLCAiQmx1ZXMiKQphcnRoX2dwIDwtIGdyaWQ6OmdwYXIoZmlsbCA9IGFydGhfcGFsKQp2Y2Q6OmRvdWJsZWRlY2tlcihJbXByb3ZlZCB+IFRyZWF0bWVudCArIFNleCwKICAgICAgICAgICAgICAgICAgZGF0YSA9IEFydGhyaXRpcywKICAgICAgICAgICAgICAgICAgZ3AgPSBhcnRoX2dwLAogICAgICAgICAgICAgICAgICBtYXJnaW5zID0gYygyLCA1LCA0LCAyKSkKYGBgCgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CnZjZDo6ZG91YmxlZGVja2VyKEltcHJvdmVkIH4gU2V4ICsgVHJlYXRtZW50LAogICAgICAgICAgICAgICAgICBkYXRhID0gQXJ0aHJpdGlzLAogICAgICAgICAgICAgICAgICBncCA9IGFydGhfZ3AsCiAgICAgICAgICAgICAgICAgIG1hcmdpbnMgPSBjKDIsIDUsIDQsIDIpKQpgYGAKClVzaW5nIGBnZ21vc2FpY2A6CgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9Cm11dGF0ZShBcnRocml0aXMsCiAgICAgICBJbXByb3ZlZCA9IGZjdF9yZXYoSW1wcm92ZWQpKSB8PgogICAgZ2dwbG90KCkgKwogICAgZ2VvbV9tb3NhaWMoCiAgICAgICAgYWVzKHggPSBwcm9kdWN0KFNleCwgVHJlYXRtZW50KSwKICAgICAgICAgICAgZmlsbCA9IEltcHJvdmVkKSwKICAgICAgICBkaXZpZGVyID0gZGRlY2tlcigpKSArCiAgICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIkJsdWVzIiwKICAgICAgICAgICAgICAgICAgICAgIGRpcmVjdGlvbiA9IC0xLAogICAgICAgICAgICAgICAgICAgICAgZ3VpZGUgPSBndWlkZV9sZWdlbmQocmV2ZXJzZSA9IFRSVUUpKSArCiAgICB0aG0gKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPQogICAgICAgICAgICAgIGVsZW1lbnRfdGV4dChhbmdsZSA9IDE1LAogICAgICAgICAgICAgICAgICAgICAgICAgICBoanVzdCA9IDEpKSArCiAgICBsYWJzKHggPSAiIiwgeSA9ICIiKQpgYGAKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KbXV0YXRlKEFydGhyaXRpcywKICAgICAgIEltcHJvdmVkID0gZmN0X3JldihJbXByb3ZlZCkpIHw+CiAgICBnZ3Bsb3QoKSArCiAgICBnZW9tX21vc2FpYygKICAgICAgICBhZXMoeCA9IHByb2R1Y3QoVHJlYXRtZW50LCBTZXgpLAogICAgICAgICAgICBmaWxsID0gSW1wcm92ZWQpLAogICAgICAgIGRpdmlkZXIgPSBkZGVja2VyKCkpICsKICAgIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiQmx1ZXMiLAogICAgICAgICAgICAgICAgICAgICAgZGlyZWN0aW9uID0gLTEsCiAgICAgICAgICAgICAgICAgICAgICBndWlkZSA9IGd1aWRlX2xlZ2VuZChyZXZlcnNlID0gVFJVRSkpICsKICAgIHRobSArCiAgICB0aGVtZShheGlzLnRleHQueCA9CiAgICAgICAgICAgICAgZWxlbWVudF90ZXh0KGFuZ2xlID0gMTUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGhqdXN0ID0gMSkpICsKICAgIGxhYnMoeCA9ICIiLCB5ID0gIiIpCmBgYAoKQW5vdGhlciBhcHByb2FjaCB1c2luZyB0aGUKW2BnZ2g0eGBdKGh0dHBzOi8vdGV1bmJyYW5kLmdpdGh1Yi5pby9nZ2g0eC8pIHBhY2thZ2U6CgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShnZ2g0eCkKZGF0YShBcnRocml0aXMsIHBhY2thZ2UgPSAidmNkIikKZ2dwbG90KEFydGhyaXRpcywgYWVzKHggPSAwLCBmaWxsID0gSW1wcm92ZWQpKSArCiAgICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIikgKwogICAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJCbHVlcyIpICsKICAgIGZhY2V0X25lc3RlZCh+IFRyZWF0bWVudCArIFNleCkgKwogICAgZm9yY2VfcGFuZWxzaXplcyhjb2xzID0gY291bnQoQXJ0aHJpdGlzLCBUcmVhdG1lbnQsIFNleCkkbikgKwogICAgbGFicyh4ID0gZWxlbWVudF9ibGFuaygpLCB5ID0gZWxlbWVudF9ibGFuaygpKSArCiAgICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgYnJlYWtzID0gTlVMTCwgbGFiZWxzID0gTlVMTCkgKwogICAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IE5VTEwsIGxhYmVscyA9IE5VTEwpCmBgYAoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2doNHgpCmRhdGEoQXJ0aHJpdGlzLCBwYWNrYWdlID0gInZjZCIpCmdncGxvdChBcnRocml0aXMsIGFlcyh4ID0gMCwgZmlsbCA9IEltcHJvdmVkKSkgKwogICAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpICsKICAgIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiQmx1ZXMiKSArCiAgICBmYWNldF9uZXN0ZWQofiBTZXggKyBUcmVhdG1lbnQpICsKICAgIGZvcmNlX3BhbmVsc2l6ZXMoY29scyA9IGNvdW50KEFydGhyaXRpcywgU2V4LCBUcmVhdG1lbnQpJG4pICsKICAgIGxhYnMoeCA9IGVsZW1lbnRfYmxhbmsoKSwgeSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IE5VTEwsIGxhYmVscyA9IE5VTEwpICsKICAgIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBicmVha3MgPSBOVUxMLCBsYWJlbHMgPSBOVUxMKQpgYGAKCiMjIE1vc2FpYyBQbG90cwoKX01vc2FpYyBwbG90c18gcmVjdXJzaXZlbHkgcGFydGl0aW9uIHRoZSBheGVzIHRvIHJlcHJlc2VudCBjb3VudHMgb2YKY2F0ZWdvcmljYWwgdmFyaWFibGVzIGFzIHJlY3RhbmdsZXMuCgoqIEJhc2UgZ3JhcGhpY3MgcHJvdmlkZXMgYG1vc2FpY3Bsb3RgOwoKKiBgdmNkYCBwcm92aWRlcyBgbW9zYWljYC4KCkJvdGggc3VwcG9ydCBhIGZvcm11bGEgaW50ZXJmYWNlLgoKQSBNb3NhaWMgcGxvdCBmb3IgdGhlIHByZWRpY3RvcnMgYFNleGAgYW5kIGBUcmVhdG1lbnRgOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQp2Y2Q6Om1vc2FpYyh+IFNleCArIFRyZWF0bWVudCwKICAgICAgICAgICAgZGF0YSA9IEFydGhyaXRpcykKYGBgCgpBZGRpbmcgYEltcHJvdmVkYCB0byB0aGUgam9pbnQgZGlzdHJpYnV0aW9uOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQp2Y2Q6Om1vc2FpYyh+IFNleCArIFRyZWF0bWVudCArIEltcHJvdmVkLAogICAgICAgICAgICBkYXRhID0gQXJ0aHJpdGlzKQpgYGAKCklkZW50aWZ5aW5nIGBJbXByb3ZlZGAgYXMgdGhlIHJlc3BvbnNlOgoKPCEtLSAjI3ZjZDo6bW9zYWljKEltcHJvdmVkIH4gU2V4ICsgVHJlYXRtZW50LCBkYXRhID0gQXJ0aHJpdGlzLCBncCA9IGFydGhfZ3ApLS0+CgpgYGB7cn0KdmNkOjptb3NhaWMoSW1wcm92ZWQgfiBTZXggKyBUcmVhdG1lbnQsCiAgICAgICAgICAgIGRhdGEgPSBBcnRocml0aXMpCmBgYAoKTWF0Y2hpbmcgdGhlIGRvdWJsZWRlY2tlciBwbG90czoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KdmNkOjptb3NhaWMoCiAgICAgICAgIEltcHJvdmVkIH4gVHJlYXRtZW50ICsgU2V4LAogICAgICAgICBkYXRhID0gQXJ0aHJpdGlzLAogICAgICAgICBzcGxpdF92ZXJ0aWNhbCA9IGMoVFJVRSwgVFJVRSwgRkFMU0UpKQpgYGAKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KdmNkOjptb3NhaWMoCiAgICAgICAgIEltcHJvdmVkIH4gU2V4ICsgVHJlYXRtZW50LAogICAgICAgICBkYXRhID0gQXJ0aHJpdGlzLAogICAgICAgICBzcGxpdF92ZXJ0aWNhbCA9IGMoVFJVRSwgVFJVRSwgRkFMU0UpKQpgYGAKClNvbWUgdmFyaWFudHMgdXNpbmcgYGdnbW9zYWljYDoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZ2dwbG90KG11dGF0ZShBcnRocml0aXMsIFNleCA9IGZjdF9yZXYoU2V4KSkpICsKICAgIGdlb21fbW9zYWljKAogICAgICAgIGFlcyh4ID0gcHJvZHVjdChUcmVhdG1lbnQsCiAgICAgICAgICAgICAgICAgICAgICAgIFNleCkpKSArCiAgICBjb29yZF9mbGlwKCkgKwogICAgbGFicyh4ID0gIiIsIHkgPSAiIikKYGBgCgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmdncGxvdChtdXRhdGUoQXJ0aHJpdGlzLAogICAgICAgICAgICAgIFNleCA9IGZjdF9yZXYoU2V4KSwKICAgICAgICAgICAgICBJbXByb3ZlZCA9IGZjdF9yZXYoSW1wcm92ZWQpKSkgKwogICAgZ2VvbV9tb3NhaWMoYWVzKHggPSBwcm9kdWN0KEltcHJvdmVkLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRyZWF0bWVudCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTZXgpKSkgKwogICAgY29vcmRfZmxpcCgpCmBgYAoKQSBtb3NhaWMgcGxvdCBmb3IgYWxsIGJpdmFyaWF0ZSBtYXJnaW5hbHM6CgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CnBhaXJzKHh0YWJzKH4gU2V4ICsgVHJlYXRtZW50ICsgSW1wcm92ZWQsIGRhdGEgPSBBcnRocml0aXMpKQpgYGAKCgojIyBTcGlub2dyYW1zIGFuZCBDRCBQbG90cwoKPCEtLSBidWlsZGluZyBhIHNwaW5vZ3JhbSBmcm9tIHNjcmF0Y2gsIG1vcmUgb3IgbGVzczoKCmBgYHIKQXJ0aCA8LSBtdXRhdGUoQXJ0aHJpdGlzLAogICAgICAgICAgICAgICBBZ2VCaW4gPSBjdXQoQWdlLCBzZXEoMjAsIGJ5ID0gMTAsIGxlbiA9IDcpKSwKICAgICAgICAgICAgICAgSW1wcm92ZWQgPSBmY3RfcmV2KEltcHJvdmVkKSkKZCA8LSBmaWx0ZXIoQXJ0aCwgVHJlYXRtZW50ID09ICJUcmVhdGVkIikgfD4KICAgIGNvdW50KEFnZUJpbikgfD4KICAgIG11dGF0ZShicmsgPSAoY3Vtc3VtKGxhZyhuLCBkZWZhdWx0ID0gMCkpICsgMC41ICogbikgLyBzdW0obikpCnAgPC0gZ2dwbG90KGQpCgpwICsgZ2VvbV9jb2woYWVzKHggPSBBZ2VCaW4sIHkgPSBuKSkKCnAyIDwtIHAgKwogICAgZ2VvbV9jb2woYWVzKHggPSAwLjUsIHkgPSBuLCBjb2xvciA9IGZjdF9yZXYoQWdlQmluKSksCiAgICAgICAgICAgICBwb3NpdGlvbiA9ICJmaWxsIiwgZmlsbCA9IE5BKSArCiAgICBndWlkZXMoY29sb3IgPSAibm9uZSIpICsKICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBkJGJyaywKICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBkJEFnZUJpbikKcDIKCnAyICsgY29vcmRfZmxpcCgpCmBgYAoKYGBgcgpwcm9wcyA8LSBmaWx0ZXIoQXJ0aCwgVHJlYXRtZW50ID09ICJUcmVhdGVkIikgfD4KICAgIGNvdW50KEFnZUJpbiwgSW1wcm92ZWQpIHw+CiAgICBtdXRhdGUoQWdlQmluID0gZmN0X2Ryb3AoQWdlQmluKSkgfD4KICAgIGNvbXBsZXRlKEFnZUJpbiwgSW1wcm92ZWQsIGZpbGwgPSBsaXN0KG4gPSAwKSkgfD4KICAgIGdyb3VwX2J5KEFnZUJpbikgfD4KICAgIG11dGF0ZShwcm9wID0gbiAvIHN1bShuKSkgfD4KICAgIHVuZ3JvdXAoKSB8PgogICAgc2VsZWN0KC1uKQpwcm9wcwpgYGAKLS0+CgpfU3Bpbm9ncmFtc18gYW5kIF9DRCBwbG90c18gc2hvdyB0aGUgY29uZGl0aW9uYWwgZGlzdHJpYnV0aW9uIG9mIGEKY2F0ZWdvcmljYWwgdmFyaWFibGUgZ2l2ZW4gdGhlIHZhbHVlIG9mIGEgbnVtZXJpYyB2YXJpYWJsZS4KCiogU3Bpbm9ncmFtcyB1c2UgdGhlIHNhbWUgYmlubmluZyBhcyBhIGhpc3RvZ3JhbSBhbmQgdGhlbiBjcmVhdGUgYQogIHNwaW5lIHBsb3QuCgoqIENEIHBsb3RzIHVzZSBhIHNtb290aGluZyBvciBkZW5zaXR5IGVzdGltYXRpb24gYXBwcm9hY2guCgpBIHNwaW5vZ3JhbSBmb3IgYEltcHJvdmVkYCBhZ2FpbnN0IGBBZ2VgOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpBcnRoVCA8LSBmaWx0ZXIoQXJ0aHJpdGlzLAogICAgICAgICAgICAgICAgVHJlYXRtZW50ID09ICJUcmVhdGVkIikgfD4KICAgIG11dGF0ZShJbXByb3ZlZCA9IGZjdF9yZXYoSW1wcm92ZWQpKQphcnRoVF9ncCA8LQogICAgZ3JpZDo6Z3BhcihmaWxsID0gcmV2KGFydGhfZ3AkZmlsbCkpCnZjZDo6c3BpbmUoSW1wcm92ZWQgfiBBZ2UsCiAgICAgICAgICAgZGF0YSA9IEFydGhULAogICAgICAgICAgIGdwID0gYXJ0aFRfZ3AsCiAgICAgICAgICAgYnJlYWtzID0gNSkKYGBgCgpBbiBhbmFsb2dvdXMgcGxvdCBjcmVhdGVkIHdpdGggYGdnbW9zYWljYCBieSBiaW5uaW5nIHRoZSBgQWdlYCB2YXJpYWJsZToKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KQXJ0aCA8LQogICAgbXV0YXRlKEFydGhyaXRpcywKICAgICAgICAgICBBZ2VCaW4gPSBjdXQoQXJ0aHJpdGlzJEFnZSwKICAgICAgICAgICAgICAgICAgICAgICAgc2VxKDIwLCBieSA9IDEwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVuID0gNykpLAogICAgICAgICAgIEltcHJvdmVkID0gZmN0X3JldihJbXByb3ZlZCkpCmZpbHRlcihBcnRoLCBUcmVhdG1lbnQgPT0gIlRyZWF0ZWQiKSB8PgogICAgY291bnQoSW1wcm92ZWQsIEFnZUJpbikgfD4KICAgIGdncGxvdCgpICsKICAgIGdlb21fbW9zYWljKGFlcyh3ZWlnaHQgPSBuLAogICAgICAgICAgICAgICAgICAgIHggPSBwcm9kdWN0KEFnZUJpbiksCiAgICAgICAgICAgICAgICAgICAgZmlsbCA9IEltcHJvdmVkKSkgKwogICAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJCbHVlcyIsCiAgICAgICAgICAgICAgICAgICAgICBkaXJlY3Rpb24gPSAtMSwKICAgICAgICAgICAgICAgICAgICAgIGd1aWRlID0gZ3VpZGVfbGVnZW5kKHJldmVyc2UgPSBUUlVFKSkgKwogICAgdGhlbWVfbWluaW1hbCgpICsKICAgIHRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKQSBmYWNldCBncmlkIGNhbiBiZSB1c2VkIHRvIGNyZWF0ZSBzcGlub2dyYW1zIGZvciBlYWNoIG9mIHRoZQpgU2V4YC9gVHJlYXRtZW50YCBjb21iaW5hdGlvbnM6CgpgYGB7ciwgZmlnLndpZHRoID0gOCwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmdncGxvdChjb3VudChBcnRoLCBJbXByb3ZlZCwgU2V4LCBUcmVhdG1lbnQsIEFnZUJpbikpICsKICAgIGdlb21fbW9zYWljKGFlcyh3ZWlnaHQgPSBuLAogICAgICAgICAgICAgICAgICAgIHggPSBwcm9kdWN0KEFnZUJpbiksCiAgICAgICAgICAgICAgICAgICAgZmlsbCA9IEltcHJvdmVkKSkgKwogICAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJCbHVlcyIsCiAgICAgICAgICAgICAgICAgICAgICBkaXJlY3Rpb24gPSAtMSwKICAgICAgICAgICAgICAgICAgICAgIGd1aWRlID0gZ3VpZGVfbGVnZW5kKHJldmVyc2UgPSBUUlVFKSkgKwogICAgdGhlbWVfbWluaW1hbCgpICsKICAgIGZhY2V0X2dyaWQoVHJlYXRtZW50IH4gU2V4KSArCiAgICB0aGVtZShheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDM1LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGp1c3QgPSAxKSwKICAgICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCkEgW3NwaW5vZ3JhbV0oaHR0cHM6Ly9mbG93aW5nZGF0YS5jb20vMjAyMS8wOC8wMi9kZWNsaW5lLW9mLXUtcy12YWNjaW5hdGlvbi1yYXRlLWNvbXBhcmVkLWFnYWluc3QtZXVyb3Blcy8pIGluIHRoZSBtZWRpYSAoTllULCBBdWd1c3QgMjAyMSk6CgpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGggPSAiNzAlIn0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoSU1HKCJWYWNjaW5hdGlvbi1yYXRlcy13aXRoLU1hcmltZWtrby0xNTM2eDkyNS5wbmciKSkKYGBgCgpTb21lIHBsb3RzIGluIGEgW1R3aXR0ZXIgdGhyZWFkXShodHRwczovL3R3aXR0ZXIuY29tL2pidXJubXVyZG9jaC9zdGF0dXMvMTUwMzQyMDY2MDg2OTIxNDIxMz9zPTIwJnQ9Ulp4T1hpSFowZVhrVjZXWE1JdVZWQSk6CgpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGggPSAiOTAlIn0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoSU1HKCJjb3ZpZC1zcGlub2dyYW0ucG5nIikpCmBgYAoKYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoID0gIjkwJSJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKElNRygiY292aWQtbW9ydC5wbmciKSkKYGBgCgpDRCBwbG90cyBlc3RpbWF0ZSB0aGUgY29uZGl0aW9uYWwgZGVuc2l0eSBvZiB0aGUgYHhgIHZhcmlhYmxlIGdpdmVuIHRoZQpsZXZlbHMgb2YgYHlgLCB3ZWlnaHRlZCBieSB0aGUgbWFyZ2luYWwgcHJvcG9ydGlvbnMgb2YgYHlgIGFuZCB1c2UKdGhlc2UgdG8gZXN0aW1hdGUgY3VtdWxhdGl2ZSBwcm9iYWJpbGl0aWVzLgoKKiBUaGUgc2xpY2UgYXQgYSBwYXJ0aWN1bGFyIGB4YCBsZXZlbCB2aXN1YWxpemVzIHRoZSBjb25kaXRpb25hbAogIGRpc3RyaWJ1dGlvbiBvZiBgeWAgZ2l2ZW4gYHhgIGF0IHRoYXQgbGV2ZWwuCgoqIGBnZW9tX2RlbnNpdHlgIHdpdGggYHBvc2l0aW9uID0gc3RhY2tgIGlzIG9uZSB3YXkgdG8gY3JlYXRlIGEgQ0QKICBwbG90LgoKKiBUaGUgYGNkX3Bsb3RgIGZ1bmN0aW9uIGZyb20gdGhlIGB2Y2RgIHBhY2thZ2UgcHJvZHVjZXMgYSBDRCBwbG90CiAgdXNpbmcgYGdyaWRgIGdyYXBoaWNzLgoKKiBUaGUgYGNkcGxvdGAgZnVuY3Rpb24gZnJvbSB0aGUgYmFzZSBgZ3JhcGhpY3NgIHBhY2thZ2UgcHJvdmlkZXMgdGhlCiAgc2FtZSBwbG90cyB1c2luZyBiYXNlIGdyYXBoaWNzLgoKPCEtLSBCdWlsZGluZyBhIENEIHBsb3QgaW4gc3RlcHM6CmBgYHIKZCA8LSBmaWx0ZXIoQXJ0aHJpdGlzLCBUcmVhdG1lbnQgPT0gIlRyZWF0ZWQiLCBTZXggPT0gIkZlbWFsZSIpCgojIyB1c2UgeSA9IGFmdGVyX3N0YXQoY291bnQpIHNvIGFyZWEgaXMgbnVtYmVyIG9mIHJvd3MKcDAgPC0gZ2dwbG90KGQsIGFlcyh4ID0gQWdlLCB5ID0gYWZ0ZXJfc3RhdChjb3VudCkpKSArCiAgICBnZW9tX2RlbnNpdHkoYncgPSA1LCBmaWxsID0gImdyZXkiKQpwMAoKIyMgc2VwYXJhdGUgY291bnQtd2VpZ2h0ZWQgZGVuc2l0aWVzIGZvciBlYWNoIGdyb3VwIHdpdGggYWxwaGEgYmxlbmRpbmcKcDEgPC0gZ2dwbG90KGQsIGFlcyh4ID0gQWdlLCB5ID0gYWZ0ZXJfc3RhdChjb3VudCksIGZpbGwgPSBJbXByb3ZlZCkpICsKICAgIGdlb21fZGVuc2l0eShidyA9IDUsIGFscGhhID0gMC41KSArCiAgICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIkJsdWVzIikgKwogICAgZ3VpZGVzKGZpbGwgPSAibm9uZSIpCnAxCgojIyBlYXNpZXIgdG8gc2VlIHRoZSBzZXBhcmF0ZSBkZW5zaXRpZXMgd2l0aCBmYWNldGluZwpwMSArIGZhY2V0X3dyYXAofiBJbXByb3ZlZCkKCiMjIHN0YWNrIHRoZSBkZW5zaXRpZXMKZ2dwbG90KGQsIGFlcyh4ID0gQWdlLCB5ID0gYWZ0ZXJfc3RhdChjb3VudCksIGZpbGwgPSBJbXByb3ZlZCkpICsKICAgIGdlb21fZGVuc2l0eShwb3NpdGlvbiA9ICJzdGFjayIsIGJ3ID0gNSkgKwogICAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJCbHVlcyIpICsKICAgIGd1aWRlcyhmaWxsID0gIm5vbmUiKQoKIyMgcmVzY2FsZSB0byBoZWlnaHQgMSB3aXRoIHBvc2l0aW9uID0gImZpbGwiCnAyIDwtIGdncGxvdChkLCBhZXMoeCA9IEFnZSwgeSA9IGFmdGVyX3N0YXQoY291bnQpLCBmaWxsID0gSW1wcm92ZWQpKSArCiAgICBnZW9tX2RlbnNpdHkocG9zaXRpb24gPSAiZmlsbCIsIGJ3ID0gNSkgKwogICAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJCbHVlcyIpICsKICAgIGd1aWRlcyhmaWxsID0gIm5vbmUiKQpwMgoKcDIgKyBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSA0MCwgbHR5ID0gMikKCnAyICsgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gNjAsIGx0eSA9IDIpCmBgYAotLT4KCkNEIHBsb3RzIGZvciB0aGUgYFRyZWF0ZWRgIGdyb3VwOgoKYGBge3IsIGZpZy5oZWlnaHQgPSA2LCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZmlsdGVyKEFydGhyaXRpcywgVHJlYXRtZW50ID09ICJUcmVhdGVkIikgfD4KICAgIGdncGxvdChhZXMoeCA9IEFnZSwgZmlsbCA9IEltcHJvdmVkKSkgKwogICAgZ2VvbV9kZW5zaXR5KHBvc2l0aW9uID0gImZpbGwiLCBidyA9IDUpICsKICAgIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiQmx1ZXMiKSArCiAgICBmYWNldF93cmFwKH4gU2V4LCBuY29sID0gMSkgKwogICAgdGhtCmBgYAoKQ0QgcGxvdHMgZm9yIGFsbCBjb21iaW5hdGlvbnMgZW5kIHVwIHdpdGggb25lIGdyb3VwIG9mIHNpemUgb25lIGFuZApvbmUgb2Ygc2l6ZSB6ZXJvLCB3aGljaCBwcm9kdWNlcyBhIG5vbi11c2VmdWwgcGxvdCBmb3Igb25lCmNvbWJpbmF0aW9uOgoKYGBge3IsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA2LCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KY291bnQoQXJ0aHJpdGlzLCBUcmVhdG1lbnQsIFNleCwgSW1wcm92ZWQpIHw+CiAgICBjb21wbGV0ZShUcmVhdG1lbnQsIFNleCwgSW1wcm92ZWQsCiAgICAgICAgICAgICBmaWxsID0gbGlzdChuID0gMCkpIHw+CiAgICBmaWx0ZXIobiA8IDIpCgpnZ3Bsb3QoQXJ0aHJpdGlzLAogICAgICAgYWVzKHggPSBBZ2UsIGZpbGwgPSBJbXByb3ZlZCkpICsKICAgIGdlb21fZGVuc2l0eShwb3NpdGlvbiA9ICJmaWxsIiwgYncgPSA1KSArCiAgICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIkJsdWVzIikgKwogICAgZmFjZXRfZ3JpZChUcmVhdG1lbnQgfiBTZXgpICsKICAgIHRobQpgYGAKCgojIyBVbmNlcnRhaW50eSBSZXByZXNlbnRhdGlvbgoKQ2F0ZWdvcmljYWwgZGF0YSBhcmUgb2Z0ZW4gYW5hbHl6ZWQgYnkgZml0dGluZyBtb2RlbHMgcmVwcmVzZW50aW5nCmNvbmRpdGlvbmFsIGluZGVwZW5kZW5jZSBzdHJ1Y3R1cmVzLgoKKiBQbG90dGluZyByZXNpZHVhbHMgZnJvbSB0aGVzZSBtb2RlbHMgY2FuIGhlbHAgYXNzZXNzIGhvdyB3ZWxsIHRoZXkKICBmaXQuCgoqIGB2Y2Q6Om1vc2FpY2Agc3VwcG9ydHMgdXNpbmcgY29sb3IgdG8gcmVwcmVzZW50IG1hZ25pdHVkZSBvZiByZXNpZHVhbHMKICBmb3IgY29tcGFyaW5nIHRvIGEgc2ltcGxlIGluZGVwZW5kZW5jZSBtb2RlbC4KCkZvciB0aGUgYEFydGhyaXRpc2AgZGF0YSwgb2JzZXJ2ZWQgY291bnRzIGFuZCBleHBlY3RlZCBjb3VudHMgdW5kZXIgYW4KaW5kZXBlbmRlbmNlIG1vZGVsIGFzc3VtaW5nIGBUcmVhdG1lbnRgIGFuZCBgSW1wcm92ZWRgIGFyZSBpbmRlcGVuZGVudApjYW4gYmUgdmlzdWFsaXplZCBhcyBtb3NhaWMgcGxvdHM6CgpgYGB7ciwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDQsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQojIyB0aGVyZSBhcmUgZWFzaWVyIHdheXMgZG8gZG8gdGhpcyAuLi4KdiA8LSBjb3VudChBcnRocml0aXMsIFRyZWF0bWVudCwgSW1wcm92ZWQpCnBUIDwtIGdyb3VwX2J5KHYsIFRyZWF0bWVudCkgfD4KICAgIHN1bW1hcml6ZShuID0gc3VtKG4pKSB8PgogICAgbXV0YXRlKHBUID0gbiAvIHN1bShuKSkgfD4KICAgIHNlbGVjdCgtbikKcEkgPC0gZ3JvdXBfYnkodiwgSW1wcm92ZWQpIHw+CiAgICBzdW1tYXJpemUobiA9IHN1bShuKSkgfD4KICAgIG11dGF0ZShwSSA9IG4gLyBzdW0obikpIHw+CiAgICBzZWxlY3QoLW4pCnYgPC0gbGVmdF9qb2luKHYsIHBULCAiVHJlYXRtZW50IikgfD4KICAgIGxlZnRfam9pbihwSSwgIkltcHJvdmVkIikgfD4KICAgIG11dGF0ZShwID0gcFQgKiBwSSwKICAgICAgICAgICBUcmVhdG1lbnQgPSBmY3RfcmV2KFRyZWF0bWVudCkpCgpwbyA8LSBnZ3Bsb3QodikgKwogICAgZ2VvbV9tb3NhaWMoYWVzKHdlaWdodCA9IG4sIHggPSBwcm9kdWN0KEltcHJvdmVkLCBUcmVhdG1lbnQpLAogICAgICAgICAgICAgICAgICAgIGZpbGwgPSBJbXByb3ZlZCkpICsKICAgIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiQmx1ZXMiKSArCiAgICBndWlkZXMoZmlsbCA9ICJub25lIikgKwogICAgbGFicyh0aXRsZSA9ICJPYnNlcnZlZCBQcm9wb3J0aW9ucyIpICsKICAgIHRobSArCiAgICBjb29yZF9mbGlwKCkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAwKSkKCnBlIDwtIGdncGxvdCh2KSArCiAgICBnZW9tX21vc2FpYyhhZXMod2VpZ2h0ID0gcCwgeCA9IHByb2R1Y3QoSW1wcm92ZWQsIFRyZWF0bWVudCksCiAgICAgICAgICAgICAgICAgICAgZmlsbCA9IEltcHJvdmVkKSkgKwogICAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJCbHVlcyIpICsKICAgIGd1aWRlcyhmaWxsID0gIm5vbmUiKSArCiAgICBsYWJzKHRpdGxlID0gIkV4cGVjdGVkIFByb3BvcnRpb25zIikgKwogICAgdGhtICsKICAgIGNvb3JkX2ZsaXAoKSArCiAgICB0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDApKQoKcG8gKyBwZQpgYGAKCkEgcGxvdCBmb3IgYXNzZXNzaW5nIHRoZSBmaXQgb2YgdGhlIHJlc2lkdWFscyBiZXR3ZWVuIHRoZSBvYnNlcnZlZCBhbmQKZXhwZWN0ZWQgZGF0YSB1bmRlciBhIG1vZGVsIGFzc3VtaW5nIGluZGVwZW5kZW5jZSBvZiBgVHJlYXRtZW50YCBhbmQKYEltcHJvdmVkYCBwcm9kdWNlczoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KdmNkOjptb3NhaWMofiBUcmVhdG1lbnQgKyBJbXByb3ZlZCwKICAgICAgICAgICAgZGF0YSA9IEFydGhyaXRpcywKICAgICAgICAgICAgZ3AgPSB2Y2Q6OnNoYWRpbmdfbWF4KQpgYGAKCkFub3RoZXIgdmlzdWFsaXphdGlvbiBvZiB0aGUgcmVzaWR1YWxzIGlzIHRoZSBfYXNzb2NpYXRpb24gcGxvdF8KcHJvZHVjZWQgYnkgYGFzc29jYDoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KdmNkOjphc3NvYyh+IFRyZWF0bWVudCArIEltcHJvdmVkLAogICAgICAgICAgIGRhdGEgPSBBcnRocml0aXMsCiAgICAgICAgICAgZ3AgPSB2Y2Q6OnNoYWRpbmdfbWF4KQpgYGAKCgojIyBSZWZlcmVuY2VzCgo+IFRoZSB2aWduZXR0ZSBbX1Jlc2lkdWFsLUJhc2VkIFNoYWRpbmdzIGluCj4gdmNkX10oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvcGFja2FnZT12Y2QvdmlnbmV0dGVzL3Jlc2lkdWFsLXNoYWRpbmdzLnBkZikKPiBpbiB0aGUgYHZjZGAgcGFja2FnZS4KCj4gWmVpbGVpcywgQWNoaW0sIERhdmlkIE1leWVyLCBhbmQgS3VydCBIb3JuaWsuICJSZXNpZHVhbC1iYXNlZAo+IHNoYWRpbmdzIGZvciB2aXN1YWxpemluZyAoY29uZGl0aW9uYWwpIGluZGVwZW5kZW5jZS4iIEpvdXJuYWwgb2YKPiBDb21wdXRhdGlvbmFsIGFuZCBHcmFwaGljYWwgU3RhdGlzdGljcyAxNiwgbm8uIDMgKDIwMDcpOiA1MDctNTI1LgoKPiBUaGUgdmlnbmV0dGUgW19Xb3JraW5nIHdpdGggY2F0ZWdvcmljYWwgZGF0YSB3aXRoIFIgYW5kIHRoZSB2Y2QgYW5kCiAgdmNkRXh0cmEKICBwYWNrYWdlc19dKGh0dHBzOi8vd3d3LmRhdGF2aXMuY2EvY291cnNlcy9WQ0QvdmNkLXR1dG9yaWFsLnBkZikKICBpbiB0aGUgYHZjZEV4dHJhYCBwYWNrYWdlLgoKU2V2ZXJhbCBvdGhlciBleHBlcmltZW50YWwgbW9zYWljIHBsb3QgaW1wbGVtZW50YXRpb25zIGFyZSBhdmFpbGFibGUKZm9yIGBnZ3Bsb3RgLgoKCiMjIFNvbWUgT3RoZXIgVmlzdWFsaXphdGlvbnMKCgojIyMgVHJlZSBNYXBzCgpUcmVlIG1hcHMgc2hvdyBoaWVyYXJjaGljYWxseSBzdHJ1Y3R1cmVkIChvciB0cmVlLXRydWN0dXJlZCkgZGF0YS4KCiogRWFjaCBicmFuY2ggaXMgcmVwcmVzZW50ZWQgYnkgYSByZWN0YW5nbGUuCgoqIExlYWYgbm9kZSB0aWxlcyBoYXZlIGFyZWFzIHByb3BvcnRpb25hbCB0byB0aGUgdmFsdWUgb2YgYSB2YXJpYWJsZS4KCiogVGlsZXMgYXJlIG9mdGVuIGNvbG9yZWQgdG8gcmVmbGVjdCB0aGUgdmFsdWUgb2YgYW5vdGhlciB2YXJpYWJsZS4KClRoZSBwYWNrYWdlIGB0cmVlbWFwaWZ5YCBwcm92aWRlcyBhIGBnZ3Bsb3RgLWJhc2VkIGltcGxlbWVudGF0aW9uLgoKVGhlIGRhdGEgc2V0IGBHMjBgIGluY2x1ZGVzIHNvbWUgdmFyaWFibGVzIG9uIHRoZSBHLTIwIG1lbWJlciBjb3VudHJpZXM6CgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmxpYnJhcnkodHJlZW1hcGlmeSkKc2VsZWN0KEcyMCwgcmVnaW9uLAogICAgICAgY291bnRyeSwgZ2RwX21pbF91c2QsIGhkaSkgfD4KICAgIGtuaXRyOjprYWJsZShmb3JtYXQgPSAiaHRtbCIpIHw+CiAgICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKAogICAgICAgICAgICAgICAgICAgIGZ1bGxfd2lkdGggPSBGQUxTRSkKYGBgCgpBIHNpbXBsZSB0cmVlIHdpdGggb25seSBvbmUgbGV2ZWwsIHRoZSBpbmRpdmlkdWFsIGNvdW50cmllczoKCmBgYHtyLCBpbmNsdWRlID0gRkFMU0V9CmxpYnJhcnkobm9tbm9tbCkKYGBgCjwhLS0KCiMgbm9saW50IHN0YXJ0Ci0tPgo8Y2VudGVyPgpgYGB7bm9tbm9tbCwgZWNobyA9IEZBTFNFfQojcGFkZGluZzogMjUKI2ZvbnRzaXplOiAxOAojbGluZXdpZHRoOiAyCiNkaXJlY3Rpb246IGRvd24KCltSb290XSAtPiBbR2VybWFueV0KW1Jvb3RdIC0+IFtGcmFuY2VdCltSb290XSAtPiBbSmFwYW5dCltSb290XSAtPiBbQ2hpbmFdCltSb290XSAtPiBbLi4uXQpgYGAKPC9jZW50ZXI+CjwhLS0KCiMgbm9saW50IGVuZAotLT4KCkEgY29ycmVzcG9uZGluZyB0cmVlIG1hcCBiYXNlZCBvbiBgZ2RwX21pbF91c2RgOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpnZ3Bsb3QoRzIwLCBhZXMoYXJlYSA9IGdkcF9taWxfdXNkKSkgKwogICAgZ2VvbV90cmVlbWFwKCkgKwogICAgZ2VvbV90cmVlbWFwX3RleHQoYWVzKGxhYmVsID0gY291bnRyeSksCiAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9ICJ3aGl0ZSIpCmBgYAoKQSB0cmVlIGdyb3VwaW5nIGJ5IHJlZ2lvbjoKCjxjZW50ZXI+CmBgYHtub21ub21sLCBlY2hvID0gRkFMU0V9CiNwYWRkaW5nOiAyNQojZm9udHNpemU6IDE4CiNsaW5ld2lkdGg6IDIKI2RpcmVjdGlvbjogZG93bgoKW1Jvb3RdIC0+IFtFdXJvcGVdCltSb290XSAtPiBbQXNpYV0KW1Jvb3RdIC0+IFsuLi5dCltFdXJvcGVdIC0+IFtHZXJtYW55XQpbRXVyb3BlXSAtPiBbRnJhbmNlXQpbRXVyb3BlXSAtPiBbLi4uLl0KW0FzaWFdIC0+IFtKYXBhbl0KW0FzaWFdIC0+IFtDaGluYV0KW0FzaWFdIC0+IFsuLi4uLl0KCmBgYAo8L2NlbnRlcj4KCkEgY29ycmVzcG9uZGluZyB0cmVlIG1hcDoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZ2dwbG90KEcyMCwgYWVzKGFyZWEgPSBnZHBfbWlsX3VzZCwKICAgICAgICAgICAgICAgIHN1Ymdyb3VwID0gcmVnaW9uKSkgKwogICAgZ2VvbV90cmVlbWFwKCkgKwogICAgZ2VvbV90cmVlbWFwX3RleHQoYWVzKGxhYmVsID0gY291bnRyeSksCiAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9ICJ3aGl0ZSIpICsKICAgIGdlb21fdHJlZW1hcF9zdWJncm91cF9ib3JkZXIoCiAgICAgICAgY29sb3IgPSAicmVkIikgKwogICAgZ2VvbV90cmVlbWFwX3N1Ymdyb3VwX3RleHQoY29sb3IgPSAicmVkIikKYGBgCgpBIHRyZWUgbWFwIHNob3dpbmcgR0RQIHZhbHVlcyBmb3IgdGhlIEctMjAgbWVtYmVycywgZ3JvdXBlZCBieSByZWdpb24sCndpdGggZmlsbCBtYXBwZWQgdG8gdGhlIGNvdW50cnkncyBIdW1hbiBEZXZlbG9wbWVudCBJbmRleDoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KZ2dwbG90KEcyMCwgYWVzKGFyZWEgPSBnZHBfbWlsX3VzZCwKICAgICAgICAgICAgICAgIGZpbGwgPSBoZGksCiAgICAgICAgICAgICAgICBzdWJncm91cCA9IHJlZ2lvbikpICsKICAgIGdlb21fdHJlZW1hcCgpICsKICAgIGdlb21fdHJlZW1hcF90ZXh0KGFlcyhsYWJlbCA9IGNvdW50cnkpLAogICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSAid2hpdGUiKSArCiAgICBnZW9tX3RyZWVtYXBfc3ViZ3JvdXBfYm9yZGVyKCkgKwogICAgZ2VvbV90cmVlbWFwX3N1Ymdyb3VwX3RleHQoCiAgICAgICAgY29sb3IgPSAibGlnaHRncmV5IikKYGBgCgpBIHRyZWVtYXAgcmVwcmVzZW50aW5nIHRoZSBkaXN0cmlidXRpb24gb2YgZXllIGNvbG9yIHdpdGhpbiBoYWlyIGNvbG9yOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpncm91cF9ieShhZ2csIEV5ZSwgSGFpcikgfD4KICAgIHN1bW1hcml6ZShuID0gc3VtKG4pKSB8PgogICAgdW5ncm91cCgpIHw+CiAgICBnZ3Bsb3QoYWVzKGFyZWEgPSBuLAogICAgICAgICAgICAgICBzdWJncm91cCA9IEhhaXIpKSArCiAgICBnZW9tX3RyZWVtYXAoYWVzKGZpbGwgPSBFeWUpLAogICAgICAgICAgICAgICAgIGNvbG9yID0gIndoaXRlIikgKwogICAgZ2VvbV90cmVlbWFwX3N1Ymdyb3VwX3RleHQoKSArCiAgICBnZW9tX3RyZWVtYXBfc3ViZ3JvdXBfYm9yZGVyKAogICAgICAgIGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDYpICsKICAgIGdlb21fdHJlZW1hcF90ZXh0KGFlcyhsYWJlbCA9IEV5ZSksCiAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9ICJncmV5OTAiKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBlY29scykgKwogICAgZ3VpZGVzKGZpbGwgPSAibm9uZSIpCmBgYAoKQSB0cmVlbWFwIHJlcHJlc2VudGluZyBwcm9wb3J0aW9ucyBmb3IgYEltcHJvdmVkYCB3aXRoaW4gYFRyZWF0bWVudGAKd2l0aGluIGBTZXhgIGZvciB0aGUgQXJ0aHJpdGlzIGRhdGE6CgpgYGB7ciwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmNvdW50KEFydGgsIFRyZWF0bWVudCwgSW1wcm92ZWQsIFNleCkgfD4KICAgIGdncGxvdChhZXMoYXJlYSA9IG4sCiAgICAgICAgICAgICAgIHN1Ymdyb3VwID0gU2V4LCBmaWxsID0gSW1wcm92ZWQsCiAgICAgICAgICAgICAgIHN1Ymdyb3VwMiA9IFRyZWF0bWVudCkpICsKICAgIGdlb21fdHJlZW1hcCgpICsKICAgIGdlb21fdHJlZW1hcF9zdWJncm91cF90ZXh0KCkgKwogICAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJCbHVlcyIsCiAgICAgICAgICAgICAgICAgICAgICBkaXJlY3Rpb24gPSAtMSkgKwogICAgZ2VvbV90cmVlbWFwX3N1Ymdyb3VwX2JvcmRlcigpICsKICAgIGdlb21fdHJlZW1hcF9zdWJncm91cDJfdGV4dChwbGFjZSA9ICJ0b3AiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSAyMCkKYGBgCgoKIyMjIEFsbHV2aWFsIHBsb3RzCgpUaGVzZSBhcmUgYWxzbyBrbm93biBhcwoKKiBfcGFyYWxsZWwgc2V0c18sIG9yCgoqIF9TYW5rZXkgZGlhZ3JhbXNfLgoKVGhleSBjYW4gYmUgdmlld2VkIGFzIGEgcGFyYWxsZWwgY29vcmRpbmF0ZXMgcGxvdCBmb3IgY2F0ZWdvcmljYWwgZGF0YS4KClNldmVyYWwgaW1wbGVtZW50YXRpb25zIGFyZSBhdmFpbGFibGUsIGluY2x1ZGluZzoKCjwhLS0gKiBgYWxsdXZpYWxgIHVzaW5nIGJhc2UgZ3JhcGhpY3M7IC0tPgoKKiBgZ2VvbV9wYXJhbGxlbF9zZXRzYCBmcm9tIGBnZ2ZvcmNlYDsKCiogYGdlb21fc2Fua2V5YCBmcm9tIFtgZ2dzYW5rZXlgXShodHRwczovL2dpdGh1Yi5jb20vZGF2aWRzam9iZXJnL2dnc2Fua2V5KTsKCiogYGdlb21fYWxsdXZpdW1gIGZyb20gYGdnYWxsdXZpYWxgLgoKPCEtLSBIYWlyL0V5ZSBjb2xvciB1c2luZyB0aGUgYGFsbHV2aWFsYCBwYWNrYWdlOiAtLT4KCmBgYHtyLCBlY2hvID0gRkFMU0UsIGV2YWwgPSBGQUxTRX0KSERGIDwtIG11dGF0ZShIYWlyRXllQ29sb3JERiwgU2V4ID0gZmN0X3JldihTZXgpKQpsaWJyYXJ5KGFsbHV2aWFsKQpwYWwgPC0gUkNvbG9yQnJld2VyOjpicmV3ZXIucGFsKDMsICJTZXQxIikKd2l0aChIREYsCiAgICAgYWxsdXZpYWwoSGFpciwgRXllLCBTZXgsIGZyZXEgPSBGcmVxLCBjb2wgPSBwYWxbYXMubnVtZXJpYyhTZXgpXSkpCmBgYAoKSGFpci9FeWUgY29sb3IgdXNpbmcgdGhlIGBnZ2ZvcmNlYCBwYWNrYWdlOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpwYWwgPC0gUkNvbG9yQnJld2VyOjpicmV3ZXIucGFsKDMsICJTZXQxIikKSERGIDwtIG11dGF0ZShIYWlyRXllQ29sb3JERiwKICAgICAgICAgICAgICBTZXggPSBmY3RfcmV2KFNleCkpCmxpYnJhcnkoZ2dmb3JjZSkKc0hERiA8LSBnYXRoZXJfc2V0X2RhdGEoSERGLCAzIDogMSkKc0hERiA8LSBtdXRhdGUoc0hERiwgeCA9IGZjdF9pbm9yZGVyKGFzLmZhY3Rvcih4KSkpICMqKioqIHNpbXBsaWZ5IHRoaXM/CmdncGxvdChzSERGLCBhZXMoeCwgaWQgPSBpZCwKICAgICAgICAgICAgICAgICBzcGxpdCA9IHksCiAgICAgICAgICAgICAgICAgdmFsdWUgPSBGcmVxKSkgKwogICAgZ2VvbV9wYXJhbGxlbF9zZXRzKGFlcyhmaWxsID0gU2V4KSwKICAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IDAuNSwKICAgICAgICAgICAgICAgICAgICAgICBheGlzLndpZHRoID0gMC4xKSArCiAgICBnZW9tX3BhcmFsbGVsX3NldHNfYXhlcygKICAgICAgICBheGlzLndpZHRoID0gMC4xKSArCiAgICBnZW9tX3BhcmFsbGVsX3NldHNfbGFiZWxzKAogICAgICAgIGNvbG91ciA9ICd3aGl0ZScpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKAogICAgICAgIHZhbHVlcyA9IGMoTWFsZSA9IHBhbFsyXSwKICAgICAgICAgICAgICAgICAgIEZlbWFsZSA9IHBhbFsxXSkpICsKICAgIHRoZW1lX3ZvaWQoKSArIGd1aWRlcyhmaWxsID0gIm5vbmUiKQpgYGAKCjwhLS0gQXJ0aHJpdGlzIGRhdGEgd2l0aCBgYWxsdXZpYWxgOiAtLT4KCmBgYHtyLCBlY2hvID0gRkFMU0UsIGV2YWwgPSBGQUxTRX0Kd2l0aChjb3VudChBcnRoLCBJbXByb3ZlZCwgVHJlYXRtZW50LCBTZXgpLAogICAgIGFsbHV2aWFsKEltcHJvdmVkLCBUcmVhdG1lbnQsIFNleCwgZnJlcSA9IG4sIGNvbCA9IHBhbFthcy5udW1lcmljKFNleCldKSkKYGBgCgpBcnRocml0aXMgZGF0YSB3aXRoIGBnZ2ZvcmNlYDoKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0Kc0FydGggPC0gbXV0YXRlKEFydGgsCiAgICAgICAgICAgICAgICBJbXByb3ZlZCA9IGZhY3RvcihJbXByb3ZlZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9yZGVyZWQgPSBGQUxTRSkpIHw+CiAgICBjb3VudChJbXByb3ZlZCwgVHJlYXRtZW50LCBTZXgpIHw+CiAgICBnYXRoZXJfc2V0X2RhdGEoMyA6IDEpCnNBcnRoIDwtIG11dGF0ZShzQXJ0aCwKICAgICAgICAgICAgICAgIHggPSBmY3RfaW5vcmRlcihmYWN0b3IoeCkpLAogICAgICAgICAgICAgICAgU2V4ID0gZmN0X3JldihTZXgpKQpnZ3Bsb3Qoc0FydGgsIGFlcyh4LAogICAgICAgICAgICAgICAgICBpZCA9IGlkLAogICAgICAgICAgICAgICAgICBzcGxpdCA9IHksCiAgICAgICAgICAgICAgICAgIHZhbHVlID0gbikpICsKICAgIGdlb21fcGFyYWxsZWxfc2V0cyhhZXMoZmlsbCA9IFNleCksCiAgICAgICAgICAgICAgICAgICAgICAgYWxwaGEgPSAwLjUsCiAgICAgICAgICAgICAgICAgICAgICAgYXhpcy53aWR0aCA9IDAuMSkgKwogICAgZ2VvbV9wYXJhbGxlbF9zZXRzX2F4ZXMoYXhpcy53aWR0aCA9IDAuMSkgKwogICAgZ2VvbV9wYXJhbGxlbF9zZXRzX2xhYmVscygKICAgICAgICBjb2xvdXIgPSAnd2hpdGUnKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCgKICAgICAgICB2YWx1ZXMgPSBjKE1hbGUgPSBwYWxbMl0sCiAgICAgICAgICAgICAgICAgICBGZW1hbGUgPSBwYWxbMV0pKSArCiAgICB0aGVtZV92b2lkKCkgKyBndWlkZXMoZmlsbCA9ICJub25lIikKYGBgCgpgYGB7ciwgZXZhbCA9IEZBTFNFLCBlY2hvID0gRkFMU0V9CiMjIHJlZnVnZWUgZGF0YSAobmVlZHMgYWRqdXN0aW5nIHRleHQgc2l6ZXMgb3IgbGFiZWxzKQojIyBjb3VsZCBhbHNvIHRyeSBjaG9yZCBkaWFncmFtClIgPC0gY291bnQocmVmLmR0bCwgRGVzdGluYXRpb24sIE5hdGlvbmFsaXR5LCB3dCA9IENhc2VzKQpuYXRzIDwtIChjb3VudChSLCBOYXRpb25hbGl0eSwgd3QgPSBuKSB8PiBzbGljZV9tYXgobiwgbiA9IDEwKSkkTmF0aW9uYWxpdHkKZHN0cyA8LSAoY291bnQoUiwgRGVzdGluYXRpb24sIHd0ID0gbikgfD4gc2xpY2VfbWF4KG4sIG4gPSAxMCkpJERlc3RpbmF0aW9uCm11dGF0ZShSLCBOYXRpb25hbGl0eSA9IGZjdF9vdGhlcihOYXRpb25hbGl0eSwga2VlcCA9IG5hdHMpLAogICAgICAgRGVzdGluYXRpb24gPSBmY3Rfb3RoZXIoRGVzdGluYXRpb24sIGtlZXAgPSBkc3RzKSkgfD4KICAgIGdhdGhlcl9zZXRfZGF0YSgxIDogMikgfD4KICAgIGdncGxvdChhZXMoeCwgaWQgPSBpZCwgc3BsaXQgPSB5LCB2YWx1ZSA9IG4pKSArCiAgICBnZW9tX3BhcmFsbGVsX3NldHMoYWVzKGZpbGwgPSBOYXRpb25hbGl0eSksIGFscGhhID0gMC41LCBheGlzLndpZHRoID0gMC4xKSArCiAgICBnZW9tX3BhcmFsbGVsX3NldHNfYXhlcyhheGlzLndpZHRoID0gMC4xKSArCiAgICBnZW9tX3BhcmFsbGVsX3NldHNfbGFiZWxzKGNvbG91ciA9ICd3aGl0ZScsIHNpemUgPSAzKSArCiAgICB0aGVtZV92b2lkKCkgKyBndWlkZXMoZmlsbCA9ICJub25lIikKYGBgCgoKIyMjIFN0cmVhbSBHcmFwaHMKCltTdHJlYW0KICBncmFwaHNdKGh0dHBzOi8vd3d3LnZpc3VhbGlzaW5nZGF0YS5jb20vMjAxMC8wOC9tYWtpbmctc2Vuc2Utb2Ytc3RyZWFtZ3JhcGhzLykKICBhcmUgYSBnZW5lcmFsaXphdGlvbiBvZiBzdGFja2VkIGJhciBjaGFydHMgcGxvdHRlZCBhZ2FpbnN0IGEgbnVtZXJpYwogIHZhcmlhYmxlLgoKSW4gc29tZSBjYXNlcyB0aGUgb3JpZ2lucyBvZiB0aGUgYmFycyBhcmUgc2hpZnRlZCB0byBpbXByb3ZlIHNvbWUKYXNwZWN0IG9mIHRoZSBvdmVyYWxsIHZpc3VhbGl6YXRpb24uCgpBbiBlYXJseSBleGFtcGxlIGlzIHRoZSBbQmFieSBOYW1lClZveWFnZXJdKGh0dHBzOi8vd3d3LmJld2l0Y2hlZC5jb20vbmFtZXZveWFnZXIuaHRtbCkuCihBIG1vcmUgcmVjZW50IHZhcmlhbnQgaXMgYWxzbwpbYXZhaWxhYmxlXShodHRwczovL25hbWVyb2xvZ3kuY29tL2JhYnktbmFtZS1ncmFwaGVyLykuKQoKQSBbTlkgVGltZXMKdmlzdWFsaXphdGlvbl0oaHR0cHM6Ly9hcmNoaXZlLm55dGltZXMuY29tL3d3dy5ueXRpbWVzLmNvbS9pbnRlcmFjdGl2ZS8yMDA4LzAyLzIzL21vdmllcy8yMDA4MDIyM19SRVZFTlVFX0dSQVBISUMuaHRtbD9fcj0wKQpvZiBtb3ZpZSBib3ggb2ZmaWNlIHJlc3VsdHMgaXMgYW5vdGhlciBleGFtcGxlLiAoW0Jsb2cgcG9zdCB3aXRoIGEKc3RhdGljCnZlcnNpb25dKGh0dHBzOi8vZmxvd2luZ2RhdGEuY29tLzIwMDgvMDIvMjUvZWJiLWFuZC1mbG93LW9mLWJveC1vZmZpY2UtcmVjZWlwdHMtb3Zlci1wYXN0LTIwLXllYXJzLykpLgoKU29tZSBSIGltcGxlbWVudGF0aW9ucyBvbiBHaXRIdWI6CgoqIFtgZ2dUaW1lU2VyaWVzYF0oaHR0cHM6Ly9naXRodWIuY29tL0F0aGVyRW5lcmd5L2dnVGltZVNlcmllcykKKiBbYHN0cmVhbWdyYXBoYF0oaHR0cHM6Ly9ocmJybXN0ci5naXRodWIuaW8vc3RyZWFtZ3JhcGgvKSAodXNlcyBEMykKKiBbYGdnc3RyZWFtYF0oaHR0cHM6Ly9naXRodWIuY29tL2Rhdmlkc2pvYmVyZy9nZ3N0cmVhbSkuCgpBIHN0cmVhbSBncmFwaCBmb3IgbW92aWUgZ2VucmVzICh0aGVzZSBhcmUgbm90IG11dHVhbGx5IGV4Y2x1c2l2ZSk6CgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KIyMgaW5zdGFsbCB3aXRoOiByZW1vdGVzOjppbnN0YWxsX2dpdGh1YigiaHJicm1zdHIvc3RyZWFtZ3JhcGgiKQpsaWJyYXJ5KHN0cmVhbWdyYXBoKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKZ2VucmVzIDwtIGMoIkFjdGlvbiIsICJBbmltYXRpb24iLCAiQ29tZWR5IiwKICAgICAgICAgICAgIkRyYW1hIiwgIkRvY3VtZW50YXJ5IiwgIlJvbWFuY2UiKQpteW1vdmllcyA8LSBzZWxlY3QoZ2dwbG90Mm1vdmllczo6bW92aWVzLAogICAgICAgICAgICAgICAgICAgeWVhciwgb25lX29mKGdlbnJlcykpCm15bW92aWVzX2xvbmcgPC0gcGl2b3RfbG9uZ2VyKAogICAgbXltb3ZpZXMsIC15ZWFyLAogICAgbmFtZXNfdG8gPSAiZ2VucmUiLAogICAgdmFsdWVzX3RvID0gInZhbHVlIikKbW92aWVfY291bnRzIDwtIGNvdW50KG15bW92aWVzX2xvbmcsCiAgICAgICAgICAgICAgICAgICAgICB5ZWFyLCBnZW5yZSkKc3RyZWFtZ3JhcGgobW92aWVfY291bnRzLCAiZ2VucmUiLCAibiIsICJ5ZWFyIikKYGBgCgo8IS0tCm5pY2UgZXhhbXBsZSB3aXRoIFhtZW4gY2hhcmFjdGVycyBmcm9tIFRpZHlUdWVzZGF5Cmh0dHBzOi8vZ2l0aHViLmNvbS9aM3R0L1RpZHlUdWVzZGF5L2Jsb2IvbWFzdGVyL1IvMjAyMF8yN19DbGFyZW1vbnRSdW5YTWVuLlJtZAotLT4KCgojIyBSZWFkaW5nCgpDaGFwdGVycyBbX1Zpc3VhbGl6aW5nCiAgcHJvcG9ydGlvbnNfXShodHRwczovL2NsYXVzd2lsa2UuY29tL2RhdGF2aXovdmlzdWFsaXppbmctcHJvcG9ydGlvbnMuaHRtbCkKICBhbmQgW19WaXN1YWxpemluZyBuZXN0ZWQKICBwcm9wb3J0aW9uc19dKGh0dHBzOi8vY2xhdXN3aWxrZS5jb20vZGF0YXZpei9uZXN0ZWQtcHJvcG9ydGlvbnMuaHRtbCkKICBpbiBbX0Z1bmRhbWVudGFscyBvZiBEYXRhCiAgVmlzdWFsaXphdGlvbl9dKGh0dHBzOi8vY2xhdXN3aWxrZS5jb20vZGF0YXZpei8pLgoKCiMjIEludGVyYWN0aXZlIFR1dG9yaWFsCgpBbiBpbnRlcmFjdGl2ZSBbYGxlYXJucmBdKGh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vbGVhcm5yLykgdHV0b3JpYWwKZm9yIHRoZXNlIG5vdGVzIGlzIFthdmFpbGFibGVdKGByIFdMTksoInR1dG9yaWFscy9wcm9wb3J0aW9ucy5SbWQiKWApLgoKWW91IGNhbiBydW4gdGhlIHR1dG9yaWFsIHdpdGgKCmBgYHtyLCBldmFsID0gRkFMU0V9ClNUQVQ0NTgwOjpydW5UdXRvcmlhbCgicHJvcG9ydGlvbnMiKQpgYGAKCllvdSBjYW4gaW5zdGFsbCB0aGUgY3VycmVudCB2ZXJzaW9uIG9mIHRoZSBgU1RBVDQ1ODBgIHBhY2thZ2Ugd2l0aAoKYGBge3IsIGV2YWwgPSBGQUxTRX0KcmVtb3Rlczo6aW5zdGFsbF9naXRsYWIoImx1a2UtdGllcm5leS9TVEFUNDU4MCIpCmBgYAoKWW91IG1heSBuZWVkIHRvIGluc3RhbGwgdGhlIGByZW1vdGVzYCBwYWNrYWdlIGZyb20gQ1JBTiBmaXJzdC4KCgojIyBFeGVyY2lzZXMKCjEuIEZpZ3VyZSBBIHNob3dzIGEgYmFyIGNoYXIgb2YgdGhlIGZsaWdodHMgbGVhdmluZyBOWUMgYWlycG9ydHMgaW4KICAgMjAxMyBmb3IgZWFjaCBkYXkgb2YgdGhlIHdlZWsuIEZpZ3VyZSBCIHNob3dzIHRoZSBtYXJrZXQgc2hhcmUgb2YKICAgZml2ZSBtYWpvciBpbnRlcm5ldCBicm93c2VycyBpbiAyMDE1LgoKICAgIGBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIGVjaG8gPSBGQUxTRSwgZmlnLmhlaWdodCA9IDQsIGZpZy53aWR0aCA9IDh9CiAgICBsaWJyYXJ5KGx1YnJpZGF0ZSkKICAgIGxpYnJhcnkoZHBseXIpCiAgICBsaWJyYXJ5KG55Y2ZsaWdodHMxMykKICAgIGxpYnJhcnkoZ2dwbG90MikKICAgIGxpYnJhcnkocGF0Y2h3b3JrKQogICAgdGhtIDwtIHRoZW1lX21pbmltYWwoKSArIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE1KSkKICAgIHAxIDwtIG11dGF0ZShmbGlnaHRzLAogICAgICAgICAgICAgICAgIGRhdGUgPSBtYWtlX2RhdGUoeWVhciwgbW9udGgsIGRheSksCiAgICAgICAgICAgICAgICAgd2RheSA9IHdkYXkoZGF0ZSwgbGFiZWwgPSBUUlVFLCBhYmJyID0gVFJVRSkpIHw+CiAgICAgICAgZ2dwbG90KGFlcyh4ID0gd2RheSkpICsKICAgICAgICBnZW9tX2JhcihmaWxsID0gImRlZXBza3libHVlMyIpICsKICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkgKwogICAgICAgIGxhYnModGl0bGUgPSAiRmxpZ2h0cyBmcm9tIE5ZQyBpbiAyMDEzIiwKICAgICAgICAgICAgIHN1YnRpdGxlID0gIkJ5IERheSBvZiB0aGUgV2VlayIsCiAgICAgICAgICAgICBjYXB0aW9uID0gIkZpZ3VyZSBBIiwKICAgICAgICAgICAgIHggPSBOVUxMLAogICAgICAgICAgICAgeSA9ICJOdW1iZXIgb2YgRmxpZ2h0cyIpICsKICAgICAgICB0aG0KCiAgICBicm93c2VyczIwMTUgPC0KICAgICAgICBkYXRhLmZyYW1lKEJyb3dzZXIgPSBjKCJPcGVyYSIsICJTYWZhcmkiLCAiRmlyZWZveCIsICJDaHJvbWUiLCAiSUUiKSwKICAgICAgICAgICAgICAgICAgIHNoYXJlID0gYygyLCAyMiwgMjEsIDI3LCAyOSkpCiAgICBwMiA8LSBnZ3Bsb3QoYnJvd3NlcnMyMDE1LCBhZXMoeCA9IEJyb3dzZXIsIHkgPSBzaGFyZSkpICsKICAgICAgICBnZW9tX2NvbChmaWxsID0gImRlZXBza3libHVlMyIpICsKICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkgKwogICAgICAgIGxhYnModGl0bGUgPSAiQnJvd3NlciBNYXJrZXQgU2hhcmUiLAogICAgICAgICAgICAgc3VidGl0bGUgPSAiMjAxNSIsCiAgICAgICAgICAgICBjYXB0aW9uID0gIkZpZ3VyZSBCIiwKICAgICAgICAgICAgIHggPSBOVUxMLAogICAgICAgICAgICAgeSA9ICJQZXJjZW50IikgKwogICAgICAgIHRobQoKICAgIHAxIHwgcDIKICAgIGBgYAoKICAgIEZvciB3aGljaCBvZiB0aGVzZSBiYXIgY2hhcnRzIHdvdWxkIGl0IGJlIGJldHRlciB0byByZW9yZGVyIHRoZQogICAgY2F0ZWdvcmllcyBzbyB0aGUgYmFycyBhcmUgb3JkZXJlZCBmcm9tIGxhcmdlc3QgdG8gc21hbGxlc3Q/CiAgICAKICAgIGEuIFllcyBmb3IgRmlndXJlIEEuIE5vIGZvciBGaWd1cmUgQi4KICAgIGIuIE5vIGZvciBGaWd1cmUgQS4gWWVzIGZvciBGaWd1cmUgQi4KICAgIGMuIFllcyBmb3IgYm90aC4KICAgIGQuIE5vIGZvciBib3RoLgoKMi4gIENvbnNpZGVyIHRoZSBzdGFja2VkIGJhciBjaGFydCBgcDFgIGFuZCB0aGUgc3BpbmUgcGxvdCBgcDJgIGZvcgogICAgdGhlIGhhaXIgYW5kIGV5ZSBjb2xvciBkYXRhIHByb2R1Y2VkIGJ5IHRoZSBmb2xsb3dpbmcgY29kZToKCiAgICBgYGB7ciwgZXZhbCA9IEZBTFNFfQogICAgbGlicmFyeShkcGx5cikKICAgIGxpYnJhcnkoZ2dwbG90MikKICAgIGxpYnJhcnkoZ2dtb3NhaWMpCiAgICBlY29scyA8LSBjKEJyb3duID0gImJyb3duNCIsIEJsdWUgPSAiYmx1ZTIiLAogICAgICAgICAgICAgICBIYXplbCA9ICJkYXJrZ29sZGVucm9kMyIsIEdyZWVuID0gImdyZWVuNCIpCiAgICBIYWlyRXllQ29sb3JERiA8LSBhcy5kYXRhLmZyYW1lKEhhaXJFeWVDb2xvcikKICAgIHAwIDwtIGdncGxvdChIYWlyRXllQ29sb3JERikgKwogICAgICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGVjb2xzKSArCiAgICAgICAgdGhlbWVfbWluaW1hbCgpCgogICAgcDEgPC0gcDAgKyBnZW9tX2NvbChhZXMoeCA9IEhhaXIsIHkgPSBGcmVxIC8gc3VtKEZyZXEpLCBmaWxsID0gRXllKSkKCiAgICBwMiA8LSBwMCArIGdlb21fbW9zYWljKGFlcyh4ID0gcHJvZHVjdChIYWlyKSwgZmlsbCA9IEV5ZSwgd2VpZ2h0ID0gRnJlcSkpCiAgICBgYGAKCiAgICBVc2UgdGhlIHR3byBwbG90cyB0byBhbnN3ZXI6IFdoaWNoIGhhaXIgY29sb3IgaGFzIHRoZSBoaWdoZXN0CiAgICBwcm9wb3J0aW9uIG9mIGluZGl2aWR1YWxzIHdpdGggZ3JlZW4gZXllcz8KCiAgICBhLiBCbGFjawogICAgYi4gQnJvd24KICAgIGMuIFJlZAogICAgZC4gQmxvbmQKCiAgICBXaGljaCBwbG90IG1ha2VzIGl0IGVhc2llc3QgdG8gYW5zd2VyIHRoaXMgcXVlc3Rpb24/CgozLiAgVXNlIHRoZSBwbG90cyBvZiB0aGUgcHJldmlvdXMgcXVlc3Rpb24gdG8gYW5zd2VyOiBUaGUgcHJvcG9ydGlvbgogICAgb2YgaW5kaXZpZHVhbHMgd2l0aCByZWQgaGFpciBpcyBjbG9zZXN0IHRvOgoKICAgIGEuIDUlCiAgICBiLiA4JQogICAgYy4gMTIlCiAgICBkLiAyMCUKCiAgICBXaGljaCBwbG90IG1ha2VzIGl0IGVhc2llc3QgdG8gYW5zd2VyIHRoaXMgcXVlc3Rpb24/Cgo8IS0tCnBhcmV0byBjaGFydAoKICAtIHBpZSBjaGFydHMKICAtIGJhciBjaGFydHMKICAtIHN0YWNrZWQgYmFyIGNoYXJ0cwogIC0gZ3JvdXBlZCBiYXIgY2hhcnRzCiAgLSBwb3B1bGF0aW9uIHB5cmFtaWRzCiAgLSB3YWZmbGUgY2hhcnRzLCBzcXVhcmUgcGllIGNoYXJ0cwoKICAtIGpvaW50IGFuZCBjb25kaXRpb25hbCBkaXN0cmlidXRpb25zCiAgLSBzcGluZSBwbG90cwogIC0gc3Bpbm9ncmFtcz8KICAtIGRvdWJsZSBkZWNrZXIgcGxvdHMKICAtIGNkX3Bsb3RzCiAgLSBtb3NhaWMgcGxvdHMKICAtIHRyZWUgbWFwcwogIC0gc2Fua2V5IGRpYWdyYW1zLCBhbGx1dmlhbCBjaGFydHMsIHBhcmFsbGVsIHNldHMKICAtIGNob3JkIGRpYWdyYW1zCiAgLSBzdHJlYW0gZ3JhcGhzCi0tPgo=