1. Introduction

This is a document designed to give a basic introduction to R for those who have no coding experience. This should help you get prepared for applied projects in CPLN and MUSA 500-600 level coursework within a couple of weeks. It coalesces introductory materials I’ve developed over the past six years for MUSA 508, MUSA 795, CPLN 501, and CPLN 675 and should give you what you need to get started in any of those courses.

This isn’t a “coding” tutorial, it’s a starter kit that demonstrates how R can do some familiar Excel or ArcGIS-like tasks - loading spreadsheets and spatial data, creating new columns of data, summarizing and querying data, making charts and maps - and do them faster, better, and more reproducibly.

Use the table of contents at left to navigate through the document - the simplest topics are first (e.g. “What is R?”, “How do I download R?”), the latter sections are more advanced (Geoprocessing, Census Data, Markdown documents). There are references to books and other materials at the end of the doc.

This document will be updated on an ongoing basis to include new packages and methods - you may see a few “Coming soon” previews towards the end of the document.

Michael Fichman

Associate Professor of Practice

University of Pennsylvania Weitzman School of Design

Department of City and Regional Planning

2. Getting Started with R

What is R?

R is a programming language you can use to do stuff that you can do in Excel, or ArcGIS, or stats software like SAS… but (with some practice) faster, better, and more efficiently.

You can load data in spreadsheet form, make new columns, create data summaries, make charts, make maps. You can load spatial data and use R as a GIS. You can make statistical models. You can also do way more complicated stuff too. Most importantly, you can set up routines or workflows to let you repeat processes that in other software would take lots of clicks and steps.

It’s “open source,” which means it is written and maintained by a community of people like you and me. That also means that sometimes, things will go wrong and there isn’t always a manual to consult.

If you’ve never programmed before, that’s OK, you’re in the right place.

What is R Studio?

I run R code and do projects in R Studio, which is like a viewer for R. You can write code to load and manipulate data. You can see plots and data sets that you’ve loaded using R Studio.

Installing R and R Studio for Windows and Mac.

  1. Install R: Go to https://cran.r-project.org/bin/windows/base/
  2. Click on your operating system (Windows, MAC, Linux) and follow directions to DL the most recent version of R – which is 4.1.1 as of this writing.
  3. DL and run the .exe file to install
  4. Install R Studio: Go to http://www.rstudio.com, click on “Download RStudio” and follow the directions for your operating system.
  5. Open R studio by clicking on the icon, R will run inside the R Studio user interface.

R Studio Orientation

Now open up R Studio and let’s take a look at some of the components.

Image by Kieran Healy Your R Studio GUI (graphical user interface) should look something like this (Image by Kieran Healy)

The Console

You can type and execute code in the console.

I could type something like myName <- "Michael" into the console, and it would create a variable called myName that is equal to the character string “Michael” in my “environment.”

Go ahead, try it.

myName <- "Michael"

The Environment

This is where you can see the data sets and other things you’ve loaded into R to work on. If I load an excel spreadsheet into R using read_excel, I could say something like myData <- read_excel("filepath_on_my_computer.xlsx") it will show up in my environment as a thing called myData.

You can clear the environment by going to the Session dropdown and saying Clear Workspace.

You can also save your environment or load up an old one from a project you were working on.

The Code window

If you load up a .R or .rmd file - two types of R files, they will show up as tabs in your code window.

If I give you some code, you can execute it a piece at a time by copying-and-pasting into the console, or highlighting code and hitting ctrl+Enter to run it.

If you are writing code, you can save your code as either a .R file (just plain code), or a .rmd file (a Markdown file, like what you are reading now, which is something you can write to present code or analysis.)

The Plotting / Files / Packages etc Window

-If you make charts or plots - they show up in this window.

-The “Files” tab is a place to graphically look through file trees on your computer.

-You can consult the “Packages” or “Help” tabs to find documentation about different functions and packages in the R world. (More about packages later)

3. Data and Coding Basics

How do you “run code” in R?

Usually, I will give you a bunch of code in class, and you will have to adapt that code for a project you are doing. Eventually, you will write some of your own code - but most people cut and paste stuff and switch variable names and input data sets around. That’s how I do much of my work - I grab things from my code base I’ve generated over the years. So don’t worry about writing things from scratch.

  • Code with a # in front of it is called “commented code.” This stuff doesn’t execute it’s where you make notes.

  • Code without the # will run - either as written in the console or by hitting ctrl+enter on code the code window

# This code doesn't do anything, but the next line does
print('hello world')
## [1] "hello world"

R is case sensitive - many of your issues can be resolved by checking your syntax. You might get some kind of error because you created a data set called derp and you try to call it in the code with DERP. Happens all the time.

TUTORIAL:

If this is your first time using R, you might want to try out a .R script called running_code_in_R.R which you can find here. It has some simple code and simple operations you can try - download the .R file, load it into your R Studio and follow the instructions to create some simple objects.

Naming Variables and Data Sets

Most pieces of data you load into R or create with R will have a name. If you load in a spreadsheet, you will want to give it a name so it shows up in my R environment. You can assign information to a variable using this sign “<-”

You can have all kinds of data objects:

  • A data frame: a matrix of data with column names, basically like an excel spreadsheet (much more on this later)

  • A vector: a list of items, denoted with the letter c, like this:

myVector <- c("A", "B", "C")
  • A character: some things treated as text, denoted with “” quotes around it.
myVariable <- "derp"
  • A number: can be in the form of an integer or a double (a floating point number).
myNumber <- 10

NA Data

There are numbers, characters, and other data types. There are also NA data - missing data - these are tricky. You can’t do arithmetic on these.

Look at the example below. We create a vector of numeric data called vector1 which is just numbers, and then we ask its median. That works.

Then we create one called vector2 with some NA in there, it doesn’t work to take the median there.

vector1 <- c(1, 4, 6, 7, 10)

median(vector1)
## [1] 6
vector2 <- c(1, 4, 6, NA, 10)

median(vector2)
## [1] NA

4. Installing and Loading Packages

A lot of the things you will want to do with R will require loading packages. “Base R” is basic R - there are a bunch of functions it does. Since R is open source, lots of people have developed their own cool packages that do additional stuff.

A package is a set of functions - some of them make graphics, some handle spatial data. You will almost always start your session by loading some packages.

The first time you use a package on your computer, you can load it with the command install.packages, like this:

install.packages('tidyverse')

If you have already installed a package on your computer, you can load it into your environment using the library command.

library(tidyverse)

Packages all come with documentation - lists of functions in these packages have little vignettes that tell you how to use them. Check that out in the “Packages” tab in your R Studio environment to see these, or just google package names or function names to find the documentation.

You can also type things like ??select in your console and documentation about the function select will pop up in your “Help” window in R Studio.

The Tidyverse

The tidyverse, which we loaded in the previous code chunk, is a group of important packages which are used to wrangle data, make graphics, load data, handle dates, handle text. Better yet, these projects are coordinated by their developers, so they all work nicely together.

For work in our class, you should load up the tidyverse on every project. Most of my code doesn’t work without it.

5. Loading and Viewing Data

Most projects will start with you loading some spreadsheet-like data - from a survey you conducted, from an excel sheet, from the Census’ website, from a shapefile that I gave you for class. For non-spatial data, these spreadsheet-like data will be in a format called a “data frame” (or as a variant called a “tibble).

There are a lot of file types you can load, and some of them have their own functions, like you can read in Excel data with the read_excel function in the readxl package. Later on I will show you how to read in a .shp. (You can read more about these data import functionalities here.)

There are several ways to load data into your R environment.

Loading Data From Your Computer

You can load data from your computer in one of two ways, either by going to File / Import Data Set in a dropdown menu R Studio, and clicking on the relevant options (note that “From txt” will let you load a csv, txt, tsv or other similar file types), or by doing it with code, like this:

myData <- read.csv("filepath/onMyComputer/fileName.csv")

Loading Data from the Web

You can load data from the web, like from Google Drive or Github, using the same kind of syntax. Here we load a csv from the web:

myData <- read.csv("https://raw.githubusercontent.com/mafichman/R_FAQ_For_Planners/main/data/someCensusData.csv")

Viewing Data

Now that you’ve loaded in some data, you can examine them in several ways. You can View(myData) and see a visual representation of the data in an excel-like way, or you can glimpse your data frame.

glimpse will tell you what data types the columns are (character, numeric, etc), let you see some samples of the data, and let you see how many rows and columns the data contain.

glimpse(myData)
## Rows: 384
## Columns: 23
## $ X                      <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, …
## $ GEOID                  <dbl> 4.2101e+10, 4.2101e+10, 4.2101e+10, 4.2101e+10,…
## $ NAME.x                 <chr> "Census Tract 1, Philadelphia County, Pennsylva…
## $ total_pop.2010         <int> 2883, 1886, 2915, 1525, 3156, 948, 938, 2538, 1…
## $ total_HU.2010          <int> 2455, 1128, 2038, 1531, 2534, 750, 713, 2284, 1…
## $ total_vacant.2010      <int> 422, 123, 206, 362, 239, 114, 60, 341, 67, 433,…
## $ med_HH_Income.2010     <int> 73041, 43218, 65577, 21832, 50020, 45357, 53938…
## $ total_White.2010       <int> 2660, 358, 2968, 876, 2762, 849, 863, 1981, 118…
## $ total_GradDeg.2010     <int> 918, 254, 1107, 323, 1048, 321, 426, 699, 496, …
## $ vacancyPct.2010        <dbl> 0.17189409, 0.10904255, 0.10107949, 0.23644677,…
## $ pctWhite.2010          <dbl> 0.9226500, 0.1898197, 1.0181818, 0.5744262, 0.8…
## $ NAME.y                 <chr> "Census Tract 1, Philadelphia County, Pennsylva…
## $ total_pop.2016         <int> 3983, 2357, 3315, 2633, 3202, 1305, 1021, 3238,…
## $ total_HU.2016          <int> 2624, 1079, 1925, 1969, 2880, 942, 757, 2729, 1…
## $ total_vacant.2016      <int> 319, 129, 160, 299, 500, 68, 81, 452, 109, 363,…
## $ med_HH_Income.2016     <int> 103772, 50455, 93036, 57604, 70038, 40568, 7125…
## $ total_White.2016       <int> 3020, 825, 2740, 1605, 2721, 1126, 991, 2138, 1…
## $ total_GradDeg.2016     <int> 1094, 455, 1075, 847, 1147, 409, 548, 1333, 743…
## $ vacancyPct.2016        <dbl> 0.12157012, 0.11955514, 0.08311688, 0.15185373,…
## $ pctWhite.2016          <dbl> 0.7582224, 0.3500212, 0.8265460, 0.6095708, 0.8…
## $ change_med_HH_Income   <dbl> 23375.771, 2884.947, 20855.396, 33573.518, 1498…
## $ change_Grad_Degree_Pct <dbl> -0.043750978, 0.058365438, -0.055476303, 0.1098…
## $ pointBreeze            <chr> "REST OF PHILADELPHIA", "REST OF PHILADELPHIA",…

Writing Data to File

After you manipulate data, you might want to write it out to file. Everything we are doing in the R-Studio environment does not affect the underlying data - so any project you do might need a step where you out put data.

You can do this with commands like write.csv - so you could data your data frame myData and write it out as a file with a name and location of your choosing like so:

write.csv(myData, "your_file_path/your_file_name.csv")

We talk a bit later about reading/writing spatial data as well.

6. Manipulating Data with dplyr

dplyr is the core data wrangling package in “tidy” R. You can use functions from dplyr to manipulate data frames.

You can make new columns, and remove or rename columns from your data frame. You can summarize data (e.g. what is the sum of all the rows in a given column). You can filter data to keep only rows that meet some criteria.

It does lots of other stuff too.

There is a handy guide for all the dplyr data tools at R Studio’s dplyr cheat sheet.

Using the “Pipe”

Very important, to what I’m about to show you from here on out is a thingy called a “pipe” - %>% - which is an object you put in your code to chain functions together and make kind of data-wrangling recipes.

To understand what the pipe does, think about the following “recipe” like this: I make an omlette by taking a data frame called eggs and applying the functions scramble and fry

omlette <- eggs %>% scramble() %>% fry()

OK, keep that in mind while we look at dplyr commands.

Dplyr commands

Let’s use a few basic dplyr commands to wrangle our dataframe myData.

Mutate

mutate makes a new column. I am going to overwrite myData - making a new version that has a new column in it. Each row of the data frame represents a census tract, and I’d like to see what the difference is in tract population between 2010 and 2016. I’ll call this new column pop_change and set its value equal to total_pop.2016 minus total_pop.2010. Note the pipe used to “chain” the mutate statement in there.

myData <- myData %>%
  mutate(pop_change = total_pop.2016 - total_pop.2010)

Did it work? Try typing View(myData) or glimpse(myData) into your console.

I’m now going to create a second variable called pop_change_positive using mutate. If the population change was positive, I’m going to call set this variable equal to TRUE - I’ll do an ifelse statement to make it’s value contingent on the value of pop_change.

myData <- myData %>%
  mutate(pop_change_positive = ifelse(pop_change > 0, TRUE, FALSE))

Filter

filter lets you reduce your data frame based on some criteria. I’d like to create a new data frame called population_loss that consists only of census tracts in myData that lost population (e.g. pop_change_positive == FALSE).

population_loss <- myData %>%
  filter(pop_change_positive == FALSE)

I can do this kind of filtering based on lots of criteria.

Select

select keeps only certain columns that you want to keep.

I’m going to make a new data frame, called twoVariables that consists of only the columns NAME.y and GEOID from the data frame myData

twoVariables <- myData %>%
  select(NAME.y, GEOID)

Rename

rename changes the names of your columns. I’d like to rename the column from the data frame twoVariables called NAME.y to be called just NAME

twoVariables <- twoVariables %>%
  rename(NAME = NAME.y)

Summarizing Data

You’ll often want to know about the characteristics or central tendencies of your data. Using the group_by and summarize commands you can do lots of this.

Here are some examples - notice I’m not creating new data frames here.

First, let’s just find out what the median pop_change was between 2010 and 2016 for Philadelphia census tracts.

myData %>%
  summarize(median_pop_change = median(pop_change))
##   median_pop_change
## 1               133

What if we want to know how one type of tract versus another varied in terms of pop_change? We can group our data into categories using group_by and then summarize.

For tracts for which pop_change_positive (remember we created this column as a TRUE/FALSE - was there population change between 2010 and 2016?) what is the median pop_change?

myData %>%
  group_by(pop_change_positive) %>%
  summarize(median_pop_change = median(pop_change))
## # A tibble: 2 × 2
##   pop_change_positive median_pop_change
##   <lgl>                           <dbl>
## 1 FALSE                            -332
## 2 TRUE                              352

Joining Data

You can do “tabular joins” between data sets when they have a “unique ID” - a number or code that identifies each observation or subject in your data. For example, the “GEOID” is the unique code associated with each census tract.

There are several types of joins you can use in R. Hadley Wickham’s “R For Data Science” has a great chapter and series of code examples on the mechanics of left, right, inner, outer, full, and other joins.

The basic mechanics of a join go like this:

new_dataframe <- left_join(dataframe1, dataframe2, by = c("unique_ID"))

This will get you a data set where you preserve all of dataframe1 (the “left” side of the left join), and tack on anything from dataframe2 that matches unique_ID found in dataframe1

Notice the by call where you specify the unique ID, that’s a weird bit of syntax. You can join on multiple Unique IDs if you want.

Wide Data and Long Data

One concept that is important but a bit hard to engage is data format. The dataframe myData that we have been working with so far is what’s called a “wide” data set. Each row is a census tract, each column is a variable. This is great for doing column math with mutate and it’s good for making certain kinds of plots - for instance a scatter plot with two continuous variables (more on this in the next section).

Take a look at myData using the command View(myData) and you’ll see that it is indeed “wide” format.

There is another form of data called “long” data that are useful for other circumstances, like making comparisons between continuous variables across categories. (Note: Often you find time-series data in long format).

Let’s examine indicators from 2010 of tracts that appreciated in population between 2010 and 2016:

  • Median household income
  • Median number of people identifying their racial background as “white alone”
  • Median number of housing units

We gather those variables, and make a data frame where each row is a tract, it’s pop_change_positve value (TRUE/FALSE), a variable name, and a value for that variable.

Then we can group_by the population change direction (TRUE - positive) and the variable, and summarize the median_value for each variable.

myData %>% 
  select(pop_change_positive, med_HH_Income.2010,  total_White.2010, total_HU.2010) %>% 
  gather(-pop_change_positive, key = "variable", value = "value") %>% 
  group_by(pop_change_positive, variable) %>%
  summarize(median_value = median(value, na.rm = TRUE))
## # A tibble: 6 × 3
## # Groups:   pop_change_positive [2]
##   pop_change_positive variable           median_value
##   <lgl>               <chr>                     <dbl>
## 1 FALSE               med_HH_Income.2010       31264 
## 2 FALSE               total_HU.2010             1859 
## 3 FALSE               total_White.2010           932.
## 4 TRUE                med_HH_Income.2010       37174 
## 5 TRUE                total_HU.2010             1673 
## 6 TRUE                total_White.2010          1326.

Note there is a more modern version of gather known as pivot_longer, where the syntax would look like this:

myData %>% 
  select(pop_change_positive, med_HH_Income.2010,  total_White.2010, total_HU.2010) %>% 
  pivot_longer(cols = -pop_change_positive) %>%
  group_by(pop_change_positive, name) %>%
  summarize(median_value = median(value, na.rm = TRUE)) 

wide and long data are a weird concept - this graphic is pretty helpful understanding how data can transform from one form to another.

Can you think of a few situations in which one form or another is appropriate?

Pivoting between wide and long data Image: https://www.garrickadenbuie.com/

The wide/long data concept is a bit nebulous, but it really comes in handy when you are making facetted graphics - a subject covered in the next section.

7. Making Plots with ggplot2

ggplot2 is the main graphics library in the tidyverse - it’s fantastic. You can make bar plots, line plots, and much more with it.

You make ggplots by writing “recipes” for charts. The + operator is a bit like what the %>% is in dplyr/tidyverse - you use it to add elements together.

The core elements of ggplot recipes are called geometries (a geom), and the way you style the data is with aesthetics (aes). You can add some other styles to your ggplots, but if it involves changing something about the appearance of the plot based on data, you use an aes call.

There are lots of resources for ggplot - the simplest and easiest one to check out is R Studio’s ggplot cheat sheet. There is also a whole (free) book on data viz in ggplot - Data Visualization by Kieran Healy. It’s fantastic.

Making a basic plot

Let’s make a simple scatterplot.

Think about the “recipe” for the following chart where we see plot Philadelphia census tract median household income as a function of the tract’s pencentage of vacant housing units using this narrative (remember each row in myData is a Philadelphia census tract):

  • Make me a ggplot
  • Add a point geometry from the data set myData where the x aesthetic is vacancyPct.2016 and the y aesthetic is med_HH_Income.2016
ggplot()+
  geom_point(data = myData, 
             aes(x = vacancyPct.2016, y = med_HH_Income.2016))

Adding more aesthetics

We can add more aesthetics to this plot. Let’s do this first by just tinkering with the colors to make it look cool - we will do this with some commands outside the aesthetics to make the points blue.

ggplot()+
  geom_point(data = myData, 
             aes(x = vacancyPct.2016, y = med_HH_Income.2016),
             color = "blue")

OK, now let’s make the point coloration change according to a data-related element, something we have to specify in the aesthetics. We specify the color as being equal to pop_change_positive:

ggplot()+
  geom_point(data = myData, 
             aes(x = vacancyPct.2016, y = med_HH_Income.2016, color = pop_change_positive))

Facets

You can very simply make “small multiple” plots where you subdivide your data into categories for graphic comparison. This is done with the facet_wrap command.

Here’s an alternate take on our previous plot, cut into two plots. Notice in the code I pulled pop_change_positive out of the aesthetics from the previous plot and put it in the facet_wrap command.

ggplot()+
  geom_point(data = myData, 
             aes(x = vacancyPct.2016, y = med_HH_Income.2016))+
  facet_wrap(~pop_change_positive)

Remember how we used gather and pivot_longer to make our wide data into long data in the last section? That’s an incredibly useful technique for making quick data-mining visualizations across a number of variables.

Notice how below, I do the same data manipulation I did in the previous section, and then I just pipe in the ggplot call, and I use an argument in the facet_wrap called scales so that each chart has a y-axis with an appropriate scale to that variable.

myData %>% 
  select(pop_change_positive, med_HH_Income.2010,  total_White.2010, total_HU.2010) %>% 
  pivot_longer(cols = -pop_change_positive) %>%
  group_by(pop_change_positive, name) %>%
  summarize(median_value = median(value, na.rm = TRUE)) %>%
ggplot()+
  geom_bar(aes(x = pop_change_positive, y = median_value), stat = "identity")+
  facet_wrap(~name, scales = "free")

Advanced graphics

You can alter most elements of the ggplot. We could take our initial scatterplot and do some things to spruce it up, like add a title, change the axis labels etc.,

ggplot()+
  geom_point(data = myData, 
             aes(x = vacancyPct.2016, y = med_HH_Income.2016))+
  labs(title="Philadelphia Tract Med. HH Income as a Function of Pct. Vacancy",
       subtitle = "Data: US Census Bureau, 2016 ACS 5-Year Estimates",
        x ="Pct Vacant Housing Units (0-1)", y = "Median Household Income (2016 $)")

I like to make some pre-made recipes where I style the lines, fonts and other elements, and then I can just add it to my plots. Here’s a recipe I call plotTheme. Load it up into your environment and then you can add it to your plots to make them nicer. The sky is the limit.

plotTheme <- theme(
  plot.title =element_text(size=12),
  plot.subtitle = element_text(size=8),
  plot.caption = element_text(size = 6),
  axis.text.x = element_text(size = 10, angle = 45, hjust = 1),
  axis.text.y = element_text(size = 10),
  axis.title.y = element_text(size = 10),
  # Set the entire chart region to blank
  panel.background=element_blank(),
  plot.background=element_blank(),
  #panel.border=element_rect(colour="#F0F0F0"),
  # Format the grid
  panel.grid.major=element_line(colour="#D0D0D0",size=.75),
  axis.ticks=element_blank())
## Warning: The `size` argument of `element_line()` is deprecated as of ggplot2 3.4.0.
## ℹ Please use the `linewidth` argument instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

Now I can add plotTheme to my recipe from the last plot for some extra styling. Notice I just tack it onto the recipe below with a +:

ggplot()+
  geom_point(data = myData, 
             aes(x = vacancyPct.2016, y = med_HH_Income.2016))+
  labs(title="Philadelphia Tract Med. HH Income as a Function of Pct. Vacancy",
       subtitle = "Data: US Census Bureau, 2016 ACS 5-Year Estimates",
        x ="Pct Vacant Housing Units (0-1)", y = "Median Household Income (2016 $)")+
  plotTheme

8. Census Data with tidycensus

You can access the US Census using the tidycensus package - this is much faster than grabbing Census data on the web. You can grab the variables you want with or without spatial data attached.

Even if you only use R for this - you’re making your life a bit easier. You can grab data with tidycensus, write it to file (more on this in the next section) and then use it in Excel or ArcGIS.

Here’s how it works - you “tell” the census, through some R Code, that you want certain variables, for certain years, and certain geographies, and it returns it to you in your R Environment.

There is a whole book on tidycensus from it’s creator/maintainer (cough cough MUSA Masterclass guest speaker 2021) Kyle Walker.

Load the tidycensus and sf packages

To use tidycensus you should install and load the tidycensus package. We are going to grab some spatial data as well, so load the sf package.

If you don’t have these packages installed, you should install them as follows (if you do, skip this step):

install.packages('tidycensus')
install.packages('sf')

OK, if you have the packages installed on your computer, now load them into your R environment with a library call:

library(tidycensus)
library(sf)

You will need a “key” to access the Census API. You can find one at their website.

Paste it into the code block below and run it to load your key in to your R environment:

census_api_key("YOUR KEY GOES HERE", overwrite = TRUE)

Load census data dictionaries

Now that we have our census credentials loaded, we can start downloading information from the API using some functions from tidycensus. We are going to grab some 2016 ACS estimates for Philadelphia census tracts. In order to choose variables of interest, we are going to load the 2016 ACS data dictionary using the tidycensus function load_variables. We turn it into a dataframe called acs_variable_list.2016.

acs_variable_list.2016 <- load_variables(2016, #year
                                         "acs5", #five year ACS estimates
                                         cache = TRUE)

Once we have loaded these data, we can observe and search through the data frames of variable information which should appear in our global environment either by clicking on them or using the View(acs_variable_list.2016) command.

Look around in th data frame for a few minutes and see what’s in there.

Downloading Data from Tidycensus

Create a vector of census variables

We can populate a vector of variable names we will send to the Census API. We call this list acs_vars. This is the beauty of a code-based workflow - you can take this vector and put anything you want in it when you have a new analysis to do and re-run it for different variables. These need to be character strings, and hence, in quotes as you see below.

Keep in mind the categories and code numbers change a bit over time - you may need separate vectors for different census years.

acs_vars <- c("B01001_001E", # ACS total Pop estimate
              "B25002_001E", # Estimate of total housing units
              "B25002_003E", # Number of vacant housing units
              "B19013_001E", # Median HH Income ($)
              "B02001_002E", # People describing themselves as "white alone"
              "B06009_006E") # Total graduate or professional degree

Call the Census API

We use the get_acs function in tidycensus to query the API and get tract level-data for all of Philadelphia from the 2016 American Community Survey’s (ACS) 5-year estimates. Notice the different arguments for the function, and that they require certain types of info. For example, geography requires one of a finite list of answers, and they have to be formatted as character string.

Remember the ?? function - you can learn about the parameters for get_acs this way. There is also a function called get_decennial which you can use for decennial census counts.

We ask for data on our acs_vars for all tracts in Philadelphia County, PA in 2016. We ask for “wide” data (e.g. one variable per column, one row per tract) and we set geometry to FALSE. (Later, we will set geometry to true and get some spatial data.)

acsTractsPHL.2016 <- get_acs(geography = "tract",
                             year = 2016, 
                             variables = acs_vars, 
                             geometry = FALSE, 
                             state = "PA", 
                             county = "Philadelphia", 
                             output = "wide") 

Voila - now you have census data. Take a look and see what you have there - since it’s the ACS, they are estimates - so you’ll have data marked “E” for estimate, or “M” for margin of error.

Let’s use a glimpse command to check it out

glimpse(acsTractsPHL.2016)
## Rows: 384
## Columns: 14
## $ GEOID       <chr> "42101008301", "42101008302", "42101006300", "42101007300"…
## $ NAME        <chr> "Census Tract 83.01, Philadelphia County, Pennsylvania", "…
## $ B01001_001E <dbl> 4809, 4334, 4264, 2529, 4443, 7415, 4311, 5856, 4738, 2960…
## $ B01001_001M <dbl> 606, 516, 637, 382, 570, 794, 591, 716, 536, 424, 449, 324…
## $ B25002_001E <dbl> 1988, 2221, 1697, 1361, 2262, 3177, 2069, 2395, 2509, 1342…
## $ B25002_001M <dbl> 56, 52, 60, 80, 72, 122, 73, 58, 55, 106, 81, 47, 63, 80, …
## $ B25002_003E <dbl> 371, 527, 374, 203, 262, 590, 459, 109, 407, 218, 369, 105…
## $ B25002_003M <dbl> 119, 118, 100, 121, 134, 175, 108, 86, 125, 99, 114, 45, 9…
## $ B19013_001E <dbl> 29770, 31202, 25871, 25369, 56944, 28201, 24402, 37390, 30…
## $ B19013_001M <dbl> 6061, 4228, 7342, 8100, 12681, 8161, 2987, 4350, 6472, 267…
## $ B02001_002E <dbl> 113, 89, 397, 124, 2649, 463, 83, 70, 47, 466, 1087, 551, …
## $ B02001_002M <dbl> 80, 119, 243, 71, 387, 224, 81, 50, 38, 170, 347, 174, 465…
## $ B06009_006E <dbl> 80, 92, 29, 55, 1033, 166, 50, 265, 175, 74, 101, 65, 10, …
## $ B06009_006M <dbl> 69, 53, 43, 48, 199, 74, 65, 130, 99, 49, 93, 38, 14, 14, …

Clean Census Data

The variable names that come with the stock Census data aren’t that helpful.

You can use the rename function from dplyr to make the columns in your data intelligible.

acsTractsPHL.2016 <- acsTractsPHL.2016 %>%
  rename (total_pop.2016 = B01001_001E,
          total_HU.2016 = B25002_001E,
          total_vacant.2016 = B25002_003E,
          med_HH_Income.2016 = B19013_001E,
          total_White.2016 = B02001_002E,
          total_GradDeg.2016 = B06009_006E)

9. Vector GIS With sf

We can use R as a GIS and easily manipulate vector data (points, lines and polygons). The main package we use for this is called sf. Here are some things that it does:

  • Reads .shps, geojson and other spatial data files.

  • Allows you to manipulate spatial data using the same tools we use to manipulate data frames - dplyr tools like mutate, rename etc.,

  • Lets you do geoprocessing like you do in ArcGIS - spatial joins, area and distance calculations, union and dissolve functions, and more.

  • Makes maps by interfacing with ggplot

Load in some spatial data

Let’s load in some spatial data in two ways.

First, let’s just load in some simple data using st_read, that’s the sf package’s basic read function. You can read stuff from the web or from a filepath on your computer this way.

