Presenter: Kyle Walker (@kyle_e_walker)

This tutorial was presented on October 9, 2020 for the Master of Urban Spatial Analytics program at the University of Pennsylvania’s Weitzman School of Design.

1 Getting started

To run the examples from the workshop yourself, open a terminal then clone the repository to your computer:

git clone https://github.com/walkerke/MUSAmasterclass.git 

Open the project in RStudio and navigate to the tutorial folder, then open the index.Rmd document. The examples in that document will run correctly if code chunks are set to be evaluated in that directory.

Alternatively, if you are unfamiliar with git, click the “Code” drop-down button in the upper right corner of this tutorial, and choose “Download Rmd.” This will download this .Rmd file to your computer. Put the .Rmd file in a directory of your choice. Next, download the data for this workshop from https://walker-data.com/MUSAmasterclass/tutorial/data.zip. Unzip the folder in the same directory as your downloaded .Rmd file.

1.1 Installing packages and dependencies

To get started with mapboxapi, you’ll need to first install some packages. mapboxapi was just released to CRAN this week, so we can install with install.packages():

install.packages("mapboxapi", dependencies = TRUE)

If you’ve been working with R Spatial packages before, installation should go smoothly. If you are new to R/R Spatial, you may need to do some configuration prior to successful installation of the package. mapboxapi depends heavily on the sf package for spatial data processing in R. On Ubuntu, use the following commands in a terminal to install required dependencies:

sudo add-apt-repository ppa:ubuntugis/ubuntugis-unstable
sudo apt-get update
sudo apt-get install -y libudunits2-dev libgdal-dev libgeos-dev libproj-dev 

mapboxapi also uses the protolite package for interacting with Mapbox vector tiles and the magick package for image processing and display. On Ubuntu, install dependencies with:

sudo apt-get install -y libprotobuf-dev protobuf-compiler libmagick++-dev

Instructions for other Linux distributions can be found on the package websites linked above.

To run all of the examples in this workshop, you’ll also need to install the following packages that don’t get picked up as mapboxapi dependencies:

install.packages(c("shiny", "fasterize", "tidycensus", "tidyverse"))

1.2 Setting up your Mapbox account

Before we get started using Mapbox services in R, you’ll need a valid Mapbox account with an access token. Fortunately, Mapbox has generously provided a coupon code for you to use as workshop participants. To set up your account, visit https://account.mapbox.com/auth/signup/ to establish an account - all you need to provide is an email address to sign up! Fill out the form and verify your account through the email Mapbox sends you; you’ll be taken directly to your Mapbox account dashboard page.

Note the “default public token” that appears on your screen - you’ll come back to this page in a moment. First, look to the right side of your screen and click “View billing.” This is where Mapbox will handle your billing information. Nothing you’ll do today will be intensive enough to incur charges - but your next three months of work will be covered by the coupons Mapbox has provided to this workshop. Scroll down and enter the coupon code you’ve received in the appropriate box, then click Add. Once you’ve entered your coupon code, return to your Mapbox dashboard. Copy the access token that appears on your screen to your clipboard, then return to R.

1.2.1 Loading mapboxapi and setting your access token

All features in mapboxapi require a valid Mapbox access token to work. Now that you have yours in hand, you can set yours up! Load the mapboxapi package and install your token as follows:

my_token <- "YOUR TOKEN GOES HERE"

library(mapboxapi)
mb_access_token(my_token, install = TRUE)

The optional argument install = TRUE saves the token to your .Renviron, allowing you to use mapboxapi functions in the future without having to worry about setting your token. To use this feature, restart your R session.

2 Using Mapbox maps in R

The most well-known feature of Mapbox services is its ability to create stunning web maps which are used on applications all around the world. While mapboxapi is not an interface to Mapbox GL JS, Mapbox’s JavaScript library for building web maps, it does include some tools to help you use Mapbox maps in your R projects. This is important as the Mapbox Terms of Service require that Mapbox API outputs be visualized on Mapbox maps.

2.1 Visualizing Mapbox “styles”

Mapbox maps are accessed through styles, which are custom design configurations applied to OpenStreetMap or even user-generated vector map tilesets. You’ll learn how to create and use your own map style with Mapbox later in this workshop. However, Mapbox provides a number of their styles to all users with a Mapbox access token. The most recent versions of these styles (as of the workshop date) are as follows:

One of the most popular R packages for interactive data visualization in R is the Leaflet package maintained by RStudio, which wraps the Leaflet JavaScript library for web mapping. Years ago, I wrote a tutorial on how to use Mapbox maps in R Leaflet projects. Now, mapboxapi provides a convenience function, addMapboxTiles(), to help you do this in a more straightforward way. This function queries the Mapbox Static Tiles API and converts a Mapbox style into static tiles for web mapping.

Let’s load the leaflet and mapboxapi libraries and set up an interactive map:

library(leaflet)
library(mapboxapi)

mapbox_map <- leaflet() %>%
  addMapboxTiles(style_id = "streets-v11",
                 username = "mapbox") 

mapbox_map

We get a browseable Leaflet map using Mapbox tiles as a basemap.

2.2 Focusing your map with the Mapbox Search API

Once we’ve set up our Leaflet map with a Mapbox basemap, we’ll likely want to focus it on a specific location. mapboxapi includes functionality for R users to interact with the Mapbox Search API. Implemented functions include mb_geocode() for forward geocoding, which refers to the conversion of a description of a place (like an address) into longitude/latitude coordinates; and mb_reverse_geocode(), which converts coordinates into a place description.

Both functions default to using the mapbox.places API endpoint, which is to be used for temporary geocoding. This means that the endpoint cannot be used to store geocoded information nor can it be used for batch geocoding (e.g., a spreadsheet of addresses). These tasks are permissible with the mapbox.places-permanent endpoint, which is not included with free accounts. In turn, R users looking for free batch geocoding solutions should use other packages like the tidygeocoder package. Mapbox geocoding with the mapbox.places endpoint can be used to focus web maps and guide navigation services, which will be illustrated in the following sections.

Let’s use mb_geocode() to identify the coordinates representing the University of Pennsylvania (specifically here, the university bookstore).

penn <- mb_geocode("3601 Walnut St, Philadelphia, PA 19104")

penn
## [1] -75.19600  39.95368

By default, mb_geocode() returns a length-2 vector representing the longitude and latitude coordinates of the geocoded location. The function can also return an sf POINT object or an R list representing the full API response, if requested. Using the returned coordinates, we can focus our Leaflet Mapbox map with the setView() function:

mapbox_map %>%
  setView(lng = penn[1],
          lat = penn[2],
          zoom = 14)

2.3 Exercise

Try it out! Make a Leaflet map in R using a Mapbox basemap of your choice, focused on a location of your choice. For locations in non-English-speaking countries: mb_geocode() has a language argument that can be used to improve the accuracy of queries in languages other than English. Supported languages (and how to specify them) are found in the Mapbox documentation here.

3 Using Mapbox Navigation APIs in R

The Mapbox Navigation Service API includes a variety of methods for performing routing and network analysis. mapboxapi allows R users to interact with the Navigation Service API by using the following functions:

  • mb_directions(): an interface to the Mapbox Directions API for calculating driving (with or without traffic), walking, or cycling routes. Users can request routes between an origin/destination pair or along a series of points, and can return route linestrings as simple features objects along with travel instructions.
  • mb_optimized_route(): duration-optimized routing with the Mapbox Optimization API, helping you determine the fastest way to visit multiple locations on your route.
  • mb_isochrone(): uses the Mapbox Isochrone API to draw isochrones around specified locations, which represent the reachable area from those locations within a given travel time by a given travel mode.
  • mb_matrix(): for a specified set of origin and destination locations, uses the Mapbox Matrix API to calculate pairwise travel times between those locations. This function is recommended for smaller travel-time matrices; large matrix requests (e.g. with results exceeding the tens of thousands) should be completed with a user-installed routing engine like Valhalla or OSRM.

The fifth Mapbox navigation service, map matching, is not yet supported by mapboxapi but will be in a future release.

3.1 Drawing isochrones with Mapbox and R

Creating and visualizing isochrones is straightforward with the mb_isochrone() function in mapboxapi. Supported travel profiles include driving (with no traffic), cycling, and walking. mb_isochrone() by default returns a simple features polygon object that can be used for visualization and even spatial analysis.

Let’s try drawing isochrones around the Penn campus. mb_isochrone() accepts an an input a coordinate pair, a location description as a character string, or an sf object. We can use our penn object here to initialize the isochrones around campus.

penn_isochrones <- mb_isochrone(penn,
                                profile = "driving",
                                time = c(4, 8, 12))

penn_isochrones
## Simple feature collection with 3 features and 1 field
## geometry type:  POLYGON
## dimension:      XY
## bbox:           xmin: -75.24737 ymin: 39.89667 xmax: -75.12698 ymax: 40.01073
## geographic CRS: WGS 84
## # A tibble: 3 x 2
##    time                                                                 geometry
##   <int>                                                            <POLYGON [°]>
## 1    12 ((-75.185 40.01073, -75.18503 40.00971, -75.189 40.00972, -75.18909 40.…
## 2     8 ((-75.18772 39.96796, -75.18685 39.96683, -75.18677 39.9659, -75.18583 …
## 3     4 ((-75.19688 39.9598, -75.19583 39.95852, -75.19434 39.95902, -75.19308 …

An sf object is returned with a time column representing the travel-time around the location. time is organized in descending order to ensure that overlapping isochrones are plotted correctly, with the shortest time visualized last (on top).

Using Leaflet’s addPolygons() function, we can add the isochrones to our map.

colors <- viridisLite::viridis(3)

mapbox_map %>%
  addPolygons(data = penn_isochrones,
              color = rev(colors),
              fillColor = rev(colors),
              fillOpacity = 0.5, 
              opacity = 1, 
              weight = 0.2) %>%
  addLegend(labels = c(4, 8, 12),
            colors = colors,
            title = "Drive-time<br/>around Penn")

The filled areas represent the estimated reachable area around Penn without traffic. The Mapbox API does not support traffic-adjusted isochrones, so isochrone results should be interpreted appropriately depending on what you plan to represent.

3.2 Routing with mapboxapi

mapboxapi can also be used to quickly represent and visualize routes between two locations, or alternatively along multiple locations. The Mapbox Directions API endpoint has a lot of options, which I’ve done my best to implement for R users. At its simplest, however, mb_directions() just requires an origin and a destination:

route <- mb_directions(origin = penn,
                       destination = "Philadelphia Museum of Art, Philadelphia PA",
                       profile = "cycling")

mapbox_map %>%
  addPolylines(data = route, 
               popup = paste0(
                 "Distance (km): ",
                 round(route$distance, 1), 
                 "<br/>Time (minutes): ",
                 round(route$duration, 1)
               ))

The optional argument steps = TRUE will break the route object into separate rows for each leg of the trip, and return travel instructions in a number of different languages (English is the default).

route_dir <- mb_directions(origin = penn,
                           destination = "Philadelphia Museum of Art, Philadelphia PA",
                           profile = "cycling",
                           steps = TRUE)

route_dir
## Simple feature collection with 13 features and 3 fields
## geometry type:  LINESTRING
## dimension:      XY
## bbox:           xmin: -75.1958 ymin: 39.95386 xmax: -75.18166 ymax: 39.96667
## geographic CRS: WGS 84
## First 10 features:
##                          geometry distance  duration
## 1  LINESTRING (-75.1958 39.953...   0.0276 0.2483333
## 2  LINESTRING (-75.19563 39.95...   0.0095 0.1083333
## 3  LINESTRING (-75.19574 39.95...   0.0874 0.4200000
## 4  LINESTRING (-75.19558 39.95...   0.0963 0.5566667
## 5  LINESTRING (-75.19446 39.95...   0.1694 0.7083333
## 6  LINESTRING (-75.19415 39.95...   0.2837 1.1783333
## 7  LINESTRING (-75.19361 39.95...   0.0995 0.5433333
## 8  LINESTRING (-75.19244 39.95...   0.4524 1.9100000
## 9  LINESTRING (-75.19285 39.96...   0.9275 3.9200000
## 10 LINESTRING (-75.18228 39.96...   0.2866 1.3033333
##                                instruction
## 1                                Head east
## 2             Turn left onto Sansom Street
## 3       Turn right onto Steve Murray's Way
## 4   Turn right onto Chestnut Street (PA 3)
## 5         Turn left onto South 36th Street
## 6          Continue onto North 36th Street
## 7              Turn right onto Race Street
## 8         Turn left onto North 35th Street
## 9     Turn right onto Spring Garden Street
## 10 Turn left onto Anne d'Harnoncourt Drive

3.3 Exercises

Now that you’ve learned how to use isochrone and routing services in mapboxapi, try them out for yourselves! Create the following maps:

  1. An isochrone map around a location of your choice. Times can be specified at 1-minute intervals all the way up to 60 minutes using a vector.
  2. A route between two locations of your choice, using a travel profile of your choice.

4 Analyzing elections and accessibility with mapboxapi

At the time of this workshop (October 9, 2020), the November 3rd election is less than a month away. This election is accompanied by massive questions around voter safety during the COVID-19 pandemic and voter suppression with unfounded concerns about voter fraud and mail-in ballots. In my home state of Texas, the governor has limited absentee ballot drop-off sites to one per county, creating significant accessibility issues for residents of large Texas counties.

Election accessibility can be analyzed using Mapbox services and the mapboxapi package. While the above examples are useful for quick queries and web mapping, my primary motivation for writing mapboxapi was to use Mapbox services for spatial data science tasks in R. As I already used Mapbox services heavily for my visualization projects, it made sense to write mapboxapi to connect these services with my existing sf-based data science workflows.

In this section of the workshop, we’ll explore three more advanced applications of mapboxapi within practical spatial data science workflows. We’ll examine how to visualize accessibility to a ballot drop-off location in Houston; identify areas where populations may have difficulty reaching early voting locations in Fort Worth; and build a routing app with Shiny that identifies the closest polling place to a user’s address. This section may include some new concepts or techniques - but it is designed to illustrate where you can go with mapboxapi in your work!

4.1 Visualizing (in)accessibility to ballot drop box locations

The tools we’ve learned how to use with mapboxapi can be used to analyze relative accessibility - or inaccessibility - to polling or ballot drop-off locations. Limiting ballot drop-off locations in Texas counties creates significant accessibility issues for Texas voters. For example, Harris County (Houston) will have one drop-off location for its 4.6 million residents, whereas many other counties in Texas have the same number of drop-off locations for populations smaller than 1,000.

We can visualize this situation in Harris County with layered isochrones. We already used this technique to show multiple drive times around the University of Pennsylvania earlier in this tutorial. In this case, we will use mb_isochrone() to generate dozens of isochrones, then visualize them simultaneously to illustrate an accessibility gradient in the region.

We’ll first generate the isochrones using a vector of times, 1 through 45 at 1-minute intervals, around NRG Arena (the ballot drop-off site).

library(mapboxapi)

isos <- mb_isochrone(
  location = "1 NRG Pkwy, Houston, TX 77054",
  profile = "driving",
  time = 1:45
)

Next, we can visualize our overlapping isochrones. We’ll use the viridis color palette as we did previously in the tutorial, and generate a color palette derived from the time column in our dataset. Once specified, we can add these polygons to our Mapbox basemap with a mostly-transparent fill opacity.

pal <- colorNumeric("viridis", isos$time, na.color = "transparent")

mapbox_map %>%
  addPolygons(data = isos,
              fillColor = ~pal(time),
              stroke = FALSE,
              fillOpacity = 0.1) %>%
  addLegend(values = isos$time,
            pal = pal,
            title = "Drive-time to NRG Arena")

The result illustrates some of the wide differences in accessibility between various parts of the region. One notable issue with this visualization approach, however, is that the layering of isochrones in the interior of Houston makes it difficult to view the basemap beneath them. This can be resolved by converting to a raster dataset and generating an “accessibility surface” for improved visualization.

4.1.1 Making an “accessibility surface”

Accessibility surfaces are commonly used in geographic information systems applications to identify the distance from any particular location to a geographic feature of interest. We can apply this concept to network-based accessibility by using mapboxapi tools. To create the accessibility surface, we will convert our isochrones to a raster dataset using the fasterize package. Raster datasets represent geographic information as grid cells defined by a cell size. Higher-resolution raster datasets are represented with smaller cell sizes.

To generate the accessibility surface raster, we will need to apply a coordinate system transformation to “project” our data to two-dimensional coordinates. This will allow us to specify the raster’s resolution in meters. We generate a 100m resolution raster, and use the fasterize() function to allocate the minimum overlapping value from our isochrones to each grid cell. The result can then be mapped with Leaflet’s addRasterImage() function.

