1 Prelude

Joining datasets is a crucial skill when working with health-related data as it allows you to combine information from multiple sources, leading to more comprehensive and insightful analyses. In this lesson, you’ll learn how to use different joining techniques using R’s dplyr package. Let’s get started!

2 Learning Objectives

  • You understand how each of the different dplyr joins work: left, right, inner and full.

  • You’re able to choose the appropriate join for your data

  • You can join simple datasets together using functions from dplyr

2.1 Packages

‣ Please load the packages needed for this lesson

# Load packages
if(!require(pacman)) install.packages("pacman")
pacman::p_load(tidyverse, countrycode)

2.2 What is a join and why do we need it?

‣ To illustrate the utility of joins, let’s start with a toy example.

‣ Consider the following two datasets: demographic and test_info

demographic: Contains names and ages of three patients

demographic <- 
  tribble(~name,     ~age,
          "Alice",    25,
          "Bob",      32,
          "Charlie",  45)

demographic

test_info: Contains tuberculosis test dates and results for those patients

test_info <- 
  tribble(~name,     ~test_date,    ~result,
          "Alice",   "2023-06-05",  "Negative",
          "Bob",     "2023-08-10",  "Positive",
          "Charlie", "2023-07-15",  "Negative")

test_info

‣ We’d like to analyze these data together, thus we need to combine them. ‣ One option: cbind() function from base R

# Use the cbind function
cbind(demographic, test_info)

‣ Issue: Name column appears twice

‣ What if the rows in the two datasets are not already aligned?

‣ For example: test_info_disordered

test_info_disordered <- 
  tribble(~name,     ~test_date,    ~result,
          "Bob",     "2023-08-10",  "Positive", # Bob in first row
          "Alice",   "2023-06-05",  "Negative",
          "Charlie", "2023-07-15",  "Negative")

test_info_disordered
cbind(demographic, test_info_disordered)

‣ The data is combined incorrectly. Very naive

‣ A third issue: One-to-many relationship

‣ Example: Alice did multiple TB tests

test_info_multiple <- 
  tribble(~name,     ~test_date,    ~result,
          "Alice",   "2023-06-05",  "Negative",
          "Alice",   "2023-06-06",  "Negative",
          "Bob",     "2023-08-10",  "Positive",
          "Charlie", "2023-07-15",  "Negative")

test_info_multiple

cbind() would not work here due to a mismatch in row counts:

cbind(demographic, test_info_multiple)

Vocab Time

One-to-many relationship: One entity in one dataset, multiple matched entities in another dataset. Will consider in future lesson.

‣ We need a smarter way to combine datasets

2.3 Introduction to left_join()

‣ Solves the problems encountered with cbind()

Simple Case: Works when datasets are perfectly matched.

left_join(demographic, test_info)
## Joining with `by = join_by(name)`

‣ Does not duplicate the name column.

Disordered Data: Works even if the datasets are not in the same order

left_join(demographic, test_info_disordered)
## Joining with `by = join_by(name)`

One to Many: Can handle multiple entries for a single entity

left_join(demographic, test_info_multiple)
## Joining with `by = join_by(name)`

Efficiency and Clarity: Simple yet powerful

Piping with left_join()

‣ Using the pipe operator %>% with left_join()

demographic %>% left_join(test_info) # equivalent:
## Joining with `by = join_by(name)`
left_join(demographic, test_info)
## Joining with `by = join_by(name)`

2.4 Joining syntax

‣ Joins operate on two dataframes: x (the left dataframe) and y (the right dataframe).

‣ You can input these dataframes either as named or unnamed arguments:

# Let's try both ways:
left_join(x= demographic, y= test_info) # named
left_join(demographic, test_info) # unnamed

‣ The by argument indicates the key for connecting tables. Sometimes it is not needed:

# these are equivalent
left_join(x = demographic, y = test_info)

left_join(x = demographic, y = test_info, by = "name")

‣ Sometimes, it’s not necessary to supply by; it can be inferred from common columns.

# Here, "name" is the common column:
left_join(x = demographic, y = test_info)

left_join(x = demographic, y = test_info, by = "name")

by is sometimes required: what if keys are named differently in the two datasets?

test_info_different_name <- 
  tribble(~test_recipient,   ~test_date,       ~result, # replace `name` with different word
          "Alice",     "2023-06-05",  "Negative",
          "Bob",       "2023-08-10",  "Positive",
          "Charlie",   "2023-07-15",  "Negative")

test_info_different_name

‣ Attempting to join test_info_different_name with demographic will lead to an error:

left_join(demographic, test_info_different_name, )

Why? No obvious key

‣ Either rename the column or specify columns to join on using by = c().

left_join(demographic, test_info_different_name, 
          by = c("name" = "test_recipient"))

c("name" = "test_recipient") tells R to connect name from data frame x with test_recipient from data frame y.

(NOTE: Answers are at the bottom of the page. Try to answer the questions yourself before checking.)

Left Join Patients and Checkups

Consider the two datasets below, one with patient details and the other with medical check-up dates for these patients.

patients <- tribble(
  ~patient_id, ~name,     ~age,
  1,          "John",      32,
  2,          "Joy",       28,
  3,          "Khan",      40
)

checkups <- tribble(
  ~patient_id, ~checkup_date,
  1,          "2023-01-20",
  2,          "2023-02-20",
  3,          "2023-05-15"
) 

Join the patients dataset with the checkups dataset using left_join()

left_join(patients, checkups)
## Joining with `by = join_by(patient_id)`

Left Join with by Argument

Two datasets are defined below, one with patient details and the other with vaccination records for those patients.

# Patient Details
patient_details <- tribble(
  ~id_number,  ~full_name,   ~address,
  "A001",      "Alice",      "123 Elm St",
  "B002",      "Bob",        "456 Maple Dr",
  "C003",      "Charlie",    "789 Oak Blvd"
)

# Vaccination Records
vaccination_records <- tribble(
  ~patient_code, ~vaccine_type,  ~vaccination_date,
  "A001",        "COVID-19",     "2022-05-10",
  "B002",        "Flu",          "2023-09-01",
  "C003",        "Hepatitis B",  "2021-12-15"
)

Join the patient_details and vaccination_records datasets. You will need to use the by argument because the patient identifier columns have different names.

left_join(patient_details, vaccination_records, by = c("id_number" = "patient_code"))

3 Types of joins

‣ In real-world, datasets do not match perfectly. Won’t always have Alice, Bob and Charlie in both datasets.

‣ Not every row in one dataset has a corresponding row in the other

‣ Different types of joins handle these imperfect matches: left_join(), right_join(), inner_join(), full_join()

3.1 left_join()

left_join() retains all records from the left dataframe

‣ Even if there is no match in the right dataframe

‣ Let’s revisit demographic dataset and a modified test_info dataset

demographic
test_info

‣ Removing Charlie, adding a new patient Xavier to test_info dataset

# Create and display modified test_info dataset
test_info_xavier <- tribble(
  ~name,    ~test_date, ~result,
  "Alice",  "2023-06-05", "Negative",
  "Bob",    "2023-08-10", "Positive",
  "Xavier", "2023-05-02", "Negative")

test_info_xavier

‣ Now, perform a left_join():

# Perform a left join with `demographic` as left dataframe
left_join(x = demographic, y = test_info_xavier)
## Joining with `by = join_by(name)`
# What will happen to Charlie and what will happen to Xavier?

Charlie is retained with NA values for test info

Xavier is discarded

‣ In left_join(x = demographic, y = test_info_xavier), all records from the left dataframe (demographic) are retained.

‣ Visual representation of how left_join() works

‣ What happens when we switch the left and right datasets?

# Perform a left join with test_info_xavier as left dataframe
left_join(test_info_xavier, demographic)
## Joining with `by = join_by(name)`

‣ Now, Xavier’s data is included and Charlie’s data is excluded. left_join() retains all rows from the left dataset, test_info_xavier

Primary Dataset: This is the “main” or “prioritized” dataset in a join. In a left join, the left dataset is the primary dataset.

Left Join Diagnoses and Demographics

Try out the following. Below are two datasets - one with disease diagnoses (disease_dx) and another with patient demographics (patient_demographics).

disease_dx <- tribble(
  ~patient_id, ~disease,       ~date_of_diagnosis,
  1,            "Influenza",    "2023-01-15",
  4,            "COVID-19",     "2023-03-05",
  8,            "Influenza",    "2023-02-20",
)

patient_demographics <- tribble(
  ~patient_id, ~name,      ~age,  ~gender,
  1,            "Fred",      28,  "Female",
  2,            "Genevieve", 45,  "Female",
  3,            "Henry",     32,  "Male",
  5,            "Irene",     55,  "Female",
  8,            "Jules",     40,  "Male"
)

Use left_join() to merge these datasets, keeping only patients for whom we have demographic information. Think carefully about which dataset to put on the left.

left_join(patient_demographics, disease_dx)
## Joining with `by = join_by(patient_id)`

‣ In this example, we’ll analyze TB incidence and government health expenditure in 47 African countries

Data on TB incidence rate per 100,000 people from the World Health Organization (WHO)

tb_2019_africa <- read_csv(here("data/tb_incidence_2019.csv"))
## Rows: 47 Columns: 3
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (2): country, conf_int_95
## dbl (1): cases
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
tb_2019_africa

Data on health expenditure per capita from countries around the world, also from the WHO

health_exp_2019 <- read_csv(here("data/health_expend_per_cap_2019.csv"))
## Rows: 185 Columns: 2
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (1): country
## dbl (1): expend_usd
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
health_exp_2019

‣ Imagine you wanted to see how TB incidence varies with health expenditure in African countries? Which dataset should be on the left?

‣ Use tb_2019_africa as the left dataframe in the join to ensure all African countries are included in the final dataset

tb_health_exp_joined <- tb_2019_africa %>% 
  left_join(health_exp_2019)
## Joining with `by = join_by(country)`
tb_health_exp_joined

‣ 47 rows retained for the 47 African countries.

‣ Next, check for any countries in tb_2019_africa that did not have a match in health_exp_2019

tb_health_exp_joined %>% 
  filter(is.na(expend_usd))

‣ Mauritius, South Sudan, and Comoros did not have expenditure data, but are still in the joined dataset

‣ Confirm these countries are absent from the expenditure dataset

health_exp_2019 %>% 
  filter(country %in% c("Mauritius", "South Sudan", "Comoros"))

‣ Will have to leave these countries out of the analysis.

Left Join TB Cases and Continents

