I. Introduction

\(~\) \(~\)

Where we’ve been –

The midterm assessment was designed to evaluate your ‘fundamental’ skills as a data scientist, corresponding to Course Objectives (1)-(4,5) in the syllabus. Those skills break down into –

  1. Workflow
    • .Rprojects + Github, Rmarkdown, best R practices, reproducibility
  2. Data Wrangling
    • dplyr, tidyr, forcats, stringr, lubridate
    • plus iteration with purrr::map family
  3. Data Visualization
    • ggplot2,gt
  4. Exploratory Data Analysis
    • Use the above to read, explore, clean/tidy, prepare for analysis and visualize new data

\(~\) \(~\)

Where we’re going –

As we move forward this semester (today and after Spring Break 3/5 - 3/13), we will continuously utilize and extend these ‘fundamental’ skills to maximize R and Rstudio’s potential for data science and analysis. With the aforementioned fundamentals in hand, we are going to learn how to use these ‘advanced’ communication and analysis tools –

  1. Enhanced Visualization
    • Interactivity w/ ggplotly, reactable
    • Enhance gt with gtExtras (and flextable, ftExtra)
    • Intro to flexdashboard
  2. Websites in R + Github
    • Basics, personal webpage
    • Websites as analytical display tools
      • Embedding flexdashboard
  3. Spatial Visualization (today)
    • ‘Tidy Maps’ w/ sforsp + tidyverse + ggmaps and ggplot2
    • Introduction to interactive maps with ggplotly and/or leaflet
  4. Shiny Apps
    • Basics
    • Publish in an R hosted website
      • Embedding interactive flexdashboards
      • Embedding shiny apps
  5. Miscellaneous
    • *Working with big data in R dt_plyr, collapse, h2o, sparklyr
    • *Working with databases in R db_plyr
    • *How to develop a package with Rstudio & Github
    • *Webscraping with R

\(~\) \(~\)

So what exactly are we going to do today?

First, we’re going to briefly cover joining multiple data sources with dplyr with spatial examples. Then we’re going to build onto last week’s lecture and expand our spatial data toolbox in R with more advanced use of sf, ggmaps, tidycensus, as well as an introduction to fully interactive leaflet. Finally, we will apply these tools in an activity where we create and edit a more advanced spatial dashboard together.

#Install the packages for today if you don't already have them
install.packages(c("sf", "ggmap", "tmap", "tidycensus", "leaflet", "osmdata", "tigris"))

II. Joining data in R

Joining data from multiple sources is another aspect of data wrangling which was covered in PUBH 7461, but is an important part of working with real-world data that we should make sure we’re on the same page about heading into the final project.

Laura Le’s wonderful lecture regarding joining data in R, as well as an example/activity with NYC flight data can be found on Canvas here.

III. Advanced sf, ggmap, tidycensus, ggplot, plotly

Simple Features sf Resources

Like many things in the R universe, the sf package has wonderful documentation and examples. Please spend some time reviewing these on your own.

  1. Introduction
  2. Reading, writing, converting Simple Features
  3. Manipulating Simple Features
  4. Plotting Simple Features

Example 1. (MN) More advanced sf + ggplotly

First, let’s download the ggthemes package for a few more thematic choices in our ggplot’s.

#Install ggrepel if necessary 
if (!require(ggthemes)) {
  install.packages("ggthemes", quiet = TRUE)
}

#Call the library
library(ggthemes, quietly = TRUE)

Next, let’s read in our MN .shp file (from last week’s lecture).

#Read in the shape file (don't make a tibble)
mn.df <- st_read("./data/USA_Counties/USA_Counties.shp", quiet = TRUE) %>%
  janitor::clean_names() %>%
  filter(state_name %in% "Minnesota")

Next, let’s build our ggplot but add a little more information with our usual data wrangling skills and employ a better ggthemes.

mn_pop.gg <- mn.df %>%
  dplyr::select(name, white:other, renter_occ, owner_occ, geometry) %>%
  rename(county = name) %>%
  pivot_longer(
    cols      = white:other, #tidy long data by category
    names_to  = "race_category",
    values_to = "race_pop"
  ) %>%
  mutate(
    race_category = str_replace_all(race_category, "_", " ") %>%
                    str_to_title() %>%
                    as_factor()
  ) %>%
  group_by(county) %>% #County level population
  mutate(county_pop = sum(race_pop)) %>%
  group_by(county, race_category) %>%
  summarise(
    perc_race = race_pop / county_pop,
    perc_rent = renter_occ / (renter_occ + owner_occ),
    geometry   = geometry
  ) %>%
  ungroup() %>%
  nest(data = c("race_category", "perc_race", "geometry")) %>%
  mutate(
    text_label = map_chr(.x = data, 
                     ~str_c(
                       "\n",
                       .x$race_category,
                       ": ", 
                       scales::percent(.x$perc_race, accuracy = 0.0001),
                       collapse = ""
                      )
                 ),
    text_label = str_c(county, "\nDemographics", text_label, "\nAvg. Rental Percentage: ", scales::percent(perc_rent, accuracy = 0.01))
  ) %>%
  unnest(data) %>%
  st_as_sf() %>%
  ggplot() +
  geom_sf(aes(fill = perc_rent, text = text_label),
          colour = "black", size = 0.8, alpha = 0.6) +
  labs(
    title = "2017 MN ACS Rent vs. Own % by County" 
  ) +
  scale_fill_viridis_c("Percent Rental", labels = scales::percent) +
  theme_map() +
  theme(
    plot.title   = element_text(size  = 24,
                                hjust = 0.5),
    legend.text  = element_text(size = 20),
    legend.title = element_text(size = 20),
    legend.position = "right"
  )


#Plotly
ggplotly(mn_pop.gg, 
         tooltip = "text",
         height  = 600,
         width   = 800) %>%
  style(hoveron = "fills")

Advanced tidycensus

Working with plotly

Example 2. (MN) More advanced tidycensus + ggplotly

Request the ACS estimates for median income in Hennepin County, MN, at the census tract level from the US census.gov via tidycensus.

#Save the cache so you don't need to call the same API request over and over
options(tigris_use_cache = TRUE)

#Call the ACS API, returns a sf object
mn_income.df <- get_acs(
  geography = "tract",
  variables = "B19013_001", #Code for median income
  state     = "MN",
  county    = "Hennepin",
  year      = 2020,
  geometry  = TRUE
)
Getting data from the 2016-2020 5-year ACS
Using FIPS code '27' for state 'MN'
Using FIPS code '053' for 'Hennepin County'

