enumerate()
is a generator that returns a running index as well as the actual element or item in the container. Optinal argument includes the start parameter (number the index should start at)
## [(0, 1), (1, 2), (2, 3)]
list(enumerate(range(3), start=10))
## [(10, 0), (11, 1), (12, 2)]
Fizz Buzz
Write a program that prints the numbers from 1 to 100 and for multiples of ‘3’ print “Fizz” instead of the number and for the multiples of ‘5’ print “Buzz”.
for i in range(1,101):
if i % 3 == 0:
if i % 5 == 0:
print('Fizz Buzz')
continue
print('Fizz')
continue
if i % 5 == 0:
if i % 3 == 0:
print('Fizz Buzz')
continue
print('Buzz')
continue
else:
print(i)
## 1
## 2
## Fizz
## 4
## Buzz
## Fizz
## 7
## 8
## Fizz
## Buzz
## 11
## Fizz
## 13
## 14
## Fizz Buzz
## 16
## 17
## Fizz
## 19
## Buzz
## Fizz
## 22
## 23
## Fizz
## Buzz
## 26
## Fizz
## 28
## 29
## Fizz Buzz
## 31
## 32
## Fizz
## 34
## Buzz
## Fizz
## 37
## 38
## Fizz
## Buzz
## 41
## Fizz
## 43
## 44
## Fizz Buzz
## 46
## 47
## Fizz
## 49
## Buzz
## Fizz
## 52
## 53
## Fizz
## Buzz
## 56
## Fizz
## 58
## 59
## Fizz Buzz
## 61
## 62
## Fizz
## 64
## Buzz
## Fizz
## 67
## 68
## Fizz
## Buzz
## 71
## Fizz
## 73
## 74
## Fizz Buzz
## 76
## 77
## Fizz
## 79
## Buzz
## Fizz
## 82
## 83
## Fizz
## Buzz
## 86
## Fizz
## 88
## 89
## Fizz Buzz
## 91
## 92
## Fizz
## 94
## Buzz
## Fizz
## 97
## 98
## Fizz
## Buzz
Write a program with the same requirements as above but mutates a list, not prints it. Use if and elif to not mutate the list so many times.
def fizz_buzz(numbers):
'''
Given a list of integers:
1. replace all integers that are evenly divisible by 3 with 'fizz'
2. replace all integers divisible by 5 with 'buzz'
3. replace all integers divisible by both 3 and 5 with 'fizzbuzz'
>>> numbers = [45, 22, 14, 65, 97, 72]
>>> fizz_buzz(numbers)
>>> numbers
['fizzbuzz', 22, 14, 'buzz', 97, 'fizz']
'''
for i in range(len(numbers)):
num = numbers[i]
if num % 3 == 0 and num % 5 == 0:
numbers[i] = 'fizzbuzz'
elif num % 3 == 0:
numbers[i] = 'fizz'
elif num % 5 == 0:
numbers[i] = 'buzz'
numbers = [1,2,3,4,5,6,7,8,9,10,15,45, 22, 14, 65, 97, 72]
fizz_buzz(numbers)
numbers
## [1, 2, 'fizz', 4, 'buzz', 'fizz', 7, 8, 'fizz', 'buzz', 'fizzbuzz', 'fizzbuzz', 22, 14, 'buzz', 97, 'fizz']
Can also use enumerate()
instead of range(len()). Difference is that eumerate()
takes an iterable while range(len()) takes a number.
def fizz_buzz(numbers):
'''
Given a list of integers:
1. replace all integers that are evenly divisible by 3 with 'fizz'
2. replace all integers divisible by 5 with 'buzz'
3. replace all integers divisible by both 3 and 5 with 'fizzbuzz'
>>> numbers = [45, 22, 14, 65, 97, 72]
>>> fizz_buzz(numbers)
>>> numbers
['fizzbuzz', 22, 14, 'buzz', 97, 'fizz']
'''
for i, num in enumerate(numbers):
num = numbers[i]
if num % 3 == 0 and num % 5 == 0:
numbers[i] = 'fizzbuzz'
elif num % 3 == 0:
numbers[i] = 'fizz'
elif num % 5 == 0:
numbers[i] = 'buzz'
numbers_1 = list(range(1,101))
fizz_buzz(numbers_1)
numbers_1
## [1, 2, 'fizz', 4, 'buzz', 'fizz', 7, 8, 'fizz', 'buzz', 11, 'fizz', 13, 14, 'fizzbuzz', 16, 17, 'fizz', 19, 'buzz', 'fizz', 22, 23, 'fizz', 'buzz', 26, 'fizz', 28, 29, 'fizzbuzz', 31, 32, 'fizz', 34, 'buzz', 'fizz', 37, 38, 'fizz', 'buzz', 41, 'fizz', 43, 44, 'fizzbuzz', 46, 47, 'fizz', 49, 'buzz', 'fizz', 52, 53, 'fizz', 'buzz', 56, 'fizz', 58, 59, 'fizzbuzz', 61, 62, 'fizz', 64, 'buzz', 'fizz', 67, 68, 'fizz', 'buzz', 71, 'fizz', 73, 74, 'fizzbuzz', 76, 77, 'fizz', 79, 'buzz', 'fizz', 82, 83, 'fizz', 'buzz', 86, 'fizz', 88, 89, 'fizzbuzz', 91, 92, 'fizz', 94, 'buzz', 'fizz', 97, 98, 'fizz', 'buzz']
Doctest
Doctest is a module that scans the Docstrings of functions looking for lines that look like input and output and uses them to test your function. The Doctest module can be either added into the script or run in concert with the script using command line arguments.
Adding to the script
if __name__ == "__main__":
import doctest
doctest.testmod()
Running from the command line
python -m doctest -v temp.py
The -v
switch will print the tests and results that Doctest is running. Without the switch, it will run but not print anything to the console unless there is an error found.
Examples
def factorial(n):
"""Return the factorial of n, an exact integer >= 0.
>>> [factorial(n) for n in range(6)]
[1, 1, 2, 6, 24, 120]
>>> factorial(30)
265252859812191058636308480000000
>>> factorial(-1)
Traceback (most recent call last):
...
ValueError: n must be >= 0
Factorials of floats are OK, but the float must be an exact integer:
>>> factorial(30.1)
Traceback (most recent call last):
...
ValueError: n must be exact integer
>>> factorial(30.0)
265252859812191058636308480000000
It must also not be ridiculously large:
>>> factorial(1e100)
Traceback (most recent call last):
...
OverflowError: n too large
"""
import math
if not n >= 0:
raise ValueError("n must be >= 0")
if math.floor(n) != n:
raise ValueError("n must be exact integer")
if n+1 == n: # catch a value like 1e300
raise OverflowError("n too large")
result = 1
factor = 2
while factor <= n:
result *= factor
factor += 1
return result
if __name__ == "__main__":
import doctest
doctest.testmod()
Differences between two lists
When comparing two lists
, you can convert them into sets
to eliminate any duplicate numbers. Also, lists can not be operated on using ‘-’ but sets
can. Lists can however use the ‘+’ which just combines the two lists
together.
list1 = [1,2,3,4]
list2 = [3,3,4,5,6,7,7]
set(list1) - set(list2)
## {1, 2}
list1 = [1,2,3,4]
list2 = [3,3,4,5,6,7,7]
set(list2) - set(list1)
## {5, 6, 7}
def diff(list1, list2):
return list(set(list1) - set(list2)) + list(set(list2) - set(list1))
list1 = [1,2,3,4]
list2 = [3,3,4,5,6,7,7]
diff(list1, list2)
## [1, 2, 5, 6, 7]
Without using set()
we can use list comprehension
def diff2(list1, list2):
list_dif = [i for i in list1 + list2 if i not in list1 or i not in list2]
return list_dif
list1 = [1,2,3,4]
list2 = [3,3,4,5,6,7,7]
list3 = diff2(list1, list2)
print(list3)
## [1, 2, 5, 6, 7, 7]
The difference between diff()
and diff2()
is that if you don’t convert the lists
to sets
then you keep both 7's
in list2
.
Underscore ’_’
The underscore can be used to ignore a single value
a, _, b = (1, 2, 3) # a = 1, b = 3
print(a, b)
## 1 3
It can also be used to ignore multiple values using the *(variable) used to assign multiple value to a variable as list while unpacking it’s called “Extended Unpacking”, only available in Python 3.x
a, *_, b = (7, 6, 5, 4, 3, 2, 1)
print(a, b)
## 7 1
Separating Digits Of Numbers
If you have a long digits number, you can separate the group of digits as you like for better understanding.
million = 1_000_000
List comprehension
lst = [1,2,-5,4]
def square(x):
return x*x
Instead of doing the following:
## [1, 4, 25, 16]
You can use list comprehension:
[square(num) for num in lst]
## [1, 4, 25, 16]
def is_odd(x):
return x % 2 == 1
We can use the filter()
function to filter out elements of our list according to if they meet certain criteria.
list(filter(is_odd, lst))
## [1, -5]
[x for x in lst if is_odd(x)]
## [1, -5]
Let’s create a 2x3 matrix filled with zeros and define it using lists.
Using for
loop
num_rows = 2
num_columns = 3
grid = []
for _ in range(num_rows):
curr_row = []
for _ in range(num_columns):
curr_row.append(0)
grid.append(curr_row)
grid
## [[0, 0, 0], [0, 0, 0]]
Using list comprehension
num_rows = 2
num_columns = 3
grid = []
grid = [[0 for _ in range(num_columns)] for _ in range(num_rows)]
grid
## [[0, 0, 0], [0, 0, 0]]
Compare the above to this where the brackets are in different places.
num_rows = 2
num_columns = 3
grid = []
grid = [[0 for _ in range(num_columns) for _ in range(num_rows)]]
grid
## [[0, 0, 0, 0, 0, 0]]
[] = list
() = tuple
{} = dictionary/set
The function max()
takes in some numbers and returns the max number
L = [1,2,3, -4]
t = (1,2,3, -4)
s = {1,2,3, -4}
## 3
## 3
## 3
## 3
max(L, key=lambda x: x*x)
## -4
min()
is the same as max but returns the min
any()
takes in an iterable and returns True
if any of the values in the iterable are True
and returns False
if none of the values are True
## True
## False
We can’t use any()
to see if any in our list is odd because it does not take arguments to key
any(L, key=lambda x: x % 2 == 1)
Instead, we have to use list comprehension. You can pass in an argument (num) by adding it after the lambda function.
[(lambda x:x % 2 == 1)(num) for num in L]
## [True, False, True, False]
any([(lambda x:x % 2 == 1)(num) for num in L])
## True
all()
is the same as any()
but only returns True
if **all* of the items are True
all([(lambda x:x % 2 == 1)(num) for num in L])
## False
F-strings when creating a new class
class A(object):
def __init__(self,name,age):
self.name = name
self.age = age
def __repr__(self):
return f"""
My name is {self.name}.
I am {self.age + 5} years old
"""
name = 'Bob'
age = 15
print(A(name,age))
##
## My name is Bob.
## I am 20 years old
##
##
## My name is Nathan.
## I am 36 years old
##
Sorting
Can sort a list alphabetically.
animals = ["cat", "dog", "cheetah", "rhino"]
sorted(animals)
## ['cat', 'cheetah', 'dog', 'rhino']
sorted(animals, reverse=True)
## ['rhino', 'dog', 'cheetah', 'cat']
Here, we have a list of animals defined by a dictionary.
animals = [
{'type': 'cat', 'name': 'Stephanie', 'age': 8},
{'type': 'dog', 'name': 'Devon', 'age': 3},
{'type': 'rhino', 'name': 'Moe', 'age': 5},
]
You can’t sort a dictionary but you can define a lambda
to sort by.
sorted(animals, key = lambda animal: animal['age'])
## [{'type': 'dog', 'name': 'Devon', 'age': 3}, {'type': 'rhino', 'name': 'Moe', 'age': 5}, {'type': 'cat', 'name': 'Stephanie', 'age': 8}]
If you wanted to return the oldest animal, pass in the reverse=True
parameter and slice into the first item in the dictionary.
sorted(animals, key = lambda animal: animal['age'], reverse=True)[0]
## {'type': 'cat', 'name': 'Stephanie', 'age': 8}
You can also use the .sort()
method, you can do the same thing but mutating the dictionary
animals.sort(key = lambda animal: animal['age'], reverse=True)
animals
## [{'type': 'cat', 'name': 'Stephanie', 'age': 8}, {'type': 'rhino', 'name': 'Moe', 'age': 5}, {'type': 'dog', 'name': 'Devon', 'age': 3}]
Set()
- Sets are unordered
- Set elements are unique (no duplicates)
- A set may be modified but the elements contained in the set must be of an immutable type
## ['q', 'u', 'u', 'x']
## {'q', 'u', 'x'}
x = {'foo', 'bar', 'baz', 'foo', 'qux'}
x
## {'foo', 'bar', 'qux', 'baz'}
## {'o', 'f'}
## <class 'dict'>
## <class 'set'>
Don’t forget that set elements must be immutable. For example, a tuple may be included in a set:
x = {42, 'foo', (1, 2, 3), 3.14159}
x
## {'foo', 42, (1, 2, 3), 3.14159}
But lists and dictionaries are mutable, so they can’t be set elements:
a = [1, 2, 3]
{a}
d = {'a': 1, 'b': 2}
{d}
x = {'foo', 'bar', 'baz'}
len(x)
## 3
## True
## False
In Python, set union can be performed with the | operator:
x1 = {'foo', 'bar', 'baz'}
x2 = {'baz', 'qux', 'quux'}
x1 | x2
## {'foo', 'baz', 'bar', 'quux', 'qux'}
Set union can also be obtained with the .union() method. The method is invoked on one of the sets, and the other is passed as an argument
## {'foo', 'baz', 'bar', 'quux', 'qux'}
When you use the | operator, both operands must be sets. The .union() method, on the other hand, will take any iterable as an argument, convert it to a set, and then perform the union.
x1.union(('baz', 'qux', 'quux'))
## {'baz', 'bar', 'qux', 'foo', 'quux'}
More than two sets may be specified with either the operator or the method:
a = {1, 2, 3, 4}
b = {2, 3, 4, 5}
c = {3, 4, 5, 6}
d = {4, 5, 6, 7}
a.union(b, c, d)
## {1, 2, 3, 4, 5, 6, 7}
## {1, 2, 3, 4, 5, 6, 7}
The resulting set contains only elements that are present in all of the specified sets.
a = {1, 2, 3, 4}
b = {2, 3, 4, 5}
c = {3, 4, 5, 6}
d = {4, 5, 6, 7}
a.intersection(b, c, d)
## {4}
## {4}
x1.difference(x2) and x1 - x2 return the set of all elements that are in x1 but not in x2:
x1 = {'foo', 'bar', 'baz'}
x2 = {'baz', 'qux', 'quux'}
x1.difference(x2)
## {'foo', 'bar'}
## {'foo', 'bar'}
Frozen Sets
Srozenset, which is in all respects exactly like a set, except that a frozenset is immutable. You can perform non-modifying operations on a frozenset but methods that attempt to modify a frozenset fail
How to Use Generators and yield in Python
Have you ever had to work with a dataset so large that it overwhelmed your machine’s memory? Or maybe you have a complex function that needs to maintain an internal state every time it’s called, but the function is too small to justify creating its own class. In these cases and more, generators and the Python yield statement are here to help.
Using Generators
Introduced with PEP 255, generator functions are a special kind of function that return a lazy iterator. These are objects that you can loop over like a list. However, unlike lists, lazy iterators do not store their contents in memory.
This is a reasonable explanation, but would this design still work if the file is very large? What if the file is larger than the memory you have available? To answer this question, let’s assume that csv_reader() just opens the file and reads it into an array:
def csv_reader(file_name):
file = open(file_name)
result = file.read().split("\n")
return result
This function opens a given file and uses file.read() along with .split() to add each line as a separate element to a list. If you were to use this version of csv_reader() in the row counting code block you saw further up, then you’d get the following output: MemoryError
In this case, open() returns a generator object that you can lazily iterate through line by line. However, file.read().split() loads everything into memory at once, causing the MemoryError.
However you can turn csv_reader() into a generator function:
def csv_reader(file_name):
for row in open(file_name, "r"):
yield row
This version opens a file, loops through each line, and yields each row instead of returning it.
You can also define a generator expression (also called a generator comprehension), which has a very similar syntax to list comprehensions. In this way, you can use the generator without calling a function:
csv_gen = (row for row in open(file_name))
- Using yield will result in a generator object.
- Using return will result in the first line of the file only.
Generating an Infinite Sequence
In Python, to get a finite sequence, you call range() and evaluate it in a list context:
## [0, 1, 2, 3, 4]
Generating an infinite sequence, however, will require the use of a generator, since your computer memory is finite:
def infinite_sequence():
num = 0
while True:
yield num
num += 1
Detecting Palindromes
def is_palindrome(num):
# Skip single-digit inputs
# Floor division returns not the remainder like % but the whole number.
if num // 10 == 0:
return False
temp = num
reversed_num = 0
while temp != 0:
reversed_num = (reversed_num * 10) + (temp % 10)
temp = temp // 10
if num == reversed_num:
return num
else:
return False
Don’t worry too much about understanding the underlying math in this code. Just note that the function takes an input number, reverses it, and checks to see if the reversed number is the same as the original. Now you can use your infinite sequence generator to get a running list of all numeric palindromes:
for i in infinite_sequence():
pal = is_palindrome(i)
if pal:
print(pal)
Understanding Generators
yield indicates where a value is sent back to the caller, but unlike return, you don’t exit the function afterward.
Instead, the state of the function is remembered. That way, when next() is called on a generator object (either explicitly or implicitly within a for loop), the previously yielded variable num is incremented, and then yielded again.
You can create Generator Expressions without building and holding the entire object in memory before iteration. In other words, you’ll have no memory penalty when you use generator expressions. Take this example of squaring some numbers.
nums_squared_lc = [num**2 for num in range(5)]
nums_squared_lc
## [0, 1, 4, 9, 16]
nums_squared_gc = (num**2 for num in range(5))
nums_squared_gc
## <generator object <genexpr> at 0x0000000060651AC0>
Understanding the Python Yield Statement
When you call a generator function or use a generator expression, you return a special iterator called a generator. You can assign this generator to a variable in order to use it. When you call special methods on the generator, such as next(), the code within the function is executed up to yield.
When the Python yield statement is hit, the program suspends function execution and returns the yielded value to the caller. (In contrast, return stops function execution completely.) When a function is suspended, the state of that function is saved. This includes any variable bindings local to the generator, the instruction pointer, the internal stack, and any exception handling.
This allows you to resume function execution whenever you call one of the generator’s methods. In this way, all function evaluation picks back up right after yield. You can see this in action by using multiple Python yield statements:
def multi_yield():
yield_str = "This will print the first string"
yield yield_str
yield_str = "This will print the second string"
yield yield_str
multi_obj = multi_yield()
print(next(multi_obj))
print(next(multi_obj))
print(next(multi_obj))
The following is the output, which returns a traceback in the end.
'This will print the first string'
'This will print the second string'
*StopIteration:*
Take a closer look at that last call to next(). You can see that execution has blown up with a traceback. This is because generators, like all iterators, can be exhausted. Unless your generator is infinite, you can iterate through it one time only. Once all values have been evaluated, iteration will stop and the for loop will exit. If you used next(), then instead you’ll get an explicit StopIteration exception.
Using Advanced Generator Methods
- .send()
- .throw()
- .close()
Creating Data Pipelines With Generators
Data pipelines allow you to string together code to process large datasets or streams of data without maxing out your machine’s memory. Imagine that you have a large CSV file:
Let’s think of a strategy:
- Read every line of the file.
- Split each line into a list of values.
- Extract the column names.
- Use the column names and lists to create a dictionary.
- Filter out the rounds you aren’t interested in.
- Calculate the total and average values for the rounds you are interested in.
Normally, you can do this with a package like pandas, but you can also achieve this functionality with just a few generators. You’ll start by reading each line from the file with a generator expression:
file_name = "data/TechCrunchcontinentalUSA.csv"
lines = (line for line in open(file_name))
# Then, you’ll use another generator expression in concert with the previous one to split each line into a list:
list_line = (s.rstrip().split(",") for s in lines)
Here, you created the generator list_line, which iterates through the first generator lines. This is a common pattern to use when designing generator pipelines. Next, you’ll pull the column names out of techcrunch.csv. Since the column names tend to make up the first line in a CSV file, you can grab that with a short next() call:
To sum this up, you first create a generator expression lines to yield each line in a file. Next, you iterate through that generator within the definition of another generator expression called list_line, which turns each line into a list of values. Then, you advance the iteration of list_line just once with next() to get a list of the column names from your CSV file.
To help you filter and perform operations on the data, you’ll create dictionaries where the keys are the column names from the CSV:
company_dicts = (dict(zip(cols, data)) for data in list_line)
This generator expression iterates through the lists produced by list_line. Then, it uses zip() and dict() to create the dictionary as specified above. Now, you’ll use a fourth generator to filter the funding round you want and pull raisedAmt as well:
funding = (
int(company_dict["raisedAmt"])
for company_dict in company_dicts
if company_dict["round"] == "a"
)
In this code snippet, your generator expression iterates through the results of company_dicts and takes the raisedAmt for any company_dict where the round key is “a”.
Remember, you aren’t iterating through all these at once in the generator expression. In fact, you aren’t iterating through anything until you actually use a for loop or a function that works on iterables, like sum(). In fact, call sum() now to iterate through the generators:
total_series_a = sum(funding)
print(f"Total series A fundraising: ${total_series_a}")
## Total series A fundraising: $4376015000
file_name = "techcrunch.csv"
lines = (line for line in open(file_name))
list_line = (s.rstrip()split(",") for s in lines)
cols = next(list_line)
company_dicts = (dict(zip(cols, data)) for data in list_line)
funding = (
int(company_dict["raisedAmt"])
for company_dict in company_dicts
if company_dict["round"] == "a"
)
total_series_a = sum(funding)
print(f"Total series A fundraising: ${total_series_a}")
Putting this all together, you’ll produce the following script:
This script pulls together every generator you’ve built, and they all function as one big data pipeline. Here’s a line by line breakdown:
- Line 2 reads in each line of the file.
- Line 3 splits each line into values and puts the values into a list.
- Line 4 uses next() to store the column names in a list.
- Line 5 creates dictionaries and unites them with a zip() call:
- The keys are the column names cols from line 4.
- The values are the rows in list form, created in line 3.
- Line 6 gets each company’s series A funding amounts. It also filters out any other raised amount.
- Line 11 begins the iteration process by calling sum() to get the total amount of series A funding found in the CSV.
When you run this code on techcrunch.csv, you should find a total of $4,376,015,000 raised in series A funding rounds.
Note: The methods for handling CSV files developed in this tutorial are important for understanding how to use generators and the Python yield statement. However, when you work with CSV files in Python, you should instead use the csv module included in Python’s standard library. This module has optimized methods for handling CSV files efficiently.
Using zip() in Python
Python’s zip() function is defined as zip(*iterables). The function takes in iterables as arguments and returns an iterator. This iterator generates a series of tuples containing elements from each iterable. zip() can accept any type of iterable, such as files, lists, tuples, dictionaries, sets, and so on.
Passing n Arguments
If you use zip() with n arguments, then the function will return an iterator that generates tuples of length n. To see this in action, take a look at the following code block:
numbers = [1, 2, 3]
letters = ['a', 'b', 'c']
zipped = zip(numbers, letters)
zipped # Holds an iterator object
## <zip object at 0x0000000060792E00>
## <class 'zip'>
## [(1, 'a'), (2, 'b'), (3, 'c')]
Here, you use zip(numbers, letters) to create an iterator that produces tuples of the form (x, y). In this case, the x values are taken from numbers and the y values are taken from letters. Notice how the Python zip() function returns an iterator. To retrieve the final list object, you need to use list() to consume the iterator.
If you’re working with sequences like lists, tuples, or strings, then your iterables are guaranteed to be evaluated from left to right. This means that the resulting list of tuples will take the form [(numbers[0], letters[0]), (numbers[1], letters[1]),…, (numbers[n], letters[n])]. However, for other types of iterables (like sets), you might see some weird results:
s1 = {2, 3, 1}
s2 = {'b', 'a', 'c'}
list(zip(s1, s2))
## [(1, 'b'), (2, 'c'), (3, 'a')]
LS0tDQp0aXRsZTogIlN0dWR5aW5nIFB5dGhvbiINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDINCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIGhpZ2hsaWdodDogemVuYnVybg0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICBpbmNsdWRlczoNCiAgICAgIGluX2hlYWRlcjogaGVhZGVyLmh0bWwNCi0tLQ0KDQpgZW51bWVyYXRlKClgIGlzIGEgZ2VuZXJhdG9yIHRoYXQgcmV0dXJucyBhIHJ1bm5pbmcgaW5kZXggYXMgd2VsbCBhcyB0aGUgYWN0dWFsIGVsZW1lbnQgb3IgaXRlbSBpbiB0aGUgY29udGFpbmVyLiBPcHRpbmFsIGFyZ3VtZW50IGluY2x1ZGVzIHRoZSBzdGFydCBwYXJhbWV0ZXIgKG51bWJlciB0aGUgaW5kZXggc2hvdWxkIHN0YXJ0IGF0KQ0KDQpgYGB7cHl0aG9ufQ0KbGlzdChlbnVtZXJhdGUoWzEsMiwzXSkpDQpgYGANCg0KYGBge3B5dGhvbn0NCmxpc3QoZW51bWVyYXRlKHJhbmdlKDMpLCBzdGFydD0xMCkpDQpgYGANCg0KKipGaXp6IEJ1enoqKg0KDQpXcml0ZSBhIHByb2dyYW0gdGhhdCBwcmludHMgdGhlIG51bWJlcnMgZnJvbSAxIHRvIDEwMCBhbmQgZm9yIG11bHRpcGxlcyBvZiAnMycgcHJpbnQg4oCcRml6euKAnSBpbnN0ZWFkIG9mIHRoZSBudW1iZXIgYW5kIGZvciB0aGUgbXVsdGlwbGVzIG9mICc1JyBwcmludCDigJxCdXp64oCdLg0KDQpgYGB7cHl0aG9ufQ0KZm9yIGkgaW4gcmFuZ2UoMSwxMDEpOg0KICBpZiBpICUgMyA9PSAwOg0KICAgIGlmIGkgJSA1ID09IDA6DQogICAgICBwcmludCgnRml6eiBCdXp6JykNCiAgICAgIGNvbnRpbnVlDQogICAgcHJpbnQoJ0ZpenonKQ0KICAgIGNvbnRpbnVlDQogIGlmIGkgJSA1ID09IDA6DQogICAgaWYgaSAlIDMgPT0gMDoNCiAgICAgIHByaW50KCdGaXp6IEJ1enonKQ0KICAgICAgY29udGludWUNCiAgICBwcmludCgnQnV6eicpDQogICAgY29udGludWUNCiAgZWxzZToNCiAgICBwcmludChpKQ0KDQpgYGANCg0KV3JpdGUgYSBwcm9ncmFtIHdpdGggdGhlIHNhbWUgcmVxdWlyZW1lbnRzIGFzIGFib3ZlIGJ1dCBtdXRhdGVzIGEgbGlzdCwgbm90IHByaW50cyBpdC4gDQpVc2UgaWYgYW5kIGVsaWYgdG8gbm90IG11dGF0ZSB0aGUgbGlzdCBzbyBtYW55IHRpbWVzLiANCg0KYGBge3B5dGhvbn0NCmRlZiBmaXp6X2J1enoobnVtYmVycyk6DQogICcnJw0KICBHaXZlbiBhIGxpc3Qgb2YgaW50ZWdlcnM6DQogIDEuIHJlcGxhY2UgYWxsIGludGVnZXJzIHRoYXQgYXJlIGV2ZW5seSBkaXZpc2libGUgYnkgMyB3aXRoICdmaXp6Jw0KICAyLiByZXBsYWNlIGFsbCBpbnRlZ2VycyBkaXZpc2libGUgYnkgNSB3aXRoICdidXp6Jw0KICAzLiByZXBsYWNlIGFsbCBpbnRlZ2VycyBkaXZpc2libGUgYnkgYm90aCAzIGFuZCA1IHdpdGggJ2ZpenpidXp6Jw0KICANCiAgPj4+IG51bWJlcnMgPSBbNDUsIDIyLCAxNCwgNjUsIDk3LCA3Ml0NCiAgPj4+IGZpenpfYnV6eihudW1iZXJzKQ0KICA+Pj4gbnVtYmVycw0KICBbJ2ZpenpidXp6JywgMjIsIDE0LCAnYnV6eicsIDk3LCAnZml6eiddDQogICcnJw0KICBmb3IgaSBpbiByYW5nZShsZW4obnVtYmVycykpOg0KICAgIG51bSA9IG51bWJlcnNbaV0NCiAgICBpZiBudW0gJSAzID09IDAgYW5kIG51bSAlIDUgPT0gMDoNCiAgICAgIG51bWJlcnNbaV0gPSAnZml6emJ1enonDQogICAgZWxpZiBudW0gJSAzID09IDA6DQogICAgICBudW1iZXJzW2ldID0gJ2ZpenonDQogICAgZWxpZiBudW0gJSA1ID09IDA6DQogICAgICBudW1iZXJzW2ldID0gJ2J1enonDQoNCmBgYA0KDQpgYGB7cHl0aG9ufQ0KbnVtYmVycyA9IFsxLDIsMyw0LDUsNiw3LDgsOSwxMCwxNSw0NSwgMjIsIDE0LCA2NSwgOTcsIDcyXQ0KZml6el9idXp6KG51bWJlcnMpDQpudW1iZXJzDQpgYGANCkNhbiBhbHNvIHVzZSBgZW51bWVyYXRlKClgIGluc3RlYWQgb2YgcmFuZ2UobGVuKCkpLiBEaWZmZXJlbmNlIGlzIHRoYXQgYGV1bWVyYXRlKClgIHRha2VzIGFuIGl0ZXJhYmxlIHdoaWxlIHJhbmdlKGxlbigpKSB0YWtlcyBhIG51bWJlci4NCg0KYGBge3B5dGhvbn0NCmRlZiBmaXp6X2J1enoobnVtYmVycyk6DQogICcnJw0KICBHaXZlbiBhIGxpc3Qgb2YgaW50ZWdlcnM6DQogIDEuIHJlcGxhY2UgYWxsIGludGVnZXJzIHRoYXQgYXJlIGV2ZW5seSBkaXZpc2libGUgYnkgMyB3aXRoICdmaXp6Jw0KICAyLiByZXBsYWNlIGFsbCBpbnRlZ2VycyBkaXZpc2libGUgYnkgNSB3aXRoICdidXp6Jw0KICAzLiByZXBsYWNlIGFsbCBpbnRlZ2VycyBkaXZpc2libGUgYnkgYm90aCAzIGFuZCA1IHdpdGggJ2ZpenpidXp6Jw0KICANCiAgPj4+IG51bWJlcnMgPSBbNDUsIDIyLCAxNCwgNjUsIDk3LCA3Ml0NCiAgPj4+IGZpenpfYnV6eihudW1iZXJzKQ0KICA+Pj4gbnVtYmVycw0KICBbJ2ZpenpidXp6JywgMjIsIDE0LCAnYnV6eicsIDk3LCAnZml6eiddDQogICcnJw0KICBmb3IgaSwgbnVtIGluIGVudW1lcmF0ZShudW1iZXJzKToNCiAgICBudW0gPSBudW1iZXJzW2ldDQogICAgaWYgbnVtICUgMyA9PSAwIGFuZCBudW0gJSA1ID09IDA6DQogICAgICBudW1iZXJzW2ldID0gJ2ZpenpidXp6Jw0KICAgIGVsaWYgbnVtICUgMyA9PSAwOg0KICAgICAgbnVtYmVyc1tpXSA9ICdmaXp6Jw0KICAgIGVsaWYgbnVtICUgNSA9PSAwOg0KICAgICAgbnVtYmVyc1tpXSA9ICdidXp6Jw0KDQpgYGANCg0KYGBge3B5dGhvbn0NCm51bWJlcnNfMSA9IGxpc3QocmFuZ2UoMSwxMDEpKQ0KZml6el9idXp6KG51bWJlcnNfMSkNCm51bWJlcnNfMQ0KYGBgDQoNCiMgRG9jdGVzdA0KDQpEb2N0ZXN0IGlzIGEgbW9kdWxlIHRoYXQgc2NhbnMgdGhlIERvY3N0cmluZ3Mgb2YgZnVuY3Rpb25zIGxvb2tpbmcgZm9yIGxpbmVzIHRoYXQgbG9vayBsaWtlIGlucHV0IGFuZCBvdXRwdXQgYW5kIHVzZXMgdGhlbSB0byB0ZXN0IHlvdXIgZnVuY3Rpb24uIFRoZSBEb2N0ZXN0IG1vZHVsZSBjYW4gYmUgZWl0aGVyIGFkZGVkIGludG8gdGhlIHNjcmlwdCBvciBydW4gaW4gY29uY2VydCB3aXRoIHRoZSBzY3JpcHQgdXNpbmcgY29tbWFuZCBsaW5lIGFyZ3VtZW50cy4NCg0KIyMgQWRkaW5nIHRvIHRoZSBzY3JpcHQNCg0KYGBge3B5dGhvbiBldmFsPUZBTFNFIH0NCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6DQogICAgaW1wb3J0IGRvY3Rlc3QNCiAgICBkb2N0ZXN0LnRlc3Rtb2QoKQ0KYGBgDQoNCiMjIFJ1bm5pbmcgZnJvbSB0aGUgY29tbWFuZCBsaW5lDQoNCmBweXRob24gLW0gZG9jdGVzdCAtdiB0ZW1wLnB5YA0KDQpUaGUgYC12YCBzd2l0Y2ggd2lsbCBwcmludCB0aGUgdGVzdHMgYW5kIHJlc3VsdHMgdGhhdCAqRG9jdGVzdCogaXMgcnVubmluZy4gV2l0aG91dCB0aGUgc3dpdGNoLCBpdCB3aWxsIHJ1biBidXQgbm90IHByaW50IGFueXRoaW5nIHRvIHRoZSBjb25zb2xlIHVubGVzcyB0aGVyZSBpcyBhbiBlcnJvciBmb3VuZC4NCg0KIyMgRXhhbXBsZXMNCg0KYGBge3B5dGhvbiBldmFsPUZBTFNFfQ0KZGVmIGZhY3RvcmlhbChuKToNCiAgICAiIiJSZXR1cm4gdGhlIGZhY3RvcmlhbCBvZiBuLCBhbiBleGFjdCBpbnRlZ2VyID49IDAuDQoNCiAgICA+Pj4gW2ZhY3RvcmlhbChuKSBmb3IgbiBpbiByYW5nZSg2KV0NCiAgICBbMSwgMSwgMiwgNiwgMjQsIDEyMF0NCiAgICA+Pj4gZmFjdG9yaWFsKDMwKQ0KICAgIDI2NTI1Mjg1OTgxMjE5MTA1ODYzNjMwODQ4MDAwMDAwMA0KICAgID4+PiBmYWN0b3JpYWwoLTEpDQogICAgVHJhY2ViYWNrIChtb3N0IHJlY2VudCBjYWxsIGxhc3QpOg0KICAgICAgICAuLi4NCiAgICBWYWx1ZUVycm9yOiBuIG11c3QgYmUgPj0gMA0KDQogICAgRmFjdG9yaWFscyBvZiBmbG9hdHMgYXJlIE9LLCBidXQgdGhlIGZsb2F0IG11c3QgYmUgYW4gZXhhY3QgaW50ZWdlcjoNCiAgICA+Pj4gZmFjdG9yaWFsKDMwLjEpDQogICAgVHJhY2ViYWNrIChtb3N0IHJlY2VudCBjYWxsIGxhc3QpOg0KICAgICAgICAuLi4NCiAgICBWYWx1ZUVycm9yOiBuIG11c3QgYmUgZXhhY3QgaW50ZWdlcg0KICAgID4+PiBmYWN0b3JpYWwoMzAuMCkNCiAgICAyNjUyNTI4NTk4MTIxOTEwNTg2MzYzMDg0ODAwMDAwMDANCg0KICAgIEl0IG11c3QgYWxzbyBub3QgYmUgcmlkaWN1bG91c2x5IGxhcmdlOg0KICAgID4+PiBmYWN0b3JpYWwoMWUxMDApDQogICAgVHJhY2ViYWNrIChtb3N0IHJlY2VudCBjYWxsIGxhc3QpOg0KICAgICAgICAuLi4NCiAgICBPdmVyZmxvd0Vycm9yOiBuIHRvbyBsYXJnZQ0KICAgICIiIg0KDQogICAgaW1wb3J0IG1hdGgNCiAgICBpZiBub3QgbiA+PSAwOg0KICAgICAgICByYWlzZSBWYWx1ZUVycm9yKCJuIG11c3QgYmUgPj0gMCIpDQogICAgaWYgbWF0aC5mbG9vcihuKSAhPSBuOg0KICAgICAgICByYWlzZSBWYWx1ZUVycm9yKCJuIG11c3QgYmUgZXhhY3QgaW50ZWdlciIpDQogICAgaWYgbisxID09IG46ICAjIGNhdGNoIGEgdmFsdWUgbGlrZSAxZTMwMA0KICAgICAgICByYWlzZSBPdmVyZmxvd0Vycm9yKCJuIHRvbyBsYXJnZSIpDQogICAgcmVzdWx0ID0gMQ0KICAgIGZhY3RvciA9IDINCiAgICB3aGlsZSBmYWN0b3IgPD0gbjoNCiAgICAgICAgcmVzdWx0ICo9IGZhY3Rvcg0KICAgICAgICBmYWN0b3IgKz0gMQ0KICAgIHJldHVybiByZXN1bHQNCg0KDQppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOg0KICAgIGltcG9ydCBkb2N0ZXN0DQogICAgZG9jdGVzdC50ZXN0bW9kKCkNCg0KYGBgDQoNCg0KDQojIERpZmZlcmVuY2VzIGJldHdlZW4gdHdvIGxpc3RzDQoNCldoZW4gY29tcGFyaW5nIHR3byBgbGlzdHNgLCB5b3UgY2FuIGNvbnZlcnQgdGhlbSBpbnRvIGBzZXRzYCB0byBlbGltaW5hdGUgYW55IGR1cGxpY2F0ZSBudW1iZXJzLiBBbHNvLCBsaXN0cyBjYW4gbm90IGJlIG9wZXJhdGVkIG9uIHVzaW5nICctJyBidXQgYHNldHNgIGNhbi4gTGlzdHMgY2FuIGhvd2V2ZXIgdXNlIHRoZSAnKycgd2hpY2gganVzdCBjb21iaW5lcyB0aGUgdHdvIGBsaXN0c2AgdG9nZXRoZXIuDQoNCmBgYHtweXRob259DQpsaXN0MSA9IFsxLDIsMyw0XQ0KbGlzdDIgPSBbMywzLDQsNSw2LDcsN10NCnNldChsaXN0MSkgLSBzZXQobGlzdDIpDQpgYGANCg0KYGBge3B5dGhvbn0NCmxpc3QxID0gWzEsMiwzLDRdDQpsaXN0MiA9IFszLDMsNCw1LDYsNyw3XQ0Kc2V0KGxpc3QyKSAtIHNldChsaXN0MSkNCmBgYA0KDQpgYGB7cHl0aG9ufQ0KZGVmIGRpZmYobGlzdDEsIGxpc3QyKToNCiAgcmV0dXJuIGxpc3Qoc2V0KGxpc3QxKSAtIHNldChsaXN0MikpICsgbGlzdChzZXQobGlzdDIpIC0gc2V0KGxpc3QxKSkNCmBgYA0KDQpgYGB7cHl0aG9ufQ0KbGlzdDEgPSBbMSwyLDMsNF0NCmxpc3QyID0gWzMsMyw0LDUsNiw3LDddDQpkaWZmKGxpc3QxLCBsaXN0MikNCmBgYA0KDQpXaXRob3V0IHVzaW5nIGBzZXQoKWAgd2UgY2FuIHVzZSBsaXN0IGNvbXByZWhlbnNpb24NCg0KYGBge3B5dGhvbn0NCg0KZGVmIGRpZmYyKGxpc3QxLCBsaXN0Mik6DQoJbGlzdF9kaWYgPSBbaSBmb3IgaSBpbiBsaXN0MSArIGxpc3QyIGlmIGkgbm90IGluIGxpc3QxIG9yIGkgbm90IGluIGxpc3QyXQ0KCXJldHVybiBsaXN0X2RpZg0KYGBgDQoNCmBgYHtweXRob259DQpsaXN0MSA9IFsxLDIsMyw0XQ0KbGlzdDIgPSBbMywzLDQsNSw2LDcsN10NCmxpc3QzID0gZGlmZjIobGlzdDEsIGxpc3QyKQ0KcHJpbnQobGlzdDMpDQpgYGANCg0KVGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBgZGlmZigpYCBhbmQgYGRpZmYyKClgIGlzIHRoYXQgaWYgeW91IGRvbid0IGNvbnZlcnQgdGhlIGBsaXN0c2AgdG8gYHNldHNgIHRoZW4geW91IGtlZXAgYm90aCBgNydzYCBpbiBgbGlzdDJgLiANCg0KIyBVbmRlcnNjb3JlICdfJw0KDQpUaGUgdW5kZXJzY29yZSBjYW4gYmUgdXNlZCB0byBpZ25vcmUgYSBzaW5nbGUgdmFsdWUNCg0KYGBge3B5dGhvbn0NCg0KYSwgXywgYiA9ICgxLCAyLCAzKSAjIGEgPSAxLCBiID0gMw0KcHJpbnQoYSwgYikNCmBgYA0KDQpJdCBjYW4gYWxzbyBiZSB1c2VkIHRvIGlnbm9yZSBtdWx0aXBsZSB2YWx1ZXMgdXNpbmcgdGhlICoodmFyaWFibGUpIHVzZWQgdG8gYXNzaWduIG11bHRpcGxlIHZhbHVlIHRvIGEgdmFyaWFibGUgYXMgbGlzdCB3aGlsZSB1bnBhY2tpbmcgaXQncyBjYWxsZWQgIkV4dGVuZGVkIFVucGFja2luZyIsIG9ubHkgYXZhaWxhYmxlIGluIFB5dGhvbiAzLngNCg0KYGBge3B5dGhvbn0NCmEsICpfLCBiID0gKDcsIDYsIDUsIDQsIDMsIDIsIDEpDQpwcmludChhLCBiKQ0KYGBgDQoNCiMgU2VwYXJhdGluZyBEaWdpdHMgT2YgTnVtYmVycw0KSWYgeW91IGhhdmUgYSBsb25nIGRpZ2l0cyBudW1iZXIsIHlvdSBjYW4gc2VwYXJhdGUgdGhlIGdyb3VwIG9mIGRpZ2l0cyBhcyB5b3UgbGlrZSBmb3IgYmV0dGVyIHVuZGVyc3RhbmRpbmcuDQoNCmBtaWxsaW9uID0gMV8wMDBfMDAwYA0KDQojIExpc3QgY29tcHJlaGVuc2lvbg0KDQpgYGB7cHl0aG9ufQ0KbHN0ID0gWzEsMiwtNSw0XQ0KDQpkZWYgc3F1YXJlKHgpOg0KICByZXR1cm4geCp4DQoNCmBgYA0KDQpJbnN0ZWFkIG9mIGRvaW5nIHRoZSBmb2xsb3dpbmc6DQoNCmBgYHtweXRob259DQpsaXN0KG1hcChzcXVhcmUsIGxzdCkpDQpgYGANCg0KWW91IGNhbiB1c2UgbGlzdCBjb21wcmVoZW5zaW9uOg0KYGBge3B5dGhvbn0NCltzcXVhcmUobnVtKSBmb3IgbnVtIGluIGxzdF0NCmBgYA0KDQpgYGB7cHl0aG9ufQ0KZGVmIGlzX29kZCh4KToNCiAgcmV0dXJuIHggJSAyID09IDENCmBgYA0KDQpXZSBjYW4gdXNlIHRoZSBgZmlsdGVyKClgIGZ1bmN0aW9uIHRvIGZpbHRlciBvdXQgZWxlbWVudHMgb2Ygb3VyIGxpc3QgYWNjb3JkaW5nIHRvIGlmIHRoZXkgbWVldCBjZXJ0YWluIGNyaXRlcmlhLg0KDQpgYGB7cHl0aG9ufQ0KbGlzdChmaWx0ZXIoaXNfb2RkLCBsc3QpKQ0KYGBgDQoNCmBgYHtweXRob259DQpbeCBmb3IgeCBpbiBsc3QgaWYgaXNfb2RkKHgpXQ0KYGBgDQpMZXQncyBjcmVhdGUgYSAyeDMgbWF0cml4IGZpbGxlZCB3aXRoIHplcm9zIGFuZCBkZWZpbmUgaXQgdXNpbmcgbGlzdHMuDQoNCmBgYHtweXRob259DQpncmlkID0gW1swLDAsMCwNCiAgICAgICAwLDAsMF1dDQpgYGANClVzaW5nIGBmb3JgIGxvb3ANCg0KYGBge3B5dGhvbn0NCm51bV9yb3dzID0gMg0KbnVtX2NvbHVtbnMgPSAzDQpncmlkID0gW10NCg0KZm9yIF8gaW4gcmFuZ2UobnVtX3Jvd3MpOg0KICBjdXJyX3JvdyA9IFtdDQogIGZvciBfIGluIHJhbmdlKG51bV9jb2x1bW5zKToNCiAgICBjdXJyX3Jvdy5hcHBlbmQoMCkNCiAgZ3JpZC5hcHBlbmQoY3Vycl9yb3cpDQoNCmdyaWQNCmBgYA0KDQpVc2luZyAqbGlzdCBjb21wcmVoZW5zaW9uKg0KDQpgYGB7cHl0aG9ufQ0KbnVtX3Jvd3MgPSAyDQpudW1fY29sdW1ucyA9IDMNCmdyaWQgPSBbXQ0KDQpgYGANCg0KYGBge3B5dGhvbn0NCmdyaWQgPSBbWzAgZm9yIF8gaW4gcmFuZ2UobnVtX2NvbHVtbnMpXSBmb3IgXyBpbiByYW5nZShudW1fcm93cyldDQpncmlkDQpgYGANCkNvbXBhcmUgdGhlIGFib3ZlIHRvIHRoaXMgd2hlcmUgdGhlIGJyYWNrZXRzIGFyZSBpbiBkaWZmZXJlbnQgcGxhY2VzLg0KDQpgYGB7cHl0aG9ufQ0KbnVtX3Jvd3MgPSAyDQpudW1fY29sdW1ucyA9IDMNCmdyaWQgPSBbXQ0KDQpncmlkID0gW1swIGZvciBfIGluIHJhbmdlKG51bV9jb2x1bW5zKSBmb3IgXyBpbiByYW5nZShudW1fcm93cyldXQ0KZ3JpZA0KYGBgDQoNCmBgYA0KW10gPSBsaXN0DQooKSA9IHR1cGxlDQp7fSA9IGRpY3Rpb25hcnkvc2V0DQpgYGANClRoZSBmdW5jdGlvbiBgbWF4KClgIHRha2VzIGluIHNvbWUgbnVtYmVycyBhbmQgcmV0dXJucyB0aGUgbWF4IG51bWJlcg0KDQpgYGB7cHl0aG9ufQ0KTCA9IFsxLDIsMywgLTRdDQp0ID0gKDEsMiwzLCAtNCkNCnMgPSB7MSwyLDMsIC00fQ0KYGBgDQpgYGB7cHl0aG9ufQ0KbWF4KEwpDQptYXgodCkNCm1heChzKQ0KbWF4KDEsMiwzKQ0KbWF4KEwsIGtleT1sYW1iZGEgeDogeCp4KQ0KDQpgYGANCg0KYG1pbigpYCBpcyB0aGUgc2FtZSBhcyBtYXggYnV0IHJldHVybnMgdGhlIG1pbg0KDQpgYW55KClgIHRha2VzIGluIGFuIGl0ZXJhYmxlIGFuZCByZXR1cm5zIGBUcnVlYCBpZiBhbnkgb2YgdGhlIHZhbHVlcyBpbiB0aGUgaXRlcmFibGUgYXJlIGBUcnVlYCBhbmQgcmV0dXJucyBgRmFsc2VgIGlmIG5vbmUgb2YgdGhlIHZhbHVlcyBhcmUgYFRydWVgDQoNCmBgYHtweXRob259DQphbnkoTCkNCmBgYA0KDQpgYGB7cHl0aG9ufQ0KYW55KFtGYWxzZSwgRmFsc2VdKQ0KYGBgDQoNCldlIGNhbid0IHVzZSBgYW55KClgIHRvIHNlZSBpZiBhbnkgaW4gb3VyIGxpc3QgaXMgb2RkIGJlY2F1c2UgaXQgZG9lcyBub3QgdGFrZSBhcmd1bWVudHMgdG8gYGtleWANCg0KYGBge3B5dGhvbiBldmFsPUZBTFNFfQ0KYW55KEwsIGtleT1sYW1iZGEgeDogeCAlIDIgPT0gMSkNCmBgYA0KDQpJbnN0ZWFkLCB3ZSBoYXZlIHRvIHVzZSBsaXN0IGNvbXByZWhlbnNpb24uIFlvdSBjYW4gcGFzcyBpbiBhbiBhcmd1bWVudCAobnVtKSBieSBhZGRpbmcgaXQgYWZ0ZXIgdGhlIGxhbWJkYSBmdW5jdGlvbi4NCg0KYGBge3B5dGhvbn0NClsobGFtYmRhIHg6eCAlIDIgPT0gMSkobnVtKSBmb3IgbnVtIGluIExdDQpgYGANCg0KYGBge3B5dGhvbn0NCmFueShbKGxhbWJkYSB4OnggJSAyID09IDEpKG51bSkgZm9yIG51bSBpbiBMXSkNCmBgYA0KYGFsbCgpYCBpcyB0aGUgc2FtZSBhcyBgYW55KClgIGJ1dCBvbmx5IHJldHVybnMgYFRydWVgIGlmICoqYWxsKiBvZiB0aGUgaXRlbXMgYXJlIGBUcnVlYA0KDQpgYGB7cHl0aG9ufQ0KYWxsKFsobGFtYmRhIHg6eCAlIDIgPT0gMSkobnVtKSBmb3IgbnVtIGluIExdKQ0KYGBgDQoNCiMgRi1zdHJpbmdzIHdoZW4gY3JlYXRpbmcgYSBuZXcgKmNsYXNzKg0KDQpgYGB7cHl0aG9ufQ0KY2xhc3MgQShvYmplY3QpOg0KICBkZWYgX19pbml0X18oc2VsZixuYW1lLGFnZSk6DQogICAgc2VsZi5uYW1lID0gbmFtZQ0KICAgIHNlbGYuYWdlID0gYWdlDQogIGRlZiBfX3JlcHJfXyhzZWxmKToNCiAgICByZXR1cm4gZiIiIg0KICAgICAgTXkgbmFtZSBpcyB7c2VsZi5uYW1lfS4NCiAgICAgIEkgYW0ge3NlbGYuYWdlICsgNX0geWVhcnMgb2xkDQogICAgICAiIiINCg0KYGBgDQoNCmBgYHtweXRob259DQpuYW1lID0gJ0JvYicNCmFnZSA9IDE1DQpwcmludChBKG5hbWUsYWdlKSkNCmBgYA0KYGBge3B5dGhvbn0NCkEoJ05hdGhhbicsIDMxKQ0KYGBgDQoNCiMgU29ydGluZw0KDQpDYW4gc29ydCBhIGxpc3QgYWxwaGFiZXRpY2FsbHkuDQoNCmBgYHtweXRob259DQphbmltYWxzID0gWyJjYXQiLCAiZG9nIiwgImNoZWV0YWgiLCAicmhpbm8iXQ0KDQpzb3J0ZWQoYW5pbWFscykNCnNvcnRlZChhbmltYWxzLCByZXZlcnNlPVRydWUpDQpgYGANCg0KSGVyZSwgd2UgaGF2ZSBhIGxpc3Qgb2YgYW5pbWFscyBkZWZpbmVkIGJ5IGEgZGljdGlvbmFyeS4NCg0KYGBge3B5dGhvbn0NCmFuaW1hbHMgPSBbDQogIHsndHlwZSc6ICdjYXQnLCAnbmFtZSc6ICdTdGVwaGFuaWUnLCAnYWdlJzogOH0sDQogIHsndHlwZSc6ICdkb2cnLCAnbmFtZSc6ICdEZXZvbicsICdhZ2UnOiAzfSwNCiAgeyd0eXBlJzogJ3JoaW5vJywgJ25hbWUnOiAnTW9lJywgJ2FnZSc6IDV9LA0KXQ0KYGBgDQpZb3UgY2FuJ3Qgc29ydCBhIGRpY3Rpb25hcnkgYnV0IHlvdSBjYW4gZGVmaW5lIGEgYGxhbWJkYWAgdG8gc29ydCBieS4NCg0KYGBge3B5dGhvbn0NCnNvcnRlZChhbmltYWxzLCBrZXkgPSBsYW1iZGEgYW5pbWFsOiBhbmltYWxbJ2FnZSddKQ0KYGBgDQoNCklmIHlvdSB3YW50ZWQgdG8gcmV0dXJuIHRoZSBvbGRlc3QgYW5pbWFsLCBwYXNzIGluIHRoZSBgcmV2ZXJzZT1UcnVlYCBwYXJhbWV0ZXIgYW5kIHNsaWNlIGludG8gdGhlIGZpcnN0IGl0ZW0gaW4gdGhlIGRpY3Rpb25hcnkuDQoNCmBgYHtweXRob259DQoNCnNvcnRlZChhbmltYWxzLCBrZXkgPSBsYW1iZGEgYW5pbWFsOiBhbmltYWxbJ2FnZSddLCByZXZlcnNlPVRydWUpWzBdDQpgYGANCg0KWW91IGNhbiBhbHNvIHVzZSB0aGUgYC5zb3J0KClgIG1ldGhvZCwgeW91IGNhbiBkbyB0aGUgc2FtZSB0aGluZyBidXQgbXV0YXRpbmcgdGhlIGRpY3Rpb25hcnkNCg0KYGBge3B5dGhvbn0NCmFuaW1hbHMuc29ydChrZXkgPSBsYW1iZGEgYW5pbWFsOiBhbmltYWxbJ2FnZSddLCByZXZlcnNlPVRydWUpDQphbmltYWxzDQpgYGANCg0KIyBTZXQoKQ0KDQoqIFNldHMgYXJlIHVub3JkZXJlZA0KKiBTZXQgZWxlbWVudHMgYXJlIHVuaXF1ZSAobm8gZHVwbGljYXRlcykNCiogQSBzZXQgbWF5IGJlIG1vZGlmaWVkIGJ1dCB0aGUgZWxlbWVudHMgY29udGFpbmVkIGluIHRoZSBzZXQgbXVzdCBiZSBvZiBhbiBpbW11dGFibGUgdHlwZQ0KDQpgYGB7cHl0aG9ufQ0KcyA9ICdxdXV4Jw0KbGlzdChzKQ0Kc2V0KHMpDQpgYGANCg0KYGBge3B5dGhvbn0NCnggPSB7J2ZvbycsICdiYXInLCAnYmF6JywgJ2ZvbycsICdxdXgnfQ0KeA0KYGBgDQoNCmBgYHtweXRob259DQpzZXQoJ2ZvbycpDQpgYGANCg0KYGBge3B5dGhvbn0NCnggPSB7fQ0KdHlwZSh4KQ0KDQp4ID0gc2V0KCkNCnR5cGUoeCkNCmBgYA0KRG9u4oCZdCBmb3JnZXQgdGhhdCBzZXQgZWxlbWVudHMgbXVzdCBiZSBpbW11dGFibGUuIEZvciBleGFtcGxlLCBhIHR1cGxlIG1heSBiZSBpbmNsdWRlZCBpbiBhIHNldDoNCg0KYGBge3B5dGhvbn0NCnggPSB7NDIsICdmb28nLCAoMSwgMiwgMyksIDMuMTQxNTl9DQp4DQoNCmBgYA0KDQpCdXQgbGlzdHMgYW5kIGRpY3Rpb25hcmllcyBhcmUgbXV0YWJsZSwgc28gdGhleSBjYW7igJl0IGJlIHNldCBlbGVtZW50czoNCg0KYGBge3B5dGhvbiBldmFsPUZBTFNFfQ0KYSA9IFsxLCAyLCAzXQ0Ke2F9DQoNCmQgPSB7J2EnOiAxLCAnYic6IDJ9DQp7ZH0NCg0KYGBgDQoNCmBgYHtweXRob259DQp4ID0geydmb28nLCAnYmFyJywgJ2Jheid9DQoNCmxlbih4KQ0KDQonYmFyJyBpbiB4DQoNCidxdXgnIGluIHgNCmBgYA0KDQpJbiBQeXRob24sIHNldCB1bmlvbiBjYW4gYmUgcGVyZm9ybWVkIHdpdGggdGhlIHwgb3BlcmF0b3I6DQoNCmBgYHtweXRob259DQp4MSA9IHsnZm9vJywgJ2JhcicsICdiYXonfQ0KeDIgPSB7J2JheicsICdxdXgnLCAncXV1eCd9DQp4MSB8IHgyDQpgYGANCg0KU2V0IHVuaW9uIGNhbiBhbHNvIGJlIG9idGFpbmVkIHdpdGggdGhlIC51bmlvbigpIG1ldGhvZC4gVGhlIG1ldGhvZCBpcyBpbnZva2VkIG9uIG9uZSBvZiB0aGUgc2V0cywgYW5kIHRoZSBvdGhlciBpcyBwYXNzZWQgYXMgYW4gYXJndW1lbnQNCg0KYGBge3B5dGhvbn0NCngxLnVuaW9uKHgyKQ0KYGBgDQoNCldoZW4geW91IHVzZSB0aGUgfCBvcGVyYXRvciwgYm90aCAqKm9wZXJhbmRzIG11c3QgYmUgc2V0cyoqLiBUaGUgLnVuaW9uKCkgbWV0aG9kLCBvbiB0aGUgb3RoZXIgaGFuZCwgd2lsbCB0YWtlIGFueSBpdGVyYWJsZSBhcyBhbiBhcmd1bWVudCwgY29udmVydCBpdCB0byBhIHNldCwgYW5kIHRoZW4gcGVyZm9ybSB0aGUgdW5pb24uDQoNCg0KYGBge3B5dGhvbn0NCngxLnVuaW9uKCgnYmF6JywgJ3F1eCcsICdxdXV4JykpDQpgYGANCg0KTW9yZSB0aGFuIHR3byBzZXRzIG1heSBiZSBzcGVjaWZpZWQgd2l0aCBlaXRoZXIgdGhlIG9wZXJhdG9yIG9yIHRoZSBtZXRob2Q6DQoNCmBgYHtweXRob259DQphID0gezEsIDIsIDMsIDR9DQpiID0gezIsIDMsIDQsIDV9DQpjID0gezMsIDQsIDUsIDZ9DQpkID0gezQsIDUsIDYsIDd9DQoNCmEudW5pb24oYiwgYywgZCkNCg0KDQphIHwgYiB8IGMgfCBkDQpgYGANClRoZSByZXN1bHRpbmcgc2V0IGNvbnRhaW5zIG9ubHkgZWxlbWVudHMgdGhhdCBhcmUgcHJlc2VudCBpbiBhbGwgb2YgdGhlIHNwZWNpZmllZCBzZXRzLg0KDQpgYGB7cHl0aG9ufQ0KYSA9IHsxLCAyLCAzLCA0fQ0KYiA9IHsyLCAzLCA0LCA1fQ0KYyA9IHszLCA0LCA1LCA2fQ0KZCA9IHs0LCA1LCA2LCA3fQ0KDQphLmludGVyc2VjdGlvbihiLCBjLCBkKQ0KDQoNCmEgJiBiICYgYyAmIGQNCmBgYA0KDQp4MS5kaWZmZXJlbmNlKHgyKSBhbmQgeDEgLSB4MiByZXR1cm4gdGhlIHNldCBvZiBhbGwgZWxlbWVudHMgdGhhdCBhcmUgaW4geDEgYnV0IG5vdCBpbiB4MjoNCg0KYGBge3B5dGhvbn0NCg0KeDEgPSB7J2ZvbycsICdiYXInLCAnYmF6J30NCngyID0geydiYXonLCAncXV4JywgJ3F1dXgnfQ0KDQp4MS5kaWZmZXJlbmNlKHgyKQ0KDQoNCngxIC0geDINCmBgYA0KDQojIEZyb3plbiBTZXRzDQoNClNyb3plbnNldCwgd2hpY2ggaXMgaW4gYWxsIHJlc3BlY3RzIGV4YWN0bHkgbGlrZSBhIHNldCwgZXhjZXB0IHRoYXQgYSBmcm96ZW5zZXQgaXMgaW1tdXRhYmxlLiBZb3UgY2FuIHBlcmZvcm0gbm9uLW1vZGlmeWluZyBvcGVyYXRpb25zIG9uIGEgZnJvemVuc2V0IGJ1dCBtZXRob2RzIHRoYXQgYXR0ZW1wdCB0byBtb2RpZnkgYSBmcm96ZW5zZXQgZmFpbA0KDQojIEhvdyB0byBVc2UgR2VuZXJhdG9ycyBhbmQgeWllbGQgaW4gUHl0aG9uDQoNCkhhdmUgeW91IGV2ZXIgaGFkIHRvIHdvcmsgd2l0aCBhIGRhdGFzZXQgc28gbGFyZ2UgdGhhdCBpdCBvdmVyd2hlbG1lZCB5b3VyIG1hY2hpbmXigJlzIG1lbW9yeT8gT3IgbWF5YmUgeW91IGhhdmUgYSBjb21wbGV4IGZ1bmN0aW9uIHRoYXQgbmVlZHMgdG8gbWFpbnRhaW4gYW4gaW50ZXJuYWwgc3RhdGUgZXZlcnkgdGltZSBpdOKAmXMgY2FsbGVkLCBidXQgdGhlIGZ1bmN0aW9uIGlzIHRvbyBzbWFsbCB0byBqdXN0aWZ5IGNyZWF0aW5nIGl0cyBvd24gY2xhc3MuIEluIHRoZXNlIGNhc2VzIGFuZCBtb3JlLCBnZW5lcmF0b3JzIGFuZCB0aGUgUHl0aG9uIHlpZWxkIHN0YXRlbWVudCBhcmUgaGVyZSB0byBoZWxwLg0KDQojIyBVc2luZyBHZW5lcmF0b3JzDQoNCkludHJvZHVjZWQgd2l0aCBQRVAgMjU1LCBnZW5lcmF0b3IgZnVuY3Rpb25zIGFyZSBhIHNwZWNpYWwga2luZCBvZiBmdW5jdGlvbiB0aGF0IHJldHVybiBhIGxhenkgaXRlcmF0b3IuIFRoZXNlIGFyZSBvYmplY3RzIHRoYXQgeW91IGNhbiBsb29wIG92ZXIgbGlrZSBhIGxpc3QuIEhvd2V2ZXIsIHVubGlrZSBsaXN0cywgbGF6eSBpdGVyYXRvcnMgZG8gbm90IHN0b3JlIHRoZWlyIGNvbnRlbnRzIGluIG1lbW9yeS4gDQoNClRoaXMgaXMgYSByZWFzb25hYmxlIGV4cGxhbmF0aW9uLCBidXQgd291bGQgdGhpcyBkZXNpZ24gc3RpbGwgd29yayBpZiB0aGUgZmlsZSBpcyB2ZXJ5IGxhcmdlPyBXaGF0IGlmIHRoZSBmaWxlIGlzIGxhcmdlciB0aGFuIHRoZSBtZW1vcnkgeW91IGhhdmUgYXZhaWxhYmxlPyBUbyBhbnN3ZXIgdGhpcyBxdWVzdGlvbiwgbGV04oCZcyBhc3N1bWUgdGhhdCBjc3ZfcmVhZGVyKCkganVzdCBvcGVucyB0aGUgZmlsZSBhbmQgcmVhZHMgaXQgaW50byBhbiBhcnJheToNCg0KYGBge3B5dGhvbiBldmFsPUZBTFNFfQ0KZGVmIGNzdl9yZWFkZXIoZmlsZV9uYW1lKToNCiAgICBmaWxlID0gb3BlbihmaWxlX25hbWUpDQogICAgcmVzdWx0ID0gZmlsZS5yZWFkKCkuc3BsaXQoIlxuIikNCiAgICByZXR1cm4gcmVzdWx0DQpgYGANCg0KDQpUaGlzIGZ1bmN0aW9uIG9wZW5zIGEgZ2l2ZW4gZmlsZSBhbmQgdXNlcyBmaWxlLnJlYWQoKSBhbG9uZyB3aXRoIC5zcGxpdCgpIHRvIGFkZCBlYWNoIGxpbmUgYXMgYSBzZXBhcmF0ZSBlbGVtZW50IHRvIGEgbGlzdC4gSWYgeW91IHdlcmUgdG8gdXNlIHRoaXMgdmVyc2lvbiBvZiBjc3ZfcmVhZGVyKCkgaW4gdGhlIHJvdyBjb3VudGluZyBjb2RlIGJsb2NrIHlvdSBzYXcgZnVydGhlciB1cCwgdGhlbiB5b3XigJlkIGdldCB0aGUgZm9sbG93aW5nIG91dHB1dDogKk1lbW9yeUVycm9yKg0KDQpJbiB0aGlzIGNhc2UsIG9wZW4oKSByZXR1cm5zIGEgZ2VuZXJhdG9yIG9iamVjdCB0aGF0IHlvdSBjYW4gbGF6aWx5IGl0ZXJhdGUgdGhyb3VnaCBsaW5lIGJ5IGxpbmUuIEhvd2V2ZXIsIGZpbGUucmVhZCgpLnNwbGl0KCkgbG9hZHMgZXZlcnl0aGluZyBpbnRvIG1lbW9yeSBhdCBvbmNlLCBjYXVzaW5nIHRoZSAqTWVtb3J5RXJyb3IqLg0KDQpIb3dldmVyIHlvdSBjYW4gdHVybiAqY3N2X3JlYWRlcigpKiBpbnRvIGEgZ2VuZXJhdG9yIGZ1bmN0aW9uOg0KDQpgYGB7cHl0aG9uIGV2YWw9RkFMU0V9DQpkZWYgY3N2X3JlYWRlcihmaWxlX25hbWUpOg0KICAgIGZvciByb3cgaW4gb3BlbihmaWxlX25hbWUsICJyIik6DQogICAgICAgIHlpZWxkIHJvdw0KDQpgYGANCg0KVGhpcyB2ZXJzaW9uIG9wZW5zIGEgZmlsZSwgbG9vcHMgdGhyb3VnaCBlYWNoIGxpbmUsIGFuZCB5aWVsZHMgZWFjaCByb3cgaW5zdGVhZCBvZiByZXR1cm5pbmcgaXQuDQoNCllvdSBjYW4gYWxzbyBkZWZpbmUgYSBnZW5lcmF0b3IgZXhwcmVzc2lvbiAoYWxzbyBjYWxsZWQgYSBnZW5lcmF0b3IgY29tcHJlaGVuc2lvbiksIHdoaWNoIGhhcyBhIHZlcnkgc2ltaWxhciBzeW50YXggdG8gbGlzdCBjb21wcmVoZW5zaW9ucy4gSW4gdGhpcyB3YXksIHlvdSBjYW4gdXNlIHRoZSBnZW5lcmF0b3Igd2l0aG91dCBjYWxsaW5nIGEgZnVuY3Rpb246DQoNCmBgYHtweXRob24gZXZhbD1GQUxTRX0NCmNzdl9nZW4gPSAocm93IGZvciByb3cgaW4gb3BlbihmaWxlX25hbWUpKQ0KYGBgDQoNCiogVXNpbmcgKip5aWVsZCoqIHdpbGwgcmVzdWx0IGluIGEgZ2VuZXJhdG9yIG9iamVjdC4NCiogVXNpbmcgKipyZXR1cm4qKiB3aWxsIHJlc3VsdCBpbiB0aGUgZmlyc3QgbGluZSBvZiB0aGUgZmlsZSBvbmx5Lg0KDQojIyBHZW5lcmF0aW5nIGFuIEluZmluaXRlIFNlcXVlbmNlDQoNCkluIFB5dGhvbiwgdG8gZ2V0IGEgZmluaXRlIHNlcXVlbmNlLCB5b3UgY2FsbCByYW5nZSgpIGFuZCBldmFsdWF0ZSBpdCBpbiBhIGxpc3QgY29udGV4dDoNCg0KYGBge3B5dGhvbn0NCmEgPSByYW5nZSg1KQ0KbGlzdChhKQ0KYGBgDQoNCkdlbmVyYXRpbmcgYW4gaW5maW5pdGUgc2VxdWVuY2UsIGhvd2V2ZXIsIHdpbGwgcmVxdWlyZSB0aGUgdXNlIG9mIGEgZ2VuZXJhdG9yLCBzaW5jZSB5b3VyIGNvbXB1dGVyIG1lbW9yeSBpcyBmaW5pdGU6DQoNCmBgYHtweXRob259DQpkZWYgaW5maW5pdGVfc2VxdWVuY2UoKToNCiAgICBudW0gPSAwDQogICAgd2hpbGUgVHJ1ZToNCiAgICAgICAgeWllbGQgbnVtDQogICAgICAgIG51bSArPSAxDQoNCmBgYA0KDQojIyBEZXRlY3RpbmcgUGFsaW5kcm9tZXMNCg0KDQpgYGB7cHl0aG9ufQ0KZGVmIGlzX3BhbGluZHJvbWUobnVtKToNCiAgICAjIFNraXAgc2luZ2xlLWRpZ2l0IGlucHV0cw0KICAgICMgRmxvb3IgZGl2aXNpb24gcmV0dXJucyBub3QgdGhlIHJlbWFpbmRlciBsaWtlICUgYnV0IHRoZSB3aG9sZSBudW1iZXIuDQogICAgaWYgbnVtIC8vIDEwID09IDA6DQogICAgICAgIHJldHVybiBGYWxzZQ0KICAgIHRlbXAgPSBudW0NCiAgICByZXZlcnNlZF9udW0gPSAwDQoNCiAgICB3aGlsZSB0ZW1wICE9IDA6DQogICAgICAgIHJldmVyc2VkX251bSA9IChyZXZlcnNlZF9udW0gKiAxMCkgKyAodGVtcCAlIDEwKQ0KICAgICAgICB0ZW1wID0gdGVtcCAvLyAxMA0KDQogICAgaWYgbnVtID09IHJldmVyc2VkX251bToNCiAgICAgICAgcmV0dXJuIG51bQ0KICAgIGVsc2U6DQogICAgICAgIHJldHVybiBGYWxzZQ0KYGBgDQoNCkRvbuKAmXQgd29ycnkgdG9vIG11Y2ggYWJvdXQgdW5kZXJzdGFuZGluZyB0aGUgdW5kZXJseWluZyBtYXRoIGluIHRoaXMgY29kZS4gSnVzdCBub3RlIHRoYXQgdGhlIGZ1bmN0aW9uIHRha2VzIGFuIGlucHV0IG51bWJlciwgcmV2ZXJzZXMgaXQsIGFuZCBjaGVja3MgdG8gc2VlIGlmIHRoZSByZXZlcnNlZCBudW1iZXIgaXMgdGhlIHNhbWUgYXMgdGhlIG9yaWdpbmFsLiBOb3cgeW91IGNhbiB1c2UgeW91ciBpbmZpbml0ZSBzZXF1ZW5jZSBnZW5lcmF0b3IgdG8gZ2V0IGEgcnVubmluZyBsaXN0IG9mIGFsbCBudW1lcmljIHBhbGluZHJvbWVzOg0KDQpgYGB7cHl0aG9uIGV2YWw9RkFMU0V9DQpmb3IgaSBpbiBpbmZpbml0ZV9zZXF1ZW5jZSgpOg0KICAgIHBhbCA9IGlzX3BhbGluZHJvbWUoaSkNCiAgICBpZiBwYWw6DQogICAgICAgIHByaW50KHBhbCkNCmBgYA0KDQojIyBVbmRlcnN0YW5kaW5nIEdlbmVyYXRvcnMNCg0KKnlpZWxkKiBpbmRpY2F0ZXMgd2hlcmUgYSB2YWx1ZSBpcyBzZW50IGJhY2sgdG8gdGhlIGNhbGxlciwgYnV0IHVubGlrZSByZXR1cm4sIHlvdSBkb27igJl0IGV4aXQgdGhlIGZ1bmN0aW9uIGFmdGVyd2FyZC4NCg0KSW5zdGVhZCwgdGhlIHN0YXRlIG9mIHRoZSBmdW5jdGlvbiBpcyByZW1lbWJlcmVkLiBUaGF0IHdheSwgd2hlbiBuZXh0KCkgaXMgY2FsbGVkIG9uIGEgZ2VuZXJhdG9yIG9iamVjdCAoZWl0aGVyIGV4cGxpY2l0bHkgb3IgaW1wbGljaXRseSB3aXRoaW4gYSBmb3IgbG9vcCksIHRoZSBwcmV2aW91c2x5IHlpZWxkZWQgdmFyaWFibGUgbnVtIGlzIGluY3JlbWVudGVkLCBhbmQgdGhlbiB5aWVsZGVkIGFnYWluLg0KDQpZb3UgY2FuIGNyZWF0ZSBHZW5lcmF0b3IgRXhwcmVzc2lvbnMgd2l0aG91dCBidWlsZGluZyBhbmQgaG9sZGluZyB0aGUgZW50aXJlIG9iamVjdCBpbiBtZW1vcnkgYmVmb3JlIGl0ZXJhdGlvbi4gSW4gb3RoZXIgd29yZHMsIHlvdeKAmWxsIGhhdmUgbm8gbWVtb3J5IHBlbmFsdHkgd2hlbiB5b3UgdXNlIGdlbmVyYXRvciBleHByZXNzaW9ucy4gVGFrZSB0aGlzIGV4YW1wbGUgb2Ygc3F1YXJpbmcgc29tZSBudW1iZXJzLg0KDQpgYGB7cHl0aG9ufQ0KbnVtc19zcXVhcmVkX2xjID0gW251bSoqMiBmb3IgbnVtIGluIHJhbmdlKDUpXQ0KbnVtc19zcXVhcmVkX2xjDQpudW1zX3NxdWFyZWRfZ2MgPSAobnVtKioyIGZvciBudW0gaW4gcmFuZ2UoNSkpDQpudW1zX3NxdWFyZWRfZ2MNCmBgYA0KDQojIyBQcm9maWxpbmcgR2VuZXJhdG9yIFBlcmZvcm1hbmNlDQoNCllvdSBsZWFybmVkIGVhcmxpZXIgdGhhdCBnZW5lcmF0b3JzIGFyZSBhIGdyZWF0IHdheSB0byBvcHRpbWl6ZSBtZW1vcnkuIFdoaWxlIGFuIGluZmluaXRlIHNlcXVlbmNlIGdlbmVyYXRvciBpcyBhbiBleHRyZW1lIGV4YW1wbGUgb2YgdGhpcyBvcHRpbWl6YXRpb24sIGxldOKAmXMgYW1wIHVwIHRoZSBudW1iZXIgc3F1YXJpbmcgZXhhbXBsZXMgeW91IGp1c3Qgc2F3IGFuZCBpbnNwZWN0IHRoZSBzaXplIG9mIHRoZSByZXN1bHRpbmcgb2JqZWN0cy4gWW91IGNhbiBkbyB0aGlzIHdpdGggYSBjYWxsIHRvIHN5cy5nZXRzaXplb2YoKToNCg0KYGBge3B5dGhvbn0NCmltcG9ydCBzeXMNCm51bXNfc3F1YXJlZF9sYyA9IFtpICogMiBmb3IgaSBpbiByYW5nZSgxMDAwMCldDQpzeXMuZ2V0c2l6ZW9mKG51bXNfc3F1YXJlZF9sYykNCg0KbnVtc19zcXVhcmVkX2djID0gKGkgKiogMiBmb3IgaSBpbiByYW5nZSgxMDAwMCkpDQpwcmludChzeXMuZ2V0c2l6ZW9mKG51bXNfc3F1YXJlZF9nYykpDQpgYGANCg0KVGhlcmUgaXMgb25lIHRoaW5nIHRvIGtlZXAgaW4gbWluZCwgdGhvdWdoLiBJZiB0aGUgbGlzdCBpcyBzbWFsbGVyIHRoYW4gdGhlIHJ1bm5pbmcgbWFjaGluZeKAmXMgYXZhaWxhYmxlIG1lbW9yeSwgdGhlbiBsaXN0IGNvbXByZWhlbnNpb25zIGNhbiBiZSBmYXN0ZXIgdG8gZXZhbHVhdGUgdGhhbiB0aGUgZXF1aXZhbGVudCBnZW5lcmF0b3IgZXhwcmVzc2lvbi4gVG8gZXhwbG9yZSB0aGlzLCBsZXTigJlzIHN1bSBhY3Jvc3MgdGhlIHJlc3VsdHMgZnJvbSB0aGUgdHdvIGNvbXByZWhlbnNpb25zIGFib3ZlLiBZb3UgY2FuIGdlbmVyYXRlIGEgcmVhZG91dCB3aXRoICpjUHJvZmlsZS5ydW4oKToqDQoNCmBgYHtweXRob259DQppbXBvcnQgY1Byb2ZpbGUNCg0KY1Byb2ZpbGUucnVuKCdzdW0oW2kgKiAyIGZvciBpIGluIHJhbmdlKDEwMDAwKV0pJykNCg0KY1Byb2ZpbGUucnVuKCdzdW0oKGkgKiAyIGZvciBpIGluIHJhbmdlKDEwMDAwKSkpJykNCg0KYGBgDQoNCklmIEkgd2Fzbid0IHJ1bm5pbmcgdGhpcyBpbiBSLCB5b3UgY291bGQgc2VlIHRoYXQgc3VtbWluZyBhY3Jvc3MgYWxsIHZhbHVlcyBpbiB0aGUgbGlzdCBjb21wcmVoZW5zaW9uIHRvb2sgYWJvdXQgYSB0aGlyZCBvZiB0aGUgdGltZSBhcyBzdW1taW5nIGFjcm9zcyB0aGUgZ2VuZXJhdG9yLiBJZiBzcGVlZCBpcyBhbiBpc3N1ZSBhbmQgbWVtb3J5IGlzbuKAmXQsIHRoZW4gYSBsaXN0IGNvbXByZWhlbnNpb24gaXMgbGlrZWx5IGEgYmV0dGVyIHRvb2wgZm9yIHRoZSBqb2IuDQoNCiMjIFVuZGVyc3RhbmRpbmcgdGhlIFB5dGhvbiBZaWVsZCBTdGF0ZW1lbnQNCg0KV2hlbiB5b3UgY2FsbCBhIGdlbmVyYXRvciBmdW5jdGlvbiBvciB1c2UgYSBnZW5lcmF0b3IgZXhwcmVzc2lvbiwgeW91IHJldHVybiBhIHNwZWNpYWwgaXRlcmF0b3IgY2FsbGVkIGEgZ2VuZXJhdG9yLiBZb3UgY2FuIGFzc2lnbiB0aGlzIGdlbmVyYXRvciB0byBhIHZhcmlhYmxlIGluIG9yZGVyIHRvIHVzZSBpdC4gV2hlbiB5b3UgY2FsbCBzcGVjaWFsIG1ldGhvZHMgb24gdGhlIGdlbmVyYXRvciwgc3VjaCBhcyBuZXh0KCksIHRoZSBjb2RlIHdpdGhpbiB0aGUgZnVuY3Rpb24gaXMgZXhlY3V0ZWQgdXAgdG8geWllbGQuDQoNCldoZW4gdGhlIFB5dGhvbiB5aWVsZCBzdGF0ZW1lbnQgaXMgaGl0LCB0aGUgcHJvZ3JhbSBzdXNwZW5kcyBmdW5jdGlvbiBleGVjdXRpb24gYW5kIHJldHVybnMgdGhlIHlpZWxkZWQgdmFsdWUgdG8gdGhlIGNhbGxlci4gKEluIGNvbnRyYXN0LCByZXR1cm4gc3RvcHMgZnVuY3Rpb24gZXhlY3V0aW9uIGNvbXBsZXRlbHkuKSBXaGVuIGEgZnVuY3Rpb24gaXMgc3VzcGVuZGVkLCB0aGUgc3RhdGUgb2YgdGhhdCBmdW5jdGlvbiBpcyBzYXZlZC4gVGhpcyBpbmNsdWRlcyBhbnkgdmFyaWFibGUgYmluZGluZ3MgbG9jYWwgdG8gdGhlIGdlbmVyYXRvciwgdGhlIGluc3RydWN0aW9uIHBvaW50ZXIsIHRoZSBpbnRlcm5hbCBzdGFjaywgYW5kIGFueSBleGNlcHRpb24gaGFuZGxpbmcuDQoNClRoaXMgYWxsb3dzIHlvdSB0byByZXN1bWUgZnVuY3Rpb24gZXhlY3V0aW9uIHdoZW5ldmVyIHlvdSBjYWxsIG9uZSBvZiB0aGUgZ2VuZXJhdG9y4oCZcyBtZXRob2RzLiBJbiB0aGlzIHdheSwgYWxsIGZ1bmN0aW9uIGV2YWx1YXRpb24gcGlja3MgYmFjayB1cCByaWdodCBhZnRlciB5aWVsZC4gWW91IGNhbiBzZWUgdGhpcyBpbiBhY3Rpb24gYnkgdXNpbmcgbXVsdGlwbGUgUHl0aG9uIHlpZWxkIHN0YXRlbWVudHM6DQoNCmBgYHtweXRob24gZXZhbD1GQUxTRX0NCmRlZiBtdWx0aV95aWVsZCgpOg0KICAgIHlpZWxkX3N0ciA9ICJUaGlzIHdpbGwgcHJpbnQgdGhlIGZpcnN0IHN0cmluZyINCiAgICB5aWVsZCB5aWVsZF9zdHINCiAgICB5aWVsZF9zdHIgPSAiVGhpcyB3aWxsIHByaW50IHRoZSBzZWNvbmQgc3RyaW5nIg0KICAgIHlpZWxkIHlpZWxkX3N0cg0KDQptdWx0aV9vYmogPSBtdWx0aV95aWVsZCgpDQpwcmludChuZXh0KG11bHRpX29iaikpDQoNCnByaW50KG5leHQobXVsdGlfb2JqKSkNCg0KcHJpbnQobmV4dChtdWx0aV9vYmopKQ0KDQpgYGANClRoZSBmb2xsb3dpbmcgaXMgdGhlIG91dHB1dCwgd2hpY2ggcmV0dXJucyBhICp0cmFjZWJhY2sqIGluIHRoZSBlbmQuDQoNCmBgYHtweXRob24gZXZhbD1GQUxTRX0NCidUaGlzIHdpbGwgcHJpbnQgdGhlIGZpcnN0IHN0cmluZycNCidUaGlzIHdpbGwgcHJpbnQgdGhlIHNlY29uZCBzdHJpbmcnDQoqU3RvcEl0ZXJhdGlvbjoqIA0KYGBgDQoNClRha2UgYSBjbG9zZXIgbG9vayBhdCB0aGF0IGxhc3QgY2FsbCB0byBuZXh0KCkuIFlvdSBjYW4gc2VlIHRoYXQgZXhlY3V0aW9uIGhhcyBibG93biB1cCB3aXRoIGEgdHJhY2ViYWNrLiBUaGlzIGlzIGJlY2F1c2UgZ2VuZXJhdG9ycywgbGlrZSBhbGwgaXRlcmF0b3JzLCBjYW4gYmUgZXhoYXVzdGVkLiBVbmxlc3MgeW91ciBnZW5lcmF0b3IgaXMgaW5maW5pdGUsIHlvdSBjYW4gaXRlcmF0ZSB0aHJvdWdoIGl0IG9uZSB0aW1lIG9ubHkuIE9uY2UgYWxsIHZhbHVlcyBoYXZlIGJlZW4gZXZhbHVhdGVkLCBpdGVyYXRpb24gd2lsbCBzdG9wIGFuZCB0aGUgZm9yIGxvb3Agd2lsbCBleGl0LiBJZiB5b3UgdXNlZCBuZXh0KCksIHRoZW4gaW5zdGVhZCB5b3XigJlsbCBnZXQgYW4gZXhwbGljaXQgU3RvcEl0ZXJhdGlvbiBleGNlcHRpb24uDQoNCiMjIFVzaW5nIEFkdmFuY2VkIEdlbmVyYXRvciBNZXRob2RzDQoNCiogLnNlbmQoKQ0KKiAudGhyb3coKQ0KKiAuY2xvc2UoKQ0KDQojIyBDcmVhdGluZyBEYXRhIFBpcGVsaW5lcyBXaXRoIEdlbmVyYXRvcnMNCg0KRGF0YSBwaXBlbGluZXMgYWxsb3cgeW91IHRvIHN0cmluZyB0b2dldGhlciBjb2RlIHRvIHByb2Nlc3MgbGFyZ2UgZGF0YXNldHMgb3Igc3RyZWFtcyBvZiBkYXRhIHdpdGhvdXQgbWF4aW5nIG91dCB5b3VyIG1hY2hpbmXigJlzIG1lbW9yeS4gSW1hZ2luZSB0aGF0IHlvdSBoYXZlIGEgbGFyZ2UgQ1NWIGZpbGU6DQoNCkxldOKAmXMgdGhpbmsgb2YgYSBzdHJhdGVneToNCg0KMS4gUmVhZCBldmVyeSBsaW5lIG9mIHRoZSBmaWxlLg0KMi4gU3BsaXQgZWFjaCBsaW5lIGludG8gYSBsaXN0IG9mIHZhbHVlcy4NCjMuIEV4dHJhY3QgdGhlIGNvbHVtbiBuYW1lcy4NCjQuIFVzZSB0aGUgY29sdW1uIG5hbWVzIGFuZCBsaXN0cyB0byBjcmVhdGUgYSBkaWN0aW9uYXJ5Lg0KNS4gRmlsdGVyIG91dCB0aGUgcm91bmRzIHlvdSBhcmVu4oCZdCBpbnRlcmVzdGVkIGluLg0KNi4gQ2FsY3VsYXRlIHRoZSB0b3RhbCBhbmQgYXZlcmFnZSB2YWx1ZXMgZm9yIHRoZSByb3VuZHMgeW91IGFyZSBpbnRlcmVzdGVkIGluLg0KDQpOb3JtYWxseSwgeW91IGNhbiBkbyB0aGlzIHdpdGggYSBwYWNrYWdlIGxpa2UgcGFuZGFzLCBidXQgeW91IGNhbiBhbHNvIGFjaGlldmUgdGhpcyBmdW5jdGlvbmFsaXR5IHdpdGgganVzdCBhIGZldyBnZW5lcmF0b3JzLiBZb3XigJlsbCBzdGFydCBieSByZWFkaW5nIGVhY2ggbGluZSBmcm9tIHRoZSBmaWxlIHdpdGggYSBnZW5lcmF0b3IgZXhwcmVzc2lvbjoNCg0KYGBge3B5dGhvbn0NCmZpbGVfbmFtZSA9ICJkYXRhL1RlY2hDcnVuY2hjb250aW5lbnRhbFVTQS5jc3YiDQpsaW5lcyA9IChsaW5lIGZvciBsaW5lIGluIG9wZW4oZmlsZV9uYW1lKSkNCiMgVGhlbiwgeW914oCZbGwgdXNlIGFub3RoZXIgZ2VuZXJhdG9yIGV4cHJlc3Npb24gaW4gY29uY2VydCB3aXRoIHRoZSBwcmV2aW91cyBvbmUgdG8gc3BsaXQgZWFjaCBsaW5lIGludG8gYSBsaXN0Og0KbGlzdF9saW5lID0gKHMucnN0cmlwKCkuc3BsaXQoIiwiKSBmb3IgcyBpbiBsaW5lcykNCmBgYA0KSGVyZSwgeW91IGNyZWF0ZWQgdGhlIGdlbmVyYXRvciBsaXN0X2xpbmUsIHdoaWNoIGl0ZXJhdGVzIHRocm91Z2ggdGhlIGZpcnN0IGdlbmVyYXRvciBsaW5lcy4gVGhpcyBpcyBhIGNvbW1vbiBwYXR0ZXJuIHRvIHVzZSB3aGVuIGRlc2lnbmluZyBnZW5lcmF0b3IgcGlwZWxpbmVzLiBOZXh0LCB5b3XigJlsbCBwdWxsIHRoZSBjb2x1bW4gbmFtZXMgb3V0IG9mIHRlY2hjcnVuY2guY3N2LiBTaW5jZSB0aGUgY29sdW1uIG5hbWVzIHRlbmQgdG8gbWFrZSB1cCB0aGUgZmlyc3QgbGluZSBpbiBhIENTViBmaWxlLCB5b3UgY2FuIGdyYWIgdGhhdCB3aXRoIGEgc2hvcnQgbmV4dCgpIGNhbGw6DQoNCmBgYHtweXRob259DQpjb2xzID0gbmV4dChsaXN0X2xpbmUpDQpgYGANCg0KVG8gc3VtIHRoaXMgdXAsIHlvdSBmaXJzdCBjcmVhdGUgYSBnZW5lcmF0b3IgZXhwcmVzc2lvbiBsaW5lcyB0byB5aWVsZCBlYWNoIGxpbmUgaW4gYSBmaWxlLiBOZXh0LCB5b3UgaXRlcmF0ZSB0aHJvdWdoIHRoYXQgZ2VuZXJhdG9yIHdpdGhpbiB0aGUgZGVmaW5pdGlvbiBvZiBhbm90aGVyIGdlbmVyYXRvciBleHByZXNzaW9uIGNhbGxlZCBsaXN0X2xpbmUsIHdoaWNoIHR1cm5zIGVhY2ggbGluZSBpbnRvIGEgbGlzdCBvZiB2YWx1ZXMuIFRoZW4sIHlvdSBhZHZhbmNlIHRoZSBpdGVyYXRpb24gb2YgbGlzdF9saW5lIGp1c3Qgb25jZSB3aXRoIG5leHQoKSB0byBnZXQgYSBsaXN0IG9mIHRoZSBjb2x1bW4gbmFtZXMgZnJvbSB5b3VyIENTViBmaWxlLg0KDQpUbyBoZWxwIHlvdSBmaWx0ZXIgYW5kIHBlcmZvcm0gb3BlcmF0aW9ucyBvbiB0aGUgZGF0YSwgeW914oCZbGwgY3JlYXRlIGRpY3Rpb25hcmllcyB3aGVyZSB0aGUga2V5cyBhcmUgdGhlIGNvbHVtbiBuYW1lcyBmcm9tIHRoZSBDU1Y6DQoNCmBgYHtweXRob259DQpjb21wYW55X2RpY3RzID0gKGRpY3QoemlwKGNvbHMsIGRhdGEpKSBmb3IgZGF0YSBpbiBsaXN0X2xpbmUpDQpgYGANCg0KVGhpcyBnZW5lcmF0b3IgZXhwcmVzc2lvbiBpdGVyYXRlcyB0aHJvdWdoIHRoZSBsaXN0cyBwcm9kdWNlZCBieSBsaXN0X2xpbmUuIFRoZW4sIGl0IHVzZXMgemlwKCkgYW5kIGRpY3QoKSB0byBjcmVhdGUgdGhlIGRpY3Rpb25hcnkgYXMgc3BlY2lmaWVkIGFib3ZlLiBOb3csIHlvdeKAmWxsIHVzZSBhIGZvdXJ0aCBnZW5lcmF0b3IgdG8gZmlsdGVyIHRoZSBmdW5kaW5nIHJvdW5kIHlvdSB3YW50IGFuZCBwdWxsIHJhaXNlZEFtdCBhcyB3ZWxsOg0KDQpgYGB7cHl0aG9ufQ0KZnVuZGluZyA9ICgNCiAgICBpbnQoY29tcGFueV9kaWN0WyJyYWlzZWRBbXQiXSkNCiAgICBmb3IgY29tcGFueV9kaWN0IGluIGNvbXBhbnlfZGljdHMNCiAgICBpZiBjb21wYW55X2RpY3RbInJvdW5kIl0gPT0gImEiDQopDQpgYGANCkluIHRoaXMgY29kZSBzbmlwcGV0LCB5b3VyIGdlbmVyYXRvciBleHByZXNzaW9uIGl0ZXJhdGVzIHRocm91Z2ggdGhlIHJlc3VsdHMgb2YgY29tcGFueV9kaWN0cyBhbmQgdGFrZXMgdGhlIHJhaXNlZEFtdCBmb3IgYW55IGNvbXBhbnlfZGljdCB3aGVyZSB0aGUgcm91bmQga2V5IGlzICJhIi4NCg0KUmVtZW1iZXIsIHlvdSBhcmVu4oCZdCBpdGVyYXRpbmcgdGhyb3VnaCBhbGwgdGhlc2UgYXQgb25jZSBpbiB0aGUgZ2VuZXJhdG9yIGV4cHJlc3Npb24uIEluIGZhY3QsIHlvdSBhcmVu4oCZdCBpdGVyYXRpbmcgdGhyb3VnaCBhbnl0aGluZyB1bnRpbCB5b3UgYWN0dWFsbHkgdXNlIGEgZm9yIGxvb3Agb3IgYSBmdW5jdGlvbiB0aGF0IHdvcmtzIG9uIGl0ZXJhYmxlcywgbGlrZSBzdW0oKS4gSW4gZmFjdCwgY2FsbCBzdW0oKSBub3cgdG8gaXRlcmF0ZSB0aHJvdWdoIHRoZSBnZW5lcmF0b3JzOg0KDQpgYGB7cHl0aG9ufQ0KdG90YWxfc2VyaWVzX2EgPSBzdW0oZnVuZGluZykNCnByaW50KGYiVG90YWwgc2VyaWVzIEEgZnVuZHJhaXNpbmc6ICR7dG90YWxfc2VyaWVzX2F9IikNCmBgYA0KDQpgYGB7cHl0aG9uIGV2YWw9RkFMU0V9DQpmaWxlX25hbWUgPSAidGVjaGNydW5jaC5jc3YiDQpsaW5lcyA9IChsaW5lIGZvciBsaW5lIGluIG9wZW4oZmlsZV9uYW1lKSkNCmxpc3RfbGluZSA9IChzLnJzdHJpcCgpc3BsaXQoIiwiKSBmb3IgcyBpbiBsaW5lcykNCmNvbHMgPSBuZXh0KGxpc3RfbGluZSkNCmNvbXBhbnlfZGljdHMgPSAoZGljdCh6aXAoY29scywgZGF0YSkpIGZvciBkYXRhIGluIGxpc3RfbGluZSkNCmZ1bmRpbmcgPSAoDQogICAgaW50KGNvbXBhbnlfZGljdFsicmFpc2VkQW10Il0pDQogICAgZm9yIGNvbXBhbnlfZGljdCBpbiBjb21wYW55X2RpY3RzDQogICAgaWYgY29tcGFueV9kaWN0WyJyb3VuZCJdID09ICJhIg0KKQ0KdG90YWxfc2VyaWVzX2EgPSBzdW0oZnVuZGluZykNCnByaW50KGYiVG90YWwgc2VyaWVzIEEgZnVuZHJhaXNpbmc6ICR7dG90YWxfc2VyaWVzX2F9IikNCmBgYA0KDQpQdXR0aW5nIHRoaXMgYWxsIHRvZ2V0aGVyLCB5b3XigJlsbCBwcm9kdWNlIHRoZSBmb2xsb3dpbmcgc2NyaXB0Og0KDQpUaGlzIHNjcmlwdCBwdWxscyB0b2dldGhlciBldmVyeSBnZW5lcmF0b3IgeW914oCZdmUgYnVpbHQsIGFuZCB0aGV5IGFsbCBmdW5jdGlvbiBhcyBvbmUgYmlnIGRhdGEgcGlwZWxpbmUuIEhlcmXigJlzIGEgbGluZSBieSBsaW5lIGJyZWFrZG93bjoNCg0KKiBMaW5lIDIgcmVhZHMgaW4gZWFjaCBsaW5lIG9mIHRoZSBmaWxlLg0KKiBMaW5lIDMgc3BsaXRzIGVhY2ggbGluZSBpbnRvIHZhbHVlcyBhbmQgcHV0cyB0aGUgdmFsdWVzIGludG8gYSBsaXN0Lg0KKiBMaW5lIDQgdXNlcyBuZXh0KCkgdG8gc3RvcmUgdGhlIGNvbHVtbiBuYW1lcyBpbiBhIGxpc3QuDQoqIExpbmUgNSBjcmVhdGVzIGRpY3Rpb25hcmllcyBhbmQgdW5pdGVzIHRoZW0gd2l0aCBhIHppcCgpIGNhbGw6DQoqIFRoZSBrZXlzIGFyZSB0aGUgY29sdW1uIG5hbWVzIGNvbHMgZnJvbSBsaW5lIDQuDQoqIFRoZSB2YWx1ZXMgYXJlIHRoZSByb3dzIGluIGxpc3QgZm9ybSwgY3JlYXRlZCBpbiBsaW5lIDMuDQoqIExpbmUgNiBnZXRzIGVhY2ggY29tcGFueeKAmXMgc2VyaWVzIEEgZnVuZGluZyBhbW91bnRzLiBJdCBhbHNvIGZpbHRlcnMgb3V0IGFueSBvdGhlciByYWlzZWQgYW1vdW50Lg0KKiBMaW5lIDExIGJlZ2lucyB0aGUgaXRlcmF0aW9uIHByb2Nlc3MgYnkgY2FsbGluZyBzdW0oKSB0byBnZXQgdGhlIHRvdGFsIGFtb3VudCBvZiBzZXJpZXMgQSBmdW5kaW5nIGZvdW5kIGluIHRoZSBDU1YuDQoNCldoZW4geW91IHJ1biB0aGlzIGNvZGUgb24gdGVjaGNydW5jaC5jc3YsIHlvdSBzaG91bGQgZmluZCBhIHRvdGFsIG9mICQ0LDM3NiwwMTUsMDAwIHJhaXNlZCBpbiBzZXJpZXMgQSBmdW5kaW5nIHJvdW5kcy4NCg0KTm90ZTogVGhlIG1ldGhvZHMgZm9yIGhhbmRsaW5nIENTViBmaWxlcyBkZXZlbG9wZWQgaW4gdGhpcyB0dXRvcmlhbCBhcmUgaW1wb3J0YW50IGZvciB1bmRlcnN0YW5kaW5nIGhvdyB0byB1c2UgZ2VuZXJhdG9ycyBhbmQgdGhlIFB5dGhvbiB5aWVsZCBzdGF0ZW1lbnQuIEhvd2V2ZXIsIHdoZW4geW91IHdvcmsgd2l0aCBDU1YgZmlsZXMgaW4gUHl0aG9uLCB5b3Ugc2hvdWxkIGluc3RlYWQgdXNlIHRoZSBjc3YgbW9kdWxlIGluY2x1ZGVkIGluIFB5dGhvbuKAmXMgc3RhbmRhcmQgbGlicmFyeS4gVGhpcyBtb2R1bGUgaGFzIG9wdGltaXplZCBtZXRob2RzIGZvciBoYW5kbGluZyBDU1YgZmlsZXMgZWZmaWNpZW50bHkuDQoNCiMjIFVzaW5nIHppcCgpIGluIFB5dGhvbg0KUHl0aG9u4oCZcyB6aXAoKSBmdW5jdGlvbiBpcyBkZWZpbmVkIGFzIHppcCgqaXRlcmFibGVzKS4gVGhlIGZ1bmN0aW9uIHRha2VzIGluIGl0ZXJhYmxlcyBhcyBhcmd1bWVudHMgYW5kIHJldHVybnMgYW4gaXRlcmF0b3IuIFRoaXMgaXRlcmF0b3IgZ2VuZXJhdGVzIGEgc2VyaWVzIG9mIHR1cGxlcyBjb250YWluaW5nIGVsZW1lbnRzIGZyb20gZWFjaCBpdGVyYWJsZS4gemlwKCkgY2FuIGFjY2VwdCBhbnkgdHlwZSBvZiBpdGVyYWJsZSwgc3VjaCBhcyBmaWxlcywgbGlzdHMsIHR1cGxlcywgZGljdGlvbmFyaWVzLCBzZXRzLCBhbmQgc28gb24uDQoNCiMjIyBQYXNzaW5nIG4gQXJndW1lbnRzDQoNCklmIHlvdSB1c2UgemlwKCkgd2l0aCBuIGFyZ3VtZW50cywgdGhlbiB0aGUgZnVuY3Rpb24gd2lsbCByZXR1cm4gYW4gaXRlcmF0b3IgdGhhdCBnZW5lcmF0ZXMgdHVwbGVzIG9mIGxlbmd0aCBuLiBUbyBzZWUgdGhpcyBpbiBhY3Rpb24sIHRha2UgYSBsb29rIGF0IHRoZSBmb2xsb3dpbmcgY29kZSBibG9jazoNCg0KYGBge3B5dGhvbn0NCm51bWJlcnMgPSBbMSwgMiwgM10NCmxldHRlcnMgPSBbJ2EnLCAnYicsICdjJ10NCnppcHBlZCA9IHppcChudW1iZXJzLCBsZXR0ZXJzKQ0KemlwcGVkICAjIEhvbGRzIGFuIGl0ZXJhdG9yIG9iamVjdA0KDQp0eXBlKHppcHBlZCkNCg0KbGlzdCh6aXBwZWQpDQpgYGANCg0KDQpIZXJlLCB5b3UgdXNlIHppcChudW1iZXJzLCBsZXR0ZXJzKSB0byBjcmVhdGUgYW4gaXRlcmF0b3IgdGhhdCBwcm9kdWNlcyB0dXBsZXMgb2YgdGhlIGZvcm0gKHgsIHkpLiBJbiB0aGlzIGNhc2UsIHRoZSB4IHZhbHVlcyBhcmUgdGFrZW4gZnJvbSBudW1iZXJzIGFuZCB0aGUgeSB2YWx1ZXMgYXJlIHRha2VuIGZyb20gbGV0dGVycy4gTm90aWNlIGhvdyB0aGUgUHl0aG9uIHppcCgpIGZ1bmN0aW9uIHJldHVybnMgYW4gaXRlcmF0b3IuIFRvIHJldHJpZXZlIHRoZSBmaW5hbCBsaXN0IG9iamVjdCwgeW91IG5lZWQgdG8gdXNlIGxpc3QoKSB0byBjb25zdW1lIHRoZSBpdGVyYXRvci4NCg0KSWYgeW914oCZcmUgd29ya2luZyB3aXRoIHNlcXVlbmNlcyBsaWtlIGxpc3RzLCB0dXBsZXMsIG9yIHN0cmluZ3MsIHRoZW4geW91ciBpdGVyYWJsZXMgYXJlIGd1YXJhbnRlZWQgdG8gYmUgZXZhbHVhdGVkIGZyb20gbGVmdCB0byByaWdodC4gVGhpcyBtZWFucyB0aGF0IHRoZSByZXN1bHRpbmcgbGlzdCBvZiB0dXBsZXMgd2lsbCB0YWtlIHRoZSBmb3JtIFsobnVtYmVyc1swXSwgbGV0dGVyc1swXSksIChudW1iZXJzWzFdLCBsZXR0ZXJzWzFdKSwuLi4sIChudW1iZXJzW25dLCBsZXR0ZXJzW25dKV0uIEhvd2V2ZXIsIGZvciBvdGhlciB0eXBlcyBvZiBpdGVyYWJsZXMgKGxpa2Ugc2V0cyksIHlvdSBtaWdodCBzZWUgc29tZSB3ZWlyZCByZXN1bHRzOg0KDQpgYGB7cHl0aG9ufQ0KczEgPSB7MiwgMywgMX0NCnMyID0geydiJywgJ2EnLCAnYyd9DQpsaXN0KHppcChzMSwgczIpKQ0KYGBgDQoNCiMgV2hlbiB0byB1c2UgbWVkaWFuDQoNCkJ1dCB0aGVuLCB3aGF0IGFyZSB0aGUgYWR2YW50YWdlcyBvZiB0aGUgbWVkaWFuPw0KVG8gaWxsdXN0cmF0ZSB0aGlzLCB3ZSByZXR1cm4gdG8gdGhlIGZpdmUgc3lzdG9saWMgYmxvb2QgcHJlc3N1cmUgdmFsdWVzIHVzZWQgYmVmb3JlOg0KDQpgMTQyLCAxMjQsIDEyMSwgMTUxLCAxMzIuYA0KDQpXZSBhc3N1bWUgdGhhdCAxNTEgaXMgYSBjb3JyZWN0IHZhbHVlLCBidXQgdGhhdCBhIGRldmljZSBmYWlsdXJlIGxlYWRzIHRvIHRoZSBmYWxzZSBtZWFzdXJlbWVudCBvZiAxNzEuIExldOKAmXMgc2VlIHdoYXQgaGFwcGVucyB0byBtZWFuIGFuZCBtZWRpYW4/DQoNClRoZSBtZWFuIG9mIHRoZSByZXN1bHRpbmcgZml2ZSB2YWx1ZXMgbm93IGlzIDEzOCBpbnN0ZWFkIG9mIDEzNCwgYXMgY2FsY3VsYXRlZCBmcm9tIHRoZSBvcmlnaW5hbCBkYXRhLCB0aHVzIHNob3dpbmcgYSBjb25zaWRlcmFibGUgZWZmZWN0IG9mIHRoZSBpbmNvcnJlY3QgbWVhc3VyZW1lbnQuDQoNClRvIGRlcml2ZSB0aGUgbWVkaWFuLCB3ZSBzb3J0IHRoZSBkYXRhIGFnYWluIGJ5IHNpemU6DQoNCmAxMjEsIDEyNCwgMTMyLCAxNDIsIDE3MS5gDQoNCkFzIGJlZm9yZSwgdGhlIHZhbHVlIDEzMiBpcyBpbiB0aGUgY2VudGVyIG9mIHRoZSBkYXRhIHJvdywgc28gdGhlIG1lZGlhbiBhY3R1YWxseSBpcyB1bmFsdGVyZWQgYnkgdGhlIGZhbHNlIG1lYXN1cmVtZW50Lg0KDQpUaGF0IGlzIHdoeSB0aGUgbWVkaWFuIGlzIGNhbGxlZCDigJxyb2J1c3QgYWdhaW5zdCBvdXRsaWVyc+KAnCwgd2hlcmVhcyB0aGUgbWVhbiBhY3R1YWxseSBpcyDigJxzZW5zaXRpdmUgdG8gb3V0bGllcnPigJwuDQoNCg==