These are some data I grabbed from Open Street Map - Philadelphia area restaurants. You might recognize some of these. If you want to know more about grabbing OSM data, check my repo with sample code.

These data are points (vector data can be points, lines or polygons).

restaurants <- st_read("https://raw.githubusercontent.com/mafichman/R_FAQ_For_Planners/main/data/restaurants.geojson")

We can also get census data from tidycensus in spatial form.

You might remember the code block below from Section 8 of this demo - where we grabbed a table of census data. We can grab the same data but in spatial form by changing the geometry argument to TRUE.

These are polygon data - each row is a census tract, each column is a variable associated with each tract.

acsTractsPHL.2016.sf <- get_acs(geography = "tract",
                             year = 2016, 
                             variables = acs_vars, 
                             geometry = TRUE, 
                             state = "PA", 
                             county = "Philadelphia", 
                             output = "wide") %>%
  rename (total_pop.2016 = B01001_001E,
          total_HU.2016 = B25002_001E,
          total_vacant.2016 = B25002_003E,
          med_HH_Income.2016 = B19013_001E,
          total_White.2016 = B02001_002E,
          total_GradDeg.2016 = B06009_006E) 

Understanding sf Objects - Geometry and Projections

Let’s examine an sf object, restaurants. Call the object in your console and look at some of the characteristics.

It is worth noting here that the 10th row in the data set, “Mi Puebla”, is a fantastic Mexican spot in Mt Airy. It makes me hungry just seeing that data point.

restaurants
## Simple feature collection with 827 features and 7 fields
## Geometry type: POINT
## Dimension:     XY
## Bounding box:  xmin: -75.29785 ymin: 39.87397 xmax: -74.92357 ymax: 40.1477
## Geodetic CRS:  WGS 84
## First 10 features:
##                                name    osm_id    addr.street addr.postcode
## 1                        Friendly's 332503202           <NA>          <NA>
## 2               Sam's Morning Glory 333786044           <NA>          <NA>
## 3         Rexy's Bar and Restaurant 363599966           <NA>          <NA>
## 4                        Applebee's 363600675           <NA>          <NA>
## 5                     Groove Ground 431179432           <NA>          <NA>
## 6  Spring Garden Pizza & Restaurant 566683522           <NA>          <NA>
## 7                   Carollo's Pizza 583287916           <NA>          <NA>
## 8             Earth Bread & Brewery 596245658 Germantown Ave          <NA>
## 9    Cresheim Valley Grain Exchange 596245661 Germantown Ave          <NA>
## 10    Mi Puebla RESTAURANT & Bakery 596261287 Germantown Ave          <NA>
##    addr.city addr.state  cuisine                   geometry
## 1       <NA>       <NA> american POINT (-74.95853 39.94496)
## 2       <NA>       <NA>     <NA> POINT (-75.15893 39.94086)
## 3       <NA>       <NA>     <NA> POINT (-75.09105 39.89246)
## 4       <NA>         NJ american POINT (-75.08793 39.89296)
## 5       <NA>       <NA>     <NA> POINT (-75.07044 39.91681)
## 6       <NA>       <NA>    pizza POINT (-75.15755 39.96205)
## 7       <NA>       <NA>     <NA>  POINT (-75.0522 39.96179)
## 8       <NA>       <NA>     <NA>  POINT (-75.19035 40.0592)
## 9       <NA>       <NA> american POINT (-75.19071 40.05961)
## 10      <NA>       <NA>     <NA> POINT (-75.19061 40.05987)

This looks a lot like a data frame, and it is! You can easily manipulate sf objects just like data frames with dplyr like this:

sf_object <- sf_object %>% mutate(column_a = column_b + column_c)

But there are a couple major differences between sf objects and data frames.

First, there is the geometry column - this is a set of drawing and geoprocessing instructions. You can’t do a mutate or other kind of data manipulation on this column. restaurants is a point file, so the geometry is an ordered series of points.

Second, there is a crs or “coordinate reference system” associated with the file. If you’ve used GIS you’ve heard of “projection” - that’s where it’s stored in the sf object.

The crs here is 4326, which is also known as “Web Mercator” or”WGS 84” - this is the common latitude/longitude coordinate system you might be familiar with. You can change the crs when you are doing geoprocessing (more on that in a moment).

The st_transform command is your friend, that’s how you do reprojections - look it up - ??st_transform

The universe of coordinate systems can be found on a great website called spatialreference.org.

Making Maps With ggplot

ggplot supports a geometry called geom_sf that allows you to plot sf objects. As with making graphs and plots, you have lots of styling options with ggplot.

Here is a simple one, using the census data we grabbed:

ggplot()+
  geom_sf(data = acsTractsPHL.2016.sf)

You can can make very complex maps in ggplot. You can add many geom_sf drawing layers, and you can use ggplot’s aes features to make cool aesthetics for your maps. Here’s something a bit more complicated:

  • I use a fill parameter in my aes call to symbologize total_GradDeg.2016 in the census data.

  • I set the linework to be transparent (this is outside the aes call because it doesn’t pertain to any data, just looks.

  • I add my Philly area restaurants on top, and give them a color and a size parameter.

ggplot()+
  geom_sf(data = acsTractsPHL.2016.sf, 
          aes(fill = total_GradDeg.2016),
          color = "transparent")+
  geom_sf(data = restaurants, color = "yellow", size = 0.5)

Vector Geoprocessing

The sf package has lots of commands that do basic geoprocessing. I can do spatial joins, I can calculate distances and areas, I can do spatial processes like buffer, union, and dissolve.

Mind your projections when you do these things. If you try to calculate distance if your crs is in decimal degrees, you’ll get outputs in decimal degrees (probably not helpful).

Spatial Join

You can join spatial objects to one another in sf just like in any other GIS. Let’s join restaurants to acsTracts.PHL.2016.sf. After we do that join, we can summarize how many restaurants are in each tract.

Before we get started, we have to make sure our data are projected in the same coordinate system. Let’s use the st_crs command to check on that. Turns out restaurants is in WGS 84 (crs = 4326), and acsTractsPHL.2016.sf is in NAD 83 (crs = 4249)

st_crs(restaurants) == st_crs(acsTractsPHL.2016.sf)
## [1] FALSE

The function st_join does the work for us here to join points to the polygons that contain them, and it has lots of options. Here, we say the type of join is intersects as in “does this point from the left hand data set intersect the polygon from the right hand data set?” We also say left=TRUE - indicating we want a “left join” - preserving all items from the left hand data set, whether or not they join to the items on the right. The things that don’t join (eg. outside of Philly), they don’t show up.

Notice, that I pipe the st_transform command to restaurants right inside the join statement and re-project it so this operation works!

restaurants.and.tracts <- st_join(restaurants %>%
                                    st_transform(crs = st_crs(acsTractsPHL.2016.sf)), acsTractsPHL.2016.sf,
                                  join = st_intersects,
                                  left = TRUE)

This operation produces a data set where the rows are restaurants, and there is a column denoting the tract it falls in.

Now we can convert to a data frame (as.data.frame) summarize the number of observations by census tract (group_by, summarize), join these to our tract sf file by GEOID (left_join).

restaurant_summary <- 
  restaurants.and.tracts %>%
  as.data.frame() %>%
  group_by(GEOID) %>%
  summarize(restaurant_sum = n())

tracts_and_restaurants <-
  left_join(acsTractsPHL.2016.sf, restaurant_summary,
            by = c("GEOID"))

ggplot()+
  geom_sf(data = tracts_and_restaurants, 
          aes(fill = restaurant_sum),
          color = "transparent")

Other Spatial Operations

The sf package can do most of what you do with vectors in ArcGIS, including…

  • Geometric functions like st_union, st_buffer, st_dissolve and so on.

  • You can find a polygon centroid with st_centroid.

  • Measurement functions: Calculate a polygon’s area or perimeter (in the native units of its projection) with st_area. Find the distance between two sf objects with st_distance.

There are some spatial functions that you need to find other packages for, like K-nearest-neighbors functions.

Check out the sf “cheat sheet” to see what it can do:

R Studio: sf cheatsheet

Making a “Fishnet”

A “fishnet” is a raster-like grid where you impose a uniform surface of aerial units - squares or hexagons - across a landscape. This can be really useful for visualizing or processing information.

I can make a “fishnet” grid across Philadelphia County, and join the restaurant point data in order to visualize the density of in a different way.

Let’s start by loading a geojson representing Philadelphia County, and coercing it to the sf format using st_as_sf.

Not sure what the projection is? It’s important to know - the cellsize you set for your fishnet will be in the native units of the projection. You can find that out using st_crs to “ask” your data about the projection. We see that this is in something called ["EPSG",4269] - there is projection in “degree” and “latitude” - those aren’t linear units like feet or meters!

philaCounty <- read_sf("https://raw.githubusercontent.com/mafichman/R_FAQ_For_Planners/main/data/phila_county.geojson") %>%
  st_as_sf()

st_crs(philaCounty)
## Coordinate Reference System:
##   User input: NAD83 
##   wkt:
## GEOGCRS["NAD83",
##     DATUM["North American Datum 1983",
##         ELLIPSOID["GRS 1980",6378137,298.257222101,
##             LENGTHUNIT["metre",1]]],
##     PRIMEM["Greenwich",0,
##         ANGLEUNIT["degree",0.0174532925199433]],
##     CS[ellipsoidal,2],
##         AXIS["geodetic latitude (Lat)",north,
##             ORDER[1],
##             ANGLEUNIT["degree",0.0174532925199433]],
##         AXIS["geodetic longitude (Lon)",east,
##             ORDER[2],
##             ANGLEUNIT["degree",0.0174532925199433]],
##     ID["EPSG",4269]]

Before we make our fishnet, let’s project it to a coordinate reference system 2272, which is in feet. How did I choose that Need to find the right coordinate system for your study area? Visit spatialreference.org or use the crs_suggest package (more on this in the end references to this document if you’re interested.)

philaCounty <- philaCounty %>%
  st_transform(2272)

Finally, let’s make our fishnet. We use the st_make_grid function to create the fishnet - and we specify that we want the cellsize to equal 2500 - that’s in the native units of the projection, feet. We ask for square cells. Check out this data pipeline - we use an operation to keep only the fishnet cells that intersect the original philaCounty shape, and then we create a uniqueID using a mutate command - giving each shape an identifier that is useful for doing analysis later.

fishnet <- 
  st_make_grid(philaCounty,
               cellsize = 2500, 
               square = TRUE) %>%
  .[philaCounty] %>%            # clips the grid to the philaCounty extent
  st_sf() %>%
  mutate(uniqueID = rownames(.))

Check it out in ggplot!

ggplot()+
  geom_sf(data = fishnet)

Now we can join our restaurants to the fishnet using the same routine as we used for our tract-based summary, but with a different result because of the different aerial units.

restaurants.and.fishnet <- st_join(restaurants %>%
                                    st_transform(crs = st_crs(fishnet)), fishnet,
                                  join = st_intersects,
                                  left = TRUE)


restaurant_fishet_summary <- 
  restaurants.and.fishnet %>%
  as.data.frame() %>%
  group_by(uniqueID) %>%
  summarize(restaurant_sum = n())

fishnet_and_restaurants <-
  left_join(fishnet, restaurant_fishet_summary,
            by = c("uniqueID"))

ggplot()+
  geom_sf(data = fishnet_and_restaurants, 
          aes(fill = restaurant_sum),
          color = "transparent")

Writing spatial data

You can write out any sf objects as .shp files which are readable in ArcGIS. This is especially useful if you want to just use R to grab data efficiently and want to map it elsewhere. I prefer to write geojsons - they are more widely usable and the column names are more stable.

Use the function “st_write” like so:

st_write(my_sf_object, "my_filepath/my_filename.shp")

10. Publishing Analyses with R Markdown

I will probably be asking you to turn in assignments using R Markdown. Markdown is a way to present your code-based projects as a more polished document (you’re reading a markdown right now!). You can use this tool to make repeatable reporting workflows for non-techincal audience using R. I often write data reports like this one for my clients, where I “hide” all the code and just show the outputs.

A markdown document embeds “chunks” of R code in between bits of text. These chunks can simply be bits of code, or they can be code that creates some kind of image like a ggplot chart or a kable table. You can manipulate the options of a code chunk so that the output appears a certain way.

The basic file format of a markdown is .rmd. You can “knit” the markdown into a polished document in HTML, pdf or doc format.

If you want to experiment with some markdown language - you can find a download button at the top right of this document and download the code that was used to make this.

For more details on using R Markdown, consult http://rmarkdown.rstudio.com or check out this great book from Xie et al.

11. Project Workflows - Github and R Projects

As you work on increasingly sophisticated or collaborative projects using R, you will want to figure out a way to manage your files and code, and to figure out ways to keep your code working as you improve and develop it.

Best practices in coding is a subject for a whole entire course, but here are a few simple tips to get you started.

R Projects

R has a “projects” file-type which can help you keep an organized file structure and working environment in R Studio. There is a file type called .rProj that coordinates this. You can open a new project by going to File -> New Project in R Studio. Check out the “Files” part of your R Studio window and see how you can now graphically view your files. Best of all, if you keep things contained in a project, it’s more portable from computer to computer because you’re just calling files inside one Project.

Read Hadley Wickham’s tutorial on how working with R Projects can help you with organization.

Github

Github is a code-collaboration environment that allows you to use “source control” and work on things with others without accidentally over-writing or breaking other peoples’ code. Github works well with R Projects. You can “push” and “pull” code and data straight from R Studio or use a program called Github Desktop (which I prefer). You will need an account on Github to make this all work.

Here is a quick step-by-step guide to setting up a collaborative repo, an R project, and sending some code back and forth, using Github Desktop and R Studio:

  • Create a new repo on your github page

  • Add your github collaborator by going to settings/collaborators

  • Go to your Github Desktop app, and “clone” the repo - this will populate a version on your computer in it’s own folder.

  • Go into R Studio, and start a new “project” - put it in the repo folder. Do some work - create a new R script, add some data.

  • When you are done - go to Github Desktop, write some annotations about the changes you made to the code, and “push” the changes to the web repo.

  • Your collaborator can clone the project, and then push and pull things themselves. This will help you manage changes and versions. If you keep all your code and data inside the Github folder, the file paths will all be the same and it will be easy to collaborate. You can work separately without breaking each other’s code - use “branches” to make changes on your own version of the code, and then push it to the main code base later.

The finer mechanics of collaborating with Github are too complex for this document, but check out Matt Harris’ tutorial from MUSA 508 on using Github for detailed instructions on how to get set up and start collaborating.

Github Pages

It’s a bit weird to send HTML markdowns to clients and n00bs and say “you need to DL this and open it in your browser to see it”, but you can easily publish a knit html markdown to the web using github. If you name the markdown (in HTML form) index, you can go to your repo settings, and activate the “pages” functionality. This will publish the html file as a github.io webpage.

This page itself is an example - (check out the repo behind it)[https://github.com/mafichman/R_FAQ_For_Planners] to see how this all works!

Writing Readable Code

You will probably find yourself writing code that other people have to use - or that you will save and use at some time in the future. It’s important to make sure you format and annotate your code so that your colleagues or your future self can use it, understand it, and troubleshoot it.

Here are some simple tips to keep your code nice and neat:

  • Name your files and variables things that are intelligible. Name a dataframe census_2011 instead of nonsense like new_dataframe. Don’t call your functions or your data objects names that might be used elsewhere in your R environment - for example, don’t try to name a dataframe false or median.

  • Use spaces in your code before and after operators like parenthesis or equals signs, like this: theMedian <- median(c(1, 4, 6))

  • Use a return at the end of each pipe when you are using tidyverse and dplyr, and use a return after each + when you are using ggplot2.

  • Use indentations and multiple lines to split apart long functions - try to use one idea per line. R Studio does a good job of managing indentations for you automatically.

Here’s a quick example of a few of these things in action by piping together a few operations. We mutate a new variable called high_income in acsTractsPHL.2016.sf and send that categorical to the fill command in a geom_sf in ggplot2 using the sf package. In the “bad” example we just write a big giant sentence. In the “better” example we use returns after our pipes and plus signs, and we break up our ifelse statement using returns to make it easier to read.

# Bad

acsTractsPHL.2016.sf %>% mutate(high_income = ifelse(med_HH_Income.2016 > 100000, "Over 100k", "Under 100k")) %>% ggplot()+ geom_sf(aes(fill = high_income)) 
# Better

acsTractsPHL.2016.sf %>% 
  mutate(high_income = ifelse(med_HH_Income.2016 > 100000, 
                              "Over 100k", 
                              "Under 100k")) %>% 
  ggplot()+ 
  geom_sf(aes(fill = high_income)) 

Check out https://style.tidyverse.org/index.html for more info on how to write legible code.

Data and file management

New additions for 2024 will include tips on how to organize your R Projects, R scripts, data, markdowns and more.

12. Querying APIs and Reading Data from Web Sources

Upcoming additions include vignettes on Socrata and REST API query functionalities for R, and using two-factor authentification with boxr and PennBox.

Presently, vignettes for boxr can be found https://github.com/mafichman/boxr.

13. Other Spatial Packages

Upcoming additions will include vignettes for some of the following packages - Leaflet, osmdata, raster/terra, and crs_suggest.

Currently, a vignette on the use of osmdata for Open Street Map data can be found https://github.com/mafichman/osm_data.

13.1. Interactive Mapping with mapview

The mapview package is a quick and easy way to look at spatial data interactively with a basemap. mapview can handle many kinds of spatial data, including sf vectors. I like to use mapview to make quality checks on my data, and quickly scan for patterns at multiple scales. With static maps in ggplot with sf, sometimes this can be cumbersome. mapview has some drawbacks - it’s hard to make sophisticated styling, and it can choke on large data sets (more than a few thousand objects).

Here is a really quick web map of our restaurants data set from Section 9 in mapview

To use mapview for the first time you should install and load the package. (If you already have it installed, skip this step):

install.packages('mapview')

Now load them into your R environment with a library call:

library(mapview)

Now we can simply feed restaurants to the mapView function. You can click on the points and see their attributes. Simple!

If you have data to symbologize, you can use the zcol argument to specify it.

mapView(restaurants)

14. Resources and texts

This section contains online resources for learning R and troubleshooting code-based problems.

14.1. Troubleshooting - How to ask a coding question

It’s super important to know how to ask a good question on the internet or on the class forum about some issue you are having. Googling around is a really good way to find help - so you can google something about the error you are having, or look on https://stackoverflow.com, which is a community of people doing Q&A and troubleshooting.

You can look up details about the functions you are using in R using the ?? command in your console like this:

??read.csv

You can also check out the documentation about the packages, functions and the arguments they take in the Packages tab in the Viewer window in R Studio.

If you are going to ask a question on the class forum, include some code, and details about your problem - like the data sources that you tried to use and how they look. This allows somebody to reproduce the issue on their own, or at least understand it.

A good question gives your respondent lots of background info:

  • What is the nature of the data in your question? Can you post a glimpse command so we can see what the column names and data types are?

  • What is the code snippet that didn’t run correctly?

  • What kind of error message did you get?

  • What packages do you have loaded?

  • Did you google the question before you came and asked for help? (The answer should be yes).

Here are some examples of some bad questions, and some alternatives:

Class forum issue: “I tried to make this code work (pastes code block) and I got an error.”

->

“I ran this code block using data that look like this (show the results of a head or glimpse command) and got this error (copy and paste error message). I have the following packages installed….”

Google query:

“How do you get my file to join the other?”

->

“Spatial joins in r with sf”

14.2. Troubleshooting using Chat Bots

Generative AI solutions like Chat GPT can be useful aids in debugging code, writing functions, and solving coding problems. It’s helpful to think of GPT as a highly structured, highly customized search engine. To get the best results, use the same kind of steps described in the previous section about asking a question on a class message board.

Keep in mind that if you have not educated yourself about how a coding language works, you not only can’t ask good questions of a GPT, you cannot audit the results of queries effectively. You might not be able to interpret the varying degrees of wrongness you get from GPT outputs. You might also generate outputs that are either a) inoperable or worse b) functional but fundamentally wrong for your task.

Consider the following situation where I want to mutate a new column and I consult ChatGPT about how to format the data. I will provide an example of a good query and a bad query as I try to solve my problem.

I am going to load the data set mtcars (a data set about cars and their attributes that comes with R and is often used to create code examples just like this!).

data(mtcars)

head(mtcars)
##                    mpg cyl disp  hp drat    wt  qsec vs am gear carb
## Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
## Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
## Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
## Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
## Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
## Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1

I’d like to mutate a column that is a ratio of horsepower (hp) to miles per gallon (mpg). Simply, I would say something like this:

mtcars %>% mutate(hp_mpg_ratio = hp/mpg)

However, I get tons of decimal places when I do this, and I don’t know how to fix that! So I ask ChatGPT:

How do I round to two decimal places in R?

ChatGPT will give me a generic code example that I have to interpret and apply to my code, but if I ask it

Can you adjust this tidy R code to round the output to two decimal places?

mtcars %>% mutate(hp_mpg_ratio = hp/mpg)

I will get a precise re-working of my code.

The more sophisticated your problem, the more important it is that you ask a very specific question, sometimes even providing background information about packages, data types, and so on.

Let’s say I wanted to make a plot of mpg, which is a continuous variable, but I am interested in the number of cars that are over or under 20 mpg. I can ask a very specific question like the following:

How do I adjust this tidy R code so that the continuous variable mpg is engineered as a categorical variable that is either over or under 20. The result should be a bar plot and the feature transformation should take place within the ggplot call.

ggplot(mtcars)+ geom_histogram(aes(mpg))

The result is as follows:

ggplot(mtcars, aes(x = ifelse(mpg > 20, "Over 20", "Under 20"))) +
  geom_bar(color = "black", fill = "steelblue") +
  labs(x = "MPG Category", y = "Frequency", title = "Bar Plot of MPG Over/Under 20")

14.3. R Cheat Sheets

R Studio’s website has some really good cheat sheets with simple walkthroughs of basic functions in some core packages

14.4. Style Guides

Handy tips for making your code readable

Color Palette ideas

14.5. Open Source GIS Resources

14.6. Books

If you’re in a class with me - these books are likely to be among the required or supplemental readings. They are all fantastic, open source, and extremely, extremely useful. Here they are in “bookdown” form for free.

14.8. FAQ

