1 Introduction

Now that we have a solid grasp on the different types of joins and how they work, we can look at how to manage messier and more complex datasets. Joining real-world data from different sources often requires a bit of thought and cleaning ahead of time.


2 Learning Objectives

  • You know how to check for mismatched values between dataframes

  • You understand how to join using a one-to-many match

  • You know how to join on multiple key columns


2.1 Packages

‣ Load the packages needed for this lesson using the code provided below:

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

2.2 Pre-join data cleaning: addressing data inconsistencies

‣ Often, data from different sources require pre-cleaning before joining.

‣ Reasons include: - Spelling errors - Differences in capitalization - Extra spaces

‣ To join values successfully, they must match perfectly in R.

2.3 A toy example

‣ Let’s use our mock patient data from the first lesson.

‣ Notice the different name formats in the demographic and test_info datasets.

demographic <- tribble(
  ~name,     ~age,
  "Alice",    25,
  "Bob",      32,
  "Charlie",  45,
)
demographic
## # A tibble: 3 × 2
##   name      age
##   <chr>   <dbl>
## 1 Alice      25
## 2 Bob        32
## 3 Charlie    45
test_info <- tribble(
  ~name,  ~test_date,    ~result,
  "alice", "2023-06-05",  "Negative",
  "Bob",   "2023-08-10",  "Positive",
  "charlie","2023-05-02",  "Negative",
)
test_info
## # A tibble: 3 × 3
##   name    test_date  result  
##   <chr>   <chr>      <chr>   
## 1 alice   2023-06-05 Negative
## 2 Bob     2023-08-10 Positive
## 3 charlie 2023-05-02 Negative

‣ Now let’s join the two datasets.

left_join(demographic,test_info, by = "name")
## # A tibble: 3 × 4
##   name      age test_date  result  
##   <chr>   <dbl> <chr>      <chr>   
## 1 Alice      25 <NA>       <NA>    
## 2 Bob        32 2023-08-10 Positive
## 3 Charlie    45 <NA>       <NA>
inner_join(demographic,test_info, by = "name")
## # A tibble: 1 × 4
##   name    age test_date  result  
##   <chr> <dbl> <chr>      <chr>   
## 1 Bob      32 2023-08-10 Positive

‣ The joins are not perfect due to the case differences in names.

‣ Solution: Convert all names to title case using str_to_title().

test_info_title <- test_info %>%
  mutate(name = str_to_title(name)) #converts to title case

test_info_title
## # A tibble: 3 × 3
##   name    test_date  result  
##   <chr>   <chr>      <chr>   
## 1 Alice   2023-06-05 Negative
## 2 Bob     2023-08-10 Positive
## 3 Charlie 2023-05-02 Negative
left_join(demographic, test_info_title, by = "name")
## # A tibble: 3 × 4
##   name      age test_date  result  
##   <chr>   <dbl> <chr>      <chr>   
## 1 Alice      25 2023-06-05 Negative
## 2 Bob        32 2023-08-10 Positive
## 3 Charlie    45 2023-05-02 Negative
inner_join(demographic,test_info_title, by = "name")
## # A tibble: 3 × 4
##   name      age test_date  result  
##   <chr>   <dbl> <chr>      <chr>   
## 1 Alice      25 2023-06-05 Negative
## 2 Bob        32 2023-08-10 Positive
## 3 Charlie    45 2023-05-02 Negative

PRACTICE TIME!

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

Q: Inner Join countries

The following two datasets contain data for India, Indonesia, and the Philippines. However an inner_join() of these datasets produces no output. What are the differences between the values in the key columns that would have to be changed before joining the datasets?

df1 <- tribble(
  ~Country,     ~Capital,
  "India",      "New Delhi",
  "Indonesia",  "Jakarta",
  "Philippines", "Manila"
)

df2 <- tribble(
  ~Country,     ~Population,   ~Life_Expectancy,
  "India ",      1393000000,   69.7,
  "indonesia",   273500000,    71.7,
  "Philipines",  113000000,    72.7
)

df2 <- df2 %>%
  mutate(Country = str_trim(Country))

df2 <- df2 %>%
  mutate(Country = str_trim(Country) %>%
           str_to_title() %>%
           str_replace("Philipines", "Philippines"))
         

inner_join(df1, df2, by = "Country")
## # A tibble: 3 × 4
##   Country     Capital   Population Life_Expectancy
##   <chr>       <chr>          <dbl>           <dbl>
## 1 India       New Delhi 1393000000            69.7
## 2 Indonesia   Jakarta    273500000            71.7
## 3 Philippines Manila     113000000            72.7

2.4 Real Data Example 1: Key Typos

‣ Working with small datasets makes it easy to spot key discrepancies

‣ But, how about dealing with larger datasets?

‣ Let’s explore this with two real-world datasets on TB in India

‣ The first dataset: TB notifications in 2022 for all 36 Indian states and Union Territories

‣ Source: Government of India Tuberculosis Report

tb_notifs <- read_csv(here("data/notif_TB_india_modified.csv"))
## Rows: 72 Columns: 3
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (2): state, hc_type
## dbl (1): tb_notif_count
## 
## ℹ 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_notifs_public <- tb_notifs %>% 
  filter(hc_type == "public") %>% #we want only public systems for now
  select(-hc_type)

tb_notifs_public
## # A tibble: 36 × 2
##    state                                    tb_notif_count
##    <chr>                                             <dbl>
##  1 Andaman & Nicobar Islands                           510
##  2 Andhra Pradesh                                    62075
##  3 Arunachal Pradesh                                  2722
##  4 Assam                                             36801
##  5 Bihar                                             79008
##  6 Chandigarh                                         5664
##  7 Chhattisgarh                                      26801
##  8 Dadra and Nagar Haveli and Daman and Diu           1294
##  9 Delhi                                             76966
## 10 Goa                                                1614
## # ℹ 26 more rows

‣ The second dataset: COVID screening among TB cases for 36 Indian states

‣ Also taken from the same TB Report

covid_screening <- read_csv(here("data/COVID_india_modified.csv")) 
## Rows: 72 Columns: 3
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (2): state, hc_type
## dbl (1): tb_covid_pos
## 
## ℹ 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.
covid_screening_public <- covid_screening %>% 
  filter(hc_type == "public") %>% #we want only public systems for now
  select(-hc_type)

covid_screening_public
## # A tibble: 36 × 2
##    state                                tb_covid_pos
##    <chr>                                       <dbl>
##  1 Andaman & Nicobar Islands                       0
##  2 Andhra Pradesh                                 97
##  3 ArunachalPradesh                                0
##  4 Assam                                          31
##  5 Bihar                                          78
##  6 Chandigarh                                      8
##  7 Chhattisgarh                                   57
##  8 Dadra & Nagar Haveli and Daman & Diu            1
##  9 Delhi                                          44
## 10 Goa                                            12
## # ℹ 26 more rows

‣ Objective: Join these datasets to calculate the percentage of TB patients in each state who tested positive for COVID-19

‣ Let’s attempt an inner_join():

tb_notifs_and_covid_screening <- 
  inner_join(tb_notifs_public, covid_screening_public)
## Joining with `by = join_by(state)`
tb_notifs_and_covid_screening
## # A tibble: 32 × 3
##    state                     tb_notif_count tb_covid_pos
##    <chr>                              <dbl>        <dbl>
##  1 Andaman & Nicobar Islands            510            0
##  2 Andhra Pradesh                     62075           97
##  3 Assam                              36801           31
##  4 Bihar                              79008           78
##  5 Chandigarh                          5664            8
##  6 Chhattisgarh                       26801           57
##  7 Delhi                              76966           44
##  8 Goa                                 1614           12
##  9 Gujarat                           100949          105
## 10 Haryana                            51231           44
## # ℹ 22 more rows

‣ Next, perform the percentage calculation:

tb_notifs_and_covid_screening %>% 
  mutate(pct_covid_pos = 100 *  tb_covid_pos/tb_notif_count) 
## # A tibble: 32 × 4
##    state                     tb_notif_count tb_covid_pos pct_covid_pos
##    <chr>                              <dbl>        <dbl>         <dbl>
##  1 Andaman & Nicobar Islands            510            0        0     
##  2 Andhra Pradesh                     62075           97        0.156 
##  3 Assam                              36801           31        0.0842
##  4 Bihar                              79008           78        0.0987
##  5 Chandigarh                          5664            8        0.141 
##  6 Chhattisgarh                       26801           57        0.213 
##  7 Delhi                              76966           44        0.0572
##  8 Goa                                 1614           12        0.743 
##  9 Gujarat                           100949          105        0.104 
## 10 Haryana                            51231           44        0.0859
## # ℹ 22 more rows

‣ Observation: We now only have 32 rows instead of 36. Why?

‣ There are “key typos” causing mismatches during the join

‣ Key Typos: Spelling/formatting inconsistencies in key columns across datasets

‣ Example: One dataset lists “New Delhi” while the other lists “Delhi”

‣ These inconsistencies prevent proper matching and result in data loss

VOCAB TIME !

‣ “Key”: Column(s) used to match rows across datasets in a join

‣ “Key Typos”: Spelling or formatting inconsistencies in key columns across datasets

2.5 Identifying unmatched values with setdiff()

‣ We want to identify key typos in our data

‣ For this, we can use the setdiff() function

‣ Let’s start by comparing the state values from two dataframes: tb_notifs_public and covid_screening_public

setdiff(tb_notifs_public$state, covid_screening_public$state)
## [1] "Arunachal Pradesh"                       
## [2] "Dadra and Nagar Haveli and Daman and Diu"
## [3] "Tamil Nadu"                              
## [4] "Tripura"

‣ By putting the tb_notifs_public dataset first, we ask:

‣ “Which values are in tb_notifs_public but not in covid_screening_public?”

‣ We should also check the reverse order:

‣ “Which values are in covid_screening_public but not in tb_notifs_public?”

setdiff(covid_screening_public$state, tb_notifs_public$state)
## [1] "ArunachalPradesh"                    
## [2] "Dadra & Nagar Haveli and Daman & Diu"
## [3] "tamil nadu"                          
## [4] "Tri pura"

‣ We found values in covid_screening_public that have spelling errors or are written differently than in tb_notifs_public

‣ Let’s clean up covid_screening_public using case_when()

covid_screening_public_clean <- covid_screening_public %>% 
  mutate(state = 
           case_when(state == "ArunachalPradesh" ~ "Arunachal Pradesh", state == "tamil nadu" ~ "Tamil Nadu", 
                     state == "Tri pura" ~ "Tripura", 
                     state == "Dadra & Nagar Haveli and Daman & Diu" ~ "Dadra and Nagar Haveli and Daman and Diu",
                     TRUE ~ state))

setdiff(tb_notifs_public$state, covid_screening_public_clean$state)
## character(0)
setdiff(covid_screening_public_clean$state, tb_notifs_public$state)
## character(0)

‣ Now, we have no differences in the region’s names

‣ We can join our datasets:

inner_join(tb_notifs_public, covid_screening_public_clean)
## Joining with `by = join_by(state)`

2.6 Identifying unmatched values with antijoin()

‣ The anti_join() function in {dplyr} is another way to identify discrepancies

‣ It returns rows from the first dataframe where the key values don’t match the second dataframe

‣ Let’s find unmatched state values in tb_notifs_public compared to covid_screening_public

anti_join(tb_notifs_public, covid_screening_public)
## Joining with `by = join_by(state)`

‣ And vice versa, for values in covid_screening_public but not in tb_notifs_public:

anti_join(covid_screening_public, tb_notifs_public)
## Joining with `by = join_by(state)`

‣ This method provides more context for discrepancies

‣ After identifying, fix the errors with mutate() and proceed with the join

PRACTICE TIME !

Q: Check and fix typos before join

The following dataframe, also taken from the TB Report, contains information on the number of pediatric TB cases and the number of pediatric patients initiated on treatment.

child <- read_csv(here("data/child_TB_india_modified.csv"))

child_public <- child %>% 
  filter(hc_type == "public") %>% 
  select(-hc_type)

child_public 
  1. Using set_diff() or anti_join() compare the key values from the child_public dataframe with those from the tb_notifs_public dataframe, which was defined previously
  2. Make any necessary changes to the child_public dataframe to ensure that the values match.
  3. Join the two datasets.
  4. Identify which two regions have the highest proportion of TB cases in children.
setdiff(tb_notifs_public$state, child_public$state)
## [1] "Arunachal Pradesh" "Jammu & Kashmir"   "Kerala"           
## [4] "Puducherry"
setdiff(child_public$state, tb_notifs_public$state)
## [1] "ArunachalPradesh"  "Jammu and Kashmir" "kerala"           
## [4] "Pondicherry"
child_public_clean <- child_public %>% 
  mutate(state = 
           case_when(state == "ArunachalPradesh" ~ "Arunachal Pradesh", 
                     state ==  "Jammu and Kashmir" ~ "Jammu & Kashmir", 
                     state == "kerala" ~  "Kerala",
                     state == "Pondicherry" ~ "Puducherry",
                     TRUE ~ state))

setdiff(tb_notifs_public$state, child_public_clean$state)
## character(0)
setdiff(child_public_clean$state, tb_notifs_public$state)
## character(0)
tb_child_public <- inner_join(tb_notifs_public, child_public_clean)
## Joining with `by = join_by(state)`
tb_child_public %>% 
  mutate(prop_tb = 100 * tb_child_notifs/tb_notif_count) %>% 
  arrange(-prop_tb) %>% 
  head(2)
## # A tibble: 2 × 4
##   state             tb_notif_count tb_child_notifs prop_tb
##   <chr>                      <dbl>           <dbl>   <dbl>
## 1 Delhi                      76966            7867   10.2 
## 2 Arunachal Pradesh           2722             256    9.40

2.7 Real Data Example 2: Key Typos and Data Gaps

Key typos and formatting inconsistencies can hinder successful joins between datasets.

‣ Let’s explore a more complex scenario involving the covid_screening_public dataset.

covid_screening_public
## # A tibble: 36 × 2
##    state                                tb_covid_pos
##    <chr>                                       <dbl>
##  1 Andaman & Nicobar Islands                       0
##  2 Andhra Pradesh                                 97
##  3 ArunachalPradesh                                0
##  4 Assam                                          31
##  5 Bihar                                          78
##  6 Chandigarh                                      8
##  7 Chhattisgarh                                   57
##  8 Dadra & Nagar Haveli and Daman & Diu            1
##  9 Delhi                                          44
## 10 Goa                                            12
## # ℹ 26 more rows

‣ Our goal is to enrich this dataset with zoning information from the regions dataset.

regions <- read_csv(here("data/region_data_india.csv"))
## Rows: 32 Columns: 3
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (3): zonal_council, subdivision_category, state_UT
## 
## ℹ 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.
regions
## # A tibble: 32 × 3
##    zonal_council          subdivision_category state_UT                         
##    <chr>                  <chr>                <chr>                            
##  1 No Zonal Council       Union Territory      Andaman & Nicobar Islands        
##  2 North Eastern Council  State                Arunachal Pradesh                
##  3 North Eastern Council  State                Assam                            
##  4 Eastern Zonal Council  State                Bihar                            
##  5 Northern Zonal Council Union Territory      Chandigarh                       
##  6 Western Zonal Council  Union Territory      Dadra and Nagar Haveli and Daman…
##  7 Northern Zonal Council Union Territory      Delhi                            
##  8 Western Zonal Council  State                Goa                              
##  9 Western Zonal Council  State                Gujarat                          
## 10 Northern Zonal Council State                Haryana                          
## # ℹ 22 more rows

‣ Columns in regions include zonal_council, subdivision_category, and state_UT.

‣ We’ll use a left join to merge without losing rows from covid_screening_public.

covid_regions <- left_join(covid_screening_public, 
                           regions, 
                           by = c("state" = "state_UT"))

covid_regions
## # A tibble: 36 × 4
##    state                         tb_covid_pos zonal_council subdivision_category
##    <chr>                                <dbl> <chr>         <chr>               
##  1 Andaman & Nicobar Islands                0 No Zonal Cou… Union Territory     
##  2 Andhra Pradesh                          97 <NA>          <NA>                
##  3 ArunachalPradesh                         0 <NA>          <NA>                
##  4 Assam                                   31 North Easter… State               
##  5 Bihar                                   78 Eastern Zona… State               
##  6 Chandigarh                               8 Northern Zon… Union Territory     
##  7 Chhattisgarh                            57 <NA>          <NA>                
##  8 Dadra & Nagar Haveli and Dam…            1 <NA>          <NA>                
##  9 Delhi                                   44 Northern Zon… Union Territory     
## 10 Goa                                     12 Western Zona… State               
## # ℹ 26 more rows

‣ After the join, some entries are missing zoning information.

