Libraries needed for this section are:

Data needed:

R comes with a basic plot command, which can also be used for simple map viewing. In this workshop we will look into several alternatives to map spatial data with R. This list is of course not comprehensive, but should give you something to start with. For more packages see the “Visualisation” section of the CRAN Task View.

Of the packages mentioned spplot only takes sp objects, tmap and leaflet can also take sf objects. The development version of ggplot2 can take sf objects, though ggmap seems to still have issues with sf.

1. Choropleth mapping with spplot

sp comes with a plot command spplot(), which takes Spatial* objects to plot. spplot() is one of the earliest functions to plot geographic objects.


Exercise 1

  1. Use readOGR() from the rgdal libryary to read the Philly3 shapefile into an object named philly.

  2. Use names() to see the attributes. I have added the following fields:

  • N_HOMIC: Number of homicides (since 2006)
  • HOMIC_R: homicide rate per 100,000 (Philadelphia Open Data)
  • PCT_COL: % 25 years and older with college or higher degree1 (ACS 2006-2010)
  • mdHHnc: estimated median household income (ACS 2006-2010)
  1. Use the base plot command and see what you get.
plot (philly)
  1. Use the spplot command (make sure you installed and loaded sp) and compare.
spplot(philly)
  1. Not particularly useful for any interpretation.

    You can see that by default spplot tries to map everything it can find in the attribute table. Sometimes, even this does not work, depending on the data types in the attribute table. It also uses one classification for all the maps. (The latter actually makes sense, as otherwise you’d be likely to compare apples with oranges.)

    In order to select specific values to map we can provide the spplot function with the name (or names) of the attribute variable we want to plot. It is the name of the column of the Spatial*Dataframe as character string (or a vector if several).

    Try that.

spplot(philly,"HOMIC_R")
# or
spplot(philly,c("HOMIC_R", "PCT_COL"))
  1. Let us stick with one variable for now and try to improve it a little.
    First we want to change the color palette. For this we use a library called RColorBrewer2. For more about ColorBrewer palettes read this. Load the RColorBrewer library and explore all sequential color schemes with

    display.brewer.all(type="seq")
  2. To make the color palettes from ColorBrewer available as R palettes we use brewer.pal() It takes two arguments:
    • the number of different colors desired and
    • the name of the palette as character string.

    Select 5 colors from the ‘Orange-Red’ plaette and assign it to an object pal. What kind of object is pal?

library(RColorBrewer)
display.brewer.all(type="seq")
pal <- brewer.pal(5, "OrRd") # we select 5 colors from the palette
class(pal)
  1. Now we pass this information on to spplot. We need to provide two more arguments:
    • col.regions which we set to the palette we just created and
    • cuts which in our case is 4. It bins our continous variable into 5 brackets and will make our colors match up with those class brackets.
spplot(philly,"HOMIC_R", col.regions = pal, cuts = 4) 
  1. Looks better already. But we still have this one area standing out with an extremely high homicide rate, which makes a large part of the map unreadable. So let’s change the class intervals to quantiles. We will use classIntervals from the classInt library, something like:

    breaks_qt <- classIntervals(vector_of_values, 
                                n = how_many_classes, 
                                style = "quantile" [can be omitted -- the default])

    This returns an object of type classIntervals. Find out its structure. How would you access the break values?

library(classInt)
breaks_qt <- classIntervals(philly$HOMIC_R, n = 5)
str(breaks_qt)
breaks_qt$brks
  1. Finally we use those breaks to set the at= argument in spplot(). Let’s also set main= to add a title.
spplot(philly, "HOMIC_R", col.regions=pal, at = breaks_qt$brks,  main = "Philadelphia homicide rate per 100,000")
  1. If you now look closely you will see that there are a few blank polygons. Here are the steps to correct the breaks3.
# add a very small value to the top breakpoint, and subtract from the bottom for symmetry 
br <- breaks_qt$brks 
offs <- 0.0000001 
br[1] <- br[1] - offs 
br[length(br)] <- br[length(br)] + offs 

# plot
spplot(philly, "HOMIC_R", col.regions=pal, at=br,  main = "Philadelphia homicide rate per 100,000")
  1. The biggest remainig issue is the legend, which shows as a graduated color, since we provided a vector of continuous values to map. Here is how we can change this:

    • Use the cut() function from the base package with the values from philly$HOMIC_R and the corrected breaks br to return a vector with the respective boundaries of the brackets. Use ?cut if you need help.
    • Assign the output vector you get as a new column HOMIC_R_bracket to the philly attributes table. It will help to map the color based on the breaks. Take a look at the values. What object class is that vector?
    • Remove the at= parameter in spplot() (which is only needed for continuous variables) and tell it to plot HOMIC_R_bracket.
philly$HOMIC_R_bracket <- cut(philly$HOMIC_R, br)
head(philly$HOMIC_R_bracket)
class(philly$HOMIC_R_bracket)
spplot(philly, "HOMIC_R_bracket", col.regions=pal, main = "Philadelphia homicide rate per 100,000")

Now, this is what you should see:

And below is the complete code:

library(rgdal)
library(sp)
library(RColorBrewer)
library(classInt)
philly <- readOGR("path_to_your_shapefile_folder", "Philly3") #
pal <- brewer.pal(5, "OrRd")
breaks_qt <- classIntervals(philly$HOMIC_R, n = 5, style = "quantile")
br <- breaks_qt$brks 
offs <- 0.0000001 
br[1] <- br[1] - offs 
br[length(br)] <- br[length(br)] + offs 
philly$HOMIC_R_bracket <- cut(philly$HOMIC_R, br)
spplot(philly, "HOMIC_R_bracket", col.regions=pal, main = "Philadelphia homicide rate per 100,000")

There are many more arguments for this function to provide additional plot parameters, like the legend position, labels, scales, etc.

However, as you have seen, this can be quite tedious.

As an alternative you may want to be aware of the GISTools package. It includes functions, like choropleth() to draw choropleth that are really just convenience functions that wrap around spplot()

Below is the code if you wanted to do a similar map as above with GISTools. Currently GISTools cannot understand sf objects.

library(GISTools)                               # load library
choropleth(philly, philly$HOMIC_R)              # plot the polygons
shd <-  auto.shading(philly$HOMIC_R)            # we need that for the legend coloring
choro.legend(                                   # plot the legend
  bbox(philly)["x","max"] - 5000,               # x coordinate of top left corner
  bbox(philly)["y","min"] + 15000,              # y coordinate of top left corner
  shd                                           # color scheme
  )                               
title("Philadelphia homicide rate per 100,000") # plot the title.

2. Plotting simple features (sf) with plot

The sf package extends the base plot command, so it can be used on sf objects. If used without any arguments it will plot all the attributes, like spplot does.

library(sf)
philly_sf <-  st_read("~/Desktop/Philly3/Philly3.shp")
plot(philly_sf)

To plot a single attribute we need to provide an object of class sf, like so:

plot(philly_sf$HOMIC_R) # this is a numeric vector
plot(philly_sf["HOMIC_R"])

If we wanted to add our own colors, legend and title we would recur to basic plot parameters to do this.

hr_cuts <-  cut(philly_sf$HOMIC_R, br)
plot(philly_sf["HOMIC_R"], main = "Philadelphia homicide rate per 100,000", col = pal[as.numeric(hr_cuts)])
legend(1760000, 471000, legend = paste("<", round(br[-1])), fill = pal)        

3. Choropleth mapping with ggplot2

ggplot2 is a widely used and powerful plotting library for R. It is not specifically geared towards mapping, but one can generate great maps.

The ggplot() syntax is different from the previous as a plot is built up by adding components with a +. You can start with a layer showing the raw data then add layers of annotations and statistical summaries. This allows to easily superimpose either different visualizations of one dataset (e.g. a scatterplot and a fitted line) or different datasets (like different layers of the same geographical area)4.

For an introduction to ggplot check out this book by the package creator or this for more pointers.

In order to build a plot you start with initializing a ggplot object. In order to do that ggplot() takes:

In addition, minimally a geometry to be used to determine how the values should be displayed. This is to be added after an +.

ggplot(data = my_data_frame, mapping = aes(x = name_of_column_with_x_value, y = name_of_column_with_y_value)) +
  geom_point()

Or shorter:

ggplot(my_data_frame, aes(name_of_column_with_x_value, name_of_column_with_y_value)) +
  geom_point()

So if we wanted to map polygons, like census tract boundaries, we would use longitude and latitude of their vertices as our x and y values and geom_polygon() as our geometry.


Exercise 2

  1. To plot the equivalent to the map we created with spplot above we need to convert philly, which is a SpatialPolygonsDataframe, to a regular dataframe.

    broom is a general purpose package which provides functions to turn the messy output of built-in functions in R, such as lm, nls, or t.test, into tidy data frames. We use the tidy() command for the conversion5.

  1. Load the broom library, and use tidy for the conversion. Create a new object, philly_df for the output. What columns and values do you get?
library(broom)
philly_df <- tidy(philly)
head(philly_df)
  1. Ha. tidy() will make us loose the attributes that we want to map, so we have to take care of that. We extract the polygon IDs from philly and add them to its dataframe as a column - I named it polyID. This requires a bit of understanding of the internal structure of philly. You can take a peek with str(philly, max.level = 2).

    I use slot(philly, "polygons") as argument to sapply() to iterate over the polygons slots and then extract the ID slot for each polygon, also with slot().

    Now we are able to use the polygon IDs with merge() to combine philly with philly_df.

philly$polyID <- sapply(slot(philly, "polygons"), function(x) slot(x, "ID"))
philly_df <- merge(philly_df, philly, by.x = "id", by.y="polyID")
head(philly_df)
  1. OK. All set to plot.

    There is a lot going on in this command, so I have provided comments in the code.

library(ggplot2)

ggplot() +                                               # initialize ggplot object
  geom_polygon(                                          # make a polygon
    data = philly_df,                                    # data frame
    aes(x = long, y = lat, group = group,                # coordinates, and group them by polygons
        fill = cut_number(HOMIC_R, 5))) +                # variable to use for filling
  scale_fill_brewer("Homicide Rate", palette = "OrRd") + # fill with brewer colors 
  ggtitle("Philadelphia homicide rate per 100,000") +    # add title
  theme(line = element_blank(),                          # remove axis lines .. 
        axis.text=element_blank(),                       # .. tickmarks..
        axis.title=element_blank(),                      # .. axis labels..
        panel.background = element_blank()) +            # .. background gridlines
  coord_equal()                                          # both axes the same scale

ggplot will soon be able to plot sf objects directly. This will look like:

ggplot(philly_sf) + geom_sf(aes(fill=HOMIC_R))

4. Adding basemaps with ggmap

ggmap builds on ggplot and allows to pull in tiled basemaps from different services, like Google Maps and OpenStreetMaps6.

So let’s overlay the map from above on a google satellite basemap.


Exercise 3

  1. First we use the get_map() command from ggmap to pull down the basemap. We need to tell it the location or the boundaries of the map, the zoom level, and what kind of map service we like (default is Google terrain). It will actually download the tile. get_map() returns a ggmap object, name it ph_basemap.

    In order to view the map we then use ggmap(ph_basemap).

    Look up the syntax of ?get_map(), go back and forth between get_map(..) and ggmap(ph_basemap) to find the correct parameters for our example.

library(ggmap)

ph_basemap <- get_map(location="Philadelphia, PA", zoom=11, maptype = 'satellite')

ggmap(ph_basemap)
  1. Then we can reuse the code from the ggplot example above, just replacing the first line, where we initialized a ggplot object above

    ggplot() + 
    ...

    with the line to call our basemap:

    ggmap(ph_basemap) +
    ...

    (We can get rid of the theme() and coord_equal() parts, as ggmap takes care of most of it.)

    See if you can copy and paste this together.

ggmap(ph_basemap) +
  geom_polygon(data = philly_df, aes(x=long, lat, group = group, fill = cut_number(HOMIC_R, 5))) + 
  scale_fill_brewer("Homicide Rate", palette = "OrRd") + 
  ggtitle("Philadelphia homicide rate per 100,000") # +
    #theme(line = element_blank(),  # don't need this here as ggmap takes care of it
    #    axis.text=element_blank(),
    #    axis.title=element_blank()
    #   panel.background = element_blank()) + 
  # coord_equal() # don't need this here as ggmap already takes care of this
  1. If you try the above code, you will notice that there is a problem. Any idea what might be going on?

Think for a moment before you look.

# Unfortunately we have to go back to our original `philly` object and reproject it 
# to the CRS that works with Google maps. 
# We then have to recreate our dataframe again.

philly_WGS84 <- spTransform(philly, CRS("+init=epsg:4326"))
philly_df_WGS84 <- tidy(philly_WGS84)
philly_WGS84$polyID <- sapply(slot(philly_WGS84, "polygons"), function(x) slot(x, "ID"))
philly_df_WGS84 <- merge(philly_df_WGS84, philly_WGS84, by.x = "id", by.y="polyID")

ggmap(ph_basemap) +
  geom_polygon(data = philly_df_WGS84, aes(x=long, lat, group = group, fill = cut_number(HOMIC_R, 5)), alpha = 0.8) + 
  scale_fill_brewer("Homicide Rate", palette = "OrRd") + 
  ggtitle("Philadelphia homicide rate per 100,000")


Be aware that the ggmap library also includes functions for distance calculations, geocoding, and calculating routes.

5. Choropleth with tmap

tmap also borrows from the ggplot syntax and is specifically designed to make creation of thematic maps more convenient. It takes care of a lot of the styling and aesthetics. This reduces our amount of code significantly. We only need:


