Purpose

This note outlines how to use the new Fingertips API to extract data, create data frames and perform further analysis. The data is accessible in JSON format, and the hierarchical, nested nature of Fingertips architecture makes extracting data from the API quite complex.

The tutorial makes extensive use of the jsonlite and tidyjson packages which have made it easier to convert complex JSON data to data frames and into tidy format for analysis. Extensions of this script could be used to extract and recombine data from different profiles, areas or time periods.

Worked examples

Load libraries

First we will load the libraries.

A simple example

As a simple example we can extract a list of ageband and gender mappings and area types directly from the API.

sex <- fromJSON("http://fingertips.phe.org.uk/api/sexes")
sex %>% head %>% knitr::kable(format = "pandoc", caption = "Sex groupings")

Sex groupings
Id Name
-1 Not applicable
4 Persons
1 Male
2 Female

age <- fromJSON("http://fingertips.phe.org.uk/api/ages")
age %>% head %>% knitr::kable(format = "pandoc", caption = "Age groupings")

Age groupings
Id Name
-1 Not applicable
1 All ages
2 < 1 yr
3 1-4 yrs
4 5-9 yrs
5 10-14 yrs

areatype <- fromJSON("http://fingertips.phe.org.uk/api/area_types")
areatype %>% head %>% knitr::kable(format = "pandoc", caption = "Areatypes")
Areatypes
Id Name Short IsSearchable
1 Local Authority District LA FALSE
2 Primary Care Trust Primary Care Trust FALSE
3 Middle Super Output Area MSOA FALSE
4 Lower Super Output Area LSOA FALSE
5 Strategic Health Authority SHA FALSE
6 Government Office Region Region TRUE

Overall Fingertips has 281 ageband categories and 51 area types.

Profile

As a more complex example, we can extract information about which profiles the API gives access to. The API gives access to 52 profiles - we just show the first 6 here.

profiles <- fromJSON("http://fingertips.phe.org.uk/api/profiles", simplifyDataFrame = TRUE)
profiles %>% head %>% select(-Key) %>%
   knitr::kable(format = "pandoc", caption = "Profiles")
Profiles
Id Name GroupIds
8 Adult Social Care 1000101, 1000102, 1000103, 1000104, 1000105, 1938132733
18 Local Tobacco Control Profiles 1938132885, 1938132886, 1938132887, 1938132888, 1938132889, 1938132890, 1938132900
19 Public Health Outcomes Framework 1000041, 1000042, 1000043, 1000044, 1000049, 1938132983
20 National General Practice Profiles 2000002, 2000003, 2000004, 2000005, 2000006, 2000008, 2000009, 2000011, 3000007, 3000008, 3000009, 3000010, 1938132829, 1938132970, 1938133086
21 National General Practice Profiles (supporting indicators) 1200006
22 Longer Lives 1000001

Extracting profile information produces a nested list of the GroupIds for each profile, and the number of groups or domains within each profile. For example, health profiles have 7 groups, the national-child-measurement-programme 2 and so on.

We can extract the profile IDs, groupIDs and names as a tidy data frame using the tidyjson package.

library(tidyjson)
prof_json <- "http://fingertips.phe.org.uk/api/profiles"
prof <- prof_json %>%
  gather_array %>% 
  spread_values(ID = jnumber("Id"),
                Name = jstring("Name"),
                Key = jstring("Key")
  ) %>%
  enter_object("GroupIds") %>%
  gather_array %>%
  append_values_number("groupid") %>%
  select(ID, Name, Key, groupid)
array.index column name already exists, changing to array.index.2
prof

The next level of complexity is to extract the latest data for a single profile. We’ll start with just the data for a single area type. The structure of Fingertips means we need to pass a profile id, group id, area code and parent_area code to the API. We’ll do this for the Health Profile for UTLAs.

We’ll first import the data using the jsonlite package and look at the data structure

hp_df <- fromJSON(hp_url, simplifyDataFrame = TRUE)
hp_df %>% str(max.level = 3, list.len = 3)
'data.frame':   3 obs. of  11 variables:
 $ Grouping          :List of 3
  ..$ :'data.frame':    2 obs. of  6 variables:
  .. ..$ GroupId           : int  1938132694 1938132694
  .. ..$ ComparatorId      : int  1 4
  .. ..$ ComparatorMethodId: int  1 1
  .. .. [list output truncated]
  ..$ :'data.frame':    2 obs. of  6 variables:
  .. ..$ GroupId           : int  1938132694 1938132694
  .. ..$ ComparatorId      : int  1 4
  .. ..$ ComparatorMethodId: int  1 1
  .. .. [list output truncated]
  ..$ :'data.frame':    2 obs. of  6 variables:
  .. ..$ GroupId           : int  1938132694 1938132694
  .. ..$ ComparatorId      : int  1 4
  .. ..$ ComparatorMethodId: int  1 1
  .. .. [list output truncated]
 $ Data              :List of 3
  ..$ :'data.frame':    9 obs. of  12 variables:
  .. ..$ AgeId   : int  168 168 168 168 168 168 168 168 168
  .. ..$ SexId   : int  4 4 4 4 4 4 4 4 4
  .. ..$ AreaCode: chr  "E06000015" "E06000016" "E06000017" "E06000018" ...
  .. .. [list output truncated]
  ..$ :'data.frame':    9 obs. of  12 variables:
  .. ..$ AgeId   : int  164 164 164 164 164 164 164 164 164
  .. ..$ SexId   : int  4 4 4 4 4 4 4 4 4
  .. ..$ AreaCode: chr  "E06000015" "E06000016" "E06000017" "E06000018" ...
  .. .. [list output truncated]
  ..$ :'data.frame':    9 obs. of  12 variables:
  .. ..$ AgeId   : int  164 164 164 164 164 164 164 164 164
  .. ..$ SexId   : int  4 4 4 4 4 4 4 4 4
  .. ..$ AreaCode: chr  "E06000015" "E06000016" "E06000017" "E06000018" ...
  .. .. [list output truncated]
 $ IID               : int  92443 90275 90640
  [list output truncated]

The data is complex. The data values are contained in a nested series of data frames as part of the $Data variable. The indicator IDs are stored in the $IID variable but the variable names are not stored here and will need to be extracted with a different call to the API.

We can use the tidyjson package to extract the data and indicator ids.

hp1 <- hp_url %>%
  gather_array %>%
  spread_values(ind_id = jnumber("IID")) %>%
  enter_object("Data") %>%
  gather_array %>%
  spread_values(
    area = jstring("AreaCode"), 
    value = jnumber("Val"), 
    lci = jnumber("LoCI"), 
    uci = jnumber("UpCI"),
    denom = jnumber("Denom"),
    count = jnumber("Count"), 
    age = jnumber("AgeId"), 
    sex = jnumber("SexId") 
    )%>%
  select(ind_id, area, age, sex, value, lci, uci, denom, count)
array.index column name already exists, changing to array.index.2
hp2 <- hp_url %>%
  gather_array() %>%
  spread_values(ind_id = jnumber("IID")) %>%
  enter_object("Grouping") %>%
  gather_array %>%
  spread_values(
    time = jstring("Period")
  ) 
array.index column name already exists, changing to array.index.2
hp1 %>%
  left_join(hp2) %>%
  select(ind_id, area, time, value, lci, uci, count, denom, age, sex) %>%
  distinct
Joining, by = "ind_id"

We have now extracted a data frame of indicator values, time periods, sex and age ids for each indicator for each area.

Extracting indicator names

To obtain indicator names from the API we need to call IndicatorMetadata https://fingertips.phe.org.uk/api#!/IndicatorMetadata/IndicatorMetadata_GetIndicatorMetadata to which we can pass a list of indicator ids.

