Session 06: Exploratory Data Analysis (EDA) + {ggplot2}

Feedback should be send to goran.milovanovic@datakolektiv.com. These notebooks accompany the Intro to Data Science: Non-Technical Background course 2020/21.


What do we want to do today?

This is the beginning of our journey into Data Science and Analytics in R. Thus far, we have been learning about R programming, more or less treating R as any other programming language. Now we begin to master the real power of R in its areas of specialization: analytics, statistics, machine learning, visualization, and reporting. And as you will see: in statistics, nothing compares to R. Exploratory Data Analysis (EDA) is a concept in Data Analytics/Statistics promoted by John Tukey in the 60s who defined data analysis as:

“Procedures for analyzing data, techniques for interpreting the results of such procedures, ways of planning the gathering of data to make its analysis easier, more precise or more accurate, and all the machinery and results of (mathematical) statistics which apply to analyzing data.”

Source: Exploratory data analysis. (n.d.). In Wikipedia. Retrieved February 03, 2021

EDA is a set of procedures, not strictly standardized, that is used to understand the data at hand before getting engaged in statistical/machine learning, hypothesis testing, and other heavy machinery found in the arsenal of contemporary Data Science. EDA is pretty much a thinking and creative step: it is when hypothesis generation should happen in the process. Before applying a statistical model to our datasets we first need to understand the properties of the data present there, and then only draw a conclusion on what mathematical model exactly should be used to learn more from them. Of course, hypothesis generation does not end in EDA. More often than not, one EDA -> data modeling cycle is followed by additional insights that lead to hypotheses about the data. Then we go back and perform some EDA again, and than perhaps model again, until we find out what is the most useful that can be learned from the present data in respect to the problem that we need to solve. In other words: EDA is found pretty much at the very core of Data Science and Analytics.

