There is a very rich set of tools for interactive geospatial visualization. This note introduces various R tools and Tableau to create interactive maps for visualizing spatial patterns.

Caution: Recent changes to popular R packages for spatial data - what you need to do (https://www.r-bloggers.com/2023/06/upcoming-changes-to-popular-r-packages-for-spatial-data-what-you-need-to-do/).



Map Types


There are two common ways of representing spatial data on a map:

  • Defining regions on a map and distinguishing them based on their value on some measure using colors and shading. This type of map is usually called choropleth map.

  • Marking individual points on a map based on their longitude and latitude (e.g., archaeological dig sites; baseball stadiums; voting locations, etc.). This type of map is also called a scatter map.

Plotting scatter maps uses geocode and are relatively easier to create. However, a choropleth map is constructed using data with a special structure with shape information. It is relatively harder to construct a choropleth map.

A basemap provides context for additional layers that are overlaid on top of the basemap. Basemaps usually provide location references for features that do not change often like boundaries, rivers, lakes, roads, and highways. Even on basemaps, these different categories of information are in layers. Usually a basemap contains this basic data, and then extra layers with particular information from a particular data set, are overlaid on the base map layers for visual analysis.

In this note, the basemaps come primarily from the open-data-source-based OpenStreepMap.



Choropleth Map




Scatter Map



Leaflet Maps



We will use R leaflet library to create both reference maps and choropleth maps and plot data on maps to display spatial patterns.

Reference Maps

1. Introduction

Leaflet is one of the most popular open-source JavaScript libraries for interactive maps. It’s used widely in practice. It has many nice features. R package leaflet allows us to make interactive maps using map tiles, markers, polygons, lines, and popups, etc.

The function leaflet() returns a Leaflet map widget, which stores a list of objects that can be modified or updated later. Most functions in this package have an argument map as their first argument, which makes it easy to use the pipe operator %>%.

Creating a leaflet map with R library leaflet consisting of the following steps.

  • Create a map widget by calling leaflet().

  • Add layers (i.e., features) to the map by using layer functions (e.g. addTiles, addMarkers, addPolygons) to modify the map widget.

  • Repeat the previous as desired.

  • Print the map widget to display it.

Let’s look at the following simple example.

# library(leaflet)        # it has been loaded in the setup chunk.
# define a leaflet map 
m <- leaflet() %>%
     setView(lng=-75.5978, lat=39.9522, zoom = 20) %>%
     addTiles() %>%        # Add default OpenStreetMap map tiles
     addMarkers(lng=-75.5978, lat=39.9522)
m    # Print the map

2. Customizing Marker Icons

We can manipulate the attributes of the map widget using a series of methods.

  • setView() sets the center of the map view and the zoom level;
  • fitBounds() fits the view into the rectangle [lng1, lat1] – [lng2, lat2];
  • clearBounds() clears the bound, so that the view will be automatically determined by the range of latitude/longitude data in the map layers if provided;

We can also define our own markers and added to the map object. For example, we use WCU’s logo as a custom marker and add it to the previous map.

# define a marker using WCU's logo.
wcuicon <- makeIcon(
  iconUrl = "https://github.com/pengdsci/sta553/blob/main/image/goldenRamLogo.png?raw=true",
  iconWidth = 60, iconHeight = 60
  )
# define a leaflet map 
m <- leaflet() %>%
     setView(lng=-75.5978, lat=39.9522, zoom = 20) %>%
     addTiles() %>%        # Add default OpenStreetMap map tiles
     addMarkers(lng=-75.5978, lat=39.9522,  icon = wcuicon)
m    # Print the map

3. Popups and Labels

Popups are small boxes containing arbitrary HTML, that point to a specific point on the map. We can use the addPopups() function to add a standalone popup to the map. When you click the marker in the following map, you will see a popup with the name of the WCU campus.

df <- read.csv(textConnection(
"Name, Lat, Long
WCU Philadelphia Campus,39.9518,-75.1525
WCU South Campus,39.9373,-75.6011
WCU Main Campus, 39.9524,-75.5982"
))

leaflet(df) %>% 
  addTiles() %>%
  setView(lng=-75.3768, lat=39.9448, zoom = 10) %>%
  addMarkers(~Long, ~Lat, popup = ~paste("Name: ",Name))

We can also change the popups in the above map to labels. The modified code is shown below

df <- read.csv(textConnection(
"Name, Lat, Long
WCU  Philadelphia Campus,39.9518,-75.1525
WCU  South Campus,39.9373,-75.6011
WCU  Main Campus, 39.9524,-75.5982"
))

leaflet(df) %>% 
  addTiles() %>%
  setView(lng=-75.3768, lat=39.9448, zoom = 10) %>%
  addMarkers(~Long, ~Lat, label = ~paste("Name:", Name))

4. Annotations

We have introduced the ways of adding hover messages through label and pop options to points on the map longitude and latitude. We also used reference location using longitude and latitude to insert images to a map.

This section introduced a method to insert text and image annotation to a map. The idea is to wrap the annotation in an HTML tag and then pass the information to map through addControl() function. This method will also be used when we create shiny apps. The relative location on the map is independent on longitude and latitude. We can use location values such as topleft, topright, bottomleft, bottomright.

df <- read.csv(textConnection(
"Name, Lat, Long
WCU  Philadelphia Campus,39.9518,-75.1525
WCU  South Campus,39.9373,-75.6011
WCU  Main Campus, 39.9524,-75.5982"
))
## HTML wrapped annotation
 AnnotateWrapper <- tags$div(
   HTML('<center><a href="https://www.wcupa.edu/"> <img border="0" alt="ImageTitle" src="https://github.com/pengdsci/sta553/blob/main/image/goldenRamLogo.png?raw=true" width="25" height="25"> </a><center>
    <font color = "purple">WCU STA 553 Example</font>')
 )  
###
leaflet(df) %>% 
  addTiles() %>%
  #addProviderTiles("NASAGIBS.ViirsEarthAtNight2012") %>%
  setView(lng=-75.3768, lat=39.9448, zoom = 10) %>%
  addMarkers(~Long, ~Lat, popup = ~paste("Name:", Name, 
                                         "<br> Longitude:", Long, 
                                         "<br>Latitude:", Lat), 
             label = ~paste("Name:", Name))%>%
  addControl(AnnotateWrapper, position = "bottomleft")

5. An Example Using Real-World Data

In the following example, we use a few leaflet functions to add some features such as drawing highlight boxes, labels, etc. to the map.

# Define bounding box using the range of longitude/latitude coordinates
# from the given data set
housing.price <- read.csv("Realestate.csv")
# making static leaflet map
leaflet(housing.price) %>%
  addTiles() %>% 
  setView(lng=mean(housing.price$Longitude), lat=mean(housing.price$Latitude), zoom = 14) %>%
   addRectangles(
    lng1 = min(housing.price$Longitude), lat1 = min(housing.price$Latitude),
    lng2 = max(housing.price$Longitude), lat2 = max(housing.price$Latitude),
    #fillOpacity = 0.2,
    fillColor = "transparent" 
    ) %>%
  fitBounds(
    lng1 = min(housing.price$Longitude), lat1 = min(housing.price$Latitude),
    lng2 = max(housing.price$Longitude), lat2 = max(housing.price$Latitude) ) %>%
  addMarkers(~Longitude, ~Latitude, label = ~PriceUnitArea)

In the next map based on the data, we add more information to that map to display higher dimensional information.

housing.price <- na.omit(read.csv("Realestate.csv"))
## color coding a continuous variable: 
colAge <- cut(housing.price$HouseAge, breaks=c(0, 5, 15, max(housing.price$HouseAge)+1), right = FALSE)
colAgeNum <- as.numeric(colAge)
##
ageColor <- rep("navy", length(colAge))
ageColor[which(colAgeNum==2)] <- "orange"
ageColor[which(colAgeNum==3)] <- "darkred"
## define label with hover messages

label.msg <- paste("Unit Price:", housing.price$PriceUnitArea,    
                   "<br>Dist to MRT:",housing.price$Distance2MRT)

#labels = cat(label.msg)
# making leaflet map
leaflet(housing.price) %>%
  addTiles() %>% 
  setView(lng=mean(housing.price$Longitude), lat=mean(housing.price$Latitude), zoom = 13) %>%
  #OpenStreetMap, Stamen, Esri and OpenWeatherMap.
  #addProviderTiles("Esri.WorldGrayCanvas") %>%
  addProviderTiles(providers$Esri.WorldGrayCanvas) %>%
  addCircleMarkers(
            ~Longitude, 
            ~Latitude,
            color = ageColor,
            radius = ~ sqrt(housing.price$Distance2MRT/10)*0.7,
            stroke = FALSE, 
            fillOpacity = 0.4,
            popup= ~label.msg)  %>%
  addLegend(position = "bottomright", 
            colors = c("navy", "orange","darkred"),
            labels= c("[0,5)", "[5,15)", "[15,44.8)"),
            title= "House Age",
            opacity = 0.4) %>%
  addLegendSize(position = 'topright', 
                  values = sqrt(housing.price$Distance2MRT/10)*0.5,
                   color = 'gray',
               fillColor = 'gray',
                 opacity = .5,
                   title = 'Distance to MRT',
                   shape = 'circle',
             orientation = 'horizontal',
                  breaks = 5)



Chorapleth Maps

Choropleth Maps With Leaflet

As an example, we use shape file shape file of US states (https://pengdsci.github.io/STA553VIZ/w07/us-states.geojson) to represent aggregated information at the state level.

# Map data preparation
electricitycost <-as.tibble(read.csv("https://github.com/pengdsci/sta553/raw/main/data/state_electricity_data_2018.csv")[-9,])  # exclude DC
electricitycost <- electricitycost %>% 
  rename(name = NAME)
#electricitycost$State <- state.abb  # add state abbrevs to specify locations in plot_ly()
# Make state borders red
borders <- list(color = toRGB("red"))
## State shapefile
USStateShpeURL <-"https://raw.githubusercontent.com/PublicaMundi/MappingAPI/master/data/geojson/us-states.json"
#USStateShpeURL <- "https://pengdsci.github.io/STA553VIZ/w07/us-states.geojson"
stateShape <- geojson_read(USStateShpeURL, what = "sp")
## The sp object created above (“states”) stores its data in slots, 
## which can be accessed with the “@” operator.
stateShape@data = left_join(stateShape@data, electricitycost)
# CAUTION: must use left_join to keep all states that were included in the state GEOJSON file!!!
# Create hover text
popuptext = paste('<strong>',stateShape@data$name, "</strong>",'<br>', "Electricity Cost:", stateShape@data$centskWh)
##
pal <- colorNumeric(
    palette = "Oranges",
    domain = stateShape@data$centskWh
)
###
simpleVersion = leaflet(data = stateShape) %>%
    addProviderTiles(provider = "CartoDB.Positron")  %>% 
    setView(lat = 38.0110306, lng = -110.4080342, zoom = 3) %>%
    addPolygons(fillColor = ~pal(centskWh), 
        fillOpacity = 0.8, 
        color = "darkred", 
        weight = 1,
        popup = ~popuptext) %>%
    addLegend(position = "bottomleft",
              pal = pal, 
              values = ~stateShape@data$centskWh, 
              title = "<strong>Price</strong><br>(2008)") 
simpleVersion



We could also add a title with predefined styles, annotated text, and images through HTML tags. Here is an example.

## map title
title <- tags$div( HTML('<font color = "darkred" size =4><b>Average Electricity Price by State (cent/KW/hr)</b></font>')
)
## adding a gif image to the map
 GIFimg <- tags$div(
   HTML('<center> <img border="0" alt="ImageTitle" src="https://pengdsci.github.io/STA553VIZ/w07/img07/banana.gif" width="75" height="75"><center>'))
###
EnhancedMap <- simpleVersion %>%
     addControl(title, position = "topright") %>%
     addControl(GIFimg, position = "bottomright")
EnhancedMap

Plotly Map

plotly aims to be a general-purpose visualization library, and thus, doesn’t aim to be the most fully-featured geospatial visualization toolkit.

plotly uses several different ways to create maps – each with its strengths and weaknesses. It utilizes plotly.js’s built-in support to render the basemap layer. The types of basemap used in plotly are Mapbox (third party software that requires an access token) and D3.js powered basemap. In other words, plotly does not use OpenStreetMap that is used in leaflet, Mapviewer, ggplot2,Shiny, and Tableau.

We will not use Mapbox in this note and focus on the D3.js basemap that does not have many details. The plot function plot_geo() will be used to make quick maps.



Choropleth Maps



In the following, we will introduce the steps for creating Choropleth maps. Since Choropleth maps need to fill and color small regions such as district, county, states, etc., it requires the data set to have a special structure that contains shape information. Two plot constructor functions plot_ly() and plot_geo() will be introduced to create choropleth maps.

  • plot_ly() requires specifying type = choropleth to make a map (basemap from plotly.js). Information in the data set is integrated to the maps by various arguments of plot_ly() and relevant graphic functions that are compatible with plot_ly().

  • plot_geo() requires addTrace() to make choropleth maps and integrate data information to the maps with relevant arguments in addTrace() and graphic functions compatible with plot_geo().


### 1. Choropleth Maps with plot_ly()

In general, making choropleth maps with plot_ly() requires two main types of input:

  • Geometry information provided by
    • one of the built-in geometries within plot_ly such as US states and world countries. See the following example 1: visualizing 2018 electricity cost per state.
    • a supplied GeoJSON file where each feature has either an id field or some identifying value in properties. See the following example 2: visualizing the unemployment rate of US counties
  • A list of values indexed by feature identifier. They control the features in the map including boundary, filled colors, legend, hover text, etc.

For the US map, two types of projections were commonly use: regulary and Albers.



Example 1: US electricity cost by States in 2018. The arguments locations = and locationmode = tell plot_ly what map information should be used to create the base map. Other arguments and functions are used to control different features of the resulting map. One cautionary note is that plot_ly() only uses state abbreviations as the state name.

In the code, the state abbreviations state.abb is a built-in data set. Several other built-in data sets about each state are also available. Check the website http://stats4stem.weebly.com/r-statex77-data.html for more information on these data sets.

# Map data preparation
electricitycost <-read.csv("https://github.com/pengdsci/sta553/raw/main/data/state_electricity_data_2018.csv")[-9,]  # exclude DC
electricitycost$State <- state.abb  # add state abbrevs to specify locations in plot_ly()
# Create hover text
electricitycost$hover <- with(electricitycost, paste(State, '<br>', "Electricity Cost:", centskWh))
# Make state borders white
borders <- list(color = toRGB("red"))
# Set up some mapping options
map_options <- list(
  scope = 'usa',
  projection = list(type = 'albers usa'),
  showlakes = TRUE,
  lakecolor = toRGB('white')
)
plot_ly( z = ~electricitycost$centskWh, 
        text = ~electricitycost$hover, 
        locations = ~electricitycost$State, 
        type = 'choropleth', 
        locationmode = 'USA-states', 
        colors = 'RdPu', 
        color = electricitycost$centskWh, 
        marker = list(line = borders)) %>%
  layout(title = 'US State Electricity Unit Cost (cents/kWh)', 
         geo = map_options)

Example 2: The unemployment rates of US counties. This example requires a JSON file to provide necessary geometric information (shape polygon) about each county in the US. The argument locations = accepts FIPS (Federal Information Process System) for US county maps. The geometric information of the US county shape is supplied in a JSON file and used through the argument geojson =.

#library(plotly)
#library(rjson)
#library("RColorBrewer")  # brewer.pal.info for list of color scales

url <- 'https://github.com/pengdsci/sta553/raw/main/data/geojson-counties-fips.json'  # contains geocode to define county boundaries in the choropleth map
counties <- rjson::fromJSON(file=url)  
load("img07//unemp.rda")
#load("/Users/chengpeng/WCU/Teaching/2022Spring/STA553/RMaps/unemp.rda")
df=unemp
g <- list(
      scope = 'usa',
      projection = list(type = 'albers usa'),
      showlakes = TRUE,
      lakecolor = toRGB('white')
    )
###
fig <- plot_ly()  %>% 
  add_trace( type = "choropleth",
          geojson = counties,
        locations = df$fips,
                z = df$rate,
       colorscale = "GnBu",  
             zmin = 0,
             zmax = 30,
             text = df$name, # hover mesg
           marker = list(line=list(width=0.2))
          )   %>% 
  colorbar(title = "Unemployment Rate (%)",
           colorscale='Viridis')  %>% 
  layout( ### Title 
          title =list(text = "US Unemployment by County", 
                          font = list(family = "Times New Roman",  # HTML font family  
                                        size = 18,
                                       color = "red")), 
          geo = g)
## The actual HTML page does shoe colorbar correctly, we hide the legend.
hide_legend(fig)

Example 3 US states facts. Similar to example 1, but with more variables. The data set is built-in in the base R package.

# Create data frame
state_pop <- read.csv("https://raw.githubusercontent.com/pengdsci/sta553/main/data/USStatesFacts.csv")
# Create hover text
state_pop$hover <- with(state_pop, 
                        paste(STName, '<br>', "Population:", Population,
                              '<br>', "Income:", Income,
                              '<br>', "Life.Exp:", Life.Exp,
                              '<br>', "Murder:", Murder,
                              '<br>', "HS.Grad:", HS.Grad))
# Make state borders white
borders <- list(color = toRGB("red"))
# Set up some mapping options
map_options <- list(
  scope = 'usa',
  projection = list(type = 'regular usa'),
  showlakes = TRUE,
  lakecolor = toRGB('white')
)
plot_ly(z = ~state_pop$Population, 
        text = ~state_pop$hover, 
        locations = ~state_pop$State, 
        type = 'choropleth', 
        locationmode = 'USA-states', 
        color = state_pop$Population, 
        colors = 'YlOrRd', 
        marker = list(line = borders)) %>%
  layout(title = 'US Population in 1975', geo = map_options)


### 2. Choropleth Maps with plot_geo()
Making a choropleth map with plot_geo requires less effort to prepare the shape data. The geo-information was called through locations = and locationmode =.


# library(plotly)
# read in cv data
df <- read.csv("https://raw.githubusercontent.com/pengdsci/sta553/main/data/2011_us_ag_exports.csv")
## Define hover text
df$hover <- with(df, paste(state, "\n",
                           "Beef", beef, "\n",
                           "Dairy", dairy, "\n",
                           "Fruits", total.fruits, "\n",
                           "Veggies", total.veggies, "\n",
                           "Wheat", wheat, "\n",
                           "Corn", corn))

# give state boundaries a white border
l <- list(color = toRGB("white"), width = 2)
# specify some map projection/options
g <- list(     scope = 'usa',
          projection = list(type = 'albers usa'),
           showlakes = TRUE,
           lakecolor = toRGB('white')
      )
## plot map
m <- plot_geo(df, locationmode = 'USA-states') %>%
     add_trace(        z = ~total.exports, 
                    text = ~hover, 
               locations = ~code,
                   color = ~total.exports, 
                  colors = 'YlOrRd'
              )  %>% 
     colorbar(title = "Millions USD")  %>% 
     layout( title = '2011 US Agriculture Exports by State<br>(Hover for breakdown)',
               geo = g
            )
m

Since the colorbars are not displayed correctly in the kitted HTML document, we take a screenshot in the following to display the correct colorbar.

include_graphics("img07/w07-map14.png")



Scatter Maps



3.Scatter Map with plot_geo() and add_markers()

A Scatter map is relatively easier to make since we only plot the base map using the longitude and latitude. No map shape information is needed for scatter maps.

Example 4 US Airport Traffic.

#library(plotly)
df <- read.csv('https://raw.githubusercontent.com/pengdsci/sta553/main/data/2011_february_us_airport_traffic.csv')
# geo styling
g <- list(      scope = 'usa',
           projection = list(type = 'albers usa'),
             showland = TRUE,
            landcolor = toRGB("gray95"),
         subunitcolor = toRGB("gray85"),
         countrycolor = toRGB("gray85"),
         countrywidth = 0.5,
         subunitwidth = 0.5
       )
###
fig <- plot_geo(df, lat = ~lat, lon = ~long) %>% 
  add_markers( text = ~paste(airport, city, state, 
                             paste("Arrivals:", cnt), 
                             sep = "<br>"),
              color = ~cnt, 
              symbol = "circle", 
              size = ~cnt, 
              hoverinfo = "text")   %>% 
  colorbar(title = "Incoming flights<br>2011.2")  %>% 
  layout( title = 'Most trafficked US airports', 
          geo = g )

fig


Custom Maps



4. Custom Map With Special Libraries


Sometimes, we may want to use custom maps to represent spatial information. For example, if we want to visualize the area of US states, the previous US maps are fine. If we want to represent the population size (Example 3), we may want to use a map such that the displayed area is proportional to the population size but not the geographical area. These types of custom maps need special tools to construct. Show you an exam without providing code to make the map.

Example 4 US population by states.





Thematic Maps

The tmap package is a relatively new way to plot thematic maps in R. Thematic maps are geographical maps in which spatial data distributions are visualized. This package offers a flexible and layer-based approach to creating thematic maps, such as choropleths and bubble maps. The syntax for creating plots is similar to that of ggplot2.

tmap_mode() will be used to determine interactivity of the map: tmap_mode("plot") produce static maps and tmap_mode("view") produce interactive maps.

1. Choropleth Maps

We will use a built-in world shapefile, World, that contains information about population, gdp, life expectancy, income, happiness index, etc.

Both choropleths and scatter maps will be illustrated using the built-in data.

Example 1: Choropleths: The default world map with the distribution of mean life expectancy among all countries. The default tmap_mode is set to be plot. The default tmap map is static.

library(tmap)
data(World)
tm_shape(World) +
    tm_polygons("life_exp")

Example 2: Interactive Map: use the above static map as the base map and add interactive features to the maps. The mode can be set with the function tmap_mode(), and toggling between the modes can be done with the ‘switch’ ttm() (which stands for toggle thematic map.

library(tmap)
#
tmap_mode("view")  # "view" gives interactive map; "plot" gives static map. 
##
## tmap_style set to "classic"
tmap_style("classic")
## other available styles are: "white", "gray", "natural", 
## "cobalt", "col_blind", "albatross", "beaver", "bw", "watercolor"
tmap_options(bg.color = "skyblue", 
             legend.text.color = "white")
##
tm_shape(World) +
      tm_polygons("life_exp", 
                  legend.title = "Life Expectancy") +
      tm_layout(bg.color = "gray", 
                inner.margins = c(0, .02, .02, .02)) 

Mixed Maps (Multilayer)

Through a multilayer map, we can make a choropleth and place another on top of it. In the following example, we add one additional layer using the metro shapefile and plot the center of the metro area to get a scatter map.

Example 3: Mixed Map: two-layer mixes maps

library(tmap)
#*
data(metro)
##
tmap_mode("view")  # "view" gives interactive map; 
#tmap_style("classic") ## tmap_style set to "classic"
## other available styles are: "white", "gray", "natural", 
## "cobalt", "col_blind", "albatross", "beaver", "bw", "watercolor"
tmap_options(bg.color = "skyblue", 
             legend.text.color = "white")
##
tm_shape(World) +
      tm_polygons("life_exp", 
                  legend.title = "Life Expectancy") +
      tm_layout(bg.color = "gray", 
                inner.margins = c(0, .02, .02, .02)) + 
tm_shape(metro) +
      tm_symbols(col = "purple", 
                 size = "pop2020", 
                 scale = .5,
                 alpha = 0.5,
                 popup.vars=c("pop1950", "pop1960", "pop1980","pop1990",                   "pop2000","pop2010","pop2020")) 
library(spData)
library(sf)
library(mapview)
gj = "https://github.com/azavea/geo-data/raw/master/Neighborhoods_Philadelphia/Neighborhoods_Philadelphia.geojson"
##
gjsf = st_read(gj)
Reading layer `Neighborhoods_Philadelphia' from data source 
  `https://github.com/azavea/geo-data/raw/master/Neighborhoods_Philadelphia/Neighborhoods_Philadelphia.geojson' 
  using driver `GeoJSON'
Simple feature collection with 158 features and 8 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -75.28027 ymin: 39.867 xmax: -74.95576 ymax: 40.13799
Geodetic CRS:  WGS 84
library(tmap)
#    tm_shape(World) +
    tm_shape(gjsf)  + 
      tm_polygons(legend.show = FALSE)  +
      tm_bubbles("shape_area",            
                 #col = "shape_area", 
                 #breaks=seq(1276674, 129254597, length = 6),
                 palette="-RdYlBu", 
                 contrast=1)

Scatter Maps With geoCoordinates

We use the real estate data set to make a scatter map using library tmap. We first need to define an sf object using st_as_sf() that shapefile with an individual point based on the longitude and latitude in the data set.

library(tmap)
library(sf)
realestate0 <- read.csv("Realestate.csv", header = TRUE)
realest <- realestate0[, -1]
## create a shapefile with POINT type.
realest <-st_as_sf(realest, coords=c("Longitude","Latitude"), crs = 4326)
###
tm_shape(realest) + 
  tm_dots(col = "purple", 
          size = "Distance2MRT", 
          alpha = 0.5,
          popup.vars=c("HouseAge", "PriceUnitArea", "NumConvenStores"),
          shapes = c(1, 0)) 

Tableau Maps

We create a choropleth map and a scatter map respectively in this note. Before creating maps, we first look understand the structure of Tableau’s sheet.

We can see that each Tableau book has several components:

  • A - Workbook name - A workbook contains sheets. A sheet can be a worksheet, a dashboard, or a story.

  • B - Cards and shelves - Drag fields to the cards and shelves in the work-space to add data to your view.

  • C - Toolbar - Use the toolbar to access commands and analysis and navigation tools.

  • D - View - This is the canvas in the work-space where you create a visualization (also referred to as a “viz”).

  • E - Start page icon - Click this icon to go to the Start page, where you can connect to data. For more information, see Start Page.

  • F - Side Bar - In a worksheet, the side bar area contains two tabs: the Data pane and the Analytics pane.

  • G - Data Source - Click this tab to go to the Data Source page and view your data.

  • H - Status bar - Displays information about the current view.

  • I - Sheet tabs - Tabs represent each sheet in your workbook. This can include worksheets, dashboards, and stories. You can rename and add more of these sheets, dashboards and stories if needed.

  • Show Me - Click this toggle to select 24 built-in charts and the information needed to create these charts.

Choropleth Map

The data set we use for a choropleth map can be downloaded from https://raw.githubusercontent.com/pengdsci/sta553/main/data/USStatesFacts.csv.

You need to download save this data file in a folder and then connect it to Tableau Public (or Tableau Online).

The following are steps for making a choropleth map:

  1. Load the .csv file to Tableau (Public);

  2. Click sheet1 in the bottom left taskbar;

  3. Drag variable State (on the left navigation panel under the table) to the main drop field (Tableau considers State as a geo-variable); at the same time, the two generated Longitude(generated) and Latitude(generated) appear in the column and row fields automatically.

  4. Click the Show Me (on the right side of a tiny color bar chart) in the top right of the screen;

  5. You will see a list of graphs. Click the middle world map in the second row, you will see an initial choropleth map.

  6. Click Show Me again to close the popup. We can click the legend on the top-right color to change the color of the map (if you like).

  7. To add more information to the hover text, you drag the variables on the list to the small icon labeled with Detail.

  8. Click Sheet 1 to change it to a meaningful title.

  9. Finally we label the states by their abbreviations. To do this, drag State to Label in the Marks table (next to Detail).

  10. You can edit the hover text by clicking Tooltip.

The resulting map can be viewed on the Tableau Public Server at https://public.tableau.com/app/profile/cpeng/viz/US-States-Facts/Sheet1

Scatter Map

We use housing price data with longitude and latitude associated with each property. The data set is at https://projectdat.s3.amazonaws.com/Realestate.csv

As we did in the previous example, we download the data set and save it in a folder.

The following are steps to create a scatter map.

  1. Open the Tableau and connect the data source to Tableau.

  2. After the data has been loaded to the Tableau, click Sheet1, you will see the list of variables on the left panel.

  3. click Latitude -> Geographic Role -> Latitude; do the same thing to Longitude.

  4. Drag Latitude to the Columns field and Longitude to the Rows field. You will see a single point in Sheet 1. The two variables were automatically renamed as AVG(Latitude) and AVG(Longitude).

  5. Click AVG(Latitude) and select dimension, you will see a line plot in Sheet 1. Do the same thing to AVG(Longitude). Now you see a scatter plot.

  6. Click Show Me (top-right corner of Book 1) and select the left-hand side map icon (the first one in the second row), you will see an initial scatter map.

  7. We want to use the size of the point to reflect the unit price. we drag PriceUnitArea to Size card in the Marks shelf.

  8. Click Show Me to close the chart menu. Click SUM(Price Unit Area) (top-right corner) to change the point size.

  9. I drag Transaction Year to the Color card to reflect the transaction year. We should choose a divergent color scale.

  10. drag variables to the Detail card to be shown in the hover text.

  11. Since many unit prices are close to each other, there are overlapped points. So we want to change the level of opacity. To do this, click Color card, choose the appropriate level of opacity, and edit the color to make a better map.

  12. Add a meaningful title.

  13. Right click the map and select Map Layers make the changes on the map background and layers.

  14. Other edits and modifications to improve map.

The resulting map can be viewed on the Tableau Public Server at https://public.tableau.com/app/profile/cpeng/viz/RealEstateData_16469067466610/Sheet1

R Color Palettes


Since color coding is particularly important in map representation. We can use the following code to view various defined color scales (continuous and discrete) in the R library RColorBrewer.
* Sequential palettes are suited to ordered data that progress from low to high (gradient). The palettes names are : Blues, BuGn, BuPu, GnBu, Greens, Greys, Oranges, OrRd, PuBu, PuBuGn, PuRd, Purples, RdPu, Reds, YlGn, YlGnBu, YlOrBr, YlOrRd.

  • Qualitative palettes are best suited to represent nominal or categorical data. They do not imply magnitude differences between groups. The palettes names are : Accent, Dark2, Paired, Pastel1, Pastel2, Set1, Set2, Set3.

  • Diverging palettes put equal emphasis on mid-range critical values and extremes at both ends of the data range. The diverging palettes are : BrBG, PiYG, PRGn, PuOr, RdBu, RdGy, RdYlBu, RdYlGn, Spectral.



All Palettes

library("RColorBrewer")
display.brewer.all() 

2. Color-bind Friendly Palettes

library("RColorBrewer")
display.brewer.all(colorblindFriendly = TRUE) 

3. Color Palette Codes

library("RColorBrewer")
kable(brewer.pal.info)
maxcolors category colorblind
BrBG 11 div TRUE
PiYG 11 div TRUE
PRGn 11 div TRUE
PuOr 11 div TRUE
RdBu 11 div TRUE
RdGy 11 div FALSE
RdYlBu 11 div TRUE
RdYlGn 11 div FALSE
Spectral 11 div FALSE
Accent 8 qual FALSE
Dark2 8 qual TRUE
Paired 12 qual TRUE
Pastel1 9 qual FALSE
Pastel2 8 qual FALSE
Set1 9 qual FALSE
Set2 8 qual TRUE
Set3 12 qual FALSE
Blues 9 seq TRUE
BuGn 9 seq TRUE
BuPu 9 seq TRUE
GnBu 9 seq TRUE
Greens 9 seq TRUE
Greys 9 seq TRUE
Oranges 9 seq TRUE
OrRd 9 seq TRUE
PuBu 9 seq TRUE
PuBuGn 9 seq TRUE
PuRd 9 seq TRUE
Purples 9 seq TRUE
RdPu 9 seq TRUE
Reds 9 seq TRUE
YlGn 9 seq TRUE
YlGnBu 9 seq TRUE
YlOrBr 9 seq TRUE
YlOrRd 9 seq TRUE

4. Functions for Selecting Specific Color Palettes

Two functions can be used to display a specific color palette or return the code of the palette.

  • display.brewer.pal(n, name) displays a single RColorBrewer palette by specifying its name.

  • brewer.pal(n, name) returns the hexadecimal color code of the palette.

The two arguments:

n = Number of different colors in the palette, minimum 3, maximum depending on palette.

name= A palette name from the lists above. For example name = RdBu.

Example 1: Display the first 8 colors of palette Dark2.

# View a single RColorBrewer palette by specifying its name
display.brewer.pal(n = 8, name = 'Dark2')

Example 2: Return the hexadecimal of the first 8 colors of palette Dark2.

# Hexadecimal color specification 
kable(t(brewer.pal(n = 8, name = "Dark2")))
#1B9E77 #D95F02 #7570B3 #E7298A #66A61E #E6AB02 #A6761D #666666

A. Functions Calling Specific rcolorbrewer Palette in ggplot()

The following color scale functions are available in ggplot2 for using the rcolorbrewer palettes:

scale_fill_brewer() for box plot, bar plot, violin plot, dot plot, etc.

scale_color_brewer() for lines and points

B. Functions Calling Specific rcolorbrewer Palette in Base Plots

The function brewer.pal() is used to generate a vector of colors.

# Barplot using RColorBrewer
barplot(c(2,5,7), col = brewer.pal(n = 3, name = "Dark2"))

LS0tDQp0aXRsZTogIkludGVyYWN0aXZlIEdlb3NwYXRpYWwgVmlzdWFsaXphdGlvbiBVc2luZyBNYXBzIg0KYXV0aG9yOiAiQ2hlbmcgUGVuZyINCmRhdGU6ICJXZXN0IENoZXN0ZXIgVW5pdmVyc2l0eSINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICBzbW9vdGhfc2Nyb2xsOiB5ZXMNCiAgICB0aGVtZTogbHVtZW4NCiAgcGRmX2RvY3VtZW50OiANCiAgICBoaWdobGlnaHQ6IHRhbmdvDQplZGl0b3Jfb3B0aW9uczoNCiAgDQogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUNCi0tLQ0KDQoNCg0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4NCg0KZGl2I1RPQyBsaSB7DQogICAgbGlzdC1zdHlsZTpub25lOw0KICAgIGJhY2tncm91bmQtaW1hZ2U6bm9uZTsNCiAgICBiYWNrZ3JvdW5kLXJlcGVhdDpub25lOw0KICAgIGJhY2tncm91bmQtcG9zaXRpb246MDsNCn0NCmgxLnRpdGxlIHsNCiAgZm9udC1zaXplOiAyNHB4Ow0KICBjb2xvcjogRGFya1JlZDsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KaDQuYXV0aG9yIHsgLyogSGVhZGVyIDQgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8NCiAgICBmb250LXNpemU6IDE4cHg7DQogIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICBjb2xvcjogRGFya1JlZDsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KaDQuZGF0ZSB7IC8qIEhlYWRlciA0IC0gYW5kIHRoZSBhdXRob3IgYW5kIGRhdGEgaGVhZGVycyB1c2UgdGhpcyB0b28gICovDQogIGZvbnQtc2l6ZTogMThweDsNCiAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogIGNvbG9yOiBEYXJrQmx1ZTsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KaDEgeyAvKiBIZWFkZXIgMyAtIGFuZCB0aGUgYXV0aG9yIGFuZCBkYXRhIGhlYWRlcnMgdXNlIHRoaXMgdG9vICAqLw0KICAgIGZvbnQtc2l6ZTogMjJweDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogZGFya3JlZDsNCiAgICB0ZXh0LWFsaWduOiBjZW50ZXI7DQp9DQpoMiB7IC8qIEhlYWRlciAzIC0gYW5kIHRoZSBhdXRob3IgYW5kIGRhdGEgaGVhZGVycyB1c2UgdGhpcyB0b28gICovDQogICAgZm9udC1zaXplOiAxOHB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBuYXZ5Ow0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQoNCmgzIHsgLyogSGVhZGVyIDMgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8NCiAgICBmb250LXNpemU6IDE1cHg7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IGRhcmtyZWQ7DQogICAgZm9udC1mYWNlOiBib2xkOw0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQoNCmg0IHsgLyogSGVhZGVyIDQgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8NCiAgICBmb250LXNpemU6IDE4cHg7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IGRhcmtyZWQ7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCi8qIFRhYiBmZWF0dXJlcyAqLw0KLm5hdj5saT5hIHsNCiAgICBwb3NpdGlvbjogcmVsYXRpdmU7DQogICAgZGlzcGxheTogYmxvY2s7DQogICAgcGFkZGluZzogMTBweCAxNXB4Ow0KICAgIGNvbG9yOiAjOTkwMDAwOw0KfQ0KLm5hdi1waWxscz5saS5hY3RpdmU+YSwgLm5hdi1waWxscz5saS5hY3RpdmU+YTpob3ZlciwgLm5hdi1waWxscz5saS5hY3RpdmU+YTpmb2N1cyB7DQogICAgY29sb3I6ICNmZmZmZmY7DQogICAgYmFja2dyb3VuZC1jb2xvcjogIzk5MDAwMDsNCn0NCi8qIGNlbnRlciBtYXBzIHVzaW5nIGNodW5rIG9wdGlvbjogZmlnLmFsaWduPSdjZW50ZXInICovDQouaHRtbC13aWRnZXQgew0KICAgIG1hcmdpbjogYXV0bzsNCn0NCjwvc3R5bGU+DQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0KIyBjb2RlIGNodW5rIHNwZWNpZmllcyB3aGV0aGVyIHRoZSBSIGNvZGUsIHdhcm5pbmdzLCBhbmQgb3V0cHV0IA0KIyB3aWxsIGJlIGluY2x1ZGVkIGluIHRoZSBvdXRwdXQgZmlsZXMuDQppZiAoIXJlcXVpcmUoInRpZHl2ZXJzZSIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKQ0KICAgbGlicmFyeSh0aWR5dmVyc2UpDQp9DQppZiAoIXJlcXVpcmUoImtuaXRyIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoImtuaXRyIikNCiAgIGxpYnJhcnkoa25pdHIpDQp9DQppZiAoIXJlcXVpcmUoInBsb3RseSIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJwbG90bHkiKQ0KICAgbGlicmFyeShwbG90bHkpDQp9DQppZiAoIXJlcXVpcmUoImdhcG1pbmRlciIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJnYXBtaW5kZXIiKQ0KICAgbGlicmFyeShnYXBtaW5kZXIpDQp9DQppZiAoIXJlcXVpcmUoIlJDdXJsIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJSQ3VybCIpICAgICAgICAgICAgICMgSW5zdGFsbCBSQ3VybCBwYWNrYWdlDQogICAgbGlicmFyeSgiUkN1cmwiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJjb2xvdXJwaWNrZXIiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoImNvbG91cnBpY2tlciIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJjb2xvdXJwaWNrZXIiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJnZ2FuaW1hdGUiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoImdnYW5pbWF0ZSIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJnZ2FuaW1hdGUiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJnaWZza2kiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoImdpZnNraSIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJnaWZza2kiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJtYWdpY2siKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoIm1hZ2ljayIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJtYWdpY2siKQ0KfQ0KaWYgKCFyZXF1aXJlKCJnckRldmljZXMiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoImdyRGV2aWNlcyIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJnckRldmljZXMiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJsZWFmbGV0IikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJsZWFmbGV0IikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoImxlYWZsZXQiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJtYXBzIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJtYXBzIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoIm1hcHMiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJodG1sdG9vbHMiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoImh0bWx0b29scyIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJodG1sdG9vbHMiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJodG1sd2lkZ2V0cyIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygiaHRtbHdpZGdldHMiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgiaHRtbHdpZGdldHMiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJsZWFmbGVnZW5kIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJsZWFmbGVnZW5kIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoImxlYWZsZWdlbmQiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJnZW9qc29uaW8iKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoImdlb2pzb25pbyIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJnZW9qc29uaW8iKQ0KfQ0KaWYgKCFyZXF1aXJlKCJzdHJpbmdpIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJzdHJpbmdpIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoInN0cmluZ2kiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJSQ29sb3JCcmV3ZXIiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoIlJDb2xvckJyZXdlciIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJSQ29sb3JCcmV3ZXIiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJ0aWdyaXMiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoInRpZ3JpcyIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJ0aWdyaXMiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJsZWFmcG9wIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJsZWFmcG9wIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoImxlYWZwb3AiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJsZWFmZW0iKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoImxlYWZlbSIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJsZWFmZW0iKQ0KfQ0KaWYgKCFyZXF1aXJlKCJ0bWFwIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJ0bWFwIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoInRtYXAiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJ0bWFwdG9vbHMiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoInRtYXB0b29scyIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJ0bWFwdG9vbHMiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJ3ZWJzaG90MiIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygid2Vic2hvdDIiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgid2Vic2hvdDIiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJzZiIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygic2YiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgic2YiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJ0ZXJyYSIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygidGVycmEiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgidGVycmEiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJsZWFmcG9wIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJsZWFmcG9wIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoImxlYWZwb3AiKQ0KfQ0KDQojIw0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UsICAgDQogICAgICAgICAgICAgICAgICAgICAgcmVzdWx0ID0gVFJVRSwgICANCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgY29tbWVudCA9IE5BKQ0KYGBgDQoNCg0KIyB7LnRhYnNldCAudGFic2V0LWZhZGUgLnRhYnNldC1waWxsc30NCg0KPEJSPjxCUj4NClRoZXJlIGlzIGEgdmVyeSByaWNoIHNldCBvZiB0b29scyBmb3IgaW50ZXJhY3RpdmUgZ2Vvc3BhdGlhbCB2aXN1YWxpemF0aW9uLiBUaGlzIG5vdGUgaW50cm9kdWNlcyB2YXJpb3VzIFIgdG9vbHMgYW5kIFRhYmxlYXUgdG8gY3JlYXRlIGludGVyYWN0aXZlIG1hcHMgZm9yIHZpc3VhbGl6aW5nIHNwYXRpYWwgcGF0dGVybnMuICA8YnI+DQoNCjxmb250IGNvbG9yID0gImRhcmtyZWQiPjxiPkNhdXRpb248L2I+PC9mb250PjogUmVjZW50IGNoYW5nZXMgdG8gcG9wdWxhciBSIHBhY2thZ2VzIGZvciBzcGF0aWFsIGRhdGEgLSB3aGF0IHlvdSBuZWVkIHRvIGRvICg8aHR0cHM6Ly93d3cuci1ibG9nZ2Vycy5jb20vMjAyMy8wNi91cGNvbWluZy1jaGFuZ2VzLXRvLXBvcHVsYXItci1wYWNrYWdlcy1mb3Itc3BhdGlhbC1kYXRhLXdoYXQteW91LW5lZWQtdG8tZG8vPikuDQoNCjxCUj4NCjxCUj4NCg0KIyMgTWFwIFR5cGVzDQoNCjxCUj4NClRoZXJlIGFyZSB0d28gY29tbW9uIHdheXMgb2YgcmVwcmVzZW50aW5nIHNwYXRpYWwgZGF0YSBvbiBhIG1hcDoNCg0KKiBEZWZpbmluZyByZWdpb25zIG9uIGEgbWFwIGFuZCBkaXN0aW5ndWlzaGluZyB0aGVtIGJhc2VkIG9uIHRoZWlyIHZhbHVlIG9uIHNvbWUgbWVhc3VyZSB1c2luZyBjb2xvcnMgYW5kIHNoYWRpbmcuIFRoaXMgdHlwZSBvZiBtYXAgaXMgdXN1YWxseSBjYWxsZWQgYGNob3JvcGxldGggbWFwYC4gDQoNCiogTWFya2luZyBpbmRpdmlkdWFsIHBvaW50cyBvbiBhIG1hcCBiYXNlZCBvbiB0aGVpciBsb25naXR1ZGUgYW5kIGxhdGl0dWRlIChlLmcuLCBhcmNoYWVvbG9naWNhbCBkaWcgc2l0ZXM7IGJhc2ViYWxsIHN0YWRpdW1zOyB2b3RpbmcgbG9jYXRpb25zLCBldGMuKS4gVGhpcyB0eXBlIG9mIG1hcCBpcyBhbHNvIGNhbGxlZCBhIGBzY2F0dGVyIG1hcGAuIA0KDQpQbG90dGluZyBgc2NhdHRlciBtYXBzYCB1c2VzIGdlb2NvZGUgYW5kIGFyZSByZWxhdGl2ZWx5IGVhc2llciB0byBjcmVhdGUuIEhvd2V2ZXIsIGEgY2hvcm9wbGV0aCBtYXAgaXMgY29uc3RydWN0ZWQgdXNpbmcgZGF0YSB3aXRoIGEgc3BlY2lhbCBzdHJ1Y3R1cmUgd2l0aCBzaGFwZSBpbmZvcm1hdGlvbi4gSXQgaXMgcmVsYXRpdmVseSBoYXJkZXIgdG8gY29uc3RydWN0IGEgY2hvcm9wbGV0aCBtYXAuDQoNCioqQSBiYXNlbWFwKiogcHJvdmlkZXMgY29udGV4dCBmb3IgYWRkaXRpb25hbCBsYXllcnMgdGhhdCBhcmUgb3ZlcmxhaWQgb24gdG9wIG9mIHRoZSBiYXNlbWFwLiBCYXNlbWFwcyB1c3VhbGx5IHByb3ZpZGUgbG9jYXRpb24gcmVmZXJlbmNlcyBmb3IgZmVhdHVyZXMgdGhhdCBkbyBub3QgY2hhbmdlIG9mdGVuIGxpa2UgYm91bmRhcmllcywgcml2ZXJzLCBsYWtlcywgcm9hZHMsIGFuZCBoaWdod2F5cy4gRXZlbiBvbiBiYXNlbWFwcywgdGhlc2UgZGlmZmVyZW50IGNhdGVnb3JpZXMgb2YgaW5mb3JtYXRpb24gYXJlIGluIGxheWVycy4gVXN1YWxseSBhIGJhc2VtYXAgY29udGFpbnMgdGhpcyBiYXNpYyBkYXRhLCBhbmQgdGhlbiBleHRyYSBsYXllcnMgd2l0aCBwYXJ0aWN1bGFyIGluZm9ybWF0aW9uIGZyb20gYSBwYXJ0aWN1bGFyIGRhdGEgc2V0LCBhcmUgb3ZlcmxhaWQgb24gdGhlIGJhc2UgbWFwIGxheWVycyBmb3IgdmlzdWFsIGFuYWx5c2lzLg0KDQpJbiB0aGlzIG5vdGUsIHRoZSBiYXNlbWFwcyBjb21lIHByaW1hcmlseSBmcm9tIHRoZSBvcGVuLWRhdGEtc291cmNlLWJhc2VkIFtPcGVuU3RyZWVwTWFwXShodHRwczovL3d3dy5vcGVuc3RyZWV0bWFwLm9yZy8jbWFwPTUvMzguMDA3Ly05NS44NDQpLg0KDQo8QlI+PEJSPg0KPGNlbnRlcj48Zm9udCBjb2xvciA9ICJyZWQiICBzaXplID00PjxiPiBDaG9yb3BsZXRoIE1hcCA8L2I+PC9mb250PjwvY2VudGVyPg0KPEJSPg0KPGJyPg0KPGNlbnRlcj48aW1nIHNyYz0iaHR0cHM6Ly9naXRodWIuY29tL3Blbmdkc2NpL3N0YTU1My9ibG9iL21haW4vaW1hZ2UvQ2hvcm9wbGV0aE1hcC5wbmc/cmF3PXRydWUiICBoZWlnaHQ9IjI2MCIgd2lkdGg9IjUwMCI+PC9jZW50ZXI+DQo8QlI+PEJSPg0KPGNlbnRlcj48Zm9udCBjb2xvciA9ICJyZWQiICBzaXplID00PjxiPiBTY2F0dGVyIE1hcCA8L2I+PC9mb250PjwvY2VudGVyPg0KPEJSPg0KPGJyPg0KPGNlbnRlcj48aW1nIHNyYz0iaHR0cHM6Ly9naXRodWIuY29tL3Blbmdkc2NpL3N0YTU1My9ibG9iL21haW4vaW1hZ2UvcmVmZXJlbmNlLmpwZz9yYXc9dHJ1ZSIgIGhlaWdodD0iMjYwIiB3aWR0aD0iNTAwIj48L2NlbnRlcj4NCjxicj4NCg0KDQoNCiMjIExlYWZsZXQgTWFwcw0KDQo8QlI+PEJSPg0KV2Ugd2lsbCB1c2UgUiBgbGVhZmxldGAgbGlicmFyeSB0byBjcmVhdGUgYm90aCByZWZlcmVuY2UgbWFwcyBhbmQgY2hvcm9wbGV0aCBtYXBzIGFuZCBwbG90IGRhdGEgb24gbWFwcyB0byBkaXNwbGF5IHNwYXRpYWwgcGF0dGVybnMuDQo8QlI+PEJSPg0KDQo8Zm9udCBzaXplID0gNCwgY29sb3IgPSAicmVkIj48Yj4gUmVmZXJlbmNlIE1hcHMgPC9iPjwvZm9udD4NCjxCUj48QlI+DQoNCiMjIyAxLiBJbnRyb2R1Y3Rpb24NCg0KYExlYWZsZXRgIGlzIG9uZSBvZiB0aGUgbW9zdCBwb3B1bGFyIG9wZW4tc291cmNlIEphdmFTY3JpcHQgbGlicmFyaWVzIGZvciBpbnRlcmFjdGl2ZSBtYXBzLiBJdOKAmXMgdXNlZCB3aWRlbHkgaW4gcHJhY3RpY2UuIEl0IGhhcyBtYW55IG5pY2UgZmVhdHVyZXMuIFIgcGFja2FnZSBgbGVhZmxldGAgYWxsb3dzIHVzIHRvIG1ha2UgaW50ZXJhY3RpdmUgbWFwcyB1c2luZyBtYXAgdGlsZXMsIG1hcmtlcnMsIHBvbHlnb25zLCBsaW5lcywgYW5kIHBvcHVwcywgZXRjLg0KDQpUaGUgZnVuY3Rpb24gYGxlYWZsZXQoKWAgcmV0dXJucyBhIExlYWZsZXQgbWFwIHdpZGdldCwgd2hpY2ggc3RvcmVzIGEgbGlzdCBvZiBvYmplY3RzIHRoYXQgY2FuIGJlIG1vZGlmaWVkIG9yIHVwZGF0ZWQgbGF0ZXIuIE1vc3QgZnVuY3Rpb25zIGluIHRoaXMgcGFja2FnZSBoYXZlIGFuIGFyZ3VtZW50IG1hcCBhcyB0aGVpciBmaXJzdCBhcmd1bWVudCwgd2hpY2ggbWFrZXMgaXQgZWFzeSB0byB1c2UgdGhlIHBpcGUgb3BlcmF0b3IgYCU+JWAuIA0KDQpDcmVhdGluZyBhIGxlYWZsZXQgbWFwIHdpdGggUiBsaWJyYXJ5IGBsZWFmbGV0YCBjb25zaXN0aW5nIG9mIHRoZSBmb2xsb3dpbmcgc3RlcHMuDQoNCiogQ3JlYXRlIGEgbWFwIHdpZGdldCBieSBjYWxsaW5nIGxlYWZsZXQoKS4NCg0KKiBBZGQgbGF5ZXJzIChpLmUuLCBmZWF0dXJlcykgdG8gdGhlIG1hcCBieSB1c2luZyBsYXllciBmdW5jdGlvbnMgKGUuZy4gYWRkVGlsZXMsIGFkZE1hcmtlcnMsIGFkZFBvbHlnb25zKSB0byBtb2RpZnkgdGhlIG1hcCB3aWRnZXQuDQoNCiogUmVwZWF0IHRoZSBwcmV2aW91cyBhcyBkZXNpcmVkLg0KDQoqIFByaW50IHRoZSBtYXAgd2lkZ2V0IHRvIGRpc3BsYXkgaXQuDQoNCkxldOKAmXMgbG9vayBhdCB0aGUgZm9sbG93aW5nIHNpbXBsZSBleGFtcGxlLg0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTZ9DQojIGxpYnJhcnkobGVhZmxldCkgICAgICAgICMgaXQgaGFzIGJlZW4gbG9hZGVkIGluIHRoZSBzZXR1cCBjaHVuay4NCiMgZGVmaW5lIGEgbGVhZmxldCBtYXAgDQptIDwtIGxlYWZsZXQoKSAlPiUNCiAgICAgc2V0Vmlldyhsbmc9LTc1LjU5NzgsIGxhdD0zOS45NTIyLCB6b29tID0gMjApICU+JQ0KICAgICBhZGRUaWxlcygpICU+JSAgICAgICAgIyBBZGQgZGVmYXVsdCBPcGVuU3RyZWV0TWFwIG1hcCB0aWxlcw0KICAgICBhZGRNYXJrZXJzKGxuZz0tNzUuNTk3OCwgbGF0PTM5Ljk1MjIpDQptICAgICMgUHJpbnQgdGhlIG1hcA0KYGBgDQoNCiMjIyAyLiBDdXN0b21pemluZyBNYXJrZXIgSWNvbnMNCg0KV2UgY2FuIG1hbmlwdWxhdGUgdGhlIGF0dHJpYnV0ZXMgb2YgdGhlIG1hcCB3aWRnZXQgdXNpbmcgYSBzZXJpZXMgb2YgbWV0aG9kcy4gDQoNCiogYHNldFZpZXcoKWAgc2V0cyB0aGUgY2VudGVyIG9mIHRoZSBtYXAgdmlldyBhbmQgdGhlIHpvb20gbGV2ZWw7DQoqIGBmaXRCb3VuZHMoKWAgZml0cyB0aGUgdmlldyBpbnRvIHRoZSByZWN0YW5nbGUgW2xuZzEsIGxhdDFdIOKAkyBbbG5nMiwgbGF0Ml07DQoqIGBjbGVhckJvdW5kcygpYCBjbGVhcnMgdGhlIGJvdW5kLCBzbyB0aGF0IHRoZSB2aWV3IHdpbGwgYmUgYXV0b21hdGljYWxseSBkZXRlcm1pbmVkIGJ5IHRoZSByYW5nZSBvZiBsYXRpdHVkZS9sb25naXR1ZGUgZGF0YSBpbiB0aGUgbWFwIGxheWVycyBpZiBwcm92aWRlZDsNCg0KV2UgY2FuIGFsc28gZGVmaW5lIG91ciBvd24gbWFya2VycyBhbmQgYWRkZWQgdG8gdGhlIG1hcCBvYmplY3QuIEZvciBleGFtcGxlLCB3ZSB1c2UgV0NVJ3MgbG9nbyBhcyBhIGN1c3RvbSBtYXJrZXIgYW5kIGFkZCBpdCB0byB0aGUgcHJldmlvdXMgbWFwLg0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTZ9DQojIGRlZmluZSBhIG1hcmtlciB1c2luZyBXQ1UncyBsb2dvLg0Kd2N1aWNvbiA8LSBtYWtlSWNvbigNCiAgaWNvblVybCA9ICJodHRwczovL2dpdGh1Yi5jb20vcGVuZ2RzY2kvc3RhNTUzL2Jsb2IvbWFpbi9pbWFnZS9nb2xkZW5SYW1Mb2dvLnBuZz9yYXc9dHJ1ZSIsDQogIGljb25XaWR0aCA9IDYwLCBpY29uSGVpZ2h0ID0gNjANCiAgKQ0KIyBkZWZpbmUgYSBsZWFmbGV0IG1hcCANCm0gPC0gbGVhZmxldCgpICU+JQ0KICAgICBzZXRWaWV3KGxuZz0tNzUuNTk3OCwgbGF0PTM5Ljk1MjIsIHpvb20gPSAyMCkgJT4lDQogICAgIGFkZFRpbGVzKCkgJT4lICAgICAgICAjIEFkZCBkZWZhdWx0IE9wZW5TdHJlZXRNYXAgbWFwIHRpbGVzDQogICAgIGFkZE1hcmtlcnMobG5nPS03NS41OTc4LCBsYXQ9MzkuOTUyMiwgIGljb24gPSB3Y3VpY29uKQ0KbSAgICAjIFByaW50IHRoZSBtYXANCmBgYA0KDQojIyMgMy4gUG9wdXBzIGFuZCBMYWJlbHMNCg0KUG9wdXBzIGFyZSBzbWFsbCBib3hlcyBjb250YWluaW5nIGFyYml0cmFyeSBIVE1MLCB0aGF0IHBvaW50IHRvIGEgc3BlY2lmaWMgcG9pbnQgb24gdGhlIG1hcC4gV2UgY2FuIHVzZSB0aGUgYGFkZFBvcHVwcygpYCBmdW5jdGlvbiB0byBhZGQgYSBzdGFuZGFsb25lIHBvcHVwIHRvIHRoZSBtYXAuIFdoZW4geW91IGNsaWNrIHRoZSBtYXJrZXIgaW4gdGhlIGZvbGxvd2luZyBtYXAsIHlvdSB3aWxsIHNlZSBhIHBvcHVwIHdpdGggdGhlIG5hbWUgb2YgdGhlIFdDVSBjYW1wdXMuDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9Nn0NCmRmIDwtIHJlYWQuY3N2KHRleHRDb25uZWN0aW9uKA0KIk5hbWUsIExhdCwgTG9uZw0KV0NVIFBoaWxhZGVscGhpYSBDYW1wdXMsMzkuOTUxOCwtNzUuMTUyNQ0KV0NVIFNvdXRoIENhbXB1cywzOS45MzczLC03NS42MDExDQpXQ1UgTWFpbiBDYW1wdXMsIDM5Ljk1MjQsLTc1LjU5ODIiDQopKQ0KDQpsZWFmbGV0KGRmKSAlPiUgDQogIGFkZFRpbGVzKCkgJT4lDQogIHNldFZpZXcobG5nPS03NS4zNzY4LCBsYXQ9MzkuOTQ0OCwgem9vbSA9IDEwKSAlPiUNCiAgYWRkTWFya2Vycyh+TG9uZywgfkxhdCwgcG9wdXAgPSB+cGFzdGUoIk5hbWU6ICIsTmFtZSkpDQpgYGANCg0KV2UgY2FuIGFsc28gY2hhbmdlIHRoZSBwb3B1cHMgaW4gdGhlIGFib3ZlIG1hcCB0byBsYWJlbHMuIFRoZSBtb2RpZmllZCBjb2RlIGlzIHNob3duIGJlbG93DQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9Nn0NCmRmIDwtIHJlYWQuY3N2KHRleHRDb25uZWN0aW9uKA0KIk5hbWUsIExhdCwgTG9uZw0KV0NVICBQaGlsYWRlbHBoaWEgQ2FtcHVzLDM5Ljk1MTgsLTc1LjE1MjUNCldDVSAgU291dGggQ2FtcHVzLDM5LjkzNzMsLTc1LjYwMTENCldDVSAgTWFpbiBDYW1wdXMsIDM5Ljk1MjQsLTc1LjU5ODIiDQopKQ0KDQpsZWFmbGV0KGRmKSAlPiUgDQogIGFkZFRpbGVzKCkgJT4lDQogIHNldFZpZXcobG5nPS03NS4zNzY4LCBsYXQ9MzkuOTQ0OCwgem9vbSA9IDEwKSAlPiUNCiAgYWRkTWFya2Vycyh+TG9uZywgfkxhdCwgbGFiZWwgPSB+cGFzdGUoIk5hbWU6IiwgTmFtZSkpDQpgYGANCg0KDQojIyMgNC4gQW5ub3RhdGlvbnMNCg0KV2UgaGF2ZSBpbnRyb2R1Y2VkIHRoZSB3YXlzIG9mIGFkZGluZyBob3ZlciBtZXNzYWdlcyB0aHJvdWdoIGBsYWJlbGAgYW5kIGBwb3BgIG9wdGlvbnMgdG8gcG9pbnRzIG9uIHRoZSBtYXAgbG9uZ2l0dWRlIGFuZCBsYXRpdHVkZS4gV2UgYWxzbyB1c2VkIHJlZmVyZW5jZSBsb2NhdGlvbiB1c2luZyBsb25naXR1ZGUgYW5kIGxhdGl0dWRlIHRvIGluc2VydCBpbWFnZXMgdG8gYSBtYXAuIA0KDQpUaGlzIHNlY3Rpb24gaW50cm9kdWNlZCBhIG1ldGhvZCB0byBpbnNlcnQgdGV4dCBhbmQgaW1hZ2UgYW5ub3RhdGlvbiB0byBhIG1hcC4gVGhlIGlkZWEgaXMgdG8gd3JhcCB0aGUgYW5ub3RhdGlvbiBpbiBhbiBIVE1MIHRhZyBhbmQgdGhlbiBwYXNzIHRoZSBpbmZvcm1hdGlvbiB0byBtYXAgdGhyb3VnaCBgYWRkQ29udHJvbCgpYCBmdW5jdGlvbi4gVGhpcyBtZXRob2Qgd2lsbCBhbHNvIGJlIHVzZWQgd2hlbiB3ZSBjcmVhdGUgc2hpbnkgYXBwcy4gIFRoZSByZWxhdGl2ZSBsb2NhdGlvbiBvbiB0aGUgbWFwIGlzIGluZGVwZW5kZW50IG9uIGxvbmdpdHVkZSBhbmQgbGF0aXR1ZGUuIFdlIGNhbiB1c2UgbG9jYXRpb24gdmFsdWVzIHN1Y2ggYXMgYHRvcGxlZnQsIHRvcHJpZ2h0LCBib3R0b21sZWZ0LCBib3R0b21yaWdodGAuDQoNCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD02fQ0KZGYgPC0gcmVhZC5jc3YodGV4dENvbm5lY3Rpb24oDQoiTmFtZSwgTGF0LCBMb25nDQpXQ1UgIFBoaWxhZGVscGhpYSBDYW1wdXMsMzkuOTUxOCwtNzUuMTUyNQ0KV0NVICBTb3V0aCBDYW1wdXMsMzkuOTM3MywtNzUuNjAxMQ0KV0NVICBNYWluIENhbXB1cywgMzkuOTUyNCwtNzUuNTk4MiINCikpDQojIyBIVE1MIHdyYXBwZWQgYW5ub3RhdGlvbg0KIEFubm90YXRlV3JhcHBlciA8LSB0YWdzJGRpdigNCiAgIEhUTUwoJzxjZW50ZXI+PGEgaHJlZj0iaHR0cHM6Ly93d3cud2N1cGEuZWR1LyI+IDxpbWcgYm9yZGVyPSIwIiBhbHQ9IkltYWdlVGl0bGUiIHNyYz0iaHR0cHM6Ly9naXRodWIuY29tL3Blbmdkc2NpL3N0YTU1My9ibG9iL21haW4vaW1hZ2UvZ29sZGVuUmFtTG9nby5wbmc/cmF3PXRydWUiIHdpZHRoPSIyNSIgaGVpZ2h0PSIyNSI+IDwvYT48Y2VudGVyPg0KICAgIDxmb250IGNvbG9yID0gInB1cnBsZSI+V0NVIFNUQSA1NTMgRXhhbXBsZTwvZm9udD4nKQ0KICkgIA0KIyMjDQpsZWFmbGV0KGRmKSAlPiUgDQogIGFkZFRpbGVzKCkgJT4lDQogICNhZGRQcm92aWRlclRpbGVzKCJOQVNBR0lCUy5WaWlyc0VhcnRoQXROaWdodDIwMTIiKSAlPiUNCiAgc2V0Vmlldyhsbmc9LTc1LjM3NjgsIGxhdD0zOS45NDQ4LCB6b29tID0gMTApICU+JQ0KICBhZGRNYXJrZXJzKH5Mb25nLCB+TGF0LCBwb3B1cCA9IH5wYXN0ZSgiTmFtZToiLCBOYW1lLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjxicj4gTG9uZ2l0dWRlOiIsIExvbmcsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiPGJyPkxhdGl0dWRlOiIsIExhdCksIA0KICAgICAgICAgICAgIGxhYmVsID0gfnBhc3RlKCJOYW1lOiIsIE5hbWUpKSU+JQ0KICBhZGRDb250cm9sKEFubm90YXRlV3JhcHBlciwgcG9zaXRpb24gPSAiYm90dG9tbGVmdCIpDQpgYGANCg0KDQoNCg0KDQojIyMgNS4gQW4gRXhhbXBsZSBVc2luZyBSZWFsLVdvcmxkIERhdGEgDQoNCkluIHRoZSBmb2xsb3dpbmcgZXhhbXBsZSwgd2UgdXNlIGEgZmV3IGxlYWZsZXQgZnVuY3Rpb25zIHRvIGFkZCBzb21lIGZlYXR1cmVzIHN1Y2ggYXMgZHJhd2luZyBoaWdobGlnaHQgYm94ZXMsIGxhYmVscywgZXRjLiB0byB0aGUgbWFwLg0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTZ9DQojIERlZmluZSBib3VuZGluZyBib3ggdXNpbmcgdGhlIHJhbmdlIG9mIGxvbmdpdHVkZS9sYXRpdHVkZSBjb29yZGluYXRlcw0KIyBmcm9tIHRoZSBnaXZlbiBkYXRhIHNldA0KaG91c2luZy5wcmljZSA8LSByZWFkLmNzdigiUmVhbGVzdGF0ZS5jc3YiKQ0KIyBtYWtpbmcgc3RhdGljIGxlYWZsZXQgbWFwDQpsZWFmbGV0KGhvdXNpbmcucHJpY2UpICU+JQ0KICBhZGRUaWxlcygpICU+JSANCiAgc2V0Vmlldyhsbmc9bWVhbihob3VzaW5nLnByaWNlJExvbmdpdHVkZSksIGxhdD1tZWFuKGhvdXNpbmcucHJpY2UkTGF0aXR1ZGUpLCB6b29tID0gMTQpICU+JQ0KICAgYWRkUmVjdGFuZ2xlcygNCiAgICBsbmcxID0gbWluKGhvdXNpbmcucHJpY2UkTG9uZ2l0dWRlKSwgbGF0MSA9IG1pbihob3VzaW5nLnByaWNlJExhdGl0dWRlKSwNCiAgICBsbmcyID0gbWF4KGhvdXNpbmcucHJpY2UkTG9uZ2l0dWRlKSwgbGF0MiA9IG1heChob3VzaW5nLnByaWNlJExhdGl0dWRlKSwNCiAgICAjZmlsbE9wYWNpdHkgPSAwLjIsDQogICAgZmlsbENvbG9yID0gInRyYW5zcGFyZW50IiANCiAgICApICU+JQ0KICBmaXRCb3VuZHMoDQogICAgbG5nMSA9IG1pbihob3VzaW5nLnByaWNlJExvbmdpdHVkZSksIGxhdDEgPSBtaW4oaG91c2luZy5wcmljZSRMYXRpdHVkZSksDQogICAgbG5nMiA9IG1heChob3VzaW5nLnByaWNlJExvbmdpdHVkZSksIGxhdDIgPSBtYXgoaG91c2luZy5wcmljZSRMYXRpdHVkZSkgKSAlPiUNCiAgYWRkTWFya2Vycyh+TG9uZ2l0dWRlLCB+TGF0aXR1ZGUsIGxhYmVsID0gflByaWNlVW5pdEFyZWEpDQpgYGANCg0KSW4gdGhlIG5leHQgbWFwIGJhc2VkIG9uIHRoZSBkYXRhLCB3ZSBhZGQgbW9yZSBpbmZvcm1hdGlvbiB0byB0aGF0IG1hcCB0byBkaXNwbGF5IGhpZ2hlciBkaW1lbnNpb25hbCBpbmZvcm1hdGlvbi4NCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD02fQ0KaG91c2luZy5wcmljZSA8LSBuYS5vbWl0KHJlYWQuY3N2KCJSZWFsZXN0YXRlLmNzdiIpKQ0KIyMgY29sb3IgY29kaW5nIGEgY29udGludW91cyB2YXJpYWJsZTogDQpjb2xBZ2UgPC0gY3V0KGhvdXNpbmcucHJpY2UkSG91c2VBZ2UsIGJyZWFrcz1jKDAsIDUsIDE1LCBtYXgoaG91c2luZy5wcmljZSRIb3VzZUFnZSkrMSksIHJpZ2h0ID0gRkFMU0UpDQpjb2xBZ2VOdW0gPC0gYXMubnVtZXJpYyhjb2xBZ2UpDQojIw0KYWdlQ29sb3IgPC0gcmVwKCJuYXZ5IiwgbGVuZ3RoKGNvbEFnZSkpDQphZ2VDb2xvclt3aGljaChjb2xBZ2VOdW09PTIpXSA8LSAib3JhbmdlIg0KYWdlQ29sb3Jbd2hpY2goY29sQWdlTnVtPT0zKV0gPC0gImRhcmtyZWQiDQojIyBkZWZpbmUgbGFiZWwgd2l0aCBob3ZlciBtZXNzYWdlcw0KDQpsYWJlbC5tc2cgPC0gcGFzdGUoIlVuaXQgUHJpY2U6IiwgaG91c2luZy5wcmljZSRQcmljZVVuaXRBcmVhLCAgICANCiAgICAgICAgICAgICAgICAgICAiPGJyPkRpc3QgdG8gTVJUOiIsaG91c2luZy5wcmljZSREaXN0YW5jZTJNUlQpDQoNCiNsYWJlbHMgPSBjYXQobGFiZWwubXNnKQ0KIyBtYWtpbmcgbGVhZmxldCBtYXANCmxlYWZsZXQoaG91c2luZy5wcmljZSkgJT4lDQogIGFkZFRpbGVzKCkgJT4lIA0KICBzZXRWaWV3KGxuZz1tZWFuKGhvdXNpbmcucHJpY2UkTG9uZ2l0dWRlKSwgbGF0PW1lYW4oaG91c2luZy5wcmljZSRMYXRpdHVkZSksIHpvb20gPSAxMykgJT4lDQogICNPcGVuU3RyZWV0TWFwLCBTdGFtZW4sIEVzcmkgYW5kIE9wZW5XZWF0aGVyTWFwLg0KICAjYWRkUHJvdmlkZXJUaWxlcygiRXNyaS5Xb3JsZEdyYXlDYW52YXMiKSAlPiUNCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkRXNyaS5Xb3JsZEdyYXlDYW52YXMpICU+JQ0KICBhZGRDaXJjbGVNYXJrZXJzKA0KICAgICAgICAgICAgfkxvbmdpdHVkZSwgDQogICAgICAgICAgICB+TGF0aXR1ZGUsDQogICAgICAgICAgICBjb2xvciA9IGFnZUNvbG9yLA0KICAgICAgICAgICAgcmFkaXVzID0gfiBzcXJ0KGhvdXNpbmcucHJpY2UkRGlzdGFuY2UyTVJULzEwKSowLjcsDQogICAgICAgICAgICBzdHJva2UgPSBGQUxTRSwgDQogICAgICAgICAgICBmaWxsT3BhY2l0eSA9IDAuNCwNCiAgICAgICAgICAgIHBvcHVwPSB+bGFiZWwubXNnKSAgJT4lDQogIGFkZExlZ2VuZChwb3NpdGlvbiA9ICJib3R0b21yaWdodCIsIA0KICAgICAgICAgICAgY29sb3JzID0gYygibmF2eSIsICJvcmFuZ2UiLCJkYXJrcmVkIiksDQogICAgICAgICAgICBsYWJlbHM9IGMoIlswLDUpIiwgIls1LDE1KSIsICJbMTUsNDQuOCkiKSwNCiAgICAgICAgICAgIHRpdGxlPSAiSG91c2UgQWdlIiwNCiAgICAgICAgICAgIG9wYWNpdHkgPSAwLjQpICU+JQ0KICBhZGRMZWdlbmRTaXplKHBvc2l0aW9uID0gJ3RvcHJpZ2h0JywgDQogICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBzcXJ0KGhvdXNpbmcucHJpY2UkRGlzdGFuY2UyTVJULzEwKSowLjUsDQogICAgICAgICAgICAgICAgICAgY29sb3IgPSAnZ3JheScsDQogICAgICAgICAgICAgICBmaWxsQ29sb3IgPSAnZ3JheScsDQogICAgICAgICAgICAgICAgIG9wYWNpdHkgPSAuNSwNCiAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICdEaXN0YW5jZSB0byBNUlQnLA0KICAgICAgICAgICAgICAgICAgIHNoYXBlID0gJ2NpcmNsZScsDQogICAgICAgICAgICAgb3JpZW50YXRpb24gPSAnaG9yaXpvbnRhbCcsDQogICAgICAgICAgICAgICAgICBicmVha3MgPSA1KQ0KYGBgDQoNCg0KPEJSPjxCUj4NCg0KPGZvbnQgc2l6ZSA9IDQsIGNvbG9yID0gInJlZCI+PGI+IENob3JhcGxldGggTWFwcyA8L2I+PC9mb250Pg0KPEJSPjxCUj4NCg0KIyMjIENob3JvcGxldGggTWFwcyBXaXRoIExlYWZsZXQNCg0KQXMgYW4gZXhhbXBsZSwgd2UgdXNlIHNoYXBlIGZpbGUgW3NoYXBlIGZpbGUgb2YgVVMgc3RhdGVzIChodHRwczovL3Blbmdkc2NpLmdpdGh1Yi5pby9TVEE1NTNWSVovdzA3L3VzLXN0YXRlcy5nZW9qc29uKSBdKGh0dHBzOi8vcGVuZ2RzY2kuZ2l0aHViLmlvL1NUQTU1M1ZJWi93MDcvdXMtc3RhdGVzLmdlb2pzb24pIHRvIHJlcHJlc2VudCBhZ2dyZWdhdGVkIGluZm9ybWF0aW9uIGF0IHRoZSBzdGF0ZSBsZXZlbC4NCg0KDQpgYGB7cn0NCiMgTWFwIGRhdGEgcHJlcGFyYXRpb24NCmVsZWN0cmljaXR5Y29zdCA8LWFzLnRpYmJsZShyZWFkLmNzdigiaHR0cHM6Ly9naXRodWIuY29tL3Blbmdkc2NpL3N0YTU1My9yYXcvbWFpbi9kYXRhL3N0YXRlX2VsZWN0cmljaXR5X2RhdGFfMjAxOC5jc3YiKVstOSxdKSAgIyBleGNsdWRlIERDDQplbGVjdHJpY2l0eWNvc3QgPC0gZWxlY3RyaWNpdHljb3N0ICU+JSANCiAgcmVuYW1lKG5hbWUgPSBOQU1FKQ0KI2VsZWN0cmljaXR5Y29zdCRTdGF0ZSA8LSBzdGF0ZS5hYmIgICMgYWRkIHN0YXRlIGFiYnJldnMgdG8gc3BlY2lmeSBsb2NhdGlvbnMgaW4gcGxvdF9seSgpDQojIE1ha2Ugc3RhdGUgYm9yZGVycyByZWQNCmJvcmRlcnMgPC0gbGlzdChjb2xvciA9IHRvUkdCKCJyZWQiKSkNCiMjIFN0YXRlIHNoYXBlZmlsZQ0KVVNTdGF0ZVNocGVVUkwgPC0iaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL1B1YmxpY2FNdW5kaS9NYXBwaW5nQVBJL21hc3Rlci9kYXRhL2dlb2pzb24vdXMtc3RhdGVzLmpzb24iDQojVVNTdGF0ZVNocGVVUkwgPC0gImh0dHBzOi8vcGVuZ2RzY2kuZ2l0aHViLmlvL1NUQTU1M1ZJWi93MDcvdXMtc3RhdGVzLmdlb2pzb24iDQpzdGF0ZVNoYXBlIDwtIGdlb2pzb25fcmVhZChVU1N0YXRlU2hwZVVSTCwgd2hhdCA9ICJzcCIpDQojIyBUaGUgc3Agb2JqZWN0IGNyZWF0ZWQgYWJvdmUgKOKAnHN0YXRlc+KAnSkgc3RvcmVzIGl0cyBkYXRhIGluIHNsb3RzLCANCiMjIHdoaWNoIGNhbiBiZSBhY2Nlc3NlZCB3aXRoIHRoZSDigJxA4oCdIG9wZXJhdG9yLg0Kc3RhdGVTaGFwZUBkYXRhID0gbGVmdF9qb2luKHN0YXRlU2hhcGVAZGF0YSwgZWxlY3RyaWNpdHljb3N0KQ0KIyBDQVVUSU9OOiBtdXN0IHVzZSBsZWZ0X2pvaW4gdG8ga2VlcCBhbGwgc3RhdGVzIHRoYXQgd2VyZSBpbmNsdWRlZCBpbiB0aGUgc3RhdGUgR0VPSlNPTiBmaWxlISEhDQojIENyZWF0ZSBob3ZlciB0ZXh0DQpwb3B1cHRleHQgPSBwYXN0ZSgnPHN0cm9uZz4nLHN0YXRlU2hhcGVAZGF0YSRuYW1lLCAiPC9zdHJvbmc+IiwnPGJyPicsICJFbGVjdHJpY2l0eSBDb3N0OiIsIHN0YXRlU2hhcGVAZGF0YSRjZW50c2tXaCkNCiMjDQpwYWwgPC0gY29sb3JOdW1lcmljKA0KICAgIHBhbGV0dGUgPSAiT3JhbmdlcyIsDQogICAgZG9tYWluID0gc3RhdGVTaGFwZUBkYXRhJGNlbnRza1doDQopDQojIyMNCnNpbXBsZVZlcnNpb24gPSBsZWFmbGV0KGRhdGEgPSBzdGF0ZVNoYXBlKSAlPiUNCiAgICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVyID0gIkNhcnRvREIuUG9zaXRyb24iKSAgJT4lIA0KICAgIHNldFZpZXcobGF0ID0gMzguMDExMDMwNiwgbG5nID0gLTExMC40MDgwMzQyLCB6b29tID0gMykgJT4lDQogICAgYWRkUG9seWdvbnMoZmlsbENvbG9yID0gfnBhbChjZW50c2tXaCksIA0KICAgICAgICBmaWxsT3BhY2l0eSA9IDAuOCwgDQogICAgICAgIGNvbG9yID0gImRhcmtyZWQiLCANCiAgICAgICAgd2VpZ2h0ID0gMSwNCiAgICAgICAgcG9wdXAgPSB+cG9wdXB0ZXh0KSAlPiUNCiAgICBhZGRMZWdlbmQocG9zaXRpb24gPSAiYm90dG9tbGVmdCIsDQogICAgICAgICAgICAgIHBhbCA9IHBhbCwgDQogICAgICAgICAgICAgIHZhbHVlcyA9IH5zdGF0ZVNoYXBlQGRhdGEkY2VudHNrV2gsIA0KICAgICAgICAgICAgICB0aXRsZSA9ICI8c3Ryb25nPlByaWNlPC9zdHJvbmc+PGJyPigyMDA4KSIpIA0Kc2ltcGxlVmVyc2lvbg0KYGBgDQoNClwNCg0KXA0KDQpXZSBjb3VsZCBhbHNvIGFkZCBhIHRpdGxlIHdpdGggcHJlZGVmaW5lZCBzdHlsZXMsIGFubm90YXRlZCB0ZXh0LCBhbmQgaW1hZ2VzIHRocm91Z2ggSFRNTCB0YWdzLiBIZXJlIGlzIGFuIGV4YW1wbGUuDQoNCmBgYHtyfQ0KIyMgbWFwIHRpdGxlDQp0aXRsZSA8LSB0YWdzJGRpdiggSFRNTCgnPGZvbnQgY29sb3IgPSAiZGFya3JlZCIgc2l6ZSA9ND48Yj5BdmVyYWdlIEVsZWN0cmljaXR5IFByaWNlIGJ5IFN0YXRlIChjZW50L0tXL2hyKTwvYj48L2ZvbnQ+JykNCikNCiMjIGFkZGluZyBhIGdpZiBpbWFnZSB0byB0aGUgbWFwDQogR0lGaW1nIDwtIHRhZ3MkZGl2KA0KICAgSFRNTCgnPGNlbnRlcj4gPGltZyBib3JkZXI9IjAiIGFsdD0iSW1hZ2VUaXRsZSIgc3JjPSJodHRwczovL3Blbmdkc2NpLmdpdGh1Yi5pby9TVEE1NTNWSVovdzA3L2ltZzA3L2JhbmFuYS5naWYiIHdpZHRoPSI3NSIgaGVpZ2h0PSI3NSI+PGNlbnRlcj4nKSkNCiMjIw0KRW5oYW5jZWRNYXAgPC0gc2ltcGxlVmVyc2lvbiAlPiUNCiAgICAgYWRkQ29udHJvbCh0aXRsZSwgcG9zaXRpb24gPSAidG9wcmlnaHQiKSAlPiUNCiAgICAgYWRkQ29udHJvbChHSUZpbWcsIHBvc2l0aW9uID0gImJvdHRvbXJpZ2h0IikNCkVuaGFuY2VkTWFwDQpgYGANCg0KDQoNCg0KIyMgUGxvdGx5IE1hcA0KDQpgcGxvdGx5YCBhaW1zIHRvIGJlIGEgZ2VuZXJhbC1wdXJwb3NlIHZpc3VhbGl6YXRpb24gbGlicmFyeSwgYW5kIHRodXMsIGRvZXNu4oCZdCBhaW0gdG8gYmUgdGhlIG1vc3QgZnVsbHktZmVhdHVyZWQgZ2Vvc3BhdGlhbCB2aXN1YWxpemF0aW9uIHRvb2xraXQuIA0KDQpgcGxvdGx5YCB1c2VzIHNldmVyYWwgZGlmZmVyZW50IHdheXMgdG8gY3JlYXRlIG1hcHMg4oCTIGVhY2ggd2l0aCBpdHMgc3RyZW5ndGhzIGFuZCB3ZWFrbmVzc2VzLiBJdCB1dGlsaXplcyBwbG90bHkuanMncyBidWlsdC1pbiBzdXBwb3J0IHRvIHJlbmRlciB0aGUgYmFzZW1hcCBsYXllci4gVGhlIHR5cGVzIG9mIGJhc2VtYXAgdXNlZCBpbiBwbG90bHkgYXJlIFtNYXBib3hdKGh0dHBzOi8vd3d3Lm1hcGJveC5jb20vKSAodGhpcmQgcGFydHkgc29mdHdhcmUgdGhhdCByZXF1aXJlcyBhbiBhY2Nlc3MgdG9rZW4pIGFuZCBEMy5qcyBwb3dlcmVkIGJhc2VtYXAuIEluIG90aGVyIHdvcmRzLCBwbG90bHkgZG9lcyBub3QgdXNlIFtPcGVuU3RyZWV0TWFwXShodHRwczovL3d3dy5vcGVuc3RyZWV0bWFwLm9yZy8jbWFwPTQvMzguMDEvLTk1Ljg0KSB0aGF0IGlzIHVzZWQgaW4gYGxlYWZsZXRgLCBgTWFwdmlld2VyYCwgYGdncGxvdDJgLGBTaGlueWAsIGFuZCBUYWJsZWF1Lg0KDQpXZSB3aWxsIG5vdCB1c2UgYE1hcGJveGAgaW4gdGhpcyBub3RlIGFuZCBmb2N1cyBvbiB0aGUgRDMuanMgYmFzZW1hcCB0aGF0IGRvZXMgbm90IGhhdmUgbWFueSBkZXRhaWxzLiBUaGUgcGxvdCBmdW5jdGlvbiBgcGxvdF9nZW8oKWAgd2lsbCBiZSB1c2VkIHRvIG1ha2UgcXVpY2sgbWFwcy4NCg0KDQo8QlI+PEJSPg0KPGNlbnRlcj48Zm9udCBzaXplID0gNCwgY29sb3IgPSAicmVkIj48Yj4gQ2hvcm9wbGV0aCBNYXBzIDwvYj48L2ZvbnQ+PC9jZW50ZXI+IA0KPEJSPjxCUj4NCg0KSW4gdGhlIGZvbGxvd2luZywgd2Ugd2lsbCBpbnRyb2R1Y2UgdGhlIHN0ZXBzIGZvciBjcmVhdGluZyBDaG9yb3BsZXRoIG1hcHMuIFNpbmNlIENob3JvcGxldGggbWFwcyBuZWVkIHRvIGZpbGwgYW5kIGNvbG9yIHNtYWxsIHJlZ2lvbnMgc3VjaCBhcyBkaXN0cmljdCwgY291bnR5LCBzdGF0ZXMsIGV0Yy4sIGl0IHJlcXVpcmVzIHRoZSBkYXRhIHNldCB0byBoYXZlIGEgc3BlY2lhbCBzdHJ1Y3R1cmUgdGhhdCBjb250YWlucyBzaGFwZSBpbmZvcm1hdGlvbi4gVHdvIHBsb3QgY29uc3RydWN0b3IgZnVuY3Rpb25zIGBwbG90X2x5KClgIGFuZCBgcGxvdF9nZW8oKWAgd2lsbCBiZSBpbnRyb2R1Y2VkIHRvIGNyZWF0ZSBjaG9yb3BsZXRoIG1hcHMuDQoNCiogKipgcGxvdF9seSgpYCoqIHJlcXVpcmVzIHNwZWNpZnlpbmcgYHR5cGUgPSBjaG9yb3BsZXRoYCB0byBtYWtlIGEgbWFwIChiYXNlbWFwIGZyb20gcGxvdGx5LmpzKS4gSW5mb3JtYXRpb24gaW4gdGhlIGRhdGEgc2V0IGlzIGludGVncmF0ZWQgdG8gdGhlIG1hcHMgYnkgdmFyaW91cyBhcmd1bWVudHMgb2YgYHBsb3RfbHkoKWAgYW5kIHJlbGV2YW50IGdyYXBoaWMgZnVuY3Rpb25zIHRoYXQgYXJlIGNvbXBhdGlibGUgd2l0aCBgcGxvdF9seSgpYC4NCg0KKiAqKmBwbG90X2dlbygpYCoqIHJlcXVpcmVzIGBhZGRUcmFjZSgpYCB0byBtYWtlIGNob3JvcGxldGggbWFwcyBhbmQgaW50ZWdyYXRlIGRhdGEgaW5mb3JtYXRpb24gdG8gdGhlIG1hcHMgd2l0aCByZWxldmFudCBhcmd1bWVudHMgaW4gYGFkZFRyYWNlKClgIGFuZCBncmFwaGljIGZ1bmN0aW9ucyBjb21wYXRpYmxlIHdpdGggYHBsb3RfZ2VvKClgLiAgDQoNCg0KDQoNCjxCUj4NCiMjIyAxLiBDaG9yb3BsZXRoIE1hcHMgd2l0aCBgcGxvdF9seSgpYA0KPEJSPg0KDQpJbiBnZW5lcmFsLCBtYWtpbmcgY2hvcm9wbGV0aCBtYXBzIHdpdGggYHBsb3RfbHkoKWAgcmVxdWlyZXMgdHdvIG1haW4gdHlwZXMgb2YgaW5wdXQ6DQoNCiogR2VvbWV0cnkgaW5mb3JtYXRpb24gcHJvdmlkZWQgYnkgDQogICsgb25lIG9mIHRoZSBidWlsdC1pbiBnZW9tZXRyaWVzIHdpdGhpbiBwbG90X2x5IHN1Y2ggYXMgVVMgc3RhdGVzIGFuZCB3b3JsZCBjb3VudHJpZXMuIFNlZSB0aGUgZm9sbG93aW5nIGV4YW1wbGUgMTogdmlzdWFsaXppbmcgMjAxOCBlbGVjdHJpY2l0eSBjb3N0IHBlciBzdGF0ZS4gDQogICsgYSBzdXBwbGllZCBHZW9KU09OIGZpbGUgd2hlcmUgZWFjaCBmZWF0dXJlIGhhcyBlaXRoZXIgYW4gaWQgZmllbGQgb3Igc29tZSBpZGVudGlmeWluZyB2YWx1ZSBpbiBwcm9wZXJ0aWVzLiBTZWUgdGhlIGZvbGxvd2luZyBleGFtcGxlIDI6IHZpc3VhbGl6aW5nIHRoZSB1bmVtcGxveW1lbnQgcmF0ZSBvZiBVUyBjb3VudGllcw0KDQoqIEEgbGlzdCBvZiB2YWx1ZXMgaW5kZXhlZCBieSBmZWF0dXJlIGlkZW50aWZpZXIuIFRoZXkgY29udHJvbCB0aGUgZmVhdHVyZXMgaW4gdGhlIG1hcCBpbmNsdWRpbmcgYm91bmRhcnksIGZpbGxlZCBjb2xvcnMsIGxlZ2VuZCwgaG92ZXIgdGV4dCwgZXRjLg0KDQoNCkZvciB0aGUgVVMgbWFwLCB0d28gdHlwZXMgb2YgcHJvamVjdGlvbnMgd2VyZSBjb21tb25seSB1c2U6IHJlZ3VsYXJ5IGFuZCBBbGJlcnMuDQoNCjxicj4NCjxjZW50ZXI+PGltZyBzcmM9Imh0dHBzOi8vcGVuZ2RzY2kuZ2l0aHViLmlvL1NUQTU1M1ZJWi93MDcvaW1nMDcvVVNNYXBQcm9qZWN0aW9uLnBuZyIgIGhlaWdodD0iMTUwIiB3aWR0aD0iNTAwIj48L2NlbnRlcj4NCjxicj4NCg0KDQoNCioqRXhhbXBsZSAxOioqIFVTIGVsZWN0cmljaXR5IGNvc3QgYnkgU3RhdGVzIGluIDIwMTguIFRoZSBhcmd1bWVudHMgYGxvY2F0aW9ucyA9YCBhbmQgYGxvY2F0aW9ubW9kZSA9YCB0ZWxsIGBwbG90X2x5YCB3aGF0IG1hcCBpbmZvcm1hdGlvbiBzaG91bGQgYmUgdXNlZCB0byBjcmVhdGUgdGhlIGJhc2UgbWFwLiBPdGhlciBhcmd1bWVudHMgYW5kIGZ1bmN0aW9ucyBhcmUgdXNlZCB0byBjb250cm9sIGRpZmZlcmVudCBmZWF0dXJlcyBvZiB0aGUgcmVzdWx0aW5nIG1hcC4gT25lIGNhdXRpb25hcnkgbm90ZSBpcyB0aGF0IGBwbG90X2x5KClgIG9ubHkgdXNlcyBzdGF0ZSBhYmJyZXZpYXRpb25zIGFzIHRoZSBzdGF0ZSBuYW1lLg0KDQpJbiB0aGUgY29kZSwgdGhlIHN0YXRlIGFiYnJldmlhdGlvbnMgYHN0YXRlLmFiYmAgaXMgYSBidWlsdC1pbiBkYXRhIHNldC4gU2V2ZXJhbCBvdGhlciBidWlsdC1pbiBkYXRhIHNldHMgYWJvdXQgZWFjaCBzdGF0ZSBhcmUgYWxzbyBhdmFpbGFibGUuIENoZWNrIHRoZSB3ZWJzaXRlIDxodHRwOi8vc3RhdHM0c3RlbS53ZWVibHkuY29tL3Itc3RhdGV4NzctZGF0YS5odG1sPiBmb3IgbW9yZSBpbmZvcm1hdGlvbiBvbiB0aGVzZSBkYXRhIHNldHMuDQoNCg0KDQoNCg0KYGBge3J9DQojIE1hcCBkYXRhIHByZXBhcmF0aW9uDQplbGVjdHJpY2l0eWNvc3QgPC1yZWFkLmNzdigiaHR0cHM6Ly9naXRodWIuY29tL3Blbmdkc2NpL3N0YTU1My9yYXcvbWFpbi9kYXRhL3N0YXRlX2VsZWN0cmljaXR5X2RhdGFfMjAxOC5jc3YiKVstOSxdICAjIGV4Y2x1ZGUgREMNCmVsZWN0cmljaXR5Y29zdCRTdGF0ZSA8LSBzdGF0ZS5hYmIgICMgYWRkIHN0YXRlIGFiYnJldnMgdG8gc3BlY2lmeSBsb2NhdGlvbnMgaW4gcGxvdF9seSgpDQojIENyZWF0ZSBob3ZlciB0ZXh0DQplbGVjdHJpY2l0eWNvc3QkaG92ZXIgPC0gd2l0aChlbGVjdHJpY2l0eWNvc3QsIHBhc3RlKFN0YXRlLCAnPGJyPicsICJFbGVjdHJpY2l0eSBDb3N0OiIsIGNlbnRza1doKSkNCiMgTWFrZSBzdGF0ZSBib3JkZXJzIHdoaXRlDQpib3JkZXJzIDwtIGxpc3QoY29sb3IgPSB0b1JHQigicmVkIikpDQojIFNldCB1cCBzb21lIG1hcHBpbmcgb3B0aW9ucw0KbWFwX29wdGlvbnMgPC0gbGlzdCgNCiAgc2NvcGUgPSAndXNhJywNCiAgcHJvamVjdGlvbiA9IGxpc3QodHlwZSA9ICdhbGJlcnMgdXNhJyksDQogIHNob3dsYWtlcyA9IFRSVUUsDQogIGxha2Vjb2xvciA9IHRvUkdCKCd3aGl0ZScpDQopDQpwbG90X2x5KCB6ID0gfmVsZWN0cmljaXR5Y29zdCRjZW50c2tXaCwgDQogICAgICAgIHRleHQgPSB+ZWxlY3RyaWNpdHljb3N0JGhvdmVyLCANCiAgICAgICAgbG9jYXRpb25zID0gfmVsZWN0cmljaXR5Y29zdCRTdGF0ZSwgDQogICAgICAgIHR5cGUgPSAnY2hvcm9wbGV0aCcsIA0KICAgICAgICBsb2NhdGlvbm1vZGUgPSAnVVNBLXN0YXRlcycsIA0KICAgICAgICBjb2xvcnMgPSAnUmRQdScsIA0KICAgICAgICBjb2xvciA9IGVsZWN0cmljaXR5Y29zdCRjZW50c2tXaCwgDQogICAgICAgIG1hcmtlciA9IGxpc3QobGluZSA9IGJvcmRlcnMpKSAlPiUNCiAgbGF5b3V0KHRpdGxlID0gJ1VTIFN0YXRlIEVsZWN0cmljaXR5IFVuaXQgQ29zdCAoY2VudHMva1doKScsIA0KICAgICAgICAgZ2VvID0gbWFwX29wdGlvbnMpDQoNCmBgYA0KDQoqKkV4YW1wbGUgMjoqKiBUaGUgdW5lbXBsb3ltZW50IHJhdGVzIG9mIFVTIGNvdW50aWVzLiBUaGlzIGV4YW1wbGUgcmVxdWlyZXMgYSBKU09OIGZpbGUgdG8gcHJvdmlkZSBuZWNlc3NhcnkgZ2VvbWV0cmljIGluZm9ybWF0aW9uIChzaGFwZSBwb2x5Z29uKSBhYm91dCBlYWNoIGNvdW50eSBpbiB0aGUgVVMuIFRoZSBhcmd1bWVudCBgbG9jYXRpb25zID0gYCBhY2NlcHRzIEZJUFMgKEZlZGVyYWwgSW5mb3JtYXRpb24gUHJvY2VzcyBTeXN0ZW0pIGZvciBVUyBjb3VudHkgbWFwcy4gVGhlIGdlb21ldHJpYyBpbmZvcm1hdGlvbiBvZiB0aGUgVVMgY291bnR5IHNoYXBlIGlzIHN1cHBsaWVkIGluIGEgSlNPTiBmaWxlIGFuZCB1c2VkIHRocm91Z2ggdGhlIGFyZ3VtZW50IGBnZW9qc29uID0gYC4gICANCg0KDQpgYGB7cn0NCiNsaWJyYXJ5KHBsb3RseSkNCiNsaWJyYXJ5KHJqc29uKQ0KI2xpYnJhcnkoIlJDb2xvckJyZXdlciIpICAjIGJyZXdlci5wYWwuaW5mbyBmb3IgbGlzdCBvZiBjb2xvciBzY2FsZXMNCg0KdXJsIDwtICdodHRwczovL2dpdGh1Yi5jb20vcGVuZ2RzY2kvc3RhNTUzL3Jhdy9tYWluL2RhdGEvZ2VvanNvbi1jb3VudGllcy1maXBzLmpzb24nICAjIGNvbnRhaW5zIGdlb2NvZGUgdG8gZGVmaW5lIGNvdW50eSBib3VuZGFyaWVzIGluIHRoZSBjaG9yb3BsZXRoIG1hcA0KY291bnRpZXMgPC0gcmpzb246OmZyb21KU09OKGZpbGU9dXJsKSAgDQpsb2FkKCJpbWcwNy8vdW5lbXAucmRhIikNCiNsb2FkKCIvVXNlcnMvY2hlbmdwZW5nL1dDVS9UZWFjaGluZy8yMDIyU3ByaW5nL1NUQTU1My9STWFwcy91bmVtcC5yZGEiKQ0KZGY9dW5lbXANCmcgPC0gbGlzdCgNCiAgICAgIHNjb3BlID0gJ3VzYScsDQogICAgICBwcm9qZWN0aW9uID0gbGlzdCh0eXBlID0gJ2FsYmVycyB1c2EnKSwNCiAgICAgIHNob3dsYWtlcyA9IFRSVUUsDQogICAgICBsYWtlY29sb3IgPSB0b1JHQignd2hpdGUnKQ0KICAgICkNCiMjIw0KZmlnIDwtIHBsb3RfbHkoKSAgJT4lIA0KICBhZGRfdHJhY2UoIHR5cGUgPSAiY2hvcm9wbGV0aCIsDQogICAgICAgICAgZ2VvanNvbiA9IGNvdW50aWVzLA0KICAgICAgICBsb2NhdGlvbnMgPSBkZiRmaXBzLA0KICAgICAgICAgICAgICAgIHogPSBkZiRyYXRlLA0KICAgICAgIGNvbG9yc2NhbGUgPSAiR25CdSIsICANCiAgICAgICAgICAgICB6bWluID0gMCwNCiAgICAgICAgICAgICB6bWF4ID0gMzAsDQogICAgICAgICAgICAgdGV4dCA9IGRmJG5hbWUsICMgaG92ZXIgbWVzZw0KICAgICAgICAgICBtYXJrZXIgPSBsaXN0KGxpbmU9bGlzdCh3aWR0aD0wLjIpKQ0KICAgICAgICAgICkgICAlPiUgDQogIGNvbG9yYmFyKHRpdGxlID0gIlVuZW1wbG95bWVudCBSYXRlICglKSIsDQogICAgICAgICAgIGNvbG9yc2NhbGU9J1ZpcmlkaXMnKSAgJT4lIA0KICBsYXlvdXQoICMjIyBUaXRsZSANCiAgICAgICAgICB0aXRsZSA9bGlzdCh0ZXh0ID0gIlVTIFVuZW1wbG95bWVudCBieSBDb3VudHkiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgZm9udCA9IGxpc3QoZmFtaWx5ID0gIlRpbWVzIE5ldyBSb21hbiIsICAjIEhUTUwgZm9udCBmYW1pbHkgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSAxOCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gInJlZCIpKSwgDQogICAgICAgICAgZ2VvID0gZykNCiMjIFRoZSBhY3R1YWwgSFRNTCBwYWdlIGRvZXMgc2hvZSBjb2xvcmJhciBjb3JyZWN0bHksIHdlIGhpZGUgdGhlIGxlZ2VuZC4NCmhpZGVfbGVnZW5kKGZpZykNCg0KYGBgDQoNCioqRXhhbXBsZSAzKiogVVMgc3RhdGVzIGZhY3RzLiBTaW1pbGFyIHRvIGV4YW1wbGUgMSwgYnV0IHdpdGggbW9yZSB2YXJpYWJsZXMuIFRoZSBkYXRhIHNldCBpcyBidWlsdC1pbiBpbiB0aGUgYmFzZSBSIHBhY2thZ2UuDQoNCmBgYHtyfQ0KIyBDcmVhdGUgZGF0YSBmcmFtZQ0Kc3RhdGVfcG9wIDwtIHJlYWQuY3N2KCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcGVuZ2RzY2kvc3RhNTUzL21haW4vZGF0YS9VU1N0YXRlc0ZhY3RzLmNzdiIpDQojIENyZWF0ZSBob3ZlciB0ZXh0DQpzdGF0ZV9wb3AkaG92ZXIgPC0gd2l0aChzdGF0ZV9wb3AsIA0KICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoU1ROYW1lLCAnPGJyPicsICJQb3B1bGF0aW9uOiIsIFBvcHVsYXRpb24sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnPGJyPicsICJJbmNvbWU6IiwgSW5jb21lLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzxicj4nLCAiTGlmZS5FeHA6IiwgTGlmZS5FeHAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnPGJyPicsICJNdXJkZXI6IiwgTXVyZGVyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzxicj4nLCAiSFMuR3JhZDoiLCBIUy5HcmFkKSkNCiMgTWFrZSBzdGF0ZSBib3JkZXJzIHdoaXRlDQpib3JkZXJzIDwtIGxpc3QoY29sb3IgPSB0b1JHQigicmVkIikpDQojIFNldCB1cCBzb21lIG1hcHBpbmcgb3B0aW9ucw0KbWFwX29wdGlvbnMgPC0gbGlzdCgNCiAgc2NvcGUgPSAndXNhJywNCiAgcHJvamVjdGlvbiA9IGxpc3QodHlwZSA9ICdyZWd1bGFyIHVzYScpLA0KICBzaG93bGFrZXMgPSBUUlVFLA0KICBsYWtlY29sb3IgPSB0b1JHQignd2hpdGUnKQ0KKQ0KcGxvdF9seSh6ID0gfnN0YXRlX3BvcCRQb3B1bGF0aW9uLCANCiAgICAgICAgdGV4dCA9IH5zdGF0ZV9wb3AkaG92ZXIsIA0KICAgICAgICBsb2NhdGlvbnMgPSB+c3RhdGVfcG9wJFN0YXRlLCANCiAgICAgICAgdHlwZSA9ICdjaG9yb3BsZXRoJywgDQogICAgICAgIGxvY2F0aW9ubW9kZSA9ICdVU0Etc3RhdGVzJywgDQogICAgICAgIGNvbG9yID0gc3RhdGVfcG9wJFBvcHVsYXRpb24sIA0KICAgICAgICBjb2xvcnMgPSAnWWxPclJkJywgDQogICAgICAgIG1hcmtlciA9IGxpc3QobGluZSA9IGJvcmRlcnMpKSAlPiUNCiAgbGF5b3V0KHRpdGxlID0gJ1VTIFBvcHVsYXRpb24gaW4gMTk3NScsIGdlbyA9IG1hcF9vcHRpb25zKQ0KYGBgDQoNCg0KPEJSPg0KIyMjIDIuIENob3JvcGxldGggTWFwcyB3aXRoIGBwbG90X2dlbygpYA0KPEJSPg0KTWFraW5nIGEgY2hvcm9wbGV0aCBtYXAgd2l0aCBgcGxvdF9nZW9gIHJlcXVpcmVzIGxlc3MgZWZmb3J0IHRvIHByZXBhcmUgdGhlIHNoYXBlIGRhdGEuIFRoZSBnZW8taW5mb3JtYXRpb24gd2FzIGNhbGxlZCB0aHJvdWdoIGBsb2NhdGlvbnMgPSBgIGFuZCBgbG9jYXRpb25tb2RlID0gYC4NCg0KPEJSPg0KYGBge3J9DQojIGxpYnJhcnkocGxvdGx5KQ0KIyByZWFkIGluIGN2IGRhdGENCmRmIDwtIHJlYWQuY3N2KCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcGVuZ2RzY2kvc3RhNTUzL21haW4vZGF0YS8yMDExX3VzX2FnX2V4cG9ydHMuY3N2IikNCiMjIERlZmluZSBob3ZlciB0ZXh0DQpkZiRob3ZlciA8LSB3aXRoKGRmLCBwYXN0ZShzdGF0ZSwgIlxuIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJCZWVmIiwgYmVlZiwgIlxuIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJEYWlyeSIsIGRhaXJ5LCAiXG4iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIkZydWl0cyIsIHRvdGFsLmZydWl0cywgIlxuIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJWZWdnaWVzIiwgdG90YWwudmVnZ2llcywgIlxuIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJXaGVhdCIsIHdoZWF0LCAiXG4iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNvcm4iLCBjb3JuKSkNCg0KIyBnaXZlIHN0YXRlIGJvdW5kYXJpZXMgYSB3aGl0ZSBib3JkZXINCmwgPC0gbGlzdChjb2xvciA9IHRvUkdCKCJ3aGl0ZSIpLCB3aWR0aCA9IDIpDQojIHNwZWNpZnkgc29tZSBtYXAgcHJvamVjdGlvbi9vcHRpb25zDQpnIDwtIGxpc3QoICAgICBzY29wZSA9ICd1c2EnLA0KICAgICAgICAgIHByb2plY3Rpb24gPSBsaXN0KHR5cGUgPSAnYWxiZXJzIHVzYScpLA0KICAgICAgICAgICBzaG93bGFrZXMgPSBUUlVFLA0KICAgICAgICAgICBsYWtlY29sb3IgPSB0b1JHQignd2hpdGUnKQ0KICAgICAgKQ0KIyMgcGxvdCBtYXANCm0gPC0gcGxvdF9nZW8oZGYsIGxvY2F0aW9ubW9kZSA9ICdVU0Etc3RhdGVzJykgJT4lDQogICAgIGFkZF90cmFjZSggICAgICAgIHogPSB+dG90YWwuZXhwb3J0cywgDQogICAgICAgICAgICAgICAgICAgIHRleHQgPSB+aG92ZXIsIA0KICAgICAgICAgICAgICAgbG9jYXRpb25zID0gfmNvZGUsDQogICAgICAgICAgICAgICAgICAgY29sb3IgPSB+dG90YWwuZXhwb3J0cywgDQogICAgICAgICAgICAgICAgICBjb2xvcnMgPSAnWWxPclJkJw0KICAgICAgICAgICAgICApICAlPiUgDQogICAgIGNvbG9yYmFyKHRpdGxlID0gIk1pbGxpb25zIFVTRCIpICAlPiUgDQogICAgIGxheW91dCggdGl0bGUgPSAnMjAxMSBVUyBBZ3JpY3VsdHVyZSBFeHBvcnRzIGJ5IFN0YXRlPGJyPihIb3ZlciBmb3IgYnJlYWtkb3duKScsDQogICAgICAgICAgICAgICBnZW8gPSBnDQogICAgICAgICAgICApDQptDQpgYGANCg0KDQpTaW5jZSB0aGUgY29sb3JiYXJzIGFyZSBub3QgZGlzcGxheWVkIGNvcnJlY3RseSBpbiB0aGUga2l0dGVkIEhUTUwgZG9jdW1lbnQsIHdlIHRha2UgYSBzY3JlZW5zaG90IGluIHRoZSBmb2xsb3dpbmcgdG8gZGlzcGxheSB0aGUgY29ycmVjdCBjb2xvcmJhci4NCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGg9IjgwJSJ9DQppbmNsdWRlX2dyYXBoaWNzKCJpbWcwNy93MDctbWFwMTQucG5nIikNCmBgYA0KDQoNCg0KDQo8QlI+PEJSPg0KPGNlbnRlcj48Zm9udCBzaXplID0gNCwgY29sb3IgPSAicmVkIj48Yj4gU2NhdHRlciBNYXBzIDwvYj48L2ZvbnQ+PC9jZW50ZXI+DQo8QlI+PEJSPg0KDQojIyMgMy5TY2F0dGVyIE1hcCB3aXRoIGBwbG90X2dlbygpYCBhbmQgYGFkZF9tYXJrZXJzKClgDQoNCkEgU2NhdHRlciBtYXAgaXMgcmVsYXRpdmVseSBlYXNpZXIgdG8gbWFrZSBzaW5jZSB3ZSBvbmx5IHBsb3QgdGhlIGJhc2UgbWFwIHVzaW5nIHRoZSBsb25naXR1ZGUgYW5kIGxhdGl0dWRlLiBObyBtYXAgc2hhcGUgaW5mb3JtYXRpb24gaXMgbmVlZGVkIGZvciBzY2F0dGVyIG1hcHMuDQoNCg0KKipFeGFtcGxlIDQqKiBVUyBBaXJwb3J0IFRyYWZmaWMuDQoNCmBgYHtyfQ0KI2xpYnJhcnkocGxvdGx5KQ0KZGYgPC0gcmVhZC5jc3YoJ2h0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9wZW5nZHNjaS9zdGE1NTMvbWFpbi9kYXRhLzIwMTFfZmVicnVhcnlfdXNfYWlycG9ydF90cmFmZmljLmNzdicpDQojIGdlbyBzdHlsaW5nDQpnIDwtIGxpc3QoICAgICAgc2NvcGUgPSAndXNhJywNCiAgICAgICAgICAgcHJvamVjdGlvbiA9IGxpc3QodHlwZSA9ICdhbGJlcnMgdXNhJyksDQogICAgICAgICAgICAgc2hvd2xhbmQgPSBUUlVFLA0KICAgICAgICAgICAgbGFuZGNvbG9yID0gdG9SR0IoImdyYXk5NSIpLA0KICAgICAgICAgc3VidW5pdGNvbG9yID0gdG9SR0IoImdyYXk4NSIpLA0KICAgICAgICAgY291bnRyeWNvbG9yID0gdG9SR0IoImdyYXk4NSIpLA0KICAgICAgICAgY291bnRyeXdpZHRoID0gMC41LA0KICAgICAgICAgc3VidW5pdHdpZHRoID0gMC41DQogICAgICAgKQ0KIyMjDQpmaWcgPC0gcGxvdF9nZW8oZGYsIGxhdCA9IH5sYXQsIGxvbiA9IH5sb25nKSAlPiUgDQogIGFkZF9tYXJrZXJzKCB0ZXh0ID0gfnBhc3RlKGFpcnBvcnQsIGNpdHksIHN0YXRlLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoIkFycml2YWxzOiIsIGNudCksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiPGJyPiIpLA0KICAgICAgICAgICAgICBjb2xvciA9IH5jbnQsIA0KICAgICAgICAgICAgICBzeW1ib2wgPSAiY2lyY2xlIiwgDQogICAgICAgICAgICAgIHNpemUgPSB+Y250LCANCiAgICAgICAgICAgICAgaG92ZXJpbmZvID0gInRleHQiKSAgICU+JSANCiAgY29sb3JiYXIodGl0bGUgPSAiSW5jb21pbmcgZmxpZ2h0czxicj4yMDExLjIiKSAgJT4lIA0KICBsYXlvdXQoIHRpdGxlID0gJ01vc3QgdHJhZmZpY2tlZCBVUyBhaXJwb3J0cycsIA0KICAgICAgICAgIGdlbyA9IGcgKQ0KDQpmaWcNCmBgYA0KDQo8QlI+PEJSPg0KPGNlbnRlcj48Zm9udCBzaXplID0gNCwgY29sb3IgPSAicmVkIj48Yj4gQ3VzdG9tIE1hcHMgPC9iPjwvZm9udD48L2NlbnRlcj4NCjxCUj48QlI+DQoNCiMjIyA0LiBDdXN0b20gTWFwIFdpdGggU3BlY2lhbCBMaWJyYXJpZXMNCjxicj4NClNvbWV0aW1lcywgd2UgbWF5IHdhbnQgdG8gdXNlIGN1c3RvbSBtYXBzIHRvIHJlcHJlc2VudCBzcGF0aWFsIGluZm9ybWF0aW9uLiBGb3IgZXhhbXBsZSwgaWYgd2Ugd2FudCB0byB2aXN1YWxpemUgdGhlIGFyZWEgb2YgVVMgc3RhdGVzLCB0aGUgcHJldmlvdXMgVVMgbWFwcyBhcmUgZmluZS4gSWYgd2Ugd2FudCB0byByZXByZXNlbnQgdGhlIHBvcHVsYXRpb24gc2l6ZSAoRXhhbXBsZSAzKSwgd2UgbWF5IHdhbnQgdG8gdXNlIGEgbWFwIHN1Y2ggdGhhdCB0aGUgZGlzcGxheWVkIGFyZWEgaXMgcHJvcG9ydGlvbmFsIHRvIHRoZSBwb3B1bGF0aW9uIHNpemUgYnV0IG5vdCB0aGUgZ2VvZ3JhcGhpY2FsIGFyZWEuIFRoZXNlIHR5cGVzIG9mIGN1c3RvbSBtYXBzIG5lZWQgc3BlY2lhbCB0b29scyB0byBjb25zdHJ1Y3QuIFNob3cgeW91IGFuIGV4YW0gd2l0aG91dCBwcm92aWRpbmcgY29kZSB0byBtYWtlIHRoZSBtYXAuDQoNCioqRXhhbXBsZSA0KiogVVMgcG9wdWxhdGlvbiBieSBzdGF0ZXMuDQoNCjxCUj4NCjxicj4NCjxjZW50ZXI+PGltZyBzcmM9Imh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9wZW5nZHNjaS9zdGE1NTMvbWFpbi9pbWFnZS9jdXN0b20tbWFwLnBuZyIgIGhlaWdodD0iMjYwIiB3aWR0aD0iNTAwIj48L2NlbnRlcj4NCjxCUj48QlI+DQoNCg0KDQoNCiMjIFRoZW1hdGljIE1hcHMNCg0KVGhlIGB0bWFwYCBwYWNrYWdlIGlzIGEgcmVsYXRpdmVseSBuZXcgd2F5IHRvIHBsb3QgdGhlbWF0aWMgbWFwcyBpbiBSLiBUaGVtYXRpYyBtYXBzIGFyZSBnZW9ncmFwaGljYWwgbWFwcyBpbiB3aGljaCBzcGF0aWFsIGRhdGEgZGlzdHJpYnV0aW9ucyBhcmUgdmlzdWFsaXplZC4gVGhpcyBwYWNrYWdlIG9mZmVycyBhIGZsZXhpYmxlIGFuZCBsYXllci1iYXNlZCBhcHByb2FjaCB0byBjcmVhdGluZyB0aGVtYXRpYyBtYXBzLCBzdWNoIGFzIGNob3JvcGxldGhzIGFuZCBidWJibGUgbWFwcy4gVGhlIHN5bnRheCBmb3IgY3JlYXRpbmcgcGxvdHMgaXMgc2ltaWxhciB0byB0aGF0IG9mIGdncGxvdDIuDQoNCmB0bWFwX21vZGUoKWAgd2lsbCBiZSB1c2VkIHRvIGRldGVybWluZSBpbnRlcmFjdGl2aXR5IG9mIHRoZSBtYXA6IGB0bWFwX21vZGUoInBsb3QiKWAgcHJvZHVjZSBzdGF0aWMgbWFwcyBhbmQgYHRtYXBfbW9kZSgidmlldyIpYCBwcm9kdWNlIGludGVyYWN0aXZlIG1hcHMuDQoNCiMjIyAxLiBDaG9yb3BsZXRoIE1hcHMNCg0KV2Ugd2lsbCB1c2UgYSBidWlsdC1pbiB3b3JsZCBzaGFwZWZpbGUsIGBXb3JsZGAsIHRoYXQgY29udGFpbnMgaW5mb3JtYXRpb24gYWJvdXQgcG9wdWxhdGlvbiwgZ2RwLCBsaWZlIGV4cGVjdGFuY3ksIGluY29tZSwgaGFwcGluZXNzIGluZGV4LCBldGMuDQoNCkJvdGggY2hvcm9wbGV0aHMgYW5kIHNjYXR0ZXIgbWFwcyB3aWxsIGJlIGlsbHVzdHJhdGVkIHVzaW5nIHRoZSBidWlsdC1pbiBkYXRhLg0KDQoqKkV4YW1wbGUgMTogQ2hvcm9wbGV0aHMqKjogVGhlIGRlZmF1bHQgd29ybGQgbWFwIHdpdGggdGhlIGRpc3RyaWJ1dGlvbiBvZiBtZWFuIGxpZmUgZXhwZWN0YW5jeSBhbW9uZyBhbGwgY291bnRyaWVzLiBUaGUgZGVmYXVsdCBgdG1hcF9tb2RlYCBpcyBzZXQgdG8gYmUgYHBsb3RgLiBUaGUgZGVmYXVsdCBgdG1hcGAgbWFwIGlzIHN0YXRpYy4NCg0KYGBge3J9DQpsaWJyYXJ5KHRtYXApDQpkYXRhKFdvcmxkKQ0KdG1fc2hhcGUoV29ybGQpICsNCiAgICB0bV9wb2x5Z29ucygibGlmZV9leHAiKQ0KYGBgDQoNCioqRXhhbXBsZSAyOiBJbnRlcmFjdGl2ZSBNYXAqKjogdXNlIHRoZSBhYm92ZSBzdGF0aWMgbWFwIGFzIHRoZSBiYXNlIG1hcCBhbmQgYWRkIGludGVyYWN0aXZlIGZlYXR1cmVzIHRvIHRoZSBtYXBzLiBUaGUgbW9kZSBjYW4gYmUgc2V0IHdpdGggdGhlIGZ1bmN0aW9uIGB0bWFwX21vZGUoKWAsIGFuZCB0b2dnbGluZyBiZXR3ZWVuIHRoZSBtb2RlcyBjYW4gYmUgZG9uZSB3aXRoIHRoZSDigJhzd2l0Y2jigJkgYHR0bSgpYCAod2hpY2ggc3RhbmRzIGZvciB0b2dnbGUgdGhlbWF0aWMgbWFwLg0KDQpgYGB7cn0NCmxpYnJhcnkodG1hcCkNCiMNCnRtYXBfbW9kZSgidmlldyIpICAjICJ2aWV3IiBnaXZlcyBpbnRlcmFjdGl2ZSBtYXA7ICJwbG90IiBnaXZlcyBzdGF0aWMgbWFwLiANCiMjDQojIyB0bWFwX3N0eWxlIHNldCB0byAiY2xhc3NpYyINCnRtYXBfc3R5bGUoImNsYXNzaWMiKQ0KIyMgb3RoZXIgYXZhaWxhYmxlIHN0eWxlcyBhcmU6ICJ3aGl0ZSIsICJncmF5IiwgIm5hdHVyYWwiLCANCiMjICJjb2JhbHQiLCAiY29sX2JsaW5kIiwgImFsYmF0cm9zcyIsICJiZWF2ZXIiLCAiYnciLCAid2F0ZXJjb2xvciINCnRtYXBfb3B0aW9ucyhiZy5jb2xvciA9ICJza3libHVlIiwgDQogICAgICAgICAgICAgbGVnZW5kLnRleHQuY29sb3IgPSAid2hpdGUiKQ0KIyMNCnRtX3NoYXBlKFdvcmxkKSArDQogICAgICB0bV9wb2x5Z29ucygibGlmZV9leHAiLCANCiAgICAgICAgICAgICAgICAgIGxlZ2VuZC50aXRsZSA9ICJMaWZlIEV4cGVjdGFuY3kiKSArDQogICAgICB0bV9sYXlvdXQoYmcuY29sb3IgPSAiZ3JheSIsIA0KICAgICAgICAgICAgICAgIGlubmVyLm1hcmdpbnMgPSBjKDAsIC4wMiwgLjAyLCAuMDIpKSANCmBgYA0KDQojIyMgTWl4ZWQgTWFwcyAoTXVsdGlsYXllcikNCg0KVGhyb3VnaCBhIG11bHRpbGF5ZXIgbWFwLCB3ZSBjYW4gbWFrZSBhIGNob3JvcGxldGggYW5kIHBsYWNlIGFub3RoZXIgb24gdG9wIG9mIGl0LiBJbiB0aGUgZm9sbG93aW5nIGV4YW1wbGUsIHdlIGFkZCBvbmUgYWRkaXRpb25hbCBsYXllciB1c2luZyB0aGUgYG1ldHJvYCBzaGFwZWZpbGUgYW5kIHBsb3QgdGhlIGNlbnRlciBvZiB0aGUgbWV0cm8gYXJlYSB0byBnZXQgYSBzY2F0dGVyIG1hcC4NCg0KKipFeGFtcGxlIDM6IE1peGVkIE1hcDogdHdvLWxheWVyIG1peGVzIG1hcHMqKg0KDQpgYGB7cn0NCmxpYnJhcnkodG1hcCkNCiMqDQpkYXRhKG1ldHJvKQ0KIyMNCnRtYXBfbW9kZSgidmlldyIpICAjICJ2aWV3IiBnaXZlcyBpbnRlcmFjdGl2ZSBtYXA7IA0KI3RtYXBfc3R5bGUoImNsYXNzaWMiKSAjIyB0bWFwX3N0eWxlIHNldCB0byAiY2xhc3NpYyINCiMjIG90aGVyIGF2YWlsYWJsZSBzdHlsZXMgYXJlOiAid2hpdGUiLCAiZ3JheSIsICJuYXR1cmFsIiwgDQojIyAiY29iYWx0IiwgImNvbF9ibGluZCIsICJhbGJhdHJvc3MiLCAiYmVhdmVyIiwgImJ3IiwgIndhdGVyY29sb3IiDQp0bWFwX29wdGlvbnMoYmcuY29sb3IgPSAic2t5Ymx1ZSIsIA0KICAgICAgICAgICAgIGxlZ2VuZC50ZXh0LmNvbG9yID0gIndoaXRlIikNCiMjDQp0bV9zaGFwZShXb3JsZCkgKw0KICAgICAgdG1fcG9seWdvbnMoImxpZmVfZXhwIiwgDQogICAgICAgICAgICAgICAgICBsZWdlbmQudGl0bGUgPSAiTGlmZSBFeHBlY3RhbmN5IikgKw0KICAgICAgdG1fbGF5b3V0KGJnLmNvbG9yID0gImdyYXkiLCANCiAgICAgICAgICAgICAgICBpbm5lci5tYXJnaW5zID0gYygwLCAuMDIsIC4wMiwgLjAyKSkgKyANCnRtX3NoYXBlKG1ldHJvKSArDQogICAgICB0bV9zeW1ib2xzKGNvbCA9ICJwdXJwbGUiLCANCiAgICAgICAgICAgICAgICAgc2l6ZSA9ICJwb3AyMDIwIiwgDQogICAgICAgICAgICAgICAgIHNjYWxlID0gLjUsDQogICAgICAgICAgICAgICAgIGFscGhhID0gMC41LA0KICAgICAgICAgICAgICAgICBwb3B1cC52YXJzPWMoInBvcDE5NTAiLCAicG9wMTk2MCIsICJwb3AxOTgwIiwicG9wMTk5MCIsICAgICAgICAgICAgICAgICAgICJwb3AyMDAwIiwicG9wMjAxMCIsInBvcDIwMjAiKSkgDQpgYGANCg0KDQoNCmBgYHtyfQ0KbGlicmFyeShzcERhdGEpDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeShtYXB2aWV3KQ0KZ2ogPSAiaHR0cHM6Ly9naXRodWIuY29tL2F6YXZlYS9nZW8tZGF0YS9yYXcvbWFzdGVyL05laWdoYm9yaG9vZHNfUGhpbGFkZWxwaGlhL05laWdoYm9yaG9vZHNfUGhpbGFkZWxwaGlhLmdlb2pzb24iDQojIw0KZ2pzZiA9IHN0X3JlYWQoZ2opDQpsaWJyYXJ5KHRtYXApDQojICAgIHRtX3NoYXBlKFdvcmxkKSArDQogICAgdG1fc2hhcGUoZ2pzZikgICsgDQogICAgICB0bV9wb2x5Z29ucyhsZWdlbmQuc2hvdyA9IEZBTFNFKSAgKw0KICAgICAgdG1fYnViYmxlcygic2hhcGVfYXJlYSIsICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICNjb2wgPSAic2hhcGVfYXJlYSIsIA0KICAgICAgICAgICAgICAgICAjYnJlYWtzPXNlcSgxMjc2Njc0LCAxMjkyNTQ1OTcsIGxlbmd0aCA9IDYpLA0KICAgICAgICAgICAgICAgICBwYWxldHRlPSItUmRZbEJ1IiwgDQogICAgICAgICAgICAgICAgIGNvbnRyYXN0PTEpDQpgYGANCg0KDQojIyMgU2NhdHRlciBNYXBzIFdpdGggZ2VvQ29vcmRpbmF0ZXMNCg0KV2UgdXNlIHRoZSByZWFsIGVzdGF0ZSBkYXRhIHNldCB0byBtYWtlIGEgc2NhdHRlciBtYXAgdXNpbmcgbGlicmFyeSBgdG1hcGAuIFdlIGZpcnN0IG5lZWQgdG8gZGVmaW5lIGFuIGBzZmAgb2JqZWN0IHVzaW5nIGBzdF9hc19zZigpYCB0aGF0IHNoYXBlZmlsZSB3aXRoIGFuIGluZGl2aWR1YWwgcG9pbnQgYmFzZWQgb24gdGhlIGxvbmdpdHVkZSBhbmQgbGF0aXR1ZGUgaW4gdGhlIGRhdGEgc2V0Lg0KDQoNCmBgYHtyfQ0KbGlicmFyeSh0bWFwKQ0KbGlicmFyeShzZikNCnJlYWxlc3RhdGUwIDwtIHJlYWQuY3N2KCJSZWFsZXN0YXRlLmNzdiIsIGhlYWRlciA9IFRSVUUpDQpyZWFsZXN0IDwtIHJlYWxlc3RhdGUwWywgLTFdDQojIyBjcmVhdGUgYSBzaGFwZWZpbGUgd2l0aCBQT0lOVCB0eXBlLg0KcmVhbGVzdCA8LXN0X2FzX3NmKHJlYWxlc3QsIGNvb3Jkcz1jKCJMb25naXR1ZGUiLCJMYXRpdHVkZSIpLCBjcnMgPSA0MzI2KQ0KIyMjDQp0bV9zaGFwZShyZWFsZXN0KSArIA0KICB0bV9kb3RzKGNvbCA9ICJwdXJwbGUiLCANCiAgICAgICAgICBzaXplID0gIkRpc3RhbmNlMk1SVCIsIA0KICAgICAgICAgIGFscGhhID0gMC41LA0KICAgICAgICAgIHBvcHVwLnZhcnM9YygiSG91c2VBZ2UiLCAiUHJpY2VVbml0QXJlYSIsICJOdW1Db252ZW5TdG9yZXMiKSwNCiAgICAgICAgICBzaGFwZXMgPSBjKDEsIDApKSANCmBgYA0KDQoNCiMjIFRhYmxlYXUgTWFwcw0KDQpXZSBjcmVhdGUgYSBjaG9yb3BsZXRoIG1hcCBhbmQgYSBzY2F0dGVyIG1hcCByZXNwZWN0aXZlbHkgaW4gdGhpcyBub3RlLiBCZWZvcmUgY3JlYXRpbmcgbWFwcywgd2UgZmlyc3QgbG9vayB1bmRlcnN0YW5kIHRoZSBzdHJ1Y3R1cmUgb2YgVGFibGVhdSdzIHNoZWV0Lg0KDQo8Y2VudGVyPg0KPGltZyBzcmMgPSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3Blbmdkc2NpL3N0YTU1My9tYWluL1RhYmxlYXUvVEJMV29ya1NwYWNlLnBuZyI+DQo8L2NlbnRlcj4NCg0KV2UgY2FuIHNlZSB0aGF0IGVhY2ggVGFibGVhdSBib29rIGhhcyBzZXZlcmFsIGNvbXBvbmVudHM6DQoNCiogKipBIC0gV29ya2Jvb2sgbmFtZSoqIC0gQSB3b3JrYm9vayBjb250YWlucyBzaGVldHMuIEEgc2hlZXQgY2FuIGJlIGEgd29ya3NoZWV0LCBhIGRhc2hib2FyZCwgb3IgYSBzdG9yeS4gDQoNCiogKipCIC0gQ2FyZHMgYW5kIHNoZWx2ZXMqKiAtIERyYWcgZmllbGRzIHRvIHRoZSBjYXJkcyBhbmQgc2hlbHZlcyBpbiB0aGUgd29yay1zcGFjZSB0byBhZGQgZGF0YSB0byB5b3VyIHZpZXcuDQoNCiogKipDIC0gVG9vbGJhcioqIC0gVXNlIHRoZSB0b29sYmFyIHRvIGFjY2VzcyBjb21tYW5kcyBhbmQgYW5hbHlzaXMgYW5kIG5hdmlnYXRpb24gdG9vbHMuDQoNCiogKipEIC0gVmlldyoqIC0gVGhpcyBpcyB0aGUgY2FudmFzIGluIHRoZSB3b3JrLXNwYWNlIHdoZXJlIHlvdSBjcmVhdGUgYSB2aXN1YWxpemF0aW9uIChhbHNvIHJlZmVycmVkIHRvIGFzIGEgInZpeiIpLg0KDQoqICoqRSAtIFN0YXJ0IHBhZ2UgaWNvbioqIC0gQ2xpY2sgdGhpcyBpY29uIHRvIGdvIHRvIHRoZSBTdGFydCBwYWdlLCB3aGVyZSB5b3UgY2FuIGNvbm5lY3QgdG8gZGF0YS4gRm9yIG1vcmUgaW5mb3JtYXRpb24sIHNlZSBTdGFydCBQYWdlLg0KDQoqICoqRiAtIFNpZGUgQmFyKiogLSBJbiBhIHdvcmtzaGVldCwgdGhlIHNpZGUgYmFyIGFyZWEgY29udGFpbnMgdHdvIHRhYnM6IHRoZSBEYXRhIHBhbmUgYW5kIHRoZSBBbmFseXRpY3MgcGFuZS4NCg0KKiAqKkcgLSBEYXRhIFNvdXJjZSoqIC0gQ2xpY2sgdGhpcyB0YWIgdG8gZ28gdG8gdGhlIERhdGEgU291cmNlIHBhZ2UgYW5kIHZpZXcgeW91ciBkYXRhLiANCg0KKiAqKkggLSBTdGF0dXMgYmFyKiogLSBEaXNwbGF5cyBpbmZvcm1hdGlvbiBhYm91dCB0aGUgY3VycmVudCB2aWV3Lg0KDQoqICoqSSAtIFNoZWV0IHRhYnMqKiAtIFRhYnMgcmVwcmVzZW50IGVhY2ggc2hlZXQgaW4geW91ciB3b3JrYm9vay4gVGhpcyBjYW4gaW5jbHVkZSB3b3Jrc2hlZXRzLCBkYXNoYm9hcmRzLCBhbmQgc3Rvcmllcy4gWW91IGNhbiByZW5hbWUgYW5kIGFkZCBtb3JlIG9mIHRoZXNlIHNoZWV0cywgZGFzaGJvYXJkcyBhbmQgc3RvcmllcyBpZiBuZWVkZWQuDQoNCiogKipTaG93IE1lKiogLSBDbGljayB0aGlzIHRvZ2dsZSB0byBzZWxlY3QgMjQgYnVpbHQtaW4gY2hhcnRzIGFuZCB0aGUgaW5mb3JtYXRpb24gbmVlZGVkIHRvIGNyZWF0ZSB0aGVzZSBjaGFydHMuIA0KDQoNCiMjIyBDaG9yb3BsZXRoIE1hcA0KDQpUaGUgZGF0YSBzZXQgd2UgdXNlIGZvciBhIGNob3JvcGxldGggbWFwIGNhbiBiZSBkb3dubG9hZGVkIGZyb20gPGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9wZW5nZHNjaS9zdGE1NTMvbWFpbi9kYXRhL1VTU3RhdGVzRmFjdHMuY3N2Pi4NCg0KWW91IG5lZWQgdG8gZG93bmxvYWQgc2F2ZSB0aGlzIGRhdGEgZmlsZSBpbiBhIGZvbGRlciBhbmQgdGhlbiBjb25uZWN0IGl0IHRvIFRhYmxlYXUgUHVibGljIChvciBUYWJsZWF1IE9ubGluZSkuDQoNClRoZSBmb2xsb3dpbmcgYXJlIHN0ZXBzIGZvciBtYWtpbmcgYSBjaG9yb3BsZXRoIG1hcDoNCg0KMS4gTG9hZCB0aGUgLmNzdiBmaWxlIHRvIFRhYmxlYXUgKFB1YmxpYyk7IA0KDQoyLiBDbGljayBgc2hlZXQxYCBpbiB0aGUgYm90dG9tIGxlZnQgdGFza2JhcjsNCg0KMy4gRHJhZyB2YXJpYWJsZSBgU3RhdGVgIChvbiB0aGUgbGVmdCBuYXZpZ2F0aW9uIHBhbmVsIHVuZGVyIHRoZSB0YWJsZSkgdG8gdGhlIG1haW4gZHJvcCBmaWVsZCAoVGFibGVhdSBjb25zaWRlcnMgYFN0YXRlYCBhcyBhIGdlby12YXJpYWJsZSk7IGF0IHRoZSBzYW1lIHRpbWUsIHRoZSB0d28gZ2VuZXJhdGVkIGBMb25naXR1ZGUoZ2VuZXJhdGVkKWAgYW5kIGBMYXRpdHVkZShnZW5lcmF0ZWQpYCBhcHBlYXIgaW4gdGhlIGNvbHVtbiBhbmQgcm93IGZpZWxkcyBhdXRvbWF0aWNhbGx5Lg0KDQo0LiBDbGljayB0aGUgYFNob3cgTWVgIChvbiB0aGUgcmlnaHQgc2lkZSBvZiBhIHRpbnkgY29sb3IgYmFyIGNoYXJ0KSBpbiB0aGUgdG9wIHJpZ2h0IG9mIHRoZSBzY3JlZW47DQoNCjUuIFlvdSB3aWxsIHNlZSBhIGxpc3Qgb2YgZ3JhcGhzLiBDbGljayB0aGUgbWlkZGxlIGB3b3JsZCBtYXBgIGluIHRoZSBzZWNvbmQgcm93LCB5b3Ugd2lsbCBzZWUgYW4gaW5pdGlhbCBjaG9yb3BsZXRoIG1hcC4gDQoNCjYuIENsaWNrIGBTaG93IE1lYCBhZ2FpbiB0byBjbG9zZSB0aGUgcG9wdXAuIFdlIGNhbiBjbGljayB0aGUgbGVnZW5kIG9uIHRoZSB0b3AtcmlnaHQgY29sb3IgdG8gY2hhbmdlIHRoZSBjb2xvciBvZiB0aGUgbWFwIChpZiB5b3UgbGlrZSkuDQoNCjcuIFRvIGFkZCBtb3JlIGluZm9ybWF0aW9uIHRvIHRoZSBob3ZlciB0ZXh0LCB5b3UgZHJhZyB0aGUgdmFyaWFibGVzIG9uIHRoZSBsaXN0IHRvIHRoZSBzbWFsbCBpY29uIGxhYmVsZWQgd2l0aCBgRGV0YWlsYC4NCg0KOC4gQ2xpY2sgYFNoZWV0IDFgIHRvIGNoYW5nZSBpdCB0byBhIG1lYW5pbmdmdWwgdGl0bGUuIA0KDQo5LiBGaW5hbGx5IHdlIGxhYmVsIHRoZSBzdGF0ZXMgYnkgdGhlaXIgYWJicmV2aWF0aW9ucy4gVG8gZG8gdGhpcywgZHJhZyBgU3RhdGVgIHRvIGBMYWJlbGAgaW4gdGhlIGBNYXJrc2AgdGFibGUgKG5leHQgdG8gYERldGFpbGApLiAgDQoNCjEwLiBZb3UgY2FuIGVkaXQgdGhlIGhvdmVyIHRleHQgYnkgY2xpY2tpbmcgYFRvb2x0aXBgLiAgDQoNCg0KVGhlIHJlc3VsdGluZyBtYXAgY2FuIGJlIHZpZXdlZCBvbiB0aGUgVGFibGVhdSBQdWJsaWMgU2VydmVyIGF0DQo8aHR0cHM6Ly9wdWJsaWMudGFibGVhdS5jb20vYXBwL3Byb2ZpbGUvY3Blbmcvdml6L1VTLVN0YXRlcy1GYWN0cy9TaGVldDE+DQoNCg0KPHRhYmxlIGJvcmRlciA9IDAgYm9yZGVyY29sb3I9ImRhcmtncmVlbiIgYmdjb2xvcj0nI2Y2ZjZmNicgIHdpZHRoPTEwMCUgIGFsaWduID0gY2VudGVyPiANCjx0cj4NCjx0ZD4NCjxkaXYgY2xhc3M9J3RhYmxlYXVQbGFjZWhvbGRlcicgaWQ9J3ZpejE3MDkwMDI5OTc2NjYnIHN0eWxlPSdwb3NpdGlvbjogcmVsYXRpdmUnPg0KPG5vc2NyaXB0PjxhIGhyZWY9JyMnPjxpbWcgYWx0PSdVUyBTdGF0ZXMgRmFjdHMgJyBzcmM9J2h0dHBzOiYjNDc7JiM0NztwdWJsaWMudGFibGVhdS5jb20mIzQ3O3N0YXRpYyYjNDc7aW1hZ2VzJiM0Nzs0RCYjNDc7NERORkozSEdSJiM0NzsxX3Jzcy5wbmcnIHN0eWxlPSdib3JkZXI6IG5vbmUnIC8+PC9hPg0KPC9ub3NjcmlwdD4NCjxvYmplY3QgY2xhc3M9J3RhYmxlYXVWaXonICBzdHlsZT0nZGlzcGxheTpub25lOyc+DQo8cGFyYW0gbmFtZT0naG9zdF91cmwnIHZhbHVlPSdodHRwcyUzQSUyRiUyRnB1YmxpYy50YWJsZWF1LmNvbSUyRicgLz4gDQo8cGFyYW0gbmFtZT0nZW1iZWRfY29kZV92ZXJzaW9uJyB2YWx1ZT0nMycgLz4gDQo8cGFyYW0gbmFtZT0ncGF0aCcgdmFsdWU9J3NoYXJlZCYjNDc7NERORkozSEdSJyAvPiANCjxwYXJhbSBuYW1lPSd0b29sYmFyJyB2YWx1ZT0neWVzJyAvPg0KPHBhcmFtIG5hbWU9J3N0YXRpY19pbWFnZScgdmFsdWU9J2h0dHBzOiYjNDc7JiM0NztwdWJsaWMudGFibGVhdS5jb20mIzQ3O3N0YXRpYyYjNDc7aW1hZ2VzJiM0Nzs0RCYjNDc7NERORkozSEdSJiM0NzsxLnBuZycgLz4gPHBhcmFtIG5hbWU9J2FuaW1hdGVfdHJhbnNpdGlvbicgdmFsdWU9J3llcycgLz4NCjxwYXJhbSBuYW1lPSdkaXNwbGF5X3N0YXRpY19pbWFnZScgdmFsdWU9J3llcycgLz4NCjxwYXJhbSBuYW1lPSdkaXNwbGF5X3NwaW5uZXInIHZhbHVlPSd5ZXMnIC8+DQo8cGFyYW0gbmFtZT0nZGlzcGxheV9vdmVybGF5JyB2YWx1ZT0neWVzJyAvPg0KPHBhcmFtIG5hbWU9J2Rpc3BsYXlfY291bnQnIHZhbHVlPSd5ZXMnIC8+DQo8cGFyYW0gbmFtZT0nbGFuZ3VhZ2UnIHZhbHVlPSdlbi1VUycgLz4NCjwvb2JqZWN0Pg0KPC9kaXY+ICAgICAgICAgICAgICAgDQoNCjxzY3JpcHQgdHlwZT0ndGV4dC9qYXZhc2NyaXB0Jz4gICAgICAgICAgICAgICAgICAgIA0KdmFyIGRpdkVsZW1lbnQgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgndml6MTcwOTAwMjk5NzY2NicpOyAgICAgICAgICAgICAgICAgICAgDQp2YXIgdml6RWxlbWVudCA9IGRpdkVsZW1lbnQuZ2V0RWxlbWVudHNCeVRhZ05hbWUoJ29iamVjdCcpWzBdOyAgICAgICAgICAgICAgICAgICAgdml6RWxlbWVudC5zdHlsZS53aWR0aD0nMTAwJSc7dml6RWxlbWVudC5zdHlsZS5oZWlnaHQ9KGRpdkVsZW1lbnQub2Zmc2V0V2lkdGgqMC43NSkrJ3B4JzsgICAgICAgICAgICAgICAgICAgIA0KdmFyIHNjcmlwdEVsZW1lbnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTsgICAgICAgICAgICAgICAgICAgIA0Kc2NyaXB0RWxlbWVudC5zcmMgPSAnaHR0cHM6Ly9wdWJsaWMudGFibGVhdS5jb20vamF2YXNjcmlwdHMvYXBpL3Zpel92MS5qcyc7ICAgICAgICAgICAgICAgICAgICB2aXpFbGVtZW50LnBhcmVudE5vZGUuaW5zZXJ0QmVmb3JlKHNjcmlwdEVsZW1lbnQsIHZpekVsZW1lbnQpOyAgICAgICAgICAgICAgICANCjwvc2NyaXB0Pg0KPC90ZD4NCjwvdHI+DQo8L3RhYmxlPg0KDQoNCg0KDQoNCg0KDQojIyMgU2NhdHRlciBNYXANCg0KV2UgdXNlIGhvdXNpbmcgcHJpY2UgZGF0YSB3aXRoIGxvbmdpdHVkZSBhbmQgbGF0aXR1ZGUgYXNzb2NpYXRlZCB3aXRoIGVhY2ggcHJvcGVydHkuIFRoZSBkYXRhIHNldCBpcyBhdCA8aHR0cHM6Ly9wcm9qZWN0ZGF0LnMzLmFtYXpvbmF3cy5jb20vUmVhbGVzdGF0ZS5jc3Y+DQoNCkFzIHdlIGRpZCBpbiB0aGUgcHJldmlvdXMgZXhhbXBsZSwgd2UgZG93bmxvYWQgdGhlIGRhdGEgc2V0IGFuZCBzYXZlIGl0IGluIGEgZm9sZGVyLg0KDQpUaGUgZm9sbG93aW5nIGFyZSBzdGVwcyB0byBjcmVhdGUgYSBzY2F0dGVyIG1hcC4NCg0KMS4gT3BlbiB0aGUgVGFibGVhdSBhbmQgY29ubmVjdCB0aGUgZGF0YSBzb3VyY2UgdG8gVGFibGVhdS4NCg0KMi4gQWZ0ZXIgdGhlIGRhdGEgaGFzIGJlZW4gbG9hZGVkIHRvIHRoZSBUYWJsZWF1LCBjbGljayBgU2hlZXQxYCwgeW91IHdpbGwgc2VlIHRoZSBsaXN0IG9mIHZhcmlhYmxlcyBvbiB0aGUgbGVmdCBwYW5lbC4NCg0KMy4gY2xpY2sgYExhdGl0dWRlYCAtPiBgR2VvZ3JhcGhpYyBSb2xlYCAtPiBgTGF0aXR1ZGVgOyBkbyB0aGUgc2FtZSB0aGluZyB0byBgTG9uZ2l0dWRlYC4gDQoNCjQuIERyYWcgYExhdGl0dWRlYCB0byB0aGUgYENvbHVtbnNgIGZpZWxkIGFuZCBgTG9uZ2l0dWRlYCB0byB0aGUgYFJvd3NgIGZpZWxkLiBZb3Ugd2lsbCBzZWUgYSBzaW5nbGUgcG9pbnQgaW4gYFNoZWV0IDFgLiBUaGUgdHdvIHZhcmlhYmxlcyB3ZXJlIGF1dG9tYXRpY2FsbHkgcmVuYW1lZCBhcyBgQVZHKExhdGl0dWRlKWAgYW5kIGBBVkcoTG9uZ2l0dWRlKWAuDQoNCjUuIENsaWNrIGBBVkcoTGF0aXR1ZGUpYCBhbmQgc2VsZWN0IGBkaW1lbnNpb25gLCB5b3Ugd2lsbCBzZWUgYSBsaW5lIHBsb3QgaW4gYFNoZWV0IDFgLiBEbyB0aGUgc2FtZSB0aGluZyB0byBgQVZHKExvbmdpdHVkZSlgLiBOb3cgeW91IHNlZSBhIHNjYXR0ZXIgcGxvdC4NCg0KNi4gQ2xpY2sgYFNob3cgTWVgICh0b3AtcmlnaHQgY29ybmVyIG9mIGBCb29rIDFgKSBhbmQgc2VsZWN0IHRoZSBsZWZ0LWhhbmQgc2lkZSBtYXAgaWNvbiAodGhlIGZpcnN0IG9uZSBpbiB0aGUgc2Vjb25kIHJvdyksIHlvdSB3aWxsIHNlZSBhbiBpbml0aWFsIHNjYXR0ZXIgbWFwLg0KDQo3LiBXZSB3YW50IHRvIHVzZSB0aGUgc2l6ZSBvZiB0aGUgcG9pbnQgdG8gcmVmbGVjdCB0aGUgdW5pdCBwcmljZS4gd2UgZHJhZyBgUHJpY2VVbml0QXJlYWAgdG8gYFNpemVgIGNhcmQgaW4gdGhlIGBNYXJrc2Agc2hlbGYuDQoNCjguIENsaWNrIGBTaG93IE1lYCB0byBjbG9zZSB0aGUgY2hhcnQgbWVudS4gQ2xpY2sgYFNVTShQcmljZSBVbml0IEFyZWEpYCAodG9wLXJpZ2h0IGNvcm5lcikgdG8gY2hhbmdlIHRoZSBwb2ludCBzaXplLg0KDQo5LiBJIGRyYWcgYFRyYW5zYWN0aW9uIFllYXJgIHRvIHRoZSBgQ29sb3JgIGNhcmQgdG8gcmVmbGVjdCB0aGUgdHJhbnNhY3Rpb24geWVhci4gV2Ugc2hvdWxkIGNob29zZSBhIGRpdmVyZ2VudCBjb2xvciBzY2FsZS4NCg0KMTAuIGRyYWcgdmFyaWFibGVzIHRvIHRoZSBgRGV0YWlsYCBjYXJkIHRvIGJlIHNob3duIGluIHRoZSBob3ZlciB0ZXh0Lg0KDQoxMS4gU2luY2UgbWFueSB1bml0IHByaWNlcyBhcmUgY2xvc2UgdG8gZWFjaCBvdGhlciwgdGhlcmUgYXJlIG92ZXJsYXBwZWQgcG9pbnRzLiBTbyB3ZSB3YW50IHRvIGNoYW5nZSB0aGUgbGV2ZWwgb2Ygb3BhY2l0eS4gVG8gZG8gdGhpcywgY2xpY2sgYENvbG9yYCBjYXJkLCBjaG9vc2UgdGhlIGFwcHJvcHJpYXRlIGxldmVsIG9mIG9wYWNpdHksIGFuZCBlZGl0IHRoZSBjb2xvciB0byBtYWtlIGEgYmV0dGVyIG1hcC4NCg0KMTIuIEFkZCBhIG1lYW5pbmdmdWwgdGl0bGUuDQoNCjEzLiBSaWdodCBjbGljayB0aGUgbWFwIGFuZCBzZWxlY3QgYE1hcCBMYXllcnNgIG1ha2UgdGhlIGNoYW5nZXMgb24gdGhlIG1hcCBiYWNrZ3JvdW5kIGFuZCBsYXllcnMuDQoNCjE0LiBPdGhlciBlZGl0cyBhbmQgbW9kaWZpY2F0aW9ucyB0byBpbXByb3ZlIG1hcC4NCg0KDQpUaGUgcmVzdWx0aW5nIG1hcCBjYW4gYmUgdmlld2VkIG9uIHRoZSBUYWJsZWF1IFB1YmxpYyBTZXJ2ZXIgYXQNCjxodHRwczovL3B1YmxpYy50YWJsZWF1LmNvbS9hcHAvcHJvZmlsZS9jcGVuZy92aXovUmVhbEVzdGF0ZURhdGFfMTY0NjkwNjc0NjY2MTAvU2hlZXQxPg0KDQoNCjx0YWJsZSBib3JkZXIgPSAwIGJvcmRlcmNvbG9yPSJkYXJrZ3JlZW4iIGJnY29sb3I9JyNmNmY2ZjYnICB3aWR0aD0xMDAlICBhbGlnbiA9IGNlbnRlcj4gDQo8dHI+DQo8dGQ+DQoNCjxkaXYgY2xhc3M9J3RhYmxlYXVQbGFjZWhvbGRlcicgaWQ9J3ZpejE3MDkwMDQ0MjE0MTknIHN0eWxlPSdwb3NpdGlvbjogcmVsYXRpdmUnPg0KPG5vc2NyaXB0PjxhIGhyZWY9JyMnPjxpbWcgYWx0PSdSZWFsIEVzdGF0ZSBNYXJrZXQgSW5mb3JtYXRpb24gJyBzcmM9J2h0dHBzOiYjNDc7JiM0NztwdWJsaWMudGFibGVhdS5jb20mIzQ3O3N0YXRpYyYjNDc7aW1hZ2VzJiM0NztSZSYjNDc7UmVhbEVzdGF0ZURhdGFfMTY0NjkwNjc0NjY2MTAmIzQ3O1NoZWV0MSYjNDc7MV9yc3MucG5nJyBzdHlsZT0nYm9yZGVyOiBub25lJyAvPjwvYT4NCjwvbm9zY3JpcHQ+DQo8b2JqZWN0IGNsYXNzPSd0YWJsZWF1Vml6JyAgc3R5bGU9J2Rpc3BsYXk6bm9uZTsnPg0KPHBhcmFtIG5hbWU9J2hvc3RfdXJsJyB2YWx1ZT0naHR0cHMlM0ElMkYlMkZwdWJsaWMudGFibGVhdS5jb20lMkYnIC8+IA0KPHBhcmFtIG5hbWU9J2VtYmVkX2NvZGVfdmVyc2lvbicgdmFsdWU9JzMnIC8+IA0KPHBhcmFtIG5hbWU9J3NpdGVfcm9vdCcgdmFsdWU9JycgLz4NCjxwYXJhbSBuYW1lPSduYW1lJyB2YWx1ZT0nUmVhbEVzdGF0ZURhdGFfMTY0NjkwNjc0NjY2MTAmIzQ3O1NoZWV0MScgLz4NCjxwYXJhbSBuYW1lPSd0YWJzJyB2YWx1ZT0nbm8nIC8+DQo8cGFyYW0gbmFtZT0ndG9vbGJhcicgdmFsdWU9J3llcycgLz4NCjxwYXJhbSBuYW1lPSdzdGF0aWNfaW1hZ2UnIHZhbHVlPSdodHRwczomIzQ3OyYjNDc7cHVibGljLnRhYmxlYXUuY29tJiM0NztzdGF0aWMmIzQ3O2ltYWdlcyYjNDc7UmUmIzQ3O1JlYWxFc3RhdGVEYXRhXzE2NDY5MDY3NDY2NjEwJiM0NztTaGVldDEmIzQ3OzEucG5nJyAvPiANCjxwYXJhbSBuYW1lPSdhbmltYXRlX3RyYW5zaXRpb24nIHZhbHVlPSd5ZXMnIC8+DQo8cGFyYW0gbmFtZT0nZGlzcGxheV9zdGF0aWNfaW1hZ2UnIHZhbHVlPSd5ZXMnIC8+DQo8cGFyYW0gbmFtZT0nZGlzcGxheV9zcGlubmVyJyB2YWx1ZT0neWVzJyAvPg0KPHBhcmFtIG5hbWU9J2Rpc3BsYXlfb3ZlcmxheScgdmFsdWU9J3llcycgLz4NCjxwYXJhbSBuYW1lPSdkaXNwbGF5X2NvdW50JyB2YWx1ZT0neWVzJyAvPg0KPHBhcmFtIG5hbWU9J2xhbmd1YWdlJyB2YWx1ZT0nZW4tVVMnIC8+DQo8L29iamVjdD4NCjwvZGl2PiAgICAgICAgICAgICAgICANCjxzY3JpcHQgdHlwZT0ndGV4dC9qYXZhc2NyaXB0Jz4gICAgICAgICAgICAgICAgICAgIA0KdmFyIGRpdkVsZW1lbnQgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgndml6MTcwOTAwNDQyMTQxOScpOyAgICAgICAgICAgICAgICAgICAgDQp2YXIgdml6RWxlbWVudCA9IGRpdkVsZW1lbnQuZ2V0RWxlbWVudHNCeVRhZ05hbWUoJ29iamVjdCcpWzBdOyAgICAgICAgICAgICAgICAgICAgdml6RWxlbWVudC5zdHlsZS53aWR0aD0nMTAwJSc7dml6RWxlbWVudC5zdHlsZS5oZWlnaHQ9KGRpdkVsZW1lbnQub2Zmc2V0V2lkdGgqMC43NSkrJ3B4JzsgICAgICAgICAgICAgICAgICAgIA0KdmFyIHNjcmlwdEVsZW1lbnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTsgICAgICAgICAgICAgICAgICAgIA0Kc2NyaXB0RWxlbWVudC5zcmMgPSAnaHR0cHM6Ly9wdWJsaWMudGFibGVhdS5jb20vamF2YXNjcmlwdHMvYXBpL3Zpel92MS5qcyc7ICAgICAgICAgICAgICAgICAgICB2aXpFbGVtZW50LnBhcmVudE5vZGUuaW5zZXJ0QmVmb3JlKHNjcmlwdEVsZW1lbnQsIHZpekVsZW1lbnQpOyAgICAgICAgICAgICAgICANCjwvc2NyaXB0Pg0KPC90ZD4NCjwvdHI+DQo8L3RhYmxlPg0KDQojIyBSIENvbG9yIFBhbGV0dGVzDQo8QlI+DQpTaW5jZSBjb2xvciBjb2RpbmcgaXMgcGFydGljdWxhcmx5IGltcG9ydGFudCBpbiBtYXAgcmVwcmVzZW50YXRpb24uIFdlIGNhbiB1c2UgdGhlIGZvbGxvd2luZyBjb2RlIHRvIHZpZXcgdmFyaW91cyBkZWZpbmVkIGNvbG9yIHNjYWxlcyAoY29udGludW91cyBhbmQgZGlzY3JldGUpIGluIHRoZSBSIGxpYnJhcnkgYFJDb2xvckJyZXdlcmAuDQo8QlI+DQoqICoqU2VxdWVudGlhbCBwYWxldHRlcyoqIGFyZSBzdWl0ZWQgdG8gb3JkZXJlZCBkYXRhIHRoYXQgcHJvZ3Jlc3MgZnJvbSBsb3cgdG8gaGlnaCAoZ3JhZGllbnQpLiBUaGUgcGFsZXR0ZXMgbmFtZXMgYXJlIDogYEJsdWVzYCwgYEJ1R25gLCBgQnVQdWAsIGBHbkJ1YCwgYEdyZWVuc2AsIGBHcmV5c2AsIGBPcmFuZ2VzYCwgYE9yUmRgLCBgUHVCdWAsIGBQdUJ1R25gLCBgUHVSZGAsIGBQdXJwbGVzYCwgYFJkUHVgLCBgUmVkc2AsIGBZbEduYCwgYFlsR25CdWAsIGBZbE9yQnJgLCBgWWxPclJkYC4NCg0KKiAqKlF1YWxpdGF0aXZlIHBhbGV0dGVzKiogYXJlIGJlc3Qgc3VpdGVkIHRvIHJlcHJlc2VudCBub21pbmFsIG9yIGNhdGVnb3JpY2FsIGRhdGEuIFRoZXkgZG8gbm90IGltcGx5IG1hZ25pdHVkZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIGdyb3Vwcy4gVGhlIHBhbGV0dGVzIG5hbWVzIGFyZSA6IGBBY2NlbnRgLCBgRGFyazJgLCBgUGFpcmVkYCwgYFBhc3RlbDFgLCBgUGFzdGVsMmAsIGBTZXQxYCwgYFNldDJgLCBgU2V0M2AuDQoNCiogKipEaXZlcmdpbmcgcGFsZXR0ZXMqKiBwdXQgZXF1YWwgZW1waGFzaXMgb24gbWlkLXJhbmdlIGNyaXRpY2FsIHZhbHVlcyBhbmQgZXh0cmVtZXMgYXQgYm90aCBlbmRzIG9mIHRoZSBkYXRhIHJhbmdlLiBUaGUgZGl2ZXJnaW5nIHBhbGV0dGVzIGFyZSA6IGBCckJHYCwgYFBpWUdgLCBgUFJHbmAsIGBQdU9yYCwgYFJkQnVgLCBgUmRHeWAsIGBSZFlsQnVgLCBgUmRZbEduYCwgYFNwZWN0cmFsYC4NCg0KPEJSPjxCUj4NCg0KIyMjIEFsbCBQYWxldHRlcw0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy5oZWlnaHQ9NywgZmlnLndpZHRoPTR9DQpsaWJyYXJ5KCJSQ29sb3JCcmV3ZXIiKQ0KZGlzcGxheS5icmV3ZXIuYWxsKCkgDQpgYGANCg0KDQojIyMgKioyLiBDb2xvci1iaW5kIEZyaWVuZGx5IFBhbGV0dGVzKioNCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD00fQ0KbGlicmFyeSgiUkNvbG9yQnJld2VyIikNCmRpc3BsYXkuYnJld2VyLmFsbChjb2xvcmJsaW5kRnJpZW5kbHkgPSBUUlVFKSANCmBgYA0KDQoNCiMjIyAqKjMuIENvbG9yIFBhbGV0dGUgQ29kZXMqKg0KDQpgYGB7cn0NCmxpYnJhcnkoIlJDb2xvckJyZXdlciIpDQprYWJsZShicmV3ZXIucGFsLmluZm8pDQpgYGANCg0KIyMjICoqNC4gRnVuY3Rpb25zIGZvciBTZWxlY3RpbmcgU3BlY2lmaWMgQ29sb3IgUGFsZXR0ZXMqKg0KDQpUd28gZnVuY3Rpb25zIGNhbiBiZSB1c2VkIHRvIGRpc3BsYXkgYSBzcGVjaWZpYyBjb2xvciBwYWxldHRlIG9yIHJldHVybiB0aGUgY29kZSBvZiB0aGUgcGFsZXR0ZS4NCg0KKiBgZGlzcGxheS5icmV3ZXIucGFsKG4sIG5hbWUpYCBkaXNwbGF5cyBhIHNpbmdsZSBgUkNvbG9yQnJld2VyYCBwYWxldHRlIGJ5IHNwZWNpZnlpbmcgaXRzIG5hbWUuDQoNCiogYGJyZXdlci5wYWwobiwgbmFtZSlgIHJldHVybnMgdGhlIGhleGFkZWNpbWFsIGNvbG9yIGNvZGUgb2YgdGhlIHBhbGV0dGUuDQoNClRoZSB0d28gYXJndW1lbnRzOg0KDQpgbmAgPSBOdW1iZXIgb2YgZGlmZmVyZW50IGNvbG9ycyBpbiB0aGUgcGFsZXR0ZSwgbWluaW11bSAzLCBtYXhpbXVtIGRlcGVuZGluZyBvbiBwYWxldHRlLg0KDQpgbmFtZWA9IEEgcGFsZXR0ZSBuYW1lIGZyb20gdGhlIGxpc3RzIGFib3ZlLiBGb3IgZXhhbXBsZSBuYW1lID0gUmRCdS4NCg0KDQoqKkV4YW1wbGUgMSoqOiBEaXNwbGF5IHRoZSBmaXJzdCA4IGNvbG9ycyBvZiBwYWxldHRlIGBEYXJrMmAuDQoNCmBgYHtyLCBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTN9DQojIFZpZXcgYSBzaW5nbGUgUkNvbG9yQnJld2VyIHBhbGV0dGUgYnkgc3BlY2lmeWluZyBpdHMgbmFtZQ0KZGlzcGxheS5icmV3ZXIucGFsKG4gPSA4LCBuYW1lID0gJ0RhcmsyJykNCmBgYA0KDQoqKkV4YW1wbGUgMioqOiBSZXR1cm4gdGhlIGhleGFkZWNpbWFsIG9mIHRoZSBmaXJzdCA4IGNvbG9ycyBvZiBwYWxldHRlIGBEYXJrMmAuDQoNCmBgYHtyfQ0KIyBIZXhhZGVjaW1hbCBjb2xvciBzcGVjaWZpY2F0aW9uIA0Ka2FibGUodChicmV3ZXIucGFsKG4gPSA4LCBuYW1lID0gIkRhcmsyIikpKQ0KYGBgDQoNCioqQS4gRnVuY3Rpb25zIENhbGxpbmcgU3BlY2lmaWMgYHJjb2xvcmJyZXdlcmAgUGFsZXR0ZSBpbiBgZ2dwbG90KClgKioNCg0KVGhlIGZvbGxvd2luZyBjb2xvciBzY2FsZSBmdW5jdGlvbnMgYXJlIGF2YWlsYWJsZSBpbiBnZ3Bsb3QyIGZvciB1c2luZyB0aGUgYHJjb2xvcmJyZXdlcmAgcGFsZXR0ZXM6DQoNCmBzY2FsZV9maWxsX2JyZXdlcigpYCBmb3IgYm94IHBsb3QsIGJhciBwbG90LCB2aW9saW4gcGxvdCwgZG90IHBsb3QsIGV0Yy4NCg0KYHNjYWxlX2NvbG9yX2JyZXdlcigpYCBmb3IgbGluZXMgYW5kIHBvaW50cw0KDQoNCioqQi4gRnVuY3Rpb25zIENhbGxpbmcgU3BlY2lmaWMgYHJjb2xvcmJyZXdlcmAgUGFsZXR0ZSBpbiBCYXNlIFBsb3RzKioNCg0KVGhlIGZ1bmN0aW9uIGBicmV3ZXIucGFsKClgIGlzIHVzZWQgdG8gZ2VuZXJhdGUgYSB2ZWN0b3Igb2YgY29sb3JzLg0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9NCwgZmlnLmhlaWdodD01fQ0KIyBCYXJwbG90IHVzaW5nIFJDb2xvckJyZXdlcg0KYmFycGxvdChjKDIsNSw3KSwgY29sID0gYnJld2VyLnBhbChuID0gMywgbmFtZSA9ICJEYXJrMiIpKQ0KYGBgDQoNCg0KDQo=