library(fasterize)
library(sf)

isos_proj <- st_transform(isos, 32615)

template <- raster(isos_proj, resolution = 100)

iso_surface <- fasterize(isos_proj, template, field = "time", fun = "min")

mapbox_map %>%
  addRasterImage(iso_surface, colors = pal, opacity = 0.5) %>%
  addLegend(values = isos$time, pal = pal,
            title = "Drive-time to NRG Arena")

Accessibility is now represented in a similar way, but with a clearer view of the basemap around NRG Arena.

4.2 Identifying populations who may have difficulty reaching polling places

The previous example illustrated how to model and visualize accessibility in Houston; however, it does not speak directly to who may have difficulties dropping off their ballots. Households with access to cars will have a much easier time reaching NRG Arena to drop off their ballots, for example, than those who need to rely on other methods of transportation. It also does not integrate other spatial data showing the boundaries of Harris County. In turn, a clearer analysis would cross-reference accessibility data with other data sources using spatial analysis. Fortunately, all of this can be completed within R!

Our task in this section is to find neighborhoods with limited access to early voting locations in Fort Worth, Texas, and cross-reference this with demographic data from the most recent American Community Survey, the US Census Bureau’s annual social and economic survey of US households. To get started, let’s load in some core packages for spatial data analysis. We’ll be using the following R packages:

  • sf: sf, which stands for simple features, has cemented itself in the last couple years as the core package for vector-based spatial data representation and analysis in R. Spatial data are represented with sf much like regular R data frames, but with a list-column representing the geometry of each row.
  • tidyverse: A collection of popular R packages maintained by RStudio that work together to facilitate data representation, wrangling, and visualization.
  • tidycensus: An R package for downloading and working with data from the US Census Bureau’s decennial Census, American Community Survey (aggregate and microdata), and Population Estimates program. I first wrote this package three years ago because I grew tired of the tedious process of downloading Census data, cleaning it, and joining to shapefiles to do spatial analysis. tidycensus does all this for you internally with the ability to return Census and ACS data as simple features objects ready for mapping and analysis.

To get started, we’ll load the required packages for analysis. We’ll also set the option tigris_use_cache = TRUE to cache downloaded shapefiles (spatial data) from the Census website; this will store them for future use and guard against occasional website downtime.

library(tidyverse)
library(tidycensus)
options(tigris_use_cache = TRUE)

For this analysis, we’ll be using a dataset of early voting locations for Tarrant County, Texas, which represents the areas around Fort Worth and Arlington. There are 50 such locations around the county, allowing voters to cast their ballots between October 13 and October 30. This is a helpful alternative for voters who might not want to (or cannot) vot on Election Day on November 3rd.

We’ll read in a dataset of these early voting sites that I’ve already geocoded and converted to an sf POINT object. This dataset can be used to analyze which areas are immediately covered by accessible early voting options, and which are not. We’ll measure accessibility using isochrones as above, and consider a 20 minute walk-time around each polling location. mb_isochrone() can accept sf objects as input, and will retain an ID from the input sf object if the column name is specified.

ev_sites <- read_rds("data/tarrant_EV_sites.rds")

walking_isos <- mb_isochrone(
  ev_sites,
  profile = "walking",
  time = 20,
  id = "name"
)

These results can be visualized on our Mapbox map:

mapbox_map %>%
  addPolygons(data = walking_isos,
              popup = ~id)

The map represents the reachable area within a 20-minute walk, modeled at an average walking speed for an able-bodied adult (about 5.1 km/hour). For individuals with disabilities, the elderly, or households without access to a car, getting to these polling sites may prove difficult in areas outside these isochrones. However, accessibility may be less of an issue in areas where car ownership is widespread. We can analyze this additional variable with demographic data, also obtained within R.

4.2.1 Obtaining demographic data with tidycensus

We’ll be using tidycensus to request data from the US Census Bureau API about the percentage of households who do not have access to an automobile. A full discussion of how to use tidycensus is beyond the scope of today’s tutorial, but you’ll learn a few things here. To use tidycensus, you must first obtain a Census API key, available at this link. The key will be emailed to you; once you activate it, you can pass it to the census_api_key() function to set it (or install it) in your environment.

We can then request data from the American Community Survey’s 2014-2018 5-year dataset with the get_acs() function. The variable we want is the percentage of households without access to a car, designated with the variable code DP04_0058P and available in the ACS Data Profile. Please see the tidycensus documentation for more information about identifying appropriate variable IDs. We’ll request this data for Tarrant County, TX at the census tract level, which is the smallest available geography available for this information. The argument geometry = TRUE uses the tigris package to download spatial data from the Census website and joins it internally to the ACS data you’ve acquired.

If you don’t already have a key (or cannot get one at this time), un-comment the appropriate line below and read in a saved version of the dataset.

# census_api_key("your key goes here", install = TRUE)
# no_cars <- read_rds("data/no_cars.rds")

no_cars <- get_acs(
  geography = "tract",
  variables = "DP04_0058P",
  state = "TX",
  county = "Tarrant",
  geometry = TRUE
)

Let’s visualize this information on our Mapbox map:

driving_pal <- colorNumeric("viridis", no_cars$estimate)

mapbox_map %>%
  addPolygons(data = no_cars,
              fillColor = ~driving_pal(estimate),
              fillOpacity = 0.5,
              stroke = FALSE,
              smoothFactor = 0.1,
              label = ~round(estimate, 1)) %>%
  addLegend(values = no_cars$estimate,
            pal = driving_pal,
            title = "% without access<br/>to automobile")

As shown visually in the map, a majority of households in all Tarrant County Census tracts have access to an automobile. However, there are some Census tracts where the percentage without access exceeds 15 or even 20 percent. That said, if those tracts are within a reasonable walk of a polling location, accessibility may not be as large of an issue. We can analyze this topic using spatial overlay.

4.2.2 Performing spatial analysis with sf

Spatial overlay is a very common operation when working with spatial data. It can be used to determine which features in one spatial layer overlap with another spatial layer, or extract data from a layer based on geographic information. In R, spatial overlay can be integrated directly into tidyverse-style data analysis pipelines using functions in sf. In our example, we want to determine the areas in Tarrant County with the greatest proportion of households without access to a car that also are beyond a 20 minute walk from an early voting polling site.

To do this, we use this following steps:

  1. We transform the coordinate reference system of our no_cars dataset to 4326, the same CRS used by the isochrones;
  2. We extract only those Census tracts with a percentage of households without cars of 15 percent or above;
  3. We use the st_difference() function to “cut out” areas from those Census tracts that overlap the 20-minute walking isochrones.

Once we complete this operation, we can visualize the result on our Mapbox map.

target_areas <- no_cars %>%
  st_transform(4326) %>%
  filter(estimate >= 15) %>%
  st_difference(
    st_union(walking_isos)
  )


mapbox_map %>%
  addPolygons(data = target_areas)

As the map illustrates, there are several areas within Tarrant County that are located beyond a 20-minute walk from an early voting location and have proportionally lower access to automobiles. Notable clusters of neighborhoods that meet this criteria are located in Fort Worth to the south of downtown and on the city’s East Side. Granted, this analysis is not definitive, but gives us some insights into potential issues with voting accessibility and how we might resolve them.

4.3 Building a polling place locator app with Shiny

All of this information can be put together to build informative dashboards for the public using mapboxapi tools. The application shown below and available at this link uses mb_geocode(), mb_matrix(), and mb_directions() to identify the closest early voting location to a user-specified address in Tarrant County, calculates the driving directions to that location, then visualizes the route along with driving instructions on the map. The code used to build this app is available in the Masterclass repository; app_local.R is the minimal code to get the app working on your computer (assuming a Mapbox access token has been installed), and app.R includes additional details necessary to deploy the app on the ShinyApps.io hosting service.

We’ll take a look right now at the Shiny app code; you can also view a live version of the app embedded below!

5 Bonus exercise: using a custom Mapbox basemap in R

We’ve just scratched the surface of what you can do with Mapbox tools in R. While mapboxapi does not do map generation directly, there are options available for you. For more advanced (and fast!) visualization using Mapbox, I strongly recommend checking out the mapdeck package. This package is an interface to Uber’s deck.gl library, which is built on Mapbox tools.

Another option for visualization is to build your own custom maps using Mapbox Studio, Mapbox’s interactive web-based tool for cartographic design. Studio allows you to customize every aspect of their vector tiles for web mapping, making basemaps that are exactly to your specification. For comprehensive tutorials on how to work with Mapbox Studio, check out their tutorials. Here, I’ll just show you how to make a custom basemap very quickly and use it in your R projects.

Mapbox has created a fun tool called Cartogram that allows you to upload an image of your choice, which will be used to create a custom map style based on that image. Visit https://apps.mapbox.com/cartogram and upload an image of your choice! I’m using Penn’s athletics logo, though you can use whatever you’d like. If you are signed into your Mapbox account (which you should be from earlier in this tutorial), the style will save automatically to your account.

Click the “Saved style!” button at the top of the screen, and you’ll be transported to the Mapbox Studio editor with your custom Cartogram style loaded. There is much you can do here - but for now, click the “Share” button in the upper right of your screen to display the “Share and Develop” options.

Copy the “Style URL” and paste it in your R Markdown so you can see it; mine here is mapbox://styles/upenn-masterclass-demo1/ckfzordv11ha519nz3qw1v7nx. After mapbox://styles/, you’ll see your username and style ID. You may recall the beginning of the workshop when we used the Mapbox Streets style as a template for our R Leaflet maps. You can use this custom style in much the same way with addMapboxTiles():

leaflet() %>%
  addMapboxTiles(style_id = "ckfzordv11ha519nz3qw1v7nx",
                 username = "upenn-masterclass-demo1") %>%
  setView(lng = penn[1],
          lat = penn[2],
          zoom = 14)

Thanks for participating today! If you have more questions about mapboxapi or any of my other packages, feel free to get in touch. Also, be sure to share anything you’ve created based on what you’ve learned today on Twitter with the #rstats and #MusaMasterClass hashtags.

