Introducion
To run SQL clauses in R, we need to use several R libraries
(installed and loaded in the above R setup code chunk). There are
different ways to run SQL queries in R. We only introduce one of them
that runs the basic SQL code.
Connect R to Existing
Database
If there is an existing database, the following code connects R to
the database.
con <- DBI::dbConnect(drv = odbc::odbc(),
Driver = "driver_name",
Server = "server_url",
Database = "database_name",
user = "user", #optional
password = "password") #optional
Create A Database to
Run SQL Queries in R
This short note shows the three basic steps to run SQL in R using R
Markdown starting with a set of relational tables.
Load relational data tables as usual to R.
Create a SQLite (relational) database that contains these
relational tables.
Create an R code chunk and connect to the created database using
Chunk options.
Create SQLite Database
with R
If modeling requires a data set that contains information from
multiple relational data tables, we need to perform data management to
aggregate the required information from different data tables. We can
load the different data sets in different formats using appropriate R
functions.
As an example, We use three ecological survey data sets to create a
database.
#Load the sample data
plots <- read.csv("https://pengdsci.github.io/datasets/AnimalSurvey/plots.csv")
species <- read.csv("https://pengdsci.github.io/datasets/AnimalSurvey/species.csv")
surveys <- read.csv("https://pengdsci.github.io/datasets/AnimalSurvey/surveys.csv")
We next explore the relationship between the tables.
summary(plots)
plot_id plot_type
Min. : 1.00 Length:24
1st Qu.: 6.75 Class :character
Median :12.50 Mode :character
Mean :12.50
3rd Qu.:18.25
Max. :24.00
summary(species)
species_id genus species taxa
Length:54 Length:54 Length:54 Length:54
Class :character Class :character Class :character Class :character
Mode :character Mode :character Mode :character Mode :character
summary(surveys)
X record_id month day
Min. : 1 Min. : 1 Min. : 1.000 Min. : 1.00
1st Qu.: 8888 1st Qu.: 8888 1st Qu.: 4.000 1st Qu.: 9.00
Median :17775 Median :17775 Median : 6.000 Median :16.00
Mean :17775 Mean :17775 Mean : 6.478 Mean :15.99
3rd Qu.:26662 3rd Qu.:26662 3rd Qu.:10.000 3rd Qu.:23.00
Max. :35549 Max. :35549 Max. :12.000 Max. :31.00
year plot_id species_id sex
Min. :1977 Min. : 1.0 Length:35549 Length:35549
1st Qu.:1984 1st Qu.: 5.0 Class :character Class :character
Median :1990 Median :11.0 Mode :character Mode :character
Mean :1990 Mean :11.4
3rd Qu.:1997 3rd Qu.:17.0
Max. :2002 Max. :24.0
hindfoot_length weight
Min. : 2.00 Min. : 4.00
1st Qu.:21.00 1st Qu.: 20.00
Median :32.00 Median : 37.00
Mean :29.29 Mean : 42.67
3rd Qu.:36.00 3rd Qu.: 48.00
Max. :70.00 Max. :280.00
NA's :4111 NA's :3266
The relational table surveys
has attributes
species_id and plot_id that connect
relational tables plots
and species
. The
primary and foreign keys are depicted in the following figure.
include_graphics("img/RelationshipBetweenTables.png")
Next, we create a SQLit database using R libraries and then remove
the data frames from the working directory.
#Create database
con <- dbConnect(drv = SQLite(),
dbname = ":memory:")
#store sample data in the database
dbWriteTable(conn = con,
name = "plots",
value = plots)
dbWriteTable(conn = con,
name = "species",
value = species)
dbWriteTable(conn = con,
name = "surveys",
value = surveys)
#remove the local data from the environment
rm(plots, species, surveys)
We can use the table view function tbl()
to view the
relational data tables in the database.
tbl(src = con, # the source if the database connection profile
c("surveys")) # the name of the table to preview
# Source: table<surveys> [?? x 10]
# Database: sqlite 3.45.2 [:memory:]
X record_id month day year plot_id species_id sex hindfoot_length
<int> <int> <int> <int> <int> <int> <chr> <chr> <int>
1 1 1 7 16 1977 2 NL M 32
2 2 2 7 16 1977 3 NL M 33
3 3 3 7 16 1977 2 DM F 37
4 4 4 7 16 1977 7 DM M 36
5 5 5 7 16 1977 3 DM M 35
6 6 6 7 16 1977 1 PF M 14
7 7 7 7 16 1977 2 PE F NA
8 8 8 7 16 1977 1 DM M 37
9 9 9 7 16 1977 1 DM F 34
10 10 10 7 16 1977 6 PF F 20
# ℹ more rows
# ℹ 1 more variable: weight <int>
tbl(src = con, "species")
# Source: table<species> [?? x 4]
# Database: sqlite 3.45.2 [:memory:]
species_id genus species taxa
<chr> <chr> <chr> <chr>
1 AB Amphispiza bilineata Bird
2 AH Ammospermophilus harrisi Rodent
3 AS Ammodramus savannarum Bird
4 BA Baiomys taylori Rodent
5 CB Campylorhynchus brunneicapillus Bird
6 CM Calamospiza melanocorys Bird
7 CQ Callipepla squamata Bird
8 CS Crotalus scutalatus Reptile
9 CT Cnemidophorus tigris Reptile
10 CU Cnemidophorus uniparens Reptile
# ℹ more rows
tbl(src = con, "plots")
# Source: table<plots> [?? x 2]
# Database: sqlite 3.45.2 [:memory:]
plot_id plot_type
<int> <chr>
1 1 Spectab exclosure
2 2 Control
3 3 Long-term Krat Exclosure
4 4 Control
5 5 Rodent Exclosure
6 6 Short-term Krat Exclosure
7 7 Rodent Exclosure
8 8 Control
9 9 Spectab exclosure
10 10 Rodent Exclosure
# ℹ more rows
Running SQL Queries in
R
To use SQL in RMarkdown, we need the following chunk options:
- sql
- connection = “database-name”
- output.var = “output-dataset-name”
If we create a data view only, we simply ignore option
output.var =
Following are few examples of SQL queries based on the animal survey
data tables in the database.
Basic SQL
Operators
While working with databases, we use SQL queries to manipulate the
data and retrieve the desired result. This manipulation of data is
achieved through various SQL operators. An operator is
a keyword in SQL that helps us access the data and returns the result
based on the operator’s functionality. SQL provides us with many such
operators to ease the process of data manipulation. In this subsection,
we will look at three major types of operators in SQL.
Arithmetic Operators
Arithmetic operators are used to perform arithmetic operations such
as addition, subtraction, division, and multiplication. These operators
usually accept numeric operands.
+ |
Addition |
Adds operands on either side of the operator |
- |
Subtraction |
Subtracts the right-hand operand from the left-hand
operand |
* |
Multiplication |
Multiplies the values on each side |
/ |
Division |
Divides left-hand operand by right-hand operand |
% |
Modulus |
Divides left-hand operand by right-hand operand and
returns the remainder |
Comparison Operators
Comparison operators in SQL are used to check the equality of two
expressions. It checks whether one expression is identical to another.
Comparison operators are generally used in the WHERE
clause of a SQL query. The result of a comparison operation may
be TRUE, FALSE, or UNKNOWN. When one or both the expression is NULL,
then the operator returns UNKNOWN.
= |
Equal to |
Checks if both operands have equal value, if yes, then
returns TRUE |
> |
Greater than |
Checks if the value of the left-hand operand is greater
than the right-hand operand or not |
< |
Less than |
Returns TRUE if the value of the left-hand operand is
less than the value of the right-hand operand |
>= |
Greater than or equal to |
It checks if the value of the left-hand operand is
greater than or equal to the value of the right-hand operand, if yes,
then returns TRUE |
<= |
Less than or equal to |
Examines if the value of the left-hand operator is less
than or equal to the right-hand operand |
<> or != |
Not equal to |
Checks if values on either side of the operator are
equal or not. Returns TRUE if values are not equal |
Logical Operators
Logical operators are those operators that take two expressions as
operands and return TRUE or False as output.
ALL |
Compares a value to all other values in a set |
AND |
Returns the records if all the conditions separated by
AND are TRUE |
ANY |
Compares a specific value to any other values in a
set |
SOME |
Compares a value to each value in a set. It is similar
to ANY operator |
LIKE |
It returns the rows for which the operand matches a
specific pattern |
IN |
Used to compare a value to a specified value in a
list |
BETWEEN |
Returns the rows for which the value lies between the
mentioned range |
NOT |
Used to reverse the output of any logical operator |
EXISTS |
Used to search a row in a specified table in the
database |
OR |
Returns the records for which any of the conditions
separated by OR is true |
NULL |
Returns the rows where the operand is NULL |
The logical and comparison operators are usually used with
conditional queries in which we can specify a conditional expression in
a SELECT statement WHERE clause which specifies that
only those rows for which the conditional expression is true are to be
retrieved. The syntax for the SELECT statement
containing the WHERE clause is as follows.
select_statement:
SELECT … FROM table_name
WHERE conditional_expression
The following is a simple example. We want to create a subset such
that species_id
equals “DM” and
weight > 0
.
SELECT *
FROM surveys
WHERE species_id LIKE "DM" AND weight > 0;
Subsetting and
Duplicating Data
- Extract year, month, and day from
surveys
table
SELECT
surveys.year, surveys.month, surveys.Day
FROM
surveys -- pointer is not needed since it is in the database
WHERE
surveys.species_id IN ('NL', 'DM') AND
surveys.sex = 'M'
The code chunk defined the output of the query as an R data frame
(Caution: not a relational table stored in the SQLite database)
with the name YMD. We check the first few records from
the data frame
head(YMD)
year month day
1 1977 7 16
2 1977 7 16
3 1977 7 16
4 1977 7 16
5 1977 7 16
6 1977 7 16
If we simply create a data view without saving it as an R data frame,
we simply ignore the option output.var="YMD"
in the
SQL code chunk.
SELECT
surveys.year, surveys.month, surveys.Day
FROM
surveys -- pointer is not needed since it is in the database
WHERE
surveys.species_id IN ('NL', 'DM') AND
surveys.sex = 'M'
Displaying records 1 - 10
1977 |
7 |
16 |
1977 |
7 |
16 |
1977 |
7 |
16 |
1977 |
7 |
16 |
1977 |
7 |
16 |
1977 |
7 |
16 |
1977 |
7 |
16 |
1977 |
7 |
17 |
1977 |
7 |
17 |
1977 |
7 |
17 |
- Duplicate data and rename it
SELECT
surveys.*
FROM
surveys
Note that surveys is a relation data table in the
SQLite database and duplicated data is not in the SQLite database but an
R data frame in the working directory.
Caution: in the SQL code chunk, query statements
only work with SQL database. They don’t work for R data frames in the
working directory. The following code doesn’t work because the data set
SurveyCopy
is not in the SQLite database.
SELECT
SurveyCopy.*
FROM
SurveyCopy
In other words, if we want to query the data table (data frame)
SurveyCopy
, we need to add it to the database
con
defined earlier.
# Store sample data in the database
dbWriteTable(conn = con,
name = "SurveyCopy",
value = SurveyCopy)
## Remove SurveyCopy in the working directory
rm(SurveyCopy)
We can use tbl()
to view the newly added table in the
SQLite database con
.
tbl(src = con, c("SurveyCopy"))
# Source: table<SurveyCopy> [?? x 10]
# Database: sqlite 3.45.2 [:memory:]
X record_id month day year plot_id species_id sex hindfoot_length
<int> <int> <int> <int> <int> <int> <chr> <chr> <int>
1 1 1 7 16 1977 2 NL M 32
2 2 2 7 16 1977 3 NL M 33
3 3 3 7 16 1977 2 DM F 37
4 4 4 7 16 1977 7 DM M 36
5 5 5 7 16 1977 3 DM M 35
6 6 6 7 16 1977 1 PF M 14
7 7 7 7 16 1977 2 PE F NA
8 8 8 7 16 1977 1 DM M 37
9 9 9 7 16 1977 1 DM F 34
10 10 10 7 16 1977 6 PF F 20
# ℹ more rows
# ℹ 1 more variable: weight <int>
In R, we can use dbListTables()
to view relational
tables in the database.
dbListTables(con)
[1] "SurveyCopy" "plots" "species" "surveys"
We now can query the relational table SurveyCopy
in the
database con
using SQL clause as usual.
- Create a table view (i.e., no data set will be created and
saved)
SELECT
surveys.year, surveys.month, surveys.Day
FROM
surveys
WHERE
surveys.species_id = 'NL' AND
surveys.sex = 'M'
Define A New
Variable
- Define a new variable with simple arithmetic operations
SELECT
surveys.plot_id,
surveys.species_id,
surveys.sex,
surveys.weight,
surveys.weight/100 AS wt_kilo -- should not the pointer in front of
-- the name of the new variable
FROM
surveys
- Define new variables using string functions in SQL
SELECT surveys.*,
surveys.species_id||'-'||surveys.sex AS newKey
FROM surveys
- Define new variables with aggregated information
SELECT surveys.species_id,
COUNT(surveys.species_id) AS species_ctr
FROM surveys
GROUP BY surveys.species_id
HAVING species_ctr > 10
Sorting
Variables
- Sort data based on the summarized statistics of a variable
Summary functions are restricted to the SELECT and HAVING clauses
only;
SELECT surveys.species_id
FROM surveys
GROUP BY surveys.species_id
ORDER BY COUNT(surveys.species_id);
- Sort data based on a new variable defined using summarized
statistics of a variable.
/* create a table view*/
SELECT surveys.species_id AS subtotal,
COUNT(*)
FROM surveys
GROUP BY surveys.species_id
ORDER BY subtotal;
SQL Joins
SQL JOIN clause is used to query and access data from multiple tables
by establishing logical relationships between them. It can access data
from multiple tables simultaneously using common key values shared
across different tables. This section briefly introduces commonly used
join operations to merge tables using the common key(s). In each of the
major join
operations, we use a visual illustration
followed by an example.
- Inner Join
Inner joins combine records from two tables whenever there are
matching values in a field common to both tables.
include_graphics("img/inner-join.png")
SELECT *
FROM surveys AS A
INNER JOIN species AS B -- by default, JOIN means INNER JOIN
ON A.species_id = B.species_id;
- Left Join
Left Join or Left Outer Join in SQL combines two or more tables,
where the first table is returned wholly; but, only the matching
record(s) are retrieved from the consequent tables. If zero (0) records
are matched in the consequent tables, the join will still return a row
in the result, but with NULL in each column from the right table.
include_graphics("img/left-join.png")
SELECT *
FROM surveys AS A
LEFT JOIN species AS B
ON A.species_id = B.species_id;
- Right Join
The Right Join query in SQL returns all rows from the right table,
even if there are no matches in the left table. In short, a right join
returns all the values from the right table, plus matched values from
the left table or NULL in case of no matching join predicate.
include_graphics("img/right-join.png")
SELECT *
FROM surveys AS A
RIGHT JOIN species AS B
ON A.species_id = B.species_id;
- Full Join
SQL Full Join creates a new table by joining two tables as a whole.
The joined table contains all records from both tables and fills NULL
values for missing matches on either side. In short, full join is a type
of outer join that combines the resulting sets of both left and right
joins.
include_graphics("img/full-join.png")
SELECT *
FROM surveys AS A
FULL JOIN species AS B
ON A.species_id = B.species_id;
SQL Aggregation
Functions
SQL is a good starting point for high-level data analysis. Many data
analysis packages and languages have their own interface to read data
from different SQL-based database systems. In fact, any real-life data
analysis starts from an RDBMS, and the basic analysis and report
generation is done on the SQL platform of that RDBMS itself. A good
preview of data within the RDBMS platform itself helps analysts to get a
fast high-level analysis in a different platform. The most commonly used
aggregation functions include MAX(), MIN(), AVG(),SUM()
and COUNT(). The following are basic examples that
involve some of these aggregation functions.
- Average
SELECT A.species_id,
A.sex,
AVG(A.weight) as mean_wgt
FROM surveys AS A
JOIN species AS B
ON A.species_id = B.species_id
WHERE taxa = 'Rodent' AND A.sex IS NOT NULL
GROUP BY A.species_id, A.sex; -- sorted by two variables
- Sample size
SELECT COUNT(*)
FROM surveys
Subqueries
Subqueries (also known as inner queries or nested queries) are a tool
for performing operations in multiple steps. For example, if you wanted
to take the sums of several columns, and then average all of those
values, you’d need to do each aggregation in a distinct step. Subqueries
can be used in several places within a query.
Subquery in SELECT
Clause
In the following example, we want to define a new attribute, the
relative percentage of taxonomic groups in the data set in the
relational table surveys
. The feature taxa
is
not in the surveys
table. We need to join tables
surveys
and species
to obtain the distribution
of taxa
in the surveys
table. To this end, we
need to know the frequency of each taxonomic group and the size of the
relational table surveys
.
We could use the following queries to find the sample size and the
sizes of each taxon group.
- Total Sample Size
SELECT COUNT(*) FROM surveys
- Calculating Taxon Group Frequencies
The group totals are calculated in the following code.
SELECT B.taxa,
COUNT(*)
FROM surveys AS A -- A is an alias of `surveys` table
INNER JOIN species AS B -- inner join
ON A.species_id = B.species_id
-- this finds the group frequencies
GROUP BY taxa;
This is not efficient. We can use the following nested query to
efficiently find the relative frequency table.
SELECT B.taxa,
100.0*COUNT(*)/(SELECT COUNT(*) FROM surveys) AS Percentage
FROM surveys AS A
JOIN species AS B
ON A.species_id = B.species_id
GROUP BY taxa;
The above query produces the following relative frequency table.
kable(subSQLinSELECT)
Bird |
1.2658584 |
Rabbit |
0.2109764 |
Reptile |
0.0393823 |
Rodent |
96.3374497 |
Subquery in FROM
Clause
As an example, we create a subset with features
record_id, year, plot_id, species_id, sex, hindfoot_length, weight
with the condition that species_id = "DM"
and
sex = "M"
. The following query that contains a sub-query
can do the trick.
SELECT sub_survey.record_id,
sub_survey.year,
sub_survey.plot_id,
sub_survey.species_id,
sub_survey.sex,
sub_survey.hindfoot_length,
sub_survey.weight
FROM (
SELECT *
FROM surveys
WHERE species_id = 'DM'
) sub_survey -- the name of a relational table defined by the subquery
WHERE sub_survey.sex = 'M'
The above subset can also be generated using the following simple
query.
SELECT record_id,
year,
plot_id,
species_id,
sex,
hindfoot_length,
weight
FROM surveys
WHERE species_id = 'DM' AND sex = 'M';
Subquery in WHERE
Clause
This example shows the way to create a subset of surveys
record_id, plot_id, species_id, year, sex, hindfoot_length, weight
for the earliest year.
SELECT record_id,
plot_id,
species_id,
year,
sex,
hindfoot_length,
weight
FROM surveys
WHERE year = (SELECT MIN(year)
FROM surveys
)
Subquery in JOIN
Clause
In the following example, we use the subquery to find the frequency
count in each species group from the surveys
table and add
the frequency to the species
table.
SELECT *
FROM species
JOIN ( SELECT COUNT(species_id) AS speciesfreq,
species_id
FROM surveys
GROUP BY species_id
) subq
ON species.species_id = subq.species_id
ORDER BY subq.species_id DESC
The resulting table is shown in the following.
kable(subSQLinJOIN)
ZL |
Zonotrichia |
leucophrys |
Bird |
2 |
ZL |
US |
Sparrow |
sp. |
Bird |
4 |
US |
UR |
Rodent |
sp. |
Rodent |
10 |
UR |
UP |
Pipilo |
sp. |
Bird |
8 |
UP |
UL |
Lizard |
sp. |
Reptile |
4 |
UL |
SU |
Sceloporus |
undulatus |
Reptile |
5 |
SU |
ST |
Spermophilus |
tereticaudus |
Rodent |
1 |
ST |
SS |
Spermophilus |
spilosoma |
Rodent |
248 |
SS |
SO |
Sigmodon |
ochrognathus |
Rodent |
43 |
SO |
SH |
Sigmodon |
hispidus |
Rodent |
147 |
SH |
SF |
Sigmodon |
fulviventer |
Rodent |
43 |
SF |
SC |
Sceloporus |
clarki |
Reptile |
1 |
SC |
SA |
Sylvilagus |
audubonii |
Rabbit |
75 |
SA |
RX |
Reithrodontomys |
sp. |
Rodent |
2 |
RX |
RO |
Reithrodontomys |
montanus |
Rodent |
8 |
RO |
RM |
Reithrodontomys |
megalotis |
Rodent |
2609 |
RM |
RF |
Reithrodontomys |
fulvescens |
Rodent |
75 |
RF |
PX |
Chaetodipus |
sp. |
Rodent |
6 |
PX |
PU |
Pipilo |
fuscus |
Bird |
5 |
PU |
PP |
Chaetodipus |
penicillatus |
Rodent |
3123 |
PP |
PM |
Peromyscus |
maniculatus |
Rodent |
899 |
PM |
PL |
Peromyscus |
leucopus |
Rodent |
36 |
PL |
PI |
Chaetodipus |
intermedius |
Rodent |
9 |
PI |
PH |
Perognathus |
hispidus |
Rodent |
32 |
PH |
PG |
Pooecetes |
gramineus |
Bird |
8 |
PG |
PF |
Perognathus |
flavus |
Rodent |
1597 |
PF |
PE |
Peromyscus |
eremicus |
Rodent |
1299 |
PE |
PC |
Pipilo |
chlorurus |
Bird |
39 |
PC |
PB |
Chaetodipus |
baileyi |
Rodent |
2891 |
PB |
OX |
Onychomys |
sp. |
Rodent |
12 |
OX |
OT |
Onychomys |
torridus |
Rodent |
2249 |
OT |
OL |
Onychomys |
leucogaster |
Rodent |
1006 |
OL |
NL |
Neotoma |
albigula |
Rodent |
1252 |
NL |
DX |
Dipodomys |
sp. |
Rodent |
40 |
DX |
DS |
Dipodomys |
spectabilis |
Rodent |
2504 |
DS |
DO |
Dipodomys |
ordii |
Rodent |
3027 |
DO |
DM |
Dipodomys |
merriami |
Rodent |
10596 |
DM |
CV |
Crotalus |
viridis |
Reptile |
1 |
CV |
CU |
Cnemidophorus |
uniparens |
Reptile |
1 |
CU |
CT |
Cnemidophorus |
tigris |
Reptile |
1 |
CT |
CS |
Crotalus |
scutalatus |
Reptile |
1 |
CS |
CQ |
Callipepla |
squamata |
Bird |
16 |
CQ |
CM |
Calamospiza |
melanocorys |
Bird |
13 |
CM |
CB |
Campylorhynchus |
brunneicapillus |
Bird |
50 |
CB |
BA |
Baiomys |
taylori |
Rodent |
46 |
BA |
AS |
Ammodramus |
savannarum |
Bird |
2 |
AS |
AH |
Ammospermophilus |
harrisi |
Rodent |
437 |
AH |
AB |
Amphispiza |
bilineata |
Bird |
303 |
AB |
LS0tDQp0aXRsZTogIlJ1bm5pbmcgU1FMIGluIFIiDQphdXRob3I6ICJDaGVuZyBQZW5nIg0KZGF0ZTogIiINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdG9jX2NvbGxhcHNlZDogeWVzDQogICAgY29kZV9mb2xkaW5nOiBzaG93DQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgc21vb3RoX3Njcm9sbDogeWVzDQogICAgdGhlbWU6IGx1bWVuDQogIHdvcmRfZG9jdW1lbnQ6IA0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogICAgZmlnX2NhcHRpb246IHllcw0KICAgIGtlZXBfbWQ6IHllcw0KICBwZGZfZG9jdW1lbnQ6IA0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogICAgZmlnX2NhcHRpb246IHllcw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgZmlnX3dpZHRoOiAzDQogICAgZmlnX2hlaWdodDogMw0KZWRpdG9yX29wdGlvbnM6IA0KICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lDQotLS0NCg0KYGBgez1odG1sfQ0KDQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KDQovKiBDYXNjYWRpbmcgU3R5bGUgU2hlZXRzIChDU1MpIGlzIGEgc3R5bGVzaGVldCBsYW5ndWFnZSB1c2VkIHRvIGRlc2NyaWJlIHRoZSBwcmVzZW50YXRpb24gb2YgYSBkb2N1bWVudCB3cml0dGVuIGluIEhUTUwgb3IgWE1MLiBpdCBpcyBhIHNpbXBsZSBtZWNoYW5pc20gZm9yIGFkZGluZyBzdHlsZSAoZS5nLiwgZm9udHMsIGNvbG9ycywgc3BhY2luZykgdG8gV2ViIGRvY3VtZW50cy4gKi8NCg0KaDEudGl0bGUgeyAgLyogVGl0bGUgLSBmb250IHNwZWNpZmljYXRpb25zIG9mIHRoZSByZXBvcnQgdGl0bGUgKi8NCiAgZm9udC1zaXplOiAyNHB4Ow0KICBmb250LXdlaWdodDogYm9sZDsNCiAgY29sb3I6IERhcmtSZWQ7DQogIHRleHQtYWxpZ246IGNlbnRlcjsNCiAgZm9udC1mYW1pbHk6ICJHaWxsIFNhbnMiLCBzYW5zLXNlcmlmOw0KfQ0KaDQuYXV0aG9yIHsgLyogSGVhZGVyIDQgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciBhdXRob3JzICAqLw0KICBmb250LXNpemU6IDIwcHg7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KICBmb250LWZhbWlseTogc3lzdGVtLXVpOw0KICBjb2xvcjogRGFya1JlZDsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KaDQuZGF0ZSB7IC8qIEhlYWRlciA0IC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgdGhlIGRhdGUgICovDQogIGZvbnQtc2l6ZTogMThweDsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogIGZvbnQtZmFtaWx5OiBzeXN0ZW0tdWk7DQogIGNvbG9yOiBEYXJrQmx1ZTsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KaDEgeyAvKiBIZWFkZXIgMSAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgZm9yIGxldmVsIDEgc2VjdGlvbiB0aXRsZSAgKi8NCiAgICBmb250LXNpemU6IDIwcHg7DQogICAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KaDIgeyAvKiBIZWFkZXIgMiAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgZm9yIGxldmVsIDIgc2VjdGlvbiB0aXRsZSAqLw0KICAgIGZvbnQtc2l6ZTogMThweDsNCiAgICBmb250LXdlaWdodDogYm9sZDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQpoMyB7IC8qIEhlYWRlciAzIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBvZiBsZXZlbCAzIHNlY3Rpb24gdGl0bGUgICovDQogICAgZm9udC1zaXplOiAxNnB4Ow0KICAgIGZvbnQtd2VpZ2h0OiBib2xkOw0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBuYXZ5Ow0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQoNCmg0IHsgLyogSGVhZGVyIDQgLSBmb250IHNwZWNpZmljYXRpb25zIG9mIGxldmVsIDQgc2VjdGlvbiB0aXRsZSAgKi8NCiAgICBmb250LXNpemU6IDE0cHg7DQogICAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IGRhcmtyZWQ7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCg0KYm9keSB7IGJhY2tncm91bmQtY29sb3I6d2hpdGU7IH0NCg0KLmhpZ2hsaWdodG1lIHsgYmFja2dyb3VuZC1jb2xvcjp5ZWxsb3c7IH0NCg0KcCB7IGJhY2tncm91bmQtY29sb3I6d2hpdGU7IH0NCg0KPC9zdHlsZT4NCmBgYA0KDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0KIyBEZXRlY3QsIGluc3RhbGwgYW5kIGxvYWQgcGFja2FnZXMgaWYgbmVlZGVkLg0KaWYgKCFyZXF1aXJlKCJrbml0ciIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJrbml0ciIpDQogICBsaWJyYXJ5KGtuaXRyKQ0KfQ0KaWYgKCFyZXF1aXJlKCJvZGJjIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoIm9kYmMiKQ0KICAgbGlicmFyeShvZGJjKQ0KfQ0KaWYgKCFyZXF1aXJlKCJEQkkiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygiREJJIikNCiAgIGxpYnJhcnkoREJJKQ0KfQ0KaWYgKCFyZXF1aXJlKCJSU1FMaXRlIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoIlJTUUxpdGUiKQ0KICAgbGlicmFyeShSU1FMaXRlKQ0KfQ0KDQppZiAoIXJlcXVpcmUoInRpZHl2ZXJzZSIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKQ0KICAgbGlicmFyeSh0aWR5dmVyc2UpDQp9DQojDQojIHNwZWNpZmljYXRpb25zIG9mIG91dHB1dHMgb2YgY29kZSBpbiBjb2RlIGNodW5rcw0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCAgICAgICMgaW5jbHVkZSBjb2RlIGNodW5rIGluIHRoZSBvdXRwdXQgZmlsZQ0KICAgICAgICAgICAgICAgICAgICAgIHdhcm5pbmdzID0gRkFMU0UsICAjIHNvbWV0aW1lcywgeW91IGNvZGUgbWF5IHByb2R1Y2Ugd2FybmluZyBtZXNzYWdlcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyB5b3UgY2FuIGNob29zZSB0byBpbmNsdWRlIHRoZSB3YXJuaW5nIG1lc3NhZ2VzIGluDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgdGhlIG91dHB1dCBmaWxlLiANCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlcyA9IEZBTFNFLCAgIw0KICAgICAgICAgICAgICAgICAgICAgIHJlc3VsdHMgPSBUUlVFLCAgICAgIyB5b3UgY2FuIGFsc28gZGVjaWRlIHdoZXRoZXIgdG8gaW5jbHVkZSB0aGUgb3V0cHV0DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgaW4gdGhlIG91dHB1dCBmaWxlLg0KICAgICAgICAgICAgICAgICAgICAgIGNvbW1lbnQgPSBOQQ0KICAgICAgICAgICAgICAgICAgICAgICkgICANCg0KI2RiIDwtIGRiQ29ubmVjdChSU1FMaXRlOjpTUUxpdGUoKSwgZGJuYW1lID0gInNxbC5zcWxpdGUiKQ0KI2tuaXRyOjpvcHRzX2NodW5rJHNldChjb25uZWN0aW9uID0gImRiIikNCg0KYGBgDQoNCiMgSW50cm9kdWNpb24NCg0KDQpUbyBydW4gU1FMIGNsYXVzZXMgaW4gUiwgd2UgbmVlZCB0byB1c2Ugc2V2ZXJhbCBSIGxpYnJhcmllcyAoaW5zdGFsbGVkIGFuZCBsb2FkZWQgaW4gdGhlIGFib3ZlIFIgc2V0dXAgY29kZSBjaHVuaykuIFRoZXJlIGFyZSBkaWZmZXJlbnQgd2F5cyB0byBydW4gU1FMIHF1ZXJpZXMgaW4gUi4gV2Ugb25seSBpbnRyb2R1Y2Ugb25lIG9mIHRoZW0gdGhhdCBydW5zIHRoZSBiYXNpYyBTUUwgY29kZS4gIA0KDQojIyBDb25uZWN0IFIgdG8gRXhpc3RpbmcgRGF0YWJhc2UNCg0KSWYgdGhlcmUgaXMgYW4gZXhpc3RpbmcgZGF0YWJhc2UsIHRoZSBmb2xsb3dpbmcgY29kZSBjb25uZWN0cyBSIHRvIHRoZSBkYXRhYmFzZS4NCg0KYGBge30NCmNvbiA8LSBEQkk6OmRiQ29ubmVjdChkcnYgPSBvZGJjOjpvZGJjKCksDQogICAgICAgICAgICAgICAgICAgICAgRHJpdmVyID0gImRyaXZlcl9uYW1lIiwNCiAgICAgICAgICAgICAgICAgICAgICBTZXJ2ZXIgPSAic2VydmVyX3VybCIsDQogICAgICAgICAgICAgICAgICAgICAgRGF0YWJhc2UgPSAiZGF0YWJhc2VfbmFtZSIsDQogICAgICAgICAgICAgICAgICAgICAgdXNlciA9ICJ1c2VyIiwgI29wdGlvbmFsDQogICAgICAgICAgICAgICAgICAgICAgcGFzc3dvcmQgPSAicGFzc3dvcmQiKSAjb3B0aW9uYWwNCmBgYA0KDQojIyBDcmVhdGUgQSBEYXRhYmFzZSB0byBSdW4gU1FMIFF1ZXJpZXMgaW4gUg0KDQoNClRoaXMgc2hvcnQgbm90ZSBzaG93cyB0aGUgdGhyZWUgYmFzaWMgc3RlcHMgdG8gcnVuIFNRTCBpbiBSIHVzaW5nIFIgTWFya2Rvd24gc3RhcnRpbmcgd2l0aCBhIHNldCBvZiByZWxhdGlvbmFsIHRhYmxlcy4NCg0KMS4gTG9hZCByZWxhdGlvbmFsIGRhdGEgdGFibGVzIGFzIHVzdWFsIHRvIFIuDQoNCjIuIENyZWF0ZSBhIFNRTGl0ZSAocmVsYXRpb25hbCkgZGF0YWJhc2UgdGhhdCBjb250YWlucyB0aGVzZSByZWxhdGlvbmFsIHRhYmxlcy4NCg0KMy4gQ3JlYXRlIGFuIFIgY29kZSBjaHVuayBhbmQgY29ubmVjdCB0byB0aGUgY3JlYXRlZCBkYXRhYmFzZSB1c2luZyBDaHVuayBvcHRpb25zLg0KDQoNCg0KIyBDcmVhdGUgU1FMaXRlIERhdGFiYXNlIHdpdGggUg0KDQpJZiBtb2RlbGluZyByZXF1aXJlcyBhIGRhdGEgc2V0IHRoYXQgY29udGFpbnMgaW5mb3JtYXRpb24gZnJvbSBtdWx0aXBsZSByZWxhdGlvbmFsIGRhdGEgdGFibGVzLCB3ZSBuZWVkIHRvIHBlcmZvcm0gZGF0YSBtYW5hZ2VtZW50IHRvIGFnZ3JlZ2F0ZSB0aGUgcmVxdWlyZWQgaW5mb3JtYXRpb24gZnJvbSBkaWZmZXJlbnQgZGF0YSB0YWJsZXMuIFdlIGNhbiBsb2FkIHRoZSBkaWZmZXJlbnQgZGF0YSBzZXRzIGluIGRpZmZlcmVudCBmb3JtYXRzIHVzaW5nIGFwcHJvcHJpYXRlIFIgZnVuY3Rpb25zLiANCg0KQXMgYW4gZXhhbXBsZSwgV2UgdXNlIHRocmVlIGVjb2xvZ2ljYWwgc3VydmV5IGRhdGEgc2V0cyB0byBjcmVhdGUgYSBkYXRhYmFzZS4NCg0KYGBge3J9DQojTG9hZCB0aGUgc2FtcGxlIGRhdGENCnBsb3RzIDwtIHJlYWQuY3N2KCJodHRwczovL3Blbmdkc2NpLmdpdGh1Yi5pby9kYXRhc2V0cy9BbmltYWxTdXJ2ZXkvcGxvdHMuY3N2IikNCnNwZWNpZXMgPC0gcmVhZC5jc3YoImh0dHBzOi8vcGVuZ2RzY2kuZ2l0aHViLmlvL2RhdGFzZXRzL0FuaW1hbFN1cnZleS9zcGVjaWVzLmNzdiIpDQpzdXJ2ZXlzIDwtIHJlYWQuY3N2KCJodHRwczovL3Blbmdkc2NpLmdpdGh1Yi5pby9kYXRhc2V0cy9BbmltYWxTdXJ2ZXkvc3VydmV5cy5jc3YiKQ0KYGBgDQoNCldlIG5leHQgZXhwbG9yZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIHRhYmxlcy4NCg0KYGBge3J9DQpzdW1tYXJ5KHBsb3RzKQ0Kc3VtbWFyeShzcGVjaWVzKQ0Kc3VtbWFyeShzdXJ2ZXlzKQ0KYGBgDQoNClRoZSByZWxhdGlvbmFsIHRhYmxlIGBzdXJ2ZXlzYCBoYXMgYXR0cmlidXRlcyAqKnNwZWNpZXNfaWQqKiBhbmQgKipwbG90X2lkKiogdGhhdCBjb25uZWN0IHJlbGF0aW9uYWwgdGFibGVzIGBwbG90c2AgYW5kIGBzcGVjaWVzYC4gVGhlIHByaW1hcnkgYW5kIGZvcmVpZ24ga2V5cyBhcmUgZGVwaWN0ZWQgaW4gdGhlIGZvbGxvd2luZyBmaWd1cmUuDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgb3V0LndpZHRoPSI5OSUiLCBmaWcuY2FwPSJUaGUgcHJpbWFyeSBhbmQgZm9yZWlnbiBrZXlzIG9mIHRoZSB0aHJlZSByZWxhdGlvbmFsIHRhYmxlcy4ifQ0KaW5jbHVkZV9ncmFwaGljcygiaW1nL1JlbGF0aW9uc2hpcEJldHdlZW5UYWJsZXMucG5nIikNCmBgYA0KDQpcDQoNCg0KTmV4dCwgd2UgY3JlYXRlIGEgU1FMaXQgZGF0YWJhc2UgdXNpbmcgUiBsaWJyYXJpZXMgYW5kIHRoZW4gcmVtb3ZlIHRoZSBkYXRhIGZyYW1lcyBmcm9tIHRoZSB3b3JraW5nIGRpcmVjdG9yeS4NCg0KYGBge3J9DQojQ3JlYXRlIGRhdGFiYXNlDQpjb24gPC0gZGJDb25uZWN0KGRydiA9IFNRTGl0ZSgpLA0KICAgICAgICAgICAgICAgICBkYm5hbWUgPSAiOm1lbW9yeToiKQ0KDQojc3RvcmUgc2FtcGxlIGRhdGEgaW4gdGhlIGRhdGFiYXNlDQpkYldyaXRlVGFibGUoY29ubiA9IGNvbiwgDQogICAgICAgICAgICAgbmFtZSA9ICJwbG90cyIsDQogICAgICAgICAgICAgdmFsdWUgPSBwbG90cykNCg0KZGJXcml0ZVRhYmxlKGNvbm4gPSBjb24sIA0KICAgICAgICAgICAgIG5hbWUgPSAic3BlY2llcyIsDQogICAgICAgICAgICAgdmFsdWUgPSBzcGVjaWVzKQ0KDQpkYldyaXRlVGFibGUoY29ubiA9IGNvbiwgDQogICAgICAgICAgICAgbmFtZSA9ICJzdXJ2ZXlzIiwNCiAgICAgICAgICAgICB2YWx1ZSA9IHN1cnZleXMpDQogDQojcmVtb3ZlIHRoZSBsb2NhbCBkYXRhIGZyb20gdGhlIGVudmlyb25tZW50DQpybShwbG90cywgc3BlY2llcywgc3VydmV5cykNCmBgYA0KDQpXZSBjYW4gdXNlIHRoZSB0YWJsZSB2aWV3IGZ1bmN0aW9uIGB0YmwoKWAgdG8gdmlldyB0aGUgcmVsYXRpb25hbCBkYXRhIHRhYmxlcyBpbiB0aGUgZGF0YWJhc2UuICANCg0KYGBge3J9DQp0Ymwoc3JjID0gY29uLCAgICAgICAgICMgIHRoZSBzb3VyY2UgaWYgdGhlIGRhdGFiYXNlIGNvbm5lY3Rpb24gcHJvZmlsZQ0KICAgIGMoInN1cnZleXMiKSkgICAgICAjICB0aGUgbmFtZSBvZiB0aGUgdGFibGUgdG8gcHJldmlldw0KdGJsKHNyYyA9IGNvbiwgInNwZWNpZXMiKQ0KdGJsKHNyYyA9IGNvbiwgInBsb3RzIikNCmBgYA0KDQojIFJ1bm5pbmcgU1FMIFF1ZXJpZXMgaW4gUiANCg0KVG8gdXNlIFNRTCBpbiBSTWFya2Rvd24sIHdlIG5lZWQgdGhlIGZvbGxvd2luZyBjaHVuayBvcHRpb25zOg0KDQoqIHNxbA0KKiBjb25uZWN0aW9uID0gImRhdGFiYXNlLW5hbWUiDQoqIG91dHB1dC52YXIgPSAib3V0cHV0LWRhdGFzZXQtbmFtZSINCg0KSWYgd2UgY3JlYXRlIGEgZGF0YSB2aWV3IG9ubHksIHdlIHNpbXBseSBpZ25vcmUgb3B0aW9uIGBvdXRwdXQudmFyID0gYA0KDQpGb2xsb3dpbmcgYXJlIGZldyBleGFtcGxlcyBvZiBTUUwgcXVlcmllcyBiYXNlZCBvbiB0aGUgYW5pbWFsIHN1cnZleSBkYXRhIHRhYmxlcyBpbiB0aGUgZGF0YWJhc2UuDQoNCiMjIEJhc2ljIFNRTCBPcGVyYXRvcnMNCg0KV2hpbGUgd29ya2luZyB3aXRoIGRhdGFiYXNlcywgd2UgdXNlIFNRTCBxdWVyaWVzIHRvIG1hbmlwdWxhdGUgdGhlIGRhdGEgYW5kIHJldHJpZXZlIHRoZSBkZXNpcmVkIHJlc3VsdC4gVGhpcyBtYW5pcHVsYXRpb24gb2YgZGF0YSBpcyBhY2hpZXZlZCB0aHJvdWdoIHZhcmlvdXMgKipTUUwgb3BlcmF0b3JzKiouIEFuIG9wZXJhdG9yIGlzIGEga2V5d29yZCBpbiBTUUwgdGhhdCBoZWxwcyB1cyBhY2Nlc3MgdGhlIGRhdGEgYW5kIHJldHVybnMgdGhlIHJlc3VsdCBiYXNlZCBvbiB0aGUgb3BlcmF0b3LigJlzIGZ1bmN0aW9uYWxpdHkuIFNRTCBwcm92aWRlcyB1cyB3aXRoIG1hbnkgc3VjaCBvcGVyYXRvcnMgdG8gZWFzZSB0aGUgcHJvY2VzcyBvZiBkYXRhIG1hbmlwdWxhdGlvbi4gSW4gdGhpcyBzdWJzZWN0aW9uLCB3ZSB3aWxsIGxvb2sgYXQgdGhyZWUgbWFqb3IgdHlwZXMgb2Ygb3BlcmF0b3JzIGluIFNRTC4NCg0KKipBcml0aG1ldGljIE9wZXJhdG9ycyoqDQoNCkFyaXRobWV0aWMgb3BlcmF0b3JzIGFyZSB1c2VkIHRvIHBlcmZvcm0gYXJpdGhtZXRpYyBvcGVyYXRpb25zIHN1Y2ggYXMgYWRkaXRpb24sIHN1YnRyYWN0aW9uLCBkaXZpc2lvbiwgYW5kIG11bHRpcGxpY2F0aW9uLiBUaGVzZSBvcGVyYXRvcnMgdXN1YWxseSBhY2NlcHQgbnVtZXJpYyBvcGVyYW5kcy4gDQoNCg0KfCBPcGVyYXRvciAgfCBPcGVyYXRpb24gfCBEZXNjcmlwdGlvbiB8DQp8Oi0tLS0tLS0tLS18Oi0tLS0tLS0tLS18Oi0tLS0tLS0tLS0tLXwNCnwgKyB8IEFkZGl0aW9uIHwgQWRkcyBvcGVyYW5kcyBvbiBlaXRoZXIgc2lkZSBvZiB0aGUgb3BlcmF0b3IgfA0KfCAtIHwgU3VidHJhY3Rpb24gfCBTdWJ0cmFjdHMgdGhlIHJpZ2h0LWhhbmQgb3BlcmFuZCBmcm9tIHRoZSBsZWZ0LWhhbmQgb3BlcmFuZHwNCnwgKiB8TXVsdGlwbGljYXRpb24gfCBNdWx0aXBsaWVzIHRoZSB2YWx1ZXMgb24gZWFjaCBzaWRlfA0KfCAvIHxEaXZpc2lvbiB8IERpdmlkZXMgbGVmdC1oYW5kIG9wZXJhbmQgYnkgcmlnaHQtaGFuZCBvcGVyYW5kIHwNCnwgJSB8IE1vZHVsdXMgfERpdmlkZXMgbGVmdC1oYW5kIG9wZXJhbmQgYnkgcmlnaHQtaGFuZCBvcGVyYW5kIGFuZCByZXR1cm5zIHRoZSByZW1haW5kZXJ8DQoNClwNCg0KKipDb21wYXJpc29uIE9wZXJhdG9ycyoqDQoNCkNvbXBhcmlzb24gb3BlcmF0b3JzIGluIFNRTCBhcmUgdXNlZCB0byBjaGVjayB0aGUgZXF1YWxpdHkgb2YgdHdvIGV4cHJlc3Npb25zLiBJdCBjaGVja3Mgd2hldGhlciBvbmUgZXhwcmVzc2lvbiBpcyBpZGVudGljYWwgdG8gYW5vdGhlci4gQ29tcGFyaXNvbiBvcGVyYXRvcnMgYXJlIGdlbmVyYWxseSB1c2VkIGluIHRoZSAqKldIRVJFIGNsYXVzZSoqIG9mIGEgU1FMIHF1ZXJ5LiBUaGUgcmVzdWx0IG9mIGEgY29tcGFyaXNvbiBvcGVyYXRpb24gbWF5IGJlIFRSVUUsIEZBTFNFLCBvciBVTktOT1dOLiBXaGVuIG9uZSBvciBib3RoIHRoZSBleHByZXNzaW9uIGlzIE5VTEwsIHRoZW4gdGhlIG9wZXJhdG9yIHJldHVybnMgVU5LTk9XTi4gDQoNCg0KDQp8IE9wZXJhdG9yICB8IE9wZXJhdGlvbiB8IERlc2NyaXB0aW9uIHwNCnw6LS0tLS0tLS0tLXw6LS0tLS0tLS0tLXw6LS0tLS0tLS0tLS0tfA0KfCA9IHxFcXVhbCB0byB8Q2hlY2tzIGlmIGJvdGggb3BlcmFuZHMgaGF2ZSBlcXVhbCB2YWx1ZSwgaWYgeWVzLCB0aGVuIHJldHVybnMgVFJVRXwNCnwgPiB8R3JlYXRlciB0aGFuIHxDaGVja3MgaWYgdGhlIHZhbHVlIG9mIHRoZSBsZWZ0LWhhbmQgb3BlcmFuZCBpcyBncmVhdGVyIHRoYW4gdGhlIHJpZ2h0LWhhbmQgb3BlcmFuZCBvciBub3R8DQp8IDwgfExlc3MgdGhhbiB8UmV0dXJucyBUUlVFIGlmIHRoZSB2YWx1ZSBvZiB0aGUgbGVmdC1oYW5kIG9wZXJhbmQgaXMgbGVzcyB0aGFuIHRoZSB2YWx1ZSBvZiB0aGUgcmlnaHQtaGFuZCBvcGVyYW5kfA0KfD49IHxHcmVhdGVyIHRoYW4gb3IgZXF1YWwgdG8gfCBJdCBjaGVja3MgaWYgdGhlIHZhbHVlIG9mIHRoZSBsZWZ0LWhhbmQgb3BlcmFuZCBpcyBncmVhdGVyIHRoYW4gb3IgZXF1YWwgdG8gdGhlIHZhbHVlIG9mIHRoZSByaWdodC1oYW5kIG9wZXJhbmQsIGlmIHllcywgdGhlbiByZXR1cm5zIFRSVUUgfA0KfDw9IHxMZXNzIHRoYW4gb3IgZXF1YWwgdG8gfEV4YW1pbmVzIGlmIHRoZSB2YWx1ZSBvZiB0aGUgbGVmdC1oYW5kIG9wZXJhdG9yIGlzIGxlc3MgdGhhbiBvciBlcXVhbCB0byB0aGUgcmlnaHQtaGFuZCBvcGVyYW5kIHwNCnw8PiBvciAhPSB8Tm90IGVxdWFsIHRvIHxDaGVja3MgaWYgdmFsdWVzIG9uIGVpdGhlciBzaWRlIG9mIHRoZSBvcGVyYXRvciBhcmUgZXF1YWwgb3Igbm90LiBSZXR1cm5zIFRSVUUgaWYgdmFsdWVzIGFyZSBub3QgZXF1YWx8DQoNClwNCg0KKipMb2dpY2FsIE9wZXJhdG9ycyoqDQoNCkxvZ2ljYWwgb3BlcmF0b3JzIGFyZSB0aG9zZSBvcGVyYXRvcnMgdGhhdCB0YWtlIHR3byBleHByZXNzaW9ucyBhcyBvcGVyYW5kcyBhbmQgcmV0dXJuIFRSVUUgb3IgRmFsc2UgYXMgb3V0cHV0LiANCg0KDQp8IE9wZXJhdG9yICB8IERlc2NyaXB0aW9uIHwNCnw6LS0tLS0tLS0tLXw6LS0tLS0tLS0tLS0tfA0KfEFMTHxDb21wYXJlcyBhIHZhbHVlIHRvIGFsbCBvdGhlciB2YWx1ZXMgaW4gYSBzZXR8DQp8QU5EIHxSZXR1cm5zIHRoZSByZWNvcmRzIGlmIGFsbCB0aGUgY29uZGl0aW9ucyBzZXBhcmF0ZWQgYnkgQU5EIGFyZSBUUlVFIHwNCnxBTlkgfENvbXBhcmVzIGEgc3BlY2lmaWMgdmFsdWUgdG8gYW55IG90aGVyIHZhbHVlcyBpbiBhIHNldCB8DQp8IFNPTUUgfENvbXBhcmVzIGEgdmFsdWUgdG8gZWFjaCB2YWx1ZSBpbiBhIHNldC4gSXQgaXMgc2ltaWxhciB0byBBTlkgb3BlcmF0b3IgfA0KfExJS0UgfCBJdCByZXR1cm5zIHRoZSByb3dzIGZvciB3aGljaCB0aGUgb3BlcmFuZCBtYXRjaGVzIGEgc3BlY2lmaWMgcGF0dGVybiB8DQp8SU4gfFVzZWQgdG8gY29tcGFyZSBhIHZhbHVlIHRvIGEgc3BlY2lmaWVkIHZhbHVlIGluIGEgbGlzdCB8DQp8QkVUV0VFTiB8IFJldHVybnMgdGhlIHJvd3MgZm9yIHdoaWNoIHRoZSB2YWx1ZSBsaWVzIGJldHdlZW4gdGhlIG1lbnRpb25lZCByYW5nZXwNCnxOT1QgfCBVc2VkIHRvIHJldmVyc2UgdGhlIG91dHB1dCBvZiBhbnkgbG9naWNhbCBvcGVyYXRvciB8DQp8RVhJU1RTIHxVc2VkIHRvIHNlYXJjaCBhIHJvdyBpbiBhIHNwZWNpZmllZCB0YWJsZSBpbiB0aGUgZGF0YWJhc2UgfA0KfCBPUiB8IFJldHVybnMgdGhlIHJlY29yZHMgZm9yIHdoaWNoIGFueSBvZiB0aGUgY29uZGl0aW9ucyBzZXBhcmF0ZWQgYnkgT1IgaXMgdHJ1ZSB8DQp8TlVMTCB8IFJldHVybnMgdGhlIHJvd3Mgd2hlcmUgdGhlIG9wZXJhbmQgaXMgTlVMTCB8DQoNCg0KVGhlIGxvZ2ljYWwgYW5kIGNvbXBhcmlzb24gb3BlcmF0b3JzIGFyZSB1c3VhbGx5IHVzZWQgd2l0aCBjb25kaXRpb25hbCBxdWVyaWVzIGluIHdoaWNoIHdlIGNhbiBzcGVjaWZ5IGEgY29uZGl0aW9uYWwgZXhwcmVzc2lvbiBpbiBhICoqU0VMRUNUIHN0YXRlbWVudCBXSEVSRSBjbGF1c2UqKiB3aGljaCBzcGVjaWZpZXMgdGhhdCBvbmx5IHRob3NlIHJvd3MgZm9yIHdoaWNoIHRoZSBjb25kaXRpb25hbCBleHByZXNzaW9uIGlzIHRydWUgYXJlIHRvIGJlIHJldHJpZXZlZC4gVGhlIHN5bnRheCBmb3IgdGhlICoqU0VMRUNUIHN0YXRlbWVudCoqIGNvbnRhaW5pbmcgdGhlICoqV0hFUkUgY2xhdXNlKiogaXMgYXMgZm9sbG93cy4NCg0KYGBgDQpzZWxlY3Rfc3RhdGVtZW50Og0KCQlTRUxFQ1Qg4oCmIEZST00gdGFibGVfbmFtZQ0KIAkJCVdIRVJFIGNvbmRpdGlvbmFsX2V4cHJlc3Npb24NCmBgYA0KDQpUaGUgZm9sbG93aW5nIGlzIGEgc2ltcGxlIGV4YW1wbGUuIFdlIHdhbnQgdG8gY3JlYXRlIGEgc3Vic2V0IHN1Y2ggdGhhdCBgc3BlY2llc19pZGAgZXF1YWxzICJETSIgYW5kIGB3ZWlnaHQgPiAwYC4NCg0KYGBge3NxbCwgY29ubmVjdGlvbiA9ICJjb24iLCBvdXRwdXQudmFyID0gIkNvbmRpdGlvbmFsUXVlcnkifSANClNFTEVDVCAqDQpGUk9NIHN1cnZleXMgDQpXSEVSRSBzcGVjaWVzX2lkIExJS0UgIkRNIiBBTkQgd2VpZ2h0ID4gMDsNCmBgYA0KDQoNCg0KDQojIyBTdWJzZXR0aW5nIGFuZCBEdXBsaWNhdGluZyBEYXRhIA0KDQoxLiBFeHRyYWN0IHllYXIsIG1vbnRoLCBhbmQgZGF5IGZyb20gYHN1cnZleXNgIHRhYmxlDQoNCmBgYHtzcWwsIGNvbm5lY3Rpb24gPSAiY29uIiwgb3V0cHV0LnZhcj0iWU1EIn0NClNFTEVDVCANCiAgc3VydmV5cy55ZWFyLCBzdXJ2ZXlzLm1vbnRoLCBzdXJ2ZXlzLkRheQ0KRlJPTSANCiBzdXJ2ZXlzICAgLS0gcG9pbnRlciBpcyBub3QgbmVlZGVkIHNpbmNlIGl0IGlzIGluIHRoZSBkYXRhYmFzZSANCldIRVJFDQogIHN1cnZleXMuc3BlY2llc19pZCBJTiAoJ05MJywgJ0RNJykgQU5EDQogIHN1cnZleXMuc2V4ID0gJ00nDQpgYGANCg0KVGhlIGNvZGUgY2h1bmsgZGVmaW5lZCB0aGUgb3V0cHV0IG9mIHRoZSBxdWVyeSBhcyBhbiBSIGRhdGEgZnJhbWUgKCpDYXV0aW9uOiBub3QgYSByZWxhdGlvbmFsIHRhYmxlIHN0b3JlZCBpbiB0aGUgU1FMaXRlIGRhdGFiYXNlKikgd2l0aCB0aGUgbmFtZSAqKllNRCoqLiBXZSBjaGVjayB0aGUgZmlyc3QgZmV3IHJlY29yZHMgZnJvbSB0aGUgZGF0YSBmcmFtZQ0KDQpgYGB7cn0NCmhlYWQoWU1EKQ0KYGBgDQpJZiB3ZSBzaW1wbHkgY3JlYXRlIGEgZGF0YSB2aWV3IHdpdGhvdXQgc2F2aW5nIGl0IGFzIGFuIFIgZGF0YSBmcmFtZSwgd2Ugc2ltcGx5IGlnbm9yZSB0aGUgb3B0aW9uIGBvdXRwdXQudmFyPSJZTUQiYCBpbiB0aGUgKipTUUwgY29kZSBjaHVuayoqLg0KDQoNCmBgYHtzcWwsIGNvbm5lY3Rpb24gPSAiY29uIn0NClNFTEVDVCANCiAgc3VydmV5cy55ZWFyLCBzdXJ2ZXlzLm1vbnRoLCBzdXJ2ZXlzLkRheQ0KRlJPTSANCiBzdXJ2ZXlzICAtLSBwb2ludGVyIGlzIG5vdCBuZWVkZWQgc2luY2UgaXQgaXMgaW4gdGhlIGRhdGFiYXNlIA0KV0hFUkUNCiAgc3VydmV5cy5zcGVjaWVzX2lkIElOICgnTkwnLCAnRE0nKSBBTkQNCiAgc3VydmV5cy5zZXggPSAnTScNCmBgYA0KDQoNCjIuIER1cGxpY2F0ZSBkYXRhIGFuZCByZW5hbWUgaXQNCg0KYGBge3NxbCwgY29ubmVjdGlvbiA9ICJjb24iLCBvdXRwdXQudmFyPSJTdXJ2ZXlDb3B5In0NClNFTEVDVCANCiAgc3VydmV5cy4qDQpGUk9NIA0KIHN1cnZleXMNCmBgYA0KDQpOb3RlIHRoYXQgKipzdXJ2ZXlzKiogaXMgYSByZWxhdGlvbiBkYXRhIHRhYmxlIGluIHRoZSBTUUxpdGUgZGF0YWJhc2UgYW5kIGR1cGxpY2F0ZWQgZGF0YSBpcyBub3QgaW4gdGhlIFNRTGl0ZSBkYXRhYmFzZSBidXQgYW4gUiBkYXRhIGZyYW1lIGluIHRoZSB3b3JraW5nIGRpcmVjdG9yeS4NCg0KKipDYXV0aW9uKio6IGluIHRoZSBTUUwgY29kZSBjaHVuaywgcXVlcnkgc3RhdGVtZW50cyBvbmx5IHdvcmsgd2l0aCBTUUwgZGF0YWJhc2UuIFRoZXkgZG9uJ3Qgd29yayBmb3IgUiBkYXRhIGZyYW1lcyBpbiB0aGUgd29ya2luZyBkaXJlY3RvcnkuIFRoZSBmb2xsb3dpbmcgY29kZSBkb2Vzbid0IHdvcmsgYmVjYXVzZSB0aGUgZGF0YSBzZXQgYFN1cnZleUNvcHlgIGlzIG5vdCBpbiB0aGUgU1FMaXRlIGRhdGFiYXNlLiANCg0KDQpgYGB7IG91dHB1dC52YXI9InN1cnZleS5jb3B5In0NClNFTEVDVCANCiAgU3VydmV5Q29weS4qDQpGUk9NIA0KIFN1cnZleUNvcHkNCmBgYA0KDQpJbiBvdGhlciB3b3JkcywgaWYgd2Ugd2FudCB0byBxdWVyeSB0aGUgZGF0YSB0YWJsZSAoZGF0YSBmcmFtZSkgYFN1cnZleUNvcHlgLCB3ZSBuZWVkIHRvIGFkZCBpdCB0byB0aGUgZGF0YWJhc2UgYGNvbmAgZGVmaW5lZCBlYXJsaWVyLg0KDQpgYGB7cn0NCiMgU3RvcmUgc2FtcGxlIGRhdGEgaW4gdGhlIGRhdGFiYXNlDQpkYldyaXRlVGFibGUoY29ubiA9IGNvbiwgDQogICAgICAgICAgICAgbmFtZSA9ICJTdXJ2ZXlDb3B5IiwNCiAgICAgICAgICAgICB2YWx1ZSA9IFN1cnZleUNvcHkpDQojIyBSZW1vdmUgU3VydmV5Q29weSBpbiB0aGUgd29ya2luZyBkaXJlY3RvcnkNCnJtKFN1cnZleUNvcHkpDQpgYGANCg0KV2UgY2FuIHVzZSBgdGJsKClgIHRvIHZpZXcgdGhlIG5ld2x5IGFkZGVkIHRhYmxlIGluIHRoZSBTUUxpdGUgZGF0YWJhc2UgYGNvbmAuDQoNCmBgYHtyfQ0KdGJsKHNyYyA9IGNvbiwgYygiU3VydmV5Q29weSIpKQ0KYGBgDQoNCkluIFIsIHdlIGNhbiB1c2UgYGRiTGlzdFRhYmxlcygpYCB0byB2aWV3IHJlbGF0aW9uYWwgdGFibGVzIGluIHRoZSBkYXRhYmFzZS4NCg0KYGBge3J9DQpkYkxpc3RUYWJsZXMoY29uKQ0KYGBgDQoNCg0KV2Ugbm93IGNhbiBxdWVyeSB0aGUgcmVsYXRpb25hbCB0YWJsZSBgU3VydmV5Q29weWAgaW4gdGhlIGRhdGFiYXNlIGBjb25gIHVzaW5nIFNRTCBjbGF1c2UgYXMgdXN1YWwuDQoNCg0KMy4gQ3JlYXRlIGEgdGFibGUgdmlldyAoaS5lLiwgbm8gZGF0YSBzZXQgd2lsbCBiZSBjcmVhdGVkIGFuZCBzYXZlZCkNCg0KYGBge3NxbCwgY29ubmVjdGlvbiA9ICJjb24iLCBvdXRwdXQudmFyID0gIllNRCJ9DQpTRUxFQ1QgDQogIHN1cnZleXMueWVhciwgc3VydmV5cy5tb250aCwgc3VydmV5cy5EYXkNCkZST00gDQogc3VydmV5cw0KV0hFUkUNCiAgc3VydmV5cy5zcGVjaWVzX2lkID0gJ05MJyBBTkQNCiAgc3VydmV5cy5zZXggPSAnTScNCmBgYA0KDQoNCg0KIyMgRGVmaW5lIEEgTmV3IFZhcmlhYmxlIA0KDQoxLiBEZWZpbmUgYSBuZXcgdmFyaWFibGUgd2l0aCBzaW1wbGUgYXJpdGhtZXRpYyBvcGVyYXRpb25zDQoNCg0KYGBge3NxbCwgY29ubmVjdGlvbiA9ICJjb24iLCBvdXRwdXQudmFyID0gIkNhbHVsYXRlZFRhYmxlIn0NClNFTEVDVCANCiAgICBzdXJ2ZXlzLnBsb3RfaWQsIA0KICAgIHN1cnZleXMuc3BlY2llc19pZCwgDQogICAgc3VydmV5cy5zZXgsIA0KICAgIHN1cnZleXMud2VpZ2h0LCANCiAgICBzdXJ2ZXlzLndlaWdodC8xMDAgQVMgd3Rfa2lsbyAgLS0gc2hvdWxkIG5vdCB0aGUgcG9pbnRlciBpbiBmcm9udCBvZiANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLS0gdGhlIG5hbWUgb2YgdGhlIG5ldyB2YXJpYWJsZSANCkZST00gDQogICBzdXJ2ZXlzDQpgYGANCg0KDQoyLiBEZWZpbmUgbmV3IHZhcmlhYmxlcyB1c2luZyBzdHJpbmcgZnVuY3Rpb25zIGluIFNRTA0KDQpgYGB7c3FsLCBjb25uZWN0aW9uID0gImNvbiIsIG91dHB1dC52YXIgPSAiTmV3U3RyaW5nVmFyIn0NClNFTEVDVCBzdXJ2ZXlzLiosIA0KICAgICAgIHN1cnZleXMuc3BlY2llc19pZHx8Jy0nfHxzdXJ2ZXlzLnNleCBBUyBuZXdLZXkNCkZST00gc3VydmV5cw0KYGBgDQoNCg0KMy4gRGVmaW5lIG5ldyB2YXJpYWJsZXMgd2l0aCBhZ2dyZWdhdGVkIGluZm9ybWF0aW9uIA0KDQpgYGB7c3FsLCBjb25uZWN0aW9uID0gImNvbiIsIG91dHB1dC52YXIgPSAiTmV3U3VtbWFyeVZhciJ9DQpTRUxFQ1Qgc3VydmV5cy5zcGVjaWVzX2lkLCANCiAgICAgICBDT1VOVChzdXJ2ZXlzLnNwZWNpZXNfaWQpIEFTIHNwZWNpZXNfY3RyDQpGUk9NIHN1cnZleXMNCkdST1VQIEJZIHN1cnZleXMuc3BlY2llc19pZA0KSEFWSU5HIHNwZWNpZXNfY3RyID4gMTANCmBgYA0KDQoNCg0KIyMgU29ydGluZyBWYXJpYWJsZXMNCg0KMS4gU29ydCBkYXRhIGJhc2VkIG9uIHRoZSBzdW1tYXJpemVkIHN0YXRpc3RpY3Mgb2YgYSB2YXJpYWJsZQ0KDQpTdW1tYXJ5IGZ1bmN0aW9ucyBhcmUgcmVzdHJpY3RlZCB0byB0aGUgU0VMRUNUIGFuZCBIQVZJTkcgY2xhdXNlcyBvbmx5Ow0KDQpgYGB7c3FsLCBjb25uZWN0aW9uID0gImNvbiIsIG91dHB1dC52YXIgPSAiU29ydGluZ0RhdGEifQ0KU0VMRUNUIHN1cnZleXMuc3BlY2llc19pZA0KRlJPTSBzdXJ2ZXlzDQpHUk9VUCBCWSBzdXJ2ZXlzLnNwZWNpZXNfaWQNCk9SREVSIEJZIENPVU5UKHN1cnZleXMuc3BlY2llc19pZCk7DQpgYGANCg0KDQoyLiBTb3J0IGRhdGEgYmFzZWQgb24gYSBuZXcgdmFyaWFibGUgZGVmaW5lZCB1c2luZyBzdW1tYXJpemVkIHN0YXRpc3RpY3Mgb2YgYSB2YXJpYWJsZS4NCg0KDQpgYGB7c3FsLCBjb25uZWN0aW9uID0gImNvbiIsIG91dHB1dC52YXIgPSAiU29ydGluZ05ld1ZhciJ9IA0KLyogY3JlYXRlIGEgdGFibGUgdmlldyovDQpTRUxFQ1Qgc3VydmV5cy5zcGVjaWVzX2lkIEFTIHN1YnRvdGFsLCANCiAgICAgICBDT1VOVCgqKSANCkZST00gc3VydmV5cw0KR1JPVVAgQlkgc3VydmV5cy5zcGVjaWVzX2lkDQpPUkRFUiBCWSBzdWJ0b3RhbDsNCmBgYA0KDQoNCiMgU1FMIEpvaW5zDQoNClNRTCBKT0lOIGNsYXVzZSBpcyB1c2VkIHRvIHF1ZXJ5IGFuZCBhY2Nlc3MgZGF0YSBmcm9tIG11bHRpcGxlIHRhYmxlcyBieSBlc3RhYmxpc2hpbmcgbG9naWNhbCByZWxhdGlvbnNoaXBzIGJldHdlZW4gdGhlbS4gSXQgY2FuIGFjY2VzcyBkYXRhIGZyb20gbXVsdGlwbGUgdGFibGVzIHNpbXVsdGFuZW91c2x5IHVzaW5nIGNvbW1vbiBrZXkgdmFsdWVzIHNoYXJlZCBhY3Jvc3MgZGlmZmVyZW50IHRhYmxlcy4gVGhpcyBzZWN0aW9uIGJyaWVmbHkgaW50cm9kdWNlcyBjb21tb25seSB1c2VkIGpvaW4gb3BlcmF0aW9ucyB0byBtZXJnZSB0YWJsZXMgdXNpbmcgdGhlIGNvbW1vbiBrZXkocykuIEluIGVhY2ggb2YgdGhlIG1ham9yIGBqb2luYCBvcGVyYXRpb25zLCB3ZSB1c2UgYSB2aXN1YWwgaWxsdXN0cmF0aW9uIGZvbGxvd2VkIGJ5IGFuIGV4YW1wbGUuDQoNCg0KMS4gSW5uZXIgSm9pbg0KDQpJbm5lciBqb2lucyBjb21iaW5lIHJlY29yZHMgZnJvbSB0d28gdGFibGVzIHdoZW5ldmVyIHRoZXJlIGFyZSBtYXRjaGluZyB2YWx1ZXMgaW4gYSBmaWVsZCBjb21tb24gdG8gYm90aCB0YWJsZXMuDQoNCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGggPSAiNDUlIiwgZmlnLmNhcD0iSWxsdXN0cmF0aW9uIG9mIGlubmVyIGpvaW4uIn0NCmluY2x1ZGVfZ3JhcGhpY3MoImltZy9pbm5lci1qb2luLnBuZyIpDQpgYGANCg0KYGBge3NxbCwgY29ubmVjdGlvbiA9ICJjb24iLCBvdXRwdXQudmFyID0gImlubmVySm9pbiJ9IA0KU0VMRUNUICoNCkZST00gc3VydmV5cyBBUyBBDQpJTk5FUiBKT0lOIHNwZWNpZXMgQVMgQiAgIC0tIGJ5IGRlZmF1bHQsIEpPSU4gbWVhbnMgSU5ORVIgSk9JTg0KT04gQS5zcGVjaWVzX2lkID0gQi5zcGVjaWVzX2lkOw0KYGBgDQoNCg0KMi4gTGVmdCBKb2luDQoNCkxlZnQgSm9pbiBvciBMZWZ0IE91dGVyIEpvaW4gaW4gU1FMIGNvbWJpbmVzIHR3byBvciBtb3JlIHRhYmxlcywgd2hlcmUgdGhlIGZpcnN0IHRhYmxlIGlzIHJldHVybmVkIHdob2xseTsgYnV0LCBvbmx5IHRoZSBtYXRjaGluZyByZWNvcmQocykgYXJlIHJldHJpZXZlZCBmcm9tIHRoZSBjb25zZXF1ZW50IHRhYmxlcy4gSWYgemVybyAoMCkgcmVjb3JkcyBhcmUgbWF0Y2hlZCBpbiB0aGUgY29uc2VxdWVudCB0YWJsZXMsIHRoZSBqb2luIHdpbGwgc3RpbGwgcmV0dXJuIGEgcm93IGluIHRoZSByZXN1bHQsIGJ1dCB3aXRoIE5VTEwgaW4gZWFjaCBjb2x1bW4gZnJvbSB0aGUgcmlnaHQgdGFibGUuDQoNCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGggPSAiNjAlIiwgZmlnLmNhcD0iSWxsdXN0cmF0aW9uIG9mIGxlZnQgam9pbi4ifQ0KaW5jbHVkZV9ncmFwaGljcygiaW1nL2xlZnQtam9pbi5wbmciKQ0KYGBgDQoNCmBgYHtzcWwsIGNvbm5lY3Rpb24gPSAiY29uIiwgb3V0cHV0LnZhciA9ICJsZWZ0Sm9pbiJ9IA0KU0VMRUNUICoNCkZST00gc3VydmV5cyBBUyBBDQpMRUZUIEpPSU4gc3BlY2llcyBBUyBCDQpPTiBBLnNwZWNpZXNfaWQgPSBCLnNwZWNpZXNfaWQ7DQpgYGANCg0KDQozLiBSaWdodCBKb2luDQoNClRoZSBSaWdodCBKb2luIHF1ZXJ5IGluIFNRTCByZXR1cm5zIGFsbCByb3dzIGZyb20gdGhlIHJpZ2h0IHRhYmxlLCBldmVuIGlmIHRoZXJlIGFyZSBubyBtYXRjaGVzIGluIHRoZSBsZWZ0IHRhYmxlLiBJbiBzaG9ydCwgYSByaWdodCBqb2luIHJldHVybnMgYWxsIHRoZSB2YWx1ZXMgZnJvbSB0aGUgcmlnaHQgdGFibGUsIHBsdXMgbWF0Y2hlZCB2YWx1ZXMgZnJvbSB0aGUgbGVmdCB0YWJsZSBvciBOVUxMIGluIGNhc2Ugb2Ygbm8gbWF0Y2hpbmcgam9pbiBwcmVkaWNhdGUuDQoNCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGggPSAiNjAlIiwgZmlnLmNhcD0iSWxsdXN0cmF0aW9uIG9mIHJpZ2h0IGpvaW4uIn0NCmluY2x1ZGVfZ3JhcGhpY3MoImltZy9yaWdodC1qb2luLnBuZyIpDQpgYGANCg0KDQpgYGB7c3FsLCBjb25uZWN0aW9uID0gImNvbiIsIG91dHB1dC52YXIgPSAicmlnaHRKb2luIn0gDQpTRUxFQ1QgKg0KRlJPTSBzdXJ2ZXlzIEFTIEENClJJR0hUIEpPSU4gc3BlY2llcyBBUyBCDQpPTiBBLnNwZWNpZXNfaWQgPSBCLnNwZWNpZXNfaWQ7DQpgYGANCg0KDQo0LiBGdWxsIEpvaW4NCg0KU1FMIEZ1bGwgSm9pbiBjcmVhdGVzIGEgbmV3IHRhYmxlIGJ5IGpvaW5pbmcgdHdvIHRhYmxlcyBhcyBhIHdob2xlLiBUaGUgam9pbmVkIHRhYmxlIGNvbnRhaW5zIGFsbCByZWNvcmRzIGZyb20gYm90aCB0YWJsZXMgYW5kIGZpbGxzIE5VTEwgdmFsdWVzIGZvciBtaXNzaW5nIG1hdGNoZXMgb24gZWl0aGVyIHNpZGUuIEluIHNob3J0LCBmdWxsIGpvaW4gaXMgYSB0eXBlIG9mIG91dGVyIGpvaW4gdGhhdCBjb21iaW5lcyB0aGUgcmVzdWx0aW5nIHNldHMgb2YgYm90aCBsZWZ0IGFuZCByaWdodCBqb2lucy4NCg0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIG91dC53aWR0aCA9ICI2MCUiLCBmaWcuY2FwPSJJbGx1c3RyYXRpb24gb2YgZnVsbCBqb2luLiJ9DQppbmNsdWRlX2dyYXBoaWNzKCJpbWcvZnVsbC1qb2luLnBuZyIpDQpgYGANCg0KDQoNCmBgYHtzcWwsIGNvbm5lY3Rpb24gPSAiY29uIiwgb3V0cHV0LnZhciA9ICJmdWxsSm9pbiJ9IA0KU0VMRUNUICoNCkZST00gc3VydmV5cyBBUyBBDQpGVUxMIEpPSU4gc3BlY2llcyBBUyBCDQpPTiBBLnNwZWNpZXNfaWQgPSBCLnNwZWNpZXNfaWQ7DQpgYGANCg0KDQoNCg0KIyBTUUwgQWdncmVnYXRpb24gRnVuY3Rpb25zDQoNClNRTCBpcyBhIGdvb2Qgc3RhcnRpbmcgcG9pbnQgZm9yIGhpZ2gtbGV2ZWwgZGF0YSBhbmFseXNpcy4gTWFueSBkYXRhIGFuYWx5c2lzIHBhY2thZ2VzIGFuZCBsYW5ndWFnZXMgaGF2ZSB0aGVpciBvd24gaW50ZXJmYWNlIHRvIHJlYWQgZGF0YSBmcm9tIGRpZmZlcmVudCBTUUwtYmFzZWQgZGF0YWJhc2Ugc3lzdGVtcy4gSW4gZmFjdCwgYW55IHJlYWwtbGlmZSBkYXRhIGFuYWx5c2lzIHN0YXJ0cyBmcm9tIGFuIFJEQk1TLCBhbmQgdGhlIGJhc2ljIGFuYWx5c2lzIGFuZCByZXBvcnQgZ2VuZXJhdGlvbiBpcyBkb25lIG9uIHRoZSBTUUwgcGxhdGZvcm0gb2YgdGhhdCBSREJNUyBpdHNlbGYuIEEgZ29vZCBwcmV2aWV3IG9mIGRhdGEgd2l0aGluIHRoZSBSREJNUyBwbGF0Zm9ybSBpdHNlbGYgaGVscHMgYW5hbHlzdHMgdG8gZ2V0IGEgZmFzdCBoaWdoLWxldmVsIGFuYWx5c2lzIGluIGEgZGlmZmVyZW50IHBsYXRmb3JtLiBUaGUgbW9zdCBjb21tb25seSB1c2VkIGFnZ3JlZ2F0aW9uIGZ1bmN0aW9ucyBpbmNsdWRlICoqTUFYKCksIE1JTigpLCBBVkcoKSxTVU0oKSoqIGFuZCAqKkNPVU5UKCkqKi4gVGhlIGZvbGxvd2luZyBhcmUgYmFzaWMgZXhhbXBsZXMgdGhhdCBpbnZvbHZlIHNvbWUgb2YgdGhlc2UgYWdncmVnYXRpb24gZnVuY3Rpb25zLiANCg0KDQoxLiBBdmVyYWdlDQoNCg0KYGBge3NxbCwgY29ubmVjdGlvbiA9ICJjb24iLCBvdXRwdXQudmFyID0gIkpvaW5TdWJ0YWJsZSJ9IA0KU0VMRUNUIEEuc3BlY2llc19pZCwgDQogICAgICAgQS5zZXgsIA0KICAgICAgIEFWRyhBLndlaWdodCkgYXMgbWVhbl93Z3QgIA0KRlJPTSBzdXJ2ZXlzIEFTIEENCkpPSU4gc3BlY2llcyBBUyBCDQpPTiBBLnNwZWNpZXNfaWQgPSBCLnNwZWNpZXNfaWQgDQpXSEVSRSB0YXhhID0gJ1JvZGVudCcgQU5EIEEuc2V4IElTIE5PVCBOVUxMIA0KR1JPVVAgQlkgQS5zcGVjaWVzX2lkLCBBLnNleDsgICAtLSBzb3J0ZWQgYnkgdHdvIHZhcmlhYmxlcyANCmBgYA0KDQoNCjIuIFNhbXBsZSBzaXplDQoNCmBgYHtzcWwsIGNvbm5lY3Rpb24gPSAiY29uIiwgb3V0cHV0LnZhciA9ICJzbXBsZVNpemUifSANClNFTEVDVCBDT1VOVCgqKSANCkZST00gc3VydmV5cw0KYGBgDQoNCg0KIyBTdWJxdWVyaWVzDQoNClN1YnF1ZXJpZXMgKGFsc28ga25vd24gYXMgaW5uZXIgcXVlcmllcyBvciBuZXN0ZWQgcXVlcmllcykgYXJlIGEgdG9vbCBmb3IgcGVyZm9ybWluZyBvcGVyYXRpb25zIGluIG11bHRpcGxlIHN0ZXBzLiBGb3IgZXhhbXBsZSwgaWYgeW91IHdhbnRlZCB0byB0YWtlIHRoZSBzdW1zIG9mIHNldmVyYWwgY29sdW1ucywgYW5kIHRoZW4gYXZlcmFnZSBhbGwgb2YgdGhvc2UgdmFsdWVzLCB5b3UnZCBuZWVkIHRvIGRvIGVhY2ggYWdncmVnYXRpb24gaW4gYSBkaXN0aW5jdCBzdGVwLiBTdWJxdWVyaWVzIGNhbiBiZSB1c2VkIGluIHNldmVyYWwgcGxhY2VzIHdpdGhpbiBhIHF1ZXJ5LiANCg0KDQojIyBTdWJxdWVyeSBpbiBTRUxFQ1QgQ2xhdXNlDQoNCkluIHRoZSBmb2xsb3dpbmcgZXhhbXBsZSwgd2Ugd2FudCB0byBkZWZpbmUgYSBuZXcgYXR0cmlidXRlLCB0aGUgcmVsYXRpdmUgcGVyY2VudGFnZSBvZiB0YXhvbm9taWMgZ3JvdXBzIGluIHRoZSBkYXRhIHNldCBpbiB0aGUgcmVsYXRpb25hbCB0YWJsZSBgc3VydmV5c2AuIFRoZSBmZWF0dXJlIGB0YXhhYCBpcyBub3QgaW4gdGhlIGBzdXJ2ZXlzYCB0YWJsZS4gV2UgbmVlZCB0byBqb2luIHRhYmxlcyBgc3VydmV5c2AgYW5kIGBzcGVjaWVzYCB0byBvYnRhaW4gdGhlIGRpc3RyaWJ1dGlvbiBvZiBgdGF4YWAgaW4gdGhlIGBzdXJ2ZXlzYCB0YWJsZS4gVG8gdGhpcyBlbmQsIHdlIG5lZWQgdG8ga25vdyB0aGUgZnJlcXVlbmN5IG9mIGVhY2ggdGF4b25vbWljIGdyb3VwIGFuZCB0aGUgc2l6ZSBvZiB0aGUgcmVsYXRpb25hbCB0YWJsZSBgc3VydmV5c2AuDQoNCldlIGNvdWxkIHVzZSB0aGUgZm9sbG93aW5nIHF1ZXJpZXMgdG8gZmluZCB0aGUgc2FtcGxlIHNpemUgYW5kIHRoZSBzaXplcyBvZiBlYWNoIHRheG9uIGdyb3VwLg0KDQoxLiAqKlRvdGFsIFNhbXBsZSBTaXplKioNCg0KYGBge3NxbCwgY29ubmVjdGlvbiA9ICJjb24iLCBvdXRwdXQudmFyID0gIlNhbXBsZVNpemUifSANClNFTEVDVCBDT1VOVCgqKSBGUk9NIHN1cnZleXMNCmBgYA0KDQoyLiAqKkNhbGN1bGF0aW5nIFRheG9uIEdyb3VwIEZyZXF1ZW5jaWVzKioNCg0KVGhlIGdyb3VwIHRvdGFscyBhcmUgY2FsY3VsYXRlZCBpbiB0aGUgZm9sbG93aW5nIGNvZGUuDQoNCmBgYHtzcWwsIGNvbm5lY3Rpb24gPSAiY29uIiwgb3V0cHV0LnZhciA9ICJzdWJUb3RhbCJ9IA0KU0VMRUNUIEIudGF4YSwgDQogICAgICAgQ09VTlQoKikNCkZST00gc3VydmV5cyBBUyBBICAtLSBBIGlzIGFuIGFsaWFzIG9mIGBzdXJ2ZXlzYCB0YWJsZQ0KSU5ORVIgSk9JTiBzcGVjaWVzIEFTIEIgLS0gaW5uZXIgam9pbg0KT04gQS5zcGVjaWVzX2lkID0gQi5zcGVjaWVzX2lkIA0KLS0gdGhpcyBmaW5kcyB0aGUgZ3JvdXAgZnJlcXVlbmNpZXMNCkdST1VQIEJZIHRheGE7IA0KYGBgDQoNCg0KVGhpcyBpcyBub3QgZWZmaWNpZW50LiBXZSBjYW4gdXNlIHRoZSBmb2xsb3dpbmcgbmVzdGVkIHF1ZXJ5IHRvIGVmZmljaWVudGx5IGZpbmQgdGhlIHJlbGF0aXZlIGZyZXF1ZW5jeSB0YWJsZS4NCg0KDQpgYGB7c3FsLCBjb25uZWN0aW9uID0gImNvbiIsIG91dHB1dC52YXIgPSAic3ViU1FMaW5TRUxFQ1QifSANClNFTEVDVCBCLnRheGEsIA0KICAgICAgIDEwMC4wKkNPVU5UKCopLyhTRUxFQ1QgQ09VTlQoKikgRlJPTSBzdXJ2ZXlzKSAgQVMgUGVyY2VudGFnZQ0KRlJPTSBzdXJ2ZXlzIEFTIEENCkpPSU4gc3BlY2llcyBBUyBCDQpPTiBBLnNwZWNpZXNfaWQgPSBCLnNwZWNpZXNfaWQgDQpHUk9VUCBCWSB0YXhhOw0KYGBgDQoNClRoZSBhYm92ZSBxdWVyeSBwcm9kdWNlcyB0aGUgZm9sbG93aW5nIHJlbGF0aXZlIGZyZXF1ZW5jeSB0YWJsZS4NCg0KYGBge3J9DQprYWJsZShzdWJTUUxpblNFTEVDVCkNCmBgYA0KDQojIyBTdWJxdWVyeSBpbiBGUk9NIENsYXVzZQ0KDQpBcyBhbiBleGFtcGxlLCB3ZSBjcmVhdGUgYSBzdWJzZXQgd2l0aCBmZWF0dXJlcyBgcmVjb3JkX2lkLCB5ZWFyLCBwbG90X2lkLCBzcGVjaWVzX2lkLCBzZXgsIGhpbmRmb290X2xlbmd0aCwgd2VpZ2h0YCB3aXRoIHRoZSBjb25kaXRpb24gdGhhdCBgc3BlY2llc19pZCA9ICJETSJgIGFuZCBgc2V4ID0gIk0iYC4gVGhlIGZvbGxvd2luZyBxdWVyeSB0aGF0IGNvbnRhaW5zIGEgc3ViLXF1ZXJ5IGNhbiBkbyB0aGUgdHJpY2suDQoNCg0KYGBge3NxbCwgY29ubmVjdGlvbiA9ICJjb24iLCBvdXRwdXQudmFyID0gInN1YlNRTGluRlJPTSJ9IA0KU0VMRUNUIHN1Yl9zdXJ2ZXkucmVjb3JkX2lkLA0KICAgICAgIHN1Yl9zdXJ2ZXkueWVhciwNCiAgICAgICBzdWJfc3VydmV5LnBsb3RfaWQsDQogICAgICAgc3ViX3N1cnZleS5zcGVjaWVzX2lkLA0KICAgICAgIHN1Yl9zdXJ2ZXkuc2V4LA0KICAgICAgIHN1Yl9zdXJ2ZXkuaGluZGZvb3RfbGVuZ3RoLA0KICAgICAgIHN1Yl9zdXJ2ZXkud2VpZ2h0DQogIEZST00gKA0KICAgICAgICBTRUxFQ1QgKg0KICAgICAgICAgRlJPTSBzdXJ2ZXlzDQogICAgICAgICBXSEVSRSBzcGVjaWVzX2lkID0gJ0RNJw0KICAgICAgICkgc3ViX3N1cnZleSAgLS0gdGhlIG5hbWUgb2YgYSByZWxhdGlvbmFsIHRhYmxlIGRlZmluZWQgYnkgdGhlIHN1YnF1ZXJ5DQogV0hFUkUgc3ViX3N1cnZleS5zZXggPSAnTScNCmBgYA0KDQpUaGUgYWJvdmUgc3Vic2V0IGNhbiBhbHNvIGJlIGdlbmVyYXRlZCB1c2luZyB0aGUgZm9sbG93aW5nIHNpbXBsZSBxdWVyeS4NCg0KYGBge3NxbCwgY29ubmVjdGlvbiA9ICJjb24iLCBvdXRwdXQudmFyID0gInN1YlNRTGluRlJPTV9uZXcifSANClNFTEVDVCByZWNvcmRfaWQsDQogICAgICAgeWVhciwNCiAgICAgICBwbG90X2lkLA0KICAgICAgIHNwZWNpZXNfaWQsDQogICAgICAgc2V4LA0KICAgICAgIGhpbmRmb290X2xlbmd0aCwNCiAgICAgICB3ZWlnaHQNCiAgRlJPTSBzdXJ2ZXlzDQogV0hFUkUgc3BlY2llc19pZCA9ICdETScgQU5EIHNleCA9ICdNJzsNCmBgYA0KDQoNCiMjIFN1YnF1ZXJ5IGluIFdIRVJFIENsYXVzZQ0KDQoNClRoaXMgZXhhbXBsZSBzaG93cyB0aGUgd2F5IHRvIGNyZWF0ZSBhIHN1YnNldCBvZiBzdXJ2ZXlzIGByZWNvcmRfaWQsIHBsb3RfaWQsIHNwZWNpZXNfaWQsIHllYXIsIHNleCwgaGluZGZvb3RfbGVuZ3RoLCB3ZWlnaHRgIGZvciB0aGUgZWFybGllc3QgeWVhci4NCg0KDQpgYGB7c3FsLCBjb25uZWN0aW9uID0gImNvbiIsIG91dHB1dC52YXIgPSAic3ViU1FMaW5XSEVSRSJ9IA0KU0VMRUNUIHJlY29yZF9pZCwNCiAgICAgICBwbG90X2lkLA0KICAgICAgIHNwZWNpZXNfaWQsDQogICAgICAgeWVhciwNCiAgICAgICBzZXgsDQogICAgICAgaGluZGZvb3RfbGVuZ3RoLA0KICAgICAgIHdlaWdodA0KICBGUk9NIHN1cnZleXMNCiBXSEVSRSB5ZWFyID0gKFNFTEVDVCBNSU4oeWVhcikNCiAgICAgICAgICAgICAgICAgRlJPTSBzdXJ2ZXlzDQogICAgICAgICAgICAgICkNCmBgYA0KDQoNCiMjIFN1YnF1ZXJ5IGluIEpPSU4gQ2xhdXNlDQoNCkluIHRoZSBmb2xsb3dpbmcgZXhhbXBsZSwgd2UgdXNlIHRoZSBzdWJxdWVyeSB0byBmaW5kIHRoZSBmcmVxdWVuY3kgY291bnQgaW4gZWFjaCBzcGVjaWVzIGdyb3VwIGZyb20gdGhlIGBzdXJ2ZXlzYCB0YWJsZSBhbmQgYWRkIHRoZSBmcmVxdWVuY3kgdG8gdGhlIGBzcGVjaWVzYCB0YWJsZS4gDQoNCmBgYHtzcWwsIGNvbm5lY3Rpb24gPSAiY29uIiwgb3V0cHV0LnZhciA9ICJzdWJTUUxpbkpPSU4ifSANClNFTEVDVCAqDQogIEZST00gc3BlY2llcw0KICBKT0lOICggU0VMRUNUIENPVU5UKHNwZWNpZXNfaWQpIEFTIHNwZWNpZXNmcmVxLA0KICAgICAgICAgICAgICAgIHNwZWNpZXNfaWQNCiAgICAgICAgICAgRlJPTSBzdXJ2ZXlzDQogICAgICAgICAgIEdST1VQIEJZIHNwZWNpZXNfaWQNCiAgICAgICApIHN1YnENCiAgICBPTiBzcGVjaWVzLnNwZWNpZXNfaWQgPSBzdWJxLnNwZWNpZXNfaWQNCiBPUkRFUiBCWSBzdWJxLnNwZWNpZXNfaWQgREVTQyANCmBgYA0KDQpUaGUgcmVzdWx0aW5nIHRhYmxlIGlzIHNob3duIGluIHRoZSBmb2xsb3dpbmcuDQoNCmBgYHtyfQ0Ka2FibGUoc3ViU1FMaW5KT0lOKQ0KYGBgDQoNCg0KIyBDb25jbHVkaW5nIFJlbWFya3MNCg0KV2UgaGF2ZSBpbnRyb2R1Y2VkIHRoZSBiYXNpYyBTUUwgY2xhdXNlcywgdGhlIGJhc2ljIGpvaW4gb3BlcmF0aW9ucywgYW5kIHRoZSBzdWItcXVlcmllcyBpbiB0aGlzIG5vdGUuIEJ1dCBpdCBpcyBieSBubyBtZWFucyBjb25zaWRlcmVkIGEgY29tcGxldGUgdHV0b3JpYWwgZm9yIFNRTCBwcm9ncmFtbWluZy4gVGhpcyBub3RlIGludGVuZHMgdG8gaGVscCB5b3UgZ2V0IHN0YXJ0ZWQgd2l0aCB0aGUgYmFzaWMgU1FMIGNvZGluZyBvdXRzaWRlIGEgREJNUyBhbmQgZXhwYW5kIHlvdXIgdGVjaG5pY2FsIFZvY2FidWxhcnkgc28geW91IGNhbiB0YWxrIGNvbmZpZGVudGx5IHdpdGggZGF0YSBwcm9mZXNzaW9uYWxzIGluIHRoZSBmdXR1cmUuDQoNClNpbmNlIHdlIGRvbid0IGhhdmUgREJNUyB0byBwcmFjdGljZSBTUUwsIHNvbWUgb2YgdGhlIGFkdmFuY2VkIFNRTCB0YXNrcyBzdWNoIGFzIGNvbnRyb2wgZmxvdywgdXNlci1kZWZpbmVkIFNRTCBmdW5jdGlvbnMsIGV0Yy4gY2FuIG5vdCBiZSBwZXJmb3JtZWQgdXNpbmcgUFJPQyBTUUwgaW4gU0FTIGFuZCBSIGxpYnJhcmllcy4gWW91IGNhbiBsZWFybiB0aGVzZSBhZHZhbmNlZCB0ZWNobmlxdWVzIHF1aWNrbHkgb25jZSB5b3UgYXJlIGNvbWZvcnRhYmxlIHdpdGggdGhlIGJhc2ljIGNvbmNlcHRzIGNvdmVyZWQgaW4gdGhpcyBub3RlLg0KDQoNCg0KDQoNCg==