1 Introduction

This Tutorial works through the ideas at Leaflet

Leaflet is a JavaScript library for creating dynamic maps that support panning and zooming along with various annotations like markers, polygons, and popups.

In this tutorial we will work only with vector data. In a second part, we will work with raster data in leaflet.

library(tidyverse)
## ── Attaching packages ───────────────────────────────────────────────────────────────────────────────────────── tidyverse 1.3.1 ──
## ✔ ggplot2 3.3.6     ✔ purrr   0.3.4
## ✔ tibble  3.1.7     ✔ dplyr   1.0.9
## ✔ tidyr   1.2.0     ✔ stringr 1.4.0
## ✔ readr   2.1.2     ✔ forcats 0.5.1
## ── Conflicts ──────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
library(leaflet)
library(maps)
## 
## Attaching package: 'maps'
## The following object is masked from 'package:purrr':
## 
##     map
library(sp)
library(sf)
## Linking to GEOS 3.9.1, GDAL 3.3.2, PROJ 7.2.1; sf_use_s2() is TRUE
# Data
library(osmdata) # Import OSM Vector Data into R
## Data (c) OpenStreetMap contributors, ODbL 1.0. https://www.openstreetmap.org/copyright
library(osmplotr) # Creating maps with OSM data in R
## Data (c) OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright
# library(OpenStreetMap) # Raster Data

2 Basic Features of a leaflet Map

# Set value for the minZoom and maxZoom settings.
#leaflet(options = leafletOptions(minZoom = 0, maxZoom = 18))

m <- leaflet() %>%
  
  # Add default OpenStreetMap map tiles
  addTiles() %>% 
  
  # Set view to be roughly centred on Bangalore City
  setView(lng = 77.580643, lat = 12.972442, zoom = 12)

m
# Click on the map to zoom in; Shift+Click to zoom out

leaflet by default uses Open Street Map as its base map. We can use other base maps too, as we will see later.

2.1 Add Shapes to a Map

leaflet offers several commands to add points, markers, icons, lines, polylines and polygons to a map. Let us examine a few of these.

2.1.1 Add Markers with popups

m %>% addMarkers(lng = 77.580643, lat = 12.972442, 
                 popup = "The birthplace of Rvind")
# Click on the Marker for the popup to appear

This uses the default pin shape as the Marker.

2.1.2 Adding Popups to a Map

Popups are small boxes containing arbitrary HTML, that point to a specific point on the map. Use the addPopups() function to add standalone popup to the map.

m %>%
  addPopups(
    lng = 77.580643,
    lat = 12.972442,
    popup = paste(
      "The birthplace of Rvind",
      "<br>",
      "Website: https://the-foundation-series.netlify.app",
      "<br>"
    ),
    
    # Ensuring we cannot close the popup, else we will not be able to find where it is, since there is no Marker
    options = popupOptions(closeButton = FALSE)
  )

Popups are usually added to icons, Markers and other shapes can show up when these are clicked.

2.1.3 Adding Labels to a Map

Labels are messages attached to all shapes, using the argument label wherever it is available.

Labels are static, and Popups are usually visible on mouse click. Hence a Marker can have both a label and a popup. For example, the function addPopup() offers only a popup argument, whereas the function addMarkers() offers both a popup and a label argument.

It is also possible to create labels standalone using addLabelOnlyMarkers() where we can show only text and no Markers.

m %>%
  addMarkers(
    lng = 77.580643,
    lat = 12.972442,
    
    # Here is the Label defn.
    label = "The birthplace of Rvind",
    labelOptions = labelOptions(noHide = TRUE, # Label always visible
                                textOnly = F, 
                                textsize = 20),
    
    # And here is the popup defn.
    popup = "This is the Popup Text"
  )

2.1.4 Adding Circles and CircleMarkers on a Map

We can add shapes on to a map to depict areas or locations of interest. NOTE: the radius argument works differently in addCircles() and addCircleMarkers().

# Some Cities in the US and their location
md_cities <- tibble(
  name = c("Baltimore","Frederick","Rockville","Gaithersburg","Bowie","Hagerstown","Annapolis","College Park","Salisbury","Laurel"),
  pop = c(619493,66169,62334,61045,55232,39890,38880,30587,30484,25346),
  lat = c(39.2920592,39.4143921,39.0840,39.1434,39.0068,39.6418,38.9784,38.9897,38.3607,39.0993), 
  lng = c(-76.6077852,-77.4204875,-77.1528,-77.2014,-76.7791,-77.7200,-76.4922,-76.9378,-75.5994,-76.8483)
)


md_cities %>%
  leaflet() %>%
  addTiles() %>%
  
  # CircleMarkers, in blue
  # radius scales the Marker. Units are in Pixels!!
  # Here, radius is made proportional to `pop` number
  addCircleMarkers(radius = ~ pop/1000, # Pixels!!
                   color = "blue",
                   stroke = FALSE, # no border for the Markers
                   opacity = 0.8) %>% 
  
  
  # Circles, in red
  addCircles(
    radius = 5000, # Meters !!!
    stroke = TRUE,
    color = "yellow", # Stroke Colour
    weight = 3, # Stroke Weight
    fill = TRUE,
    fillColor = "red",

  )
## Assuming "lng" and "lat" are longitude and latitude, respectively
## Assuming "lng" and "lat" are longitude and latitude, respectively

The shapes need not be of fixed size or colour; their attributes can be made to correspond to other attribute variables in the geospatial data, as we did with radius in the addCircleMarkers() function above.

2.1.5 Adding Rectangles to a Map

## Adding Rectangles
leaflet() %>%
  addTiles() %>%
  setView(lng = 77.580643, lat = 12.972442, zoom = 6) %>% 
  addRectangles(lat1 = 10.3858, lng1 = 75.0595, 
                lat2 = 12.8890, lng2 = 77.9625)

2.1.6 Add Polygons to a Map

## Adding Polygons
leaflet() %>%
  addTiles() %>%
  setView(lng = 77.580643, lat = 12.972442, zoom = 6) %>% 
  
  # arbitrary vector data for lat and lng
   addPolygons(lng = c(73.5, 75.9, 76.1, 77.23, 79.8),
               lat =c(10.12, 11.04, 11.87, 12.04, 10.7))

2.1.7 Add PolyLines to a Map

This can be useful say for manually marking a route on a map, with waypoints.

leaflet() %>%
  addTiles() %>%
  setView(lng = 77.580643, lat = 12.972442, zoom = 6) %>% 
  
  # arbitrary vector data for lat and lng
  # If start and end points are the same, it looks like Polygon
  # Without the fill
   addPolylines(lng = c(73.5, 75.9, 76.1, 77.23, 79.8),
               lat =c(10.12, 11.04, 11.87, 12.04, 10.7)) %>% 
  
  # Add Waypoint Icons
  addMarkers(lng = c(73.5, 75.9, 76.1, 77.23, 79.8),
               lat =c(10.12, 11.04, 11.87, 12.04, 10.7))

As seen, we have created Markers, Labels, Polygons, and PolyLines using fixed.i.e. literal text and numbers. In the following we will also see how external geospatial data columns can be used instead of these literals.

NOTE: The mapedit package https://r-spatial.org//r/2017/01/30/mapedit_intro.html can also be used to interactively add shapes onto a map and save as an geo-spatial object.

3 Using leaflet with external geospatial data

On to something more complex. We want to plot a known set of locations on a leaflet map. leaflet takes in geographical data in many ways and we will explore most of them.

3.1 Point Data Sources for leaflet

Point data for markers can come from a variety of sources:

  • SpatialPoints or SpatialPointsDataFrame objects (from the sp package)
  • POINT, sfc_POINT, and sf objects (from the sf package); only X and Y dimensions will be considered
  • Two-column numeric matrices (first column is longitude, second is latitude)
  • Data frame/tibble with latitude and longitude columns. You can explicitly tell the marker function which columns contain the coordinate data (e.g. addMarkers(lng = ~Longitude, lat = ~Latitude)), or let the function look for columns named lat/latitude and lon/lng/long/longitude (case insensitive).
  • Simply provide numeric vectors as lng and lat arguments, which we have covered already in the preceding sections.

Note that MULTIPOINT objects from sf are not supported at this time.

We will not consider the use of sp related data structures for plotting POINTs in leaflet since sp is being phased out in favour of the more modern package sf.

3.1.1 Points using simple Data Frames

Let us read in the data set from data.world that gives us POINT locations of all airports in India in a data frame / tibble. The dataset is available at https://query.data.world/s/ahtyvnm2ybylf65syp4rsb5tulxe6a. You can either download it, save a copy, and read it in as usual, or use the URL itself to read it in from the web. In the latter case, you will need the package data.world and also need to register your credentials for that page with RStudio. The (simple!) instructions are available here at data.world.

#library(devtools)
#devtools::install_github("datadotworld/data.world-r", build_vignettes = TRUE)

library(data.world)
## Loading required package: dwapi
## 
## Attaching package: 'dwapi'
## The following object is masked from 'package:dplyr':
## 
##     sql
india_airports <-
  read_csv("https://query.data.world/s/ahtyvnm2ybylf65syp4rsb5tulxe6a") %>% 
  slice(-1) %>% # Drop the first row which contains labels
  dplyr::mutate(
    id = as.integer(id),
    latitude_deg = as.numeric(latitude_deg),
    longitude_deg = as.numeric(longitude_deg),
    elevation_ft = as.integer(elevation_ft)
  ) %>% 
  rename("lon" = longitude_deg, "lat" = latitude_deg) %>% 
  # Remove four locations which seem to be in the African Atlantic
  filter(!id %in% c(330834, 330867, 325010, 331083))
## Rows: 345 Columns: 20
## ── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────
## Delimiter: ","
## chr (20): id, ident, type, name, latitude_deg, longitude_deg, elevation_ft, continent, iso_country, iso_region, municipality, ...
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
india_airports %>% head()

Let us plot this in leaflet, using an ESRI National Geographic style map instead of the OSM Base Map. We will also place small circle markers for each airport.

leaflet(data = india_airports) %>% 
  setView(lat = 18, lng = 77, zoom = 4) %>% 
  
  # Add NatGeo style base map
  addProviderTiles(providers$Esri.NatGeoWorldMap) %>% # ESRI Basemap
  
  # Add Markers for each airport
  addCircleMarkers(lng = ~lon, lat = ~lat,
                   # Optional, variables stated for clarity
                   # leaflet can automatically detect lon-lat columns
                   # if they are appropriately named in the data
                   # longitude/lon/lng
                   # latitude/lat
                   radius = 2, # Pixels
                   color = "red",
                   opacity = 1)

We can also change the icon for each airport. Let us try one of theseveral icon families that we can use with leaflet : glyphicons, ionicons, and fontawesome icons.

# Define popup message for each airport
# Based on data in india_airports
popup <- paste(
  "<strong>",
  india_airports$name,
  "</strong><br>",
  india_airports$iata_code,
  "<br>",
  india_airports$municipality,
  "<br>",
  "Elevation(feet)",
  india_airports$elevation_ft,
  "<br>",
  india_airports$wikipedia_link,
  "<br>"
)

iata_icon <- makeIcon(
  "iata-logo-transp.png", # Downloaded from www.iata.org
  iconWidth = 24,
  iconHeight = 24,
  iconAnchorX = 0,
  iconAnchorY = 0
)

