Introduction

The overall goal of this tutorial is to load a CSV file, split the data into multiple tables that follow normal forms, create tables in a database, and then save the data from the CSV via data frames to the tables in the database. We will demonstrate several important methods and approaches. Of course, there are many other approaches that are equally valid. In fact, the approaches shown here may not be the most efficient in terms of memory use and speed, but they are simple to implement and easy to follow.

All work occurs in the R Notebook and is thus reproducible.

Note that the use of dbWriteTable() generally requires that “bulk or batch loading” is enabled for the database. It will work with SQLite, but this feature may not be available for some cloud implementations of MySQL. For example, dbWriteTable() does not work with MySQL on db4free.net. It does work with local installations of MySQL or a MySQL RDS on AWS.


If you are less experienced with R programming or find this tutorial a bit too complex to follow, consider reading this simpler tutorial first: 50.302A Load Subset of Birdstrikes Sample Data from CSV into Database in R.


Connect to Database

Since we will work with SQLite and MySQL, we will make two separate database connections.

First, we will create a new in-memory SQLite database. The code below makes a connection to an in-memory temporary SQLite database that will not persist once the program terminates. This is useful for testing or quick ad hoc work.

# change this code to connect to your specific database
library(RSQLite, quietly=T)

# this makes a connection to an in-memory temporary SQLite database
dbcon <- dbConnect(RSQLite::SQLite(), ":memory:")

Next, we will connect to an existing MySQL cloud database. The code below connects to an existing (pre-created) MySQL cloud database hosted on db4free.net1.

# this makes a connection to a cloud db4free database that was pre-created
library(RMySQL, quietly=T)
## 
## Attaching package: 'RMySQL'
## The following object is masked from 'package:RSQLite':
## 
##     isIdCurrent
db_user <- 'cs5200' 
db_password <- 'khour#2021'
db_name <- 'cs5200db1'
db_host <- 'db4free.net' # Host 85.10.205.173
db_port <- 3306

db4free <-  dbConnect(RMySQL::MySQL(), 
                      user = db_user, 
                      password = db_password,
                      dbname = db_name, 
                      host = db_host, 
                      port = db_port)

Load CSV Data

Now that we have a database connection, let’s load data from a CSV file and then store that CSV file into two tables in the database. Of course, loading a CSV file into tables can be done in other ways, too. Many databases allow loading of CSV files directly into tables using the database’s management console, e.g., MySQL Workbench for MySQL. Here we are demonstrating an simple ETL pipeline, where a CSV file is loaded into R, the data is transformed, and then stored in the database.

fn = "FlightsWithAirlines.csv"

df.raw <- read.csv(file = fn,
                   header = T,
                   stringsAsFactors = F)

Let’s inspect the raw data we read from the CSV.

head(df.raw, 3)
##   year month day dep_hr dep_min dep_delay carrier           airline country flight  equip tailnum origin dest
## 1 2013     9  18      2      38         0      UA            United     USA   1545 B737-8  N14228    EWR  IAH
## 2 2013     2  14     12      25        13      AA American Airlines     USA    441   B777  N24211    MIA  GRU
## 3 2015     1   7     13      56         9      AA American Airlines     USA   1141   A321  N619AA    JFK  MIA
str(df.raw)
## 'data.frame':    9 obs. of  14 variables:
##  $ year     : int  2013 2013 2015 2016 2015 2014 2017 2018 2020
##  $ month    : int  9 2 1 6 1 5 5 10 6
##  $ day      : int  18 14 7 7 30 2 5 24 16
##  $ dep_hr   : int  2 12 13 22 24 11 5 20 5
##  $ dep_min  : int  38 25 56 56 2 2 50 23 31
##  $ dep_delay: int  0 13 9 7 14 49 13 50 30
##  $ carrier  : chr  "UA" "AA" "AA" "B6" ...
##  $ airline  : chr  "United" "American Airlines" "American Airlines" "JetBlue" ...
##  $ country  : chr  "USA" "USA" "USA" "USA" ...
##  $ flight   : int  1545 441 1141 725 461 1696 507 5708 411
##  $ equip    : chr  "B737-8" "B777" "A321" "A321" ...
##  $ tailnum  : chr  "N14228" "N24211" "N619AA" "N804JB" ...
##  $ origin   : chr  "EWR" "MIA" "JFK" "JFK" ...
##  $ dest     : chr  "IAH" "GRU" "MIA" "BQN" ...

Create Data Frames

The strategy we will use to load the data from the CSV to the database is to create internal data frames that mirror the tables in the database and then write the data frames to the database. This requires extra memory but is fast and convenient. It would not work well if the CSV were very large.

We will need to find the unique set of airlines. There are many duplicates in the CSV file as it is unnormalized. We will use sqldf with a SQL query to find the unique set of airlines. We could do this also with tidyverse.

Notice how we place a fixed value in the first column of the result as we will need a data frame with the first column acting as an artificial primary key. We will assign those values as sequential values.

Airlines Data

We are going to make use of sqldf, a package for using SQL to query dataframes rather than tables in databases. Of course, we could also using tidyverse but sqldf is convenient and relies on SQL.

sqldf actually uses a database to temporarily store dataframes as tables, query them, and then delete them in the database. It will use a temporary in-memory SQLite database by default unless a connection has been made to another database. Unfortunately, this connection sharing can be an issue when using MySQL as that database requires authentication before tables can be created. So, we need to direct sqldf to use SQLite as a backing store. The code below accomplishes this.

# set backing store to SQLite for sqldf
options(sqldf.driver = 'SQLite')
# find the unique airlines
# notice how we select a fixed value for the first column
# it acts as a placeholder for the artificial PK we will assign later
df.airlines <- sqldf::sqldf("select 1 as aid, carrier, airline, country from `df.raw` group by carrier")

# assign an artificial PK using a counter
n.airlines <- nrow(df.airlines)
df.airlines[,1] <- seq(1, n.airlines)

Flights Data

# while we could also use sqldf here, we'll use a different approach to extract
# the data -- we will use entire columns; also note how the aid FK is set to 1;
# we will assign that later to the right PK in the airlines table
n.flights <- nrow(df.raw)
df.flights <- data.frame(fid = 100 + seq(1,n.flights),
                         dep = df.raw$origin,
                         arr = df.raw$dest,
                         aid = 1)

Bulk Load Data to Database

Now we’ll write the data to the tables in the database. We’ll first use the simpler and faster bulk loading approach using dbWriteTable(). Interestingly, when using dbWriteTable() it actually creates the tables in the database based on the structure of the data frame – we did not actually need to pre-create the tables. If the tables were pre-created, we would need to overwrite them by specifying overwrite = T, but that would essentially ignore any prior table structure.

dbWriteTable(dbcon, "airlines", df.airlines, overwrite = T)
dbWriteTable(dbcon, "flights", df.flights, overwrite = T)

Let’s see if it worked…

select * from flights limit 5;
Table 1: 5 records
fid dep arr aid
101 EWR IAH 6
102 MIA GRU 1
103 JFK MIA 1
104 JFK BQN 2
105 LGA ATL 3
select * from airlines limit 5;
Table 2: 5 records
aid carrier airline country
1 AA American Airlines USA
2 B6 JetBlue USA
3 DL Delta Airlines USA
4 LH Lufthansa Germany
5 NK Spirit Airways USA

Save to Existing Schema

If we want to use an existing schema and already created tables, then we have to change the parameters for dbWriteTable() and make sure that the columns in the dataframe match the columns in the table (same order and same data types). One reason for not overwriting the tables – and essentially letting R create tables for us – is that we want to have foreign key and other constraint checks.

Let’s take a look at how to do that. We will start by creating our schema.

Before we create the two tables flights and airlines, we need to delete the tables that were automatically created. In the flights table, we’ll store an artificial primary key value and the subset of the columns from the CSV: departure airport and arrival airport. We then add a foreign key that links to an airline in the airlines table. That table will contain airline information. While we could use the airline code as a primary key, we will create an artificial key for demonstration purposes.

create table airlines (
  aid integer primary key,
  carrier text not null,
  airline text not null,
  country text not null
);
create table flights (
  fid integer primary key,
  dep text not null,
  arr text not null,
  aid integer not null,
  foreign key (aid) references airlines(aid)
);

Note that the columns of the table must match the column names and data types of the dataframe – the dataframe must be a mirror of the table in the database.

Note the use of the parameters overwrite = F and append = T so that any existing table is not overwritten and the data is appended to the table. Of course, running the code twice would require deleting the data first otherwise you would get primary key duplicates.

dbWriteTable(dbcon, "airlines", df.airlines, overwrite = F, append = T)
dbWriteTable(dbcon, "flights", df.flights, overwrite = F, append = T)

Let’s see if it worked…

select * from flights limit 3;
Table 3: 3 records
fid dep arr aid
101 EWR IAH 6
102 MIA GRU 1
103 JFK MIA 1
select * from airlines limit 3;
Table 4: 3 records
aid carrier airline country
1 AA American Airlines USA
2 B6 JetBlue USA
3 DL Delta Airlines USA

To summarize, if you pre-create the schema and want dbWriteTable() then you need to append the data, not overwrite the table, and ensure that the column names and types of the dataframe match those in the table.

Load Data by Row

Some databases (notably db4free.net) may not allow bulk saving of data with dbWriteTable(). So, an alternate approach, if dbWriteTable() is not supported, is to write the data to a table row by row using INSERT.

This approach writes out the rows from each data frame to the corresponding table one by one – not very efficient and potentially very slow but effective and easy to understand.