[1] "92443%2C90275%2C90640"
List of 3
 $ 90275:List of 7
  ..$ IID                     : int 90275
  ..$ Unit                    :List of 3
  .. ..$ Id   : int 5
  .. ..$ Value: num 100
  .. ..$ Label: chr "%"
  ..$ ValueType               :List of 2
  .. ..$ Id  : int 5
  .. ..$ Name: chr "Proportion"
  ..$ YearType                :List of 2
  .. ..$ Id  : int 1
  .. ..$ Name: chr "Calendar"
  ..$ ConfidenceLevel         : num 95
  ..$ ConfidenceIntervalMethod:List of 3
  .. ..$ Id         : int 5
  .. ..$ Name       : chr "Normal approximation"
  .. ..$ Description: chr "<p>A confidence interval is a range of values that is used to quantify the imprecision in the estimate of a particular indicato"| __truncated__
  ..$ Descriptive             :List of 3
  .. ..$ Name      : chr "2.13i - Percentage of physically active and inactive adults - active adults"
  .. ..$ NameLong  : chr "2.13i - Percentage of adults achieving at least 150 minutes of physical activity per week in accordance with UK Chief Medical O"| __truncated__
  .. ..$ DataSource: chr "Active People Survey, Sport England"
 $ 90640:List of 7
  ..$ IID                     : int 90640
  ..$ Unit                    :List of 3
  .. ..$ Id   : int 5
  .. ..$ Value: num 100
  .. ..$ Label: chr "%"
  ..$ ValueType               :List of 2
  .. ..$ Id  : int 5
  .. ..$ Name: chr "Proportion"
  ..$ YearType                :List of 2
  .. ..$ Id  : int 1
  .. ..$ Name: chr "Calendar"
  ..$ ConfidenceLevel         : num 95
  ..$ ConfidenceIntervalMethod:List of 3
  .. ..$ Id         : int 5
  .. ..$ Name       : chr "Normal approximation"
  .. ..$ Description: chr "<p>A confidence interval is a range of values that is used to quantify the imprecision in the estimate of a particular indicato"| __truncated__
  ..$ Descriptive             :List of 3
  .. ..$ Name      : chr "2.12 - Excess weight in Adults"
  .. ..$ NameLong  : chr "2.12 - Percentage of adults classified as overweight or obese"
  .. ..$ DataSource: chr "Active People Survey, Sport England"
 $ 92443:List of 7
  ..$ IID                     : int 92443
  ..$ Unit                    :List of 3
  .. ..$ Id   : int 5
  .. ..$ Value: num 100
  .. ..$ Label: chr "%"
  ..$ ValueType               :List of 2
  .. ..$ Id  : int 5
  .. ..$ Name: chr "Proportion"
  ..$ YearType                :List of 2
  .. ..$ Id  : int 1
  .. ..$ Name: chr "Calendar"
  ..$ ConfidenceLevel         : num 95
  ..$ ConfidenceIntervalMethod:List of 3
  .. ..$ Id         : int 5
  .. ..$ Name       : chr "Normal approximation"
  .. ..$ Description: chr "<p>A confidence interval is a range of values that is used to quantify the imprecision in the estimate of a particular indicato"| __truncated__
  ..$ Descriptive             :List of 3
  .. ..$ Name      : chr "Smoking Prevalence in adults - current smokers (APS)"
  .. ..$ NameLong  : chr "Smoking Prevalence in adults - current smokers (APS)"
  .. ..$ DataSource: chr "Annual Population Survey (APS)"
Joining, by = "ind_id"
Joining, by = "ind_id"
Indicator names
ind_id area time value lci uci count denom age sex Descriptive.NameLong
92443 E06000015 2015 18.7 16.31 21.0 -1 1045 168 4 Smoking Prevalence in adults - current smokers (APS)
92443 E06000016 2015 20.1 17.82 22.3 -1 1226 168 4 Smoking Prevalence in adults - current smokers (APS)
92443 E06000017 2015 11.6 8.46 14.7 -1 399 168 4 Smoking Prevalence in adults - current smokers (APS)
92443 E06000018 2015 24.0 21.78 26.3 -1 1368 168 4 Smoking Prevalence in adults - current smokers (APS)
92443 E10000007 2015 17.9 15.82 19.9 -1 1332 168 4 Smoking Prevalence in adults - current smokers (APS)
92443 E10000018 2015 17.4 15.26 19.4 -1 1262 168 4 Smoking Prevalence in adults - current smokers (APS)

Scaling up and simplifying

To extract all the indicators for the profiles we need to loop through group IDs and parent codes. We’ll use a different example - data from the National Child Measurement Programme (NCMP).

## Firstly extract the profile ID
hpid <- prof[prof$Key == "national-child-measurement-programme", 1][[1]][1]
## Then extract group (domain) IDs
groupids <- prof[prof$Key == "national-child-measurement-programme", 4]
## Create and empty data frame
df <- data_frame()
## Loop through group IDs and parent codes
for(groupid in groupids){
  for(parentcode in parentareacodes$Code) {
   
    
    ## Create API URLs
     
    hp_url1 <- paste0("http://fingertips.phe.org.uk/api/latest_data/all_indicators_in_profile_group_for_child_areas",
                                  "?profile_id=",hpid,
                                  "&group_id=",groupid,
                                  "&area_type_id=",102,
                                  "&parent_area_code=",parentcode)
 
    ## Extract data
       
    data <- hp_url1 %>%
  gather_array %>%
  spread_values(ind_id = jnumber("IID")) %>%
  enter_object("Data") %>%
  gather_array %>%
  spread_values(
    area = jstring("AreaCode"), 
    value = jnumber("Val"), 
    lci = jnumber("LoCI"), 
    uci = jnumber("UpCI"),
    denom = jnumber("Denom"),
    count = jnumber("Count"), 
    age = jnumber("AgeId"), 
    sex = jnumber("SexId") 
    )%>% 
  select(ind_id, area, age, sex, value, lci, uci, denom, count)
    
    ## Build data frame
    
    df <- bind_rows(df, data)
  }
}
array.index column name already exists, changing to array.index.2array.index column name already exists, changing to array.index.2array.index column name already exists, changing to array.index.2array.index column name already exists, changing to array.index.2array.index column name already exists, changing to array.index.2array.index column name already exists, changing to array.index.2array.index column name already exists, changing to array.index.2array.index column name already exists, changing to array.index.2array.index column name already exists, changing to array.index.2
  
## Add time period (uses the same looping process)
dft <- data_frame()
for(groupid in groupids){
  for(parentcode in parentareacodes$Code) {
    
hp_url1 <- paste0("http://fingertips.phe.org.uk/api/latest_data/all_indicators_in_profile_group_for_child_areas",
                                  "?profile_id=",hpid,
                                  "&group_id=",groupid,
                                  "&area_type_id=",102,
                                  "&parent_area_code=",parentcode)    
    
data1 <- hp_url1 %>%
  gather_array() %>%
  spread_values(ind_id = jnumber("IID")) %>%
  enter_object("Grouping") %>%
  gather_array %>%
  spread_values(
    time = jstring("Period")
  
  ) 
dft <- bind_rows(dft, data1)
  }
}  
array.index column name already exists, changing to array.index.2array.index column name already exists, changing to array.index.2array.index column name already exists, changing to array.index.2array.index column name already exists, changing to array.index.2array.index column name already exists, changing to array.index.2array.index column name already exists, changing to array.index.2array.index column name already exists, changing to array.index.2array.index column name already exists, changing to array.index.2array.index column name already exists, changing to array.index.2
##  Join time periods to main data frame
df <- df %>%
  left_join(dft) %>%
  select(ind_id, area, time, value, lci, uci, count, denom, age, sex) %>%
  distinct
Joining, by = "ind_id"
df %>% head
NA

Attaching further metadata