Now let’s plot it with a nice theme and turn it into a plotly

#Add a text label to mn_income.df
mn_income_plotly.df <- mn_income.df %>%
  mutate(
    tract      = str_split(NAME, ",") %>%
                 map_chr(1) %>%
                 str_remove("Census Tract "),
    text_label = str_c(
                  "Tract: ",
                  tract,
                  "\nMedian Income: ",
                  scales::dollar(estimate)
                 )
  )

#Generate the ggplot
income.gg <- ggplot() + 
  geom_sf(data = mn_income_plotly.df, 
          aes(fill = estimate, text = text_label),
          colour = "black", size = 0.1) + 
  labs(title = "Hennepin County, MN 2020 ACS Median Income") + 
  scale_fill_viridis_c("Median Income", labels = scales::dollar) +
  theme_map() +
  theme(
    plot.title   = element_text(size  = 24,
                                hjust = 0.5),
    legend.text  = element_text(size = 20),
    legend.title = element_text(size = 20),
    legend.position = "right"
  )


#Display
ggplotly(income.gg,
         tooltip = "text",
         height  = 600,
         width   = 800) %>%
    style(hoveron = "fills")

Combining with ggmap

Example 3. (MN) More advanced ggmap + tidycensus + ggplotly

Now, let’s take the same plot as above but overlay it onto a ‘nice’ map of MN with ggmap.

For reference, here is the complete documentation for the options and types of maps available with ggmap. Alternatively, a nice cheat sheet for ggmap can be found here

1. Create the base map

2. Add the income layer(s) from before and transform into ggplotly

#Display plotly
ggplotly(mn_income.ggmap,
         tooltip = "text",
         height  = 600,
         width   = 800) %>%
    style(hoveron = "fills")

Joining other spatial data (osmdata)

Advanced ggmap

Working with plotly

Working with tidycensus

III. Advanced sf, ggmap, tidycensus, ggplot, plotly

IV. Leaflet

Introduction to leaflet

Example 1. NYC Airbnb

V. Activity

Activity 1 (together). Creating a spatial dashboard of NYC Airbnb’s in Manhattan

Today’s NYC spatial dashboard can be accessed on Canvas under Week 11 Lecture Materials.

Activity 2 (your turn). Edit the dashboard

For today’s activity, please –

  1. Set up a Google Maps API key (directions above) for ggmap
  2. Edit the dashboard as follows –
    • Pick another variable from the ACS Codebook and replace the Median Income plot with this new variable
    • Colour the map’s points by rating instead of price
    • Show the distribution of ratings rather than price (boxplot)