# Create the Leaflet map
leaflet(data = india_airports) %>%
  setView(lat = 18, lng = 77, zoom = 4) %>%
  addProviderTiles(providers$Esri.NatGeoWorldMap) %>%
  addMarkers(
    icon = iata_icon,
    popup = popup
  )
## Assuming "lon" and "lat" are longitude and latitude, respectively

There are other icons we can use to mark the POINTs. leaflet allows the use of ionicons, glyphicons, and FontAwesomeIcons

It is possible to create a list of icons, so that different Markers can have different icons. Let us try to map the MNCs in the ITPL area of Bangalore: we use the ideas in Using Leaflet Markers @JLA-Data.net

# Make a dataframe of addresses of Companies we wan to plot in ITPL
companies_itpl <-
  data.frame(
    ticker = c(
      "MBRDI",
      "DTICI",
      "IBM",
      "Exxon",
      "Mindtree",
      "FIS Global",
      "Sasken",
      "LTI"),
    lat = c(
      12.986178620989264,
      12.984160906190121,
      12.983659088566357,
      12.985112265986636,
      12.983794997606187,
      12.980658616215155,
      12.982080447350246,
      12.981338168875348),
    lon = c(
      77.7270652183105,
      77.72808445774321,
      77.73103488768001,
      77.72935046040699,
      77.7227844126931,
      77.72685064158782,
      77.72545589289041,
      77.72287024338216)
  ) %>% sf::st_as_sf(coords = c("lon", "lat"), crs = 4326)
 
# Vanilla leaflet map
leaflet(companies_itpl) %>% 
  addTiles() %>% 
  addMarkers()

Let us make a list of logos of the Companies and use them as markers!

# a named list of rescaled icons with links to images
favicons <- iconList(
  "MBRDI" = makeIcon(
    iconUrl = "https://www.mercedes-benz.com/etc/designs/brandhub/frontend/static-assets/header/logo.svg", 
    iconWidth = 25,
    iconHeight = 25
  ),
  "DTICI" = makeIcon(
    iconUrl = "https://media-exp1.licdn.com/dms/image/C4D0BAQGzOep26lC03w/company-logo_200_200/0/1638298367374?e=2147483647&v=beta&t=mPyF4gvNhNFvd-tedbqNzJofq4q9qcw6A9z9jQeLAwc",
    iconWidth = 45,
    iconHeight = 45
  ),
  "IBM" = makeIcon(
    iconUrl = "https://www.ibm.com/favicon.ico",
    iconWidth = 25,
    iconHeight = 25
  ),
  "Exxon" = makeIcon(
    iconUrl = "https://corporate.exxonmobil.com/-/media/Global/Icons/logos/ExxonMobilLogoColor2x.png",
    iconWidth = 45,
    iconHeight = 25
  ),
  "Mindtree" = makeIcon(
    iconUrl = "https://www.mindtree.com/themes/custom/mindtree_theme/mindtree-lnt-logo-png.png",
    iconWidth = 75,
    iconHeight = 25
  ),
  "FIS Global" = makeIcon(
    iconUrl = "https://1000logos.net/wp-content/uploads/2021/09/FIS-Logo-768x432.png",
    iconWidth = 25,
    iconHeight = 25
  ),
  "Sasken" = makeIcon(
    iconUrl = "https://www.sasken.com/sites/all/themes/sasken_website/logo.png",
    iconWidth = 35,
    iconHeight = 35,
  ),
  "LTI" = makeIcon(
    iconUrl = "https://www.lntinfotech.com/wp-content/uploads/2021/09/LTI-logo.svg",
    iconWidth = 25,
    iconHeight = 25
  )
)


# Create the Leaflet map

leaflet(companies_itpl) %>% 
  addMarkers(icon = ~ favicons[ticker], # lookup based on ticker
             label = ~ companies_itpl$ticker,
             labelOptions = labelOptions(noHide = F,offset = c(15,-25))) %>%
  addProviderTiles("CartoDB.Positron")

3.1.2 Points using sf objects

We will use data from an sf data object. This differs from the earlier situation where we had a simple data frame with lon and lat columns. In sf, the lon and lat info is embedded in the geometry column of the sf data frame.

The tmap package has a data set of all World metro cities, titled metro. We will plot these on the map and also scale the markers in proportion to one of the feature attributes, pop2030. The popup will be the name of the metro city. We will also use the CartoDB.Positron base map.

Note that the metro data set has a POINT geometry, as needed!

data(metro, package = "tmap")
metro
leaflet(data = metro) %>% 
  setView(lat = 18, lng = 77, zoom = 4) %>% 
  
  # Add CartoDB.Positron
  addProviderTiles(providers$CartoDB.Positron) %>% # CartoDB Basemap
  
  # Add Markers for each airport
  addCircleMarkers(radius = ~ sqrt(pop2030)/350,
                   color = "red",
                   popup = paste("Name: ", metro$name, "<br>",
                                  "Population 2030: ", metro$pop2030))

We can also try downloading an sf data frame with POINT geometry from say OSM data<https://osm. Let us get hold of restaurants data in Malleswaram, Bangalore from OSM data:

bbox<- osmdata::getbb("Malleswaram, Bengaluru")
bbox
##        min      max
## x 77.55033 77.59033
## y 12.98274 13.02274
locations <- osmplotr::extract_osm_objects(
  bbox = bbox,
  key = "amenity",
  value = "restaurant",
  return_type = "point") 
## Issuing query to Overpass API ...
## Announced endpoint: z.overpass-api.de/api/
## Query complete!
## converting OSM data to sf format
locations <- locations %>% 
  dplyr::filter(cuisine == "indian")
locations %>% head()
# Fontawesome icons seem to work in `leaflet` only up to FontAwesome V4.7.0.
# The Fontawesome V4.7.0 Cheatsheet is here: <https://fontawesome.com/v4/cheatsheet/>


leaflet(data = locations, options = leafletOptions(minZoom = 12)) %>% 
  
  addProviderTiles(providers$CartoDB.Voyager) %>% 
  
  # Regular `leaflet` code
  addAwesomeMarkers(icon = awesomeIcons(icon = "fa-coffee", 
                                        library = "fa",
                                        markerColor = "blue",
                                        iconColor = "black",
                                        iconRotate = TRUE),
                     popup = paste("Name: ", locations$name,"<br>",
                           "Food: ", locations$cuisine)) 

Fontawesome Workaround

For more later versions of Fontawesome, here below is a workaround from https://github.com/rstudio/leaflet/issues/691. Despite this some fontawesome icons simply do not seem to show up. ;-()

library(fontawesome)
coffee <- makeAwesomeIcon(
  text = fa("mug-hot"), # mug-hot was introduced in fa version 5
  iconColor = "black",
  markerColor = "blue",
  library = "fa"
)


leaflet(data = locations) %>% 
  addProviderTiles(providers$CartoDB.Voyager) %>% 
  
  # Workaround code

  addAwesomeMarkers(icon = coffee,
             popup = paste("Name: ", locations$name,"<br>",
                           "Food: ", locations$cuisine, "<br>"))

Note that leaflet automatically detects the lon/lat columns from within the POINT geometry column of the sf data frame.

3.1.3 Points using Two-Column Matrices

We can now quickly try providing lon and lat info in a two column matrix.This can be useful to plot a bunch of points recorded on a mobile phone app.

mysore5 <- matrix(c(runif(5, 76.652985-0.01, 76.652985+0.01),
                 runif(5, 12.311827-0.01, 12.311827+0.01)),
                 nrow = 5)
mysore5
##          [,1]     [,2]
## [1,] 76.65678 12.30318
## [2,] 76.65522 12.30188
## [3,] 76.65444 12.31461
## [4,] 76.64755 12.31135
## [5,] 76.66118 12.31496
leaflet(data = mysore5) %>% 
  addProviderTiles(providers$OpenStreetMap) %>% 
  
# Pick an icon from <https://www.w3schools.com/bootstrap/bootstrap_ref_comp_glyphs.asp>
  addAwesomeMarkers(icon = awesomeIcons(
  icon = 'music',
  iconColor = 'black',
  library = 'glyphicon'),
  popup = "Carnatic Music !!")

3.2 Polygons, Lines, and Polylines Data Sources for leaflet

We have seen how to get POINT data into leaflet.

Line and polygon data can come from a variety of sources:

  • SpatialPolygons, SpatialPolygonsDataFrame, Polygons, and Polygon objects (from the sp package)
  • SpatialLines, SpatialLinesDataFrame, Lines, and Line objects (from the sp package)
  • MULTIPOLYGON, POLYGON, MULTILINESTRING, and LINESTRING objects (from the sf package)
  • map objects (from the maps package’s map() function); use map(fill = TRUE) for polygons, FALSE for polylines
  • Two-column numeric matrix; the first column is longitude and the second is latitude. Polygons are separated by rows of (NA, NA). It is not possible to represent multi-polygons nor polygons with holes using this method; use SpatialPolygons instead.

We will concentrate on using sf data into leaflet. We may explore maps() objects at a later date.

3.2.1 Polygons/MultiPolygons and LineString/MultiLineString using sf data frames

Let us download College buildings, parks, and the cycling lanes in Amsterdam, Netherlands, and plot these in leaflet.

bbox <- osmdata::getbb("Amsterdam, Netherlands")
bbox
##         min       max
## x  4.728756  5.079162
## y 52.278174 52.431064
colleges <- osmplotr::extract_osm_objects(bbox = bbox,
                                           key = "amenity",
                                           value = "college",
                                           return_type = "polygon" )
## Issuing query to Overpass API ...
## Announced endpoint: z.overpass-api.de/api/
## Query complete!
## converting OSM data to sf format
parks <- osmplotr::extract_osm_objects(bbox = bbox,
                                           key = "park",
                                           return_type = "polygon" )
## Issuing query to Overpass API ...
## Announced endpoint: z.overpass-api.de/api/
## Query complete!
## converting OSM data to sf format
roads <- osmplotr::extract_osm_objects(bbox = bbox,
                                       key = "highway",
                                       value = "primary",
                                       return_type = "line")
## Issuing query to Overpass API ...
## Announced endpoint: z.overpass-api.de/api/
## Query complete!
## converting OSM data to sf format
cyclelanes <-
  osmplotr::extract_osm_objects(bbox,
                                key = "cycleway",
                                value =  "lane",
                                return_type = "line")
## Issuing query to Overpass API ...
## Announced endpoint: z.overpass-api.de/api/
## Query complete!
## converting OSM data to sf format

We have 16 colleges in our data and 365 parks in our data.

leaflet() %>% 
  addTiles() %>% 
  addPolygons(data = colleges, popup = ~colleges$name) %>% 
  addPolygons(data = parks, color = "green", popup = parks$name) %>% 
  addPolylines(data = roads, color = "red") %>% 
  addPolylines(data = cyclelanes, color = "purple")

3.3 Chapter 3: Using Raster Data in leaflet

So far all the geospatial data we have plotted in leaflet has been vector data. We will now explore how to plot raster data using leaflet. Raster data are used to depict continuous variables across space, such as vegitation, salinity, forest cover etc. Satellite imagery is frequently available as raster data.

3.3.1 Importing Raster Data [Work in Progress!]

