1 Introduction

This code showcases the utilization of various R libraries, including ggplot2, dplyr, kable, kableExtra, scales, and plotly, to analyze and visualize air quality surveillance data from New York City. The dataset contains comprehensive information on air quality data from New York City. With exposures to common air pollutants linked to various health issues, understanding air quality variations across NYC neighborhoods is crucial.

1.1 Code-Through Highlights & Learning Goals

This code-through tutorial covers various aspects of data manipulation and visualization in R. Throughout the semester, we have been working with census data and choropleth maps, unraveling spatial trends as part of our coursework. Recently, I stumbled upon the plotly library, renowned for its prowess in crafting plots based on geographical data. Captivated by its potential, I’ve chosen to harness coordinates and geographical data for this code-through assignment as well. This assignment will showcase the utilization of geographical data and the plotting capabilities of plotly, offering insights into creating scatter plots on maps in R for spatial data analysis.

It also covers the versatile features of the kableExtra package, with a specific focus on kable_styling to elevate table formatting. Additionally, it provides a detailed exploration of ggplot2, demonstrating its adaptability through diverse plotting methods and visualizations. Furthermore, the code-through examines the robust data manipulation functionalities offered by the dplyr package, highlighting essential functions like filter, group_by, reframe, and arrange.

In general, this code-through assignment aims to provide sample usages of indispensable tools and techniques for data analysis and visualization in R, through clear explanations and practical examples.



2 Data Setup

First, we need to import/set up the data first. The required packages and libraries are listed in the below code. The data is sourced from https://data.cityofnewyork.us/Environment/Air-Quality/c3uy-2p5r/about_data.fromJSON function from jsonlite library is utilized for importing the JSON data to the data frame dat.

# SET GLOBAL KNITR OPTIONS

knitr::opts_chunk$set(echo = TRUE, 
                      message = FALSE, 
                      warning = FALSE, 
                      fig.width = 10, 
                      fig.height = 8)



# LOAD PACKAGES

library(pander)
library(kableExtra)
library(dplyr)
library(ggplot2)
library(knitr)
library(jsonlite)
library(scales)
library(plotly)

# READ IN DATA

url <- paste("https://data.cityofnewyork.us/resource/c3uy-2p5r.json")

dat <- fromJSON(url)



2.1 A basic example using “kableExtra”

  • The below example shows

    • how to utilize the kableExtra library to apply the kable_styling function. In this specific example, 10 lines of data will be displayed, allowing you to hover over each row.
kable_table <- dat %>% slice(1:10)

kable(kable_table) %>%
  kable_styling(bootstrap_options = c("hover", "condensed"))
unique_id indicator_id name measure measure_info geo_type_name geo_join_id geo_place_name time_period start_date data_value
825967 375 Nitrogen dioxide (NO2) Mean ppb UHF34 104 Pelham - Throgs Neck Summer 2022 2022-06-01T00:00:00.000 12.0
823492 365 Fine particles (PM 2.5) Mean mcg/m3 CD 307 Sunset Park (CD7) Summer 2022 2022-06-01T00:00:00.000 6.7
827012 386 Ozone (O3) Mean ppb CD 313 Coney Island (CD13) Summer 2022 2022-06-01T00:00:00.000 37.7
827081 386 Ozone (O3) Mean ppb UHF34 103 Fordham - Bronx Pk Summer 2022 2022-06-01T00:00:00.000 31.7
827103 386 Ozone (O3) Mean ppb UHF42 503 Willowbrook Summer 2022 2022-06-01T00:00:00.000 34.8
823211 365 Fine particles (PM 2.5) Mean mcg/m3 CD 105 Midtown (CD5) Summer 2022 2022-06-01T00:00:00.000 8.7
823241 365 Fine particles (PM 2.5) Mean mcg/m3 UHF42 401 Long Island City - Astoria Summer 2022 2022-06-01T00:00:00.000 7.2
825903 375 Nitrogen dioxide (NO2) Mean ppb UHF34 303 East Harlem Summer 2022 2022-06-01T00:00:00.000 13.0
823337 365 Fine particles (PM 2.5) Mean mcg/m3 Borough 2 Brooklyn Summer 2022 2022-06-01T00:00:00.000 6.3
827065 386 Ozone (O3) Mean ppb UHF34 304 Upper West Side Summer 2022 2022-06-01T00:00:00.000 29.9


2.2 Plotting the pie chart with ggplot for the distribution of pollutants affecting Air Quality

  • This code calculates the distribution of pollutants and converts the data values to numeric.
  • Then, it computes the percentage for each category.
  • Finally, it creates a pie chart showing the distribution of pollutants affecting Air Quality in NYC, with each category represented as a slice of the pie.
  • Labels indicating the percentage of each type are placed within the slices, and the chart is styled with a title, legend, and custom themes.
# calculate total count for each name of pollutant and convert data_value to numeric
name_counts <- aggregate(data_value ~ name, data = dat, FUN = function(x) sum(as.numeric(x)))

# calculate percentage for each type
name_counts$percentage <- (name_counts$data_value / sum(name_counts$data_value)) * 100

# create pie chart with percentages
ggplot(name_counts, aes(x = "", y = data_value, fill = name)) +
  geom_bar(stat = "identity", width = 1, color = "black", size = 0.3) +
  geom_text(aes(label = percent(percentage / 100), y = data_value + 0.5), 
            position = position_stack(vjust = 0.5), size = 5, color = "white", fontface = "bold") +
  coord_polar("y", start = 0) +
  labs(title = "Distrubution of pollutants affecting Air Quality in NYC",
       fill = "Types of Pollutants",
       x = NULL, y = NULL) +
  theme_void() +
  theme(legend.position = "right",
        plot.title = element_text(size = 20, face = "bold", hjust = 0.5),
        legend.text = element_text(size = 12),
        legend.title = element_text(size = 14),
        legend.key.size = unit(1.5, "lines"))

2.3 Advanced Example of utilizing “dplyr” with “kable”

  • In this example, the dataframe is filtered to include only rows where the geo_type_name is Borough. Then, the data is grouped by several columns including unique_id, name, data_value, and geo_type_name. The column geo_place_name is renamed to Location_Name using the reframe function. Finally, the data is arranged in descending order based on Location_Name.

  • The split function divides data_borough_summary into separate tables based on the values of Location_Name (borough names). Each resulting table contains data specific to a particular borough.

  • A loop iterates over each borough. For each borough, a header indicating the borough name is printed using cat. Then, the corresponding table (stored in borough_tables[[borough]]) is printed using pander.

  • Finally, the result is a series of tables, each displaying summarized data values for a specific borough. Each table is preceded by a header indicating the corresponding borough name. This approach provides a structured presentation of air quality data values for individual boroughs, facilitating analysis and interpretation.

