Libraries needed for this section are:

Data needed:

1. About geocoding

What is Geocoding?

  • Geocoding is the process of transforming a description of a location (such as an address, name of a place, or coordinates) to a location on the earth’s surface.”
  • “A geocoder is a piece of software or a (web) service that implements a geocoding process i.e. a set of inter-related components in the form of operations, algorithms, and data sources that work together to produce a spatial representation for descriptive locational references.”
  • Reverse geocoding uses geographic coordinates to find a description of the location, most typically a postal address or place name.” (I rarely have needed to do this.)1

How is it done?

There are a number of ways, for example:

  • Interpolation for Street adresses

Interpolation for street adress geocoding

Issues

  • Quality of input data or: how specific is your location information?
  • Quality of output data: return rate, accuracy and precision
  • Regional differences: Geocoding adresses in the US is a different from than geocoding in Nigeria or Germany. Geocoding adresses in suburban Chicago is different from geocoding in rural Alabama.
  • Limitations of geocoding services: Bulding and maintaining an accurate global geocoding service is very resource intensive. So is running a geocoding server, particularly if it is hit with millions of requests per second. For that reason geocoding services are usually very limited in what they offer for free, if at all. Secondly, the results returned usually vary from service to service in terms of accuracy and values returned.

2. About APIs

What is an API and what does it have to do with geocoding?


Exercise 1

  1. Go to your browser.

  2. Point it to: http://maps.googleapis.com/maps/api/geocode/xml?address=Stanford+CA

  3. What just happened?

  4. Now write the URL to return the location of Toledo – what do we get here?

  5. Now we want the city in Spain. Extra bonus: Request the results in JSON format.

Here is some documentation: https://developers.google.com/maps/documentation/geocoding/

http://maps.googleapis.com/maps/api/geocode/xml?address=Toledo

http://maps.googleapis.com/maps/api/geocode/json?address=Toledo&region=ES

# this also works:
http://maps.googleapis.com/maps/api/geocode/json?address=Toledo,Spain

What have we learned?

  • We can use an API to access a service (or tools) provided by someone else without knowing the details of how this service or tool is implemented.
  • A geocoding API provides a direct way to access these services via an HTTP request (simply speaking: a URL).
  • A geocoding service API request must be in a particular form as specified by the service provier.
  • Geocoding services responses are returned in a structured format, which is typically XML or JSON, sometimes also KMZ.

Our goal is now to do what we did in a web browser from R. For this we have to take into account also:

  • Authentication: Using geocoders often may require a valid API key. The key can usually be requested for free by registering at the website of the provider.

  • Rate Limiting: Geocode service providers typically use a rate limiting mechanism to ensure that the service stays available to all users.

There are many geocoding providers2. They vary in terms of format specifications, access and use(!) resrictions, quality of results, and more. So choosing a geocoder for a research project depends on the specifics of your situation.

Generic structure of a geocoding script

If you were to write your own script, here are the basic steps to take.

  1. Load address data. Clean them up if necessary.

  2. Send each address to the geocoding service (typically: create a URL request for each address)

  3. Process results. Extract geogrpahic coordinates and any other values you are interested in and turn into into a convenient format, usually a table.

  4. Save the output.

3. Geocoding with the Google maps API

Interactively

We will start by using the geocode() command from the ggmap library.


Exercise 2

  1. Install (if you haven’t) and load the ggmap library.

  2. Using the geocode command, how would you search for location of the city of Toledo

library(ggmap)
geocode("Toledo")
  1. How does your result compare to what you got from when you did the query through the web browser? Check out the output= option of the command, what does that do?
geocode("Toledo", ouput = "all")
  1. Now geocode this address: "380 New York St, Redlands, CA". Compare the results you get with the Google geocoder versus the Data Science Toolkit geocoder.
geocode("380 New York St, Redlands, CA")
geocode("380 New York St, Redlands, CA", source = "dsk")

Batch geocoding

Now let’s write an R script to process an entire list of adresses for geocoding this way. We will use the generic outline above and implement it in R.


Exercise 3

  1. Load address data.

    Let’s use the addresses of declared dangerous dogs in Austin, TX, courtesy of the city of Austin’s open data portal.

    The direct link to the CSV file of the data is this one:

    https://data.austintexas.gov/api/views/ykw4-j3aj/rows.csv?accessType=DOWNLOAD

    Read the table into R and call it dogs.

dogs <- read.csv("https://data.austintexas.gov/api/views/ykw4-j3aj/rows.csv?accessType=DOWNLOAD")
  1. Send each address to the geocoding service.

    The nice thing is that geocode() can take a vector of adresses. So all we have to do is find out where the addresses are in our dogs data frame and then submit them to the geocode() function. And of course, catch the results in a variable.

    (For the purpose of this exercise we will overlook the fact that the coordinates already come with the table.)

# concatenate the address
dogs_adr <- paste(dogs$Address, "Austin,TX", dogs$Zip.Code)

# geocode - check for warnings
dogs_coords <- geocode(dogs_adr)
  1. Process results.

    Most of this is all taken care of already in the geocode() function, which returns a vector of geographic coordinates. All we need to do is bind it the back to our original dataframe.

dogs <- data.frame(cbind(dogs, dogs_coords))
  1. Save the output as shapefile.

    Saving the coordinates out as csv is pretty easy with write.table(). To save the data frame as a shapefile, we convert the dataframe to a Spatial*Dataframe first, assign a projection, and then save with writeOGR(). Remember that you will need both the sp and the rgdal libraries for this.

    Optional: save the shapefile using sf.

library(sp)
library(rgdal)