Data visualization is used heavily in the EDA phase. Thus, in this Session 06: enter {ggplot2}, the industrial standard in data visualization, and in conjunction with some elements of {dplyr} and [{tidyr}(https://tidyr.tidyverse.org/)], a {tidyverse} packages that suits the EDA step just perfectly.

0. Prerequisits

Install the following packages (if not already installed):

install.packages('tidyverse')

1. Summarise a dataset

1.1 Basic descriptive statistics

We will begin by understanding what is in mtcars, a famous dataset (see mtcars documentation) often used in educational purposes that is built-in R:

data(mtcars)
head(mtcars)

The shape of the mtcars data:

print(paste0("mtcars has ", 
             dim(mtcars)[1], 
             " rows and ", 
             dim(mtcars)[2], 
             " columns."))
[1] "mtcars has 32 rows and 11 columns."

Let’s learn about base R summary() function:

summary(mtcars)
      mpg             cyl             disp             hp             drat             wt       
 Min.   :10.40   Min.   :4.000   Min.   : 71.1   Min.   : 52.0   Min.   :2.760   Min.   :1.513  
 1st Qu.:15.43   1st Qu.:4.000   1st Qu.:120.8   1st Qu.: 96.5   1st Qu.:3.080   1st Qu.:2.581  
 Median :19.20   Median :6.000   Median :196.3   Median :123.0   Median :3.695   Median :3.325  
 Mean   :20.09   Mean   :6.188   Mean   :230.7   Mean   :146.7   Mean   :3.597   Mean   :3.217  
 3rd Qu.:22.80   3rd Qu.:8.000   3rd Qu.:326.0   3rd Qu.:180.0   3rd Qu.:3.920   3rd Qu.:3.610  
 Max.   :33.90   Max.   :8.000   Max.   :472.0   Max.   :335.0   Max.   :4.930   Max.   :5.424  
      qsec             vs               am              gear            carb      
 Min.   :14.50   Min.   :0.0000   Min.   :0.0000   Min.   :3.000   Min.   :1.000  
 1st Qu.:16.89   1st Qu.:0.0000   1st Qu.:0.0000   1st Qu.:3.000   1st Qu.:2.000  
 Median :17.71   Median :0.0000   Median :0.0000   Median :4.000   Median :2.000  
 Mean   :17.85   Mean   :0.4375   Mean   :0.4062   Mean   :3.688   Mean   :2.812  
 3rd Qu.:18.90   3rd Qu.:1.0000   3rd Qu.:1.0000   3rd Qu.:4.000   3rd Qu.:4.000  
 Max.   :22.90   Max.   :1.0000   Max.   :1.0000   Max.   :5.000   Max.   :8.000  

summary() is the simplest possible way to obtain the basic statistics of a dataset in R. As you can see, for each variable in mtcars, we have obtained: (a) its minimum, (b) its maximum, (c) its mean and median as measures of central tendency, (d) the value of Q1 which is the 25th quantile indeed and the value of Q3 which is the 75th quantile (to be explained in the session).

Of course we can ask for any of these statistics for any of the variables present in mtcars separately, e.g.:

mean(mtcars$qsec)
[1] 17.84875
median(mtcars$qsec)
[1] 17.71
min(mtcars$qsec)
[1] 14.5
max(mtcars$qsec)
[1] 22.9

What is the range of mtcars$qsec - the difference between its maximum and minimum?

range(mtcars$qsec)
[1] 14.5 22.9

So the first element is the minimum and the second is the maximum. Then, maybe:

rangeQsec <- abs(Reduce("-", range(mtcars$qsec)))
print(rangeQsec)
[1] 8.4

And abs() is, of course, to obtain the absolute value.

Let’s talk about quantiles now:

quantile(mtcars$qsec, probs = .25)
    25% 
16.8925 
quantile(mtcars$qsec, probs = c(.25, .5, .75))
    25%     50%     75% 
16.8925 17.7100 18.9000 

And the 50% quantile is the median of the mtcars$qsec variable.

1.2 The Boxplot: base R and then {ggplot2}

Numbers and numbers only… Data visualization is way better to understand the data than by just looking at numbers. Let’s begin with the base R function boxplot() to visualize only one variable, mtcars$qsec, and learn what boxplots are for. As you will see, they are among the most helpfull tools to understand the distribution of the values of some measurement in statistics.

boxplot(mtcars$qsec,
        horizontal = TRUE, 
        xlab="qsec",
        col = "darkorange",
        main = "Boxplot: qsec")

The thick line in the box stands where the median of the mtcars$qsec is found. The box is bounded by Q1 (25%) from the left and Q3 (75%) from the right. The width of the box thus equates the IQR - Interquartile Range - which is the difference between Q3 and Q1: IQR = Q3 - Q1. What about the length of the whiskers, and why is there that lonely point to the right marked? That needs some discussion:

## NOTE: Boxplot "fences" and outlier detection
## -----------------------------------------
# Boxplot in R recognizes as outliers those data points that are found beyond OUTTER fences
# Source: http://www.itl.nist.gov/div898/handbook/prc/section1/prc16.htm
# Q3 = 75 percentile, Q1 = 25 percentile
Q3 <- quantile(mtcars$qsec, .75)
Q3
 75% 
18.9 
Q1 <- quantile(mtcars$qsec, .25)
Q1
    25% 
16.8925 
# IQ = Q3 - Q1; Interquartile range
IQR <- unname(Q3 - Q1)
IQR
[1] 2.0075

The definitions of the fences used in R are:

  • Lower inner fence: Q1 - 1.5*IQR
  • Upper inner fence: Q3 + 1.5*IQR
  • Lower outer fence: Q1 - 3*IQR
  • Upper outer fence: Q3 + 3*IQR
  • A point beyond an inner fence on either side is considered a mild outlier
  • A point beyond an outer fence is considered an extreme outlier

Now, let’s find out about the outlier to the right of the boxplot’s whiskers in our plot:

lif <- Q1 - 1.5*IQR
uif <- Q3 + 1.5*IQR
lof <- Q1 - 3*IQR
uof <- Q3 + 3*IQR
mtcars$qsec[mtcars$qsec > uif]
[1] 22.9
mtcars$qsec[mtcars$qsec > uof]
numeric(0)

Conclusion: there is one point in mtcars$qsec that is positioned above the upper inner fence in the boxplot, and that one point represents a mild outlier because it does not go beyond the upper outter fence. There are no outliers bellow lower fences in mtcars$qsec.

We have found one outlier… And that would be your first step on the road to anomaly detection in Data Science!

Now, can we visualize more than one variable in the mtcars dataset and thus try to understand the dataset as a whole, or at least a large part of it? Of course:

boxplot(mtcars[ , c('mpg', 'disp', 'hp', 'drat', 'wt', 'qsec')], 
        horizontal = FALSE, 
        xlab="qsec",
        ylab = "value",
        col = "darkorange",
        main = "Boxplot: qsec")

But the variables seem to be on different scales. What can often help in situations like this one is to use the logarithmic scaling:

boxplot(mtcars[ , c('mpg', 'disp', 'hp', 'drat', 'wt', 'qsec')], 
        horizontal = FALSE, 
        xlab="qsec",
        ylab = "log(value)",
        log = "y",
        col = "indianred",
        main = "Boxplot: qsec")

But is now also difficult to understand the distributions of a particular variable in the boxplot.

While even base R offers great means to visualize data, {ggplot2} is the industrial standard data visualization package and it is definitely way, way better. We will now begin our study of the anatomy of a ggplot2 plot using our boxplot as an example.

In order to produce a boxplot with ggplot2, mtcars first need to be transformed from the wide data representation format into a long data representation format:

library(tidyverse)
mtcars$id <- 1:dim(mtcars)[1]
mtcarsPlot <- mtcars %>%
  select(id, mpg, disp, hp, drat, wt, qsec) %>% 
  pivot_longer(cols = -id,
               names_to = "Measurement",
               values_to = "Value")
head(mtcarsPlot, 30)

Do you understand the difference between long and wide data formats? We will discuss it live in our session! The select() function used in the code above is from the {dplyr} package: it simply selects only the desired columns from a dataframe. Then, the pivot_longer() function, from the {tidyr} package, transform the wide mtcarsinto the long mtcarsPlot following the addition of the id column to mtcars because we want to keep track of the mapping between models (i.e. rows, records) in the original dataset and its long format! The pipe operator - %>% - works in the following way (note: a simplified explanation follows):

  • begin with a data set, pipe it to
  • a transformation, then pipe the result of the transformation to
  • another transformation, then …
  • etc.

Pipes are ideal to chain data transformations in R!

Now, the boxplot with {ggplot2}:

ggplot(data = mtcarsPlot, 
       aes(x = Measurement, 
           y = Value, 
           fill = Measurement)) + 
  geom_boxplot() + 
  ggtitle("mtcars boxplot") + 
  theme_bw() + 
  theme(panel.border = element_blank()) + 
  theme(plot.title = element_text(hjust = .5))

Note. We use + to chain the layers in {ggplot2} visualizations, similarly as we use the pipes to chain data transformations. Do not confuse + as used to build a {ggplot2} visualization with + as an arithmetic operation or %>%.

We will play with various transformations of this boxplot in our session to learn about the basics of the fantastic, powerful {ggplot2} package! For example, let’s produce a scatterplot - another very important visualization in statistics and analytics to learn about - of two variables from mtcars with {ggplot2}, just to see how easy it is:

ggplot(data = mtcars, 
       aes(x = hp, 
           y = qsec)) + 
  geom_point(size = 2, color = "cadetblue3") +
  ggtitle("mtcars: hp vs. qsec") + 
  theme(panel.border = element_blank()) + 
  theme(plot.title = element_text(hjust = .5))

Hypothesis. There is a negative linear relationship between mtcars$hp and mtcars$qsec. Let’s see:

ggplot(data = mtcars, 
       aes(x = hp, 
           y = qsec)) + 
  geom_smooth(method = "lm", size = .25, color = "cadetblue3") + 
  geom_point(size = 2, color = "cadetblue3") +
  ggtitle("mtcars: hp vs. qsec") + 
  theme(panel.border = element_blank()) + 
  theme(plot.title = element_text(hjust = .5))

Can our hypothesis be falsified? We will see in the remainder of the course. Now back to the EDA things! Before that, just another {ggplot2} trick to help you understand your data better…

mtcarsPlot <- mtcars %>% 
  select(hp, qsec)
mtcarsPlot$label <- paste0("(", 
                           mtcarsPlot$hp, ", ", 
                           mtcarsPlot$qsec, 
                           ")")
ggplot(data = mtcarsPlot, 
       aes(x = hp, 
           y = qsec, 
           label = label)) + 
  geom_smooth(method = "lm", size = .25, color = "cadetblue3") + 
  geom_point(size = 2, color = "cadetblue3") +
  geom_text(size = 2) + 
  ggtitle("mtcars: hp vs. qsec") + 
  theme(panel.border = element_blank()) + 
  theme(plot.title = element_text(hjust = .5))

Not readable enough? install.packages(ggrepel). Now,

library(ggrepel)
mtcarsPlot <- mtcars %>% 
  select(hp, qsec)
mtcarsPlot$label <- paste0("(", 
                           mtcarsPlot$hp, ", ", 
                           mtcarsPlot$qsec, 
                           ")")
ggplot(data = mtcarsPlot, 
       aes(x = hp, 
           y = qsec, 
           label = label)) + 
  geom_smooth(method = "lm", size = .25, color = "cadetblue3") + 
  geom_point(size = 1.5, color = "cadetblue3") +
  geom_text_repel(size = 2) + 
  ggtitle("mtcars: hp vs. qsec") + 
  theme(panel.border = element_blank()) + 
  theme(plot.title = element_text(hjust = .5))

2. Distributions and histograms

Q. How is the number of cylinders - mtcars$cyl - distributed across the models in the dataset?

mtcarsPlot <- mtcars %>% 
  select(cyl) %>% 
  group_by(cyl) %>% 
  summarise(count = n())
mtcarsPlot

I have used dplyr::select() to single out only the cyl variable, then grouped the data with dplyr::group_by to be able to aggregate across the distinct values in cyl, and finally used dplyr::summarise() to compute a new variable, count, in mtcarsPlot, by the dplyr::n() function which simply counts the number of elements in each group following dplyr::group_by. The chart:

ggplot(data = mtcarsPlot, 
       aes(x = cyl, 
           y = count)) + 
  geom_bar(stat = "identity", fill = "cadetblue4", width = .5) + 
  ggtitle("Number of cylinders across the models found in mtcars") +
  theme(panel.border = element_blank()) + 
  theme(plot.title = element_text(hjust = .5))

How about the distribution of continuous variables? Remember our Exercises in Session03?

ggplot(mtcars, 
       aes(x = hp, 
           fill = cyl, 
           group = cyl)) + 
  geom_density(alpha = .15, color = "black") + 
  ggtitle("Distrubutions of hp across cyl") + 
  xlab('hp') + 
  ylab('Density') + 
  theme_bw() + 
  theme(panel.border = element_blank()) +
  theme(plot.title = element_text(hjust = .5))

Fine, but… mtcars$cyl needs to become an R factor first!

mtcars$cyl <- as.factor(mtcars$cyl)
ggplot(mtcars, 
       aes(x = hp, 
           fill = cyl, 
           group = cyl)) + 
  geom_density(alpha = .15, color = "black") + 
  ggtitle("Distrubutions of hp across cyl") + 
  xlab('hp') + 
  ylab('Density') + 
  theme_bw() + 
  theme(panel.border = element_blank()) +
  theme(plot.title = element_text(hjust = .5))

The overall distribution of mtcars$hp is:

ggplot(mtcars, 
       aes(x = hp)) + 
  geom_density(alpha = .15, color = "black", fill = "darkorange") + 
  ggtitle("Distrubution of hp in mtcars") + 
  xlab('hp') + 
  ylab('Density') + 
  theme_bw() + 
  theme(panel.border = element_blank()) +
  theme(plot.title = element_text(hjust = .5))

3. Cross-Tabulations and Aggregations

Q. How is the average qsec distributed across cyl and gear in mtcars? Remember table():

distribution <- 
  mtcars %>% 
  select(cyl, gear, qsec) %>% 
  group_by(cyl, gear) %>% 
  summarise(meanQsec = mean(qsec))
print(distribution)

Now, one of my favorite {ggplot2} tricks to visualize a dataset like this one:

ggplot(distribution, 
       aes(x = cyl, 
           y = gear, 
           size = meanQsec, 
           label = meanQsec)) + 
  geom_point(color = "cadetblue3") +
  ggtitle("Mean qsec across cyl and gear in mtcars") + 
  geom_text_repel(size = 3) + 
  theme_bw() + 
  theme(panel.border = element_blank()) +
  theme(plot.title = element_text(hjust = .5))

Or you can use facet_warp() in {ggplot2}:

ggplot(distribution, 
       aes(x = gear, 
           y = meanQsec, 
           label = meanQsec)) + 
  geom_bar(stat = "identity", 
           color = "black", 
           fill = "darkorange") +
  facet_wrap(~cyl) + 
  ggtitle("Mean qsec across cyl and gear in mtcars") + 
  geom_text_repel(size = 3) + 
  theme_bw() + 
  theme(panel.border = element_blank()) +
  theme(plot.title = element_text(hjust = .5))

R Markdown

R Markdown is what I have used to produce this beautiful Notebook. We will learn more about it near the end of the course, but if you already feel ready to dive deep, here’s a book: R Markdown: The Definitive Guide, Yihui Xie, J. J. Allaire, Garrett Grolemunds.


Goran S. Milovanović

DataKolektiv, 2020/21

contact:


License: GPLv3 This Notebook is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This Notebook is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this Notebook. If not, see http://www.gnu.org/licenses/.


LS0tDQp0aXRsZTogSW50cm8gdG8gRGF0YSBTY2llbmNlIChOb24tVGVjaG5pY2FsIEJhY2tncm91bmQsIFIpIC0gU2Vzc2lvbjA2DQphdXRob3I6DQotIG5hbWU6IEdvcmFuIFMuIE1pbG92YW5vdmnEhywgUGhEDQogIGFmZmlsaWF0aW9uOiBEYXRhS29sZWt0aXYsIENoaWVmIFNjaWVudGlzdCAmIE93bmVyOyBEYXRhIFNjaWVudGlzdCBmb3IgV2lraWRhdGEsIFdNREUNCmFic3RyYWN0OiANCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCiAgICB0aGVtZTogc3BhY2VsYWINCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgdG9jX2RlcHRoOiA1DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDUNCi0tLQ0KDQohW10oLi4vX2ltZy9ES19Mb2dvXzEwMC5wbmcpDQoNCioqKg0KIyBTZXNzaW9uIDA2OiBFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzIChFREEpICsge2dncGxvdDJ9DQogDQoqKkZlZWRiYWNrKiogc2hvdWxkIGJlIHNlbmQgdG8gYGdvcmFuLm1pbG92YW5vdmljQGRhdGFrb2xla3Rpdi5jb21gLiANClRoZXNlIG5vdGVib29rcyBhY2NvbXBhbnkgdGhlIEludHJvIHRvIERhdGEgU2NpZW5jZTogTm9uLVRlY2huaWNhbCBCYWNrZ3JvdW5kIGNvdXJzZSAyMDIwLzIxLg0KDQoqKioNCg0KIyMjIFdoYXQgZG8gd2Ugd2FudCB0byBkbyB0b2RheT8NCg0KVGhpcyBpcyB0aGUgYmVnaW5uaW5nIG9mIG91ciBqb3VybmV5IGludG8gRGF0YSBTY2llbmNlIGFuZCBBbmFseXRpY3MgaW4gUi4gVGh1cyBmYXIsIHdlIGhhdmUgYmVlbiBsZWFybmluZyBhYm91dCBSIHByb2dyYW1taW5nLCBtb3JlIG9yIGxlc3MgdHJlYXRpbmcgUiBhcyBhbnkgb3RoZXIgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2UuIE5vdyB3ZSBiZWdpbiB0byBtYXN0ZXIgdGhlIHJlYWwgcG93ZXIgb2YgUiBpbiBpdHMgYXJlYXMgb2Ygc3BlY2lhbGl6YXRpb246IGFuYWx5dGljcywgc3RhdGlzdGljcywgbWFjaGluZSBsZWFybmluZywgdmlzdWFsaXphdGlvbiwgYW5kIHJlcG9ydGluZy4gQW5kIGFzIHlvdSB3aWxsIHNlZTogaW4gc3RhdGlzdGljcywgbm90aGluZyBjb21wYXJlcyB0byBSLg0KRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcyAoRURBKSBpcyBhIGNvbmNlcHQgaW4gRGF0YSBBbmFseXRpY3MvU3RhdGlzdGljcyBwcm9tb3RlZCBieSBKb2huIFR1a2V5IGluIHRoZSA2MHMgd2hvIGRlZmluZWQgKipkYXRhIGFuYWx5c2lzKiogYXM6DQoNCj4gIlByb2NlZHVyZXMgZm9yIGFuYWx5emluZyBkYXRhLCB0ZWNobmlxdWVzIGZvciBpbnRlcnByZXRpbmcgdGhlIHJlc3VsdHMgb2Ygc3VjaCBwcm9jZWR1cmVzLCB3YXlzIG9mIHBsYW5uaW5nIHRoZSBnYXRoZXJpbmcgb2YgZGF0YSB0byBtYWtlIGl0cyBhbmFseXNpcyBlYXNpZXIsIG1vcmUgcHJlY2lzZSBvciBtb3JlIGFjY3VyYXRlLCBhbmQgYWxsIHRoZSBtYWNoaW5lcnkgYW5kIHJlc3VsdHMgb2YgKG1hdGhlbWF0aWNhbCkgc3RhdGlzdGljcyB3aGljaCBhcHBseSB0byBhbmFseXppbmcgZGF0YS4iIA0KDQpbU291cmNlOiBFeHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzLiAobi5kLikuIEluIFdpa2lwZWRpYS4gUmV0cmlldmVkIEZlYnJ1YXJ5IDAzLCAyMDIxXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9FeHBsb3JhdG9yeV9kYXRhX2FuYWx5c2lzKQ0KDQpFREEgaXMgYSBzZXQgb2YgcHJvY2VkdXJlcywgbm90IHN0cmljdGx5IHN0YW5kYXJkaXplZCwgdGhhdCBpcyB1c2VkIHRvICp1bmRlcnN0YW5kKiB0aGUgZGF0YSBhdCBoYW5kIGJlZm9yZSBnZXR0aW5nIGVuZ2FnZWQgaW4gc3RhdGlzdGljYWwvbWFjaGluZSBsZWFybmluZywgaHlwb3RoZXNpcyB0ZXN0aW5nLCBhbmQgb3RoZXIgaGVhdnkgbWFjaGluZXJ5IGZvdW5kIGluIHRoZSBhcnNlbmFsIG9mIGNvbnRlbXBvcmFyeSBEYXRhIFNjaWVuY2UuIEVEQSBpcyBwcmV0dHkgbXVjaCBhIHRoaW5raW5nIGFuZCBjcmVhdGl2ZSBzdGVwOiBpdCBpcyB3aGVuIGh5cG90aGVzaXMgZ2VuZXJhdGlvbiBzaG91bGQgaGFwcGVuIGluIHRoZSBwcm9jZXNzLiBCZWZvcmUgYXBwbHlpbmcgYSBzdGF0aXN0aWNhbCBtb2RlbCB0byBvdXIgZGF0YXNldHMgd2UgZmlyc3QgbmVlZCB0byB1bmRlcnN0YW5kIHRoZSBwcm9wZXJ0aWVzIG9mIHRoZSBkYXRhIHByZXNlbnQgdGhlcmUsIGFuZCB0aGVuIG9ubHkgZHJhdyBhIGNvbmNsdXNpb24gb24gd2hhdCBtYXRoZW1hdGljYWwgbW9kZWwgZXhhY3RseSBzaG91bGQgYmUgdXNlZCB0byBsZWFybiBtb3JlIGZyb20gdGhlbS4gT2YgY291cnNlLCBoeXBvdGhlc2lzIGdlbmVyYXRpb24gZG9lcyBub3QgZW5kIGluIEVEQS4gTW9yZSBvZnRlbiB0aGFuIG5vdCwgb25lICpFREEgLT4gZGF0YSBtb2RlbGluZyBjeWNsZSogaXMgZm9sbG93ZWQgYnkgYWRkaXRpb25hbCBpbnNpZ2h0cyB0aGF0IGxlYWQgdG8gaHlwb3RoZXNlcyBhYm91dCB0aGUgZGF0YS4gVGhlbiB3ZSBnbyBiYWNrIGFuZCBwZXJmb3JtIHNvbWUgRURBIGFnYWluLCBhbmQgdGhhbiBwZXJoYXBzIG1vZGVsIGFnYWluLCB1bnRpbCB3ZSBmaW5kIG91dCB3aGF0IGlzIHRoZSBtb3N0IHVzZWZ1bCB0aGF0IGNhbiBiZSBsZWFybmVkIGZyb20gdGhlIHByZXNlbnQgZGF0YSBpbiByZXNwZWN0IHRvIHRoZSBwcm9ibGVtIHRoYXQgd2UgbmVlZCB0byBzb2x2ZS4gSW4gb3RoZXIgd29yZHM6IEVEQSBpcyBmb3VuZCBwcmV0dHkgbXVjaCBhdCB0aGUgdmVyeSBjb3JlIG9mIERhdGEgU2NpZW5jZSBhbmQgQW5hbHl0aWNzLg0KDQpEYXRhIHZpc3VhbGl6YXRpb24gaXMgdXNlZCBoZWF2aWx5IGluIHRoZSBFREEgcGhhc2UuIFRodXMsIGluIHRoaXMgU2Vzc2lvbiAwNjogZW50ZXIgW3tnZ3Bsb3QyfV0oaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvKSwgdGhlIGluZHVzdHJpYWwgc3RhbmRhcmQgaW4gZGF0YSB2aXN1YWxpemF0aW9uLCBhbmQgaW4gY29uanVuY3Rpb24gd2l0aCBzb21lIGVsZW1lbnRzIG9mIFt7ZHBseXJ9XShodHRwczovL2RwbHlyLnRpZHl2ZXJzZS5vcmcvKSBhbmQgW3t0aWR5cn0oaHR0cHM6Ly90aWR5ci50aWR5dmVyc2Uub3JnLyldLCBhIFt7dGlkeXZlcnNlfV0oaHR0cHM6Ly93d3cudGlkeXZlcnNlLm9yZy8pIHBhY2thZ2VzIHRoYXQgc3VpdHMgdGhlIEVEQSBzdGVwIGp1c3QgcGVyZmVjdGx5Lg0KDQoNCiMjIyAwLiBQcmVyZXF1aXNpdHMNCg0KSW5zdGFsbCB0aGUgZm9sbG93aW5nIHBhY2thZ2VzIChpZiBub3QgYWxyZWFkeSBpbnN0YWxsZWQpOg0KDQpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IEYsIG1lc3NhZ2UgPSBGfQ0KaW5zdGFsbC5wYWNrYWdlcygndGlkeXZlcnNlJykNCmBgYA0KDQojIyMgMS4gU3VtbWFyaXNlIGEgZGF0YXNldA0KDQojIyMjIDEuMSBCYXNpYyBkZXNjcmlwdGl2ZSBzdGF0aXN0aWNzDQoNCldlIHdpbGwgYmVnaW4gYnkgdW5kZXJzdGFuZGluZyB3aGF0IGlzIGluIGBtdGNhcnNgLCBhIGZhbW91cyBkYXRhc2V0IChbc2VlIG10Y2FycyBkb2N1bWVudGF0aW9uXShodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvZGF0YXNldHMvdmVyc2lvbnMvMy42LjIvdG9waWNzL210Y2FycykpIG9mdGVuIHVzZWQgaW4gZWR1Y2F0aW9uYWwgcHVycG9zZXMgdGhhdCBpcyBidWlsdC1pbiBSOg0KDQpgYGB7ciBlY2hvID0gVH0NCmRhdGEobXRjYXJzKQ0KaGVhZChtdGNhcnMpDQpgYGANCg0KVGhlIHNoYXBlIG9mIHRoZSBgbXRjYXJzYCBkYXRhOg0KDQpgYGB7ciBlY2hvID0gVH0NCnByaW50KHBhc3RlMCgibXRjYXJzIGhhcyAiLCANCiAgICAgICAgICAgICBkaW0obXRjYXJzKVsxXSwgDQogICAgICAgICAgICAgIiByb3dzIGFuZCAiLCANCiAgICAgICAgICAgICBkaW0obXRjYXJzKVsyXSwgDQogICAgICAgICAgICAgIiBjb2x1bW5zLiIpKQ0KYGBgDQpMZXQncyBsZWFybiBhYm91dCBiYXNlIFIgYHN1bW1hcnkoKWAgZnVuY3Rpb246DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3VtbWFyeShtdGNhcnMpDQpgYGANCg0KYHN1bW1hcnkoKWAgaXMgdGhlIHNpbXBsZXN0IHBvc3NpYmxlIHdheSB0byBvYnRhaW4gdGhlIGJhc2ljIHN0YXRpc3RpY3Mgb2YgYSBkYXRhc2V0IGluIFIuIEFzIHlvdSBjYW4gc2VlLCBmb3IgZWFjaCB2YXJpYWJsZSBpbiBgbXRjYXJzYCwgd2UgaGF2ZSBvYnRhaW5lZDogKGEpIGl0cyBtaW5pbXVtLCAoYikgaXRzIG1heGltdW0sIChjKSBpdHMgbWVhbiBhbmQgbWVkaWFuIGFzIG1lYXN1cmVzIG9mIGNlbnRyYWwgdGVuZGVuY3ksIChkKSB0aGUgdmFsdWUgb2YgUTEgd2hpY2ggaXMgdGhlIDI1dGggcXVhbnRpbGUgaW5kZWVkIGFuZCB0aGUgdmFsdWUgb2YgUTMgd2hpY2ggaXMgdGhlIDc1dGggcXVhbnRpbGUgKHRvIGJlIGV4cGxhaW5lZCBpbiB0aGUgc2Vzc2lvbikuDQoNCk9mIGNvdXJzZSB3ZSBjYW4gYXNrIGZvciBhbnkgb2YgdGhlc2Ugc3RhdGlzdGljcyBmb3IgYW55IG9mIHRoZSB2YXJpYWJsZXMgcHJlc2VudCBpbiBgbXRjYXJzYCBzZXBhcmF0ZWx5LCBlLmcuOg0KDQpgYGB7ciBlY2hvID0gVH0NCm1lYW4obXRjYXJzJHFzZWMpDQpgYGANCmBgYHtyIGVjaG8gPSBUfQ0KbWVkaWFuKG10Y2FycyRxc2VjKQ0KYGBgDQpgYGB7ciBlY2hvID0gVH0NCm1pbihtdGNhcnMkcXNlYykNCmBgYA0KYGBge3IgZWNobyA9IFR9DQptYXgobXRjYXJzJHFzZWMpDQpgYGANCg0KV2hhdCBpcyB0aGUgcmFuZ2Ugb2YgYG10Y2FycyRxc2VjYCAtIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gaXRzIG1heGltdW0gYW5kIG1pbmltdW0/DQoNCmBgYHtyIGVjaG8gPSBUfQ0KcmFuZ2UobXRjYXJzJHFzZWMpDQpgYGANClNvIHRoZSBmaXJzdCBlbGVtZW50IGlzIHRoZSBtaW5pbXVtIGFuZCB0aGUgc2Vjb25kIGlzIHRoZSBtYXhpbXVtLiBUaGVuLCBtYXliZToNCg0KYGBge3IgZWNobyA9IFR9DQpyYW5nZVFzZWMgPC0gYWJzKFJlZHVjZSgiLSIsIHJhbmdlKG10Y2FycyRxc2VjKSkpDQpwcmludChyYW5nZVFzZWMpDQpgYGANCkFuZCBgYWJzKClgIGlzLCBvZiBjb3Vyc2UsIHRvIG9idGFpbiB0aGUgYWJzb2x1dGUgdmFsdWUuDQoNCkxldCdzIHRhbGsgYWJvdXQgcXVhbnRpbGVzIG5vdzoNCg0KYGBge3IgZWNobyA9IFR9DQpxdWFudGlsZShtdGNhcnMkcXNlYywgcHJvYnMgPSAuMjUpDQpgYGANCmBgYHtyIGVjaG8gPSBUfQ0KcXVhbnRpbGUobXRjYXJzJHFzZWMsIHByb2JzID0gYyguMjUsIC41LCAuNzUpKQ0KYGBgDQpBbmQgdGhlIDUwJSBxdWFudGlsZSBpcyB0aGUgKm1lZGlhbiogb2YgdGhlIGBtdGNhcnMkcXNlY2AgdmFyaWFibGUuDQoNCiMjIyMgMS4yIFRoZSBCb3hwbG90OiBiYXNlIFIgYW5kIHRoZW4ge2dncGxvdDJ9DQoNCk51bWJlcnMgYW5kIG51bWJlcnMgb25seS4uLiBEYXRhIHZpc3VhbGl6YXRpb24gaXMgKndheSBiZXR0ZXIqIHRvIHVuZGVyc3RhbmQgdGhlIGRhdGEgdGhhbiBieSBqdXN0IGxvb2tpbmcgYXQgbnVtYmVycy4gTGV0J3MgYmVnaW4gd2l0aCB0aGUgYmFzZSBSIGZ1bmN0aW9uIGBib3hwbG90KClgIHRvIHZpc3VhbGl6ZSBvbmx5IG9uZSB2YXJpYWJsZSwgYG10Y2FycyRxc2VjYCwgYW5kIGxlYXJuIHdoYXQgYm94cGxvdHMgYXJlIGZvci4gQXMgeW91IHdpbGwgc2VlLCB0aGV5IGFyZSBhbW9uZyB0aGUgbW9zdCBoZWxwZnVsbCB0b29scyB0byB1bmRlcnN0YW5kIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIHZhbHVlcyBvZiBzb21lIG1lYXN1cmVtZW50IGluIHN0YXRpc3RpY3MuDQoNCmBgYHtyIGVjaG8gPSBUfQ0KYm94cGxvdChtdGNhcnMkcXNlYywNCiAgICAgICAgaG9yaXpvbnRhbCA9IFRSVUUsIA0KICAgICAgICB4bGFiPSJxc2VjIiwNCiAgICAgICAgY29sID0gImRhcmtvcmFuZ2UiLA0KICAgICAgICBtYWluID0gIkJveHBsb3Q6IHFzZWMiKQ0KYGBgDQpUaGUgdGhpY2sgbGluZSBpbiB0aGUgYm94IHN0YW5kcyB3aGVyZSB0aGUgKm1lZGlhbiogb2YgdGhlIGBtdGNhcnMkcXNlY2AgaXMgZm91bmQuIFRoZSBib3ggaXMgYm91bmRlZCBieSBRMSAoMjUlKSBmcm9tIHRoZSBsZWZ0IGFuZCBRMyAoNzUlKSBmcm9tIHRoZSByaWdodC4gVGhlIHdpZHRoIG9mIHRoZSBib3ggdGh1cyBlcXVhdGVzIHRoZSAqKklRUioqIC0gSW50ZXJxdWFydGlsZSBSYW5nZSAtIHdoaWNoIGlzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gUTMgYW5kIFExOiBgSVFSID0gUTMgLSBRMWAuIFdoYXQgYWJvdXQgdGhlIGxlbmd0aCBvZiB0aGUgd2hpc2tlcnMsIGFuZCB3aHkgaXMgdGhlcmUgdGhhdCBsb25lbHkgcG9pbnQgdG8gdGhlIHJpZ2h0IG1hcmtlZD8gVGhhdCBuZWVkcyBzb21lIGRpc2N1c3Npb246DQoNCmBgYHtyIGVjaG8gPSBUfQ0KIyMgTk9URTogQm94cGxvdCAiZmVuY2VzIiBhbmQgb3V0bGllciBkZXRlY3Rpb24NCiMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQojIEJveHBsb3QgaW4gUiByZWNvZ25pemVzIGFzIG91dGxpZXJzIHRob3NlIGRhdGEgcG9pbnRzIHRoYXQgYXJlIGZvdW5kIGJleW9uZCBPVVRURVIgZmVuY2VzDQojIFNvdXJjZTogaHR0cDovL3d3dy5pdGwubmlzdC5nb3YvZGl2ODk4L2hhbmRib29rL3ByYy9zZWN0aW9uMS9wcmMxNi5odG0NCiMgUTMgPSA3NSBwZXJjZW50aWxlLCBRMSA9IDI1IHBlcmNlbnRpbGUNClEzIDwtIHF1YW50aWxlKG10Y2FycyRxc2VjLCAuNzUpDQpRMw0KYGBgDQoNCmBgYHtyIGVjaG8gPSBUfQ0KUTEgPC0gcXVhbnRpbGUobXRjYXJzJHFzZWMsIC4yNSkNClExDQpgYGANCg0KYGBge3IgZWNobyA9IFR9DQojIElRID0gUTMgLSBRMTsgSW50ZXJxdWFydGlsZSByYW5nZQ0KSVFSIDwtIHVubmFtZShRMyAtIFExKQ0KSVFSDQpgYGANClRoZSBkZWZpbml0aW9ucyBvZiB0aGUgZmVuY2VzIHVzZWQgaW4gUiBhcmU6DQoNCi0gTG93ZXIgaW5uZXIgZmVuY2U6IGBRMSAtIDEuNSpJUVJgDQotIFVwcGVyIGlubmVyIGZlbmNlOiBgUTMgKyAxLjUqSVFSYA0KLSBMb3dlciBvdXRlciBmZW5jZTogYFExIC0gMypJUVJgDQotIFVwcGVyIG91dGVyIGZlbmNlOiBgUTMgKyAzKklRUmAgDQotIEEgcG9pbnQgYmV5b25kIGFuIGlubmVyIGZlbmNlIG9uIGVpdGhlciBzaWRlIGlzIGNvbnNpZGVyZWQgYSAqKm1pbGQgb3V0bGllcioqDQotIEEgcG9pbnQgYmV5b25kIGFuIG91dGVyIGZlbmNlIGlzIGNvbnNpZGVyZWQgYW4gKipleHRyZW1lIG91dGxpZXIqKg0KDQpOb3csIGxldCdzIGZpbmQgb3V0IGFib3V0IHRoZSBvdXRsaWVyIHRvIHRoZSByaWdodCBvZiB0aGUgYm94cGxvdCdzIHdoaXNrZXJzIGluIG91ciBwbG90Og0KDQpgYGB7ciBlY2hvID0gVH0NCmxpZiA8LSBRMSAtIDEuNSpJUVINCnVpZiA8LSBRMyArIDEuNSpJUVINCmxvZiA8LSBRMSAtIDMqSVFSDQp1b2YgPC0gUTMgKyAzKklRUg0KbXRjYXJzJHFzZWNbbXRjYXJzJHFzZWMgPiB1aWZdDQpgYGANCmBgYHtyIGVjaG8gPSBUfQ0KbXRjYXJzJHFzZWNbbXRjYXJzJHFzZWMgPiB1b2ZdDQpgYGANCkNvbmNsdXNpb246IHRoZXJlIGlzIG9uZSBwb2ludCBpbiBgbXRjYXJzJHFzZWNgIHRoYXQgaXMgcG9zaXRpb25lZCBhYm92ZSB0aGUgKnVwcGVyIGlubmVyIGZlbmNlKiBpbiB0aGUgYm94cGxvdCwgYW5kIHRoYXQgb25lIHBvaW50IHJlcHJlc2VudHMgYSAqbWlsZCBvdXRsaWVyKiBiZWNhdXNlIGl0IGRvZXMgbm90IGdvIGJleW9uZCB0aGUgKnVwcGVyIG91dHRlciBmZW5jZSouIFRoZXJlIGFyZSBubyBvdXRsaWVycyBiZWxsb3cgbG93ZXIgZmVuY2VzIGluIGBtdGNhcnMkcXNlY2AuDQoNCldlIGhhdmUgZm91bmQgb25lIG91dGxpZXIuLi4gQW5kIHRoYXQgd291bGQgYmUgeW91ciBmaXJzdCBzdGVwIG9uIHRoZSByb2FkIHRvIGFub21hbHkgZGV0ZWN0aW9uIGluIERhdGEgU2NpZW5jZSENCg0KTm93LCBjYW4gd2UgdmlzdWFsaXplIG1vcmUgdGhhbiBvbmUgdmFyaWFibGUgaW4gdGhlIGBtdGNhcnNgIGRhdGFzZXQgYW5kIHRodXMgdHJ5IHRvIHVuZGVyc3RhbmQgdGhlIGRhdGFzZXQgYXMgYSB3aG9sZSwgb3IgYXQgbGVhc3QgYSBsYXJnZSBwYXJ0IG9mIGl0PyBPZiBjb3Vyc2U6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KYm94cGxvdChtdGNhcnNbICwgYygnbXBnJywgJ2Rpc3AnLCAnaHAnLCAnZHJhdCcsICd3dCcsICdxc2VjJyldLCANCiAgICAgICAgaG9yaXpvbnRhbCA9IEZBTFNFLCANCiAgICAgICAgeGxhYj0icXNlYyIsDQogICAgICAgIHlsYWIgPSAidmFsdWUiLA0KICAgICAgICBjb2wgPSAiZGFya29yYW5nZSIsDQogICAgICAgIG1haW4gPSAiQm94cGxvdDogcXNlYyIpDQpgYGANCkJ1dCB0aGUgdmFyaWFibGVzIHNlZW0gdG8gYmUgb24gZGlmZmVyZW50IHNjYWxlcy4gV2hhdCBjYW4gb2Z0ZW4gaGVscCBpbiBzaXR1YXRpb25zIGxpa2UgdGhpcyBvbmUgaXMgdG8gdXNlIHRoZSAqbG9nYXJpdGhtaWMgc2NhbGluZyo6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KYm94cGxvdChtdGNhcnNbICwgYygnbXBnJywgJ2Rpc3AnLCAnaHAnLCAnZHJhdCcsICd3dCcsICdxc2VjJyldLCANCiAgICAgICAgaG9yaXpvbnRhbCA9IEZBTFNFLCANCiAgICAgICAgeGxhYj0icXNlYyIsDQogICAgICAgIHlsYWIgPSAibG9nKHZhbHVlKSIsDQogICAgICAgIGxvZyA9ICJ5IiwNCiAgICAgICAgY29sID0gImluZGlhbnJlZCIsDQogICAgICAgIG1haW4gPSAiQm94cGxvdDogcXNlYyIpDQpgYGANCkJ1dCBpcyBub3cgYWxzbyBkaWZmaWN1bHQgdG8gdW5kZXJzdGFuZCB0aGUgZGlzdHJpYnV0aW9ucyBvZiBhIHBhcnRpY3VsYXIgdmFyaWFibGUgaW4gdGhlIGJveHBsb3QuDQoNCldoaWxlIGV2ZW4gYmFzZSBSIG9mZmVycyBncmVhdCBtZWFucyB0byB2aXN1YWxpemUgZGF0YSwge2dncGxvdDJ9IGlzIHRoZSBpbmR1c3RyaWFsIHN0YW5kYXJkIGRhdGEgdmlzdWFsaXphdGlvbiBwYWNrYWdlIGFuZCBpdCBpcyBkZWZpbml0ZWx5IHdheSwgd2F5IGJldHRlci4gV2Ugd2lsbCBub3cgYmVnaW4gb3VyIHN0dWR5IG9mIHRoZSBhbmF0b215IG9mIGEgZ2dwbG90MiBwbG90IHVzaW5nIG91ciBib3hwbG90IGFzIGFuIGV4YW1wbGUuDQoNCkluIG9yZGVyIHRvIHByb2R1Y2UgYSBib3hwbG90IHdpdGggZ2dwbG90MiwgbXRjYXJzIGZpcnN0IG5lZWQgdG8gYmUgdHJhbnNmb3JtZWQgZnJvbSB0aGUgKndpZGUgZGF0YSByZXByZXNlbnRhdGlvbiogZm9ybWF0IGludG8gYSAqbG9uZyBkYXRhIHJlcHJlc2VudGF0aW9uIGZvcm1hdCo6DQoNCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCm10Y2FycyRpZCA8LSAxOmRpbShtdGNhcnMpWzFdDQptdGNhcnNQbG90IDwtIG10Y2FycyAlPiUNCiAgc2VsZWN0KGlkLCBtcGcsIGRpc3AsIGhwLCBkcmF0LCB3dCwgcXNlYykgJT4lIA0KICBwaXZvdF9sb25nZXIoY29scyA9IC1pZCwNCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gIk1lYXN1cmVtZW50IiwNCiAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJWYWx1ZSIpDQpoZWFkKG10Y2Fyc1Bsb3QsIDMwKQ0KYGBgDQoNCkRvIHlvdSB1bmRlcnN0YW5kIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gKipsb25nKiogYW5kICoqd2lkZSoqIGRhdGEgZm9ybWF0cz8gV2Ugd2lsbCBkaXNjdXNzIGl0IGxpdmUgaW4gb3VyIHNlc3Npb24hIFRoZSBgc2VsZWN0KClgIGZ1bmN0aW9uIHVzZWQgaW4gdGhlIGNvZGUgYWJvdmUgaXMgZnJvbSB0aGUgYHtkcGx5cn1gIHBhY2thZ2U6IGl0IHNpbXBseSBzZWxlY3RzIG9ubHkgdGhlIGRlc2lyZWQgY29sdW1ucyBmcm9tIGEgZGF0YWZyYW1lLiBUaGVuLCB0aGUgYHBpdm90X2xvbmdlcigpYCBmdW5jdGlvbiwgZnJvbSB0aGUgYHt0aWR5cn1gIHBhY2thZ2UsIHRyYW5zZm9ybSB0aGUgd2lkZSBgbXRjYXJzYGludG8gdGhlIGxvbmcgYG10Y2Fyc1Bsb3RgIGZvbGxvd2luZyB0aGUgYWRkaXRpb24gb2YgdGhlIGBpZGAgY29sdW1uIHRvIGBtdGNhcnNgIGJlY2F1c2Ugd2Ugd2FudCB0byBrZWVwIHRyYWNrIG9mIHRoZSBtYXBwaW5nIGJldHdlZW4gbW9kZWxzIChpLmUuIHJvd3MsIHJlY29yZHMpIGluIHRoZSBvcmlnaW5hbCBkYXRhc2V0IGFuZCBpdHMgbG9uZyBmb3JtYXQhIFRoZSAqKnBpcGUgb3BlcmF0b3IqKiAtIGAlPiVgIC0gd29ya3MgaW4gdGhlIGZvbGxvd2luZyB3YXkgKG5vdGU6IGEgc2ltcGxpZmllZCBleHBsYW5hdGlvbiBmb2xsb3dzKToNCg0KLSBiZWdpbiB3aXRoIGEgZGF0YSBzZXQsIHBpcGUgaXQgdG8NCi0gYSB0cmFuc2Zvcm1hdGlvbiwgdGhlbiBwaXBlIHRoZSByZXN1bHQgb2YgdGhlIHRyYW5zZm9ybWF0aW9uIHRvDQotIGFub3RoZXIgdHJhbnNmb3JtYXRpb24sIHRoZW4gLi4uDQotIGV0Yy4NCg0KUGlwZXMgYXJlICoqaWRlYWwqKiB0byBjaGFpbiBkYXRhIHRyYW5zZm9ybWF0aW9ucyBpbiBSIQ0KDQpOb3csIHRoZSBib3hwbG90IHdpdGgge2dncGxvdDJ9Og0KDQpgYGB7ciBlY2hvID0gVH0NCmdncGxvdChkYXRhID0gbXRjYXJzUGxvdCwgDQogICAgICAgYWVzKHggPSBNZWFzdXJlbWVudCwgDQogICAgICAgICAgIHkgPSBWYWx1ZSwgDQogICAgICAgICAgIGZpbGwgPSBNZWFzdXJlbWVudCkpICsgDQogIGdlb21fYm94cGxvdCgpICsgDQogIGdndGl0bGUoIm10Y2FycyBib3hwbG90IikgKyANCiAgdGhlbWVfYncoKSArIA0KICB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCkpICsgDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAuNSkpDQpgYGANCioqTm90ZS4qKiBXZSB1c2UgYCtgIHRvIGNoYWluIHRoZSBsYXllcnMgaW4ge2dncGxvdDJ9IHZpc3VhbGl6YXRpb25zLCBzaW1pbGFybHkgYXMgd2UgdXNlIHRoZSBwaXBlcyB0byBjaGFpbiBkYXRhIHRyYW5zZm9ybWF0aW9ucy4gRG8gbm90IGNvbmZ1c2UgYCtgIGFzIHVzZWQgdG8gYnVpbGQgYSB7Z2dwbG90Mn0gdmlzdWFsaXphdGlvbiB3aXRoIGArYCBhcyBhbiBhcml0aG1ldGljIG9wZXJhdGlvbiBvciBgJT4lYC4NCg0KV2Ugd2lsbCBwbGF5IHdpdGggdmFyaW91cyB0cmFuc2Zvcm1hdGlvbnMgb2YgdGhpcyBib3hwbG90IGluIG91ciBzZXNzaW9uIHRvIGxlYXJuIGFib3V0IHRoZSBiYXNpY3Mgb2YgdGhlIGZhbnRhc3RpYywgcG93ZXJmdWwge2dncGxvdDJ9IHBhY2thZ2UhIEZvciBleGFtcGxlLCBsZXQncyBwcm9kdWNlIGEgKipzY2F0dGVycGxvdCoqIC0gYW5vdGhlciB2ZXJ5IGltcG9ydGFudCB2aXN1YWxpemF0aW9uIGluIHN0YXRpc3RpY3MgYW5kIGFuYWx5dGljcyB0byBsZWFybiBhYm91dCAtIG9mIHR3byB2YXJpYWJsZXMgZnJvbSBgbXRjYXJzYCB3aXRoIHtnZ3Bsb3QyfSwganVzdCB0byBzZWUgaG93IGVhc3kgaXQgaXM6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KZ2dwbG90KGRhdGEgPSBtdGNhcnMsIA0KICAgICAgIGFlcyh4ID0gaHAsIA0KICAgICAgICAgICB5ID0gcXNlYykpICsgDQogIGdlb21fcG9pbnQoc2l6ZSA9IDIsIGNvbG9yID0gImNhZGV0Ymx1ZTMiKSArDQogIGdndGl0bGUoIm10Y2FyczogaHAgdnMuIHFzZWMiKSArIA0KICB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCkpICsgDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAuNSkpDQpgYGANCioqSHlwb3RoZXNpcy4qKiBUaGVyZSBpcyBhIG5lZ2F0aXZlIGxpbmVhciByZWxhdGlvbnNoaXAgYmV0d2VlbiBgbXRjYXJzJGhwYCBhbmQgYG10Y2FycyRxc2VjYC4gTGV0J3Mgc2VlOg0KDQpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KZ2dwbG90KGRhdGEgPSBtdGNhcnMsIA0KICAgICAgIGFlcyh4ID0gaHAsIA0KICAgICAgICAgICB5ID0gcXNlYykpICsgDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNpemUgPSAuMjUsIGNvbG9yID0gImNhZGV0Ymx1ZTMiKSArIA0KICBnZW9tX3BvaW50KHNpemUgPSAyLCBjb2xvciA9ICJjYWRldGJsdWUzIikgKw0KICBnZ3RpdGxlKCJtdGNhcnM6IGhwIHZzLiBxc2VjIikgKyANCiAgdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKSArIA0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gLjUpKQ0KYGBgDQpDYW4gb3VyIGh5cG90aGVzaXMgYmUgZmFsc2lmaWVkPyBXZSB3aWxsIHNlZSBpbiB0aGUgcmVtYWluZGVyIG9mIHRoZSBjb3Vyc2UuIE5vdyBiYWNrIHRvIHRoZSBFREEgdGhpbmdzISBCZWZvcmUgdGhhdCwganVzdCBhbm90aGVyIHtnZ3Bsb3QyfSB0cmljayB0byBoZWxwIHlvdSB1bmRlcnN0YW5kIHlvdXIgZGF0YSBiZXR0ZXIuLi4NCg0KYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCm10Y2Fyc1Bsb3QgPC0gbXRjYXJzICU+JSANCiAgc2VsZWN0KGhwLCBxc2VjKQ0KbXRjYXJzUGxvdCRsYWJlbCA8LSBwYXN0ZTAoIigiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG10Y2Fyc1Bsb3QkaHAsICIsICIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgbXRjYXJzUGxvdCRxc2VjLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICIpIikNCmdncGxvdChkYXRhID0gbXRjYXJzUGxvdCwgDQogICAgICAgYWVzKHggPSBocCwgDQogICAgICAgICAgIHkgPSBxc2VjLCANCiAgICAgICAgICAgbGFiZWwgPSBsYWJlbCkpICsgDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNpemUgPSAuMjUsIGNvbG9yID0gImNhZGV0Ymx1ZTMiKSArIA0KICBnZW9tX3BvaW50KHNpemUgPSAyLCBjb2xvciA9ICJjYWRldGJsdWUzIikgKw0KICBnZW9tX3RleHQoc2l6ZSA9IDIpICsgDQogIGdndGl0bGUoIm10Y2FyczogaHAgdnMuIHFzZWMiKSArIA0KICB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCkpICsgDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAuNSkpDQpgYGANCk5vdCByZWFkYWJsZSBlbm91Z2g/IGBpbnN0YWxsLnBhY2thZ2VzKGdncmVwZWwpYC4gTm93LA0KDQpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KbGlicmFyeShnZ3JlcGVsKQ0KbXRjYXJzUGxvdCA8LSBtdGNhcnMgJT4lIA0KICBzZWxlY3QoaHAsIHFzZWMpDQptdGNhcnNQbG90JGxhYmVsIDwtIHBhc3RlMCgiKCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgbXRjYXJzUGxvdCRocCwgIiwgIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICBtdGNhcnNQbG90JHFzZWMsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIikiKQ0KZ2dwbG90KGRhdGEgPSBtdGNhcnNQbG90LCANCiAgICAgICBhZXMoeCA9IGhwLCANCiAgICAgICAgICAgeSA9IHFzZWMsIA0KICAgICAgICAgICBsYWJlbCA9IGxhYmVsKSkgKyANCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2l6ZSA9IC4yNSwgY29sb3IgPSAiY2FkZXRibHVlMyIpICsgDQogIGdlb21fcG9pbnQoc2l6ZSA9IDEuNSwgY29sb3IgPSAiY2FkZXRibHVlMyIpICsNCiAgZ2VvbV90ZXh0X3JlcGVsKHNpemUgPSAyKSArIA0KICBnZ3RpdGxlKCJtdGNhcnM6IGhwIHZzLiBxc2VjIikgKyANCiAgdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKSArIA0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gLjUpKQ0KYGBgDQoNCiMjIyAyLiBEaXN0cmlidXRpb25zIGFuZCBoaXN0b2dyYW1zDQoNCioqUS4qKiBIb3cgaXMgdGhlIG51bWJlciBvZiBjeWxpbmRlcnMgLSBgbXRjYXJzJGN5bGAgLSBkaXN0cmlidXRlZCBhY3Jvc3MgdGhlIG1vZGVscyBpbiB0aGUgZGF0YXNldD8NCg0KYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCm10Y2Fyc1Bsb3QgPC0gbXRjYXJzICU+JSANCiAgc2VsZWN0KGN5bCkgJT4lIA0KICBncm91cF9ieShjeWwpICU+JSANCiAgc3VtbWFyaXNlKGNvdW50ID0gbigpKQ0KbXRjYXJzUGxvdA0KYGBgDQoNCkkgaGF2ZSB1c2VkIGBkcGx5cjo6c2VsZWN0KClgIHRvIHNpbmdsZSBvdXQgb25seSB0aGUgYGN5bGAgdmFyaWFibGUsIHRoZW4gKmdyb3VwZWQqIHRoZSBkYXRhIHdpdGggYGRwbHlyOjpncm91cF9ieWAgdG8gYmUgYWJsZSB0byBhZ2dyZWdhdGUgYWNyb3NzIHRoZSBkaXN0aW5jdCB2YWx1ZXMgaW4gYGN5bGAsIGFuZCBmaW5hbGx5IHVzZWQgYGRwbHlyOjpzdW1tYXJpc2UoKWAgdG8gY29tcHV0ZSBhIG5ldyB2YXJpYWJsZSwgYGNvdW50YCwgaW4gYG10Y2Fyc1Bsb3RgLCBieSB0aGUgYGRwbHlyOjpuKClgIGZ1bmN0aW9uIHdoaWNoIHNpbXBseSBjb3VudHMgdGhlIG51bWJlciBvZiBlbGVtZW50cyBpbiBlYWNoIGdyb3VwIGZvbGxvd2luZyBgZHBseXI6Omdyb3VwX2J5YC4gVGhlIGNoYXJ0Og0KDQpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KZ2dwbG90KGRhdGEgPSBtdGNhcnNQbG90LCANCiAgICAgICBhZXMoeCA9IGN5bCwgDQogICAgICAgICAgIHkgPSBjb3VudCkpICsgDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gImNhZGV0Ymx1ZTQiLCB3aWR0aCA9IC41KSArIA0KICBnZ3RpdGxlKCJOdW1iZXIgb2YgY3lsaW5kZXJzIGFjcm9zcyB0aGUgbW9kZWxzIGZvdW5kIGluIG10Y2FycyIpICsNCiAgdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKSArIA0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gLjUpKQ0KYGBgDQpIb3cgYWJvdXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiBjb250aW51b3VzIHZhcmlhYmxlcz8gUmVtZW1iZXIgb3VyIEV4ZXJjaXNlcyBpbiBTZXNzaW9uMDM/DQoNCmBgYHtyIGVjaG89VFJVRX0NCmdncGxvdChtdGNhcnMsIA0KICAgICAgIGFlcyh4ID0gaHAsIA0KICAgICAgICAgICBmaWxsID0gY3lsLCANCiAgICAgICAgICAgZ3JvdXAgPSBjeWwpKSArIA0KICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAuMTUsIGNvbG9yID0gImJsYWNrIikgKyANCiAgZ2d0aXRsZSgiRGlzdHJ1YnV0aW9ucyBvZiBocCBhY3Jvc3MgY3lsIikgKyANCiAgeGxhYignaHAnKSArIA0KICB5bGFiKCdEZW5zaXR5JykgKyANCiAgdGhlbWVfYncoKSArIA0KICB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCkpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IC41KSkNCmBgYA0KRmluZSwgYnV0Li4uIGBtdGNhcnMkY3lsYCBuZWVkcyB0byBiZWNvbWUgYW4gUiAqKmZhY3RvcioqIGZpcnN0IQ0KDQpgYGB7ciBlY2hvPVRSVUV9DQptdGNhcnMkY3lsIDwtIGFzLmZhY3RvcihtdGNhcnMkY3lsKQ0KZ2dwbG90KG10Y2FycywgDQogICAgICAgYWVzKHggPSBocCwgDQogICAgICAgICAgIGZpbGwgPSBjeWwsIA0KICAgICAgICAgICBncm91cCA9IGN5bCkpICsgDQogIGdlb21fZGVuc2l0eShhbHBoYSA9IC4xNSwgY29sb3IgPSAiYmxhY2siKSArIA0KICBnZ3RpdGxlKCJEaXN0cnVidXRpb25zIG9mIGhwIGFjcm9zcyBjeWwiKSArIA0KICB4bGFiKCdocCcpICsgDQogIHlsYWIoJ0RlbnNpdHknKSArIA0KICB0aGVtZV9idygpICsgDQogIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gLjUpKQ0KYGBgDQpUaGUgb3ZlcmFsbCBkaXN0cmlidXRpb24gb2YgYG10Y2FycyRocGAgaXM6DQoNCmBgYHtyIGVjaG89VFJVRX0NCmdncGxvdChtdGNhcnMsIA0KICAgICAgIGFlcyh4ID0gaHApKSArIA0KICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAuMTUsIGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJkYXJrb3JhbmdlIikgKyANCiAgZ2d0aXRsZSgiRGlzdHJ1YnV0aW9uIG9mIGhwIGluIG10Y2FycyIpICsgDQogIHhsYWIoJ2hwJykgKyANCiAgeWxhYignRGVuc2l0eScpICsgDQogIHRoZW1lX2J3KCkgKyANCiAgdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAuNSkpDQpgYGANCiMjIyAzLiBDcm9zcy1UYWJ1bGF0aW9ucyBhbmQgQWdncmVnYXRpb25zDQoNCioqUS4qKiBIb3cgaXMgdGhlIGF2ZXJhZ2UgYHFzZWNgIGRpc3RyaWJ1dGVkIGFjcm9zcyBgY3lsYCBhbmQgYGdlYXJgIGluIGBtdGNhcnNgPyBSZW1lbWJlciBgdGFibGUoKWA6DQoNCmBgYHtyIGVjaG8gPSBUUlVFLCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9DQpkaXN0cmlidXRpb24gPC0gDQogIG10Y2FycyAlPiUgDQogIHNlbGVjdChjeWwsIGdlYXIsIHFzZWMpICU+JSANCiAgZ3JvdXBfYnkoY3lsLCBnZWFyKSAlPiUgDQogIHN1bW1hcmlzZShtZWFuUXNlYyA9IG1lYW4ocXNlYykpDQpwcmludChkaXN0cmlidXRpb24pDQpgYGANCk5vdywgb25lIG9mIG15IGZhdm9yaXRlIHtnZ3Bsb3QyfSB0cmlja3MgdG8gdmlzdWFsaXplIGEgZGF0YXNldCBsaWtlIHRoaXMgb25lOg0KDQpgYGB7ciBlY2hvPVRSVUV9DQpnZ3Bsb3QoZGlzdHJpYnV0aW9uLCANCiAgICAgICBhZXMoeCA9IGN5bCwgDQogICAgICAgICAgIHkgPSBnZWFyLCANCiAgICAgICAgICAgc2l6ZSA9IG1lYW5Rc2VjLCANCiAgICAgICAgICAgbGFiZWwgPSBtZWFuUXNlYykpICsgDQogIGdlb21fcG9pbnQoY29sb3IgPSAiY2FkZXRibHVlMyIpICsNCiAgZ2d0aXRsZSgiTWVhbiBxc2VjIGFjcm9zcyBjeWwgYW5kIGdlYXIgaW4gbXRjYXJzIikgKyANCiAgZ2VvbV90ZXh0X3JlcGVsKHNpemUgPSAzKSArIA0KICB0aGVtZV9idygpICsgDQogIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gLjUpKQ0KYGBgDQpPciB5b3UgY2FuIHVzZSBgZmFjZXRfd2FycCgpYCBpbiB7Z2dwbG90Mn06DQoNCmBgYHtyIGVjaG89VFJVRX0NCmdncGxvdChkaXN0cmlidXRpb24sIA0KICAgICAgIGFlcyh4ID0gZ2VhciwgDQogICAgICAgICAgIHkgPSBtZWFuUXNlYywgDQogICAgICAgICAgIGxhYmVsID0gbWVhblFzZWMpKSArIA0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgDQogICAgICAgICAgIGNvbG9yID0gImJsYWNrIiwgDQogICAgICAgICAgIGZpbGwgPSAiZGFya29yYW5nZSIpICsNCiAgZmFjZXRfd3JhcCh+Y3lsKSArIA0KICBnZ3RpdGxlKCJNZWFuIHFzZWMgYWNyb3NzIGN5bCBhbmQgZ2VhciBpbiBtdGNhcnMiKSArIA0KICBnZW9tX3RleHRfcmVwZWwoc2l6ZSA9IDMpICsgDQogIHRoZW1lX2J3KCkgKyANCiAgdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAuNSkpDQpgYGANCg0KIyMjIEZ1cnRoZXIgUmVhZGluZ3MNCg0KLSBbQ2hhcHRlciA3LCBFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzLCBmcm9tIGZyb20gUiBmb3IgRGF0YSBTY2llbmNlLCBIYWRsZXkgV2lja2hhbSAmIEdhcnJldHQgR3JvbGVtdW5kXShodHRwczovL3I0ZHMuaGFkLmNvLm56L2V4cGxvcmF0b3J5LWRhdGEtYW5hbHlzaXMuaHRtbCkNCi0gRXZlcnl0aGluZyBmcm9tIFtDaGFwdGVyIDcsIEV4cGxvcmF0b3J5IERhdGEgQW5hbHlzaXMsIGZyb20gZnJvbSBSIGZvciBEYXRhIFNjaWVuY2UsIEhhZGxleSBXaWNraGFtICYgR2FycmV0dCBHcm9sZW11bmRdKGh0dHBzOi8vcjRkcy5oYWQuY28ubnopIG9uIHtnZ3Bsb3QyfS4NCg0KIyMjIFIgTWFya2Rvd24NCg0KW1IgTWFya2Rvd25dKGh0dHBzOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tLykgaXMgd2hhdCBJIGhhdmUgdXNlZCB0byBwcm9kdWNlIHRoaXMgYmVhdXRpZnVsIE5vdGVib29rLiBXZSB3aWxsIGxlYXJuIG1vcmUgYWJvdXQgaXQgbmVhciB0aGUgZW5kIG9mIHRoZSBjb3Vyc2UsIGJ1dCBpZiB5b3UgYWxyZWFkeSBmZWVsIHJlYWR5IHRvIGRpdmUgZGVlcCwgaGVyZSdzIGEgYm9vazogW1IgTWFya2Rvd246IFRoZSBEZWZpbml0aXZlIEd1aWRlLCBZaWh1aSBYaWUsIEouIEouIEFsbGFpcmUsIEdhcnJldHQgR3JvbGVtdW5kcy5dKGh0dHBzOi8vYm9va2Rvd24ub3JnL3lpaHVpL3JtYXJrZG93bi8pIA0KDQoNCioqKg0KR29yYW4gUy4gTWlsb3Zhbm92acSHDQoNCkRhdGFLb2xla3RpdiwgMjAyMC8yMQ0KDQpjb250YWN0OiBnb3Jhbi5taWxvdmFub3ZpY0BkYXRha29sZWt0aXYuY29tDQoNCiFbXSguLi9faW1nL0RLX0xvZ29fMTAwLnBuZykNCg0KKioqDQpMaWNlbnNlOiBbR1BMdjNdKGh0dHA6Ly93d3cuZ251Lm9yZy9saWNlbnNlcy9ncGwtMy4wLnR4dCkNClRoaXMgTm90ZWJvb2sgaXMgZnJlZSBzb2Z0d2FyZTogeW91IGNhbiByZWRpc3RyaWJ1dGUgaXQgYW5kL29yIG1vZGlmeSBpdCB1bmRlciB0aGUgdGVybXMgb2YgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGFzIHB1Ymxpc2hlZCBieSB0aGUgRnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9uLCBlaXRoZXIgdmVyc2lvbiAzIG9mIHRoZSBMaWNlbnNlLCBvciAoYXQgeW91ciBvcHRpb24pIGFueSBsYXRlciB2ZXJzaW9uLg0KVGhpcyBOb3RlYm9vayBpcyBkaXN0cmlidXRlZCBpbiB0aGUgaG9wZSB0aGF0IGl0IHdpbGwgYmUgdXNlZnVsLCBidXQgV0lUSE9VVCBBTlkgV0FSUkFOVFk7IHdpdGhvdXQgZXZlbiB0aGUgaW1wbGllZCB3YXJyYW50eSBvZiBNRVJDSEFOVEFCSUxJVFkgb3IgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UuICBTZWUgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGZvciBtb3JlIGRldGFpbHMuDQpZb3Ugc2hvdWxkIGhhdmUgcmVjZWl2ZWQgYSBjb3B5IG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBhbG9uZyB3aXRoIHRoaXMgTm90ZWJvb2suIElmIG5vdCwgc2VlIDxodHRwOi8vd3d3LmdudS5vcmcvbGljZW5zZXMvPi4NCg0KKioqDQoNCg==