Associated Material

Module: Module 07 - Combining data

Readings

Combining Data

So far, each data set we have worked with has been contained in a single monolithic data file with all variables as columns and all observations as rows. In a real reserach situation, this will not always be the case. Complex data sets are often deployed as multiple related files. Each file represents a single, well-defined data entity, and the relationships between entities are part of the data provided. In order to answer our research questions, we need to be able to combine these different files in various, sometimes complex ways.

Describing the Data Universe

The complex systems we study generally consist of many different entities (“things”, like participants, test results, experimental conditions, species, researchers, equipment, lakes, etc.). Each entity has important properties (e.g. subject ID, reaction time, equipment type, etc.). There are complex relationships between entities (e.g. a particular participant was given a specific experimental treatment, a particular animal species was seen in a specific lake). Together, these constitute our data universe.

To answer our research questions, we need to be able to represent the data universe in an organised way. The challenges this imposes can be seen even in comparatively simple contexts.

For example, assume you are involved with the local softball competitions. There are multiple leagues, each league has multiple teams, each team has a coach and many players. Your job might be to maintain contact information so that people can be informed when there are changes to the game schedule.

As an experienced computer user, you can decide to put all this informtion into some nice spreadsheets. For example, you might have a spreadsheet like this for one team:

The Gnomes

The Gnomes

You would have a comparable spreadsheet for each team in each league.

If your data only need to be available to people, this system would probably be adequate, if a little cumbersome. However, if you wanted to load your data into a computer for processing, this file format will not work. Recall that R needs a rectangle of data with column headers across the top and one row for each data point. You couldn’t even import your data using this “human-friendly” spreadsheet.

To provide the same information in a format that a computer can understand, you must do the following:

  1. Identify the cohesive entities in your data
  2. Identify the properties for each entity
  3. Make a separate file for each entity
  4. Make the relationships between entities explicit in their data (see examples below).

In our Sports League example, we see that we have two clear entities – Teams and Players. A Team has four properties: the name, the league, the coach, and the coach’s phone number. A Player has four properties: their team, their name, their phone number, and the position they play. In our current format, Teams and Players are all mixed into the same file. For the computer, we need to separate them. Making each property a column, and each instance a row, we can reformat our data into two separate files, like this:

Sports League for Computers

Sports League for Computers

Note that these files are in exactly the rectangular “rows are instances; columns are variables” format that R requires. You can easily read them into R and start processing them. For example, if there are 800 players in the Players spreadsheet, how would you determine the number of players assigned to each position, across the entire league?

Assuming you have used read.csv to import the Players spreadsheet into a dataframe players_df, you could say: players_df %>% group_by(PlayerPosition) %>% count().

By making two separate spreadsheets, you have separated the information about players from the information about teams. It is essential that, when your data are reorganised in this way, no information has been lost. That is can you answer the same questions with this arrangement as you could with the original one?

For example, with the first human-friendly version, it is easy to answer “Who is Fred’s coach?”, because Fred and his coach are in the same spreadsheet. But how do you do it with the computer-friendly organisation where Fred’s data and his Team’s data are in different files?

As a person you would say “Fred’s team is The Gnomes (from sheet 2) and Bob is the coach of The Gnomes (from sheet 1) therefore Bob is the coach of Fred.” To do it in R (as you would if you needed to perform this operation for 800 players) you would select the TeamName value for Fred in the Players file, then filter for that value in the Teams file and select the Coach column. After you learn how to write loops, you could loop through all the players and do this computation, accruing the results into a vector. Then you could add that vector as a new column somewhere to match up player and coach. However, R provides much more succinct ways to perform this operation.

This is fortunate, because real research data sets are often deployed in many more than just the two files we needed in our toy Sports League example. Large, complex data sets are typically not managed with spreadsheets at all, they are managed as databases. While this term is used quite broadly in casual discussion, it technically refers specifically to Relational Database Management Systems, special applications that are optimised for searching, deleting, filtering, and generally managing huge amounts of data.

In an RDBMS each entity has its own table and the tools have EXTREMELY strict rules about the contents and structure of their entities. The data universe is broken down into atomic pieces of data and recombined through the rules of a mathematical framework called the Relational Algebra. It is a beautiful thing, but you end up with a lot of tables.

The figure below is an Entity Relationship Diagram. It shows the structure of the database for a relatively simple database system that keeps track of Food Diaries – where people record what they eat for nutritional analysis.

Example ERD

Example ERD

When real data are exported to you for processing, therefore, you can end up with a lot of separate files. Using R, you have to be able to recombine data as needed to answer your specific research questions.

Simple Combining (Binds)

Before we look at the sometimes complex issues around combining tables representing different entities, we will quickly cover two simpler combining situations that occur when you have, essentially, a single table that has been broken into multiple input files.

Our data for today come from the Geckos and Gumboots project. Geckos and Gumboots is a multi-year, multi-disciplinary reconstruction of wild New Zealand Gecko habitat on the Otago Peninsula (South Island, New Zealand). A large area of land on the peninsula has been planted with native gecko habitat species, divided into replicate quadrats and, at intervals, data are collected about plant growth, and invertebrate, predator, and gecko population abundance. Undergraduate students participate in data collection and analysis to gain experience with ecology fieldwork.

Combining Rows

Assume the field ecologists have sent you some predator observation data for analysis. They have sent you three separate csv files, one for each of three data collection days.

Combining Rows

Combining Rows

All three files have exactly the same format – four variable columns and one row for each recorded predator observation. This is a nice way to get data – everything is already rectangular, and they have all the same columns in the same order. You simply need to gather them all up into a single dataframe – in R this is straightforward. Use function rbind, passing in all the data frames to be concatenated, separated by commas.

pred_01_df <- read.csv("data/predator_observations_2022_04_30.csv")
pred_02_df <- read.csv("data/predator_observations_2023_04_22.csv")
pred_03_df <- read.csv("data/predator_observations_2023_04_23.csv")

pred_all_df <- rbind(pred_01_df, pred_02_df, pred_03_df)
pred_all_df[50:60,]
#>    ObservationDate ReplicateSite  PredatorType CardType
#> 50      30/04/2022            R4 none detected     Chew
#> 51      30/04/2022            R5 none detected     Chew
#> 52      30/04/2022            R6      Hedgehog   Tunnel
#> 53      30/04/2022            R6         Mouse   Tunnel
#> 54      30/04/2022            R6 none detected     Chew
#> 55      22/04/2023            R1         Mouse     Chew
#> 56      22/04/2023            R1         Mouse   Tunnel
#> 57      22/04/2023            R3         Mouse     Chew
#> 58      22/04/2023            R3         Mouse   Tunnel
#> 59      22/04/2023            R3           Rat     Chew
#> 60      22/04/2023            R6         Mouse     Chew

Note that if the arguments to rbind don’t all have exactly the same columns, the function will, quite sensibly, throw an error:

# For purposes of illustrion, we drop the 4th column from pred_01_df
pred_01_df <- pred_01_df[ , -4]

# The rbind command now fails
pred_all_df <- rbind(pred_01_df, pred_02_df, pred_03_df)
#> Error in rbind(deparse.level, ...): numbers of columns of arguments do not match

Combining Columns

Less commonly, you might have multiple data sets with the same rows (obervations), but different columns (dependent and independent variables). In this case, use function cbind – the syntax is the same as for rbind.

In practice, this situation actually occurs very rarely. This is fortunate, because column binding is extremely risky. Column binding relies entirely upon the rows in the separate data frames being identical and in the same order; the function itself performs no checks on this constraint.

Because of the danger of mixing up data from different observations, I only use cbind when I am generating columns dynamically (in code) and wish to build a data frame from them.

Relational Combining (Joins)

Recall our Sports League data:

Sports League for Computers

Sports League for Computers

Our “research question” was how to find the name of Bob’s coach. More generally, assume we want to know the coaches of all the players. When working with data frames, this means we want to add columns to the Player’s table that contain the associated coach information for each row.

We noted that the Team table and the Player table have a common column – TeamName – and that we could match players and coaches by looking for matching values of TeamName. This is a requirement for relational combining – two tables must share a common column (or columns) that can be used to match observations betwen the tables. These common columns are called keys.

In formal databases, there are lots of rules about keys, particularly around uniqueness. Note how confusing it would be if there were two rows in the Teams data frame with teamname The Gnomes – which of the two coaches would you want to match with Fred? 1 For today we will assume that these requirements are met – for further details, see the R for Data Science chapter.

Join

In database terminology, the process of matching up records from multiple tables is called a join. R (more specifically tidyverse) uses this terminology as well. Solving problems like finding the coach for each player involves joining two or more data frames on one or more common columns.

To slightly complicate things, R does not provide a function called “join”. It provide six different functions which perform slightly different types of join operation. The function names are derived from the formal database terminology. Fortunately, in the vast majority of cases we encounter, you only need one of the join family – left_join. We will cover left_join here; see the additional readings for discussions of the less common forms.

The function left_join takes as arguments the two tables (data frames) you wish to join. The name left_join helps you to remember how to order your arguments – put the base table on the left (i.e. as the first argument). The base table is the one you wish to add columns to.

Recall that we wanted to know each player’s coach. We therefore want to add a CoachName column to the Players data frame. Thus Players is the base table. We pass it first into function ‘left_join’, along with the name of the key column.

# We need the tidyverse to be able to join
library(tidyverse)