covid_regions %>% 
  filter(is.na(zonal_council))
## # A tibble: 7 × 4
##   state                          tb_covid_pos zonal_council subdivision_category
##   <chr>                                 <dbl> <chr>         <chr>               
## 1 Andhra Pradesh                           97 <NA>          <NA>                
## 2 ArunachalPradesh                          0 <NA>          <NA>                
## 3 Chhattisgarh                             57 <NA>          <NA>                
## 4 Dadra & Nagar Haveli and Dama…            1 <NA>          <NA>                
## 5 Ladakh                                    0 <NA>          <NA>                
## 6 tamil nadu                              178 <NA>          <NA>                
## 7 Tri pura                                  2 <NA>          <NA>

‣ To understand why, we’ll investigate using anti_join().

anti_join(regions, covid_screening_public, by = c("state_UT" = "state"))
## # A tibble: 3 × 3
##   zonal_council         subdivision_category state_UT                           
##   <chr>                 <chr>                <chr>                              
## 1 North Eastern Council State                Arunachal Pradesh                  
## 2 Western Zonal Council Union Territory      Dadra and Nagar Haveli and Daman a…
## 3 North Eastern Council State                Tripura

‣ 3 states are present in regions but absent in covid_screening_public.

‣ Now, let’s reverse the check.

anti_join(covid_screening_public, regions, by = c("state" = "state_UT"))
## # A tibble: 7 × 2
##   state                                tb_covid_pos
##   <chr>                                       <dbl>
## 1 Andhra Pradesh                                 97
## 2 ArunachalPradesh                                0
## 3 Chhattisgarh                                   57
## 4 Dadra & Nagar Haveli and Daman & Diu            1
## 5 Ladakh                                          0
## 6 tamil nadu                                    178
## 7 Tri pura                                        2

‣ Some mismatches are due to key typos, while others are absent from the regions dataset.

‣ To correct typos, we’ll apply similar fixes as in a previous example.

# Correct state typos:
covid_screening_public_fixed <- covid_screening_public %>% 
  mutate(state = 
           case_when(state == "ArunachalPradesh" ~ "Arunachal Pradesh", 
                     state == "Tri pura" ~ "Tripura", 
                     state == "Dadra & Nagar Haveli and Daman & Diu" ~ "Dadra and Nagar Haveli and Daman and Diu", 
                     TRUE ~ state))

‣ After applying the fixes, we perform another left join.

covid_regions_joined_fixed <- left_join(covid_screening_public_fixed, 
                                        regions, 
                                        by = c("state" = "state_UT"))

covid_regions_joined_fixed
## # A tibble: 36 × 4
##    state                         tb_covid_pos zonal_council subdivision_category
##    <chr>                                <dbl> <chr>         <chr>               
##  1 Andaman & Nicobar Islands                0 No Zonal Cou… Union Territory     
##  2 Andhra Pradesh                          97 <NA>          <NA>                
##  3 Arunachal Pradesh                        0 North Easter… State               
##  4 Assam                                   31 North Easter… State               
##  5 Bihar                                   78 Eastern Zona… State               
##  6 Chandigarh                               8 Northern Zon… Union Territory     
##  7 Chhattisgarh                            57 <NA>          <NA>                
##  8 Dadra and Nagar Haveli and D…            1 Western Zona… Union Territory     
##  9 Delhi                                   44 Northern Zon… Union Territory     
## 10 Goa                                     12 Western Zona… State               
## # ℹ 26 more rows

‣ Check for entries still missing zoning information.

# Check for missing zonal council information again:
covid_regions_joined_fixed %>% 
  filter(is.na(zonal_council))
## # A tibble: 4 × 4
##   state          tb_covid_pos zonal_council subdivision_category
##   <chr>                 <dbl> <chr>         <chr>               
## 1 Andhra Pradesh           97 <NA>          <NA>                
## 2 Chhattisgarh             57 <NA>          <NA>                
## 3 Ladakh                    0 <NA>          <NA>                
## 4 tamil nadu              178 <NA>          <NA>

‣ Some regions were not included in the regions dataset.

‣ This example highlights the challenges of ensuring no data loss during joins.

REMEMBER!

‣ Correcting typographical errors for successful joins is a complex task.

‣ Fuzzy matching may be necessary for imperfect string comparisons.

‣ Explore the {fuzzyjoin} package in R for solutions.

Q: Merging TB Cases with Geographic Data

Run the code bellow to define two datasets.

The first, top_tb_cases_kids records the top 20 countries with the highest incidence of tuberculosis (TB) in children for the year 2012:

top_tb_cases_kids <- tidyr::who %>% 
  filter(year == 2012) %>% 
  transmute(country, iso3, tb_cases_smear_0_14 = new_sp_m014 + new_sp_f014) %>% 
  arrange(desc(tb_cases_smear_0_14)) %>% 
  head(20)

top_tb_cases_kids

And country_regions lists countries along with their respective regions and continents:

country_regions <- countrycode::codelist %>% 
  select(country_name = iso.name.en, iso3c, region) %>% 
  filter(complete.cases(country_name, region))

country_regions

Your task is to augment the TB cases data with the region and continent information without losing any relevant data.

  1. Perform a left_join of top_tb_cases_kids with country_regions with the country names as the key. Identify which five countries fail to match correctly.
top_tb_country_regions <- left_join(top_tb_cases_kids, country_regions, by = c("country" = "country_name"))

top_tb_country_regions %>% 
  filter(is.na(region))
  1. Using the code below, amend the country names in top_tb_cases_kids using case_when to rectify mismatches:
top_tb_cases_kids_fixed <- top_tb_cases_kids %>%
  mutate(country = case_when(
    country == "Democratic Republic of the Congo" ~ "Congo, Democratic Republic of the",
    country == "Philippines" ~ "Philippines (the)",
    country == "Democratic People's Republic of Korea" ~ "Korea, Democratic People's Republic of",
    country == "United Republic of Tanzania" ~ "Tanzania, United Republic of",
    country == "Cote d'Ivoire" ~ "Côte d'Ivoire",
    TRUE ~ country 
  ))

top_tb_cases_kids_fixed
## # A tibble: 20 × 3
##    country                                iso3  tb_cases_smear_0_14
##    <chr>                                  <chr>               <dbl>
##  1 India                                  IND                 12957
##  2 Pakistan                               PAK                  3947
##  3 Congo, Democratic Republic of the      COD                  3138
##  4 South Africa                           ZAF                  2677
##  5 Indonesia                              IDN                  1703
##  6 Nigeria                                NGA                  1187
##  7 China                                  CHN                  1091
##  8 Philippines (the)                      PHL                  1049
##  9 Kenya                                  KEN                   996
## 10 Angola                                 AGO                   982
## 11 Bangladesh                             BGD                   966
## 12 Uganda                                 UGA                   636
## 13 Afghanistan                            AFG                   588
## 14 Brazil                                 BRA                   580
## 15 Korea, Democratic People's Republic of PRK                   520
## 16 Tanzania, United Republic of           TZA                   493
## 17 Nepal                                  NPL                   460
## 18 Madagascar                             MDG                   419
## 19 Côte d'Ivoire                          CIV                   367
## 20 Myanmar                                MMR                   338

Now attempt the join again using the revised dataset.

left_join(top_tb_cases_kids_fixed, country_regions, by = c("country" = "country_name"))
  1. Try another left_join, but this time use the three-letter ISO code as the key. Do those initial five countries now align properly?
left_join(top_tb_cases_kids, country_regions, by = c("iso3" = "iso3c"))
  1. What is the advantage of utilizing ISO codes when recording and storing country information?

ISO codes improve accuracy, interoperability, and efficiency when working with country information, making them a best practice for managing international data.

2.8 One-to-many relationships

‣ We’ve mainly looked at one-to-one joins.

‣ But what about one-to-many joins?

‣ Here, an observation in one dataframe corresponds to multiple observations in the other.

The concept of one-to-many relationships
The concept of one-to-many relationships

‣ To illustrate, let’s return to our patients and their COVID test data.

‣ Imagine Alice and Xavier got tested multiple times for COVID.

test_info_many <- tribble(
  ~name,    ~test_date, ~result,
  "Alice",  "2023-06-05", "Negative",
  "Alice",  "2023-06-10", "Positive",
  "Bob",    "2023-08-10", "Positive",
  "Xavier", "2023-05-02", "Negative",
  "Xavier", "2023-05-12", "Negative",
)

‣ Let’s see what happens when we use a left_join() with demographic as the dataset to the left of the call:

left_join(demographic, test_info_many)
## Joining with `by = join_by(name)`
## # A tibble: 4 × 4
##   name      age test_date  result  
##   <chr>   <dbl> <chr>      <chr>   
## 1 Alice      25 2023-06-05 Negative
## 2 Alice      25 2023-06-10 Positive
## 3 Bob        32 2023-08-10 Positive
## 4 Charlie    45 <NA>       <NA>

‣ Here’s what happened:

Alice was retained.

‣ But she featured twice in the right dataset, so her demographic information was duplicated in the final dataset.

Xavier was dropped entirely.

‣ When performing a one-to-many join, the data from the “one” side is duplicated for each matching row of the “many” side.

Q: Merging TB Cases with Geographic Data

Copy the code below to create two small dataframes:

patient_info <- tribble(
  ~patient_id, ~name,     ~age,
  1,          "Liam",     32,
  2,          "Manny",    28,
  3,          "Nico",     40
)

conditions <- tribble(
  ~patient_id, ~disease,
  1,           "Diabetes",
  1,           "Hypertension",
  2,           "Asthma",
  3,           "High Cholesterol",
  3,           "Arthritis"
)

If you use a left_join() to join these datasets, how many rows will be in the final dataframe? Try to figure it out and then perform the join to see if you were right!

left_join(patient_info, conditions)
## Joining with `by = join_by(patient_id)`
## # A tibble: 5 × 4
##   patient_id name    age disease         
##        <dbl> <chr> <dbl> <chr>           
## 1          1 Liam     32 Diabetes        
## 2          1 Liam     32 Hypertension    
## 3          2 Manny    28 Asthma          
## 4          3 Nico     40 High Cholesterol
## 5          3 Nico     40 Arthritis

‣ Explore the tb_notifs dataset

tb_notifs
## # A tibble: 72 × 3
##    state                     hc_type tb_notif_count
##    <chr>                     <chr>            <dbl>
##  1 Andaman & Nicobar Islands public             510
##  2 Andaman & Nicobar Islands private             24
##  3 Andhra Pradesh            public           62075
##  4 Andhra Pradesh            private          30112
##  5 Arunachal Pradesh         public            2722
##  6 Arunachal Pradesh         private            141
##  7 Assam                     public           36801
##  8 Assam                     private          11021
##  9 Bihar                     public           79008
## 10 Bihar                     private          82157
## # ℹ 62 more rows

‣ Note: Two rows per state, for public and private health facilities

‣ Second dataset: regions dataset, containing Indian state and Union Territories

full_regions <- read_csv(here("data/region_data_india_full.csv"))
## Rows: 36 Columns: 3
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (3): zonal_council, subdivision_category, state_UT
## 
## ℹ 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.
full_regions
## # A tibble: 36 × 3
##    zonal_council          subdivision_category state_UT                         
##    <chr>                  <chr>                <chr>                            
##  1 No Zonal Council       Union Territory      Andaman & Nicobar Islands        
##  2 North Eastern Council  State                Arunachal Pradesh                
##  3 North Eastern Council  State                Assam                            
##  4 Eastern Zonal Council  State                Bihar                            
##  5 Northern Zonal Council Union Territory      Chandigarh                       
##  6 Western Zonal Council  Union Territory      Dadra and Nagar Haveli and Daman…
##  7 Northern Zonal Council Union Territory      Delhi                            
##  8 Western Zonal Council  State                Goa                              
##  9 Western Zonal Council  State                Gujarat                          
## 10 Northern Zonal Council State                Haryana                          
## # ℹ 26 more rows

‣ Let’s try joining the datasets:

notif_regions <- tb_notifs %>% 
  left_join(regions, by = c("state" = "state_UT"))
notif_regions
## # A tibble: 72 × 5
##    state               hc_type tb_notif_count zonal_council subdivision_category
##    <chr>               <chr>            <dbl> <chr>         <chr>               
##  1 Andaman & Nicobar … public             510 No Zonal Cou… Union Territory     
##  2 Andaman & Nicobar … private             24 No Zonal Cou… Union Territory     
##  3 Andhra Pradesh      public           62075 <NA>          <NA>                
##  4 Andhra Pradesh      private          30112 <NA>          <NA>                
##  5 Arunachal Pradesh   public            2722 North Easter… State               
##  6 Arunachal Pradesh   private            141 North Easter… State               
##  7 Assam               public           36801 North Easter… State               
##  8 Assam               private          11021 North Easter… State               
##  9 Bihar               public           79008 Eastern Zona… State               
## 10 Bihar               private          82157 Eastern Zona… State               
## # ℹ 62 more rows

‣ Data from the regions dataframe was duplicated

2.9 Multiple key columns

‣ Sometimes we have more than one column that uniquely identifies the observations that we want to match on.

‣ Consider systolic blood pressure measures for patients before and after a drug trial

blood_pressure <- tribble(
  ~name,    ~time_point,  ~systolic, 
  "Alice",   "pre",         139,      
  "Alice",   "post",        121,      
  "Bob",     "pre",         137,      
  "Bob",     "post",        128,      
  "Charlie", "pre",         137,      
  "Charlie", "post",        130 )

‣ Another dataset contains serum creatinine levels for the same patients and time points

kidney <- tribble(
  ~name,    ~time_point,  ~creatinine, 
  "Alice",   "pre",         0.84,      
  "Alice",   "post",        1.03,      
  "Bob",     "pre",         0.87,      
  "Bob",     "post",        1.21,      
  "Charlie", "pre",         0.88,      
  "Charlie", "post",        1.25 )

‣ Our goal: join two datasets so each patient has two rows, one for levels before the drug and one for levels after.

‣ First instinct: join on the patient’s name.

‣ Let’s try this out:

bp_kidney_dups <- blood_pressure %>% 
  left_join(kidney, by = "name")
## Warning in left_join(., kidney, by = "name"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 1 of `x` matches multiple rows in `y`.
## ℹ Row 1 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.
bp_kidney_dups
## # A tibble: 12 × 5
##    name    time_point.x systolic time_point.y creatinine
##    <chr>   <chr>           <dbl> <chr>             <dbl>
##  1 Alice   pre               139 pre                0.84
##  2 Alice   pre               139 post               1.03
##  3 Alice   post              121 pre                0.84
##  4 Alice   post              121 post               1.03
##  5 Bob     pre               137 pre                0.87
##  6 Bob     pre               137 post               1.21
##  7 Bob     post              128 pre                0.87
##  8 Bob     post              128 post               1.21
##  9 Charlie pre               137 pre                0.88
## 10 Charlie pre               137 post               1.25
## 11 Charlie post              130 pre                0.88
## 12 Charlie post              130 post               1.25

‣ Result: Duplicated rows, leading to four rows per person.

‣ “Many-to-many” relationship: A scenario we generally want to avoid!

‣ We also see two time_point columns differentiated by .x and .y.

‣ Instead, we should match on BOTH name and time_point.

‣ Use c() function to specify both column names.

bp_kidney <- blood_pressure %>%
  left_join(kidney, by = c("name", "time_point"))

bp_kidney
## # A tibble: 6 × 4
##   name    time_point systolic creatinine
##   <chr>   <chr>         <dbl>      <dbl>
## 1 Alice   pre             139       0.84
## 2 Alice   post            121       1.03
## 3 Bob     pre             137       0.87
## 4 Bob     post            128       1.21
## 5 Charlie pre             137       0.88
## 6 Charlie post            130       1.25

‣ This gives us the desired outcome!

‣ Now, let’s apply this to our tb_notifs and covid_screening datasets.

tb_notifs
## # A tibble: 72 × 3
##    state                     hc_type tb_notif_count
##    <chr>                     <chr>            <dbl>
##  1 Andaman & Nicobar Islands public             510
##  2 Andaman & Nicobar Islands private             24
##  3 Andhra Pradesh            public           62075
##  4 Andhra Pradesh            private          30112
##  5 Arunachal Pradesh         public            2722
##  6 Arunachal Pradesh         private            141
##  7 Assam                     public           36801
##  8 Assam                     private          11021
##  9 Bihar                     public           79008
## 10 Bihar                     private          82157
## # ℹ 62 more rows
covid_screening
## # A tibble: 72 × 3
##    state                     hc_type tb_covid_pos
##    <chr>                     <chr>          <dbl>
##  1 Andaman & Nicobar Islands public             0
##  2 Andaman & Nicobar Islands private            0
##  3 Andhra Pradesh            public            97
##  4 Andhra Pradesh            private           17
##  5 ArunachalPradesh          public             0
##  6 ArunachalPradesh          private            0
##  7 Assam                     public            31
##  8 Assam                     private           16
##  9 Bihar                     public            78
## 10 Bihar                     private           53
## # ℹ 62 more rows