Copy the code below to define two datasets.

The first, tb_cases_children contains the number of TB cases in under 15s in 2012, by country:

tb_cases_children <- tidyr::who %>% 
  filter(year == 2012) %>% 
  transmute(country, tb_cases_smear_0_14 = new_sp_m014 + new_sp_f014)

tb_cases_children

And country_continents, from the {countrycode} package, lists all countries and their corresponding region and continent:

country_continents <- 
  countrycode::codelist %>% 
  select(country.name.en, continent, region)

country_continents

Your goal is to add the continent and region data to the TB cases dataset.

Which dataset should be the left dataframe, x? And which should be the right, y? Once you’ve decided, join the datasets appropriately using left_join().

left_join(country_continents,tb_cases_children, by = c("country.name.en" = "country"))

3.2 right_join()

‣ A right_join() is like a mirror image of a left_join()

‣ Retains all rows from the RIGHT dataset

‣ Example using the demographic and test_info_xavier datasets

# Recall the demographic and test_info_xavier datasets
demographic
test_info_xavier

‣ Now, let’s use right_join() with demographic as the right dataframe

right_join(test_info_xavier,demographic )
## Joining with `by = join_by(name)`

‣ All rows from demographic are kept (Alice, Bob and Charlie)

‣ Only matching records in test_info_xavier are kept

‣ Right join prioritizes the dataset on the right, demographic

‣ The image below illustrates the right_join() process

right_join illustration
right_join illustration

‣ The same final dataframe can be created with either left_join() or right_join()

‣ It depends on the order of the data frames

# Using right_join
right_join(test_info_xavier, demographic)
## Joining with `by = join_by(name)`
# Using left_join
left_join(demographic, test_info_xavier)
## Joining with `by = join_by(name)`

‣ Column order may vary between left_join() and right_join()

‣ Columns can be rearranged, so no need to worry about their order

‣ Data scientists typically favor left_join() over right_join()

‣ Clearer logic and less error-prone. Primary dataset (x) comes FIRST in the function.

‣ No practice question here. Just ignore right_join(). By the time you need it, you’ll know what to do.

‣ Moving on from left_join() and right_join(), let’s explore inner_join() and full_join().

3.3 inner_join()

inner_join keeps rows that are common to both dataframes.

‣ Let’s revisit our example with patients and their COVID test results.

demographic
test_info_xavier

‣ Think about what the result would be using inner_join().

‣ Only Alice and Bob are in both datasets.

inner_join(demographic, test_info_xavier)
## Joining with `by = join_by(name)`

Charlie was only in demographic, Xavier was only in test_info, so they are removed.

‣ The order of datasets in inner_join() does not affect the result.

inner_join(test_info_xavier, demographic)
## Joining with `by = join_by(name)`

Inner Join Pathogens

The following data is on foodborne-outbreaks in the US in 2019, from the CDC. Copy the code below to create two new dataframes:

total_inf <- tribble(
  ~pathogen,         ~total_infections,   
  "Campylobacter",    9751,     
  "Listeria",         136,   
  "Salmonella",       8285,
  "Shigella",         2478,    
)

outcomes <- tribble(
  ~pathogen,        ~n_hosp,    ~n_deaths,
  "Listeria",          128,        30,
  "STEC",              582,        11,
  "Campylobacter",     1938,       42,
  "Yersinia",          200,        5,
)

Which pathogens are common between both datasets? Use an inner_join() to join the dataframes, in order to keep only the pathogens that feature in both datasets.

inner_join(total_inf, outcomes)
## Joining with `by = join_by(pathogen)`

‣ Now, let’s return to our health expenditure and TB incidence rates data.

tb_2019_africa
health_exp_2019

‣ Create a new dataframe, inner_exp_tb, to retain only countries present in both datasets.

inner_exp_tb <- tb_2019_africa %>% 
  inner_join(health_exp_2019)
## Joining with `by = join_by(country)`
inner_exp_tb

inner_join() is a commonly used join, but remember it can exclude a lot of data.

‣ Next, we will explore full_join(), the most inclusive join.

Inner Join One Row

The code chunk below filters the health_exp_2019 dataset to the 70 countries with the highest spending:

highest_exp <- 
  health_exp_2019 %>% 
  arrange(-expend_usd) %>% 
  head(70)

highest_exp

Use an inner_join() to join this highest_exp dataset with the African TB incidence dataset, tb_2019_africa.

If you do this correctly, there will be just one row returned. Why?

highest_exp %>% 
  inner_join(tb_2019_africa)
## Joining with `by = join_by(country)`
  • This is because the remaining 46 countries are not included in the dataset of countries with the highest healthcare expenditures

3.4 full_join()

full_join() retains all records from both datasets.

‣ If there are missing matches between the datasets, the function fills in with NA.

‣ Let’s demonstrate this with the demographic and test_info_xavier datasets.

demographic
test_info_xavier
# `full_join` with `demographic` as the primary dataset.
full_join(demographic, test_info_xavier)
## Joining with `by = join_by(name)`

‣ All rows are kept, preventing information loss.

‣ The order of datasets affects the order of columns, but not the retained information.

full_join(test_info_xavier, demographic)
## Joining with `by = join_by(name)`

‣ Again, all data is retained with missing values filled as NA.

Full join illustration
Full join illustration

PRACTICE TIME !

Full Join Malaria Data

The following dataframes contain global malaria incidence rates per 100’000 people and global death rates per 100’000 people from malaria, from Our World in Data. Copy the code to create two small dataframes:

malaria_inc <- tribble(
  ~year, ~inc_100k,
  2010, 69.485344,
  2011, 66.507935,
  2014, 59.831020,
  2016, 58.704540,
  2017, 59.151703,
)

malaria_deaths <- tribble(
  ~year, ~deaths_100k,
  2011, 12.92,
  2013, 11.00,
  2015, 10.11,
  2016, 9.40,
  2019, 8.95
)

Then, join the above tables using a full_join() in order to retain all information from the two datasets.

full_join(malaria_inc, malaria_deaths)
## Joining with `by = join_by(year)`

‣ Now, let’s revisit the TB dataset and health expenditure dataset.

tb_2019_africa 
health_exp_2019

‣ Create a new dataframe, full_tb_health, using a full_join.

full_tb_health <- tb_2019_africa %>%
 full_join(health_exp_2019)
## Joining with `by = join_by(country)`
full_tb_health

‣ All rows are kept, with NA for missing values.


‣ Venn diagrams of Left, Right, Inner and Full join:

4 Learning Objectives

  • You understand how each of the different dplyr joins work: left, right, inner and full.

  • You’re able to choose the appropriate join for your data

  • You can join simple datasets together using functions from dplyr

Answer Key

Q: Left Join Patients and Checkups

left_join(x=patients, y=checkups)
## Joining with `by = join_by(patient_id)`

Q: Left Join with by Argument

left_join(x=patient_details, y=vaccination_records, by=c("id_number"="patient_code"))

Q: Left Join Diagnoses and Demographics

left_join(x=patient_demographics, y=disease_dx)
## Joining with `by = join_by(patient_id)`

Q: Left Join TB Cases and Continents

left_join(x=tb_cases_children, y=country_continents, by=c(country="country.name.en"))

Q: Inner Join Pathogens

inner_join(total_inf, outcomes)
## Joining with `by = join_by(pathogen)`

Q: Inner Join One Row

inner_join(highest_exp, tb_2019_africa)
## Joining with `by = join_by(country)`

There is only one country in common between the two datasets.

Q: Full Join Malaria Data

full_join(malaria_inc, malaria_deaths)
## Joining with `by = join_by(year)`

Contributors

The following team members contributed to this lesson:

