Session 04: Functions and vectorization. Overview: R programming. Some non-tabular data representations: XML and JSON.

Feedback should be send to goran.milovanovic@datakolektiv.com. These notebooks accompany the Intro to Data Science: Non-Technical Background course 2020/21.


What do we want to do today?

Exactly as the Course Overview states: “Week 4. Serious programming in R begins: functions, vectorization. More data formats and structures (simple things in JSON and XML). Overview: data types and structures, control flow, functions and functional programming. Why do we have different and rich data structures in R: the philosophy of functional programming.” This session encompasses three “units”. In the first one (sections 1, 2) we will learn about user-defined functions in R, introduce matrices, and discuss vectorization in R. We will use the second third of our time in this session to provide an overview of (almost) everything learned about R thus far: data types and structures (i.e. classes), control flow, and functions and functional programming - in an attempt to start synthesizing our knowledge of R programming. Finally, and while we are still at the beginning of our journey, we introduce XML and JSON data formats in order to discover the world of non-tabular data representation. These two formats will be of uttermost importance to us when we start contacting REST APIs from our R environments to obtain data.

0. Prerequisits.

Install the following packages:

install.packages('XML')
install.packages('jsonlite')
install.packages('httr')

{XML} is a well-know R package to manipulate, load, and store data in the XML format, while {jsonlite} does the same for JSON.

1. Functions