# Assume we have imported out sports league data using read.csv
head(teams_df)
#>        TeamName          League   Coach   CoachPhone
#> 1    The Gnomes        Over 50s     Bob     474-5555
#> 2      The Orcs Women's Premier  Ngaire 021 333-4567
#> 3 The Arachnids       Under 15s Dorothy 022 987-6543
head(players_df)
#>   PlayerName      TeamName PlayerPosition  PlayerPhone
#> 1       Fred    The Gnomes     First Base 021 111 2222
#> 2      Sally    The Gnomes        Pitcher 021 222 3333
#> 3      Ethel    The Gnomes     Left Field 021 111 4444
#> 4       Lucy The Arachnids        Catcher     474-1234

# Perform the join
extended_players_df <- left_join(players_df, teams_df, by = "TeamName")

# The new data frame
extended_players_df
#>   PlayerName      TeamName PlayerPosition  PlayerPhone    League   Coach
#> 1       Fred    The Gnomes     First Base 021 111 2222  Over 50s     Bob
#> 2      Sally    The Gnomes        Pitcher 021 222 3333  Over 50s     Bob
#> 3      Ethel    The Gnomes     Left Field 021 111 4444  Over 50s     Bob
#> 4       Lucy The Arachnids        Catcher     474-1234 Under 15s Dorothy
#>     CoachPhone
#> 1     474-5555
#> 2     474-5555
#> 3     474-5555
#> 4 022 987-6543

The new dataframe has all the columns in Players, in the original order. It also has all the columns in Teams, in the original order. For each row of Players (the base table; the one on the left), the values of the Teams columns are taken from the row in Teams that has the same value in column TeamName (the key).

Exercise

What output do you expect if you reverse the order of players_df and teams_df in the call to left_join, above? Try to think through this exercise before testing the code. Hint: When you reach the point where you say “but that doesn’t make sense!”, recognise that the computer will be in the same situation.

Practice Joining

The Geckos and Gumboots project provides information about the plants observed growing in each quadrat (resource file plant_observations.csv). In a separate file, they provide additional information about plant species (resource file plant_species.csv). This mirrors the structure of the database, where these entities are stored separately to allow efficient data operations and complete representation of the data universe (e.g. to allow information to be stored for plant species that have not yet been observed, but which are expected to be in the future).

Plant Observation and Species Data Input Files

Plant Observation and Species Data Input Files

Your job: Produce a new data frame that adds family and common names for each plant observation.

# Load the input data frames
plant_obs_df <- read.csv("data/plant_observations.csv")
plant_species_df <- read.csv("data/plant_species.csv")

# Perform the join
extended_plant_obs_df <- left_join(plant_obs_df, plant_species_df, 
                                   join_by(PlantSpecies))

# Display a section of the result
extended_plant_obs_df[55:65,]
#>    ObservationDate ReplicateSite           PlantSpecies Height Circumference
#> 55      22/04/2023            R1      Jacobaea vulgaris     80         18.85
#> 56      22/04/2023            R4   Coprosma crassifolia     60         25.00
#> 57      22/04/2023            R4      Podocarpus laetus     70         25.00
#> 58      22/04/2023            R4     Coprosma propinqua     42         25.00
#> 59      22/04/2023            R5   Coprosma crassifolia     32         54.00
#> 60      22/04/2023            R2         Ulex europaeus     41         13.00
#> 61      22/04/2023            R2         Ulex europaeus     49         12.00
#> 62      22/04/2023            L2 Tetragonia implexicoma     19        540.00
#> 63      22/04/2023            L2        Myoporum laetum     26         40.00
#> 64      22/04/2023            L2        Isolepis cernua     35         25.00
#> 65      22/04/2023            L2        Isolepis cernua     15         52.00
#>     PlantFamilyNAme    PlantCommonName
#> 55       Asteraceae            Ragwort
#> 56        Rubiaceae                   
#> 57    Podocarpaceae Thin-barked t?tara
#> 58        Rubiaceae                   
#> 59        Rubiaceae                   
#> 60         Fabaceae              Gorse
#> 61         Fabaceae              Gorse
#> 62        Aizoaceae         NZ spinach
#> 63 Scrophulariaceae     Mousehole tree
#> 64       Cyperaceae   Slender clubrush
#> 65       Cyperaceae   Slender clubrush

A Complete Research Question

Package nycflights13 contains several data frames which hold historical data about airplane flights in and out of the several airports in New York city. There is one table for each important entity. Each table contains entity properties, and relation information.

  • airports: Airport FAA code, name, latitude, longitude, time zone, etc. for each airport
  • planes: Tail number, year of manufacture, and technical specification for each plane
  • airlines: FAA code and company name for each airline
  • flights: Date, departure and arrival times, plane information, distance, airline code, etc. for each flight

Your job: Determine which airline flew the total greatest distance using single engine planes.

Hint: Do not start typing code yet.

With any but the simplest problems, you should always make a plan before you start coding. Remember that computers need to have every single step laid out for them, and in ways they understand. It is often difficult to work out exactly what those steps should be.

Our intial plan might be:

  1. Find all the flights made by single engine planes
  2. Add the distances up for each airline
  3. See which is the largest.

But this plan is not sufficiently detailed for the computer. How, for example, will you tell the computer to determine which flights were made by single engine planes? The planes data frame contains the number of engines for each plane, but the flights data frame does not – it contains only the flight number (column flightnum). Fortunately, planes also contains flightnum so we can use that column as a key to join the two tables.

Our computer-friendly plan:

  1. Join flights and planes with flights as the base and tailnum as the key to add the plane information for each flight.
  2. From the resulting extended flights data, select those with only one engine (i.e. field engines == 1)
  3. The flights table contains column carrier to identify the airline. Use group_by and summarise to compute the total distance grouped by carrier.
  4. Pick the largest

Now you are ready to write your code:


library(nycflights13)

# 1. Join `flights` and `planes` with flights as the base and `tailnum` as the key to add the plane information for each flight.

flights_planes <- left_join(flights, planes, by = "tailnum")

# 2. From the resulting extended flights data, select those with only one engine (i.e. field engines == 1)

single_engine_flights <- flights_planes %>% filter(engines == 1)

# 3. The flights table contains column `carrier` to identify the aireline. Using group_by and summarise compute the total `distance` grouped by `carrier`.

carrier_distance <- single_engine_flights %>% group_by(carrier) %>% summarise(TotalDistance = sum(distance))

# 4. Pick the largest

# Sort descending
carrier_distance <- carrier_distance %>% arrange(desc(TotalDistance))

# Display
carrier_distance
#> # A tibble: 4 × 2
#>   carrier TotalDistance
#>   <chr>           <dbl>
#> 1 B6            1077735
#> 2 AA             882584
#> 3 MQ             244203
#> 4 FL               2286

This is close, but what if you don’t know what B6 stands for?. Recall that data frame airlines contains carrier code and full name. To improve your analysis clarity, extend your carrier_distance table to include the full airline name. What operation will you perform? What arguments will you provide?

left_join(carrier_distance, airlines, by = "carrier")
#> # A tibble: 4 × 3
#>   carrier TotalDistance name                       
#>   <chr>           <dbl> <chr>                      
#> 1 B6            1077735 JetBlue Airways            
#> 2 AA             882584 American Airlines Inc.     
#> 3 MQ             244203 Envoy Air                  
#> 4 FL               2286 AirTran Airways Corporation

You have now established that JetBlue Airways flew the greatest total distance using single engine planes.


  1. NB: There is a method for dealing with the “multiple coaches” problem, but it is out of scope for today’s module. Ask me for more detail if you’re interested.↩︎