We can add age and gender descriptions and indicator names to the main data frame.

sex <- sex %>%
  rename(sex = Id, gender = Name)
age <- age %>%
  rename(age = Id)
ind_hp <- paste(unique(df$ind_id), collapse = "%2C")
ind_url <- paste0("https://fingertips.phe.org.uk/api/indicator_metadata/by_indicator_id?indicator_ids=",ind_hp)
ind_url %>%
  gather_object() %>%
  spread_all() %>%
  select(ind_id = IID, Descriptive.NameLong)->ind_names
df <- df %>%
  left_join(ind_names, by = "ind_id") 
df %>%
  # select(-c(Descriptive.NameLong.x, Descriptive.NameLong.y.y, Descriptive.NameLong.x.x)) %>%
  select(Ind_name = Descriptive.NameLong, 2:10) %>%
  left_join(age) %>%
  left_join(sex) -> df_analysis
Joining, by = "age"
Joining, by = "sex"

We now have an analytical data frame df_analysis we can explore further. We can follow the same processes to extract and combine data from other profiles or other areas, add area names, and add other elements of metadata.

Data exploration

Given the df_analysis data frame we can explore further.

Extracting GP practice data

Scaling up further, the following code adapted from above, extracts the latest data from the General Practice Profiles.

We now have an analytical data set of the latest general practice indicators which contains 204 indicators - we are just showing the first few rows of 1710254.