LS0tDQp0aXRsZTogJ0ludHJvIHRvIEpvaW5pbmcgRGF0YXNldHMnDQphdXRob3I6IA0KICAtIG5hbWU6ICJLZW5lIERhdmlkIE53b3N1IiANCiAgLSBuYW1lOiAiQW1hbmRhIE1jS2lubGV5Ig0KICAtIG5hbWU6ICJBeW9kZWxlIE9tb3RvbGEgTGF3YWwiDQpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY29kZV9mb2xkaW5nOiAic2hvdyIgIA0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjc3M6ICFleHByIGhlcmU6OmhlcmUoImdsb2JhbC9zdHlsZS9zdHlsZS5jc3MiKQ0KICAgIGhpZ2hsaWdodDoga2F0ZQ0KZWRpdG9yX29wdGlvbnM6IA0KICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lDQogIG1hcmtkb3duOiANCiAgICB3cmFwOiA3Mg0KLS0tDQoNCmBgYHtyLCBlY2hvID0gRiwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KIyBMb2FkIHBhY2thZ2VzIA0KaWYoIXJlcXVpcmUocGFjbWFuKSkgaW5zdGFsbC5wYWNrYWdlcygicGFjbWFuIikNCnBhY21hbjo6cF9sb2FkKHJsYW5nLCB0aWR5dmVyc2UsIGtuaXRyLCBoZXJlLCByZWFjdGFibGUsIGd0LCBmbGV4dGFibGUpDQoNCiMjIGZ1bmN0aW9ucw0Kc291cmNlKGhlcmU6OmhlcmUoImdsb2JhbC9mdW5jdGlvbnMvbWlzY19mdW5jdGlvbnMuUiIpKQ0KDQojIyBkZWZhdWx0IHJlbmRlcg0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGNsYXNzLnNvdXJjZSA9ICJ0Z2MtY29kZS1ibG9jayIsIHJlbmRlciA9IHJlYWN0YWJsZV81X3Jvd3MpDQoNCmBgYA0KDQojIFByZWx1ZGUNCg0KSm9pbmluZyBkYXRhc2V0cyBpcyBhIGNydWNpYWwgc2tpbGwgd2hlbiB3b3JraW5nIHdpdGggaGVhbHRoLXJlbGF0ZWQgZGF0YSBhcyBpdCBhbGxvd3MgeW91IHRvIGNvbWJpbmUgaW5mb3JtYXRpb24gZnJvbSBtdWx0aXBsZSBzb3VyY2VzLCBsZWFkaW5nIHRvIG1vcmUgY29tcHJlaGVuc2l2ZSBhbmQgaW5zaWdodGZ1bCBhbmFseXNlcy4gSW4gdGhpcyBsZXNzb24sIHlvdSdsbCBsZWFybiBob3cgdG8gdXNlIGRpZmZlcmVudCBqb2luaW5nIHRlY2huaXF1ZXMgdXNpbmcgUidzIGBkcGx5cmAgcGFja2FnZS4gTGV0J3MgZ2V0IHN0YXJ0ZWQhDQoNCg0KIyBMZWFybmluZyBPYmplY3RpdmVzDQoNCi0gICBZb3UgdW5kZXJzdGFuZCBob3cgZWFjaCBvZiB0aGUgZGlmZmVyZW50IGBkcGx5cmAgam9pbnMgd29yazogbGVmdCwgcmlnaHQsIGlubmVyIGFuZCBmdWxsLiAgDQoNCi0gICBZb3UncmUgYWJsZSB0byBjaG9vc2UgdGhlIGFwcHJvcHJpYXRlIGpvaW4gZm9yIHlvdXIgZGF0YQ0KDQotICAgWW91IGNhbiBqb2luIHNpbXBsZSBkYXRhc2V0cyB0b2dldGhlciB1c2luZyBmdW5jdGlvbnMgZnJvbSBgZHBseXJgDQoNCiAgICANCiMjIFBhY2thZ2VzDQoNCuKAoyBQbGVhc2UgKipsb2FkIHRoZSBwYWNrYWdlcyoqIG5lZWRlZCBmb3IgdGhpcyBsZXNzb24NCg0KYGBge3J9DQojIExvYWQgcGFja2FnZXMNCmlmKCFyZXF1aXJlKHBhY21hbikpIGluc3RhbGwucGFja2FnZXMoInBhY21hbiIpDQpwYWNtYW46OnBfbG9hZCh0aWR5dmVyc2UsIGNvdW50cnljb2RlKQ0KYGBgDQoNCiMjIFdoYXQgaXMgYSBqb2luIGFuZCB3aHkgZG8gd2UgbmVlZCBpdD8NCg0K4oCjIFRvIGlsbHVzdHJhdGUgKip0aGUgdXRpbGl0eSBvZiBqb2lucyoqLCBsZXQncyBzdGFydCB3aXRoIGEgdG95IGV4YW1wbGUuDQoNCuKAoyBDb25zaWRlciB0aGUgZm9sbG93aW5nIHR3byBkYXRhc2V0czogYGRlbW9ncmFwaGljYCBhbmQgYHRlc3RfaW5mb2ANCg0K4oCjIGBkZW1vZ3JhcGhpY2A6IENvbnRhaW5zIG5hbWVzIGFuZCBhZ2VzIG9mIHRocmVlIHBhdGllbnRzDQoNCmBgYHtyfQ0KZGVtb2dyYXBoaWMgPC0gDQogIHRyaWJibGUofm5hbWUsICAgICB+YWdlLA0KICAgICAgICAgICJBbGljZSIsICAgIDI1LA0KICAgICAgICAgICJCb2IiLCAgICAgIDMyLA0KICAgICAgICAgICJDaGFybGllIiwgIDQ1KQ0KDQpkZW1vZ3JhcGhpYw0KYGBgDQoNCuKAoyBgdGVzdF9pbmZvYDogQ29udGFpbnMgdHViZXJjdWxvc2lzIHRlc3QgZGF0ZXMgYW5kIHJlc3VsdHMgZm9yIHRob3NlIHBhdGllbnRzDQoNCmBgYHtyfQ0KdGVzdF9pbmZvIDwtIA0KICB0cmliYmxlKH5uYW1lLCAgICAgfnRlc3RfZGF0ZSwgICAgfnJlc3VsdCwNCiAgICAgICAgICAiQWxpY2UiLCAgICIyMDIzLTA2LTA1IiwgICJOZWdhdGl2ZSIsDQogICAgICAgICAgIkJvYiIsICAgICAiMjAyMy0wOC0xMCIsICAiUG9zaXRpdmUiLA0KICAgICAgICAgICJDaGFybGllIiwgIjIwMjMtMDctMTUiLCAgIk5lZ2F0aXZlIikNCg0KdGVzdF9pbmZvDQpgYGANCg0K4oCjIFdlJ2QgbGlrZSB0byAqKmFuYWx5emUgdGhlc2UgZGF0YSB0b2dldGhlcioqLCB0aHVzIHdlIG5lZWQgdG8gY29tYmluZSB0aGVtLg0KIOKAoyBPbmUgb3B0aW9uOiBgY2JpbmQoKWAgZnVuY3Rpb24gZnJvbSBiYXNlIFINCg0KYGBge3J9DQojIFVzZSB0aGUgY2JpbmQgZnVuY3Rpb24NCmNiaW5kKGRlbW9ncmFwaGljLCB0ZXN0X2luZm8pDQpgYGANCg0K4oCjIElzc3VlOiAqKk5hbWUgY29sdW1uIGFwcGVhcnMgdHdpY2UqKg0KDQrigKMgV2hhdCBpZiB0aGUgcm93cyBpbiB0aGUgdHdvIGRhdGFzZXRzIGFyZSAqKm5vdCBhbHJlYWR5IGFsaWduZWQqKj8NCg0K4oCjIEZvciBleGFtcGxlOiBgdGVzdF9pbmZvX2Rpc29yZGVyZWRgDQoNCmBgYHtyfQ0KdGVzdF9pbmZvX2Rpc29yZGVyZWQgPC0gDQogIHRyaWJibGUofm5hbWUsICAgICB+dGVzdF9kYXRlLCAgICB+cmVzdWx0LA0KICAgICAgICAgICJCb2IiLCAgICAgIjIwMjMtMDgtMTAiLCAgIlBvc2l0aXZlIiwgIyBCb2IgaW4gZmlyc3Qgcm93DQogICAgICAgICAgIkFsaWNlIiwgICAiMjAyMy0wNi0wNSIsICAiTmVnYXRpdmUiLA0KICAgICAgICAgICJDaGFybGllIiwgIjIwMjMtMDctMTUiLCAgIk5lZ2F0aXZlIikNCg0KdGVzdF9pbmZvX2Rpc29yZGVyZWQNCg0KY2JpbmQoZGVtb2dyYXBoaWMsIHRlc3RfaW5mb19kaXNvcmRlcmVkKQ0KYGBgDQoNCuKAoyBUaGUgZGF0YSBpcyBjb21iaW5lZCBpbmNvcnJlY3RseS4gVmVyeSBuYWl2ZQ0KDQrigKMgQSB0aGlyZCBpc3N1ZTogKipPbmUtdG8tbWFueSByZWxhdGlvbnNoaXAqKg0KDQrigKMgRXhhbXBsZTogQWxpY2UgZGlkIG11bHRpcGxlIFRCIHRlc3RzDQoNCmBgYHtyfQ0KdGVzdF9pbmZvX211bHRpcGxlIDwtIA0KICB0cmliYmxlKH5uYW1lLCAgICAgfnRlc3RfZGF0ZSwgICAgfnJlc3VsdCwNCiAgICAgICAgICAiQWxpY2UiLCAgICIyMDIzLTA2LTA1IiwgICJOZWdhdGl2ZSIsDQogICAgICAgICAgIkFsaWNlIiwgICAiMjAyMy0wNi0wNiIsICAiTmVnYXRpdmUiLA0KICAgICAgICAgICJCb2IiLCAgICAgIjIwMjMtMDgtMTAiLCAgIlBvc2l0aXZlIiwNCiAgICAgICAgICAiQ2hhcmxpZSIsICIyMDIzLTA3LTE1IiwgICJOZWdhdGl2ZSIpDQoNCnRlc3RfaW5mb19tdWx0aXBsZQ0KYGBgDQoNCuKAoyBgY2JpbmQoKWAgd291bGQgbm90IHdvcmsgaGVyZSBkdWUgdG8gYSBtaXNtYXRjaCBpbiByb3cgY291bnRzOg0KDQpgYGB7ciBldmFsID0gRn0NCmNiaW5kKGRlbW9ncmFwaGljLCB0ZXN0X2luZm9fbXVsdGlwbGUpDQpgYGANCg0KKipWb2NhYiBUaW1lKioNCg0K4oCjICoqT25lLXRvLW1hbnkgcmVsYXRpb25zaGlwKio6IE9uZSBlbnRpdHkgaW4gb25lIGRhdGFzZXQsIG11bHRpcGxlIG1hdGNoZWQgZW50aXRpZXMgaW4gYW5vdGhlciBkYXRhc2V0LiBXaWxsIGNvbnNpZGVyIGluIGZ1dHVyZSBsZXNzb24uDQoNCuKAoyBXZSBuZWVkIGEgc21hcnRlciB3YXkgdG8gY29tYmluZSBkYXRhc2V0cw0KDQojIyBJbnRyb2R1Y3Rpb24gdG8gYGxlZnRfam9pbigpYA0KDQrigKMgU29sdmVzIHRoZSBwcm9ibGVtcyBlbmNvdW50ZXJlZCB3aXRoIGBjYmluZCgpYA0KDQrigKMgKipTaW1wbGUgQ2FzZSoqOiBXb3JrcyB3aGVuIGRhdGFzZXRzIGFyZSBwZXJmZWN0bHkgbWF0Y2hlZC4NCg0KYGBge3J9DQpsZWZ0X2pvaW4oZGVtb2dyYXBoaWMsIHRlc3RfaW5mbykNCmBgYA0KDQrigKMgRG9lcyBub3QgZHVwbGljYXRlIHRoZSBuYW1lIGNvbHVtbi4NCg0K4oCjICoqRGlzb3JkZXJlZCBEYXRhKio6IFdvcmtzIGV2ZW4gaWYgdGhlIGRhdGFzZXRzIGFyZSBub3QgaW4gdGhlIHNhbWUgb3JkZXINCg0KYGBge3J9DQpsZWZ0X2pvaW4oZGVtb2dyYXBoaWMsIHRlc3RfaW5mb19kaXNvcmRlcmVkKQ0KYGBgDQoNCuKAoyAqKk9uZSB0byBNYW55Kio6IENhbiBoYW5kbGUgbXVsdGlwbGUgZW50cmllcyBmb3IgYSBzaW5nbGUgZW50aXR5DQoNCmBgYHtyfQ0KbGVmdF9qb2luKGRlbW9ncmFwaGljLCB0ZXN0X2luZm9fbXVsdGlwbGUpDQpgYGANCg0K4oCjICoqRWZmaWNpZW5jeSBhbmQgQ2xhcml0eSoqOiBTaW1wbGUgeWV0IHBvd2VyZnVsDQoNCioqUGlwaW5nIHdpdGggYGxlZnRfam9pbigpYCoqDQoNCuKAoyBVc2luZyB0aGUgcGlwZSBvcGVyYXRvciBgJT4lYCB3aXRoIGBsZWZ0X2pvaW4oKWANCg0KYGBge3IgcmVzdWx0cyA9ICJoaWRlIn0NCmRlbW9ncmFwaGljICU+JSBsZWZ0X2pvaW4odGVzdF9pbmZvKSAjIGVxdWl2YWxlbnQ6DQpsZWZ0X2pvaW4oZGVtb2dyYXBoaWMsIHRlc3RfaW5mbykNCmBgYA0KDQojIyBKb2luaW5nIHN5bnRheA0KDQrigKMgSm9pbnMgb3BlcmF0ZSBvbiB0d28gZGF0YWZyYW1lczogYHhgICh0aGUgKmxlZnQqIGRhdGFmcmFtZSkgYW5kIGB5YCAodGhlICpyaWdodCogZGF0YWZyYW1lKS4NCg0K4oCjIFlvdSBjYW4gaW5wdXQgdGhlc2UgZGF0YWZyYW1lcyBlaXRoZXIgYXMgKipuYW1lZCoqIG9yICoqdW5uYW1lZCoqIGFyZ3VtZW50czoNCg0KYGBge3IgZXZhbCA9IEZBTFNFfQ0KIyBMZXQncyB0cnkgYm90aCB3YXlzOg0KbGVmdF9qb2luKHg9IGRlbW9ncmFwaGljLCB5PSB0ZXN0X2luZm8pICMgbmFtZWQNCmxlZnRfam9pbihkZW1vZ3JhcGhpYywgdGVzdF9pbmZvKSAjIHVubmFtZWQNCmBgYA0KDQrigKMgVGhlIGBieWAgYXJndW1lbnQgaW5kaWNhdGVzIHRoZSAqKmtleSoqIGZvciBjb25uZWN0aW5nIHRhYmxlcy4gU29tZXRpbWVzIGl0IGlzIG5vdCBuZWVkZWQ6DQoNCmBgYHtyIGV2YWwgPSBGfQ0KIyB0aGVzZSBhcmUgZXF1aXZhbGVudA0KbGVmdF9qb2luKHggPSBkZW1vZ3JhcGhpYywgeSA9IHRlc3RfaW5mbykNCg0KbGVmdF9qb2luKHggPSBkZW1vZ3JhcGhpYywgeSA9IHRlc3RfaW5mbywgYnkgPSAibmFtZSIpDQpgYGANCg0K4oCjIFNvbWV0aW1lcywgaXQncyBub3QgbmVjZXNzYXJ5IHRvIHN1cHBseSBgYnlgOyBpdCBjYW4gYmUgKippbmZlcnJlZCoqIGZyb20gY29tbW9uIGNvbHVtbnMuDQoNCmBgYHtyIGV2YWwgPSBGQUxTRX0NCiMgSGVyZSwgIm5hbWUiIGlzIHRoZSBjb21tb24gY29sdW1uOg0KbGVmdF9qb2luKHggPSBkZW1vZ3JhcGhpYywgeSA9IHRlc3RfaW5mbykNCg0KbGVmdF9qb2luKHggPSBkZW1vZ3JhcGhpYywgeSA9IHRlc3RfaW5mbywgYnkgPSAibmFtZSIpDQpgYGANCg0K4oCjIGBieWAgaXMgc29tZXRpbWVzIHJlcXVpcmVkOiB3aGF0IGlmIGtleXMgYXJlICoqbmFtZWQgZGlmZmVyZW50bHkqKiBpbiB0aGUgdHdvIGRhdGFzZXRzPw0KDQpgYGB7cn0NCnRlc3RfaW5mb19kaWZmZXJlbnRfbmFtZSA8LSANCiAgdHJpYmJsZSh+dGVzdF9yZWNpcGllbnQsICAgfnRlc3RfZGF0ZSwgICAgICAgfnJlc3VsdCwgIyByZXBsYWNlIGBuYW1lYCB3aXRoIGRpZmZlcmVudCB3b3JkDQogICAgICAgICAgIkFsaWNlIiwgICAgICIyMDIzLTA2LTA1IiwgICJOZWdhdGl2ZSIsDQogICAgICAgICAgIkJvYiIsICAgICAgICIyMDIzLTA4LTEwIiwgICJQb3NpdGl2ZSIsDQogICAgICAgICAgIkNoYXJsaWUiLCAgICIyMDIzLTA3LTE1IiwgICJOZWdhdGl2ZSIpDQoNCnRlc3RfaW5mb19kaWZmZXJlbnRfbmFtZQ0KYGBgDQoNCuKAoyBBdHRlbXB0aW5nIHRvIGpvaW4gYHRlc3RfaW5mb19kaWZmZXJlbnRfbmFtZWAgd2l0aCBgZGVtb2dyYXBoaWNgIHdpbGwgbGVhZCB0byBhbiBlcnJvcjoNCg0KYGBge3IgZXZhbCA9IEZBTFNFfQ0KbGVmdF9qb2luKGRlbW9ncmFwaGljLCB0ZXN0X2luZm9fZGlmZmVyZW50X25hbWUsICkNCmBgYA0KDQrigKMgKipXaHk/IE5vIG9idmlvdXMga2V5KioNCg0K4oCjIEVpdGhlciAqKnJlbmFtZSB0aGUgY29sdW1uKiogb3Igc3BlY2lmeSBjb2x1bW5zIHRvIGpvaW4gb24gdXNpbmcgYGJ5ID0gYygpYC4NCg0KYGBge3J9DQpsZWZ0X2pvaW4oZGVtb2dyYXBoaWMsIHRlc3RfaW5mb19kaWZmZXJlbnRfbmFtZSwgDQogICAgICAgICAgYnkgPSBjKCJuYW1lIiA9ICJ0ZXN0X3JlY2lwaWVudCIpKQ0KYGBgDQoNCuKAoyBgYygibmFtZSIgPSAidGVzdF9yZWNpcGllbnQiKWAgdGVsbHMgUiB0byBjb25uZWN0IGBuYW1lYCBmcm9tIGRhdGEgZnJhbWUgeCB3aXRoIGB0ZXN0X3JlY2lwaWVudGAgZnJvbSBkYXRhIGZyYW1lIHkuDQoNCjo6OiBwcmFjdGljZQ0KDQoqKE5PVEU6IEFuc3dlcnMgYXJlIGF0IHRoZSBib3R0b20gb2YgdGhlIHBhZ2UuIFRyeSB0byBhbnN3ZXIgdGhlIHF1ZXN0aW9ucyB5b3Vyc2VsZiBiZWZvcmUgY2hlY2tpbmcuKSoNCg0KKipMZWZ0IEpvaW4gUGF0aWVudHMgYW5kIENoZWNrdXBzKioNCg0KQ29uc2lkZXIgdGhlIHR3byBkYXRhc2V0cyBiZWxvdywgb25lIHdpdGggcGF0aWVudCBkZXRhaWxzIGFuZCB0aGUgb3RoZXIgd2l0aCBtZWRpY2FsIGNoZWNrLXVwIGRhdGVzIGZvciB0aGVzZSBwYXRpZW50cy4NCg0KYGBge3J9DQpwYXRpZW50cyA8LSB0cmliYmxlKA0KICB+cGF0aWVudF9pZCwgfm5hbWUsICAgICB+YWdlLA0KICAxLCAgICAgICAgICAiSm9obiIsICAgICAgMzIsDQogIDIsICAgICAgICAgICJKb3kiLCAgICAgICAyOCwNCiAgMywgICAgICAgICAgIktoYW4iLCAgICAgIDQwDQopDQoNCmNoZWNrdXBzIDwtIHRyaWJibGUoDQogIH5wYXRpZW50X2lkLCB+Y2hlY2t1cF9kYXRlLA0KICAxLCAgICAgICAgICAiMjAyMy0wMS0yMCIsDQogIDIsICAgICAgICAgICIyMDIzLTAyLTIwIiwNCiAgMywgICAgICAgICAgIjIwMjMtMDUtMTUiDQopIA0KYGBgDQoNCkpvaW4gdGhlIGBwYXRpZW50c2AgZGF0YXNldCB3aXRoIHRoZSBgY2hlY2t1cHNgIGRhdGFzZXQgdXNpbmcgYGxlZnRfam9pbigpYA0KDQpgYGB7cn0NCmxlZnRfam9pbihwYXRpZW50cywgY2hlY2t1cHMpDQpgYGANCg0KOjo6DQoNCjo6OiBwcmFjdGljZQ0KKipMZWZ0IEpvaW4gd2l0aCBieSBBcmd1bWVudCoqDQoNClR3byBkYXRhc2V0cyBhcmUgZGVmaW5lZCBiZWxvdywgb25lIHdpdGggcGF0aWVudCBkZXRhaWxzIGFuZCB0aGUgb3RoZXIgd2l0aCB2YWNjaW5hdGlvbiByZWNvcmRzIGZvciB0aG9zZSBwYXRpZW50cy4NCg0KYGBge3J9DQojIFBhdGllbnQgRGV0YWlscw0KcGF0aWVudF9kZXRhaWxzIDwtIHRyaWJibGUoDQogIH5pZF9udW1iZXIsICB+ZnVsbF9uYW1lLCAgIH5hZGRyZXNzLA0KICAiQTAwMSIsICAgICAgIkFsaWNlIiwgICAgICAiMTIzIEVsbSBTdCIsDQogICJCMDAyIiwgICAgICAiQm9iIiwgICAgICAgICI0NTYgTWFwbGUgRHIiLA0KICAiQzAwMyIsICAgICAgIkNoYXJsaWUiLCAgICAiNzg5IE9hayBCbHZkIg0KKQ0KDQojIFZhY2NpbmF0aW9uIFJlY29yZHMNCnZhY2NpbmF0aW9uX3JlY29yZHMgPC0gdHJpYmJsZSgNCiAgfnBhdGllbnRfY29kZSwgfnZhY2NpbmVfdHlwZSwgIH52YWNjaW5hdGlvbl9kYXRlLA0KICAiQTAwMSIsICAgICAgICAiQ09WSUQtMTkiLCAgICAgIjIwMjItMDUtMTAiLA0KICAiQjAwMiIsICAgICAgICAiRmx1IiwgICAgICAgICAgIjIwMjMtMDktMDEiLA0KICAiQzAwMyIsICAgICAgICAiSGVwYXRpdGlzIEIiLCAgIjIwMjEtMTItMTUiDQopDQpgYGANCg0KSm9pbiB0aGUgYHBhdGllbnRfZGV0YWlsc2AgYW5kIGB2YWNjaW5hdGlvbl9yZWNvcmRzYCBkYXRhc2V0cy4gWW91IHdpbGwgbmVlZCB0byB1c2UgdGhlIGBieWAgYXJndW1lbnQgYmVjYXVzZSB0aGUgcGF0aWVudCBpZGVudGlmaWVyIGNvbHVtbnMgaGF2ZSBkaWZmZXJlbnQgbmFtZXMuDQoNCmBgYHtyfQ0KbGVmdF9qb2luKHBhdGllbnRfZGV0YWlscywgdmFjY2luYXRpb25fcmVjb3JkcywgYnkgPSBjKCJpZF9udW1iZXIiID0gInBhdGllbnRfY29kZSIpKQ0KYGBgDQoNCjo6Og0KDQojIFR5cGVzIG9mIGpvaW5zDQoNCuKAoyBJbiByZWFsLXdvcmxkLCBkYXRhc2V0cyBkbyBub3QgbWF0Y2ggcGVyZmVjdGx5LiBXb24ndCBhbHdheXMgaGF2ZSBBbGljZSwgQm9iIGFuZCBDaGFybGllIGluIGJvdGggZGF0YXNldHMuDQoNCuKAoyBOb3QgZXZlcnkgcm93IGluIG9uZSBkYXRhc2V0IGhhcyBhIGNvcnJlc3BvbmRpbmcgcm93IGluIHRoZSBvdGhlcg0KDQrigKMgRGlmZmVyZW50IHR5cGVzIG9mIGpvaW5zIGhhbmRsZSB0aGVzZSBpbXBlcmZlY3QgbWF0Y2hlczogYGxlZnRfam9pbigpYCwgYHJpZ2h0X2pvaW4oKWAsIGBpbm5lcl9qb2luKClgLCBgZnVsbF9qb2luKClgDQoNCiMjIGBsZWZ0X2pvaW4oKWANCg0K4oCjIGBsZWZ0X2pvaW4oKWAgcmV0YWlucyBhbGwgcmVjb3JkcyBmcm9tIHRoZSBsZWZ0IGRhdGFmcmFtZQ0KDQrigKMgRXZlbiBpZiB0aGVyZSBpcyBubyBtYXRjaCBpbiB0aGUgcmlnaHQgZGF0YWZyYW1lDQoNCuKAoyBMZXQncyByZXZpc2l0IGBkZW1vZ3JhcGhpY2AgZGF0YXNldCBhbmQgYSBtb2RpZmllZCBgdGVzdF9pbmZvYCBkYXRhc2V0DQoNCmBgYHtyfQ0KZGVtb2dyYXBoaWMNCnRlc3RfaW5mbw0KYGBgDQoNCuKAoyBSZW1vdmluZyBgQ2hhcmxpZWAsIGFkZGluZyBhIG5ldyBwYXRpZW50IGBYYXZpZXJgIHRvIGB0ZXN0X2luZm9gIGRhdGFzZXQNCg0KYGBge3J9DQojIENyZWF0ZSBhbmQgZGlzcGxheSBtb2RpZmllZCB0ZXN0X2luZm8gZGF0YXNldA0KdGVzdF9pbmZvX3hhdmllciA8LSB0cmliYmxlKA0KICB+bmFtZSwgICAgfnRlc3RfZGF0ZSwgfnJlc3VsdCwNCiAgIkFsaWNlIiwgICIyMDIzLTA2LTA1IiwgIk5lZ2F0aXZlIiwNCiAgIkJvYiIsICAgICIyMDIzLTA4LTEwIiwgIlBvc2l0aXZlIiwNCiAgIlhhdmllciIsICIyMDIzLTA1LTAyIiwgIk5lZ2F0aXZlIikNCg0KdGVzdF9pbmZvX3hhdmllcg0KYGBgDQoNCuKAoyBOb3csIHBlcmZvcm0gYSBgbGVmdF9qb2luKClgOg0KDQpgYGB7cn0NCiMgUGVyZm9ybSBhIGxlZnQgam9pbiB3aXRoIGBkZW1vZ3JhcGhpY2AgYXMgbGVmdCBkYXRhZnJhbWUNCmxlZnRfam9pbih4ID0gZGVtb2dyYXBoaWMsIHkgPSB0ZXN0X2luZm9feGF2aWVyKQ0KIyBXaGF0IHdpbGwgaGFwcGVuIHRvIENoYXJsaWUgYW5kIHdoYXQgd2lsbCBoYXBwZW4gdG8gWGF2aWVyPw0KYGBgDQoNCuKAoyBgQ2hhcmxpZWAgaXMgcmV0YWluZWQgd2l0aCBgTkFgIHZhbHVlcyBmb3IgdGVzdCBpbmZvDQoNCuKAoyBgWGF2aWVyYCBpcyBkaXNjYXJkZWQNCg0K4oCjIEluIGBsZWZ0X2pvaW4oeCA9IGRlbW9ncmFwaGljLCB5ID0gdGVzdF9pbmZvX3hhdmllcilgLCBhbGwgcmVjb3JkcyBmcm9tIHRoZSAqKmxlZnQqKiBkYXRhZnJhbWUgKGBkZW1vZ3JhcGhpY2ApIGFyZSByZXRhaW5lZC4NCg0K4oCjIFZpc3VhbCByZXByZXNlbnRhdGlvbiBvZiBob3cgYGxlZnRfam9pbigpYCB3b3Jrcw0KIA0KIVtdKGltYWdlcy9sZWZ0X2pvaW4xLmdpZikNCg0K4oCjIFdoYXQgaGFwcGVucyB3aGVuIHdlIHN3aXRjaCB0aGUgbGVmdCBhbmQgcmlnaHQgZGF0YXNldHM/DQoNCmBgYHtyIHJlbmRlcj1yZWFjdGFibGVfNV9yb3dzfQ0KIyBQZXJmb3JtIGEgbGVmdCBqb2luIHdpdGggdGVzdF9pbmZvX3hhdmllciBhcyBsZWZ0IGRhdGFmcmFtZQ0KbGVmdF9qb2luKHRlc3RfaW5mb194YXZpZXIsIGRlbW9ncmFwaGljKQ0KYGBgDQoNCuKAoyBOb3csIFhhdmllcidzIGRhdGEgaXMgaW5jbHVkZWQgYW5kIENoYXJsaWUncyBkYXRhIGlzIGV4Y2x1ZGVkLiBgbGVmdF9qb2luKClgIHJldGFpbnMgYWxsIHJvd3MgZnJvbSB0aGUgbGVmdCBkYXRhc2V0LCBgdGVzdF9pbmZvX3hhdmllcmANCg0KOjo6IHZvY2FiDQoqKlByaW1hcnkgRGF0YXNldCoqOiBUaGlzIGlzIHRoZSAibWFpbiIgb3IgInByaW9yaXRpemVkIiBkYXRhc2V0IGluIGEgam9pbi4gSW4gYSBsZWZ0IGpvaW4sIHRoZSBsZWZ0IGRhdGFzZXQgaXMgdGhlIHByaW1hcnkgZGF0YXNldC4NCjo6Og0KDQo6OjogcHJhY3RpY2UNCioqTGVmdCBKb2luIERpYWdub3NlcyBhbmQgRGVtb2dyYXBoaWNzKioNCg0KVHJ5IG91dCB0aGUgZm9sbG93aW5nLiBCZWxvdyBhcmUgdHdvIGRhdGFzZXRzIC0gb25lIHdpdGggZGlzZWFzZSBkaWFnbm9zZXMgKGBkaXNlYXNlX2R4YCkgYW5kIGFub3RoZXIgd2l0aCBwYXRpZW50IGRlbW9ncmFwaGljcyAoYHBhdGllbnRfZGVtb2dyYXBoaWNzYCkuDQoNCmBgYHtyfQ0KZGlzZWFzZV9keCA8LSB0cmliYmxlKA0KICB+cGF0aWVudF9pZCwgfmRpc2Vhc2UsICAgICAgIH5kYXRlX29mX2RpYWdub3NpcywNCiAgMSwgICAgICAgICAgICAiSW5mbHVlbnphIiwgICAgIjIwMjMtMDEtMTUiLA0KICA0LCAgICAgICAgICAgICJDT1ZJRC0xOSIsICAgICAiMjAyMy0wMy0wNSIsDQogIDgsICAgICAgICAgICAgIkluZmx1ZW56YSIsICAgICIyMDIzLTAyLTIwIiwNCikNCg0KcGF0aWVudF9kZW1vZ3JhcGhpY3MgPC0gdHJpYmJsZSgNCiAgfnBhdGllbnRfaWQsIH5uYW1lLCAgICAgIH5hZ2UsICB+Z2VuZGVyLA0KICAxLCAgICAgICAgICAgICJGcmVkIiwgICAgICAyOCwgICJGZW1hbGUiLA0KICAyLCAgICAgICAgICAgICJHZW5ldmlldmUiLCA0NSwgICJGZW1hbGUiLA0KICAzLCAgICAgICAgICAgICJIZW5yeSIsICAgICAzMiwgICJNYWxlIiwNCiAgNSwgICAgICAgICAgICAiSXJlbmUiLCAgICAgNTUsICAiRmVtYWxlIiwNCiAgOCwgICAgICAgICAgICAiSnVsZXMiLCAgICAgNDAsICAiTWFsZSINCikNCmBgYA0KDQpVc2UgYGxlZnRfam9pbigpYCB0byBtZXJnZSB0aGVzZSBkYXRhc2V0cywga2VlcGluZyBvbmx5IHBhdGllbnRzIGZvciB3aG9tIHdlIGhhdmUgZGVtb2dyYXBoaWMgaW5mb3JtYXRpb24uIFRoaW5rIGNhcmVmdWxseSBhYm91dCB3aGljaCBkYXRhc2V0IHRvIHB1dCBvbiB0aGUgbGVmdC4NCg0KYGBge3J9DQpsZWZ0X2pvaW4ocGF0aWVudF9kZW1vZ3JhcGhpY3MsIGRpc2Vhc2VfZHgpDQpgYGANCg0KOjo6DQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQrigKMgSW4gdGhpcyBleGFtcGxlLCB3ZSdsbCBhbmFseXplIFRCIGluY2lkZW5jZSBhbmQgZ292ZXJubWVudCBoZWFsdGggZXhwZW5kaXR1cmUgaW4gNDcgQWZyaWNhbiBjb3VudHJpZXMNCg0K4oCjICoqRGF0YSBvbiBUQiBpbmNpZGVuY2UgcmF0ZSoqIHBlciAxMDAsMDAwIHBlb3BsZSBmcm9tIHRoZSBbV29ybGQgSGVhbHRoIE9yZ2FuaXphdGlvbiAoV0hPKV0oaHR0cHM6Ly93d3cud2hvLmludC9kYXRhL2doby9kYXRhL2luZGljYXRvcnMvaW5kaWNhdG9yLWRldGFpbHMvR0hPL2luY2lkZW5jZS1vZi10dWJlcmN1bG9zaXMtKHBlci0xMDAtMDAwLXBvcHVsYXRpb24tcGVyLXllYXIpKQ0KDQpgYGB7cn0NCnRiXzIwMTlfYWZyaWNhIDwtIHJlYWRfY3N2KGhlcmUoImRhdGEvdGJfaW5jaWRlbmNlXzIwMTkuY3N2IikpDQoNCnRiXzIwMTlfYWZyaWNhDQpgYGANCg0K4oCjICoqRGF0YSBvbiBoZWFsdGggZXhwZW5kaXR1cmUgcGVyIGNhcGl0YSoqIGZyb20gY291bnRyaWVzIGFyb3VuZCB0aGUgd29ybGQsIGFsc28gZnJvbSB0aGUgV0hPDQoNCmBgYHtyfQ0KaGVhbHRoX2V4cF8yMDE5IDwtIHJlYWRfY3N2KGhlcmUoImRhdGEvaGVhbHRoX2V4cGVuZF9wZXJfY2FwXzIwMTkuY3N2IikpDQpoZWFsdGhfZXhwXzIwMTkNCmBgYA0KDQrigKMgSW1hZ2luZSB5b3Ugd2FudGVkIHRvIHNlZSBob3cgVEIgaW5jaWRlbmNlIHZhcmllcyB3aXRoIGhlYWx0aCBleHBlbmRpdHVyZSBpbiBBZnJpY2FuIGNvdW50cmllcz8gV2hpY2ggZGF0YXNldCBzaG91bGQgYmUgb24gdGhlIGxlZnQ/DQoNCuKAoyBVc2UgYHRiXzIwMTlfYWZyaWNhYCBhcyB0aGUgKipsZWZ0IGRhdGFmcmFtZSoqIGluIHRoZSBqb2luIHRvIGVuc3VyZSBhbGwgQWZyaWNhbiBjb3VudHJpZXMgYXJlIGluY2x1ZGVkIGluIHRoZSBmaW5hbCBkYXRhc2V0DQoNCmBgYHtyfQ0KdGJfaGVhbHRoX2V4cF9qb2luZWQgPC0gdGJfMjAxOV9hZnJpY2EgJT4lIA0KICBsZWZ0X2pvaW4oaGVhbHRoX2V4cF8yMDE5KQ0KDQp0Yl9oZWFsdGhfZXhwX2pvaW5lZA0KYGBgDQoNCuKAoyA0NyByb3dzIHJldGFpbmVkIGZvciB0aGUgNDcgQWZyaWNhbiBjb3VudHJpZXMuDQoNCuKAoyBOZXh0LCBjaGVjayBmb3IgYW55IGNvdW50cmllcyBpbiBgdGJfMjAxOV9hZnJpY2FgIHRoYXQgZGlkIG5vdCBoYXZlIGEgbWF0Y2ggaW4gYGhlYWx0aF9leHBfMjAxOWANCg0KYGBge3J9DQp0Yl9oZWFsdGhfZXhwX2pvaW5lZCAlPiUgDQogIGZpbHRlcihpcy5uYShleHBlbmRfdXNkKSkNCmBgYA0KDQrigKMgTWF1cml0aXVzLCBTb3V0aCBTdWRhbiwgYW5kIENvbW9yb3MgZGlkIG5vdCBoYXZlIGV4cGVuZGl0dXJlIGRhdGEsIGJ1dCBhcmUgc3RpbGwgaW4gdGhlIGpvaW5lZCBkYXRhc2V0DQoNCuKAoyBDb25maXJtIHRoZXNlIGNvdW50cmllcyBhcmUgYWJzZW50IGZyb20gdGhlIGV4cGVuZGl0dXJlIGRhdGFzZXQNCg0KYGBge3J9DQpoZWFsdGhfZXhwXzIwMTkgJT4lIA0KICBmaWx0ZXIoY291bnRyeSAlaW4lIGMoIk1hdXJpdGl1cyIsICJTb3V0aCBTdWRhbiIsICJDb21vcm9zIikpDQpgYGANCg0K4oCjIFdpbGwgaGF2ZSB0byBsZWF2ZSB0aGVzZSBjb3VudHJpZXMgb3V0IG9mIHRoZSBhbmFseXNpcy4NCg0KOjo6IHByYWN0aWNlDQoqKkxlZnQgSm9pbiBUQiBDYXNlcyBhbmQgQ29udGluZW50cyoqDQoNCkNvcHkgdGhlIGNvZGUgYmVsb3cgdG8gZGVmaW5lIHR3byBkYXRhc2V0cy4NCg0KVGhlIGZpcnN0LCBgdGJfY2FzZXNfY2hpbGRyZW5gIGNvbnRhaW5zIHRoZSBudW1iZXIgb2YgVEIgY2FzZXMgaW4gdW5kZXIgMTVzIGluIDIwMTIsIGJ5IGNvdW50cnk6DQoNCmBgYHtyfQ0KdGJfY2FzZXNfY2hpbGRyZW4gPC0gdGlkeXI6OndobyAlPiUgDQogIGZpbHRlcih5ZWFyID09IDIwMTIpICU+JSANCiAgdHJhbnNtdXRlKGNvdW50cnksIHRiX2Nhc2VzX3NtZWFyXzBfMTQgPSBuZXdfc3BfbTAxNCArIG5ld19zcF9mMDE0KQ0KDQp0Yl9jYXNlc19jaGlsZHJlbg0KYGBgDQogDQpBbmQgYGNvdW50cnlfY29udGluZW50c2AsIGZyb20gdGhlIHtjb3VudHJ5Y29kZX0gcGFja2FnZSwgbGlzdHMgYWxsIGNvdW50cmllcyBhbmQgdGhlaXIgY29ycmVzcG9uZGluZyByZWdpb24gYW5kIGNvbnRpbmVudDoNCg0KYGBge3J9DQpjb3VudHJ5X2NvbnRpbmVudHMgPC0gDQogIGNvdW50cnljb2RlOjpjb2RlbGlzdCAlPiUgDQogIHNlbGVjdChjb3VudHJ5Lm5hbWUuZW4sIGNvbnRpbmVudCwgcmVnaW9uKQ0KDQpjb3VudHJ5X2NvbnRpbmVudHMNCmBgYA0KDQpZb3VyIGdvYWwgaXMgdG8gYWRkIHRoZSBjb250aW5lbnQgYW5kIHJlZ2lvbiBkYXRhIHRvIHRoZSBUQiBjYXNlcyBkYXRhc2V0Lg0KDQpXaGljaCBkYXRhc2V0IHNob3VsZCBiZSB0aGUgbGVmdCBkYXRhZnJhbWUsIGB4YD8gQW5kIHdoaWNoIHNob3VsZCBiZSB0aGUgcmlnaHQsIGB5YD8gT25jZSB5b3UndmUgZGVjaWRlZCwgam9pbiB0aGUgZGF0YXNldHMgYXBwcm9wcmlhdGVseSB1c2luZyBgbGVmdF9qb2luKClgLg0KDQpgYGB7cn0NCmxlZnRfam9pbihjb3VudHJ5X2NvbnRpbmVudHMsdGJfY2FzZXNfY2hpbGRyZW4sIGJ5ID0gYygiY291bnRyeS5uYW1lLmVuIiA9ICJjb3VudHJ5IikpDQpgYGANCg0KOjo6DQoNCiMjIGByaWdodF9qb2luKClgDQoNCuKAoyBBIGByaWdodF9qb2luKClgIGlzIGxpa2UgYSBtaXJyb3IgaW1hZ2Ugb2YgYSBgbGVmdF9qb2luKClgDQoNCuKAoyBSZXRhaW5zIGFsbCByb3dzIGZyb20gdGhlICoqUklHSFQgZGF0YXNldCoqDQoNCuKAoyBFeGFtcGxlIHVzaW5nIHRoZSBgZGVtb2dyYXBoaWNgIGFuZCBgdGVzdF9pbmZvX3hhdmllcmAgZGF0YXNldHMNCg0KYGBge3J9DQojIFJlY2FsbCB0aGUgZGVtb2dyYXBoaWMgYW5kIHRlc3RfaW5mb194YXZpZXIgZGF0YXNldHMNCmRlbW9ncmFwaGljDQp0ZXN0X2luZm9feGF2aWVyDQpgYGANCg0K4oCjIE5vdywgbGV0J3MgdXNlIGByaWdodF9qb2luKClgIHdpdGggYGRlbW9ncmFwaGljYCBhcyB0aGUgcmlnaHQgZGF0YWZyYW1lDQoNCmBgYHtyfQ0KcmlnaHRfam9pbih0ZXN0X2luZm9feGF2aWVyLGRlbW9ncmFwaGljICkNCmBgYA0KDQrigKMgQWxsIHJvd3MgZnJvbSBgZGVtb2dyYXBoaWNgIGFyZSBrZXB0IChBbGljZSwgQm9iIGFuZCBDaGFybGllKQ0KDQrigKMgT25seSBtYXRjaGluZyByZWNvcmRzIGluIGB0ZXN0X2luZm9feGF2aWVyYCBhcmUga2VwdA0KDQrigKMgUmlnaHQgam9pbiBwcmlvcml0aXplcyB0aGUgZGF0YXNldCBvbiB0aGUgcmlnaHQsIGBkZW1vZ3JhcGhpY2ANCg0K4oCjIFRoZSBpbWFnZSBiZWxvdyBpbGx1c3RyYXRlcyB0aGUgYHJpZ2h0X2pvaW4oKWAgcHJvY2Vzcw0KDQohW3JpZ2h0X2pvaW4gaWxsdXN0cmF0aW9uXShpbWFnZXMvcmlnaHRfam9pbi5naWYpDQoNCuKAoyBUaGUgc2FtZSBmaW5hbCBkYXRhZnJhbWUgY2FuIGJlIGNyZWF0ZWQgd2l0aCBlaXRoZXIgYGxlZnRfam9pbigpYCBvciBgcmlnaHRfam9pbigpYA0KDQrigKMgSXQgZGVwZW5kcyBvbiB0aGUgb3JkZXIgb2YgdGhlIGRhdGEgZnJhbWVzDQoNCmBgYHtyfQ0KIyBVc2luZyByaWdodF9qb2luDQpyaWdodF9qb2luKHRlc3RfaW5mb194YXZpZXIsIGRlbW9ncmFwaGljKQ0KDQojIFVzaW5nIGxlZnRfam9pbg0KbGVmdF9qb2luKGRlbW9ncmFwaGljLCB0ZXN0X2luZm9feGF2aWVyKQ0KYGBgDQoNCjo6OiBzaWRlLW5vdGUNCuKAoyBDb2x1bW4gb3JkZXIgbWF5IHZhcnkgYmV0d2VlbiBgbGVmdF9qb2luKClgIGFuZCBgcmlnaHRfam9pbigpYA0KDQrigKMgQ29sdW1ucyBjYW4gYmUgcmVhcnJhbmdlZCwgc28gbm8gbmVlZCB0byB3b3JyeSBhYm91dCB0aGVpciBvcmRlcg0KOjo6DQoNCuKAoyBEYXRhIHNjaWVudGlzdHMgdHlwaWNhbGx5IGZhdm9yIGBsZWZ0X2pvaW4oKWAgb3ZlciBgcmlnaHRfam9pbigpYA0KDQrigKMgQ2xlYXJlciBsb2dpYyBhbmQgbGVzcyBlcnJvci1wcm9uZS4gUHJpbWFyeSBkYXRhc2V0IChgeGApIGNvbWVzIEZJUlNUIGluIHRoZSBmdW5jdGlvbi4NCg0K4oCjIE5vIHByYWN0aWNlIHF1ZXN0aW9uIGhlcmUuIEp1c3QgaWdub3JlIGByaWdodF9qb2luKClgLiBCeSB0aGUgdGltZSB5b3UgbmVlZCBpdCwgeW91J2xsIGtub3cgd2hhdCB0byBkby4NCg0K4oCjIE1vdmluZyBvbiBmcm9tIGBsZWZ0X2pvaW4oKWAgYW5kIGByaWdodF9qb2luKClgLCBsZXQncyBleHBsb3JlIGBpbm5lcl9qb2luKClgIGFuZCBgZnVsbF9qb2luKClgLg0KDQojIyBgaW5uZXJfam9pbigpYA0KDQrigKMgYGlubmVyX2pvaW5gIGtlZXBzIHJvd3MgdGhhdCBhcmUgKipjb21tb24gdG8gYm90aCoqIGRhdGFmcmFtZXMuDQoNCuKAoyBMZXQncyByZXZpc2l0IG91ciBleGFtcGxlIHdpdGggcGF0aWVudHMgYW5kIHRoZWlyIENPVklEIHRlc3QgcmVzdWx0cy4NCg0KYGBge3J9DQpkZW1vZ3JhcGhpYw0KdGVzdF9pbmZvX3hhdmllcg0KYGBgDQoNCuKAoyBUaGluayBhYm91dCB3aGF0IHRoZSByZXN1bHQgd291bGQgYmUgdXNpbmcgYGlubmVyX2pvaW4oKWAuDQoNCuKAoyBPbmx5IGBBbGljZWAgYW5kIGBCb2JgIGFyZSBpbiAqYm90aCogZGF0YXNldHMuDQoNCmBgYHtyfQ0KaW5uZXJfam9pbihkZW1vZ3JhcGhpYywgdGVzdF9pbmZvX3hhdmllcikNCmBgYA0KDQrigKMgYENoYXJsaWVgIHdhcyBvbmx5IGluIGBkZW1vZ3JhcGhpY2AsIGBYYXZpZXJgIHdhcyBvbmx5IGluIGB0ZXN0X2luZm9gLCBzbyB0aGV5IGFyZSByZW1vdmVkLg0KDQrigKMgVGhlIG9yZGVyIG9mIGRhdGFzZXRzIGluIGBpbm5lcl9qb2luKClgIGRvZXMgKm5vdCogYWZmZWN0IHRoZSByZXN1bHQuDQoNCmBgYHtyfQ0KaW5uZXJfam9pbih0ZXN0X2luZm9feGF2aWVyLCBkZW1vZ3JhcGhpYykNCmBgYA0KDQo6OjogcHJhY3RpY2UNCioqSW5uZXIgSm9pbiBQYXRob2dlbnMqKg0KDQpUaGUgZm9sbG93aW5nIGRhdGEgaXMgb24gZm9vZGJvcm5lLW91dGJyZWFrcyBpbiB0aGUgVVMgaW4gMjAxOSwgZnJvbSB0aGUgW0NEQ10oaHR0cHM6Ly93d3duLmNkYy5nb3YvRm9vZE5ldEZhc3QvUGF0aG9nZW5TdXJ2ZWlsbGFuY2UvQW5udWFsU3VtbWFyeSkuIENvcHkgdGhlIGNvZGUgYmVsb3cgdG8gY3JlYXRlIHR3byBuZXcgZGF0YWZyYW1lczoNCg0KYGBge3J9DQp0b3RhbF9pbmYgPC0gdHJpYmJsZSgNCiAgfnBhdGhvZ2VuLCAgICAgICAgIH50b3RhbF9pbmZlY3Rpb25zLCAgIA0KICAiQ2FtcHlsb2JhY3RlciIsICAgIDk3NTEsICAgICANCiAgIkxpc3RlcmlhIiwgICAgICAgICAxMzYsICAgDQogICJTYWxtb25lbGxhIiwgICAgICAgODI4NSwNCiAgIlNoaWdlbGxhIiwgICAgICAgICAyNDc4LCAgICANCikNCg0Kb3V0Y29tZXMgPC0gdHJpYmJsZSgNCiAgfnBhdGhvZ2VuLCAgICAgICAgfm5faG9zcCwgICAgfm5fZGVhdGhzLA0KICAiTGlzdGVyaWEiLCAgICAgICAgICAxMjgsICAgICAgICAzMCwNCiAgIlNURUMiLCAgICAgICAgICAgICAgNTgyLCAgICAgICAgMTEsDQogICJDYW1weWxvYmFjdGVyIiwgICAgIDE5MzgsICAgICAgIDQyLA0KICAiWWVyc2luaWEiLCAgICAgICAgICAyMDAsICAgICAgICA1LA0KKQ0KYGBgDQoNCldoaWNoIHBhdGhvZ2VucyBhcmUgY29tbW9uIGJldHdlZW4gYm90aCBkYXRhc2V0cz8gVXNlIGFuIGBpbm5lcl9qb2luKClgIHRvIGpvaW4gdGhlIGRhdGFmcmFtZXMsIGluIG9yZGVyIHRvIGtlZXAgb25seSB0aGUgcGF0aG9nZW5zIHRoYXQgZmVhdHVyZSBpbiBib3RoIGRhdGFzZXRzLg0KDQpgYGB7cn0NCmlubmVyX2pvaW4odG90YWxfaW5mLCBvdXRjb21lcykNCmBgYA0KDQo6OjoNCg0K4oCjIE5vdywgbGV0J3MgcmV0dXJuIHRvIG91ciBoZWFsdGggZXhwZW5kaXR1cmUgYW5kIFRCIGluY2lkZW5jZSByYXRlcyBkYXRhLg0KDQpgYGB7cn0NCnRiXzIwMTlfYWZyaWNhDQpoZWFsdGhfZXhwXzIwMTkNCmBgYA0KDQrigKMgQ3JlYXRlIGEgbmV3IGRhdGFmcmFtZSwgYGlubmVyX2V4cF90YmAsIHRvIHJldGFpbiBvbmx5IGNvdW50cmllcyBwcmVzZW50IGluIGJvdGggZGF0YXNldHMuDQoNCmBgYHtyfQ0KaW5uZXJfZXhwX3RiIDwtIHRiXzIwMTlfYWZyaWNhICU+JSANCiAgaW5uZXJfam9pbihoZWFsdGhfZXhwXzIwMTkpDQoNCmlubmVyX2V4cF90Yg0KYGBgDQoNCuKAoyBgaW5uZXJfam9pbigpYCBpcyBhIGNvbW1vbmx5IHVzZWQgam9pbiwgYnV0IHJlbWVtYmVyIGl0IGNhbiBleGNsdWRlIGEgbG90IG9mIGRhdGEuDQoNCuKAoyBOZXh0LCB3ZSB3aWxsIGV4cGxvcmUgYGZ1bGxfam9pbigpYCwgdGhlIG1vc3QgaW5jbHVzaXZlIGpvaW4uDQoNCjo6OiByLXByYWN0aWNlDQoqKklubmVyIEpvaW4gT25lIFJvdyoqDQoNClRoZSBjb2RlIGNodW5rIGJlbG93IGZpbHRlcnMgdGhlIGBoZWFsdGhfZXhwXzIwMTlgIGRhdGFzZXQgdG8gdGhlIDcwIGNvdW50cmllcyB3aXRoIHRoZSBoaWdoZXN0IHNwZW5kaW5nOg0KDQpgYGB7cn0NCmhpZ2hlc3RfZXhwIDwtIA0KICBoZWFsdGhfZXhwXzIwMTkgJT4lIA0KICBhcnJhbmdlKC1leHBlbmRfdXNkKSAlPiUgDQogIGhlYWQoNzApDQoNCmhpZ2hlc3RfZXhwDQpgYGANCg0KVXNlIGFuIGBpbm5lcl9qb2luKClgIHRvIGpvaW4gdGhpcyBgaGlnaGVzdF9leHBgIGRhdGFzZXQgd2l0aCB0aGUgQWZyaWNhbiBUQiBpbmNpZGVuY2UgZGF0YXNldCwgYHRiXzIwMTlfYWZyaWNhYC4NCg0KSWYgeW91IGRvIHRoaXMgY29ycmVjdGx5LCB0aGVyZSB3aWxsIGJlIGp1c3Qgb25lIHJvdyByZXR1cm5lZC4gV2h5Pw0KDQpgYGB7cn0NCmhpZ2hlc3RfZXhwICU+JSANCiAgaW5uZXJfam9pbih0Yl8yMDE5X2FmcmljYSkNCmBgYA0KLSAqVGhpcyBpcyBiZWNhdXNlIHRoZSByZW1haW5pbmcgNDYgY291bnRyaWVzIGFyZSBub3QgaW5jbHVkZWQgaW4gdGhlIGRhdGFzZXQgb2YgY291bnRyaWVzIHdpdGggdGhlIGhpZ2hlc3QgaGVhbHRoY2FyZSBleHBlbmRpdHVyZXMqDQo6OjoNCg0KIyMgYGZ1bGxfam9pbigpYA0KDQrigKMgYGZ1bGxfam9pbigpYCByZXRhaW5zIGFsbCByZWNvcmRzIGZyb20gYm90aCBkYXRhc2V0cy4NCg0K4oCjIElmIHRoZXJlIGFyZSBtaXNzaW5nIG1hdGNoZXMgYmV0d2VlbiB0aGUgZGF0YXNldHMsIHRoZSBmdW5jdGlvbiBmaWxscyBpbiB3aXRoIGBOQWAuDQoNCuKAoyBMZXQncyBkZW1vbnN0cmF0ZSB0aGlzIHdpdGggdGhlIGBkZW1vZ3JhcGhpY2AgYW5kIGB0ZXN0X2luZm9feGF2aWVyYCBkYXRhc2V0cy4NCg0KYGBge3J9DQpkZW1vZ3JhcGhpYw0KdGVzdF9pbmZvX3hhdmllcg0KYGBgDQoNCmBgYHtyfQ0KIyBgZnVsbF9qb2luYCB3aXRoIGBkZW1vZ3JhcGhpY2AgYXMgdGhlIHByaW1hcnkgZGF0YXNldC4NCmZ1bGxfam9pbihkZW1vZ3JhcGhpYywgdGVzdF9pbmZvX3hhdmllcikNCmBgYA0KDQrigKMgQWxsIHJvd3MgYXJlIGtlcHQsIHByZXZlbnRpbmcgaW5mb3JtYXRpb24gbG9zcy4NCg0K4oCjIFRoZSBvcmRlciBvZiBkYXRhc2V0cyBhZmZlY3RzIHRoZSBvcmRlciBvZiBjb2x1bW5zLCBidXQgbm90IHRoZSByZXRhaW5lZCBpbmZvcm1hdGlvbi4NCg0KYGBge3J9DQpmdWxsX2pvaW4odGVzdF9pbmZvX3hhdmllciwgZGVtb2dyYXBoaWMpDQpgYGANCg0K4oCjIEFnYWluLCBhbGwgZGF0YSBpcyByZXRhaW5lZCB3aXRoIG1pc3NpbmcgdmFsdWVzIGZpbGxlZCBhcyBgTkFgLg0KDQohW0Z1bGwgam9pbiBpbGx1c3RyYXRpb25dKGltYWdlcy9mdWxsX2pvaW4uZ2lmKXt3aWR0aD0iNjk0In0NCg0KKipQUkFDVElDRSBUSU1FICEqKg0KDQo6OjogcHJhY3RpY2UNCioqRnVsbCBKb2luIE1hbGFyaWEgRGF0YSoqDQoNClRoZSBmb2xsb3dpbmcgZGF0YWZyYW1lcyBjb250YWluIGdsb2JhbCBtYWxhcmlhIGluY2lkZW5jZSByYXRlcyBwZXIgMTAwJzAwMCBwZW9wbGUgYW5kIGdsb2JhbCBkZWF0aCByYXRlcyBwZXIgMTAwJzAwMCBwZW9wbGUgZnJvbSBtYWxhcmlhLCBmcm9tIFtPdXIgV29ybGQgaW4gRGF0YV0oaHR0cHM6Ly9vdXJ3b3JsZGluZGF0YS5vcmcvbWFsYXJpYSkuIENvcHkgdGhlIGNvZGUgdG8gY3JlYXRlIHR3byBzbWFsbCBkYXRhZnJhbWVzOg0KDQpgYGB7cn0NCm1hbGFyaWFfaW5jIDwtIHRyaWJibGUoDQogIH55ZWFyLCB+aW5jXzEwMGssDQogIDIwMTAsIDY5LjQ4NTM0NCwNCiAgMjAxMSwgNjYuNTA3OTM1LA0KICAyMDE0LCA1OS44MzEwMjAsDQogIDIwMTYsIDU4LjcwNDU0MCwNCiAgMjAxNywgNTkuMTUxNzAzLA0KKQ0KDQptYWxhcmlhX2RlYXRocyA8LSB0cmliYmxlKA0KICB+eWVhciwgfmRlYXRoc18xMDBrLA0KICAyMDExLCAxMi45MiwNCiAgMjAxMywgMTEuMDAsDQogIDIwMTUsIDEwLjExLA0KICAyMDE2LCA5LjQwLA0KICAyMDE5LCA4Ljk1DQopDQpgYGANCg0KVGhlbiwgam9pbiB0aGUgYWJvdmUgdGFibGVzIHVzaW5nIGEgYGZ1bGxfam9pbigpYCBpbiBvcmRlciB0byByZXRhaW4gYWxsIGluZm9ybWF0aW9uIGZyb20gdGhlIHR3byBkYXRhc2V0cy4NCg0KYGBge3J9DQpmdWxsX2pvaW4obWFsYXJpYV9pbmMsIG1hbGFyaWFfZGVhdGhzKQ0KYGBgDQoNCjo6Og0KDQrigKMgTm93LCBsZXQncyByZXZpc2l0IHRoZSBUQiBkYXRhc2V0IGFuZCBoZWFsdGggZXhwZW5kaXR1cmUgZGF0YXNldC4NCg0KYGBge3J9DQp0Yl8yMDE5X2FmcmljYSANCmhlYWx0aF9leHBfMjAxOQ0KYGBgDQoNCuKAoyBDcmVhdGUgYSBuZXcgZGF0YWZyYW1lLCBgZnVsbF90Yl9oZWFsdGhgLCB1c2luZyBhIGBmdWxsX2pvaW5gLg0KDQpgYGB7cn0NCmZ1bGxfdGJfaGVhbHRoIDwtIHRiXzIwMTlfYWZyaWNhICU+JQ0KIGZ1bGxfam9pbihoZWFsdGhfZXhwXzIwMTkpDQoNCmZ1bGxfdGJfaGVhbHRoDQpgYGANCg0K4oCjIEFsbCByb3dzIGFyZSBrZXB0LCB3aXRoIGBOQWAgZm9yIG1pc3NpbmcgdmFsdWVzLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0K4oCjIFZlbm4gZGlhZ3JhbXMgb2YgTGVmdCwgUmlnaHQsIElubmVyIGFuZCBGdWxsIGpvaW46DQoNCiFbXShpbWFnZXMvdmVubl9jcm9wcGVkLTAxLmpwZykNCg0KIyBMZWFybmluZyBPYmplY3RpdmVzDQoNCi0gICBZb3UgdW5kZXJzdGFuZCBob3cgZWFjaCBvZiB0aGUgZGlmZmVyZW50IGBkcGx5cmAgam9pbnMgd29yazogbGVmdCwgcmlnaHQsIGlubmVyIGFuZCBmdWxsLg0KDQotICAgWW91J3JlIGFibGUgdG8gY2hvb3NlIHRoZSBhcHByb3ByaWF0ZSBqb2luIGZvciB5b3VyIGRhdGENCg0KLSAgIFlvdSBjYW4gam9pbiBzaW1wbGUgZGF0YXNldHMgdG9nZXRoZXIgdXNpbmcgZnVuY3Rpb25zIGZyb20gYGRwbHlyYA0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMgQW5zd2VyIEtleSB7LnVubnVtYmVyZWR9DQoNCiMjIyBROiBMZWZ0IEpvaW4gUGF0aWVudHMgYW5kIENoZWNrdXBzIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCmBgYHtyfQ0KbGVmdF9qb2luKHg9cGF0aWVudHMsIHk9Y2hlY2t1cHMpDQpgYGANCg0KIyMjIFE6IExlZnQgSm9pbiB3aXRoIGJ5IEFyZ3VtZW50IHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCmBgYHtyfQ0KbGVmdF9qb2luKHg9cGF0aWVudF9kZXRhaWxzLCB5PXZhY2NpbmF0aW9uX3JlY29yZHMsIGJ5PWMoImlkX251bWJlciI9InBhdGllbnRfY29kZSIpKQ0KYGBgDQoNCiMjIyBROiBMZWZ0IEpvaW4gRGlhZ25vc2VzIGFuZCBEZW1vZ3JhcGhpY3Mgey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KYGBge3J9DQpsZWZ0X2pvaW4oeD1wYXRpZW50X2RlbW9ncmFwaGljcywgeT1kaXNlYXNlX2R4KQ0KYGBgDQoNCiMjIyBROiBMZWZ0IEpvaW4gVEIgQ2FzZXMgYW5kIENvbnRpbmVudHMgey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KYGBge3J9DQpsZWZ0X2pvaW4oeD10Yl9jYXNlc19jaGlsZHJlbiwgeT1jb3VudHJ5X2NvbnRpbmVudHMsIGJ5PWMoY291bnRyeT0iY291bnRyeS5uYW1lLmVuIikpDQpgYGANCg0KIyMjIFE6IElubmVyIEpvaW4gUGF0aG9nZW5zIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCmBgYHtyfQ0KaW5uZXJfam9pbih0b3RhbF9pbmYsIG91dGNvbWVzKQ0KYGBgDQoNCiMjIyBROiBJbm5lciBKb2luIE9uZSBSb3cgey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KYGBge3J9DQppbm5lcl9qb2luKGhpZ2hlc3RfZXhwLCB0Yl8yMDE5X2FmcmljYSkNCmBgYA0KVGhlcmUgaXMgb25seSBvbmUgY291bnRyeSBpbiBjb21tb24gYmV0d2VlbiB0aGUgdHdvIGRhdGFzZXRzLiANCg0KIyMjIFE6IEZ1bGwgSm9pbiBNYWxhcmlhIERhdGEgey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KYGBge3J9DQpmdWxsX2pvaW4obWFsYXJpYV9pbmMsIG1hbGFyaWFfZGVhdGhzKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIENvbnRyaWJ1dG9ycyB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpUaGUgZm9sbG93aW5nIHRlYW0gbWVtYmVycyBjb250cmlidXRlZCB0byB0aGlzIGxlc3NvbjoNCg0KYHIgdGdjX2NvbnRyaWJ1dG9yc19saXN0KGlkcyA9IGMoImtlbmRhdmlkbiIsICJhbWNraW5sZXkiKSlgIA0KDQo=