Session 10: Working with a local RDBS from {dplyr} and {DBI} + t-test for unpaired samples

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?

Serious data wrangling with {dplyr}. What Relational Databases (RDBS) are and how do we connect to them? Prerequisites: installing MariaDB on your local machine. A crash course in SQL: so similar to {dplyr}.

0. Prerequisits

  1. Download MariaDB: pick version 10.5.9 from the current stable series: use this link to download the Maria DB MSI package for Windows 10.

  2. Double-click mariadb-10.5.9-winx64 once the download is finished.

  3. Follow the instructions here: Installing MariaDB MSI Packages on Windows once the installation process begins. Just go for the defaults, e.g. leave the Database instance feature selected to create a database instance. Please do read the instructions carefully. Write down or memorize your root password; Options: do use UTF8 as default server’s character set when asked; Uncheck: Enable access from remote machines for ‘root’ user; Install as service: yes, Enable networking: yes, leave the TCP port as is.

  4. Install.

  5. Install {dbplyr}: install.packages("dbplyr").

  6. Install {RMariaDB}: install.packages("RMariaDB").

  7. Open MySQL Client. You will be prompted for a root password.

  8. Create database: CREATE DATABASE datakolektiv;

  9. Create user: CREATE USER <YourUsername>@localhost IDENTIFIED BY '<YourPassword>'; (NOTE. This is a new password that you create for the new user, not the root password).

  10. Grant all rights to user: GRANT ALL PRIVILEGES ON *.* TO <YourUsername>@localhost;.

  11. Activate privileges: flush privileges;.

  12. Exit: exit.

  13. Install nycflights13: install.packages('nycflights13')

library(tidyverse)
library(dbplyr)
library(RMariaDB)

1. Working with a local RDBS from {DBI}/{dplyr}/{dbplyr}

1.1 {DBI} and {RMariaDB}

{DBI} is a package to work with various databases systems from R. The essential advantage of {DBI} is that it provides a unified interface to work with different database systems. The drivers - pieces of software that implement protocols, sets of rules that control the communication with a specific database - are provided by other, databases system specific packages, like {RMardiaDB}, {RMySQL}, {RPostgreSQL}, and similar.

We begin by connecting to our local MariaDB instance: we establish a connection and present ourselves as a particular database user:

drv <- RMariaDB::MariaDB()
con <- RMariaDB::dbConnect(drv, 
  user = "goransm",
  dbname = "datakolektiv",
  host = "localhost",
  port = 3306,
  password = rstudioapi::askForPassword("Database password:")
)

The drv <- RMariaDB::MariaDB() instantiates an object of the MariaDBDriver class, which is used in the RMariaDB::dbConnect() call as its first argument. The RMariaDB::dbConnect() call also passes on the user argument, the database for which the user has permissions as dbname, the host which is for local instances always localhost, the port to which the database system listens to, and finally invokes rstudioapi::askForPassword("Database password:") - a nice piece of RStudio functionally - to provide an interactive password prompt.

To list all tables in the datakolektiv database:

RMariaDB::dbListTables(con)
character(0)

… and in the beginning there are none, of course. We will use DBI::dbWriteTable() to copy the mtcars dataframe to the datakolektiv database:

DBI::dbWriteTable(con, "mtcars", mtcars)
RMariaDB::dbListTables(con)
[1] "mtcars"

And it is that easy. Now, we want to send our first SQL query to datakolektiv and pick up its result back in our R environment:

res <- DBI::dbSendQuery(con,
                        statement = "SELECT * FROM mtcars;")
mtcarsFrame <- DBI::dbFetch(res)
DBI::dbClearResult(res)
print(mtcarsFrame)

Step by step:

  • res <- DBI::dbSendQuery(con, statement = "SELECT * FROM mtcars;"): using the con connection to datakolektiv, send the following SQL statement: "SELECT * FROM mtcars;", which selects everything in the mtcars table of the datakolektiv database;
  • mtcarsFrame <- DBI::dbFetch(res): fetch the result res, which means: pick up the result set obtained from the SQL query execution in datakolektiv and load it into the mtcarsFrame object in the R environment;
  • DBI::dbClearResult(res): very important especially for large result sets - it frees all resources (local and remote) associated with a result set;
  • print(mtcarsFrame): just print the SQL query results which is now stored in the R object mtcarsFrame.

We could have used DBI::dbReadTable() instead of sending the "SELECT * FROM mtcars;" SQL query to the database. They both return everything found in the specified table:

DBI::dbReadTable(con, 'mtcars')

Or the {RMariaDB} version, RMariaDB::dbReadTable(con, 'mtcars'):

RMariaDB::dbReadTable(con, 'mtcars')

Let’s take a look at the following simple SQL aggregation to demonstrate how similar {dplyr} and SQL really are:

res <- dbSendQuery(con,
                      statement = "SELECT cyl, AVG(disp) AS avg_disp, AVG(drat) AS avg_drat 
                                      FROM mtcars 
                                      GROUP BY cyl;")
result <- dbFetch(res)
dbClearResult(res)
print(result)

Step by step:

  • we have first explained what do we want selected from the table: cyl, AVG(disp) AS avg_disp, AVG(drat) AS avg_drat following the SELECT keyword (note: SQL keywords are really case insensitive, select would also do), defining new column names in the result set by using AS;
  • then we have explained in which table are we looking for the data: FROM mtcars;
  • and finally explained that we need to GROUP BY cyl.

In {dplyr}, that would be:

mtcars %>% 
  select(cyl, disp, drat) %>% 
  group_by(cyl) %>% 
  summarise(avg_disp = mean(disp), 
            avg_drat = mean(drat))

Finally, let’s remove the mtcars table from the datakolektiv database in MariaDB:

RMariaDB::dbRemoveTable(con, "mtcars")
RMariaDB::dbListTables(con)
character(0)

Empty. Disconnect (do not forget to disconnect!):

RMariaDB::dbDisconnect(con)

1.2 {dplyr}

Now, what is interesting is that {dplyr}, supported by {dbplyr} - its database backend - can be used to send queries to a database which are silently, under the hood translated from the native {dplyr} sintax into SQL queries!

The following examples are from RStudio’s Using dplyr with databases. Connect:

drv <- RMariaDB::MariaDB()
con <- dbConnect(drv, 
  user = "goransm",
  dbname = "datakolektiv",
  host = "localhost",
  port = 3306,
  password = rstudioapi::askForPassword("Password:")
)

Use copy_to() to copy the flights dataframe from nycflights13 to datakolektiv in MariaDB (this might take some time):

copy_to(dest = con, 
        df = nycflights13::flights, 
        name = "flights",
        temporary = FALSE,
        indexes = list(c("year", "month", "day"),
                       "carrier",
                       "tailnum",
                       "dest"
                       )
        )

We will discuss the indexes parameter later. For now, let’s just check if flights are now found in datakolektiv:

res <- dbSendQuery(con,
                      statement = "SHOW TABLES;")
result <- dbFetch(res)
dbClearResult(res)
print(result)
res <- dbSendQuery(con,
                      statement = "DESCRIBE flights;")
result <- dbFetch(res)
dbClearResult(res)
print(result)

It’s there, definitely. To work with a table in a RDBS from {dplyr}, we need to use tbl() to make a reference to it as an external data source:

flights_db <- tbl(con, "flights")

And now we can send a {dplyr} “query” to a database, e.g:

flights_db %>% 
  select(year:day, dep_delay, arr_delay) %>% 
  head(10)

Compute the mean dep_time grouped by

flights_db %>% 
  group_by(dest) %>%
  summarise(delay = mean(dep_time))

Something that we need to be aware of - a great advantage of working with RDBS from {dplyr} and R in fact - is that our code is lazy evaluated. Let’s analyze the following {dplyr} pipeline:

tailnum_delay_db <- flights_db %>% 
  group_by(tailnum) %>%
  summarise(
    delay = mean(arr_delay),
    n = n()
  ) %>% 
  arrange(desc(delay)) %>%
  filter(n > 100)

When we execute a code like this, nothing happens in the database. No action is taken, no processing beings: because the structure of the code simply defines what needs to happen, but does not invoke any direct action: the result tailnum_delay_db is not used in R in any way!

See how {dplyr} translates to SQL:

tailnum_delay_db %>% show_query()
<SQL>
SELECT *
FROM (SELECT `tailnum`, AVG(`arr_delay`) AS `delay`, COUNT(*) AS `n`
FROM `flights`
GROUP BY `tailnum`) `q01`
WHERE (`n` > 100.0)

This ^^ is exactly the SQL query that will be run in MariaDB to perform the work defined by the {dplyr} pipeline that defines tailnum_delay_db above. From everything that you already now about R you would probably think that tailnum_delay_db has a data.frame class. Look:

class(tailnum_delay_db)
[1] "tbl_MariaDBConnection" "tbl_dbi"               "tbl_sql"               "tbl_lazy"             
[5] "tbl"                  

tbl_lazy :). Now we collect() our tailnum_delay_db and only that triggers an action in MariaDB:

tailnum_delay <- tailnum_delay_db %>% collect()
tailnum_delay

We do not need the flights table anymore:

RMariaDB::dbRemoveTable(con, 'flights')
RMariaDB::dbListTables(con)
character(0)

and begin using mtcars again:

RMariaDB::dbWriteTable(con, 'mtcars', mtcars)

Aggregation by sending a SQL query directly:

