You will need to load in (and possibly install) the following packages:

library(tidyverse)
library(viridis)

You will also need the following datasets:

  1. simpson-paradox-data.csv
  2. boxplot-problems-data.csv
  3. simulated-data.csv

And here is the .Rmd file to follow along with me.

1 Welcome to Dataviz!

Visualising data is more than just turning numbers into pictures. It’s telling a story, creating a narrative, and providing analysis all at once. To visualise data well, there are a few aspects you must consider:

  1. The shape of the data
  2. Principles of aesthetic composition
  3. Priorities of your audience

1.1 Shape of your data

Read in a data file to form the following plots.

spdata <- read.csv("data/simpson-paradox-data.csv")

First, plot the data as simply and in as raw a form as possible to see if there are any clear problems or internal structures.

plot(spdata$y~spdata$x)

You may also want to look at the raw data to get a sense of how it’s structured or whether there are any major problems.

head(spdata)

A summary of the data can also be useful in getting a sense of its shape.

summary(spdata)
       x                y               z            
 Min.   : 48.13   Min.   : 28.28   Length:304        
 1st Qu.:187.58   1st Qu.:127.53   Class :character  
 Median :243.09   Median :193.93   Mode  :character  
 Mean   :244.71   Mean   :198.23                     
 3rd Qu.:307.36   3rd Qu.:270.22                     
 Max.   :479.85   Max.   :374.08                     

We could make our own summaries.

spdata %>% 
  group_by() %>% 
  summarise(number=n(),
            x.mean=mean(x),
            x.sd=sd(x),
            x.se=x.sd/sqrt(number),
            y.mean=mean(y),
            y.sd=sd(y),
            y.se=y.sd/sqrt(number))

We could look at statistical tests.

cor.test(spdata$x, spdata$y)

    Pearson's product-moment correlation

data:  spdata$x and spdata$y
t = 7.8973, df = 302, p-value = 5.332e-14
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.3159325 0.5028124
sample estimates:
      cor 
0.4137212 

And that could be it.

However, without doing more detailed and principled exploration, you might miss something in your data that would cause a Simpson’s Paradox.

ggplot(spdata, aes(x=x, y=y)) +
  geom_point() +
  geom_smooth(method="lm") +
  theme_bw()

Above we see what looks like a clear and significant positive correlation. But from looking at the data, we should know that there is another column of interest that could affect the shape. (Column z, which contains letters.)

ggplot(spdata, aes(x=x, y=y)) +
  geom_point(aes(colour=z)) +
  geom_smooth(aes(colour=z), method="lm") +
  theme_bw()

To make this even more clear, we can overlay the groups’ regression lines with the overall regression line.

ggplot(spdata, aes(x=x, y=y)) +
  geom_point(aes(colour=z)) +
  geom_smooth(method="lm") +
  geom_smooth(aes(colour=z), method="lm") +
  theme_bw()

Each of these tells us something true, but they aren’t all equally useful for informing us about the shape of our data.

For more examples: Datasaurus Dozen

1.1.1 Shape of your visualisation

By the same token, not ever graph gives you the same information.

Read in the next data file.

bpdata <- read.csv("data/boxplot-problems-data.csv")

If we plot this data using a boxplot, we get a lot of information about the median and quartile distribution of the data.

bpdata %>% 
  ggplot(aes(y=y,x=z,fill=z)) +
  geom_boxplot()

But these four categories look to have approximately the same median. The d category has a narrow distribution, but a and b look pretty similar and c is only slightly more broadly distributed. It’s possible that there are no statistical differences between these categories!

# Bonferroni-corrected alpha for three comparisons:
0.05/3
[1] 0.01666667

So, any p-values below 0.0167 can be considered statistically significant.

bpdata %>% # AB, AC, AD
  mutate(z=z %>% as.factor) %>% 
  lm(y ~ z, data=.) %>%
  summary() # A is different to D at alpha=0.05

Call:
lm(formula = y ~ z, data = .)

Residuals:
     Min       1Q   Median       3Q      Max 
-282.440 -104.122    1.443   94.873  308.170 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  237.711      7.994  29.736   <2e-16 ***
zb             4.621     10.040   0.460   0.6454    
zc            10.712     13.755   0.779   0.4362    
zd            24.290     10.588   2.294   0.0219 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 138 on 1357 degrees of freedom
Multiple R-squared:  0.004832,  Adjusted R-squared:  0.002632 
F-statistic: 2.196 on 3 and 1357 DF,  p-value: 0.08675
bpdata %>% # BC, BD
  mutate(z=z %>% as.factor,
         z=fct_relevel(z, "b")) %>% 
  lm(y ~ z, data=.) %>%
  summary() # B is different to D at alpha=0.05

Call:
lm(formula = y ~ z, data = .)

Residuals:
     Min       1Q   Median       3Q      Max 
-282.440 -104.122    1.443   94.873  308.170 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  242.333      6.075  39.890   <2e-16 ***
za            -4.621     10.040  -0.460   0.6454    
zc             6.091     12.735   0.478   0.6325    
zd            19.669      9.226   2.132   0.0332 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 138 on 1357 degrees of freedom
Multiple R-squared:  0.004832,  Adjusted R-squared:  0.002632 
F-statistic: 2.196 on 3 and 1357 DF,  p-value: 0.08675
bpdata %>% # CD
  mutate(z=z %>% as.factor,
         z=fct_relevel(z, "c")) %>% 
  lm(y ~ z, data=.) %>%
  summary() # All ns

Call:
lm(formula = y ~ z, data = .)

Residuals:
     Min       1Q   Median       3Q      Max 
-282.440 -104.122    1.443   94.873  308.170 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  248.423     11.193  22.194   <2e-16 ***
za           -10.712     13.755  -0.779    0.436    
zb            -6.091     12.735  -0.478    0.633    
zd            13.578     13.172   1.031    0.303    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 138 on 1357 degrees of freedom
Multiple R-squared:  0.004832,  Adjusted R-squared:  0.002632 
F-statistic: 2.196 on 3 and 1357 DF,  p-value: 0.08675

We’ve run some simple stats and taken a look at the data, but we can’t see the differences, and it’s not very convincing that they’re really there, especially after correcting for multiple comparisons. So what’s going on?

bpdata %>% 
  ggplot(aes(y=y,fill=z,x=z)) +
  geom_violin()

Oh these all have different shape density distributions! We couldn’t see that in the boxplot because it only showed us information about the quartile distribution.

But we still don’t know enough. For instance, we can see there are fewer points around 200 in category c than in the other categories, but what does this translate to? Are there no points there or just very few?

bpdata %>% 
  ggplot(aes(y=y,fill=z,x=z)) +
  geom_violin() +
  geom_point(position=position_jitter(.2), alpha=.5)

It turns out that c has no points in the middle and that it’s a strongly bimodal distribution. What does using a violin plot imply to your audience, then?

1.2 Principles of aesthetic composition

Create the data set.

tibble(c("a","a","b","b"),c("c","d","c","d"),c(121, 120, 122, 125)) %>% 
  rename(factor="c(\"a\", \"a\", \"b\", \"b\")",
         level="c(\"c\", \"d\", \"c\", \"d\")",
         value="c(121, 120, 122, 125)") -> toydata

1.2.1 Lines and axes

There are many things being communicated, even in a simple plot for dataset such as this.

toydata %>% 
  ggplot(aes(x=factor,y=value,colour=level)) +
  geom_point()+
  geom_path(aes(group=level))

What is this plot telling us?

  • There is an interaction between c and d, whereby the increase from a to b is larger for d than for c
  • This must be a within-subjects analysis, at least within each of c and d
  • This must be a an order, with a coming before b (left to right)
  • There isn’t an order within c and d, since they appear overlaid
  • All values between 120 and 125 are possible values one might observe (continuous data)
toydata %>% 
  ggplot(aes(x=factor,y=value,fill=level)) +
  geom_bar(position="dodge",stat="identity")

What is this plot telling us?

  • There is the possibility for a tiny interaction between c and d, whereby the increase from a to b is larger for d than for c
  • Conditions a and b are independent, as are conditions c and d
  • These data could be unordered and there is no need for it to be within items or within subjects.
  • All values between 0 and 125 are possible values one might observe (continuous data)

1.2.2 Visibility

Busyness can confuse and mislead your audience, but over-simplicity can as well. In order to balance presenting enough data against presenting too much data, consider how many “dimensions” you are showing, and how much of the data and how many plot elements you display at once.

Let’s create a new dataset to play with.

set.seed(3); spdata %>% 
  mutate(rValX = rnorm(x),
         rValY = rnorm(y) %>% abs() %>% log()) -> vizdata

1.2.2.1 Round 1

What’s wrong with this graph?

vizdata %>% 
  ggplot(aes(x=rValX, 
             y=rValY,
             colour=z)) +
  geom_point()

  1. Unlikely to print well
  2. Unhelpful labels
  3. Missing important information

1.2.2.2 Round 2

  1. Unlikely to print well →→ Reduce greys and increase contrast
  2. Unhelpful labels →→ Add axis labels and title
  3. Missing important information →→ Add more dimensions? (Or make companion graphs.)
vizdata %>% 
  ggplot(aes(x=rValX, 
             y=rValY,
             fill=x, # add dimension for column `x`
             size=y, # add dimension for column `y`
             colour=z)) +
  geom_point(shape=21) + # change 'point character' to allow for colour + fill
  scale_fill_gradientn(colours = rainbow(7)) + # add visually striking gradient scale
  theme_bw() + # make background more plain
  # add the labels:
  xlab("Randomized normal distribution of `x`") +
  ylab("Log of the absolute value of a\nrandomized normal distribution of `y`") +
  ggtitle("My cool plot", subtitle = "UKCLC2020")

  1. Overlapping points obscure data
  2. Too many dimensions being encoded in too-similar visual spaces
  3. Perceptually nonlinear gradient scale
  4. Colourblind-unfriendly

1.2.2.3 Round 3

  1. Overlapping points obscure data →→ Add transparency (“alpha”)
  2. Too many dimensions being encoded in too-similar visual spaces →→ Add “shape” as a dimension
  3. Perceptually nonlinear gradient scale →→ Choose a better palette (e.g., viridis)
  4. Colourblind-unfriendly →→ Choose a bespoke palette; check with 3rd party app (e.g., Color Oracle)

From Intro to Viridis:

These color scales are designed to be:

  • Colorful, spanning as wide a palette as possible so as to make differences easy to see,
  • Perceptually uniform, meaning that values close to each other have similar-appearing colors and values far away from each other have more different-appearing colors, consistently across the range of values,
  • Robust to colorblindness, so that the above properties hold true for people with common forms of colorblindness, as well as in grey scale printing, and
  • Pretty, oh so pretty
vizdata %>% 
  ggplot(aes(x=rValX, 
             y=rValY,
             fill=x, 
             size=y, 
             shape=z, # add shape
             colour=z)) +
  geom_point(alpha=.75) + # add transparency
  scale_shape_manual(values=c(21, 22, 23, 24)) + # manually choose which shapes to plot
  scale_fill_viridis(option="plasma") + # choose a better palette
  theme_bw() +
  xlab("Randomized normal distribution of `x`") +
  ylab("Log of the absolute value of a\nrandomized normal distribution of `y`") +
  ggtitle("My cool plot", subtitle = "UKCLC2020")

  1. WAY too busy! Impossible to see any patterns!
  2. Shape and (outline) colour are redundant

