Prerequisites
This lesson presumes that you understand the structure of an XML document. If you are not familiar with XML, consult this lesson first.
Introduction
XML is a common means to externalize structured data an is commonly used for data interchange between systems and organizations. There are many standard XML languages for expressing data in specific domains, such as finance, agriculture, publishing, among others. It is also often used as a standard for configuration files and for web content.
Being able to extract data from XML is an important skill that every data programmers must master. This lesson explains how to use the functions from the XML package to extract data from XML files (also often called XML documents) in R. Other programming languages have very similar mechanisms and the skills learned in this lesson can be transferred to other languages, such as JavaScript, Java, C++, Rust, and many others.
Most often the data extracted from XML is analyzed statistically or used for constructing machine learning models, as well as being added to relational databases. These use cases are outside the scope of this lesson.
Required Packages
To extract data from XML, requires minimally the following packages:
In addition, these packages contain functions that are often quite useful:
Note the XML is not the only package available for working with XML.
Loading an XML File
The process of extracting data starts by “parsing” the XML file from either a local folder or from a URL. The code below illustrates that for one of the included XML files (BookCatalog.xml). Download the file and run the code in your own R Notebook or R Script.
The code fragment below illustrates how to load and parse an XML document from a local file as well as from a URL.
library(XML)
xmlFile <- "BookCatalog.xml"
xmlURL <- "http://artificium.us/lessons/06.r/l-6-114-parse-xml-r-primer/BookCatalog.xml"
dom1 <- xmlParse(xmlFile)
dom2 <- xmlParse(xmlURL)
If you get the error “Error: XML content does not seem to be XML: ’’” then it is most likely that the file name, path, or URL are not correct.
Document Object Model (DOM)
The function xmlParse()
returns a reference to an internal tree of nodes that represents the document object model (DOM) for the XML. Processing of the nodes and extraction of data is done through that reference.
Alternatively, one could use the function xmlTreeParse()
which will also return a DOM but one that is represented as an R data structure rather than a C data structure. While xmlParse()
is more efficient and uses less memory, xmlTreeParse()
has the benefit of element access using the $ operator. In practice, xmlParse()
is most commonly used, though.
The variable dom (or to whichever variable you assigned the return value of xmlParse()
) points to an in-memory representation of the XML tree. All access to the XML elements is via that pointer.
Processing an XML through its DOM requires that the XML is loaded completely into memory. Naturally, this is only feasible when sufficient memory is available. For very large XML files, another approach is available for extraction: SAX. This is beyond the scope of this tutorial.
Validation
By default, xmlParse()
does not validate the file against any DTD or XML Schema; it only checks whether the XML is well-formed. To ensure that the XML conforms to the rules of a DTD or XML Schema, the parameter validate=T must be specified. Of course, this parameter is only meaningful if there is a DTD or Schema; if there isn’t and validate=T is specified then an error will result.
The XML file pagevisits.xml contains a DTD and therefore parsing with validation is possible.
library(XML)
xmlFile <- "pagevisits.xml"
dom <- xmlParse(xmlFile, validate=T)
Other useful parameters for xmlParse()
include:
- trim – a Boolean indicating whether to strip leading and trailing whitespace from values
- getDTD – a Boolean flag indicating whether the DTD (both internal and external) should be returned along with the nodes
- isURL – a Boolean indicating whether the document path is a URL; this is not strictly required if the URL starts with a common protocol such as http://
If you need to process HTML documents (a type of XML document), then the function htmlParse()
is preferable.
Pretty Tables with “kable”
The code below demonstrates the use of the “knitr::kable()” package. First, we are extracting the title and the author of each book into a data frame and we use the kableExtra package to “pretty print” the data frames.
library(XML)
xmlURL <- "http://artificium.us/lessons/06.r/l-6-114-parse-xml-r-primer/BookCatalog.xml"
xmlDOM <- xmlParse(xmlURL, validate = F)
r <- xmlRoot(xmlDOM)
# number of <book> nodes
n <- xmlSize(r)
df.books <- data.frame(
title = character(n),
author = character(n)
)
for (i in 1:n)
{
## access the ith book node
aBook <- r[[i]]
## extract title (child 2) and author (child 1) from
## the book node
theTitle <- xmlValue(aBook[[2]])
theAuthor <- xmlValue(aBook[[1]])
## store values in the ith row of the data frame
df.books[i,1] <- theTitle
df.books$author[i] <- theAuthor
}
head(df.books)
## title author
## 1 XML Developer's Guide Gambardella, Matthew
## 2 Midnight Rain Ralls, Kim
## 3 Maeve Ascendant Corets, Eva
## 4 Oberon's Legacy Corets, Eva
## 5 The Sundered Grail Corets, Eva
## 6 Lover Birds Randall, Cynthia
Example I
Note that only the first six rows of the data frame are displayed.
library(kableExtra)
df.books[1:6,] %>%
kbl() %>%
kable_paper("hover", full_width = F)
title
|
author
|
XML Developer’s Guide
|
Gambardella, Matthew
|
Midnight Rain
|
Ralls, Kim
|
Maeve Ascendant
|
Corets, Eva
|
Oberon’s Legacy
|
Corets, Eva
|
The Sundered Grail
|
Corets, Eva
|
Lover Birds
|
Randall, Cynthia
|
Example 2
This example uses a different format style and also prints the table over the entire width of the document.
df.books[1:6,] %>%
kbl(caption = "Books by Title with Author") %>%
kable_classic(full_width = T, html_font = "Cambria")
Table 1: Books by Title with Author
title
|
author
|
XML Developer’s Guide
|
Gambardella, Matthew
|
Midnight Rain
|
Ralls, Kim
|
Maeve Ascendant
|
Corets, Eva
|
Oberon’s Legacy
|
Corets, Eva
|
The Sundered Grail
|
Corets, Eva
|
Lover Birds
|
Randall, Cynthia
|
Tutorial I
The content from the lesson to this point is narrated in the code walk below.
XPath
A simple and more elegant, albeit less flexible and perhaps less efficient, way is to use XPath expressions to access elements and attributes. We will redo the above extractions using XPath rather than indexed node access.
Let’s start first by demonstrating how to execute an XPath query on an XML document. After the XML is parsed using xmlParse()
, the XPath expression is executed using xpathSApply()
. The function returns a list (not a vector) of all nodes that match the XPath path expression.
library(XML)
xmlDoc <- xmlParse("SimpleXML.xml", validate=F)
# XPath expression to get titles of all book nodes
xpathExpr <- "//book/title"
# execute/apply the XPath query
rs <- xpathSApply(xmlDoc, xpathExpr)
# print the list of matching elements
print(rs)
## [[1]]
## <title>XML Developer's Guide</title>
##
## [[2]]
## <title>Midnight Rain</title>
To get the values of the elements, add “xmlValue” as a parameter as shown below and xpathSApply()
returns a vector of element values.
library(XML)
xmlDoc <- xmlParse("SimpleXML.xml", validate=F)
# XPath expression to get titles of all book nodes
xpathExpr <- "//book/title"
# execute/apply the XPath query and extract values
rs <- xpathSApply(xmlDoc, xpathExpr, xmlValue)
print(rs)
## [1] "XML Developer's Guide" "Midnight Rain"
The example below extracts the title, price, and edition using XPath expressions and adds them to a data frame.Notice how we are no longer concerned about whether the optional <outofprint/>
element is part of a <book>
node or not. XPath simplifies access.
library(XML)
xmlDoc <- xmlParse("SimpleXML.xml", validate=F)
# number of <book> nodes
n <- xmlSize(root)
titles <- xpathSApply(xmlDoc,
"//book/title", xmlValue)
prices <- xpathSApply(xmlDoc,
"//book/price", xmlValue)
editions <- xpathSApply(xmlDoc,
"//book/@edition")
# create new data frame
df <- data.frame(title = titles,
price = as.numeric(prices),
edition = as.numeric(editions))
print(head(df,3))
## title price edition
## 1 XML Developer's Guide 349.00 3
## 2 Midnight Rain 5.95 1
Notice how we do not use xmlValue
when retrieving attribute values.
Missing Elements
The approach to check whether an element is present is a bit more difficult with XPath than indexed access. The function xpathSApply()
returns an empty list when the XPath expression has no matching elements. We can check whether a list is empty by finding its length (or size) using the function length()
. If it is empty, then its length is 0.
xpath <- "//book/genre"
# XPath expression should not return a value
rs <- xpathSApply(xmlDoc, xpath, xmlValue)
if (length(rs) == 0)
{
print("no genre")
}
## [1] "no genre"
XPath with Indexed Access
The aforementioned code presumes that every book has one title, one price, and an edition. If there are multiple titles or prices, or some do not exist, then a combination of node traversal and XPath would be necessary.
Let’s say we had a more complex XML that had multiple prices and we only wanted to extract the US prices (where the currency attributes is “US$”). Here’s what one of the nodes in the XML file SimpleXML-2.xml looks like:
<catalog>
<book id="bk101" edition="3">
<author>
<surname>Gambardella</surname>
<given>Matthew</given>
</author>
<title>XML Developer's Guide</title>
<outofprint />
<price currency="R$">349</price>
<price currency="US$">29.95</price>
<price currency="€">34.00</price>
</book>
...
</catalog>
There are multiple ways to solve this. One approach, of course, is to use XPath expressions that extract price values only when currency is “US$”. However, to demonstrate the mixing of indexed access and XPath, we will choose an approach the uses loops. This approach is generally slower (loops are slow, especially in R), but affords more flexibility.
library(XML)
xmlDoc <- xmlParse("SimpleXML-2.xml")
n <- xmlSize(root)
# get all <book> nodes from the XML
books <- xpathSApply(xmlDoc, "//book")
# iterate over the <book> nodes
for (i in 1:n)
{
# get the i-th book node
aBook <- books[[i]]
# use XPath to extract the <price> child elements
price <- xpathSApply(aBook,
"price[./@currency='US$']",
xmlValue)
print(price)
}
## [1] "29.95"
## [1] "5.95"
Summary of XML Functions
This section presents the most useful functions from the XML package. Naturally, the list is not exclusive and you should consult the documentation for the package for more information.
xmlValue
xmlSize
xmlRoot
xmlAttrs
xmlxpathSApply
xmlParse
xmlName
xmlChildren
Summary
The XML package provides numerous functions for extracting data from XML documents in R.
LS0tDQp0aXRsZTogIlByaW1lciBvbiBQYXJzaW5nIFhNTCB3aXRoIFIiDQpwYXJhbXM6DQogIGNhdGVnb3J5OiA2DQogIG51bWJlcjogMTE0DQogIHRpbWU6IDQ1DQogIGxldmVsOiBiZWdpbm5lcg0KICB0YWdzOiAicix4bWwseHBhdGgscHJpbWVyIg0KICBkZXNjcmlwdGlvbjogIkEgcHJpbWVyIG9uIGxvYWRpbmcgWE1MIGRvY3VtZW50cyBpbnRvIFINCiAgICAgICAgICAgICAgICBhbmQgcHJvY2Vzc2luZyB0aGUgZWxlbWVudHMuIg0KZGF0ZTogIjxzbWFsbD5gciBTeXMuRGF0ZSgpYDwvc21hbGw+Ig0KYXV0aG9yOiAiPHNtYWxsPk1hcnRpbiBTY2hlZGxiYXVlcjwvc21hbGw+Ig0KZW1haWw6ICJtLnNjaGVkbGJhdWVyQG5ldS5lZHUiDQphZmZpbGl0YXRpb246ICJOb3J0aGVhc3Rlcm4gVW5pdmVyc2l0eSINCm91dHB1dDogDQogIGJvb2tkb3duOjpodG1sX2RvY3VtZW50MjoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2xsYXBzZWQ6IGZhbHNlDQogICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZQ0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICB0aGVtZTogc3BhY2VsYWINCiAgICBoaWdobGlnaHQ6IHRhbmdvDQotLS0NCg0KLS0tDQp0aXRsZTogIjxzbWFsbD5gciBwYXJhbXMkY2F0ZWdvcnlgLmByIHBhcmFtcyRudW1iZXJgPC9zbWFsbD48YnIvPjxzcGFuIHN0eWxlPSdjb2xvcjogIzJFNDA1MzsgZm9udC1zaXplOiAwLjllbSc+YHIgcm1hcmtkb3duOjptZXRhZGF0YSR0aXRsZWA8L3NwYW4+Ig0KLS0tDQoNCmBgYHtyIGNvZGU9eGZ1bjo6cmVhZF91dGY4KHBhc3RlMChoZXJlOjpoZXJlKCksJy9SL19pbnNlcnQyREIuUicpKSwgaW5jbHVkZSA9IEZBTFNFfQ0KYGBgDQoNCiMjIFByZXJlcXVpc2l0ZXMNCg0KVGhpcyBsZXNzb24gcHJlc3VtZXMgdGhhdCB5b3UgdW5kZXJzdGFuZCB0aGUgc3RydWN0dXJlIG9mIGFuIFhNTCBkb2N1bWVudC4gSWYgeW91IGFyZSBub3QgZmFtaWxpYXIgd2l0aCBYTUwsIGNvbnN1bHQgdGhpcyBsZXNzb24gZmlyc3QuDQoNCi0gICBbODAuMTAxIEJhc2ljcyBvZiBYTUwgYW5kIERURF0oaHR0cDovL2FydGlmaWNpdW0udXMvbGVzc29ucy84MC54bWwvbC04MC0xMDEtYmFzaWMteG1sL2wtODAtMTAxLmh0bWwpDQoNCiMjIEludHJvZHVjdGlvbg0KDQpYTUwgaXMgYSBjb21tb24gbWVhbnMgdG8gZXh0ZXJuYWxpemUgc3RydWN0dXJlZCBkYXRhIGFuIGlzIGNvbW1vbmx5IHVzZWQgZm9yIGRhdGEgaW50ZXJjaGFuZ2UgYmV0d2VlbiBzeXN0ZW1zIGFuZCBvcmdhbml6YXRpb25zLiBUaGVyZSBhcmUgbWFueSBzdGFuZGFyZCBYTUwgbGFuZ3VhZ2VzIGZvciBleHByZXNzaW5nIGRhdGEgaW4gc3BlY2lmaWMgZG9tYWlucywgc3VjaCBhcyBmaW5hbmNlLCBhZ3JpY3VsdHVyZSwgcHVibGlzaGluZywgYW1vbmcgb3RoZXJzLiBJdCBpcyBhbHNvIG9mdGVuIHVzZWQgYXMgYSBzdGFuZGFyZCBmb3IgY29uZmlndXJhdGlvbiBmaWxlcyBhbmQgZm9yIHdlYiBjb250ZW50Lg0KDQpCZWluZyBhYmxlIHRvIGV4dHJhY3QgZGF0YSBmcm9tIFhNTCBpcyBhbiBpbXBvcnRhbnQgc2tpbGwgdGhhdCBldmVyeSBkYXRhIHByb2dyYW1tZXJzIG11c3QgbWFzdGVyLiBUaGlzIGxlc3NvbiBleHBsYWlucyBob3cgdG8gdXNlIHRoZSBmdW5jdGlvbnMgZnJvbSB0aGUgKipYTUwqKiBwYWNrYWdlIHRvIGV4dHJhY3QgZGF0YSBmcm9tIFhNTCBmaWxlcyAoYWxzbyBvZnRlbiBjYWxsZWQgWE1MIGRvY3VtZW50cykgaW4gUi4gT3RoZXIgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2VzIGhhdmUgdmVyeSBzaW1pbGFyIG1lY2hhbmlzbXMgYW5kIHRoZSBza2lsbHMgbGVhcm5lZCBpbiB0aGlzIGxlc3NvbiBjYW4gYmUgdHJhbnNmZXJyZWQgdG8gb3RoZXIgbGFuZ3VhZ2VzLCBzdWNoIGFzIEphdmFTY3JpcHQsIEphdmEsIEMrKywgUnVzdCwgYW5kIG1hbnkgb3RoZXJzLg0KDQpNb3N0IG9mdGVuIHRoZSBkYXRhIGV4dHJhY3RlZCBmcm9tIFhNTCBpcyBhbmFseXplZCBzdGF0aXN0aWNhbGx5IG9yIHVzZWQgZm9yIGNvbnN0cnVjdGluZyBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscywgYXMgd2VsbCBhcyBiZWluZyBhZGRlZCB0byByZWxhdGlvbmFsIGRhdGFiYXNlcy4gVGhlc2UgdXNlIGNhc2VzIGFyZSBvdXRzaWRlIHRoZSBzY29wZSBvZiB0aGlzIGxlc3Nvbi4NCg0KIyMgUmVxdWlyZWQgUGFja2FnZXMNCg0KVG8gZXh0cmFjdCBkYXRhIGZyb20gWE1MLCByZXF1aXJlcyBtaW5pbWFsbHkgdGhlIGZvbGxvd2luZyBwYWNrYWdlczoNCg0KLSAgICoqWE1MKioNCg0KSW4gYWRkaXRpb24sIHRoZXNlIHBhY2thZ2VzIGNvbnRhaW4gZnVuY3Rpb25zIHRoYXQgYXJlIG9mdGVuIHF1aXRlIHVzZWZ1bDoNCg0KLSAgICoqc3RyaW5ncioqDQoNCk5vdGUgdGhlICoqWE1MKiogaXMgbm90IHRoZSBvbmx5IHBhY2thZ2UgYXZhaWxhYmxlIGZvciB3b3JraW5nIHdpdGggWE1MLg0KDQojIyBMb2FkaW5nIGFuIFhNTCBGaWxlDQoNClRoZSBwcm9jZXNzIG9mIGV4dHJhY3RpbmcgZGF0YSBzdGFydHMgYnkgInBhcnNpbmciIHRoZSBYTUwgZmlsZSBmcm9tIGVpdGhlciBhIGxvY2FsIGZvbGRlciBvciBmcm9tIGEgVVJMLiBUaGUgY29kZSBiZWxvdyBpbGx1c3RyYXRlcyB0aGF0IGZvciBvbmUgb2YgdGhlIGluY2x1ZGVkIFhNTCBmaWxlcyAoW0Jvb2tDYXRhbG9nLnhtbF0oQm9va0NhdGFsb2cueG1sKSkuIERvd25sb2FkIHRoZSBmaWxlIGFuZCBydW4gdGhlIGNvZGUgaW4geW91ciBvd24gUiBOb3RlYm9vayBvciBSIFNjcmlwdC4NCg0KVGhlIGNvZGUgZnJhZ21lbnQgYmVsb3cgaWxsdXN0cmF0ZXMgaG93IHRvIGxvYWQgYW5kIHBhcnNlIGFuIFhNTCBkb2N1bWVudCBmcm9tIGEgbG9jYWwgZmlsZSBhcyB3ZWxsIGFzIGZyb20gYSBVUkwuDQoNCmBgYHtyIGxvYWRYTUxMb2NhbGx5fQ0KbGlicmFyeShYTUwpDQoNCnhtbEZpbGUgPC0gIkJvb2tDYXRhbG9nLnhtbCINCnhtbFVSTCA8LSAiaHR0cDovL2FydGlmaWNpdW0udXMvbGVzc29ucy8wNi5yL2wtNi0xMTQtcGFyc2UteG1sLXItcHJpbWVyL0Jvb2tDYXRhbG9nLnhtbCINCg0KZG9tMSA8LSB4bWxQYXJzZSh4bWxGaWxlKQ0KZG9tMiA8LSB4bWxQYXJzZSh4bWxVUkwpDQpgYGANCg0KSWYgeW91IGdldCB0aGUgZXJyb3IgKiJFcnJvcjogWE1MIGNvbnRlbnQgZG9lcyBub3Qgc2VlbSB0byBiZSBYTUw6ICcnIiogdGhlbiBpdCBpcyBtb3N0IGxpa2VseSB0aGF0IHRoZSBmaWxlIG5hbWUsIHBhdGgsIG9yIFVSTCBhcmUgbm90IGNvcnJlY3QuDQoNCiMjIERvY3VtZW50IE9iamVjdCBNb2RlbCAoRE9NKQ0KDQpUaGUgZnVuY3Rpb24gPGNvZGU+eG1sUGFyc2UoKTwvY29kZT4gcmV0dXJucyBhIHJlZmVyZW5jZSB0byBhbiBpbnRlcm5hbCB0cmVlIG9mIG5vZGVzIHRoYXQgcmVwcmVzZW50cyB0aGUgZG9jdW1lbnQgb2JqZWN0IG1vZGVsIChET00pIGZvciB0aGUgWE1MLiBQcm9jZXNzaW5nIG9mIHRoZSBub2RlcyBhbmQgZXh0cmFjdGlvbiBvZiBkYXRhIGlzIGRvbmUgdGhyb3VnaCB0aGF0IHJlZmVyZW5jZS4NCg0KQWx0ZXJuYXRpdmVseSwgb25lIGNvdWxkIHVzZSB0aGUgZnVuY3Rpb24gPGNvZGU+eG1sVHJlZVBhcnNlKCk8L2NvZGU+IHdoaWNoIHdpbGwgYWxzbyByZXR1cm4gYSBET00gYnV0IG9uZSB0aGF0IGlzIHJlcHJlc2VudGVkIGFzIGFuIFIgZGF0YSBzdHJ1Y3R1cmUgcmF0aGVyIHRoYW4gYSBDIGRhdGEgc3RydWN0dXJlW14xXS4gV2hpbGUgPGNvZGU+eG1sUGFyc2UoKTwvY29kZT4gaXMgbW9yZSBlZmZpY2llbnQgYW5kIHVzZXMgbGVzcyBtZW1vcnksIDxjb2RlPnhtbFRyZWVQYXJzZSgpPC9jb2RlPiBoYXMgdGhlIGJlbmVmaXQgb2YgZWxlbWVudCBhY2Nlc3MgdXNpbmcgdGhlICpcJCogb3BlcmF0b3IuIEluIHByYWN0aWNlLCA8Y29kZT54bWxQYXJzZSgpPC9jb2RlPiBpcyBtb3N0IGNvbW1vbmx5IHVzZWQsIHRob3VnaC4NCg0KW14xXTogVGhlIFhNTCBwYWNrYWdlIGlzIHdyaXR0ZW4gaW4gQywgc28gdGhlICJpbnRlcm5hbCIgcmVwcmVzZW50YXRpb24gb2YgdGhlIERPTSBpcyBhIEMgZGF0YSBzdHJ1Y3R1cmUuDQoNClRoZSB2YXJpYWJsZSAqZG9tKiAob3IgdG8gd2hpY2hldmVyIHZhcmlhYmxlIHlvdSBhc3NpZ25lZCB0aGUgcmV0dXJuIHZhbHVlIG9mIDxjb2RlPnhtbFBhcnNlKCk8L2NvZGU+KSBwb2ludHMgdG8gYW4gaW4tbWVtb3J5IHJlcHJlc2VudGF0aW9uIG9mIHRoZSBYTUwgdHJlZS4gQWxsIGFjY2VzcyB0byB0aGUgWE1MIGVsZW1lbnRzIGlzIHZpYSB0aGF0IHBvaW50ZXIuDQoNClByb2Nlc3NpbmcgYW4gWE1MIHRocm91Z2ggaXRzIERPTSByZXF1aXJlcyB0aGF0IHRoZSBYTUwgaXMgbG9hZGVkIGNvbXBsZXRlbHkgaW50byBtZW1vcnkuIE5hdHVyYWxseSwgdGhpcyBpcyBvbmx5IGZlYXNpYmxlIHdoZW4gc3VmZmljaWVudCBtZW1vcnkgaXMgYXZhaWxhYmxlLiBGb3IgdmVyeSBsYXJnZSBYTUwgZmlsZXMsIGFub3RoZXIgYXBwcm9hY2ggaXMgYXZhaWxhYmxlIGZvciBleHRyYWN0aW9uOiBTQVguIFRoaXMgaXMgYmV5b25kIHRoZSBzY29wZSBvZiB0aGlzIHR1dG9yaWFsLg0KDQojIyMgVmFsaWRhdGlvbg0KDQpCeSBkZWZhdWx0LCA8Y29kZT54bWxQYXJzZSgpPC9jb2RlPiBkb2VzIG5vdCB2YWxpZGF0ZSB0aGUgZmlsZSBhZ2FpbnN0IGFueSBEVEQgb3IgWE1MIFNjaGVtYTsgaXQgb25seSBjaGVja3Mgd2hldGhlciB0aGUgWE1MIGlzIHdlbGwtZm9ybWVkLiBUbyBlbnN1cmUgdGhhdCB0aGUgWE1MIGNvbmZvcm1zIHRvIHRoZSBydWxlcyBvZiBhIERURCBvciBYTUwgU2NoZW1hLCB0aGUgcGFyYW1ldGVyICp2YWxpZGF0ZT1UKiBtdXN0IGJlIHNwZWNpZmllZC4gT2YgY291cnNlLCB0aGlzIHBhcmFtZXRlciBpcyBvbmx5IG1lYW5pbmdmdWwgaWYgdGhlcmUgaXMgYSBEVEQgb3IgU2NoZW1hOyBpZiB0aGVyZSBpc24ndCBhbmQgKnZhbGlkYXRlPVQqIGlzIHNwZWNpZmllZCB0aGVuIGFuIGVycm9yIHdpbGwgcmVzdWx0Lg0KDQpUaGUgWE1MIGZpbGUgW3BhZ2V2aXNpdHMueG1sXShwYWdldmlzaXRzLnhtbCkgY29udGFpbnMgYSBEVEQgYW5kIHRoZXJlZm9yZSBwYXJzaW5nIHdpdGggdmFsaWRhdGlvbiBpcyBwb3NzaWJsZS4NCg0KYGBge3IgbG9hZFhNTExvY2FsbHlXaXRoVmFsaWRhdGlvbn0NCmxpYnJhcnkoWE1MKQ0KDQp4bWxGaWxlIDwtICJwYWdldmlzaXRzLnhtbCINCg0KZG9tIDwtIHhtbFBhcnNlKHhtbEZpbGUsIHZhbGlkYXRlPVQpDQpgYGANCg0KT3RoZXIgdXNlZnVsIHBhcmFtZXRlcnMgZm9yIDxjb2RlPnhtbFBhcnNlKCk8L2NvZGU+IGluY2x1ZGU6DQoNCi0gICAqdHJpbSogLS0gYSBCb29sZWFuIGluZGljYXRpbmcgd2hldGhlciB0byBzdHJpcCBsZWFkaW5nIGFuZCB0cmFpbGluZyB3aGl0ZXNwYWNlIGZyb20gdmFsdWVzDQotICAgKmdldERURCogLS0gYSBCb29sZWFuIGZsYWcgaW5kaWNhdGluZyB3aGV0aGVyIHRoZSBEVEQgKGJvdGggaW50ZXJuYWwgYW5kIGV4dGVybmFsKSBzaG91bGQgYmUgcmV0dXJuZWQgYWxvbmcgd2l0aCB0aGUgbm9kZXMNCi0gICAqaXNVUkwqIC0tIGEgQm9vbGVhbiBpbmRpY2F0aW5nIHdoZXRoZXIgdGhlIGRvY3VtZW50IHBhdGggaXMgYSBVUkw7IHRoaXMgaXMgbm90IHN0cmljdGx5IHJlcXVpcmVkIGlmIHRoZSBVUkwgc3RhcnRzIHdpdGggYSBjb21tb24gcHJvdG9jb2wgc3VjaCBhcyA8aHR0cDovLz4NCg0KSWYgeW91IG5lZWQgdG8gcHJvY2VzcyBIVE1MIGRvY3VtZW50cyAoYSB0eXBlIG9mIFhNTCBkb2N1bWVudCksIHRoZW4gdGhlIGZ1bmN0aW9uIDxjb2RlPmh0bWxQYXJzZSgpPC9jb2RlPiBpcyBwcmVmZXJhYmxlLg0KDQojIyBFeHRyYWN0aW5nIEVsZW1lbnRzDQoNClRoZSBkYXRhIGluIGFuIFhNTCBkb2N1bWVudCBpcyBjb250YWluZWQgaW4gZWxlbWVudHMgd2hpY2ggYXJlIG1hcmtlZCBieSBwYWlycyBvZiB0YWdzLCAqZS5nLiosIGA8dGFnPnZhbHVlPC90YWc+YC4gVGhlcmUgYXJlIHNldmVyYWwgY29tbW9uIHdheXMgdG8gZXh0cmFjdCB0aGUgdmFsdWVzIG9mIGVsZW1lbnRzIChhbHNvIHNvbWV0aW1lcyBjYWxsZWQgIm5vZGVzIik6IGFjY2VzcyB2aWEgaW5kZXhpbmcgb2YgdGhlIERPTSBvYmplY3QgYW5kIHRocm91Z2ggWFBhdGguIExldCdzIHRha2UgYSBsb29rIGF0IGJvdGggd2F5cywgc3RhcnRpbmcgd2l0aCBhY2Nlc3NpbmcgZWxlbWVudHMgb2YgdGhlIHRyZWUgdGhyb3VnaCBpbmRleGluZy4NCg0KVG8gaWxsdXN0cmF0ZSB0aGUgdGVjaG5pcXVlcywgd2Ugd2lsbCB1c2UgYSBzaW1wbGUgZXhhbXBsZSBYTUwgKFtTaW1wbGVYTUwueG1sXShTaW1wbGVYTUwueG1sKSkgd2l0aCB0d28gdG9wLWxldmVsIGVsZW1lbnRzLg0KDQpgYGAgeG1sDQo8Y2F0YWxvZz4NCiAgIDxib29rIGlkPSJiazEwMSIgZWRpdGlvbj0iMyI+DQogICAgICA8YXV0aG9yPg0KICAgICAgICA8c3VybmFtZT5HYW1iYXJkZWxsYTwvc3VybmFtZT4NCiAgICAgICAgPGdpdmVuPk1hdHRoZXc8L2dpdmVuPg0KICAgICAgPC9hdXRob3I+DQogICAgICA8dGl0bGU+WE1MIERldmVsb3BlcidzIEd1aWRlPC90aXRsZT4NCiAgICAgIDxvdXRvZnByaW50IC8+DQogICAgICA8cHJpY2UgY3VycmVuY3k9IlIkIj4zNDk8L3ByaWNlPg0KICAgPC9ib29rPg0KICAgPGJvb2sgaWQ9ImJrMTAyIiBlZGl0aW9uPSIxIj4NCiAgICAgIDxhdXRob3I+DQogICAgICAgIDxzdXJuYW1lPlJhbGxzPC9zdXJuYW1lPg0KICAgICAgICA8Z2l2ZW4+S2ltPC9naXZlbj48L2F1dGhvcj4NCiAgICAgIDx0aXRsZT5NaWRuaWdodCBSYWluPC90aXRsZT4NCiAgICAgIDxwcmljZSBjdXJyZW5jeT0iVVMkIj41Ljk1PC9wcmljZT4NCiAgIDwvYm9vaz4NCjwvY2F0YWxvZz4NCmBgYA0KDQpMZXQncyBzdGFydCBieSBsb2FkaW5nIHRoZSBYTUwgaW50byBhIERPTSBvYmplY3QuIEFzIGl0IGhhcyBubyBhc3NvY2lhdGVkIERURCwgd2Ugd2lsbCBub3QgdmFsaWRhdGUgdGhlIGZpbGUgZHVyaW5nIHBhcnNpbmcuDQoNCmBgYHtyfQ0KeG1sRG9jIDwtIHhtbFBhcnNlKCJTaW1wbGVYTUwueG1sIiwgdmFsaWRhdGU9RikNCmBgYA0KDQojIyMgSW5kZXhlZCBBY2Nlc3MNCg0KVGhlIGZpcnN0IHN0ZXAgaW4gYWNjZXNzaW5nIHRoZSBlbGVtZW50cyAobm9kZXMpIG9mIHRoZSBET00gdHJlZSBpcyB0byBnZXQgdGhlIHJvb3QgZWxlbWVudCAoZm9yIHRoZSBhYm92ZSBYTUwgdGhhdCB3b3VsZCBgPGNhdGFsb2c+YCkgdXNpbmcgdGhlIGZ1bmN0aW9uIGB4bWxSb290KClgLg0KDQpgYGB7cn0NCnJvb3QgPC0geG1sUm9vdCh4bWxEb2MpDQpgYGANCg0KQWNjZXNzaW5nIHRoZSAqaVxedGgqIGNoaWxkIG5vZGUgZGlyZWN0bHkgdW5kZXJuZWF0aCB0aGUgcm9vdCBjYW4gYmUgZG9uZSB1c2luZyB0aGUgbGlzdCBhY2Nlc3Mgb3BlcmF0b3IgYFtbaV1dYGFzIHRoZSByZXByZXNlbnRhdGlvbiB3aXRoaW4gUiBpcyBhIGxpc3QuDQoNCmBgYHtyfQ0KIyBhY2Nlc3MgdGhlIGZpcnN0IGNoaWxkIG5vZGUgdW5kZXJuZWF0aCB0aGUgcm9vdA0KYU5vZGUgPC0gcm9vdFtbMV1dDQoNCnByaW50KGFOb2RlKQ0KYGBgDQoNClRoaXMgY2FuIGJlIGNvbnRpbnVlZCBkb3duIHRoZSB0cmVlLiBGb3IgZXhhbXBsZSB0byBhY2Nlc3MgdGhlIGdpdmVuIG5hbWUgZWxlbWVudCBvZiB0aGUgYXV0aG9yIGZvciB0aGUgc2Vjb25kIGJvb2ssIHlvdSB3b3VsZCB1c2U6DQoNCmBgYHtyfQ0KYU5vZGUgPC0gcm9vdFtbMl1dW1sxXV1bWzJdXQ0KcHJpbnQoYU5vZGUpDQpgYGANCg0KVG8gZ2V0IGl0cyB2YWx1ZSwgeW91IHdvdWxkIG5lZWQgdG8gdXNlIHRoZSBmdW5jdGlvbiBgeG1sVmFsdWUoKWAuDQoNCmBgYHtyfQ0KdiA8LSB4bWxWYWx1ZShhTm9kZSkNCg0KcHJpbnQodikNCmBgYA0KDQpUaGUgaW1hZ2UgYmVsb3cgaWxsdXN0cmF0ZXMgdGhlIHN5bnRheCB0byBhY2Nlc3MgY2hpbGQgZWxlbWVudHMgYnkgcG9zaXRpb24gd2l0aGluIHRoZSBET00gdHJlZS4NCg0KIVtdKGltYWdlcy90cmVlLXRyYXZlcnNhbC5wbmcpe3dpZHRoPSI2MCUifQ0KDQojIyMgSXRlcmF0aW5nIHRocm91Z2ggTm9kZXMNCg0KT25lIG9mIHRoZSBhZHZhbnRhZ2VzIG9mIHVzaW5nIHRoaXMgbW9kZSBvZiBhY2Nlc3MgaXMgdGhhdCB0aGUgdHJlZSBjYW4gYmUgcHJvY2Vzc2VkIGluIGEgbG9vcC4gRm9yIGV4YW1wbGUsIHRvIGdldCBhbGwgdGhlIGxhc3QgbmFtZXMgb2YgdGhlIGF1dGhvcnMgb2YgYWxsIGJvb2tzLCB3ZSBjYW4gbG9vcCB0aHJvdWdoIHRoZSBub2Rlcy4gVGhlIGZ1bmN0aW9uIGB4bWxTaXplKClgIHJldHVybnMgdGhlIG51bWJlciBvZiBkaXJlY3QgY2hpbGQgbm9kZXMgdW5kZXJuZWF0aCBhIGdpdmVuIG5vZGUuDQoNClRoZSBjb2RlIGZyYWdtZW50IGJlbG93IGV4dHJhY3RzIGFsbCBzdXJuYW1lcyBmb3IgYWxsIGF1dGhvcnMgYW5kIHBsYWNlcyB0aGVtIGludG8gYSB2ZWN0b3IuIEVhY2ggYHJvb3RbW2ldXVtbMV1dW1sxXV1gIHJldHVybnMgdGhlIHZhbHVlIHRvIHRoZSBmaXJzdCBjaGlsZCBvZiB0aGUgZmlyc3QgY2hpbGQgd2l0aGluIGVhY2ggb2YgdGhlIG5vZGVzIHVuZGVybmVhdGggYDxjYXRhbG9nPmAuIFNvLCBpZiB5b3UgdmlzdWFsaXplIHRoZSB0cmVlIG9mIHRoZSBYTUwsIHRoYXQgaXMgYDxib29rPjxhdXRob3I+PHN1cm5hbWU+YA0KDQpgYGB7cn0NCiMgbnVtYmVyIG9mIDxib29rPiBub2Rlcw0KbiA8LSB4bWxTaXplKHJvb3QpDQoNCiMgcHJlLWFsbG9jYXRlZCB2ZWN0b3IgZm9yIHRoZSBzdXJuYW1lcw0KbmFtZXMgPC0gYyhsZW5ndGggPSBuKQ0KDQpmb3IgKGkgaW4gMTpuKQ0Kew0KICBuYW1lc1tpXSA8LSB4bWxWYWx1ZShyb290W1tpXV1bWzFdXVtbMV1dKQ0KfQ0KDQpjYXQobmFtZXMpDQpgYGANCg0KT2YgY291cnNlLCByYXRoZXIgdGhhbiBwbGFjaW5nIHRoZW0gaW50byBhIHZlY3Rvciwgb25lIGNhbiBhbHNvIHN0b3JlIHRoZW0gaW4gYSBjb2x1bW4gb2YgYSBkYXRhIGZyYW1lLCB3aGljaCBpcywgYWZ0ZXIgYWxsLCBzaW1wbHkgYSB2ZWN0b3IuDQoNCiMjIyMgT3B0aW9uYWwgRWxlbWVudHMNCg0KRGVhbGluZyB3aXRoIG9wdGlvbmFsIGVsZW1lbnRzIGNhbiBiZSBhIGJpdCB0cmlja3kgd2hlbiBleHRyYWN0aW5nIG5vZGVzIHVzaW5nIGluZGV4ZWQgYWNjZXNzLiBGb3IgZXhhbXBsZSwgaW4gdGhlIHNhbXBsZSBYTUwsIHRoZSBlbGVtZW50IGA8b3V0b2ZwcmludCAvPmAgaXMgbm90IHByZXNlbnQgaW4gYWxsIG5vZGVzLCBzbyBleHRyYWN0aW5nIHRoZSB2YWx1ZSBvZiB0aGUgYm9vayBwcmljZXMgbWVhbnMgdGhhdCB0aGUgYDxwcmljZT5gIGVsZW1lbnQgY2FuIGJlIGNoaWxkIG5vZGUgMyBvciA0IGRlcGVuZGluZyB3aGV0aGVyIHRoZSBlbGVtZW50IGA8b3V0b2ZwcmludCAvPmAgaXMgcHJlc2VudCBiZWZvcmUgdGhlIGVsZW1lbnQgYDxwcmljZT5gLiBPbmUgdGVjaG5pcXVlIGlzIHRvIGNoZWNrIHRoZSBuYW1lIG9mIHRoZSAzXF5yZCBlbGVtZW50IHRvIHNlZSBpZiBpdCBpcyAqcHJpY2UqIG9yICpvdXRvZnByaW50KiB1c2luZyB0aGUgZnVuY3Rpb24gYHhtbE5hbWUoKWAuDQoNClRoZSBjb2RlIGZyYWdtZW50IGJlbG93IGFsc28gaWxsdXN0cmF0ZXMgdGhlIGNvbnZlcnNpb24gb2YgdGV4dCB2YWx1ZXMgdG8gbnVtYmVycy4gQWxsIHZhbHVlcyByZXR1cm5lZCBmcm9tIGB4bWx2YWx1ZSgpYCBhcmUgb2YgdHlwZSAiY2hhcmFjdGVyIiwgc28gdXNpbmcgdGhlbSBhIG51bWVyaWMgdmFsdWVzIHJlcXVpcmVzIGV4cGxpY2l0IGNvZXJjaW9uIHVzaW5nIGBhcy5udW1lcmljKClgLiBPZiBjb3Vyc2UsIGlmIHRoZSB2YWx1ZSBoYWQgbm9uLWRpZ2l0IGNoYXJhY3RlcnMsIHN1Y2ggYXMgIlwkIiwgdGhlbiBzb21lIHN0cmluZyBleHRyYWN0aW9uIHdvdWxkIGZpcnN0IGJlIG5lZWRlZC4NCg0KYGBge3J9DQpuIDwtIHhtbFNpemUocm9vdCkNCg0KZm9yIChpIGluIDE6bikNCnsNCiAgbm9kZSA8LSB4bWxOYW1lKHJvb3RbW2ldXVtbM11dKQ0KICBpZiAobm9kZSA9PSAib3V0b2ZwcmludCIpDQogICAgcHJpY2Uubm9kZSA8LSA0DQogIGVsc2UNCiAgICBwcmljZS5ub2RlIDwtIDMNCiAgDQogIHByaWNlIDwtIHhtbFZhbHVlKHJvb3RbW2ldXVtbcHJpY2Uubm9kZV1dKQ0KICBwcmljZS52YWx1ZSA8LSBhcy5udW1lcmljKHByaWNlKQ0KfQ0KYGBgDQoNCiMjIyMgU3RvcmluZyBYTUwgRGF0YSBpbiBEYXRhIEZyYW1lDQoNClRoZSBjb2RlIGJlbG93IGV4dHJhY3RzIHRoZSB0aXRsZXMgYW5kIHRoZSBwcmljZXMgYW5kIHBsYWNlcyB0aGVtIGludG8gYSBkYXRhIGZyYW1lLiBUaGlzIGlzIGEgY29tbW9uIHN0cmF0ZWd5IHdoZW4gY29udmVydGluZyB0aGUgZGF0YSBmcm9tIFhNTCB0byBhIHRhYnVsYXIgZm9ybWF0IGZvciBleHRlcm5hbGl6YXRpb24gaW4gYSBDU1Ygb3Igd2hlbiBzYXZpbmcgdGhlIGRhdGEgdG8gYSByZWxhdGlvbmFsIGRhdGFiYXNlLg0KDQpgYGB7ciB4bWwyZGF0YWZyYW1lfQ0KIyBudW1iZXIgb2YgPGJvb2s+IG5vZGVzDQpuIDwtIHhtbFNpemUocm9vdCkNCg0KIyBlbXB0eSBkYXRhIGZyYW1lDQpkZiA8LSBkYXRhLmZyYW1lKHRpdGxlID0gYXMuY2hhcmFjdGVyKG4pLA0KICAgICAgICAgICAgICAgICBwcmljZSA9IGFzLm51bWVyaWMobikpDQoNCmZvciAoaSBpbiAxOm4pDQp7DQogIHRpdGxlIDwtIHhtbFZhbHVlKHJvb3RbW2ldXVtbMl1dKQ0KICBub2RlIDwtIHhtbE5hbWUocm9vdFtbaV1dW1szXV0pDQogIGlmIChub2RlID09ICJvdXRvZnByaW50IikNCiAgICBwcmljZS5ub2RlIDwtIDQNCiAgZWxzZQ0KICAgIHByaWNlLm5vZGUgPC0gMw0KICANCiAgcHJpY2UgPC0geG1sVmFsdWUocm9vdFtbaV1dW1twcmljZS5ub2RlXV0pDQogIHByaWNlLnZhbHVlIDwtIGFzLm51bWVyaWMocHJpY2UpDQogIA0KICAjIGFkZCB0byBkYXRhIGZyYW1lDQogIGRmW2ksInRpdGxlIl0gPC0gdGl0bGUNCiAgZGZbaSwicHJpY2UiXSA8LSBwcmljZS52YWx1ZQ0KfQ0KDQpwcmludChoZWFkKGRmLDMpKQ0KYGBgDQoNClRoZSBYTUwgcGFja2FnZSBjb250YWlucyB0aGUgZnVuY3Rpb24gYHhtbFRvRGF0YUZyYW1lKClgIHRoYXQgY2FuIG1vcmUgY29udmVuaWVudGx5IGV4dHJhY3QgWE1MIGRhdGEgdG8gYSBkYXRhZnJhbWUgYnV0IG9ubHkgaWYgdGhlIFhNTCBoYXMgdHdvIGxldmVscyBhbmQgdGhlIGVsZW1lbnRzIGFyZSBpbiB0aGUgc2FtZSBvcmRlci4gSXQgd291bGQgbm90IHdvcmsgcHJvcGVybHkgb24gdGhlIHNhbXBsZSBYTUwgKlNpbXBsZVhNTC54bWwqLiBTZWUgTGVzc29uIFs2LjMyMyBMb2FkIFNpbXBsZSBYTUwgaW50byBEYXRhZnJhbWUgaW4gUiB1c2luZyB4bWxUb0RhdGFGcmFtZSgpXShodHRwOi8vYXJ0aWZpY2l1bS51cy9sZXNzb25zLzA2LnIvbC02LTMyMy1sb2FkLXhtbC14bWxUb0RhdGFGcmFtZS9sLTYtMzIzLmh0bWwpLg0KDQojIyMjIEV4dHJhY3RpbmcgQXR0cmlidXRlcw0KDQpEYXRhIGlzIG5vdCBvbmx5IGluIGVsZW1lbnRzIGJ1dCBjYW4gYWxzbyBiZSBpbiBhdHRyaWJ1dGVzIG9mIGVsZW1lbnRzLiBGb3IgZXhhbXBsZSwgaW4gKlNpbXBsZVhNTC54bWwqLCB0aGUgKmVkaXRpb24qIGFuZCAqaWQqIGFyZSBhdHRyaWJ1dGVzIG9mIHRoZSBgPGJvb2s+YCBlbGVtZW50LCBhcyBzaG93biBiZWxvdy4NCg0KYGBgIHhtbA0KPGNhdGFsb2c+DQogICA8Ym9vayBpZD0iYmsxMDEiIGVkaXRpb249IjMiPg0KICAgICAgPGF1dGhvcj4NCiAgICAgICAgPHN1cm5hbWU+R2FtYmFyZGVsbGE8L3N1cm5hbWU+DQogICAgICAgIDxnaXZlbj5NYXR0aGV3PC9naXZlbj4NCiAgICAgIDwvYXV0aG9yPg0KICAgICAgLi4uDQpgYGANCg0KQXR0cmlidXRlcyBhcmUgZXh0cmFjdGVkIHVzaW5nIHRoZSBgeG1sQXR0cnMoKWAgZnVuY3Rpb24gdG8gd2hpY2ggYW4gZWxlbWVudCBpcyBwYXNzZWQuIFRoZSBmdW5jdGlvbiByZXR1cm5zIGEgKmxpc3QqIG9mICphbGwqIGF0dHJpYnV0ZXMsIHNvIHdlIG5lZWQgdGhlIGRvdWJsZS1icmFja2V0IGFjY2VzcyBvcGVyYXRvciBbW11dIHRvIGFjY2VzcyB0aGUgZWxlbWVudHMgb3IgdXNlIHRoZSBmdW5jdGlvbiBgdW5saXN0KClgIHRvIGNvbnZlcnQgdGhlIGxpc3QgdG8gYSB2ZWN0b3IuDQoNCmBgYHtyIGV4dHJhY3RBdHRyc30NCm4gPC0geG1sU2l6ZShyb290KQ0KDQpmb3IgKGkgaW4gMTpuKQ0Kew0KICAjIGdldCB0aGUgaS10aCBib29rDQogIGFCb29rIDwtIHJvb3RbW2ldXQ0KICANCiAgIyBnZXQgYXR0cmlidXRlcyBvZiB0aGUgaS10aCBib29rDQogIGJvb2suYXR0cnMgPC0geG1sQXR0cnMoYUJvb2spDQogIA0KICAjIHNlY29uZCBhdHRyaWJ1dGUgaW4gdGhlIGxpc3QgaXMgdGhlIGVkaXRpb24NCiAgZWRpdGlvbiA8LSBib29rLmF0dHJzW1syXV0NCiAgDQogIHByaW50KGVkaXRpb24pDQp9DQpgYGANCg0KVG8gZXh0cmFjdCBhIHNwZWNpZmljIGF0dHJpYnV0ZSwgdGhlIGZ1bmN0aW9uIGB4bWxHZXRBdHRyKClgIGlzIG9mdGVuIG1vcmUgY29udmVuaWVudC4gTGlrZSB2YWx1ZXMsIGF0dHJpYnV0ZXMgYXJlIHJldHVybmVkIGFzIGNoYXJhY3RlciBzdHJpbmdzIHJlcXVpcmluZyBjb2VyY2lvbiB0byB0aGUgYXBwcm9wcmlhdGUgZGF0YSB0eXBlIHVzaW5nIGNvZXJjaW9uIGZ1bmN0aW9ucyBzdWNoIGFzIGBhcy5udW1lcmljKClgLg0KDQpgYGB7ciBleHRyYWN0U2luZ2xlQXR0cn0NCm4gPC0geG1sU2l6ZShyb290KQ0KDQpmb3IgKGkgaW4gMTpuKQ0Kew0KICAjIGdldCB0aGUgaS10aCBib29rDQogIGFCb29rIDwtIHJvb3RbW2ldXQ0KICANCiAgIyBnZXQgdGhlIHZhbHVlIG9mIHRoZSBhdHRyaWJ1dGUgImVkaXRpb24iDQogIGVkaXRpb24gPC0geG1sR2V0QXR0cihhQm9vaywgImVkaXRpb24iKQ0KICANCiAgcHJpbnQoZWRpdGlvbikNCn0NCmBgYA0KDQojIyBQcmV0dHkgVGFibGVzIHdpdGggImthYmxlIg0KDQpUaGUgY29kZSBiZWxvdyBkZW1vbnN0cmF0ZXMgdGhlIHVzZSBvZiB0aGUgImtuaXRyOjprYWJsZSgpIiBwYWNrYWdlLiBGaXJzdCwgd2UgYXJlIGV4dHJhY3RpbmcgdGhlIHRpdGxlIGFuZCB0aGUgYXV0aG9yIG9mIGVhY2ggYm9vayBpbnRvIGEgZGF0YSBmcmFtZSBhbmQgd2UgdXNlIHRoZSAqKmthYmxlRXh0cmEqKiBwYWNrYWdlIHRvICJwcmV0dHkgcHJpbnQiIHRoZSBkYXRhIGZyYW1lcy4NCg0KYGBge3IgZWNobz1UfQ0KbGlicmFyeShYTUwpDQoNCnhtbFVSTCA8LSAiaHR0cDovL2FydGlmaWNpdW0udXMvbGVzc29ucy8wNi5yL2wtNi0xMTQtcGFyc2UteG1sLXItcHJpbWVyL0Jvb2tDYXRhbG9nLnhtbCINCg0KeG1sRE9NIDwtIHhtbFBhcnNlKHhtbFVSTCwgdmFsaWRhdGUgPSBGKQ0KDQpyIDwtIHhtbFJvb3QoeG1sRE9NKQ0KDQojIG51bWJlciBvZiA8Ym9vaz4gbm9kZXMNCm4gPC0geG1sU2l6ZShyKQ0KDQpkZi5ib29rcyA8LSBkYXRhLmZyYW1lKA0KICB0aXRsZSA9IGNoYXJhY3RlcihuKSwNCiAgYXV0aG9yID0gY2hhcmFjdGVyKG4pDQopDQoNCmZvciAoaSBpbiAxOm4pDQp7DQogICMjIGFjY2VzcyB0aGUgaXRoIGJvb2sgbm9kZQ0KICBhQm9vayA8LSByW1tpXV0NCiAgDQogICMjIGV4dHJhY3QgdGl0bGUgKGNoaWxkIDIpIGFuZCBhdXRob3IgKGNoaWxkIDEpIGZyb20NCiAgIyMgdGhlIGJvb2sgbm9kZQ0KICB0aGVUaXRsZSA8LSB4bWxWYWx1ZShhQm9va1tbMl1dKQ0KICB0aGVBdXRob3IgPC0geG1sVmFsdWUoYUJvb2tbWzFdXSkNCiAgDQogICMjIHN0b3JlIHZhbHVlcyBpbiB0aGUgaXRoIHJvdyBvZiB0aGUgZGF0YSBmcmFtZQ0KICBkZi5ib29rc1tpLDFdIDwtIHRoZVRpdGxlDQogIGRmLmJvb2tzJGF1dGhvcltpXSA8LSB0aGVBdXRob3INCn0NCg0KaGVhZChkZi5ib29rcykNCmBgYA0KDQojIyMgRXhhbXBsZSBJDQoNCk5vdGUgdGhhdCBvbmx5IHRoZSBmaXJzdCBzaXggcm93cyBvZiB0aGUgZGF0YSBmcmFtZSBhcmUgZGlzcGxheWVkLg0KDQpgYGB7ciBwcmludFdpdGhLYWJsZSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoa2FibGVFeHRyYSkNCg0KZGYuYm9va3NbMTo2LF0gJT4lDQogIGtibCgpICU+JQ0KICBrYWJsZV9wYXBlcigiaG92ZXIiLCBmdWxsX3dpZHRoID0gRikNCmBgYA0KDQojIyMgRXhhbXBsZSAyDQoNClRoaXMgZXhhbXBsZSB1c2VzIGEgZGlmZmVyZW50IGZvcm1hdCBzdHlsZSBhbmQgYWxzbyBwcmludHMgdGhlIHRhYmxlIG92ZXIgdGhlIGVudGlyZSB3aWR0aCBvZiB0aGUgZG9jdW1lbnQuDQoNCmBgYHtyIHdhcm5pbmc9RkFMU0V9DQpkZi5ib29rc1sxOjYsXSAlPiUNCiAga2JsKGNhcHRpb24gPSAiQm9va3MgYnkgVGl0bGUgd2l0aCBBdXRob3IiKSAlPiUNCiAga2FibGVfY2xhc3NpYyhmdWxsX3dpZHRoID0gVCwgaHRtbF9mb250ID0gIkNhbWJyaWEiKQ0KYGBgDQoNCiMjIFR1dG9yaWFsIEkNCg0KVGhlIGNvbnRlbnQgZnJvbSB0aGUgbGVzc29uIHRvIHRoaXMgcG9pbnQgaXMgbmFycmF0ZWQgaW4gdGhlIGNvZGUgd2FsayBiZWxvdy4NCg0KPGlmcmFtZSBzcmM9Imh0dHBzOi8vcGxheWVyLnZpbWVvLmNvbS92aWRlby85MTUyMzk3NjM/dGl0bGU9MCZhbXA7YnlsaW5lPTAmYW1wO3BvcnRyYWl0PTAmYW1wO2JhZGdlPTAmYW1wO2F1dG9wYXVzZT0wJmFtcDtwbGF5ZXJfaWQ9MCZhbXA7YXBwX2lkPTU4NDc5IiB3aWR0aD0iNTYwIiBoZWlnaHQ9IjMxNSIgZnJhbWVib3JkZXI9IjAiIGFsbG93PSJhdXRvcGxheTsgZnVsbHNjcmVlbjsgcGljdHVyZS1pbi1waWN0dXJlIiB0aXRsZT0ibC02LTExNCBQYXJzaW5nIFhNTCBpbiBSIFVzaW5nIE5vZGUgVHJhdmVyc2FsIiBkYXRhLWV4dGVybmFsPSIxIj4NCg0KPC9pZnJhbWU+DQoNCiMjIFhQYXRoDQoNCkEgc2ltcGxlIGFuZCBtb3JlIGVsZWdhbnQsIGFsYmVpdCBsZXNzIGZsZXhpYmxlIGFuZCBwZXJoYXBzIGxlc3MgZWZmaWNpZW50LCB3YXkgaXMgdG8gdXNlIFhQYXRoIGV4cHJlc3Npb25zIHRvIGFjY2VzcyBlbGVtZW50cyBhbmQgYXR0cmlidXRlcy4gV2Ugd2lsbCByZWRvIHRoZSBhYm92ZSBleHRyYWN0aW9ucyB1c2luZyBYUGF0aCByYXRoZXIgdGhhbiBpbmRleGVkIG5vZGUgYWNjZXNzLg0KDQpMZXQncyBzdGFydCBmaXJzdCBieSBkZW1vbnN0cmF0aW5nIGhvdyB0byBleGVjdXRlIGFuIFhQYXRoIHF1ZXJ5IG9uIGFuIFhNTCBkb2N1bWVudC4gQWZ0ZXIgdGhlIFhNTCBpcyBwYXJzZWQgdXNpbmcgYHhtbFBhcnNlKClgLCB0aGUgWFBhdGggZXhwcmVzc2lvbiBpcyBleGVjdXRlZCB1c2luZyBgeHBhdGhTQXBwbHkoKWAuIFRoZSBmdW5jdGlvbiByZXR1cm5zIGEgbGlzdCAobm90IGEgdmVjdG9yKSBvZiBhbGwgbm9kZXMgdGhhdCBtYXRjaCB0aGUgWFBhdGggcGF0aCBleHByZXNzaW9uLg0KDQpgYGB7cn0NCmxpYnJhcnkoWE1MKQ0KDQp4bWxEb2MgPC0geG1sUGFyc2UoIlNpbXBsZVhNTC54bWwiLCB2YWxpZGF0ZT1GKQ0KDQojIFhQYXRoIGV4cHJlc3Npb24gdG8gZ2V0IHRpdGxlcyBvZiBhbGwgYm9vayBub2Rlcw0KeHBhdGhFeHByIDwtICIvL2Jvb2svdGl0bGUiDQoNCiMgZXhlY3V0ZS9hcHBseSB0aGUgWFBhdGggcXVlcnkNCnJzIDwtIHhwYXRoU0FwcGx5KHhtbERvYywgeHBhdGhFeHByKQ0KDQojIHByaW50IHRoZSBsaXN0IG9mIG1hdGNoaW5nIGVsZW1lbnRzDQpwcmludChycykNCmBgYA0KDQpUbyBnZXQgdGhlIHZhbHVlcyBvZiB0aGUgZWxlbWVudHMsIGFkZCAieG1sVmFsdWUiIGFzIGEgcGFyYW1ldGVyIGFzIHNob3duIGJlbG93IGFuZCBgeHBhdGhTQXBwbHkoKWAgcmV0dXJucyBhIHZlY3RvciBvZiBlbGVtZW50IHZhbHVlcy4NCg0KYGBge3J9DQpsaWJyYXJ5KFhNTCkNCg0KeG1sRG9jIDwtIHhtbFBhcnNlKCJTaW1wbGVYTUwueG1sIiwgdmFsaWRhdGU9RikNCg0KIyBYUGF0aCBleHByZXNzaW9uIHRvIGdldCB0aXRsZXMgb2YgYWxsIGJvb2sgbm9kZXMNCnhwYXRoRXhwciA8LSAiLy9ib29rL3RpdGxlIg0KDQojIGV4ZWN1dGUvYXBwbHkgdGhlIFhQYXRoIHF1ZXJ5IGFuZCBleHRyYWN0IHZhbHVlcw0KcnMgPC0geHBhdGhTQXBwbHkoeG1sRG9jLCB4cGF0aEV4cHIsIHhtbFZhbHVlKQ0KDQpwcmludChycykNCmBgYA0KDQpUaGUgZXhhbXBsZSBiZWxvdyBleHRyYWN0cyB0aGUgdGl0bGUsIHByaWNlLCBhbmQgZWRpdGlvbiB1c2luZyBYUGF0aCBleHByZXNzaW9ucyBhbmQgYWRkcyB0aGVtIHRvIGEgZGF0YSBmcmFtZS5Ob3RpY2UgaG93IHdlIGFyZSBubyBsb25nZXIgY29uY2VybmVkIGFib3V0IHdoZXRoZXIgdGhlIG9wdGlvbmFsIGA8b3V0b2ZwcmludC8+YCBlbGVtZW50IGlzIHBhcnQgb2YgYSBgPGJvb2s+YCBub2RlIG9yIG5vdC4gWFBhdGggc2ltcGxpZmllcyBhY2Nlc3MuDQoNCmBgYHtyfQ0KbGlicmFyeShYTUwpDQoNCnhtbERvYyA8LSB4bWxQYXJzZSgiU2ltcGxlWE1MLnhtbCIsIHZhbGlkYXRlPUYpDQoNCiMgbnVtYmVyIG9mIDxib29rPiBub2Rlcw0KbiA8LSB4bWxTaXplKHJvb3QpDQoNCnRpdGxlcyA8LSB4cGF0aFNBcHBseSh4bWxEb2MsIA0KICAgICAgICAgICAgICAgICAgICAgICIvL2Jvb2svdGl0bGUiLCB4bWxWYWx1ZSkNCg0KcHJpY2VzIDwtIHhwYXRoU0FwcGx5KHhtbERvYywgDQogICAgICAgICAgICAgICAgICAgICAgIi8vYm9vay9wcmljZSIsIHhtbFZhbHVlKQ0KDQplZGl0aW9ucyA8LSB4cGF0aFNBcHBseSh4bWxEb2MsIA0KICAgICAgICAgICAgICAgICAgICAgICIvL2Jvb2svQGVkaXRpb24iKQ0KDQoNCiMgY3JlYXRlIG5ldyBkYXRhIGZyYW1lDQpkZiA8LSBkYXRhLmZyYW1lKHRpdGxlID0gdGl0bGVzLA0KICAgICAgICAgICAgICAgICBwcmljZSA9IGFzLm51bWVyaWMocHJpY2VzKSwNCiAgICAgICAgICAgICAgICAgZWRpdGlvbiA9IGFzLm51bWVyaWMoZWRpdGlvbnMpKQ0KDQoNCnByaW50KGhlYWQoZGYsMykpDQpgYGANCg0KTm90aWNlIGhvdyB3ZSBkbyBub3QgdXNlIGB4bWxWYWx1ZWAgd2hlbiByZXRyaWV2aW5nIGF0dHJpYnV0ZSB2YWx1ZXMuDQoNCiMjIyBNaXNzaW5nIEVsZW1lbnRzDQoNClRoZSBhcHByb2FjaCB0byBjaGVjayB3aGV0aGVyIGFuIGVsZW1lbnQgaXMgcHJlc2VudCBpcyBhIGJpdCBtb3JlIGRpZmZpY3VsdCB3aXRoIFhQYXRoIHRoYW4gaW5kZXhlZCBhY2Nlc3MuIFRoZSBmdW5jdGlvbiBgeHBhdGhTQXBwbHkoKWAgcmV0dXJucyBhbiBlbXB0eSBsaXN0IHdoZW4gdGhlIFhQYXRoIGV4cHJlc3Npb24gaGFzIG5vIG1hdGNoaW5nIGVsZW1lbnRzLiBXZSBjYW4gY2hlY2sgd2hldGhlciBhIGxpc3QgaXMgZW1wdHkgYnkgZmluZGluZyBpdHMgbGVuZ3RoIChvciBzaXplKSB1c2luZyB0aGUgZnVuY3Rpb24gYGxlbmd0aCgpYC4gSWYgaXQgaXMgZW1wdHksIHRoZW4gaXRzIGxlbmd0aCBpcyAwLg0KDQpgYGB7cn0NCnhwYXRoIDwtICIvL2Jvb2svZ2VucmUiDQoNCiMgWFBhdGggZXhwcmVzc2lvbiBzaG91bGQgbm90IHJldHVybiBhIHZhbHVlDQpycyA8LSB4cGF0aFNBcHBseSh4bWxEb2MsIHhwYXRoLCB4bWxWYWx1ZSkNCg0KaWYgKGxlbmd0aChycykgPT0gMCkNCnsNCiAgcHJpbnQoIm5vIGdlbnJlIikNCn0NCmBgYA0KDQojIyMgWFBhdGggd2l0aCBJbmRleGVkIEFjY2Vzcw0KDQpUaGUgYWZvcmVtZW50aW9uZWQgY29kZSBwcmVzdW1lcyB0aGF0IGV2ZXJ5IGJvb2sgaGFzIG9uZSB0aXRsZSwgb25lIHByaWNlLCBhbmQgYW4gZWRpdGlvbi4gSWYgdGhlcmUgYXJlIG11bHRpcGxlIHRpdGxlcyBvciBwcmljZXMsIG9yIHNvbWUgZG8gbm90IGV4aXN0LCB0aGVuIGEgY29tYmluYXRpb24gb2Ygbm9kZSB0cmF2ZXJzYWwgYW5kIFhQYXRoIHdvdWxkIGJlIG5lY2Vzc2FyeS4NCg0KTGV0J3Mgc2F5IHdlIGhhZCBhIG1vcmUgY29tcGxleCBYTUwgdGhhdCBoYWQgbXVsdGlwbGUgcHJpY2VzIGFuZCB3ZSBvbmx5IHdhbnRlZCB0byBleHRyYWN0IHRoZSBVUyBwcmljZXMgKHdoZXJlIHRoZSAqY3VycmVuY3kqIGF0dHJpYnV0ZXMgaXMgIlVTXCQiKS4gSGVyZSdzIHdoYXQgb25lIG9mIHRoZSBub2RlcyBpbiB0aGUgWE1MIGZpbGUgW1NpbXBsZVhNTC0yLnhtbF0oU2ltcGxlWE1MLTIueG1sKSBsb29rcyBsaWtlOg0KDQpgYGAgeG1sDQo8Y2F0YWxvZz4NCiAgIDxib29rIGlkPSJiazEwMSIgZWRpdGlvbj0iMyI+DQogICAgICA8YXV0aG9yPg0KICAgICAgICA8c3VybmFtZT5HYW1iYXJkZWxsYTwvc3VybmFtZT4NCiAgICAgICAgPGdpdmVuPk1hdHRoZXc8L2dpdmVuPg0KICAgICAgPC9hdXRob3I+DQogICAgICA8dGl0bGU+WE1MIERldmVsb3BlcidzIEd1aWRlPC90aXRsZT4NCiAgICAgIDxvdXRvZnByaW50IC8+DQogICAgICA8cHJpY2UgY3VycmVuY3k9IlIkIj4zNDk8L3ByaWNlPg0KICAgICAgPHByaWNlIGN1cnJlbmN5PSJVUyQiPjI5Ljk1PC9wcmljZT4NCiAgICAgIDxwcmljZSBjdXJyZW5jeT0i4oKsIj4zNC4wMDwvcHJpY2U+DQogICA8L2Jvb2s+DQogICAuLi4NCiA8L2NhdGFsb2c+DQpgYGANCg0KVGhlcmUgYXJlIG11bHRpcGxlIHdheXMgdG8gc29sdmUgdGhpcy4gT25lIGFwcHJvYWNoLCBvZiBjb3Vyc2UsIGlzIHRvIHVzZSBYUGF0aCBleHByZXNzaW9ucyB0aGF0IGV4dHJhY3QgcHJpY2UgdmFsdWVzIG9ubHkgd2hlbiBjdXJyZW5jeSBpcyAiVVNcJCIuIEhvd2V2ZXIsIHRvIGRlbW9uc3RyYXRlIHRoZSBtaXhpbmcgb2YgaW5kZXhlZCBhY2Nlc3MgYW5kIFhQYXRoLCB3ZSB3aWxsIGNob29zZSBhbiBhcHByb2FjaCB0aGUgdXNlcyBsb29wcy4gVGhpcyBhcHByb2FjaCBpcyBnZW5lcmFsbHkgc2xvd2VyIChsb29wcyBhcmUgc2xvdywgZXNwZWNpYWxseSBpbiBSKSwgYnV0IGFmZm9yZHMgbW9yZSBmbGV4aWJpbGl0eS4NCg0KYGBge3J9DQpsaWJyYXJ5KFhNTCkNCnhtbERvYyA8LSB4bWxQYXJzZSgiU2ltcGxlWE1MLTIueG1sIikNCg0KbiA8LSB4bWxTaXplKHJvb3QpDQoNCiMgZ2V0IGFsbCA8Ym9vaz4gbm9kZXMgZnJvbSB0aGUgWE1MDQpib29rcyA8LSB4cGF0aFNBcHBseSh4bWxEb2MsICIvL2Jvb2siKQ0KDQojIGl0ZXJhdGUgb3ZlciB0aGUgPGJvb2s+IG5vZGVzDQpmb3IgKGkgaW4gMTpuKQ0Kew0KICAjIGdldCB0aGUgaS10aCBib29rIG5vZGUNCiAgYUJvb2sgPC0gYm9va3NbW2ldXQ0KICANCiAgIyB1c2UgWFBhdGggdG8gZXh0cmFjdCB0aGUgPHByaWNlPiBjaGlsZCBlbGVtZW50cw0KICBwcmljZSA8LSB4cGF0aFNBcHBseShhQm9vaywgDQogICAgICAgICAgICAgICAgICAgICAgICJwcmljZVsuL0BjdXJyZW5jeT0nVVMkJ10iLA0KICAgICAgICAgICAgICAgICAgICAgICB4bWxWYWx1ZSkNCiAgDQogIHByaW50KHByaWNlKQ0KfQ0KYGBgDQoNCiMjIFN1bW1hcnkgb2YgWE1MIEZ1bmN0aW9ucw0KDQpUaGlzIHNlY3Rpb24gcHJlc2VudHMgdGhlIG1vc3QgdXNlZnVsIGZ1bmN0aW9ucyBmcm9tIHRoZSAqKlhNTCoqIHBhY2thZ2UuIE5hdHVyYWxseSwgdGhlIGxpc3QgaXMgbm90IGV4Y2x1c2l2ZSBhbmQgeW91IHNob3VsZCBjb25zdWx0IHRoZSBbZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL1hNTC92ZXJzaW9ucy8zLjk5LTAuMTApIGZvciB0aGUgcGFja2FnZSBmb3IgbW9yZSBpbmZvcm1hdGlvbi4NCg0KLSAgIGB4bWxWYWx1ZWANCi0gICBgeG1sU2l6ZWANCi0gICBgeG1sUm9vdGANCi0gICBgeG1sQXR0cnNgDQotICAgYHhtbHhwYXRoU0FwcGx5YA0KLSAgIGB4bWxQYXJzZWANCi0gICBgeG1sTmFtZWANCi0gICBgeG1sQ2hpbGRyZW5gDQoNCiMjIFN1bW1hcnkNCg0KVGhlICoqWE1MKiogcGFja2FnZSBwcm92aWRlcyBudW1lcm91cyBmdW5jdGlvbnMgZm9yIGV4dHJhY3RpbmcgZGF0YSBmcm9tIFhNTCBkb2N1bWVudHMgaW4gUi4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIEZpbGVzICYgUmVzb3VyY2VzDQoNCmBgYHtyIHppcEZpbGVzLCBlY2hvPUZBTFNFfQ0KemlwTmFtZSA9IHNwcmludGYoIkxlc3NvbkZpbGVzLSVzLSVzLnppcCIsIA0KICAgICAgICAgICAgICAgICBwYXJhbXMkY2F0ZWdvcnksDQogICAgICAgICAgICAgICAgIHBhcmFtcyRudW1iZXIpDQoNCnRleHRBTGluayA9IHBhc3RlMCgiQWxsIEZpbGVzIGZvciBMZXNzb24gIiwgDQogICAgICAgICAgICAgICBwYXJhbXMkY2F0ZWdvcnksIi4iLHBhcmFtcyRudW1iZXIpDQoNCiMgZG93bmxvYWRGaWxlc0xpbmsoKSBpcyBpbmNsdWRlZCBmcm9tIF9pbnNlcnQyREIuUg0Ka25pdHI6OnJhd19odG1sKGRvd25sb2FkRmlsZXNMaW5rKCIuIiwgemlwTmFtZSwgdGV4dEFMaW5rKSkNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgU2VlIEFsc28NCg0KLSAgIFs2LjMwMyBEYXRhIFJldHJpZXZhbCBmcm9tIFhNTCB2aWEgWFBhdGggaW4gUl0oaHR0cDovL2FydGlmaWNpdW0udXMvbGVzc29ucy8wNi5yL2wtNi0zMDMteHBhdGgtaW4tci9sLTYtMzAzLmh0bWwpDQotICAgWzYuMzA1IFByb2Nlc3MgWE1MIERPTSB2aWEgWFBhdGggYW5kIE5vZGUgVHJhdmVyc2FsXShodHRwOi8vYXJ0aWZpY2l1bS51cy9sZXNzb25zLzA2LnIvbC02LTMwNS1wcm9jLXhtbC1kb20teHBhdGgtaW4tci9sLTYtMzA1Lmh0bWwpDQotICAgWzYuMzIzIExvYWQgU2ltcGxlIFhNTCBpbnRvIERhdGFmcmFtZSBpbiBSIHVzaW5nIHhtbFRvRGF0YUZyYW1lKCldKGh0dHA6Ly9hcnRpZmljaXVtLnVzL2xlc3NvbnMvMDYuci9sLTYtMzIzLWxvYWQteG1sLXhtbFRvRGF0YUZyYW1lL2wtNi0zMjMuaHRtbCkNCi0gICBbNi4zMjQgVHJhdmVyc2UgYW5kIFBhcnNlIFhNTCBET00gaW4gUl0oaHR0cDovL2FydGlmaWNpdW0udXMvbGVzc29ucy8wNi5yL2wtNi0zMjQtcGFyc2UteG1sLWRvbS9sLTYtMzI0Lmh0bWwpDQotICAgWzYuMzI4IFBhcnNpbmcgYW4gWE1MIERvY3VtZW50IGFuZCBTYXZpbmcgdG8gU1FMaXRlIERhdGFiYXNlIGluIFJdKGh0dHA6Ly9hcnRpZmljaXVtLnVzL2xlc3NvbnMvMDYuci9sLTYtMzI4LXhtbC10by1yZWxkYi1zcWxpdGUvbC02LTMyOC5odG1sKQ0KDQojIyBSZWZlcmVuY2VzDQoNCltYTUwgUGFja2FnZSAzLjk5LTAuMTAuIFIgRG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL1hNTC92ZXJzaW9ucy8zLjk5LTAuMTApDQoNCiMjIEVycmF0YQ0KDQpbTGV0IHVzIGtub3ddKGh0dHBzOi8vZm9ybS5qb3Rmb3JtLmNvbS8yMTIxODcwNzI3ODQxNTcpe3RhcmdldD0iX2JsYW5rIn0uDQo=