1 Map 1: Randomly Selected Gas Stations

1.1 Overview

Our first visualization will be an interactive map, containing various locations of gas stations from the contential United States. Each gas station will be a point on the map, and each point will have a hover text box containing state, county, address, and zip code.

1.2 Randomly Selecting Data

Since our source data contains nearly 73,000 observations, we will randomly select 500 observations.

gas<-read.csv("https://pengdsci.github.io/datasets/POC/POC.csv")

rand.df <- gas[sample(nrow(gas), size=500), ]

1.3 Mapping the Data

We will use the plotly package to map the data onto an interactive map.

g <- list(      scope = 'p',
                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(rand.df, lat = ~ycoord, lon = ~xcoord) %>% 
  add_markers( text = ~paste(STATE, county, ADDRESS, ZIPnew,
                             
                             sep = "<br>"),
               #color = , 
               symbol = "circle", 
               #size = , 
               hoverinfo = "text")   %>% 
  layout( title = 'Randomly Selected US Gas Stations', 
          geo = g )

fig

The above is a demonstration of a simple - yet effective - way to visualize data on a map. Our next will be a little more complex.

2 Map 2: Crime in Philadelphia, 2023

2.1 Overview

For this visualization, we will begin with a dataset of crimes committed in the city of Philadelphia between 2015 and early March of 2024.

2.2 Preparing the Data

We will need to subset the 2023 data before we can impose it over a map. We will use the stringr library for this.

crime<-read.csv("https://pengdsci.github.io/STA553VIZ/w08/PhillyCrimeSince2015.csv")

df<-crime

df$year <- str_extract(df$date, "\\d{4}")
df$year <- as.numeric(df$year)

crime23<-subset(df, year==2023)

write.csv(crime23, "C:\\Users\\Alex\\Documents\\R\\Grad\\553\\datasets\\wk7.csv")

A copy of the 2023 data can be found at https://raw.githubusercontent.com/AlexDragonetti/STA553/main/hw7/wk7.csv

#remove observations with missing values - at least one has a missing value for coordinates
crime23.nona<-na.omit(crime23)

2.3 Mapping the Data

Finally, we will map the incident data using the leaflet package.

color2 <- rep("red", length(crime23.nona))
color2[which(crime23.nona$fatal=="Nonfatal")] <- "blue"
color2[which(crime23.nona$fatal=="Fatal")] <- "red"



label.msg <- paste("Street:", crime23.nona$street_name,    
                   "<br>Block Number:",crime23.nona$block_number,
                   "<br>Neighborhood:", crime23.nona$neighborhood,
                   "<br>Incident Type:", crime23.nona$fatal)


leaflet(crime23.nona) %>%
  addTiles() %>% 
  setView(lng=mean(crime23.nona$lng), lat=mean(crime23.nona$lat), zoom = 11) %>%
  addProviderTiles(providers$Esri.WorldGrayCanvas) %>%
  addCircleMarkers(
            ~lng, 
            ~lat,
            color = color2,
            stroke = FALSE, 
            fillOpacity = 0.5,
            popup= ~label.msg)  %>%
  addLegend(position = "bottomright", 
            colors = c("red", "blue"),
            labels= c("Fatal", "Nonfatal"),
            title= "Type of Incident",
            opacity = 0.4)

Our resulting graph is fully interactive - clicking a dot will show details of the incident.

Please note that the above graph has spots that appear purple. This is due to the opaque, overlapping red and blue dots, indicating both fatal and nonfatal incidents at the same address. For example, 1000 E Bristol Street in Juniata saw an incident with two victims, one being a fatality. For clarification on any confusing point, please refer to the dataset linked at the end of Preparing the Data.

3 Map 3: Philadelphia Shootings, 2015-2024

3.1 Overview

Our next visualization will specifically focus on the demographics of shooting victims. The data is from OpenDataPhilly and can be accessed here:https://opendataphilly.org/datasets/shooting-victims (see below code for raw, direct link). We will again utilize leaflet.

philly.data<-read.csv("https://phl.carto.com/api/v2/sql?q=SELECT+*,+ST_Y(the_geom)+AS+lat,+ST_X(the_geom)+AS+lng+FROM+shootings&filename=shootings&format=csv&skipfields=cartodb_id")
phillyNeighborShooting  <- na.omit(st_read("https://pengdsci.github.io/STA553VIZ/w08/PhillyShootings.geojson"))
Reading layer `PhillyShootings' from data source 
  `https://pengdsci.github.io/STA553VIZ/w08/PhillyShootings.geojson' 
  using driver `GeoJSON'
replacing null geometries with empty geometries
Simple feature collection with 15555 features and 21 fields (with 29 geometries empty)
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: -75.27362 ymin: 39.87799 xmax: -74.95936 ymax: 40.13117
Geodetic CRS:  WGS 84
phillyNeighbor  <- st_read("https://pengdsci.github.io/STA553VIZ/w08/Neighborhoods_Philadelphia.geojson")
Reading layer `Neighborhoods_Philadelphia' from data source 
  `https://pengdsci.github.io/STA553VIZ/w08/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
philly  <- st_read("https://pengdsci.github.io/STA553VIZ/w08/PhillyNeighborhood-blocks.geojson")
Reading layer `PhillyNeighborhood-blocks' from data source 
  `https://pengdsci.github.io/STA553VIZ/w08/PhillyNeighborhood-blocks.geojson' 
  using driver `GeoJSON'
Simple feature collection with 17555 features and 7 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -75.28027 ymin: 39.867 xmax: -74.95576 ymax: 40.13799
Geodetic CRS:  WGS 84

3.2 Preparing the Data

While the data is usable, we intend to display much more information with each point, so we will remove unnecessary or redundant data, while fixing a formatting quirk with the ‘date_’ variable (which will also be renamed to ‘date’) using the stringr package. Without this, the ‘date_’ variable ends each entry with ‘00:00:00+00’, which is clunky and likely unintended.

philly.data2 <- philly.data %>%
  select(-c(1, 2, 3, 5, 6, 17, 18))

names(philly.data2)[which(names(philly.data2) == "date_")] <- "date"

philly.data2$date <- str_replace(philly.data2$date, " 00:00:00\\+00", "")

# Convert 'date' variable to Date format for possible future use
philly.data2$date <- as.Date(philly.data2$date, format = "%Y-%m-%d")

3.3 Preparing Visualization for Aggregate Data

With out map, we would like to feature individual incidents and allow someone to click to see victim demographic information, but we would also like to have ‘big picture’ aggregate plots available as well. We will prepare three simple plots here using ggplot for illustrative purposes.

#Distribution of Age across Race

ar.plot<-ggplot(philly.data2, aes(x = age, fill = race)) +
  geom_density(alpha = 0.5) +
  labs(title = "Distribution of Age by Race",
       x = "Age",
       y = "Density") +
  scale_fill_discrete(name = "Race") +
  theme_minimal()

#Probably unnecessary step, but made an extra dataset while testing something out
philly.data3<-subset(philly.data2)
philly.data3$fatal <- factor(philly.data3$fatal, levels = c(0, 1), labels = c("Nonfatal", "Fatal"))

# Plot the point graph with colored points
year.plot <- ggplot(philly.data3, aes(x = year, fill = fatal)) +
  geom_bar(position = position_dodge(width = 0.5), stat = "count") +
  scale_fill_manual(values = c("Nonfatal" = "blue", "Fatal" = "red")) +
  labs(title = "Number of Fatal and Nonfatal Incidents by Year",
       x = "Year",
       y = "Count") +
  theme_minimal() +
  scale_x_continuous(breaks = seq(min(philly.data3$year), max(philly.data3$year), by = 1))

#Frequency of indoor vs outdoor incidents

outside.plot <- ggplot(philly.data2, aes(x = year, fill = factor(outside, labels = c("Indoor", "Outdoor")))) +
  geom_bar(stat = "count") +
  labs(title = "Number of Indoor and Outdoor Incidents by Year",
       x = "Year",
       y = "Count",
       fill = "Location") +
  scale_fill_manual(values = c("Indoor" = "darkorchid", "Outdoor" = "darkgreen")) +
  scale_x_continuous(breaks = seq(min(philly.data2$year), max(philly.data2$year), by = 1)) +
  theme_minimal()

We have exported and uploaded these images separately and will now redefine them:

ar="https://raw.githubusercontent.com/AlexDragonetti/STA553/main/hw8/arplot.png"

out="https://raw.githubusercontent.com/AlexDragonetti/STA553/main/hw8/outsideplot2.png"

yr="https://raw.githubusercontent.com/AlexDragonetti/STA553/main/hw8/yearplot2.png"

3.4 Mapping the Data

Our final visualization will contain a map of shooting victims, fully interactive with demographic information, as well as additional, big-picture data available.

#defining things
pal <- colorFactor(c("blue", "red"), domain = c(0, 1))


ageraceplot = st_as_sf(data.frame(x = -75.4077, y = 39.9168),
                coords = c("x", "y"),
                crs = 4326)
yearplot = st_as_sf(data.frame(x = -75.3877, y = 39.9168),
                coords = c("x", "y"),
                crs = 4326)
outdoorplot = st_as_sf(data.frame(x = -75.3677, y = 39.9168),
                coords = c("x", "y"),
                crs = 4326)
fig <- plot_ly(philly.data2, x = ~lng, y = ~lat, 
               type = 'scatter', 
               mode = 'markers', 
                              marker = list(symbol = 'circle', 
                           sizemode = 'diameter',
                               line = list(width = 2, color = '#FFFFFF')))
              
tag.map.title <- tags$style(HTML("
               .leaflet-control.map-title {
                   transform: translate(50%,50%);
                   position: fixed !important;
                   left: 50%;
                   text-align: center;
                   padding-left: 10px;
                   padding-right: 10px;
                   background: transparent;
                   font-weight: bold;
                   font-size: 18px;}
                 "))

rr <- tags$div(
   HTML('<img border="0" alt="ImageTitle" src="https://raw.githubusercontent.com/AlexDragonetti/STA553/main/hw8/map%20title.png" width="200" height="45">')
 ) 

### 
leaflet() %>%
  setView(lng=-75.190429, lat=40.0039, zoom = 10.5) %>%
  addProviderTiles(providers$CartoDB.DarkMatter, group="Dark") %>%
  addProviderTiles(providers$CartoDB.DarkMatterNoLabels, group="DarkLabel") %>%  
  addProviderTiles(providers$Esri.NatGeoWorldMap, group="Esri") %>%
  addControl(rr, position = "topleft", className="map-title") %>%
  ## mini reference map
  addMiniMap() %>%
  ## neighborhood boundary
  addPolygons(data = phillyNeighbor,
              color = 'skyblue',
              weight = 1)  %>%
  
    
  addCircleMarkers(data = ageraceplot, 
                   color = "white",
                   weight = 2,
                   label = "Distribution of Age by Race",
                   stroke = FALSE, 
                   fillOpacity = 0.95,
                   group = "ageraceplot") %>%
  addPopupImages(ar, 
                  width = 500,
                  height = 320,
                  tooltip = FALSE,
                  group = "ageraceplot") %>%
    
 addCircleMarkers(data = yearplot, 
                   color = "skyblue",
                   weight = 2,
                   label = "Incident Rate by Year",
                   stroke = FALSE, 
                   fillOpacity = 0.95,
                   group = "yearplot") %>%
  addPopupImages(yr, 
                  width = 500,
                  height = 320,
                  tooltip = FALSE,
                  group = "yearplot") %>%

  
  addCircleMarkers(data = outdoorplot, 
                   color = "darkgreen",
                   weight = 2,
                   label = "Rate of Indoor vs Outdoor Incidents",
                   stroke = FALSE, 
                   fillOpacity = 0.95,
                   group = "outdoorplot") %>%
  addPopupImages(out, 
                   width = 500,
                  height = 320,
                   group = "outdoorplot" ) %>%


 
  
  ## plot information on the map
  addCircleMarkers(data = philly.data2,
                   color = ~pal(as.factor(fatal)),
                   stroke = FALSE, 
                   fillOpacity = 0.5,
                   popup = ~popupTable(philly.data2),
                   clusterOptions = markerClusterOptions(maxClusterRadius = 40)) %>%

  
          addLayersControl(baseGroups = c('Dark', 'DarkLabel', 'Esri'),
                   overlayGroups = c("Crime Data"),
                   options = layersControlOptions(collapsed = TRUE)) %>%
  ##
  browsable()

The above map is fully interactive, maintains our red/blue fatal/nonfatal color coding from the previous example, but allows us to provide much more information for each incident, as well as include aggregate data visualizations (by clicking on the dots blocking out Media on the map).

4 Map 4: Mapping Data with Tableau

4.1 Overview

Our dataset contains presidential election data for each county in the continental United States. Our goal is to create an interactive map in Tableau that displays county-level information for each presidential election from 2000-2020. First, we must import the data. We have been instructed to only consider the two major American political parties, Republican and Democrat, for this visualization.

##Voting Data
vote<-read.csv("https://pengdsci.github.io/datasets/countypresidential_election_2000-2020.csv")
fips<-read.csv("https://pengdsci.github.io/datasets/fips2geocode.csv")

colnames(fips)[1]<-"county_fips"

##subset data
vote.min <- subset(vote, party %in% c("REPUBLICAN", "DEMOCRAT"), 
                    select = c("state_po", "county_name", "candidate", "county_fips", "party", "candidatevotes", "year"))

4.2 Preparing the Data

Using dplyr and tidyr packages, we are: + Counting all votes in a given county and year - being mindful of state, because multiple counties in different states share a name + Removing the losing candidate’s data for a given county and year - still mindful of state + Creating a variable for the winning candidate’s percentage of the vote + Merging our finished dataset with the data about each county

total_votes <- vote.min %>%
  group_by(state_po, county_name, year) %>%
  summarise(total_votes = sum(candidatevotes))


winning_party_data2 <- vote.min %>%
  group_by(state_po, county_name, year) %>%
  filter(candidatevotes == max(candidatevotes)) %>%
  ungroup() %>%
  left_join(total_votes, by = c("state_po", "county_name", "year")) %>%
  mutate(winning_percentage = candidatevotes / total_votes * 100)


vote3.merge <- merge(winning_party_data2, fips, by = "county_fips", all.x = TRUE)

4.3 Mapping the Data with Tableau

Tableau is very intuitive and allows one to map data by clicking and dragging desired variables to certain function (ie, displaying the Winning Party variable as ‘color’). The downside of this being: there is no code to share!

The Tableau map is embedded below. It is interactive as well - mousing over a county should give you its election information. The year can be adjusted to see data from each presidential election from 2000 to 2020.

LS0tDQp0aXRsZTogIkludGVyYWN0aXZlIE1hcHMiDQphdXRob3I6ICJBbGV4IERyYWdvbmV0dGkiDQpkYXRlOiAiMy0yNS0yMDI0Ig0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0b2NfY29sbGFwc2VkOiB5ZXMNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlDQogICAgdGhlbWU6IGx1bWVuDQplZGl0b3Jfb3B0aW9uczoNCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQ0KLS0tDQpgYGB7PWh0bWx9DQoNCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+DQoNCi8qIENhc2NhZGluZyBTdHlsZSBTaGVldHMgKENTUykgaXMgYSBzdHlsZXNoZWV0IGxhbmd1YWdlIHVzZWQgdG8gZGVzY3JpYmUgdGhlIHByZXNlbnRhdGlvbiBvZiBhIGRvY3VtZW50IHdyaXR0ZW4gaW4gSFRNTCBvciBYTUwuIGl0IGlzIGEgc2ltcGxlIG1lY2hhbmlzbSBmb3IgYWRkaW5nIHN0eWxlIChlLmcuLCBmb250cywgY29sb3JzLCBzcGFjaW5nKSB0byBXZWIgZG9jdW1lbnRzLiAqLw0KDQpoMS50aXRsZSB7ICAvKiBUaXRsZSAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgb2YgdGhlIHJlcG9ydCB0aXRsZSAqLw0KICBmb250LXNpemU6IDI0cHg7DQogIGNvbG9yOiBEYXJrUmVkOw0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQogIGZvbnQtZmFtaWx5OiAiR2lsbCBTYW5zIiwgc2Fucy1zZXJpZjsNCn0NCmg0LmF1dGhvciB7IC8qIEhlYWRlciA0IC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgYXV0aG9ycyAgKi8NCiAgZm9udC1zaXplOiAyMHB4Ow0KICBmb250LWZhbWlseTogc3lzdGVtLXVpOw0KICBjb2xvcjogRGFya1JlZDsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KaDQuZGF0ZSB7IC8qIEhlYWRlciA0IC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgdGhlIGRhdGUgICovDQogIGZvbnQtc2l6ZTogMThweDsNCiAgZm9udC1mYW1pbHk6IHN5c3RlbS11aTsNCiAgY29sb3I6IERhcmtCbHVlOw0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQp9DQpoMSB7IC8qIEhlYWRlciAxIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgbGV2ZWwgMSBzZWN0aW9uIHRpdGxlICAqLw0KICAgIGZvbnQtc2l6ZTogMjJweDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBjZW50ZXI7DQp9DQpoMiB7IC8qIEhlYWRlciAyIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgbGV2ZWwgMiBzZWN0aW9uIHRpdGxlICovDQogICAgZm9udC1zaXplOiAyMHB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBuYXZ5Ow0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQoNCmgzIHsgLyogSGVhZGVyIDMgLSBmb250IHNwZWNpZmljYXRpb25zIG9mIGxldmVsIDMgc2VjdGlvbiB0aXRsZSAgKi8NCiAgICBmb250LXNpemU6IDE4cHg7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCg0KaDQgeyAvKiBIZWFkZXIgNCAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgb2YgbGV2ZWwgNCBzZWN0aW9uIHRpdGxlICAqLw0KICAgIGZvbnQtc2l6ZTogMThweDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogZGFya3JlZDsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQpib2R5IHsgYmFja2dyb3VuZC1jb2xvcjp3aGl0ZTsgfQ0KDQouaGlnaGxpZ2h0bWUgeyBiYWNrZ3JvdW5kLWNvbG9yOnllbGxvdzsgfQ0KDQpwIHsgYmFja2dyb3VuZC1jb2xvcjp3aGl0ZTsgfQ0KDQo8L3N0eWxlPg0KYGBgDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0KIyBEZXRlY3QsIGluc3RhbGwsIGFuZCBsb2FkIHBhY2thZ2VzIGlmIG5lZWRlZC4NCmlmICghcmVxdWlyZSgidGlkeXZlcnNlIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIpDQogICBsaWJyYXJ5KHRpZHl2ZXJzZSkNCn0NCmlmICghcmVxdWlyZSgia25pdHIiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygia25pdHIiKQ0KICAgbGlicmFyeShrbml0cikNCn0NCmlmICghcmVxdWlyZSgic2YiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygic2YiKQ0KICAgbGlicmFyeShzZikNCn0NCmlmICghcmVxdWlyZSgidGVycmEiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygidGVycmEiKQ0KICAgbGlicmFyeSh0ZXJyYSkNCn0NCmlmICghcmVxdWlyZSgicGxvdGx5IikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoInBsb3RseSIpDQogICBsaWJyYXJ5KHBsb3RseSkNCn0NCmlmICghcmVxdWlyZSgiZHBseXIiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygiZHBseXIiKQ0KICAgbGlicmFyeShkcGx5cikNCn0NCmlmICghcmVxdWlyZSgicG5nIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJwbmciKSAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJwbmciKQ0KfQ0KaWYgKCFyZXF1aXJlKCJzcERhdGEiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoInNwRGF0YSIpICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoInNwRGF0YSIpDQp9DQppZiAoIXJlcXVpcmUoImNvbG91cnBpY2tlciIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygiY29sb3VycGlja2VyIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoImNvbG91cnBpY2tlciIpDQp9DQppZiAoIXJlcXVpcmUoImdpZnNraSIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygiZ2lmc2tpIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoImdpZnNraSIpDQp9DQppZiAoIXJlcXVpcmUoIm1hZ2ljayIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygibWFnaWNrIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoIm1hZ2ljayIpDQp9DQppZiAoIXJlcXVpcmUoInNwRGF0YUxhcmdlIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJzcERhdGFMYXJnZSIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJzcERhdGFMYXJnZSIpDQp9DQojIyMgZ2dwbG90IGFuZCBleHRlbnNpb25zDQppZiAoIXJlcXVpcmUoImdncGxvdDIiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgiZ2dwbG90MiIpDQp9DQppZiAoIXJlcXVpcmUoImdnYW5pbWF0ZSIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygiZ2dhbmltYXRlIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoImdnYW5pbWF0ZSIpDQp9DQppZiAoIXJlcXVpcmUoInRtYXAiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoInRtYXAiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgidG1hcCIpDQp9DQppZiAoIXJlcXVpcmUoInNmIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJzZiIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJzZiIpDQp9DQppZiAoIXJlcXVpcmUoInRpZ3JpcyIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygidGlncmlzIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoInRpZ3JpcyIpDQp9DQppZiAoIXJlcXVpcmUoIm1hcHZpZXciKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoIm1hcHZpZXciKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgibWFwdmlldyIpDQp9DQppZiAoIXJlcXVpcmUoInBhbmRlciIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygicGFuZGVyIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoInBhbmRlciIpDQp9DQppZiAoIXJlcXVpcmUoImxhdHRpY2UiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoImxhdHRpY2UiKQ0KbGlicmFyeSgibGF0dGljZSIpDQp9DQppZiAoIXJlcXVpcmUoInNwIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJzcCIpDQpsaWJyYXJ5KCJzcCIpDQp9DQppZiAoIXJlcXVpcmUoImxlYWZsZXQiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoImxlYWZsZXQiKQ0KbGlicmFyeSgibGVhZmxldCIpDQp9DQppZiAoIXJlcXVpcmUoImxlYWZwb3AiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoImxlYWZwb3AiKQ0KbGlicmFyeSgibGVhZnBvcCIpDQp9DQppZiAoIXJlcXVpcmUoImxlYWZlbSIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygibGVhZmVtIikNCmxpYnJhcnkoImxlYWZlbSIpDQp9DQppZiAoIXJlcXVpcmUoInNwRGF0YUxhcmdlIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJzcERhdGFMYXJnZSIsIHJlcG9zID0gImh0dHBzOi8vZ2VvY29tcHIuci11bml2ZXJzZS5kZXYiKQ0KbGlicmFyeSgic3BEYXRhTGFyZ2UiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJodG1sd2lkZ2V0cyIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygiaHRtbHdpZGdldHMiKQ0KbGlicmFyeSgiaHRtbHdpZGdldHMiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJsZWFmbGV0LmV4dHJhcyIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygibGVhZmxldC5leHRyYXMiKQ0KbGlicmFyeSgibGVhZmxldC5leHRyYXMiKQ0KfQ0KaWYgKCFyZXF1aXJlKCJodG1sdG9vbHMiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoImh0bWx0b29scyIpDQpsaWJyYXJ5KCJodG1sdG9vbHMiKQ0KfQ0KaWYoIXJlcXVpcmUoInBuZyIpKXsNCiAgaW5zdGFsbC5wYWNrYWdlcygicG5nIikNCiAgbGlicmFyeShwbmcpDQp9DQppZighcmVxdWlyZSgidmlyaWRpcyIpKXsNCiAgaW5zdGFsbC5wYWNrYWdlcygidmlyaWRpcyIpDQogIGxpYnJhcnkodmlyaWRpcykNCn0NCmlmKCFyZXF1aXJlKCJnZ21hcCIpKXsNCiAgaW5zdGFsbC5wYWNrYWdlcygiZ2dtYXAiKQ0KICBsaWJyYXJ5KGdnbWFwKQ0KfQ0KaWYoIXJlcXVpcmUoIndlYnNob3QiKSl7DQogIGluc3RhbGwucGFja2FnZXMoIndlYnNob3QiKQ0KICBsaWJyYXJ5KHdlYnNob3QpDQp9DQppZighcmVxdWlyZSgiaHRtbHdpZGdldHMiKSl7DQogIGluc3RhbGwucGFja2FnZXMoImh0bWx3aWRnZXRzIikNCiAgbGlicmFyeShodG1sd2lkZ2V0cykNCn0NCmlmKCFyZXF1aXJlKCJhbmltYXRpb24iKSl7DQogIGluc3RhbGwucGFja2FnZXMoImFuaW1hdGlvbiIpDQogIGxpYnJhcnkoYW5pbWF0aW9uKQ0KfQ0KaWYoIXJlcXVpcmUoImdpZnNraSIpKXsNCiAgaW5zdGFsbC5wYWNrYWdlcygiZ2lmc2tpIikNCiAgbGlicmFyeShnaWZza2kpDQp9DQppZighcmVxdWlyZSgiaHRtbFRhYmxlIikpew0KICBpbnN0YWxsLnBhY2thZ2VzKCJodG1sVGFibGUiKQ0KICBsaWJyYXJ5KGh0bWxUYWJsZSkNCn0NCmlmKCFyZXF1aXJlKCJtYWdyaXR0ciIpKXsNCiAgaW5zdGFsbC5wYWNrYWdlcygibWFncml0dHIiKQ0KICBsaWJyYXJ5KG1hZ3JpdHRyKQ0KfQ0KIyBTcGVjaWZpY2F0aW9ucyBvZiBvdXRwdXRzIG9mIGNvZGUgaW4gY29kZSBjaHVua3MNCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLCAgIA0KICAgICAgICAgICAgICAgICAgICAgIHJlc3VsdCA9IFRSVUUsICAgDQogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIGNvbW1lbnQgPSBOQSkNCmBgYA0KDQoNCg0KIyBNYXAgMTogUmFuZG9tbHkgU2VsZWN0ZWQgR2FzIFN0YXRpb25zDQoNCg0KIyMgT3ZlcnZpZXcNCg0KDQpPdXIgZmlyc3QgdmlzdWFsaXphdGlvbiB3aWxsIGJlIGFuIGludGVyYWN0aXZlIG1hcCwgY29udGFpbmluZyB2YXJpb3VzIGxvY2F0aW9ucyBvZiBnYXMgc3RhdGlvbnMgZnJvbSB0aGUgY29udGVudGlhbCBVbml0ZWQgU3RhdGVzLiBFYWNoIGdhcyBzdGF0aW9uIHdpbGwgYmUgYSBwb2ludCBvbiB0aGUgbWFwLCBhbmQgZWFjaCBwb2ludCB3aWxsIGhhdmUgYSBob3ZlciB0ZXh0IGJveCBjb250YWluaW5nIHN0YXRlLCBjb3VudHksIGFkZHJlc3MsIGFuZCB6aXAgY29kZS4NCg0KDQojIyBSYW5kb21seSBTZWxlY3RpbmcgRGF0YQ0KDQoNClNpbmNlIG91ciBzb3VyY2UgZGF0YSBjb250YWlucyBuZWFybHkgNzMsMDAwIG9ic2VydmF0aW9ucywgd2Ugd2lsbCByYW5kb21seSBzZWxlY3QgNTAwIG9ic2VydmF0aW9ucy4NCg0KYGBge3J9DQpnYXM8LXJlYWQuY3N2KCJodHRwczovL3Blbmdkc2NpLmdpdGh1Yi5pby9kYXRhc2V0cy9QT0MvUE9DLmNzdiIpDQoNCnJhbmQuZGYgPC0gZ2FzW3NhbXBsZShucm93KGdhcyksIHNpemU9NTAwKSwgXQ0KYGBgDQoNCg0KIyMgTWFwcGluZyB0aGUgRGF0YQ0KDQoNCldlIHdpbGwgdXNlIHRoZSBgcGxvdGx5YCBwYWNrYWdlIHRvIG1hcCB0aGUgZGF0YSBvbnRvIGFuIGludGVyYWN0aXZlIG1hcC4NCg0KDQpgYGB7cn0NCmcgPC0gbGlzdCggICAgICBzY29wZSA9ICdwJywNCiAgICAgICAgICAgICAgICBwcm9qZWN0aW9uID0gbGlzdCh0eXBlID0gJ2FsYmVycyB1c2EnKSwNCiAgICAgICAgICAgICAgICBzaG93bGFuZCA9IFRSVUUsDQogICAgICAgICAgICAgICAgbGFuZGNvbG9yID0gdG9SR0IoImdyYXk5NSIpLA0KICAgICAgICAgICAgICAgIHN1YnVuaXRjb2xvciA9IHRvUkdCKCJncmF5ODUiKSwNCiAgICAgICAgICAgICAgICBjb3VudHJ5Y29sb3IgPSB0b1JHQigiZ3JheTg1IiksDQogICAgICAgICAgICAgICAgY291bnRyeXdpZHRoID0gMC41LA0KICAgICAgICAgICAgICAgIHN1YnVuaXR3aWR0aCA9IDAuNQ0KKQ0KDQoNCmZpZyA8LSBwbG90X2dlbyhyYW5kLmRmLCBsYXQgPSB+eWNvb3JkLCBsb24gPSB+eGNvb3JkKSAlPiUgDQogIGFkZF9tYXJrZXJzKCB0ZXh0ID0gfnBhc3RlKFNUQVRFLCBjb3VudHksIEFERFJFU1MsIFpJUG5ldywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICI8YnI+IiksDQogICAgICAgICAgICAgICAjY29sb3IgPSAsIA0KICAgICAgICAgICAgICAgc3ltYm9sID0gImNpcmNsZSIsIA0KICAgICAgICAgICAgICAgI3NpemUgPSAsIA0KICAgICAgICAgICAgICAgaG92ZXJpbmZvID0gInRleHQiKSAgICU+JSANCiAgbGF5b3V0KCB0aXRsZSA9ICdSYW5kb21seSBTZWxlY3RlZCBVUyBHYXMgU3RhdGlvbnMnLCANCiAgICAgICAgICBnZW8gPSBnICkNCg0KZmlnDQpgYGANCg0KDQpUaGUgYWJvdmUgaXMgYSBkZW1vbnN0cmF0aW9uIG9mIGEgc2ltcGxlIC0geWV0IGVmZmVjdGl2ZSAtIHdheSB0byB2aXN1YWxpemUgZGF0YSBvbiBhIG1hcC4gT3VyIG5leHQgd2lsbCBiZSBhIGxpdHRsZSBtb3JlIGNvbXBsZXguDQoNCg0KDQojIE1hcCAyOiBDcmltZSBpbiBQaGlsYWRlbHBoaWEsIDIwMjMNCg0KDQojIyBPdmVydmlldw0KDQoNCkZvciB0aGlzIHZpc3VhbGl6YXRpb24sIHdlIHdpbGwgYmVnaW4gd2l0aCBhIGRhdGFzZXQgb2YgY3JpbWVzIGNvbW1pdHRlZCBpbiB0aGUgY2l0eSBvZiBQaGlsYWRlbHBoaWEgYmV0d2VlbiAyMDE1IGFuZCBlYXJseSBNYXJjaCBvZiAyMDI0Lg0KDQoNCiMjIFByZXBhcmluZyB0aGUgRGF0YQ0KDQpXZSB3aWxsIG5lZWQgdG8gc3Vic2V0IHRoZSAyMDIzIGRhdGEgYmVmb3JlIHdlIGNhbiBpbXBvc2UgaXQgb3ZlciBhIG1hcC4gV2Ugd2lsbCB1c2UgdGhlIGBzdHJpbmdyYCBsaWJyYXJ5IGZvciB0aGlzLg0KDQpgYGB7cn0NCmNyaW1lPC1yZWFkLmNzdigiaHR0cHM6Ly9wZW5nZHNjaS5naXRodWIuaW8vU1RBNTUzVklaL3cwOC9QaGlsbHlDcmltZVNpbmNlMjAxNS5jc3YiKQ0KDQpkZjwtY3JpbWUNCg0KZGYkeWVhciA8LSBzdHJfZXh0cmFjdChkZiRkYXRlLCAiXFxkezR9IikNCmRmJHllYXIgPC0gYXMubnVtZXJpYyhkZiR5ZWFyKQ0KDQpjcmltZTIzPC1zdWJzZXQoZGYsIHllYXI9PTIwMjMpDQoNCndyaXRlLmNzdihjcmltZTIzLCAiQzpcXFVzZXJzXFxBbGV4XFxEb2N1bWVudHNcXFJcXEdyYWRcXDU1M1xcZGF0YXNldHNcXHdrNy5jc3YiKQ0KYGBgDQoNCkEgY29weSBvZiB0aGUgMjAyMyBkYXRhIGNhbiBiZSBmb3VuZCBhdCBodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vQWxleERyYWdvbmV0dGkvU1RBNTUzL21haW4vaHc3L3drNy5jc3YNCg0KYGBge3J9DQojcmVtb3ZlIG9ic2VydmF0aW9ucyB3aXRoIG1pc3NpbmcgdmFsdWVzIC0gYXQgbGVhc3Qgb25lIGhhcyBhIG1pc3NpbmcgdmFsdWUgZm9yIGNvb3JkaW5hdGVzDQpjcmltZTIzLm5vbmE8LW5hLm9taXQoY3JpbWUyMykNCg0KYGBgDQoNCg0KDQojIyBNYXBwaW5nIHRoZSBEYXRhDQoNCg0KRmluYWxseSwgd2Ugd2lsbCBtYXAgdGhlIGluY2lkZW50IGRhdGEgdXNpbmcgdGhlIGBsZWFmbGV0YCBwYWNrYWdlLg0KDQpgYGB7cn0NCmNvbG9yMiA8LSByZXAoInJlZCIsIGxlbmd0aChjcmltZTIzLm5vbmEpKQ0KY29sb3IyW3doaWNoKGNyaW1lMjMubm9uYSRmYXRhbD09Ik5vbmZhdGFsIildIDwtICJibHVlIg0KY29sb3IyW3doaWNoKGNyaW1lMjMubm9uYSRmYXRhbD09IkZhdGFsIildIDwtICJyZWQiDQoNCg0KDQpsYWJlbC5tc2cgPC0gcGFzdGUoIlN0cmVldDoiLCBjcmltZTIzLm5vbmEkc3RyZWV0X25hbWUsICAgIA0KICAgICAgICAgICAgICAgICAgICI8YnI+QmxvY2sgTnVtYmVyOiIsY3JpbWUyMy5ub25hJGJsb2NrX251bWJlciwNCiAgICAgICAgICAgICAgICAgICAiPGJyPk5laWdoYm9yaG9vZDoiLCBjcmltZTIzLm5vbmEkbmVpZ2hib3Job29kLA0KICAgICAgICAgICAgICAgICAgICI8YnI+SW5jaWRlbnQgVHlwZToiLCBjcmltZTIzLm5vbmEkZmF0YWwpDQoNCg0KbGVhZmxldChjcmltZTIzLm5vbmEpICU+JQ0KICBhZGRUaWxlcygpICU+JSANCiAgc2V0Vmlldyhsbmc9bWVhbihjcmltZTIzLm5vbmEkbG5nKSwgbGF0PW1lYW4oY3JpbWUyMy5ub25hJGxhdCksIHpvb20gPSAxMSkgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJEVzcmkuV29ybGRHcmF5Q2FudmFzKSAlPiUNCiAgYWRkQ2lyY2xlTWFya2VycygNCiAgICAgICAgICAgIH5sbmcsIA0KICAgICAgICAgICAgfmxhdCwNCiAgICAgICAgICAgIGNvbG9yID0gY29sb3IyLA0KICAgICAgICAgICAgc3Ryb2tlID0gRkFMU0UsIA0KICAgICAgICAgICAgZmlsbE9wYWNpdHkgPSAwLjUsDQogICAgICAgICAgICBwb3B1cD0gfmxhYmVsLm1zZykgICU+JQ0KICBhZGRMZWdlbmQocG9zaXRpb24gPSAiYm90dG9tcmlnaHQiLCANCiAgICAgICAgICAgIGNvbG9ycyA9IGMoInJlZCIsICJibHVlIiksDQogICAgICAgICAgICBsYWJlbHM9IGMoIkZhdGFsIiwgIk5vbmZhdGFsIiksDQogICAgICAgICAgICB0aXRsZT0gIlR5cGUgb2YgSW5jaWRlbnQiLA0KICAgICAgICAgICAgb3BhY2l0eSA9IDAuNCkNCmBgYA0KDQpPdXIgcmVzdWx0aW5nIGdyYXBoIGlzIGZ1bGx5IGludGVyYWN0aXZlIC0gY2xpY2tpbmcgYSBkb3Qgd2lsbCBzaG93IGRldGFpbHMgb2YgdGhlIGluY2lkZW50LiANCg0KUGxlYXNlIG5vdGUgdGhhdCB0aGUgYWJvdmUgZ3JhcGggaGFzIHNwb3RzIHRoYXQgYXBwZWFyIHB1cnBsZS4gVGhpcyBpcyBkdWUgdG8gdGhlIG9wYXF1ZSwgb3ZlcmxhcHBpbmcgcmVkIGFuZCBibHVlIGRvdHMsIGluZGljYXRpbmcgYm90aCBmYXRhbCBhbmQgbm9uZmF0YWwgaW5jaWRlbnRzIGF0IHRoZSBzYW1lIGFkZHJlc3MuIEZvciBleGFtcGxlLCAxMDAwIEUgQnJpc3RvbCBTdHJlZXQgaW4gSnVuaWF0YSBzYXcgYW4gaW5jaWRlbnQgd2l0aCB0d28gdmljdGltcywgb25lIGJlaW5nIGEgZmF0YWxpdHkuIEZvciBjbGFyaWZpY2F0aW9uIG9uIGFueSBjb25mdXNpbmcgcG9pbnQsIHBsZWFzZSByZWZlciB0byB0aGUgZGF0YXNldCBsaW5rZWQgYXQgdGhlIGVuZCBvZiBgUHJlcGFyaW5nIHRoZSBEYXRhYC4NCg0KDQoNCiMgTWFwIDM6IFBoaWxhZGVscGhpYSBTaG9vdGluZ3MsIDIwMTUtMjAyNA0KDQoNCiMjIE92ZXJ2aWV3DQoNCk91ciBuZXh0IHZpc3VhbGl6YXRpb24gd2lsbCBzcGVjaWZpY2FsbHkgZm9jdXMgb24gdGhlIGRlbW9ncmFwaGljcyBvZiBzaG9vdGluZyB2aWN0aW1zLiBUaGUgZGF0YSBpcyBmcm9tIE9wZW5EYXRhUGhpbGx5IGFuZCBjYW4gYmUgYWNjZXNzZWQgaGVyZTpodHRwczovL29wZW5kYXRhcGhpbGx5Lm9yZy9kYXRhc2V0cy9zaG9vdGluZy12aWN0aW1zIChzZWUgYmVsb3cgY29kZSBmb3IgcmF3LCBkaXJlY3QgbGluaykuIFdlIHdpbGwgYWdhaW4gdXRpbGl6ZSBgbGVhZmxldGAuDQoNCg0KYGBge3J9DQoNCnBoaWxseS5kYXRhPC1yZWFkLmNzdigiaHR0cHM6Ly9waGwuY2FydG8uY29tL2FwaS92Mi9zcWw/cT1TRUxFQ1QrKiwrU1RfWSh0aGVfZ2VvbSkrQVMrbGF0LCtTVF9YKHRoZV9nZW9tKStBUytsbmcrRlJPTStzaG9vdGluZ3MmZmlsZW5hbWU9c2hvb3RpbmdzJmZvcm1hdD1jc3Ymc2tpcGZpZWxkcz1jYXJ0b2RiX2lkIikNCnBoaWxseU5laWdoYm9yU2hvb3RpbmcgIDwtIG5hLm9taXQoc3RfcmVhZCgiaHR0cHM6Ly9wZW5nZHNjaS5naXRodWIuaW8vU1RBNTUzVklaL3cwOC9QaGlsbHlTaG9vdGluZ3MuZ2VvanNvbiIpKQ0KcGhpbGx5TmVpZ2hib3IgIDwtIHN0X3JlYWQoImh0dHBzOi8vcGVuZ2RzY2kuZ2l0aHViLmlvL1NUQTU1M1ZJWi93MDgvTmVpZ2hib3Job29kc19QaGlsYWRlbHBoaWEuZ2VvanNvbiIpDQpwaGlsbHkgIDwtIHN0X3JlYWQoImh0dHBzOi8vcGVuZ2RzY2kuZ2l0aHViLmlvL1NUQTU1M1ZJWi93MDgvUGhpbGx5TmVpZ2hib3Job29kLWJsb2Nrcy5nZW9qc29uIikNCmBgYA0KDQoNCiMjIFByZXBhcmluZyB0aGUgRGF0YQ0KDQpXaGlsZSB0aGUgZGF0YSBpcyB1c2FibGUsIHdlIGludGVuZCB0byBkaXNwbGF5IG11Y2ggbW9yZSBpbmZvcm1hdGlvbiB3aXRoIGVhY2ggcG9pbnQsIHNvIHdlIHdpbGwgcmVtb3ZlIHVubmVjZXNzYXJ5IG9yIHJlZHVuZGFudCBkYXRhLCB3aGlsZSBmaXhpbmcgYSBmb3JtYXR0aW5nIHF1aXJrIHdpdGggdGhlICdkYXRlXycgdmFyaWFibGUgKHdoaWNoIHdpbGwgYWxzbyBiZSByZW5hbWVkIHRvICdkYXRlJykgdXNpbmcgdGhlIGBzdHJpbmdyYCBwYWNrYWdlLiBXaXRob3V0IHRoaXMsIHRoZSAnZGF0ZV8nIHZhcmlhYmxlIGVuZHMgZWFjaCBlbnRyeSB3aXRoICcwMDowMDowMCswMCcsIHdoaWNoIGlzIGNsdW5reSBhbmQgbGlrZWx5IHVuaW50ZW5kZWQuDQoNCmBgYHtyfQ0KcGhpbGx5LmRhdGEyIDwtIHBoaWxseS5kYXRhICU+JQ0KICBzZWxlY3QoLWMoMSwgMiwgMywgNSwgNiwgMTcsIDE4KSkNCg0KbmFtZXMocGhpbGx5LmRhdGEyKVt3aGljaChuYW1lcyhwaGlsbHkuZGF0YTIpID09ICJkYXRlXyIpXSA8LSAiZGF0ZSINCg0KcGhpbGx5LmRhdGEyJGRhdGUgPC0gc3RyX3JlcGxhY2UocGhpbGx5LmRhdGEyJGRhdGUsICIgMDA6MDA6MDBcXCswMCIsICIiKQ0KDQojIENvbnZlcnQgJ2RhdGUnIHZhcmlhYmxlIHRvIERhdGUgZm9ybWF0IGZvciBwb3NzaWJsZSBmdXR1cmUgdXNlDQpwaGlsbHkuZGF0YTIkZGF0ZSA8LSBhcy5EYXRlKHBoaWxseS5kYXRhMiRkYXRlLCBmb3JtYXQgPSAiJVktJW0tJWQiKQ0KYGBgDQoNCg0KDQojIyBQcmVwYXJpbmcgVmlzdWFsaXphdGlvbiBmb3IgQWdncmVnYXRlIERhdGENCg0KDQpXaXRoIG91dCBtYXAsIHdlIHdvdWxkIGxpa2UgdG8gZmVhdHVyZSBpbmRpdmlkdWFsIGluY2lkZW50cyBhbmQgYWxsb3cgc29tZW9uZSB0byBjbGljayB0byBzZWUgdmljdGltIGRlbW9ncmFwaGljIGluZm9ybWF0aW9uLCBidXQgd2Ugd291bGQgYWxzbyBsaWtlIHRvIGhhdmUgJ2JpZyBwaWN0dXJlJyBhZ2dyZWdhdGUgcGxvdHMgYXZhaWxhYmxlIGFzIHdlbGwuIFdlIHdpbGwgcHJlcGFyZSB0aHJlZSBzaW1wbGUgcGxvdHMgaGVyZSB1c2luZyBgZ2dwbG90YCBmb3IgaWxsdXN0cmF0aXZlIHB1cnBvc2VzLg0KDQoNCmBgYHtyfQ0KDQojRGlzdHJpYnV0aW9uIG9mIEFnZSBhY3Jvc3MgUmFjZQ0KDQphci5wbG90PC1nZ3Bsb3QocGhpbGx5LmRhdGEyLCBhZXMoeCA9IGFnZSwgZmlsbCA9IHJhY2UpKSArDQogIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuNSkgKw0KICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBvZiBBZ2UgYnkgUmFjZSIsDQogICAgICAgeCA9ICJBZ2UiLA0KICAgICAgIHkgPSAiRGVuc2l0eSIpICsNCiAgc2NhbGVfZmlsbF9kaXNjcmV0ZShuYW1lID0gIlJhY2UiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQojUHJvYmFibHkgdW5uZWNlc3Nhcnkgc3RlcCwgYnV0IG1hZGUgYW4gZXh0cmEgZGF0YXNldCB3aGlsZSB0ZXN0aW5nIHNvbWV0aGluZyBvdXQNCnBoaWxseS5kYXRhMzwtc3Vic2V0KHBoaWxseS5kYXRhMikNCnBoaWxseS5kYXRhMyRmYXRhbCA8LSBmYWN0b3IocGhpbGx5LmRhdGEzJGZhdGFsLCBsZXZlbHMgPSBjKDAsIDEpLCBsYWJlbHMgPSBjKCJOb25mYXRhbCIsICJGYXRhbCIpKQ0KDQojIFBsb3QgdGhlIHBvaW50IGdyYXBoIHdpdGggY29sb3JlZCBwb2ludHMNCnllYXIucGxvdCA8LSBnZ3Bsb3QocGhpbGx5LmRhdGEzLCBhZXMoeCA9IHllYXIsIGZpbGwgPSBmYXRhbCkpICsNCiAgZ2VvbV9iYXIocG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSh3aWR0aCA9IDAuNSksIHN0YXQgPSAiY291bnQiKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIk5vbmZhdGFsIiA9ICJibHVlIiwgIkZhdGFsIiA9ICJyZWQiKSkgKw0KICBsYWJzKHRpdGxlID0gIk51bWJlciBvZiBGYXRhbCBhbmQgTm9uZmF0YWwgSW5jaWRlbnRzIGJ5IFllYXIiLA0KICAgICAgIHggPSAiWWVhciIsDQogICAgICAgeSA9ICJDb3VudCIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcShtaW4ocGhpbGx5LmRhdGEzJHllYXIpLCBtYXgocGhpbGx5LmRhdGEzJHllYXIpLCBieSA9IDEpKQ0KDQojRnJlcXVlbmN5IG9mIGluZG9vciB2cyBvdXRkb29yIGluY2lkZW50cw0KDQpvdXRzaWRlLnBsb3QgPC0gZ2dwbG90KHBoaWxseS5kYXRhMiwgYWVzKHggPSB5ZWFyLCBmaWxsID0gZmFjdG9yKG91dHNpZGUsIGxhYmVscyA9IGMoIkluZG9vciIsICJPdXRkb29yIikpKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImNvdW50IikgKw0KICBsYWJzKHRpdGxlID0gIk51bWJlciBvZiBJbmRvb3IgYW5kIE91dGRvb3IgSW5jaWRlbnRzIGJ5IFllYXIiLA0KICAgICAgIHggPSAiWWVhciIsDQogICAgICAgeSA9ICJDb3VudCIsDQogICAgICAgZmlsbCA9ICJMb2NhdGlvbiIpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiSW5kb29yIiA9ICJkYXJrb3JjaGlkIiwgIk91dGRvb3IiID0gImRhcmtncmVlbiIpKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEobWluKHBoaWxseS5kYXRhMiR5ZWFyKSwgbWF4KHBoaWxseS5kYXRhMiR5ZWFyKSwgYnkgPSAxKSkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KYGBgDQoNCldlIGhhdmUgZXhwb3J0ZWQgYW5kIHVwbG9hZGVkIHRoZXNlIGltYWdlcyBzZXBhcmF0ZWx5IGFuZCB3aWxsIG5vdyByZWRlZmluZSB0aGVtOg0KDQpgYGB7cn0NCg0KYXI9Imh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9BbGV4RHJhZ29uZXR0aS9TVEE1NTMvbWFpbi9odzgvYXJwbG90LnBuZyINCg0Kb3V0PSJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vQWxleERyYWdvbmV0dGkvU1RBNTUzL21haW4vaHc4L291dHNpZGVwbG90Mi5wbmciDQoNCnlyPSJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vQWxleERyYWdvbmV0dGkvU1RBNTUzL21haW4vaHc4L3llYXJwbG90Mi5wbmciDQoNCmBgYA0KDQoNCiMjIE1hcHBpbmcgdGhlIERhdGENCg0KDQpPdXIgZmluYWwgdmlzdWFsaXphdGlvbiB3aWxsIGNvbnRhaW4gYSBtYXAgb2Ygc2hvb3RpbmcgdmljdGltcywgZnVsbHkgaW50ZXJhY3RpdmUgd2l0aCBkZW1vZ3JhcGhpYyBpbmZvcm1hdGlvbiwgYXMgd2VsbCBhcyBhZGRpdGlvbmFsLCBiaWctcGljdHVyZSBkYXRhIGF2YWlsYWJsZS4NCg0KDQpgYGB7cn0NCiNkZWZpbmluZyB0aGluZ3MNCnBhbCA8LSBjb2xvckZhY3RvcihjKCJibHVlIiwgInJlZCIpLCBkb21haW4gPSBjKDAsIDEpKQ0KDQoNCmFnZXJhY2VwbG90ID0gc3RfYXNfc2YoZGF0YS5mcmFtZSh4ID0gLTc1LjQwNzcsIHkgPSAzOS45MTY4KSwNCiAgICAgICAgICAgICAgICBjb29yZHMgPSBjKCJ4IiwgInkiKSwNCiAgICAgICAgICAgICAgICBjcnMgPSA0MzI2KQ0KeWVhcnBsb3QgPSBzdF9hc19zZihkYXRhLmZyYW1lKHggPSAtNzUuMzg3NywgeSA9IDM5LjkxNjgpLA0KICAgICAgICAgICAgICAgIGNvb3JkcyA9IGMoIngiLCAieSIpLA0KICAgICAgICAgICAgICAgIGNycyA9IDQzMjYpDQpvdXRkb29ycGxvdCA9IHN0X2FzX3NmKGRhdGEuZnJhbWUoeCA9IC03NS4zNjc3LCB5ID0gMzkuOTE2OCksDQogICAgICAgICAgICAgICAgY29vcmRzID0gYygieCIsICJ5IiksDQogICAgICAgICAgICAgICAgY3JzID0gNDMyNikNCmBgYA0KDQpgYGB7cn0NCmZpZyA8LSBwbG90X2x5KHBoaWxseS5kYXRhMiwgeCA9IH5sbmcsIHkgPSB+bGF0LCANCiAgICAgICAgICAgICAgIHR5cGUgPSAnc2NhdHRlcicsIA0KICAgICAgICAgICAgICAgbW9kZSA9ICdtYXJrZXJzJywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXJrZXIgPSBsaXN0KHN5bWJvbCA9ICdjaXJjbGUnLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemVtb2RlID0gJ2RpYW1ldGVyJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaW5lID0gbGlzdCh3aWR0aCA9IDIsIGNvbG9yID0gJyNGRkZGRkYnKSkpDQogICAgICAgICAgICAgIA0KdGFnLm1hcC50aXRsZSA8LSB0YWdzJHN0eWxlKEhUTUwoIg0KICAgICAgICAgICAgICAgLmxlYWZsZXQtY29udHJvbC5tYXAtdGl0bGUgew0KICAgICAgICAgICAgICAgICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlKDUwJSw1MCUpOw0KICAgICAgICAgICAgICAgICAgIHBvc2l0aW9uOiBmaXhlZCAhaW1wb3J0YW50Ow0KICAgICAgICAgICAgICAgICAgIGxlZnQ6IDUwJTsNCiAgICAgICAgICAgICAgICAgICB0ZXh0LWFsaWduOiBjZW50ZXI7DQogICAgICAgICAgICAgICAgICAgcGFkZGluZy1sZWZ0OiAxMHB4Ow0KICAgICAgICAgICAgICAgICAgIHBhZGRpbmctcmlnaHQ6IDEwcHg7DQogICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZDogdHJhbnNwYXJlbnQ7DQogICAgICAgICAgICAgICAgICAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogICAgICAgICAgICAgICAgICAgZm9udC1zaXplOiAxOHB4O30NCiAgICAgICAgICAgICAgICAgIikpDQoNCnJyIDwtIHRhZ3MkZGl2KA0KICAgSFRNTCgnPGltZyBib3JkZXI9IjAiIGFsdD0iSW1hZ2VUaXRsZSIgc3JjPSJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vQWxleERyYWdvbmV0dGkvU1RBNTUzL21haW4vaHc4L21hcCUyMHRpdGxlLnBuZyIgd2lkdGg9IjIwMCIgaGVpZ2h0PSI0NSI+JykNCiApIA0KDQojIyMgDQpsZWFmbGV0KCkgJT4lDQogIHNldFZpZXcobG5nPS03NS4xOTA0MjksIGxhdD00MC4wMDM5LCB6b29tID0gMTAuNSkgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuRGFya01hdHRlciwgZ3JvdXA9IkRhcmsiKSAlPiUNCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5EYXJrTWF0dGVyTm9MYWJlbHMsIGdyb3VwPSJEYXJrTGFiZWwiKSAlPiUgIA0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRFc3JpLk5hdEdlb1dvcmxkTWFwLCBncm91cD0iRXNyaSIpICU+JQ0KICBhZGRDb250cm9sKHJyLCBwb3NpdGlvbiA9ICJ0b3BsZWZ0IiwgY2xhc3NOYW1lPSJtYXAtdGl0bGUiKSAlPiUNCiAgIyMgbWluaSByZWZlcmVuY2UgbWFwDQogIGFkZE1pbmlNYXAoKSAlPiUNCiAgIyMgbmVpZ2hib3Job29kIGJvdW5kYXJ5DQogIGFkZFBvbHlnb25zKGRhdGEgPSBwaGlsbHlOZWlnaGJvciwNCiAgICAgICAgICAgICAgY29sb3IgPSAnc2t5Ymx1ZScsDQogICAgICAgICAgICAgIHdlaWdodCA9IDEpICAlPiUNCiAgDQogICAgDQogIGFkZENpcmNsZU1hcmtlcnMoZGF0YSA9IGFnZXJhY2VwbG90LCANCiAgICAgICAgICAgICAgICAgICBjb2xvciA9ICJ3aGl0ZSIsDQogICAgICAgICAgICAgICAgICAgd2VpZ2h0ID0gMiwNCiAgICAgICAgICAgICAgICAgICBsYWJlbCA9ICJEaXN0cmlidXRpb24gb2YgQWdlIGJ5IFJhY2UiLA0KICAgICAgICAgICAgICAgICAgIHN0cm9rZSA9IEZBTFNFLCANCiAgICAgICAgICAgICAgICAgICBmaWxsT3BhY2l0eSA9IDAuOTUsDQogICAgICAgICAgICAgICAgICAgZ3JvdXAgPSAiYWdlcmFjZXBsb3QiKSAlPiUNCiAgYWRkUG9wdXBJbWFnZXMoYXIsIA0KICAgICAgICAgICAgICAgICAgd2lkdGggPSA1MDAsDQogICAgICAgICAgICAgICAgICBoZWlnaHQgPSAzMjAsDQogICAgICAgICAgICAgICAgICB0b29sdGlwID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICBncm91cCA9ICJhZ2VyYWNlcGxvdCIpICU+JQ0KICAgIA0KIGFkZENpcmNsZU1hcmtlcnMoZGF0YSA9IHllYXJwbG90LCANCiAgICAgICAgICAgICAgICAgICBjb2xvciA9ICJza3libHVlIiwNCiAgICAgICAgICAgICAgICAgICB3ZWlnaHQgPSAyLA0KICAgICAgICAgICAgICAgICAgIGxhYmVsID0gIkluY2lkZW50IFJhdGUgYnkgWWVhciIsDQogICAgICAgICAgICAgICAgICAgc3Ryb2tlID0gRkFMU0UsIA0KICAgICAgICAgICAgICAgICAgIGZpbGxPcGFjaXR5ID0gMC45NSwNCiAgICAgICAgICAgICAgICAgICBncm91cCA9ICJ5ZWFycGxvdCIpICU+JQ0KICBhZGRQb3B1cEltYWdlcyh5ciwgDQogICAgICAgICAgICAgICAgICB3aWR0aCA9IDUwMCwNCiAgICAgICAgICAgICAgICAgIGhlaWdodCA9IDMyMCwNCiAgICAgICAgICAgICAgICAgIHRvb2x0aXAgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgIGdyb3VwID0gInllYXJwbG90IikgJT4lDQoNCiAgDQogIGFkZENpcmNsZU1hcmtlcnMoZGF0YSA9IG91dGRvb3JwbG90LCANCiAgICAgICAgICAgICAgICAgICBjb2xvciA9ICJkYXJrZ3JlZW4iLA0KICAgICAgICAgICAgICAgICAgIHdlaWdodCA9IDIsDQogICAgICAgICAgICAgICAgICAgbGFiZWwgPSAiUmF0ZSBvZiBJbmRvb3IgdnMgT3V0ZG9vciBJbmNpZGVudHMiLA0KICAgICAgICAgICAgICAgICAgIHN0cm9rZSA9IEZBTFNFLCANCiAgICAgICAgICAgICAgICAgICBmaWxsT3BhY2l0eSA9IDAuOTUsDQogICAgICAgICAgICAgICAgICAgZ3JvdXAgPSAib3V0ZG9vcnBsb3QiKSAlPiUNCiAgYWRkUG9wdXBJbWFnZXMob3V0LCANCiAgICAgICAgICAgICAgICAgICB3aWR0aCA9IDUwMCwNCiAgICAgICAgICAgICAgICAgIGhlaWdodCA9IDMyMCwNCiAgICAgICAgICAgICAgICAgICBncm91cCA9ICJvdXRkb29ycGxvdCIgKSAlPiUNCg0KDQogDQogIA0KICAjIyBwbG90IGluZm9ybWF0aW9uIG9uIHRoZSBtYXANCiAgYWRkQ2lyY2xlTWFya2VycyhkYXRhID0gcGhpbGx5LmRhdGEyLA0KICAgICAgICAgICAgICAgICAgIGNvbG9yID0gfnBhbChhcy5mYWN0b3IoZmF0YWwpKSwNCiAgICAgICAgICAgICAgICAgICBzdHJva2UgPSBGQUxTRSwgDQogICAgICAgICAgICAgICAgICAgZmlsbE9wYWNpdHkgPSAwLjUsDQogICAgICAgICAgICAgICAgICAgcG9wdXAgPSB+cG9wdXBUYWJsZShwaGlsbHkuZGF0YTIpLA0KICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJPcHRpb25zID0gbWFya2VyQ2x1c3Rlck9wdGlvbnMobWF4Q2x1c3RlclJhZGl1cyA9IDQwKSkgJT4lDQoNCiAgDQogICAgICAgICAgYWRkTGF5ZXJzQ29udHJvbChiYXNlR3JvdXBzID0gYygnRGFyaycsICdEYXJrTGFiZWwnLCAnRXNyaScpLA0KICAgICAgICAgICAgICAgICAgIG92ZXJsYXlHcm91cHMgPSBjKCJDcmltZSBEYXRhIiksDQogICAgICAgICAgICAgICAgICAgb3B0aW9ucyA9IGxheWVyc0NvbnRyb2xPcHRpb25zKGNvbGxhcHNlZCA9IFRSVUUpKSAlPiUNCiAgIyMNCiAgYnJvd3NhYmxlKCkNCg0KYGBgDQoNCg0KDQpUaGUgYWJvdmUgbWFwIGlzIGZ1bGx5IGludGVyYWN0aXZlLCBtYWludGFpbnMgb3VyIHJlZC9ibHVlIGZhdGFsL25vbmZhdGFsIGNvbG9yIGNvZGluZyBmcm9tIHRoZSBwcmV2aW91cyBleGFtcGxlLCBidXQgYWxsb3dzIHVzIHRvIHByb3ZpZGUgbXVjaCBtb3JlIGluZm9ybWF0aW9uIGZvciBlYWNoIGluY2lkZW50LCBhcyB3ZWxsIGFzIGluY2x1ZGUgYWdncmVnYXRlIGRhdGEgdmlzdWFsaXphdGlvbnMgKGJ5IGNsaWNraW5nIG9uIHRoZSBkb3RzIGJsb2NraW5nIG91dCBNZWRpYSBvbiB0aGUgbWFwKS4NCg0KDQoNCiMgTWFwIDQ6IE1hcHBpbmcgRGF0YSB3aXRoIFRhYmxlYXUNCg0KDQojIyBPdmVydmlldw0KDQpPdXIgZGF0YXNldCBjb250YWlucyBwcmVzaWRlbnRpYWwgZWxlY3Rpb24gZGF0YSBmb3IgZWFjaCBjb3VudHkgaW4gdGhlIGNvbnRpbmVudGFsIFVuaXRlZCBTdGF0ZXMuIE91ciBnb2FsIGlzIHRvIGNyZWF0ZSBhbiBpbnRlcmFjdGl2ZSBtYXAgaW4gVGFibGVhdSB0aGF0IGRpc3BsYXlzIGNvdW50eS1sZXZlbCBpbmZvcm1hdGlvbiBmb3IgZWFjaCBwcmVzaWRlbnRpYWwgZWxlY3Rpb24gZnJvbSAyMDAwLTIwMjAuIEZpcnN0LCB3ZSBtdXN0IGltcG9ydCB0aGUgZGF0YS4gV2UgaGF2ZSBiZWVuIGluc3RydWN0ZWQgdG8gb25seSBjb25zaWRlciB0aGUgdHdvIG1ham9yIEFtZXJpY2FuIHBvbGl0aWNhbCBwYXJ0aWVzLCBSZXB1YmxpY2FuIGFuZCBEZW1vY3JhdCwgZm9yIHRoaXMgdmlzdWFsaXphdGlvbi4NCg0KDQpgYGB7cn0NCg0KIyNWb3RpbmcgRGF0YQ0Kdm90ZTwtcmVhZC5jc3YoImh0dHBzOi8vcGVuZ2RzY2kuZ2l0aHViLmlvL2RhdGFzZXRzL2NvdW50eXByZXNpZGVudGlhbF9lbGVjdGlvbl8yMDAwLTIwMjAuY3N2IikNCmZpcHM8LXJlYWQuY3N2KCJodHRwczovL3Blbmdkc2NpLmdpdGh1Yi5pby9kYXRhc2V0cy9maXBzMmdlb2NvZGUuY3N2IikNCg0KY29sbmFtZXMoZmlwcylbMV08LSJjb3VudHlfZmlwcyINCg0KIyNzdWJzZXQgZGF0YQ0Kdm90ZS5taW4gPC0gc3Vic2V0KHZvdGUsIHBhcnR5ICVpbiUgYygiUkVQVUJMSUNBTiIsICJERU1PQ1JBVCIpLCANCiAgICAgICAgICAgICAgICAgICAgc2VsZWN0ID0gYygic3RhdGVfcG8iLCAiY291bnR5X25hbWUiLCAiY2FuZGlkYXRlIiwgImNvdW50eV9maXBzIiwgInBhcnR5IiwgImNhbmRpZGF0ZXZvdGVzIiwgInllYXIiKSkNCmBgYA0KDQoNCiMjIFByZXBhcmluZyB0aGUgRGF0YQ0KDQpVc2luZyBgZHBseXJgIGFuZCBgdGlkeXJgIHBhY2thZ2VzLCB3ZSBhcmU6DQorIENvdW50aW5nIGFsbCB2b3RlcyBpbiBhIGdpdmVuIGNvdW50eSBhbmQgeWVhciAtIGJlaW5nIG1pbmRmdWwgb2Ygc3RhdGUsIGJlY2F1c2UgbXVsdGlwbGUgY291bnRpZXMgaW4gZGlmZmVyZW50IHN0YXRlcyBzaGFyZSBhIG5hbWUNCisgUmVtb3ZpbmcgdGhlIGxvc2luZyBjYW5kaWRhdGUncyBkYXRhIGZvciBhIGdpdmVuIGNvdW50eSBhbmQgeWVhciAtIHN0aWxsIG1pbmRmdWwgb2Ygc3RhdGUNCisgQ3JlYXRpbmcgYSB2YXJpYWJsZSBmb3IgdGhlIHdpbm5pbmcgY2FuZGlkYXRlJ3MgcGVyY2VudGFnZSBvZiB0aGUgdm90ZQ0KKyBNZXJnaW5nIG91ciBmaW5pc2hlZCBkYXRhc2V0IHdpdGggdGhlIGRhdGEgYWJvdXQgZWFjaCBjb3VudHkNCg0KDQpgYGB7cn0NCnRvdGFsX3ZvdGVzIDwtIHZvdGUubWluICU+JQ0KICBncm91cF9ieShzdGF0ZV9wbywgY291bnR5X25hbWUsIHllYXIpICU+JQ0KICBzdW1tYXJpc2UodG90YWxfdm90ZXMgPSBzdW0oY2FuZGlkYXRldm90ZXMpKQ0KDQoNCndpbm5pbmdfcGFydHlfZGF0YTIgPC0gdm90ZS5taW4gJT4lDQogIGdyb3VwX2J5KHN0YXRlX3BvLCBjb3VudHlfbmFtZSwgeWVhcikgJT4lDQogIGZpbHRlcihjYW5kaWRhdGV2b3RlcyA9PSBtYXgoY2FuZGlkYXRldm90ZXMpKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBsZWZ0X2pvaW4odG90YWxfdm90ZXMsIGJ5ID0gYygic3RhdGVfcG8iLCAiY291bnR5X25hbWUiLCAieWVhciIpKSAlPiUNCiAgbXV0YXRlKHdpbm5pbmdfcGVyY2VudGFnZSA9IGNhbmRpZGF0ZXZvdGVzIC8gdG90YWxfdm90ZXMgKiAxMDApDQoNCg0Kdm90ZTMubWVyZ2UgPC0gbWVyZ2Uod2lubmluZ19wYXJ0eV9kYXRhMiwgZmlwcywgYnkgPSAiY291bnR5X2ZpcHMiLCBhbGwueCA9IFRSVUUpDQoNCmBgYA0KDQoNCiMjIE1hcHBpbmcgdGhlIERhdGEgd2l0aCBUYWJsZWF1DQoNClRhYmxlYXUgaXMgdmVyeSBpbnR1aXRpdmUgYW5kIGFsbG93cyBvbmUgdG8gbWFwIGRhdGEgYnkgY2xpY2tpbmcgYW5kIGRyYWdnaW5nIGRlc2lyZWQgdmFyaWFibGVzIHRvIGNlcnRhaW4gZnVuY3Rpb24gKGllLCBkaXNwbGF5aW5nIHRoZSBXaW5uaW5nIFBhcnR5IHZhcmlhYmxlIGFzICdjb2xvcicpLiBUaGUgZG93bnNpZGUgb2YgdGhpcyBiZWluZzogdGhlcmUgaXMgbm8gY29kZSB0byBzaGFyZSENCg0KVGhlIFRhYmxlYXUgbWFwIGlzIGVtYmVkZGVkIGJlbG93LiBJdCBpcyBpbnRlcmFjdGl2ZSBhcyB3ZWxsIC0gbW91c2luZyBvdmVyIGEgY291bnR5IHNob3VsZCBnaXZlIHlvdSBpdHMgZWxlY3Rpb24gaW5mb3JtYXRpb24uIFRoZSB5ZWFyIGNhbiBiZSBhZGp1c3RlZCB0byBzZWUgZGF0YSBmcm9tIGVhY2ggcHJlc2lkZW50aWFsIGVsZWN0aW9uIGZyb20gMjAwMCB0byAyMDIwLiAgDQoNCg0KDQo8dGFibGUgYm9yZGVyID0gMCBib3JkZXJjb2xvcj0iZGFya2dyZWVuIiBiZ2NvbG9yPScjZjZmNmY2JyAgd2lkdGg9MTAwJSAgYWxpZ24gPSBjZW50ZXI+DQo8dHI+DQo8dGQ+DQoNCg0KDQo8ZGl2IGNsYXNzPSd0YWJsZWF1UGxhY2Vob2xkZXInIGlkPSd2aXoxNzEyODcyOTI2OTU0JyBzdHlsZT0ncG9zaXRpb246IHJlbGF0aXZlJz4NCjxub3NjcmlwdD4NCjxhIGhyZWY9JyMnPg0KPGltZyBhbHQ9J0NvdW50eS1MZXZlbCBQcmVzaWRlbnRpYWwgRWxlY3Rpb24gUmVzdWx0cywgMjAwMC0yMDIwICcgc3JjPSdodHRwczomIzQ3OyYjNDc7cHVibGljLnRhYmxlYXUuY29tJiM0NztzdGF0aWMmIzQ3O2ltYWdlcyYjNDc7dm8mIzQ3O3ZvdGluZ21hcDIwMDAtMjAyMCYjNDc7U2hlZXQxJiM0NzsxX3Jzcy5wbmcnIHN0eWxlPSdib3JkZXI6IG5vbmUnIC8+DQo8L2E+DQo8L25vc2NyaXB0Pg0KPG9iamVjdCBjbGFzcz0ndGFibGVhdVZpeicgIHN0eWxlPSdkaXNwbGF5Om5vbmU7Jz48cGFyYW0gbmFtZT0naG9zdF91cmwnIHZhbHVlPSdodHRwcyUzQSUyRiUyRnB1YmxpYy50YWJsZWF1LmNvbSUyRicgLz4gDQo8cGFyYW0gbmFtZT0nZW1iZWRfY29kZV92ZXJzaW9uJyB2YWx1ZT0nMycgLz4gDQo8cGFyYW0gbmFtZT0nc2l0ZV9yb290JyB2YWx1ZT0nJyAvPjxwYXJhbSBuYW1lPSduYW1lJyB2YWx1ZT0ndm90aW5nbWFwMjAwMC0yMDIwJiM0NztTaGVldDEnIC8+PHBhcmFtIG5hbWU9J3RhYnMnIHZhbHVlPSdubycgLz4NCjxwYXJhbSBuYW1lPSd0b29sYmFyJyB2YWx1ZT0neWVzJyAvPg0KPHBhcmFtIG5hbWU9J3N0YXRpY19pbWFnZScgdmFsdWU9J2h0dHBzOiYjNDc7JiM0NztwdWJsaWMudGFibGVhdS5jb20mIzQ3O3N0YXRpYyYjNDc7aW1hZ2VzJiM0Nzt2byYjNDc7dm90aW5nbWFwMjAwMC0yMDIwJiM0NztTaGVldDEmIzQ3OzEucG5nJyAvPiANCjxwYXJhbSBuYW1lPSdhbmltYXRlX3RyYW5zaXRpb24nIHZhbHVlPSd5ZXMnIC8+DQo8cGFyYW0gbmFtZT0nZGlzcGxheV9zdGF0aWNfaW1hZ2UnIHZhbHVlPSd5ZXMnIC8+DQo8cGFyYW0gbmFtZT0nZGlzcGxheV9zcGlubmVyJyB2YWx1ZT0neWVzJyAvPg0KPHBhcmFtIG5hbWU9J2Rpc3BsYXlfb3ZlcmxheScgdmFsdWU9J3llcycgLz4NCjxwYXJhbSBuYW1lPSdkaXNwbGF5X2NvdW50JyB2YWx1ZT0neWVzJyAvPg0KPHBhcmFtIG5hbWU9J2xhbmd1YWdlJyB2YWx1ZT0nZW4tVVMnIC8+DQo8cGFyYW0gbmFtZT0nZmlsdGVyJyB2YWx1ZT0ncHVibGlzaD15ZXMnIC8+DQo8L29iamVjdD4NCjwvZGl2PiAgICAgICAgICAgICAgIA0KPHNjcmlwdCB0eXBlPSd0ZXh0L2phdmFzY3JpcHQnPiAgDQp2YXIgZGl2RWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd2aXoxNzEyODcyOTI2OTU0Jyk7ICAgICAgICAgICAgICAgICAgICB2YXIgdml6RWxlbWVudCA9IGRpdkVsZW1lbnQuZ2V0RWxlbWVudHNCeVRhZ05hbWUoJ29iamVjdCcpWzBdOyAgICAgICAgICAgICAgICAgICAgdml6RWxlbWVudC5zdHlsZS53aWR0aD0nMTAwJSc7dml6RWxlbWVudC5zdHlsZS5oZWlnaHQ9KGRpdkVsZW1lbnQub2Zmc2V0V2lkdGgqMC43NSkrJ3B4JzsgICAgICAgICAgICAgICAgICAgIHZhciBzY3JpcHRFbGVtZW50ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc2NyaXB0Jyk7ICAgICAgICAgICAgICAgICAgICBzY3JpcHRFbGVtZW50LnNyYyA9ICdodHRwczovL3B1YmxpYy50YWJsZWF1LmNvbS9qYXZhc2NyaXB0cy9hcGkvdml6X3YxLmpzJzsgICAgICAgICAgICAgICAgICAgIHZpekVsZW1lbnQucGFyZW50Tm9kZS5pbnNlcnRCZWZvcmUoc2NyaXB0RWxlbWVudCwgdml6RWxlbWVudCk7ICAgICAgICAgICAgICAgIDwvc2NyaXB0Pg0KDQo8L3RkPg0KPC90cj4NCjwvdGFibGU+