Exercise 4

  1. Check tm_shape() and ?tm_polygons for how to set the parameters (map, break, title) and try on your own before you look.
library(tmap)
tm_shape(philly) +
  tm_polygons("HOMIC_R", style="quantile", title="Philadelphia \nhomicide rate \nper 100,000")
  1. tmap has a very nice feature that allows us to give basic interactivity to the map. We can easily switch from “plot” mode into “view” mode and call the last plot, like so:
tmap_mode("view")
last_map()

Cool huh?

The tmap library also includes functions for simple spatial operations, geocoding and reverse geocoding using OSM. For more check vignette("tmap-nutshell").

6. Web mapping with leaflet

Lastly, leaflet7 makes use of the widely known ‘Leaflet’ JavaScript library, “the leading open-source JavaScript library for mobile-friendly interactive maps”. We have already seen a simple use of leaflet in the tmap example.

The good news is that the leaflet library gives us loads of options to customize the web look and feel of the map.

The bad news is that the leaflet library gives us loads of options to customize the web look and feel of the map.

You don’t have to, but it makes the code more accessible if you use the pipe operator %>% to chain the elements together when building up a map with leaflet.

Let’s build up the map step by step.


Exercise 5

  1. Load the leaflet library. Use the leaflet() function with the Spatial*Object and pipe it to addPolygons() function. Just to show us a default map of Philly.

    leaflet(name_of_our_spatialPoly) %>%
      addPolygons()

    Is this what you did?:

library(leaflet) 

# first try... ops what happened here
leaflet(philly) %>%
  addPolygons()
  1. Second try.. better.
leaflet(philly_WGS84) %>%
  addPolygons()
  1. Map the homicide rate. For this we provide several parameters to the addPolygons() function that:

    • remove stroke (polygon borders)
    • set a fillColor for each polygon based on HOMIC_R and make it look nice by adjusting fillOpacity and smoothFactor (how much to simplify the polyline on each zoom level). The fill color is generated using the colorQuantile() function, which takes the color scheme and the desired number of classes. colorQuantile() then returns a function that we supply to addPolygons() with the name of the value we want to map to constuct the color scheme.
    • add a popup with the HOMIC_R values. We will create as a vector of strings, that we then supply to addPolygons().

    Try the code below.

pal_fun <- colorQuantile("YlOrRd", NULL, n = 5)

p_popup <- paste0("<strong>Homicide Rate: </strong>", philly_WGS84$HOMIC_R)

leaflet(philly_WGS84) %>%
  addPolygons(
    stroke = FALSE, # remove polygon borders
    fillColor = ~pal_fun(HOMIC_R), # set fill color with function from above and value
    fillOpacity = 0.8, smoothFactor = 0.5, # make it nicer
    popup = p_popup)  # add popup
  1. Add the default basemap, which is OSM, with addTiles(). This you can do!
leaflet(philly_WGS84) %>%
  addPolygons(
    stroke = FALSE, 
    fillColor = ~pal_fun(HOMIC_R),
    fillOpacity = 0.8, smoothFactor = 0.5,
    popup = p_popup) %>%
  addTiles()
  1. Add a legend. We will provide the addLegend() function with:

    • the location of the legend on the map
    • the function that creates the color palette
    • the value we want the palette function to use
    • a title
leaflet(philly_WGS84) %>%
  addPolygons(
    stroke = FALSE, 
    fillColor = ~pal_fun(HOMIC_R),
    fillOpacity = 0.8, smoothFactor = 0.5,
    popup = p_popup) %>%
  addTiles() %>%
  addLegend("bottomright",  # location
            pal=pal_fun,    # palette function
            values=~HOMIC_R,  # value to be passed to palette function
            title = 'Philadelphia homicide rate per 100,000') # legend title
  1. Ok, so this is a bit annoying, since the labels of the legend show percentages instead of the actual value breaks.

    The formatting is set with ‘labFormat()’ and in the documentation we discover that:

    “By default, labFormat is basically format(scientific = FALSE,big.mark = ‘,’) for the numeric palette, as.character() for the factor palette, and a function to return labels of the form ‘x[i] - x[i + 1]’ for bin and quantile palettes (in the case of quantile palettes, x is the probabilities instead of the values of breaks)”

    So it appears that we need to set the labels for our breaks manually. We replace the pal and values with the colors and labels arguments and set those directly using brewer.pal() and breaks_qt from an earlier section above.

leaflet(philly_WGS84) %>%
  addPolygons(
    stroke = FALSE, 
    fillColor = ~pal_fun(HOMIC_R),
    fillOpacity = 0.8, smoothFactor = 0.5,
    popup = p_popup) %>%
  addTiles() %>%
  addLegend("bottomright", 
            colors = brewer.pal(5, "YlOrRd"), 
            labels = paste0("up to ", as.character(round(breaks_qt$brks[-1]))),
            title = 'Philadelphia homicide rate per 100,000')
  1. That’s more like it. Finally, I have added for you a control to switch to another basemap and turn the philly polygon off and on. Take a look at the changes in the code below.
leaflet(philly_WGS84) %>%
  addPolygons(
    stroke = FALSE, 
    fillColor = ~pal_fun(HOMIC_R),
    fillOpacity = 0.8, smoothFactor = 0.5,
    popup = p_popup,
    group = "philly") %>%
  addTiles(group = "OSM") %>%
  addProviderTiles("CartoDB.DarkMatter", group = "Carto") %>%
  addLegend("bottomright", 
            colors = brewer.pal(5, "YlOrRd"), 
            labels = paste0("up to ", as.character(round(breaks_qt$brks[-1]))),
            title = 'Philadelphia homicide rate per 100,000') %>%
  addLayersControl(baseGroups = c("OSM", "Carto"), 
                   overlayGroups = c("philly"))  

If you want to take this further you may want to look into additional tools. Here is a demo using ggplot, leaflet, shiny, and RStudio’s flexdashboard template to bring it all together.


  1. Higher degrees are: Associate’s, Bachelor’s, Master’s, Professional school, Doctorate

  2. This is not the only way to provide color palettes. You can create your customized palette in many different ways or simply as a vector of hexbin color codes, like c( "#FDBB84" "#FC8D59" "#EF6548").

  3. For the correction of breaks after using classIntervals with spplot/levelplot see here http://r.789695.n4.nabble.com/SpatialPolygon-with-the-max-value-gets-no-color-assigned-in-spplot-function-when-using-quot-at-quot-r-td4654672.html

  4. See Wilkinson L (2005): “The grammar of graphics”. Statistics and computing, 2nd ed. Springer, New York.

  5. You may still see examples that use ggplot2::fortify. Be aware that this may be deprecated in the future.

  6. Note that the use of Stamen Maps currently only works with a patch and that Cloudmade maps retired its API so it is no longer possible to be used as basemap. RgoogleMaps is another library that provides an interface to query the Google server for static maps.

  7. The leafletR package does very similar things. The syntax approach is different between the two packages. My reason for using leaflet is that it integrates well with RStudio, Shiny, and R Markdown.