LS0tCnRpdGxlOiAnSlNPTiB0dXRvcmlhbDogZmluZ2VydGlwcyBBUEknCm91dHB1dDoKICBodG1sX25vdGVib29rOiBkZWZhdWx0CiAgcGRmX2RvY3VtZW50OgogICAgZmlnX2NhcHRpb246IHllcwogICAga2VlcF90ZXg6IHllcwogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRvYzogeWVzCiAgd29yZF9kb2N1bWVudDogZGVmYXVsdAotLS0KCgojIFB1cnBvc2UKClRoaXMgbm90ZSBvdXRsaW5lcyBob3cgdG8gdXNlIHRoZSBuZXcgRmluZ2VydGlwcyBbQVBJXShodHRwczovL2ZpbmdlcnRpcHMucGhlLm9yZy51ay9hcGkpIHRvIGV4dHJhY3QgZGF0YSwgY3JlYXRlIGRhdGEgZnJhbWVzIGFuZCBwZXJmb3JtIGZ1cnRoZXIgYW5hbHlzaXMuIFRoZSBkYXRhIGlzIGFjY2Vzc2libGUgaW4gSlNPTiBmb3JtYXQsIGFuZCB0aGUgaGllcmFyY2hpY2FsLCBuZXN0ZWQgbmF0dXJlIG9mIEZpbmdlcnRpcHMgYXJjaGl0ZWN0dXJlIG1ha2VzIGV4dHJhY3RpbmcgZGF0YSBmcm9tIHRoZSBBUEkgcXVpdGUgY29tcGxleC4KClRoZSB0dXRvcmlhbCBtYWtlcyBleHRlbnNpdmUgdXNlIG9mIHRoZSBganNvbmxpdGVgIGFuZCBgdGlkeWpzb25gIHBhY2thZ2VzIHdoaWNoIGhhdmUgbWFkZSBpdCBlYXNpZXIgdG8gY29udmVydCBjb21wbGV4IEpTT04gZGF0YSB0byBkYXRhIGZyYW1lcyBhbmQgaW50byB0aWR5IGZvcm1hdCBmb3IgYW5hbHlzaXMuIEV4dGVuc2lvbnMgb2YgdGhpcyBzY3JpcHQgY291bGQgYmUgdXNlZCB0byBleHRyYWN0IGFuZCByZWNvbWJpbmUgZGF0YSBmcm9tIGRpZmZlcmVudCBwcm9maWxlcywgYXJlYXMgb3IgdGltZSBwZXJpb2RzLgoKIyBXb3JrZWQgZXhhbXBsZXMKIyMgTG9hZCBsaWJyYXJpZXMKRmlyc3Qgd2Ugd2lsbCBsb2FkIHRoZSBsaWJyYXJpZXMuCgpgYGB7ciBsaWJyYXJpZXMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGlicmFyeShqc29ubGl0ZSkpCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KGRwbHlyKSkKc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkoZ2dwbG90MikpCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KHN0cmluZ3IpKQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGlicmFyeSh0aWR5anNvbikpCgpgYGAKCgojIyBBIHNpbXBsZSBleGFtcGxlCgpBcyBhIHNpbXBsZSBleGFtcGxlIHdlIGNhbiBleHRyYWN0IGEgbGlzdCBvZiBhZ2ViYW5kIGFuZCBnZW5kZXIgbWFwcGluZ3MgYW5kIGFyZWEgdHlwZXMgZGlyZWN0bHkgZnJvbSB0aGUgQVBJLiAgCgoKYGBge3IsIGVjaG89VFJVRX0KCnNleCA8LSBmcm9tSlNPTigiaHR0cDovL2ZpbmdlcnRpcHMucGhlLm9yZy51ay9hcGkvc2V4ZXMiKQpzZXggJT4lIGhlYWQgJT4lIGtuaXRyOjprYWJsZShmb3JtYXQgPSAicGFuZG9jIiwgY2FwdGlvbiA9ICJTZXggZ3JvdXBpbmdzIikKCmFnZSA8LSBmcm9tSlNPTigiaHR0cDovL2ZpbmdlcnRpcHMucGhlLm9yZy51ay9hcGkvYWdlcyIpCmFnZSAlPiUgaGVhZCAlPiUga25pdHI6OmthYmxlKGZvcm1hdCA9ICJwYW5kb2MiLCBjYXB0aW9uID0gIkFnZSBncm91cGluZ3MiKQoKYXJlYXR5cGUgPC0gZnJvbUpTT04oImh0dHA6Ly9maW5nZXJ0aXBzLnBoZS5vcmcudWsvYXBpL2FyZWFfdHlwZXMiKQphcmVhdHlwZSAlPiUgaGVhZCAlPiUga25pdHI6OmthYmxlKGZvcm1hdCA9ICJwYW5kb2MiLCBjYXB0aW9uID0gIkFyZWF0eXBlcyIpCmBgYAoKT3ZlcmFsbCBGaW5nZXJ0aXBzIGhhcyBgciBucm93KGFnZSlgIGFnZWJhbmQgY2F0ZWdvcmllcyBhbmQgYHIgbnJvdyhhcmVhdHlwZSlgIGFyZWEgdHlwZXMuCgojIyBQcm9maWxlCgpBcyBhIG1vcmUgY29tcGxleCBleGFtcGxlLCB3ZSBjYW4gZXh0cmFjdCBpbmZvcm1hdGlvbiBhYm91dCB3aGljaCBwcm9maWxlcyB0aGUgQVBJIGdpdmVzIGFjY2VzcyB0by4gVGhlIEFQSSBnaXZlcyBhY2Nlc3MgdG8gYHIgbnJvdyhwcm9maWxlcylgIHByb2ZpbGVzIC0gd2UganVzdCBzaG93IHRoZSBmaXJzdCA2IGhlcmUuCgpgYGB7ciwgZWNobz1UUlVFfQpwcm9maWxlcyA8LSBmcm9tSlNPTigiaHR0cDovL2ZpbmdlcnRpcHMucGhlLm9yZy51ay9hcGkvcHJvZmlsZXMiLCBzaW1wbGlmeURhdGFGcmFtZSA9IFRSVUUpCnByb2ZpbGVzICU+JSBoZWFkICU+JSBzZWxlY3QoLUtleSkgJT4lCiAgIGtuaXRyOjprYWJsZShmb3JtYXQgPSAicGFuZG9jIiwgY2FwdGlvbiA9ICJQcm9maWxlcyIpCmBgYAoKRXh0cmFjdGluZyBwcm9maWxlIGluZm9ybWF0aW9uIHByb2R1Y2VzIGEgbmVzdGVkIGxpc3Qgb2YgdGhlIEdyb3VwSWRzIGZvciBlYWNoIHByb2ZpbGUsIGFuZCB0aGUgbnVtYmVyIG9mIGdyb3VwcyBvciBkb21haW5zIHdpdGhpbiBlYWNoIHByb2ZpbGUuIEZvciBleGFtcGxlLCBoZWFsdGggcHJvZmlsZXMgaGF2ZSA3IGdyb3VwcywgdGhlIG5hdGlvbmFsLWNoaWxkLW1lYXN1cmVtZW50LXByb2dyYW1tZSAyIGFuZCBzbyBvbi4KCldlIGNhbiBleHRyYWN0IHRoZSBwcm9maWxlIElEcywgZ3JvdXBJRHMgYW5kIG5hbWVzIGFzIGEgdGlkeSBkYXRhIGZyYW1lIHVzaW5nIHRoZSBgdGlkeWpzb25gIHBhY2thZ2UuCgpgYGB7ciwgZWNobz1UUlVFfQpsaWJyYXJ5KHRpZHlqc29uKQoKcHJvZl9qc29uIDwtICJodHRwOi8vZmluZ2VydGlwcy5waGUub3JnLnVrL2FwaS9wcm9maWxlcyIKCnByb2YgPC0gcHJvZl9qc29uICU+JQogIGdhdGhlcl9hcnJheSAlPiUgCiAgc3ByZWFkX3ZhbHVlcyhJRCA9IGpudW1iZXIoIklkIiksCiAgICAgICAgICAgICAgICBOYW1lID0ganN0cmluZygiTmFtZSIpLAogICAgICAgICAgICAgICAgS2V5ID0ganN0cmluZygiS2V5IikKICApICU+JQogIGVudGVyX29iamVjdCgiR3JvdXBJZHMiKSAlPiUKICBnYXRoZXJfYXJyYXkgJT4lCiAgYXBwZW5kX3ZhbHVlc19udW1iZXIoImdyb3VwaWQiKSAlPiUKICBzZWxlY3QoSUQsIE5hbWUsIEtleSwgZ3JvdXBpZCkKCnByb2YKCmBgYAoKVGhlIG5leHQgbGV2ZWwgb2YgY29tcGxleGl0eSBpcyB0byBleHRyYWN0IHRoZSBsYXRlc3QgZGF0YSBmb3IgYSBzaW5nbGUgcHJvZmlsZS4gV2UnbGwgc3RhcnQgd2l0aCBqdXN0IHRoZSBkYXRhIGZvciBhIHNpbmdsZSBhcmVhIHR5cGUuIFRoZSBzdHJ1Y3R1cmUgb2YgRmluZ2VydGlwcyBtZWFucyB3ZSBuZWVkIHRvIHBhc3MgYSBwcm9maWxlIGlkLCBncm91cCBpZCwgYXJlYSBjb2RlIGFuZCBwYXJlbnRfYXJlYSBjb2RlIHRvIHRoZSBBUEkuIFdlJ2xsIGRvIHRoaXMgZm9yIHRoZSBIZWFsdGggUHJvZmlsZSBmb3IgVVRMQXMuCgpgYGB7cn0KIyMgU2VsZWN0IGEgcHJvZmlsZQpocCA8LSBwcm9mICU+JQogIGZpbHRlcihOYW1lID09ICJIZWFsdGggUHJvZmlsZXMiKQoKIyMgU2VsZWN0IGEgZ3JvdXAgKGRvbWFpbikKZ3JvdXBpZCA8LSBwcm9mICU+JQogIGZpbHRlcihncm91cGlkID09IDE5MzgxMzI2OTQpCgojIyBTZWxlY3QgYW4gYXJlYSB0eXBlIChVVExBKQoKYXJlYSA8LSBhcmVhdHlwZSAlPiUKICBmaWx0ZXIoSWQgPT0gMTAyKQoKIyMgU2VsZWN0IGEgcGFyZW50IGNvZGUKCmFyZWF0eXBlR09SSUQgPC0gZnJvbUpTT04oImh0dHA6Ly9maW5nZXJ0aXBzLnBoZS5vcmcudWsvYXBpL2FyZWFfdHlwZXMiKSAlPiUKICAgICAgICBmaWx0ZXIoTmFtZSA9PSAiR292ZXJubWVudCBPZmZpY2UgUmVnaW9uIikgJT4lCiAgICAgICAgc2VsZWN0KElkKSAlPiUKICAgICAgICBhcy5udW1lcmljKCkKCnBhcmVudGFyZWFjb2RlcyA8LSBmcm9tSlNPTihwYXN0ZTAoImh0dHA6Ly9maW5nZXJ0aXBzLnBoZS5vcmcudWsvYXBpL2FyZWFzL2J5X2FyZWFfdHlwZT9hcmVhX3R5cGVfaWQ9IixhcmVhdHlwZUdPUklEKSkgCgoKCiMjIExldCdzIGNob29zZSBFYXN0IE1pZGxhbmRzLi4uRTEyMDAwMDA0LiBXZSB0YWtlIHRoZSByb290IHF1ZXJ5IGFuZCBwb3B1bGF0ZSBpdCB3aXRoIHRoZSByZWxldmFudCBpZHMuCgplbSA8LSBwYXJlbnRhcmVhY29kZXMgJT4lCiAgZmlsdGVyKE5hbWUgPT0gIkVhc3QgTWlkbGFuZHMgcmVnaW9uIikKCmhwX3VybCA8LSBwYXN0ZTAoImh0dHA6Ly9maW5nZXJ0aXBzLnBoZS5vcmcudWsvYXBpL2xhdGVzdF9kYXRhL2FsbF9pbmRpY2F0b3JzX2luX3Byb2ZpbGVfZ3JvdXBfZm9yX2NoaWxkX2FyZWFzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI/cHJvZmlsZV9pZD0iLDI2LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiZncm91cF9pZD0iLDE5MzgxMzI2OTQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiJmFyZWFfdHlwZV9pZD0iLDEwMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICImcGFyZW50X2FyZWFfY29kZT0iLGVtJENvZGUpCgpgYGAKCldlJ2xsIGZpcnN0IGltcG9ydCB0aGUgZGF0YSB1c2luZyB0aGUgYGpzb25saXRlYCBwYWNrYWdlIGFuZCBsb29rIGF0IHRoZSBkYXRhIHN0cnVjdHVyZQoKYGBge3IsIGVjaG89VFJVRX0KCmhwX2RmIDwtIGZyb21KU09OKGhwX3VybCwgc2ltcGxpZnlEYXRhRnJhbWUgPSBUUlVFKQoKaHBfZGYgJT4lIHN0cihtYXgubGV2ZWwgPSAzLCBsaXN0LmxlbiA9IDMpCgpgYGAKClRoZSBkYXRhIGlzIGNvbXBsZXguIFRoZSBkYXRhIHZhbHVlcyBhcmUgY29udGFpbmVkIGluIGEgbmVzdGVkIHNlcmllcyBvZiBkYXRhIGZyYW1lcyBhcyBwYXJ0IG9mIHRoZSBgJERhdGFgIHZhcmlhYmxlLiBUaGUgaW5kaWNhdG9yIElEcyBhcmUgc3RvcmVkIGluIHRoZSBgJElJRGAgdmFyaWFibGUgYnV0IHRoZSB2YXJpYWJsZSBuYW1lcyBhcmUgbm90IHN0b3JlZCBoZXJlIGFuZCB3aWxsIG5lZWQgdG8gYmUgZXh0cmFjdGVkIHdpdGggYSBkaWZmZXJlbnQgY2FsbCB0byB0aGUgQVBJLiAKCldlIGNhbiB1c2UgdGhlIGB0aWR5anNvbmAgcGFja2FnZSB0byBleHRyYWN0IHRoZSBkYXRhIGFuZCBpbmRpY2F0b3IgaWRzLgoKYGBge3IsIGVjaG89VFJVRX0KCmhwMSA8LSBocF91cmwgJT4lCiAgZ2F0aGVyX2FycmF5ICU+JQogIHNwcmVhZF92YWx1ZXMoaW5kX2lkID0gam51bWJlcigiSUlEIikpICU+JQogIGVudGVyX29iamVjdCgiRGF0YSIpICU+JQogIGdhdGhlcl9hcnJheSAlPiUKICBzcHJlYWRfdmFsdWVzKAogICAgYXJlYSA9IGpzdHJpbmcoIkFyZWFDb2RlIiksIAogICAgdmFsdWUgPSBqbnVtYmVyKCJWYWwiKSwgCiAgICBsY2kgPSBqbnVtYmVyKCJMb0NJIiksIAogICAgdWNpID0gam51bWJlcigiVXBDSSIpLAogICAgZGVub20gPSBqbnVtYmVyKCJEZW5vbSIpLAogICAgY291bnQgPSBqbnVtYmVyKCJDb3VudCIpLCAKICAgIGFnZSA9IGpudW1iZXIoIkFnZUlkIiksIAogICAgc2V4ID0gam51bWJlcigiU2V4SWQiKSAKICAgICklPiUKICBzZWxlY3QoaW5kX2lkLCBhcmVhLCBhZ2UsIHNleCwgdmFsdWUsIGxjaSwgdWNpLCBkZW5vbSwgY291bnQpCgoKCmhwMiA8LSBocF91cmwgJT4lCiAgZ2F0aGVyX2FycmF5KCkgJT4lCiAgc3ByZWFkX3ZhbHVlcyhpbmRfaWQgPSBqbnVtYmVyKCJJSUQiKSkgJT4lCiAgZW50ZXJfb2JqZWN0KCJHcm91cGluZyIpICU+JQogIGdhdGhlcl9hcnJheSAlPiUKICBzcHJlYWRfdmFsdWVzKAogICAgdGltZSA9IGpzdHJpbmcoIlBlcmlvZCIpCiAgKSAKCmhwMSAlPiUKICBsZWZ0X2pvaW4oaHAyKSAlPiUKICBzZWxlY3QoaW5kX2lkLCBhcmVhLCB0aW1lLCB2YWx1ZSwgbGNpLCB1Y2ksIGNvdW50LCBkZW5vbSwgYWdlLCBzZXgpICU+JQogIGRpc3RpbmN0IAoKYGBgCgpXZSBoYXZlIG5vdyBleHRyYWN0ZWQgYSBkYXRhIGZyYW1lIG9mIGluZGljYXRvciB2YWx1ZXMsIHRpbWUgcGVyaW9kcywgc2V4IGFuZCBhZ2UgaWRzIGZvciBlYWNoIGluZGljYXRvciBmb3IgZWFjaCBhcmVhLgogCiMjIEV4dHJhY3RpbmcgaW5kaWNhdG9yIG5hbWVzCgpUbyBvYnRhaW4gaW5kaWNhdG9yIG5hbWVzIGZyb20gdGhlIEFQSSB3ZSBuZWVkIHRvIGNhbGwgSW5kaWNhdG9yTWV0YWRhdGEgIGh0dHBzOi8vZmluZ2VydGlwcy5waGUub3JnLnVrL2FwaSMhL0luZGljYXRvck1ldGFkYXRhL0luZGljYXRvck1ldGFkYXRhX0dldEluZGljYXRvck1ldGFkYXRhIHRvIHdoaWNoIHdlIGNhbiBwYXNzIGEgbGlzdCBvZiBpbmRpY2F0b3IgaWRzLgoKYGBge3J9CiMjIEZpcnN0IGV4dHJhY3QgaW5kaWNhdG9yIElEcwoKaW5kX2lkcyA8LSB1bmlxdWUoaHAxJGluZF9pZCkKaW5kX2lkcyA8LSBwYXN0ZShpbmRfaWRzLCBjb2xsYXBzZSA9ICIlMkMiKQppbmRfaWRzCgojIyBQYXNzIHRoZSBzdHJpbmcgdG8gdGhlIG1ldGFkYXRhIHVybAoKaW5kX3VybCA8LSBwYXN0ZTAoImh0dHBzOi8vZmluZ2VydGlwcy5waGUub3JnLnVrL2FwaS9pbmRpY2F0b3JfbWV0YWRhdGEvYnlfaW5kaWNhdG9yX2lkP2luZGljYXRvcl9pZHM9IixpbmRfaWRzKQoKaW5kX25hbWVzIDwtIGZyb21KU09OKGluZF91cmwpCmluZF9uYW1lcyAlPiUgc3RyCgojIyBFeHRyYWN0IG5hbWVzCgppbmRfdXJsICU+JQogIGdhdGhlcl9vYmplY3QoKSAlPiUKICBzcHJlYWRfYWxsKCkgJT4lCiAgc2VsZWN0KGluZF9pZCA9IElJRCwgRGVzY3JpcHRpdmUuTmFtZUxvbmcpLT5pbmRfbmFtZXMKCiMjIEFuZCBhdHRhY2ggdG8gb3VyIGRhdGEgZnJhbWUKCmhwMSAlPiUKICBsZWZ0X2pvaW4oaHAyKSAlPiUKICBzZWxlY3QoaW5kX2lkLCBhcmVhLCB0aW1lLCB2YWx1ZSwgbGNpLCB1Y2ksIGNvdW50LCBkZW5vbSwgYWdlLCBzZXgpICU+JQogIGRpc3RpbmN0IC0+IGhwX2RmCgpocF9kZiAlPiUKICBsZWZ0X2pvaW4oaW5kX25hbWVzKSAlPiUKICBoZWFkICU+JQogIGtuaXRyOjprYWJsZShjYXB0aW9uID0gIkluZGljYXRvciBuYW1lcyIsIGZvcm1hdCA9ICJwYW5kb2MiKQogIAoKYGBgCgojIyBTY2FsaW5nIHVwIGFuZCBzaW1wbGlmeWluZwoKVG8gZXh0cmFjdCAqYWxsKiB0aGUgaW5kaWNhdG9ycyBmb3IgdGhlIHByb2ZpbGVzIHdlIG5lZWQgdG8gbG9vcCB0aHJvdWdoIGdyb3VwIElEcyBhbmQgcGFyZW50IGNvZGVzLiBXZSdsbCB1c2UgYSBkaWZmZXJlbnQgZXhhbXBsZSAtIGRhdGEgZnJvbSB0aGUgTmF0aW9uYWwgQ2hpbGQgTWVhc3VyZW1lbnQgUHJvZ3JhbW1lIChOQ01QKS4KCmBgYHtyLCBlY2hvPVRSVUUsIGNhY2hlPVRSVUV9CiMjIEZpcnN0bHkgZXh0cmFjdCB0aGUgcHJvZmlsZSBJRApocGlkIDwtIHByb2ZbcHJvZiRLZXkgPT0gIm5hdGlvbmFsLWNoaWxkLW1lYXN1cmVtZW50LXByb2dyYW1tZSIsIDFdW1sxXV1bMV0KCiMjIFRoZW4gZXh0cmFjdCBncm91cCAoZG9tYWluKSBJRHMKCmdyb3VwaWRzIDwtIHByb2ZbcHJvZiRLZXkgPT0gIm5hdGlvbmFsLWNoaWxkLW1lYXN1cmVtZW50LXByb2dyYW1tZSIsIDRdCgojIyBDcmVhdGUgYW5kIGVtcHR5IGRhdGEgZnJhbWUKZGYgPC0gZGF0YV9mcmFtZSgpCgojIyBMb29wIHRocm91Z2ggZ3JvdXAgSURzIGFuZCBwYXJlbnQgY29kZXMKCmZvcihncm91cGlkIGluIGdyb3VwaWRzKXsKICBmb3IocGFyZW50Y29kZSBpbiBwYXJlbnRhcmVhY29kZXMkQ29kZSkgewogICAKICAgIAogICAgIyMgQ3JlYXRlIEFQSSBVUkxzCiAgICAgCiAgICBocF91cmwxIDwtIHBhc3RlMCgiaHR0cDovL2ZpbmdlcnRpcHMucGhlLm9yZy51ay9hcGkvbGF0ZXN0X2RhdGEvYWxsX2luZGljYXRvcnNfaW5fcHJvZmlsZV9ncm91cF9mb3JfY2hpbGRfYXJlYXMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIj9wcm9maWxlX2lkPSIsaHBpZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICImZ3JvdXBfaWQ9Iixncm91cGlkLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiZhcmVhX3R5cGVfaWQ9IiwxMDIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiJnBhcmVudF9hcmVhX2NvZGU9IixwYXJlbnRjb2RlKQogCiAgICAjIyBFeHRyYWN0IGRhdGEKICAgICAgIAogICAgZGF0YSA8LSBocF91cmwxICU+JQogIGdhdGhlcl9hcnJheSAlPiUKICBzcHJlYWRfdmFsdWVzKGluZF9pZCA9IGpudW1iZXIoIklJRCIpKSAlPiUKICBlbnRlcl9vYmplY3QoIkRhdGEiKSAlPiUKICBnYXRoZXJfYXJyYXkgJT4lCiAgc3ByZWFkX3ZhbHVlcygKICAgIGFyZWEgPSBqc3RyaW5nKCJBcmVhQ29kZSIpLCAKICAgIHZhbHVlID0gam51bWJlcigiVmFsIiksIAogICAgbGNpID0gam51bWJlcigiTG9DSSIpLCAKICAgIHVjaSA9IGpudW1iZXIoIlVwQ0kiKSwKICAgIGRlbm9tID0gam51bWJlcigiRGVub20iKSwKICAgIGNvdW50ID0gam51bWJlcigiQ291bnQiKSwgCiAgICBhZ2UgPSBqbnVtYmVyKCJBZ2VJZCIpLCAKICAgIHNleCA9IGpudW1iZXIoIlNleElkIikgCiAgICApJT4lIAogIHNlbGVjdChpbmRfaWQsIGFyZWEsIGFnZSwgc2V4LCB2YWx1ZSwgbGNpLCB1Y2ksIGRlbm9tLCBjb3VudCkKICAgIAogICAgIyMgQnVpbGQgZGF0YSBmcmFtZQogICAgCiAgICBkZiA8LSBiaW5kX3Jvd3MoZGYsIGRhdGEpCiAgfQp9CiAgCiMjIEFkZCB0aW1lIHBlcmlvZCAodXNlcyB0aGUgc2FtZSBsb29waW5nIHByb2Nlc3MpCgpkZnQgPC0gZGF0YV9mcmFtZSgpCgpmb3IoZ3JvdXBpZCBpbiBncm91cGlkcyl7CiAgZm9yKHBhcmVudGNvZGUgaW4gcGFyZW50YXJlYWNvZGVzJENvZGUpIHsKICAgIApocF91cmwxIDwtIHBhc3RlMCgiaHR0cDovL2ZpbmdlcnRpcHMucGhlLm9yZy51ay9hcGkvbGF0ZXN0X2RhdGEvYWxsX2luZGljYXRvcnNfaW5fcHJvZmlsZV9ncm91cF9mb3JfY2hpbGRfYXJlYXMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIj9wcm9maWxlX2lkPSIsaHBpZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICImZ3JvdXBfaWQ9Iixncm91cGlkLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiZhcmVhX3R5cGVfaWQ9IiwxMDIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiJnBhcmVudF9hcmVhX2NvZGU9IixwYXJlbnRjb2RlKSAgICAKICAgIApkYXRhMSA8LSBocF91cmwxICU+JQogIGdhdGhlcl9hcnJheSgpICU+JQogIHNwcmVhZF92YWx1ZXMoaW5kX2lkID0gam51bWJlcigiSUlEIikpICU+JQogIGVudGVyX29iamVjdCgiR3JvdXBpbmciKSAlPiUKICBnYXRoZXJfYXJyYXkgJT4lCiAgc3ByZWFkX3ZhbHVlcygKICAgIHRpbWUgPSBqc3RyaW5nKCJQZXJpb2QiKQogIAogICkgCgpkZnQgPC0gYmluZF9yb3dzKGRmdCwgZGF0YTEpCiAgfQp9ICAKCgojIyAgSm9pbiB0aW1lIHBlcmlvZHMgdG8gbWFpbiBkYXRhIGZyYW1lCgoKZGYgPC0gZGYgJT4lCiAgbGVmdF9qb2luKGRmdCkgJT4lCiAgc2VsZWN0KGluZF9pZCwgYXJlYSwgdGltZSwgdmFsdWUsIGxjaSwgdWNpLCBjb3VudCwgZGVub20sIGFnZSwgc2V4KSAlPiUKICBkaXN0aW5jdAoKZGYgJT4lIGhlYWQKICAKCgpgYGAKCiMjIEF0dGFjaGluZyBmdXJ0aGVyIG1ldGFkYXRhCgpXZSBjYW4gYWRkIGFnZSBhbmQgZ2VuZGVyIGRlc2NyaXB0aW9ucyBhbmQgaW5kaWNhdG9yIG5hbWVzIHRvIHRoZSBtYWluIGRhdGEgZnJhbWUuCgpgYGB7ciwgZWNobz1UUlVFfQoKc2V4IDwtIHNleCAlPiUKICByZW5hbWUoc2V4ID0gSWQsIGdlbmRlciA9IE5hbWUpCgphZ2UgPC0gYWdlICU+JQogIHJlbmFtZShhZ2UgPSBJZCkKCgppbmRfaHAgPC0gcGFzdGUodW5pcXVlKGRmJGluZF9pZCksIGNvbGxhcHNlID0gIiUyQyIpCgppbmRfdXJsIDwtIHBhc3RlMCgiaHR0cHM6Ly9maW5nZXJ0aXBzLnBoZS5vcmcudWsvYXBpL2luZGljYXRvcl9tZXRhZGF0YS9ieV9pbmRpY2F0b3JfaWQ/aW5kaWNhdG9yX2lkcz0iLGluZF9ocCkKCmluZF91cmwgJT4lCiAgZ2F0aGVyX29iamVjdCgpICU+JQogIHNwcmVhZF9hbGwoKSAlPiUKICBzZWxlY3QoaW5kX2lkID0gSUlELCBEZXNjcmlwdGl2ZS5OYW1lTG9uZyktPmluZF9uYW1lcwoKZGYgPC0gZGYgJT4lCiAgbGVmdF9qb2luKGluZF9uYW1lcywgYnkgPSAiaW5kX2lkIikgCgpkZiAlPiUKICAjIHNlbGVjdCgtYyhEZXNjcmlwdGl2ZS5OYW1lTG9uZy54LCBEZXNjcmlwdGl2ZS5OYW1lTG9uZy55LnksIERlc2NyaXB0aXZlLk5hbWVMb25nLngueCkpICU+JQogIHNlbGVjdChJbmRfbmFtZSA9IERlc2NyaXB0aXZlLk5hbWVMb25nLCAyOjEwKSAlPiUKICBsZWZ0X2pvaW4oYWdlKSAlPiUKICBsZWZ0X2pvaW4oc2V4KSAtPiBkZl9hbmFseXNpcwoKCgpgYGAKCldlIG5vdyBoYXZlIGFuIGFuYWx5dGljYWwgZGF0YSBmcmFtZSBgZGZfYW5hbHlzaXNgIHdlIGNhbiBleHBsb3JlIGZ1cnRoZXIuIFdlIGNhbiBmb2xsb3cgdGhlIHNhbWUgcHJvY2Vzc2VzIHRvIGV4dHJhY3QgYW5kIGNvbWJpbmUgZGF0YSBmcm9tIG90aGVyIHByb2ZpbGVzIG9yIG90aGVyIGFyZWFzLCBhZGQgYXJlYSBuYW1lcywgYW5kIGFkZCBvdGhlciBlbGVtZW50cyBvZiBtZXRhZGF0YS4KCiMjIERhdGEgZXhwbG9yYXRpb24KCkdpdmVuIHRoZSBgZGZfYW5hbHlzaXNgIGRhdGEgZnJhbWUgd2UgY2FuIGV4cGxvcmUgZnVydGhlci4gIAoKYGBge3IsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTYsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgoKIyMgLTEgaXMgdXNlZCBpbiBGaW5nZXJ0aXBzIGZvciBtaXNzaW5nIG9yIHN1cHByZXNzZWQgZGF0YS4gCiMjIFdlIG5lZWQgdG8gcmVwbGFjZSB3aXRoIE5BcyBmb3IgYW5hbHlzaXMgaW4gUgoKZGZfYW5hbHlzaXMgPC0gZGZfYW5hbHlzaXMgJT4lCiAgbXV0YXRlKHZhbHVlID0gaWZlbHNlKHZhbHVlID09IC0xLCBOQSwgdmFsdWUpKSAlPiUKICBzZWxlY3QoLWMoYWdlLCBzZXgpKQoKIyMgQm94cGxvdHMgb2YgaW5kaWNhdG9ycwoKZGZfYW5hbHlzaXMgJT4lCiAgZ2dwbG90KGFlcyhJbmRfbmFtZSwgdmFsdWUsIGZpbGwgPSB0aW1lKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBjb29yZF9mbGlwKCkgKwogIHRoZW1lKGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gNiksIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSArCiAgbGFicyh0aXRsZSA9ICJCb3hwbG90IG9mIHByb2ZpbGUgXG5pbmRpY2F0b3IgdmFsdWVzIiwgeCA9ICIiKQoKYGBgCgoKYGBge3IsIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmRmX2FuYWx5c2lzICU+JQogIGdyb3VwX2J5KGFyZWEpICU+JQogIGZpbHRlcihpcy5uYSh2YWx1ZSkpICU+JQogIHNlbGVjdChhcmVhLCBJbmRfbmFtZSwgdmFsdWUpCgoKYGBgCgojIyBFeHRyYWN0aW5nIEdQIHByYWN0aWNlIGRhdGEKClNjYWxpbmcgdXAgZnVydGhlciwgdGhlIGZvbGxvd2luZyBjb2RlIGFkYXB0ZWQgZnJvbSBhYm92ZSwgZXh0cmFjdHMgdGhlIGxhdGVzdCBkYXRhIGZyb20gdGhlIFtHZW5lcmFsIFByYWN0aWNlIFByb2ZpbGVzXShodHRwOi8vZmluZ2VydGlwcy5waGUub3JnLnVrL2dlbmVyYWwtcHJhY3RpY2UpLgoKYGBge3IsIGNhY2hlPSBUUlVFfQojIyBGaXJzdGx5IGV4dHJhY3QgdGhlIHByb2ZpbGUgSUQKZ3BpZCA8LSBwcm9mW3Byb2YkS2V5ID09ICJnZW5lcmFsLXByYWN0aWNlIiwgMV1bWzFdXVsxXQoKIyMgVGhlbiBleHRyYWN0IGdyb3VwIChkb21haW4pIElEcwoKZ3JvdXBpZHMgPC0gcHJvZltwcm9mJEtleSA9PSAiZ2VuZXJhbC1wcmFjdGljZSIsIDRdCgphcmVhdHlwZUNDR0lEIDwtIGZyb21KU09OKCJodHRwOi8vZmluZ2VydGlwcy5waGUub3JnLnVrL2FwaS9hcmVhX3R5cGVzIikgJT4lCiAgICAgICAgZmlsdGVyKE5hbWUgPT0gIkNsaW5pY2FsIENvbW1pc3Npb25pbmcgR3JvdXAiKSAlPiUKICAgICAgICBzZWxlY3QoSWQpICU+JQogICAgICAgIGFzLm51bWVyaWMoKQoKcGFyZW50YXJlYWNvZGVzIDwtIGZyb21KU09OKHBhc3RlMCgiaHR0cDovL2ZpbmdlcnRpcHMucGhlLm9yZy51ay9hcGkvYXJlYXMvYnlfYXJlYV90eXBlP2FyZWFfdHlwZV9pZD0iLGFyZWF0eXBlQ0NHSUQpKSAKCiMjIENyZWF0ZSBhbmQgZW1wdHkgZGF0YSBmcmFtZQpkZmdwIDwtIGRhdGFfZnJhbWUoKQoKIyMgTG9vcCB0aHJvdWdoIGdyb3VwIElEcyBhbmQgcGFyZW50IGNvZGVzCgpmb3IoZ3JvdXBpZCBpbiBncm91cGlkcyl7CiAgZm9yKHBhcmVudGNvZGUgaW4gcGFyZW50YXJlYWNvZGVzJENvZGUpIHsKICAgCiAgICAKICAgICMjIENyZWF0ZSBBUEkgVVJMcwogICAgIAogICAgZ3BfdXJsMSA8LSBwYXN0ZTAoImh0dHA6Ly9maW5nZXJ0aXBzLnBoZS5vcmcudWsvYXBpL2xhdGVzdF9kYXRhL2FsbF9pbmRpY2F0b3JzX2luX3Byb2ZpbGVfZ3JvdXBfZm9yX2NoaWxkX2FyZWFzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI/cHJvZmlsZV9pZD0iLGdwaWQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiJmdyb3VwX2lkPSIsZ3JvdXBpZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICImYXJlYV90eXBlX2lkPSIsNywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICImcGFyZW50X2FyZWFfY29kZT0iLHBhcmVudGNvZGUpCiAKICAgICMjIEV4dHJhY3QgZGF0YQogICAgICAgCiAgICBkYXRhIDwtIGdwX3VybDEgJT4lCiAgZ2F0aGVyX2FycmF5ICU+JQogIHNwcmVhZF92YWx1ZXMoaW5kX2lkID0gam51bWJlcigiSUlEIikpICU+JQogIGVudGVyX29iamVjdCgiRGF0YSIpICU+JQogIGdhdGhlcl9hcnJheSAlPiUKICBzcHJlYWRfdmFsdWVzKAogICAgYXJlYSA9IGpzdHJpbmcoIkFyZWFDb2RlIiksIAogICAgdmFsdWUgPSBqbnVtYmVyKCJWYWwiKSwgCiAgICBsY2kgPSBqbnVtYmVyKCJMb0NJIiksIAogICAgdWNpID0gam51bWJlcigiVXBDSSIpLAogICAgZGVub20gPSBqbnVtYmVyKCJEZW5vbSIpLAogICAgY291bnQgPSBqbnVtYmVyKCJDb3VudCIpLCAKICAgIGFnZSA9IGpudW1iZXIoIkFnZUlkIiksIAogICAgc2V4ID0gam51bWJlcigiU2V4SWQiKSAKICAgICklPiUgCiAgc2VsZWN0KGluZF9pZCwgYXJlYSwgYWdlLCBzZXgsIHZhbHVlLCBsY2ksIHVjaSwgZGVub20sIGNvdW50KQogICAgCiAgICAjIyBCdWlsZCBkYXRhIGZyYW1lCiAgICAKICAgIGRmZ3AgPC0gYmluZF9yb3dzKGRmZ3AsIGRhdGEpCiAgfQp9CiAgCiMjIEFkZCB0aW1lIHBlcmlvZCAodXNlcyB0aGUgc2FtZSBsb29waW5nIHByb2Nlc3MpCgpkZnRncCA8LSBkYXRhX2ZyYW1lKCkKCmZvcihncm91cGlkIGluIGdyb3VwaWRzKXsKICBmb3IocGFyZW50Y29kZSBpbiBwYXJlbnRhcmVhY29kZXMkQ29kZSkgewogICAgCmdwX3VybDEgPC0gcGFzdGUwKCJodHRwOi8vZmluZ2VydGlwcy5waGUub3JnLnVrL2FwaS9sYXRlc3RfZGF0YS9hbGxfaW5kaWNhdG9yc19pbl9wcm9maWxlX2dyb3VwX2Zvcl9jaGlsZF9hcmVhcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiP3Byb2ZpbGVfaWQ9IixncGlkLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiZncm91cF9pZD0iLGdyb3VwaWQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiJmFyZWFfdHlwZV9pZD0iLDcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiJnBhcmVudF9hcmVhX2NvZGU9IixwYXJlbnRjb2RlKSAgICAKICAgIApkYXRhMSA8LSBncF91cmwxICU+JQogIGdhdGhlcl9hcnJheSgpICU+JQogIHNwcmVhZF92YWx1ZXMoaW5kX2lkID0gam51bWJlcigiSUlEIikpICU+JQogIGVudGVyX29iamVjdCgiR3JvdXBpbmciKSAlPiUKICBnYXRoZXJfYXJyYXkgJT4lCiAgc3ByZWFkX3ZhbHVlcygKICAgIHRpbWUgPSBqc3RyaW5nKCJQZXJpb2QiKQogIAogICkgCgpkZnRncCA8LSBiaW5kX3Jvd3MoZGZ0Z3AsIGRhdGExKQogIH0KfSAgCgojIyBTZWxlY3QgZGlzdGluY3QgSURzIGZvciBsYXRlc3QgdGltZSBwZXJpb2QKCmRmdGdwIDwtZGZ0Z3AgJT4lIGRpc3RpbmN0KGluZF9pZCwgdGltZSkKCiMjIEpvaW4gdGltZSBwZXJpb2RzIHRvIG1haW4gZGF0YSBmcmFtZQpkZmdwIDwtIGRmZ3AgJT4lIAogIGxlZnRfam9pbihkZnRncCkKCgojIFJlbmFtZSBzZXggdmFyaWFibGUKZ3BzZXggPC0gc2V4ICU+JQogIHJlbmFtZShzZXggPSBJZCwgZ2VuZGVyID0gTmFtZSkKCiMgUmVuYW1lIGFnZSB2YXJpYWJsZQphZ2UgPC0gYWdlICU+JQogIHJlbmFtZShhZ2UgPSBJZCkKCiMjIENyZWF0ZSBhIGxpc3Qgb2YgaW5kaWNhdG9yIElEcwppbmRfZ3AgPC0gcGFzdGUodW5pcXVlKGRmZ3AkaW5kX2lkKSwgY29sbGFwc2UgPSAiJTJDIikKCiMjIFBhc3QgbGlzdCB0byBhcGkgCmluZF91cmwgPC0gcGFzdGUwKCJodHRwczovL2ZpbmdlcnRpcHMucGhlLm9yZy51ay9hcGkvaW5kaWNhdG9yX21ldGFkYXRhL2J5X2luZGljYXRvcl9pZD9pbmRpY2F0b3JfaWRzPSIsaW5kX2dwKQoKCiMjIEV4dHJhY3QgaW5kaWNhdG9yIG5hbWVzCmluZF91cmwgJT4lCiAgZ2F0aGVyX29iamVjdCgpICU+JQogIHNwcmVhZF9hbGwoKSAlPiUKICBzZWxlY3QoaW5kX2lkID0gSUlELCBEZXNjcmlwdGl2ZS5OYW1lTG9uZyktPmluZF9uYW1lcwoKaW5kX25hbWVzCgojIyBKb2luIG5hbWVzCmRmZ3BhIDwtIGRmZ3AgJT4lCiAgbGVmdF9qb2luKGluZF9uYW1lcywgYnkgPSAiaW5kX2lkIikgCgpkZmdwYQoKIyMgSm9pbiBhbmQgc2V4IG1ldGFkYWF0YSwgcmVtb3ZlIHVud2FudGVkIGZpZWxkcyBhbmQgc2F2ZSB0byBhIG5ldyBvYmplY3QgZm9yIGFuYWx5c2lzCmRmZ3BhICU+JQogIGxlZnRfam9pbihhZ2UpICU+JQogIGxlZnRfam9pbihncHNleCkgLT4gZGZfYW5hbHlzaXNfZ3AKCmRmX2FuYWx5c2lzX2dwCgpgYGAKCldlIG5vdyBoYXZlIGFuIGFuYWx5dGljYWwgZGF0YSBzZXQgb2YgdGhlIGxhdGVzdCBnZW5lcmFsIHByYWN0aWNlIGluZGljYXRvcnMgd2hpY2ggY29udGFpbnMgYHIgbGVuZ3RoKHVuaXF1ZShkZl9hbmFseXNpc19ncCRpbmRpY2F0b3IpKWAgaW5kaWNhdG9ycyAtIHdlIGFyZSBqdXN0IHNob3dpbmcgdGhlIGZpcnN0IGZldyByb3dzIG9mIGByIG5yb3coZGZfYW5hbHlzaXNfZ3ApYC4KCmBgYHtyIEFuYWx5dGljYWwgZGF0YXNldCwgZWNobz1UUlVFfQoKZGZfYW5hbHlzaXNfZ3AgPC0gZGZfYW5hbHlzaXNfZ3AgJT4lCiAgc2VsZWN0KGluZF9pZCwgaW5kaWNhdG9yID0gRGVzY3JpcHRpdmUuTmFtZUxvbmcueCwgYXJlYSwgYWdlID0gTmFtZSwgc2V4ID0gZ2VuZGVyLCB0aW1lLCB2YWx1ZSwgbGNpLCB1Y2ksIGNvdW50LCBkZW5vbSkKCmRmX2FuYWx5c2lzX2dwICU+JSByZWFkcjo6d3JpdGVfY3N2KCJncF9hbmFseXNpcy5jc3YiKSAjIyBzYXZlIHRoZSBmaWxlCgpkZl9hbmFseXNpc19ncCAlPiUKICBhcnJhbmdlKGluZGljYXRvciwgYXJlYSkgJT4lCiAgaGVhZCgyMCkKCgpgYGAKCmBgYHtyIFNlbGVjdCBwcmV2YWxlbmNlIGVzdGltYXRlcywgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KCmxpYnJhcnkoc3RyaW5ncikKCmdwcHJldiA8LSBkZl9hbmFseXNpc19ncCAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdChpbmRpY2F0b3IsICJbUHBdcmV2YWxlbmNlIikpICU+JQogIGFycmFuZ2UoaW5kaWNhdG9yLCBhcmVhKQoKZ3BwcmV2CgpgYGAKCmBgYHtyLCBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQpncHByZXYgJT4lCiAgc2VsZWN0KGluZGljYXRvcikgJT4lCiAgZGlzdGluY3QoKQpgYGAKCg==