1.2.2.4 Round 4

  1. WAY too busy! Impossible to see any patterns! →→ Split plot into facets
vizdata %>% 
  ggplot(aes(x=rValX, 
             y=rValY,
             fill=x, 
             size=y, 
             shape=z,
             colour=z)) +
  geom_point(alpha=.75) + 
  scale_shape_manual(values=c(21, 22, 23, 24)) + 
  scale_fill_viridis(option="plasma") + 
  theme_bw() +
  xlab("Randomized normal distribution of `x`") +
  ylab("Log of the absolute value of a\nrandomized normal distribution of `y`") +
  ggtitle("My cool plot", subtitle = "UKCLC2020") +
  facet_grid(~z) # facet the data by column `z` category

1.3 Priorities of your audience

A picture is worth a thousand words. The purpose of a graph is to support the “story” you are telling about your data. It should be true, but not everything true about it is relevant.

In our vizdata dataset, x and y are meaningful, and as it turns out, rValX and rValY are… random. So those values might not be as relevant to our audience. We will want to visualise them for ourselves as we explore the shape of the data in preparation for analysis, but we don’t necessarily need to include every possible dimension.

Our eyes are more sensitive to spacial location than to colour gradients, so let’s make the colours the less important properties of the data.

Shape and (outline) colour are doubly redundant now that we’re faceting the data by column z category, so we can get rid of those dimensions altogether.

Since the correlation between x and y is known to be important, we can add a smooth to draw our audience’s attention to this property.

vizdata %>% 
  ggplot(aes(x=x, 
             y=y,
             colour=rValX, 
             size=rValY)) +
  geom_point(alpha=.5) + 
  scale_colour_viridis(option="plasma") + # change to 'colour' to fill default point shapes
  geom_smooth(method = "lm", fill=NA, colour="black", lwd=0.5) + #add visually distinctive smooth
  theme_bw() +
  xlab("Column `x` value") +
  ylab("Column `y` value") +
  ggtitle("My cool plot", subtitle = "UKCLC2020") +
  facet_grid(~z)  

This graph crams ALL of the information in our dataset into a single plot. But it’s not necessary to do so. After all, our original plot showing the four categories and their smooths gives us the same relevant information.

vizdata %>% 
  ggplot(aes(x=x, 
             y=y)) +
  geom_point(alpha=.5) + 
  geom_smooth(method = "lm", fill=NA, colour="black", lwd=0.5) + #add visually distinctive smooth
  theme_bw() +
  xlab("Randomized normal distribution of `x`") +
  ylab("Log of the absolute value of a\nrandomized normal distribution of `y`") +
  ggtitle("My cool plot", subtitle = "UKCLC2020") +
  facet_grid(~z)  

Now, this plot has a lot less information that the previous, but it’s very easy to print and definitely has no colourblindness accessibility issues. There are many ways to consider your audience’s priorities.

2 Tidyverse tools

During this time, attendees will learn about how the structure of a dataset can be changed to best suit their analytical purposes. I will then introduce ggplot2 and other plotting methods in R, with a focus on what types of graphs communicate what information.

2.1 Data manipulation

First, let’s load in the dataset you’ll be using for the group challenge.

challenge <- read_csv("data/simulated-data.csv")

You can read more about the dataset and its structure in the next section.

head(challenge)

The basic tools for data manipulation are mutate, filter, group_by/summarise, and , pivot_longer/pivot_wider. There are many, many, others, but these four will get you far.

2.1.1 mutate

To mutate is to create a new column. It’s not the best name for the function, but it gets the job done.

Let’s say we want to turn our subj column into a factor:

challenge %>% 
  mutate(subj = as.factor(subj))

This overwrites the column that’s already named subj.

To create a brand new column, name it something else:

challenge %>% 
  mutate(region.fct = as.factor(region))

2.1.2 filter

This is more straightforward. Just as in acoustics, if you have a low-pass filter, only low frequencies will get through, the filter function lets the defined properties pass through.

challenge %>% 
  filter(word == "the",
         age <= 40,
         rating != 1 &
           rating != 2)

2.1.3 group_by/summarise

This pair of functions lets you perform descriptive statistics on your whole dataframe all at once, based on the groups you define.

challenge %>% 
  group_by(gram, freq) %>% 
  summarise(mean = mean(rt),
            sd = sd(rt),
            se = sd/sqrt(n()))

2.1.4 pivot_longer

We’ll only talk about pivot_longer, since long data is generally better for ggplot than wide data, but they operate on the same principles.

challenge %>% 
  # same code as above
  group_by(gram, freq) %>% 
  summarise(mean = mean(rt),
            sd = sd(rt),
            se = sd/sqrt(n())) %>% 
  # new code
  pivot_longer(cols = c("mean","sd","se"), # cols = 3:5
               names_to = "statistic",
               values_to = "value")

2.2 ggplot2

The ggplot package works by layering plots, as we have seen. Now I’ll step through each basic layer so you can understand what the previous code was doing.

2.2.1 Base plot

The first layer is the base plot. It simply wakes up ggplot, which is now listening for more information to add.

ggplot(data = spdata)


#spdata %>%
#  ggplot()

2.2.2 Aesthetics

The first information to add is what the axes of our plot will be. This sort of information is ‘inherited’ by all following plot layers, so we want to define it here. Because these defined elements influence the visual properties of the plot, they’re called “aesthetics”, abbreviated aes.

ggplot(data = spdata, 
       aes(x=x, 
           y=y,
           colour=z))

These properties will, by default, be inherited by all following layers, when appropriate. You can override this with inherit.aes = FALSE, or you can assign aesthetics specifically to some layers and not others within the geometry layers.

2.2.3 Geometry

We haven’t plotted the data yet because R doesn’t know what form the data should take (e.g., points, bars, box and whisker, histogram, etc). We tell R what geometry (abbreviated geom) to use with a function we add (+) after the base plot.

Here we’re adding two geometries: geom_point to make the scatterplot and geom_smooth to draw the regression lines.

ggplot(data = spdata, 
       aes(x=x, 
           y=y,
           colour=z)) +
  geom_point() +
  geom_smooth(method="lm")

As you can see, geom_smooth has an argument that isn’t within an aes() function. This means it applies to the properties of the layer without regard to the source dataset (spdata). In this case, the method for drawing the regression is “lm”, which is irrelevant to the original dataset, but we can use arguments outside of aes() to “hard-code” properties in the graph as well.

ggplot(data = spdata, 
       aes(x=x, 
           y=y,
           colour=z)) +
  geom_point(shape=15) + # also called "pch" for "point character"
  geom_smooth(method="lm",
              lty="dashed", # lty = "Line TYpe"
              lwd=.5) # lwd = Line WiDth"

2.2.4 Extras

There are a mind-blowing number of extras you can add to your plots, as we saw above. Some are specific to ggplot2 and some are specific to other packages.

ggplot(data = spdata, 
       aes(x=x, 
           y=y,
           colour=z)) +
  geom_point(shape=15) + # also called "pch" for "point character"
  geom_smooth(method="lm",
              lty="dashed", # lty = "Line TYpe"
              lwd=.5, # lwd = Line WiDth"
              fill="pink") + 
  theme_classic() +
  scale_colour_manual(name="Category Z:",
    values = c("red","gold","turquoise","blueviolet")) +
  theme(legend.position = "bottom")

3 Group challenge

For the next 45 minutes, you can work on your own or in a small group to create a graph from the simulated-data.csv file.

This challenge brings together the skills learned in the previous part of the workshop.

When you have finished your graph, save it (or screenshot it) and send me the image file at lauren[DOT]ackerman[AT]ncl[DOT]ac[DOT]uk. Make sure that the file name is something by which I can identify you (e.g., your name or a “team name”).

We will return for the final 20 minutes to look at each others’ creations and compete for the title of DataViz Champion! (I know it’s silly, but it’s also a lot of fun.)

3.1 Simulated data

The “Simulated Data” file is a comma separated value (CSV) file, which R can read in easily, either using the read.csv() function or by clicking “Import dataset”.

This dataset is simulated experimental data for a reading time, rating, and comprehension experiment, which means there are several types of responses. Each column is described below.

  • subj = unique number for each subject or participant
  • age = the age of the participant
  • item = the item number that elicited the given response
  • freq = the categorical frequency of the manipulated region (high or low)
  • gram = whether or not the item is grammatical (yes or no)
  • rating = the subject’s response to rating the acceptability of the item (1-5)
  • accuracy = the subject’s response to a comprehension question about the item (1=correct, 0=incorrect)
  • region = which word number the reading time was measured for
  • word = an example word that might have appeared in the region (simplified across items)
  • rt = how long the subject spent reading the word in the given region

There are two crossed factors, each with two levels: * gram = yes / no * freq = high / low

This means there are four conditions: 1. High frequency grammatical 2. Low frequency grammatical 3. High frequency ungrammatical 4. Low frequency ungrammatical

Sentence: “The old VERB the boat.”

VERB Frequency Grammaticality
man low yes
steer high yes
put high no
stare low no

This experiment doesn’t really ask a question, because it’s not a real experiment. Instead, it sets up a paradigm that will allow you to compare across conditions using three different types of data:

  • continuous data (rt)
  • binomial data (accuracy)
  • ordinal rating data (rating)

Since each item contains five regions, but rating and accuracy data are per item rather than per region, you will have to take a subset of the total dataset in order to properly graph and analyse rating or accuracy data. If you don’t you’ll be graphing or analysing five copies of this data at once.

