1 Drawing maps with ggplot

The step-by-step process of making a map showing some information is the following:

  1. Get the map data, \(M\), e.g. the world map.
  2. Get the data you want to visualize, \(D\), e.g. life expectancy for all countries.
  3. Make sure the region names used by the map \(M\) match the names used by the data \(D\). Recode all the names in \(D\) that don’t match.
  4. Add the variable(s) you want to visualize to the map data, by merging \(M\) and \(D\).
  5. Make the map using ggplot, using the fill aesthetic to show your variable of interest. All ggplot’s options, like color palettes, faceting (to create many maps at once) or adding more things to the map, are available to you.

1.1 Map data

You can get various maps from the maps and mapdata packages, and then use the tidyverse function map_data() to transform the maps into ggplot-ready dataframes. Available maps:

Usage Map Package
“county” US counties maps
“china” China provincial boundaries mapdata
“france” France NUTS III maps
“italy” Italy NUTS III maps
“japan” Japan prefecture boundaries mapdata
“legacy_world” World in 1990, Atlantic centered maps
“legacy_world2” World in 1990, Pacific centered maps
“nz” New Zealand maps
“nzHires” New Zealand, high resolution mapdata
“state” US state boundaries maps
“usa” US coast maps
“world” World, present, Atlantic centered maps
“world2” World, present, Pacific centered maps
“worldHires” World, present, high resolution, Atlantic centered mapdata
“world2Hires” World, present, high resolution, Pacific centered mapdata
“worldLores” World, present, low resolution, Atlantic centered mapdata
“world2Lores” World, present, low resolution, Pacific centered mapdata

To plot the map in ggplot we use the geom_polygon (except for China, see example below), using the variables aes(x = long, y = lat, group = group). The group variable is needed to connect the points properly. To add information to the map we will use the fill variable. The color variable can be used for the boundaries. Finally, we use coord_fixed(1.3) to keep the proportions fixed (you can play with the 1.3 number)

library(maps)
library(mapdata)
library(tidyverse)
library(ggrepel)

1.2 Examples

World

ggplot() + 
  geom_polygon(data = map_data("world"),
               mapping = aes(x = long, y = lat, group = group),
               fill = "dark gray", color = "white") + 
  coord_fixed(1.3) +
  theme_void()

France

ggplot() + 
  geom_polygon(data = map_data("france"),
               mapping = aes(x = long, y = lat, group = group),
               fill = "dark gray", color = "white") + 
  coord_fixed(1.3) +
  theme_void()

Italy

ggplot() + 
  geom_polygon(data = map_data("italy"),
               mapping = aes(x = long, y = lat, group = group),
               fill = "dark gray", color = "white") + 
  coord_fixed(1.3) +
  theme_void()

New Zealand

ggplot() + 
  geom_polygon(data = map_data("nz"),
               mapping = aes(x = long, y = lat, group = group),
               fill = "dark gray", color = "white") + 
  coord_fixed(1.3) +
  theme_void()

Japan

ggplot() + 
  geom_polygon(data = map_data("japan"),
               mapping = aes(x = long, y = lat, group = group),
               fill = "light gray", color = "black") + 
  coord_fixed(1.3) +
  theme_void()

United States

ggplot() + 
  geom_polygon(data = map_data("county"),
               aes(x = long, y = lat, group = group),
               fill = "white", color = "light gray") +
  geom_polygon(data = map_data("state"),
               aes(x = long, y = lat, group = group),
               fill = NA, color = "gray") +
  coord_fixed(1.3) +
  theme_void()

China

ggplot() + 
  geom_path(data = maps::map("china", plot = FALSE),
            mapping = aes(x = long, y = lat, group = group),
            color = "black") + 
  coord_fixed(1.3) +
  theme_void()

2 Adding aggregated stats to the world map

We want to use the maps to show various variables. For instance, let’s see how to make a world map showing the variation in life expectancy.

If we look at the map_data("world") we see it has the following structure:

head(map_data("world"))

The key to adding information to the map is to add variables to this dataset, such as the life expectancy variable, making sure you match the region name properly.

2.1 Getting the QoG data

Let’s load the cross-section data from the Quality of Government Institute:

qog <- rio::import("http://www.qogdata.pol.gu.se/dataarchive/qog_std_cs_jan18.dta")

The variables in the dataset:

data.frame(
    Variable    = names(qog), 
    Description = sjlabelled::get_label(qog), 
    Obs.        = map_dbl(qog, ~table(is.na(.x))[1]),
    Missing     = map_dbl(qog, ~table(is.na(.x))[2])
  ) %>%
  DT::datatable(rownames = FALSE)

If you search for “life expectancy” you’ll find several variables. I’m going to use wdi_lifexp which is from the World Bank.

2.2 Matching country names

The main difficulty in adding this data to the map is that the region names in the map dataset do not match exactly the country names in the QoG data:

# which countries in qog don't match world map names properly
setdiff(qog$cname, map_data("world")$region)
##  [1] "Antigua and Barbuda"           "Congo"                        
##  [3] "Congo, Democratic Republic"    "Cyprus (1975-)"               
##  [5] "Ethiopia (1993-)"              "France (1963-)"               
##  [7] "Cote d'Ivoire"                 "Korea, North"                 
##  [9] "Korea, South"                  "Malaysia (1966-)"             
## [11] "Pakistan (1971-)"              "St Kitts and Nevis"           
## [13] "St Lucia"                      "St Vincent and the Grenadines"
## [15] "Sudan (2012-)"                 "Trinidad and Tobago"          
## [17] "Tuvalu"                        "United Kingdom"               
## [19] "United States"

These are the country names that we need to correct.

Here are the names in the world map that don’t match the QoG properly:

# which world map names don't match countries in qog 
setdiff(map_data("world")$region, qog$cname)
##  [1] "Aruba"                               "Anguilla"                           
##  [3] "American Samoa"                      "Antarctica"                         
##  [5] "French Southern and Antarctic Lands" "Antigua"                            
##  [7] "Barbuda"                             "Saint Barthelemy"                   
##  [9] "Bermuda"                             "Ivory Coast"                        
## [11] "Democratic Republic of the Congo"    "Republic of Congo"                  
## [13] "Cook Islands"                        "Curacao"                            
## [15] "Cayman Islands"                      "Cyprus"                             
## [17] "Canary Islands"                      "Ethiopia"                           
## [19] "Falkland Islands"                    "Reunion"                            
## [21] "Mayotte"                             "French Guiana"                      
## [23] "Martinique"                          "Guadeloupe"                         
## [25] "France"                              "Faroe Islands"                      
## [27] "UK"                                  "Guernsey"                           
## [29] "Greenland"                           "Guam"                               
## [31] "Heard Island"                        "Isle of Man"                        
## [33] "Cocos Islands"                       "Christmas Island"                   
## [35] "Chagos Archipelago"                  "Jersey"                             
## [37] "Siachen Glacier"                     "Nevis"                              
## [39] "Saint Kitts"                         "South Korea"                        
## [41] "Kosovo"                              "Saint Lucia"                        
## [43] "Saint Martin"                        "Northern Mariana Islands"           
## [45] "Montserrat"                          "Malaysia"                           
## [47] "New Caledonia"                       "Norfolk Island"                     
## [49] "Niue"                                "Bonaire"                            
## [51] "Sint Eustatius"                      "Saba"                               
## [53] "Pakistan"                            "Pitcairn Islands"                   
## [55] "Puerto Rico"                         "North Korea"                        
## [57] "Madeira Islands"                     "Azores"                             
## [59] "Palestine"                           "French Polynesia"                   
## [61] "Western Sahara"                      "Sudan"                              
## [63] "South Sandwich Islands"              "South Georgia"                      
## [65] "Saint Helena"                        "Ascension Island"                   
## [67] "Saint Pierre and Miquelon"           "Sint Maarten"                       
## [69] "Turks and Caicos Islands"            "Trinidad"                           
## [71] "Tobago"                              "USA"                                
## [73] "Vatican"                             "Grenadines"                         
## [75] "Saint Vincent"                       "Virgin Islands"                     
## [77] "Wallis and Futuna"

We can see from this list what names should’ve been in the QoG data. For instance, instead of “United States” it should be “USA”.

The most straigthforward thing to do now is to just manually recode the country names in the QoG to match the names that the map uses. It’s a bit tedious, but only takes a few minutes. I’m creating a new variable called cname1 with the corrected country names:

# recode qog cnames
qog$cname1 <- qog$cname %>% 
  recode("Antigua and Barbuda" = "Antigua",
         "Congo" = "Republic of Congo",
         "Congo, Democratic Republic" = "Democratic Republic of the Congo",
         "Cote d'Ivoire" = "Ivory Coast",
         "Cyprus (1975-)" = "Cyprus",
         "Ethiopia (1993-)" = "Ethiopia",
         "France (1963-)" = "France",
         "Korea, North" = "North Korea",
         "Korea, South" = "South Korea",
         "Malaysia (1966-)" = "Malaysia",
         "Pakistan (1971-)" = "Pakistan",
         "St Kitts and Nevis" = "Saint Kitts",
         "St Lucia" = "Saint Lucia",
         "St Vincent and the Grenadines" = "Saint Vincent",
         "Sudan (2012-)" = "Sudan",
         "Trinidad and Tobago" = "Trinidad",
         "Tuvalu" = "Tuvalu",  # not on map? or I can't find the proper name
         "United Kingdom" = "UK",
         "United States" = "USA")

We can now add the data to the map dataset, and plot it with ggplot using the fill variable to show the life expectancies:

# add life expectency to the map data
WorldMapData1 <- full_join(map_data("world"), 
                          qog %>% select(cname1, 
                                         wdi_gdpcappppcur, 
                                         wdi_povgap190,
                                         wdi_lifexp),
                          by = c("region" = "cname1"))

Make the map:

WorldMapData1 %>% 
  ggplot() + 
  geom_polygon(aes(x = long, y = lat, group = group, fill = wdi_gdpcappppcur)) + 
  coord_fixed(1.1) +
  theme_void() +
  labs(fill = "Average\nincomes", title = "GDP per capita, current dollars, PPP") +
  scale_fill_binned(low = "tomato2", high = "dark blue", 
                    trans = "log", 
                    labels = scales::dollar_format(accuracy = 1),
                    breaks = c(1000, 2000, 10000, 30000, 60000)) +
  ggthemes::theme_map(base_family = "serif", base_size = 18)

WorldMapData1 %>% 
  ggplot() + 
  geom_polygon(aes(x = long, y = lat, group = group, fill = wdi_lifexp)) + 
  coord_fixed(1.1) +
  theme_void() +
  labs(fill = "Years", title = "Life expectancy at birth") +
  scale_fill_binned(low = "black", high = "light gray", 
                    #trans = "log", 
                    labels = scales::comma) +
  ggthemes::theme_map(base_family = "serif", base_size = 18)