There are two approaches we can use: prepared statements and manufactured statements.

We will first show prepared statements that take parameters. Besides being easier than to build the SQL INSERT, it also guards against SQL injection attacks.

Prepared Statements

The code below works for SQLite but does not work for MySQL as it does not support prepared statements through the RMySQL package. An alternative for MySQL is shown later.

Now that we have the tables we are ready to insert the data row by row into the cloud MySQL database. We will use prepared statements for this. To create a prepared statement, we first create a SQL statement with named parameters and then bind values to those named parameters. The names of the parameters are defined by the programmer and should reflect the names of the columns and data being inserted.

Of course, we assume that the database is used in single-user mode; if that’s not the case, then the insertion of the data below would have to be within a transaction.

The function dbSendStatement() can be used to send a fully constructed SQL statement or one with parameters – a so called prepared statement. In the latter case, the parameters must be bound to actual values before it can be executed. The function dbBind() binds the parameters and dbGetRowsAffected() executes the statement and returns the number of rows that were inserted in this case. You can specify parameters by position or by name – both are shown below.

n.airlines <- nrow(df.airlines)
for (r in 1:n.airlines){
  sqlStmt <- 'insert into airlines values (?, ?, ?, ?)'
  ps <- dbSendStatement(conn = dbcon, 
                        statement = sqlStmt,
                        immediate=F)
  dbBind(ps, params = list(df.airlines$aid[r],
                           df.airlines$carrier[r],
                           df.airlines$airline[r],
                           df.airlines$country[r]))
  nr <- dbGetRowsAffected(ps)
  if (nr < 1) {
    message(paste0("Could not save airline ", r))
  }

  dbClearResult(ps)
}

n.flights <- nrow(df.flights)
for (f in 1:n.flights) {
  sqlStmt <- "insert into flights values (:fid,:dep,:arr,:aid)"
  ps <- dbSendStatement(conn = dbcon, 
                        statement = sqlStmt)
  dbBind(ps, 
         params = list(fid = df.flights$fid[f],
                       dep = df.flights$dep[f],
                       arr = df.flights$arr[f],
                       aid = df.flights$aid[f]),
         immediate = T)

  nr <- dbGetRowsAffected(ps)
  if (nr < 1) {
    message(paste0("Could not save flight ", f))
  }
    
  dbClearResult(ps)
}

Let’s do a quick check to ensure that the data is in the database.

select * from airlines limit 3;
Table 5: 3 records
aid carrier airline country
1 AA American Airlines USA
2 B6 JetBlue USA
3 DL Delta Airlines USA

Manufactured SQL Command Strings

As an alternative to prepared statements we could also build SQL statements as text strings from the variables that we want to insert as values. This may inadvertently cause SQL injection security issues but is often necessary if a database or the programming language’s database drivers do not support prepared statements.

The code below shows this approach. This approach will work for db4free and other cloud MySQL databases.

create table airlines (
  aid integer primary key,
  carrier text not null,
  airline text not null,
  country text not null
);
create table flights (
  fid integer primary key,
  dep text not null,
  arr text not null,
  aid integer not null,
  foreign key (aid) references airlines(aid)
);
n.airlines <- nrow(df.airlines)
for (r in 1:n.airlines){
  sqlStmt <- paste0(
    "insert into airlines values (",
        df.airlines$aid[r], ",",
        "'", df.airlines$carrier[r], "',",
        "'", df.airlines$airline[r], "',",
        "'", df.airlines$country[r], "'",
        ")")
  nr <- dbSendStatement(conn = db4free, 
                        statement = sqlStmt)
}

n.flights <- nrow(df.flights)
for (r in 1:n.flights) {
  sqlStmt <- paste0(
    "insert into flights values (",
        df.flights$fid[r], ",",
        "'", df.flights$dep[r], "',",
        "'", df.flights$arr[r], "',",
        "'", df.flights$aid[r], "'",
        ")")
  nr <- dbSendStatement(conn = db4free, 
                        statement = sqlStmt)
}

Let’s do a final check to ensure that the data is in the database.

select * from airlines limit 3;
Table 6: 3 records
aid carrier airline country
1 AA American Airlines USA
2 B6 JetBlue USA
3 DL Delta Airlines USA
select * from flights limit 3;
Table 7: 3 records
fid dep arr aid
101 EWR IAH 6
102 MIA GRU 1
103 JFK MIA 1

It worked too. So, now you have two ways to load data from data frames into a database. You also have a way to split data from a single CSV into multiple tables and linking them. This type of work is often necessary when loading data from third party applications or external sources.

Close the Database

After accessing a database and when no further access is needed, always close the connection and release local resources and resources on the database server. Most databases restrict the number of simultaneous connections and by not disconnecting when needed we can often consume all resources needlessly and bar other applications from connecting to the database. The number of concurrent connections is a factor of the vendor’s license (more connections cost more money) or configuration. When connecting, you can sometimes specify the number of concurrent connections. Note that having too many connections open simultaneously can slow down database performance.

## [1] TRUE

Files & Resources

All Files for Lesson 6.302

References