LS0tCnRpdGxlOiAiSW50cm9kdWN0aW9uIHRvIERhdGF2aXogaW4gUiIKZGF0ZTogIjI1IEp1bHkgMjAyMCwgMDk6MDAtMTI6MDAiCmF1dGhvcjogIkRyIExhdXJlbiBBY2tlcm1hbiIKc3VidGl0bGU6ICJVS0NMQyBQcmUtQ29uZmVyZW5jZSBXb3Jrc2hvcCIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgdGhlbWU6IHNhbmRzdG9uZQogICAgaGlnaGxpZ2h0OiBkZWZhdWx0CiAgICB0b2M6IHRydWUKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBpbmNsdWRlczogCiAgICAgIGluX2hlYWRlcjogZ29vZ2xlX2FuYWx5dGljcy5odG1sCi0tLQoKWW91IHdpbGwgbmVlZCB0byBsb2FkIGluIChhbmQgcG9zc2libHkgaW5zdGFsbCkgdGhlIGZvbGxvd2luZyBwYWNrYWdlczoKCmBgYHtyIHNldHVwLCBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeSh2aXJpZGlzKQpgYGAKCllvdSB3aWxsIGFsc28gbmVlZCB0aGUgZm9sbG93aW5nIGRhdGFzZXRzOiAKCjEuIFtzaW1wc29uLXBhcmFkb3gtZGF0YS5jc3ZdKGRhdGEvc2ltcHNvbi1wYXJhZG94LWRhdGEuY3N2KQoyLiBbYm94cGxvdC1wcm9ibGVtcy1kYXRhLmNzdl0oZGF0YS9ib3hwbG90LXByb2JsZW1zLWRhdGEuY3N2KQozLiBbc2ltdWxhdGVkLWRhdGEuY3N2XShkYXRhL3NpbXVsYXRlZC1kYXRhLmNzdikKCkFuZCBbaGVyZSBpcyB0aGUgLlJtZCBmaWxlXShVS0NMQzIwMjAuUm1kKSB0byBmb2xsb3cgYWxvbmcgd2l0aCBtZS4KCiMgV2VsY29tZSB0byBEYXRhdml6IQoKVmlzdWFsaXNpbmcgZGF0YSBpcyBtb3JlIHRoYW4ganVzdCB0dXJuaW5nIG51bWJlcnMgaW50byBwaWN0dXJlcy4gSXQncyB0ZWxsaW5nIGEgc3RvcnksIGNyZWF0aW5nIGEgbmFycmF0aXZlLCBhbmQgcHJvdmlkaW5nIGFuYWx5c2lzIGFsbCBhdCBvbmNlLiBUbyB2aXN1YWxpc2UgZGF0YSB3ZWxsLCB0aGVyZSBhcmUgYSBmZXcgYXNwZWN0cyB5b3UgbXVzdCBjb25zaWRlcjogCgoxLiBUaGUgc2hhcGUgb2YgdGhlIGRhdGEKMi4gUHJpbmNpcGxlcyBvZiBhZXN0aGV0aWMgY29tcG9zaXRpb24KMy4gUHJpb3JpdGllcyBvZiB5b3VyIGF1ZGllbmNlCgojIyBTaGFwZSBvZiB5b3VyIGRhdGEKClJlYWQgaW4gYSBkYXRhIGZpbGUgdG8gZm9ybSB0aGUgZm9sbG93aW5nIHBsb3RzLgpgYGB7ciByZWFkLXNwZGF0YSwgbWVzc2FnZT1GQUxTRX0Kc3BkYXRhIDwtIHJlYWQuY3N2KCJkYXRhL3NpbXBzb24tcGFyYWRveC1kYXRhLmNzdiIpCmBgYAoKRmlyc3QsIHBsb3QgdGhlIGRhdGEgYXMgc2ltcGx5IGFuZCBpbiBhcyByYXcgYSBmb3JtIGFzIHBvc3NpYmxlIHRvIHNlZSBpZiB0aGVyZSBhcmUgYW55IGNsZWFyIHByb2JsZW1zIG9yIGludGVybmFsIHN0cnVjdHVyZXMuCgpgYGB7ciBzcC1zaW1wbGUtcGxvdH0KcGxvdChzcGRhdGEkeX5zcGRhdGEkeCkKYGBgCgpZb3UgbWF5IGFsc28gd2FudCB0byBsb29rIGF0IHRoZSByYXcgZGF0YSB0byBnZXQgYSBzZW5zZSBvZiBob3cgaXQncyBzdHJ1Y3R1cmVkIG9yIHdoZXRoZXIgdGhlcmUgYXJlIGFueSBtYWpvciBwcm9ibGVtcy4KCmBgYHtyIGhlYWQtc3B9CmhlYWQoc3BkYXRhKQpgYGAKQSBzdW1tYXJ5IG9mIHRoZSBkYXRhIGNhbiBhbHNvIGJlIHVzZWZ1bCBpbiBnZXR0aW5nIGEgc2Vuc2Ugb2YgaXRzIHNoYXBlLgoKYGBge3Igc3VtbWFyeS1zcH0Kc3VtbWFyeShzcGRhdGEpCmBgYAoKV2UgY291bGQgbWFrZSBvdXIgb3duIHN1bW1hcmllcy4KCmBgYHtyIGRldGFpbGVkLXN1bW1hcnktc3B9CnNwZGF0YSAlPiUgCiAgZ3JvdXBfYnkoKSAlPiUgCiAgc3VtbWFyaXNlKG51bWJlcj1uKCksCiAgICAgICAgICAgIHgubWVhbj1tZWFuKHgpLAogICAgICAgICAgICB4LnNkPXNkKHgpLAogICAgICAgICAgICB4LnNlPXguc2Qvc3FydChudW1iZXIpLAogICAgICAgICAgICB5Lm1lYW49bWVhbih5KSwKICAgICAgICAgICAgeS5zZD1zZCh5KSwKICAgICAgICAgICAgeS5zZT15LnNkL3NxcnQobnVtYmVyKSkKYGBgCgpXZSBjb3VsZCBsb29rIGF0IHN0YXRpc3RpY2FsIHRlc3RzLgoKYGBge3IgY29yLnRlc3Qtc3B9CmNvci50ZXN0KHNwZGF0YSR4LCBzcGRhdGEkeSkKYGBgCkFuZCB0aGF0IGNvdWxkIGJlIGl0LgoKSG93ZXZlciwgd2l0aG91dCBkb2luZyBtb3JlIGRldGFpbGVkIGFuZCBwcmluY2lwbGVkIGV4cGxvcmF0aW9uLCB5b3UgbWlnaHQgbWlzcyBzb21ldGhpbmcgaW4geW91ciBkYXRhIHRoYXQgd291bGQgY2F1c2UgYSBbU2ltcHNvbidzIFBhcmFkb3hdKGh0dHBzOi8vdG93YXJkc2RhdGFzY2llbmNlLmNvbS9zaW1wc29ucy1wYXJhZG94LWhvdy10by1wcm92ZS10d28tb3Bwb3NpdGUtYXJndW1lbnRzLXVzaW5nLW9uZS1kYXRhc2V0LTFjOWM5MTdmNWZmOSkuCgpgYGB7ciBzcC1vdmVyYWxsLXNtb290aCwgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpnZ3Bsb3Qoc3BkYXRhLCBhZXMoeD14LCB5PXkpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX3Ntb290aChtZXRob2Q9ImxtIikgKwogIHRoZW1lX2J3KCkKYGBgCkFib3ZlIHdlIHNlZSB3aGF0IGxvb2tzIGxpa2UgYSBjbGVhciBhbmQgc2lnbmlmaWNhbnQgcG9zaXRpdmUgY29ycmVsYXRpb24uIEJ1dCBmcm9tIGxvb2tpbmcgYXQgdGhlIGRhdGEsIHdlIHNob3VsZCBrbm93IHRoYXQgdGhlcmUgaXMgYW5vdGhlciBjb2x1bW4gb2YgaW50ZXJlc3QgdGhhdCBjb3VsZCBhZmZlY3QgdGhlIHNoYXBlLiAoQ29sdW1uIGB6YCwgd2hpY2ggY29udGFpbnMgbGV0dGVycy4pCgpgYGB7ciBzcC1ncm91cGVkLXNtb290aCwgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpnZ3Bsb3Qoc3BkYXRhLCBhZXMoeD14LCB5PXkpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3VyPXopKSArCiAgZ2VvbV9zbW9vdGgoYWVzKGNvbG91cj16KSwgbWV0aG9kPSJsbSIpICsKICB0aGVtZV9idygpCmBgYApUbyBtYWtlIHRoaXMgZXZlbiBtb3JlIGNsZWFyLCB3ZSBjYW4gb3ZlcmxheSB0aGUgZ3JvdXBzJyByZWdyZXNzaW9uIGxpbmVzIHdpdGggdGhlIG92ZXJhbGwgcmVncmVzc2lvbiBsaW5lLgoKYGBge3Igc3AtYWxsLXNtb290aHMsIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZ2dwbG90KHNwZGF0YSwgYWVzKHg9eCwgeT15KSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG91cj16KSkgKwogIGdlb21fc21vb3RoKG1ldGhvZD0ibG0iKSArCiAgZ2VvbV9zbW9vdGgoYWVzKGNvbG91cj16KSwgbWV0aG9kPSJsbSIpICsKICB0aGVtZV9idygpCmBgYAoKRWFjaCBvZiB0aGVzZSB0ZWxscyB1cyBzb21ldGhpbmcgdHJ1ZSwgYnV0IHRoZXkgYXJlbid0IGFsbCBlcXVhbGx5IHVzZWZ1bCBmb3IgaW5mb3JtaW5nIHVzIGFib3V0IHRoZSBzaGFwZSBvZiBvdXIgZGF0YS4KCkZvciBtb3JlIGV4YW1wbGVzOiBbRGF0YXNhdXJ1cyBEb3plbl0oaHR0cHM6Ly93d3cuYXV0b2Rlc2tyZXNlYXJjaC5jb20vcHVibGljYXRpb25zL3NhbWVzdGF0cykKCiMjIyBTaGFwZSBvZiB5b3VyIHZpc3VhbGlzYXRpb24KCkJ5IHRoZSBzYW1lIHRva2VuLCBub3QgZXZlciBncmFwaCBnaXZlcyB5b3UgdGhlIHNhbWUgaW5mb3JtYXRpb24uIAoKUmVhZCBpbiB0aGUgbmV4dCBkYXRhIGZpbGUuCmBgYHtyIHJlYWQtYnBkYXRhfQpicGRhdGEgPC0gcmVhZC5jc3YoImRhdGEvYm94cGxvdC1wcm9ibGVtcy1kYXRhLmNzdiIpCmBgYAoKSWYgd2UgcGxvdCB0aGlzIGRhdGEgdXNpbmcgYSBib3hwbG90LCB3ZSBnZXQgYSBsb3Qgb2YgaW5mb3JtYXRpb24gYWJvdXQgdGhlIG1lZGlhbiBhbmQgcXVhcnRpbGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBkYXRhLgoKYGBge3IgYnAtYm94cGxvdCwgbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQpicGRhdGEgJT4lIAogIGdncGxvdChhZXMoeT15LHg9eixmaWxsPXopKSArCiAgZ2VvbV9ib3hwbG90KCkKYGBgCkJ1dCB0aGVzZSBmb3VyIGNhdGVnb3JpZXMgbG9vayB0byBoYXZlIGFwcHJveGltYXRlbHkgdGhlIHNhbWUgbWVkaWFuLiBUaGUgYGRgIGNhdGVnb3J5IGhhcyBhIG5hcnJvdyBkaXN0cmlidXRpb24sIGJ1dCBgYWAgYW5kIGBiYCBsb29rIHByZXR0eSBzaW1pbGFyIGFuZCBgY2AgaXMgb25seSBzbGlnaHRseSBtb3JlIGJyb2FkbHkgZGlzdHJpYnV0ZWQuIEl0J3MgcG9zc2libGUgdGhhdCB0aGVyZSBhcmUgbm8gc3RhdGlzdGljYWwgZGlmZmVyZW5jZXMgYmV0d2VlbiB0aGVzZSBjYXRlZ29yaWVzIQoKYGBge3IgYm9uZmVycm9uaS1hbHBoYX0KIyBCb25mZXJyb25pLWNvcnJlY3RlZCBhbHBoYSBmb3IgdGhyZWUgY29tcGFyaXNvbnM6CjAuMDUvMwpgYGAKClNvLCBhbnkgcC12YWx1ZXMgYmVsb3cgMC4wMTY3IGNhbiBiZSBjb25zaWRlcmVkIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQuCgpgYGB7ciBicC1sbXN9CmJwZGF0YSAlPiUgIyBBQiwgQUMsIEFECiAgbXV0YXRlKHo9eiAlPiUgYXMuZmFjdG9yKSAlPiUgCiAgbG0oeSB+IHosIGRhdGE9LikgJT4lCiAgc3VtbWFyeSgpICMgQSBpcyBkaWZmZXJlbnQgdG8gRCBhdCBhbHBoYT0wLjA1CgpicGRhdGEgJT4lICMgQkMsIEJECiAgbXV0YXRlKHo9eiAlPiUgYXMuZmFjdG9yLAogICAgICAgICB6PWZjdF9yZWxldmVsKHosICJiIikpICU+JSAKICBsbSh5IH4geiwgZGF0YT0uKSAlPiUKICBzdW1tYXJ5KCkgIyBCIGlzIGRpZmZlcmVudCB0byBEIGF0IGFscGhhPTAuMDUKCmJwZGF0YSAlPiUgIyBDRAogIG11dGF0ZSh6PXogJT4lIGFzLmZhY3RvciwKICAgICAgICAgej1mY3RfcmVsZXZlbCh6LCAiYyIpKSAlPiUgCiAgbG0oeSB+IHosIGRhdGE9LikgJT4lCiAgc3VtbWFyeSgpICMgQWxsIG5zCmBgYAoKV2UndmUgcnVuIHNvbWUgc2ltcGxlIHN0YXRzIGFuZCB0YWtlbiBhIGxvb2sgYXQgdGhlIGRhdGEsIGJ1dCB3ZSBjYW4ndCAqc2VlKiB0aGUgZGlmZmVyZW5jZXMsIGFuZCBpdCdzIG5vdCB2ZXJ5IGNvbnZpbmNpbmcgdGhhdCB0aGV5J3JlIHJlYWxseSB0aGVyZSwgZXNwZWNpYWxseSBhZnRlciBjb3JyZWN0aW5nIGZvciBtdWx0aXBsZSBjb21wYXJpc29ucy4gU28gd2hhdCdzIGdvaW5nIG9uPwoKYGBge3IgYnAtdmlvbGlufQpicGRhdGEgJT4lIAogIGdncGxvdChhZXMoeT15LGZpbGw9eix4PXopKSArCiAgZ2VvbV92aW9saW4oKQpgYGAKT2ggdGhlc2UgYWxsIGhhdmUgZGlmZmVyZW50IHNoYXBlIGRlbnNpdHkgZGlzdHJpYnV0aW9ucyEgV2UgY291bGRuJ3Qgc2VlIHRoYXQgaW4gdGhlIGJveHBsb3QgYmVjYXVzZSBpdCBvbmx5IHNob3dlZCB1cyBpbmZvcm1hdGlvbiBhYm91dCB0aGUgcXVhcnRpbGUgZGlzdHJpYnV0aW9uLgoKQnV0IHdlIHN0aWxsIGRvbid0IGtub3cgZW5vdWdoLiBGb3IgaW5zdGFuY2UsIHdlIGNhbiBzZWUgdGhlcmUgYXJlIGZld2VyIHBvaW50cyBhcm91bmQgMjAwIGluIGNhdGVnb3J5IGBjYCB0aGFuIGluIHRoZSBvdGhlciBjYXRlZ29yaWVzLCBidXQgd2hhdCBkb2VzIHRoaXMgdHJhbnNsYXRlIHRvPyBBcmUgdGhlcmUgbm8gcG9pbnRzIHRoZXJlIG9yIGp1c3QgdmVyeSBmZXc/CgpgYGB7ciBicC12aW9saW4taml0dGVyfQpicGRhdGEgJT4lIAogIGdncGxvdChhZXMoeT15LGZpbGw9eix4PXopKSArCiAgZ2VvbV92aW9saW4oKSArCiAgZ2VvbV9wb2ludChwb3NpdGlvbj1wb3NpdGlvbl9qaXR0ZXIoLjIpLCBhbHBoYT0uNSkKYGBgCgpJdCB0dXJucyBvdXQgdGhhdCBgY2AgaGFzICoqbm8qKiBwb2ludHMgaW4gdGhlIG1pZGRsZSBhbmQgdGhhdCBpdCdzIGEgc3Ryb25nbHkgYmltb2RhbCBkaXN0cmlidXRpb24uIFdoYXQgZG9lcyB1c2luZyBhIHZpb2xpbiBwbG90IGltcGx5IHRvIHlvdXIgYXVkaWVuY2UsIHRoZW4/CgojIyBQcmluY2lwbGVzIG9mIGFlc3RoZXRpYyBjb21wb3NpdGlvbgoKQ3JlYXRlIHRoZSBkYXRhIHNldC4KYGBge3IgY3JlYXRlLXRveWRhdGF9CnRpYmJsZShjKCJhIiwiYSIsImIiLCJiIiksYygiYyIsImQiLCJjIiwiZCIpLGMoMTIxLCAxMjAsIDEyMiwgMTI1KSkgJT4lIAogIHJlbmFtZShmYWN0b3I9ImMoXCJhXCIsIFwiYVwiLCBcImJcIiwgXCJiXCIpIiwKICAgICAgICAgbGV2ZWw9ImMoXCJjXCIsIFwiZFwiLCBcImNcIiwgXCJkXCIpIiwKICAgICAgICAgdmFsdWU9ImMoMTIxLCAxMjAsIDEyMiwgMTI1KSIpIC0+IHRveWRhdGEKYGBgCgojIyMgTGluZXMgYW5kIGF4ZXMKClRoZXJlIGFyZSBtYW55IHRoaW5ncyBiZWluZyBjb21tdW5pY2F0ZWQsIGV2ZW4gaW4gYSBzaW1wbGUgcGxvdCBmb3IgZGF0YXNldCBzdWNoIGFzIHRoaXMuCgpgYGB7ciB0b3ktcG9pbnQtcGF0aH0KdG95ZGF0YSAlPiUgCiAgZ2dwbG90KGFlcyh4PWZhY3Rvcix5PXZhbHVlLGNvbG91cj1sZXZlbCkpICsKICBnZW9tX3BvaW50KCkrCiAgZ2VvbV9wYXRoKGFlcyhncm91cD1sZXZlbCkpCmBgYApXaGF0IGlzIHRoaXMgcGxvdCB0ZWxsaW5nIHVzPyAgCgoqIFRoZXJlIGlzIGFuIGludGVyYWN0aW9uIGJldHdlZW4gYGNgIGFuZCBgZGAsIHdoZXJlYnkgdGhlIGluY3JlYXNlIGZyb20gYGFgIHRvIGBiYCBpcyBsYXJnZXIgZm9yIGBkYCB0aGFuIGZvciBgY2AKKiBUaGlzICptdXN0KiBiZSBhIHdpdGhpbi1zdWJqZWN0cyBhbmFseXNpcywgYXQgbGVhc3Qgd2l0aGluIGVhY2ggb2YgYGNgIGFuZCBgZGAKKiBUaGlzICptdXN0KiBiZSBhIGFuIG9yZGVyLCB3aXRoIGBhYCBjb21pbmcgYmVmb3JlIGBiYCAobGVmdCB0byByaWdodCkKKiBUaGVyZSBpc24ndCBhbiBvcmRlciB3aXRoaW4gYGNgIGFuZCBgZGAsIHNpbmNlIHRoZXkgYXBwZWFyIG92ZXJsYWlkCiogQWxsIHZhbHVlcyBiZXR3ZWVuIDEyMCBhbmQgMTI1IGFyZSBwb3NzaWJsZSB2YWx1ZXMgb25lIG1pZ2h0IG9ic2VydmUgKGNvbnRpbnVvdXMgZGF0YSkKCgpgYGB7ciB0b3ktYmFyfQp0b3lkYXRhICU+JSAKICBnZ3Bsb3QoYWVzKHg9ZmFjdG9yLHk9dmFsdWUsZmlsbD1sZXZlbCkpICsKICBnZW9tX2Jhcihwb3NpdGlvbj0iZG9kZ2UiLHN0YXQ9ImlkZW50aXR5IikKYGBgCgpXaGF0IGlzICp0aGlzKiBwbG90IHRlbGxpbmcgdXM/ICAKCiogVGhlcmUgaXMgdGhlIHBvc3NpYmlsaXR5IGZvciBhIHRpbnkgaW50ZXJhY3Rpb24gYmV0d2VlbiBgY2AgYW5kIGBkYCwgd2hlcmVieSB0aGUgaW5jcmVhc2UgZnJvbSBgYWAgdG8gYGJgIGlzIGxhcmdlciBmb3IgYGRgIHRoYW4gZm9yIGBjYAoqIENvbmRpdGlvbnMgYGFgIGFuZCBgYmAgYXJlIGluZGVwZW5kZW50LCBhcyBhcmUgY29uZGl0aW9ucyBgY2AgYW5kIGBkYAoqIFRoZXNlIGRhdGEgY291bGQgYmUgdW5vcmRlcmVkIGFuZCB0aGVyZSBpcyBubyBuZWVkIGZvciBpdCB0byBiZSB3aXRoaW4gaXRlbXMgb3Igd2l0aGluIHN1YmplY3RzLgoqIEFsbCB2YWx1ZXMgYmV0d2VlbiAwIGFuZCAxMjUgYXJlIHBvc3NpYmxlIHZhbHVlcyBvbmUgbWlnaHQgb2JzZXJ2ZSAoY29udGludW91cyBkYXRhKQoKIyMjIFZpc2liaWxpdHkgey50YWJzZXR9CgpCdXN5bmVzcyBjYW4gY29uZnVzZSBhbmQgbWlzbGVhZCB5b3VyIGF1ZGllbmNlLCBidXQgb3Zlci1zaW1wbGljaXR5IGNhbiBhcyB3ZWxsLiBJbiBvcmRlciB0byBiYWxhbmNlIHByZXNlbnRpbmcgZW5vdWdoIGRhdGEgYWdhaW5zdCBwcmVzZW50aW5nIHRvbyBtdWNoIGRhdGEsIGNvbnNpZGVyIGhvdyBtYW55ICJkaW1lbnNpb25zIiB5b3UgYXJlIHNob3dpbmcsIGFuZCBob3cgbXVjaCBvZiB0aGUgZGF0YSAqYW5kKiBob3cgbWFueSBwbG90IGVsZW1lbnRzIHlvdSBkaXNwbGF5IGF0IG9uY2UuCgpMZXQncyBjcmVhdGUgYSBuZXcgZGF0YXNldCB0byBwbGF5IHdpdGguCmBgYHtyIGNyZWF0ZS12aXpkYXRhfQpzZXQuc2VlZCgzKTsgc3BkYXRhICU+JSAKICBtdXRhdGUoclZhbFggPSBybm9ybSh4KSwKICAgICAgICAgclZhbFkgPSBybm9ybSh5KSAlPiUgYWJzKCkgJT4lIGxvZygpKSAtPiB2aXpkYXRhCmBgYAoKCiMjIyMgUm91bmQgMQoKV2hhdCdzIHdyb25nIHdpdGggdGhpcyBncmFwaD8KCmBgYHtyIGJhZDF9CnZpemRhdGEgJT4lIAogIGdncGxvdChhZXMoeD1yVmFsWCwgCiAgICAgICAgICAgICB5PXJWYWxZLAogICAgICAgICAgICAgY29sb3VyPXopKSArCiAgZ2VvbV9wb2ludCgpCmBgYAoKMS4gVW5saWtlbHkgdG8gcHJpbnQgd2VsbAoyLiBVbmhlbHBmdWwgbGFiZWxzCjMuIE1pc3NpbmcgaW1wb3J0YW50IGluZm9ybWF0aW9uCgojIyMjIFJvdW5kIDIKCjEuIFVubGlrZWx5IHRvIHByaW50IHdlbGwgJiM4NTk0O+KGkiBSZWR1Y2UgZ3JleXMgYW5kIGluY3JlYXNlIGNvbnRyYXN0CjIuIFVuaGVscGZ1bCBsYWJlbHMgJiM4NTk0O+KGkiBBZGQgYXhpcyBsYWJlbHMgYW5kIHRpdGxlCjMuIE1pc3NpbmcgaW1wb3J0YW50IGluZm9ybWF0aW9uICYjODU5NDvihpIgQWRkIG1vcmUgZGltZW5zaW9ucz8gKE9yIG1ha2UgY29tcGFuaW9uIGdyYXBocy4pCgpgYGB7ciB3b3JzZX0Kdml6ZGF0YSAlPiUgCiAgZ2dwbG90KGFlcyh4PXJWYWxYLCAKICAgICAgICAgICAgIHk9clZhbFksCiAgICAgICAgICAgICBmaWxsPXgsICMgYWRkIGRpbWVuc2lvbiBmb3IgY29sdW1uIGB4YAogICAgICAgICAgICAgc2l6ZT15LCAjIGFkZCBkaW1lbnNpb24gZm9yIGNvbHVtbiBgeWAKICAgICAgICAgICAgIGNvbG91cj16KSkgKwogIGdlb21fcG9pbnQoc2hhcGU9MjEpICsgIyBjaGFuZ2UgJ3BvaW50IGNoYXJhY3RlcicgdG8gYWxsb3cgZm9yIGNvbG91ciArIGZpbGwKICBzY2FsZV9maWxsX2dyYWRpZW50bihjb2xvdXJzID0gcmFpbmJvdyg3KSkgKyAjIGFkZCB2aXN1YWxseSBzdHJpa2luZyBncmFkaWVudCBzY2FsZQogIHRoZW1lX2J3KCkgKyAjIG1ha2UgYmFja2dyb3VuZCBtb3JlIHBsYWluCiAgIyBhZGQgdGhlIGxhYmVsczoKICB4bGFiKCJSYW5kb21pemVkIG5vcm1hbCBkaXN0cmlidXRpb24gb2YgYHhgIikgKwogIHlsYWIoIkxvZyBvZiB0aGUgYWJzb2x1dGUgdmFsdWUgb2YgYVxucmFuZG9taXplZCBub3JtYWwgZGlzdHJpYnV0aW9uIG9mIGB5YCIpICsKICBnZ3RpdGxlKCJNeSBjb29sIHBsb3QiLCBzdWJ0aXRsZSA9ICJVS0NMQzIwMjAiKQpgYGAKCjEuIE92ZXJsYXBwaW5nIHBvaW50cyBvYnNjdXJlIGRhdGEKMi4gVG9vIG1hbnkgZGltZW5zaW9ucyBiZWluZyBlbmNvZGVkIGluIHRvby1zaW1pbGFyIHZpc3VhbCBzcGFjZXMKMy4gUGVyY2VwdHVhbGx5IG5vbmxpbmVhciBncmFkaWVudCBzY2FsZQo0LiBDb2xvdXJibGluZC11bmZyaWVuZGx5CgojIyMjIFJvdW5kIDMKCjEuIE92ZXJsYXBwaW5nIHBvaW50cyBvYnNjdXJlIGRhdGEgJiM4NTk0O+KGkiBBZGQgdHJhbnNwYXJlbmN5ICgiYWxwaGEiKQoyLiBUb28gbWFueSBkaW1lbnNpb25zIGJlaW5nIGVuY29kZWQgaW4gdG9vLXNpbWlsYXIgdmlzdWFsIHNwYWNlcyAmIzg1OTQ74oaSIEFkZCAic2hhcGUiIGFzIGEgZGltZW5zaW9uCjMuIFBlcmNlcHR1YWxseSBub25saW5lYXIgZ3JhZGllbnQgc2NhbGUgJiM4NTk0O+KGkiBDaG9vc2UgYSBiZXR0ZXIgcGFsZXR0ZSAoZS5nLiwgYHZpcmlkaXNgKQo0LiBDb2xvdXJibGluZC11bmZyaWVuZGx5ICYjODU5NDvihpIgQ2hvb3NlIGEgYmVzcG9rZSBwYWxldHRlOyBjaGVjayB3aXRoIDNyZCBwYXJ0eSBhcHAgKGUuZy4sIFtDb2xvciBPcmFjbGVdKGh0dHBzOi8vY29sb3JvcmFjbGUub3JnLykpCgpGcm9tIFtJbnRybyB0byBWaXJpZGlzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvdmlyaWRpcy92aWduZXR0ZXMvaW50cm8tdG8tdmlyaWRpcy5odG1sKToKCj4gKlRoZXNlIGNvbG9yIHNjYWxlcyBhcmUgZGVzaWduZWQgdG8gYmU6Kgo+Cj4tICpDb2xvcmZ1bCwgc3Bhbm5pbmcgYXMgd2lkZSBhIHBhbGV0dGUgYXMgcG9zc2libGUgc28gYXMgdG8gbWFrZSBkaWZmZXJlbmNlcyBlYXN5IHRvIHNlZSwqCi0gKlBlcmNlcHR1YWxseSB1bmlmb3JtLCBtZWFuaW5nIHRoYXQgdmFsdWVzIGNsb3NlIHRvIGVhY2ggb3RoZXIgaGF2ZSBzaW1pbGFyLWFwcGVhcmluZyBjb2xvcnMgYW5kIHZhbHVlcyBmYXIgYXdheSBmcm9tIGVhY2ggb3RoZXIgaGF2ZSBtb3JlIGRpZmZlcmVudC1hcHBlYXJpbmcgY29sb3JzLCBjb25zaXN0ZW50bHkgYWNyb3NzIHRoZSByYW5nZSBvZiB2YWx1ZXMsKgotICpSb2J1c3QgdG8gY29sb3JibGluZG5lc3MsIHNvIHRoYXQgdGhlIGFib3ZlIHByb3BlcnRpZXMgaG9sZCB0cnVlIGZvciBwZW9wbGUgd2l0aCBjb21tb24gZm9ybXMgb2YgY29sb3JibGluZG5lc3MsIGFzIHdlbGwgYXMgaW4gZ3JleSBzY2FsZSBwcmludGluZywgYW5kKiAKLSAqUHJldHR5LCBvaCBzbyBwcmV0dHkqCgpgYGB7ciB3b3JzdH0Kdml6ZGF0YSAlPiUgCiAgZ2dwbG90KGFlcyh4PXJWYWxYLCAKICAgICAgICAgICAgIHk9clZhbFksCiAgICAgICAgICAgICBmaWxsPXgsIAogICAgICAgICAgICAgc2l6ZT15LCAKICAgICAgICAgICAgIHNoYXBlPXosICMgYWRkIHNoYXBlCiAgICAgICAgICAgICBjb2xvdXI9eikpICsKICBnZW9tX3BvaW50KGFscGhhPS43NSkgKyAjIGFkZCB0cmFuc3BhcmVuY3kKICBzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzPWMoMjEsIDIyLCAyMywgMjQpKSArICMgbWFudWFsbHkgY2hvb3NlIHdoaWNoIHNoYXBlcyB0byBwbG90CiAgc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbj0icGxhc21hIikgKyAjIGNob29zZSBhIGJldHRlciBwYWxldHRlCiAgdGhlbWVfYncoKSArCiAgeGxhYigiUmFuZG9taXplZCBub3JtYWwgZGlzdHJpYnV0aW9uIG9mIGB4YCIpICsKICB5bGFiKCJMb2cgb2YgdGhlIGFic29sdXRlIHZhbHVlIG9mIGFcbnJhbmRvbWl6ZWQgbm9ybWFsIGRpc3RyaWJ1dGlvbiBvZiBgeWAiKSArCiAgZ2d0aXRsZSgiTXkgY29vbCBwbG90Iiwgc3VidGl0bGUgPSAiVUtDTEMyMDIwIikKYGBgCgoxLiBXQVkgdG9vIGJ1c3khIEltcG9zc2libGUgdG8gc2VlIGFueSBwYXR0ZXJucyEKMi4gU2hhcGUgYW5kIChvdXRsaW5lKSBjb2xvdXIgYXJlIHJlZHVuZGFudAoKIyMjIyBSb3VuZCA0CgoxLiBXQVkgdG9vIGJ1c3khIEltcG9zc2libGUgdG8gc2VlIGFueSBwYXR0ZXJucyEgJiM4NTk0O+KGkiBTcGxpdCBwbG90IGludG8gZmFjZXRzCgpgYGB7ciBnb29kfQp2aXpkYXRhICU+JSAKICBnZ3Bsb3QoYWVzKHg9clZhbFgsIAogICAgICAgICAgICAgeT1yVmFsWSwKICAgICAgICAgICAgIGZpbGw9eCwgCiAgICAgICAgICAgICBzaXplPXksIAogICAgICAgICAgICAgc2hhcGU9eiwKICAgICAgICAgICAgIGNvbG91cj16KSkgKwogIGdlb21fcG9pbnQoYWxwaGE9Ljc1KSArIAogIHNjYWxlX3NoYXBlX21hbnVhbCh2YWx1ZXM9YygyMSwgMjIsIDIzLCAyNCkpICsgCiAgc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbj0icGxhc21hIikgKyAKICB0aGVtZV9idygpICsKICB4bGFiKCJSYW5kb21pemVkIG5vcm1hbCBkaXN0cmlidXRpb24gb2YgYHhgIikgKwogIHlsYWIoIkxvZyBvZiB0aGUgYWJzb2x1dGUgdmFsdWUgb2YgYVxucmFuZG9taXplZCBub3JtYWwgZGlzdHJpYnV0aW9uIG9mIGB5YCIpICsKICBnZ3RpdGxlKCJNeSBjb29sIHBsb3QiLCBzdWJ0aXRsZSA9ICJVS0NMQzIwMjAiKSArCiAgZmFjZXRfZ3JpZCh+eikgIyBmYWNldCB0aGUgZGF0YSBieSBjb2x1bW4gYHpgIGNhdGVnb3J5CmBgYAoKIyMgUHJpb3JpdGllcyBvZiB5b3VyIGF1ZGllbmNlCgpBIHBpY3R1cmUgaXMgd29ydGggYSB0aG91c2FuZCB3b3Jkcy4gVGhlIHB1cnBvc2Ugb2YgYSBncmFwaCBpcyB0byBzdXBwb3J0IHRoZSAic3RvcnkiIHlvdSBhcmUgdGVsbGluZyBhYm91dCB5b3VyIGRhdGEuIEl0IHNob3VsZCBiZSB0cnVlLCBidXQgbm90IGV2ZXJ5dGhpbmcgdHJ1ZSBhYm91dCBpdCBpcyByZWxldmFudC4gCgpJbiBvdXIgYHZpemRhdGFgIGRhdGFzZXQsIGB4YCBhbmQgYHlgIGFyZSBtZWFuaW5nZnVsLCBhbmQgYXMgaXQgdHVybnMgb3V0LCBgclZhbFhgIGFuZCBgclZhbFlgIGFyZS4uLiByYW5kb20uIFNvIHRob3NlIHZhbHVlcyBtaWdodCBub3QgYmUgYXMgcmVsZXZhbnQgdG8gb3VyIGF1ZGllbmNlLiBXZSB3aWxsIHdhbnQgdG8gdmlzdWFsaXNlIHRoZW0gZm9yIG91cnNlbHZlcyBhcyB3ZSBleHBsb3JlIHRoZSBzaGFwZSBvZiB0aGUgZGF0YSBpbiBwcmVwYXJhdGlvbiBmb3IgYW5hbHlzaXMsIGJ1dCB3ZSBkb24ndCBuZWNlc3NhcmlseSBuZWVkIHRvIGluY2x1ZGUgZXZlcnkgcG9zc2libGUgZGltZW5zaW9uLgoKT3VyIGV5ZXMgYXJlIG1vcmUgc2Vuc2l0aXZlIHRvIHNwYWNpYWwgbG9jYXRpb24gdGhhbiB0byBjb2xvdXIgZ3JhZGllbnRzLCBzbyBsZXQncyBtYWtlIHRoZSBjb2xvdXJzIHRoZSBsZXNzIGltcG9ydGFudCBwcm9wZXJ0aWVzIG9mIHRoZSBkYXRhLgoKU2hhcGUgYW5kIChvdXRsaW5lKSBjb2xvdXIgYXJlIGRvdWJseSByZWR1bmRhbnQgbm93IHRoYXQgd2UncmUgZmFjZXRpbmcgdGhlIGRhdGEgYnkgY29sdW1uIGB6YCBjYXRlZ29yeSwgc28gd2UgY2FuIGdldCByaWQgb2YgdGhvc2UgZGltZW5zaW9ucyBhbHRvZ2V0aGVyLgoKU2luY2UgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4geCBhbmQgeSBpcyBrbm93biB0byBiZSBpbXBvcnRhbnQsIHdlIGNhbiBhZGQgYSBzbW9vdGggdG8gZHJhdyBvdXIgYXVkaWVuY2UncyBhdHRlbnRpb24gdG8gdGhpcyBwcm9wZXJ0eS4KCmBgYHtyIGJldHRlciwgbWVzc2FnZT1GQUxTRX0Kdml6ZGF0YSAlPiUgCiAgZ2dwbG90KGFlcyh4PXgsIAogICAgICAgICAgICAgeT15LAogICAgICAgICAgICAgY29sb3VyPXJWYWxYLCAKICAgICAgICAgICAgIHNpemU9clZhbFkpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0uNSkgKyAKICBzY2FsZV9jb2xvdXJfdmlyaWRpcyhvcHRpb249InBsYXNtYSIpICsgIyBjaGFuZ2UgdG8gJ2NvbG91cicgdG8gZmlsbCBkZWZhdWx0IHBvaW50IHNoYXBlcwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIGZpbGw9TkEsIGNvbG91cj0iYmxhY2siLCBsd2Q9MC41KSArICNhZGQgdmlzdWFsbHkgZGlzdGluY3RpdmUgc21vb3RoCiAgdGhlbWVfYncoKSArCiAgeGxhYigiQ29sdW1uIGB4YCB2YWx1ZSIpICsKICB5bGFiKCJDb2x1bW4gYHlgIHZhbHVlIikgKwogIGdndGl0bGUoIk15IGNvb2wgcGxvdCIsIHN1YnRpdGxlID0gIlVLQ0xDMjAyMCIpICsKICBmYWNldF9ncmlkKH56KSAgCmBgYAoKVGhpcyBncmFwaCBjcmFtcyBBTEwgb2YgdGhlIGluZm9ybWF0aW9uIGluIG91ciBkYXRhc2V0IGludG8gYSBzaW5nbGUgcGxvdC4gQnV0IGl0J3Mgbm90IG5lY2Vzc2FyeSB0byBkbyBzby4gQWZ0ZXIgYWxsLCBvdXIgb3JpZ2luYWwgcGxvdCBzaG93aW5nIHRoZSBmb3VyIGNhdGVnb3JpZXMgYW5kIHRoZWlyIHNtb290aHMgZ2l2ZXMgdXMgdGhlIHNhbWUgKnJlbGV2YW50KiBpbmZvcm1hdGlvbi4KCmBgYHtyIGJlc3QsIG1lc3NhZ2U9RkFMU0V9CnZpemRhdGEgJT4lIAogIGdncGxvdChhZXMoeD14LCAKICAgICAgICAgICAgIHk9eSkpICsKICBnZW9tX3BvaW50KGFscGhhPS41KSArIAogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIGZpbGw9TkEsIGNvbG91cj0iYmxhY2siLCBsd2Q9MC41KSArICNhZGQgdmlzdWFsbHkgZGlzdGluY3RpdmUgc21vb3RoCiAgdGhlbWVfYncoKSArCiAgeGxhYigiUmFuZG9taXplZCBub3JtYWwgZGlzdHJpYnV0aW9uIG9mIGB4YCIpICsKICB5bGFiKCJMb2cgb2YgdGhlIGFic29sdXRlIHZhbHVlIG9mIGFcbnJhbmRvbWl6ZWQgbm9ybWFsIGRpc3RyaWJ1dGlvbiBvZiBgeWAiKSArCiAgZ2d0aXRsZSgiTXkgY29vbCBwbG90Iiwgc3VidGl0bGUgPSAiVUtDTEMyMDIwIikgKwogIGZhY2V0X2dyaWQofnopICAKYGBgCgpOb3csIHRoaXMgcGxvdCBoYXMgYSBsb3QgbGVzcyBpbmZvcm1hdGlvbiB0aGF0IHRoZSBwcmV2aW91cywgYnV0IGl0J3MgdmVyeSBlYXN5IHRvIHByaW50IGFuZCBkZWZpbml0ZWx5IGhhcyBubyBjb2xvdXJibGluZG5lc3MgYWNjZXNzaWJpbGl0eSBpc3N1ZXMuIFRoZXJlIGFyZSBtYW55IHdheXMgdG8gY29uc2lkZXIgeW91ciBhdWRpZW5jZSdzIHByaW9yaXRpZXMuCgojIFRpZHl2ZXJzZSB0b29scyAKCkR1cmluZyB0aGlzIHRpbWUsIGF0dGVuZGVlcyB3aWxsIGxlYXJuIGFib3V0IGhvdyB0aGUgc3RydWN0dXJlIG9mIGEgZGF0YXNldCBjYW4gYmUgY2hhbmdlZCB0byBiZXN0IHN1aXQgdGhlaXIgYW5hbHl0aWNhbCBwdXJwb3Nlcy4gCkkgd2lsbCB0aGVuIGludHJvZHVjZSBgZ2dwbG90MmAgYW5kIG90aGVyIHBsb3R0aW5nIG1ldGhvZHMgaW4gUiwgd2l0aCBhIGZvY3VzIG9uIHdoYXQgdHlwZXMgb2YgZ3JhcGhzIGNvbW11bmljYXRlIHdoYXQgaW5mb3JtYXRpb24uIAoKIyMgRGF0YSBtYW5pcHVsYXRpb24KCkZpcnN0LCBsZXQncyBsb2FkIGluIHRoZSBkYXRhc2V0IHlvdSdsbCBiZSB1c2luZyBmb3IgdGhlIGdyb3VwIGNoYWxsZW5nZS4KCmBgYHtyIGNyZWF0ZS1jaGFsbGVuZ2UsIG1lc3NhZ2U9RkFMU0V9CmNoYWxsZW5nZSA8LSByZWFkX2NzdigiZGF0YS9zaW11bGF0ZWQtZGF0YS5jc3YiKQpgYGAKCllvdSBjYW4gcmVhZCBtb3JlIGFib3V0IHRoZSBkYXRhc2V0IGFuZCBpdHMgc3RydWN0dXJlIGluIFt0aGUgbmV4dCBzZWN0aW9uXSgjU2ltdWxhdGVkX2RhdGEpLgoKYGBge3Igdmlldy1jaGFsbGVuZ2V9CmhlYWQoY2hhbGxlbmdlKQpgYGAKClRoZSBiYXNpYyB0b29scyBmb3IgZGF0YSBtYW5pcHVsYXRpb24gYXJlIGBtdXRhdGVgLCBgZmlsdGVyYCwgYGdyb3VwX2J5YC9gc3VtbWFyaXNlYCwgYW5kICwgYHBpdm90X2xvbmdlcmAvYHBpdm90X3dpZGVyYC4gVGhlcmUgYXJlIG1hbnksIG1hbnksIG90aGVycywgYnV0IHRoZXNlIGZvdXIgd2lsbCBnZXQgeW91IGZhci4KCiMjIyBgbXV0YXRlYAoKVG8gbXV0YXRlIGlzIHRvIGNyZWF0ZSBhIG5ldyBjb2x1bW4uIEl0J3Mgbm90IHRoZSBiZXN0IG5hbWUgZm9yIHRoZSBmdW5jdGlvbiwgYnV0IGl0IGdldHMgdGhlIGpvYiBkb25lLgoKTGV0J3Mgc2F5IHdlIHdhbnQgdG8gdHVybiBvdXIgYHN1YmpgIGNvbHVtbiBpbnRvIGEgZmFjdG9yOgpgYGB7ciBtdXRhdGUtZXgxfQpjaGFsbGVuZ2UgJT4lIAogIG11dGF0ZShzdWJqID0gYXMuZmFjdG9yKHN1YmopKQpgYGAKVGhpcyBvdmVyd3JpdGVzIHRoZSBjb2x1bW4gdGhhdCdzIGFscmVhZHkgbmFtZWQgYHN1YmpgLgoKVG8gY3JlYXRlIGEgYnJhbmQgbmV3IGNvbHVtbiwgbmFtZSBpdCBzb21ldGhpbmcgZWxzZToKYGBge3IgbXV0YXRlLWV4Mn0KY2hhbGxlbmdlICU+JSAKICBtdXRhdGUocmVnaW9uLmZjdCA9IGFzLmZhY3RvcihyZWdpb24pKQpgYGAKCiMjIyBgZmlsdGVyYAoKVGhpcyBpcyBtb3JlIHN0cmFpZ2h0Zm9yd2FyZC4gSnVzdCBhcyBpbiBhY291c3RpY3MsIGlmIHlvdSBoYXZlIGEgbG93LXBhc3MgZmlsdGVyLCBvbmx5IGxvdyBmcmVxdWVuY2llcyB3aWxsIGdldCB0aHJvdWdoLCB0aGUgYGZpbHRlcmAgZnVuY3Rpb24gbGV0cyB0aGUgZGVmaW5lZCBwcm9wZXJ0aWVzIHBhc3MgdGhyb3VnaC4KYGBge3IgZmlsdGVyfQpjaGFsbGVuZ2UgJT4lIAogIGZpbHRlcih3b3JkID09ICJ0aGUiLAogICAgICAgICBhZ2UgPD0gNDAsCiAgICAgICAgIHJhdGluZyAhPSAxICYKICAgICAgICAgICByYXRpbmcgIT0gMikKYGBgCgojIyMgYGdyb3VwX2J5YC9gc3VtbWFyaXNlYAoKVGhpcyBwYWlyIG9mIGZ1bmN0aW9ucyBsZXRzIHlvdSBwZXJmb3JtIGRlc2NyaXB0aXZlIHN0YXRpc3RpY3Mgb24geW91ciB3aG9sZSBkYXRhZnJhbWUgYWxsIGF0IG9uY2UsIGJhc2VkIG9uIHRoZSBncm91cHMgeW91IGRlZmluZS4KCmBgYHtyIGdyb3VwLXN1bSwgbWVzc2FnZT1GQUxTRX0KY2hhbGxlbmdlICU+JSAKICBncm91cF9ieShncmFtLCBmcmVxKSAlPiUgCiAgc3VtbWFyaXNlKG1lYW4gPSBtZWFuKHJ0KSwKICAgICAgICAgICAgc2QgPSBzZChydCksCiAgICAgICAgICAgIHNlID0gc2Qvc3FydChuKCkpKQpgYGAKCiMjIyBgcGl2b3RfbG9uZ2VyYAoKV2UnbGwgb25seSB0YWxrIGFib3V0IGBwaXZvdF9sb25nZXJgLCBzaW5jZSBsb25nIGRhdGEgaXMgZ2VuZXJhbGx5IGJldHRlciBmb3IgYGdncGxvdGAgdGhhbiB3aWRlIGRhdGEsIGJ1dCB0aGV5IG9wZXJhdGUgb24gdGhlIHNhbWUgcHJpbmNpcGxlcy4KCmBgYHtyIHBpdm90LCBtZXNzYWdlPUZBTFNFfQpjaGFsbGVuZ2UgJT4lIAogICMgc2FtZSBjb2RlIGFzIGFib3ZlCiAgZ3JvdXBfYnkoZ3JhbSwgZnJlcSkgJT4lIAogIHN1bW1hcmlzZShtZWFuID0gbWVhbihydCksCiAgICAgICAgICAgIHNkID0gc2QocnQpLAogICAgICAgICAgICBzZSA9IHNkL3NxcnQobigpKSkgJT4lIAogICMgbmV3IGNvZGUKICBwaXZvdF9sb25nZXIoY29scyA9IGMoIm1lYW4iLCJzZCIsInNlIiksICMgY29scyA9IDM6NQogICAgICAgICAgICAgICBuYW1lc190byA9ICJzdGF0aXN0aWMiLAogICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAidmFsdWUiKQpgYGAKCiMjIGBnZ3Bsb3QyYAoKVGhlIGBnZ3Bsb3RgIHBhY2thZ2Ugd29ya3MgYnkgbGF5ZXJpbmcgcGxvdHMsIGFzIHdlIGhhdmUgc2Vlbi4gTm93IEknbGwgc3RlcCB0aHJvdWdoIGVhY2ggYmFzaWMgbGF5ZXIgc28geW91IGNhbiB1bmRlcnN0YW5kIHdoYXQgdGhlIHByZXZpb3VzIGNvZGUgd2FzIGRvaW5nLgoKIyMjIEJhc2UgcGxvdAoKVGhlIGZpcnN0IGxheWVyIGlzIHRoZSBiYXNlIHBsb3QuIEl0IHNpbXBseSB3YWtlcyB1cCBgZ2dwbG90YCwgd2hpY2ggaXMgbm93IGxpc3RlbmluZyBmb3IgbW9yZSBpbmZvcm1hdGlvbiB0byBhZGQuCgpgYGB7ciBiYXNlLXBsb3QsIG1lc3NhZ2U9RkFMU0V9CmdncGxvdChkYXRhID0gc3BkYXRhKQoKI3NwZGF0YSAlPiUKIyAgZ2dwbG90KCkKYGBgCgoKIyMjIEFlc3RoZXRpY3MKClRoZSBmaXJzdCBpbmZvcm1hdGlvbiB0byBhZGQgaXMgd2hhdCB0aGUgYXhlcyBvZiBvdXIgcGxvdCB3aWxsIGJlLiBUaGlzIHNvcnQgb2YgaW5mb3JtYXRpb24gaXMgJ2luaGVyaXRlZCcgYnkgYWxsIGZvbGxvd2luZyBwbG90IGxheWVycywgc28gd2Ugd2FudCB0byBkZWZpbmUgaXQgaGVyZS4gQmVjYXVzZSB0aGVzZSBkZWZpbmVkIGVsZW1lbnRzIGluZmx1ZW5jZSB0aGUgdmlzdWFsIHByb3BlcnRpZXMgb2YgdGhlIHBsb3QsIHRoZXkncmUgY2FsbGVkICJhZXN0aGV0aWNzIiwgYWJicmV2aWF0ZWQgYGFlc2AuCgpgYGB7ciBhZXMsIG1lc3NhZ2U9RkFMU0V9CmdncGxvdChkYXRhID0gc3BkYXRhLCAKICAgICAgIGFlcyh4PXgsIAogICAgICAgICAgIHk9eSwKICAgICAgICAgICBjb2xvdXI9eikpCmBgYApUaGVzZSBwcm9wZXJ0aWVzIHdpbGwsIGJ5IGRlZmF1bHQsIGJlIGluaGVyaXRlZCBieSBhbGwgZm9sbG93aW5nIGxheWVycywgd2hlbiBhcHByb3ByaWF0ZS4gWW91IGNhbiBvdmVycmlkZSB0aGlzIHdpdGggYGluaGVyaXQuYWVzID0gRkFMU0VgLCBvciB5b3UgY2FuIGFzc2lnbiBhZXN0aGV0aWNzIHNwZWNpZmljYWxseSB0byBzb21lIGxheWVycyBhbmQgbm90IG90aGVycyB3aXRoaW4gdGhlIGdlb21ldHJ5IGxheWVycy4KCiMjIyBHZW9tZXRyeQoKV2UgaGF2ZW4ndCBwbG90dGVkIHRoZSBkYXRhIHlldCBiZWNhdXNlIFIgZG9lc24ndCBrbm93IHdoYXQgZm9ybSB0aGUgZGF0YSBzaG91bGQgdGFrZSAoZS5nLiwgcG9pbnRzLCBiYXJzLCBib3ggYW5kIHdoaXNrZXIsIGhpc3RvZ3JhbSwgZXRjKS4gV2UgdGVsbCBSIHdoYXQgZ2VvbWV0cnkgKGFiYnJldmlhdGVkIGBnZW9tYCkgdG8gdXNlIHdpdGggYSBmdW5jdGlvbiB3ZSBhZGQgKGArYCkgYWZ0ZXIgdGhlIGJhc2UgcGxvdC4KCkhlcmUgd2UncmUgYWRkaW5nIHR3byBnZW9tZXRyaWVzOiBgZ2VvbV9wb2ludGAgdG8gbWFrZSB0aGUgc2NhdHRlcnBsb3QgYW5kIGBnZW9tX3Ntb290aGAgdG8gZHJhdyB0aGUgcmVncmVzc2lvbiBsaW5lcy4KCmBgYHtyIGdlb20xLCBtZXNzYWdlPUZBTFNFfQpnZ3Bsb3QoZGF0YSA9IHNwZGF0YSwgCiAgICAgICBhZXMoeD14LCAKICAgICAgICAgICB5PXksCiAgICAgICAgICAgY29sb3VyPXopKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX3Ntb290aChtZXRob2Q9ImxtIikKYGBgCgpBcyB5b3UgY2FuIHNlZSwgYGdlb21fc21vb3RoYCBoYXMgYW4gYXJndW1lbnQgdGhhdCBpc24ndCB3aXRoaW4gYW4gYGFlcygpYCBmdW5jdGlvbi4gVGhpcyBtZWFucyBpdCBhcHBsaWVzIHRvIHRoZSBwcm9wZXJ0aWVzIG9mIHRoZSBsYXllciB3aXRob3V0IHJlZ2FyZCB0byB0aGUgc291cmNlIGRhdGFzZXQgKGBzcGRhdGFgKS4gSW4gdGhpcyBjYXNlLCB0aGUgbWV0aG9kIGZvciBkcmF3aW5nIHRoZSByZWdyZXNzaW9uIGlzICJsbSIsIHdoaWNoIGlzIGlycmVsZXZhbnQgdG8gdGhlIG9yaWdpbmFsIGRhdGFzZXQsIGJ1dCB3ZSBjYW4gdXNlIGFyZ3VtZW50cyBvdXRzaWRlIG9mIGBhZXMoKWAgdG8gImhhcmQtY29kZSIgcHJvcGVydGllcyBpbiB0aGUgZ3JhcGggYXMgd2VsbC4KCmBgYHtyIGdlb20yLCBtZXNzYWdlPUZBTFNFfQpnZ3Bsb3QoZGF0YSA9IHNwZGF0YSwgCiAgICAgICBhZXMoeD14LCAKICAgICAgICAgICB5PXksCiAgICAgICAgICAgY29sb3VyPXopKSArCiAgZ2VvbV9wb2ludChzaGFwZT0xNSkgKyAjIGFsc28gY2FsbGVkICJwY2giIGZvciAicG9pbnQgY2hhcmFjdGVyIgogIGdlb21fc21vb3RoKG1ldGhvZD0ibG0iLAogICAgICAgICAgICAgIGx0eT0iZGFzaGVkIiwgIyBsdHkgPSAiTGluZSBUWXBlIgogICAgICAgICAgICAgIGx3ZD0uNSkgIyBsd2QgPSBMaW5lIFdpRHRoIgpgYGAKCiMjIyBFeHRyYXMKClRoZXJlIGFyZSBhIG1pbmQtYmxvd2luZyBudW1iZXIgb2YgZXh0cmFzIHlvdSBjYW4gYWRkIHRvIHlvdXIgcGxvdHMsIGFzIHdlIHNhdyBhYm92ZS4gU29tZSBhcmUgc3BlY2lmaWMgdG8gYGdncGxvdDJgIGFuZCBzb21lIGFyZSBzcGVjaWZpYyB0byBvdGhlciBwYWNrYWdlcy4KCiogW1IgR3JhcGhpY3MgQ29va2Jvb2tdKGh0dHBzOi8vci1ncmFwaGljcy5vcmcvKQoqIFtnZ3Bsb3QyOiBFbGVnYW50IEdyYXBoaWNzIGZvciBEYXRhIEFuYWx5c2lzXShodHRwczovL2dncGxvdDItYm9vay5vcmcvKQoqIFtUaGUgUiBHcmFwaCBHYWxsZXJ5XShodHRwczovL3d3dy5yLWdyYXBoLWdhbGxlcnkuY29tLykKKiBbUGxvdGx5IFIgT3BlbiBTb3VyY2UgR3JhcGhpbmcgTGlicmFyeV0oaHR0cHM6Ly9wbG90bHkuY29tL3IvKQoqIFtZYVJyciEgVGhlIFBpcmF0ZeKAmXMgR3VpZGUgdG8gUl0oaHR0cHM6Ly9ib29rZG93bi5vcmcvbmRwaGlsbGlwcy9ZYVJyci8pCgpgYGB7ciBleHRyYXMsIG1lc3NhZ2U9RkFMU0V9CmdncGxvdChkYXRhID0gc3BkYXRhLCAKICAgICAgIGFlcyh4PXgsIAogICAgICAgICAgIHk9eSwKICAgICAgICAgICBjb2xvdXI9eikpICsKICBnZW9tX3BvaW50KHNoYXBlPTE1KSArICMgYWxzbyBjYWxsZWQgInBjaCIgZm9yICJwb2ludCBjaGFyYWN0ZXIiCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSJsbSIsCiAgICAgICAgICAgICAgbHR5PSJkYXNoZWQiLCAjIGx0eSA9ICJMaW5lIFRZcGUiCiAgICAgICAgICAgICAgbHdkPS41LCAjIGx3ZCA9IExpbmUgV2lEdGgiCiAgICAgICAgICAgICAgZmlsbD0icGluayIpICsgCiAgdGhlbWVfY2xhc3NpYygpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKG5hbWU9IkNhdGVnb3J5IFo6IiwKICAgIHZhbHVlcyA9IGMoInJlZCIsImdvbGQiLCJ0dXJxdW9pc2UiLCJibHVldmlvbGV0IikpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKYGBgCgoKIyBHcm91cCBjaGFsbGVuZ2UKCkZvciB0aGUgbmV4dCA0NSBtaW51dGVzLCB5b3UgY2FuIHdvcmsgb24geW91ciBvd24gb3IgaW4gYSBzbWFsbCBncm91cCB0byBjcmVhdGUgYSBncmFwaCBmcm9tIHRoZSBbc2ltdWxhdGVkLWRhdGEuY3N2XShkYXRhL3NpbXVsYXRlZC1kYXRhLmNzdikgZmlsZS4KClRoaXMgY2hhbGxlbmdlIGJyaW5ncyB0b2dldGhlciB0aGUgc2tpbGxzIGxlYXJuZWQgaW4gdGhlIHByZXZpb3VzIHBhcnQgb2YgdGhlIHdvcmtzaG9wLiAKCldoZW4geW91IGhhdmUgZmluaXNoZWQgeW91ciBncmFwaCwgc2F2ZSBpdCAob3Igc2NyZWVuc2hvdCBpdCkgYW5kIHNlbmQgbWUgdGhlIGltYWdlIGZpbGUgYXQgbGF1cmVuW0RPVF1hY2tlcm1hbltBVF1uY2xbRE9UXWFjW0RPVF11ay4gTWFrZSBzdXJlIHRoYXQgdGhlIGZpbGUgbmFtZSBpcyBzb21ldGhpbmcgYnkgd2hpY2ggSSBjYW4gaWRlbnRpZnkgeW91IChlLmcuLCB5b3VyIG5hbWUgb3IgYSAidGVhbSBuYW1lIikuCgpXZSB3aWxsIHJldHVybiBmb3IgdGhlIGZpbmFsIDIwIG1pbnV0ZXMgdG8gbG9vayBhdCBlYWNoIG90aGVycycgY3JlYXRpb25zIGFuZCBjb21wZXRlIGZvciB0aGUgdGl0bGUgb2YgRGF0YVZpeiBDaGFtcGlvbiEgKEkga25vdyBpdCdzIHNpbGx5LCBidXQgaXQncyBhbHNvIGEgbG90IG9mIGZ1bi4pCgojIyBTaW11bGF0ZWQgZGF0YQoKVGhlICJTaW11bGF0ZWQgRGF0YSIgZmlsZSBpcyBhIGNvbW1hIHNlcGFyYXRlZCB2YWx1ZSAoQ1NWKSBmaWxlLCB3aGljaCBSIGNhbiByZWFkIGluIGVhc2lseSwgZWl0aGVyIHVzaW5nIHRoZSBgcmVhZC5jc3YoKWAgZnVuY3Rpb24gb3IgYnkgY2xpY2tpbmcgIkltcG9ydCBkYXRhc2V0Ii4KClRoaXMgZGF0YXNldCBpcyBzaW11bGF0ZWQgZXhwZXJpbWVudGFsIGRhdGEgZm9yIGEgcmVhZGluZyB0aW1lLCByYXRpbmcsIGFuZCBjb21wcmVoZW5zaW9uIGV4cGVyaW1lbnQsIHdoaWNoIG1lYW5zIHRoZXJlIGFyZSBzZXZlcmFsIHR5cGVzIG9mIHJlc3BvbnNlcy4gRWFjaCBjb2x1bW4gaXMgZGVzY3JpYmVkIGJlbG93LgoKKiBzdWJqID0gdW5pcXVlIG51bWJlciBmb3IgZWFjaCBzdWJqZWN0IG9yIHBhcnRpY2lwYW50CiogYWdlID0gdGhlIGFnZSBvZiB0aGUgcGFydGljaXBhbnQKKiBpdGVtID0gdGhlIGl0ZW0gbnVtYmVyIHRoYXQgZWxpY2l0ZWQgdGhlIGdpdmVuIHJlc3BvbnNlCiogZnJlcSA9IHRoZSBjYXRlZ29yaWNhbCBmcmVxdWVuY3kgb2YgdGhlIG1hbmlwdWxhdGVkIHJlZ2lvbiAoaGlnaCBvciBsb3cpCiogZ3JhbSA9IHdoZXRoZXIgb3Igbm90IHRoZSBpdGVtIGlzIGdyYW1tYXRpY2FsICh5ZXMgb3Igbm8pCiogcmF0aW5nID0gdGhlIHN1YmplY3QncyByZXNwb25zZSB0byByYXRpbmcgdGhlIGFjY2VwdGFiaWxpdHkgb2YgdGhlIGl0ZW0gKDEtNSkKKiBhY2N1cmFjeSA9IHRoZSBzdWJqZWN0J3MgcmVzcG9uc2UgdG8gYSBjb21wcmVoZW5zaW9uIHF1ZXN0aW9uIGFib3V0IHRoZSBpdGVtICgxPWNvcnJlY3QsIDA9aW5jb3JyZWN0KQoqIHJlZ2lvbiA9IHdoaWNoIHdvcmQgbnVtYmVyIHRoZSByZWFkaW5nIHRpbWUgd2FzIG1lYXN1cmVkIGZvcgoqIHdvcmQgPSBhbiBleGFtcGxlIHdvcmQgdGhhdCBtaWdodCBoYXZlIGFwcGVhcmVkIGluIHRoZSByZWdpb24gKHNpbXBsaWZpZWQgYWNyb3NzIGl0ZW1zKQoqIHJ0ID0gaG93IGxvbmcgdGhlIHN1YmplY3Qgc3BlbnQgcmVhZGluZyB0aGUgd29yZCBpbiB0aGUgZ2l2ZW4gcmVnaW9uCgpUaGVyZSBhcmUgdHdvIGNyb3NzZWQgZmFjdG9ycywgZWFjaCB3aXRoIHR3byBsZXZlbHM6CiogZ3JhbSA9IHllcyAvIG5vCiogZnJlcSA9IGhpZ2ggLyBsb3cKClRoaXMgbWVhbnMgdGhlcmUgYXJlIGZvdXIgY29uZGl0aW9uczoKMS4gSGlnaCBmcmVxdWVuY3kgZ3JhbW1hdGljYWwKMi4gTG93IGZyZXF1ZW5jeSBncmFtbWF0aWNhbAozLiBIaWdoIGZyZXF1ZW5jeSB1bmdyYW1tYXRpY2FsCjQuIExvdyBmcmVxdWVuY3kgdW5ncmFtbWF0aWNhbAoKU2VudGVuY2U6ICJUaGUgb2xkIFZFUkIgdGhlIGJvYXQuIgoKfCAgIFZFUkIgICB8IEZyZXF1ZW5jeSB8IEdyYW1tYXRpY2FsaXR5IHwKfC0tLS0tLS0tLS18LS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLXwKfCBtYW4gICAgICB8IGxvdyAgICAgICB8IHllcyB8Cnwgc3RlZXIgICAgfCBoaWdoICAgICAgfCB5ZXMgfAp8IHB1dCAgICAgIHwgaGlnaCAgICAgIHwgbm8gIHwKfCBzdGFyZSAgICB8IGxvdyAgICAgICB8IG5vICB8CgpUaGlzIGV4cGVyaW1lbnQgZG9lc24ndCByZWFsbHkgYXNrIGEgcXVlc3Rpb24sIGJlY2F1c2UgaXQncyBub3QgYSByZWFsIGV4cGVyaW1lbnQuIEluc3RlYWQsIGl0IHNldHMgdXAgYSBwYXJhZGlnbSB0aGF0IHdpbGwgYWxsb3cgeW91IHRvIGNvbXBhcmUgYWNyb3NzIGNvbmRpdGlvbnMgdXNpbmcgdGhyZWUgZGlmZmVyZW50IHR5cGVzIG9mIGRhdGE6CgoqIGNvbnRpbnVvdXMgZGF0YSAocnQpCiogYmlub21pYWwgZGF0YSAoYWNjdXJhY3kpCiogb3JkaW5hbCByYXRpbmcgZGF0YSAocmF0aW5nKQoKU2luY2UgZWFjaCBpdGVtIGNvbnRhaW5zIGZpdmUgcmVnaW9ucywgYnV0IHJhdGluZyBhbmQgYWNjdXJhY3kgZGF0YSBhcmUgcGVyIGl0ZW0gcmF0aGVyIHRoYW4gcGVyIHJlZ2lvbiwgeW91IHdpbGwgaGF2ZSB0byB0YWtlIGEgc3Vic2V0IG9mIHRoZSB0b3RhbCBkYXRhc2V0IGluIG9yZGVyIHRvIHByb3Blcmx5IGdyYXBoIGFuZCBhbmFseXNlIHJhdGluZyBvciBhY2N1cmFjeSBkYXRhLiBJZiB5b3UgZG9uJ3QgeW91J2xsIGJlIGdyYXBoaW5nIG9yIGFuYWx5c2luZyBmaXZlIGNvcGllcyBvZiB0aGlzIGRhdGEgYXQgb25jZS4=