2.3 Automatic country names matching

The alternative to matching the names by hand is to use the function maps::iso.expand() which allows you to create the proper map names from standard ISO codes. The QoG contains the 3-letter ISO codes in the variable ccodealp. (The maps::iso.alpha() function does the reverse conversion, from map region names to the ISO codes.)

qog$cname2 <- iso.expand(qog$ccodealp)

This creates a complication though, because the ISO codes don’t have a 1-to-1 correspondence to region names on the map, and iso.expand() creates regex expressions. As such, you need to do a fuzzy join when you’re merging the datasets:

# add life expectency to the map data
WorldMapData2 <- fuzzyjoin::regex_full_join(
                          map_data("world"), 
                          qog %>% select(cname2, wdi_gdpcappppcur),
                          by = c("region" = "cname2"))

Here’s the same map again:

WorldMapData2 %>% 
  ggplot() + 
  geom_polygon(aes(x = long, y = lat, group = group, fill = wdi_gdpcappppcur)) + 
  coord_fixed(1.1) +
  theme_void() +
  labs(fill = "Life\nexpectancy\nat birth") +
  scale_fill_gradient(low = "red", high = "dark blue", trans = "log")

2.4 Examples:

Of course, once you matched the country names (by one method or another), you can add any other variables from the QoG. Here’s economic freedom from Fraser Institute, democracy from Freedom House, and liberal democracy from the Varieties of Democracy dataset:

# add variables to the map data
WorldMapData <- full_join(
                  map_data("world"), 
                  qog %>% select(cname1, wdi_lifexp, 
                                 fi_index, vdem_libdem, fh_polity2),
                  by = c("region" = "cname1")
                )

I’m also illustrating different color scales, there are basically endless visual possibilities.

Economic freedom

WorldMapData %>% 
  ggplot() + 
  geom_polygon(aes(x = long, y = lat, group = group, fill = fi_index)) + 
  coord_fixed(1.1) +
  theme_void() +
  labs(fill = "Economic\nfreedom") +
  scale_fill_viridis_c()

Democracy (polity2, Freedom House)

WorldMapData %>% 
  ggplot() + 
  geom_polygon(aes(x = long, y = lat, group = group, fill = fh_polity2)) + 
  coord_fixed(1.1) +
  theme_void() +
  labs(fill = "Democracy") +
  see::scale_fill_pizza_c() # gradient(low = "red", high = "blue")

Liberal democracy (V-Dem)

WorldMapData %>% 
  ggplot() + 
  geom_polygon(aes(x = long, y = lat, group = group, fill = vdem_libdem)) + 
  coord_fixed(1.1) +
  theme_void() +
  labs(fill = "Liberal\ndemocracy") +
  ggthemes::scale_fill_continuous_tableau()

3 Adding aggregated stats to the US states map

3.1 Loading state policies data

Let’s use the Sorens, Muedini and Ruger dataset of state policies.1

After you download the data and merge everything using Jason Sorens’ R script, you have the following variables for all states, from 1937 to 2017:

state_policy      <- readRDS("data/state_policies.rds")
state_policy_meta <- readRDS("data/state_policies_meta.rds")

The available variables:

state_policy_meta %>% 
  select(Variable = `Variable Code`, Description = `Variable Name`) %>% 
  mutate(Variable    = stringi::stri_enc_toascii(Variable),
         Description = stringi::stri_enc_toascii(Description)) %>%
  DT::datatable()

(I’m recoding the variables to ASCII to eliminate some special characters, which otherwise give an error in the DT::datatable().)

3.2 Checking that state names match with map state names

Before we add a variable to the map, let us first check the state names are ok. Check out the state names in the map data:

map_data("state")$region %>% unique()
##  [1] "alabama"              "arizona"              "arkansas"            
##  [4] "california"           "colorado"             "connecticut"         
##  [7] "delaware"             "district of columbia" "florida"             
## [10] "georgia"              "idaho"                "illinois"            
## [13] "indiana"              "iowa"                 "kansas"              
## [16] "kentucky"             "louisiana"            "maine"               
## [19] "maryland"             "massachusetts"        "michigan"            
## [22] "minnesota"            "mississippi"          "missouri"            
## [25] "montana"              "nebraska"             "nevada"              
## [28] "new hampshire"        "new jersey"           "new mexico"          
## [31] "new york"             "north carolina"       "north dakota"        
## [34] "ohio"                 "oklahoma"             "oregon"              
## [37] "pennsylvania"         "rhode island"         "south carolina"      
## [40] "south dakota"         "tennessee"            "texas"               
## [43] "utah"                 "vermont"              "virginia"            
## [46] "washington"           "west virginia"        "wisconsin"           
## [49] "wyoming"

Notice that Alaska and Hawaii are missing, and the region names are all lower case. So let’s first change the names in the state_policy dataset to lower case:

state_policy$State1 <- str_to_lower(state_policy$State)

Let’s double check that the state names match:

setdiff(state_policy$State1, map_data("state")$region)
## [1] "alaska" "hawaii" NA

It looks like they are all good! Alaska and Hawaii don’t appear on the map, because the map is only of the mainland.

NOTE: If the names didn’t match, the maps package offers a handy rescue: the state.fips dataset gives the US Census Bureau numeric identification for the states (as well as region, etc.). Similarly, if you’re plotting county-level data, there’s the county.fips dataset with the US Census Bureau FIPS identification numbers for counties.

3.3 Adding state variables to the map

Let’s make maps of following variable:

  • Apovrate: Percentage of state population in poverty
  • Apopdens: Population density (Apop/Aland)
  • cpbeerc: State average beer prices, in constant 2008 dollars
  • cspirwet: Percentage of state population living in counties that are wet for distilled spirits
  • Bopen: Open carry of loaded handgun (permitted without permit=2, permitted with permit=1, not generally permitted=0)

Add the variables to the map data:

StatesMapData <- full_join(
                    map_data("state"), 
                    state_policy %>% 
                      select(State1, Year,
                             Apovrate, Apopdens, 
                             cpbeerc, cspirwet, 
                             Bopen),
                    by = c("region" = "State1")
                  )

3.4 Examples:

Poverty

StatesMapData %>%
  filter(Year >= 1980) %>% 
  filter(Year %% 5 == 0) %>% 
  drop_na(Year) %>% 

ggplot() + 
  geom_polygon(aes(x = long, y = lat, group = group, fill = Apovrate)) +
  coord_fixed(1.3) +
  theme_void() +
  labs(fill = "Poverty\nrate") +
  scale_fill_gradient(low = "dark blue", high = "red") +
  facet_wrap(~Year, ncol = 2) +
  theme(legend.position = "bottom", legend.direction = "horizontal")

Population density

ggplot() + 
  geom_polygon(data = StatesMapData %>% filter(Year == 2015),
               aes(x = long, y = lat, group = group, fill = Apopdens),
               color = "light gray") +
  coord_fixed(1.3) +
  theme_void() +
  labs(fill = "Population\ndensity") +
  scale_fill_continuous(trans = "log", labels = scales::comma)

Beer prices

ggplot() + 
  geom_polygon(data = StatesMapData %>% filter(Year == 2003),
               aes(x = long, y = lat, group = group, fill = cpbeerc),
               color = "gray") +
  coord_fixed(1.3) +
  theme_void() +
  labs(fill = "Beer\nprices") +
  scale_fill_gradient(low = "dark blue", high = "red")

Wet counties (spirits)

StatesMapData %>% filter(Year %in% c(1967, 1984)) %>% 

ggplot() + 
  geom_polygon(aes(x = long, y = lat, group = group, fill = cspirwet),
               color = "gray") +
  coord_fixed(1.3) +
  scale_fill_gradient(low = "red", high = "dark blue") +
  labs(fill = "Wet counties:\n") +
  theme_void() +
  theme(legend.position = "bottom", legend.direction = "horizontal") +
  facet_wrap(~Year)

Open carry

StatesMapData %>% 
  filter(Year %% 5 == 0) %>% 
  drop_na(Bopen) %>% 
  mutate(Bopen = recode(Bopen,
    `2` = "permitted without permit",
    `1` = "permitted with permit", 
    `0` = "not generally permitted"
  )) %>% 

ggplot() + 
  geom_polygon(aes(x = long, y = lat, group = group, fill = Bopen),
               color = "gray") +
  coord_fixed(1.3) +
  theme_void() +
  labs(fill = "Open carry:") +
  see::scale_fill_flat_d(reverse = TRUE) +
  facet_wrap(~Year, ncol = 2) +
  theme(legend.position = "top", legend.direction = "horizontal")

4 Adding cities to the map

We can add things to the map in standard ggplot fashion – using additional geoms. For example, let me manually add Tucson and Denver to the US map.

First, google the latitude and longitude of the cities and then add the information to a dataframe:

cities <- tribble(
      ~long,    ~lat,      ~name,
  -110.9747,   32.2226, "Tucson",
  -104.9903,   39.7392, "Denver"
  )

Now we can just add them to the US map:

ggplot() + 
  geom_polygon(data = map_data("usa"),
               mapping = aes(x = long, y = lat, group = group),
               fill = "light gray", color = "light gray") + 
  geom_point(data = cities, aes(x = long, y = lat), color = "black") +
  geom_text(data = cities, aes(x = long, y = lat, label = name), nudge_y = 1) +
  coord_fixed(1.3) +
  theme_void()

But we can do better than add the cities manually. The maps package also contains a few dataframes of cities locations.

Dataset Description
canada.cities Canadian cities larger than 1,000
us.cities US cities larger than 40,000
world.cities World cities larger than 40,000

Here is the structure of the world.cities data frame:

head(world.cities)

Let’s add all US cities greater than half a million people to the map:

ggplot() + 
  geom_polygon(data = map_data("usa"),
               aes(x = long, y = lat, group = group),
               fill = "light gray", color = "light gray") + 
  geom_point(data = us.cities %>% filter(pop > 500000),
             aes(x = long, y = lat)) +
  geom_text_repel(data = us.cities %>% filter(pop > 500000),
            aes(x = long, y = lat, label = name)) +
  coord_fixed(1.3) +
  theme_void()

Here are all world cities larger than one million, with labels for cities greater than 5 million:

ggplot() + 
  geom_polygon(data = map_data("world"),
               aes(x = long, y = lat, group = group),
               fill = "light gray", color = "white") + 
  geom_point(data = world.cities %>% filter(pop > 1000000),
             aes(x = long, y = lat), color = "dark gray") +
  geom_text_repel(data = world.cities %>% filter(pop > 5000000),
            aes(x = long, y = lat, label = name), color = "black") +
  coord_fixed(1.3) +
  theme_void()

4.1 Zooming in on the map

We can zoom in by specifying the limits of the map in the coord_fixed() option. The high resolution mapHires is useful particularly for zooming in, but I’m still using the regular map here. Here’s for example a zoom on China:

ggplot() + 
  geom_polygon(data = map_data("world"),
               aes(x = long, y = lat, group = group),
               fill = "light gray", color = "white") + 
  geom_point(data = world.cities %>% filter(pop > 500000, country.etc == "China"),
             aes(x = long, y = lat), color = "dark gray") +
  geom_text_repel(data = world.cities %>% filter(pop > 2000000, country.etc == "China"),
            aes(x = long, y = lat, label = name), color = "black") +
  coord_fixed(1.3, xlim = c(75, 135), ylim = c(20, 50)) +
  theme_void()

I got the limits from this map. Notice that I’ve also changed what cities to show: I’m now showing all Chinese cities greater than half a million, and labeling all cities greater than 2 million.

5 Adding detailed information to a map

Let’s plot the crime rates in various places, with data taken from the Open Crime Database. This data is even more precise than the county-level.

library(crimedata)

Get a small sample of the available crime data, using the “simple features” (sf) output option:

crime <- get_crime_data(type = "sample", quiet = TRUE, output = "sf")

This is how the data looks like:

head(crime)

Let us consider only the robberies and focus on Los Angeles:

crimeLA <- crime %>% 
  filter(offense_group == "robbery", city_name == "Los Angeles") 
map_data("county") %>%
  filter(subregion == "los angeles") %>%
  ggplot() +
    geom_polygon(aes(x = long, y = lat, group = group),
                 fill = "light gray", color = "white") +
    geom_sf(data = crimeLA) 

Make a map showing robberies density:

ggplot() +
  geom_polygon(data = map_data("state"), 
               aes(x = long, y = lat, group = group),
               color = "white", fill = "light gray") +
  geom_count(data = crime %>% filter(offense_group == "robbery"),
                  aes(x = longitude, y = latitude),
             alpha = 0.5) +
  theme_void()


  1. Sorens, Jason, Fait Muedini, and William P. Ruger. 2008. “State and Local Public Policies in 2006: A New Database.” State Politics and Policy Quarterly 8 (3): 309–26.↩︎

