suppressPackageStartupMessages({
library(magrittr)
library(tidyverse)
library(knitr)
library(kableExtra)
library(WikidataQueryServiceR)
import::from(polloi, compress)
})
options(knitr.kable.NA = "—")
wikidata_link <- function(id) {
linked_cells <- text_spec(id, link = paste0("https://www.wikidata.org/entity/", id))
linked_cells[is.na(id)] <- ""
return(linked_cells)
}
make_list <- function(items) {
if (all(is.na(items))) {
return("—")
} else {
return(sprintf("<ul style = \"padding-left: 0;\">%s</ul>", paste0(sprintf("<li>%s</li>", items), collapse = "")))
}
}
maybe_make_list <- function(x) {
if (length(x) == 1) {
if (is.na(x)) return("—")
else return(x)
} else {
return(make_list(x))
}
}
make_mini_table <- function(x) {
if (is.na(x[[1]]$item)) return("—")
map_dfr(x, as_data_frame) %>%
mutate(item = wikidata_link(item)) %>%
kable(col.names = NULL, escape = FALSE) %>%
kable_styling("striped", full_width = TRUE) %>%
column_spec(1, width = "100px")
}
suppressMessages({
examples <- read_tsv("data/example.tsv")
taxons <- read_rds("data/wikidata_taxons_refined.rds")
commons <- read_rds("data/wikidata_organisms_known_by_common_name.rds")
})
This project (tracked in T197986) is about quantifying the state of incompleteness of Q-items on Wikidata. Unfortunately the question of inconsistency is an enormous research project and quantifying it would require concrete (and automatable) definition. It is also outside of the ability of this report’s author (Mikhail Popov) and the scope of the Wikimedia Audiences Product Analytics team. The code, queries, and data are available in the corresponding repository on GitHub.
Disclaimer: in order to facilitate the analysis, the data was limited to English labels/descriptions/aliases/taxon common names. Due to the prevalence of English on Wikidata, it could be safe to assume that however bad the statistics look in English, they look much worse in other languages.
The following are examples which were highlighted in the deck Wikidata completeness and quality issues:
formatted_examples <- examples %>%
mutate(entity = wikidata_link(entity),
description = ifelse(is.na(description), "—", description),
language = c("en" = "English", "es" = "Spanish")[language],
instance_of = sprintf("%s (%s)", instance_of_label, wikidata_link(instance_of)),
taxon_common_name = ifelse(is.na(taxon_common_name), "—", taxon_common_name),
group = sprintf("%s (%s)", group_label, wikidata_link(group)),
group = ifelse(group == "NA ()", "—", group)) %>%
select(-c(instance_of_label, group_label)) %>%
group_by(entity, language, label, description, instance_of, group) %>%
summarize(taxon_common_names = paste0(unique(taxon_common_name), collapse = ", "),
aliases = make_list(alias)) %>%
ungroup %>%
arrange(entity, language)
formatted_examples %>%
kable(col.names = c(
"Entity", "Language", "Label", "Description",
"Instance of", "Group", "Taxon common name(s)", "Alias(es)"
), escape = FALSE, caption = "Examples highlighted in the \"Wikidata completeness and quality issues\" slides.") %>%
kable_styling() %>%
collapse_rows(columns = 1:2, valign = "top")
Examples highlighted in the "Wikidata completeness and quality issues" slides.
Entity |
Language |
Label |
Description |
Instance of |
Group |
Taxon common name(s) |
Alias(es) |
Q11946202 |
English |
butterfly |
insect of the order Lepidoptera |
group of organisms known by one particular common name (Q55983715) |
Lepidoptera (Q28319) |
— |
|
Spanish |
Rhopalocera |
clado obsoleto de insectos del orden Lepidoptera |
group of organisms known by one particular common name (Q55983715) |
lepidópteros (Q28319) |
— |
- ropalócero
- ropalocero
- mariposas diurnas
|
Q28319 |
English |
Lepidoptera |
order of insects that includes butterflies and moths |
taxon (Q16521) |
— |
Butterflies and Moths |
|
Spanish |
lepidópteros |
orden de insectos holometábolos |
taxon (Q16521) |
— |
— |
- mariposas
- lepidopteros
- lepidoptero
- Lepidoptera
- lepidóptero
- mariposa
|
Q311230 |
English |
Epiphyllum oxypetalum |
species of plant |
taxon (Q16521) |
— |
Dutchman's pipe cactus, Dutchman's Pipe Cactus |
— |
Spanish |
Epiphyllum oxypetalum |
especie de planta |
taxon (Q16521) |
— |
Nopalillo Criollo |
- Phyllocactus oxypetalus
- Phyllocactus grandis
- Epiphyllum latifrons
- Epiphyllum grande
- Epiphyllum acuminatum
- Cereus latifrons
- Cereus oxypetalus
|
Q319469 |
English |
Salamandridae |
family of amphibians |
taxon (Q16521) |
— |
— |
— |
Spanish |
Salamandridae |
— |
taxon (Q16521) |
— |
— |
- Salamandrininae
- Salamandrinae
- Salamándridos
- Salamandridos
- Salamándrido
- Salamandrido
- Pleurodelinae
|
Q3469592 |
English |
salamander |
animal |
group of organisms known by one particular common name (Q55983715) |
Salamandridae (Q319469) |
— |
— |
Search Indexing
|
|
|
|
Searching for “butterfly” with English as UI language |
Searching for “mariposa” with English as UI language |
Searching for “mariposa” with Spanish as UI language |
Searching for “mariposas” with Spanish as UI language |
One might expect Q11946202 (Rhopalocera) to show when searching for “mariposa” with Spanish as the display language because that’s the common name for butterfly in Spanish, but because it has “mariposas” as an alias while Q28319 (lepidópteros) has “mariposa” as an alias, Q28319 is shown higher than Q11946202 until an “s” is added.
Unfortunately that’s just how information retrieval works. Exact matches yield higher scores than partial matches. During my time with Search Platform team as part of Discovery (RIP), the most important thing I learned was: search is hard.
Details
In my chat with Stas (Senior Performance Engineer, Search Platform):
Stas: labels & descriptions are indexed
some statement values are indexed too
aliases are indexed as labels
statements are indexed twofold - as P123=Value in dedicated field
and also values are added into all field
Me: what determines when a statement value is indexed?
Stas: value type. right now only item and string valued statements are indexed
oh and external ID (which is a kind of string)
but in general if you have a lot of similar items the order may not be what you want
Searching for “salamander animal”:
Even though “salamander animal” contains both the label and the description for salamander entity (Q3469592), it only shows up halfway in the top 20 results because:
Right now it’s a combination of item weight and query score, and the weights between those are pretty much invented out of the thin air so now we’re collecting click statistics to try and make them more based in reality
So that’s the answer to the problem of why some items don’t show up in the top 10 autocomplete suggest feature.
Cool Trick: since statements with item & string values are indexed, it’s possible to search with qualifiers too: haswbstatement:P31=Q55983715[P642=Q319469]
. In its Cirrus index entry, we can see that:
"statement_keywords":[
"P31=Q55983715",
"P31=Q55983715[P642=Q319469]"
]
Taxons
Note: technical limitations (SPARQL queries timing out) prevented us from compiling a dataset of all taxons on Wikidata, and we limited our dataset to those which had either: (1) at least one English alias, or (2) at least one English taxon common name. However, we can at least count how many taxons there are on Wikidata:
SELECT (COUNT(?item) AS ?n_taxons) WHERE {
?item wdt:P31 wd:Q16521.
}
today <- lubridate::today()
n_taxons <- query_wikidata("SELECT (COUNT(?item) AS ?n_taxons) WHERE { ?item wdt:P31 wd:Q16521.}")$n_taxons
As of 2018-12-12, there are 2.49M items on Wikidata which are instances of taxon. This means that our dataset of 122.06K items – those which had at least one English alias or English taxon common name – is approximately 4.91% of all taxon items on Wikidata.
Beyond that it is hard to say how many taxons even have labels. If we try use Wikidata Query Service to count how many taxons have an English label, the following query times out:
SELECT (COUNT(?item) AS ?n_taxons) WHERE {
?item wdt:P31 wd:Q16521.
?item rdfs:label ?itemLabel.
FILTER(LANG(?itemLabel) = "en").
}
Let’s take a look at a few taxons to get a sense of what data they may have available:
taxons %>%
filter(entity %in% c("Q28319", "Q311230", "Q1000270", "Q59392949", "Q1034859", "Q1035244", "Q25327", "Q29995", "Q1010571")) %>%
arrange(desc(entity)) %>%
mutate(
label = ifelse(is.na(sqoop_label), "—", sqoop_label),
description = ifelse(is.na(sqoop_description), "—", sqoop_description),
item = sprintf("%s (%s)", label, wikidata_link(entity)),
aliases = map_chr(query_aliases, maybe_make_list),
taxon_common_names = map_chr(query_taxon_common_names, maybe_make_list)
) %>%
select(item, description, aliases, taxon_common_names) %>%
kable(escape = FALSE, caption = "Taxons on Wikidata",
col.names = c("Item", "Description", "Alias(es)", "Taxon common name(s)")) %>%
kable_styling(bootstrap_options = "striped")
Taxons on Wikidata
Item |
Description |
Alias(es) |
Taxon common name(s) |
— (Q59392949) |
— |
Crinipellis carecomoeis |
— |
Epiphyllum oxypetalum (Q311230) |
species of plant |
— |
- Dutchman's Pipe Cactus
- Dutchman's pipe cactus
|
European Otter (Q29995) |
otter |
- Common otter
- Eurasian otter
- Eurasian river otter
- Lutra lutra
- Old World otter
- common otter
- lutra lutra
|
- Common Otter
- Common otter
- Eurasian Otter
- European Otter
- European River Otter
- Old World Otter
|
Lepidoptera (Q28319) |
order of insects that includes butterflies and moths |
lepidopterans |
Butterflies and Moths |
Coccinellidae (Q25327) |
family of insects |
- Lady Birds
- Lady beetles
- Lady bugs
- Lady-Birds
- LadyBirds
- LadyBugs
- Ladybeetles
- Ladybird beetles
- Ladybirds
- Ladybugs
|
— |
Caquetá titi (Q1035244) |
titi monkey endemic to Colombia |
- Callicebus caquetensis
- Caqueta titi
- Caquet· titi monkey
- bushy-bearded titi
- red-bearded titi
|
- Caquet· titi monkey
- Caquetá Titi
- Caquetá Titi Monkey
- Caquetá Tití Monkey
|
Quercus acutissima (Q1034859) |
species of plant |
|
sawtooth oak |
mizuna (Q1010571) |
— |
Brassica rapa var. laciniifolia |
— |
Epinephelus coeruleopunctatus (Q1000270) |
species of fish |
Whitespotted grouper |
- Garrupa
- Ocellated Rock-cod
- Rock Cod
- Small-spotted Rock Cod
- Snowy Grouper
- Vieille Cuisinier
- White-spotted Grouper
- White-spotted Reef-cod
- White-spotted Rockcod
- Whitespotted Grouper
- Whitespotted Rockcod
- Whitespotted grouper
|
When the items are indexed, the index includes labels (if any), descriptions (if any), aliases (if any), and any values of statements which are plain text or Q-items. So if an item is, say, a person who is an instance of (P31) of human (Q5), then their index includes “P31=Q5” but not that they’re a “human”. However, if the value is plain text (e.g. taxon common name), then that gets included in the index and can be searched for. For example, Wikimedia Foundation, Inc. (Q180) has a statement for property “IPv4 routing prefix” (P3761) so if one does a full-text search for “198.35.26.0/23” (the value currently in that statement), WMF is the first result listed.
So one way to assess the searchability of taxons on Wikidata is to assess how many have lebels, descriptions, aliases, and taxon common names (P1843). Among the 122,059 collected taxons, we have following completeness statistics:
taxon_completeness <- taxons %>%
select(entity, sqoop_label, sqoop_description, query_aliases, query_taxon_common_names) %>%
transmute(
item = entity,
`alias(es)` = !map_lgl(query_aliases, ~ all(is.na(.x))),
`taxon common name(s)` = !map_lgl(query_taxon_common_names, ~ all(is.na(.x))),
label = !is.na(sqoop_label),
description = !is.na(sqoop_description),
) %>%
gather(has, val, -item) %>%
arrange(item, has, val)
taxon_completeness %>%
filter(val) %>%
group_by(item) %>%
summarize(n_has = n(), has = paste0(has, collapse = ", ")) %>%
count(n_has, has) %>%
arrange(desc(n)) %>%
mutate(prop = sprintf("%.3f%%", 100 * n / sum(n))) %>%
kable(escape = FALSE, caption = "English info completeness of taxons on Wikidata",
col.names = c("Fields available for a taxon", "Info available (in English)", "Items in dataset", "Proportion of dataset")) %>%
kable_styling(bootstrap_options = "striped")
English info completeness of taxons on Wikidata
Fields available for a taxon |
Info available (in English) |
Items in dataset |
Proportion of dataset |
3 |
description, label, taxon common name(s) |
53736 |
44.025% |
4 |
alias(es), description, label, taxon common name(s) |
30660 |
25.119% |
3 |
alias(es), description, label |
30477 |
24.969% |
2 |
alias(es), label |
5561 |
4.556% |
2 |
label, taxon common name(s) |
974 |
0.798% |
3 |
alias(es), label, taxon common name(s) |
650 |
0.533% |
1 |
alias(es) |
1 |
0.001% |
And, conversely, the following missingness statistics:
taxon_completeness %>%
filter(!val) %>%
group_by(item) %>%
summarize(n_has = n(), has = paste0(has, collapse = ", ")) %>%
count(n_has, has) %>%
arrange(desc(n)) %>%
mutate(prop = sprintf("%.3f%%", 100 * n / sum(n))) %>%
kable(escape = FALSE, caption = "English info missingness of taxons on Wikidata",
col.names = c("Fields NOT available for a taxon", "Info NOT available (in English)", "Items in dataset", "Proportion of dataset")) %>%
kable_styling(bootstrap_options = "striped")
English info missingness of taxons on Wikidata
Fields NOT available for a taxon |
Info NOT available (in English) |
Items in dataset |
Proportion of dataset |
1 |
alias(es) |
53736 |
58.793% |
1 |
taxon common name(s) |
30477 |
33.345% |
2 |
description, taxon common name(s) |
5561 |
6.084% |
2 |
alias(es), description |
974 |
1.066% |
1 |
description |
650 |
0.711% |
3 |
description, label, taxon common name(s) |
1 |
0.001% |
Those are combinations of missing fields. The following are per-field completeness & missingness statistics:
info_completeness <- taxon_completeness %>%
group_by(has) %>%
summarize(n = sum(val), total = n())
info_completeness_n <- set_names(info_completeness$n, info_completeness$has)
info_completeness %>%
transmute(has = has,
prop1 = sprintf("%s (%.3f%%)", compress(n), 100 * n / total),
prop2 = sprintf("%s (%.3f%%)", compress(total - n), 100 * (total - n) / total)) %>%
kable(col.names = c("Information a taxon item may have", "How many have a value (in English)", "How many do NOT have a value (in English)"),
caption = sprintf("English completeness of items among a subset of %s taxons", compress(nrow(taxons))),
align = c("l", "r", "r")) %>%
kable_styling(bootstrap_options = "striped")
English completeness of items among a subset of 122.06K taxons
Information a taxon item may have |
How many have a value (in English) |
How many do NOT have a value (in English) |
alias(es) |
67.35K (55.177%) |
54.71K (44.823%) |
description |
114.87K (94.113%) |
7.19K (5.887%) |
label |
122.06K (99.999%) |
1 (0.001%) |
taxon common name(s) |
86.02K (70.474%) |
36.04K (29.526%) |
Considering that the collected dataset only included taxons which had at least one alias or at least one taxon common name, the missingness of those two items – both of which aid a lot in search – is rather concerning.
Again, these numbers are not representative of all 2.49M taxons. As a reminder, the dataset of 122.06K taxons studied was limited to those which had (1) at least one English alias or (2) at least one English taxon common name. It’s not clear how many taxons on Wikidata do have a description or a label, but we can at least say that of 2.49M, only 67.35K (2.708%) taxons have at least one alias in English and only 86.02K (3.459%) taxons have at least one taxon common name in English.
Note: A follow-up of this work should include all languages. We restricted this initial exploration to English as that is the analyst’s primary language.
Groups of organisms known by one particular common name
An item may also be an instance of “groups of organisms known by one particular common name” (Q55983715). The following are some examples of such items:
commons %>%
filter(item %in% c("Q5", "Q11946202", "Q3469592", "Q11065036", "Q17128757")) %>%
arrange(desc(item)) %>%
mutate(
item = sprintf("%s (%s)", label, wikidata_link(item)),
description = ifelse(is.na(description), "—", description),
aliases = map(aliases, maybe_make_list),
groups = map(groups, make_mini_table),
different_from = map(different_from, make_mini_table)
) %>%
select(-label) %>%
kable(escape = FALSE,
col.names = c("Item", "Description", "Alias(es)",
"Group(s) (Item, Label)", "Different From (Item, Label)"),
caption = "Wikidata entities that are instances of 'groups of organisms known by one particular common name'") %>%
kable_styling(full_width = TRUE) %>%
column_spec(1, width = "100px") %>%
column_spec(2, width = "150px")
Wikidata entities that are instances of 'groups of organisms known by one particular common name'
Item |
Description |
Alias(es) |
Group(s) (Item, Label) |
Different From (Item, Label) |
human (Q5) |
common name of Homo sapiens, unique extant species of the genus Homo |
- person
- homosapiens
- human being
- humankind
- people
|
|
|
salamander (Q3469592) |
animal |
— |
|
— |
heather (Q17128757) |
— |
— |
— |
|
butterfly (Q11946202) |
insect of the order Lepidoptera |
Rhopalocera |
|
|
Bedla (Q11065036) |
— |
— |
|
— |
Similar the work on taxons, we can calculate some completeness statistics on these items. Although unlike the case with taxons, these (English-focused) statistics apply to all instances found on Wikidata.
commons %>%
transmute(
label = !is.na(label),
description = !is.na(description),
`alias(es)` = map_int(aliases, ~ sum(!is.na(.x))) > 0,
`at least one "of" qualifier` = map_int(groups, ~ sum(!is.na(.x[[1]]$item))) > 0,
`at least one "different from" statement` = map_int(different_from, ~ sum(!is.na(.x[[1]]$item))) > 0
) %>%
gather(has, val) %>%
mutate(has = factor(has, c("label", "description", "alias(es)", "at least one \"of\" qualifier", "at least one \"different from\" statement"))) %>%
group_by(has) %>%
summarize(prop = sprintf("%.1f%%", 100 * mean(val))) %>%
kable(col.names = c("Information an item may have", "How many have a value (in English)"),
caption = "English completeness of items which are instances of 'group of organisms known by one particular common name'",
align = c("l", "r")) %>%
kable_styling("striped") %>%
group_rows(index = c("Indexed for search as text" = 3, "Indexed for search as statement_keywords" = 2))
English completeness of items which are instances of 'group of organisms known by one particular common name'
Information an item may have |
How many have a value (in English) |
Indexed for search as text |
label |
73.0% |
description |
41.5% |
alias(es) |
10.9% |
Indexed for search as statement_keywords |
at least one "of" qualifier |
18.1% |
at least one "different from" statement |
6.6% |
As before, we can also look at combinations of missing fields to determine how many items would be easily found by searching (e.g. an item which has a label, a description, and an alias would be more likely to be found by someone looking for it than an item with only, say, a label):
commons_completeness <- commons %>%
transmute(
item = item,
label = !is.na(label),
description = !is.na(description),
`alias(es)` = map_int(aliases, ~ sum(!is.na(.x))) > 0,
`at least one "of" qualifier` = map_int(groups, ~ sum(!is.na(.x[[1]]$item))) > 0,
`at least one "different from" statement` = map_int(different_from, ~ sum(!is.na(.x[[1]]$item))) > 0
) %>%
gather(has, val, -item) %>%
mutate(has = factor(has, c("label", "description", "alias(es)", "at least one \"of\" qualifier", "at least one \"different from\" statement"))) %>%
filter(val) %>%
group_by(item) %>%
summarize(n_has = n(), has = paste0(has, collapse = ", ")) %>%
count(n_has, has) %>%
arrange(desc(n_has), desc(n)) %>%
mutate(prop = sprintf("%.2f%%", 100 * n / sum(n)))
commons_completeness %>%
dplyr::select(-n_has) %>%
kable(escape = FALSE, caption = "English information completeness of 'group of organisms known by one particular common name' instances on Wikidata",
col.names = c("Fields of information available (in English)", "Items on Wikidata", "Proportion among all such instances")) %>%
kable_styling(bootstrap_options = "striped") %>%
group_rows(index = auto_index(commons_completeness$n_has), group_label = "Fields available for an item")
English information completeness of 'group of organisms known by one particular common name' instances on Wikidata
Fields of information available (in English) |
Items on Wikidata |
Proportion among all such instances |
5 |
label, description, alias(es), at least one "of" qualifier, at least one "different from" statement |
7 |
1.19% |
4 |
label, description, alias(es), at least one "of" qualifier |
12 |
2.05% |
label, description, at least one "of" qualifier, at least one "different from" statement |
9 |
1.54% |
label, description, alias(es), at least one "different from" statement |
8 |
1.37% |
3 |
label, description, at least one "of" qualifier |
56 |
9.56% |
label, description, alias(es) |
39 |
6.66% |
label, description, at least one "different from" statement |
16 |
2.73% |
label, alias(es), at least one "of" qualifier |
2 |
0.34% |
label, at least one "of" qualifier, at least one "different from" statement |
2 |
0.34% |
label, alias(es), at least one "different from" statement |
1 |
0.17% |
2 |
label, description |
158 |
26.96% |
label, at least one "of" qualifier |
27 |
4.61% |
label, alias(es) |
14 |
2.39% |
label, at least one "different from" statement |
5 |
0.85% |
description, at least one "of" qualifier |
2 |
0.34% |
1 |
label |
198 |
33.79% |
at least one "of" qualifier |
20 |
3.41% |
description |
8 |
1.37% |
at least one "different from" statement |
2 |
0.34% |
LS0tCnRpdGxlOiAiVDE5Nzk4NiIKZGVzY3JpcHRpb246IHwgCiAgUmVwb3J0IG9uIHRoZSBzdGF0ZSBvZiBXaWtpZGF0YSBpbmNvbXBsZXRlbmVzcwpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRoZW1lOiByZWFkYWJsZQogICAgY29kZV9mb2xkaW5nOiBoaWRlCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCi0tLQoKYGBge3Igc2V0dXB9CnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7CiAgbGlicmFyeShtYWdyaXR0cikKICBsaWJyYXJ5KHRpZHl2ZXJzZSkKICBsaWJyYXJ5KGtuaXRyKQogIGxpYnJhcnkoa2FibGVFeHRyYSkKICBsaWJyYXJ5KFdpa2lkYXRhUXVlcnlTZXJ2aWNlUikKICBpbXBvcnQ6OmZyb20ocG9sbG9pLCBjb21wcmVzcykKfSkKb3B0aW9ucyhrbml0ci5rYWJsZS5OQSA9ICLigJQiKQp3aWtpZGF0YV9saW5rIDwtIGZ1bmN0aW9uKGlkKSB7CiAgbGlua2VkX2NlbGxzIDwtIHRleHRfc3BlYyhpZCwgbGluayA9IHBhc3RlMCgiaHR0cHM6Ly93d3cud2lraWRhdGEub3JnL2VudGl0eS8iLCBpZCkpCiAgbGlua2VkX2NlbGxzW2lzLm5hKGlkKV0gPC0gIiIKICByZXR1cm4obGlua2VkX2NlbGxzKQp9Cm1ha2VfbGlzdCA8LSBmdW5jdGlvbihpdGVtcykgewogIGlmIChhbGwoaXMubmEoaXRlbXMpKSkgewogICAgcmV0dXJuKCLigJQiKQogIH0gZWxzZSB7CiAgICByZXR1cm4oc3ByaW50ZigiPHVsIHN0eWxlID0gXCJwYWRkaW5nLWxlZnQ6IDA7XCI+JXM8L3VsPiIsIHBhc3RlMChzcHJpbnRmKCI8bGk+JXM8L2xpPiIsIGl0ZW1zKSwgY29sbGFwc2UgPSAiIikpKQogIH0KfQptYXliZV9tYWtlX2xpc3QgPC0gZnVuY3Rpb24oeCkgewogIGlmIChsZW5ndGgoeCkgPT0gMSkgewogICAgaWYgKGlzLm5hKHgpKSByZXR1cm4oIuKAlCIpCiAgICBlbHNlIHJldHVybih4KQogIH0gZWxzZSB7CiAgICByZXR1cm4obWFrZV9saXN0KHgpKQogIH0KfQptYWtlX21pbmlfdGFibGUgPC0gZnVuY3Rpb24oeCkgewogIGlmIChpcy5uYSh4W1sxXV0kaXRlbSkpIHJldHVybigi4oCUIikKICBtYXBfZGZyKHgsIGFzX2RhdGFfZnJhbWUpICU+JQogICAgbXV0YXRlKGl0ZW0gPSB3aWtpZGF0YV9saW5rKGl0ZW0pKSAlPiUKICAgIGthYmxlKGNvbC5uYW1lcyA9IE5VTEwsIGVzY2FwZSA9IEZBTFNFKSAlPiUKICAgIGthYmxlX3N0eWxpbmcoInN0cmlwZWQiLCBmdWxsX3dpZHRoID0gVFJVRSkgJT4lCiAgICBjb2x1bW5fc3BlYygxLCB3aWR0aCA9ICIxMDBweCIpCn0KYGBgCmBgYHtyIGRhdGF9CnN1cHByZXNzTWVzc2FnZXMoewogIGV4YW1wbGVzIDwtIHJlYWRfdHN2KCJkYXRhL2V4YW1wbGUudHN2IikKICB0YXhvbnMgPC0gcmVhZF9yZHMoImRhdGEvd2lraWRhdGFfdGF4b25zX3JlZmluZWQucmRzIikKICBjb21tb25zIDwtIHJlYWRfcmRzKCJkYXRhL3dpa2lkYXRhX29yZ2FuaXNtc19rbm93bl9ieV9jb21tb25fbmFtZS5yZHMiKQp9KQpgYGAKClRoaXMgcHJvamVjdCAodHJhY2tlZCBpbiBbVDE5Nzk4Nl0oaHR0cHM6Ly9waGFicmljYXRvci53aWtpbWVkaWEub3JnL1QxOTc5ODYpKSBpcyBhYm91dCBxdWFudGlmeWluZyB0aGUgc3RhdGUgb2YgaW5jb21wbGV0ZW5lc3Mgb2YgUS1pdGVtcyBvbiBXaWtpZGF0YS4gVW5mb3J0dW5hdGVseSB0aGUgcXVlc3Rpb24gb2YgaW5jb25zaXN0ZW5jeSBpcyBhbiBlbm9ybW91cyByZXNlYXJjaCBwcm9qZWN0IGFuZCBxdWFudGlmeWluZyBpdCB3b3VsZCByZXF1aXJlIGNvbmNyZXRlIChhbmQgYXV0b21hdGFibGUpIGRlZmluaXRpb24uIEl0IGlzIGFsc28gb3V0c2lkZSBvZiB0aGUgYWJpbGl0eSBvZiB0aGlzIHJlcG9ydCdzIGF1dGhvciAoW01pa2hhaWwgUG9wb3ZdKGh0dHBzOi8vbWV0YS53aWtpbWVkaWEub3JnL3dpa2kvVXNlcjpNUG9wb3ZfKFdNRikpKSBhbmQgdGhlIHNjb3BlIG9mIHRoZSBbV2lraW1lZGlhIEF1ZGllbmNlcyBQcm9kdWN0IEFuYWx5dGljcyB0ZWFtXShodHRwczovL21lZGlhd2lraS5jb20vd2lraS9Qcm9kdWN0X0FuYWx5dGljcykuIFRoZSBjb2RlLCBxdWVyaWVzLCBhbmQgZGF0YSBhcmUgYXZhaWxhYmxlIGluIFt0aGUgY29ycmVzcG9uZGluZyByZXBvc2l0b3J5IG9uIEdpdEh1Yl0oaHR0cHM6Ly9naXRodWIuY29tL3dpa2ltZWRpYS1yZXNlYXJjaC9TRG9DLVQxOTc5ODYpLgoKKipEaXNjbGFpbWVyKio6IGluIG9yZGVyIHRvIGZhY2lsaXRhdGUgdGhlIGFuYWx5c2lzLCB0aGUgZGF0YSB3YXMgbGltaXRlZCB0byBFbmdsaXNoIGxhYmVscy9kZXNjcmlwdGlvbnMvYWxpYXNlcy90YXhvbiBjb21tb24gbmFtZXMuIER1ZSB0byB0aGUgcHJldmFsZW5jZSBvZiBFbmdsaXNoIG9uIFdpa2lkYXRhLCBpdCBjb3VsZCBiZSBzYWZlIHRvIGFzc3VtZSB0aGF0IGhvd2V2ZXIgYmFkIHRoZSBzdGF0aXN0aWNzIGxvb2sgaW4gRW5nbGlzaCwgdGhleSBsb29rIG11Y2ggd29yc2UgaW4gb3RoZXIgbGFuZ3VhZ2VzLgoKVGhlIGZvbGxvd2luZyBhcmUgZXhhbXBsZXMgd2hpY2ggd2VyZSBoaWdobGlnaHRlZCBpbiB0aGUgZGVjayBbV2lraWRhdGEgY29tcGxldGVuZXNzIGFuZCBxdWFsaXR5IGlzc3Vlc10oaHR0cHM6Ly9kb2NzLmdvb2dsZS5jb20vcHJlc2VudGF0aW9uL2QvMWRBTnU5NHk5QUExNnQ2Zk5faV9oYWdxbHNkSTkycU1tZXpoSWIxNEYtYm8vZWRpdD91c3A9c2hhcmluZyk6CgpgYGB7ciBleGFtcGxlc30KZm9ybWF0dGVkX2V4YW1wbGVzIDwtIGV4YW1wbGVzICU+JQogIG11dGF0ZShlbnRpdHkgPSB3aWtpZGF0YV9saW5rKGVudGl0eSksCiAgICAgICAgIGRlc2NyaXB0aW9uID0gaWZlbHNlKGlzLm5hKGRlc2NyaXB0aW9uKSwgIuKAlCIsIGRlc2NyaXB0aW9uKSwKICAgICAgICAgbGFuZ3VhZ2UgPSBjKCJlbiIgPSAiRW5nbGlzaCIsICJlcyIgPSAiU3BhbmlzaCIpW2xhbmd1YWdlXSwKICAgICAgICAgaW5zdGFuY2Vfb2YgPSBzcHJpbnRmKCIlcyAoJXMpIiwgaW5zdGFuY2Vfb2ZfbGFiZWwsIHdpa2lkYXRhX2xpbmsoaW5zdGFuY2Vfb2YpKSwKICAgICAgICAgdGF4b25fY29tbW9uX25hbWUgPSBpZmVsc2UoaXMubmEodGF4b25fY29tbW9uX25hbWUpLCAi4oCUIiwgdGF4b25fY29tbW9uX25hbWUpLAogICAgICAgICBncm91cCA9IHNwcmludGYoIiVzICglcykiLCBncm91cF9sYWJlbCwgd2lraWRhdGFfbGluayhncm91cCkpLAogICAgICAgICBncm91cCA9IGlmZWxzZShncm91cCA9PSAiTkEgKCkiLCAi4oCUIiwgZ3JvdXApKSAlPiUKICBzZWxlY3QoLWMoaW5zdGFuY2Vfb2ZfbGFiZWwsIGdyb3VwX2xhYmVsKSkgJT4lCiAgZ3JvdXBfYnkoZW50aXR5LCBsYW5ndWFnZSwgbGFiZWwsIGRlc2NyaXB0aW9uLCBpbnN0YW5jZV9vZiwgZ3JvdXApICU+JQogIHN1bW1hcml6ZSh0YXhvbl9jb21tb25fbmFtZXMgPSBwYXN0ZTAodW5pcXVlKHRheG9uX2NvbW1vbl9uYW1lKSwgY29sbGFwc2UgPSAiLCAiKSwKICAgICAgICAgICAgYWxpYXNlcyA9IG1ha2VfbGlzdChhbGlhcykpICU+JQogIHVuZ3JvdXAgJT4lCiAgYXJyYW5nZShlbnRpdHksIGxhbmd1YWdlKQpmb3JtYXR0ZWRfZXhhbXBsZXMgJT4lCiAga2FibGUoY29sLm5hbWVzID0gYygKICAgICJFbnRpdHkiLCAiTGFuZ3VhZ2UiLCAiTGFiZWwiLCAiRGVzY3JpcHRpb24iLAogICAgIkluc3RhbmNlIG9mIiwgIkdyb3VwIiwgIlRheG9uIGNvbW1vbiBuYW1lKHMpIiwgIkFsaWFzKGVzKSIKICApLCBlc2NhcGUgPSBGQUxTRSwgY2FwdGlvbiA9ICJFeGFtcGxlcyBoaWdobGlnaHRlZCBpbiB0aGUgXCJXaWtpZGF0YSBjb21wbGV0ZW5lc3MgYW5kIHF1YWxpdHkgaXNzdWVzXCIgc2xpZGVzLiIpICU+JQogIGthYmxlX3N0eWxpbmcoKSAlPiUKICBjb2xsYXBzZV9yb3dzKGNvbHVtbnMgPSAxOjIsIHZhbGlnbiA9ICJ0b3AiKQpgYGAKCgoKIyMgU2VhcmNoIEluZGV4aW5nCgp8IEV4YW1wbGUgMWEgfCBFeGFtcGxlIDFiIHwgRXhhbXBsZSAxYyB8IEV4YW1wbGUgMWQgfAp8Oi0tLS0tLS0tLS06fDotLS0tLS0tLS0tOnw6LS0tLS0tLS0tLTp8Oi0tLS0tLS0tLS06fAp8IVtFeGFtcGxlIDFhXShpbWFnZXMvYnV0dGVyZmx5LWEucG5nKXwhW0V4YW1wbGUgMWJdKGltYWdlcy9idXR0ZXJmbHktYi5wbmcpfCFbRXhhbXBsZSAxY10oaW1hZ2VzL2J1dHRlcmZseS1jLnBuZyl8IVtFeGFtcGxlIDFkXShpbWFnZXMvYnV0dGVyZmx5LWQucG5nKXwKfFNlYXJjaGluZyBmb3IgImJ1dHRlcmZseSIgd2l0aCBFbmdsaXNoIGFzIFVJIGxhbmd1YWdlfFNlYXJjaGluZyBmb3IgIm1hcmlwb3NhIiB3aXRoIEVuZ2xpc2ggYXMgVUkgbGFuZ3VhZ2V8U2VhcmNoaW5nIGZvciAibWFyaXBvc2EiIHdpdGggU3BhbmlzaCBhcyBVSSBsYW5ndWFnZXxTZWFyY2hpbmcgZm9yICJtYXJpcG9zYXMiIHdpdGggU3BhbmlzaCBhcyBVSSBsYW5ndWFnZXwKCk9uZSBtaWdodCBleHBlY3QgUTExOTQ2MjAyIChSaG9wYWxvY2VyYSkgdG8gc2hvdyB3aGVuIHNlYXJjaGluZyBmb3IgIm1hcmlwb3NhIiB3aXRoIFNwYW5pc2ggYXMgdGhlIGRpc3BsYXkgbGFuZ3VhZ2UgYmVjYXVzZSB0aGF0J3MgdGhlIGNvbW1vbiBuYW1lIGZvciBidXR0ZXJmbHkgaW4gU3BhbmlzaCwgYnV0IGJlY2F1c2UgaXQgaGFzICJtYXJpcG9zYXMiIGFzIGFuIGFsaWFzIHdoaWxlIFEyODMxOSAobGVwaWTDs3B0ZXJvcykgaGFzICJtYXJpcG9zYSIgYXMgYW4gYWxpYXMsIFEyODMxOSBpcyBzaG93biBoaWdoZXIgdGhhbiBRMTE5NDYyMDIgdW50aWwgYW4gInMiIGlzIGFkZGVkLgoKVW5mb3J0dW5hdGVseSB0aGF0J3MganVzdCBob3cgaW5mb3JtYXRpb24gcmV0cmlldmFsIHdvcmtzLiBFeGFjdCBtYXRjaGVzIHlpZWxkIGhpZ2hlciBzY29yZXMgdGhhbiBwYXJ0aWFsIG1hdGNoZXMuIER1cmluZyBteSB0aW1lIHdpdGggU2VhcmNoIFBsYXRmb3JtIHRlYW0gYXMgcGFydCBvZiBEaXNjb3ZlcnkgKFJJUCksIHRoZSBtb3N0IGltcG9ydGFudCB0aGluZyBJIGxlYXJuZWQgd2FzOiAqKl9zZWFyY2ggaXMgaGFyZF8qKi4KCiMjIyBEZXRhaWxzCgpJbiBteSBjaGF0IHdpdGggU3RhcyAoU2VuaW9yIFBlcmZvcm1hbmNlIEVuZ2luZWVyLCBTZWFyY2ggUGxhdGZvcm0pOgoKYGBgClN0YXM6IGxhYmVscyAmIGRlc2NyaXB0aW9ucyBhcmUgaW5kZXhlZAogICAgICBzb21lIHN0YXRlbWVudCB2YWx1ZXMgYXJlIGluZGV4ZWQgdG9vCiAgICAgIGFsaWFzZXMgYXJlIGluZGV4ZWQgYXMgbGFiZWxzCiAgICAgIHN0YXRlbWVudHMgYXJlIGluZGV4ZWQgdHdvZm9sZCAtIGFzIFAxMjM9VmFsdWUgaW4gZGVkaWNhdGVkIGZpZWxkCiAgICAgIGFuZCBhbHNvIHZhbHVlcyBhcmUgYWRkZWQgaW50byBhbGwgZmllbGQKICBNZTogd2hhdCBkZXRlcm1pbmVzIHdoZW4gYSBzdGF0ZW1lbnQgdmFsdWUgaXMgaW5kZXhlZD8KU3RhczogdmFsdWUgdHlwZS4gcmlnaHQgbm93IG9ubHkgaXRlbSBhbmQgc3RyaW5nIHZhbHVlZCBzdGF0ZW1lbnRzIGFyZSBpbmRleGVkCiAgICAgIG9oIGFuZCBleHRlcm5hbCBJRCAod2hpY2ggaXMgYSBraW5kIG9mIHN0cmluZykKICAgICAgYnV0IGluIGdlbmVyYWwgaWYgeW91IGhhdmUgYSBsb3Qgb2Ygc2ltaWxhciBpdGVtcyB0aGUgb3JkZXIgbWF5IG5vdCBiZSB3aGF0IHlvdSB3YW50CmBgYAoKU2VhcmNoaW5nIGZvciAic2FsYW1hbmRlciBhbmltYWwiOgoKLSBbU2VhcmNoIHJlc3VsdHMgcGFnZV0oaHR0cHM6Ly93d3cud2lraWRhdGEub3JnL3cvaW5kZXgucGhwP3NlYXJjaD1zYWxhbWFuZGVyK2FuaW1hbCkKLSBbQ2lycnVzIHF1ZXJ5IGR1bXBdKGh0dHBzOi8vd3d3Lndpa2lkYXRhLm9yZy93L2luZGV4LnBocD9zZWFyY2g9c2FsYW1hbmRlcithbmltYWwmY2lycnVzRHVtcFF1ZXJ5PXllcykKLSBbQ2lycnVzIHJlc3VsdHMgZHVtcF0oaHR0cHM6Ly93d3cud2lraWRhdGEub3JnL3cvaW5kZXgucGhwP3NlYXJjaD1zYWxhbWFuZGVyK2FuaW1hbCZjaXJydXNEdW1wUmVzdWx0PXllcykKCkV2ZW4gdGhvdWdoICJzYWxhbWFuZGVyIGFuaW1hbCIgY29udGFpbnMgYm90aCB0aGUgbGFiZWwgYW5kIHRoZSBkZXNjcmlwdGlvbiBmb3Igc2FsYW1hbmRlciBlbnRpdHkgKFtRMzQ2OTU5Ml0oaHR0cHM6Ly93d3cud2lraWRhdGEub3JnL3dpa2kvUTM0Njk1OTIpKSwgaXQgb25seSBzaG93cyB1cCBoYWxmd2F5IGluIHRoZSB0b3AgMjAgcmVzdWx0cyBiZWNhdXNlOgoKPiBSaWdodCBub3cgaXQncyBhIGNvbWJpbmF0aW9uIG9mIGl0ZW0gd2VpZ2h0IGFuZCBxdWVyeSBzY29yZSwgYW5kIHRoZSB3ZWlnaHRzIGJldHdlZW4gdGhvc2UgYXJlIHByZXR0eSBtdWNoIGludmVudGVkIG91dCBvZiB0aGUgdGhpbiBhaXIKPiBzbyBub3cgd2UncmUgY29sbGVjdGluZyBjbGljayBzdGF0aXN0aWNzIHRvIHRyeSBhbmQgbWFrZSB0aGVtIG1vcmUgYmFzZWQgaW4gcmVhbGl0eQoKU28gdGhhdCdzIHRoZSBhbnN3ZXIgdG8gdGhlIHByb2JsZW0gb2Ygd2h5IHNvbWUgaXRlbXMgZG9uJ3Qgc2hvdyB1cCBpbiB0aGUgdG9wIDEwIGF1dG9jb21wbGV0ZSBzdWdnZXN0IGZlYXR1cmUuCgoqKkNvb2wgVHJpY2sqKjogc2luY2Ugc3RhdGVtZW50cyB3aXRoIGl0ZW0gJiBzdHJpbmcgdmFsdWVzIGFyZSBpbmRleGVkLCBpdCdzIHBvc3NpYmxlIHRvIHNlYXJjaCB3aXRoIHF1YWxpZmllcnMgdG9vOiBbYGhhc3dic3RhdGVtZW50OlAzMT1RNTU5ODM3MTVbUDY0Mj1RMzE5NDY5XWBdKGh0dHBzOi8vd3d3Lndpa2lkYXRhLm9yZy93L2luZGV4LnBocD9zZWFyY2g9JnNlYXJjaD1oYXN3YnN0YXRlbWVudCUzQVAzMSUzRFE1NTk4MzcxNSU1QlA2NDIlM0RRMzE5NDY5JTVEJnRpdGxlPVNwZWNpYWwlM0FTZWFyY2gmZ289R28pLiBJbiBbaXRzIENpcnJ1cyBpbmRleCBlbnRyeV0oaHR0cHM6Ly93d3cud2lraWRhdGEub3JnL3dpa2kvUTM0Njk1OTI/YWN0aW9uPWNpcnJ1c0R1bXApLCB3ZSBjYW4gc2VlIHRoYXQ6CgpgYGBKU09OCiJzdGF0ZW1lbnRfa2V5d29yZHMiOlsKICAiUDMxPVE1NTk4MzcxNSIsCiAgIlAzMT1RNTU5ODM3MTVbUDY0Mj1RMzE5NDY5XSIKXQpgYGAKCiMjIFRheG9ucwoKKipOb3RlKio6IHRlY2huaWNhbCBsaW1pdGF0aW9ucyAoU1BBUlFMIHF1ZXJpZXMgdGltaW5nIG91dCkgcHJldmVudGVkIHVzIGZyb20gY29tcGlsaW5nIGEgZGF0YXNldCBvZiBhbGwgdGF4b25zIG9uIFdpa2lkYXRhLCBhbmQgd2UgbGltaXRlZCBvdXIgZGF0YXNldCB0byB0aG9zZSB3aGljaCBoYWQgZWl0aGVyOiAoMSkgYXQgbGVhc3Qgb25lIEVuZ2xpc2ggYWxpYXMsIG9yICgyKSBhdCBsZWFzdCBvbmUgRW5nbGlzaCB0YXhvbiBjb21tb24gbmFtZS4gSG93ZXZlciwgd2UgY2FuIGF0IGxlYXN0IFtjb3VudCBob3cgbWFueSB0YXhvbnMgdGhlcmUgYXJlIG9uIFdpa2lkYXRhXShodHRwczovL3F1ZXJ5Lndpa2lkYXRhLm9yZy8jU0VMRUNUJTIwJTI4Q09VTlQlMjglM0ZpdGVtJTI5JTIwQVMlMjAlM0ZuX3RheG9ucyUyOSUyMFdIRVJFJTIwJTdCJTBBJTIwJTIwJTNGaXRlbSUyMHdkdCUzQVAzMSUyMHdkJTNBUTE2NTIxLiUwQSU3RCk6CgpgYGBTUEFSUUwKU0VMRUNUIChDT1VOVCg/aXRlbSkgQVMgP25fdGF4b25zKSBXSEVSRSB7CiAgP2l0ZW0gd2R0OlAzMSB3ZDpRMTY1MjEuCn0KYGBgCmBgYHtyIG5fdGF4b25zLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1UUlVFfQp0b2RheSA8LSBsdWJyaWRhdGU6OnRvZGF5KCkKbl90YXhvbnMgPC0gcXVlcnlfd2lraWRhdGEoIlNFTEVDVCAoQ09VTlQoP2l0ZW0pIEFTID9uX3RheG9ucykgV0hFUkUgeyA/aXRlbSB3ZHQ6UDMxIHdkOlExNjUyMS59Iikkbl90YXhvbnMKYGBgCgpBcyBvZiBgciB0b2RheWAsIHRoZXJlIGFyZSBgciBjb21wcmVzcyhuX3RheG9ucylgIGl0ZW1zIG9uIFdpa2lkYXRhIHdoaWNoIGFyZSBpbnN0YW5jZXMgb2YgdGF4b24uIFRoaXMgbWVhbnMgdGhhdCBvdXIgZGF0YXNldCBvZiBgciBjb21wcmVzcyhucm93KHRheG9ucykpYCBpdGVtcyAtLSB0aG9zZSB3aGljaCBoYWQgYXQgbGVhc3Qgb25lIEVuZ2xpc2ggYWxpYXMgb3IgRW5nbGlzaCB0YXhvbiBjb21tb24gbmFtZSAtLSBpcyBhcHByb3hpbWF0ZWx5IGByIHNwcmludGYoIiUuMmYlJSIsIDEwMCAqIG5yb3codGF4b25zKSAvIG5fdGF4b25zKWAgb2YgYWxsIHRheG9uIGl0ZW1zIG9uIFdpa2lkYXRhLgoKQmV5b25kIHRoYXQgaXQgaXMgaGFyZCB0byBzYXkgaG93IG1hbnkgdGF4b25zIGV2ZW4gaGF2ZSBsYWJlbHMuIElmIHdlIHRyeSB1c2UgV2lraWRhdGEgUXVlcnkgU2VydmljZSB0byBjb3VudCBob3cgbWFueSB0YXhvbnMgaGF2ZSBhbiBFbmdsaXNoIGxhYmVsLCB0aGUgZm9sbG93aW5nIFtxdWVyeV0oaHR0cHM6Ly9xdWVyeS53aWtpZGF0YS5vcmcvI1NFTEVDVCUyMCUyOENPVU5UJTI4JTNGaXRlbSUyOSUyMEFTJTIwJTNGbl90YXhvbnMlMjklMjBXSEVSRSUyMCU3QiUwQSUyMCUyMCUzRml0ZW0lMjB3ZHQlM0FQMzElMjB3ZCUzQVExNjUyMS4lMEElMjAlMjAlM0ZpdGVtJTIwcmRmcyUzQWxhYmVsJTIwJTNGaXRlbUxhYmVsLiUwQSUyMCUyMEZJTFRFUiUyOExBTkclMjglM0ZpdGVtTGFiZWwlMjklMjAlM0QlMjAlMjJlbiUyMiUyOS4lMEElN0QpIHRpbWVzIG91dDoKCmBgYFNQQVJRTApTRUxFQ1QgKENPVU5UKD9pdGVtKSBBUyA/bl90YXhvbnMpIFdIRVJFIHsKICA/aXRlbSB3ZHQ6UDMxIHdkOlExNjUyMS4KICA/aXRlbSByZGZzOmxhYmVsID9pdGVtTGFiZWwuCiAgRklMVEVSKExBTkcoP2l0ZW1MYWJlbCkgPSAiZW4iKS4KfQpgYGAKCkxldCdzIHRha2UgYSBsb29rIGF0IGEgZmV3IHRheG9ucyB0byBnZXQgYSBzZW5zZSBvZiB3aGF0IGRhdGEgdGhleSBtYXkgaGF2ZSBhdmFpbGFibGU6CgpgYGB7ciBleGFtcGxlX3RheG9uc30KdGF4b25zICU+JQogIGZpbHRlcihlbnRpdHkgJWluJSBjKCJRMjgzMTkiLCAiUTMxMTIzMCIsICJRMTAwMDI3MCIsICJRNTkzOTI5NDkiLCAiUTEwMzQ4NTkiLCAiUTEwMzUyNDQiLCAiUTI1MzI3IiwgIlEyOTk5NSIsICJRMTAxMDU3MSIpKSAlPiUKICBhcnJhbmdlKGRlc2MoZW50aXR5KSkgJT4lCiAgbXV0YXRlKAogICAgbGFiZWwgPSBpZmVsc2UoaXMubmEoc3Fvb3BfbGFiZWwpLCAi4oCUIiwgc3Fvb3BfbGFiZWwpLAogICAgZGVzY3JpcHRpb24gPSBpZmVsc2UoaXMubmEoc3Fvb3BfZGVzY3JpcHRpb24pLCAi4oCUIiwgc3Fvb3BfZGVzY3JpcHRpb24pLAogICAgaXRlbSA9IHNwcmludGYoIiVzICglcykiLCBsYWJlbCwgd2lraWRhdGFfbGluayhlbnRpdHkpKSwKICAgIGFsaWFzZXMgPSBtYXBfY2hyKHF1ZXJ5X2FsaWFzZXMsIG1heWJlX21ha2VfbGlzdCksCiAgICB0YXhvbl9jb21tb25fbmFtZXMgPSBtYXBfY2hyKHF1ZXJ5X3RheG9uX2NvbW1vbl9uYW1lcywgbWF5YmVfbWFrZV9saXN0KQogICkgJT4lCiAgc2VsZWN0KGl0ZW0sIGRlc2NyaXB0aW9uLCBhbGlhc2VzLCB0YXhvbl9jb21tb25fbmFtZXMpICU+JQogIGthYmxlKGVzY2FwZSA9IEZBTFNFLCBjYXB0aW9uID0gIlRheG9ucyBvbiBXaWtpZGF0YSIsCiAgICAgICAgY29sLm5hbWVzID0gYygiSXRlbSIsICJEZXNjcmlwdGlvbiIsICJBbGlhcyhlcykiLCAiVGF4b24gY29tbW9uIG5hbWUocykiKSkgJT4lCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9ICJzdHJpcGVkIikKYGBgCgpXaGVuIHRoZSBpdGVtcyBhcmUgaW5kZXhlZCwgdGhlIGluZGV4IGluY2x1ZGVzIGxhYmVscyAoaWYgYW55KSwgZGVzY3JpcHRpb25zIChpZiBhbnkpLCBhbGlhc2VzIChpZiBhbnkpLCBhbmQgYW55IHZhbHVlcyBvZiBzdGF0ZW1lbnRzIHdoaWNoIGFyZSBwbGFpbiB0ZXh0IG9yIFEtaXRlbXMuIFNvIGlmIGFuIGl0ZW0gaXMsIHNheSwgYSBwZXJzb24gd2hvIGlzIGFuIGluc3RhbmNlIG9mIChQMzEpIG9mIGh1bWFuIChRNSksIHRoZW4gdGhlaXIgaW5kZXggaW5jbHVkZXMgIlAzMT1RNSIgYnV0ICpub3QqIHRoYXQgdGhleSdyZSBhICJodW1hbiIuIEhvd2V2ZXIsIGlmIHRoZSB2YWx1ZSBpcyBwbGFpbiB0ZXh0IChlLmcuIHRheG9uIGNvbW1vbiBuYW1lKSwgdGhlbiB0aGF0IGdldHMgaW5jbHVkZWQgaW4gdGhlIGluZGV4IGFuZCBjYW4gYmUgc2VhcmNoZWQgZm9yLiBGb3IgZXhhbXBsZSwgV2lraW1lZGlhIEZvdW5kYXRpb24sIEluYy4gKFtRMTgwXShodHRwczovL3d3dy53aWtpZGF0YS5vcmcvd2lraS9RMTgwKSkgaGFzIGEgc3RhdGVtZW50IGZvciBwcm9wZXJ0eSAiSVB2NCByb3V0aW5nIHByZWZpeCIgKFtQMzc2MV0oaHR0cHM6Ly93d3cud2lraWRhdGEub3JnL3dpa2kvUHJvcGVydHk6UDM3NjEpKSBzbyBpZiBvbmUgZG9lcyBhIFtmdWxsLXRleHQgc2VhcmNoIGZvciAiMTk4LjM1LjI2LjAvMjMiXShodHRwczovL3d3dy53aWtpZGF0YS5vcmcvdy9pbmRleC5waHA/c2VhcmNoPTE5OC4zNS4yNi4wJTJGMjMmdGl0bGU9U3BlY2lhbCUzQVNlYXJjaCZmdWxsdGV4dD0xKSAodGhlIHZhbHVlIGN1cnJlbnRseSBpbiB0aGF0IHN0YXRlbWVudCksIFdNRiBpcyB0aGUgZmlyc3QgcmVzdWx0IGxpc3RlZC4KClNvIG9uZSB3YXkgdG8gYXNzZXNzIHRoZSBzZWFyY2hhYmlsaXR5IG9mIHRheG9ucyBvbiBXaWtpZGF0YSBpcyB0byBhc3Nlc3MgaG93IG1hbnkgaGF2ZSBsZWJlbHMsIGRlc2NyaXB0aW9ucywgYWxpYXNlcywgYW5kIHRheG9uIGNvbW1vbiBuYW1lcyAoW1AxODQzXShodHRwczovL3d3dy53aWtpZGF0YS5vcmcvd2lraS9Qcm9wZXJ0eTpQMTg0MykpLiBBbW9uZyB0aGUgYHIgcHJldHR5TnVtKG5yb3codGF4b25zKSwgIiwiKWAgY29sbGVjdGVkIHRheG9ucywgd2UgaGF2ZSBmb2xsb3dpbmcgY29tcGxldGVuZXNzIHN0YXRpc3RpY3M6CgpgYGB7ciB0YXhvbl9jb21wbGV0ZW5lc3N9CnRheG9uX2NvbXBsZXRlbmVzcyA8LSB0YXhvbnMgJT4lCiAgc2VsZWN0KGVudGl0eSwgc3Fvb3BfbGFiZWwsIHNxb29wX2Rlc2NyaXB0aW9uLCBxdWVyeV9hbGlhc2VzLCBxdWVyeV90YXhvbl9jb21tb25fbmFtZXMpICU+JQogIHRyYW5zbXV0ZSgKICAgIGl0ZW0gPSBlbnRpdHksCiAgICBgYWxpYXMoZXMpYCA9ICFtYXBfbGdsKHF1ZXJ5X2FsaWFzZXMsIH4gYWxsKGlzLm5hKC54KSkpLAogICAgYHRheG9uIGNvbW1vbiBuYW1lKHMpYCA9ICFtYXBfbGdsKHF1ZXJ5X3RheG9uX2NvbW1vbl9uYW1lcywgfiBhbGwoaXMubmEoLngpKSksCiAgICBsYWJlbCA9ICFpcy5uYShzcW9vcF9sYWJlbCksCiAgICBkZXNjcmlwdGlvbiA9ICFpcy5uYShzcW9vcF9kZXNjcmlwdGlvbiksCiAgKSAlPiUKICBnYXRoZXIoaGFzLCB2YWwsIC1pdGVtKSAlPiUKICBhcnJhbmdlKGl0ZW0sIGhhcywgdmFsKQpgYGAKCmBgYHtyIGNvbXBsZXRlbmVzc19jb21iaW5hdGlvbnN9CnRheG9uX2NvbXBsZXRlbmVzcyAlPiUKICBmaWx0ZXIodmFsKSAlPiUKICBncm91cF9ieShpdGVtKSAlPiUKICBzdW1tYXJpemUobl9oYXMgPSBuKCksIGhhcyA9IHBhc3RlMChoYXMsIGNvbGxhcHNlID0gIiwgIikpICU+JQogIGNvdW50KG5faGFzLCBoYXMpICU+JQogIGFycmFuZ2UoZGVzYyhuKSkgJT4lCiAgbXV0YXRlKHByb3AgPSBzcHJpbnRmKCIlLjNmJSUiLCAxMDAgKiBuIC8gc3VtKG4pKSkgJT4lCiAga2FibGUoZXNjYXBlID0gRkFMU0UsIGNhcHRpb24gPSAiRW5nbGlzaCBpbmZvIGNvbXBsZXRlbmVzcyBvZiB0YXhvbnMgb24gV2lraWRhdGEiLAogICAgICAgIGNvbC5uYW1lcyA9IGMoIkZpZWxkcyBhdmFpbGFibGUgZm9yIGEgdGF4b24iLCAiSW5mbyBhdmFpbGFibGUgKGluIEVuZ2xpc2gpIiwgIkl0ZW1zIGluIGRhdGFzZXQiLCAiUHJvcG9ydGlvbiBvZiBkYXRhc2V0IikpICU+JQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSAic3RyaXBlZCIpCmBgYAoKQW5kLCBjb252ZXJzZWx5LCB0aGUgZm9sbG93aW5nIG1pc3NpbmduZXNzIHN0YXRpc3RpY3M6CgpgYGB7ciBtaXNzaW5nbmVzc19jb21iaW5hdGlvbnN9CnRheG9uX2NvbXBsZXRlbmVzcyAlPiUKICBmaWx0ZXIoIXZhbCkgJT4lCiAgZ3JvdXBfYnkoaXRlbSkgJT4lCiAgc3VtbWFyaXplKG5faGFzID0gbigpLCBoYXMgPSBwYXN0ZTAoaGFzLCBjb2xsYXBzZSA9ICIsICIpKSAlPiUKICBjb3VudChuX2hhcywgaGFzKSAlPiUKICBhcnJhbmdlKGRlc2MobikpICU+JQogIG11dGF0ZShwcm9wID0gc3ByaW50ZigiJS4zZiUlIiwgMTAwICogbiAvIHN1bShuKSkpICU+JQogIGthYmxlKGVzY2FwZSA9IEZBTFNFLCBjYXB0aW9uID0gIkVuZ2xpc2ggaW5mbyBtaXNzaW5nbmVzcyBvZiB0YXhvbnMgb24gV2lraWRhdGEiLAogICAgICAgIGNvbC5uYW1lcyA9IGMoIkZpZWxkcyBOT1QgYXZhaWxhYmxlIGZvciBhIHRheG9uIiwgIkluZm8gTk9UIGF2YWlsYWJsZSAoaW4gRW5nbGlzaCkiLCAiSXRlbXMgaW4gZGF0YXNldCIsICJQcm9wb3J0aW9uIG9mIGRhdGFzZXQiKSkgJT4lCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9ICJzdHJpcGVkIikKYGBgCgpUaG9zZSBhcmUgKmNvbWJpbmF0aW9ucyogb2YgbWlzc2luZyBmaWVsZHMuIFRoZSBmb2xsb3dpbmcgYXJlIHBlci1maWVsZCBjb21wbGV0ZW5lc3MgJiBtaXNzaW5nbmVzcyBzdGF0aXN0aWNzOgoKYGBge3IgaW5mb19jb21wbGV0ZW5lc3N9CmluZm9fY29tcGxldGVuZXNzIDwtIHRheG9uX2NvbXBsZXRlbmVzcyAlPiUKICBncm91cF9ieShoYXMpICU+JQogIHN1bW1hcml6ZShuID0gc3VtKHZhbCksIHRvdGFsID0gbigpKQppbmZvX2NvbXBsZXRlbmVzc19uIDwtIHNldF9uYW1lcyhpbmZvX2NvbXBsZXRlbmVzcyRuLCBpbmZvX2NvbXBsZXRlbmVzcyRoYXMpCmluZm9fY29tcGxldGVuZXNzICU+JQogIHRyYW5zbXV0ZShoYXMgPSBoYXMsCiAgICAgICAgICAgIHByb3AxID0gc3ByaW50ZigiJXMgKCUuM2YlJSkiLCBjb21wcmVzcyhuKSwgMTAwICogbiAvIHRvdGFsKSwKICAgICAgICAgICAgcHJvcDIgPSBzcHJpbnRmKCIlcyAoJS4zZiUlKSIsIGNvbXByZXNzKHRvdGFsIC0gbiksIDEwMCAqICh0b3RhbCAtIG4pIC8gdG90YWwpKSAlPiUKICBrYWJsZShjb2wubmFtZXMgPSBjKCJJbmZvcm1hdGlvbiBhIHRheG9uIGl0ZW0gbWF5IGhhdmUiLCAiSG93IG1hbnkgaGF2ZSBhIHZhbHVlIChpbiBFbmdsaXNoKSIsICJIb3cgbWFueSBkbyBOT1QgaGF2ZSBhIHZhbHVlIChpbiBFbmdsaXNoKSIpLAogICAgICAgIGNhcHRpb24gPSBzcHJpbnRmKCJFbmdsaXNoIGNvbXBsZXRlbmVzcyBvZiBpdGVtcyBhbW9uZyBhIHN1YnNldCBvZiAlcyB0YXhvbnMiLCBjb21wcmVzcyhucm93KHRheG9ucykpKSwKICAgICAgICBhbGlnbiA9IGMoImwiLCAiciIsICJyIikpICU+JQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSAic3RyaXBlZCIpCmBgYAoKQ29uc2lkZXJpbmcgdGhhdCB0aGUgY29sbGVjdGVkIGRhdGFzZXQgKm9ubHkqIGluY2x1ZGVkIHRheG9ucyB3aGljaCBoYWQgYXQgbGVhc3Qgb25lIGFsaWFzIG9yIGF0IGxlYXN0IG9uZSB0YXhvbiBjb21tb24gbmFtZSwgdGhlIG1pc3NpbmduZXNzIG9mIHRob3NlIHR3byBpdGVtcyAtLSBib3RoIG9mIHdoaWNoIGFpZCBhIGxvdCBpbiBzZWFyY2ggLS0gaXMgcmF0aGVyIGNvbmNlcm5pbmcuCgpBZ2FpbiwgdGhlc2UgbnVtYmVycyBhcmUgKm5vdCogcmVwcmVzZW50YXRpdmUgb2YgYWxsIGByIGNvbXByZXNzKG5fdGF4b25zKWAgdGF4b25zLiBBcyBhIHJlbWluZGVyLCB0aGUgZGF0YXNldCBvZiBgciBjb21wcmVzcyhucm93KHRheG9ucykpYCB0YXhvbnMgc3R1ZGllZCB3YXMgbGltaXRlZCB0byB0aG9zZSB3aGljaCBoYWQgKDEpIGF0IGxlYXN0IG9uZSBFbmdsaXNoIGFsaWFzIG9yICgyKSBhdCBsZWFzdCBvbmUgRW5nbGlzaCB0YXhvbiBjb21tb24gbmFtZS4gSXQncyBub3QgY2xlYXIgaG93IG1hbnkgdGF4b25zIG9uIFdpa2lkYXRhIGRvIGhhdmUgYSBkZXNjcmlwdGlvbiBvciBhIGxhYmVsLCBidXQgd2UgY2FuIGF0IGxlYXN0IHNheSB0aGF0IG9mIGByIGNvbXByZXNzKG5fdGF4b25zKWAsIG9ubHkgYHIgY29tcHJlc3MoIGluZm9fY29tcGxldGVuZXNzX25bImFsaWFzKGVzKSJdKWAgKGByIHNwcmludGYoIiUuM2YlJSIsIDEwMCAqIGluZm9fY29tcGxldGVuZXNzX25bImFsaWFzKGVzKSJdIC8gbl90YXhvbnMpYCkgdGF4b25zIGhhdmUgYXQgbGVhc3Qgb25lIGFsaWFzIGluIEVuZ2xpc2ggYW5kIG9ubHkgYHIgY29tcHJlc3MoaW5mb19jb21wbGV0ZW5lc3NfblsidGF4b24gY29tbW9uIG5hbWUocykiXSlgIChgciBzcHJpbnRmKCIlLjNmJSUiLCAxMDAgKiBpbmZvX2NvbXBsZXRlbmVzc19uWyJ0YXhvbiBjb21tb24gbmFtZShzKSJdIC8gbl90YXhvbnMpYCkgdGF4b25zIGhhdmUgYXQgbGVhc3Qgb25lIHRheG9uIGNvbW1vbiBuYW1lIGluIEVuZ2xpc2guCgoqKk5vdGUqKjogQSBmb2xsb3ctdXAgb2YgdGhpcyB3b3JrIHNob3VsZCBpbmNsdWRlIGFsbCBsYW5ndWFnZXMuIFdlIHJlc3RyaWN0ZWQgdGhpcyBpbml0aWFsIGV4cGxvcmF0aW9uIHRvIEVuZ2xpc2ggYXMgdGhhdCBpcyB0aGUgYW5hbHlzdCdzIHByaW1hcnkgbGFuZ3VhZ2UuCgojIyBHcm91cHMgb2Ygb3JnYW5pc21zIGtub3duIGJ5IG9uZSBwYXJ0aWN1bGFyIGNvbW1vbiBuYW1lCgpBbiBpdGVtIG1heSBhbHNvIGJlIGFuIGluc3RhbmNlIG9mICJncm91cHMgb2Ygb3JnYW5pc21zIGtub3duIGJ5IG9uZSBwYXJ0aWN1bGFyIGNvbW1vbiBuYW1lIiAoW1E1NTk4MzcxNV0oaHR0cHM6Ly93d3cud2lraWRhdGEub3JnL3dpa2kvUTU1OTgzNzE1KSkuIFRoZSBmb2xsb3dpbmcgYXJlIHNvbWUgZXhhbXBsZXMgb2Ygc3VjaCBpdGVtczoKCmBgYHtyIGV4YW1wbGVfY29tbW9uc30KY29tbW9ucyAlPiUKICBmaWx0ZXIoaXRlbSAlaW4lIGMoIlE1IiwgIlExMTk0NjIwMiIsICJRMzQ2OTU5MiIsICJRMTEwNjUwMzYiLCAiUTE3MTI4NzU3IikpICU+JQogIGFycmFuZ2UoZGVzYyhpdGVtKSkgJT4lCiAgbXV0YXRlKAogICAgaXRlbSA9IHNwcmludGYoIiVzICglcykiLCBsYWJlbCwgd2lraWRhdGFfbGluayhpdGVtKSksCiAgICBkZXNjcmlwdGlvbiA9IGlmZWxzZShpcy5uYShkZXNjcmlwdGlvbiksICLigJQiLCBkZXNjcmlwdGlvbiksCiAgICBhbGlhc2VzID0gbWFwKGFsaWFzZXMsIG1heWJlX21ha2VfbGlzdCksCiAgICBncm91cHMgPSBtYXAoZ3JvdXBzLCBtYWtlX21pbmlfdGFibGUpLAogICAgZGlmZmVyZW50X2Zyb20gPSBtYXAoZGlmZmVyZW50X2Zyb20sIG1ha2VfbWluaV90YWJsZSkKICApICU+JQogIHNlbGVjdCgtbGFiZWwpICU+JQogIGthYmxlKGVzY2FwZSA9IEZBTFNFLAogICAgICAgIGNvbC5uYW1lcyA9IGMoIkl0ZW0iLCAiRGVzY3JpcHRpb24iLCAiQWxpYXMoZXMpIiwKICAgICAgICAgICAgICAgICAgICAgICJHcm91cChzKSAoSXRlbSwgTGFiZWwpIiwgIkRpZmZlcmVudCBGcm9tIChJdGVtLCBMYWJlbCkiKSwKICAgICAgICBjYXB0aW9uID0gIldpa2lkYXRhIGVudGl0aWVzIHRoYXQgYXJlIGluc3RhbmNlcyBvZiAnZ3JvdXBzIG9mIG9yZ2FuaXNtcyBrbm93biBieSBvbmUgcGFydGljdWxhciBjb21tb24gbmFtZSciKSAlPiUKICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBUUlVFKSAlPiUKICBjb2x1bW5fc3BlYygxLCB3aWR0aCA9ICIxMDBweCIpICU+JQogIGNvbHVtbl9zcGVjKDIsIHdpZHRoID0gIjE1MHB4IikKYGBgCgpTaW1pbGFyIHRoZSB3b3JrIG9uIHRheG9ucywgd2UgY2FuIGNhbGN1bGF0ZSBzb21lIGNvbXBsZXRlbmVzcyBzdGF0aXN0aWNzIG9uIHRoZXNlIGl0ZW1zLiBBbHRob3VnaCB1bmxpa2UgdGhlIGNhc2Ugd2l0aCB0YXhvbnMsIHRoZXNlIChFbmdsaXNoLWZvY3VzZWQpIHN0YXRpc3RpY3MgYXBwbHkgdG8gKmFsbCogaW5zdGFuY2VzIGZvdW5kIG9uIFdpa2lkYXRhLgoKYGBge3J9CmNvbW1vbnMgJT4lCiAgdHJhbnNtdXRlKAogICAgbGFiZWwgPSAhaXMubmEobGFiZWwpLAogICAgZGVzY3JpcHRpb24gPSAhaXMubmEoZGVzY3JpcHRpb24pLAogICAgYGFsaWFzKGVzKWAgPSBtYXBfaW50KGFsaWFzZXMsIH4gc3VtKCFpcy5uYSgueCkpKSA+IDAsCiAgICBgYXQgbGVhc3Qgb25lICJvZiIgcXVhbGlmaWVyYCA9IG1hcF9pbnQoZ3JvdXBzLCB+IHN1bSghaXMubmEoLnhbWzFdXSRpdGVtKSkpID4gMCwKICAgIGBhdCBsZWFzdCBvbmUgImRpZmZlcmVudCBmcm9tIiBzdGF0ZW1lbnRgID0gbWFwX2ludChkaWZmZXJlbnRfZnJvbSwgfiBzdW0oIWlzLm5hKC54W1sxXV0kaXRlbSkpKSA+IDAKICApICU+JQogIGdhdGhlcihoYXMsIHZhbCkgJT4lCiAgbXV0YXRlKGhhcyA9IGZhY3RvcihoYXMsIGMoImxhYmVsIiwgImRlc2NyaXB0aW9uIiwgImFsaWFzKGVzKSIsICJhdCBsZWFzdCBvbmUgXCJvZlwiIHF1YWxpZmllciIsICJhdCBsZWFzdCBvbmUgXCJkaWZmZXJlbnQgZnJvbVwiIHN0YXRlbWVudCIpKSkgJT4lCiAgZ3JvdXBfYnkoaGFzKSAlPiUKICBzdW1tYXJpemUocHJvcCA9IHNwcmludGYoIiUuMWYlJSIsIDEwMCAqIG1lYW4odmFsKSkpICU+JQogIGthYmxlKGNvbC5uYW1lcyA9IGMoIkluZm9ybWF0aW9uIGFuIGl0ZW0gbWF5IGhhdmUiLCAiSG93IG1hbnkgaGF2ZSBhIHZhbHVlIChpbiBFbmdsaXNoKSIpLAogICAgICAgIGNhcHRpb24gPSAiRW5nbGlzaCBjb21wbGV0ZW5lc3Mgb2YgaXRlbXMgd2hpY2ggYXJlIGluc3RhbmNlcyBvZiAnZ3JvdXAgb2Ygb3JnYW5pc21zIGtub3duIGJ5IG9uZSBwYXJ0aWN1bGFyIGNvbW1vbiBuYW1lJyIsCiAgICAgICAgYWxpZ24gPSBjKCJsIiwgInIiKSkgJT4lCiAga2FibGVfc3R5bGluZygic3RyaXBlZCIpICU+JQogIGdyb3VwX3Jvd3MoaW5kZXggPSBjKCJJbmRleGVkIGZvciBzZWFyY2ggYXMgdGV4dCIgPSAzLCAiSW5kZXhlZCBmb3Igc2VhcmNoIGFzIHN0YXRlbWVudF9rZXl3b3JkcyIgPSAyKSkKYGBgCgpBcyBiZWZvcmUsIHdlIGNhbiBhbHNvIGxvb2sgYXQgY29tYmluYXRpb25zIG9mIG1pc3NpbmcgZmllbGRzIHRvIGRldGVybWluZSBob3cgbWFueSBpdGVtcyB3b3VsZCBiZSBlYXNpbHkgZm91bmQgYnkgc2VhcmNoaW5nIChlLmcuIGFuIGl0ZW0gd2hpY2ggaGFzIGEgbGFiZWwsIGEgZGVzY3JpcHRpb24sIGFuZCBhbiBhbGlhcyB3b3VsZCBiZSBtb3JlIGxpa2VseSB0byBiZSBmb3VuZCBieSBzb21lb25lIGxvb2tpbmcgZm9yIGl0IHRoYW4gYW4gaXRlbSB3aXRoIG9ubHksIHNheSwgYSBsYWJlbCk6CgpgYGB7cn0KY29tbW9uc19jb21wbGV0ZW5lc3MgPC0gY29tbW9ucyAlPiUKICB0cmFuc211dGUoCiAgICBpdGVtID0gaXRlbSwKICAgIGxhYmVsID0gIWlzLm5hKGxhYmVsKSwKICAgIGRlc2NyaXB0aW9uID0gIWlzLm5hKGRlc2NyaXB0aW9uKSwKICAgIGBhbGlhcyhlcylgID0gbWFwX2ludChhbGlhc2VzLCB+IHN1bSghaXMubmEoLngpKSkgPiAwLAogICAgYGF0IGxlYXN0IG9uZSAib2YiIHF1YWxpZmllcmAgPSBtYXBfaW50KGdyb3VwcywgfiBzdW0oIWlzLm5hKC54W1sxXV0kaXRlbSkpKSA+IDAsCiAgICBgYXQgbGVhc3Qgb25lICJkaWZmZXJlbnQgZnJvbSIgc3RhdGVtZW50YCA9IG1hcF9pbnQoZGlmZmVyZW50X2Zyb20sIH4gc3VtKCFpcy5uYSgueFtbMV1dJGl0ZW0pKSkgPiAwCiAgKSAlPiUKICBnYXRoZXIoaGFzLCB2YWwsIC1pdGVtKSAlPiUKICBtdXRhdGUoaGFzID0gZmFjdG9yKGhhcywgYygibGFiZWwiLCAiZGVzY3JpcHRpb24iLCAiYWxpYXMoZXMpIiwgImF0IGxlYXN0IG9uZSBcIm9mXCIgcXVhbGlmaWVyIiwgImF0IGxlYXN0IG9uZSBcImRpZmZlcmVudCBmcm9tXCIgc3RhdGVtZW50IikpKSAlPiUKICBmaWx0ZXIodmFsKSAlPiUKICBncm91cF9ieShpdGVtKSAlPiUKICBzdW1tYXJpemUobl9oYXMgPSBuKCksIGhhcyA9IHBhc3RlMChoYXMsIGNvbGxhcHNlID0gIiwgIikpICU+JQogIGNvdW50KG5faGFzLCBoYXMpICU+JQogIGFycmFuZ2UoZGVzYyhuX2hhcyksIGRlc2MobikpICU+JQogIG11dGF0ZShwcm9wID0gc3ByaW50ZigiJS4yZiUlIiwgMTAwICogbiAvIHN1bShuKSkpCmNvbW1vbnNfY29tcGxldGVuZXNzICU+JQogIGRwbHlyOjpzZWxlY3QoLW5faGFzKSAlPiUKICBrYWJsZShlc2NhcGUgPSBGQUxTRSwgY2FwdGlvbiA9ICJFbmdsaXNoIGluZm9ybWF0aW9uIGNvbXBsZXRlbmVzcyBvZiAnZ3JvdXAgb2Ygb3JnYW5pc21zIGtub3duIGJ5IG9uZSBwYXJ0aWN1bGFyIGNvbW1vbiBuYW1lJyBpbnN0YW5jZXMgb24gV2lraWRhdGEiLAogICAgICAgIGNvbC5uYW1lcyA9IGMoIkZpZWxkcyBvZiBpbmZvcm1hdGlvbiBhdmFpbGFibGUgKGluIEVuZ2xpc2gpIiwgIkl0ZW1zIG9uIFdpa2lkYXRhIiwgIlByb3BvcnRpb24gYW1vbmcgYWxsIHN1Y2ggaW5zdGFuY2VzIikpICU+JQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSAic3RyaXBlZCIpICU+JQogIGdyb3VwX3Jvd3MoaW5kZXggPSBhdXRvX2luZGV4KGNvbW1vbnNfY29tcGxldGVuZXNzJG5faGFzKSwgZ3JvdXBfbGFiZWwgPSAiRmllbGRzIGF2YWlsYWJsZSBmb3IgYW4gaXRlbSIpCmBgYAo=