Have a question or something you’d like to see included here? Add it to [https://github.com/mafichman/R_FAQ_For_Planners/issues](the “issues” section of the repo for this text!)

LS0tDQp0aXRsZTogIkludHJvIHRvIFIgYW5kIEZBUSBmb3IgUGxhbm5lcnMiDQphdXRob3I6ICJNaWNoYWVsIEZpY2htYW4iDQpkYXRlOiAiNy8yMC8yMDIzIg0Kb3V0cHV0OiANCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpgYGANCg0KIyAxLiBJbnRyb2R1Y3Rpb24NCg0KVGhpcyBpcyBhIGRvY3VtZW50IGRlc2lnbmVkIHRvIGdpdmUgYSBiYXNpYyBpbnRyb2R1Y3Rpb24gdG8gUiBmb3IgdGhvc2Ugd2hvIGhhdmUgbm8gY29kaW5nIGV4cGVyaWVuY2UuIFRoaXMgc2hvdWxkIGhlbHAgeW91IGdldCBwcmVwYXJlZCBmb3IgYXBwbGllZCBwcm9qZWN0cyBpbiBDUExOIGFuZCBNVVNBIDUwMC02MDAgbGV2ZWwgY291cnNld29yayB3aXRoaW4gYSBjb3VwbGUgb2Ygd2Vla3MuIEl0IGNvYWxlc2NlcyBpbnRyb2R1Y3RvcnkgbWF0ZXJpYWxzIEkndmUgZGV2ZWxvcGVkIG92ZXIgdGhlIHBhc3Qgc2l4IHllYXJzIGZvciBNVVNBIDUwOCwgTVVTQSA3OTUsIENQTE4gNTAxLCBhbmQgQ1BMTiA2NzUgYW5kIHNob3VsZCBnaXZlIHlvdSB3aGF0IHlvdSBuZWVkIHRvIGdldCBzdGFydGVkIGluIGFueSBvZiB0aG9zZSBjb3Vyc2VzLg0KDQpUaGlzIGlzbid0IGEgImNvZGluZyIgdHV0b3JpYWwsIGl0J3MgYSBzdGFydGVyIGtpdCB0aGF0IGRlbW9uc3RyYXRlcyBob3cgUiBjYW4gZG8gc29tZSBmYW1pbGlhciBFeGNlbCBvciBBcmNHSVMtbGlrZSB0YXNrcyAtIGxvYWRpbmcgc3ByZWFkc2hlZXRzIGFuZCBzcGF0aWFsIGRhdGEsIGNyZWF0aW5nIG5ldyBjb2x1bW5zIG9mIGRhdGEsIHN1bW1hcml6aW5nIGFuZCBxdWVyeWluZyBkYXRhLCBtYWtpbmcgY2hhcnRzIGFuZCBtYXBzIC0gYW5kIGRvIHRoZW0gZmFzdGVyLCBiZXR0ZXIsIGFuZCBtb3JlIHJlcHJvZHVjaWJseS4NCg0KVXNlIHRoZSB0YWJsZSBvZiBjb250ZW50cyBhdCBsZWZ0IHRvIG5hdmlnYXRlIHRocm91Z2ggdGhlIGRvY3VtZW50IC0gdGhlIHNpbXBsZXN0IHRvcGljcyBhcmUgZmlyc3QgKGUuZy4gIldoYXQgaXMgUj8iLCAiSG93IGRvIEkgZG93bmxvYWQgUj8iKSwgdGhlIGxhdHRlciBzZWN0aW9ucyBhcmUgbW9yZSBhZHZhbmNlZCAoR2VvcHJvY2Vzc2luZywgQ2Vuc3VzIERhdGEsIE1hcmtkb3duIGRvY3VtZW50cykuIFRoZXJlIGFyZSByZWZlcmVuY2VzIHRvIGJvb2tzIGFuZCBvdGhlciBtYXRlcmlhbHMgYXQgdGhlIGVuZCBvZiB0aGUgZG9jLg0KDQpUaGlzIGRvY3VtZW50IHdpbGwgYmUgdXBkYXRlZCBvbiBhbiBvbmdvaW5nIGJhc2lzIHRvIGluY2x1ZGUgbmV3IHBhY2thZ2VzIGFuZCBtZXRob2RzIC0geW91IG1heSBzZWUgYSBmZXcgIkNvbWluZyBzb29uIiBwcmV2aWV3cyB0b3dhcmRzIHRoZSBlbmQgb2YgdGhlIGRvY3VtZW50Lg0KDQoqKk1pY2hhZWwgRmljaG1hbioqDQoNCipBc3NvY2lhdGUgUHJvZmVzc29yIG9mIFByYWN0aWNlKg0KDQoqVW5pdmVyc2l0eSBvZiBQZW5uc3lsdmFuaWEgV2VpdHptYW4gU2Nob29sIG9mIERlc2lnbioNCg0KKkRlcGFydG1lbnQgb2YgQ2l0eSBhbmQgUmVnaW9uYWwgUGxhbm5pbmcqDQoNCiMgMi4gR2V0dGluZyBTdGFydGVkIHdpdGggUg0KDQojIyBXaGF0IGlzIFI/DQoNClIgaXMgYSBwcm9ncmFtbWluZyBsYW5ndWFnZSB5b3UgY2FuIHVzZSB0byBkbyBzdHVmZiB0aGF0IHlvdSBjYW4gZG8gaW4gRXhjZWwsIG9yIEFyY0dJUywgb3Igc3RhdHMgc29mdHdhcmUgbGlrZSBTQVMuLi4gYnV0ICh3aXRoIHNvbWUgcHJhY3RpY2UpIGZhc3RlciwgYmV0dGVyLCBhbmQgbW9yZSBlZmZpY2llbnRseS4gDQoNCllvdSBjYW4gbG9hZCBkYXRhIGluIHNwcmVhZHNoZWV0IGZvcm0sIG1ha2UgbmV3IGNvbHVtbnMsIGNyZWF0ZSBkYXRhIHN1bW1hcmllcywgbWFrZSBjaGFydHMsIG1ha2UgbWFwcy4gWW91IGNhbiBsb2FkIHNwYXRpYWwgZGF0YSBhbmQgdXNlIFIgYXMgYSBHSVMuIFlvdSBjYW4gbWFrZSBzdGF0aXN0aWNhbCBtb2RlbHMuICBZb3UgY2FuIGFsc28gZG8gd2F5IG1vcmUgY29tcGxpY2F0ZWQgc3R1ZmYgdG9vLiAqTW9zdCBpbXBvcnRhbnRseSwgeW91IGNhbiBzZXQgdXAgcm91dGluZXMgb3Igd29ya2Zsb3dzIHRvIGxldCB5b3UgcmVwZWF0IHByb2Nlc3NlcyB0aGF0IGluIG90aGVyIHNvZnR3YXJlIHdvdWxkIHRha2UgbG90cyBvZiBjbGlja3MgYW5kIHN0ZXBzLioNCg0KSXQncyAib3BlbiBzb3VyY2UsIiB3aGljaCBtZWFucyBpdCBpcyB3cml0dGVuIGFuZCBtYWludGFpbmVkIGJ5IGEgY29tbXVuaXR5IG9mIHBlb3BsZSBsaWtlIHlvdSBhbmQgbWUuIFRoYXQgYWxzbyBtZWFucyB0aGF0IHNvbWV0aW1lcywgdGhpbmdzIHdpbGwgZ28gd3JvbmcgYW5kIHRoZXJlIGlzbid0IGFsd2F5cyBhIG1hbnVhbCB0byBjb25zdWx0Lg0KDQpJZiB5b3UndmUgbmV2ZXIgcHJvZ3JhbW1lZCBiZWZvcmUsIHRoYXQncyBPSywgeW91J3JlIGluIHRoZSByaWdodCBwbGFjZS4NCg0KIyMgV2hhdCBpcyBSIFN0dWRpbz8NCg0KSSBydW4gUiBjb2RlIGFuZCBkbyBwcm9qZWN0cyBpbiBSIFN0dWRpbywgd2hpY2ggaXMgbGlrZSBhIHZpZXdlciBmb3IgUi4gWW91IGNhbiB3cml0ZSBjb2RlIHRvIGxvYWQgYW5kIG1hbmlwdWxhdGUgZGF0YS4gWW91IGNhbiBzZWUgcGxvdHMgYW5kIGRhdGEgc2V0cyB0aGF0IHlvdSd2ZSBsb2FkZWQgdXNpbmcgUiBTdHVkaW8uIA0KDQojIyBJbnN0YWxsaW5nIFIgYW5kIFIgU3R1ZGlvIGZvciBXaW5kb3dzIGFuZCBNYWMuIA0KDQoxLglJbnN0YWxsIFI6IEdvIHRvIFtodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy9iaW4vd2luZG93cy9iYXNlL10oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvYmluL3dpbmRvd3MvYmFzZS8pDQoyLglDbGljayBvbiB5b3VyIG9wZXJhdGluZyBzeXN0ZW0gKFdpbmRvd3MsIE1BQywgTGludXgpIGFuZCBmb2xsb3cgZGlyZWN0aW9ucyB0byBETCB0aGUgbW9zdCByZWNlbnQgdmVyc2lvbiBvZiBSIOKAkyB3aGljaCBpcyA0LjEuMSBhcyBvZiB0aGlzIHdyaXRpbmcuDQozLglETCBhbmQgcnVuIHRoZSAuZXhlIGZpbGUgdG8gaW5zdGFsbA0KNC4JSW5zdGFsbCBSIFN0dWRpbzogR28gdG8gW2h0dHA6Ly93d3cucnN0dWRpby5jb21dKGh0dHA6Ly93d3cucnN0dWRpby5jb20pLCBjbGljayBvbiDigJxEb3dubG9hZCBSU3R1ZGlv4oCdIGFuZCBmb2xsb3cgdGhlIGRpcmVjdGlvbnMgZm9yIHlvdXIgb3BlcmF0aW5nIHN5c3RlbS4gDQo1LglPcGVuIFIgc3R1ZGlvIGJ5IGNsaWNraW5nIG9uIHRoZSBpY29uLCBSIHdpbGwgcnVuIGluc2lkZSB0aGUgUiBTdHVkaW8gdXNlciBpbnRlcmZhY2UuDQoNCiMjIFIgU3R1ZGlvIE9yaWVudGF0aW9uDQoNCk5vdyBvcGVuIHVwIFIgU3R1ZGlvIGFuZCBsZXQncyB0YWtlIGEgbG9vayBhdCBzb21lIG9mIHRoZSBjb21wb25lbnRzLg0KDQohW0ltYWdlIGJ5IEtpZXJhbiBIZWFseV0oaW1hZ2VzL2hlYWx5X2d1aS5wbmcpDQpZb3VyIFIgU3R1ZGlvIEdVSSAoZ3JhcGhpY2FsIHVzZXIgaW50ZXJmYWNlKSBzaG91bGQgbG9vayBzb21ldGhpbmcgbGlrZSB0aGlzIChJbWFnZSBieSBLaWVyYW4gSGVhbHkpDQoNCiMjIyBUaGUgQ29uc29sZQ0KDQpZb3UgY2FuIHR5cGUgYW5kIGV4ZWN1dGUgY29kZSBpbiB0aGUgY29uc29sZS4NCg0KSSBjb3VsZCB0eXBlIHNvbWV0aGluZyBsaWtlIGBteU5hbWUgPC0gIk1pY2hhZWwiYCBpbnRvIHRoZSBjb25zb2xlLCBhbmQgaXQgd291bGQgY3JlYXRlIGEgdmFyaWFibGUgY2FsbGVkIGBteU5hbWVgIHRoYXQgaXMgZXF1YWwgdG8gdGhlIGNoYXJhY3RlciBzdHJpbmcgIk1pY2hhZWwiIGluIG15ICJlbnZpcm9ubWVudC4iDQoNCkdvIGFoZWFkLCB0cnkgaXQuDQoNCmBgYHtyIG1pY2hhZWx9DQoNCm15TmFtZSA8LSAiTWljaGFlbCINCg0KYGBgDQoNCiMjIyBUaGUgRW52aXJvbm1lbnQNCg0KVGhpcyBpcyB3aGVyZSB5b3UgY2FuIHNlZSB0aGUgZGF0YSBzZXRzIGFuZCBvdGhlciB0aGluZ3MgeW91J3ZlIGxvYWRlZCBpbnRvIFIgdG8gd29yayBvbi4gSWYgSSBsb2FkIGFuIGV4Y2VsIHNwcmVhZHNoZWV0IGludG8gUiB1c2luZyBgcmVhZF9leGNlbGAsIEkgY291bGQgc2F5IHNvbWV0aGluZyBsaWtlIGBteURhdGEgPC0gcmVhZF9leGNlbCgiZmlsZXBhdGhfb25fbXlfY29tcHV0ZXIueGxzeCIpYCBpdCB3aWxsIHNob3cgdXAgaW4gbXkgZW52aXJvbm1lbnQgYXMgYSB0aGluZyBjYWxsZWQgYG15RGF0YWAuDQoNCllvdSBjYW4gY2xlYXIgdGhlIGVudmlyb25tZW50IGJ5IGdvaW5nIHRvIHRoZSBgU2Vzc2lvbmAgZHJvcGRvd24gYW5kIHNheWluZyBgQ2xlYXIgV29ya3NwYWNlYC4NCg0KWW91IGNhbiBhbHNvIHNhdmUgeW91ciBlbnZpcm9ubWVudCBvciBsb2FkIHVwIGFuIG9sZCBvbmUgZnJvbSBhIHByb2plY3QgeW91IHdlcmUgd29ya2luZyBvbi4NCg0KIyMjIFRoZSBDb2RlIHdpbmRvdw0KDQpJZiB5b3UgbG9hZCB1cCBhIC5SIG9yIC5ybWQgZmlsZSAtIHR3byB0eXBlcyBvZiBSIGZpbGVzLCB0aGV5IHdpbGwgc2hvdyB1cCBhcyB0YWJzIGluIHlvdXIgY29kZSB3aW5kb3cuDQoNCklmIEkgZ2l2ZSB5b3Ugc29tZSBjb2RlLCB5b3UgY2FuIGV4ZWN1dGUgaXQgYSBwaWVjZSBhdCBhIHRpbWUgYnkgY29weWluZy1hbmQtcGFzdGluZyBpbnRvIHRoZSBjb25zb2xlLCBvciBoaWdobGlnaHRpbmcgY29kZSBhbmQgaGl0dGluZyBgY3RybCtFbnRlcmAgdG8gcnVuIGl0Lg0KDQpJZiB5b3UgYXJlIHdyaXRpbmcgY29kZSwgeW91IGNhbiBzYXZlIHlvdXIgY29kZSBhcyBlaXRoZXIgYSAuUiBmaWxlIChqdXN0IHBsYWluIGNvZGUpLCBvciBhIC5ybWQgZmlsZSAoYSBNYXJrZG93biBmaWxlLCBsaWtlIHdoYXQgeW91IGFyZSByZWFkaW5nIG5vdywgd2hpY2ggaXMgc29tZXRoaW5nIHlvdSBjYW4gd3JpdGUgdG8gcHJlc2VudCBjb2RlIG9yIGFuYWx5c2lzLikNCg0KIyMjIFRoZSBQbG90dGluZyAvIEZpbGVzIC8gUGFja2FnZXMgZXRjIFdpbmRvdw0KDQotSWYgeW91IG1ha2UgY2hhcnRzIG9yIHBsb3RzIC0gdGhleSBzaG93IHVwIGluIHRoaXMgd2luZG93Lg0KDQotVGhlICJGaWxlcyIgdGFiIGlzIGEgcGxhY2UgdG8gZ3JhcGhpY2FsbHkgbG9vayB0aHJvdWdoIGZpbGUgdHJlZXMgb24geW91ciBjb21wdXRlci4NCg0KLVlvdSBjYW4gY29uc3VsdCB0aGUgIlBhY2thZ2VzIiBvciAiSGVscCIgdGFicyB0byBmaW5kIGRvY3VtZW50YXRpb24gYWJvdXQgZGlmZmVyZW50IGZ1bmN0aW9ucyBhbmQgcGFja2FnZXMgaW4gdGhlIFIgd29ybGQuIChNb3JlIGFib3V0IHBhY2thZ2VzIGxhdGVyKQ0KDQojIDMuIERhdGEgYW5kIENvZGluZyBCYXNpY3MNCg0KSG93IGRvIHlvdSAicnVuIGNvZGUiIGluIFI/DQoNClVzdWFsbHksIEkgd2lsbCBnaXZlIHlvdSBhIGJ1bmNoIG9mIGNvZGUgaW4gY2xhc3MsIGFuZCB5b3Ugd2lsbCBoYXZlIHRvIGFkYXB0IHRoYXQgY29kZSBmb3IgYSBwcm9qZWN0IHlvdSBhcmUgZG9pbmcuIEV2ZW50dWFsbHksIHlvdSB3aWxsIHdyaXRlIHNvbWUgb2YgeW91ciBvd24gY29kZSAtIGJ1dCAqbW9zdCBwZW9wbGUgY3V0IGFuZCBwYXN0ZSBzdHVmZiogYW5kIHN3aXRjaCB2YXJpYWJsZSBuYW1lcyBhbmQgaW5wdXQgZGF0YSBzZXRzIGFyb3VuZC4gVGhhdCdzIGhvdyBJIGRvIG11Y2ggb2YgbXkgd29yayAtIEkgZ3JhYiB0aGluZ3MgZnJvbSBteSBjb2RlIGJhc2UgSSd2ZSBnZW5lcmF0ZWQgb3ZlciB0aGUgeWVhcnMuIFNvIGRvbid0IHdvcnJ5IGFib3V0IHdyaXRpbmcgdGhpbmdzIGZyb20gc2NyYXRjaC4NCg0KLSBDb2RlIHdpdGggYSBgI2AgaW4gZnJvbnQgb2YgaXQgaXMgY2FsbGVkICJjb21tZW50ZWQgY29kZS4iIFRoaXMgc3R1ZmYgZG9lc24ndCBleGVjdXRlIGl0J3Mgd2hlcmUgeW91IG1ha2Ugbm90ZXMuDQoNCi0gQ29kZSB3aXRob3V0IHRoZSBgI2Agd2lsbCBydW4gLSBlaXRoZXIgYXMgd3JpdHRlbiBpbiB0aGUgY29uc29sZSBvciBieSBoaXR0aW5nIGN0cmwrZW50ZXIgb24gY29kZSB0aGUgY29kZSB3aW5kb3cNCg0KYGBge3IgY29tbWVudGVkfQ0KDQojIFRoaXMgY29kZSBkb2Vzbid0IGRvIGFueXRoaW5nLCBidXQgdGhlIG5leHQgbGluZSBkb2VzDQpwcmludCgnaGVsbG8gd29ybGQnKQ0KDQpgYGANCg0KUiBpcyBjYXNlIHNlbnNpdGl2ZSAtIG1hbnkgb2YgeW91ciBpc3N1ZXMgY2FuIGJlIHJlc29sdmVkIGJ5IGNoZWNraW5nIHlvdXIgc3ludGF4LiBZb3UgbWlnaHQgZ2V0IHNvbWUga2luZCBvZiBlcnJvciBiZWNhdXNlIHlvdSBjcmVhdGVkIGEgZGF0YSBzZXQgY2FsbGVkIGBkZXJwYCBhbmQgeW91IHRyeSB0byBjYWxsIGl0IGluIHRoZSBjb2RlIHdpdGggYERFUlBgLiBIYXBwZW5zICphbGwgdGhlIHRpbWUqLg0KDQpUVVRPUklBTDoNCg0KKipJZiB0aGlzIGlzIHlvdXIgZmlyc3QgdGltZSB1c2luZyBSLCB5b3UgbWlnaHQgd2FudCB0byB0cnkgb3V0IGEgLlIgc2NyaXB0IGNhbGxlZCBgcnVubmluZ19jb2RlX2luX1IuUmAgd2hpY2ggW3lvdSBjYW4gZmluZCBoZXJlXShodHRwczovL2dpdGh1Yi5jb20vbWFmaWNobWFuL1JfRkFRX0Zvcl9QbGFubmVycy9ibG9iL21haW4vUi9ydW5uaW5nX2NvZGVfaW5fUi5SKS4gSXQgaGFzIHNvbWUgc2ltcGxlIGNvZGUgYW5kIHNpbXBsZSBvcGVyYXRpb25zIHlvdSBjYW4gdHJ5IC0gZG93bmxvYWQgdGhlIC5SIGZpbGUsIGxvYWQgaXQgaW50byB5b3VyIFIgU3R1ZGlvIGFuZCBmb2xsb3cgdGhlIGluc3RydWN0aW9ucyB0byBjcmVhdGUgc29tZSBzaW1wbGUgb2JqZWN0cy4qKg0KDQojIyBOYW1pbmcgVmFyaWFibGVzIGFuZCBEYXRhIFNldHMNCg0KTW9zdCBwaWVjZXMgb2YgZGF0YSB5b3UgbG9hZCBpbnRvIFIgb3IgY3JlYXRlIHdpdGggUiB3aWxsIGhhdmUgYSBuYW1lLiBJZiB5b3UgbG9hZCBpbiBhIHNwcmVhZHNoZWV0LCB5b3Ugd2lsbCB3YW50IHRvIGdpdmUgaXQgYSBuYW1lIHNvIGl0IHNob3dzIHVwIGluIG15IFIgZW52aXJvbm1lbnQuICBZb3UgY2FuIGFzc2lnbiBpbmZvcm1hdGlvbiB0byBhIHZhcmlhYmxlIHVzaW5nIHRoaXMgc2lnbiAiPC0iDQoNCllvdSBjYW4gaGF2ZSBhbGwga2luZHMgb2YgZGF0YSBvYmplY3RzOg0KDQotIEEgZGF0YSBmcmFtZTogYSBtYXRyaXggb2YgZGF0YSB3aXRoIGNvbHVtbiBuYW1lcywgYmFzaWNhbGx5IGxpa2UgYW4gZXhjZWwgc3ByZWFkc2hlZXQgKG11Y2ggbW9yZSBvbiB0aGlzIGxhdGVyKQ0KDQotIEEgdmVjdG9yOiBhIGxpc3Qgb2YgaXRlbXMsIGRlbm90ZWQgd2l0aCB0aGUgbGV0dGVyIGMsIGxpa2UgdGhpczoNCmBgYHtyIG15VmVjdG9yfQ0KbXlWZWN0b3IgPC0gYygiQSIsICJCIiwgIkMiKQ0KYGBgDQotIEEgY2hhcmFjdGVyOiBzb21lIHRoaW5ncyB0cmVhdGVkIGFzIHRleHQsIGRlbm90ZWQgd2l0aCAiIiBxdW90ZXMgYXJvdW5kIGl0Lg0KDQpgYGB7ciBteVZhcmlhYmxlfQ0KbXlWYXJpYWJsZSA8LSAiZGVycCINCmBgYA0KLSBBIG51bWJlcjogY2FuIGJlIGluIHRoZSBmb3JtIG9mIGFuIGludGVnZXIgb3IgYSBkb3VibGUgKGEgZmxvYXRpbmcgcG9pbnQgbnVtYmVyKS4NCg0KYGBge3IgbXlOdW1iZXJ9DQpteU51bWJlciA8LSAxMA0KYGBgDQoNCiMjIE5BIERhdGENCg0KVGhlcmUgYXJlIG51bWJlcnMsIGNoYXJhY3RlcnMsIGFuZCBvdGhlciBkYXRhIHR5cGVzLiBUaGVyZSBhcmUgYWxzbyBgTkFgIGRhdGEgLSBtaXNzaW5nIGRhdGEgLSB0aGVzZSBhcmUgdHJpY2t5LiBZb3UgY2FuJ3QgZG8gYXJpdGhtZXRpYyBvbiB0aGVzZS4NCg0KTG9vayBhdCB0aGUgZXhhbXBsZSBiZWxvdy4gV2UgY3JlYXRlIGEgdmVjdG9yIG9mIG51bWVyaWMgZGF0YSBjYWxsZWQgYHZlY3RvcjFgIHdoaWNoIGlzIGp1c3QgbnVtYmVycywgYW5kIHRoZW4gd2UgYXNrIGl0cyBtZWRpYW4uIFRoYXQgd29ya3MuDQoNClRoZW4gd2UgY3JlYXRlIG9uZSBjYWxsZWQgYHZlY3RvcjJgIHdpdGggc29tZSBOQSBpbiB0aGVyZSwgaXQgZG9lc24ndCB3b3JrIHRvIHRha2UgdGhlIG1lZGlhbiB0aGVyZS4NCg0KYGBge3IgdmVjdG9yMX0NCnZlY3RvcjEgPC0gYygxLCA0LCA2LCA3LCAxMCkNCg0KbWVkaWFuKHZlY3RvcjEpDQpgYGANCg0KYGBge3IgdmVjdG9yMn0NCnZlY3RvcjIgPC0gYygxLCA0LCA2LCBOQSwgMTApDQoNCm1lZGlhbih2ZWN0b3IyKQ0KYGBgDQoNCiMgNC4gSW5zdGFsbGluZyBhbmQgTG9hZGluZyBQYWNrYWdlcw0KDQpBIGxvdCBvZiB0aGUgdGhpbmdzIHlvdSB3aWxsIHdhbnQgdG8gZG8gd2l0aCBSIHdpbGwgcmVxdWlyZSBsb2FkaW5nIHBhY2thZ2VzLiAiQmFzZSBSIiBpcyBiYXNpYyBSIC0gdGhlcmUgYXJlIGEgYnVuY2ggb2YgZnVuY3Rpb25zIGl0IGRvZXMuIFNpbmNlIFIgaXMgb3BlbiBzb3VyY2UsIGxvdHMgb2YgcGVvcGxlIGhhdmUgZGV2ZWxvcGVkIHRoZWlyIG93biBjb29sIHBhY2thZ2VzIHRoYXQgZG8gYWRkaXRpb25hbCBzdHVmZi4NCg0KQSBwYWNrYWdlIGlzIGEgc2V0IG9mIGZ1bmN0aW9ucyAtIHNvbWUgb2YgdGhlbSBtYWtlIGdyYXBoaWNzLCBzb21lIGhhbmRsZSBzcGF0aWFsIGRhdGEuIFlvdSB3aWxsIGFsbW9zdCBhbHdheXMgc3RhcnQgeW91ciBzZXNzaW9uIGJ5IGxvYWRpbmcgc29tZSBwYWNrYWdlcy4NCg0KVGhlIGZpcnN0IHRpbWUgeW91IHVzZSBhIHBhY2thZ2Ugb24geW91ciBjb21wdXRlciwgeW91IGNhbiBsb2FkIGl0IHdpdGggdGhlIGNvbW1hbmQgYGluc3RhbGwucGFja2FnZXNgLCBsaWtlIHRoaXM6DQoNCmBgYHtyIGluc3RhbGxfcGtnLCBldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUV9DQppbnN0YWxsLnBhY2thZ2VzKCd0aWR5dmVyc2UnKQ0KYGBgDQoNCklmIHlvdSBoYXZlIGFscmVhZHkgaW5zdGFsbGVkIGEgcGFja2FnZSBvbiB5b3VyIGNvbXB1dGVyLCB5b3UgY2FuIGxvYWQgaXQgaW50byB5b3VyIGVudmlyb25tZW50IHVzaW5nIHRoZSBgbGlicmFyeWAgY29tbWFuZC4NCg0KYGBge3IgbG9hZF9wa2cgLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KYGBgDQoNClBhY2thZ2VzIGFsbCBjb21lIHdpdGggZG9jdW1lbnRhdGlvbiAtIGxpc3RzIG9mIGZ1bmN0aW9ucyBpbiB0aGVzZSBwYWNrYWdlcyBoYXZlIGxpdHRsZSB2aWduZXR0ZXMgdGhhdCB0ZWxsIHlvdSBob3cgdG8gdXNlIHRoZW0uICBDaGVjayB0aGF0IG91dCBpbiB0aGUgIlBhY2thZ2VzIiB0YWIgaW4geW91ciBSIFN0dWRpbyBlbnZpcm9ubWVudCB0byBzZWUgdGhlc2UsIG9yIGp1c3QgZ29vZ2xlIHBhY2thZ2UgbmFtZXMgb3IgZnVuY3Rpb24gbmFtZXMgdG8gZmluZCB0aGUgZG9jdW1lbnRhdGlvbi4NCg0KWW91IGNhbiBhbHNvIHR5cGUgdGhpbmdzIGxpa2UgYD8/c2VsZWN0YCBpbiB5b3VyIGNvbnNvbGUgYW5kIGRvY3VtZW50YXRpb24gYWJvdXQgdGhlIGZ1bmN0aW9uIGBzZWxlY3RgIHdpbGwgcG9wIHVwIGluIHlvdXIgIkhlbHAiIHdpbmRvdyBpbiBSIFN0dWRpby4NCg0KIyMgVGhlIFRpZHl2ZXJzZQ0KDQpUaGUgYHRpZHl2ZXJzZWAsIHdoaWNoIHdlIGxvYWRlZCBpbiB0aGUgcHJldmlvdXMgY29kZSBjaHVuaywgaXMgYSBncm91cCBvZiBpbXBvcnRhbnQgcGFja2FnZXMgd2hpY2ggYXJlIHVzZWQgdG8gd3JhbmdsZSBkYXRhLCBtYWtlIGdyYXBoaWNzLCBsb2FkIGRhdGEsIGhhbmRsZSBkYXRlcywgaGFuZGxlIHRleHQuIEJldHRlciB5ZXQsIHRoZXNlIHByb2plY3RzIGFyZSBjb29yZGluYXRlZCBieSB0aGVpciBkZXZlbG9wZXJzLCBzbyB0aGV5IGFsbCB3b3JrIG5pY2VseSB0b2dldGhlci4NCg0KRm9yIHdvcmsgaW4gb3VyIGNsYXNzLCB5b3Ugc2hvdWxkIGxvYWQgdXAgdGhlIHRpZHl2ZXJzZSBvbiBldmVyeSBwcm9qZWN0LiBNb3N0IG9mIG15IGNvZGUgZG9lc24ndCB3b3JrIHdpdGhvdXQgaXQuDQoNCiMgNS4gTG9hZGluZyBhbmQgVmlld2luZyBEYXRhIA0KDQpNb3N0IHByb2plY3RzIHdpbGwgc3RhcnQgd2l0aCB5b3UgbG9hZGluZyBzb21lIHNwcmVhZHNoZWV0LWxpa2UgZGF0YSAtIGZyb20gYSBzdXJ2ZXkgeW91IGNvbmR1Y3RlZCwgZnJvbSBhbiBleGNlbCBzaGVldCwgZnJvbSB0aGUgQ2Vuc3VzJyB3ZWJzaXRlLCBmcm9tIGEgc2hhcGVmaWxlIHRoYXQgSSBnYXZlIHlvdSBmb3IgY2xhc3MuIEZvciBub24tc3BhdGlhbCBkYXRhLCB0aGVzZSBzcHJlYWRzaGVldC1saWtlIGRhdGEgd2lsbCBiZSBpbiBhIGZvcm1hdCBjYWxsZWQgYSAiZGF0YSBmcmFtZSIgKG9yIGFzIGEgdmFyaWFudCBjYWxsZWQgYSAidGliYmxlKS4NCg0KVGhlcmUgYXJlIGEgbG90IG9mIGZpbGUgdHlwZXMgeW91IGNhbiBsb2FkLCBhbmQgc29tZSBvZiB0aGVtIGhhdmUgdGhlaXIgb3duIGZ1bmN0aW9ucywgbGlrZSB5b3UgY2FuIHJlYWQgaW4gRXhjZWwgZGF0YSB3aXRoIHRoZSBgcmVhZF9leGNlbGAgZnVuY3Rpb24gaW4gdGhlIGByZWFkeGxgIHBhY2thZ2UuIExhdGVyIG9uIEkgd2lsbCBzaG93IHlvdSBob3cgdG8gcmVhZCBpbiBhIC5zaHAuIChZb3UgY2FuIHJlYWQgbW9yZSBhYm91dCB0aGVzZSBkYXRhIGltcG9ydCBmdW5jdGlvbmFsaXRpZXMgW2hlcmUuXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcnN0dWRpby9jaGVhdHNoZWV0cy9tYWluL2RhdGEtaW1wb3J0LnBkZikpDQoNClRoZXJlIGFyZSBzZXZlcmFsIHdheXMgdG8gbG9hZCBkYXRhIGludG8geW91ciBSIGVudmlyb25tZW50Lg0KDQojIyBMb2FkaW5nIERhdGEgRnJvbSBZb3VyIENvbXB1dGVyDQoNCllvdSBjYW4gbG9hZCBkYXRhIGZyb20geW91ciBjb21wdXRlciBpbiBvbmUgb2YgdHdvIHdheXMsIGVpdGhlciBieSBnb2luZyB0byBgRmlsZSAvIEltcG9ydCBEYXRhIFNldGAgaW4gYSBkcm9wZG93biBtZW51IFIgU3R1ZGlvLCBhbmQgY2xpY2tpbmcgb24gdGhlIHJlbGV2YW50IG9wdGlvbnMgKG5vdGUgdGhhdCAiRnJvbSB0eHQiIHdpbGwgbGV0IHlvdSBsb2FkIGEgY3N2LCB0eHQsIHRzdiBvciBvdGhlciBzaW1pbGFyIGZpbGUgdHlwZXMpLCBvciBieSBkb2luZyBpdCB3aXRoIGNvZGUsIGxpa2UgdGhpczoNCg0KYGBge3IgbXlEYXRhX2NwdSwgZXZhbCA9IEZBTFNFfQ0KbXlEYXRhIDwtIHJlYWQuY3N2KCJmaWxlcGF0aC9vbk15Q29tcHV0ZXIvZmlsZU5hbWUuY3N2IikNCmBgYA0KDQojIyBMb2FkaW5nIERhdGEgZnJvbSB0aGUgV2ViDQoNCllvdSBjYW4gbG9hZCBkYXRhIGZyb20gdGhlIHdlYiwgbGlrZSBmcm9tIEdvb2dsZSBEcml2ZSBvciBHaXRodWIsIHVzaW5nIHRoZSBzYW1lIGtpbmQgb2Ygc3ludGF4LiBIZXJlIHdlIGxvYWQgYSBjc3YgZnJvbSB0aGUgd2ViOg0KDQpgYGB7ciBteURhdGFfdXJsfQ0KbXlEYXRhIDwtIHJlYWQuY3N2KCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vbWFmaWNobWFuL1JfRkFRX0Zvcl9QbGFubmVycy9tYWluL2RhdGEvc29tZUNlbnN1c0RhdGEuY3N2IikNCmBgYA0KDQojIyBWaWV3aW5nIERhdGENCg0KTm93IHRoYXQgeW91J3ZlIGxvYWRlZCBpbiBzb21lIGRhdGEsIHlvdSBjYW4gZXhhbWluZSB0aGVtIGluIHNldmVyYWwgd2F5cy4gWW91IGNhbiBgVmlldyhteURhdGEpYCBhbmQgc2VlIGEgdmlzdWFsIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBkYXRhIGluIGFuIGV4Y2VsLWxpa2Ugd2F5LCBvciB5b3UgY2FuIGBnbGltcHNlYCB5b3VyIGRhdGEgZnJhbWUuDQoNCmBnbGltcHNlYCB3aWxsIHRlbGwgeW91IHdoYXQgZGF0YSB0eXBlcyB0aGUgY29sdW1ucyBhcmUgKGNoYXJhY3RlciwgbnVtZXJpYywgZXRjKSwgbGV0IHlvdSBzZWUgc29tZSBzYW1wbGVzIG9mIHRoZSBkYXRhLCBhbmQgbGV0IHlvdSBzZWUgaG93IG1hbnkgcm93cyBhbmQgY29sdW1ucyB0aGUgZGF0YSBjb250YWluLg0KDQpgYGB7ciBnbGltcHNlX215RGF0YX0NCmdsaW1wc2UobXlEYXRhKQ0KYGBgDQojIyBXcml0aW5nIERhdGEgdG8gRmlsZQ0KDQpBZnRlciB5b3UgbWFuaXB1bGF0ZSBkYXRhLCB5b3UgbWlnaHQgd2FudCB0byB3cml0ZSBpdCBvdXQgdG8gZmlsZS4gRXZlcnl0aGluZyB3ZSBhcmUgZG9pbmcgaW4gdGhlIFItU3R1ZGlvIGVudmlyb25tZW50IGRvZXMgbm90IGFmZmVjdCB0aGUgdW5kZXJseWluZyBkYXRhIC0gc28gYW55IHByb2plY3QgeW91IGRvIG1pZ2h0IG5lZWQgYSBzdGVwIHdoZXJlIHlvdSBvdXQgcHV0IGRhdGEuDQoNCllvdSBjYW4gZG8gdGhpcyB3aXRoIGNvbW1hbmRzIGxpa2UgYHdyaXRlLmNzdmAgLSBzbyB5b3UgY291bGQgZGF0YSB5b3VyIGRhdGEgZnJhbWUgYG15RGF0YWAgYW5kIHdyaXRlIGl0IG91dCBhcyBhIGZpbGUgd2l0aCBhIG5hbWUgYW5kIGxvY2F0aW9uIG9mIHlvdXIgY2hvb3NpbmcgbGlrZSBzbzoNCg0KYHdyaXRlLmNzdihteURhdGEsICJ5b3VyX2ZpbGVfcGF0aC95b3VyX2ZpbGVfbmFtZS5jc3YiKWANCg0KV2UgdGFsayBhIGJpdCBsYXRlciBhYm91dCByZWFkaW5nL3dyaXRpbmcgc3BhdGlhbCBkYXRhIGFzIHdlbGwuDQoNCiMgNi4gTWFuaXB1bGF0aW5nIERhdGEgd2l0aCBkcGx5cg0KDQpgZHBseXJgIGlzIHRoZSBjb3JlIGRhdGEgd3JhbmdsaW5nIHBhY2thZ2UgaW4gInRpZHkiIFIuIFlvdSBjYW4gdXNlIGZ1bmN0aW9ucyBmcm9tIGBkcGx5cmAgdG8gbWFuaXB1bGF0ZSBkYXRhIGZyYW1lcy4gDQoNCllvdSBjYW4gbWFrZSBuZXcgY29sdW1ucywgYW5kIHJlbW92ZSBvciByZW5hbWUgY29sdW1ucyBmcm9tIHlvdXIgZGF0YSBmcmFtZS4gWW91IGNhbiBzdW1tYXJpemUgZGF0YSAoZS5nLiB3aGF0IGlzIHRoZSBzdW0gb2YgYWxsIHRoZSByb3dzIGluIGEgZ2l2ZW4gY29sdW1uKS4gWW91IGNhbiBmaWx0ZXIgZGF0YSB0byBrZWVwIG9ubHkgcm93cyB0aGF0IG1lZXQgc29tZSBjcml0ZXJpYS4NCg0KSXQgZG9lcyBsb3RzIG9mIG90aGVyIHN0dWZmIHRvby4NCg0KVGhlcmUgaXMgYSBoYW5keSBndWlkZSBmb3IgYWxsIHRoZSBkcGx5ciBkYXRhIHRvb2xzIGF0IFtSIFN0dWRpbydzIGRwbHlyIGNoZWF0IHNoZWV0XShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcnN0dWRpby9jaGVhdHNoZWV0cy9tYWluL2RhdGEtdHJhbnNmb3JtYXRpb24ucGRmKS4NCg0KIyMgVXNpbmcgdGhlICJQaXBlIg0KDQoqVmVyeSBpbXBvcnRhbnQqLCB0byB3aGF0IEknbSBhYm91dCB0byBzaG93IHlvdSBmcm9tIGhlcmUgb24gb3V0IGlzIGEgdGhpbmd5IGNhbGxlZCBhICJwaXBlIiAtIGAlPiVgIC0gd2hpY2ggaXMgYW4gb2JqZWN0IHlvdSBwdXQgaW4geW91ciBjb2RlIHRvIGNoYWluIGZ1bmN0aW9ucyB0b2dldGhlciBhbmQgbWFrZSBraW5kIG9mIGRhdGEtd3JhbmdsaW5nIHJlY2lwZXMuDQoNClRvIHVuZGVyc3RhbmQgd2hhdCB0aGUgcGlwZSBkb2VzLCB0aGluayBhYm91dCB0aGUgZm9sbG93aW5nICJyZWNpcGUiIGxpa2UgdGhpczogSSBtYWtlIGFuIGBvbWxldHRlYCBieSB0YWtpbmcgYSBkYXRhIGZyYW1lIGNhbGxlZCBgZWdnc2AgYW5kIGFwcGx5aW5nIHRoZSBmdW5jdGlvbnMgYHNjcmFtYmxlYCBhbmQgYGZyeWANCg0KYG9tbGV0dGUgPC0gZWdncyAlPiUgc2NyYW1ibGUoKSAlPiUgZnJ5KClgDQoNCk9LLCBrZWVwIHRoYXQgaW4gbWluZCB3aGlsZSB3ZSBsb29rIGF0IGBkcGx5cmAgY29tbWFuZHMuDQoNCiMjIERwbHlyIGNvbW1hbmRzDQoNCkxldCdzIHVzZSBhIGZldyBiYXNpYyBgZHBseXJgIGNvbW1hbmRzIHRvIHdyYW5nbGUgb3VyIGRhdGFmcmFtZSBgbXlEYXRhYC4NCg0KIyMjIE11dGF0ZQ0KDQpgbXV0YXRlYCBtYWtlcyBhIG5ldyBjb2x1bW4uIEkgYW0gZ29pbmcgdG8gKm92ZXJ3cml0ZSogYG15RGF0YWAgLSBtYWtpbmcgYSBuZXcgdmVyc2lvbiB0aGF0IGhhcyBhIG5ldyBjb2x1bW4gaW4gaXQuIEVhY2ggcm93IG9mIHRoZSBkYXRhIGZyYW1lIHJlcHJlc2VudHMgYSBjZW5zdXMgdHJhY3QsIGFuZCBJJ2QgbGlrZSB0byBzZWUgd2hhdCB0aGUgZGlmZmVyZW5jZSBpcyBpbiB0cmFjdCBwb3B1bGF0aW9uIGJldHdlZW4gMjAxMCBhbmQgMjAxNi4gSSdsbCBjYWxsIHRoaXMgbmV3IGNvbHVtbiBgcG9wX2NoYW5nZWAgYW5kIHNldCBpdHMgdmFsdWUgZXF1YWwgdG8gYHRvdGFsX3BvcC4yMDE2YCBtaW51cyBgdG90YWxfcG9wLjIwMTBgLiBOb3RlIHRoZSBwaXBlIHVzZWQgdG8gImNoYWluIiB0aGUgbXV0YXRlIHN0YXRlbWVudCBpbiB0aGVyZS4NCg0KYGBge3IgbXV0YXRlX2ZpcnN0fQ0KDQpteURhdGEgPC0gbXlEYXRhICU+JQ0KICBtdXRhdGUocG9wX2NoYW5nZSA9IHRvdGFsX3BvcC4yMDE2IC0gdG90YWxfcG9wLjIwMTApDQoNCmBgYA0KDQpEaWQgaXQgd29yaz8gVHJ5IHR5cGluZyBgVmlldyhteURhdGEpYCBvciBgZ2xpbXBzZShteURhdGEpYCBpbnRvIHlvdXIgY29uc29sZS4NCg0KSSdtIG5vdyBnb2luZyB0byBjcmVhdGUgYSBzZWNvbmQgdmFyaWFibGUgY2FsbGVkIGBwb3BfY2hhbmdlX3Bvc2l0aXZlYCB1c2luZyBgbXV0YXRlYC4gSWYgdGhlIHBvcHVsYXRpb24gY2hhbmdlIHdhcyBwb3NpdGl2ZSwgSSdtIGdvaW5nIHRvIGNhbGwgc2V0IHRoaXMgdmFyaWFibGUgZXF1YWwgdG8gYFRSVUVgIC0gSSdsbCBkbyBhbiBgaWZlbHNlYCBzdGF0ZW1lbnQgdG8gbWFrZSBpdCdzIHZhbHVlIGNvbnRpbmdlbnQgb24gdGhlIHZhbHVlIG9mIGBwb3BfY2hhbmdlYC4NCg0KYGBge3IgbXV0YXRlX3NlY29uZH0NCg0KbXlEYXRhIDwtIG15RGF0YSAlPiUNCiAgbXV0YXRlKHBvcF9jaGFuZ2VfcG9zaXRpdmUgPSBpZmVsc2UocG9wX2NoYW5nZSA+IDAsIFRSVUUsIEZBTFNFKSkNCg0KYGBgDQoNCiMjIyBGaWx0ZXINCg0KYGZpbHRlcmAgbGV0cyB5b3UgcmVkdWNlIHlvdXIgZGF0YSBmcmFtZSBiYXNlZCBvbiBzb21lIGNyaXRlcmlhLiBJJ2QgbGlrZSB0byBjcmVhdGUgYSBuZXcgZGF0YSBmcmFtZSBjYWxsZWQgYHBvcHVsYXRpb25fbG9zc2AgdGhhdCBjb25zaXN0cyBvbmx5IG9mIGNlbnN1cyB0cmFjdHMgaW4gYG15RGF0YWAgdGhhdCBsb3N0IHBvcHVsYXRpb24gKGUuZy4gcG9wX2NoYW5nZV9wb3NpdGl2ZSA9PSBGQUxTRSkuDQoNCg0KYGBge3IgZmlsdGVyfQ0KcG9wdWxhdGlvbl9sb3NzIDwtIG15RGF0YSAlPiUNCiAgZmlsdGVyKHBvcF9jaGFuZ2VfcG9zaXRpdmUgPT0gRkFMU0UpDQpgYGANCg0KSSBjYW4gZG8gdGhpcyBraW5kIG9mIGZpbHRlcmluZyBiYXNlZCBvbiBsb3RzIG9mIGNyaXRlcmlhLg0KDQojIyMgU2VsZWN0DQoNCmBzZWxlY3RgIGtlZXBzIG9ubHkgY2VydGFpbiBjb2x1bW5zIHRoYXQgeW91IHdhbnQgdG8ga2VlcC4NCg0KSSdtIGdvaW5nIHRvIG1ha2UgYSBuZXcgZGF0YSBmcmFtZSwgY2FsbGVkIGB0d29WYXJpYWJsZXNgIHRoYXQgY29uc2lzdHMgb2Ygb25seSB0aGUgY29sdW1ucyBgTkFNRS55YCBhbmQgYEdFT0lEYCBmcm9tIHRoZSBkYXRhIGZyYW1lIGBteURhdGFgDQoNCmBgYHtyIHNlbGVjdH0NCnR3b1ZhcmlhYmxlcyA8LSBteURhdGEgJT4lDQogIHNlbGVjdChOQU1FLnksIEdFT0lEKQ0KYGBgDQoNCiMjIyBSZW5hbWUNCg0KYHJlbmFtZWAgY2hhbmdlcyB0aGUgbmFtZXMgb2YgeW91ciBjb2x1bW5zLiBJJ2QgbGlrZSB0byByZW5hbWUgdGhlIGNvbHVtbiBmcm9tIHRoZSBkYXRhIGZyYW1lIGB0d29WYXJpYWJsZXNgIGNhbGxlZCBgTkFNRS55YCB0byBiZSBjYWxsZWQganVzdCBgTkFNRWANCg0KYGBge3IgcmVuYW1lfQ0KdHdvVmFyaWFibGVzIDwtIHR3b1ZhcmlhYmxlcyAlPiUNCiAgcmVuYW1lKE5BTUUgPSBOQU1FLnkpDQpgYGANCg0KIyMgU3VtbWFyaXppbmcgRGF0YQ0KDQpZb3UnbGwgb2Z0ZW4gd2FudCB0byBrbm93IGFib3V0IHRoZSBjaGFyYWN0ZXJpc3RpY3Mgb3IgY2VudHJhbCB0ZW5kZW5jaWVzIG9mIHlvdXIgZGF0YS4gVXNpbmcgdGhlIGBncm91cF9ieWAgYW5kIGBzdW1tYXJpemVgIGNvbW1hbmRzIHlvdSBjYW4gZG8gbG90cyBvZiB0aGlzLg0KDQpIZXJlIGFyZSBzb21lIGV4YW1wbGVzIC0gbm90aWNlIEknbSBub3QgY3JlYXRpbmcgbmV3IGRhdGEgZnJhbWVzIGhlcmUuDQoNCkZpcnN0LCBsZXQncyBqdXN0IGZpbmQgb3V0IHdoYXQgdGhlIG1lZGlhbiBgcG9wX2NoYW5nZWAgd2FzIGJldHdlZW4gMjAxMCBhbmQgMjAxNiBmb3IgUGhpbGFkZWxwaGlhIGNlbnN1cyB0cmFjdHMuDQoNCmBgYHtyIHN1bW1hcml6ZV8xfQ0KbXlEYXRhICU+JQ0KICBzdW1tYXJpemUobWVkaWFuX3BvcF9jaGFuZ2UgPSBtZWRpYW4ocG9wX2NoYW5nZSkpDQpgYGANCg0KV2hhdCBpZiB3ZSB3YW50IHRvIGtub3cgaG93IG9uZSB0eXBlIG9mIHRyYWN0IHZlcnN1cyBhbm90aGVyIHZhcmllZCBpbiB0ZXJtcyBvZiBgcG9wX2NoYW5nZWA/IFdlIGNhbiBncm91cCBvdXIgZGF0YSBpbnRvIGNhdGVnb3JpZXMgdXNpbmcgYGdyb3VwX2J5YCBhbmQgdGhlbiBzdW1tYXJpemUuDQoNCkZvciB0cmFjdHMgZm9yIHdoaWNoIGBwb3BfY2hhbmdlX3Bvc2l0aXZlYCAocmVtZW1iZXIgd2UgY3JlYXRlZCB0aGlzIGNvbHVtbiBhcyBhIFRSVUUvRkFMU0UgLSB3YXMgdGhlcmUgcG9wdWxhdGlvbiBjaGFuZ2UgYmV0d2VlbiAyMDEwIGFuZCAyMDE2Pykgd2hhdCBpcyB0aGUgbWVkaWFuIGBwb3BfY2hhbmdlYD8NCg0KYGBge3Igc3VtbWFyaXplXzJ9DQpteURhdGEgJT4lDQogIGdyb3VwX2J5KHBvcF9jaGFuZ2VfcG9zaXRpdmUpICU+JQ0KICBzdW1tYXJpemUobWVkaWFuX3BvcF9jaGFuZ2UgPSBtZWRpYW4ocG9wX2NoYW5nZSkpDQpgYGANCg0KIyMgSm9pbmluZyBEYXRhDQoNCllvdSBjYW4gZG8gInRhYnVsYXIgam9pbnMiIGJldHdlZW4gZGF0YSBzZXRzIHdoZW4gdGhleSBoYXZlIGEgInVuaXF1ZSBJRCIgLSBhIG51bWJlciBvciBjb2RlIHRoYXQgaWRlbnRpZmllcyBlYWNoIG9ic2VydmF0aW9uIG9yIHN1YmplY3QgaW4geW91ciBkYXRhLiBGb3IgZXhhbXBsZSwgdGhlICJHRU9JRCIgaXMgdGhlIHVuaXF1ZSBjb2RlIGFzc29jaWF0ZWQgd2l0aCBlYWNoIGNlbnN1cyB0cmFjdC4NCg0KVGhlcmUgYXJlIHNldmVyYWwgdHlwZXMgb2Ygam9pbnMgeW91IGNhbiB1c2UgaW4gUi4gSGFkbGV5IFdpY2toYW0ncyAiUiBGb3IgRGF0YSBTY2llbmNlIiBbaGFzIGEgZ3JlYXQgY2hhcHRlciBhbmQgc2VyaWVzIG9mIGNvZGUgZXhhbXBsZXNdKGh0dHBzOi8vcjRkcy5oYWQuY28ubnovcmVsYXRpb25hbC1kYXRhLmh0bWwpIG9uIHRoZSBtZWNoYW5pY3Mgb2YgbGVmdCwgcmlnaHQsIGlubmVyLCBvdXRlciwgZnVsbCwgYW5kIG90aGVyIGpvaW5zLg0KDQpUaGUgYmFzaWMgbWVjaGFuaWNzIG9mIGEgam9pbiBnbyBsaWtlIHRoaXM6DQoNCmBuZXdfZGF0YWZyYW1lIDwtIGxlZnRfam9pbihkYXRhZnJhbWUxLCBkYXRhZnJhbWUyLCBieSA9IGMoInVuaXF1ZV9JRCIpKWANCg0KVGhpcyB3aWxsIGdldCB5b3UgYSBkYXRhIHNldCB3aGVyZSB5b3UgcHJlc2VydmUgYWxsIG9mIGBkYXRhZnJhbWUxYCAodGhlICJsZWZ0IiBzaWRlIG9mIHRoZSBsZWZ0IGpvaW4pLCBhbmQgdGFjayBvbiBhbnl0aGluZyBmcm9tIGBkYXRhZnJhbWUyYCB0aGF0IG1hdGNoZXMgYHVuaXF1ZV9JRGAgZm91bmQgaW4gYGRhdGFmcmFtZTFgDQoNCk5vdGljZSB0aGUgYGJ5YCBjYWxsIHdoZXJlIHlvdSBzcGVjaWZ5IHRoZSB1bmlxdWUgSUQsIHRoYXQncyBhIHdlaXJkIGJpdCBvZiBzeW50YXguIFlvdSBjYW4gam9pbiBvbiBtdWx0aXBsZSBVbmlxdWUgSURzIGlmIHlvdSB3YW50Lg0KDQojIyBXaWRlIERhdGEgYW5kIExvbmcgRGF0YQ0KDQpPbmUgY29uY2VwdCB0aGF0IGlzIGltcG9ydGFudCBidXQgYSBiaXQgaGFyZCB0byBlbmdhZ2UgaXMgZGF0YSBmb3JtYXQuIFRoZSBkYXRhZnJhbWUgYG15RGF0YWAgdGhhdCB3ZSBoYXZlIGJlZW4gd29ya2luZyB3aXRoIHNvIGZhciBpcyB3aGF0J3MgY2FsbGVkIGEgIndpZGUiIGRhdGEgc2V0LiBFYWNoIHJvdyBpcyBhIGNlbnN1cyB0cmFjdCwgZWFjaCBjb2x1bW4gaXMgYSB2YXJpYWJsZS4gVGhpcyBpcyBncmVhdCBmb3IgZG9pbmcgY29sdW1uIG1hdGggd2l0aCBgbXV0YXRlYCBhbmQgaXQncyBnb29kIGZvciBtYWtpbmcgY2VydGFpbiBraW5kcyBvZiBwbG90cyAtIGZvciBpbnN0YW5jZSBhIHNjYXR0ZXIgcGxvdCB3aXRoIHR3byBjb250aW51b3VzIHZhcmlhYmxlcyAobW9yZSBvbiB0aGlzIGluIHRoZSBuZXh0IHNlY3Rpb24pLg0KDQpUYWtlIGEgbG9vayBhdCBgbXlEYXRhYCB1c2luZyB0aGUgY29tbWFuZCBgVmlldyhteURhdGEpYCBhbmQgeW91J2xsIHNlZSB0aGF0IGl0IGlzIGluZGVlZCAid2lkZSIgZm9ybWF0Lg0KDQpUaGVyZSBpcyBhbm90aGVyIGZvcm0gb2YgZGF0YSBjYWxsZWQgImxvbmciIGRhdGEgdGhhdCBhcmUgdXNlZnVsIGZvciBvdGhlciBjaXJjdW1zdGFuY2VzLCBsaWtlIG1ha2luZyBjb21wYXJpc29ucyBiZXR3ZWVuIGNvbnRpbnVvdXMgdmFyaWFibGVzIGFjcm9zcyBjYXRlZ29yaWVzLiAoTm90ZTogT2Z0ZW4geW91IGZpbmQgdGltZS1zZXJpZXMgZGF0YSBpbiBsb25nIGZvcm1hdCkuDQoNCkxldCdzIGV4YW1pbmUgaW5kaWNhdG9ycyBmcm9tIDIwMTAgb2YgdHJhY3RzIHRoYXQgYXBwcmVjaWF0ZWQgaW4gcG9wdWxhdGlvbiBiZXR3ZWVuIDIwMTAgYW5kIDIwMTY6DQoNCi0gTWVkaWFuIGhvdXNlaG9sZCBpbmNvbWUNCi0gTWVkaWFuIG51bWJlciBvZiBwZW9wbGUgaWRlbnRpZnlpbmcgdGhlaXIgcmFjaWFsIGJhY2tncm91bmQgYXMgIndoaXRlIGFsb25lIg0KLSBNZWRpYW4gbnVtYmVyIG9mIGhvdXNpbmcgdW5pdHMNCg0KV2UgYGdhdGhlcmAgdGhvc2UgdmFyaWFibGVzLCBhbmQgbWFrZSBhIGRhdGEgZnJhbWUgd2hlcmUgZWFjaCByb3cgaXMgYSB0cmFjdCwgaXQncyBgcG9wX2NoYW5nZV9wb3NpdHZlYCB2YWx1ZSAoVFJVRS9GQUxTRSksIGEgdmFyaWFibGUgbmFtZSwgYW5kIGEgdmFsdWUgZm9yIHRoYXQgdmFyaWFibGUuDQoNClRoZW4gd2UgY2FuIGBncm91cF9ieWAgdGhlIHBvcHVsYXRpb24gY2hhbmdlIGRpcmVjdGlvbiAoVFJVRSAtIHBvc2l0aXZlKSBhbmQgdGhlIGB2YXJpYWJsZWAsIGFuZCBzdW1tYXJpemUgdGhlIGBtZWRpYW5fdmFsdWVgIGZvciBlYWNoIHZhcmlhYmxlLg0KDQpgYGB7ciBnYXRoZXJfZXhhbXBsZSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9DQpteURhdGEgJT4lIA0KICBzZWxlY3QocG9wX2NoYW5nZV9wb3NpdGl2ZSwgbWVkX0hIX0luY29tZS4yMDEwLCAgdG90YWxfV2hpdGUuMjAxMCwgdG90YWxfSFUuMjAxMCkgJT4lIA0KICBnYXRoZXIoLXBvcF9jaGFuZ2VfcG9zaXRpdmUsIGtleSA9ICJ2YXJpYWJsZSIsIHZhbHVlID0gInZhbHVlIikgJT4lIA0KICBncm91cF9ieShwb3BfY2hhbmdlX3Bvc2l0aXZlLCB2YXJpYWJsZSkgJT4lDQogIHN1bW1hcml6ZShtZWRpYW5fdmFsdWUgPSBtZWRpYW4odmFsdWUsIG5hLnJtID0gVFJVRSkpDQoNCmBgYA0KDQoqTm90ZSogdGhlcmUgaXMgYSBtb3JlIG1vZGVybiB2ZXJzaW9uIG9mIGBnYXRoZXJgIGtub3duIGFzIGBwaXZvdF9sb25nZXJgLCB3aGVyZSB0aGUgc3ludGF4IHdvdWxkIGxvb2sgbGlrZSB0aGlzOg0KDQpgYGB7ciBwaXZvdF9sb25nZXIsIGV2YWw9RkFMU0V9DQpteURhdGEgJT4lIA0KICBzZWxlY3QocG9wX2NoYW5nZV9wb3NpdGl2ZSwgbWVkX0hIX0luY29tZS4yMDEwLCAgdG90YWxfV2hpdGUuMjAxMCwgdG90YWxfSFUuMjAxMCkgJT4lIA0KICBwaXZvdF9sb25nZXIoY29scyA9IC1wb3BfY2hhbmdlX3Bvc2l0aXZlKSAlPiUNCiAgZ3JvdXBfYnkocG9wX2NoYW5nZV9wb3NpdGl2ZSwgbmFtZSkgJT4lDQogIHN1bW1hcml6ZShtZWRpYW5fdmFsdWUgPSBtZWRpYW4odmFsdWUsIG5hLnJtID0gVFJVRSkpIA0KICAgIA0KYGBgDQoNCndpZGUgYW5kIGxvbmcgZGF0YSBhcmUgYSB3ZWlyZCBjb25jZXB0IC0gdGhpcyBncmFwaGljIGlzIHByZXR0eSBoZWxwZnVsIHVuZGVyc3RhbmRpbmcgaG93IGRhdGEgY2FuIHRyYW5zZm9ybSBmcm9tIG9uZSBmb3JtIHRvIGFub3RoZXIuDQoNCipDYW4geW91IHRoaW5rIG9mIGEgZmV3IHNpdHVhdGlvbnMgaW4gd2hpY2ggb25lIGZvcm0gb3IgYW5vdGhlciBpcyBhcHByb3ByaWF0ZT8qDQoNCiFbUGl2b3RpbmcgYmV0d2VlbiB3aWRlIGFuZCBsb25nIGRhdGFdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9tYWZpY2htYW4vUl9GQVFfRm9yX1BsYW5uZXJzLzc3YTU1YTUxZDcyMDllNGI2ZjZiMzlhNzMxY2VkMGE2NTNhMGUxNzgvaW1hZ2VzL3RpZHlyLXNwcmVhZC1nYXRoZXIuZ2lmKQ0KSW1hZ2U6IGh0dHBzOi8vd3d3LmdhcnJpY2thZGVuYnVpZS5jb20vDQoNClRoZSB3aWRlL2xvbmcgZGF0YSBjb25jZXB0IGlzIGEgYml0IG5lYnVsb3VzLCBidXQgaXQgcmVhbGx5IGNvbWVzIGluIGhhbmR5IHdoZW4geW91IGFyZSBtYWtpbmcgZmFjZXR0ZWQgZ3JhcGhpY3MgLSBhIHN1YmplY3QgY292ZXJlZCBpbiB0aGUgbmV4dCBzZWN0aW9uLg0KDQojIDcuIE1ha2luZyBQbG90cyB3aXRoIGdncGxvdDINCg0KYGdncGxvdDJgIGlzIHRoZSBtYWluIGdyYXBoaWNzIGxpYnJhcnkgaW4gdGhlIGB0aWR5dmVyc2VgIC0gaXQncyBmYW50YXN0aWMuIFlvdSBjYW4gbWFrZSBiYXIgcGxvdHMsIGxpbmUgcGxvdHMsIGFuZCBtdWNoIG1vcmUgd2l0aCBpdC4NCg0KWW91IG1ha2UgZ2dwbG90cyBieSB3cml0aW5nICJyZWNpcGVzIiBmb3IgY2hhcnRzLiBUaGUgYCtgIG9wZXJhdG9yIGlzIGEgYml0IGxpa2Ugd2hhdCB0aGUgYCU+JWAgaXMgaW4gZHBseXIvdGlkeXZlcnNlIC0geW91IHVzZSBpdCB0byBhZGQgZWxlbWVudHMgdG9nZXRoZXIuDQoNClRoZSBjb3JlIGVsZW1lbnRzIG9mIGdncGxvdCByZWNpcGVzIGFyZSBjYWxsZWQgZ2VvbWV0cmllcyAoYSBgZ2VvbWApLCBhbmQgdGhlIHdheSB5b3Ugc3R5bGUgdGhlIGRhdGEgaXMgd2l0aCBhZXN0aGV0aWNzIChgYWVzYCkuIFlvdSBjYW4gYWRkIHNvbWUgb3RoZXIgc3R5bGVzIHRvIHlvdXIgZ2dwbG90cywgYnV0IGlmIGl0IGludm9sdmVzIGNoYW5naW5nIHNvbWV0aGluZyBhYm91dCB0aGUgYXBwZWFyYW5jZSBvZiB0aGUgcGxvdCBiYXNlZCBvbiBkYXRhLCB5b3UgdXNlIGFuIGBhZXNgIGNhbGwuDQoNClRoZXJlIGFyZSBsb3RzIG9mIHJlc291cmNlcyBmb3IgZ2dwbG90IC0gdGhlIHNpbXBsZXN0IGFuZCBlYXNpZXN0IG9uZSB0byBjaGVjayBvdXQgaXMgW1IgU3R1ZGlvJ3MgZ2dwbG90IGNoZWF0IHNoZWV0XShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcnN0dWRpby9jaGVhdHNoZWV0cy9tYWluL2RhdGEtdmlzdWFsaXphdGlvbi5wZGYpLiBUaGVyZSBpcyBhbHNvIGEgd2hvbGUgKGZyZWUpIGJvb2sgb24gZGF0YSB2aXogaW4gZ2dwbG90IC0gW0RhdGEgVmlzdWFsaXphdGlvbiBieSBLaWVyYW4gSGVhbHldKGh0dHBzOi8vc29jdml6LmNvLykuIEl0J3MgZmFudGFzdGljLg0KDQojIyBNYWtpbmcgYSBiYXNpYyBwbG90DQoNCkxldCdzIG1ha2UgYSBzaW1wbGUgc2NhdHRlcnBsb3QuDQoNClRoaW5rIGFib3V0IHRoZSAicmVjaXBlIiBmb3IgdGhlIGZvbGxvd2luZyBjaGFydCB3aGVyZSB3ZSBzZWUgcGxvdCBQaGlsYWRlbHBoaWEgY2Vuc3VzIHRyYWN0IG1lZGlhbiBob3VzZWhvbGQgaW5jb21lIGFzIGEgZnVuY3Rpb24gb2YgdGhlIHRyYWN0J3MgcGVuY2VudGFnZSBvZiB2YWNhbnQgaG91c2luZyB1bml0cyB1c2luZyB0aGlzIG5hcnJhdGl2ZSAocmVtZW1iZXIgZWFjaCByb3cgaW4gbXlEYXRhIGlzIGEgUGhpbGFkZWxwaGlhIGNlbnN1cyB0cmFjdCk6DQoNCi0gTWFrZSBtZSBhIGdncGxvdA0KLSBBZGQgYSBwb2ludCBnZW9tZXRyeSBmcm9tIHRoZSBkYXRhIHNldCBgbXlEYXRhYCB3aGVyZSB0aGUgeCBhZXN0aGV0aWMgaXMgYHZhY2FuY3lQY3QuMjAxNmAgYW5kIHRoZSB5IGFlc3RoZXRpYyBpcyBgbWVkX0hIX0luY29tZS4yMDE2YA0KDQpgYGB7ciBnZ3Bsb3QxLCB3YXJuaW5nID0gRkFMU0V9DQpnZ3Bsb3QoKSsNCiAgZ2VvbV9wb2ludChkYXRhID0gbXlEYXRhLCANCiAgICAgICAgICAgICBhZXMoeCA9IHZhY2FuY3lQY3QuMjAxNiwgeSA9IG1lZF9ISF9JbmNvbWUuMjAxNikpDQpgYGANCg0KIyMgQWRkaW5nIG1vcmUgYWVzdGhldGljcw0KDQpXZSBjYW4gYWRkIG1vcmUgYWVzdGhldGljcyB0byB0aGlzIHBsb3QuIExldCdzIGRvIHRoaXMgZmlyc3QgYnkganVzdCB0aW5rZXJpbmcgd2l0aCB0aGUgY29sb3JzIHRvIG1ha2UgaXQgbG9vayBjb29sIC0gd2Ugd2lsbCBkbyB0aGlzIHdpdGggc29tZSBjb21tYW5kcyAqb3V0c2lkZSogdGhlIGFlc3RoZXRpY3MgdG8gbWFrZSB0aGUgcG9pbnRzIGJsdWUuDQoNCmBgYHtyIGdncGxvdDIsIHdhcm5pbmcgPSBGQUxTRX0NCmdncGxvdCgpKw0KICBnZW9tX3BvaW50KGRhdGEgPSBteURhdGEsIA0KICAgICAgICAgICAgIGFlcyh4ID0gdmFjYW5jeVBjdC4yMDE2LCB5ID0gbWVkX0hIX0luY29tZS4yMDE2KSwNCiAgICAgICAgICAgICBjb2xvciA9ICJibHVlIikNCmBgYA0KDQpPSywgbm93IGxldCdzIG1ha2UgdGhlIHBvaW50IGNvbG9yYXRpb24gY2hhbmdlIGFjY29yZGluZyB0byBhIGRhdGEtcmVsYXRlZCBlbGVtZW50LCBzb21ldGhpbmcgd2UgaGF2ZSB0byBzcGVjaWZ5IGluIHRoZSBhZXN0aGV0aWNzLiBXZSBzcGVjaWZ5IHRoZSBjb2xvciBhcyBiZWluZyBlcXVhbCB0byBgcG9wX2NoYW5nZV9wb3NpdGl2ZWA6DQoNCmBgYHtyIGdncGxvdDMsIHdhcm5pbmcgPSBGQUxTRX0NCmdncGxvdCgpKw0KICBnZW9tX3BvaW50KGRhdGEgPSBteURhdGEsIA0KICAgICAgICAgICAgIGFlcyh4ID0gdmFjYW5jeVBjdC4yMDE2LCB5ID0gbWVkX0hIX0luY29tZS4yMDE2LCBjb2xvciA9IHBvcF9jaGFuZ2VfcG9zaXRpdmUpKQ0KYGBgDQoNCiMjIEZhY2V0cw0KDQpZb3UgY2FuIHZlcnkgc2ltcGx5IG1ha2UgInNtYWxsIG11bHRpcGxlIiBwbG90cyB3aGVyZSB5b3Ugc3ViZGl2aWRlIHlvdXIgZGF0YSBpbnRvIGNhdGVnb3JpZXMgZm9yIGdyYXBoaWMgY29tcGFyaXNvbi4gVGhpcyBpcyBkb25lIHdpdGggdGhlIGBmYWNldF93cmFwYCBjb21tYW5kLiANCg0KSGVyZSdzIGFuIGFsdGVybmF0ZSB0YWtlIG9uIG91ciBwcmV2aW91cyBwbG90LCBjdXQgaW50byB0d28gcGxvdHMuIE5vdGljZSBpbiB0aGUgY29kZSBJIHB1bGxlZCBgcG9wX2NoYW5nZV9wb3NpdGl2ZWAgb3V0IG9mIHRoZSBhZXN0aGV0aWNzIGZyb20gdGhlIHByZXZpb3VzIHBsb3QgYW5kIHB1dCBpdCBpbiB0aGUgYGZhY2V0X3dyYXBgIGNvbW1hbmQuDQoNCmBgYHtyIGdncGxvdDQsIHdhcm5pbmcgPSBGQUxTRX0NCmdncGxvdCgpKw0KICBnZW9tX3BvaW50KGRhdGEgPSBteURhdGEsIA0KICAgICAgICAgICAgIGFlcyh4ID0gdmFjYW5jeVBjdC4yMDE2LCB5ID0gbWVkX0hIX0luY29tZS4yMDE2KSkrDQogIGZhY2V0X3dyYXAofnBvcF9jaGFuZ2VfcG9zaXRpdmUpDQpgYGANCg0KUmVtZW1iZXIgaG93IHdlIHVzZWQgYGdhdGhlcmAgYW5kIGBwaXZvdF9sb25nZXJgIHRvIG1ha2Ugb3VyIHdpZGUgZGF0YSBpbnRvIGxvbmcgZGF0YSBpbiB0aGUgbGFzdCBzZWN0aW9uPyBUaGF0J3MgYW4gaW5jcmVkaWJseSB1c2VmdWwgdGVjaG5pcXVlIGZvciBtYWtpbmcgcXVpY2sgZGF0YS1taW5pbmcgdmlzdWFsaXphdGlvbnMgYWNyb3NzIGEgbnVtYmVyIG9mIHZhcmlhYmxlcy4NCg0KTm90aWNlIGhvdyBiZWxvdywgSSBkbyB0aGUgc2FtZSBkYXRhIG1hbmlwdWxhdGlvbiBJIGRpZCBpbiB0aGUgcHJldmlvdXMgc2VjdGlvbiwgYW5kIHRoZW4gSSBqdXN0IHBpcGUgaW4gdGhlIGdncGxvdCBjYWxsLCBhbmQgSSB1c2UgYW4gYXJndW1lbnQgaW4gdGhlIGBmYWNldF93cmFwYCBjYWxsZWQgYHNjYWxlc2Agc28gdGhhdCBlYWNoIGNoYXJ0IGhhcyBhIHktYXhpcyB3aXRoIGFuIGFwcHJvcHJpYXRlIHNjYWxlIHRvIHRoYXQgdmFyaWFibGUuDQoNCmBgYHtyIGdncGxvdF9sb25nLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0NCm15RGF0YSAlPiUgDQogIHNlbGVjdChwb3BfY2hhbmdlX3Bvc2l0aXZlLCBtZWRfSEhfSW5jb21lLjIwMTAsICB0b3RhbF9XaGl0ZS4yMDEwLCB0b3RhbF9IVS4yMDEwKSAlPiUgDQogIHBpdm90X2xvbmdlcihjb2xzID0gLXBvcF9jaGFuZ2VfcG9zaXRpdmUpICU+JQ0KICBncm91cF9ieShwb3BfY2hhbmdlX3Bvc2l0aXZlLCBuYW1lKSAlPiUNCiAgc3VtbWFyaXplKG1lZGlhbl92YWx1ZSA9IG1lZGlhbih2YWx1ZSwgbmEucm0gPSBUUlVFKSkgJT4lDQpnZ3Bsb3QoKSsNCiAgZ2VvbV9iYXIoYWVzKHggPSBwb3BfY2hhbmdlX3Bvc2l0aXZlLCB5ID0gbWVkaWFuX3ZhbHVlKSwgc3RhdCA9ICJpZGVudGl0eSIpKw0KICBmYWNldF93cmFwKH5uYW1lLCBzY2FsZXMgPSAiZnJlZSIpDQpgYGANCg0KIyMgQWR2YW5jZWQgZ3JhcGhpY3MNCg0KWW91IGNhbiBhbHRlciBtb3N0IGVsZW1lbnRzIG9mIHRoZSBnZ3Bsb3QuIFdlIGNvdWxkIHRha2Ugb3VyIGluaXRpYWwgc2NhdHRlcnBsb3QgYW5kIGRvIHNvbWUgdGhpbmdzIHRvIHNwcnVjZSBpdCB1cCwgbGlrZSBhZGQgYSB0aXRsZSwgY2hhbmdlIHRoZSBheGlzIGxhYmVscyBldGMuLA0KDQpgYGB7ciBnZ3Bsb3Q1LCB3YXJuaW5nID0gRkFMU0V9DQpnZ3Bsb3QoKSsNCiAgZ2VvbV9wb2ludChkYXRhID0gbXlEYXRhLCANCiAgICAgICAgICAgICBhZXMoeCA9IHZhY2FuY3lQY3QuMjAxNiwgeSA9IG1lZF9ISF9JbmNvbWUuMjAxNikpKw0KICBsYWJzKHRpdGxlPSJQaGlsYWRlbHBoaWEgVHJhY3QgTWVkLiBISCBJbmNvbWUgYXMgYSBGdW5jdGlvbiBvZiBQY3QuIFZhY2FuY3kiLA0KICAgICAgIHN1YnRpdGxlID0gIkRhdGE6IFVTIENlbnN1cyBCdXJlYXUsIDIwMTYgQUNTIDUtWWVhciBFc3RpbWF0ZXMiLA0KICAgICAgICB4ID0iUGN0IFZhY2FudCBIb3VzaW5nIFVuaXRzICgwLTEpIiwgeSA9ICJNZWRpYW4gSG91c2Vob2xkIEluY29tZSAoMjAxNiAkKSIpDQpgYGANCg0KSSBsaWtlIHRvIG1ha2Ugc29tZSBwcmUtbWFkZSByZWNpcGVzIHdoZXJlIEkgc3R5bGUgdGhlIGxpbmVzLCBmb250cyBhbmQgb3RoZXIgZWxlbWVudHMsIGFuZCB0aGVuIEkgY2FuIGp1c3QgYWRkIGl0IHRvIG15IHBsb3RzLiBIZXJlJ3MgYSByZWNpcGUgSSBjYWxsIGBwbG90VGhlbWVgLiBMb2FkIGl0IHVwIGludG8geW91ciBlbnZpcm9ubWVudCBhbmQgdGhlbiB5b3UgY2FuIGFkZCBpdCB0byB5b3VyIHBsb3RzIHRvIG1ha2UgdGhlbSBuaWNlci4gVGhlIHNreSBpcyB0aGUgbGltaXQuDQoNCmBgYHtyIHBsb3RUaGVtZX0NCg0KcGxvdFRoZW1lIDwtIHRoZW1lKA0KICBwbG90LnRpdGxlID1lbGVtZW50X3RleHQoc2l6ZT0xMiksDQogIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT04KSwNCiAgcGxvdC5jYXB0aW9uID0gZWxlbWVudF90ZXh0KHNpemUgPSA2KSwNCiAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLA0KICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLA0KICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgIyBTZXQgdGhlIGVudGlyZSBjaGFydCByZWdpb24gdG8gYmxhbmsNCiAgcGFuZWwuYmFja2dyb3VuZD1lbGVtZW50X2JsYW5rKCksDQogIHBsb3QuYmFja2dyb3VuZD1lbGVtZW50X2JsYW5rKCksDQogICNwYW5lbC5ib3JkZXI9ZWxlbWVudF9yZWN0KGNvbG91cj0iI0YwRjBGMCIpLA0KICAjIEZvcm1hdCB0aGUgZ3JpZA0KICBwYW5lbC5ncmlkLm1ham9yPWVsZW1lbnRfbGluZShjb2xvdXI9IiNEMEQwRDAiLHNpemU9Ljc1KSwNCiAgYXhpcy50aWNrcz1lbGVtZW50X2JsYW5rKCkpDQpgYGANCg0KTm93IEkgY2FuIGFkZCBgcGxvdFRoZW1lYCB0byBteSByZWNpcGUgZnJvbSB0aGUgbGFzdCBwbG90IGZvciBzb21lIGV4dHJhIHN0eWxpbmcuIE5vdGljZSBJIGp1c3QgdGFjayBpdCBvbnRvIHRoZSByZWNpcGUgYmVsb3cgd2l0aCBhIGArYDoNCg0KYGBge3IgZ2dwbG90Niwgd2FybmluZyA9IEZBTFNFfQ0KZ2dwbG90KCkrDQogIGdlb21fcG9pbnQoZGF0YSA9IG15RGF0YSwgDQogICAgICAgICAgICAgYWVzKHggPSB2YWNhbmN5UGN0LjIwMTYsIHkgPSBtZWRfSEhfSW5jb21lLjIwMTYpKSsNCiAgbGFicyh0aXRsZT0iUGhpbGFkZWxwaGlhIFRyYWN0IE1lZC4gSEggSW5jb21lIGFzIGEgRnVuY3Rpb24gb2YgUGN0LiBWYWNhbmN5IiwNCiAgICAgICBzdWJ0aXRsZSA9ICJEYXRhOiBVUyBDZW5zdXMgQnVyZWF1LCAyMDE2IEFDUyA1LVllYXIgRXN0aW1hdGVzIiwNCiAgICAgICAgeCA9IlBjdCBWYWNhbnQgSG91c2luZyBVbml0cyAoMC0xKSIsIHkgPSAiTWVkaWFuIEhvdXNlaG9sZCBJbmNvbWUgKDIwMTYgJCkiKSsNCiAgcGxvdFRoZW1lDQpgYGANCg0KIyA4LiBDZW5zdXMgRGF0YSB3aXRoIHRpZHljZW5zdXMNCg0KWW91IGNhbiBhY2Nlc3MgdGhlIFVTIENlbnN1cyB1c2luZyB0aGUgYHRpZHljZW5zdXNgIHBhY2thZ2UgLSB0aGlzIGlzIG11Y2ggZmFzdGVyIHRoYW4gZ3JhYmJpbmcgQ2Vuc3VzIGRhdGEgb24gdGhlIHdlYi4gWW91IGNhbiBncmFiIHRoZSB2YXJpYWJsZXMgeW91IHdhbnQgd2l0aCBvciB3aXRob3V0IHNwYXRpYWwgZGF0YSBhdHRhY2hlZC4gDQoNCkV2ZW4gaWYgeW91ICpvbmx5KiB1c2UgUiBmb3IgdGhpcyAtIHlvdSdyZSBtYWtpbmcgeW91ciBsaWZlIGEgYml0IGVhc2llci4gWW91IGNhbiBncmFiIGRhdGEgd2l0aCBgdGlkeWNlbnN1c2AsIHdyaXRlIGl0IHRvIGZpbGUgKG1vcmUgb24gdGhpcyBpbiB0aGUgbmV4dCBzZWN0aW9uKSBhbmQgdGhlbiB1c2UgaXQgaW4gRXhjZWwgb3IgQXJjR0lTLg0KDQpIZXJlJ3MgaG93IGl0IHdvcmtzIC0geW91ICJ0ZWxsIiB0aGUgY2Vuc3VzLCB0aHJvdWdoIHNvbWUgUiBDb2RlLCB0aGF0IHlvdSB3YW50IGNlcnRhaW4gdmFyaWFibGVzLCBmb3IgY2VydGFpbiB5ZWFycywgYW5kIGNlcnRhaW4gZ2VvZ3JhcGhpZXMsIGFuZCBpdCByZXR1cm5zIGl0IHRvIHlvdSBpbiB5b3VyIFIgRW52aXJvbm1lbnQuDQoNClRoZXJlIGlzIGEgd2hvbGUgYm9vayBvbiB0aWR5Y2Vuc3VzIGZyb20gaXQncyBjcmVhdG9yL21haW50YWluZXIgKCpjb3VnaCBjb3VnaCBNVVNBIE1hc3RlcmNsYXNzIGd1ZXN0IHNwZWFrZXIgMjAyMSopIFtLeWxlIFdhbGtlcl0oaHR0cHM6Ly93YWxrZXItZGF0YS5jb20vY2Vuc3VzLXIvKS4NCg0KIyMgTG9hZCB0aGUgdGlkeWNlbnN1cyBhbmQgc2YgcGFja2FnZXMNCg0KVG8gdXNlIGB0aWR5Y2Vuc3VzYCB5b3Ugc2hvdWxkIGluc3RhbGwgYW5kIGxvYWQgdGhlIGB0aWR5Y2Vuc3VzYCBwYWNrYWdlLiBXZSBhcmUgZ29pbmcgdG8gZ3JhYiBzb21lIHNwYXRpYWwgZGF0YSBhcyB3ZWxsLCBzbyBsb2FkIHRoZSBgc2ZgIHBhY2thZ2UuDQoNCklmIHlvdSBkb24ndCBoYXZlIHRoZXNlIHBhY2thZ2VzIGluc3RhbGxlZCwgeW91IHNob3VsZCBpbnN0YWxsIHRoZW0gYXMgZm9sbG93cyAoaWYgeW91IGRvLCBza2lwIHRoaXMgc3RlcCk6DQoNCmBgYHtyIGluc3RhbGxfcGFja2FnZXMsIHdhcm5pbmcgPSBGQUxTRSwgZXZhbCA9IEZBTFNFfQ0KaW5zdGFsbC5wYWNrYWdlcygndGlkeWNlbnN1cycpDQppbnN0YWxsLnBhY2thZ2VzKCdzZicpDQpgYGANCg0KT0ssIGlmIHlvdSBoYXZlIHRoZSBwYWNrYWdlcyBpbnN0YWxsZWQgb24geW91ciBjb21wdXRlciwgbm93IGxvYWQgdGhlbSBpbnRvIHlvdXIgUiBlbnZpcm9ubWVudCB3aXRoIGEgYGxpYnJhcnlgIGNhbGw6DQoNCmBgYHtyIHNldHVwX3BhY2thZ2VzMiwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9DQpsaWJyYXJ5KHRpZHljZW5zdXMpDQpsaWJyYXJ5KHNmKQ0KYGBgDQoNCllvdSB3aWxsIG5lZWQgYSAia2V5IiB0byBhY2Nlc3MgdGhlIENlbnN1cyBBUEkuIFlvdSBjYW4gZmluZCBvbmUgYXQgW3RoZWlyIHdlYnNpdGVdKGh0dHBzOi8vYXBpLmNlbnN1cy5nb3YvZGF0YS9rZXlfc2lnbnVwLmh0bWwpLg0KDQpQYXN0ZSBpdCBpbnRvIHRoZSBjb2RlIGJsb2NrIGJlbG93IGFuZCBydW4gaXQgdG8gbG9hZCB5b3VyIGtleSBpbiB0byB5b3VyIFIgZW52aXJvbm1lbnQ6DQoNCmBgYHtyIGxvYWRfa2V5X2hpZGUsIHdhcm5pbmc9IEZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KY2Vuc3VzX2tleSA8LSByZWFkLnRhYmxlKCJ+L0dpdEh1Yi9jZW5zdXNfa2V5LnR4dCIsIHF1b3RlPSJcIiIsIGNvbW1lbnQuY2hhcj0iIikNCmNlbnN1c19hcGlfa2V5KGNlbnN1c19rZXlbMV0gJT4lIGFzLmNoYXJhY3RlcigpLCBvdmVyd3JpdGUgPSBUUlVFKQ0KYGBgDQoNCmBgYHtyIGxvYWRfa2V5LCB3YXJuaW5nID0gRkFMU0UsIGV2YWwgPSBGQUxTRX0NCmNlbnN1c19hcGlfa2V5KCJZT1VSIEtFWSBHT0VTIEhFUkUiLCBvdmVyd3JpdGUgPSBUUlVFKQ0KYGBgDQoNCiMjIExvYWQgY2Vuc3VzIGRhdGEgZGljdGlvbmFyaWVzDQoNCk5vdyB0aGF0IHdlIGhhdmUgb3VyIGNlbnN1cyBjcmVkZW50aWFscyBsb2FkZWQsIHdlIGNhbiBzdGFydCBkb3dubG9hZGluZyBpbmZvcm1hdGlvbiBmcm9tIHRoZSBBUEkgdXNpbmcgc29tZSBmdW5jdGlvbnMgZnJvbSB0aWR5Y2Vuc3VzLiBXZSBhcmUgZ29pbmcgdG8gZ3JhYiBzb21lIDIwMTYgQUNTIGVzdGltYXRlcyBmb3IgUGhpbGFkZWxwaGlhIGNlbnN1cyB0cmFjdHMuIEluIG9yZGVyIHRvIGNob29zZSB2YXJpYWJsZXMgb2YgaW50ZXJlc3QsIHdlIGFyZSBnb2luZyB0byBsb2FkIHRoZSAyMDE2IEFDUyBkYXRhIGRpY3Rpb25hcnkgdXNpbmcgdGhlIHRpZHljZW5zdXMgZnVuY3Rpb24gYGxvYWRfdmFyaWFibGVzYC4gV2UgdHVybiBpdCBpbnRvIGEgZGF0YWZyYW1lIGNhbGxlZCBgYWNzX3ZhcmlhYmxlX2xpc3QuMjAxNmAuDQoNCmBgYHtyIGxvYWRfdmFyaWFibGVzLCBjYWNoZSA9IFRSVUV9DQphY3NfdmFyaWFibGVfbGlzdC4yMDE2IDwtIGxvYWRfdmFyaWFibGVzKDIwMTYsICN5ZWFyDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJhY3M1IiwgI2ZpdmUgeWVhciBBQ1MgZXN0aW1hdGVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhY2hlID0gVFJVRSkNCmBgYA0KDQpPbmNlIHdlIGhhdmUgbG9hZGVkIHRoZXNlIGRhdGEsIHdlIGNhbiBvYnNlcnZlIGFuZCBzZWFyY2ggdGhyb3VnaCB0aGUgZGF0YSBmcmFtZXMgb2YgdmFyaWFibGUgaW5mb3JtYXRpb24gd2hpY2ggc2hvdWxkIGFwcGVhciBpbiBvdXIgZ2xvYmFsIGVudmlyb25tZW50IGVpdGhlciBieSBjbGlja2luZyBvbiB0aGVtIG9yIHVzaW5nIHRoZSBgVmlldyhhY3NfdmFyaWFibGVfbGlzdC4yMDE2KWAgY29tbWFuZC4NCg0KTG9vayBhcm91bmQgaW4gdGggZGF0YSBmcmFtZSBmb3IgYSBmZXcgbWludXRlcyBhbmQgc2VlIHdoYXQncyBpbiB0aGVyZS4NCg0KIyMgRG93bmxvYWRpbmcgRGF0YSBmcm9tIFRpZHljZW5zdXMNCg0KIyMgQ3JlYXRlIGEgdmVjdG9yIG9mIGNlbnN1cyB2YXJpYWJsZXMNCg0KV2UgY2FuIHBvcHVsYXRlIGEgdmVjdG9yIG9mIHZhcmlhYmxlIG5hbWVzIHdlIHdpbGwgc2VuZCB0byB0aGUgQ2Vuc3VzIEFQSS4gV2UgY2FsbCB0aGlzIGxpc3QgYGFjc192YXJzYC4gVGhpcyBpcyB0aGUgYmVhdXR5IG9mIGEgY29kZS1iYXNlZCB3b3JrZmxvdyAtIHlvdSBjYW4gdGFrZSB0aGlzIHZlY3RvciBhbmQgcHV0IGFueXRoaW5nIHlvdSB3YW50IGluIGl0IHdoZW4geW91IGhhdmUgYSBuZXcgYW5hbHlzaXMgdG8gZG8gYW5kIHJlLXJ1biBpdCBmb3IgZGlmZmVyZW50IHZhcmlhYmxlcy4gVGhlc2UgbmVlZCB0byBiZSBjaGFyYWN0ZXIgc3RyaW5ncywgYW5kIGhlbmNlLCBpbiBxdW90ZXMgYXMgeW91IHNlZSBiZWxvdy4NCg0KS2VlcCBpbiBtaW5kIHRoZSBjYXRlZ29yaWVzIGFuZCBjb2RlIG51bWJlcnMgY2hhbmdlIGEgYml0IG92ZXIgdGltZSAtIHlvdSBtYXkgbmVlZCBzZXBhcmF0ZSB2ZWN0b3JzIGZvciBkaWZmZXJlbnQgY2Vuc3VzIHllYXJzLg0KDQpgYGB7ciBhY3NfdmFyc30NCmFjc192YXJzIDwtIGMoIkIwMTAwMV8wMDFFIiwgIyBBQ1MgdG90YWwgUG9wIGVzdGltYXRlDQogICAgICAgICAgICAgICJCMjUwMDJfMDAxRSIsICMgRXN0aW1hdGUgb2YgdG90YWwgaG91c2luZyB1bml0cw0KICAgICAgICAgICAgICAiQjI1MDAyXzAwM0UiLCAjIE51bWJlciBvZiB2YWNhbnQgaG91c2luZyB1bml0cw0KICAgICAgICAgICAgICAiQjE5MDEzXzAwMUUiLCAjIE1lZGlhbiBISCBJbmNvbWUgKCQpDQogICAgICAgICAgICAgICJCMDIwMDFfMDAyRSIsICMgUGVvcGxlIGRlc2NyaWJpbmcgdGhlbXNlbHZlcyBhcyAid2hpdGUgYWxvbmUiDQogICAgICAgICAgICAgICJCMDYwMDlfMDA2RSIpICMgVG90YWwgZ3JhZHVhdGUgb3IgcHJvZmVzc2lvbmFsIGRlZ3JlZQ0KYGBgDQoNCiMjIENhbGwgdGhlIENlbnN1cyBBUEkNCg0KV2UgdXNlIHRoZSBgZ2V0X2Fjc2AgZnVuY3Rpb24gaW4gYHRpZHljZW5zdXNgIHRvIHF1ZXJ5IHRoZSBBUEkgYW5kIGdldCB0cmFjdCBsZXZlbC1kYXRhIGZvciBhbGwgb2YgUGhpbGFkZWxwaGlhIGZyb20gdGhlIDIwMTYgQW1lcmljYW4gQ29tbXVuaXR5IFN1cnZleSdzIChBQ1MpIDUteWVhciBlc3RpbWF0ZXMuIE5vdGljZSB0aGUgZGlmZmVyZW50IGFyZ3VtZW50cyBmb3IgdGhlIGZ1bmN0aW9uLCBhbmQgdGhhdCB0aGV5IHJlcXVpcmUgY2VydGFpbiB0eXBlcyBvZiBpbmZvLiBGb3IgZXhhbXBsZSwgYGdlb2dyYXBoeWAgcmVxdWlyZXMgb25lIG9mIGEgZmluaXRlIGxpc3Qgb2YgYW5zd2VycywgYW5kIHRoZXkgaGF2ZSB0byBiZSBmb3JtYXR0ZWQgYXMgY2hhcmFjdGVyIHN0cmluZy4NCg0KUmVtZW1iZXIgdGhlIGA/P2AgZnVuY3Rpb24gLSB5b3UgY2FuIGxlYXJuIGFib3V0IHRoZSBwYXJhbWV0ZXJzIGZvciBgZ2V0X2Fjc2AgdGhpcyB3YXkuIFRoZXJlIGlzIGFsc28gYSBmdW5jdGlvbiBjYWxsZWQgYGdldF9kZWNlbm5pYWxgIHdoaWNoIHlvdSBjYW4gdXNlIGZvciBkZWNlbm5pYWwgY2Vuc3VzIGNvdW50cy4NCg0KV2UgYXNrIGZvciBkYXRhIG9uIG91ciBgYWNzX3ZhcnNgIGZvciBhbGwgdHJhY3RzIGluIFBoaWxhZGVscGhpYSBDb3VudHksIFBBIGluIDIwMTYuIFdlIGFzayBmb3IgIndpZGUiIGRhdGEgKGUuZy4gb25lIHZhcmlhYmxlIHBlciBjb2x1bW4sIG9uZSByb3cgcGVyIHRyYWN0KSBhbmQgd2Ugc2V0IGBnZW9tZXRyeWAgdG8gYEZBTFNFYC4gKExhdGVyLCB3ZSB3aWxsIHNldCBgZ2VvbWV0cnlgIHRvIGB0cnVlYCBhbmQgZ2V0IHNvbWUgc3BhdGlhbCBkYXRhLikNCg0KYGBge3IgZ2V0X2Fjc18yMDE2LCBjYWNoZSA9IFRSVUUsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KYWNzVHJhY3RzUEhMLjIwMTYgPC0gZ2V0X2FjcyhnZW9ncmFwaHkgPSAidHJhY3QiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ZWFyID0gMjAxNiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhcmlhYmxlcyA9IGFjc192YXJzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VvbWV0cnkgPSBGQUxTRSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0YXRlID0gIlBBIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvdW50eSA9ICJQaGlsYWRlbHBoaWEiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3V0cHV0ID0gIndpZGUiKSANCmBgYA0KDQpWb2lsYSAtIG5vdyB5b3UgaGF2ZSBjZW5zdXMgZGF0YS4gVGFrZSBhIGxvb2sgYW5kIHNlZSB3aGF0IHlvdSBoYXZlIHRoZXJlIC0gc2luY2UgaXQncyB0aGUgQUNTLCB0aGV5IGFyZSBlc3RpbWF0ZXMgLSBzbyB5b3UnbGwgaGF2ZSBkYXRhIG1hcmtlZCAiRSIgZm9yIGVzdGltYXRlLCBvciAiTSIgZm9yIG1hcmdpbiBvZiBlcnJvci4NCg0KTGV0J3MgdXNlIGEgYGdsaW1wc2VgIGNvbW1hbmQgdG8gY2hlY2sgaXQgb3V0DQoNCmBgYHtyIGdsaW1wc2VfY2Vuc3VzfQ0KZ2xpbXBzZShhY3NUcmFjdHNQSEwuMjAxNikNCmBgYA0KDQojIyBDbGVhbiBDZW5zdXMgRGF0YQ0KDQpUaGUgdmFyaWFibGUgbmFtZXMgdGhhdCBjb21lIHdpdGggdGhlIHN0b2NrIENlbnN1cyBkYXRhIGFyZW4ndCB0aGF0IGhlbHBmdWwuDQoNCllvdSBjYW4gdXNlIHRoZSBgcmVuYW1lYCBmdW5jdGlvbiBmcm9tIGBkcGx5cmAgdG8gbWFrZSB0aGUgY29sdW1ucyBpbiB5b3VyIGRhdGEgaW50ZWxsaWdpYmxlLg0KDQpgYGB7ciBkb19zb21lX2RwbHlyLCBjYWNoZSA9IFRSVUV9DQphY3NUcmFjdHNQSEwuMjAxNiA8LSBhY3NUcmFjdHNQSEwuMjAxNiAlPiUNCiAgcmVuYW1lICh0b3RhbF9wb3AuMjAxNiA9IEIwMTAwMV8wMDFFLA0KICAgICAgICAgIHRvdGFsX0hVLjIwMTYgPSBCMjUwMDJfMDAxRSwNCiAgICAgICAgICB0b3RhbF92YWNhbnQuMjAxNiA9IEIyNTAwMl8wMDNFLA0KICAgICAgICAgIG1lZF9ISF9JbmNvbWUuMjAxNiA9IEIxOTAxM18wMDFFLA0KICAgICAgICAgIHRvdGFsX1doaXRlLjIwMTYgPSBCMDIwMDFfMDAyRSwNCiAgICAgICAgICB0b3RhbF9HcmFkRGVnLjIwMTYgPSBCMDYwMDlfMDA2RSkNCmBgYA0KDQojIDkuIFZlY3RvciBHSVMgV2l0aCBzZg0KDQpXZSBjYW4gdXNlIFIgYXMgYSBHSVMgYW5kIGVhc2lseSBtYW5pcHVsYXRlIHZlY3RvciBkYXRhIChwb2ludHMsIGxpbmVzIGFuZCBwb2x5Z29ucykuIFRoZSBtYWluIHBhY2thZ2Ugd2UgdXNlIGZvciB0aGlzIGlzIGNhbGxlZCBgc2ZgLiBIZXJlIGFyZSBzb21lIHRoaW5ncyB0aGF0IGl0IGRvZXM6DQoNCi0gUmVhZHMgLnNocHMsIGdlb2pzb24gYW5kIG90aGVyIHNwYXRpYWwgZGF0YSBmaWxlcy4NCg0KLSBBbGxvd3MgeW91IHRvIG1hbmlwdWxhdGUgc3BhdGlhbCBkYXRhIHVzaW5nIHRoZSBzYW1lIHRvb2xzIHdlIHVzZSB0byBtYW5pcHVsYXRlIGRhdGEgZnJhbWVzIC0gYGRwbHlyYCB0b29scyBsaWtlIGBtdXRhdGVgLCBgcmVuYW1lYCBldGMuLA0KDQotIExldHMgeW91IGRvIGdlb3Byb2Nlc3NpbmcgbGlrZSB5b3UgZG8gaW4gQXJjR0lTIC0gc3BhdGlhbCBqb2lucywgYXJlYSBhbmQgZGlzdGFuY2UgY2FsY3VsYXRpb25zLCB1bmlvbiBhbmQgZGlzc29sdmUgZnVuY3Rpb25zLCBhbmQgbW9yZS4NCg0KLSBNYWtlcyBtYXBzIGJ5IGludGVyZmFjaW5nIHdpdGggYGdncGxvdGANCg0KIyMgTG9hZCBpbiBzb21lIHNwYXRpYWwgZGF0YQ0KDQpMZXQncyBsb2FkIGluIHNvbWUgc3BhdGlhbCBkYXRhIGluIHR3byB3YXlzLg0KDQpGaXJzdCwgbGV0J3MganVzdCBsb2FkIGluIHNvbWUgc2ltcGxlIGRhdGEgdXNpbmcgYHN0X3JlYWRgLCB0aGF0J3MgdGhlIGBzZmAgcGFja2FnZSdzIGJhc2ljIHJlYWQgZnVuY3Rpb24uIFlvdSBjYW4gcmVhZCBzdHVmZiBmcm9tIHRoZSB3ZWIgb3IgZnJvbSBhIGZpbGVwYXRoIG9uIHlvdXIgY29tcHV0ZXIgdGhpcyB3YXkuDQoNClRoZXNlIGFyZSBzb21lIGRhdGEgSSBncmFiYmVkIGZyb20gT3BlbiBTdHJlZXQgTWFwIC0gUGhpbGFkZWxwaGlhIGFyZWEgcmVzdGF1cmFudHMuIFlvdSBtaWdodCByZWNvZ25pemUgc29tZSBvZiB0aGVzZS4gSWYgeW91IHdhbnQgdG8ga25vdyBtb3JlIGFib3V0IGdyYWJiaW5nIE9TTSBkYXRhLCBbY2hlY2sgbXkgcmVwbyB3aXRoIHNhbXBsZSBjb2RlLl0oaHR0cHM6Ly9naXRodWIuY29tL21hZmljaG1hbi9vc21fZGF0YSkNCg0KVGhlc2UgZGF0YSBhcmUgcG9pbnRzICh2ZWN0b3IgZGF0YSBjYW4gYmUgcG9pbnRzLCBsaW5lcyBvciBwb2x5Z29ucykuDQoNCmBgYHtyIGxvYWRfZ2VvanNvbiwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIHJlc3VsdHMgPSAnaGlkZSd9DQoNCnJlc3RhdXJhbnRzIDwtIHN0X3JlYWQoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9tYWZpY2htYW4vUl9GQVFfRm9yX1BsYW5uZXJzL21haW4vZGF0YS9yZXN0YXVyYW50cy5nZW9qc29uIikNCg0KYGBgDQoNCldlIGNhbiBhbHNvIGdldCBjZW5zdXMgZGF0YSBmcm9tIGB0aWR5Y2Vuc3VzYCBpbiBzcGF0aWFsIGZvcm0uDQoNCllvdSBtaWdodCByZW1lbWJlciB0aGUgY29kZSBibG9jayBiZWxvdyBmcm9tIFNlY3Rpb24gOCBvZiB0aGlzIGRlbW8gLSB3aGVyZSB3ZSBncmFiYmVkIGEgdGFibGUgb2YgY2Vuc3VzIGRhdGEuIFdlIGNhbiBncmFiIHRoZSBzYW1lIGRhdGEgYnV0IGluIHNwYXRpYWwgZm9ybSBieSBjaGFuZ2luZyB0aGUgYGdlb21ldHJ5YCBhcmd1bWVudCB0byBgVFJVRWAuDQoNClRoZXNlIGFyZSBwb2x5Z29uIGRhdGEgLSBlYWNoIHJvdyBpcyBhIGNlbnN1cyB0cmFjdCwgZWFjaCBjb2x1bW4gaXMgYSB2YXJpYWJsZSBhc3NvY2lhdGVkIHdpdGggZWFjaCB0cmFjdC4NCg0KYGBge3IgZ2V0X2Fjc18yMDE2LnNmLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBjYWNoZT1UUlVFLCByZXN1bHRzID0gJ2hpZGUnfQ0KYWNzVHJhY3RzUEhMLjIwMTYuc2YgPC0gZ2V0X2FjcyhnZW9ncmFwaHkgPSAidHJhY3QiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ZWFyID0gMjAxNiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhcmlhYmxlcyA9IGFjc192YXJzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VvbWV0cnkgPSBUUlVFLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhdGUgPSAiUEEiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY291bnR5ID0gIlBoaWxhZGVscGhpYSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdXRwdXQgPSAid2lkZSIpICU+JQ0KICByZW5hbWUgKHRvdGFsX3BvcC4yMDE2ID0gQjAxMDAxXzAwMUUsDQogICAgICAgICAgdG90YWxfSFUuMjAxNiA9IEIyNTAwMl8wMDFFLA0KICAgICAgICAgIHRvdGFsX3ZhY2FudC4yMDE2ID0gQjI1MDAyXzAwM0UsDQogICAgICAgICAgbWVkX0hIX0luY29tZS4yMDE2ID0gQjE5MDEzXzAwMUUsDQogICAgICAgICAgdG90YWxfV2hpdGUuMjAxNiA9IEIwMjAwMV8wMDJFLA0KICAgICAgICAgIHRvdGFsX0dyYWREZWcuMjAxNiA9IEIwNjAwOV8wMDZFKSANCmBgYA0KDQojIyBVbmRlcnN0YW5kaW5nIHNmIE9iamVjdHMgLSBHZW9tZXRyeSBhbmQgUHJvamVjdGlvbnMNCg0KTGV0J3MgZXhhbWluZSBhbiBzZiBvYmplY3QsIGByZXN0YXVyYW50c2AuIENhbGwgdGhlIG9iamVjdCBpbiB5b3VyIGNvbnNvbGUgYW5kIGxvb2sgYXQgc29tZSBvZiB0aGUgY2hhcmFjdGVyaXN0aWNzLg0KDQpJdCBpcyB3b3J0aCBub3RpbmcgaGVyZSB0aGF0IHRoZSAxMHRoIHJvdyBpbiB0aGUgZGF0YSBzZXQsICJNaSBQdWVibGEiLCBpcyBhICBmYW50YXN0aWMgTWV4aWNhbiBzcG90IGluIE10IEFpcnkuIEl0IG1ha2VzIG1lIGh1bmdyeSBqdXN0IHNlZWluZyB0aGF0IGRhdGEgcG9pbnQuDQoNCg0KYGBge3IgZXhhbWluZV9zZn0NCg0KcmVzdGF1cmFudHMNCg0KYGBgDQoNClRoaXMgbG9va3MgYSBsb3QgbGlrZSBhIGRhdGEgZnJhbWUsIGFuZCBpdCBpcyEgWW91IGNhbiBlYXNpbHkgbWFuaXB1bGF0ZSBzZiBvYmplY3RzIGp1c3QgbGlrZSBkYXRhIGZyYW1lcyB3aXRoIGBkcGx5cmAgbGlrZSB0aGlzOg0KDQpgc2Zfb2JqZWN0IDwtIHNmX29iamVjdCAlPiUgbXV0YXRlKGNvbHVtbl9hID0gY29sdW1uX2IgKyBjb2x1bW5fYylgDQoNCkJ1dCB0aGVyZSBhcmUgYSBjb3VwbGUgbWFqb3IgZGlmZmVyZW5jZXMgYmV0d2VlbiBzZiBvYmplY3RzIGFuZCBkYXRhIGZyYW1lcy4NCg0KRmlyc3QsIHRoZXJlIGlzIHRoZSBgZ2VvbWV0cnlgIGNvbHVtbiAtIHRoaXMgaXMgYSBzZXQgb2YgZHJhd2luZyBhbmQgZ2VvcHJvY2Vzc2luZyBpbnN0cnVjdGlvbnMuIFlvdSBjYW4ndCBkbyBhIGBtdXRhdGVgIG9yIG90aGVyIGtpbmQgb2YgZGF0YSBtYW5pcHVsYXRpb24gb24gdGhpcyBjb2x1bW4uIGByZXN0YXVyYW50c2AgaXMgYSBwb2ludCBmaWxlLCBzbyB0aGUgZ2VvbWV0cnkgaXMgYW4gb3JkZXJlZCBzZXJpZXMgb2YgcG9pbnRzLg0KDQpTZWNvbmQsIHRoZXJlIGlzIGEgYGNyc2Agb3IgImNvb3JkaW5hdGUgcmVmZXJlbmNlIHN5c3RlbSIgYXNzb2NpYXRlZCB3aXRoIHRoZSBmaWxlLiBJZiB5b3UndmUgdXNlZCBHSVMgeW91J3ZlIGhlYXJkIG9mICJwcm9qZWN0aW9uIiAtIHRoYXQncyB3aGVyZSBpdCdzIHN0b3JlZCBpbiB0aGUgc2Ygb2JqZWN0Lg0KDQpUaGUgY3JzIGhlcmUgaXMgNDMyNiwgd2hpY2ggaXMgYWxzbyBrbm93biBhcyAiV2ViIE1lcmNhdG9yIiBvciJXR1MgODQiIC0gdGhpcyBpcyB0aGUgY29tbW9uIGxhdGl0dWRlL2xvbmdpdHVkZSBjb29yZGluYXRlIHN5c3RlbSB5b3UgbWlnaHQgYmUgZmFtaWxpYXIgd2l0aC4gWW91IGNhbiBjaGFuZ2UgdGhlIGNycyB3aGVuIHlvdSBhcmUgZG9pbmcgZ2VvcHJvY2Vzc2luZyAobW9yZSBvbiB0aGF0IGluIGEgbW9tZW50KS4NCg0KVGhlIGBzdF90cmFuc2Zvcm1gIGNvbW1hbmQgaXMgeW91ciBmcmllbmQsIHRoYXQncyBob3cgeW91IGRvIHJlcHJvamVjdGlvbnMgLSBsb29rIGl0IHVwIC0gYD8/c3RfdHJhbnNmb3JtYA0KDQpUaGUgdW5pdmVyc2Ugb2YgY29vcmRpbmF0ZSBzeXN0ZW1zIGNhbiBiZSBmb3VuZCBvbiBhIGdyZWF0IHdlYnNpdGUgY2FsbGVkIFtzcGF0aWFscmVmZXJlbmNlLm9yZ10oaHR0cDovL3NwYXRpYWxyZWZlcmVuY2Uub3JnKS4NCg0KIyMgTWFraW5nIE1hcHMgV2l0aCBnZ3Bsb3QNCg0KYGdncGxvdGAgc3VwcG9ydHMgYSBnZW9tZXRyeSBjYWxsZWQgYGdlb21fc2ZgIHRoYXQgYWxsb3dzIHlvdSB0byBwbG90IHNmIG9iamVjdHMuIEFzIHdpdGggbWFraW5nIGdyYXBocyBhbmQgcGxvdHMsIHlvdSBoYXZlIGxvdHMgb2Ygc3R5bGluZyBvcHRpb25zIHdpdGggZ2dwbG90Lg0KDQpIZXJlIGlzIGEgc2ltcGxlIG9uZSwgdXNpbmcgdGhlIGNlbnN1cyBkYXRhIHdlIGdyYWJiZWQ6DQoNCmBgYHtyIGdncGxvdF9zZjF9DQpnZ3Bsb3QoKSsNCiAgZ2VvbV9zZihkYXRhID0gYWNzVHJhY3RzUEhMLjIwMTYuc2YpDQoNCmBgYA0KDQpZb3UgY2FuIGNhbiBtYWtlIHZlcnkgY29tcGxleCBtYXBzIGluIGdncGxvdC4gWW91IGNhbiBhZGQgbWFueSBnZW9tX3NmIGRyYXdpbmcgbGF5ZXJzLCBhbmQgeW91IGNhbiB1c2UgZ2dwbG90J3MgYGFlc2AgZmVhdHVyZXMgdG8gbWFrZSBjb29sIGFlc3RoZXRpY3MgZm9yIHlvdXIgbWFwcy4gSGVyZSdzIHNvbWV0aGluZyBhIGJpdCBtb3JlIGNvbXBsaWNhdGVkOiANCg0KLSBJIHVzZSBhIGBmaWxsYCBwYXJhbWV0ZXIgaW4gbXkgYGFlc2AgY2FsbCB0byBzeW1ib2xvZ2l6ZSBgdG90YWxfR3JhZERlZy4yMDE2YCBpbiB0aGUgY2Vuc3VzIGRhdGEuDQoNCi0gSSBzZXQgdGhlIGxpbmV3b3JrIHRvIGJlIHRyYW5zcGFyZW50ICh0aGlzIGlzIG91dHNpZGUgdGhlIGBhZXNgIGNhbGwgYmVjYXVzZSBpdCBkb2Vzbid0IHBlcnRhaW4gdG8gYW55IGRhdGEsIGp1c3QgbG9va3MuDQoNCi0gSSBhZGQgbXkgUGhpbGx5IGFyZWEgcmVzdGF1cmFudHMgb24gdG9wLCBhbmQgZ2l2ZSB0aGVtIGEgY29sb3IgYW5kIGEgc2l6ZSBwYXJhbWV0ZXIuDQoNCmBgYHtyIGdncGxvdF9zZjJ9DQpnZ3Bsb3QoKSsNCiAgZ2VvbV9zZihkYXRhID0gYWNzVHJhY3RzUEhMLjIwMTYuc2YsIA0KICAgICAgICAgIGFlcyhmaWxsID0gdG90YWxfR3JhZERlZy4yMDE2KSwNCiAgICAgICAgICBjb2xvciA9ICJ0cmFuc3BhcmVudCIpKw0KICBnZW9tX3NmKGRhdGEgPSByZXN0YXVyYW50cywgY29sb3IgPSAieWVsbG93Iiwgc2l6ZSA9IDAuNSkNCmBgYA0KIA0KIyMgVmVjdG9yIEdlb3Byb2Nlc3NpbmcNCg0KVGhlIGBzZmAgcGFja2FnZSBoYXMgbG90cyBvZiBjb21tYW5kcyB0aGF0IGRvIGJhc2ljIGdlb3Byb2Nlc3NpbmcuIEkgY2FuIGRvIHNwYXRpYWwgam9pbnMsIEkgY2FuIGNhbGN1bGF0ZSBkaXN0YW5jZXMgYW5kIGFyZWFzLCBJIGNhbiBkbyBzcGF0aWFsIHByb2Nlc3NlcyBsaWtlIGJ1ZmZlciwgdW5pb24sIGFuZCBkaXNzb2x2ZS4NCg0KTWluZCB5b3VyIHByb2plY3Rpb25zIHdoZW4geW91IGRvIHRoZXNlIHRoaW5ncy4gSWYgeW91IHRyeSB0byBjYWxjdWxhdGUgZGlzdGFuY2UgaWYgeW91ciBjcnMgaXMgaW4gZGVjaW1hbCBkZWdyZWVzLCB5b3UnbGwgZ2V0IG91dHB1dHMgaW4gZGVjaW1hbCBkZWdyZWVzIChwcm9iYWJseSBub3QgaGVscGZ1bCkuDQoNCiMjIyBTcGF0aWFsIEpvaW4NCg0KWW91IGNhbiBqb2luIHNwYXRpYWwgb2JqZWN0cyB0byBvbmUgYW5vdGhlciBpbiBgc2ZgIGp1c3QgbGlrZSBpbiBhbnkgb3RoZXIgR0lTLiBMZXQncyBqb2luIGByZXN0YXVyYW50c2AgdG8gYGFjc1RyYWN0cy5QSEwuMjAxNi5zZmAuIEFmdGVyIHdlIGRvIHRoYXQgam9pbiwgd2UgY2FuIHN1bW1hcml6ZSBob3cgbWFueSByZXN0YXVyYW50cyBhcmUgaW4gZWFjaCB0cmFjdC4NCg0KQmVmb3JlIHdlIGdldCBzdGFydGVkLCB3ZSBoYXZlIHRvIG1ha2Ugc3VyZSBvdXIgZGF0YSBhcmUgcHJvamVjdGVkIGluIHRoZSBzYW1lIGNvb3JkaW5hdGUgc3lzdGVtLiBMZXQncyB1c2UgdGhlIGBzdF9jcnNgIGNvbW1hbmQgdG8gY2hlY2sgb24gdGhhdC4gVHVybnMgb3V0IGByZXN0YXVyYW50c2AgaXMgaW4gV0dTIDg0IChjcnMgPSA0MzI2KSwgYW5kIGBhY3NUcmFjdHNQSEwuMjAxNi5zZmAgaXMgaW4gTkFEIDgzIChjcnMgPSA0MjQ5KQ0KDQpgYGB7cn0NCnN0X2NycyhyZXN0YXVyYW50cykgPT0gc3RfY3JzKGFjc1RyYWN0c1BITC4yMDE2LnNmKQ0KYGBgDQoNClRoZSBmdW5jdGlvbiBgc3Rfam9pbmAgZG9lcyB0aGUgd29yayBmb3IgdXMgaGVyZSB0byBqb2luIHBvaW50cyB0byB0aGUgcG9seWdvbnMgdGhhdCBjb250YWluIHRoZW0sIGFuZCBpdCBoYXMgbG90cyBvZiBvcHRpb25zLiBIZXJlLCB3ZSBzYXkgdGhlIHR5cGUgb2Ygam9pbiBpcyBgaW50ZXJzZWN0c2AgYXMgaW4gImRvZXMgdGhpcyBwb2ludCBmcm9tIHRoZSBsZWZ0IGhhbmQgZGF0YSBzZXQgaW50ZXJzZWN0IHRoZSBwb2x5Z29uIGZyb20gdGhlIHJpZ2h0IGhhbmQgZGF0YSBzZXQ/IiBXZSBhbHNvIHNheSBgbGVmdD1UUlVFYCAtIGluZGljYXRpbmcgd2Ugd2FudCBhICJsZWZ0IGpvaW4iIC0gcHJlc2VydmluZyBhbGwgaXRlbXMgZnJvbSB0aGUgbGVmdCBoYW5kIGRhdGEgc2V0LCB3aGV0aGVyIG9yIG5vdCB0aGV5IGpvaW4gdG8gdGhlIGl0ZW1zIG9uIHRoZSByaWdodC4gVGhlIHRoaW5ncyB0aGF0IGRvbid0IGpvaW4gKGVnLiBvdXRzaWRlIG9mIFBoaWxseSksIHRoZXkgZG9uJ3Qgc2hvdyB1cC4NCg0KTm90aWNlLCB0aGF0IEkgcGlwZSB0aGUgYHN0X3RyYW5zZm9ybWAgY29tbWFuZCB0byBgcmVzdGF1cmFudHNgIHJpZ2h0IGluc2lkZSB0aGUgam9pbiBzdGF0ZW1lbnQgYW5kIHJlLXByb2plY3QgaXQgc28gdGhpcyBvcGVyYXRpb24gd29ya3MhDQoNCmBgYHtyIHNwYXRpYWxfam9pbiwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9DQoNCnJlc3RhdXJhbnRzLmFuZC50cmFjdHMgPC0gc3Rfam9pbihyZXN0YXVyYW50cyAlPiUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0X3RyYW5zZm9ybShjcnMgPSBzdF9jcnMoYWNzVHJhY3RzUEhMLjIwMTYuc2YpKSwgYWNzVHJhY3RzUEhMLjIwMTYuc2YsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgam9pbiA9IHN0X2ludGVyc2VjdHMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVmdCA9IFRSVUUpDQoNCmBgYA0KDQpUaGlzIG9wZXJhdGlvbiBwcm9kdWNlcyBhIGRhdGEgc2V0IHdoZXJlIHRoZSByb3dzIGFyZSByZXN0YXVyYW50cywgYW5kIHRoZXJlIGlzIGEgY29sdW1uIGRlbm90aW5nIHRoZSB0cmFjdCBpdCBmYWxscyBpbi4NCg0KTm93IHdlIGNhbiBjb252ZXJ0IHRvIGEgZGF0YSBmcmFtZSAoYGFzLmRhdGEuZnJhbWVgKSBzdW1tYXJpemUgdGhlIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgYnkgY2Vuc3VzIHRyYWN0IChgZ3JvdXBfYnlgLCBgc3VtbWFyaXplYCksIGpvaW4gdGhlc2UgdG8gb3VyIHRyYWN0IGBzZmAgZmlsZSBieSBgR0VPSURgIChgbGVmdF9qb2luYCkuDQoNCmBgYHtyIHN1bW1hcml6ZV9qb2luX21hcH0NCg0KcmVzdGF1cmFudF9zdW1tYXJ5IDwtIA0KICByZXN0YXVyYW50cy5hbmQudHJhY3RzICU+JQ0KICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogIGdyb3VwX2J5KEdFT0lEKSAlPiUNCiAgc3VtbWFyaXplKHJlc3RhdXJhbnRfc3VtID0gbigpKQ0KDQp0cmFjdHNfYW5kX3Jlc3RhdXJhbnRzIDwtDQogIGxlZnRfam9pbihhY3NUcmFjdHNQSEwuMjAxNi5zZiwgcmVzdGF1cmFudF9zdW1tYXJ5LA0KICAgICAgICAgICAgYnkgPSBjKCJHRU9JRCIpKQ0KDQpnZ3Bsb3QoKSsNCiAgZ2VvbV9zZihkYXRhID0gdHJhY3RzX2FuZF9yZXN0YXVyYW50cywgDQogICAgICAgICAgYWVzKGZpbGwgPSByZXN0YXVyYW50X3N1bSksDQogICAgICAgICAgY29sb3IgPSAidHJhbnNwYXJlbnQiKQ0KDQpgYGANCg0KIyMjIE90aGVyIFNwYXRpYWwgT3BlcmF0aW9ucw0KDQpUaGUgYHNmYCBwYWNrYWdlIGNhbiBkbyBtb3N0IG9mIHdoYXQgeW91IGRvIHdpdGggdmVjdG9ycyBpbiBBcmNHSVMsIGluY2x1ZGluZy4uLiANCg0KLSBHZW9tZXRyaWMgZnVuY3Rpb25zIGxpa2UgYHN0X3VuaW9uYCwgYHN0X2J1ZmZlcmAsIGBzdF9kaXNzb2x2ZWAgYW5kIHNvIG9uLiANCg0KLSBZb3UgY2FuIGZpbmQgYSBwb2x5Z29uIGNlbnRyb2lkIHdpdGggYHN0X2NlbnRyb2lkYC4NCg0KLSBNZWFzdXJlbWVudCBmdW5jdGlvbnM6IENhbGN1bGF0ZSBhIHBvbHlnb24ncyBhcmVhIG9yIHBlcmltZXRlciAoaW4gdGhlIG5hdGl2ZSB1bml0cyBvZiBpdHMgcHJvamVjdGlvbikgd2l0aCBgc3RfYXJlYWAuIEZpbmQgdGhlIGRpc3RhbmNlIGJldHdlZW4gdHdvIGBzZmAgb2JqZWN0cyB3aXRoIGBzdF9kaXN0YW5jZWAuDQoNClRoZXJlIGFyZSBzb21lIHNwYXRpYWwgZnVuY3Rpb25zIHRoYXQgeW91IG5lZWQgdG8gZmluZCBvdGhlciBwYWNrYWdlcyBmb3IsIGxpa2UgSy1uZWFyZXN0LW5laWdoYm9ycyBmdW5jdGlvbnMuDQoNCkNoZWNrIG91dCB0aGUgYHNmYCAiY2hlYXQgc2hlZXQiIHRvIHNlZSB3aGF0IGl0IGNhbiBkbzoNCg0KW1IgU3R1ZGlvOiBzZiBjaGVhdHNoZWV0XShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcnN0dWRpby9jaGVhdHNoZWV0cy9tYWluL3NmLnBkZikNCg0KIyMjIE1ha2luZyBhICJGaXNobmV0Ig0KDQpBICJmaXNobmV0IiBpcyBhIHJhc3Rlci1saWtlIGdyaWQgd2hlcmUgeW91IGltcG9zZSBhIHVuaWZvcm0gc3VyZmFjZSBvZiBhZXJpYWwgdW5pdHMgLSBzcXVhcmVzIG9yIGhleGFnb25zIC0gYWNyb3NzIGEgbGFuZHNjYXBlLiBUaGlzIGNhbiBiZSByZWFsbHkgdXNlZnVsIGZvciB2aXN1YWxpemluZyBvciBwcm9jZXNzaW5nIGluZm9ybWF0aW9uLg0KDQpJIGNhbiBtYWtlIGEgImZpc2huZXQiIGdyaWQgYWNyb3NzIFBoaWxhZGVscGhpYSBDb3VudHksIGFuZCBqb2luIHRoZSByZXN0YXVyYW50IHBvaW50IGRhdGEgaW4gb3JkZXIgdG8gdmlzdWFsaXplIHRoZSBkZW5zaXR5IG9mIGluIGEgZGlmZmVyZW50IHdheS4NCg0KTGV0J3Mgc3RhcnQgYnkgbG9hZGluZyBhIGdlb2pzb24gcmVwcmVzZW50aW5nIFBoaWxhZGVscGhpYSBDb3VudHksIGFuZCBjb2VyY2luZyBpdCB0byB0aGUgc2YgZm9ybWF0IHVzaW5nIGBzdF9hc19zZmAuIA0KDQpOb3Qgc3VyZSB3aGF0IHRoZSBwcm9qZWN0aW9uIGlzPyBJdCdzIGltcG9ydGFudCB0byBrbm93IC0gdGhlIGNlbGxzaXplIHlvdSBzZXQgZm9yIHlvdXIgZmlzaG5ldCB3aWxsIGJlIGluIHRoZSBuYXRpdmUgdW5pdHMgb2YgdGhlIHByb2plY3Rpb24uIFlvdSBjYW4gZmluZCB0aGF0IG91dCB1c2luZyBgc3RfY3JzYCB0byAiYXNrIiB5b3VyIGRhdGEgYWJvdXQgdGhlIHByb2plY3Rpb24uIFdlIHNlZSB0aGF0IHRoaXMgaXMgaW4gc29tZXRoaW5nIGNhbGxlZCBgWyJFUFNHIiw0MjY5XWAgLSB0aGVyZSBpcyBwcm9qZWN0aW9uIGluICJkZWdyZWUiIGFuZCAibGF0aXR1ZGUiIC0gdGhvc2UgYXJlbid0IGxpbmVhciB1bml0cyBsaWtlIGZlZXQgb3IgbWV0ZXJzIQ0KDQpgYGB7ciBsb2FkX2NvdW50eSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9DQpwaGlsYUNvdW50eSA8LSByZWFkX3NmKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vbWFmaWNobWFuL1JfRkFRX0Zvcl9QbGFubmVycy9tYWluL2RhdGEvcGhpbGFfY291bnR5Lmdlb2pzb24iKSAlPiUNCiAgc3RfYXNfc2YoKQ0KDQpzdF9jcnMocGhpbGFDb3VudHkpDQoNCmBgYA0KQmVmb3JlIHdlIG1ha2Ugb3VyIGZpc2huZXQsIGxldCdzIHByb2plY3QgaXQgdG8gYSBjb29yZGluYXRlIHJlZmVyZW5jZSBzeXN0ZW0gMjI3Miwgd2hpY2ggaXMgaW4gZmVldC4gSG93IGRpZCBJIGNob29zZSB0aGF0IE5lZWQgdG8gZmluZCB0aGUgcmlnaHQgY29vcmRpbmF0ZSBzeXN0ZW0gZm9yIHlvdXIgc3R1ZHkgYXJlYT8gVmlzaXQgW3NwYXRpYWxyZWZlcmVuY2Uub3JnXShodHRwOi8vc3BhdGlhbHJlZmVyZW5jZS5vcmcpIG9yIHVzZSB0aGUgYGNyc19zdWdnZXN0YCBwYWNrYWdlIChtb3JlIG9uIHRoaXMgaW4gdGhlIGVuZCByZWZlcmVuY2VzIHRvIHRoaXMgZG9jdW1lbnQgaWYgeW91J3JlIGludGVyZXN0ZWQuKQ0KDQpgYGB7ciB0cmFuc2Zvcm1fY291bnR5LCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0NCnBoaWxhQ291bnR5IDwtIHBoaWxhQ291bnR5ICU+JQ0KICBzdF90cmFuc2Zvcm0oMjI3MikNCg0KYGBgDQoNCg0KRmluYWxseSwgbGV0J3MgbWFrZSBvdXIgZmlzaG5ldC4gV2UgdXNlIHRoZSBgc3RfbWFrZV9ncmlkYCBmdW5jdGlvbiB0byBjcmVhdGUgdGhlIGZpc2huZXQgLSBhbmQgd2Ugc3BlY2lmeSB0aGF0IHdlIHdhbnQgdGhlIGNlbGxzaXplIHRvIGVxdWFsIDI1MDAgLSB0aGF0J3MgaW4gdGhlIG5hdGl2ZSB1bml0cyBvZiB0aGUgcHJvamVjdGlvbiwgZmVldC4gV2UgYXNrIGZvciBzcXVhcmUgY2VsbHMuIENoZWNrIG91dCB0aGlzIGRhdGEgcGlwZWxpbmUgLSB3ZSB1c2UgYW4gb3BlcmF0aW9uIHRvIGtlZXAgb25seSB0aGUgZmlzaG5ldCBjZWxscyB0aGF0IGludGVyc2VjdCB0aGUgb3JpZ2luYWwgYHBoaWxhQ291bnR5YCBzaGFwZSwgYW5kIHRoZW4gd2UgY3JlYXRlIGEgYHVuaXF1ZUlEYCB1c2luZyBhIGBtdXRhdGVgIGNvbW1hbmQgLSBnaXZpbmcgZWFjaCBzaGFwZSBhbiBpZGVudGlmaWVyIHRoYXQgaXMgdXNlZnVsIGZvciBkb2luZyBhbmFseXNpcyBsYXRlci4NCg0KDQpgYGB7ciBtYWtlX2Zpc2huZXR9DQoNCmZpc2huZXQgPC0gDQogIHN0X21ha2VfZ3JpZChwaGlsYUNvdW50eSwNCiAgICAgICAgICAgICAgIGNlbGxzaXplID0gMjUwMCwgDQogICAgICAgICAgICAgICBzcXVhcmUgPSBUUlVFKSAlPiUNCiAgLltwaGlsYUNvdW50eV0gJT4lICAgICAgICAgICAgIyBjbGlwcyB0aGUgZ3JpZCB0byB0aGUgcGhpbGFDb3VudHkgZXh0ZW50DQogIHN0X3NmKCkgJT4lDQogIG11dGF0ZSh1bmlxdWVJRCA9IHJvd25hbWVzKC4pKQ0KYGBgDQoNCkNoZWNrIGl0IG91dCBpbiBnZ3Bsb3QhDQoNCmBgYHtyIHBsb3RfZmlzaG5ldH0NCmdncGxvdCgpKw0KICBnZW9tX3NmKGRhdGEgPSBmaXNobmV0KQ0KDQpgYGANCg0KTm93IHdlIGNhbiBqb2luIG91ciByZXN0YXVyYW50cyB0byB0aGUgZmlzaG5ldCB1c2luZyB0aGUgc2FtZSByb3V0aW5lIGFzIHdlIHVzZWQgZm9yIG91ciB0cmFjdC1iYXNlZCBzdW1tYXJ5LCBidXQgd2l0aCBhIGRpZmZlcmVudCByZXN1bHQgYmVjYXVzZSBvZiB0aGUgZGlmZmVyZW50IGFlcmlhbCB1bml0cy4NCg0KYGBge3J9DQpyZXN0YXVyYW50cy5hbmQuZmlzaG5ldCA8LSBzdF9qb2luKHJlc3RhdXJhbnRzICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RfdHJhbnNmb3JtKGNycyA9IHN0X2NycyhmaXNobmV0KSksIGZpc2huZXQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgam9pbiA9IHN0X2ludGVyc2VjdHMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVmdCA9IFRSVUUpDQoNCg0KcmVzdGF1cmFudF9maXNoZXRfc3VtbWFyeSA8LSANCiAgcmVzdGF1cmFudHMuYW5kLmZpc2huZXQgJT4lDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgZ3JvdXBfYnkodW5pcXVlSUQpICU+JQ0KICBzdW1tYXJpemUocmVzdGF1cmFudF9zdW0gPSBuKCkpDQoNCmZpc2huZXRfYW5kX3Jlc3RhdXJhbnRzIDwtDQogIGxlZnRfam9pbihmaXNobmV0LCByZXN0YXVyYW50X2Zpc2hldF9zdW1tYXJ5LA0KICAgICAgICAgICAgYnkgPSBjKCJ1bmlxdWVJRCIpKQ0KDQpnZ3Bsb3QoKSsNCiAgZ2VvbV9zZihkYXRhID0gZmlzaG5ldF9hbmRfcmVzdGF1cmFudHMsIA0KICAgICAgICAgIGFlcyhmaWxsID0gcmVzdGF1cmFudF9zdW0pLA0KICAgICAgICAgIGNvbG9yID0gInRyYW5zcGFyZW50IikNCg0KYGBgDQoNCg0KIyMgV3JpdGluZyBzcGF0aWFsIGRhdGENCg0KWW91IGNhbiB3cml0ZSBvdXQgYW55IHNmIG9iamVjdHMgYXMgLnNocCBmaWxlcyB3aGljaCBhcmUgcmVhZGFibGUgaW4gQXJjR0lTLiBUaGlzIGlzIGVzcGVjaWFsbHkgdXNlZnVsIGlmIHlvdSB3YW50IHRvIGp1c3QgdXNlIFIgdG8gZ3JhYiBkYXRhIGVmZmljaWVudGx5IGFuZCB3YW50IHRvIG1hcCBpdCBlbHNld2hlcmUuIEkgcHJlZmVyIHRvIHdyaXRlIGdlb2pzb25zIC0gdGhleSBhcmUgbW9yZSB3aWRlbHkgdXNhYmxlIGFuZCB0aGUgY29sdW1uIG5hbWVzIGFyZSBtb3JlIHN0YWJsZS4NCg0KVXNlIHRoZSBmdW5jdGlvbiAic3Rfd3JpdGUiIGxpa2Ugc286DQoNCmBzdF93cml0ZShteV9zZl9vYmplY3QsICJteV9maWxlcGF0aC9teV9maWxlbmFtZS5zaHAiKWANCg0KIyAxMC4gUHVibGlzaGluZyBBbmFseXNlcyB3aXRoIFIgTWFya2Rvd24NCg0KSSB3aWxsIHByb2JhYmx5IGJlIGFza2luZyB5b3UgdG8gdHVybiBpbiBhc3NpZ25tZW50cyB1c2luZyBSIE1hcmtkb3duLiBNYXJrZG93biBpcyBhIHdheSB0byBwcmVzZW50IHlvdXIgY29kZS1iYXNlZCBwcm9qZWN0cyBhcyBhIG1vcmUgcG9saXNoZWQgZG9jdW1lbnQgKHlvdSdyZSByZWFkaW5nIGEgbWFya2Rvd24gcmlnaHQgbm93ISkuICBZb3UgY2FuIHVzZSB0aGlzIHRvb2wgdG8gbWFrZSByZXBlYXRhYmxlIHJlcG9ydGluZyB3b3JrZmxvd3MgZm9yIG5vbi10ZWNoaW5jYWwgYXVkaWVuY2UgdXNpbmcgUi4gW0kgb2Z0ZW4gd3JpdGUgZGF0YSByZXBvcnRzIGxpa2UgdGhpcyBvbmUgZm9yIG15IGNsaWVudHNdKGh0dHBzOi8vdm9pY2Vzb2ZjcmVhdGl2ZWRhdGEubmlnaHR0aW1lLm9yZy8pLCB3aGVyZSBJICJoaWRlIiBhbGwgdGhlIGNvZGUgYW5kIGp1c3Qgc2hvdyB0aGUgb3V0cHV0cy4NCg0KQSBtYXJrZG93biBkb2N1bWVudCBlbWJlZHMgImNodW5rcyIgb2YgUiBjb2RlIGluIGJldHdlZW4gYml0cyBvZiB0ZXh0LiBUaGVzZSBjaHVua3MgY2FuIHNpbXBseSBiZSBiaXRzIG9mIGNvZGUsIG9yIHRoZXkgY2FuIGJlIGNvZGUgdGhhdCBjcmVhdGVzIHNvbWUga2luZCBvZiBpbWFnZSBsaWtlIGEgYGdncGxvdGAgY2hhcnQgb3IgYSBga2FibGVgIHRhYmxlLiBZb3UgY2FuIG1hbmlwdWxhdGUgdGhlIG9wdGlvbnMgb2YgYSBjb2RlIGNodW5rIHNvIHRoYXQgdGhlIG91dHB1dCBhcHBlYXJzIGEgY2VydGFpbiB3YXkuDQoNClRoZSBiYXNpYyBmaWxlIGZvcm1hdCBvZiBhIG1hcmtkb3duIGlzIGAucm1kYC4gWW91IGNhbiAia25pdCIgdGhlIG1hcmtkb3duIGludG8gYSBwb2xpc2hlZCBkb2N1bWVudCBpbiBIVE1MLCBwZGYgb3IgZG9jIGZvcm1hdC4NCg0KSWYgeW91IHdhbnQgdG8gZXhwZXJpbWVudCB3aXRoIHNvbWUgbWFya2Rvd24gbGFuZ3VhZ2UgLSB5b3UgY2FuIGZpbmQgYSBkb3dubG9hZCBidXR0b24gYXQgdGhlIHRvcCByaWdodCBvZiB0aGlzIGRvY3VtZW50IGFuZCBkb3dubG9hZCB0aGUgY29kZSB0aGF0IHdhcyB1c2VkIHRvIG1ha2UgdGhpcy4NCg0KRm9yIG1vcmUgZGV0YWlscyBvbiB1c2luZyBSIE1hcmtkb3duLCBjb25zdWx0IDxodHRwOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tPiBvciBjaGVjayBvdXQgW3RoaXMgZ3JlYXQgYm9vayBmcm9tIFhpZSBldCBhbF0oaHR0cHM6Ly9ib29rZG93bi5vcmcveWlodWkvcm1hcmtkb3duLWNvb2tib29rLykuDQoNCiMgMTEuIFByb2plY3QgV29ya2Zsb3dzIC0gR2l0aHViIGFuZCBSIFByb2plY3RzDQoNCkFzIHlvdSB3b3JrIG9uIGluY3JlYXNpbmdseSBzb3BoaXN0aWNhdGVkIG9yIGNvbGxhYm9yYXRpdmUgcHJvamVjdHMgdXNpbmcgUiwgeW91IHdpbGwgd2FudCB0byBmaWd1cmUgb3V0IGEgd2F5IHRvIG1hbmFnZSB5b3VyIGZpbGVzIGFuZCBjb2RlLCBhbmQgdG8gZmlndXJlIG91dCB3YXlzIHRvIGtlZXAgeW91ciBjb2RlIHdvcmtpbmcgYXMgeW91IGltcHJvdmUgYW5kIGRldmVsb3AgaXQuDQoNCkJlc3QgcHJhY3RpY2VzIGluIGNvZGluZyBpcyBhIHN1YmplY3QgZm9yIGEgd2hvbGUgZW50aXJlIGNvdXJzZSwgYnV0IGhlcmUgYXJlIGEgZmV3IHNpbXBsZSB0aXBzIHRvIGdldCB5b3Ugc3RhcnRlZC4NCg0KIyMgUiBQcm9qZWN0cw0KDQpSIGhhcyBhICJwcm9qZWN0cyIgZmlsZS10eXBlIHdoaWNoIGNhbiBoZWxwIHlvdSBrZWVwIGFuIG9yZ2FuaXplZCBmaWxlIHN0cnVjdHVyZSBhbmQgd29ya2luZyBlbnZpcm9ubWVudCBpbiBSIFN0dWRpby4gVGhlcmUgaXMgYSBmaWxlIHR5cGUgY2FsbGVkIGAuclByb2pgIHRoYXQgY29vcmRpbmF0ZXMgdGhpcy4gWW91IGNhbiBvcGVuIGEgbmV3IHByb2plY3QgYnkgZ29pbmcgdG8gRmlsZSAtPiBOZXcgUHJvamVjdCBpbiBSIFN0dWRpby4gQ2hlY2sgb3V0IHRoZSAiRmlsZXMiIHBhcnQgb2YgeW91ciBSIFN0dWRpbyB3aW5kb3cgYW5kIHNlZSBob3cgeW91IGNhbiBub3cgZ3JhcGhpY2FsbHkgdmlldyB5b3VyIGZpbGVzLiBCZXN0IG9mIGFsbCwgaWYgeW91IGtlZXAgdGhpbmdzIGNvbnRhaW5lZCBpbiBhIHByb2plY3QsIGl0J3MgbW9yZSBwb3J0YWJsZSBmcm9tIGNvbXB1dGVyIHRvIGNvbXB1dGVyIGJlY2F1c2UgeW91J3JlIGp1c3QgY2FsbGluZyBmaWxlcyBpbnNpZGUgb25lIFByb2plY3QuDQoNCltSZWFkIEhhZGxleSBXaWNraGFtJ3MgdHV0b3JpYWxdKGh0dHBzOi8vcjRkcy5oYWQuY28ubnovd29ya2Zsb3ctcHJvamVjdHMuaHRtbCkgb24gaG93IHdvcmtpbmcgd2l0aCBSIFByb2plY3RzIGNhbiBoZWxwIHlvdSB3aXRoIG9yZ2FuaXphdGlvbi4NCg0KIyMgR2l0aHViDQoNCkdpdGh1YiBpcyBhIGNvZGUtY29sbGFib3JhdGlvbiBlbnZpcm9ubWVudCB0aGF0IGFsbG93cyB5b3UgdG8gdXNlICJzb3VyY2UgY29udHJvbCIgYW5kIHdvcmsgb24gdGhpbmdzIHdpdGggb3RoZXJzIHdpdGhvdXQgYWNjaWRlbnRhbGx5IG92ZXItd3JpdGluZyBvciBicmVha2luZyBvdGhlciBwZW9wbGVzJyBjb2RlLiBHaXRodWIgd29ya3Mgd2VsbCB3aXRoIFIgUHJvamVjdHMuIFlvdSBjYW4gInB1c2giIGFuZCAicHVsbCIgY29kZSBhbmQgZGF0YSBzdHJhaWdodCBmcm9tIFIgU3R1ZGlvIG9yIHVzZSBhIHByb2dyYW0gY2FsbGVkIEdpdGh1YiBEZXNrdG9wICh3aGljaCBJIHByZWZlcikuIFlvdSB3aWxsIG5lZWQgYW4gYWNjb3VudCBvbiBHaXRodWIgdG8gbWFrZSB0aGlzIGFsbCB3b3JrLg0KDQpIZXJlIGlzIGEgcXVpY2sgc3RlcC1ieS1zdGVwIGd1aWRlIHRvIHNldHRpbmcgdXAgYSBjb2xsYWJvcmF0aXZlIHJlcG8sIGFuIFIgcHJvamVjdCwgYW5kIHNlbmRpbmcgc29tZSBjb2RlIGJhY2sgYW5kIGZvcnRoLCB1c2luZyBHaXRodWIgRGVza3RvcCBhbmQgUiBTdHVkaW86DQoNCi0gQ3JlYXRlIGEgbmV3IHJlcG8gb24geW91ciBnaXRodWIgcGFnZQ0KDQotIEFkZCB5b3VyIGdpdGh1YiBjb2xsYWJvcmF0b3IgYnkgZ29pbmcgdG8gc2V0dGluZ3MvY29sbGFib3JhdG9ycw0KDQotIEdvIHRvIHlvdXIgR2l0aHViIERlc2t0b3AgYXBwLCBhbmQgImNsb25lIiB0aGUgcmVwbyAtIHRoaXMgd2lsbCBwb3B1bGF0ZSBhIHZlcnNpb24gb24geW91ciBjb21wdXRlciBpbiBpdCdzIG93biBmb2xkZXIuDQoNCi0gR28gaW50byBSIFN0dWRpbywgYW5kIHN0YXJ0IGEgbmV3ICJwcm9qZWN0IiAtIHB1dCBpdCBpbiB0aGUgcmVwbyBmb2xkZXIuIERvIHNvbWUgd29yayAtIGNyZWF0ZSBhIG5ldyBSIHNjcmlwdCwgYWRkIHNvbWUgZGF0YS4NCg0KLSBXaGVuIHlvdSBhcmUgZG9uZSAtIGdvIHRvIEdpdGh1YiBEZXNrdG9wLCB3cml0ZSBzb21lIGFubm90YXRpb25zIGFib3V0IHRoZSBjaGFuZ2VzIHlvdSBtYWRlIHRvIHRoZSBjb2RlLCBhbmQgInB1c2giIHRoZSBjaGFuZ2VzIHRvIHRoZSB3ZWIgcmVwby4NCg0KLSBZb3VyIGNvbGxhYm9yYXRvciBjYW4gY2xvbmUgdGhlIHByb2plY3QsIGFuZCB0aGVuIHB1c2ggYW5kIHB1bGwgdGhpbmdzIHRoZW1zZWx2ZXMuIFRoaXMgd2lsbCBoZWxwIHlvdSBtYW5hZ2UgY2hhbmdlcyBhbmQgdmVyc2lvbnMuIElmIHlvdSBrZWVwIGFsbCB5b3VyIGNvZGUgYW5kIGRhdGEgaW5zaWRlIHRoZSBHaXRodWIgZm9sZGVyLCB0aGUgZmlsZSBwYXRocyB3aWxsIGFsbCBiZSB0aGUgc2FtZSBhbmQgaXQgd2lsbCBiZSBlYXN5IHRvIGNvbGxhYm9yYXRlLiBZb3UgY2FuIHdvcmsgc2VwYXJhdGVseSB3aXRob3V0IGJyZWFraW5nIGVhY2ggb3RoZXIncyBjb2RlIC0gdXNlICJicmFuY2hlcyIgdG8gbWFrZSBjaGFuZ2VzIG9uIHlvdXIgb3duIHZlcnNpb24gb2YgdGhlIGNvZGUsIGFuZCB0aGVuIHB1c2ggaXQgdG8gdGhlIG1haW4gY29kZSBiYXNlIGxhdGVyLg0KDQoNClRoZSBmaW5lciBtZWNoYW5pY3Mgb2YgY29sbGFib3JhdGluZyB3aXRoIEdpdGh1YiBhcmUgdG9vIGNvbXBsZXggZm9yIHRoaXMgZG9jdW1lbnQsIGJ1dCBbY2hlY2sgb3V0IE1hdHQgSGFycmlzJyB0dXRvcmlhbCBmcm9tIE1VU0EgNTA4IG9uIHVzaW5nIEdpdGh1Yl0oaHR0cHM6Ly9naXRodWIuY29tL21hZmljaG1hbi9SX0ZBUV9Gb3JfUGxhbm5lcnMvYmxvYi9tYWluL3BkZi9XZWVrXzFfTGFiX0ludHJvX3RvX0dpdGh1YigxKS5wZGYpIGZvciBkZXRhaWxlZCBpbnN0cnVjdGlvbnMgb24gaG93IHRvIGdldCBzZXQgdXAgYW5kIHN0YXJ0IGNvbGxhYm9yYXRpbmcuDQoNCiMjIyBHaXRodWIgUGFnZXMNCg0KSXQncyBhIGJpdCB3ZWlyZCB0byBzZW5kIEhUTUwgbWFya2Rvd25zIHRvIGNsaWVudHMgYW5kIG4wMGJzIGFuZCBzYXkgInlvdSBuZWVkIHRvIERMIHRoaXMgYW5kIG9wZW4gaXQgaW4geW91ciBicm93c2VyIHRvIHNlZSBpdCIsIGJ1dCB5b3UgY2FuIGVhc2lseSBwdWJsaXNoIGEga25pdCBodG1sIG1hcmtkb3duIHRvIHRoZSB3ZWIgdXNpbmcgZ2l0aHViLiBJZiB5b3UgbmFtZSB0aGUgbWFya2Rvd24gKGluIEhUTUwgZm9ybSkgYGluZGV4YCwgeW91IGNhbiBnbyB0byB5b3VyIHJlcG8gc2V0dGluZ3MsIGFuZCBhY3RpdmF0ZSB0aGUgInBhZ2VzIiBmdW5jdGlvbmFsaXR5LiBUaGlzIHdpbGwgcHVibGlzaCB0aGUgaHRtbCBmaWxlIGFzIGEgZ2l0aHViLmlvIHdlYnBhZ2UuDQoNClRoaXMgcGFnZSBpdHNlbGYgaXMgYW4gZXhhbXBsZSAtIChjaGVjayBvdXQgdGhlIHJlcG8gYmVoaW5kIGl0KVtodHRwczovL2dpdGh1Yi5jb20vbWFmaWNobWFuL1JfRkFRX0Zvcl9QbGFubmVyc10gdG8gc2VlIGhvdyB0aGlzIGFsbCB3b3JrcyENCg0KIyMgV3JpdGluZyBSZWFkYWJsZSBDb2RlDQoNCllvdSB3aWxsIHByb2JhYmx5IGZpbmQgeW91cnNlbGYgd3JpdGluZyBjb2RlIHRoYXQgb3RoZXIgcGVvcGxlIGhhdmUgdG8gdXNlIC0gb3IgdGhhdCB5b3Ugd2lsbCBzYXZlIGFuZCB1c2UgYXQgc29tZSB0aW1lIGluIHRoZSBmdXR1cmUuIEl0J3MgaW1wb3J0YW50IHRvIG1ha2Ugc3VyZSB5b3UgZm9ybWF0IGFuZCBhbm5vdGF0ZSB5b3VyIGNvZGUgc28gdGhhdCB5b3VyIGNvbGxlYWd1ZXMgb3IgeW91ciBmdXR1cmUgc2VsZiBjYW4gdXNlIGl0LCB1bmRlcnN0YW5kIGl0LCBhbmQgdHJvdWJsZXNob290IGl0Lg0KDQpIZXJlIGFyZSBzb21lIHNpbXBsZSB0aXBzIHRvIGtlZXAgeW91ciBjb2RlIG5pY2UgYW5kIG5lYXQ6DQoNCi0gTmFtZSB5b3VyIGZpbGVzIGFuZCB2YXJpYWJsZXMgdGhpbmdzIHRoYXQgYXJlIGludGVsbGlnaWJsZS4gTmFtZSBhIGRhdGFmcmFtZSBgY2Vuc3VzXzIwMTFgIGluc3RlYWQgb2Ygbm9uc2Vuc2UgbGlrZSBgbmV3X2RhdGFmcmFtZWAuIERvbid0IGNhbGwgeW91ciBmdW5jdGlvbnMgb3IgeW91ciBkYXRhIG9iamVjdHMgbmFtZXMgdGhhdCBtaWdodCBiZSB1c2VkIGVsc2V3aGVyZSBpbiB5b3VyIFIgZW52aXJvbm1lbnQgLSBmb3IgZXhhbXBsZSwgZG9uJ3QgdHJ5IHRvIG5hbWUgYSBkYXRhZnJhbWUgYGZhbHNlYCBvciBgbWVkaWFuYC4NCg0KLSBVc2Ugc3BhY2VzIGluIHlvdXIgY29kZSBiZWZvcmUgYW5kIGFmdGVyIG9wZXJhdG9ycyBsaWtlIHBhcmVudGhlc2lzIG9yIGVxdWFscyBzaWducywgbGlrZSB0aGlzOiBgdGhlTWVkaWFuIDwtIG1lZGlhbihjKDEsIDQsIDYpKWANCg0KLSBVc2UgYSByZXR1cm4gYXQgdGhlIGVuZCBvZiBlYWNoIHBpcGUgd2hlbiB5b3UgYXJlIHVzaW5nIHRpZHl2ZXJzZSBhbmQgZHBseXIsIGFuZCB1c2UgYSByZXR1cm4gYWZ0ZXIgZWFjaCBgK2Agd2hlbiB5b3UgYXJlIHVzaW5nIGdncGxvdDIuDQoNCi0gVXNlIGluZGVudGF0aW9ucyBhbmQgbXVsdGlwbGUgbGluZXMgdG8gc3BsaXQgYXBhcnQgbG9uZyBmdW5jdGlvbnMgLSB0cnkgdG8gdXNlIG9uZSBpZGVhIHBlciBsaW5lLiBSIFN0dWRpbyBkb2VzIGEgZ29vZCBqb2Igb2YgbWFuYWdpbmcgaW5kZW50YXRpb25zIGZvciB5b3UgYXV0b21hdGljYWxseS4gDQoNCkhlcmUncyBhIHF1aWNrIGV4YW1wbGUgb2YgYSBmZXcgb2YgdGhlc2UgdGhpbmdzIGluIGFjdGlvbiBieSBwaXBpbmcgdG9nZXRoZXIgYSBmZXcgb3BlcmF0aW9ucy4gV2UgYG11dGF0ZWAgYSBuZXcgdmFyaWFibGUgY2FsbGVkIGBoaWdoX2luY29tZWAgaW4gYGFjc1RyYWN0c1BITC4yMDE2LnNmYCBhbmQgc2VuZCB0aGF0IGNhdGVnb3JpY2FsIHRvIHRoZSBgZmlsbGAgY29tbWFuZCBpbiBhIGBnZW9tX3NmYCBpbiBgZ2dwbG90MmAgdXNpbmcgdGhlIGBzZmAgcGFja2FnZS4gSW4gdGhlICJiYWQiIGV4YW1wbGUgd2UganVzdCB3cml0ZSBhIGJpZyBnaWFudCBzZW50ZW5jZS4gSW4gdGhlICJiZXR0ZXIiIGV4YW1wbGUgd2UgdXNlIHJldHVybnMgYWZ0ZXIgb3VyIHBpcGVzIGFuZCBwbHVzIHNpZ25zLCBhbmQgd2UgYnJlYWsgdXAgb3VyIGBpZmVsc2VgIHN0YXRlbWVudCB1c2luZyByZXR1cm5zIHRvIG1ha2UgaXQgZWFzaWVyIHRvIHJlYWQuDQoNCmBgYHtyIGV2YWw9RkFMU0V9DQojIEJhZA0KDQphY3NUcmFjdHNQSEwuMjAxNi5zZiAlPiUgbXV0YXRlKGhpZ2hfaW5jb21lID0gaWZlbHNlKG1lZF9ISF9JbmNvbWUuMjAxNiA+IDEwMDAwMCwgIk92ZXIgMTAwayIsICJVbmRlciAxMDBrIikpICU+JSBnZ3Bsb3QoKSsgZ2VvbV9zZihhZXMoZmlsbCA9IGhpZ2hfaW5jb21lKSkgDQpgYGANCg0KDQpgYGB7cn0NCiMgQmV0dGVyDQoNCmFjc1RyYWN0c1BITC4yMDE2LnNmICU+JSANCiAgbXV0YXRlKGhpZ2hfaW5jb21lID0gaWZlbHNlKG1lZF9ISF9JbmNvbWUuMjAxNiA+IDEwMDAwMCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiT3ZlciAxMDBrIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiVW5kZXIgMTAwayIpKSAlPiUgDQogIGdncGxvdCgpKyANCiAgZ2VvbV9zZihhZXMoZmlsbCA9IGhpZ2hfaW5jb21lKSkgDQpgYGANCg0KDQpDaGVjayBvdXQgW2h0dHBzOi8vc3R5bGUudGlkeXZlcnNlLm9yZy9pbmRleC5odG1sXSh0aGUgdGlkeXZlcnNlIFN0eWxlIEd1aWRlKSBmb3IgbW9yZSBpbmZvIG9uIGhvdyB0byB3cml0ZSBsZWdpYmxlIGNvZGUuDQoNCiMjIERhdGEgYW5kIGZpbGUgbWFuYWdlbWVudA0KDQpOZXcgYWRkaXRpb25zIGZvciAyMDI0IHdpbGwgaW5jbHVkZSB0aXBzIG9uIGhvdyB0byBvcmdhbml6ZSB5b3VyIFIgUHJvamVjdHMsIFIgc2NyaXB0cywgZGF0YSwgbWFya2Rvd25zIGFuZCBtb3JlLg0KDQojIDEyLiBRdWVyeWluZyBBUElzIGFuZCBSZWFkaW5nIERhdGEgZnJvbSBXZWIgU291cmNlcw0KDQpVcGNvbWluZyBhZGRpdGlvbnMgaW5jbHVkZSB2aWduZXR0ZXMgb24gU29jcmF0YSBhbmQgUkVTVCBBUEkgcXVlcnkgZnVuY3Rpb25hbGl0aWVzIGZvciBSLCBhbmQgdXNpbmcgdHdvLWZhY3RvciBhdXRoZW50aWZpY2F0aW9uIHdpdGggYm94ciBhbmQgUGVubkJveC4NCg0KUHJlc2VudGx5LCB2aWduZXR0ZXMgZm9yIGJveHIgY2FuIGJlIGZvdW5kIFtodHRwczovL2dpdGh1Yi5jb20vbWFmaWNobWFuL2JveHJdKGF0IHRoaXMgcmVwbykuDQoNCiMgMTMuIE90aGVyIFNwYXRpYWwgUGFja2FnZXMNCg0KVXBjb21pbmcgYWRkaXRpb25zIHdpbGwgaW5jbHVkZSB2aWduZXR0ZXMgZm9yIHNvbWUgb2YgdGhlIGZvbGxvd2luZyBwYWNrYWdlcyAtIExlYWZsZXQsIG9zbWRhdGEsIHJhc3Rlci90ZXJyYSwgYW5kIGNyc19zdWdnZXN0Lg0KDQpDdXJyZW50bHksIGEgdmlnbmV0dGUgb24gdGhlIHVzZSBvZiBvc21kYXRhIGZvciBPcGVuIFN0cmVldCBNYXAgZGF0YSBjYW4gYmUgZm91bmQgW2h0dHBzOi8vZ2l0aHViLmNvbS9tYWZpY2htYW4vb3NtX2RhdGFdKGF0IHRoaXMgcmVwbykuDQoNCiMjIDEzLjEuIEludGVyYWN0aXZlIE1hcHBpbmcgd2l0aCBtYXB2aWV3DQoNClRoZSBgbWFwdmlld2AgcGFja2FnZSBpcyBhIHF1aWNrIGFuZCBlYXN5IHdheSB0byBsb29rIGF0IHNwYXRpYWwgZGF0YSBpbnRlcmFjdGl2ZWx5IHdpdGggYSBiYXNlbWFwLiBgbWFwdmlld2AgY2FuIGhhbmRsZSBtYW55IGtpbmRzIG9mIHNwYXRpYWwgZGF0YSwgaW5jbHVkaW5nIGBzZmAgdmVjdG9ycy4gSSBsaWtlIHRvIHVzZSBgbWFwdmlld2AgdG8gbWFrZSBxdWFsaXR5IGNoZWNrcyBvbiBteSBkYXRhLCBhbmQgcXVpY2tseSBzY2FuIGZvciBwYXR0ZXJucyBhdCBtdWx0aXBsZSBzY2FsZXMuIFdpdGggc3RhdGljIG1hcHMgaW4gYGdncGxvdGAgd2l0aCBgc2ZgLCBzb21ldGltZXMgdGhpcyBjYW4gYmUgY3VtYmVyc29tZS4gYG1hcHZpZXdgIGhhcyBzb21lIGRyYXdiYWNrcyAtIGl0J3MgaGFyZCB0byBtYWtlIHNvcGhpc3RpY2F0ZWQgc3R5bGluZywgYW5kIGl0IGNhbiBjaG9rZSBvbiBsYXJnZSBkYXRhIHNldHMgKG1vcmUgdGhhbiBhIGZldyB0aG91c2FuZCBvYmplY3RzKS4NCg0KSGVyZSBpcyBhIHJlYWxseSBxdWljayB3ZWIgbWFwIG9mIG91ciBgcmVzdGF1cmFudHNgIGRhdGEgc2V0IGZyb20gU2VjdGlvbiA5IGluIGBtYXB2aWV3YA0KDQpUbyB1c2UgYG1hcHZpZXdgIGZvciB0aGUgZmlyc3QgdGltZSB5b3Ugc2hvdWxkIGluc3RhbGwgYW5kIGxvYWQgdGhlIHBhY2thZ2UuIChJZiB5b3UgYWxyZWFkeSBoYXZlIGl0IGluc3RhbGxlZCwgc2tpcCB0aGlzIHN0ZXApOg0KDQpgYGB7ciBpbnN0YWxsX3BhY2thZ2VzX21hcHZpZXcsIHdhcm5pbmcgPSBGQUxTRSwgZXZhbCA9IEZBTFNFfQ0KaW5zdGFsbC5wYWNrYWdlcygnbWFwdmlldycpDQpgYGANCg0KTm93IGxvYWQgdGhlbSBpbnRvIHlvdXIgUiBlbnZpcm9ubWVudCB3aXRoIGEgYGxpYnJhcnlgIGNhbGw6DQoNCmBgYHtyIHNldHVwX3BhY2thZ2VzMywgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9DQpsaWJyYXJ5KG1hcHZpZXcpDQpgYGANCg0KTm93IHdlIGNhbiBzaW1wbHkgZmVlZCBgcmVzdGF1cmFudHNgIHRvIHRoZSBgbWFwVmlld2AgZnVuY3Rpb24uIFlvdSBjYW4gY2xpY2sgb24gdGhlIHBvaW50cyBhbmQgc2VlIHRoZWlyIGF0dHJpYnV0ZXMuIFNpbXBsZSENCg0KSWYgeW91IGhhdmUgZGF0YSB0byBzeW1ib2xvZ2l6ZSwgeW91IGNhbiB1c2UgdGhlIGB6Y29sYCBhcmd1bWVudCB0byBzcGVjaWZ5IGl0Lg0KDQpgYGB7ciBzaW1wbGVfbWFwdmV3LCB3YXJuaW5nPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQ0KbWFwVmlldyhyZXN0YXVyYW50cykNCg0KYGBgDQoNCiMgMTQuIFJlc291cmNlcyBhbmQgdGV4dHMNCg0KVGhpcyBzZWN0aW9uIGNvbnRhaW5zIG9ubGluZSByZXNvdXJjZXMgZm9yIGxlYXJuaW5nIFIgYW5kIHRyb3VibGVzaG9vdGluZyBjb2RlLWJhc2VkIHByb2JsZW1zLg0KDQojIyAxNC4xLiBUcm91Ymxlc2hvb3RpbmcgLSBIb3cgdG8gYXNrIGEgY29kaW5nIHF1ZXN0aW9uDQoNCkl0J3Mgc3VwZXIgaW1wb3J0YW50IHRvIGtub3cgaG93IHRvIGFzayBhIGdvb2QgcXVlc3Rpb24gb24gdGhlIGludGVybmV0IG9yIG9uIHRoZSBjbGFzcyBmb3J1bSBhYm91dCBzb21lIGlzc3VlIHlvdSBhcmUgaGF2aW5nLiBHb29nbGluZyBhcm91bmQgaXMgYSByZWFsbHkgZ29vZCB3YXkgdG8gZmluZCBoZWxwIC0gc28geW91IGNhbiBnb29nbGUgc29tZXRoaW5nIGFib3V0IHRoZSBlcnJvciB5b3UgYXJlIGhhdmluZywgb3IgbG9vayBvbiBbaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbV0oaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbSksIHdoaWNoIGlzIGEgY29tbXVuaXR5IG9mIHBlb3BsZSBkb2luZyBRJkEgYW5kIHRyb3VibGVzaG9vdGluZy4NCg0KWW91IGNhbiBsb29rIHVwIGRldGFpbHMgYWJvdXQgdGhlIGZ1bmN0aW9ucyB5b3UgYXJlIHVzaW5nIGluIFIgdXNpbmcgdGhlIGA/P2AgY29tbWFuZCBpbiB5b3VyIGNvbnNvbGUgbGlrZSB0aGlzOg0KDQpgPz9yZWFkLmNzdmANCg0KWW91IGNhbiBhbHNvIGNoZWNrIG91dCB0aGUgZG9jdW1lbnRhdGlvbiBhYm91dCB0aGUgcGFja2FnZXMsIGZ1bmN0aW9ucyBhbmQgdGhlIGFyZ3VtZW50cyB0aGV5IHRha2UgaW4gdGhlIFBhY2thZ2VzIHRhYiBpbiB0aGUgVmlld2VyIHdpbmRvdyBpbiBSIFN0dWRpby4NCg0KSWYgeW91IGFyZSBnb2luZyB0byBhc2sgYSBxdWVzdGlvbiBvbiB0aGUgY2xhc3MgZm9ydW0sIGluY2x1ZGUgc29tZSBjb2RlLCBhbmQgZGV0YWlscyBhYm91dCB5b3VyIHByb2JsZW0gLSBsaWtlIHRoZSBkYXRhIHNvdXJjZXMgdGhhdCB5b3UgdHJpZWQgdG8gdXNlIGFuZCBob3cgdGhleSBsb29rLiBUaGlzIGFsbG93cyBzb21lYm9keSB0byByZXByb2R1Y2UgdGhlIGlzc3VlIG9uIHRoZWlyIG93biwgb3IgYXQgbGVhc3QgdW5kZXJzdGFuZCBpdC4NCg0KKkEgZ29vZCBxdWVzdGlvbiBnaXZlcyB5b3VyIHJlc3BvbmRlbnQgbG90cyBvZiBiYWNrZ3JvdW5kIGluZm86Kg0KDQotIFdoYXQgaXMgdGhlIG5hdHVyZSBvZiB0aGUgZGF0YSBpbiB5b3VyIHF1ZXN0aW9uPyBDYW4geW91IHBvc3QgYSBgZ2xpbXBzZWAgY29tbWFuZCBzbyB3ZSBjYW4gc2VlIHdoYXQgdGhlIGNvbHVtbiBuYW1lcyBhbmQgZGF0YSB0eXBlcyBhcmU/DQoNCi0gV2hhdCBpcyB0aGUgY29kZSBzbmlwcGV0IHRoYXQgZGlkbid0IHJ1biBjb3JyZWN0bHk/DQoNCi0gV2hhdCBraW5kIG9mIGVycm9yIG1lc3NhZ2UgZGlkIHlvdSBnZXQ/DQoNCi0gV2hhdCBwYWNrYWdlcyBkbyB5b3UgaGF2ZSBsb2FkZWQ/DQoNCi0gRGlkIHlvdSBnb29nbGUgdGhlIHF1ZXN0aW9uIGJlZm9yZSB5b3UgY2FtZSBhbmQgYXNrZWQgZm9yIGhlbHA/IChUaGUgYW5zd2VyIHNob3VsZCBiZSB5ZXMpLg0KDQpIZXJlIGFyZSBzb21lIGV4YW1wbGVzIG9mIHNvbWUgYmFkIHF1ZXN0aW9ucywgYW5kIHNvbWUgYWx0ZXJuYXRpdmVzOg0KDQpDbGFzcyBmb3J1bSBpc3N1ZTogDQoiSSB0cmllZCB0byBtYWtlIHRoaXMgY29kZSB3b3JrIChwYXN0ZXMgY29kZSBibG9jaykgYW5kIEkgZ290IGFuIGVycm9yLiIgDQoNCi0+IA0KDQoiSSByYW4gdGhpcyBjb2RlIGJsb2NrIHVzaW5nIGRhdGEgdGhhdCBsb29rIGxpa2UgdGhpcyAoc2hvdyB0aGUgcmVzdWx0cyBvZiBhIGBoZWFkYCBvciBgZ2xpbXBzZWAgY29tbWFuZCkgYW5kIGdvdCB0aGlzIGVycm9yIChjb3B5IGFuZCBwYXN0ZSBlcnJvciBtZXNzYWdlKS4gSSBoYXZlIHRoZSBmb2xsb3dpbmcgcGFja2FnZXMgaW5zdGFsbGVkLi4uLiINCg0KDQpHb29nbGUgcXVlcnk6DQoNCiJIb3cgZG8geW91IGdldCBteSBmaWxlIHRvIGpvaW4gdGhlIG90aGVyPyINCg0KLT4NCg0KIlNwYXRpYWwgam9pbnMgaW4gciB3aXRoIHNmIg0KDQojIyAxNC4yLiBUcm91Ymxlc2hvb3RpbmcgdXNpbmcgQ2hhdCBCb3RzDQoNCkdlbmVyYXRpdmUgQUkgc29sdXRpb25zIGxpa2UgQ2hhdCBHUFQgY2FuIGJlIHVzZWZ1bCBhaWRzIGluIGRlYnVnZ2luZyBjb2RlLCB3cml0aW5nIGZ1bmN0aW9ucywgYW5kIHNvbHZpbmcgY29kaW5nIHByb2JsZW1zLiBJdCdzIGhlbHBmdWwgdG8gdGhpbmsgb2YgR1BUIGFzIGEgaGlnaGx5IHN0cnVjdHVyZWQsIGhpZ2hseSBjdXN0b21pemVkIHNlYXJjaCBlbmdpbmUuIFRvIGdldCB0aGUgYmVzdCByZXN1bHRzLCB1c2UgdGhlIHNhbWUga2luZCBvZiBzdGVwcyBkZXNjcmliZWQgaW4gdGhlIHByZXZpb3VzIHNlY3Rpb24gYWJvdXQgYXNraW5nIGEgcXVlc3Rpb24gb24gYSBjbGFzcyBtZXNzYWdlIGJvYXJkLg0KDQpLZWVwIGluIG1pbmQgdGhhdCBpZiB5b3UgaGF2ZSBub3QgZWR1Y2F0ZWQgeW91cnNlbGYgYWJvdXQgaG93IGEgY29kaW5nIGxhbmd1YWdlIHdvcmtzLCB5b3Ugbm90IG9ubHkgY2FuJ3QgYXNrIGdvb2QgcXVlc3Rpb25zIG9mIGEgR1BULCB5b3UgY2Fubm90IGF1ZGl0IHRoZSByZXN1bHRzIG9mIHF1ZXJpZXMgZWZmZWN0aXZlbHkuIFlvdSBtaWdodCBub3QgYmUgYWJsZSB0byBpbnRlcnByZXQgdGhlIHZhcnlpbmcgZGVncmVlcyBvZiB3cm9uZ25lc3MgeW91IGdldCBmcm9tIEdQVCBvdXRwdXRzLiBZb3UgbWlnaHQgYWxzbyBnZW5lcmF0ZSBvdXRwdXRzIHRoYXQgYXJlIGVpdGhlciBhKSBpbm9wZXJhYmxlIG9yIHdvcnNlIGIpIGZ1bmN0aW9uYWwgYnV0IGZ1bmRhbWVudGFsbHkgd3JvbmcgZm9yIHlvdXIgdGFzay4NCg0KQ29uc2lkZXIgdGhlIGZvbGxvd2luZyBzaXR1YXRpb24gd2hlcmUgSSB3YW50IHRvIGBtdXRhdGVgIGEgbmV3IGNvbHVtbiBhbmQgSSBjb25zdWx0IENoYXRHUFQgYWJvdXQgaG93IHRvIGZvcm1hdCB0aGUgZGF0YS4gSSB3aWxsIHByb3ZpZGUgYW4gZXhhbXBsZSBvZiBhIGdvb2QgcXVlcnkgYW5kIGEgYmFkIHF1ZXJ5IGFzIEkgdHJ5IHRvIHNvbHZlIG15IHByb2JsZW0uDQoNCkkgYW0gZ29pbmcgdG8gbG9hZCB0aGUgZGF0YSBzZXQgYG10Y2Fyc2AgKGEgZGF0YSBzZXQgYWJvdXQgY2FycyBhbmQgdGhlaXIgYXR0cmlidXRlcyB0aGF0IGNvbWVzIHdpdGggUiBhbmQgaXMgb2Z0ZW4gdXNlZCB0byBjcmVhdGUgY29kZSBleGFtcGxlcyBqdXN0IGxpa2UgdGhpcyEpLg0KDQpgYGB7cn0NCmRhdGEobXRjYXJzKQ0KDQpoZWFkKG10Y2FycykNCmBgYA0KDQpJJ2QgbGlrZSB0byBgbXV0YXRlYCBhIGNvbHVtbiB0aGF0IGlzIGEgcmF0aW8gb2YgaG9yc2Vwb3dlciAoYGhwYCkgdG8gbWlsZXMgcGVyIGdhbGxvbiAoYG1wZ2ApLiBTaW1wbHksIEkgd291bGQgc2F5IHNvbWV0aGluZyBsaWtlIHRoaXM6DQoNCmBtdGNhcnMgJT4lIG11dGF0ZShocF9tcGdfcmF0aW8gPSBocC9tcGcpYA0KDQpIb3dldmVyLCBJIGdldCB0b25zIG9mIGRlY2ltYWwgcGxhY2VzIHdoZW4gSSBkbyB0aGlzLCBhbmQgSSBkb24ndCBrbm93IGhvdyB0byBmaXggdGhhdCEgU28gSSBhc2sgQ2hhdEdQVDoNCg0KKkhvdyBkbyBJIHJvdW5kIHRvIHR3byBkZWNpbWFsIHBsYWNlcyBpbiBSPyoNCg0KQ2hhdEdQVCB3aWxsIGdpdmUgbWUgYSBnZW5lcmljIGNvZGUgZXhhbXBsZSB0aGF0IEkgaGF2ZSB0byBpbnRlcnByZXQgYW5kIGFwcGx5IHRvIG15IGNvZGUsIGJ1dCBpZiBJIGFzayBpdA0KDQoqQ2FuIHlvdSBhZGp1c3QgdGhpcyB0aWR5IFIgY29kZSB0byByb3VuZCB0aGUgb3V0cHV0IHRvIHR3byBkZWNpbWFsIHBsYWNlcz8qIA0KDQoqbXRjYXJzICU+JSBtdXRhdGUoaHBfbXBnX3JhdGlvID0gaHAvbXBnKSoNCg0KSSB3aWxsIGdldCBhIHByZWNpc2UgcmUtd29ya2luZyBvZiBteSBjb2RlLg0KDQpUaGUgbW9yZSBzb3BoaXN0aWNhdGVkIHlvdXIgcHJvYmxlbSwgdGhlIG1vcmUgaW1wb3J0YW50IGl0IGlzIHRoYXQgeW91IGFzayBhIHZlcnkgc3BlY2lmaWMgcXVlc3Rpb24sIHNvbWV0aW1lcyBldmVuIHByb3ZpZGluZyBiYWNrZ3JvdW5kIGluZm9ybWF0aW9uIGFib3V0IHBhY2thZ2VzLCBkYXRhIHR5cGVzLCBhbmQgc28gb24uDQoNCkxldCdzIHNheSBJIHdhbnRlZCB0byBtYWtlIGEgcGxvdCBvZiBgbXBnYCwgd2hpY2ggaXMgYSBjb250aW51b3VzIHZhcmlhYmxlLCBidXQgSSBhbSBpbnRlcmVzdGVkIGluIHRoZSBudW1iZXIgb2YgY2FycyB0aGF0IGFyZSBvdmVyIG9yIHVuZGVyIDIwIG1wZy4gSSBjYW4gYXNrIGEgdmVyeSBzcGVjaWZpYyBxdWVzdGlvbiBsaWtlIHRoZSBmb2xsb3dpbmc6DQoNCipIb3cgZG8gSSBhZGp1c3QgdGhpcyB0aWR5IFIgY29kZSBzbyB0aGF0IHRoZSBjb250aW51b3VzIHZhcmlhYmxlIG1wZyBpcyBlbmdpbmVlcmVkIGFzIGEgY2F0ZWdvcmljYWwgdmFyaWFibGUgdGhhdCBpcyBlaXRoZXIgb3ZlciBvciB1bmRlciAyMC4gVGhlIHJlc3VsdCBzaG91bGQgYmUgYSBiYXIgcGxvdCBhbmQgdGhlIGZlYXR1cmUgdHJhbnNmb3JtYXRpb24gc2hvdWxkIHRha2UgcGxhY2Ugd2l0aGluIHRoZSBnZ3Bsb3QgY2FsbC4qDQoNCipnZ3Bsb3QobXRjYXJzKSsgZ2VvbV9oaXN0b2dyYW0oYWVzKG1wZykpKg0KDQpUaGUgcmVzdWx0IGlzIGFzIGZvbGxvd3M6DQoNCmBgYHtyfQ0KZ2dwbG90KG10Y2FycywgYWVzKHggPSBpZmVsc2UobXBnID4gMjAsICJPdmVyIDIwIiwgIlVuZGVyIDIwIikpKSArDQogIGdlb21fYmFyKGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJzdGVlbGJsdWUiKSArDQogIGxhYnMoeCA9ICJNUEcgQ2F0ZWdvcnkiLCB5ID0gIkZyZXF1ZW5jeSIsIHRpdGxlID0gIkJhciBQbG90IG9mIE1QRyBPdmVyL1VuZGVyIDIwIikNCg0KYGBgDQoNCiMjIDE0LjMuIFIgQ2hlYXQgU2hlZXRzDQoNClIgU3R1ZGlvJ3Mgd2Vic2l0ZSBoYXMgc29tZSByZWFsbHkgZ29vZCBbY2hlYXQgc2hlZXRzXShodHRwczovL3d3dy5yc3R1ZGlvLmNvbS9yZXNvdXJjZXMvY2hlYXRzaGVldHMvKSB3aXRoIHNpbXBsZSB3YWxrdGhyb3VnaHMgb2YgYmFzaWMgZnVuY3Rpb25zIGluIHNvbWUgY29yZSBwYWNrYWdlcw0KDQotIFtnZ3Bsb3QyIC0gZ3JhcGhpY3MgYW5kIHBsb3RzXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcnN0dWRpby9jaGVhdHNoZWV0cy9tYWluL2RhdGEtdmlzdWFsaXphdGlvbi5wZGYpDQoNCi0gW2RwbHlyIC0gbWFuaXB1bGF0aW5nIGRhdGEgZnJhbWVzXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcnN0dWRpby9jaGVhdHNoZWV0cy9tYWluL2RhdGEtdHJhbnNmb3JtYXRpb24ucGRmKQ0KDQotIFtzZiAtIHZlY3RvciBHSVNdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9yc3R1ZGlvL2NoZWF0c2hlZXRzL21haW4vc2YucGRmKQ0KDQotIFtSIG1hcmtkb3duIC0gZG9jdW1lbnQgcHVibGlzaGluZywgaW5jbHVkZXMga25pdHJdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9yc3R1ZGlvL2NoZWF0c2hlZXRzL21haW4vcm1hcmtkb3duLnBkZikNCg0KLSBbbHVicmlkYXRlIC0gZGF0ZXMgYW5kIHRpbWVzXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcnN0dWRpby9jaGVhdHNoZWV0cy9tYWluL2x1YnJpZGF0ZS5wZGYpDQoNCi0gWyhyZWFkciAtIGxvYWRpbmcgZGF0YSldKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9yc3R1ZGlvL2NoZWF0c2hlZXRzL21haW4vZGF0YS1pbXBvcnQucGRmKQ0KDQojIyAxNC40LiBTdHlsZSBHdWlkZXMNCg0KSGFuZHkgdGlwcyBmb3IgbWFraW5nIHlvdXIgY29kZSByZWFkYWJsZQ0KDQotIFtSIFN0eWxlIEd1aWRlXShodHRwczovL2dvb2dsZS5naXRodWIuaW8vc3R5bGVndWlkZS9SZ3VpZGUuaHRtbCkNCg0KLSBbVGlkeXZlcnNlIFN0eWxlIEd1aWRlXShodHRwczovL3N0eWxlLnRpZHl2ZXJzZS5vcmcvKQ0KDQpDb2xvciBQYWxldHRlIGlkZWFzDQoNCi0gW0NvbG9yIEJyZXdlciAyIC0gQ29sb3IgUGFsZXR0ZXMgYW5kIFJhbXBzXShodHRwczovL2NvbG9yYnJld2VyMi5vcmcvI3R5cGU9c2VxdWVudGlhbCZzY2hlbWU9QnVHbiZuPTMpDQoNCiMjIDE0LjUuIE9wZW4gU291cmNlIEdJUyBSZXNvdXJjZXMNCg0KLSBbc3BhdGlhbHJlZmVyZW5jZS5vcmddKGh0dHA6Ly9zcGF0aWFscmVmZXJlbmNlLm9yZykNCg0KIyMgMTQuNi4gQm9va3MNCg0KSWYgeW91J3JlIGluIGEgY2xhc3Mgd2l0aCBtZSAtIHRoZXNlIGJvb2tzIGFyZSBsaWtlbHkgdG8gYmUgYW1vbmcgdGhlIHJlcXVpcmVkIG9yIHN1cHBsZW1lbnRhbCByZWFkaW5ncy4gVGhleSBhcmUgYWxsIGZhbnRhc3RpYywgb3BlbiBzb3VyY2UsIGFuZCBleHRyZW1lbHksIGV4dHJlbWVseSB1c2VmdWwuIEhlcmUgdGhleSBhcmUgaW4gImJvb2tkb3duIiBmb3JtIGZvciBmcmVlLg0KDQotIFtSIGZvciBEYXRhIFNjaWVuY2UgLSBIYWRsZXkgV2lja2hhbV0oaHR0cHM6Ly9yNGRzLmhhZC5jby5uei8pIC0gU3VwcGxlbWVudGFyeSB0ZXh0IGZvciBDUExOIDY3NQ0KDQotIFtQdWJsaWMgUG9saWN5IEFuYWx5dGljcyAtIEtlbiBTdGVpZl0oaHR0cHM6Ly91cmJhbnNwYXRpYWwuZ2l0aHViLmlvL1B1YmxpY1BvbGljeUFuYWx5dGljcy8pIC0gVGV4dCBmb3IgTVVTQSA1MDgNCg0KLSBbU3BhdGlhbCBTdGF0aXN0aWNzIGZvciBEYXRhIFNjaWVuY2UgLSBQYXVsYSBNb3JhZ2FdKGh0dHBzOi8vd3d3LnBhdWxhbW9yYWdhLmNvbS9ib29rLXNwYXRpYWwvaW5kZXguaHRtbCkNCg0KLSBbRGF0YSBWaXN1YWxpemF0aW9uIC0gS2llcmFuIEhlYWx5XShodHRwczovL3NvY3Zpei5jby8pDQoNCi0gW0dlb2NvbXB1dGF0aW9uIHdpdGggUiAtIFJvYmluIExvdmVsYWNlXShodHRwczovL2dlb2NvbXByLnJvYmlubG92ZWxhY2UubmV0LykNCg0KLSBbQW5hbHl6aW5nIFVTIENlbnN1cyBEYXRhIC0gS3lsZSBXYWxrZXJdKGh0dHBzOi8vd2Fsa2VyLWRhdGEuY29tL2NlbnN1cy1yLykNCg0KLSBbR2Vvc3BhdGlhbCBIZWFsdGggRGF0YSAtIFBhdWxhIE1vcmFnYV0oaHR0cHM6Ly93d3cucGF1bGFtb3JhZ2EuY29tL2Jvb2stZ2Vvc3BhdGlhbC8pDQoNCi0gW1IgTWFya2Rvd24gQ29va2Jvb2sgLSBZaWh1aSBYaWUsIENocmlzdG9waGUgRGVydmlldXgsIEVtaWx5IFJpZWRlcmVyXShodHRwczovL2Jvb2tkb3duLm9yZy95aWh1aS9ybWFya2Rvd24tY29va2Jvb2svKQ0KDQojIyAxNC43LiBBcnRpY2xlcyBhbmQgT3RoZXIgUmVzb3VyY2VzDQoNCi0gW0EgZGV0YWlsZWQgZ3VpZGUgdG8gY29sb3JzIGluIGRhdGEgdmlzIHN0eWxlIGd1aWRlcyAtIExpc2EgQ2hhcmxvdHRlIE11dGhdKGh0dHBzOi8vYmxvZy5kYXRhd3JhcHBlci5kZS9jb2xvcnMtZm9yLWRhdGEtdmlzLXN0eWxlLWd1aWRlcy8pDQoNCiMgMTQuOC4gRkFRDQoNCkhhdmUgYSBxdWVzdGlvbiBvciBzb21ldGhpbmcgeW91J2QgbGlrZSB0byBzZWUgaW5jbHVkZWQgaGVyZT8gQWRkIGl0IHRvIFtodHRwczovL2dpdGh1Yi5jb20vbWFmaWNobWFuL1JfRkFRX0Zvcl9QbGFubmVycy9pc3N1ZXNdKHRoZSAiaXNzdWVzIiBzZWN0aW9uIG9mIHRoZSByZXBvIGZvciB0aGlzIHRleHQhKQ==