We have already used a myriad of R functions: lapply(), sapply(), print(), paste0(), unique(), c()… And now we want to become able to write our own, user-defined functions! Think of a function as a piece of reusable code that can be called upon whenever we need it to perform the same action. Also, functions can vary in their application, and that variability is controlled by their arguments, paste0(a, b) will do a different thing when a <- 5; b <- "Alpha" and when `a <- “Beta”; b <- 3". It is really easy to define a function in R:

sumAB <- function(a, b) {
  return(a + b)
}

1.1 Functions: their arguments and behaviour

Upon executing this code, we find a new object in our R environment, a function sumAB:

ls()
[1] "m"     "sumAB"

So, let’s start getting used to the fact that functions in R are first-class citizens and can perform many things that variables and other objects can: for example, a function can be an argument to another function, a function can return a function as its output… Let’s call sumAB() on some values:

result <- sumAB(a = 10, b = 2)
print(result)
[1] 12

The function return() used inside the sumAB() function tells R what is the function output. In an assignment of result <- sumAB(a = 10, b = 2), that output will be assigned to the result. You do not have to use return(), in which case R will return the result of the last evaluation inside the function as its output, so

sumAB <- function(a, b) {
  a + b
}

works just nice. But it is a convention to always use return().

In the previous call to sumAB we have explicitly used the argument names, a and b. However, it would also work if we did this:

result <- sumAB(10, 2)
print(result)
[1] 12

How did R knew that a should be 10 and b set to 2? There are three ways in which R determines the binding of the actual arguments - the one supplied in a function call - to the formal arguments - the ones supplied in the definition of the function. sumAB(10, 2) worked by interpreting 10 and 2 as positional arguments - because no formal argument names a and b were provided - and thus bound a = 10 and b = 2. What else can happen?

First, match by argument names:

sumAB(a = 5, b = 2)
[1] 7

Second, match by position:

sumAB(5, 2)
[1] 7

Then, we can have a partial match by argument name (when the match is unambiguous):

sumAB(2, a = 5)
[1] 7

And finally, if we have long formal argument names, we can abbreviate them (again only when the match is unambiguous):

sumSquares <- function(a_Argument, b_Argument) {
  return(a_Argument^2 + b_Argument^2)
}
sumSquares(a_ = 10, 3)
[1] 109

1.2 Functions, vectorization, and functional programming

Do not be surprised by the following:

squares <- function(x) {
  return(x^2)
}
someVector <- 1:10
squares(someVector)
 [1]   1   4   9  16  25  36  49  64  81 100

“What - my functions are vectorized?” - Well, if you use vectorized base R functions to compute their value - such as "^"() - yes, they are :) - Vector in, ^ - vector out. Such a beautiful programming language. Look:

sumSquares <- function(x, y) {
  return(x^2 + y^2)
}
a <- 1:10
b <- 11:20
sumSquares(a, b)
 [1] 122 148 178 212 250 292 338 388 442 500

Note. Do not ever forget about recycling in R. Never.

Remember Map(), only mentioned in our Session03? Look:

summa <- function(x, y) {
  return(x + y)
}
a <- 1:10
b <- 11:20
unlist(Map(summa, x = a, y = b))
 [1] 12 14 16 18 20 22 24 26 28 30

Or:

summa <- function(x) {
  return(x[1] + x[2])
}
arguments <- list(c(1, 11), 
                  c(2, 12), 
                  c(3, 13), 
                  c(4, 14), 
                  c(5, 15),
                  c(6, 16), 
                  c(7, 17), 
                  c(8, 18), 
                  c(9, 19), 
                  c(10, 20))
unlist(lapply(arguments, summa))
 [1] 12 14 16 18 20 22 24 26 28 30

In other words: of course you can do with your own functions whatever you were able to do with the in-built R functions.

1.3 Scoping

Take a look at the following example:

b <- 10
myFun <- function(a) {
  b <- 5
  return(a + b)
}
result <- myFun(3)

What will be the value of result: 13, or 8? Let’s see:

print(result)
[1] 8

Ok: how did R know that b should be 5 (as defined inside the myFun function) and not 10 (as defined outside the function and before it was introduced)?

Variable Scoping in R simplified. If there is a variable outside the function bearing the same name as the variable inside the function, the variable inside the function will have priority. In other words, define b to be 10 outside the function, and declare b to be 3 inside the function, and we you use b to compute something inside the function it will have the value of 3 i.e. the one assigned to it inside the function.

Function in R are a serious topic indeed, and we are able to provide a basic introduction here only. To learn about nuances and observe R functions in all their beauty, refer to the Functions, Chapter 6 in Hadley Wickham, Advanced R.

1.4 Creating functions on the fly

Imagine creating a function that can be customized for specific needs every now and then? For example, to create a function that returns x^n for a given n:

createPower <- function(exp) {
  f <- function(x) {x^exp}
  return(f)
}

createPower() is a function factory: it returns a function after customizing it for one given argument, exp in this case. Now we can easily create functions that return x^2, x^3, etc:

square <- createPower(2)
square(4)
[1] 16
cube <- createPower(3)
cube(10)
[1] 1000

2. Vectorization, matrices, and sequences

Here is a glimpse at what follows in Session05 on vectorization and vector arithmetic. We have met various beings in R already, but I think that we were not introduced to matrices yet:

square <- matrix(1:4, 
                 nrow = 2)
square
     [,1] [,2]
[1,]    1    3
[2,]    2    4

Good. Now, what happens if:

square <- matrix(1:4, 
                 nrow = 2)
constant <- 10
constant*square
     [,1] [,2]
[1,]   10   30
[2,]   20   40

Vectorization. Look:

square <- matrix(1:4, 
                 ncol = 2)
constant <- 2
square^constant
     [,1] [,2]
[1,]    1    9
[2,]    4   16

Similarly:

constant <- 2
(1:10)^constant
 [1]   1   4   9  16  25  36  49  64  81 100

Do not confuse vectorization with linear algebra:

square1 <- matrix(1:4, 
                 nrow = 2)
square2 <- matrix(rep(10, 4), 
                  nrow  = 2)
square1 * square2
     [,1] [,2]
[1,]   10   30
[2,]   20   40

But that is not how matrices are multiplied:

square1 <- matrix(1:4, 
                 nrow = 2)
square2 <- matrix(rep(10, 4), 
                  nrow  = 2)
square1 %*% square2
     [,1] [,2]
[1,]   40   40
[2,]   60   60

Right? Check it out manually to confirm. So, * is a vectorized operator (a function, in fact) in R, while %*% is a matrix multiplication operator. We will cover more about vector arithmetics in our next Session05.

rep() and seq() both produce vectors in R. rep() takes two arguments, x which is a vector, and times which explains how many times should X be repeated:

myVec <- c("Paris", "London", "New York")
rep(x = myVec, times = 4)
 [1] "Paris"    "London"   "New York" "Paris"    "London"   "New York" "Paris"    "London"  
 [9] "New York" "Paris"    "London"   "New York"

But also:

square <- matrix(1:4,
                 nrow = 2)
rep(square, 3)
 [1] 1 2 3 4 1 2 3 4 1 2 3 4

Note how square was stripped to a numeric vector when rep() was applied to it. Also, times can play a different role if a vector is provided:

myVec <- c("Paris", "London", "New York")
rep(x = myVec, times = 1:3)
[1] "Paris"    "London"   "London"   "New York" "New York" "New York"

seq():

seq(from = 10, to = 100, by = 10)
 [1]  10  20  30  40  50  60  70  80  90 100

Easy.

Matrix subsetting is similar to what can be done with a dataframe:

square <- matrix(1:9,
                 nrow = 3)
square
     [,1] [,2] [,3]
[1,]    1    4    7
[2,]    2    5    8
[3,]    3    6    9
square[1, 1]
[1] 1
square[2, 2]
[1] 5

Subsetting rows:

square[2, ]
[1] 2 5 8

Subsetting columns:

square[ , 2]
[1] 4 5 6
square <- matrix(1:9,
                 nrow = 3)
colnames(square) <- paste0("c_", 1:3)
rownames(square) <- paste0("r_", 1:3)
square
    c_1 c_2 c_3
r_1   1   4   7
r_2   2   5   8
r_3   3   6   9

This is possible; however, square is not a data.frame class, so square$c_1 will not work.

The matrix main diagonal:

diag(square)
[1] 1 5 9

The matrix transpose:

t(square)
    r_1 r_2 r_3
c_1   1   2   3
c_2   4   5   6
c_3   7   8   9

Sums:

colSums(square)
c_1 c_2 c_3 
  6  15  24 
rowSums(square)
r_1 r_2 r_3 
 12  15  18 

Let’s take a look at the following example: we will multiply the square matrix by c(2, 3):

square <- matrix(1:9,
                 nrow = 3)
square
     [,1] [,2] [,3]
[1,]    1    4    7
[2,]    2    5    8
[3,]    3    6    9

Now:

square * c(2, 3)
     [,1] [,2] [,3]
[1,]    2   12   14
[2,]    6   10   24
[3,]    6   18   18

Question for you: how does R order the elements in a matrix? Do you understand recycling in R fully?

3. Overview: R programming

A long, elaborated synthesis of what is learned by now: - data types and structures, - control flow, - functions, functionals (lapply(), for example: vector + function enter -> results a function), and functional programming, - vectorization.

What is missing in this (big) picture are the principles of Object-Oriented Programming (OOP) in R which are not covered by this introductory course. Anyways, what is important to understand is that at this point already you are have a nice toolbox of R principles and techniques to develop serious Data Science projects - while our journey has just started!

4. Not all data are tabular: a glimpse at XML and JSON

4.1 Various data formats: why? Theory

Until now, we have used only data stored as .csv or .xlsx files. As we have seen it was possible to map such files 1:1 onto dataframes in R: columns were defined, with the first rows holding column names by convention… However, in Data Science, many times we face the situation in which we have to obtain some data from a source that does not necessarily deliver “tabular” data structures. Also, many times we will be facing data structures that are essentially not “tabular”: for example, data structures in which some elements contain certain fields which other elements do not - and not because there are missing data, but because sometimes it does not make sense for something to be described by a certain attribute at all. Imagine describing David Bowie, a famous English musician, by a data structure: we can know his date of birth, and the release dates of his albums perhaps, but should be place all that data into one single column? No, because the semantics of such a field would be weird, of course. How would we organize the rows in a dataframe describing David Bowie: the first row describes the person, while other rows describe his works of art? So, we have a column for dateOfBirth, and that column has a value in the first row of the dataframe only, and then we have a column for releaseDate, and that column holds NA in the first row and then a timestamp in all other rows that refer to his albums? Wait, what about his spouse, children, collaborators: assign a row in a dataframe to each one?

No. Of course, a list in R would do, correct?

JSON (abbr. JavaScript Object Notation) and XML (abbr. eXtensible Markup Language) are two data-interchange formats that are often used to store and transport data between different systems online. While it is not possible to dive into any details of JSON and XML representations in this session, it is important to make you aware of the fact that you can obtain any data from any online (or not) source in JSON or XML from within your R environment, transform them into R lists (or dataframes, if the transformation makes sense), and use them in your work.

In the following examples we will use the {httr} package to get in touch with one REST API on the Internet and obtain some data from it. Think of REST APIs as systems that can understand user requests for data formulated in an URL pointing to an online server, process such requests, and send the data back to the user. In the following examples we will be using the Wikibase API to obtain data from Wikidata, the World’s largest open knowledge base that comprises all structured information from Wikipedia and many other sources.

NOTE. Not all code in this section will be immediately transparent to you in this Session. We will work with APIs more in the future sessions and learn how to obtain data from them in a step by step fashion. Our immediate goal is to prepare you for what follows in the weeks ahead us: you need to see JSON and XML and start felling comfortable with transferring data from these two data formats into R data structures. Lists, of course. We will analyze some JSON representations online in this Session to get a glimpse of how it used to describe just any data at hand.

4.2 A JSON response from a REST API

Load {httr}, {XML}, {jsonlite}:

library(httr)
library(XML)
library(jsonlite)

We will contact the Wikibase API to obtain all data stored in Wikidata on David Bowie (who has a Q identifier of Q5383 in this knowlegde base). We will ask the Wikibase API to use JSON to describe its response. Here is how the JSON response will look like: Wikibase API response.

query <- 
  'https://www.wikidata.org/w/api.php?action=wbgetentities&ids=Q5383&languages=en&format=json'
response <- GET(URLencode(query))
response <- rawToChar(response$content)
response <- fromJSON(response)
class(response)
[1] "list"

Now, Wikidata is very complex (and thus very powerful as a descriptive system; after all, it’s goal is to be able to describe just anything that we can imagine, talk, and write about), so what fromJSON() returns is a nasty, nasty, nested list:

instaceOf_DavidBowie <- response$entities$Q5383$claims$P31
instaceOf_DavidBowie$mainsnak$datavalue$value

It is necessary to study the Wikidata DataModel carefully in order to be able to navigate the knowledge structures that it describes:

labelOf_DavidBowie <- response$entities$Q5383$labels$en$value
labelOf_DavidBowie
[1] "David Bowie"

However, once you do learn about Wikidata’s data model… More than 100 million highly structured items and relations among them will become accessible to you. Order emerges from chaos in this case, I assure you. Besides JSON, there is XML (and many more, but we will focus on these two formats).

4.3 An XML response from a REST API

Let’s get back to the Wikibase API and ask for the same data on David Bowie wrapped in an XML response:

query <- 
  'https://www.wikidata.org/w/api.php?action=wbgetentities&ids=Q5383&languages=en&format=xml'
response <- GET(URLencode(query))
response <- rawToChar(response$content)
response <- xmlParse(response)
response <- xmlToList(response)

The English label for David Bowie in Wikidata:

response$entities$entity$labels$label
     language         value 
         "en" "David Bowie" 
class(response$entities$entity$labels$label)
[1] "character"

4.4 All names of David Bowie

Now, David Bowie, in all languages available in Wikidata. First, we get the data.

query <- 
  'https://www.wikidata.org/w/api.php?action=wbgetentities&ids=Q5383&format=json'
response <- GET(URLencode(query))
response <- rawToChar(response$content)
response <- fromJSON(response)

Second: study the structure of the response, and then lapply() across the appropriate set of lists:

labels <- lapply(response$entities$Q5383$labels, function(x) {
  paste0(x$value, "(", x$language, ")")
})
labels <- paste(labels, collapse = ", ")
print(labels)
[1] "David Bowie(fr), David Bowie(de), David Bowie(en-ca), David Bowie(en-gb), ديفيد بوي(ar), Devid Boui(az), Дэвід Боўі(be), Дейвид Боуи(bg), David Bowie(br), David Bowie(bs), David Bowie(ca), David Bowie(co), David Bowie(cs), David Bowie(cy), David Bowie(da), David Bowie(diq), Ντέιβιντ Μπόουι(el), David Bowie(eo), David Bowie(es), David Bowie(et), David Bowie(eu), دیوید بویی(fa), David Bowie(fi), David Bowie(ga), David Bowie(gl), דייוויד בואי(he), डेविड बोवी(hi), David Bowie(hr), David Bowie(hu), David Bowie(id), David Bowie(io), David Bowie(is), David Bowie(it), デヴィッド・ボウイ(ja), David Bowie(jv), დევიდ ბოუი(ka), 데이비드 보위(ko), David Bowie(li), David Bowie(lt), Deivids Bovijs(lv), Дејвид Боуви(mk), David Bowie(nl), David Bowie(nn), David Bowie(oc), David Bowie(pl), David Bowie(pms), David Bowie(pt), David Bowie(pt-br), David Bowie(ro), Дэвид Боуи(ru), David Bowie(scn), David Bowie(sh), David Bowie(sk), David Bowie(sl), David Bowie(sq), Дејвид Боуи(sr), David Bowie(sv), డేవిడ్ బౌవీ(te), เดวิด โบอี(th), David Bowie(tr), Девід Боуї(uk), David Bowie(uz), David Bowie(vi), David Bowie(vls), 大卫·鲍伊(zh), 大衛寶兒(yue), David Bowie(de-ch), David Bowie(qu), David Bowie(la), Дэйвід Боўі(be-tarask), David Bowie(nb), David Bowie(mg), Դեյվիդ Բոուի(hy), David Bowie(ast), David Bowie(sco), David Bowie(lb), Дэвид Боуи(kk), David Bowie(ia), David Bowie(sd), David Bowie(gsw), डेविड बोवी(bho), David Bowie(fo), David Bowie(hsb), ਡੇਵਿਡ ਬੋਵੀ(pa), David Bowie(szl), David Bowie(af), David Bowie(an), David Bowie(bar), David Bowie(de-at), David Bowie(frp), David Bowie(fur), David Bowie(gd), David Bowie(ie), David Bowie(kg), David Bowie(lij), David Bowie(min), David Bowie(ms), David Bowie(nap), David Bowie(nds), David Bowie(nds-nl), David Bowie(nrm), David Bowie(pcd), David Bowie(rm), David Bowie(sc), David Bowie(sr-el), David Bowie(sw), David Bowie(vec), David Bowie(vo), David Bowie(wa), David Bowie(wo), David Bowie(zu), دیوید بویی(azb), ডেভিড বোয়ি(bn), David Bowie(eml), David Bowie(bcl), دەیڤد بویی(ckb), David Bowie(fy), David Bowie(lmo), David Bowie(nah), Devid Bowi(tt), Боуи Дэвид(cv), ഡേവിഡ് ബോയി(ml), David Bowie(nan), David Bowie(war), David Bowie(ilo), Дэвид Боуи(ba), 大衛寶兒(zh-hk), 大卫·鲍伊(zh-cn), 大卫·鲍伊(zh-hans), 大衛·鮑伊(zh-hant), 大衛·鮑伊(zh-mo), 大卫·鲍伊(zh-my), 大卫·鲍伊(zh-sg), 大衛·鮑伊(zh-tw), დევიდ ბოუი(xmf), David Bowie(ext), ديفيد باوى(arz), David Bowie(lfn), டேவிட் போவி(ta), ڈیوڈ بوئی(ur), David Bowie(en), 大卫·鲍伊(wuu), David Bowie(vro), ዴቪድ ቦሊ(am), Տէյվիտ Պոուի(hyw), David Bowie(kl), David Bowie(als), David Bowie(simple), David Bowie(tl)"

Didn’t I tell you how lists and functional programming are important in R?

Important sources, documentation, etc.

R Markdown

R Markdown is what I have used to produce this beautiful Notebook. We will learn more about it near the end of the course, but if you already feel ready to dive deep, here’s a book: R Markdown: The Definitive Guide, Yihui Xie, J. J. Allaire, Garrett Grolemunds.

Exercises

  • E1. Write a function enlistVectors() that take three numeric() vectors as its arguments and returns a named list encompassing them.

  • E2. Write a function mySumSquares that takes one argument, x, and decides (a) if x is numeric, returns a square of sum(x), but (b) if x is a matrix, returns a numeric encompassing the squared sums of all its rows. Carefully: if m <- matrix(1:4, nrow = 2), what is class(m)?

  • E3. Write a function that returns a function! Create a function fixCase(string, how) that returns (1) a function which transforms all characters in a string to upper case if how == 'upper' and (2) transforms all characters in a string to lower case if how == 'lower'. Hint: study base R functions tolower() and toupper().

  • E4. Do you use any online service like Spotify, or a social network like Twitter? Ok, here is what you need to do: find if your service provides an REST API to obtain some data from it, read the API documentation and try to register as an API user. Hint: you will probably need to set a username and obtain your credentials (probably an authorization token, or a password, or something similar). In the following sessions we will be using APIs a lot to fetch data from the Internet.


Goran S. Milovanović

DataKolektiv, 2020/21

contact:


License: GPLv3 This Notebook is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This Notebook is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this Notebook. If not, see http://www.gnu.org/licenses/.


LS0tDQp0aXRsZTogSW50cm8gdG8gRGF0YSBTY2llbmNlIChOb24tVGVjaG5pY2FsIEJhY2tncm91bmQsIFIpIC0gU2Vzc2lvbjA0DQphdXRob3I6DQotIG5hbWU6IEdvcmFuIFMuIE1pbG92YW5vdmnEhywgUGhEDQogIGFmZmlsaWF0aW9uOiBEYXRhS29sZWt0aXYsIENoaWVmIFNjaWVudGlzdCAmIE93bmVyOyBEYXRhIFNjaWVudGlzdCBmb3IgV2lraWRhdGEsIFdNREUNCmFic3RyYWN0OiANCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCiAgICB0aGVtZTogc3BhY2VsYWINCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgdG9jX2RlcHRoOiA1DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDUNCi0tLQ0KDQohW10oLi4vX2ltZy9ES19Mb2dvXzEwMC5wbmcpDQoNCioqKg0KIyBTZXNzaW9uIDA0OiBGdW5jdGlvbnMgYW5kIHZlY3Rvcml6YXRpb24uIE92ZXJ2aWV3OiBSIHByb2dyYW1taW5nLiBTb21lIG5vbi10YWJ1bGFyIGRhdGEgcmVwcmVzZW50YXRpb25zOiBYTUwgYW5kIEpTT04uIA0KKipGZWVkYmFjayoqIHNob3VsZCBiZSBzZW5kIHRvIGBnb3Jhbi5taWxvdmFub3ZpY0BkYXRha29sZWt0aXYuY29tYC4gDQpUaGVzZSBub3RlYm9va3MgYWNjb21wYW55IHRoZSBJbnRybyB0byBEYXRhIFNjaWVuY2U6IE5vbi1UZWNobmljYWwgQmFja2dyb3VuZCBjb3Vyc2UgMjAyMC8yMS4NCg0KKioqDQoNCiMjIyBXaGF0IGRvIHdlIHdhbnQgdG8gZG8gdG9kYXk/DQoNCkV4YWN0bHkgYXMgdGhlIFtDb3Vyc2UgT3ZlcnZpZXddKGh0dHA6Ly9kYXRha29sZWt0aXYub3JnL2FwcF9kaXJlY3QvaW50cm9kc25vbnRlY2gvb3ZlcnZpZXcuaHRtbCN3ZWVrLTQpIHN0YXRlczogKiJXZWVrIDQuIFNlcmlvdXMgcHJvZ3JhbW1pbmcgaW4gUiBiZWdpbnM6IGZ1bmN0aW9ucywgdmVjdG9yaXphdGlvbi4gTW9yZSBkYXRhIGZvcm1hdHMgYW5kIHN0cnVjdHVyZXMgKHNpbXBsZSB0aGluZ3MgaW4gSlNPTiBhbmQgWE1MKS4gT3ZlcnZpZXc6IGRhdGEgdHlwZXMgYW5kIHN0cnVjdHVyZXMsIGNvbnRyb2wgZmxvdywgZnVuY3Rpb25zIGFuZCBmdW5jdGlvbmFsIHByb2dyYW1taW5nLiBXaHkgZG8gd2UgaGF2ZSBkaWZmZXJlbnQgYW5kIHJpY2ggZGF0YSBzdHJ1Y3R1cmVzIGluIFI6IHRoZSBwaGlsb3NvcGh5IG9mIGZ1bmN0aW9uYWwgcHJvZ3JhbW1pbmcuIioNClRoaXMgc2Vzc2lvbiBlbmNvbXBhc3NlcyB0aHJlZSAidW5pdHMiLiBJbiB0aGUgZmlyc3Qgb25lIChzZWN0aW9ucyAxLCAyKSB3ZSB3aWxsIGxlYXJuIGFib3V0IHVzZXItZGVmaW5lZCBmdW5jdGlvbnMgaW4gUiwgaW50cm9kdWNlIG1hdHJpY2VzLCBhbmQgZGlzY3VzcyB2ZWN0b3JpemF0aW9uIGluIFIuIFdlIHdpbGwgdXNlIHRoZSBzZWNvbmQgdGhpcmQgb2Ygb3VyIHRpbWUgaW4gdGhpcyBzZXNzaW9uIHRvIHByb3ZpZGUgYW4gb3ZlcnZpZXcgb2YgKGFsbW9zdCkgZXZlcnl0aGluZyBsZWFybmVkIGFib3V0IFIgdGh1cyBmYXI6IGRhdGEgdHlwZXMgYW5kIHN0cnVjdHVyZXMgKGkuZS4gKmNsYXNzZXMqKSwgY29udHJvbCBmbG93LCBhbmQgZnVuY3Rpb25zIGFuZCBmdW5jdGlvbmFsIHByb2dyYW1taW5nIC0gaW4gYW4gYXR0ZW1wdCB0byBzdGFydCBzeW50aGVzaXppbmcgb3VyIGtub3dsZWRnZSBvZiBSIHByb2dyYW1taW5nLiBGaW5hbGx5LCBhbmQgd2hpbGUgd2UgYXJlIHN0aWxsIGF0IHRoZSBiZWdpbm5pbmcgb2Ygb3VyIGpvdXJuZXksIHdlIGludHJvZHVjZSBbWE1MXShodHRwczovL3d3dy53M3NjaG9vbHMuY29tL3htbC94bWxfd2hhdGlzLmFzcCkgYW5kIFtKU09OXShodHRwczovL3d3dy5qc29uLm9yZy9qc29uLWVuLmh0bWwpIGRhdGEgZm9ybWF0cyBpbiBvcmRlciB0byBkaXNjb3ZlciB0aGUgd29ybGQgb2Ygbm9uLXRhYnVsYXIgZGF0YSByZXByZXNlbnRhdGlvbi4gVGhlc2UgdHdvIGZvcm1hdHMgd2lsbCBiZSBvZiB1dHRlcm1vc3QgaW1wb3J0YW5jZSB0byB1cyB3aGVuIHdlIHN0YXJ0IGNvbnRhY3RpbmcgW1JFU1QgQVBJc10oaHR0cHM6Ly93d3cuc21hc2hpbmdtYWdhemluZS5jb20vMjAxOC8wMS91bmRlcnN0YW5kaW5nLXVzaW5nLXJlc3QtYXBpLykgZnJvbSBvdXIgUiBlbnZpcm9ubWVudHMgdG8gb2J0YWluIGRhdGEuIA0KDQoNCg0KIyMjIDAuIFByZXJlcXVpc2l0cy4NCg0KSW5zdGFsbCB0aGUgZm9sbG93aW5nIHBhY2thZ2VzOg0KDQpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IEYsIG1lc3NhZ2UgPSBGfQ0KaW5zdGFsbC5wYWNrYWdlcygnWE1MJykNCmluc3RhbGwucGFja2FnZXMoJ2pzb25saXRlJykNCmluc3RhbGwucGFja2FnZXMoJ2h0dHInKQ0KYGBgDQoNCmB7WE1MfWAgaXMgYSB3ZWxsLWtub3cgUiBwYWNrYWdlIHRvIG1hbmlwdWxhdGUsIGxvYWQsIGFuZCBzdG9yZSBkYXRhIGluIHRoZSBYTUwgZm9ybWF0LCB3aGlsZSBge2pzb25saXRlfWAgZG9lcyB0aGUgc2FtZSBmb3IgSlNPTi4NCg0KIyMjIDEuIEZ1bmN0aW9ucw0KDQpXZSBoYXZlIGFscmVhZHkgdXNlZCBhIG15cmlhZCBvZiBSIGZ1bmN0aW9uczogYGxhcHBseSgpYCwgYHNhcHBseSgpYCwgYHByaW50KClgLCBgcGFzdGUwKClgLCBgdW5pcXVlKClgLCBgYygpYC4uLiBBbmQgbm93IHdlIHdhbnQgdG8gYmVjb21lIGFibGUgdG8gd3JpdGUgb3VyIG93biwgdXNlci1kZWZpbmVkIGZ1bmN0aW9ucyEgVGhpbmsgb2YgYSBmdW5jdGlvbiBhcyBhIHBpZWNlIG9mIHJldXNhYmxlIGNvZGUgdGhhdCBjYW4gYmUgY2FsbGVkIHVwb24gd2hlbmV2ZXIgd2UgbmVlZCBpdCB0byBwZXJmb3JtIHRoZSBzYW1lIGFjdGlvbi4gQWxzbywgZnVuY3Rpb25zIGNhbiB2YXJ5IGluIHRoZWlyIGFwcGxpY2F0aW9uLCBhbmQgdGhhdCB2YXJpYWJpbGl0eSBpcyBjb250cm9sbGVkIGJ5IHRoZWlyIGFyZ3VtZW50cywgYHBhc3RlMChhLCBiKWAgd2lsbCBkbyBhIGRpZmZlcmVudCB0aGluZyB3aGVuIGBhIDwtIDU7IGIgPC0gIkFscGhhImAgYW5kIHdoZW4gYGEgPC0gIkJldGEiOyBiIDwtIDMiLiBJdCBpcyAqKnJlYWxseSBlYXN5KiogdG8gZGVmaW5lIGEgZnVuY3Rpb24gaW4gUjoNCg0KYGBge3IgZWNobyA9IFR9DQpzdW1BQiA8LSBmdW5jdGlvbihhLCBiKSB7DQogIHJldHVybihhICsgYikNCn0NCmBgYA0KDQojIyMjIDEuMSBGdW5jdGlvbnM6IHRoZWlyIGFyZ3VtZW50cyBhbmQgYmVoYXZpb3VyDQoNClVwb24gZXhlY3V0aW5nIHRoaXMgY29kZSwgd2UgZmluZCBhIG5ldyBvYmplY3QgaW4gb3VyIFIgZW52aXJvbm1lbnQsIGEgZnVuY3Rpb24gYHN1bUFCYDoNCg0KYGBge3IgZWNobyA9IFR9DQpscygpDQpgYGANClNvLCBsZXQncyBzdGFydCBnZXR0aW5nIHVzZWQgdG8gdGhlIGZhY3QgdGhhdCBmdW5jdGlvbnMgaW4gUiBhcmUgZmlyc3QtY2xhc3MgY2l0aXplbnMgYW5kIGNhbiBwZXJmb3JtIG1hbnkgdGhpbmdzIHRoYXQgdmFyaWFibGVzIGFuZCBvdGhlciBvYmplY3RzIGNhbjogZm9yIGV4YW1wbGUsIGEgZnVuY3Rpb24gY2FuIGJlIGFuIGFyZ3VtZW50IHRvIGFub3RoZXIgZnVuY3Rpb24sIGEgZnVuY3Rpb24gY2FuIHJldHVybiBhIGZ1bmN0aW9uIGFzIGl0cyBvdXRwdXQuLi4gTGV0J3MgY2FsbCBgc3VtQUIoKWAgb24gc29tZSB2YWx1ZXM6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KcmVzdWx0IDwtIHN1bUFCKGEgPSAxMCwgYiA9IDIpDQpwcmludChyZXN1bHQpDQpgYGANClRoZSBmdW5jdGlvbiBgcmV0dXJuKClgIHVzZWQgaW5zaWRlIHRoZSBgc3VtQUIoKWAgZnVuY3Rpb24gdGVsbHMgUiB3aGF0IGlzIHRoZSBmdW5jdGlvbiBvdXRwdXQuIEluIGFuIGFzc2lnbm1lbnQgb2YgYHJlc3VsdCA8LSBzdW1BQihhID0gMTAsIGIgPSAyKWAsIHRoYXQgb3V0cHV0IHdpbGwgYmUgYXNzaWduZWQgdG8gdGhlIGByZXN1bHRgLiBZb3UgZG8gbm90IGhhdmUgdG8gdXNlIGByZXR1cm4oKWAsIGluIHdoaWNoIGNhc2UgUiB3aWxsIHJldHVybiB0aGUgcmVzdWx0IG9mIHRoZSBsYXN0IGV2YWx1YXRpb24gaW5zaWRlIHRoZSBmdW5jdGlvbiBhcyBpdHMgb3V0cHV0LCBzbw0KDQpgYGB7ciBlY2hvID0gVH0NCnN1bUFCIDwtIGZ1bmN0aW9uKGEsIGIpIHsNCiAgYSArIGINCn0NCmBgYA0KDQp3b3JrcyBqdXN0IG5pY2UuIEJ1dCBpdCBpcyBhICoqY29udmVudGlvbioqIHRvIGFsd2F5cyB1c2UgYHJldHVybigpYC4NCg0KSW4gdGhlIHByZXZpb3VzIGNhbGwgdG8gYHN1bUFCYCB3ZSBoYXZlIGV4cGxpY2l0bHkgdXNlZCB0aGUgYXJndW1lbnQgbmFtZXMsIGBhYCBhbmQgYGJgLiBIb3dldmVyLCBpdCB3b3VsZCBhbHNvIHdvcmsgaWYgd2UgZGlkIHRoaXM6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KcmVzdWx0IDwtIHN1bUFCKDEwLCAyKQ0KcHJpbnQocmVzdWx0KQ0KYGBgDQpIb3cgZGlkIFIga25ldyB0aGF0IGBhYCBzaG91bGQgYmUgYDEwYCBhbmQgYGJgIHNldCB0byBgMmA/IFRoZXJlIGFyZSB0aHJlZSB3YXlzIGluIHdoaWNoIFIgZGV0ZXJtaW5lcyB0aGUgYmluZGluZyBvZiB0aGUgKmFjdHVhbCBhcmd1bWVudHMqIC0gdGhlIG9uZSBzdXBwbGllZCBpbiBhIGZ1bmN0aW9uIGNhbGwgLSB0byB0aGUgKmZvcm1hbCBhcmd1bWVudHMqIC0gdGhlIG9uZXMgc3VwcGxpZWQgaW4gdGhlIGRlZmluaXRpb24gb2YgdGhlIGZ1bmN0aW9uLiBgc3VtQUIoMTAsIDIpYCB3b3JrZWQgYnkgaW50ZXJwcmV0aW5nIGAxMGAgYW5kIGAyYCBhcyBwb3NpdGlvbmFsIGFyZ3VtZW50cyAtIGJlY2F1c2Ugbm8gZm9ybWFsIGFyZ3VtZW50IG5hbWVzIGBhYCBhbmQgYGJgIHdlcmUgcHJvdmlkZWQgLSBhbmQgdGh1cyBib3VuZCBgYSA9IDEwYCBhbmQgYGIgPSAyYC4gV2hhdCBlbHNlIGNhbiBoYXBwZW4/DQoNCkZpcnN0LCBtYXRjaCBieSBhcmd1bWVudCBuYW1lczoNCg0KYGBge3IgZWNobyA9IFR9DQpzdW1BQihhID0gNSwgYiA9IDIpDQpgYGANClNlY29uZCwgbWF0Y2ggYnkgcG9zaXRpb246DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3VtQUIoNSwgMikNCmBgYA0KVGhlbiwgd2UgY2FuIGhhdmUgYSBwYXJ0aWFsIG1hdGNoIGJ5IGFyZ3VtZW50IG5hbWUgKHdoZW4gdGhlIG1hdGNoIGlzIHVuYW1iaWd1b3VzKToNCg0KYGBge3IgZWNobyA9IFR9DQpzdW1BQigyLCBhID0gNSkNCmBgYA0KQW5kIGZpbmFsbHksIGlmIHdlIGhhdmUgbG9uZyBmb3JtYWwgYXJndW1lbnQgbmFtZXMsIHdlIGNhbiBhYmJyZXZpYXRlIHRoZW0gKGFnYWluIG9ubHkgd2hlbiB0aGUgbWF0Y2ggaXMgdW5hbWJpZ3VvdXMpOg0KDQpgYGB7ciBlY2hvID0gVH0NCnN1bVNxdWFyZXMgPC0gZnVuY3Rpb24oYV9Bcmd1bWVudCwgYl9Bcmd1bWVudCkgew0KICByZXR1cm4oYV9Bcmd1bWVudF4yICsgYl9Bcmd1bWVudF4yKQ0KfQ0Kc3VtU3F1YXJlcyhhXyA9IDEwLCAzKQ0KYGBgDQojIyMjIDEuMiBGdW5jdGlvbnMsIHZlY3Rvcml6YXRpb24sIGFuZCBmdW5jdGlvbmFsIHByb2dyYW1taW5nDQoNCkRvIG5vdCBiZSBzdXJwcmlzZWQgYnkgdGhlIGZvbGxvd2luZzoNCg0KYGBge3IgZWNobyA9IFR9DQpzcXVhcmVzIDwtIGZ1bmN0aW9uKHgpIHsNCiAgcmV0dXJuKHheMikNCn0NCnNvbWVWZWN0b3IgPC0gMToxMA0Kc3F1YXJlcyhzb21lVmVjdG9yKQ0KYGBgDQoiV2hhdCAtIG15IGZ1bmN0aW9ucyBhcmUgdmVjdG9yaXplZD8iIC0gV2VsbCwgaWYgeW91IHVzZSB2ZWN0b3JpemVkIGJhc2UgUiBmdW5jdGlvbnMgdG8gY29tcHV0ZSB0aGVpciB2YWx1ZSAtIHN1Y2ggYXMgYCJeIigpYCAtIHllcywgdGhleSBhcmUgOikgLSBWZWN0b3IgaW4sIGBeYCAtIHZlY3RvciBvdXQuIFN1Y2ggYSBiZWF1dGlmdWwgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2UuIExvb2s6DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3VtU3F1YXJlcyA8LSBmdW5jdGlvbih4LCB5KSB7DQogIHJldHVybih4XjIgKyB5XjIpDQp9DQphIDwtIDE6MTANCmIgPC0gMTE6MjANCnN1bVNxdWFyZXMoYSwgYikNCmBgYA0KKipOb3RlLioqIERvIG5vdCBldmVyIGZvcmdldCBhYm91dCAqKnJlY3ljbGluZyoqIGluIFIuIE5ldmVyLg0KDQpSZW1lbWJlciBgTWFwKClgLCBvbmx5IG1lbnRpb25lZCBpbiBvdXIgYFNlc3Npb24wM2A/IExvb2s6DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3VtbWEgPC0gZnVuY3Rpb24oeCwgeSkgew0KICByZXR1cm4oeCArIHkpDQp9DQphIDwtIDE6MTANCmIgPC0gMTE6MjANCnVubGlzdChNYXAoc3VtbWEsIHggPSBhLCB5ID0gYikpDQpgYGANCk9yOg0KDQpgYGB7ciBlY2hvID0gVH0NCnN1bW1hIDwtIGZ1bmN0aW9uKHgpIHsNCiAgcmV0dXJuKHhbMV0gKyB4WzJdKQ0KfQ0KYXJndW1lbnRzIDwtIGxpc3QoYygxLCAxMSksIA0KICAgICAgICAgICAgICAgICAgYygyLCAxMiksIA0KICAgICAgICAgICAgICAgICAgYygzLCAxMyksIA0KICAgICAgICAgICAgICAgICAgYyg0LCAxNCksIA0KICAgICAgICAgICAgICAgICAgYyg1LCAxNSksDQogICAgICAgICAgICAgICAgICBjKDYsIDE2KSwgDQogICAgICAgICAgICAgICAgICBjKDcsIDE3KSwgDQogICAgICAgICAgICAgICAgICBjKDgsIDE4KSwgDQogICAgICAgICAgICAgICAgICBjKDksIDE5KSwgDQogICAgICAgICAgICAgICAgICBjKDEwLCAyMCkpDQp1bmxpc3QobGFwcGx5KGFyZ3VtZW50cywgc3VtbWEpKQ0KYGBgDQpJbiBvdGhlciB3b3Jkczogb2YgY291cnNlIHlvdSBjYW4gZG8gd2l0aCB5b3VyIG93biBmdW5jdGlvbnMgd2hhdGV2ZXIgeW91IHdlcmUgYWJsZSB0byBkbyB3aXRoIHRoZSBpbi1idWlsdCBSIGZ1bmN0aW9ucy4NCg0KIyMjIyAxLjMgU2NvcGluZw0KDQpUYWtlIGEgbG9vayBhdCB0aGUgZm9sbG93aW5nIGV4YW1wbGU6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KYiA8LSAxMA0KbXlGdW4gPC0gZnVuY3Rpb24oYSkgew0KICBiIDwtIDUNCiAgcmV0dXJuKGEgKyBiKQ0KfQ0KcmVzdWx0IDwtIG15RnVuKDMpDQpgYGANCg0KV2hhdCB3aWxsIGJlIHRoZSB2YWx1ZSBvZiBgcmVzdWx0YDogYDEzYCwgb3IgYDhgPyBMZXQncyBzZWU6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KcHJpbnQocmVzdWx0KQ0KYGBgDQpPazogaG93IGRpZCBSIGtub3cgdGhhdCBgYmAgc2hvdWxkIGJlIGA1YCAoYXMgZGVmaW5lZCBpbnNpZGUgdGhlIGBteUZ1bmAgZnVuY3Rpb24pIGFuZCBub3QgYDEwYCAoYXMgZGVmaW5lZCBvdXRzaWRlIHRoZSBmdW5jdGlvbiBhbmQgYmVmb3JlIGl0IHdhcyBpbnRyb2R1Y2VkKT8NCg0KKipWYXJpYWJsZSBTY29waW5nIGluIFIgc2ltcGxpZmllZC4qKiBJZiB0aGVyZSBpcyBhIHZhcmlhYmxlIG91dHNpZGUgdGhlIGZ1bmN0aW9uIGJlYXJpbmcgdGhlIHNhbWUgbmFtZSBhcyB0aGUgdmFyaWFibGUgaW5zaWRlIHRoZSBmdW5jdGlvbiwgdGhlIHZhcmlhYmxlIGluc2lkZSB0aGUgZnVuY3Rpb24gd2lsbCBoYXZlIHByaW9yaXR5LiBJbiBvdGhlciB3b3JkcywgZGVmaW5lIGBiYCB0byBiZSBgMTBgIG91dHNpZGUgdGhlIGZ1bmN0aW9uLCBhbmQgZGVjbGFyZSBgYmAgdG8gYmUgYDNgIGluc2lkZSB0aGUgZnVuY3Rpb24sIGFuZCB3ZSB5b3UgdXNlIGBiYCB0byBjb21wdXRlIHNvbWV0aGluZyBpbnNpZGUgdGhlIGZ1bmN0aW9uIGl0IHdpbGwgaGF2ZSB0aGUgdmFsdWUgb2YgYDNgIGkuZS4gdGhlIG9uZSBhc3NpZ25lZCB0byBpdCBpbnNpZGUgdGhlIGZ1bmN0aW9uLg0KDQpGdW5jdGlvbiBpbiBSIGFyZSBhIHNlcmlvdXMgdG9waWMgaW5kZWVkLCBhbmQgd2UgYXJlIGFibGUgdG8gcHJvdmlkZSBhIGJhc2ljIGludHJvZHVjdGlvbiBoZXJlIG9ubHkuIFRvIGxlYXJuIGFib3V0IG51YW5jZXMgYW5kIG9ic2VydmUgUiBmdW5jdGlvbnMgaW4gYWxsIHRoZWlyIGJlYXV0eSwgcmVmZXIgdG8gdGhlIFtGdW5jdGlvbnMsIENoYXB0ZXIgNiBpbiBIYWRsZXkgV2lja2hhbSwgQWR2YW5jZWQgUiBdKGh0dHBzOi8vYWR2LXIuaGFkbGV5Lm56L2Z1bmN0aW9ucy5odG1sKS4NCg0KIyMjIyAxLjQgQ3JlYXRpbmcgZnVuY3Rpb25zIG9uIHRoZSBmbHkNCg0KSW1hZ2luZSBjcmVhdGluZyBhIGZ1bmN0aW9uIHRoYXQgY2FuIGJlIGN1c3RvbWl6ZWQgZm9yIHNwZWNpZmljIG5lZWRzIGV2ZXJ5IG5vdyBhbmQgdGhlbj8gRm9yIGV4YW1wbGUsIHRvIGNyZWF0ZSBhIGZ1bmN0aW9uIHRoYXQgcmV0dXJucyBgeF5uYCBmb3IgYSBnaXZlbiBgbmA6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KY3JlYXRlUG93ZXIgPC0gZnVuY3Rpb24oZXhwKSB7DQogIGYgPC0gZnVuY3Rpb24oeCkge3heZXhwfQ0KICByZXR1cm4oZikNCn0NCmBgYA0KDQpgY3JlYXRlUG93ZXIoKWAgaXMgYSAqZnVuY3Rpb24gZmFjdG9yeSo6IGl0IHJldHVybnMgYSBmdW5jdGlvbiBhZnRlciBjdXN0b21pemluZyBpdCBmb3Igb25lIGdpdmVuIGFyZ3VtZW50LCBgZXhwYCBpbiB0aGlzIGNhc2UuIE5vdyB3ZSBjYW4gZWFzaWx5IGNyZWF0ZSBmdW5jdGlvbnMgdGhhdCByZXR1cm4gYHheMmAsIGB4XjNgLCBldGM6DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3F1YXJlIDwtIGNyZWF0ZVBvd2VyKDIpDQpzcXVhcmUoNCkNCmBgYA0KYGBge3IgZWNobyA9IFR9DQpjdWJlIDwtIGNyZWF0ZVBvd2VyKDMpDQpjdWJlKDEwKQ0KYGBgDQoNCiMjIyAyLiBWZWN0b3JpemF0aW9uLCBtYXRyaWNlcywgYW5kIHNlcXVlbmNlcw0KDQpIZXJlIGlzIGEgZ2xpbXBzZSBhdCB3aGF0IGZvbGxvd3MgaW4gU2Vzc2lvbjA1IG9uIHZlY3Rvcml6YXRpb24gYW5kIHZlY3RvciBhcml0aG1ldGljLiBXZSBoYXZlIG1ldCB2YXJpb3VzIGJlaW5ncyBpbiBSIGFscmVhZHksIGJ1dCBJIHRoaW5rIHRoYXQgd2Ugd2VyZSBub3QgaW50cm9kdWNlZCB0byBtYXRyaWNlcyB5ZXQ6DQoNCg0KYGBge3IgZWNobyA9IFR9DQpzcXVhcmUgPC0gbWF0cml4KDE6NCwgDQogICAgICAgICAgICAgICAgIG5yb3cgPSAyKQ0Kc3F1YXJlDQpgYGANCg0KR29vZC4gTm93LCB3aGF0IGhhcHBlbnMgaWY6DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3F1YXJlIDwtIG1hdHJpeCgxOjQsIA0KICAgICAgICAgICAgICAgICBucm93ID0gMikNCmNvbnN0YW50IDwtIDEwDQpjb25zdGFudCpzcXVhcmUNCmBgYA0KVmVjdG9yaXphdGlvbi4gTG9vazoNCg0KYGBge3IgZWNobyA9IFR9DQpzcXVhcmUgPC0gbWF0cml4KDE6NCwgDQogICAgICAgICAgICAgICAgIG5jb2wgPSAyKQ0KY29uc3RhbnQgPC0gMg0Kc3F1YXJlXmNvbnN0YW50DQpgYGANClNpbWlsYXJseToNCg0KYGBge3IgZWNobyA9IFR9DQpjb25zdGFudCA8LSAyDQooMToxMCleY29uc3RhbnQNCmBgYA0KRG8gbm90IGNvbmZ1c2UgdmVjdG9yaXphdGlvbiB3aXRoIGxpbmVhciBhbGdlYnJhOg0KDQpgYGB7ciBlY2hvID0gVH0NCnNxdWFyZTEgPC0gbWF0cml4KDE6NCwgDQogICAgICAgICAgICAgICAgIG5yb3cgPSAyKQ0Kc3F1YXJlMiA8LSBtYXRyaXgocmVwKDEwLCA0KSwgDQogICAgICAgICAgICAgICAgICBucm93ICA9IDIpDQpzcXVhcmUxICogc3F1YXJlMg0KYGBgDQpCdXQgdGhhdCBpcyAqKm5vdCoqIGhvdyBtYXRyaWNlcyBhcmUgbXVsdGlwbGllZDoNCg0KYGBge3IgZWNobyA9IFR9DQpzcXVhcmUxIDwtIG1hdHJpeCgxOjQsIA0KICAgICAgICAgICAgICAgICBucm93ID0gMikNCnNxdWFyZTIgPC0gbWF0cml4KHJlcCgxMCwgNCksIA0KICAgICAgICAgICAgICAgICAgbnJvdyAgPSAyKQ0Kc3F1YXJlMSAlKiUgc3F1YXJlMg0KYGBgDQpSaWdodD8gQ2hlY2sgaXQgb3V0IG1hbnVhbGx5IHRvIGNvbmZpcm0uIFNvLCBgKmAgaXMgYSB2ZWN0b3JpemVkIG9wZXJhdG9yIChhIGZ1bmN0aW9uLCBpbiBmYWN0KSBpbiBSLCB3aGlsZSBgJSolYCBpcyBhIG1hdHJpeCBtdWx0aXBsaWNhdGlvbiBvcGVyYXRvci4gV2Ugd2lsbCBjb3ZlciBtb3JlIGFib3V0IHZlY3RvciBhcml0aG1ldGljcyBpbiBvdXIgbmV4dCBTZXNzaW9uMDUuDQoNCmByZXAoKWAgYW5kIGBzZXEoKWAgYm90aCBwcm9kdWNlIHZlY3RvcnMgaW4gUi4gYHJlcCgpYCB0YWtlcyB0d28gYXJndW1lbnRzLCBgeGAgd2hpY2ggaXMgYSB2ZWN0b3IsIGFuZCBgdGltZXNgIHdoaWNoIGV4cGxhaW5zIGhvdyBtYW55IHRpbWVzIHNob3VsZCBgWGAgYmUgcmVwZWF0ZWQ6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KbXlWZWMgPC0gYygiUGFyaXMiLCAiTG9uZG9uIiwgIk5ldyBZb3JrIikNCnJlcCh4ID0gbXlWZWMsIHRpbWVzID0gNCkNCmBgYA0KQnV0IGFsc286DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3F1YXJlIDwtIG1hdHJpeCgxOjQsDQogICAgICAgICAgICAgICAgIG5yb3cgPSAyKQ0KcmVwKHNxdWFyZSwgMykNCmBgYA0KTm90ZSBob3cgYHNxdWFyZWAgd2FzIHN0cmlwcGVkIHRvIGEgbnVtZXJpYyB2ZWN0b3Igd2hlbiBgcmVwKClgIHdhcyBhcHBsaWVkIHRvIGl0LiBBbHNvLCBgdGltZXNgIGNhbiBwbGF5IGEgZGlmZmVyZW50IHJvbGUgaWYgYSB2ZWN0b3IgaXMgcHJvdmlkZWQ6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KbXlWZWMgPC0gYygiUGFyaXMiLCAiTG9uZG9uIiwgIk5ldyBZb3JrIikNCnJlcCh4ID0gbXlWZWMsIHRpbWVzID0gMTozKQ0KYGBgDQpgc2VxKClgOg0KDQpgYGB7ciBlY2hvID0gVH0NCnNlcShmcm9tID0gMTAsIHRvID0gMTAwLCBieSA9IDEwKQ0KYGBgDQpFYXN5Lg0KDQpNYXRyaXggc3Vic2V0dGluZyBpcyBzaW1pbGFyIHRvIHdoYXQgY2FuIGJlIGRvbmUgd2l0aCBhIGRhdGFmcmFtZToNCg0KYGBge3IgZWNobyA9IFR9DQpzcXVhcmUgPC0gbWF0cml4KDE6OSwNCiAgICAgICAgICAgICAgICAgbnJvdyA9IDMpDQpzcXVhcmUNCmBgYA0KDQpgYGB7ciBlY2hvID0gVH0NCnNxdWFyZVsxLCAxXQ0KYGBgDQpgYGB7ciBlY2hvID0gVH0NCnNxdWFyZVsyLCAyXQ0KYGBgDQpTdWJzZXR0aW5nIHJvd3M6DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3F1YXJlWzIsIF0NCmBgYA0KDQpTdWJzZXR0aW5nIGNvbHVtbnM6DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3F1YXJlWyAsIDJdDQpgYGANCg0KYGBge3IgZWNobyA9IFR9DQpzcXVhcmUgPC0gbWF0cml4KDE6OSwNCiAgICAgICAgICAgICAgICAgbnJvdyA9IDMpDQpjb2xuYW1lcyhzcXVhcmUpIDwtIHBhc3RlMCgiY18iLCAxOjMpDQpyb3duYW1lcyhzcXVhcmUpIDwtIHBhc3RlMCgicl8iLCAxOjMpDQpzcXVhcmUNCmBgYA0KVGhpcyBpcyBwb3NzaWJsZTsgaG93ZXZlciwgYHNxdWFyZWAgaXMgbm90IGEgYGRhdGEuZnJhbWVgIGNsYXNzLCBzbyBgc3F1YXJlJGNfMWAgd2lsbCBub3Qgd29yay4NCg0KVGhlIG1hdHJpeCBtYWluIGRpYWdvbmFsOg0KDQpgYGB7ciBlY2hvID0gVH0NCmRpYWcoc3F1YXJlKQ0KYGBgDQpUaGUgbWF0cml4IHRyYW5zcG9zZToNCg0KYGBge3IgZWNobyA9IFR9DQp0KHNxdWFyZSkNCmBgYA0KU3VtczoNCg0KYGBge3IgZWNobyA9IFR9DQpjb2xTdW1zKHNxdWFyZSkNCmBgYA0KYGBge3IgZWNobyA9IFR9DQpyb3dTdW1zKHNxdWFyZSkNCmBgYA0KTGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIGZvbGxvd2luZyBleGFtcGxlOiB3ZSB3aWxsIG11bHRpcGx5IHRoZSBgc3F1YXJlYCBtYXRyaXggYnkgYGMoMiwgMylgOg0KDQpgYGB7ciBlY2hvID0gVH0NCnNxdWFyZSA8LSBtYXRyaXgoMTo5LA0KICAgICAgICAgICAgICAgICBucm93ID0gMykNCnNxdWFyZQ0KYGBgDQpOb3c6DQoNCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gRn0NCnNxdWFyZSAqIGMoMiwgMykNCmBgYA0KKipRdWVzdGlvbiBmb3IgeW91OioqIGhvdyBkb2VzIFIgb3JkZXIgdGhlIGVsZW1lbnRzIGluIGEgbWF0cml4PyBEbyB5b3UgdW5kZXJzdGFuZCByZWN5Y2xpbmcgaW4gUiBmdWxseT8NCg0KDQojIyMgMy4gT3ZlcnZpZXc6IFIgcHJvZ3JhbW1pbmcNCg0KQSBsb25nLCBlbGFib3JhdGVkIHN5bnRoZXNpcyBvZiB3aGF0IGlzIGxlYXJuZWQgYnkgbm93OiANCi0gZGF0YSB0eXBlcyBhbmQgc3RydWN0dXJlcywgDQotIGNvbnRyb2wgZmxvdywgDQotIGZ1bmN0aW9ucywgZnVuY3Rpb25hbHMgKGBsYXBwbHkoKWAsIGZvciBleGFtcGxlOiB2ZWN0b3IgKyBmdW5jdGlvbiBlbnRlciAtPiByZXN1bHRzIGEgZnVuY3Rpb24pLCBhbmQgZnVuY3Rpb25hbCBwcm9ncmFtbWluZywgDQotIHZlY3Rvcml6YXRpb24uIA0KDQpXaGF0IGlzIG1pc3NpbmcgaW4gdGhpcyAoYmlnKSBwaWN0dXJlIGFyZSB0aGUgcHJpbmNpcGxlcyBvZiBbT2JqZWN0LU9yaWVudGVkIFByb2dyYW1taW5nIChPT1ApIGluIFJdKGh0dHBzOi8vYWR2LXIuaGFkbGV5Lm56L29vLmh0bWwpIHdoaWNoIGFyZSBub3QgY292ZXJlZCBieSB0aGlzIGludHJvZHVjdG9yeSBjb3Vyc2UuIEFueXdheXMsIHdoYXQgaXMgaW1wb3J0YW50IHRvIHVuZGVyc3RhbmQgaXMgdGhhdCBhdCB0aGlzIHBvaW50IGFscmVhZHkgeW91IGFyZSBoYXZlIGEgbmljZSB0b29sYm94IG9mIFIgcHJpbmNpcGxlcyBhbmQgdGVjaG5pcXVlcyB0byBkZXZlbG9wIHNlcmlvdXMgRGF0YSBTY2llbmNlIHByb2plY3RzIC0gd2hpbGUgb3VyIGpvdXJuZXkgaGFzIGp1c3Qgc3RhcnRlZCENCg0KIVtdKC4uL19pbWcvUzA0XzAxX1JQcm9ncmFtbWluZy5qcGVnKQ0KDQojIyMgNC4gTm90IGFsbCBkYXRhIGFyZSB0YWJ1bGFyOiBhIGdsaW1wc2UgYXQgWE1MIGFuZCBKU09ODQoNCiMjIyMgNC4xIFZhcmlvdXMgZGF0YSBmb3JtYXRzOiB3aHk/IFRoZW9yeQ0KDQpVbnRpbCBub3csIHdlIGhhdmUgdXNlZCBvbmx5IGRhdGEgc3RvcmVkIGFzIGAuY3N2YCBvciBgLnhsc3hgIGZpbGVzLiBBcyB3ZSBoYXZlIHNlZW4gaXQgd2FzIHBvc3NpYmxlIHRvIG1hcCBzdWNoIGZpbGVzIDE6MSBvbnRvIGRhdGFmcmFtZXMgaW4gUjogY29sdW1ucyB3ZXJlIGRlZmluZWQsIHdpdGggdGhlIGZpcnN0IHJvd3MgaG9sZGluZyBjb2x1bW4gbmFtZXMgYnkgY29udmVudGlvbi4uLiBIb3dldmVyLCBpbiBEYXRhIFNjaWVuY2UsIG1hbnkgdGltZXMgd2UgZmFjZSB0aGUgc2l0dWF0aW9uIGluIHdoaWNoIHdlIGhhdmUgdG8gb2J0YWluIHNvbWUgZGF0YSBmcm9tIGEgc291cmNlIHRoYXQgZG9lcyBub3QgbmVjZXNzYXJpbHkgZGVsaXZlciAqInRhYnVsYXIiKiBkYXRhIHN0cnVjdHVyZXMuIEFsc28sIG1hbnkgdGltZXMgd2Ugd2lsbCBiZSBmYWNpbmcgZGF0YSBzdHJ1Y3R1cmVzIHRoYXQgYXJlIGVzc2VudGlhbGx5IG5vdCAidGFidWxhciI6IGZvciBleGFtcGxlLCBkYXRhIHN0cnVjdHVyZXMgaW4gd2hpY2ggc29tZSBlbGVtZW50cyBjb250YWluIGNlcnRhaW4gZmllbGRzIHdoaWNoIG90aGVyIGVsZW1lbnRzIGRvIG5vdCAtIGFuZCBub3QgYmVjYXVzZSB0aGVyZSBhcmUgbWlzc2luZyBkYXRhLCBidXQgYmVjYXVzZSBzb21ldGltZXMgaXQgZG9lcyBub3QgbWFrZSBzZW5zZSBmb3Igc29tZXRoaW5nIHRvIGJlIGRlc2NyaWJlZCBieSBhIGNlcnRhaW4gYXR0cmlidXRlIGF0IGFsbC4gSW1hZ2luZSBkZXNjcmliaW5nIFtEYXZpZCBCb3dpZV0oaHR0cHM6Ly93d3cud2lraWRhdGEub3JnL3dpa2kvUTUzODMpLCBhIGZhbW91cyBFbmdsaXNoIG11c2ljaWFuLCBieSBhIGRhdGEgc3RydWN0dXJlOiB3ZSBjYW4ga25vdyBoaXMgZGF0ZSBvZiBiaXJ0aCwgYW5kIHRoZSByZWxlYXNlIGRhdGVzIG9mIGhpcyBhbGJ1bXMgcGVyaGFwcywgYnV0IHNob3VsZCBiZSBwbGFjZSBhbGwgdGhhdCBkYXRhIGludG8gb25lIHNpbmdsZSBjb2x1bW4/IE5vLCBiZWNhdXNlIHRoZSBzZW1hbnRpY3Mgb2Ygc3VjaCBhIGZpZWxkIHdvdWxkIGJlIHdlaXJkLCBvZiBjb3Vyc2UuIEhvdyB3b3VsZCB3ZSBvcmdhbml6ZSB0aGUgcm93cyBpbiBhIGRhdGFmcmFtZSBkZXNjcmliaW5nIERhdmlkIEJvd2llOiB0aGUgZmlyc3Qgcm93IGRlc2NyaWJlcyB0aGUgcGVyc29uLCB3aGlsZSBvdGhlciByb3dzIGRlc2NyaWJlIGhpcyB3b3JrcyBvZiBhcnQ/IFNvLCB3ZSBoYXZlIGEgY29sdW1uIGZvciBgZGF0ZU9mQmlydGhgLCBhbmQgdGhhdCBjb2x1bW4gaGFzIGEgdmFsdWUgaW4gdGhlIGZpcnN0IHJvdyBvZiB0aGUgZGF0YWZyYW1lIG9ubHksIGFuZCB0aGVuIHdlIGhhdmUgYSBjb2x1bW4gZm9yIGByZWxlYXNlRGF0ZWAsIGFuZCB0aGF0IGNvbHVtbiBob2xkcyBgTkFgIGluIHRoZSBmaXJzdCByb3cgYW5kIHRoZW4gYSB0aW1lc3RhbXAgaW4gYWxsIG90aGVyIHJvd3MgdGhhdCByZWZlciB0byBoaXMgYWxidW1zPyBXYWl0LCB3aGF0IGFib3V0IGhpcyBzcG91c2UsIGNoaWxkcmVuLCBjb2xsYWJvcmF0b3JzOiBhc3NpZ24gYSByb3cgaW4gYSBkYXRhZnJhbWUgdG8gZWFjaCBvbmU/DQoNCg0KTm8uIE9mIGNvdXJzZSwgYSAqKmxpc3QqKiBpbiBSIHdvdWxkIGRvLCBjb3JyZWN0Pw0KDQoqKkpTT04qKiAoYWJici4gKkphdmFTY3JpcHQgT2JqZWN0IE5vdGF0aW9uKikgYW5kICoqWE1MKiogKGFiYnIuICAqZVh0ZW5zaWJsZSBNYXJrdXAgTGFuZ3VhZ2UqKSBhcmUgdHdvIGRhdGEtaW50ZXJjaGFuZ2UgZm9ybWF0cyB0aGF0IGFyZSBvZnRlbiB1c2VkIHRvIHN0b3JlIGFuZCB0cmFuc3BvcnQgZGF0YSBiZXR3ZWVuIGRpZmZlcmVudCBzeXN0ZW1zIG9ubGluZS4gV2hpbGUgaXQgaXMgbm90IHBvc3NpYmxlIHRvIGRpdmUgaW50byBhbnkgZGV0YWlscyBvZiBKU09OIGFuZCBYTUwgcmVwcmVzZW50YXRpb25zIGluIHRoaXMgc2Vzc2lvbiwgaXQgaXMgaW1wb3J0YW50IHRvIG1ha2UgeW91IGF3YXJlIG9mIHRoZSBmYWN0IHRoYXQgeW91IGNhbiBvYnRhaW4gKmFueSogZGF0YSBmcm9tIGFueSBvbmxpbmUgKG9yIG5vdCkgc291cmNlIGluIEpTT04gb3IgWE1MIGZyb20gd2l0aGluIHlvdXIgUiBlbnZpcm9ubWVudCwgdHJhbnNmb3JtIHRoZW0gaW50byBSIGxpc3RzIChvciBkYXRhZnJhbWVzLCBpZiB0aGUgdHJhbnNmb3JtYXRpb24gbWFrZXMgc2Vuc2UpLCBhbmQgdXNlIHRoZW0gaW4geW91ciB3b3JrLg0KDQpJbiB0aGUgZm9sbG93aW5nIGV4YW1wbGVzIHdlIHdpbGwgdXNlIHRoZSBbYHtodHRyfWBdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9odHRyL3ZpZ25ldHRlcy9xdWlja3N0YXJ0Lmh0bWwpIHBhY2thZ2UgdG8gZ2V0IGluIHRvdWNoIHdpdGggb25lIFtSRVNUIEFQSV0oaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tL2ludHJvZHVjdGlvbi10by1yZXN0LWFwaXMtOTBiNWQ5Njc2MDA0KSBvbiB0aGUgSW50ZXJuZXQgYW5kIG9idGFpbiBzb21lIGRhdGEgZnJvbSBpdC4gVGhpbmsgb2YgUkVTVCBBUElzIGFzIHN5c3RlbXMgdGhhdCBjYW4gdW5kZXJzdGFuZCB1c2VyIHJlcXVlc3RzIGZvciBkYXRhIGZvcm11bGF0ZWQgaW4gYW4gVVJMIHBvaW50aW5nIHRvIGFuIG9ubGluZSBzZXJ2ZXIsIHByb2Nlc3Mgc3VjaCByZXF1ZXN0cywgYW5kIHNlbmQgdGhlIGRhdGEgYmFjayB0byB0aGUgdXNlci4gSW4gdGhlIGZvbGxvd2luZyBleGFtcGxlcyB3ZSB3aWxsIGJlIHVzaW5nIHRoZSBbV2lraWJhc2UgQVBJXShodHRwczovL3d3dy5tZWRpYXdpa2kub3JnL3dpa2kvV2lraWJhc2UvQVBJKSB0byBvYnRhaW4gZGF0YSBmcm9tIFtXaWtpZGF0YV0oaHR0cHM6Ly93d3cud2lraWRhdGEub3JnL3dpa2kvV2lraWRhdGE6TWFpbl9QYWdlKSwgdGhlIFdvcmxkJ3MgbGFyZ2VzdCBvcGVuIGtub3dsZWRnZSBiYXNlIHRoYXQgY29tcHJpc2VzIGFsbCBzdHJ1Y3R1cmVkIGluZm9ybWF0aW9uIGZyb20gV2lraXBlZGlhIGFuZCBtYW55IG90aGVyIHNvdXJjZXMuDQoNCioqTk9URS4qKiBOb3QgYWxsIGNvZGUgaW4gdGhpcyBzZWN0aW9uIHdpbGwgYmUgaW1tZWRpYXRlbHkgdHJhbnNwYXJlbnQgdG8geW91IGluIHRoaXMgU2Vzc2lvbi4gV2Ugd2lsbCB3b3JrIHdpdGggQVBJcyBtb3JlIGluIHRoZSBmdXR1cmUgc2Vzc2lvbnMgYW5kIGxlYXJuIGhvdyB0byBvYnRhaW4gZGF0YSBmcm9tIHRoZW0gaW4gYSBzdGVwIGJ5IHN0ZXAgZmFzaGlvbi4gT3VyIGltbWVkaWF0ZSBnb2FsIGlzIHRvIHByZXBhcmUgeW91IGZvciB3aGF0IGZvbGxvd3MgaW4gdGhlIHdlZWtzIGFoZWFkIHVzOiB5b3UgbmVlZCB0byBzZWUgSlNPTiBhbmQgWE1MIGFuZCBzdGFydCBmZWxsaW5nIGNvbWZvcnRhYmxlIHdpdGggdHJhbnNmZXJyaW5nIGRhdGEgZnJvbSB0aGVzZSB0d28gZGF0YSBmb3JtYXRzIGludG8gUiBkYXRhIHN0cnVjdHVyZXMuIExpc3RzLCBvZiBjb3Vyc2UuIFdlIHdpbGwgYW5hbHl6ZSBzb21lIEpTT04gcmVwcmVzZW50YXRpb25zIG9ubGluZSBpbiB0aGlzIFNlc3Npb24gdG8gZ2V0IGEgZ2xpbXBzZSBvZiBob3cgaXQgdXNlZCB0byBkZXNjcmliZSBqdXN0IGFueSBkYXRhIGF0IGhhbmQuDQoNCiMjIyMgNC4yIEEgSlNPTiByZXNwb25zZSBmcm9tIGEgUkVTVCBBUEkNCg0KTG9hZCBge2h0dHJ9YCwgYHtYTUx9YCwgYHtqc29ubGl0ZX1gOg0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KbGlicmFyeShodHRyKQ0KbGlicmFyeShYTUwpDQpsaWJyYXJ5KGpzb25saXRlKQ0KYGBgDQoNCldlIHdpbGwgY29udGFjdCB0aGUgV2lraWJhc2UgQVBJIHRvIG9idGFpbiBhbGwgZGF0YSBzdG9yZWQgaW4gV2lraWRhdGEgb24gRGF2aWQgQm93aWUgKHdobyBoYXMgYSAqKlEgaWRlbnRpZmllcioqIG9mIFE1MzgzIGluIHRoaXMga25vd2xlZ2RlIGJhc2UpLiBXZSB3aWxsIGFzayB0aGUgV2lraWJhc2UgQVBJIHRvIHVzZSBKU09OIHRvIGRlc2NyaWJlIGl0cyByZXNwb25zZS4gSGVyZSBpcyBob3cgdGhlIEpTT04gcmVzcG9uc2Ugd2lsbCBsb29rIGxpa2U6IFtXaWtpYmFzZSBBUEkgcmVzcG9uc2VdKGh0dHBzOi8vd3d3Lndpa2lkYXRhLm9yZy93L2FwaS5waHA/YWN0aW9uPXdiZ2V0ZW50aXRpZXMmaWRzPVE1MzgzJmxhbmd1YWdlcz1lbikuDQoNCmBgYHtyIGVjaG8gPSBUfQ0KcXVlcnkgPC0gDQogICdodHRwczovL3d3dy53aWtpZGF0YS5vcmcvdy9hcGkucGhwP2FjdGlvbj13YmdldGVudGl0aWVzJmlkcz1RNTM4MyZsYW5ndWFnZXM9ZW4mZm9ybWF0PWpzb24nDQpyZXNwb25zZSA8LSBHRVQoVVJMZW5jb2RlKHF1ZXJ5KSkNCnJlc3BvbnNlIDwtIHJhd1RvQ2hhcihyZXNwb25zZSRjb250ZW50KQ0KcmVzcG9uc2UgPC0gZnJvbUpTT04ocmVzcG9uc2UpDQpjbGFzcyhyZXNwb25zZSkNCmBgYA0KTm93LCBXaWtpZGF0YSBpcyAqKnZlcnkgY29tcGxleCoqIChhbmQgdGh1cyB2ZXJ5IHBvd2VyZnVsIGFzIGEgZGVzY3JpcHRpdmUgc3lzdGVtOyBhZnRlciBhbGwsIGl0J3MgZ29hbCBpcyB0byBiZSBhYmxlIHRvIGRlc2NyaWJlICpqdXN0IGFueXRoaW5nKiB0aGF0IHdlIGNhbiBpbWFnaW5lLCB0YWxrLCBhbmQgd3JpdGUgYWJvdXQpLCBzbyB3aGF0IGBmcm9tSlNPTigpYCByZXR1cm5zIGlzIGEgbmFzdHksIG5hc3R5LCBuZXN0ZWQgbGlzdDoNCg0KYGBge3IgZWNobyA9IFR9DQppbnN0YWNlT2ZfRGF2aWRCb3dpZSA8LSByZXNwb25zZSRlbnRpdGllcyRRNTM4MyRjbGFpbXMkUDMxDQppbnN0YWNlT2ZfRGF2aWRCb3dpZSRtYWluc25hayRkYXRhdmFsdWUkdmFsdWUNCmBgYA0KDQpJdCBpcyBuZWNlc3NhcnkgdG8gc3R1ZHkgdGhlIFtXaWtpZGF0YSBEYXRhTW9kZWxdKGh0dHBzOi8vd3d3Lm1lZGlhd2lraS5vcmcvd2lraS9XaWtpYmFzZS9EYXRhTW9kZWwpIGNhcmVmdWxseSBpbiBvcmRlciB0byBiZSBhYmxlIHRvIG5hdmlnYXRlIHRoZSBrbm93bGVkZ2Ugc3RydWN0dXJlcyB0aGF0IGl0IGRlc2NyaWJlczoNCg0KYGBge3IgZWNobyA9IFR9DQpsYWJlbE9mX0RhdmlkQm93aWUgPC0gcmVzcG9uc2UkZW50aXRpZXMkUTUzODMkbGFiZWxzJGVuJHZhbHVlDQpsYWJlbE9mX0RhdmlkQm93aWUNCmBgYA0KSG93ZXZlciwgb25jZSB5b3UgZG8gbGVhcm4gYWJvdXQgV2lraWRhdGEncyBkYXRhIG1vZGVsLi4uIE1vcmUgdGhhbiAxMDAgbWlsbGlvbiBoaWdobHkgc3RydWN0dXJlZCBpdGVtcyBhbmQgcmVsYXRpb25zIGFtb25nIHRoZW0gd2lsbCBiZWNvbWUgYWNjZXNzaWJsZSB0byB5b3UuIE9yZGVyIGVtZXJnZXMgZnJvbSBjaGFvcyBpbiB0aGlzIGNhc2UsIEkgYXNzdXJlIHlvdS4gQmVzaWRlcyBKU09OLCB0aGVyZSBpcyBYTUwgKGFuZCBtYW55IG1vcmUsIGJ1dCB3ZSB3aWxsIGZvY3VzIG9uIHRoZXNlIHR3byBmb3JtYXRzKS4gDQoNCiMjIyMgNC4zIEFuIFhNTCByZXNwb25zZSBmcm9tIGEgUkVTVCBBUEkNCg0KTGV0J3MgZ2V0IGJhY2sgdG8gdGhlIFdpa2liYXNlIEFQSSBhbmQgYXNrIGZvciB0aGUgc2FtZSBkYXRhIG9uIERhdmlkIEJvd2llIHdyYXBwZWQgaW4gYW4gW1hNTCByZXNwb25zZV0oaHR0cHM6Ly93d3cud2lraWRhdGEub3JnL3cvYXBpLnBocD9hY3Rpb249d2JnZXRlbnRpdGllcyZpZHM9UTUzODMmbGFuZ3VhZ2VzPWVuJmZvcm1hdD14bWwpOg0KDQpgYGB7ciBlY2hvID0gVH0NCnF1ZXJ5IDwtIA0KICAnaHR0cHM6Ly93d3cud2lraWRhdGEub3JnL3cvYXBpLnBocD9hY3Rpb249d2JnZXRlbnRpdGllcyZpZHM9UTUzODMmbGFuZ3VhZ2VzPWVuJmZvcm1hdD14bWwnDQpyZXNwb25zZSA8LSBHRVQoVVJMZW5jb2RlKHF1ZXJ5KSkNCnJlc3BvbnNlIDwtIHJhd1RvQ2hhcihyZXNwb25zZSRjb250ZW50KQ0KcmVzcG9uc2UgPC0geG1sUGFyc2UocmVzcG9uc2UpDQpyZXNwb25zZSA8LSB4bWxUb0xpc3QocmVzcG9uc2UpDQpgYGANCg0KVGhlIEVuZ2xpc2ggbGFiZWwgZm9yIERhdmlkIEJvd2llIGluIFdpa2lkYXRhOg0KDQpgYGB7ciBlY2hvID0gVH0NCnJlc3BvbnNlJGVudGl0aWVzJGVudGl0eSRsYWJlbHMkbGFiZWwNCmBgYA0KDQpgYGB7ciBlY2hvID0gVH0NCmNsYXNzKHJlc3BvbnNlJGVudGl0aWVzJGVudGl0eSRsYWJlbHMkbGFiZWwpDQpgYGANCg0KIyMjIyA0LjQgQWxsIG5hbWVzIG9mIERhdmlkIEJvd2llDQoNCk5vdywgRGF2aWQgQm93aWUsIGluIGFsbCBsYW5ndWFnZXMgYXZhaWxhYmxlIGluIFdpa2lkYXRhLiBGaXJzdCwgd2UgZ2V0IHRoZSBkYXRhLg0KDQpgYGB7ciBlY2hvID0gVH0NCnF1ZXJ5IDwtIA0KICAnaHR0cHM6Ly93d3cud2lraWRhdGEub3JnL3cvYXBpLnBocD9hY3Rpb249d2JnZXRlbnRpdGllcyZpZHM9UTUzODMmZm9ybWF0PWpzb24nDQpyZXNwb25zZSA8LSBHRVQoVVJMZW5jb2RlKHF1ZXJ5KSkNCnJlc3BvbnNlIDwtIHJhd1RvQ2hhcihyZXNwb25zZSRjb250ZW50KQ0KcmVzcG9uc2UgPC0gZnJvbUpTT04ocmVzcG9uc2UpDQpgYGANCg0KU2Vjb25kOiBzdHVkeSB0aGUgc3RydWN0dXJlIG9mIHRoZSByZXNwb25zZSwgYW5kIHRoZW4gYGxhcHBseSgpYCBhY3Jvc3MgdGhlIGFwcHJvcHJpYXRlIHNldCBvZiBsaXN0czogDQoNCmBgYHtyIGVjaG8gPSBUfQ0KbGFiZWxzIDwtIGxhcHBseShyZXNwb25zZSRlbnRpdGllcyRRNTM4MyRsYWJlbHMsIGZ1bmN0aW9uKHgpIHsNCiAgcGFzdGUwKHgkdmFsdWUsICIoIiwgeCRsYW5ndWFnZSwgIikiKQ0KfSkNCmxhYmVscyA8LSBwYXN0ZShsYWJlbHMsIGNvbGxhcHNlID0gIiwgIikNCnByaW50KGxhYmVscykNCmBgYA0KDQpEaWRuJ3QgSSB0ZWxsIHlvdSBob3cgbGlzdHMgYW5kIGZ1bmN0aW9uYWwgcHJvZ3JhbW1pbmcgYXJlIGltcG9ydGFudCBpbiBSPw0KDQojIyMgRnVydGhlciBSZWFkaW5ncw0KDQotIFtDaGFwdGVyIDE5LCBGdW5jdGlvbnMsIGZyb20gZnJvbSBSIGZvciBEYXRhIFNjaWVuY2UsIEhhZGxleSBXaWNraGFtICYgR2FycmV0dCBHcm9sZW11bmRdKGh0dHBzOi8vcjRkcy5oYWQuY28ubnovZnVuY3Rpb25zLmh0bWwpDQotIFtNYXRyaXgsIGZyb20gUi10dXRvciBdKGh0dHA6Ly93d3cuci10dXRvci5jb20vci1pbnRyb2R1Y3Rpb24vbWF0cml4KQ0KLSBbQ2hhcHRlciAxMCwgTWF0cmljZXMsIGZyb20gQSBTaG9ydCBJbnRyb2R1Y3Rpb24gdG8gUiBQcm9ncmFtbWluZywgTWFuaXNoIFBhdHdhbF0oaHR0cHM6Ly9ib29rZG93bi5vcmcvbWFuaXNocGF0d2FsL2Jvb2tkb3duLWRlbW8vbWF0cmljZXMtaW4tci5odG1sKQ0KLSBbQW4gSW50cm9kdWN0aW9uIHRvIEpTT04sIGZyb20gRGlnaXRhbCBPY2Vhbl0oaHR0cHM6Ly93d3cuZGlnaXRhbG9jZWFuLmNvbS9jb21tdW5pdHkvdHV0b3JpYWxzL2FuLWludHJvZHVjdGlvbi10by1qc29uKQ0KLSBbSW50cm9kdWN0aW9uIHRvIFhNTCwgZnJvbSBJQk0sIGJ5IERvdWcgVGlkd2VsbF0oaHR0cHM6Ly93d3cuaWJtLmNvbS9kZXZlbG9wZXJ3b3Jrcy94bWwvdHV0b3JpYWxzL3htbGludHJvL3htbGludHJvLmh0bWwpDQoNCiMjIyBIaWdobHkgUmVjb21tZW5kZWQgVG8gRG8gKGFuZCBiaXQgYWR2YW5jZWQgYXQgdGhpcyBwb2ludC4uLikNCg0KLSBbQ2hhcHRlciA3LCBGdW5jdGlvbnMgYXJlIG9iamVjdHMsIGZyb20gTm9ybWFuIE1hdGxvZmbigJlzIFRoZSBBcnQgb2YgUiBQcm9ncmFtbWluZ10oaHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS9zZWFyY2g/Y2xpZW50PWZpcmVmb3gtYi1kJmNoYW5uZWw9dHJvdzImc3hzcmY9QUxlS2swM1RfcUxDTXpSSUNZV2o1VUhxRkJIbnZmVjZVdyUzQTE2MDcyMjA3OTU1NTMmZWk9T3o3TVg4YWVJWUs2a3dXYzVKYmdBdyZxPU5vcm1hbitNYXRsb2ZmK1RoZStBcnQrb2YrUitQcm9ncmFtbWluZytwZGYmb3E9Tm9ybWFuK01hdGxvZmYrVGhlK0FydCtvZitSK1Byb2dyYW1taW5nK3BkZiZnc19sY3A9Q2dad2Mza3RZV0lRQXpJRkNBQVF5UU02QndnakVNa0RFQ2M2QWdndVVKazlXT2RBWUtSQmFBQndBSGdBZ0FHSEFZZ0I5UU9TQVFNd0xqU1lBUUNnQVFHcUFRZG5kM010ZDJsNndBRUImc2NsaWVudD1wc3ktYWImdmVkPTBhaFVLRXdpR3FPQ0VwTGp0QWhVQzNhUUtIUnl5QlR3UTRkVURDQXcmdWFjdD01KQ0KLSBbSG93IHRvIEFjY2VzcyBBbnkgUkVTVGZ1bCBBUEkgVXNpbmcgdGhlIFIgTGFuZ3VhZ2UsIGJ5IEFuZHJldyBDYXJwZW50ZXJdKGh0dHBzOi8vd3d3LnByb2dyYW1tYWJsZXdlYi5jb20vbmV3cy9ob3ctdG8tYWNjZXNzLWFueS1yZXN0ZnVsLWFwaS11c2luZy1yLWxhbmd1YWdlL2hvdy10by8yMDE3LzA3LzIxKQ0KDQojIyMgSW1wb3J0YW50IHNvdXJjZXMsIGRvY3VtZW50YXRpb24sIGV0Yy4NCg0KLSBbWE1MIFR1dG9yaWFsLCBXM0NdKGh0dHBzOi8vd3d3Lnczc2Nob29scy5jb20veG1sLykNCi0gW0pTT04gZGVmaW5lZF0oaHR0cHM6Ly93d3cuanNvbi5vcmcvanNvbi1lbi5odG1sKQ0KLSBbSlMgSlNPTiBmcm9tIHczc2Nob29sc10oaHR0cHM6Ly93d3cudzNzY2hvb2xzLmNvbS9qcy9qc19qc29uX2ludHJvLmFzcCkNCi0gW1F1aWNrLVJdKGh0dHBzOi8vd3d3LnN0YXRtZXRob2RzLm5ldC8pIC0gQW4gZXhjZWxsZW50IHNldCBvZiBjb25jaXNlIFIgdHV0b3JpYWxzLg0KLSBbQ1JBTjogVGhlIENvbXByZWhlbnNpdmUgUiBBcmNoaXZlIE5ldHdvcmtdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnLykgLSBUaGlzIGlzIHdlcmUgdGhlIG9mZmljaWFsIHZlcnNpb25zIG9mIFIgcGFja2FnZXMgbGl2ZQ0KLSBbQ29sb3JzIGluIFIgKFBERildKGh0dHBzOi8vd3d3Lmdvb2dsZS5jb20vdXJsP3NhPXQmcmN0PWomcT0mZXNyYz1zJnNvdXJjZT13ZWImY2Q9JnZlZD0yYWhVS0V3alhoTlBJdV9UdEFoVlNrTU1LSFJ4NkEzUVFGakFCZWdRSUFoQUMmdXJsPWh0dHAlM0ElMkYlMkZ3d3cuc3RhdC5jb2x1bWJpYS5lZHUlMkZ+dHpoZW5nJTJGZmlsZXMlMkZSY29sb3IucGRmJnVzZz1BT3ZWYXcxWEluLW13WjczUmdHOGJ4aDRMYW40KQ0KLSBbZ2dwbG90MiBSZWZlcmVuY2VdKGh0dHBzOi8vZ2dwbG90Mi50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS8pDQotIFtSRG9jdW1lbnRhdGlvbjogU2VhcmNoIGFsbCAyMSwyMTcgQ1JBTiwgQmlvY29uZHVjdG9yIGFuZCBHaXRIdWIgcGFja2FnZXNdKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy8pDQoNCiMjIyBSIE1hcmtkb3duDQoNCltSIE1hcmtkb3duXShodHRwczovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbS8pIGlzIHdoYXQgSSBoYXZlIHVzZWQgdG8gcHJvZHVjZSB0aGlzIGJlYXV0aWZ1bCBOb3RlYm9vay4gV2Ugd2lsbCBsZWFybiBtb3JlIGFib3V0IGl0IG5lYXIgdGhlIGVuZCBvZiB0aGUgY291cnNlLCBidXQgaWYgeW91IGFscmVhZHkgZmVlbCByZWFkeSB0byBkaXZlIGRlZXAsIGhlcmUncyBhIGJvb2s6IFtSIE1hcmtkb3duOiBUaGUgRGVmaW5pdGl2ZSBHdWlkZSwgWWlodWkgWGllLCBKLiBKLiBBbGxhaXJlLCBHYXJyZXR0IEdyb2xlbXVuZHMuXShodHRwczovL2Jvb2tkb3duLm9yZy95aWh1aS9ybWFya2Rvd24vKSANCg0KIyMjIEV4ZXJjaXNlcw0KDQotICoqRTEuKiogV3JpdGUgYSBmdW5jdGlvbiBgZW5saXN0VmVjdG9ycygpYCB0aGF0IHRha2UgdGhyZWUgYG51bWVyaWMoKWAgdmVjdG9ycyBhcyBpdHMgYXJndW1lbnRzIGFuZCByZXR1cm5zIGEgbmFtZWQgbGlzdCBlbmNvbXBhc3NpbmcgdGhlbS4NCg0KLSAqKkUyLioqIFdyaXRlIGEgZnVuY3Rpb24gYG15U3VtU3F1YXJlc2AgdGhhdCB0YWtlcyBvbmUgYXJndW1lbnQsIGB4YCwgYW5kIGRlY2lkZXMgKGEpIGlmIGB4YCBpcyBgbnVtZXJpY2AsIHJldHVybnMgYSBzcXVhcmUgb2YgYHN1bSh4KWAsIGJ1dCAoYikgaWYgYHhgIGlzIGEgYG1hdHJpeGAsIHJldHVybnMgYSBudW1lcmljIGVuY29tcGFzc2luZyB0aGUgc3F1YXJlZCBzdW1zIG9mIGFsbCBpdHMgcm93cy4gQ2FyZWZ1bGx5OiBpZiBgbSA8LSBtYXRyaXgoMTo0LCBucm93ID0gMilgLCB3aGF0IGlzIGBjbGFzcyhtKWA/DQoNCi0gKipFMy4qKiBXcml0ZSBhIGZ1bmN0aW9uIHRoYXQgcmV0dXJucyBhIGZ1bmN0aW9uISBDcmVhdGUgYSBmdW5jdGlvbiBgZml4Q2FzZShzdHJpbmcsIGhvdylgIHRoYXQgcmV0dXJucyAoMSkgYSBmdW5jdGlvbiB3aGljaCB0cmFuc2Zvcm1zIGFsbCBjaGFyYWN0ZXJzIGluIGEgc3RyaW5nIHRvIHVwcGVyIGNhc2UgaWYgYGhvdyA9PSAndXBwZXInYCBhbmQgKDIpIHRyYW5zZm9ybXMgYWxsIGNoYXJhY3RlcnMgaW4gYSBzdHJpbmcgdG8gbG93ZXIgY2FzZSBpZiBgaG93ID09ICdsb3dlcidgLiBIaW50OiBzdHVkeSBiYXNlIFIgZnVuY3Rpb25zIGB0b2xvd2VyKClgIGFuZCBgdG91cHBlcigpYC4gDQoNCi0gKipFNC4qKiBEbyB5b3UgdXNlIGFueSBvbmxpbmUgc2VydmljZSBsaWtlIFNwb3RpZnksIG9yIGEgc29jaWFsIG5ldHdvcmsgbGlrZSBUd2l0dGVyPyBPaywgaGVyZSBpcyB3aGF0IHlvdSBuZWVkIHRvIGRvOiBmaW5kIGlmIHlvdXIgc2VydmljZSBwcm92aWRlcyBhbiBSRVNUIEFQSSB0byBvYnRhaW4gc29tZSBkYXRhIGZyb20gaXQsIHJlYWQgdGhlIEFQSSBkb2N1bWVudGF0aW9uIGFuZCB0cnkgdG8gcmVnaXN0ZXIgYXMgYW4gQVBJIHVzZXIuIEhpbnQ6IHlvdSB3aWxsIHByb2JhYmx5IG5lZWQgdG8gc2V0IGEgdXNlcm5hbWUgYW5kIG9idGFpbiB5b3VyICpjcmVkZW50aWFscyogKHByb2JhYmx5IGFuIGF1dGhvcml6YXRpb24gdG9rZW4sIG9yIGEgcGFzc3dvcmQsIG9yIHNvbWV0aGluZyBzaW1pbGFyKS4gSW4gdGhlIGZvbGxvd2luZyBzZXNzaW9ucyB3ZSB3aWxsIGJlIHVzaW5nIEFQSXMgYSBsb3QgdG8gZmV0Y2ggZGF0YSBmcm9tIHRoZSBJbnRlcm5ldC4NCg0KDQoqKioNCkdvcmFuIFMuIE1pbG92YW5vdmnEhw0KDQpEYXRhS29sZWt0aXYsIDIwMjAvMjENCg0KY29udGFjdDogZ29yYW4ubWlsb3Zhbm92aWNAZGF0YWtvbGVrdGl2LmNvbQ0KDQohW10oLi4vX2ltZy9ES19Mb2dvXzEwMC5wbmcpDQoNCioqKg0KTGljZW5zZTogW0dQTHYzXShodHRwOi8vd3d3LmdudS5vcmcvbGljZW5zZXMvZ3BsLTMuMC50eHQpDQpUaGlzIE5vdGVib29rIGlzIGZyZWUgc29mdHdhcmU6IHlvdSBjYW4gcmVkaXN0cmlidXRlIGl0IGFuZC9vciBtb2RpZnkgaXQgdW5kZXIgdGhlIHRlcm1zIG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBhcyBwdWJsaXNoZWQgYnkgdGhlIEZyZWUgU29mdHdhcmUgRm91bmRhdGlvbiwgZWl0aGVyIHZlcnNpb24gMyBvZiB0aGUgTGljZW5zZSwgb3IgKGF0IHlvdXIgb3B0aW9uKSBhbnkgbGF0ZXIgdmVyc2lvbi4NClRoaXMgTm90ZWJvb2sgaXMgZGlzdHJpYnV0ZWQgaW4gdGhlIGhvcGUgdGhhdCBpdCB3aWxsIGJlIHVzZWZ1bCwgYnV0IFdJVEhPVVQgQU5ZIFdBUlJBTlRZOyB3aXRob3V0IGV2ZW4gdGhlIGltcGxpZWQgd2FycmFudHkgb2YgTUVSQ0hBTlRBQklMSVRZIG9yIEZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFLiAgU2VlIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBmb3IgbW9yZSBkZXRhaWxzLg0KWW91IHNob3VsZCBoYXZlIHJlY2VpdmVkIGEgY29weSBvZiB0aGUgR05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2UgYWxvbmcgd2l0aCB0aGlzIE5vdGVib29rLiBJZiBub3QsIHNlZSA8aHR0cDovL3d3dy5nbnUub3JnL2xpY2Vuc2VzLz4uDQoNCioqKg0KDQo=