Setup
First, install some new packages.
#install.packages("Rtools", version = "4.2")
#install.packages("Rcpp", version = "1.0.10")
if(!require(tmap)){install.packages("tmap", options = options(timeout = 900))}
if(!require(sf)){install.packages("sf", options = options(timeout = 900))}
if(!require(maps)){install.packages("maps")}
if(!require(mapsf)){install.packages("mapsf")}
Then, activate the ones we need.
library(tidyverse)
library(readxl)
library(tmap) # Geographical plotting
library(gapminder) # Country level data
library(maps) # Geographical datasets
library(sf) # Shape files (sf) are a common geographical format
library(plotly) # Dynamic graphs
library(mapsf) # A cool new package!
Now, load the data (included in the packages). The World dataset
contains geographical data.
load("World.RData") # From tmap!
names(World)[2] <- "country" # Change the column name
str(World) # structure of World
Classes ‘sf’ and 'data.frame': 177 obs. of 16 variables:
$ iso_a3 : Factor w/ 177 levels "AFG","AGO","ALB",..: 1 2 3 4 5 6 7 8 9 10 ...
$ country : Factor w/ 177 levels "Afghanistan",..: 1 4 2 166 6 7 5 56 8 9 ...
$ sovereignt : Factor w/ 171 levels "Afghanistan",..: 1 4 2 159 6 7 5 52 8 9 ...
$ continent : Factor w/ 8 levels "Africa","Antarctica",..: 3 1 4 3 8 3 2 7 6 4 ...
$ area : Units: [km^2] num 652860 1246700 27400 71252 2736690 ...
$ pop_est : num 28400000 12799293 3639453 4798491 40913584 ...
$ pop_est_dens: num 43.5 10.3 132.8 67.3 15 ...
$ economy : Factor w/ 7 levels "1. Developed region: G7",..: 7 7 6 6 5 6 6 6 2 2 ...
$ income_grp : Factor w/ 5 levels "1. High income: OECD",..: 5 3 4 2 3 4 2 2 1 1 ...
$ gdp_cap_est : num 784 8618 5993 38408 14027 ...
$ life_exp : num 59.7 NA 77.3 NA 75.9 ...
$ well_being : num 3.8 NA 5.5 NA 6.5 4.3 NA NA 7.2 7.4 ...
$ footprint : num 0.79 NA 2.21 NA 3.14 2.23 NA NA 9.31 6.06 ...
$ inequality : num 0.427 NA 0.165 NA 0.164 ...
$ HPI : num 20.2 NA 36.8 NA 35.2 ...
$ geometry :sfc_MULTIPOLYGON of length 177; first list element: List of 1
..$ :List of 1
.. ..$ : num [1:69, 1:2] 61.2 62.2 63 63.2 64 ...
..- attr(*, "class")= chr [1:3] "XY" "MULTIPOLYGON" "sfg"
- attr(*, "sf_column")= chr "geometry"
- attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA NA NA NA NA NA NA ...
..- attr(*, "names")= chr [1:15] "iso_a3" "name" "sovereignt" "continent" ...
data("gapminder")
head(gapminder)
The world data is very rich: it has many attributes (population,
economy, etc.). The one column we are interested in is
geometry: it contains the data for the maps!
NOTE: there are 2 very useful packages that allow to
retrieve data from the World Bank: WID and
wbstats: have a look!
We see that a few points are missing for life expectancy, so let’s
see if we can complete with the gapminder data (spoiler: not so
much!).
This exercise (see below) of grouping two datasets is
paramount when plotting geographical content because usually, the
geometric properties come from one file and the items we wish to plot
come from another file. Thus, the joining step is KEY.
Areas
Maps in which areas are coloured to show some numerical attribute are
called choropleths.
With the tmap package
The idea below is to merge the two sources to combine geographical
forms and life expectancy. To do that, we need
left_join(). But before, we need to create a common
variable (i.e., a key) between the two. In World, the term
“name” is not so great so let’s change it into “country” (just like in
gapminder!). Then we can merge!
gapminder %>%
left_join(World, by = "country") %>% # Join the two datasets
select(country, lifeExp, pop, geometry)
nrow(gapminder) # Number of instances
[1] 1704
There are missing points. As a rough approximation, let’s just remove
them and store the result in data (more on them
later).
data <- gapminder %>% # Join and save via the arrow operator <-
left_join(World, by = "country") %>%
na.omit() # Remove rows with missing data
nrow(data) # Number of instances in dataset
[1] 1296
The loss is large because of missing points! We then use the
tmap package to create a choropleth.
Some countries are missing => data problem because gapminder is
incomplete. Let’s complete it via: https://www.gapminder.org/data/ This yields a much
bigger dataset, called “gap2.RData”. Let’s see:
load("gap2.RData")
head(gap2)
Again, we resort to left_join to merge the two data sources.
data <- World %>%
left_join(gap2, by = "country") #%>%
#na.omit()
With the mapsf package
References:
https://riatelab.github.io/mapsf/articles/mapsf.html
https://github.com/riatelab/mapsf
It’s super easy: you just need a shape file with the attribute you
want to plot!
sf::sf_use_s2(FALSE)
data %>%
filter(year == 2050) %>% # Keep only one year (problems otherwise)
sf::st_sf() %>%
mf_map(x = ., var = "population", type = "choro")
The package does not really use pipes/layers.
map <- data %>%
filter(year == 2018) %>% # Keep only one year (problems otherwise)
sf::st_sf()
my_theme <- list(
name = "mytheme",
bg = "#113366", # Light blue
fg = "#FFFFFF", # Dark grey
mar = c(0, 0, 0, 0),
tab = TRUE,
pos = "center",
inner = TRUE,
line = 1,
cex = .9,
font = 3
)
mf_theme(my_theme)
mf_map(x = map, type = "base")
mf_map(x = map,
add = T, # After you created a map with mf_map, you need this!
var = c("inequality", "well_being"), # Two variables! BUT BE CAREFUL!
type = "prop_choro",
inches = 0.1, # Scale of circles
lwd = 1, # Line width
breaks = "equal",
nbreaks = 6, # Number of colors breaks
leg_title_cex = c(1,1), # Size of legend title
leg_val_cex = c(0.5,0.8), # Size of legend text
pal = "Spectral", # Palette
leg_pos = c("bottomleft", "left"), # Legend position
leg_title= c("Inequality","Well being"), # Legend titles
leg_val_rnd = c(2, 2), # Number of decimals in legends
leg_frame = c(TRUE, TRUE)) # Frames around legend?
mf_layout(title = "Inequality & well-being in the world",
credits = paste0("Sources: MISC"),
frame = TRUE)
With the leaflet package
Next, we move towards interactive maps with the leaflet
package. The advantage of leaflet lies in its multiple options
that create dynamic plots. The example below is strongly inspired from
the reference page: https://rstudio.github.io/leaflet/choropleths.html Thus,
looking at the difference between the two codes helps understand how
they work.
We are going two use two features:
- a customized color scale;
- labels that appear when the cursor is on the map.
For the scale, we define it manually, both for the colors and for the
separation points (bins).
The labels are defined using html functions.
One word on missing data: in the World data, “Czech Rep.”
stands for “Czech Republic”, so we recode manually it to fill in a hole.
Since it’s a factor in World, we need to use the
recode_factor function. For simple text,
recode would be sufficient.
if(!require(leaflet)){install.packages("leaflet")} # Install package
library(leaflet) # Load package
datamap <- World %>%
mutate(country = country %>% recode_factor(`Czech Rep.` = "Czech Republic")) %>%
left_join(gap2, by = "country") %>%
filter(year == 2018) # Keep only one year (recent)
palet <- colorBin("YlGnBu", # Yellow-Green-Blue palette
domain = datamap %>% pull(lifeExp), # LifeExp # Domain of labels: lifeExp
bins = c(50, 55, 60, 65, 70, 75, 80, 85)) # Age categories
labels <- sprintf( # Below we define the labels
"<strong>%s</strong><br/>%g Years", # Adding text to label
datamap$country, # We show the country name...
datamap$lifeExp # ... and the life expectancy
) %>% lapply(htmltools::HTML) # Embedded all into html language
We can then move forward with the plot.
datamap %>%
data.frame() %>% # Turn into dataframe (technical)
sf::st_sf() %>% # Format in sf
#st_transform("+init=epsg:4326") %>% # Convert in particular coordinate reference
leaflet() %>% # Call leaflet
addPolygons(fillColor = ~palet(lifeExp), # Create the map (colored polygons)
weight = 2, # Width of separation line
opacity = 1, # Opacity of separation line
color = "white", # Color of separation line
dashArray = "3", # Dash size of separation line
fillOpacity = 0.7, # Opacity of polygon colors
highlight = highlightOptions( # 5 lines below control the cursor impact
weight = 2, # Width of line
color = "#333333", # Color of line
dashArray = "", # No dash
fillOpacity = 0.9, # Opacity
bringToFront = TRUE),
label = labels, # LABEL! Defined above!
labelOptions = labelOptions( # Label options below...
style = list("font-weight" = "normal", padding = "3px 8px"),
textsize = "15px",
direction = "auto")
) %>%
addLegend(pal = palet, # Legend: comes from palet colors defined above
values = ~lifeExp, # Values come from lifeExp variable
opacity = 0.9, # Opacity of legend
title = "Map Legend", # Title of legend
position = "bottomright") # Position of legend
Better, but some African countries are still missing… Data
problem!
It is possible to integrate leaflet into shiny: https://rstudio.github.io/leaflet/shiny.html You need
renderLeaflet() in the server and
leafletOutput() in the UI.
Points (with ggplot!)
Points are more easy to handle because they require less information
(two coordinates at least plus other attributes like size or color).
This is exactly the kind of data type that fits in the
ggplot syntax! Below, we show how to combine ggplot
with geographical data.
Country level data
While areas must be defined at the country level (complex!), points
are simply characterized by two numbers: latitude &
longitude. So a list of these two features suffices to
plot points. For countries, it is possible to find data online, ex: https://developers.google.com/public-data/docs/canonical/countries_csv
The latitudes and longitudes correspond to the center of the
countries.
Below, we create a new dataset with longitudes & latitutes
combined with gapminder (updated version).
if(!require(readxl)){install.packages("readxl")} # Install package to read excel files
library(readxl) # Activate package
options(scipen=999) # Remove scientific notation
country_LL <- read_excel("country_LL.xlsx") # Read longitude/latitude data
data2 <- left_join(gap2, country_LL) %>% # Merge two datasets
#na.omit() %>% # Remove missing points
mutate(latitude = as.numeric(latitude), # 'Numerize' latitude
longitude = as.numeric(longitude), # 'Numerize' longitude
population = round(population / 10^6,1)) # Scaling population into millions
data2 %>% head()
Be careful, the code below is computationally demanding (be patient).
We use the viridis color palette. In ggplot, a small
list of maps is available: world, France, Italy, New Zealand, US (and
states).
if(!require(scales)){install.packages("scales")}
library(scales)
world <- map_data("world") # From ggplot
g <- ggplot(data = world) + # Data source
geom_polygon(data = world, aes(x = long, # Dark background of the world
y = lat,
group = group)) +
geom_point(data = data2 %>% filter(year == 2018), # Most recent data points
aes(x = longitude, # Classical ggplot syntax!
y = latitude,
size = gdpPercap,
color = lifeExp,
label = country)) + # Label (for plotly)
scale_color_viridis_c(option = "magma") + ylim(-70,80) # Color scale
ggplotly(g) # Plotly integration
City level data
You can find longitudes & latitudes by searching on Google.
Here’s one example for France: https://simplemaps.com/data/fr-cities Worldwide data can
be accessed here: https://simplemaps.com/data/world-cities
cities <- read_excel("worldcities.xlsx")
That’s too big. Let’s focus on Germany, Italy, Austria, Slovenia and
Switzerland (arbitrarily) - or other choices.
cities_short <- cities %>%
filter(#country %in% c("Germany", "Italy", "Switzerland", "Austria", "Slovenia"),
country %in% c("China", "India", "Nepal", "Bangladesh", "Thailand"),
population > 350000) %>%
na.omit()
head(cities_short)
world <- map_data("world")
g <- ggplot(data = world) + # Data source
geom_polygon(data = world, # Dark background of the world
aes(x = long, y = lat, group = group),
size = 0.3, color = "grey") + # Outer lines of polygons
geom_point(data = cities_short, # The points
aes(x = lng,
y = lat,
size = population,
color = country,
label = city)) +
#scale_colour_gradient(low = "#FFFB00", high = "#FF1B00") + # This line is for color = population
scale_colour_manual(values = c("#FFEC00", "#66E234", "#1BCBF7", "#CC44F8", "#FC2361")) +
coord_sf(xlim = c(70, 125), ylim = c(6, 52), expand = FALSE) + theme_bw() # Zoom on Europe
# coord_sf(xlim = c(5, 18), ylim = c(36, 57), expand = FALSE) + theme_bw() # Zoom on Europe
ggplotly(g)
Other maps
For points, it’s easy: just use the same coordinate system via
longitude & latitude. It’s much more tricky for areas.
The key is to find shape files (one alternative
format is GeoJSON, but it is not treated here).
Example: http://datapages.com/gis-map-publishing-program/gis-open-files/global-framework/
Be very careful: some maps are EXTREMELY precise (up to 1m
precision). Thus, the files are heavy and the plotting takes a
LOT of time. Below, we use 100m precision, which is largely
enough.
The full description of all shape file items can be accessed on
wikipedia:
- .shp — shape format; the feature geometry itself
- .shx — shape index format; a positional index of the feature
geometry to allow seeking forwards and backwards quickly
- .dbf — attribute format; columnar attributes for each shape, in
dBase IV format
For instance, it’s possible to fetch data for French regions at: https://www.data.gouv.fr/fr/datasets/contours-geographiques-des-nouvelles-regions-metropole/
regions <- sf::st_read("regions-20140306-100m.shp")
Reading layer `regions-20140306-100m' from data source
`/Users/coqueret/Documents/IT/COURS/R4DS/S7_extensions/Geocomputing/regions-20140306-100m.shp' using driver `ESRI Shapefile'
Simple feature collection with 27 features and 10 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: -61.80976 ymin: -21.38973 xmax: 55.83665 ymax: 51.08984
Geodetic CRS: WGS 84
str(regions) # Structure of the variable region
Classes ‘sf’ and 'data.frame': 27 obs. of 11 variables:
$ code_insee: chr "42" "72" "83" "25" ...
$ nom : chr "Alsace" "Aquitaine" "Auvergne" "Basse-Normandie" ...
$ nom_cl : chr "Strasbourg" "Bordeaux" "Clermont-Ferrand" "Caen" ...
$ insee_cl : chr "67482" "33063" "63113" "14118" ...
$ nuts2 : chr "FR42" "FR61" "FR72" "FR25" ...
$ iso3166_2 : chr NA NA NA NA ...
$ wikipedia : chr "fr:Alsace" "fr:Aquitaine" "fr:Auvergne" "fr:Basse-Normandie" ...
$ nb_dep : num 2 5 4 3 4 4 6 4 2 4 ...
$ nb_comm : num 904 2296 1310 1812 2046 ...
$ surf_km2 : num 8328 41818 26172 17786 31752 ...
$ geometry :sfc_MULTIPOLYGON of length 27; first list element: List of 1
..$ :List of 1
.. ..$ : num [1:760, 1:2] 7.43 7.43 7.42 7.42 7.4 ...
..- attr(*, "class")= chr [1:3] "XY" "MULTIPOLYGON" "sfg"
- attr(*, "sf_column")= chr "geometry"
- attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA NA NA NA NA NA NA
..- attr(*, "names")= chr [1:10] "code_insee" "nom" "nom_cl" "insee_cl" ...
That’s complicated. The only important column for now is the
name of the region which is easy to recognize. Below, we plot a
choropleth where the color is link to the area of the region (bigger =
darker).
regions %>%
tm_shape() +
tm_polygons("surf_km2")
regions %>%
sf::st_sf() %>%
mf_map(x = ., var = "surf_km2", type = "choro")
That’s prety ugly. Let’s remove the regions outside the mainland.
regions %>%
filter(! (nom %in% c("Guyane", "Mayotte", "Martinique", "Guadeloupe", "La Réunion"))) %>%
tm_shape() +
tm_polygons("surf_km2", border.col = "white") # With the border of polygons
regions %>%
filter(! (nom %in% c("Guyane", "Mayotte", "Martinique", "Guadeloupe", "La Réunion"))) %>%
sf::st_sf() %>%
mf_map(x = ., var = "surf_km2", type = "choro", pal = "Spectral")
Below, we change the color palette and plot the number of French
“départements” inside each region.
regions %>%
filter(! (nom %in% c("Guyane", "Mayotte", "Martinique", "Guadeloupe", "La Réunion"))) %>%
tm_shape() +
tm_fill("nb_dep", palette = "Spectral") # Without the border
This is static: leaflet integration would be
better.
Highcharts
The material below is inspired from the vignette: https://jkunst.com/highcharter/
The highcharter package creates good looking plots. Let’s
install it (and activate it).
if(!require(highcharter)){install.packages("highcharter")}
library(highcharter)
The package has a large library of maps. The list is given here: https://code.highcharts.com/mapdata/
If you click on a link, you get the address of the map:
The last part is what you specify to get access to a map:
mapdata <- get_data_from_map(download_map_data("countries/fr/custom/fr-all-mainland"))
trying URL 'https://code.highcharts.com/mapdata/countries/fr/custom/fr-all-mainland.js'
Content type 'text/javascript' length 23677 bytes (23 KB)
==================================================
downloaded 23 KB
head(mapdata)
The information that will be plotted comes from this file. We need to
add the value that we want to use for the plot. There are 21 regions in
the variable map_data. This bypasses the merging of external
data via the left_join() function.
mapdata <- mapdata %>% mutate(value = 1:nrow(mapdata)) # Completely random values
head(mapdata, 20)
Below, we plot the regions of France and the color code is driven by
the column “value” defined above.
hcmap("countries/fr/custom/fr-all-mainland", # Source for the map
data = mapdata, # Source for the color code
value = "value", # Column for color code
joinBy = c("name"), # Common name & label
name = "Random Plot", # Name of plot
dataLabels = list(enabled = TRUE, format = "{point.name}"), # Allow labels
borderColor = "#FAFAFA", borderWidth = 0.5, # Border info
tooltip = list(valueDecimals = 1, # Label info
valuePrefix = "Value = ", # Before label
valueSuffix = " Units")) %>% # After label
hc_colorAxis(
stops = color_stops(colors = viridisLite::inferno(10, begin = 0.1)),
type = "logarithmic"
)
trying URL 'https://code.highcharts.com/mapdata/countries/fr/custom/fr-all-mainland.js'
Content type 'text/javascript' length 23677 bytes (23 KB)
==================================================
downloaded 23 KB
Fine grain local maps
Now let’s turn to smaller scale maps. The data comes from Kaggle,
scrapped from tripadvisor. The useful columns: Longitude &
Latitude, the MuseumName, the Rating and the
LengthOfVisit.
tripadvisor <- read_excel("tripadvisor_museum_US.xlsx") %>% # Importing the data
mutate(Longitude = as.numeric(Longitude), # Getting numbers back
Latitude = as.numeric(Latitude),
Rating = as.numeric(Rating),
LengthOfVisit = as.factor(LengthOfVisit)) %>%
filter(Latitude > 40, Latitude < 41, Longitude > (-74.5), Longitude < (-73.5)) # NY museums only
Warning: There were 2 warnings in `mutate()`.
The first warning was:
ℹ In argument: `Longitude = as.numeric(Longitude)`.
Caused by warning:
! NAs introduced by coercion
ℹ Run ]8;;ide:run:dplyr::last_dplyr_warnings()dplyr::last_dplyr_warnings()]8;; to see the 1 remaining warning.
tripadvisor
With leaflet, it’s easy to obtain empty maps as long as you know the
longitude & latitude of a particular location. Below:
Lyon.
leaflet() %>% # An empty map: a good start!
setView(lng = 4.85, lat = 45.75, zoom = 12) %>%
addTiles()
pal <- colorFactor(palette = "Spectral", domain = tripadvisor$LengthOfVisit)
tripadvisor %>%
leaflet() %>% # A blank sheet...
setView(lng = -73.9772, lat = 40.7808, zoom = 11) %>%
addTiles() %>%
addCircles(lng = ~Longitude, lat = ~Latitude,
radius = ~Rating^4, label = ~MuseumName,
fillColor = ~pal(LengthOfVisit), fillOpacity = 0.9, # Circle colors
stroke = TRUE, color = "black", weight = 2) %>% # Circle stroke
addLegend(pal = pal, # Legend: comes from palet colors defined above
values = ~LengthOfVisit, # Values come from lifeExp variable
opacity = 0.7, # Opacity of legend
title = "Map Legend", # Title of legend
position = "bottomright") # Position of legend
Below, you need to specify lon and
lat in the source dataframe. Or you can use the
hcaes() function.
mapdata2 <- get_data_from_map(download_map_data("countries/us/custom/us-ny-congress-113"))
trying URL 'https://code.highcharts.com/mapdata/countries/us/custom/us-ny-congress-113.js'
Content type 'text/javascript' length 23597 bytes (23 KB)
==================================================
downloaded 23 KB
hcmap("countries/us/custom/us-ny-congress-113") %>%
hc_mapNavigation(enabled = TRUE,
buttonOptions = list(
align = "right",
verticalAlign = "bottom")) %>%
hc_add_series(data = tripadvisor %>% mutate(lon = Longitude,
lat = Latitude,
name = MuseumName), # Add correct names
type = "mappoint", name = "Museums")
trying URL 'https://code.highcharts.com/mapdata/countries/us/custom/us-ny-congress-113.js'
Content type 'text/javascript' length 23597 bytes (23 KB)
==================================================
downloaded 23 KB
Not as impressive as leaflet, but the map could probably be improved.
NOTE: in the example above, the geographical background
and the points are unrelated.
Other map, same result below…
mapdata3 <- get_data_from_map(download_map_data("countries/us/us-ny-all"))
trying URL 'https://code.highcharts.com/mapdata/countries/us/us-ny-all.js'
Content type 'text/javascript' length 34989 bytes (34 KB)
==================================================
downloaded 34 KB
hcmap("countries/us/us-ny-all") %>%
hc_mapNavigation(enabled = TRUE,
buttonOptions = list(
align = "right",
verticalAlign = "bottom")) %>%
hc_add_series(data = tripadvisor %>% mutate(lon = Longitude,
lat = Latitude,
name = MuseumName), # Add correct names
type = "mappoint", name = "Museums")
trying URL 'https://code.highcharts.com/mapdata/countries/us/us-ny-all.js'
Content type 'text/javascript' length 34989 bytes (34 KB)
==================================================
downloaded 34 KB
A test with GeoJSON
GeoJSON are, with shapefiles the other main standard in
geocomputing.
It’s easy to obtain files, e.g., via https://geojson-maps.ash.ms Also, you will need the
rgdal package.
#library(rgdal)
library(geojsonsf)
#africa <- rgdal::readOGR("africa.geo.json")
#africa <- gdal_read("custom.geo.json")
africa <- geojson_sf("africa.geo.json")
labels <- sprintf(
"<strong>%s</strong><br/>%g people",
africa$name, africa$pop_est
) %>% lapply(htmltools::HTML)
pal <- colorBin("YlOrRd", domain = africa$pop_est,
bins = c(0, 100, 500, 1000, 5000, 10000, Inf))
leaflet(africa) %>%
addTiles() %>%
addPolygons(fillOpacity = 0.7,
fillColor = ~pal(pop_est),
weight = 2,
opacity = 1,
color = "white",
# highlight = highlightOptions( # 5 lines below control the cursor impact
# weight = 2, # Width of line
# color = "#333333", # Color of line
# dashArray = "", # No dash
# fillOpacity = 0.9, # Opacity
# bringToFront = TRUE),
label = labels,
labelOptions = labelOptions(
style = list("font-weight" = "normal", padding = "3px 8px"),
textsize = "15px",
direction = "auto")) %>%
addLegend(pal = pal, values = ~pop_est, opacity = 0.7, title = NULL,
position = "bottomleft")
Warning: Some values were outside the color scale and will be treated as NA
LS0tCnRpdGxlOiAiR2VvY29tcHV0aW5nIHdpdGggUiIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwotLS0KCgojIFNldHVwCgpGaXJzdCwgaW5zdGFsbCBzb21lIG5ldyBwYWNrYWdlcy4KCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CiNpbnN0YWxsLnBhY2thZ2VzKCJSdG9vbHMiLCB2ZXJzaW9uID0gIjQuMiIpCiNpbnN0YWxsLnBhY2thZ2VzKCJSY3BwIiwgdmVyc2lvbiA9ICIxLjAuMTAiKQppZighcmVxdWlyZSh0bWFwKSl7aW5zdGFsbC5wYWNrYWdlcygidG1hcCIsIG9wdGlvbnMgPSBvcHRpb25zKHRpbWVvdXQgPSA5MDApKX0KaWYoIXJlcXVpcmUoc2YpKXtpbnN0YWxsLnBhY2thZ2VzKCJzZiIsIG9wdGlvbnMgPSBvcHRpb25zKHRpbWVvdXQgPSA5MDApKX0KaWYoIXJlcXVpcmUobWFwcykpe2luc3RhbGwucGFja2FnZXMoIm1hcHMiKX0KaWYoIXJlcXVpcmUobWFwc2YpKXtpbnN0YWxsLnBhY2thZ2VzKCJtYXBzZiIpfQpgYGAKCgpUaGVuLCBhY3RpdmF0ZSB0aGUgb25lcyB3ZSBuZWVkLgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShyZWFkeGwpCmxpYnJhcnkodG1hcCkgICAgICAgICAgICAgICAjIEdlb2dyYXBoaWNhbCBwbG90dGluZwpsaWJyYXJ5KGdhcG1pbmRlcikgICAgICAgICAgIyBDb3VudHJ5IGxldmVsIGRhdGEKbGlicmFyeShtYXBzKSAgICAgICAgICAgICAgICMgR2VvZ3JhcGhpY2FsIGRhdGFzZXRzCmxpYnJhcnkoc2YpICAgICAgICAgICAgICAgICAjIFNoYXBlIGZpbGVzIChzZikgYXJlIGEgY29tbW9uIGdlb2dyYXBoaWNhbCBmb3JtYXQgCmxpYnJhcnkocGxvdGx5KSAgICAgICAgICAgICAjIER5bmFtaWMgZ3JhcGhzCmxpYnJhcnkobWFwc2YpICAgICAgICAgICAgICAjIEEgY29vbCBuZXcgcGFja2FnZSEKYGBgCgoKTm93LCBsb2FkIHRoZSBkYXRhIChpbmNsdWRlZCBpbiB0aGUgcGFja2FnZXMpLiBUaGUgV29ybGQgZGF0YXNldCBjb250YWlucyBnZW9ncmFwaGljYWwgZGF0YS4gCgpgYGB7cn0KbG9hZCgiV29ybGQuUkRhdGEiKSAgICAgICAgIyBGcm9tIHRtYXAhCm5hbWVzKFdvcmxkKVsyXSA8LSAiY291bnRyeSIgICAgICAgICAgICAgICAgICAgICMgQ2hhbmdlIHRoZSBjb2x1bW4gbmFtZQpzdHIoV29ybGQpICAgICAgICAgICAjIHN0cnVjdHVyZSBvZiBXb3JsZApkYXRhKCJnYXBtaW5kZXIiKQpoZWFkKGdhcG1pbmRlcikKYGBgCgpUaGUgd29ybGQgZGF0YSBpcyB2ZXJ5IHJpY2g6IGl0IGhhcyBtYW55IGF0dHJpYnV0ZXMgKHBvcHVsYXRpb24sIGVjb25vbXksIGV0Yy4pLiBUaGUgb25lIGNvbHVtbiB3ZSBhcmUgaW50ZXJlc3RlZCBpbiBpcyAqKmdlb21ldHJ5Kio6IGl0IGNvbnRhaW5zIHRoZSBkYXRhIGZvciB0aGUgbWFwcyEKCioqTk9URSoqOiB0aGVyZSBhcmUgMiB2ZXJ5IHVzZWZ1bCBwYWNrYWdlcyB0aGF0IGFsbG93IHRvIHJldHJpZXZlIGRhdGEgZnJvbSB0aGUgV29ybGQgQmFuazogKipXSUQqKiBhbmQgKip3YnN0YXRzKio6IGhhdmUgYSBsb29rIQoKV2Ugc2VlIHRoYXQgYSBmZXcgcG9pbnRzIGFyZSBtaXNzaW5nIGZvciBsaWZlIGV4cGVjdGFuY3ksIHNvIGxldCdzIHNlZSBpZiB3ZSBjYW4gY29tcGxldGUgd2l0aCB0aGUgZ2FwbWluZGVyIGRhdGEgKHNwb2lsZXI6IG5vdCBzbyBtdWNoISkuCgoqKlRoaXMgZXhlcmNpc2UgKHNlZSBiZWxvdykgb2YgZ3JvdXBpbmcgdHdvIGRhdGFzZXRzIGlzIHBhcmFtb3VudCB3aGVuIHBsb3R0aW5nIGdlb2dyYXBoaWNhbCBjb250ZW50IGJlY2F1c2UgdXN1YWxseSwgdGhlIGdlb21ldHJpYyBwcm9wZXJ0aWVzIGNvbWUgZnJvbSBvbmUgZmlsZSBhbmQgdGhlIGl0ZW1zIHdlIHdpc2ggdG8gcGxvdCBjb21lIGZyb20gYW5vdGhlciBmaWxlLiBUaHVzLCB0aGUgam9pbmluZyBzdGVwIGlzIEtFWS4qKgoKIyBBcmVhcwoKTWFwcyBpbiB3aGljaCBhcmVhcyBhcmUgY29sb3VyZWQgdG8gc2hvdyBzb21lIG51bWVyaWNhbCBhdHRyaWJ1dGUgYXJlIGNhbGxlZCAqY2hvcm9wbGV0aHMqLgoKIyMgV2l0aCB0aGUgdG1hcCBwYWNrYWdlCgpUaGUgaWRlYSBiZWxvdyBpcyB0byBtZXJnZSB0aGUgdHdvIHNvdXJjZXMgdG8gY29tYmluZSBnZW9ncmFwaGljYWwgZm9ybXMgYW5kIGxpZmUgZXhwZWN0YW5jeS4gVG8gZG8gdGhhdCwgd2UgbmVlZCAqKmxlZnRfam9pbioqKCkuIEJ1dCBiZWZvcmUsIHdlIG5lZWQgdG8gY3JlYXRlIGEgY29tbW9uIHZhcmlhYmxlIChpLmUuLCBhICprZXkqKSBiZXR3ZWVuIHRoZSB0d28uIEluIFdvcmxkLCB0aGUgdGVybSAibmFtZSIgaXMgbm90IHNvIGdyZWF0IHNvIGxldCdzIGNoYW5nZSBpdCBpbnRvICJjb3VudHJ5IiAoanVzdCBsaWtlIGluIGdhcG1pbmRlciEpLiBUaGVuIHdlIGNhbiBtZXJnZSEKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KZ2FwbWluZGVyICU+JSAKICBsZWZ0X2pvaW4oV29ybGQsIGJ5ID0gImNvdW50cnkiKSAlPiUgICAgICAgICAgIyBKb2luIHRoZSB0d28gZGF0YXNldHMKICBzZWxlY3QoY291bnRyeSwgbGlmZUV4cCwgcG9wLCBnZW9tZXRyeSkKbnJvdyhnYXBtaW5kZXIpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIE51bWJlciBvZiBpbnN0YW5jZXMKYGBgCgpUaGVyZSBhcmUgbWlzc2luZyBwb2ludHMuIEFzIGEgcm91Z2ggYXBwcm94aW1hdGlvbiwgbGV0J3MganVzdCByZW1vdmUgdGhlbSBhbmQgc3RvcmUgdGhlIHJlc3VsdCBpbiAqKmRhdGEqKiAobW9yZSBvbiB0aGVtIGxhdGVyKS4gCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CmRhdGEgPC0gZ2FwbWluZGVyICU+JSAgICAgICAgICAgICAgICAgICAgICMgSm9pbiBhbmQgc2F2ZSB2aWEgdGhlIGFycm93IG9wZXJhdG9yIDwtIAogIGxlZnRfam9pbihXb3JsZCwgYnkgPSAiY291bnRyeSIpICU+JQogIG5hLm9taXQoKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFJlbW92ZSByb3dzIHdpdGggbWlzc2luZyBkYXRhCm5yb3coZGF0YSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgTnVtYmVyIG9mIGluc3RhbmNlcyBpbiBkYXRhc2V0CmBgYAoKVGhlIGxvc3MgaXMgbGFyZ2UgYmVjYXVzZSBvZiBtaXNzaW5nIHBvaW50cyEgV2UgdGhlbiB1c2UgdGhlICp0bWFwKiBwYWNrYWdlIHRvIGNyZWF0ZSBhICoqY2hvcm9wbGV0aCoqLgoKCmBgYHtyLCBmaWcud2lkdGg9OX0KZGF0YSAlPiUgCiAgZmlsdGVyKHllYXIgPT0gMjAwNykgJT4lICAgICMgS2VlcCBvbmx5IG9uZSB5ZWFyIChwcm9ibGVtcyBvdGhlcndpc2UpCiAgc2Y6OnN0X3NmKCkgJT4lICAgICAgICAgICAgICMgVHJhbnNmb3JtIGRhdGEgaW50byBzaGFwZSBmaWxlIChzZikgZm9ybWF0CiAgdG1fc2hhcGUoKSArICAgICAgICAgICAgICAgICMgVHJhbmZvcm1zIHRoZSBzZiBpbnRvIGEgdG1hcCBvYmplY3QKICB0bV9wb2x5Z29ucygibGlmZUV4cCIsICAgICAgIyBQbG90IQogICAgICAgICAgICAgIGFscGhhID0gMC45LAogICAgICAgICAgICAgIHBhbGV0dGUgPSAiU3BlY3RyYWwiKSAgICAKYGBgCgoKU29tZSBjb3VudHJpZXMgYXJlIG1pc3NpbmcgPT4gZGF0YSBwcm9ibGVtIGJlY2F1c2UgZ2FwbWluZGVyIGlzIGluY29tcGxldGUuCkxldCdzIGNvbXBsZXRlIGl0IHZpYTogaHR0cHM6Ly93d3cuZ2FwbWluZGVyLm9yZy9kYXRhLwpUaGlzIHlpZWxkcyBhIG11Y2ggYmlnZ2VyIGRhdGFzZXQsIGNhbGxlZCAiZ2FwMi5SRGF0YSIuIExldCdzIHNlZToKCmBgYHtyfQpsb2FkKCJnYXAyLlJEYXRhIikKaGVhZChnYXAyKQpgYGAKCgpBZ2Fpbiwgd2UgcmVzb3J0IHRvIGxlZnRfam9pbiB0byBtZXJnZSB0aGUgdHdvIGRhdGEgc291cmNlcy4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KZGF0YSA8LSBXb3JsZCAlPiUgCiAgbGVmdF9qb2luKGdhcDIsIGJ5ID0gImNvdW50cnkiKSAjJT4lCiNuYS5vbWl0KCkgCmBgYAoKCiMjIFdpdGggdGhlICoqbWFwc2YqKiBwYWNrYWdlCgoqKlJlZmVyZW5jZXMqKjogIApodHRwczovL3JpYXRlbGFiLmdpdGh1Yi5pby9tYXBzZi9hcnRpY2xlcy9tYXBzZi5odG1sICAKaHR0cHM6Ly9naXRodWIuY29tL3JpYXRlbGFiL21hcHNmICAKCkl0J3Mgc3VwZXIgZWFzeTogeW91IGp1c3QgbmVlZCBhIHNoYXBlIGZpbGUgd2l0aCB0aGUgYXR0cmlidXRlIHlvdSB3YW50IHRvIHBsb3QhCgpgYGB7ciwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGLCBmaWcud2lkdGg9OH0Kc2Y6OnNmX3VzZV9zMihGQUxTRSkKZGF0YSAlPiUgCiAgZmlsdGVyKHllYXIgPT0gMjA1MCkgJT4lICAgICMgS2VlcCBvbmx5IG9uZSB5ZWFyIChwcm9ibGVtcyBvdGhlcndpc2UpCiAgc2Y6OnN0X3NmKCkgJT4lCiAgbWZfbWFwKHggPSAuLCB2YXIgPSAicG9wdWxhdGlvbiIsIHR5cGUgPSAiY2hvcm8iKQpgYGAKClRoZSBwYWNrYWdlIGRvZXMgbm90IHJlYWxseSB1c2UgcGlwZXMvbGF5ZXJzLgoKYGBge3IsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRiwgZmlnLndpZHRoPTl9Cm1hcCA8LSBkYXRhICU+JSAKICBmaWx0ZXIoeWVhciA9PSAyMDE4KSAlPiUgICAgIyBLZWVwIG9ubHkgb25lIHllYXIgKHByb2JsZW1zIG90aGVyd2lzZSkKICBzZjo6c3Rfc2YoKQoKbXlfdGhlbWUgPC0gbGlzdCgKICBuYW1lID0gIm15dGhlbWUiLCAKICBiZyA9ICIjMTEzMzY2IiwgICAgICAgICMgTGlnaHQgYmx1ZQogIGZnID0gIiNGRkZGRkYiLCAgICAgICAgIyBEYXJrIGdyZXkKICBtYXIgPSBjKDAsIDAsIDAsIDApLCAKICB0YWIgPSBUUlVFLCAKICBwb3MgPSAiY2VudGVyIiwgCiAgaW5uZXIgPSBUUlVFLCAKICBsaW5lID0gMSwgCiAgY2V4ID0gLjksIAogIGZvbnQgPSAzCikKCm1mX3RoZW1lKG15X3RoZW1lKQptZl9tYXAoeCA9IG1hcCwgdHlwZSA9ICJiYXNlIikKbWZfbWFwKHggPSBtYXAsCiAgICAgICBhZGQgPSBULCAgICAgICAgICAgICAjIEFmdGVyIHlvdSBjcmVhdGVkIGEgbWFwIHdpdGggbWZfbWFwLCB5b3UgbmVlZCB0aGlzIQogICAgICAgdmFyID0gYygiaW5lcXVhbGl0eSIsICJ3ZWxsX2JlaW5nIiksICAgICAgIyBUd28gdmFyaWFibGVzISBCVVQgQkUgQ0FSRUZVTCEKICAgICAgIHR5cGUgPSAicHJvcF9jaG9ybyIsCiAgICAgICBpbmNoZXMgPSAwLjEsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFNjYWxlIG9mIGNpcmNsZXMKICAgICAgIGx3ZCA9IDEsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgTGluZSB3aWR0aAogICAgICAgYnJlYWtzID0gImVxdWFsIiwgCiAgICAgICBuYnJlYWtzID0gNiwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIE51bWJlciBvZiBjb2xvcnMgYnJlYWtzCiAgICAgICBsZWdfdGl0bGVfY2V4ID0gYygxLDEpLCAgICAgICAgICAgICAgICAgICAjIFNpemUgb2YgbGVnZW5kIHRpdGxlCiAgICAgICBsZWdfdmFsX2NleCA9IGMoMC41LDAuOCksICAgICAgICAgICAgICAgICAjIFNpemUgb2YgbGVnZW5kIHRleHQKICAgICAgIHBhbCA9ICJTcGVjdHJhbCIsICAgICAgICAgICAgICAgICAgICAgICAgICMgUGFsZXR0ZQogICAgICAgbGVnX3BvcyA9IGMoImJvdHRvbWxlZnQiLCAibGVmdCIpLCAgICAgICAgIyBMZWdlbmQgcG9zaXRpb24KICAgICAgIGxlZ190aXRsZT0gYygiSW5lcXVhbGl0eSIsIldlbGwgYmVpbmciKSwgICMgTGVnZW5kIHRpdGxlcwogICAgICAgbGVnX3ZhbF9ybmQgPSBjKDIsIDIpLCAgICAgICAgICAgICAgICAgICAgIyBOdW1iZXIgb2YgZGVjaW1hbHMgaW4gbGVnZW5kcwogICAgICAgbGVnX2ZyYW1lID0gYyhUUlVFLCBUUlVFKSkgICAgICAgICAgICAgICAgIyBGcmFtZXMgYXJvdW5kIGxlZ2VuZD8KbWZfbGF5b3V0KHRpdGxlID0gIkluZXF1YWxpdHkgJiB3ZWxsLWJlaW5nIGluIHRoZSB3b3JsZCIsIAogICAgICAgICAgY3JlZGl0cyA9IHBhc3RlMCgiU291cmNlczogTUlTQyIpLCAKICAgICAgICAgIGZyYW1lID0gVFJVRSkKYGBgCgoKIyMgV2l0aCB0aGUgKipsZWFmbGV0KiogcGFja2FnZQoKTmV4dCwgd2UgbW92ZSB0b3dhcmRzIGludGVyYWN0aXZlIG1hcHMgd2l0aCB0aGUgKmxlYWZsZXQqIHBhY2thZ2UuIFRoZSBhZHZhbnRhZ2Ugb2YgKmxlYWZsZXQqIGxpZXMgaW4gaXRzIG11bHRpcGxlIG9wdGlvbnMgdGhhdCBjcmVhdGUgZHluYW1pYyBwbG90cy4gVGhlIGV4YW1wbGUgYmVsb3cgaXMgc3Ryb25nbHkgaW5zcGlyZWQgZnJvbSB0aGUgcmVmZXJlbmNlIHBhZ2U6IGh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vbGVhZmxldC9jaG9yb3BsZXRocy5odG1sClRodXMsIGxvb2tpbmcgYXQgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgdHdvIGNvZGVzIGhlbHBzIHVuZGVyc3RhbmQgaG93IHRoZXkgd29yay4gCgpXZSBhcmUgZ29pbmcgdHdvIHVzZSB0d28gZmVhdHVyZXM6ICAgCi0gYSBjdXN0b21pemVkIGNvbG9yIHNjYWxlOyAgICAKLSBsYWJlbHMgdGhhdCBhcHBlYXIgd2hlbiB0aGUgY3Vyc29yIGlzIG9uIHRoZSBtYXAuCgpGb3IgdGhlIHNjYWxlLCB3ZSBkZWZpbmUgaXQgbWFudWFsbHksIGJvdGggZm9yIHRoZSBjb2xvcnMgYW5kIGZvciB0aGUgc2VwYXJhdGlvbiBwb2ludHMgKGJpbnMpLiAgIApUaGUgbGFiZWxzIGFyZSBkZWZpbmVkIHVzaW5nIGh0bWwgZnVuY3Rpb25zLgoKT25lIHdvcmQgb24gbWlzc2luZyBkYXRhOiBpbiB0aGUgKldvcmxkKiBkYXRhLCAiQ3plY2ggUmVwLiIgc3RhbmRzIGZvciAiQ3plY2ggUmVwdWJsaWMiLCBzbyB3ZSByZWNvZGUgbWFudWFsbHkgaXQgdG8gZmlsbCBpbiBhIGhvbGUuIFNpbmNlIGl0J3MgYSBmYWN0b3IgaW4gKldvcmxkKiwgd2UgbmVlZCB0byB1c2UgdGhlICoqcmVjb2RlX2ZhY3RvcioqIGZ1bmN0aW9uLiBGb3Igc2ltcGxlIHRleHQsICoqcmVjb2RlKiogd291bGQgYmUgc3VmZmljaWVudC4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KaWYoIXJlcXVpcmUobGVhZmxldCkpe2luc3RhbGwucGFja2FnZXMoImxlYWZsZXQiKX0gICAgICAgICAgICAgICAgICAjIEluc3RhbGwgcGFja2FnZQpsaWJyYXJ5KGxlYWZsZXQpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgTG9hZCBwYWNrYWdlCmRhdGFtYXAgPC0gV29ybGQgJT4lIAogIG11dGF0ZShjb3VudHJ5ID0gY291bnRyeSAlPiUgcmVjb2RlX2ZhY3RvcihgQ3plY2ggUmVwLmAgPSAiQ3plY2ggUmVwdWJsaWMiKSkgJT4lCiAgbGVmdF9qb2luKGdhcDIsIGJ5ID0gImNvdW50cnkiKSAlPiUKICBmaWx0ZXIoeWVhciA9PSAyMDE4KSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBLZWVwIG9ubHkgb25lIHllYXIgKHJlY2VudCkKCnBhbGV0IDwtIGNvbG9yQmluKCJZbEduQnUiLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBZZWxsb3ctR3JlZW4tQmx1ZSBwYWxldHRlCiAgICAgICAgICAgICAgICAgIGRvbWFpbiA9IGRhdGFtYXAgJT4lIHB1bGwobGlmZUV4cCksICMgTGlmZUV4cCAgICAgIyBEb21haW4gb2YgbGFiZWxzOiBsaWZlRXhwCiAgICAgICAgICAgICAgICAgIGJpbnMgPSBjKDUwLCA1NSwgNjAsIDY1LCA3MCwgNzUsIDgwLCA4NSkpICAgICAgICAgIyBBZ2UgY2F0ZWdvcmllcwoKbGFiZWxzIDwtIHNwcmludGYoICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEJlbG93IHdlIGRlZmluZSB0aGUgbGFiZWxzCiAgIjxzdHJvbmc+JXM8L3N0cm9uZz48YnIvPiVnIFllYXJzIiwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBBZGRpbmcgdGV4dCB0byBsYWJlbAogIGRhdGFtYXAkY291bnRyeSwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgV2Ugc2hvdyB0aGUgY291bnRyeSBuYW1lLi4uCiAgZGF0YW1hcCRsaWZlRXhwICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIC4uLiBhbmQgdGhlIGxpZmUgZXhwZWN0YW5jeQopICU+JSBsYXBwbHkoaHRtbHRvb2xzOjpIVE1MKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRW1iZWRkZWQgYWxsIGludG8gaHRtbCBsYW5ndWFnZQpgYGAKCldlIGNhbiB0aGVuIG1vdmUgZm9yd2FyZCB3aXRoIHRoZSBwbG90LgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpkYXRhbWFwICU+JSAKICBkYXRhLmZyYW1lKCkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFR1cm4gaW50byBkYXRhZnJhbWUgKHRlY2huaWNhbCkKICBzZjo6c3Rfc2YoKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEZvcm1hdCBpbiBzZgogICNzdF90cmFuc2Zvcm0oIitpbml0PWVwc2c6NDMyNiIpICU+JSAgICAgICAgICAgICAjIENvbnZlcnQgaW4gcGFydGljdWxhciBjb29yZGluYXRlIHJlZmVyZW5jZSAKICBsZWFmbGV0KCkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIENhbGwgbGVhZmxldAogIGFkZFBvbHlnb25zKGZpbGxDb2xvciA9IH5wYWxldChsaWZlRXhwKSwgICAgICAgICMgQ3JlYXRlIHRoZSBtYXAgKGNvbG9yZWQgcG9seWdvbnMpCiAgICAgICAgICAgICAgd2VpZ2h0ID0gMiwgICAgICAgICAgICAgICAgICAgICAgICAgIyBXaWR0aCBvZiBzZXBhcmF0aW9uIGxpbmUKICAgICAgICAgICAgICBvcGFjaXR5ID0gMSwgICAgICAgICAgICAgICAgICAgICAgICAjIE9wYWNpdHkgb2Ygc2VwYXJhdGlvbiBsaW5lCiAgICAgICAgICAgICAgY29sb3IgPSAid2hpdGUiLCAgICAgICAgICAgICAgICAgICAgIyBDb2xvciBvZiBzZXBhcmF0aW9uIGxpbmUKICAgICAgICAgICAgICBkYXNoQXJyYXkgPSAiMyIsICAgICAgICAgICAgICAgICAgICAjIERhc2ggc2l6ZSBvZiBzZXBhcmF0aW9uIGxpbmUKICAgICAgICAgICAgICBmaWxsT3BhY2l0eSA9IDAuNywgICAgICAgICAgICAgICAgICAjIE9wYWNpdHkgb2YgcG9seWdvbiBjb2xvcnMKICAgICAgICAgICAgICBoaWdobGlnaHQgPSBoaWdobGlnaHRPcHRpb25zKCAgICAgICAjIDUgbGluZXMgYmVsb3cgY29udHJvbCB0aGUgY3Vyc29yIGltcGFjdAogICAgICAgICAgICAgICAgd2VpZ2h0ID0gMiwgICAgICAgICAgICAgICAgICAgICAgICMgV2lkdGggb2YgbGluZQogICAgICAgICAgICAgICAgY29sb3IgPSAiIzMzMzMzMyIsICAgICAgICAgICAgICAgICMgQ29sb3Igb2YgbGluZQogICAgICAgICAgICAgICAgZGFzaEFycmF5ID0gIiIsICAgICAgICAgICAgICAgICAgICMgTm8gZGFzaAogICAgICAgICAgICAgICAgZmlsbE9wYWNpdHkgPSAwLjksICAgICAgICAgICAgICAgICMgT3BhY2l0eQogICAgICAgICAgICAgICAgYnJpbmdUb0Zyb250ID0gVFJVRSksCiAgICAgICAgICAgICAgbGFiZWwgPSBsYWJlbHMsICAgICAgICAgICAgICAgICAgICAgIyBMQUJFTCEgRGVmaW5lZCBhYm92ZSEKICAgICAgICAgICAgICBsYWJlbE9wdGlvbnMgPSBsYWJlbE9wdGlvbnMoICAgICAgICAjIExhYmVsIG9wdGlvbnMgYmVsb3cuLi4KICAgICAgICAgICAgICAgIHN0eWxlID0gbGlzdCgiZm9udC13ZWlnaHQiID0gIm5vcm1hbCIsIHBhZGRpbmcgPSAiM3B4IDhweCIpLAogICAgICAgICAgICAgICAgdGV4dHNpemUgPSAiMTVweCIsCiAgICAgICAgICAgICAgICBkaXJlY3Rpb24gPSAiYXV0byIpCiAgKSAlPiUKICBhZGRMZWdlbmQocGFsID0gcGFsZXQsICAgICAgICAgICAgICAgICAgICAjIExlZ2VuZDogY29tZXMgZnJvbSBwYWxldCBjb2xvcnMgZGVmaW5lZCBhYm92ZQogICAgICAgICAgICB2YWx1ZXMgPSB+bGlmZUV4cCwgICAgICAgICAgICAgICMgVmFsdWVzIGNvbWUgZnJvbSBsaWZlRXhwIHZhcmlhYmxlCiAgICAgICAgICAgIG9wYWNpdHkgPSAwLjksICAgICAgICAgICAgICAgICAgIyBPcGFjaXR5IG9mIGxlZ2VuZAogICAgICAgICAgICB0aXRsZSA9ICJNYXAgTGVnZW5kIiwgICAgICAgICAgICMgVGl0bGUgb2YgbGVnZW5kCiAgICAgICAgICAgIHBvc2l0aW9uID0gImJvdHRvbXJpZ2h0IikgICAgICAgIyBQb3NpdGlvbiBvZiBsZWdlbmQKYGBgCgoKQmV0dGVyLCBidXQgc29tZSBBZnJpY2FuIGNvdW50cmllcyBhcmUgc3RpbGwgbWlzc2luZy4uLiBEYXRhIHByb2JsZW0hCgpJdCBpcyBwb3NzaWJsZSB0byBpbnRlZ3JhdGUgKmxlYWZsZXQqIGludG8gKnNoaW55KjogaHR0cHM6Ly9yc3R1ZGlvLmdpdGh1Yi5pby9sZWFmbGV0L3NoaW55Lmh0bWwKWW91IG5lZWQgKipyZW5kZXJMZWFmbGV0KiooKSBpbiB0aGUgc2VydmVyIGFuZCAqKmxlYWZsZXRPdXRwdXQqKigpIGluIHRoZSBVSS4KCgojIFBvaW50cyAod2l0aCBnZ3Bsb3QhKQoKUG9pbnRzIGFyZSBtb3JlIGVhc3kgdG8gaGFuZGxlIGJlY2F1c2UgdGhleSByZXF1aXJlIGxlc3MgaW5mb3JtYXRpb24gKHR3byBjb29yZGluYXRlcyBhdCBsZWFzdCBwbHVzIG90aGVyIGF0dHJpYnV0ZXMgbGlrZSBzaXplIG9yIGNvbG9yKS4gVGhpcyBpcyBleGFjdGx5IHRoZSBraW5kIG9mIGRhdGEgdHlwZSB0aGF0IGZpdHMgaW4gdGhlICoqZ2dwbG90Kiogc3ludGF4ISBCZWxvdywgd2Ugc2hvdyBob3cgdG8gY29tYmluZSBnZ3Bsb3Qgd2l0aCBnZW9ncmFwaGljYWwgZGF0YS4KCiMjIENvdW50cnkgbGV2ZWwgZGF0YQoKV2hpbGUgYXJlYXMgbXVzdCBiZSBkZWZpbmVkIGF0IHRoZSBjb3VudHJ5IGxldmVsIChjb21wbGV4ISksIHBvaW50cyBhcmUgc2ltcGx5IGNoYXJhY3Rlcml6ZWQgYnkgdHdvIG51bWJlcnM6ICoqbGF0aXR1ZGUqKiAmICoqbG9uZ2l0dWRlKiouIFNvIGEgbGlzdCBvZiB0aGVzZSB0d28gZmVhdHVyZXMgc3VmZmljZXMgdG8gcGxvdCBwb2ludHMuIEZvciBjb3VudHJpZXMsIGl0IGlzIHBvc3NpYmxlIHRvIGZpbmQgZGF0YSBvbmxpbmUsIGV4OiBodHRwczovL2RldmVsb3BlcnMuZ29vZ2xlLmNvbS9wdWJsaWMtZGF0YS9kb2NzL2Nhbm9uaWNhbC9jb3VudHJpZXNfY3N2CgpUaGUgbGF0aXR1ZGVzIGFuZCBsb25naXR1ZGVzIGNvcnJlc3BvbmQgdG8gdGhlIGNlbnRlciBvZiB0aGUgY291bnRyaWVzLgoKQmVsb3csIHdlIGNyZWF0ZSBhIG5ldyBkYXRhc2V0IHdpdGggbG9uZ2l0dWRlcyAmIGxhdGl0dXRlcyBjb21iaW5lZCB3aXRoICpnYXBtaW5kZXIqICh1cGRhdGVkIHZlcnNpb24pLgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CmlmKCFyZXF1aXJlKHJlYWR4bCkpe2luc3RhbGwucGFja2FnZXMoInJlYWR4bCIpfSAgICAgICMgSW5zdGFsbCBwYWNrYWdlIHRvIHJlYWQgZXhjZWwgZmlsZXMKbGlicmFyeShyZWFkeGwpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBBY3RpdmF0ZSBwYWNrYWdlCm9wdGlvbnMoc2NpcGVuPTk5OSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgUmVtb3ZlIHNjaWVudGlmaWMgbm90YXRpb24KY291bnRyeV9MTCA8LSByZWFkX2V4Y2VsKCJjb3VudHJ5X0xMLnhsc3giKSAgICAgICAgICAgIyBSZWFkIGxvbmdpdHVkZS9sYXRpdHVkZSBkYXRhCmRhdGEyIDwtIGxlZnRfam9pbihnYXAyLCBjb3VudHJ5X0xMKSAlPiUgICAgICAgICAgICAgICMgTWVyZ2UgdHdvIGRhdGFzZXRzCiAgI25hLm9taXQoKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFJlbW92ZSBtaXNzaW5nIHBvaW50cwogIG11dGF0ZShsYXRpdHVkZSA9IGFzLm51bWVyaWMobGF0aXR1ZGUpLCAgICAgICAgICAgIyAnTnVtZXJpemUnIGxhdGl0dWRlCiAgICAgICAgIGxvbmdpdHVkZSA9IGFzLm51bWVyaWMobG9uZ2l0dWRlKSwgICAgICAgICAjICdOdW1lcml6ZScgbG9uZ2l0dWRlCiAgICAgICAgIHBvcHVsYXRpb24gPSByb3VuZChwb3B1bGF0aW9uIC8gMTBeNiwxKSkgICAjIFNjYWxpbmcgcG9wdWxhdGlvbiBpbnRvIG1pbGxpb25zCmRhdGEyICU+JSBoZWFkKCkKYGBgCgpCZSBjYXJlZnVsLCB0aGUgY29kZSBiZWxvdyBpcyBjb21wdXRhdGlvbmFsbHkgZGVtYW5kaW5nIChiZSBwYXRpZW50KS4gV2UgdXNlIHRoZSAqdmlyaWRpcyogY29sb3IgcGFsZXR0ZS4gCkluICpnZ3Bsb3QqLCBhIHNtYWxsIGxpc3Qgb2YgbWFwcyBpcyBhdmFpbGFibGU6IHdvcmxkLCBGcmFuY2UsIEl0YWx5LCBOZXcgWmVhbGFuZCwgVVMgKGFuZCBzdGF0ZXMpLiAKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgZmlnLndpZHRoPTh9CmlmKCFyZXF1aXJlKHNjYWxlcykpe2luc3RhbGwucGFja2FnZXMoInNjYWxlcyIpfQpsaWJyYXJ5KHNjYWxlcykgCndvcmxkIDwtIG1hcF9kYXRhKCJ3b3JsZCIpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEZyb20gZ2dwbG90CmcgPC0gZ2dwbG90KGRhdGEgPSB3b3JsZCkgKyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERhdGEgc291cmNlCiAgZ2VvbV9wb2x5Z29uKGRhdGEgPSB3b3JsZCwgYWVzKHggPSBsb25nLCAgICAgICAgICAgICAgICAjIERhcmsgYmFja2dyb3VuZCBvZiB0aGUgd29ybGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IGxhdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSBncm91cCkpICsgICAKICBnZW9tX3BvaW50KGRhdGEgPSBkYXRhMiAlPiUgZmlsdGVyKHllYXIgPT0gMjAxOCksICAgICAgICMgTW9zdCByZWNlbnQgZGF0YSBwb2ludHMKICAgICAgICAgICAgIGFlcyh4ID0gbG9uZ2l0dWRlLCAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQ2xhc3NpY2FsIGdncGxvdCBzeW50YXghCiAgICAgICAgICAgICAgICAgeSA9IGxhdGl0dWRlLCAKICAgICAgICAgICAgICAgICBzaXplID0gZ2RwUGVyY2FwLCAKICAgICAgICAgICAgICAgICBjb2xvciA9IGxpZmVFeHAsIAogICAgICAgICAgICAgICAgIGxhYmVsID0gY291bnRyeSkpICsgICAgICAgICAgICAgICAgICAgICAgIyBMYWJlbCAoZm9yIHBsb3RseSkKICBzY2FsZV9jb2xvcl92aXJpZGlzX2Mob3B0aW9uID0gIm1hZ21hIikgKyB5bGltKC03MCw4MCkgICAgIyBDb2xvciBzY2FsZQpnZ3Bsb3RseShnKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBQbG90bHkgaW50ZWdyYXRpb24KYGBgCgojIyBDaXR5IGxldmVsIGRhdGEKCllvdSBjYW4gZmluZCBsb25naXR1ZGVzICYgbGF0aXR1ZGVzIGJ5IHNlYXJjaGluZyBvbiBHb29nbGUuIEhlcmUncyBvbmUgZXhhbXBsZSBmb3IgRnJhbmNlOgpodHRwczovL3NpbXBsZW1hcHMuY29tL2RhdGEvZnItY2l0aWVzCldvcmxkd2lkZSBkYXRhIGNhbiBiZSBhY2Nlc3NlZCBoZXJlOiBodHRwczovL3NpbXBsZW1hcHMuY29tL2RhdGEvd29ybGQtY2l0aWVzCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CmNpdGllcyA8LSByZWFkX2V4Y2VsKCJ3b3JsZGNpdGllcy54bHN4IikKYGBgCgpUaGF0J3MgdG9vIGJpZy4gTGV0J3MgZm9jdXMgb24gR2VybWFueSwgSXRhbHksIEF1c3RyaWEsIFNsb3ZlbmlhIGFuZCBTd2l0emVybGFuZCAoYXJiaXRyYXJpbHkpIC0gb3Igb3RoZXIgY2hvaWNlcy4KCmBgYHtyfQpjaXRpZXNfc2hvcnQgPC0gY2l0aWVzICU+JSAKICBmaWx0ZXIoI2NvdW50cnkgJWluJSBjKCJHZXJtYW55IiwgIkl0YWx5IiwgIlN3aXR6ZXJsYW5kIiwgIkF1c3RyaWEiLCAiU2xvdmVuaWEiKSwKICAgIGNvdW50cnkgJWluJSBjKCJDaGluYSIsICJJbmRpYSIsICJOZXBhbCIsICJCYW5nbGFkZXNoIiwgIlRoYWlsYW5kIiksCiAgICBwb3B1bGF0aW9uID4gMzUwMDAwKSAlPiUKICBuYS5vbWl0KCkKaGVhZChjaXRpZXNfc2hvcnQpCmBgYAoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFLCBmaWcud2lkdGg9OH0Kd29ybGQgPC0gbWFwX2RhdGEoIndvcmxkIikKZyA8LSBnZ3Bsb3QoZGF0YSA9IHdvcmxkKSArICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRGF0YSBzb3VyY2UKICBnZW9tX3BvbHlnb24oZGF0YSA9IHdvcmxkLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRGFyayBiYWNrZ3JvdW5kIG9mIHRoZSB3b3JsZAogICAgICAgICAgICAgICBhZXMoeCA9IGxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXApLAogICAgICAgICAgICAgICBzaXplID0gMC4zLCBjb2xvciA9ICJncmV5IikgKyAgICAgICAgICAgICAgICAgICAgIyBPdXRlciBsaW5lcyBvZiBwb2x5Z29ucwogIGdlb21fcG9pbnQoZGF0YSA9IGNpdGllc19zaG9ydCwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBUaGUgcG9pbnRzCiAgICAgICAgICAgICBhZXMoeCA9IGxuZywgCiAgICAgICAgICAgICAgICAgeSA9IGxhdCwgCiAgICAgICAgICAgICAgICAgc2l6ZSA9IHBvcHVsYXRpb24sIAogICAgICAgICAgICAgICAgIGNvbG9yID0gY291bnRyeSwgCiAgICAgICAgICAgICAgICAgbGFiZWwgPSBjaXR5KSkgKwogICNzY2FsZV9jb2xvdXJfZ3JhZGllbnQobG93ID0gIiNGRkZCMDAiLCBoaWdoID0gIiNGRjFCMDAiKSArICAjIFRoaXMgbGluZSBpcyBmb3IgY29sb3IgPSBwb3B1bGF0aW9uCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCIjRkZFQzAwIiwgIiM2NkUyMzQiLCAiIzFCQ0JGNyIsICIjQ0M0NEY4IiwgIiNGQzIzNjEiKSkgKwogIGNvb3JkX3NmKHhsaW0gPSBjKDcwLCAxMjUpLCB5bGltID0gYyg2LCA1MiksIGV4cGFuZCA9IEZBTFNFKSArIHRoZW1lX2J3KCkgIyBab29tIG9uIEV1cm9wZQojIGNvb3JkX3NmKHhsaW0gPSBjKDUsIDE4KSwgeWxpbSA9IGMoMzYsIDU3KSwgZXhwYW5kID0gRkFMU0UpICsgdGhlbWVfYncoKSAjIFpvb20gb24gRXVyb3BlCmdncGxvdGx5KGcpCmBgYAoKCiMgT3RoZXIgbWFwcwoKRm9yIHBvaW50cywgaXQncyBlYXN5OiBqdXN0IHVzZSB0aGUgc2FtZSBjb29yZGluYXRlIHN5c3RlbSB2aWEgbG9uZ2l0dWRlICYgbGF0aXR1ZGUuIEl0J3MgbXVjaCBtb3JlIHRyaWNreSBmb3IgYXJlYXMuCgpUaGUga2V5IGlzIHRvIGZpbmQgKipzaGFwZSBmaWxlcyoqIChvbmUgYWx0ZXJuYXRpdmUgZm9ybWF0IGlzIEdlb0pTT04sIGJ1dCBpdCBpcyBub3QgdHJlYXRlZCBoZXJlKS4gIApFeGFtcGxlOiBodHRwOi8vZGF0YXBhZ2VzLmNvbS9naXMtbWFwLXB1Ymxpc2hpbmctcHJvZ3JhbS9naXMtb3Blbi1maWxlcy9nbG9iYWwtZnJhbWV3b3JrLyAKQmUgdmVyeSBjYXJlZnVsOiBzb21lIG1hcHMgYXJlICpFWFRSRU1FTFkqIHByZWNpc2UgKHVwIHRvIDFtIHByZWNpc2lvbikuIFRodXMsIHRoZSBmaWxlcyBhcmUgaGVhdnkgYW5kIHRoZSBwbG90dGluZyB0YWtlcyBhICpMT1QqIG9mIHRpbWUuIEJlbG93LCB3ZSB1c2UgMTAwbSBwcmVjaXNpb24sIHdoaWNoIGlzIGxhcmdlbHkgZW5vdWdoLgoKVGhlIGZ1bGwgZGVzY3JpcHRpb24gb2YgYWxsIHNoYXBlIGZpbGUgaXRlbXMgY2FuIGJlIGFjY2Vzc2VkIG9uIHdpa2lwZWRpYToKCiogLnNocCDigJQgc2hhcGUgZm9ybWF0OyB0aGUgZmVhdHVyZSBnZW9tZXRyeSBpdHNlbGYgICAKKiAuc2h4IOKAlCBzaGFwZSBpbmRleCBmb3JtYXQ7IGEgcG9zaXRpb25hbCBpbmRleCBvZiB0aGUgZmVhdHVyZSBnZW9tZXRyeSB0byBhbGxvdyBzZWVraW5nIGZvcndhcmRzIGFuZCBiYWNrd2FyZHMgcXVpY2tseSAgIAoqIC5kYmYg4oCUIGF0dHJpYnV0ZSBmb3JtYXQ7IGNvbHVtbmFyIGF0dHJpYnV0ZXMgZm9yIGVhY2ggc2hhcGUsIGluIGRCYXNlIElWIGZvcm1hdAoKRm9yIGluc3RhbmNlLCBpdCdzIHBvc3NpYmxlIHRvIGZldGNoIGRhdGEgZm9yIEZyZW5jaCByZWdpb25zIGF0OgpodHRwczovL3d3dy5kYXRhLmdvdXYuZnIvZnIvZGF0YXNldHMvY29udG91cnMtZ2VvZ3JhcGhpcXVlcy1kZXMtbm91dmVsbGVzLXJlZ2lvbnMtbWV0cm9wb2xlLwoKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KcmVnaW9ucyA8LSBzZjo6c3RfcmVhZCgicmVnaW9ucy0yMDE0MDMwNi0xMDBtLnNocCIpCnN0cihyZWdpb25zKSAgICAjIFN0cnVjdHVyZSBvZiB0aGUgdmFyaWFibGUgcmVnaW9uCmBgYAoKVGhhdCdzIGNvbXBsaWNhdGVkLiBUaGUgb25seSBpbXBvcnRhbnQgY29sdW1uIGZvciBub3cgaXMgdGhlICpuYW1lKiBvZiB0aGUgcmVnaW9uIHdoaWNoIGlzIGVhc3kgdG8gcmVjb2duaXplLiBCZWxvdywgd2UgcGxvdCBhIGNob3JvcGxldGggd2hlcmUgdGhlIGNvbG9yIGlzIGxpbmsgdG8gdGhlIGFyZWEgb2YgdGhlIHJlZ2lvbiAoYmlnZ2VyID0gZGFya2VyKS4KCmBgYHtyfQpyZWdpb25zICU+JSAKICB0bV9zaGFwZSgpICsKICB0bV9wb2x5Z29ucygic3VyZl9rbTIiKQoKcmVnaW9ucyAlPiUgCiAgc2Y6OnN0X3NmKCkgJT4lCiAgbWZfbWFwKHggPSAuLCB2YXIgPSAic3VyZl9rbTIiLCB0eXBlID0gImNob3JvIikKYGBgCgpUaGF0J3MgcHJldHkgdWdseS4gTGV0J3MgcmVtb3ZlIHRoZSByZWdpb25zIG91dHNpZGUgdGhlIG1haW5sYW5kLiAKCmBgYHtyfQpyZWdpb25zICU+JQogIGZpbHRlcighIChub20gJWluJSBjKCJHdXlhbmUiLCAiTWF5b3R0ZSIsICJNYXJ0aW5pcXVlIiwgIkd1YWRlbG91cGUiLCAiTGEgUsOpdW5pb24iKSkpICU+JSAKICB0bV9zaGFwZSgpICsKICB0bV9wb2x5Z29ucygic3VyZl9rbTIiLCBib3JkZXIuY29sID0gIndoaXRlIikgIyBXaXRoIHRoZSBib3JkZXIgb2YgcG9seWdvbnMKCnJlZ2lvbnMgJT4lIAogIGZpbHRlcighIChub20gJWluJSBjKCJHdXlhbmUiLCAiTWF5b3R0ZSIsICJNYXJ0aW5pcXVlIiwgIkd1YWRlbG91cGUiLCAiTGEgUsOpdW5pb24iKSkpICU+JSAKICBzZjo6c3Rfc2YoKSAlPiUKICBtZl9tYXAoeCA9IC4sIHZhciA9ICJzdXJmX2ttMiIsIHR5cGUgPSAiY2hvcm8iLCBwYWwgPSAiU3BlY3RyYWwiKQpgYGAKCkJlbG93LCB3ZSBjaGFuZ2UgdGhlIGNvbG9yIHBhbGV0dGUgYW5kIHBsb3QgdGhlIG51bWJlciBvZiBGcmVuY2ggImTDqXBhcnRlbWVudHMiIGluc2lkZSBlYWNoIHJlZ2lvbi4KCmBgYHtyfQpyZWdpb25zICU+JQogIGZpbHRlcighIChub20gJWluJSBjKCJHdXlhbmUiLCAiTWF5b3R0ZSIsICJNYXJ0aW5pcXVlIiwgIkd1YWRlbG91cGUiLCAiTGEgUsOpdW5pb24iKSkpICU+JSAKICB0bV9zaGFwZSgpICsKICB0bV9maWxsKCJuYl9kZXAiLCBwYWxldHRlID0gIlNwZWN0cmFsIikgICMgV2l0aG91dCB0aGUgYm9yZGVyCmBgYAoKVGhpcyBpcyBzdGF0aWM6ICoqbGVhZmxldCoqIGludGVncmF0aW9uIHdvdWxkIGJlIGJldHRlci4KCiMgSGlnaGNoYXJ0cwoKVGhlIG1hdGVyaWFsIGJlbG93IGlzIGluc3BpcmVkIGZyb20gdGhlIHZpZ25ldHRlOiBodHRwczovL2prdW5zdC5jb20vaGlnaGNoYXJ0ZXIvCgpUaGUgKmhpZ2hjaGFydGVyKiBwYWNrYWdlIGNyZWF0ZXMgZ29vZCBsb29raW5nIHBsb3RzLiBMZXQncyBpbnN0YWxsIGl0IChhbmQgYWN0aXZhdGUgaXQpLgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IFRSVUV9CmlmKCFyZXF1aXJlKGhpZ2hjaGFydGVyKSl7aW5zdGFsbC5wYWNrYWdlcygiaGlnaGNoYXJ0ZXIiKX0KbGlicmFyeShoaWdoY2hhcnRlcikKYGBgCgpUaGUgcGFja2FnZSBoYXMgYSBsYXJnZSBsaWJyYXJ5IG9mIG1hcHMuIFRoZSBsaXN0IGlzIGdpdmVuIGhlcmU6Cmh0dHBzOi8vY29kZS5oaWdoY2hhcnRzLmNvbS9tYXBkYXRhLwoKCklmIHlvdSBjbGljayBvbiBhIGxpbmssIHlvdSBnZXQgdGhlIGFkZHJlc3Mgb2YgdGhlIG1hcDoKCiFbXShoaWdoX2xpc3QucG5nKQoKVGhlIGxhc3QgcGFydCBpcyB3aGF0IHlvdSBzcGVjaWZ5IHRvIGdldCBhY2Nlc3MgdG8gYSBtYXA6CgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9Cm1hcGRhdGEgPC0gZ2V0X2RhdGFfZnJvbV9tYXAoZG93bmxvYWRfbWFwX2RhdGEoImNvdW50cmllcy9mci9jdXN0b20vZnItYWxsLW1haW5sYW5kIikpCmhlYWQobWFwZGF0YSkKYGBgCgpUaGUgaW5mb3JtYXRpb24gdGhhdCB3aWxsIGJlIHBsb3R0ZWQgY29tZXMgZnJvbSB0aGlzIGZpbGUuIFdlIG5lZWQgdG8gYWRkIHRoZSB2YWx1ZSB0aGF0IHdlIHdhbnQgdG8gdXNlIGZvciB0aGUgcGxvdC4gVGhlcmUgYXJlIDIxIHJlZ2lvbnMgaW4gdGhlIHZhcmlhYmxlICptYXBfZGF0YSouIFRoaXMgYnlwYXNzZXMgdGhlIG1lcmdpbmcgb2YgZXh0ZXJuYWwgZGF0YSB2aWEgdGhlICoqbGVmdF9qb2luKiooKSBmdW5jdGlvbi4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KbWFwZGF0YSA8LSBtYXBkYXRhICU+JSBtdXRhdGUodmFsdWUgPSAxOm5yb3cobWFwZGF0YSkpICMgQ29tcGxldGVseSByYW5kb20gdmFsdWVzCmhlYWQobWFwZGF0YSwgMjApCmBgYAoKQmVsb3csIHdlIHBsb3QgdGhlIHJlZ2lvbnMgb2YgRnJhbmNlIGFuZCB0aGUgY29sb3IgY29kZSBpcyBkcml2ZW4gYnkgdGhlIGNvbHVtbiAidmFsdWUiIGRlZmluZWQgYWJvdmUuIAoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpoY21hcCgiY291bnRyaWVzL2ZyL2N1c3RvbS9mci1hbGwtbWFpbmxhbmQiLCAgICAgICAgICAgICAgICAgICAgICAjIFNvdXJjZSBmb3IgdGhlIG1hcAogICAgICBkYXRhID0gbWFwZGF0YSwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFNvdXJjZSBmb3IgdGhlIGNvbG9yIGNvZGUKICAgICAgdmFsdWUgPSAidmFsdWUiLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBDb2x1bW4gZm9yIGNvbG9yIGNvZGUKICAgICAgam9pbkJ5ID0gYygibmFtZSIpLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBDb21tb24gbmFtZSAmIGxhYmVsCiAgICAgIG5hbWUgPSAiUmFuZG9tIFBsb3QiLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgTmFtZSBvZiBwbG90CiAgICAgIGRhdGFMYWJlbHMgPSBsaXN0KGVuYWJsZWQgPSBUUlVFLCBmb3JtYXQgPSAie3BvaW50Lm5hbWV9IiksICMgQWxsb3cgbGFiZWxzCiAgICAgIGJvcmRlckNvbG9yID0gIiNGQUZBRkEiLCBib3JkZXJXaWR0aCA9IDAuNSwgICAgICAgICAgICAgICAgICMgQm9yZGVyIGluZm8KICAgICAgdG9vbHRpcCA9IGxpc3QodmFsdWVEZWNpbWFscyA9IDEsICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBMYWJlbCBpbmZvCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlUHJlZml4ID0gIlZhbHVlID0gIiwgICAgICAgICAgICAgICAgICAgICMgQmVmb3JlIGxhYmVsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlU3VmZml4ID0gIiBVbml0cyIpKSAlPiUgICAgICAgICAgICAgICAgICMgQWZ0ZXIgbGFiZWwKICBoY19jb2xvckF4aXMoCiAgICBzdG9wcyA9IGNvbG9yX3N0b3BzKGNvbG9ycyA9IHZpcmlkaXNMaXRlOjppbmZlcm5vKDEwLCBiZWdpbiA9IDAuMSkpLAogICAgdHlwZSA9ICJsb2dhcml0aG1pYyIKICApIApgYGAKCgojIEZpbmUgZ3JhaW4gbG9jYWwgbWFwcwoKTm93IGxldCdzIHR1cm4gdG8gc21hbGxlciBzY2FsZSBtYXBzLiBUaGUgZGF0YSBjb21lcyBmcm9tIEthZ2dsZSwgc2NyYXBwZWQgZnJvbSB0cmlwYWR2aXNvci4gVGhlIHVzZWZ1bCBjb2x1bW5zOiAqTG9uZ2l0dWRlKiAmICpMYXRpdHVkZSosIHRoZSAqTXVzZXVtTmFtZSosIHRoZSAqUmF0aW5nKiBhbmQgdGhlICpMZW5ndGhPZlZpc2l0Ki4gCgpgYGB7cn0KdHJpcGFkdmlzb3IgPC0gcmVhZF9leGNlbCgidHJpcGFkdmlzb3JfbXVzZXVtX1VTLnhsc3giKSAlPiUgICAgIyBJbXBvcnRpbmcgdGhlIGRhdGEKICBtdXRhdGUoTG9uZ2l0dWRlID0gYXMubnVtZXJpYyhMb25naXR1ZGUpLCAgICAgICAgICAgICAgICAgICAgIyBHZXR0aW5nIG51bWJlcnMgYmFjawogICAgICAgICBMYXRpdHVkZSA9IGFzLm51bWVyaWMoTGF0aXR1ZGUpLAogICAgICAgICBSYXRpbmcgPSBhcy5udW1lcmljKFJhdGluZyksCiAgICAgICAgIExlbmd0aE9mVmlzaXQgPSBhcy5mYWN0b3IoTGVuZ3RoT2ZWaXNpdCkpICU+JQogIGZpbHRlcihMYXRpdHVkZSA+IDQwLCBMYXRpdHVkZSA8IDQxLCBMb25naXR1ZGUgPiAoLTc0LjUpLCBMb25naXR1ZGUgPCAoLTczLjUpKSAjIE5ZIG11c2V1bXMgb25seQp0cmlwYWR2aXNvcgpgYGAKCgpXaXRoIGxlYWZsZXQsIGl0J3MgZWFzeSB0byBvYnRhaW4gZW1wdHkgbWFwcyBhcyBsb25nIGFzIHlvdSBrbm93IHRoZSBsb25naXR1ZGUgJiBsYXRpdHVkZSBvZiBhIHBhcnRpY3VsYXIgbG9jYXRpb24uIEJlbG93OiAqKkx5b24qKi4KCmBgYHtyLCBmaWcud2lkdGg9OH0KbGVhZmxldCgpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBBbiBlbXB0eSBtYXA6IGEgZ29vZCBzdGFydCEKICBzZXRWaWV3KGxuZyA9IDQuODUsIGxhdCA9IDQ1Ljc1LCB6b29tID0gMTIpICU+JSAKICBhZGRUaWxlcygpCmBgYAoKYGBge3J9CnBhbCA8LSBjb2xvckZhY3RvcihwYWxldHRlID0gIlNwZWN0cmFsIiwgZG9tYWluID0gdHJpcGFkdmlzb3IkTGVuZ3RoT2ZWaXNpdCkKdHJpcGFkdmlzb3IgJT4lCiAgbGVhZmxldCgpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQSBibGFuayBzaGVldC4uLgogIHNldFZpZXcobG5nID0gLTczLjk3NzIsIGxhdCA9IDQwLjc4MDgsIHpvb20gPSAxMSkgJT4lIAogIGFkZFRpbGVzKCkgJT4lCiAgYWRkQ2lyY2xlcyhsbmcgPSB+TG9uZ2l0dWRlLCBsYXQgPSB+TGF0aXR1ZGUsIAogICAgICAgICAgICAgcmFkaXVzID0gflJhdGluZ140LCBsYWJlbCA9ICB+TXVzZXVtTmFtZSwKICAgICAgICAgICAgIGZpbGxDb2xvciA9IH5wYWwoTGVuZ3RoT2ZWaXNpdCksIGZpbGxPcGFjaXR5ID0gMC45LCAgIyBDaXJjbGUgY29sb3JzCiAgICAgICAgICAgICBzdHJva2UgPSBUUlVFLCBjb2xvciA9ICJibGFjayIsIHdlaWdodCA9IDIpICU+JSAgICAgICMgQ2lyY2xlIHN0cm9rZQogIGFkZExlZ2VuZChwYWwgPSBwYWwsICAgICAgICAgICAgICAgICAgICAgICMgTGVnZW5kOiBjb21lcyBmcm9tIHBhbGV0IGNvbG9ycyBkZWZpbmVkIGFib3ZlCiAgICAgICAgICAgIHZhbHVlcyA9IH5MZW5ndGhPZlZpc2l0LCAgICAgICAgIyBWYWx1ZXMgY29tZSBmcm9tIGxpZmVFeHAgdmFyaWFibGUKICAgICAgICAgICAgb3BhY2l0eSA9IDAuNywgICAgICAgICAgICAgICAgICAjIE9wYWNpdHkgb2YgbGVnZW5kCiAgICAgICAgICAgIHRpdGxlID0gIk1hcCBMZWdlbmQiLCAgICAgICAgICAgIyBUaXRsZSBvZiBsZWdlbmQKICAgICAgICAgICAgcG9zaXRpb24gPSAiYm90dG9tcmlnaHQiKSAgICAgICAjIFBvc2l0aW9uIG9mIGxlZ2VuZApgYGAKCkJlbG93LCB5b3UgbmVlZCB0byBzcGVjaWZ5ICoqbG9uKiogYW5kICoqbGF0KiogaW4gdGhlIHNvdXJjZSBkYXRhZnJhbWUuIE9yIHlvdSBjYW4gdXNlIHRoZSAqaGNhZXMqKCkgZnVuY3Rpb24uCgpgYGB7cn0KbWFwZGF0YTIgPC0gZ2V0X2RhdGFfZnJvbV9tYXAoZG93bmxvYWRfbWFwX2RhdGEoImNvdW50cmllcy91cy9jdXN0b20vdXMtbnktY29uZ3Jlc3MtMTEzIikpCmhjbWFwKCJjb3VudHJpZXMvdXMvY3VzdG9tL3VzLW55LWNvbmdyZXNzLTExMyIpICU+JQogIGhjX21hcE5hdmlnYXRpb24oZW5hYmxlZCA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICBidXR0b25PcHRpb25zID0gbGlzdCgKICAgICAgICAgICAgICAgICAgICAgYWxpZ24gPSAicmlnaHQiLAogICAgICAgICAgICAgICAgICAgICB2ZXJ0aWNhbEFsaWduID0gImJvdHRvbSIpKSAlPiUKICBoY19hZGRfc2VyaWVzKGRhdGEgPSB0cmlwYWR2aXNvciAlPiUgbXV0YXRlKGxvbiA9IExvbmdpdHVkZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXQgPSBMYXRpdHVkZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gTXVzZXVtTmFtZSksICAjIEFkZCBjb3JyZWN0IG5hbWVzCiAgICAgICAgICAgICAgICB0eXBlID0gIm1hcHBvaW50IiwgbmFtZSA9ICJNdXNldW1zIikgCgpgYGAKCk5vdCBhcyBpbXByZXNzaXZlIGFzIGxlYWZsZXQsIGJ1dCB0aGUgbWFwIGNvdWxkIHByb2JhYmx5IGJlIGltcHJvdmVkLiAKKipOT1RFKio6IGluIHRoZSBleGFtcGxlIGFib3ZlLCB0aGUgZ2VvZ3JhcGhpY2FsIGJhY2tncm91bmQgYW5kIHRoZSBwb2ludHMgYXJlIHVucmVsYXRlZC4gIApPdGhlciBtYXAsIHNhbWUgcmVzdWx0IGJlbG93Li4uCgpgYGB7ciwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQptYXBkYXRhMyA8LSBnZXRfZGF0YV9mcm9tX21hcChkb3dubG9hZF9tYXBfZGF0YSgiY291bnRyaWVzL3VzL3VzLW55LWFsbCIpKQpoY21hcCgiY291bnRyaWVzL3VzL3VzLW55LWFsbCIpICU+JQogIGhjX21hcE5hdmlnYXRpb24oZW5hYmxlZCA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICBidXR0b25PcHRpb25zID0gbGlzdCgKICAgICAgICAgICAgICAgICAgICAgYWxpZ24gPSAicmlnaHQiLAogICAgICAgICAgICAgICAgICAgICB2ZXJ0aWNhbEFsaWduID0gImJvdHRvbSIpKSAlPiUKICBoY19hZGRfc2VyaWVzKGRhdGEgPSB0cmlwYWR2aXNvciAlPiUgbXV0YXRlKGxvbiA9IExvbmdpdHVkZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXQgPSBMYXRpdHVkZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gTXVzZXVtTmFtZSksICAjIEFkZCBjb3JyZWN0IG5hbWVzCiAgICAgICAgICAgICAgICB0eXBlID0gIm1hcHBvaW50IiwgbmFtZSA9ICJNdXNldW1zIikgCmBgYAoKCiMgUmVzb3VyY2VzCgpCZWxvdywgYSBsaW5rIG9mIGdyZWF0IHBhZ2VzICYgdHV0b3JpYWxzOgoKKipHZW9jb21wdXRhdGlvbiB3aXRoIFIqKiAob25saW5lIGJvb2spOiBodHRwczovL2dlb2NvbXByLnJvYmlubG92ZWxhY2UubmV0ICAgIAoqKmdncGxvdCoqITogaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL21hcF9kYXRhLmh0bWxQdXJlICAgICAKKipzZioqIChzaGFwZSBmaWxlcyk6IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9zZi92aWduZXR0ZXMvc2YxLmh0bWwgICAKKipzZiAmIFIqKjogaHR0cHM6Ly9yLXNwYXRpYWwuZ2l0aHViLmlvL3NmL2FydGljbGVzL3NmMS5odG1sICAgCioqc2YgJiBnZ3Bsb3QqKjogaHR0cHM6Ly93d3cuci1zcGF0aWFsLm9yZy9yLzIwMTgvMTAvMjUvZ2dwbG90Mi1zZi0yLmh0bWwgICAKKipsaXN0IG9mIHNmKio6IGh0dHA6Ly9kYXRhcGFnZXMuY29tL2dpcy1tYXAtcHVibGlzaGluZy1wcm9ncmFtL2dpcy1vcGVuLWZpbGVzL2dsb2JhbC1mcmFtZXdvcmsvZ2xvYmFsLWhlYXQtZmxvdy1kYXRhYmFzZS9zaGFwZWZpbGVzLWxpc3QgIAoqKnRtYXAqKjogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3RtYXAvdmlnbmV0dGVzL3RtYXAtZ2V0c3RhcnRlZC5odG1sICAgCioqZ2dtYXAqKjogaHR0cHM6Ly9naXRodWIuY29tL2RrYWhsZS9nZ21hcCAgICAKKipsZWFmbGV0Kio6IGh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vbGVhZmxldC8gICAgCioqT3JlZ29uKiogKHR1dG9yaWFsKTogaHR0cDovL2dlb2cudW9yZWdvbi5lZHUvYmFydGxlaW4vY291cnNlcy9nZW9nNDk1L2xlYzA2Lmh0bWwgICAgCgoKCgojIEEgdGVzdCB3aXRoIEdlb0pTT04KCkdlb0pTT04gYXJlLCB3aXRoIHNoYXBlZmlsZXMgdGhlIG90aGVyIG1haW4gc3RhbmRhcmQgaW4gZ2VvY29tcHV0aW5nLiAgIApJdCdzIGVhc3kgdG8gb2J0YWluIGZpbGVzLCBlLmcuLCB2aWEgaHR0cHM6Ly9nZW9qc29uLW1hcHMuYXNoLm1zCkFsc28sIHlvdSB3aWxsIG5lZWQgdGhlICoqcmdkYWwqKiBwYWNrYWdlLgoKCmBgYHtyLCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CiNsaWJyYXJ5KHJnZGFsKQpsaWJyYXJ5KGdlb2pzb25zZikKI2FmcmljYSA8LSByZ2RhbDo6cmVhZE9HUigiYWZyaWNhLmdlby5qc29uIikKI2FmcmljYSA8LSBnZGFsX3JlYWQoImN1c3RvbS5nZW8uanNvbiIpCmFmcmljYSA8LSBnZW9qc29uX3NmKCJhZnJpY2EuZ2VvLmpzb24iKQphZnJpY2EgPC0gYWZyaWNhIHw+IG11dGF0ZShwb3BfZXN0ID0gcm91bmQocG9wX2VzdC8xMF42LDEpKSAKCmxhYmVscyA8LSBzcHJpbnRmKAogICI8c3Ryb25nPiVzPC9zdHJvbmc+PGJyLz4lZyBNIGluaGFiaXRhbnRzIiwKICBhZnJpY2EkbmFtZSwgYWZyaWNhJHBvcF9lc3QKKSAlPiUgbGFwcGx5KGh0bWx0b29sczo6SFRNTCkKYGBgCgpgYGB7cn0KcGFsIDwtIGNvbG9yQmluKCJZbE9yUmQiLCBkb21haW4gPSBhZnJpY2EkcG9wX2VzdCwgCiAgICAgICAgICAgICAgICBiaW5zID0gYygwLCAxMDAsIDUwMCwgMTAwMCwgNTAwMCwgMTAwMDAsIEluZikpCgpsZWFmbGV0KGFmcmljYSkgJT4lCiAgYWRkVGlsZXMoKSAlPiUKICBhZGRQb2x5Z29ucyhmaWxsT3BhY2l0eSA9IDAuNywKICAgICAgICAgICAgICBmaWxsQ29sb3IgPSB+cGFsKHBvcF9lc3QpLAogICAgICAgICAgICAgIHdlaWdodCA9IDIsCiAgICAgICAgICAgICAgb3BhY2l0eSA9IDEsCiAgICAgICAgICAgICAgY29sb3IgPSAid2hpdGUiLAogICAgICAgICAgICAgICMgaGlnaGxpZ2h0ID0gaGlnaGxpZ2h0T3B0aW9ucyggICAgICAgIyA1IGxpbmVzIGJlbG93IGNvbnRyb2wgdGhlIGN1cnNvciBpbXBhY3QKICAgICAgICAgICAgICAjICAgd2VpZ2h0ID0gMiwgICAgICAgICAgICAgICAgICAgICAgICMgV2lkdGggb2YgbGluZQogICAgICAgICAgICAgICMgICBjb2xvciA9ICIjMzMzMzMzIiwgICAgICAgICAgICAgICAgIyBDb2xvciBvZiBsaW5lCiAgICAgICAgICAgICAgIyAgIGRhc2hBcnJheSA9ICIiLCAgICAgICAgICAgICAgICAgICAjIE5vIGRhc2gKICAgICAgICAgICAgICAjICAgZmlsbE9wYWNpdHkgPSAwLjksICAgICAgICAgICAgICAgICMgT3BhY2l0eQogICAgICAgICAgICAgICMgICBicmluZ1RvRnJvbnQgPSBUUlVFKSwKICAgICAgICAgICAgICBsYWJlbCA9IGxhYmVscywKICAgICAgICAgICAgICBsYWJlbE9wdGlvbnMgPSBsYWJlbE9wdGlvbnMoCiAgICAgICAgICAgICAgICBzdHlsZSA9IGxpc3QoImZvbnQtd2VpZ2h0IiA9ICJub3JtYWwiLCBwYWRkaW5nID0gIjNweCA4cHgiKSwKICAgICAgICAgICAgICAgIHRleHRzaXplID0gIjE1cHgiLAogICAgICAgICAgICAgICAgZGlyZWN0aW9uID0gImF1dG8iKSkgJT4lCiAgYWRkTGVnZW5kKHBhbCA9IHBhbCwgdmFsdWVzID0gfnBvcF9lc3QsIG9wYWNpdHkgPSAwLjcsIHRpdGxlID0gTlVMTCwKICAgICAgICAgICAgcG9zaXRpb24gPSAiYm90dG9tbGVmdCIpCmBgYAoKCmBgYHtyfQoKYGBgCgpgYGB7cn0KCmBgYAoK