### Install and oad packages if necessary
pacman::p_load(tidyverse, 
               magrittr, 
               tidymodels
               )

Welcome to todays session.

Exploring Chinese Patent Data

Introduction

  • So, let’s start the fun.
  • I for you extracted Chinese patents from our EPO PATSTAT databases filed at either the EPO or the USTPO.
  • I further provide you additional data to be found in PATSTAT.
patents <- readRDS(url("https://github.com/daniel-hain/ML_course_maastricht/raw/master/data/CN_patent.rds?raw=true"))
pat_abstr <- readRDS(url("https://github.com/daniel-hain/ML_course_maastricht/raw/master/data/CN_el_patent_abstract.rds?raw=true"))
pat_cpc <- readRDS(url("https://github.com/daniel-hain/ML_course_maastricht/raw/master/data/CN_el_cpc.rds?raw=true"))
  • Filter for post-2013
patents %<>% filter(appln_filing_year >= 2013)
  • Filter for only patents with entry in main data table
pat_abstr %<>% semi_join(patents, by = 'appln_id')
pat_cpc %<>% semi_join(patents, by = 'appln_id')

Patent main data

patents %>% head()
patents %>% glimpse()
Rows: 23,254
Columns: 5
$ appln_id            <int> 447532870, 410465871, 410460263, 418875468, 416430384, 420520135, 438136257, 408895739, 417619658, 423833383, 420883518, 46…
$ appln_filing_year   <int> 2015, 2013, 2013, 2014, 2013, 2014, 2014, 2013, 2014, 2013, 2014, 2014, 2014, 2014, 2014, 2015, 2015, 2015, 2014, 2013, 201…
$ docdb_family_size   <int> 20, 19, 8, 18, 4, 17, 13, 10, 11, 5, 5, 12, 13, 4, 11, 11, 7, 2, 9, 3, 9, 23, 10, 12, 15, 20, 25, 20, 20, 7, 19, 19, 30, 17…
$ nb_citing_docdb_fam <int> 61, 17, 125, 0, 36, 21, 21, 8, 5, 10, 64, 44, 36, 7, 75, 33, 17, 28, 40, 5, 123, 8, 4, 70, 16, 32, 31, 16, 11, 10, 5, 11, 1…
$ nb_inventors        <int> 3, 6, 9, 2, 4, 4, 2, 1, 2, 3, 2, 6, 2, 2, 4, 4, 6, 4, 5, 3, 4, 7, 2, 4, 4, 3, 4, 3, 4, 3, 6, 5, 5, 4, 3, 3, 5, 9, 10, 1, 10…

This main dataset contains all Patents in the 2000-2015 period with Chinese inventors, filed at the USTPO or EPO. I only included priority (earliest) patent applications which got granted up to now. We have the following variables:

  • appln_id: PATSTAT id, unique identifier of patent application
  • appln_filing_year: Filing year of first priority
  • docdb_family_size: Size of the (simple) patent family
  • nb_citing_docdb_fam: Number of citations recieved by the patent family
  • nb_inventors: Number of inventors
patents %>% skimr::skim()
── Data Summary ────────────────────────
                           Values    
Name                       Piped data
Number of rows             23254     
Number of columns          5         
_______________________              
Column type frequency:               
  numeric                  5         
________________________             
Group variables            None      

── Variable type: numeric ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  skim_variable       n_missing complete_rate         mean           sd        p0        p25        p50        p75      p100 hist 
1 appln_id                    0             1 441983572.   20224263.    380196462 422944781. 443529288. 451974322. 497361534 ▁▇▇▅▂
2 appln_filing_year           0             1      2014.          0.796      2013      2013       2014       2015       2015 ▇▁▇▁▆
3 docdb_family_size           0             1         3.79        3.47          1         2          3          4        195 ▇▁▁▁▁
4 nb_citing_docdb_fam         0             1         3.45        7.58          0         0          2          4        365 ▇▁▁▁▁
5 nb_inventors                0             1         3.19        2.21          1         2          3          4         30 ▇▁▁▁▁

Patent CPC Class

  • The Cooperative Patent Classification (CPC) assigns patents to technology classes
pat_cpc %>% glimpse()
Rows: 150,944
Columns: 2
$ appln_id         <int> 380196462, 380277821, 380277821, 380277821, 380277821, 380277821, 380277821, 380331159, 380331159, 380331159, 380331159, 38033…
$ cpc_class_symbol <chr> "B66B  23/22", "A61B   6/0407", "A61B   6/102", "A61B   6/4283", "A61B   6/4452", "F16M  13/022", "G03B  42/025", "F16J  15/06…
pat_cpc %>% head()
pat_cpc %>% 
  count(cpc_class_symbol, sort = TRUE) %>%
  head(10)

Patent Abstracts

  • The patent abstract briefly summarizes the content of the patent. This is what we will use for our clasifiction exercise later
pat_abstr %>% glimpse()
Rows: 22,694
Columns: 2
$ appln_id       <int> 380196462, 380277821, 380331159, 380487873, 380565640, 380623547, 380646500, 380646539, 380717584, 380717586, 380717588, 3807528…
$ appln_abstract <chr> "An easily mounting and dismantling outer decking apparatus for an escalator or moving walkway is provided, which comprises: a c…
pat_abstr %>% select(-appln_id) %>% head(3)

Exploratory Analysis

We could have so much fun here exploring Chinese patents, but we have no time. However, I do another more exploratory lecture on the ame dataset, feel free to check:

The Y Tag: How to identify renewable energy patents

We now aim at identifying renewable energy patents. This could be the starting point for an interesting analysis on all kind of things, but we here went to ask the following question:

  1. Could we develop a model that detects renewable energy patents based on their abstract?
  • We exploit the WIPOs Y-tag.
  • Here, the WIPO labels patents identified to be related to renewable energy with the additional CPC class assignment Y02 which helps us to easily identify them.
  • Check here for further information.

Lets identify renewable energy patents.

y_tag <- pat_cpc %>%
  filter(cpc_class_symbol %>% str_starts('Y02')) %>%
  distinct(appln_id) %>%
  pull()
patents %<>%
  mutate(y_tag = appln_id %in% y_tag)
patents %>% head()
rm(pat_cpc)

Text analysis of patent data

The R NLP ecosystem (Brief reminder)

Most language analysis approaches are based on the analysis of texts word-by-word. Here, their order might matter (word sequence models) or not (bag-of-words models), but the smallest unit of analysis is usually the word. This is usually done in context of the document the word appeared in. Therefore, on first glance three types datastructures make sense:

  1. Tidy: Approach, where data is served in a 2-column document-word format (e.g., tidytext)
  2. Token lists: Creation of special objects, saved as document-token lists or corpus (e.g., tm, quanteda)
  3. Matrix: Long approach, where data is served as document-term matrix, term-frequency matrix, etc.

Different forms of analysis (and the packages used therefore) favor different structures, so we need to be fluent in transfering original raw-text in these formats, as well as switching between them. (for more infos, check here).

library(tidytext)

Tidy Text Formats

  • To explore the text a bit, lets bring it into a tidy format
pat_abstr_tidy <- pat_abstr %>%
  unnest_tokens(output = word, 
                input = appln_abstract, 
                token = "words",
                to_lower = TRUE,
                drop = TRUE)
  • And do the usual preprocessing
 pat_abstr_tidy %<>%
  mutate(word = word %>% str_remove_all('[^[:alnum:]]')) %>%
  filter(str_length(word) > 2 ) %>%
  group_by(word) %>%
  filter(n() > 100) %>%
  ungroup() %>%
  anti_join(get_stopwords()) 
Joining, by = "word"
  • MAke a bag-of-words model with TFIDF weights
pat_abstr_tidy %<>%
  add_count(appln_id, word) %>%
  bind_tf_idf(term = word,
              document = appln_id,
              n = n)
  • Lets merge the created Y-tag to teh abstracts
pat_abstr_tidy %<>%
  left_join(patents %>% select(appln_id, y_tag), by = "appln_id") %>%
  relocate(appln_id, y_tag)
  • ANd see what we got
pat_abstr_tidy %>%
  head()
pat_abstr_tidy %>%
  count(word, wt = tf_idf, sort = TRUE) %>%
  head(50)
  • Do patents with Y tags use distinct vocabulary?
pat_ytag_words <- pat_abstr_tidy %>%
  group_by(y_tag) %>%
  count(word, wt = tf_idf, sort = TRUE, name = "tf_idf") %>%
  slice(1:20) %>%
  ungroup() %>%
  mutate(word = reorder_within(word, by = tf_idf, within = y_tag)) 