Raster data can be imported into R in many ways:

  • using the maptiles package
  • using the OpenStreetMap package
library(terra)
## terra 1.5.34
## 
## Attaching package: 'terra'
## The following object is masked from 'package:data.world':
## 
##     query
## The following object is masked from 'package:tidyr':
## 
##     extract
library(maptiles)
#library(OpenStreetMap) # causes RStudio to crash...

4 Bells and Whistles in leaflet: layers, groups, legends, and graticules

4.1 Adding Legends[Work in Progress!]

## Generate some random lat lon data around Bangalore
df <- data.frame(lat = runif(20, min = 11.97, max = 13.07),
                 lng = runif(20, min = 77.48, max = 77.68),
                 col = sample(c("red", "blue", "green"), 20, 
                              replace = TRUE),
                 stringsAsFactors = FALSE)

df %>%
  leaflet() %>%
  addTiles() %>%
  addCircleMarkers(color = df$col) %>%
  addLegend(values = df$col, labels = LETTERS[1:3], colors = c("blue", "red", "green"))
## Assuming "lng" and "lat" are longitude and latitude, respectively

5 Using Web Map Services (WMS) [Work in Progress!]

To be included.

LS0tDQp0aXRsZTogIlBsYXlpbmcgd2l0aCBMZWFmbGV0Ig0KYXV0aG9yOiAiQXJ2aW5kIFZlbmthdGFkcmkiDQpkYXRlOiAiTWF5IDEzLCAyMDE3Ig0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIHRoZW1lOiB1bml0ZWQNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfdGl0bGU6IENvbnRlbnRzDQogICAgdG9jX2Zsb2F0OiBUUlVFDQogICAgdG9jX2RlcHRoOiAzDQogICAgbnVtYmVyX3NlY3Rpb25zOiBUUlVFDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogVFJVRQ0KLS0tDQoNCiMgSW50cm9kdWN0aW9uDQoNClRoaXMgVHV0b3JpYWwgd29ya3MgdGhyb3VnaCB0aGUgaWRlYXMgYXQgW0xlYWZsZXRdKGh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vbGVhZmxldC8pDQoNCj4gTGVhZmxldCBpcyBhIEphdmFTY3JpcHQgbGlicmFyeSBmb3IgY3JlYXRpbmcgZHluYW1pYyBtYXBzIHRoYXQgc3VwcG9ydCBwYW5uaW5nIGFuZCB6b29taW5nIGFsb25nIHdpdGggdmFyaW91cyBhbm5vdGF0aW9ucyBsaWtlIG1hcmtlcnMsIHBvbHlnb25zLCBhbmQgcG9wdXBzLg0KDQpJbiB0aGlzIHR1dG9yaWFsIHdlIHdpbGwgd29yayBvbmx5IHdpdGggdmVjdG9yIGRhdGEuIEluIGEgc2Vjb25kIHBhcnQsIHdlIHdpbGwgd29yayB3aXRoIHJhc3RlciBkYXRhIGluIGBsZWFmbGV0YC4gDQoNCmBgYHtyIHNldHVwfQ0KDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkobGVhZmxldCkNCmxpYnJhcnkobWFwcykNCmxpYnJhcnkoc3ApDQpsaWJyYXJ5KHNmKQ0KDQojIERhdGENCmxpYnJhcnkob3NtZGF0YSkgIyBJbXBvcnQgT1NNIFZlY3RvciBEYXRhIGludG8gUg0KbGlicmFyeShvc21wbG90cikgIyBDcmVhdGluZyBtYXBzIHdpdGggT1NNIGRhdGEgaW4gUg0KIyBsaWJyYXJ5KE9wZW5TdHJlZXRNYXApICMgUmFzdGVyIERhdGENCg0KYGBgDQoNCiMgQmFzaWMgRmVhdHVyZXMgb2YgYSBsZWFmbGV0IE1hcA0KDQpgYGB7ciBTdGFydGluZ191cF93aXRoX2xlYWZsZXR9DQoNCiMgU2V0IHZhbHVlIGZvciB0aGUgbWluWm9vbSBhbmQgbWF4Wm9vbSBzZXR0aW5ncy4NCiNsZWFmbGV0KG9wdGlvbnMgPSBsZWFmbGV0T3B0aW9ucyhtaW5ab29tID0gMCwgbWF4Wm9vbSA9IDE4KSkNCg0KbSA8LSBsZWFmbGV0KCkgJT4lDQogIA0KICAjIEFkZCBkZWZhdWx0IE9wZW5TdHJlZXRNYXAgbWFwIHRpbGVzDQogIGFkZFRpbGVzKCkgJT4lIA0KICANCiAgIyBTZXQgdmlldyB0byBiZSByb3VnaGx5IGNlbnRyZWQgb24gQmFuZ2Fsb3JlIENpdHkNCiAgc2V0VmlldyhsbmcgPSA3Ny41ODA2NDMsIGxhdCA9IDEyLjk3MjQ0Miwgem9vbSA9IDEyKQ0KDQptDQojIENsaWNrIG9uIHRoZSBtYXAgdG8gem9vbSBpbjsgU2hpZnQrQ2xpY2sgdG8gem9vbSBvdXQNCg0KYGBgDQoNCmBsZWFmbGV0YCBieSBkZWZhdWx0IHVzZXMgW09wZW4gU3RyZWV0IE1hcF0oaHR0cHM6Ly93d3cub3BlbnN0cmVldG1hcC5vcmcvICJDbGljayB0byBnbyB0byBPU00iKSBhcyBpdHMgYmFzZSBtYXAuIFdlIGNhbiB1c2Ugb3RoZXIgYmFzZSBtYXBzIHRvbywgYXMgd2Ugd2lsbCBzZWUgbGF0ZXIuDQoNCg0KDQojIyBBZGQgU2hhcGVzIHRvIGEgTWFwDQoNCmBsZWFmbGV0YCBvZmZlcnMgc2V2ZXJhbCBjb21tYW5kcyB0byBhZGQgcG9pbnRzLCBtYXJrZXJzLCBpY29ucywgbGluZXMsIHBvbHlsaW5lcyBhbmQgcG9seWdvbnMgdG8gYSBtYXAuIExldCB1cyBleGFtaW5lIGEgZmV3IG9mIHRoZXNlLg0KDQojIyMgQWRkIE1hcmtlcnMgd2l0aCBwb3B1cHMNCg0KYGBge3IgYWRkaW5nX21hcmtlcnN9DQoNCm0gJT4lIGFkZE1hcmtlcnMobG5nID0gNzcuNTgwNjQzLCBsYXQgPSAxMi45NzI0NDIsIA0KICAgICAgICAgICAgICAgICBwb3B1cCA9ICJUaGUgYmlydGhwbGFjZSBvZiBSdmluZCIpDQoNCiMgQ2xpY2sgb24gdGhlIE1hcmtlciBmb3IgdGhlIHBvcHVwIHRvIGFwcGVhcg0KDQpgYGANCg0KVGhpcyB1c2VzIHRoZSBkZWZhdWx0ICoqcGluIHNoYXBlKiogYXMgdGhlIE1hcmtlci4gDQoNCg0KIyMjIEFkZGluZyBQb3B1cHMgdG8gYSBNYXANCg0KUG9wdXBzIGFyZSBzbWFsbCBib3hlcyBjb250YWluaW5nIGFyYml0cmFyeSBIVE1MLCB0aGF0IHBvaW50IHRvIGEgc3BlY2lmaWMgcG9pbnQgb24gdGhlIG1hcC4gVXNlIHRoZSBgYWRkUG9wdXBzKClgIGZ1bmN0aW9uIHRvIGFkZCBzdGFuZGFsb25lIHBvcHVwIHRvIHRoZSBtYXAuDQoNCmBgYHtyIHBvcHVwc30NCm0gJT4lDQogIGFkZFBvcHVwcygNCiAgICBsbmcgPSA3Ny41ODA2NDMsDQogICAgbGF0ID0gMTIuOTcyNDQyLA0KICAgIHBvcHVwID0gcGFzdGUoDQogICAgICAiVGhlIGJpcnRocGxhY2Ugb2YgUnZpbmQiLA0KICAgICAgIjxicj4iLA0KICAgICAgIldlYnNpdGU6IGh0dHBzOi8vdGhlLWZvdW5kYXRpb24tc2VyaWVzLm5ldGxpZnkuYXBwIiwNCiAgICAgICI8YnI+Ig0KICAgICksDQogICAgDQogICAgIyBFbnN1cmluZyB3ZSBjYW5ub3QgY2xvc2UgdGhlIHBvcHVwLCBlbHNlIHdlIHdpbGwgbm90IGJlIGFibGUgdG8gZmluZCB3aGVyZSBpdCBpcywgc2luY2UgdGhlcmUgaXMgbm8gTWFya2VyDQogICAgb3B0aW9ucyA9IHBvcHVwT3B0aW9ucyhjbG9zZUJ1dHRvbiA9IEZBTFNFKQ0KICApDQoNCmBgYA0KDQoNClBvcHVwcyBhcmUgdXN1YWxseSBhZGRlZCB0byBgaWNvbnNgLCBgTWFya2Vyc2AgYW5kIG90aGVyIHNoYXBlcyBjYW4gc2hvdyB1cCB3aGVuIHRoZXNlIGFyZSBjbGlja2VkLg0KDQoNCiMjIyBBZGRpbmcgTGFiZWxzIHRvIGEgTWFwDQoNCkxhYmVscyBhcmUgbWVzc2FnZXMgYXR0YWNoZWQgdG8gYWxsIHNoYXBlcywgdXNpbmcgdGhlIGFyZ3VtZW50IGBsYWJlbGAgd2hlcmV2ZXIgaXQgaXMgYXZhaWxhYmxlLiANCg0KKipMYWJlbHMgYXJlIHN0YXRpYywgYW5kIFBvcHVwcyBhcmUgdXN1YWxseSB2aXNpYmxlIG9uIG1vdXNlIGNsaWNrLioqIEhlbmNlIGEgYE1hcmtlcmAgY2FuIGhhdmUgYm90aCBhIGBsYWJlbGAgYW5kIGEgYHBvcHVwYC4gRm9yIGV4YW1wbGUsIHRoZSBmdW5jdGlvbiBgYWRkUG9wdXAoKWAgb2ZmZXJzIG9ubHkgYSBgcG9wdXBgIGFyZ3VtZW50LCB3aGVyZWFzIHRoZSBmdW5jdGlvbiBgYWRkTWFya2VycygpYCBvZmZlcnMgKmJvdGgqIGEgYHBvcHVwYCBhbmQgYSBgbGFiZWxgIGFyZ3VtZW50LiANCg0KSXQgaXMgYWxzbyBwb3NzaWJsZSB0byBjcmVhdGUgbGFiZWxzIHN0YW5kYWxvbmUgdXNpbmcgYGFkZExhYmVsT25seU1hcmtlcnMoKWAgd2hlcmUgd2UgY2FuIHNob3cgb25seSB0ZXh0IGFuZCBubyBNYXJrZXJzLiANCg0KYGBge3IgbGFiZWxzfQ0KbSAlPiUNCiAgYWRkTWFya2VycygNCiAgICBsbmcgPSA3Ny41ODA2NDMsDQogICAgbGF0ID0gMTIuOTcyNDQyLA0KICAgIA0KICAgICMgSGVyZSBpcyB0aGUgTGFiZWwgZGVmbi4NCiAgICBsYWJlbCA9ICJUaGUgYmlydGhwbGFjZSBvZiBSdmluZCIsDQogICAgbGFiZWxPcHRpb25zID0gbGFiZWxPcHRpb25zKG5vSGlkZSA9IFRSVUUsICMgTGFiZWwgYWx3YXlzIHZpc2libGUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGV4dE9ubHkgPSBGLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGV4dHNpemUgPSAyMCksDQogICAgDQogICAgIyBBbmQgaGVyZSBpcyB0aGUgcG9wdXAgZGVmbi4NCiAgICBwb3B1cCA9ICJUaGlzIGlzIHRoZSBQb3B1cCBUZXh0Ig0KICApDQoNCg0KYGBgDQoNCg0KDQoNCg0KIyMjIEFkZGluZyBDaXJjbGVzIGFuZCBDaXJjbGVNYXJrZXJzIG9uIGEgTWFwDQoNCldlIGNhbiBhZGQgc2hhcGVzIG9uIHRvIGEgbWFwIHRvIGRlcGljdCBhcmVhcyBvciBsb2NhdGlvbnMgb2YgaW50ZXJlc3QuIE5PVEU6IHRoZSBgcmFkaXVzYCBhcmd1bWVudCB3b3JrcyBkaWZmZXJlbnRseSBpbiBgYWRkQ2lyY2xlcygpYCBhbmQgYGFkZENpcmNsZU1hcmtlcnMoKWAuIA0KDQpgYGB7ciBkcmF3aW5nX2NpcmNsZXNfb25fYV9tYXB9DQoNCiMgU29tZSBDaXRpZXMgaW4gdGhlIFVTIGFuZCB0aGVpciBsb2NhdGlvbg0KbWRfY2l0aWVzIDwtIHRpYmJsZSgNCiAgbmFtZSA9IGMoIkJhbHRpbW9yZSIsIkZyZWRlcmljayIsIlJvY2t2aWxsZSIsIkdhaXRoZXJzYnVyZyIsIkJvd2llIiwiSGFnZXJzdG93biIsIkFubmFwb2xpcyIsIkNvbGxlZ2UgUGFyayIsIlNhbGlzYnVyeSIsIkxhdXJlbCIpLA0KICBwb3AgPSBjKDYxOTQ5Myw2NjE2OSw2MjMzNCw2MTA0NSw1NTIzMiwzOTg5MCwzODg4MCwzMDU4NywzMDQ4NCwyNTM0NiksDQogIGxhdCA9IGMoMzkuMjkyMDU5MiwzOS40MTQzOTIxLDM5LjA4NDAsMzkuMTQzNCwzOS4wMDY4LDM5LjY0MTgsMzguOTc4NCwzOC45ODk3LDM4LjM2MDcsMzkuMDk5MyksIA0KICBsbmcgPSBjKC03Ni42MDc3ODUyLC03Ny40MjA0ODc1LC03Ny4xNTI4LC03Ny4yMDE0LC03Ni43NzkxLC03Ny43MjAwLC03Ni40OTIyLC03Ni45Mzc4LC03NS41OTk0LC03Ni44NDgzKQ0KKQ0KDQoNCm1kX2NpdGllcyAlPiUNCiAgbGVhZmxldCgpICU+JQ0KICBhZGRUaWxlcygpICU+JQ0KICANCiAgIyBDaXJjbGVNYXJrZXJzLCBpbiBibHVlDQogICMgcmFkaXVzIHNjYWxlcyB0aGUgTWFya2VyLiBVbml0cyBhcmUgaW4gUGl4ZWxzISENCiAgIyBIZXJlLCByYWRpdXMgaXMgbWFkZSBwcm9wb3J0aW9uYWwgdG8gYHBvcGAgbnVtYmVyDQogIGFkZENpcmNsZU1hcmtlcnMocmFkaXVzID0gfiBwb3AvMTAwMCwgIyBQaXhlbHMhIQ0KICAgICAgICAgICAgICAgICAgIGNvbG9yID0gImJsdWUiLA0KICAgICAgICAgICAgICAgICAgIHN0cm9rZSA9IEZBTFNFLCAjIG5vIGJvcmRlciBmb3IgdGhlIE1hcmtlcnMNCiAgICAgICAgICAgICAgICAgICBvcGFjaXR5ID0gMC44KSAlPiUgDQogIA0KICANCiAgIyBDaXJjbGVzLCBpbiByZWQNCiAgYWRkQ2lyY2xlcygNCiAgICByYWRpdXMgPSA1MDAwLCAjIE1ldGVycyAhISENCiAgICBzdHJva2UgPSBUUlVFLA0KICAgIGNvbG9yID0gInllbGxvdyIsICMgU3Ryb2tlIENvbG91cg0KICAgIHdlaWdodCA9IDMsICMgU3Ryb2tlIFdlaWdodA0KICAgIGZpbGwgPSBUUlVFLA0KICAgIGZpbGxDb2xvciA9ICJyZWQiLA0KDQogICkNCg0KYGBgDQoNCg0KVGhlIHNoYXBlcyBuZWVkIG5vdCBiZSBvZiBmaXhlZCBzaXplIG9yIGNvbG91cjsgdGhlaXIgYXR0cmlidXRlcyBjYW4gYmUgbWFkZSB0byBjb3JyZXNwb25kIHRvIG90aGVyICoqYXR0cmlidXRlIHZhcmlhYmxlcyoqIGluIHRoZSBnZW9zcGF0aWFsIGRhdGEsIGFzIHdlIGRpZCB3aXRoIGByYWRpdXNgIGluIHRoZSBgYWRkQ2lyY2xlTWFya2VycygpYCBmdW5jdGlvbiBhYm92ZS4gDQoNCiMjIyBBZGRpbmcgUmVjdGFuZ2xlcyB0byBhIE1hcA0KDQpgYGB7cn0NCg0KIyMgQWRkaW5nIFJlY3RhbmdsZXMNCmxlYWZsZXQoKSAlPiUNCiAgYWRkVGlsZXMoKSAlPiUNCiAgc2V0VmlldyhsbmcgPSA3Ny41ODA2NDMsIGxhdCA9IDEyLjk3MjQ0Miwgem9vbSA9IDYpICU+JSANCiAgYWRkUmVjdGFuZ2xlcyhsYXQxID0gMTAuMzg1OCwgbG5nMSA9IDc1LjA1OTUsIA0KICAgICAgICAgICAgICAgIGxhdDIgPSAxMi44ODkwLCBsbmcyID0gNzcuOTYyNSkNCg0KYGBgDQoNCg0KDQojIyMgQWRkIFBvbHlnb25zIHRvIGEgTWFwDQoNCmBgYHtyfQ0KDQojIyBBZGRpbmcgUG9seWdvbnMNCmxlYWZsZXQoKSAlPiUNCiAgYWRkVGlsZXMoKSAlPiUNCiAgc2V0VmlldyhsbmcgPSA3Ny41ODA2NDMsIGxhdCA9IDEyLjk3MjQ0Miwgem9vbSA9IDYpICU+JSANCiAgDQogICMgYXJiaXRyYXJ5IHZlY3RvciBkYXRhIGZvciBsYXQgYW5kIGxuZw0KICAgYWRkUG9seWdvbnMobG5nID0gYyg3My41LCA3NS45LCA3Ni4xLCA3Ny4yMywgNzkuOCksDQogICAgICAgICAgICAgICBsYXQgPWMoMTAuMTIsIDExLjA0LCAxMS44NywgMTIuMDQsIDEwLjcpKQ0KDQpgYGANCg0KDQoNCiMjIyBBZGQgUG9seUxpbmVzIHRvIGEgTWFwDQoNClRoaXMgY2FuIGJlIHVzZWZ1bCBzYXkgZm9yIG1hbnVhbGx5IG1hcmtpbmcgYSByb3V0ZSBvbiBhIG1hcCwgd2l0aCB3YXlwb2ludHMuDQoNCmBgYHtyfQ0KbGVhZmxldCgpICU+JQ0KICBhZGRUaWxlcygpICU+JQ0KICBzZXRWaWV3KGxuZyA9IDc3LjU4MDY0MywgbGF0ID0gMTIuOTcyNDQyLCB6b29tID0gNikgJT4lIA0KICANCiAgIyBhcmJpdHJhcnkgdmVjdG9yIGRhdGEgZm9yIGxhdCBhbmQgbG5nDQogICMgSWYgc3RhcnQgYW5kIGVuZCBwb2ludHMgYXJlIHRoZSBzYW1lLCBpdCBsb29rcyBsaWtlIFBvbHlnb24NCiAgIyBXaXRob3V0IHRoZSBmaWxsDQogICBhZGRQb2x5bGluZXMobG5nID0gYyg3My41LCA3NS45LCA3Ni4xLCA3Ny4yMywgNzkuOCksDQogICAgICAgICAgICAgICBsYXQgPWMoMTAuMTIsIDExLjA0LCAxMS44NywgMTIuMDQsIDEwLjcpKSAlPiUgDQogIA0KICAjIEFkZCBXYXlwb2ludCBJY29ucw0KICBhZGRNYXJrZXJzKGxuZyA9IGMoNzMuNSwgNzUuOSwgNzYuMSwgNzcuMjMsIDc5LjgpLA0KICAgICAgICAgICAgICAgbGF0ID1jKDEwLjEyLCAxMS4wNCwgMTEuODcsIDEyLjA0LCAxMC43KSkNCg0KYGBgDQoNCkFzIHNlZW4sIHdlIGhhdmUgY3JlYXRlZCBNYXJrZXJzLCBMYWJlbHMsIFBvbHlnb25zLCBhbmQgUG9seUxpbmVzIHVzaW5nIGZpeGVkLmkuZS4gbGl0ZXJhbCB0ZXh0IGFuZCBudW1iZXJzLiBJbiB0aGUgZm9sbG93aW5nIHdlIHdpbGwgYWxzbyBzZWUgaG93IGV4dGVybmFsIGdlb3NwYXRpYWwgZGF0YSBjb2x1bW5zIGNhbiBiZSB1c2VkIGluc3RlYWQgb2YgdGhlc2UgbGl0ZXJhbHMuICANCg0KTk9URTogVGhlIGBtYXBlZGl0YCBwYWNrYWdlIDxodHRwczovL3Itc3BhdGlhbC5vcmcvL3IvMjAxNy8wMS8zMC9tYXBlZGl0X2ludHJvLmh0bWw+IGNhbiBhbHNvIGJlIHVzZWQgdG8gaW50ZXJhY3RpdmVseSBhZGQgc2hhcGVzIG9udG8gYSBtYXAgYW5kIHNhdmUgYXMgYW4gZ2VvLXNwYXRpYWwgb2JqZWN0LiANCg0KDQojIFVzaW5nIGBsZWFmbGV0YCB3aXRoIGV4dGVybmFsIGdlb3NwYXRpYWwgZGF0YQ0KDQpPbiB0byBzb21ldGhpbmcgbW9yZSBjb21wbGV4LiBXZSB3YW50IHRvIHBsb3QgYSBrbm93biBzZXQgb2YgbG9jYXRpb25zIG9uIGEgYGxlYWZsZXRgIG1hcC4gYGxlYWZsZXRgIHRha2VzIGluIGdlb2dyYXBoaWNhbCBkYXRhIGluIG1hbnkgd2F5cyBhbmQgd2Ugd2lsbCBleHBsb3JlIG1vc3Qgb2YgdGhlbS4NCg0KIyMgUG9pbnQgRGF0YSBTb3VyY2VzIGZvciBgbGVhZmxldGANCg0KUG9pbnQgZGF0YSBmb3IgbWFya2VycyBjYW4gY29tZSBmcm9tIGEgdmFyaWV0eSBvZiBzb3VyY2VzOg0KDQotIGBTcGF0aWFsUG9pbnRzYCBvciBgU3BhdGlhbFBvaW50c0RhdGFGcmFtZWAgb2JqZWN0cyAoZnJvbSB0aGUgYHNwYCBwYWNrYWdlKSAgDQotIGBQT0lOVGAsIGBzZmNfUE9JTlRgLCBhbmQgYHNmYCBvYmplY3RzIChmcm9tIHRoZSBgc2ZgIHBhY2thZ2UpOyBvbmx5IFggYW5kIFkgZGltZW5zaW9ucyB3aWxsIGJlIGNvbnNpZGVyZWQgIA0KLSBUd28tY29sdW1uIG51bWVyaWMgbWF0cmljZXMgKGZpcnN0IGNvbHVtbiBpcyBgbG9uZ2l0dWRlYCwgc2Vjb25kIGlzIGBsYXRpdHVkZWApICANCi0gYERhdGEgZnJhbWUvdGliYmxlYCB3aXRoIGBsYXRpdHVkZWAgYW5kIGBsb25naXR1ZGVgIGNvbHVtbnMuIFlvdSBjYW4gZXhwbGljaXRseSB0ZWxsIHRoZSBtYXJrZXIgZnVuY3Rpb24gd2hpY2ggY29sdW1ucyBjb250YWluIHRoZSBjb29yZGluYXRlIGRhdGEgKGUuZy4gYGFkZE1hcmtlcnMobG5nID0gfkxvbmdpdHVkZSwgbGF0ID0gfkxhdGl0dWRlKSlgLCBvciBsZXQgdGhlIGZ1bmN0aW9uIGxvb2sgZm9yIGNvbHVtbnMgbmFtZWQgYGxhdC9sYXRpdHVkZWAgYW5kIGBsb24vbG5nL2xvbmcvbG9uZ2l0dWRlYCAoY2FzZSBpbnNlbnNpdGl2ZSkuICAgDQotIFNpbXBseSBwcm92aWRlIG51bWVyaWMgYHZlY3RvcnNgIGFzIGBsbmdgIGFuZCBgbGF0YCBhcmd1bWVudHMsIHdoaWNoIHdlIGhhdmUgY292ZXJlZCBhbHJlYWR5IGluIHRoZSBwcmVjZWRpbmcgc2VjdGlvbnMuICANCg0KTm90ZSB0aGF0IE1VTFRJUE9JTlQgb2JqZWN0cyBmcm9tIGBzZmAgYXJlIG5vdCBzdXBwb3J0ZWQgYXQgdGhpcyB0aW1lLg0KDQpXZSB3aWxsIG5vdCBjb25zaWRlciB0aGUgdXNlIG9mIGBzcGAgcmVsYXRlZCBkYXRhIHN0cnVjdHVyZXMgZm9yIHBsb3R0aW5nIFBPSU5UcyBpbiBgbGVhZmxldGAgc2luY2UgYHNwYCBpcyBiZWluZyBwaGFzZWQgb3V0IGluIGZhdm91ciBvZiB0aGUgbW9yZSBtb2Rlcm4gcGFja2FnZSBgc2ZgLiANCg0KDQojIyMgUG9pbnRzIHVzaW5nIHNpbXBsZSBEYXRhIEZyYW1lcw0KDQpMZXQgdXMgcmVhZCBpbiB0aGUgZGF0YSBzZXQgZnJvbSBgZGF0YS53b3JsZGAgdGhhdCBnaXZlcyB1cyBQT0lOVCBsb2NhdGlvbnMgb2YgYWxsIGFpcnBvcnRzIGluIEluZGlhIGluIGEgYGRhdGEgZnJhbWVgIC8gYHRpYmJsZWAuIFRoZSBkYXRhc2V0IGlzIGF2YWlsYWJsZSBhdCA8aHR0cHM6Ly9xdWVyeS5kYXRhLndvcmxkL3MvYWh0eXZubTJ5YnlsZjY1c3lwNHJzYjV0dWx4ZTZhPi4gWW91IGNhbiBlaXRoZXIgZG93bmxvYWQgaXQsIHNhdmUgYSBjb3B5LCBhbmQgcmVhZCBpdCBpbiBhcyB1c3VhbCwgb3IgdXNlIHRoZSBVUkwgaXRzZWxmIHRvIHJlYWQgaXQgaW4gZnJvbSB0aGUgd2ViLiBJbiB0aGUgbGF0dGVyIGNhc2UsIHlvdSB3aWxsIG5lZWQgdGhlIHBhY2thZ2UgYGRhdGEud29ybGRgIGFuZCBhbHNvIG5lZWQgdG8gcmVnaXN0ZXIgeW91ciBjcmVkZW50aWFscyBmb3IgdGhhdCBwYWdlIHdpdGggUlN0dWRpby4gVGhlIChzaW1wbGUhKSBpbnN0cnVjdGlvbnMgYXJlIGF2YWlsYWJsZSBoZXJlIGF0IFtkYXRhLndvcmxkXShodHRwczovL2RhdGEud29ybGQvaW50ZWdyYXRpb25zL3ItYW5kLXItc3R1ZGlvKS4NCg0KDQpgYGB7ciBkYXRhLndvcmxkX2xlYWZsZXRfZXhhbXBsZX0NCg0KI2xpYnJhcnkoZGV2dG9vbHMpDQojZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJkYXRhZG90d29ybGQvZGF0YS53b3JsZC1yIiwgYnVpbGRfdmlnbmV0dGVzID0gVFJVRSkNCg0KbGlicmFyeShkYXRhLndvcmxkKQ0KDQppbmRpYV9haXJwb3J0cyA8LQ0KICByZWFkX2NzdigiaHR0cHM6Ly9xdWVyeS5kYXRhLndvcmxkL3MvYWh0eXZubTJ5YnlsZjY1c3lwNHJzYjV0dWx4ZTZhIikgJT4lIA0KICBzbGljZSgtMSkgJT4lICMgRHJvcCB0aGUgZmlyc3Qgcm93IHdoaWNoIGNvbnRhaW5zIGxhYmVscw0KICBkcGx5cjo6bXV0YXRlKA0KICAgIGlkID0gYXMuaW50ZWdlcihpZCksDQogICAgbGF0aXR1ZGVfZGVnID0gYXMubnVtZXJpYyhsYXRpdHVkZV9kZWcpLA0KICAgIGxvbmdpdHVkZV9kZWcgPSBhcy5udW1lcmljKGxvbmdpdHVkZV9kZWcpLA0KICAgIGVsZXZhdGlvbl9mdCA9IGFzLmludGVnZXIoZWxldmF0aW9uX2Z0KQ0KICApICU+JSANCiAgcmVuYW1lKCJsb24iID0gbG9uZ2l0dWRlX2RlZywgImxhdCIgPSBsYXRpdHVkZV9kZWcpICU+JSANCiAgIyBSZW1vdmUgZm91ciBsb2NhdGlvbnMgd2hpY2ggc2VlbSB0byBiZSBpbiB0aGUgQWZyaWNhbiBBdGxhbnRpYw0KICBmaWx0ZXIoIWlkICVpbiUgYygzMzA4MzQsIDMzMDg2NywgMzI1MDEwLCAzMzEwODMpKQ0KDQppbmRpYV9haXJwb3J0cyAlPiUgaGVhZCgpDQoNCmBgYA0KDQoNCg0KTGV0IHVzIHBsb3QgdGhpcyBpbiBgbGVhZmxldGAsIHVzaW5nIGFuIEVTUkkgTmF0aW9uYWwgR2VvZ3JhcGhpYyBzdHlsZSBtYXAgaW5zdGVhZCBvZiB0aGUgT1NNIEJhc2UgTWFwLiBXZSB3aWxsIGFsc28gcGxhY2Ugc21hbGwgY2lyY2xlIG1hcmtlcnMgZm9yIGVhY2ggYWlycG9ydC4NCg0KYGBge3J9DQoNCmxlYWZsZXQoZGF0YSA9IGluZGlhX2FpcnBvcnRzKSAlPiUgDQogIHNldFZpZXcobGF0ID0gMTgsIGxuZyA9IDc3LCB6b29tID0gNCkgJT4lIA0KICANCiAgIyBBZGQgTmF0R2VvIHN0eWxlIGJhc2UgbWFwDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJEVzcmkuTmF0R2VvV29ybGRNYXApICU+JSAjIEVTUkkgQmFzZW1hcA0KICANCiAgIyBBZGQgTWFya2VycyBmb3IgZWFjaCBhaXJwb3J0DQogIGFkZENpcmNsZU1hcmtlcnMobG5nID0gfmxvbiwgbGF0ID0gfmxhdCwNCiAgICAgICAgICAgICAgICAgICAjIE9wdGlvbmFsLCB2YXJpYWJsZXMgc3RhdGVkIGZvciBjbGFyaXR5DQogICAgICAgICAgICAgICAgICAgIyBsZWFmbGV0IGNhbiBhdXRvbWF0aWNhbGx5IGRldGVjdCBsb24tbGF0IGNvbHVtbnMNCiAgICAgICAgICAgICAgICAgICAjIGlmIHRoZXkgYXJlIGFwcHJvcHJpYXRlbHkgbmFtZWQgaW4gdGhlIGRhdGENCiAgICAgICAgICAgICAgICAgICAjIGxvbmdpdHVkZS9sb24vbG5nDQogICAgICAgICAgICAgICAgICAgIyBsYXRpdHVkZS9sYXQNCiAgICAgICAgICAgICAgICAgICByYWRpdXMgPSAyLCAjIFBpeGVscw0KICAgICAgICAgICAgICAgICAgIGNvbG9yID0gInJlZCIsDQogICAgICAgICAgICAgICAgICAgb3BhY2l0eSA9IDEpDQoNCg0KYGBgDQoNCg0KV2UgY2FuIGFsc28gY2hhbmdlIHRoZSBpY29uIGZvciBlYWNoIGFpcnBvcnQuIExldCB1cyB0cnkgb25lIG9mIHRoZXNldmVyYWwgaWNvbiBmYW1pbGllcyB0aGF0IHdlIGNhbiB1c2Ugd2l0aCBgbGVhZmxldGAgOiBnbHlwaGljb25zLCBpb25pY29ucywgYW5kIGZvbnRhd2Vzb21lIGljb25zLiANCg0KYGBge3IgYWlycG9ydHNfd2l0aF9wb3B1cHN9DQoNCiMgRGVmaW5lIHBvcHVwIG1lc3NhZ2UgZm9yIGVhY2ggYWlycG9ydA0KIyBCYXNlZCBvbiBkYXRhIGluIGluZGlhX2FpcnBvcnRzDQpwb3B1cCA8LSBwYXN0ZSgNCiAgIjxzdHJvbmc+IiwNCiAgaW5kaWFfYWlycG9ydHMkbmFtZSwNCiAgIjwvc3Ryb25nPjxicj4iLA0KICBpbmRpYV9haXJwb3J0cyRpYXRhX2NvZGUsDQogICI8YnI+IiwNCiAgaW5kaWFfYWlycG9ydHMkbXVuaWNpcGFsaXR5LA0KICAiPGJyPiIsDQogICJFbGV2YXRpb24oZmVldCkiLA0KICBpbmRpYV9haXJwb3J0cyRlbGV2YXRpb25fZnQsDQogICI8YnI+IiwNCiAgaW5kaWFfYWlycG9ydHMkd2lraXBlZGlhX2xpbmssDQogICI8YnI+Ig0KKQ0KDQppYXRhX2ljb24gPC0gbWFrZUljb24oDQogICJpYXRhLWxvZ28tdHJhbnNwLnBuZyIsICMgRG93bmxvYWRlZCBmcm9tIHd3dy5pYXRhLm9yZw0KICBpY29uV2lkdGggPSAyNCwNCiAgaWNvbkhlaWdodCA9IDI0LA0KICBpY29uQW5jaG9yWCA9IDAsDQogIGljb25BbmNob3JZID0gMA0KKQ0KDQojIENyZWF0ZSB0aGUgTGVhZmxldCBtYXANCmxlYWZsZXQoZGF0YSA9IGluZGlhX2FpcnBvcnRzKSAlPiUNCiAgc2V0VmlldyhsYXQgPSAxOCwgbG5nID0gNzcsIHpvb20gPSA0KSAlPiUNCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkRXNyaS5OYXRHZW9Xb3JsZE1hcCkgJT4lDQogIGFkZE1hcmtlcnMoDQogICAgaWNvbiA9IGlhdGFfaWNvbiwNCiAgICBwb3B1cCA9IHBvcHVwDQogICkNCmBgYA0KDQpUaGVyZSBhcmUgb3RoZXIgaWNvbnMgd2UgY2FuIHVzZSB0byBtYXJrIHRoZSBQT0lOVHMuIGBsZWFmbGV0YCBhbGxvd3MgdGhlIHVzZSBvZiBbaW9uaWNvbnNdKGh0dHA6Ly9pb25pY29ucy5jb20vKSwgW2dseXBoaWNvbnNdKGh0dHBzOi8vaWNvbnMuZ2V0Ym9vdHN0cmFwLmNvbS8jaWNvbnMpLCBhbmQgW0ZvbnRBd2Vzb21lSWNvbnNdKGh0dHA6Ly9mb250YXdlc29tZS5pby9pY29ucy8pICANCg0KSXQgaXMgcG9zc2libGUgdG8gY3JlYXRlIGEgYGxpc3RgIG9mIGljb25zLCBzbyB0aGF0IGRpZmZlcmVudCBNYXJrZXJzIGNhbiBoYXZlIGRpZmZlcmVudCBpY29ucy4gTGV0IHVzIHRyeSB0byBtYXAgdGhlIE1OQ3MgaW4gdGhlIElUUEwgYXJlYSBvZiBCYW5nYWxvcmU6IHdlIHVzZSB0aGUgaWRlYXMgaW4gW1VzaW5nIExlYWZsZXQgTWFya2VycyBASkxBLURhdGEubmV0XShodHRwczovL3d3dy5qbGEtZGF0YS5uZXQvZW5nL2xlYWZsZXQtbWFya2Vycy1pbi1yLykNCg0KDQpgYGB7ciBpdHBsfQ0KDQojIE1ha2UgYSBkYXRhZnJhbWUgb2YgYWRkcmVzc2VzIG9mIENvbXBhbmllcyB3ZSB3YW4gdG8gcGxvdCBpbiBJVFBMDQpjb21wYW5pZXNfaXRwbCA8LQ0KICBkYXRhLmZyYW1lKA0KICAgIHRpY2tlciA9IGMoDQogICAgICAiTUJSREkiLA0KICAgICAgIkRUSUNJIiwNCiAgICAgICJJQk0iLA0KICAgICAgIkV4eG9uIiwNCiAgICAgICJNaW5kdHJlZSIsDQogICAgICAiRklTIEdsb2JhbCIsDQogICAgICAiU2Fza2VuIiwNCiAgICAgICJMVEkiKSwNCiAgICBsYXQgPSBjKA0KICAgICAgMTIuOTg2MTc4NjIwOTg5MjY0LA0KICAgICAgMTIuOTg0MTYwOTA2MTkwMTIxLA0KICAgICAgMTIuOTgzNjU5MDg4NTY2MzU3LA0KICAgICAgMTIuOTg1MTEyMjY1OTg2NjM2LA0KICAgICAgMTIuOTgzNzk0OTk3NjA2MTg3LA0KICAgICAgMTIuOTgwNjU4NjE2MjE1MTU1LA0KICAgICAgMTIuOTgyMDgwNDQ3MzUwMjQ2LA0KICAgICAgMTIuOTgxMzM4MTY4ODc1MzQ4KSwNCiAgICBsb24gPSBjKA0KICAgICAgNzcuNzI3MDY1MjE4MzEwNSwNCiAgICAgIDc3LjcyODA4NDQ1Nzc0MzIxLA0KICAgICAgNzcuNzMxMDM0ODg3NjgwMDEsDQogICAgICA3Ny43MjkzNTA0NjA0MDY5OSwNCiAgICAgIDc3LjcyMjc4NDQxMjY5MzEsDQogICAgICA3Ny43MjY4NTA2NDE1ODc4MiwNCiAgICAgIDc3LjcyNTQ1NTg5Mjg5MDQxLA0KICAgICAgNzcuNzIyODcwMjQzMzgyMTYpDQogICkgJT4lIHNmOjpzdF9hc19zZihjb29yZHMgPSBjKCJsb24iLCAibGF0IiksIGNycyA9IDQzMjYpDQogDQojIFZhbmlsbGEgbGVhZmxldCBtYXANCmxlYWZsZXQoY29tcGFuaWVzX2l0cGwpICU+JSANCiAgYWRkVGlsZXMoKSAlPiUgDQogIGFkZE1hcmtlcnMoKQ0KDQpgYGANCg0KDQoNCg0KDQpMZXQgdXMgbWFrZSBhIGxpc3Qgb2YgbG9nb3Mgb2YgdGhlIENvbXBhbmllcyBhbmQgdXNlIHRoZW0gYXMgbWFya2VycyENCg0KYGBge3J9DQoNCiMgYSBuYW1lZCBsaXN0IG9mIHJlc2NhbGVkIGljb25zIHdpdGggbGlua3MgdG8gaW1hZ2VzDQpmYXZpY29ucyA8LSBpY29uTGlzdCgNCiAgIk1CUkRJIiA9IG1ha2VJY29uKA0KICAgIGljb25VcmwgPSAiaHR0cHM6Ly93d3cubWVyY2VkZXMtYmVuei5jb20vZXRjL2Rlc2lnbnMvYnJhbmRodWIvZnJvbnRlbmQvc3RhdGljLWFzc2V0cy9oZWFkZXIvbG9nby5zdmciLCANCiAgICBpY29uV2lkdGggPSAyNSwNCiAgICBpY29uSGVpZ2h0ID0gMjUNCiAgKSwNCiAgIkRUSUNJIiA9IG1ha2VJY29uKA0KICAgIGljb25VcmwgPSAiaHR0cHM6Ly9tZWRpYS1leHAxLmxpY2RuLmNvbS9kbXMvaW1hZ2UvQzREMEJBUUd6T2VwMjZsQzAzdy9jb21wYW55LWxvZ29fMjAwXzIwMC8wLzE2MzgyOTgzNjczNzQ/ZT0yMTQ3NDgzNjQ3JnY9YmV0YSZ0PW1QeUY0Z3ZOaE5GdmQtdGVkYnFOekpvZnE0cTlxY3c2QTl6OWpRZUxBd2MiLA0KICAgIGljb25XaWR0aCA9IDQ1LA0KICAgIGljb25IZWlnaHQgPSA0NQ0KICApLA0KICAiSUJNIiA9IG1ha2VJY29uKA0KICAgIGljb25VcmwgPSAiaHR0cHM6Ly93d3cuaWJtLmNvbS9mYXZpY29uLmljbyIsDQogICAgaWNvbldpZHRoID0gMjUsDQogICAgaWNvbkhlaWdodCA9IDI1DQogICksDQogICJFeHhvbiIgPSBtYWtlSWNvbigNCiAgICBpY29uVXJsID0gImh0dHBzOi8vY29ycG9yYXRlLmV4eG9ubW9iaWwuY29tLy0vbWVkaWEvR2xvYmFsL0ljb25zL2xvZ29zL0V4eG9uTW9iaWxMb2dvQ29sb3IyeC5wbmciLA0KICAgIGljb25XaWR0aCA9IDQ1LA0KICAgIGljb25IZWlnaHQgPSAyNQ0KICApLA0KICAiTWluZHRyZWUiID0gbWFrZUljb24oDQogICAgaWNvblVybCA9ICJodHRwczovL3d3dy5taW5kdHJlZS5jb20vdGhlbWVzL2N1c3RvbS9taW5kdHJlZV90aGVtZS9taW5kdHJlZS1sbnQtbG9nby1wbmcucG5nIiwNCiAgICBpY29uV2lkdGggPSA3NSwNCiAgICBpY29uSGVpZ2h0ID0gMjUNCiAgKSwNCiAgIkZJUyBHbG9iYWwiID0gbWFrZUljb24oDQogICAgaWNvblVybCA9ICJodHRwczovLzEwMDBsb2dvcy5uZXQvd3AtY29udGVudC91cGxvYWRzLzIwMjEvMDkvRklTLUxvZ28tNzY4eDQzMi5wbmciLA0KICAgIGljb25XaWR0aCA9IDI1LA0KICAgIGljb25IZWlnaHQgPSAyNQ0KICApLA0KICAiU2Fza2VuIiA9IG1ha2VJY29uKA0KICAgIGljb25VcmwgPSAiaHR0cHM6Ly93d3cuc2Fza2VuLmNvbS9zaXRlcy9hbGwvdGhlbWVzL3Nhc2tlbl93ZWJzaXRlL2xvZ28ucG5nIiwNCiAgICBpY29uV2lkdGggPSAzNSwNCiAgICBpY29uSGVpZ2h0ID0gMzUsDQogICksDQogICJMVEkiID0gbWFrZUljb24oDQogICAgaWNvblVybCA9ICJodHRwczovL3d3dy5sbnRpbmZvdGVjaC5jb20vd3AtY29udGVudC91cGxvYWRzLzIwMjEvMDkvTFRJLWxvZ28uc3ZnIiwNCiAgICBpY29uV2lkdGggPSAyNSwNCiAgICBpY29uSGVpZ2h0ID0gMjUNCiAgKQ0KKQ0KDQoNCiMgQ3JlYXRlIHRoZSBMZWFmbGV0IG1hcA0KDQpsZWFmbGV0KGNvbXBhbmllc19pdHBsKSAlPiUgDQogIGFkZE1hcmtlcnMoaWNvbiA9IH4gZmF2aWNvbnNbdGlja2VyXSwgIyBsb29rdXAgYmFzZWQgb24gdGlja2VyDQogICAgICAgICAgICAgbGFiZWwgPSB+IGNvbXBhbmllc19pdHBsJHRpY2tlciwNCiAgICAgICAgICAgICBsYWJlbE9wdGlvbnMgPSBsYWJlbE9wdGlvbnMobm9IaWRlID0gRixvZmZzZXQgPSBjKDE1LC0yNSkpKSAlPiUNCiAgYWRkUHJvdmlkZXJUaWxlcygiQ2FydG9EQi5Qb3NpdHJvbiIpDQoNCmBgYA0KDQoNCg0KDQoNCiMjIyBQb2ludHMgdXNpbmcgYHNmYCBvYmplY3RzDQoNCldlIHdpbGwgdXNlIGRhdGEgZnJvbSBhbiBgc2ZgIGRhdGEgb2JqZWN0LiBUaGlzIGRpZmZlcnMgZnJvbSB0aGUgZWFybGllciBzaXR1YXRpb24gd2hlcmUgd2UgaGFkIGEgc2ltcGxlIGRhdGEgZnJhbWUgd2l0aCBgbG9uYCBhbmQgYGxhdGAgY29sdW1ucy4gSW4gYHNmYCwgdGhlIGBsb25gIGFuZCBgbGF0YCBpbmZvIGlzIGVtYmVkZGVkIGluIHRoZSBgZ2VvbWV0cnlgIGNvbHVtbiBvZiB0aGUgYHNmIGRhdGEgZnJhbWVgLiANCg0KVGhlIGB0bWFwYCBwYWNrYWdlIGhhcyBhIGRhdGEgc2V0IG9mIGFsbCBXb3JsZCBtZXRybyBjaXRpZXMsIHRpdGxlZCBgbWV0cm9gLiAgV2Ugd2lsbCBwbG90IHRoZXNlIG9uIHRoZSBtYXAgYW5kIGFsc28gc2NhbGUgdGhlIG1hcmtlcnMgaW4gcHJvcG9ydGlvbiB0byBvbmUgb2YgdGhlICoqZmVhdHVyZSBhdHRyaWJ1dGVzKiosIGBwb3AyMDMwYC4gVGhlIGBwb3B1cGAgd2lsbCBiZSB0aGUgbmFtZSBvZiB0aGUgbWV0cm8gY2l0eS4gV2Ugd2lsbCBhbHNvIHVzZSB0aGUgYENhcnRvREIuUG9zaXRyb25gIGJhc2UgbWFwLiANCg0KTm90ZSB0aGF0IHRoZSBgbWV0cm9gIGRhdGEgc2V0IGhhcyBhIFBPSU5UIGdlb21ldHJ5LCBhcyBuZWVkZWQhDQoNCmBgYHtyLG1lc3NhZ2U9RkFMU0V9DQpkYXRhKG1ldHJvLCBwYWNrYWdlID0gInRtYXAiKQ0KbWV0cm8NCg0KbGVhZmxldChkYXRhID0gbWV0cm8pICU+JSANCiAgc2V0VmlldyhsYXQgPSAxOCwgbG5nID0gNzcsIHpvb20gPSA0KSAlPiUgDQogIA0KICAjIEFkZCBDYXJ0b0RCLlBvc2l0cm9uDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JSAjIENhcnRvREIgQmFzZW1hcA0KICANCiAgIyBBZGQgTWFya2VycyBmb3IgZWFjaCBhaXJwb3J0DQogIGFkZENpcmNsZU1hcmtlcnMocmFkaXVzID0gfiBzcXJ0KHBvcDIwMzApLzM1MCwNCiAgICAgICAgICAgICAgICAgICBjb2xvciA9ICJyZWQiLA0KICAgICAgICAgICAgICAgICAgIHBvcHVwID0gcGFzdGUoIk5hbWU6ICIsIG1ldHJvJG5hbWUsICI8YnI+IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiUG9wdWxhdGlvbiAyMDMwOiAiLCBtZXRybyRwb3AyMDMwKSkNCg0KDQoNCmBgYA0KDQoNCldlIGNhbiBhbHNvIHRyeSBkb3dubG9hZGluZyBhbiBgc2ZgIGRhdGEgZnJhbWUgd2l0aCBQT0lOVCBnZW9tZXRyeSBmcm9tIHNheSBPU00gZGF0YTxodHRwczovL29zbS4gTGV0IHVzIGdldCBob2xkIG9mIHJlc3RhdXJhbnRzIGRhdGEgaW4gTWFsbGVzd2FyYW0sIEJhbmdhbG9yZSBmcm9tIE9TTSBkYXRhOg0KDQpgYGB7cn0NCmJib3g8LSBvc21kYXRhOjpnZXRiYigiTWFsbGVzd2FyYW0sIEJlbmdhbHVydSIpDQpiYm94DQoNCmxvY2F0aW9ucyA8LSBvc21wbG90cjo6ZXh0cmFjdF9vc21fb2JqZWN0cygNCiAgYmJveCA9IGJib3gsDQogIGtleSA9ICJhbWVuaXR5IiwNCiAgdmFsdWUgPSAicmVzdGF1cmFudCIsDQogIHJldHVybl90eXBlID0gInBvaW50IikgDQoNCmxvY2F0aW9ucyA8LSBsb2NhdGlvbnMgJT4lIA0KICBkcGx5cjo6ZmlsdGVyKGN1aXNpbmUgPT0gImluZGlhbiIpDQpsb2NhdGlvbnMgJT4lIGhlYWQoKQ0KDQojIEZvbnRhd2Vzb21lIGljb25zIHNlZW0gdG8gd29yayBpbiBgbGVhZmxldGAgb25seSB1cCB0byBGb250QXdlc29tZSBWNC43LjAuDQojIFRoZSBGb250YXdlc29tZSBWNC43LjAgQ2hlYXRzaGVldCBpcyBoZXJlOiA8aHR0cHM6Ly9mb250YXdlc29tZS5jb20vdjQvY2hlYXRzaGVldC8+DQoNCg0KbGVhZmxldChkYXRhID0gbG9jYXRpb25zLCBvcHRpb25zID0gbGVhZmxldE9wdGlvbnMobWluWm9vbSA9IDEyKSkgJT4lIA0KICANCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Wb3lhZ2VyKSAlPiUgDQogIA0KICAjIFJlZ3VsYXIgYGxlYWZsZXRgIGNvZGUNCiAgYWRkQXdlc29tZU1hcmtlcnMoaWNvbiA9IGF3ZXNvbWVJY29ucyhpY29uID0gImZhLWNvZmZlZSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpYnJhcnkgPSAiZmEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmtlckNvbG9yID0gImJsdWUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGljb25Db2xvciA9ICJibGFjayIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWNvblJvdGF0ZSA9IFRSVUUpLA0KICAgICAgICAgICAgICAgICAgICAgcG9wdXAgPSBwYXN0ZSgiTmFtZTogIiwgbG9jYXRpb25zJG5hbWUsIjxicj4iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIkZvb2Q6ICIsIGxvY2F0aW9ucyRjdWlzaW5lKSkgDQoNCg0KYGBgDQoNCg0KKipGb250YXdlc29tZSBXb3JrYXJvdW5kKioNCg0KRm9yIG1vcmUgbGF0ZXIgdmVyc2lvbnMgb2YgRm9udGF3ZXNvbWUsIGhlcmUgYmVsb3cgaXMgYSB3b3JrYXJvdW5kIGZyb20gPGh0dHBzOi8vZ2l0aHViLmNvbS9yc3R1ZGlvL2xlYWZsZXQvaXNzdWVzLzY5MT4uIERlc3BpdGUgdGhpcyBzb21lIGZvbnRhd2Vzb21lIGljb25zIHNpbXBseSBkbyBub3Qgc2VlbSB0byBzaG93IHVwLiA7LSgpDQoNCmBgYHtyIGZvbnRhd2Vzb21lX3dvcmthcm91bmR9DQoNCmxpYnJhcnkoZm9udGF3ZXNvbWUpDQpjb2ZmZWUgPC0gbWFrZUF3ZXNvbWVJY29uKA0KICB0ZXh0ID0gZmEoIm11Zy1ob3QiKSwgIyBtdWctaG90IHdhcyBpbnRyb2R1Y2VkIGluIGZhIHZlcnNpb24gNQ0KICBpY29uQ29sb3IgPSAiYmxhY2siLA0KICBtYXJrZXJDb2xvciA9ICJibHVlIiwNCiAgbGlicmFyeSA9ICJmYSINCikNCg0KDQpsZWFmbGV0KGRhdGEgPSBsb2NhdGlvbnMpICU+JSANCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Wb3lhZ2VyKSAlPiUgDQogIA0KICAjIFdvcmthcm91bmQgY29kZQ0KDQogIGFkZEF3ZXNvbWVNYXJrZXJzKGljb24gPSBjb2ZmZWUsDQogICAgICAgICAgICAgcG9wdXAgPSBwYXN0ZSgiTmFtZTogIiwgbG9jYXRpb25zJG5hbWUsIjxicj4iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIkZvb2Q6ICIsIGxvY2F0aW9ucyRjdWlzaW5lLCAiPGJyPiIpKQ0KDQoNCg0KYGBgDQoNCg0KDQoNCg0KTm90ZSB0aGF0IGBsZWFmbGV0YCBhdXRvbWF0aWNhbGx5IGRldGVjdHMgdGhlIGxvbi9sYXQgY29sdW1ucyBmcm9tIHdpdGhpbiB0aGUgUE9JTlQgYGdlb21ldHJ5YCBjb2x1bW4gb2YgdGhlIGBzZmAgZGF0YSBmcmFtZS4gDQoNCg0KIyMjIFBvaW50cyB1c2luZyBUd28tQ29sdW1uIE1hdHJpY2VzDQoNCldlIGNhbiBub3cgcXVpY2tseSB0cnkgcHJvdmlkaW5nIGBsb25gIGFuZCBgbGF0YCBpbmZvIGluIGEgdHdvIGNvbHVtbiBtYXRyaXguVGhpcyBjYW4gYmUgdXNlZnVsIHRvIHBsb3QgYSBidW5jaCBvZiBwb2ludHMgcmVjb3JkZWQgb24gYSBtb2JpbGUgcGhvbmUgYXBwLiANCg0KYGBge3IgbWF0cml4X3BvaW50X2RhdGF9DQoNCm15c29yZTUgPC0gbWF0cml4KGMocnVuaWYoNSwgNzYuNjUyOTg1LTAuMDEsIDc2LjY1Mjk4NSswLjAxKSwNCiAgICAgICAgICAgICAgICAgcnVuaWYoNSwgMTIuMzExODI3LTAuMDEsIDEyLjMxMTgyNyswLjAxKSksDQogICAgICAgICAgICAgICAgIG5yb3cgPSA1KQ0KbXlzb3JlNQ0KDQpsZWFmbGV0KGRhdGEgPSBteXNvcmU1KSAlPiUgDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJE9wZW5TdHJlZXRNYXApICU+JSANCiAgDQojIFBpY2sgYW4gaWNvbiBmcm9tIDxodHRwczovL3d3dy53M3NjaG9vbHMuY29tL2Jvb3RzdHJhcC9ib290c3RyYXBfcmVmX2NvbXBfZ2x5cGhzLmFzcD4NCiAgYWRkQXdlc29tZU1hcmtlcnMoaWNvbiA9IGF3ZXNvbWVJY29ucygNCiAgaWNvbiA9ICdtdXNpYycsDQogIGljb25Db2xvciA9ICdibGFjaycsDQogIGxpYnJhcnkgPSAnZ2x5cGhpY29uJyksDQogIHBvcHVwID0gIkNhcm5hdGljIE11c2ljICEhIikNCg0KYGBgDQoNCg0KDQoNCiMjIFBvbHlnb25zLCBMaW5lcywgYW5kIFBvbHlsaW5lcyBEYXRhIFNvdXJjZXMgZm9yIGBsZWFmbGV0YA0KDQpXZSBoYXZlIHNlZW4gaG93IHRvIGdldCBQT0lOVCBkYXRhIGludG8gYGxlYWZsZXRgLg0KDQpMaW5lIGFuZCBwb2x5Z29uIGRhdGEgY2FuIGNvbWUgZnJvbSBhIHZhcmlldHkgb2Ygc291cmNlczoNCg0KLSBgU3BhdGlhbFBvbHlnb25zYCwgYFNwYXRpYWxQb2x5Z29uc0RhdGFGcmFtZWAsIGBQb2x5Z29uc2AsIGFuZCBgUG9seWdvbiBvYmplY3RzYCAoZnJvbSB0aGUgYHNwYCBwYWNrYWdlKSAgDQotIGBTcGF0aWFsTGluZXNgLCBgU3BhdGlhbExpbmVzRGF0YUZyYW1lYCwgYExpbmVzYCwgYW5kIGBMaW5lIG9iamVjdHNgIChmcm9tIHRoZSBgc3BgIHBhY2thZ2UpICANCi0gYE1VTFRJUE9MWUdPTmAsIGBQT0xZR09OYCwgYE1VTFRJTElORVNUUklOR2AsIGFuZCBgTElORVNUUklOR2Agb2JqZWN0cyAoZnJvbSB0aGUgYHNmYCBwYWNrYWdlKSAgDQotIGBtYXBgIG9iamVjdHMgKGZyb20gdGhlIGBtYXBzYCBwYWNrYWdl4oCZcyBgbWFwKClgIGZ1bmN0aW9uKTsgdXNlIGBtYXAoZmlsbCA9IFRSVUUpYCBmb3IgcG9seWdvbnMsIGBGQUxTRWAgZm9yIHBvbHlsaW5lcyAgDQotIFR3by1jb2x1bW4gbnVtZXJpYyBgbWF0cml4YDsgdGhlIGZpcnN0IGNvbHVtbiBpcyBsb25naXR1ZGUgYW5kIHRoZSBzZWNvbmQgaXMgbGF0aXR1ZGUuIFBvbHlnb25zIGFyZSBzZXBhcmF0ZWQgYnkgcm93cyBvZiAoTkEsIE5BKS4gSXQgaXMgbm90IHBvc3NpYmxlIHRvIHJlcHJlc2VudCBtdWx0aS1wb2x5Z29ucyBub3IgcG9seWdvbnMgd2l0aCBob2xlcyB1c2luZyB0aGlzIG1ldGhvZDsgdXNlIGBTcGF0aWFsUG9seWdvbnNgIGluc3RlYWQuIA0KDQpXZSB3aWxsIGNvbmNlbnRyYXRlIG9uIHVzaW5nIGBzZmAgZGF0YSBpbnRvIGBsZWFmbGV0YC4gV2UgbWF5IGV4cGxvcmUgYG1hcHMoKWAgb2JqZWN0cyBhdCBhIGxhdGVyIGRhdGUuIA0KDQojIyMgUG9seWdvbnMvTXVsdGlQb2x5Z29ucyBhbmQgTGluZVN0cmluZy9NdWx0aUxpbmVTdHJpbmcgdXNpbmcgYHNmYCBkYXRhIGZyYW1lcw0KDQpMZXQgdXMgZG93bmxvYWQgQ29sbGVnZSBidWlsZGluZ3MsIHBhcmtzLCBhbmQgdGhlIGN5Y2xpbmcgbGFuZXMgaW4gQW1zdGVyZGFtLCBOZXRoZXJsYW5kcywgYW5kIHBsb3QgdGhlc2UgaW4gYGxlYWZsZXRgLg0KDQpgYGB7cn0NCmJib3ggPC0gb3NtZGF0YTo6Z2V0YmIoIkFtc3RlcmRhbSwgTmV0aGVybGFuZHMiKQ0KYmJveA0KDQpjb2xsZWdlcyA8LSBvc21wbG90cjo6ZXh0cmFjdF9vc21fb2JqZWN0cyhiYm94ID0gYmJveCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZXkgPSAiYW1lbml0eSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSAiY29sbGVnZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuX3R5cGUgPSAicG9seWdvbiIgKQ0KcGFya3MgPC0gb3NtcGxvdHI6OmV4dHJhY3Rfb3NtX29iamVjdHMoYmJveCA9IGJib3gsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2V5ID0gInBhcmsiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybl90eXBlID0gInBvbHlnb24iICkNCnJvYWRzIDwtIG9zbXBsb3RyOjpleHRyYWN0X29zbV9vYmplY3RzKGJib3ggPSBiYm94LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2V5ID0gImhpZ2h3YXkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSAicHJpbWFyeSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm5fdHlwZSA9ICJsaW5lIikNCmN5Y2xlbGFuZXMgPC0NCiAgb3NtcGxvdHI6OmV4dHJhY3Rfb3NtX29iamVjdHMoYmJveCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2V5ID0gImN5Y2xld2F5IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSAgImxhbmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm5fdHlwZSA9ICJsaW5lIikNCg0KDQoNCmBgYA0KDQpXZSBoYXZlIGByIG5yb3coY29sbGVnZXMpYCBjb2xsZWdlcyBpbiBvdXIgZGF0YSBhbmQgYHIgbnJvdyhwYXJrcylgIHBhcmtzIGluIG91ciBkYXRhLiANCg0KYGBge3J9DQoNCmxlYWZsZXQoKSAlPiUgDQogIGFkZFRpbGVzKCkgJT4lIA0KICBhZGRQb2x5Z29ucyhkYXRhID0gY29sbGVnZXMsIHBvcHVwID0gfmNvbGxlZ2VzJG5hbWUpICU+JSANCiAgYWRkUG9seWdvbnMoZGF0YSA9IHBhcmtzLCBjb2xvciA9ICJncmVlbiIsIHBvcHVwID0gcGFya3MkbmFtZSkgJT4lIA0KICBhZGRQb2x5bGluZXMoZGF0YSA9IHJvYWRzLCBjb2xvciA9ICJyZWQiKSAlPiUgDQogIGFkZFBvbHlsaW5lcyhkYXRhID0gY3ljbGVsYW5lcywgY29sb3IgPSAicHVycGxlIikNCg0KYGBgDQoNCg0KDQojIyBDaGFwdGVyIDM6IFVzaW5nIFJhc3RlciBEYXRhIGluIGBsZWFmbGV0YA0KDQpTbyBmYXIgYWxsIHRoZSBnZW9zcGF0aWFsIGRhdGEgd2UgaGF2ZSBwbG90dGVkIGluIGBsZWFmbGV0YCBoYXMgYmVlbiAqKnZlY3RvcioqIGRhdGEuIFdlIHdpbGwgbm93IGV4cGxvcmUgaG93IHRvIHBsb3QgKipyYXN0ZXIqKiBkYXRhIHVzaW5nIGBsZWFmbGV0YC4gUmFzdGVyIGRhdGEgYXJlIHVzZWQgdG8gZGVwaWN0IGNvbnRpbnVvdXMgdmFyaWFibGVzIGFjcm9zcyBzcGFjZSwgc3VjaCBhcyB2ZWdpdGF0aW9uLCBzYWxpbml0eSwgZm9yZXN0IGNvdmVyIGV0Yy4gU2F0ZWxsaXRlIGltYWdlcnkgaXMgZnJlcXVlbnRseSBhdmFpbGFibGUgYXMgcmFzdGVyIGRhdGEuDQoNCg0KIyMjIEltcG9ydGluZyBSYXN0ZXIgRGF0YSBbV29yayBpbiBQcm9ncmVzcyFdDQoNClJhc3RlciBkYXRhIGNhbiBiZSBpbXBvcnRlZCBpbnRvIFIgaW4gbWFueSB3YXlzOg0KDQotIHVzaW5nIHRoZSBgbWFwdGlsZXNgIHBhY2thZ2UgIA0KLSB1c2luZyB0aGUgYE9wZW5TdHJlZXRNYXBgIHBhY2thZ2UgIA0KDQpgYGB7ciByYXN0ZXJfZGF0YV9pbl9sZWFmbGV0fQ0KbGlicmFyeSh0ZXJyYSkNCmxpYnJhcnkobWFwdGlsZXMpDQojbGlicmFyeShPcGVuU3RyZWV0TWFwKSAjIGNhdXNlcyBSU3R1ZGlvIHRvIGNyYXNoLi4uDQpgYGANCg0KDQoNCiMgQmVsbHMgYW5kIFdoaXN0bGVzIGluIGBsZWFmbGV0YDogbGF5ZXJzLCBncm91cHMsIGxlZ2VuZHMsIGFuZCBncmF0aWN1bGVzDQoNCiMjIEFkZGluZyBMZWdlbmRzW1dvcmsgaW4gUHJvZ3Jlc3MhXQ0KDQpgYGB7cn0NCg0KIyMgR2VuZXJhdGUgc29tZSByYW5kb20gbGF0IGxvbiBkYXRhIGFyb3VuZCBCYW5nYWxvcmUNCmRmIDwtIGRhdGEuZnJhbWUobGF0ID0gcnVuaWYoMjAsIG1pbiA9IDExLjk3LCBtYXggPSAxMy4wNyksDQogICAgICAgICAgICAgICAgIGxuZyA9IHJ1bmlmKDIwLCBtaW4gPSA3Ny40OCwgbWF4ID0gNzcuNjgpLA0KICAgICAgICAgICAgICAgICBjb2wgPSBzYW1wbGUoYygicmVkIiwgImJsdWUiLCAiZ3JlZW4iKSwgMjAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwbGFjZSA9IFRSVUUpLA0KICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpDQoNCmRmICU+JQ0KICBsZWFmbGV0KCkgJT4lDQogIGFkZFRpbGVzKCkgJT4lDQogIGFkZENpcmNsZU1hcmtlcnMoY29sb3IgPSBkZiRjb2wpICU+JQ0KICBhZGRMZWdlbmQodmFsdWVzID0gZGYkY29sLCBsYWJlbHMgPSBMRVRURVJTWzE6M10sIGNvbG9ycyA9IGMoImJsdWUiLCAicmVkIiwgImdyZWVuIikpDQoNCmBgYA0KDQoNCiMgVXNpbmcgV2ViIE1hcCBTZXJ2aWNlcyAoV01TKSBbV29yayBpbiBQcm9ncmVzcyFdDQpUbyBiZSBpbmNsdWRlZC4gDQo=