res <- dbSendQuery(con,
                      statement = "SELECT cyl, AVG(disp) AS avg_disp, AVG(drat) AS avg_drat 
                                      FROM mtcars 
                                      GROUP BY cyl;")
result <- dbFetch(res)
dbClearResult(res)
print(result)

Now make a reference to mtcars to use {dplyr}:

mtcars_db <- tbl(con, 'mtcars')

The same aggregation from {dplyr}:

mtcars_db %>% select(cyl, disp, drat) %>% 
  group_by(cyl) %>% 
  summarise(avg_disp = mean(disp), 
            avg_drat = mean(drat))

Remove mtcars and disconnect:

RMariaDB::dbRemoveTable(con, 'mtcars')
RMariaDB::dbDisconnect(con)

2. {dplyr}, SQL, indexes, and relations

Connect:

drv <- RMariaDB::MariaDB()
con <- dbConnect(drv, 
  user = "goransm",
  dbname = "datakolektiv",
  host = "localhost",
  port = 3306,
  password = rstudioapi::askForPassword("Password:")
)

Copy flights and planes from nycflights13 to datakolektiv, this time using RMariaDB::dbWriteTable() in place of dplyr::copy_to() (this might take some time):

RMariaDB::dbWriteTable(con,
                       name = "flights",
                       value = nycflights13::flights)
RMariaDB::dbWriteTable(con, 
                       "planes",
                       nycflights13::planes)

Remember how we have used indexes in our dplyr::copy_to() call previously? What are database indexes?

A database index is a data structure that improves the speed of data retrieval operations on a database table at the cost of additional writes and storage space to maintain the index data structure. Indexes are used to quickly locate data without having to search every row in a database table every time a database table is accessed. Indexes can be created using one or more columns of a database table, providing the basis for both rapid random lookups and efficient access of ordered records. Source: Database index, from English Wikipedia

Indexing a table in a RDBS is a pretty advanced topic. We will discuss more of it in our Session.

In Session 11, next week, we will see how the powerful {data.table} R package uses indexes in R to speed up data processing for orders of magnitude in comparison to base R dataframes or dplyr tibbles. For now, indexing flights in datakolektiv with the following set of SQL queries:

# indexes = list(c("year", "month", "day"),
#                "carrier", "tailnum", "dest")
query <- c('CREATE INDEX ixcarrier ON flights(carrier)', 
           'CREATE INDEX ixtailnum ON flights(tailnum);',
           'CREATE INDEX ixdest ON flights(dest);')
lapply(query, function(x) {
  res <- DBI::dbSendQuery(con, x)
  DBI::dbClearResult(res)
})
[[1]]
[1] TRUE

[[2]]
[1] TRUE

[[3]]
[1] TRUE
query <- 'SHOW INDEX FROM flights;'
res <- DBI::dbSendQuery(con, query)
result <- DBI::dbFetch(res)
DBI::dbClearResult(res)
print(result)

Now the composite index over year, month, and day:

# indexes = list(c("year", "month", "day"),
#                "carrier", "tailnum", "dest")
query <- 'CREATE INDEX ixdmy ON flights(year, month, day)'
res <- DBI::dbSendQuery(con, query)
DBI::dbClearResult(res)
query <- 'SHOW INDEX FROM flights;'
res <- DBI::dbSendQuery(con, query)
result <- DBI::dbFetch(res)
DBI::dbClearResult(res)
print(result)

So how does flights look like in datakolektiv?

query <- 'DESCRIBE flights'
res <- DBI::dbSendQuery(con, query)
result <- DBI::dbFetch(res)
DBI::dbClearResult(res)
print(result)

And what do we have in planes?

query <- 'DESCRIBE planes'
res <- DBI::dbSendQuery(con, query)
result <- DBI::dbFetch(res)
DBI::dbClearResult(res)
print(result)

Interesting, for each flight in flights we know the tail number tailnum of the plane which is also present in planes. I am a curious Data Scientist and I want to see what are the technical characteristics a plane that flew each flight in flights, so I need to join them, right? {dplyr}:

flights_db <- tbl(con, "flights")
planes_db <- tbl(con, "planes")
flights_relations <- dplyr::left_join(flights_db, 
                                      planes_db, 
                                      by = "tailnum")

So… what is flights_relations?

class(flights_relations)
[1] "tbl_MariaDBConnection" "tbl_dbi"               "tbl_sql"               "tbl_lazy"             
[5] "tbl"                  

Of course, collect(): (note: please be patient)

flightsFrame <- flights_relations %>% 
  collect()
head(flightsFrame)

How would that left_join() look in SQL? Well, a bit clumsy in my SQL code because I needed to consider the fact that tailnum is present in both flights and planes which would then cause a duplicated column name in the result set, but after some struggle… (note: please be patient)

query <- 'CREATE TABLE flightsframe
            AS (
              SELECT flights.flight,
                     flights.origin,
                     flights.dest,
                     flights.air_time,
                     flights.distance,
                     planes.year,
                     planes.type,
                     planes.manufacturer, 
                     planes.model, 
                     planes.engines,
                     planes.seats, 
                     planes.speed,
                     planes.engine
              FROM flights LEFT JOIN planes USING(tailnum));'
res <- DBI::dbSendQuery(con, query)
DBI::dbClearResult(res)

Let’s take a look at the resulting flightsframe table in datakolektiv:

query <- 'DESCRIBE flightsframe'
res <- DBI::dbSendQuery(con, query)
result <- DBI::dbFetch(res)
DBI::dbClearResult(res)
print(result)

And then if we want to continue in RAM processing in R:

rm(flights_frame)
flights_frame <- RMariaDB::dbReadTable(con, 'flightsframe')
head(flights_frame)

Clear all; disconnect:

RMariaDB::dbRemoveTable(con, 'flights')
RMariaDB::dbRemoveTable(con, 'planes')
RMariaDB::dbRemoveTable(con, 'flightsframe')
RMariaDB::dbDisconnect(con)

3. t-test for unpaired samples

In Lab02 we have introduced the t-test to test if a sample mean is really obtained from a population with some predefined, known mean. The t-test can be used in various settings and now we will see how to use it to compare if two sample means are drawn from the same or different populations, i.e. is their difference statistically significant or not.

The specific case that we will consider is the t-test for unpaired samples. Imagine we test a group of men and women on the same task and wish to compare their mean performances. Obviously, no man from the first group could have also been a member of the second group, and vice versa, no woman from the second group could also have been a member of the first group. This is an example of a between-subjects measurement, where there are two sets of measurements obtained from some “objects” of measurement that are not related nor interchangeable in any way. Imagine if we test a group of men on two related but specific tasks, and we want to compare their mean performance in the first test against their mean performance in the second test. Now, each member of the study sample provides a performance score twice: once in the first and then again in the second test situation. In that case, to test whether the mean performances are any different, we would use a paired samples t-test - which we will not discuss today - and the measure is said to be within-subjects.

table(flights_frame$model) %>% 
  as.data.frame(stringsAsFactors = F) %>% 
  arrange(desc(Freq)) %>% 
  head(10)

This is the list of airplane models that are most frequently observed in flights_frame. Let’s focus on the two most frequently observed models and ask there are any differences on the air_time variable between the flights performed by A320-232 and flights performed by EMB-145LR:

models <- c('A320-232', 'EMB-145LR')
ttestTable <- flights_frame %>% 
  select(distance, air_time, model) %>% 
  filter(model %in% models) %>% 
  na.omit
head(ttestTable)

How many flights, per airplane model, are selected?

table(ttestTable$model)

 A320-232 EMB-145LR 
    45437     26475 

This is not good. In a t-test setting, we would like to have unpaired samples of at least approximately same size. Here goes a bit of sampling magic:

props <- as.numeric(
  table(ttestTable$model)/sum(table(ttestTable$model))
)
props
[1] 0.6318417 0.3681583

It is now easy, just sample the larger class in a proportion of the smaller one, and the smaller class in a proportion of the larger one:

ttestTable$select <- sapply(ttestTable$model, function(x) {
  if (x == 'EMB-145LR') {
    rbinom(1, 1, props[1])
  } else {
    rbinom(1, 1, props[2])
  }
})
table(ttestTable$select)

    0     1 
38438 33474 

We just need to pick-up the 1s on ttestTable$select:

ttestTable <- ttestTable[ttestTable$select == 1, ]
table(ttestTable$model)

 A320-232 EMB-145LR 
    16712     16762 

Almost there. The t-test now!

3.1 How to perform a t-test for unpaired samples in R

t.test(x = ttestTable$distance[ttestTable$model == "A320-232"],
       y = ttestTable$distance[ttestTable$model == "EMB-145LR"], 
       alternative = "two.sided", 
       var.equal = T)

    Two Sample t-test

data:  ttestTable$distance[ttestTable$model == "A320-232"] and ttestTable$distance[ttestTable$model == "EMB-145LR"]
t = 161.51, df = 33472, p-value < 2.2e-16
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 839.7887 860.4219
sample estimates:
mean of x mean of y 
1360.5117  510.4064 

Note the var.equal = T argument in the t.test() call. One of the assumptions of the t-test is that the measure variances in both groups are equal, which is almost certainly not the case here. But we have a bigger problem: the t-test is meant for normally distributed data…

ttestPlotFrame <- ttestTable %>% 
  select(model, distance)
ggplot(ttestPlotFrame, 
       aes(x = distance,
           group = model,
           fill = model)) + 
  geom_density(alpha = .5, color = "black") + 
  ggtitle("NYC Flights Dataset") + 
  scale_fill_manual(values = c('deepskyblue', 'darkorange')) + 
  theme_bw() + 
  theme(plot.title = element_text(hjust = .5, size = 9)) + 
  theme(panel.grid = element_blank()) + 
  theme(panel.border = element_blank()) + 
  theme(legend.title = element_text(size = 8)) + 
  theme(legend.text = element_text(size = 8))

Not even remotely:

ks.test(ttestTable$distance[ttestTable$model == "A320-232"], 
        y = "pnorm")
ties should not be present for the Kolmogorov-Smirnov test

    One-sample Kolmogorov-Smirnov test

data:  ttestTable$distance[ttestTable$model == "A320-232"]
D = 1, p-value < 2.2e-16
alternative hypothesis: two-sided
ks.test(ttestTable$distance[ttestTable$model == "EMB-145LR"], 
        y = "pnorm")
ties should not be present for the Kolmogorov-Smirnov test

    One-sample Kolmogorov-Smirnov test

data:  ttestTable$distance[ttestTable$model == "EMB-145LR"]
D = 1, p-value < 2.2e-16
alternative hypothesis: two-sided

3.2 Normally distributed data in iris

So now we now how to use a t-test in an unpaired samples setting. Let’s go for some at least approximately normally distributed data.

head(iris)
shapiro.test(iris$Sepal.Length[iris$Species == "setosa"])

    Shapiro-Wilk normality test

data:  iris$Sepal.Length[iris$Species == "setosa"]
W = 0.9777, p-value = 0.4595
shapiro.test(iris$Sepal.Length[iris$Species == "versicolor"])

    Shapiro-Wilk normality test

data:  iris$Sepal.Length[iris$Species == "versicolor"]
W = 0.97784, p-value = 0.4647
ggplot(iris %>% 
         filter(Species != 'virginica'), 
       aes(x = Sepal.Length,
           group = Species,
           fill = Species)) + 
  geom_density(alpha = .5, color = "black") + 
  ggtitle("Iris Dataset") + 
  scale_fill_manual(values = c('deepskyblue', 'darkorange')) +
  theme_bw() + 
  theme(plot.title = element_text(hjust = .5, size = 9)) + 
  theme(panel.grid = element_blank()) + 
  theme(panel.border = element_blank()) + 
  theme(legend.title = element_text(size = 8)) + 
  theme(legend.text = element_text(size = 8))

Boxplot:

ggplot(iris %>% 
         filter(Species != 'virginica'), 
       aes(x = Species,
           y = Sepal.Length,
           group = Species,
           fill = Species)) + 
  geom_boxplot() + 
  ggtitle("Iris Dataset") + 
  scale_fill_manual(values = c('deepskyblue', 'darkorange')) +
  theme_bw() + 
  theme(plot.title = element_text(hjust = .5, size = 9)) + 
  theme(panel.grid = element_blank()) + 
  theme(panel.border = element_blank()) + 
  theme(legend.title = element_text(size = 8)) + 
  theme(legend.text = element_text(size = 8))

It definitely looks like there is a difference, the data seem to follow a normal distribution…

t.test(x = iris$Sepal.Length[iris$Species == "setosa"],
       y = iris$Sepal.Length[iris$Species == "versicolor"], 
       alternative = "two.sided", 
       var.equal = T)

    Two Sample t-test

data:  iris$Sepal.Length[iris$Species == "setosa"] and iris$Sepal.Length[iris$Species == "versicolor"]
t = -10.521, df = 98, p-value < 2.2e-16
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 -1.1054165 -0.7545835
sample estimates:
mean of x mean of y 
    5.006     5.936 

Great. But…

var(iris$Sepal.Length[iris$Species == "setosa"])
[1] 0.124249
var(iris$Sepal.Length[iris$Species == "versicolor"])
[1] 0.2664327

It does not look good: the versicolor variance on Sepal.Length seems to be more than twice large than in the setosa group. A correction needs to be applied.

3.3 Welch test: unequal group variances

Change: var.equal = F to perform a Welch test:

t.test(x = iris$Sepal.Length[iris$Species == "setosa"],
       y = iris$Sepal.Length[iris$Species == "versicolor"], 
       alternative = "two.sided", 
       var.equal = F)

    Welch Two Sample t-test

data:  iris$Sepal.Length[iris$Species == "setosa"] and iris$Sepal.Length[iris$Species == "versicolor"]
t = -10.521, df = 86.538, p-value < 2.2e-16
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 -1.1057074 -0.7542926
sample estimates:
mean of x mean of y 
    5.006     5.936 

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/.


LS0tDQp0aXRsZTogSW50cm8gdG8gRGF0YSBTY2llbmNlIChOb24tVGVjaG5pY2FsIEJhY2tncm91bmQsIFIpIC0gU2Vzc2lvbjEwDQphdXRob3I6DQotIG5hbWU6IEdvcmFuIFMuIE1pbG92YW5vdmnEhywgUGhEDQogIGFmZmlsaWF0aW9uOiBEYXRhS29sZWt0aXYsIENoaWVmIFNjaWVudGlzdCAmIE93bmVyOyBEYXRhIFNjaWVudGlzdCBmb3IgV2lraWRhdGEsIFdNREUNCmFic3RyYWN0OiANCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCiAgICB0aGVtZTogc3BhY2VsYWINCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgdG9jX2RlcHRoOiA1DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDUNCi0tLQ0KDQohW10oLi4vX2ltZy9ES19Mb2dvXzEwMC5wbmcpDQoNCioqKg0KIyBTZXNzaW9uIDEwOiBXb3JraW5nIHdpdGggYSBsb2NhbCBSREJTIGZyb20ge2RwbHlyfSBhbmQge0RCSX0gKyB0LXRlc3QgZm9yIHVucGFpcmVkIHNhbXBsZXMgDQoNCioqRmVlZGJhY2sqKiBzaG91bGQgYmUgc2VuZCB0byBgZ29yYW4ubWlsb3Zhbm92aWNAZGF0YWtvbGVrdGl2LmNvbWAuIA0KVGhlc2Ugbm90ZWJvb2tzIGFjY29tcGFueSB0aGUgSW50cm8gdG8gRGF0YSBTY2llbmNlOiBOb24tVGVjaG5pY2FsIEJhY2tncm91bmQgY291cnNlIDIwMjAvMjEuDQoNCioqKg0KDQojIyMgV2hhdCBkbyB3ZSB3YW50IHRvIGRvIHRvZGF5Pw0KDQpTZXJpb3VzIGRhdGEgd3JhbmdsaW5nIHdpdGgge2RwbHlyfS4gV2hhdCBSZWxhdGlvbmFsIERhdGFiYXNlcyAoUkRCUykgYXJlIGFuZCBob3cgZG8gd2UgY29ubmVjdCB0byB0aGVtPyBQcmVyZXF1aXNpdGVzOiBpbnN0YWxsaW5nIFtNYXJpYURCXShodHRwczovL21hcmlhZGIub3JnLykgb24geW91ciBsb2NhbCBtYWNoaW5lLiBBIGNyYXNoIGNvdXJzZSBpbiBTUUw6IHNvIHNpbWlsYXIgdG8ge2RwbHlyfS4NCg0KDQojIyMgMC4gUHJlcmVxdWlzaXRzDQoNCiAgMS4gW0Rvd25sb2FkIE1hcmlhREJdKGh0dHBzOi8vZG93bmxvYWRzLm1hcmlhZGIub3JnLyk6IHBpY2sgdmVyc2lvbiAxMC41LjkgZnJvbSB0aGUgY3VycmVudCBzdGFibGUgc2VyaWVzOiB1c2UgW3RoaXMgbGlua10oaHR0cHM6Ly9kb3dubG9hZHMubWFyaWFkYi5vcmcvaW50ZXJzdGl0aWFsL21hcmlhZGItMTAuNS45L3dpbng2NC1wYWNrYWdlcy9tYXJpYWRiLTEwLjUuOS13aW54NjQubXNpL2Zyb20vaHR0cHMlM0EvL21pcnJvci5vbmUuY29tL21hcmlhZGIvKSB0byBkb3dubG9hZCB0aGUgTWFyaWEgREIgTVNJIHBhY2thZ2UgZm9yIFdpbmRvd3MgMTAuDQogIA0KICAyLiBEb3VibGUtY2xpY2sgYG1hcmlhZGItMTAuNS45LXdpbng2NGAgb25jZSB0aGUgZG93bmxvYWQgaXMgZmluaXNoZWQuDQogIA0KICAzLiBGb2xsb3cgdGhlIGluc3RydWN0aW9ucyBoZXJlOiBbSW5zdGFsbGluZyBNYXJpYURCIE1TSSBQYWNrYWdlcyBvbiBXaW5kb3dzXShodHRwczovL21hcmlhZGIuY29tL2tiL2VuL2luc3RhbGxpbmctbWFyaWFkYi1tc2ktcGFja2FnZXMtb24td2luZG93cy8pIG9uY2UgdGhlIGluc3RhbGxhdGlvbiBwcm9jZXNzIGJlZ2lucy4gSnVzdCBnbyBmb3IgdGhlIGRlZmF1bHRzLCBlLmcuIGxlYXZlIHRoZSAqRGF0YWJhc2UgaW5zdGFuY2UqIGZlYXR1cmUgc2VsZWN0ZWQgdG8gY3JlYXRlIGEgZGF0YWJhc2UgaW5zdGFuY2UuICoqUGxlYXNlIGRvIHJlYWQgdGhlIGluc3RydWN0aW9ucyBjYXJlZnVsbHkuKiogV3JpdGUgZG93biBvciBtZW1vcml6ZSB5b3VyICpyb290IHBhc3N3b3JkKjsgT3B0aW9uczogKipkbyB1c2UqKiBgVVRGOGAgYXMgZGVmYXVsdCBzZXJ2ZXIncyBjaGFyYWN0ZXIgc2V0IHdoZW4gYXNrZWQ7ICoqVW5jaGVjazoqKiBFbmFibGUgYWNjZXNzIGZyb20gcmVtb3RlIG1hY2hpbmVzIGZvciAncm9vdCcgdXNlcjsgYEluc3RhbGwgYXMgc2VydmljZWA6ICoqeWVzKiosIGBFbmFibGUgbmV0d29ya2luZ2A6ICoqeWVzKiosIGxlYXZlIHRoZSBUQ1AgcG9ydCBhcyBpcy4gICAgICAgDQogIA0KICA0LiAqKkluc3RhbGwuKioNCiAgDQogIDUuIEluc3RhbGwge2RicGx5cn06IGBpbnN0YWxsLnBhY2thZ2VzKCJkYnBseXIiKWAuDQogIA0KICA2LiBJbnN0YWxsIHtSTWFyaWFEQn06IGBpbnN0YWxsLnBhY2thZ2VzKCJSTWFyaWFEQiIpYC4NCiAgDQogIDcuIE9wZW4gKipNeVNRTCBDbGllbnQqKi4gWW91IHdpbGwgYmUgcHJvbXB0ZWQgZm9yIGEgcm9vdCBwYXNzd29yZC4NCiAgDQogIDguIENyZWF0ZSBkYXRhYmFzZTogYENSRUFURSBEQVRBQkFTRSBkYXRha29sZWt0aXY7YA0KICANCiAgOS4gQ3JlYXRlIHVzZXI6IGBDUkVBVEUgVVNFUiA8WW91clVzZXJuYW1lPkBsb2NhbGhvc3QgSURFTlRJRklFRCBCWSAnPFlvdXJQYXNzd29yZD4nO2AgKCoqTk9URS4qKiBUaGlzIGlzIGEgbmV3IHBhc3N3b3JkIHRoYXQgeW91IGNyZWF0ZSBmb3IgdGhlIG5ldyB1c2VyLCBub3QgdGhlIHJvb3QgcGFzc3dvcmQpLg0KICANCiAgMTAuIEdyYW50IGFsbCByaWdodHMgdG8gdXNlcjogYEdSQU5UIEFMTCBQUklWSUxFR0VTIE9OICouKiBUTyA8WW91clVzZXJuYW1lPkBsb2NhbGhvc3Q7YC4NCiAgDQogIDExLiBBY3RpdmF0ZSBwcml2aWxlZ2VzOiBgZmx1c2ggcHJpdmlsZWdlcztgLg0KDQogIDEyLiBFeGl0OiBgZXhpdGAuDQogIA0KICAxMy4gSW5zdGFsbCBgbnljZmxpZ2h0czEzYDogYGluc3RhbGwucGFja2FnZXMoJ255Y2ZsaWdodHMxMycpYA0KICANCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoZGJwbHlyKQ0KbGlicmFyeShSTWFyaWFEQikNCmBgYA0KDQojIyMgMS4gV29ya2luZyB3aXRoIGEgbG9jYWwgUkRCUyBmcm9tIHtEQkl9L3tkcGx5cn0ve2RicGx5cn0NCg0KIyMjIyAxLjEge0RCSX0gYW5kIHtSTWFyaWFEQn0NCg0KW3tEQkl9XShodHRwczovL2RiLnJzdHVkaW8uY29tL2RiaS8pIGlzIGEgcGFja2FnZSB0byB3b3JrIHdpdGggdmFyaW91cyBkYXRhYmFzZXMgc3lzdGVtcyBmcm9tIFIuIFRoZSBlc3NlbnRpYWwgYWR2YW50YWdlIG9mIHtEQkl9IGlzIHRoYXQgaXQgcHJvdmlkZXMgYSAqdW5pZmllZCBpbnRlcmZhY2UqIHRvIHdvcmsgd2l0aCBkaWZmZXJlbnQgZGF0YWJhc2Ugc3lzdGVtcy4gVGhlICpkcml2ZXJzKiAtIHBpZWNlcyBvZiBzb2Z0d2FyZSB0aGF0IGltcGxlbWVudCAqcHJvdG9jb2xzKiwgc2V0cyBvZiBydWxlcyB0aGF0IGNvbnRyb2wgdGhlIGNvbW11bmljYXRpb24gd2l0aCBhIHNwZWNpZmljIGRhdGFiYXNlIC0gYXJlIHByb3ZpZGVkIGJ5IG90aGVyLCBkYXRhYmFzZXMgc3lzdGVtIHNwZWNpZmljIHBhY2thZ2VzLCBsaWtlIFt7Uk1hcmRpYURCfV0oaHR0cHM6Ly9naXRodWIuY29tL3ItZGJpL1JNYXJpYURCKSwgW3tSTXlTUUx9XShodHRwczovL2dpdGh1Yi5jb20vci1kYmkvUk15U1FMKSwgW3tSUG9zdGdyZVNRTH1dKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9SUG9zdGdyZVNRTC9pbmRleC5odG1sKSwgYW5kIHNpbWlsYXIuDQoNCldlIGJlZ2luIGJ5IGNvbm5lY3RpbmcgdG8gb3VyIGxvY2FsIE1hcmlhREIgaW5zdGFuY2U6IHdlIGVzdGFibGlzaCBhICpjb25uZWN0aW9uKiBhbmQgcHJlc2VudCBvdXJzZWx2ZXMgYXMgYSBwYXJ0aWN1bGFyIGRhdGFiYXNlIHVzZXI6ICANCg0KYGBge3IgZWNobyA9IFR9DQpkcnYgPC0gUk1hcmlhREI6Ok1hcmlhREIoKQ0KY29uIDwtIFJNYXJpYURCOjpkYkNvbm5lY3QoZHJ2LCANCiAgdXNlciA9ICJnb3JhbnNtIiwNCiAgZGJuYW1lID0gImRhdGFrb2xla3RpdiIsDQogIGhvc3QgPSAibG9jYWxob3N0IiwNCiAgcG9ydCA9IDMzMDYsDQogIHBhc3N3b3JkID0gcnN0dWRpb2FwaTo6YXNrRm9yUGFzc3dvcmQoIkRhdGFiYXNlIHBhc3N3b3JkOiIpDQopDQpgYGANCg0KVGhlIGBkcnYgPC0gUk1hcmlhREI6Ok1hcmlhREIoKWAgaW5zdGFudGlhdGVzIGFuIG9iamVjdCBvZiB0aGUgYE1hcmlhREJEcml2ZXJgIGNsYXNzLCB3aGljaCBpcyB1c2VkIGluIHRoZSBgUk1hcmlhREI6OmRiQ29ubmVjdCgpYCBjYWxsIGFzIGl0cyBmaXJzdCBhcmd1bWVudC4gVGhlIGBSTWFyaWFEQjo6ZGJDb25uZWN0KClgIGNhbGwgYWxzbyBwYXNzZXMgb24gdGhlIGB1c2VyYCBhcmd1bWVudCwgdGhlIGRhdGFiYXNlIGZvciB3aGljaCB0aGUgdXNlciBoYXMgcGVybWlzc2lvbnMgYXMgYGRibmFtZWAsIHRoZSBgaG9zdGAgd2hpY2ggaXMgZm9yIGxvY2FsIGluc3RhbmNlcyBhbHdheXMgYGxvY2FsaG9zdGAsIHRoZSBgcG9ydGAgdG8gd2hpY2ggdGhlIGRhdGFiYXNlIHN5c3RlbSBsaXN0ZW5zIHRvLCBhbmQgZmluYWxseSBpbnZva2VzIGByc3R1ZGlvYXBpOjphc2tGb3JQYXNzd29yZCgiRGF0YWJhc2UgcGFzc3dvcmQ6IilgIC0gYSBuaWNlIHBpZWNlIG9mIFJTdHVkaW8gZnVuY3Rpb25hbGx5IC0gdG8gcHJvdmlkZSBhbiBpbnRlcmFjdGl2ZSBwYXNzd29yZCBwcm9tcHQuDQoNClRvIGxpc3QgYWxsIHRhYmxlcyBpbiB0aGUgYGRhdGFrb2xla3RpdmAgZGF0YWJhc2U6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KUk1hcmlhREI6OmRiTGlzdFRhYmxlcyhjb24pDQpgYGANCi4uLiBhbmQgaW4gdGhlIGJlZ2lubmluZyB0aGVyZSBhcmUgbm9uZSwgb2YgY291cnNlLiBXZSB3aWxsIHVzZSBgREJJOjpkYldyaXRlVGFibGUoKWAgdG8gY29weSB0aGUgYG10Y2Fyc2AgZGF0YWZyYW1lIHRvIHRoZSBgZGF0YWtvbGVrdGl2YCBkYXRhYmFzZToNCg0KYGBge3IgZWNobyA9IFR9DQpEQkk6OmRiV3JpdGVUYWJsZShjb24sICJtdGNhcnMiLCBtdGNhcnMpDQpSTWFyaWFEQjo6ZGJMaXN0VGFibGVzKGNvbikNCmBgYA0KQW5kIGl0IGlzIHRoYXQgZWFzeS4gTm93LCB3ZSB3YW50IHRvIHNlbmQgb3VyIGZpcnN0IFNRTCBxdWVyeSB0byBgZGF0YWtvbGVrdGl2YCBhbmQgcGljayB1cCBpdHMgcmVzdWx0IGJhY2sgaW4gb3VyIFIgZW52aXJvbm1lbnQ6DQoNCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gRn0NCnJlcyA8LSBEQkk6OmRiU2VuZFF1ZXJ5KGNvbiwNCiAgICAgICAgICAgICAgICAgICAgICAgIHN0YXRlbWVudCA9ICJTRUxFQ1QgKiBGUk9NIG10Y2FyczsiKQ0KbXRjYXJzRnJhbWUgPC0gREJJOjpkYkZldGNoKHJlcykNCkRCSTo6ZGJDbGVhclJlc3VsdChyZXMpDQpwcmludChtdGNhcnNGcmFtZSkNCmBgYA0KDQpTdGVwIGJ5IHN0ZXA6DQoNCi0gYHJlcyA8LSBEQkk6OmRiU2VuZFF1ZXJ5KGNvbiwgc3RhdGVtZW50ID0gIlNFTEVDVCAqIEZST00gbXRjYXJzOyIpYDogdXNpbmcgdGhlIGBjb25gIGNvbm5lY3Rpb24gdG8gYGRhdGFrb2xla3RpdmAsIHNlbmQgdGhlIGZvbGxvd2luZyBTUUwgc3RhdGVtZW50OiBgIlNFTEVDVCAqIEZST00gbXRjYXJzOyJgLCB3aGljaCBzZWxlY3RzIGV2ZXJ5dGhpbmcgaW4gdGhlIGBtdGNhcnNgIHRhYmxlIG9mIHRoZSBgZGF0YWtvbGVrdGl2YCBkYXRhYmFzZTsNCi0gYG10Y2Fyc0ZyYW1lIDwtIERCSTo6ZGJGZXRjaChyZXMpYDogKipmZXRjaCoqIHRoZSByZXN1bHQgYHJlc2AsIHdoaWNoIG1lYW5zOiBwaWNrIHVwIHRoZSByZXN1bHQgc2V0IG9idGFpbmVkIGZyb20gdGhlIFNRTCBxdWVyeSBleGVjdXRpb24gaW4gYGRhdGFrb2xla3RpdmAgYW5kIGxvYWQgaXQgaW50byB0aGUgYG10Y2Fyc0ZyYW1lYCBvYmplY3QgaW4gdGhlIFIgZW52aXJvbm1lbnQ7DQotIGBEQkk6OmRiQ2xlYXJSZXN1bHQocmVzKWA6ICoqdmVyeSBpbXBvcnRhbnQqKiBlc3BlY2lhbGx5IGZvciBsYXJnZSByZXN1bHQgc2V0cyAtIGl0IGZyZWVzIGFsbCByZXNvdXJjZXMgKGxvY2FsIGFuZCByZW1vdGUpIGFzc29jaWF0ZWQgd2l0aCBhIHJlc3VsdCBzZXQ7DQotIGBwcmludChtdGNhcnNGcmFtZSlgOiBqdXN0IHByaW50IHRoZSBTUUwgcXVlcnkgcmVzdWx0cyB3aGljaCBpcyBub3cgc3RvcmVkIGluIHRoZSBSIG9iamVjdCBgbXRjYXJzRnJhbWVgLg0KDQpXZSBjb3VsZCBoYXZlIHVzZWQgYERCSTo6ZGJSZWFkVGFibGUoKWAgaW5zdGVhZCBvZiBzZW5kaW5nIHRoZSBgIlNFTEVDVCAqIEZST00gbXRjYXJzOyJgIFNRTCBxdWVyeSB0byB0aGUgZGF0YWJhc2UuIFRoZXkgYm90aCByZXR1cm4gZXZlcnl0aGluZyBmb3VuZCBpbiB0aGUgc3BlY2lmaWVkIHRhYmxlOg0KDQpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9IEZ9DQpEQkk6OmRiUmVhZFRhYmxlKGNvbiwgJ210Y2FycycpDQpgYGANCg0KT3IgdGhlIHtSTWFyaWFEQn0gdmVyc2lvbiwgYFJNYXJpYURCOjpkYlJlYWRUYWJsZShjb24sICdtdGNhcnMnKWA6DQoNCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gRn0NClJNYXJpYURCOjpkYlJlYWRUYWJsZShjb24sICdtdGNhcnMnKQ0KYGBgDQoNCkxldCdzIHRha2UgYSBsb29rIGF0IHRoZSBmb2xsb3dpbmcgc2ltcGxlIFNRTCBhZ2dyZWdhdGlvbiB0byBkZW1vbnN0cmF0ZSBob3cgc2ltaWxhciB7ZHBseXJ9IGFuZCBTUUwgcmVhbGx5IGFyZToNCg0KYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSBGfQ0KcmVzIDwtIGRiU2VuZFF1ZXJ5KGNvbiwNCiAgICAgICAgICAgICAgICAgICAgICBzdGF0ZW1lbnQgPSAiU0VMRUNUIGN5bCwgQVZHKGRpc3ApIEFTIGF2Z19kaXNwLCBBVkcoZHJhdCkgQVMgYXZnX2RyYXQgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEZST00gbXRjYXJzIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBHUk9VUCBCWSBjeWw7IikNCnJlc3VsdCA8LSBkYkZldGNoKHJlcykNCmRiQ2xlYXJSZXN1bHQocmVzKQ0KcHJpbnQocmVzdWx0KQ0KYGBgDQoNClN0ZXAgYnkgc3RlcDoNCg0KLSB3ZSBoYXZlIGZpcnN0IGV4cGxhaW5lZCB3aGF0IGRvIHdlIHdhbnQgKnNlbGVjdGVkKiBmcm9tIHRoZSB0YWJsZTogYGN5bCwgQVZHKGRpc3ApIEFTIGF2Z19kaXNwLCBBVkcoZHJhdCkgQVMgYXZnX2RyYXRgIGZvbGxvd2luZyB0aGUgYFNFTEVDVGAga2V5d29yZCAoKipub3RlOioqIFNRTCBrZXl3b3JkcyBhcmUgcmVhbGx5IGNhc2UgaW5zZW5zaXRpdmUsIGBzZWxlY3RgIHdvdWxkIGFsc28gZG8pLCBkZWZpbmluZyBuZXcgY29sdW1uIG5hbWVzIGluIHRoZSByZXN1bHQgc2V0IGJ5IHVzaW5nIGBBU2A7DQotIHRoZW4gd2UgaGF2ZSBleHBsYWluZWQgaW4gd2hpY2ggdGFibGUgYXJlIHdlIGxvb2tpbmcgZm9yIHRoZSBkYXRhOiBgRlJPTSBtdGNhcnNgOyANCi0gYW5kIGZpbmFsbHkgZXhwbGFpbmVkIHRoYXQgd2UgbmVlZCB0byBgR1JPVVAgQlkgY3lsYC4gDQoNCkluIHtkcGx5cn0sIHRoYXQgd291bGQgYmU6DQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRn0NCm10Y2FycyAlPiUgDQogIHNlbGVjdChjeWwsIGRpc3AsIGRyYXQpICU+JSANCiAgZ3JvdXBfYnkoY3lsKSAlPiUgDQogIHN1bW1hcmlzZShhdmdfZGlzcCA9IG1lYW4oZGlzcCksIA0KICAgICAgICAgICAgYXZnX2RyYXQgPSBtZWFuKGRyYXQpKQ0KYGBgDQoNCkZpbmFsbHksIGxldCdzIHJlbW92ZSB0aGUgYG10Y2Fyc2AgdGFibGUgZnJvbSB0aGUgYGRhdGFrb2xla3RpdmAgZGF0YWJhc2UgaW4gTWFyaWFEQjoNCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGfQ0KUk1hcmlhREI6OmRiUmVtb3ZlVGFibGUoY29uLCAibXRjYXJzIikNClJNYXJpYURCOjpkYkxpc3RUYWJsZXMoY29uKQ0KYGBgDQpFbXB0eS4gRGlzY29ubmVjdCAoZG8gbm90IGZvcmdldCB0byBkaXNjb25uZWN0ISk6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KUk1hcmlhREI6OmRiRGlzY29ubmVjdChjb24pDQpgYGANCg0KDQojIyMjIDEuMiB7ZHBseXJ9DQoNCk5vdywgd2hhdCBpcyBpbnRlcmVzdGluZyBpcyB0aGF0IHtkcGx5cn0sIHN1cHBvcnRlZCBieSBbe2RicGx5cn1dKGh0dHBzOi8vZGJwbHlyLnRpZHl2ZXJzZS5vcmcvKSAtIGl0cyBkYXRhYmFzZSBiYWNrZW5kIC0gY2FuIGJlIHVzZWQgdG8gc2VuZCBxdWVyaWVzIHRvIGEgZGF0YWJhc2Ugd2hpY2ggYXJlIHNpbGVudGx5LCB1bmRlciB0aGUgaG9vZCB0cmFuc2xhdGVkIGZyb20gdGhlIG5hdGl2ZSB7ZHBseXJ9IHNpbnRheCBpbnRvIFNRTCBxdWVyaWVzIQ0KDQpUaGUgZm9sbG93aW5nIGV4YW1wbGVzIGFyZSBmcm9tIFJTdHVkaW8ncyBbVXNpbmcgZHBseXIgd2l0aCBkYXRhYmFzZXNdKGh0dHBzOi8vZGIucnN0dWRpby5jb20vZHBseXIvKS4gQ29ubmVjdDoNCg0KYGBge3IgZWNobyA9IFR9DQpkcnYgPC0gUk1hcmlhREI6Ok1hcmlhREIoKQ0KY29uIDwtIGRiQ29ubmVjdChkcnYsIA0KICB1c2VyID0gImdvcmFuc20iLA0KICBkYm5hbWUgPSAiZGF0YWtvbGVrdGl2IiwNCiAgaG9zdCA9ICJsb2NhbGhvc3QiLA0KICBwb3J0ID0gMzMwNiwNCiAgcGFzc3dvcmQgPSByc3R1ZGlvYXBpOjphc2tGb3JQYXNzd29yZCgiUGFzc3dvcmQ6IikNCikNCmBgYA0KDQpVc2UgYGNvcHlfdG8oKWAgdG8gY29weSB0aGUgYGZsaWdodHNgIGRhdGFmcmFtZSBmcm9tIGBueWNmbGlnaHRzMTNgIHRvIGBkYXRha29sZWt0aXZgIGluIE1hcmlhREIgKHRoaXMgbWlnaHQgdGFrZSBzb21lIHRpbWUpOg0KDQpgYGB7ciBlY2hvID0gVH0NCmNvcHlfdG8oZGVzdCA9IGNvbiwgDQogICAgICAgIGRmID0gbnljZmxpZ2h0czEzOjpmbGlnaHRzLCANCiAgICAgICAgbmFtZSA9ICJmbGlnaHRzIiwNCiAgICAgICAgdGVtcG9yYXJ5ID0gRkFMU0UsDQogICAgICAgIGluZGV4ZXMgPSBsaXN0KGMoInllYXIiLCAibW9udGgiLCAiZGF5IiksDQogICAgICAgICAgICAgICAgICAgICAgICJjYXJyaWVyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgInRhaWxudW0iLA0KICAgICAgICAgICAgICAgICAgICAgICAiZGVzdCINCiAgICAgICAgICAgICAgICAgICAgICAgKQ0KICAgICAgICApDQpgYGANCg0KV2Ugd2lsbCBkaXNjdXNzIHRoZSBgaW5kZXhlc2AgcGFyYW1ldGVyIGxhdGVyLiBGb3Igbm93LCBsZXQncyBqdXN0IGNoZWNrIGlmIGBmbGlnaHRzYCBhcmUgbm93IGZvdW5kIGluIGBkYXRha29sZWt0aXZgOg0KDQpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9IEZ9DQpyZXMgPC0gZGJTZW5kUXVlcnkoY29uLA0KICAgICAgICAgICAgICAgICAgICAgIHN0YXRlbWVudCA9ICJTSE9XIFRBQkxFUzsiKQ0KcmVzdWx0IDwtIGRiRmV0Y2gocmVzKQ0KZGJDbGVhclJlc3VsdChyZXMpDQpwcmludChyZXN1bHQpDQpgYGANCg0KYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSBGfQ0KcmVzIDwtIGRiU2VuZFF1ZXJ5KGNvbiwNCiAgICAgICAgICAgICAgICAgICAgICBzdGF0ZW1lbnQgPSAiREVTQ1JJQkUgZmxpZ2h0czsiKQ0KcmVzdWx0IDwtIGRiRmV0Y2gocmVzKQ0KZGJDbGVhclJlc3VsdChyZXMpDQpwcmludChyZXN1bHQpDQpgYGANCg0KSXQncyB0aGVyZSwgZGVmaW5pdGVseS4gVG8gd29yayB3aXRoIGEgdGFibGUgaW4gYSBSREJTIGZyb20ge2RwbHlyfSwgd2UgbmVlZCB0byB1c2UgYHRibCgpYCB0byBtYWtlIGEgcmVmZXJlbmNlIHRvIGl0IGFzIGFuIGV4dGVybmFsIGRhdGEgc291cmNlOg0KDQpgYGB7ciBlY2hvID0gVH0NCmZsaWdodHNfZGIgPC0gdGJsKGNvbiwgImZsaWdodHMiKQ0KYGBgDQoNCkFuZCBub3cgd2UgY2FuIHNlbmQgYSB7ZHBseXJ9ICJxdWVyeSIgdG8gYSBkYXRhYmFzZSwgZS5nOg0KDQpgYGB7ciBlY2hvID0gVH0NCmZsaWdodHNfZGIgJT4lIA0KICBzZWxlY3QoeWVhcjpkYXksIGRlcF9kZWxheSwgYXJyX2RlbGF5KSAlPiUgDQogIGhlYWQoMTApDQpgYGANCkNvbXB1dGUgdGhlIG1lYW4gYGRlcF90aW1lYCBncm91cGVkIGJ5DQoNCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gRn0NCmZsaWdodHNfZGIgJT4lIA0KICBncm91cF9ieShkZXN0KSAlPiUNCiAgc3VtbWFyaXNlKGRlbGF5ID0gbWVhbihkZXBfdGltZSkpDQpgYGANCg0KU29tZXRoaW5nIHRoYXQgd2UgbmVlZCB0byBiZSBhd2FyZSBvZiAtIGEgZ3JlYXQgYWR2YW50YWdlIG9mIHdvcmtpbmcgd2l0aCBSREJTIGZyb20ge2RwbHlyfSBhbmQgUiBpbiBmYWN0IC0gaXMgdGhhdCBvdXIgY29kZSBpcyAqbGF6eSBldmFsdWF0ZWQqLiBMZXQncyBhbmFseXplIHRoZSBmb2xsb3dpbmcge2RwbHlyfSBwaXBlbGluZToNCg0KYGBge3IgZWNobyA9IFR9DQp0YWlsbnVtX2RlbGF5X2RiIDwtIGZsaWdodHNfZGIgJT4lIA0KICBncm91cF9ieSh0YWlsbnVtKSAlPiUNCiAgc3VtbWFyaXNlKA0KICAgIGRlbGF5ID0gbWVhbihhcnJfZGVsYXkpLA0KICAgIG4gPSBuKCkNCiAgKSAlPiUgDQogIGFycmFuZ2UoZGVzYyhkZWxheSkpICU+JQ0KICBmaWx0ZXIobiA+IDEwMCkNCmBgYA0KDQpXaGVuIHdlIGV4ZWN1dGUgYSBjb2RlIGxpa2UgdGhpcywgKm5vdGhpbmcqIGhhcHBlbnMgaW4gdGhlIGRhdGFiYXNlLiBObyBhY3Rpb24gaXMgdGFrZW4sIG5vIHByb2Nlc3NpbmcgYmVpbmdzOiBiZWNhdXNlIHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIGNvZGUgc2ltcGx5IGRlZmluZXMgd2hhdCAqbmVlZHMgdG8gaGFwcGVuKiwgYnV0IGRvZXMgbm90IGludm9rZSBhbnkgKmRpcmVjdCBhY3Rpb24qOiB0aGUgcmVzdWx0IGB0YWlsbnVtX2RlbGF5X2RiYCBpcyBub3QgdXNlZCBpbiBSIGluIGFueSB3YXkhDQoNClNlZSBob3cge2RwbHlyfSB0cmFuc2xhdGVzIHRvIFNRTDoNCg0KYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSBGfQ0KdGFpbG51bV9kZWxheV9kYiAlPiUgc2hvd19xdWVyeSgpDQpgYGANClRoaXMgXl4gaXMgZXhhY3RseSB0aGUgU1FMIHF1ZXJ5IHRoYXQgd2lsbCBiZSBydW4gaW4gTWFyaWFEQiB0byBwZXJmb3JtIHRoZSB3b3JrIGRlZmluZWQgYnkgdGhlIHtkcGx5cn0gcGlwZWxpbmUgdGhhdCBkZWZpbmVzIGB0YWlsbnVtX2RlbGF5X2RiYCBhYm92ZS4gRnJvbSBldmVyeXRoaW5nIHRoYXQgeW91IGFscmVhZHkgbm93IGFib3V0IFIgeW91IHdvdWxkIHByb2JhYmx5IHRoaW5rIHRoYXQgYHRhaWxudW1fZGVsYXlfZGJgIGhhcyBhIGBkYXRhLmZyYW1lYCBjbGFzcy4gTG9vazoNCg0KYGBge3IgZWNobyA9IFR9DQpjbGFzcyh0YWlsbnVtX2RlbGF5X2RiKQ0KYGBgDQpgdGJsX2xhenlgIDopLiBOb3cgd2UgYGNvbGxlY3QoKWAgb3VyIGB0YWlsbnVtX2RlbGF5X2RiYCBhbmQgb25seSB0aGF0IHRyaWdnZXJzIGFuIGFjdGlvbiBpbiBNYXJpYURCOg0KDQpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9IEZ9DQp0YWlsbnVtX2RlbGF5IDwtIHRhaWxudW1fZGVsYXlfZGIgJT4lIGNvbGxlY3QoKQ0KdGFpbG51bV9kZWxheQ0KYGBgDQoNCldlIGRvIG5vdCBuZWVkIHRoZSBgZmxpZ2h0c2AgdGFibGUgYW55bW9yZToNCg0KYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSBGfQ0KUk1hcmlhREI6OmRiUmVtb3ZlVGFibGUoY29uLCAnZmxpZ2h0cycpDQpSTWFyaWFEQjo6ZGJMaXN0VGFibGVzKGNvbikNCmBgYA0KYW5kIGJlZ2luIHVzaW5nIGBtdGNhcnNgIGFnYWluOg0KDQpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9IEZ9DQpSTWFyaWFEQjo6ZGJXcml0ZVRhYmxlKGNvbiwgJ210Y2FycycsIG10Y2FycykNCmBgYA0KDQpBZ2dyZWdhdGlvbiBieSBzZW5kaW5nIGEgU1FMIHF1ZXJ5IGRpcmVjdGx5Og0KDQpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9IEZ9DQpyZXMgPC0gZGJTZW5kUXVlcnkoY29uLA0KICAgICAgICAgICAgICAgICAgICAgIHN0YXRlbWVudCA9ICJTRUxFQ1QgY3lsLCBBVkcoZGlzcCkgQVMgYXZnX2Rpc3AsIEFWRyhkcmF0KSBBUyBhdmdfZHJhdCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlJPTSBtdGNhcnMgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEdST1VQIEJZIGN5bDsiKQ0KcmVzdWx0IDwtIGRiRmV0Y2gocmVzKQ0KZGJDbGVhclJlc3VsdChyZXMpDQpwcmludChyZXN1bHQpDQpgYGANCg0KTm93IG1ha2UgYSByZWZlcmVuY2UgdG8gYG10Y2Fyc2AgdG8gdXNlIHtkcGx5cn06DQoNCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gRn0NCm10Y2Fyc19kYiA8LSB0YmwoY29uLCAnbXRjYXJzJykNCmBgYA0KDQpUaGUgc2FtZSBhZ2dyZWdhdGlvbiBmcm9tIHtkcGx5cn06DQoNCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gRn0NCm10Y2Fyc19kYiAlPiUgc2VsZWN0KGN5bCwgZGlzcCwgZHJhdCkgJT4lIA0KICBncm91cF9ieShjeWwpICU+JSANCiAgc3VtbWFyaXNlKGF2Z19kaXNwID0gbWVhbihkaXNwKSwgDQogICAgICAgICAgICBhdmdfZHJhdCA9IG1lYW4oZHJhdCkpDQpgYGANCg0KUmVtb3ZlIGBtdGNhcnNgIGFuZCBkaXNjb25uZWN0Og0KDQpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9IEZ9DQpSTWFyaWFEQjo6ZGJSZW1vdmVUYWJsZShjb24sICdtdGNhcnMnKQ0KUk1hcmlhREI6OmRiRGlzY29ubmVjdChjb24pDQpgYGANCg0KDQojIyMgMi4ge2RwbHlyfSwgU1FMLCBpbmRleGVzLCBhbmQgcmVsYXRpb25zDQoNCkNvbm5lY3Q6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KZHJ2IDwtIFJNYXJpYURCOjpNYXJpYURCKCkNCmNvbiA8LSBkYkNvbm5lY3QoZHJ2LCANCiAgdXNlciA9ICJnb3JhbnNtIiwNCiAgZGJuYW1lID0gImRhdGFrb2xla3RpdiIsDQogIGhvc3QgPSAibG9jYWxob3N0IiwNCiAgcG9ydCA9IDMzMDYsDQogIHBhc3N3b3JkID0gcnN0dWRpb2FwaTo6YXNrRm9yUGFzc3dvcmQoIlBhc3N3b3JkOiIpDQopDQpgYGANCg0KQ29weSBgZmxpZ2h0c2AgYW5kIGBwbGFuZXNgIGZyb20gYG55Y2ZsaWdodHMxM2AgdG8gYGRhdGFrb2xla3RpdmAsIHRoaXMgdGltZSB1c2luZyBgUk1hcmlhREI6OmRiV3JpdGVUYWJsZSgpYCBpbiBwbGFjZSBvZiBgZHBseXI6OmNvcHlfdG8oKWAgKHRoaXMgbWlnaHQgdGFrZSBzb21lIHRpbWUpOg0KDQpgYGB7ciBlY2hvID0gVH0NClJNYXJpYURCOjpkYldyaXRlVGFibGUoY29uLA0KICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gImZsaWdodHMiLA0KICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IG55Y2ZsaWdodHMxMzo6ZmxpZ2h0cykNClJNYXJpYURCOjpkYldyaXRlVGFibGUoY29uLCANCiAgICAgICAgICAgICAgICAgICAgICAgInBsYW5lcyIsDQogICAgICAgICAgICAgICAgICAgICAgIG55Y2ZsaWdodHMxMzo6cGxhbmVzKQ0KDQpgYGANCg0KUmVtZW1iZXIgaG93IHdlIGhhdmUgdXNlZCBgaW5kZXhlc2AgaW4gb3VyIGBkcGx5cjo6Y29weV90bygpYCBjYWxsIHByZXZpb3VzbHk/IFdoYXQgYXJlIGRhdGFiYXNlICppbmRleGVzKj8NCg0KPiBBIGRhdGFiYXNlIGluZGV4IGlzIGEgZGF0YSBzdHJ1Y3R1cmUgdGhhdCBpbXByb3ZlcyB0aGUgc3BlZWQgb2YgZGF0YSByZXRyaWV2YWwgb3BlcmF0aW9ucyBvbiBhIGRhdGFiYXNlIHRhYmxlIGF0IHRoZSBjb3N0IG9mIGFkZGl0aW9uYWwgd3JpdGVzIGFuZCBzdG9yYWdlIHNwYWNlIHRvIG1haW50YWluIHRoZSBpbmRleCBkYXRhIHN0cnVjdHVyZS4gSW5kZXhlcyBhcmUgdXNlZCB0byBxdWlja2x5IGxvY2F0ZSBkYXRhIHdpdGhvdXQgaGF2aW5nIHRvIHNlYXJjaCBldmVyeSByb3cgaW4gYSBkYXRhYmFzZSB0YWJsZSBldmVyeSB0aW1lIGEgZGF0YWJhc2UgdGFibGUgaXMgYWNjZXNzZWQuIEluZGV4ZXMgY2FuIGJlIGNyZWF0ZWQgdXNpbmcgb25lIG9yIG1vcmUgY29sdW1ucyBvZiBhIGRhdGFiYXNlIHRhYmxlLCBwcm92aWRpbmcgdGhlIGJhc2lzIGZvciBib3RoIHJhcGlkIHJhbmRvbSBsb29rdXBzIGFuZCBlZmZpY2llbnQgYWNjZXNzIG9mIG9yZGVyZWQgcmVjb3Jkcy4gU291cmNlOiBbRGF0YWJhc2UgaW5kZXgsIGZyb20gRW5nbGlzaCBXaWtpcGVkaWFdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0RhdGFiYXNlX2luZGV4KQ0KDQpJbmRleGluZyBhIHRhYmxlIGluIGEgUkRCUyBpcyBhIHByZXR0eSBhZHZhbmNlZCB0b3BpYy4gV2Ugd2lsbCBkaXNjdXNzIG1vcmUgb2YgaXQgaW4gb3VyIFNlc3Npb24uDQoNCkluIFNlc3Npb24gMTEsIG5leHQgd2Vlaywgd2Ugd2lsbCBzZWUgaG93IHRoZSBwb3dlcmZ1bCB7ZGF0YS50YWJsZX0gUiBwYWNrYWdlIHVzZXMgaW5kZXhlcyBpbiBSIHRvIHNwZWVkIHVwIGRhdGEgcHJvY2Vzc2luZyBmb3Igb3JkZXJzIG9mIG1hZ25pdHVkZSBpbiBjb21wYXJpc29uIHRvIGJhc2UgUiBkYXRhZnJhbWVzIG9yIGRwbHlyIHRpYmJsZXMuIEZvciBub3csIGluZGV4aW5nIGBmbGlnaHRzYCBpbiBgZGF0YWtvbGVrdGl2YCB3aXRoIHRoZSBmb2xsb3dpbmcgc2V0IG9mIFNRTCBxdWVyaWVzOg0KDQpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9IEZ9DQojIGluZGV4ZXMgPSBsaXN0KGMoInllYXIiLCAibW9udGgiLCAiZGF5IiksDQojICAgICAgICAgICAgICAgICJjYXJyaWVyIiwgInRhaWxudW0iLCAiZGVzdCIpDQpxdWVyeSA8LSBjKCdDUkVBVEUgSU5ERVggaXhjYXJyaWVyIE9OIGZsaWdodHMoY2FycmllciknLCANCiAgICAgICAgICAgJ0NSRUFURSBJTkRFWCBpeHRhaWxudW0gT04gZmxpZ2h0cyh0YWlsbnVtKTsnLA0KICAgICAgICAgICAnQ1JFQVRFIElOREVYIGl4ZGVzdCBPTiBmbGlnaHRzKGRlc3QpOycpDQpsYXBwbHkocXVlcnksIGZ1bmN0aW9uKHgpIHsNCiAgcmVzIDwtIERCSTo6ZGJTZW5kUXVlcnkoY29uLCB4KQ0KICBEQkk6OmRiQ2xlYXJSZXN1bHQocmVzKQ0KfSkNCnF1ZXJ5IDwtICdTSE9XIElOREVYIEZST00gZmxpZ2h0czsnDQpyZXMgPC0gREJJOjpkYlNlbmRRdWVyeShjb24sIHF1ZXJ5KQ0KcmVzdWx0IDwtIERCSTo6ZGJGZXRjaChyZXMpDQpEQkk6OmRiQ2xlYXJSZXN1bHQocmVzKQ0KcHJpbnQocmVzdWx0KQ0KYGBgDQpOb3cgdGhlIGNvbXBvc2l0ZSBpbmRleCBvdmVyIGB5ZWFyYCwgYG1vbnRoYCwgYW5kIGBkYXlgOg0KDQpgYGB7ciBlY2hvID0gVH0NCiMgaW5kZXhlcyA9IGxpc3QoYygieWVhciIsICJtb250aCIsICJkYXkiKSwNCiMgICAgICAgICAgICAgICAgImNhcnJpZXIiLCAidGFpbG51bSIsICJkZXN0IikNCnF1ZXJ5IDwtICdDUkVBVEUgSU5ERVggaXhkbXkgT04gZmxpZ2h0cyh5ZWFyLCBtb250aCwgZGF5KScNCnJlcyA8LSBEQkk6OmRiU2VuZFF1ZXJ5KGNvbiwgcXVlcnkpDQpEQkk6OmRiQ2xlYXJSZXN1bHQocmVzKQ0KcXVlcnkgPC0gJ1NIT1cgSU5ERVggRlJPTSBmbGlnaHRzOycNCnJlcyA8LSBEQkk6OmRiU2VuZFF1ZXJ5KGNvbiwgcXVlcnkpDQpyZXN1bHQgPC0gREJJOjpkYkZldGNoKHJlcykNCkRCSTo6ZGJDbGVhclJlc3VsdChyZXMpDQpwcmludChyZXN1bHQpDQpgYGANCg0KU28gaG93IGRvZXMgYGZsaWdodHNgIGxvb2sgbGlrZSBpbiBgZGF0YWtvbGVrdGl2YD8NCg0KYGBge3IgZWNobyA9IFR9DQpxdWVyeSA8LSAnREVTQ1JJQkUgZmxpZ2h0cycNCnJlcyA8LSBEQkk6OmRiU2VuZFF1ZXJ5KGNvbiwgcXVlcnkpDQpyZXN1bHQgPC0gREJJOjpkYkZldGNoKHJlcykNCkRCSTo6ZGJDbGVhclJlc3VsdChyZXMpDQpwcmludChyZXN1bHQpDQpgYGANCg0KQW5kIHdoYXQgZG8gd2UgaGF2ZSBpbiBgcGxhbmVzYD8NCg0KYGBge3IgZWNobyA9IFR9DQpxdWVyeSA8LSAnREVTQ1JJQkUgcGxhbmVzJw0KcmVzIDwtIERCSTo6ZGJTZW5kUXVlcnkoY29uLCBxdWVyeSkNCnJlc3VsdCA8LSBEQkk6OmRiRmV0Y2gocmVzKQ0KREJJOjpkYkNsZWFyUmVzdWx0KHJlcykNCnByaW50KHJlc3VsdCkNCmBgYA0KSW50ZXJlc3RpbmcsIGZvciBlYWNoIGZsaWdodCBpbiBgZmxpZ2h0c2Agd2Uga25vdyB0aGUgdGFpbCBudW1iZXIgYHRhaWxudW1gIG9mIHRoZSBwbGFuZSB3aGljaCBpcyBhbHNvIHByZXNlbnQgaW4gYHBsYW5lc2AuIEkgYW0gYSBjdXJpb3VzIERhdGEgU2NpZW50aXN0IGFuZCBJIHdhbnQgdG8gc2VlIHdoYXQgYXJlIHRoZSB0ZWNobmljYWwgY2hhcmFjdGVyaXN0aWNzIGEgcGxhbmUgdGhhdCBmbGV3IGVhY2ggZmxpZ2h0IGluIGBmbGlnaHRzYCwgc28gSSBuZWVkIHRvIGpvaW4gdGhlbSwgcmlnaHQ/IHtkcGx5cn06DQoNCmBgYHtyIGVjaG8gPSBUfQ0KZmxpZ2h0c19kYiA8LSB0YmwoY29uLCAiZmxpZ2h0cyIpDQpwbGFuZXNfZGIgPC0gdGJsKGNvbiwgInBsYW5lcyIpDQpmbGlnaHRzX3JlbGF0aW9ucyA8LSBkcGx5cjo6bGVmdF9qb2luKGZsaWdodHNfZGIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbGFuZXNfZGIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9ICJ0YWlsbnVtIikNCmBgYA0KDQpTby4uLiB3aGF0IGlzIGBmbGlnaHRzX3JlbGF0aW9uc2A/DQoNCmBgYHtyIGVjaG8gPSBUfQ0KY2xhc3MoZmxpZ2h0c19yZWxhdGlvbnMpDQpgYGANCk9mIGNvdXJzZSwgYGNvbGxlY3QoKWA6DQooKipub3RlKio6IHBsZWFzZSBiZSBwYXRpZW50KQ0KDQpgYGB7ciBlY2hvID0gVH0NCmZsaWdodHNGcmFtZSA8LSBmbGlnaHRzX3JlbGF0aW9ucyAlPiUgDQogIGNvbGxlY3QoKQ0KaGVhZChmbGlnaHRzRnJhbWUpDQpgYGANCg0KSG93IHdvdWxkIHRoYXQgYGxlZnRfam9pbigpYCBsb29rIGluIFNRTD8gV2VsbCwgYSBiaXQgY2x1bXN5IGluIG15IFNRTCBjb2RlIGJlY2F1c2UgSSBuZWVkZWQgdG8gY29uc2lkZXIgdGhlIGZhY3QgdGhhdCBgdGFpbG51bWAgaXMgcHJlc2VudCBpbiBib3RoIGBmbGlnaHRzYCBhbmQgYHBsYW5lc2Agd2hpY2ggd291bGQgdGhlbiBjYXVzZSBhIGR1cGxpY2F0ZWQgY29sdW1uIG5hbWUgaW4gdGhlIHJlc3VsdCBzZXQsIGJ1dCBhZnRlciBzb21lIHN0cnVnZ2xlLi4uDQooKipub3RlKio6IHBsZWFzZSBiZSBwYXRpZW50KQ0KDQpgYGB7ciBlY2hvID0gVH0NCnF1ZXJ5IDwtICdDUkVBVEUgVEFCTEUgZmxpZ2h0c2ZyYW1lDQogICAgICAgICAgICBBUyAoDQogICAgICAgICAgICAgIFNFTEVDVCBmbGlnaHRzLmZsaWdodCwNCiAgICAgICAgICAgICAgICAgICAgIGZsaWdodHMub3JpZ2luLA0KICAgICAgICAgICAgICAgICAgICAgZmxpZ2h0cy5kZXN0LA0KICAgICAgICAgICAgICAgICAgICAgZmxpZ2h0cy5haXJfdGltZSwNCiAgICAgICAgICAgICAgICAgICAgIGZsaWdodHMuZGlzdGFuY2UsDQogICAgICAgICAgICAgICAgICAgICBwbGFuZXMueWVhciwNCiAgICAgICAgICAgICAgICAgICAgIHBsYW5lcy50eXBlLA0KICAgICAgICAgICAgICAgICAgICAgcGxhbmVzLm1hbnVmYWN0dXJlciwgDQogICAgICAgICAgICAgICAgICAgICBwbGFuZXMubW9kZWwsIA0KICAgICAgICAgICAgICAgICAgICAgcGxhbmVzLmVuZ2luZXMsDQogICAgICAgICAgICAgICAgICAgICBwbGFuZXMuc2VhdHMsIA0KICAgICAgICAgICAgICAgICAgICAgcGxhbmVzLnNwZWVkLA0KICAgICAgICAgICAgICAgICAgICAgcGxhbmVzLmVuZ2luZQ0KICAgICAgICAgICAgICBGUk9NIGZsaWdodHMgTEVGVCBKT0lOIHBsYW5lcyBVU0lORyh0YWlsbnVtKSk7Jw0KcmVzIDwtIERCSTo6ZGJTZW5kUXVlcnkoY29uLCBxdWVyeSkNCkRCSTo6ZGJDbGVhclJlc3VsdChyZXMpDQpgYGANCg0KTGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIHJlc3VsdGluZyBgZmxpZ2h0c2ZyYW1lYCB0YWJsZSBpbiBgZGF0YWtvbGVrdGl2YDoNCg0KYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSBGfQ0KcXVlcnkgPC0gJ0RFU0NSSUJFIGZsaWdodHNmcmFtZScNCnJlcyA8LSBEQkk6OmRiU2VuZFF1ZXJ5KGNvbiwgcXVlcnkpDQpyZXN1bHQgPC0gREJJOjpkYkZldGNoKHJlcykNCkRCSTo6ZGJDbGVhclJlc3VsdChyZXMpDQpwcmludChyZXN1bHQpDQpgYGANCg0KQW5kIHRoZW4gaWYgd2Ugd2FudCB0byBjb250aW51ZSBpbiBSQU0gcHJvY2Vzc2luZyBpbiBSOg0KDQpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9IEZ9DQpybShmbGlnaHRzX2ZyYW1lKQ0KZmxpZ2h0c19mcmFtZSA8LSBSTWFyaWFEQjo6ZGJSZWFkVGFibGUoY29uLCAnZmxpZ2h0c2ZyYW1lJykNCmhlYWQoZmxpZ2h0c19mcmFtZSkNCmBgYA0KDQpDbGVhciBhbGw7IGRpc2Nvbm5lY3Q6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KUk1hcmlhREI6OmRiUmVtb3ZlVGFibGUoY29uLCAnZmxpZ2h0cycpDQpSTWFyaWFEQjo6ZGJSZW1vdmVUYWJsZShjb24sICdwbGFuZXMnKQ0KUk1hcmlhREI6OmRiUmVtb3ZlVGFibGUoY29uLCAnZmxpZ2h0c2ZyYW1lJykNClJNYXJpYURCOjpkYkRpc2Nvbm5lY3QoY29uKQ0KYGBgDQoNCiMjIyAzLiB0LXRlc3QgZm9yIHVucGFpcmVkIHNhbXBsZXMNCg0KSW4gTGFiMDIgd2UgaGF2ZSBpbnRyb2R1Y2VkIHRoZSB0LXRlc3QgdG8gdGVzdCBpZiBhIHNhbXBsZSBtZWFuIGlzIHJlYWxseSBvYnRhaW5lZCBmcm9tIGEgcG9wdWxhdGlvbiB3aXRoIHNvbWUgcHJlZGVmaW5lZCwga25vd24gbWVhbi4gVGhlIHQtdGVzdCBjYW4gYmUgdXNlZCBpbiB2YXJpb3VzIHNldHRpbmdzIGFuZCBub3cgd2Ugd2lsbCBzZWUgaG93IHRvIHVzZSBpdCB0byBjb21wYXJlIGlmIHR3byBzYW1wbGUgbWVhbnMgYXJlIGRyYXduIGZyb20gdGhlIHNhbWUgb3IgZGlmZmVyZW50IHBvcHVsYXRpb25zLCBpLmUuIGlzIHRoZWlyIGRpZmZlcmVuY2Ugc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBvciBub3QuDQoNClRoZSBzcGVjaWZpYyBjYXNlIHRoYXQgd2Ugd2lsbCBjb25zaWRlciBpcyB0aGUgdC10ZXN0IGZvciAqdW5wYWlyZWQgc2FtcGxlcyouIEltYWdpbmUgd2UgdGVzdCBhIGdyb3VwIG9mIG1lbiBhbmQgd29tZW4gb24gdGhlIHNhbWUgdGFzayBhbmQgd2lzaCB0byBjb21wYXJlIHRoZWlyIG1lYW4gcGVyZm9ybWFuY2VzLiBPYnZpb3VzbHksIG5vIG1hbiBmcm9tIHRoZSBmaXJzdCBncm91cCBjb3VsZCBoYXZlIGFsc28gYmVlbiBhIG1lbWJlciBvZiB0aGUgc2Vjb25kIGdyb3VwLCBhbmQgKnZpY2UgdmVyc2EqLCBubyB3b21hbiBmcm9tIHRoZSBzZWNvbmQgZ3JvdXAgY291bGQgYWxzbyBoYXZlIGJlZW4gYSBtZW1iZXIgb2YgdGhlIGZpcnN0IGdyb3VwLiBUaGlzIGlzIGFuIGV4YW1wbGUgb2YgYSAqYmV0d2Vlbi1zdWJqZWN0cyogbWVhc3VyZW1lbnQsIHdoZXJlIHRoZXJlIGFyZSB0d28gc2V0cyBvZiBtZWFzdXJlbWVudHMgb2J0YWluZWQgZnJvbSBzb21lICJvYmplY3RzIiBvZiBtZWFzdXJlbWVudCB0aGF0IGFyZSBub3QgcmVsYXRlZCBub3IgaW50ZXJjaGFuZ2VhYmxlIGluIGFueSB3YXkuIEltYWdpbmUgaWYgd2UgdGVzdCBhIGdyb3VwIG9mIG1lbiBvbiB0d28gcmVsYXRlZCBidXQgc3BlY2lmaWMgdGFza3MsIGFuZCB3ZSB3YW50IHRvIGNvbXBhcmUgdGhlaXIgbWVhbiBwZXJmb3JtYW5jZSBpbiB0aGUgZmlyc3QgdGVzdCBhZ2FpbnN0IHRoZWlyIG1lYW4gcGVyZm9ybWFuY2UgaW4gdGhlIHNlY29uZCB0ZXN0LiBOb3csIGVhY2ggbWVtYmVyIG9mIHRoZSBzdHVkeSBzYW1wbGUgcHJvdmlkZXMgYSBwZXJmb3JtYW5jZSBzY29yZSB0d2ljZTogb25jZSBpbiB0aGUgZmlyc3QgYW5kIHRoZW4gYWdhaW4gaW4gdGhlIHNlY29uZCB0ZXN0IHNpdHVhdGlvbi4gSW4gdGhhdCBjYXNlLCB0byB0ZXN0IHdoZXRoZXIgdGhlIG1lYW4gcGVyZm9ybWFuY2VzIGFyZSBhbnkgZGlmZmVyZW50LCB3ZSB3b3VsZCB1c2UgYSAqcGFpcmVkIHNhbXBsZXMgdC10ZXN0KiAtIHdoaWNoIHdlIHdpbGwgbm90IGRpc2N1c3MgdG9kYXkgLSBhbmQgdGhlIG1lYXN1cmUgaXMgc2FpZCB0byBiZSAqd2l0aGluLXN1YmplY3RzKi4NCg0KYGBge3IgZWNobyA9IFR9DQp0YWJsZShmbGlnaHRzX2ZyYW1lJG1vZGVsKSAlPiUgDQogIGFzLmRhdGEuZnJhbWUoc3RyaW5nc0FzRmFjdG9ycyA9IEYpICU+JSANCiAgYXJyYW5nZShkZXNjKEZyZXEpKSAlPiUgDQogIGhlYWQoMTApDQpgYGANClRoaXMgaXMgdGhlIGxpc3Qgb2YgYWlycGxhbmUgbW9kZWxzIHRoYXQgYXJlIG1vc3QgZnJlcXVlbnRseSBvYnNlcnZlZCBpbiBgZmxpZ2h0c19mcmFtZWAuIExldCdzIGZvY3VzIG9uIHRoZSB0d28gbW9zdCBmcmVxdWVudGx5IG9ic2VydmVkIG1vZGVscyBhbmQgYXNrIHRoZXJlIGFyZSBhbnkgZGlmZmVyZW5jZXMgb24gdGhlIGBhaXJfdGltZWAgdmFyaWFibGUgYmV0d2VlbiB0aGUgZmxpZ2h0cyBwZXJmb3JtZWQgYnkgYEEzMjAtMjMyYCBhbmQgZmxpZ2h0cyBwZXJmb3JtZWQgYnkgYEVNQi0xNDVMUmA6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KbW9kZWxzIDwtIGMoJ0EzMjAtMjMyJywgJ0VNQi0xNDVMUicpDQp0dGVzdFRhYmxlIDwtIGZsaWdodHNfZnJhbWUgJT4lIA0KICBzZWxlY3QoZGlzdGFuY2UsIGFpcl90aW1lLCBtb2RlbCkgJT4lIA0KICBmaWx0ZXIobW9kZWwgJWluJSBtb2RlbHMpICU+JSANCiAgbmEub21pdA0KaGVhZCh0dGVzdFRhYmxlKQ0KYGBgDQoNCkhvdyBtYW55IGZsaWdodHMsIHBlciBhaXJwbGFuZSBtb2RlbCwgYXJlIHNlbGVjdGVkPw0KDQpgYGB7ciBlY2hvID0gVH0NCnRhYmxlKHR0ZXN0VGFibGUkbW9kZWwpDQpgYGANClRoaXMgaXMgbm90IGdvb2QuIEluIGEgdC10ZXN0IHNldHRpbmcsIHdlIHdvdWxkIGxpa2UgdG8gaGF2ZSB1bnBhaXJlZCBzYW1wbGVzIG9mIGF0IGxlYXN0IGFwcHJveGltYXRlbHkgc2FtZSBzaXplLiBIZXJlIGdvZXMgYSBiaXQgb2Ygc2FtcGxpbmcgbWFnaWM6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KcHJvcHMgPC0gYXMubnVtZXJpYygNCiAgdGFibGUodHRlc3RUYWJsZSRtb2RlbCkvc3VtKHRhYmxlKHR0ZXN0VGFibGUkbW9kZWwpKQ0KKQ0KcHJvcHMNCmBgYA0KSXQgaXMgbm93IGVhc3ksIGp1c3Qgc2FtcGxlIHRoZSBsYXJnZXIgY2xhc3MgaW4gYSBwcm9wb3J0aW9uIG9mIHRoZSBzbWFsbGVyIG9uZSwgYW5kIHRoZSBzbWFsbGVyIGNsYXNzIGluIGEgcHJvcG9ydGlvbiBvZiB0aGUgbGFyZ2VyIG9uZToNCg0KYGBge3IgZWNobyA9IFR9DQp0dGVzdFRhYmxlJHNlbGVjdCA8LSBzYXBwbHkodHRlc3RUYWJsZSRtb2RlbCwgZnVuY3Rpb24oeCkgew0KICBpZiAoeCA9PSAnRU1CLTE0NUxSJykgew0KICAgIHJiaW5vbSgxLCAxLCBwcm9wc1sxXSkNCiAgfSBlbHNlIHsNCiAgICByYmlub20oMSwgMSwgcHJvcHNbMl0pDQogIH0NCn0pDQp0YWJsZSh0dGVzdFRhYmxlJHNlbGVjdCkNCmBgYA0KV2UganVzdCBuZWVkIHRvIHBpY2stdXAgdGhlIGAxc2Agb24gYHR0ZXN0VGFibGUkc2VsZWN0YDoNCg0KYGBge3IgZWNobyA9IFR9DQp0dGVzdFRhYmxlIDwtIHR0ZXN0VGFibGVbdHRlc3RUYWJsZSRzZWxlY3QgPT0gMSwgXQ0KdGFibGUodHRlc3RUYWJsZSRtb2RlbCkNCmBgYA0KQWxtb3N0IHRoZXJlLiBUaGUgdC10ZXN0IG5vdyENCg0KIyMjIyAzLjEgSG93IHRvIHBlcmZvcm0gYSB0LXRlc3QgZm9yIHVucGFpcmVkIHNhbXBsZXMgaW4gUg0KDQoNCmBgYHtyIGVjaG8gPSBUfQ0KdC50ZXN0KHggPSB0dGVzdFRhYmxlJGRpc3RhbmNlW3R0ZXN0VGFibGUkbW9kZWwgPT0gIkEzMjAtMjMyIl0sDQogICAgICAgeSA9IHR0ZXN0VGFibGUkZGlzdGFuY2VbdHRlc3RUYWJsZSRtb2RlbCA9PSAiRU1CLTE0NUxSIl0sIA0KICAgICAgIGFsdGVybmF0aXZlID0gInR3by5zaWRlZCIsIA0KICAgICAgIHZhci5lcXVhbCA9IFQpDQpgYGANCg0KTm90ZSB0aGUgYHZhci5lcXVhbCA9IFRgIGFyZ3VtZW50IGluIHRoZSBgdC50ZXN0KClgIGNhbGwuIE9uZSBvZiB0aGUgYXNzdW1wdGlvbnMgb2YgdGhlIHQtdGVzdCBpcyB0aGF0IHRoZSBtZWFzdXJlIHZhcmlhbmNlcyBpbiBib3RoIGdyb3VwcyAqYXJlIGVxdWFsKiwgd2hpY2ggaXMgYWxtb3N0IGNlcnRhaW5seSBub3QgdGhlIGNhc2UgaGVyZS4gQnV0IHdlIGhhdmUgYSBiaWdnZXIgcHJvYmxlbTogdGhlIHQtdGVzdCBpcyBtZWFudCBmb3Igbm9ybWFsbHkgZGlzdHJpYnV0ZWQgZGF0YS4uLiANCg0KYGBge3IgZWNobyA9IFR9DQp0dGVzdFBsb3RGcmFtZSA8LSB0dGVzdFRhYmxlICU+JSANCiAgc2VsZWN0KG1vZGVsLCBkaXN0YW5jZSkNCmdncGxvdCh0dGVzdFBsb3RGcmFtZSwgDQogICAgICAgYWVzKHggPSBkaXN0YW5jZSwNCiAgICAgICAgICAgZ3JvdXAgPSBtb2RlbCwNCiAgICAgICAgICAgZmlsbCA9IG1vZGVsKSkgKyANCiAgZ2VvbV9kZW5zaXR5KGFscGhhID0gLjUsIGNvbG9yID0gImJsYWNrIikgKyANCiAgZ2d0aXRsZSgiTllDIEZsaWdodHMgRGF0YXNldCIpICsgDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoJ2RlZXBza3libHVlJywgJ2RhcmtvcmFuZ2UnKSkgKyANCiAgdGhlbWVfYncoKSArIA0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gLjUsIHNpemUgPSA5KSkgKyANCiAgdGhlbWUocGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSkgKyANCiAgdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKSArIA0KICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgpKSArIA0KICB0aGVtZShsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gOCkpDQpgYGANCk5vdCBldmVuIHJlbW90ZWx5Og0KDQpgYGB7ciBlY2hvID0gVH0NCmtzLnRlc3QodHRlc3RUYWJsZSRkaXN0YW5jZVt0dGVzdFRhYmxlJG1vZGVsID09ICJBMzIwLTIzMiJdLCANCiAgICAgICAgeSA9ICJwbm9ybSIpDQpgYGANCg0KYGBge3IgZWNobyA9IFR9DQprcy50ZXN0KHR0ZXN0VGFibGUkZGlzdGFuY2VbdHRlc3RUYWJsZSRtb2RlbCA9PSAiRU1CLTE0NUxSIl0sIA0KICAgICAgICB5ID0gInBub3JtIikNCmBgYA0KDQojIyMjIDMuMiBOb3JtYWxseSBkaXN0cmlidXRlZCBkYXRhIGluIGBpcmlzYA0KDQpTbyBub3cgd2Ugbm93IGhvdyB0byB1c2UgYSB0LXRlc3QgaW4gYW4gdW5wYWlyZWQgc2FtcGxlcyBzZXR0aW5nLiBMZXQncyBnbyBmb3Igc29tZSBhdCBsZWFzdCBhcHByb3hpbWF0ZWx5IG5vcm1hbGx5IGRpc3RyaWJ1dGVkIGRhdGEuIA0KDQpgYGB7ciBlY2hvID0gVH0NCmhlYWQoaXJpcykNCmBgYA0KDQpgYGB7ciBlY2hvID0gVH0NCnNoYXBpcm8udGVzdChpcmlzJFNlcGFsLkxlbmd0aFtpcmlzJFNwZWNpZXMgPT0gInNldG9zYSJdKQ0KYGBgDQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc2hhcGlyby50ZXN0KGlyaXMkU2VwYWwuTGVuZ3RoW2lyaXMkU3BlY2llcyA9PSAidmVyc2ljb2xvciJdKQ0KYGBgDQoNCmBgYHtyIGVjaG8gPSBUfQ0KZ2dwbG90KGlyaXMgJT4lIA0KICAgICAgICAgZmlsdGVyKFNwZWNpZXMgIT0gJ3ZpcmdpbmljYScpLCANCiAgICAgICBhZXMoeCA9IFNlcGFsLkxlbmd0aCwNCiAgICAgICAgICAgZ3JvdXAgPSBTcGVjaWVzLA0KICAgICAgICAgICBmaWxsID0gU3BlY2llcykpICsgDQogIGdlb21fZGVuc2l0eShhbHBoYSA9IC41LCBjb2xvciA9ICJibGFjayIpICsgDQogIGdndGl0bGUoIklyaXMgRGF0YXNldCIpICsgDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoJ2RlZXBza3libHVlJywgJ2RhcmtvcmFuZ2UnKSkgKw0KICB0aGVtZV9idygpICsgDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAuNSwgc2l6ZSA9IDkpKSArIA0KICB0aGVtZShwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpKSArIA0KICB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCkpICsgDQogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOCkpICsgDQogIHRoZW1lKGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSA4KSkNCmBgYA0KQm94cGxvdDoNCg0KYGBge3IgZWNobyA9IFR9DQpnZ3Bsb3QoaXJpcyAlPiUgDQogICAgICAgICBmaWx0ZXIoU3BlY2llcyAhPSAndmlyZ2luaWNhJyksIA0KICAgICAgIGFlcyh4ID0gU3BlY2llcywNCiAgICAgICAgICAgeSA9IFNlcGFsLkxlbmd0aCwNCiAgICAgICAgICAgZ3JvdXAgPSBTcGVjaWVzLA0KICAgICAgICAgICBmaWxsID0gU3BlY2llcykpICsgDQogIGdlb21fYm94cGxvdCgpICsgDQogIGdndGl0bGUoIklyaXMgRGF0YXNldCIpICsgDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoJ2RlZXBza3libHVlJywgJ2RhcmtvcmFuZ2UnKSkgKw0KICB0aGVtZV9idygpICsgDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAuNSwgc2l6ZSA9IDkpKSArIA0KICB0aGVtZShwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpKSArIA0KICB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCkpICsgDQogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOCkpICsgDQogIHRoZW1lKGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSA4KSkNCmBgYA0KSXQgZGVmaW5pdGVseSBsb29rcyBsaWtlIHRoZXJlIGlzIGEgZGlmZmVyZW5jZSwgdGhlIGRhdGEgc2VlbSB0byBmb2xsb3cgYSBub3JtYWwgZGlzdHJpYnV0aW9uLi4uDQoNCmBgYHtyIGVjaG8gPSBUfQ0KdC50ZXN0KHggPSBpcmlzJFNlcGFsLkxlbmd0aFtpcmlzJFNwZWNpZXMgPT0gInNldG9zYSJdLA0KICAgICAgIHkgPSBpcmlzJFNlcGFsLkxlbmd0aFtpcmlzJFNwZWNpZXMgPT0gInZlcnNpY29sb3IiXSwgDQogICAgICAgYWx0ZXJuYXRpdmUgPSAidHdvLnNpZGVkIiwgDQogICAgICAgdmFyLmVxdWFsID0gVCkNCmBgYA0KR3JlYXQuIEJ1dC4uLg0KDQpgYGB7ciBlY2hvID0gVH0NCnZhcihpcmlzJFNlcGFsLkxlbmd0aFtpcmlzJFNwZWNpZXMgPT0gInNldG9zYSJdKQ0KYGBgDQoNCmBgYHtyIGVjaG8gPSBUfQ0KdmFyKGlyaXMkU2VwYWwuTGVuZ3RoW2lyaXMkU3BlY2llcyA9PSAidmVyc2ljb2xvciJdKQ0KYGBgDQpJdCBkb2VzIG5vdCBsb29rIGdvb2Q6IHRoZSBgdmVyc2ljb2xvcmAgdmFyaWFuY2Ugb24gYFNlcGFsLkxlbmd0aGAgc2VlbXMgdG8gYmUgbW9yZSB0aGFuIHR3aWNlIGxhcmdlIHRoYW4gaW4gdGhlIGBzZXRvc2FgIGdyb3VwLiBBIGNvcnJlY3Rpb24gbmVlZHMgdG8gYmUgYXBwbGllZC4NCg0KIyMjIyAzLjMgV2VsY2ggdGVzdDogdW5lcXVhbCBncm91cCB2YXJpYW5jZXMNCg0KQ2hhbmdlOiBgdmFyLmVxdWFsID0gRmAgdG8gcGVyZm9ybSBhICpXZWxjaCB0ZXN0KjoNCg0KYGBge3IgZWNobyA9IFR9DQp0LnRlc3QoeCA9IGlyaXMkU2VwYWwuTGVuZ3RoW2lyaXMkU3BlY2llcyA9PSAic2V0b3NhIl0sDQogICAgICAgeSA9IGlyaXMkU2VwYWwuTGVuZ3RoW2lyaXMkU3BlY2llcyA9PSAidmVyc2ljb2xvciJdLCANCiAgICAgICBhbHRlcm5hdGl2ZSA9ICJ0d28uc2lkZWQiLCANCiAgICAgICB2YXIuZXF1YWwgPSBGKQ0KYGBgDQoNCg0KIyMjIEZ1cnRoZXIgUmVhZGluZ3MNCg0KLSBbSW5zdGFsbCBNYXJpYURCIFR1dG9yaWFsXShodHRwczovL3d3dy5tYXJpYWRidHV0b3JpYWwuY29tL2dldHRpbmctc3RhcnRlZC9pbnN0YWxsLW1hcmlhZGIvKQ0KLSBbSG93IFRvIENyZWF0ZSBNYXJpYURCIFVzZXIgQW5kIEdyYW50IFByaXZpbGVnZSBmcm9tIFBob2VuaXhOYXBdKGh0dHBzOi8vcGhvZW5peG5hcC5jb20va2IvaG93LXRvLWNyZWF0ZS1tYXJpYWRiLXVzZXItZ3JhbnQtcHJpdmlsZWdlcykNCi0gW1VzaW5nIGRwbHlyIHdpdGggZGF0YWJhc2VzLCBSU3R1ZGlvXShodHRwczovL2RiLnJzdHVkaW8uY29tL2RwbHlyLykNCi0gW3tSTWFyaWFEQn0gcGFja2FnZV0oaHR0cHM6Ly9naXRodWIuY29tL3ItZGJpL1JNYXJpYURCKQ0KLSBbdzNzY2hvb2xzOiBTUUwgVHV0b3JpYWxdKGh0dHBzOi8vd3d3Lnczc2Nob29scy5jb20vc3FsLykNCi0gW0ludHJvZHVjdGlvbiB0byBEQkkgSmFtZXMgV29uZHJhc2VrLCBLYXRoYXJpbmEgQnJ1bm5lciwgS2lyaWxsIE3DvGxsZXJdKGh0dHBzOi8vZGJpLnItZGJpLm9yZy9hcnRpY2xlcy9kYmkpDQotIFtVbnBhaXJlZCBUd28tU2FtcGxlcyBULXRlc3QgaW4gUiBmcm9tIFNUSERBXShodHRwOi8vd3d3LnN0aGRhLmNvbS9lbmdsaXNoL3dpa2kvdW5wYWlyZWQtdHdvLXNhbXBsZXMtdC10ZXN0LWluLXIpDQoNCg0KIyMjIFIgTWFya2Rvd24NCg0KW1IgTWFya2Rvd25dKGh0dHBzOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tLykgaXMgd2hhdCBJIGhhdmUgdXNlZCB0byBwcm9kdWNlIHRoaXMgYmVhdXRpZnVsIE5vdGVib29rLiBXZSB3aWxsIGxlYXJuIG1vcmUgYWJvdXQgaXQgbmVhciB0aGUgZW5kIG9mIHRoZSBjb3Vyc2UsIGJ1dCBpZiB5b3UgYWxyZWFkeSBmZWVsIHJlYWR5IHRvIGRpdmUgZGVlcCwgaGVyZSdzIGEgYm9vazogW1IgTWFya2Rvd246IFRoZSBEZWZpbml0aXZlIEd1aWRlLCBZaWh1aSBYaWUsIEouIEouIEFsbGFpcmUsIEdhcnJldHQgR3JvbGVtdW5kcy5dKGh0dHBzOi8vYm9va2Rvd24ub3JnL3lpaHVpL3JtYXJrZG93bi8pIA0KDQoNCioqKg0KR29yYW4gUy4gTWlsb3Zhbm92acSHDQoNCkRhdGFLb2xla3RpdiwgMjAyMC8yMQ0KDQpjb250YWN0OiBnb3Jhbi5taWxvdmFub3ZpY0BkYXRha29sZWt0aXYuY29tDQoNCiFbXSguLi9faW1nL0RLX0xvZ29fMTAwLnBuZykNCg0KKioqDQpMaWNlbnNlOiBbR1BMdjNdKGh0dHA6Ly93d3cuZ251Lm9yZy9saWNlbnNlcy9ncGwtMy4wLnR4dCkNClRoaXMgTm90ZWJvb2sgaXMgZnJlZSBzb2Z0d2FyZTogeW91IGNhbiByZWRpc3RyaWJ1dGUgaXQgYW5kL29yIG1vZGlmeSBpdCB1bmRlciB0aGUgdGVybXMgb2YgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGFzIHB1Ymxpc2hlZCBieSB0aGUgRnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9uLCBlaXRoZXIgdmVyc2lvbiAzIG9mIHRoZSBMaWNlbnNlLCBvciAoYXQgeW91ciBvcHRpb24pIGFueSBsYXRlciB2ZXJzaW9uLg0KVGhpcyBOb3RlYm9vayBpcyBkaXN0cmlidXRlZCBpbiB0aGUgaG9wZSB0aGF0IGl0IHdpbGwgYmUgdXNlZnVsLCBidXQgV0lUSE9VVCBBTlkgV0FSUkFOVFk7IHdpdGhvdXQgZXZlbiB0aGUgaW1wbGllZCB3YXJyYW50eSBvZiBNRVJDSEFOVEFCSUxJVFkgb3IgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UuICBTZWUgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGZvciBtb3JlIGRldGFpbHMuDQpZb3Ugc2hvdWxkIGhhdmUgcmVjZWl2ZWQgYSBjb3B5IG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBhbG9uZyB3aXRoIHRoaXMgTm90ZWJvb2suIElmIG5vdCwgc2VlIDxodHRwOi8vd3d3LmdudS5vcmcvbGljZW5zZXMvPi4NCg0KKioqDQoNCg==