pat_ytag_words %>%
  ggplot(aes(x = word, y = tf_idf, fill = y_tag)) +
  geom_col(show.legend = FALSE) +
  labs(x = NULL, y = "tf-idf") +
  facet_wrap(~y_tag, ncol = 2, scales = "free") +
  coord_flip() +
  scale_x_reordered()

Towards a predictive model?

  • We see there seems to be quite a difference in the words used in Y-tagged renewable energy patents.
  • Therefore it indeed might be possible to build a predictive model based on text…
  • How would we do that?
  • The easiest way would be to create a document-term-matrix, basically creating a dummy variable for every term.
  • We could do it a bit more sophisticated, and instead use the TFIDF weight.
# only a small sample for illustration
pat_dtm <- pat_abstr_tidy %>%  
  select(appln_id, y_tag, word, tf_idf) %>%
  head(1000) %>%
  distinct(appln_id, word, .keep_all = TRUE)
# Now we just pivot wider
pat_dtm %<>% 
  pivot_wider(names_from = word, values_from = tf_idf, names_prefix = 'word_', values_fill = 0)
pat_dtm %>% head()
  • We could straight put this matrix into a model
  • However, there are easier ways to do so, if the purpose is to create a predictive model.
rm(pat_dtm)

Building a predictive model

data <- pat_abstr %>%
  inner_join(patents %>% select(appln_id, y_tag), by = "appln_id") %>%
  select(y_tag, appln_abstract) %>%
  rename(y = y_tag, text = appln_abstract) %>%
  mutate(y = y %>% as_factor()) %>%
  mutate(text = text %>% str_to_lower() %>% str_remove_all('[^[:alnum:] ]') %>% str_squish()) %>%
  drop_na() 
data %>% skimr::skim()
── Data Summary ────────────────────────
                           Values    
Name                       Piped data
Number of rows             22694     
Number of columns          2         
_______________________              
Column type frequency:               
  character                1         
  factor                   1         
________________________             
Group variables            None      

── Variable type: character ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  skim_variable n_missing complete_rate   min   max empty n_unique whitespace
1 text                  0             1    22  1981     0    22615          0

── Variable type: factor ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  skim_variable n_missing complete_rate ordered n_unique top_counts           
1 y                     0             1 FALSE          2 FAL: 21302, TRU: 1392

Training & Test split

set.seed(1337)

data_split <- initial_split(data, prop = 0.75, strata = y)

data_train <- data_split  %>%  training()
data_test <- data_split %>% testing()

Preprocessing pipeline

  • While we up to now did all the NLP by hand, we can also just use the textrecipes package to automatize the workflow
library(textrecipes)
data_recipe <- data_train %>%
  recipe(y ~.) %>%
  themis::step_downsample(y) %>% # due to class imbalances%>%
  step_filter(text != "")  %>%
  # How textrecipes start
  step_tokenize(text) %>% # Tokenizing
  step_tokenfilter(text, min_times = 100) %>%  # filter sparse terms
  step_stopwords(text, keep = FALSE) %>% # Filter stopwords
  step_tfidf(text) %>% # tfidf weighting
  # step_knnimpute(all_predictors()) %>% #  knn inputation of missing values Not necessary here
  prep() # !!! NOTE: Only prep() the recipe if you dont want to use it in a workflow, otherwise there might b ussues
data_recipe
Recipe

Inputs:

Training data contained 17020 data points and no missing data.

Operations:

Down-sampling based on y [trained]
Row filtering [trained]
Tokenization for text [trained]
Text filtering for text [trained]
Stop word removal for text [trained]
Term frequency-inverse document frequency with text [trained]
  • Since we do not use any workflows lter, we can directly prepare the test and training data
data_train_prep <- data_recipe %>% juice()
data_test_prep <- data_recipe %>% bake(data_test)

Defining the models

# # Use this code in case you want to do parallel processing

# library(doParallel)
# all_cores <- parallel::detectCores(logical = FALSE)

# cl <- makePSOCKcluster(all_cores -1)
#registerDoParallel(cl)
model_en <- logistic_reg(mode = 'classification', 
                         mixture = 0.25, 
                         penalty = 0.25) %>%
  set_engine('glm', family = binomial()) 

Define the workflow

We will skip the workflow step this time, since we do not evaluate different models against each others.

fit the model

fit_en <- model_en %>% fit(formula = y ~., data = data_train_prep)
pred_collected <- tibble(
  truth = data_train_prep %>% pull(y),
  pred = fit_en %>% predict(new_data = data_train_prep) %>% pull(.pred_class),
  pred_prob = fit_en %>% predict(new_data = data_train_prep, type = "prob") %>% pull(.pred_TRUE),
  ) 
pred_collected %>% conf_mat(truth, pred) 
          Truth
Prediction FALSE TRUE
     FALSE   792  363
     TRUE    253  682
pred_collected %>% conf_mat(truth, pred) %>% autoplot(type = 'heatmap')

pred_collected %>% conf_mat(truth, pred) %>% summary()

Model explainability

Machine learning (ML) models are often considered black boxes due to their complex inner-workings. More advanced ML models such as random forests, gradient boosting machines (GBM), artificial neural networks (ANN), among others are typically more accurate for predicting nonlinear, faint, or rare phenomena. Unfortunately, more accuracy often comes at the expense of interpretability, and interpretability is crucial for business adoption, model documentation, regulatory oversight, and human acceptance and trust. Luckily, several advancements have been made to aid in interpreting ML models.

Moreover, it’s often important to understand the ML model that you’ve trained on a global scale, and also to zoom into local regions of your data or your predictions and derive local explanations.

  • Global interpretations help us understand the inputs and their entire modeled relationship with the prediction target, but global interpretations can be highly approximate in some cases.
  • Local interpretation help us understand model predictions for a single row of data or a group of similar rows.

Global explanation

Finally, let’s get a feeling what variables the models mostly draw from. There are numerous ways for such inspections, in which we will just scratch the surface here. Here, I just want to present the most intuitive and common one, Variable Importance.

Most (but not all) model classes offer some possibility to derive measures of variable importance. Note, this is currently not implemented for SVMs. Again, in most ML models and setups, these measures are nice to give a rough intuition, but CANNOT be interpreted as constant marginal effects, left alone causal.

library(vip)

Attaching package: ‘vip’

The following object is masked from ‘package:utils’:

    vi
vip(fit_en) + ggtitle("VarImp: Elastic Net")

Local interpretations

In complex nonparametric models, it is often more helpful to get explanations why a certain datapoint is classified in the way it is than to look at the overall importance of variables.

Local Interpretable Model-agnostic Explanations (LIME) is a visualization technique that helps explain individual predictions. As the name implies, it is model agnostic so it can be applied to any supervised regression or classification model. There is a neath R implementation in the lime package. If you want to investigate further, feel free! Otherwise, keep in mind this exists, we will come back to it again in later lectures.

BTW: The original paper is mindblowing, if you find time, just read it!

  • Marco Tulio Ribeiro, Sameer Singh, and Carlos Guestrin. 2016. "“hy Should I Trust You?: Explaining the Predictions of Any Classifier.” In Proceedings of the 22nd ACM SIGKDD International Conference on Knowledge Discovery and Data Mining (KDD 2016). ACM, New York, NY, USA, 1135-1144. DOI: https://doi.org/10.1145/2939672.2939778
library(lime)

Attaching package: ‘lime’

The following object is masked from ‘package:dplyr’:

    explain
# Create an explainer object
explainer <- lime(data_train_prep, fit_en)
# Explain new observation
explanation <- explain(data_train_prep %>% slice(1:6), explainer, n_labels = 1, n_features = 5)
# The output is provided in a consistent tabular format and includes the output from the model.
explanation %>% head()
explanation %>% plot_features()

Bonus: Own predictions

  • We could also just apply the model to some own text and see how it predicts…
text_own = tibble(text = 'This device is going to save the world that includes a portion of solar photovoltaic power to control bio enriched energy saviung algae that produces a highly energy body state that can be used for mobile energy production.')
  • We run this text through our preprocessing recipe and let our model predict…
fit_en %>% predict(new_data = data_recipe %>% bake(text_own))
text_cordis <- read_csv('https://github.com/SDS-AAU/SDS-master/raw/master/M2/data/cordis-h2020reports.gz') 
New names:
* `` -> ...1
Rows: 500 Columns: 15
── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr  (10): language, title, teaser, summary, workPerformed, finalResults, projectAcronym, programme, topics, url
dbl   (3): ...1, rcn, projectID
lgl   (1): country
dttm  (1): lastUpdateDate

