Introduction
The first step in data collection, processing, and analytics is to get the data from its various source files. Among the common file types for data exchange are CSV (comma-separated values), TSV (tab-separated values), Excel (.xlsx and .xls files), and XML files. This tutorial demonstrates how to load from all but the latter file type.
In addition to reading data from files, it is also critical that the data analyst knows how to store binary objects in files for faster loading after initial processing. For R, this means learning how to save and load R object files.
Base R contains most commonly used functions for reading CSV and TSV files. However, other packages are often needed for special files types (e.g., the readxl and xlsx packages for Excel files) and packages such as readr for faster loading of large files.
The term text file means that the file is human-readable text and can be inspected in any plain text editor, e.g., R Studio, Sublime, JEdit, Notepad, TextEdit, etc. Note that you should never use a word processor to inspect, modify, edit, or create a text file.
As always the packages must be installed first into your local installation of R and then loaded using the library()
or require()
functions as needed.
Of course, data is also found in relational and non-relational (NoSQL) databases, as well as on web pages. This tutorial does not show how to get data from these sources.
Importing Data from CSV and TSV Text Files
Text files are a common way to store and exchange tabular (row/column) data as almost any data application supports exporting data to the .csv text file format. Text file formats use delimiters to separate the columns on each row; each row of data is on its own line in the text file. Therefore, importing different kinds of text files can follow a fairly consistent process once you know the delimiter. The most common delimiter is the comma (,) and thus text files that use comma delimiters are called comma-separated values (CSV) files. Other common delimiters include semicolon (;), colon (:), slash (/), bar (|), and the tab (\t) character.
There are three main groups of functions that are commonly used to import data from text files:
- Base R functions
- readr package functions
- data.table package functions
In this tutorial we will restrict ourselves to the functions in Base R. However, the other two packages often have versions of the functions found in Base R that are more efficient for very large (100k rows or more) text files.
All of the functions import the data and store it in memory in a data frame in R. The data frame mimics the row/column structure of the file.
All rows in the file must have a value for each column. Empty values are either between consecutive delimiters or use a special marker for a missing value such as NA, ““, or ’’.
There is no standard CSV file format, although RFC 4180 is an attempt to standardize some aspects of the CSV format. You might still encounter processing problems when dealing with non-standard CSV files.
Base R Functions: read.table() and read.csv()
The function read.table()
is a Base R function and does not require any additional packages. It is among the most commonly used functions. A simplified version of read.table()
that sets the delimiter to comma (,) is read.csv()
, while another that sets the delimiter to semicolon (;) is read.csv2()
.
A Simple CSV
We will use the following CSV file for some of the examples below. You can download the files from the link or create it using any text editor (R Studio, TextEdit, Sublime, JEdit, Notepad, etc.) and save it under the file name beverages.csv in the folder of your choice. You will need to know the path of the folder, so be sure to remember it.
Note that text (aka, a string or string of characters) is often enclosed in quotes, generally double quotes (“Almond Delight”) but single quotes are fine, too (‘Almond Delight’). It’s one or the other. Of course that makes it tricky to put quotes into a string, which is why we have the backslash for “escaping” a character: “\”Gordon’s\“” Bran”. This can be a bit difficult to load and will require “text parsing”. Some CSV files, like ours, will not use any quotes but that will make it impossible to store text to place commas into the text. So, you can have “Coca, Cola” but not Coca, Cola.
Another common issue is the encoding of true/false or Boolean values. They are sometimes encoded as 0 (false) and 1 (true), as 0 (false) and any other number as true (e.g., -1), as T and F, as TRUE and FALSE, or some other way. A data dictionary sometimes accompanies a CSV file that explains what data each column contains and how it is encoded and to be interpreted. Finally, some CSV files will contain a header row as the first row that contains the names of each column, while some CSV files will only have the data – then, of course, a data dictionary is critical.
Here’s an example of a CSV:
Product,Cereal Name,Manufacturer,Calories,Sodium,Fiber,Carbs,Sugars,Shelf,Year
1,100% Bran,Nabisco,70,130,10,5,6,3,1942
2,All-Bran,Kellogg,70,260,9,7,5,3,1916
3,All-Bran w/Extra Fiber,Kellogg,50,140,14,8,0,3,1916
4,Almond Delight,Ralston Purina,110,200,1,14,8,3,1987
5,Apple Cinn Cheerios,General Mills,110,180,1.50,10.50,10,1,1988
6,Apple Jacks,Kellogg,110,125,1,11,14,2,1965
7,Basic 4,General Mills,130,210,2,18,8,3,1991
8,Bran Chex,Ralston Purina,90,200,4,15,6,1,1978
9,Bran Flakes,Post,90,210,5,13,5,3,1915
10,Cap'n'Crunch,Quaker,120,220,0,12,12,2,1963
11,Cheerios,General Mills,110,290,2,17,1,1,1941
12,Cinnamon Toast Crunch,General Mills,120,210,0,13,9,2,1984
13,Clusters,General Mills,110,140,2,13,7,3,1986
14,Cocoa Puffs,General Mills,110,180,0,12,13,2,1958
Many programs recognize what kind of a file it is by it’s “extension”, the characters at the end of a file name separated from the rest of the file name with a period (.). For example, rides.csv likely contains comma-separated values while flights.tsv likely contains tab-separated values. We say “likely” because there is no requirement that a text file with a .csv extension actually contain even text – it is simply a convention. So, be sure to inspect any text file first in a text editor before importing.
Reading a CSV File
CSV files are most commonly imported with the read.csv()
. It is the same as read.table()
but you do not have to specify the delimiter – it is assumed to be comma.
Remember that you can get the full definition of the function along with a full description of its parameters by consulting the R documentation or with ? read.csv
from the R Console.
The first argument (parameter) to read.csv()
is the file name; whether you use file =
or not is up to you. If you do not specify the path as part of the file, R will search in its default directory (folder). It is strongly suggested that you provide path names or that you use the function setwd()
to set a default directory. Alternatively, you can use the function file.choose()
to interactively ask the user to pick a file. Naturally, you cannot use file.choose()
if you want to knit an R Notebook.
fn <- "CerealData.csv"
bevDF <- read.csv(fn, header = TRUE, stringsAsFactors = FALSE)
Rather than specifying a path to a local file, you can also specify a URL and load the file from the web.
Reading a TSV File
A TSV file uses a tab as a delimiter. It is an “unprintable” character so we need to use a code: \t.
The section below is a sample of the TSV file we will read. The space between the fields is a “tab” – you will have to believe us…
"tconst" "averageRating" "numVotes"
"1" "tt0000001" 5.6 1658
"2" "tt0000002" 6.1 201
"3" "tt0000003" 6.5 1371
"4" "tt0000004" 6.2 122
"5" "tt0000005" 6.2 2158
"6" "tt0000006" 5.3 115
"7" "tt0000007" 5.4 661
fn <- "title.ratings.sample.tsv"
ratingsDF <- read.table(fn, header = TRUE, stringsAsFactors = FALSE,
sep = '\t')
str(ratingsDF)
## 'data.frame': 7 obs. of 3 variables:
## $ tconst : chr "tt0000001" "tt0000002" "tt0000003" "tt0000004" ...
## $ averageRating: num 5.6 6.1 6.5 6.2 6.2 5.3 5.4
## $ numVotes : int 1658 201 1371 122 2158 115 661
Key Parameters
Some key parameters for read.csv()
, read.tsv()
, and read.table()
(there are many more, so consult the documentation):
header |
whether the first line contains headers |
TRUE |
col.names |
a vector of column names if there are no headers or if the headers are to be overwritten |
Empty |
colClasses |
the data type for each column that overrides the type that R guess |
NA |
stringsAsFactors |
whether to import character strings as text or as “factor” variables |
TRUE |
skip |
how many lines to skip before importing |
0 |
quote |
default quote character |
” |
nrows |
the number of rows to read |
-1 |
File Paths in Windows
A note about paths: On Windows, paths are generally written as c:\users\name\downloads using the backslash as the path separator. This will not work in R as the backslash has a special meaning. So, in R (and most other programming languages), we use forward slash, although you often cannot use that in Windows: c:/users/name/downloads. It’s a historical anomaly – ask Bill Gates… On MacOS, since it is based on Unix, we use the standard Unix path separator: the forward slash. And, on Unix, there are no drive letters, so a path on Unix or MacOS never starts with c:\.
Relative vs Absolute Paths
Rather than specifying a path to a file, it is often better to create an R Project and place all data files into the project folder. See Lesson 6.202 Working with R Projects.
Strings vs Factors
The parameter stringsAsFactors
is very important when reading text files that contain, well, text. By default, any column with text will be converted to a “factor” – R’s way to representing categorical variables. That’s most often not what is desired, unless the column is indeed a categorical variables such as gender, job title, among many others. Factor variables are very important for many statistical and data mining functions in R but most often we want to read text columns as strings. Check out the difference in the code below; the class()
function displays a variable’s “data type” in R.
bevDF <- read.csv(fn, header = TRUE, stringsAsFactors = FALSE)
str(bevDF)
## 'data.frame': 7 obs. of 1 variable:
## $ tconst.averageRating.numVotes: chr "1\ttt0000001\t5.6\t1658" "2\ttt0000002\t6.1\t201" "3\ttt0000003\t6.5\t1371" "4\ttt0000004\t6.2\t122" ...
## tconst.averageRating.numVotes
## 1 1\ttt0000001\t5.6\t1658
## 2 2\ttt0000002\t6.1\t201
## 3 3\ttt0000003\t6.5\t1371
## 4 4\ttt0000004\t6.2\t122
## 5 5\ttt0000005\t6.2\t2158
## 6 6\ttt0000006\t5.3\t115
bevDF <- read.csv(fn, header = TRUE, stringsAsFactors = TRUE)
str(bevDF)
## 'data.frame': 7 obs. of 1 variable:
## $ tconst.averageRating.numVotes: Factor w/ 7 levels "1\ttt0000001\t5.6\t1658",..: 1 2 3 4 5 6 7
Note how Manufacturer, as an example, is now a factor variable rather than a strings (a character in R).
It’s gets tricky if you have some columns that are factors and other that are text (strings). Now, you have to read them as strings and convert those columns that you want as factors explicity using as.factor
.
bevDF <- read.csv(fn, header = TRUE, stringsAsFactors = FALSE)
bevDF$Manufacturer <- as.factor(bevDF$Manufacturer)
str(bevDF)
Now Cereal.Name is text while Manufacturer is a factor.
You could have specified the data types you need using the colClasses
parameters as shown next.
bevDF <- read.csv(fn, header = TRUE, stringsAsFactors = TRUE,
colClasses = c("integer","character","factor","integer",
"integer","double","double","integer","integer",
"integer"))
str(bevDF)
The atomic modes that you can specify in colClasses
are “logical”, “integer”, “numeric” (synonym “double”), “complex”, “character” and “raw”.
Instead of read.csv()
you could have used read.table()
which would have the same parameters, but it would allow you to specify the delimiter explicitly. You also will need to specify the ‘quote’ character explicitly.
bevDF <- read.table(fn, sep = ",", quote = "\"",
header = TRUE, stringsAsFactors = TRUE,
colClasses = c("integer","character","factor","integer",
"integer","double","double","integer","integer",
"integer"))
str(bevDF)
Loading CSV Files from a URL
Often a CSV file is located on a URL (on the web) instead of a local file. R also allows URLs to be specified instead of a filename, as long as it starts with _http://_. Note that R cannot access files behind paywalls or using SSL \(_https://_\). The RCurl package contains many additional functions for managing URLs and data on the web.
As an alternative, you can use download.file()
to explicitly download a file through a URL using HTTP or FTP. This might be more efficient than retrieving the data over a network multiple times, particularly if the file is very large. Not only is that potentially quite slow, it also uses precious network bandwidth or cost money if the connection is metered.
url <- "https://drive.google.com/uc?id=1dYvbI2D85AQIHa8yeTL-SzMEjXXnqwpO"
bevDF <- read.csv(url, header = TRUE, stringsAsFactors = TRUE)
str(bevDF)
## 'data.frame': 67 obs. of 10 variables:
## $ Product : int 1 2 3 4 5 6 7 8 9 10 ...
## $ Cereal.Name : Factor w/ 67 levels "100% Bran","All-Bran",..: 1 2 3 4 5 6 7 8 9 10 ...
## $ Manufacturer: Factor w/ 6 levels "General Mills",..: 3 2 2 6 1 2 1 6 4 5 ...
## $ Calories : int 70 70 50 110 110 110 130 90 90 120 ...
## $ Sodium : int 130 260 140 200 180 125 210 200 210 220 ...
## $ Fiber : num 10 9 14 1 1.5 1 2 4 5 0 ...
## $ Carbs : num 5 7 8 14 10.5 11 18 15 13 12 ...
## $ Sugars : int 6 5 0 8 10 14 8 6 5 12 ...
## $ Shelf : int 3 3 3 3 1 2 3 1 3 2 ...
## $ Year : int 1942 1916 1916 1987 1988 1965 1991 1978 1915 1963 ...
To make a text or any other data file accessible on Google Drive, generate a share link with permissions set so Anyone with link can view the file. The link will look like this: _https://drive.google.com/file/d/fc5e038c38a570/view?usp=sharing_. Copy the file identifier fc5e038c38a570 and add it to the end of this URL: _https://drive.google.com/uc?id=_. The complete URL is: _https://drive.google.com/uc?id=fc5e038c38a570_
Compressed Files
Text files are often compressed to save space; it can reduce the size of a file down to 10-20% of its original size. This not only saves space on disk, it also makes downloading over a network much faster. Compressed files often have a .csv.zip, .zip, or a .gz extension. Fortunately, R automatically uncompresses (expands) files if it recognizes the extension. If it can’t, then either uncompress on your operating system or use the unzip()
function.
fn <- "CerealData.zip"
bevDF <- read.csv(unzip(fn), header = TRUE, stringsAsFactors = FALSE)
The readr Package
Compared to the equivalent Base R functions, the functions in readr can be about 10x faster (although, truth be told, I often found the Base R functions to be faster). They have another advantage though: they bring consistency to importing functions, they produce data frames in a data.table format (rather than data frame) which are easier to view for large data sets, the default settings remove the problems with strings and factors, and they have a more flexible column specification.
To demonstrate, we can use read_csv()
which is equivalent to Base R’s read.csv()
function. However, notice how read_csv()
maintains the full variable names.
library(readr)
fn <- "CerealData.csv"
cerDF <- read_csv(fn)
head(cerDF)
One other benefit of read_csv()
is its max
parameters which allows you to set how many lines it will read at most – rather than reading the entire file. This can be useful for “sampling” just a portion of the file.
Importing R Object Files
Creating large and complex data frames is compute-intensive. However, after an R session terminates, all objects in memory go away. The solution is to save them in an R object file using save()
function as a .RData file. This is also useful for archiving.
To load such archived objects back into your current R workspace, use the load()
function. The code below shows how to save and then how to load back an object. Run the first chunk, then restart R – the object will be “gone”, then load the object and you can access it again.
fn <- "CerealData.csv"
fnD <- "bevDF.RData"
bevDF <- read.csv(fn, header = TRUE, stringsAsFactors = FALSE)
head(bevDF)
## Product Cereal.Name Manufacturer Calories Sodium Fiber Carbs Sugars Shelf Year
## 1 1 100% Bran Nabisco 70 130 10.0 5.0 6 3 1942
## 2 2 All-Bran Kellogg 70 260 9.0 7.0 5 3 1916
## 3 3 All-Bran w/Extra Fiber Kellogg 50 140 14.0 8.0 0 3 1916
## 4 4 Almond Delight Ralston Purina 110 200 1.0 14.0 8 3 1987
## 5 5 Apple Cinn Cheerios General Mills 110 180 1.5 10.5 10 1 1988
## 6 6 Apple Jacks Kellogg 110 125 1.0 11.0 14 2 1965
# save the bevDF object to a binary RData file
save(bevDF, file = fnD)
So, now, restart your R Session or clear the bevDF
data frame from memory with rm()
.
rm(bevDF)
gc(verbose = TRUE)
## used (Mb) gc trigger (Mb) limit (Mb) max used (Mb)
## Ncells 2014203 107.6 3545600 189.4 NA 3545600 189.4
## Vcells 4620926 35.3 10146329 77.5 32768 10146329 77.5
To “prove” that it is gone, access the object. You should get the error “Error in head(bevDF) : object ‘bevDF’ not found”.
Let’s load it back (but without rebuilding it), directly from its “binary” representation. This is extremely fast. The function load()
recreates all R objects contained in the file.
fnD <- "bevDF.RData"
load(fnD)
And to prove that it is there, let’s access it…
## Product Cereal.Name Manufacturer Calories Sodium Fiber Carbs Sugars Shelf Year
## 1 1 100% Bran Nabisco 70 130 10.0 5.0 6 3 1942
## 2 2 All-Bran Kellogg 70 260 9.0 7.0 5 3 1916
## 3 3 All-Bran w/Extra Fiber Kellogg 50 140 14.0 8.0 0 3 1916
## 4 4 Almond Delight Ralston Purina 110 200 1.0 14.0 8 3 1987
## 5 5 Apple Cinn Cheerios General Mills 110 180 1.5 10.5 10 1 1988
## 6 6 Apple Jacks Kellogg 110 125 1.0 11.0 14 2 1965
Voila… the object is back.
LS0tCnRpdGxlOiAiSW1wb3J0IERhdGEgaW50byBSIGZyb20gQ1NWLCBUU1YsIGFuZCBFeGNlbCBGaWxlcyIKcGFyYW1zOgogIGNhdGVnb3J5OiA2CiAgbnVtYmVyOiAxMDYKICB0aW1lOiA0NQogIGxldmVsOiBiZWdpbm5lcgogIHRhZ3M6ICJyLGNzdix0c3YsZmlsZXMsZXhjZWwsZGF0YSBmcmFtZXMiCiAgZGVzY3JpcHRpb246ICJEZW1vbnN0cmF0ZXMgaG93IHRvIGxvYWQgZGF0YSBmcm9tIENTViwgVFNWLCBFeGNlbCwgYW5kCiAgICAgICAgICAgICAgICBvdGhlciB0ZXh0IGZpbGVzIGludG8gZGF0YSBmcmFtZXMgZm9yIHByb2Nlc3NpbmcuIEV4cGxhaW5zCiAgICAgICAgICAgICAgICBob3cgdG8gc2F2ZSBsYXJnZSBSIG9iamVjdHMgaW4gYmluYXJ5IFJEYXRhIGZpbGVzLiIKZGF0ZTogIjxzbWFsbD5gciBTeXMuRGF0ZSgpYDwvc21hbGw+IgphdXRob3I6ICI8c21hbGw+TWFydGluIFNjaGVkbGJhdWVyPC9zbWFsbD4iCmVtYWlsOiAibS5zY2hlZGxiYXVlckBub3J0aGVhc3Rlcm4uZWR1IgphZmZpbGl0YXRpb246ICJOb3J0aGVhc3Rlcm4gVW5pdmVyc2l0eSIKb3V0cHV0OiAKICBib29rZG93bjo6aHRtbF9kb2N1bWVudDI6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY29sbGFwc2VkOiBmYWxzZQogICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgdGhlbWU6IHJlYWRhYmxlCiAgICBoaWdobGlnaHQ6IHRhbmdvCi0tLQoKLS0tCnRpdGxlOiAiPHNtYWxsPmByIHBhcmFtcyRjYXRlZ29yeWAuYHIgcGFyYW1zJG51bWJlcmA8L3NtYWxsPjxici8+PHNwYW4gc3R5bGU9J2NvbG9yOiAjMkU0MDUzOyBmb250LXNpemU6IDAuOWVtJz5gciBybWFya2Rvd246Om1ldGFkYXRhJHRpdGxlYDwvc3Bhbj4iCi0tLQoKYGBge3IgY29kZT14ZnVuOjpyZWFkX3V0ZjgocGFzdGUwKGhlcmU6OmhlcmUoKSwnL1IvX2luc2VydDJEQi5SJykpLCBpbmNsdWRlID0gRkFMU0V9CmBgYAoKIyMgSW50cm9kdWN0aW9uCgpUaGUgZmlyc3Qgc3RlcCBpbiBkYXRhIGNvbGxlY3Rpb24sIHByb2Nlc3NpbmcsIGFuZCBhbmFseXRpY3MgaXMgdG8gZ2V0IHRoZSBkYXRhIGZyb20gaXRzIHZhcmlvdXMgc291cmNlIGZpbGVzLiBBbW9uZyB0aGUgY29tbW9uIGZpbGUgdHlwZXMgZm9yIGRhdGEgZXhjaGFuZ2UgYXJlIENTViAoY29tbWEtc2VwYXJhdGVkIHZhbHVlcyksIFRTViAodGFiLXNlcGFyYXRlZCB2YWx1ZXMpLCBFeGNlbCAoKi54bHN4KiBhbmQgKi54bHMqIGZpbGVzKSwgYW5kIFhNTCBmaWxlcy4gVGhpcyB0dXRvcmlhbCBkZW1vbnN0cmF0ZXMgaG93IHRvIGxvYWQgZnJvbSBhbGwgYnV0IHRoZSBsYXR0ZXIgZmlsZSB0eXBlLgoKSW4gYWRkaXRpb24gdG8gcmVhZGluZyBkYXRhIGZyb20gZmlsZXMsIGl0IGlzIGFsc28gY3JpdGljYWwgdGhhdCB0aGUgZGF0YSBhbmFseXN0IGtub3dzIGhvdyB0byBzdG9yZSBiaW5hcnkgb2JqZWN0cyBpbiBmaWxlcyBmb3IgZmFzdGVyIGxvYWRpbmcgYWZ0ZXIgaW5pdGlhbCBwcm9jZXNzaW5nLiBGb3IgUiwgdGhpcyBtZWFucyBsZWFybmluZyBob3cgdG8gc2F2ZSBhbmQgbG9hZCBSIG9iamVjdCBmaWxlcy4KCkJhc2UgUiBjb250YWlucyBtb3N0IGNvbW1vbmx5IHVzZWQgZnVuY3Rpb25zIGZvciByZWFkaW5nIENTViBhbmQgVFNWIGZpbGVzLiBIb3dldmVyLCBvdGhlciBwYWNrYWdlcyBhcmUgb2Z0ZW4gbmVlZGVkIGZvciBzcGVjaWFsIGZpbGVzIHR5cGVzICgqZS5nLiosIHRoZSAqKnJlYWR4bCoqIGFuZCAqKnhsc3gqKiBwYWNrYWdlcyBmb3IgRXhjZWwgZmlsZXMpIGFuZCBwYWNrYWdlcyBzdWNoIGFzICoqcmVhZHIqKiBmb3IgZmFzdGVyIGxvYWRpbmcgb2YgbGFyZ2UgZmlsZXMuCgpUaGUgdGVybSAqdGV4dCBmaWxlKiBtZWFucyB0aGF0IHRoZSBmaWxlIGlzIGh1bWFuLXJlYWRhYmxlIHRleHQgYW5kIGNhbiBiZSBpbnNwZWN0ZWQgaW4gYW55IHBsYWluIHRleHQgZWRpdG9yLCAqZS5nLiosIFIgU3R1ZGlvLCBTdWJsaW1lLCBKRWRpdCwgTm90ZXBhZCwgVGV4dEVkaXQsICpldGMqLiBOb3RlIHRoYXQgeW91IHNob3VsZCBuZXZlciB1c2UgYSB3b3JkIHByb2Nlc3NvciB0byBpbnNwZWN0LCBtb2RpZnksIGVkaXQsIG9yIGNyZWF0ZSBhIHRleHQgZmlsZS4KCkFzIGFsd2F5cyB0aGUgcGFja2FnZXMgbXVzdCBiZSBpbnN0YWxsZWQgZmlyc3QgaW50byB5b3VyIGxvY2FsIGluc3RhbGxhdGlvbiBvZiBSIGFuZCB0aGVuIGxvYWRlZCB1c2luZyB0aGUgPGNvZGU+bGlicmFyeSgpPC9jb2RlPiBvciA8Y29kZT5yZXF1aXJlKCk8L2NvZGU+IGZ1bmN0aW9ucyBhcyBuZWVkZWQuCgpPZiBjb3Vyc2UsIGRhdGEgaXMgYWxzbyBmb3VuZCBpbiByZWxhdGlvbmFsIGFuZCBub24tcmVsYXRpb25hbCAoTm9TUUwpIGRhdGFiYXNlcywgYXMgd2VsbCBhcyBvbiB3ZWIgcGFnZXMuIFRoaXMgdHV0b3JpYWwgZG9lcyBub3Qgc2hvdyBob3cgdG8gZ2V0IGRhdGEgZnJvbSB0aGVzZSBzb3VyY2VzLgoKIyMgSW1wb3J0aW5nIERhdGEgZnJvbSAqQ1NWKiBhbmQgKlRTViogVGV4dCBGaWxlcwoKVGV4dCBmaWxlcyBhcmUgYSBjb21tb24gd2F5IHRvIHN0b3JlIGFuZCBleGNoYW5nZSB0YWJ1bGFyIChyb3cvY29sdW1uKSBkYXRhIGFzIGFsbW9zdCBhbnkgZGF0YSBhcHBsaWNhdGlvbiBzdXBwb3J0cyBleHBvcnRpbmcgZGF0YSB0byB0aGUgKi5jc3YqIHRleHQgZmlsZSBmb3JtYXQuIFRleHQgZmlsZSBmb3JtYXRzIHVzZSBkZWxpbWl0ZXJzIHRvIHNlcGFyYXRlIHRoZSBjb2x1bW5zIG9uIGVhY2ggcm93OyBlYWNoIHJvdyBvZiBkYXRhIGlzIG9uIGl0cyBvd24gbGluZSBpbiB0aGUgdGV4dCBmaWxlLiBUaGVyZWZvcmUsIGltcG9ydGluZyBkaWZmZXJlbnQga2luZHMgb2YgdGV4dCBmaWxlcyBjYW4gZm9sbG93IGEgZmFpcmx5IGNvbnNpc3RlbnQgcHJvY2VzcyBvbmNlIHlvdSBrbm93IHRoZSBkZWxpbWl0ZXIuIFRoZSBtb3N0IGNvbW1vbiBkZWxpbWl0ZXIgaXMgdGhlIGNvbW1hICgsKSBhbmQgdGh1cyB0ZXh0IGZpbGVzIHRoYXQgdXNlIGNvbW1hIGRlbGltaXRlcnMgYXJlIGNhbGxlZCBjb21tYS1zZXBhcmF0ZWQgdmFsdWVzIChDU1YpIGZpbGVzLiBPdGhlciBjb21tb24gZGVsaW1pdGVycyBpbmNsdWRlIHNlbWljb2xvbiAoOyksIGNvbG9uICg6KSwgc2xhc2ggKC8pLCBiYXIgKFx8KSwgYW5kIHRoZSB0YWIgKFxcdCkgY2hhcmFjdGVyLgoKVGhlcmUgYXJlIHRocmVlIG1haW4gZ3JvdXBzIG9mIGZ1bmN0aW9ucyB0aGF0IGFyZSBjb21tb25seSB1c2VkIHRvIGltcG9ydCBkYXRhIGZyb20gdGV4dCBmaWxlczoKCi0gICBCYXNlIFIgZnVuY3Rpb25zCi0gICAqKnJlYWRyKiogcGFja2FnZSBmdW5jdGlvbnMKLSAgICoqZGF0YS50YWJsZSoqIHBhY2thZ2UgZnVuY3Rpb25zCgpJbiB0aGlzIHR1dG9yaWFsIHdlIHdpbGwgcmVzdHJpY3Qgb3Vyc2VsdmVzIHRvIHRoZSBmdW5jdGlvbnMgaW4gQmFzZSBSLiBIb3dldmVyLCB0aGUgb3RoZXIgdHdvIHBhY2thZ2VzIG9mdGVuIGhhdmUgdmVyc2lvbnMgb2YgdGhlIGZ1bmN0aW9ucyBmb3VuZCBpbiBCYXNlIFIgdGhhdCBhcmUgbW9yZSBlZmZpY2llbnQgZm9yIHZlcnkgbGFyZ2UgKDEwMGsgcm93cyBvciBtb3JlKSB0ZXh0IGZpbGVzLgoKQWxsIG9mIHRoZSBmdW5jdGlvbnMgaW1wb3J0IHRoZSBkYXRhIGFuZCBzdG9yZSBpdCBpbiBtZW1vcnkgaW4gYSBkYXRhIGZyYW1lIGluIFIuIFRoZSBkYXRhIGZyYW1lIG1pbWljcyB0aGUgcm93L2NvbHVtbiBzdHJ1Y3R1cmUgb2YgdGhlIGZpbGUuCgo+IEFsbCByb3dzIGluIHRoZSBmaWxlIG11c3QgaGF2ZSBhIHZhbHVlIGZvciBlYWNoIGNvbHVtbi4gRW1wdHkgdmFsdWVzIGFyZSBlaXRoZXIgYmV0d2VlbiBjb25zZWN1dGl2ZSBkZWxpbWl0ZXJzIG9yIHVzZSBhIHNwZWNpYWwgbWFya2VyIGZvciBhIG1pc3NpbmcgdmFsdWUgc3VjaCBhcyAqTkEqLCAqIiIqLCBvciAqJycqLgoKVGhlcmUgaXMgbm8gc3RhbmRhcmQgQ1NWIGZpbGUgZm9ybWF0LCBhbHRob3VnaCBSRkMgNDE4MCBpcyBhbiBhdHRlbXB0IHRvIHN0YW5kYXJkaXplIHNvbWUgYXNwZWN0cyBvZiB0aGUgQ1NWIGZvcm1hdC4gWW91IG1pZ2h0IHN0aWxsIGVuY291bnRlciBwcm9jZXNzaW5nIHByb2JsZW1zIHdoZW4gZGVhbGluZyB3aXRoIG5vbi1zdGFuZGFyZCBDU1YgZmlsZXMuCgojIyMgQmFzZSBSIEZ1bmN0aW9uczogKnJlYWQudGFibGUoKSogYW5kICpyZWFkLmNzdigpKgoKVGhlIGZ1bmN0aW9uIDxjb2RlPnJlYWQudGFibGUoKTwvY29kZT4gaXMgYSBCYXNlIFIgZnVuY3Rpb24gYW5kIGRvZXMgbm90IHJlcXVpcmUgYW55IGFkZGl0aW9uYWwgcGFja2FnZXMuIEl0IGlzIGFtb25nIHRoZSBtb3N0IGNvbW1vbmx5IHVzZWQgZnVuY3Rpb25zLiBBIHNpbXBsaWZpZWQgdmVyc2lvbiBvZiA8Y29kZT5yZWFkLnRhYmxlKCk8L2NvZGU+IHRoYXQgc2V0cyB0aGUgZGVsaW1pdGVyIHRvIGNvbW1hICgqLCopIGlzIDxjb2RlPnJlYWQuY3N2KCk8L2NvZGU+LCB3aGlsZSBhbm90aGVyIHRoYXQgc2V0cyB0aGUgZGVsaW1pdGVyIHRvIHNlbWljb2xvbiAoKjsqKSBpcyA8Y29kZT5yZWFkLmNzdjIoKTwvY29kZT4uCgojIyMgQSBTaW1wbGUgQ1NWCgpXZSB3aWxsIHVzZSB0aGUgZm9sbG93aW5nIENTViBmaWxlIGZvciBzb21lIG9mIHRoZSBleGFtcGxlcyBiZWxvdy4gWW91IGNhbiBkb3dubG9hZCB0aGUgZmlsZXMgZnJvbSB0aGUgbGluayBvciBjcmVhdGUgaXQgdXNpbmcgYW55IHRleHQgZWRpdG9yIChSIFN0dWRpbywgVGV4dEVkaXQsIFN1YmxpbWUsIEpFZGl0LCBOb3RlcGFkLCAqZXRjLiopIGFuZCBzYXZlIGl0IHVuZGVyIHRoZSBmaWxlIG5hbWUgKmJldmVyYWdlcy5jc3YqIGluIHRoZSBmb2xkZXIgb2YgeW91ciBjaG9pY2UuIFlvdSB3aWxsIG5lZWQgdG8ga25vdyB0aGUgcGF0aCBvZiB0aGUgZm9sZGVyLCBzbyBiZSBzdXJlIHRvIHJlbWVtYmVyIGl0LgoKTm90ZSB0aGF0IHRleHQgKCpha2EqLCBhIHN0cmluZyBvciBzdHJpbmcgb2YgY2hhcmFjdGVycykgaXMgb2Z0ZW4gZW5jbG9zZWQgaW4gcXVvdGVzLCBnZW5lcmFsbHkgZG91YmxlIHF1b3RlcyAoIkFsbW9uZCBEZWxpZ2h0IikgYnV0IHNpbmdsZSBxdW90ZXMgYXJlIGZpbmUsIHRvbyAoJ0FsbW9uZCBEZWxpZ2h0JykuIEl0J3Mgb25lIG9yIHRoZSBvdGhlci4gT2YgY291cnNlIHRoYXQgbWFrZXMgaXQgdHJpY2t5IHRvIHB1dCBxdW90ZXMgaW50byBhIHN0cmluZywgd2hpY2ggaXMgd2h5IHdlIGhhdmUgdGhlIGJhY2tzbGFzaCBmb3IgImVzY2FwaW5nIiBhIGNoYXJhY3RlcjogIlxcIkdvcmRvbidzXFwiIiBCcmFuIi4gVGhpcyBjYW4gYmUgYSBiaXQgZGlmZmljdWx0IHRvIGxvYWQgYW5kIHdpbGwgcmVxdWlyZSAidGV4dCBwYXJzaW5nIi4gU29tZSBDU1YgZmlsZXMsIGxpa2Ugb3Vycywgd2lsbCBub3QgdXNlIGFueSBxdW90ZXMgYnV0IHRoYXQgd2lsbCBtYWtlIGl0IGltcG9zc2libGUgdG8gc3RvcmUgdGV4dCB0byBwbGFjZSBjb21tYXMgaW50byB0aGUgdGV4dC4gU28sIHlvdSBjYW4gaGF2ZSAqIkNvY2EsIENvbGEiKiBidXQgbm90ICpDb2NhLCBDb2xhKi4KCkFub3RoZXIgY29tbW9uIGlzc3VlIGlzIHRoZSBlbmNvZGluZyBvZiB0cnVlL2ZhbHNlIG9yIEJvb2xlYW4gdmFsdWVzLiBUaGV5IGFyZSBzb21ldGltZXMgZW5jb2RlZCBhcyAwIChmYWxzZSkgYW5kIDEgKHRydWUpLCBhcyAwIChmYWxzZSkgYW5kIGFueSBvdGhlciBudW1iZXIgYXMgdHJ1ZSAoKmUuZy4qLCAtMSksIGFzICpUKiBhbmQgKkYqLCBhcyAqVFJVRSogYW5kICpGQUxTRSosIG9yIHNvbWUgb3RoZXIgd2F5LiBBIGRhdGEgZGljdGlvbmFyeSBzb21ldGltZXMgYWNjb21wYW5pZXMgYSBDU1YgZmlsZSB0aGF0IGV4cGxhaW5zIHdoYXQgZGF0YSBlYWNoIGNvbHVtbiBjb250YWlucyBhbmQgaG93IGl0IGlzIGVuY29kZWQgYW5kIHRvIGJlIGludGVycHJldGVkLiBGaW5hbGx5LCBzb21lIENTViBmaWxlcyB3aWxsIGNvbnRhaW4gYSBoZWFkZXIgcm93IGFzIHRoZSBmaXJzdCByb3cgdGhhdCBjb250YWlucyB0aGUgbmFtZXMgb2YgZWFjaCBjb2x1bW4sIHdoaWxlIHNvbWUgQ1NWIGZpbGVzIHdpbGwgb25seSBoYXZlIHRoZSBkYXRhIC0tIHRoZW4sIG9mIGNvdXJzZSwgYSBkYXRhIGRpY3Rpb25hcnkgaXMgY3JpdGljYWwuCgpIZXJlJ3MgYW4gZXhhbXBsZSBvZiBhIENTVjoKCmBgYHs9aHRtbH0KPHByZT4KUHJvZHVjdCxDZXJlYWwgTmFtZSxNYW51ZmFjdHVyZXIsQ2Fsb3JpZXMsU29kaXVtLEZpYmVyLENhcmJzLFN1Z2FycyxTaGVsZixZZWFyCjEsMTAwJSBCcmFuLE5hYmlzY28sNzAsMTMwLDEwLDUsNiwzLDE5NDIKMixBbGwtQnJhbixLZWxsb2dnLDcwLDI2MCw5LDcsNSwzLDE5MTYKMyxBbGwtQnJhbiB3L0V4dHJhIEZpYmVyLEtlbGxvZ2csNTAsMTQwLDE0LDgsMCwzLDE5MTYKNCxBbG1vbmQgRGVsaWdodCxSYWxzdG9uIFB1cmluYSwxMTAsMjAwLDEsMTQsOCwzLDE5ODcKNSxBcHBsZSBDaW5uIENoZWVyaW9zLEdlbmVyYWwgTWlsbHMsMTEwLDE4MCwxLjUwLDEwLjUwLDEwLDEsMTk4OAo2LEFwcGxlIEphY2tzLEtlbGxvZ2csMTEwLDEyNSwxLDExLDE0LDIsMTk2NQo3LEJhc2ljIDQsR2VuZXJhbCBNaWxscywxMzAsMjEwLDIsMTgsOCwzLDE5OTEKOCxCcmFuIENoZXgsUmFsc3RvbiBQdXJpbmEsOTAsMjAwLDQsMTUsNiwxLDE5NzgKOSxCcmFuIEZsYWtlcyxQb3N0LDkwLDIxMCw1LDEzLDUsMywxOTE1CjEwLENhcCduJ0NydW5jaCxRdWFrZXIsMTIwLDIyMCwwLDEyLDEyLDIsMTk2MwoxMSxDaGVlcmlvcyxHZW5lcmFsIE1pbGxzLDExMCwyOTAsMiwxNywxLDEsMTk0MQoxMixDaW5uYW1vbiBUb2FzdCBDcnVuY2gsR2VuZXJhbCBNaWxscywxMjAsMjEwLDAsMTMsOSwyLDE5ODQKMTMsQ2x1c3RlcnMsR2VuZXJhbCBNaWxscywxMTAsMTQwLDIsMTMsNywzLDE5ODYKMTQsQ29jb2EgUHVmZnMsR2VuZXJhbCBNaWxscywxMTAsMTgwLDAsMTIsMTMsMiwxOTU4CjwvcHJlPgpgYGAKTWFueSBwcm9ncmFtcyByZWNvZ25pemUgd2hhdCBraW5kIG9mIGEgZmlsZSBpdCBpcyBieSBpdCdzICJleHRlbnNpb24iLCB0aGUgY2hhcmFjdGVycyBhdCB0aGUgZW5kIG9mIGEgZmlsZSBuYW1lIHNlcGFyYXRlZCBmcm9tIHRoZSByZXN0IG9mIHRoZSBmaWxlIG5hbWUgd2l0aCBhIHBlcmlvZCAoLikuIEZvciBleGFtcGxlLCAqcmlkZXMuY3N2KiBsaWtlbHkgY29udGFpbnMgY29tbWEtc2VwYXJhdGVkIHZhbHVlcyB3aGlsZSAqZmxpZ2h0cy50c3YqIGxpa2VseSBjb250YWlucyB0YWItc2VwYXJhdGVkIHZhbHVlcy4gV2Ugc2F5ICJsaWtlbHkiIGJlY2F1c2UgdGhlcmUgaXMgbm8gcmVxdWlyZW1lbnQgdGhhdCBhIHRleHQgZmlsZSB3aXRoIGEgKi5jc3YqIGV4dGVuc2lvbiBhY3R1YWxseSBjb250YWluIGV2ZW4gdGV4dCAtLSBpdCBpcyBzaW1wbHkgYSBjb252ZW50aW9uLiBTbywgYmUgc3VyZSB0byBpbnNwZWN0IGFueSB0ZXh0IGZpbGUgZmlyc3QgaW4gYSB0ZXh0IGVkaXRvciBiZWZvcmUgaW1wb3J0aW5nLgoKIyMjIFJlYWRpbmcgYSBDU1YgRmlsZQoKQ1NWIGZpbGVzIGFyZSBtb3N0IGNvbW1vbmx5IGltcG9ydGVkIHdpdGggdGhlIDxjb2RlPnJlYWQuY3N2KCk8L2NvZGU+LiBJdCBpcyB0aGUgc2FtZSBhcyA8Y29kZT5yZWFkLnRhYmxlKCk8L2NvZGU+IGJ1dCB5b3UgZG8gbm90IGhhdmUgdG8gc3BlY2lmeSB0aGUgZGVsaW1pdGVyIC0tIGl0IGlzIGFzc3VtZWQgdG8gYmUgY29tbWEuCgo+IFJlbWVtYmVyIHRoYXQgeW91IGNhbiBnZXQgdGhlIGZ1bGwgZGVmaW5pdGlvbiBvZiB0aGUgZnVuY3Rpb24gYWxvbmcgd2l0aCBhIGZ1bGwgZGVzY3JpcHRpb24gb2YgaXRzIHBhcmFtZXRlcnMgYnkgY29uc3VsdGluZyB0aGUgUiBkb2N1bWVudGF0aW9uIG9yIHdpdGggPGNvZGU+PyByZWFkLmNzdjwvY29kZT4gZnJvbSB0aGUgUiBDb25zb2xlLgoKVGhlIGZpcnN0IGFyZ3VtZW50IChwYXJhbWV0ZXIpIHRvIDxjb2RlPnJlYWQuY3N2KCk8L2NvZGU+IGlzIHRoZSBmaWxlIG5hbWU7IHdoZXRoZXIgeW91IHVzZSA8Y29kZT5maWxlID08L2NvZGU+IG9yIG5vdCBpcyB1cCB0byB5b3UuIElmIHlvdSBkbyBub3Qgc3BlY2lmeSB0aGUgcGF0aCBhcyBwYXJ0IG9mIHRoZSBmaWxlLCBSIHdpbGwgc2VhcmNoIGluIGl0cyBkZWZhdWx0IGRpcmVjdG9yeSAoZm9sZGVyKS4gSXQgaXMgc3Ryb25nbHkgc3VnZ2VzdGVkIHRoYXQgeW91IHByb3ZpZGUgcGF0aCBuYW1lcyBvciB0aGF0IHlvdSB1c2UgdGhlIGZ1bmN0aW9uIDxjb2RlPnNldHdkKCk8L2NvZGU+IHRvIHNldCBhIGRlZmF1bHQgZGlyZWN0b3J5LiBBbHRlcm5hdGl2ZWx5LCB5b3UgY2FuIHVzZSB0aGUgZnVuY3Rpb24gPGNvZGU+ZmlsZS5jaG9vc2UoKTwvY29kZT4gdG8gaW50ZXJhY3RpdmVseSBhc2sgdGhlIHVzZXIgdG8gcGljayBhIGZpbGUuIE5hdHVyYWxseSwgeW91IGNhbm5vdCB1c2UgPGNvZGU+ZmlsZS5jaG9vc2UoKTwvY29kZT4gaWYgeW91IHdhbnQgdG8ga25pdCBhbiBSIE5vdGVib29rLgoKYGBge3J9CmZuIDwtICJDZXJlYWxEYXRhLmNzdiIKCmJldkRGIDwtIHJlYWQuY3N2KGZuLCBoZWFkZXIgPSBUUlVFLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmBgYAoKUmF0aGVyIHRoYW4gc3BlY2lmeWluZyBhIHBhdGggdG8gYSBsb2NhbCBmaWxlLCB5b3UgY2FuIGFsc28gc3BlY2lmeSBhIFVSTCBhbmQgbG9hZCB0aGUgZmlsZSBmcm9tIHRoZSB3ZWIuCgojIyMgUmVhZGluZyBhIFRTViBGaWxlCgpBIFRTViBmaWxlIHVzZXMgYSB0YWIgYXMgYSBkZWxpbWl0ZXIuIEl0IGlzIGFuICJ1bnByaW50YWJsZSIgY2hhcmFjdGVyIHNvIHdlIG5lZWQgdG8gdXNlIGEgY29kZTogXFx0LgoKVGhlIHNlY3Rpb24gYmVsb3cgaXMgYSBzYW1wbGUgb2YgdGhlIFRTViBmaWxlIHdlIHdpbGwgcmVhZC4gVGhlIHNwYWNlIGJldHdlZW4gdGhlIGZpZWxkcyBpcyBhICJ0YWIiIC0tIHlvdSB3aWxsIGhhdmUgdG8gYmVsaWV2ZSB1cy4uLgoKYGBgez1odG1sfQo8cHJlPgoidGNvbnN0IiAgICAiYXZlcmFnZVJhdGluZyIgIm51bVZvdGVzIgoiMSIgInR0MDAwMDAwMSIgNS42IDE2NTgKIjIiICJ0dDAwMDAwMDIiIDYuMSAyMDEKIjMiICJ0dDAwMDAwMDMiIDYuNSAxMzcxCiI0IiAidHQwMDAwMDA0IiA2LjIgMTIyCiI1IiAidHQwMDAwMDA1IiA2LjIgMjE1OAoiNiIgInR0MDAwMDAwNiIgNS4zIDExNQoiNyIgInR0MDAwMDAwNyIgNS40IDY2MQo8L3ByZT4KYGBgCmBgYHtyfQpmbiA8LSAidGl0bGUucmF0aW5ncy5zYW1wbGUudHN2IgoKcmF0aW5nc0RGIDwtIHJlYWQudGFibGUoZm4sIGhlYWRlciA9IFRSVUUsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gJ1x0JykKCnN0cihyYXRpbmdzREYpCmBgYAoKIyMjIEtleSBQYXJhbWV0ZXJzCgpTb21lIGtleSBwYXJhbWV0ZXJzIGZvciA8Y29kZT5yZWFkLmNzdigpPC9jb2RlPiwgPGNvZGU+cmVhZC50c3YoKTwvY29kZT4sIGFuZCA8Y29kZT5yZWFkLnRhYmxlKCk8L2NvZGU+ICh0aGVyZSBhcmUgbWFueSBtb3JlLCBzbyBjb25zdWx0IHRoZSBkb2N1bWVudGF0aW9uKToKCnwgUGFyYW1ldGVyICAgICAgICAgICAgICAgICAgICAgfCBVc2UvTWVhbmluZyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgRGVmYXVsdCB8Cnw6LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfDotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXw6LS0tLS0tLS18CnwgPGNvZGU+aGVhZGVyPC9jb2RlPiAgICAgICAgICAgfCB3aGV0aGVyIHRoZSBmaXJzdCBsaW5lIGNvbnRhaW5zIGhlYWRlcnMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgVFJVRSAgICB8CnwgPGNvZGU+Y29sLm5hbWVzPC9jb2RlPiAgICAgICAgfCBhIHZlY3RvciBvZiBjb2x1bW4gbmFtZXMgaWYgdGhlcmUgYXJlIG5vIGhlYWRlcnMgb3IgaWYgdGhlIGhlYWRlcnMgYXJlIHRvIGJlIG92ZXJ3cml0dGVuIHwgKkVtcHR5KiB8CnwgPGNvZGU+Y29sQ2xhc3NlczwvY29kZT4gICAgICAgfCB0aGUgZGF0YSB0eXBlIGZvciBlYWNoIGNvbHVtbiB0aGF0IG92ZXJyaWRlcyB0aGUgdHlwZSB0aGF0IFIgZ3Vlc3MgICAgICAgICAgICAgICAgICAgICAgIHwgKk5BKiAgICB8CnwgPGNvZGU+c3RyaW5nc0FzRmFjdG9yczwvY29kZT4gfCB3aGV0aGVyIHRvIGltcG9ydCBjaGFyYWN0ZXIgc3RyaW5ncyBhcyB0ZXh0IG9yIGFzICJmYWN0b3IiIHZhcmlhYmxlcyAgICAgICAgICAgICAgICAgICAgIHwgVFJVRSAgICB8CnwgPGNvZGU+c2tpcDwvY29kZT4gICAgICAgICAgICAgfCBob3cgbWFueSBsaW5lcyB0byBza2lwIGJlZm9yZSBpbXBvcnRpbmcgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgMCAgICAgICB8CnwgPGNvZGU+cXVvdGU8L2NvZGU+ICAgICAgICAgICAgfCBkZWZhdWx0IHF1b3RlIGNoYXJhY3RlciAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgIiAgICAgICB8CnwgPGNvZGU+bnJvd3M8L2NvZGU+ICAgICAgICAgICAgfCB0aGUgbnVtYmVyIG9mIHJvd3MgdG8gcmVhZCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgLTEgICAgICB8CgojIyMgRmlsZSBQYXRocyBpbiBXaW5kb3dzCgo+IEEgbm90ZSBhYm91dCBwYXRoczogT24gV2luZG93cywgcGF0aHMgYXJlIGdlbmVyYWxseSB3cml0dGVuIGFzICpjOlxcdXNlcnNcXG5hbWVcXGRvd25sb2FkcyogdXNpbmcgdGhlIGJhY2tzbGFzaCBhcyB0aGUgcGF0aCBzZXBhcmF0b3IuIFRoaXMgd2lsbCBub3Qgd29yayBpbiBSIGFzIHRoZSBiYWNrc2xhc2ggaGFzIGEgc3BlY2lhbCBtZWFuaW5nLiBTbywgaW4gUiAoYW5kIG1vc3Qgb3RoZXIgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2VzKSwgd2UgdXNlIGZvcndhcmQgc2xhc2gsIGFsdGhvdWdoIHlvdSBvZnRlbiBjYW5ub3QgdXNlIHRoYXQgaW4gV2luZG93czogKmM6L3VzZXJzL25hbWUvZG93bmxvYWRzKi4gSXQncyBhIGhpc3RvcmljYWwgYW5vbWFseSAtLSBhc2sgQmlsbCBHYXRlcy4uLiBPbiBNYWNPUywgc2luY2UgaXQgaXMgYmFzZWQgb24gVW5peCwgd2UgdXNlIHRoZSBzdGFuZGFyZCBVbml4IHBhdGggc2VwYXJhdG9yOiB0aGUgZm9yd2FyZCBzbGFzaC4gQW5kLCBvbiBVbml4LCB0aGVyZSBhcmUgbm8gZHJpdmUgbGV0dGVycywgc28gYSBwYXRoIG9uIFVuaXggb3IgTWFjT1MgbmV2ZXIgc3RhcnRzIHdpdGggKmM6XFwqLgoKIyMjIFJlbGF0aXZlIHZzIEFic29sdXRlIFBhdGhzCgpSYXRoZXIgdGhhbiBzcGVjaWZ5aW5nIGEgcGF0aCB0byBhIGZpbGUsIGl0IGlzIG9mdGVuIGJldHRlciB0byBjcmVhdGUgYW4gUiBQcm9qZWN0IGFuZCBwbGFjZSBhbGwgZGF0YSBmaWxlcyBpbnRvIHRoZSBwcm9qZWN0IGZvbGRlci4gU2VlIExlc3NvbiBbNi4yMDIgV29ya2luZyB3aXRoIFIgUHJvamVjdHNdKGh0dHA6Ly9hcnRpZmljaXVtLnVzL2xlc3NvbnMvMDYuci9sLTYtMjAyLXItcHJvamVjdHMvbC02LTIwMi5odG1sKS4KCiMjIyBTdHJpbmdzIHZzIEZhY3RvcnMKClRoZSBwYXJhbWV0ZXIgPGNvZGU+c3RyaW5nc0FzRmFjdG9yczwvY29kZT4gaXMgdmVyeSBpbXBvcnRhbnQgd2hlbiByZWFkaW5nIHRleHQgZmlsZXMgdGhhdCBjb250YWluLCB3ZWxsLCB0ZXh0LiBCeSBkZWZhdWx0LCBhbnkgY29sdW1uIHdpdGggdGV4dCB3aWxsIGJlIGNvbnZlcnRlZCB0byBhICJmYWN0b3IiIC0tIFIncyB3YXkgdG8gcmVwcmVzZW50aW5nIGNhdGVnb3JpY2FsIHZhcmlhYmxlcy4gVGhhdCdzIG1vc3Qgb2Z0ZW4gbm90IHdoYXQgaXMgZGVzaXJlZCwgdW5sZXNzIHRoZSBjb2x1bW4gaXMgaW5kZWVkIGEgY2F0ZWdvcmljYWwgdmFyaWFibGVzIHN1Y2ggYXMgKmdlbmRlciosICpqb2IgdGl0bGUqLCBhbW9uZyBtYW55IG90aGVycy4gRmFjdG9yIHZhcmlhYmxlcyBhcmUgdmVyeSBpbXBvcnRhbnQgZm9yIG1hbnkgc3RhdGlzdGljYWwgYW5kIGRhdGEgbWluaW5nIGZ1bmN0aW9ucyBpbiBSIGJ1dCBtb3N0IG9mdGVuIHdlIHdhbnQgdG8gcmVhZCB0ZXh0IGNvbHVtbnMgYXMgc3RyaW5ncy4gQ2hlY2sgb3V0IHRoZSBkaWZmZXJlbmNlIGluIHRoZSBjb2RlIGJlbG93OyB0aGUgPGNvZGU+Y2xhc3MoKTwvY29kZT4gZnVuY3Rpb24gZGlzcGxheXMgYSB2YXJpYWJsZSdzICJkYXRhIHR5cGUiIGluIFIuCgpgYGB7cn0KYmV2REYgPC0gcmVhZC5jc3YoZm4sIGhlYWRlciA9IFRSVUUsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKc3RyKGJldkRGKQpoZWFkKGJldkRGKQpgYGAKCmBgYHtyfQpiZXZERiA8LSByZWFkLmNzdihmbiwgaGVhZGVyID0gVFJVRSwgc3RyaW5nc0FzRmFjdG9ycyA9IFRSVUUpCnN0cihiZXZERikKYGBgCgpOb3RlIGhvdyAqTWFudWZhY3R1cmVyKiwgYXMgYW4gZXhhbXBsZSwgaXMgbm93IGEgZmFjdG9yIHZhcmlhYmxlIHJhdGhlciB0aGFuIGEgc3RyaW5ncyAoYSAqY2hhcmFjdGVyKiBpbiBSKS4KCkl0J3MgZ2V0cyB0cmlja3kgaWYgeW91IGhhdmUgc29tZSBjb2x1bW5zIHRoYXQgYXJlIGZhY3RvcnMgYW5kIG90aGVyIHRoYXQgYXJlIHRleHQgKHN0cmluZ3MpLiBOb3csIHlvdSBoYXZlIHRvIHJlYWQgdGhlbSBhcyBzdHJpbmdzIGFuZCBjb252ZXJ0IHRob3NlIGNvbHVtbnMgdGhhdCB5b3Ugd2FudCBhcyBmYWN0b3JzIGV4cGxpY2l0eSB1c2luZyA8Y29kZT5hcy5mYWN0b3I8L2NvZGU+LgoKYGBge3IgZXZhbD1GQUxTRX0KYmV2REYgPC0gcmVhZC5jc3YoZm4sIGhlYWRlciA9IFRSVUUsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKYmV2REYkTWFudWZhY3R1cmVyIDwtIGFzLmZhY3RvcihiZXZERiRNYW51ZmFjdHVyZXIpCnN0cihiZXZERikKYGBgCgpOb3cgKkNlcmVhbC5OYW1lKiBpcyB0ZXh0IHdoaWxlICpNYW51ZmFjdHVyZXIqIGlzIGEgZmFjdG9yLgoKWW91IGNvdWxkIGhhdmUgc3BlY2lmaWVkIHRoZSBkYXRhIHR5cGVzIHlvdSBuZWVkIHVzaW5nIHRoZSA8Y29kZT5jb2xDbGFzc2VzPC9jb2RlPiBwYXJhbWV0ZXJzIGFzIHNob3duIG5leHQuCgpgYGB7ciBldmFsPUZBTFNFfQpiZXZERiA8LSByZWFkLmNzdihmbiwgaGVhZGVyID0gVFJVRSwgc3RyaW5nc0FzRmFjdG9ycyA9IFRSVUUsCiAgICAgICAgICAgICAgICAgIGNvbENsYXNzZXMgPSBjKCJpbnRlZ2VyIiwiY2hhcmFjdGVyIiwiZmFjdG9yIiwiaW50ZWdlciIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlZ2VyIiwiZG91YmxlIiwiZG91YmxlIiwiaW50ZWdlciIsImludGVnZXIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaW50ZWdlciIpKQpzdHIoYmV2REYpCmBgYAoKVGhlIGF0b21pYyBtb2RlcyB0aGF0IHlvdSBjYW4gc3BlY2lmeSBpbiA8Y29kZT5jb2xDbGFzc2VzPC9jb2RlPiBhcmUgImxvZ2ljYWwiLCAiaW50ZWdlciIsICJudW1lcmljIiAoc3lub255bSAiZG91YmxlIiksICJjb21wbGV4IiwgImNoYXJhY3RlciIgYW5kICJyYXciLgoKSW5zdGVhZCBvZiA8Y29kZT5yZWFkLmNzdigpPC9jb2RlPiB5b3UgY291bGQgaGF2ZSB1c2VkIDxjb2RlPnJlYWQudGFibGUoKTwvY29kZT4gd2hpY2ggd291bGQgaGF2ZSB0aGUgc2FtZSBwYXJhbWV0ZXJzLCBidXQgaXQgd291bGQgYWxsb3cgeW91IHRvIHNwZWNpZnkgdGhlIGRlbGltaXRlciBleHBsaWNpdGx5LiBZb3UgYWxzbyB3aWxsIG5lZWQgdG8gc3BlY2lmeSB0aGUgJ3F1b3RlJyBjaGFyYWN0ZXIgZXhwbGljaXRseS4KCmBgYHtyIGV2YWw9RkFMU0V9CmJldkRGIDwtIHJlYWQudGFibGUoZm4sIHNlcCA9ICIsIiwgcXVvdGUgPSAiXCIiLAogICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFRSVUUsIHN0cmluZ3NBc0ZhY3RvcnMgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgIGNvbENsYXNzZXMgPSBjKCJpbnRlZ2VyIiwiY2hhcmFjdGVyIiwiZmFjdG9yIiwiaW50ZWdlciIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImludGVnZXIiLCJkb3VibGUiLCJkb3VibGUiLCJpbnRlZ2VyIiwiaW50ZWdlciIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImludGVnZXIiKSkKc3RyKGJldkRGKQpgYGAKCiMjIyBMb2FkaW5nIENTViBGaWxlcyBmcm9tIGEgVVJMCgpPZnRlbiBhIENTViBmaWxlIGlzIGxvY2F0ZWQgb24gYSBVUkwgKG9uIHRoZSB3ZWIpIGluc3RlYWQgb2YgYSBsb2NhbCBmaWxlLiBSIGFsc28gYWxsb3dzIFVSTHMgdG8gYmUgc3BlY2lmaWVkIGluc3RlYWQgb2YgYSBmaWxlbmFtZSwgYXMgbG9uZyBhcyBpdCBzdGFydHMgd2l0aCBcXzxodHRwOi8vXz4uIE5vdGUgdGhhdCBSIGNhbm5vdCBhY2Nlc3MgZmlsZXMgYmVoaW5kIHBheXdhbGxzIG9yIHVzaW5nIFNTTCAkX2h0dHBzOi8vXyQuIFRoZSAqKlJDdXJsKiogcGFja2FnZSBjb250YWlucyBtYW55IGFkZGl0aW9uYWwgZnVuY3Rpb25zIGZvciBtYW5hZ2luZyBVUkxzIGFuZCBkYXRhIG9uIHRoZSB3ZWIuCgpBcyBhbiBhbHRlcm5hdGl2ZSwgeW91IGNhbiB1c2UgPGNvZGU+ZG93bmxvYWQuZmlsZSgpPC9jb2RlPiB0byBleHBsaWNpdGx5IGRvd25sb2FkIGEgZmlsZSB0aHJvdWdoIGEgVVJMIHVzaW5nIEhUVFAgb3IgRlRQLiBUaGlzIG1pZ2h0IGJlIG1vcmUgZWZmaWNpZW50IHRoYW4gcmV0cmlldmluZyB0aGUgZGF0YSBvdmVyIGEgbmV0d29yayBtdWx0aXBsZSB0aW1lcywgcGFydGljdWxhcmx5IGlmIHRoZSBmaWxlIGlzIHZlcnkgbGFyZ2UuIE5vdCBvbmx5IGlzIHRoYXQgcG90ZW50aWFsbHkgcXVpdGUgc2xvdywgaXQgYWxzbyB1c2VzIHByZWNpb3VzIG5ldHdvcmsgYmFuZHdpZHRoIG9yIGNvc3QgbW9uZXkgaWYgdGhlIGNvbm5lY3Rpb24gaXMgbWV0ZXJlZC4KCmBgYHtyfQp1cmwgPC0gImh0dHBzOi8vZHJpdmUuZ29vZ2xlLmNvbS91Yz9pZD0xZFl2YkkyRDg1QVFJSGE4eWVUTC1Tek1FalhYbnF3cE8iCmJldkRGIDwtIHJlYWQuY3N2KHVybCwgaGVhZGVyID0gVFJVRSwgc3RyaW5nc0FzRmFjdG9ycyA9IFRSVUUpCnN0cihiZXZERikKYGBgCgo+IFRvIG1ha2UgYSB0ZXh0IG9yIGFueSBvdGhlciBkYXRhIGZpbGUgYWNjZXNzaWJsZSBvbiBHb29nbGUgRHJpdmUsIGdlbmVyYXRlIGEgc2hhcmUgbGluayB3aXRoIHBlcm1pc3Npb25zIHNldCBzbyAqQW55b25lIHdpdGggbGluayBjYW4gdmlldyB0aGUgZmlsZSouIFRoZSBsaW5rIHdpbGwgbG9vayBsaWtlIHRoaXM6IFxfPGh0dHBzOi8vZHJpdmUuZ29vZ2xlLmNvbS9maWxlL2QvZmM1ZTAzOGMzOGE1NzAvdmlldz91c3A9c2hhcmluZ18+LiBDb3B5IHRoZSBmaWxlIGlkZW50aWZpZXIgKmZjNWUwMzhjMzhhNTcwKiBhbmQgYWRkIGl0IHRvIHRoZSBlbmQgb2YgdGhpcyBVUkw6IFxfPGh0dHBzOi8vZHJpdmUuZ29vZ2xlLmNvbS91Yz9pZD1fPi4gVGhlIGNvbXBsZXRlIFVSTCBpczogXF88aHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL3VjP2lkPWZjNWUwMzhjMzhhNTcwXz4KCiMjIyBDb21wcmVzc2VkIEZpbGVzCgpUZXh0IGZpbGVzIGFyZSBvZnRlbiBjb21wcmVzc2VkIHRvIHNhdmUgc3BhY2U7IGl0IGNhbiByZWR1Y2UgdGhlIHNpemUgb2YgYSBmaWxlIGRvd24gdG8gMTAtMjAlIG9mIGl0cyBvcmlnaW5hbCBzaXplLiBUaGlzIG5vdCBvbmx5IHNhdmVzIHNwYWNlIG9uIGRpc2ssIGl0IGFsc28gbWFrZXMgZG93bmxvYWRpbmcgb3ZlciBhIG5ldHdvcmsgbXVjaCBmYXN0ZXIuIENvbXByZXNzZWQgZmlsZXMgb2Z0ZW4gaGF2ZSBhICouY3N2LnppcCosICouemlwKiwgb3IgYSAqLmd6KiBleHRlbnNpb24uIEZvcnR1bmF0ZWx5LCBSIGF1dG9tYXRpY2FsbHkgdW5jb21wcmVzc2VzIChleHBhbmRzKSBmaWxlcyBpZiBpdCByZWNvZ25pemVzIHRoZSBleHRlbnNpb24uIElmIGl0IGNhbid0LCB0aGVuIGVpdGhlciB1bmNvbXByZXNzIG9uIHlvdXIgb3BlcmF0aW5nIHN5c3RlbSBvciB1c2UgdGhlIDxjb2RlPnVuemlwKCk8L2NvZGU+IGZ1bmN0aW9uLgoKYGBge3J9CmZuIDwtICJDZXJlYWxEYXRhLnppcCIKCmJldkRGIDwtIHJlYWQuY3N2KHVuemlwKGZuKSwgaGVhZGVyID0gVFJVRSwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpgYGAKCiMjIyBUaGUgKipyZWFkcioqIFBhY2thZ2UKCkNvbXBhcmVkIHRvIHRoZSBlcXVpdmFsZW50IEJhc2UgUiBmdW5jdGlvbnMsIHRoZSBmdW5jdGlvbnMgaW4gKipyZWFkcioqIGNhbiBiZSBhYm91dCAxMHggZmFzdGVyIChhbHRob3VnaCwgdHJ1dGggYmUgdG9sZCwgSSBvZnRlbiBmb3VuZCB0aGUgQmFzZSBSIGZ1bmN0aW9ucyB0byBiZSBmYXN0ZXIpLiBUaGV5IGhhdmUgYW5vdGhlciBhZHZhbnRhZ2UgdGhvdWdoOiB0aGV5IGJyaW5nIGNvbnNpc3RlbmN5IHRvIGltcG9ydGluZyBmdW5jdGlvbnMsIHRoZXkgcHJvZHVjZSBkYXRhIGZyYW1lcyBpbiBhICpkYXRhLnRhYmxlKiBmb3JtYXQgKHJhdGhlciB0aGFuIGRhdGEgZnJhbWUpIHdoaWNoIGFyZSBlYXNpZXIgdG8gdmlldyBmb3IgbGFyZ2UgZGF0YSBzZXRzLCB0aGUgZGVmYXVsdCBzZXR0aW5ncyByZW1vdmUgdGhlIHByb2JsZW1zIHdpdGggc3RyaW5ncyBhbmQgZmFjdG9ycywgYW5kIHRoZXkgaGF2ZSBhIG1vcmUgZmxleGlibGUgY29sdW1uIHNwZWNpZmljYXRpb24uCgpUbyBkZW1vbnN0cmF0ZSwgd2UgY2FuIHVzZSA8Y29kZT5yZWFkX2NzdigpPC9jb2RlPiB3aGljaCBpcyBlcXVpdmFsZW50IHRvIEJhc2UgUidzIDxjb2RlPnJlYWQuY3N2KCk8L2NvZGU+IGZ1bmN0aW9uLiBIb3dldmVyLCBub3RpY2UgaG93IDxjb2RlPnJlYWRfY3N2KCk8L2NvZGU+IG1haW50YWlucyB0aGUgZnVsbCB2YXJpYWJsZSBuYW1lcy4KCmBgYHtyIGV2YWw9Rn0KbGlicmFyeShyZWFkcikKCmZuIDwtICJDZXJlYWxEYXRhLmNzdiIKCmNlckRGIDwtIHJlYWRfY3N2KGZuKQpoZWFkKGNlckRGKQpgYGAKCk9uZSBvdGhlciBiZW5lZml0IG9mIDxjb2RlPnJlYWRfY3N2KCk8L2NvZGU+IGlzIGl0cyA8Y29kZT5tYXg8L2NvZGU+IHBhcmFtZXRlcnMgd2hpY2ggYWxsb3dzIHlvdSB0byBzZXQgaG93IG1hbnkgbGluZXMgaXQgd2lsbCByZWFkIGF0IG1vc3QgLS0gcmF0aGVyIHRoYW4gcmVhZGluZyB0aGUgZW50aXJlIGZpbGUuIFRoaXMgY2FuIGJlIHVzZWZ1bCBmb3IgInNhbXBsaW5nIiBqdXN0IGEgcG9ydGlvbiBvZiB0aGUgZmlsZS4KCiMjIFdyaXRpbmcgQ1NWIEZpbGVzCgpUbyBzYXZlIGEgZGF0YSBmcmFtZSBhbmQgaXRzIHJvd3MgdG8gYSBDU1YgZmlsZSwgdXNlIHRoZSBmdW5jdGlvbiA8Y29kZT53cml0ZS5jc3YoKTwvY29kZT4gZnJvbSBCYXNlIFIuIEluIHRoZSBjb2RlIGV4YW1wbGUgYmVsb3csIHdlIHdyaXRlIHRoZSBidWlsdC1pbiBkYXRhIGZyYW1lICp3YXJwYnJlYWtzKiB0byBhIENTViBmaWxlLgoKYGBge3J9CmZuIDwtICJ3ZWF2aW5nLmNzdiIKCmJldkRGIDwtIHdyaXRlLmNzdih3YXJwYnJlYWtzLCBmaWxlID0gZm4pCmBgYAoKIyMgSW1wb3J0aW5nIEV4Y2VsIEZpbGVzCgpFeGNlbCBmaWxlcyAoaGF2aW5nIGEgKi54bHMqIG9yICoueGxzeCopIGFyZSBhbm90aGVyIGNvbW1vbiBzb3VyY2Ugb2YgZGF0YS4gdG8gaW1wb3J0IGRhdGEgZnJvbSBFeGNlbCB5b3Ugd2lsbCBuZWVkIHRoZSAqKnJlYWR4bCoqIHBhY2thZ2U7IEJhc2UgUiBjYW5ub3QgcmVhZCB0aGVtLiBJZiBwb3NzaWJsZSwgaXQgaXMgb2Z0ZW4gZWFzaWVyIHRvIHdyaXRlIHRoZSBkYXRhIGFzIGEgQ1NWIGZyb20gRXhjZWwgKHVzZSB0aGUgKkV4cG9ydCogZmVhdHVyZSBvZiBFeGNlbCkuCgpXaGlsZSB0aGVyZSBhcmUgc2V2ZXJhbCBwYWNrYWdlcyBmb3IgcmVhZGluZyBFeGNlbCBmaWxlcywgd2UgcmVjb21tZW5kIGVpdGhlciAqKnhsc3gqKiBvciB0aGUgbmV3ZXN0IHBhY2thZ2UgZnJvbSBIYWRsZXkgV2lja2hhbSBhdCBSIFN0dWRpbywgKipyZWFkeGwqKi4gVW5saWtlIHRoZSBmdW5jdGlvbnMgaW4gKipyZWFkeGwqKiB0aGF0IGFyZSBidWlsdCBpbiBDKysgKGFuZCB0aHVzIGFyZSBibGF6aW5nIGZhc3QpLCB0aGUgKip4bHN4KiogcGFja2FnZSBpcyBidWlsdCBpbiBKYXZhLiBUaGF0IG1lYW5zIGl0IHJlcXVpcmVzIGEgSmF2YSBWTSBhbmQgYWRkaXRpb25hbCBzZXQgdXAsIHNvIHdlIGRvIG5vdCByZWNvbW1lbmQgaXRzIHVzZS4KClRoZSBjb2RlIHNhbXBsZSBiZWxvdyB1c2VzIHRoZSA8Y29kZT5yZWFkLnhsc3goKTwvY29kZT4gZnVuY3Rpb24gZnJvbSB0aGUgKipyZWFkeGwqKiBwYWNrYWdlLgoKYGBge3J9CmxpYnJhcnkocmVhZHhsKQoKZm4gPC0gIkNlcmVhbERhdGEueGxzeCIKCmJldkRGIDwtIHJlYWRfZXhjZWwoZm4sIHNoZWV0ID0gMSkKaGVhZChiZXZERikKYGBgCgpUaGUgPGNvZGU+c2hlZXQ8L2NvZGU+IHBhcmFtZXRlciBzcGVjaWZpZXMgd2hpY2ggd29ya3NoZWV0IHRvIHJlYWQgZnJvbSB0aGUgRXhjZWwgd29ya2Jvb2s7IGl0IGlzIGVpdGhlciB0aGUgbnVtYmVyIG9mIHRoZSB3b3Jrc2hlZXQgb3IgaXRzIG5hbWUuCgojIyBJbXBvcnRpbmcgUiBPYmplY3QgRmlsZXMKCkNyZWF0aW5nIGxhcmdlIGFuZCBjb21wbGV4IGRhdGEgZnJhbWVzIGlzIGNvbXB1dGUtaW50ZW5zaXZlLiBIb3dldmVyLCBhZnRlciBhbiBSIHNlc3Npb24gdGVybWluYXRlcywgYWxsIG9iamVjdHMgaW4gbWVtb3J5IGdvIGF3YXkuIFRoZSBzb2x1dGlvbiBpcyB0byBzYXZlIHRoZW0gaW4gYW4gUiBvYmplY3QgZmlsZSB1c2luZyA8Y29kZT5zYXZlKCk8L2NvZGU+IGZ1bmN0aW9uIGFzIGEgKi5SRGF0YSogZmlsZS4gVGhpcyBpcyBhbHNvIHVzZWZ1bCBmb3IgYXJjaGl2aW5nLgoKVG8gbG9hZCBzdWNoIGFyY2hpdmVkIG9iamVjdHMgYmFjayBpbnRvIHlvdXIgY3VycmVudCBSIHdvcmtzcGFjZSwgdXNlIHRoZSA8Y29kZT5sb2FkKCk8L2NvZGU+IGZ1bmN0aW9uLiBUaGUgY29kZSBiZWxvdyBzaG93cyBob3cgdG8gc2F2ZSBhbmQgdGhlbiBob3cgdG8gbG9hZCBiYWNrIGFuIG9iamVjdC4gUnVuIHRoZSBmaXJzdCBjaHVuaywgdGhlbiByZXN0YXJ0IFIgLS0gdGhlIG9iamVjdCB3aWxsIGJlICJnb25lIiwgdGhlbiBsb2FkIHRoZSBvYmplY3QgYW5kIHlvdSBjYW4gYWNjZXNzIGl0IGFnYWluLgoKYGBge3J9CmZuIDwtICJDZXJlYWxEYXRhLmNzdiIKZm5EIDwtICJiZXZERi5SRGF0YSIKCmJldkRGIDwtIHJlYWQuY3N2KGZuLCBoZWFkZXIgPSBUUlVFLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmhlYWQoYmV2REYpCgojIHNhdmUgdGhlIGJldkRGIG9iamVjdCB0byBhIGJpbmFyeSBSRGF0YSBmaWxlCnNhdmUoYmV2REYsIGZpbGUgPSBmbkQpCmBgYAoKU28sIG5vdywgcmVzdGFydCB5b3VyIFIgU2Vzc2lvbiBvciBjbGVhciB0aGUgPGNvZGU+YmV2REY8L2NvZGU+IGRhdGEgZnJhbWUgZnJvbSBtZW1vcnkgd2l0aCA8Y29kZT5ybSgpPC9jb2RlPi4KCmBgYHtyfQpybShiZXZERikKZ2ModmVyYm9zZSA9IFRSVUUpCmBgYAoKVG8gInByb3ZlIiB0aGF0IGl0IGlzIGdvbmUsIGFjY2VzcyB0aGUgb2JqZWN0LiBZb3Ugc2hvdWxkIGdldCB0aGUgZXJyb3IgKioiRXJyb3IgaW4gaGVhZChiZXZERikgOiBvYmplY3QgJ2JldkRGJyBub3QgZm91bmQiKiouCgpgYGB7ciBldmFsPUZBTFNFfQpoZWFkKGJldkRGKQpgYGAKCkxldCdzIGxvYWQgaXQgYmFjayAoYnV0IHdpdGhvdXQgcmVidWlsZGluZyBpdCksIGRpcmVjdGx5IGZyb20gaXRzICJiaW5hcnkiIHJlcHJlc2VudGF0aW9uLiBUaGlzIGlzIGV4dHJlbWVseSBmYXN0LiBUaGUgZnVuY3Rpb24gPGNvZGU+bG9hZCgpPC9jb2RlPiByZWNyZWF0ZXMgYWxsIFIgb2JqZWN0cyBjb250YWluZWQgaW4gdGhlIGZpbGUuCgpgYGB7cn0KZm5EIDwtICJiZXZERi5SRGF0YSIKbG9hZChmbkQpCmBgYAoKQW5kIHRvIHByb3ZlIHRoYXQgaXQgaXMgdGhlcmUsIGxldCdzIGFjY2VzcyBpdC4uLgoKYGBge3J9CmhlYWQoYmV2REYpCmBgYAoKVm9pbGEuLi4gdGhlIG9iamVjdCBpcyBiYWNrLgoKIyMgT3RoZXIgRGF0YSBGaWxlIEZvcm1hdHMKCk90aGVyIHRoYW4gdGV4dCBhbmQgUiBiaW5hcnkgZmlsZXMsIFIgY2FuIGFsc28gaW1wb3J0IGRhdGEgZnJvbSBtYW55IHN0YXRpc3RpY2FsIHByb2dyYW1zIGFuZCBhbmFseXRpY3MgcGxhdGZvcm1zIHVzaW5nIHRoZSBmdW5jdGlvbnMgb2YgdGhlIFsqKmZvcmVpZ24qKiBwYWNrYWdlXShodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvZm9yZWlnbi92ZXJzaW9ucy8wLjgtODEpLiBBbW9uZyBtYW55IG90aGVycywgdGhlICoqZm9yZWlnbioqIHBhY2thZ2Ugc3VwcG9ydHMgaW1wb3J0aW5nIGZyb206IFN0YXRhLCBTMywgU1BTUywgTWluaXRhYiwgYW5kIFNBUy4KCkluIGFkZGl0aW9uLCBSIHN1cHBvcnRzIHJlYWRpbmcgYW5kIHdyaXRpbmcgZGF0YSB0byBhbmQgZnJvbSBYTUwgZmlsZXMsIGFzIHdlbGwgYXMgcmVsYXRpb25hbCAoYW5kIE5vU1FMKSBkYXRhYmFzZXMuIEhvd2V2ZXIsIGltcG9ydGluZyBmb3IgdGhlc2Ugc291cmNlcyBpcyBiZXlvbmQgdGhlIHNjb3BlIG9mIHRoaXMgdHV0b3JpYWwuCgpUaGUgdGFibGUgYmVsb3cgbGlzdHMgdGhlIG1vc3QgaW1wb3J0YW50IGZ1bmN0aW9ucyBmcm9tIHRoZSAqKmZvcmVpZ24qKiBwYWNrYWdlLgoKfCBGdW5jdGlvbiAgICAgICAgICAgICAgICAgIHwgQ29tbW9uIFVzZSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8Oi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfDotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18CnwgPGNvZGU+ZGF0YS5yZXN0b3JlPC9jb2RlPiB8IFJlYWQgYW4gUzMgQmluYXJ5IEZpbGUgICAgICAgICAgICAgICAgIHwKfCA8Y29kZT5yZWFkLmRiZjwvY29kZT4gICAgIHwgUmVhZCBhIERCRiBGaWxlICAgICAgICAgICAgICAgICAgICAgICAgfAp8IDxjb2RlPnJlYWQuZHRhPC9jb2RlPiAgICAgfCBSZWFkIFN0YXRhIGJpbmFyeSBmaWxlcyAgICAgICAgICAgICAgICB8CnwgPGNvZGU+cmVhZC5tdHA8L2NvZGU+ICAgICB8IFJlYWQgYSBNaW5pdGFiIFBvcnRhYmxlIFdvcmtzaGVldCAgICAgIHwKfCA8Y29kZT5yZWFkLnNwc3M8L2NvZGU+ICAgIHwgUmVhZCBhbiBTUFNTIGRhdGEgZmlsZSAgICAgICAgICAgICAgICAgfAp8IDxjb2RlPnJlYWQuc3lzdGF0PC9jb2RlPiAgfCBPYnRhaW4gYSBEYXRhIEZyYW1lIGZyb20gYSBTeXN0YXQgRmlsZSB8CnwgPGNvZGU+cmVhZC54cG9ydDwvY29kZT4gICB8IFJlYWQgYSBTQVMgWFBPUlQgRm9ybWF0IExpYnJhcnkgICAgICAgIHwKfCA8Y29kZT53cml0ZS5kYmY8L2NvZGU+ICAgIHwgV3JpdGUgYSBEQkYgRmlsZSAgICAgICAgICAgICAgICAgICAgICAgfAp8IDxjb2RlPndyaXRlLmR0YTwvY29kZT4gICAgfCBXcml0ZSBGaWxlcyBpbiBTdGF0YSBCaW5hcnkgRm9ybWF0ICAgICB8CgojIyBXb3JraW5nIHdpdGggdGhlIEZpbGUgU3lzdGVtCgpBbiBpbXBvcnRhbnQgZnVuY3Rpb24gZm9yIGNoZWNraW5nIGRpcmVjdG9yeSAoZm9sZGVyKSBvciBmaWxlIGV4aXN0ZW5jZSBpcyA8Y29kZT5maWxlLmV4aXN0cygpPC9jb2RlPi4gSXQgY2hlY2tzIGlmIHRoZSBmaWxlIG9yIGRpcmVjdG9yeSBleGlzdHMgYW5kIHJldHVybnMgVFJVRSBpZiBpdCBkb2VzLCBGQUxTRSBvdGhlcndpc2UuIFRoZSBmdW5jdGlvbjxjb2RlPmRpci5jcmVhdGUoKTwvY29kZT4gY3JlYXRlcyBhIGRpcmVjdG9yeSBpZiBpdCBkb2VzIG5vdCBleGlzdC4KClNlZSBMZXNzb24gWzYuNDAyIE5hdmlnYXRpbmcgdGhlIEZpbGUgU3lzdGVtIGluIFJdKGh0dHA6Ly9hcnRpZmljaXVtLnVzL2xlc3NvbnMvMDYuci9sLTYtNDAyLWZpbGVzeXN0ZW0tZnJvbS1yL2wtNi00MDIuaHRtbCkgZm9yIG1vcmUgaW5mb3JtYXRpb24gb24gd29ya2luZyB3aXRoIGZpbGVzLgoKIyMgU3VtbWFyeQoKVGhpcyB0dXRvcmlhbCBwcmVzZW50ZWQgbWVjaGFuaXNtcyBmb3IgaW1wb3J0aW5nIGRhdGEgZnJvbSB0d28gY29tbW9uIHRleHQgZmlsZXMgKENTViBhbmQgVFNWKSBhbmQgZnJvbSBFeGNlbC4gSXQgYWxzbyBzaG93ZWQgaG93IHRvIHNhdmUgZGF0YSBmcmFtZXMgaW50byBhIENTViB0ZXh0IGZpbGUuIEFkZGl0aW9uYWxseSwgaXQgZGVtb25zdHJhdGVkIGhvdyB0byBzYXZlIGluLW1lbW9yeSBvYmplY3RzIGluIHRoZWlyIGJpbmFyeSByZXByZXNlbnRhdGlvbiBmb3IgYXJjaGl2aW5nIGFuZCBmdXR1cmUgcmFwaWQgcmUtY3JlYXRpb24uCgpBc2lkZSBmcm9tIGltcG9ydGFudCBkYXRhIGZyb20gQ1NWIGFuZCBFeGNlbCBmaWxlcywgZGF0YSBpcyBhbHNvIG9mdGVuIGVuY29kZWQgaW4gWE1MIG9yIHN0b3JlZCBpbiByZWxhdGlvbmFsIGRhdGFiYXNlcy4gT3RoZXIgdHV0b3JpYWxzIHNob3cgaG93IHRvIGFjY2VzcyBkYXRhIGluIHRob3NlIHN0b3Jlcy4KCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMgVHV0b3JpYWwKClRoZSB2aWRlbyB0dXRvcmlhbCBkZW1vbnN0cmF0ZXMgdGhlIGNvbnN0cnVjdHMgaW50cm9kdWNlZCBpbiB0aGlzIGxlc3Nvbi4KCmBgYHs9aHRtbH0KPGlmcmFtZSBzcmM9Imh0dHBzOi8vcGxheWVyLnZpbWVvLmNvbS92aWRlby85MDM3MTgyMTk/dGl0bGU9MCZhbXA7YnlsaW5lPTAmYW1wO3BvcnRyYWl0PTAmYW1wO2JhZGdlPTAmYW1wO2F1dG9wYXVzZT0wJmFtcDtwbGF5ZXJfaWQ9MCZhbXA7YXBwX2lkPTU4NDc5IiB3aWR0aD0iNjQwIiBoZWlnaHQ9IjM2MCIgZnJhbWVib3JkZXI9IjEiIGFsbG93PSJhdXRvcGxheTsgZnVsbHNjcmVlbjsgcGljdHVyZS1pbi1waWN0dXJlIiB0aXRsZT0iNi4xMDYgLyBMb2FkaW5nIERhdGEgZnJvbSBGaWxlcyBpbnRvIFIiIGRhdGEtZXh0ZXJuYWw9IjEiPjwvaWZyYW1lPgpgYGAKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMgRmlsZXMgJiBSZXNvdXJjZXMKCmBgYHtyIHppcEZpbGVzLCBlY2hvPUZBTFNFfQp6aXBOYW1lID0gc3ByaW50ZigiTGVzc29uRmlsZXMtJXMtJXMuemlwIiwgCiAgICAgICAgICAgICAgICAgcGFyYW1zJGNhdGVnb3J5LAogICAgICAgICAgICAgICAgIHBhcmFtcyRudW1iZXIpCgp0ZXh0QUxpbmsgPSBwYXN0ZTAoIkFsbCBGaWxlcyBmb3IgTGVzc29uICIsIAogICAgICAgICAgICAgICBwYXJhbXMkY2F0ZWdvcnksIi4iLHBhcmFtcyRudW1iZXIpCgojIGRvd25sb2FkRmlsZXNMaW5rKCkgaXMgaW5jbHVkZWQgZnJvbSBfaW5zZXJ0MkRCLlIKa25pdHI6OnJhd19odG1sKGRvd25sb2FkRmlsZXNMaW5rKCIuIiwgemlwTmFtZSwgdGV4dEFMaW5rKSkKYGBgCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIFJlZmVyZW5jZXMKCk5vbmUgeWV0LgoKIyMgRXJyYXRhCgpbTGV0IHVzIGtub3ddKGh0dHBzOi8vZm9ybS5qb3Rmb3JtLmNvbS8yMTIxODcwNzI3ODQxNTcpe3RhcmdldD0iX2JsYW5rIn0uCg==