# filter data for boroughs and summarize(reframe) data values
data_borough_summary <- dat %>%
  filter(geo_type_name == "Borough") %>%  
  group_by(ID = unique_id, Name_Of_Pollutants = name, Data_Value = data_value, Type_Of_Location = geo_type_name) %>%
  reframe(Location_Name = geo_place_name) %>%
  arrange(desc(Location_Name))

# create separate tables for each borough
borough_tables <- split(data_borough_summary, f = data_borough_summary$Location_Name)

# print each table using kable
for (borough in names(borough_tables)) {
  cat(paste("**", borough, "**", "\n"))
  print(kable(borough_tables[[borough]]), format = "markdown")
  cat("\n\n") 
 
}
## ** Bronx ** 
## 
## 
## |ID     |Name_Of_Pollutants      |Data_Value |Type_Of_Location |Location_Name |
## |:------|:-----------------------|:----------|:----------------|:-------------|
## |823339 |Fine particles (PM 2.5) |6.1        |Borough          |Bronx         |
## |823340 |Fine particles (PM 2.5) |7.1        |Borough          |Bronx         |
## |823341 |Fine particles (PM 2.5) |7.3        |Borough          |Bronx         |
## |825810 |Nitrogen dioxide (NO2)  |16.0       |Borough          |Bronx         |
## |825811 |Nitrogen dioxide (NO2)  |12.2       |Borough          |Bronx         |
## |825812 |Nitrogen dioxide (NO2)  |21.8       |Borough          |Bronx         |
## |827148 |Ozone (O3)              |32.2       |Borough          |Bronx         |
## 
## 
## ** Brooklyn ** 
## 
## 
## |ID     |Name_Of_Pollutants      |Data_Value |Type_Of_Location |Location_Name |
## |:------|:-----------------------|:----------|:----------------|:-------------|
## |823336 |Fine particles (PM 2.5) |5.8        |Borough          |Brooklyn      |
## |823337 |Fine particles (PM 2.5) |6.3        |Borough          |Brooklyn      |
## |823338 |Fine particles (PM 2.5) |6.9        |Borough          |Brooklyn      |
## |825807 |Nitrogen dioxide (NO2)  |15.4       |Borough          |Brooklyn      |
## |825808 |Nitrogen dioxide (NO2)  |10.7       |Borough          |Brooklyn      |
## |825809 |Nitrogen dioxide (NO2)  |21.2       |Borough          |Brooklyn      |
## |827147 |Ozone (O3)              |34.7       |Borough          |Brooklyn      |
## 
## 
## ** Manhattan ** 
## 
## 
## |ID     |Name_Of_Pollutants      |Data_Value |Type_Of_Location |Location_Name |
## |:------|:-----------------------|:----------|:----------------|:-------------|
## |740885 |Nitrogen dioxide (NO2)  |16.4       |Borough          |Manhattan     |
## |823333 |Fine particles (PM 2.5) |7.0        |Borough          |Manhattan     |
## |823334 |Fine particles (PM 2.5) |7.5        |Borough          |Manhattan     |
## |823335 |Fine particles (PM 2.5) |7.9        |Borough          |Manhattan     |
## |825804 |Nitrogen dioxide (NO2)  |19.1       |Borough          |Manhattan     |
## |825805 |Nitrogen dioxide (NO2)  |15.4       |Borough          |Manhattan     |
## |825806 |Nitrogen dioxide (NO2)  |23.4       |Borough          |Manhattan     |
## |827146 |Ozone (O3)              |30.2       |Borough          |Manhattan     |
## 
## 
## ** Queens ** 
## 
## 
## |ID     |Name_Of_Pollutants      |Data_Value |Type_Of_Location |Location_Name |
## |:------|:-----------------------|:----------|:----------------|:-------------|
## |743728 |Ozone (O3)              |30.9       |Borough          |Queens        |
## |823330 |Fine particles (PM 2.5) |5.7        |Borough          |Queens        |
## |823331 |Fine particles (PM 2.5) |6.4        |Borough          |Queens        |
## |823332 |Fine particles (PM 2.5) |6.7        |Borough          |Queens        |
## |825801 |Nitrogen dioxide (NO2)  |14.9       |Borough          |Queens        |
## |825802 |Nitrogen dioxide (NO2)  |10.6       |Borough          |Queens        |
## |825803 |Nitrogen dioxide (NO2)  |20.1       |Borough          |Queens        |
## |827145 |Ozone (O3)              |34.5       |Borough          |Queens        |
## 
## 
## ** Staten Island ** 
## 
## 
## |ID     |Name_Of_Pollutants      |Data_Value |Type_Of_Location |Location_Name |
## |:------|:-----------------------|:----------|:----------------|:-------------|
## |823327 |Fine particles (PM 2.5) |5.2        |Borough          |Staten Island |
## |823328 |Fine particles (PM 2.5) |5.8        |Borough          |Staten Island |
## |823329 |Fine particles (PM 2.5) |6.1        |Borough          |Staten Island |
## |825798 |Nitrogen dioxide (NO2)  |11.2       |Borough          |Staten Island |
## |825799 |Nitrogen dioxide (NO2)  |7.8        |Borough          |Staten Island |
## |825800 |Nitrogen dioxide (NO2)  |16.4       |Borough          |Staten Island |
## |827144 |Ozone (O3)              |35.3       |Borough          |Staten Island |

2.4 An example use of scatter plot using plotly

  • This following code simply plots the Air Quality Index (AQI) data for New York City (NYC) boroughs on a map.
  • For the purpose of this example, I would like to emphasize the usage of scatter plots on maps in R; thus, predefined air quality index numbers will be used, as calculating AQI itself is way too complex.
  • It first prepares the coordinates and AQI values for each borough.
  • The latitude and longitude coordinates for each borough are sourced from https://www.latlong.net/.
  • Then, it creates a scatter plot using plotly, where each borough is represented by a marker with color indicating its AQI level.
  • Finally, it adjusts the map layout for clarity, including the title and axis labels.
# sample data for borough coordinates (latitude and longitude)
borough_coordinates <- data.frame(
  Borough = c("Manhattan", "Brooklyn", "Queens", "Bronx", "Staten Island"),
  Latitude = c(40.776676, 40.650002, 40.742054, 40.837048, 40.579021),
  Longitude = c(-73.971321, -73.949997, -73.769417, -73.865433, -74.151535)
)

# sample data for AQI summary 
aqi_summary <- data.frame(
  Name = c("Manhattan", "Brooklyn", "Queens", "Bronx", "Staten Island"),
  Air_Quality_Index = c(24, 30.5, 29.1, 31.9, 36) # made-up AQI numbers
)