[The sqldf Package. R Documentation.]](https://www.rdocumentation.org/packages/sqldf/versions/0.4-11)

Errata

None collected yet. Let us know.


  1. If the database does not exist, you need to create it with the same data or modify the connection information below, if you wish to run the code. Should you not be able to connect, it might be either because the database does not exist, the connection information is incorrect, or port 3306 is blocked on your computer due to a firewall or anti-virus program setting.↩︎

LS0tCnRpdGxlOiAiQnVsayBMb2FkIERhdGEgZnJvbSBDU1YgaW50byBEYXRhYmFzZSBpbiBSIgpwYXJhbXM6CiAgY2F0ZWdvcnk6IDYKICBudW1iZXI6IDMwMgogIHRpbWU6IDQ1CiAgbGV2ZWw6IGJlZ2lubmVyCiAgdGFnczogIlIsU1FMLHNxbGRmLGRhdGFiYXNlLHNxbGl0ZSxkYldyaXRlVGFibGUiCiAgZGVzY3JpcHRpb246ICJFeHBsYWlucyBob3cgdG8gbG9hZCBkYXRhIGZyb20gYSBDU1YgaW50byBhIHJlbGF0aW9uYWwKICAgICAgICAgICAgICAgIGRhdGFiYXNlIG9mIG11bHRpcGxlIHRhYmxlcyBhbmQgbWFwIHByaW1hcnkga2V5IHRvCiAgICAgICAgICAgICAgICBmb3JlaWduIGtleSByZWZlcmVuY2VzLiIKZGF0ZTogIjxzbWFsbD5gciBTeXMuRGF0ZSgpYDwvc21hbGw+IgphdXRob3I6ICI8c21hbGw+TWFydGluIFNjaGVkbGJhdWVyPC9zbWFsbD4iCmVtYWlsOiAibS5zY2hlZGxiYXVlckBuZXUuZWR1IgphZmZpbGl0YXRpb246ICJOb3J0aGVhc3Rlcm4gVW5pdmVyc2l0eSIKb3V0cHV0OiAKICBib29rZG93bjo6aHRtbF9kb2N1bWVudDI6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY29sbGFwc2VkOiBmYWxzZQogICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgdGhlbWU6IHNwYWNlbGFiCiAgICBoaWdobGlnaHQ6IHRhbmdvCi0tLQoKLS0tCnRpdGxlOiAiPHNtYWxsPmByIHBhcmFtcyRjYXRlZ29yeWAuYHIgcGFyYW1zJG51bWJlcmA8L3NtYWxsPjxici8+PHNwYW4gc3R5bGU9J2NvbG9yOiAjMkU0MDUzOyBmb250LXNpemU6IDAuOWVtJz5gciBybWFya2Rvd246Om1ldGFkYXRhJHRpdGxlYDwvc3Bhbj4iCi0tLQoKYGBge3IgY29kZT14ZnVuOjpyZWFkX3V0ZjgocGFzdGUwKGhlcmU6OmhlcmUoKSwnL1IvX2luc2VydDJEQi5SJykpLCBpbmNsdWRlID0gRkFMU0V9CmBgYAoKIyMgSW50cm9kdWN0aW9uCgpUaGUgb3ZlcmFsbCBnb2FsIG9mIHRoaXMgdHV0b3JpYWwgaXMgdG8gbG9hZCBhIENTViBmaWxlLCBzcGxpdCB0aGUgZGF0YSBpbnRvIG11bHRpcGxlIHRhYmxlcyB0aGF0IGZvbGxvdyBub3JtYWwgZm9ybXMsIGNyZWF0ZSB0YWJsZXMgaW4gYSBkYXRhYmFzZSwgYW5kIHRoZW4gc2F2ZSB0aGUgZGF0YSBmcm9tIHRoZSBDU1YgdmlhIGRhdGEgZnJhbWVzIHRvIHRoZSB0YWJsZXMgaW4gdGhlIGRhdGFiYXNlLiBXZSB3aWxsIGRlbW9uc3RyYXRlIHNldmVyYWwgaW1wb3J0YW50IG1ldGhvZHMgYW5kIGFwcHJvYWNoZXMuIE9mIGNvdXJzZSwgdGhlcmUgYXJlIG1hbnkgb3RoZXIgYXBwcm9hY2hlcyB0aGF0IGFyZSBlcXVhbGx5IHZhbGlkLiBJbiBmYWN0LCB0aGUgYXBwcm9hY2hlcyBzaG93biBoZXJlIG1heSBub3QgYmUgdGhlIG1vc3QgZWZmaWNpZW50IGluIHRlcm1zIG9mIG1lbW9yeSB1c2UgYW5kIHNwZWVkLCBidXQgdGhleSBhcmUgc2ltcGxlIHRvIGltcGxlbWVudCBhbmQgZWFzeSB0byBmb2xsb3cuCgpBbGwgd29yayBvY2N1cnMgaW4gdGhlIFIgTm90ZWJvb2sgYW5kIGlzIHRodXMgcmVwcm9kdWNpYmxlLgoKPiBOb3RlIHRoYXQgdGhlIHVzZSBvZiA8Y29kZT5kYldyaXRlVGFibGUoKTwvY29kZT4gZ2VuZXJhbGx5IHJlcXVpcmVzIHRoYXQgImJ1bGsgb3IgYmF0Y2ggbG9hZGluZyIgaXMgZW5hYmxlZCBmb3IgdGhlIGRhdGFiYXNlLiBJdCB3aWxsIHdvcmsgd2l0aCBTUUxpdGUsIGJ1dCB0aGlzIGZlYXR1cmUgbWF5IG5vdCBiZSBhdmFpbGFibGUgZm9yIHNvbWUgY2xvdWQgaW1wbGVtZW50YXRpb25zIG9mIE15U1FMLiBGb3IgZXhhbXBsZSwgPGNvZGU+ZGJXcml0ZVRhYmxlKCk8L2NvZGU+IGRvZXMgbm90IHdvcmsgd2l0aCBNeVNRTCBvbiAqZGI0ZnJlZS5uZXQqLiBJdCBkb2VzIHdvcmsgd2l0aCBsb2NhbCBpbnN0YWxsYXRpb25zIG9mIE15U1FMIG9yIGEgTXlTUUwgUkRTIG9uIEFXUy4KCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKSWYgeW91IGFyZSBsZXNzIGV4cGVyaWVuY2VkIHdpdGggUiBwcm9ncmFtbWluZyBvciBmaW5kIHRoaXMgdHV0b3JpYWwgYSBiaXQgdG9vIGNvbXBsZXggdG8gZm9sbG93LCBjb25zaWRlciByZWFkaW5nIHRoaXMgc2ltcGxlciB0dXRvcmlhbCBmaXJzdDogWzUwLjMwMkEgTG9hZCBTdWJzZXQgb2YgQmlyZHN0cmlrZXMgU2FtcGxlIERhdGEgZnJvbSBDU1YgaW50byBEYXRhYmFzZSBpbiBSXShodHRwOi8vYXJ0aWZpY2l1bS51cy9sZXNzb25zLzA2LnIvbC02LTMwMi1idWxrbG9hZC1kYXRhLWludG8tZGIvbC02LTMwMi1zYW5kYm94LWxvYWQtYmlyZHN0cmlrZXMuaHRtbCkuCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIyBDb25uZWN0IHRvIERhdGFiYXNlCgpTaW5jZSB3ZSB3aWxsIHdvcmsgd2l0aCBTUUxpdGUgYW5kIE15U1FMLCB3ZSB3aWxsIG1ha2UgdHdvIHNlcGFyYXRlIGRhdGFiYXNlIGNvbm5lY3Rpb25zLgoKRmlyc3QsIHdlIHdpbGwgY3JlYXRlIGEgbmV3IGluLW1lbW9yeSBTUUxpdGUgZGF0YWJhc2UuIFRoZSBjb2RlIGJlbG93IG1ha2VzIGEgY29ubmVjdGlvbiB0byBhbiBpbi1tZW1vcnkgdGVtcG9yYXJ5IFNRTGl0ZSBkYXRhYmFzZSB0aGF0IHdpbGwgbm90IHBlcnNpc3Qgb25jZSB0aGUgcHJvZ3JhbSB0ZXJtaW5hdGVzLiBUaGlzIGlzIHVzZWZ1bCBmb3IgdGVzdGluZyBvciBxdWljayAqYWQgaG9jKiB3b3JrLgoKYGBge3IgY29ubmVjdDJTUUxpdGVEQiwgZXZhbD1UUlVFLCB3YXJuaW5nPUZBTFNFfQojIGNoYW5nZSB0aGlzIGNvZGUgdG8gY29ubmVjdCB0byB5b3VyIHNwZWNpZmljIGRhdGFiYXNlCmxpYnJhcnkoUlNRTGl0ZSwgcXVpZXRseT1UKQoKIyB0aGlzIG1ha2VzIGEgY29ubmVjdGlvbiB0byBhbiBpbi1tZW1vcnkgdGVtcG9yYXJ5IFNRTGl0ZSBkYXRhYmFzZQpkYmNvbiA8LSBkYkNvbm5lY3QoUlNRTGl0ZTo6U1FMaXRlKCksICI6bWVtb3J5OiIpCmBgYAoKTmV4dCwgd2Ugd2lsbCBjb25uZWN0IHRvIGFuIGV4aXN0aW5nIE15U1FMIGNsb3VkIGRhdGFiYXNlLiBUaGUgY29kZSBiZWxvdyBjb25uZWN0cyB0byBhbiBleGlzdGluZyAocHJlLWNyZWF0ZWQpIE15U1FMIGNsb3VkIGRhdGFiYXNlIGhvc3RlZCBvbiBbZGI0ZnJlZS5uZXRdKGh0dHA6Ly9kYjRmcmVlLm5ldClbXmwtNi0zMDItMV0uCgpbXmwtNi0zMDItMV06IElmIHRoZSBkYXRhYmFzZSBkb2VzIG5vdCBleGlzdCwgeW91IG5lZWQgdG8gY3JlYXRlIGl0IHdpdGggdGhlIHNhbWUgZGF0YSBvciBtb2RpZnkgdGhlIGNvbm5lY3Rpb24gaW5mb3JtYXRpb24gYmVsb3csIGlmIHlvdSB3aXNoIHRvIHJ1biB0aGUgY29kZS4gU2hvdWxkIHlvdSBub3QgYmUgYWJsZSB0byBjb25uZWN0LCBpdCBtaWdodCBiZSBlaXRoZXIgYmVjYXVzZSB0aGUgZGF0YWJhc2UgZG9lcyBub3QgZXhpc3QsIHRoZSBjb25uZWN0aW9uIGluZm9ybWF0aW9uIGlzIGluY29ycmVjdCwgb3IgcG9ydCAzMzA2IGlzIGJsb2NrZWQgb24geW91ciBjb21wdXRlciBkdWUgdG8gYSBmaXJld2FsbCBvciBhbnRpLXZpcnVzIHByb2dyYW0gc2V0dGluZy4KCmBgYHtyIGNvbm5lY3QyQ2xvdWRNeVNRTCwgZXZhbD1UUlVFLCB3YXJuaW5nPUZBTFNFfQojIHRoaXMgbWFrZXMgYSBjb25uZWN0aW9uIHRvIGEgY2xvdWQgZGI0ZnJlZSBkYXRhYmFzZSB0aGF0IHdhcyBwcmUtY3JlYXRlZApsaWJyYXJ5KFJNeVNRTCwgcXVpZXRseT1UKQoKZGJfdXNlciA8LSAnY3M1MjAwJyAKZGJfcGFzc3dvcmQgPC0gJ2tob3VyIzIwMjEnCmRiX25hbWUgPC0gJ2NzNTIwMGRiMScKZGJfaG9zdCA8LSAnZGI0ZnJlZS5uZXQnICMgSG9zdCA4NS4xMC4yMDUuMTczCmRiX3BvcnQgPC0gMzMwNgoKZGI0ZnJlZSA8LSAgZGJDb25uZWN0KFJNeVNRTDo6TXlTUUwoKSwgCiAgICAgICAgICAgICAgICAgICAgICB1c2VyID0gZGJfdXNlciwgCiAgICAgICAgICAgICAgICAgICAgICBwYXNzd29yZCA9IGRiX3Bhc3N3b3JkLAogICAgICAgICAgICAgICAgICAgICAgZGJuYW1lID0gZGJfbmFtZSwgCiAgICAgICAgICAgICAgICAgICAgICBob3N0ID0gZGJfaG9zdCwgCiAgICAgICAgICAgICAgICAgICAgICBwb3J0ID0gZGJfcG9ydCkKYGBgCgojIyBMb2FkIENTViBEYXRhCgpOb3cgdGhhdCB3ZSBoYXZlIGEgZGF0YWJhc2UgY29ubmVjdGlvbiwgbGV0J3MgbG9hZCBkYXRhIGZyb20gYSBDU1YgZmlsZSBhbmQgdGhlbiBzdG9yZSB0aGF0IENTViBmaWxlIGludG8gdHdvIHRhYmxlcyBpbiB0aGUgZGF0YWJhc2UuIE9mIGNvdXJzZSwgbG9hZGluZyBhIENTViBmaWxlIGludG8gdGFibGVzIGNhbiBiZSBkb25lIGluIG90aGVyIHdheXMsIHRvby4gTWFueSBkYXRhYmFzZXMgYWxsb3cgbG9hZGluZyBvZiBDU1YgZmlsZXMgZGlyZWN0bHkgaW50byB0YWJsZXMgdXNpbmcgdGhlIGRhdGFiYXNlJ3MgbWFuYWdlbWVudCBjb25zb2xlLCAqZS5nLiosIE15U1FMIFdvcmtiZW5jaCBmb3IgTXlTUUwuIEhlcmUgd2UgYXJlIGRlbW9uc3RyYXRpbmcgYW4gc2ltcGxlIEVUTCBwaXBlbGluZSwgd2hlcmUgYSBDU1YgZmlsZSBpcyBsb2FkZWQgaW50byBSLCB0aGUgZGF0YSBpcyB0cmFuc2Zvcm1lZCwgYW5kIHRoZW4gc3RvcmVkIGluIHRoZSBkYXRhYmFzZS4KCmBgYHtyIGxvYWRDU1Z9CmZuID0gIkZsaWdodHNXaXRoQWlybGluZXMuY3N2IgoKZGYucmF3IDwtIHJlYWQuY3N2KGZpbGUgPSBmbiwKICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsCiAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKYGBgCgpMZXQncyBpbnNwZWN0IHRoZSByYXcgZGF0YSB3ZSByZWFkIGZyb20gdGhlIENTVi4KCmBgYHtyfQpoZWFkKGRmLnJhdywgMykKc3RyKGRmLnJhdykKYGBgCgojIyMgQ3JlYXRlIERhdGEgRnJhbWVzCgpUaGUgc3RyYXRlZ3kgd2Ugd2lsbCB1c2UgdG8gbG9hZCB0aGUgZGF0YSBmcm9tIHRoZSBDU1YgdG8gdGhlIGRhdGFiYXNlIGlzIHRvIGNyZWF0ZSBpbnRlcm5hbCBkYXRhIGZyYW1lcyB0aGF0IG1pcnJvciB0aGUgdGFibGVzIGluIHRoZSBkYXRhYmFzZSBhbmQgdGhlbiB3cml0ZSB0aGUgZGF0YSBmcmFtZXMgdG8gdGhlIGRhdGFiYXNlLiBUaGlzIHJlcXVpcmVzIGV4dHJhIG1lbW9yeSBidXQgaXMgZmFzdCBhbmQgY29udmVuaWVudC4gSXQgd291bGQgbm90IHdvcmsgd2VsbCBpZiB0aGUgQ1NWIHdlcmUgdmVyeSBsYXJnZS4KCldlIHdpbGwgbmVlZCB0byBmaW5kIHRoZSB1bmlxdWUgc2V0IG9mIGFpcmxpbmVzLiBUaGVyZSBhcmUgbWFueSBkdXBsaWNhdGVzIGluIHRoZSBDU1YgZmlsZSBhcyBpdCBpcyB1bm5vcm1hbGl6ZWQuIFdlIHdpbGwgdXNlICoqc3FsZGYqKiB3aXRoIGEgU1FMIHF1ZXJ5IHRvIGZpbmQgdGhlIHVuaXF1ZSBzZXQgb2YgYWlybGluZXMuIFdlIGNvdWxkIGRvIHRoaXMgYWxzbyB3aXRoICoqdGlkeXZlcnNlKiouCgpOb3RpY2UgaG93IHdlIHBsYWNlIGEgZml4ZWQgdmFsdWUgaW4gdGhlIGZpcnN0IGNvbHVtbiBvZiB0aGUgcmVzdWx0IGFzIHdlIHdpbGwgbmVlZCBhIGRhdGEgZnJhbWUgd2l0aCB0aGUgZmlyc3QgY29sdW1uIGFjdGluZyBhcyBhbiBhcnRpZmljaWFsIHByaW1hcnkga2V5LiBXZSB3aWxsIGFzc2lnbiB0aG9zZSB2YWx1ZXMgYXMgc2VxdWVudGlhbCB2YWx1ZXMuCgojIyMjIEFpcmxpbmVzIERhdGEKCldlIGFyZSBnb2luZyB0byBtYWtlIHVzZSBvZiAqKnNxbGRmKiosIGEgcGFja2FnZSBmb3IgdXNpbmcgU1FMIHRvIHF1ZXJ5IGRhdGFmcmFtZXMgcmF0aGVyIHRoYW4gdGFibGVzIGluIGRhdGFiYXNlcy4gT2YgY291cnNlLCB3ZSBjb3VsZCBhbHNvIHVzaW5nICoqdGlkeXZlcnNlKiogYnV0ICoqc3FsZGYqKiBpcyBjb252ZW5pZW50IGFuZCByZWxpZXMgb24gU1FMLgoKYGBge3IgbG9hZHNxbGRmLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPUZ9CiMgbG9hZCB0aGUgc3FsZGYgcGFja2FnZQpsaWJyYXJ5KHNxbGRmLCBxdWlldGx5ID0gVCwgdmVyYm9zZSA9IEYsIHdhcm4uY29uZmxpY3RzID0gRikKYGBgCgoqKnNxbGRmKiogYWN0dWFsbHkgdXNlcyBhIGRhdGFiYXNlIHRvIHRlbXBvcmFyaWx5IHN0b3JlIGRhdGFmcmFtZXMgYXMgdGFibGVzLCBxdWVyeSB0aGVtLCBhbmQgdGhlbiBkZWxldGUgdGhlbSBpbiB0aGUgZGF0YWJhc2UuIEl0IHdpbGwgdXNlIGEgdGVtcG9yYXJ5IGluLW1lbW9yeSBTUUxpdGUgZGF0YWJhc2UgYnkgZGVmYXVsdCB1bmxlc3MgYSBjb25uZWN0aW9uIGhhcyBiZWVuIG1hZGUgdG8gYW5vdGhlciBkYXRhYmFzZS4gVW5mb3J0dW5hdGVseSwgdGhpcyBjb25uZWN0aW9uIHNoYXJpbmcgY2FuIGJlIGFuIGlzc3VlIHdoZW4gdXNpbmcgTXlTUUwgYXMgdGhhdCBkYXRhYmFzZSByZXF1aXJlcyBhdXRoZW50aWNhdGlvbiBiZWZvcmUgdGFibGVzIGNhbiBiZSBjcmVhdGVkLiBTbywgd2UgbmVlZCB0byBkaXJlY3QgKipzcWxkZioqIHRvIHVzZSBTUUxpdGUgYXMgYSBiYWNraW5nIHN0b3JlLiBUaGUgY29kZSBiZWxvdyBhY2NvbXBsaXNoZXMgdGhpcy4KCmBgYHtyIHNldHNxbGRmYmFja3N0b3JlfQojIHNldCBiYWNraW5nIHN0b3JlIHRvIFNRTGl0ZSBmb3Igc3FsZGYKb3B0aW9ucyhzcWxkZi5kcml2ZXIgPSAnU1FMaXRlJykKYGBgCgpgYGB7ciBleHRyYWN0QWlybGluZURhdGEsIHdhcm5pbmc9RkFMU0V9CiMgZmluZCB0aGUgdW5pcXVlIGFpcmxpbmVzCiMgbm90aWNlIGhvdyB3ZSBzZWxlY3QgYSBmaXhlZCB2YWx1ZSBmb3IgdGhlIGZpcnN0IGNvbHVtbgojIGl0IGFjdHMgYXMgYSBwbGFjZWhvbGRlciBmb3IgdGhlIGFydGlmaWNpYWwgUEsgd2Ugd2lsbCBhc3NpZ24gbGF0ZXIKZGYuYWlybGluZXMgPC0gc3FsZGY6OnNxbGRmKCJzZWxlY3QgMSBhcyBhaWQsIGNhcnJpZXIsIGFpcmxpbmUsIGNvdW50cnkgZnJvbSBgZGYucmF3YCBncm91cCBieSBjYXJyaWVyIikKCiMgYXNzaWduIGFuIGFydGlmaWNpYWwgUEsgdXNpbmcgYSBjb3VudGVyCm4uYWlybGluZXMgPC0gbnJvdyhkZi5haXJsaW5lcykKZGYuYWlybGluZXNbLDFdIDwtIHNlcSgxLCBuLmFpcmxpbmVzKQpgYGAKCiMjIyMgRmxpZ2h0cyBEYXRhCgpgYGB7ciBleHRyYWN0RmxpZ2h0RGF0YX0KIyB3aGlsZSB3ZSBjb3VsZCBhbHNvIHVzZSBzcWxkZiBoZXJlLCB3ZSdsbCB1c2UgYSBkaWZmZXJlbnQgYXBwcm9hY2ggdG8gZXh0cmFjdAojIHRoZSBkYXRhIC0tIHdlIHdpbGwgdXNlIGVudGlyZSBjb2x1bW5zOyBhbHNvIG5vdGUgaG93IHRoZSBhaWQgRksgaXMgc2V0IHRvIDE7CiMgd2Ugd2lsbCBhc3NpZ24gdGhhdCBsYXRlciB0byB0aGUgcmlnaHQgUEsgaW4gdGhlIGFpcmxpbmVzIHRhYmxlCm4uZmxpZ2h0cyA8LSBucm93KGRmLnJhdykKZGYuZmxpZ2h0cyA8LSBkYXRhLmZyYW1lKGZpZCA9IDEwMCArIHNlcSgxLG4uZmxpZ2h0cyksCiAgICAgICAgICAgICAgICAgICAgICAgICBkZXAgPSBkZi5yYXckb3JpZ2luLAogICAgICAgICAgICAgICAgICAgICAgICAgYXJyID0gZGYucmF3JGRlc3QsCiAgICAgICAgICAgICAgICAgICAgICAgICBhaWQgPSAxKQpgYGAKCiMjIExpbmsgVGFibGVzCgpPbmUgaHVyZGxlIHdpdGggdGhpcyBhcHByb2FjaCBpcyB0aGUgbGlua2luZyBvZiBmb3JlaWduIGtleXMgaW4gb25lIHRhYmxlIHRvIG5ld2x5IGNyZWF0ZWQgYXJ0aWZpY2lhbCBwcmltYXJ5IGtleXMuIEluIG91ciBleGFtcGxlLCB3ZSB3aWxsIG5lZWQgdG8gbGluayBlYWNoIGZsaWdodCB0byBhbiBhaXJsaW5lLiBUaGUgc3RyYXRlZ3kgd2Ugd2lsbCB1c2UgaXMgdG8gdHJhdmVyc2UgdGhlIGRhdGEgZnJhbWUgY29udGFpbmluZyB0aGUgb3JpZ2luYWwgZmxpZ2h0cyBkYXRhIHJvdyBieSByb3cgdXNpbmcgYSAqZm9yIGxvb3AqLiBGb3IgZWFjaCByb3cgd2Ugd2lsbCBmaW5kIHRoZSBhaXJsaW5lIGluIHRoZSBkYXRhIGZyYW1lIGNvbnRhaW5pbmcgdGhlIGFpcmxpbmVzIC0tIHdlIHVzZSB0aGUgPGNvZGU+IHdoaWNoKCk8L2NvZGU+IGZ1bmN0aW9uIHRvIG1hdGNoLiBUaGlzIHN0cmF0ZWd5IGFzc3VtZXMgdGhhdCB0aGUgcm93cyBpbiB0aGUgb3JpZ2luYWwgcmF3IGRhdGEgY29ycmVzcG9uZCByb3cgYnkgcm93IHRvIHRoZSBzdWJzZXQgZGF0YSBpbiB0aGUgZmxpZ2h0cyBkYXRhIGZyYW1lLiBJZiB3ZSByZW1vdmVkIHJvd3MsIHRoZW4gdGhpcyB3b3VsZCBub3Qgd29yay4gQW4gYWx0ZXJuYXRpdmUgbWlnaHQgYmUgdG8gY29weSBtb3JlIGRhdGEgZnJvbSB0aGUgb3JpZ2luYWwgZGF0YSBmcmFtZSBhbmQgdGhlbiBldmVudHVhbGx5IHJlbW92ZSBjb2x1bW5zLgoKYGBge3IgbGlua0ZLc30KIyBsZXQncyBsaW5rIGVhY2ggZmxpZ2h0IHRvIHRoZSBjb3JyZWN0IGFpcmxpbmUgYnkgYXNzaWduaW5nIHRoZSBGSyB0byB0aGUgCiMgY29ycmVjdCBQSwoKIyBwcm9jZXNzIGVhY2ggcm93IChmbGlnaHQpIG9uZSBieSBvbmUKZm9yIChyIGluIDE6bi5mbGlnaHRzKSB7CiAgIyBmaW5kIHRoZSBhaXJsaW5lIFBLIGZvciB0aGF0IGZsaWdodCBpbiB0aGUgYWlybGluZXMgdGFibGUKICBhIDwtIGRmLmFpcmxpbmVzJGFpZFt3aGljaChkZi5haXJsaW5lcyRjYXJyaWVyID09IGRmLnJhdyRjYXJyaWVyW3JdICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRmLmFpcmxpbmVzJGFpcmxpbmUgPT0gZGYucmF3JGFpcmxpbmVbcl0gJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGYuYWlybGluZXMkY291bnRyeSA9PSBkZi5yYXckY291bnRyeVtyXSldCiAgZGYuZmxpZ2h0cyRhaWRbcl0gPC0gYQp9CgpgYGAKCiMjIEJ1bGsgTG9hZCBEYXRhIHRvIERhdGFiYXNlCgpOb3cgd2UnbGwgd3JpdGUgdGhlIGRhdGEgdG8gdGhlIHRhYmxlcyBpbiB0aGUgZGF0YWJhc2UuIFdlJ2xsIGZpcnN0IHVzZSB0aGUgc2ltcGxlciBhbmQgZmFzdGVyIGJ1bGsgbG9hZGluZyBhcHByb2FjaCB1c2luZyA8Y29kZT5kYldyaXRlVGFibGUoKTwvY29kZT4uIEludGVyZXN0aW5nbHksIHdoZW4gdXNpbmcgPGNvZGU+ZGJXcml0ZVRhYmxlKCk8L2NvZGU+IGl0IGFjdHVhbGx5IGNyZWF0ZXMgdGhlIHRhYmxlcyBpbiB0aGUgZGF0YWJhc2UgYmFzZWQgb24gdGhlIHN0cnVjdHVyZSBvZiB0aGUgZGF0YSBmcmFtZSAtLSB3ZSBkaWQgbm90IGFjdHVhbGx5IG5lZWQgdG8gcHJlLWNyZWF0ZSB0aGUgdGFibGVzLiBJZiB0aGUgdGFibGVzIHdlcmUgcHJlLWNyZWF0ZWQsIHdlIHdvdWxkIG5lZWQgdG8gb3ZlcndyaXRlIHRoZW0gYnkgc3BlY2lmeWluZyAqb3ZlcndyaXRlID0gVCosIGJ1dCB0aGF0IHdvdWxkIGVzc2VudGlhbGx5IGlnbm9yZSBhbnkgcHJpb3IgdGFibGUgc3RydWN0dXJlLgoKYGBge3IgbG9hZERhdGFCdWxrfQpkYldyaXRlVGFibGUoZGJjb24sICJhaXJsaW5lcyIsIGRmLmFpcmxpbmVzLCBvdmVyd3JpdGUgPSBUKQpkYldyaXRlVGFibGUoZGJjb24sICJmbGlnaHRzIiwgZGYuZmxpZ2h0cywgb3ZlcndyaXRlID0gVCkKYGBgCgpMZXQncyBzZWUgaWYgaXQgd29ya2VkLi4uCgpgYGB7c3FsIHNob3dGbGlnaHRzLCBjb25uZWN0aW9uPWRiY29ufQpzZWxlY3QgKiBmcm9tIGZsaWdodHMgbGltaXQgNTsKYGBgCgpgYGB7c3FsIHNob3dBaXJsaW5lcywgY29ubmVjdGlvbj1kYmNvbn0Kc2VsZWN0ICogZnJvbSBhaXJsaW5lcyBsaW1pdCA1OwpgYGAKCiMjIFNhdmUgdG8gRXhpc3RpbmcgU2NoZW1hCgpJZiB3ZSB3YW50IHRvIHVzZSBhbiBleGlzdGluZyBzY2hlbWEgYW5kIGFscmVhZHkgY3JlYXRlZCB0YWJsZXMsIHRoZW4gd2UgaGF2ZSB0byBjaGFuZ2UgdGhlIHBhcmFtZXRlcnMgZm9yIDxjb2RlPmRiV3JpdGVUYWJsZSgpPC9jb2RlPiBhbmQgbWFrZSBzdXJlIHRoYXQgdGhlIGNvbHVtbnMgaW4gdGhlIGRhdGFmcmFtZSBtYXRjaCB0aGUgY29sdW1ucyBpbiB0aGUgdGFibGUgKHNhbWUgb3JkZXIgYW5kIHNhbWUgZGF0YSB0eXBlcykuIE9uZSByZWFzb24gZm9yIG5vdCBvdmVyd3JpdGluZyB0aGUgdGFibGVzIC0tIGFuZCBlc3NlbnRpYWxseSBsZXR0aW5nIFIgY3JlYXRlIHRhYmxlcyBmb3IgdXMgLS0gaXMgdGhhdCB3ZSB3YW50IHRvIGhhdmUgZm9yZWlnbiBrZXkgYW5kIG90aGVyIGNvbnN0cmFpbnQgY2hlY2tzLgoKTGV0J3MgdGFrZSBhIGxvb2sgYXQgaG93IHRvIGRvIHRoYXQuIFdlIHdpbGwgc3RhcnQgYnkgY3JlYXRpbmcgb3VyIHNjaGVtYS4KCkJlZm9yZSB3ZSBjcmVhdGUgdGhlIHR3byB0YWJsZXMgKmZsaWdodHMqIGFuZCAqYWlybGluZXMqLCB3ZSBuZWVkIHRvIGRlbGV0ZSB0aGUgdGFibGVzIHRoYXQgd2VyZSBhdXRvbWF0aWNhbGx5IGNyZWF0ZWQuIEluIHRoZSAqZmxpZ2h0cyogdGFibGUsIHdlJ2xsIHN0b3JlIGFuIGFydGlmaWNpYWwgcHJpbWFyeSBrZXkgdmFsdWUgYW5kIHRoZSBzdWJzZXQgb2YgdGhlIGNvbHVtbnMgZnJvbSB0aGUgQ1NWOiBkZXBhcnR1cmUgYWlycG9ydCBhbmQgYXJyaXZhbCBhaXJwb3J0LiBXZSB0aGVuIGFkZCBhIGZvcmVpZ24ga2V5IHRoYXQgbGlua3MgdG8gYW4gYWlybGluZSBpbiB0aGUgKmFpcmxpbmVzKiB0YWJsZS4gVGhhdCB0YWJsZSB3aWxsIGNvbnRhaW4gYWlybGluZSBpbmZvcm1hdGlvbi4gV2hpbGUgd2UgY291bGQgdXNlIHRoZSBhaXJsaW5lIGNvZGUgYXMgYSBwcmltYXJ5IGtleSwgd2Ugd2lsbCBjcmVhdGUgYW4gYXJ0aWZpY2lhbCBrZXkgZm9yIGRlbW9uc3RyYXRpb24gcHVycG9zZXMuCgpgYGB7c3FsIGNvbm5lY3Rpb249ZGJjb24sIGVjaG89Rn0KZHJvcCB0YWJsZSBhaXJsaW5lczsKYGBgCgpgYGB7c3FsIGNvbm5lY3Rpb249ZGJjb24sIGVjaG89Rn0KZHJvcCB0YWJsZSBmbGlnaHRzOwpgYGAKCmBgYHtzcWwgY3JlYXRlVGFibGVBaXJsaW5lcywgY29ubmVjdGlvbj1kYmNvbn0KY3JlYXRlIHRhYmxlIGFpcmxpbmVzICgKICBhaWQgaW50ZWdlciBwcmltYXJ5IGtleSwKICBjYXJyaWVyIHRleHQgbm90IG51bGwsCiAgYWlybGluZSB0ZXh0IG5vdCBudWxsLAogIGNvdW50cnkgdGV4dCBub3QgbnVsbAopOwpgYGAKCmBgYHtzcWwgY3JlYXRlVGFibGVGbGlnaHRzLCBjb25uZWN0aW9uPWRiY29ufQpjcmVhdGUgdGFibGUgZmxpZ2h0cyAoCiAgZmlkIGludGVnZXIgcHJpbWFyeSBrZXksCiAgZGVwIHRleHQgbm90IG51bGwsCiAgYXJyIHRleHQgbm90IG51bGwsCiAgYWlkIGludGVnZXIgbm90IG51bGwsCiAgZm9yZWlnbiBrZXkgKGFpZCkgcmVmZXJlbmNlcyBhaXJsaW5lcyhhaWQpCik7CmBgYAoKPiBOb3RlIHRoYXQgdGhlIGNvbHVtbnMgb2YgdGhlIHRhYmxlIG11c3QgbWF0Y2ggdGhlIGNvbHVtbiBuYW1lcyBhbmQgZGF0YSB0eXBlcyBvZiB0aGUgZGF0YWZyYW1lIC0tIHRoZSBkYXRhZnJhbWUgbXVzdCBiZSBhIG1pcnJvciBvZiB0aGUgdGFibGUgaW4gdGhlIGRhdGFiYXNlLgoKTm90ZSB0aGUgdXNlIG9mIHRoZSBwYXJhbWV0ZXJzICpvdmVyd3JpdGUgPSBGKiBhbmQgKmFwcGVuZCA9IFQqIHNvIHRoYXQgYW55IGV4aXN0aW5nIHRhYmxlIGlzIG5vdCBvdmVyd3JpdHRlbiBhbmQgdGhlIGRhdGEgaXMgYXBwZW5kZWQgdG8gdGhlIHRhYmxlLiBPZiBjb3Vyc2UsIHJ1bm5pbmcgdGhlIGNvZGUgdHdpY2Ugd291bGQgcmVxdWlyZSBkZWxldGluZyB0aGUgZGF0YSBmaXJzdCBvdGhlcndpc2UgeW91IHdvdWxkIGdldCBwcmltYXJ5IGtleSBkdXBsaWNhdGVzLgoKYGBge3IgbG9hZERhdGFCdWxrTm9PdmVyd3JpdGV9CmRiV3JpdGVUYWJsZShkYmNvbiwgImFpcmxpbmVzIiwgZGYuYWlybGluZXMsIG92ZXJ3cml0ZSA9IEYsIGFwcGVuZCA9IFQpCmRiV3JpdGVUYWJsZShkYmNvbiwgImZsaWdodHMiLCBkZi5mbGlnaHRzLCBvdmVyd3JpdGUgPSBGLCBhcHBlbmQgPSBUKQpgYGAKCkxldCdzIHNlZSBpZiBpdCB3b3JrZWQuLi4KCmBgYHtzcWwgc2hvd0ZsaWdodHNBZ2FpbiwgY29ubmVjdGlvbj1kYmNvbn0Kc2VsZWN0ICogZnJvbSBmbGlnaHRzIGxpbWl0IDM7CmBgYAoKYGBge3NxbCBzaG93QWlybGluZXNBZ2FpbiwgY29ubmVjdGlvbj1kYmNvbn0Kc2VsZWN0ICogZnJvbSBhaXJsaW5lcyBsaW1pdCAzOwpgYGAKClRvIHN1bW1hcml6ZSwgaWYgeW91IHByZS1jcmVhdGUgdGhlIHNjaGVtYSBhbmQgd2FudCA8Y29kZT5kYldyaXRlVGFibGUoKTwvY29kZT4gdGhlbiB5b3UgbmVlZCB0byBhcHBlbmQgdGhlIGRhdGEsIG5vdCBvdmVyd3JpdGUgdGhlIHRhYmxlLCBhbmQgZW5zdXJlIHRoYXQgdGhlIGNvbHVtbiBuYW1lcyBhbmQgdHlwZXMgb2YgdGhlIGRhdGFmcmFtZSBtYXRjaCB0aG9zZSBpbiB0aGUgdGFibGUuCgojIyBMb2FkIERhdGEgYnkgUm93CgpTb21lIGRhdGFiYXNlcyAobm90YWJseSAqZGI0ZnJlZS5uZXQqKSBtYXkgbm90IGFsbG93IGJ1bGsgc2F2aW5nIG9mIGRhdGEgd2l0aCA8Y29kZT5kYldyaXRlVGFibGUoKTwvY29kZT4uIFNvLCBhbiBhbHRlcm5hdGUgYXBwcm9hY2gsIGlmIDxjb2RlPmRiV3JpdGVUYWJsZSgpPC9jb2RlPiBpcyBub3Qgc3VwcG9ydGVkLCBpcyB0byB3cml0ZSB0aGUgZGF0YSB0byBhIHRhYmxlIHJvdyBieSByb3cgdXNpbmcgSU5TRVJULgoKVGhpcyBhcHByb2FjaCB3cml0ZXMgb3V0IHRoZSByb3dzIGZyb20gZWFjaCBkYXRhIGZyYW1lIHRvIHRoZSBjb3JyZXNwb25kaW5nIHRhYmxlIG9uZSBieSBvbmUgLS0gbm90IHZlcnkgZWZmaWNpZW50IGFuZCBwb3RlbnRpYWxseSB2ZXJ5IHNsb3cgYnV0IGVmZmVjdGl2ZSBhbmQgZWFzeSB0byB1bmRlcnN0YW5kLgoKVGhlcmUgYXJlIHR3byBhcHByb2FjaGVzIHdlIGNhbiB1c2U6IHByZXBhcmVkIHN0YXRlbWVudHMgYW5kIG1hbnVmYWN0dXJlZCBzdGF0ZW1lbnRzLgoKV2Ugd2lsbCBmaXJzdCBzaG93IHByZXBhcmVkIHN0YXRlbWVudHMgdGhhdCB0YWtlIHBhcmFtZXRlcnMuIEJlc2lkZXMgYmVpbmcgZWFzaWVyIHRoYW4gdG8gYnVpbGQgdGhlIFNRTCBJTlNFUlQsIGl0IGFsc28gZ3VhcmRzIGFnYWluc3QgU1FMIGluamVjdGlvbiBhdHRhY2tzLgoKIyMjIFByZXBhcmVkIFN0YXRlbWVudHMKCj4gVGhlIGNvZGUgYmVsb3cgd29ya3MgZm9yIFNRTGl0ZSBidXQgZG9lcyBub3Qgd29yayBmb3IgTXlTUUwgYXMgaXQgZG9lcyBub3Qgc3VwcG9ydCBwcmVwYXJlZCBzdGF0ZW1lbnRzIHRocm91Z2ggdGhlICoqUk15U1FMKiogcGFja2FnZS4gQW4gYWx0ZXJuYXRpdmUgZm9yIE15U1FMIGlzIHNob3duIGxhdGVyLgoKTm93IHRoYXQgd2UgaGF2ZSB0aGUgdGFibGVzIHdlIGFyZSByZWFkeSB0byBpbnNlcnQgdGhlIGRhdGEgcm93IGJ5IHJvdyBpbnRvIHRoZSBjbG91ZCBNeVNRTCBkYXRhYmFzZS4gV2Ugd2lsbCB1c2UgcHJlcGFyZWQgc3RhdGVtZW50cyBmb3IgdGhpcy4gVG8gY3JlYXRlIGEgcHJlcGFyZWQgc3RhdGVtZW50LCB3ZSBmaXJzdCBjcmVhdGUgYSBTUUwgc3RhdGVtZW50IHdpdGggbmFtZWQgcGFyYW1ldGVycyBhbmQgdGhlbiBiaW5kIHZhbHVlcyB0byB0aG9zZSBuYW1lZCBwYXJhbWV0ZXJzLiBUaGUgbmFtZXMgb2YgdGhlIHBhcmFtZXRlcnMgYXJlIGRlZmluZWQgYnkgdGhlIHByb2dyYW1tZXIgYW5kIHNob3VsZCByZWZsZWN0IHRoZSBuYW1lcyBvZiB0aGUgY29sdW1ucyBhbmQgZGF0YSBiZWluZyBpbnNlcnRlZC4KCk9mIGNvdXJzZSwgd2UgYXNzdW1lIHRoYXQgdGhlIGRhdGFiYXNlIGlzIHVzZWQgaW4gc2luZ2xlLXVzZXIgbW9kZTsgaWYgdGhhdCdzIG5vdCB0aGUgY2FzZSwgdGhlbiB0aGUgaW5zZXJ0aW9uIG9mIHRoZSBkYXRhIGJlbG93IHdvdWxkIGhhdmUgdG8gYmUgd2l0aGluIGEgdHJhbnNhY3Rpb24uCgpgYGB7c3FsIGNvbm5lY3Rpb249ZGJjb24sIGVjaG89Rn0KZGVsZXRlIGZyb20gZmxpZ2h0czsKYGBgCgpgYGB7c3FsIGNvbm5lY3Rpb249ZGJjb24sIGVjaG89Rn0KZGVsZXRlIGZyb20gYWlybGluZXM7CmBgYAoKVGhlIGZ1bmN0aW9uIDxjb2RlPmRiU2VuZFN0YXRlbWVudCgpPC9jb2RlPiBjYW4gYmUgdXNlZCB0byBzZW5kIGEgZnVsbHkgY29uc3RydWN0ZWQgU1FMIHN0YXRlbWVudCBvciBvbmUgd2l0aCBwYXJhbWV0ZXJzIC0tIGEgc28gY2FsbGVkICpwcmVwYXJlZCBzdGF0ZW1lbnQqLiBJbiB0aGUgbGF0dGVyIGNhc2UsIHRoZSBwYXJhbWV0ZXJzIG11c3QgYmUgYm91bmQgdG8gYWN0dWFsIHZhbHVlcyBiZWZvcmUgaXQgY2FuIGJlIGV4ZWN1dGVkLiBUaGUgZnVuY3Rpb24gPGNvZGU+ZGJCaW5kKCk8L2NvZGU+IGJpbmRzIHRoZSBwYXJhbWV0ZXJzIGFuZCA8Y29kZT5kYkdldFJvd3NBZmZlY3RlZCgpPC9jb2RlPiBleGVjdXRlcyB0aGUgc3RhdGVtZW50IGFuZCByZXR1cm5zIHRoZSBudW1iZXIgb2Ygcm93cyB0aGF0IHdlcmUgaW5zZXJ0ZWQgaW4gdGhpcyBjYXNlLiBZb3UgY2FuIHNwZWNpZnkgcGFyYW1ldGVycyBieSBwb3NpdGlvbiBvciBieSBuYW1lIC0tIGJvdGggYXJlIHNob3duIGJlbG93LgoKYGBge3IgbG9hZERhdGFCeVJvd0ludG9kYjRmcmVlfQpuLmFpcmxpbmVzIDwtIG5yb3coZGYuYWlybGluZXMpCmZvciAociBpbiAxOm4uYWlybGluZXMpewogIHNxbFN0bXQgPC0gJ2luc2VydCBpbnRvIGFpcmxpbmVzIHZhbHVlcyAoPywgPywgPywgPyknCiAgcHMgPC0gZGJTZW5kU3RhdGVtZW50KGNvbm4gPSBkYmNvbiwgCiAgICAgICAgICAgICAgICAgICAgICAgIHN0YXRlbWVudCA9IHNxbFN0bXQsCiAgICAgICAgICAgICAgICAgICAgICAgIGltbWVkaWF0ZT1GKQogIGRiQmluZChwcywgcGFyYW1zID0gbGlzdChkZi5haXJsaW5lcyRhaWRbcl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGRmLmFpcmxpbmVzJGNhcnJpZXJbcl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGRmLmFpcmxpbmVzJGFpcmxpbmVbcl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGRmLmFpcmxpbmVzJGNvdW50cnlbcl0pKQogIG5yIDwtIGRiR2V0Um93c0FmZmVjdGVkKHBzKQogIGlmIChuciA8IDEpIHsKICAgIG1lc3NhZ2UocGFzdGUwKCJDb3VsZCBub3Qgc2F2ZSBhaXJsaW5lICIsIHIpKQogIH0KCiAgZGJDbGVhclJlc3VsdChwcykKfQoKbi5mbGlnaHRzIDwtIG5yb3coZGYuZmxpZ2h0cykKZm9yIChmIGluIDE6bi5mbGlnaHRzKSB7CiAgc3FsU3RtdCA8LSAiaW5zZXJ0IGludG8gZmxpZ2h0cyB2YWx1ZXMgKDpmaWQsOmRlcCw6YXJyLDphaWQpIgogIHBzIDwtIGRiU2VuZFN0YXRlbWVudChjb25uID0gZGJjb24sIAogICAgICAgICAgICAgICAgICAgICAgICBzdGF0ZW1lbnQgPSBzcWxTdG10KQogIGRiQmluZChwcywgCiAgICAgICAgIHBhcmFtcyA9IGxpc3QoZmlkID0gZGYuZmxpZ2h0cyRmaWRbZl0sCiAgICAgICAgICAgICAgICAgICAgICAgZGVwID0gZGYuZmxpZ2h0cyRkZXBbZl0sCiAgICAgICAgICAgICAgICAgICAgICAgYXJyID0gZGYuZmxpZ2h0cyRhcnJbZl0sCiAgICAgICAgICAgICAgICAgICAgICAgYWlkID0gZGYuZmxpZ2h0cyRhaWRbZl0pLAogICAgICAgICBpbW1lZGlhdGUgPSBUKQoKICBuciA8LSBkYkdldFJvd3NBZmZlY3RlZChwcykKICBpZiAobnIgPCAxKSB7CiAgICBtZXNzYWdlKHBhc3RlMCgiQ291bGQgbm90IHNhdmUgZmxpZ2h0ICIsIGYpKQogIH0KICAgIAogIGRiQ2xlYXJSZXN1bHQocHMpCn0KYGBgCgpMZXQncyBkbyBhIHF1aWNrIGNoZWNrIHRvIGVuc3VyZSB0aGF0IHRoZSBkYXRhIGlzIGluIHRoZSBkYXRhYmFzZS4KCmBgYHtzcWwgc2hvd0FpcmxpbmVzMywgY29ubmVjdGlvbj1kYmNvbn0Kc2VsZWN0ICogZnJvbSBhaXJsaW5lcyBsaW1pdCAzOwpgYGAKCiMjIyBNYW51ZmFjdHVyZWQgU1FMIENvbW1hbmQgU3RyaW5ncwoKQXMgYW4gYWx0ZXJuYXRpdmUgdG8gcHJlcGFyZWQgc3RhdGVtZW50cyB3ZSBjb3VsZCBhbHNvIGJ1aWxkIFNRTCBzdGF0ZW1lbnRzIGFzIHRleHQgc3RyaW5ncyBmcm9tIHRoZSB2YXJpYWJsZXMgdGhhdCB3ZSB3YW50IHRvIGluc2VydCBhcyB2YWx1ZXMuIFRoaXMgbWF5IGluYWR2ZXJ0ZW50bHkgY2F1c2UgU1FMIGluamVjdGlvbiBzZWN1cml0eSBpc3N1ZXMgYnV0IGlzIG9mdGVuIG5lY2Vzc2FyeSBpZiBhIGRhdGFiYXNlIG9yIHRoZSBwcm9ncmFtbWluZyBsYW5ndWFnZSdzIGRhdGFiYXNlIGRyaXZlcnMgZG8gbm90IHN1cHBvcnQgcHJlcGFyZWQgc3RhdGVtZW50cy4KClRoZSBjb2RlIGJlbG93IHNob3dzIHRoaXMgYXBwcm9hY2guIFRoaXMgYXBwcm9hY2ggd2lsbCB3b3JrIGZvciAqZGI0ZnJlZSogYW5kIG90aGVyIGNsb3VkIE15U1FMIGRhdGFiYXNlcy4KCmBgYHtzcWwgY29ubmVjdGlvbj1kYjRmcmVlLCBlY2hvPUZ9CmRyb3AgdGFibGUgaWYgZXhpc3RzIGZsaWdodHM7CmBgYAoKYGBge3NxbCBjb25uZWN0aW9uPWRiNGZyZWUsIGVjaG89Rn0KZHJvcCB0YWJsZSBpZiBleGlzdHMgYWlybGluZXM7CmBgYAoKYGBge3NxbCBjcmVhdGVUYWJsZUFpcmxpbmVzUHJlcFN0bXQsIGNvbm5lY3Rpb249ZGI0ZnJlZX0KY3JlYXRlIHRhYmxlIGFpcmxpbmVzICgKICBhaWQgaW50ZWdlciBwcmltYXJ5IGtleSwKICBjYXJyaWVyIHRleHQgbm90IG51bGwsCiAgYWlybGluZSB0ZXh0IG5vdCBudWxsLAogIGNvdW50cnkgdGV4dCBub3QgbnVsbAopOwpgYGAKCmBgYHtzcWwgY3JlYXRlVGFibGVGbGlnaHRzcHJlcFN0bXQsIGNvbm5lY3Rpb249ZGI0ZnJlZX0KY3JlYXRlIHRhYmxlIGZsaWdodHMgKAogIGZpZCBpbnRlZ2VyIHByaW1hcnkga2V5LAogIGRlcCB0ZXh0IG5vdCBudWxsLAogIGFyciB0ZXh0IG5vdCBudWxsLAogIGFpZCBpbnRlZ2VyIG5vdCBudWxsLAogIGZvcmVpZ24ga2V5IChhaWQpIHJlZmVyZW5jZXMgYWlybGluZXMoYWlkKQopOwpgYGAKCmBgYHtzcWwgY29ubmVjdGlvbj1kYjRmcmVlLCBlY2hvPUZ9CmRlbGV0ZSBmcm9tIGZsaWdodHM7CmBgYAoKYGBge3NxbCBjb25uZWN0aW9uPWRiNGZyZWUsIGVjaG89Rn0KZGVsZXRlIGZyb20gYWlybGluZXM7CmBgYAoKYGBge3IgbG9hZERhdGFCeVJvd0ludG9kYjRmcmVlV2l0aFBhc3RlfQpuLmFpcmxpbmVzIDwtIG5yb3coZGYuYWlybGluZXMpCmZvciAociBpbiAxOm4uYWlybGluZXMpewogIHNxbFN0bXQgPC0gcGFzdGUwKAogICAgImluc2VydCBpbnRvIGFpcmxpbmVzIHZhbHVlcyAoIiwKICAgICAgICBkZi5haXJsaW5lcyRhaWRbcl0sICIsIiwKICAgICAgICAiJyIsIGRmLmFpcmxpbmVzJGNhcnJpZXJbcl0sICInLCIsCiAgICAgICAgIiciLCBkZi5haXJsaW5lcyRhaXJsaW5lW3JdLCAiJywiLAogICAgICAgICInIiwgZGYuYWlybGluZXMkY291bnRyeVtyXSwgIiciLAogICAgICAgICIpIikKICBuciA8LSBkYlNlbmRTdGF0ZW1lbnQoY29ubiA9IGRiNGZyZWUsIAogICAgICAgICAgICAgICAgICAgICAgICBzdGF0ZW1lbnQgPSBzcWxTdG10KQp9CgpuLmZsaWdodHMgPC0gbnJvdyhkZi5mbGlnaHRzKQpmb3IgKHIgaW4gMTpuLmZsaWdodHMpIHsKICBzcWxTdG10IDwtIHBhc3RlMCgKICAgICJpbnNlcnQgaW50byBmbGlnaHRzIHZhbHVlcyAoIiwKICAgICAgICBkZi5mbGlnaHRzJGZpZFtyXSwgIiwiLAogICAgICAgICInIiwgZGYuZmxpZ2h0cyRkZXBbcl0sICInLCIsCiAgICAgICAgIiciLCBkZi5mbGlnaHRzJGFycltyXSwgIicsIiwKICAgICAgICAiJyIsIGRmLmZsaWdodHMkYWlkW3JdLCAiJyIsCiAgICAgICAgIikiKQogIG5yIDwtIGRiU2VuZFN0YXRlbWVudChjb25uID0gZGI0ZnJlZSwgCiAgICAgICAgICAgICAgICAgICAgICAgIHN0YXRlbWVudCA9IHNxbFN0bXQpCn0KYGBgCgpMZXQncyBkbyBhIGZpbmFsIGNoZWNrIHRvIGVuc3VyZSB0aGF0IHRoZSBkYXRhIGlzIGluIHRoZSBkYXRhYmFzZS4KCmBgYHtzcWwgc2hvd0FpcmxpbmVzNCwgY29ubmVjdGlvbj1kYjRmcmVlfQpzZWxlY3QgKiBmcm9tIGFpcmxpbmVzIGxpbWl0IDM7CmBgYAoKYGBge3NxbCBzaG93RmxpZ2h0czQsIGNvbm5lY3Rpb249ZGI0ZnJlZX0Kc2VsZWN0ICogZnJvbSBmbGlnaHRzIGxpbWl0IDM7CmBgYAoKSXQgd29ya2VkIHRvby4gU28sIG5vdyB5b3UgaGF2ZSB0d28gd2F5cyB0byBsb2FkIGRhdGEgZnJvbSBkYXRhIGZyYW1lcyBpbnRvIGEgZGF0YWJhc2UuIFlvdSBhbHNvIGhhdmUgYSB3YXkgdG8gc3BsaXQgZGF0YSBmcm9tIGEgc2luZ2xlIENTViBpbnRvIG11bHRpcGxlIHRhYmxlcyBhbmQgbGlua2luZyB0aGVtLiBUaGlzIHR5cGUgb2Ygd29yayBpcyBvZnRlbiBuZWNlc3Nhcnkgd2hlbiBsb2FkaW5nIGRhdGEgZnJvbSB0aGlyZCBwYXJ0eSBhcHBsaWNhdGlvbnMgb3IgZXh0ZXJuYWwgc291cmNlcy4KCiMjIENsb3NlIHRoZSBEYXRhYmFzZQoKQWZ0ZXIgYWNjZXNzaW5nIGEgZGF0YWJhc2UgYW5kIHdoZW4gbm8gZnVydGhlciBhY2Nlc3MgaXMgbmVlZGVkLCBhbHdheXMgY2xvc2UgdGhlIGNvbm5lY3Rpb24gYW5kIHJlbGVhc2UgbG9jYWwgcmVzb3VyY2VzIGFuZCByZXNvdXJjZXMgb24gdGhlIGRhdGFiYXNlIHNlcnZlci4gTW9zdCBkYXRhYmFzZXMgcmVzdHJpY3QgdGhlIG51bWJlciBvZiBzaW11bHRhbmVvdXMgY29ubmVjdGlvbnMgYW5kIGJ5IG5vdCBkaXNjb25uZWN0aW5nIHdoZW4gbmVlZGVkIHdlIGNhbiBvZnRlbiBjb25zdW1lIGFsbCByZXNvdXJjZXMgbmVlZGxlc3NseSBhbmQgYmFyIG90aGVyIGFwcGxpY2F0aW9ucyBmcm9tIGNvbm5lY3RpbmcgdG8gdGhlIGRhdGFiYXNlLiBUaGUgbnVtYmVyIG9mIGNvbmN1cnJlbnQgY29ubmVjdGlvbnMgaXMgYSBmYWN0b3Igb2YgdGhlIHZlbmRvcidzIGxpY2Vuc2UgKG1vcmUgY29ubmVjdGlvbnMgY29zdCBtb3JlIG1vbmV5KSBvciBjb25maWd1cmF0aW9uLiBXaGVuIGNvbm5lY3RpbmcsIHlvdSBjYW4gc29tZXRpbWVzIHNwZWNpZnkgdGhlIG51bWJlciBvZiBjb25jdXJyZW50IGNvbm5lY3Rpb25zLiBOb3RlIHRoYXQgaGF2aW5nIHRvbyBtYW55IGNvbm5lY3Rpb25zIG9wZW4gc2ltdWx0YW5lb3VzbHkgY2FuIHNsb3cgZG93biBkYXRhYmFzZSBwZXJmb3JtYW5jZS4KCmBgYHtyIGRpc2Nvbm5lY3REQiwgZWNobz1GfQpkYkRpc2Nvbm5lY3QoZGJjb24pCmRiRGlzY29ubmVjdChkYjRmcmVlKQpgYGAKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMgRmlsZXMgJiBSZXNvdXJjZXMKCmBgYHtyIHppcEZpbGVzLCBlY2hvPUZBTFNFfQp6aXBOYW1lID0gc3ByaW50ZigiTGVzc29uRmlsZXMtJXMtJXMuemlwIiwgCiAgICAgICAgICAgICAgICAgcGFyYW1zJGNhdGVnb3J5LAogICAgICAgICAgICAgICAgIHBhcmFtcyRudW1iZXIpCgp0ZXh0QUxpbmsgPSBwYXN0ZTAoIkFsbCBGaWxlcyBmb3IgTGVzc29uICIsIAogICAgICAgICAgICAgICBwYXJhbXMkY2F0ZWdvcnksIi4iLHBhcmFtcyRudW1iZXIpCgojIGRvd25sb2FkRmlsZXNMaW5rKCkgaXMgaW5jbHVkZWQgZnJvbSBfaW5zZXJ0MkRCLlIKa25pdHI6OnJhd19odG1sKGRvd25sb2FkRmlsZXNMaW5rKCIuIiwgemlwTmFtZSwgdGV4dEFMaW5rKSkKYGBgCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIFJlZmVyZW5jZXMKCltUaGUgc3FsZGYgUGFja2FnZS4gUiBEb2N1bWVudGF0aW9uLl1dKDxodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvc3FsZGYvdmVyc2lvbnMvMC40LTExPikKCiMjIEVycmF0YQoKTm9uZSBjb2xsZWN0ZWQgeWV0LiBMZXQgdXMga25vdy4KCmBgYHs9aHRtbH0KPHNjcmlwdCBzcmM9Imh0dHBzOi8vZm9ybS5qb3Rmb3JtLmNvbS9zdGF0aWMvZmVlZGJhY2syLmpzIiB0eXBlPSJ0ZXh0L2phdmFzY3JpcHQiPgogIG5ldyBKb3Rmb3JtRmVlZGJhY2soewogICAgZm9ybUlkOiAiMjEyMTg3MDcyNzg0MTU3IiwKICAgIGJ1dHRvblRleHQ6ICJGZWVkYmFjayIsCiAgICBiYXNlOiAiaHR0cHM6Ly9mb3JtLmpvdGZvcm0uY29tLyIsCiAgICBiYWNrZ3JvdW5kOiAiI0Y1OTIwMiIsCiAgICBmb250Q29sb3I6ICIjRkZGRkZGIiwKICAgIGJ1dHRvblNpZGU6ICJsZWZ0IiwKICAgIGJ1dHRvbkFsaWduOiAiY2VudGVyIiwKICAgIHR5cGU6IGZhbHNlLAogICAgd2lkdGg6IDcwMCwKICAgIGhlaWdodDogNTAwLAogICAgaXNDYXJkRm9ybTogZmFsc2UKICB9KTsKPC9zY3JpcHQ+CmBgYApgYGB7ciBjb2RlPXhmdW46OnJlYWRfdXRmOChwYXN0ZTAoaGVyZTo6aGVyZSgpLCcvUi9fZGVwbG95S25pdC5SJykpLCBpbmNsdWRlID0gRkFMU0V9CmBgYAo=