1 Downloading Files and Using API Clients

1.1 Introduction: Working With Web Data in R

1.1.1 Downloading files and reading them into R

In this first exercise we’re going to look at reading already-formatted datasets - CSV or TSV files, with which you’ll no doubt be familiar! - into R from the internet. This is a lot easier than it might sound because R’s file-reading functions accept not just file paths, but also URLs.

# Here are the URLs! As you can see they're just normal strings
csv_url <- "http://s3.amazonaws.com/assets.datacamp.com/production/course_1561/datasets/chickwts.csv"
tsv_url <- "http://s3.amazonaws.com/assets.datacamp.com/production/course_3026/datasets/tsv_data.tsv"

# Read a file in from the CSV URL and assign it to csv_data
csv_data <- read.csv(csv_url)

# Read a file in from the TSV URL and assign it to tsv_data
tsv_data <- read.delim(tsv_url)

# Examine the objects with head()
head(csv_data)
head(tsv_data)

1.1.2 Saving raw files to disk

Sometimes just reading the file in from the web is enough, but often you’ll want to store it locally so that you can refer back to it. This also lets you avoid having to spend the start of every analysis session twiddling your thumbs while particularly large files download.

Helpfully, R has download.file(), a function that lets you do just that: download a file to a location of your choice on your computer. It takes two arguments; url, indicating the URL to read from, and destfile, the destination to write the downloaded file to. In this case, we’ve pre-defined the URL - once again, it’s csv_url.

# Download the file with download.file()
download.file(url = csv_url, destfile = "feed_data.csv")
trying URL 'http://s3.amazonaws.com/assets.datacamp.com/production/course_1561/datasets/chickwts.csv'
Content type '' length 1053 bytes
downloaded 1053 bytes
# Read it in with read.csv()
csv_data <- read.csv("feed_data.csv")

1.1.3 Saving formatted files to disk

Whether you’re downloading the raw files with download.file() or using read.csv() and its sibling functions, at some point you’re probably going to find the need to modify your input data, and then save the modified data to disk so you don’t lose the changes.

You could use write.table(), but then you have to worry about accidentally writing out data in a format R can’t read back in. An easy way to avoid this risk is to use saveRDS() and readRDS(), which save R objects in an R-specific file format, with the data structure intact. That means you can use it for any type of R object (even ones that don’t turn into tables easily), and not worry you’ll lose data reading it back in. saveRDS() takes two arguments, object, pointing to the R object to save and file pointing to where to save it to. readRDS() expects file, referring to the path to the RDS file to read in.

# Add a new column: square_weight
csv_data$square_weight <- csv_data$weight^2

# Save it to disk with saveRDS()
saveRDS(object = csv_data, file = "modified_feed_data.RDS")

# Read it back in with readRDS()
modified_feed_data <- readRDS(file = "modified_feed_data.RDS")

# Examine modified_feed_data
str(modified_feed_data)
'data.frame':   71 obs. of  3 variables:
 $ weight       : int  179 160 136 227 217 168 108 124 143 140 ...
 $ feed         : chr  "horsebean" "horsebean" "horsebean" "horsebean" ...
 $ square_weight: num  32041 25600 18496 51529 47089 ...

1.2 Understanding Application Programming Interfaces

1.2.1 Using API clients

So we know that APIs are server components to make it easy for your code to interact with a service and get data from it. We also know that R features many “clients” - packages that wrap around connections to APIs so you don’t have to worry about the details.

Let’s look at a really simple API client - the pageviews package, which acts as a client to Wikipedia’s API of pageview data. As with other R API clients, it’s formatted as a package, and lives on CRAN - the central repository of R packages. The goal here is just to show how simple clients are to use: they look just like other R code, because they are just like other R code.

# Load pageviews
library(pageviews)
package 㤼㸱pageviews㤼㸲 was built under R version 4.0.3
# Get the pageviews for "Hadley Wickham"
hadley_pageviews <- article_pageviews(project = "en.wikipedia", "Hadley Wickham")

# Examine the resulting object
str(hadley_pageviews)
'data.frame':   1 obs. of  8 variables:
 $ project    : chr "wikipedia"
 $ language   : chr "en"
 $ article    : chr "Hadley_Wickham"
 $ access     : chr "all-access"
 $ agent      : chr "all-agents"
 $ granularity: chr "daily"
 $ date       : POSIXct, format: "2015-10-01"
 $ views      : num 53

– ## Access tokens and APIs

1.2.2 Using access tokens

As we discussed in the last video, it’s common for APIs to require access tokens - unique keys that verify you’re authorised to use a service. They’re usually pretty easy to use with an API client.

To show how they work, and how easy it can be, we’re going to use the R client for the Wordnik dictionary and word use service - ‘birdnik’ - and an API token we prepared earlier. Birdnik is fairly simple and lets you get all sorts of interesting information about word usage in published works. For example, to get the frequency of the use of the word “chocolate”, you would write:

word_frequency(api_key, "chocolate")
# Load birdnik
library(birdnik)
library(httr)

# Get the word frequency for "vector", using api_key to access it
vector_frequency <- word_frequency(api_key, "vector")

2 Using httr to interact with APIs directly

2.1 GET and POST requests in theory

2.1.1 GET requests in practice

To start with you’re going to make a GET request. As discussed in the video, this is a request that asks the server to give you a particular piece of data or content (usually specified in the URL). These make up the majority of the requests you’ll make in a data science context, since most of the time you’ll be getting data from servers, not giving it to them.

To do this you’ll use the httr package, written by Hadley Wickham (of course), which makes HTTP requests extremely easy. You’re going to make a very simple GET request, and then inspect the output to see what it looks like.

#Load the httr package
library(httr)

# Make a GET request to http://httpbin.org/get
get_result <- GET("http://httpbin.org/get")

