Prerequisites
Prior to this lesson, consider working through:
Introduction
This tutorial uses SQL code chunks in R Studio to create a new SQLite database, create several tables, insert sample data, and perform simple data retrievals.
In this quick tutorial with demonstrations, Khoury Boston’s Prof. Schedlbauer explains how to create a new SQLite data using R Studio, how to create tables, inert data into those tables, and show the data in the tables.
The tutorial video below goes through the code interactively and should be viewed in conjunction with the code below.
Let’s take a look at each of the elements that were addressed in the above video…
Create Database
A new SQLite database is created by connecting to a database that does not exist. This forces creation of a new database file. Note that SQLite is not a server-based database management system like MySQL or Oracle, but is a file-based relational database akin to Microsoft Access.
In the example below, the database will be created in the current folder – if the code is in an R Project, then the database file will be created in the project folder. If the database is to be created in a different folder, then a path name must be provided.
By convention, SQLite database files generally have the extension .db or .sqlitedb.
library(RSQLite)
fpath = ""
dbfile = "committeeDB.sqlite"
# if database file already exists, we connect to it, otherwise
# we create a new database
dbcon <- dbConnect(RSQLite::SQLite(), paste0(fpath,dbfile))
The SQL code below is inserted using {sql} code chunks in an R Notebook, although there are also R functions for executing SQL statements, but {sql} chunks are simpler. Each chunk can only contain a single SQL statement, so the semi-colon at the end is optional.
After inserting a SQL chunk, the parameter connection= must be set to the database connection object returning from dbConnect()
. So, given our connection code above, we need to write a SQL chunk starting with ```{sql connection=dbcon}
.
The remaining sections are independent of SQLite and also apply to other relational databases, such as MySQL.
Setting Default Connection
If only one SQLite (or other) database connection is used for SQL code chunks, then a default connection can bet configured which means that the connections string connection=dbcon can be omitted. The R code fragment must be included after you made a connection to the database. Naturally, replace the connection variable dbcon below with the variable from your own code, i.e., the variable to which you assigned the return value from dbConnect()
.
library(DBI)
dbcon <- dbConnect(RSQLite::SQLite(), dbname = "sql.sqlite")
knitr::opts_chunk$set(connection = "dbcon")
Create New Tables
Creating Tables with {sql} Chunks
The DROP statement deletes any tables that might already exist.
DROP TABLE IF EXISTS Faculty
Now that we know that the table Faculty does not exist, we will create it usign the CREATE TABLE statement, listing each column and tagging the primary key column(s).
CREATE TABLE Faculty(
fid INTEGER NOT NULL,
fname TEXT NOT NULL,
lname TEXT NOT NULL,
rank TEXT,
PRIMARY KEY (fid)
);
We will now create another table and link that table with a foreign key to the Faculty table.
DROP TABLE IF EXISTS Committee
CREATE TABLE Committee (
name TEXT NOT NULL,
level TEXT,
chair INTEGER NOT NULL,
PRIMARY KEY (name),
FOREIGN KEY (chair) REFERENCES Faculty(fid)
)
Creating Tables with R Functions
Let’s redo the same SQL statements as above but this time using R functions. Assume that the tables have been deleted (dropped). We will use the same database connection object. The most common function to sending a SQL statement to the database is dbExecute()
. It submits and synchronously executes the SQL data manipulation statement (_e.g., UPDATE, DELETE, INSERT, CREATE TABLE, DROP TABLE, etc.). To submit SQL SELECT statements use either dbExecute()
or dbGetQuery()
. dbExecute()
returns the number of rows that were affected by the statement. An alternative function is dbSendStatement()
which is often used to build prepared statements.
Note how we build the SQL command as a character string using paste0()
so it can span multiple lines and is easier to read.
It is common to not get the SQL statement correct. So, it’s best to test the SQL statement using either a {sql} chunk or an interactive SQL query tool such as SQLite Console or MySQL Workbench.
sql <- paste0(
"CREATE TABLE Faculty (",
"fid INTEGER NOT NULL,",
"fname TEXT NOT NULL,",
"lname TEXT NOT NULL,",
"rank TEXT,",
"PRIMARY KEY (fid)",
")"
)
dbExecute(dbcon, sql)
sql <- paste0(
"CREATE TABLE Committee (",
"name TEXT NOT NULL,",
"level TEXT,",
"chair INTEGER NOT NULL,",
"PRIMARY KEY (name),",
"FOREIGN KEY (chair) REFERENCES Faculty(fid)",
")"
)
dbExecute(dbcon, sql)
dbExecute()
is also used to call a stored procedure.
Foreign Key Constraints
Unlike other databases, SQLite does not check foreign key constraints, by default. To enable foreign key constraints, include the following PRAGMA command before creating any tables:
Insert Data
Now that we have the tables, we will insert sample data for testing. The code below uses {sql} chunks and is most commonly used in R Notebooks. After this, we will show how to use R functions to do the same operations. That approach is required for R Scripts (aka R programs).
Insert Using {sql} Chunks
INSERT INTO Faculty (fid,fname,lname,rank) VALUES
(100,"Jose","Annunziato","Assistant"),
(200,"Dan","Feinberg","Adjunct"),
(300,"Martin","Schedlbauer","Full"),
(400,"Kathleen","Durant","Associate");
INSERT INTO Committee VALUES
("Hiring","College",300),
("Merit","College", 300),
("Teaching","Department",400)
Insert Using R Functions
Let’s insert some additional data using the R function dbexecute()
. An alternative would be to use dbSendStatement()
. Recall that dbexecute()
returns the number of rows affected, so for an INSERT that should be the number of rows inserted. If it it less than what was stated or 0, then some error occurred. We can check for that in the application logic.
Note the use of single quotes so that double quotes can be embedded.
sql <- paste0(
'INSERT INTO Committee VALUES',
'("Integrity","College",100),',
'("Research","College", 300),',
'("CATLR","University",400)'
)
n <- dbExecute(dbcon, sql)
if (n < 1)
print("Error during insert")
Other Data Manipulations
Other SQL operations such as DELETE, DROP TABLE, UPDATE, ALTER TABLE, etc. can also be executed using {sql} chunks or dbExecute()
.
Retrieve Data
The database is now complete. Verify for yourself that the data is in the database by executing some SELECT statements. Again, we will first do a retrieval using a {sql} chunks and then using R functions.
Select in {sql} Chunks
SELECT * FROM Faculty LIMIT 3;
Table 1: 3 records
100 |
Jose |
Annunziato |
Assistant |
200 |
Dan |
Feinberg |
Adjunct |
300 |
Martin |
Schedlbauer |
Full |
The SQL keywords (e.g., SELECT and INSERT) can be upper or lower case, but the columns should follow the capitalization in the schema definition.
The semi-colon (;) at the end of a SQL statement is optional in a {sql} chunk.
Select Using R Functions
While there are several functions for retrieving data from a database. The most common, and simplest, is dbGetQuery()
. It returns a result set in the form of a data frame.
sql <- "SELECT * FROM Faculty"
rs <- dbGetQuery(dbcon, sql)
print(rs)
fid fname lname rank
1 100 Jose Annunziato Assistant
2 200 Dan Feinberg Adjunct
3 300 Martin Schedlbauer Full
4 400 Kathleen Durant Associate
As the result set is a data frame, it can be processed like any other data frame. Note that dbGetQuery()
attempts to match the data types for the columns of the data frame to the database data types.
lnames <- rs$lname
print(lnames)
[1] "Annunziato" "Feinberg" "Schedlbauer" "Durant"
Disconnect from Database
When a database connection is no longer needed, it must be closed by disconnecting from the database. This frees up database and other resources.
Tutorial
In this video tutorial, Khoury Boston’s Prof. Durant explains in detail how to create tables in a relational databases using SQL Data Definition Language constructs, particularly CREATE TABLE
. Although the tutorial uses examples from MySQL, the same SQL statements apply to SQLite, SQL Server, Informix, DB2, and all other relational databases as SQL is a standard.
Example R Program
The code below shows the complete sequence of connect to, creating, inserting, and querying a SQLite database from an R program, rather than using SQL code chunks within an R Notebook. Of course, in an R Notebook, the SQL code chunks are translated into R function calls when the notebook is knitted, so there’s no difference ultimately between an R Notebook or an R program – the difference is that an R Notebook produces a document whereas an R program is an actual program.
# Load necessary library
library(RSQLite)
# Create a new SQLite database
db <- dbConnect(SQLite(), dbname = "sample.db")
# Create a table named 'customers'
dbExecute(db, "
CREATE TABLE IF NOT EXISTS customers (
cid INTEGER PRIMARY KEY,
name TEXT,
email TEXT
)
")
## [1] 0
# Insert 3 rows of synthetic data into the 'customers' table
dbExecute(db, "INSERT INTO customers (cid, name, email) VALUES (1, 'Alice', 'alice@example.com')")
## [1] 1
dbExecute(db, "INSERT INTO customers (cid, name, email) VALUES (2, 'Bob', 'bob@example.com')")
## [1] 1
dbExecute(db, "INSERT INTO customers (cid, name, email) VALUES (3, 'Charlie', 'charlie@example.com')")
## [1] 1
# Retrieve all rows in the 'customers' table and display them
customers <- dbGetQuery(db, "SELECT * FROM customers")
print(customers)
## cid name email
## 1 1 Alice alice@example.com
## 2 2 Bob bob@example.com
## 3 3 Charlie charlie@example.com
# Close the connection to the database
dbDisconnect(db)
LS0tCnRpdGxlOiAiU1FMaXRlIHdpdGggUjogQSBQcmltZXIiCnBhcmFtczoKICBjYXRlZ29yeTogNgogIG51bWJlcjogMzAwCiAgdGltZTogMjAKICBsZXZlbDogYmVnaW5uZXIKICB0YWdzOiAicixTUUwsc3FsZGYsZGF0YWJhc2Usc3FsaXRlIgogIGRlc2NyaXB0aW9uOiAiRGVtb25zdHJhdGVzIGhvdyB0byBjcmVhdGUgYSBzaW1wbGUgU1FMaXRlIChvcgogICAgICAgICAgICAgICAgYW55IG90aGVyIHJlbGF0aW9uYWwpIGRhdGFiYXNlIGZyb20gUiB1c2luZyB7c3FsfQogICAgICAgICAgICAgICAgY29kZSBjaHVua3MgYW5kIGZ1bmN0aW9uIGNhbGxzLiBNb3N0IG9mIHRoZSBjb25jZXB0cwogICAgICAgICAgICAgICAgYXBwbHkgdG8gYWxsIHJlbGF0aW9uYWwgZGF0YWJhc2VzLCBpbmNsdWRpbmcgTXlTUUwiCmRhdGU6ICI8c21hbGw+YHIgU3lzLkRhdGUoKWA8L3NtYWxsPiIKYXV0aG9yOiAiPHNtYWxsPk1hcnRpbiBTY2hlZGxiYXVlcjwvc21hbGw+IgplbWFpbDogIm0uc2NoZWRsYmF1ZXJAbmV1LmVkdSIKYWZmaWxpdGF0aW9uOiAiTm9ydGhlYXN0ZXJuIFVuaXZlcnNpdHkiCm91dHB1dDogCiAgYm9va2Rvd246Omh0bWxfZG9jdW1lbnQyOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvbGxhcHNlZDogZmFsc2UKICAgIG51bWJlcl9zZWN0aW9uczogZmFsc2UKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIHRoZW1lOiBzcGFjZWxhYgogICAgaGlnaGxpZ2h0OiB0YW5nbwotLS0KCi0tLQp0aXRsZTogIjxzbWFsbD5gciBwYXJhbXMkY2F0ZWdvcnlgLmByIHBhcmFtcyRudW1iZXJgPC9zbWFsbD48YnIvPjxzcGFuIHN0eWxlPSdjb2xvcjogIzJFNDA1MzsgZm9udC1zaXplOiAwLjllbSc+YHIgcm1hcmtkb3duOjptZXRhZGF0YSR0aXRsZWA8L3NwYW4+IgotLS0KCmBgYHtyIGNvZGU9eGZ1bjo6cmVhZF91dGY4KHBhc3RlMChoZXJlOjpoZXJlKCksJy9SL19pbnNlcnQyREIuUicpKSwgaW5jbHVkZSA9IEZBTFNFfQpgYGAKCiMjIFByZXJlcXVpc2l0ZXMKClByaW9yIHRvIHRoaXMgbGVzc29uLCBjb25zaWRlciB3b3JraW5nIHRocm91Z2g6CgotICAgWzcwLjgwMSDilIYgVGhlIFNRTGl0ZSBEYXRhYmFzZV0oaHR0cDovL2FydGlmaWNpdW0udXMvbGVzc29ucy83MC5zcWwvbC03MC04MDEtaW50cm8tc3FsaXRlL2wtNzAtODAxLmh0bWwpCgojIyBJbnRyb2R1Y3Rpb24KClRoaXMgdHV0b3JpYWwgdXNlcyBTUUwgY29kZSBjaHVua3MgaW4gUiBTdHVkaW8gdG8gY3JlYXRlIGEgbmV3IFNRTGl0ZSBkYXRhYmFzZSwgY3JlYXRlIHNldmVyYWwgdGFibGVzLCBpbnNlcnQgc2FtcGxlIGRhdGEsIGFuZCBwZXJmb3JtIHNpbXBsZSBkYXRhIHJldHJpZXZhbHMuCgpJbiB0aGlzIHF1aWNrIHR1dG9yaWFsIHdpdGggZGVtb25zdHJhdGlvbnMsIEtob3VyeSBCb3N0b24ncyBQcm9mLiBTY2hlZGxiYXVlciBleHBsYWlucyBob3cgdG8gY3JlYXRlIGEgbmV3IFNRTGl0ZSBkYXRhIHVzaW5nIFIgU3R1ZGlvLCBob3cgdG8gY3JlYXRlIHRhYmxlcywgaW5lcnQgZGF0YSBpbnRvIHRob3NlIHRhYmxlcywgYW5kIHNob3cgdGhlIGRhdGEgaW4gdGhlIHRhYmxlcy4KClRoZSB0dXRvcmlhbCB2aWRlbyBiZWxvdyBnb2VzIHRocm91Z2ggdGhlIGNvZGUgaW50ZXJhY3RpdmVseSBhbmQgc2hvdWxkIGJlIHZpZXdlZCBpbiBjb25qdW5jdGlvbiB3aXRoIHRoZSBjb2RlIGJlbG93LgoKYGBgez1odG1sfQo8aWZyYW1lIHN0eWxlPSJib3JkZXI6IDBweCBzb2xpZCAjNDY0NjQ2OyIgc3JjPSJodHRwczovL25vcnRoZWFzdGVybi5ob3N0ZWQucGFub3B0by5jb20vUGFub3B0by9QYWdlcy9FbWJlZC5hc3B4P2lkPTljNTgxYmU3LWU4OTQtNDg3MS05YzE4LWFjYzgwMTcyYmQ4MiZhbXA7YXV0b3BsYXk9ZmFsc2UmYW1wO29mZmVydmlld2VyPXRydWUmYW1wO3Nob3d0aXRsZT1mYWxzZSZhbXA7c2hvd2JyYW5kPWZhbHNlJmFtcDtzdGFydD0wJmFtcDtpbnRlcmFjdGl2aXR5PWFsbCIgd2lkdGg9IjQ4MCIgaGVpZ2h0PSIyNzAiIGFsbG93ZnVsbHNjcmVlbj0iYWxsb3dmdWxsc2NyZWVuIiBhbGxvdz0iYXV0b3BsYXkiIGRhdGEtZXh0ZXJuYWw9IjEiPgo8L2lmcmFtZT4KYGBgCkxldCdzIHRha2UgYSBsb29rIGF0IGVhY2ggb2YgdGhlIGVsZW1lbnRzIHRoYXQgd2VyZSBhZGRyZXNzZWQgaW4gdGhlIGFib3ZlIHZpZGVvLi4uCgojIyBDcmVhdGUgRGF0YWJhc2UKCkEgbmV3IFNRTGl0ZSBkYXRhYmFzZSBpcyBjcmVhdGVkIGJ5IGNvbm5lY3RpbmcgdG8gYSBkYXRhYmFzZSB0aGF0IGRvZXMgbm90IGV4aXN0LiBUaGlzIGZvcmNlcyBjcmVhdGlvbiBvZiBhIG5ldyBkYXRhYmFzZSBmaWxlLiBOb3RlIHRoYXQgU1FMaXRlIGlzIG5vdCBhIHNlcnZlci1iYXNlZCBkYXRhYmFzZSBtYW5hZ2VtZW50IHN5c3RlbSBsaWtlIE15U1FMIG9yIE9yYWNsZSwgYnV0IGlzIGEgZmlsZS1iYXNlZCByZWxhdGlvbmFsIGRhdGFiYXNlIGFraW4gdG8gTWljcm9zb2Z0IEFjY2Vzcy4KCkluIHRoZSBleGFtcGxlIGJlbG93LCB0aGUgZGF0YWJhc2Ugd2lsbCBiZSBjcmVhdGVkIGluIHRoZSBjdXJyZW50IGZvbGRlciAtLSBpZiB0aGUgY29kZSBpcyBpbiBhbiBSIFByb2plY3QsIHRoZW4gdGhlIGRhdGFiYXNlIGZpbGUgd2lsbCBiZSBjcmVhdGVkIGluIHRoZSBwcm9qZWN0IGZvbGRlci4gSWYgdGhlIGRhdGFiYXNlIGlzIHRvIGJlIGNyZWF0ZWQgaW4gYSBkaWZmZXJlbnQgZm9sZGVyLCB0aGVuIGEgcGF0aCBuYW1lIG11c3QgYmUgcHJvdmlkZWQuCgpCeSBjb252ZW50aW9uLCBTUUxpdGUgZGF0YWJhc2UgZmlsZXMgZ2VuZXJhbGx5IGhhdmUgdGhlIGV4dGVuc2lvbiAqLmRiKiBvciAqLnNxbGl0ZWRiKi4KCmBgYHtyfQpsaWJyYXJ5KFJTUUxpdGUpCgpmcGF0aCA9ICIiCmRiZmlsZSA9ICJjb21taXR0ZWVEQi5zcWxpdGUiCgojIGlmIGRhdGFiYXNlIGZpbGUgYWxyZWFkeSBleGlzdHMsIHdlIGNvbm5lY3QgdG8gaXQsIG90aGVyd2lzZQojIHdlIGNyZWF0ZSBhIG5ldyBkYXRhYmFzZQpkYmNvbiA8LSBkYkNvbm5lY3QoUlNRTGl0ZTo6U1FMaXRlKCksIHBhc3RlMChmcGF0aCxkYmZpbGUpKQpgYGAKClRoZSBTUUwgY29kZSBiZWxvdyBpcyBpbnNlcnRlZCB1c2luZyAqe3NxbH0qIGNvZGUgY2h1bmtzIGluIGFuIFIgTm90ZWJvb2ssIGFsdGhvdWdoIHRoZXJlIGFyZSBhbHNvIFIgZnVuY3Rpb25zIGZvciBleGVjdXRpbmcgU1FMIHN0YXRlbWVudHMsIGJ1dCAqe3NxbH0qIGNodW5rcyBhcmUgc2ltcGxlci4gRWFjaCBjaHVuayBjYW4gb25seSBjb250YWluIGEgc2luZ2xlIFNRTCBzdGF0ZW1lbnQsIHNvIHRoZSBzZW1pLWNvbG9uIGF0IHRoZSBlbmQgaXMgb3B0aW9uYWwuCgpBZnRlciBpbnNlcnRpbmcgYSBTUUwgY2h1bmssIHRoZSBwYXJhbWV0ZXIgKmNvbm5lY3Rpb249KiBtdXN0IGJlIHNldCB0byB0aGUgZGF0YWJhc2UgY29ubmVjdGlvbiBvYmplY3QgcmV0dXJuaW5nIGZyb20gPGNvZGU+ZGJDb25uZWN0KCk8L2NvZGU+LiBTbywgZ2l2ZW4gb3VyIGNvbm5lY3Rpb24gY29kZSBhYm92ZSwgd2UgbmVlZCB0byB3cml0ZSBhIFNRTCBjaHVuayBzdGFydGluZyB3aXRoIDxjb2RlPlxgXGBcYHtzcWwgY29ubmVjdGlvbj1kYmNvbn08L2NvZGU+LgoKVGhlIHJlbWFpbmluZyBzZWN0aW9ucyBhcmUgaW5kZXBlbmRlbnQgb2YgU1FMaXRlIGFuZCBhbHNvIGFwcGx5IHRvIG90aGVyIHJlbGF0aW9uYWwgZGF0YWJhc2VzLCBzdWNoIGFzIE15U1FMLgoKIyMjIFNldHRpbmcgRGVmYXVsdCBDb25uZWN0aW9uCgpJZiBvbmx5IG9uZSBTUUxpdGUgKG9yIG90aGVyKSBkYXRhYmFzZSBjb25uZWN0aW9uIGlzIHVzZWQgZm9yIFNRTCBjb2RlIGNodW5rcywgdGhlbiBhIGRlZmF1bHQgY29ubmVjdGlvbiBjYW4gYmV0IGNvbmZpZ3VyZWQgd2hpY2ggbWVhbnMgdGhhdCB0aGUgY29ubmVjdGlvbnMgc3RyaW5nICpjb25uZWN0aW9uPWRiY29uKiBjYW4gYmUgb21pdHRlZC4gVGhlIFIgY29kZSBmcmFnbWVudCBtdXN0IGJlIGluY2x1ZGVkIGFmdGVyIHlvdSBtYWRlIGEgY29ubmVjdGlvbiB0byB0aGUgZGF0YWJhc2UuIE5hdHVyYWxseSwgcmVwbGFjZSB0aGUgY29ubmVjdGlvbiB2YXJpYWJsZSAqZGJjb24qIGJlbG93IHdpdGggdGhlIHZhcmlhYmxlIGZyb20geW91ciBvd24gY29kZSwgKmkuZS4qLCB0aGUgdmFyaWFibGUgdG8gd2hpY2ggeW91IGFzc2lnbmVkIHRoZSByZXR1cm4gdmFsdWUgZnJvbSBgZGJDb25uZWN0KClgLgoKYGBge3IgZGVmYXVsdENvbm5TZXR1cCwgZXZhbD1GfQpsaWJyYXJ5KERCSSkKZGJjb24gPC0gZGJDb25uZWN0KFJTUUxpdGU6OlNRTGl0ZSgpLCBkYm5hbWUgPSAic3FsLnNxbGl0ZSIpCmtuaXRyOjpvcHRzX2NodW5rJHNldChjb25uZWN0aW9uID0gImRiY29uIikKYGBgCgojIyBDcmVhdGUgTmV3IFRhYmxlcwoKIyMjIENyZWF0aW5nIFRhYmxlcyB3aXRoIHtzcWx9IENodW5rcwoKVGhlICpEUk9QKiBzdGF0ZW1lbnQgZGVsZXRlcyBhbnkgdGFibGVzIHRoYXQgbWlnaHQgYWxyZWFkeSBleGlzdC4KCmBgYHtzcWwgY29ubmVjdGlvbj1kYmNvbn0KRFJPUCBUQUJMRSBJRiBFWElTVFMgRmFjdWx0eQpgYGAKCk5vdyB0aGF0IHdlIGtub3cgdGhhdCB0aGUgdGFibGUgKkZhY3VsdHkqIGRvZXMgbm90IGV4aXN0LCB3ZSB3aWxsIGNyZWF0ZSBpdCB1c2lnbiB0aGUgKkNSRUFURSBUQUJMRSogc3RhdGVtZW50LCBsaXN0aW5nIGVhY2ggY29sdW1uIGFuZCB0YWdnaW5nIHRoZSBwcmltYXJ5IGtleSBjb2x1bW4ocykuCgpgYGB7c3FsIGNvbm5lY3Rpb249ZGJjb259CkNSRUFURSBUQUJMRSBGYWN1bHR5KAogIGZpZCBJTlRFR0VSIE5PVCBOVUxMLAogIGZuYW1lIFRFWFQgTk9UIE5VTEwsCiAgbG5hbWUgVEVYVCBOT1QgTlVMTCwKICByYW5rIFRFWFQsCiAgUFJJTUFSWSBLRVkgKGZpZCkKKTsKYGBgCgpXZSB3aWxsIG5vdyBjcmVhdGUgYW5vdGhlciB0YWJsZSBhbmQgbGluayB0aGF0IHRhYmxlIHdpdGggYSBmb3JlaWduIGtleSB0byB0aGUgKkZhY3VsdHkqIHRhYmxlLgoKYGBge3NxbCBjb25uZWN0aW9uPWRiY29ufQpEUk9QIFRBQkxFIElGIEVYSVNUUyBDb21taXR0ZWUKYGBgCgpgYGB7c3FsIGNvbm5lY3Rpb249ZGJjb259CkNSRUFURSBUQUJMRSBDb21taXR0ZWUgKAogIG5hbWUgVEVYVCBOT1QgTlVMTCwKICBsZXZlbCBURVhULAogIGNoYWlyIElOVEVHRVIgTk9UIE5VTEwsCiAgUFJJTUFSWSBLRVkgKG5hbWUpLAogIEZPUkVJR04gS0VZIChjaGFpcikgUkVGRVJFTkNFUyBGYWN1bHR5KGZpZCkKKQpgYGAKCiMjIyBDcmVhdGluZyBUYWJsZXMgd2l0aCBSIEZ1bmN0aW9ucwoKTGV0J3MgcmVkbyB0aGUgc2FtZSBTUUwgc3RhdGVtZW50cyBhcyBhYm92ZSBidXQgdGhpcyB0aW1lIHVzaW5nIFIgZnVuY3Rpb25zLiBBc3N1bWUgdGhhdCB0aGUgdGFibGVzIGhhdmUgYmVlbiBkZWxldGVkIChkcm9wcGVkKS4gV2Ugd2lsbCB1c2UgdGhlIHNhbWUgZGF0YWJhc2UgY29ubmVjdGlvbiBvYmplY3QuIFRoZSBtb3N0IGNvbW1vbiBmdW5jdGlvbiB0byBzZW5kaW5nIGEgU1FMIHN0YXRlbWVudCB0byB0aGUgZGF0YWJhc2UgaXMgYGRiRXhlY3V0ZSgpYC4gSXQgc3VibWl0cyBhbmQgc3luY2hyb25vdXNseSBleGVjdXRlcyB0aGUgU1FMIGRhdGEgbWFuaXB1bGF0aW9uIHN0YXRlbWVudCAoXF9lLmcuLCAqVVBEQVRFKiwgKkRFTEVURSosICpJTlNFUlQqLCAqQ1JFQVRFIFRBQkxFKiwgKkRST1AgVEFCTEUqLCAqZXRjLiopLiBUbyBzdWJtaXQgU1FMICpTRUxFQ1QqIHN0YXRlbWVudHMgdXNlIGVpdGhlciBgZGJFeGVjdXRlKClgIG9yIGBkYkdldFF1ZXJ5KClgLiBgZGJFeGVjdXRlKClgIHJldHVybnMgdGhlIG51bWJlciBvZiByb3dzIHRoYXQgd2VyZSBhZmZlY3RlZCBieSB0aGUgc3RhdGVtZW50LiBBbiBhbHRlcm5hdGl2ZSBmdW5jdGlvbiBpcyBgZGJTZW5kU3RhdGVtZW50KClgIHdoaWNoIGlzIG9mdGVuIHVzZWQgdG8gYnVpbGQgcHJlcGFyZWQgc3RhdGVtZW50cy4KCmBgYHtzcWwgY29ubmVjdGlvbj1kYmNvbiwgZXZhbD1GLCBpbmNsdWRlPUZ9CmRyb3AgdGFibGUgaWYgZXhpc3RzIEZhY3VsdHk7CmBgYAoKYGBge3NxbCBjb25uZWN0aW9uPWRiY29uLCBldmFsPUYsIGluY2x1ZGU9Rn0KZHJvcCB0YWJsZSBpZiBleGlzdHMgQ29tbWl0dGVlOwpgYGAKCk5vdGUgaG93IHdlIGJ1aWxkIHRoZSBTUUwgY29tbWFuZCBhcyBhIGNoYXJhY3RlciBzdHJpbmcgdXNpbmcgYHBhc3RlMCgpYCBzbyBpdCBjYW4gc3BhbiBtdWx0aXBsZSBsaW5lcyBhbmQgaXMgZWFzaWVyIHRvIHJlYWQuCgpJdCBpcyBjb21tb24gdG8gbm90IGdldCB0aGUgU1FMIHN0YXRlbWVudCBjb3JyZWN0LiBTbywgaXQncyBiZXN0IHRvIHRlc3QgdGhlIFNRTCBzdGF0ZW1lbnQgdXNpbmcgZWl0aGVyIGEge3NxbH0gY2h1bmsgb3IgYW4gaW50ZXJhY3RpdmUgU1FMIHF1ZXJ5IHRvb2wgc3VjaCBhcyBTUUxpdGUgQ29uc29sZSBvciBNeVNRTCBXb3JrYmVuY2guCgpgYGB7ciBjb21tZW50PSIiLCBldmFsPUZ9CnNxbCA8LSBwYXN0ZTAoCiAgIkNSRUFURSBUQUJMRSBGYWN1bHR5ICgiLAogICAgImZpZCBJTlRFR0VSIE5PVCBOVUxMLCIsCiAgICAiZm5hbWUgVEVYVCBOT1QgTlVMTCwiLAogICAgImxuYW1lIFRFWFQgTk9UIE5VTEwsIiwKICAgICJyYW5rIFRFWFQsIiwKICAgICJQUklNQVJZIEtFWSAoZmlkKSIsCiAgIikiCikKCmRiRXhlY3V0ZShkYmNvbiwgc3FsKQpgYGAKCmBgYHtyIGNvbW1lbnQ9IiIsIGV2YWw9Rn0Kc3FsIDwtIHBhc3RlMCgKICAiQ1JFQVRFIFRBQkxFIENvbW1pdHRlZSAoIiwKICAgICJuYW1lIFRFWFQgTk9UIE5VTEwsIiwKICAgICJsZXZlbCBURVhULCIsCiAgICAiY2hhaXIgSU5URUdFUiBOT1QgTlVMTCwiLAogICAgIlBSSU1BUlkgS0VZIChuYW1lKSwiLAogICAgIkZPUkVJR04gS0VZIChjaGFpcikgUkVGRVJFTkNFUyBGYWN1bHR5KGZpZCkiLAogICIpIgopCgpkYkV4ZWN1dGUoZGJjb24sIHNxbCkKYGBgCgpgZGJFeGVjdXRlKClgIGlzIGFsc28gdXNlZCB0byBjYWxsIGEgc3RvcmVkIHByb2NlZHVyZS4KCiMjIyBGb3JlaWduIEtleSBDb25zdHJhaW50cwoKVW5saWtlIG90aGVyIGRhdGFiYXNlcywgU1FMaXRlIGRvZXMgbm90IGNoZWNrIGZvcmVpZ24ga2V5IGNvbnN0cmFpbnRzLCBieSBkZWZhdWx0LiBUbyBlbmFibGUgZm9yZWlnbiBrZXkgY29uc3RyYWludHMsIGluY2x1ZGUgdGhlIGZvbGxvd2luZyBQUkFHTUEgY29tbWFuZCBiZWZvcmUgY3JlYXRpbmcgYW55IHRhYmxlczoKCmBgYHtzcWwgY29ubmVjdGlvbj1kYmNvbn0KUFJBR01BIGZvcmVpZ25fa2V5cyA9IE9OCmBgYAoKIyMgSW5zZXJ0IERhdGEKCk5vdyB0aGF0IHdlIGhhdmUgdGhlIHRhYmxlcywgd2Ugd2lsbCBpbnNlcnQgc2FtcGxlIGRhdGEgZm9yIHRlc3RpbmcuIFRoZSBjb2RlIGJlbG93IHVzZXMge3NxbH0gY2h1bmtzIGFuZCBpcyBtb3N0IGNvbW1vbmx5IHVzZWQgaW4gUiBOb3RlYm9va3MuIEFmdGVyIHRoaXMsIHdlIHdpbGwgc2hvdyBob3cgdG8gdXNlIFIgZnVuY3Rpb25zIHRvIGRvIHRoZSBzYW1lIG9wZXJhdGlvbnMuIFRoYXQgYXBwcm9hY2ggaXMgcmVxdWlyZWQgZm9yIFIgU2NyaXB0cyAoKmFrYSogUiBwcm9ncmFtcykuCgojIyMgSW5zZXJ0IFVzaW5nIHtzcWx9IENodW5rcwoKYGBge3NxbCBjb25uZWN0aW9uPWRiY29ufQpJTlNFUlQgSU5UTyBGYWN1bHR5IChmaWQsZm5hbWUsbG5hbWUscmFuaykgVkFMVUVTIAogICgxMDAsIkpvc2UiLCJBbm51bnppYXRvIiwiQXNzaXN0YW50IiksCiAgKDIwMCwiRGFuIiwiRmVpbmJlcmciLCJBZGp1bmN0IiksCiAgKDMwMCwiTWFydGluIiwiU2NoZWRsYmF1ZXIiLCJGdWxsIiksCiAgKDQwMCwiS2F0aGxlZW4iLCJEdXJhbnQiLCJBc3NvY2lhdGUiKTsKYGBgCgpgYGB7c3FsIGNvbm5lY3Rpb249ZGJjb259CklOU0VSVCBJTlRPIENvbW1pdHRlZSBWQUxVRVMgCiAgKCJIaXJpbmciLCJDb2xsZWdlIiwzMDApLAogICgiTWVyaXQiLCJDb2xsZWdlIiwgMzAwKSwKICAoIlRlYWNoaW5nIiwiRGVwYXJ0bWVudCIsNDAwKQpgYGAKCiMjIyBJbnNlcnQgVXNpbmcgUiBGdW5jdGlvbnMKCkxldCdzIGluc2VydCBzb21lIGFkZGl0aW9uYWwgZGF0YSB1c2luZyB0aGUgUiBmdW5jdGlvbiBgZGJleGVjdXRlKClgLiBBbiBhbHRlcm5hdGl2ZSB3b3VsZCBiZSB0byB1c2UgYGRiU2VuZFN0YXRlbWVudCgpYC4gUmVjYWxsIHRoYXQgYGRiZXhlY3V0ZSgpYCByZXR1cm5zIHRoZSBudW1iZXIgb2Ygcm93cyBhZmZlY3RlZCwgc28gZm9yIGFuICpJTlNFUlQqIHRoYXQgc2hvdWxkIGJlIHRoZSBudW1iZXIgb2Ygcm93cyBpbnNlcnRlZC4gSWYgaXQgaXQgbGVzcyB0aGFuIHdoYXQgd2FzIHN0YXRlZCBvciAwLCB0aGVuIHNvbWUgZXJyb3Igb2NjdXJyZWQuIFdlIGNhbiBjaGVjayBmb3IgdGhhdCBpbiB0aGUgYXBwbGljYXRpb24gbG9naWMuCgpOb3RlIHRoZSB1c2Ugb2Ygc2luZ2xlIHF1b3RlcyBzbyB0aGF0IGRvdWJsZSBxdW90ZXMgY2FuIGJlIGVtYmVkZGVkLgoKYGBge3J9CnNxbCA8LSBwYXN0ZTAoCiAgJ0lOU0VSVCBJTlRPIENvbW1pdHRlZSBWQUxVRVMnLCAKICAnKCJJbnRlZ3JpdHkiLCJDb2xsZWdlIiwxMDApLCcsCiAgJygiUmVzZWFyY2giLCJDb2xsZWdlIiwgMzAwKSwnLAogICcoIkNBVExSIiwiVW5pdmVyc2l0eSIsNDAwKScKICApCgpuIDwtIGRiRXhlY3V0ZShkYmNvbiwgc3FsKQoKaWYgKG4gPCAxKQogIHByaW50KCJFcnJvciBkdXJpbmcgaW5zZXJ0IikKYGBgCgojIyMgT3RoZXIgRGF0YSBNYW5pcHVsYXRpb25zCgpPdGhlciBTUUwgb3BlcmF0aW9ucyBzdWNoIGFzICpERUxFVEUqLCAqRFJPUCBUQUJMRSosICpVUERBVEUqLCAqQUxURVIgVEFCTEUqLCAqZXRjLiogY2FuIGFsc28gYmUgZXhlY3V0ZWQgdXNpbmcge3NxbH0gY2h1bmtzIG9yIGBkYkV4ZWN1dGUoKWAuCgojIyBSZXRyaWV2ZSBEYXRhCgpUaGUgZGF0YWJhc2UgaXMgbm93IGNvbXBsZXRlLiBWZXJpZnkgZm9yIHlvdXJzZWxmIHRoYXQgdGhlIGRhdGEgaXMgaW4gdGhlIGRhdGFiYXNlIGJ5IGV4ZWN1dGluZyBzb21lICpTRUxFQ1QqIHN0YXRlbWVudHMuIEFnYWluLCB3ZSB3aWxsIGZpcnN0IGRvIGEgcmV0cmlldmFsIHVzaW5nIGEge3NxbH0gY2h1bmtzIGFuZCB0aGVuIHVzaW5nIFIgZnVuY3Rpb25zLgoKIyMjIFNlbGVjdCBpbiB7c3FsfSBDaHVua3MKCmBgYHtzcWwgY29ubmVjdGlvbj1kYmNvbn0KU0VMRUNUICogRlJPTSBGYWN1bHR5IExJTUlUIDM7CmBgYAoKVGhlIFNRTCBrZXl3b3JkcyAoKmUuZy4qLCBTRUxFQ1QgYW5kIElOU0VSVCkgY2FuIGJlIHVwcGVyIG9yIGxvd2VyIGNhc2UsIGJ1dCB0aGUgY29sdW1ucyBzaG91bGQgZm9sbG93IHRoZSBjYXBpdGFsaXphdGlvbiBpbiB0aGUgc2NoZW1hIGRlZmluaXRpb24uCgpUaGUgc2VtaS1jb2xvbiAoOykgYXQgdGhlIGVuZCBvZiBhIFNRTCBzdGF0ZW1lbnQgaXMgb3B0aW9uYWwgaW4gYSB7c3FsfSBjaHVuay4KCiMjIyBTZWxlY3QgVXNpbmcgUiBGdW5jdGlvbnMKCldoaWxlIHRoZXJlIGFyZSBzZXZlcmFsIGZ1bmN0aW9ucyBmb3IgcmV0cmlldmluZyBkYXRhIGZyb20gYSBkYXRhYmFzZS4gVGhlIG1vc3QgY29tbW9uLCBhbmQgc2ltcGxlc3QsIGlzIGBkYkdldFF1ZXJ5KClgLiBJdCByZXR1cm5zIGEgcmVzdWx0IHNldCBpbiB0aGUgZm9ybSBvZiBhIGRhdGEgZnJhbWUuCgpgYGB7ciBjb21tZW50PSIifQpzcWwgPC0gIlNFTEVDVCAqIEZST00gRmFjdWx0eSIKCnJzIDwtIGRiR2V0UXVlcnkoZGJjb24sIHNxbCkKCnByaW50KHJzKQpgYGAKCkFzIHRoZSByZXN1bHQgc2V0IGlzIGEgZGF0YSBmcmFtZSwgaXQgY2FuIGJlIHByb2Nlc3NlZCBsaWtlIGFueSBvdGhlciBkYXRhIGZyYW1lLiBOb3RlIHRoYXQgYGRiR2V0UXVlcnkoKWAgYXR0ZW1wdHMgdG8gbWF0Y2ggdGhlIGRhdGEgdHlwZXMgZm9yIHRoZSBjb2x1bW5zIG9mIHRoZSBkYXRhIGZyYW1lIHRvIHRoZSBkYXRhYmFzZSBkYXRhIHR5cGVzLgoKYGBge3IgY29tbWVudD0iIn0KbG5hbWVzIDwtIHJzJGxuYW1lCgpwcmludChsbmFtZXMpCmBgYAoKIyMgRGlzY29ubmVjdCBmcm9tIERhdGFiYXNlCgpXaGVuIGEgZGF0YWJhc2UgY29ubmVjdGlvbiBpcyBubyBsb25nZXIgbmVlZGVkLCBpdCBtdXN0IGJlIGNsb3NlZCBieSBkaXNjb25uZWN0aW5nIGZyb20gdGhlIGRhdGFiYXNlLiBUaGlzIGZyZWVzIHVwIGRhdGFiYXNlIGFuZCBvdGhlciByZXNvdXJjZXMuCgpgYGB7ciBkaXNjb25uZWN0REJ9CmRiRGlzY29ubmVjdChkYmNvbikKYGBgCgojIyBUdXRvcmlhbAoKSW4gdGhpcyB2aWRlbyB0dXRvcmlhbCwgS2hvdXJ5IEJvc3RvbidzIFByb2YuIER1cmFudCBleHBsYWlucyBpbiBkZXRhaWwgaG93IHRvIGNyZWF0ZSB0YWJsZXMgaW4gYSByZWxhdGlvbmFsIGRhdGFiYXNlcyB1c2luZyBTUUwgRGF0YSBEZWZpbml0aW9uIExhbmd1YWdlIGNvbnN0cnVjdHMsIHBhcnRpY3VsYXJseSBgQ1JFQVRFIFRBQkxFYC4gQWx0aG91Z2ggdGhlIHR1dG9yaWFsIHVzZXMgZXhhbXBsZXMgZnJvbSBNeVNRTCwgdGhlIHNhbWUgU1FMIHN0YXRlbWVudHMgYXBwbHkgdG8gU1FMaXRlLCBTUUwgU2VydmVyLCBJbmZvcm1peCwgREIyLCBhbmQgYWxsIG90aGVyIHJlbGF0aW9uYWwgZGF0YWJhc2VzIGFzIFNRTCBpcyBhIHN0YW5kYXJkLgoKPGlmcmFtZSBzdHlsZT0iYm9yZGVyOiAxcHggc29saWQgIzQ2NDY0NjsiIHNyYz0iaHR0cDovL25vcnRoZWFzdGVybi5ob3N0ZWQucGFub3B0by5jb20vUGFub3B0by9QYWdlcy9FbWJlZC5hc3B4P2lkPTRhYzlhZGQ0LTk4ZWItNDQyYS04ZTVkLWFiZWQwMTBhNDFiNyZhbXA7YXV0b3BsYXk9ZmFsc2UmYW1wO29mZmVydmlld2VyPXRydWUmYW1wO3Nob3d0aXRsZT1mYWxzZSZhbXA7c2hvd2JyYW5kPWZhbHNlJmFtcDtzdGFydD0wJmFtcDtpbnRlcmFjdGl2aXR5PWFsbCIgd2lkdGg9IjQ4MCIgaGVpZ2h0PSIyNzAiIGFsbG93ZnVsbHNjcmVlbj0iYWxsb3dmdWxsc2NyZWVuIiBhbGxvdz0iYXV0b3BsYXkiIGRhdGEtZXh0ZXJuYWw9IjEiPgoKPC9pZnJhbWU+CgojIyBFeGFtcGxlIFIgUHJvZ3JhbQoKVGhlIGNvZGUgYmVsb3cgc2hvd3MgdGhlIGNvbXBsZXRlIHNlcXVlbmNlIG9mIGNvbm5lY3QgdG8sIGNyZWF0aW5nLCBpbnNlcnRpbmcsIGFuZCBxdWVyeWluZyBhIFNRTGl0ZSBkYXRhYmFzZSBmcm9tIGFuIFIgcHJvZ3JhbSwgcmF0aGVyIHRoYW4gdXNpbmcgU1FMIGNvZGUgY2h1bmtzIHdpdGhpbiBhbiBSIE5vdGVib29rLiBPZiBjb3Vyc2UsIGluIGFuIFIgTm90ZWJvb2ssIHRoZSBTUUwgY29kZSBjaHVua3MgYXJlIHRyYW5zbGF0ZWQgaW50byBSIGZ1bmN0aW9uIGNhbGxzIHdoZW4gdGhlIG5vdGVib29rIGlzIGtuaXR0ZWQsIHNvIHRoZXJlJ3Mgbm8gZGlmZmVyZW5jZSB1bHRpbWF0ZWx5IGJldHdlZW4gYW4gUiBOb3RlYm9vayBvciBhbiBSIHByb2dyYW0gLS0gdGhlIGRpZmZlcmVuY2UgaXMgdGhhdCBhbiBSIE5vdGVib29rIHByb2R1Y2VzIGEgZG9jdW1lbnQgd2hlcmVhcyBhbiBSIHByb2dyYW0gaXMgYW4gYWN0dWFsIHByb2dyYW0uCgpgYGB7ciByZW1EQiwgZWNobz1GfQp1bmxpbmsoInNhbXBsZS5kYiIpCmBgYAoKYGBge3IgY29tcGxldGVFeGFtcGxlLCBlY2hvPVR9CiMgTG9hZCBuZWNlc3NhcnkgbGlicmFyeQpsaWJyYXJ5KFJTUUxpdGUpCgojIENyZWF0ZSBhIG5ldyBTUUxpdGUgZGF0YWJhc2UKZGIgPC0gZGJDb25uZWN0KFNRTGl0ZSgpLCBkYm5hbWUgPSAic2FtcGxlLmRiIikKCiMgQ3JlYXRlIGEgdGFibGUgbmFtZWQgJ2N1c3RvbWVycycKZGJFeGVjdXRlKGRiLCAiCiAgQ1JFQVRFIFRBQkxFIElGIE5PVCBFWElTVFMgY3VzdG9tZXJzICgKICAgIGNpZCBJTlRFR0VSIFBSSU1BUlkgS0VZLAogICAgbmFtZSBURVhULAogICAgZW1haWwgVEVYVAogICkKIikKCiMgSW5zZXJ0IDMgcm93cyBvZiBzeW50aGV0aWMgZGF0YSBpbnRvIHRoZSAnY3VzdG9tZXJzJyB0YWJsZQpkYkV4ZWN1dGUoZGIsICJJTlNFUlQgSU5UTyBjdXN0b21lcnMgKGNpZCwgbmFtZSwgZW1haWwpIFZBTFVFUyAoMSwgJ0FsaWNlJywgJ2FsaWNlQGV4YW1wbGUuY29tJykiKQpkYkV4ZWN1dGUoZGIsICJJTlNFUlQgSU5UTyBjdXN0b21lcnMgKGNpZCwgbmFtZSwgZW1haWwpIFZBTFVFUyAoMiwgJ0JvYicsICdib2JAZXhhbXBsZS5jb20nKSIpCmRiRXhlY3V0ZShkYiwgIklOU0VSVCBJTlRPIGN1c3RvbWVycyAoY2lkLCBuYW1lLCBlbWFpbCkgVkFMVUVTICgzLCAnQ2hhcmxpZScsICdjaGFybGllQGV4YW1wbGUuY29tJykiKQoKIyBSZXRyaWV2ZSBhbGwgcm93cyBpbiB0aGUgJ2N1c3RvbWVycycgdGFibGUgYW5kIGRpc3BsYXkgdGhlbQpjdXN0b21lcnMgPC0gZGJHZXRRdWVyeShkYiwgIlNFTEVDVCAqIEZST00gY3VzdG9tZXJzIikKcHJpbnQoY3VzdG9tZXJzKQoKIyBDbG9zZSB0aGUgY29ubmVjdGlvbiB0byB0aGUgZGF0YWJhc2UKZGJEaXNjb25uZWN0KGRiKQoKYGBgCgojIyBTdW1tYXJ5CgpOb3cgdGhhdCB5b3UgaGF2ZSBjb21wbGV0ZWQgdGhpcyBwcmltZXIsIGNvbnNpZGVyIExlc3NvbiBbNi4zMDEgLSBXb3JraW5nIHdpdGggRGF0YWJhc2VzIGluIFJdKGh0dHA6Ly9hcnRpZmljaXVtLnVzL2xlc3NvbnMvMDYuci9sLTYtMzAxLXNxbGl0ZS1mcm9tLXIvbC02LTMwMS5odG1sKS4KCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMgRmlsZXMgJiBSZXNvdXJjZXMKCmBgYHtyIHppcEZpbGVzLCBlY2hvPUZBTFNFfQp6aXBOYW1lID0gc3ByaW50ZigiTGVzc29uRmlsZXMtJXMtJXMuemlwIiwgCiAgICAgICAgICAgICAgICAgcGFyYW1zJGNhdGVnb3J5LAogICAgICAgICAgICAgICAgIHBhcmFtcyRudW1iZXIpCgp0ZXh0QUxpbmsgPSBwYXN0ZTAoIkFsbCBGaWxlcyBmb3IgTGVzc29uICIsIAogICAgICAgICAgICAgICBwYXJhbXMkY2F0ZWdvcnksIi4iLHBhcmFtcyRudW1iZXIpCgojIGRvd25sb2FkRmlsZXNMaW5rKCkgaXMgaW5jbHVkZWQgZnJvbSBfaW5zZXJ0MkRCLlIKa25pdHI6OnJhd19odG1sKGRvd25sb2FkRmlsZXNMaW5rKCIuIiwgemlwTmFtZSwgdGV4dEFMaW5rKSkKYGBgCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIFNlZSBBbHNvCgotICAgWzcwLjgwMSAtIFRoZSBTUUxpdGUgRGF0YWJhc2VdKGh0dHA6Ly9hcnRpZmljaXVtLnVzL2xlc3NvbnMvNzAuc3FsL2wtNzAtODAxLWludHJvLXNxbGl0ZS9sLTcwLTgwMS5odG1sKQotICAgWzYuMTA0IC0gUXVpY2sgR3VpZGUgdG8gUiBGb3IgUHJvZ3JhbW1lcnNdKGh0dHA6Ly9hcnRpZmljaXVtLnVzL2xlc3NvbnMvMDYuci9sLTYtMTA0LXI0cHJvZ3MvbC02LTEwNC5odG1sKQotICAgWzYuMzAxIC0gV29ya2luZyB3aXRoIERhdGFiYXNlcyBpbiBSXShodHRwOi8vYXJ0aWZpY2l1bS51cy9sZXNzb25zLzA2LnIvbC02LTMwMS1zcWxpdGUtZnJvbS1yL2wtNi0zMDEuaHRtbCkKCiMjIFJlZmVyZW5jZXMKCltTUUxpdGUgQ1JFQVRFIFRBQkxFXShodHRwczovL3d3dy50dXRvcmlhbHNwb2ludC5jb20vc3FsaXRlL3NxbGl0ZV9jcmVhdGVfdGFibGUuaHRtKSBbU1FMaXRlIElOU0VSVF0oaHR0cHM6Ly93d3cuc3FsaXRldHV0b3JpYWwubmV0L3NxbGl0ZS1pbnNlcnQvKQoKIyMgRXJyYXRhCgpbTGV0IHVzIGtub3ddKGh0dHBzOi8vZm9ybS5qb3Rmb3JtLmNvbS8yMTIxODcwNzI3ODQxNTcpe3RhcmdldD0iX2JsYW5rIn0uCg==