LS0tCnRpdGxlOiAiQ29tYmluaW5nIgpkYXRlOiAiU2VtZXN0ZXIgMiwgMjAyMyIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeShrbml0cikKbGlicmFyeSh0aWR5dmVyc2UpCmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBjb21tZW50ID0gIiM+IiwKICBmaWcucGF0aCA9ICJmaWd1cmVzLzA3LyIsICMgdXNlIG9ubHkgZm9yIHNpbmdsZSBSbWQgZmlsZXMKICBjb2xsYXBzZSA9IFRSVUUsCiAgZWNobyA9IFRSVUUKKQpgYGAKCj4gIyMjIyBBc3NvY2lhdGVkIE1hdGVyaWFsCj4KPiBNb2R1bGU6IFtNb2R1bGUgMDcgLSBDb21iaW5pbmcgZGF0YV0oMDctY29tYmluZS5odG1sKQo+Cj4gUmVhZGluZ3MKPgo+IC0gW1IgZm9yIERhdGEgU2NpZW5jZSBDaGFwdGVyIDEzXShodHRwczovL3I0ZHMuaGFkLmNvLm56L3JlbGF0aW9uYWwtZGF0YS5odG1sKQoKCiMjIENvbWJpbmluZyBEYXRhClNvIGZhciwgZWFjaCBkYXRhIHNldCB3ZSBoYXZlIHdvcmtlZCB3aXRoIGhhcyBiZWVuIGNvbnRhaW5lZCBpbiBhIHNpbmdsZSBtb25vbGl0aGljIGRhdGEgZmlsZSB3aXRoIGFsbCB2YXJpYWJsZXMgYXMgY29sdW1ucyBhbmQgYWxsIG9ic2VydmF0aW9ucyBhcyByb3dzLiBJbiBhIHJlYWwgcmVzZXJhY2ggc2l0dWF0aW9uLCB0aGlzIHdpbGwgbm90IGFsd2F5cyBiZSB0aGUgY2FzZS4gQ29tcGxleCBkYXRhIHNldHMgYXJlIG9mdGVuIGRlcGxveWVkIGFzIG11bHRpcGxlIHJlbGF0ZWQgZmlsZXMuIEVhY2ggZmlsZSByZXByZXNlbnRzIGEgc2luZ2xlLCB3ZWxsLWRlZmluZWQgZGF0YSBlbnRpdHksIGFuZCB0aGUgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIGVudGl0aWVzIGFyZSBwYXJ0IG9mIHRoZSBkYXRhIHByb3ZpZGVkLiBJbiBvcmRlciB0byBhbnN3ZXIgb3VyIHJlc2VhcmNoIHF1ZXN0aW9ucywgd2UgbmVlZCB0byBiZSBhYmxlIHRvIGNvbWJpbmUgdGhlc2UgZGlmZmVyZW50IGZpbGVzIGluIHZhcmlvdXMsIHNvbWV0aW1lcyBjb21wbGV4IHdheXMuCgoKIyMgRGVzY3JpYmluZyB0aGUgRGF0YSBVbml2ZXJzZQpUaGUgY29tcGxleCBzeXN0ZW1zIHdlIHN0dWR5IGdlbmVyYWxseSBjb25zaXN0IG9mIG1hbnkgZGlmZmVyZW50ICoqZW50aXRpZXMqKiAoInRoaW5ncyIsIGxpa2UgcGFydGljaXBhbnRzLCB0ZXN0IHJlc3VsdHMsIGV4cGVyaW1lbnRhbCBjb25kaXRpb25zLCBzcGVjaWVzLCByZXNlYXJjaGVycywgZXF1aXBtZW50LCBsYWtlcywgZXRjLikuIEVhY2ggZW50aXR5IGhhcyBpbXBvcnRhbnQgKipwcm9wZXJ0aWVzKiogKGUuZy4gc3ViamVjdCBJRCwgcmVhY3Rpb24gdGltZSwgZXF1aXBtZW50IHR5cGUsIGV0Yy4pLiBUaGVyZSBhcmUgY29tcGxleCAqKnJlbGF0aW9uc2hpcHMqKiBiZXR3ZWVuIGVudGl0aWVzIChlLmcuIGEgcGFydGljdWxhciBwYXJ0aWNpcGFudCB3YXMgZ2l2ZW4gYSBzcGVjaWZpYyBleHBlcmltZW50YWwgdHJlYXRtZW50LCBhIHBhcnRpY3VsYXIgYW5pbWFsIHNwZWNpZXMgd2FzIHNlZW4gaW4gYSBzcGVjaWZpYyBsYWtlKS4gVG9nZXRoZXIsIHRoZXNlIGNvbnN0aXR1dGUgb3VyICoqZGF0YSB1bml2ZXJzZSoqLgoKVG8gYW5zd2VyIG91ciByZXNlYXJjaCBxdWVzdGlvbnMsIHdlIG5lZWQgdG8gYmUgYWJsZSB0byByZXByZXNlbnQgdGhlIGRhdGEgdW5pdmVyc2UgaW4gYW4gb3JnYW5pc2VkIHdheS4gVGhlIGNoYWxsZW5nZXMgdGhpcyBpbXBvc2VzIGNhbiBiZSBzZWVuIGV2ZW4gaW4gY29tcGFyYXRpdmVseSBzaW1wbGUgY29udGV4dHMuIAoKRm9yIGV4YW1wbGUsIGFzc3VtZSB5b3UgYXJlIGludm9sdmVkIHdpdGggdGhlIGxvY2FsIHNvZnRiYWxsIGNvbXBldGl0aW9ucy4gVGhlcmUgYXJlIG11bHRpcGxlIGxlYWd1ZXMsIGVhY2ggbGVhZ3VlIGhhcyBtdWx0aXBsZSB0ZWFtcywgZWFjaCB0ZWFtIGhhcyBhIGNvYWNoIGFuZCBtYW55IHBsYXllcnMuIFlvdXIgam9iIG1pZ2h0IGJlIHRvIG1haW50YWluIGNvbnRhY3QgaW5mb3JtYXRpb24gc28gdGhhdCBwZW9wbGUgY2FuIGJlIGluZm9ybWVkIHdoZW4gdGhlcmUgYXJlIGNoYW5nZXMgdG8gdGhlIGdhbWUgc2NoZWR1bGUuCgpBcyBhbiBleHBlcmllbmNlZCBjb21wdXRlciB1c2VyLCB5b3UgY2FuIGRlY2lkZSB0byBwdXQgYWxsIHRoaXMgaW5mb3JtdGlvbiBpbnRvIHNvbWUgbmljZSBzcHJlYWRzaGVldHMuIEZvciBleGFtcGxlLCB5b3UgbWlnaHQgaGF2ZSBhIHNwcmVhZHNoZWV0IGxpa2UgdGhpcyBmb3Igb25lIHRlYW06CgpgYGB7ciwgZmlnLmNhcCA9ICJUaGUgR25vbWVzIiwgZWNobz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGg9IjEwMCUifQppbmNsdWRlX2dyYXBoaWNzKCJpbWFnZXMvMDctc3BvcnRzX2xlYWd1ZV8wMS5wbmciKQpgYGAKCllvdSB3b3VsZCBoYXZlIGEgY29tcGFyYWJsZSBzcHJlYWRzaGVldCBmb3IgZWFjaCB0ZWFtIGluIGVhY2ggbGVhZ3VlLiAKCklmIHlvdXIgZGF0YSBvbmx5IG5lZWQgdG8gYmUgYXZhaWxhYmxlIHRvIHBlb3BsZSwgdGhpcyBzeXN0ZW0gd291bGQgcHJvYmFibHkgYmUgYWRlcXVhdGUsIGlmIGEgbGl0dGxlIGN1bWJlcnNvbWUuIEhvd2V2ZXIsIGlmIHlvdSB3YW50ZWQgdG8gbG9hZCB5b3VyIGRhdGEgaW50byBhIGNvbXB1dGVyIGZvciBwcm9jZXNzaW5nLCB0aGlzIGZpbGUgZm9ybWF0IHdpbGwgbm90IHdvcmsuIFJlY2FsbCB0aGF0IFIgbmVlZHMgYSByZWN0YW5nbGUgb2YgZGF0YSB3aXRoIGNvbHVtbiBoZWFkZXJzIGFjcm9zcyB0aGUgdG9wIGFuZCBvbmUgcm93IGZvciBlYWNoIGRhdGEgcG9pbnQuIFlvdSBjb3VsZG7igJl0IGV2ZW4gaW1wb3J0IHlvdXIgZGF0YSB1c2luZyB0aGlzICJodW1hbi1mcmllbmRseSIgc3ByZWFkc2hlZXQuCgpUbyBwcm92aWRlIHRoZSBzYW1lIGluZm9ybWF0aW9uIGluIGEgZm9ybWF0IHRoYXQgYSBjb21wdXRlciBjYW4gdW5kZXJzdGFuZCwgeW91IG11c3QgZG8gdGhlIGZvbGxvd2luZzoKCjEuIElkZW50aWZ5IHRoZSBjb2hlc2l2ZSBlbnRpdGllcyBpbiB5b3VyIGRhdGEKMi4gSWRlbnRpZnkgdGhlIHByb3BlcnRpZXMgZm9yIGVhY2ggZW50aXR5CjMuIE1ha2UgYSBzZXBhcmF0ZSBmaWxlIGZvciBlYWNoIGVudGl0eQo0LiBNYWtlIHRoZSByZWxhdGlvbnNoaXBzIGJldHdlZW4gZW50aXRpZXMgZXhwbGljaXQgaW4gdGhlaXIgZGF0YSAoc2VlIGV4YW1wbGVzIGJlbG93KS4KCkluIG91ciBTcG9ydHMgTGVhZ3VlIGV4YW1wbGUsIHdlIHNlZSB0aGF0IHdlIGhhdmUgdHdvIGNsZWFyIGVudGl0aWVzIC0tIFRlYW1zIGFuZCBQbGF5ZXJzLiBBIFRlYW0gaGFzIGZvdXIgcHJvcGVydGllczogdGhlIG5hbWUsIHRoZSBsZWFndWUsIHRoZSBjb2FjaCwgYW5kIHRoZSBjb2FjaCdzIHBob25lIG51bWJlci4gQSBQbGF5ZXIgaGFzIGZvdXIgcHJvcGVydGllczogdGhlaXIgdGVhbSwgdGhlaXIgbmFtZSwgdGhlaXIgcGhvbmUgbnVtYmVyLCBhbmQgdGhlIHBvc2l0aW9uIHRoZXkgcGxheS4gSW4gb3VyIGN1cnJlbnQgZm9ybWF0LCBUZWFtcyBhbmQgUGxheWVycyBhcmUgYWxsIG1peGVkIGludG8gdGhlIHNhbWUgZmlsZS4gRm9yIHRoZSBjb21wdXRlciwgd2UgbmVlZCB0byBzZXBhcmF0ZSB0aGVtLiBNYWtpbmcgZWFjaCBwcm9wZXJ0eSBhIGNvbHVtbiwgYW5kIGVhY2ggaW5zdGFuY2UgYSByb3csIHdlIGNhbiByZWZvcm1hdCBvdXIgZGF0YSBpbnRvIHR3byBzZXBhcmF0ZSBmaWxlcywgbGlrZSB0aGlzOgoKCmBgYHtyLCBmaWcuY2FwID0gIlNwb3J0cyBMZWFndWUgZm9yIENvbXB1dGVycyIsIGVjaG89RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgb3V0LndpZHRoPSIxMDAlIn0KaW5jbHVkZV9ncmFwaGljcygiaW1hZ2VzLzA3LXNwb3J0c19sZWFndWVfMDIucG5nIikKYGBgCgpOb3RlIHRoYXQgdGhlc2UgZmlsZXMgYXJlIGluIGV4YWN0bHkgdGhlIHJlY3Rhbmd1bGFyICJyb3dzIGFyZSBpbnN0YW5jZXM7IGNvbHVtbnMgYXJlIHZhcmlhYmxlcyIgZm9ybWF0IHRoYXQgUiByZXF1aXJlcy4gWW91IGNhbiBlYXNpbHkgcmVhZCB0aGVtIGludG8gUiBhbmQgc3RhcnQgcHJvY2Vzc2luZyB0aGVtLiBGb3IgZXhhbXBsZSwgaWYgdGhlcmUgYXJlIDgwMCBwbGF5ZXJzIGluIHRoZSBQbGF5ZXJzIHNwcmVhZHNoZWV0LCBob3cgd291bGQgeW91IGRldGVybWluZSB0aGUgbnVtYmVyIG9mIHBsYXllcnMgYXNzaWduZWQgdG8gZWFjaCBwb3NpdGlvbiwgYWNyb3NzIHRoZSBlbnRpcmUgbGVhZ3VlPwoKQXNzdW1pbmcgeW91IGhhdmUgdXNlZCBgcmVhZC5jc3ZgIHRvIGltcG9ydCB0aGUgUGxheWVycyBzcHJlYWRzaGVldCBpbnRvIGEgZGF0YWZyYW1lIGBwbGF5ZXJzX2RmYCwgeW91IGNvdWxkIHNheToKYHBsYXllcnNfZGYgJT4lIGdyb3VwX2J5KFBsYXllclBvc2l0aW9uKSAlPiUgY291bnQoKWAuCgpCeSBtYWtpbmcgdHdvIHNlcGFyYXRlIHNwcmVhZHNoZWV0cywgeW91IGhhdmUgc2VwYXJhdGVkIHRoZSBpbmZvcm1hdGlvbiBhYm91dCBwbGF5ZXJzIGZyb20gdGhlIGluZm9ybWF0aW9uIGFib3V0IHRlYW1zLiBJdCBpcyBlc3NlbnRpYWwgdGhhdCwgd2hlbiB5b3VyIGRhdGEgYXJlIHJlb3JnYW5pc2VkIGluIHRoaXMgd2F5LCBubyBpbmZvcm1hdGlvbiBoYXMgYmVlbiBsb3N0LiBUaGF0IGlzICpjYW4geW91IGFuc3dlciB0aGUgc2FtZSBxdWVzdGlvbnMgd2l0aCB0aGlzIGFycmFuZ2VtZW50IGFzIHlvdSBjb3VsZCB3aXRoIHRoZSBvcmlnaW5hbCBvbmU/KgoKRm9yIGV4YW1wbGUsIHdpdGggdGhlIGZpcnN0IGh1bWFuLWZyaWVuZGx5IHZlcnNpb24sIGl0IGlzIGVhc3kgdG8gYW5zd2VyICrigJxXaG8gaXMgRnJlZOKAmXMgY29hY2g/4oCdKiwgYmVjYXVzZSBGcmVkIGFuZCBoaXMgY29hY2ggYXJlIGluIHRoZSBzYW1lIHNwcmVhZHNoZWV0LiBCdXQgaG93IGRvIHlvdSBkbyBpdCB3aXRoIHRoZSBjb21wdXRlci1mcmllbmRseSBvcmdhbmlzYXRpb24gd2hlcmUgRnJlZCdzIGRhdGEgYW5kIGhpcyBUZWFtJ3MgZGF0YSBhcmUgaW4gZGlmZmVyZW50IGZpbGVzPwoKQXMgYSBwZXJzb24geW91IHdvdWxkIHNheSDigJxGcmVk4oCZcyB0ZWFtIGlzIFRoZSBHbm9tZXMgKGZyb20gc2hlZXQgMikgYW5kIEJvYiBpcyB0aGUgY29hY2ggb2YgVGhlIEdub21lcyAoZnJvbSBzaGVldCAxKSB0aGVyZWZvcmUgQm9iIGlzIHRoZSBjb2FjaCBvZiBGcmVkLuKAnSBUbyBkbyBpdCBpbiBSIChhcyB5b3Ugd291bGQgaWYgeW91IG5lZWRlZCB0byBwZXJmb3JtIHRoaXMgb3BlcmF0aW9uIGZvciA4MDAgcGxheWVycykgeW91IHdvdWxkIHNlbGVjdCB0aGUgVGVhbU5hbWUgdmFsdWUgZm9yIEZyZWQgaW4gdGhlIFBsYXllcnMgZmlsZSwgdGhlbiBmaWx0ZXIgZm9yIHRoYXQgdmFsdWUgaW4gdGhlIFRlYW1zIGZpbGUgYW5kIHNlbGVjdCB0aGUgQ29hY2ggY29sdW1uLiBBZnRlciB5b3UgbGVhcm4gaG93IHRvIHdyaXRlIGxvb3BzLCB5b3UgY291bGQgbG9vcCB0aHJvdWdoIGFsbCB0aGUgcGxheWVycyBhbmQgZG8gdGhpcyBjb21wdXRhdGlvbiwgYWNjcnVpbmcgdGhlIHJlc3VsdHMgaW50byBhIHZlY3Rvci4gVGhlbiB5b3UgY291bGQgYWRkIHRoYXQgdmVjdG9yIGFzIGEgbmV3IGNvbHVtbiBzb21ld2hlcmUgdG8gbWF0Y2ggdXAgcGxheWVyIGFuZCBjb2FjaC4gSG93ZXZlciwgUiBwcm92aWRlcyBtdWNoIG1vcmUgc3VjY2luY3Qgd2F5cyB0byBwZXJmb3JtIHRoaXMgb3BlcmF0aW9uLgoKVGhpcyBpcyBmb3J0dW5hdGUsIGJlY2F1c2UgcmVhbCByZXNlYXJjaCBkYXRhIHNldHMgYXJlIG9mdGVuIGRlcGxveWVkIGluIG1hbnkgbW9yZSB0aGFuIGp1c3QgdGhlIHR3byBmaWxlcyB3ZSBuZWVkZWQgaW4gb3VyIHRveSBTcG9ydHMgTGVhZ3VlIGV4YW1wbGUuIExhcmdlLCBjb21wbGV4IGRhdGEgc2V0cyBhcmUgdHlwaWNhbGx5IG5vdCBtYW5hZ2VkIHdpdGggc3ByZWFkc2hlZXRzIGF0IGFsbCwgdGhleSBhcmUgbWFuYWdlZCBhcyAqKmRhdGFiYXNlcyoqLiBXaGlsZSB0aGlzIHRlcm0gaXMgdXNlZCBxdWl0ZSBicm9hZGx5IGluIGNhc3VhbCBkaXNjdXNzaW9uLCBpdCB0ZWNobmljYWxseSByZWZlcnMgc3BlY2lmaWNhbGx5IHRvICoqUmVsYXRpb25hbCBEYXRhYmFzZSBNYW5hZ2VtZW50IFN5c3RlbXMqKiwgc3BlY2lhbCBhcHBsaWNhdGlvbnMgdGhhdCBhcmUgb3B0aW1pc2VkIGZvciBzZWFyY2hpbmcsIGRlbGV0aW5nLCBmaWx0ZXJpbmcsIGFuZCBnZW5lcmFsbHkgbWFuYWdpbmcgaHVnZSBhbW91bnRzIG9mIGRhdGEuCgpJbiBhbiBSREJNUyBlYWNoICoqZW50aXR5KiogaGFzIGl0cyBvd24gKip0YWJsZSoqIGFuZCB0aGUgdG9vbHMgaGF2ZSBFWFRSRU1FTFkgc3RyaWN0IHJ1bGVzIGFib3V0IHRoZSBjb250ZW50cyBhbmQgc3RydWN0dXJlIG9mIHRoZWlyIGVudGl0aWVzLiBUaGUgZGF0YSB1bml2ZXJzZSBpcyBicm9rZW4gZG93biBpbnRvIGF0b21pYyBwaWVjZXMgb2YgZGF0YSBhbmQgcmVjb21iaW5lZCB0aHJvdWdoIHRoZSBydWxlcyBvZiBhIG1hdGhlbWF0aWNhbCBmcmFtZXdvcmsgY2FsbGVkIHRoZSBSZWxhdGlvbmFsIEFsZ2VicmEuIEl0IGlzIGEgYmVhdXRpZnVsIHRoaW5nLCBidXQgeW91IGVuZCB1cCB3aXRoIGEgbG90IG9mIHRhYmxlcy4KClRoZSBmaWd1cmUgYmVsb3cgaXMgYW4gKipFbnRpdHkgUmVsYXRpb25zaGlwIERpYWdyYW0qKi4gSXQgc2hvd3MgdGhlIHN0cnVjdHVyZSBvZiB0aGUgZGF0YWJhc2UgZm9yIGEgKnJlbGF0aXZlbHkgc2ltcGxlKiBkYXRhYmFzZSBzeXN0ZW0gdGhhdCBrZWVwcyB0cmFjayBvZiBGb29kIERpYXJpZXMg4oCTIHdoZXJlIHBlb3BsZSByZWNvcmQgd2hhdCB0aGV5IGVhdCBmb3IgbnV0cml0aW9uYWwgYW5hbHlzaXMuIAoKCmBgYHtyLCBmaWcuY2FwID0gIkV4YW1wbGUgRVJEIiwgZWNobz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGg9IjEwMCUifQppbmNsdWRlX2dyYXBoaWNzKCJpbWFnZXMvMDctRVJELnBuZyIpCmBgYAoKCldoZW4gcmVhbCBkYXRhIGFyZSBleHBvcnRlZCB0byB5b3UgZm9yIHByb2Nlc3NpbmcsIHRoZXJlZm9yZSwgeW91IGNhbiBlbmQgdXAgd2l0aCBhIGxvdCBvZiBzZXBhcmF0ZSBmaWxlcy4gVXNpbmcgUiwgeW91IGhhdmUgdG8gYmUgYWJsZSB0byByZWNvbWJpbmUgZGF0YSBhcyBuZWVkZWQgdG8gYW5zd2VyIHlvdXIgc3BlY2lmaWMgcmVzZWFyY2ggcXVlc3Rpb25zLgoKCiMjIFNpbXBsZSBDb21iaW5pbmcgKEJpbmRzKQoKQmVmb3JlIHdlIGxvb2sgYXQgdGhlIHNvbWV0aW1lcyBjb21wbGV4IGlzc3VlcyBhcm91bmQgY29tYmluaW5nIHRhYmxlcyByZXByZXNlbnRpbmcgZGlmZmVyZW50IGVudGl0aWVzLCB3ZSB3aWxsIHF1aWNrbHkgY292ZXIgdHdvIHNpbXBsZXIgY29tYmluaW5nIHNpdHVhdGlvbnMgdGhhdCBvY2N1ciB3aGVuIHlvdSBoYXZlLCBlc3NlbnRpYWxseSwgYSBzaW5nbGUgdGFibGUgdGhhdCBoYXMgYmVlbiBicm9rZW4gaW50byBtdWx0aXBsZSBpbnB1dCBmaWxlcy4gCgpPdXIgZGF0YSBmb3IgdG9kYXkgY29tZSBmcm9tIHRoZSAqKkdlY2tvcyBhbmQgR3VtYm9vdHMqKiBwcm9qZWN0LiBHZWNrb3MgYW5kIEd1bWJvb3RzIGlzIGEgbXVsdGkteWVhciwgbXVsdGktZGlzY2lwbGluYXJ5IHJlY29uc3RydWN0aW9uIG9mIHdpbGQgTmV3IFplYWxhbmQgR2Vja28gaGFiaXRhdCBvbiB0aGUgT3RhZ28gUGVuaW5zdWxhIChTb3V0aCBJc2xhbmQsIE5ldyBaZWFsYW5kKS4gQSBsYXJnZSBhcmVhIG9mIGxhbmQgb24gdGhlIHBlbmluc3VsYSBoYXMgYmVlbiBwbGFudGVkIHdpdGggbmF0aXZlIGdlY2tvIGhhYml0YXQgc3BlY2llcywgZGl2aWRlZCBpbnRvIHJlcGxpY2F0ZSBxdWFkcmF0cyBhbmQsIGF0IGludGVydmFscywgZGF0YSBhcmUgY29sbGVjdGVkIGFib3V0IHBsYW50IGdyb3d0aCwgYW5kIGludmVydGVicmF0ZSwgcHJlZGF0b3IsIGFuZCBnZWNrbyBwb3B1bGF0aW9uIGFidW5kYW5jZS4gVW5kZXJncmFkdWF0ZSBzdHVkZW50cyBwYXJ0aWNpcGF0ZSBpbiBkYXRhIGNvbGxlY3Rpb24gYW5kIGFuYWx5c2lzIHRvIGdhaW4gZXhwZXJpZW5jZSB3aXRoIGVjb2xvZ3kgZmllbGR3b3JrLgoKCiMjIyBDb21iaW5pbmcgUm93cwoKQXNzdW1lIHRoZSBmaWVsZCBlY29sb2dpc3RzIGhhdmUgc2VudCB5b3Ugc29tZSBwcmVkYXRvciBvYnNlcnZhdGlvbiBkYXRhIGZvciBhbmFseXNpcy4gVGhleSBoYXZlIHNlbnQgeW91IHRocmVlIHNlcGFyYXRlIGNzdiBmaWxlcywgb25lIGZvciBlYWNoIG9mIHRocmVlIGRhdGEgY29sbGVjdGlvbiBkYXlzLgoKCmBgYHtyLCBmaWcuY2FwID0gIkNvbWJpbmluZyBSb3dzIiwgZWNobz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGg9IjEwMCUifQppbmNsdWRlX2dyYXBoaWNzKCJpbWFnZXMvMDctcmJpbmQucG5nIikKYGBgCgpBbGwgdGhyZWUgZmlsZXMgaGF2ZSBleGFjdGx5IHRoZSBzYW1lIGZvcm1hdCDigJMgZm91ciB2YXJpYWJsZSBjb2x1bW5zIGFuZCBvbmUgcm93IGZvciBlYWNoIHJlY29yZGVkIHByZWRhdG9yIG9ic2VydmF0aW9uLiBUaGlzIGlzIGEgbmljZSB3YXkgdG8gZ2V0IGRhdGEg4oCTIGV2ZXJ5dGhpbmcgaXMgYWxyZWFkeSByZWN0YW5ndWxhciwgYW5kIHRoZXkgaGF2ZSBhbGwgdGhlIHNhbWUgY29sdW1ucyBpbiB0aGUgc2FtZSBvcmRlci4gWW91IHNpbXBseSBuZWVkIHRvIGdhdGhlciB0aGVtIGFsbCB1cCBpbnRvIGEgc2luZ2xlIGRhdGFmcmFtZSDigJMgaW4gUiB0aGlzIGlzIHN0cmFpZ2h0Zm9yd2FyZC4gVXNlIGZ1bmN0aW9uIGByYmluZGAsIHBhc3NpbmcgaW4gYWxsIHRoZSBkYXRhIGZyYW1lcyB0byBiZSBjb25jYXRlbmF0ZWQsIHNlcGFyYXRlZCBieSBjb21tYXMuCgpgYGB7ciByYmluZH0KcHJlZF8wMV9kZiA8LSByZWFkLmNzdigiZGF0YS9wcmVkYXRvcl9vYnNlcnZhdGlvbnNfMjAyMl8wNF8zMC5jc3YiKQpwcmVkXzAyX2RmIDwtIHJlYWQuY3N2KCJkYXRhL3ByZWRhdG9yX29ic2VydmF0aW9uc18yMDIzXzA0XzIyLmNzdiIpCnByZWRfMDNfZGYgPC0gcmVhZC5jc3YoImRhdGEvcHJlZGF0b3Jfb2JzZXJ2YXRpb25zXzIwMjNfMDRfMjMuY3N2IikKCnByZWRfYWxsX2RmIDwtIHJiaW5kKHByZWRfMDFfZGYsIHByZWRfMDJfZGYsIHByZWRfMDNfZGYpCnByZWRfYWxsX2RmWzUwOjYwLF0KYGBgCgpOb3RlIHRoYXQgaWYgdGhlIGFyZ3VtZW50cyB0byByYmluZCBkb24ndCBhbGwgaGF2ZSBleGFjdGx5IHRoZSBzYW1lIGNvbHVtbnMsIHRoZSBmdW5jdGlvbiB3aWxsLCBxdWl0ZSBzZW5zaWJseSwgdGhyb3cgYW4gZXJyb3I6CgpgYGB7ciByYmluZCBlcnJvciwgZXJyb3I9VFJVRX0KIyBGb3IgcHVycG9zZXMgb2YgaWxsdXN0cmlvbiwgd2UgZHJvcCB0aGUgNHRoIGNvbHVtbiBmcm9tIHByZWRfMDFfZGYKcHJlZF8wMV9kZiA8LSBwcmVkXzAxX2RmWyAsIC00XQoKIyBUaGUgcmJpbmQgY29tbWFuZCBub3cgZmFpbHMKcHJlZF9hbGxfZGYgPC0gcmJpbmQocHJlZF8wMV9kZiwgcHJlZF8wMl9kZiwgcHJlZF8wM19kZikKCmBgYAoKIyMjIENvbWJpbmluZyBDb2x1bW5zCgpMZXNzIGNvbW1vbmx5LCB5b3UgbWlnaHQgaGF2ZSBtdWx0aXBsZSBkYXRhIHNldHMgd2l0aCB0aGUgc2FtZSByb3dzIChvYmVydmF0aW9ucyksIGJ1dCBkaWZmZXJlbnQgY29sdW1ucyAoZGVwZW5kZW50IGFuZCBpbmRlcGVuZGVudCB2YXJpYWJsZXMpLiBJbiB0aGlzIGNhc2UsIHVzZSBmdW5jdGlvbiBgY2JpbmRgIC0tIHRoZSBzeW50YXggaXMgdGhlIHNhbWUgYXMgZm9yICBgcmJpbmRgLiAKCkluIHByYWN0aWNlLCB0aGlzIHNpdHVhdGlvbiBhY3R1YWxseSBvY2N1cnMgdmVyeSByYXJlbHkuIFRoaXMgaXMgZm9ydHVuYXRlLCBiZWNhdXNlIGNvbHVtbiBiaW5kaW5nIGlzIGV4dHJlbWVseSByaXNreS4gQ29sdW1uIGJpbmRpbmcgcmVsaWVzIGVudGlyZWx5IHVwb24gdGhlIHJvd3MgaW4gdGhlIHNlcGFyYXRlIGRhdGEgZnJhbWVzIGJlaW5nIGlkZW50aWNhbCBhbmQgaW4gdGhlIHNhbWUgb3JkZXI7IHRoZSBmdW5jdGlvbiBpdHNlbGYgcGVyZm9ybXMgbm8gY2hlY2tzIG9uIHRoaXMgY29uc3RyYWludC4gCgpCZWNhdXNlIG9mIHRoZSBkYW5nZXIgb2YgbWl4aW5nIHVwIGRhdGEgZnJvbSBkaWZmZXJlbnQgb2JzZXJ2YXRpb25zLCBJIG9ubHkgdXNlIGNiaW5kIHdoZW4gSSBhbSBnZW5lcmF0aW5nIGNvbHVtbnMgZHluYW1pY2FsbHkgKGluIGNvZGUpIGFuZCB3aXNoIHRvIGJ1aWxkIGEgZGF0YSBmcmFtZSBmcm9tIHRoZW0uCgogIAojIyBSZWxhdGlvbmFsIENvbWJpbmluZyAoSm9pbnMpCgpSZWNhbGwgb3VyIFNwb3J0cyBMZWFndWUgZGF0YToKCmBgYHtyLCBmaWcuY2FwID0gIlNwb3J0cyBMZWFndWUgZm9yIENvbXB1dGVycyIsIGVjaG89RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgb3V0LndpZHRoPSIxMDAlIn0KaW5jbHVkZV9ncmFwaGljcygiaW1hZ2VzLzA3LXNwb3J0c19sZWFndWVfMDIucG5nIikKYGBgCgpPdXIgInJlc2VhcmNoIHF1ZXN0aW9uIiB3YXMgaG93IHRvIGZpbmQgdGhlIG5hbWUgb2YgQm9iJ3MgY29hY2guIE1vcmUgZ2VuZXJhbGx5LCBhc3N1bWUgd2Ugd2FudCB0byBrbm93IHRoZSBjb2FjaGVzIG9mIGFsbCB0aGUgcGxheWVycy4gV2hlbiB3b3JraW5nIHdpdGggZGF0YSBmcmFtZXMsIHRoaXMgbWVhbnMgd2Ugd2FudCB0byBhZGQgY29sdW1ucyB0byB0aGUgUGxheWVyJ3MgdGFibGUgdGhhdCBjb250YWluIHRoZSBhc3NvY2lhdGVkIGNvYWNoIGluZm9ybWF0aW9uIGZvciBlYWNoIHJvdy4KCldlIG5vdGVkIHRoYXQgdGhlIFRlYW0gdGFibGUgYW5kIHRoZSBQbGF5ZXIgdGFibGUgaGF2ZSBhIGNvbW1vbiBjb2x1bW4g4oCTIFRlYW1OYW1lIOKAkyBhbmQgdGhhdCB3ZSBjb3VsZCBtYXRjaCBwbGF5ZXJzIGFuZCBjb2FjaGVzIGJ5IGxvb2tpbmcgZm9yIG1hdGNoaW5nIHZhbHVlcyBvZiBUZWFtTmFtZS4gVGhpcyBpcyBhIHJlcXVpcmVtZW50IGZvciByZWxhdGlvbmFsIGNvbWJpbmluZyAtLSB0d28gdGFibGVzIG11c3Qgc2hhcmUgYSBjb21tb24gY29sdW1uIChvciBjb2x1bW5zKSB0aGF0IGNhbiBiZSB1c2VkIHRvIG1hdGNoIG9ic2VydmF0aW9ucyBiZXR3ZW4gdGhlIHRhYmxlcy4gVGhlc2UgY29tbW9uIGNvbHVtbnMgYXJlIGNhbGxlZCAqKmtleXMqKi4gCgpJbiBmb3JtYWwgZGF0YWJhc2VzLCB0aGVyZSBhcmUgbG90cyBvZiBydWxlcyBhYm91dCBrZXlzLCBwYXJ0aWN1bGFybHkgYXJvdW5kICoqdW5pcXVlbmVzcyoqLiBOb3RlIGhvdyBjb25mdXNpbmcgaXQgd291bGQgYmUgaWYgdGhlcmUgd2VyZSAqdHdvKiByb3dzIGluIHRoZSBUZWFtcyBkYXRhIGZyYW1lIHdpdGggdGVhbW5hbWUgKlRoZSBHbm9tZXMqIOKAkyB3aGljaCBvZiB0aGUgdHdvIGNvYWNoZXMgd291bGQgeW91IHdhbnQgdG8gbWF0Y2ggd2l0aCBGcmVkPyBeW05COiBUaGVyZSBpcyBhIG1ldGhvZCBmb3IgZGVhbGluZyB3aXRoIHRoZSAibXVsdGlwbGUgY29hY2hlcyIgcHJvYmxlbSwgYnV0IGl0IGlzIG91dCBvZiBzY29wZSBmb3IgdG9kYXkncyBtb2R1bGUuIEFzayBtZSBmb3IgbW9yZSBkZXRhaWwgaWYgeW91J3JlIGludGVyZXN0ZWQuXSBGb3IgdG9kYXkgd2Ugd2lsbCBhc3N1bWUgdGhhdCB0aGVzZSByZXF1aXJlbWVudHMgYXJlIG1ldCDigJMgZm9yIGZ1cnRoZXIgZGV0YWlscywgc2VlIHRoZSBSIGZvciBEYXRhIFNjaWVuY2UgY2hhcHRlci4KCiMjIEpvaW4KCkluIGRhdGFiYXNlIHRlcm1pbm9sb2d5LCB0aGUgcHJvY2VzcyBvZiBtYXRjaGluZyB1cCByZWNvcmRzIGZyb20gbXVsdGlwbGUgdGFibGVzIGlzIGNhbGxlZCBhICoqam9pbioqLiBSIChtb3JlIHNwZWNpZmljYWxseSB0aWR5dmVyc2UpIHVzZXMgdGhpcyB0ZXJtaW5vbG9neSBhcyB3ZWxsLiAgU29sdmluZyBwcm9ibGVtcyBsaWtlIGZpbmRpbmcgdGhlIGNvYWNoIGZvciBlYWNoIHBsYXllciBpbnZvbHZlcyBqb2luaW5nIHR3byBvciBtb3JlIGRhdGEgZnJhbWVzIG9uIG9uZSBvciBtb3JlIGNvbW1vbiBjb2x1bW5zLgoKVG8gc2xpZ2h0bHkgY29tcGxpY2F0ZSB0aGluZ3MsIFIgZG9lcyBub3QgcHJvdmlkZSBhIGZ1bmN0aW9uIGNhbGxlZCDigJxqb2lu4oCdLiAgSXQgcHJvdmlkZSAqc2l4IGRpZmZlcmVudCBmdW5jdGlvbnMqIHdoaWNoIHBlcmZvcm0gc2xpZ2h0bHkgZGlmZmVyZW50IHR5cGVzIG9mIGpvaW4gb3BlcmF0aW9uLiBUaGUgZnVuY3Rpb24gbmFtZXMgYXJlIGRlcml2ZWQgZnJvbSB0aGUgZm9ybWFsIGRhdGFiYXNlIHRlcm1pbm9sb2d5LiBGb3J0dW5hdGVseSwgaW4gdGhlIHZhc3QgbWFqb3JpdHkgb2YgY2FzZXMgd2UgZW5jb3VudGVyLCB5b3Ugb25seSBuZWVkIG9uZSBvZiB0aGUgam9pbiBmYW1pbHkgLS0gYGxlZnRfam9pbmAuIFdlIHdpbGwgY292ZXIgYGxlZnRfam9pbmAgaGVyZTsgc2VlIHRoZSBhZGRpdGlvbmFsIHJlYWRpbmdzIGZvciBkaXNjdXNzaW9ucyBvZiB0aGUgbGVzcyBjb21tb24gZm9ybXMuCgpUaGUgZnVuY3Rpb24gYGxlZnRfam9pbmAgdGFrZXMgYXMgYXJndW1lbnRzIHRoZSB0d28gdGFibGVzIChkYXRhIGZyYW1lcykgeW91IHdpc2ggdG8gam9pbi4gVGhlIG5hbWUgYGxlZnRfam9pbmAgaGVscHMgeW91IHRvIHJlbWVtYmVyIGhvdyB0byBvcmRlciB5b3VyIGFyZ3VtZW50cyDigJMgcHV0IHRoZSAqKmJhc2UgdGFibGUqKiBvbiB0aGUgbGVmdCAoaS5lLiBhcyB0aGUgZmlyc3QgYXJndW1lbnQpLiBUaGUgKipiYXNlIHRhYmxlKiogaXMgdGhlIG9uZSB5b3Ugd2lzaCB0byBhZGQgY29sdW1ucyB0by4KClJlY2FsbCB0aGF0IHdlIHdhbnRlZCB0byBrbm93IGVhY2ggcGxheWVy4oCZcyBjb2FjaC4gV2UgdGhlcmVmb3JlIHdhbnQgdG8gYWRkIGEgQ29hY2hOYW1lIGNvbHVtbiB0byB0aGUgUGxheWVycyBkYXRhIGZyYW1lLiBUaHVzIFBsYXllcnMgaXMgdGhlICoqYmFzZSB0YWJsZSoqLiBXZSBwYXNzIGl0IGZpcnN0IGludG8gZnVuY3Rpb24gJ2xlZnRfam9pbicsIGFsb25nIHdpdGggdGhlIG5hbWUgb2YgdGhlICoqa2V5KiogY29sdW1uLgoKCmBgYHtyIG1ha2UgZGYsIGVjaG8gPSBGQUxTRX0KdGVhbXNfbWF0cml4IDwtIGNiaW5kKFRlYW1OYW1lID0gYygiVGhlIEdub21lcyIsICJUaGUgT3JjcyIsICJUaGUgQXJhY2huaWRzIiksCiAgICAgICAgICAgICAgICAgIExlYWd1ZSA9IGMoIk92ZXIgNTBzIiwgIldvbWVuJ3MgUHJlbWllciIsICJVbmRlciAxNXMiKSwKICAgICAgICAgICAgICAgICAgQ29hY2ggPSBjKCJCb2IiLCAiTmdhaXJlIiwgIkRvcm90aHkiKSwKICAgICAgICAgICAgICAgICAgQ29hY2hQaG9uZSA9IGMoIjQ3NC01NTU1IiwgIjAyMSAzMzMtNDU2NyIsICIwMjIgOTg3LTY1NDMiKSkKdGVhbXNfZGYgPC0gYXMuZGF0YS5mcmFtZSh0ZWFtc19tYXRyaXgpCgpwbGF5ZXJzX21hdHJpeCA8LSBjYmluZChQbGF5ZXJOYW1lID0gYygiRnJlZCIsICJTYWxseSIsICJFdGhlbCIsICJMdWN5IiksCiAgICAgICAgICAgICAgICAgICAgICAgIFRlYW1OYW1lID0gYygiVGhlIEdub21lcyIsICJUaGUgR25vbWVzIiwgIlRoZSBHbm9tZXMiLCAiVGhlIEFyYWNobmlkcyIpLAogICAgICAgICAgICAgICAgICAgICAgICBQbGF5ZXJQb3NpdGlvbiA9IGMoIkZpcnN0IEJhc2UiLCAiUGl0Y2hlciIsICJMZWZ0IEZpZWxkIiwgIkNhdGNoZXIiKSwKICAgICAgICAgICAgICAgICAgICAgICAgUGxheWVyUGhvbmUgPSBjKCIwMjEgMTExIDIyMjIiLCAiMDIxIDIyMiAzMzMzIiwgIjAyMSAxMTEgNDQ0NCIsICI0NzQtMTIzNCIpKQpwbGF5ZXJzX2RmIDwtIGFzLmRhdGEuZnJhbWUocGxheWVyc19tYXRyaXgpCgpgYGAKCgpgYGB7ciBqb2luXzAxfQojIFdlIG5lZWQgdGhlIHRpZHl2ZXJzZSB0byBiZSBhYmxlIHRvIGpvaW4KbGlicmFyeSh0aWR5dmVyc2UpCgojIEFzc3VtZSB3ZSBoYXZlIGltcG9ydGVkIG91dCBzcG9ydHMgbGVhZ3VlIGRhdGEgdXNpbmcgcmVhZC5jc3YKaGVhZCh0ZWFtc19kZikKaGVhZChwbGF5ZXJzX2RmKQoKIyBQZXJmb3JtIHRoZSBqb2luCmV4dGVuZGVkX3BsYXllcnNfZGYgPC0gbGVmdF9qb2luKHBsYXllcnNfZGYsIHRlYW1zX2RmLCBieSA9ICJUZWFtTmFtZSIpCgojIFRoZSBuZXcgZGF0YSBmcmFtZQpleHRlbmRlZF9wbGF5ZXJzX2RmCmBgYAoKVGhlIG5ldyBkYXRhZnJhbWUgaGFzIGFsbCB0aGUgY29sdW1ucyBpbiBQbGF5ZXJzLCBpbiB0aGUgb3JpZ2luYWwgb3JkZXIuIEl0IGFsc28gaGFzIGFsbCB0aGUgY29sdW1ucyBpbiBUZWFtcywgaW4gdGhlIG9yaWdpbmFsIG9yZGVyLiBGb3IgKmVhY2ggcm93IG9mIFBsYXllcnMqICh0aGUgYmFzZSB0YWJsZTsgdGhlIG9uZSBvbiB0aGUgbGVmdCksIHRoZSB2YWx1ZXMgb2YgdGhlIFRlYW1zIGNvbHVtbnMgYXJlIHRha2VuIGZyb20gdGhlIHJvdyBpbiBUZWFtcyB0aGF0IGhhcyB0aGUgc2FtZSB2YWx1ZSBpbiBjb2x1bW4gVGVhbU5hbWUgKHRoZSBrZXkpLgoKIyMjIEV4ZXJjaXNlCgpXaGF0IG91dHB1dCBkbyB5b3UgZXhwZWN0IGlmIHlvdSByZXZlcnNlIHRoZSBvcmRlciBvZiBgcGxheWVyc19kZmAgYW5kIGB0ZWFtc19kZmAgaW4gdGhlIGNhbGwgdG8gYGxlZnRfam9pbmAsIGFib3ZlPyBUcnkgdG8gdGhpbmsgdGhyb3VnaCB0aGlzIGV4ZXJjaXNlIGJlZm9yZSB0ZXN0aW5nIHRoZSBjb2RlLiBIaW50OiBXaGVuIHlvdSByZWFjaCB0aGUgcG9pbnQgd2hlcmUgeW91IHNheSAiYnV0IHRoYXQgZG9lc24ndCBtYWtlIHNlbnNlISIsIHJlY29nbmlzZSB0aGF0IHRoZSBjb21wdXRlciB3aWxsIGJlIGluIHRoZSBzYW1lIHNpdHVhdGlvbi4KCiMjIFByYWN0aWNlIEpvaW5pbmcKClRoZSBHZWNrb3MgYW5kIEd1bWJvb3RzIHByb2plY3QgcHJvdmlkZXMgaW5mb3JtYXRpb24gYWJvdXQgdGhlIHBsYW50cyBvYnNlcnZlZCBncm93aW5nIGluIGVhY2ggcXVhZHJhdCAocmVzb3VyY2UgZmlsZSAqcGxhbnRfb2JzZXJ2YXRpb25zLmNzdiopLiBJbiBhIHNlcGFyYXRlIGZpbGUsIHRoZXkgcHJvdmlkZSBhZGRpdGlvbmFsIGluZm9ybWF0aW9uIGFib3V0IHBsYW50IHNwZWNpZXMgKHJlc291cmNlIGZpbGUgKnBsYW50X3NwZWNpZXMuY3N2KikuIFRoaXMgbWlycm9ycyB0aGUgc3RydWN0dXJlIG9mIHRoZSBkYXRhYmFzZSwgd2hlcmUgdGhlc2UgZW50aXRpZXMgYXJlIHN0b3JlZCBzZXBhcmF0ZWx5IHRvIGFsbG93IGVmZmljaWVudCBkYXRhIG9wZXJhdGlvbnMgYW5kIGNvbXBsZXRlIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBkYXRhIHVuaXZlcnNlIChlLmcuIHRvIGFsbG93IGluZm9ybWF0aW9uIHRvIGJlIHN0b3JlZCBmb3IgcGxhbnQgc3BlY2llcyB0aGF0IGhhdmUgbm90IHlldCBiZWVuIG9ic2VydmVkLCBidXQgd2hpY2ggYXJlIGV4cGVjdGVkIHRvIGJlIGluIHRoZSBmdXR1cmUpLgoKYGBge3IsIGZpZy5jYXAgPSAiUGxhbnQgT2JzZXJ2YXRpb24gYW5kIFNwZWNpZXMgRGF0YSBJbnB1dCBGaWxlcyIsIGVjaG89RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgb3V0LndpZHRoPSIxMDAlIn0KaW5jbHVkZV9ncmFwaGljcygiaW1hZ2VzLzA3LXBsYW50X2RhdGFfaW5wdXRfZmlsZXMucG5nIikKYGBgCgoqKllvdXIgam9iKio6IFByb2R1Y2UgYSBuZXcgZGF0YSBmcmFtZSB0aGF0IGFkZHMgZmFtaWx5IGFuZCBjb21tb24gbmFtZXMgZm9yIGVhY2ggcGxhbnQgb2JzZXJ2YXRpb24uCgpgYGB7ciBwbGFudCBqb2luIHNvbHV0aW9ufQojIExvYWQgdGhlIGlucHV0IGRhdGEgZnJhbWVzCnBsYW50X29ic19kZiA8LSByZWFkLmNzdigiZGF0YS9wbGFudF9vYnNlcnZhdGlvbnMuY3N2IikKcGxhbnRfc3BlY2llc19kZiA8LSByZWFkLmNzdigiZGF0YS9wbGFudF9zcGVjaWVzLmNzdiIpCgojIFBlcmZvcm0gdGhlIGpvaW4KZXh0ZW5kZWRfcGxhbnRfb2JzX2RmIDwtIGxlZnRfam9pbihwbGFudF9vYnNfZGYsIHBsYW50X3NwZWNpZXNfZGYsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGpvaW5fYnkoUGxhbnRTcGVjaWVzKSkKCiMgRGlzcGxheSBhIHNlY3Rpb24gb2YgdGhlIHJlc3VsdApleHRlbmRlZF9wbGFudF9vYnNfZGZbNTU6NjUsXQpgYGAKCiMjIEEgQ29tcGxldGUgUmVzZWFyY2ggUXVlc3Rpb24KClBhY2thZ2UgYG55Y2ZsaWdodHMxM2AgY29udGFpbnMgc2V2ZXJhbCBkYXRhIGZyYW1lcyB3aGljaCBob2xkIGhpc3RvcmljYWwgZGF0YSBhYm91dCBhaXJwbGFuZSBmbGlnaHRzIGluIGFuZCBvdXQgb2YgdGhlIHNldmVyYWwgYWlycG9ydHMgaW4gTmV3IFlvcmsgY2l0eS4gVGhlcmUgaXMgb25lIHRhYmxlIGZvciBlYWNoIGltcG9ydGFudCBlbnRpdHkuIEVhY2ggdGFibGUgY29udGFpbnMgZW50aXR5IHByb3BlcnRpZXMsIGFuZCByZWxhdGlvbiBpbmZvcm1hdGlvbi4KCi0gYWlycG9ydHM6IEFpcnBvcnQgRkFBIGNvZGUsIG5hbWUsIGxhdGl0dWRlLCBsb25naXR1ZGUsIHRpbWUgem9uZSwgZXRjLiBmb3IgZWFjaCBhaXJwb3J0Ci0gcGxhbmVzOiBUYWlsIG51bWJlciwgeWVhciBvZiBtYW51ZmFjdHVyZSwgYW5kIHRlY2huaWNhbCBzcGVjaWZpY2F0aW9uIGZvciBlYWNoIHBsYW5lCi0gYWlybGluZXM6IEZBQSBjb2RlIGFuZCBjb21wYW55IG5hbWUgZm9yIGVhY2ggYWlybGluZQotIGZsaWdodHM6IERhdGUsIGRlcGFydHVyZSBhbmQgYXJyaXZhbCB0aW1lcywgcGxhbmUgaW5mb3JtYXRpb24sIGRpc3RhbmNlLCBhaXJsaW5lIGNvZGUsIGV0Yy4gZm9yIGVhY2ggZmxpZ2h0CgoKKipZb3VyIGpvYioqOiBEZXRlcm1pbmUgd2hpY2ggYWlybGluZSBmbGV3IHRoZSB0b3RhbCBncmVhdGVzdCBkaXN0YW5jZSB1c2luZyBzaW5nbGUgZW5naW5lIHBsYW5lcy4KCioqSGludCoqOiBEbyBub3Qgc3RhcnQgdHlwaW5nIGNvZGUgeWV0LgoKV2l0aCBhbnkgYnV0IHRoZSBzaW1wbGVzdCBwcm9ibGVtcywgeW91IHNob3VsZCBhbHdheXMgbWFrZSBhIHBsYW4gKmJlZm9yZSogeW91IHN0YXJ0IGNvZGluZy4gUmVtZW1iZXIgdGhhdCBjb21wdXRlcnMgbmVlZCB0byBoYXZlICoqZXZlcnkgc2luZ2xlIHN0ZXAqKiBsYWlkIG91dCBmb3IgdGhlbSwgYW5kIGluIHdheXMgdGhleSB1bmRlcnN0YW5kLiBJdCBpcyBvZnRlbiBkaWZmaWN1bHQgdG8gd29yayBvdXQgZXhhY3RseSB3aGF0IHRob3NlIHN0ZXBzIHNob3VsZCBiZS4KCk91ciBpbnRpYWwgcGxhbiBtaWdodCBiZToKCjEuIEZpbmQgYWxsIHRoZSBmbGlnaHRzIG1hZGUgYnkgc2luZ2xlIGVuZ2luZSBwbGFuZXMKMi4gQWRkIHRoZSBkaXN0YW5jZXMgdXAgZm9yIGVhY2ggYWlybGluZQozLiBTZWUgd2hpY2ggaXMgdGhlIGxhcmdlc3QuCgpCdXQgdGhpcyBwbGFuIGlzIG5vdCBzdWZmaWNpZW50bHkgZGV0YWlsZWQgZm9yIHRoZSBjb21wdXRlci4gSG93LCBmb3IgZXhhbXBsZSwgd2lsbCB5b3UgdGVsbCB0aGUgY29tcHV0ZXIgdG8gZGV0ZXJtaW5lIHdoaWNoIGZsaWdodHMgd2VyZSBtYWRlIGJ5IHNpbmdsZSBlbmdpbmUgcGxhbmVzPyBUaGUgYHBsYW5lc2AgZGF0YSBmcmFtZSBjb250YWlucyB0aGUgbnVtYmVyIG9mIGVuZ2luZXMgZm9yIGVhY2ggcGxhbmUsIGJ1dCB0aGUgYGZsaWdodHNgIGRhdGEgZnJhbWUgZG9lcyBub3QgLS0gaXQgY29udGFpbnMgb25seSB0aGUgZmxpZ2h0IG51bWJlciAoY29sdW1uIGBmbGlnaHRudW1gKS4gRm9ydHVuYXRlbHksIGBwbGFuZXNgIGFsc28gY29udGFpbnMgYGZsaWdodG51bWAgc28gd2UgY2FuIHVzZSB0aGF0IGNvbHVtbiBhcyBhIGtleSB0byBqb2luIHRoZSB0d28gdGFibGVzLgoKT3VyIGNvbXB1dGVyLWZyaWVuZGx5IHBsYW46CgoxLiBKb2luIGBmbGlnaHRzYCBhbmQgYHBsYW5lc2Agd2l0aCBmbGlnaHRzIGFzIHRoZSBiYXNlIGFuZCBgdGFpbG51bWAgYXMgdGhlIGtleSB0byBhZGQgdGhlIHBsYW5lIGluZm9ybWF0aW9uIGZvciBlYWNoIGZsaWdodC4KMi4gRnJvbSB0aGUgcmVzdWx0aW5nIGV4dGVuZGVkIGZsaWdodHMgZGF0YSwgc2VsZWN0IHRob3NlIHdpdGggb25seSBvbmUgZW5naW5lIChpLmUuIGZpZWxkIGVuZ2luZXMgPT0gMSkKMy4gVGhlIGZsaWdodHMgdGFibGUgY29udGFpbnMgY29sdW1uIGBjYXJyaWVyYCB0byBpZGVudGlmeSB0aGUgYWlybGluZS4gVXNlIGdyb3VwX2J5IGFuZCBzdW1tYXJpc2UgdG8gY29tcHV0ZSB0aGUgdG90YWwgYGRpc3RhbmNlYCBncm91cGVkIGJ5IGBjYXJyaWVyYC4KNC4gUGljayB0aGUgbGFyZ2VzdAoKCk5vdyB5b3UgYXJlIHJlYWR5IHRvIHdyaXRlIHlvdXIgY29kZToKCmBgYHtyIGZsaWdodCBkaXN0YW5jZX0KCmxpYnJhcnkobnljZmxpZ2h0czEzKQoKIyAxLiBKb2luIGBmbGlnaHRzYCBhbmQgYHBsYW5lc2Agd2l0aCBmbGlnaHRzIGFzIHRoZSBiYXNlIGFuZCBgdGFpbG51bWAgYXMgdGhlIGtleSB0byBhZGQgdGhlIHBsYW5lIGluZm9ybWF0aW9uIGZvciBlYWNoIGZsaWdodC4KCmZsaWdodHNfcGxhbmVzIDwtIGxlZnRfam9pbihmbGlnaHRzLCBwbGFuZXMsIGJ5ID0gInRhaWxudW0iKQoKIyAyLiBGcm9tIHRoZSByZXN1bHRpbmcgZXh0ZW5kZWQgZmxpZ2h0cyBkYXRhLCBzZWxlY3QgdGhvc2Ugd2l0aCBvbmx5IG9uZSBlbmdpbmUgKGkuZS4gZmllbGQgZW5naW5lcyA9PSAxKQoKc2luZ2xlX2VuZ2luZV9mbGlnaHRzIDwtIGZsaWdodHNfcGxhbmVzICU+JSBmaWx0ZXIoZW5naW5lcyA9PSAxKQoKIyAzLiBUaGUgZmxpZ2h0cyB0YWJsZSBjb250YWlucyBjb2x1bW4gYGNhcnJpZXJgIHRvIGlkZW50aWZ5IHRoZSBhaXJlbGluZS4gVXNpbmcgZ3JvdXBfYnkgYW5kIHN1bW1hcmlzZSBjb21wdXRlIHRoZSB0b3RhbCBgZGlzdGFuY2VgIGdyb3VwZWQgYnkgYGNhcnJpZXJgLgoKY2Fycmllcl9kaXN0YW5jZSA8LSBzaW5nbGVfZW5naW5lX2ZsaWdodHMgJT4lIGdyb3VwX2J5KGNhcnJpZXIpICU+JSBzdW1tYXJpc2UoVG90YWxEaXN0YW5jZSA9IHN1bShkaXN0YW5jZSkpCgojIDQuIFBpY2sgdGhlIGxhcmdlc3QKCiMgU29ydCBkZXNjZW5kaW5nCmNhcnJpZXJfZGlzdGFuY2UgPC0gY2Fycmllcl9kaXN0YW5jZSAlPiUgYXJyYW5nZShkZXNjKFRvdGFsRGlzdGFuY2UpKQoKIyBEaXNwbGF5CmNhcnJpZXJfZGlzdGFuY2UKCgpgYGAKClRoaXMgaXMgY2xvc2UsIGJ1dCB3aGF0IGlmIHlvdSBkb24ndCBrbm93IHdoYXQgQjYgc3RhbmRzIGZvcj8uIFJlY2FsbCB0aGF0IGRhdGEgZnJhbWUgYGFpcmxpbmVzYCBjb250YWlucyBjYXJyaWVyIGNvZGUgYW5kIGZ1bGwgbmFtZS4gVG8gaW1wcm92ZSB5b3VyIGFuYWx5c2lzIGNsYXJpdHksIGV4dGVuZCB5b3VyIGBjYXJyaWVyX2Rpc3RhbmNlYCB0YWJsZSB0byBpbmNsdWRlIHRoZSBmdWxsIGFpcmxpbmUgbmFtZS4gV2hhdCBvcGVyYXRpb24gd2lsbCB5b3UgcGVyZm9ybT8gV2hhdCBhcmd1bWVudHMgd2lsbCB5b3UgcHJvdmlkZT8KCmBgYHtyIGFpcmxpbmUgbmFtZX0KbGVmdF9qb2luKGNhcnJpZXJfZGlzdGFuY2UsIGFpcmxpbmVzLCBieSA9ICJjYXJyaWVyIikKYGBgCgpZb3UgaGF2ZSBub3cgZXN0YWJsaXNoZWQgdGhhdCBKZXRCbHVlIEFpcndheXMgZmxldyB0aGUgZ3JlYXRlc3QgdG90YWwgZGlzdGFuY2UgdXNpbmcgc2luZ2xlIGVuZ2luZSBwbGFuZXMuCg==