ℹ 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.
pred_cordis <- tibble(pred = fit_en %>% predict(new_data = data_recipe %>% bake(text_cordis %>% rename(text = summary) %>% select(text))) %>% pull(.pred_class),
                      pred_prob = fit_en %>% predict(new_data = data_recipe %>% bake(text_cordis %>% rename(text = summary) %>% select(text)), type = "prob") %>% pull(.pred_TRUE))
text_cordis %>%
  bind_cols(pred_cordis) %>%
  filter(pred == 'TRUE') %>%
  arrange(desc(pred_prob)) %>%
  select(projectAcronym, title, pred_prob) %>%
  head(50)

Endnotes

Packages and Ecosystem

  • tidymodels: Tidy statistical and predictive modeling ecosystem
  • tidytext: Tidy text analysis in R ecosystem
  • textrecipes: Preprocessing workflows for text data

Further Readings

Session info

sessionInfo()
LS0tCnRpdGxlOiAnTWFjaGluZSBMZWFybmluZzogQXBwbGljYXRpb25zIGluIHRlY2hub2xvZ3kgQW5hbHlzaXMgMScKYXV0aG9yOiAiRGFuaWVsIFMuIEhhaW4gKGRzaEBidXNpbmVzcy5hYXUuZGspIgpkYXRlOiAiVXBkYXRlZCBgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVCICVkLCAlWScpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdG9jOiB0cnVlCiAgICB0b2NfZGVwdGg6IDIKICAgIHRvY19mbG9hdDoKICAgICAgY29sbGFwc2VkOiBmYWxzZQogICAgdGhlbWU6IGZsYXRseQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQojIyMgR2VuZXJpYyBwcmVhbWJsZQpybShsaXN0PWxzKCkpClN5cy5zZXRlbnYoTEFORyA9ICJlbiIpICMgRm9yIGVuZ2xpc2ggbGFuZ3VhZ2UKb3B0aW9ucyhzY2lwZW4gPSA1KSAjIFRvIGRlYWN0aXZhdGUgYW5ub3lpbmcgc2NpZW50aWZpYyBudW1iZXIgbm90YXRpb24Kc2V0LnNlZWQoMTMzNykgIyBUbyBoYXZlIGEgc2VlZCBkZWZpbmVkIGZvciByZXByb2R1Y2FiaWxpdHkKCiMjIyBLbml0ciBvcHRpb25zCmxpYnJhcnkoa25pdHIpICMgRm9yIGRpc3BsYXkgb2YgdGhlIG1hcmtkb3duCmtuaXRyOjpvcHRzX2NodW5rJHNldCh3YXJuaW5nPUZBTFNFLAogICAgICAgICAgICAgICAgICAgICBtZXNzYWdlPUZBTFNFLAogICAgICAgICAgICAgICAgICAgICBmaWcuYWxpZ249ImNlbnRlciIKICAgICAgICAgICAgICAgICAgICAgKQoKIyMjIEluc3RhbGwgcGFja2FnZXMgaWYgbmVjZXNzYXJ5CmlmICghcmVxdWlyZSgicGFjbWFuIikpIGluc3RhbGwucGFja2FnZXMoInBhY21hbiIpICMgcGFja2FnZSBmb3IgbG9hZGluZyBhbmQgY2hlY2tpbmcgcGFja2FnZXMgOikKYGBgCgpgYGB7cn0KIyMjIEluc3RhbGwgYW5kIG9hZCBwYWNrYWdlcyBpZiBuZWNlc3NhcnkKcGFjbWFuOjpwX2xvYWQodGlkeXZlcnNlLCAKICAgICAgICAgICAgICAgbWFncml0dHIsIAogICAgICAgICAgICAgICB0aWR5bW9kZWxzCiAgICAgICAgICAgICAgICkKYGBgCgpXZWxjb21lIHRvIHRvZGF5cyBzZXNzaW9uLgoKIyBFeHBsb3JpbmcgQ2hpbmVzZSBQYXRlbnQgRGF0YQoKIyMgSW50cm9kdWN0aW9uCgotICAgU28sIGxldCdzIHN0YXJ0IHRoZSBmdW4uCi0gICBJIGZvciB5b3UgZXh0cmFjdGVkIENoaW5lc2UgcGF0ZW50cyBmcm9tIG91ciBFUE8gW1BBVFNUQVRdKGh0dHBzOi8vd3d3LmVwby5vcmcvc2VhcmNoaW5nLWZvci1wYXRlbnRzL2J1c2luZXNzL3BhdHN0YXQuaHRtbCkgZGF0YWJhc2VzIGZpbGVkIGF0IGVpdGhlciB0aGUgRVBPIG9yIHRoZSBVU1RQTy4KLSAgIEkgZnVydGhlciBwcm92aWRlIHlvdSBhZGRpdGlvbmFsIGRhdGEgdG8gYmUgZm91bmQgaW4gUEFUU1RBVC4KCmBgYHtyfQpwYXRlbnRzIDwtIHJlYWRSRFModXJsKCJodHRwczovL2dpdGh1Yi5jb20vZGFuaWVsLWhhaW4vTUxfY291cnNlX21hYXN0cmljaHQvcmF3L21hc3Rlci9kYXRhL0NOX3BhdGVudC5yZHM/cmF3PXRydWUiKSkKcGF0X2Fic3RyIDwtIHJlYWRSRFModXJsKCJodHRwczovL2dpdGh1Yi5jb20vZGFuaWVsLWhhaW4vTUxfY291cnNlX21hYXN0cmljaHQvcmF3L21hc3Rlci9kYXRhL0NOX2VsX3BhdGVudF9hYnN0cmFjdC5yZHM/cmF3PXRydWUiKSkKcGF0X2NwYyA8LSByZWFkUkRTKHVybCgiaHR0cHM6Ly9naXRodWIuY29tL2RhbmllbC1oYWluL01MX2NvdXJzZV9tYWFzdHJpY2h0L3Jhdy9tYXN0ZXIvZGF0YS9DTl9lbF9jcGMucmRzP3Jhdz10cnVlIikpCmBgYAoKLSAgIEZpbHRlciBmb3IgcG9zdC0yMDEzCgpgYGB7cn0KcGF0ZW50cyAlPD4lIGZpbHRlcihhcHBsbl9maWxpbmdfeWVhciA+PSAyMDEzKQpgYGAKCi0gICBGaWx0ZXIgZm9yIG9ubHkgcGF0ZW50cyB3aXRoIGVudHJ5IGluIG1haW4gZGF0YSB0YWJsZQoKYGBge3J9CnBhdF9hYnN0ciAlPD4lIHNlbWlfam9pbihwYXRlbnRzLCBieSA9ICdhcHBsbl9pZCcpCnBhdF9jcGMgJTw+JSBzZW1pX2pvaW4ocGF0ZW50cywgYnkgPSAnYXBwbG5faWQnKQpgYGAKCiMjIFBhdGVudCBtYWluIGRhdGEKCmBgYHtyfQpwYXRlbnRzICU+JSBoZWFkKCkKYGBgCgpgYGB7cn0KcGF0ZW50cyAlPiUgZ2xpbXBzZSgpCmBgYAoKVGhpcyBtYWluIGRhdGFzZXQgY29udGFpbnMgYWxsIFBhdGVudHMgaW4gdGhlIDIwMDAtMjAxNSBwZXJpb2Qgd2l0aCBDaGluZXNlIGludmVudG9ycywgZmlsZWQgYXQgdGhlIFVTVFBPIG9yIEVQTy4gSSBvbmx5IGluY2x1ZGVkIHByaW9yaXR5IChlYXJsaWVzdCkgcGF0ZW50IGFwcGxpY2F0aW9ucyB3aGljaCBnb3QgZ3JhbnRlZCB1cCB0byBub3cuIFdlIGhhdmUgdGhlIGZvbGxvd2luZyB2YXJpYWJsZXM6CgotICAgYGFwcGxuX2lkYDogUEFUU1RBVCBpZCwgdW5pcXVlIGlkZW50aWZpZXIgb2YgcGF0ZW50IGFwcGxpY2F0aW9uCi0gICBgYXBwbG5fZmlsaW5nX3llYXJgOiBGaWxpbmcgeWVhciBvZiBmaXJzdCBwcmlvcml0eQotICAgYGRvY2RiX2ZhbWlseV9zaXplYDogU2l6ZSBvZiB0aGUgKHNpbXBsZSkgcGF0ZW50IGZhbWlseQotICAgYG5iX2NpdGluZ19kb2NkYl9mYW1gOiBOdW1iZXIgb2YgY2l0YXRpb25zIHJlY2lldmVkIGJ5IHRoZSBwYXRlbnQgZmFtaWx5Ci0gICBgbmJfaW52ZW50b3JzYDogTnVtYmVyIG9mIGludmVudG9ycwoKYGBge3J9CnBhdGVudHMgJT4lIHNraW1yOjpza2ltKCkKYGBgCgojIyBQYXRlbnQgQ1BDIENsYXNzCgotICAgVGhlIENvb3BlcmF0aXZlIFBhdGVudCBDbGFzc2lmaWNhdGlvbiAoQ1BDKSBhc3NpZ25zIHBhdGVudHMgdG8gdGVjaG5vbG9neSBjbGFzc2VzCgpgYGB7cn0KcGF0X2NwYyAlPiUgZ2xpbXBzZSgpCmBgYAoKYGBge3J9CnBhdF9jcGMgJT4lIGhlYWQoKQpgYGAKCmBgYHtyfQpwYXRfY3BjICU+JSAKICBjb3VudChjcGNfY2xhc3Nfc3ltYm9sLCBzb3J0ID0gVFJVRSkgJT4lCiAgaGVhZCgxMCkKYGBgCgojIyBQYXRlbnQgQWJzdHJhY3RzCgotICAgVGhlIHBhdGVudCBhYnN0cmFjdCBicmllZmx5IHN1bW1hcml6ZXMgdGhlIGNvbnRlbnQgb2YgdGhlIHBhdGVudC4gVGhpcyBpcyB3aGF0IHdlIHdpbGwgdXNlIGZvciBvdXIgY2xhc2lmaWN0aW9uIGV4ZXJjaXNlIGxhdGVyCgpgYGB7cn0KcGF0X2Fic3RyICU+JSBnbGltcHNlKCkKYGBgCgpgYGB7cn0KcGF0X2Fic3RyICU+JSBzZWxlY3QoLWFwcGxuX2lkKSAlPiUgaGVhZCgzKQpgYGAKCiMgRXhwbG9yYXRvcnkgQW5hbHlzaXMKCldlIGNvdWxkIGhhdmUgc28gbXVjaCBmdW4gaGVyZSBleHBsb3JpbmcgQ2hpbmVzZSBwYXRlbnRzLCBidXQgd2UgaGF2ZSBubyB0aW1lLiBIb3dldmVyLCBJIGRvIGFub3RoZXIgbW9yZSBleHBsb3JhdG9yeSBsZWN0dXJlIG9uIHRoZSBhbWUgZGF0YXNldCwgZmVlbCBmcmVlIHRvIGNoZWNrOgoKLSAgIFtFY29ub21pYyBHZW9ncmFwaHkgJiBQYXRlbnRzXShodHRwczovL2RhbmllbC1oYWluLmdpdGh1Yi5pby9TRENfSU0vbm90ZWJvb2tzL1MzXzFfRWNvbm9taWNfZ2VvZ3JhcGh5Lmh0bWwpCi0gICBbRWNvbm9taWMgQ29tcGxleGl0eSAmIFBhdGVudHNdKGh0dHBzOi8vZGFuaWVsLWhhaW4uZ2l0aHViLmlvL1NEQ19JTS9ub3RlYm9va3MvUzNfMl9FY29ub21pY19jb21wbGV4aXR5Lmh0bWwpCgojIFRoZSBZIFRhZzogSG93IHRvIGlkZW50aWZ5IHJlbmV3YWJsZSBlbmVyZ3kgcGF0ZW50cwoKV2Ugbm93IGFpbSBhdCBpZGVudGlmeWluZyByZW5ld2FibGUgZW5lcmd5IHBhdGVudHMuIFRoaXMgY291bGQgYmUgdGhlIHN0YXJ0aW5nIHBvaW50IGZvciBhbiBpbnRlcmVzdGluZyBhbmFseXNpcyBvbiBhbGwga2luZCBvZiB0aGluZ3MsIGJ1dCB3ZSBoZXJlIHdlbnQgdG8gYXNrIHRoZSBmb2xsb3dpbmcgcXVlc3Rpb246CgoxLiAgQ291bGQgd2UgZGV2ZWxvcCBhIG1vZGVsIHRoYXQgZGV0ZWN0cyByZW5ld2FibGUgZW5lcmd5IHBhdGVudHMgYmFzZWQgb24gdGhlaXIgYWJzdHJhY3Q/CgotICAgV2UgZXhwbG9pdCB0aGUgV0lQT3MgWS10YWcuCi0gICBIZXJlLCB0aGUgV0lQTyBsYWJlbHMgcGF0ZW50cyBpZGVudGlmaWVkIHRvIGJlIHJlbGF0ZWQgdG8gcmVuZXdhYmxlIGVuZXJneSB3aXRoIHRoZSBhZGRpdGlvbmFsIENQQyBjbGFzcyBhc3NpZ25tZW50IGBZMDJgIHdoaWNoIGhlbHBzIHVzIHRvIGVhc2lseSBpZGVudGlmeSB0aGVtLgotICAgQ2hlY2sgW2hlcmVdKGh0dHBzOi8vd3d3LmdvbnN0Lmx1LnNlL2FydGljbGUvcmVwb3J0LW9uLWdyZWVuLXBhdGVudHMpIGZvciBmdXJ0aGVyIGluZm9ybWF0aW9uLgoKTGV0cyBpZGVudGlmeSByZW5ld2FibGUgZW5lcmd5IHBhdGVudHMuCgpgYGB7cn0KeV90YWcgPC0gcGF0X2NwYyAlPiUKICBmaWx0ZXIoY3BjX2NsYXNzX3N5bWJvbCAlPiUgc3RyX3N0YXJ0cygnWTAyJykpICU+JQogIGRpc3RpbmN0KGFwcGxuX2lkKSAlPiUKICBwdWxsKCkKYGBgCgpgYGB7cn0KcGF0ZW50cyAlPD4lCiAgbXV0YXRlKHlfdGFnID0gYXBwbG5faWQgJWluJSB5X3RhZykKYGBgCgpgYGB7cn0KcGF0ZW50cyAlPiUgaGVhZCgpCmBgYAoKYGBge3J9CnJtKHBhdF9jcGMpCmBgYAoKIyBUZXh0IGFuYWx5c2lzIG9mIHBhdGVudCBkYXRhCgojIyBUaGUgUiBOTFAgZWNvc3lzdGVtIChCcmllZiByZW1pbmRlcikKCk1vc3QgbGFuZ3VhZ2UgYW5hbHlzaXMgYXBwcm9hY2hlcyBhcmUgYmFzZWQgb24gdGhlIGFuYWx5c2lzIG9mIHRleHRzIHdvcmQtYnktd29yZC4gSGVyZSwgdGhlaXIgb3JkZXIgbWlnaHQgbWF0dGVyICh3b3JkIHNlcXVlbmNlIG1vZGVscykgb3Igbm90IChiYWctb2Ytd29yZHMgbW9kZWxzKSwgYnV0IHRoZSBzbWFsbGVzdCB1bml0IG9mIGFuYWx5c2lzIGlzIHVzdWFsbHkgdGhlIHdvcmQuIFRoaXMgaXMgdXN1YWxseSBkb25lIGluIGNvbnRleHQgb2YgdGhlIGRvY3VtZW50IHRoZSB3b3JkIGFwcGVhcmVkIGluLiBUaGVyZWZvcmUsIG9uIGZpcnN0IGdsYW5jZSB0aHJlZSB0eXBlcyBkYXRhc3RydWN0dXJlcyBtYWtlIHNlbnNlOgoKMS4gICoqVGlkeToqKiBBcHByb2FjaCwgd2hlcmUgZGF0YSBpcyBzZXJ2ZWQgaW4gYSAyLWNvbHVtbiBkb2N1bWVudC13b3JkIGZvcm1hdCAoZS5nLiwgYHRpZHl0ZXh0YCkKMi4gICoqVG9rZW4gbGlzdHM6KiogQ3JlYXRpb24gb2Ygc3BlY2lhbCBvYmplY3RzLCBzYXZlZCBhcyBkb2N1bWVudC10b2tlbiBsaXN0cyBvciBjb3JwdXMgKGUuZy4sIGB0bWAsIGBxdWFudGVkYWApCjMuICAqKk1hdHJpeDoqKiBMb25nIGFwcHJvYWNoLCB3aGVyZSBkYXRhIGlzIHNlcnZlZCBhcyBkb2N1bWVudC10ZXJtIG1hdHJpeCwgdGVybS1mcmVxdWVuY3kgbWF0cml4LCBldGMuCgpEaWZmZXJlbnQgZm9ybXMgb2YgYW5hbHlzaXMgKGFuZCB0aGUgcGFja2FnZXMgdXNlZCB0aGVyZWZvcmUpIGZhdm9yIGRpZmZlcmVudCBzdHJ1Y3R1cmVzLCBzbyB3ZSBuZWVkIHRvIGJlIGZsdWVudCBpbiB0cmFuc2ZlcmluZyBvcmlnaW5hbCByYXctdGV4dCBpbiB0aGVzZSBmb3JtYXRzLCBhcyB3ZWxsIGFzIHN3aXRjaGluZyBiZXR3ZWVuIHRoZW0uIChmb3IgbW9yZSBpbmZvcywgY2hlY2sgW2hlcmVdKGh0dHBzOi8vd3d3LnRpZHl0ZXh0bWluaW5nLmNvbS9kdG0uaHRtbCkpLgoKIVtdKGh0dHBzOi8vc2RzLWFhdS5naXRodWIuaW8vU0RTLW1hc3Rlci8wMF9tZWRpYS9ubHBfdGlkeXdvcmtmbG93LnBuZykKCmBgYHtyfQpsaWJyYXJ5KHRpZHl0ZXh0KQpgYGAKCiMjIFRpZHkgVGV4dCBGb3JtYXRzCgotICAgVG8gZXhwbG9yZSB0aGUgdGV4dCBhIGJpdCwgbGV0cyBicmluZyBpdCBpbnRvIGEgdGlkeSBmb3JtYXQKCmBgYHtyfQpwYXRfYWJzdHJfdGlkeSA8LSBwYXRfYWJzdHIgJT4lCiAgdW5uZXN0X3Rva2VucyhvdXRwdXQgPSB3b3JkLCAKICAgICAgICAgICAgICAgIGlucHV0ID0gYXBwbG5fYWJzdHJhY3QsIAogICAgICAgICAgICAgICAgdG9rZW4gPSAid29yZHMiLAogICAgICAgICAgICAgICAgdG9fbG93ZXIgPSBUUlVFLAogICAgICAgICAgICAgICAgZHJvcCA9IFRSVUUpCmBgYAoKLSAgIEFuZCBkbyB0aGUgdXN1YWwgcHJlcHJvY2Vzc2luZwoKYGBge3J9CiBwYXRfYWJzdHJfdGlkeSAlPD4lCiAgbXV0YXRlKHdvcmQgPSB3b3JkICU+JSBzdHJfcmVtb3ZlX2FsbCgnW15bOmFsbnVtOl1dJykpICU+JQogIGZpbHRlcihzdHJfbGVuZ3RoKHdvcmQpID4gMiApICU+JQogIGdyb3VwX2J5KHdvcmQpICU+JQogIGZpbHRlcihuKCkgPiAxMDApICU+JQogIHVuZ3JvdXAoKSAlPiUKICBhbnRpX2pvaW4oZ2V0X3N0b3B3b3JkcygpKSAKYGBgCgotICAgTUFrZSBhIGJhZy1vZi13b3JkcyBtb2RlbCB3aXRoIFRGSURGIHdlaWdodHMKCmBgYHtyfQpwYXRfYWJzdHJfdGlkeSAlPD4lCiAgYWRkX2NvdW50KGFwcGxuX2lkLCB3b3JkKSAlPiUKICBiaW5kX3RmX2lkZih0ZXJtID0gd29yZCwKICAgICAgICAgICAgICBkb2N1bWVudCA9IGFwcGxuX2lkLAogICAgICAgICAgICAgIG4gPSBuKQpgYGAKCi0gICBMZXRzIG1lcmdlIHRoZSBjcmVhdGVkIFktdGFnIHRvIHRlaCBhYnN0cmFjdHMKCmBgYHtyfQpwYXRfYWJzdHJfdGlkeSAlPD4lCiAgbGVmdF9qb2luKHBhdGVudHMgJT4lIHNlbGVjdChhcHBsbl9pZCwgeV90YWcpLCBieSA9ICJhcHBsbl9pZCIpICU+JQogIHJlbG9jYXRlKGFwcGxuX2lkLCB5X3RhZykKYGBgCgotICAgQU5kIHNlZSB3aGF0IHdlIGdvdAoKYGBge3J9CnBhdF9hYnN0cl90aWR5ICU+JQogIGhlYWQoKQpgYGAKCmBgYHtyfQpwYXRfYWJzdHJfdGlkeSAlPiUKICBjb3VudCh3b3JkLCB3dCA9IHRmX2lkZiwgc29ydCA9IFRSVUUpICU+JQogIGhlYWQoNTApCmBgYAoKLSAgIERvIHBhdGVudHMgd2l0aCBZIHRhZ3MgdXNlIGRpc3RpbmN0IHZvY2FidWxhcnk/CgpgYGB7cn0KcGF0X3l0YWdfd29yZHMgPC0gcGF0X2Fic3RyX3RpZHkgJT4lCiAgZ3JvdXBfYnkoeV90YWcpICU+JQogIGNvdW50KHdvcmQsIHd0ID0gdGZfaWRmLCBzb3J0ID0gVFJVRSwgbmFtZSA9ICJ0Zl9pZGYiKSAlPiUKICBzbGljZSgxOjIwKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKHdvcmQgPSByZW9yZGVyX3dpdGhpbih3b3JkLCBieSA9IHRmX2lkZiwgd2l0aGluID0geV90YWcpKSAKYGBgCgpgYGB7cn0KcGF0X3l0YWdfd29yZHMgJT4lCiAgZ2dwbG90KGFlcyh4ID0gd29yZCwgeSA9IHRmX2lkZiwgZmlsbCA9IHlfdGFnKSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBsYWJzKHggPSBOVUxMLCB5ID0gInRmLWlkZiIpICsKICBmYWNldF93cmFwKH55X3RhZywgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlIikgKwogIGNvb3JkX2ZsaXAoKSArCiAgc2NhbGVfeF9yZW9yZGVyZWQoKQpgYGAKCiMjIFRvd2FyZHMgYSBwcmVkaWN0aXZlIG1vZGVsPwoKLSAgIFdlIHNlZSB0aGVyZSBzZWVtcyB0byBiZSBxdWl0ZSBhIGRpZmZlcmVuY2UgaW4gdGhlIHdvcmRzIHVzZWQgaW4gWS10YWdnZWQgcmVuZXdhYmxlIGVuZXJneSBwYXRlbnRzLgotICAgVGhlcmVmb3JlIGl0IGluZGVlZCBtaWdodCBiZSBwb3NzaWJsZSB0byBidWlsZCBhIHByZWRpY3RpdmUgbW9kZWwgYmFzZWQgb24gdGV4dC4uLgotICAgSG93IHdvdWxkIHdlIGRvIHRoYXQ/Ci0gICBUaGUgZWFzaWVzdCB3YXkgd291bGQgYmUgdG8gY3JlYXRlIGEgZG9jdW1lbnQtdGVybS1tYXRyaXgsIGJhc2ljYWxseSBjcmVhdGluZyBhIGR1bW15IHZhcmlhYmxlIGZvciBldmVyeSB0ZXJtLgotICAgV2UgY291bGQgZG8gaXQgYSBiaXQgbW9yZSBzb3BoaXN0aWNhdGVkLCBhbmQgaW5zdGVhZCB1c2UgdGhlIFRGSURGIHdlaWdodC4KCmBgYHtyfQojIG9ubHkgYSBzbWFsbCBzYW1wbGUgZm9yIGlsbHVzdHJhdGlvbgpwYXRfZHRtIDwtIHBhdF9hYnN0cl90aWR5ICU+JSAgCiAgc2VsZWN0KGFwcGxuX2lkLCB5X3RhZywgd29yZCwgdGZfaWRmKSAlPiUKICBoZWFkKDEwMDApICU+JQogIGRpc3RpbmN0KGFwcGxuX2lkLCB3b3JkLCAua2VlcF9hbGwgPSBUUlVFKQpgYGAKCmBgYHtyfQojIE5vdyB3ZSBqdXN0IHBpdm90IHdpZGVyCnBhdF9kdG0gJTw+JSAKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gd29yZCwgdmFsdWVzX2Zyb20gPSB0Zl9pZGYsIG5hbWVzX3ByZWZpeCA9ICd3b3JkXycsIHZhbHVlc19maWxsID0gMCkKCmBgYAoKYGBge3J9CnBhdF9kdG0gJT4lIGhlYWQoKQpgYGAKCi0gICBXZSBjb3VsZCBzdHJhaWdodCBwdXQgdGhpcyBtYXRyaXggaW50byBhIG1vZGVsCi0gICBIb3dldmVyLCB0aGVyZSBhcmUgZWFzaWVyIHdheXMgdG8gZG8gc28sIGlmIHRoZSBwdXJwb3NlIGlzIHRvIGNyZWF0ZSBhIHByZWRpY3RpdmUgbW9kZWwuCgpgYGB7cn0Kcm0ocGF0X2R0bSkKYGBgCgojIEJ1aWxkaW5nIGEgcHJlZGljdGl2ZSBtb2RlbAoKYGBge3J9CmRhdGEgPC0gcGF0X2Fic3RyICU+JQogIGlubmVyX2pvaW4ocGF0ZW50cyAlPiUgc2VsZWN0KGFwcGxuX2lkLCB5X3RhZyksIGJ5ID0gImFwcGxuX2lkIikgJT4lCiAgc2VsZWN0KHlfdGFnLCBhcHBsbl9hYnN0cmFjdCkgJT4lCiAgcmVuYW1lKHkgPSB5X3RhZywgdGV4dCA9IGFwcGxuX2Fic3RyYWN0KSAlPiUKICBtdXRhdGUoeSA9IHkgJT4lIGFzX2ZhY3RvcigpKSAlPiUKICBtdXRhdGUodGV4dCA9IHRleHQgJT4lIHN0cl90b19sb3dlcigpICU+JSBzdHJfcmVtb3ZlX2FsbCgnW15bOmFsbnVtOl0gXScpICU+JSBzdHJfc3F1aXNoKCkpICU+JQogIGRyb3BfbmEoKSAKYGBgCgpgYGB7cn0KZGF0YSAlPiUgc2tpbXI6OnNraW0oKQpgYGAKCiMjIFRyYWluaW5nICYgVGVzdCBzcGxpdAoKYGBge3J9CnNldC5zZWVkKDEzMzcpCgpkYXRhX3NwbGl0IDwtIGluaXRpYWxfc3BsaXQoZGF0YSwgcHJvcCA9IDAuNzUsIHN0cmF0YSA9IHkpCgpkYXRhX3RyYWluIDwtIGRhdGFfc3BsaXQgICU+JSAgdHJhaW5pbmcoKQpkYXRhX3Rlc3QgPC0gZGF0YV9zcGxpdCAlPiUgdGVzdGluZygpCmBgYAoKIyMgUHJlcHJvY2Vzc2luZyBwaXBlbGluZQoKLSAgIFdoaWxlIHdlIHVwIHRvIG5vdyBkaWQgYWxsIHRoZSBOTFAgYnkgaGFuZCwgd2UgY2FuIGFsc28ganVzdCB1c2UgdGhlIHRleHRyZWNpcGVzIHBhY2thZ2UgdG8gYXV0b21hdGl6ZSB0aGUgd29ya2Zsb3cKCmBgYHtyfQpsaWJyYXJ5KHRleHRyZWNpcGVzKQpgYGAKCmBgYHtyfQpkYXRhX3JlY2lwZSA8LSBkYXRhX3RyYWluICU+JQogIHJlY2lwZSh5IH4uKSAlPiUKICB0aGVtaXM6OnN0ZXBfZG93bnNhbXBsZSh5KSAlPiUgIyBkdWUgdG8gY2xhc3MgaW1iYWxhbmNlcyU+JQogIHN0ZXBfZmlsdGVyKHRleHQgIT0gIiIpICAlPiUKICAjIEhvdyB0ZXh0cmVjaXBlcyBzdGFydAogIHN0ZXBfdG9rZW5pemUodGV4dCkgJT4lICMgVG9rZW5pemluZwogIHN0ZXBfdG9rZW5maWx0ZXIodGV4dCwgbWluX3RpbWVzID0gMTAwKSAlPiUgICMgZmlsdGVyIHNwYXJzZSB0ZXJtcwogIHN0ZXBfc3RvcHdvcmRzKHRleHQsIGtlZXAgPSBGQUxTRSkgJT4lICMgRmlsdGVyIHN0b3B3b3JkcwogIHN0ZXBfdGZpZGYodGV4dCkgJT4lICMgdGZpZGYgd2VpZ2h0aW5nCiAgIyBzdGVwX2tubmltcHV0ZShhbGxfcHJlZGljdG9ycygpKSAlPiUgIyAga25uIGlucHV0YXRpb24gb2YgbWlzc2luZyB2YWx1ZXMgTm90IG5lY2Vzc2FyeSBoZXJlCiAgcHJlcCgpICMgISEhIE5PVEU6IE9ubHkgcHJlcCgpIHRoZSByZWNpcGUgaWYgeW91IGRvbnQgd2FudCB0byB1c2UgaXQgaW4gYSB3b3JrZmxvdywgb3RoZXJ3aXNlIHRoZXJlIG1pZ2h0IGIgdXNzdWVzCmBgYAoKYGBge3J9CmRhdGFfcmVjaXBlCmBgYAoKLSAgIFNpbmNlIHdlIGRvIG5vdCB1c2UgYW55IHdvcmtmbG93cyBsdGVyLCB3ZSBjYW4gZGlyZWN0bHkgcHJlcGFyZSB0aGUgdGVzdCBhbmQgdHJhaW5pbmcgZGF0YQoKYGBge3J9CmRhdGFfdHJhaW5fcHJlcCA8LSBkYXRhX3JlY2lwZSAlPiUganVpY2UoKQpkYXRhX3Rlc3RfcHJlcCA8LSBkYXRhX3JlY2lwZSAlPiUgYmFrZShkYXRhX3Rlc3QpCmBgYAoKIyMgRGVmaW5pbmcgdGhlIG1vZGVscwoKYGBge3J9CiMgIyBVc2UgdGhpcyBjb2RlIGluIGNhc2UgeW91IHdhbnQgdG8gZG8gcGFyYWxsZWwgcHJvY2Vzc2luZwoKIyBsaWJyYXJ5KGRvUGFyYWxsZWwpCiMgYWxsX2NvcmVzIDwtIHBhcmFsbGVsOjpkZXRlY3RDb3Jlcyhsb2dpY2FsID0gRkFMU0UpCgojIGNsIDwtIG1ha2VQU09DS2NsdXN0ZXIoYWxsX2NvcmVzIC0xKQojcmVnaXN0ZXJEb1BhcmFsbGVsKGNsKQpgYGAKCmBgYHtyfQptb2RlbF9lbiA8LSBsb2dpc3RpY19yZWcobW9kZSA9ICdjbGFzc2lmaWNhdGlvbicsIAogICAgICAgICAgICAgICAgICAgICAgICAgbWl4dHVyZSA9IDAuMjUsIAogICAgICAgICAgICAgICAgICAgICAgICAgcGVuYWx0eSA9IDAuMjUpICU+JQogIHNldF9lbmdpbmUoJ2dsbScsIGZhbWlseSA9IGJpbm9taWFsKCkpIApgYGAKCiMjIERlZmluZSB0aGUgd29ya2Zsb3cKCldlIHdpbGwgc2tpcCB0aGUgd29ya2Zsb3cgc3RlcCB0aGlzIHRpbWUsIHNpbmNlIHdlIGRvIG5vdCBldmFsdWF0ZSBkaWZmZXJlbnQgbW9kZWxzIGFnYWluc3QgZWFjaCBvdGhlcnMuCgojIyBmaXQgdGhlIG1vZGVsCgpgYGB7cn0KZml0X2VuIDwtIG1vZGVsX2VuICU+JSBmaXQoZm9ybXVsYSA9IHkgfi4sIGRhdGEgPSBkYXRhX3RyYWluX3ByZXApCmBgYAoKYGBge3J9CnByZWRfY29sbGVjdGVkIDwtIHRpYmJsZSgKICB0cnV0aCA9IGRhdGFfdHJhaW5fcHJlcCAlPiUgcHVsbCh5KSwKICBwcmVkID0gZml0X2VuICU+JSBwcmVkaWN0KG5ld19kYXRhID0gZGF0YV90cmFpbl9wcmVwKSAlPiUgcHVsbCgucHJlZF9jbGFzcyksCiAgcHJlZF9wcm9iID0gZml0X2VuICU+JSBwcmVkaWN0KG5ld19kYXRhID0gZGF0YV90cmFpbl9wcmVwLCB0eXBlID0gInByb2IiKSAlPiUgcHVsbCgucHJlZF9UUlVFKSwKICApIApgYGAKCmBgYHtyfQpwcmVkX2NvbGxlY3RlZCAlPiUgY29uZl9tYXQodHJ1dGgsIHByZWQpIApwcmVkX2NvbGxlY3RlZCAlPiUgY29uZl9tYXQodHJ1dGgsIHByZWQpICU+JSBhdXRvcGxvdCh0eXBlID0gJ2hlYXRtYXAnKQpgYGAKCmBgYHtyfQpwcmVkX2NvbGxlY3RlZCAlPiUgY29uZl9tYXQodHJ1dGgsIHByZWQpICU+JSBzdW1tYXJ5KCkKYGBgCgojIE1vZGVsIGV4cGxhaW5hYmlsaXR5CgpNYWNoaW5lIGxlYXJuaW5nIChNTCkgbW9kZWxzIGFyZSBvZnRlbiBjb25zaWRlcmVkIGJsYWNrIGJveGVzIGR1ZSB0byB0aGVpciBjb21wbGV4IGlubmVyLXdvcmtpbmdzLiBNb3JlIGFkdmFuY2VkIE1MIG1vZGVscyBzdWNoIGFzIHJhbmRvbSBmb3Jlc3RzLCBncmFkaWVudCBib29zdGluZyBtYWNoaW5lcyAoR0JNKSwgYXJ0aWZpY2lhbCBuZXVyYWwgbmV0d29ya3MgKEFOTiksIGFtb25nIG90aGVycyBhcmUgdHlwaWNhbGx5IG1vcmUgYWNjdXJhdGUgZm9yIHByZWRpY3Rpbmcgbm9ubGluZWFyLCBmYWludCwgb3IgcmFyZSBwaGVub21lbmEuIFVuZm9ydHVuYXRlbHksIG1vcmUgYWNjdXJhY3kgb2Z0ZW4gY29tZXMgYXQgdGhlIGV4cGVuc2Ugb2YgaW50ZXJwcmV0YWJpbGl0eSwgYW5kIGludGVycHJldGFiaWxpdHkgaXMgY3J1Y2lhbCBmb3IgYnVzaW5lc3MgYWRvcHRpb24sIG1vZGVsIGRvY3VtZW50YXRpb24sIHJlZ3VsYXRvcnkgb3ZlcnNpZ2h0LCBhbmQgaHVtYW4gYWNjZXB0YW5jZSBhbmQgdHJ1c3QuIEx1Y2tpbHksIHNldmVyYWwgYWR2YW5jZW1lbnRzIGhhdmUgYmVlbiBtYWRlIHRvIGFpZCBpbiBpbnRlcnByZXRpbmcgTUwgbW9kZWxzLgoKTW9yZW92ZXIsIGl0J3Mgb2Z0ZW4gaW1wb3J0YW50IHRvIHVuZGVyc3RhbmQgdGhlIE1MIG1vZGVsIHRoYXQgeW91J3ZlIHRyYWluZWQgb24gYSBnbG9iYWwgc2NhbGUsIGFuZCBhbHNvIHRvIHpvb20gaW50byBsb2NhbCByZWdpb25zIG9mIHlvdXIgZGF0YSBvciB5b3VyIHByZWRpY3Rpb25zIGFuZCBkZXJpdmUgbG9jYWwgZXhwbGFuYXRpb25zLgoKLSAgICoqR2xvYmFsIGludGVycHJldGF0aW9ucyoqIGhlbHAgdXMgdW5kZXJzdGFuZCB0aGUgaW5wdXRzIGFuZCB0aGVpciBlbnRpcmUgbW9kZWxlZCByZWxhdGlvbnNoaXAgd2l0aCB0aGUgcHJlZGljdGlvbiB0YXJnZXQsIGJ1dCBnbG9iYWwgaW50ZXJwcmV0YXRpb25zIGNhbiBiZSBoaWdobHkgYXBwcm94aW1hdGUgaW4gc29tZSBjYXNlcy4KLSAgICoqTG9jYWwgaW50ZXJwcmV0YXRpb24qKiBoZWxwIHVzIHVuZGVyc3RhbmQgbW9kZWwgcHJlZGljdGlvbnMgZm9yIGEgc2luZ2xlIHJvdyBvZiBkYXRhIG9yIGEgZ3JvdXAgb2Ygc2ltaWxhciByb3dzLgoKIyMgR2xvYmFsIGV4cGxhbmF0aW9uCgpGaW5hbGx5LCBsZXQncyBnZXQgYSBmZWVsaW5nIHdoYXQgdmFyaWFibGVzIHRoZSBtb2RlbHMgbW9zdGx5IGRyYXcgZnJvbS4gVGhlcmUgYXJlIG51bWVyb3VzIHdheXMgZm9yIHN1Y2ggaW5zcGVjdGlvbnMsIGluIHdoaWNoIHdlIHdpbGwganVzdCBzY3JhdGNoIHRoZSBzdXJmYWNlIGhlcmUuIEhlcmUsIEkganVzdCB3YW50IHRvIHByZXNlbnQgdGhlIG1vc3QgaW50dWl0aXZlIGFuZCBjb21tb24gb25lLCAqKlZhcmlhYmxlIEltcG9ydGFuY2UqKi4KCk1vc3QgKGJ1dCBub3QgYWxsKSBtb2RlbCBjbGFzc2VzIG9mZmVyIHNvbWUgcG9zc2liaWxpdHkgdG8gZGVyaXZlIG1lYXN1cmVzIG9mIHZhcmlhYmxlIGltcG9ydGFuY2UuIE5vdGUsIHRoaXMgaXMgY3VycmVudGx5IG5vdCBpbXBsZW1lbnRlZCBmb3IgU1ZNcy4gQWdhaW4sIGluIG1vc3QgTUwgbW9kZWxzIGFuZCBzZXR1cHMsIHRoZXNlIG1lYXN1cmVzIGFyZSBuaWNlIHRvIGdpdmUgYSByb3VnaCBpbnR1aXRpb24sIGJ1dCBDQU5OT1QgYmUgaW50ZXJwcmV0ZWQgYXMgY29uc3RhbnQgbWFyZ2luYWwgZWZmZWN0cywgbGVmdCBhbG9uZSBjYXVzYWwuCgpgYGB7cn0KbGlicmFyeSh2aXApCmBgYAoKYGBge3J9CnZpcChmaXRfZW4pICsgZ2d0aXRsZSgiVmFySW1wOiBFbGFzdGljIE5ldCIpCmBgYAoKIyMgTG9jYWwgaW50ZXJwcmV0YXRpb25zCgpJbiBjb21wbGV4IG5vbnBhcmFtZXRyaWMgbW9kZWxzLCBpdCBpcyBvZnRlbiBtb3JlIGhlbHBmdWwgdG8gZ2V0IGV4cGxhbmF0aW9ucyB3aHkgYSBjZXJ0YWluIGRhdGFwb2ludCBpcyBjbGFzc2lmaWVkIGluIHRoZSB3YXkgaXQgaXMgdGhhbiB0byBsb29rIGF0IHRoZSBvdmVyYWxsIGltcG9ydGFuY2Ugb2YgdmFyaWFibGVzLgoKKipMb2NhbCBJbnRlcnByZXRhYmxlIE1vZGVsLWFnbm9zdGljIEV4cGxhbmF0aW9ucyoqIChMSU1FKSBpcyBhIHZpc3VhbGl6YXRpb24gdGVjaG5pcXVlIHRoYXQgaGVscHMgZXhwbGFpbiBpbmRpdmlkdWFsIHByZWRpY3Rpb25zLiBBcyB0aGUgbmFtZSBpbXBsaWVzLCBpdCBpcyBtb2RlbCBhZ25vc3RpYyBzbyBpdCBjYW4gYmUgYXBwbGllZCB0byBhbnkgc3VwZXJ2aXNlZCByZWdyZXNzaW9uIG9yIGNsYXNzaWZpY2F0aW9uIG1vZGVsLiBUaGVyZSBpcyBhIG5lYXRoIGBSYCBpbXBsZW1lbnRhdGlvbiBpbiB0aGUgW2BsaW1lYF0oaHR0cHM6Ly9saW1lLmRhdGEtaW1hZ2luaXN0LmNvbSkgcGFja2FnZS4gSWYgeW91IHdhbnQgdG8gaW52ZXN0aWdhdGUgZnVydGhlciwgZmVlbCBmcmVlISBPdGhlcndpc2UsIGtlZXAgaW4gbWluZCB0aGlzIGV4aXN0cywgd2Ugd2lsbCBjb21lIGJhY2sgdG8gaXQgYWdhaW4gaW4gbGF0ZXIgbGVjdHVyZXMuCgpCVFc6IFRoZSBvcmlnaW5hbCBwYXBlciBpcyBtaW5kYmxvd2luZywgaWYgeW91IGZpbmQgdGltZSwganVzdCByZWFkIGl0IQoKLSAgIE1hcmNvIFR1bGlvIFJpYmVpcm8sIFNhbWVlciBTaW5naCwgYW5kIENhcmxvcyBHdWVzdHJpbi4gMjAxNi4gIiJoeSBTaG91bGQgSSBUcnVzdCBZb3U/OiBFeHBsYWluaW5nIHRoZSBQcmVkaWN0aW9ucyBvZiBBbnkgQ2xhc3NpZmllci4iIEluIFByb2NlZWRpbmdzIG9mIHRoZSAyMm5kIEFDTSBTSUdLREQgSW50ZXJuYXRpb25hbCBDb25mZXJlbmNlIG9uIEtub3dsZWRnZSBEaXNjb3ZlcnkgYW5kIERhdGEgTWluaW5nIChLREQgMjAxNikuIEFDTSwgTmV3IFlvcmssIE5ZLCBVU0EsIDExMzUtMTE0NC4gRE9JOiA8aHR0cHM6Ly9kb2kub3JnLzEwLjExNDUvMjkzOTY3Mi4yOTM5Nzc4PgoKYGBge3J9CmxpYnJhcnkobGltZSkKYGBgCgpgYGB7cn0KIyBDcmVhdGUgYW4gZXhwbGFpbmVyIG9iamVjdApleHBsYWluZXIgPC0gbGltZShkYXRhX3RyYWluX3ByZXAsIGZpdF9lbikKYGBgCgpgYGB7cn0KIyBFeHBsYWluIG5ldyBvYnNlcnZhdGlvbgpleHBsYW5hdGlvbiA8LSBleHBsYWluKGRhdGFfdHJhaW5fcHJlcCAlPiUgc2xpY2UoMTo2KSwgZXhwbGFpbmVyLCBuX2xhYmVscyA9IDEsIG5fZmVhdHVyZXMgPSA1KQpgYGAKCmBgYHtyfQojIFRoZSBvdXRwdXQgaXMgcHJvdmlkZWQgaW4gYSBjb25zaXN0ZW50IHRhYnVsYXIgZm9ybWF0IGFuZCBpbmNsdWRlcyB0aGUgb3V0cHV0IGZyb20gdGhlIG1vZGVsLgpleHBsYW5hdGlvbiAlPiUgaGVhZCgpCmBgYAoKYGBge3IsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xNX0KZXhwbGFuYXRpb24gJT4lIHBsb3RfZmVhdHVyZXMoKQpgYGAKCiMgQm9udXM6IE93biBwcmVkaWN0aW9ucwoKLSAgIFdlIGNvdWxkIGFsc28ganVzdCBhcHBseSB0aGUgbW9kZWwgdG8gc29tZSBvd24gdGV4dCBhbmQgc2VlIGhvdyBpdCBwcmVkaWN0cy4uLgoKYGBge3J9CnRleHRfb3duID0gdGliYmxlKHRleHQgPSAnVGhpcyBkZXZpY2UgaXMgZ29pbmcgdG8gc2F2ZSB0aGUgd29ybGQgdGhhdCBpbmNsdWRlcyBhIHBvcnRpb24gb2Ygc29sYXIgcGhvdG92b2x0YWljIHBvd2VyIHRvIGNvbnRyb2wgYmlvIGVucmljaGVkIGVuZXJneSBzYXZpdW5nIGFsZ2FlIHRoYXQgcHJvZHVjZXMgYSBoaWdobHkgZW5lcmd5IGJvZHkgc3RhdGUgdGhhdCBjYW4gYmUgdXNlZCBmb3IgbW9iaWxlIGVuZXJneSBwcm9kdWN0aW9uLicpCmBgYAoKLSAgIFdlIHJ1biB0aGlzIHRleHQgdGhyb3VnaCBvdXIgcHJlcHJvY2Vzc2luZyByZWNpcGUgYW5kIGxldCBvdXIgbW9kZWwgcHJlZGljdC4uLgoKYGBge3J9CmZpdF9lbiAlPiUgcHJlZGljdChuZXdfZGF0YSA9IGRhdGFfcmVjaXBlICU+JSBiYWtlKHRleHRfb3duKSkKYGBgCgpgYGB7cn0KdGV4dF9jb3JkaXMgPC0gcmVhZF9jc3YoJ2h0dHBzOi8vZ2l0aHViLmNvbS9TRFMtQUFVL1NEUy1tYXN0ZXIvcmF3L21hc3Rlci9NMi9kYXRhL2NvcmRpcy1oMjAyMHJlcG9ydHMuZ3onKSAKYGBgCgpgYGB7cn0KcHJlZF9jb3JkaXMgPC0gdGliYmxlKHByZWQgPSBmaXRfZW4gJT4lIHByZWRpY3QobmV3X2RhdGEgPSBkYXRhX3JlY2lwZSAlPiUgYmFrZSh0ZXh0X2NvcmRpcyAlPiUgcmVuYW1lKHRleHQgPSBzdW1tYXJ5KSAlPiUgc2VsZWN0KHRleHQpKSkgJT4lIHB1bGwoLnByZWRfY2xhc3MpLAogICAgICAgICAgICAgICAgICAgICAgcHJlZF9wcm9iID0gZml0X2VuICU+JSBwcmVkaWN0KG5ld19kYXRhID0gZGF0YV9yZWNpcGUgJT4lIGJha2UodGV4dF9jb3JkaXMgJT4lIHJlbmFtZSh0ZXh0ID0gc3VtbWFyeSkgJT4lIHNlbGVjdCh0ZXh0KSksIHR5cGUgPSAicHJvYiIpICU+JSBwdWxsKC5wcmVkX1RSVUUpKQoKYGBgCgpgYGB7cn0KdGV4dF9jb3JkaXMgJT4lCiAgYmluZF9jb2xzKHByZWRfY29yZGlzKSAlPiUKICBmaWx0ZXIocHJlZCA9PSAnVFJVRScpICU+JQogIGFycmFuZ2UoZGVzYyhwcmVkX3Byb2IpKSAlPiUKICBzZWxlY3QocHJvamVjdEFjcm9ueW0sIHRpdGxlLCBwcmVkX3Byb2IpICU+JQogIGhlYWQoNTApCmBgYAoKIyBFbmRub3RlcwoKIyMjIFJlZmVyZW5jZXMKCi0gICBbSGFpbiwgRC4sICYgSnVyb3dldHpraSwgUi4gKDIwMjApLiBJbnRyb2R1Y3Rpb24gdG8gUmFyZS1FdmVudCBQcmVkaWN0aXZlIE1vZGVsaW5nIGZvciBJbmZlcmVudGlhbCBTdGF0aXN0aWNpYW5zLS1BIEhhbmRzLU9uIEFwcGxpY2F0aW9uIGluIHRoZSBQcmVkaWN0aW9uIG9mIEJyZWFrdGhyb3VnaCBQYXRlbnRzLiBhclhpdiBwcmVwcmludCBhclhpdjoyMDAzLjEzNDQxLl0oaHR0cHM6Ly9hcnhpdi5vcmcvYWJzLzIwMDMuMTM0NDEpCgojIyMgUGFja2FnZXMgYW5kIEVjb3N5c3RlbQoKLSAgIFtgdGlkeW1vZGVsc2BdKGh0dHBzOi8vd3d3LnRpZHltb2RlbHMub3JnLyk6IFRpZHkgc3RhdGlzdGljYWwgYW5kIHByZWRpY3RpdmUgbW9kZWxpbmcgZWNvc3lzdGVtCi0gICBbYHRpZHl0ZXh0YF0oaHR0cHM6Ly9qdWxpYXNpbGdlLmdpdGh1Yi5pby90aWR5dGV4dC8pOiBUaWR5IHRleHQgYW5hbHlzaXMgaW4gUiBlY29zeXN0ZW0KLSAgIFtgdGV4dHJlY2lwZXNgXShodHRwczovL3RleHRyZWNpcGVzLnRpZHltb2RlbHMub3JnLyk6IFByZXByb2Nlc3Npbmcgd29ya2Zsb3dzIGZvciB0ZXh0IGRhdGEKCiMjIyBGdXJ0aGVyIFJlYWRpbmdzCgotICAgW2BUaWR5IFRleHQgTWluaW5nIGluIFJgXShodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20vKQoKIyMjIFNlc3Npb24gaW5mbwoKYGBge3J9CnNlc3Npb25JbmZvKCkKYGBgCg==