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:
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:
- Identify the cohesive entities in your data
- Identify the properties for each entity
- Make a separate file for each entity
- 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:
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.
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.
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:
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? 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).
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:
- Find all the flights made by single engine planes
- Add the distances up for each airline
- 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:
- Join
flights
and planes
with flights as
the base and tailnum
as the key to add the plane
information for each flight.
- From the resulting extended flights data, select those with only one
engine (i.e. field engines == 1)
- The flights table contains column
carrier
to identify
the airline. Use group_by and summarise to compute the total
distance
grouped by carrier
.
- 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.
LS0tCnRpdGxlOiAiQ29tYmluaW5nIgpkYXRlOiAiU2VtZXN0ZXIgMiwgMjAyMyIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeShrbml0cikKbGlicmFyeSh0aWR5dmVyc2UpCmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBjb21tZW50ID0gIiM+IiwKICBmaWcucGF0aCA9ICJmaWd1cmVzLzA3LyIsICMgdXNlIG9ubHkgZm9yIHNpbmdsZSBSbWQgZmlsZXMKICBjb2xsYXBzZSA9IFRSVUUsCiAgZWNobyA9IFRSVUUKKQpgYGAKCj4gIyMjIyBBc3NvY2lhdGVkIE1hdGVyaWFsCj4KPiBNb2R1bGU6IFtNb2R1bGUgMDcgLSBDb21iaW5pbmcgZGF0YV0oMDctY29tYmluZS5odG1sKQo+Cj4gUmVhZGluZ3MKPgo+IC0gW1IgZm9yIERhdGEgU2NpZW5jZSBDaGFwdGVyIDEzXShodHRwczovL3I0ZHMuaGFkLmNvLm56L3JlbGF0aW9uYWwtZGF0YS5odG1sKQoKCiMjIENvbWJpbmluZyBEYXRhClNvIGZhciwgZWFjaCBkYXRhIHNldCB3ZSBoYXZlIHdvcmtlZCB3aXRoIGhhcyBiZWVuIGNvbnRhaW5lZCBpbiBhIHNpbmdsZSBtb25vbGl0aGljIGRhdGEgZmlsZSB3aXRoIGFsbCB2YXJpYWJsZXMgYXMgY29sdW1ucyBhbmQgYWxsIG9ic2VydmF0aW9ucyBhcyByb3dzLiBJbiBhIHJlYWwgcmVzZXJhY2ggc2l0dWF0aW9uLCB0aGlzIHdpbGwgbm90IGFsd2F5cyBiZSB0aGUgY2FzZS4gQ29tcGxleCBkYXRhIHNldHMgYXJlIG9mdGVuIGRlcGxveWVkIGFzIG11bHRpcGxlIHJlbGF0ZWQgZmlsZXMuIEVhY2ggZmlsZSByZXByZXNlbnRzIGEgc2luZ2xlLCB3ZWxsLWRlZmluZWQgZGF0YSBlbnRpdHksIGFuZCB0aGUgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIGVudGl0aWVzIGFyZSBwYXJ0IG9mIHRoZSBkYXRhIHByb3ZpZGVkLiBJbiBvcmRlciB0byBhbnN3ZXIgb3VyIHJlc2VhcmNoIHF1ZXN0aW9ucywgd2UgbmVlZCB0byBiZSBhYmxlIHRvIGNvbWJpbmUgdGhlc2UgZGlmZmVyZW50IGZpbGVzIGluIHZhcmlvdXMsIHNvbWV0aW1lcyBjb21wbGV4IHdheXMuCgoKIyMgRGVzY3JpYmluZyB0aGUgRGF0YSBVbml2ZXJzZQpUaGUgY29tcGxleCBzeXN0ZW1zIHdlIHN0dWR5IGdlbmVyYWxseSBjb25zaXN0IG9mIG1hbnkgZGlmZmVyZW50ICoqZW50aXRpZXMqKiAoInRoaW5ncyIsIGxpa2UgcGFydGljaXBhbnRzLCB0ZXN0IHJlc3VsdHMsIGV4cGVyaW1lbnRhbCBjb25kaXRpb25zLCBzcGVjaWVzLCByZXNlYXJjaGVycywgZXF1aXBtZW50LCBsYWtlcywgZXRjLikuIEVhY2ggZW50aXR5IGhhcyBpbXBvcnRhbnQgKipwcm9wZXJ0aWVzKiogKGUuZy4gc3ViamVjdCBJRCwgcmVhY3Rpb24gdGltZSwgZXF1aXBtZW50IHR5cGUsIGV0Yy4pLiBUaGVyZSBhcmUgY29tcGxleCAqKnJlbGF0aW9uc2hpcHMqKiBiZXR3ZWVuIGVudGl0aWVzIChlLmcuIGEgcGFydGljdWxhciBwYXJ0aWNpcGFudCB3YXMgZ2l2ZW4gYSBzcGVjaWZpYyBleHBlcmltZW50YWwgdHJlYXRtZW50LCBhIHBhcnRpY3VsYXIgYW5pbWFsIHNwZWNpZXMgd2FzIHNlZW4gaW4gYSBzcGVjaWZpYyBsYWtlKS4gVG9nZXRoZXIsIHRoZXNlIGNvbnN0aXR1dGUgb3VyICoqZGF0YSB1bml2ZXJzZSoqLgoKVG8gYW5zd2VyIG91ciByZXNlYXJjaCBxdWVzdGlvbnMsIHdlIG5lZWQgdG8gYmUgYWJsZSB0byByZXByZXNlbnQgdGhlIGRhdGEgdW5pdmVyc2UgaW4gYW4gb3JnYW5pc2VkIHdheS4gVGhlIGNoYWxsZW5nZXMgdGhpcyBpbXBvc2VzIGNhbiBiZSBzZWVuIGV2ZW4gaW4gY29tcGFyYXRpdmVseSBzaW1wbGUgY29udGV4dHMuIAoKRm9yIGV4YW1wbGUsIGFzc3VtZSB5b3UgYXJlIGludm9sdmVkIHdpdGggdGhlIGxvY2FsIHNvZnRiYWxsIGNvbXBldGl0aW9ucy4gVGhlcmUgYXJlIG11bHRpcGxlIGxlYWd1ZXMsIGVhY2ggbGVhZ3VlIGhhcyBtdWx0aXBsZSB0ZWFtcywgZWFjaCB0ZWFtIGhhcyBhIGNvYWNoIGFuZCBtYW55IHBsYXllcnMuIFlvdXIgam9iIG1pZ2h0IGJlIHRvIG1haW50YWluIGNvbnRhY3QgaW5mb3JtYXRpb24gc28gdGhhdCBwZW9wbGUgY2FuIGJlIGluZm9ybWVkIHdoZW4gdGhlcmUgYXJlIGNoYW5nZXMgdG8gdGhlIGdhbWUgc2NoZWR1bGUuCgpBcyBhbiBleHBlcmllbmNlZCBjb21wdXRlciB1c2VyLCB5b3UgY2FuIGRlY2lkZSB0byBwdXQgYWxsIHRoaXMgaW5mb3JtdGlvbiBpbnRvIHNvbWUgbmljZSBzcHJlYWRzaGVldHMuIEZvciBleGFtcGxlLCB5b3UgbWlnaHQgaGF2ZSBhIHNwcmVhZHNoZWV0IGxpa2UgdGhpcyBmb3Igb25lIHRlYW06CgpgYGB7ciwgZmlnLmNhcCA9ICJUaGUgR25vbWVzIiwgZWNobz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGg9IjEwMCUifQppbmNsdWRlX2dyYXBoaWNzKCJpbWFnZXMvMDctc3BvcnRzX2xlYWd1ZV8wMS5wbmciKQpgYGAKCllvdSB3b3VsZCBoYXZlIGEgY29tcGFyYWJsZSBzcHJlYWRzaGVldCBmb3IgZWFjaCB0ZWFtIGluIGVhY2ggbGVhZ3VlLiAKCklmIHlvdXIgZGF0YSBvbmx5IG5lZWQgdG8gYmUgYXZhaWxhYmxlIHRvIHBlb3BsZSwgdGhpcyBzeXN0ZW0gd291bGQgcHJvYmFibHkgYmUgYWRlcXVhdGUsIGlmIGEgbGl0dGxlIGN1bWJlcnNvbWUuIEhvd2V2ZXIsIGlmIHlvdSB3YW50ZWQgdG8gbG9hZCB5b3VyIGRhdGEgaW50byBhIGNvbXB1dGVyIGZvciBwcm9jZXNzaW5nLCB0aGlzIGZpbGUgZm9ybWF0IHdpbGwgbm90IHdvcmsuIFJlY2FsbCB0aGF0IFIgbmVlZHMgYSByZWN0YW5nbGUgb2YgZGF0YSB3aXRoIGNvbHVtbiBoZWFkZXJzIGFjcm9zcyB0aGUgdG9wIGFuZCBvbmUgcm93IGZvciBlYWNoIGRhdGEgcG9pbnQuIFlvdSBjb3VsZG7igJl0IGV2ZW4gaW1wb3J0IHlvdXIgZGF0YSB1c2luZyB0aGlzICJodW1hbi1mcmllbmRseSIgc3ByZWFkc2hlZXQuCgpUbyBwcm92aWRlIHRoZSBzYW1lIGluZm9ybWF0aW9uIGluIGEgZm9ybWF0IHRoYXQgYSBjb21wdXRlciBjYW4gdW5kZXJzdGFuZCwgeW91IG11c3QgZG8gdGhlIGZvbGxvd2luZzoKCjEuIElkZW50aWZ5IHRoZSBjb2hlc2l2ZSBlbnRpdGllcyBpbiB5b3VyIGRhdGEKMi4gSWRlbnRpZnkgdGhlIHByb3BlcnRpZXMgZm9yIGVhY2ggZW50aXR5CjMuIE1ha2UgYSBzZXBhcmF0ZSBmaWxlIGZvciBlYWNoIGVudGl0eQo0LiBNYWtlIHRoZSByZWxhdGlvbnNoaXBzIGJldHdlZW4gZW50aXRpZXMgZXhwbGljaXQgaW4gdGhlaXIgZGF0YSAoc2VlIGV4YW1wbGVzIGJlbG93KS4KCkluIG91ciBTcG9ydHMgTGVhZ3VlIGV4YW1wbGUsIHdlIHNlZSB0aGF0IHdlIGhhdmUgdHdvIGNsZWFyIGVudGl0aWVzIC0tIFRlYW1zIGFuZCBQbGF5ZXJzLiBBIFRlYW0gaGFzIGZvdXIgcHJvcGVydGllczogdGhlIG5hbWUsIHRoZSBsZWFndWUsIHRoZSBjb2FjaCwgYW5kIHRoZSBjb2FjaCdzIHBob25lIG51bWJlci4gQSBQbGF5ZXIgaGFzIGZvdXIgcHJvcGVydGllczogdGhlaXIgdGVhbSwgdGhlaXIgbmFtZSwgdGhlaXIgcGhvbmUgbnVtYmVyLCBhbmQgdGhlIHBvc2l0aW9uIHRoZXkgcGxheS4gSW4gb3VyIGN1cnJlbnQgZm9ybWF0LCBUZWFtcyBhbmQgUGxheWVycyBhcmUgYWxsIG1peGVkIGludG8gdGhlIHNhbWUgZmlsZS4gRm9yIHRoZSBjb21wdXRlciwgd2UgbmVlZCB0byBzZXBhcmF0ZSB0aGVtLiBNYWtpbmcgZWFjaCBwcm9wZXJ0eSBhIGNvbHVtbiwgYW5kIGVhY2ggaW5zdGFuY2UgYSByb3csIHdlIGNhbiByZWZvcm1hdCBvdXIgZGF0YSBpbnRvIHR3byBzZXBhcmF0ZSBmaWxlcywgbGlrZSB0aGlzOgoKCmBgYHtyLCBmaWcuY2FwID0gIlNwb3J0cyBMZWFndWUgZm9yIENvbXB1dGVycyIsIGVjaG89RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgb3V0LndpZHRoPSIxMDAlIn0KaW5jbHVkZV9ncmFwaGljcygiaW1hZ2VzLzA3LXNwb3J0c19sZWFndWVfMDIucG5nIikKYGBgCgpOb3RlIHRoYXQgdGhlc2UgZmlsZXMgYXJlIGluIGV4YWN0bHkgdGhlIHJlY3Rhbmd1bGFyICJyb3dzIGFyZSBpbnN0YW5jZXM7IGNvbHVtbnMgYXJlIHZhcmlhYmxlcyIgZm9ybWF0IHRoYXQgUiByZXF1aXJlcy4gWW91IGNhbiBlYXNpbHkgcmVhZCB0aGVtIGludG8gUiBhbmQgc3RhcnQgcHJvY2Vzc2luZyB0aGVtLiBGb3IgZXhhbXBsZSwgaWYgdGhlcmUgYXJlIDgwMCBwbGF5ZXJzIGluIHRoZSBQbGF5ZXJzIHNwcmVhZHNoZWV0LCBob3cgd291bGQgeW91IGRldGVybWluZSB0aGUgbnVtYmVyIG9mIHBsYXllcnMgYXNzaWduZWQgdG8gZWFjaCBwb3NpdGlvbiwgYWNyb3NzIHRoZSBlbnRpcmUgbGVhZ3VlPwoKQXNzdW1pbmcgeW91IGhhdmUgdXNlZCBgcmVhZC5jc3ZgIHRvIGltcG9ydCB0aGUgUGxheWVycyBzcHJlYWRzaGVldCBpbnRvIGEgZGF0YWZyYW1lIGBwbGF5ZXJzX2RmYCwgeW91IGNvdWxkIHNheToKYHBsYXllcnNfZGYgJT4lIGdyb3VwX2J5KFBsYXllclBvc2l0aW9uKSAlPiUgY291bnQoKWAuCgpCeSBtYWtpbmcgdHdvIHNlcGFyYXRlIHNwcmVhZHNoZWV0cywgeW91IGhhdmUgc2VwYXJhdGVkIHRoZSBpbmZvcm1hdGlvbiBhYm91dCBwbGF5ZXJzIGZyb20gdGhlIGluZm9ybWF0aW9uIGFib3V0IHRlYW1zLiBJdCBpcyBlc3NlbnRpYWwgdGhhdCwgd2hlbiB5b3VyIGRhdGEgYXJlIHJlb3JnYW5pc2VkIGluIHRoaXMgd2F5LCBubyBpbmZvcm1hdGlvbiBoYXMgYmVlbiBsb3N0LiBUaGF0IGlzICpjYW4geW91IGFuc3dlciB0aGUgc2FtZSBxdWVzdGlvbnMgd2l0aCB0aGlzIGFycmFuZ2VtZW50IGFzIHlvdSBjb3VsZCB3aXRoIHRoZSBvcmlnaW5hbCBvbmU/KgoKRm9yIGV4YW1wbGUsIHdpdGggdGhlIGZpcnN0IGh1bWFuLWZyaWVuZGx5IHZlcnNpb24sIGl0IGlzIGVhc3kgdG8gYW5zd2VyICrigJxXaG8gaXMgRnJlZOKAmXMgY29hY2g/4oCdKiwgYmVjYXVzZSBGcmVkIGFuZCBoaXMgY29hY2ggYXJlIGluIHRoZSBzYW1lIHNwcmVhZHNoZWV0LiBCdXQgaG93IGRvIHlvdSBkbyBpdCB3aXRoIHRoZSBjb21wdXRlci1mcmllbmRseSBvcmdhbmlzYXRpb24gd2hlcmUgRnJlZCdzIGRhdGEgYW5kIGhpcyBUZWFtJ3MgZGF0YSBhcmUgaW4gZGlmZmVyZW50IGZpbGVzPwoKQXMgYSBwZXJzb24geW91IHdvdWxkIHNheSDigJxGcmVk4oCZcyB0ZWFtIGlzIFRoZSBHbm9tZXMgKGZyb20gc2hlZXQgMikgYW5kIEJvYiBpcyB0aGUgY29hY2ggb2YgVGhlIEdub21lcyAoZnJvbSBzaGVldCAxKSB0aGVyZWZvcmUgQm9iIGlzIHRoZSBjb2FjaCBvZiBGcmVkLuKAnSBUbyBkbyBpdCBpbiBSIChhcyB5b3Ugd291bGQgaWYgeW91IG5lZWRlZCB0byBwZXJmb3JtIHRoaXMgb3BlcmF0aW9uIGZvciA4MDAgcGxheWVycykgeW91IHdvdWxkIHNlbGVjdCB0aGUgVGVhbU5hbWUgdmFsdWUgZm9yIEZyZWQgaW4gdGhlIFBsYXllcnMgZmlsZSwgdGhlbiBmaWx0ZXIgZm9yIHRoYXQgdmFsdWUgaW4gdGhlIFRlYW1zIGZpbGUgYW5kIHNlbGVjdCB0aGUgQ29hY2ggY29sdW1uLiBBZnRlciB5b3UgbGVhcm4gaG93IHRvIHdyaXRlIGxvb3BzLCB5b3UgY291bGQgbG9vcCB0aHJvdWdoIGFsbCB0aGUgcGxheWVycyBhbmQgZG8gdGhpcyBjb21wdXRhdGlvbiwgYWNjcnVpbmcgdGhlIHJlc3VsdHMgaW50byBhIHZlY3Rvci4gVGhlbiB5b3UgY291bGQgYWRkIHRoYXQgdmVjdG9yIGFzIGEgbmV3IGNvbHVtbiBzb21ld2hlcmUgdG8gbWF0Y2ggdXAgcGxheWVyIGFuZCBjb2FjaC4gSG93ZXZlciwgUiBwcm92aWRlcyBtdWNoIG1vcmUgc3VjY2luY3Qgd2F5cyB0byBwZXJmb3JtIHRoaXMgb3BlcmF0aW9uLgoKVGhpcyBpcyBmb3J0dW5hdGUsIGJlY2F1c2UgcmVhbCByZXNlYXJjaCBkYXRhIHNldHMgYXJlIG9mdGVuIGRlcGxveWVkIGluIG1hbnkgbW9yZSB0aGFuIGp1c3QgdGhlIHR3byBmaWxlcyB3ZSBuZWVkZWQgaW4gb3VyIHRveSBTcG9ydHMgTGVhZ3VlIGV4YW1wbGUuIExhcmdlLCBjb21wbGV4IGRhdGEgc2V0cyBhcmUgdHlwaWNhbGx5IG5vdCBtYW5hZ2VkIHdpdGggc3ByZWFkc2hlZXRzIGF0IGFsbCwgdGhleSBhcmUgbWFuYWdlZCBhcyAqKmRhdGFiYXNlcyoqLiBXaGlsZSB0aGlzIHRlcm0gaXMgdXNlZCBxdWl0ZSBicm9hZGx5IGluIGNhc3VhbCBkaXNjdXNzaW9uLCBpdCB0ZWNobmljYWxseSByZWZlcnMgc3BlY2lmaWNhbGx5IHRvICoqUmVsYXRpb25hbCBEYXRhYmFzZSBNYW5hZ2VtZW50IFN5c3RlbXMqKiwgc3BlY2lhbCBhcHBsaWNhdGlvbnMgdGhhdCBhcmUgb3B0aW1pc2VkIGZvciBzZWFyY2hpbmcsIGRlbGV0aW5nLCBmaWx0ZXJpbmcsIGFuZCBnZW5lcmFsbHkgbWFuYWdpbmcgaHVnZSBhbW91bnRzIG9mIGRhdGEuCgpJbiBhbiBSREJNUyBlYWNoICoqZW50aXR5KiogaGFzIGl0cyBvd24gKip0YWJsZSoqIGFuZCB0aGUgdG9vbHMgaGF2ZSBFWFRSRU1FTFkgc3RyaWN0IHJ1bGVzIGFib3V0IHRoZSBjb250ZW50cyBhbmQgc3RydWN0dXJlIG9mIHRoZWlyIGVudGl0aWVzLiBUaGUgZGF0YSB1bml2ZXJzZSBpcyBicm9rZW4gZG93biBpbnRvIGF0b21pYyBwaWVjZXMgb2YgZGF0YSBhbmQgcmVjb21iaW5lZCB0aHJvdWdoIHRoZSBydWxlcyBvZiBhIG1hdGhlbWF0aWNhbCBmcmFtZXdvcmsgY2FsbGVkIHRoZSBSZWxhdGlvbmFsIEFsZ2VicmEuIEl0IGlzIGEgYmVhdXRpZnVsIHRoaW5nLCBidXQgeW91IGVuZCB1cCB3aXRoIGEgbG90IG9mIHRhYmxlcy4KClRoZSBmaWd1cmUgYmVsb3cgaXMgYW4gKipFbnRpdHkgUmVsYXRpb25zaGlwIERpYWdyYW0qKi4gSXQgc2hvd3MgdGhlIHN0cnVjdHVyZSBvZiB0aGUgZGF0YWJhc2UgZm9yIGEgKnJlbGF0aXZlbHkgc2ltcGxlKiBkYXRhYmFzZSBzeXN0ZW0gdGhhdCBrZWVwcyB0cmFjayBvZiBGb29kIERpYXJpZXMg4oCTIHdoZXJlIHBlb3BsZSByZWNvcmQgd2hhdCB0aGV5IGVhdCBmb3IgbnV0cml0aW9uYWwgYW5hbHlzaXMuIAoKCmBgYHtyLCBmaWcuY2FwID0gIkV4YW1wbGUgRVJEIiwgZWNobz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGg9IjEwMCUifQppbmNsdWRlX2dyYXBoaWNzKCJpbWFnZXMvMDctRVJELnBuZyIpCmBgYAoKCldoZW4gcmVhbCBkYXRhIGFyZSBleHBvcnRlZCB0byB5b3UgZm9yIHByb2Nlc3NpbmcsIHRoZXJlZm9yZSwgeW91IGNhbiBlbmQgdXAgd2l0aCBhIGxvdCBvZiBzZXBhcmF0ZSBmaWxlcy4gVXNpbmcgUiwgeW91IGhhdmUgdG8gYmUgYWJsZSB0byByZWNvbWJpbmUgZGF0YSBhcyBuZWVkZWQgdG8gYW5zd2VyIHlvdXIgc3BlY2lmaWMgcmVzZWFyY2ggcXVlc3Rpb25zLgoKCiMjIFNpbXBsZSBDb21iaW5pbmcgKEJpbmRzKQoKQmVmb3JlIHdlIGxvb2sgYXQgdGhlIHNvbWV0aW1lcyBjb21wbGV4IGlzc3VlcyBhcm91bmQgY29tYmluaW5nIHRhYmxlcyByZXByZXNlbnRpbmcgZGlmZmVyZW50IGVudGl0aWVzLCB3ZSB3aWxsIHF1aWNrbHkgY292ZXIgdHdvIHNpbXBsZXIgY29tYmluaW5nIHNpdHVhdGlvbnMgdGhhdCBvY2N1ciB3aGVuIHlvdSBoYXZlLCBlc3NlbnRpYWxseSwgYSBzaW5nbGUgdGFibGUgdGhhdCBoYXMgYmVlbiBicm9rZW4gaW50byBtdWx0aXBsZSBpbnB1dCBmaWxlcy4gCgpPdXIgZGF0YSBmb3IgdG9kYXkgY29tZSBmcm9tIHRoZSAqKkdlY2tvcyBhbmQgR3VtYm9vdHMqKiBwcm9qZWN0LiBHZWNrb3MgYW5kIEd1bWJvb3RzIGlzIGEgbXVsdGkteWVhciwgbXVsdGktZGlzY2lwbGluYXJ5IHJlY29uc3RydWN0aW9uIG9mIHdpbGQgTmV3IFplYWxhbmQgR2Vja28gaGFiaXRhdCBvbiB0aGUgT3RhZ28gUGVuaW5zdWxhIChTb3V0aCBJc2xhbmQsIE5ldyBaZWFsYW5kKS4gQSBsYXJnZSBhcmVhIG9mIGxhbmQgb24gdGhlIHBlbmluc3VsYSBoYXMgYmVlbiBwbGFudGVkIHdpdGggbmF0aXZlIGdlY2tvIGhhYml0YXQgc3BlY2llcywgZGl2aWRlZCBpbnRvIHJlcGxpY2F0ZSBxdWFkcmF0cyBhbmQsIGF0IGludGVydmFscywgZGF0YSBhcmUgY29sbGVjdGVkIGFib3V0IHBsYW50IGdyb3d0aCwgYW5kIGludmVydGVicmF0ZSwgcHJlZGF0b3IsIGFuZCBnZWNrbyBwb3B1bGF0aW9uIGFidW5kYW5jZS4gVW5kZXJncmFkdWF0ZSBzdHVkZW50cyBwYXJ0aWNpcGF0ZSBpbiBkYXRhIGNvbGxlY3Rpb24gYW5kIGFuYWx5c2lzIHRvIGdhaW4gZXhwZXJpZW5jZSB3aXRoIGVjb2xvZ3kgZmllbGR3b3JrLgoKCiMjIyBDb21iaW5pbmcgUm93cwoKQXNzdW1lIHRoZSBmaWVsZCBlY29sb2dpc3RzIGhhdmUgc2VudCB5b3Ugc29tZSBwcmVkYXRvciBvYnNlcnZhdGlvbiBkYXRhIGZvciBhbmFseXNpcy4gVGhleSBoYXZlIHNlbnQgeW91IHRocmVlIHNlcGFyYXRlIGNzdiBmaWxlcywgb25lIGZvciBlYWNoIG9mIHRocmVlIGRhdGEgY29sbGVjdGlvbiBkYXlzLgoKCmBgYHtyLCBmaWcuY2FwID0gIkNvbWJpbmluZyBSb3dzIiwgZWNobz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGg9IjEwMCUifQppbmNsdWRlX2dyYXBoaWNzKCJpbWFnZXMvMDctcmJpbmQucG5nIikKYGBgCgpBbGwgdGhyZWUgZmlsZXMgaGF2ZSBleGFjdGx5IHRoZSBzYW1lIGZvcm1hdCDigJMgZm91ciB2YXJpYWJsZSBjb2x1bW5zIGFuZCBvbmUgcm93IGZvciBlYWNoIHJlY29yZGVkIHByZWRhdG9yIG9ic2VydmF0aW9uLiBUaGlzIGlzIGEgbmljZSB3YXkgdG8gZ2V0IGRhdGEg4oCTIGV2ZXJ5dGhpbmcgaXMgYWxyZWFkeSByZWN0YW5ndWxhciwgYW5kIHRoZXkgaGF2ZSBhbGwgdGhlIHNhbWUgY29sdW1ucyBpbiB0aGUgc2FtZSBvcmRlci4gWW91IHNpbXBseSBuZWVkIHRvIGdhdGhlciB0aGVtIGFsbCB1cCBpbnRvIGEgc2luZ2xlIGRhdGFmcmFtZSDigJMgaW4gUiB0aGlzIGlzIHN0cmFpZ2h0Zm9yd2FyZC4gVXNlIGZ1bmN0aW9uIGByYmluZGAsIHBhc3NpbmcgaW4gYWxsIHRoZSBkYXRhIGZyYW1lcyB0byBiZSBjb25jYXRlbmF0ZWQsIHNlcGFyYXRlZCBieSBjb21tYXMuCgpgYGB7ciByYmluZH0KcHJlZF8wMV9kZiA8LSByZWFkLmNzdigiZGF0YS9wcmVkYXRvcl9vYnNlcnZhdGlvbnNfMjAyMl8wNF8zMC5jc3YiKQpwcmVkXzAyX2RmIDwtIHJlYWQuY3N2KCJkYXRhL3ByZWRhdG9yX29ic2VydmF0aW9uc18yMDIzXzA0XzIyLmNzdiIpCnByZWRfMDNfZGYgPC0gcmVhZC5jc3YoImRhdGEvcHJlZGF0b3Jfb2JzZXJ2YXRpb25zXzIwMjNfMDRfMjMuY3N2IikKCnByZWRfYWxsX2RmIDwtIHJiaW5kKHByZWRfMDFfZGYsIHByZWRfMDJfZGYsIHByZWRfMDNfZGYpCnByZWRfYWxsX2RmWzUwOjYwLF0KYGBgCgpOb3RlIHRoYXQgaWYgdGhlIGFyZ3VtZW50cyB0byByYmluZCBkb24ndCBhbGwgaGF2ZSBleGFjdGx5IHRoZSBzYW1lIGNvbHVtbnMsIHRoZSBmdW5jdGlvbiB3aWxsLCBxdWl0ZSBzZW5zaWJseSwgdGhyb3cgYW4gZXJyb3I6CgpgYGB7ciByYmluZCBlcnJvciwgZXJyb3I9VFJVRX0KIyBGb3IgcHVycG9zZXMgb2YgaWxsdXN0cmlvbiwgd2UgZHJvcCB0aGUgNHRoIGNvbHVtbiBmcm9tIHByZWRfMDFfZGYKcHJlZF8wMV9kZiA8LSBwcmVkXzAxX2RmWyAsIC00XQoKIyBUaGUgcmJpbmQgY29tbWFuZCBub3cgZmFpbHMKcHJlZF9hbGxfZGYgPC0gcmJpbmQocHJlZF8wMV9kZiwgcHJlZF8wMl9kZiwgcHJlZF8wM19kZikKCmBgYAoKIyMjIENvbWJpbmluZyBDb2x1bW5zCgpMZXNzIGNvbW1vbmx5LCB5b3UgbWlnaHQgaGF2ZSBtdWx0aXBsZSBkYXRhIHNldHMgd2l0aCB0aGUgc2FtZSByb3dzIChvYmVydmF0aW9ucyksIGJ1dCBkaWZmZXJlbnQgY29sdW1ucyAoZGVwZW5kZW50IGFuZCBpbmRlcGVuZGVudCB2YXJpYWJsZXMpLiBJbiB0aGlzIGNhc2UsIHVzZSBmdW5jdGlvbiBgY2JpbmRgIC0tIHRoZSBzeW50YXggaXMgdGhlIHNhbWUgYXMgZm9yICBgcmJpbmRgLiAKCkluIHByYWN0aWNlLCB0aGlzIHNpdHVhdGlvbiBhY3R1YWxseSBvY2N1cnMgdmVyeSByYXJlbHkuIFRoaXMgaXMgZm9ydHVuYXRlLCBiZWNhdXNlIGNvbHVtbiBiaW5kaW5nIGlzIGV4dHJlbWVseSByaXNreS4gQ29sdW1uIGJpbmRpbmcgcmVsaWVzIGVudGlyZWx5IHVwb24gdGhlIHJvd3MgaW4gdGhlIHNlcGFyYXRlIGRhdGEgZnJhbWVzIGJlaW5nIGlkZW50aWNhbCBhbmQgaW4gdGhlIHNhbWUgb3JkZXI7IHRoZSBmdW5jdGlvbiBpdHNlbGYgcGVyZm9ybXMgbm8gY2hlY2tzIG9uIHRoaXMgY29uc3RyYWludC4gCgpCZWNhdXNlIG9mIHRoZSBkYW5nZXIgb2YgbWl4aW5nIHVwIGRhdGEgZnJvbSBkaWZmZXJlbnQgb2JzZXJ2YXRpb25zLCBJIG9ubHkgdXNlIGNiaW5kIHdoZW4gSSBhbSBnZW5lcmF0aW5nIGNvbHVtbnMgZHluYW1pY2FsbHkgKGluIGNvZGUpIGFuZCB3aXNoIHRvIGJ1aWxkIGEgZGF0YSBmcmFtZSBmcm9tIHRoZW0uCgogIAojIyBSZWxhdGlvbmFsIENvbWJpbmluZyAoSm9pbnMpCgpSZWNhbGwgb3VyIFNwb3J0cyBMZWFndWUgZGF0YToKCmBgYHtyLCBmaWcuY2FwID0gIlNwb3J0cyBMZWFndWUgZm9yIENvbXB1dGVycyIsIGVjaG89RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgb3V0LndpZHRoPSIxMDAlIn0KaW5jbHVkZV9ncmFwaGljcygiaW1hZ2VzLzA3LXNwb3J0c19sZWFndWVfMDIucG5nIikKYGBgCgpPdXIgInJlc2VhcmNoIHF1ZXN0aW9uIiB3YXMgaG93IHRvIGZpbmQgdGhlIG5hbWUgb2YgQm9iJ3MgY29hY2guIE1vcmUgZ2VuZXJhbGx5LCBhc3N1bWUgd2Ugd2FudCB0byBrbm93IHRoZSBjb2FjaGVzIG9mIGFsbCB0aGUgcGxheWVycy4gV2hlbiB3b3JraW5nIHdpdGggZGF0YSBmcmFtZXMsIHRoaXMgbWVhbnMgd2Ugd2FudCB0byBhZGQgY29sdW1ucyB0byB0aGUgUGxheWVyJ3MgdGFibGUgdGhhdCBjb250YWluIHRoZSBhc3NvY2lhdGVkIGNvYWNoIGluZm9ybWF0aW9uIGZvciBlYWNoIHJvdy4KCldlIG5vdGVkIHRoYXQgdGhlIFRlYW0gdGFibGUgYW5kIHRoZSBQbGF5ZXIgdGFibGUgaGF2ZSBhIGNvbW1vbiBjb2x1bW4g4oCTIFRlYW1OYW1lIOKAkyBhbmQgdGhhdCB3ZSBjb3VsZCBtYXRjaCBwbGF5ZXJzIGFuZCBjb2FjaGVzIGJ5IGxvb2tpbmcgZm9yIG1hdGNoaW5nIHZhbHVlcyBvZiBUZWFtTmFtZS4gVGhpcyBpcyBhIHJlcXVpcmVtZW50IGZvciByZWxhdGlvbmFsIGNvbWJpbmluZyAtLSB0d28gdGFibGVzIG11c3Qgc2hhcmUgYSBjb21tb24gY29sdW1uIChvciBjb2x1bW5zKSB0aGF0IGNhbiBiZSB1c2VkIHRvIG1hdGNoIG9ic2VydmF0aW9ucyBiZXR3ZW4gdGhlIHRhYmxlcy4gVGhlc2UgY29tbW9uIGNvbHVtbnMgYXJlIGNhbGxlZCAqKmtleXMqKi4gCgpJbiBmb3JtYWwgZGF0YWJhc2VzLCB0aGVyZSBhcmUgbG90cyBvZiBydWxlcyBhYm91dCBrZXlzLCBwYXJ0aWN1bGFybHkgYXJvdW5kICoqdW5pcXVlbmVzcyoqLiBOb3RlIGhvdyBjb25mdXNpbmcgaXQgd291bGQgYmUgaWYgdGhlcmUgd2VyZSAqdHdvKiByb3dzIGluIHRoZSBUZWFtcyBkYXRhIGZyYW1lIHdpdGggdGVhbW5hbWUgKlRoZSBHbm9tZXMqIOKAkyB3aGljaCBvZiB0aGUgdHdvIGNvYWNoZXMgd291bGQgeW91IHdhbnQgdG8gbWF0Y2ggd2l0aCBGcmVkPyBeW05COiBUaGVyZSBpcyBhIG1ldGhvZCBmb3IgZGVhbGluZyB3aXRoIHRoZSAibXVsdGlwbGUgY29hY2hlcyIgcHJvYmxlbSwgYnV0IGl0IGlzIG91dCBvZiBzY29wZSBmb3IgdG9kYXkncyBtb2R1bGUuIEFzayBtZSBmb3IgbW9yZSBkZXRhaWwgaWYgeW91J3JlIGludGVyZXN0ZWQuXSBGb3IgdG9kYXkgd2Ugd2lsbCBhc3N1bWUgdGhhdCB0aGVzZSByZXF1aXJlbWVudHMgYXJlIG1ldCDigJMgZm9yIGZ1cnRoZXIgZGV0YWlscywgc2VlIHRoZSBSIGZvciBEYXRhIFNjaWVuY2UgY2hhcHRlci4KCiMjIEpvaW4KCkluIGRhdGFiYXNlIHRlcm1pbm9sb2d5LCB0aGUgcHJvY2VzcyBvZiBtYXRjaGluZyB1cCByZWNvcmRzIGZyb20gbXVsdGlwbGUgdGFibGVzIGlzIGNhbGxlZCBhICoqam9pbioqLiBSIChtb3JlIHNwZWNpZmljYWxseSB0aWR5dmVyc2UpIHVzZXMgdGhpcyB0ZXJtaW5vbG9neSBhcyB3ZWxsLiAgU29sdmluZyBwcm9ibGVtcyBsaWtlIGZpbmRpbmcgdGhlIGNvYWNoIGZvciBlYWNoIHBsYXllciBpbnZvbHZlcyBqb2luaW5nIHR3byBvciBtb3JlIGRhdGEgZnJhbWVzIG9uIG9uZSBvciBtb3JlIGNvbW1vbiBjb2x1bW5zLgoKVG8gc2xpZ2h0bHkgY29tcGxpY2F0ZSB0aGluZ3MsIFIgZG9lcyBub3QgcHJvdmlkZSBhIGZ1bmN0aW9uIGNhbGxlZCDigJxqb2lu4oCdLiAgSXQgcHJvdmlkZSAqc2l4IGRpZmZlcmVudCBmdW5jdGlvbnMqIHdoaWNoIHBlcmZvcm0gc2xpZ2h0bHkgZGlmZmVyZW50IHR5cGVzIG9mIGpvaW4gb3BlcmF0aW9uLiBUaGUgZnVuY3Rpb24gbmFtZXMgYXJlIGRlcml2ZWQgZnJvbSB0aGUgZm9ybWFsIGRhdGFiYXNlIHRlcm1pbm9sb2d5LiBGb3J0dW5hdGVseSwgaW4gdGhlIHZhc3QgbWFqb3JpdHkgb2YgY2FzZXMgd2UgZW5jb3VudGVyLCB5b3Ugb25seSBuZWVkIG9uZSBvZiB0aGUgam9pbiBmYW1pbHkgLS0gYGxlZnRfam9pbmAuIFdlIHdpbGwgY292ZXIgYGxlZnRfam9pbmAgaGVyZTsgc2VlIHRoZSBhZGRpdGlvbmFsIHJlYWRpbmdzIGZvciBkaXNjdXNzaW9ucyBvZiB0aGUgbGVzcyBjb21tb24gZm9ybXMuCgpUaGUgZnVuY3Rpb24gYGxlZnRfam9pbmAgdGFrZXMgYXMgYXJndW1lbnRzIHRoZSB0d28gdGFibGVzIChkYXRhIGZyYW1lcykgeW91IHdpc2ggdG8gam9pbi4gVGhlIG5hbWUgYGxlZnRfam9pbmAgaGVscHMgeW91IHRvIHJlbWVtYmVyIGhvdyB0byBvcmRlciB5b3VyIGFyZ3VtZW50cyDigJMgcHV0IHRoZSAqKmJhc2UgdGFibGUqKiBvbiB0aGUgbGVmdCAoaS5lLiBhcyB0aGUgZmlyc3QgYXJndW1lbnQpLiBUaGUgKipiYXNlIHRhYmxlKiogaXMgdGhlIG9uZSB5b3Ugd2lzaCB0byBhZGQgY29sdW1ucyB0by4KClJlY2FsbCB0aGF0IHdlIHdhbnRlZCB0byBrbm93IGVhY2ggcGxheWVy4oCZcyBjb2FjaC4gV2UgdGhlcmVmb3JlIHdhbnQgdG8gYWRkIGEgQ29hY2hOYW1lIGNvbHVtbiB0byB0aGUgUGxheWVycyBkYXRhIGZyYW1lLiBUaHVzIFBsYXllcnMgaXMgdGhlICoqYmFzZSB0YWJsZSoqLiBXZSBwYXNzIGl0IGZpcnN0IGludG8gZnVuY3Rpb24gJ2xlZnRfam9pbicsIGFsb25nIHdpdGggdGhlIG5hbWUgb2YgdGhlICoqa2V5KiogY29sdW1uLgoKCmBgYHtyIG1ha2UgZGYsIGVjaG8gPSBGQUxTRX0KdGVhbXNfbWF0cml4IDwtIGNiaW5kKFRlYW1OYW1lID0gYygiVGhlIEdub21lcyIsICJUaGUgT3JjcyIsICJUaGUgQXJhY2huaWRzIiksCiAgICAgICAgICAgICAgICAgIExlYWd1ZSA9IGMoIk92ZXIgNTBzIiwgIldvbWVuJ3MgUHJlbWllciIsICJVbmRlciAxNXMiKSwKICAgICAgICAgICAgICAgICAgQ29hY2ggPSBjKCJCb2IiLCAiTmdhaXJlIiwgIkRvcm90aHkiKSwKICAgICAgICAgICAgICAgICAgQ29hY2hQaG9uZSA9IGMoIjQ3NC01NTU1IiwgIjAyMSAzMzMtNDU2NyIsICIwMjIgOTg3LTY1NDMiKSkKdGVhbXNfZGYgPC0gYXMuZGF0YS5mcmFtZSh0ZWFtc19tYXRyaXgpCgpwbGF5ZXJzX21hdHJpeCA8LSBjYmluZChQbGF5ZXJOYW1lID0gYygiRnJlZCIsICJTYWxseSIsICJFdGhlbCIsICJMdWN5IiksCiAgICAgICAgICAgICAgICAgICAgICAgIFRlYW1OYW1lID0gYygiVGhlIEdub21lcyIsICJUaGUgR25vbWVzIiwgIlRoZSBHbm9tZXMiLCAiVGhlIEFyYWNobmlkcyIpLAogICAgICAgICAgICAgICAgICAgICAgICBQbGF5ZXJQb3NpdGlvbiA9IGMoIkZpcnN0IEJhc2UiLCAiUGl0Y2hlciIsICJMZWZ0IEZpZWxkIiwgIkNhdGNoZXIiKSwKICAgICAgICAgICAgICAgICAgICAgICAgUGxheWVyUGhvbmUgPSBjKCIwMjEgMTExIDIyMjIiLCAiMDIxIDIyMiAzMzMzIiwgIjAyMSAxMTEgNDQ0NCIsICI0NzQtMTIzNCIpKQpwbGF5ZXJzX2RmIDwtIGFzLmRhdGEuZnJhbWUocGxheWVyc19tYXRyaXgpCgpgYGAKCgpgYGB7ciBqb2luXzAxfQojIFdlIG5lZWQgdGhlIHRpZHl2ZXJzZSB0byBiZSBhYmxlIHRvIGpvaW4KbGlicmFyeSh0aWR5dmVyc2UpCgojIEFzc3VtZSB3ZSBoYXZlIGltcG9ydGVkIG91dCBzcG9ydHMgbGVhZ3VlIGRhdGEgdXNpbmcgcmVhZC5jc3YKaGVhZCh0ZWFtc19kZikKaGVhZChwbGF5ZXJzX2RmKQoKIyBQZXJmb3JtIHRoZSBqb2luCmV4dGVuZGVkX3BsYXllcnNfZGYgPC0gbGVmdF9qb2luKHBsYXllcnNfZGYsIHRlYW1zX2RmLCBieSA9ICJUZWFtTmFtZSIpCgojIFRoZSBuZXcgZGF0YSBmcmFtZQpleHRlbmRlZF9wbGF5ZXJzX2RmCmBgYAoKVGhlIG5ldyBkYXRhZnJhbWUgaGFzIGFsbCB0aGUgY29sdW1ucyBpbiBQbGF5ZXJzLCBpbiB0aGUgb3JpZ2luYWwgb3JkZXIuIEl0IGFsc28gaGFzIGFsbCB0aGUgY29sdW1ucyBpbiBUZWFtcywgaW4gdGhlIG9yaWdpbmFsIG9yZGVyLiBGb3IgKmVhY2ggcm93IG9mIFBsYXllcnMqICh0aGUgYmFzZSB0YWJsZTsgdGhlIG9uZSBvbiB0aGUgbGVmdCksIHRoZSB2YWx1ZXMgb2YgdGhlIFRlYW1zIGNvbHVtbnMgYXJlIHRha2VuIGZyb20gdGhlIHJvdyBpbiBUZWFtcyB0aGF0IGhhcyB0aGUgc2FtZSB2YWx1ZSBpbiBjb2x1bW4gVGVhbU5hbWUgKHRoZSBrZXkpLgoKIyMjIEV4ZXJjaXNlCgpXaGF0IG91dHB1dCBkbyB5b3UgZXhwZWN0IGlmIHlvdSByZXZlcnNlIHRoZSBvcmRlciBvZiBgcGxheWVyc19kZmAgYW5kIGB0ZWFtc19kZmAgaW4gdGhlIGNhbGwgdG8gYGxlZnRfam9pbmAsIGFib3ZlPyBUcnkgdG8gdGhpbmsgdGhyb3VnaCB0aGlzIGV4ZXJjaXNlIGJlZm9yZSB0ZXN0aW5nIHRoZSBjb2RlLiBIaW50OiBXaGVuIHlvdSByZWFjaCB0aGUgcG9pbnQgd2hlcmUgeW91IHNheSAiYnV0IHRoYXQgZG9lc24ndCBtYWtlIHNlbnNlISIsIHJlY29nbmlzZSB0aGF0IHRoZSBjb21wdXRlciB3aWxsIGJlIGluIHRoZSBzYW1lIHNpdHVhdGlvbi4KCiMjIFByYWN0aWNlIEpvaW5pbmcKClRoZSBHZWNrb3MgYW5kIEd1bWJvb3RzIHByb2plY3QgcHJvdmlkZXMgaW5mb3JtYXRpb24gYWJvdXQgdGhlIHBsYW50cyBvYnNlcnZlZCBncm93aW5nIGluIGVhY2ggcXVhZHJhdCAocmVzb3VyY2UgZmlsZSAqcGxhbnRfb2JzZXJ2YXRpb25zLmNzdiopLiBJbiBhIHNlcGFyYXRlIGZpbGUsIHRoZXkgcHJvdmlkZSBhZGRpdGlvbmFsIGluZm9ybWF0aW9uIGFib3V0IHBsYW50IHNwZWNpZXMgKHJlc291cmNlIGZpbGUgKnBsYW50X3NwZWNpZXMuY3N2KikuIFRoaXMgbWlycm9ycyB0aGUgc3RydWN0dXJlIG9mIHRoZSBkYXRhYmFzZSwgd2hlcmUgdGhlc2UgZW50aXRpZXMgYXJlIHN0b3JlZCBzZXBhcmF0ZWx5IHRvIGFsbG93IGVmZmljaWVudCBkYXRhIG9wZXJhdGlvbnMgYW5kIGNvbXBsZXRlIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBkYXRhIHVuaXZlcnNlIChlLmcuIHRvIGFsbG93IGluZm9ybWF0aW9uIHRvIGJlIHN0b3JlZCBmb3IgcGxhbnQgc3BlY2llcyB0aGF0IGhhdmUgbm90IHlldCBiZWVuIG9ic2VydmVkLCBidXQgd2hpY2ggYXJlIGV4cGVjdGVkIHRvIGJlIGluIHRoZSBmdXR1cmUpLgoKYGBge3IsIGZpZy5jYXAgPSAiUGxhbnQgT2JzZXJ2YXRpb24gYW5kIFNwZWNpZXMgRGF0YSBJbnB1dCBGaWxlcyIsIGVjaG89RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgb3V0LndpZHRoPSIxMDAlIn0KaW5jbHVkZV9ncmFwaGljcygiaW1hZ2VzLzA3LXBsYW50X2RhdGFfaW5wdXRfZmlsZXMucG5nIikKYGBgCgoqKllvdXIgam9iKio6IFByb2R1Y2UgYSBuZXcgZGF0YSBmcmFtZSB0aGF0IGFkZHMgZmFtaWx5IGFuZCBjb21tb24gbmFtZXMgZm9yIGVhY2ggcGxhbnQgb2JzZXJ2YXRpb24uCgpgYGB7ciBwbGFudCBqb2luIHNvbHV0aW9ufQojIExvYWQgdGhlIGlucHV0IGRhdGEgZnJhbWVzCnBsYW50X29ic19kZiA8LSByZWFkLmNzdigiZGF0YS9wbGFudF9vYnNlcnZhdGlvbnMuY3N2IikKcGxhbnRfc3BlY2llc19kZiA8LSByZWFkLmNzdigiZGF0YS9wbGFudF9zcGVjaWVzLmNzdiIpCgojIFBlcmZvcm0gdGhlIGpvaW4KZXh0ZW5kZWRfcGxhbnRfb2JzX2RmIDwtIGxlZnRfam9pbihwbGFudF9vYnNfZGYsIHBsYW50X3NwZWNpZXNfZGYsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGpvaW5fYnkoUGxhbnRTcGVjaWVzKSkKCiMgRGlzcGxheSBhIHNlY3Rpb24gb2YgdGhlIHJlc3VsdApleHRlbmRlZF9wbGFudF9vYnNfZGZbNTU6NjUsXQpgYGAKCiMjIEEgQ29tcGxldGUgUmVzZWFyY2ggUXVlc3Rpb24KClBhY2thZ2UgYG55Y2ZsaWdodHMxM2AgY29udGFpbnMgc2V2ZXJhbCBkYXRhIGZyYW1lcyB3aGljaCBob2xkIGhpc3RvcmljYWwgZGF0YSBhYm91dCBhaXJwbGFuZSBmbGlnaHRzIGluIGFuZCBvdXQgb2YgdGhlIHNldmVyYWwgYWlycG9ydHMgaW4gTmV3IFlvcmsgY2l0eS4gVGhlcmUgaXMgb25lIHRhYmxlIGZvciBlYWNoIGltcG9ydGFudCBlbnRpdHkuIEVhY2ggdGFibGUgY29udGFpbnMgZW50aXR5IHByb3BlcnRpZXMsIGFuZCByZWxhdGlvbiBpbmZvcm1hdGlvbi4KCi0gYWlycG9ydHM6IEFpcnBvcnQgRkFBIGNvZGUsIG5hbWUsIGxhdGl0dWRlLCBsb25naXR1ZGUsIHRpbWUgem9uZSwgZXRjLiBmb3IgZWFjaCBhaXJwb3J0Ci0gcGxhbmVzOiBUYWlsIG51bWJlciwgeWVhciBvZiBtYW51ZmFjdHVyZSwgYW5kIHRlY2huaWNhbCBzcGVjaWZpY2F0aW9uIGZvciBlYWNoIHBsYW5lCi0gYWlybGluZXM6IEZBQSBjb2RlIGFuZCBjb21wYW55IG5hbWUgZm9yIGVhY2ggYWlybGluZQotIGZsaWdodHM6IERhdGUsIGRlcGFydHVyZSBhbmQgYXJyaXZhbCB0aW1lcywgcGxhbmUgaW5mb3JtYXRpb24sIGRpc3RhbmNlLCBhaXJsaW5lIGNvZGUsIGV0Yy4gZm9yIGVhY2ggZmxpZ2h0CgoKKipZb3VyIGpvYioqOiBEZXRlcm1pbmUgd2hpY2ggYWlybGluZSBmbGV3IHRoZSB0b3RhbCBncmVhdGVzdCBkaXN0YW5jZSB1c2luZyBzaW5nbGUgZW5naW5lIHBsYW5lcy4KCioqSGludCoqOiBEbyBub3Qgc3RhcnQgdHlwaW5nIGNvZGUgeWV0LgoKV2l0aCBhbnkgYnV0IHRoZSBzaW1wbGVzdCBwcm9ibGVtcywgeW91IHNob3VsZCBhbHdheXMgbWFrZSBhIHBsYW4gKmJlZm9yZSogeW91IHN0YXJ0IGNvZGluZy4gUmVtZW1iZXIgdGhhdCBjb21wdXRlcnMgbmVlZCB0byBoYXZlICoqZXZlcnkgc2luZ2xlIHN0ZXAqKiBsYWlkIG91dCBmb3IgdGhlbSwgYW5kIGluIHdheXMgdGhleSB1bmRlcnN0YW5kLiBJdCBpcyBvZnRlbiBkaWZmaWN1bHQgdG8gd29yayBvdXQgZXhhY3RseSB3aGF0IHRob3NlIHN0ZXBzIHNob3VsZCBiZS4KCk91ciBpbnRpYWwgcGxhbiBtaWdodCBiZToKCjEuIEZpbmQgYWxsIHRoZSBmbGlnaHRzIG1hZGUgYnkgc2luZ2xlIGVuZ2luZSBwbGFuZXMKMi4gQWRkIHRoZSBkaXN0YW5jZXMgdXAgZm9yIGVhY2ggYWlybGluZQozLiBTZWUgd2hpY2ggaXMgdGhlIGxhcmdlc3QuCgpCdXQgdGhpcyBwbGFuIGlzIG5vdCBzdWZmaWNpZW50bHkgZGV0YWlsZWQgZm9yIHRoZSBjb21wdXRlci4gSG93LCBmb3IgZXhhbXBsZSwgd2lsbCB5b3UgdGVsbCB0aGUgY29tcHV0ZXIgdG8gZGV0ZXJtaW5lIHdoaWNoIGZsaWdodHMgd2VyZSBtYWRlIGJ5IHNpbmdsZSBlbmdpbmUgcGxhbmVzPyBUaGUgYHBsYW5lc2AgZGF0YSBmcmFtZSBjb250YWlucyB0aGUgbnVtYmVyIG9mIGVuZ2luZXMgZm9yIGVhY2ggcGxhbmUsIGJ1dCB0aGUgYGZsaWdodHNgIGRhdGEgZnJhbWUgZG9lcyBub3QgLS0gaXQgY29udGFpbnMgb25seSB0aGUgZmxpZ2h0IG51bWJlciAoY29sdW1uIGBmbGlnaHRudW1gKS4gRm9ydHVuYXRlbHksIGBwbGFuZXNgIGFsc28gY29udGFpbnMgYGZsaWdodG51bWAgc28gd2UgY2FuIHVzZSB0aGF0IGNvbHVtbiBhcyBhIGtleSB0byBqb2luIHRoZSB0d28gdGFibGVzLgoKT3VyIGNvbXB1dGVyLWZyaWVuZGx5IHBsYW46CgoxLiBKb2luIGBmbGlnaHRzYCBhbmQgYHBsYW5lc2Agd2l0aCBmbGlnaHRzIGFzIHRoZSBiYXNlIGFuZCBgdGFpbG51bWAgYXMgdGhlIGtleSB0byBhZGQgdGhlIHBsYW5lIGluZm9ybWF0aW9uIGZvciBlYWNoIGZsaWdodC4KMi4gRnJvbSB0aGUgcmVzdWx0aW5nIGV4dGVuZGVkIGZsaWdodHMgZGF0YSwgc2VsZWN0IHRob3NlIHdpdGggb25seSBvbmUgZW5naW5lIChpLmUuIGZpZWxkIGVuZ2luZXMgPT0gMSkKMy4gVGhlIGZsaWdodHMgdGFibGUgY29udGFpbnMgY29sdW1uIGBjYXJyaWVyYCB0byBpZGVudGlmeSB0aGUgYWlybGluZS4gVXNlIGdyb3VwX2J5IGFuZCBzdW1tYXJpc2UgdG8gY29tcHV0ZSB0aGUgdG90YWwgYGRpc3RhbmNlYCBncm91cGVkIGJ5IGBjYXJyaWVyYC4KNC4gUGljayB0aGUgbGFyZ2VzdAoKCk5vdyB5b3UgYXJlIHJlYWR5IHRvIHdyaXRlIHlvdXIgY29kZToKCmBgYHtyIGZsaWdodCBkaXN0YW5jZX0KCmxpYnJhcnkobnljZmxpZ2h0czEzKQoKIyAxLiBKb2luIGBmbGlnaHRzYCBhbmQgYHBsYW5lc2Agd2l0aCBmbGlnaHRzIGFzIHRoZSBiYXNlIGFuZCBgdGFpbG51bWAgYXMgdGhlIGtleSB0byBhZGQgdGhlIHBsYW5lIGluZm9ybWF0aW9uIGZvciBlYWNoIGZsaWdodC4KCmZsaWdodHNfcGxhbmVzIDwtIGxlZnRfam9pbihmbGlnaHRzLCBwbGFuZXMsIGJ5ID0gInRhaWxudW0iKQoKIyAyLiBGcm9tIHRoZSByZXN1bHRpbmcgZXh0ZW5kZWQgZmxpZ2h0cyBkYXRhLCBzZWxlY3QgdGhvc2Ugd2l0aCBvbmx5IG9uZSBlbmdpbmUgKGkuZS4gZmllbGQgZW5naW5lcyA9PSAxKQoKc2luZ2xlX2VuZ2luZV9mbGlnaHRzIDwtIGZsaWdodHNfcGxhbmVzICU+JSBmaWx0ZXIoZW5naW5lcyA9PSAxKQoKIyAzLiBUaGUgZmxpZ2h0cyB0YWJsZSBjb250YWlucyBjb2x1bW4gYGNhcnJpZXJgIHRvIGlkZW50aWZ5IHRoZSBhaXJlbGluZS4gVXNpbmcgZ3JvdXBfYnkgYW5kIHN1bW1hcmlzZSBjb21wdXRlIHRoZSB0b3RhbCBgZGlzdGFuY2VgIGdyb3VwZWQgYnkgYGNhcnJpZXJgLgoKY2Fycmllcl9kaXN0YW5jZSA8LSBzaW5nbGVfZW5naW5lX2ZsaWdodHMgJT4lIGdyb3VwX2J5KGNhcnJpZXIpICU+JSBzdW1tYXJpc2UoVG90YWxEaXN0YW5jZSA9IHN1bShkaXN0YW5jZSkpCgojIDQuIFBpY2sgdGhlIGxhcmdlc3QKCiMgU29ydCBkZXNjZW5kaW5nCmNhcnJpZXJfZGlzdGFuY2UgPC0gY2Fycmllcl9kaXN0YW5jZSAlPiUgYXJyYW5nZShkZXNjKFRvdGFsRGlzdGFuY2UpKQoKIyBEaXNwbGF5CmNhcnJpZXJfZGlzdGFuY2UKCgpgYGAKClRoaXMgaXMgY2xvc2UsIGJ1dCB3aGF0IGlmIHlvdSBkb24ndCBrbm93IHdoYXQgQjYgc3RhbmRzIGZvcj8uIFJlY2FsbCB0aGF0IGRhdGEgZnJhbWUgYGFpcmxpbmVzYCBjb250YWlucyBjYXJyaWVyIGNvZGUgYW5kIGZ1bGwgbmFtZS4gVG8gaW1wcm92ZSB5b3VyIGFuYWx5c2lzIGNsYXJpdHksIGV4dGVuZCB5b3VyIGBjYXJyaWVyX2Rpc3RhbmNlYCB0YWJsZSB0byBpbmNsdWRlIHRoZSBmdWxsIGFpcmxpbmUgbmFtZS4gV2hhdCBvcGVyYXRpb24gd2lsbCB5b3UgcGVyZm9ybT8gV2hhdCBhcmd1bWVudHMgd2lsbCB5b3UgcHJvdmlkZT8KCmBgYHtyIGFpcmxpbmUgbmFtZX0KbGVmdF9qb2luKGNhcnJpZXJfZGlzdGFuY2UsIGFpcmxpbmVzLCBieSA9ICJjYXJyaWVyIikKYGBgCgpZb3UgaGF2ZSBub3cgZXN0YWJsaXNoZWQgdGhhdCBKZXRCbHVlIEFpcndheXMgZmxldyB0aGUgZ3JlYXRlc3QgdG90YWwgZGlzdGFuY2UgdXNpbmcgc2luZ2xlIGVuZ2luZSBwbGFuZXMuCg==