# create a map-based scatter plot using plotly with the custom colorscale
fig <- plot_ly(borough_coordinates, 
               type = 'scattermapbox',
               mode = 'markers',
               lat = ~Latitude,
               lon = ~Longitude,
               marker = list(size = 10, color = aqi_summary$Air_Quality_Index, colorscale = "Viridis"),
               text = ~paste("Borough: ", Borough, "<br>AQI: ", round(aqi_summary$Air_Quality_Index, 2))) %>%
  layout(title = "Air Quality Index in NYC Boroughs",
         mapbox = list(
           style = "carto-positron",
           zoom = 8,  # adjust the zoom level as needed
           center = list(lat = 40.7128, lon = -74.0060)
         ),
         xaxis = list(title = "Longitude"),
         yaxis = list(title = "Latitude"))

fig

2.5 Further utilization of kable_styling and the creation of an aesthetically pleasing simple scatter plot

  • This code selects the top 10 community districts from NYC, containing air quality data relating to nitrogen dioxide (NO2) levels.
  • It filters the data to include only records related to nitrogen dioxide (NO2) levels in New York City during the summer of 2022.
  • Then, it arranges the filtered data in a descending order by NO2 levels and extracts specific columns.
  • Finally, it displays the selected the top three rows in the table with styled formatting, including borders and hover effects.
# filter data and select top 10 rows
community_districts_summary <- dat %>%
  filter(
    geo_type_name == "CD" & 
    name == "Nitrogen dioxide (NO2)" & 
    time_period == "Summer 2022"
  ) %>%
  arrange(desc(data_value)) %>%
  slice(1:10) %>%
  select(unique_id, name, geo_place_name, time_period, data_value)

# print the top 10 rows with kable_styling
community_districts_summary %>%
  kable("html") %>%
  kable_styling(full_width = FALSE, bootstrap_options = c("bordered","hover", "condensed")) %>%
  row_spec(1:3, italic = TRUE, color = "gold", background = "brown")
unique_id name geo_place_name time_period data_value
826336 Nitrogen dioxide (NO2) Flatbush and Midwood (CD14) Summer 2022 9.8
826327 Nitrogen dioxide (NO2) Bensonhurst (CD11) Summer 2022 9.4
826111 Nitrogen dioxide (NO2) St. George and Stapleton (CD1) Summer 2022 9.3
826378 Nitrogen dioxide (NO2) South Ozone Park and Howard Beach (CD10) Summer 2022 9.2
826339 Nitrogen dioxide (NO2) Sheepshead Bay (CD15) Summer 2022 8.5
826348 Nitrogen dioxide (NO2) Flatlands and Canarsie (CD18) Summer 2022 8.5
826333 Nitrogen dioxide (NO2) Coney Island (CD13) Summer 2022 8.2
826114 Nitrogen dioxide (NO2) South Beach and Willowbrook (CD2) Summer 2022 8.0
826076 Nitrogen dioxide (NO2) Rockaway and Broad Channel (CD14) Summer 2022 6.9
826117 Nitrogen dioxide (NO2) Tottenville and Great Kills (CD3) Summer 2022 6.8

We can utilize the obtained data which comprises the top 10 NYC Community Districts with the highest NO2 levels to create a visually appealing simple scatter plot as shown below.

# create the scatter plot
scatter_plot <- ggplot(community_districts_summary, aes(x = unique_id, y = data_value, color = geo_place_name)) +
  geom_point() + 
  labs(title = "Scatter Plot of Nitrogen Dioxide (NO2) Levels in NYC Community Districts",
       x = "Unique ID of each community district(CD)",
       y = "NO2 Data Value",
       color = "Community District") +  
  theme_minimal() +  # apply a minimal theme
  theme(legend.position = "right", 
        axis.text.x = element_text(angle = 85, hjust = 0.7))  

scatter_plot


3 Further Resources and References




