Welcome to your first lesson in mapping! Mapping forms the foundation of most geographic analysis, and maps help us to better understand spatial patterns and distributions. But it isn’t enough to just make a map - we need to make maps that are accurate, clear, and effective geographical representations of the phenomena we are interested in. In this tutorial, we will start learning some of the tools that will help us do just that in R. We will start mapping using the urbnmapr package, which will help us to make maps at the state and county level.

All maps are made up of points, lines, and polygons. A point is a single dot on a map that corresponds to one lat/long coordinate. A line consists of a series of dots connected together, while a polygon is a shape that is created by the intersection of multiple lines. We will be working on learning to symbolize our maps using points and polygons in this lesson.

#Load the urbnmapr package and the sf (spatial features) library 
#The sf package will help us to work with the data from the urbnmapr package
#We'll talk about sf in more detail later, but sf (simple features) objects are essentially the points, lines, and polygons that we will use to make maps - this is a special object type that is similar to the shapefiles that are used in GIS software
#Each sf object contains a special "geometry" column that defines the spatial properties of the object (don't worry, you don't need to understand it any deeper than that)
#Later on, I will show you how to transform lat/long coordinates into an sf object
library(sf)
library(urbnmapr)
library(dplyr)
library(ggplot2)
library(ggthemes)

#grab county shape files for our map
counties <- get_urbn_map("counties", sf = TRUE)

#filter the data to get just NYS
counties <- counties %>% 
  filter(state_abbv == "NY")
old-style crs object detected; please recreate object with a recent sf::st_crs()
head(counties)
Simple feature collection with 6 features and 6 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 1797804 ymin: -99527.13 xmax: 2146630 ymax: 325411.8
Projected CRS: NAD27 / US National Atlas Equal Area
  county_fips state_abbv state_fips        county_name fips_class
1       36021         NY         36    Columbia County         H1
2       36087         NY         36    Rockland County         H1
3       36101         NY         36     Steuben County         H1
4       36123         NY         36       Yates County         H1
5       36033         NY         36    Franklin County         H1
6       36093         NY         36 Schenectady County         H1
  state_name                       geometry