‣ Final dataframe goal: Two rows for each state, one for public and one for private sector data.

‣ Match on both state and hc_type using c() in the by= statement.

notif_covid <- tb_notifs %>%
  left_join(covid_screening, by=c("state", "hc_type"))
notif_covid
## # A tibble: 72 × 4
##    state                     hc_type tb_notif_count tb_covid_pos
##    <chr>                     <chr>            <dbl>        <dbl>
##  1 Andaman & Nicobar Islands public             510            0
##  2 Andaman & Nicobar Islands private             24            0
##  3 Andhra Pradesh            public           62075           97
##  4 Andhra Pradesh            private          30112           17
##  5 Arunachal Pradesh         public            2722           NA
##  6 Arunachal Pradesh         private            141           NA
##  7 Assam                     public           36801           31
##  8 Assam                     private          11021           16
##  9 Bihar                     public           79008           78
## 10 Bihar                     private          82157           53
## # ℹ 62 more rows

‣ Success! We got the exact structure we wanted.

PRACTICE TIME !

Q: Joining three datasets, including one-to-many

In this practice, you will join three datasets: notif_covid, child, and regions. Follow the steps below to ensure that no data is lost.

Follow these steps and fill in the blanked code fragments to complete the joining process.

  1. Check for mismatches between notif_covid and child using anti_join().
# Check mismatches
anti_join(notif_covid, child)
anti_join(child, notif_covid)
  1. Clean the mismatches in child by creating child_fixed. Use case_when() to update state names.
# Clean mismatches
child_fixed <- child %>%
  mutate(state = case_when(
    state == "ArunachalPradesh" ~ "Arunachal Pradesh",
    state == "Jammu and Kashmir" ~ "Jammu & Kashmir",
    state == "kerala" ~ "Kerala",
    state == "Pondicherry" ~ "Puducherry",
    TRUE ~ state
  ))
  1. Verify that the mismatches have been resolved using anti_join(). These should return empty dataframes.
# Verify mismatches resolved
anti_join(child_fixed, notif_covid)
anti_join(notif_covid, child_fixed)
  1. Join notif_covid and child_fixed using left_join() on state and hc_type. Assign the result to join_1.
# Join notif_covid and child_fixed
join_1 <- notif_covid %>% 
  left_join(child_fixed, by = c("state", "hc_type"))
  1. Check for mismatches between join_1 and regions using anti_join().
# Check mismatches
anti_join(join_1, regions, by = c("state" = "state_UT"))
anti_join(regions, join_1, by = c("state_UT" = "state"))

You may notice that some states, e.g. Ladakh, are missing from the regions dataset. There is nothing we can do about this for the moment, so we will proceed with the join.

  1. Perform the final join using left_join() on state from join_1 and state_UT from regions. Assign the result to final_join.
# Final join
final_join <- join_1 %>% 
  left_join(regions, by = c("state" = "state_UT"))

The question said to avoid losing information, so you may be wondering why we used a left_join() of a full_join()? The outputs are actually the same in this case. Remember that left_join() keeps all rows from the left dataset. The missing states we commented in step 5 are present in the left, join_1 dataset, so they are not lost in the final join. And when you can choose between left_join() and full_join(), it is better to use left_join() as it is easier for your audience to reason about what your code is doing.

  1. Display the final_join dataset.
# Display final_join
final_join

3 Wrap Up!

In this lesson, we delved into the intricacies of data cleaning before a join, focusing on how to detect and correct mismatches or inconsistencies in key columns. We also highlighted the impact of one-to-many relationships in joining dataframes, showing how data from the “one” side is duplicated for each matching row of the “many” side. Finally, we demonstrated how to join dataframes using multiple key columns.

As we conclude this lesson, we hope that you have gained a deeper understanding of the importance and utility of joining dataframes in R.


Answer Key

Q: Inner Join countries

df2_fixed <- df2 %>% 
  mutate(Country = 
           case_match(Country, 
                             "India " ~ "India", # Remove blank space at end
                             "indonesia" ~ "Indonesia", # Capitalize
                             "Philipines" ~ "Philippines", # Fix spelling
                             .default=Country))

inner_join(df1, df2_fixed)
## Joining with `by = join_by(Country)`
## # A tibble: 3 × 4
##   Country     Capital   Population Life_Expectancy
##   <chr>       <chr>          <dbl>           <dbl>
## 1 India       New Delhi 1393000000            69.7
## 2 Indonesia   Jakarta    273500000            71.7
## 3 Philippines Manila     113000000            72.7

Q: Check and fix typos before join

# setdiff()
setdiff(child_public$state, tb_notifs_public$state)
## [1] "ArunachalPradesh"  "Jammu and Kashmir" "kerala"           
## [4] "Pondicherry"
setdiff(tb_notifs_public$state, child_public$state)
## [1] "Arunachal Pradesh" "Jammu & Kashmir"   "Kerala"           
## [4] "Puducherry"
# antijoin
anti_join(child_public, tb_notifs_public)
## Joining with `by = join_by(state)`
## # A tibble: 4 × 2
##   state             tb_child_notifs
##   <chr>                       <dbl>
## 1 ArunachalPradesh              256
## 2 Jammu and Kashmir             511
## 3 kerala                        480
## 4 Pondicherry                   101
anti_join(tb_notifs_public, child_public)
## Joining with `by = join_by(state)`
## # A tibble: 4 × 2
##   state             tb_notif_count
##   <chr>                      <dbl>
## 1 Arunachal Pradesh           2722
## 2 Jammu & Kashmir            10022
## 3 Kerala                     16766
## 4 Puducherry                  3732
child_public_fixed <- child_public %>%
  mutate(state = 
           case_when(state == "ArunachalPradesh" ~ "Arunachal Pradesh", 
                     state == "Jammu and Kashmir" ~ "Jammu & Kashmir", 
                     state == "kerala" ~ "Kerala", 
                     state == "Pondicherry" ~ "Puducherry",
                     TRUE ~ state))
child_tb_public <- child_public_fixed %>%
  inner_join(tb_notifs_public)
## Joining with `by = join_by(state)`
child_tb_public %>%
  mutate(tb_child_prop = tb_child_notifs/tb_notif_count) %>%
  arrange(-tb_child_prop)

Q: Merging TB Cases with Geographic Data

left_join(top_tb_cases_kids, country_regions, by = c("country"="country_name"))
setdiff(top_tb_cases_kids$country, country_regions$country_name)
## [1] "Democratic Republic of the Congo"     
## [2] "Philippines"                          
## [3] "Democratic People's Republic of Korea"
## [4] "United Republic of Tanzania"          
## [5] "Cote d'Ivoire"
left_join(top_tb_cases_kids_fixed, country_regions, by = c("country"="country_name"))
left_join(top_tb_cases_kids, country_regions, by = c("iso3" = "iso3c"))

ISO codes are standardized - there in only one way of writing them. This makes it useful for joining.

Q: Merging One-to-Many Patient Records

# 5 rows in the final dataframe
patient_info %>% 
  left_join(conditions)
## Joining with `by = join_by(patient_id)`

Q: Joining three datasets, including one-to-many

  1. Check for mismatches between notif_covid and child using anti_join().
# Check mismatches
anti_join(child, notif_covid)
## Joining with `by = join_by(state, hc_type)`
## # A tibble: 8 × 3
##   state             hc_type tb_child_notifs
##   <chr>             <chr>             <dbl>
## 1 ArunachalPradesh  public              256
## 2 ArunachalPradesh  private              19
## 3 Jammu and Kashmir public              511
## 4 Jammu and Kashmir private              89
## 5 kerala            public              480
## 6 kerala            private             409
## 7 Pondicherry       public              101
## 8 Pondicherry       private               6
anti_join(notif_covid, child)
## Joining with `by = join_by(state, hc_type)`
## # A tibble: 8 × 4
##   state             hc_type tb_notif_count tb_covid_pos
##   <chr>             <chr>            <dbl>        <dbl>
## 1 Arunachal Pradesh public            2722           NA
## 2 Arunachal Pradesh private            141           NA
## 3 Jammu & Kashmir   public           10022            9
## 4 Jammu & Kashmir   private           1782            1
## 5 Kerala            public           16766          322
## 6 Kerala            private           6622           37
## 7 Puducherry        public            3732           16
## 8 Puducherry        private            103            0
  1. Clean the mismatches in child by creating child_fixed. Use case_when() to update state names.
# Clean mismatches
child_fixed <- child %>%
  mutate(state = case_when(
    state == "ArunachalPradesh" ~ "Arunachal Pradesh",
    state == "Jammu and Kashmir" ~ "Jammu & Kashmir",
    state == "kerala" ~ "Kerala",
    state == "Pondicherry" ~ "Puducherry",
    TRUE ~ state
  ))
  1. Verify that the mismatches have been resolved using anti_join().
# Verify mismatches resolved
anti_join(child_fixed, notif_covid)
## Joining with `by = join_by(state, hc_type)`
## # A tibble: 0 × 3
## # ℹ 3 variables: state <chr>, hc_type <chr>, tb_child_notifs <dbl>
anti_join(notif_covid, child_fixed)
## Joining with `by = join_by(state, hc_type)`
## # A tibble: 0 × 4
## # ℹ 4 variables: state <chr>, hc_type <chr>, tb_notif_count <dbl>,
## #   tb_covid_pos <dbl>
  1. Join notif_covid and child_fixed using left_join() on state and hc_type. Assign the result to join_1.
# Join notif_covid and child_fixed
join_1 <- notif_covid %>% 
  left_join(child_fixed, by = c("state", "hc_type"))
  1. Check for mismatches between join_1 and regions using anti_join().
# Check mismatches
anti_join(join_1, regions, by = c("state" = "state_UT"))
## # A tibble: 8 × 5
##   state          hc_type tb_notif_count tb_covid_pos tb_child_notifs
##   <chr>          <chr>            <dbl>        <dbl>           <dbl>
## 1 Andhra Pradesh public           62075           97            1347
## 2 Andhra Pradesh private          30112           17            1333
## 3 Chhattisgarh   public           26801           57             935
## 4 Chhattisgarh   private          11720            8             974
## 5 Ladakh         public             311            0               8
## 6 Ladakh         private              9            0               0
## 7 Tamil Nadu     public           71896           NA            1765
## 8 Tamil Nadu     private          21983           NA            1651
anti_join(regions, join_1, by = c("state_UT" = "state"))
## # A tibble: 0 × 3
## # ℹ 3 variables: zonal_council <chr>, subdivision_category <chr>,
## #   state_UT <chr>

You may notice that some states, e.g. Ladakh, which are present in join_1 are missing from the regions dataset. There is nothing we can do about this for the moment, so we will proceed with the join.

  1. Perform the final join using left_join() on state from join_1 and state_UT from regions. Assign the result to final_join.
# Final join
final_join <- join_1 %>% 
  left_join(regions, by = c("state" = "state_UT"))

The question said to avoid losing information, so you may be wondering why we used a left_join() of a full_join()? The outputs are actually the same in this case. Remember that left_join() keeps all rows from the left dataset. The missing states we commented in step 5 are present in the left, join_1 dataset, so they are not lost in the final join. And when you can choose between left_join() and full_join(), it is better to use left_join() as it is easier for your audience to reason about what your code is doing.

  1. Display the final_join dataset.
# Display final_join
final_join
## # A tibble: 72 × 7
##    state       hc_type tb_notif_count tb_covid_pos tb_child_notifs zonal_council
##    <chr>       <chr>            <dbl>        <dbl>           <dbl> <chr>        
##  1 Andaman & … public             510            0              18 No Zonal Cou…
##  2 Andaman & … private             24            0               1 No Zonal Cou…
##  3 Andhra Pra… public           62075           97            1347 <NA>         
##  4 Andhra Pra… private          30112           17            1333 <NA>         
##  5 Arunachal … public            2722           NA             256 North Easter…
##  6 Arunachal … private            141           NA              19 North Easter…
##  7 Assam       public           36801           31             992 North Easter…
##  8 Assam       private          11021           16             433 North Easter…
##  9 Bihar       public           79008           78            4434 Eastern Zona…
## 10 Bihar       private          82157           53           10778 Eastern Zona…
## # ℹ 62 more rows
## # ℹ 1 more variable: subdivision_category <chr>

Contributors

The following team members contributed to this lesson:

LS0tDQp0aXRsZTogJ0pvaW5pbmcgMjogTWlzbWF0Y2hlZCBWYWx1ZXMsIE9uZS10by1NYW55ICYgTXVsdGktS2V5IEpvaW5zJw0KYXV0aG9yOiANCiAgLSBuYW1lOiAiQ2FtaWxsZSBCZWF0cmljZSBWYWxlcmEiDQogIC0gbmFtZTogIktlbmUgRGF2aWQgTndvc3UiIA0KICAtIG5hbWU6ICJBbWFuZGEgTWNLaW5sZXkiDQpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY29kZV9mb2xkaW5nOiAic2hvdyIgIA0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjc3M6ICFleHByIGhlcmU6OmhlcmUoImdsb2JhbC9zdHlsZS9zdHlsZS5jc3MiKQ0KICAgIGhpZ2hsaWdodDoga2F0ZQ0KZWRpdG9yX29wdGlvbnM6IA0KICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lDQogIG1hcmtkb3duOiANCiAgICB3cmFwOiA3Mg0KLS0tDQoNCmBgYHtyLCBlY2hvID0gRiwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KIyBMb2FkIHBhY2thZ2VzIA0KaWYoIXJlcXVpcmUocGFjbWFuKSkgaW5zdGFsbC5wYWNrYWdlcygicGFjbWFuIikNCnBhY21hbjo6cF9sb2FkKHJsYW5nLCB0aWR5dmVyc2UsIGtuaXRyLCBoZXJlLCByZWFjdGFibGUsIGd0LCBmbGV4dGFibGUpDQoNCiMjIGZ1bmN0aW9ucw0Kc291cmNlKGhlcmU6OmhlcmUoImdsb2JhbC9mdW5jdGlvbnMvbWlzY19mdW5jdGlvbnMuUiIpKQ0KDQojIyBkZWZhdWx0IHJlbmRlcg0KcmVnaXN0ZXJTM21ldGhvZCgicmVhY3RhYmxlXzVfcm93cyIsICJkYXRhLmZyYW1lIiwgcmVhY3RhYmxlXzVfcm93cykNCmtuaXRyOjpvcHRzX2NodW5rJHNldChjbGFzcy5zb3VyY2UgPSAidGdjLWNvZGUtYmxvY2siKQ0KYGBgDQoNCg0KIyBJbnRyb2R1Y3Rpb24NCg0KTm93IHRoYXQgd2UgaGF2ZSBhIHNvbGlkIGdyYXNwIG9uIHRoZSBkaWZmZXJlbnQgdHlwZXMgb2Ygam9pbnMgYW5kIGhvdyB0aGV5IHdvcmssIHdlIGNhbiBsb29rIGF0IGhvdyB0byBtYW5hZ2UgbWVzc2llciBhbmQgbW9yZSBjb21wbGV4IGRhdGFzZXRzLiBKb2luaW5nIHJlYWwtd29ybGQgZGF0YSBmcm9tIGRpZmZlcmVudCBzb3VyY2VzIG9mdGVuIHJlcXVpcmVzIGEgYml0IG9mIHRob3VnaHQgYW5kIGNsZWFuaW5nIGFoZWFkIG9mIHRpbWUuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIExlYXJuaW5nIE9iamVjdGl2ZXMNCg0KLSAgIFlvdSBrbm93IGhvdyB0byBjaGVjayBmb3IgbWlzbWF0Y2hlZCB2YWx1ZXMgYmV0d2VlbiBkYXRhZnJhbWVzDQoNCi0gICBZb3UgdW5kZXJzdGFuZCBob3cgdG8gam9pbiB1c2luZyBhIG9uZS10by1tYW55IG1hdGNoDQoNCi0gICBZb3Uga25vdyBob3cgdG8gam9pbiBvbiBtdWx0aXBsZSBrZXkgY29sdW1ucw0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgUGFja2FnZXMNCg0K4oCjIExvYWQgdGhlIHBhY2thZ2VzIG5lZWRlZCBmb3IgdGhpcyBsZXNzb24gdXNpbmcgdGhlIGNvZGUgcHJvdmlkZWQgYmVsb3c6DQoNCmBgYHtyfQ0KaWYoIXJlcXVpcmUocGFjbWFuKSkgaW5zdGFsbC5wYWNrYWdlcygicGFjbWFuIikNCnBhY21hbjo6cF9sb2FkKHRpZHl2ZXJzZSwgY291bnRyeWNvZGUpDQpgYGANCg0KIyMgUHJlLWpvaW4gZGF0YSBjbGVhbmluZzogYWRkcmVzc2luZyBkYXRhIGluY29uc2lzdGVuY2llcw0KDQrigKMgT2Z0ZW4sIGRhdGEgZnJvbSAqKmRpZmZlcmVudCBzb3VyY2VzKiogcmVxdWlyZSBwcmUtY2xlYW5pbmcgYmVmb3JlIGpvaW5pbmcuDQoNCuKAoyBSZWFzb25zIGluY2x1ZGU6IC0gU3BlbGxpbmcgZXJyb3JzIC0gRGlmZmVyZW5jZXMgaW4gY2FwaXRhbGl6YXRpb24gLSBFeHRyYSBzcGFjZXMNCg0K4oCjIFRvIGpvaW4gdmFsdWVzIHN1Y2Nlc3NmdWxseSwgdGhleSBtdXN0ICoqbWF0Y2ggcGVyZmVjdGx5KiogaW4gUi4NCg0KIyMgQSB0b3kgZXhhbXBsZQ0KDQrigKMgTGV0J3MgdXNlIG91ciBtb2NrIHBhdGllbnQgZGF0YSBmcm9tIHRoZSBmaXJzdCBsZXNzb24uDQoNCuKAoyBOb3RpY2UgdGhlIGRpZmZlcmVudCBuYW1lIGZvcm1hdHMgaW4gdGhlIGBkZW1vZ3JhcGhpY2AgYW5kIGB0ZXN0X2luZm9gIGRhdGFzZXRzLg0KDQpgYGB7cn0NCmRlbW9ncmFwaGljIDwtIHRyaWJibGUoDQogIH5uYW1lLCAgICAgfmFnZSwNCiAgIkFsaWNlIiwgICAgMjUsDQogICJCb2IiLCAgICAgIDMyLA0KICAiQ2hhcmxpZSIsICA0NSwNCikNCmRlbW9ncmFwaGljDQpgYGANCg0KYGBge3J9DQp0ZXN0X2luZm8gPC0gdHJpYmJsZSgNCiAgfm5hbWUsICB+dGVzdF9kYXRlLCAgICB+cmVzdWx0LA0KICAiYWxpY2UiLCAiMjAyMy0wNi0wNSIsICAiTmVnYXRpdmUiLA0KICAiQm9iIiwgICAiMjAyMy0wOC0xMCIsICAiUG9zaXRpdmUiLA0KICAiY2hhcmxpZSIsIjIwMjMtMDUtMDIiLCAgIk5lZ2F0aXZlIiwNCikNCnRlc3RfaW5mbw0KYGBgDQoNCuKAoyBOb3cgbGV0J3Mgam9pbiB0aGUgdHdvIGRhdGFzZXRzLg0KDQpgYGB7cn0NCmxlZnRfam9pbihkZW1vZ3JhcGhpYyx0ZXN0X2luZm8sIGJ5ID0gIm5hbWUiKQ0KYGBgDQoNCmBgYHtyfQ0KaW5uZXJfam9pbihkZW1vZ3JhcGhpYyx0ZXN0X2luZm8sIGJ5ID0gIm5hbWUiKQ0KYGBgDQoNCuKAoyBUaGUgam9pbnMgYXJlIG5vdCBwZXJmZWN0IGR1ZSB0byB0aGUgY2FzZSBkaWZmZXJlbmNlcyBpbiBuYW1lcy4NCg0K4oCjIFNvbHV0aW9uOiBDb252ZXJ0IGFsbCBuYW1lcyB0byB0aXRsZSBjYXNlIHVzaW5nIGBzdHJfdG9fdGl0bGUoKWAuDQoNCmBgYHtyfQ0KdGVzdF9pbmZvX3RpdGxlIDwtIHRlc3RfaW5mbyAlPiUNCiAgbXV0YXRlKG5hbWUgPSBzdHJfdG9fdGl0bGUobmFtZSkpICNjb252ZXJ0cyB0byB0aXRsZSBjYXNlDQoNCnRlc3RfaW5mb190aXRsZQ0KYGBgDQoNCmBgYHtyfQ0KbGVmdF9qb2luKGRlbW9ncmFwaGljLCB0ZXN0X2luZm9fdGl0bGUsIGJ5ID0gIm5hbWUiKQ0KYGBgDQoNCmBgYHtyfQ0KaW5uZXJfam9pbihkZW1vZ3JhcGhpYyx0ZXN0X2luZm9fdGl0bGUsIGJ5ID0gIm5hbWUiKQ0KYGBgDQoNCioqUFJBQ1RJQ0UgVElNRSEqKg0KDQo6OjogcHJhY3RpY2UNCg0KKihOT1RFOiBBbnN3ZXJzIGFyZSBhdCB0aGUgYm90dG9tIG9mIHRoZSBwYWdlLiBUcnkgdG8gYW5zd2VyIHRoZSBxdWVzdGlvbnMgeW91cnNlbGYgYmVmb3JlIGNoZWNraW5nLikqDQoNCiMjIyBROiBJbm5lciBKb2luIGNvdW50cmllcyB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpUaGUgZm9sbG93aW5nIHR3byBkYXRhc2V0cyBjb250YWluIGRhdGEgZm9yIEluZGlhLCBJbmRvbmVzaWEsIGFuZCB0aGUgUGhpbGlwcGluZXMuIEhvd2V2ZXIgYW4gYGlubmVyX2pvaW4oKWAgb2YgdGhlc2UgZGF0YXNldHMgcHJvZHVjZXMgbm8gb3V0cHV0LiBXaGF0IGFyZSB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiB0aGUgdmFsdWVzIGluIHRoZSBrZXkgY29sdW1ucyB0aGF0IHdvdWxkIGhhdmUgdG8gYmUgY2hhbmdlZCBiZWZvcmUgam9pbmluZyB0aGUgZGF0YXNldHM/DQoNCmBgYHtyfQ0KZGYxIDwtIHRyaWJibGUoDQogIH5Db3VudHJ5LCAgICAgfkNhcGl0YWwsDQogICJJbmRpYSIsICAgICAgIk5ldyBEZWxoaSIsDQogICJJbmRvbmVzaWEiLCAgIkpha2FydGEiLA0KICAiUGhpbGlwcGluZXMiLCAiTWFuaWxhIg0KKQ0KDQpkZjIgPC0gdHJpYmJsZSgNCiAgfkNvdW50cnksICAgICB+UG9wdWxhdGlvbiwgICB+TGlmZV9FeHBlY3RhbmN5LA0KICAiSW5kaWEgIiwgICAgICAxMzkzMDAwMDAwLCAgIDY5LjcsDQogICJpbmRvbmVzaWEiLCAgIDI3MzUwMDAwMCwgICAgNzEuNywNCiAgIlBoaWxpcGluZXMiLCAgMTEzMDAwMDAwLCAgICA3Mi43DQopDQoNCmRmMiA8LSBkZjIgJT4lDQogIG11dGF0ZShDb3VudHJ5ID0gc3RyX3RyaW0oQ291bnRyeSkpDQoNCmRmMiA8LSBkZjIgJT4lDQogIG11dGF0ZShDb3VudHJ5ID0gc3RyX3RyaW0oQ291bnRyeSkgJT4lDQogICAgICAgICAgIHN0cl90b190aXRsZSgpICU+JQ0KICAgICAgICAgICBzdHJfcmVwbGFjZSgiUGhpbGlwaW5lcyIsICJQaGlsaXBwaW5lcyIpKQ0KICAgICAgICAgDQoNCmlubmVyX2pvaW4oZGYxLCBkZjIsIGJ5ID0gIkNvdW50cnkiKQ0KYGBgDQo6OjoNCg0KIyMgUmVhbCBEYXRhIEV4YW1wbGUgMTogS2V5IFR5cG9zDQoNCuKAoyBXb3JraW5nIHdpdGggc21hbGwgZGF0YXNldHMgbWFrZXMgaXQgZWFzeSB0byBzcG90IGtleSBkaXNjcmVwYW5jaWVzDQoNCuKAoyBCdXQsIGhvdyBhYm91dCBkZWFsaW5nIHdpdGggbGFyZ2VyIGRhdGFzZXRzPw0KDQrigKMgTGV0J3MgZXhwbG9yZSB0aGlzIHdpdGggdHdvIHJlYWwtd29ybGQgZGF0YXNldHMgb24gVEIgaW4gSW5kaWENCg0K4oCjIFRoZSBmaXJzdCBkYXRhc2V0OiBUQiBub3RpZmljYXRpb25zIGluIDIwMjIgZm9yIGFsbCAzNiBJbmRpYW4gc3RhdGVzIGFuZCBVbmlvbiBUZXJyaXRvcmllcw0KDQrigKMgU291cmNlOiBbR292ZXJubWVudCBvZiBJbmRpYSBUdWJlcmN1bG9zaXMgUmVwb3J0XShodHRwczovL2RhdGEuZ292LmluL2NhdGFsb2cvaW5kaWEtdHViZXJjdWxvc2lzLXJlcG9ydC0yMDIzKQ0KDQpgYGB7cn0NCnRiX25vdGlmcyA8LSByZWFkX2NzdihoZXJlKCJkYXRhL25vdGlmX1RCX2luZGlhX21vZGlmaWVkLmNzdiIpKQ0KDQp0Yl9ub3RpZnNfcHVibGljIDwtIHRiX25vdGlmcyAlPiUgDQogIGZpbHRlcihoY190eXBlID09ICJwdWJsaWMiKSAlPiUgI3dlIHdhbnQgb25seSBwdWJsaWMgc3lzdGVtcyBmb3Igbm93DQogIHNlbGVjdCgtaGNfdHlwZSkNCg0KdGJfbm90aWZzX3B1YmxpYw0KYGBgDQoNCuKAoyBUaGUgc2Vjb25kIGRhdGFzZXQ6IENPVklEIHNjcmVlbmluZyBhbW9uZyBUQiBjYXNlcyBmb3IgMzYgSW5kaWFuIHN0YXRlcw0KDQrigKMgQWxzbyB0YWtlbiBmcm9tIHRoZSBzYW1lIFtUQiBSZXBvcnRdKGh0dHBzOi8vZGF0YS5nb3YuaW4vY2F0YWxvZy9pbmRpYS10dWJlcmN1bG9zaXMtcmVwb3J0LTIwMjMpDQoNCmBgYHtyfQ0KY292aWRfc2NyZWVuaW5nIDwtIHJlYWRfY3N2KGhlcmUoImRhdGEvQ09WSURfaW5kaWFfbW9kaWZpZWQuY3N2IikpIA0KDQpjb3ZpZF9zY3JlZW5pbmdfcHVibGljIDwtIGNvdmlkX3NjcmVlbmluZyAlPiUgDQogIGZpbHRlcihoY190eXBlID09ICJwdWJsaWMiKSAlPiUgI3dlIHdhbnQgb25seSBwdWJsaWMgc3lzdGVtcyBmb3Igbm93DQogIHNlbGVjdCgtaGNfdHlwZSkNCg0KY292aWRfc2NyZWVuaW5nX3B1YmxpYw0KYGBgDQoNCuKAoyBPYmplY3RpdmU6IEpvaW4gdGhlc2UgZGF0YXNldHMgdG8gY2FsY3VsYXRlIHRoZSBwZXJjZW50YWdlIG9mIFRCIHBhdGllbnRzIGluIGVhY2ggc3RhdGUgd2hvIHRlc3RlZCBwb3NpdGl2ZSBmb3IgQ09WSUQtMTkNCg0K4oCjIExldCdzIGF0dGVtcHQgYW4gYGlubmVyX2pvaW4oKWA6DQoNCmBgYHtyfQ0KdGJfbm90aWZzX2FuZF9jb3ZpZF9zY3JlZW5pbmcgPC0gDQogIGlubmVyX2pvaW4odGJfbm90aWZzX3B1YmxpYywgY292aWRfc2NyZWVuaW5nX3B1YmxpYykNCg0KdGJfbm90aWZzX2FuZF9jb3ZpZF9zY3JlZW5pbmcNCmBgYA0KDQrigKMgTmV4dCwgcGVyZm9ybSB0aGUgcGVyY2VudGFnZSBjYWxjdWxhdGlvbjoNCg0KYGBge3J9DQp0Yl9ub3RpZnNfYW5kX2NvdmlkX3NjcmVlbmluZyAlPiUgDQogIG11dGF0ZShwY3RfY292aWRfcG9zID0gMTAwICogIHRiX2NvdmlkX3Bvcy90Yl9ub3RpZl9jb3VudCkgDQpgYGANCg0K4oCjIE9ic2VydmF0aW9uOiBXZSBub3cgb25seSBoYXZlIDMyIHJvd3MgaW5zdGVhZCBvZiAzNi4gV2h5Pw0KDQrigKMgVGhlcmUgYXJlICJrZXkgdHlwb3MiIGNhdXNpbmcgbWlzbWF0Y2hlcyBkdXJpbmcgdGhlIGpvaW4NCg0K4oCjIEtleSBUeXBvczogU3BlbGxpbmcvZm9ybWF0dGluZyBpbmNvbnNpc3RlbmNpZXMgaW4ga2V5IGNvbHVtbnMgYWNyb3NzIGRhdGFzZXRzDQoNCuKAoyBFeGFtcGxlOiBPbmUgZGF0YXNldCBsaXN0cyAiTmV3IERlbGhpIiB3aGlsZSB0aGUgb3RoZXIgbGlzdHMgIkRlbGhpIg0KDQrigKMgVGhlc2UgaW5jb25zaXN0ZW5jaWVzIHByZXZlbnQgcHJvcGVyIG1hdGNoaW5nIGFuZCByZXN1bHQgaW4gZGF0YSBsb3NzDQoNCjo6OiB2b2NhYg0KKipWT0NBQiBUSU1FICEqKg0KDQrigKMgIktleSI6IENvbHVtbihzKSB1c2VkIHRvIG1hdGNoIHJvd3MgYWNyb3NzIGRhdGFzZXRzIGluIGEgam9pbg0KDQrigKMgIktleSBUeXBvcyI6IFNwZWxsaW5nIG9yIGZvcm1hdHRpbmcgaW5jb25zaXN0ZW5jaWVzIGluIGtleSBjb2x1bW5zIGFjcm9zcyBkYXRhc2V0cw0KOjo6DQoNCiMjIElkZW50aWZ5aW5nIHVubWF0Y2hlZCB2YWx1ZXMgd2l0aCBgc2V0ZGlmZigpYA0KDQrigKMgV2Ugd2FudCB0byAqKmlkZW50aWZ5IGtleSB0eXBvcyoqIGluIG91ciBkYXRhDQoNCuKAoyBGb3IgdGhpcywgd2UgY2FuIHVzZSB0aGUgYHNldGRpZmYoKWAgZnVuY3Rpb24NCg0K4oCjIExldCdzIHN0YXJ0IGJ5IGNvbXBhcmluZyB0aGUgYHN0YXRlYCB2YWx1ZXMgZnJvbSB0d28gZGF0YWZyYW1lczogYHRiX25vdGlmc19wdWJsaWNgIGFuZCBgY292aWRfc2NyZWVuaW5nX3B1YmxpY2ANCg0KYGBge3J9DQpzZXRkaWZmKHRiX25vdGlmc19wdWJsaWMkc3RhdGUsIGNvdmlkX3NjcmVlbmluZ19wdWJsaWMkc3RhdGUpDQpgYGANCg0K4oCjIEJ5IHB1dHRpbmcgdGhlIGB0Yl9ub3RpZnNfcHVibGljYCBkYXRhc2V0IGZpcnN0LCB3ZSBhc2s6DQoNCuKAoyAiV2hpY2ggdmFsdWVzIGFyZSBpbiBgdGJfbm90aWZzX3B1YmxpY2AgYnV0ICpub3QqIGluIGBjb3ZpZF9zY3JlZW5pbmdfcHVibGljYD8iDQoNCuKAoyBXZSBzaG91bGQgYWxzbyBjaGVjayB0aGUgcmV2ZXJzZSBvcmRlcjoNCg0K4oCjICJXaGljaCB2YWx1ZXMgYXJlIGluIGBjb3ZpZF9zY3JlZW5pbmdfcHVibGljYCBidXQgKm5vdCogaW4gYHRiX25vdGlmc19wdWJsaWNgPyINCg0KYGBge3J9DQpzZXRkaWZmKGNvdmlkX3NjcmVlbmluZ19wdWJsaWMkc3RhdGUsIHRiX25vdGlmc19wdWJsaWMkc3RhdGUpDQpgYGANCg0K4oCjIFdlIGZvdW5kIHZhbHVlcyBpbiBgY292aWRfc2NyZWVuaW5nX3B1YmxpY2AgdGhhdCBoYXZlIHNwZWxsaW5nIGVycm9ycyBvciBhcmUgd3JpdHRlbiBkaWZmZXJlbnRseSB0aGFuIGluIGB0Yl9ub3RpZnNfcHVibGljYA0KDQrigKMgTGV0J3MgY2xlYW4gdXAgYGNvdmlkX3NjcmVlbmluZ19wdWJsaWNgIHVzaW5nIGBjYXNlX3doZW4oKWANCg0KYGBge3J9DQpjb3ZpZF9zY3JlZW5pbmdfcHVibGljX2NsZWFuIDwtIGNvdmlkX3NjcmVlbmluZ19wdWJsaWMgJT4lIA0KICBtdXRhdGUoc3RhdGUgPSANCiAgICAgICAgICAgY2FzZV93aGVuKHN0YXRlID09ICJBcnVuYWNoYWxQcmFkZXNoIiB+ICJBcnVuYWNoYWwgUHJhZGVzaCIsIHN0YXRlID09ICJ0YW1pbCBuYWR1IiB+ICJUYW1pbCBOYWR1IiwgDQogICAgICAgICAgICAgICAgICAgICBzdGF0ZSA9PSAiVHJpIHB1cmEiIH4gIlRyaXB1cmEiLCANCiAgICAgICAgICAgICAgICAgICAgIHN0YXRlID09ICJEYWRyYSAmIE5hZ2FyIEhhdmVsaSBhbmQgRGFtYW4gJiBEaXUiIH4gIkRhZHJhIGFuZCBOYWdhciBIYXZlbGkgYW5kIERhbWFuIGFuZCBEaXUiLA0KICAgICAgICAgICAgICAgICAgICAgVFJVRSB+IHN0YXRlKSkNCg0Kc2V0ZGlmZih0Yl9ub3RpZnNfcHVibGljJHN0YXRlLCBjb3ZpZF9zY3JlZW5pbmdfcHVibGljX2NsZWFuJHN0YXRlKQ0KDQpzZXRkaWZmKGNvdmlkX3NjcmVlbmluZ19wdWJsaWNfY2xlYW4kc3RhdGUsIHRiX25vdGlmc19wdWJsaWMkc3RhdGUpDQpgYGANCg0K4oCjIE5vdywgd2UgaGF2ZSBubyBkaWZmZXJlbmNlcyBpbiB0aGUgcmVnaW9uJ3MgbmFtZXMNCg0K4oCjIFdlIGNhbiBqb2luIG91ciBkYXRhc2V0czoNCg0KYGBge3IsIHJlbmRlciA9IHJlYWN0YWJsZV81X3Jvd3N9DQppbm5lcl9qb2luKHRiX25vdGlmc19wdWJsaWMsIGNvdmlkX3NjcmVlbmluZ19wdWJsaWNfY2xlYW4pDQpgYGANCg0KIyMgSWRlbnRpZnlpbmcgdW5tYXRjaGVkIHZhbHVlcyB3aXRoIGBhbnRpam9pbigpYA0KDQrigKMgVGhlIGBhbnRpX2pvaW4oKWAgZnVuY3Rpb24gaW4ge2RwbHlyfSBpcyBhbm90aGVyIHdheSB0byBpZGVudGlmeSBkaXNjcmVwYW5jaWVzDQoNCuKAoyBJdCByZXR1cm5zIHJvd3MgZnJvbSB0aGUgZmlyc3QgZGF0YWZyYW1lIHdoZXJlIHRoZSBrZXkgdmFsdWVzICoqZG9uJ3QgbWF0Y2gqKiB0aGUgc2Vjb25kIGRhdGFmcmFtZQ0KDQrigKMgTGV0J3MgZmluZCB1bm1hdGNoZWQgYHN0YXRlYCB2YWx1ZXMgaW4gYHRiX25vdGlmc19wdWJsaWNgIGNvbXBhcmVkIHRvIGBjb3ZpZF9zY3JlZW5pbmdfcHVibGljYA0KDQpgYGB7ciwgcmVuZGVyID0gcmVhY3RhYmxlXzVfcm93c30NCmFudGlfam9pbih0Yl9ub3RpZnNfcHVibGljLCBjb3ZpZF9zY3JlZW5pbmdfcHVibGljKQ0KYGBgDQoNCuKAoyBBbmQgdmljZSB2ZXJzYSwgZm9yIHZhbHVlcyBpbiBgY292aWRfc2NyZWVuaW5nX3B1YmxpY2AgYnV0IG5vdCBpbiBgdGJfbm90aWZzX3B1YmxpY2A6DQoNCmBgYHtyLCByZW5kZXIgPSByZWFjdGFibGVfNV9yb3dzfQ0KYW50aV9qb2luKGNvdmlkX3NjcmVlbmluZ19wdWJsaWMsIHRiX25vdGlmc19wdWJsaWMpDQpgYGANCg0K4oCjIFRoaXMgbWV0aG9kIHByb3ZpZGVzIG1vcmUgY29udGV4dCBmb3IgZGlzY3JlcGFuY2llcw0KDQrigKMgQWZ0ZXIgaWRlbnRpZnlpbmcsIGZpeCB0aGUgZXJyb3JzIHdpdGggYG11dGF0ZSgpYCBhbmQgcHJvY2VlZCB3aXRoIHRoZSBqb2luDQoNCioqUFJBQ1RJQ0UgVElNRSAhKioNCg0KOjo6IHByYWN0aWNlDQojIyMgUTogQ2hlY2sgYW5kIGZpeCB0eXBvcyBiZWZvcmUgam9pbiB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpUaGUgZm9sbG93aW5nIGRhdGFmcmFtZSwgYWxzbyB0YWtlbiBmcm9tIHRoZSBbVEIgUmVwb3J0XShodHRwczovL2RhdGEuZ292LmluL2NhdGFsb2cvaW5kaWEtdHViZXJjdWxvc2lzLXJlcG9ydC0yMDIzKSwgY29udGFpbnMgaW5mb3JtYXRpb24gb24gdGhlIG51bWJlciBvZiBwZWRpYXRyaWMgVEIgY2FzZXMgYW5kIHRoZSBudW1iZXIgb2YgcGVkaWF0cmljIHBhdGllbnRzIGluaXRpYXRlZCBvbiB0cmVhdG1lbnQuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHJlbmRlciA9IHJlYWN0YWJsZV81X3Jvd3N9DQpjaGlsZCA8LSByZWFkX2NzdihoZXJlKCJkYXRhL2NoaWxkX1RCX2luZGlhX21vZGlmaWVkLmNzdiIpKQ0KDQpjaGlsZF9wdWJsaWMgPC0gY2hpbGQgJT4lIA0KICBmaWx0ZXIoaGNfdHlwZSA9PSAicHVibGljIikgJT4lIA0KICBzZWxlY3QoLWhjX3R5cGUpDQoNCmNoaWxkX3B1YmxpYyANCmBgYA0KDQoxLiAgVXNpbmcgYHNldF9kaWZmKClgIG9yIGBhbnRpX2pvaW4oKWAgY29tcGFyZSB0aGUga2V5IHZhbHVlcyBmcm9tIHRoZSBgY2hpbGRfcHVibGljYCBkYXRhZnJhbWUgd2l0aCB0aG9zZSBmcm9tIHRoZSBgdGJfbm90aWZzX3B1YmxpY2AgZGF0YWZyYW1lLCB3aGljaCB3YXMgZGVmaW5lZCBwcmV2aW91c2x5DQoyLiAgTWFrZSBhbnkgbmVjZXNzYXJ5IGNoYW5nZXMgdG8gdGhlIGBjaGlsZF9wdWJsaWNgIGRhdGFmcmFtZSB0byBlbnN1cmUgdGhhdCB0aGUgdmFsdWVzIG1hdGNoLg0KMy4gIEpvaW4gdGhlIHR3byBkYXRhc2V0cy4NCjQuICBJZGVudGlmeSB3aGljaCB0d28gcmVnaW9ucyBoYXZlIHRoZSBoaWdoZXN0IHByb3BvcnRpb24gb2YgVEIgY2FzZXMgaW4gY2hpbGRyZW4uDQoNCmBgYHtyfQ0Kc2V0ZGlmZih0Yl9ub3RpZnNfcHVibGljJHN0YXRlLCBjaGlsZF9wdWJsaWMkc3RhdGUpDQoNCnNldGRpZmYoY2hpbGRfcHVibGljJHN0YXRlLCB0Yl9ub3RpZnNfcHVibGljJHN0YXRlKQ0KYGBgDQpgYGB7cn0NCmNoaWxkX3B1YmxpY19jbGVhbiA8LSBjaGlsZF9wdWJsaWMgJT4lIA0KICBtdXRhdGUoc3RhdGUgPSANCiAgICAgICAgICAgY2FzZV93aGVuKHN0YXRlID09ICJBcnVuYWNoYWxQcmFkZXNoIiB+ICJBcnVuYWNoYWwgUHJhZGVzaCIsIA0KICAgICAgICAgICAgICAgICAgICAgc3RhdGUgPT0gICJKYW1tdSBhbmQgS2FzaG1pciIgfiAiSmFtbXUgJiBLYXNobWlyIiwgDQogICAgICAgICAgICAgICAgICAgICBzdGF0ZSA9PSAia2VyYWxhIiB+ICAiS2VyYWxhIiwNCiAgICAgICAgICAgICAgICAgICAgIHN0YXRlID09ICJQb25kaWNoZXJyeSIgfiAiUHVkdWNoZXJyeSIsDQogICAgICAgICAgICAgICAgICAgICBUUlVFIH4gc3RhdGUpKQ0KDQpzZXRkaWZmKHRiX25vdGlmc19wdWJsaWMkc3RhdGUsIGNoaWxkX3B1YmxpY19jbGVhbiRzdGF0ZSkNCg0Kc2V0ZGlmZihjaGlsZF9wdWJsaWNfY2xlYW4kc3RhdGUsIHRiX25vdGlmc19wdWJsaWMkc3RhdGUpDQpgYGANCmBgYHtyfQ0KdGJfY2hpbGRfcHVibGljIDwtIGlubmVyX2pvaW4odGJfbm90aWZzX3B1YmxpYywgY2hpbGRfcHVibGljX2NsZWFuKQ0KDQp0Yl9jaGlsZF9wdWJsaWMgJT4lIA0KICBtdXRhdGUocHJvcF90YiA9IDEwMCAqIHRiX2NoaWxkX25vdGlmcy90Yl9ub3RpZl9jb3VudCkgJT4lIA0KICBhcnJhbmdlKC1wcm9wX3RiKSAlPiUgDQogIGhlYWQoMikNCmBgYA0KDQo6OjoNCg0KIyMgUmVhbCBEYXRhIEV4YW1wbGUgMjogS2V5IFR5cG9zIGFuZCBEYXRhIEdhcHMNCg0K4oCjICoqS2V5IHR5cG9zKiogYW5kICoqZm9ybWF0dGluZyBpbmNvbnNpc3RlbmNpZXMqKiBjYW4gaGluZGVyIHN1Y2Nlc3NmdWwgam9pbnMgYmV0d2VlbiBkYXRhc2V0cy4NCg0K4oCjIExldCdzIGV4cGxvcmUgYSBtb3JlIGNvbXBsZXggc2NlbmFyaW8gaW52b2x2aW5nIHRoZSBgY292aWRfc2NyZWVuaW5nX3B1YmxpY2AgZGF0YXNldC4NCg0KYGBge3J9DQpjb3ZpZF9zY3JlZW5pbmdfcHVibGljDQpgYGANCg0K4oCjIE91ciBnb2FsIGlzIHRvIGVucmljaCB0aGlzIGRhdGFzZXQgd2l0aCAqKnpvbmluZyBpbmZvcm1hdGlvbioqIGZyb20gdGhlIGByZWdpb25zYCBkYXRhc2V0Lg0KDQpgYGB7cn0NCnJlZ2lvbnMgPC0gcmVhZF9jc3YoaGVyZSgiZGF0YS9yZWdpb25fZGF0YV9pbmRpYS5jc3YiKSkNCnJlZ2lvbnMNCmBgYA0KDQrigKMgQ29sdW1ucyBpbiBgcmVnaW9uc2AgaW5jbHVkZSBgem9uYWxfY291bmNpbGAsIGBzdWJkaXZpc2lvbl9jYXRlZ29yeWAsIGFuZCBgc3RhdGVfVVRgLg0KDQrigKMgV2UnbGwgdXNlIGEgKipsZWZ0IGpvaW4qKiB0byBtZXJnZSB3aXRob3V0IGxvc2luZyByb3dzIGZyb20gYGNvdmlkX3NjcmVlbmluZ19wdWJsaWNgLg0KDQpgYGB7cn0NCmNvdmlkX3JlZ2lvbnMgPC0gbGVmdF9qb2luKGNvdmlkX3NjcmVlbmluZ19wdWJsaWMsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVnaW9ucywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9IGMoInN0YXRlIiA9ICJzdGF0ZV9VVCIpKQ0KDQpjb3ZpZF9yZWdpb25zDQpgYGANCg0K4oCjIEFmdGVyIHRoZSBqb2luLCBzb21lIGVudHJpZXMgYXJlICoqbWlzc2luZyB6b25pbmcgaW5mb3JtYXRpb24qKi4NCg0KYGBge3J9DQpjb3ZpZF9yZWdpb25zICU+JSANCiAgZmlsdGVyKGlzLm5hKHpvbmFsX2NvdW5jaWwpKQ0KYGBgDQoNCuKAoyBUbyB1bmRlcnN0YW5kIHdoeSwgd2UnbGwgaW52ZXN0aWdhdGUgdXNpbmcgYGFudGlfam9pbigpYC4NCg0KYGBge3J9DQphbnRpX2pvaW4ocmVnaW9ucywgY292aWRfc2NyZWVuaW5nX3B1YmxpYywgYnkgPSBjKCJzdGF0ZV9VVCIgPSAic3RhdGUiKSkNCmBgYA0KDQrigKMgMyBzdGF0ZXMgYXJlIHByZXNlbnQgaW4gYHJlZ2lvbnNgIGJ1dCBhYnNlbnQgaW4gYGNvdmlkX3NjcmVlbmluZ19wdWJsaWNgLg0KDQrigKMgTm93LCBsZXQncyByZXZlcnNlIHRoZSBjaGVjay4NCg0KYGBge3J9DQphbnRpX2pvaW4oY292aWRfc2NyZWVuaW5nX3B1YmxpYywgcmVnaW9ucywgYnkgPSBjKCJzdGF0ZSIgPSAic3RhdGVfVVQiKSkNCmBgYA0KDQrigKMgU29tZSBtaXNtYXRjaGVzIGFyZSBkdWUgdG8gKiprZXkgdHlwb3MqKiwgd2hpbGUgb3RoZXJzIGFyZSAqKmFic2VudCBmcm9tIHRoZSBgcmVnaW9uc2AgZGF0YXNldCoqLg0KDQrigKMgVG8gY29ycmVjdCB0eXBvcywgd2UnbGwgYXBwbHkgc2ltaWxhciBmaXhlcyBhcyBpbiBhIHByZXZpb3VzIGV4YW1wbGUuDQoNCmBgYHtyfQ0KIyBDb3JyZWN0IHN0YXRlIHR5cG9zOg0KY292aWRfc2NyZWVuaW5nX3B1YmxpY19maXhlZCA8LSBjb3ZpZF9zY3JlZW5pbmdfcHVibGljICU+JSANCiAgbXV0YXRlKHN0YXRlID0gDQogICAgICAgICAgIGNhc2Vfd2hlbihzdGF0ZSA9PSAiQXJ1bmFjaGFsUHJhZGVzaCIgfiAiQXJ1bmFjaGFsIFByYWRlc2giLCANCiAgICAgICAgICAgICAgICAgICAgIHN0YXRlID09ICJUcmkgcHVyYSIgfiAiVHJpcHVyYSIsIA0KICAgICAgICAgICAgICAgICAgICAgc3RhdGUgPT0gIkRhZHJhICYgTmFnYXIgSGF2ZWxpIGFuZCBEYW1hbiAmIERpdSIgfiAiRGFkcmEgYW5kIE5hZ2FyIEhhdmVsaSBhbmQgRGFtYW4gYW5kIERpdSIsIA0KICAgICAgICAgICAgICAgICAgICAgVFJVRSB+IHN0YXRlKSkNCmBgYA0KDQrigKMgQWZ0ZXIgYXBwbHlpbmcgdGhlIGZpeGVzLCB3ZSBwZXJmb3JtIGFub3RoZXIgbGVmdCBqb2luLg0KDQpgYGB7cn0NCmNvdmlkX3JlZ2lvbnNfam9pbmVkX2ZpeGVkIDwtIGxlZnRfam9pbihjb3ZpZF9zY3JlZW5pbmdfcHVibGljX2ZpeGVkLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWdpb25zLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9IGMoInN0YXRlIiA9ICJzdGF0ZV9VVCIpKQ0KDQpjb3ZpZF9yZWdpb25zX2pvaW5lZF9maXhlZA0KYGBgDQoNCuKAoyBDaGVjayBmb3IgZW50cmllcyBzdGlsbCBtaXNzaW5nIHpvbmluZyBpbmZvcm1hdGlvbi4NCg0KYGBge3J9DQojIENoZWNrIGZvciBtaXNzaW5nIHpvbmFsIGNvdW5jaWwgaW5mb3JtYXRpb24gYWdhaW46DQpjb3ZpZF9yZWdpb25zX2pvaW5lZF9maXhlZCAlPiUgDQogIGZpbHRlcihpcy5uYSh6b25hbF9jb3VuY2lsKSkNCmBgYA0KDQrigKMgU29tZSByZWdpb25zIHdlcmUgbm90IGluY2x1ZGVkIGluIHRoZSBgcmVnaW9uc2AgZGF0YXNldC4NCg0K4oCjIFRoaXMgZXhhbXBsZSBoaWdobGlnaHRzIHRoZSBjaGFsbGVuZ2VzIG9mIGVuc3VyaW5nICoqbm8gZGF0YSBsb3NzIGR1cmluZyBqb2lucyoqLg0KDQoqKlJFTUVNQkVSISoqDQoNCuKAoyBDb3JyZWN0aW5nIHR5cG9ncmFwaGljYWwgZXJyb3JzIGZvciBzdWNjZXNzZnVsIGpvaW5zIGlzIGEgKipjb21wbGV4IHRhc2sqKi4NCg0K4oCjIEZ1enp5IG1hdGNoaW5nIG1heSBiZSBuZWNlc3NhcnkgZm9yICoqaW1wZXJmZWN0IHN0cmluZyBjb21wYXJpc29ucyoqLg0KDQrigKMgRXhwbG9yZSB0aGUgYHtmdXp6eWpvaW59YCBwYWNrYWdlIGluIFIgZm9yIHNvbHV0aW9ucy4NCg0KOjo6IHByYWN0aWNlDQojIyMgUTogTWVyZ2luZyBUQiBDYXNlcyB3aXRoIEdlb2dyYXBoaWMgRGF0YSB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpSdW4gdGhlIGNvZGUgYmVsbG93IHRvIGRlZmluZSB0d28gZGF0YXNldHMuDQoNClRoZSBmaXJzdCwgYHRvcF90Yl9jYXNlc19raWRzYCByZWNvcmRzIHRoZSB0b3AgMjAgY291bnRyaWVzIHdpdGggdGhlIGhpZ2hlc3QgaW5jaWRlbmNlIG9mIHR1YmVyY3Vsb3NpcyAoVEIpIGluIGNoaWxkcmVuIGZvciB0aGUgeWVhciAyMDEyOg0KDQpgYGB7ciwgcmVuZGVyID0gcmVhY3RhYmxlXzVfcm93c30NCnRvcF90Yl9jYXNlc19raWRzIDwtIHRpZHlyOjp3aG8gJT4lIA0KICBmaWx0ZXIoeWVhciA9PSAyMDEyKSAlPiUgDQogIHRyYW5zbXV0ZShjb3VudHJ5LCBpc28zLCB0Yl9jYXNlc19zbWVhcl8wXzE0ID0gbmV3X3NwX20wMTQgKyBuZXdfc3BfZjAxNCkgJT4lIA0KICBhcnJhbmdlKGRlc2ModGJfY2FzZXNfc21lYXJfMF8xNCkpICU+JSANCiAgaGVhZCgyMCkNCg0KdG9wX3RiX2Nhc2VzX2tpZHMNCmBgYA0KDQpBbmQgYGNvdW50cnlfcmVnaW9uc2AgbGlzdHMgY291bnRyaWVzIGFsb25nIHdpdGggdGhlaXIgcmVzcGVjdGl2ZSByZWdpb25zIGFuZCBjb250aW5lbnRzOg0KDQpgYGB7ciwgcmVuZGVyID0gcmVhY3RhYmxlXzVfcm93c30NCmNvdW50cnlfcmVnaW9ucyA8LSBjb3VudHJ5Y29kZTo6Y29kZWxpc3QgJT4lIA0KICBzZWxlY3QoY291bnRyeV9uYW1lID0gaXNvLm5hbWUuZW4sIGlzbzNjLCByZWdpb24pICU+JSANCiAgZmlsdGVyKGNvbXBsZXRlLmNhc2VzKGNvdW50cnlfbmFtZSwgcmVnaW9uKSkNCg0KY291bnRyeV9yZWdpb25zDQpgYGANCg0KWW91ciB0YXNrIGlzIHRvIGF1Z21lbnQgdGhlIFRCIGNhc2VzIGRhdGEgd2l0aCB0aGUgcmVnaW9uIGFuZCBjb250aW5lbnQgaW5mb3JtYXRpb24gd2l0aG91dCBsb3NpbmcgYW55IHJlbGV2YW50IGRhdGEuDQoNCjEuICBQZXJmb3JtIGEgYGxlZnRfam9pbmAgb2YgYHRvcF90Yl9jYXNlc19raWRzYCB3aXRoIGBjb3VudHJ5X3JlZ2lvbnNgIHdpdGggdGhlIGNvdW50cnkgbmFtZXMgYXMgdGhlIGtleS4gSWRlbnRpZnkgd2hpY2ggZml2ZSBjb3VudHJpZXMgZmFpbCB0byBtYXRjaCBjb3JyZWN0bHkuDQoNCmBgYHtyIGV2YWwgPSBGfQ0KdG9wX3RiX2NvdW50cnlfcmVnaW9ucyA8LSBsZWZ0X2pvaW4odG9wX3RiX2Nhc2VzX2tpZHMsIGNvdW50cnlfcmVnaW9ucywgYnkgPSBjKCJjb3VudHJ5IiA9ICJjb3VudHJ5X25hbWUiKSkNCg0KdG9wX3RiX2NvdW50cnlfcmVnaW9ucyAlPiUgDQogIGZpbHRlcihpcy5uYShyZWdpb24pKQ0KYGBgDQoNCjIuICBVc2luZyB0aGUgY29kZSBiZWxvdywgYW1lbmQgdGhlIGNvdW50cnkgbmFtZXMgaW4gYHRvcF90Yl9jYXNlc19raWRzYCB1c2luZyBgY2FzZV93aGVuYCB0byByZWN0aWZ5IG1pc21hdGNoZXM6DQoNCmBgYHtyfQ0KdG9wX3RiX2Nhc2VzX2tpZHNfZml4ZWQgPC0gdG9wX3RiX2Nhc2VzX2tpZHMgJT4lDQogIG11dGF0ZShjb3VudHJ5ID0gY2FzZV93aGVuKA0KICAgIGNvdW50cnkgPT0gIkRlbW9jcmF0aWMgUmVwdWJsaWMgb2YgdGhlIENvbmdvIiB+ICJDb25nbywgRGVtb2NyYXRpYyBSZXB1YmxpYyBvZiB0aGUiLA0KICAgIGNvdW50cnkgPT0gIlBoaWxpcHBpbmVzIiB+ICJQaGlsaXBwaW5lcyAodGhlKSIsDQogICAgY291bnRyeSA9PSAiRGVtb2NyYXRpYyBQZW9wbGUncyBSZXB1YmxpYyBvZiBLb3JlYSIgfiAiS29yZWEsIERlbW9jcmF0aWMgUGVvcGxlJ3MgUmVwdWJsaWMgb2YiLA0KICAgIGNvdW50cnkgPT0gIlVuaXRlZCBSZXB1YmxpYyBvZiBUYW56YW5pYSIgfiAiVGFuemFuaWEsIFVuaXRlZCBSZXB1YmxpYyBvZiIsDQogICAgY291bnRyeSA9PSAiQ290ZSBkJ0l2b2lyZSIgfiAiQ8O0dGUgZCdJdm9pcmUiLA0KICAgIFRSVUUgfiBjb3VudHJ5IA0KICApKQ0KDQp0b3BfdGJfY2FzZXNfa2lkc19maXhlZA0KYGBgDQoNCk5vdyBhdHRlbXB0IHRoZSBqb2luIGFnYWluIHVzaW5nIHRoZSByZXZpc2VkIGRhdGFzZXQuDQoNCmBgYHtyIGV2YWwgPSBGfQ0KbGVmdF9qb2luKHRvcF90Yl9jYXNlc19raWRzX2ZpeGVkLCBjb3VudHJ5X3JlZ2lvbnMsIGJ5ID0gYygiY291bnRyeSIgPSAiY291bnRyeV9uYW1lIikpDQpgYGANCg0KMy4gIFRyeSBhbm90aGVyIGBsZWZ0X2pvaW5gLCBidXQgdGhpcyB0aW1lIHVzZSB0aGUgdGhyZWUtbGV0dGVyIElTTyBjb2RlIGFzIHRoZSBrZXkuIERvIHRob3NlIGluaXRpYWwgZml2ZSBjb3VudHJpZXMgbm93IGFsaWduIHByb3Blcmx5Pw0KDQpgYGB7ciBldmFsID0gRn0NCmxlZnRfam9pbih0b3BfdGJfY2FzZXNfa2lkcywgY291bnRyeV9yZWdpb25zLCBieSA9IGMoImlzbzMiID0gImlzbzNjIikpDQpgYGANCg0KNC4gIFdoYXQgaXMgdGhlIGFkdmFudGFnZSBvZiB1dGlsaXppbmcgSVNPIGNvZGVzIHdoZW4gcmVjb3JkaW5nIGFuZCBzdG9yaW5nIGNvdW50cnkgaW5mb3JtYXRpb24/DQoNCioqSVNPIGNvZGVzIGltcHJvdmUgYWNjdXJhY3ksIGludGVyb3BlcmFiaWxpdHksIGFuZCBlZmZpY2llbmN5IHdoZW4gd29ya2luZyB3aXRoIGNvdW50cnkgaW5mb3JtYXRpb24sIG1ha2luZyB0aGVtIGEgYmVzdCBwcmFjdGljZSBmb3IgbWFuYWdpbmcgaW50ZXJuYXRpb25hbCBkYXRhLioqDQo6OjoNCg0KIyMgT25lLXRvLW1hbnkgcmVsYXRpb25zaGlwcw0KDQrigKMgV2UndmUgbWFpbmx5IGxvb2tlZCBhdCAqKm9uZS10by1vbmUgam9pbnMqKi4NCg0K4oCjIEJ1dCB3aGF0IGFib3V0ICoqb25lLXRvLW1hbnkgam9pbnMqKj8NCg0K4oCjIEhlcmUsIGFuIG9ic2VydmF0aW9uIGluIG9uZSBkYXRhZnJhbWUgY29ycmVzcG9uZHMgdG8gbXVsdGlwbGUgb2JzZXJ2YXRpb25zIGluIHRoZSBvdGhlci4NCg0KIVtUaGUgY29uY2VwdCBvZiBvbmUtdG8tbWFueSByZWxhdGlvbnNoaXBzXShpbWFnZXMvb25lX3RvX21hbnkuanBnKQ0KDQrigKMgVG8gaWxsdXN0cmF0ZSwgbGV0J3MgcmV0dXJuIHRvIG91ciBwYXRpZW50cyBhbmQgdGhlaXIgQ09WSUQgdGVzdCBkYXRhLg0KDQrigKMgSW1hZ2luZSBgQWxpY2VgIGFuZCBgWGF2aWVyYCBnb3QgdGVzdGVkIG11bHRpcGxlIHRpbWVzIGZvciBDT1ZJRC4NCg0KYGBge3J9DQp0ZXN0X2luZm9fbWFueSA8LSB0cmliYmxlKA0KICB+bmFtZSwgICAgfnRlc3RfZGF0ZSwgfnJlc3VsdCwNCiAgIkFsaWNlIiwgICIyMDIzLTA2LTA1IiwgIk5lZ2F0aXZlIiwNCiAgIkFsaWNlIiwgICIyMDIzLTA2LTEwIiwgIlBvc2l0aXZlIiwNCiAgIkJvYiIsICAgICIyMDIzLTA4LTEwIiwgIlBvc2l0aXZlIiwNCiAgIlhhdmllciIsICIyMDIzLTA1LTAyIiwgIk5lZ2F0aXZlIiwNCiAgIlhhdmllciIsICIyMDIzLTA1LTEyIiwgIk5lZ2F0aXZlIiwNCikNCmBgYA0KDQrigKMgTGV0J3Mgc2VlIHdoYXQgaGFwcGVucyB3aGVuIHdlIHVzZSBhIGBsZWZ0X2pvaW4oKWAgd2l0aCBgZGVtb2dyYXBoaWNgIGFzIHRoZSBkYXRhc2V0IHRvIHRoZSBsZWZ0IG9mIHRoZSBjYWxsOg0KDQpgYGB7cn0NCmxlZnRfam9pbihkZW1vZ3JhcGhpYywgdGVzdF9pbmZvX21hbnkpDQpgYGANCg0K4oCjIEhlcmUncyB3aGF0IGhhcHBlbmVkOg0KDQrigKMgYEFsaWNlYCB3YXMgcmV0YWluZWQuDQoNCuKAoyBCdXQgc2hlIGZlYXR1cmVkIHR3aWNlIGluIHRoZSByaWdodCBkYXRhc2V0LCBzbyBoZXIgZGVtb2dyYXBoaWMgaW5mb3JtYXRpb24gd2FzIGR1cGxpY2F0ZWQgaW4gdGhlIGZpbmFsIGRhdGFzZXQuDQoNCuKAoyBgWGF2aWVyYCB3YXMgZHJvcHBlZCBlbnRpcmVseS4NCg0K4oCjIFdoZW4gcGVyZm9ybWluZyBhIG9uZS10by1tYW55IGpvaW4sIHRoZSBkYXRhIGZyb20gdGhlICJvbmUiIHNpZGUgaXMgZHVwbGljYXRlZCBmb3IgZWFjaCBtYXRjaGluZyByb3cgb2YgdGhlICJtYW55IiBzaWRlLg0KDQohW10oaW1hZ2VzL21hbnlfdG9fb25lLmdpZikNCg0KOjo6IHByYWN0aWNlDQojIyMgUTogTWVyZ2luZyBUQiBDYXNlcyB3aXRoIEdlb2dyYXBoaWMgRGF0YSB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpDb3B5IHRoZSBjb2RlIGJlbG93IHRvIGNyZWF0ZSB0d28gc21hbGwgZGF0YWZyYW1lczoNCg0KYGBge3J9DQpwYXRpZW50X2luZm8gPC0gdHJpYmJsZSgNCiAgfnBhdGllbnRfaWQsIH5uYW1lLCAgICAgfmFnZSwNCiAgMSwgICAgICAgICAgIkxpYW0iLCAgICAgMzIsDQogIDIsICAgICAgICAgICJNYW5ueSIsICAgIDI4LA0KICAzLCAgICAgICAgICAiTmljbyIsICAgICA0MA0KKQ0KDQpjb25kaXRpb25zIDwtIHRyaWJibGUoDQogIH5wYXRpZW50X2lkLCB+ZGlzZWFzZSwNCiAgMSwgICAgICAgICAgICJEaWFiZXRlcyIsDQogIDEsICAgICAgICAgICAiSHlwZXJ0ZW5zaW9uIiwNCiAgMiwgICAgICAgICAgICJBc3RobWEiLA0KICAzLCAgICAgICAgICAgIkhpZ2ggQ2hvbGVzdGVyb2wiLA0KICAzLCAgICAgICAgICAgIkFydGhyaXRpcyINCikNCg0KYGBgDQoNCklmIHlvdSB1c2UgYSBgbGVmdF9qb2luKClgIHRvIGpvaW4gdGhlc2UgZGF0YXNldHMsIGhvdyBtYW55IHJvd3Mgd2lsbCBiZSBpbiB0aGUgZmluYWwgZGF0YWZyYW1lPyBUcnkgdG8gZmlndXJlIGl0IG91dCBhbmQgdGhlbiBwZXJmb3JtIHRoZSBqb2luIHRvIHNlZSBpZiB5b3Ugd2VyZSByaWdodCENCg0KYGBge3J9DQpsZWZ0X2pvaW4ocGF0aWVudF9pbmZvLCBjb25kaXRpb25zKQ0KYGBgDQoNCjo6Og0KDQrigKMgRXhwbG9yZSB0aGUgYHRiX25vdGlmc2AgZGF0YXNldA0KDQpgYGB7cn0NCnRiX25vdGlmcw0KYGBgDQoNCuKAoyBOb3RlOiBUd28gcm93cyBwZXIgc3RhdGUsIGZvciBwdWJsaWMgYW5kIHByaXZhdGUgaGVhbHRoIGZhY2lsaXRpZXMNCg0K4oCjIFNlY29uZCBkYXRhc2V0OiBgcmVnaW9uc2AgZGF0YXNldCwgY29udGFpbmluZyBJbmRpYW4gc3RhdGUgYW5kIFVuaW9uIFRlcnJpdG9yaWVzDQoNCmBgYHtyfQ0KZnVsbF9yZWdpb25zIDwtIHJlYWRfY3N2KGhlcmUoImRhdGEvcmVnaW9uX2RhdGFfaW5kaWFfZnVsbC5jc3YiKSkNCmZ1bGxfcmVnaW9ucw0KYGBgDQoNCuKAoyBMZXQncyB0cnkgam9pbmluZyB0aGUgZGF0YXNldHM6DQoNCmBgYHtyfQ0Kbm90aWZfcmVnaW9ucyA8LSB0Yl9ub3RpZnMgJT4lIA0KICBsZWZ0X2pvaW4ocmVnaW9ucywgYnkgPSBjKCJzdGF0ZSIgPSAic3RhdGVfVVQiKSkNCm5vdGlmX3JlZ2lvbnMNCmBgYA0KDQrigKMgRGF0YSBmcm9tIHRoZSBgcmVnaW9uc2AgZGF0YWZyYW1lIHdhcyBkdXBsaWNhdGVkDQoNCiMjIE11bHRpcGxlIGtleSBjb2x1bW5zDQoNCuKAoyBTb21ldGltZXMgd2UgaGF2ZSBtb3JlIHRoYW4gb25lIGNvbHVtbiB0aGF0IHVuaXF1ZWx5IGlkZW50aWZpZXMgdGhlIG9ic2VydmF0aW9ucyB0aGF0IHdlIHdhbnQgdG8gbWF0Y2ggb24uDQoNCuKAoyBDb25zaWRlciBzeXN0b2xpYyBibG9vZCBwcmVzc3VyZSBtZWFzdXJlcyBmb3IgcGF0aWVudHMgYmVmb3JlIGFuZCBhZnRlciBhIGRydWcgdHJpYWwNCg0KYGBge3J9DQpibG9vZF9wcmVzc3VyZSA8LSB0cmliYmxlKA0KICB+bmFtZSwgICAgfnRpbWVfcG9pbnQsICB+c3lzdG9saWMsIA0KICAiQWxpY2UiLCAgICJwcmUiLCAgICAgICAgIDEzOSwgICAgICANCiAgIkFsaWNlIiwgICAicG9zdCIsICAgICAgICAxMjEsICAgICAgDQogICJCb2IiLCAgICAgInByZSIsICAgICAgICAgMTM3LCAgICAgIA0KICAiQm9iIiwgICAgICJwb3N0IiwgICAgICAgIDEyOCwgICAgICANCiAgIkNoYXJsaWUiLCAicHJlIiwgICAgICAgICAxMzcsICAgICAgDQogICJDaGFybGllIiwgInBvc3QiLCAgICAgICAgMTMwICkNCmBgYA0KDQrigKMgQW5vdGhlciBkYXRhc2V0IGNvbnRhaW5zIHNlcnVtIGNyZWF0aW5pbmUgbGV2ZWxzIGZvciB0aGUgc2FtZSBwYXRpZW50cyBhbmQgdGltZSBwb2ludHMNCg0KYGBge3J9DQpraWRuZXkgPC0gdHJpYmJsZSgNCiAgfm5hbWUsICAgIH50aW1lX3BvaW50LCAgfmNyZWF0aW5pbmUsIA0KICAiQWxpY2UiLCAgICJwcmUiLCAgICAgICAgIDAuODQsICAgICAgDQogICJBbGljZSIsICAgInBvc3QiLCAgICAgICAgMS4wMywgICAgICANCiAgIkJvYiIsICAgICAicHJlIiwgICAgICAgICAwLjg3LCAgICAgIA0KICAiQm9iIiwgICAgICJwb3N0IiwgICAgICAgIDEuMjEsICAgICAgDQogICJDaGFybGllIiwgInByZSIsICAgICAgICAgMC44OCwgICAgICANCiAgIkNoYXJsaWUiLCAicG9zdCIsICAgICAgICAxLjI1ICkNCmBgYA0KDQrigKMgT3VyIGdvYWw6IGpvaW4gdHdvIGRhdGFzZXRzIHNvIGVhY2ggcGF0aWVudCBoYXMgdHdvIHJvd3MsIG9uZSBmb3IgbGV2ZWxzIGJlZm9yZSB0aGUgZHJ1ZyBhbmQgb25lIGZvciBsZXZlbHMgYWZ0ZXIuDQoNCuKAoyBGaXJzdCBpbnN0aW5jdDogam9pbiBvbiB0aGUgcGF0aWVudCdzIG5hbWUuDQoNCuKAoyBMZXQncyB0cnkgdGhpcyBvdXQ6DQoNCmBgYHtyfQ0KYnBfa2lkbmV5X2R1cHMgPC0gYmxvb2RfcHJlc3N1cmUgJT4lIA0KICBsZWZ0X2pvaW4oa2lkbmV5LCBieSA9ICJuYW1lIikNCg0KYnBfa2lkbmV5X2R1cHMNCmBgYA0KDQrigKMgUmVzdWx0OiBEdXBsaWNhdGVkIHJvd3MsIGxlYWRpbmcgdG8gZm91ciByb3dzIHBlciBwZXJzb24uDQoNCuKAoyAiTWFueS10by1tYW55IiByZWxhdGlvbnNoaXA6IEEgc2NlbmFyaW8gd2UgZ2VuZXJhbGx5IHdhbnQgdG8gYXZvaWQhDQoNCuKAoyBXZSBhbHNvIHNlZSB0d28gYHRpbWVfcG9pbnRgIGNvbHVtbnMgZGlmZmVyZW50aWF0ZWQgYnkgYC54YCBhbmQgYC55YC4NCg0K4oCjIEluc3RlYWQsIHdlIHNob3VsZCBtYXRjaCBvbiBCT1RIIGBuYW1lYCBhbmQgYHRpbWVfcG9pbnRgLg0KDQrigKMgVXNlIGBjKClgIGZ1bmN0aW9uIHRvIHNwZWNpZnkgYm90aCBjb2x1bW4gbmFtZXMuDQoNCmBgYHtyfQ0KYnBfa2lkbmV5IDwtIGJsb29kX3ByZXNzdXJlICU+JQ0KICBsZWZ0X2pvaW4oa2lkbmV5LCBieSA9IGMoIm5hbWUiLCAidGltZV9wb2ludCIpKQ0KDQpicF9raWRuZXkNCmBgYA0KDQrigKMgVGhpcyBnaXZlcyB1cyB0aGUgZGVzaXJlZCBvdXRjb21lIQ0KDQrigKMgTm93LCBsZXQncyBhcHBseSB0aGlzIHRvIG91ciBgdGJfbm90aWZzYCBhbmQgYGNvdmlkX3NjcmVlbmluZ2AgZGF0YXNldHMuDQoNCmBgYHtyfQ0KdGJfbm90aWZzDQpjb3ZpZF9zY3JlZW5pbmcNCmBgYA0KDQrigKMgRmluYWwgZGF0YWZyYW1lIGdvYWw6IFR3byByb3dzIGZvciBlYWNoIHN0YXRlLCBvbmUgZm9yIHB1YmxpYyBhbmQgb25lIGZvciBwcml2YXRlIHNlY3RvciBkYXRhLg0KDQrigKMgTWF0Y2ggb24gYm90aCBgc3RhdGVgIGFuZCBgaGNfdHlwZWAgdXNpbmcgYGMoKWAgaW4gdGhlIGBieT1gIHN0YXRlbWVudC4NCg0KYGBge3J9DQpub3RpZl9jb3ZpZCA8LSB0Yl9ub3RpZnMgJT4lDQogIGxlZnRfam9pbihjb3ZpZF9zY3JlZW5pbmcsIGJ5PWMoInN0YXRlIiwgImhjX3R5cGUiKSkNCm5vdGlmX2NvdmlkDQpgYGANCg0K4oCjIFN1Y2Nlc3MhIFdlIGdvdCB0aGUgZXhhY3Qgc3RydWN0dXJlIHdlIHdhbnRlZC4NCg0KKipQUkFDVElDRSBUSU1FICEqKg0KDQo6OjogcHJhY3RpY2UNCg0KIyMjIFE6IEpvaW5pbmcgdGhyZWUgZGF0YXNldHMsIGluY2x1ZGluZyBvbmUtdG8tbWFueSB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpJbiB0aGlzIHByYWN0aWNlLCB5b3Ugd2lsbCBqb2luIHRocmVlIGRhdGFzZXRzOiBgbm90aWZfY292aWRgLCBgY2hpbGRgLCBhbmQgYHJlZ2lvbnNgLiBGb2xsb3cgdGhlIHN0ZXBzIGJlbG93IHRvIGVuc3VyZSB0aGF0IG5vIGRhdGEgaXMgbG9zdC4NCg0KRm9sbG93IHRoZXNlIHN0ZXBzIGFuZCBmaWxsIGluIHRoZSBibGFua2VkIGNvZGUgZnJhZ21lbnRzIHRvIGNvbXBsZXRlIHRoZSBqb2luaW5nIHByb2Nlc3MuDQoNCjEuIENoZWNrIGZvciBtaXNtYXRjaGVzIGJldHdlZW4gYG5vdGlmX2NvdmlkYCBhbmQgYGNoaWxkYCB1c2luZyBgYW50aV9qb2luKClgLg0KDQpgYGB7ciBldmFsPUZ9DQojIENoZWNrIG1pc21hdGNoZXMNCmFudGlfam9pbihub3RpZl9jb3ZpZCwgY2hpbGQpDQphbnRpX2pvaW4oY2hpbGQsIG5vdGlmX2NvdmlkKQ0KYGBgDQoNCjIuIENsZWFuIHRoZSBtaXNtYXRjaGVzIGluIGBjaGlsZGAgYnkgY3JlYXRpbmcgYGNoaWxkX2ZpeGVkYC4gVXNlIGBjYXNlX3doZW4oKWAgdG8gdXBkYXRlIHN0YXRlIG5hbWVzLg0KDQpgYGB7ciBldmFsPUZ9DQojIENsZWFuIG1pc21hdGNoZXMNCmNoaWxkX2ZpeGVkIDwtIGNoaWxkICU+JQ0KICBtdXRhdGUoc3RhdGUgPSBjYXNlX3doZW4oDQogICAgc3RhdGUgPT0gIkFydW5hY2hhbFByYWRlc2giIH4gIkFydW5hY2hhbCBQcmFkZXNoIiwNCiAgICBzdGF0ZSA9PSAiSmFtbXUgYW5kIEthc2htaXIiIH4gIkphbW11ICYgS2FzaG1pciIsDQogICAgc3RhdGUgPT0gImtlcmFsYSIgfiAiS2VyYWxhIiwNCiAgICBzdGF0ZSA9PSAiUG9uZGljaGVycnkiIH4gIlB1ZHVjaGVycnkiLA0KICAgIFRSVUUgfiBzdGF0ZQ0KICApKQ0KYGBgDQoNCjMuIFZlcmlmeSB0aGF0IHRoZSBtaXNtYXRjaGVzIGhhdmUgYmVlbiByZXNvbHZlZCB1c2luZyBgYW50aV9qb2luKClgLiBUaGVzZSBzaG91bGQgcmV0dXJuIGVtcHR5IGRhdGFmcmFtZXMuDQoNCmBgYHtyIGV2YWw9Rn0NCiMgVmVyaWZ5IG1pc21hdGNoZXMgcmVzb2x2ZWQNCmFudGlfam9pbihjaGlsZF9maXhlZCwgbm90aWZfY292aWQpDQphbnRpX2pvaW4obm90aWZfY292aWQsIGNoaWxkX2ZpeGVkKQ0KYGBgDQoNCjQuIEpvaW4gYG5vdGlmX2NvdmlkYCBhbmQgYGNoaWxkX2ZpeGVkYCB1c2luZyBgbGVmdF9qb2luKClgIG9uIGBzdGF0ZWAgYW5kIGBoY190eXBlYC4gQXNzaWduIHRoZSByZXN1bHQgdG8gYGpvaW5fMWAuDQoNCmBgYHtyIGV2YWw9Rn0NCiMgSm9pbiBub3RpZl9jb3ZpZCBhbmQgY2hpbGRfZml4ZWQNCmpvaW5fMSA8LSBub3RpZl9jb3ZpZCAlPiUgDQogIGxlZnRfam9pbihjaGlsZF9maXhlZCwgYnkgPSBjKCJzdGF0ZSIsICJoY190eXBlIikpDQpgYGANCg0KNS4gQ2hlY2sgZm9yIG1pc21hdGNoZXMgYmV0d2VlbiBgam9pbl8xYCBhbmQgYHJlZ2lvbnNgIHVzaW5nIGBhbnRpX2pvaW4oKWAuDQoNCmBgYHtyIGV2YWw9Rn0NCiMgQ2hlY2sgbWlzbWF0Y2hlcw0KYW50aV9qb2luKGpvaW5fMSwgcmVnaW9ucywgYnkgPSBjKCJzdGF0ZSIgPSAic3RhdGVfVVQiKSkNCmFudGlfam9pbihyZWdpb25zLCBqb2luXzEsIGJ5ID0gYygic3RhdGVfVVQiID0gInN0YXRlIikpDQpgYGANCg0KWW91IG1heSBub3RpY2UgdGhhdCBzb21lIHN0YXRlcywgZS5nLiBMYWRha2gsIGFyZSBtaXNzaW5nIGZyb20gdGhlIGByZWdpb25zYCBkYXRhc2V0LiBUaGVyZSBpcyBub3RoaW5nIHdlIGNhbiBkbyBhYm91dCB0aGlzIGZvciB0aGUgbW9tZW50LCBzbyB3ZSB3aWxsIHByb2NlZWQgd2l0aCB0aGUgam9pbi4NCg0KDQo2LiBQZXJmb3JtIHRoZSBmaW5hbCBqb2luIHVzaW5nIGBsZWZ0X2pvaW4oKWAgb24gYHN0YXRlYCBmcm9tIGBqb2luXzFgIGFuZCBgc3RhdGVfVVRgIGZyb20gYHJlZ2lvbnNgLiBBc3NpZ24gdGhlIHJlc3VsdCB0byBgZmluYWxfam9pbmAuDQoNCmBgYHtyIGV2YWw9Rn0NCiMgRmluYWwgam9pbg0KZmluYWxfam9pbiA8LSBqb2luXzEgJT4lIA0KICBsZWZ0X2pvaW4ocmVnaW9ucywgYnkgPSBjKCJzdGF0ZSIgPSAic3RhdGVfVVQiKSkNCmBgYA0KDQpUaGUgcXVlc3Rpb24gc2FpZCB0byBhdm9pZCBsb3NpbmcgaW5mb3JtYXRpb24sIHNvIHlvdSBtYXkgYmUgd29uZGVyaW5nIHdoeSB3ZSB1c2VkIGEgYGxlZnRfam9pbigpYCBvZiBhIGBmdWxsX2pvaW4oKWA/IFRoZSBvdXRwdXRzIGFyZSBhY3R1YWxseSB0aGUgc2FtZSBpbiB0aGlzIGNhc2UuIFJlbWVtYmVyIHRoYXQgYGxlZnRfam9pbigpYCBrZWVwcyBhbGwgcm93cyBmcm9tIHRoZSBsZWZ0IGRhdGFzZXQuIFRoZSBtaXNzaW5nIHN0YXRlcyB3ZSBjb21tZW50ZWQgaW4gc3RlcCA1IGFyZSBwcmVzZW50IGluIHRoZSBsZWZ0LCAgYGpvaW5fMWAgZGF0YXNldCwgc28gdGhleSBhcmUgbm90IGxvc3QgaW4gdGhlIGZpbmFsIGpvaW4uIEFuZCB3aGVuIHlvdSBjYW4gY2hvb3NlIGJldHdlZW4gYGxlZnRfam9pbigpYCBhbmQgYGZ1bGxfam9pbigpYCwgaXQgaXMgYmV0dGVyIHRvIHVzZSBgbGVmdF9qb2luKClgIGFzIGl0IGlzIGVhc2llciBmb3IgeW91ciBhdWRpZW5jZSB0byByZWFzb24gYWJvdXQgd2hhdCB5b3VyIGNvZGUgaXMgZG9pbmcuDQoNCjcuIERpc3BsYXkgdGhlIGBmaW5hbF9qb2luYCBkYXRhc2V0Lg0KDQpgYGB7ciBldmFsPUZ9DQojIERpc3BsYXkgZmluYWxfam9pbg0KZmluYWxfam9pbg0KYGBgDQoNCjo6Og0KDQoNCiMgV3JhcCBVcCENCg0KSW4gdGhpcyBsZXNzb24sIHdlIGRlbHZlZCBpbnRvIHRoZSBpbnRyaWNhY2llcyBvZiBkYXRhIGNsZWFuaW5nIGJlZm9yZSBhIGpvaW4sIGZvY3VzaW5nIG9uIGhvdyB0byBkZXRlY3QgYW5kIGNvcnJlY3QgbWlzbWF0Y2hlcyBvciBpbmNvbnNpc3RlbmNpZXMgaW4ga2V5IGNvbHVtbnMuIFdlIGFsc28gaGlnaGxpZ2h0ZWQgdGhlIGltcGFjdCBvZiBvbmUtdG8tbWFueSByZWxhdGlvbnNoaXBzIGluIGpvaW5pbmcgZGF0YWZyYW1lcywgc2hvd2luZyBob3cgZGF0YSBmcm9tIHRoZSAib25lIiBzaWRlIGlzIGR1cGxpY2F0ZWQgZm9yIGVhY2ggbWF0Y2hpbmcgcm93IG9mIHRoZSAibWFueSIgc2lkZS4gRmluYWxseSwgd2UgZGVtb25zdHJhdGVkIGhvdyB0byBqb2luIGRhdGFmcmFtZXMgdXNpbmcgbXVsdGlwbGUga2V5IGNvbHVtbnMuDQoNCkFzIHdlIGNvbmNsdWRlIHRoaXMgbGVzc29uLCB3ZSBob3BlIHRoYXQgeW91IGhhdmUgZ2FpbmVkIGEgZGVlcGVyIHVuZGVyc3RhbmRpbmcgb2YgdGhlIGltcG9ydGFuY2UgYW5kIHV0aWxpdHkgb2Ygam9pbmluZyBkYXRhZnJhbWVzIGluIFIuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIEFuc3dlciBLZXkgey51bm51bWJlcmVkfQ0KDQojIyMgUTogSW5uZXIgSm9pbiBjb3VudHJpZXMgey51bmxpc3RlZCAudW5udW1iZXJlZH0NCmBgYHtyfQ0KZGYyX2ZpeGVkIDwtIGRmMiAlPiUgDQogIG11dGF0ZShDb3VudHJ5ID0gDQogICAgICAgICAgIGNhc2VfbWF0Y2goQ291bnRyeSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJJbmRpYSAiIH4gIkluZGlhIiwgIyBSZW1vdmUgYmxhbmsgc3BhY2UgYXQgZW5kDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJpbmRvbmVzaWEiIH4gIkluZG9uZXNpYSIsICMgQ2FwaXRhbGl6ZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiUGhpbGlwaW5lcyIgfiAiUGhpbGlwcGluZXMiLCAjIEZpeCBzcGVsbGluZw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuZGVmYXVsdD1Db3VudHJ5KSkNCg0KaW5uZXJfam9pbihkZjEsIGRmMl9maXhlZCkNCmBgYA0KDQoNCiMjIyBROiBDaGVjayBhbmQgZml4IHR5cG9zIGJlZm9yZSBqb2luIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQoNCjEuIA0KYGBge3J9DQojIHNldGRpZmYoKQ0Kc2V0ZGlmZihjaGlsZF9wdWJsaWMkc3RhdGUsIHRiX25vdGlmc19wdWJsaWMkc3RhdGUpDQpzZXRkaWZmKHRiX25vdGlmc19wdWJsaWMkc3RhdGUsIGNoaWxkX3B1YmxpYyRzdGF0ZSkNCg0KIyBhbnRpam9pbg0KYW50aV9qb2luKGNoaWxkX3B1YmxpYywgdGJfbm90aWZzX3B1YmxpYykNCmFudGlfam9pbih0Yl9ub3RpZnNfcHVibGljLCBjaGlsZF9wdWJsaWMpDQpgYGANCg0KDQoyLiANCmBgYHtyfQ0KY2hpbGRfcHVibGljX2ZpeGVkIDwtIGNoaWxkX3B1YmxpYyAlPiUNCiAgbXV0YXRlKHN0YXRlID0gDQogICAgICAgICAgIGNhc2Vfd2hlbihzdGF0ZSA9PSAiQXJ1bmFjaGFsUHJhZGVzaCIgfiAiQXJ1bmFjaGFsIFByYWRlc2giLCANCiAgICAgICAgICAgICAgICAgICAgIHN0YXRlID09ICJKYW1tdSBhbmQgS2FzaG1pciIgfiAiSmFtbXUgJiBLYXNobWlyIiwgDQogICAgICAgICAgICAgICAgICAgICBzdGF0ZSA9PSAia2VyYWxhIiB+ICJLZXJhbGEiLCANCiAgICAgICAgICAgICAgICAgICAgIHN0YXRlID09ICJQb25kaWNoZXJyeSIgfiAiUHVkdWNoZXJyeSIsDQogICAgICAgICAgICAgICAgICAgICBUUlVFIH4gc3RhdGUpKQ0KYGBgDQoNCg0KMy4NCmBgYHtyfQ0KY2hpbGRfdGJfcHVibGljIDwtIGNoaWxkX3B1YmxpY19maXhlZCAlPiUNCiAgaW5uZXJfam9pbih0Yl9ub3RpZnNfcHVibGljKQ0KYGBgDQoNCjQuIA0KYGBge3IgcmVuZGVyID0gcmVhY3RhYmxlXzVfcm93c30NCmNoaWxkX3RiX3B1YmxpYyAlPiUNCiAgbXV0YXRlKHRiX2NoaWxkX3Byb3AgPSB0Yl9jaGlsZF9ub3RpZnMvdGJfbm90aWZfY291bnQpICU+JQ0KICBhcnJhbmdlKC10Yl9jaGlsZF9wcm9wKQ0KYGBgDQoNCg0KIyMjIFE6IE1lcmdpbmcgVEIgQ2FzZXMgd2l0aCBHZW9ncmFwaGljIERhdGEgey51bmxpc3RlZCAudW5udW1iZXJlZH0NCg0KMS4NCmBgYHtyIHJlbmRlciA9IHJlYWN0YWJsZV81X3Jvd3N9DQpsZWZ0X2pvaW4odG9wX3RiX2Nhc2VzX2tpZHMsIGNvdW50cnlfcmVnaW9ucywgYnkgPSBjKCJjb3VudHJ5Ij0iY291bnRyeV9uYW1lIikpDQpgYGANCg0KYGBge3J9DQpzZXRkaWZmKHRvcF90Yl9jYXNlc19raWRzJGNvdW50cnksIGNvdW50cnlfcmVnaW9ucyRjb3VudHJ5X25hbWUpDQpgYGANCg0KMi4gDQpgYGB7ciByZW5kZXIgPSByZWFjdGFibGVfNV9yb3dzfQ0KbGVmdF9qb2luKHRvcF90Yl9jYXNlc19raWRzX2ZpeGVkLCBjb3VudHJ5X3JlZ2lvbnMsIGJ5ID0gYygiY291bnRyeSI9ImNvdW50cnlfbmFtZSIpKQ0KYGBgDQoNCjMuDQoNCmBgYHtyIHJlbmRlciA9IHJlYWN0YWJsZV81X3Jvd3N9DQpsZWZ0X2pvaW4odG9wX3RiX2Nhc2VzX2tpZHMsIGNvdW50cnlfcmVnaW9ucywgYnkgPSBjKCJpc28zIiA9ICJpc28zYyIpKQ0KYGBgDQoNCjQuIA0KDQpJU08gY29kZXMgYXJlIHN0YW5kYXJkaXplZCAtIHRoZXJlIGluIG9ubHkgb25lIHdheSBvZiB3cml0aW5nIHRoZW0uIFRoaXMgbWFrZXMgaXQgdXNlZnVsIGZvciBqb2luaW5nLg0KDQoNCiMjIyBROiBNZXJnaW5nIE9uZS10by1NYW55IFBhdGllbnQgUmVjb3JkcyB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpgYGB7ciwgcmVuZGVyID0gcmVhY3RhYmxlXzVfcm93c30NCiMgNSByb3dzIGluIHRoZSBmaW5hbCBkYXRhZnJhbWUNCnBhdGllbnRfaW5mbyAlPiUgDQogIGxlZnRfam9pbihjb25kaXRpb25zKQ0KYGBgDQoNCg0KIyMjIFE6IEpvaW5pbmcgdGhyZWUgZGF0YXNldHMsIGluY2x1ZGluZyBvbmUtdG8tbWFueSB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQoxLiBDaGVjayBmb3IgbWlzbWF0Y2hlcyBiZXR3ZWVuIGBub3RpZl9jb3ZpZGAgYW5kIGBjaGlsZGAgdXNpbmcgYGFudGlfam9pbigpYC4NCg0KYGBge3J9DQojIENoZWNrIG1pc21hdGNoZXMNCmFudGlfam9pbihjaGlsZCwgbm90aWZfY292aWQpDQphbnRpX2pvaW4obm90aWZfY292aWQsIGNoaWxkKQ0KYGBgDQoNCjIuIENsZWFuIHRoZSBtaXNtYXRjaGVzIGluIGBjaGlsZGAgYnkgY3JlYXRpbmcgYGNoaWxkX2ZpeGVkYC4gVXNlIGBjYXNlX3doZW4oKWAgdG8gdXBkYXRlIHN0YXRlIG5hbWVzLg0KDQpgYGB7cn0NCiMgQ2xlYW4gbWlzbWF0Y2hlcw0KY2hpbGRfZml4ZWQgPC0gY2hpbGQgJT4lDQogIG11dGF0ZShzdGF0ZSA9IGNhc2Vfd2hlbigNCiAgICBzdGF0ZSA9PSAiQXJ1bmFjaGFsUHJhZGVzaCIgfiAiQXJ1bmFjaGFsIFByYWRlc2giLA0KICAgIHN0YXRlID09ICJKYW1tdSBhbmQgS2FzaG1pciIgfiAiSmFtbXUgJiBLYXNobWlyIiwNCiAgICBzdGF0ZSA9PSAia2VyYWxhIiB+ICJLZXJhbGEiLA0KICAgIHN0YXRlID09ICJQb25kaWNoZXJyeSIgfiAiUHVkdWNoZXJyeSIsDQogICAgVFJVRSB+IHN0YXRlDQogICkpDQpgYGANCg0KMy4gVmVyaWZ5IHRoYXQgdGhlIG1pc21hdGNoZXMgaGF2ZSBiZWVuIHJlc29sdmVkIHVzaW5nIGBhbnRpX2pvaW4oKWAuDQoNCmBgYHtyfQ0KIyBWZXJpZnkgbWlzbWF0Y2hlcyByZXNvbHZlZA0KYW50aV9qb2luKGNoaWxkX2ZpeGVkLCBub3RpZl9jb3ZpZCkNCmFudGlfam9pbihub3RpZl9jb3ZpZCwgY2hpbGRfZml4ZWQpDQpgYGANCg0KNC4gSm9pbiBgbm90aWZfY292aWRgIGFuZCBgY2hpbGRfZml4ZWRgIHVzaW5nIGBsZWZ0X2pvaW4oKWAgb24gYHN0YXRlYCBhbmQgYGhjX3R5cGVgLiBBc3NpZ24gdGhlIHJlc3VsdCB0byBgam9pbl8xYC4NCg0KYGBge3J9DQojIEpvaW4gbm90aWZfY292aWQgYW5kIGNoaWxkX2ZpeGVkDQpqb2luXzEgPC0gbm90aWZfY292aWQgJT4lIA0KICBsZWZ0X2pvaW4oY2hpbGRfZml4ZWQsIGJ5ID0gYygic3RhdGUiLCAiaGNfdHlwZSIpKQ0KYGBgDQoNCjUuIENoZWNrIGZvciBtaXNtYXRjaGVzIGJldHdlZW4gYGpvaW5fMWAgYW5kIGByZWdpb25zYCB1c2luZyBgYW50aV9qb2luKClgLg0KDQpgYGB7cn0NCiMgQ2hlY2sgbWlzbWF0Y2hlcw0KYW50aV9qb2luKGpvaW5fMSwgcmVnaW9ucywgYnkgPSBjKCJzdGF0ZSIgPSAic3RhdGVfVVQiKSkNCmFudGlfam9pbihyZWdpb25zLCBqb2luXzEsIGJ5ID0gYygic3RhdGVfVVQiID0gInN0YXRlIikpDQpgYGANCg0KWW91IG1heSBub3RpY2UgdGhhdCBzb21lIHN0YXRlcywgZS5nLiBMYWRha2gsIHdoaWNoIGFyZSBwcmVzZW50IGluIGBqb2luXzFgIGFyZSBtaXNzaW5nIGZyb20gdGhlIGByZWdpb25zYCBkYXRhc2V0LiBUaGVyZSBpcyBub3RoaW5nIHdlIGNhbiBkbyBhYm91dCB0aGlzIGZvciB0aGUgbW9tZW50LCBzbyB3ZSB3aWxsIHByb2NlZWQgd2l0aCB0aGUgam9pbi4NCg0KNi4gUGVyZm9ybSB0aGUgZmluYWwgam9pbiB1c2luZyBgbGVmdF9qb2luKClgIG9uIGBzdGF0ZWAgZnJvbSBgam9pbl8xYCBhbmQgYHN0YXRlX1VUYCBmcm9tIGByZWdpb25zYC4gQXNzaWduIHRoZSByZXN1bHQgdG8gYGZpbmFsX2pvaW5gLg0KDQpgYGB7cn0NCiMgRmluYWwgam9pbg0KZmluYWxfam9pbiA8LSBqb2luXzEgJT4lIA0KICBsZWZ0X2pvaW4ocmVnaW9ucywgYnkgPSBjKCJzdGF0ZSIgPSAic3RhdGVfVVQiKSkNCmBgYA0KDQpUaGUgcXVlc3Rpb24gc2FpZCB0byBhdm9pZCBsb3NpbmcgaW5mb3JtYXRpb24sIHNvIHlvdSBtYXkgYmUgd29uZGVyaW5nIHdoeSB3ZSB1c2VkIGEgYGxlZnRfam9pbigpYCBvZiBhIGBmdWxsX2pvaW4oKWA/IFRoZSBvdXRwdXRzIGFyZSBhY3R1YWxseSB0aGUgc2FtZSBpbiB0aGlzIGNhc2UuIFJlbWVtYmVyIHRoYXQgYGxlZnRfam9pbigpYCBrZWVwcyBhbGwgcm93cyBmcm9tIHRoZSBsZWZ0IGRhdGFzZXQuIFRoZSBtaXNzaW5nIHN0YXRlcyB3ZSBjb21tZW50ZWQgaW4gc3RlcCA1IGFyZSBwcmVzZW50IGluIHRoZSBsZWZ0LCAgYGpvaW5fMWAgZGF0YXNldCwgc28gdGhleSBhcmUgbm90IGxvc3QgaW4gdGhlIGZpbmFsIGpvaW4uIEFuZCB3aGVuIHlvdSBjYW4gY2hvb3NlIGJldHdlZW4gYGxlZnRfam9pbigpYCBhbmQgYGZ1bGxfam9pbigpYCwgaXQgaXMgYmV0dGVyIHRvIHVzZSBgbGVmdF9qb2luKClgIGFzIGl0IGlzIGVhc2llciBmb3IgeW91ciBhdWRpZW5jZSB0byByZWFzb24gYWJvdXQgd2hhdCB5b3VyIGNvZGUgaXMgZG9pbmcuDQoNCjcuIERpc3BsYXkgdGhlIGBmaW5hbF9qb2luYCBkYXRhc2V0Lg0KDQpgYGB7cn0NCiMgRGlzcGxheSBmaW5hbF9qb2luDQpmaW5hbF9qb2luDQpgYGANCg0KDQojIENvbnRyaWJ1dG9ycyB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQpUaGUgZm9sbG93aW5nIHRlYW0gbWVtYmVycyBjb250cmlidXRlZCB0byB0aGlzIGxlc3NvbjoNCg0KYHIgdGdjX2NvbnRyaWJ1dG9yc19saXN0KGlkcyA9IGMoImFtY2tpbmxleSIsICJrZW5kYXZpZG4iLCAiY2FtaWxsZSIpKWAgDQoNCg==