LS0tDQp0aXRsZTogIldlZWsgMTE6IFNwYXRpYWwgTWFwcGluZyBJSSINCnN1YnRpdGxlOiAic2YsIGdncGxvdCwgZ2dtYXBzLCB0aWR5Y2Vuc3VzLCBhbmQgbGVhZmxldCINCmF1dGhvcjogIlF1aW50b24gTmV2aWxsZSINCmRhdGU6ICJBcHJpbCAxc3QsIDIwMjIiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogIGh0bWxfbm90ZWJvb2s6DQogICAgaGlnaGxpZ2h0OiB0YW5nbw0KICAgIHRoZW1lOiByZWFkYWJsZQ0KaGVhZGVyLWluY2x1ZGVzOiANCiAgXHVzZXBhY2thZ2V7Z3JhcGhpY3h9DQogIFx1c2VwYWNrYWdle2Zsb2F0fQ0KICBcdXNlcGFja2FnZXthbXNtYXRofQ0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlID0gRkFMU0UsIGVjaG8gPSBGQUxTRX0NCiNMb2FkIHRoZSBnb29kIHN0dWZmDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeShndCkNCmxpYnJhcnkocGFsZXR0ZWVyKQ0KbGlicmFyeShnZ3JpZGdlcykNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeShndEV4dHJhcykNCmxpYnJhcnkoc2YpDQpsaWJyYXJ5KGdnbWFwKQ0KbGlicmFyeSh0aWR5Y2Vuc3VzKQ0KbGlicmFyeShsZWFmbGV0KQ0KbGlicmFyeShvc21kYXRhKQ0KbGlicmFyeSh0aWdyaXMpDQpsaWJyYXJ5KGdnc2ZsYWJlbCkNCmxpYnJhcnkoZ2d0aGVtZXMpDQoNCiNXb3JraW5nIGRpcmVjdG9yeSBmb3IgLlJNRA0Ka25pdHI6Om9wdHNfa25pdCRzZXQoZWNobyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICByb290LmRpciA9IHJwcm9qcm9vdDo6ZmluZF9yc3R1ZGlvX3Jvb3RfZmlsZSgpKQ0KDQojQ29udHJvbGxpbmcgZmlndXJlIG91dHB1dCBpbiBtYXJrZG93bg0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KA0KIyAgZmlnLmhlaWdodCA9ICAgDQogIGZpZy53aWR0aCA9IDYsDQojICBmaWcuYXNwID0gLjUsDQogIG91dC53aWR0aCA9ICI5MCUiLA0KIyAgb3V0LmhlaWdodCA9IA0KIGZpZy5hbGlnbiAgPSAiY2VudGVyIiwNCiAgY2FjaGUgPSBUUlVFLA0KICBldmFsICA9IFRSVUUsDQogIGVjaG8gID0gVFJVRSwNCiAgd2FybmluZyA9IEZBTFNFDQopDQoNCiNNeSBDb2xvdXJzIChmcm9tIHZpcmlkaXMpDQpteV9wdXJwbGUgPC0gIiM0NDAxNTRGRiINCm15X3llbGxvdyA8LSAiI0ZERTcyNUZGIg0KDQojU2V0IFRoZW1lIGZvciBnZ3Bsb3QyDQp0aGVtZV9zZXQodGhlbWVfYncoKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLCBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikpDQoNCiNTZXQgU2NpZW50aWZpYyBub3RhdGlvbiBvdXRwdXQgYW5kIGRlY2ltYWwgcGxhY2VzIGZvciBrbml0cg0Kb3B0aW9ucyhzY2lwZW4gPSA5OTkpDQpvcHRpb25zKGRpZ2l0cyA9IDQpDQpvcHRpb25zKGRwbHlyLnN1bW1hcmlzZS5pbmZvcm0gPSBGQUxTRSkNCmBgYA0KDQojIEkuIEludHJvZHVjdGlvbiB7Lmp1bWJvdHJvbn0gIA0KDQokfiQNCiR+JA0KDQojIyBXaGVyZSB3ZSd2ZSBiZWVuIC0tDQoNClRoZSBtaWR0ZXJtIGFzc2Vzc21lbnQgd2FzIGRlc2lnbmVkIHRvIGV2YWx1YXRlIHlvdXIgJ2Z1bmRhbWVudGFsJyBza2lsbHMgYXMgYSBkYXRhIHNjaWVudGlzdCwgY29ycmVzcG9uZGluZyB0byBfQ291cnNlIE9iamVjdGl2ZXNfICgxKS0oNCw1KSBpbiB0aGUgW3N5bGxhYnVzXShodHRwczovL2NhbnZhcy51bW4uZWR1L2NvdXJzZXMvMjkzMDQ5L2ZpbGVzP3ByZXZpZXc9MjY1MTY1ODIpLiBUaG9zZSBza2lsbHMgYnJlYWsgZG93biBpbnRvIC0tIA0KDQoxLiAqKldvcmtmbG93KiogDQogICAgLSAuUnByb2plY3RzICsgR2l0aHViLCBSbWFya2Rvd24sIGJlc3QgYFJgIHByYWN0aWNlcywgcmVwcm9kdWNpYmlsaXR5DQoyLiAqKkRhdGEgV3JhbmdsaW5nKioNCiAgICAtIGBkcGx5cmAsIGB0aWR5cmAsIGBmb3JjYXRzYCwgYHN0cmluZ3JgLCBgbHVicmlkYXRlYA0KICAgIC0gcGx1cyBpdGVyYXRpb24gd2l0aCBgcHVycnI6Om1hcGAgZmFtaWx5DQozLiAqKkRhdGEgVmlzdWFsaXphdGlvbioqIA0KICAgIC0gYGdncGxvdDJgLGBndGANCjQuICoqRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcyoqDQogICAgLSBVc2UgdGhlIGFib3ZlIHRvIHJlYWQsIGV4cGxvcmUsIGNsZWFuL3RpZHksIHByZXBhcmUgZm9yIGFuYWx5c2lzIGFuZCB2aXN1YWxpemUgX25ld18gZGF0YSAgIA0KDQokfiQNCiR+JA0KDQojIyBXaGVyZSB3ZSdyZSBnb2luZyAtLSAgICANCg0KQXMgd2UgbW92ZSBmb3J3YXJkIHRoaXMgc2VtZXN0ZXIgKHRvZGF5IGFuZCBhZnRlciBTcHJpbmcgQnJlYWsgMy81IC0gMy8xMyksIHdlIHdpbGwgY29udGludW91c2x5IHV0aWxpemUgYW5kIGV4dGVuZCB0aGVzZSAnZnVuZGFtZW50YWwnIHNraWxscyB0byBtYXhpbWl6ZSBgUmAgYW5kIFJzdHVkaW8ncyBwb3RlbnRpYWwgZm9yIGRhdGEgc2NpZW5jZSBhbmQgYW5hbHlzaXMuIFdpdGggdGhlIGFmb3JlbWVudGlvbmVkIGZ1bmRhbWVudGFscyBpbiBoYW5kLCB3ZSBhcmUgZ29pbmcgdG8gbGVhcm4gaG93IHRvIHVzZSB0aGVzZSAnYWR2YW5jZWQnIGNvbW11bmljYXRpb24gYW5kIGFuYWx5c2lzIHRvb2xzIC0tDQoNCjUuICoqRW5oYW5jZWQgVmlzdWFsaXphdGlvbioqIA0KICAgIC0gSW50ZXJhY3Rpdml0eSB3LyBgZ2dwbG90bHlgLCBgcmVhY3RhYmxlYA0KICAgIC0gRW5oYW5jZSBgZ3RgIHdpdGggYGd0RXh0cmFzYCAoYW5kIGBmbGV4dGFibGVgLCBgZnRFeHRyYWApDQogICAgLSBJbnRybyB0byBgZmxleGRhc2hib2FyZGANCjYuICoqV2Vic2l0ZXMgaW4gUiArIEdpdGh1YioqDQogICAgLSBCYXNpY3MsIHBlcnNvbmFsIHdlYnBhZ2UgDQogICAgLSBXZWJzaXRlcyBhcyBhbmFseXRpY2FsIGRpc3BsYXkgdG9vbHMNCiAgICAgICAgLSBFbWJlZGRpbmcgYGZsZXhkYXNoYm9hcmRgDQo3LiAqKlNwYXRpYWwgVmlzdWFsaXphdGlvbioqICoqKHRvZGF5KSoqDQogICAgLSAnVGlkeSBNYXBzJyB3LyBgc2Zgb3Jgc3BgICsgYHRpZHl2ZXJzZWAgKyBgZ2dtYXBzYCBhbmQgYGdncGxvdDJgDQogICAgLSBJbnRyb2R1Y3Rpb24gdG8gaW50ZXJhY3RpdmUgbWFwcyB3aXRoIGBnZ3Bsb3RseWAgYW5kL29yIGBsZWFmbGV0YA0KOC4gKipTaGlueSBBcHBzKioNCiAgICAtIEJhc2ljcw0KICAgIC0gUHVibGlzaCBpbiBhbiBgUmAgaG9zdGVkIHdlYnNpdGUNCiAgICAgICAgLSBFbWJlZGRpbmcgaW50ZXJhY3RpdmUgYGZsZXhkYXNoYm9hcmRzYA0KICAgICAgICAtIEVtYmVkZGluZyBgc2hpbnlgIGFwcHMNCjkuICoqTWlzY2VsbGFuZW91cyoqDQogICAgLSAqV29ya2luZyB3aXRoIGJpZyBkYXRhIGluIFIgYGR0X3BseXJgLCBgY29sbGFwc2VgLCBgaDJvYCwgYHNwYXJrbHlyYA0KICAgIC0gKldvcmtpbmcgd2l0aCBkYXRhYmFzZXMgaW4gUiBgZGJfcGx5cmANCiAgICAtICpIb3cgdG8gZGV2ZWxvcCBhIHBhY2thZ2Ugd2l0aCBSc3R1ZGlvICYgR2l0aHViDQogICAgLSAqV2Vic2NyYXBpbmcgd2l0aCBSDQoNCiR+JA0KJH4kDQoNCiMgey5wYW5lbCAucGFuZWwtcHJpbWFyeX0NCiMjIHsucGFuZWwtaGVhZGluZ30NCiMjIyBTbyB3aGF0IGV4YWN0bHkgYXJlIHdlIGdvaW5nIHRvIGRvIHRvZGF5PyB7LnBhbmVsLXRpdGxlfQ0KIyMgey5wYW5lbC1ib2R5fQ0KDQoNCkZpcnN0LCB3ZSdyZSBnb2luZyB0byBicmllZmx5IGNvdmVyIGpvaW5pbmcgbXVsdGlwbGUgZGF0YSBzb3VyY2VzIHdpdGggYGRwbHlyYCB3aXRoIHNwYXRpYWwgZXhhbXBsZXMuIFRoZW4gd2UncmUgZ29pbmcgdG8gYnVpbGQgb250byBsYXN0IHdlZWsncyBsZWN0dXJlIGFuZCBleHBhbmQgb3VyIHNwYXRpYWwgZGF0YSB0b29sYm94IGluIGBSYCB3aXRoIG1vcmUgYWR2YW5jZWQgdXNlIG9mIGBzZmAsIGBnZ21hcHNgLCBgdGlkeWNlbnN1c2AsIGFzIHdlbGwgYXMgYW4gaW50cm9kdWN0aW9uIHRvIGZ1bGx5IGludGVyYWN0aXZlIGBsZWFmbGV0YC4gRmluYWxseSwgd2Ugd2lsbCBhcHBseSB0aGVzZSB0b29scyBpbiBhbiBhY3Rpdml0eSB3aGVyZSB3ZSBjcmVhdGUgYW5kIGVkaXQgYSBtb3JlIGFkdmFuY2VkIHNwYXRpYWwgZGFzaGJvYXJkIHRvZ2V0aGVyLiAgDQoNCmBgYHtyIGV2YWwgPSBGQUxTRX0NCiNJbnN0YWxsIHRoZSBwYWNrYWdlcyBmb3IgdG9kYXkgaWYgeW91IGRvbid0IGFscmVhZHkgaGF2ZSB0aGVtDQppbnN0YWxsLnBhY2thZ2VzKGMoInNmIiwgImdnbWFwIiwgInRtYXAiLCAidGlkeWNlbnN1cyIsICJsZWFmbGV0IiwgIm9zbWRhdGEiLCAidGlncmlzIikpDQpgYGANCg0KIyBJSS4gSm9pbmluZyBkYXRhIGluIGBSYCB7Lmp1bWJvdHJvbn0gIA0KDQpKb2luaW5nIGRhdGEgZnJvbSBtdWx0aXBsZSBzb3VyY2VzIGlzIGFub3RoZXIgYXNwZWN0IG9mICpkYXRhIHdyYW5nbGluZyogd2hpY2ggd2FzIGNvdmVyZWQgaW4gUFVCSCA3NDYxLCBidXQgaXMgYW4gaW1wb3J0YW50IHBhcnQgb2Ygd29ya2luZyB3aXRoIHJlYWwtd29ybGQgZGF0YSB0aGF0IHdlIHNob3VsZCBtYWtlIHN1cmUgd2UncmUgb24gdGhlIHNhbWUgcGFnZSBhYm91dCBoZWFkaW5nIGludG8gdGhlIGZpbmFsIHByb2plY3QuICANCg0KTGF1cmEgTGUncyB3b25kZXJmdWwgbGVjdHVyZSByZWdhcmRpbmcgam9pbmluZyBkYXRhIGluIFIsIGFzIHdlbGwgYXMgYW4gZXhhbXBsZS9hY3Rpdml0eSB3aXRoIE5ZQyBmbGlnaHQgZGF0YSBjYW4gYmUgZm91bmQgb24gQ2FudmFzIFtoZXJlXShodHRwczovL2NhbnZhcy51bW4uZWR1L2NvdXJzZXMvMjkzMDQ5L3BhZ2VzL2pvaW5pbmctZGF0YS13aXRoLWRwbHlyKS4NCg0KIVtdKC4vaW1hZ2VzL3B1YmhfNzQ2Ml9taWR0ZXJtX3J1YnJpYy5wbmcpDQoNCiMgSUlJLiBBZHZhbmNlZCBgc2ZgLCBgZ2dtYXBgLCBgdGlkeWNlbnN1c2AsIGBnZ3Bsb3RgLCBgcGxvdGx5YCB7Lmp1bWJvdHJvbn0gIA0KDQojIFNpbXBsZSBGZWF0dXJlcyBgc2ZgIFJlc291cmNlcyAgIA0KDQpMaWtlIG1hbnkgdGhpbmdzIGluIHRoZSBgUmAgdW5pdmVyc2UsIHRoZSBgc2ZgIHBhY2thZ2UgaGFzIHdvbmRlcmZ1bCBkb2N1bWVudGF0aW9uIGFuZCBleGFtcGxlcy4gUGxlYXNlIHNwZW5kIHNvbWUgdGltZSByZXZpZXdpbmcgdGhlc2Ugb24geW91ciBvd24uICANCg0KMS4gW0ludHJvZHVjdGlvbl0oaHR0cHM6Ly9yLXNwYXRpYWwuZ2l0aHViLmlvL3NmL2FydGljbGVzL3NmMS5odG1sKQ0KMi4gW1JlYWRpbmcsIHdyaXRpbmcsIGNvbnZlcnRpbmcgU2ltcGxlIEZlYXR1cmVzXShodHRwczovL3Itc3BhdGlhbC5naXRodWIuaW8vc2YvYXJ0aWNsZXMvc2YyLmh0bWwpDQozLiBbTWFuaXB1bGF0aW5nIFNpbXBsZSBGZWF0dXJlc10oaHR0cHM6Ly9yLXNwYXRpYWwuZ2l0aHViLmlvL3NmL2FydGljbGVzL3NmMy5odG1sKQ0KNC4gW1Bsb3R0aW5nIFNpbXBsZSBGZWF0dXJlc10oaHR0cHM6Ly9yLXNwYXRpYWwuZ2l0aHViLmlvL3NmL2FydGljbGVzL3NmNS5odG1sKQ0KDQojIHsucGFuZWwgLnBhbmVsLXByaW1hcnl9DQojIyB7LnBhbmVsLWhlYWRpbmd9DQojIyMgRXhhbXBsZSAxLiAoTU4pIE1vcmUgYWR2YW5jZWQgYHNmYCArIGBnZ3Bsb3RseWAgIHsucGFuZWwtdGl0bGV9DQojIyB7LnBhbmVsLWJvZHl9ICANCg0KRmlyc3QsIGxldCdzIGRvd25sb2FkIHRoZSBgZ2d0aGVtZXNgIHBhY2thZ2UgZm9yIGEgZmV3IG1vcmUgdGhlbWF0aWMgY2hvaWNlcyBpbiBvdXIgYGdncGxvdGAncy4gIA0KDQpgYGB7ciBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0NCiNJbnN0YWxsIGdncmVwZWwgaWYgbmVjZXNzYXJ5IA0KaWYgKCFyZXF1aXJlKGdndGhlbWVzKSkgew0KICBpbnN0YWxsLnBhY2thZ2VzKCJnZ3RoZW1lcyIsIHF1aWV0ID0gVFJVRSkNCn0NCg0KI0NhbGwgdGhlIGxpYnJhcnkNCmxpYnJhcnkoZ2d0aGVtZXMsIHF1aWV0bHkgPSBUUlVFKQ0KYGBgDQoNCg0KTmV4dCwgbGV0J3MgcmVhZCBpbiBvdXIgTU4gLnNocCBmaWxlIChmcm9tIGxhc3Qgd2VlaydzIGxlY3R1cmUpLiAgDQoNCmBgYHtyIG1uX3JlYWR9DQojUmVhZCBpbiB0aGUgc2hhcGUgZmlsZSAoZG9uJ3QgbWFrZSBhIHRpYmJsZSkNCm1uLmRmIDwtIHN0X3JlYWQoIi4vZGF0YS9VU0FfQ291bnRpZXMvVVNBX0NvdW50aWVzLnNocCIsIHF1aWV0ID0gVFJVRSkgJT4lDQogIGphbml0b3I6OmNsZWFuX25hbWVzKCkgJT4lDQogIGZpbHRlcihzdGF0ZV9uYW1lICVpbiUgIk1pbm5lc290YSIpDQpgYGANCg0KTmV4dCwgbGV0J3MgYnVpbGQgb3VyIGBnZ3Bsb3RgIGJ1dCBhZGQgYSBsaXR0bGUgbW9yZSBpbmZvcm1hdGlvbiB3aXRoIG91ciB1c3VhbCBkYXRhIHdyYW5nbGluZyBza2lsbHMgYW5kIGVtcGxveSBhIGJldHRlciBgZ2d0aGVtZXNgLiAgDQoNCmBgYHtyIG1uX3Bsb3RfMX0NCm1uX3BvcC5nZyA8LSBtbi5kZiAlPiUNCiAgZHBseXI6OnNlbGVjdChuYW1lLCB3aGl0ZTpvdGhlciwgcmVudGVyX29jYywgb3duZXJfb2NjLCBnZW9tZXRyeSkgJT4lDQogIHJlbmFtZShjb3VudHkgPSBuYW1lKSAlPiUNCiAgcGl2b3RfbG9uZ2VyKA0KICAgIGNvbHMgICAgICA9IHdoaXRlOm90aGVyLCAjdGlkeSBsb25nIGRhdGEgYnkgY2F0ZWdvcnkNCiAgICBuYW1lc190byAgPSAicmFjZV9jYXRlZ29yeSIsDQogICAgdmFsdWVzX3RvID0gInJhY2VfcG9wIg0KICApICU+JQ0KICBtdXRhdGUoDQogICAgcmFjZV9jYXRlZ29yeSA9IHN0cl9yZXBsYWNlX2FsbChyYWNlX2NhdGVnb3J5LCAiXyIsICIgIikgJT4lDQogICAgICAgICAgICAgICAgICAgIHN0cl90b190aXRsZSgpICU+JQ0KICAgICAgICAgICAgICAgICAgICBhc19mYWN0b3IoKQ0KICApICU+JQ0KICBncm91cF9ieShjb3VudHkpICU+JSAjQ291bnR5IGxldmVsIHBvcHVsYXRpb24NCiAgbXV0YXRlKGNvdW50eV9wb3AgPSBzdW0ocmFjZV9wb3ApKSAlPiUNCiAgZ3JvdXBfYnkoY291bnR5LCByYWNlX2NhdGVnb3J5KSAlPiUNCiAgc3VtbWFyaXNlKA0KICAgIHBlcmNfcmFjZSA9IHJhY2VfcG9wIC8gY291bnR5X3BvcCwNCiAgICBwZXJjX3JlbnQgPSByZW50ZXJfb2NjIC8gKHJlbnRlcl9vY2MgKyBvd25lcl9vY2MpLA0KICAgIGdlb21ldHJ5ICAgPSBnZW9tZXRyeQ0KICApICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIG5lc3QoZGF0YSA9IGMoInJhY2VfY2F0ZWdvcnkiLCAicGVyY19yYWNlIiwgImdlb21ldHJ5IikpICU+JQ0KICBtdXRhdGUoDQogICAgdGV4dF9sYWJlbCA9IG1hcF9jaHIoLnggPSBkYXRhLCANCiAgICAgICAgICAgICAgICAgICAgIH5zdHJfYygNCiAgICAgICAgICAgICAgICAgICAgICAgIlxuIiwNCiAgICAgICAgICAgICAgICAgICAgICAgLngkcmFjZV9jYXRlZ29yeSwNCiAgICAgICAgICAgICAgICAgICAgICAgIjogIiwgDQogICAgICAgICAgICAgICAgICAgICAgIHNjYWxlczo6cGVyY2VudCgueCRwZXJjX3JhY2UsIGFjY3VyYWN5ID0gMC4wMDAxKSwNCiAgICAgICAgICAgICAgICAgICAgICAgY29sbGFwc2UgPSAiIg0KICAgICAgICAgICAgICAgICAgICAgICkNCiAgICAgICAgICAgICAgICAgKSwNCiAgICB0ZXh0X2xhYmVsID0gc3RyX2MoY291bnR5LCAiXG5EZW1vZ3JhcGhpY3MiLCB0ZXh0X2xhYmVsLCAiXG5BdmcuIFJlbnRhbCBQZXJjZW50YWdlOiAiLCBzY2FsZXM6OnBlcmNlbnQocGVyY19yZW50LCBhY2N1cmFjeSA9IDAuMDEpKQ0KICApICU+JQ0KICB1bm5lc3QoZGF0YSkgJT4lDQogIHN0X2FzX3NmKCkgJT4lDQogIGdncGxvdCgpICsNCiAgZ2VvbV9zZihhZXMoZmlsbCA9IHBlcmNfcmVudCwgdGV4dCA9IHRleHRfbGFiZWwpLA0KICAgICAgICAgIGNvbG91ciA9ICJibGFjayIsIHNpemUgPSAwLjgsIGFscGhhID0gMC42KSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiMjAxNyBNTiBBQ1MgUmVudCB2cy4gT3duICUgYnkgQ291bnR5IiANCiAgKSArDQogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCJQZXJjZW50IFJlbnRhbCIsIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkgKw0KICB0aGVtZV9tYXAoKSArDQogIHRoZW1lKA0KICAgIHBsb3QudGl0bGUgICA9IGVsZW1lbnRfdGV4dChzaXplICA9IDI0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoanVzdCA9IDAuNSksDQogICAgbGVnZW5kLnRleHQgID0gZWxlbWVudF90ZXh0KHNpemUgPSAyMCksDQogICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAyMCksDQogICAgbGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0Ig0KICApDQoNCg0KI1Bsb3RseQ0KZ2dwbG90bHkobW5fcG9wLmdnLCANCiAgICAgICAgIHRvb2x0aXAgPSAidGV4dCIsDQogICAgICAgICBoZWlnaHQgID0gNjAwLA0KICAgICAgICAgd2lkdGggICA9IDgwMCkgJT4lDQogIHN0eWxlKGhvdmVyb24gPSAiZmlsbHMiKQ0KYGBgDQoNCiMgQWR2YW5jZWQgYHRpZHljZW5zdXNgICANCg0KIyMgV29ya2luZyB3aXRoIGBwbG90bHlgIA0KDQojIHsucGFuZWwgLnBhbmVsLXByaW1hcnl9DQojIyB7LnBhbmVsLWhlYWRpbmd9DQojIyMgRXhhbXBsZSAyLiAoTU4pIE1vcmUgYWR2YW5jZWQgYHRpZHljZW5zdXNgICsgYGdncGxvdGx5YCB7LnBhbmVsLXRpdGxlfQ0KIyMgey5wYW5lbC1ib2R5fSAgDQoNClJlcXVlc3QgdGhlIEFDUyBlc3RpbWF0ZXMgZm9yIF9tZWRpYW4gaW5jb21lXyBpbiBIZW5uZXBpbiBDb3VudHksIE1OLCBhdCB0aGUgY2Vuc3VzIHRyYWN0IGxldmVsIGZyb20gdGhlIFVTIGNlbnN1cy5nb3YgdmlhIGB0aWR5Y2Vuc3VzYC4NCg0KYGBge3IgdGlkeV9jZW5zdXMsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KI1NhdmUgdGhlIGNhY2hlIHNvIHlvdSBkb24ndCBuZWVkIHRvIGNhbGwgdGhlIHNhbWUgQVBJIHJlcXVlc3Qgb3ZlciBhbmQgb3Zlcg0Kb3B0aW9ucyh0aWdyaXNfdXNlX2NhY2hlID0gVFJVRSkNCg0KI0NhbGwgdGhlIEFDUyBBUEksIHJldHVybnMgYSBzZiBvYmplY3QNCm1uX2luY29tZS5kZiA8LSBnZXRfYWNzKA0KICBnZW9ncmFwaHkgPSAidHJhY3QiLA0KICB2YXJpYWJsZXMgPSAiQjE5MDEzXzAwMSIsICNDb2RlIGZvciBtZWRpYW4gaW5jb21lDQogIHN0YXRlICAgICA9ICJNTiIsDQogIGNvdW50eSAgICA9ICJIZW5uZXBpbiIsDQogIHllYXIgICAgICA9IDIwMjAsDQogIGdlb21ldHJ5ICA9IFRSVUUNCikNCmBgYA0KDQpOb3cgbGV0J3MgcGxvdCBpdCB3aXRoIGEgbmljZSB0aGVtZSBhbmQgdHVybiBpdCBpbnRvIGEgcGxvdGx5DQoNCmBgYHtyIG1uX2luY29tZX0NCiNBZGQgYSB0ZXh0IGxhYmVsIHRvIG1uX2luY29tZS5kZg0KbW5faW5jb21lX3Bsb3RseS5kZiA8LSBtbl9pbmNvbWUuZGYgJT4lDQogIG11dGF0ZSgNCiAgICB0cmFjdCAgICAgID0gc3RyX3NwbGl0KE5BTUUsICIsIikgJT4lDQogICAgICAgICAgICAgICAgIG1hcF9jaHIoMSkgJT4lDQogICAgICAgICAgICAgICAgIHN0cl9yZW1vdmUoIkNlbnN1cyBUcmFjdCAiKSwNCiAgICB0ZXh0X2xhYmVsID0gc3RyX2MoDQogICAgICAgICAgICAgICAgICAiVHJhY3Q6ICIsDQogICAgICAgICAgICAgICAgICB0cmFjdCwNCiAgICAgICAgICAgICAgICAgICJcbk1lZGlhbiBJbmNvbWU6ICIsDQogICAgICAgICAgICAgICAgICBzY2FsZXM6OmRvbGxhcihlc3RpbWF0ZSkNCiAgICAgICAgICAgICAgICAgKQ0KICApDQoNCiNHZW5lcmF0ZSB0aGUgZ2dwbG90DQppbmNvbWUuZ2cgPC0gZ2dwbG90KCkgKyANCiAgZ2VvbV9zZihkYXRhID0gbW5faW5jb21lX3Bsb3RseS5kZiwgDQogICAgICAgICAgYWVzKGZpbGwgPSBlc3RpbWF0ZSwgdGV4dCA9IHRleHRfbGFiZWwpLA0KICAgICAgICAgIGNvbG91ciA9ICJibGFjayIsIHNpemUgPSAwLjEpICsgDQogIGxhYnModGl0bGUgPSAiSGVubmVwaW4gQ291bnR5LCBNTiAyMDIwIEFDUyBNZWRpYW4gSW5jb21lIikgKyANCiAgc2NhbGVfZmlsbF92aXJpZGlzX2MoIk1lZGlhbiBJbmNvbWUiLCBsYWJlbHMgPSBzY2FsZXM6OmRvbGxhcikgKw0KICB0aGVtZV9tYXAoKSArDQogIHRoZW1lKA0KICAgIHBsb3QudGl0bGUgICA9IGVsZW1lbnRfdGV4dChzaXplICA9IDI0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoanVzdCA9IDAuNSksDQogICAgbGVnZW5kLnRleHQgID0gZWxlbWVudF90ZXh0KHNpemUgPSAyMCksDQogICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAyMCksDQogICAgbGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0Ig0KICApDQoNCg0KI0Rpc3BsYXkNCmdncGxvdGx5KGluY29tZS5nZywNCiAgICAgICAgIHRvb2x0aXAgPSAidGV4dCIsDQogICAgICAgICBoZWlnaHQgID0gNjAwLA0KICAgICAgICAgd2lkdGggICA9IDgwMCkgJT4lDQogICAgc3R5bGUoaG92ZXJvbiA9ICJmaWxscyIpDQpgYGANCg0KIyMgQ29tYmluaW5nIHdpdGggYGdnbWFwYCAgDQoNCiMgey5wYW5lbCAucGFuZWwtcHJpbWFyeX0NCiMjIHsucGFuZWwtaGVhZGluZ30NCiMjIyBFeGFtcGxlIDMuIChNTikgTW9yZSBhZHZhbmNlZCBgZ2dtYXBgICsgYHRpZHljZW5zdXNgICsgYGdncGxvdGx5YCB7LnBhbmVsLXRpdGxlfQ0KIyMgey5wYW5lbC1ib2R5fSAgDQoNCk5vdywgbGV0J3MgdGFrZSB0aGUgc2FtZSBwbG90IGFzIGFib3ZlIGJ1dCBvdmVybGF5IGl0IG9udG8gYSAnbmljZScgbWFwIG9mIE1OIHdpdGggYGdnbWFwYC4gIA0KDQpGb3IgcmVmZXJlbmNlLCBbaGVyZV0oaHR0cHM6Ly9yZHJyLmlvL2NyYW4vZ2dtYXAvbWFuL2dldF9tYXAuaHRtbCkgaXMgdGhlIGNvbXBsZXRlIGRvY3VtZW50YXRpb24gZm9yIHRoZSBvcHRpb25zIGFuZCB0eXBlcyBvZiBtYXBzIGF2YWlsYWJsZSB3aXRoIGBnZ21hcGAuIEFsdGVybmF0aXZlbHksIGEgbmljZSBjaGVhdCBzaGVldCBmb3IgYGdnbWFwYCBjYW4gYmUgZm91bmQgW2hlcmVdKGh0dHBzOi8vd3d3Lm5jZWFzLnVjc2IuZWR1L3NpdGVzL2RlZmF1bHQvZmlsZXMvMjAyMC0wNC9nZ21hcENoZWF0c2hlZXQucGRmKSAgDQoNCiMjIyAxLiBDcmVhdGUgdGhlIGJhc2UgbWFwICANCg0KYGBge3IgZ2dtYXBfYmFzZSwgbWVzc2FnZSA9IEZBTFNFfQ0KI0dldCB0aGUgYm91bmRpbmcgYm94IGFuZCBjb3VudHkgb3V0bGluZQ0KbW4uYm94ICAgICAgICAgICA8LSBvc21kYXRhOjpnZXRiYigibWlubmVzb3RhIikNCmhlbm5lcGluLmJveCAgICAgPC0gb3NtZGF0YTo6Z2V0YmIoImhlbm5lcGluIikNCmhlbm5lcGluLm91dGxpbmUgPC0gb3NtZGF0YTo6Z2V0YmIoImhlbm5lcGluIiwgZm9ybWF0X291dCA9ICJwb2x5Z29uIilbWzFdXSAlPiUNCiAgYXNfdGliYmxlKCkgJT4lDQogIHJlbmFtZShsb25naXR1ZGUgPSBWMSwgbGF0aXR1ZGUgPSBWMikNCg0KDQojR2V0IG1hcCBmcm9tIGdnbWFwDQojR2V0IHRoZSBiYXNlIG1hcCAoZm91bmRhdGlvbmFsIGxheWVyKQ0KbW5fYmFzZS5tYXAgPC0gZ2V0X21hcCgNCiAgICAgICAgICAgICAgICBsb2NhdGlvbiA9IGhlbm5lcGluLmJveCwNCiAgICAgICAgICAgICAgICBzb3VyY2UgICA9ICJnb29nbGUiLA0KICAgICAgICAgICAgICAgIG1hcHR5cGUgID0gInJvYWRtYXAiLA0KICAgICAgICAgICAgICAgIGNyb3AgPSBUUlVFDQogICAgICAgICAgICAgICApDQoNCiNDcmVhdGUgdGhlIGJhc2UgbWFwDQpoZW5uZXBpbl9iYXNlLmdnIDwtIGdnbWFwKG1uX2Jhc2UubWFwKSArDQogIGdlb21fcG9seWdvbihkYXRhID0gbW4ub3V0bGluZSwgYWVzKHggPSBsb25naXR1ZGUsIHkgPSBsYXRpdHVkZSksIGNvbG91ciA9ICJibGFjayIsIHNpemUgPSAxLjYsIGFscGhhID0gMC4xKSArDQogIHRoZW1lX21hcCgpICsNCiAgdGhlbWUoDQogICAgcGxvdC50aXRsZSAgID0gZWxlbWVudF90ZXh0KHNpemUgID0gMjQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhqdXN0ID0gMC41KSwNCiAgICBsZWdlbmQudGV4dCAgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDIwKSwNCiAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDIwKSwNCiAgICBsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiDQogICkNCiAgICANCiNEaXNwbGF5IGJhc2UgbWFwDQpoZW5uZXBpbl9iYXNlLmdnDQpgYGANCg0KIyMjIDIuIEFkZCB0aGUgaW5jb21lIGxheWVyKHMpIGZyb20gYmVmb3JlIGFuZCB0cmFuc2Zvcm0gaW50byBgZ2dwbG90bHlgICANCg0KYGBge3IgZ2dtYXBfcGxvdGx5LCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgcmVzdWx0cyA9IEZBTFNFfQ0KI0ZpcnN0LCBuZWVkIHRvIG1ha2Ugc3VyZSB0aGUgY29vcmRpbmF0ZSBzeXN0ZW1zIGJldHdlZW4gdGhlIGdnbWFwIGFuZCBnZW9tX3NmIG1hdGNoDQpjb29yZF9nZ21hcCA8LSBzdF9jcnMoaGVubmVwaW5fYmFzZS5nZykgI05BDQpjb29yZF9zZiAgICA8LSBzdF9jcnMobW5faW5jb21lX3Bsb3RseS5kZikgI05BRDgzDQoNCiNPdmVybGF5IHRoZ2Ugc2YgaW5mbyBmcm9tIHRpZHljZW5zdXMgQUNTIGluY29tZSBlc3RpbWF0ZXMNCm1uX2luY29tZS5nZ21hcCA8LSBoZW5uZXBpbl9iYXNlLmdnICsgIA0KICBzdF9jcnMoaGVubmVwaW5fYmFzZS5nZykNCnN0X2Nycyhtbl9pbmNvbWVfcGxvdGx5LmRmKQ0KICBnZW9tX3NmKGRhdGEgPSBtbl9pbmNvbWVfcGxvdGx5LmRmLCANCiAgICAgICAgICBhZXMoZmlsbCA9IGVzdGltYXRlLCB0ZXh0ID0gdGV4dF9sYWJlbCksDQogICAgICAgICAgY29sb3VyID0gImJsYWNrIiwgc2l6ZSA9IDAuMSwNCiAgICAgICAgICBpbmhlcml0LmFlcyA9IEZBTFNFKSArIA0KICBsYWJzKHRpdGxlID0gIkhlbm5lcGluIENvdW50eSwgTU4gMjAyMCBBQ1MgTWVkaWFuIEluY29tZSIpICsgDQogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCJNZWRpYW4gSW5jb21lIiwgbGFiZWxzID0gc2NhbGVzOjpkb2xsYXIpICsNCiAgdGhlbWVfbWFwKCkgKw0KICB0aGVtZSgNCiAgICBwbG90LnRpdGxlICAgPSBlbGVtZW50X3RleHQoc2l6ZSAgPSAyNCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGp1c3QgPSAwLjUpLA0KICAgIGxlZ2VuZC50ZXh0ICA9IGVsZW1lbnRfdGV4dChzaXplID0gMjApLA0KICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMjApLA0KICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCINCiAgKQ0KYGBgDQoNCmBgYHtyfQ0KI0Rpc3BsYXkgcGxvdGx5DQpnZ3Bsb3RseShtbl9pbmNvbWUuZ2dtYXAsDQogICAgICAgICB0b29sdGlwID0gInRleHQiLA0KICAgICAgICAgaGVpZ2h0ICA9IDYwMCwNCiAgICAgICAgIHdpZHRoICAgPSA4MDApICU+JQ0KICAgIHN0eWxlKGhvdmVyb24gPSAiZmlsbHMiKQ0KYGBgDQoNCiMjIEpvaW5pbmcgb3RoZXIgc3BhdGlhbCBkYXRhICAoYG9zbWRhdGFgKQ0KDQojIEFkdmFuY2VkIGBnZ21hcGAgIA0KDQojIyBXb3JraW5nIHdpdGggYHBsb3RseWAgIA0KDQojIyBXb3JraW5nIHdpdGggYHRpZHljZW5zdXNgICANCg0KIyBJSUkuIEFkdmFuY2VkIGBzZmAsIGBnZ21hcGAsIGB0aWR5Y2Vuc3VzYCwgYGdncGxvdGAsIGBwbG90bHlgIHsuanVtYm90cm9ufSAgDQoNCg0KIyBJVi4gTGVhZmxldCB7Lmp1bWJvdHJvbn0gIA0KDQojIEludHJvZHVjdGlvbiB0byBgbGVhZmxldGAgIA0KDQojIHsucGFuZWwgLnBhbmVsLXByaW1hcnl9DQojIyB7LnBhbmVsLWhlYWRpbmd9DQojIyMgRXhhbXBsZSAxLiBOWUMgQWlyYm5iIHsucGFuZWwtdGl0bGV9DQojIyB7LnBhbmVsLWJvZHl9DQoNCiMgVi4gQWN0aXZpdHkgey5qdW1ib3Ryb259ICANCg0KIyB7LnBhbmVsIC5wYW5lbC1zdWNjZXNzfQ0KIyMgey5wYW5lbC1oZWFkaW5nfQ0KIyMjIEFjdGl2aXR5IDEgKHRvZ2V0aGVyKS4gQ3JlYXRpbmcgYSBzcGF0aWFsIGRhc2hib2FyZCBvZiBOWUMgQWlyYm5iJ3MgaW4gTWFuaGF0dGFuIHsucGFuZWwtdGl0bGV9DQojIyB7LnBhbmVsLWJvZHl9ICANCg0KVG9kYXkncyBOWUMgc3BhdGlhbCBkYXNoYm9hcmQgY2FuIGJlIGFjY2Vzc2VkIG9uIENhbnZhcyB1bmRlciBbV2VlayAxMSBMZWN0dXJlIE1hdGVyaWFsc10oaHR0cHM6Ly9jYW52YXMudW1uLmVkdS9jb3Vyc2VzLzI5MzA0OS9wYWdlcy93ZWVrLTExLWxlY3R1cmU/bW9kdWxlX2l0ZW1faWQ9NzQ5NjI3MCkuICANCg0KIyB7LnBhbmVsIC5wYW5lbC1zdWNjZXNzfQ0KIyMgey5wYW5lbC1oZWFkaW5nfQ0KIyMjIEFjdGl2aXR5IDIgKHlvdXIgdHVybikuIEVkaXQgdGhlIGRhc2hib2FyZCB7LnBhbmVsLXRpdGxlfQ0KIyMgey5wYW5lbC1ib2R5fSAgDQoNCkZvciB0b2RheSdzIGFjdGl2aXR5LCBwbGVhc2UgLS0gDQoNCjEuIFNldCB1cCBhIEdvb2dsZSBNYXBzIEFQSSBrZXkgKGRpcmVjdGlvbnMgYWJvdmUpIGZvciBgZ2dtYXBgDQoyLiBFZGl0IHRoZSBkYXNoYm9hcmQgYXMgZm9sbG93cyAtLSANCiAgICAtIFBpY2sgYW5vdGhlciB2YXJpYWJsZSBmcm9tIHRoZSBbQUNTIENvZGVib29rXSgpIGFuZCByZXBsYWNlIHRoZSBNZWRpYW4gSW5jb21lIHBsb3Qgd2l0aCB0aGlzIG5ldyB2YXJpYWJsZQ0KICAgIC0gQ29sb3VyIHRoZSBtYXAncyBwb2ludHMgYnkgcmF0aW5nIGluc3RlYWQgb2YgcHJpY2UNCiAgICAtIFNob3cgdGhlIGRpc3RyaWJ1dGlvbiBvZiByYXRpbmdzIHJhdGhlciB0aGFuIHByaWNlIChib3hwbG90KSAgIA0K