1   New York MULTIPOLYGON (((2106726 192...
2   New York MULTIPOLYGON (((2114963 -92...
3   New York MULTIPOLYGON (((1800842 -40...
4   New York MULTIPOLYGON (((1821444 -41...
5   New York MULTIPOLYGON (((1947143 308...
6   New York MULTIPOLYGON (((2056558 791...

Let’s plot the state-level data that we have!

ggplot() +
  geom_sf(data = counties,
    mapping = aes())+
  theme_minimal()

Hmmmm…NYS shouldn’t be crooked! What happened?

All maps have a Coordinate Reference System (CRS) - this consists of the coordinate system, datum, and map projection specified by the map creator. The default CRS works well for the entire US, but it doesn’t work as nicely for NYS. We need to define a more regionally-specific CRS. Let’s start by checking the current CRS of the map.

#This command comes from the sf package
st_crs(counties)
Coordinate Reference System:
  User input: EPSG:2163 
  wkt:
PROJCRS["NAD27 / US National Atlas Equal Area",
    BASEGEOGCRS["NAD27",
        DATUM["North American Datum 1927",
            ELLIPSOID["Clarke 1866",6378206.4,294.978698213898,
                LENGTHUNIT["metre",1]]],
        PRIMEM["Greenwich",0,
            ANGLEUNIT["degree",0.0174532925199433]],
        ID["EPSG",4267]],
    CONVERSION["US National Atlas Equal Area",
        METHOD["Lambert Azimuthal Equal Area (Spherical)",
            ID["EPSG",1027]],
        PARAMETER["Latitude of natural origin",45,
            ANGLEUNIT["degree",0.0174532925199433],
            ID["EPSG",8801]],
        PARAMETER["Longitude of natural origin",-100,
            ANGLEUNIT["degree",0.0174532925199433],
            ID["EPSG",8802]],
        PARAMETER["False easting",0,
            LENGTHUNIT["metre",1],
            ID["EPSG",8806]],
        PARAMETER["False northing",0,
            LENGTHUNIT["metre",1],
            ID["EPSG",8807]]],
    CS[Cartesian,2],
        AXIS["easting (X)",east,
            ORDER[1],
            LENGTHUNIT["metre",1]],
        AXIS["northing (Y)",north,
            ORDER[2],
            LENGTHUNIT["metre",1]],
    USAGE[
        SCOPE["Statistical analysis."],
        AREA["United States (USA) - onshore and offshore."],
        BBOX[15.56,167.65,74.71,-65.69]],
    ID["EPSG",9311]]

Based on this, we can see that the CRS is EPSG:2163. The datum is North American Datum 1927, while the map uses the US National Atlas Equal Area projection. All we really need to know here is what the current CRS is, so that we can determine whether to alter it. For our NYS map, we will need to change the CRS.

How do we know what CRS to choose? There are likely a number of CRS’ that would work well for this map. To pick one, we can look at a list of state projections here: https://github.com/veltman/d3-stateplane. I picked one of the NY specific options.

#Transform the CRS

counties <- counties %>% 
  st_transform("EPSG:32116")

#Let's see if this looks any better

ggplot() +
  geom_sf(data = counties,
    mapping = aes())+
  theme_minimal()


#Much better!

Our map is really coming along! Next, we are going to want to load in some data to visualize on the map. We’ll start with the Covid-19 data that you used for your homework assignment. This first type of map is called a choropleth map - essentially, this is a map that uses the polygons on a map (in our case, counties) to visualize a variable with color.

#Set wd
setwd("~/Binghamton/geog380")

#load in the data
covid <- read.csv("covid_data_ny.csv")

#We'll need to merge this data with our counties dataset
#We can use the left_join function to do this
#First though, we need to inspect our ID columns

unique(covid$County)
 [1] "Kings"        "Queens"       "New York"     "Suffolk"     
 [5] "Nassau"       "Bronx"        "Westchester"  "Erie"        
 [9] "Richmond"     "Monroe"       "Onondaga"     "Orange"      
[13] "Rockland"     "Dutchess"     "Albany"       "Oneida"      
[17] "Saratoga"     "Niagara"      "Broome"       "Schenectady" 
[21] "Ulster"       "Rensselaer"   "Oswego"       "Putnam"      
[25] "Chautauqua"   "Chemung"      "St. Lawrence" "Tompkins"    
[29] "Jefferson"    "Ontario"      "Steuben"      "Sullivan"    
[33] "Clinton"      "Wayne"        "Cayuga"       "Cattaraugus" 
[37] "Warren"       "Herkimer"     "Madison"      "Genesee"     
[41] "Fulton"       "Washington"   "Montgomery"   "Livingston"  
[45] "Tioga"        "Columbia"     "Cortland"     "Otsego"      
[49] "Franklin"     "Chenango"     "Greene"       "Allegany"    
[53] "Orleans"      "Wyoming"      "Delaware"     "Essex"       
[57] "Seneca"       "Lewis"        "Schoharie"    "Schuyler"    
[61] "Yates"        "Hamilton"    
unique(counties$county_name)
 [1] "Columbia County"     "Rockland County"     "Steuben County"     
 [4] "Yates County"        "Franklin County"     "Schenectady County" 
 [7] "Fulton County"       "Lewis County"        "Allegany County"    
[10] "Montgomery County"   "Tompkins County"     "Orleans County"     
[13] "Suffolk County"      "Jefferson County"    "Clinton County"     
[16] "Wyoming County"      "Schuyler County"     "Niagara County"     
[19] "Hamilton County"     "Genesee County"      "Cattaraugus County" 
[22] "Monroe County"       "New York County"     "Oswego County"      
[25] "Tioga County"        "Madison County"      "Queens County"      
[28] "Bronx County"        "Livingston County"   "Orange County"      
[31] "Albany County"       "Ontario County"      "Essex County"       
[34] "Greene County"       "Broome County"       "Herkimer County"    
[37] "Otsego County"       "Washington County"   "Chenango County"    
[40] "Dutchess County"     "Chemung County"      "Cortland County"    
[43] "Saratoga County"     "Delaware County"     "Nassau County"      
[46] "Erie County"         "Cayuga County"       "Rensselaer County"  
[49] "Westchester County"  "Putnam County"       "Ulster County"      
[52] "Kings County"        "Wayne County"        "St. Lawrence County"
[55] "Seneca County"       "Schoharie County"    "Oneida County"      
[58] "Richmond County"     "Warren County"       "Onondaga County"    
[61] "Chautauqua County"   "Sullivan County"    
#There's an easier way to do this!
#We can use a logical operation to check whether the county names match
#The names need to be in the same order, or we'll get only False
counties <- counties %>% arrange(county_name)
covid <- covid %>% arrange(County)

counties$county_name == covid$County
 [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[12] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[23] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[34] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[45] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[56] FALSE FALSE FALSE FALSE FALSE FALSE FALSE

We have a problem! The counties data contains the word “County” in each county name, while the Covid data does not. You will run into problems like this pretty often, which is why it is so important to inspect your data before you analyze it. We can fix this with the separate() function from the tidyr package like this:

library(tidyr)

#Think about the county names - why did I use " County" as a separator instead of just a space?
counties1 <- counties %>% 
  separate(county_name, c("county_name", "county"), sep = " County") %>% 
  select(-county)

#Did it work?
head(counties1)
Simple feature collection with 6 features and 6 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 40348.68 ymin: 92189.85 xmax: 482314.7 ymax: 381253.8
Projected CRS: NAD83 / New York Central
  county_fips state_abbv state_fips county_name fips_class state_name
1       36001         NY         36      Albany         H1   New York
2       36003         NY         36    Allegany         H1   New York
3       36005         NY         36       Bronx         H6   New York
4       36007         NY         36      Broome         H1   New York
5       36009         NY         36 Cattaraugus         H1   New York
6       36011         NY         36      Cayuga         H1   New York
                        geometry
1 MULTIPOLYGON (((435438.3 27...
2 MULTIPOLYGON (((103693.1 28...
3 MULTIPOLYGON (((467992.4 10...
4 MULTIPOLYGON (((282328.4 26...
5 MULTIPOLYGON (((42112.87 28...
6 MULTIPOLYGON (((232317.8 31...
#Let's check if they match now!

counties1$county_name == covid$County
 [1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
[14] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
[27] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
[40] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
[53] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE

Now we can proceed to join the data.

covid_county <- counties1 %>%
  left_join(
    covid,
    #This command allows us to join the data using two variables with different names
    by = c("county_name" = "County")
  )

head(covid_county)
Simple feature collection with 6 features and 12 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 40348.68 ymin: 92189.85 xmax: 482314.7 ymax: 381253.8
Projected CRS: NAD83 / New York Central
  county_fips state_abbv state_fips county_name fips_class state_name
1       36001         NY         36      Albany         H1   New York
2       36003         NY         36    Allegany         H1   New York
3       36005         NY         36       Bronx         H6   New York
4       36007         NY         36      Broome         H1   New York
5       36009         NY         36 Cattaraugus         H1   New York
6       36011         NY         36      Cayuga         H1   New York
  new_positive positive_cumulative new_tests tests_cumulative geography
1           47               70822       461          1353242    COUNTY
2            7                9849       166           241680    COUNTY
3          347              454174      4510          8453317    COUNTY
4           42               52622       464          1135294    COUNTY
5           23               17488       146           296531    COUNTY
6           12               18378       172           378493    COUNTY
         region                       geometry
1       capital MULTIPOLYGON (((435438.3 27...
2       western MULTIPOLYGON (((103693.1 28...
3      new york MULTIPOLYGON (((467992.4 10...
4 southern tier MULTIPOLYGON (((282328.4 26...
5       western MULTIPOLYGON (((42112.87 28...
6       central MULTIPOLYGON (((232317.8 31...

Now, we’ll create our first map!

#Using Ggplot2 and the urbnmapr package to produce the map:
ggplot() +
  geom_sf(data = covid_county,
    mapping = aes(fill = new_positive))+
  theme_minimal()

Hmmmmmmm….that’s not very nice looking, is it? Any suggestions?

First, let’s talk about normalizing our data. It looks like NYC has a huge number of cases, but we also know that NYC is bigger than any other region in the state. What might this look like from a more relative perspective? Often, it is much more meaningful to normalize our data so that we can better compare across counties. For example, here I am going to map positive as a percentage of total tests. Other common ways to normalize the data include calculating the number of cases per capita (using population data) or the percent growth in positive cases (if we had data over time).

#calculate the new variable that we will map
covid_county <- covid_county %>% 
  mutate(positive_percent = new_positive/new_tests)

#inspect the data
head(covid_county)
Simple feature collection with 6 features and 13 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 40348.68 ymin: 92189.85 xmax: 482314.7 ymax: 381253.8
Projected CRS: NAD83 / New York Central
  county_fips state_abbv state_fips county_name fips_class state_name
1       36001         NY         36      Albany         H1   New York
2       36003         NY         36    Allegany         H1   New York
3       36005         NY         36       Bronx         H6   New York
4       36007         NY         36      Broome         H1   New York
5       36009         NY         36 Cattaraugus         H1   New York
6       36011         NY         36      Cayuga         H1   New York
  new_positive positive_cumulative new_tests tests_cumulative geography
1           47               70822       461          1353242    COUNTY
2            7                9849       166           241680    COUNTY
3          347              454174      4510          8453317    COUNTY
4           42               52622       464          1135294    COUNTY
5           23               17488       146           296531    COUNTY
6           12               18378       172           378493    COUNTY
         region                       geometry positive_percent
1       capital MULTIPOLYGON (((435438.3 27...       0.10195228
2       western MULTIPOLYGON (((103693.1 28...       0.04216867
3      new york MULTIPOLYGON (((467992.4 10...       0.07694013
4 southern tier MULTIPOLYGON (((282328.4 26...       0.09051724
5       western MULTIPOLYGON (((42112.87 28...       0.15753425
6       central MULTIPOLYGON (((232317.8 31...       0.06976744

Everything looks pretty good! Now we’ll map this new variable.

ggplot() +
  geom_sf(data = covid_county,
    mapping = aes(fill = positive_percent))+
  theme_minimal()

This looks much nicer, but we’re not done yet! I don’t like that the color gradient starts with dark blue - low values are colored in dark colors, while high values are lighter in this map. That’s not very intuitive. Let’s play around with the color gradient and some color options for our map.

ggplot() +
  geom_sf(data = covid_county,
    mapping = aes(fill = positive_percent))+
  theme_minimal()+
scale_fill_gradient(
    # Make legend title more readable
    name = "Percent \nPositive Cases",
    #set the color gradient
    low = "lightblue",
    high = "navyblue") +
labs(title = "Positive Covid Cases in NYS")+
theme(legend.title.align = 0.5,
      plot.title = element_text(hjust = 0.5))

NA

Unfortunately, we cannot center the legend box without creating a new function to do so or hacking the source code - ggplot2 currently only supports a left-aligned legend box. Next, we’ll play around with some map colors and styles.

#play around with the color and theme
ggplot() +
  geom_sf(data = covid_county,
    mapping = aes(fill = positive_percent))+
  theme_minimal()+
  #Maybe I want to make my legend discrete - scale_fill_steps allows me to do that!
  scale_fill_steps(
    # Make legend title more readable
    name = "Percent \nPositive Cases",
    #set the color gradient
    low = "blue",
    high = "red",
    #Convert from decimal to percent format
    labels = scales::percent_format(),
    #tells R how many breaks to put in the data
    n.breaks = 4,
    #Tells R to include the min and max values in the legend
    show.limits = T) +
  labs(title = "Positive Covid Cases in NYS",
       subtitle = "As a Percent of Daily Tests",
       caption = "Source: health.data.ny.gov")+
  theme(legend.title.align = 0.5,
      plot.title = element_text(hjust = 0.5, size = 18),
      plot.subtitle = element_text(hjust = 0.5),
      plot.caption = element_text(hjust = 0.5),
      #Remove axis labels and ticks
      line = element_blank(),
      axis.text.x=element_blank(),
      axis.ticks.x=element_blank(),
      axis.text.y=element_blank(),
      axis.ticks.y=element_blank())

Ok, so now we’ve learned more about creating choropleth maps. What if I have lat/long coordinates? How can I map those? We’ll create a point map (in the tutorial it is called a Bubble map) to visualize these coordinates. Let’s map cities in NYS.

#load in some city data - this contains data for all cities in the US, so we'll have to filter for NY
#This data is from: https://simplemaps.com/data/us-cities 
cities <- read.csv("uscities.csv")

nycities <- cities %>% 
  filter(state_name == "New York",
         population > 100000)

nycities1 <- nycities %>% 
  #I need to transform my lat/long coordinates into an sf object 
  #4326 is the crs used in gps - it is defined in lat/long coords
  #we need to first convert to an sf object using this crs
  st_as_sf(coords = c("lng", "lat"), crs = 4326) %>% 
  #now we can correctly convert to the same crs as our counties file, which is defined in meters
  st_transform(crs = st_crs(counties))

#load in ggsflabel library
#make sure you install the devtools package first!
#This package will help us to make the labels look nice
devtools::install_github("yutannihilation/ggsflabel")
Skipping install of 'ggsflabel' from a github remote, the SHA1 (a489481b) has not changed since last install.
  Use `force = TRUE` to force installation
library(ggsflabel)

Attaching package: ‘ggsflabel’

The following objects are masked from ‘package:ggplot2’:

    geom_sf_label, geom_sf_text, StatSfCoordinates
#Let's plot our cities!
ggplot() +
  geom_sf(data = counties)+
  geom_sf(data = nycities1, color="blue")+
  #Here, I am just widening the plot area - I want to label my cities, but the labels
  #take up too much space
  #How did I find these units? You can print out your sf object, and it will tell you the    minimum and maximum x and y values for the object
  #The units are in meters
  coord_sf(ylim = c(20000, 550000))+
  #add the labels! The geom_sf_label_repel argument lets me repel overlapping labels
  #That way, we can read all of the text
  #force tells R the force of the repulsion - I set it at 20, which is a bit low
  #By default, it only displays the first 10 overlapping labels - I increased this to 20
  #I also made the labels size 2, which is slightly smaller than the default
  geom_sf_label_repel(data = nycities1, aes(label = city), force = 20, 
                      max.overlaps = 20, size = 2)+
  theme_minimal()+
  labs(title = "Major Cities in New York State")+
  theme(plot.title = element_text(hjust = 0.5, size = 16))

Ok, so there’s a lot going into this map. Here’s a breakdown of the steps you should use when you have lat/long coords:

  1. Check the crs of your basemap. When you use the st_crs() function, it will tell you about the crs. If you look where it says “LENGTHUNIT”, it’ll tell you the units that your crs is in. The crs we used to map our counties was actually defined in meters, not lat/long coords.
  2. If your base map doesn’t use lat/long coords, then there are two steps to transforming your new data in order to map on top of the base map:
  1. Use the st_as_sf function and crs 4326 to initially create your sf object
  2. Then, use st_transform to transform the object into your desired final crs *if you don’t know what that is, you can put st_crs(my_base_map) into the function to ensure that your point object matches your original base map (my_base_map) Note: if you forget to first transform your coordinates into crs 4326, your sf object will convert incorrectly and not be compatible with your base map. You’ll know this is the case because no points will appear.

Using these steps, you should be able to convert any lat/long coords into an sf object! Don’t worry if you don’t understand everything that is going on underneath these functions - you really just need to be able to follow the instructions here.

Ok, we will work on one final skill using point data today: sizing the points. Suppose I wanted to compare the cities by their population per sqkm (density) - I could change the sizes of my points to accomplish that!

#Re-run some code from earlier - I don't want to remove small cities this time
nycities <- cities %>% 
  filter(state_name == "New York") %>% 
  filter(population > 20000)

nycities1 <- nycities %>% 
  st_as_sf(coords = c("lng", "lat"), crs = 4326) %>% 
  st_transform(crs = st_crs(counties))


options(scipen = 999)

ggplot() +
  geom_sf(data = counties)+
  geom_sf(data = nycities1, aes(size = density), color="blue", alpha = 0.5)+
  theme_minimal()+
  labs(title = "Major Cities in New York State")+
  theme(plot.title = element_text(hjust = 0.5, size = 16))

Hmmmm this is not terribly helpful…I could also think about changing the colors of the points!

ggplot() +
  geom_sf(data = counties, fill = "white")+
  geom_sf(data = nycities1, aes(color = density), size = 3)+
  theme_minimal()+
  labs(title = "Major Cities in New York State",
       color = "Density \n (People per sqkm)")+
  theme(plot.title = element_text(hjust = 0.5, size = 16),
        legend.title.align = 0.5,)+
  scale_color_gradient(
    #set the color gradient
    low = "lightblue",
    high = "navyblue")

Do we love this map? No (it’s really ugly! This is not a good way to visualize density!), but it illustrates an important skill.

Resources

New York State (2022). New York State Statewide Covid-19 Testing. [Data Set]. Retreived from: https://health.data.ny.gov/Health/New-York-State-Statewide-COVID-19-Testing/xdss-u53e.

Simple Maps (2022). United States Cities Database. [Data Set]. Retrieved from: https://simplemaps.com/data/us-cities.

Urban Institute (2022). Introduction to Mapping. Retrieved from: https://urbaninstitute.github.io/r-at-urban/mapping.html.

LS0tDQp0aXRsZTogIkdlb2cgMzgwQSBXZWVrIDQiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KV2VsY29tZSB0byB5b3VyIGZpcnN0IGxlc3NvbiBpbiBtYXBwaW5nISBNYXBwaW5nIGZvcm1zIHRoZSBmb3VuZGF0aW9uIG9mIG1vc3QgZ2VvZ3JhcGhpYyBhbmFseXNpcywgYW5kIG1hcHMgaGVscCB1cyB0byBiZXR0ZXIgdW5kZXJzdGFuZCBzcGF0aWFsIHBhdHRlcm5zIGFuZCBkaXN0cmlidXRpb25zLiBCdXQgaXQgaXNuJ3QgZW5vdWdoIHRvIGp1c3QgbWFrZSBhIG1hcCAtIHdlIG5lZWQgdG8gbWFrZSBtYXBzIHRoYXQgYXJlIGFjY3VyYXRlLCBjbGVhciwgYW5kIGVmZmVjdGl2ZSBnZW9ncmFwaGljYWwgcmVwcmVzZW50YXRpb25zIG9mIHRoZSBwaGVub21lbmEgd2UgYXJlIGludGVyZXN0ZWQgaW4uIEluIHRoaXMgdHV0b3JpYWwsIHdlIHdpbGwgc3RhcnQgbGVhcm5pbmcgc29tZSBvZiB0aGUgdG9vbHMgdGhhdCB3aWxsIGhlbHAgdXMgZG8ganVzdCB0aGF0IGluIFIuIFdlIHdpbGwgc3RhcnQgbWFwcGluZyB1c2luZyB0aGUgdXJibm1hcHIgcGFja2FnZSwgd2hpY2ggd2lsbCBoZWxwIHVzIHRvIG1ha2UgbWFwcyBhdCB0aGUgc3RhdGUgYW5kIGNvdW50eSBsZXZlbC4gDQoNCkFsbCBtYXBzIGFyZSBtYWRlIHVwIG9mIHBvaW50cywgbGluZXMsIGFuZCBwb2x5Z29ucy4gQSBwb2ludCBpcyBhIHNpbmdsZSBkb3Qgb24gYSBtYXAgdGhhdCBjb3JyZXNwb25kcyB0byBvbmUgbGF0L2xvbmcgY29vcmRpbmF0ZS4gQSBsaW5lIGNvbnNpc3RzIG9mIGEgc2VyaWVzIG9mIGRvdHMgY29ubmVjdGVkIHRvZ2V0aGVyLCB3aGlsZSBhIHBvbHlnb24gaXMgYSBzaGFwZSB0aGF0IGlzIGNyZWF0ZWQgYnkgdGhlIGludGVyc2VjdGlvbiBvZiBtdWx0aXBsZSBsaW5lcy4gV2Ugd2lsbCBiZSB3b3JraW5nIG9uIGxlYXJuaW5nIHRvIHN5bWJvbGl6ZSBvdXIgbWFwcyB1c2luZyBwb2ludHMgYW5kIHBvbHlnb25zIGluIHRoaXMgbGVzc29uLiANCg0KYGBge3J9DQojTG9hZCB0aGUgdXJibm1hcHIgcGFja2FnZSBhbmQgdGhlIHNmIChzcGF0aWFsIGZlYXR1cmVzKSBsaWJyYXJ5IA0KI1RoZSBzZiBwYWNrYWdlIHdpbGwgaGVscCB1cyB0byB3b3JrIHdpdGggdGhlIGRhdGEgZnJvbSB0aGUgdXJibm1hcHIgcGFja2FnZQ0KI1dlJ2xsIHRhbGsgYWJvdXQgc2YgaW4gbW9yZSBkZXRhaWwgbGF0ZXIsIGJ1dCBzZiAoc2ltcGxlIGZlYXR1cmVzKSBvYmplY3RzIGFyZSBlc3NlbnRpYWxseSB0aGUgcG9pbnRzLCBsaW5lcywgYW5kIHBvbHlnb25zIHRoYXQgd2Ugd2lsbCB1c2UgdG8gbWFrZSBtYXBzIC0gdGhpcyBpcyBhIHNwZWNpYWwgb2JqZWN0IHR5cGUgdGhhdCBpcyBzaW1pbGFyIHRvIHRoZSBzaGFwZWZpbGVzIHRoYXQgYXJlIHVzZWQgaW4gR0lTIHNvZnR3YXJlDQojRWFjaCBzZiBvYmplY3QgY29udGFpbnMgYSBzcGVjaWFsICJnZW9tZXRyeSIgY29sdW1uIHRoYXQgZGVmaW5lcyB0aGUgc3BhdGlhbCBwcm9wZXJ0aWVzIG9mIHRoZSBvYmplY3QgKGRvbid0IHdvcnJ5LCB5b3UgZG9uJ3QgbmVlZCB0byB1bmRlcnN0YW5kIGl0IGFueSBkZWVwZXIgdGhhbiB0aGF0KQ0KI0xhdGVyIG9uLCBJIHdpbGwgc2hvdyB5b3UgaG93IHRvIHRyYW5zZm9ybSBsYXQvbG9uZyBjb29yZGluYXRlcyBpbnRvIGFuIHNmIG9iamVjdA0KbGlicmFyeShzZikNCmxpYnJhcnkodXJibm1hcHIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShnZ3RoZW1lcykNCg0KI2dyYWIgY291bnR5IHNoYXBlIGZpbGVzIGZvciBvdXIgbWFwDQpjb3VudGllcyA8LSBnZXRfdXJibl9tYXAoImNvdW50aWVzIiwgc2YgPSBUUlVFKQ0KDQojZmlsdGVyIHRoZSBkYXRhIHRvIGdldCBqdXN0IE5ZUw0KY291bnRpZXMgPC0gY291bnRpZXMgJT4lIA0KICBmaWx0ZXIoc3RhdGVfYWJidiA9PSAiTlkiKQ0KDQpoZWFkKGNvdW50aWVzKQ0KYGBgDQoNCkxldCdzIHBsb3QgdGhlIHN0YXRlLWxldmVsIGRhdGEgdGhhdCB3ZSBoYXZlIQ0KDQpgYGB7cn0NCmdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gY291bnRpZXMsDQogICAgbWFwcGluZyA9IGFlcygpKSsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KSG1tbW0uLi5OWVMgc2hvdWxkbid0IGJlIGNyb29rZWQhIFdoYXQgaGFwcGVuZWQ/DQoNCkFsbCBtYXBzIGhhdmUgYSBDb29yZGluYXRlIFJlZmVyZW5jZSBTeXN0ZW0gKENSUykgLSB0aGlzIGNvbnNpc3RzIG9mIHRoZSBjb29yZGluYXRlIHN5c3RlbSwgZGF0dW0sIGFuZCBtYXAgcHJvamVjdGlvbiBzcGVjaWZpZWQgYnkgdGhlIG1hcCBjcmVhdG9yLiBUaGUgZGVmYXVsdCBDUlMgd29ya3Mgd2VsbCBmb3IgdGhlIGVudGlyZSBVUywgYnV0IGl0IGRvZXNuJ3Qgd29yayBhcyBuaWNlbHkgZm9yIE5ZUy4gV2UgbmVlZCB0byBkZWZpbmUgYSBtb3JlIHJlZ2lvbmFsbHktc3BlY2lmaWMgQ1JTLiBMZXQncyBzdGFydCBieSBjaGVja2luZyB0aGUgY3VycmVudCBDUlMgb2YgdGhlIG1hcC4NCg0KYGBge3J9DQojVGhpcyBjb21tYW5kIGNvbWVzIGZyb20gdGhlIHNmIHBhY2thZ2UNCnN0X2Nycyhjb3VudGllcykNCmBgYA0KQmFzZWQgb24gdGhpcywgd2UgY2FuIHNlZSB0aGF0IHRoZSBDUlMgaXMgRVBTRzoyMTYzLiBUaGUgZGF0dW0gaXMgTm9ydGggQW1lcmljYW4gRGF0dW0gMTkyNywgd2hpbGUgdGhlIG1hcCB1c2VzIHRoZSBVUyBOYXRpb25hbCBBdGxhcyBFcXVhbCBBcmVhIHByb2plY3Rpb24uIEFsbCB3ZSByZWFsbHkgbmVlZCB0byBrbm93IGhlcmUgaXMgd2hhdCB0aGUgY3VycmVudCBDUlMgaXMsIHNvIHRoYXQgd2UgY2FuIGRldGVybWluZSB3aGV0aGVyIHRvIGFsdGVyIGl0LiBGb3Igb3VyIE5ZUyBtYXAsIHdlIHdpbGwgbmVlZCB0byBjaGFuZ2UgdGhlIENSUy4gDQoNCkhvdyBkbyB3ZSBrbm93IHdoYXQgQ1JTIHRvIGNob29zZT8gVGhlcmUgYXJlIGxpa2VseSBhIG51bWJlciBvZiBDUlMnIHRoYXQgd291bGQgd29yayB3ZWxsIGZvciB0aGlzIG1hcC4gVG8gcGljayBvbmUsIHdlIGNhbiBsb29rIGF0IGEgbGlzdCBvZiBzdGF0ZSBwcm9qZWN0aW9ucyBoZXJlOiBodHRwczovL2dpdGh1Yi5jb20vdmVsdG1hbi9kMy1zdGF0ZXBsYW5lLiBJIHBpY2tlZCBvbmUgb2YgdGhlIE5ZIHNwZWNpZmljIG9wdGlvbnMuIA0KDQpgYGB7cn0NCiNUcmFuc2Zvcm0gdGhlIENSUw0KDQpjb3VudGllcyA8LSBjb3VudGllcyAlPiUgDQogIHN0X3RyYW5zZm9ybSgiRVBTRzozMjExNiIpDQoNCiNMZXQncyBzZWUgaWYgdGhpcyBsb29rcyBhbnkgYmV0dGVyDQoNCmdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gY291bnRpZXMsDQogICAgbWFwcGluZyA9IGFlcygpKSsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCiNNdWNoIGJldHRlciENCmBgYA0KT3VyIG1hcCBpcyByZWFsbHkgY29taW5nIGFsb25nISBOZXh0LCB3ZSBhcmUgZ29pbmcgdG8gd2FudCB0byBsb2FkIGluIHNvbWUgZGF0YSB0byB2aXN1YWxpemUgb24gdGhlIG1hcC4gDQpXZSdsbCBzdGFydCB3aXRoIHRoZSBDb3ZpZC0xOSBkYXRhIHRoYXQgeW91IHVzZWQgZm9yIHlvdXIgaG9tZXdvcmsgYXNzaWdubWVudC4gVGhpcyBmaXJzdCB0eXBlIG9mIG1hcCBpcyBjYWxsZWQgYSBjaG9yb3BsZXRoIG1hcCAtIGVzc2VudGlhbGx5LCB0aGlzIGlzIGEgbWFwIHRoYXQgdXNlcyB0aGUgcG9seWdvbnMgb24gYSBtYXAgKGluIG91ciBjYXNlLCBjb3VudGllcykgdG8gdmlzdWFsaXplIGEgdmFyaWFibGUgd2l0aCBjb2xvci4gDQpgYGB7cn0NCiNTZXQgd2QNCnNldHdkKCJ+L0JpbmdoYW10b24vZ2VvZzM4MCIpDQoNCiNsb2FkIGluIHRoZSBkYXRhDQpjb3ZpZCA8LSByZWFkLmNzdigiY292aWRfZGF0YV9ueS5jc3YiKQ0KDQojV2UnbGwgbmVlZCB0byBtZXJnZSB0aGlzIGRhdGEgd2l0aCBvdXIgY291bnRpZXMgZGF0YXNldA0KI1dlIGNhbiB1c2UgdGhlIGxlZnRfam9pbiBmdW5jdGlvbiB0byBkbyB0aGlzDQojRmlyc3QgdGhvdWdoLCB3ZSBuZWVkIHRvIGluc3BlY3Qgb3VyIElEIGNvbHVtbnMNCg0KdW5pcXVlKGNvdmlkJENvdW50eSkNCnVuaXF1ZShjb3VudGllcyRjb3VudHlfbmFtZSkNCg0KDQpgYGANCmBgYHtyfQ0KI1RoZXJlJ3MgYW4gZWFzaWVyIHdheSB0byBkbyB0aGlzIQ0KI1dlIGNhbiB1c2UgYSBsb2dpY2FsIG9wZXJhdGlvbiB0byBjaGVjayB3aGV0aGVyIHRoZSBjb3VudHkgbmFtZXMgbWF0Y2gNCiNUaGUgbmFtZXMgbmVlZCB0byBiZSBpbiB0aGUgc2FtZSBvcmRlciwgb3Igd2UnbGwgZ2V0IG9ubHkgRmFsc2UNCmNvdW50aWVzIDwtIGNvdW50aWVzICU+JSBhcnJhbmdlKGNvdW50eV9uYW1lKQ0KY292aWQgPC0gY292aWQgJT4lIGFycmFuZ2UoQ291bnR5KQ0KDQpjb3VudGllcyRjb3VudHlfbmFtZSA9PSBjb3ZpZCRDb3VudHkNCmBgYA0KV2UgaGF2ZSBhIHByb2JsZW0hIFRoZSBjb3VudGllcyBkYXRhIGNvbnRhaW5zIHRoZSB3b3JkICJDb3VudHkiIGluIGVhY2ggY291bnR5IG5hbWUsIHdoaWxlIHRoZSBDb3ZpZCBkYXRhIGRvZXMgbm90LiBZb3Ugd2lsbCBydW4gaW50byBwcm9ibGVtcyBsaWtlIHRoaXMgcHJldHR5IG9mdGVuLCB3aGljaCBpcyB3aHkgaXQgaXMgc28gaW1wb3J0YW50IHRvIGluc3BlY3QgeW91ciBkYXRhIGJlZm9yZSB5b3UgYW5hbHl6ZSBpdC4gV2UgY2FuIGZpeCB0aGlzIHdpdGggdGhlIHNlcGFyYXRlKCkgZnVuY3Rpb24gZnJvbSB0aGUgdGlkeXIgcGFja2FnZSBsaWtlIHRoaXM6DQpgYGB7cn0NCmxpYnJhcnkodGlkeXIpDQoNCiNUaGluayBhYm91dCB0aGUgY291bnR5IG5hbWVzIC0gd2h5IGRpZCBJIHVzZSAiIENvdW50eSIgYXMgYSBzZXBhcmF0b3IgaW5zdGVhZCBvZiBqdXN0IGEgc3BhY2U/DQpjb3VudGllczEgPC0gY291bnRpZXMgJT4lIA0KICBzZXBhcmF0ZShjb3VudHlfbmFtZSwgYygiY291bnR5X25hbWUiLCAiY291bnR5IiksIHNlcCA9ICIgQ291bnR5IikgJT4lIA0KICBzZWxlY3QoLWNvdW50eSkNCg0KI0RpZCBpdCB3b3JrPw0KaGVhZChjb3VudGllczEpDQpgYGANCmBgYHtyfQ0KI0xldCdzIGNoZWNrIGlmIHRoZXkgbWF0Y2ggbm93IQ0KDQpjb3VudGllczEkY291bnR5X25hbWUgPT0gY292aWQkQ291bnR5DQpgYGANCk5vdyB3ZSBjYW4gcHJvY2VlZCB0byBqb2luIHRoZSBkYXRhLiANCg0KYGBge3J9DQpjb3ZpZF9jb3VudHkgPC0gY291bnRpZXMxICU+JQ0KICBsZWZ0X2pvaW4oDQogICAgY292aWQsDQogICAgI1RoaXMgY29tbWFuZCBhbGxvd3MgdXMgdG8gam9pbiB0aGUgZGF0YSB1c2luZyB0d28gdmFyaWFibGVzIHdpdGggZGlmZmVyZW50IG5hbWVzDQogICAgYnkgPSBjKCJjb3VudHlfbmFtZSIgPSAiQ291bnR5IikNCiAgKQ0KDQpoZWFkKGNvdmlkX2NvdW50eSkNCmBgYA0KTm93LCB3ZSdsbCBjcmVhdGUgb3VyIGZpcnN0IG1hcCENCg0KYGBge3J9DQojVXNpbmcgR2dwbG90MiBhbmQgdGhlIHVyYm5tYXByIHBhY2thZ2UgdG8gcHJvZHVjZSB0aGUgbWFwOg0KZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSBjb3ZpZF9jb3VudHksDQogICAgbWFwcGluZyA9IGFlcyhmaWxsID0gbmV3X3Bvc2l0aXZlKSkrDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQpIbW1tbW1tbS4uLi50aGF0J3Mgbm90IHZlcnkgbmljZSBsb29raW5nLCBpcyBpdD8gQW55IHN1Z2dlc3Rpb25zPw0KDQpGaXJzdCwgbGV0J3MgdGFsayBhYm91dCBub3JtYWxpemluZyBvdXIgZGF0YS4gSXQgbG9va3MgbGlrZSBOWUMgaGFzIGEgaHVnZSBudW1iZXIgb2YgY2FzZXMsIGJ1dCB3ZSBhbHNvIGtub3cgdGhhdCBOWUMgaXMgYmlnZ2VyIHRoYW4gYW55IG90aGVyIHJlZ2lvbiBpbiB0aGUgc3RhdGUuIFdoYXQgbWlnaHQgdGhpcyBsb29rIGxpa2UgZnJvbSBhIG1vcmUgcmVsYXRpdmUgcGVyc3BlY3RpdmU/IE9mdGVuLCBpdCBpcyBtdWNoIG1vcmUgbWVhbmluZ2Z1bCB0byBub3JtYWxpemUgb3VyIGRhdGEgc28gdGhhdCB3ZSBjYW4gYmV0dGVyIGNvbXBhcmUgYWNyb3NzIGNvdW50aWVzLiBGb3IgZXhhbXBsZSwgaGVyZSBJIGFtIGdvaW5nIHRvIG1hcCBwb3NpdGl2ZSBhcyBhIHBlcmNlbnRhZ2Ugb2YgdG90YWwgdGVzdHMuIE90aGVyIGNvbW1vbiB3YXlzIHRvIG5vcm1hbGl6ZSB0aGUgZGF0YSBpbmNsdWRlIGNhbGN1bGF0aW5nIHRoZSBudW1iZXIgb2YgY2FzZXMgcGVyIGNhcGl0YSAodXNpbmcgcG9wdWxhdGlvbiBkYXRhKSBvciB0aGUgcGVyY2VudCBncm93dGggaW4gcG9zaXRpdmUgY2FzZXMgKGlmIHdlIGhhZCBkYXRhIG92ZXIgdGltZSkuDQoNCmBgYHtyfQ0KI2NhbGN1bGF0ZSB0aGUgbmV3IHZhcmlhYmxlIHRoYXQgd2Ugd2lsbCBtYXANCmNvdmlkX2NvdW50eSA8LSBjb3ZpZF9jb3VudHkgJT4lIA0KICBtdXRhdGUocG9zaXRpdmVfcGVyY2VudCA9IG5ld19wb3NpdGl2ZS9uZXdfdGVzdHMpDQoNCiNpbnNwZWN0IHRoZSBkYXRhDQpoZWFkKGNvdmlkX2NvdW50eSkNCmBgYA0KRXZlcnl0aGluZyBsb29rcyBwcmV0dHkgZ29vZCEgTm93IHdlJ2xsIG1hcCB0aGlzIG5ldyB2YXJpYWJsZS4gDQoNCmBgYHtyfQ0KZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSBjb3ZpZF9jb3VudHksDQogICAgbWFwcGluZyA9IGFlcyhmaWxsID0gcG9zaXRpdmVfcGVyY2VudCkpKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KYGBgDQpUaGlzIGxvb2tzIG11Y2ggbmljZXIsIGJ1dCB3ZSdyZSBub3QgZG9uZSB5ZXQhIEkgZG9uJ3QgbGlrZSB0aGF0IHRoZSBjb2xvciBncmFkaWVudCBzdGFydHMgd2l0aCBkYXJrIGJsdWUgLSBsb3cgdmFsdWVzIGFyZSBjb2xvcmVkIGluIGRhcmsgY29sb3JzLCB3aGlsZSBoaWdoIHZhbHVlcyBhcmUgbGlnaHRlciBpbiB0aGlzIG1hcC4gVGhhdCdzIG5vdCB2ZXJ5IGludHVpdGl2ZS4gTGV0J3MgcGxheSBhcm91bmQgd2l0aCB0aGUgY29sb3IgZ3JhZGllbnQgYW5kIHNvbWUgY29sb3Igb3B0aW9ucyBmb3Igb3VyIG1hcC4gDQoNCmBgYHtyfQ0KZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSBjb3ZpZF9jb3VudHksDQogICAgbWFwcGluZyA9IGFlcyhmaWxsID0gcG9zaXRpdmVfcGVyY2VudCkpKw0KICB0aGVtZV9taW5pbWFsKCkrDQpzY2FsZV9maWxsX2dyYWRpZW50KA0KICAgICMgTWFrZSBsZWdlbmQgdGl0bGUgbW9yZSByZWFkYWJsZQ0KICAgIG5hbWUgPSAiUGVyY2VudCBcblBvc2l0aXZlIENhc2VzIiwNCiAgICAjc2V0IHRoZSBjb2xvciBncmFkaWVudA0KICAgIGxvdyA9ICJsaWdodGJsdWUiLA0KICAgIGhpZ2ggPSAibmF2eWJsdWUiKSArDQpsYWJzKHRpdGxlID0gIlBvc2l0aXZlIENvdmlkIENhc2VzIGluIE5ZUyIpKw0KdGhlbWUobGVnZW5kLnRpdGxlLmFsaWduID0gMC41LA0KICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpDQogICAgDQpgYGANClVuZm9ydHVuYXRlbHksIHdlIGNhbm5vdCBjZW50ZXIgdGhlIGxlZ2VuZCBib3ggd2l0aG91dCBjcmVhdGluZyBhIG5ldyBmdW5jdGlvbiB0byBkbyBzbyBvciBoYWNraW5nIHRoZSBzb3VyY2UgY29kZSAtIGdncGxvdDIgY3VycmVudGx5IG9ubHkgc3VwcG9ydHMgYSBsZWZ0LWFsaWduZWQgbGVnZW5kIGJveC4gTmV4dCwgd2UnbGwgcGxheSBhcm91bmQgd2l0aCBzb21lIG1hcCBjb2xvcnMgYW5kIHN0eWxlcy4gDQoNCmBgYHtyfQ0KI3BsYXkgYXJvdW5kIHdpdGggdGhlIGNvbG9yIGFuZCB0aGVtZQ0KZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSBjb3ZpZF9jb3VudHksDQogICAgbWFwcGluZyA9IGFlcyhmaWxsID0gcG9zaXRpdmVfcGVyY2VudCkpKw0KICB0aGVtZV9taW5pbWFsKCkrDQogICNNYXliZSBJIHdhbnQgdG8gbWFrZSBteSBsZWdlbmQgZGlzY3JldGUgLSBzY2FsZV9maWxsX3N0ZXBzIGFsbG93cyBtZSB0byBkbyB0aGF0IQ0KICBzY2FsZV9maWxsX3N0ZXBzKA0KICAgICMgTWFrZSBsZWdlbmQgdGl0bGUgbW9yZSByZWFkYWJsZQ0KICAgIG5hbWUgPSAiUGVyY2VudCBcblBvc2l0aXZlIENhc2VzIiwNCiAgICAjc2V0IHRoZSBjb2xvciBncmFkaWVudA0KICAgIGxvdyA9ICJibHVlIiwNCiAgICBoaWdoID0gInJlZCIsDQogICAgI0NvbnZlcnQgZnJvbSBkZWNpbWFsIHRvIHBlcmNlbnQgZm9ybWF0DQogICAgbGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpLA0KICAgICN0ZWxscyBSIGhvdyBtYW55IGJyZWFrcyB0byBwdXQgaW4gdGhlIGRhdGENCiAgICBuLmJyZWFrcyA9IDQsDQogICAgI1RlbGxzIFIgdG8gaW5jbHVkZSB0aGUgbWluIGFuZCBtYXggdmFsdWVzIGluIHRoZSBsZWdlbmQNCiAgICBzaG93LmxpbWl0cyA9IFQpICsNCiAgbGFicyh0aXRsZSA9ICJQb3NpdGl2ZSBDb3ZpZCBDYXNlcyBpbiBOWVMiLA0KICAgICAgIHN1YnRpdGxlID0gIkFzIGEgUGVyY2VudCBvZiBEYWlseSBUZXN0cyIsDQogICAgICAgY2FwdGlvbiA9ICJTb3VyY2U6IGhlYWx0aC5kYXRhLm55LmdvdiIpKw0KICB0aGVtZShsZWdlbmQudGl0bGUuYWxpZ24gPSAwLjUsDQogICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBzaXplID0gMTgpLA0KICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksDQogICAgICBwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLA0KICAgICAgI1JlbW92ZSBheGlzIGxhYmVscyBhbmQgdGlja3MNCiAgICAgIGxpbmUgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICBheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksDQogICAgICBheGlzLnRpY2tzLng9ZWxlbWVudF9ibGFuaygpLA0KICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLA0KICAgICAgYXhpcy50aWNrcy55PWVsZW1lbnRfYmxhbmsoKSkNCg0KYGBgDQpPaywgc28gbm93IHdlJ3ZlIGxlYXJuZWQgbW9yZSBhYm91dCBjcmVhdGluZyBjaG9yb3BsZXRoIG1hcHMuIFdoYXQgaWYgSSBoYXZlIGxhdC9sb25nIGNvb3JkaW5hdGVzPyBIb3cgY2FuIEkgbWFwIHRob3NlPyBXZSdsbCBjcmVhdGUgYSBwb2ludCBtYXAgKGluIHRoZSB0dXRvcmlhbCBpdCBpcyBjYWxsZWQgYSBCdWJibGUgbWFwKSB0byB2aXN1YWxpemUgdGhlc2UgY29vcmRpbmF0ZXMuIExldCdzIG1hcCBjaXRpZXMgaW4gTllTLiANCg0KYGBge3J9DQojbG9hZCBpbiBzb21lIGNpdHkgZGF0YSAtIHRoaXMgY29udGFpbnMgZGF0YSBmb3IgYWxsIGNpdGllcyBpbiB0aGUgVVMsIHNvIHdlJ2xsIGhhdmUgdG8gZmlsdGVyIGZvciBOWQ0KI1RoaXMgZGF0YSBpcyBmcm9tOiBodHRwczovL3NpbXBsZW1hcHMuY29tL2RhdGEvdXMtY2l0aWVzIA0KY2l0aWVzIDwtIHJlYWQuY3N2KCJ1c2NpdGllcy5jc3YiKQ0KDQpueWNpdGllcyA8LSBjaXRpZXMgJT4lIA0KICBmaWx0ZXIoc3RhdGVfbmFtZSA9PSAiTmV3IFlvcmsiLA0KICAgICAgICAgcG9wdWxhdGlvbiA+IDEwMDAwMCkNCg0KbnljaXRpZXMxIDwtIG55Y2l0aWVzICU+JSANCiAgI0kgbmVlZCB0byB0cmFuc2Zvcm0gbXkgbGF0L2xvbmcgY29vcmRpbmF0ZXMgaW50byBhbiBzZiBvYmplY3QgDQogICM0MzI2IGlzIHRoZSBjcnMgdXNlZCBpbiBncHMgLSBpdCBpcyBkZWZpbmVkIGluIGxhdC9sb25nIGNvb3Jkcw0KICAjd2UgbmVlZCB0byBmaXJzdCBjb252ZXJ0IHRvIGFuIHNmIG9iamVjdCB1c2luZyB0aGlzIGNycw0KICBzdF9hc19zZihjb29yZHMgPSBjKCJsbmciLCAibGF0IiksIGNycyA9IDQzMjYpICU+JSANCiAgI25vdyB3ZSBjYW4gY29ycmVjdGx5IGNvbnZlcnQgdG8gdGhlIHNhbWUgY3JzIGFzIG91ciBjb3VudGllcyBmaWxlLCB3aGljaCBpcyBkZWZpbmVkIGluIG1ldGVycw0KICBzdF90cmFuc2Zvcm0oY3JzID0gc3RfY3JzKGNvdW50aWVzKSkNCg0KI2xvYWQgaW4gZ2dzZmxhYmVsIGxpYnJhcnkNCiNtYWtlIHN1cmUgeW91IGluc3RhbGwgdGhlIGRldnRvb2xzIHBhY2thZ2UgZmlyc3QhDQojVGhpcyBwYWNrYWdlIHdpbGwgaGVscCB1cyB0byBtYWtlIHRoZSBsYWJlbHMgbG9vayBuaWNlDQpkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoInl1dGFubmloaWxhdGlvbi9nZ3NmbGFiZWwiKQ0KbGlicmFyeShnZ3NmbGFiZWwpDQoNCiNMZXQncyBwbG90IG91ciBjaXRpZXMhDQpnZ3Bsb3QoKSArDQogIGdlb21fc2YoZGF0YSA9IGNvdW50aWVzKSsNCiAgZ2VvbV9zZihkYXRhID0gbnljaXRpZXMxLCBjb2xvcj0iYmx1ZSIpKw0KICAjSGVyZSwgSSBhbSBqdXN0IHdpZGVuaW5nIHRoZSBwbG90IGFyZWEgLSBJIHdhbnQgdG8gbGFiZWwgbXkgY2l0aWVzLCBidXQgdGhlIGxhYmVscw0KICAjdGFrZSB1cCB0b28gbXVjaCBzcGFjZQ0KICAjSG93IGRpZCBJIGZpbmQgdGhlc2UgdW5pdHM/IFlvdSBjYW4gcHJpbnQgb3V0IHlvdXIgc2Ygb2JqZWN0LCBhbmQgaXQgd2lsbCB0ZWxsIHlvdSB0aGUgICAgbWluaW11bSBhbmQgbWF4aW11bSB4IGFuZCB5IHZhbHVlcyBmb3IgdGhlIG9iamVjdA0KICAjVGhlIHVuaXRzIGFyZSBpbiBtZXRlcnMNCiAgY29vcmRfc2YoeWxpbSA9IGMoMjAwMDAsIDU1MDAwMCkpKw0KICAjYWRkIHRoZSBsYWJlbHMhIFRoZSBnZW9tX3NmX2xhYmVsX3JlcGVsIGFyZ3VtZW50IGxldHMgbWUgcmVwZWwgb3ZlcmxhcHBpbmcgbGFiZWxzDQogICNUaGF0IHdheSwgd2UgY2FuIHJlYWQgYWxsIG9mIHRoZSB0ZXh0DQogICNmb3JjZSB0ZWxscyBSIHRoZSBmb3JjZSBvZiB0aGUgcmVwdWxzaW9uIC0gSSBzZXQgaXQgYXQgMjAsIHdoaWNoIGlzIGEgYml0IGxvdw0KICAjQnkgZGVmYXVsdCwgaXQgb25seSBkaXNwbGF5cyB0aGUgZmlyc3QgMTAgb3ZlcmxhcHBpbmcgbGFiZWxzIC0gSSBpbmNyZWFzZWQgdGhpcyB0byAyMA0KICAjSSBhbHNvIG1hZGUgdGhlIGxhYmVscyBzaXplIDIsIHdoaWNoIGlzIHNsaWdodGx5IHNtYWxsZXIgdGhhbiB0aGUgZGVmYXVsdA0KICBnZW9tX3NmX2xhYmVsX3JlcGVsKGRhdGEgPSBueWNpdGllczEsIGFlcyhsYWJlbCA9IGNpdHkpLCBmb3JjZSA9IDIwLCANCiAgICAgICAgICAgICAgICAgICAgICBtYXgub3ZlcmxhcHMgPSAyMCwgc2l6ZSA9IDIpKw0KICB0aGVtZV9taW5pbWFsKCkrDQogIGxhYnModGl0bGUgPSAiTWFqb3IgQ2l0aWVzIGluIE5ldyBZb3JrIFN0YXRlIikrDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIHNpemUgPSAxNikpDQoNCmBgYA0KDQpPaywgc28gdGhlcmUncyBhIGxvdCBnb2luZyBpbnRvIHRoaXMgbWFwLiBIZXJlJ3MgYSBicmVha2Rvd24gb2YgdGhlIHN0ZXBzIHlvdSBzaG91bGQgdXNlIHdoZW4geW91IGhhdmUgbGF0L2xvbmcgY29vcmRzOg0KDQoxKSBDaGVjayB0aGUgY3JzIG9mIHlvdXIgYmFzZW1hcC4gV2hlbiB5b3UgdXNlIHRoZSBzdF9jcnMoKSBmdW5jdGlvbiwgaXQgd2lsbCB0ZWxsIHlvdSBhYm91dCB0aGUgY3JzLiBJZiB5b3UgbG9vayB3aGVyZSBpdCBzYXlzICJMRU5HVEhVTklUIiwgaXQnbGwgdGVsbCB5b3UgdGhlIHVuaXRzIHRoYXQgeW91ciBjcnMgaXMgaW4uIFRoZSBjcnMgd2UgdXNlZCB0byBtYXAgb3VyIGNvdW50aWVzIHdhcyBhY3R1YWxseSBkZWZpbmVkIGluIG1ldGVycywgbm90IGxhdC9sb25nIGNvb3Jkcy4gDQoyKSBJZiB5b3VyIGJhc2UgbWFwIGRvZXNuJ3QgdXNlIGxhdC9sb25nIGNvb3JkcywgdGhlbiB0aGVyZSBhcmUgdHdvIHN0ZXBzIHRvIHRyYW5zZm9ybWluZyB5b3VyIG5ldyBkYXRhIGluIG9yZGVyIHRvIG1hcCBvbiB0b3Agb2YgdGhlIGJhc2UgbWFwOg0KICBhKSBVc2UgdGhlIHN0X2FzX3NmIGZ1bmN0aW9uIGFuZCBjcnMgNDMyNiB0byBpbml0aWFsbHkgY3JlYXRlIHlvdXIgc2Ygb2JqZWN0DQogIGIpIFRoZW4sIHVzZSBzdF90cmFuc2Zvcm0gdG8gdHJhbnNmb3JtIHRoZSBvYmplY3QgaW50byB5b3VyIGRlc2lyZWQgZmluYWwgY3JzDQogICppZiB5b3UgZG9uJ3Qga25vdyB3aGF0IHRoYXQgaXMsIHlvdSBjYW4gcHV0IHN0X2NycyhteV9iYXNlX21hcCkgaW50byB0aGUgZnVuY3Rpb24gdG8gICAgICBlbnN1cmUgdGhhdCB5b3VyIHBvaW50IG9iamVjdCBtYXRjaGVzIHlvdXIgb3JpZ2luYWwgYmFzZSBtYXAgKG15X2Jhc2VfbWFwKQ0KTm90ZTogaWYgeW91IGZvcmdldCB0byBmaXJzdCB0cmFuc2Zvcm0geW91ciBjb29yZGluYXRlcyBpbnRvIGNycyA0MzI2LCB5b3VyIHNmIG9iamVjdCB3aWxsIGNvbnZlcnQgaW5jb3JyZWN0bHkgYW5kIG5vdCBiZSBjb21wYXRpYmxlIHdpdGggeW91ciBiYXNlIG1hcC4gWW91J2xsIGtub3cgdGhpcyBpcyB0aGUgY2FzZSBiZWNhdXNlIG5vIHBvaW50cyB3aWxsIGFwcGVhci4gDQoNClVzaW5nIHRoZXNlIHN0ZXBzLCB5b3Ugc2hvdWxkIGJlIGFibGUgdG8gY29udmVydCBhbnkgbGF0L2xvbmcgY29vcmRzIGludG8gYW4gc2Ygb2JqZWN0ISBEb24ndCB3b3JyeSBpZiB5b3UgZG9uJ3QgdW5kZXJzdGFuZCBldmVyeXRoaW5nIHRoYXQgaXMgZ29pbmcgb24gdW5kZXJuZWF0aCB0aGVzZSBmdW5jdGlvbnMgLSB5b3UgcmVhbGx5IGp1c3QgbmVlZCB0byBiZSBhYmxlIHRvIGZvbGxvdyB0aGUgaW5zdHJ1Y3Rpb25zIGhlcmUuIA0KDQpPaywgd2Ugd2lsbCB3b3JrIG9uIG9uZSBmaW5hbCBza2lsbCB1c2luZyBwb2ludCBkYXRhIHRvZGF5OiBzaXppbmcgdGhlIHBvaW50cy4gU3VwcG9zZSBJIHdhbnRlZCB0byBjb21wYXJlIHRoZSBjaXRpZXMgYnkgdGhlaXIgcG9wdWxhdGlvbiBwZXIgc3FrbSAoZGVuc2l0eSkgLSBJIGNvdWxkIGNoYW5nZSB0aGUgc2l6ZXMgb2YgbXkgcG9pbnRzIHRvIGFjY29tcGxpc2ggdGhhdCENCg0KYGBge3J9DQojUmUtcnVuIHNvbWUgY29kZSBmcm9tIGVhcmxpZXIgLSBJIGRvbid0IHdhbnQgdG8gcmVtb3ZlIHNtYWxsIGNpdGllcyB0aGlzIHRpbWUNCm55Y2l0aWVzIDwtIGNpdGllcyAlPiUgDQogIGZpbHRlcihzdGF0ZV9uYW1lID09ICJOZXcgWW9yayIpICU+JSANCiAgZmlsdGVyKHBvcHVsYXRpb24gPiAyMDAwMCkNCg0KbnljaXRpZXMxIDwtIG55Y2l0aWVzICU+JSANCiAgc3RfYXNfc2YoY29vcmRzID0gYygibG5nIiwgImxhdCIpLCBjcnMgPSA0MzI2KSAlPiUgDQogIHN0X3RyYW5zZm9ybShjcnMgPSBzdF9jcnMoY291bnRpZXMpKQ0KDQoNCm9wdGlvbnMoc2NpcGVuID0gOTk5KQ0KDQpnZ3Bsb3QoKSArDQogIGdlb21fc2YoZGF0YSA9IGNvdW50aWVzKSsNCiAgZ2VvbV9zZihkYXRhID0gbnljaXRpZXMxLCBhZXMoc2l6ZSA9IGRlbnNpdHkpLCBjb2xvcj0iYmx1ZSIsIGFscGhhID0gMC41KSsNCiAgdGhlbWVfbWluaW1hbCgpKw0KICBsYWJzKHRpdGxlID0gIk1ham9yIENpdGllcyBpbiBOZXcgWW9yayBTdGF0ZSIpKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBzaXplID0gMTYpKQ0KDQpgYGANCg0KSG1tbW0gdGhpcyBpcyBub3QgdGVycmlibHkgaGVscGZ1bC4uLkkgY291bGQgYWxzbyB0aGluayBhYm91dCBjaGFuZ2luZyB0aGUgY29sb3JzIG9mIHRoZSBwb2ludHMhDQoNCmBgYHtyfQ0KZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSBjb3VudGllcywgZmlsbCA9ICJ3aGl0ZSIpKw0KICBnZW9tX3NmKGRhdGEgPSBueWNpdGllczEsIGFlcyhjb2xvciA9IGRlbnNpdHkpLCBzaXplID0gMykrDQogIHRoZW1lX21pbmltYWwoKSsNCiAgbGFicyh0aXRsZSA9ICJNYWpvciBDaXRpZXMgaW4gTmV3IFlvcmsgU3RhdGUiLA0KICAgICAgIGNvbG9yID0gIkRlbnNpdHkgXG4gKFBlb3BsZSBwZXIgc3FrbSkiKSsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgc2l6ZSA9IDE2KSwNCiAgICAgICAgbGVnZW5kLnRpdGxlLmFsaWduID0gMC41LCkrDQogIHNjYWxlX2NvbG9yX2dyYWRpZW50KA0KICAgICNzZXQgdGhlIGNvbG9yIGdyYWRpZW50DQogICAgbG93ID0gImxpZ2h0Ymx1ZSIsDQogICAgaGlnaCA9ICJuYXZ5Ymx1ZSIpDQpgYGANCkRvIHdlIGxvdmUgdGhpcyBtYXA/IE5vIChpdCdzIHJlYWxseSB1Z2x5ISBUaGlzIGlzIG5vdCBhIGdvb2Qgd2F5IHRvIHZpc3VhbGl6ZSBkZW5zaXR5ISksIGJ1dCBpdCBpbGx1c3RyYXRlcyBhbiBpbXBvcnRhbnQgc2tpbGwuICANCg0KUmVzb3VyY2VzDQoNCk5ldyBZb3JrIFN0YXRlICgyMDIyKS4gTmV3IFlvcmsgU3RhdGUgU3RhdGV3aWRlIENvdmlkLTE5IFRlc3RpbmcuIFtEYXRhIFNldF0uIFJldHJlaXZlZCBmcm9tOiBodHRwczovL2hlYWx0aC5kYXRhLm55Lmdvdi9IZWFsdGgvTmV3LVlvcmstU3RhdGUtU3RhdGV3aWRlLUNPVklELTE5LVRlc3RpbmcveGRzcy11NTNlLiANCg0KU2ltcGxlIE1hcHMgKDIwMjIpLiBVbml0ZWQgU3RhdGVzIENpdGllcyBEYXRhYmFzZS4gW0RhdGEgU2V0XS4gUmV0cmlldmVkIGZyb206IGh0dHBzOi8vc2ltcGxlbWFwcy5jb20vZGF0YS91cy1jaXRpZXMuIA0KDQpVcmJhbiBJbnN0aXR1dGUgKDIwMjIpLiBJbnRyb2R1Y3Rpb24gdG8gTWFwcGluZy4gUmV0cmlldmVkIGZyb206IGh0dHBzOi8vdXJiYW5pbnN0aXR1dGUuZ2l0aHViLmlvL3ItYXQtdXJiYW4vbWFwcGluZy5odG1sLiA=