LS0tCnRpdGxlOiAiUGVubiBNVVNBIE1hc3RlcmNsYXNzIDIwMjAiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDogCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UpCmBgYAoKUHJlc2VudGVyOiBLeWxlIFdhbGtlciAoQGt5bGVfZV93YWxrZXIpCgoqIEdpdGh1YjogaHR0cHM6Ly9naXRodWIuY29tL3dhbGtlcmtlCiogVHV0b3JpYWwgcmVwb3NpdG9yeTogaHR0cHM6Ly9naXRodWIuY29tL3dhbGtlcmtlL01VU0FtYXN0ZXJjbGFzcwoqIG1hcGJveGFwaSBkb2N1bWVudGF0aW9uOiBodHRwczovL3dhbGtlci1kYXRhLmNvbS9tYXBib3hhcGkvCgoKVGhpcyB0dXRvcmlhbCB3YXMgcHJlc2VudGVkIG9uIE9jdG9iZXIgOSwgMjAyMCBmb3IgdGhlIFtNYXN0ZXIgb2YgVXJiYW4gU3BhdGlhbCBBbmFseXRpY3NdKGh0dHBzOi8vd3d3LmRlc2lnbi51cGVubi5lZHUvbXVzYS9hYm91dCkgcHJvZ3JhbSBhdCB0aGUgW1VuaXZlcnNpdHkgb2YgUGVubnN5bHZhbmlhJ3NdKGh0dHBzOi8vd3d3LnVwZW5uLmVkdS8pIFtXZWl0em1hbiBTY2hvb2wgb2YgRGVzaWduXShodHRwczovL3d3dy5kZXNpZ24udXBlbm4uZWR1LykuIAoKCiMgR2V0dGluZyBzdGFydGVkCgpUbyBydW4gdGhlIGV4YW1wbGVzIGZyb20gdGhlIHdvcmtzaG9wIHlvdXJzZWxmLCBvcGVuIGEgdGVybWluYWwgdGhlbiBjbG9uZSB0aGUgcmVwb3NpdG9yeSB0byB5b3VyIGNvbXB1dGVyOgoKYGBgYmFzaApnaXQgY2xvbmUgaHR0cHM6Ly9naXRodWIuY29tL3dhbGtlcmtlL01VU0FtYXN0ZXJjbGFzcy5naXQgCmBgYAoKT3BlbiB0aGUgcHJvamVjdCBpbiBSU3R1ZGlvIGFuZCBuYXZpZ2F0ZSB0byB0aGUgYHR1dG9yaWFsYCBmb2xkZXIsIHRoZW4gb3BlbiB0aGUgYGluZGV4LlJtZGAgZG9jdW1lbnQuICBUaGUgZXhhbXBsZXMgaW4gdGhhdCBkb2N1bWVudCB3aWxsIHJ1biBjb3JyZWN0bHkgaWYgY29kZSBjaHVua3MgYXJlIHNldCB0byBiZSBldmFsdWF0ZWQgaW4gdGhhdCBkaXJlY3RvcnkuICAKCkFsdGVybmF0aXZlbHksIGlmIHlvdSBhcmUgdW5mYW1pbGlhciB3aXRoIGdpdCwgY2xpY2sgdGhlICJDb2RlIiBkcm9wLWRvd24gYnV0dG9uIGluIHRoZSB1cHBlciByaWdodCBjb3JuZXIgb2YgdGhpcyB0dXRvcmlhbCwgYW5kIGNob29zZSAiRG93bmxvYWQgUm1kLiIgIFRoaXMgd2lsbCBkb3dubG9hZCB0aGlzIC5SbWQgZmlsZSB0byB5b3VyIGNvbXB1dGVyLiAgUHV0IHRoZSAuUm1kIGZpbGUgaW4gYSBkaXJlY3Rvcnkgb2YgeW91ciBjaG9pY2UuICBOZXh0LCBkb3dubG9hZCB0aGUgZGF0YSBmb3IgdGhpcyB3b3Jrc2hvcCBmcm9tIGh0dHBzOi8vd2Fsa2VyLWRhdGEuY29tL01VU0FtYXN0ZXJjbGFzcy90dXRvcmlhbC9kYXRhLnppcC4gIFVuemlwIHRoZSBmb2xkZXIgaW4gdGhlIHNhbWUgZGlyZWN0b3J5IGFzIHlvdXIgZG93bmxvYWRlZCAuUm1kIGZpbGUuIAoKIyMgSW5zdGFsbGluZyBwYWNrYWdlcyBhbmQgZGVwZW5kZW5jaWVzCgpUbyBnZXQgc3RhcnRlZCB3aXRoIG1hcGJveGFwaSwgeW91J2xsIG5lZWQgdG8gZmlyc3QgaW5zdGFsbCBzb21lIHBhY2thZ2VzLiBtYXBib3hhcGkgd2FzIGp1c3QgcmVsZWFzZWQgdG8gQ1JBTiB0aGlzIHdlZWssIHNvIHdlIGNhbiBpbnN0YWxsIHdpdGggYGluc3RhbGwucGFja2FnZXMoKWA6CgpgYGB7ciBpbnN0YWxsLW1hcGJveGFwaSwgZXZhbCA9IEZBTFNFfQppbnN0YWxsLnBhY2thZ2VzKCJtYXBib3hhcGkiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKQpgYGAKCklmIHlvdSd2ZSBiZWVuIHdvcmtpbmcgd2l0aCBSIFNwYXRpYWwgcGFja2FnZXMgYmVmb3JlLCBpbnN0YWxsYXRpb24gc2hvdWxkIGdvIHNtb290aGx5LiAgSWYgeW91IGFyZSBuZXcgdG8gUi9SIFNwYXRpYWwsIHlvdSBtYXkgbmVlZCB0byBkbyBzb21lIGNvbmZpZ3VyYXRpb24gcHJpb3IgdG8gc3VjY2Vzc2Z1bCBpbnN0YWxsYXRpb24gb2YgdGhlIHBhY2thZ2UuICBtYXBib3hhcGkgZGVwZW5kcyBoZWF2aWx5IG9uIHRoZSBbc2ZdKGh0dHBzOi8vZ2l0aHViLmNvbS9yLXNwYXRpYWwvc2YpIHBhY2thZ2UgZm9yIHNwYXRpYWwgZGF0YSBwcm9jZXNzaW5nIGluIFIuICBPbiBVYnVudHUsIHVzZSB0aGUgZm9sbG93aW5nIGNvbW1hbmRzIGluIGEgdGVybWluYWwgdG8gaW5zdGFsbCByZXF1aXJlZCBkZXBlbmRlbmNpZXM6IAoKYGBgYmFzaApzdWRvIGFkZC1hcHQtcmVwb3NpdG9yeSBwcGE6dWJ1bnR1Z2lzL3VidW50dWdpcy11bnN0YWJsZQpzdWRvIGFwdC1nZXQgdXBkYXRlCnN1ZG8gYXB0LWdldCBpbnN0YWxsIC15IGxpYnVkdW5pdHMyLWRldiBsaWJnZGFsLWRldiBsaWJnZW9zLWRldiBsaWJwcm9qLWRldiAKYGBgCgptYXBib3hhcGkgYWxzbyB1c2VzIHRoZSBbcHJvdG9saXRlXShodHRwczovL2dpdGh1Yi5jb20vamVyb2VuL3Byb3RvbGl0ZSkgcGFja2FnZSBmb3IgaW50ZXJhY3Rpbmcgd2l0aCBNYXBib3ggdmVjdG9yIHRpbGVzIGFuZCB0aGUgW21hZ2lja10oaHR0cHM6Ly9naXRodWIuY29tL3JvcGVuc2NpL21hZ2ljaykgcGFja2FnZSBmb3IgaW1hZ2UgcHJvY2Vzc2luZyBhbmQgZGlzcGxheS4gIE9uIFVidW50dSwgaW5zdGFsbCBkZXBlbmRlbmNpZXMgd2l0aDoKCmBgYGJhc2gKc3VkbyBhcHQtZ2V0IGluc3RhbGwgLXkgbGlicHJvdG9idWYtZGV2IHByb3RvYnVmLWNvbXBpbGVyIGxpYm1hZ2ljaysrLWRldgpgYGAKCkluc3RydWN0aW9ucyBmb3Igb3RoZXIgTGludXggZGlzdHJpYnV0aW9ucyBjYW4gYmUgZm91bmQgb24gdGhlIHBhY2thZ2Ugd2Vic2l0ZXMgbGlua2VkIGFib3ZlLiAgCgpUbyBydW4gYWxsIG9mIHRoZSBleGFtcGxlcyBpbiB0aGlzIHdvcmtzaG9wLCB5b3UnbGwgYWxzbyBuZWVkIHRvIGluc3RhbGwgdGhlIGZvbGxvd2luZyBwYWNrYWdlcyB0aGF0IGRvbid0IGdldCBwaWNrZWQgdXAgYXMgbWFwYm94YXBpIGRlcGVuZGVuY2llczoKCmBgYHtyIGluc3RhbGwtcGFja2FnZXMsIGV2YWwgPSBGQUxTRX0KaW5zdGFsbC5wYWNrYWdlcyhjKCJzaGlueSIsICJmYXN0ZXJpemUiLCAidGlkeWNlbnN1cyIsICJ0aWR5dmVyc2UiKSkKYGBgCgoKIyMgU2V0dGluZyB1cCB5b3VyIE1hcGJveCBhY2NvdW50CgpCZWZvcmUgd2UgZ2V0IHN0YXJ0ZWQgdXNpbmcgTWFwYm94IHNlcnZpY2VzIGluIFIsIHlvdSdsbCBuZWVkIGEgdmFsaWQgTWFwYm94IGFjY291bnQgd2l0aCBhbiBfX2FjY2VzcyB0b2tlbl9fLiAgRm9ydHVuYXRlbHksIE1hcGJveCBoYXMgZ2VuZXJvdXNseSBwcm92aWRlZCBhIGNvdXBvbiBjb2RlIGZvciB5b3UgdG8gdXNlIGFzIHdvcmtzaG9wIHBhcnRpY2lwYW50cy4gIFRvIHNldCB1cCB5b3VyIGFjY291bnQsIHZpc2l0IGh0dHBzOi8vYWNjb3VudC5tYXBib3guY29tL2F1dGgvc2lnbnVwLyB0byBlc3RhYmxpc2ggYW4gYWNjb3VudCAtIGFsbCB5b3UgbmVlZCB0byBwcm92aWRlIGlzIGFuIGVtYWlsIGFkZHJlc3MgdG8gc2lnbiB1cCEgIEZpbGwgb3V0IHRoZSBmb3JtIGFuZCB2ZXJpZnkgeW91ciBhY2NvdW50IHRocm91Z2ggdGhlIGVtYWlsIE1hcGJveCBzZW5kcyB5b3U7IHlvdSdsbCBiZSB0YWtlbiBkaXJlY3RseSB0byB5b3VyIE1hcGJveCBhY2NvdW50IGRhc2hib2FyZCBwYWdlLiAgCgohW10oaW1nL21iX2Rhc2hib2FyZC5wbmcpCk5vdGUgdGhlICJkZWZhdWx0IHB1YmxpYyB0b2tlbiIgdGhhdCBhcHBlYXJzIG9uIHlvdXIgc2NyZWVuIC0geW91J2xsIGNvbWUgYmFjayB0byB0aGlzIHBhZ2UgaW4gYSBtb21lbnQuICBGaXJzdCwgbG9vayB0byB0aGUgcmlnaHQgc2lkZSBvZiB5b3VyIHNjcmVlbiBhbmQgY2xpY2sgIlZpZXcgYmlsbGluZy4iICBUaGlzIGlzIHdoZXJlIE1hcGJveCB3aWxsIGhhbmRsZSB5b3VyIGJpbGxpbmcgaW5mb3JtYXRpb24uICBOb3RoaW5nIHlvdSdsbCBkbyB0b2RheSB3aWxsIGJlIGludGVuc2l2ZSBlbm91Z2ggdG8gaW5jdXIgY2hhcmdlcyAtIGJ1dCB5b3VyIG5leHQgdGhyZWUgbW9udGhzIG9mIHdvcmsgd2lsbCBiZSBjb3ZlcmVkIGJ5IHRoZSBjb3Vwb25zIE1hcGJveCBoYXMgcHJvdmlkZWQgdG8gdGhpcyB3b3Jrc2hvcC4gIFNjcm9sbCBkb3duIGFuZCBlbnRlciB0aGUgY291cG9uIGNvZGUgeW91J3ZlIHJlY2VpdmVkIGluIHRoZSBhcHByb3ByaWF0ZSBib3gsIHRoZW4gY2xpY2sgX19BZGRfXy4gIE9uY2UgeW91J3ZlIGVudGVyZWQgeW91ciBjb3Vwb24gY29kZSwgcmV0dXJuIHRvIHlvdXIgTWFwYm94IGRhc2hib2FyZC4gIENvcHkgdGhlIGFjY2VzcyB0b2tlbiB0aGF0IGFwcGVhcnMgb24geW91ciBzY3JlZW4gdG8geW91ciBjbGlwYm9hcmQsIHRoZW4gcmV0dXJuIHRvIFIuICAKCiMjIyBMb2FkaW5nIG1hcGJveGFwaSBhbmQgc2V0dGluZyB5b3VyIGFjY2VzcyB0b2tlbgoKQWxsIGZlYXR1cmVzIGluIG1hcGJveGFwaSByZXF1aXJlIGEgdmFsaWQgTWFwYm94IGFjY2VzcyB0b2tlbiB0byB3b3JrLiAgTm93IHRoYXQgeW91IGhhdmUgeW91cnMgaW4gaGFuZCwgeW91IGNhbiBzZXQgeW91cnMgdXAhICBMb2FkIHRoZSBtYXBib3hhcGkgcGFja2FnZSBhbmQgaW5zdGFsbCB5b3VyIHRva2VuIGFzIGZvbGxvd3M6ICAKCmBgYHtyIGluc3RhbGwtdG9rZW4sIGV2YWwgPSBGQUxTRX0KbXlfdG9rZW4gPC0gIllPVVIgVE9LRU4gR09FUyBIRVJFIgoKbGlicmFyeShtYXBib3hhcGkpCm1iX2FjY2Vzc190b2tlbihteV90b2tlbiwgaW5zdGFsbCA9IFRSVUUpCgpgYGAKClRoZSBvcHRpb25hbCBhcmd1bWVudCBgaW5zdGFsbCA9IFRSVUVgIHNhdmVzIHRoZSB0b2tlbiB0byB5b3VyIC5SZW52aXJvbiwgYWxsb3dpbmcgeW91IHRvIHVzZSBtYXBib3hhcGkgZnVuY3Rpb25zIGluIHRoZSBmdXR1cmUgd2l0aG91dCBoYXZpbmcgdG8gd29ycnkgYWJvdXQgc2V0dGluZyB5b3VyIHRva2VuLiAgVG8gdXNlIHRoaXMgZmVhdHVyZSwgcmVzdGFydCB5b3VyIFIgc2Vzc2lvbi4gIAoKIyBVc2luZyBNYXBib3ggbWFwcyBpbiBSCgpUaGUgbW9zdCB3ZWxsLWtub3duIGZlYXR1cmUgb2YgTWFwYm94IHNlcnZpY2VzIGlzIGl0cyBhYmlsaXR5IHRvIGNyZWF0ZSBzdHVubmluZyB3ZWIgbWFwcyB3aGljaCBhcmUgdXNlZCBvbiBhcHBsaWNhdGlvbnMgYWxsIGFyb3VuZCB0aGUgd29ybGQuICBXaGlsZSBtYXBib3hhcGkgaXMgbm90IGFuIGludGVyZmFjZSB0byBbTWFwYm94IEdMIEpTXShodHRwczovL2RvY3MubWFwYm94LmNvbS9tYXBib3gtZ2wtanMvYXBpLyksIE1hcGJveCdzIEphdmFTY3JpcHQgbGlicmFyeSBmb3IgYnVpbGRpbmcgd2ViIG1hcHMsIGl0IGRvZXMgaW5jbHVkZSBzb21lIHRvb2xzIHRvIGhlbHAgeW91IHVzZSBNYXBib3ggbWFwcyBpbiB5b3VyIFIgcHJvamVjdHMuICBUaGlzIGlzIGltcG9ydGFudCBhcyB0aGUgW01hcGJveCBUZXJtcyBvZiBTZXJ2aWNlXShodHRwczovL3d3dy5tYXBib3guY29tL2xlZ2FsL3Rvcy8pIHJlcXVpcmUgdGhhdCBNYXBib3ggQVBJIG91dHB1dHMgYmUgdmlzdWFsaXplZCBvbiBNYXBib3ggbWFwcy4gIAoKIyMgVmlzdWFsaXppbmcgTWFwYm94ICJzdHlsZXMiCgpNYXBib3ggbWFwcyBhcmUgYWNjZXNzZWQgdGhyb3VnaCBfc3R5bGVzXywgd2hpY2ggYXJlIGN1c3RvbSBkZXNpZ24gY29uZmlndXJhdGlvbnMgYXBwbGllZCB0byBPcGVuU3RyZWV0TWFwIG9yIGV2ZW4gdXNlci1nZW5lcmF0ZWQgdmVjdG9yIG1hcCB0aWxlc2V0cy4gIFlvdSdsbCBsZWFybiBob3cgdG8gY3JlYXRlIGFuZCB1c2UgeW91ciBvd24gbWFwIHN0eWxlIHdpdGggTWFwYm94IGxhdGVyIGluIHRoaXMgd29ya3Nob3AuICBIb3dldmVyLCBNYXBib3ggcHJvdmlkZXMgYSBudW1iZXIgb2YgdGhlaXIgc3R5bGVzIHRvIGFsbCB1c2VycyB3aXRoIGEgTWFwYm94IGFjY2VzcyB0b2tlbi4gIFRoZSBtb3N0IHJlY2VudCB2ZXJzaW9ucyBvZiB0aGVzZSBzdHlsZXMgKGFzIG9mIHRoZSB3b3Jrc2hvcCBkYXRlKSBhcmUgYXMgZm9sbG93czoKCiogYHN0cmVldHMtdjExYDogW1RoZSBjb3JlIE1hcGJveCBTdHJlZXRzIGJhc2VtYXBdKGh0dHBzOi8vd3d3Lm1hcGJveC5jb20vbWFwcy9zdHJlZXRzKQoqIGBvdXRkb29ycy12MTFgOiBbQSBiYXNlbWFwIGRlc2lnbmVkIGZvciBvdXRkb29yIHJlY3JlYXRpb24gdXNlc10oaHR0cHM6Ly93d3cubWFwYm94LmNvbS9tYXBzL291dGRvb3JzKQoqIGBsaWdodC12MTBgOiBbQSBsaWdodCwgZ3JleXNjYWxlIGJhY2tncm91bmQgc3VpdGFibGUgZm9yIHRoZW1hdGljIG92ZXJsYXldKGh0dHBzOi8vd3d3Lm1hcGJveC5jb20vbWFwcy9saWdodCkKKiBgZGFyay12MTBgOiBbQSBkYXJrIGJhc2VtYXAgc3VpdGFibGUgZm9yIHRoZW1hdGljIG92ZXJsYXldKGh0dHBzOi8vd3d3Lm1hcGJveC5jb20vbWFwcy9kYXJrKQoqIGBzYXRlbGxpdGUtdjlgOiBbQSBnbG9iYWwgc2F0ZWxsaXRlIGJhc2VtYXAgZGVyaXZlZCBmcm9tIE1PRElTLCBMYW5kc2F0LCAmIHByb3ByaWV0YXJ5IGltYWdlcnkgc291cmNlc10oaHR0cHM6Ly93d3cubWFwYm94LmNvbS9tYXBzL3NhdGVsbGl0ZSkKKiBgc2F0ZWxsaXRlLXN0cmVldHMtdjExYDogVGhlIHNhdGVsbGl0ZSBiYXNlbWFwIHdpdGggYSBzdHJlZXRzIG92ZXJsYXkKCk9uZSBvZiB0aGUgbW9zdCBwb3B1bGFyIFIgcGFja2FnZXMgZm9yIGludGVyYWN0aXZlIGRhdGEgdmlzdWFsaXphdGlvbiBpbiBSIGlzIHRoZSBbTGVhZmxldCBwYWNrYWdlXShodHRwczovL3JzdHVkaW8uZ2l0aHViLmlvL2xlYWZsZXQvKSBtYWludGFpbmVkIGJ5IFJTdHVkaW8sIHdoaWNoIHdyYXBzIHRoZSBbTGVhZmxldCBKYXZhU2NyaXB0IGxpYnJhcnldKGh0dHBzOi8vbGVhZmxldGpzLmNvbS8pIGZvciB3ZWIgbWFwcGluZy4gIFtZZWFycyBhZ28sIEkgd3JvdGUgYSB0dXRvcmlhbCBvbiBob3cgdG8gdXNlIE1hcGJveCBtYXBzIGluIFIgTGVhZmxldCBwcm9qZWN0c10oaHR0cHM6Ly9ycHVicy5jb20vd2Fsa2Vya2UvcnN0dWRpby1tYXBib3gpLiAgTm93LCBtYXBib3hhcGkgcHJvdmlkZXMgYSBjb252ZW5pZW5jZSBmdW5jdGlvbiwgYGFkZE1hcGJveFRpbGVzKClgLCB0byBoZWxwIHlvdSBkbyB0aGlzIGluIGEgbW9yZSBzdHJhaWdodGZvcndhcmQgd2F5LiAgVGhpcyBmdW5jdGlvbiBxdWVyaWVzIHRoZSBbTWFwYm94IFN0YXRpYyBUaWxlcyBBUEldKGh0dHBzOi8vZG9jcy5tYXBib3guY29tL2FwaS9tYXBzLyNzdGF0aWMtdGlsZXMpIGFuZCBjb252ZXJ0cyBhIE1hcGJveCBzdHlsZSBpbnRvIHN0YXRpYyB0aWxlcyBmb3Igd2ViIG1hcHBpbmcuICAKCkxldCdzIGxvYWQgdGhlIGxlYWZsZXQgYW5kIG1hcGJveGFwaSBsaWJyYXJpZXMgYW5kIHNldCB1cCBhbiBpbnRlcmFjdGl2ZSBtYXA6IAoKYGBge3IgbWFwYm94LW1hcH0KbGlicmFyeShsZWFmbGV0KQpsaWJyYXJ5KG1hcGJveGFwaSkKCm1hcGJveF9tYXAgPC0gbGVhZmxldCgpICU+JQogIGFkZE1hcGJveFRpbGVzKHN0eWxlX2lkID0gInN0cmVldHMtdjExIiwKICAgICAgICAgICAgICAgICB1c2VybmFtZSA9ICJtYXBib3giKSAKCm1hcGJveF9tYXAKYGBgCgpXZSBnZXQgYSBicm93c2VhYmxlIExlYWZsZXQgbWFwIHVzaW5nIE1hcGJveCB0aWxlcyBhcyBhIGJhc2VtYXAuICAKCiMjIEZvY3VzaW5nIHlvdXIgbWFwIHdpdGggdGhlIE1hcGJveCBTZWFyY2ggQVBJCgpPbmNlIHdlJ3ZlIHNldCB1cCBvdXIgTGVhZmxldCBtYXAgd2l0aCBhIE1hcGJveCBiYXNlbWFwLCB3ZSdsbCBsaWtlbHkgd2FudCB0byBmb2N1cyBpdCBvbiBhIHNwZWNpZmljIGxvY2F0aW9uLiAgbWFwYm94YXBpIGluY2x1ZGVzIGZ1bmN0aW9uYWxpdHkgZm9yIFIgdXNlcnMgdG8gaW50ZXJhY3Qgd2l0aCB0aGUgW01hcGJveCBTZWFyY2ggQVBJXShodHRwczovL2RvY3MubWFwYm94LmNvbS9hcGkvc2VhcmNoLykuICBJbXBsZW1lbnRlZCBmdW5jdGlvbnMgaW5jbHVkZSBgbWJfZ2VvY29kZSgpYCBmb3IgX2ZvcndhcmQgZ2VvY29kaW5nXywgd2hpY2ggcmVmZXJzIHRvIHRoZSBjb252ZXJzaW9uIG9mIGEgZGVzY3JpcHRpb24gb2YgYSBwbGFjZSAobGlrZSBhbiBhZGRyZXNzKSBpbnRvIGxvbmdpdHVkZS9sYXRpdHVkZSBjb29yZGluYXRlczsgYW5kIGBtYl9yZXZlcnNlX2dlb2NvZGUoKWAsIHdoaWNoIGNvbnZlcnRzIGNvb3JkaW5hdGVzIGludG8gYSBwbGFjZSBkZXNjcmlwdGlvbi4gIAoKQm90aCBmdW5jdGlvbnMgZGVmYXVsdCB0byB1c2luZyB0aGUgYG1hcGJveC5wbGFjZXNgIEFQSSBlbmRwb2ludCwgd2hpY2ggaXMgdG8gYmUgdXNlZCBmb3IgX3RlbXBvcmFyeSBnZW9jb2RpbmdfLiAgVGhpcyBtZWFucyB0aGF0IHRoZSBlbmRwb2ludCBjYW5ub3QgYmUgdXNlZCB0byBzdG9yZSBnZW9jb2RlZCBpbmZvcm1hdGlvbiBub3IgY2FuIGl0IGJlIHVzZWQgZm9yIGJhdGNoIGdlb2NvZGluZyAoZS5nLiwgYSBzcHJlYWRzaGVldCBvZiBhZGRyZXNzZXMpLiAgVGhlc2UgdGFza3MgYXJlIHBlcm1pc3NpYmxlIHdpdGggdGhlIGBtYXBib3gucGxhY2VzLXBlcm1hbmVudGAgZW5kcG9pbnQsIHdoaWNoIGlzIG5vdCBpbmNsdWRlZCB3aXRoIGZyZWUgYWNjb3VudHMuICBJbiB0dXJuLCBSIHVzZXJzIGxvb2tpbmcgZm9yIGZyZWUgYmF0Y2ggZ2VvY29kaW5nIHNvbHV0aW9ucyBzaG91bGQgdXNlIG90aGVyIHBhY2thZ2VzIFtsaWtlIHRoZSB0aWR5Z2VvY29kZXIgcGFja2FnZV0oaHR0cHM6Ly9qZXNzZWNhbWJvbi5naXRodWIuaW8vdGlkeWdlb2NvZGVyLykuICBNYXBib3ggZ2VvY29kaW5nIHdpdGggdGhlIGBtYXBib3gucGxhY2VzYCBlbmRwb2ludCBfY2FuXyBiZSB1c2VkIHRvIGZvY3VzIHdlYiBtYXBzIGFuZCBndWlkZSBuYXZpZ2F0aW9uIHNlcnZpY2VzLCB3aGljaCB3aWxsIGJlIGlsbHVzdHJhdGVkIGluIHRoZSBmb2xsb3dpbmcgc2VjdGlvbnMuICAKCkxldCdzIHVzZSBgbWJfZ2VvY29kZSgpYCB0byBpZGVudGlmeSB0aGUgY29vcmRpbmF0ZXMgcmVwcmVzZW50aW5nIHRoZSBVbml2ZXJzaXR5IG9mIFBlbm5zeWx2YW5pYSAoc3BlY2lmaWNhbGx5IGhlcmUsIHRoZSB1bml2ZXJzaXR5IGJvb2tzdG9yZSkuCgpgYGB7ciBnZW9jb2RlLXBlbm59CnBlbm4gPC0gbWJfZ2VvY29kZSgiMzYwMSBXYWxudXQgU3QsIFBoaWxhZGVscGhpYSwgUEEgMTkxMDQiKQoKcGVubgpgYGAKCkJ5IGRlZmF1bHQsIGBtYl9nZW9jb2RlKClgIHJldHVybnMgYSBsZW5ndGgtMiB2ZWN0b3IgcmVwcmVzZW50aW5nIHRoZSBsb25naXR1ZGUgYW5kIGxhdGl0dWRlIGNvb3JkaW5hdGVzIG9mIHRoZSBnZW9jb2RlZCBsb2NhdGlvbi4gIFRoZSBmdW5jdGlvbiBjYW4gYWxzbyByZXR1cm4gYW4gc2YgUE9JTlQgb2JqZWN0IG9yIGFuIFIgbGlzdCByZXByZXNlbnRpbmcgdGhlIGZ1bGwgQVBJIHJlc3BvbnNlLCBpZiByZXF1ZXN0ZWQuICBVc2luZyB0aGUgcmV0dXJuZWQgY29vcmRpbmF0ZXMsIHdlIGNhbiBmb2N1cyBvdXIgTGVhZmxldCBNYXBib3ggbWFwIHdpdGggdGhlIGBzZXRWaWV3KClgIGZ1bmN0aW9uOgoKCmBgYHtyIHZpZXctcGVubn0KbWFwYm94X21hcCAlPiUKICBzZXRWaWV3KGxuZyA9IHBlbm5bMV0sCiAgICAgICAgICBsYXQgPSBwZW5uWzJdLAogICAgICAgICAgem9vbSA9IDE0KQoKYGBgCgojIyBFeGVyY2lzZQoKX19UcnkgaXQgb3V0X18hIE1ha2UgYSBMZWFmbGV0IG1hcCBpbiBSIHVzaW5nIGEgTWFwYm94IGJhc2VtYXAgb2YgeW91ciBjaG9pY2UsIGZvY3VzZWQgb24gYSBsb2NhdGlvbiBvZiB5b3VyIGNob2ljZS4gIEZvciBsb2NhdGlvbnMgaW4gbm9uLUVuZ2xpc2gtc3BlYWtpbmcgY291bnRyaWVzOiBgbWJfZ2VvY29kZSgpYCBoYXMgYSBgbGFuZ3VhZ2VgIGFyZ3VtZW50IHRoYXQgY2FuIGJlIHVzZWQgdG8gaW1wcm92ZSB0aGUgYWNjdXJhY3kgb2YgcXVlcmllcyBpbiBsYW5ndWFnZXMgb3RoZXIgdGhhbiBFbmdsaXNoLiAgW1N1cHBvcnRlZCBsYW5ndWFnZXMgKGFuZCBob3cgdG8gc3BlY2lmeSB0aGVtKSBhcmUgZm91bmQgaW4gdGhlIE1hcGJveCBkb2N1bWVudGF0aW9uIGhlcmUuXShodHRwczovL2RvY3MubWFwYm94LmNvbS9hcGkvc2VhcmNoLyNsYW5ndWFnZS1jb3ZlcmFnZSkKCgojIFVzaW5nIE1hcGJveCBOYXZpZ2F0aW9uIEFQSXMgaW4gUgoKW1RoZSBNYXBib3ggTmF2aWdhdGlvbiBTZXJ2aWNlIEFQSV0oaHR0cHM6Ly9kb2NzLm1hcGJveC5jb20vYXBpL25hdmlnYXRpb24vKSBpbmNsdWRlcyBhIHZhcmlldHkgb2YgbWV0aG9kcyBmb3IgcGVyZm9ybWluZyByb3V0aW5nIGFuZCBuZXR3b3JrIGFuYWx5c2lzLiAgbWFwYm94YXBpIGFsbG93cyBSIHVzZXJzIHRvIGludGVyYWN0IHdpdGggdGhlIE5hdmlnYXRpb24gU2VydmljZSBBUEkgYnkgdXNpbmcgdGhlIGZvbGxvd2luZyBmdW5jdGlvbnM6IAoKKiBgbWJfZGlyZWN0aW9ucygpYDogYW4gaW50ZXJmYWNlIHRvIHRoZSBbTWFwYm94IERpcmVjdGlvbnMgQVBJXShodHRwczovL2RvY3MubWFwYm94LmNvbS9hcGkvbmF2aWdhdGlvbi8jZGlyZWN0aW9ucykgZm9yIGNhbGN1bGF0aW5nIGRyaXZpbmcgKHdpdGggb3Igd2l0aG91dCB0cmFmZmljKSwgd2Fsa2luZywgb3IgY3ljbGluZyByb3V0ZXMuICBVc2VycyBjYW4gcmVxdWVzdCByb3V0ZXMgYmV0d2VlbiBhbiBvcmlnaW4vZGVzdGluYXRpb24gcGFpciBvciBhbG9uZyBhIHNlcmllcyBvZiBwb2ludHMsIGFuZCBjYW4gcmV0dXJuIHJvdXRlIGxpbmVzdHJpbmdzIGFzIHNpbXBsZSBmZWF0dXJlcyBvYmplY3RzIGFsb25nIHdpdGggdHJhdmVsIGluc3RydWN0aW9ucy4gIAoqIGBtYl9vcHRpbWl6ZWRfcm91dGUoKWA6IGR1cmF0aW9uLW9wdGltaXplZCByb3V0aW5nIHdpdGggdGhlIFtNYXBib3ggT3B0aW1pemF0aW9uIEFQSV0oaHR0cHM6Ly9kb2NzLm1hcGJveC5jb20vYXBpL25hdmlnYXRpb24vI29wdGltaXphdGlvbiksIGhlbHBpbmcgeW91IGRldGVybWluZSB0aGUgZmFzdGVzdCB3YXkgdG8gdmlzaXQgbXVsdGlwbGUgbG9jYXRpb25zIG9uIHlvdXIgcm91dGUuICAKKiBgbWJfaXNvY2hyb25lKClgOiB1c2VzIHRoZSBbTWFwYm94IElzb2Nocm9uZSBBUEldKGh0dHBzOi8vZG9jcy5tYXBib3guY29tL2FwaS9uYXZpZ2F0aW9uLyNpc29jaHJvbmUpIHRvIGRyYXcgX2lzb2Nocm9uZXNfIGFyb3VuZCBzcGVjaWZpZWQgbG9jYXRpb25zLCB3aGljaCByZXByZXNlbnQgdGhlIHJlYWNoYWJsZSBhcmVhIGZyb20gdGhvc2UgbG9jYXRpb25zIHdpdGhpbiBhIGdpdmVuIHRyYXZlbCB0aW1lIGJ5IGEgZ2l2ZW4gdHJhdmVsIG1vZGUuICAKKiBgbWJfbWF0cml4KClgOiBmb3IgYSBzcGVjaWZpZWQgc2V0IG9mIG9yaWdpbiBhbmQgZGVzdGluYXRpb24gbG9jYXRpb25zLCB1c2VzIHRoZSBbTWFwYm94IE1hdHJpeCBBUEldKGh0dHBzOi8vZG9jcy5tYXBib3guY29tL2FwaS9uYXZpZ2F0aW9uLyNtYXRyaXgpIHRvIGNhbGN1bGF0ZSBwYWlyd2lzZSB0cmF2ZWwgdGltZXMgYmV0d2VlbiB0aG9zZSBsb2NhdGlvbnMuICBUaGlzIGZ1bmN0aW9uIGlzIHJlY29tbWVuZGVkIGZvciBzbWFsbGVyIHRyYXZlbC10aW1lIG1hdHJpY2VzOyBsYXJnZSBtYXRyaXggcmVxdWVzdHMgKGUuZy4gd2l0aCByZXN1bHRzIGV4Y2VlZGluZyB0aGUgdGVucyBvZiB0aG91c2FuZHMpIHNob3VsZCBiZSBjb21wbGV0ZWQgd2l0aCBhIHVzZXItaW5zdGFsbGVkIHJvdXRpbmcgZW5naW5lIGxpa2UgW1ZhbGhhbGxhXShodHRwczovL2dpdGh1Yi5jb20vdmFsaGFsbGEvdmFsaGFsbGEpIG9yIFtPU1JNXShodHRwOi8vcHJvamVjdC1vc3JtLm9yZy8pLiAgCgpUaGUgZmlmdGggTWFwYm94IG5hdmlnYXRpb24gc2VydmljZSwgW21hcCBtYXRjaGluZ10oaHR0cHM6Ly9kb2NzLm1hcGJveC5jb20vYXBpL25hdmlnYXRpb24vI21hcC1tYXRjaGluZyksIGlzIG5vdCB5ZXQgc3VwcG9ydGVkIGJ5IG1hcGJveGFwaSBidXQgd2lsbCBiZSBpbiBhIGZ1dHVyZSByZWxlYXNlLiAgCgojIyBEcmF3aW5nIGlzb2Nocm9uZXMgd2l0aCBNYXBib3ggYW5kIFIKCkNyZWF0aW5nIGFuZCB2aXN1YWxpemluZyBpc29jaHJvbmVzIGlzIHN0cmFpZ2h0Zm9yd2FyZCB3aXRoIHRoZSBgbWJfaXNvY2hyb25lKClgIGZ1bmN0aW9uIGluIG1hcGJveGFwaS4gIFN1cHBvcnRlZCB0cmF2ZWwgcHJvZmlsZXMgaW5jbHVkZSBkcml2aW5nICh3aXRoIG5vIHRyYWZmaWMpLCBjeWNsaW5nLCBhbmQgd2Fsa2luZy4gIGBtYl9pc29jaHJvbmUoKWAgYnkgZGVmYXVsdCByZXR1cm5zIGEgc2ltcGxlIGZlYXR1cmVzIHBvbHlnb24gb2JqZWN0IHRoYXQgY2FuIGJlIHVzZWQgZm9yIHZpc3VhbGl6YXRpb24gYW5kIGV2ZW4gc3BhdGlhbCBhbmFseXNpcy4gIAoKTGV0J3MgdHJ5IGRyYXdpbmcgaXNvY2hyb25lcyBhcm91bmQgdGhlIFBlbm4gY2FtcHVzLiAgYG1iX2lzb2Nocm9uZSgpYCBhY2NlcHRzIGFuIGFuIGlucHV0IGEgY29vcmRpbmF0ZSBwYWlyLCBhIGxvY2F0aW9uIGRlc2NyaXB0aW9uIGFzIGEgY2hhcmFjdGVyIHN0cmluZywgb3IgYW4gc2Ygb2JqZWN0LiAgV2UgY2FuIHVzZSBvdXIgYHBlbm5gIG9iamVjdCBoZXJlIHRvIGluaXRpYWxpemUgdGhlIGlzb2Nocm9uZXMgYXJvdW5kIGNhbXB1cy4gIAoKYGBge3IgcGVubi1pc29jaHJvbmVzfQpwZW5uX2lzb2Nocm9uZXMgPC0gbWJfaXNvY2hyb25lKHBlbm4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvZmlsZSA9ICJkcml2aW5nIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aW1lID0gYyg0LCA4LCAxMikpCgpwZW5uX2lzb2Nocm9uZXMKYGBgCgpBbiBzZiBvYmplY3QgaXMgcmV0dXJuZWQgd2l0aCBhIGB0aW1lYCBjb2x1bW4gcmVwcmVzZW50aW5nIHRoZSB0cmF2ZWwtdGltZSBhcm91bmQgdGhlIGxvY2F0aW9uLiAgYHRpbWVgIGlzIG9yZ2FuaXplZCBpbiBkZXNjZW5kaW5nIG9yZGVyIHRvIGVuc3VyZSB0aGF0IG92ZXJsYXBwaW5nIGlzb2Nocm9uZXMgYXJlIHBsb3R0ZWQgY29ycmVjdGx5LCB3aXRoIHRoZSBzaG9ydGVzdCB0aW1lIHZpc3VhbGl6ZWQgbGFzdCAob24gdG9wKS4gIAoKVXNpbmcgTGVhZmxldCdzIGBhZGRQb2x5Z29ucygpYCBmdW5jdGlvbiwgd2UgY2FuIGFkZCB0aGUgaXNvY2hyb25lcyB0byBvdXIgbWFwLiAKCmBgYHtyIG1hcC1pc29jaHJvbmVzfQpjb2xvcnMgPC0gdmlyaWRpc0xpdGU6OnZpcmlkaXMoMykKCm1hcGJveF9tYXAgJT4lCiAgYWRkUG9seWdvbnMoZGF0YSA9IHBlbm5faXNvY2hyb25lcywKICAgICAgICAgICAgICBjb2xvciA9IHJldihjb2xvcnMpLAogICAgICAgICAgICAgIGZpbGxDb2xvciA9IHJldihjb2xvcnMpLAogICAgICAgICAgICAgIGZpbGxPcGFjaXR5ID0gMC41LCAKICAgICAgICAgICAgICBvcGFjaXR5ID0gMSwgCiAgICAgICAgICAgICAgd2VpZ2h0ID0gMC4yKSAlPiUKICBhZGRMZWdlbmQobGFiZWxzID0gYyg0LCA4LCAxMiksCiAgICAgICAgICAgIGNvbG9ycyA9IGNvbG9ycywKICAgICAgICAgICAgdGl0bGUgPSAiRHJpdmUtdGltZTxici8+YXJvdW5kIFBlbm4iKQpgYGAKClRoZSBmaWxsZWQgYXJlYXMgcmVwcmVzZW50IHRoZSBlc3RpbWF0ZWQgcmVhY2hhYmxlIGFyZWEgYXJvdW5kIFBlbm4gd2l0aG91dCB0cmFmZmljLiAgVGhlIE1hcGJveCBBUEkgZG9lcyBub3Qgc3VwcG9ydCB0cmFmZmljLWFkanVzdGVkIGlzb2Nocm9uZXMsIHNvIGlzb2Nocm9uZSByZXN1bHRzIHNob3VsZCBiZSBpbnRlcnByZXRlZCBhcHByb3ByaWF0ZWx5IGRlcGVuZGluZyBvbiB3aGF0IHlvdSBwbGFuIHRvIHJlcHJlc2VudC4gIAoKCiMjIFJvdXRpbmcgd2l0aCBtYXBib3hhcGkKCm1hcGJveGFwaSBjYW4gYWxzbyBiZSB1c2VkIHRvIHF1aWNrbHkgcmVwcmVzZW50IGFuZCB2aXN1YWxpemUgcm91dGVzIGJldHdlZW4gdHdvIGxvY2F0aW9ucywgb3IgYWx0ZXJuYXRpdmVseSBhbG9uZyBtdWx0aXBsZSBsb2NhdGlvbnMuICBUaGUgTWFwYm94IERpcmVjdGlvbnMgQVBJIGVuZHBvaW50IGhhcyBfYSBsb3RfIG9mIG9wdGlvbnMsIHdoaWNoIEkndmUgZG9uZSBteSBiZXN0IHRvIGltcGxlbWVudCBmb3IgUiB1c2Vycy4gIEF0IGl0cyBzaW1wbGVzdCwgaG93ZXZlciwgYG1iX2RpcmVjdGlvbnMoKWAganVzdCByZXF1aXJlcyBhbiBvcmlnaW4gYW5kIGEgZGVzdGluYXRpb246IAoKYGBge3IgZGlyZWN0aW9uc30Kcm91dGUgPC0gbWJfZGlyZWN0aW9ucyhvcmlnaW4gPSBwZW5uLAogICAgICAgICAgICAgICAgICAgICAgIGRlc3RpbmF0aW9uID0gIlBoaWxhZGVscGhpYSBNdXNldW0gb2YgQXJ0LCBQaGlsYWRlbHBoaWEgUEEiLAogICAgICAgICAgICAgICAgICAgICAgIHByb2ZpbGUgPSAiY3ljbGluZyIpCgptYXBib3hfbWFwICU+JQogIGFkZFBvbHlsaW5lcyhkYXRhID0gcm91dGUsIAogICAgICAgICAgICAgICBwb3B1cCA9IHBhc3RlMCgKICAgICAgICAgICAgICAgICAiRGlzdGFuY2UgKGttKTogIiwKICAgICAgICAgICAgICAgICByb3VuZChyb3V0ZSRkaXN0YW5jZSwgMSksIAogICAgICAgICAgICAgICAgICI8YnIvPlRpbWUgKG1pbnV0ZXMpOiAiLAogICAgICAgICAgICAgICAgIHJvdW5kKHJvdXRlJGR1cmF0aW9uLCAxKQogICAgICAgICAgICAgICApKQpgYGAKClRoZSBvcHRpb25hbCBhcmd1bWVudCBgc3RlcHMgPSBUUlVFYCB3aWxsIGJyZWFrIHRoZSByb3V0ZSBvYmplY3QgaW50byBzZXBhcmF0ZSByb3dzIGZvciBlYWNoIGxlZyBvZiB0aGUgdHJpcCwgYW5kIHJldHVybiB0cmF2ZWwgaW5zdHJ1Y3Rpb25zIGluIGEgbnVtYmVyIG9mIGRpZmZlcmVudCBsYW5ndWFnZXMgKEVuZ2xpc2ggaXMgdGhlIGRlZmF1bHQpLiAKCmBgYHtyIGluc3RydWN0aW9uc30Kcm91dGVfZGlyIDwtIG1iX2RpcmVjdGlvbnMob3JpZ2luID0gcGVubiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVzdGluYXRpb24gPSAiUGhpbGFkZWxwaGlhIE11c2V1bSBvZiBBcnQsIFBoaWxhZGVscGhpYSBQQSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2ZpbGUgPSAiY3ljbGluZyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0ZXBzID0gVFJVRSkKCnJvdXRlX2RpcgpgYGAKIyMgRXhlcmNpc2VzCgpOb3cgdGhhdCB5b3UndmUgbGVhcm5lZCBob3cgdG8gdXNlIGlzb2Nocm9uZSBhbmQgcm91dGluZyBzZXJ2aWNlcyBpbiBtYXBib3hhcGksIHRyeSB0aGVtIG91dCBmb3IgeW91cnNlbHZlcyEgIENyZWF0ZSB0aGUgZm9sbG93aW5nIG1hcHM6ICAKCjEuIEFuIGlzb2Nocm9uZSBtYXAgYXJvdW5kIGEgbG9jYXRpb24gb2YgeW91ciBjaG9pY2UuICBUaW1lcyBjYW4gYmUgc3BlY2lmaWVkIGF0IDEtbWludXRlIGludGVydmFscyBhbGwgdGhlIHdheSB1cCB0byA2MCBtaW51dGVzIHVzaW5nIGEgdmVjdG9yLiAKMi4gQSByb3V0ZSBiZXR3ZWVuIHR3byBsb2NhdGlvbnMgb2YgeW91ciBjaG9pY2UsIHVzaW5nIGEgdHJhdmVsIHByb2ZpbGUgb2YgeW91ciBjaG9pY2UuIAoKCiMgQW5hbHl6aW5nIGVsZWN0aW9ucyBhbmQgYWNjZXNzaWJpbGl0eSB3aXRoIG1hcGJveGFwaQoKQXQgdGhlIHRpbWUgb2YgdGhpcyB3b3Jrc2hvcCAoT2N0b2JlciA5LCAyMDIwKSwgdGhlIE5vdmVtYmVyIDNyZCBlbGVjdGlvbiBpcyBsZXNzIHRoYW4gYSBtb250aCBhd2F5LiAgVGhpcyBlbGVjdGlvbiBpcyBhY2NvbXBhbmllZCBieSBtYXNzaXZlIHF1ZXN0aW9ucyBhcm91bmQgdm90ZXIgc2FmZXR5IGR1cmluZyB0aGUgQ09WSUQtMTkgcGFuZGVtaWMgYW5kIHZvdGVyIHN1cHByZXNzaW9uIHdpdGggdW5mb3VuZGVkIGNvbmNlcm5zIGFib3V0IHZvdGVyIGZyYXVkIGFuZCBtYWlsLWluIGJhbGxvdHMuICBJbiBteSBob21lIHN0YXRlIG9mIFRleGFzLCBbdGhlIGdvdmVybm9yIGhhcyBsaW1pdGVkIGFic2VudGVlIGJhbGxvdCBkcm9wLW9mZiBzaXRlcyB0byBvbmUgcGVyIGNvdW50eV0oaHR0cHM6Ly93d3cuY25uLmNvbS8yMDIwLzEwLzAxL3BvbGl0aWNzL3RleGFzLWdvdmVybm9yLWRyb3Atb2ZmLWxvY2F0aW9ucy1iYWxsb3RzL2luZGV4Lmh0bWwpLCBjcmVhdGluZyBzaWduaWZpY2FudCBhY2Nlc3NpYmlsaXR5IGlzc3VlcyBmb3IgcmVzaWRlbnRzIG9mIGxhcmdlIFRleGFzIGNvdW50aWVzLiAgCgpFbGVjdGlvbiBhY2Nlc3NpYmlsaXR5IGNhbiBiZSBhbmFseXplZCB1c2luZyBNYXBib3ggc2VydmljZXMgYW5kIHRoZSBtYXBib3hhcGkgcGFja2FnZS4gIFdoaWxlIHRoZSBhYm92ZSBleGFtcGxlcyBhcmUgdXNlZnVsIGZvciBxdWljayBxdWVyaWVzIGFuZCB3ZWIgbWFwcGluZywgbXkgcHJpbWFyeSBtb3RpdmF0aW9uIGZvciB3cml0aW5nIG1hcGJveGFwaSB3YXMgX3RvIHVzZSBNYXBib3ggc2VydmljZXMgZm9yIHNwYXRpYWwgZGF0YSBzY2llbmNlIHRhc2tzIGluIFJfLiAgQXMgSSBhbHJlYWR5IHVzZWQgTWFwYm94IHNlcnZpY2VzIGhlYXZpbHkgZm9yIG15IHZpc3VhbGl6YXRpb24gcHJvamVjdHMsIGl0IG1hZGUgc2Vuc2UgdG8gd3JpdGUgbWFwYm94YXBpIHRvIGNvbm5lY3QgdGhlc2Ugc2VydmljZXMgd2l0aCBteSBleGlzdGluZyBzZi1iYXNlZCBkYXRhIHNjaWVuY2Ugd29ya2Zsb3dzLiAgCgpJbiB0aGlzIHNlY3Rpb24gb2YgdGhlIHdvcmtzaG9wLCB3ZSdsbCBleHBsb3JlIHRocmVlIG1vcmUgYWR2YW5jZWQgYXBwbGljYXRpb25zIG9mIG1hcGJveGFwaSB3aXRoaW4gcHJhY3RpY2FsIHNwYXRpYWwgZGF0YSBzY2llbmNlIHdvcmtmbG93cy4gIFdlJ2xsIGV4YW1pbmUgaG93IHRvIHZpc3VhbGl6ZSBhY2Nlc3NpYmlsaXR5IHRvIGEgYmFsbG90IGRyb3Atb2ZmIGxvY2F0aW9uIGluIEhvdXN0b247IGlkZW50aWZ5IGFyZWFzIHdoZXJlIHBvcHVsYXRpb25zIG1heSBoYXZlIGRpZmZpY3VsdHkgcmVhY2hpbmcgZWFybHkgdm90aW5nIGxvY2F0aW9ucyBpbiBGb3J0IFdvcnRoOyBhbmQgYnVpbGQgYSByb3V0aW5nIGFwcCB3aXRoIFNoaW55IHRoYXQgaWRlbnRpZmllcyB0aGUgY2xvc2VzdCBwb2xsaW5nIHBsYWNlIHRvIGEgdXNlcidzIGFkZHJlc3MuICBUaGlzIHNlY3Rpb24gbWF5IGluY2x1ZGUgc29tZSBuZXcgY29uY2VwdHMgb3IgdGVjaG5pcXVlcyAtIGJ1dCBpdCBpcyBkZXNpZ25lZCB0byBpbGx1c3RyYXRlIHdoZXJlIHlvdSBjYW4gZ28gd2l0aCBtYXBib3hhcGkgaW4geW91ciB3b3JrIQoKCiMjIFZpc3VhbGl6aW5nIChpbilhY2Nlc3NpYmlsaXR5IHRvIGJhbGxvdCBkcm9wIGJveCBsb2NhdGlvbnMKClRoZSB0b29scyB3ZSd2ZSBsZWFybmVkIGhvdyB0byB1c2Ugd2l0aCBtYXBib3hhcGkgY2FuIGJlIHVzZWQgdG8gYW5hbHl6ZSByZWxhdGl2ZSBhY2Nlc3NpYmlsaXR5IC0gb3IgaW5hY2Nlc3NpYmlsaXR5IC0gdG8gcG9sbGluZyBvciBiYWxsb3QgZHJvcC1vZmYgbG9jYXRpb25zLiAgTGltaXRpbmcgYmFsbG90IGRyb3Atb2ZmIGxvY2F0aW9ucyBpbiBUZXhhcyBjb3VudGllcyBjcmVhdGVzIHNpZ25pZmljYW50IGFjY2Vzc2liaWxpdHkgaXNzdWVzIGZvciBUZXhhcyB2b3RlcnMuICBGb3IgZXhhbXBsZSwgSGFycmlzIENvdW50eSAoSG91c3Rvbikgd2lsbCBoYXZlIG9uZSBkcm9wLW9mZiBsb2NhdGlvbiBmb3IgaXRzIDQuNiBtaWxsaW9uIHJlc2lkZW50cywgd2hlcmVhcyBtYW55IG90aGVyIGNvdW50aWVzIGluIFRleGFzIGhhdmUgdGhlIHNhbWUgbnVtYmVyIG9mIGRyb3Atb2ZmIGxvY2F0aW9ucyBmb3IgcG9wdWxhdGlvbnMgc21hbGxlciB0aGFuIDEsMDAwLiAgCgpXZSBjYW4gdmlzdWFsaXplIHRoaXMgc2l0dWF0aW9uIGluIEhhcnJpcyBDb3VudHkgd2l0aCBfbGF5ZXJlZCBpc29jaHJvbmVzXy4gIFdlIGFscmVhZHkgdXNlZCB0aGlzIHRlY2huaXF1ZSB0byBzaG93IG11bHRpcGxlIGRyaXZlIHRpbWVzIGFyb3VuZCB0aGUgVW5pdmVyc2l0eSBvZiBQZW5uc3lsdmFuaWEgZWFybGllciBpbiB0aGlzIHR1dG9yaWFsLiAgSW4gdGhpcyBjYXNlLCB3ZSB3aWxsIHVzZSBgbWJfaXNvY2hyb25lKClgIHRvIGdlbmVyYXRlIGRvemVucyBvZiBpc29jaHJvbmVzLCB0aGVuIHZpc3VhbGl6ZSB0aGVtIHNpbXVsdGFuZW91c2x5IHRvIGlsbHVzdHJhdGUgYW4gYWNjZXNzaWJpbGl0eSBncmFkaWVudCBpbiB0aGUgcmVnaW9uLiAgCgpXZSdsbCBmaXJzdCBnZW5lcmF0ZSB0aGUgaXNvY2hyb25lcyB1c2luZyBhIHZlY3RvciBvZiB0aW1lcywgMSB0aHJvdWdoIDQ1IGF0IDEtbWludXRlIGludGVydmFscywgYXJvdW5kIE5SRyBBcmVuYSAodGhlIGJhbGxvdCBkcm9wLW9mZiBzaXRlKS4gICAKCmBgYHtyIGhvdXN0b24taXNvY2hyb25lc30KbGlicmFyeShtYXBib3hhcGkpCgppc29zIDwtIG1iX2lzb2Nocm9uZSgKICBsb2NhdGlvbiA9ICIxIE5SRyBQa3d5LCBIb3VzdG9uLCBUWCA3NzA1NCIsCiAgcHJvZmlsZSA9ICJkcml2aW5nIiwKICB0aW1lID0gMTo0NQopCmBgYAoKCk5leHQsIHdlIGNhbiB2aXN1YWxpemUgb3VyIG92ZXJsYXBwaW5nIGlzb2Nocm9uZXMuICBXZSdsbCB1c2UgdGhlIHZpcmlkaXMgY29sb3IgcGFsZXR0ZSBhcyB3ZSBkaWQgcHJldmlvdXNseSBpbiB0aGUgdHV0b3JpYWwsIGFuZCBnZW5lcmF0ZSBhIGNvbG9yIHBhbGV0dGUgZGVyaXZlZCBmcm9tIHRoZSBgdGltZWAgY29sdW1uIGluIG91ciBkYXRhc2V0LiAgT25jZSBzcGVjaWZpZWQsIHdlIGNhbiBhZGQgdGhlc2UgcG9seWdvbnMgdG8gb3VyIE1hcGJveCBiYXNlbWFwIHdpdGggYSBtb3N0bHktdHJhbnNwYXJlbnQgZmlsbCBvcGFjaXR5LiAgCgpgYGB7ciBsYXllcmVkLWlzb2Nocm9uZXN9CgpwYWwgPC0gY29sb3JOdW1lcmljKCJ2aXJpZGlzIiwgaXNvcyR0aW1lLCBuYS5jb2xvciA9ICJ0cmFuc3BhcmVudCIpCgptYXBib3hfbWFwICU+JQogIGFkZFBvbHlnb25zKGRhdGEgPSBpc29zLAogICAgICAgICAgICAgIGZpbGxDb2xvciA9IH5wYWwodGltZSksCiAgICAgICAgICAgICAgc3Ryb2tlID0gRkFMU0UsCiAgICAgICAgICAgICAgZmlsbE9wYWNpdHkgPSAwLjEpICU+JQogIGFkZExlZ2VuZCh2YWx1ZXMgPSBpc29zJHRpbWUsCiAgICAgICAgICAgIHBhbCA9IHBhbCwKICAgICAgICAgICAgdGl0bGUgPSAiRHJpdmUtdGltZSB0byBOUkcgQXJlbmEiKQoKYGBgCgoKVGhlIHJlc3VsdCBpbGx1c3RyYXRlcyBzb21lIG9mIHRoZSB3aWRlIGRpZmZlcmVuY2VzIGluIGFjY2Vzc2liaWxpdHkgYmV0d2VlbiB2YXJpb3VzIHBhcnRzIG9mIHRoZSByZWdpb24uICBPbmUgbm90YWJsZSBpc3N1ZSB3aXRoIHRoaXMgdmlzdWFsaXphdGlvbiBhcHByb2FjaCwgaG93ZXZlciwgaXMgdGhhdCB0aGUgbGF5ZXJpbmcgb2YgaXNvY2hyb25lcyBpbiB0aGUgaW50ZXJpb3Igb2YgSG91c3RvbiBtYWtlcyBpdCBkaWZmaWN1bHQgdG8gdmlldyB0aGUgYmFzZW1hcCBiZW5lYXRoIHRoZW0uICBUaGlzIGNhbiBiZSByZXNvbHZlZCBieSBjb252ZXJ0aW5nIHRvIGEgX3Jhc3RlciBkYXRhc2V0XyBhbmQgZ2VuZXJhdGluZyBhbiAiYWNjZXNzaWJpbGl0eSBzdXJmYWNlIiBmb3IgaW1wcm92ZWQgdmlzdWFsaXphdGlvbi4gIAoKIyMjIE1ha2luZyBhbiAiYWNjZXNzaWJpbGl0eSBzdXJmYWNlIgoKX0FjY2Vzc2liaWxpdHkgc3VyZmFjZXNfIGFyZSBjb21tb25seSB1c2VkIGluIGdlb2dyYXBoaWMgaW5mb3JtYXRpb24gc3lzdGVtcyBhcHBsaWNhdGlvbnMgdG8gaWRlbnRpZnkgdGhlIGRpc3RhbmNlIGZyb20gYW55IHBhcnRpY3VsYXIgbG9jYXRpb24gdG8gYSBnZW9ncmFwaGljIGZlYXR1cmUgb2YgaW50ZXJlc3QuICBXZSBjYW4gYXBwbHkgdGhpcyBjb25jZXB0IHRvIG5ldHdvcmstYmFzZWQgYWNjZXNzaWJpbGl0eSBieSB1c2luZyBtYXBib3hhcGkgdG9vbHMuICBUbyBjcmVhdGUgdGhlIGFjY2Vzc2liaWxpdHkgc3VyZmFjZSwgd2Ugd2lsbCBjb252ZXJ0IG91ciBpc29jaHJvbmVzIHRvIGEgcmFzdGVyIGRhdGFzZXQgdXNpbmcgdGhlIFtmYXN0ZXJpemUgcGFja2FnZV0oaHR0cHM6Ly9naXRodWIuY29tL2Vjb2hlYWx0aGFsbGlhbmNlL2Zhc3Rlcml6ZSkuICBSYXN0ZXIgZGF0YXNldHMgcmVwcmVzZW50IGdlb2dyYXBoaWMgaW5mb3JtYXRpb24gYXMgX2dyaWQgY2VsbHNfIGRlZmluZWQgYnkgYSBjZWxsIHNpemUuICBIaWdoZXItcmVzb2x1dGlvbiByYXN0ZXIgZGF0YXNldHMgYXJlIHJlcHJlc2VudGVkIHdpdGggc21hbGxlciBjZWxsIHNpemVzLiAgCgpUbyBnZW5lcmF0ZSB0aGUgYWNjZXNzaWJpbGl0eSBzdXJmYWNlIHJhc3Rlciwgd2Ugd2lsbCBuZWVkIHRvIGFwcGx5IGEgX2Nvb3JkaW5hdGUgc3lzdGVtIHRyYW5zZm9ybWF0aW9uXyB0byAicHJvamVjdCIgb3VyIGRhdGEgdG8gdHdvLWRpbWVuc2lvbmFsIGNvb3JkaW5hdGVzLiAgVGhpcyB3aWxsIGFsbG93IHVzIHRvIHNwZWNpZnkgdGhlIHJhc3RlcidzIHJlc29sdXRpb24gaW4gbWV0ZXJzLiAgV2UgZ2VuZXJhdGUgYSAxMDBtIHJlc29sdXRpb24gcmFzdGVyLCBhbmQgdXNlIHRoZSBgZmFzdGVyaXplKClgIGZ1bmN0aW9uIHRvIGFsbG9jYXRlIHRoZSBtaW5pbXVtIG92ZXJsYXBwaW5nIHZhbHVlIGZyb20gb3VyIGlzb2Nocm9uZXMgdG8gZWFjaCBncmlkIGNlbGwuICBUaGUgcmVzdWx0IGNhbiB0aGVuIGJlIG1hcHBlZCB3aXRoIExlYWZsZXQncyBgYWRkUmFzdGVySW1hZ2UoKWAgZnVuY3Rpb24uICAKCmBgYHtyIG1ha2Utc3VyZmFjZX0KbGlicmFyeShmYXN0ZXJpemUpCmxpYnJhcnkoc2YpCgppc29zX3Byb2ogPC0gc3RfdHJhbnNmb3JtKGlzb3MsIDMyNjE1KQoKdGVtcGxhdGUgPC0gcmFzdGVyKGlzb3NfcHJvaiwgcmVzb2x1dGlvbiA9IDEwMCkKCmlzb19zdXJmYWNlIDwtIGZhc3Rlcml6ZShpc29zX3Byb2osIHRlbXBsYXRlLCBmaWVsZCA9ICJ0aW1lIiwgZnVuID0gIm1pbiIpCgptYXBib3hfbWFwICU+JQogIGFkZFJhc3RlckltYWdlKGlzb19zdXJmYWNlLCBjb2xvcnMgPSBwYWwsIG9wYWNpdHkgPSAwLjUpICU+JQogIGFkZExlZ2VuZCh2YWx1ZXMgPSBpc29zJHRpbWUsIHBhbCA9IHBhbCwKICAgICAgICAgICAgdGl0bGUgPSAiRHJpdmUtdGltZSB0byBOUkcgQXJlbmEiKQoKYGBgCgpBY2Nlc3NpYmlsaXR5IGlzIG5vdyByZXByZXNlbnRlZCBpbiBhIHNpbWlsYXIgd2F5LCBidXQgd2l0aCBhIGNsZWFyZXIgdmlldyBvZiB0aGUgYmFzZW1hcCBhcm91bmQgTlJHIEFyZW5hLiAgCgoKIyMgSWRlbnRpZnlpbmcgcG9wdWxhdGlvbnMgd2hvIG1heSBoYXZlIGRpZmZpY3VsdHkgcmVhY2hpbmcgcG9sbGluZyBwbGFjZXMKClRoZSBwcmV2aW91cyBleGFtcGxlIGlsbHVzdHJhdGVkIGhvdyB0byBtb2RlbCBhbmQgdmlzdWFsaXplIGFjY2Vzc2liaWxpdHkgaW4gSG91c3RvbjsgaG93ZXZlciwgaXQgZG9lcyBub3Qgc3BlYWsgZGlyZWN0bHkgdG8gX3dob18gbWF5IGhhdmUgZGlmZmljdWx0aWVzIGRyb3BwaW5nIG9mZiB0aGVpciBiYWxsb3RzLiAgSG91c2Vob2xkcyB3aXRoIGFjY2VzcyB0byBjYXJzIHdpbGwgaGF2ZSBhIG11Y2ggZWFzaWVyIHRpbWUgcmVhY2hpbmcgTlJHIEFyZW5hIHRvIGRyb3Agb2ZmIHRoZWlyIGJhbGxvdHMsIGZvciBleGFtcGxlLCB0aGFuIHRob3NlIHdobyBuZWVkIHRvIHJlbHkgb24gb3RoZXIgbWV0aG9kcyBvZiB0cmFuc3BvcnRhdGlvbi4gIEl0IGFsc28gZG9lcyBub3QgaW50ZWdyYXRlIG90aGVyIHNwYXRpYWwgZGF0YSBzaG93aW5nIHRoZSBib3VuZGFyaWVzIG9mIEhhcnJpcyBDb3VudHkuICBJbiB0dXJuLCBhIGNsZWFyZXIgYW5hbHlzaXMgd291bGQgY3Jvc3MtcmVmZXJlbmNlIGFjY2Vzc2liaWxpdHkgZGF0YSB3aXRoIG90aGVyIGRhdGEgc291cmNlcyB1c2luZyBfc3BhdGlhbCBhbmFseXNpc18uICBGb3J0dW5hdGVseSwgYWxsIG9mIHRoaXMgY2FuIGJlIGNvbXBsZXRlZCB3aXRoaW4gUiEgIAoKT3VyIHRhc2sgaW4gdGhpcyBzZWN0aW9uIGlzIHRvIGZpbmQgbmVpZ2hib3Job29kcyB3aXRoIGxpbWl0ZWQgYWNjZXNzIHRvIGVhcmx5IHZvdGluZyBsb2NhdGlvbnMgaW4gRm9ydCBXb3J0aCwgVGV4YXMsIGFuZCBjcm9zcy1yZWZlcmVuY2UgdGhpcyB3aXRoIGRlbW9ncmFwaGljIGRhdGEgZnJvbSB0aGUgbW9zdCByZWNlbnQgQW1lcmljYW4gQ29tbXVuaXR5IFN1cnZleSwgdGhlIFVTIENlbnN1cyBCdXJlYXUncyBhbm51YWwgc29jaWFsIGFuZCBlY29ub21pYyBzdXJ2ZXkgb2YgVVMgaG91c2Vob2xkcy4gIFRvIGdldCBzdGFydGVkLCBsZXQncyBsb2FkIGluIHNvbWUgY29yZSBwYWNrYWdlcyBmb3Igc3BhdGlhbCBkYXRhIGFuYWx5c2lzLiAgV2UnbGwgYmUgdXNpbmcgdGhlIGZvbGxvd2luZyBSIHBhY2thZ2VzOgoKKiBbc2ZdKGh0dHBzOi8vci1zcGF0aWFsLmdpdGh1Yi5pby9zZi8pOiBzZiwgd2hpY2ggc3RhbmRzIGZvciBfc2ltcGxlIGZlYXR1cmVzXywgaGFzIGNlbWVudGVkIGl0c2VsZiBpbiB0aGUgbGFzdCBjb3VwbGUgeWVhcnMgYXMgdGhlIGNvcmUgcGFja2FnZSBmb3IgdmVjdG9yLWJhc2VkIHNwYXRpYWwgZGF0YSByZXByZXNlbnRhdGlvbiBhbmQgYW5hbHlzaXMgaW4gUi4gIFNwYXRpYWwgZGF0YSBhcmUgcmVwcmVzZW50ZWQgd2l0aCBzZiBtdWNoIGxpa2UgcmVndWxhciBSIGRhdGEgZnJhbWVzLCBidXQgd2l0aCBhIGxpc3QtY29sdW1uIHJlcHJlc2VudGluZyB0aGUgZ2VvbWV0cnkgb2YgZWFjaCByb3cuIAoqIFt0aWR5dmVyc2VdKGh0dHBzOi8vd3d3LnRpZHl2ZXJzZS5vcmcvKTogQSBjb2xsZWN0aW9uIG9mIHBvcHVsYXIgUiBwYWNrYWdlcyBtYWludGFpbmVkIGJ5IFtSU3R1ZGlvXShodHRwczovL3JzdHVkaW8uY29tLykgdGhhdCB3b3JrIHRvZ2V0aGVyIHRvIGZhY2lsaXRhdGUgZGF0YSByZXByZXNlbnRhdGlvbiwgd3JhbmdsaW5nLCBhbmQgdmlzdWFsaXphdGlvbi4gCiogW3RpZHljZW5zdXNdKGh0dHBzOi8vd2Fsa2VyLWRhdGEuY29tL3RpZHljZW5zdXMvKTogQW4gUiBwYWNrYWdlIGZvciBkb3dubG9hZGluZyBhbmQgd29ya2luZyB3aXRoIGRhdGEgZnJvbSB0aGUgVVMgQ2Vuc3VzIEJ1cmVhdSdzIGRlY2VubmlhbCBDZW5zdXMsIEFtZXJpY2FuIENvbW11bml0eSBTdXJ2ZXkgKGFnZ3JlZ2F0ZSBhbmQgbWljcm9kYXRhKSwgYW5kIFBvcHVsYXRpb24gRXN0aW1hdGVzIHByb2dyYW0uIEkgZmlyc3Qgd3JvdGUgdGhpcyBwYWNrYWdlIHRocmVlIHllYXJzIGFnbyBiZWNhdXNlIEkgZ3JldyB0aXJlZCBvZiB0aGUgdGVkaW91cyBwcm9jZXNzIG9mIGRvd25sb2FkaW5nIENlbnN1cyBkYXRhLCBjbGVhbmluZyBpdCwgYW5kIGpvaW5pbmcgdG8gc2hhcGVmaWxlcyB0byBkbyBzcGF0aWFsIGFuYWx5c2lzLiAgdGlkeWNlbnN1cyBkb2VzIGFsbCB0aGlzIGZvciB5b3UgaW50ZXJuYWxseSB3aXRoIHRoZSBhYmlsaXR5IHRvIHJldHVybiBDZW5zdXMgYW5kIEFDUyBkYXRhIGFzIHNpbXBsZSBmZWF0dXJlcyBvYmplY3RzIHJlYWR5IGZvciBtYXBwaW5nIGFuZCBhbmFseXNpcy4gIAoKVG8gZ2V0IHN0YXJ0ZWQsIHdlJ2xsIGxvYWQgdGhlIHJlcXVpcmVkIHBhY2thZ2VzIGZvciBhbmFseXNpcy4gV2UnbGwgYWxzbyBzZXQgdGhlIG9wdGlvbiBgdGlncmlzX3VzZV9jYWNoZSA9IFRSVUVgIHRvIGNhY2hlIGRvd25sb2FkZWQgc2hhcGVmaWxlcyAoc3BhdGlhbCBkYXRhKSBmcm9tIHRoZSBDZW5zdXMgd2Vic2l0ZTsgdGhpcyB3aWxsIHN0b3JlIHRoZW0gZm9yIGZ1dHVyZSB1c2UgYW5kIGd1YXJkIGFnYWluc3Qgb2NjYXNpb25hbCB3ZWJzaXRlIGRvd250aW1lLgoKYGBge3IgbG9hZC10aWR5Y2Vuc3VzfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeSh0aWR5Y2Vuc3VzKQpvcHRpb25zKHRpZ3Jpc191c2VfY2FjaGUgPSBUUlVFKQpgYGAKCkZvciB0aGlzIGFuYWx5c2lzLCB3ZSdsbCBiZSB1c2luZyBhIGRhdGFzZXQgb2YgW2Vhcmx5IHZvdGluZyBsb2NhdGlvbnMgZm9yIFRhcnJhbnQgQ291bnR5LCBUZXhhc10oaHR0cHM6Ly93d3cudGFycmFudGNvdW50eS5jb20vY29udGVudC9kYW0vbWFpbi9lbGVjdGlvbnMvMjAyMC8xMTIwL2xvY2F0aW9ucy8xMTIwX0VWX1NjaGVkLnBkZiksIHdoaWNoIHJlcHJlc2VudHMgdGhlIGFyZWFzIGFyb3VuZCBGb3J0IFdvcnRoIGFuZCBBcmxpbmd0b24uICBUaGVyZSBhcmUgNTAgc3VjaCBsb2NhdGlvbnMgYXJvdW5kIHRoZSBjb3VudHksIGFsbG93aW5nIHZvdGVycyB0byBjYXN0IHRoZWlyIGJhbGxvdHMgYmV0d2VlbiBPY3RvYmVyIDEzIGFuZCBPY3RvYmVyIDMwLiAgVGhpcyBpcyBhIGhlbHBmdWwgYWx0ZXJuYXRpdmUgZm9yIHZvdGVycyB3aG8gbWlnaHQgbm90IHdhbnQgdG8gKG9yIGNhbm5vdCkgdm90IG9uIEVsZWN0aW9uIERheSBvbiBOb3ZlbWJlciAzcmQuICAKCldlJ2xsIHJlYWQgaW4gYSBkYXRhc2V0IG9mIHRoZXNlIGVhcmx5IHZvdGluZyBzaXRlcyB0aGF0IEkndmUgYWxyZWFkeSBnZW9jb2RlZCBhbmQgY29udmVydGVkIHRvIGFuIHNmIFBPSU5UIG9iamVjdC4gIFRoaXMgZGF0YXNldCBjYW4gYmUgdXNlZCB0byBhbmFseXplIHdoaWNoIGFyZWFzIGFyZSBpbW1lZGlhdGVseSBjb3ZlcmVkIGJ5IGFjY2Vzc2libGUgZWFybHkgdm90aW5nIG9wdGlvbnMsIGFuZCB3aGljaCBhcmUgbm90LiAgV2UnbGwgbWVhc3VyZSBhY2Nlc3NpYmlsaXR5IHVzaW5nIGlzb2Nocm9uZXMgYXMgYWJvdmUsIGFuZCBjb25zaWRlciBhIDIwIG1pbnV0ZSB3YWxrLXRpbWUgYXJvdW5kIGVhY2ggcG9sbGluZyBsb2NhdGlvbi4gIGBtYl9pc29jaHJvbmUoKWAgY2FuIGFjY2VwdCBzZiBvYmplY3RzIGFzIGlucHV0LCBhbmQgd2lsbCByZXRhaW4gYW4gSUQgZnJvbSB0aGUgaW5wdXQgc2Ygb2JqZWN0IGlmIHRoZSBjb2x1bW4gbmFtZSBpcyBzcGVjaWZpZWQuICAKCmBgYHtyIHNpdGUtaXNvY2hyb25lc30KZXZfc2l0ZXMgPC0gcmVhZF9yZHMoImRhdGEvdGFycmFudF9FVl9zaXRlcy5yZHMiKQoKd2Fsa2luZ19pc29zIDwtIG1iX2lzb2Nocm9uZSgKICBldl9zaXRlcywKICBwcm9maWxlID0gIndhbGtpbmciLAogIHRpbWUgPSAyMCwKICBpZCA9ICJuYW1lIgopCgpgYGAKClRoZXNlIHJlc3VsdHMgY2FuIGJlIHZpc3VhbGl6ZWQgb24gb3VyIE1hcGJveCBtYXA6CgpgYGB7ciBtYXAtc2l0ZS1pc29jaHJvbmVzfQptYXBib3hfbWFwICU+JQogIGFkZFBvbHlnb25zKGRhdGEgPSB3YWxraW5nX2lzb3MsCiAgICAgICAgICAgICAgcG9wdXAgPSB+aWQpCmBgYAoKVGhlIG1hcCByZXByZXNlbnRzIHRoZSByZWFjaGFibGUgYXJlYSB3aXRoaW4gYSAyMC1taW51dGUgd2FsaywgbW9kZWxlZCBhdCBhbiBhdmVyYWdlIHdhbGtpbmcgc3BlZWQgZm9yIGFuIGFibGUtYm9kaWVkIGFkdWx0IChhYm91dCA1LjEga20vaG91cikuICBGb3IgaW5kaXZpZHVhbHMgd2l0aCBkaXNhYmlsaXRpZXMsIHRoZSBlbGRlcmx5LCBvciBob3VzZWhvbGRzIHdpdGhvdXQgYWNjZXNzIHRvIGEgY2FyLCBnZXR0aW5nIHRvIHRoZXNlIHBvbGxpbmcgc2l0ZXMgbWF5IHByb3ZlIGRpZmZpY3VsdCBpbiBhcmVhcyBvdXRzaWRlIHRoZXNlIGlzb2Nocm9uZXMuICBIb3dldmVyLCBhY2Nlc3NpYmlsaXR5IG1heSBiZSBsZXNzIG9mIGFuIGlzc3VlIGluIGFyZWFzIHdoZXJlIGNhciBvd25lcnNoaXAgaXMgd2lkZXNwcmVhZC4gIFdlIGNhbiBhbmFseXplIHRoaXMgYWRkaXRpb25hbCB2YXJpYWJsZSB3aXRoIGRlbW9ncmFwaGljIGRhdGEsIGFsc28gb2J0YWluZWQgd2l0aGluIFIuCgojIyMgT2J0YWluaW5nIGRlbW9ncmFwaGljIGRhdGEgd2l0aCB0aWR5Y2Vuc3VzCgpXZSdsbCBiZSB1c2luZyB0aWR5Y2Vuc3VzIHRvIHJlcXVlc3QgZGF0YSBmcm9tIHRoZSBVUyBDZW5zdXMgQnVyZWF1IEFQSSBhYm91dCB0aGUgcGVyY2VudGFnZSBvZiBob3VzZWhvbGRzIHdobyBkbyBub3QgaGF2ZSBhY2Nlc3MgdG8gYW4gYXV0b21vYmlsZS4gIEEgZnVsbCBkaXNjdXNzaW9uIG9mIGhvdyB0byB1c2UgdGlkeWNlbnN1cyBpcyBiZXlvbmQgdGhlIHNjb3BlIG9mIHRvZGF5J3MgdHV0b3JpYWwsIGJ1dCB5b3UnbGwgbGVhcm4gYSBmZXcgdGhpbmdzIGhlcmUuICBUbyB1c2UgdGlkeWNlbnN1cywgeW91IG11c3QgZmlyc3QgW29idGFpbiBhIENlbnN1cyBBUEkga2V5LCBhdmFpbGFibGUgYXQgdGhpcyBsaW5rXShodHRwczovL2FwaS5jZW5zdXMuZ292L2RhdGEva2V5X3NpZ251cC5odG1sKS4gIFRoZSBrZXkgd2lsbCBiZSBlbWFpbGVkIHRvIHlvdTsgb25jZSB5b3UgYWN0aXZhdGUgaXQsIHlvdSBjYW4gcGFzcyBpdCB0byB0aGUgYGNlbnN1c19hcGlfa2V5KClgIGZ1bmN0aW9uIHRvIHNldCBpdCAob3IgaW5zdGFsbCBpdCkgaW4geW91ciBlbnZpcm9ubWVudC4gIAoKV2UgY2FuIHRoZW4gcmVxdWVzdCBkYXRhIGZyb20gdGhlIEFtZXJpY2FuIENvbW11bml0eSBTdXJ2ZXkncyAyMDE0LTIwMTggNS15ZWFyIGRhdGFzZXQgd2l0aCB0aGUgYGdldF9hY3MoKWAgZnVuY3Rpb24uICBUaGUgdmFyaWFibGUgd2Ugd2FudCBpcyB0aGUgcGVyY2VudGFnZSBvZiBob3VzZWhvbGRzIHdpdGhvdXQgYWNjZXNzIHRvIGEgY2FyLCBkZXNpZ25hdGVkIHdpdGggdGhlIHZhcmlhYmxlIGNvZGUgYERQMDRfMDA1OFBgIGFuZCBhdmFpbGFibGUgaW4gdGhlIEFDUyBEYXRhIFByb2ZpbGUuICBbUGxlYXNlIHNlZSB0aGUgdGlkeWNlbnN1cyBkb2N1bWVudGF0aW9uIGZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IGlkZW50aWZ5aW5nIGFwcHJvcHJpYXRlIHZhcmlhYmxlIElEc10oaHR0cHM6Ly93YWxrZXItZGF0YS5jb20vdGlkeWNlbnN1cy9hcnRpY2xlcy9iYXNpYy11c2FnZS5odG1sI3NlYXJjaGluZy1mb3ItdmFyaWFibGVzLTEpLiAgV2UnbGwgcmVxdWVzdCB0aGlzIGRhdGEgZm9yIFRhcnJhbnQgQ291bnR5LCBUWCBhdCB0aGUgX19jZW5zdXMgdHJhY3RfXyBsZXZlbCwgd2hpY2ggaXMgdGhlIHNtYWxsZXN0IGF2YWlsYWJsZSBnZW9ncmFwaHkgYXZhaWxhYmxlIGZvciB0aGlzIGluZm9ybWF0aW9uLiAgVGhlIGFyZ3VtZW50IGBnZW9tZXRyeSA9IFRSVUVgIHVzZXMgdGhlIFt0aWdyaXMgcGFja2FnZV0oaHR0cHM6Ly9naXRodWIuY29tL3dhbGtlcmtlL3RpZ3JpcykgdG8gZG93bmxvYWQgc3BhdGlhbCBkYXRhIGZyb20gdGhlIENlbnN1cyB3ZWJzaXRlIGFuZCBqb2lucyBpdCBpbnRlcm5hbGx5IHRvIHRoZSBBQ1MgZGF0YSB5b3UndmUgYWNxdWlyZWQuCgpJZiB5b3UgZG9uJ3QgYWxyZWFkeSBoYXZlIGEga2V5IChvciBjYW5ub3QgZ2V0IG9uZSBhdCB0aGlzIHRpbWUpLCB1bi1jb21tZW50IHRoZSBhcHByb3ByaWF0ZSBsaW5lIGJlbG93IGFuZCByZWFkIGluIGEgc2F2ZWQgdmVyc2lvbiBvZiB0aGUgZGF0YXNldC4gIAoKYGBge3IgZ2V0LWRlbW9ncmFwaGljc30KIyBjZW5zdXNfYXBpX2tleSgieW91ciBrZXkgZ29lcyBoZXJlIiwgaW5zdGFsbCA9IFRSVUUpCiMgbm9fY2FycyA8LSByZWFkX3JkcygiZGF0YS9ub19jYXJzLnJkcyIpCgpub19jYXJzIDwtIGdldF9hY3MoCiAgZ2VvZ3JhcGh5ID0gInRyYWN0IiwKICB2YXJpYWJsZXMgPSAiRFAwNF8wMDU4UCIsCiAgc3RhdGUgPSAiVFgiLAogIGNvdW50eSA9ICJUYXJyYW50IiwKICBnZW9tZXRyeSA9IFRSVUUKKQpgYGAKCkxldCdzIHZpc3VhbGl6ZSB0aGlzIGluZm9ybWF0aW9uIG9uIG91ciBNYXBib3ggbWFwOgoKYGBge3Igdmlldy1kZW1vZ3JhcGhpY3N9CmRyaXZpbmdfcGFsIDwtIGNvbG9yTnVtZXJpYygidmlyaWRpcyIsIG5vX2NhcnMkZXN0aW1hdGUpCgptYXBib3hfbWFwICU+JQogIGFkZFBvbHlnb25zKGRhdGEgPSBub19jYXJzLAogICAgICAgICAgICAgIGZpbGxDb2xvciA9IH5kcml2aW5nX3BhbChlc3RpbWF0ZSksCiAgICAgICAgICAgICAgZmlsbE9wYWNpdHkgPSAwLjUsCiAgICAgICAgICAgICAgc3Ryb2tlID0gRkFMU0UsCiAgICAgICAgICAgICAgc21vb3RoRmFjdG9yID0gMC4xLAogICAgICAgICAgICAgIGxhYmVsID0gfnJvdW5kKGVzdGltYXRlLCAxKSkgJT4lCiAgYWRkTGVnZW5kKHZhbHVlcyA9IG5vX2NhcnMkZXN0aW1hdGUsCiAgICAgICAgICAgIHBhbCA9IGRyaXZpbmdfcGFsLAogICAgICAgICAgICB0aXRsZSA9ICIlIHdpdGhvdXQgYWNjZXNzPGJyLz50byBhdXRvbW9iaWxlIikKCmBgYAoKQXMgc2hvd24gdmlzdWFsbHkgaW4gdGhlIG1hcCwgYSBtYWpvcml0eSBvZiBob3VzZWhvbGRzIGluIGFsbCBUYXJyYW50IENvdW50eSBDZW5zdXMgdHJhY3RzIGhhdmUgYWNjZXNzIHRvIGFuIGF1dG9tb2JpbGUuICBIb3dldmVyLCB0aGVyZSBhcmUgc29tZSBDZW5zdXMgdHJhY3RzIHdoZXJlIHRoZSBwZXJjZW50YWdlIHdpdGhvdXQgYWNjZXNzIGV4Y2VlZHMgMTUgb3IgZXZlbiAyMCBwZXJjZW50LiAgVGhhdCBzYWlkLCBpZiB0aG9zZSB0cmFjdHMgYXJlIHdpdGhpbiBhIHJlYXNvbmFibGUgd2FsayBvZiBhIHBvbGxpbmcgbG9jYXRpb24sIGFjY2Vzc2liaWxpdHkgbWF5IG5vdCBiZSBhcyBsYXJnZSBvZiBhbiBpc3N1ZS4gIFdlIGNhbiBhbmFseXplIHRoaXMgdG9waWMgdXNpbmcgX3NwYXRpYWwgb3ZlcmxheV8uICAKCiMjIyBQZXJmb3JtaW5nIHNwYXRpYWwgYW5hbHlzaXMgd2l0aCBzZgoKU3BhdGlhbCBvdmVybGF5IGlzIGEgdmVyeSBjb21tb24gb3BlcmF0aW9uIHdoZW4gd29ya2luZyB3aXRoIHNwYXRpYWwgZGF0YS4gIEl0IGNhbiBiZSB1c2VkIHRvIGRldGVybWluZSB3aGljaCBmZWF0dXJlcyBpbiBvbmUgc3BhdGlhbCBsYXllciBvdmVybGFwIHdpdGggYW5vdGhlciBzcGF0aWFsIGxheWVyLCBvciBleHRyYWN0IGRhdGEgZnJvbSBhIGxheWVyIGJhc2VkIG9uIGdlb2dyYXBoaWMgaW5mb3JtYXRpb24uICBJbiBSLCBzcGF0aWFsIG92ZXJsYXkgY2FuIGJlIGludGVncmF0ZWQgZGlyZWN0bHkgaW50byB0aWR5dmVyc2Utc3R5bGUgZGF0YSBhbmFseXNpcyBwaXBlbGluZXMgdXNpbmcgZnVuY3Rpb25zIGluIHNmLiAgSW4gb3VyIGV4YW1wbGUsIHdlIHdhbnQgdG8gZGV0ZXJtaW5lIHRoZSBhcmVhcyBpbiBUYXJyYW50IENvdW50eSB3aXRoIHRoZSBncmVhdGVzdCBwcm9wb3J0aW9uIG9mIGhvdXNlaG9sZHMgd2l0aG91dCBhY2Nlc3MgdG8gYSBjYXIgX3RoYXQgYWxzb18gYXJlIGJleW9uZCBhIDIwIG1pbnV0ZSB3YWxrIGZyb20gYW4gZWFybHkgdm90aW5nIHBvbGxpbmcgc2l0ZS4gIAoKVG8gZG8gdGhpcywgd2UgdXNlIHRoaXMgZm9sbG93aW5nIHN0ZXBzOiAKCjEuIFdlIHRyYW5zZm9ybSB0aGUgY29vcmRpbmF0ZSByZWZlcmVuY2Ugc3lzdGVtIG9mIG91ciBgbm9fY2Fyc2AgZGF0YXNldCB0byA0MzI2LCB0aGUgc2FtZSBDUlMgdXNlZCBieSB0aGUgaXNvY2hyb25lczsKMi4gV2UgZXh0cmFjdCBvbmx5IHRob3NlIENlbnN1cyB0cmFjdHMgd2l0aCBhIHBlcmNlbnRhZ2Ugb2YgaG91c2Vob2xkcyB3aXRob3V0IGNhcnMgb2YgMTUgcGVyY2VudCBvciBhYm92ZTsKMy4gV2UgdXNlIHRoZSBgc3RfZGlmZmVyZW5jZSgpYCBmdW5jdGlvbiB0byAiY3V0IG91dCIgYXJlYXMgZnJvbSB0aG9zZSBDZW5zdXMgdHJhY3RzIHRoYXQgb3ZlcmxhcCB0aGUgMjAtbWludXRlIHdhbGtpbmcgaXNvY2hyb25lcy4KCk9uY2Ugd2UgY29tcGxldGUgdGhpcyBvcGVyYXRpb24sIHdlIGNhbiB2aXN1YWxpemUgdGhlIHJlc3VsdCBvbiBvdXIgTWFwYm94IG1hcC4gCgpgYGB7ciBzcGF0aWFsLW92ZXJsYXl9CnRhcmdldF9hcmVhcyA8LSBub19jYXJzICU+JQogIHN0X3RyYW5zZm9ybSg0MzI2KSAlPiUKICBmaWx0ZXIoZXN0aW1hdGUgPj0gMTUpICU+JQogIHN0X2RpZmZlcmVuY2UoCiAgICBzdF91bmlvbih3YWxraW5nX2lzb3MpCiAgKQoKCm1hcGJveF9tYXAgJT4lCiAgYWRkUG9seWdvbnMoZGF0YSA9IHRhcmdldF9hcmVhcykKYGBgCgpBcyB0aGUgbWFwIGlsbHVzdHJhdGVzLCB0aGVyZSBhcmUgc2V2ZXJhbCBhcmVhcyB3aXRoaW4gVGFycmFudCBDb3VudHkgdGhhdCBhcmUgbG9jYXRlZCBiZXlvbmQgYSAyMC1taW51dGUgd2FsayBmcm9tIGFuIGVhcmx5IHZvdGluZyBsb2NhdGlvbiBhbmQgaGF2ZSBwcm9wb3J0aW9uYWxseSBsb3dlciBhY2Nlc3MgdG8gYXV0b21vYmlsZXMuICBOb3RhYmxlIGNsdXN0ZXJzIG9mIG5laWdoYm9yaG9vZHMgdGhhdCBtZWV0IHRoaXMgY3JpdGVyaWEgYXJlIGxvY2F0ZWQgaW4gRm9ydCBXb3J0aCB0byB0aGUgc291dGggb2YgZG93bnRvd24gYW5kIG9uIHRoZSBjaXR5J3MgRWFzdCBTaWRlLiBHcmFudGVkLCB0aGlzIGFuYWx5c2lzIGlzIG5vdCBkZWZpbml0aXZlLCBidXQgZ2l2ZXMgdXMgc29tZSBpbnNpZ2h0cyBpbnRvIHBvdGVudGlhbCBpc3N1ZXMgd2l0aCB2b3RpbmcgYWNjZXNzaWJpbGl0eSBhbmQgaG93IHdlIG1pZ2h0IHJlc29sdmUgdGhlbS4gIAoKIyMgQnVpbGRpbmcgYSBwb2xsaW5nIHBsYWNlIGxvY2F0b3IgYXBwIHdpdGggU2hpbnkKCkFsbCBvZiB0aGlzIGluZm9ybWF0aW9uIGNhbiBiZSBwdXQgdG9nZXRoZXIgdG8gYnVpbGQgaW5mb3JtYXRpdmUgZGFzaGJvYXJkcyBmb3IgdGhlIHB1YmxpYyB1c2luZyBtYXBib3hhcGkgdG9vbHMuICBUaGUgYXBwbGljYXRpb24gc2hvd24gYmVsb3cgW2FuZCBhdmFpbGFibGUgYXQgdGhpcyBsaW5rXShodHRwczovL3dhbGtlcmtlLnNoaW55YXBwcy5pby9tYXBib3hfdm90aW5nX2xvY2F0b3IvKSB1c2VzIGBtYl9nZW9jb2RlKClgLCBgbWJfbWF0cml4KClgLCBhbmQgYG1iX2RpcmVjdGlvbnMoKWAgdG8gaWRlbnRpZnkgdGhlIGNsb3Nlc3QgZWFybHkgdm90aW5nIGxvY2F0aW9uIHRvIGEgdXNlci1zcGVjaWZpZWQgYWRkcmVzcyBpbiBUYXJyYW50IENvdW50eSwgY2FsY3VsYXRlcyB0aGUgZHJpdmluZyBkaXJlY3Rpb25zIHRvIHRoYXQgbG9jYXRpb24sIHRoZW4gdmlzdWFsaXplcyB0aGUgcm91dGUgYWxvbmcgd2l0aCBkcml2aW5nIGluc3RydWN0aW9ucyBvbiB0aGUgbWFwLiAgVGhlIGNvZGUgdXNlZCB0byBidWlsZCB0aGlzIGFwcCBpcyBhdmFpbGFibGUgaW4gdGhlIE1hc3RlcmNsYXNzIHJlcG9zaXRvcnk7IGBhcHBfbG9jYWwuUmAgaXMgdGhlIG1pbmltYWwgY29kZSB0byBnZXQgdGhlIGFwcCB3b3JraW5nIG9uIHlvdXIgY29tcHV0ZXIgKGFzc3VtaW5nIGEgTWFwYm94IGFjY2VzcyB0b2tlbiBoYXMgYmVlbiBpbnN0YWxsZWQpLCBhbmQgYGFwcC5SYCBpbmNsdWRlcyBhZGRpdGlvbmFsIGRldGFpbHMgbmVjZXNzYXJ5IHRvIGRlcGxveSB0aGUgYXBwIG9uIHRoZSBTaGlueUFwcHMuaW8gaG9zdGluZyBzZXJ2aWNlLiAgCgpXZSdsbCB0YWtlIGEgbG9vayByaWdodCBub3cgYXQgdGhlIFNoaW55IGFwcCBjb2RlOyB5b3UgY2FuIGFsc28gdmlldyBhIGxpdmUgdmVyc2lvbiBvZiB0aGUgYXBwIGVtYmVkZGVkIGJlbG93IQoKPGlmcmFtZSBzcmM9Imh0dHBzOi8vd2Fsa2Vya2Uuc2hpbnlhcHBzLmlvL21hcGJveF92b3RpbmdfbG9jYXRvci8iIHdpZHRoPSI2MDAiIGhlaWdodD0iODAwIiBmcmFtZUJvcmRlcj0iMCI+PC9pZnJhbWU+CgoKIyBCb251cyBleGVyY2lzZTogdXNpbmcgYSBjdXN0b20gTWFwYm94IGJhc2VtYXAgaW4gUgoKV2UndmUganVzdCBzY3JhdGNoZWQgdGhlIHN1cmZhY2Ugb2Ygd2hhdCB5b3UgY2FuIGRvIHdpdGggTWFwYm94IHRvb2xzIGluIFIuICBXaGlsZSBtYXBib3hhcGkgZG9lcyBub3QgZG8gbWFwIGdlbmVyYXRpb24gZGlyZWN0bHksIHRoZXJlIGFyZSBvcHRpb25zIGF2YWlsYWJsZSBmb3IgeW91LiBGb3IgbW9yZSBhZHZhbmNlZCAoYW5kIGZhc3QhKSB2aXN1YWxpemF0aW9uIHVzaW5nIE1hcGJveCwgSSBzdHJvbmdseSByZWNvbW1lbmQgY2hlY2tpbmcgb3V0IHRoZSBbbWFwZGVjayBwYWNrYWdlXShodHRwczovL2dpdGh1Yi5jb20vU3ltYm9saXhBVS9tYXBkZWNrKS4gIFRoaXMgcGFja2FnZSBpcyBhbiBpbnRlcmZhY2UgdG8gW1ViZXIncyBkZWNrLmdsXShodHRwczovL2RlY2suZ2wvKSBsaWJyYXJ5LCB3aGljaCBpcyBidWlsdCBvbiBNYXBib3ggdG9vbHMuICAKCkFub3RoZXIgb3B0aW9uIGZvciB2aXN1YWxpemF0aW9uIGlzIHRvIGJ1aWxkIHlvdXIgb3duIGN1c3RvbSBtYXBzIHVzaW5nIFtNYXBib3ggU3R1ZGlvXShodHRwczovL3d3dy5tYXBib3guY29tL21hcGJveC1zdHVkaW8vKSwgTWFwYm94J3MgaW50ZXJhY3RpdmUgd2ViLWJhc2VkIHRvb2wgZm9yIGNhcnRvZ3JhcGhpYyBkZXNpZ24uICBTdHVkaW8gYWxsb3dzIHlvdSB0byBjdXN0b21pemUgZXZlcnkgYXNwZWN0IG9mIHRoZWlyIHZlY3RvciB0aWxlcyBmb3Igd2ViIG1hcHBpbmcsIG1ha2luZyBiYXNlbWFwcyB0aGF0IGFyZSBleGFjdGx5IHRvIHlvdXIgc3BlY2lmaWNhdGlvbi4gRm9yIGNvbXByZWhlbnNpdmUgdHV0b3JpYWxzIG9uIGhvdyB0byB3b3JrIHdpdGggTWFwYm94IFN0dWRpbywgW2NoZWNrIG91dCB0aGVpciB0dXRvcmlhbHNdKGh0dHBzOi8vZG9jcy5tYXBib3guY29tL2hlbHAvdHV0b3JpYWxzLykuICBIZXJlLCBJJ2xsIGp1c3Qgc2hvdyB5b3UgaG93IHRvIG1ha2UgYSBjdXN0b20gYmFzZW1hcCB2ZXJ5IHF1aWNrbHkgYW5kIHVzZSBpdCBpbiB5b3VyIFIgcHJvamVjdHMuICAKCk1hcGJveCBoYXMgY3JlYXRlZCBhIGZ1biB0b29sIGNhbGxlZCBbQ2FydG9ncmFtXShodHRwczovL2FwcHMubWFwYm94LmNvbS9jYXJ0b2dyYW0vIzEzLjAxLzQwLjcyNTEvLTc0LjAwNTEpIHRoYXQgYWxsb3dzIHlvdSB0byB1cGxvYWQgYW4gaW1hZ2Ugb2YgeW91ciBjaG9pY2UsIHdoaWNoIHdpbGwgYmUgdXNlZCB0byBjcmVhdGUgYSBjdXN0b20gbWFwIHN0eWxlIGJhc2VkIG9uIHRoYXQgaW1hZ2UuICBWaXNpdCBodHRwczovL2FwcHMubWFwYm94LmNvbS9jYXJ0b2dyYW0gYW5kIHVwbG9hZCBhbiBpbWFnZSBvZiB5b3VyIGNob2ljZSEgSSdtIHVzaW5nIFtQZW5uJ3MgYXRobGV0aWNzIGxvZ29dKGh0dHBzOi8vcGVubmF0aGxldGljcy5jb20vaW5kZXguYXNweCksIHRob3VnaCB5b3UgY2FuIHVzZSB3aGF0ZXZlciB5b3UnZCBsaWtlLiAgSWYgeW91IGFyZSBzaWduZWQgaW50byB5b3VyIE1hcGJveCBhY2NvdW50ICh3aGljaCB5b3Ugc2hvdWxkIGJlIGZyb20gZWFybGllciBpbiB0aGlzIHR1dG9yaWFsKSwgdGhlIHN0eWxlIHdpbGwgc2F2ZSBhdXRvbWF0aWNhbGx5IHRvIHlvdXIgYWNjb3VudC4gIAoKIVtdKGltZy9wZW5uX2NhcnRvZ3JhbS5wbmcpCkNsaWNrIHRoZSAiU2F2ZWQgc3R5bGUhIiBidXR0b24gYXQgdGhlIHRvcCBvZiB0aGUgc2NyZWVuLCBhbmQgeW91J2xsIGJlIHRyYW5zcG9ydGVkIHRvIHRoZSBNYXBib3ggU3R1ZGlvIGVkaXRvciB3aXRoIHlvdXIgY3VzdG9tIENhcnRvZ3JhbSBzdHlsZSBsb2FkZWQuICBUaGVyZSBpcyBtdWNoIHlvdSBjYW4gZG8gaGVyZSAtIGJ1dCBmb3Igbm93LCBjbGljayB0aGUgIlNoYXJlIiBidXR0b24gaW4gdGhlIHVwcGVyIHJpZ2h0IG9mIHlvdXIgc2NyZWVuIHRvIGRpc3BsYXkgdGhlICJTaGFyZSBhbmQgRGV2ZWxvcCIgb3B0aW9ucy4gIAoKIVtdKGltZy9zaGFyZV9hbmRfZGV2ZWxvcC5wbmcpCgpDb3B5IHRoZSAiU3R5bGUgVVJMIiBhbmQgcGFzdGUgaXQgaW4geW91ciBSIE1hcmtkb3duIHNvIHlvdSBjYW4gc2VlIGl0OyBtaW5lIGhlcmUgaXMgYG1hcGJveDovL3N0eWxlcy91cGVubi1tYXN0ZXJjbGFzcy1kZW1vMS9ja2Z6b3JkdjExaGE1MTluejNxdzF2N254YC4gIEFmdGVyIGBtYXBib3g6Ly9zdHlsZXMvYCwgeW91J2xsIHNlZSB5b3VyIF91c2VybmFtZV8gYW5kIF9zdHlsZSBJRF8uICBZb3UgbWF5IHJlY2FsbCB0aGUgYmVnaW5uaW5nIG9mIHRoZSB3b3Jrc2hvcCB3aGVuIHdlIHVzZWQgdGhlIE1hcGJveCBTdHJlZXRzIHN0eWxlIGFzIGEgdGVtcGxhdGUgZm9yIG91ciBSIExlYWZsZXQgbWFwcy4gIFlvdSBjYW4gdXNlIHRoaXMgY3VzdG9tIHN0eWxlIGluIG11Y2ggdGhlIHNhbWUgd2F5IHdpdGggYGFkZE1hcGJveFRpbGVzKClgOgoKCmBgYHtyIHNob3ctY2FydG9ncmFtfQpsZWFmbGV0KCkgJT4lCiAgYWRkTWFwYm94VGlsZXMoc3R5bGVfaWQgPSAiY2tmem9yZHYxMWhhNTE5bnozcXcxdjdueCIsCiAgICAgICAgICAgICAgICAgdXNlcm5hbWUgPSAidXBlbm4tbWFzdGVyY2xhc3MtZGVtbzEiKSAlPiUKICBzZXRWaWV3KGxuZyA9IHBlbm5bMV0sCiAgICAgICAgICBsYXQgPSBwZW5uWzJdLAogICAgICAgICAgem9vbSA9IDE0KQpgYGAKClRoYW5rcyBmb3IgcGFydGljaXBhdGluZyB0b2RheSEgIElmIHlvdSBoYXZlIG1vcmUgcXVlc3Rpb25zIGFib3V0IG1hcGJveGFwaSBvciBhbnkgb2YgbXkgb3RoZXIgcGFja2FnZXMsIGZlZWwgZnJlZSB0byBnZXQgaW4gdG91Y2guICBBbHNvLCBiZSBzdXJlIHRvIHNoYXJlIGFueXRoaW5nIHlvdSd2ZSBjcmVhdGVkIGJhc2VkIG9uIHdoYXQgeW91J3ZlIGxlYXJuZWQgdG9kYXkgb24gVHdpdHRlciB3aXRoIHRoZSAjcnN0YXRzIGFuZCAjTXVzYU1hc3RlckNsYXNzIGhhc2h0YWdzLiAgCgpgYGB7Y3NzLCBlY2hvID0gRkFMU0V9CgpoMSwgaDIsIGgzIHsKICBjb2xvcjogIzAzNTAwNDsgCiAgZm9udC1mYW1pbHk6ICJWZXJkYW5hIgp9Cgpib2R5IHsKICBmb250LWZhbWlseTogIlZlcmRhbmEiCn0KCmEgewogIGNvbG9yOiAjMWE3MzBmOyAKfQoKLmludmVyc2UgewogIGJhY2tncm91bmQtY29sb3I6ICMwMzUwMDQ7IAoKfQoKLmxpc3QtZ3JvdXAtaXRlbS5hY3RpdmUsIC5saXN0LWdyb3VwLWl0ZW0uYWN0aXZlOmZvY3VzLCAubGlzdC1ncm91cC1pdGVtLmFjdGl2ZTpob3ZlciB7CiAgICB6LWluZGV4OiAyOwogICAgY29sb3I6ICNmZmY7CiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjMDM1MDA0OwogICAgYm9yZGVyLWNvbG9yOiAjMDM1MDA0Owp9CmBgYAo=