LS0tDQp0aXRsZTogIkV4cGxvcmluZyBhbmQgVmlzdWFsaXppbmcgQWlyIFF1YWxpdHkgRGF0YSBmcm9tIE5ZQyB1c2luZyBnZ3Bsb3QyLCBkcGx5ciwga2FibGUsIGthYmxlRXh0cmEsIHNjYWxlcywgYW5kIHBsb3RseSBMaWJyYXJpZXMiDQphdXRob3I6ICJOeWVpbiBDaGFuIEt5YXciDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiAlWScpYCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0aGVtZTogY29zbW8NCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZGVwdGg6IDMNCiAgICB0b2NfZmxvYXQ6DQogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KDQotLS0NCjxicj4NCg0KIyAqKkludHJvZHVjdGlvbioqDQoNClRoaXMgY29kZSBzaG93Y2FzZXMgdGhlIHV0aWxpemF0aW9uIG9mIHZhcmlvdXMgUiBsaWJyYXJpZXMsIGluY2x1ZGluZyBgZ2dwbG90MmAsIGBkcGx5cmAsIGBrYWJsZWAsIGBrYWJsZUV4dHJhYCwgYHNjYWxlc2AsIGFuZCBgcGxvdGx5YCwgdG8gYW5hbHl6ZSBhbmQgdmlzdWFsaXplIGFpciBxdWFsaXR5IHN1cnZlaWxsYW5jZSBkYXRhIGZyb20gTmV3IFlvcmsgQ2l0eS4gVGhlIGRhdGFzZXQgY29udGFpbnMgY29tcHJlaGVuc2l2ZSBpbmZvcm1hdGlvbiBvbiBhaXIgcXVhbGl0eSBkYXRhIGZyb20gTmV3IFlvcmsgQ2l0eS4gV2l0aCBleHBvc3VyZXMgdG8gY29tbW9uIGFpciBwb2xsdXRhbnRzIGxpbmtlZCB0byB2YXJpb3VzIGhlYWx0aCBpc3N1ZXMsIHVuZGVyc3RhbmRpbmcgYWlyIHF1YWxpdHkgdmFyaWF0aW9ucyBhY3Jvc3MgTllDIG5laWdoYm9yaG9vZHMgaXMgY3J1Y2lhbC4gDQo8YnI+DQoNCg0KIyMgKipDb2RlLVRocm91Z2ggSGlnaGxpZ2h0cyAmIExlYXJuaW5nIEdvYWxzKioNCg0KVGhpcyBjb2RlLXRocm91Z2ggdHV0b3JpYWwgY292ZXJzIHZhcmlvdXMgYXNwZWN0cyBvZiBkYXRhIG1hbmlwdWxhdGlvbiBhbmQgdmlzdWFsaXphdGlvbiBpbiBSLiBUaHJvdWdob3V0IHRoZSBzZW1lc3Rlciwgd2UgaGF2ZSBiZWVuIHdvcmtpbmcgd2l0aCBjZW5zdXMgZGF0YSBhbmQgY2hvcm9wbGV0aCBtYXBzLCB1bnJhdmVsaW5nIHNwYXRpYWwgdHJlbmRzIGFzIHBhcnQgb2Ygb3VyIGNvdXJzZXdvcmsuIFJlY2VudGx5LCBJIHN0dW1ibGVkIHVwb24gdGhlIGBwbG90bHlgIGxpYnJhcnksIHJlbm93bmVkIGZvciBpdHMgcHJvd2VzcyBpbiBjcmFmdGluZyBwbG90cyBiYXNlZCBvbiBnZW9ncmFwaGljYWwgZGF0YS4gQ2FwdGl2YXRlZCBieSBpdHMgcG90ZW50aWFsLCBJJ3ZlIGNob3NlbiB0byBoYXJuZXNzIGNvb3JkaW5hdGVzIGFuZCBnZW9ncmFwaGljYWwgZGF0YSBmb3IgdGhpcyBjb2RlLXRocm91Z2ggYXNzaWdubWVudCBhcyB3ZWxsLiBUaGlzIGFzc2lnbm1lbnQgd2lsbCBzaG93Y2FzZSB0aGUgdXRpbGl6YXRpb24gb2YgZ2VvZ3JhcGhpY2FsIGRhdGEgYW5kIHRoZSBwbG90dGluZyBjYXBhYmlsaXRpZXMgb2YgYHBsb3RseWAsIG9mZmVyaW5nIGluc2lnaHRzIGludG8gY3JlYXRpbmcgc2NhdHRlciBwbG90cyBvbiBtYXBzIGluIFIgZm9yIHNwYXRpYWwgZGF0YSBhbmFseXNpcy4gDQoNCkl0IGFsc28gY292ZXJzIHRoZSB2ZXJzYXRpbGUgZmVhdHVyZXMgb2YgdGhlIGBrYWJsZUV4dHJhYCBwYWNrYWdlLCB3aXRoIGEgc3BlY2lmaWMgZm9jdXMgb24gYGthYmxlX3N0eWxpbmdgIHRvIGVsZXZhdGUgdGFibGUgZm9ybWF0dGluZy4gQWRkaXRpb25hbGx5LCBpdCBwcm92aWRlcyBhIGRldGFpbGVkIGV4cGxvcmF0aW9uIG9mIGBnZ3Bsb3QyYCwgZGVtb25zdHJhdGluZyBpdHMgYWRhcHRhYmlsaXR5IHRocm91Z2ggZGl2ZXJzZSBwbG90dGluZyBtZXRob2RzIGFuZCB2aXN1YWxpemF0aW9ucy4gRnVydGhlcm1vcmUsIHRoZSBjb2RlLXRocm91Z2ggZXhhbWluZXMgdGhlIHJvYnVzdCBkYXRhIG1hbmlwdWxhdGlvbiBmdW5jdGlvbmFsaXRpZXMgb2ZmZXJlZCBieSB0aGUgYGRwbHlyYCBwYWNrYWdlLCBoaWdobGlnaHRpbmcgZXNzZW50aWFsIGZ1bmN0aW9ucyBsaWtlIGBmaWx0ZXJgLCBgZ3JvdXBfYnlgLCBgcmVmcmFtZWAsIGFuZCBgYXJyYW5nZWAuDQoNCkluIGdlbmVyYWwsIHRoaXMgY29kZS10aHJvdWdoIGFzc2lnbm1lbnQgYWltcyB0byBwcm92aWRlIHNhbXBsZSB1c2FnZXMgb2YgaW5kaXNwZW5zYWJsZSB0b29scyBhbmQgdGVjaG5pcXVlcyBmb3IgZGF0YSBhbmFseXNpcyBhbmQgdmlzdWFsaXphdGlvbiBpbiBSLCB0aHJvdWdoIGNsZWFyIGV4cGxhbmF0aW9ucyBhbmQgcHJhY3RpY2FsIGV4YW1wbGVzLiAgDQoNCjxicj4NCjxicj4NCg0KIyAqKkRhdGEgU2V0dXAqKg0KDQpGaXJzdCwgd2UgbmVlZCB0byBpbXBvcnQvc2V0IHVwIHRoZSBkYXRhIGZpcnN0LiBUaGUgcmVxdWlyZWQgcGFja2FnZXMgYW5kIGxpYnJhcmllcyBhcmUgbGlzdGVkIGluIHRoZSBiZWxvdyBjb2RlLiBUaGUgZGF0YSBpcyBzb3VyY2VkIGZyb20gaHR0cHM6Ly9kYXRhLmNpdHlvZm5ld3lvcmsudXMvRW52aXJvbm1lbnQvQWlyLVF1YWxpdHkvYzN1eS0ycDVyL2Fib3V0X2RhdGEuYGZyb21KU09OYCBmdW5jdGlvbiBmcm9tIGBqc29ubGl0ZWAgbGlicmFyeSBpcyB1dGlsaXplZCBmb3IgaW1wb3J0aW5nIHRoZSBKU09OIGRhdGEgdG8gdGhlIGRhdGEgZnJhbWUgYGRhdGAuDQoNCmBgYHtyIHdhcm5pbmc9RkFMU0UsICBtZXNzYWdlID0gRkFMU0V9DQoNCiMgU0VUIEdMT0JBTCBLTklUUiBPUFRJT05TDQoNCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgDQogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEZBTFNFLCANCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UsIA0KICAgICAgICAgICAgICAgICAgICAgIGZpZy53aWR0aCA9IDEwLCANCiAgICAgICAgICAgICAgICAgICAgICBmaWcuaGVpZ2h0ID0gOCkNCg0KDQoNCiMgTE9BRCBQQUNLQUdFUw0KDQpsaWJyYXJ5KHBhbmRlcikNCmxpYnJhcnkoa2FibGVFeHRyYSkNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGtuaXRyKQ0KbGlicmFyeShqc29ubGl0ZSkNCmxpYnJhcnkoc2NhbGVzKQ0KbGlicmFyeShwbG90bHkpDQoNCiMgUkVBRCBJTiBEQVRBDQoNCnVybCA8LSBwYXN0ZSgiaHR0cHM6Ly9kYXRhLmNpdHlvZm5ld3lvcmsudXMvcmVzb3VyY2UvYzN1eS0ycDVyLmpzb24iKQ0KDQpkYXQgPC0gZnJvbUpTT04odXJsKQ0KDQoNCg0KYGBgDQo8YnI+DQo8YnI+DQoNCg0KIyMgKipBIGJhc2ljIGV4YW1wbGUgdXNpbmcgImthYmxlRXh0cmEiKioNCg0KKiBUaGUgYmVsb3cgZXhhbXBsZSBzaG93cyANCiAgDQogIC0gaG93IHRvIHV0aWxpemUgdGhlIGBrYWJsZUV4dHJhYCBsaWJyYXJ5IHRvIGFwcGx5IHRoZSBga2FibGVfc3R5bGluZ2AgZnVuY3Rpb24uIEluIHRoaXMgc3BlY2lmaWMgZXhhbXBsZSwgMTAgbGluZXMgb2YgZGF0YSB3aWxsIGJlIGRpc3BsYXllZCwgYWxsb3dpbmcgeW91IHRvIGhvdmVyIG92ZXIgZWFjaCByb3cuDQo8YnI+DQoNCmBgYHtyfQ0KDQprYWJsZV90YWJsZSA8LSBkYXQgJT4lIHNsaWNlKDE6MTApDQoNCmthYmxlKGthYmxlX3RhYmxlKSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoImhvdmVyIiwgImNvbmRlbnNlZCIpKQ0KDQoNCmBgYCANCg0KPGJyPg0KDQoNCiMjICoqUGxvdHRpbmcgdGhlIHBpZSBjaGFydCB3aXRoIGdncGxvdCBmb3IgdGhlIGRpc3RyaWJ1dGlvbiBvZiBwb2xsdXRhbnRzIGFmZmVjdGluZyBBaXIgUXVhbGl0eSoqDQoNCiogVGhpcyBjb2RlIGNhbGN1bGF0ZXMgdGhlIGRpc3RyaWJ1dGlvbiBvZiBwb2xsdXRhbnRzIGFuZCBjb252ZXJ0cyB0aGUgZGF0YSB2YWx1ZXMgdG8gbnVtZXJpYy4gDQoqIFRoZW4sIGl0IGNvbXB1dGVzIHRoZSBwZXJjZW50YWdlIGZvciBlYWNoIGNhdGVnb3J5LiANCiogRmluYWxseSwgaXQgY3JlYXRlcyBhIHBpZSBjaGFydCBzaG93aW5nIHRoZSBkaXN0cmlidXRpb24gb2YgcG9sbHV0YW50cyBhZmZlY3RpbmcgQWlyIFF1YWxpdHkgaW4gTllDLCB3aXRoIGVhY2ggY2F0ZWdvcnkgcmVwcmVzZW50ZWQgYXMgYSBzbGljZSBvZiB0aGUgcGllLiANCiogTGFiZWxzIGluZGljYXRpbmcgdGhlIHBlcmNlbnRhZ2Ugb2YgZWFjaCB0eXBlIGFyZSBwbGFjZWQgd2l0aGluIHRoZSBzbGljZXMsIGFuZCB0aGUgY2hhcnQgaXMgc3R5bGVkIHdpdGggYSB0aXRsZSwgbGVnZW5kLCBhbmQgY3VzdG9tIHRoZW1lcy4NCg0KYGBge3J9DQoNCiMgY2FsY3VsYXRlIHRvdGFsIGNvdW50IGZvciBlYWNoIG5hbWUgb2YgcG9sbHV0YW50IGFuZCBjb252ZXJ0IGRhdGFfdmFsdWUgdG8gbnVtZXJpYw0KbmFtZV9jb3VudHMgPC0gYWdncmVnYXRlKGRhdGFfdmFsdWUgfiBuYW1lLCBkYXRhID0gZGF0LCBGVU4gPSBmdW5jdGlvbih4KSBzdW0oYXMubnVtZXJpYyh4KSkpDQoNCiMgY2FsY3VsYXRlIHBlcmNlbnRhZ2UgZm9yIGVhY2ggdHlwZQ0KbmFtZV9jb3VudHMkcGVyY2VudGFnZSA8LSAobmFtZV9jb3VudHMkZGF0YV92YWx1ZSAvIHN1bShuYW1lX2NvdW50cyRkYXRhX3ZhbHVlKSkgKiAxMDANCg0KIyBjcmVhdGUgcGllIGNoYXJ0IHdpdGggcGVyY2VudGFnZXMNCmdncGxvdChuYW1lX2NvdW50cywgYWVzKHggPSAiIiwgeSA9IGRhdGFfdmFsdWUsIGZpbGwgPSBuYW1lKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAxLCBjb2xvciA9ICJibGFjayIsIHNpemUgPSAwLjMpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHBlcmNlbnQocGVyY2VudGFnZSAvIDEwMCksIHkgPSBkYXRhX3ZhbHVlICsgMC41KSwgDQogICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX3N0YWNrKHZqdXN0ID0gMC41KSwgc2l6ZSA9IDUsIGNvbG9yID0gIndoaXRlIiwgZm9udGZhY2UgPSAiYm9sZCIpICsNCiAgY29vcmRfcG9sYXIoInkiLCBzdGFydCA9IDApICsNCiAgbGFicyh0aXRsZSA9ICJEaXN0cnVidXRpb24gb2YgcG9sbHV0YW50cyBhZmZlY3RpbmcgQWlyIFF1YWxpdHkgaW4gTllDIiwNCiAgICAgICBmaWxsID0gIlR5cGVzIG9mIFBvbGx1dGFudHMiLA0KICAgICAgIHggPSBOVUxMLCB5ID0gTlVMTCkgKw0KICB0aGVtZV92b2lkKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiLA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAyMCwgZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwLjUpLA0KICAgICAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLA0KICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0KSwNCiAgICAgICAgbGVnZW5kLmtleS5zaXplID0gdW5pdCgxLjUsICJsaW5lcyIpKQ0KDQoNCmBgYA0KDQoNCiMjICoqQWR2YW5jZWQgRXhhbXBsZSBvZiB1dGlsaXppbmcgImRwbHlyIiB3aXRoICJrYWJsZSIqKg0KDQoNCiogSW4gdGhpcyBleGFtcGxlLCB0aGUgZGF0YWZyYW1lIGlzIGZpbHRlcmVkIHRvIGluY2x1ZGUgb25seSByb3dzIHdoZXJlIHRoZSBgZ2VvX3R5cGVfbmFtZWAgaXMgYEJvcm91Z2hgLiBUaGVuLCB0aGUgZGF0YSBpcyBncm91cGVkIGJ5IHNldmVyYWwgY29sdW1ucyBpbmNsdWRpbmcgYHVuaXF1ZV9pZGAsIGBuYW1lYCwgYGRhdGFfdmFsdWVgLCBhbmQgYGdlb190eXBlX25hbWVgLiBUaGUgY29sdW1uIGBnZW9fcGxhY2VfbmFtZWAgaXMgcmVuYW1lZCB0byBgTG9jYXRpb25fTmFtZWAgdXNpbmcgdGhlIGByZWZyYW1lYCBmdW5jdGlvbi4gRmluYWxseSwgdGhlIGRhdGEgaXMgYXJyYW5nZWQgaW4gZGVzY2VuZGluZyBvcmRlciBiYXNlZCBvbiBgTG9jYXRpb25fTmFtZWAuDQoNCiogVGhlIHNwbGl0IGZ1bmN0aW9uIGRpdmlkZXMgYGRhdGFfYm9yb3VnaF9zdW1tYXJ5YCBpbnRvIHNlcGFyYXRlIHRhYmxlcyBiYXNlZCBvbiB0aGUgdmFsdWVzIG9mIGBMb2NhdGlvbl9OYW1lYCAoYm9yb3VnaCBuYW1lcykuIEVhY2ggcmVzdWx0aW5nIHRhYmxlIGNvbnRhaW5zIGRhdGEgc3BlY2lmaWMgdG8gYSBwYXJ0aWN1bGFyIGJvcm91Z2guDQoNCiogQSBsb29wIGl0ZXJhdGVzIG92ZXIgZWFjaCBib3JvdWdoLiBGb3IgZWFjaCBib3JvdWdoLCBhIGhlYWRlciBpbmRpY2F0aW5nIHRoZSBib3JvdWdoIG5hbWUgaXMgcHJpbnRlZCB1c2luZyBjYXQuIFRoZW4sIHRoZSBjb3JyZXNwb25kaW5nIHRhYmxlIChzdG9yZWQgaW4gYGJvcm91Z2hfdGFibGVzW1tib3JvdWdoXV1gKSBpcyBwcmludGVkIHVzaW5nIGBwYW5kZXJgLg0KDQoqIEZpbmFsbHksIHRoZSByZXN1bHQgaXMgYSBzZXJpZXMgb2YgdGFibGVzLCBlYWNoIGRpc3BsYXlpbmcgc3VtbWFyaXplZCBkYXRhIHZhbHVlcyBmb3IgYSBzcGVjaWZpYyBib3JvdWdoLiBFYWNoIHRhYmxlIGlzIHByZWNlZGVkIGJ5IGEgaGVhZGVyIGluZGljYXRpbmcgdGhlIGNvcnJlc3BvbmRpbmcgYm9yb3VnaCBuYW1lLiBUaGlzIGFwcHJvYWNoIHByb3ZpZGVzIGEgc3RydWN0dXJlZCBwcmVzZW50YXRpb24gb2YgYWlyIHF1YWxpdHkgZGF0YSB2YWx1ZXMgZm9yIGluZGl2aWR1YWwgYm9yb3VnaHMsIGZhY2lsaXRhdGluZyBhbmFseXNpcyBhbmQgaW50ZXJwcmV0YXRpb24uDQoNCmBgYHtyfSANCg0KDQojIGZpbHRlciBkYXRhIGZvciBib3JvdWdocyBhbmQgc3VtbWFyaXplKHJlZnJhbWUpIGRhdGEgdmFsdWVzDQpkYXRhX2Jvcm91Z2hfc3VtbWFyeSA8LSBkYXQgJT4lDQogIGZpbHRlcihnZW9fdHlwZV9uYW1lID09ICJCb3JvdWdoIikgJT4lICANCiAgZ3JvdXBfYnkoSUQgPSB1bmlxdWVfaWQsIE5hbWVfT2ZfUG9sbHV0YW50cyA9IG5hbWUsIERhdGFfVmFsdWUgPSBkYXRhX3ZhbHVlLCBUeXBlX09mX0xvY2F0aW9uID0gZ2VvX3R5cGVfbmFtZSkgJT4lDQogIHJlZnJhbWUoTG9jYXRpb25fTmFtZSA9IGdlb19wbGFjZV9uYW1lKSAlPiUNCiAgYXJyYW5nZShkZXNjKExvY2F0aW9uX05hbWUpKQ0KDQojIGNyZWF0ZSBzZXBhcmF0ZSB0YWJsZXMgZm9yIGVhY2ggYm9yb3VnaA0KYm9yb3VnaF90YWJsZXMgPC0gc3BsaXQoZGF0YV9ib3JvdWdoX3N1bW1hcnksIGYgPSBkYXRhX2Jvcm91Z2hfc3VtbWFyeSRMb2NhdGlvbl9OYW1lKQ0KDQojIHByaW50IGVhY2ggdGFibGUgdXNpbmcga2FibGUNCmZvciAoYm9yb3VnaCBpbiBuYW1lcyhib3JvdWdoX3RhYmxlcykpIHsNCiAgY2F0KHBhc3RlKCIqKiIsIGJvcm91Z2gsICIqKiIsICJcbiIpKQ0KICBwcmludChrYWJsZShib3JvdWdoX3RhYmxlc1tbYm9yb3VnaF1dKSwgZm9ybWF0ID0gIm1hcmtkb3duIikNCiAgY2F0KCJcblxuIikgDQogDQp9DQoNCg0KDQoNCmBgYA0KDQojIyAqKkFuIGV4YW1wbGUgdXNlIG9mIHNjYXR0ZXIgcGxvdCB1c2luZyBwbG90bHkqKg0KDQoNCg0KKiBUaGlzIGZvbGxvd2luZyBjb2RlIHNpbXBseSBwbG90cyB0aGUgQWlyIFF1YWxpdHkgSW5kZXggKEFRSSkgZGF0YSBmb3IgTmV3IFlvcmsgQ2l0eSAoTllDKSBib3JvdWdocyBvbiBhIG1hcC4gDQoqIEZvciB0aGUgcHVycG9zZSBvZiB0aGlzIGV4YW1wbGUsIEkgd291bGQgbGlrZSB0byBlbXBoYXNpemUgdGhlIHVzYWdlIG9mIHNjYXR0ZXIgcGxvdHMgb24gbWFwcyBpbiBSOyB0aHVzLCBwcmVkZWZpbmVkIGFpciBxdWFsaXR5IGluZGV4IG51bWJlcnMgd2lsbCBiZSB1c2VkLCBhcyBjYWxjdWxhdGluZyBBUUkgaXRzZWxmIGlzIHdheSB0b28gY29tcGxleC4gDQoqIEl0IGZpcnN0IHByZXBhcmVzIHRoZSBjb29yZGluYXRlcyBhbmQgQVFJIHZhbHVlcyBmb3IgZWFjaCBib3JvdWdoLiANCiogVGhlIGxhdGl0dWRlIGFuZCBsb25naXR1ZGUgY29vcmRpbmF0ZXMgZm9yIGVhY2ggYm9yb3VnaCBhcmUgc291cmNlZCBmcm9tIGh0dHBzOi8vd3d3LmxhdGxvbmcubmV0Ly4gDQoqIFRoZW4sIGl0IGNyZWF0ZXMgYSBzY2F0dGVyIHBsb3QgdXNpbmcgYHBsb3RseWAsIHdoZXJlIGVhY2ggYm9yb3VnaCBpcyByZXByZXNlbnRlZCBieSBhIG1hcmtlciB3aXRoIGNvbG9yIGluZGljYXRpbmcgaXRzIEFRSSBsZXZlbC4gDQoqIEZpbmFsbHksIGl0IGFkanVzdHMgdGhlIG1hcCBsYXlvdXQgZm9yIGNsYXJpdHksIGluY2x1ZGluZyB0aGUgdGl0bGUgYW5kIGF4aXMgbGFiZWxzLg0KDQoNCg0KYGBge3J9DQoNCg0KIyBzYW1wbGUgZGF0YSBmb3IgYm9yb3VnaCBjb29yZGluYXRlcyAobGF0aXR1ZGUgYW5kIGxvbmdpdHVkZSkNCmJvcm91Z2hfY29vcmRpbmF0ZXMgPC0gZGF0YS5mcmFtZSgNCiAgQm9yb3VnaCA9IGMoIk1hbmhhdHRhbiIsICJCcm9va2x5biIsICJRdWVlbnMiLCAiQnJvbngiLCAiU3RhdGVuIElzbGFuZCIpLA0KICBMYXRpdHVkZSA9IGMoNDAuNzc2Njc2LCA0MC42NTAwMDIsIDQwLjc0MjA1NCwgNDAuODM3MDQ4LCA0MC41NzkwMjEpLA0KICBMb25naXR1ZGUgPSBjKC03My45NzEzMjEsIC03My45NDk5OTcsIC03My43Njk0MTcsIC03My44NjU0MzMsIC03NC4xNTE1MzUpDQopDQoNCiMgc2FtcGxlIGRhdGEgZm9yIEFRSSBzdW1tYXJ5IA0KYXFpX3N1bW1hcnkgPC0gZGF0YS5mcmFtZSgNCiAgTmFtZSA9IGMoIk1hbmhhdHRhbiIsICJCcm9va2x5biIsICJRdWVlbnMiLCAiQnJvbngiLCAiU3RhdGVuIElzbGFuZCIpLA0KICBBaXJfUXVhbGl0eV9JbmRleCA9IGMoMjQsIDMwLjUsIDI5LjEsIDMxLjksIDM2KSAjIG1hZGUtdXAgQVFJIG51bWJlcnMNCikNCg0KIyBjcmVhdGUgYSBtYXAtYmFzZWQgc2NhdHRlciBwbG90IHVzaW5nIHBsb3RseSB3aXRoIHRoZSBjdXN0b20gY29sb3JzY2FsZQ0KZmlnIDwtIHBsb3RfbHkoYm9yb3VnaF9jb29yZGluYXRlcywgDQogICAgICAgICAgICAgICB0eXBlID0gJ3NjYXR0ZXJtYXBib3gnLA0KICAgICAgICAgICAgICAgbW9kZSA9ICdtYXJrZXJzJywNCiAgICAgICAgICAgICAgIGxhdCA9IH5MYXRpdHVkZSwNCiAgICAgICAgICAgICAgIGxvbiA9IH5Mb25naXR1ZGUsDQogICAgICAgICAgICAgICBtYXJrZXIgPSBsaXN0KHNpemUgPSAxMCwgY29sb3IgPSBhcWlfc3VtbWFyeSRBaXJfUXVhbGl0eV9JbmRleCwgY29sb3JzY2FsZSA9ICJWaXJpZGlzIiksDQogICAgICAgICAgICAgICB0ZXh0ID0gfnBhc3RlKCJCb3JvdWdoOiAiLCBCb3JvdWdoLCAiPGJyPkFRSTogIiwgcm91bmQoYXFpX3N1bW1hcnkkQWlyX1F1YWxpdHlfSW5kZXgsIDIpKSkgJT4lDQogIGxheW91dCh0aXRsZSA9ICJBaXIgUXVhbGl0eSBJbmRleCBpbiBOWUMgQm9yb3VnaHMiLA0KICAgICAgICAgbWFwYm94ID0gbGlzdCgNCiAgICAgICAgICAgc3R5bGUgPSAiY2FydG8tcG9zaXRyb24iLA0KICAgICAgICAgICB6b29tID0gOCwgICMgYWRqdXN0IHRoZSB6b29tIGxldmVsIGFzIG5lZWRlZA0KICAgICAgICAgICBjZW50ZXIgPSBsaXN0KGxhdCA9IDQwLjcxMjgsIGxvbiA9IC03NC4wMDYwKQ0KICAgICAgICAgKSwNCiAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJMb25naXR1ZGUiKSwNCiAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJMYXRpdHVkZSIpKQ0KDQpmaWcNCg0KDQoNCmBgYA0KDQoNCiMjICoqRnVydGhlciB1dGlsaXphdGlvbiBvZiBrYWJsZV9zdHlsaW5nIGFuZCB0aGUgY3JlYXRpb24gb2YgYW4gYWVzdGhldGljYWxseSBwbGVhc2luZyBzaW1wbGUgc2NhdHRlciBwbG90KioNCg0KKiBUaGlzIGNvZGUgc2VsZWN0cyB0aGUgdG9wIDEwIGNvbW11bml0eSBkaXN0cmljdHMgZnJvbSBOWUMsIGNvbnRhaW5pbmcgYWlyIHF1YWxpdHkgZGF0YSByZWxhdGluZyB0byBuaXRyb2dlbiBkaW94aWRlIChOTzIpIGxldmVscy4gDQoqIEl0IGZpbHRlcnMgdGhlIGRhdGEgdG8gaW5jbHVkZSBvbmx5IHJlY29yZHMgcmVsYXRlZCB0byBuaXRyb2dlbiBkaW94aWRlIChOTzIpIGxldmVscyBpbiBOZXcgWW9yayBDaXR5IGR1cmluZyB0aGUgc3VtbWVyIG9mIDIwMjIuIA0KKiBUaGVuLCBpdCBhcnJhbmdlcyB0aGUgZmlsdGVyZWQgZGF0YSBpbiBhIGRlc2NlbmRpbmcgb3JkZXIgYnkgTk8yIGxldmVscyBhbmQgZXh0cmFjdHMgc3BlY2lmaWMgY29sdW1ucy4gDQoqIEZpbmFsbHksIGl0IGRpc3BsYXlzIHRoZSBzZWxlY3RlZCB0aGUgdG9wIHRocmVlIHJvd3MgaW4gdGhlIHRhYmxlIHdpdGggc3R5bGVkIGZvcm1hdHRpbmcsIGluY2x1ZGluZyBib3JkZXJzIGFuZCBob3ZlciBlZmZlY3RzLg0KDQpgYGB7cn0NCg0KIyBmaWx0ZXIgZGF0YSBhbmQgc2VsZWN0IHRvcCAxMCByb3dzDQpjb21tdW5pdHlfZGlzdHJpY3RzX3N1bW1hcnkgPC0gZGF0ICU+JQ0KICBmaWx0ZXIoDQogICAgZ2VvX3R5cGVfbmFtZSA9PSAiQ0QiICYgDQogICAgbmFtZSA9PSAiTml0cm9nZW4gZGlveGlkZSAoTk8yKSIgJiANCiAgICB0aW1lX3BlcmlvZCA9PSAiU3VtbWVyIDIwMjIiDQogICkgJT4lDQogIGFycmFuZ2UoZGVzYyhkYXRhX3ZhbHVlKSkgJT4lDQogIHNsaWNlKDE6MTApICU+JQ0KICBzZWxlY3QodW5pcXVlX2lkLCBuYW1lLCBnZW9fcGxhY2VfbmFtZSwgdGltZV9wZXJpb2QsIGRhdGFfdmFsdWUpDQoNCiMgcHJpbnQgdGhlIHRvcCAxMCByb3dzIHdpdGgga2FibGVfc3R5bGluZw0KY29tbXVuaXR5X2Rpc3RyaWN0c19zdW1tYXJ5ICU+JQ0KICBrYWJsZSgiaHRtbCIpICU+JQ0KICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGQUxTRSwgYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJib3JkZXJlZCIsImhvdmVyIiwgImNvbmRlbnNlZCIpKSAlPiUNCiAgcm93X3NwZWMoMTozLCBpdGFsaWMgPSBUUlVFLCBjb2xvciA9ICJnb2xkIiwgYmFja2dyb3VuZCA9ICJicm93biIpDQoNCg0KYGBgDQoNCioqV2UgY2FuIHV0aWxpemUgdGhlIG9idGFpbmVkIGRhdGEgd2hpY2ggY29tcHJpc2VzIHRoZSB0b3AgMTAgTllDIENvbW11bml0eSBEaXN0cmljdHMgd2l0aCB0aGUgaGlnaGVzdCBOTzIgbGV2ZWxzIHRvIGNyZWF0ZSBhIHZpc3VhbGx5IGFwcGVhbGluZyBzaW1wbGUgc2NhdHRlciBwbG90IGFzIHNob3duIGJlbG93LioqDQoNCmBgYHtyfQ0KIyBjcmVhdGUgdGhlIHNjYXR0ZXIgcGxvdA0Kc2NhdHRlcl9wbG90IDwtIGdncGxvdChjb21tdW5pdHlfZGlzdHJpY3RzX3N1bW1hcnksIGFlcyh4ID0gdW5pcXVlX2lkLCB5ID0gZGF0YV92YWx1ZSwgY29sb3IgPSBnZW9fcGxhY2VfbmFtZSkpICsNCiAgZ2VvbV9wb2ludCgpICsgDQogIGxhYnModGl0bGUgPSAiU2NhdHRlciBQbG90IG9mIE5pdHJvZ2VuIERpb3hpZGUgKE5PMikgTGV2ZWxzIGluIE5ZQyBDb21tdW5pdHkgRGlzdHJpY3RzIiwNCiAgICAgICB4ID0gIlVuaXF1ZSBJRCBvZiBlYWNoIGNvbW11bml0eSBkaXN0cmljdChDRCkiLA0KICAgICAgIHkgPSAiTk8yIERhdGEgVmFsdWUiLA0KICAgICAgIGNvbG9yID0gIkNvbW11bml0eSBEaXN0cmljdCIpICsgIA0KICB0aGVtZV9taW5pbWFsKCkgKyAgIyBhcHBseSBhIG1pbmltYWwgdGhlbWUNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IiwgDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gODUsIGhqdXN0ID0gMC43KSkgIA0KDQpzY2F0dGVyX3Bsb3QNCg0KDQpgYGANCg0KDQo8YnI+DQoNCiMgKipGdXJ0aGVyIFJlc291cmNlcyBhbmQgUmVmZXJlbmNlcyoqDQoNCg0KPGJyPg0KDQoqIFJlc291cmNlIEkgLSBOZXcgWW9yayBDaXR5IE9wZW4gRGF0YS4oaHR0cHM6Ly9kYXRhLmNpdHlvZm5ld3lvcmsudXMvRW52aXJvbm1lbnQvQWlyLVF1YWxpdHkvYzN1eS0ycDVyL2Fib3V0X2RhdGEpICANCg0KKiBSZXNvdXJjZSBJSSAtIFNjYXR0ZXIgUGxvdHMgb24gTWFwcyBpbiBSIHVzaW5nIHBsb3RseS4gKGh0dHBzOi8vcGxvdGx5LmNvbS9yL3NjYXR0ZXItcGxvdHMtb24tbWFwcy8pDQoNCiogUmVzb3VyY2UgSUlJIC0ga2FibGVfc3R5bGluZzogSFRNTCB0YWJsZSBhdHRyaWJ1dGVzLiAoaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL2thYmxlRXh0cmEvdmVyc2lvbnMvMS4zLjQvdG9waWNzL2thYmxlX3N0eWxpbmcpDQoNCiogUmVzb3VyY2UgSVYgLVBpZSBjaGFydCBpbiBnZ3Bsb3QyLiAoaHR0cHM6Ly9yLWNoYXJ0cy5jb20vcGFydC13aG9sZS9waWUtY2hhcnQtZ2dwbG90Mi8pDQogIA0KKiBSZXNvdXJjZSBWIC0gQmFyIHBsb3QgaW4gZ2dwbG90MiB3aXRoIGdlb21fYmFyIGFuZCBnZW9tX2NvbC4gKGh0dHBzOi8vci1jaGFydHMuY29tL3JhbmtpbmcvYmFyLXBsb3QtZ2dwbG90Mi8pDQoNCiogUmVzb3VyY2UgVkkgLSBMYXRpdHVkZSBhbmQgTG9uZ2l0dWRlIEZpbmRlci4gKGh0dHBzOi8vd3d3LmxhdGxvbmcubmV0LykNCg0KPGJyPg0KPGJyPg==