LS0tCnRpdGxlOiAiTWFraW5nIHNpbXBsZSBtYXBzIGluIFIiCmF1dGhvcjogIlZsYWQgVGFya28iCmRhdGU6ICI4LzYvMjAxOSIKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgdGhlbWU6IHlldGkKICAgIGRmX3ByaW50OiBwYWdlZAogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoCgllY2hvID0gVFJVRSwKCW1lc3NhZ2UgPSBGQUxTRSwKCXdhcm5pbmcgPSBGQUxTRQopCmBgYAoKIyBEcmF3aW5nIG1hcHMgd2l0aCBgZ2dwbG90YAoKVGhlIHN0ZXAtYnktc3RlcCBwcm9jZXNzIG9mIG1ha2luZyBhIG1hcCBzaG93aW5nIHNvbWUgaW5mb3JtYXRpb24gaXMgdGhlIGZvbGxvd2luZzoKCjEuIEdldCB0aGUgbWFwIGRhdGEsICRNJCwgZS5nLiB0aGUgd29ybGQgbWFwLgoyLiBHZXQgdGhlIGRhdGEgeW91IHdhbnQgdG8gdmlzdWFsaXplLCAkRCQsIGUuZy4gbGlmZSBleHBlY3RhbmN5IGZvciBhbGwgY291bnRyaWVzLgozLiBNYWtlIHN1cmUgdGhlIHJlZ2lvbiBuYW1lcyB1c2VkIGJ5IHRoZSBtYXAgJE0kIG1hdGNoIHRoZSBuYW1lcyB1c2VkIGJ5IHRoZSBkYXRhICREJC4gUmVjb2RlIGFsbCB0aGUgbmFtZXMgaW4gJEQkIHRoYXQgZG9uJ3QgbWF0Y2guCjQuIEFkZCB0aGUgdmFyaWFibGUocykgeW91IHdhbnQgdG8gdmlzdWFsaXplIHRvIHRoZSBtYXAgZGF0YSwgYnkgbWVyZ2luZyAkTSQgYW5kICREJC4KNS4gTWFrZSB0aGUgbWFwIHVzaW5nIGBnZ3Bsb3RgLCB1c2luZyB0aGUgYGZpbGxgIGFlc3RoZXRpYyB0byBzaG93IHlvdXIgdmFyaWFibGUgb2YgaW50ZXJlc3QuIEFsbCBgZ2dwbG90YCdzIG9wdGlvbnMsIGxpa2UgY29sb3IgcGFsZXR0ZXMsIGZhY2V0aW5nICh0byBjcmVhdGUgbWFueSBtYXBzIGF0IG9uY2UpIG9yIGFkZGluZyBtb3JlIHRoaW5ncyB0byB0aGUgbWFwLCBhcmUgYXZhaWxhYmxlIHRvIHlvdS4KCiMjIE1hcCBkYXRhCgpZb3UgY2FuIGdldCB2YXJpb3VzIG1hcHMgZnJvbSB0aGUgYG1hcHNgIGFuZCBgbWFwZGF0YWAgcGFja2FnZXMsIGFuZCB0aGVuIHVzZSB0aGUgdGlkeXZlcnNlIGZ1bmN0aW9uIGBtYXBfZGF0YSgpYCB0byB0cmFuc2Zvcm0gdGhlIG1hcHMgaW50byBgZ2dwbG90YC1yZWFkeSBkYXRhZnJhbWVzLiBBdmFpbGFibGUgbWFwczoKCnwgVXNhZ2UgfCBNYXAgfCBQYWNrYWdlIHwKfC0tLS0tfC0tLS0tLXwtLS0tLS0tLS18CnwgImNvdW50eSIgfCAgVVMgY291bnRpZXMgfCBgbWFwc2AgfAp8ICJjaGluYSIgfCBDaGluYSBwcm92aW5jaWFsIGJvdW5kYXJpZXMgfCBgbWFwZGF0YWAgfAp8ICJmcmFuY2UiIHwgRnJhbmNlIE5VVFMgSUlJIHwgYG1hcHNgIHwKfCAiaXRhbHkiIHwgSXRhbHkgTlVUUyBJSUkgfCBgbWFwc2AgfAp8ICJqYXBhbiIgfCBKYXBhbiBwcmVmZWN0dXJlIGJvdW5kYXJpZXMgfCBgbWFwZGF0YWAgfAp8ICJsZWdhY3lfd29ybGQiIHwgV29ybGQgaW4gMTk5MCwgQXRsYW50aWMgY2VudGVyZWQgfCBgbWFwc2AgfAp8ICJsZWdhY3lfd29ybGQyIgl8IFdvcmxkIGluIDE5OTAsIFBhY2lmaWMgY2VudGVyZWQgfCBgbWFwc2AgfAp8ICJueiIgfCBOZXcgWmVhbGFuZCAgfCBgbWFwc2AgfAp8ICJuekhpcmVzIiB8IE5ldyBaZWFsYW5kLCBoaWdoIHJlc29sdXRpb24gIHwgYG1hcGRhdGFgIHwKfCAic3RhdGUiIHwgVVMgc3RhdGUgYm91bmRhcmllcyB8IGBtYXBzYCB8CnwgInVzYSIgfCBVUyBjb2FzdCB8IGBtYXBzYCB8CnwgIndvcmxkIiB8IFdvcmxkLCBwcmVzZW50LCBBdGxhbnRpYyBjZW50ZXJlZCB8IGBtYXBzYCB8CnwgIndvcmxkMiIgfCBXb3JsZCwgcHJlc2VudCwgUGFjaWZpYyBjZW50ZXJlZCAgfCBgbWFwc2AgfAp8ICJ3b3JsZEhpcmVzIiB8IFdvcmxkLCBwcmVzZW50LCBoaWdoIHJlc29sdXRpb24sIEF0bGFudGljIGNlbnRlcmVkIHwgYG1hcGRhdGFgIHwKfCAid29ybGQySGlyZXMiIHwgV29ybGQsIHByZXNlbnQsIGhpZ2ggcmVzb2x1dGlvbiwgUGFjaWZpYyBjZW50ZXJlZCAgfCBgbWFwZGF0YWAgfAp8ICJ3b3JsZExvcmVzIiB8IFdvcmxkLCBwcmVzZW50LCBsb3cgcmVzb2x1dGlvbiwgQXRsYW50aWMgY2VudGVyZWQgfCBgbWFwZGF0YWAgfAp8ICJ3b3JsZDJMb3JlcyIgfCBXb3JsZCwgcHJlc2VudCwgbG93IHJlc29sdXRpb24sIFBhY2lmaWMgY2VudGVyZWQgIHwgYG1hcGRhdGFgIHwKClRvIHBsb3QgdGhlIG1hcCBpbiBgZ2dwbG90YCB3ZSB1c2UgdGhlIGBnZW9tX3BvbHlnb25gIChleGNlcHQgZm9yIENoaW5hLCBzZWUgZXhhbXBsZSBiZWxvdyksIHVzaW5nIHRoZSB2YXJpYWJsZXMgYGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cClgLiBUaGUgYGdyb3VwYCB2YXJpYWJsZSBpcyBuZWVkZWQgdG8gY29ubmVjdCB0aGUgcG9pbnRzIHByb3Blcmx5LiBUbyBhZGQgaW5mb3JtYXRpb24gdG8gdGhlIG1hcCB3ZSB3aWxsIHVzZSB0aGUgYGZpbGxgIHZhcmlhYmxlLiBUaGUgYGNvbG9yYCB2YXJpYWJsZSBjYW4gYmUgdXNlZCBmb3IgdGhlIGJvdW5kYXJpZXMuIEZpbmFsbHksIHdlIHVzZSBgY29vcmRfZml4ZWQoMS4zKWAgdG8ga2VlcCB0aGUgcHJvcG9ydGlvbnMgZml4ZWQgKHlvdSBjYW4gcGxheSB3aXRoIHRoZSAxLjMgbnVtYmVyKQoKYGBge3J9CmxpYnJhcnkobWFwcykKbGlicmFyeShtYXBkYXRhKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShnZ3JlcGVsKQpgYGAKCgojIyBFeGFtcGxlcyB7LnRhYnNldH0KCiMjIyBXb3JsZCB7LX0KCmBgYHtyfQpnZ3Bsb3QoKSArIAogIGdlb21fcG9seWdvbihkYXRhID0gbWFwX2RhdGEoIndvcmxkIiksCiAgICAgICAgICAgICAgIG1hcHBpbmcgPSBhZXMoeCA9IGxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXApLAogICAgICAgICAgICAgICBmaWxsID0gImRhcmsgZ3JheSIsIGNvbG9yID0gIndoaXRlIikgKyAKICBjb29yZF9maXhlZCgxLjMpICsKICB0aGVtZV92b2lkKCkKYGBgCgojIyMgRnJhbmNlIHstfQoKYGBge3J9CmdncGxvdCgpICsgCiAgZ2VvbV9wb2x5Z29uKGRhdGEgPSBtYXBfZGF0YSgiZnJhbmNlIiksCiAgICAgICAgICAgICAgIG1hcHBpbmcgPSBhZXMoeCA9IGxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXApLAogICAgICAgICAgICAgICBmaWxsID0gImRhcmsgZ3JheSIsIGNvbG9yID0gIndoaXRlIikgKyAKICBjb29yZF9maXhlZCgxLjMpICsKICB0aGVtZV92b2lkKCkKYGBgCgoKIyMjIEl0YWx5IHstfQoKYGBge3J9CmdncGxvdCgpICsgCiAgZ2VvbV9wb2x5Z29uKGRhdGEgPSBtYXBfZGF0YSgiaXRhbHkiKSwKICAgICAgICAgICAgICAgbWFwcGluZyA9IGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCksCiAgICAgICAgICAgICAgIGZpbGwgPSAiZGFyayBncmF5IiwgY29sb3IgPSAid2hpdGUiKSArIAogIGNvb3JkX2ZpeGVkKDEuMykgKwogIHRoZW1lX3ZvaWQoKQpgYGAKCiMjIyBOZXcgWmVhbGFuZCB7LX0KCmBgYHtyfQpnZ3Bsb3QoKSArIAogIGdlb21fcG9seWdvbihkYXRhID0gbWFwX2RhdGEoIm56IiksCiAgICAgICAgICAgICAgIG1hcHBpbmcgPSBhZXMoeCA9IGxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXApLAogICAgICAgICAgICAgICBmaWxsID0gImRhcmsgZ3JheSIsIGNvbG9yID0gIndoaXRlIikgKyAKICBjb29yZF9maXhlZCgxLjMpICsKICB0aGVtZV92b2lkKCkKYGBgCgojIyMgSmFwYW4gey19CgpgYGB7cn0KZ2dwbG90KCkgKyAKICBnZW9tX3BvbHlnb24oZGF0YSA9IG1hcF9kYXRhKCJqYXBhbiIpLAogICAgICAgICAgICAgICBtYXBwaW5nID0gYWVzKHggPSBsb25nLCB5ID0gbGF0LCBncm91cCA9IGdyb3VwKSwKICAgICAgICAgICAgICAgZmlsbCA9ICJsaWdodCBncmF5IiwgY29sb3IgPSAiYmxhY2siKSArIAogIGNvb3JkX2ZpeGVkKDEuMykgKwogIHRoZW1lX3ZvaWQoKQpgYGAKCiMjIyBVbml0ZWQgU3RhdGVzIHstfQoKYGBge3J9CmdncGxvdCgpICsgCiAgZ2VvbV9wb2x5Z29uKGRhdGEgPSBtYXBfZGF0YSgiY291bnR5IiksCiAgICAgICAgICAgICAgIGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCksCiAgICAgICAgICAgICAgIGZpbGwgPSAid2hpdGUiLCBjb2xvciA9ICJsaWdodCBncmF5IikgKwogIGdlb21fcG9seWdvbihkYXRhID0gbWFwX2RhdGEoInN0YXRlIiksCiAgICAgICAgICAgICAgIGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCksCiAgICAgICAgICAgICAgIGZpbGwgPSBOQSwgY29sb3IgPSAiZ3JheSIpICsKICBjb29yZF9maXhlZCgxLjMpICsKICB0aGVtZV92b2lkKCkKYGBgCgojIyMgQ2hpbmEgey19CgpgYGB7cn0KZ2dwbG90KCkgKyAKICBnZW9tX3BhdGgoZGF0YSA9IG1hcHM6Om1hcCgiY2hpbmEiLCBwbG90ID0gRkFMU0UpLAogICAgICAgICAgICBtYXBwaW5nID0gYWVzKHggPSBsb25nLCB5ID0gbGF0LCBncm91cCA9IGdyb3VwKSwKICAgICAgICAgICAgY29sb3IgPSAiYmxhY2siKSArIAogIGNvb3JkX2ZpeGVkKDEuMykgKwogIHRoZW1lX3ZvaWQoKQpgYGAKCgojIEFkZGluZyBhZ2dyZWdhdGVkIHN0YXRzIHRvIHRoZSB3b3JsZCBtYXAKCldlIHdhbnQgdG8gdXNlIHRoZSBtYXBzIHRvIHNob3cgdmFyaW91cyB2YXJpYWJsZXMuIEZvciBpbnN0YW5jZSwgbGV0J3Mgc2VlIGhvdyB0byBtYWtlIGEgd29ybGQgbWFwIHNob3dpbmcgdGhlIHZhcmlhdGlvbiBpbiBsaWZlIGV4cGVjdGFuY3kuCgpJZiB3ZSBsb29rIGF0IHRoZSBgbWFwX2RhdGEoIndvcmxkIilgIHdlIHNlZSBpdCBoYXMgdGhlIGZvbGxvd2luZyBzdHJ1Y3R1cmU6CgpgYGB7cn0KaGVhZChtYXBfZGF0YSgid29ybGQiKSkKYGBgCgpUaGUga2V5IHRvIGFkZGluZyBpbmZvcm1hdGlvbiB0byB0aGUgbWFwIGlzIHRvIGFkZCB2YXJpYWJsZXMgdG8gdGhpcyBkYXRhc2V0LCBzdWNoIGFzIHRoZSBsaWZlIGV4cGVjdGFuY3kgdmFyaWFibGUsIG1ha2luZyBzdXJlIHlvdSBtYXRjaCB0aGUgcmVnaW9uIG5hbWUgcHJvcGVybHkuIAoKIyMgR2V0dGluZyB0aGUgUW9HIGRhdGEKCkxldCdzIGxvYWQgdGhlIGNyb3NzLXNlY3Rpb24gZGF0YSBmcm9tIHRoZSBRdWFsaXR5IG9mIEdvdmVybm1lbnQgSW5zdGl0dXRlOgoKYGBge3J9CnFvZyA8LSByaW86OmltcG9ydCgiaHR0cDovL3d3dy5xb2dkYXRhLnBvbC5ndS5zZS9kYXRhYXJjaGl2ZS9xb2dfc3RkX2NzX2phbjE4LmR0YSIpCmBgYAoKVGhlIHZhcmlhYmxlcyBpbiB0aGUgZGF0YXNldDoKCmBgYHtyfQpkYXRhLmZyYW1lKAogICAgVmFyaWFibGUgICAgPSBuYW1lcyhxb2cpLCAKICAgIERlc2NyaXB0aW9uID0gc2psYWJlbGxlZDo6Z2V0X2xhYmVsKHFvZyksIAogICAgT2JzLiAgICAgICAgPSBtYXBfZGJsKHFvZywgfnRhYmxlKGlzLm5hKC54KSlbMV0pLAogICAgTWlzc2luZyAgICAgPSBtYXBfZGJsKHFvZywgfnRhYmxlKGlzLm5hKC54KSlbMl0pCiAgKSAlPiUKICBEVDo6ZGF0YXRhYmxlKHJvd25hbWVzID0gRkFMU0UpCmBgYAoKSWYgeW91IHNlYXJjaCBmb3IgImxpZmUgZXhwZWN0YW5jeSIgeW91J2xsIGZpbmQgc2V2ZXJhbCB2YXJpYWJsZXMuIEknbSBnb2luZyB0byB1c2UgYHdkaV9saWZleHBgIHdoaWNoIGlzIGZyb20gdGhlIFdvcmxkIEJhbmsuCgojIyBNYXRjaGluZyBjb3VudHJ5IG5hbWVzCgpUaGUgbWFpbiBkaWZmaWN1bHR5IGluIGFkZGluZyB0aGlzIGRhdGEgdG8gdGhlIG1hcCBpcyB0aGF0IHRoZSBgcmVnaW9uYCBuYW1lcyBpbiB0aGUgbWFwIGRhdGFzZXQgZG8gbm90IG1hdGNoIGV4YWN0bHkgdGhlIGNvdW50cnkgbmFtZXMgaW4gdGhlIFFvRyBkYXRhOgoKYGBge3J9CiMgd2hpY2ggY291bnRyaWVzIGluIHFvZyBkb24ndCBtYXRjaCB3b3JsZCBtYXAgbmFtZXMgcHJvcGVybHkKc2V0ZGlmZihxb2ckY25hbWUsIG1hcF9kYXRhKCJ3b3JsZCIpJHJlZ2lvbikKYGBgCgoKVGhlc2UgYXJlIHRoZSBjb3VudHJ5IG5hbWVzIHRoYXQgd2UgbmVlZCB0byBjb3JyZWN0LgoKSGVyZSBhcmUgdGhlIG5hbWVzIGluIHRoZSB3b3JsZCBtYXAgdGhhdCBkb24ndCBtYXRjaCB0aGUgUW9HIHByb3Blcmx5OgoKYGBge3J9CiMgd2hpY2ggd29ybGQgbWFwIG5hbWVzIGRvbid0IG1hdGNoIGNvdW50cmllcyBpbiBxb2cgCnNldGRpZmYobWFwX2RhdGEoIndvcmxkIikkcmVnaW9uLCBxb2ckY25hbWUpCmBgYAoKV2UgY2FuIHNlZSBmcm9tIHRoaXMgbGlzdCB3aGF0IG5hbWVzIHNob3VsZCd2ZSBiZWVuIGluIHRoZSBRb0cgZGF0YS4gRm9yIGluc3RhbmNlLCBpbnN0ZWFkIG9mICJVbml0ZWQgU3RhdGVzIiBpdCBzaG91bGQgYmUgIlVTQSIuCgpUaGUgbW9zdCBzdHJhaWd0aGZvcndhcmQgdGhpbmcgdG8gZG8gbm93IGlzIHRvIGp1c3QgX21hbnVhbGx5IHJlY29kZV8gdGhlIGNvdW50cnkgbmFtZXMgaW4gdGhlIFFvRyB0byBtYXRjaCB0aGUgbmFtZXMgdGhhdCB0aGUgbWFwIHVzZXMuIEl0J3MgYSBiaXQgdGVkaW91cywgYnV0IG9ubHkgdGFrZXMgYSBmZXcgbWludXRlcy4gSSdtIGNyZWF0aW5nIGEgbmV3IHZhcmlhYmxlIGNhbGxlZCBgY25hbWUxYCB3aXRoIHRoZSBjb3JyZWN0ZWQgY291bnRyeSBuYW1lczoKCmBgYHtyfQojIHJlY29kZSBxb2cgY25hbWVzCnFvZyRjbmFtZTEgPC0gcW9nJGNuYW1lICU+JSAKICByZWNvZGUoIkFudGlndWEgYW5kIEJhcmJ1ZGEiID0gIkFudGlndWEiLAogICAgICAgICAiQ29uZ28iID0gIlJlcHVibGljIG9mIENvbmdvIiwKICAgICAgICAgIkNvbmdvLCBEZW1vY3JhdGljIFJlcHVibGljIiA9ICJEZW1vY3JhdGljIFJlcHVibGljIG9mIHRoZSBDb25nbyIsCiAgICAgICAgICJDb3RlIGQnSXZvaXJlIiA9ICJJdm9yeSBDb2FzdCIsCiAgICAgICAgICJDeXBydXMgKDE5NzUtKSIgPSAiQ3lwcnVzIiwKICAgICAgICAgIkV0aGlvcGlhICgxOTkzLSkiID0gIkV0aGlvcGlhIiwKICAgICAgICAgIkZyYW5jZSAoMTk2My0pIiA9ICJGcmFuY2UiLAogICAgICAgICAiS29yZWEsIE5vcnRoIiA9ICJOb3J0aCBLb3JlYSIsCiAgICAgICAgICJLb3JlYSwgU291dGgiID0gIlNvdXRoIEtvcmVhIiwKICAgICAgICAgIk1hbGF5c2lhICgxOTY2LSkiID0gIk1hbGF5c2lhIiwKICAgICAgICAgIlBha2lzdGFuICgxOTcxLSkiID0gIlBha2lzdGFuIiwKICAgICAgICAgIlN0IEtpdHRzIGFuZCBOZXZpcyIgPSAiU2FpbnQgS2l0dHMiLAogICAgICAgICAiU3QgTHVjaWEiID0gIlNhaW50IEx1Y2lhIiwKICAgICAgICAgIlN0IFZpbmNlbnQgYW5kIHRoZSBHcmVuYWRpbmVzIiA9ICJTYWludCBWaW5jZW50IiwKICAgICAgICAgIlN1ZGFuICgyMDEyLSkiID0gIlN1ZGFuIiwKICAgICAgICAgIlRyaW5pZGFkIGFuZCBUb2JhZ28iID0gIlRyaW5pZGFkIiwKICAgICAgICAgIlR1dmFsdSIgPSAiVHV2YWx1IiwgICMgbm90IG9uIG1hcD8gb3IgSSBjYW4ndCBmaW5kIHRoZSBwcm9wZXIgbmFtZQogICAgICAgICAiVW5pdGVkIEtpbmdkb20iID0gIlVLIiwKICAgICAgICAgIlVuaXRlZCBTdGF0ZXMiID0gIlVTQSIpCmBgYAoKV2UgY2FuIG5vdyBhZGQgdGhlIGRhdGEgdG8gdGhlIG1hcCBkYXRhc2V0LCBhbmQgcGxvdCBpdCB3aXRoIGBnZ3Bsb3RgIHVzaW5nIHRoZSBgZmlsbGAgdmFyaWFibGUgdG8gc2hvdyB0aGUgbGlmZSBleHBlY3RhbmNpZXM6CgpgYGB7cn0KIyBhZGQgbGlmZSBleHBlY3RlbmN5IHRvIHRoZSBtYXAgZGF0YQpXb3JsZE1hcERhdGExIDwtIGZ1bGxfam9pbihtYXBfZGF0YSgid29ybGQiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgcW9nICU+JSBzZWxlY3QoY25hbWUxLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3ZGlfZ2RwY2FwcHBwY3VyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3ZGlfcG92Z2FwMTkwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdkaV9saWZleHApLAogICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gYygicmVnaW9uIiA9ICJjbmFtZTEiKSkKYGBgCgpNYWtlIHRoZSBtYXA6CgpgYGB7cn0KV29ybGRNYXBEYXRhMSAlPiUgCiAgZ2dwbG90KCkgKyAKICBnZW9tX3BvbHlnb24oYWVzKHggPSBsb25nLCB5ID0gbGF0LCBncm91cCA9IGdyb3VwLCBmaWxsID0gd2RpX2dkcGNhcHBwcGN1cikpICsgCiAgY29vcmRfZml4ZWQoMS4xKSArCiAgdGhlbWVfdm9pZCgpICsKICBsYWJzKGZpbGwgPSAiQXZlcmFnZVxuaW5jb21lcyIsIHRpdGxlID0gIkdEUCBwZXIgY2FwaXRhLCBjdXJyZW50IGRvbGxhcnMsIFBQUCIpICsKICBzY2FsZV9maWxsX2Jpbm5lZChsb3cgPSAidG9tYXRvMiIsIGhpZ2ggPSAiZGFyayBibHVlIiwgCiAgICAgICAgICAgICAgICAgICAgdHJhbnMgPSAibG9nIiwgCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gc2NhbGVzOjpkb2xsYXJfZm9ybWF0KGFjY3VyYWN5ID0gMSksCiAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYygxMDAwLCAyMDAwLCAxMDAwMCwgMzAwMDAsIDYwMDAwKSkgKwogIGdndGhlbWVzOjp0aGVtZV9tYXAoYmFzZV9mYW1pbHkgPSAic2VyaWYiLCBiYXNlX3NpemUgPSAxOCkKYGBgCgpgYGB7cn0KV29ybGRNYXBEYXRhMSAlPiUgCiAgZ2dwbG90KCkgKyAKICBnZW9tX3BvbHlnb24oYWVzKHggPSBsb25nLCB5ID0gbGF0LCBncm91cCA9IGdyb3VwLCBmaWxsID0gd2RpX2xpZmV4cCkpICsgCiAgY29vcmRfZml4ZWQoMS4xKSArCiAgdGhlbWVfdm9pZCgpICsKICBsYWJzKGZpbGwgPSAiWWVhcnMiLCB0aXRsZSA9ICJMaWZlIGV4cGVjdGFuY3kgYXQgYmlydGgiKSArCiAgc2NhbGVfZmlsbF9iaW5uZWQobG93ID0gImJsYWNrIiwgaGlnaCA9ICJsaWdodCBncmF5IiwgCiAgICAgICAgICAgICAgICAgICAgI3RyYW5zID0gImxvZyIsIAogICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHNjYWxlczo6Y29tbWEpICsKICBnZ3RoZW1lczo6dGhlbWVfbWFwKGJhc2VfZmFtaWx5ID0gInNlcmlmIiwgYmFzZV9zaXplID0gMTgpCmBgYAoKIyMgQXV0b21hdGljIGNvdW50cnkgbmFtZXMgbWF0Y2hpbmcgCgpUaGUgYWx0ZXJuYXRpdmUgdG8gbWF0Y2hpbmcgdGhlIG5hbWVzIGJ5IGhhbmQgaXMgdG8gdXNlIHRoZSBmdW5jdGlvbiBgbWFwczo6aXNvLmV4cGFuZCgpYCB3aGljaCBhbGxvd3MgeW91IHRvIGNyZWF0ZSB0aGUgcHJvcGVyIG1hcCBuYW1lcyBmcm9tIHN0YW5kYXJkIElTTyBjb2Rlcy4gVGhlIFFvRyBjb250YWlucyB0aGUgMy1sZXR0ZXIgSVNPIGNvZGVzIGluIHRoZSB2YXJpYWJsZSBgY2NvZGVhbHBgLiAoVGhlIGBtYXBzOjppc28uYWxwaGEoKWAgZnVuY3Rpb24gZG9lcyB0aGUgcmV2ZXJzZSBjb252ZXJzaW9uLCBmcm9tIG1hcCByZWdpb24gbmFtZXMgdG8gdGhlIElTTyBjb2Rlcy4pCgpgYGB7cn0KcW9nJGNuYW1lMiA8LSBpc28uZXhwYW5kKHFvZyRjY29kZWFscCkKYGBgCgpUaGlzIGNyZWF0ZXMgYSBjb21wbGljYXRpb24gdGhvdWdoLCBiZWNhdXNlIHRoZSBJU08gY29kZXMgZG9uJ3QgaGF2ZSBhIDEtdG8tMSBjb3JyZXNwb25kZW5jZSB0byByZWdpb24gbmFtZXMgb24gdGhlIG1hcCwgYW5kIGBpc28uZXhwYW5kKClgIGNyZWF0ZXMgcmVnZXggZXhwcmVzc2lvbnMuIEFzIHN1Y2gsIHlvdSBuZWVkIHRvIGRvIGEgZnV6enkgam9pbiB3aGVuIHlvdSdyZSBtZXJnaW5nIHRoZSBkYXRhc2V0czoKCmBgYHtyfQojIGFkZCBsaWZlIGV4cGVjdGVuY3kgdG8gdGhlIG1hcCBkYXRhCldvcmxkTWFwRGF0YTIgPC0gZnV6enlqb2luOjpyZWdleF9mdWxsX2pvaW4oCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWFwX2RhdGEoIndvcmxkIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgIHFvZyAlPiUgc2VsZWN0KGNuYW1lMiwgd2RpX2dkcGNhcHBwcGN1ciksCiAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgPSBjKCJyZWdpb24iID0gImNuYW1lMiIpKQpgYGAKCkhlcmUncyB0aGUgc2FtZSBtYXAgYWdhaW46CgpgYGB7cn0KV29ybGRNYXBEYXRhMiAlPiUgCiAgZ2dwbG90KCkgKyAKICBnZW9tX3BvbHlnb24oYWVzKHggPSBsb25nLCB5ID0gbGF0LCBncm91cCA9IGdyb3VwLCBmaWxsID0gd2RpX2dkcGNhcHBwcGN1cikpICsgCiAgY29vcmRfZml4ZWQoMS4xKSArCiAgdGhlbWVfdm9pZCgpICsKICBsYWJzKGZpbGwgPSAiTGlmZVxuZXhwZWN0YW5jeVxuYXQgYmlydGgiKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAicmVkIiwgaGlnaCA9ICJkYXJrIGJsdWUiLCB0cmFucyA9ICJsb2ciKQpgYGAKCiMjIEV4YW1wbGVzOiB7LnRhYnNldH0KCk9mIGNvdXJzZSwgb25jZSB5b3UgbWF0Y2hlZCB0aGUgY291bnRyeSBuYW1lcyAoYnkgb25lIG1ldGhvZCBvciBhbm90aGVyKSwgeW91IGNhbiBhZGQgYW55IG90aGVyIHZhcmlhYmxlcyBmcm9tIHRoZSBRb0cuIEhlcmUncyBlY29ub21pYyBmcmVlZG9tIGZyb20gRnJhc2VyIEluc3RpdHV0ZSwgZGVtb2NyYWN5IGZyb20gRnJlZWRvbSBIb3VzZSwgYW5kIGxpYmVyYWwgZGVtb2NyYWN5IGZyb20gdGhlIFZhcmlldGllcyBvZiBEZW1vY3JhY3kgZGF0YXNldDoKCmBgYHtyfQojIGFkZCB2YXJpYWJsZXMgdG8gdGhlIG1hcCBkYXRhCldvcmxkTWFwRGF0YSA8LSBmdWxsX2pvaW4oCiAgICAgICAgICAgICAgICAgIG1hcF9kYXRhKCJ3b3JsZCIpLCAKICAgICAgICAgICAgICAgICAgcW9nICU+JSBzZWxlY3QoY25hbWUxLCB3ZGlfbGlmZXhwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlfaW5kZXgsIHZkZW1fbGliZGVtLCBmaF9wb2xpdHkyKSwKICAgICAgICAgICAgICAgICAgYnkgPSBjKCJyZWdpb24iID0gImNuYW1lMSIpCiAgICAgICAgICAgICAgICApCmBgYAoKSSdtIGFsc28gaWxsdXN0cmF0aW5nIGRpZmZlcmVudCBjb2xvciBzY2FsZXMsIHRoZXJlIGFyZSBiYXNpY2FsbHkgZW5kbGVzcyB2aXN1YWwgcG9zc2liaWxpdGllcy4KCiMjIyBFY29ub21pYyBmcmVlZG9tIHstfQoKYGBge3J9CldvcmxkTWFwRGF0YSAlPiUgCiAgZ2dwbG90KCkgKyAKICBnZW9tX3BvbHlnb24oYWVzKHggPSBsb25nLCB5ID0gbGF0LCBncm91cCA9IGdyb3VwLCBmaWxsID0gZmlfaW5kZXgpKSArIAogIGNvb3JkX2ZpeGVkKDEuMSkgKwogIHRoZW1lX3ZvaWQoKSArCiAgbGFicyhmaWxsID0gIkVjb25vbWljXG5mcmVlZG9tIikgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkKYGBgCgojIyMgRGVtb2NyYWN5IChwb2xpdHkyLCBGcmVlZG9tIEhvdXNlKSB7LX0KCmBgYHtyfQpXb3JsZE1hcERhdGEgJT4lIAogIGdncGxvdCgpICsgCiAgZ2VvbV9wb2x5Z29uKGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCwgZmlsbCA9IGZoX3BvbGl0eTIpKSArIAogIGNvb3JkX2ZpeGVkKDEuMSkgKwogIHRoZW1lX3ZvaWQoKSArCiAgbGFicyhmaWxsID0gIkRlbW9jcmFjeSIpICsKICBzZWU6OnNjYWxlX2ZpbGxfcGl6emFfYygpICMgZ3JhZGllbnQobG93ID0gInJlZCIsIGhpZ2ggPSAiYmx1ZSIpCmBgYAoKCiMjIyBMaWJlcmFsIGRlbW9jcmFjeSAoVi1EZW0pIHstfQoKYGBge3J9CldvcmxkTWFwRGF0YSAlPiUgCiAgZ2dwbG90KCkgKyAKICBnZW9tX3BvbHlnb24oYWVzKHggPSBsb25nLCB5ID0gbGF0LCBncm91cCA9IGdyb3VwLCBmaWxsID0gdmRlbV9saWJkZW0pKSArIAogIGNvb3JkX2ZpeGVkKDEuMSkgKwogIHRoZW1lX3ZvaWQoKSArCiAgbGFicyhmaWxsID0gIkxpYmVyYWxcbmRlbW9jcmFjeSIpICsKICBnZ3RoZW1lczo6c2NhbGVfZmlsbF9jb250aW51b3VzX3RhYmxlYXUoKQpgYGAKCiMgQWRkaW5nIGFnZ3JlZ2F0ZWQgc3RhdHMgdG8gdGhlIFVTIHN0YXRlcyBtYXAKCiMjIExvYWRpbmcgc3RhdGUgcG9saWNpZXMgZGF0YQoKTGV0J3MgdXNlIHRoZSBbU29yZW5zLCBNdWVkaW5pIGFuZCBSdWdlciBkYXRhc2V0IG9mIHN0YXRlIHBvbGljaWVzXShodHRwOi8vd3d3LnN0YXRlcG9saWN5aW5kZXguY29tL2RhdGEvKS5eW1NvcmVucywgSmFzb24sIEZhaXQgTXVlZGluaSwgYW5kIFdpbGxpYW0gUC4gUnVnZXIuIDIwMDguICJTdGF0ZSBhbmQgTG9jYWwgUHVibGljIFBvbGljaWVzIGluIDIwMDY6IEEgTmV3IERhdGFiYXNlLiIgX1N0YXRlIFBvbGl0aWNzIGFuZCBQb2xpY3kgUXVhcnRlcmx5XyA4ICgzKTogMzA54oCTMjYuXQoKQWZ0ZXIgeW91IGRvd25sb2FkIHRoZSBkYXRhIGFuZCBtZXJnZSBldmVyeXRoaW5nIHVzaW5nIFtKYXNvbiBTb3JlbnMnIFIgc2NyaXB0XShodHRwOi8vd3d3LnN0YXRlcG9saWN5aW5kZXguY29tL2RhdGEvc2FtcGxlLXItY29kZS1mb3ItaW1wb3J0aW5nLWFuZC1tZXJnaW5nLWRhdGEtYW5kLW1ldGFkYXRhLWNzdi1maWxlcy8pLCB5b3UgaGF2ZSB0aGUgZm9sbG93aW5nIHZhcmlhYmxlcyBmb3IgYWxsIHN0YXRlcywgZnJvbSAxOTM3IHRvIDIwMTc6CgpgYGB7cn0Kc3RhdGVfcG9saWN5ICAgICAgPC0gcmVhZFJEUygiZGF0YS9zdGF0ZV9wb2xpY2llcy5yZHMiKQpzdGF0ZV9wb2xpY3lfbWV0YSA8LSByZWFkUkRTKCJkYXRhL3N0YXRlX3BvbGljaWVzX21ldGEucmRzIikKYGBgCgpUaGUgYXZhaWxhYmxlIHZhcmlhYmxlczoKCmBgYHtyfQpzdGF0ZV9wb2xpY3lfbWV0YSAlPiUgCiAgc2VsZWN0KFZhcmlhYmxlID0gYFZhcmlhYmxlIENvZGVgLCBEZXNjcmlwdGlvbiA9IGBWYXJpYWJsZSBOYW1lYCkgJT4lIAogIG11dGF0ZShWYXJpYWJsZSAgICA9IHN0cmluZ2k6OnN0cmlfZW5jX3RvYXNjaWkoVmFyaWFibGUpLAogICAgICAgICBEZXNjcmlwdGlvbiA9IHN0cmluZ2k6OnN0cmlfZW5jX3RvYXNjaWkoRGVzY3JpcHRpb24pKSAlPiUKICBEVDo6ZGF0YXRhYmxlKCkKYGBgCgooSSdtIHJlY29kaW5nIHRoZSB2YXJpYWJsZXMgdG8gQVNDSUkgdG8gZWxpbWluYXRlIHNvbWUgc3BlY2lhbCBjaGFyYWN0ZXJzLCB3aGljaCBvdGhlcndpc2UgZ2l2ZSBhbiBlcnJvciBpbiB0aGUgYERUOjpkYXRhdGFibGUoKWAuKQoKIyMgQ2hlY2tpbmcgdGhhdCBzdGF0ZSBuYW1lcyBtYXRjaCB3aXRoIG1hcCBzdGF0ZSBuYW1lcwoKQmVmb3JlIHdlIGFkZCBhIHZhcmlhYmxlIHRvIHRoZSBtYXAsIGxldCB1cyBmaXJzdCBjaGVjayB0aGUgc3RhdGUgbmFtZXMgYXJlIG9rLiBDaGVjayBvdXQgdGhlIHN0YXRlIG5hbWVzIGluIHRoZSBtYXAgZGF0YToKCmBgYHtyfQptYXBfZGF0YSgic3RhdGUiKSRyZWdpb24gJT4lIHVuaXF1ZSgpCmBgYAoKTm90aWNlIHRoYXQgQWxhc2thIGFuZCBIYXdhaWkgYXJlIG1pc3NpbmcsIGFuZCB0aGUgcmVnaW9uIG5hbWVzIGFyZSBhbGwgbG93ZXIgY2FzZS4gU28gbGV0J3MgZmlyc3QgY2hhbmdlIHRoZSBuYW1lcyBpbiB0aGUgYHN0YXRlX3BvbGljeWAgZGF0YXNldCB0byBsb3dlciBjYXNlOgoKYGBge3J9CnN0YXRlX3BvbGljeSRTdGF0ZTEgPC0gc3RyX3RvX2xvd2VyKHN0YXRlX3BvbGljeSRTdGF0ZSkKYGBgCgpMZXQncyBkb3VibGUgY2hlY2sgdGhhdCB0aGUgc3RhdGUgbmFtZXMgbWF0Y2g6CgpgYGB7cn0Kc2V0ZGlmZihzdGF0ZV9wb2xpY3kkU3RhdGUxLCBtYXBfZGF0YSgic3RhdGUiKSRyZWdpb24pCmBgYAoKSXQgbG9va3MgbGlrZSB0aGV5IGFyZSBhbGwgZ29vZCEgQWxhc2thIGFuZCBIYXdhaWkgZG9uJ3QgYXBwZWFyIG9uIHRoZSBtYXAsIGJlY2F1c2UgdGhlIG1hcCBpcyBvbmx5IG9mIHRoZSBtYWlubGFuZC4KCk5PVEU6IElmIHRoZSBuYW1lcyBfZGlkbid0XyBtYXRjaCwgdGhlIGBtYXBzYCBwYWNrYWdlIG9mZmVycyBhIGhhbmR5IHJlc2N1ZTogdGhlIGBzdGF0ZS5maXBzYCBkYXRhc2V0IGdpdmVzIHRoZSBVUyBDZW5zdXMgQnVyZWF1IG51bWVyaWMgaWRlbnRpZmljYXRpb24gZm9yIHRoZSBzdGF0ZXMgKGFzIHdlbGwgYXMgcmVnaW9uLCBldGMuKS4gU2ltaWxhcmx5LCBpZiB5b3UncmUgcGxvdHRpbmcgY291bnR5LWxldmVsIGRhdGEsIHRoZXJlJ3MgdGhlIGBjb3VudHkuZmlwc2AgZGF0YXNldCB3aXRoIHRoZSBVUyBDZW5zdXMgQnVyZWF1IEZJUFMgaWRlbnRpZmljYXRpb24gbnVtYmVycyBmb3IgY291bnRpZXMuCgojIyBBZGRpbmcgc3RhdGUgdmFyaWFibGVzIHRvIHRoZSBtYXAKCkxldCdzIG1ha2UgbWFwcyBvZiBmb2xsb3dpbmcgdmFyaWFibGU6CgotIGBBcG92cmF0ZWA6IFBlcmNlbnRhZ2Ugb2Ygc3RhdGUgcG9wdWxhdGlvbiBpbiBwb3ZlcnR5Ci0gYEFwb3BkZW5zYDoJUG9wdWxhdGlvbiBkZW5zaXR5IChBcG9wL0FsYW5kKQotIGBjcGJlZXJjYDogU3RhdGUgYXZlcmFnZSBiZWVyIHByaWNlcywgaW4gY29uc3RhbnQgMjAwOCBkb2xsYXJzCi0gYGNzcGlyd2V0YDogUGVyY2VudGFnZSBvZiBzdGF0ZSBwb3B1bGF0aW9uIGxpdmluZyBpbiBjb3VudGllcyB0aGF0IGFyZSB3ZXQgZm9yIGRpc3RpbGxlZCBzcGlyaXRzCi0gYEJvcGVuYDogT3BlbiBjYXJyeSBvZiBsb2FkZWQgaGFuZGd1biAocGVybWl0dGVkIHdpdGhvdXQgcGVybWl0PTIsIHBlcm1pdHRlZCB3aXRoIHBlcm1pdD0xLCBub3QgZ2VuZXJhbGx5IHBlcm1pdHRlZD0wKQoKQWRkIHRoZSB2YXJpYWJsZXMgdG8gdGhlIG1hcCBkYXRhOgoKYGBge3J9ClN0YXRlc01hcERhdGEgPC0gZnVsbF9qb2luKAogICAgICAgICAgICAgICAgICAgIG1hcF9kYXRhKCJzdGF0ZSIpLCAKICAgICAgICAgICAgICAgICAgICBzdGF0ZV9wb2xpY3kgJT4lIAogICAgICAgICAgICAgICAgICAgICAgc2VsZWN0KFN0YXRlMSwgWWVhciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBcG92cmF0ZSwgQXBvcGRlbnMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNwYmVlcmMsIGNzcGlyd2V0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBCb3BlbiksCiAgICAgICAgICAgICAgICAgICAgYnkgPSBjKCJyZWdpb24iID0gIlN0YXRlMSIpCiAgICAgICAgICAgICAgICAgICkKYGBgCgojIyBFeGFtcGxlczogey50YWJzZXR9CgojIyMgUG92ZXJ0eSB7LX0KCmBgYHtyLCBmaWcuaGVpZ2h0PTd9ClN0YXRlc01hcERhdGEgJT4lCiAgZmlsdGVyKFllYXIgPj0gMTk4MCkgJT4lIAogIGZpbHRlcihZZWFyICUlIDUgPT0gMCkgJT4lIAogIGRyb3BfbmEoWWVhcikgJT4lIAoKZ2dwbG90KCkgKyAKICBnZW9tX3BvbHlnb24oYWVzKHggPSBsb25nLCB5ID0gbGF0LCBncm91cCA9IGdyb3VwLCBmaWxsID0gQXBvdnJhdGUpKSArCiAgY29vcmRfZml4ZWQoMS4zKSArCiAgdGhlbWVfdm9pZCgpICsKICBsYWJzKGZpbGwgPSAiUG92ZXJ0eVxucmF0ZSIpICsKICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJkYXJrIGJsdWUiLCBoaWdoID0gInJlZCIpICsKICBmYWNldF93cmFwKH5ZZWFyLCBuY29sID0gMikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLCBsZWdlbmQuZGlyZWN0aW9uID0gImhvcml6b250YWwiKQpgYGAKCiMjIyBQb3B1bGF0aW9uIGRlbnNpdHkgey19CgpgYGB7cn0KZ2dwbG90KCkgKyAKICBnZW9tX3BvbHlnb24oZGF0YSA9IFN0YXRlc01hcERhdGEgJT4lIGZpbHRlcihZZWFyID09IDIwMTUpLAogICAgICAgICAgICAgICBhZXMoeCA9IGxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXAsIGZpbGwgPSBBcG9wZGVucyksCiAgICAgICAgICAgICAgIGNvbG9yID0gImxpZ2h0IGdyYXkiKSArCiAgY29vcmRfZml4ZWQoMS4zKSArCiAgdGhlbWVfdm9pZCgpICsKICBsYWJzKGZpbGwgPSAiUG9wdWxhdGlvblxuZGVuc2l0eSIpICsKICBzY2FsZV9maWxsX2NvbnRpbnVvdXModHJhbnMgPSAibG9nIiwgbGFiZWxzID0gc2NhbGVzOjpjb21tYSkKYGBgCgojIyMgQmVlciBwcmljZXMgey19CgpgYGB7cn0KZ2dwbG90KCkgKyAKICBnZW9tX3BvbHlnb24oZGF0YSA9IFN0YXRlc01hcERhdGEgJT4lIGZpbHRlcihZZWFyID09IDIwMDMpLAogICAgICAgICAgICAgICBhZXMoeCA9IGxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXAsIGZpbGwgPSBjcGJlZXJjKSwKICAgICAgICAgICAgICAgY29sb3IgPSAiZ3JheSIpICsKICBjb29yZF9maXhlZCgxLjMpICsKICB0aGVtZV92b2lkKCkgKwogIGxhYnMoZmlsbCA9ICJCZWVyXG5wcmljZXMiKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAiZGFyayBibHVlIiwgaGlnaCA9ICJyZWQiKQpgYGAKCiMjIyBXZXQgY291bnRpZXMgKHNwaXJpdHMpIHstfQoKYGBge3J9ClN0YXRlc01hcERhdGEgJT4lIGZpbHRlcihZZWFyICVpbiUgYygxOTY3LCAxOTg0KSkgJT4lIAoKZ2dwbG90KCkgKyAKICBnZW9tX3BvbHlnb24oYWVzKHggPSBsb25nLCB5ID0gbGF0LCBncm91cCA9IGdyb3VwLCBmaWxsID0gY3NwaXJ3ZXQpLAogICAgICAgICAgICAgICBjb2xvciA9ICJncmF5IikgKwogIGNvb3JkX2ZpeGVkKDEuMykgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gInJlZCIsIGhpZ2ggPSAiZGFyayBibHVlIikgKwogIGxhYnMoZmlsbCA9ICJXZXQgY291bnRpZXM6XG4iKSArCiAgdGhlbWVfdm9pZCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwgbGVnZW5kLmRpcmVjdGlvbiA9ICJob3Jpem9udGFsIikgKwogIGZhY2V0X3dyYXAoflllYXIpCmBgYAoKIyMjIE9wZW4gY2Fycnkgey19CgpgYGB7ciwgZmlnLmhlaWdodD03fQpTdGF0ZXNNYXBEYXRhICU+JSAKICBmaWx0ZXIoWWVhciAlJSA1ID09IDApICU+JSAKICBkcm9wX25hKEJvcGVuKSAlPiUgCiAgbXV0YXRlKEJvcGVuID0gcmVjb2RlKEJvcGVuLAogICAgYDJgID0gInBlcm1pdHRlZCB3aXRob3V0IHBlcm1pdCIsCiAgICBgMWAgPSAicGVybWl0dGVkIHdpdGggcGVybWl0IiwgCiAgICBgMGAgPSAibm90IGdlbmVyYWxseSBwZXJtaXR0ZWQiCiAgKSkgJT4lIAoKZ2dwbG90KCkgKyAKICBnZW9tX3BvbHlnb24oYWVzKHggPSBsb25nLCB5ID0gbGF0LCBncm91cCA9IGdyb3VwLCBmaWxsID0gQm9wZW4pLAogICAgICAgICAgICAgICBjb2xvciA9ICJncmF5IikgKwogIGNvb3JkX2ZpeGVkKDEuMykgKwogIHRoZW1lX3ZvaWQoKSArCiAgbGFicyhmaWxsID0gIk9wZW4gY2Fycnk6IikgKwogIHNlZTo6c2NhbGVfZmlsbF9mbGF0X2QocmV2ZXJzZSA9IFRSVUUpICsKICBmYWNldF93cmFwKH5ZZWFyLCBuY29sID0gMikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiLCBsZWdlbmQuZGlyZWN0aW9uID0gImhvcml6b250YWwiKQpgYGAKCiMgQWRkaW5nIGNpdGllcyB0byB0aGUgbWFwCgpXZSBjYW4gYWRkIHRoaW5ncyB0byB0aGUgbWFwIGluIHN0YW5kYXJkIGBnZ3Bsb3RgIGZhc2hpb24gLS0gdXNpbmcgYWRkaXRpb25hbCBnZW9tcy4gRm9yIGV4YW1wbGUsIGxldCBtZSBfbWFudWFsbHlfIGFkZCBUdWNzb24gYW5kIERlbnZlciB0byB0aGUgVVMgbWFwLgoKRmlyc3QsIGdvb2dsZSB0aGUgbGF0aXR1ZGUgYW5kIGxvbmdpdHVkZSBvZiB0aGUgY2l0aWVzIGFuZCB0aGVuIGFkZCB0aGUgaW5mb3JtYXRpb24gdG8gYSBkYXRhZnJhbWU6CgpgYGB7cn0KY2l0aWVzIDwtIHRyaWJibGUoCiAgICAgIH5sb25nLCAgICB+bGF0LCAgICAgIH5uYW1lLAogIC0xMTAuOTc0NywgICAzMi4yMjI2LCAiVHVjc29uIiwKICAtMTA0Ljk5MDMsICAgMzkuNzM5MiwgIkRlbnZlciIKICApCmBgYAoKTm93IHdlIGNhbiBqdXN0IGFkZCB0aGVtIHRvIHRoZSBVUyBtYXA6CgpgYGB7cn0KZ2dwbG90KCkgKyAKICBnZW9tX3BvbHlnb24oZGF0YSA9IG1hcF9kYXRhKCJ1c2EiKSwKICAgICAgICAgICAgICAgbWFwcGluZyA9IGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCksCiAgICAgICAgICAgICAgIGZpbGwgPSAibGlnaHQgZ3JheSIsIGNvbG9yID0gImxpZ2h0IGdyYXkiKSArIAogIGdlb21fcG9pbnQoZGF0YSA9IGNpdGllcywgYWVzKHggPSBsb25nLCB5ID0gbGF0KSwgY29sb3IgPSAiYmxhY2siKSArCiAgZ2VvbV90ZXh0KGRhdGEgPSBjaXRpZXMsIGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgbGFiZWwgPSBuYW1lKSwgbnVkZ2VfeSA9IDEpICsKICBjb29yZF9maXhlZCgxLjMpICsKICB0aGVtZV92b2lkKCkKYGBgCgpCdXQgd2UgY2FuIGRvIGJldHRlciB0aGFuIGFkZCB0aGUgY2l0aWVzIG1hbnVhbGx5LiBUaGUgYG1hcHNgIHBhY2thZ2UgYWxzbyBjb250YWlucyBhIGZldyBkYXRhZnJhbWVzIG9mIGNpdGllcyBsb2NhdGlvbnMuCgp8IERhdGFzZXQgfCBEZXNjcmlwdGlvbiB8CnwtLS0tLS0tfC0tLS0tLS0tLS0tLS18CnwgYGNhbmFkYS5jaXRpZXNgCXwgQ2FuYWRpYW4gY2l0aWVzIGxhcmdlciB0aGFuIDEsMDAwIHwKfCBgdXMuY2l0aWVzYAl8IFVTIGNpdGllcyBsYXJnZXIgdGhhbiA0MCwwMDAgfAp8IGB3b3JsZC5jaXRpZXNgIHwgV29ybGQgY2l0aWVzIGxhcmdlciB0aGFuIDQwLDAwMCB8CgpIZXJlIGlzIHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIGB3b3JsZC5jaXRpZXNgIGRhdGEgZnJhbWU6CgpgYGB7cn0KaGVhZCh3b3JsZC5jaXRpZXMpCmBgYAoKTGV0J3MgYWRkIGFsbCBVUyBjaXRpZXMgZ3JlYXRlciB0aGFuIGhhbGYgYSBtaWxsaW9uIHBlb3BsZSB0byB0aGUgbWFwOgoKYGBge3J9CmdncGxvdCgpICsgCiAgZ2VvbV9wb2x5Z29uKGRhdGEgPSBtYXBfZGF0YSgidXNhIiksCiAgICAgICAgICAgICAgIGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCksCiAgICAgICAgICAgICAgIGZpbGwgPSAibGlnaHQgZ3JheSIsIGNvbG9yID0gImxpZ2h0IGdyYXkiKSArIAogIGdlb21fcG9pbnQoZGF0YSA9IHVzLmNpdGllcyAlPiUgZmlsdGVyKHBvcCA+IDUwMDAwMCksCiAgICAgICAgICAgICBhZXMoeCA9IGxvbmcsIHkgPSBsYXQpKSArCiAgZ2VvbV90ZXh0X3JlcGVsKGRhdGEgPSB1cy5jaXRpZXMgJT4lIGZpbHRlcihwb3AgPiA1MDAwMDApLAogICAgICAgICAgICBhZXMoeCA9IGxvbmcsIHkgPSBsYXQsIGxhYmVsID0gbmFtZSkpICsKICBjb29yZF9maXhlZCgxLjMpICsKICB0aGVtZV92b2lkKCkKYGBgCgpIZXJlIGFyZSBhbGwgd29ybGQgY2l0aWVzIGxhcmdlciB0aGFuIG9uZSBtaWxsaW9uLCB3aXRoIGxhYmVscyBmb3IgY2l0aWVzIGdyZWF0ZXIgdGhhbiA1IG1pbGxpb246CgpgYGB7cn0KZ2dwbG90KCkgKyAKICBnZW9tX3BvbHlnb24oZGF0YSA9IG1hcF9kYXRhKCJ3b3JsZCIpLAogICAgICAgICAgICAgICBhZXMoeCA9IGxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXApLAogICAgICAgICAgICAgICBmaWxsID0gImxpZ2h0IGdyYXkiLCBjb2xvciA9ICJ3aGl0ZSIpICsgCiAgZ2VvbV9wb2ludChkYXRhID0gd29ybGQuY2l0aWVzICU+JSBmaWx0ZXIocG9wID4gMTAwMDAwMCksCiAgICAgICAgICAgICBhZXMoeCA9IGxvbmcsIHkgPSBsYXQpLCBjb2xvciA9ICJkYXJrIGdyYXkiKSArCiAgZ2VvbV90ZXh0X3JlcGVsKGRhdGEgPSB3b3JsZC5jaXRpZXMgJT4lIGZpbHRlcihwb3AgPiA1MDAwMDAwKSwKICAgICAgICAgICAgYWVzKHggPSBsb25nLCB5ID0gbGF0LCBsYWJlbCA9IG5hbWUpLCBjb2xvciA9ICJibGFjayIpICsKICBjb29yZF9maXhlZCgxLjMpICsKICB0aGVtZV92b2lkKCkKYGBgCgoKIyMgWm9vbWluZyBpbiBvbiB0aGUgbWFwCgpXZSBjYW4gem9vbSBpbiBieSBzcGVjaWZ5aW5nIHRoZSBsaW1pdHMgb2YgdGhlIG1hcCBpbiB0aGUgYGNvb3JkX2ZpeGVkKClgIG9wdGlvbi4gVGhlIGhpZ2ggcmVzb2x1dGlvbiBgbWFwSGlyZXNgIGlzIHVzZWZ1bCBwYXJ0aWN1bGFybHkgZm9yIHpvb21pbmcgaW4sIGJ1dCBJJ20gc3RpbGwgdXNpbmcgdGhlIHJlZ3VsYXIgbWFwIGhlcmUuIEhlcmUncyBmb3IgZXhhbXBsZSBhIHpvb20gb24gQ2hpbmE6CgpgYGB7cn0KZ2dwbG90KCkgKyAKICBnZW9tX3BvbHlnb24oZGF0YSA9IG1hcF9kYXRhKCJ3b3JsZCIpLAogICAgICAgICAgICAgICBhZXMoeCA9IGxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXApLAogICAgICAgICAgICAgICBmaWxsID0gImxpZ2h0IGdyYXkiLCBjb2xvciA9ICJ3aGl0ZSIpICsgCiAgZ2VvbV9wb2ludChkYXRhID0gd29ybGQuY2l0aWVzICU+JSBmaWx0ZXIocG9wID4gNTAwMDAwLCBjb3VudHJ5LmV0YyA9PSAiQ2hpbmEiKSwKICAgICAgICAgICAgIGFlcyh4ID0gbG9uZywgeSA9IGxhdCksIGNvbG9yID0gImRhcmsgZ3JheSIpICsKICBnZW9tX3RleHRfcmVwZWwoZGF0YSA9IHdvcmxkLmNpdGllcyAlPiUgZmlsdGVyKHBvcCA+IDIwMDAwMDAsIGNvdW50cnkuZXRjID09ICJDaGluYSIpLAogICAgICAgICAgICBhZXMoeCA9IGxvbmcsIHkgPSBsYXQsIGxhYmVsID0gbmFtZSksIGNvbG9yID0gImJsYWNrIikgKwogIGNvb3JkX2ZpeGVkKDEuMywgeGxpbSA9IGMoNzUsIDEzNSksIHlsaW0gPSBjKDIwLCA1MCkpICsKICB0aGVtZV92b2lkKCkKYGBgCgpJIGdvdCB0aGUgbGltaXRzIGZyb20gW3RoaXMgbWFwXShodHRwczovL3d3dy5tYXBzb2Z3b3JsZC5jb20vbGF0X2xvbmcvY2hpbmEtbGF0LWxvbmcuaHRtbCkuIE5vdGljZSB0aGF0IEkndmUgYWxzbyBjaGFuZ2VkIHdoYXQgY2l0aWVzIHRvIHNob3c6IEknbSBub3cgc2hvd2luZyBhbGwgQ2hpbmVzZSBjaXRpZXMgZ3JlYXRlciB0aGFuIGhhbGYgYSBtaWxsaW9uLCBhbmQgbGFiZWxpbmcgYWxsIGNpdGllcyBncmVhdGVyIHRoYW4gMiBtaWxsaW9uLgoKIyBBZGRpbmcgZGV0YWlsZWQgaW5mb3JtYXRpb24gdG8gYSBtYXAKCkxldCdzIHBsb3QgdGhlIGNyaW1lIHJhdGVzIGluIHZhcmlvdXMgcGxhY2VzLCB3aXRoIGRhdGEgdGFrZW4gZnJvbSB0aGUgW09wZW4gQ3JpbWUgRGF0YWJhc2VdKGh0dHBzOi8vb3NmLmlvL3p5YXFuLykuIFRoaXMgZGF0YSBpcyBldmVuIG1vcmUgcHJlY2lzZSB0aGFuIHRoZSBjb3VudHktbGV2ZWwuCgpgYGB7cn0KbGlicmFyeShjcmltZWRhdGEpCmBgYAoKR2V0IGEgc21hbGwgc2FtcGxlIG9mIHRoZSBhdmFpbGFibGUgY3JpbWUgZGF0YSwgdXNpbmcgdGhlICJzaW1wbGUgZmVhdHVyZXMiIChzZikgb3V0cHV0IG9wdGlvbjoKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmNyaW1lIDwtIGdldF9jcmltZV9kYXRhKHR5cGUgPSAic2FtcGxlIiwgcXVpZXQgPSBUUlVFLCBvdXRwdXQgPSAic2YiKQpgYGAKCmBgYHtyIGV2YWw9RkFMU0V9CmNyaW1lIDwtIGdldF9jcmltZV9kYXRhKHR5cGUgPSAic2FtcGxlIiwgcXVpZXQgPSBUUlVFLCBvdXRwdXQgPSAic2YiKQpgYGAKClRoaXMgaXMgaG93IHRoZSBkYXRhIGxvb2tzIGxpa2U6CgpgYGB7cn0KaGVhZChjcmltZSkKYGBgCgpMZXQgdXMgY29uc2lkZXIgb25seSB0aGUgcm9iYmVyaWVzIGFuZCBmb2N1cyBvbiBMb3MgQW5nZWxlczoKCmBgYHtyfQpjcmltZUxBIDwtIGNyaW1lICU+JSAKICBmaWx0ZXIob2ZmZW5zZV9ncm91cCA9PSAicm9iYmVyeSIsIGNpdHlfbmFtZSA9PSAiTG9zIEFuZ2VsZXMiKSAKYGBgCgpgYGB7cn0KbWFwX2RhdGEoImNvdW50eSIpICU+JQogIGZpbHRlcihzdWJyZWdpb24gPT0gImxvcyBhbmdlbGVzIikgJT4lCiAgZ2dwbG90KCkgKwogICAgZ2VvbV9wb2x5Z29uKGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCksCiAgICAgICAgICAgICAgICAgZmlsbCA9ICJsaWdodCBncmF5IiwgY29sb3IgPSAid2hpdGUiKSArCiAgICBnZW9tX3NmKGRhdGEgPSBjcmltZUxBKSAKYGBgCgpNYWtlIGEgbWFwIHNob3dpbmcgcm9iYmVyaWVzIGRlbnNpdHk6CgpgYGB7cn0KZ2dwbG90KCkgKwogIGdlb21fcG9seWdvbihkYXRhID0gbWFwX2RhdGEoInN0YXRlIiksIAogICAgICAgICAgICAgICBhZXMoeCA9IGxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXApLAogICAgICAgICAgICAgICBjb2xvciA9ICJ3aGl0ZSIsIGZpbGwgPSAibGlnaHQgZ3JheSIpICsKICBnZW9tX2NvdW50KGRhdGEgPSBjcmltZSAlPiUgZmlsdGVyKG9mZmVuc2VfZ3JvdXAgPT0gInJvYmJlcnkiKSwKICAgICAgICAgICAgICAgICAgYWVzKHggPSBsb25naXR1ZGUsIHkgPSBsYXRpdHVkZSksCiAgICAgICAgICAgICBhbHBoYSA9IDAuNSkgKwogIHRoZW1lX3ZvaWQoKQpgYGAKCg==