LS0tCnRpdGxlOiAiTWFraW5nIG1hcHMgaW4gUiIKYXV0aG9yOiAiY2xhdWRpYSBhIGVuZ2VsIgpkYXRlOiAiTGFzdCB1cGRhdGVkOiBgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVCICVkLCAlWScpYCIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICBmaWdfY2FwdGlvbjogbm8KICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDQKLS0tCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQojIyBsaWJyYXJpZXMgbmVlZGVkIGZvciBSIGNvZGUgZXhhbXBsZXMKbGlicmFyeShzcCkKbGlicmFyeShzZikKbGlicmFyeShyZ2RhbCkKbGlicmFyeShjbGFzc0ludCkKbGlicmFyeShSQ29sb3JCcmV3ZXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShnZ21hcCkKbGlicmFyeShsZWFmbGV0KQpsaWJyYXJ5KGJyb29tKQpsaWJyYXJ5KHRtYXApCmxpYnJhcnkoR0lTVG9vbHMpCgojIG5lZWQgdGhpcyBmaWxlIGZvciBsYXRlcgpwaGlsbHkgPC0gcmVhZE9HUigiL1VzZXJzL2NlbmdlbC9EZXNrdG9wL1BoaWxseTMvIiwgIlBoaWxseTMiLCB2ZXJib3NlID0gRikKYGBgCkxpYnJhcmllcyBuZWVkZWQgZm9yIHRoaXMgc2VjdGlvbiBhcmU6CgoqIFtgc3BgXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy9wYWNrYWdlPXNwKQoqIFtgcmdkYWxgXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy9wYWNrYWdlPXJnZGFsKQoqIFtgc2ZgXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy9wYWNrYWdlPXNmKQoqIFtgY2xhc3NJbnRgXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy9wYWNrYWdlPWNsYXNzSW50KQoqIFtgUkNvbG9yQnJld2VyYF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvcGFja2FnZT1SQ29sb3JCcmV3ZXIpCiogW2Bicm9vbWBdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3BhY2thZ2U9YnJvb20pCiogW2BnZ3Bsb3QyYF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvcGFja2FnZT1nZ3Bsb3QyKQoqIFtgZ2dtYXBgXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy9wYWNrYWdlPWdnbWFwKQoqIFtgdG1hcGBdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3BhY2thZ2U9dG1hcCkKKiBbYGxlYWZsZXRgXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy9wYWNrYWdlPWxlYWZsZXQpCgpEYXRhIG5lZWRlZDoKCiogU2hhcGVmaWxlIG9mIFBoaWxhZGVscGhpYSBjZW5zdXMgdHJhY3RzIGFuZCBob21pY2lkZSByYXRlczogW1BoaWxseTMuemlwXShodHRwczovL3d3dy5kcm9wYm94LmNvbS9zL3B3NDYxMXA3eDNoNXNxYS9QaGlsbHkzLnppcD9kbD0xKSAKClIgY29tZXMgd2l0aCBhIGJhc2ljIGBwbG90YCBjb21tYW5kLCB3aGljaCBjYW4gYWxzbyBiZSB1c2VkIGZvciBzaW1wbGUgbWFwIHZpZXdpbmcuIEluIHRoaXMgd29ya3Nob3Agd2Ugd2lsbCBsb29rIGludG8gc2V2ZXJhbCBhbHRlcm5hdGl2ZXMgdG8gbWFwIHNwYXRpYWwgZGF0YSB3aXRoIFIuIFRoaXMgbGlzdCBpcyBvZiBjb3Vyc2Ugbm90IGNvbXByZWhlbnNpdmUsIGJ1dCBzaG91bGQgZ2l2ZSB5b3Ugc29tZXRoaW5nIHRvIHN0YXJ0IHdpdGguIEZvciBtb3JlIHBhY2thZ2VzIHNlZSB0aGUgIlZpc3VhbGlzYXRpb24iIHNlY3Rpb24gb2YgdGhlIFtDUkFOIFRhc2sgVmlld10oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3ZpZXdzL1NwYXRpYWwuaHRtbCkuCgpPZiB0aGUgcGFja2FnZXMgbWVudGlvbmVkICBgc3BwbG90YCAgb25seSB0YWtlcyBgc3BgIG9iamVjdHMsIApgdG1hcGAgYW5kIGBsZWFmbGV0YCBjYW4gYWxzbyB0YWtlIGBzZmAgb2JqZWN0cy4gVGhlIFtkZXZlbG9wbWVudCB2ZXJzaW9uIG9mIGBnZ3Bsb3QyYF0oaHR0cHM6Ly9naXRodWIuY29tL3RpZHl2ZXJzZS9nZ3Bsb3QyL3JlbGVhc2VzKSBjYW4gdGFrZSBgc2ZgIG9iamVjdHMsIHRob3VnaCBgZ2dtYXBgIFtzZWVtcyB0byBzdGlsbCBoYXZlIGlzc3Vlc10oaHR0cHM6Ly9naXRodWIuY29tL3RpZHl2ZXJzZS9nZ3Bsb3QyL2lzc3Vlcy8yMTMwKSB3aXRoIGBzZmAuCgojIDEuIENob3JvcGxldGggbWFwcGluZyB3aXRoIGBzcHBsb3RgIAoKYHNwYCBjb21lcyB3aXRoIGEgcGxvdCBjb21tYW5kIGBzcHBsb3QoKWAsIHdoaWNoIHRha2VzIGBTcGF0aWFsKmAgb2JqZWN0cyB0byBwbG90LiBgc3BwbG90KClgIGlzIG9uZSBvZiB0aGUgZWFybGllc3QgZnVuY3Rpb25zIHRvIHBsb3QgZ2VvZ3JhcGhpYyBvYmplY3RzLgoKKioqCiMjIyBFeGVyY2lzZSAxCgoxLiBVc2UgYHJlYWRPR1IoKWAgZnJvbSB0aGUgYHJnZGFsYCBsaWJyeWFyeSB0byByZWFkIHRoZSBgUGhpbGx5M2Agc2hhcGVmaWxlIGludG8gYW4gb2JqZWN0IG5hbWVkIGBwaGlsbHlgLgoKMi4gVXNlIGBuYW1lcygpYCB0byBzZWUgdGhlIGF0dHJpYnV0ZXMuIEkgaGF2ZSBhZGRlZCB0aGUgZm9sbG93aW5nIGZpZWxkczoKCiogX05fSE9NSUNfOiBOdW1iZXIgb2YgaG9taWNpZGVzIChzaW5jZSAyMDA2KQoqIF9IT01JQ19SXzogaG9taWNpZGUgcmF0ZSBwZXIgMTAwLDAwMCAoUGhpbGFkZWxwaGlhIE9wZW4gRGF0YSkKKiBfUENUX0NPTF86ICUgMjUgeWVhcnMgYW5kIG9sZGVyIHdpdGggY29sbGVnZSBvciBoaWdoZXIgZGVncmVlW14xXSAoQUNTIDIwMDYtMjAxMCkKKiBfbWRISG5jXzogZXN0aW1hdGVkIG1lZGlhbiBob3VzZWhvbGQgaW5jb21lIChBQ1MgMjAwNi0yMDEwKQoKW14xXTogSGlnaGVyIGRlZ3JlZXMgYXJlOiBBc3NvY2lhdGUncywgQmFjaGVsb3IncywgTWFzdGVyJ3MsIFByb2Zlc3Npb25hbCBzY2hvb2wsIERvY3RvcmF0ZQoKMy4gVXNlIHRoZSBiYXNlIGBwbG90YCBjb21tYW5kIGFuZCBzZWUgd2hhdCB5b3UgZ2V0LgoKYGBge3IgZXZhbD1GQUxTRX0KcGxvdCAocGhpbGx5KQpgYGAKCjQuIFVzZSB0aGUgYHNwcGxvdGAgY29tbWFuZCAobWFrZSBzdXJlIHlvdSBpbnN0YWxsZWQgYW5kIGxvYWRlZCBgc3BgKSBhbmQgY29tcGFyZS4KCmBgYHtyIGV2YWw9RkFMU0V9CnNwcGxvdChwaGlsbHkpCmBgYAoKNS4gTm90IHBhcnRpY3VsYXJseSB1c2VmdWwgZm9yIGFueSBpbnRlcnByZXRhdGlvbi4gIAoKICAgIFlvdSBjYW4gc2VlIHRoYXQgYnkgZGVmYXVsdCBgc3BwbG90YCB0cmllcyB0byBtYXAgZXZlcnl0aGluZyBpdCBjYW4gZmluZCBpbiB0aGUgYXR0cmlidXRlIHRhYmxlLiBTb21ldGltZXMsIGV2ZW4gdGhpcyBkb2VzIG5vdCB3b3JrLCBkZXBlbmRpbmcgb24gdGhlIGRhdGEgdHlwZXMgaW4gdGhlIGF0dHJpYnV0ZSB0YWJsZS4gSXQgYWxzbyB1c2VzIG9uZSBjbGFzc2lmaWNhdGlvbiBmb3IgYWxsIHRoZSBtYXBzLiAoVGhlIGxhdHRlciBhY3R1YWxseSBtYWtlcyBzZW5zZSwgYXMgb3RoZXJ3aXNlIHlvdSdkIGJlIGxpa2VseSB0byBjb21wYXJlIGFwcGxlcyB3aXRoIG9yYW5nZXMuKSAgCgogICAgSW4gb3JkZXIgdG8gc2VsZWN0IHNwZWNpZmljIHZhbHVlcyB0byBtYXAgd2UgY2FuIHByb3ZpZGUgdGhlIGBzcHBsb3RgIGZ1bmN0aW9uIHdpdGggdGhlIG5hbWUgKG9yIG5hbWVzKSBvZiB0aGUgYXR0cmlidXRlIHZhcmlhYmxlIHdlIHdhbnQgdG8gcGxvdC4gSXQgaXMgdGhlIG5hbWUgb2YgdGhlIGNvbHVtbiBvZiB0aGUgYFNwYXRpYWwqRGF0YWZyYW1lYCBhcyBjaGFyYWN0ZXIgc3RyaW5nIChvciBhIHZlY3RvciBpZiBzZXZlcmFsKS4gICAgIAoKICAgIFRyeSB0aGF0LgoKYGBge3IgZXZhbD1GQUxTRX0Kc3BwbG90KHBoaWxseSwiSE9NSUNfUiIpCiMgb3IKc3BwbG90KHBoaWxseSxjKCJIT01JQ19SIiwgIlBDVF9DT0wiKSkKYGBgCgo2LiBMZXQgdXMgc3RpY2sgd2l0aCBvbmUgdmFyaWFibGUgZm9yIG5vdyBhbmQgdHJ5IHRvIGltcHJvdmUgaXQgYSBsaXR0bGUuICAKRmlyc3Qgd2Ugd2FudCB0byBjaGFuZ2UgdGhlIGNvbG9yIHBhbGV0dGUuIEZvciB0aGlzIHdlIHVzZSBhIGxpYnJhcnkgY2FsbGVkIGBSQ29sb3JCcmV3ZXJgW14yXS4gRm9yIG1vcmUgYWJvdXQgQ29sb3JCcmV3ZXIgcGFsZXR0ZXMgcmVhZCBbdGhpc10oaHR0cDovL2NvbG9yYnJld2VyMi5vcmcpLiBMb2FkIHRoZSBgUkNvbG9yQnJld2VyYCBsaWJyYXJ5IGFuZCBleHBsb3JlIGFsbCBzZXF1ZW50aWFsIGNvbG9yIHNjaGVtZXMgd2l0aAoKICAgICAgICBkaXNwbGF5LmJyZXdlci5hbGwodHlwZT0ic2VxIikKCjcuIFRvIG1ha2UgdGhlIGNvbG9yIHBhbGV0dGVzIGZyb20gQ29sb3JCcmV3ZXIgYXZhaWxhYmxlIGFzIFIgcGFsZXR0ZXMgd2UgdXNlIGBicmV3ZXIucGFsKClgIEl0IHRha2VzIHR3byBhcmd1bWVudHM6CiAgICAtIHRoZSBudW1iZXIgb2YgZGlmZmVyZW50IGNvbG9ycyBkZXNpcmVkIGFuZCAKICAgIC0gdGhlIG5hbWUgb2YgdGhlIHBhbGV0dGUgYXMgY2hhcmFjdGVyIHN0cmluZy4gCiAgICAKICAgIFNlbGVjdCA1IGNvbG9ycyBmcm9tIHRoZSAnT3JhbmdlLVJlZCcgcGxhZXR0ZSBhbmQgYXNzaWduIGl0IHRvIGFuIG9iamVjdCBgcGFsYC4gV2hhdCBraW5kIG9mIG9iamVjdCBpcyBgcGFsYD8KCmBgYHtyIGV2YWw9RkFMU0V9CmxpYnJhcnkoUkNvbG9yQnJld2VyKQpkaXNwbGF5LmJyZXdlci5hbGwodHlwZT0ic2VxIikKcGFsIDwtIGJyZXdlci5wYWwoNSwgIk9yUmQiKSAjIHdlIHNlbGVjdCA1IGNvbG9ycyBmcm9tIHRoZSBwYWxldHRlCmNsYXNzKHBhbCkKYGBgCgpbXjJdOiBUaGlzIGlzIG5vdCB0aGUgb25seSB3YXkgdG8gcHJvdmlkZSBjb2xvciBwYWxldHRlcy4gWW91IGNhbiBjcmVhdGUgeW91ciBjdXN0b21pemVkIHBhbGV0dGUgaW4gbWFueSBkaWZmZXJlbnQgd2F5cyBvciBzaW1wbHkgYXMgYSB2ZWN0b3Igb2YgaGV4YmluIGNvbG9yIGNvZGVzLCBsaWtlIGBjKCAiI0ZEQkI4NCIgIiNGQzhENTkiICIjRUY2NTQ4IilgLgoKOC4gTm93IHdlIHBhc3MgdGhpcyBpbmZvcm1hdGlvbiBvbiB0byBgc3BwbG90YC4gV2UgbmVlZCB0byBwcm92aWRlIHR3byBtb3JlIGFyZ3VtZW50czoKICAgIC0gYGNvbC5yZWdpb25zYCB3aGljaCB3ZSBzZXQgdG8gdGhlIHBhbGV0dGUgd2UganVzdCBjcmVhdGVkIGFuZCAKICAgIC0gYGN1dHNgIHdoaWNoIGluIG91ciBjYXNlIGlzIDQuIEl0IGJpbnMgb3VyIGNvbnRpbm91cyB2YXJpYWJsZSBpbnRvIDUgYnJhY2tldHMgYW5kIHdpbGwgbWFrZSBvdXIgY29sb3JzIG1hdGNoIHVwIHdpdGggdGhvc2UgY2xhc3MgYnJhY2tldHMuCgpgYGB7ciBldmFsPUZBTFNFfQpzcHBsb3QocGhpbGx5LCJIT01JQ19SIiwgY29sLnJlZ2lvbnMgPSBwYWwsIGN1dHMgPSA0KSAKYGBgCgo5LiBMb29rcyBiZXR0ZXIgYWxyZWFkeS4gQnV0IHdlIHN0aWxsIGhhdmUgdGhpcyBvbmUgYXJlYSBzdGFuZGluZyBvdXQgd2l0aCBhbiBleHRyZW1lbHkgaGlnaCBob21pY2lkZSByYXRlLCB3aGljaCBtYWtlcyBhIGxhcmdlIHBhcnQgb2YgdGhlIG1hcCB1bnJlYWRhYmxlLiBTbyBsZXQncyBjaGFuZ2UgdGhlIGNsYXNzIGludGVydmFscyB0byBxdWFudGlsZXMuIFdlIHdpbGwgdXNlIGBjbGFzc0ludGVydmFsc2AgZnJvbSB0aGUgYGNsYXNzSW50YCBsaWJyYXJ5LCBzb21ldGhpbmcgbGlrZToKCiAgICAgICAgYnJlYWtzX3F0IDwtIGNsYXNzSW50ZXJ2YWxzKHZlY3Rvcl9vZl92YWx1ZXMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuID0gaG93X21hbnlfY2xhc3NlcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0eWxlID0gInF1YW50aWxlIiBbY2FuIGJlIG9taXR0ZWQgLS0gdGhlIGRlZmF1bHRdKQoKICAgIFRoaXMgcmV0dXJucyBhbiBvYmplY3Qgb2YgdHlwZSBgY2xhc3NJbnRlcnZhbHNgLiBGaW5kIG91dCBpdHMgc3RydWN0dXJlLiBIb3cgd291bGQgeW91IGFjY2VzcyB0aGUgYnJlYWsgdmFsdWVzPwoKYGBge3IgZXZhbD1GQUxTRX0KbGlicmFyeShjbGFzc0ludCkKYnJlYWtzX3F0IDwtIGNsYXNzSW50ZXJ2YWxzKHBoaWxseSRIT01JQ19SLCBuID0gNSkKc3RyKGJyZWFrc19xdCkKYnJlYWtzX3F0JGJya3MKYGBgCgoxMC4gRmluYWxseSB3ZSB1c2UgdGhvc2UgYnJlYWtzIHRvIHNldCB0aGUgYGF0PWAgYXJndW1lbnQgaW4gYHNwcGxvdCgpYC4gTGV0J3MgYWxzbyBzZXQgYG1haW49YCB0byBhZGQgYSB0aXRsZS4gCgpgYGB7ciBldmFsPUZBTFNFfQpzcHBsb3QocGhpbGx5LCAiSE9NSUNfUiIsIGNvbC5yZWdpb25zPXBhbCwgYXQgPSBicmVha3NfcXQkYnJrcywgIG1haW4gPSAiUGhpbGFkZWxwaGlhIGhvbWljaWRlIHJhdGUgcGVyIDEwMCwwMDAiKQpgYGAKCjExLiBJZiB5b3Ugbm93IGxvb2sgY2xvc2VseSB5b3Ugd2lsbCBzZWUgdGhhdCB0aGVyZSBhcmUgYSBmZXcgYmxhbmsgcG9seWdvbnMuIEhlcmUgYXJlIHRoZSBzdGVwcyB0byBjb3JyZWN0IHRoZSBicmVha3NbXjNdLgoKYGBge3IgZXZhbD1GQUxTRX0KIyBhZGQgYSB2ZXJ5IHNtYWxsIHZhbHVlIHRvIHRoZSB0b3AgYnJlYWtwb2ludCwgYW5kIHN1YnRyYWN0IGZyb20gdGhlIGJvdHRvbSBmb3Igc3ltbWV0cnkgCmJyIDwtIGJyZWFrc19xdCRicmtzIApvZmZzIDwtIDAuMDAwMDAwMSAKYnJbMV0gPC0gYnJbMV0gLSBvZmZzIApicltsZW5ndGgoYnIpXSA8LSBicltsZW5ndGgoYnIpXSArIG9mZnMgCgojIHBsb3QKc3BwbG90KHBoaWxseSwgIkhPTUlDX1IiLCBjb2wucmVnaW9ucz1wYWwsIGF0PWJyLCAgbWFpbiA9ICJQaGlsYWRlbHBoaWEgaG9taWNpZGUgcmF0ZSBwZXIgMTAwLDAwMCIpCmBgYAoKMTIuIFRoZSBiaWdnZXN0IHJlbWFpbmlnIGlzc3VlIGlzIHRoZSBsZWdlbmQsIHdoaWNoIHNob3dzIGFzIGEgZ3JhZHVhdGVkIGNvbG9yLCBzaW5jZSB3ZSBwcm92aWRlZCBhIHZlY3RvciBvZiBjb250aW51b3VzIHZhbHVlcyB0byBtYXAuIEhlcmUgaXMgaG93IHdlIGNhbiBjaGFuZ2UgdGhpczogCgogICAgLSBVc2UgdGhlIGBjdXQoKWAgZnVuY3Rpb24gZnJvbSB0aGUgYmFzZSBwYWNrYWdlIHdpdGggdGhlIHZhbHVlcyBmcm9tIGBwaGlsbHkkSE9NSUNfUmAgYW5kIHRoZSBjb3JyZWN0ZWQgYnJlYWtzIGBicmAgdG8gcmV0dXJuIGEgdmVjdG9yIHdpdGggdGhlIHJlc3BlY3RpdmUgYm91bmRhcmllcyBvZiB0aGUgYnJhY2tldHMuIFVzZSBgP2N1dGAgaWYgeW91IG5lZWQgaGVscC4KICAgIC0gQXNzaWduIHRoZSBvdXRwdXQgdmVjdG9yIHlvdSBnZXQgYXMgYSBuZXcgY29sdW1uIGBIT01JQ19SX2JyYWNrZXRgIHRvIHRoZSBgcGhpbGx5YCBhdHRyaWJ1dGVzIHRhYmxlLiBJdCB3aWxsIGhlbHAgdG8gbWFwIHRoZSBjb2xvciBiYXNlZCBvbiB0aGUgYnJlYWtzLiBUYWtlIGEgbG9vayBhdCB0aGUgdmFsdWVzLiBXaGF0IG9iamVjdCBjbGFzcyBpcyB0aGF0IHZlY3Rvcj8gIAogICAgLSBSZW1vdmUgdGhlIGBhdD1gIHBhcmFtZXRlciBpbiBgc3BwbG90KClgICh3aGljaCBpcyBvbmx5IG5lZWRlZCBmb3IgY29udGludW91cyB2YXJpYWJsZXMpIGFuZCB0ZWxsIGl0IHRvIHBsb3QgYEhPTUlDX1JfYnJhY2tldGAuICAKCmBgYHtyIGV2YWw9RkFMU0V9CnBoaWxseSRIT01JQ19SX2JyYWNrZXQgPC0gY3V0KHBoaWxseSRIT01JQ19SLCBicikKaGVhZChwaGlsbHkkSE9NSUNfUl9icmFja2V0KQpjbGFzcyhwaGlsbHkkSE9NSUNfUl9icmFja2V0KQpzcHBsb3QocGhpbGx5LCAiSE9NSUNfUl9icmFja2V0IiwgY29sLnJlZ2lvbnM9cGFsLCBtYWluID0gIlBoaWxhZGVscGhpYSBob21pY2lkZSByYXRlIHBlciAxMDAsMDAwIikKYGBgCgpOb3csIHRoaXMgaXMgd2hhdCB5b3Ugc2hvdWxkIHNlZToKCmBgYHtyIGVjaG89RkFMU0V9CnBoaWxseSA8LSByZWFkT0dSKCIvVXNlcnMvY2VuZ2VsL0Rlc2t0b3AvUGhpbGx5My8iLCAiUGhpbGx5MyIsIHZlcmJvc2UgPSBGKQpwYWwgPC0gYnJld2VyLnBhbCg1LCAiT3JSZCIpCmJyZWFrc19xdCA8LSBjbGFzc0ludGVydmFscyhwaGlsbHkkSE9NSUNfUiwgbiA9IDUsIHN0eWxlID0gInF1YW50aWxlIikKYnIgPC0gYnJlYWtzX3F0JGJya3MgCm9mZnMgPC0gMC4wMDAwMDAxIApiclsxXSA8LSBiclsxXSAtIG9mZnMgCmJyW2xlbmd0aChicildIDwtIGJyW2xlbmd0aChicildICsgb2ZmcyAKcGhpbGx5JEhPTUlDX1JfYnJhY2tldCA8LSBjdXQocGhpbGx5JEhPTUlDX1IsIGJyKQpzcHBsb3QocGhpbGx5LCAiSE9NSUNfUl9icmFja2V0IiwgY29sLnJlZ2lvbnM9cGFsLCBtYWluID0gIlBoaWxhZGVscGhpYSBob21pY2lkZSByYXRlIHBlciAxMDAsMDAwIikKYGBgCkFuZCBiZWxvdyBpcyB0aGUgY29tcGxldGUgY29kZToKCmBgYHtyIGV2YWw9RkFMU0V9CmxpYnJhcnkocmdkYWwpCmxpYnJhcnkoc3ApCmxpYnJhcnkoUkNvbG9yQnJld2VyKQpsaWJyYXJ5KGNsYXNzSW50KQpwaGlsbHkgPC0gcmVhZE9HUigicGF0aF90b195b3VyX3NoYXBlZmlsZV9mb2xkZXIiLCAiUGhpbGx5MyIpICMKcGFsIDwtIGJyZXdlci5wYWwoNSwgIk9yUmQiKQpicmVha3NfcXQgPC0gY2xhc3NJbnRlcnZhbHMocGhpbGx5JEhPTUlDX1IsIG4gPSA1LCBzdHlsZSA9ICJxdWFudGlsZSIpCmJyIDwtIGJyZWFrc19xdCRicmtzIApvZmZzIDwtIDAuMDAwMDAwMSAKYnJbMV0gPC0gYnJbMV0gLSBvZmZzIApicltsZW5ndGgoYnIpXSA8LSBicltsZW5ndGgoYnIpXSArIG9mZnMgCnBoaWxseSRIT01JQ19SX2JyYWNrZXQgPC0gY3V0KHBoaWxseSRIT01JQ19SLCBicikKc3BwbG90KHBoaWxseSwgIkhPTUlDX1JfYnJhY2tldCIsIGNvbC5yZWdpb25zPXBhbCwgbWFpbiA9ICJQaGlsYWRlbHBoaWEgaG9taWNpZGUgcmF0ZSBwZXIgMTAwLDAwMCIpCmBgYAoKCioqKgoKVGhlcmUgYXJlIG1hbnkgbW9yZSBhcmd1bWVudHMgZm9yIHRoaXMgZnVuY3Rpb24gdG8gcHJvdmlkZSBhZGRpdGlvbmFsIHBsb3QgcGFyYW1ldGVycywgbGlrZSB0aGUgbGVnZW5kIHBvc2l0aW9uLCBsYWJlbHMsIHNjYWxlcywgZXRjLiAKCkhvd2V2ZXIsIGFzIHlvdSBoYXZlIHNlZW4sIHRoaXMgY2FuIGJlIHF1aXRlIHRlZGlvdXMuIAoKQXMgYW4gYWx0ZXJuYXRpdmUgeW91IG1heSB3YW50IHRvIGJlIGF3YXJlIG9mIHRoZSBbYEdJU1Rvb2xzYF0oaHR0cHM6Ly9DUkFOLlItcHJvamVjdC5vcmcvcGFja2FnZT1HSVNUb29scykgcGFja2FnZS4gSXQgaW5jbHVkZXMgZnVuY3Rpb25zLCBsaWtlIGBjaG9yb3BsZXRoKClgIHRvIGRyYXcgY2hvcm9wbGV0aCB0aGF0IGFyZSByZWFsbHkganVzdCBjb252ZW5pZW5jZSBmdW5jdGlvbnMgdGhhdCB3cmFwIGFyb3VuZCBgc3BwbG90KClgCgpCZWxvdyBpcyB0aGUgY29kZSBpZiB5b3Ugd2FudGVkIHRvIGRvIGEgc2ltaWxhciBtYXAgYXMgYWJvdmUgd2l0aCBgR0lTVG9vbHNgLiBDdXJyZW50bHkgYEdJU1Rvb2xzYCBjYW5ub3QgdW5kZXJzdGFuZCBgc2ZgIG9iamVjdHMuCgpgYGB7ciBldmFsPUZBTFNFfQpsaWJyYXJ5KEdJU1Rvb2xzKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGxvYWQgbGlicmFyeQpjaG9yb3BsZXRoKHBoaWxseSwgcGhpbGx5JEhPTUlDX1IpICAgICAgICAgICAgICAjIHBsb3QgdGhlIHBvbHlnb25zCnNoZCA8LSAgYXV0by5zaGFkaW5nKHBoaWxseSRIT01JQ19SKSAgICAgICAgICAgICMgd2UgbmVlZCB0aGF0IGZvciB0aGUgbGVnZW5kIGNvbG9yaW5nCmNob3JvLmxlZ2VuZCggICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgcGxvdCB0aGUgbGVnZW5kCiAgYmJveChwaGlsbHkpWyJ4IiwibWF4Il0gLSA1MDAwLCAgICAgICAgICAgICAgICMgeCBjb29yZGluYXRlIG9mIHRvcCBsZWZ0IGNvcm5lcgogIGJib3gocGhpbGx5KVsieSIsIm1pbiJdICsgMTUwMDAsICAgICAgICAgICAgICAjIHkgY29vcmRpbmF0ZSBvZiB0b3AgbGVmdCBjb3JuZXIKICBzaGQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBjb2xvciBzY2hlbWUKICApICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAp0aXRsZSgiUGhpbGFkZWxwaGlhIGhvbWljaWRlIHJhdGUgcGVyIDEwMCwwMDAiKSAjIHBsb3QgdGhlIHRpdGxlLgpgYGAKCmBgYHtyIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmNob3JvcGxldGgocGhpbGx5LCBwaGlsbHkkSE9NSUNfUikgICMgcGxvdApzaGQgPC0gIGF1dG8uc2hhZGluZyhwaGlsbHkkSE9NSUNfUikgICMgd2UgbmVlZCB0aGF0IGZvciB0aGUgbGVnZW5kCmNob3JvLmxlZ2VuZChiYm94KHBoaWxseSlbIngiLCJtYXgiXSAtIDUwMDAsIGJib3gocGhpbGx5KVsieSIsIm1pbiJdICsgMTUwMDAsIHNoZCkgIyBhZGQgbGVnZW5kIHRvIHBsb3QKdGl0bGUoIlBoaWxhZGVscGhpYSBob21pY2lkZSByYXRlIHBlciAxMDAsMDAwIikgIyBhZGQgdGl0bGUuCmBgYAoKCiMgMi4gUGxvdHRpbmcgc2ltcGxlIGZlYXR1cmVzIChgc2ZgKSB3aXRoIGBwbG90YAoKVGhlIGBzZmAgcGFja2FnZSBleHRlbmRzIHRoZSBiYXNlIGBwbG90YCBjb21tYW5kLCBzbyBpdCBjYW4gYmUgdXNlZCBvbiBgc2ZgIG9iamVjdHMuIElmIHVzZWQgd2l0aG91dCBhbnkgYXJndW1lbnRzIGl0IHdpbGwgcGxvdCBhbGwgdGhlIGF0dHJpYnV0ZXMsIGxpa2UgYHNwcGxvdGAgZG9lcy4KCmBgYHtyIGV2YWw9RkFMU0V9CmxpYnJhcnkoc2YpCnBoaWxseV9zZiA8LSAgc3RfcmVhZCgifi9EZXNrdG9wL1BoaWxseTMvUGhpbGx5My5zaHAiKQpwbG90KHBoaWxseV9zZikKYGBgCgpUbyBwbG90IGEgc2luZ2xlIGF0dHJpYnV0ZSB3ZSBuZWVkIHRvIHByb3ZpZGUgYW4gb2JqZWN0IG9mIGNsYXNzIGBzZmAsIGxpa2Ugc286CgpgYGB7ciBldmFsPUZBTFNFfQpwbG90KHBoaWxseV9zZiRIT01JQ19SKSAjIHRoaXMgaXMgYSBudW1lcmljIHZlY3RvcgpwbG90KHBoaWxseV9zZlsiSE9NSUNfUiJdKQpgYGAKCklmIHdlIHdhbnRlZCB0byBhZGQgb3VyIG93biBjb2xvcnMsIGxlZ2VuZCBhbmQgdGl0bGUgd2Ugd291bGQgcmVjdXIgdG8gYmFzaWMgcGxvdCBwYXJhbWV0ZXJzIHRvIGRvIHRoaXMuCgpgYGB7ciBldmFsPUZBTFNFfQpocl9jdXRzIDwtICBjdXQocGhpbGx5X3NmJEhPTUlDX1IsIGJyKQpwbG90KHBoaWxseV9zZlsiSE9NSUNfUiJdLCBtYWluID0gIlBoaWxhZGVscGhpYSBob21pY2lkZSByYXRlIHBlciAxMDAsMDAwIiwgY29sID0gcGFsW2FzLm51bWVyaWMoaHJfY3V0cyldKQpsZWdlbmQoMTc2MDAwMCwgNDcxMDAwLCBsZWdlbmQgPSBwYXN0ZSgiPCIsIHJvdW5kKGJyWy0xXSkpLCBmaWxsID0gcGFsKSAgICAgICAgCmBgYAoKYGBge3IgZWNobz1GLCBtZXNzYWdlPUZBTFNFfQpwaGlsbHlfc2YgPC0gIHN0X3JlYWQoIn4vRGVza3RvcC9QaGlsbHkzL1BoaWxseTMuc2hwIiwgcXVpZXQgPSBUKQpocl9jdXRzIDwtICBjdXQocGhpbGx5X3NmJEhPTUlDX1IsIGJyKQpwbG90KHBoaWxseV9zZlsiSE9NSUNfUiJdLCBtYWluID0gIlBoaWxhZGVscGhpYSBob21pY2lkZSByYXRlIHBlciAxMDAsMDAwIiwgY29sID0gcGFsW2FzLm51bWVyaWMoaHJfY3V0cyldKQpsZWdlbmQoMTc2MDAwMCwgNDcxMDAwLCBsZWdlbmQgPSBwYXN0ZSgiPCIsIHJvdW5kKGJyWy0xXSkpLCBmaWxsID0gcGFsKQpgYGAKICAgCgpbXjNdOiBGb3IgdGhlIGNvcnJlY3Rpb24gb2YgYnJlYWtzIGFmdGVyIHVzaW5nIGNsYXNzSW50ZXJ2YWxzIHdpdGggc3BwbG90L2xldmVscGxvdCBzZWUgaGVyZSBodHRwOi8vci43ODk2OTUubjQubmFiYmxlLmNvbS9TcGF0aWFsUG9seWdvbi13aXRoLXRoZS1tYXgtdmFsdWUtZ2V0cy1uby1jb2xvci1hc3NpZ25lZC1pbi1zcHBsb3QtZnVuY3Rpb24td2hlbi11c2luZy1xdW90LWF0LXF1b3Qtci10ZDQ2NTQ2NzIuaHRtbAoKCgojIDMuIENob3JvcGxldGggbWFwcGluZyB3aXRoIGBnZ3Bsb3QyYAoKW2BnZ3Bsb3QyYF0oaHR0cDovL2dncGxvdDIub3JnLykgaXMgYSB3aWRlbHkgdXNlZCBhbmQgcG93ZXJmdWwgcGxvdHRpbmcgbGlicmFyeSBmb3IgUi4gSXQgaXMgbm90IHNwZWNpZmljYWxseSBnZWFyZWQgdG93YXJkcyBtYXBwaW5nLCBidXQgb25lIGNhbiBnZW5lcmF0ZSBncmVhdCBtYXBzLiAKClRoZSBgZ2dwbG90KClgIHN5bnRheCBpcyBkaWZmZXJlbnQgZnJvbSB0aGUgcHJldmlvdXMgYXMgYSBwbG90IGlzIGJ1aWx0IHVwIGJ5IGFkZGluZyBjb21wb25lbnRzIHdpdGggYSBgK2AuIFlvdSBjYW4gc3RhcnQgd2l0aCBhIGxheWVyIHNob3dpbmcgdGhlIHJhdyBkYXRhIHRoZW4gYWRkIGxheWVycyBvZiBhbm5vdGF0aW9ucyBhbmQgc3RhdGlzdGljYWwgc3VtbWFyaWVzLiBUaGlzIGFsbG93cyB0byBlYXNpbHkgc3VwZXJpbXBvc2UgZWl0aGVyIGRpZmZlcmVudCB2aXN1YWxpemF0aW9ucyBvZiBvbmUgZGF0YXNldCAoZS5nLiBhIHNjYXR0ZXJwbG90IGFuZCBhIGZpdHRlZCBsaW5lKSBvciBkaWZmZXJlbnQgZGF0YXNldHMgKGxpa2UgZGlmZmVyZW50IGxheWVycyBvZiB0aGUgc2FtZSBnZW9ncmFwaGljYWwgYXJlYSlbXjRdLiAKCkZvciBhbiBpbnRyb2R1Y3Rpb24gdG8gYGdncGxvdGAgY2hlY2sgb3V0IFt0aGlzIGJvb2sgYnkgdGhlIHBhY2thZ2UgY3JlYXRvcl0oaHR0cDovL2xpbmsuc3ByaW5nZXIuY29tL2Jvb2svMTAuMTAwNyUyRjk3OC0zLTMxOS0yNDI3Ny00KSBvciBbdGhpc10oaHR0cDovL2dncGxvdDIudGlkeXZlcnNlLm9yZy8pIGZvciBtb3JlIHBvaW50ZXJzLgoKClteNF06IFNlZSBXaWxraW5zb24gTCAoMjAwNSk6ICJUaGUgZ3JhbW1hciBvZiBncmFwaGljcyIuIFN0YXRpc3RpY3MgYW5kIGNvbXB1dGluZywgMm5kIGVkLiBTcHJpbmdlciwgTmV3IFlvcmsuIAoKSW4gb3JkZXIgdG8gYnVpbGQgYSBwbG90IHlvdSBzdGFydCB3aXRoIGluaXRpYWxpemluZyBhIGdncGxvdCBvYmplY3QuIEluIG9yZGVyIHRvIGRvIHRoYXQKYGdncGxvdCgpYCB0YWtlczoKCi0gYSBkYXRhIGFyZ3VtZW50IHVzdWFsbHkgYSBfX2RhdGFmcmFtZV9fIGFuZCAKLSBhIG1hcHBpbmcgYXJndW1lbnQgd2hlcmUgeCBhbmQgeSB2YWx1ZXMgdG8gYmUgcGxvdHRlZCBhcmUgc3VwcGxpZWQuCgpJbiBhZGRpdGlvbiwgbWluaW1hbGx5IGEgZ2VvbWV0cnkgdG8gYmUgdXNlZCB0byBkZXRlcm1pbmUgaG93IHRoZSB2YWx1ZXMgc2hvdWxkIGJlIGRpc3BsYXllZC4gVGhpcyBpcyB0byBiZSBhZGRlZCBhZnRlciBhbiBgK2AuIAoKICAgIGdncGxvdChkYXRhID0gbXlfZGF0YV9mcmFtZSwgbWFwcGluZyA9IGFlcyh4ID0gbmFtZV9vZl9jb2x1bW5fd2l0aF94X3ZhbHVlLCB5ID0gbmFtZV9vZl9jb2x1bW5fd2l0aF95X3ZhbHVlKSkgKwogICAgICBnZW9tX3BvaW50KCkKCk9yIHNob3J0ZXI6CgogICAgZ2dwbG90KG15X2RhdGFfZnJhbWUsIGFlcyhuYW1lX29mX2NvbHVtbl93aXRoX3hfdmFsdWUsIG5hbWVfb2ZfY29sdW1uX3dpdGhfeV92YWx1ZSkpICsKICAgICAgZ2VvbV9wb2ludCgpCgoKU28gaWYgd2Ugd2FudGVkIHRvIG1hcCBwb2x5Z29ucywgbGlrZSBjZW5zdXMgdHJhY3QgYm91bmRhcmllcywgd2Ugd291bGQgdXNlIGxvbmdpdHVkZSBhbmQgbGF0aXR1ZGUgb2YgdGhlaXIgdmVydGljZXMgYXMgb3VyIGB4YCBhbmQgYHlgIHZhbHVlcyBhbmQgYGdlb21fcG9seWdvbigpYCBhcyBvdXIgZ2VvbWV0cnkuCgoqKioKCiMjIyBFeGVyY2lzZSAyCgoxLiBUbyBwbG90IHRoZSBlcXVpdmFsZW50IHRvIHRoZSBtYXAgd2UgY3JlYXRlZCB3aXRoIGBzcHBsb3RgIGFib3ZlIHdlIG5lZWQgdG8gY29udmVydCBgcGhpbGx5YCwgd2hpY2ggaXMgYSBgU3BhdGlhbFBvbHlnb25zRGF0YWZyYW1lYCwgdG8gYSByZWd1bGFyIGRhdGFmcmFtZS4gCgogICAgYGJyb29tYCBpcyBhIGdlbmVyYWwgcHVycG9zZSBwYWNrYWdlIHdoaWNoIHByb3ZpZGVzIGZ1bmN0aW9ucyB0byB0dXJuIHRoZSBtZXNzeSBvdXRwdXQgb2YgYnVpbHQtaW4gZnVuY3Rpb25zIGluIFIsIHN1Y2ggYXMgbG0sIG5scywgb3IgdC50ZXN0LCBpbnRvIFt0aWR5IGRhdGFdKGh0dHBzOi8vd3d3LmpzdGF0c29mdC5vcmcvYXJ0aWNsZS92aWV3L3YwNTlpMTApIGZyYW1lcy4gV2UgdXNlIHRoZSBgdGlkeSgpYCBjb21tYW5kIGZvciB0aGUgY29udmVyc2lvblteNV0uCgpbXjVdOiBZb3UgbWF5IHN0aWxsIHNlZSBleGFtcGxlcyB0aGF0IHVzZSBgZ2dwbG90Mjo6Zm9ydGlmeWAuIEJlIGF3YXJlIHRoYXQgdGhpcyBtYXkgYmUgZGVwcmVjYXRlZCBpbiB0aGUgZnV0dXJlLgoKMi4gTG9hZCB0aGUgYGJyb29tYCBsaWJyYXJ5LCBhbmQgdXNlIGB0aWR5YCBmb3IgdGhlIGNvbnZlcnNpb24uIENyZWF0ZSBhIG5ldyBvYmplY3QsIGBwaGlsbHlfZGZgIGZvciB0aGUgb3V0cHV0LiBXaGF0IGNvbHVtbnMgYW5kIHZhbHVlcyBkbyB5b3UgZ2V0PyAKCmBgYHtyIGV2YWw9RkFMU0V9CmxpYnJhcnkoYnJvb20pCnBoaWxseV9kZiA8LSB0aWR5KHBoaWxseSkKaGVhZChwaGlsbHlfZGYpCmBgYAoKMy4gSGEuIGB0aWR5KClgIHdpbGwgbWFrZSB1cyBsb29zZSB0aGUgYXR0cmlidXRlcyB0aGF0IHdlIHdhbnQgdG8gbWFwLCBzbyB3ZSBoYXZlIHRvIHRha2UgY2FyZSBvZiB0aGF0LiBXZSBleHRyYWN0IHRoZSBwb2x5Z29uIElEcyBmcm9tIGBwaGlsbHlgIGFuZCBhZGQgdGhlbSB0byBpdHMgZGF0YWZyYW1lIGFzIGEgY29sdW1uIC0gSSBuYW1lZCBpdCBgcG9seUlEYC4gVGhpcyByZXF1aXJlcyBhIGJpdCBvZiB1bmRlcnN0YW5kaW5nIG9mIHRoZSBpbnRlcm5hbCBzdHJ1Y3R1cmUgb2YgYHBoaWxseWAuIFlvdSBjYW4gdGFrZSBhIHBlZWsgd2l0aCBgc3RyKHBoaWxseSwgbWF4LmxldmVsID0gMilgLiAKCiAgICBJIHVzZSBgc2xvdChwaGlsbHksICJwb2x5Z29ucyIpYCBhcyBhcmd1bWVudCB0byBgc2FwcGx5KClgIHRvIGl0ZXJhdGUgb3ZlciB0aGUgcG9seWdvbnMgc2xvdHMgYW5kIHRoZW4gZXh0cmFjdCB0aGUgSUQgc2xvdCBmb3IgZWFjaCBwb2x5Z29uLCBhbHNvIHdpdGggYHNsb3QoKWAuIAoKICAgIE5vdyB3ZSBhcmUgYWJsZSB0byB1c2UgdGhlIHBvbHlnb24gSURzIHdpdGggYG1lcmdlKClgIHRvIGNvbWJpbmUgYHBoaWxseWAgd2l0aCBgcGhpbGx5X2RmYC4gCgpgYGB7ciBldmFsPUZBTFNFfQpwaGlsbHkkcG9seUlEIDwtIHNhcHBseShzbG90KHBoaWxseSwgInBvbHlnb25zIiksIGZ1bmN0aW9uKHgpIHNsb3QoeCwgIklEIikpCnBoaWxseV9kZiA8LSBtZXJnZShwaGlsbHlfZGYsIHBoaWxseSwgYnkueCA9ICJpZCIsIGJ5Lnk9InBvbHlJRCIpCmhlYWQocGhpbGx5X2RmKQpgYGAKCjQuIE9LLiBBbGwgc2V0IHRvIHBsb3QuIAogICAgCiAgICBUaGVyZSBpcyBhIGxvdCBnb2luZyBvbiBpbiB0aGlzIGNvbW1hbmQsIHNvIEkgaGF2ZSBwcm92aWRlZCBjb21tZW50cyBpbiB0aGUgY29kZS4KICAKCmBgYHtyIGV2YWw9RkFMU0V9CmxpYnJhcnkoZ2dwbG90MikKCmdncGxvdCgpICsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgaW5pdGlhbGl6ZSBnZ3Bsb3Qgb2JqZWN0CiAgZ2VvbV9wb2x5Z29uKCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgbWFrZSBhIHBvbHlnb24KICAgIGRhdGEgPSBwaGlsbHlfZGYsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBkYXRhIGZyYW1lCiAgICBhZXMoeCA9IGxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXAsICAgICAgICAgICAgICAgICMgY29vcmRpbmF0ZXMsIGFuZCBncm91cCB0aGVtIGJ5IHBvbHlnb25zCiAgICAgICAgZmlsbCA9IGN1dF9udW1iZXIoSE9NSUNfUiwgNSkpKSArICAgICAgICAgICAgICAgICMgdmFyaWFibGUgdG8gdXNlIGZvciBmaWxsaW5nCiAgc2NhbGVfZmlsbF9icmV3ZXIoIkhvbWljaWRlIFJhdGUiLCBwYWxldHRlID0gIk9yUmQiKSArICMgZmlsbCB3aXRoIGJyZXdlciBjb2xvcnMgCiAgZ2d0aXRsZSgiUGhpbGFkZWxwaGlhIGhvbWljaWRlIHJhdGUgcGVyIDEwMCwwMDAiKSArICAgICMgYWRkIHRpdGxlCiAgdGhlbWUobGluZSA9IGVsZW1lbnRfYmxhbmsoKSwgICAgICAgICAgICAgICAgICAgICAgICAgICMgcmVtb3ZlIGF4aXMgbGluZXMgLi4gCiAgICAgICAgYXhpcy50ZXh0PWVsZW1lbnRfYmxhbmsoKSwgICAgICAgICAgICAgICAgICAgICAgICMgLi4gdGlja21hcmtzLi4KICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfYmxhbmsoKSwgICAgICAgICAgICAgICAgICAgICAgIyAuLiBheGlzIGxhYmVscy4uCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkgKyAgICAgICAgICAgICMgLi4gYmFja2dyb3VuZCBncmlkbGluZXMKICBjb29yZF9lcXVhbCgpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBib3RoIGF4ZXMgdGhlIHNhbWUgc2NhbGUKYGBgCgpgYGB7ciBlY2hvPUYsIG1lc3NhZ2U9RkFMU0V9CnBoaWxseV9kZiA8LSB0aWR5KHBoaWxseSkKcGhpbGx5JHBvbHlJRCA8LSBzYXBwbHkoc2xvdChwaGlsbHksICJwb2x5Z29ucyIpLCBmdW5jdGlvbih4KSBzbG90KHgsICJJRCIpKQpwaGlsbHlfZGYgPC0gbWVyZ2UocGhpbGx5X2RmLCBwaGlsbHksIGJ5LnggPSAiaWQiLCBieS55PSJwb2x5SUQiKQpnZ3Bsb3QoKSArICMgaW5pdGlhbGl6ZSBnZ3Bsb3QKICBnZW9tX3BvbHlnb24oZGF0YSA9IHBoaWxseV9kZiwgYWVzKHg9bG9uZywgbGF0LCBncm91cCA9IGdyb3VwLCBmaWxsID0gY3V0X251bWJlcihIT01JQ19SLCA1KSkpICsKICBzY2FsZV9maWxsX2JyZXdlcigiSG9taWNpZGUgUmF0ZSIsIHBhbGV0dGUgPSAiT3JSZCIpICsgIyBmaWxsIHdpdGggYnJld2VyIGNvbG9ycyAKICBnZ3RpdGxlKCJQaGlsYWRlbHBoaWEgaG9taWNpZGUgcmF0ZSBwZXIgMTAwLDAwMCIpICsgICMgdGl0bGUKICB0aGVtZShsaW5lID0gZWxlbWVudF9ibGFuaygpLCAgIyByZW1vdmUgdGhlIGJhY2tncm91bmQsIHRpY2ttYXJrcywgZXRjCiAgICAgICAgYXhpcy50ZXh0PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSArCiAgY29vcmRfZXF1YWwoKQpgYGAKCioqYGdncGxvdGAgd2lsbCBzb29uIGJlIGFibGUgdG8gcGxvdCBgc2ZgIG9iamVjdHMgZGlyZWN0bHkuKiogVGhpcyB3aWxsIGxvb2sgbGlrZToKCiAgICBnZ3Bsb3QocGhpbGx5X3NmKSArIGdlb21fc2YoYWVzKGZpbGw9SE9NSUNfUikpCgoKIyA0LiBBZGRpbmcgYmFzZW1hcHMgd2l0aCBgZ2dtYXBgCgpgZ2dtYXBgIGJ1aWxkcyBvbiBgZ2dwbG90YCBhbmQgYWxsb3dzIHRvIHB1bGwgaW4gdGlsZWQgYmFzZW1hcHMgZnJvbSBkaWZmZXJlbnQgc2VydmljZXMsIGxpa2UgR29vZ2xlIE1hcHMgYW5kIE9wZW5TdHJlZXRNYXBzW142XS4KClNvIGxldCdzIG92ZXJsYXkgdGhlIG1hcCBmcm9tIGFib3ZlIG9uIGEgZ29vZ2xlIHNhdGVsbGl0ZSBiYXNlbWFwLgoKKioqCiMjIyBFeGVyY2lzZSAzCgoxLiBGaXJzdCB3ZSB1c2UgdGhlIGBnZXRfbWFwKClgIGNvbW1hbmQgZnJvbSBgZ2dtYXBgIHRvIHB1bGwgZG93biB0aGUgYmFzZW1hcC4gV2UgbmVlZCB0byB0ZWxsIGl0IHRoZSBsb2NhdGlvbiBvciB0aGUgYm91bmRhcmllcyBvZiB0aGUgbWFwLCB0aGUgem9vbSBsZXZlbCwgYW5kIHdoYXQga2luZCBvZiBtYXAgc2VydmljZSB3ZSBsaWtlIChkZWZhdWx0IGlzIEdvb2dsZSB0ZXJyYWluKS4gSXQgd2lsbCBhY3R1YWxseSBkb3dubG9hZCB0aGUgdGlsZS4gYGdldF9tYXAoKWAgcmV0dXJucyBhIGdnbWFwIG9iamVjdCwgbmFtZSBpdCBgcGhfYmFzZW1hcGAuCgogICAgSW4gb3JkZXIgdG8gdmlldyB0aGUgbWFwIHdlIHRoZW4gdXNlIGBnZ21hcChwaF9iYXNlbWFwKWAuCgogICAgTG9vayB1cCB0aGUgc3ludGF4IG9mIGA/Z2V0X21hcCgpYCwgZ28gYmFjayBhbmQgZm9ydGggYmV0d2VlbiBgZ2V0X21hcCguLilgIGFuZCBgZ2dtYXAocGhfYmFzZW1hcClgIHRvIGZpbmQgdGhlIGNvcnJlY3QgcGFyYW1ldGVycyBmb3Igb3VyIGV4YW1wbGUuCgoKYGBge3IgZXZhbD1GQUxTRX0KbGlicmFyeShnZ21hcCkKCnBoX2Jhc2VtYXAgPC0gZ2V0X21hcChsb2NhdGlvbj0iUGhpbGFkZWxwaGlhLCBQQSIsIHpvb209MTEsIG1hcHR5cGUgPSAnc2F0ZWxsaXRlJykKCmdnbWFwKHBoX2Jhc2VtYXApCmBgYAoKMi4gVGhlbiB3ZSBjYW4gcmV1c2UgdGhlIGNvZGUgZnJvbSB0aGUgZ2dwbG90IGV4YW1wbGUgYWJvdmUsIGp1c3QgcmVwbGFjaW5nIHRoZSBmaXJzdCBsaW5lLCB3aGVyZSB3ZSBpbml0aWFsaXplZCBhIGdncGxvdCBvYmplY3QgYWJvdmUKICAgICAgICAKICAgICAgICBnZ3Bsb3QoKSArIAogICAgICAgIC4uLgoKICAgIHdpdGggdGhlIGxpbmUgdG8gY2FsbCBvdXIgYmFzZW1hcDoKCiAgICAgICAgZ2dtYXAocGhfYmFzZW1hcCkgKwogICAgICAgIC4uLgogICAgICAgIAogICAgKFdlIGNhbiBnZXQgcmlkIG9mIHRoZSBgdGhlbWUoKWAgYW5kIGBjb29yZF9lcXVhbCgpYCBwYXJ0cywgYXMgYGdnbWFwYCB0YWtlcyBjYXJlIG9mIG1vc3Qgb2YgaXQuKQogICAgCiAgICBTZWUgaWYgeW91IGNhbiBjb3B5IGFuZCBwYXN0ZSB0aGlzIHRvZ2V0aGVyLiAKCmBgYHtyIGV2YWw9RkFMU0V9CmdnbWFwKHBoX2Jhc2VtYXApICsKICBnZW9tX3BvbHlnb24oZGF0YSA9IHBoaWxseV9kZiwgYWVzKHg9bG9uZywgbGF0LCBncm91cCA9IGdyb3VwLCBmaWxsID0gY3V0X251bWJlcihIT01JQ19SLCA1KSkpICsgCiAgc2NhbGVfZmlsbF9icmV3ZXIoIkhvbWljaWRlIFJhdGUiLCBwYWxldHRlID0gIk9yUmQiKSArIAogIGdndGl0bGUoIlBoaWxhZGVscGhpYSBob21pY2lkZSByYXRlIHBlciAxMDAsMDAwIikgIyArCiAgICAjdGhlbWUobGluZSA9IGVsZW1lbnRfYmxhbmsoKSwgICMgZG9uJ3QgbmVlZCB0aGlzIGhlcmUgYXMgZ2dtYXAgdGFrZXMgY2FyZSBvZiBpdAogICAgIyAgICBheGlzLnRleHQ9ZWxlbWVudF9ibGFuaygpLAogICAgIyAgICBheGlzLnRpdGxlPWVsZW1lbnRfYmxhbmsoKQogICAgIyAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpICsgCiAgIyBjb29yZF9lcXVhbCgpICMgZG9uJ3QgbmVlZCB0aGlzIGhlcmUgYXMgZ2dtYXAgYWxyZWFkeSB0YWtlcyBjYXJlIG9mIHRoaXMKYGBgCgoKMy4gSWYgeW91IHRyeSB0aGUgYWJvdmUgY29kZSwgeW91IHdpbGwgbm90aWNlIHRoYXQgdGhlcmUgaXMgYSBwcm9ibGVtLiBBbnkgaWRlYSB3aGF0IG1pZ2h0IGJlIGdvaW5nIG9uPyAKCj4gVGhpbmsgZm9yIGEgbW9tZW50IGJlZm9yZSB5b3UgbG9vay4KCmBgYHtyIGV2YWw9RkFMU0V9CiMgVW5mb3J0dW5hdGVseSB3ZSBoYXZlIHRvIGdvIGJhY2sgdG8gb3VyIG9yaWdpbmFsIGBwaGlsbHlgIG9iamVjdCBhbmQgcmVwcm9qZWN0IGl0IAojIHRvIHRoZSBDUlMgdGhhdCB3b3JrcyB3aXRoIEdvb2dsZSBtYXBzLiAKIyBXZSB0aGVuIGhhdmUgdG8gcmVjcmVhdGUgb3VyIGRhdGFmcmFtZSBhZ2Fpbi4KCnBoaWxseV9XR1M4NCA8LSBzcFRyYW5zZm9ybShwaGlsbHksIENSUygiK2luaXQ9ZXBzZzo0MzI2IikpCnBoaWxseV9kZl9XR1M4NCA8LSB0aWR5KHBoaWxseV9XR1M4NCkKcGhpbGx5X1dHUzg0JHBvbHlJRCA8LSBzYXBwbHkoc2xvdChwaGlsbHlfV0dTODQsICJwb2x5Z29ucyIpLCBmdW5jdGlvbih4KSBzbG90KHgsICJJRCIpKQpwaGlsbHlfZGZfV0dTODQgPC0gbWVyZ2UocGhpbGx5X2RmX1dHUzg0LCBwaGlsbHlfV0dTODQsIGJ5LnggPSAiaWQiLCBieS55PSJwb2x5SUQiKQoKZ2dtYXAocGhfYmFzZW1hcCkgKwogIGdlb21fcG9seWdvbihkYXRhID0gcGhpbGx5X2RmX1dHUzg0LCBhZXMoeD1sb25nLCBsYXQsIGdyb3VwID0gZ3JvdXAsIGZpbGwgPSBjdXRfbnVtYmVyKEhPTUlDX1IsIDUpKSwgYWxwaGEgPSAwLjgpICsgCiAgc2NhbGVfZmlsbF9icmV3ZXIoIkhvbWljaWRlIFJhdGUiLCBwYWxldHRlID0gIk9yUmQiKSArIAogIGdndGl0bGUoIlBoaWxhZGVscGhpYSBob21pY2lkZSByYXRlIHBlciAxMDAsMDAwIikKYGBgCgoKYGBge3IgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KcGhpbGx5X1dHUzg0IDwtIHNwVHJhbnNmb3JtKHBoaWxseSwgQ1JTKCIraW5pdD1lcHNnOjQzMjYiKSkKcGhpbGx5X2RmX1dHUzg0IDwtIHRpZHkocGhpbGx5X1dHUzg0KQpwaGlsbHlfV0dTODQkcG9seUlEIDwtIHNhcHBseShzbG90KHBoaWxseV9XR1M4NCwgInBvbHlnb25zIiksIGZ1bmN0aW9uKHgpIHNsb3QoeCwgIklEIikpCnBoaWxseV9kZl9XR1M4NCA8LSBtZXJnZShwaGlsbHlfZGZfV0dTODQsIHBoaWxseV9XR1M4NCwgYnkueCA9ICJpZCIsIGJ5Lnk9InBvbHlJRCIpCnBoX2Jhc2VtYXAgPC0gZ2V0X21hcChsb2NhdGlvbj0iUGhpbGFkZWxwaGlhLCBQQSIsIHpvb209MTEsIG1hcHR5cGUgPSAnc2F0ZWxsaXRlJykKZ2dtYXAocGhfYmFzZW1hcCkgKwogIGdlb21fcG9seWdvbihkYXRhID0gcGhpbGx5X2RmX1dHUzg0LCBhZXMoeD1sb25nLCBsYXQsIGdyb3VwID0gZ3JvdXAsIGZpbGwgPSBjdXRfbnVtYmVyKEhPTUlDX1IsIDUpKSwgYWxwaGEgPSAwLjgpICsgCiAgc2NhbGVfZmlsbF9icmV3ZXIoIkhvbWljaWRlIFJhdGUiLCBwYWxldHRlID0gIk9yUmQiKSArIAogIGdndGl0bGUoIlBoaWxhZGVscGhpYSBob21pY2lkZSByYXRlIHBlciAxMDAsMDAwIikKYGBgCgoKCioqKgoKQmUgYXdhcmUgdGhhdCB0aGUgYGdnbWFwYCBsaWJyYXJ5IGFsc28gaW5jbHVkZXMgZnVuY3Rpb25zIGZvciBkaXN0YW5jZSBjYWxjdWxhdGlvbnMsIGdlb2NvZGluZywgYW5kIGNhbGN1bGF0aW5nIHJvdXRlcy4KCgpbXjZdOiBOb3RlIHRoYXQgdGhlIHVzZSBvZiBTdGFtZW4gTWFwcyBjdXJyZW50bHkgb25seSB3b3JrcyB3aXRoIGEgcGF0Y2ggYW5kIHRoYXQgQ2xvdWRtYWRlIG1hcHMgcmV0aXJlZCBpdHMgQVBJIHNvIGl0IGlzIG5vIGxvbmdlciBwb3NzaWJsZSB0byBiZSB1c2VkIGFzIGJhc2VtYXAuIFtgUmdvb2dsZU1hcHNgXShodHRwczovL0NSQU4uUi1wcm9qZWN0Lm9yZy9wYWNrYWdlPVJnb29nbGVNYXBzKSBpcyBhbm90aGVyIGxpYnJhcnkgdGhhdCBwcm92aWRlcyBhbiBpbnRlcmZhY2UgdG8gcXVlcnkgdGhlIEdvb2dsZSBzZXJ2ZXIgZm9yIHN0YXRpYyBtYXBzLgoKCiMgNS4gQ2hvcm9wbGV0aCB3aXRoIGB0bWFwYAoKYHRtYXBgIGFsc28gYm9ycm93cyBmcm9tIHRoZSBnZ3Bsb3Qgc3ludGF4IGFuZCBpcyBzcGVjaWZpY2FsbHkgZGVzaWduZWQgdG8gbWFrZSBjcmVhdGlvbiBvZiB0aGVtYXRpYyBtYXBzIG1vcmUgY29udmVuaWVudC4gSXQgdGFrZXMgY2FyZSBvZiBhIGxvdCBvZiB0aGUgc3R5bGluZyBhbmQgYWVzdGhldGljcy4gVGhpcyByZWR1Y2VzIG91ciBhbW91bnQgb2YgY29kZSBzaWduaWZpY2FudGx5LiBXZSBvbmx5IG5lZWQ6IAoKLSBgdG1fc2hhcGUoKWAgYW5kIHByb3ZpZGUgdGhlIGBTcGF0aWFsUG9seWdvbnNEYXRhZnJhbWVgIGFzIGFyZ3VtZW50IGRpcmVjdGx5LCBubyBuZWVkIHRvIGNvbnZlcnQgaW50byBhIGRhdGEgZnJhbWUuIFRoaXMgaXMgZm9sbG93ZWQgYnkKLSBgK2AsIGFuZCAKLSBgdG1fcG9seWdvbnNgIHdoZXJlIHdlIHNldAogICAgLSB0aGUgYXR0cmlidXRlIHZhcmlhYmxlIHRvIG1hcCwgCiAgICAtIHRoZSBicmVhayBzdHlsZSwgYW5kIAogICAgLSB0aGUgdGl0bGUuCgoqKioKIyMjIEV4ZXJjaXNlIDQKCjEuIENoZWNrIGB0bV9zaGFwZSgpYCBhbmQgYD90bV9wb2x5Z29uc2AgZm9yIGhvdyB0byBzZXQgdGhlIHBhcmFtZXRlcnMgKG1hcCwgYnJlYWssIHRpdGxlKSBhbmQgdHJ5IG9uIHlvdXIgb3duIGJlZm9yZSB5b3UgbG9vay4KCmBgYHtyIGV2YWw9RkFMU0V9CmxpYnJhcnkodG1hcCkKdG1fc2hhcGUocGhpbGx5KSArCiAgdG1fcG9seWdvbnMoIkhPTUlDX1IiLCBzdHlsZT0icXVhbnRpbGUiLCB0aXRsZT0iUGhpbGFkZWxwaGlhIFxuaG9taWNpZGUgcmF0ZSBcbnBlciAxMDAsMDAwIikKYGBgCgoKMi4gYHRtYXBgIGhhcyBhIHZlcnkgbmljZSBmZWF0dXJlIHRoYXQgYWxsb3dzIHVzIHRvIGdpdmUgYmFzaWMgaW50ZXJhY3Rpdml0eSB0byB0aGUgbWFwLiBXZSBjYW4gZWFzaWx5IHN3aXRjaCBmcm9tICJwbG90IiBtb2RlIGludG8gInZpZXciIG1vZGUgYW5kIGNhbGwgdGhlIGxhc3QgcGxvdCwgbGlrZSBzbzoKCmBgYHtyIGV2YWw9RkFMU0V9CnRtYXBfbW9kZSgidmlldyIpCmxhc3RfbWFwKCkKYGBgCgpDb29sIGh1aD8KCmBgYHtyIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CnBoaWxseV90bWFwIDwtIHRtX3NoYXBlKHBoaWxseSkgKwogIHRtX3BvbHlnb25zKCJIT01JQ19SIiwgc3R5bGU9InF1YW50aWxlIiwgdGl0bGU9IlBoaWxhZGVscGhpYSBcbmhvbWljaWRlIHJhdGUgXG5wZXIgMTAwLDAwMCIpCnRtYXBfbW9kZSgidmlldyIpCnBoaWxseV90bWFwCmBgYAoKClRoZSBgdG1hcGAgbGlicmFyeSBhbHNvIGluY2x1ZGVzIGZ1bmN0aW9ucyBmb3Igc2ltcGxlIHNwYXRpYWwgb3BlcmF0aW9ucywgZ2VvY29kaW5nIGFuZCByZXZlcnNlIGdlb2NvZGluZyB1c2luZyBPU00uIEZvciBtb3JlIGNoZWNrIGB2aWduZXR0ZSgidG1hcC1udXRzaGVsbCIpIGAuIAoKCiMgNi4gV2ViIG1hcHBpbmcgd2l0aCBgbGVhZmxldGAKCkxhc3RseSwgYGxlYWZsZXRgW143XSBtYWtlcyB1c2Ugb2YgdGhlIHdpZGVseSBrbm93biBbJ0xlYWZsZXQnIEphdmFTY3JpcHQgbGlicmFyeV0oaHR0cDovL2xlYWZsZXRqcy5jb20pLCAidGhlIGxlYWRpbmcgb3Blbi1zb3VyY2UgSmF2YVNjcmlwdCBsaWJyYXJ5IGZvciBtb2JpbGUtZnJpZW5kbHkgaW50ZXJhY3RpdmUgbWFwcyIuIFdlIGhhdmUgYWxyZWFkeSBzZWVuIGEgc2ltcGxlIHVzZSBvZiBsZWFmbGV0IGluIHRoZSBgdG1hcGAgZXhhbXBsZS4gCgpUaGUgZ29vZCBuZXdzIGlzIHRoYXQgdGhlIGBsZWFmbGV0YCBsaWJyYXJ5IGdpdmVzIHVzIGxvYWRzIG9mIG9wdGlvbnMgdG8gY3VzdG9taXplIHRoZSB3ZWIgbG9vayBhbmQgZmVlbCBvZiB0aGUgbWFwLiAKClRoZSBiYWQgbmV3cyBpcyB0aGF0IHRoZSBgbGVhZmxldGAgbGlicmFyeSBnaXZlcyB1cyBsb2FkcyBvZiBvcHRpb25zIHRvIGN1c3RvbWl6ZSB0aGUgd2ViIGxvb2sgYW5kIGZlZWwgb2YgdGhlIG1hcC4KCllvdSBkb24ndCBoYXZlIHRvLCBidXQgaXQgbWFrZXMgdGhlIGNvZGUgbW9yZSBhY2Nlc3NpYmxlIGlmIHlvdSB1c2UgW3RoZSBwaXBlIG9wZXJhdG9yIGAlPiVgXShodHRwczovL2dpdGh1Yi5jb20vdGlkeXZlcnNlL21hZ3JpdHRyKSB0byBjaGFpbiB0aGUgZWxlbWVudHMgdG9nZXRoZXIgd2hlbiBidWlsZGluZyB1cCBhIG1hcCB3aXRoIGBsZWFmbGV0YC4gCgpMZXQncyBidWlsZCB1cCB0aGUgbWFwIHN0ZXAgYnkgc3RlcC4KCioqKgojIyMgRXhlcmNpc2UgNQoKMS4gTG9hZCB0aGUgYGxlYWZsZXRgIGxpYnJhcnkuIFVzZSB0aGUgYGxlYWZsZXQoKWAgZnVuY3Rpb24gd2l0aCB0aGUgU3BhdGlhbCpPYmplY3QgYW5kIHBpcGUgaXQgdG8gYWRkUG9seWdvbnMoKSBmdW5jdGlvbi4gSnVzdCB0byBzaG93IHVzIGEgZGVmYXVsdCBtYXAgb2YgUGhpbGx5LgoKICAgICAgICBsZWFmbGV0KG5hbWVfb2Zfb3VyX3NwYXRpYWxQb2x5KSAlPiUKICAgICAgICAgIGFkZFBvbHlnb25zKCkKICAKICAgIElzIHRoaXMgd2hhdCB5b3UgZGlkPzoKICAgIApgYGB7ciBldmFsPUZBTFNFfQpsaWJyYXJ5KGxlYWZsZXQpIAoKIyBmaXJzdCB0cnkuLi4gb3BzIHdoYXQgaGFwcGVuZWQgaGVyZQpsZWFmbGV0KHBoaWxseSkgJT4lCiAgYWRkUG9seWdvbnMoKQpgYGAKCjIuIFNlY29uZCB0cnkuLiBiZXR0ZXIuCgpgYGB7ciBldmFsPUZBTFNFfQpsZWFmbGV0KHBoaWxseV9XR1M4NCkgJT4lCiAgYWRkUG9seWdvbnMoKQpgYGAKCjMuIE1hcCB0aGUgaG9taWNpZGUgcmF0ZS4gRm9yIHRoaXMgd2UgcHJvdmlkZSBzZXZlcmFsIHBhcmFtZXRlcnMgdG8gdGhlIGBhZGRQb2x5Z29ucygpYCBmdW5jdGlvbiB0aGF0OgoKICAgIC0gcmVtb3ZlIHN0cm9rZSAocG9seWdvbiBib3JkZXJzKSAKICAgIC0gc2V0IGEgZmlsbENvbG9yIGZvciBlYWNoIHBvbHlnb24gYmFzZWQgb24gYEhPTUlDX1JgIGFuZCBtYWtlIGl0IGxvb2sgbmljZSBieSBhZGp1c3RpbmcgZmlsbE9wYWNpdHkgYW5kIHNtb290aEZhY3RvciAoaG93IG11Y2ggdG8gc2ltcGxpZnkgdGhlIHBvbHlsaW5lIG9uIGVhY2ggem9vbSBsZXZlbCkuIFRoZSBmaWxsIGNvbG9yIGlzIGdlbmVyYXRlZCB1c2luZyB0aGUgYGNvbG9yUXVhbnRpbGUoKWAgZnVuY3Rpb24sIHdoaWNoIHRha2VzIHRoZSBjb2xvciBzY2hlbWUgYW5kIHRoZSBkZXNpcmVkIG51bWJlciBvZiBjbGFzc2VzLiBgY29sb3JRdWFudGlsZSgpYCB0aGVuIHJldHVybnMgYSBmdW5jdGlvbiB0aGF0IHdlIHN1cHBseSB0byBgYWRkUG9seWdvbnMoKWAgd2l0aCB0aGUgbmFtZSBvZiB0aGUgdmFsdWUgd2Ugd2FudCB0byBtYXAgdG8gY29uc3R1Y3QgdGhlIGNvbG9yIHNjaGVtZS4gCiAgICAtIGFkZCBhIHBvcHVwIHdpdGggdGhlIGBIT01JQ19SYCB2YWx1ZXMuIFdlIHdpbGwgY3JlYXRlIGFzIGEgdmVjdG9yIG9mIHN0cmluZ3MsIHRoYXQgd2UgdGhlbiBzdXBwbHkgdG8gYGFkZFBvbHlnb25zKClgLgoKICAgIFRyeSB0aGUgY29kZSBiZWxvdy4KCmBgYHtyIGV2YWw9RkFMU0V9CnBhbF9mdW4gPC0gY29sb3JRdWFudGlsZSgiWWxPclJkIiwgTlVMTCwgbiA9IDUpCgpwX3BvcHVwIDwtIHBhc3RlMCgiPHN0cm9uZz5Ib21pY2lkZSBSYXRlOiA8L3N0cm9uZz4iLCBwaGlsbHlfV0dTODQkSE9NSUNfUikKCmxlYWZsZXQocGhpbGx5X1dHUzg0KSAlPiUKICBhZGRQb2x5Z29ucygKICAgIHN0cm9rZSA9IEZBTFNFLCAjIHJlbW92ZSBwb2x5Z29uIGJvcmRlcnMKICAgIGZpbGxDb2xvciA9IH5wYWxfZnVuKEhPTUlDX1IpLCAjIHNldCBmaWxsIGNvbG9yIHdpdGggZnVuY3Rpb24gZnJvbSBhYm92ZSBhbmQgdmFsdWUKICAgIGZpbGxPcGFjaXR5ID0gMC44LCBzbW9vdGhGYWN0b3IgPSAwLjUsICMgbWFrZSBpdCBuaWNlcgogICAgcG9wdXAgPSBwX3BvcHVwKSAgIyBhZGQgcG9wdXAKYGBgIAoKCjQuIEFkZCB0aGUgZGVmYXVsdCBiYXNlbWFwLCB3aGljaCBpcyBPU00sIHdpdGggYGFkZFRpbGVzKClgLiBUaGlzIHlvdSBjYW4gZG8hCgpgYGB7ciBldmFsPUZBTFNFfQpsZWFmbGV0KHBoaWxseV9XR1M4NCkgJT4lCiAgYWRkUG9seWdvbnMoCiAgICBzdHJva2UgPSBGQUxTRSwgCiAgICBmaWxsQ29sb3IgPSB+cGFsX2Z1bihIT01JQ19SKSwKICAgIGZpbGxPcGFjaXR5ID0gMC44LCBzbW9vdGhGYWN0b3IgPSAwLjUsCiAgICBwb3B1cCA9IHBfcG9wdXApICU+JQogIGFkZFRpbGVzKCkKYGBgCgo1LiBBZGQgYSBsZWdlbmQuIFdlIHdpbGwgcHJvdmlkZSB0aGUgYGFkZExlZ2VuZCgpYCBmdW5jdGlvbiB3aXRoOgoKICAgIC0gdGhlIGxvY2F0aW9uIG9mIHRoZSBsZWdlbmQgb24gdGhlIG1hcAogICAgLSB0aGUgZnVuY3Rpb24gdGhhdCBjcmVhdGVzIHRoZSBjb2xvciBwYWxldHRlCiAgICAtIHRoZSB2YWx1ZSB3ZSB3YW50IHRoZSBwYWxldHRlIGZ1bmN0aW9uIHRvIHVzZQogICAgLSBhIHRpdGxlCgpgYGB7ciBldmFsPUZBTFNFfQpsZWFmbGV0KHBoaWxseV9XR1M4NCkgJT4lCiAgYWRkUG9seWdvbnMoCiAgICBzdHJva2UgPSBGQUxTRSwgCiAgICBmaWxsQ29sb3IgPSB+cGFsX2Z1bihIT01JQ19SKSwKICAgIGZpbGxPcGFjaXR5ID0gMC44LCBzbW9vdGhGYWN0b3IgPSAwLjUsCiAgICBwb3B1cCA9IHBfcG9wdXApICU+JQogIGFkZFRpbGVzKCkgJT4lCiAgYWRkTGVnZW5kKCJib3R0b21yaWdodCIsICAjIGxvY2F0aW9uCiAgICAgICAgICAgIHBhbD1wYWxfZnVuLCAgICAjIHBhbGV0dGUgZnVuY3Rpb24KICAgICAgICAgICAgdmFsdWVzPX5IT01JQ19SLCAgIyB2YWx1ZSB0byBiZSBwYXNzZWQgdG8gcGFsZXR0ZSBmdW5jdGlvbgogICAgICAgICAgICB0aXRsZSA9ICdQaGlsYWRlbHBoaWEgaG9taWNpZGUgcmF0ZSBwZXIgMTAwLDAwMCcpICMgbGVnZW5kIHRpdGxlCmBgYAoKNi4gT2ssIHNvIHRoaXMgaXMgYSBiaXQgYW5ub3lpbmcsIHNpbmNlIHRoZSBsYWJlbHMgb2YgdGhlIGxlZ2VuZCBzaG93IHBlcmNlbnRhZ2VzIGluc3RlYWQgb2YgdGhlIGFjdHVhbCB2YWx1ZSBicmVha3MuIAoKICAgIFRoZSBmb3JtYXR0aW5nIGlzIHNldCB3aXRoICdsYWJGb3JtYXQoKScgYW5kIGluIHRoZSBbZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2xlYWZsZXQvbGVhZmxldC5wZGYpIHdlIGRpc2NvdmVyIHRoYXQ6CiAgICAKICAgICJCeSBkZWZhdWx0LCBsYWJGb3JtYXQgaXMgYmFzaWNhbGx5IGZvcm1hdChzY2llbnRpZmljID0gRkFMU0UsYmlnLm1hcmsgPSAnLCcpIGZvciB0aGUgbnVtZXJpYyBwYWxldHRlLCBhcy5jaGFyYWN0ZXIoKSBmb3IgdGhlIGZhY3RvciBwYWxldHRlLCBhbmQgYSBmdW5jdGlvbiB0byByZXR1cm4gbGFiZWxzIG9mIHRoZSBmb3JtIOKAmHhbaV0gLSB4W2kgKyAxXeKAmSBmb3IgYmluIGFuZCBxdWFudGlsZSBwYWxldHRlcyAoX19pbiB0aGUgY2FzZSBvZiBxdWFudGlsZSBwYWxldHRlcywgeCBpcyB0aGUgcHJvYmFiaWxpdGllcyBpbnN0ZWFkIG9mIHRoZSB2YWx1ZXMgb2YgYnJlYWtzX18pIgoKICAgIFNvIGl0IGFwcGVhcnMgdGhhdCB3ZSBuZWVkIHRvIHNldCB0aGUgbGFiZWxzIGZvciBvdXIgYnJlYWtzIG1hbnVhbGx5LiBXZSByZXBsYWNlIHRoZSBgcGFsYCBhbmQgYHZhbHVlc2Agd2l0aCB0aGUgYGNvbG9yc2AgYW5kIGBsYWJlbHNgIGFyZ3VtZW50cyBhbmQgc2V0IHRob3NlIGRpcmVjdGx5IHVzaW5nIGBicmV3ZXIucGFsKClgIGFuZCBgYnJlYWtzX3F0YCBmcm9tIGFuIGVhcmxpZXIgc2VjdGlvbiBhYm92ZS4KICAgIApgYGB7ciBldmFsPUZBTFNFfQpsZWFmbGV0KHBoaWxseV9XR1M4NCkgJT4lCiAgYWRkUG9seWdvbnMoCiAgICBzdHJva2UgPSBGQUxTRSwgCiAgICBmaWxsQ29sb3IgPSB+cGFsX2Z1bihIT01JQ19SKSwKICAgIGZpbGxPcGFjaXR5ID0gMC44LCBzbW9vdGhGYWN0b3IgPSAwLjUsCiAgICBwb3B1cCA9IHBfcG9wdXApICU+JQogIGFkZFRpbGVzKCkgJT4lCiAgYWRkTGVnZW5kKCJib3R0b21yaWdodCIsIAogICAgICAgICAgICBjb2xvcnMgPSBicmV3ZXIucGFsKDUsICJZbE9yUmQiKSwgCiAgICAgICAgICAgIGxhYmVscyA9IHBhc3RlMCgidXAgdG8gIiwgYXMuY2hhcmFjdGVyKHJvdW5kKGJyZWFrc19xdCRicmtzWy0xXSkpKSwKICAgICAgICAgICAgdGl0bGUgPSAnUGhpbGFkZWxwaGlhIGhvbWljaWRlIHJhdGUgcGVyIDEwMCwwMDAnKQpgYGAKCjcuIFRoYXQncyBtb3JlIGxpa2UgaXQuIEZpbmFsbHksIEkgaGF2ZSBhZGRlZCBmb3IgeW91IGEgY29udHJvbCB0byBzd2l0Y2ggdG8gYW5vdGhlciBiYXNlbWFwIGFuZCB0dXJuIHRoZSBwaGlsbHkgcG9seWdvbiBvZmYgYW5kIG9uLiBUYWtlIGEgbG9vayBhdCB0aGUgY2hhbmdlcyBpbiB0aGUgY29kZSBiZWxvdy4KCmBgYHtyIGV2YWw9RkFMU0V9CmxlYWZsZXQocGhpbGx5X1dHUzg0KSAlPiUKICBhZGRQb2x5Z29ucygKICAgIHN0cm9rZSA9IEZBTFNFLCAKICAgIGZpbGxDb2xvciA9IH5wYWxfZnVuKEhPTUlDX1IpLAogICAgZmlsbE9wYWNpdHkgPSAwLjgsIHNtb290aEZhY3RvciA9IDAuNSwKICAgIHBvcHVwID0gcF9wb3B1cCwKICAgIGdyb3VwID0gInBoaWxseSIpICU+JQogIGFkZFRpbGVzKGdyb3VwID0gIk9TTSIpICU+JQogIGFkZFByb3ZpZGVyVGlsZXMoIkNhcnRvREIuRGFya01hdHRlciIsIGdyb3VwID0gIkNhcnRvIikgJT4lCiAgYWRkTGVnZW5kKCJib3R0b21yaWdodCIsIAogICAgICAgICAgICBjb2xvcnMgPSBicmV3ZXIucGFsKDUsICJZbE9yUmQiKSwgCiAgICAgICAgICAgIGxhYmVscyA9IHBhc3RlMCgidXAgdG8gIiwgYXMuY2hhcmFjdGVyKHJvdW5kKGJyZWFrc19xdCRicmtzWy0xXSkpKSwKICAgICAgICAgICAgdGl0bGUgPSAnUGhpbGFkZWxwaGlhIGhvbWljaWRlIHJhdGUgcGVyIDEwMCwwMDAnKSAlPiUKICBhZGRMYXllcnNDb250cm9sKGJhc2VHcm91cHMgPSBjKCJPU00iLCAiQ2FydG8iKSwgCiAgICAgICAgICAgICAgICAgICBvdmVybGF5R3JvdXBzID0gYygicGhpbGx5IikpICAKYGBgCgoKYGBge3IgZWNobz1GQUxTRX0KcGhpbGx5X1dHUzg0IDwtIHNwVHJhbnNmb3JtKHBoaWxseSwgQ1JTKCIraW5pdD1lcHNnOjQzMjYiKSkKcGFsX2Z1biA8LSBjb2xvclF1YW50aWxlKCJZbE9yUmQiLCBOVUxMLCBuID0gNSkKcF9wb3B1cCA8LSBwYXN0ZTAoIjxzdHJvbmc+SG9taWNpZGUgUmF0ZTogPC9zdHJvbmc+IiwgCiAgICAgICAgICAgICAgICAgICAgICBwaGlsbHlfV0dTODQkSE9NSUNfUikKbGVhZmxldChwaGlsbHlfV0dTODQpICU+JQogIGFkZFBvbHlnb25zKAogICAgc3Ryb2tlID0gRkFMU0UsIAogICAgZmlsbENvbG9yID0gfnBhbF9mdW4oSE9NSUNfUiksCiAgICBmaWxsT3BhY2l0eSA9IDAuOCwgc21vb3RoRmFjdG9yID0gMC41LAogICAgcG9wdXAgPSBwX3BvcHVwLAogICAgZ3JvdXAgPSAicGhpbGx5IikgJT4lCiAgYWRkVGlsZXMoZ3JvdXAgPSAiT1NNIikgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcygiQ2FydG9EQi5EYXJrTWF0dGVyIiwgZ3JvdXAgPSAiQ2FydG8iKSAlPiUKICBhZGRMZWdlbmQoImJvdHRvbXJpZ2h0IiwgCiAgICAgICAgICAgIGNvbG9ycyA9IGJyZXdlci5wYWwoNSwgIllsT3JSZCIpLCAKICAgICAgICAgICAgbGFiZWxzID0gcGFzdGUwKCJ1cCB0byAiLCBhcy5jaGFyYWN0ZXIocm91bmQoYnJlYWtzX3F0JGJya3NbLTFdKSkpLAogICAgICAgICAgICB0aXRsZSA9ICdQaGlsYWRlbHBoaWEgaG9taWNpZGUgcmF0ZSBwZXIgMTAwLDAwMCcpICU+JQogIGFkZExheWVyc0NvbnRyb2woYmFzZUdyb3VwcyA9IGMoIk9TTSIsICJDYXJ0byIpLCAKICAgICAgICAgICAgICAgICAgIG92ZXJsYXlHcm91cHMgPSBjKCJwaGlsbHkiKSkgIApgYGAKCgpbXjddOiBUaGUgW2BsZWFmbGV0UmBdKGh0dHBzOi8vQ1JBTi5SLXByb2plY3Qub3JnL3BhY2thZ2U9bGVhZmxldFIpIHBhY2thZ2UgZG9lcyB2ZXJ5IHNpbWlsYXIgdGhpbmdzLiBUaGUgc3ludGF4IGFwcHJvYWNoIGlzIGRpZmZlcmVudCBiZXR3ZWVuIHRoZSB0d28gcGFja2FnZXMuIE15IHJlYXNvbiBmb3IgdXNpbmcgYGxlYWZsZXRgIGlzIHRoYXQgaXQgaW50ZWdyYXRlcyB3ZWxsIHdpdGggUlN0dWRpbywgU2hpbnksIGFuZCBSIE1hcmtkb3duLgoKSWYgeW91IHdhbnQgdG8gdGFrZSB0aGlzIGZ1cnRoZXIgeW91IG1heSB3YW50IHRvIGxvb2sgaW50byBhZGRpdGlvbmFsIHRvb2xzLiBbSGVyZSBpcyBhIGRlbW9dKGh0dHBzOi8vY2VuZ2VsLnNoaW55YXBwcy5pby9SaW9TbGF2ZU1hcmtldC8pIHVzaW5nIGBnZ3Bsb3RgLCBgbGVhZmxldGAsIGBzaGlueWAsIGFuZCBbUlN0dWRpbydzIGZsZXhkYXNoYm9hcmRdKGh0dHA6Ly9ybWFya2Rvd24ucnN0dWRpby5jb20vZmxleGRhc2hib2FyZC8pIHRlbXBsYXRlIHRvIGJyaW5nIGl0IGFsbCB0b2dldGhlci4K