dogs <- dogs[complete.cases(dogs),] # remove NA geocoding results 
coordinates(dogs) <- c("lon", "lat") 
proj4string(dogs) <- CRS("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
writeOGR(dogs, ".", "dangerous_dogs", driver="ESRI Shapefile")


# If we were to use the `sf` package we would do:
    
library(sf)
dogs_sf <- st_as_sf(dogs, c("lon", "lat"), crs = "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
st_write(dogs_sf, "dangerous_dogs.shp")
  1. Now let’s use leaflet() to plot the dots over a basemap.
leaflet(dogs) %>%  
  addTiles() %>% 
  addCircleMarkers(dogs$lon, dogs$lat, popup=dogs$Description.of.Dog) 

Something like this:

4. Geocoding with the ArcGIS API (Stanford affiliates only)

Thanks to our fabulous geospatial manager Stace Maples who is tirelessly working to make our GIS lives easier we have our own geolocator at Stanford at

http://locator.stanford.edu/arcgis/rest/services/geocode

The services available here cover the US only. The good news here are that there are no limits as of how many addresses you can throw at this server. However, you should let Stace know if you are intending to run a major job!

You can find more details as of how to use this service from R here.


Exercise 4

  1. Using the provided function geocodeSL we can geocode the adress table from above using the ArcGIS geocoding service:
library(readr)
library(rgdal)

# set your token
myToken <- "XXXXXXXX"

# source the function
source("https://raw.githubusercontent.com/cengel/ArcGIS_geocoding/master/SUL_gcFunctions.R")

# geocode
dogs_coords2 <- do.call("rbind", lapply(dogs_adr, function(x) geocodeSL(x, myToken)))

# add attibutes back
dogs2 <- cbind(dogs, dogs_coords2)

# write out a CSV
write.csv(dogs2, "dangerous_dogs2.csv", row.names=F)

5. More geocoding services and libraries

Other R libraries

ggmap is certainly not the only R library that includes a geocoding function. The libraries below also do geocoding with different services:

For more see the Geolocation/Geocoding secion on the CRAN Task View for Web Technologies and Services.

A word about Open Data Science Toolkit (DSK)

The open Data Science Toolkit (DSK) is available as a self-contained Vagrant VM or EC2 AMI that you can deploy yourself. It includes a Google-style geocoder which emulates Google’s geocoding API. This API uses data from the US Census and OpenStreetMap, along with code from GeoIQ and Schuyler Erle’s Modular Street Address Geocoder.

Insructions for how to run DSK on Amazon or Vagrant are here: http://www.datasciencetoolkit.org/developerdocs#amazon

Note that geocode from ggmap also has the option to access DSK, but it will use their public server, which is often slow or unavailable.

Geocoding IP adresses

If you are interested to do this in R see here: https://github.com/cengel/r_IPgeocode

6. Putting it all together: Webscraping for maps

Now for fun. We will retrieve a table from the Wikipedia page on Crime statistics U.S. cities with a population of 250,000 or greater. We will use the city names, geocode their locations and plot the cities by population and crime rate.

You should be able to run the code below as is. It might take a little while.


Exercise 5

library(XML)
library(ggmap)
library(dplyr)
library(RCurl)
# read in the data
uri <- "https://en.wikipedia.org/wiki/List_of_United_States_cities_by_crime_rate"
# we want the second table: which=2
citiesCR <- readHTMLTable(getURL(uri), which = 2, stringsAsFactors = FALSE)
# clean up (with mutate_each function from dplyr): 
# remove the comma in 1,000 and above and convert numbers from strings to numeric
citiesCRclean <- mutate_each(citiesCR, funs(as.numeric(gsub(",", "", .))),  -(State:City))
 
# geocode loations
latlon <- geocode(paste(citiesCRclean$City, citiesCRclean$State, sep=", "))
# combine into a new dataframe
citiesCRll <- data.frame(citiesCRclean, latlon)
#get basmap
map_us <- get_map (location='United States', zoom=4, color="bw")
# plot
ggmap(map_us, legend='bottomright', extent='device') +
  geom_point(data=citiesCRll,
            aes(x=lon, y=lat, 
            color=Violent.Crime, 
            size=Population),
            na.rm=T) +
  scale_colour_gradient(low="white", high="red") +
  scale_size_continuous(range = c(4,12))


  1. From: https://en.wikipedia.org/wiki/Geocoding

  2. For a quite comprehensive overview look here and here.

LS0tCnRpdGxlOiAiR2VvY29kaW5nIGluIFIiCmF1dGhvcjogImNsYXVkaWEgYSBlbmdlbCIKZGF0ZTogIkxhc3QgdXBkYXRlZDogYHIgZm9ybWF0KFN5cy50aW1lKCksICclQiAlZCwgJVknKWAiCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazoKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgZmlnX2NhcHRpb246IG5vCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiA0Ci0tLQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KCiMgbG9hZCBhZGRpdGlvbmFsIGxpYnJhcmllcyBmb3IgYmVsb3cKbGlicmFyeShnZ21hcCkKbGlicmFyeShzcCkKbGlicmFyeShyZ2RhbCkKbGlicmFyeShodHRyKQpsaWJyYXJ5KGpzb25saXRlKQpsaWJyYXJ5KGxlYWZsZXQpCmBgYAoKTGlicmFyaWVzIG5lZWRlZCBmb3IgdGhpcyBzZWN0aW9uIGFyZToKCiogYGdnbWFwYAoqIGByZWFkcmAKKiBgc3BgCiogYHJnZGFsYAoqIGBodHRyYAoqIGBqc29ubGl0ZWAKCgpEYXRhIG5lZWRlZDoKCiogW1NvbWUgYWRkcmVzc2VzXShodHRwczovL3d3dy5kcm9wYm94LmNvbS9zL3owZWw2dmZnMXZ0bXh3NS9QaGlsbHlCYW5rc19zbS5jc3Y/ZGw9MSkKCiMgMS4gQWJvdXQgZ2VvY29kaW5nIAojIyBXaGF0IGlzIEdlb2NvZGluZz8KCi0gIioqR2VvY29kaW5nKiogaXMgdGhlIHByb2Nlc3Mgb2YgdHJhbnNmb3JtaW5nIGEgZGVzY3JpcHRpb24gb2YgYSBsb2NhdGlvbiAoc3VjaCBhcyBhbiBhZGRyZXNzLCBuYW1lIG9mIGEgcGxhY2UsIG9yIGNvb3JkaW5hdGVzKSB0byBhIGxvY2F0aW9uIG9uIHRoZSBlYXJ0aCdzIHN1cmZhY2UuIiAKLSAiQSAqKmdlb2NvZGVyKiogaXMgYSBwaWVjZSBvZiBzb2Z0d2FyZSBvciBhICh3ZWIpIHNlcnZpY2UgdGhhdCBpbXBsZW1lbnRzIGEgZ2VvY29kaW5nIHByb2Nlc3MgaS5lLiBhIHNldCBvZiBpbnRlci1yZWxhdGVkIGNvbXBvbmVudHMgaW4gdGhlIGZvcm0gb2Ygb3BlcmF0aW9ucywgYWxnb3JpdGhtcywgYW5kIGRhdGEgc291cmNlcyB0aGF0IHdvcmsgdG9nZXRoZXIgdG8gcHJvZHVjZSBhIHNwYXRpYWwgcmVwcmVzZW50YXRpb24gZm9yIGRlc2NyaXB0aXZlIGxvY2F0aW9uYWwgcmVmZXJlbmNlcy4iCi0gIioqUmV2ZXJzZSBnZW9jb2RpbmcqKiB1c2VzIGdlb2dyYXBoaWMgY29vcmRpbmF0ZXMgdG8gZmluZCBhIGRlc2NyaXB0aW9uIG9mIHRoZSBsb2NhdGlvbiwgbW9zdCB0eXBpY2FsbHkgYSBwb3N0YWwgYWRkcmVzcyBvciBwbGFjZSBuYW1lLiIgKEkgcmFyZWx5IGhhdmUgbmVlZGVkIHRvIGRvIHRoaXMuKVteMV0KClteMV06IEZyb206IGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0dlb2NvZGluZwoKIyMgSG93IGlzIGl0IGRvbmU/CgpUaGVyZSBhcmUgYSBudW1iZXIgb2Ygd2F5cywgZm9yIGV4YW1wbGU6CgotICoqSW50ZXJwb2xhdGlvbioqIGZvciBTdHJlZXQgYWRyZXNzZXMKCiFbSW50ZXJwb2xhdGlvbiBmb3Igc3RyZWV0IGFkcmVzcyBnZW9jb2RpbmddKGltYWdlcy9zdHJlZXRsZXZlbEdlb2NvZGluZy5wbmcpCgotICoqUm9vZnRvcCBMZXZlbCoqIGZvciBTdHJlZXQgYWRyZXNzZXMKCi0gT3BlbiBhY2Nlc3MgKipnYXpldHRlcnMgb3IgZGF0YWJhc2VzKiogZm9yIGxvY2F0aW9uL3BsYWNlIG5hbWVzIChodHRwOi8vZ2VvbmFtZXMub3JnIG9yIGh0dHA6Ly9nZW9uYW1lcy51c2dzLmdvdi9wbHMvZ25pc3B1YmxpYy8pIG9yIElQIGFkcmVzc2VzIChodHRwOi8vZnJlZWdlb2lwLm5ldCkKCiMjIElzc3VlcwotICoqUXVhbGl0eSBvZiBpbnB1dCBkYXRhKiogb3I6IGhvdyBzcGVjaWZpYyBpcyB5b3VyIGxvY2F0aW9uIGluZm9ybWF0aW9uPwotICoqUXVhbGl0eSBvZiBvdXRwdXQgZGF0YSoqOiByZXR1cm4gcmF0ZSwgYWNjdXJhY3kgYW5kIHByZWNpc2lvbgotICoqUmVnaW9uYWwgZGlmZmVyZW5jZXMqKjogR2VvY29kaW5nIGFkcmVzc2VzIGluIHRoZSBVUyBpcyBhIGRpZmZlcmVudCBmcm9tIHRoYW4gZ2VvY29kaW5nIGluIE5pZ2VyaWEgb3IgR2VybWFueS4gR2VvY29kaW5nIGFkcmVzc2VzIGluIHN1YnVyYmFuIENoaWNhZ28gaXMgZGlmZmVyZW50IGZyb20gZ2VvY29kaW5nIGluIHJ1cmFsIEFsYWJhbWEuCi0gKipMaW1pdGF0aW9ucyBvZiBnZW9jb2Rpbmcgc2VydmljZXMqKjogQnVsZGluZyBhbmQgbWFpbnRhaW5pbmcgYW4gYWNjdXJhdGUgZ2xvYmFsIGdlb2NvZGluZyBzZXJ2aWNlIGlzIHZlcnkgcmVzb3VyY2UgaW50ZW5zaXZlLiBTbyBpcyBydW5uaW5nIGEgZ2VvY29kaW5nIHNlcnZlciwgcGFydGljdWxhcmx5IGlmIGl0IGlzIGhpdCB3aXRoIG1pbGxpb25zIG9mIHJlcXVlc3RzIHBlciBzZWNvbmQuIEZvciB0aGF0IHJlYXNvbiBnZW9jb2Rpbmcgc2VydmljZXMgYXJlIHVzdWFsbHkgdmVyeSBsaW1pdGVkIGluIHdoYXQgdGhleSBvZmZlciBmb3IgZnJlZSwgaWYgYXQgYWxsLiBTZWNvbmRseSwgdGhlIHJlc3VsdHMgcmV0dXJuZWQgdXN1YWxseSB2YXJ5IGZyb20gc2VydmljZSB0byBzZXJ2aWNlIGluIHRlcm1zIG9mIGFjY3VyYWN5IGFuZCB2YWx1ZXMgcmV0dXJuZWQuCgojIDIuIEFib3V0IEFQSXMKIyMgV2hhdCBpcyBhbiBBUEkgYW5kIHdoYXQgZG9lcyBpdCBoYXZlIHRvIGRvIHdpdGggZ2VvY29kaW5nPwoKKioqCiMjIyBFeGVyY2lzZSAxCgojLiBHbyB0byB5b3VyIGJyb3dzZXIuCgojLiBQb2ludCBpdCB0bzogaHR0cDovL21hcHMuZ29vZ2xlYXBpcy5jb20vbWFwcy9hcGkvZ2VvY29kZS94bWw/YWRkcmVzcz1TdGFuZm9yZCtDQQoKIy4gV2hhdCBqdXN0IGhhcHBlbmVkPwoKIy4gTm93IHdyaXRlIHRoZSBVUkwgdG8gcmV0dXJuIHRoZSBsb2NhdGlvbiBvZiBUb2xlZG8gLS0gd2hhdCBkbyB3ZSBnZXQgaGVyZT8gCgojLiBOb3cgd2Ugd2FudCB0aGUgY2l0eSBpbiBTcGFpbi4gRXh0cmEgYm9udXM6IFJlcXVlc3QgdGhlIHJlc3VsdHMgaW4gSlNPTiBmb3JtYXQuIAoKSGVyZSBpcyBzb21lIGRvY3VtZW50YXRpb246IGh0dHBzOi8vZGV2ZWxvcGVycy5nb29nbGUuY29tL21hcHMvZG9jdW1lbnRhdGlvbi9nZW9jb2RpbmcvCgpgYGB7ciBldmFsPUZ9Cmh0dHA6Ly9tYXBzLmdvb2dsZWFwaXMuY29tL21hcHMvYXBpL2dlb2NvZGUveG1sP2FkZHJlc3M9VG9sZWRvCgpodHRwOi8vbWFwcy5nb29nbGVhcGlzLmNvbS9tYXBzL2FwaS9nZW9jb2RlL2pzb24/YWRkcmVzcz1Ub2xlZG8mcmVnaW9uPUVTCgojIHRoaXMgYWxzbyB3b3JrczoKaHR0cDovL21hcHMuZ29vZ2xlYXBpcy5jb20vbWFwcy9hcGkvZ2VvY29kZS9qc29uP2FkZHJlc3M9VG9sZWRvLFNwYWluCmBgYAoKV2hhdCBoYXZlIHdlIGxlYXJuZWQ/Cgo+IC0gV2UgY2FuIHVzZSBhbiBBUEkgdG8gYWNjZXNzIGEgc2VydmljZSAob3IgdG9vbHMpIHByb3ZpZGVkIGJ5IHNvbWVvbmUgZWxzZSB3aXRob3V0IGtub3dpbmcgdGhlIGRldGFpbHMgb2YgaG93IHRoaXMgc2VydmljZSBvciB0b29sIGlzIGltcGxlbWVudGVkLgogICAgICAKPiAtIEEgZ2VvY29kaW5nIEFQSSBwcm92aWRlcyBhIGRpcmVjdCB3YXkgdG8gYWNjZXNzIHRoZXNlIHNlcnZpY2VzIHZpYSBhbiBIVFRQIHJlcXVlc3QgKHNpbXBseSBzcGVha2luZzogYSBVUkwpLgoKPiAtIEEgZ2VvY29kaW5nIHNlcnZpY2UgQVBJIHJlcXVlc3QgbXVzdCBiZSBpbiBhIHBhcnRpY3VsYXIgZm9ybSBhcyBzcGVjaWZpZWQgYnkgdGhlIHNlcnZpY2UgcHJvdmllci4KCj4gLSBHZW9jb2Rpbmcgc2VydmljZXMgcmVzcG9uc2VzIGFyZSByZXR1cm5lZCBpbiBhIHN0cnVjdHVyZWQgZm9ybWF0LCB3aGljaCBpcyB0eXBpY2FsbHkgWE1MIG9yIEpTT04sIHNvbWV0aW1lcyBhbHNvIEtNWi4gCgpPdXIgZ29hbCBpcyBub3cgdG8gZG8gd2hhdCB3ZSBkaWQgaW4gYSB3ZWIgYnJvd3NlciBmcm9tIFIuIEZvciB0aGlzIHdlIGhhdmUgdG8gdGFrZSBpbnRvIGFjY291bnQgYWxzbzoKCi0gKipBdXRoZW50aWNhdGlvbioqOiBVc2luZyBnZW9jb2RlcnMgb2Z0ZW4gbWF5IHJlcXVpcmUgYSB2YWxpZCBBUEkga2V5LiBUaGUga2V5IGNhbiB1c3VhbGx5IGJlIHJlcXVlc3RlZCBmb3IgZnJlZSBieSByZWdpc3RlcmluZyBhdCB0aGUgd2Vic2l0ZSBvZiB0aGUgcHJvdmlkZXIuCgotICoqUmF0ZSBMaW1pdGluZyoqOiBHZW9jb2RlIHNlcnZpY2UgcHJvdmlkZXJzIHR5cGljYWxseSB1c2UgYSByYXRlIGxpbWl0aW5nIG1lY2hhbmlzbSB0byBlbnN1cmUgdGhhdCB0aGUgc2VydmljZSBzdGF5cyBhdmFpbGFibGUgdG8gYWxsIHVzZXJzLgoKVGhlcmUgYXJlIG1hbnkgZ2VvY29kaW5nIHByb3ZpZGVyc1teMl0uIFRoZXkgdmFyeSBpbiB0ZXJtcyBvZiBmb3JtYXQgc3BlY2lmaWNhdGlvbnMsIGFjY2VzcyBhbmQgdXNlKCEpIHJlc3JpY3Rpb25zLCBxdWFsaXR5IG9mIHJlc3VsdHMsIGFuZCBtb3JlLiBTbyBjaG9vc2luZyBhIGdlb2NvZGVyIGZvciBhIHJlc2VhcmNoIHByb2plY3QgZGVwZW5kcyBvbiB0aGUgc3BlY2lmaWNzIG9mIHlvdXIgc2l0dWF0aW9uLiAKClteMl06IEZvciBhIHF1aXRlIGNvbXByZWhlbnNpdmUgb3ZlcnZpZXcgbG9vayBbaGVyZV0oaHR0cHM6Ly9kb2NzLmdvb2dsZS5jb20vc3ByZWFkc2hlZXQvY2NjP2tleT0wQWlkRVd5YV9wNlhGZEd3MVJtWjZUakIxYWpaeFZrODFkMnBJU0RNelZVRSZ1c3A9c2hhcmluZykgYW5kIFtoZXJlXShodHRwOi8vZ2Vvc2VydmljZXMudGFtdS5lZHUvU2VydmljZXMvR2VvY29kZS9PdGhlckdlb2NvZGVycy8pLiAKCiMjIEdlbmVyaWMgc3RydWN0dXJlIG9mIGEgZ2VvY29kaW5nIHNjcmlwdAoKSWYgeW91IHdlcmUgdG8gd3JpdGUgeW91ciBvd24gc2NyaXB0LCBoZXJlIGFyZSB0aGUgYmFzaWMgc3RlcHMgdG8gdGFrZS4KCiMuIExvYWQgYWRkcmVzcyBkYXRhLiBDbGVhbiB0aGVtIHVwIGlmIG5lY2Vzc2FyeS4KCiMuIFNlbmQgZWFjaCBhZGRyZXNzIHRvIHRoZSBnZW9jb2Rpbmcgc2VydmljZSAodHlwaWNhbGx5OiBjcmVhdGUgYSBVUkwgcmVxdWVzdCBmb3IgZWFjaCBhZGRyZXNzKQoKIy4gUHJvY2VzcyByZXN1bHRzLiBFeHRyYWN0IGdlb2dycGFoaWMgY29vcmRpbmF0ZXMgYW5kIGFueSBvdGhlciB2YWx1ZXMgeW91IGFyZSBpbnRlcmVzdGVkIGluIGFuZCB0dXJuIGludG8gaW50byBhIGNvbnZlbmllbnQgZm9ybWF0LCB1c3VhbGx5IGEgdGFibGUuCgojLiBTYXZlIHRoZSBvdXRwdXQuCgoKIyAzLiBHZW9jb2Rpbmcgd2l0aCB0aGUgR29vZ2xlIG1hcHMgQVBJCgojIyBJbnRlcmFjdGl2ZWx5IAoKV2Ugd2lsbCBzdGFydCBieSB1c2luZyB0aGUgYGdlb2NvZGUoKWAgY29tbWFuZCBmcm9tIHRoZSBgZ2dtYXBgIGxpYnJhcnkuIAoKKioqCiMjIyBFeGVyY2lzZSAyCgoxLiBJbnN0YWxsIChpZiB5b3UgaGF2ZW4ndCkgYW5kIGxvYWQgdGhlIGBnZ21hcGAgbGlicmFyeS4KCjIuIFVzaW5nIHRoZSBgZ2VvY29kZWAgY29tbWFuZCwgaG93IHdvdWxkIHlvdSBzZWFyY2ggZm9yIGxvY2F0aW9uIG9mIHRoZSBjaXR5IG9mIFRvbGVkbwoKYGBge3IgZXZhbD1GQUxTRX0KbGlicmFyeShnZ21hcCkKZ2VvY29kZSgiVG9sZWRvIikKYGBgCgozLiBIb3cgZG9lcyB5b3VyIHJlc3VsdCBjb21wYXJlIHRvIHdoYXQgeW91IGdvdCBmcm9tIHdoZW4geW91IGRpZCB0aGUgcXVlcnkgdGhyb3VnaCB0aGUgd2ViIGJyb3dzZXI/IENoZWNrIG91dCB0aGUgYG91dHB1dD1gIG9wdGlvbiBvZiB0aGUgY29tbWFuZCwgd2hhdCBkb2VzIHRoYXQgZG8/CgpgYGB7ciBldmFsPUZBTFNFfQpnZW9jb2RlKCJUb2xlZG8iLCBvdXB1dCA9ICJhbGwiKQpgYGAKCjQuIE5vdyBnZW9jb2RlIHRoaXMgYWRkcmVzczogYCIzODAgTmV3IFlvcmsgU3QsIFJlZGxhbmRzLCBDQSJgLiBDb21wYXJlIHRoZSByZXN1bHRzIHlvdSBnZXQgd2l0aCB0aGUgR29vZ2xlIGdlb2NvZGVyIHZlcnN1cyB0aGUgRGF0YSBTY2llbmNlIFRvb2xraXQgZ2VvY29kZXIuCgpgYGB7ciBldmFsPUZBTFNFfQpnZW9jb2RlKCIzODAgTmV3IFlvcmsgU3QsIFJlZGxhbmRzLCBDQSIpCmdlb2NvZGUoIjM4MCBOZXcgWW9yayBTdCwgUmVkbGFuZHMsIENBIiwgc291cmNlID0gImRzayIpCmBgYAoKIyMgQmF0Y2ggZ2VvY29kaW5nCgpOb3cgbGV0J3Mgd3JpdGUgYW4gUiBzY3JpcHQgdG8gcHJvY2VzcyBhbiBlbnRpcmUgbGlzdCBvZiBhZHJlc3NlcyBmb3IgZ2VvY29kaW5nIHRoaXMgd2F5LiBXZSB3aWxsIHVzZSB0aGUgZ2VuZXJpYyBvdXRsaW5lIGFib3ZlIGFuZCBpbXBsZW1lbnQgaXQgaW4gUi4KCioqKgojIyMgRXhlcmNpc2UgMwoKMS4gTG9hZCBhZGRyZXNzIGRhdGEuICAKCiAgICBMZXQncyB1c2UgdGhlIGFkZHJlc3NlcyBvZiBkZWNsYXJlZCBkYW5nZXJvdXMgZG9ncyBpbiBBdXN0aW4sIFRYLCBjb3VydGVzeSBvZiB0aGUgW2NpdHkgb2YgQXVzdGluJ3Mgb3BlbiBkYXRhIHBvcnRhbF0oaHR0cHM6Ly9kYXRhLmF1c3RpbnRleGFzLmdvdikuIAoKICAgIFRoZSBkaXJlY3QgbGluayB0byB0aGUgQ1NWIGZpbGUgb2YgdGhlIGRhdGEgaXMgdGhpcyBvbmU6CgogICAgaHR0cHM6Ly9kYXRhLmF1c3RpbnRleGFzLmdvdi9hcGkvdmlld3MveWt3NC1qM2FqL3Jvd3MuY3N2P2FjY2Vzc1R5cGU9RE9XTkxPQUQKCiAgICBSZWFkIHRoZSB0YWJsZSBpbnRvIFIgYW5kIGNhbGwgaXQgYGRvZ3NgLgoKYGBge3IgZXZhbD1GQUxTRX0KZG9ncyA8LSByZWFkLmNzdigiaHR0cHM6Ly9kYXRhLmF1c3RpbnRleGFzLmdvdi9hcGkvdmlld3MveWt3NC1qM2FqL3Jvd3MuY3N2P2FjY2Vzc1R5cGU9RE9XTkxPQUQiKQpgYGAKCjIuIFNlbmQgZWFjaCBhZGRyZXNzIHRvIHRoZSBnZW9jb2Rpbmcgc2VydmljZS4gCiAgICAKICAgIFRoZSBuaWNlIHRoaW5nIGlzIHRoYXQgYGdlb2NvZGUoKWAgY2FuIHRha2UgYSB2ZWN0b3Igb2YgYWRyZXNzZXMuIFNvIGFsbCB3ZSBoYXZlIHRvIGRvIGlzIGZpbmQgb3V0IHdoZXJlIHRoZSBhZGRyZXNzZXMgYXJlIGluIG91ciBgZG9nc2AgZGF0YSBmcmFtZSBhbmQgdGhlbiBzdWJtaXQgdGhlbSB0byB0aGUgYGdlb2NvZGUoKWAgZnVuY3Rpb24uIEFuZCBvZiBjb3Vyc2UsIGNhdGNoIHRoZSByZXN1bHRzIGluIGEgdmFyaWFibGUuCgogICAgKEZvciB0aGUgcHVycG9zZSBvZiB0aGlzIGV4ZXJjaXNlIHdlIHdpbGwgb3Zlcmxvb2sgdGhlIGZhY3QgdGhhdCB0aGUgY29vcmRpbmF0ZXMgYWxyZWFkeSBjb21lIHdpdGggdGhlIHRhYmxlLikKCmBgYHtyIGV2YWw9RkFMU0V9CiMgY29uY2F0ZW5hdGUgdGhlIGFkZHJlc3MKZG9nc19hZHIgPC0gcGFzdGUoZG9ncyRBZGRyZXNzLCAiQXVzdGluLFRYIiwgZG9ncyRaaXAuQ29kZSkKCiMgZ2VvY29kZSAtIGNoZWNrIGZvciB3YXJuaW5ncwpkb2dzX2Nvb3JkcyA8LSBnZW9jb2RlKGRvZ3NfYWRyKQpgYGAKCjMuIFByb2Nlc3MgcmVzdWx0cy4gCgogICAgTW9zdCBvZiB0aGlzIGlzIGFsbCB0YWtlbiBjYXJlIG9mIGFscmVhZHkgaW4gdGhlIGBnZW9jb2RlKClgIGZ1bmN0aW9uLCB3aGljaCByZXR1cm5zIGEgdmVjdG9yIG9mIGdlb2dyYXBoaWMgY29vcmRpbmF0ZXMuIEFsbCB3ZSBuZWVkIHRvIGRvIGlzIGJpbmQgaXQgdGhlIGJhY2sgdG8gb3VyIG9yaWdpbmFsIGRhdGFmcmFtZS4gCgpgYGB7ciBldmFsPUZBTFNFfQpkb2dzIDwtIGRhdGEuZnJhbWUoY2JpbmQoZG9ncywgZG9nc19jb29yZHMpKQpgYGAKCjQuIFNhdmUgdGhlIG91dHB1dCBhcyBzaGFwZWZpbGUuIAogICAgCiAgICBTYXZpbmcgdGhlIGNvb3JkaW5hdGVzIG91dCBhcyBjc3YgaXMgcHJldHR5IGVhc3kgd2l0aCBgd3JpdGUudGFibGUoKWAuIFRvIHNhdmUgdGhlIGRhdGEgZnJhbWUgYXMgYSBzaGFwZWZpbGUsIHdlIGNvbnZlcnQgdGhlIGRhdGFmcmFtZSB0byBhIGBTcGF0aWFsKkRhdGFmcmFtZWAgZmlyc3QsIGFzc2lnbiBhIHByb2plY3Rpb24sIGFuZCB0aGVuIHNhdmUgd2l0aCBgd3JpdGVPR1IoKWAuIFJlbWVtYmVyIHRoYXQgeW91IHdpbGwgbmVlZCBib3RoIHRoZSBgc3BgIGFuZCB0aGUgYHJnZGFsYCBsaWJyYXJpZXMgZm9yIHRoaXMuIAogICAgCiAgICBPcHRpb25hbDogc2F2ZSB0aGUgc2hhcGVmaWxlIHVzaW5nIGBzZmAuCiAgICAKYGBge3IgZXZhbD1GQUxTRX0KbGlicmFyeShzcCkKbGlicmFyeShyZ2RhbCkKCmRvZ3MgPC0gZG9nc1tjb21wbGV0ZS5jYXNlcyhkb2dzKSxdICMgcmVtb3ZlIE5BIGdlb2NvZGluZyByZXN1bHRzIApjb29yZGluYXRlcyhkb2dzKSA8LSBjKCJsb24iLCAibGF0IikgCnByb2o0c3RyaW5nKGRvZ3MpIDwtIENSUygiK3Byb2o9bG9uZ2xhdCArZWxscHM9V0dTODQgK2RhdHVtPVdHUzg0ICtub19kZWZzIikKd3JpdGVPR1IoZG9ncywgIi4iLCAiZGFuZ2Vyb3VzX2RvZ3MiLCBkcml2ZXI9IkVTUkkgU2hhcGVmaWxlIikKCgojIElmIHdlIHdlcmUgdG8gdXNlIHRoZSBgc2ZgIHBhY2thZ2Ugd2Ugd291bGQgZG86CiAgICAKbGlicmFyeShzZikKZG9nc19zZiA8LSBzdF9hc19zZihkb2dzLCBjKCJsb24iLCAibGF0IiksIGNycyA9ICIrcHJvaj1sb25nbGF0ICtlbGxwcz1XR1M4NCArZGF0dW09V0dTODQgK25vX2RlZnMiKQpzdF93cml0ZShkb2dzX3NmLCAiZGFuZ2Vyb3VzX2RvZ3Muc2hwIikKCmBgYAogICAgCgo1LiBOb3cgbGV0J3MgdXNlIGBsZWFmbGV0KClgIHRvIHBsb3QgdGhlIGRvdHMgb3ZlciBhIGJhc2VtYXAuCgpgYGB7ciBldmFsPUZBTFNFfQpsZWFmbGV0KGRvZ3MpICU+JSAgCiAgYWRkVGlsZXMoKSAlPiUgCiAgYWRkQ2lyY2xlTWFya2Vycyhkb2dzJGxvbiwgZG9ncyRsYXQsIHBvcHVwPWRvZ3MkRGVzY3JpcHRpb24ub2YuRG9nKSAKYGBgCgpTb21ldGhpbmcgbGlrZSB0aGlzOgoKYGBge3IgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY29tbWVudD1OQX0KZG9ncyA8LSByZWFkLmNzdigiaHR0cHM6Ly9kYXRhLmF1c3RpbnRleGFzLmdvdi9hcGkvdmlld3MveWt3NC1qM2FqL3Jvd3MuY3N2P2FjY2Vzc1R5cGU9RE9XTkxPQUQiKQojZG9nc19hZHIgPC0gcGFzdGUoZG9ncyRBZGRyZXNzLCAiQXVzdGluLFRYIiwgZG9ncyRaaXAuQ29kZSkKZG9nc19hZHIgPC0gcGFzdGUoZG9ncyRBZGRyZXNzLCAiQXVzdGluLFRYIiwgZG9ncyRaaXAuQ29kZSkKZG9nc19jb29yZHMgPC0gZ2VvY29kZShkb2dzX2FkcikKZG9ncyA8LSBkYXRhLmZyYW1lKGNiaW5kKGRvZ3MsIGRvZ3NfY29vcmRzKSkKZG9ncyA8LSBkb2dzW2NvbXBsZXRlLmNhc2VzKGRvZ3MpLF0gIyByZW1vdmUgTkEgZ2VvY29kaW5nIHJlc3VsdHMgCgpsZWFmbGV0KGRvZ3MpICU+JSAgCiAgYWRkVGlsZXMoKSAlPiUgCiAgYWRkQ2lyY2xlTWFya2Vycyhkb2dzJGxvbiwgZG9ncyRsYXQsIHBvcHVwPWRvZ3MkRGVzY3JpcHRpb24ub2YuRG9nKSAKYGBgCgoKIyA0LiBHZW9jb2Rpbmcgd2l0aCB0aGUgQXJjR0lTIEFQSSAoU3RhbmZvcmQgYWZmaWxpYXRlcyBvbmx5KQoKVGhhbmtzIHRvIG91ciBmYWJ1bG91cyBnZW9zcGF0aWFsIG1hbmFnZXIgW1N0YWNlIE1hcGxlc10oaHR0cHM6Ly9saWJyYXJ5LnN0YW5mb3JkLmVkdS9wZW9wbGUvbWFwbGVzKSB3aG8gaXMgdGlyZWxlc3NseSB3b3JraW5nIHRvIG1ha2Ugb3VyIEdJUyBsaXZlcyBlYXNpZXIgd2UgaGF2ZSBvdXIgb3duIGdlb2xvY2F0b3IgYXQgU3RhbmZvcmQgYXQKCj4+IGh0dHA6Ly9sb2NhdG9yLnN0YW5mb3JkLmVkdS9hcmNnaXMvcmVzdC9zZXJ2aWNlcy9nZW9jb2RlCgpUaGUgc2VydmljZXMgYXZhaWxhYmxlIGhlcmUgY292ZXIgdGhlIFVTIG9ubHkuIFRoZSBnb29kIG5ld3MgaGVyZSBhcmUgdGhhdCB0aGVyZSBhcmUgbm8gbGltaXRzIGFzIG9mIGhvdyBtYW55IGFkZHJlc3NlcyB5b3UgY2FuIHRocm93IGF0IHRoaXMgc2VydmVyLiBIb3dldmVyLCAqKnlvdSBzaG91bGQgbGV0IFN0YWNlIGtub3cgaWYgeW91IGFyZSBpbnRlbmRpbmcgdG8gcnVuIGEgbWFqb3Igam9iISoqCgpZb3UgY2FuIGZpbmQgbW9yZSBkZXRhaWxzIGFzIG9mIGhvdyB0byB1c2UgdGhpcyBzZXJ2aWNlIGZyb20gUiBbaGVyZV0oaHR0cHM6Ly9naXRodWIuY29tL2NlbmdlbC9BcmNHSVNfZ2VvY29kaW5nKS4KCgoqKioKIyMjIEV4ZXJjaXNlIDQKCiMuIFVzaW5nIHRoZSBwcm92aWRlZCBmdW5jdGlvbiBgZ2VvY29kZVNMYCB3ZSBjYW4gZ2VvY29kZSB0aGUgYWRyZXNzIHRhYmxlIGZyb20gYWJvdmUgdXNpbmcgdGhlIEFyY0dJUyBnZW9jb2Rpbmcgc2VydmljZToKCmBgYHtyIGV2YWw9RkFMU0V9CmxpYnJhcnkocmVhZHIpCmxpYnJhcnkocmdkYWwpCgojIHNldCB5b3VyIHRva2VuCm15VG9rZW4gPC0gIlhYWFhYWFhYIgoKIyBzb3VyY2UgdGhlIGZ1bmN0aW9uCnNvdXJjZSgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2NlbmdlbC9BcmNHSVNfZ2VvY29kaW5nL21hc3Rlci9TVUxfZ2NGdW5jdGlvbnMuUiIpCgojIGdlb2NvZGUKZG9nc19jb29yZHMyIDwtIGRvLmNhbGwoInJiaW5kIiwgbGFwcGx5KGRvZ3NfYWRyLCBmdW5jdGlvbih4KSBnZW9jb2RlU0woeCwgbXlUb2tlbikpKQoKIyBhZGQgYXR0aWJ1dGVzIGJhY2sKZG9nczIgPC0gY2JpbmQoZG9ncywgZG9nc19jb29yZHMyKQoKIyB3cml0ZSBvdXQgYSBDU1YKd3JpdGUuY3N2KGRvZ3MyLCAiZGFuZ2Vyb3VzX2RvZ3MyLmNzdiIsIHJvdy5uYW1lcz1GKQoKYGBgCgojIDUuIE1vcmUgZ2VvY29kaW5nIHNlcnZpY2VzIGFuZCBsaWJyYXJpZXMKCiMjIE90aGVyIFIgbGlicmFyaWVzCgpgZ2dtYXBgIGlzIGNlcnRhaW5seSBub3QgdGhlIG9ubHkgUiBsaWJyYXJ5IHRoYXQgaW5jbHVkZXMgYSBnZW9jb2RpbmcgZnVuY3Rpb24uIFRoZSBsaWJyYXJpZXMgYmVsb3cgYWxzbyBkbyBnZW9jb2Rpbmcgd2l0aCBkaWZmZXJlbnQgc2VydmljZXM6CgotIFtgZ29vZ2xld2F5YF0oaHR0cHM6Ly9DUkFOLlItcHJvamVjdC5vcmcvcGFja2FnZT1nb29nbGV3YXkpIGFsc28gY29ubmVjdHMgdG8gR29vZ2xlIAoKLSBbYHRtYXBgXShodHRwczovL0NSQU4uUi1wcm9qZWN0Lm9yZy9wYWNrYWdlPXRtYXApIGNvbm5lY3RzIHRvIFtPU00gTm9taW5hdGltXShodHRwOi8vbm9taW5hdGltLm9wZW5zdHJlZXRtYXAub3JnKQoKLSBbYG5vbWluYXRpbWBdKGh0dHBzOi8vZ2l0aHViLmNvbS9ocmJybXN0ci9ub21pbmF0aW0pIChub3Qgb24gQ1JBTikgY29ubmVjdHMgdG8gW09TTSBOb21pbmF0aW1dKGh0dHA6Ly9ub21pbmF0aW0ub3BlbnN0cmVldG1hcC5vcmcpCgotIFtgb3BlbmNhZ2VgXShodHRwczovL0NSQU4uUi1wcm9qZWN0Lm9yZy9wYWNrYWdlPW9wZW5jYWdlKSBjb25uZWN0cyB0byBbT3BlbkNhZ2UgZ2VvY29kZXJdKGh0dHBzOi8vZ2VvY29kZXIub3BlbmNhZ2VkYXRhLmNvbS8pIAoKLSBbdGhyZWV3b3Jkc10oaHR0cHM6Ly9DUkFOLlItcHJvamVjdC5vcmcvcGFja2FnZT10aHJlZXdvcmRzKSBjb25uZWN0cyB0byB0aGUgW1doYXQzV29yZHNdKGh0dHA6Ly93aGF0M3dvcmRzLmNvbS8pIAoKRm9yIG1vcmUgc2VlIHRoZSBHZW9sb2NhdGlvbi9HZW9jb2Rpbmcgc2VjaW9uIG9uIHRoZSBDUkFOIFRhc2sgVmlldyBmb3IgW1dlYiBUZWNobm9sb2dpZXMgYW5kIFNlcnZpY2VzXShodHRwczovL0NSQU4uUi1wcm9qZWN0Lm9yZy92aWV3PVdlYlRlY2hub2xvZ2llcykuCgoKIyMgQSB3b3JkIGFib3V0IE9wZW4gRGF0YSBTY2llbmNlIFRvb2xraXQgKERTSykKClRoZSBvcGVuIERhdGEgU2NpZW5jZSBUb29sa2l0IChEU0spIGlzIGF2YWlsYWJsZSBhcyBhIHNlbGYtY29udGFpbmVkIFZhZ3JhbnQgVk0gb3IgRUMyIEFNSSB0aGF0IHlvdSBjYW4gZGVwbG95IHlvdXJzZWxmLiBJdCBpbmNsdWRlcyBhIEdvb2dsZS1zdHlsZSBnZW9jb2RlciB3aGljaCBlbXVsYXRlcyBHb29nbGUncyBnZW9jb2RpbmcgQVBJLiBUaGlzIEFQSSB1c2VzIGRhdGEgZnJvbSB0aGUgVVMgQ2Vuc3VzIGFuZCBPcGVuU3RyZWV0TWFwLCBhbG9uZyB3aXRoIGNvZGUgZnJvbSBHZW9JUSBhbmQgU2NodXlsZXIgRXJsZSdzIE1vZHVsYXIgU3RyZWV0IEFkZHJlc3MgR2VvY29kZXIuCgpJbnNydWN0aW9ucyBmb3IgaG93IHRvIHJ1biBEU0sgb24gQW1hem9uIG9yIFZhZ3JhbnQgYXJlIGhlcmU6IGh0dHA6Ly93d3cuZGF0YXNjaWVuY2V0b29sa2l0Lm9yZy9kZXZlbG9wZXJkb2NzI2FtYXpvbgoKTm90ZSB0aGF0IGBnZW9jb2RlYCBmcm9tIGBnZ21hcGAgYWxzbyBoYXMgdGhlIG9wdGlvbiB0byBhY2Nlc3MgRFNLLCBidXQgaXQgd2lsbCB1c2UgdGhlaXIgcHVibGljIHNlcnZlciwgd2hpY2ggaXMgb2Z0ZW4gc2xvdyBvciB1bmF2YWlsYWJsZS4KCiMjIEdlb2NvZGluZyBJUCBhZHJlc3NlcwoKSWYgeW91IGFyZSBpbnRlcmVzdGVkIHRvIGRvIHRoaXMgaW4gUiBzZWUgaGVyZTogaHR0cHM6Ly9naXRodWIuY29tL2NlbmdlbC9yX0lQZ2VvY29kZSAKCgojIDYuIFB1dHRpbmcgaXQgYWxsIHRvZ2V0aGVyOiBXZWJzY3JhcGluZyBmb3IgbWFwcwoKTm93IGZvciBmdW4uIFdlIHdpbGwgcmV0cmlldmUgYSB0YWJsZSBmcm9tIHRoZSBbV2lraXBlZGlhIHBhZ2Ugb24gQ3JpbWUgc3RhdGlzdGljcyBVLlMuIGNpdGllcyB3aXRoIGEgcG9wdWxhdGlvbiBvZiAyNTAsMDAwIG9yIGdyZWF0ZXJdKGh0dHA6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvTGlzdF9vZl9Vbml0ZWRfU3RhdGVzX2NpdGllc19ieV9jcmltZV9yYXRlI0NyaW1lX3JhdGVzX3Blcl8xMDAuMkMwMDBfcGVvcGxlXy4yODIwMTIuMjkpLiAKV2Ugd2lsbCB1c2UgdGhlIGNpdHkgbmFtZXMsIGdlb2NvZGUgdGhlaXIgbG9jYXRpb25zIGFuZCBwbG90IHRoZSBjaXRpZXMgYnkgcG9wdWxhdGlvbiBhbmQgY3JpbWUgcmF0ZS4KCllvdSBzaG91bGQgYmUgYWJsZSB0byBydW4gdGhlIGNvZGUgYmVsb3cgYXMgaXMuIEl0IG1pZ2h0IHRha2UgYSBsaXR0bGUgd2hpbGUuCgoqKioKIyMjIEV4ZXJjaXNlIDUKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY29tbWVudD1OQX0KbGlicmFyeShYTUwpCmxpYnJhcnkoZ2dtYXApCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoUkN1cmwpCgojIHJlYWQgaW4gdGhlIGRhdGEKdXJpIDwtICJodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9MaXN0X29mX1VuaXRlZF9TdGF0ZXNfY2l0aWVzX2J5X2NyaW1lX3JhdGUiCiMgd2Ugd2FudCB0aGUgc2Vjb25kIHRhYmxlOiB3aGljaD0yCmNpdGllc0NSIDwtIHJlYWRIVE1MVGFibGUoZ2V0VVJMKHVyaSksIHdoaWNoID0gMiwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQoKIyBjbGVhbiB1cCAod2l0aCBtdXRhdGVfZWFjaCBmdW5jdGlvbiBmcm9tIGRwbHlyKTogCiMgcmVtb3ZlIHRoZSBjb21tYSBpbiAxLDAwMCBhbmQgYWJvdmUgYW5kIGNvbnZlcnQgbnVtYmVycyBmcm9tIHN0cmluZ3MgdG8gbnVtZXJpYwpjaXRpZXNDUmNsZWFuIDwtIG11dGF0ZV9lYWNoKGNpdGllc0NSLCBmdW5zKGFzLm51bWVyaWMoZ3N1YigiLCIsICIiLCAuKSkpLCAgLShTdGF0ZTpDaXR5KSkKIAojIGdlb2NvZGUgbG9hdGlvbnMKbGF0bG9uIDwtIGdlb2NvZGUocGFzdGUoY2l0aWVzQ1JjbGVhbiRDaXR5LCBjaXRpZXNDUmNsZWFuJFN0YXRlLCBzZXA9IiwgIikpCgojIGNvbWJpbmUgaW50byBhIG5ldyBkYXRhZnJhbWUKY2l0aWVzQ1JsbCA8LSBkYXRhLmZyYW1lKGNpdGllc0NSY2xlYW4sIGxhdGxvbikKCiNnZXQgYmFzbWFwCm1hcF91cyA8LSBnZXRfbWFwIChsb2NhdGlvbj0nVW5pdGVkIFN0YXRlcycsIHpvb209NCwgY29sb3I9ImJ3IikKCiMgcGxvdApnZ21hcChtYXBfdXMsIGxlZ2VuZD0nYm90dG9tcmlnaHQnLCBleHRlbnQ9J2RldmljZScpICsKICBnZW9tX3BvaW50KGRhdGE9Y2l0aWVzQ1JsbCwKICAgICAgICAgICAgYWVzKHg9bG9uLCB5PWxhdCwgCiAgICAgICAgICAgIGNvbG9yPVZpb2xlbnQuQ3JpbWUsIAogICAgICAgICAgICBzaXplPVBvcHVsYXRpb24pLAogICAgICAgICAgICBuYS5ybT1UKSArCiAgc2NhbGVfY29sb3VyX2dyYWRpZW50KGxvdz0id2hpdGUiLCBoaWdoPSJyZWQiKSArCiAgc2NhbGVfc2l6ZV9jb250aW51b3VzKHJhbmdlID0gYyg0LDEyKSkKYGBgCg==