# Print it to inspect it
get_result
Response [http://httpbin.org/get]
  Date: 2020-11-19 17:39
  Status: 200
  Content-Type: application/json
  Size: 364 B
{
  "args": {}, 
  "headers": {
    "Accept": "application/json, text/xml, application/xml, */*", 
    "Accept-Encoding": "deflate, gzip", 
    "Host": "httpbin.org", 
    "User-Agent": "libcurl/7.64.1 r-curl/4.3 httr/1.4.2", 
    "X-Amzn-Trace-Id": "Root=1-5fb6adee-31fe36137bf1bb7a03afc3ea"
  }, 
  "origin": "92.18.64.211", 
...

As you can see from inspecting the output, there are a lot of parts of a HTTP response.

2.1.2 POST requests in practice

Next we’ll look at POST requests, also made through httr, with the function (you’ve guessed it) POST(). Rather than asking the server to give you something, as in GET requests, a POST request asks the server to accept something from you. They’re commonly used for things like file upload, or authentication. As a result of their use for uploading things, POST() accepts not just a url but also a body argument containing whatever you want to give to the server.

You’ll make a very simple POST request, just uploading a piece of text, and then inspect the output to see what it looks like.

# Load the httr package
library(httr)

# Make a POST request to http://httpbin.org/post with the body "this is a test"
post_result <- POST("http://httpbin.org/post", body = "this is a test")

# Print it to inspect it
post_result
Response [http://httpbin.org/post]
  Date: 2020-11-19 17:40
  Status: 200
  Content-Type: application/json
  Size: 471 B
{
  "args": {}, 
  "data": "this is a test", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "application/json, text/xml, application/xml, */*", 
    "Accept-Encoding": "deflate, gzip", 
    "Content-Length": "14", 
    "Host": "httpbin.org", 
...

The output for POST requests looks pretty similar to that for GET requests, although (in this case) the body of your message is included - this is a test.

2.1.3 Extracting the response

Making requests is all well and good, but it’s also not why you’re here. What we really want to do is get the data the server sent back, which can be done with httr’s content() function. You pass it an object returned from a GET (or POST, or DELETE, or…) call, and it spits out whatever the server actually sent in an R-compatible structure.

We’re going to demonstrate that now, using a slightly more complicated URL than before - in fact, using a URL from the Wikimedia pageviews system you dealt with through the pageviews package, which is stored as url. Without looking too much at the structure for the time being (we’ll get to that later) this request asks for the number of pageviews to the English-language Wikipedia’s “Hadley Wickham” article on 1 and 2 January 2017.

url <- "https://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/en.wikipedia.org/all-access/all-agents/Hadley_Wickham/daily/20170101/20170102"

# Make a GET request to url and save the results
pageview_response <- GET(url)

# Call content() to retrieve the data the server sent back
pageview_data <- content(pageview_response)

# Examine the results with str()
str(pageview_data)
List of 1
 $ items:List of 2
  ..$ :List of 7
  .. ..$ project    : chr "en.wikipedia"
  .. ..$ article    : chr "Hadley_Wickham"
  .. ..$ granularity: chr "daily"
  .. ..$ timestamp  : chr "2017010100"
  .. ..$ access     : chr "all-access"
  .. ..$ agent      : chr "all-agents"
  .. ..$ views      : int 45
  ..$ :List of 7
  .. ..$ project    : chr "en.wikipedia"
  .. ..$ article    : chr "Hadley_Wickham"
  .. ..$ granularity: chr "daily"
  .. ..$ timestamp  : chr "2017010200"
  .. ..$ access     : chr "all-access"
  .. ..$ agent      : chr "all-agents"
  .. ..$ views      : int 86

As you can see, the result of extracting the content is a list, which is pretty common (but not uniform) to API responses.

2.2 Graceful httr

2.2.1 Handling http failures

As mentioned, HTTP calls can go wrong. Handling that can be done with httr’s http_error() function, which identifies whether a server response contains an error. If the response does contain an error, calling http_error() over the response will produce TRUE; otherwise, FALSE. You can use this for really fine-grained control over results. For example, you could check whether the request contained an error, and (if so) issue a warning and re-try the request.

For now we’ll try something a bit simpler - issuing a warning that something went wrong if http_error() returns TRUE, and printing the content if it doesn’t.

fake_url <- "http://google.com/fakepagethatdoesnotexist"

# Make the GET request
request_result <- GET(fake_url)

# Check request_result
if(http_error(request_result)){
    warning("The request failed")
} else {
    content(request_result)
}
The request failed

Error handling is really important for writing robust code, and it looks like you’ve got a good handle on it.

2.2.2 Constructing queries (Part I)

As briefly discussed in the previous video, the actual API query (which tells the API what you want to do) tends to be in one of the two forms. The first is directory-based, where values are separated by / marks within the URL. The second is parameter-based, where all the values exist at the end of the URL and take the form of key=value.

Constructing directory-based URLs can be done via paste(), which takes an unlimited number of strings, along with a separator, as sep. So to construct http://swapi.co/api/vehicles/12 we’d call: paste(“http://swapi.co”, “api”, “vehicles”, “12”, sep = “/”) We can play with SWAPI, mentioned above, which is an API chock full of star wars data. This time, rather than a vehicle, we’ll look for a person.

# Construct a directory-based API URL to `http://swapi.co/api`,
# looking for person `1` in `people`
directory_url <- paste("http://swapi.co/api", "people", 1, sep = "/")

# Make a GET call with it
result <- GET(directory_url)

2.2.3 Constructing queries (Part II)

As mentioned (albeit briefly) in the last exercise, there are also parameter based URLs, where all the query values exist at the end of the URL and take the form of key=value - they look something like http://fakeurl.com/foo.php?country=spain&food=goulash

Constructing parameter-based URLs can also be done with paste(), but the easiest way to do it is with GET() and POST() themselves, which accept a query argument consisting of a list of keys and values. So, to continue with the food-based examples, we could construct fakeurl.com/api.php?fruit=peaches&day=thursday with: GET(“fakeurl.com/api.php”, query = list(fruit = “peaches”, day = “thursday”))

# Create list with nationality and country elements
query_params <- list(nationality = "americans", 
    country = "antigua")
    
# Make parameter-based call to httpbin, with query_params
parameter_response <- GET("https://httpbin.org/get", query = query_params)

# Print parameter_response
parameter_response
Response [https://httpbin.org/get?nationality=americans&country=antigua]
  Date: 2020-11-19 17:43
  Status: 200
  Content-Type: application/json
  Size: 464 B
{
  "args": {
    "country": "antigua", 
    "nationality": "americans"
  }, 
  "headers": {
    "Accept": "application/json, text/xml, application/xml, */*", 
    "Accept-Encoding": "deflate, gzip", 
    "Host": "httpbin.org", 
    "User-Agent": "libcurl/7.64.1 r-curl/4.3 httr/1.4.2", 
...

Did you notice the parameters you passed in were listed in the args of the response?

2.3 Respectful API usage

2.3.1 Using user agents

informative user-agents are a good way of being respectful of the developers running the API you’re interacting with. They make it easy for them to contact you in the event something goes wrong. I always try to include:

My email address; A URL for the project the code is a part of, if it’s got a URL. Building user agents is done by passing a call to user_agent() into the GET() or POST() request; something like:

GET("http://url.goes.here/", user_agent("somefakeemail@domain.com http://project.website"))

In the event you don’t have a website, a short one-sentence description of what the project is about serves pretty well.

# Do not change the url
url <- "https://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/en.wikipedia/all-access/all-agents/Aaron_Halfaker/daily/2015100100/2015103100"

# Add the email address and the test sentence inside user_agent()
server_response <- GET(url, user_agent("my@email.address this is a test"))

From your end, the request looks exactly the same with or without a user agent, but for the server the difference can be vital.

2.3.2 Rate-limiting

The next stage of respectful API usage is rate-limiting: making sure you only make a certain number of requests to the server in a given time period. Your limit will vary from server to server, but the implementation is always pretty much the same and involves a call to Sys.sleep(). This function takes one argument, a number, which represents the number of seconds to “sleep” (pause) the R session for. So if you call Sys.sleep(15), it’ll pause for 15 seconds before allowing further code to run.

As you can imagine, this is really useful for rate-limiting. If you are only allowed 4 requests a minute? No problem! Just pause for 15 seconds between each request and you’re guaranteed to never exceed it. Let’s demonstrate now by putting together a little loop that sends multiple requests on a 5-second time delay. We’ll use httpbin.org ’s APIs, which allow you to test different HTTP libraries.

# Construct a vector of 2 URLs
urls <- c("http://httpbin.org/status/404", "http://httpbin.org/status/301")

for(url in urls){
    # Send a GET request to url
    result <- GET(url)
    # Delay for 5 seconds between requests
    Sys.sleep(5)
}

2.3.3 Tying it all together

Using everything that you learned in the chapter, let’s make a simple replica of one of the ‘pageviews’ functions - building queries, sending GET requests (with an appropriate user agent) and handling the output in a fault-tolerant way. You’ll build this function up piece by piece in this exercise.

To output an error, you will use the function stop(), which takes a string as an argument, stops the execution of the program, and outputs the string as an error. You can try it right now by running stop(“This is an error”). First, get the function to construct the url.

get_pageviews <- function(article_title){
  url <- paste(
    "https://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/en.wikipedia/all-access/all-agents", 
    # Include article title
    article_title, 
    "daily/2015100100/2015103100", 
    sep = "/"
  ) 
  url
}

Now, make the request

get_pageviews <- function(article_title){
  url <- paste(
    "https://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/en.wikipedia/all-access/all-agents", 
    article_title, 
    "daily/2015100100/2015103100", 
    sep = "/"
  ) 
  # Get the webpage  
  response <- GET(url, config = user_agent("my@email.com this is a test")) 
  response
}

Now, add an error check.

get_pageviews <- function(article_title){
  url <- paste(
    "https://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/en.wikipedia/all-access/all-agents", 
    article_title, 
    "daily/2015100100/2015103100", 
    sep = "/"
  )   
  response <- GET(url, user_agent("my@email.com this is a test")) 
  # Is there an HTTP error?
  if(http_error(response)){ 
    # Throw an R error
     stop("the request failed") 
  }
  response
}

Finally, instead of returning response, return the content() of the response.

get_pageviews <- function(article_title){
  url <- paste(
    "https://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/en.wikipedia/all-access/all-agents", 
    article_title, 
    "daily/2015100100/2015103100", 
    sep = "/"
  )   
  response <- GET(url, user_agent("my@email.com this is a test")) 
  # Is there an HTTP error?
  if(http_error(response)){ 
    # Throw an R error
    stop("the request failed") 
  }
  # Return the response's content
  content(response)
}

3 Handling JSON and XML

3.1 JSON

3.1.1 Parsing JSON

While JSON is a useful format for sharing data, your first step will often be to parse it into an R object, so you can manipulate it with R.

The content() function in httr retrieves the content from a request. It takes an as argument that specifies the type of output to return. You’ve already seen that as = “text” will return the content as a character string which is useful for checking the content is as you expect.

If you don’t specify as, the default as = “parsed” is used. In this case the type of content() will be guessed based on the header and content() will choose an appropriate parsing function. For JSON this function is fromJSON() from the jsonlite package. If you know your response is JSON, you may want to use fromJSON() directly.

To practice, you’ll retrieve some revision history from the Wikipedia API, check it is JSON, then parse it into a list two ways.

rev_history <- function (title, format = "json") {
    if (title != "Hadley Wickham") {
        stop("rev_history() only works for `title = \"Hadley Wickham\"`")
    }
    if (format == "json") {
        resp <- readRDS("had_rev_json.rds")
    }
    else if (format == "xml") {
        resp <- readRDS("had_rev_xml.rds")
    }
    else {
        stop("Invalid format supplied, try \"json\" or \"xml\"")
    }
    resp
}
# Get revision history for "Hadley Wickham"
resp_json <- pageview_response

# Check http_type() of resp_json
http_type(resp_json)
[1] "application/json"
# Examine returned text with content()
content(resp_json, as = "text")
[1] "{\"items\":[{\"project\":\"en.wikipedia\",\"article\":\"Hadley_Wickham\",\"granularity\":\"daily\",\"timestamp\":\"2017010100\",\"access\":\"all-access\",\"agent\":\"all-agents\",\"views\":45},{\"project\":\"en.wikipedia\",\"article\":\"Hadley_Wickham\",\"granularity\":\"daily\",\"timestamp\":\"2017010200\",\"access\":\"all-access\",\"agent\":\"all-agents\",\"views\":86}]}"
# Parse response with content()
content(resp_json, as = "parsed")
$items
$items[[1]]
$items[[1]]$project
[1] "en.wikipedia"

$items[[1]]$article
[1] "Hadley_Wickham"

$items[[1]]$granularity
[1] "daily"

$items[[1]]$timestamp
[1] "2017010100"

$items[[1]]$access
[1] "all-access"

$items[[1]]$agent
[1] "all-agents"

$items[[1]]$views
[1] 45


$items[[2]]
$items[[2]]$project
[1] "en.wikipedia"

$items[[2]]$article
[1] "Hadley_Wickham"

$items[[2]]$granularity
[1] "daily"

$items[[2]]$timestamp
[1] "2017010200"

$items[[2]]$access
[1] "all-access"

$items[[2]]$agent
[1] "all-agents"

$items[[2]]$views
[1] 86
# Parse returned text with fromJSON()
library(jsonlite)
fromJSON(content(resp_json, as = "text"))
$items
NA

3.2 Manipulating JSON

3.2.1 Manipulating parsed JSON

As you saw in the video, the output from parsing JSON is a list. One way to extract relevant data from that list is to use a package specifically designed for manipulating lists, rlist.

rlist provides two particularly useful functions for selecting and combining elements from a list: list.select() and list.stack(). list.select() extracts sub-elements by name from each element in a list. For example using the parsed movies data from the video (movies_list), we might ask for the title and year elements from each element:

list.select(movies_list, title, year)

The result is still a list, that is where list.stack() comes in. It will stack the elements of a list into a data frame.

list.stack(
    list.select(movies_list, title, year)
)

First, you’ll need to figure out where the revisions are. Examine the output from the str() call. Can you see where the list of 5 revisions is?


url <- "https://en.wikipedia.org/w/api.php?action=query&titles=Hadley%20Wickham&prop=revisions&rvprop=timestamp%7Cuser%7Ccomment%7Ccontent&rvlimit=5&format=json&rvdir=newer&rvstart=2015-01-14T17%3A12%3A45Z&rvsection=0"

resp_json <- GET(url)
# Load rlist
library(rlist)
package 㤼㸱rlist㤼㸲 was built under R version 4.0.3Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
# Examine output of this code
str(content(resp_json), max.level = 4)
List of 3
 $ continue:List of 2
  ..$ rvcontinue: chr "20150528042700|664370232"
  ..$ continue  : chr "||"
 $ warnings:List of 2
  ..$ main     :List of 1
  .. ..$ *: chr "Subscribe to the mediawiki-api-announce mailing list at <https://lists.wikimedia.org/mailman/listinfo/mediawiki"| __truncated__
  ..$ revisions:List of 1
  .. ..$ *: chr "Because \"rvslots\" was not specified, a legacy format has been used for the output. This format is deprecated,"| __truncated__
 $ query   :List of 1
  ..$ pages:List of 1
  .. ..$ 41916270:List of 4
  .. .. ..$ pageid   : int 41916270
  .. .. ..$ ns       : int 0
  .. .. ..$ title    : chr "Hadley Wickham"
  .. .. ..$ revisions:List of 5

Use list.select() to pull out the user and timestamp elements from each revision, store in user_time.

# Store revision list
revs <- content(resp_json)$query$pages$`41916270`$revisions
# Extract the user element
user_time <- list.select(revs, user, timestamp)

# Print user_time
user_time
[[1]]
[[1]]$user
[1] "214.28.226.251"

[[1]]$timestamp
[1] "2015-01-14T17:12:45Z"


[[2]]
[[2]]$user
[1] "73.183.151.193"

[[2]]$timestamp
[1] "2015-01-15T15:49:34Z"


[[3]]
[[3]]$user
[1] "FeanorStar7"

[[3]]$timestamp
[1] "2015-01-24T16:34:31Z"


[[4]]
[[4]]$user
[1] "KasparBot"

[[4]]$timestamp
[1] "2015-04-26T19:18:17Z"


[[5]]
[[5]]$user
[1] "Spkal"

[[5]]$timestamp
[1] "2015-05-06T18:24:57Z"
# Stack to turn into a data frame
list.stack(user_time)

rlist is designed to make working with lists easy, so if find you are working with JSON data a lot, you should explore more of its functionality.

3.2.2 Reformatting JSON

Of course you don’t have to use rlist. You can achieve the same thing by using functions from base R or the tidyverse. In this exercise you’ll repeat the task of extracting the username and timestamp using the dplyr package which is part of the tidyverse.

Conceptually, you’ll take the list of revisions, stack them into a data frame, then pull out the relevant columns.

dplyr’s bind_rows() function takes a list and turns it into a data frame. Then you can use select() to extract the relevant columns. And of course if we can make use of the %>% (pipe) operator to chain them all together.

# Load dplyr
library(dplyr)

Attaching package: 㤼㸱dplyr㤼㸲

The following objects are masked from 㤼㸱package:stats㤼㸲:

    filter, lag

The following objects are masked from 㤼㸱package:base㤼㸲:

    intersect, setdiff, setequal, union
# Pull out revision list
revs <- content(resp_json)$query$pages$`41916270`$revisions

# Extract user and timestamp
revs %>%
  bind_rows %>%           
  select(user, timestamp)

3.3 XML structure

3.3.1 Examining XML documents

Just like JSON, you should first verify the response is indeed XML with http_type() and by examining the result of content(r, as = “text”). Then you can turn the response into an XML document object with read_xml().

One benefit of using the XML document object is the available functions that help you explore and manipulate the document. For example xml_structure() will print a representation of the XML document that emphasizes the hierarchical structure by displaying the elements without the data.

In this exercise you’ll grab the same revision history you’ve been working with as XML, and take a look at it with xml_structure().

url <- "https://en.wikipedia.org/w/api.php?action=query&titles=Hadley%20Wickham&prop=revisions&rvprop=timestamp%7Cuser%7Ccomment%7Ccontent&rvlimit=5&format=xml&rvdir=newer&rvstart=2015-01-14T17%3A12%3A45Z&rvsection=0"


# Load xml2
library(xml2)

# Get XML revision history
resp_xml <- GET(url)

# Check response is XML 
http_type(resp_xml)
[1] "text/xml"
# Examine returned text with content()
rev_text <- content(resp_xml, as="text")

# Turn rev_text into an XML document
rev_xml <- read_xml(rev_text)

# Examine the structure of rev_xml
xml_structure(rev_xml)
<api>
  <continue [rvcontinue, continue]>
  <warnings>
    <main [space]>
      {text}
    <revisions [space]>
      {text}
  <query>
    <pages>
      <page [_idx, pageid, ns, title]>
        <revisions>
          <rev [user, anon, timestamp, contentformat, contentmodel, comment, space]>
            {text}
          <rev [user, anon, timestamp, contentformat, contentmodel, comment, space]>
            {text}
          <rev [user, timestamp, contentformat, contentmodel, comment, space]>
            {text}
          <rev [user, timestamp, contentformat, contentmodel, comment, space]>
            {text}
          <rev [user, timestamp, contentformat, contentmodel, comment, space]>
            {text}

xml_structure() helps you understand the structure of your document, without overwhelming you with content.

3.4 XPATHs

3.4.1 Extracting XML data

XPATHs are designed to specifying nodes in an XML document. Remember /node_name specifies nodes at the current level that have the tag node_name, where as //node_name specifies nodes at any level below the current level that have the tag node_name.

xml2 provides the function xml_find_all() to extract nodes that match a given XPATH. For example, xml_find_all(rev_xml, “/api”) will find all the nodes at the top level of the rev_xml document that have the tag api. Try running that in the console. You’ll get a nodeset of one node because there is only one node that satisfies that XPATH.

The object returned from xml_find_all() is a nodeset (think of it like a list of nodes). To actually get data out of the nodes in the nodeset, you’ll have to explicitly ask for it with xml_text() (or xml_double() or xml_integer()).

Use what you know about the location of the revisions data in the returned XML document extract just the content of the revision.

# Find all nodes using XPATH "/api/query/pages/page/revisions/rev"
xml_find_all(rev_xml, "/api/query/pages/page/revisions/rev")
{xml_nodeset (5)}
[1] <rev user="214.28.226.251" anon="" timestamp="2015-01-14T17:12:45Z" contentformat="text/x-wiki" contentmodel="wi ...
[2] <rev user="73.183.151.193" anon="" timestamp="2015-01-15T15:49:34Z" contentformat="text/x-wiki" contentmodel="wi ...
[3] <rev user="FeanorStar7" timestamp="2015-01-24T16:34:31Z" contentformat="text/x-wiki" contentmodel="wikitext" com ...
[4] <rev user="KasparBot" timestamp="2015-04-26T19:18:17Z" contentformat="text/x-wiki" contentmodel="wikitext" comme ...
[5] <rev user="Spkal" timestamp="2015-05-06T18:24:57Z" contentformat="text/x-wiki" contentmodel="wikitext" comment=" ...
# Find all rev nodes anywhere in document
rev_nodes <- xml_find_all(rev_xml, "//rev")

# Use xml_text() to get text from rev_nodes
# xml_text(rev_nodes)

3.4.2 Extracting XML attributes

Not all the useful data will be in the content of a node, some might also be in the attributes of a node. To extract attributes from a nodeset, xml2 provides xml_attrs() and xml_attr().

xml_attrs() takes a nodeset and returns all of the attributes for every node in the nodeset. xml_attr() takes a nodeset and an additional argument attr to extract a single named argument from each node in the nodeset.

In this exercise you’ll grab the user and anon attributes for each revision. You’ll see xml_find_first() in the sample code. It works just like xml_find_all() but it only extracts the first node it finds.

# All rev nodes
rev_nodes <- xml_find_all(rev_xml, "//rev")

# The first rev node
first_rev_node <- xml_find_first(rev_xml, "//rev")

# Find all attributes with xml_attrs()
xml_attrs(first_rev_node)
                  user                   anon              timestamp          contentformat           contentmodel 
      "214.28.226.251"                     "" "2015-01-14T17:12:45Z"          "text/x-wiki"             "wikitext" 
               comment                  space 
                    ""             "preserve" 
# Find user attribute with xml_attr()
xml_attr(first_rev_node, "user")
[1] "214.28.226.251"
# Find user attribute for all rev nodes
xml_attr(rev_nodes, "user")
[1] "214.28.226.251" "73.183.151.193" "FeanorStar7"    "KasparBot"      "Spkal"         
# Find anon attribute for all rev nodes
xml_attr(rev_nodes, "anon")
[1] "" "" NA NA NA

Did you notice that if a node didn’t have the anon attribute xml_attr() returned an NA?

3.4.3 Wrapup: returning nice API output

How might all this work together? A useful API function will retrieve results from an API and return them in a useful form. In Chapter 2, you finished up by writing a function that retrieves data from an API that relied on content() to convert it to a useful form. To write a more robust API function you shouldn’t rely on content() but instead parse the data yourself.

To finish up this chapter you’ll do exactly that: write get_revision_history() which retrieves the XML data for the revision history of page on Wikipedia, parses it, and returns it in a nice data frame.

So that you can focus on the parts of the function that parse the return object, you’ll see your function calls rev_history() to get the response from the API. You can assume this function returns the raw response and follows the best practices you learnt in Chapter 2, like using a user agent, and checking the response status.

get_revision_history <- function(article_title){
  # Get raw revision response
  rev_resp <- resp_xml
  
  # Turn the content() of rev_resp into XML
  rev_xml <- read_xml(content(rev_resp, "text"))
  
  # Find revision nodes
  rev_nodes <- xml_find_all(rev_xml, "//rev")

  # Parse out usernames
  user <- xml_attr(rev_nodes, "user")
  
  # Parse out timestamps
  timestamp <- readr::parse_datetime(xml_attr(rev_nodes, "timestamp"))
  
  # Parse out content
  content <- xml_text(rev_nodes)
  
  # Return data frame 
  data.frame(user = user,
    timestamp = timestamp,
    content = substr(content, 1, 40))
}

# Call function for "Hadley Wickham"
get_revision_history(article_title = "Hadley Wickham")
NA

Your function parsed the XML data, but you could have just as easily parsed the JSON data.

4 Web scraping with XPATHs

4.1 Web scraping 101

4.1.1 Reading HTML

The first step with web scraping is actually reading the HTML in. This can be done with a function from xml2, which is imported by rvest - read_html(). This accepts a single URL, and returns a big blob of XML that we can use further on.

We’re going to experiment with that by grabbing Hadley Wickham’s wikipedia page, with rvest, and then printing it just to see what the structure looks like.

# Load rvest
library(rvest)

# Hadley Wickham's Wikipedia page
test_url <- "https://en.wikipedia.org/wiki/Hadley_Wickham"

# Read the URL stored as "test_url" with read_html()
test_xml <- read_html(test_url)

# Print test_xml
test_xml
{html_document}
<html class="client-nojs" lang="en" dir="ltr">
[1] <head>\n<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">\n<meta charset="UTF-8">\n<title>Hadl ...
[2] <body class="mediawiki ltr sitedir-ltr mw-hide-empty-elt ns-0 ns-subject mw-editable page-Hadley_Wickham rootpag ...

4.1.2 Extracting nodes by XPATH

Now you’ve got a HTML page read into R. Great! But how do you get individual, identifiable pieces of it?

The answer is to use html_node(), which extracts individual chunks of HTML from a HTML document. There are a couple of ways of identifying and filtering nodes, and for now we’re going to use XPATHs: unique identifiers for individual pieces of a HTML document.

These can be retrieved using a browser gadget we’ll talk about later - in the meanwhile the XPATH for the information box in the page you just downloaded is stored as test_node_xpath. We’re going to retrieve the box from the HTML doc with html_node(), using test_node_xpath as the xpath argument.

test_node_xpath <- "//*[contains(concat( \" \", @class, \" \" ), concat( \" \", \"vcard\", \" \" ))]"
test_node_xpath
[1] "//*[contains(concat( \" \", @class, \" \" ), concat( \" \", \"vcard\", \" \" ))]"
# Use html_node() to grab the node with the XPATH stored as `test_node_xpath`
node <- html_node(x = test_xml, xpath = test_node_xpath)

# Print the first element of the result
node[1]
$node
<pointer: 0x000001516b97e6d0>

XML nodes are the building block of an XML document - extracting them leads to everything else.

4.2 HTML structure

4.2.1 Extracting names

The first thing we’ll grab is a name, from the first element of the previously extracted table (now stored as table_element). We can do this with html_name(). As you may recall from when you printed it, the element has the tag …

, so we’d expect the name to be, well, table.

# Extract the name of table_element
element_name <- html_name(node)

# Print the name
element_name
[1] "table"

You’ve started extracting components from HTML and XML nodes. The tag might not seem important (and most of the time, it’s not) but it’s a good first step, and the actual node contents (text, say) is something we’ll move on to next.

4.2.2 Extracting values

Just knowing the type of HTML object a node is isn’t much use, though (although it can be very helpful). What we really want is to extract the actual text stored within the value.

We can do that with (shocker) html_text(), another convenient rvest function that accepts a node and passes back the text inside it. For this we’ll want a node within the extracted element - specifically, the one containing the page title. The xpath value for that node is stored as second_xpath_val.

Using this xpath value, extract the node within table_element that we want, and then use html_text to extract the text, before printing it.

second_xpath_val <- "//*[contains(concat( \" \", @class, \" \" ), concat( \" \", \"fn\", \" \" ))]"
# Extract the element of table_element referred to by second_xpath_val and store it as page_name
page_name <- html_node(x = test_xml, xpath = second_xpath_val)

# Extract the text from page_name
page_title <- html_text(page_name)

# Print page_title
page_title
[1] "Hadley Wickham"

Text extraction is most of what you’re likely to do with XML - after all, the content of an XML tag is almost always the important value. If it’s consistently a set of digits, say, you can always use as.integer() or as.numeric() to turn it from a string, into a number.

4.3 Reformatting Data

4.3.1 Extracting tables

The data from Wikipedia that we’ve been playing around with can be extracted bit by bit and cleaned up manually, but since it’s a table, we have an easier way of turning it into an R object. rvest contains the function html_table() which, as the name suggests, extracts tables. It accepts a node containing a table object, and outputs a data frame.

# Turn table_element into a data frame and assign it to wiki_table
wiki_table <- html_table(node)

# Print wiki_table
wiki_table

Being able to extract tables directly is a massive speedup, since otherwise they’re a ton of different nested tags.

4.3.2 Cleaning a data frame

n the last exercise, we looked at extracting tables with html_table(). The resulting data frame was pretty clean, but had two problems - first, the column names weren’t descriptive, and second, there was an empty row.

In this exercise we’re going to look at fixing both of those problems. First, column names. Column names can be cleaned up with the colnames() function. You call it on the object you want to rename, and then assign to that call a vector of new names.

The missing row, meanwhile, can be removed with the subset() function. subset takes an object, and a condition. For example, if you have a data frame df containing a column x, you could run

subset(df, !x == "")

to remove all rows from df consisting of empty strings ("") in the column x.

# Rename the columns of wiki_table
colnames(wiki_table) <- c("key", "value")

# Remove the empty row from wiki_table
cleaned_table <- subset(wiki_table, !key == "")

# Print cleaned_table
cleaned_table

Cleaning up data, or ‘munging’, is a really common thing to have to do, particularly when someone else picked how it’s formatted. If you can scrape data and clean it, you can do anything.

5 CSS Web Scraping and Final Case Study

5.1 CSS web scraping in theory

Using CSS to scrape nodes As mentioned in the video, CSS is a way to add design information to HTML, that instructs the browser on how to display the content. You can leverage these design instructions to identify content on the page.

You’ve already used html_node(), but it’s more common with CSS selectors to use html_nodes() since you’ll often want more than one node returned. Both functions allow you to specify a css argument to use a CSS selector, instead of specifying the xpath argument.

# Select the table elements
html_nodes(test_xml, css = "table")
{xml_nodeset (3)}
[1] <table class="infobox biography vcard" style="width:22em"><tbody>\n<tr><th colspan="2" style="text-align:center; ...
[2] <table class="nowraplinks mw-collapsible autocollapse navbox-inner" style="border-spacing:0;background:transpare ...
[3] <table class="nowraplinks hlist navbox-inner" style="border-spacing:0;background:transparent;color:inherit"><tbo ...
# Select elements with class = "infobox"
html_nodes(test_xml, css = ".infobox")
{xml_nodeset (1)}
[1] <table class="infobox biography vcard" style="width:22em"><tbody>\n<tr><th colspan="2" style="text-align:center; ...
# Select elements with id = "firstHeading"
html_nodes(test_xml, css = "#firstHeading")
{xml_nodeset (1)}
[1] <h1 id="firstHeading" class="firstHeading" lang="en">Hadley Wickham</h1>

5.2 Scraping names

You might have noticed in the previous exercise, to select elements with a certain class, you add a . in front of the class name. If you need to select an element based on its id, you add a # in front of the id name.

For example if this element was inside your HTML document:

<h1 class = "heading" id = "intro">
  Introduction
</h1>

You could select it by its class using the CSS selector “.heading”, or by its id using the CSS selector “#intro”.

Once you’ve selected an element with a CSS selector, you can get the element tag name just like you did with XPATH selectors, with html_name().

# Extract element with class infobox
infobox_element <- html_nodes(test_xml, css = ".infobox")

# Get tag name of infobox_element
element_name <- html_name(infobox_element)

# Print element_name
element_name
[1] "table"

5.2.1 Scraping text

Of course you can get the contents of a node extracted using a CSS selector too, with html_text().

# Extract element with class fn
page_name <- html_node(x = infobox_element, ".fn")

# Get contents of page_name
page_title <- html_text(page_name)

# Print page_title
page_title
[1] "Hadley Wickham"

Why do you think the class for this element is fn? I suspect it’s short for full name.

5.3 Final case study: Introduction

5.3.1 API calls

Your first step is to use the Wikipedia API to get the page contents for a specific page. We’ll continue to work with the Hadley Wickham page, but as your last exercise, you’ll make it more general.

To get the content of a page from the Wikipedia API you need to use a parameter based URL. The URL you want is

https://en.wikipedia.org/w/api.php?action=parse&page=Hadley%20Wickham&format=xml

which specifies that you want the parsed content (i.e the HTML) for the “Hadley Wickham” page, and the API response should be XML.

# Load httr
library(httr)

# The API url
base_url <- "https://en.wikipedia.org/w/api.php"

# Set query parameters
query_params <- list(action = "parse", 
  page = "Hadley Wickham", 
  format = "xml")

# Get data from API
resp <- GET(url = base_url, query = query_params)
    
# Parse response
resp_xml <- content(resp)

5.3.2 Extracting information

Now we have a response from the API, we need to extract the HTML for the page from it. It turns out the HTML is stored in the contents of the XML response. Take a look, by using xml_text() to pull out the text from the XML response:

xml_text(resp_xml)
# Load rvest
library(rvest)

# Read page contents as HTML
page_html <- read_html(xml_text(resp_xml))

# Extract infobox element
infobox_element <- html_node(page_html, ".infobox")

# Extract page name element from infobox
page_name <- html_node(infobox_element, ".fn")

# Extract page name as text
page_title <- html_text(page_name)

5.3.3 Normalising information

Now it’s time to put together the information in a nice format. You’ve already seen you can use html_table() to parse the infobox into a data frame. But one piece of important information is missing from that table: who the information is about!

# Your code from earlier exercises
wiki_table <- html_table(infobox_element)
colnames(wiki_table) <- c("key", "value")
cleaned_table <- subset(wiki_table, !key == "")

# Create a dataframe for full name
name_df <- data.frame(key = "Full name", value = page_title)

# Combine name_df with cleaned_table
wiki_table2 <- rbind(name_df, cleaned_table)

# Print wiki_table
wiki_table2

5.3.4 Reproducibility

Now you’ve figured out the process for requesting and parsing the infobox for the Hadley Wickham page, it’s time to turn it into a function that does the same thing for anyone.

You’ve already done all the hard work! In the sample script we’ve just copied all your code from the previous three exercises, with only one change: we’ve wrapped it in the function definition syntax, and chosen the name get_infobox() for this function.

library(httr)
library(rvest)
library(xml2)

get_infobox <- function(title){
  base_url <- "https://en.wikipedia.org/w/api.php"
  
  # Change "Hadley Wickham" to title
  query_params <- list(action = "parse", 
    page = title, 
    format = "xml")
  
  resp <- GET(url = base_url, query = query_params)
  resp_xml <- content(resp)
  
  page_html <- read_html(xml_text(resp_xml))
  infobox_element <- html_node(x = page_html, css =".infobox")
  page_name <- html_node(x = infobox_element, css = ".fn")
  page_title <- html_text(page_name)
  
  wiki_table <- html_table(infobox_element)
  colnames(wiki_table) <- c("key", "value")
  cleaned_table <- subset(wiki_table, !wiki_table$key == "")
  name_df <- data.frame(key = "Full name", value = page_title)
  wiki_table <- rbind(name_df, cleaned_table)
  
  wiki_table
}

# Test get_infobox with "Hadley Wickham"
get_infobox(title = "Hadley Wickham")

# Try get_infobox with "Ross Ihaka"
get_infobox(title = "Ross Ihaka")

# Try get_infobox with "Grace Hopper"
get_infobox(title = "Grace Hopper")
LS0tDQp0aXRsZTogIldvcmtpbmcgd2l0aCBXZWIgRGF0YSBpbiBSIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIHRvY19jb2xsYXBzZWQ6IGZhbHNlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQogICAgDQp0b2NfZGVwdGg6IDMNCi0tLQ0KIyBEb3dubG9hZGluZyBGaWxlcyBhbmQgVXNpbmcgQVBJIENsaWVudHMNCg0KIyMgSW50cm9kdWN0aW9uOiBXb3JraW5nIFdpdGggV2ViIERhdGEgaW4gUg0KDQojIyMgRG93bmxvYWRpbmcgZmlsZXMgYW5kIHJlYWRpbmcgdGhlbSBpbnRvIFINCg0KSW4gdGhpcyBmaXJzdCBleGVyY2lzZSB3ZSdyZSBnb2luZyB0byBsb29rIGF0IHJlYWRpbmcgYWxyZWFkeS1mb3JtYXR0ZWQgZGF0YXNldHMgLSBDU1Ygb3IgVFNWIGZpbGVzLCB3aXRoIHdoaWNoIHlvdSdsbCBubyBkb3VidCBiZSBmYW1pbGlhciEgLSBpbnRvIFIgZnJvbSB0aGUgaW50ZXJuZXQuIFRoaXMgaXMgYSBsb3QgZWFzaWVyIHRoYW4gaXQgbWlnaHQgc291bmQgYmVjYXVzZSBSJ3MgZmlsZS1yZWFkaW5nIGZ1bmN0aW9ucyBhY2NlcHQgbm90IGp1c3QgZmlsZSBwYXRocywgYnV0IGFsc28gVVJMcy4NCmBgYHtyfQ0KIyBIZXJlIGFyZSB0aGUgVVJMcyEgQXMgeW91IGNhbiBzZWUgdGhleSdyZSBqdXN0IG5vcm1hbCBzdHJpbmdzDQpjc3ZfdXJsIDwtICJodHRwOi8vczMuYW1hem9uYXdzLmNvbS9hc3NldHMuZGF0YWNhbXAuY29tL3Byb2R1Y3Rpb24vY291cnNlXzE1NjEvZGF0YXNldHMvY2hpY2t3dHMuY3N2Ig0KdHN2X3VybCA8LSAiaHR0cDovL3MzLmFtYXpvbmF3cy5jb20vYXNzZXRzLmRhdGFjYW1wLmNvbS9wcm9kdWN0aW9uL2NvdXJzZV8zMDI2L2RhdGFzZXRzL3Rzdl9kYXRhLnRzdiINCg0KIyBSZWFkIGEgZmlsZSBpbiBmcm9tIHRoZSBDU1YgVVJMIGFuZCBhc3NpZ24gaXQgdG8gY3N2X2RhdGENCmNzdl9kYXRhIDwtIHJlYWQuY3N2KGNzdl91cmwpDQoNCiMgUmVhZCBhIGZpbGUgaW4gZnJvbSB0aGUgVFNWIFVSTCBhbmQgYXNzaWduIGl0IHRvIHRzdl9kYXRhDQp0c3ZfZGF0YSA8LSByZWFkLmRlbGltKHRzdl91cmwpDQoNCiMgRXhhbWluZSB0aGUgb2JqZWN0cyB3aXRoIGhlYWQoKQ0KaGVhZChjc3ZfZGF0YSkNCmhlYWQodHN2X2RhdGEpDQpgYGANCiMjIyBTYXZpbmcgcmF3IGZpbGVzIHRvIGRpc2sNCg0KU29tZXRpbWVzIGp1c3QgcmVhZGluZyB0aGUgZmlsZSBpbiBmcm9tIHRoZSB3ZWIgaXMgZW5vdWdoLCBidXQgb2Z0ZW4geW91J2xsIHdhbnQgdG8gc3RvcmUgaXQgbG9jYWxseSBzbyB0aGF0IHlvdSBjYW4gcmVmZXIgYmFjayB0byBpdC4gVGhpcyBhbHNvIGxldHMgeW91IGF2b2lkIGhhdmluZyB0byBzcGVuZCB0aGUgc3RhcnQgb2YgZXZlcnkgYW5hbHlzaXMgc2Vzc2lvbiB0d2lkZGxpbmcgeW91ciB0aHVtYnMgd2hpbGUgcGFydGljdWxhcmx5IGxhcmdlIGZpbGVzIGRvd25sb2FkLg0KDQpIZWxwZnVsbHksIFIgaGFzIGRvd25sb2FkLmZpbGUoKSwgYSBmdW5jdGlvbiB0aGF0IGxldHMgeW91IGRvIGp1c3QgdGhhdDogZG93bmxvYWQgYSBmaWxlIHRvIGEgbG9jYXRpb24gb2YgeW91ciBjaG9pY2Ugb24geW91ciBjb21wdXRlci4gSXQgdGFrZXMgdHdvIGFyZ3VtZW50czsgdXJsLCBpbmRpY2F0aW5nIHRoZSBVUkwgdG8gcmVhZCBmcm9tLCBhbmQgZGVzdGZpbGUsIHRoZSBkZXN0aW5hdGlvbiB0byB3cml0ZSB0aGUgZG93bmxvYWRlZCBmaWxlIHRvLiBJbiB0aGlzIGNhc2UsIHdlJ3ZlIHByZS1kZWZpbmVkIHRoZSBVUkwgLSBvbmNlIGFnYWluLCBpdCdzIGNzdl91cmwuDQpgYGB7cn0NCiMgRG93bmxvYWQgdGhlIGZpbGUgd2l0aCBkb3dubG9hZC5maWxlKCkNCiMgZG93bmxvYWQuZmlsZSh1cmwgPSBjc3ZfdXJsLCBkZXN0ZmlsZSA9ICJmZWVkX2RhdGEuY3N2IikNCg0KIyBSZWFkIGl0IGluIHdpdGggcmVhZC5jc3YoKQ0KY3N2X2RhdGEgPC0gcmVhZC5jc3YoImZlZWRfZGF0YS5jc3YiKQ0KYGBgDQojIyMgU2F2aW5nIGZvcm1hdHRlZCBmaWxlcyB0byBkaXNrDQoNCldoZXRoZXIgeW91J3JlIGRvd25sb2FkaW5nIHRoZSByYXcgZmlsZXMgd2l0aCBkb3dubG9hZC5maWxlKCkgb3IgdXNpbmcgcmVhZC5jc3YoKSBhbmQgaXRzIHNpYmxpbmcgZnVuY3Rpb25zLCBhdCBzb21lIHBvaW50IHlvdSdyZSBwcm9iYWJseSBnb2luZyB0byBmaW5kIHRoZSBuZWVkIHRvIG1vZGlmeSB5b3VyIGlucHV0IGRhdGEsIGFuZCB0aGVuIHNhdmUgdGhlIG1vZGlmaWVkIGRhdGEgdG8gZGlzayBzbyB5b3UgZG9uJ3QgbG9zZSB0aGUgY2hhbmdlcy4NCg0KWW91IGNvdWxkIHVzZSB3cml0ZS50YWJsZSgpLCBidXQgdGhlbiB5b3UgaGF2ZSB0byB3b3JyeSBhYm91dCBhY2NpZGVudGFsbHkgd3JpdGluZyBvdXQgZGF0YSBpbiBhIGZvcm1hdCBSIGNhbid0IHJlYWQgYmFjayBpbi4gQW4gZWFzeSB3YXkgdG8gYXZvaWQgdGhpcyByaXNrIGlzIHRvIHVzZSBzYXZlUkRTKCkgYW5kIHJlYWRSRFMoKSwgd2hpY2ggc2F2ZSBSIG9iamVjdHMgaW4gYW4gUi1zcGVjaWZpYyBmaWxlIGZvcm1hdCwgd2l0aCB0aGUgZGF0YSBzdHJ1Y3R1cmUgaW50YWN0LiBUaGF0IG1lYW5zIHlvdSBjYW4gdXNlIGl0IGZvciBhbnkgdHlwZSBvZiBSIG9iamVjdCAoZXZlbiBvbmVzIHRoYXQgZG9uJ3QgdHVybiBpbnRvIHRhYmxlcyBlYXNpbHkpLCBhbmQgbm90IHdvcnJ5IHlvdSdsbCBsb3NlIGRhdGEgcmVhZGluZyBpdCBiYWNrIGluLiBzYXZlUkRTKCkgdGFrZXMgdHdvIGFyZ3VtZW50cywgb2JqZWN0LCBwb2ludGluZyB0byB0aGUgUiBvYmplY3QgdG8gc2F2ZSBhbmQgZmlsZSBwb2ludGluZyB0byB3aGVyZSB0byBzYXZlIGl0IHRvLiByZWFkUkRTKCkgZXhwZWN0cyBmaWxlLCByZWZlcnJpbmcgdG8gdGhlIHBhdGggdG8gdGhlIFJEUyBmaWxlIHRvIHJlYWQgaW4uDQpgYGB7cn0NCiMgQWRkIGEgbmV3IGNvbHVtbjogc3F1YXJlX3dlaWdodA0KIyBjc3ZfZGF0YSRzcXVhcmVfd2VpZ2h0IDwtIGNzdl9kYXRhJHdlaWdodF4yDQoNCiMgU2F2ZSBpdCB0byBkaXNrIHdpdGggc2F2ZVJEUygpDQojIHNhdmVSRFMob2JqZWN0ID0gY3N2X2RhdGEsIGZpbGUgPSAibW9kaWZpZWRfZmVlZF9kYXRhLlJEUyIpDQoNCiMgUmVhZCBpdCBiYWNrIGluIHdpdGggcmVhZFJEUygpDQptb2RpZmllZF9mZWVkX2RhdGEgPC0gcmVhZFJEUyhmaWxlID0gIm1vZGlmaWVkX2ZlZWRfZGF0YS5SRFMiKQ0KDQojIEV4YW1pbmUgbW9kaWZpZWRfZmVlZF9kYXRhDQpzdHIobW9kaWZpZWRfZmVlZF9kYXRhKQ0KYGBgDQojIyBVbmRlcnN0YW5kaW5nIEFwcGxpY2F0aW9uIFByb2dyYW1taW5nIEludGVyZmFjZXMNCg0KIyMjIFVzaW5nIEFQSSBjbGllbnRzDQoNClNvIHdlIGtub3cgdGhhdCBBUElzIGFyZSBzZXJ2ZXIgY29tcG9uZW50cyB0byBtYWtlIGl0IGVhc3kgZm9yIHlvdXIgY29kZSB0byBpbnRlcmFjdCB3aXRoIGEgc2VydmljZSBhbmQgZ2V0IGRhdGEgZnJvbSBpdC4gV2UgYWxzbyBrbm93IHRoYXQgUiBmZWF0dXJlcyBtYW55ICJjbGllbnRzIiAtIHBhY2thZ2VzIHRoYXQgd3JhcCBhcm91bmQgY29ubmVjdGlvbnMgdG8gQVBJcyBzbyB5b3UgZG9uJ3QgaGF2ZSB0byB3b3JyeSBhYm91dCB0aGUgZGV0YWlscy4NCg0KTGV0J3MgbG9vayBhdCBhIHJlYWxseSBzaW1wbGUgQVBJIGNsaWVudCAtIHRoZSBwYWdldmlld3MgcGFja2FnZSwgd2hpY2ggYWN0cyBhcyBhIGNsaWVudCB0byBXaWtpcGVkaWEncyBBUEkgb2YgcGFnZXZpZXcgZGF0YS4gQXMgd2l0aCBvdGhlciBSIEFQSSBjbGllbnRzLCBpdCdzIGZvcm1hdHRlZCBhcyBhIHBhY2thZ2UsIGFuZCBsaXZlcyBvbiBDUkFOIC0gdGhlIGNlbnRyYWwgcmVwb3NpdG9yeSBvZiBSIHBhY2thZ2VzLiBUaGUgZ29hbCBoZXJlIGlzIGp1c3QgdG8gc2hvdyBob3cgc2ltcGxlIGNsaWVudHMgYXJlIHRvIHVzZTogdGhleSBsb29rIGp1c3QgbGlrZSBvdGhlciBSIGNvZGUsIGJlY2F1c2UgdGhleSBhcmUganVzdCBsaWtlIG90aGVyIFIgY29kZS4NCmBgYHtyfQ0KIyBMb2FkIHBhZ2V2aWV3cw0KbGlicmFyeShwYWdldmlld3MpDQoNCiMgR2V0IHRoZSBwYWdldmlld3MgZm9yICJIYWRsZXkgV2lja2hhbSINCmhhZGxleV9wYWdldmlld3MgPC0gYXJ0aWNsZV9wYWdldmlld3MocHJvamVjdCA9ICJlbi53aWtpcGVkaWEiLCAiSGFkbGV5IFdpY2toYW0iKQ0KDQojIEV4YW1pbmUgdGhlIHJlc3VsdGluZyBvYmplY3QNCnN0cihoYWRsZXlfcGFnZXZpZXdzKQ0KYGBgDQoNCi0tDQojIyBBY2Nlc3MgdG9rZW5zIGFuZCBBUElzDQoNCiMjIyBVc2luZyBhY2Nlc3MgdG9rZW5zDQoNCkFzIHdlIGRpc2N1c3NlZCBpbiB0aGUgbGFzdCB2aWRlbywgaXQncyBjb21tb24gZm9yIEFQSXMgdG8gcmVxdWlyZSBhY2Nlc3MgdG9rZW5zIC0gdW5pcXVlIGtleXMgdGhhdCB2ZXJpZnkgeW91J3JlIGF1dGhvcmlzZWQgdG8gdXNlIGEgc2VydmljZS4gVGhleSdyZSB1c3VhbGx5IHByZXR0eSBlYXN5IHRvIHVzZSB3aXRoIGFuIEFQSSBjbGllbnQuDQoNClRvIHNob3cgaG93IHRoZXkgd29yaywgYW5kIGhvdyBlYXN5IGl0IGNhbiBiZSwgd2UncmUgZ29pbmcgdG8gdXNlIHRoZSBSIGNsaWVudCBmb3IgdGhlIFdvcmRuaWsgZGljdGlvbmFyeSBhbmQgd29yZCB1c2Ugc2VydmljZSAtICdiaXJkbmlrJyAtIGFuZCBhbiBBUEkgdG9rZW4gd2UgcHJlcGFyZWQgZWFybGllci4gQmlyZG5payBpcyBmYWlybHkgc2ltcGxlIGFuZCBsZXRzIHlvdSBnZXQgYWxsIHNvcnRzIG9mIGludGVyZXN0aW5nIGluZm9ybWF0aW9uIGFib3V0IHdvcmQgdXNhZ2UgaW4gcHVibGlzaGVkIHdvcmtzLiBGb3IgZXhhbXBsZSwgdG8gZ2V0IHRoZSBmcmVxdWVuY3kgb2YgdGhlIHVzZSBvZiB0aGUgd29yZCAiY2hvY29sYXRlIiwgeW91IHdvdWxkIHdyaXRlOg0KDQogICAgd29yZF9mcmVxdWVuY3koYXBpX2tleSwgImNob2NvbGF0ZSIpDQogICAgDQpgYGB7ciBpbmNsdWRlPUZBTFNFfQ0KYXBpX2tleSA8LSAiZDhlZDY2ZjAxZGEwMWIwYzZhMDA3MGQ3YzE1MDM4MDE5OTNhMzljMTI2ZmJjMzM4MiINCmBgYA0KDQoNCmBgYHtyfQ0KIyBMb2FkIGJpcmRuaWsNCmxpYnJhcnkoYmlyZG5paykNCmxpYnJhcnkoaHR0cikNCg0KIyBHZXQgdGhlIHdvcmQgZnJlcXVlbmN5IGZvciAidmVjdG9yIiwgdXNpbmcgYXBpX2tleSB0byBhY2Nlc3MgaXQNCnZlY3Rvcl9mcmVxdWVuY3kgPC0gd29yZF9mcmVxdWVuY3koYXBpX2tleSwgInZlY3RvciIpDQpgYGANCg0KIyBVc2luZyBodHRyIHRvIGludGVyYWN0IHdpdGggQVBJcyBkaXJlY3RseQ0KDQojIyBHRVQgYW5kIFBPU1QgcmVxdWVzdHMgaW4gdGhlb3J5DQoNCiMjIyBHRVQgcmVxdWVzdHMgaW4gcHJhY3RpY2UNCg0KVG8gc3RhcnQgd2l0aCB5b3UncmUgZ29pbmcgdG8gbWFrZSBhIEdFVCByZXF1ZXN0LiBBcyBkaXNjdXNzZWQgaW4gdGhlIHZpZGVvLCB0aGlzIGlzIGEgcmVxdWVzdCB0aGF0IGFza3MgdGhlIHNlcnZlciB0byBnaXZlIHlvdSBhIHBhcnRpY3VsYXIgcGllY2Ugb2YgZGF0YSBvciBjb250ZW50ICh1c3VhbGx5IHNwZWNpZmllZCBpbiB0aGUgVVJMKS4gVGhlc2UgbWFrZSB1cCB0aGUgbWFqb3JpdHkgb2YgdGhlIHJlcXVlc3RzIHlvdSdsbCBtYWtlIGluIGEgZGF0YSBzY2llbmNlIGNvbnRleHQsIHNpbmNlIG1vc3Qgb2YgdGhlIHRpbWUgeW91J2xsIGJlIGdldHRpbmcgZGF0YSBmcm9tIHNlcnZlcnMsIG5vdCBnaXZpbmcgaXQgdG8gdGhlbS4NCg0KVG8gZG8gdGhpcyB5b3UnbGwgdXNlIHRoZSBodHRyIHBhY2thZ2UsIHdyaXR0ZW4gYnkgSGFkbGV5IFdpY2toYW0gKG9mIGNvdXJzZSksIHdoaWNoIG1ha2VzIEhUVFAgcmVxdWVzdHMgZXh0cmVtZWx5IGVhc3kuIFlvdSdyZSBnb2luZyB0byBtYWtlIGEgdmVyeSBzaW1wbGUgR0VUIHJlcXVlc3QsIGFuZCB0aGVuIGluc3BlY3QgdGhlIG91dHB1dCB0byBzZWUgd2hhdCBpdCBsb29rcyBsaWtlLg0KYGBge3J9DQojTG9hZCB0aGUgaHR0ciBwYWNrYWdlDQpsaWJyYXJ5KGh0dHIpDQoNCiMgTWFrZSBhIEdFVCByZXF1ZXN0IHRvIGh0dHA6Ly9odHRwYmluLm9yZy9nZXQNCmdldF9yZXN1bHQgPC0gR0VUKCJodHRwOi8vaHR0cGJpbi5vcmcvZ2V0IikNCg0KIyBQcmludCBpdCB0byBpbnNwZWN0IGl0DQpnZXRfcmVzdWx0DQpgYGANCkFzIHlvdSBjYW4gc2VlIGZyb20gaW5zcGVjdGluZyB0aGUgb3V0cHV0LCB0aGVyZSBhcmUgYSBsb3Qgb2YgcGFydHMgb2YgYSBIVFRQIHJlc3BvbnNlLg0KDQojIyMgUE9TVCByZXF1ZXN0cyBpbiBwcmFjdGljZQ0KDQpOZXh0IHdlJ2xsIGxvb2sgYXQgUE9TVCByZXF1ZXN0cywgYWxzbyBtYWRlIHRocm91Z2ggaHR0ciwgd2l0aCB0aGUgZnVuY3Rpb24gKHlvdSd2ZSBndWVzc2VkIGl0KSBQT1NUKCkuIFJhdGhlciB0aGFuIGFza2luZyB0aGUgc2VydmVyIHRvIGdpdmUgeW91IHNvbWV0aGluZywgYXMgaW4gR0VUIHJlcXVlc3RzLCBhIFBPU1QgcmVxdWVzdCBhc2tzIHRoZSBzZXJ2ZXIgdG8gYWNjZXB0IHNvbWV0aGluZyBmcm9tIHlvdS4gVGhleSdyZSBjb21tb25seSB1c2VkIGZvciB0aGluZ3MgbGlrZSBmaWxlIHVwbG9hZCwgb3IgYXV0aGVudGljYXRpb24uIEFzIGEgcmVzdWx0IG9mIHRoZWlyIHVzZSBmb3IgdXBsb2FkaW5nIHRoaW5ncywgUE9TVCgpIGFjY2VwdHMgbm90IGp1c3QgYSB1cmwgYnV0IGFsc28gYSBib2R5IGFyZ3VtZW50IGNvbnRhaW5pbmcgd2hhdGV2ZXIgeW91IHdhbnQgdG8gZ2l2ZSB0byB0aGUgc2VydmVyLg0KDQpZb3UnbGwgbWFrZSBhIHZlcnkgc2ltcGxlIFBPU1QgcmVxdWVzdCwganVzdCB1cGxvYWRpbmcgYSBwaWVjZSBvZiB0ZXh0LCBhbmQgdGhlbiBpbnNwZWN0IHRoZSBvdXRwdXQgdG8gc2VlIHdoYXQgaXQgbG9va3MgbGlrZS4NCmBgYHtyfQ0KIyBMb2FkIHRoZSBodHRyIHBhY2thZ2UNCmxpYnJhcnkoaHR0cikNCg0KIyBNYWtlIGEgUE9TVCByZXF1ZXN0IHRvIGh0dHA6Ly9odHRwYmluLm9yZy9wb3N0IHdpdGggdGhlIGJvZHkgInRoaXMgaXMgYSB0ZXN0Ig0KcG9zdF9yZXN1bHQgPC0gUE9TVCgiaHR0cDovL2h0dHBiaW4ub3JnL3Bvc3QiLCBib2R5ID0gInRoaXMgaXMgYSB0ZXN0IikNCg0KIyBQcmludCBpdCB0byBpbnNwZWN0IGl0DQpwb3N0X3Jlc3VsdA0KYGBgDQoNClRoZSBvdXRwdXQgZm9yIFBPU1QgcmVxdWVzdHMgbG9va3MgcHJldHR5IHNpbWlsYXIgdG8gdGhhdCBmb3IgR0VUIHJlcXVlc3RzLCBhbHRob3VnaCAoaW4gdGhpcyBjYXNlKSB0aGUgYm9keSBvZiB5b3VyIG1lc3NhZ2UgaXMgaW5jbHVkZWQgLSB0aGlzIGlzIGEgdGVzdC4NCg0KIyMjIEV4dHJhY3RpbmcgdGhlIHJlc3BvbnNlDQoNCk1ha2luZyByZXF1ZXN0cyBpcyBhbGwgd2VsbCBhbmQgZ29vZCwgYnV0IGl0J3MgYWxzbyBub3Qgd2h5IHlvdSdyZSBoZXJlLiBXaGF0IHdlIHJlYWxseSB3YW50IHRvIGRvIGlzIGdldCB0aGUgZGF0YSB0aGUgc2VydmVyIHNlbnQgYmFjaywgd2hpY2ggY2FuIGJlIGRvbmUgd2l0aCBodHRyJ3MgY29udGVudCgpIGZ1bmN0aW9uLiBZb3UgcGFzcyBpdCBhbiBvYmplY3QgcmV0dXJuZWQgZnJvbSBhIEdFVCAob3IgUE9TVCwgb3IgREVMRVRFLCBvci4uLikgY2FsbCwgYW5kIGl0IHNwaXRzIG91dCB3aGF0ZXZlciB0aGUgc2VydmVyIGFjdHVhbGx5IHNlbnQgaW4gYW4gUi1jb21wYXRpYmxlIHN0cnVjdHVyZS4NCg0KV2UncmUgZ29pbmcgdG8gZGVtb25zdHJhdGUgdGhhdCBub3csIHVzaW5nIGEgc2xpZ2h0bHkgbW9yZSBjb21wbGljYXRlZCBVUkwgdGhhbiBiZWZvcmUgLSBpbiBmYWN0LCB1c2luZyBhIFVSTCBmcm9tIHRoZSBXaWtpbWVkaWEgcGFnZXZpZXdzIHN5c3RlbSB5b3UgZGVhbHQgd2l0aCB0aHJvdWdoIHRoZSBwYWdldmlld3MgcGFja2FnZSwgd2hpY2ggaXMgc3RvcmVkIGFzIHVybC4gV2l0aG91dCBsb29raW5nIHRvbyBtdWNoIGF0IHRoZSBzdHJ1Y3R1cmUgZm9yIHRoZSB0aW1lIGJlaW5nICh3ZSdsbCBnZXQgdG8gdGhhdCBsYXRlcikgdGhpcyByZXF1ZXN0IGFza3MgZm9yIHRoZSBudW1iZXIgb2YgcGFnZXZpZXdzIHRvIHRoZSBFbmdsaXNoLWxhbmd1YWdlIFdpa2lwZWRpYSdzICJIYWRsZXkgV2lja2hhbSIgYXJ0aWNsZSBvbiAxIGFuZCAyIEphbnVhcnkgMjAxNy4NCmBgYHtyfQ0KdXJsIDwtICJodHRwczovL3dpa2ltZWRpYS5vcmcvYXBpL3Jlc3RfdjEvbWV0cmljcy9wYWdldmlld3MvcGVyLWFydGljbGUvZW4ud2lraXBlZGlhLm9yZy9hbGwtYWNjZXNzL2FsbC1hZ2VudHMvSGFkbGV5X1dpY2toYW0vZGFpbHkvMjAxNzAxMDEvMjAxNzAxMDIiDQoNCiMgTWFrZSBhIEdFVCByZXF1ZXN0IHRvIHVybCBhbmQgc2F2ZSB0aGUgcmVzdWx0cw0KcGFnZXZpZXdfcmVzcG9uc2UgPC0gR0VUKHVybCkNCg0KIyBDYWxsIGNvbnRlbnQoKSB0byByZXRyaWV2ZSB0aGUgZGF0YSB0aGUgc2VydmVyIHNlbnQgYmFjaw0KcGFnZXZpZXdfZGF0YSA8LSBjb250ZW50KHBhZ2V2aWV3X3Jlc3BvbnNlKQ0KDQojIEV4YW1pbmUgdGhlIHJlc3VsdHMgd2l0aCBzdHIoKQ0Kc3RyKHBhZ2V2aWV3X2RhdGEpDQpgYGANCkFzIHlvdSBjYW4gc2VlLCB0aGUgcmVzdWx0IG9mIGV4dHJhY3RpbmcgdGhlIGNvbnRlbnQgaXMgYSBsaXN0LCB3aGljaCBpcyBwcmV0dHkgY29tbW9uIChidXQgbm90IHVuaWZvcm0pIHRvIEFQSSByZXNwb25zZXMuDQoNCiMjIEdyYWNlZnVsIGh0dHINCg0KIyMjIEhhbmRsaW5nIGh0dHAgZmFpbHVyZXMNCg0KQXMgbWVudGlvbmVkLCBIVFRQIGNhbGxzIGNhbiBnbyB3cm9uZy4gSGFuZGxpbmcgdGhhdCBjYW4gYmUgZG9uZSB3aXRoIGh0dHIncyBodHRwX2Vycm9yKCkgZnVuY3Rpb24sIHdoaWNoIGlkZW50aWZpZXMgd2hldGhlciBhIHNlcnZlciByZXNwb25zZSBjb250YWlucyBhbiBlcnJvci4NCklmIHRoZSByZXNwb25zZSBkb2VzIGNvbnRhaW4gYW4gZXJyb3IsIGNhbGxpbmcgaHR0cF9lcnJvcigpIG92ZXIgdGhlIHJlc3BvbnNlIHdpbGwgcHJvZHVjZSBUUlVFOyBvdGhlcndpc2UsIEZBTFNFLiBZb3UgY2FuIHVzZSB0aGlzIGZvciByZWFsbHkgZmluZS1ncmFpbmVkIGNvbnRyb2wgb3ZlciByZXN1bHRzLiBGb3IgZXhhbXBsZSwgeW91IGNvdWxkIGNoZWNrIHdoZXRoZXIgdGhlIHJlcXVlc3QgY29udGFpbmVkIGFuIGVycm9yLCBhbmQgKGlmIHNvKSBpc3N1ZSBhIHdhcm5pbmcgYW5kIHJlLXRyeSB0aGUgcmVxdWVzdC4NCg0KRm9yIG5vdyB3ZSdsbCB0cnkgc29tZXRoaW5nIGEgYml0IHNpbXBsZXIgLSBpc3N1aW5nIGEgd2FybmluZyB0aGF0IHNvbWV0aGluZyB3ZW50IHdyb25nIGlmIGh0dHBfZXJyb3IoKSByZXR1cm5zIFRSVUUsIGFuZCBwcmludGluZyB0aGUgY29udGVudCBpZiBpdCBkb2Vzbid0Lg0KYGBge3J9DQpmYWtlX3VybCA8LSAiaHR0cDovL2dvb2dsZS5jb20vZmFrZXBhZ2V0aGF0ZG9lc25vdGV4aXN0Ig0KDQojIE1ha2UgdGhlIEdFVCByZXF1ZXN0DQpyZXF1ZXN0X3Jlc3VsdCA8LSBHRVQoZmFrZV91cmwpDQoNCiMgQ2hlY2sgcmVxdWVzdF9yZXN1bHQNCmlmKGh0dHBfZXJyb3IocmVxdWVzdF9yZXN1bHQpKXsNCgl3YXJuaW5nKCJUaGUgcmVxdWVzdCBmYWlsZWQiKQ0KfSBlbHNlIHsNCgljb250ZW50KHJlcXVlc3RfcmVzdWx0KQ0KfQ0KYGBgDQpFcnJvciBoYW5kbGluZyBpcyByZWFsbHkgaW1wb3J0YW50IGZvciB3cml0aW5nIHJvYnVzdCBjb2RlLCBhbmQgaXQgbG9va3MgbGlrZSB5b3UndmUgZ290IGEgZ29vZCBoYW5kbGUgb24gaXQuDQoNCiMjIyBDb25zdHJ1Y3RpbmcgcXVlcmllcyAoUGFydCBJKQ0KDQpBcyBicmllZmx5IGRpc2N1c3NlZCBpbiB0aGUgcHJldmlvdXMgdmlkZW8sIHRoZSBhY3R1YWwgQVBJIHF1ZXJ5ICh3aGljaCB0ZWxscyB0aGUgQVBJIHdoYXQgeW91IHdhbnQgdG8gZG8pIHRlbmRzIHRvIGJlIGluIG9uZSBvZiB0aGUgdHdvIGZvcm1zLiBUaGUgZmlyc3QgaXMgZGlyZWN0b3J5LWJhc2VkLCB3aGVyZSB2YWx1ZXMgYXJlIHNlcGFyYXRlZCBieSAvIG1hcmtzIHdpdGhpbiB0aGUgVVJMLiBUaGUgc2Vjb25kIGlzIHBhcmFtZXRlci1iYXNlZCwgd2hlcmUgYWxsIHRoZSB2YWx1ZXMgZXhpc3QgYXQgdGhlIGVuZCBvZiB0aGUgVVJMIGFuZCB0YWtlIHRoZSBmb3JtIG9mIGtleT12YWx1ZS4NCg0KQ29uc3RydWN0aW5nIGRpcmVjdG9yeS1iYXNlZCBVUkxzIGNhbiBiZSBkb25lIHZpYSBwYXN0ZSgpLCB3aGljaCB0YWtlcyBhbiB1bmxpbWl0ZWQgbnVtYmVyIG9mIHN0cmluZ3MsIGFsb25nIHdpdGggYSBzZXBhcmF0b3IsIGFzIHNlcC4gU28gdG8gY29uc3RydWN0IGh0dHA6Ly9zd2FwaS5jby9hcGkvdmVoaWNsZXMvMTIgd2UnZCBjYWxsOg0KcGFzdGUoImh0dHA6Ly9zd2FwaS5jbyIsICJhcGkiLCAidmVoaWNsZXMiLCAiMTIiLCBzZXAgPSAiLyIpDQpXZSBjYW4gcGxheSB3aXRoIFNXQVBJLCBtZW50aW9uZWQgYWJvdmUsIHdoaWNoIGlzIGFuIEFQSSBjaG9jayBmdWxsIG9mIHN0YXIgd2FycyBkYXRhLiBUaGlzIHRpbWUsIHJhdGhlciB0aGFuIGEgdmVoaWNsZSwgd2UnbGwgbG9vayBmb3IgYSBwZXJzb24uDQpgYGB7cn0NCiMgQ29uc3RydWN0IGEgZGlyZWN0b3J5LWJhc2VkIEFQSSBVUkwgdG8gYGh0dHA6Ly9zd2FwaS5jby9hcGlgLA0KIyBsb29raW5nIGZvciBwZXJzb24gYDFgIGluIGBwZW9wbGVgDQpkaXJlY3RvcnlfdXJsIDwtIHBhc3RlKCJodHRwOi8vc3dhcGkuY28vYXBpIiwgInBlb3BsZSIsIDEsIHNlcCA9ICIvIikNCg0KIyBNYWtlIGEgR0VUIGNhbGwgd2l0aCBpdA0KcmVzdWx0IDwtIEdFVChkaXJlY3RvcnlfdXJsKQ0KYGBgDQoNCiMjIyBDb25zdHJ1Y3RpbmcgcXVlcmllcyAoUGFydCBJSSkNCg0KQXMgbWVudGlvbmVkIChhbGJlaXQgYnJpZWZseSkgaW4gdGhlIGxhc3QgZXhlcmNpc2UsIHRoZXJlIGFyZSBhbHNvIHBhcmFtZXRlciBiYXNlZCBVUkxzLCB3aGVyZSBhbGwgdGhlIHF1ZXJ5IHZhbHVlcyBleGlzdCBhdCB0aGUgZW5kIG9mIHRoZSBVUkwgYW5kIHRha2UgdGhlIGZvcm0gb2Yga2V5PXZhbHVlIC0gdGhleSBsb29rIHNvbWV0aGluZyBsaWtlIGh0dHA6Ly9mYWtldXJsLmNvbS9mb28ucGhwP2NvdW50cnk9c3BhaW4mZm9vZD1nb3VsYXNoDQoNCkNvbnN0cnVjdGluZyBwYXJhbWV0ZXItYmFzZWQgVVJMcyBjYW4gYWxzbyBiZSBkb25lIHdpdGggcGFzdGUoKSwgYnV0IHRoZSBlYXNpZXN0IHdheSB0byBkbyBpdCBpcyB3aXRoIEdFVCgpIGFuZCBQT1NUKCkgdGhlbXNlbHZlcywgd2hpY2ggYWNjZXB0IGEgcXVlcnkgYXJndW1lbnQgY29uc2lzdGluZyBvZiBhIGxpc3Qgb2Yga2V5cyBhbmQgdmFsdWVzLiBTbywgdG8gY29udGludWUgd2l0aCB0aGUgZm9vZC1iYXNlZCBleGFtcGxlcywgd2UgY291bGQgY29uc3RydWN0IGZha2V1cmwuY29tL2FwaS5waHA/ZnJ1aXQ9cGVhY2hlcyZkYXk9dGh1cnNkYXkgd2l0aDoNCkdFVCgiZmFrZXVybC5jb20vYXBpLnBocCIsIHF1ZXJ5ID0gbGlzdChmcnVpdCA9ICJwZWFjaGVzIiwgZGF5ID0gInRodXJzZGF5IikpDQpgYGB7cn0NCiMgQ3JlYXRlIGxpc3Qgd2l0aCBuYXRpb25hbGl0eSBhbmQgY291bnRyeSBlbGVtZW50cw0KcXVlcnlfcGFyYW1zIDwtIGxpc3QobmF0aW9uYWxpdHkgPSAiYW1lcmljYW5zIiwgDQogICAgY291bnRyeSA9ICJhbnRpZ3VhIikNCiAgICANCiMgTWFrZSBwYXJhbWV0ZXItYmFzZWQgY2FsbCB0byBodHRwYmluLCB3aXRoIHF1ZXJ5X3BhcmFtcw0KcGFyYW1ldGVyX3Jlc3BvbnNlIDwtIEdFVCgiaHR0cHM6Ly9odHRwYmluLm9yZy9nZXQiLCBxdWVyeSA9IHF1ZXJ5X3BhcmFtcykNCg0KIyBQcmludCBwYXJhbWV0ZXJfcmVzcG9uc2UNCnBhcmFtZXRlcl9yZXNwb25zZQ0KYGBgDQpEaWQgeW91IG5vdGljZSB0aGUgcGFyYW1ldGVycyB5b3UgcGFzc2VkIGluIHdlcmUgbGlzdGVkIGluIHRoZSBhcmdzIG9mIHRoZSByZXNwb25zZT8NCg0KIyMgUmVzcGVjdGZ1bCBBUEkgdXNhZ2UNCg0KIyMjIFVzaW5nIHVzZXIgYWdlbnRzDQoNCmluZm9ybWF0aXZlIHVzZXItYWdlbnRzIGFyZSBhIGdvb2Qgd2F5IG9mIGJlaW5nIHJlc3BlY3RmdWwgb2YgdGhlIGRldmVsb3BlcnMgcnVubmluZyB0aGUgQVBJIHlvdSdyZSBpbnRlcmFjdGluZyB3aXRoLiBUaGV5IG1ha2UgaXQgZWFzeSBmb3IgdGhlbSB0byBjb250YWN0IHlvdSBpbiB0aGUgZXZlbnQgc29tZXRoaW5nIGdvZXMgd3JvbmcuIEkgYWx3YXlzIHRyeSB0byBpbmNsdWRlOg0KDQpNeSBlbWFpbCBhZGRyZXNzOw0KQSBVUkwgZm9yIHRoZSBwcm9qZWN0IHRoZSBjb2RlIGlzIGEgcGFydCBvZiwgaWYgaXQncyBnb3QgYSBVUkwuDQpCdWlsZGluZyB1c2VyIGFnZW50cyBpcyBkb25lIGJ5IHBhc3NpbmcgYSBjYWxsIHRvIHVzZXJfYWdlbnQoKSBpbnRvIHRoZSBHRVQoKSBvciBQT1NUKCkgcmVxdWVzdDsgc29tZXRoaW5nIGxpa2U6DQoNCiAgICBHRVQoImh0dHA6Ly91cmwuZ29lcy5oZXJlLyIsIHVzZXJfYWdlbnQoInNvbWVmYWtlZW1haWxAZG9tYWluLmNvbSBodHRwOi8vcHJvamVjdC53ZWJzaXRlIikpDQoNCkluIHRoZSBldmVudCB5b3UgZG9uJ3QgaGF2ZSBhIHdlYnNpdGUsIGEgc2hvcnQgb25lLXNlbnRlbmNlIGRlc2NyaXB0aW9uIG9mIHdoYXQgdGhlIHByb2plY3QgaXMgYWJvdXQgc2VydmVzIHByZXR0eSB3ZWxsLg0KYGBge3J9DQojIERvIG5vdCBjaGFuZ2UgdGhlIHVybA0KdXJsIDwtICJodHRwczovL3dpa2ltZWRpYS5vcmcvYXBpL3Jlc3RfdjEvbWV0cmljcy9wYWdldmlld3MvcGVyLWFydGljbGUvZW4ud2lraXBlZGlhL2FsbC1hY2Nlc3MvYWxsLWFnZW50cy9BYXJvbl9IYWxmYWtlci9kYWlseS8yMDE1MTAwMTAwLzIwMTUxMDMxMDAiDQoNCiMgQWRkIHRoZSBlbWFpbCBhZGRyZXNzIGFuZCB0aGUgdGVzdCBzZW50ZW5jZSBpbnNpZGUgdXNlcl9hZ2VudCgpDQpzZXJ2ZXJfcmVzcG9uc2UgPC0gR0VUKHVybCwgdXNlcl9hZ2VudCgibXlAZW1haWwuYWRkcmVzcyB0aGlzIGlzIGEgdGVzdCIpKQ0KYGBgDQpGcm9tIHlvdXIgZW5kLCB0aGUgcmVxdWVzdCBsb29rcyBleGFjdGx5IHRoZSBzYW1lIHdpdGggb3Igd2l0aG91dCBhIHVzZXIgYWdlbnQsIGJ1dCBmb3IgdGhlIHNlcnZlciB0aGUgZGlmZmVyZW5jZSBjYW4gYmUgdml0YWwuDQoNCiMjIyBSYXRlLWxpbWl0aW5nDQoNClRoZSBuZXh0IHN0YWdlIG9mIHJlc3BlY3RmdWwgQVBJIHVzYWdlIGlzIHJhdGUtbGltaXRpbmc6IG1ha2luZyBzdXJlIHlvdSBvbmx5IG1ha2UgYSBjZXJ0YWluIG51bWJlciBvZiByZXF1ZXN0cyB0byB0aGUgc2VydmVyIGluIGEgZ2l2ZW4gdGltZSBwZXJpb2QuIFlvdXIgbGltaXQgd2lsbCB2YXJ5IGZyb20gc2VydmVyIHRvIHNlcnZlciwgYnV0IHRoZSBpbXBsZW1lbnRhdGlvbiBpcyBhbHdheXMgcHJldHR5IG11Y2ggdGhlIHNhbWUgYW5kIGludm9sdmVzIGEgY2FsbCB0byBTeXMuc2xlZXAoKS4gVGhpcyBmdW5jdGlvbiB0YWtlcyBvbmUgYXJndW1lbnQsIGEgbnVtYmVyLCB3aGljaCByZXByZXNlbnRzIHRoZSBudW1iZXIgb2Ygc2Vjb25kcyB0byAic2xlZXAiIChwYXVzZSkgdGhlIFIgc2Vzc2lvbiBmb3IuIFNvIGlmIHlvdSBjYWxsIFN5cy5zbGVlcCgxNSksIGl0J2xsIHBhdXNlIGZvciAxNSBzZWNvbmRzIGJlZm9yZSBhbGxvd2luZyBmdXJ0aGVyIGNvZGUgdG8gcnVuLg0KDQpBcyB5b3UgY2FuIGltYWdpbmUsIHRoaXMgaXMgcmVhbGx5IHVzZWZ1bCBmb3IgcmF0ZS1saW1pdGluZy4gSWYgeW91IGFyZSBvbmx5IGFsbG93ZWQgNCByZXF1ZXN0cyBhIG1pbnV0ZT8gTm8gcHJvYmxlbSEgSnVzdCBwYXVzZSBmb3IgMTUgc2Vjb25kcyBiZXR3ZWVuIGVhY2ggcmVxdWVzdCBhbmQgeW91J3JlIGd1YXJhbnRlZWQgdG8gbmV2ZXIgZXhjZWVkIGl0LiBMZXQncyBkZW1vbnN0cmF0ZSBub3cgYnkgcHV0dGluZyB0b2dldGhlciBhIGxpdHRsZSBsb29wIHRoYXQgc2VuZHMgbXVsdGlwbGUgcmVxdWVzdHMgb24gYSA1LXNlY29uZCB0aW1lIGRlbGF5LiBXZSdsbCB1c2UgaHR0cGJpbi5vcmcgJ3MgQVBJcywgd2hpY2ggYWxsb3cgeW91IHRvIHRlc3QgZGlmZmVyZW50IEhUVFAgbGlicmFyaWVzLg0KYGBge3J9DQojIENvbnN0cnVjdCBhIHZlY3RvciBvZiAyIFVSTHMNCnVybHMgPC0gYygiaHR0cDovL2h0dHBiaW4ub3JnL3N0YXR1cy80MDQiLCAiaHR0cDovL2h0dHBiaW4ub3JnL3N0YXR1cy8zMDEiKQ0KDQpmb3IodXJsIGluIHVybHMpew0KICAgICMgU2VuZCBhIEdFVCByZXF1ZXN0IHRvIHVybA0KICAgIHJlc3VsdCA8LSBHRVQodXJsKQ0KICAgICMgRGVsYXkgZm9yIDUgc2Vjb25kcyBiZXR3ZWVuIHJlcXVlc3RzDQogICAgU3lzLnNsZWVwKDUpDQp9DQpgYGANCiMjIyBUeWluZyBpdCBhbGwgdG9nZXRoZXINCg0KVXNpbmcgZXZlcnl0aGluZyB0aGF0IHlvdSBsZWFybmVkIGluIHRoZSBjaGFwdGVyLCBsZXQncyBtYWtlIGEgc2ltcGxlIHJlcGxpY2Egb2Ygb25lIG9mIHRoZSAncGFnZXZpZXdzJyBmdW5jdGlvbnMgLSBidWlsZGluZyBxdWVyaWVzLCBzZW5kaW5nIEdFVCByZXF1ZXN0cyAod2l0aCBhbiBhcHByb3ByaWF0ZSB1c2VyIGFnZW50KSBhbmQgaGFuZGxpbmcgdGhlIG91dHB1dCBpbiBhIGZhdWx0LXRvbGVyYW50IHdheS4gWW91J2xsIGJ1aWxkIHRoaXMgZnVuY3Rpb24gdXAgcGllY2UgYnkgcGllY2UgaW4gdGhpcyBleGVyY2lzZS4NCg0KVG8gb3V0cHV0IGFuIGVycm9yLCB5b3Ugd2lsbCB1c2UgdGhlIGZ1bmN0aW9uIHN0b3AoKSwgd2hpY2ggdGFrZXMgYSBzdHJpbmcgYXMgYW4gYXJndW1lbnQsIHN0b3BzIHRoZSBleGVjdXRpb24gb2YgdGhlIHByb2dyYW0sIGFuZCBvdXRwdXRzIHRoZSBzdHJpbmcgYXMgYW4gZXJyb3IuIFlvdSBjYW4gdHJ5IGl0IHJpZ2h0IG5vdyBieSBydW5uaW5nIHN0b3AoIlRoaXMgaXMgYW4gZXJyb3IiKS4NCkZpcnN0LCBnZXQgdGhlIGZ1bmN0aW9uIHRvIGNvbnN0cnVjdCB0aGUgdXJsLg0KYGBge3J9DQpnZXRfcGFnZXZpZXdzIDwtIGZ1bmN0aW9uKGFydGljbGVfdGl0bGUpew0KICB1cmwgPC0gcGFzdGUoDQogICAgImh0dHBzOi8vd2lraW1lZGlhLm9yZy9hcGkvcmVzdF92MS9tZXRyaWNzL3BhZ2V2aWV3cy9wZXItYXJ0aWNsZS9lbi53aWtpcGVkaWEvYWxsLWFjY2Vzcy9hbGwtYWdlbnRzIiwgDQogICAgIyBJbmNsdWRlIGFydGljbGUgdGl0bGUNCiAgICBhcnRpY2xlX3RpdGxlLCANCiAgICAiZGFpbHkvMjAxNTEwMDEwMC8yMDE1MTAzMTAwIiwgDQogICAgc2VwID0gIi8iDQogICkgDQogIHVybA0KfQ0KYGBgDQpOb3csIG1ha2UgdGhlIHJlcXVlc3QNCmBgYHtyfQ0KZ2V0X3BhZ2V2aWV3cyA8LSBmdW5jdGlvbihhcnRpY2xlX3RpdGxlKXsNCiAgdXJsIDwtIHBhc3RlKA0KICAgICJodHRwczovL3dpa2ltZWRpYS5vcmcvYXBpL3Jlc3RfdjEvbWV0cmljcy9wYWdldmlld3MvcGVyLWFydGljbGUvZW4ud2lraXBlZGlhL2FsbC1hY2Nlc3MvYWxsLWFnZW50cyIsIA0KICAgIGFydGljbGVfdGl0bGUsIA0KICAgICJkYWlseS8yMDE1MTAwMTAwLzIwMTUxMDMxMDAiLCANCiAgICBzZXAgPSAiLyINCiAgKSANCiAgIyBHZXQgdGhlIHdlYnBhZ2UgIA0KICByZXNwb25zZSA8LSBHRVQodXJsLCBjb25maWcgPSB1c2VyX2FnZW50KCJteUBlbWFpbC5jb20gdGhpcyBpcyBhIHRlc3QiKSkgDQogIHJlc3BvbnNlDQp9DQpgYGANCk5vdywgYWRkIGFuIGVycm9yIGNoZWNrLg0KYGBge3J9DQpnZXRfcGFnZXZpZXdzIDwtIGZ1bmN0aW9uKGFydGljbGVfdGl0bGUpew0KICB1cmwgPC0gcGFzdGUoDQogICAgImh0dHBzOi8vd2lraW1lZGlhLm9yZy9hcGkvcmVzdF92MS9tZXRyaWNzL3BhZ2V2aWV3cy9wZXItYXJ0aWNsZS9lbi53aWtpcGVkaWEvYWxsLWFjY2Vzcy9hbGwtYWdlbnRzIiwgDQogICAgYXJ0aWNsZV90aXRsZSwgDQogICAgImRhaWx5LzIwMTUxMDAxMDAvMjAxNTEwMzEwMCIsIA0KICAgIHNlcCA9ICIvIg0KICApICAgDQogIHJlc3BvbnNlIDwtIEdFVCh1cmwsIHVzZXJfYWdlbnQoIm15QGVtYWlsLmNvbSB0aGlzIGlzIGEgdGVzdCIpKSANCiAgIyBJcyB0aGVyZSBhbiBIVFRQIGVycm9yPw0KICBpZihodHRwX2Vycm9yKHJlc3BvbnNlKSl7IA0KICAgICMgVGhyb3cgYW4gUiBlcnJvcg0KICAgICBzdG9wKCJ0aGUgcmVxdWVzdCBmYWlsZWQiKSANCiAgfQ0KICByZXNwb25zZQ0KfQ0KYGBgDQpGaW5hbGx5LCBpbnN0ZWFkIG9mIHJldHVybmluZyByZXNwb25zZSwgcmV0dXJuIHRoZSBjb250ZW50KCkgb2YgdGhlIHJlc3BvbnNlLg0KYGBge3J9DQpnZXRfcGFnZXZpZXdzIDwtIGZ1bmN0aW9uKGFydGljbGVfdGl0bGUpew0KICB1cmwgPC0gcGFzdGUoDQogICAgImh0dHBzOi8vd2lraW1lZGlhLm9yZy9hcGkvcmVzdF92MS9tZXRyaWNzL3BhZ2V2aWV3cy9wZXItYXJ0aWNsZS9lbi53aWtpcGVkaWEvYWxsLWFjY2Vzcy9hbGwtYWdlbnRzIiwgDQogICAgYXJ0aWNsZV90aXRsZSwgDQogICAgImRhaWx5LzIwMTUxMDAxMDAvMjAxNTEwMzEwMCIsIA0KICAgIHNlcCA9ICIvIg0KICApICAgDQogIHJlc3BvbnNlIDwtIEdFVCh1cmwsIHVzZXJfYWdlbnQoIm15QGVtYWlsLmNvbSB0aGlzIGlzIGEgdGVzdCIpKSANCiAgIyBJcyB0aGVyZSBhbiBIVFRQIGVycm9yPw0KICBpZihodHRwX2Vycm9yKHJlc3BvbnNlKSl7IA0KICAgICMgVGhyb3cgYW4gUiBlcnJvcg0KICAgIHN0b3AoInRoZSByZXF1ZXN0IGZhaWxlZCIpIA0KICB9DQogICMgUmV0dXJuIHRoZSByZXNwb25zZSdzIGNvbnRlbnQNCiAgY29udGVudChyZXNwb25zZSkNCn0NCmBgYA0KIyBIYW5kbGluZyBKU09OIGFuZCBYTUwNCg0KIyMgSlNPTg0KDQojIyMgUGFyc2luZyBKU09ODQoNCldoaWxlIEpTT04gaXMgYSB1c2VmdWwgZm9ybWF0IGZvciBzaGFyaW5nIGRhdGEsIHlvdXIgZmlyc3Qgc3RlcCB3aWxsIG9mdGVuIGJlIHRvIHBhcnNlIGl0IGludG8gYW4gUiBvYmplY3QsIHNvIHlvdSBjYW4gbWFuaXB1bGF0ZSBpdCB3aXRoIFIuDQoNClRoZSBjb250ZW50KCkgZnVuY3Rpb24gaW4gaHR0ciByZXRyaWV2ZXMgdGhlIGNvbnRlbnQgZnJvbSBhIHJlcXVlc3QuIEl0IHRha2VzIGFuIGFzIGFyZ3VtZW50IHRoYXQgc3BlY2lmaWVzIHRoZSB0eXBlIG9mIG91dHB1dCB0byByZXR1cm4uIFlvdSd2ZSBhbHJlYWR5IHNlZW4gdGhhdCBhcyA9ICJ0ZXh0IiB3aWxsIHJldHVybiB0aGUgY29udGVudCBhcyBhIGNoYXJhY3RlciBzdHJpbmcgd2hpY2ggaXMgdXNlZnVsIGZvciBjaGVja2luZyB0aGUgY29udGVudCBpcyBhcyB5b3UgZXhwZWN0Lg0KDQpJZiB5b3UgZG9uJ3Qgc3BlY2lmeSBhcywgdGhlIGRlZmF1bHQgYXMgPSAicGFyc2VkIiBpcyB1c2VkLiBJbiB0aGlzIGNhc2UgdGhlIHR5cGUgb2YgY29udGVudCgpIHdpbGwgYmUgZ3Vlc3NlZCBiYXNlZCBvbiB0aGUgaGVhZGVyIGFuZCBjb250ZW50KCkgd2lsbCBjaG9vc2UgYW4gYXBwcm9wcmlhdGUgcGFyc2luZyBmdW5jdGlvbi4gRm9yIEpTT04gdGhpcyBmdW5jdGlvbiBpcyBmcm9tSlNPTigpIGZyb20gdGhlIGpzb25saXRlIHBhY2thZ2UuIElmIHlvdSBrbm93IHlvdXIgcmVzcG9uc2UgaXMgSlNPTiwgeW91IG1heSB3YW50IHRvIHVzZSBmcm9tSlNPTigpIGRpcmVjdGx5Lg0KDQpUbyBwcmFjdGljZSwgeW91J2xsIHJldHJpZXZlIHNvbWUgcmV2aXNpb24gaGlzdG9yeSBmcm9tIHRoZSBXaWtpcGVkaWEgQVBJLCBjaGVjayBpdCBpcyBKU09OLCB0aGVuIHBhcnNlIGl0IGludG8gYSBsaXN0IHR3byB3YXlzLg0KYGBge3J9DQpyZXZfaGlzdG9yeSA8LSBmdW5jdGlvbiAodGl0bGUsIGZvcm1hdCA9ICJqc29uIikgew0KICAgIGlmICh0aXRsZSAhPSAiSGFkbGV5IFdpY2toYW0iKSB7DQogICAgICAgIHN0b3AoInJldl9oaXN0b3J5KCkgb25seSB3b3JrcyBmb3IgYHRpdGxlID0gXCJIYWRsZXkgV2lja2hhbVwiYCIpDQogICAgfQ0KICAgIGlmIChmb3JtYXQgPT0gImpzb24iKSB7DQogICAgICAgIHJlc3AgPC0gcmVhZFJEUygiaGFkX3Jldl9qc29uLnJkcyIpDQogICAgfQ0KICAgIGVsc2UgaWYgKGZvcm1hdCA9PSAieG1sIikgew0KICAgICAgICByZXNwIDwtIHJlYWRSRFMoImhhZF9yZXZfeG1sLnJkcyIpDQogICAgfQ0KICAgIGVsc2Ugew0KICAgICAgICBzdG9wKCJJbnZhbGlkIGZvcm1hdCBzdXBwbGllZCwgdHJ5IFwianNvblwiIG9yIFwieG1sXCIiKQ0KICAgIH0NCiAgICByZXNwDQp9DQpgYGANCmBgYHtyfQ0KIyBHZXQgcmV2aXNpb24gaGlzdG9yeSBmb3IgIkhhZGxleSBXaWNraGFtIg0KcmVzcF9qc29uIDwtIHBhZ2V2aWV3X3Jlc3BvbnNlDQoNCiMgQ2hlY2sgaHR0cF90eXBlKCkgb2YgcmVzcF9qc29uDQpodHRwX3R5cGUocmVzcF9qc29uKQ0KDQojIEV4YW1pbmUgcmV0dXJuZWQgdGV4dCB3aXRoIGNvbnRlbnQoKQ0KY29udGVudChyZXNwX2pzb24sIGFzID0gInRleHQiKQ0KDQojIFBhcnNlIHJlc3BvbnNlIHdpdGggY29udGVudCgpDQpjb250ZW50KHJlc3BfanNvbiwgYXMgPSAicGFyc2VkIikNCg0KIyBQYXJzZSByZXR1cm5lZCB0ZXh0IHdpdGggZnJvbUpTT04oKQ0KbGlicmFyeShqc29ubGl0ZSkNCmZyb21KU09OKGNvbnRlbnQocmVzcF9qc29uLCBhcyA9ICJ0ZXh0IikpDQpgYGANCiMjIE1hbmlwdWxhdGluZyBKU09ODQoNCiMjIyBNYW5pcHVsYXRpbmcgcGFyc2VkIEpTT04NCg0KQXMgeW91IHNhdyBpbiB0aGUgdmlkZW8sIHRoZSBvdXRwdXQgZnJvbSBwYXJzaW5nIEpTT04gaXMgYSBsaXN0LiBPbmUgd2F5IHRvIGV4dHJhY3QgcmVsZXZhbnQgZGF0YSBmcm9tIHRoYXQgbGlzdCBpcyB0byB1c2UgYSBwYWNrYWdlIHNwZWNpZmljYWxseSBkZXNpZ25lZCBmb3IgbWFuaXB1bGF0aW5nIGxpc3RzLCBybGlzdC4NCg0Kcmxpc3QgcHJvdmlkZXMgdHdvIHBhcnRpY3VsYXJseSB1c2VmdWwgZnVuY3Rpb25zIGZvciBzZWxlY3RpbmcgYW5kIGNvbWJpbmluZyBlbGVtZW50cyBmcm9tIGEgbGlzdDogbGlzdC5zZWxlY3QoKSBhbmQgbGlzdC5zdGFjaygpLiBsaXN0LnNlbGVjdCgpIGV4dHJhY3RzIHN1Yi1lbGVtZW50cyBieSBuYW1lIGZyb20gZWFjaCBlbGVtZW50IGluIGEgbGlzdC4gRm9yIGV4YW1wbGUgdXNpbmcgdGhlIHBhcnNlZCBtb3ZpZXMgZGF0YSBmcm9tIHRoZSB2aWRlbyAobW92aWVzX2xpc3QpLCB3ZSBtaWdodCBhc2sgZm9yIHRoZSB0aXRsZSBhbmQgeWVhciBlbGVtZW50cyBmcm9tIGVhY2ggZWxlbWVudDoNCg0KICAgIGxpc3Quc2VsZWN0KG1vdmllc19saXN0LCB0aXRsZSwgeWVhcikNCg0KVGhlIHJlc3VsdCBpcyBzdGlsbCBhIGxpc3QsIHRoYXQgaXMgd2hlcmUgbGlzdC5zdGFjaygpIGNvbWVzIGluLiBJdCB3aWxsIHN0YWNrIHRoZSBlbGVtZW50cyBvZiBhIGxpc3QgaW50byBhIGRhdGEgZnJhbWUuDQoNCiAgICBsaXN0LnN0YWNrKA0KICAgICAgICBsaXN0LnNlbGVjdChtb3ZpZXNfbGlzdCwgdGl0bGUsIHllYXIpDQogICAgKQ0KDQpGaXJzdCwgeW91J2xsIG5lZWQgdG8gZmlndXJlIG91dCB3aGVyZSB0aGUgcmV2aXNpb25zIGFyZS4gRXhhbWluZSB0aGUgb3V0cHV0IGZyb20gdGhlIHN0cigpIGNhbGwuIENhbiB5b3Ugc2VlIHdoZXJlIHRoZSBsaXN0IG9mIDUgcmV2aXNpb25zIGlzPw0KYGBge3J9DQoNCnVybCA8LSAiaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3cvYXBpLnBocD9hY3Rpb249cXVlcnkmdGl0bGVzPUhhZGxleSUyMFdpY2toYW0mcHJvcD1yZXZpc2lvbnMmcnZwcm9wPXRpbWVzdGFtcCU3Q3VzZXIlN0Njb21tZW50JTdDY29udGVudCZydmxpbWl0PTUmZm9ybWF0PWpzb24mcnZkaXI9bmV3ZXImcnZzdGFydD0yMDE1LTAxLTE0VDE3JTNBMTIlM0E0NVomcnZzZWN0aW9uPTAiDQoNCnJlc3BfanNvbiA8LSBHRVQodXJsKQ0KIyBMb2FkIHJsaXN0DQpsaWJyYXJ5KHJsaXN0KQ0KDQojIEV4YW1pbmUgb3V0cHV0IG9mIHRoaXMgY29kZQ0Kc3RyKGNvbnRlbnQocmVzcF9qc29uKSwgbWF4LmxldmVsID0gNCkNCmBgYA0KVXNlIGxpc3Quc2VsZWN0KCkgdG8gcHVsbCBvdXQgdGhlIHVzZXIgYW5kIHRpbWVzdGFtcCBlbGVtZW50cyBmcm9tIGVhY2ggcmV2aXNpb24sIHN0b3JlIGluIHVzZXJfdGltZS4NCmBgYHtyfQ0KIyBTdG9yZSByZXZpc2lvbiBsaXN0DQpyZXZzIDwtIGNvbnRlbnQocmVzcF9qc29uKSRxdWVyeSRwYWdlcyRgNDE5MTYyNzBgJHJldmlzaW9ucw0KIyBFeHRyYWN0IHRoZSB1c2VyIGVsZW1lbnQNCnVzZXJfdGltZSA8LSBsaXN0LnNlbGVjdChyZXZzLCB1c2VyLCB0aW1lc3RhbXApDQoNCiMgUHJpbnQgdXNlcl90aW1lDQp1c2VyX3RpbWUNCg0KIyBTdGFjayB0byB0dXJuIGludG8gYSBkYXRhIGZyYW1lDQpsaXN0LnN0YWNrKHVzZXJfdGltZSkNCmBgYA0Kcmxpc3QgaXMgZGVzaWduZWQgdG8gbWFrZSB3b3JraW5nIHdpdGggbGlzdHMgZWFzeSwgc28gaWYgZmluZCB5b3UgYXJlIHdvcmtpbmcgd2l0aCBKU09OIGRhdGEgYSBsb3QsIHlvdSBzaG91bGQgZXhwbG9yZSBtb3JlIG9mIGl0cyBmdW5jdGlvbmFsaXR5Lg0KDQojIyMgUmVmb3JtYXR0aW5nIEpTT04NCg0KT2YgY291cnNlIHlvdSBkb24ndCBoYXZlIHRvIHVzZSBybGlzdC4gWW91IGNhbiBhY2hpZXZlIHRoZSBzYW1lIHRoaW5nIGJ5IHVzaW5nIGZ1bmN0aW9ucyBmcm9tIGJhc2UgUiBvciB0aGUgdGlkeXZlcnNlLiBJbiB0aGlzIGV4ZXJjaXNlIHlvdSdsbCByZXBlYXQgdGhlIHRhc2sgb2YgZXh0cmFjdGluZyB0aGUgdXNlcm5hbWUgYW5kIHRpbWVzdGFtcCB1c2luZyB0aGUgZHBseXIgcGFja2FnZSB3aGljaCBpcyBwYXJ0IG9mIHRoZSB0aWR5dmVyc2UuDQoNCkNvbmNlcHR1YWxseSwgeW91J2xsIHRha2UgdGhlIGxpc3Qgb2YgcmV2aXNpb25zLCBzdGFjayB0aGVtIGludG8gYSBkYXRhIGZyYW1lLCB0aGVuIHB1bGwgb3V0IHRoZSByZWxldmFudCBjb2x1bW5zLg0KDQpkcGx5cidzIGJpbmRfcm93cygpIGZ1bmN0aW9uIHRha2VzIGEgbGlzdCBhbmQgdHVybnMgaXQgaW50byBhIGRhdGEgZnJhbWUuIFRoZW4geW91IGNhbiB1c2Ugc2VsZWN0KCkgdG8gZXh0cmFjdCB0aGUgcmVsZXZhbnQgY29sdW1ucy4gQW5kIG9mIGNvdXJzZSBpZiB3ZSBjYW4gbWFrZSB1c2Ugb2YgdGhlICU+JSAocGlwZSkgb3BlcmF0b3IgdG8gY2hhaW4gdGhlbSBhbGwgdG9nZXRoZXIuDQpgYGB7cn0NCiMgTG9hZCBkcGx5cg0KbGlicmFyeShkcGx5cikNCg0KIyBQdWxsIG91dCByZXZpc2lvbiBsaXN0DQpyZXZzIDwtIGNvbnRlbnQocmVzcF9qc29uKSRxdWVyeSRwYWdlcyRgNDE5MTYyNzBgJHJldmlzaW9ucw0KDQojIEV4dHJhY3QgdXNlciBhbmQgdGltZXN0YW1wDQpyZXZzICU+JQ0KICBiaW5kX3Jvd3MgJT4lICAgICAgICAgICANCiAgc2VsZWN0KHVzZXIsIHRpbWVzdGFtcCkNCmBgYA0KIyMgWE1MIHN0cnVjdHVyZQ0KDQojIyMgRXhhbWluaW5nIFhNTCBkb2N1bWVudHMNCg0KSnVzdCBsaWtlIEpTT04sIHlvdSBzaG91bGQgZmlyc3QgdmVyaWZ5IHRoZSByZXNwb25zZSBpcyBpbmRlZWQgWE1MIHdpdGggaHR0cF90eXBlKCkgYW5kIGJ5IGV4YW1pbmluZyB0aGUgcmVzdWx0IG9mIGNvbnRlbnQociwgYXMgPSAidGV4dCIpLiBUaGVuIHlvdSBjYW4gdHVybiB0aGUgcmVzcG9uc2UgaW50byBhbiBYTUwgZG9jdW1lbnQgb2JqZWN0IHdpdGggcmVhZF94bWwoKS4NCg0KT25lIGJlbmVmaXQgb2YgdXNpbmcgdGhlIFhNTCBkb2N1bWVudCBvYmplY3QgaXMgdGhlIGF2YWlsYWJsZSBmdW5jdGlvbnMgdGhhdCBoZWxwIHlvdSBleHBsb3JlIGFuZCBtYW5pcHVsYXRlIHRoZSBkb2N1bWVudC4gRm9yIGV4YW1wbGUgeG1sX3N0cnVjdHVyZSgpIHdpbGwgcHJpbnQgYSByZXByZXNlbnRhdGlvbiBvZiB0aGUgWE1MIGRvY3VtZW50IHRoYXQgZW1waGFzaXplcyB0aGUgaGllcmFyY2hpY2FsIHN0cnVjdHVyZSBieSBkaXNwbGF5aW5nIHRoZSBlbGVtZW50cyB3aXRob3V0IHRoZSBkYXRhLg0KDQpJbiB0aGlzIGV4ZXJjaXNlIHlvdSdsbCBncmFiIHRoZSBzYW1lIHJldmlzaW9uIGhpc3RvcnkgeW91J3ZlIGJlZW4gd29ya2luZyB3aXRoIGFzIFhNTCwgYW5kIHRha2UgYSBsb29rIGF0IGl0IHdpdGggeG1sX3N0cnVjdHVyZSgpLg0KYGBge3J9DQp1cmwgPC0gImh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93L2FwaS5waHA/YWN0aW9uPXF1ZXJ5JnRpdGxlcz1IYWRsZXklMjBXaWNraGFtJnByb3A9cmV2aXNpb25zJnJ2cHJvcD10aW1lc3RhbXAlN0N1c2VyJTdDY29tbWVudCU3Q2NvbnRlbnQmcnZsaW1pdD01JmZvcm1hdD14bWwmcnZkaXI9bmV3ZXImcnZzdGFydD0yMDE1LTAxLTE0VDE3JTNBMTIlM0E0NVomcnZzZWN0aW9uPTAiDQoNCg0KIyBMb2FkIHhtbDINCmxpYnJhcnkoeG1sMikNCg0KIyBHZXQgWE1MIHJldmlzaW9uIGhpc3RvcnkNCnJlc3BfeG1sIDwtIEdFVCh1cmwpDQoNCiMgQ2hlY2sgcmVzcG9uc2UgaXMgWE1MIA0KaHR0cF90eXBlKHJlc3BfeG1sKQ0KDQojIEV4YW1pbmUgcmV0dXJuZWQgdGV4dCB3aXRoIGNvbnRlbnQoKQ0KcmV2X3RleHQgPC0gY29udGVudChyZXNwX3htbCwgYXM9InRleHQiKQ0KDQojIFR1cm4gcmV2X3RleHQgaW50byBhbiBYTUwgZG9jdW1lbnQNCnJldl94bWwgPC0gcmVhZF94bWwocmV2X3RleHQpDQoNCiMgRXhhbWluZSB0aGUgc3RydWN0dXJlIG9mIHJldl94bWwNCnhtbF9zdHJ1Y3R1cmUocmV2X3htbCkNCmBgYA0KeG1sX3N0cnVjdHVyZSgpIGhlbHBzIHlvdSB1bmRlcnN0YW5kIHRoZSBzdHJ1Y3R1cmUgb2YgeW91ciBkb2N1bWVudCwgd2l0aG91dCBvdmVyd2hlbG1pbmcgeW91IHdpdGggY29udGVudC4NCg0KIyMgWFBBVEhzDQoNCiMjIyBFeHRyYWN0aW5nIFhNTCBkYXRhDQoNClhQQVRIcyBhcmUgZGVzaWduZWQgdG8gc3BlY2lmeWluZyBub2RlcyBpbiBhbiBYTUwgZG9jdW1lbnQuIFJlbWVtYmVyIC9ub2RlX25hbWUgc3BlY2lmaWVzIG5vZGVzIGF0IHRoZSBjdXJyZW50IGxldmVsIHRoYXQgaGF2ZSB0aGUgdGFnIG5vZGVfbmFtZSwgd2hlcmUgYXMgLy9ub2RlX25hbWUgc3BlY2lmaWVzIG5vZGVzIGF0IGFueSBsZXZlbCBiZWxvdyB0aGUgY3VycmVudCBsZXZlbCB0aGF0IGhhdmUgdGhlIHRhZyBub2RlX25hbWUuDQoNCnhtbDIgcHJvdmlkZXMgdGhlIGZ1bmN0aW9uIHhtbF9maW5kX2FsbCgpIHRvIGV4dHJhY3Qgbm9kZXMgdGhhdCBtYXRjaCBhIGdpdmVuIFhQQVRILiBGb3IgZXhhbXBsZSwgeG1sX2ZpbmRfYWxsKHJldl94bWwsICIvYXBpIikgd2lsbCBmaW5kIGFsbCB0aGUgbm9kZXMgYXQgdGhlIHRvcCBsZXZlbCBvZiB0aGUgcmV2X3htbCBkb2N1bWVudCB0aGF0IGhhdmUgdGhlIHRhZyBhcGkuIFRyeSBydW5uaW5nIHRoYXQgaW4gdGhlIGNvbnNvbGUuIFlvdSdsbCBnZXQgYSBub2Rlc2V0IG9mIG9uZSBub2RlIGJlY2F1c2UgdGhlcmUgaXMgb25seSBvbmUgbm9kZSB0aGF0IHNhdGlzZmllcyB0aGF0IFhQQVRILg0KDQpUaGUgb2JqZWN0IHJldHVybmVkIGZyb20geG1sX2ZpbmRfYWxsKCkgaXMgYSBub2Rlc2V0ICh0aGluayBvZiBpdCBsaWtlIGEgbGlzdCBvZiBub2RlcykuIFRvIGFjdHVhbGx5IGdldCBkYXRhIG91dCBvZiB0aGUgbm9kZXMgaW4gdGhlIG5vZGVzZXQsIHlvdSdsbCBoYXZlIHRvIGV4cGxpY2l0bHkgYXNrIGZvciBpdCB3aXRoIHhtbF90ZXh0KCkgKG9yIHhtbF9kb3VibGUoKSBvciB4bWxfaW50ZWdlcigpKS4NCg0KVXNlIHdoYXQgeW91IGtub3cgYWJvdXQgdGhlIGxvY2F0aW9uIG9mIHRoZSByZXZpc2lvbnMgZGF0YSBpbiB0aGUgcmV0dXJuZWQgWE1MIGRvY3VtZW50IGV4dHJhY3QganVzdCB0aGUgY29udGVudCBvZiB0aGUgcmV2aXNpb24uDQpgYGB7cn0NCiMgRmluZCBhbGwgbm9kZXMgdXNpbmcgWFBBVEggIi9hcGkvcXVlcnkvcGFnZXMvcGFnZS9yZXZpc2lvbnMvcmV2Ig0KeG1sX2ZpbmRfYWxsKHJldl94bWwsICIvYXBpL3F1ZXJ5L3BhZ2VzL3BhZ2UvcmV2aXNpb25zL3JldiIpDQoNCiMgRmluZCBhbGwgcmV2IG5vZGVzIGFueXdoZXJlIGluIGRvY3VtZW50DQpyZXZfbm9kZXMgPC0geG1sX2ZpbmRfYWxsKHJldl94bWwsICIvL3JldiIpDQoNCiMgVXNlIHhtbF90ZXh0KCkgdG8gZ2V0IHRleHQgZnJvbSByZXZfbm9kZXMNCiMgeG1sX3RleHQocmV2X25vZGVzKQ0KYGBgDQojIyMgRXh0cmFjdGluZyBYTUwgYXR0cmlidXRlcw0KDQpOb3QgYWxsIHRoZSB1c2VmdWwgZGF0YSB3aWxsIGJlIGluIHRoZSBjb250ZW50IG9mIGEgbm9kZSwgc29tZSBtaWdodCBhbHNvIGJlIGluIHRoZSBhdHRyaWJ1dGVzIG9mIGEgbm9kZS4gVG8gZXh0cmFjdCBhdHRyaWJ1dGVzIGZyb20gYSBub2Rlc2V0LCB4bWwyIHByb3ZpZGVzIHhtbF9hdHRycygpIGFuZCB4bWxfYXR0cigpLg0KDQp4bWxfYXR0cnMoKSB0YWtlcyBhIG5vZGVzZXQgYW5kIHJldHVybnMgYWxsIG9mIHRoZSBhdHRyaWJ1dGVzIGZvciBldmVyeSBub2RlIGluIHRoZSBub2Rlc2V0LiB4bWxfYXR0cigpIHRha2VzIGEgbm9kZXNldCBhbmQgYW4gYWRkaXRpb25hbCBhcmd1bWVudCBhdHRyIHRvIGV4dHJhY3QgYSBzaW5nbGUgbmFtZWQgYXJndW1lbnQgZnJvbSBlYWNoIG5vZGUgaW4gdGhlIG5vZGVzZXQuDQoNCkluIHRoaXMgZXhlcmNpc2UgeW91J2xsIGdyYWIgdGhlIHVzZXIgYW5kIGFub24gYXR0cmlidXRlcyBmb3IgZWFjaCByZXZpc2lvbi4gWW91J2xsIHNlZSB4bWxfZmluZF9maXJzdCgpIGluIHRoZSBzYW1wbGUgY29kZS4gSXQgd29ya3MganVzdCBsaWtlIHhtbF9maW5kX2FsbCgpIGJ1dCBpdCBvbmx5IGV4dHJhY3RzIHRoZSBmaXJzdCBub2RlIGl0IGZpbmRzLg0KYGBge3J9DQojIEFsbCByZXYgbm9kZXMNCnJldl9ub2RlcyA8LSB4bWxfZmluZF9hbGwocmV2X3htbCwgIi8vcmV2IikNCg0KIyBUaGUgZmlyc3QgcmV2IG5vZGUNCmZpcnN0X3Jldl9ub2RlIDwtIHhtbF9maW5kX2ZpcnN0KHJldl94bWwsICIvL3JldiIpDQoNCiMgRmluZCBhbGwgYXR0cmlidXRlcyB3aXRoIHhtbF9hdHRycygpDQp4bWxfYXR0cnMoZmlyc3RfcmV2X25vZGUpDQoNCiMgRmluZCB1c2VyIGF0dHJpYnV0ZSB3aXRoIHhtbF9hdHRyKCkNCnhtbF9hdHRyKGZpcnN0X3Jldl9ub2RlLCAidXNlciIpDQoNCiMgRmluZCB1c2VyIGF0dHJpYnV0ZSBmb3IgYWxsIHJldiBub2Rlcw0KeG1sX2F0dHIocmV2X25vZGVzLCAidXNlciIpDQoNCiMgRmluZCBhbm9uIGF0dHJpYnV0ZSBmb3IgYWxsIHJldiBub2Rlcw0KeG1sX2F0dHIocmV2X25vZGVzLCAiYW5vbiIpDQpgYGANCkRpZCB5b3Ugbm90aWNlIHRoYXQgaWYgYSBub2RlIGRpZG4ndCBoYXZlIHRoZSBhbm9uIGF0dHJpYnV0ZSB4bWxfYXR0cigpIHJldHVybmVkIGFuIE5BPw0KDQojIyMgV3JhcHVwOiByZXR1cm5pbmcgbmljZSBBUEkgb3V0cHV0DQoNCkhvdyBtaWdodCBhbGwgdGhpcyB3b3JrIHRvZ2V0aGVyPyBBIHVzZWZ1bCBBUEkgZnVuY3Rpb24gd2lsbCByZXRyaWV2ZSByZXN1bHRzIGZyb20gYW4gQVBJIGFuZCByZXR1cm4gdGhlbSBpbiBhIHVzZWZ1bCBmb3JtLiBJbiBDaGFwdGVyIDIsIHlvdSBmaW5pc2hlZCB1cCBieSB3cml0aW5nIGEgZnVuY3Rpb24gdGhhdCByZXRyaWV2ZXMgZGF0YSBmcm9tIGFuIEFQSSB0aGF0IHJlbGllZCBvbiBjb250ZW50KCkgdG8gY29udmVydCBpdCB0byBhIHVzZWZ1bCBmb3JtLiBUbyB3cml0ZSBhIG1vcmUgcm9idXN0IEFQSSBmdW5jdGlvbiB5b3Ugc2hvdWxkbid0IHJlbHkgb24gY29udGVudCgpIGJ1dCBpbnN0ZWFkIHBhcnNlIHRoZSBkYXRhIHlvdXJzZWxmLg0KDQpUbyBmaW5pc2ggdXAgdGhpcyBjaGFwdGVyIHlvdSdsbCBkbyBleGFjdGx5IHRoYXQ6IHdyaXRlIGdldF9yZXZpc2lvbl9oaXN0b3J5KCkgd2hpY2ggcmV0cmlldmVzIHRoZSBYTUwgZGF0YSBmb3IgdGhlIHJldmlzaW9uIGhpc3Rvcnkgb2YgcGFnZSBvbiBXaWtpcGVkaWEsIHBhcnNlcyBpdCwgYW5kIHJldHVybnMgaXQgaW4gYSBuaWNlIGRhdGEgZnJhbWUuDQoNClNvIHRoYXQgeW91IGNhbiBmb2N1cyBvbiB0aGUgcGFydHMgb2YgdGhlIGZ1bmN0aW9uIHRoYXQgcGFyc2UgdGhlIHJldHVybiBvYmplY3QsIHlvdSdsbCBzZWUgeW91ciBmdW5jdGlvbiBjYWxscyByZXZfaGlzdG9yeSgpIHRvIGdldCB0aGUgcmVzcG9uc2UgZnJvbSB0aGUgQVBJLiBZb3UgY2FuIGFzc3VtZSB0aGlzIGZ1bmN0aW9uIHJldHVybnMgdGhlIHJhdyByZXNwb25zZSBhbmQgZm9sbG93cyB0aGUgYmVzdCBwcmFjdGljZXMgeW91IGxlYXJudCBpbiBDaGFwdGVyIDIsIGxpa2UgdXNpbmcgYSB1c2VyIGFnZW50LCBhbmQgY2hlY2tpbmcgdGhlIHJlc3BvbnNlIHN0YXR1cy4NCmBgYHtyfQ0KZ2V0X3JldmlzaW9uX2hpc3RvcnkgPC0gZnVuY3Rpb24oYXJ0aWNsZV90aXRsZSl7DQogICMgR2V0IHJhdyByZXZpc2lvbiByZXNwb25zZQ0KICByZXZfcmVzcCA8LSByZXNwX3htbA0KICANCiAgIyBUdXJuIHRoZSBjb250ZW50KCkgb2YgcmV2X3Jlc3AgaW50byBYTUwNCiAgcmV2X3htbCA8LSByZWFkX3htbChjb250ZW50KHJldl9yZXNwLCAidGV4dCIpKQ0KICANCiAgIyBGaW5kIHJldmlzaW9uIG5vZGVzDQogIHJldl9ub2RlcyA8LSB4bWxfZmluZF9hbGwocmV2X3htbCwgIi8vcmV2IikNCg0KICAjIFBhcnNlIG91dCB1c2VybmFtZXMNCiAgdXNlciA8LSB4bWxfYXR0cihyZXZfbm9kZXMsICJ1c2VyIikNCiAgDQogICMgUGFyc2Ugb3V0IHRpbWVzdGFtcHMNCiAgdGltZXN0YW1wIDwtIHJlYWRyOjpwYXJzZV9kYXRldGltZSh4bWxfYXR0cihyZXZfbm9kZXMsICJ0aW1lc3RhbXAiKSkNCiAgDQogICMgUGFyc2Ugb3V0IGNvbnRlbnQNCiAgY29udGVudCA8LSB4bWxfdGV4dChyZXZfbm9kZXMpDQogIA0KICAjIFJldHVybiBkYXRhIGZyYW1lIA0KICBkYXRhLmZyYW1lKHVzZXIgPSB1c2VyLA0KICAgIHRpbWVzdGFtcCA9IHRpbWVzdGFtcCwNCiAgICBjb250ZW50ID0gc3Vic3RyKGNvbnRlbnQsIDEsIDQwKSkNCn0NCg0KIyBDYWxsIGZ1bmN0aW9uIGZvciAiSGFkbGV5IFdpY2toYW0iDQpnZXRfcmV2aXNpb25faGlzdG9yeShhcnRpY2xlX3RpdGxlID0gIkhhZGxleSBXaWNraGFtIikNCg0KYGBgDQpZb3VyIGZ1bmN0aW9uIHBhcnNlZCB0aGUgWE1MIGRhdGEsIGJ1dCB5b3UgY291bGQgaGF2ZSBqdXN0IGFzIGVhc2lseSBwYXJzZWQgdGhlIEpTT04gZGF0YS4NCg0KIyBXZWIgc2NyYXBpbmcgd2l0aCBYUEFUSHMNCg0KIyMgV2ViIHNjcmFwaW5nIDEwMQ0KDQojIyMgUmVhZGluZyBIVE1MDQoNClRoZSBmaXJzdCBzdGVwIHdpdGggd2ViIHNjcmFwaW5nIGlzIGFjdHVhbGx5IHJlYWRpbmcgdGhlIEhUTUwgaW4uIFRoaXMgY2FuIGJlIGRvbmUgd2l0aCBhIGZ1bmN0aW9uIGZyb20geG1sMiwgd2hpY2ggaXMgaW1wb3J0ZWQgYnkgcnZlc3QgLSByZWFkX2h0bWwoKS4gVGhpcyBhY2NlcHRzIGEgc2luZ2xlIFVSTCwgYW5kIHJldHVybnMgYSBiaWcgYmxvYiBvZiBYTUwgdGhhdCB3ZSBjYW4gdXNlIGZ1cnRoZXIgb24uDQoNCldlJ3JlIGdvaW5nIHRvIGV4cGVyaW1lbnQgd2l0aCB0aGF0IGJ5IGdyYWJiaW5nIEhhZGxleSBXaWNraGFtJ3Mgd2lraXBlZGlhIHBhZ2UsIHdpdGggcnZlc3QsIGFuZCB0aGVuIHByaW50aW5nIGl0IGp1c3QgdG8gc2VlIHdoYXQgdGhlIHN0cnVjdHVyZSBsb29rcyBsaWtlLg0KYGBge3J9DQojIExvYWQgcnZlc3QNCmxpYnJhcnkocnZlc3QpDQoNCiMgSGFkbGV5IFdpY2toYW0ncyBXaWtpcGVkaWEgcGFnZQ0KdGVzdF91cmwgPC0gImh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0hhZGxleV9XaWNraGFtIg0KDQojIFJlYWQgdGhlIFVSTCBzdG9yZWQgYXMgInRlc3RfdXJsIiB3aXRoIHJlYWRfaHRtbCgpDQp0ZXN0X3htbCA8LSByZWFkX2h0bWwodGVzdF91cmwpDQoNCiMgUHJpbnQgdGVzdF94bWwNCnRlc3RfeG1sDQpgYGANCiMjIyBFeHRyYWN0aW5nIG5vZGVzIGJ5IFhQQVRIDQoNCk5vdyB5b3UndmUgZ290IGEgSFRNTCBwYWdlIHJlYWQgaW50byBSLiBHcmVhdCEgQnV0IGhvdyBkbyB5b3UgZ2V0IGluZGl2aWR1YWwsIGlkZW50aWZpYWJsZSBwaWVjZXMgb2YgaXQ/DQoNClRoZSBhbnN3ZXIgaXMgdG8gdXNlIGh0bWxfbm9kZSgpLCB3aGljaCBleHRyYWN0cyBpbmRpdmlkdWFsIGNodW5rcyBvZiBIVE1MIGZyb20gYSBIVE1MIGRvY3VtZW50LiBUaGVyZSBhcmUgYSBjb3VwbGUgb2Ygd2F5cyBvZiBpZGVudGlmeWluZyBhbmQgZmlsdGVyaW5nIG5vZGVzLCBhbmQgZm9yIG5vdyB3ZSdyZSBnb2luZyB0byB1c2UgWFBBVEhzOiB1bmlxdWUgaWRlbnRpZmllcnMgZm9yIGluZGl2aWR1YWwgcGllY2VzIG9mIGEgSFRNTCBkb2N1bWVudC4NCg0KVGhlc2UgY2FuIGJlIHJldHJpZXZlZCB1c2luZyBhIGJyb3dzZXIgZ2FkZ2V0IHdlJ2xsIHRhbGsgYWJvdXQgbGF0ZXIgLSBpbiB0aGUgbWVhbndoaWxlIHRoZSBYUEFUSCBmb3IgdGhlIGluZm9ybWF0aW9uIGJveCBpbiB0aGUgcGFnZSB5b3UganVzdCBkb3dubG9hZGVkIGlzIHN0b3JlZCBhcyB0ZXN0X25vZGVfeHBhdGguIFdlJ3JlIGdvaW5nIHRvIHJldHJpZXZlIHRoZSBib3ggZnJvbSB0aGUgSFRNTCBkb2Mgd2l0aCBodG1sX25vZGUoKSwgdXNpbmcgdGVzdF9ub2RlX3hwYXRoIGFzIHRoZSB4cGF0aCBhcmd1bWVudC4NCg0KYGBge3J9DQp0ZXN0X25vZGVfeHBhdGggPC0gIi8vKltjb250YWlucyhjb25jYXQoIFwiIFwiLCBAY2xhc3MsIFwiIFwiICksIGNvbmNhdCggXCIgXCIsIFwidmNhcmRcIiwgXCIgXCIgKSldIg0KYGBgDQpgYGB7cn0NCiMgVXNlIGh0bWxfbm9kZSgpIHRvIGdyYWIgdGhlIG5vZGUgd2l0aCB0aGUgWFBBVEggc3RvcmVkIGFzIGB0ZXN0X25vZGVfeHBhdGhgDQpub2RlIDwtIGh0bWxfbm9kZSh4ID0gdGVzdF94bWwsIHhwYXRoID0gdGVzdF9ub2RlX3hwYXRoKQ0KDQojIFByaW50IHRoZSBmaXJzdCBlbGVtZW50IG9mIHRoZSByZXN1bHQNCm5vZGVbMV0NCmBgYA0KWE1MIG5vZGVzIGFyZSB0aGUgYnVpbGRpbmcgYmxvY2sgb2YgYW4gWE1MIGRvY3VtZW50IC0gZXh0cmFjdGluZyB0aGVtIGxlYWRzIHRvIGV2ZXJ5dGhpbmcgZWxzZS4gDQoNCiMjIEhUTUwgc3RydWN0dXJlDQoNCiMjIyBFeHRyYWN0aW5nIG5hbWVzDQoNClRoZSBmaXJzdCB0aGluZyB3ZSdsbCBncmFiIGlzIGEgbmFtZSwgZnJvbSB0aGUgZmlyc3QgZWxlbWVudCBvZiB0aGUgcHJldmlvdXNseSBleHRyYWN0ZWQgdGFibGUgKG5vdyBzdG9yZWQgYXMgdGFibGVfZWxlbWVudCkuIFdlIGNhbiBkbyB0aGlzIHdpdGggaHRtbF9uYW1lKCkuIEFzIHlvdSBtYXkgcmVjYWxsIGZyb20gd2hlbiB5b3UgcHJpbnRlZCBpdCwgdGhlIGVsZW1lbnQgaGFzIHRoZSB0YWcgPHRhYmxlPi4uLjwvdGFibGU+LCBzbyB3ZSdkIGV4cGVjdCB0aGUgbmFtZSB0byBiZSwgd2VsbCwgdGFibGUuDQoNCmBgYHtyfQ0KIyBFeHRyYWN0IHRoZSBuYW1lIG9mIHRhYmxlX2VsZW1lbnQNCmVsZW1lbnRfbmFtZSA8LSBodG1sX25hbWUobm9kZSkNCg0KIyBQcmludCB0aGUgbmFtZQ0KZWxlbWVudF9uYW1lDQpgYGANCllvdSd2ZSBzdGFydGVkIGV4dHJhY3RpbmcgY29tcG9uZW50cyBmcm9tIEhUTUwgYW5kIFhNTCBub2Rlcy4gVGhlIHRhZyBtaWdodCBub3Qgc2VlbSBpbXBvcnRhbnQgKGFuZCBtb3N0IG9mIHRoZSB0aW1lLCBpdCdzIG5vdCkgYnV0IGl0J3MgYSBnb29kIGZpcnN0IHN0ZXAsIGFuZCB0aGUgYWN0dWFsIG5vZGUgY29udGVudHMgKHRleHQsIHNheSkgaXMgc29tZXRoaW5nIHdlJ2xsIG1vdmUgb24gdG8gbmV4dC4NCg0KIyMjIEV4dHJhY3RpbmcgdmFsdWVzDQoNCkp1c3Qga25vd2luZyB0aGUgdHlwZSBvZiBIVE1MIG9iamVjdCBhIG5vZGUgaXMgaXNuJ3QgbXVjaCB1c2UsIHRob3VnaCAoYWx0aG91Z2ggaXQgY2FuIGJlIHZlcnkgaGVscGZ1bCkuIFdoYXQgd2UgcmVhbGx5IHdhbnQgaXMgdG8gZXh0cmFjdCB0aGUgYWN0dWFsIHRleHQgc3RvcmVkIHdpdGhpbiB0aGUgdmFsdWUuDQoNCldlIGNhbiBkbyB0aGF0IHdpdGggKHNob2NrZXIpIGh0bWxfdGV4dCgpLCBhbm90aGVyIGNvbnZlbmllbnQgcnZlc3QgZnVuY3Rpb24gdGhhdCBhY2NlcHRzIGEgbm9kZSBhbmQgcGFzc2VzIGJhY2sgdGhlIHRleHQgaW5zaWRlIGl0LiBGb3IgdGhpcyB3ZSdsbCB3YW50IGEgbm9kZSB3aXRoaW4gdGhlIGV4dHJhY3RlZCBlbGVtZW50IC0gc3BlY2lmaWNhbGx5LCB0aGUgb25lIGNvbnRhaW5pbmcgdGhlIHBhZ2UgdGl0bGUuIFRoZSB4cGF0aCB2YWx1ZSBmb3IgdGhhdCBub2RlIGlzIHN0b3JlZCBhcyBzZWNvbmRfeHBhdGhfdmFsLg0KDQpVc2luZyB0aGlzIHhwYXRoIHZhbHVlLCBleHRyYWN0IHRoZSBub2RlIHdpdGhpbiB0YWJsZV9lbGVtZW50IHRoYXQgd2Ugd2FudCwgYW5kIHRoZW4gdXNlIGh0bWxfdGV4dCB0byBleHRyYWN0IHRoZSB0ZXh0LCBiZWZvcmUgcHJpbnRpbmcgaXQuDQpgYGB7cn0NCnNlY29uZF94cGF0aF92YWwgPC0gIi8vKltjb250YWlucyhjb25jYXQoIFwiIFwiLCBAY2xhc3MsIFwiIFwiICksIGNvbmNhdCggXCIgXCIsIFwiZm5cIiwgXCIgXCIgKSldIg0KYGBgDQpgYGB7cn0NCiMgRXh0cmFjdCB0aGUgZWxlbWVudCBvZiB0YWJsZV9lbGVtZW50IHJlZmVycmVkIHRvIGJ5IHNlY29uZF94cGF0aF92YWwgYW5kIHN0b3JlIGl0IGFzIHBhZ2VfbmFtZQ0KcGFnZV9uYW1lIDwtIGh0bWxfbm9kZSh4ID0gdGVzdF94bWwsIHhwYXRoID0gc2Vjb25kX3hwYXRoX3ZhbCkNCg0KIyBFeHRyYWN0IHRoZSB0ZXh0IGZyb20gcGFnZV9uYW1lDQpwYWdlX3RpdGxlIDwtIGh0bWxfdGV4dChwYWdlX25hbWUpDQoNCiMgUHJpbnQgcGFnZV90aXRsZQ0KcGFnZV90aXRsZQ0KYGBgDQpUZXh0IGV4dHJhY3Rpb24gaXMgbW9zdCBvZiB3aGF0IHlvdSdyZSBsaWtlbHkgdG8gZG8gd2l0aCBYTUwgLSBhZnRlciBhbGwsIHRoZSBjb250ZW50IG9mIGFuIFhNTCB0YWcgaXMgYWxtb3N0IGFsd2F5cyB0aGUgaW1wb3J0YW50IHZhbHVlLiBJZiBpdCdzIGNvbnNpc3RlbnRseSBhIHNldCBvZiBkaWdpdHMsIHNheSwgeW91IGNhbiBhbHdheXMgdXNlIGFzLmludGVnZXIoKSBvciBhcy5udW1lcmljKCkgdG8gdHVybiBpdCBmcm9tIGEgc3RyaW5nLCBpbnRvIGEgbnVtYmVyLg0KDQojIyBSZWZvcm1hdHRpbmcgRGF0YQ0KDQojIyMgRXh0cmFjdGluZyB0YWJsZXMNCg0KVGhlIGRhdGEgZnJvbSBXaWtpcGVkaWEgdGhhdCB3ZSd2ZSBiZWVuIHBsYXlpbmcgYXJvdW5kIHdpdGggY2FuIGJlIGV4dHJhY3RlZCBiaXQgYnkgYml0IGFuZCBjbGVhbmVkIHVwIG1hbnVhbGx5LCBidXQgc2luY2UgaXQncyBhIHRhYmxlLCB3ZSBoYXZlIGFuIGVhc2llciB3YXkgb2YgdHVybmluZyBpdCBpbnRvIGFuIFIgb2JqZWN0LiBydmVzdCBjb250YWlucyB0aGUgZnVuY3Rpb24gaHRtbF90YWJsZSgpIHdoaWNoLCBhcyB0aGUgbmFtZSBzdWdnZXN0cywgZXh0cmFjdHMgdGFibGVzLiBJdCBhY2NlcHRzIGEgbm9kZSBjb250YWluaW5nIGEgdGFibGUgb2JqZWN0LCBhbmQgb3V0cHV0cyBhIGRhdGEgZnJhbWUuDQpgYGB7cn0NCiMgVHVybiB0YWJsZV9lbGVtZW50IGludG8gYSBkYXRhIGZyYW1lIGFuZCBhc3NpZ24gaXQgdG8gd2lraV90YWJsZQ0Kd2lraV90YWJsZSA8LSBodG1sX3RhYmxlKG5vZGUpDQoNCiMgUHJpbnQgd2lraV90YWJsZQ0Kd2lraV90YWJsZQ0KYGBgDQpCZWluZyBhYmxlIHRvIGV4dHJhY3QgdGFibGVzIGRpcmVjdGx5IGlzIGEgbWFzc2l2ZSBzcGVlZHVwLCBzaW5jZSBvdGhlcndpc2UgdGhleSdyZSBhIHRvbiBvZiBkaWZmZXJlbnQgbmVzdGVkIHRhZ3MuDQoNCiMjIyBDbGVhbmluZyBhIGRhdGEgZnJhbWUNCg0KbiB0aGUgbGFzdCBleGVyY2lzZSwgd2UgbG9va2VkIGF0IGV4dHJhY3RpbmcgdGFibGVzIHdpdGggaHRtbF90YWJsZSgpLiBUaGUgcmVzdWx0aW5nIGRhdGEgZnJhbWUgd2FzIHByZXR0eSBjbGVhbiwgYnV0IGhhZCB0d28gcHJvYmxlbXMgLSBmaXJzdCwgdGhlIGNvbHVtbiBuYW1lcyB3ZXJlbid0IGRlc2NyaXB0aXZlLCBhbmQgc2Vjb25kLCB0aGVyZSB3YXMgYW4gZW1wdHkgcm93Lg0KDQpJbiB0aGlzIGV4ZXJjaXNlIHdlJ3JlIGdvaW5nIHRvIGxvb2sgYXQgZml4aW5nIGJvdGggb2YgdGhvc2UgcHJvYmxlbXMuIEZpcnN0LCBjb2x1bW4gbmFtZXMuIENvbHVtbiBuYW1lcyBjYW4gYmUgY2xlYW5lZCB1cCB3aXRoIHRoZSBjb2xuYW1lcygpIGZ1bmN0aW9uLiBZb3UgY2FsbCBpdCBvbiB0aGUgb2JqZWN0IHlvdSB3YW50IHRvIHJlbmFtZSwgYW5kIHRoZW4gYXNzaWduIHRvIHRoYXQgY2FsbCBhIHZlY3RvciBvZiBuZXcgbmFtZXMuDQoNClRoZSBtaXNzaW5nIHJvdywgbWVhbndoaWxlLCBjYW4gYmUgcmVtb3ZlZCB3aXRoIHRoZSBzdWJzZXQoKSBmdW5jdGlvbi4gc3Vic2V0IHRha2VzIGFuIG9iamVjdCwgYW5kIGEgY29uZGl0aW9uLiBGb3IgZXhhbXBsZSwgaWYgeW91IGhhdmUgYSBkYXRhIGZyYW1lIGRmIGNvbnRhaW5pbmcgYSBjb2x1bW4geCwgeW91IGNvdWxkIHJ1bg0KDQogICAgc3Vic2V0KGRmLCAheCA9PSAiIikNCiAgICANCnRvIHJlbW92ZSBhbGwgcm93cyBmcm9tIGRmIGNvbnNpc3Rpbmcgb2YgZW1wdHkgc3RyaW5ncyAoIiIpIGluIHRoZSBjb2x1bW4geC4NCmBgYHtyfQ0KIyBSZW5hbWUgdGhlIGNvbHVtbnMgb2Ygd2lraV90YWJsZQ0KY29sbmFtZXMod2lraV90YWJsZSkgPC0gYygia2V5IiwgInZhbHVlIikNCg0KIyBSZW1vdmUgdGhlIGVtcHR5IHJvdyBmcm9tIHdpa2lfdGFibGUNCmNsZWFuZWRfdGFibGUgPC0gc3Vic2V0KHdpa2lfdGFibGUsICFrZXkgPT0gIiIpDQoNCiMgUHJpbnQgY2xlYW5lZF90YWJsZQ0KY2xlYW5lZF90YWJsZQ0KYGBgDQpDbGVhbmluZyB1cCBkYXRhLCBvciAnbXVuZ2luZycsIGlzIGEgcmVhbGx5IGNvbW1vbiB0aGluZyB0byBoYXZlIHRvIGRvLCBwYXJ0aWN1bGFybHkgd2hlbiBzb21lb25lIGVsc2UgcGlja2VkIGhvdyBpdCdzIGZvcm1hdHRlZC4gSWYgeW91IGNhbiBzY3JhcGUgZGF0YSBhbmQgY2xlYW4gaXQsIHlvdSBjYW4gZG8gYW55dGhpbmcuDQoNCiMgQ1NTIFdlYiBTY3JhcGluZyBhbmQgRmluYWwgQ2FzZSBTdHVkeQ0KDQojIyBDU1Mgd2ViIHNjcmFwaW5nIGluIHRoZW9yeQ0KDQpVc2luZyBDU1MgdG8gc2NyYXBlIG5vZGVzDQpBcyBtZW50aW9uZWQgaW4gdGhlIHZpZGVvLCBDU1MgaXMgYSB3YXkgdG8gYWRkIGRlc2lnbiBpbmZvcm1hdGlvbiB0byBIVE1MLCB0aGF0IGluc3RydWN0cyB0aGUgYnJvd3NlciBvbiBob3cgdG8gZGlzcGxheSB0aGUgY29udGVudC4gWW91IGNhbiBsZXZlcmFnZSB0aGVzZSBkZXNpZ24gaW5zdHJ1Y3Rpb25zIHRvIGlkZW50aWZ5IGNvbnRlbnQgb24gdGhlIHBhZ2UuDQoNCllvdSd2ZSBhbHJlYWR5IHVzZWQgaHRtbF9ub2RlKCksIGJ1dCBpdCdzIG1vcmUgY29tbW9uIHdpdGggQ1NTIHNlbGVjdG9ycyB0byB1c2UgaHRtbF9ub2RlcygpIHNpbmNlIHlvdSdsbCBvZnRlbiB3YW50IG1vcmUgdGhhbiBvbmUgbm9kZSByZXR1cm5lZC4gQm90aCBmdW5jdGlvbnMgYWxsb3cgeW91IHRvIHNwZWNpZnkgYSBjc3MgYXJndW1lbnQgdG8gdXNlIGEgQ1NTIHNlbGVjdG9yLCBpbnN0ZWFkIG9mIHNwZWNpZnlpbmcgdGhlIHhwYXRoIGFyZ3VtZW50Lg0KDQpgYGB7cn0NCiMgU2VsZWN0IHRoZSB0YWJsZSBlbGVtZW50cw0KaHRtbF9ub2Rlcyh0ZXN0X3htbCwgY3NzID0gInRhYmxlIikNCg0KIyBTZWxlY3QgZWxlbWVudHMgd2l0aCBjbGFzcyA9ICJpbmZvYm94Ig0KaHRtbF9ub2Rlcyh0ZXN0X3htbCwgY3NzID0gIi5pbmZvYm94IikNCg0KIyBTZWxlY3QgZWxlbWVudHMgd2l0aCBpZCA9ICJmaXJzdEhlYWRpbmciDQpodG1sX25vZGVzKHRlc3RfeG1sLCBjc3MgPSAiI2ZpcnN0SGVhZGluZyIpDQpgYGANCg0KIyMgU2NyYXBpbmcgbmFtZXMNCg0KWW91IG1pZ2h0IGhhdmUgbm90aWNlZCBpbiB0aGUgcHJldmlvdXMgZXhlcmNpc2UsIHRvIHNlbGVjdCBlbGVtZW50cyB3aXRoIGEgY2VydGFpbiBjbGFzcywgeW91IGFkZCBhIC4gaW4gZnJvbnQgb2YgdGhlIGNsYXNzIG5hbWUuIElmIHlvdSBuZWVkIHRvIHNlbGVjdCBhbiBlbGVtZW50IGJhc2VkIG9uIGl0cyBpZCwgeW91IGFkZCBhICMgaW4gZnJvbnQgb2YgdGhlIGlkIG5hbWUuDQoNCkZvciBleGFtcGxlIGlmIHRoaXMgZWxlbWVudCB3YXMgaW5zaWRlIHlvdXIgSFRNTCBkb2N1bWVudDoNCg0KICAgIDxoMSBjbGFzcyA9ICJoZWFkaW5nIiBpZCA9ICJpbnRybyI+DQogICAgICBJbnRyb2R1Y3Rpb24NCiAgICA8L2gxPg0KWW91IGNvdWxkIHNlbGVjdCBpdCBieSBpdHMgY2xhc3MgdXNpbmcgdGhlIENTUyBzZWxlY3RvciAiLmhlYWRpbmciLCBvciBieSBpdHMgaWQgdXNpbmcgdGhlIENTUyBzZWxlY3RvciAiI2ludHJvIi4NCg0KT25jZSB5b3UndmUgc2VsZWN0ZWQgYW4gZWxlbWVudCB3aXRoIGEgQ1NTIHNlbGVjdG9yLCB5b3UgY2FuIGdldCB0aGUgZWxlbWVudCB0YWcgbmFtZSBqdXN0IGxpa2UgeW91IGRpZCB3aXRoIFhQQVRIIHNlbGVjdG9ycywgd2l0aCBodG1sX25hbWUoKS4NCg0KYGBge3J9DQojIEV4dHJhY3QgZWxlbWVudCB3aXRoIGNsYXNzIGluZm9ib3gNCmluZm9ib3hfZWxlbWVudCA8LSBodG1sX25vZGVzKHRlc3RfeG1sLCBjc3MgPSAiLmluZm9ib3giKQ0KDQojIEdldCB0YWcgbmFtZSBvZiBpbmZvYm94X2VsZW1lbnQNCmVsZW1lbnRfbmFtZSA8LSBodG1sX25hbWUoaW5mb2JveF9lbGVtZW50KQ0KDQojIFByaW50IGVsZW1lbnRfbmFtZQ0KZWxlbWVudF9uYW1lDQpgYGANCiMjIyBTY3JhcGluZyB0ZXh0DQoNCk9mIGNvdXJzZSB5b3UgY2FuIGdldCB0aGUgY29udGVudHMgb2YgYSBub2RlIGV4dHJhY3RlZCB1c2luZyBhIENTUyBzZWxlY3RvciB0b28sIHdpdGggaHRtbF90ZXh0KCkuDQpgYGB7cn0NCiMgRXh0cmFjdCBlbGVtZW50IHdpdGggY2xhc3MgZm4NCnBhZ2VfbmFtZSA8LSBodG1sX25vZGUoeCA9IGluZm9ib3hfZWxlbWVudCwgIi5mbiIpDQoNCiMgR2V0IGNvbnRlbnRzIG9mIHBhZ2VfbmFtZQ0KcGFnZV90aXRsZSA8LSBodG1sX3RleHQocGFnZV9uYW1lKQ0KDQojIFByaW50IHBhZ2VfdGl0bGUNCnBhZ2VfdGl0bGUNCmBgYA0KV2h5IGRvIHlvdSB0aGluayB0aGUgY2xhc3MgZm9yIHRoaXMgZWxlbWVudCBpcyBmbj8gSSBzdXNwZWN0IGl0J3Mgc2hvcnQgZm9yIGZ1bGwgbmFtZS4NCg0KIyMgRmluYWwgY2FzZSBzdHVkeTogSW50cm9kdWN0aW9uDQoNCiMjIyBBUEkgY2FsbHMNCg0KWW91ciBmaXJzdCBzdGVwIGlzIHRvIHVzZSB0aGUgV2lraXBlZGlhIEFQSSB0byBnZXQgdGhlIHBhZ2UgY29udGVudHMgZm9yIGEgc3BlY2lmaWMgcGFnZS4gV2UnbGwgY29udGludWUgdG8gd29yayB3aXRoIHRoZSBIYWRsZXkgV2lja2hhbSBwYWdlLCBidXQgYXMgeW91ciBsYXN0IGV4ZXJjaXNlLCB5b3UnbGwgbWFrZSBpdCBtb3JlIGdlbmVyYWwuDQoNClRvIGdldCB0aGUgY29udGVudCBvZiBhIHBhZ2UgZnJvbSB0aGUgV2lraXBlZGlhIEFQSSB5b3UgbmVlZCB0byB1c2UgYSBwYXJhbWV0ZXIgYmFzZWQgVVJMLiBUaGUgVVJMIHlvdSB3YW50IGlzDQoNCiAgICBodHRwczovL2VuLndpa2lwZWRpYS5vcmcvdy9hcGkucGhwP2FjdGlvbj1wYXJzZSZwYWdlPUhhZGxleSUyMFdpY2toYW0mZm9ybWF0PXhtbA0KDQp3aGljaCBzcGVjaWZpZXMgdGhhdCB5b3Ugd2FudCB0aGUgcGFyc2VkIGNvbnRlbnQgKGkuZSB0aGUgSFRNTCkgZm9yIHRoZSAiSGFkbGV5IFdpY2toYW0iIHBhZ2UsIGFuZCB0aGUgQVBJIHJlc3BvbnNlIHNob3VsZCBiZSBYTUwuDQpgYGB7cn0NCiMgTG9hZCBodHRyDQpsaWJyYXJ5KGh0dHIpDQoNCiMgVGhlIEFQSSB1cmwNCmJhc2VfdXJsIDwtICJodHRwczovL2VuLndpa2lwZWRpYS5vcmcvdy9hcGkucGhwIg0KDQojIFNldCBxdWVyeSBwYXJhbWV0ZXJzDQpxdWVyeV9wYXJhbXMgPC0gbGlzdChhY3Rpb24gPSAicGFyc2UiLCANCiAgcGFnZSA9ICJIYWRsZXkgV2lja2hhbSIsIA0KICBmb3JtYXQgPSAieG1sIikNCg0KIyBHZXQgZGF0YSBmcm9tIEFQSQ0KcmVzcCA8LSBHRVQodXJsID0gYmFzZV91cmwsIHF1ZXJ5ID0gcXVlcnlfcGFyYW1zKQ0KICAgIA0KIyBQYXJzZSByZXNwb25zZQ0KcmVzcF94bWwgPC0gY29udGVudChyZXNwKQ0KYGBgDQojIyMgRXh0cmFjdGluZyBpbmZvcm1hdGlvbg0KDQpOb3cgd2UgaGF2ZSBhIHJlc3BvbnNlIGZyb20gdGhlIEFQSSwgd2UgbmVlZCB0byBleHRyYWN0IHRoZSBIVE1MIGZvciB0aGUgcGFnZSBmcm9tIGl0LiBJdCB0dXJucyBvdXQgdGhlIEhUTUwgaXMgc3RvcmVkIGluIHRoZSBjb250ZW50cyBvZiB0aGUgWE1MIHJlc3BvbnNlLg0KVGFrZSBhIGxvb2ssIGJ5IHVzaW5nIHhtbF90ZXh0KCkgdG8gcHVsbCBvdXQgdGhlIHRleHQgZnJvbSB0aGUgWE1MIHJlc3BvbnNlOg0KDQogICAgeG1sX3RleHQocmVzcF94bWwpDQoNCmBgYHtyfQ0KIyBMb2FkIHJ2ZXN0DQpsaWJyYXJ5KHJ2ZXN0KQ0KDQojIFJlYWQgcGFnZSBjb250ZW50cyBhcyBIVE1MDQpwYWdlX2h0bWwgPC0gcmVhZF9odG1sKHhtbF90ZXh0KHJlc3BfeG1sKSkNCg0KIyBFeHRyYWN0IGluZm9ib3ggZWxlbWVudA0KaW5mb2JveF9lbGVtZW50IDwtIGh0bWxfbm9kZShwYWdlX2h0bWwsICIuaW5mb2JveCIpDQoNCiMgRXh0cmFjdCBwYWdlIG5hbWUgZWxlbWVudCBmcm9tIGluZm9ib3gNCnBhZ2VfbmFtZSA8LSBodG1sX25vZGUoaW5mb2JveF9lbGVtZW50LCAiLmZuIikNCg0KIyBFeHRyYWN0IHBhZ2UgbmFtZSBhcyB0ZXh0DQpwYWdlX3RpdGxlIDwtIGh0bWxfdGV4dChwYWdlX25hbWUpDQpgYGANCiMjIyBOb3JtYWxpc2luZyBpbmZvcm1hdGlvbg0KDQpOb3cgaXQncyB0aW1lIHRvIHB1dCB0b2dldGhlciB0aGUgaW5mb3JtYXRpb24gaW4gYSBuaWNlIGZvcm1hdC4gWW91J3ZlIGFscmVhZHkgc2VlbiB5b3UgY2FuIHVzZSBodG1sX3RhYmxlKCkgdG8gcGFyc2UgdGhlIGluZm9ib3ggaW50byBhIGRhdGEgZnJhbWUuIEJ1dCBvbmUgcGllY2Ugb2YgaW1wb3J0YW50IGluZm9ybWF0aW9uIGlzIG1pc3NpbmcgZnJvbSB0aGF0IHRhYmxlOiB3aG8gdGhlIGluZm9ybWF0aW9uIGlzIGFib3V0IQ0KYGBge3J9DQojIFlvdXIgY29kZSBmcm9tIGVhcmxpZXIgZXhlcmNpc2VzDQp3aWtpX3RhYmxlIDwtIGh0bWxfdGFibGUoaW5mb2JveF9lbGVtZW50KQ0KY29sbmFtZXMod2lraV90YWJsZSkgPC0gYygia2V5IiwgInZhbHVlIikNCmNsZWFuZWRfdGFibGUgPC0gc3Vic2V0KHdpa2lfdGFibGUsICFrZXkgPT0gIiIpDQoNCiMgQ3JlYXRlIGEgZGF0YWZyYW1lIGZvciBmdWxsIG5hbWUNCm5hbWVfZGYgPC0gZGF0YS5mcmFtZShrZXkgPSAiRnVsbCBuYW1lIiwgdmFsdWUgPSBwYWdlX3RpdGxlKQ0KDQojIENvbWJpbmUgbmFtZV9kZiB3aXRoIGNsZWFuZWRfdGFibGUNCndpa2lfdGFibGUyIDwtIHJiaW5kKG5hbWVfZGYsIGNsZWFuZWRfdGFibGUpDQoNCiMgUHJpbnQgd2lraV90YWJsZQ0Kd2lraV90YWJsZTINCmBgYA0KIyMjIFJlcHJvZHVjaWJpbGl0eQ0KDQpOb3cgeW91J3ZlIGZpZ3VyZWQgb3V0IHRoZSBwcm9jZXNzIGZvciByZXF1ZXN0aW5nIGFuZCBwYXJzaW5nIHRoZSBpbmZvYm94IGZvciB0aGUgSGFkbGV5IFdpY2toYW0gcGFnZSwgaXQncyB0aW1lIHRvIHR1cm4gaXQgaW50byBhIGZ1bmN0aW9uIHRoYXQgZG9lcyB0aGUgc2FtZSB0aGluZyBmb3IgYW55b25lLg0KDQpZb3UndmUgYWxyZWFkeSBkb25lIGFsbCB0aGUgaGFyZCB3b3JrISBJbiB0aGUgc2FtcGxlIHNjcmlwdCB3ZSd2ZSBqdXN0IGNvcGllZCBhbGwgeW91ciBjb2RlIGZyb20gdGhlIHByZXZpb3VzIHRocmVlIGV4ZXJjaXNlcywgd2l0aCBvbmx5IG9uZSBjaGFuZ2U6IHdlJ3ZlIHdyYXBwZWQgaXQgaW4gdGhlIGZ1bmN0aW9uIGRlZmluaXRpb24gc3ludGF4LCBhbmQgY2hvc2VuIHRoZSBuYW1lIGdldF9pbmZvYm94KCkgZm9yIHRoaXMgZnVuY3Rpb24uDQpgYGB7cn0NCmxpYnJhcnkoaHR0cikNCmxpYnJhcnkocnZlc3QpDQpsaWJyYXJ5KHhtbDIpDQoNCmdldF9pbmZvYm94IDwtIGZ1bmN0aW9uKHRpdGxlKXsNCiAgYmFzZV91cmwgPC0gImh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93L2FwaS5waHAiDQogIA0KICAjIENoYW5nZSAiSGFkbGV5IFdpY2toYW0iIHRvIHRpdGxlDQogIHF1ZXJ5X3BhcmFtcyA8LSBsaXN0KGFjdGlvbiA9ICJwYXJzZSIsIA0KICAgIHBhZ2UgPSB0aXRsZSwgDQogICAgZm9ybWF0ID0gInhtbCIpDQogIA0KICByZXNwIDwtIEdFVCh1cmwgPSBiYXNlX3VybCwgcXVlcnkgPSBxdWVyeV9wYXJhbXMpDQogIHJlc3BfeG1sIDwtIGNvbnRlbnQocmVzcCkNCiAgDQogIHBhZ2VfaHRtbCA8LSByZWFkX2h0bWwoeG1sX3RleHQocmVzcF94bWwpKQ0KICBpbmZvYm94X2VsZW1lbnQgPC0gaHRtbF9ub2RlKHggPSBwYWdlX2h0bWwsIGNzcyA9Ii5pbmZvYm94IikNCiAgcGFnZV9uYW1lIDwtIGh0bWxfbm9kZSh4ID0gaW5mb2JveF9lbGVtZW50LCBjc3MgPSAiLmZuIikNCiAgcGFnZV90aXRsZSA8LSBodG1sX3RleHQocGFnZV9uYW1lKQ0KICANCiAgd2lraV90YWJsZSA8LSBodG1sX3RhYmxlKGluZm9ib3hfZWxlbWVudCkNCiAgY29sbmFtZXMod2lraV90YWJsZSkgPC0gYygia2V5IiwgInZhbHVlIikNCiAgY2xlYW5lZF90YWJsZSA8LSBzdWJzZXQod2lraV90YWJsZSwgIXdpa2lfdGFibGUka2V5ID09ICIiKQ0KICBuYW1lX2RmIDwtIGRhdGEuZnJhbWUoa2V5ID0gIkZ1bGwgbmFtZSIsIHZhbHVlID0gcGFnZV90aXRsZSkNCiAgd2lraV90YWJsZSA8LSByYmluZChuYW1lX2RmLCBjbGVhbmVkX3RhYmxlKQ0KICANCiAgd2lraV90YWJsZQ0KfQ0KDQojIFRlc3QgZ2V0X2luZm9ib3ggd2l0aCAiSGFkbGV5IFdpY2toYW0iDQpnZXRfaW5mb2JveCh0aXRsZSA9ICJIYWRsZXkgV2lja2hhbSIpDQoNCiMgVHJ5IGdldF9pbmZvYm94IHdpdGggIlJvc3MgSWhha2EiDQpnZXRfaW5mb2JveCh0aXRsZSA9ICJSb3NzIEloYWthIikNCg0KIyBUcnkgZ2V0X2luZm9ib3ggd2l0aCAiR3JhY2UgSG9wcGVyIg0KZ2V0X2luZm9ib3godGl0bGUgPSAiR3JhY2UgSG9wcGVyIikNCmBgYA0KDQo=