Association Rule Mining Study Guide
Overview of Association Analysis
Association analysis examines relationships between items in a
dataset to determine patterns of co-occurrence. In association rule
mining, these patterns are used to generate rules that
predict when certain items will appear together.
For example, in a retail setting, this analysis might reveal that “if
a customer buys bread and milk, they are likely to also buy eggs.”
Key Concepts
- Association Rule Mining:
- Definition: Given a set of transactions,
association rule mining seeks to find patterns or rules that explain
when items co-occur in transactions.
- Applications: Common in recommendation systems
(e.g., Amazon’s “Customers who bought this also bought”) and in
healthcare data (e.g., high blood pressure often co-occurs with
stress).
1. Association Rule Mining Recap
Association rule mining aims to uncover relationships between
variables in transaction data. Each transaction is a collection of
items, and the objective is to determine if certain items frequently
co-occur. These relationships can be represented as association
rules of the form \(X \rightarrow
Y\), where \(X\) implies \(Y\) with a certain degree of
confidence and support.
- Frequent Itemsets:
- Definition: Collections of one or more items that
appear frequently together in the dataset. For example,
{bread, milk}
could be a frequent itemset.
- Support Count: The frequency of occurrence of an
itemset within the dataset.
- Support: The proportion of transactions that
contain the itemset, calculated as: \[
\text{Support} = \frac{\text{Support Count}}{\text{Total Transactions}}
\]
2. Frequent Itemset Generation
Generating frequent itemsets involves identifying groups of items
that often appear together in transactions. The candidate
itemset lattice is used to systematically evaluate itemset
combinations, starting from single items and progressing to larger
groups. Key to reducing computational load is the Apriori
principle, which helps to avoid evaluating all possible
combinations by eliminating itemsets that do not meet the minimum
support threshold.
Example: For items like
{milk, bread, diapers}
, frequent itemsets are those that
appear in a sufficient number of transactions. Calculating support for
itemsets and generating candidate lattices enables structured
exploration of co-occurrence relationships.
- Association Rules:
- Definition: Expressed as \(X \rightarrow Y\), where \(X\) and \(Y\) are itemsets, meaning “If \(X\) occurs, \(Y\) is likely to occur.”
- Support for a Rule: The fraction of transactions
that contain both \(X\) and \(Y\).
- Confidence for a Rule: Indicates how often \(Y\) appears in transactions that contain
\(X\), given by: \[
\text{Confidence}(X \rightarrow Y) = \frac{\text{Support of } X \cup
Y}{\text{Support of } X}
\]
Rule Evaluation Metrics
- Support: Helps in identifying the overall frequency
of an itemset.
- Confidence: Measures the reliability of an
inference; higher confidence indicates a stronger association.
- Lift (optional): Measures how much more likely
\(Y\) is to occur when \(X\) is present compared to when \(X\) is absent.
3. Rule Generation
Once frequent itemsets are identified, the next step is creating
association rules from these sets, ensuring each rule meets minimum
support and confidence requirements. These rules are derived from
itemsets by dividing them into antecedents (if-part) and consequents
(then-part), with metrics calculated for each rule.
Rule Metrics: - Support: Measures
how often \(X \cup Y\) appears in the
dataset. - Confidence: Measures the likelihood of \(Y\) appearing when \(X\) is present, calculated as: \[
\text{Confidence}(X \rightarrow Y) = \frac{\text{Support of } X \cup
Y}{\text{Support of } X}
\] - Lift: Evaluates the strength of an
association rule relative to random chance, providing insight into the
rule’s significance.
Steps in Association Rule Mining
- Frequent Itemset Generation:
- Generate all itemsets that meet a minimum support threshold, a
process that is computationally intensive.
- Rule Generation:
- Use the frequent itemsets to create association rules, ensuring they
meet both minimum support and confidence thresholds.
- Optimizing Computation:
- Candidate Lattice: Visualizes all potential
itemsets for frequent set mining.
- Apriori Principle: If an itemset is infrequent, all
supersets are also infrequent, helping to reduce computation by
pruning.
Example Calculations
Suppose a dataset has 5 transactions, and
{milk, bread, diapers}
appears in 2 of them: -
Support Count for {milk, bread, diapers}
:
2 - Support for {milk, bread, diapers}
:
\[
\text{Support} = \frac{2}{5} = 0.4
\]
If {milk, diapers}
appears in 3 transactions, the
confidence that
{milk, diapers} \rightarrow beer
is: \[
\text{Confidence} = \frac{\text{Support of } \{milk, diapers,
beer\}}{\text{Support of } \{milk, diapers\}}
\]
Python Code for Support and Confidence Calculation
from itertools import combinations
# Example transaction data
transactions = [
{'milk', 'bread', 'beer'},
{'milk', 'diapers', 'beer'},
{'milk', 'bread', 'diapers'},
{'bread', 'diapers', 'beer'},
{'milk', 'bread'}
]
def support(itemset, transactions):
count = sum(1 for transaction in transactions if itemset.issubset(transaction))
return count / len(transactions)
def confidence(itemset_X, itemset_Y, transactions):
return support(itemset_X | itemset_Y, transactions) / support(itemset_X, transactions)
# Calculate support and confidence for a rule
itemset_X = {'milk', 'diapers'}
itemset_Y = {'beer'}
rule_support = support(itemset_X | itemset_Y, transactions)
rule_confidence = confidence(itemset_X, itemset_Y, transactions)
print("Support:", rule_support)
print("Confidence:", rule_confidence)
Visualizing the Candidate Lattice
To visualize a candidate lattice and optimize
computation: 1. List items in a hierarchy (e.g., single items, pairs,
triples). 2. Use the Apriori Principle to prune
infrequent itemsets.
import networkx as nx
import matplotlib.pyplot as plt
# Example of candidate lattice visualization
G = nx.DiGraph()
G.add_edges_from([
("A", "AB"), ("A", "AC"), ("B", "AB"),
("B", "BC"), ("C", "AC"), ("C", "BC"),
("AB", "ABC"), ("AC", "ABC"), ("BC", "ABC")
])
plt.figure(figsize=(8, 6))
nx.draw(G, with_labels=True, node_size=3000, node_color="lightblue", font_size=10, font_weight="bold")
plt.show()
Summary
- Association rule mining finds co-occurrence
patterns in transaction data.
- Support and confidence help
quantify how strongly items are associated.
- Apriori Principle and candidate
lattice reduce computational load, enabling scalable
analysis.
4. Efficient Computation with the Apriori Principle
The Apriori algorithm optimizes frequent itemset generation by
eliminating non-frequent itemsets and their supersets early in the
process. This reduces the search space and makes rule generation
computationally feasible for large datasets, often visualized as a
candidate lattice to track the hierarchical progression
of item combinations.
5. Implementation Example in Python
To compute support and confidence for specific rules in Python:
from itertools import combinations
# Example transactions
transactions = [
{'milk', 'bread', 'beer'},
{'milk', 'diapers', 'beer'},
{'milk', 'bread', 'diapers'},
{'bread', 'diapers', 'beer'},
{'milk', 'bread'}
]
# Function to calculate support
def support(itemset, transactions):
count = sum(1 for transaction in transactions if itemset.issubset(transaction))
return count / len(transactions)
# Function to calculate confidence
def confidence(itemset_X, itemset_Y, transactions):
return support(itemset_X | itemset_Y, transactions) / support(itemset_X, transactions)
# Calculate support and confidence for a rule
itemset_X = {'milk', 'diapers'}
itemset_Y = {'beer'}
rule_support = support(itemset_X | itemset_Y, transactions)
rule_confidence = confidence(itemset_X, itemset_Y, transactions)
print("Support:", rule_support)
print("Confidence:", rule_confidence)
This code provides a structured way to calculate support and
confidence for a rule such as {milk, diapers} → beer
,
helping identify meaningful associations in transaction data.
6. Visualizing Candidate Lattices
The candidate lattice organizes itemsets by their frequency, aiding
in the application of the Apriori principle. Visualizing it with
NetworkX:
import networkx as nx
import matplotlib.pyplot as plt
G = nx.DiGraph()
G.add_edges_from([
("A", "AB"), ("A", "AC"), ("B", "AB"),
("B", "BC"), ("C", "AC"), ("C", "BC"),
("AB", "ABC"), ("AC", "ABC"), ("BC", "ABC")
])
plt.figure(figsize=(8, 6))
nx.draw(G, with_labels=True, node_size=3000, font_size=10, font_weight="bold")
plt.show()
Recap
Association rule mining, supported by efficient itemset generation
through the Apriori principle and candidate lattices, enables scalable
analysis of transactional data, revealing patterns with practical
applications across industries.
These concepts can be used to identify valuable patterns that can be
applied in business, healthcare, and other domains requiring
co-occurrence analysis.
Quiz 1:
The Apriori principle states that if an itemset is
infrequent, any larger itemsets containing it as a subset must also be
infrequent. Since we know that ACE is infrequent, only
those itemsets that contain ACE as a subset can
immediately be concluded to be infrequent.
Here’s how this reasoning applies to each option:
- ABCE and ABCDE:
- These contain ACE as a subset, but since we are
considering infrequency, we are not focusing on sets that mix additional
items beyond ACE with B and
D unnecessarily.
- ACDE:
- This itemset contains ACE as a subset (with the
addition of D) and thus directly follows from the
Apriori principle. If ACE is infrequent,
ACDE must also be infrequent because it includes all
items of ACE plus an additional item,
D.
- BCDE only:
- This itemset does not contain ACE as a subset, so
we cannot conclude anything about its frequency based on the infrequency
of ACE alone.
Therefore, the correct answer is ACDE only because
it is the only itemset in the choices provided that includes
ACE as a subset, making it directly inferrable as
infrequent based on the Apriori principle.
Frequent Itemset Generation:
This diagram shows a candidate lattice for itemsets where
AB has been found to be infrequent. According to the
Apriori principle, any supersets of an infrequent
itemset are also infrequent.
AB is infrequent, so all itemsets containing
AB (like ABC, ABD,
ABE, ABCD, ABCE,
ABDE, and ABCDE) are pruned (marked
with the pink dashed line) and considered infrequent without further
checking.
The itemset ACDE (highlighted in yellow) does
not include AB as a subset, so it is not affected by
the infrequency of AB. This means ACDE
is evaluated separately from AB and its
supersets.
So, Only supersets containing AB are immediately
pruned. - ACDE remains unaffected because it does not
contain AB.
Continuing
Here’s an enhanced study guide for
Association Rule Mining and Frequent Itemset
Generation, including additional context from the latest
file.
Study Guide: Association Rule Mining and Frequent Itemset
Generation
1. Overview of Association Rule Mining
- Purpose: Identify patterns in large datasets to
reveal relationships between items. Applications include market
basket analysis and recommendation
systems.
- Example: Retail transactions may show that buying
milk and bread often correlates with purchasing eggs, aiding inventory
management and marketing strategies.
Key Definitions:
Association Rule: A rule showing when one item
or itemset co-occurs with another.
Frequent Itemset: A set of items that appears
frequently together in a dataset.
Support: Proportion of transactions containing
an itemset. Formula: \[
\text{Support}(X) = \frac{\text{Frequency of } X}{\text{Total
Transactions}}
\]
Confidence: Reliability of an association rule,
showing how often \(Y\) appears in
transactions containing \(X\). Formula:
\[
\text{Confidence}(X \rightarrow Y) = \frac{\text{Support}(X \cup
Y)}{\text{Support}(X)}
\]
Objective: Association analysis uncovers
patterns in transaction data, often formulated as association
rules \(X \rightarrow Y\),
where the presence of \(X\) suggests
the likelihood of \(Y\).
Applications: Widely used in retail (e.g.,
market basket analysis), healthcare (co-occurrence of symptoms), and
finance (transaction pattern analysis).
Key Metrics:
- Support: Frequency of an itemset’s occurrence in
transactions. \[
\text{Support}(X) = \frac{\text{Transactions containing } X}{\text{Total
Transactions}}
\]
- Confidence: Likelihood that \(Y\) occurs given \(X\). \[
\text{Confidence}(X \rightarrow Y) = \frac{\text{Support}(X \cup
Y)}{\text{Support}(X)}
\]
- Lift: Strength of association, comparing observed
to expected co-occurrence. \[
\text{Lift}(X \rightarrow Y) = \frac{\text{Confidence}(X \rightarrow
Y)}{\text{Support}(Y)}
\]
2. Frequent Itemset Generation Using the Apriori
Algorithm
The Apriori Algorithm iteratively identifies
frequent itemsets by leveraging smaller, frequent subsets.
- Candidate Itemsets: Start with single frequent
items, and combine them iteratively to generate larger itemsets.
- Apriori Principle: If an itemset is infrequent, all
supersets are also infrequent, reducing search space.
- Support Count: Count occurrences of each candidate
itemset, pruning those below the minimum support threshold.
- Iterative Pruning: Repeat the process for
k-itemsets until no further frequent itemsets remain.
3. Steps in Association Rule Mining
Step 1: Frequent Itemset Generation
- Objective: Identify itemsets meeting minimum
support thresholds.
- Candidate Lattice: Structures possible itemsets in
levels (individual items, pairs, triplets) to filter based on
support.
Step 2: Rule Generation from Frequent Itemsets
- Use frequent itemsets to generate “If-Then” association rules,
applying minimum confidence and support to retain significant
rules.
4. Efficiency Improvements in Itemset
Generation
A. Apriori Principle:
- Concept: If an itemset is infrequent, all its
supersets are also infrequent.
- Application: If
{ACE}
is infrequent,
then all supersets {ABCE}
, {ACDE}
, and
{ABCDE}
can be skipped.
B. FP-Growth (Frequent Pattern Growth):
- An efficient alternative to Apriori using an
FP-tree structure.
- FP-tree organizes transactions in two passes, enabling frequent
itemset mining without explicitly generating candidate sets.
5. Frequent Itemset Generation with Apriori
The Apriori Algorithm:
- Step-by-Step:
- Start with k = 1 for single items and find frequent itemsets.
- Generate itemsets of length \(k+1\)
from frequent \(k\)-itemsets.
- Prune infrequent sets and repeat until no more frequent itemsets are
found.
- Complexity: Each iteration over the dataset
(especially for large datasets) increases computational costs,
motivating the use of efficient data structures and pruning
methods.
6. Reducing Complexity with Hash Trees
- Hash Tree: Used in Apriori to reduce comparison
costs by organizing itemsets efficiently, checking only relevant
candidates per transaction.
- Functionality: Divides items based on hash values
to minimize comparison time.
- Example: Given three-item candidate sets, the hash
tree filters based on the first item’s hash and splits nodes as needed
to ensure optimal access time.
7. Complexity Reduction in Apriori
- Candidate Pruning: By applying the Apriori
principle, avoid testing itemsets that include infrequent subsets.
- Hash Trees: Hash trees store candidate itemsets for
efficient lookups, reducing the comparison load when calculating
support.
Code Example for Support Calculation
from itertools import combinations
# Example transaction data
transactions = [
{'milk', 'bread', 'beer'},
{'milk', 'diapers', 'beer'},
{'milk', 'bread', 'diapers'},
{'bread', 'diapers', 'beer'},
{'milk', 'bread'}
]
def support(itemset, transactions):
count = sum(1 for transaction in transactions if itemset.issubset(transaction))
return count / len(transactions)
# Calculating support for {milk, bread}
itemset = {'milk', 'bread'}
print("Support:", support(itemset, transactions))
8. FP-Growth Algorithm and Pattern Trees
- Overview: Unlike Apriori, FP-Growth only scans the
database twice.
- Process:
- Count frequent 1-itemsets and filter by minimum support.
- Sort transactions by item frequency and build the
FP-tree by adding transactions sequentially,
incrementing counts at existing nodes.
- Divide-and-Conquer: Once constructed, the tree is
recursively divided to find frequent itemsets without re-scanning the
dataset.
9. FP-Growth Algorithm
The FP-Growth Algorithm offers a scalable
alternative to Apriori by constructing a Frequent Pattern Tree
(FP-Tree), eliminating the need for multiple database scans and
candidate generation.
FP-Growth Process:
- First Pass: Identify frequent 1-itemsets, sorting
them by frequency.
- FP-Tree Construction: Build a tree where
transactions are hierarchically organized, reducing redundancy.
- Divide and Conquer: Recursively generate
conditional trees from the FP-Tree to mine frequent patterns.
Code Example for FP-Tree Construction
class TreeNode:
def __init__(self, name, count):
self.name = name
self.count = count
self.parent = None
self.children = {}
# Adding transactions to the FP-tree
root = TreeNode("null", 1)
transactions = [["milk", "bread"], ["beer", "diapers"], ["milk", "diapers"]]
def add_transaction(node, transaction):
for item in transaction:
if item in node.children:
node.children[item].count += 1
else:
child_node = TreeNode(item, 1)
child_node.parent = node
node.children[item] = child_node
node = node.children[item]
for transaction in transactions:
add_transaction(root, sorted(transaction))
# Visualization of FP-tree
import networkx as nx
import matplotlib.pyplot as plt
def visualize_tree(node, graph, parent=None):
graph.add_node(node.name)
if parent:
graph.add_edge(parent, node.name)
for child in node.children.values():
visualize_tree(child, graph, node.name)
G = nx.DiGraph()
visualize_tree(root, G)
plt.figure(figsize=(8, 6))
nx.draw(G, with_labels=True, node_size=2000, font_size=10, font_weight="bold")
plt.show()
10. Comparison and Key Insights
- Support Threshold: Setting an appropriate minimum
support level is critical; low thresholds generate excessive patterns,
while high thresholds might miss useful patterns.
- Apriori vs. FP-Growth:
- Apriori uses multiple database passes and explicit candidate
generation.
- FP-Growth organizes items into a tree, significantly reducing
database access.
- Hash Trees in Apriori: Reduces the number of
comparisons by grouping items based on hash values.
- Conditional Trees in FP-Growth: Splits the FP-Tree
to allow targeted, recursive mining of itemsets, enhancing computational
efficiency.
11. Practical Applications of Association Rule
Mining
- Retail: Product recommendation and shelf
arrangement based on item co-occurrence.
- Healthcare: Identifying relationships between
symptoms or medical conditions.
- Finance: Pattern detection for fraud and unusual
transaction behaviors.
12. Example Calculations
Calculating Support and Confidence
Given transactions, calculate support and confidence for
{milk, bread} \rightarrow {eggs}
.
# Example transactions
transactions = [
{'milk', 'bread', 'beer'},
{'milk', 'bread', 'diapers'},
{'milk', 'bread', 'eggs'},
{'bread', 'diapers', 'eggs'},
{'milk', 'bread'}
]
# Support function
def support(itemset, transactions):
count = sum(1 for transaction in transactions if itemset.issubset(transaction))
return count / len(transactions)
# Confidence function
def confidence(X, Y, transactions):
return support(X | Y, transactions) / support(X, transactions)
# Calculate support and confidence
itemset_X = {'milk', 'bread'}
itemset_Y = {'eggs'}
print("Support:", support(itemset_X | itemset_Y, transactions))
print("Confidence:", confidence(itemset_X, itemset_Y, transactions))
13. Visualization: Candidate Lattice
import networkx as nx
import matplotlib.pyplot as plt
# Visualization of a candidate lattice
G = nx.DiGraph()
G.add_edges_from([
("A", "AB"), ("A", "AC"), ("B", "AB"),
("B", "BC"), ("C", "AC"), ("C", "BC"),
("AB", "ABC"), ("AC", "ABC"), ("BC", "ABC")
])
plt.figure(figsize=(8, 6))
nx.draw(G, with_labels=True, node_size=3000, node_color="skyblue", font_size=10, font_weight="bold")
plt.title("Candidate Lattice for Itemset Generation")
plt.show()
Recap
- Association Rule Mining identifies co-occurrence
patterns, critical in fields from e-commerce to healthcare.
- Support and Confidence provide
core evaluation metrics, with Lift further refining
association strength.
- Apriori Principle and FP-Growth
enable scalable itemset generation by reducing dataset passes and
simplifying comparisons.
- FP-Tree offers a compact structure, facilitating
recursive pattern discovery without additional database scans.
Part 3
Rules Generation
Study Guide: Association Analysis and Rule Mining (Module 12)
1. Overview
Association analysis focuses on identifying patterns in transactional
data where the occurrence of certain items is associated with others.
This type of analysis is foundational in fields such as retail for
market basket analysis and healthcare for symptom-diagnosis
relationships.
Key Concepts and Definitions
Association Rule Mining: -
Objective: To uncover co-occurrence patterns within
transaction data, such as in retail where buying one item suggests
another purchase. - Association Rule Format: \(X \rightarrow Y\), meaning if \(X\) occurs, \(Y\) is likely to occur as well.
Key Metrics: 1. Support: Measures
how frequently an item or itemset appears in the data. \[
\text{Support}(X) = \frac{\text{Transactions containing }
X}{\text{Total Transactions}}
\] 2. Confidence: Probability of \(Y\) occurring given that \(X\) has occurred. \[
\text{Confidence}(X \rightarrow Y) = \frac{\text{Support}(X \cup
Y)}{\text{Support}(X)}
\] 3. Lift: Evaluates association strength; a
lift greater than 1 indicates positive association, while less than 1
indicates negative association. \[
\text{Lift}(X \rightarrow Y) = \frac{\text{Confidence}(X \rightarrow
Y)}{\text{Support}(Y)}
\] 4. Interest: Compares observed and
expected frequency of co-occurrence. \[
\text{Interest}(X \rightarrow Y) = \text{Support}(X \cup Y) -
\text{Support}(X) \times \text{Support}(Y)
\]
- Frequent Itemsets:
- Definition: Sets of items frequently appearing
together in transactions.
- Support: Measures how often an itemset appears in
the database: \[
\text{Support}(X) = \frac{\text{Frequency of } X \text{ in
transactions}}{\text{Total transactions}}
\]
- Example Calculation: If itemset
{A, B, C}
appears in 3 out of 10 transactions, then: \[
\text{Support} = \frac{3}{10} = 0.3
\]
- Association Rules:
- Definition: Rules of the form \(X \rightarrow Y\) meaning “If \(X\), then \(Y\)”.
- Confidence: The likelihood of \(Y\) given \(X\), calculated as: \[
\text{Confidence}(X \rightarrow Y) = \frac{\text{Support}(X \cup
Y)}{\text{Support}(X)}
\]
- Lift: Measures the strength of a rule by comparing
the observed support to that expected if \(X\) and \(Y\) were independent: \[
\text{Lift}(X \rightarrow Y) = \frac{\text{Confidence}(X \rightarrow
Y)}{\text{Support}(Y)}
\]
- Maximal and Closed Itemsets:
- Maximal Itemsets: An itemset with no frequent
supersets.
- Closed Itemsets: An itemset is closed if it has no
superset with the same support. ————————————————————————
Step-by-Step Methodology
- Identify Frequent Itemsets: Find sets of items that
occur frequently based on a minimum support threshold.
- Generate Association Rules: From frequent itemsets,
generate rules that meet minimum confidence.
Algorithms for Association Rule Mining
1. Apriori Algorithm
- Purpose: Efficiently finds frequent itemsets by
iteratively expanding smaller frequent itemsets.
- Apriori Principle: If an itemset is infrequent, all
its supersets are also infrequent.
- Process:
- Begin with frequent 1-itemsets.
- Generate \(k\)-itemset candidates
from frequent \(k-1\)-itemsets.
- Count occurrences and prune non-frequent sets based on support
threshold.
Example Code:
from itertools import combinations
transactions = [
{'milk', 'bread', 'beer'},
{'milk', 'diapers', 'beer'},
{'milk', 'bread', 'diapers'},
{'bread', 'diapers', 'beer'},
{'milk', 'bread'}
]
def apriori(transactions, min_support):
# Generate frequent itemsets and calculate support
# Implementation would follow here
pass
AGAIN for good measure
The Apriori algorithm generates frequent itemsets by
iteratively expanding smaller itemsets (of size \(k\)) to larger ones (of size \(k+1\)) and pruning non-frequent candidates
based on minimum support criteria. This reduces computational costs by
eliminating unnecessary evaluations:
from itertools import combinations
def apriori(transactions, min_support):
single_items = {item for t in transactions for item in t}
current_itemsets = [{i} for i in single_items]
def get_support(itemset):
return sum(1 for t in transactions if itemset <= set(t)) / len(transactions)
result = []
while current_itemsets:
frequent_itemsets = [s for s in current_itemsets if get_support(s) >= min_support]
result.extend(frequent_itemsets)
current_itemsets = [a | b for a in frequent_itemsets for b in frequent_itemsets if len(a | b) == len(a) + 1]
return result
2. FP-Growth Algorithm
- Purpose: Avoids generating candidates by creating a
Frequent Pattern Tree (FP-Tree), a compressed structure
that retains item occurrence patterns.
- Process:
- Create an FP-tree from transactions, sorted by frequency.
- Extract patterns through recursive decomposition of conditional
trees.
FP-Tree Code Example:
class TreeNode:
def __init__(self, name, count):
self.name = name
self.count = count
self.parent = None
self.children = {}
# Example FP-tree construction
root = TreeNode("null", 1)
transactions = [["milk", "bread"], ["beer", "diapers"], ["milk", "diapers"]]
def add_transaction(node, transaction):
# Constructing FP-tree logic here
pass
Advanced Topics: Rule Generation and Lattice Structure
Rule Generation: - Generate rules by evaluating
subsets of a frequent itemset. - Candidate Lattice for
Rules: - For a frequent itemset, create rules by assigning each
subset as antecedent or consequent. - Pruning: Use
confidence threshold to prune low-confidence rules.
Visualization of Rule Lattice: A rule
lattice represents various rule possibilities from a frequent
itemset. It helps visualize how rules with lower confidence can be
pruned based on the anti-monotonicity property of confidence.
Example: If \(\text{Confidence}(ABC \rightarrow D)\) is
low, prune any rule from the lattice that would have lower
confidence.
Compact Representation of Patterns
- Maximal Itemsets: Largest itemsets that are
frequent; no superset is frequent.
- Closed Itemsets: Itemsets with no superset having
the same support.
- Purpose: Reduces redundancy by only tracking the
most informative itemsets.
Practical Application: Evaluating Rule Interestingness
- Objective Metrics:
- Calculate support, confidence,
lift, and interest using contingency
tables.
- Subjective Interestingness:
- Incorporate domain knowledge to prioritize actionable or unexpected
patterns.
Example Calculation
For the rule # Students <80% → Pop Quiz (based on
the provided table): - Confidence: \(\frac{4}{12} = 0.33\) -
Lift: \(\frac{0.33}{(5/25)} =
1.65\) - Interest: \(1.66\)
This guide summarizes core principles of association analysis,
frequent itemset generation, and rule mining, highlighting practical
metrics and algorithms used in the field. It combines theoretical
insights with code examples to facilitate comprehension of complex
topics.
Images:
image with answers
4. Rule Generation and Lattice Visualization
For a frequent itemset \(L\),
generate rules by finding all subsets \(F\) and using \(F
\rightarrow (L \setminus F)\) as candidate rules. Apply
confidence to filter significant rules.
- Lattice Visualization: A lattice graph visualizes
relationships between itemset levels. The anti-monotone
property helps prune less-confident rules early in this structure.
5. Code Example for Rule Generation
The following example calculates support and confidence for candidate
rules from a list of transactions.
transactions = [{'milk', 'bread', 'butter'}, {'milk', 'bread'}, {'bread', 'butter'}, {'milk', 'butter'}]
def support(itemset, transactions):
return sum(1 for t in transactions if itemset <= t) / len(transactions)
def confidence(antecedent, consequent, transactions):
return support(antecedent | consequent, transactions) / support(antecedent, transactions)
antecedent, consequent = {'milk'}, {'bread'}
print("Support:", support(antecedent | consequent, transactions))
print("Confidence:", confidence(antecedent, consequent, transactions))
6. Evaluation Metrics
Lift: Indicates the strength of association
beyond random chance. \[
\text{Lift}(X \rightarrow Y) = \frac{\text{Confidence}(X \rightarrow
Y)}{\text{Support}(Y)}
\]
Interest: The difference between observed
co-occurrence and expected independence.
7. Practical Example in Python
Visualize rules and relationships using a graph to highlight
confidence levels and prune based on thresholds.
import networkx as nx
import matplotlib.pyplot as plt
G = nx.DiGraph()
G.add_edges_from([("A", "AB"), ("B", "BC"), ("C", "ABC")])
plt.figure(figsize=(8, 6))
nx.draw(G, with_labels=True, node_size=3000, font_size=10)
plt.show()# Study Guide: Association Analysis and Rule Mining (Module 12)
---
### 1. Overview
**Association Analysis** focuses on identifying patterns in transactional data where the occurrence of certain items suggests the presence of others. This analysis is essential in fields such as **retail** (market basket analysis) and **healthcare** (symptom-diagnosis relationships).
### Key Concepts and Definitions
- **Association Rule Mining**:
- **Objective**: To uncover co-occurrence patterns within transaction data, where buying one item might suggest purchasing another.
- **Association Rule Format**: \( X \rightarrow Y \), meaning if \( X \) occurs, \( Y \) is likely to occur as well.
#### Key Metrics
1. **Support**: Measures how frequently an item or itemset appears in the data.
\[
\text{Support}(X) = \frac{\text{Transactions containing } X}{\text{Total Transactions}}
\]
2. **Confidence**: Probability of \( Y \) occurring given that \( X \) has occurred.
\[
\text{Confidence}(X \rightarrow Y) = \frac{\text{Support}(X \cup Y)}{\text{Support}(X)}
\]
3. **Lift**: Evaluates association strength; a lift greater than 1 indicates a positive association, while less than 1 indicates a negative association.
\[
\text{Lift}(X \rightarrow Y) = \frac{\text{Confidence}(X \rightarrow Y)}{\text{Support}(Y)}
\]
4. **Interest**: Compares observed and expected frequency of co-occurrence.
\[
\text{Interest}(X \rightarrow Y) = \text{Support}(X \cup Y) - \text{Support}(X) \times \text{Support}(Y)
\]
---
### Methodology
1. **Identify Frequent Itemsets**: Find sets of items that occur frequently based on a minimum support threshold.
2. **Generate Association Rules**: From frequent itemsets, generate rules that meet minimum confidence.
---
### Algorithms for Association Rule Mining
#### 1. **Apriori Algorithm**
- **Purpose**: Efficiently finds frequent itemsets by expanding smaller itemsets iteratively.
- **Apriori Principle**: If an itemset is infrequent, all its supersets are also infrequent.
- **Process**:
- Start with frequent 1-itemsets.
- Generate \( k \)-itemset candidates from frequent \( k-1 \)-itemsets.
- Count occurrences and prune non-frequent sets based on a support threshold.
**Example Code**:
```python
from itertools import combinations
transactions = [
{'milk', 'bread', 'beer'},
{'milk', 'diapers', 'beer'},
{'milk', 'bread', 'diapers'},
{'bread', 'diapers', 'beer'},
{'milk', 'bread'}
]
def apriori(transactions, min_support):
# Generate frequent itemsets and calculate support
pass
- or-
from itertools import combinations
transactions = [
{'milk', 'bread', 'beer'},
{'milk', 'diapers', 'beer'},
{'milk', 'bread', 'diapers'},
{'bread', 'diapers', 'beer'},
{'milk', 'bread'}
]
def apriori(transactions, min_support):
single_items = {item for t in transactions for item in t}
current_itemsets = [{i} for i in single_items]
def get_support(itemset):
return sum(1 for t in transactions if itemset <= set(t)) / len(transactions)
result = []
while current_itemsets:
frequent_itemsets = [s for s in current_itemsets if get_support(s) >= min_support]
result.extend(frequent_itemsets)
current_itemsets = [a | b for a in frequent_itemsets for b in frequent_itemsets if len(a | b) == len(a) + 1]
return result
2. FP-Growth Algorithm
- Purpose: Avoids generating candidates by creating a
Frequent Pattern Tree (FP-Tree), a compressed structure
that retains item occurrence patterns.
- Process:
- Build an FP-tree from transactions, sorted by frequency.
- Extract patterns through recursive decomposition of conditional
trees.
FP-Tree Code Example:
class TreeNode:
def __init__(self, name, count):
self.name = name
self.count = count
self.parent = None
self.children = {}
# Example FP-tree construction
root = TreeNode("null", 1)
transactions = [["milk", "bread"], ["beer", "diapers"], ["milk", "diapers"]]
def add_transaction(node, transaction):
# Constructing FP-tree logic here
pass
Advanced Topics: Rule Generation and Lattice Structure
- Rule Generation:
- Generate rules by evaluating subsets of a frequent itemset.
- Candidate Lattice for Rules: Create rules by
assigning subsets as antecedent or consequent, using confidence to prune
low-confidence rules.
Visualization of Rule Lattice: A rule
lattice helps visualize potential rules from a frequent
itemset, allowing pruning of rules with lower confidence based on the
anti-monotonicity property.
Example: If \(\text{Confidence}(ABC \rightarrow D)\) is
low, prune any rule in the lattice with lower confidence.
Compact Representation of Patterns
- Maximal Itemsets: Largest itemsets that are
frequent; no superset is frequent.
- Closed Itemsets: Itemsets with no superset having
the same support.
- Purpose: Reduces redundancy by only tracking the
most informative itemsets.
Practical Application: Evaluating Rule Interestingness
- Objective Metrics:
- Calculate support, confidence,
lift, and interest using contingency
tables.
- Subjective Interestingness:
- Use domain knowledge to prioritize actionable or unexpected
patterns.
Example Calculation
For the rule # Students <80% → Pop Quiz: -
Confidence: \(\frac{4}{12} =
0.33\) - Lift: \(\frac{0.33}{(5/25)} = 1.65\) -
Interest: 1.66
Practical Example in Python
Visualization:
import networkx as nx
import matplotlib.pyplot as plt
G = nx.DiGraph()
G.add_edges_from([("A", "AB"), ("B", "BC"), ("C", "ABC")])
plt.figure(figsize=(8, 6))
nx.draw(G, with_labels=True, node_size=3000, font_size=10)
plt.show()

This image represents a rule generation lattice for
a frequent itemset, specifically {Male, 3rd, Not Survive}
.
In this lattice, each node corresponds to a rule derived by assigning
subsets of the itemset as antecedents or consequents. The arrows
illustrate the process of rule decomposition, showing how larger rules
can be broken down into simpler rules. Here’s an analysis based on our
previous discussion:
Structure of the Lattice
- Top Node:
{Male, 3rd, Not Survive} => ∅
- This node represents the entire frequent itemset with no specific
rule created yet (i.e., no assignment of antecedent or consequent).
- Next Level Down: The lattice splits into rules with
a single item as the consequent. Examples include:
{3rd, Not Survive} => {Male}
{Male, Not Survive} => {3rd}
{3rd, Male} => {Not Survive}
- These represent rules with two-item antecedents implying the
remaining item as the consequent.
- Further Levels: The lattice continues to split
downwards, creating rules with fewer items in the antecedent and more
items in the consequent.
- For example,
{Not Survive} => {Male, 3rd}
implies
that if someone did not survive, they are likely a male in 3rd
class.
- Each node lower in the lattice represents rules with simpler
antecedents, leading to broader consequents.
Key Concepts Illustrated
Rule Generation: This lattice structure visually
represents the process of generating association rules from a frequent
itemset by evaluating all possible subset combinations for antecedents
and consequents.
Anti-Monotonicity Property: Confidence typically
follows an anti-monotonic property as we move down the lattice. This
means that if a rule higher in the lattice (e.g.,
{3rd, Not Survive} => {Male}
) has low confidence, then
all rules derived from it by reducing the antecedent will have even
lower confidence. This property allows us to prune
lower confidence rules from consideration.
Pruning and Efficiency: By using the lattice
structure, we can effectively prune rules that do not meet confidence or
other evaluation thresholds, similar to how the Apriori principle works
in frequent itemset generation. For example, if
{Male, Not Survive} => {3rd}
has low confidence, any
further rules generated from this (with fewer antecedents and more items
in the consequent) can be ignored.
Application of Rule Evaluation Metrics:
- Confidence: For each rule, we would calculate the
confidence by determining how often the antecedent appears with the
consequent in the dataset.
- Lift and Interest: These additional metrics could
further evaluate the “interestingness” of each rule, especially for
rules that seem statistically significant or actionable.
Summary
This lattice visualization is a valuable tool for rule
generation and pruning. It systematically
represents all possible rules derived from a frequent itemset and helps
apply metrics (like confidence and lift) to keep only the most
interesting and relevant rules. This technique is particularly helpful
in complex datasets, enabling efficient rule mining and association
analysis.
LS0tDQp0aXRsZTogIk1MNzMzMSBNb2R1bGUgMTIiDQphdXRob3I6ICJKZXNzaWNhIE1jUGhhdWwiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCmVkaXRvcl9vcHRpb25zOiANCiAgbWFya2Rvd246IA0KICAgIHdyYXA6IDcyDQotLS0NCg0KIyMjIEFzc29jaWF0aW9uIFJ1bGUgTWluaW5nIFN0dWR5IEd1aWRlDQoNCiMjIyMgT3ZlcnZpZXcgb2YgQXNzb2NpYXRpb24gQW5hbHlzaXMNCg0KQXNzb2NpYXRpb24gYW5hbHlzaXMgZXhhbWluZXMgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIGl0ZW1zIGluIGEgZGF0YXNldA0KdG8gZGV0ZXJtaW5lIHBhdHRlcm5zIG9mIGNvLW9jY3VycmVuY2UuIEluIGFzc29jaWF0aW9uIHJ1bGUgbWluaW5nLA0KdGhlc2UgcGF0dGVybnMgYXJlIHVzZWQgdG8gZ2VuZXJhdGUgKipydWxlcyoqIHRoYXQgcHJlZGljdCB3aGVuIGNlcnRhaW4NCml0ZW1zIHdpbGwgYXBwZWFyIHRvZ2V0aGVyLg0KDQpGb3IgZXhhbXBsZSwgaW4gYSByZXRhaWwgc2V0dGluZywgdGhpcyBhbmFseXNpcyBtaWdodCByZXZlYWwgdGhhdCAiaWYgYQ0KY3VzdG9tZXIgYnV5cyBicmVhZCBhbmQgbWlsaywgdGhleSBhcmUgbGlrZWx5IHRvIGFsc28gYnV5IGVnZ3MuIg0KDQojIyMjIEtleSBDb25jZXB0cw0KDQoxLiAgKipBc3NvY2lhdGlvbiBSdWxlIE1pbmluZyoqOg0KICAgIC0gICAqKkRlZmluaXRpb24qKjogR2l2ZW4gYSBzZXQgb2YgdHJhbnNhY3Rpb25zLCBhc3NvY2lhdGlvbiBydWxlDQogICAgICAgIG1pbmluZyBzZWVrcyB0byBmaW5kIHBhdHRlcm5zIG9yIHJ1bGVzIHRoYXQgZXhwbGFpbiB3aGVuIGl0ZW1zDQogICAgICAgIGNvLW9jY3VyIGluIHRyYW5zYWN0aW9ucy4NCiAgICAtICAgKipBcHBsaWNhdGlvbnMqKjogQ29tbW9uIGluIHJlY29tbWVuZGF0aW9uIHN5c3RlbXMgKGUuZy4sDQogICAgICAgIEFtYXpvbuKAmXMgIkN1c3RvbWVycyB3aG8gYm91Z2h0IHRoaXMgYWxzbyBib3VnaHQiKSBhbmQgaW4NCiAgICAgICAgaGVhbHRoY2FyZSBkYXRhIChlLmcuLCBoaWdoIGJsb29kIHByZXNzdXJlIG9mdGVuIGNvLW9jY3VycyB3aXRoDQogICAgICAgIHN0cmVzcykuDQoNCiMjIyMgMS4gQXNzb2NpYXRpb24gUnVsZSBNaW5pbmcgUmVjYXANCg0KQXNzb2NpYXRpb24gcnVsZSBtaW5pbmcgYWltcyB0byB1bmNvdmVyIHJlbGF0aW9uc2hpcHMgYmV0d2VlbiB2YXJpYWJsZXMNCmluIHRyYW5zYWN0aW9uIGRhdGEuIEVhY2ggdHJhbnNhY3Rpb24gaXMgYSBjb2xsZWN0aW9uIG9mIGl0ZW1zLCBhbmQgdGhlDQpvYmplY3RpdmUgaXMgdG8gZGV0ZXJtaW5lIGlmIGNlcnRhaW4gaXRlbXMgZnJlcXVlbnRseSBjby1vY2N1ci4gVGhlc2UNCnJlbGF0aW9uc2hpcHMgY2FuIGJlIHJlcHJlc2VudGVkIGFzICoqYXNzb2NpYXRpb24gcnVsZXMqKiBvZiB0aGUgZm9ybQ0KJFggXHJpZ2h0YXJyb3cgWSQsIHdoZXJlICRYJCBpbXBsaWVzICRZJCB3aXRoIGEgY2VydGFpbiBkZWdyZWUgb2YNCioqY29uZmlkZW5jZSoqIGFuZCAqKnN1cHBvcnQqKi4NCg0KMi4gICoqRnJlcXVlbnQgSXRlbXNldHMqKjoNCiAgICAtICAgKipEZWZpbml0aW9uKio6IENvbGxlY3Rpb25zIG9mIG9uZSBvciBtb3JlIGl0ZW1zIHRoYXQgYXBwZWFyDQogICAgICAgIGZyZXF1ZW50bHkgdG9nZXRoZXIgaW4gdGhlIGRhdGFzZXQuIEZvciBleGFtcGxlLCBge2JyZWFkLCBtaWxrfWANCiAgICAgICAgY291bGQgYmUgYSBmcmVxdWVudCBpdGVtc2V0Lg0KICAgIC0gICAqKlN1cHBvcnQgQ291bnQqKjogVGhlIGZyZXF1ZW5jeSBvZiBvY2N1cnJlbmNlIG9mIGFuIGl0ZW1zZXQNCiAgICAgICAgd2l0aGluIHRoZSBkYXRhc2V0Lg0KICAgIC0gICAqKlN1cHBvcnQqKjogVGhlIHByb3BvcnRpb24gb2YgdHJhbnNhY3Rpb25zIHRoYXQgY29udGFpbiB0aGUNCiAgICAgICAgaXRlbXNldCwgY2FsY3VsYXRlZCBhczogJCQNCiAgICAgICAgXHRleHR7U3VwcG9ydH0gPSBcZnJhY3tcdGV4dHtTdXBwb3J0IENvdW50fX17XHRleHR7VG90YWwgVHJhbnNhY3Rpb25zfX0NCiAgICAgICAgJCQNCg0KIyMjIyAyLiBGcmVxdWVudCBJdGVtc2V0IEdlbmVyYXRpb24NCg0KR2VuZXJhdGluZyBmcmVxdWVudCBpdGVtc2V0cyBpbnZvbHZlcyBpZGVudGlmeWluZyBncm91cHMgb2YgaXRlbXMgdGhhdA0Kb2Z0ZW4gYXBwZWFyIHRvZ2V0aGVyIGluIHRyYW5zYWN0aW9ucy4gVGhlICoqY2FuZGlkYXRlIGl0ZW1zZXQgbGF0dGljZSoqDQppcyB1c2VkIHRvIHN5c3RlbWF0aWNhbGx5IGV2YWx1YXRlIGl0ZW1zZXQgY29tYmluYXRpb25zLCBzdGFydGluZyBmcm9tDQpzaW5nbGUgaXRlbXMgYW5kIHByb2dyZXNzaW5nIHRvIGxhcmdlciBncm91cHMuIEtleSB0byByZWR1Y2luZw0KY29tcHV0YXRpb25hbCBsb2FkIGlzIHRoZSAqKkFwcmlvcmkgcHJpbmNpcGxlKiosIHdoaWNoIGhlbHBzIHRvIGF2b2lkDQpldmFsdWF0aW5nIGFsbCBwb3NzaWJsZSBjb21iaW5hdGlvbnMgYnkgZWxpbWluYXRpbmcgaXRlbXNldHMgdGhhdCBkbyBub3QNCm1lZXQgdGhlIG1pbmltdW0gc3VwcG9ydCB0aHJlc2hvbGQuDQoNCioqRXhhbXBsZToqKiBGb3IgaXRlbXMgbGlrZSBge21pbGssIGJyZWFkLCBkaWFwZXJzfWAsIGZyZXF1ZW50IGl0ZW1zZXRzDQphcmUgdGhvc2UgdGhhdCBhcHBlYXIgaW4gYSBzdWZmaWNpZW50IG51bWJlciBvZiB0cmFuc2FjdGlvbnMuDQpDYWxjdWxhdGluZyBzdXBwb3J0IGZvciBpdGVtc2V0cyBhbmQgZ2VuZXJhdGluZyBjYW5kaWRhdGUgbGF0dGljZXMNCmVuYWJsZXMgc3RydWN0dXJlZCBleHBsb3JhdGlvbiBvZiBjby1vY2N1cnJlbmNlIHJlbGF0aW9uc2hpcHMuDQoNCjMuICAqKkFzc29jaWF0aW9uIFJ1bGVzKio6DQogICAgLSAgICoqRGVmaW5pdGlvbioqOiBFeHByZXNzZWQgYXMgJFggXHJpZ2h0YXJyb3cgWSQsIHdoZXJlICRYJCBhbmQNCiAgICAgICAgJFkkIGFyZSBpdGVtc2V0cywgbWVhbmluZyAiSWYgJFgkIG9jY3VycywgJFkkIGlzIGxpa2VseSB0bw0KICAgICAgICBvY2N1ci4iDQogICAgLSAgICoqU3VwcG9ydCBmb3IgYSBSdWxlKio6IFRoZSBmcmFjdGlvbiBvZiB0cmFuc2FjdGlvbnMgdGhhdA0KICAgICAgICBjb250YWluIGJvdGggJFgkIGFuZCAkWSQuDQogICAgLSAgICoqQ29uZmlkZW5jZSBmb3IgYSBSdWxlKio6IEluZGljYXRlcyBob3cgb2Z0ZW4gJFkkIGFwcGVhcnMgaW4NCiAgICAgICAgdHJhbnNhY3Rpb25zIHRoYXQgY29udGFpbiAkWCQsIGdpdmVuIGJ5OiAkJA0KICAgICAgICBcdGV4dHtDb25maWRlbmNlfShYIFxyaWdodGFycm93IFkpID0gXGZyYWN7XHRleHR7U3VwcG9ydCBvZiB9IFggXGN1cCBZfXtcdGV4dHtTdXBwb3J0IG9mIH0gWH0NCiAgICAgICAgJCQNCg0KIyMjIyBSdWxlIEV2YWx1YXRpb24gTWV0cmljcw0KDQotICAgKipTdXBwb3J0Kio6IEhlbHBzIGluIGlkZW50aWZ5aW5nIHRoZSBvdmVyYWxsIGZyZXF1ZW5jeSBvZiBhbg0KICAgIGl0ZW1zZXQuDQotICAgKipDb25maWRlbmNlKio6IE1lYXN1cmVzIHRoZSByZWxpYWJpbGl0eSBvZiBhbiBpbmZlcmVuY2U7IGhpZ2hlcg0KICAgIGNvbmZpZGVuY2UgaW5kaWNhdGVzIGEgc3Ryb25nZXIgYXNzb2NpYXRpb24uDQotICAgKipMaWZ0IChvcHRpb25hbCkqKjogTWVhc3VyZXMgaG93IG11Y2ggbW9yZSBsaWtlbHkgJFkkIGlzIHRvIG9jY3VyDQogICAgd2hlbiAkWCQgaXMgcHJlc2VudCBjb21wYXJlZCB0byB3aGVuICRYJCBpcyBhYnNlbnQuDQoNCiMjIyMgMy4gUnVsZSBHZW5lcmF0aW9uDQoNCk9uY2UgZnJlcXVlbnQgaXRlbXNldHMgYXJlIGlkZW50aWZpZWQsIHRoZSBuZXh0IHN0ZXAgaXMgY3JlYXRpbmcNCmFzc29jaWF0aW9uIHJ1bGVzIGZyb20gdGhlc2Ugc2V0cywgZW5zdXJpbmcgZWFjaCBydWxlIG1lZXRzIG1pbmltdW0NCnN1cHBvcnQgYW5kIGNvbmZpZGVuY2UgcmVxdWlyZW1lbnRzLiBUaGVzZSBydWxlcyBhcmUgZGVyaXZlZCBmcm9tDQppdGVtc2V0cyBieSBkaXZpZGluZyB0aGVtIGludG8gYW50ZWNlZGVudHMgKGlmLXBhcnQpIGFuZCBjb25zZXF1ZW50cw0KKHRoZW4tcGFydCksIHdpdGggbWV0cmljcyBjYWxjdWxhdGVkIGZvciBlYWNoIHJ1bGUuDQoNCioqUnVsZSBNZXRyaWNzKio6IC0gKipTdXBwb3J0Kio6IE1lYXN1cmVzIGhvdyBvZnRlbiAkWCBcY3VwIFkkIGFwcGVhcnMNCmluIHRoZSBkYXRhc2V0LiAtICoqQ29uZmlkZW5jZSoqOiBNZWFzdXJlcyB0aGUgbGlrZWxpaG9vZCBvZiAkWSQNCmFwcGVhcmluZyB3aGVuICRYJCBpcyBwcmVzZW50LCBjYWxjdWxhdGVkIGFzOiAkJA0KICBcdGV4dHtDb25maWRlbmNlfShYIFxyaWdodGFycm93IFkpID0gXGZyYWN7XHRleHR7U3VwcG9ydCBvZiB9IFggXGN1cCBZfXtcdGV4dHtTdXBwb3J0IG9mIH0gWH0NCiAgJCQgLSAqKkxpZnQqKjogRXZhbHVhdGVzIHRoZSBzdHJlbmd0aCBvZiBhbiBhc3NvY2lhdGlvbiBydWxlIHJlbGF0aXZlDQp0byByYW5kb20gY2hhbmNlLCBwcm92aWRpbmcgaW5zaWdodCBpbnRvIHRoZSBydWxl4oCZcyBzaWduaWZpY2FuY2UuDQoNCiMjIyMgU3RlcHMgaW4gQXNzb2NpYXRpb24gUnVsZSBNaW5pbmcNCg0KMS4gICoqRnJlcXVlbnQgSXRlbXNldCBHZW5lcmF0aW9uKio6DQogICAgLSAgIEdlbmVyYXRlIGFsbCBpdGVtc2V0cyB0aGF0IG1lZXQgYSBtaW5pbXVtIHN1cHBvcnQgdGhyZXNob2xkLCBhDQogICAgICAgIHByb2Nlc3MgdGhhdCBpcyBjb21wdXRhdGlvbmFsbHkgaW50ZW5zaXZlLg0KMi4gICoqUnVsZSBHZW5lcmF0aW9uKio6DQogICAgLSAgIFVzZSB0aGUgZnJlcXVlbnQgaXRlbXNldHMgdG8gY3JlYXRlIGFzc29jaWF0aW9uIHJ1bGVzLCBlbnN1cmluZw0KICAgICAgICB0aGV5IG1lZXQgYm90aCBtaW5pbXVtIHN1cHBvcnQgYW5kIGNvbmZpZGVuY2UgdGhyZXNob2xkcy4NCjMuICAqKk9wdGltaXppbmcgQ29tcHV0YXRpb24qKjoNCiAgICAtICAgKipDYW5kaWRhdGUgTGF0dGljZSoqOiBWaXN1YWxpemVzIGFsbCBwb3RlbnRpYWwgaXRlbXNldHMgZm9yDQogICAgICAgIGZyZXF1ZW50IHNldCBtaW5pbmcuDQogICAgLSAgICoqQXByaW9yaSBQcmluY2lwbGUqKjogSWYgYW4gaXRlbXNldCBpcyBpbmZyZXF1ZW50LCBhbGwNCiAgICAgICAgc3VwZXJzZXRzIGFyZSBhbHNvIGluZnJlcXVlbnQsIGhlbHBpbmcgdG8gcmVkdWNlIGNvbXB1dGF0aW9uIGJ5DQogICAgICAgIHBydW5pbmcuDQoNCiMjIyMgRXhhbXBsZSBDYWxjdWxhdGlvbnMNCg0KU3VwcG9zZSBhIGRhdGFzZXQgaGFzIDUgdHJhbnNhY3Rpb25zLCBhbmQgYHttaWxrLCBicmVhZCwgZGlhcGVyc31gDQphcHBlYXJzIGluIDIgb2YgdGhlbTogLSAqKlN1cHBvcnQgQ291bnQgZm9yIGB7bWlsaywgYnJlYWQsIGRpYXBlcnN9YCoqOg0KMiAtICoqU3VwcG9ydCBmb3IgYHttaWxrLCBicmVhZCwgZGlhcGVyc31gKio6ICQkDQogIFx0ZXh0e1N1cHBvcnR9ID0gXGZyYWN7Mn17NX0gPSAwLjQNCiAgJCQNCg0KSWYgYHttaWxrLCBkaWFwZXJzfWAgYXBwZWFycyBpbiAzIHRyYW5zYWN0aW9ucywgdGhlICoqY29uZmlkZW5jZSoqIHRoYXQNCmB7bWlsaywgZGlhcGVyc30gXHJpZ2h0YXJyb3cgYmVlcmAgaXM6ICQkDQpcdGV4dHtDb25maWRlbmNlfSA9IFxmcmFje1x0ZXh0e1N1cHBvcnQgb2YgfSBce21pbGssIGRpYXBlcnMsIGJlZXJcfX17XHRleHR7U3VwcG9ydCBvZiB9IFx7bWlsaywgZGlhcGVyc1x9fQ0KJCQNCg0KIyMjIyBQeXRob24gQ29kZSBmb3IgU3VwcG9ydCBhbmQgQ29uZmlkZW5jZSBDYWxjdWxhdGlvbg0KDQpgYGAgcHl0aG9uDQpmcm9tIGl0ZXJ0b29scyBpbXBvcnQgY29tYmluYXRpb25zDQoNCiMgRXhhbXBsZSB0cmFuc2FjdGlvbiBkYXRhDQp0cmFuc2FjdGlvbnMgPSBbDQogICAgeydtaWxrJywgJ2JyZWFkJywgJ2JlZXInfSwNCiAgICB7J21pbGsnLCAnZGlhcGVycycsICdiZWVyJ30sDQogICAgeydtaWxrJywgJ2JyZWFkJywgJ2RpYXBlcnMnfSwNCiAgICB7J2JyZWFkJywgJ2RpYXBlcnMnLCAnYmVlcid9LA0KICAgIHsnbWlsaycsICdicmVhZCd9DQpdDQoNCmRlZiBzdXBwb3J0KGl0ZW1zZXQsIHRyYW5zYWN0aW9ucyk6DQogICAgY291bnQgPSBzdW0oMSBmb3IgdHJhbnNhY3Rpb24gaW4gdHJhbnNhY3Rpb25zIGlmIGl0ZW1zZXQuaXNzdWJzZXQodHJhbnNhY3Rpb24pKQ0KICAgIHJldHVybiBjb3VudCAvIGxlbih0cmFuc2FjdGlvbnMpDQoNCmRlZiBjb25maWRlbmNlKGl0ZW1zZXRfWCwgaXRlbXNldF9ZLCB0cmFuc2FjdGlvbnMpOg0KICAgIHJldHVybiBzdXBwb3J0KGl0ZW1zZXRfWCB8IGl0ZW1zZXRfWSwgdHJhbnNhY3Rpb25zKSAvIHN1cHBvcnQoaXRlbXNldF9YLCB0cmFuc2FjdGlvbnMpDQoNCiMgQ2FsY3VsYXRlIHN1cHBvcnQgYW5kIGNvbmZpZGVuY2UgZm9yIGEgcnVsZQ0KaXRlbXNldF9YID0geydtaWxrJywgJ2RpYXBlcnMnfQ0KaXRlbXNldF9ZID0geydiZWVyJ30NCg0KcnVsZV9zdXBwb3J0ID0gc3VwcG9ydChpdGVtc2V0X1ggfCBpdGVtc2V0X1ksIHRyYW5zYWN0aW9ucykNCnJ1bGVfY29uZmlkZW5jZSA9IGNvbmZpZGVuY2UoaXRlbXNldF9YLCBpdGVtc2V0X1ksIHRyYW5zYWN0aW9ucykNCg0KcHJpbnQoIlN1cHBvcnQ6IiwgcnVsZV9zdXBwb3J0KQ0KcHJpbnQoIkNvbmZpZGVuY2U6IiwgcnVsZV9jb25maWRlbmNlKQ0KYGBgDQoNCiMjIyMgVmlzdWFsaXppbmcgdGhlIENhbmRpZGF0ZSBMYXR0aWNlDQoNClRvIHZpc3VhbGl6ZSBhICoqY2FuZGlkYXRlIGxhdHRpY2UqKiBhbmQgb3B0aW1pemUgY29tcHV0YXRpb246IDEuIExpc3QNCml0ZW1zIGluIGEgaGllcmFyY2h5IChlLmcuLCBzaW5nbGUgaXRlbXMsIHBhaXJzLCB0cmlwbGVzKS4gMi4gVXNlIHRoZQ0KKipBcHJpb3JpIFByaW5jaXBsZSoqIHRvIHBydW5lIGluZnJlcXVlbnQgaXRlbXNldHMuDQoNCmBgYCBweXRob24NCmltcG9ydCBuZXR3b3JreCBhcyBueA0KaW1wb3J0IG1hdHBsb3RsaWIucHlwbG90IGFzIHBsdA0KDQojIEV4YW1wbGUgb2YgY2FuZGlkYXRlIGxhdHRpY2UgdmlzdWFsaXphdGlvbg0KRyA9IG54LkRpR3JhcGgoKQ0KRy5hZGRfZWRnZXNfZnJvbShbDQogICAgKCJBIiwgIkFCIiksICgiQSIsICJBQyIpLCAoIkIiLCAiQUIiKSwgDQogICAgKCJCIiwgIkJDIiksICgiQyIsICJBQyIpLCAoIkMiLCAiQkMiKSwNCiAgICAoIkFCIiwgIkFCQyIpLCAoIkFDIiwgIkFCQyIpLCAoIkJDIiwgIkFCQyIpDQpdKQ0KDQpwbHQuZmlndXJlKGZpZ3NpemU9KDgsIDYpKQ0KbnguZHJhdyhHLCB3aXRoX2xhYmVscz1UcnVlLCBub2RlX3NpemU9MzAwMCwgbm9kZV9jb2xvcj0ibGlnaHRibHVlIiwgZm9udF9zaXplPTEwLCBmb250X3dlaWdodD0iYm9sZCIpDQpwbHQuc2hvdygpDQpgYGANCg0KIyMjIFN1bW1hcnkNCg0KLSAgICoqQXNzb2NpYXRpb24gcnVsZSBtaW5pbmcqKiBmaW5kcyBjby1vY2N1cnJlbmNlIHBhdHRlcm5zIGluDQogICAgdHJhbnNhY3Rpb24gZGF0YS4NCi0gICAqKlN1cHBvcnQqKiBhbmQgKipjb25maWRlbmNlKiogaGVscCBxdWFudGlmeSBob3cgc3Ryb25nbHkgaXRlbXMgYXJlDQogICAgYXNzb2NpYXRlZC4NCi0gICAqKkFwcmlvcmkgUHJpbmNpcGxlKiogYW5kICoqY2FuZGlkYXRlIGxhdHRpY2UqKiByZWR1Y2UgY29tcHV0YXRpb25hbA0KICAgIGxvYWQsIGVuYWJsaW5nIHNjYWxhYmxlIGFuYWx5c2lzLg0KDQojIyMjIDQuIEVmZmljaWVudCBDb21wdXRhdGlvbiB3aXRoIHRoZSBBcHJpb3JpIFByaW5jaXBsZQ0KDQpUaGUgQXByaW9yaSBhbGdvcml0aG0gb3B0aW1pemVzIGZyZXF1ZW50IGl0ZW1zZXQgZ2VuZXJhdGlvbiBieQ0KZWxpbWluYXRpbmcgbm9uLWZyZXF1ZW50IGl0ZW1zZXRzIGFuZCB0aGVpciBzdXBlcnNldHMgZWFybHkgaW4gdGhlDQpwcm9jZXNzLiBUaGlzIHJlZHVjZXMgdGhlIHNlYXJjaCBzcGFjZSBhbmQgbWFrZXMgcnVsZSBnZW5lcmF0aW9uDQpjb21wdXRhdGlvbmFsbHkgZmVhc2libGUgZm9yIGxhcmdlIGRhdGFzZXRzLCBvZnRlbiB2aXN1YWxpemVkIGFzIGENCioqY2FuZGlkYXRlIGxhdHRpY2UqKiB0byB0cmFjayB0aGUgaGllcmFyY2hpY2FsIHByb2dyZXNzaW9uIG9mIGl0ZW0NCmNvbWJpbmF0aW9ucy4NCg0KIyMjIyA1LiBJbXBsZW1lbnRhdGlvbiBFeGFtcGxlIGluIFB5dGhvbg0KDQpUbyBjb21wdXRlIHN1cHBvcnQgYW5kIGNvbmZpZGVuY2UgZm9yIHNwZWNpZmljIHJ1bGVzIGluIFB5dGhvbjoNCg0KYGBgIHB5dGhvbg0KZnJvbSBpdGVydG9vbHMgaW1wb3J0IGNvbWJpbmF0aW9ucw0KDQojIEV4YW1wbGUgdHJhbnNhY3Rpb25zDQp0cmFuc2FjdGlvbnMgPSBbDQogICAgeydtaWxrJywgJ2JyZWFkJywgJ2JlZXInfSwNCiAgICB7J21pbGsnLCAnZGlhcGVycycsICdiZWVyJ30sDQogICAgeydtaWxrJywgJ2JyZWFkJywgJ2RpYXBlcnMnfSwNCiAgICB7J2JyZWFkJywgJ2RpYXBlcnMnLCAnYmVlcid9LA0KICAgIHsnbWlsaycsICdicmVhZCd9DQpdDQoNCiMgRnVuY3Rpb24gdG8gY2FsY3VsYXRlIHN1cHBvcnQNCmRlZiBzdXBwb3J0KGl0ZW1zZXQsIHRyYW5zYWN0aW9ucyk6DQogICAgY291bnQgPSBzdW0oMSBmb3IgdHJhbnNhY3Rpb24gaW4gdHJhbnNhY3Rpb25zIGlmIGl0ZW1zZXQuaXNzdWJzZXQodHJhbnNhY3Rpb24pKQ0KICAgIHJldHVybiBjb3VudCAvIGxlbih0cmFuc2FjdGlvbnMpDQoNCiMgRnVuY3Rpb24gdG8gY2FsY3VsYXRlIGNvbmZpZGVuY2UNCmRlZiBjb25maWRlbmNlKGl0ZW1zZXRfWCwgaXRlbXNldF9ZLCB0cmFuc2FjdGlvbnMpOg0KICAgIHJldHVybiBzdXBwb3J0KGl0ZW1zZXRfWCB8IGl0ZW1zZXRfWSwgdHJhbnNhY3Rpb25zKSAvIHN1cHBvcnQoaXRlbXNldF9YLCB0cmFuc2FjdGlvbnMpDQoNCiMgQ2FsY3VsYXRlIHN1cHBvcnQgYW5kIGNvbmZpZGVuY2UgZm9yIGEgcnVsZQ0KaXRlbXNldF9YID0geydtaWxrJywgJ2RpYXBlcnMnfQ0KaXRlbXNldF9ZID0geydiZWVyJ30NCg0KcnVsZV9zdXBwb3J0ID0gc3VwcG9ydChpdGVtc2V0X1ggfCBpdGVtc2V0X1ksIHRyYW5zYWN0aW9ucykNCnJ1bGVfY29uZmlkZW5jZSA9IGNvbmZpZGVuY2UoaXRlbXNldF9YLCBpdGVtc2V0X1ksIHRyYW5zYWN0aW9ucykNCg0KcHJpbnQoIlN1cHBvcnQ6IiwgcnVsZV9zdXBwb3J0KQ0KcHJpbnQoIkNvbmZpZGVuY2U6IiwgcnVsZV9jb25maWRlbmNlKQ0KYGBgDQoNClRoaXMgY29kZSBwcm92aWRlcyBhIHN0cnVjdHVyZWQgd2F5IHRvIGNhbGN1bGF0ZSBzdXBwb3J0IGFuZCBjb25maWRlbmNlDQpmb3IgYSBydWxlIHN1Y2ggYXMgYHttaWxrLCBkaWFwZXJzfSDihpIgYmVlcmAsIGhlbHBpbmcgaWRlbnRpZnkgbWVhbmluZ2Z1bA0KYXNzb2NpYXRpb25zIGluIHRyYW5zYWN0aW9uIGRhdGEuDQoNCiMjIyMgNi4gVmlzdWFsaXppbmcgQ2FuZGlkYXRlIExhdHRpY2VzDQoNClRoZSBjYW5kaWRhdGUgbGF0dGljZSBvcmdhbml6ZXMgaXRlbXNldHMgYnkgdGhlaXIgZnJlcXVlbmN5LCBhaWRpbmcgaW4NCnRoZSBhcHBsaWNhdGlvbiBvZiB0aGUgQXByaW9yaSBwcmluY2lwbGUuIFZpc3VhbGl6aW5nIGl0IHdpdGggTmV0d29ya1g6DQoNCmBgYCBweXRob24NCmltcG9ydCBuZXR3b3JreCBhcyBueA0KaW1wb3J0IG1hdHBsb3RsaWIucHlwbG90IGFzIHBsdA0KDQpHID0gbnguRGlHcmFwaCgpDQpHLmFkZF9lZGdlc19mcm9tKFsNCiAgICAoIkEiLCAiQUIiKSwgKCJBIiwgIkFDIiksICgiQiIsICJBQiIpLCANCiAgICAoIkIiLCAiQkMiKSwgKCJDIiwgIkFDIiksICgiQyIsICJCQyIpLA0KICAgICgiQUIiLCAiQUJDIiksICgiQUMiLCAiQUJDIiksICgiQkMiLCAiQUJDIikNCl0pDQoNCnBsdC5maWd1cmUoZmlnc2l6ZT0oOCwgNikpDQpueC5kcmF3KEcsIHdpdGhfbGFiZWxzPVRydWUsIG5vZGVfc2l6ZT0zMDAwLCBmb250X3NpemU9MTAsIGZvbnRfd2VpZ2h0PSJib2xkIikNCnBsdC5zaG93KCkNCmBgYA0KDQojIyMgUmVjYXANCg0KQXNzb2NpYXRpb24gcnVsZSBtaW5pbmcsIHN1cHBvcnRlZCBieSBlZmZpY2llbnQgaXRlbXNldCBnZW5lcmF0aW9uDQp0aHJvdWdoIHRoZSBBcHJpb3JpIHByaW5jaXBsZSBhbmQgY2FuZGlkYXRlIGxhdHRpY2VzLCBlbmFibGVzIHNjYWxhYmxlDQphbmFseXNpcyBvZiB0cmFuc2FjdGlvbmFsIGRhdGEsIHJldmVhbGluZyBwYXR0ZXJucyB3aXRoIHByYWN0aWNhbA0KYXBwbGljYXRpb25zIGFjcm9zcyBpbmR1c3RyaWVzLg0KDQpUaGVzZSBjb25jZXB0cyBjYW4gYmUgdXNlZCB0byBpZGVudGlmeSB2YWx1YWJsZSBwYXR0ZXJucyB0aGF0IGNhbiBiZQ0KYXBwbGllZCBpbiBidXNpbmVzcywgaGVhbHRoY2FyZSwgYW5kIG90aGVyIGRvbWFpbnMgcmVxdWlyaW5nDQpjby1vY2N1cnJlbmNlIGFuYWx5c2lzLg0KDQojIyMgUXVpeiAxOg0KDQpUaGUgKipBcHJpb3JpIHByaW5jaXBsZSoqIHN0YXRlcyB0aGF0IGlmIGFuIGl0ZW1zZXQgaXMgaW5mcmVxdWVudCwgYW55DQpsYXJnZXIgaXRlbXNldHMgY29udGFpbmluZyBpdCBhcyBhIHN1YnNldCBtdXN0IGFsc28gYmUgaW5mcmVxdWVudC4gU2luY2UNCndlIGtub3cgdGhhdCAqKkFDRSoqIGlzIGluZnJlcXVlbnQsIG9ubHkgdGhvc2UgaXRlbXNldHMgdGhhdCBjb250YWluDQoqKkFDRSoqIGFzIGEgc3Vic2V0IGNhbiBpbW1lZGlhdGVseSBiZSBjb25jbHVkZWQgdG8gYmUgaW5mcmVxdWVudC4NCg0KSGVyZSdzIGhvdyB0aGlzIHJlYXNvbmluZyBhcHBsaWVzIHRvIGVhY2ggb3B0aW9uOg0KDQoxLiAgKipBQkNFIGFuZCBBQkNERSoqOg0KICAgIC0gICBUaGVzZSBjb250YWluICoqQUNFKiogYXMgYSBzdWJzZXQsIGJ1dCBzaW5jZSB3ZSBhcmUgY29uc2lkZXJpbmcNCiAgICAgICAgaW5mcmVxdWVuY3ksIHdlIGFyZSBub3QgZm9jdXNpbmcgb24gc2V0cyB0aGF0IG1peCBhZGRpdGlvbmFsDQogICAgICAgIGl0ZW1zIGJleW9uZCAqKkFDRSoqIHdpdGggKipCKiogYW5kICoqRCoqIHVubmVjZXNzYXJpbHkuDQoyLiAgKipBQ0RFKio6DQogICAgLSAgIFRoaXMgaXRlbXNldCBjb250YWlucyAqKkFDRSoqIGFzIGEgc3Vic2V0ICh3aXRoIHRoZSBhZGRpdGlvbiBvZg0KICAgICAgICAqKkQqKikgYW5kIHRodXMgZGlyZWN0bHkgZm9sbG93cyBmcm9tIHRoZSBBcHJpb3JpIHByaW5jaXBsZS4gSWYNCiAgICAgICAgKipBQ0UqKiBpcyBpbmZyZXF1ZW50LCAqKkFDREUqKiBtdXN0IGFsc28gYmUgaW5mcmVxdWVudCBiZWNhdXNlDQogICAgICAgIGl0IGluY2x1ZGVzIGFsbCBpdGVtcyBvZiAqKkFDRSoqIHBsdXMgYW4gYWRkaXRpb25hbCBpdGVtLCAqKkQqKi4NCjMuICAqKkJDREUgb25seSoqOg0KICAgIC0gICBUaGlzIGl0ZW1zZXQgZG9lcyBub3QgY29udGFpbiAqKkFDRSoqIGFzIGEgc3Vic2V0LCBzbyB3ZSBjYW5ub3QNCiAgICAgICAgY29uY2x1ZGUgYW55dGhpbmcgYWJvdXQgaXRzIGZyZXF1ZW5jeSBiYXNlZCBvbiB0aGUgaW5mcmVxdWVuY3kNCiAgICAgICAgb2YgKipBQ0UqKiBhbG9uZS4NCg0KVGhlcmVmb3JlLCB0aGUgY29ycmVjdCBhbnN3ZXIgaXMgKipBQ0RFIG9ubHkqKiBiZWNhdXNlIGl0IGlzIHRoZSBvbmx5DQppdGVtc2V0IGluIHRoZSBjaG9pY2VzIHByb3ZpZGVkIHRoYXQgaW5jbHVkZXMgKipBQ0UqKiBhcyBhIHN1YnNldCwNCm1ha2luZyBpdCBkaXJlY3RseSBpbmZlcnJhYmxlIGFzIGluZnJlcXVlbnQgYmFzZWQgb24gdGhlIEFwcmlvcmkNCnByaW5jaXBsZS4NCg0KIVtGcmVxdWVudCBJdGVtc2V0IEdlbmVyYXRpb246XShpbWFnZXMvTVNEUzczMzEtMTIuNC40LTAxLnBuZykNCg0KVGhpcyBkaWFncmFtIHNob3dzIGEgY2FuZGlkYXRlIGxhdHRpY2UgZm9yIGl0ZW1zZXRzIHdoZXJlICoqQUIqKiBoYXMNCmJlZW4gZm91bmQgdG8gYmUgaW5mcmVxdWVudC4gQWNjb3JkaW5nIHRvIHRoZSAqKkFwcmlvcmkgcHJpbmNpcGxlKiosIGFueQ0Kc3VwZXJzZXRzIG9mIGFuIGluZnJlcXVlbnQgaXRlbXNldCBhcmUgYWxzbyBpbmZyZXF1ZW50Lg0KDQoxLiAgKipBQioqIGlzIGluZnJlcXVlbnQsIHNvIGFsbCBpdGVtc2V0cyBjb250YWluaW5nICoqQUIqKiAobGlrZQ0KICAgICoqQUJDKiosICoqQUJEKiosICoqQUJFKiosICoqQUJDRCoqLCAqKkFCQ0UqKiwgKipBQkRFKiosIGFuZA0KICAgICoqQUJDREUqKikgYXJlIHBydW5lZCAobWFya2VkIHdpdGggdGhlIHBpbmsgZGFzaGVkIGxpbmUpIGFuZA0KICAgIGNvbnNpZGVyZWQgaW5mcmVxdWVudCB3aXRob3V0IGZ1cnRoZXIgY2hlY2tpbmcuDQoNCjIuICBUaGUgaXRlbXNldCAqKkFDREUqKiAoaGlnaGxpZ2h0ZWQgaW4geWVsbG93KSBkb2VzIG5vdCBpbmNsdWRlICoqQUIqKg0KICAgIGFzIGEgc3Vic2V0LCBzbyBpdCBpcyBub3QgYWZmZWN0ZWQgYnkgdGhlIGluZnJlcXVlbmN5IG9mICoqQUIqKi4NCiAgICBUaGlzIG1lYW5zICoqQUNERSoqIGlzIGV2YWx1YXRlZCBzZXBhcmF0ZWx5IGZyb20gKipBQioqIGFuZCBpdHMNCiAgICBzdXBlcnNldHMuDQoNCjMuICBTbywgT25seSBzdXBlcnNldHMgY29udGFpbmluZyAqKkFCKiogYXJlIGltbWVkaWF0ZWx5IHBydW5lZC4gLQ0KICAgICoqQUNERSoqIHJlbWFpbnMgdW5hZmZlY3RlZCBiZWNhdXNlIGl0IGRvZXMgbm90IGNvbnRhaW4gKipBQioqLg0KDQojIyBDb250aW51aW5nDQoNCkhlcmXigJlzIGFuIGVuaGFuY2VkICoqc3R1ZHkgZ3VpZGUqKiBmb3IgKipBc3NvY2lhdGlvbiBSdWxlIE1pbmluZyBhbmQNCkZyZXF1ZW50IEl0ZW1zZXQgR2VuZXJhdGlvbioqLCBpbmNsdWRpbmcgYWRkaXRpb25hbCBjb250ZXh0IGZyb20gdGhlDQpsYXRlc3QgZmlsZS4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyBTdHVkeSBHdWlkZTogQXNzb2NpYXRpb24gUnVsZSBNaW5pbmcgYW5kIEZyZXF1ZW50IEl0ZW1zZXQgR2VuZXJhdGlvbg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjIDEuICoqT3ZlcnZpZXcgb2YgQXNzb2NpYXRpb24gUnVsZSBNaW5pbmcqKg0KDQotICAgKipQdXJwb3NlKio6IElkZW50aWZ5IHBhdHRlcm5zIGluIGxhcmdlIGRhdGFzZXRzIHRvIHJldmVhbA0KICAgIHJlbGF0aW9uc2hpcHMgYmV0d2VlbiBpdGVtcy4gQXBwbGljYXRpb25zIGluY2x1ZGUgKiptYXJrZXQgYmFza2V0DQogICAgYW5hbHlzaXMqKiBhbmQgKipyZWNvbW1lbmRhdGlvbiBzeXN0ZW1zKiouDQotICAgKipFeGFtcGxlKio6IFJldGFpbCB0cmFuc2FjdGlvbnMgbWF5IHNob3cgdGhhdCBidXlpbmcgbWlsayBhbmQgYnJlYWQNCiAgICBvZnRlbiBjb3JyZWxhdGVzIHdpdGggcHVyY2hhc2luZyBlZ2dzLCBhaWRpbmcgaW52ZW50b3J5IG1hbmFnZW1lbnQNCiAgICBhbmQgbWFya2V0aW5nIHN0cmF0ZWdpZXMuDQoNCiMjIyMgS2V5IERlZmluaXRpb25zOg0KDQotICAgKipBc3NvY2lhdGlvbiBSdWxlKio6IEEgcnVsZSBzaG93aW5nIHdoZW4gb25lIGl0ZW0gb3IgaXRlbXNldA0KICAgIGNvLW9jY3VycyB3aXRoIGFub3RoZXIuDQoNCi0gICAqKkZyZXF1ZW50IEl0ZW1zZXQqKjogQSBzZXQgb2YgaXRlbXMgdGhhdCBhcHBlYXJzIGZyZXF1ZW50bHkNCiAgICB0b2dldGhlciBpbiBhIGRhdGFzZXQuDQoNCi0gICAqKlN1cHBvcnQqKjogUHJvcG9ydGlvbiBvZiB0cmFuc2FjdGlvbnMgY29udGFpbmluZyBhbiBpdGVtc2V0Lg0KICAgIEZvcm11bGE6ICQkDQogICAgXHRleHR7U3VwcG9ydH0oWCkgPSBcZnJhY3tcdGV4dHtGcmVxdWVuY3kgb2YgfSBYfXtcdGV4dHtUb3RhbCBUcmFuc2FjdGlvbnN9fQ0KICAgICQkDQoNCi0gICAqKkNvbmZpZGVuY2UqKjogUmVsaWFiaWxpdHkgb2YgYW4gYXNzb2NpYXRpb24gcnVsZSwgc2hvd2luZyBob3cNCiAgICBvZnRlbiAkWSQgYXBwZWFycyBpbiB0cmFuc2FjdGlvbnMgY29udGFpbmluZyAkWCQuIEZvcm11bGE6ICQkDQogICAgXHRleHR7Q29uZmlkZW5jZX0oWCBccmlnaHRhcnJvdyBZKSA9IFxmcmFje1x0ZXh0e1N1cHBvcnR9KFggXGN1cCBZKX17XHRleHR7U3VwcG9ydH0oWCl9DQogICAgJCQNCg0KLSAgICoqT2JqZWN0aXZlKio6IEFzc29jaWF0aW9uIGFuYWx5c2lzIHVuY292ZXJzIHBhdHRlcm5zIGluIHRyYW5zYWN0aW9uDQogICAgZGF0YSwgb2Z0ZW4gZm9ybXVsYXRlZCBhcyAqKmFzc29jaWF0aW9uIHJ1bGVzKiogJFggXHJpZ2h0YXJyb3cgWSQsDQogICAgd2hlcmUgdGhlIHByZXNlbmNlIG9mICRYJCBzdWdnZXN0cyB0aGUgbGlrZWxpaG9vZCBvZiAkWSQuDQoNCi0gICAqKkFwcGxpY2F0aW9ucyoqOiBXaWRlbHkgdXNlZCBpbiByZXRhaWwgKGUuZy4sIG1hcmtldCBiYXNrZXQNCiAgICBhbmFseXNpcyksIGhlYWx0aGNhcmUgKGNvLW9jY3VycmVuY2Ugb2Ygc3ltcHRvbXMpLCBhbmQgZmluYW5jZQ0KICAgICh0cmFuc2FjdGlvbiBwYXR0ZXJuIGFuYWx5c2lzKS4NCg0KIyMjIyBLZXkgTWV0cmljczoNCg0KLSAgICoqU3VwcG9ydCoqOiBGcmVxdWVuY3kgb2YgYW4gaXRlbXNldOKAmXMgb2NjdXJyZW5jZSBpbiB0cmFuc2FjdGlvbnMuDQogICAgJCQNCiAgICBcdGV4dHtTdXBwb3J0fShYKSA9IFxmcmFje1x0ZXh0e1RyYW5zYWN0aW9ucyBjb250YWluaW5nIH0gWH17XHRleHR7VG90YWwgVHJhbnNhY3Rpb25zfX0NCiAgICAkJA0KLSAgICoqQ29uZmlkZW5jZSoqOiBMaWtlbGlob29kIHRoYXQgJFkkIG9jY3VycyBnaXZlbiAkWCQuICQkDQogICAgXHRleHR7Q29uZmlkZW5jZX0oWCBccmlnaHRhcnJvdyBZKSA9IFxmcmFje1x0ZXh0e1N1cHBvcnR9KFggXGN1cCBZKX17XHRleHR7U3VwcG9ydH0oWCl9DQogICAgJCQNCi0gICAqKkxpZnQqKjogU3RyZW5ndGggb2YgYXNzb2NpYXRpb24sIGNvbXBhcmluZyBvYnNlcnZlZCB0byBleHBlY3RlZA0KICAgIGNvLW9jY3VycmVuY2UuICQkDQogICAgXHRleHR7TGlmdH0oWCBccmlnaHRhcnJvdyBZKSA9IFxmcmFje1x0ZXh0e0NvbmZpZGVuY2V9KFggXHJpZ2h0YXJyb3cgWSl9e1x0ZXh0e1N1cHBvcnR9KFkpfQ0KICAgICQkDQoNCiMjIyAyLiAqKkZyZXF1ZW50IEl0ZW1zZXQgR2VuZXJhdGlvbiBVc2luZyB0aGUgQXByaW9yaSBBbGdvcml0aG0qKg0KDQpUaGUgKipBcHJpb3JpIEFsZ29yaXRobSoqIGl0ZXJhdGl2ZWx5IGlkZW50aWZpZXMgZnJlcXVlbnQgaXRlbXNldHMgYnkNCmxldmVyYWdpbmcgc21hbGxlciwgZnJlcXVlbnQgc3Vic2V0cy4NCg0KMS4gICoqQ2FuZGlkYXRlIEl0ZW1zZXRzKio6IFN0YXJ0IHdpdGggc2luZ2xlIGZyZXF1ZW50IGl0ZW1zLCBhbmQNCiAgICBjb21iaW5lIHRoZW0gaXRlcmF0aXZlbHkgdG8gZ2VuZXJhdGUgbGFyZ2VyIGl0ZW1zZXRzLg0KMi4gICoqQXByaW9yaSBQcmluY2lwbGUqKjogSWYgYW4gaXRlbXNldCBpcyBpbmZyZXF1ZW50LCBhbGwgc3VwZXJzZXRzDQogICAgYXJlIGFsc28gaW5mcmVxdWVudCwgcmVkdWNpbmcgc2VhcmNoIHNwYWNlLg0KMy4gICoqU3VwcG9ydCBDb3VudCoqOiBDb3VudCBvY2N1cnJlbmNlcyBvZiBlYWNoIGNhbmRpZGF0ZSBpdGVtc2V0LA0KICAgIHBydW5pbmcgdGhvc2UgYmVsb3cgdGhlIG1pbmltdW0gc3VwcG9ydCB0aHJlc2hvbGQuDQo0LiAgKipJdGVyYXRpdmUgUHJ1bmluZyoqOiBSZXBlYXQgdGhlIHByb2Nlc3MgZm9yIGstaXRlbXNldHMgdW50aWwgbm8NCiAgICBmdXJ0aGVyIGZyZXF1ZW50IGl0ZW1zZXRzIHJlbWFpbi4NCg0KIyMjIDMuICoqU3RlcHMgaW4gQXNzb2NpYXRpb24gUnVsZSBNaW5pbmcqKg0KDQojIyMjIFN0ZXAgMTogKipGcmVxdWVudCBJdGVtc2V0IEdlbmVyYXRpb24qKg0KDQotICAgKipPYmplY3RpdmUqKjogSWRlbnRpZnkgaXRlbXNldHMgbWVldGluZyBtaW5pbXVtIHN1cHBvcnQgdGhyZXNob2xkcy4NCi0gICAqKkNhbmRpZGF0ZSBMYXR0aWNlKio6IFN0cnVjdHVyZXMgcG9zc2libGUgaXRlbXNldHMgaW4gbGV2ZWxzDQogICAgKGluZGl2aWR1YWwgaXRlbXMsIHBhaXJzLCB0cmlwbGV0cykgdG8gZmlsdGVyIGJhc2VkIG9uIHN1cHBvcnQuDQoNCiMjIyMgU3RlcCAyOiAqKlJ1bGUgR2VuZXJhdGlvbiBmcm9tIEZyZXF1ZW50IEl0ZW1zZXRzKioNCg0KLSAgIFVzZSBmcmVxdWVudCBpdGVtc2V0cyB0byBnZW5lcmF0ZSAiSWYtVGhlbiIgYXNzb2NpYXRpb24gcnVsZXMsDQogICAgYXBwbHlpbmcgbWluaW11bSBjb25maWRlbmNlIGFuZCBzdXBwb3J0IHRvIHJldGFpbiBzaWduaWZpY2FudCBydWxlcy4NCg0KIyMjIDQuICoqRWZmaWNpZW5jeSBJbXByb3ZlbWVudHMgaW4gSXRlbXNldCBHZW5lcmF0aW9uKioNCg0KIyMjIyBBLiAqKkFwcmlvcmkgUHJpbmNpcGxlKio6DQoNCi0gICAqKkNvbmNlcHQqKjogSWYgYW4gaXRlbXNldCBpcyBpbmZyZXF1ZW50LCBhbGwgaXRzIHN1cGVyc2V0cyBhcmUgYWxzbw0KICAgIGluZnJlcXVlbnQuDQotICAgKipBcHBsaWNhdGlvbioqOiBJZiBge0FDRX1gIGlzIGluZnJlcXVlbnQsIHRoZW4gYWxsIHN1cGVyc2V0cw0KICAgIGB7QUJDRX1gLCBge0FDREV9YCwgYW5kIGB7QUJDREV9YCBjYW4gYmUgc2tpcHBlZC4NCg0KIyMjIyBCLiAqKkZQLUdyb3d0aCAoRnJlcXVlbnQgUGF0dGVybiBHcm93dGgpKio6DQoNCi0gICBBbiBlZmZpY2llbnQgYWx0ZXJuYXRpdmUgdG8gQXByaW9yaSB1c2luZyBhbiAqKkZQLXRyZWUqKiBzdHJ1Y3R1cmUuDQotICAgRlAtdHJlZSBvcmdhbml6ZXMgdHJhbnNhY3Rpb25zIGluIHR3byBwYXNzZXMsIGVuYWJsaW5nIGZyZXF1ZW50DQogICAgaXRlbXNldCBtaW5pbmcgd2l0aG91dCBleHBsaWNpdGx5IGdlbmVyYXRpbmcgY2FuZGlkYXRlIHNldHMuDQoNCiMjIyA1LiAqKkZyZXF1ZW50IEl0ZW1zZXQgR2VuZXJhdGlvbiB3aXRoIEFwcmlvcmkqKg0KDQojIyMjIFRoZSBBcHJpb3JpIEFsZ29yaXRobToNCg0KLSAgICoqU3RlcC1ieS1TdGVwKio6DQogICAgMS4gIFN0YXJ0IHdpdGggayA9IDEgZm9yIHNpbmdsZSBpdGVtcyBhbmQgZmluZCBmcmVxdWVudCBpdGVtc2V0cy4NCiAgICAyLiAgR2VuZXJhdGUgaXRlbXNldHMgb2YgbGVuZ3RoICRrKzEkIGZyb20gZnJlcXVlbnQgJGskLWl0ZW1zZXRzLg0KICAgIDMuICBQcnVuZSBpbmZyZXF1ZW50IHNldHMgYW5kIHJlcGVhdCB1bnRpbCBubyBtb3JlIGZyZXF1ZW50IGl0ZW1zZXRzDQogICAgICAgIGFyZSBmb3VuZC4NCi0gICAqKkNvbXBsZXhpdHkqKjogRWFjaCBpdGVyYXRpb24gb3ZlciB0aGUgZGF0YXNldCAoZXNwZWNpYWxseSBmb3INCiAgICBsYXJnZSBkYXRhc2V0cykgaW5jcmVhc2VzIGNvbXB1dGF0aW9uYWwgY29zdHMsIG1vdGl2YXRpbmcgdGhlIHVzZSBvZg0KICAgIGVmZmljaWVudCBkYXRhIHN0cnVjdHVyZXMgYW5kIHBydW5pbmcgbWV0aG9kcy4NCg0KIyMjIDYuICoqUmVkdWNpbmcgQ29tcGxleGl0eSB3aXRoIEhhc2ggVHJlZXMqKg0KDQotICAgKipIYXNoIFRyZWUqKjogVXNlZCBpbiBBcHJpb3JpIHRvIHJlZHVjZSBjb21wYXJpc29uIGNvc3RzIGJ5DQogICAgb3JnYW5pemluZyBpdGVtc2V0cyBlZmZpY2llbnRseSwgY2hlY2tpbmcgb25seSByZWxldmFudCBjYW5kaWRhdGVzDQogICAgcGVyIHRyYW5zYWN0aW9uLg0KLSAgICoqRnVuY3Rpb25hbGl0eSoqOiBEaXZpZGVzIGl0ZW1zIGJhc2VkIG9uIGhhc2ggdmFsdWVzIHRvIG1pbmltaXplDQogICAgY29tcGFyaXNvbiB0aW1lLg0KLSAgICoqRXhhbXBsZSoqOiBHaXZlbiB0aHJlZS1pdGVtIGNhbmRpZGF0ZSBzZXRzLCB0aGUgaGFzaCB0cmVlIGZpbHRlcnMNCiAgICBiYXNlZCBvbiB0aGUgZmlyc3QgaXRlbeKAmXMgaGFzaCBhbmQgc3BsaXRzIG5vZGVzIGFzIG5lZWRlZCB0byBlbnN1cmUNCiAgICBvcHRpbWFsIGFjY2VzcyB0aW1lLg0KDQojIyMgNy4gKipDb21wbGV4aXR5IFJlZHVjdGlvbiBpbiBBcHJpb3JpKioNCg0KLSAgICoqQ2FuZGlkYXRlIFBydW5pbmcqKjogQnkgYXBwbHlpbmcgdGhlIEFwcmlvcmkgcHJpbmNpcGxlLCBhdm9pZA0KICAgIHRlc3RpbmcgaXRlbXNldHMgdGhhdCBpbmNsdWRlIGluZnJlcXVlbnQgc3Vic2V0cy4NCi0gICAqKkhhc2ggVHJlZXMqKjogSGFzaCB0cmVlcyBzdG9yZSBjYW5kaWRhdGUgaXRlbXNldHMgZm9yIGVmZmljaWVudA0KICAgIGxvb2t1cHMsIHJlZHVjaW5nIHRoZSBjb21wYXJpc29uIGxvYWQgd2hlbiBjYWxjdWxhdGluZyBzdXBwb3J0Lg0KDQojIyMjIENvZGUgRXhhbXBsZSBmb3IgU3VwcG9ydCBDYWxjdWxhdGlvbg0KDQpgYGAgcHl0aG9uDQpmcm9tIGl0ZXJ0b29scyBpbXBvcnQgY29tYmluYXRpb25zDQoNCiMgRXhhbXBsZSB0cmFuc2FjdGlvbiBkYXRhDQp0cmFuc2FjdGlvbnMgPSBbDQogICAgeydtaWxrJywgJ2JyZWFkJywgJ2JlZXInfSwNCiAgICB7J21pbGsnLCAnZGlhcGVycycsICdiZWVyJ30sDQogICAgeydtaWxrJywgJ2JyZWFkJywgJ2RpYXBlcnMnfSwNCiAgICB7J2JyZWFkJywgJ2RpYXBlcnMnLCAnYmVlcid9LA0KICAgIHsnbWlsaycsICdicmVhZCd9DQpdDQoNCmRlZiBzdXBwb3J0KGl0ZW1zZXQsIHRyYW5zYWN0aW9ucyk6DQogICAgY291bnQgPSBzdW0oMSBmb3IgdHJhbnNhY3Rpb24gaW4gdHJhbnNhY3Rpb25zIGlmIGl0ZW1zZXQuaXNzdWJzZXQodHJhbnNhY3Rpb24pKQ0KICAgIHJldHVybiBjb3VudCAvIGxlbih0cmFuc2FjdGlvbnMpDQoNCiMgQ2FsY3VsYXRpbmcgc3VwcG9ydCBmb3Ige21pbGssIGJyZWFkfQ0KaXRlbXNldCA9IHsnbWlsaycsICdicmVhZCd9DQpwcmludCgiU3VwcG9ydDoiLCBzdXBwb3J0KGl0ZW1zZXQsIHRyYW5zYWN0aW9ucykpDQpgYGANCg0KIyMjIDguICoqRlAtR3Jvd3RoIEFsZ29yaXRobSBhbmQgUGF0dGVybiBUcmVlcyoqDQoNCi0gICAqKk92ZXJ2aWV3Kio6IFVubGlrZSBBcHJpb3JpLCBGUC1Hcm93dGggb25seSBzY2FucyB0aGUgZGF0YWJhc2UNCiAgICB0d2ljZS4NCi0gICAqKlByb2Nlc3MqKjoNCiAgICAxLiAgQ291bnQgZnJlcXVlbnQgMS1pdGVtc2V0cyBhbmQgZmlsdGVyIGJ5IG1pbmltdW0gc3VwcG9ydC4NCiAgICAyLiAgU29ydCB0cmFuc2FjdGlvbnMgYnkgaXRlbSBmcmVxdWVuY3kgYW5kIGJ1aWxkIHRoZSAqKkZQLXRyZWUqKiBieQ0KICAgICAgICBhZGRpbmcgdHJhbnNhY3Rpb25zIHNlcXVlbnRpYWxseSwgaW5jcmVtZW50aW5nIGNvdW50cyBhdA0KICAgICAgICBleGlzdGluZyBub2Rlcy4NCi0gICAqKkRpdmlkZS1hbmQtQ29ucXVlcioqOiBPbmNlIGNvbnN0cnVjdGVkLCB0aGUgdHJlZSBpcyByZWN1cnNpdmVseQ0KICAgIGRpdmlkZWQgdG8gZmluZCBmcmVxdWVudCBpdGVtc2V0cyB3aXRob3V0IHJlLXNjYW5uaW5nIHRoZSBkYXRhc2V0Lg0KDQojIyMgOS4gKipGUC1Hcm93dGggQWxnb3JpdGhtKioNCg0KVGhlICoqRlAtR3Jvd3RoIEFsZ29yaXRobSoqIG9mZmVycyBhIHNjYWxhYmxlIGFsdGVybmF0aXZlIHRvIEFwcmlvcmkgYnkNCmNvbnN0cnVjdGluZyBhICoqRnJlcXVlbnQgUGF0dGVybiBUcmVlIChGUC1UcmVlKSoqLCBlbGltaW5hdGluZyB0aGUgbmVlZA0KZm9yIG11bHRpcGxlIGRhdGFiYXNlIHNjYW5zIGFuZCBjYW5kaWRhdGUgZ2VuZXJhdGlvbi4NCg0KIyMjIyBGUC1Hcm93dGggUHJvY2VzczoNCg0KMS4gICoqRmlyc3QgUGFzcyoqOiBJZGVudGlmeSBmcmVxdWVudCAxLWl0ZW1zZXRzLCBzb3J0aW5nIHRoZW0gYnkNCiAgICBmcmVxdWVuY3kuDQoyLiAgKipGUC1UcmVlIENvbnN0cnVjdGlvbioqOiBCdWlsZCBhIHRyZWUgd2hlcmUgdHJhbnNhY3Rpb25zIGFyZQ0KICAgIGhpZXJhcmNoaWNhbGx5IG9yZ2FuaXplZCwgcmVkdWNpbmcgcmVkdW5kYW5jeS4NCjMuICAqKkRpdmlkZSBhbmQgQ29ucXVlcioqOiBSZWN1cnNpdmVseSBnZW5lcmF0ZSBjb25kaXRpb25hbCB0cmVlcyBmcm9tDQogICAgdGhlIEZQLVRyZWUgdG8gbWluZSBmcmVxdWVudCBwYXR0ZXJucy4NCg0KIyMjIyBDb2RlIEV4YW1wbGUgZm9yIEZQLVRyZWUgQ29uc3RydWN0aW9uDQoNCmBgYCBweXRob24NCmNsYXNzIFRyZWVOb2RlOg0KICAgIGRlZiBfX2luaXRfXyhzZWxmLCBuYW1lLCBjb3VudCk6DQogICAgICAgIHNlbGYubmFtZSA9IG5hbWUNCiAgICAgICAgc2VsZi5jb3VudCA9IGNvdW50DQogICAgICAgIHNlbGYucGFyZW50ID0gTm9uZQ0KICAgICAgICBzZWxmLmNoaWxkcmVuID0ge30NCg0KIyBBZGRpbmcgdHJhbnNhY3Rpb25zIHRvIHRoZSBGUC10cmVlDQpyb290ID0gVHJlZU5vZGUoIm51bGwiLCAxKQ0KdHJhbnNhY3Rpb25zID0gW1sibWlsayIsICJicmVhZCJdLCBbImJlZXIiLCAiZGlhcGVycyJdLCBbIm1pbGsiLCAiZGlhcGVycyJdXQ0KDQpkZWYgYWRkX3RyYW5zYWN0aW9uKG5vZGUsIHRyYW5zYWN0aW9uKToNCiAgICBmb3IgaXRlbSBpbiB0cmFuc2FjdGlvbjoNCiAgICAgICAgaWYgaXRlbSBpbiBub2RlLmNoaWxkcmVuOg0KICAgICAgICAgICAgbm9kZS5jaGlsZHJlbltpdGVtXS5jb3VudCArPSAxDQogICAgICAgIGVsc2U6DQogICAgICAgICAgICBjaGlsZF9ub2RlID0gVHJlZU5vZGUoaXRlbSwgMSkNCiAgICAgICAgICAgIGNoaWxkX25vZGUucGFyZW50ID0gbm9kZQ0KICAgICAgICAgICAgbm9kZS5jaGlsZHJlbltpdGVtXSA9IGNoaWxkX25vZGUNCiAgICAgICAgbm9kZSA9IG5vZGUuY2hpbGRyZW5baXRlbV0NCg0KZm9yIHRyYW5zYWN0aW9uIGluIHRyYW5zYWN0aW9uczoNCiAgICBhZGRfdHJhbnNhY3Rpb24ocm9vdCwgc29ydGVkKHRyYW5zYWN0aW9uKSkNCg0KIyBWaXN1YWxpemF0aW9uIG9mIEZQLXRyZWUNCmltcG9ydCBuZXR3b3JreCBhcyBueA0KaW1wb3J0IG1hdHBsb3RsaWIucHlwbG90IGFzIHBsdA0KDQpkZWYgdmlzdWFsaXplX3RyZWUobm9kZSwgZ3JhcGgsIHBhcmVudD1Ob25lKToNCiAgICBncmFwaC5hZGRfbm9kZShub2RlLm5hbWUpDQogICAgaWYgcGFyZW50Og0KICAgICAgICBncmFwaC5hZGRfZWRnZShwYXJlbnQsIG5vZGUubmFtZSkNCiAgICBmb3IgY2hpbGQgaW4gbm9kZS5jaGlsZHJlbi52YWx1ZXMoKToNCiAgICAgICAgdmlzdWFsaXplX3RyZWUoY2hpbGQsIGdyYXBoLCBub2RlLm5hbWUpDQoNCkcgPSBueC5EaUdyYXBoKCkNCnZpc3VhbGl6ZV90cmVlKHJvb3QsIEcpDQpwbHQuZmlndXJlKGZpZ3NpemU9KDgsIDYpKQ0KbnguZHJhdyhHLCB3aXRoX2xhYmVscz1UcnVlLCBub2RlX3NpemU9MjAwMCwgZm9udF9zaXplPTEwLCBmb250X3dlaWdodD0iYm9sZCIpDQpwbHQuc2hvdygpDQpgYGANCg0KIyMjIDEwLiAqKkNvbXBhcmlzb24gYW5kIEtleSBJbnNpZ2h0cyoqDQoNCi0gICAqKlN1cHBvcnQgVGhyZXNob2xkKio6IFNldHRpbmcgYW4gYXBwcm9wcmlhdGUgbWluaW11bSBzdXBwb3J0IGxldmVsDQogICAgaXMgY3JpdGljYWw7IGxvdyB0aHJlc2hvbGRzIGdlbmVyYXRlIGV4Y2Vzc2l2ZSBwYXR0ZXJucywgd2hpbGUgaGlnaA0KICAgIHRocmVzaG9sZHMgbWlnaHQgbWlzcyB1c2VmdWwgcGF0dGVybnMuDQotICAgKipBcHJpb3JpIHZzLiBGUC1Hcm93dGgqKjoNCiAgICAtICAgQXByaW9yaSB1c2VzIG11bHRpcGxlIGRhdGFiYXNlIHBhc3NlcyBhbmQgZXhwbGljaXQgY2FuZGlkYXRlDQogICAgICAgIGdlbmVyYXRpb24uDQogICAgLSAgIEZQLUdyb3d0aCBvcmdhbml6ZXMgaXRlbXMgaW50byBhIHRyZWUsIHNpZ25pZmljYW50bHkgcmVkdWNpbmcNCiAgICAgICAgZGF0YWJhc2UgYWNjZXNzLg0KLSAgICoqSGFzaCBUcmVlcyBpbiBBcHJpb3JpKio6IFJlZHVjZXMgdGhlIG51bWJlciBvZiBjb21wYXJpc29ucyBieQ0KICAgIGdyb3VwaW5nIGl0ZW1zIGJhc2VkIG9uIGhhc2ggdmFsdWVzLg0KLSAgICoqQ29uZGl0aW9uYWwgVHJlZXMgaW4gRlAtR3Jvd3RoKio6IFNwbGl0cyB0aGUgRlAtVHJlZSB0byBhbGxvdw0KICAgIHRhcmdldGVkLCByZWN1cnNpdmUgbWluaW5nIG9mIGl0ZW1zZXRzLCBlbmhhbmNpbmcgY29tcHV0YXRpb25hbA0KICAgIGVmZmljaWVuY3kuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgMTEuICoqUHJhY3RpY2FsIEFwcGxpY2F0aW9ucyBvZiBBc3NvY2lhdGlvbiBSdWxlIE1pbmluZyoqDQoNCi0gICAqKlJldGFpbCoqOiBQcm9kdWN0IHJlY29tbWVuZGF0aW9uIGFuZCBzaGVsZiBhcnJhbmdlbWVudCBiYXNlZCBvbg0KICAgIGl0ZW0gY28tb2NjdXJyZW5jZS4NCi0gICAqKkhlYWx0aGNhcmUqKjogSWRlbnRpZnlpbmcgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIHN5bXB0b21zIG9yDQogICAgbWVkaWNhbCBjb25kaXRpb25zLg0KLSAgICoqRmluYW5jZSoqOiBQYXR0ZXJuIGRldGVjdGlvbiBmb3IgZnJhdWQgYW5kIHVudXN1YWwgdHJhbnNhY3Rpb24NCiAgICBiZWhhdmlvcnMuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgMTIuICoqRXhhbXBsZSBDYWxjdWxhdGlvbnMqKg0KDQojIyMjIENhbGN1bGF0aW5nIFN1cHBvcnQgYW5kIENvbmZpZGVuY2UNCg0KR2l2ZW4gdHJhbnNhY3Rpb25zLCBjYWxjdWxhdGUgc3VwcG9ydCBhbmQgY29uZmlkZW5jZSBmb3INCmB7bWlsaywgYnJlYWR9IFxyaWdodGFycm93IHtlZ2dzfWAuDQoNCmBgYCBweXRob24NCiMgRXhhbXBsZSB0cmFuc2FjdGlvbnMNCnRyYW5zYWN0aW9ucyA9IFsNCiAgICB7J21pbGsnLCAnYnJlYWQnLCAnYmVlcid9LA0KICAgIHsnbWlsaycsICdicmVhZCcsICdkaWFwZXJzJ30sDQogICAgeydtaWxrJywgJ2JyZWFkJywgJ2VnZ3MnfSwNCiAgICB7J2JyZWFkJywgJ2RpYXBlcnMnLCAnZWdncyd9LA0KICAgIHsnbWlsaycsICdicmVhZCd9DQpdDQoNCiMgU3VwcG9ydCBmdW5jdGlvbg0KZGVmIHN1cHBvcnQoaXRlbXNldCwgdHJhbnNhY3Rpb25zKToNCiAgICBjb3VudCA9IHN1bSgxIGZvciB0cmFuc2FjdGlvbiBpbiB0cmFuc2FjdGlvbnMgaWYgaXRlbXNldC5pc3N1YnNldCh0cmFuc2FjdGlvbikpDQogICAgcmV0dXJuIGNvdW50IC8gbGVuKHRyYW5zYWN0aW9ucykNCg0KIyBDb25maWRlbmNlIGZ1bmN0aW9uDQpkZWYgY29uZmlkZW5jZShYLCBZLCB0cmFuc2FjdGlvbnMpOg0KICAgIHJldHVybiBzdXBwb3J0KFggfCBZLCB0cmFuc2FjdGlvbnMpIC8gc3VwcG9ydChYLCB0cmFuc2FjdGlvbnMpDQoNCiMgQ2FsY3VsYXRlIHN1cHBvcnQgYW5kIGNvbmZpZGVuY2UNCml0ZW1zZXRfWCA9IHsnbWlsaycsICdicmVhZCd9DQppdGVtc2V0X1kgPSB7J2VnZ3MnfQ0KcHJpbnQoIlN1cHBvcnQ6Iiwgc3VwcG9ydChpdGVtc2V0X1ggfCBpdGVtc2V0X1ksIHRyYW5zYWN0aW9ucykpDQpwcmludCgiQ29uZmlkZW5jZToiLCBjb25maWRlbmNlKGl0ZW1zZXRfWCwgaXRlbXNldF9ZLCB0cmFuc2FjdGlvbnMpKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgMTMuICoqVmlzdWFsaXphdGlvbjogQ2FuZGlkYXRlIExhdHRpY2UqKg0KDQpgYGAgcHl0aG9uDQppbXBvcnQgbmV0d29ya3ggYXMgbngNCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQNCg0KIyBWaXN1YWxpemF0aW9uIG9mIGEgY2FuZGlkYXRlIGxhdHRpY2UNCkcgPSBueC5EaUdyYXBoKCkNCkcuYWRkX2VkZ2VzX2Zyb20oWw0KICAgICgiQSIsICJBQiIpLCAoIkEiLCAiQUMiKSwgKCJCIiwgIkFCIiksIA0KICAgICgiQiIsICJCQyIpLCAoIkMiLCAiQUMiKSwgKCJDIiwgIkJDIiksDQogICAgKCJBQiIsICJBQkMiKSwgKCJBQyIsICJBQkMiKSwgKCJCQyIsICJBQkMiKQ0KXSkNCg0KcGx0LmZpZ3VyZShmaWdzaXplPSg4LCA2KSkNCm54LmRyYXcoRywgd2l0aF9sYWJlbHM9VHJ1ZSwgbm9kZV9zaXplPTMwMDAsIG5vZGVfY29sb3I9InNreWJsdWUiLCBmb250X3NpemU9MTAsIGZvbnRfd2VpZ2h0PSJib2xkIikNCnBsdC50aXRsZSgiQ2FuZGlkYXRlIExhdHRpY2UgZm9yIEl0ZW1zZXQgR2VuZXJhdGlvbiIpDQpwbHQuc2hvdygpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyBSZWNhcA0KDQoxLiAgKipBc3NvY2lhdGlvbiBSdWxlIE1pbmluZyoqIGlkZW50aWZpZXMgY28tb2NjdXJyZW5jZSBwYXR0ZXJucywNCiAgICBjcml0aWNhbCBpbiBmaWVsZHMgZnJvbSBlLWNvbW1lcmNlIHRvIGhlYWx0aGNhcmUuDQoyLiAgKipTdXBwb3J0KiogYW5kICoqQ29uZmlkZW5jZSoqIHByb3ZpZGUgY29yZSBldmFsdWF0aW9uIG1ldHJpY3MsIHdpdGgNCiAgICAqKkxpZnQqKiBmdXJ0aGVyIHJlZmluaW5nIGFzc29jaWF0aW9uIHN0cmVuZ3RoLg0KMy4gICoqQXByaW9yaSBQcmluY2lwbGUqKiBhbmQgKipGUC1Hcm93dGgqKiBlbmFibGUgc2NhbGFibGUgaXRlbXNldA0KICAgIGdlbmVyYXRpb24gYnkgcmVkdWNpbmcgZGF0YXNldCBwYXNzZXMgYW5kIHNpbXBsaWZ5aW5nIGNvbXBhcmlzb25zLg0KNC4gICoqRlAtVHJlZSoqIG9mZmVycyBhIGNvbXBhY3Qgc3RydWN0dXJlLCBmYWNpbGl0YXRpbmcgcmVjdXJzaXZlDQogICAgcGF0dGVybiBkaXNjb3Zlcnkgd2l0aG91dCBhZGRpdGlvbmFsIGRhdGFiYXNlIHNjYW5zLg0KDQojIyMgKipQYXJ0IDMqKg0KDQohW1J1bGVzIEdlbmVyYXRpb25dKHBhcnQlMjAzL3J1bGVzJTIwZ2VuZXJhdGlvbi5wbmcpDQoNCiMgU3R1ZHkgR3VpZGU6IEFzc29jaWF0aW9uIEFuYWx5c2lzIGFuZCBSdWxlIE1pbmluZyAoTW9kdWxlIDEyKQ0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjIyAxLiBPdmVydmlldw0KDQpBc3NvY2lhdGlvbiBhbmFseXNpcyBmb2N1c2VzIG9uIGlkZW50aWZ5aW5nIHBhdHRlcm5zIGluIHRyYW5zYWN0aW9uYWwNCmRhdGEgd2hlcmUgdGhlIG9jY3VycmVuY2Ugb2YgY2VydGFpbiBpdGVtcyBpcyBhc3NvY2lhdGVkIHdpdGggb3RoZXJzLg0KVGhpcyB0eXBlIG9mIGFuYWx5c2lzIGlzIGZvdW5kYXRpb25hbCBpbiBmaWVsZHMgc3VjaCBhcyByZXRhaWwgZm9yDQptYXJrZXQgYmFza2V0IGFuYWx5c2lzIGFuZCBoZWFsdGhjYXJlIGZvciBzeW1wdG9tLWRpYWdub3Npcw0KcmVsYXRpb25zaGlwcy4NCg0KIyMjIEtleSBDb25jZXB0cyBhbmQgRGVmaW5pdGlvbnMNCg0KKipBc3NvY2lhdGlvbiBSdWxlIE1pbmluZyoqOiAtICoqT2JqZWN0aXZlKio6IFRvIHVuY292ZXIgY28tb2NjdXJyZW5jZQ0KcGF0dGVybnMgd2l0aGluIHRyYW5zYWN0aW9uIGRhdGEsIHN1Y2ggYXMgaW4gcmV0YWlsIHdoZXJlIGJ1eWluZyBvbmUNCml0ZW0gc3VnZ2VzdHMgYW5vdGhlciBwdXJjaGFzZS4gLSAqKkFzc29jaWF0aW9uIFJ1bGUgRm9ybWF0Kio6DQokWCBccmlnaHRhcnJvdyBZJCwgbWVhbmluZyBpZiAkWCQgb2NjdXJzLCAkWSQgaXMgbGlrZWx5IHRvIG9jY3VyIGFzDQp3ZWxsLg0KDQoqKktleSBNZXRyaWNzKio6IDEuICoqU3VwcG9ydCoqOiBNZWFzdXJlcyBob3cgZnJlcXVlbnRseSBhbiBpdGVtIG9yDQppdGVtc2V0IGFwcGVhcnMgaW4gdGhlIGRhdGEuICQkDQogICBcdGV4dHtTdXBwb3J0fShYKSA9IFxmcmFje1x0ZXh0e1RyYW5zYWN0aW9ucyBjb250YWluaW5nIH0gWH17XHRleHR7VG90YWwgVHJhbnNhY3Rpb25zfX0NCiAgICQkIDIuICoqQ29uZmlkZW5jZSoqOiBQcm9iYWJpbGl0eSBvZiAkWSQgb2NjdXJyaW5nIGdpdmVuIHRoYXQgJFgkIGhhcw0Kb2NjdXJyZWQuICQkDQogICBcdGV4dHtDb25maWRlbmNlfShYIFxyaWdodGFycm93IFkpID0gXGZyYWN7XHRleHR7U3VwcG9ydH0oWCBcY3VwIFkpfXtcdGV4dHtTdXBwb3J0fShYKX0NCiAgICQkIDMuICoqTGlmdCoqOiBFdmFsdWF0ZXMgYXNzb2NpYXRpb24gc3RyZW5ndGg7IGEgbGlmdCBncmVhdGVyIHRoYW4gMQ0KaW5kaWNhdGVzIHBvc2l0aXZlIGFzc29jaWF0aW9uLCB3aGlsZSBsZXNzIHRoYW4gMSBpbmRpY2F0ZXMgbmVnYXRpdmUNCmFzc29jaWF0aW9uLiAkJA0KICAgXHRleHR7TGlmdH0oWCBccmlnaHRhcnJvdyBZKSA9IFxmcmFje1x0ZXh0e0NvbmZpZGVuY2V9KFggXHJpZ2h0YXJyb3cgWSl9e1x0ZXh0e1N1cHBvcnR9KFkpfQ0KICAgJCQgNC4gKipJbnRlcmVzdCoqOiBDb21wYXJlcyBvYnNlcnZlZCBhbmQgZXhwZWN0ZWQgZnJlcXVlbmN5IG9mDQpjby1vY2N1cnJlbmNlLiAkJA0KICAgXHRleHR7SW50ZXJlc3R9KFggXHJpZ2h0YXJyb3cgWSkgPSBcdGV4dHtTdXBwb3J0fShYIFxjdXAgWSkgLSBcdGV4dHtTdXBwb3J0fShYKSBcdGltZXMgXHRleHR7U3VwcG9ydH0oWSkNCiAgICQkDQoNCjEuICAqKkZyZXF1ZW50IEl0ZW1zZXRzKio6DQogICAgLSAgICoqRGVmaW5pdGlvbioqOiBTZXRzIG9mIGl0ZW1zIGZyZXF1ZW50bHkgYXBwZWFyaW5nIHRvZ2V0aGVyIGluDQogICAgICAgIHRyYW5zYWN0aW9ucy4NCiAgICAtICAgKipTdXBwb3J0Kio6IE1lYXN1cmVzIGhvdyBvZnRlbiBhbiBpdGVtc2V0IGFwcGVhcnMgaW4gdGhlDQogICAgICAgIGRhdGFiYXNlOiAkJA0KICAgICAgICBcdGV4dHtTdXBwb3J0fShYKSA9IFxmcmFje1x0ZXh0e0ZyZXF1ZW5jeSBvZiB9IFggXHRleHR7IGluIHRyYW5zYWN0aW9uc319e1x0ZXh0e1RvdGFsIHRyYW5zYWN0aW9uc319DQogICAgICAgICQkDQogICAgLSAgICoqRXhhbXBsZSBDYWxjdWxhdGlvbioqOiBJZiBpdGVtc2V0IGB7QSwgQiwgQ31gIGFwcGVhcnMgaW4gMyBvdXQNCiAgICAgICAgb2YgMTAgdHJhbnNhY3Rpb25zLCB0aGVuOiAkJA0KICAgICAgICBcdGV4dHtTdXBwb3J0fSA9IFxmcmFjezN9ezEwfSA9IDAuMw0KICAgICAgICAkJA0KMi4gICoqQXNzb2NpYXRpb24gUnVsZXMqKjoNCiAgICAtICAgKipEZWZpbml0aW9uKio6IFJ1bGVzIG9mIHRoZSBmb3JtICRYIFxyaWdodGFycm93IFkkIG1lYW5pbmcgIklmDQogICAgICAgICRYJCwgdGhlbiAkWSQiLg0KICAgIC0gICAqKkNvbmZpZGVuY2UqKjogVGhlIGxpa2VsaWhvb2Qgb2YgJFkkIGdpdmVuICRYJCwgY2FsY3VsYXRlZCBhczoNCiAgICAgICAgJCQNCiAgICAgICAgXHRleHR7Q29uZmlkZW5jZX0oWCBccmlnaHRhcnJvdyBZKSA9IFxmcmFje1x0ZXh0e1N1cHBvcnR9KFggXGN1cCBZKX17XHRleHR7U3VwcG9ydH0oWCl9DQogICAgICAgICQkDQogICAgLSAgICoqTGlmdCoqOiBNZWFzdXJlcyB0aGUgc3RyZW5ndGggb2YgYSBydWxlIGJ5IGNvbXBhcmluZyB0aGUNCiAgICAgICAgb2JzZXJ2ZWQgc3VwcG9ydCB0byB0aGF0IGV4cGVjdGVkIGlmICRYJCBhbmQgJFkkIHdlcmUNCiAgICAgICAgaW5kZXBlbmRlbnQ6ICQkDQogICAgICAgIFx0ZXh0e0xpZnR9KFggXHJpZ2h0YXJyb3cgWSkgPSBcZnJhY3tcdGV4dHtDb25maWRlbmNlfShYIFxyaWdodGFycm93IFkpfXtcdGV4dHtTdXBwb3J0fShZKX0NCiAgICAgICAgJCQNCjMuICAqKk1heGltYWwgYW5kIENsb3NlZCBJdGVtc2V0cyoqOg0KICAgIC0gICAqKk1heGltYWwgSXRlbXNldHMqKjogQW4gaXRlbXNldCB3aXRoIG5vIGZyZXF1ZW50IHN1cGVyc2V0cy4NCiAgICAtICAgKipDbG9zZWQgSXRlbXNldHMqKjogQW4gaXRlbXNldCBpcyBjbG9zZWQgaWYgaXQgaGFzIG5vIHN1cGVyc2V0DQogICAgICAgIHdpdGggdGhlIHNhbWUgc3VwcG9ydC4NCiAgICAgICAgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyBTdGVwLWJ5LVN0ZXAgTWV0aG9kb2xvZ3kNCg0KMS4gICoqSWRlbnRpZnkgRnJlcXVlbnQgSXRlbXNldHMqKjogRmluZCBzZXRzIG9mIGl0ZW1zIHRoYXQgb2NjdXINCiAgICBmcmVxdWVudGx5IGJhc2VkIG9uIGEgbWluaW11bSBzdXBwb3J0IHRocmVzaG9sZC4NCjIuICAqKkdlbmVyYXRlIEFzc29jaWF0aW9uIFJ1bGVzKio6IEZyb20gZnJlcXVlbnQgaXRlbXNldHMsIGdlbmVyYXRlDQogICAgcnVsZXMgdGhhdCBtZWV0IG1pbmltdW0gY29uZmlkZW5jZS4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyBBbGdvcml0aG1zIGZvciBBc3NvY2lhdGlvbiBSdWxlIE1pbmluZw0KDQojIyMjIDEuICoqQXByaW9yaSBBbGdvcml0aG0qKg0KDQotICAgKipQdXJwb3NlKio6IEVmZmljaWVudGx5IGZpbmRzIGZyZXF1ZW50IGl0ZW1zZXRzIGJ5IGl0ZXJhdGl2ZWx5DQogICAgZXhwYW5kaW5nIHNtYWxsZXIgZnJlcXVlbnQgaXRlbXNldHMuDQotICAgKipBcHJpb3JpIFByaW5jaXBsZSoqOiBJZiBhbiBpdGVtc2V0IGlzIGluZnJlcXVlbnQsIGFsbCBpdHMNCiAgICBzdXBlcnNldHMgYXJlIGFsc28gaW5mcmVxdWVudC4NCi0gICAqKlByb2Nlc3MqKjoNCiAgICAtICAgQmVnaW4gd2l0aCBmcmVxdWVudCAxLWl0ZW1zZXRzLg0KICAgIC0gICBHZW5lcmF0ZSAkayQtaXRlbXNldCBjYW5kaWRhdGVzIGZyb20gZnJlcXVlbnQgJGstMSQtaXRlbXNldHMuDQogICAgLSAgIENvdW50IG9jY3VycmVuY2VzIGFuZCBwcnVuZSBub24tZnJlcXVlbnQgc2V0cyBiYXNlZCBvbiBzdXBwb3J0DQogICAgICAgIHRocmVzaG9sZC4NCg0KKipFeGFtcGxlIENvZGUqKjoNCg0KYGBgIHB5dGhvbg0KZnJvbSBpdGVydG9vbHMgaW1wb3J0IGNvbWJpbmF0aW9ucw0KDQp0cmFuc2FjdGlvbnMgPSBbDQogICAgeydtaWxrJywgJ2JyZWFkJywgJ2JlZXInfSwNCiAgICB7J21pbGsnLCAnZGlhcGVycycsICdiZWVyJ30sDQogICAgeydtaWxrJywgJ2JyZWFkJywgJ2RpYXBlcnMnfSwNCiAgICB7J2JyZWFkJywgJ2RpYXBlcnMnLCAnYmVlcid9LA0KICAgIHsnbWlsaycsICdicmVhZCd9DQpdDQoNCmRlZiBhcHJpb3JpKHRyYW5zYWN0aW9ucywgbWluX3N1cHBvcnQpOg0KICAgICMgR2VuZXJhdGUgZnJlcXVlbnQgaXRlbXNldHMgYW5kIGNhbGN1bGF0ZSBzdXBwb3J0DQogICAgIyBJbXBsZW1lbnRhdGlvbiB3b3VsZCBmb2xsb3cgaGVyZQ0KICAgIHBhc3MNCmBgYA0KDQojIyMgQUdBSU4gZm9yIGdvb2QgbWVhc3VyZQ0KDQpUaGUgKipBcHJpb3JpIGFsZ29yaXRobSoqIGdlbmVyYXRlcyBmcmVxdWVudCBpdGVtc2V0cyBieSBpdGVyYXRpdmVseQ0KZXhwYW5kaW5nIHNtYWxsZXIgaXRlbXNldHMgKG9mIHNpemUgJGskKSB0byBsYXJnZXIgb25lcyAob2Ygc2l6ZSAkaysxJCkNCmFuZCBwcnVuaW5nIG5vbi1mcmVxdWVudCBjYW5kaWRhdGVzIGJhc2VkIG9uIG1pbmltdW0gc3VwcG9ydCBjcml0ZXJpYS4NClRoaXMgcmVkdWNlcyBjb21wdXRhdGlvbmFsIGNvc3RzIGJ5IGVsaW1pbmF0aW5nIHVubmVjZXNzYXJ5IGV2YWx1YXRpb25zOg0KDQpgYGAgcHl0aG9uDQpmcm9tIGl0ZXJ0b29scyBpbXBvcnQgY29tYmluYXRpb25zDQoNCmRlZiBhcHJpb3JpKHRyYW5zYWN0aW9ucywgbWluX3N1cHBvcnQpOg0KICAgIHNpbmdsZV9pdGVtcyA9IHtpdGVtIGZvciB0IGluIHRyYW5zYWN0aW9ucyBmb3IgaXRlbSBpbiB0fQ0KICAgIGN1cnJlbnRfaXRlbXNldHMgPSBbe2l9IGZvciBpIGluIHNpbmdsZV9pdGVtc10NCiAgICANCiAgICBkZWYgZ2V0X3N1cHBvcnQoaXRlbXNldCk6DQogICAgICAgIHJldHVybiBzdW0oMSBmb3IgdCBpbiB0cmFuc2FjdGlvbnMgaWYgaXRlbXNldCA8PSBzZXQodCkpIC8gbGVuKHRyYW5zYWN0aW9ucykNCiAgICANCiAgICByZXN1bHQgPSBbXQ0KICAgIHdoaWxlIGN1cnJlbnRfaXRlbXNldHM6DQogICAgICAgIGZyZXF1ZW50X2l0ZW1zZXRzID0gW3MgZm9yIHMgaW4gY3VycmVudF9pdGVtc2V0cyBpZiBnZXRfc3VwcG9ydChzKSA+PSBtaW5fc3VwcG9ydF0NCiAgICAgICAgcmVzdWx0LmV4dGVuZChmcmVxdWVudF9pdGVtc2V0cykNCiAgICAgICAgY3VycmVudF9pdGVtc2V0cyA9IFthIHwgYiBmb3IgYSBpbiBmcmVxdWVudF9pdGVtc2V0cyBmb3IgYiBpbiBmcmVxdWVudF9pdGVtc2V0cyBpZiBsZW4oYSB8IGIpID09IGxlbihhKSArIDFdDQogICAgcmV0dXJuIHJlc3VsdA0KYGBgDQoNCiMjIyMgMi4gKipGUC1Hcm93dGggQWxnb3JpdGhtKioNCg0KLSAgICoqUHVycG9zZSoqOiBBdm9pZHMgZ2VuZXJhdGluZyBjYW5kaWRhdGVzIGJ5IGNyZWF0aW5nIGEgKipGcmVxdWVudA0KICAgIFBhdHRlcm4gVHJlZSAoRlAtVHJlZSkqKiwgYSBjb21wcmVzc2VkIHN0cnVjdHVyZSB0aGF0IHJldGFpbnMgaXRlbQ0KICAgIG9jY3VycmVuY2UgcGF0dGVybnMuDQotICAgKipQcm9jZXNzKio6DQogICAgLSAgIENyZWF0ZSBhbiBGUC10cmVlIGZyb20gdHJhbnNhY3Rpb25zLCBzb3J0ZWQgYnkgZnJlcXVlbmN5Lg0KICAgIC0gICBFeHRyYWN0IHBhdHRlcm5zIHRocm91Z2ggcmVjdXJzaXZlIGRlY29tcG9zaXRpb24gb2YgY29uZGl0aW9uYWwNCiAgICAgICAgdHJlZXMuDQoNCioqRlAtVHJlZSBDb2RlIEV4YW1wbGUqKjoNCg0KYGBgIHB5dGhvbg0KY2xhc3MgVHJlZU5vZGU6DQogICAgZGVmIF9faW5pdF9fKHNlbGYsIG5hbWUsIGNvdW50KToNCiAgICAgICAgc2VsZi5uYW1lID0gbmFtZQ0KICAgICAgICBzZWxmLmNvdW50ID0gY291bnQNCiAgICAgICAgc2VsZi5wYXJlbnQgPSBOb25lDQogICAgICAgIHNlbGYuY2hpbGRyZW4gPSB7fQ0KDQojIEV4YW1wbGUgRlAtdHJlZSBjb25zdHJ1Y3Rpb24NCnJvb3QgPSBUcmVlTm9kZSgibnVsbCIsIDEpDQp0cmFuc2FjdGlvbnMgPSBbWyJtaWxrIiwgImJyZWFkIl0sIFsiYmVlciIsICJkaWFwZXJzIl0sIFsibWlsayIsICJkaWFwZXJzIl1dDQoNCmRlZiBhZGRfdHJhbnNhY3Rpb24obm9kZSwgdHJhbnNhY3Rpb24pOg0KICAgICMgQ29uc3RydWN0aW5nIEZQLXRyZWUgbG9naWMgaGVyZQ0KICAgIHBhc3MNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjIEFkdmFuY2VkIFRvcGljczogUnVsZSBHZW5lcmF0aW9uIGFuZCBMYXR0aWNlIFN0cnVjdHVyZQ0KDQoqKlJ1bGUgR2VuZXJhdGlvbioqOiAtIEdlbmVyYXRlIHJ1bGVzIGJ5IGV2YWx1YXRpbmcgc3Vic2V0cyBvZiBhDQpmcmVxdWVudCBpdGVtc2V0LiAtICoqQ2FuZGlkYXRlIExhdHRpY2UgZm9yIFJ1bGVzKio6IC0gRm9yIGEgZnJlcXVlbnQNCml0ZW1zZXQsIGNyZWF0ZSBydWxlcyBieSBhc3NpZ25pbmcgZWFjaCBzdWJzZXQgYXMgYW50ZWNlZGVudCBvcg0KY29uc2VxdWVudC4gLSAqKlBydW5pbmcqKjogVXNlIGNvbmZpZGVuY2UgdGhyZXNob2xkIHRvIHBydW5lDQpsb3ctY29uZmlkZW5jZSBydWxlcy4NCg0KKipWaXN1YWxpemF0aW9uIG9mIFJ1bGUgTGF0dGljZSoqOiBBICoqcnVsZSBsYXR0aWNlKiogcmVwcmVzZW50cyB2YXJpb3VzDQpydWxlIHBvc3NpYmlsaXRpZXMgZnJvbSBhIGZyZXF1ZW50IGl0ZW1zZXQuIEl0IGhlbHBzIHZpc3VhbGl6ZSBob3cgcnVsZXMNCndpdGggbG93ZXIgY29uZmlkZW5jZSBjYW4gYmUgcHJ1bmVkIGJhc2VkIG9uIHRoZSBhbnRpLW1vbm90b25pY2l0eQ0KcHJvcGVydHkgb2YgY29uZmlkZW5jZS4NCg0KKipFeGFtcGxlKio6IElmICRcdGV4dHtDb25maWRlbmNlfShBQkMgXHJpZ2h0YXJyb3cgRCkkIGlzIGxvdywgcHJ1bmUgYW55DQpydWxlIGZyb20gdGhlIGxhdHRpY2UgdGhhdCB3b3VsZCBoYXZlIGxvd2VyIGNvbmZpZGVuY2UuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgQ29tcGFjdCBSZXByZXNlbnRhdGlvbiBvZiBQYXR0ZXJucw0KDQoxLiAgKipNYXhpbWFsIEl0ZW1zZXRzKio6IExhcmdlc3QgaXRlbXNldHMgdGhhdCBhcmUgZnJlcXVlbnQ7IG5vDQogICAgc3VwZXJzZXQgaXMgZnJlcXVlbnQuDQoyLiAgKipDbG9zZWQgSXRlbXNldHMqKjogSXRlbXNldHMgd2l0aCBubyBzdXBlcnNldCBoYXZpbmcgdGhlIHNhbWUNCiAgICBzdXBwb3J0Lg0KICAgIC0gICAqKlB1cnBvc2UqKjogUmVkdWNlcyByZWR1bmRhbmN5IGJ5IG9ubHkgdHJhY2tpbmcgdGhlIG1vc3QNCiAgICAgICAgaW5mb3JtYXRpdmUgaXRlbXNldHMuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgUHJhY3RpY2FsIEFwcGxpY2F0aW9uOiBFdmFsdWF0aW5nIFJ1bGUgSW50ZXJlc3RpbmduZXNzDQoNCjEuICAqKk9iamVjdGl2ZSBNZXRyaWNzKio6DQogICAgLSAgIENhbGN1bGF0ZSAqKnN1cHBvcnQqKiwgKipjb25maWRlbmNlKiosICoqbGlmdCoqLCBhbmQNCiAgICAgICAgKippbnRlcmVzdCoqIHVzaW5nIGNvbnRpbmdlbmN5IHRhYmxlcy4NCjIuICAqKlN1YmplY3RpdmUgSW50ZXJlc3RpbmduZXNzKio6DQogICAgLSAgIEluY29ycG9yYXRlIGRvbWFpbiBrbm93bGVkZ2UgdG8gcHJpb3JpdGl6ZSBhY3Rpb25hYmxlIG9yDQogICAgICAgIHVuZXhwZWN0ZWQgcGF0dGVybnMuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgRXhhbXBsZSBDYWxjdWxhdGlvbg0KDQpGb3IgdGhlIHJ1bGUgKipcIyBTdHVkZW50cyBcPDgwJSDihpIgUG9wIFF1aXoqKiAoYmFzZWQgb24gdGhlIHByb3ZpZGVkDQp0YWJsZSk6IC0gKipDb25maWRlbmNlKio6ICRcZnJhY3s0fXsxMn0gPSAwLjMzJCAtICoqTGlmdCoqOg0KJFxmcmFjezAuMzN9eyg1LzI1KX0gPSAxLjY1JCAtICoqSW50ZXJlc3QqKjogJDEuNjYkDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQpUaGlzIGd1aWRlIHN1bW1hcml6ZXMgY29yZSBwcmluY2lwbGVzIG9mIGFzc29jaWF0aW9uIGFuYWx5c2lzLCBmcmVxdWVudA0KaXRlbXNldCBnZW5lcmF0aW9uLCBhbmQgcnVsZSBtaW5pbmcsIGhpZ2hsaWdodGluZyBwcmFjdGljYWwgbWV0cmljcyBhbmQNCmFsZ29yaXRobXMgdXNlZCBpbiB0aGUgZmllbGQuIEl0IGNvbWJpbmVzIHRoZW9yZXRpY2FsIGluc2lnaHRzIHdpdGggY29kZQ0KZXhhbXBsZXMgdG8gZmFjaWxpdGF0ZSBjb21wcmVoZW5zaW9uIG9mIGNvbXBsZXggdG9waWNzLg0KDQpJbWFnZXM6DQoNCiFbaW1hZ2Ugd2l0aCBhbnN3ZXJzXShpbWFnZXMvaW1hZ2UucG5nKQ0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjIyA0LiBSdWxlIEdlbmVyYXRpb24gYW5kIExhdHRpY2UgVmlzdWFsaXphdGlvbg0KDQpGb3IgYSBmcmVxdWVudCBpdGVtc2V0ICRMJCwgZ2VuZXJhdGUgcnVsZXMgYnkgZmluZGluZyBhbGwgc3Vic2V0cyAkRiQNCmFuZCB1c2luZyAkRiBccmlnaHRhcnJvdyAoTCBcc2V0bWludXMgRikkIGFzIGNhbmRpZGF0ZSBydWxlcy4gQXBwbHkNCmNvbmZpZGVuY2UgdG8gZmlsdGVyIHNpZ25pZmljYW50IHJ1bGVzLg0KDQotICAgKipMYXR0aWNlIFZpc3VhbGl6YXRpb24qKjogQSBsYXR0aWNlIGdyYXBoIHZpc3VhbGl6ZXMgcmVsYXRpb25zaGlwcw0KICAgIGJldHdlZW4gaXRlbXNldCBsZXZlbHMuIFRoZSAqKmFudGktbW9ub3RvbmUqKiBwcm9wZXJ0eSBoZWxwcyBwcnVuZQ0KICAgIGxlc3MtY29uZmlkZW50IHJ1bGVzIGVhcmx5IGluIHRoaXMgc3RydWN0dXJlLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjIyA1LiBDb2RlIEV4YW1wbGUgZm9yIFJ1bGUgR2VuZXJhdGlvbg0KDQpUaGUgZm9sbG93aW5nIGV4YW1wbGUgY2FsY3VsYXRlcyBzdXBwb3J0IGFuZCBjb25maWRlbmNlIGZvciBjYW5kaWRhdGUNCnJ1bGVzIGZyb20gYSBsaXN0IG9mIHRyYW5zYWN0aW9ucy4NCg0KYGBgIHB5dGhvbg0KdHJhbnNhY3Rpb25zID0gW3snbWlsaycsICdicmVhZCcsICdidXR0ZXInfSwgeydtaWxrJywgJ2JyZWFkJ30sIHsnYnJlYWQnLCAnYnV0dGVyJ30sIHsnbWlsaycsICdidXR0ZXInfV0NCg0KZGVmIHN1cHBvcnQoaXRlbXNldCwgdHJhbnNhY3Rpb25zKToNCiAgICByZXR1cm4gc3VtKDEgZm9yIHQgaW4gdHJhbnNhY3Rpb25zIGlmIGl0ZW1zZXQgPD0gdCkgLyBsZW4odHJhbnNhY3Rpb25zKQ0KDQpkZWYgY29uZmlkZW5jZShhbnRlY2VkZW50LCBjb25zZXF1ZW50LCB0cmFuc2FjdGlvbnMpOg0KICAgIHJldHVybiBzdXBwb3J0KGFudGVjZWRlbnQgfCBjb25zZXF1ZW50LCB0cmFuc2FjdGlvbnMpIC8gc3VwcG9ydChhbnRlY2VkZW50LCB0cmFuc2FjdGlvbnMpDQoNCmFudGVjZWRlbnQsIGNvbnNlcXVlbnQgPSB7J21pbGsnfSwgeydicmVhZCd9DQpwcmludCgiU3VwcG9ydDoiLCBzdXBwb3J0KGFudGVjZWRlbnQgfCBjb25zZXF1ZW50LCB0cmFuc2FjdGlvbnMpKQ0KcHJpbnQoIkNvbmZpZGVuY2U6IiwgY29uZmlkZW5jZShhbnRlY2VkZW50LCBjb25zZXF1ZW50LCB0cmFuc2FjdGlvbnMpKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMjIDYuIEV2YWx1YXRpb24gTWV0cmljcw0KDQoxLiAgKipMaWZ0Kio6IEluZGljYXRlcyB0aGUgc3RyZW5ndGggb2YgYXNzb2NpYXRpb24gYmV5b25kIHJhbmRvbQ0KICAgIGNoYW5jZS4gJCQNCiAgICBcdGV4dHtMaWZ0fShYIFxyaWdodGFycm93IFkpID0gXGZyYWN7XHRleHR7Q29uZmlkZW5jZX0oWCBccmlnaHRhcnJvdyBZKX17XHRleHR7U3VwcG9ydH0oWSl9DQogICAgJCQNCg0KMi4gICoqSW50ZXJlc3QqKjogVGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBvYnNlcnZlZCBjby1vY2N1cnJlbmNlIGFuZA0KICAgIGV4cGVjdGVkIGluZGVwZW5kZW5jZS4NCg0KIyMjIyA3LiBQcmFjdGljYWwgRXhhbXBsZSBpbiBQeXRob24NCg0KVmlzdWFsaXplIHJ1bGVzIGFuZCByZWxhdGlvbnNoaXBzIHVzaW5nIGEgZ3JhcGggdG8gaGlnaGxpZ2h0IGNvbmZpZGVuY2UNCmxldmVscyBhbmQgcHJ1bmUgYmFzZWQgb24gdGhyZXNob2xkcy4NCg0KYGBgIHB5dGhvbg0KaW1wb3J0IG5ldHdvcmt4IGFzIG54DQppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0DQoNCkcgPSBueC5EaUdyYXBoKCkNCkcuYWRkX2VkZ2VzX2Zyb20oWygiQSIsICJBQiIpLCAoIkIiLCAiQkMiKSwgKCJDIiwgIkFCQyIpXSkNCnBsdC5maWd1cmUoZmlnc2l6ZT0oOCwgNikpDQpueC5kcmF3KEcsIHdpdGhfbGFiZWxzPVRydWUsIG5vZGVfc2l6ZT0zMDAwLCBmb250X3NpemU9MTApDQpwbHQuc2hvdygpIyBTdHVkeSBHdWlkZTogQXNzb2NpYXRpb24gQW5hbHlzaXMgYW5kIFJ1bGUgTWluaW5nIChNb2R1bGUgMTIpDQoNCi0tLQ0KDQojIyMgMS4gT3ZlcnZpZXcNCg0KKipBc3NvY2lhdGlvbiBBbmFseXNpcyoqIGZvY3VzZXMgb24gaWRlbnRpZnlpbmcgcGF0dGVybnMgaW4gdHJhbnNhY3Rpb25hbCBkYXRhIHdoZXJlIHRoZSBvY2N1cnJlbmNlIG9mIGNlcnRhaW4gaXRlbXMgc3VnZ2VzdHMgdGhlIHByZXNlbmNlIG9mIG90aGVycy4gVGhpcyBhbmFseXNpcyBpcyBlc3NlbnRpYWwgaW4gZmllbGRzIHN1Y2ggYXMgKipyZXRhaWwqKiAobWFya2V0IGJhc2tldCBhbmFseXNpcykgYW5kICoqaGVhbHRoY2FyZSoqIChzeW1wdG9tLWRpYWdub3NpcyByZWxhdGlvbnNoaXBzKS4NCg0KIyMjIEtleSBDb25jZXB0cyBhbmQgRGVmaW5pdGlvbnMNCg0KLSAqKkFzc29jaWF0aW9uIFJ1bGUgTWluaW5nKio6DQogICAtICoqT2JqZWN0aXZlKio6IFRvIHVuY292ZXIgY28tb2NjdXJyZW5jZSBwYXR0ZXJucyB3aXRoaW4gdHJhbnNhY3Rpb24gZGF0YSwgd2hlcmUgYnV5aW5nIG9uZSBpdGVtIG1pZ2h0IHN1Z2dlc3QgcHVyY2hhc2luZyBhbm90aGVyLg0KICAgLSAqKkFzc29jaWF0aW9uIFJ1bGUgRm9ybWF0Kio6IFwoIFggXHJpZ2h0YXJyb3cgWSBcKSwgbWVhbmluZyBpZiBcKCBYIFwpIG9jY3VycywgXCggWSBcKSBpcyBsaWtlbHkgdG8gb2NjdXIgYXMgd2VsbC4NCg0KIyMjIyBLZXkgTWV0cmljcw0KDQoxLiAqKlN1cHBvcnQqKjogTWVhc3VyZXMgaG93IGZyZXF1ZW50bHkgYW4gaXRlbSBvciBpdGVtc2V0IGFwcGVhcnMgaW4gdGhlIGRhdGEuDQogICBcWw0KICAgXHRleHR7U3VwcG9ydH0oWCkgPSBcZnJhY3tcdGV4dHtUcmFuc2FjdGlvbnMgY29udGFpbmluZyB9IFh9e1x0ZXh0e1RvdGFsIFRyYW5zYWN0aW9uc319DQogICBcXQ0KDQoyLiAqKkNvbmZpZGVuY2UqKjogUHJvYmFiaWxpdHkgb2YgXCggWSBcKSBvY2N1cnJpbmcgZ2l2ZW4gdGhhdCBcKCBYIFwpIGhhcyBvY2N1cnJlZC4NCiAgIFxbDQogICBcdGV4dHtDb25maWRlbmNlfShYIFxyaWdodGFycm93IFkpID0gXGZyYWN7XHRleHR7U3VwcG9ydH0oWCBcY3VwIFkpfXtcdGV4dHtTdXBwb3J0fShYKX0NCiAgIFxdDQoNCjMuICoqTGlmdCoqOiBFdmFsdWF0ZXMgYXNzb2NpYXRpb24gc3RyZW5ndGg7IGEgbGlmdCBncmVhdGVyIHRoYW4gMSBpbmRpY2F0ZXMgYSBwb3NpdGl2ZSBhc3NvY2lhdGlvbiwgd2hpbGUgbGVzcyB0aGFuIDEgaW5kaWNhdGVzIGEgbmVnYXRpdmUgYXNzb2NpYXRpb24uDQogICBcWw0KICAgXHRleHR7TGlmdH0oWCBccmlnaHRhcnJvdyBZKSA9IFxmcmFje1x0ZXh0e0NvbmZpZGVuY2V9KFggXHJpZ2h0YXJyb3cgWSl9e1x0ZXh0e1N1cHBvcnR9KFkpfQ0KICAgXF0NCg0KNC4gKipJbnRlcmVzdCoqOiBDb21wYXJlcyBvYnNlcnZlZCBhbmQgZXhwZWN0ZWQgZnJlcXVlbmN5IG9mIGNvLW9jY3VycmVuY2UuDQogICBcWw0KICAgXHRleHR7SW50ZXJlc3R9KFggXHJpZ2h0YXJyb3cgWSkgPSBcdGV4dHtTdXBwb3J0fShYIFxjdXAgWSkgLSBcdGV4dHtTdXBwb3J0fShYKSBcdGltZXMgXHRleHR7U3VwcG9ydH0oWSkNCiAgIFxdDQoNCi0tLQ0KDQojIyMgTWV0aG9kb2xvZ3kNCg0KMS4gKipJZGVudGlmeSBGcmVxdWVudCBJdGVtc2V0cyoqOiBGaW5kIHNldHMgb2YgaXRlbXMgdGhhdCBvY2N1ciBmcmVxdWVudGx5IGJhc2VkIG9uIGEgbWluaW11bSBzdXBwb3J0IHRocmVzaG9sZC4NCjIuICoqR2VuZXJhdGUgQXNzb2NpYXRpb24gUnVsZXMqKjogRnJvbSBmcmVxdWVudCBpdGVtc2V0cywgZ2VuZXJhdGUgcnVsZXMgdGhhdCBtZWV0IG1pbmltdW0gY29uZmlkZW5jZS4NCg0KLS0tDQoNCiMjIyBBbGdvcml0aG1zIGZvciBBc3NvY2lhdGlvbiBSdWxlIE1pbmluZw0KDQojIyMjIDEuICoqQXByaW9yaSBBbGdvcml0aG0qKg0KDQotICoqUHVycG9zZSoqOiBFZmZpY2llbnRseSBmaW5kcyBmcmVxdWVudCBpdGVtc2V0cyBieSBleHBhbmRpbmcgc21hbGxlciBpdGVtc2V0cyBpdGVyYXRpdmVseS4NCi0gKipBcHJpb3JpIFByaW5jaXBsZSoqOiBJZiBhbiBpdGVtc2V0IGlzIGluZnJlcXVlbnQsIGFsbCBpdHMgc3VwZXJzZXRzIGFyZSBhbHNvIGluZnJlcXVlbnQuDQotICoqUHJvY2VzcyoqOg0KICAgLSBTdGFydCB3aXRoIGZyZXF1ZW50IDEtaXRlbXNldHMuDQogICAtIEdlbmVyYXRlIFwoIGsgXCktaXRlbXNldCBjYW5kaWRhdGVzIGZyb20gZnJlcXVlbnQgXCggay0xIFwpLWl0ZW1zZXRzLg0KICAgLSBDb3VudCBvY2N1cnJlbmNlcyBhbmQgcHJ1bmUgbm9uLWZyZXF1ZW50IHNldHMgYmFzZWQgb24gYSBzdXBwb3J0IHRocmVzaG9sZC4NCg0KKipFeGFtcGxlIENvZGUqKjoNCmBgYHB5dGhvbg0KZnJvbSBpdGVydG9vbHMgaW1wb3J0IGNvbWJpbmF0aW9ucw0KDQp0cmFuc2FjdGlvbnMgPSBbDQogICAgeydtaWxrJywgJ2JyZWFkJywgJ2JlZXInfSwNCiAgICB7J21pbGsnLCAnZGlhcGVycycsICdiZWVyJ30sDQogICAgeydtaWxrJywgJ2JyZWFkJywgJ2RpYXBlcnMnfSwNCiAgICB7J2JyZWFkJywgJ2RpYXBlcnMnLCAnYmVlcid9LA0KICAgIHsnbWlsaycsICdicmVhZCd9DQpdDQoNCmRlZiBhcHJpb3JpKHRyYW5zYWN0aW9ucywgbWluX3N1cHBvcnQpOg0KICAgICMgR2VuZXJhdGUgZnJlcXVlbnQgaXRlbXNldHMgYW5kIGNhbGN1bGF0ZSBzdXBwb3J0DQogICAgcGFzcw0KYGBgDQoNCmBgYCAgICAgICAgIA0KLSBvci0gDQpgYGANCg0KYGBgIHB5dGhvbg0KZnJvbSBpdGVydG9vbHMgaW1wb3J0IGNvbWJpbmF0aW9ucw0KDQp0cmFuc2FjdGlvbnMgPSBbDQogICAgeydtaWxrJywgJ2JyZWFkJywgJ2JlZXInfSwNCiAgICB7J21pbGsnLCAnZGlhcGVycycsICdiZWVyJ30sDQogICAgeydtaWxrJywgJ2JyZWFkJywgJ2RpYXBlcnMnfSwNCiAgICB7J2JyZWFkJywgJ2RpYXBlcnMnLCAnYmVlcid9LA0KICAgIHsnbWlsaycsICdicmVhZCd9DQpdDQoNCmRlZiBhcHJpb3JpKHRyYW5zYWN0aW9ucywgbWluX3N1cHBvcnQpOg0KICAgIHNpbmdsZV9pdGVtcyA9IHtpdGVtIGZvciB0IGluIHRyYW5zYWN0aW9ucyBmb3IgaXRlbSBpbiB0fQ0KICAgIGN1cnJlbnRfaXRlbXNldHMgPSBbe2l9IGZvciBpIGluIHNpbmdsZV9pdGVtc10NCiAgICANCiAgICBkZWYgZ2V0X3N1cHBvcnQoaXRlbXNldCk6DQogICAgICAgIHJldHVybiBzdW0oMSBmb3IgdCBpbiB0cmFuc2FjdGlvbnMgaWYgaXRlbXNldCA8PSBzZXQodCkpIC8gbGVuKHRyYW5zYWN0aW9ucykNCiAgICANCiAgICByZXN1bHQgPSBbXQ0KICAgIHdoaWxlIGN1cnJlbnRfaXRlbXNldHM6DQogICAgICAgIGZyZXF1ZW50X2l0ZW1zZXRzID0gW3MgZm9yIHMgaW4gY3VycmVudF9pdGVtc2V0cyBpZiBnZXRfc3VwcG9ydChzKSA+PSBtaW5fc3VwcG9ydF0NCiAgICAgICAgcmVzdWx0LmV4dGVuZChmcmVxdWVudF9pdGVtc2V0cykNCiAgICAgICAgY3VycmVudF9pdGVtc2V0cyA9IFthIHwgYiBmb3IgYSBpbiBmcmVxdWVudF9pdGVtc2V0cyBmb3IgYiBpbiBmcmVxdWVudF9pdGVtc2V0cyBpZiBsZW4oYSB8IGIpID09IGxlbihhKSArIDFdDQogICAgcmV0dXJuIHJlc3VsdA0KYGBgDQoNCiMjIyMgMi4gKipGUC1Hcm93dGggQWxnb3JpdGhtKioNCg0KLSAgICoqUHVycG9zZSoqOiBBdm9pZHMgZ2VuZXJhdGluZyBjYW5kaWRhdGVzIGJ5IGNyZWF0aW5nIGEgKipGcmVxdWVudA0KICAgIFBhdHRlcm4gVHJlZSAoRlAtVHJlZSkqKiwgYSBjb21wcmVzc2VkIHN0cnVjdHVyZSB0aGF0IHJldGFpbnMgaXRlbQ0KICAgIG9jY3VycmVuY2UgcGF0dGVybnMuDQotICAgKipQcm9jZXNzKio6DQogICAgLSAgIEJ1aWxkIGFuIEZQLXRyZWUgZnJvbSB0cmFuc2FjdGlvbnMsIHNvcnRlZCBieSBmcmVxdWVuY3kuDQogICAgLSAgIEV4dHJhY3QgcGF0dGVybnMgdGhyb3VnaCByZWN1cnNpdmUgZGVjb21wb3NpdGlvbiBvZiBjb25kaXRpb25hbA0KICAgICAgICB0cmVlcy4NCg0KKipGUC1UcmVlIENvZGUgRXhhbXBsZSoqOg0KDQpgYGAgcHl0aG9uDQpjbGFzcyBUcmVlTm9kZToNCiAgICBkZWYgX19pbml0X18oc2VsZiwgbmFtZSwgY291bnQpOg0KICAgICAgICBzZWxmLm5hbWUgPSBuYW1lDQogICAgICAgIHNlbGYuY291bnQgPSBjb3VudA0KICAgICAgICBzZWxmLnBhcmVudCA9IE5vbmUNCiAgICAgICAgc2VsZi5jaGlsZHJlbiA9IHt9DQoNCiMgRXhhbXBsZSBGUC10cmVlIGNvbnN0cnVjdGlvbg0Kcm9vdCA9IFRyZWVOb2RlKCJudWxsIiwgMSkNCnRyYW5zYWN0aW9ucyA9IFtbIm1pbGsiLCAiYnJlYWQiXSwgWyJiZWVyIiwgImRpYXBlcnMiXSwgWyJtaWxrIiwgImRpYXBlcnMiXV0NCg0KZGVmIGFkZF90cmFuc2FjdGlvbihub2RlLCB0cmFuc2FjdGlvbik6DQogICAgIyBDb25zdHJ1Y3RpbmcgRlAtdHJlZSBsb2dpYyBoZXJlDQogICAgcGFzcw0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgQWR2YW5jZWQgVG9waWNzOiBSdWxlIEdlbmVyYXRpb24gYW5kIExhdHRpY2UgU3RydWN0dXJlDQoNCi0gICAqKlJ1bGUgR2VuZXJhdGlvbioqOg0KICAgIC0gICBHZW5lcmF0ZSBydWxlcyBieSBldmFsdWF0aW5nIHN1YnNldHMgb2YgYSBmcmVxdWVudCBpdGVtc2V0Lg0KICAgIC0gICAqKkNhbmRpZGF0ZSBMYXR0aWNlIGZvciBSdWxlcyoqOiBDcmVhdGUgcnVsZXMgYnkgYXNzaWduaW5nDQogICAgICAgIHN1YnNldHMgYXMgYW50ZWNlZGVudCBvciBjb25zZXF1ZW50LCB1c2luZyBjb25maWRlbmNlIHRvIHBydW5lDQogICAgICAgIGxvdy1jb25maWRlbmNlIHJ1bGVzLg0KDQoqKlZpc3VhbGl6YXRpb24gb2YgUnVsZSBMYXR0aWNlKio6IEEgKipydWxlIGxhdHRpY2UqKiBoZWxwcyB2aXN1YWxpemUNCnBvdGVudGlhbCBydWxlcyBmcm9tIGEgZnJlcXVlbnQgaXRlbXNldCwgYWxsb3dpbmcgcHJ1bmluZyBvZiBydWxlcyB3aXRoDQpsb3dlciBjb25maWRlbmNlIGJhc2VkIG9uIHRoZSBhbnRpLW1vbm90b25pY2l0eSBwcm9wZXJ0eS4NCg0KKipFeGFtcGxlKio6IElmICRcdGV4dHtDb25maWRlbmNlfShBQkMgXHJpZ2h0YXJyb3cgRCkkIGlzIGxvdywgcHJ1bmUgYW55DQpydWxlIGluIHRoZSBsYXR0aWNlIHdpdGggbG93ZXIgY29uZmlkZW5jZS4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyBDb21wYWN0IFJlcHJlc2VudGF0aW9uIG9mIFBhdHRlcm5zDQoNCjEuICAqKk1heGltYWwgSXRlbXNldHMqKjogTGFyZ2VzdCBpdGVtc2V0cyB0aGF0IGFyZSBmcmVxdWVudDsgbm8NCiAgICBzdXBlcnNldCBpcyBmcmVxdWVudC4NCjIuICAqKkNsb3NlZCBJdGVtc2V0cyoqOiBJdGVtc2V0cyB3aXRoIG5vIHN1cGVyc2V0IGhhdmluZyB0aGUgc2FtZQ0KICAgIHN1cHBvcnQuDQogICAgLSAgICoqUHVycG9zZSoqOiBSZWR1Y2VzIHJlZHVuZGFuY3kgYnkgb25seSB0cmFja2luZyB0aGUgbW9zdA0KICAgICAgICBpbmZvcm1hdGl2ZSBpdGVtc2V0cy4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyBQcmFjdGljYWwgQXBwbGljYXRpb246IEV2YWx1YXRpbmcgUnVsZSBJbnRlcmVzdGluZ25lc3MNCg0KMS4gICoqT2JqZWN0aXZlIE1ldHJpY3MqKjoNCiAgICAtICAgQ2FsY3VsYXRlICoqc3VwcG9ydCoqLCAqKmNvbmZpZGVuY2UqKiwgKipsaWZ0KiosIGFuZA0KICAgICAgICAqKmludGVyZXN0KiogdXNpbmcgY29udGluZ2VuY3kgdGFibGVzLg0KMi4gICoqU3ViamVjdGl2ZSBJbnRlcmVzdGluZ25lc3MqKjoNCiAgICAtICAgVXNlIGRvbWFpbiBrbm93bGVkZ2UgdG8gcHJpb3JpdGl6ZSBhY3Rpb25hYmxlIG9yIHVuZXhwZWN0ZWQNCiAgICAgICAgcGF0dGVybnMuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgRXhhbXBsZSBDYWxjdWxhdGlvbg0KDQpGb3IgdGhlIHJ1bGUgKipcIyBTdHVkZW50cyBcPDgwJSDihpIgUG9wIFF1aXoqKjogLSAqKkNvbmZpZGVuY2UqKjoNCiRcZnJhY3s0fXsxMn0gPSAwLjMzJCAtICoqTGlmdCoqOiAkXGZyYWN7MC4zM317KDUvMjUpfSA9IDEuNjUkIC0NCioqSW50ZXJlc3QqKjogMS42Ng0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjIFByYWN0aWNhbCBFeGFtcGxlIGluIFB5dGhvbg0KDQoqKlZpc3VhbGl6YXRpb24qKjoNCg0KYGBgIHB5dGhvbg0KaW1wb3J0IG5ldHdvcmt4IGFzIG54DQppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0DQoNCkcgPSBueC5EaUdyYXBoKCkNCkcuYWRkX2VkZ2VzX2Zyb20oWygiQSIsICJBQiIpLCAoIkIiLCAiQkMiKSwgKCJDIiwgIkFCQyIpXSkNCnBsdC5maWd1cmUoZmlnc2l6ZT0oOCwgNikpDQpueC5kcmF3KEcsIHdpdGhfbGFiZWxzPVRydWUsIG5vZGVfc2l6ZT0zMDAwLCBmb250X3NpemU9MTApDQpwbHQuc2hvdygpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiFbXShpbWFnZXMvcnVsZXMgZ2VuZXJhdGlvbi5wbmcpDQoNClRoaXMgaW1hZ2UgcmVwcmVzZW50cyBhICoqcnVsZSBnZW5lcmF0aW9uIGxhdHRpY2UqKiBmb3IgYSBmcmVxdWVudA0KaXRlbXNldCwgc3BlY2lmaWNhbGx5IGB7TWFsZSwgM3JkLCBOb3QgU3Vydml2ZX1gLiBJbiB0aGlzIGxhdHRpY2UsIGVhY2gNCm5vZGUgY29ycmVzcG9uZHMgdG8gYSBydWxlIGRlcml2ZWQgYnkgYXNzaWduaW5nIHN1YnNldHMgb2YgdGhlIGl0ZW1zZXQNCmFzIGFudGVjZWRlbnRzIG9yIGNvbnNlcXVlbnRzLiBUaGUgYXJyb3dzIGlsbHVzdHJhdGUgdGhlIHByb2Nlc3Mgb2YgcnVsZQ0KZGVjb21wb3NpdGlvbiwgc2hvd2luZyBob3cgbGFyZ2VyIHJ1bGVzIGNhbiBiZSBicm9rZW4gZG93biBpbnRvIHNpbXBsZXINCnJ1bGVzLiBIZXJlJ3MgYW4gYW5hbHlzaXMgYmFzZWQgb24gb3VyIHByZXZpb3VzIGRpc2N1c3Npb246DQoNCiMjIyBTdHJ1Y3R1cmUgb2YgdGhlIExhdHRpY2UNCg0KLSAgICoqVG9wIE5vZGUqKjogYHtNYWxlLCAzcmQsIE5vdCBTdXJ2aXZlfSA9PiDiiIVgDQogICAgLSAgIFRoaXMgbm9kZSByZXByZXNlbnRzIHRoZSBlbnRpcmUgZnJlcXVlbnQgaXRlbXNldCB3aXRoIG5vDQogICAgICAgIHNwZWNpZmljIHJ1bGUgY3JlYXRlZCB5ZXQgKGkuZS4sIG5vIGFzc2lnbm1lbnQgb2YgYW50ZWNlZGVudCBvcg0KICAgICAgICBjb25zZXF1ZW50KS4NCi0gICAqKk5leHQgTGV2ZWwgRG93bioqOiBUaGUgbGF0dGljZSBzcGxpdHMgaW50byBydWxlcyB3aXRoIGEgc2luZ2xlDQogICAgaXRlbSBhcyB0aGUgY29uc2VxdWVudC4gRXhhbXBsZXMgaW5jbHVkZToNCiAgICAtICAgYHszcmQsIE5vdCBTdXJ2aXZlfSA9PiB7TWFsZX1gDQogICAgLSAgIGB7TWFsZSwgTm90IFN1cnZpdmV9ID0+IHszcmR9YA0KICAgIC0gICBgezNyZCwgTWFsZX0gPT4ge05vdCBTdXJ2aXZlfWANCiAgICAtICAgVGhlc2UgcmVwcmVzZW50IHJ1bGVzIHdpdGggdHdvLWl0ZW0gYW50ZWNlZGVudHMgaW1wbHlpbmcgdGhlDQogICAgICAgIHJlbWFpbmluZyBpdGVtIGFzIHRoZSBjb25zZXF1ZW50Lg0KLSAgICoqRnVydGhlciBMZXZlbHMqKjogVGhlIGxhdHRpY2UgY29udGludWVzIHRvIHNwbGl0IGRvd253YXJkcywNCiAgICBjcmVhdGluZyBydWxlcyB3aXRoIGZld2VyIGl0ZW1zIGluIHRoZSBhbnRlY2VkZW50IGFuZCBtb3JlIGl0ZW1zIGluDQogICAgdGhlIGNvbnNlcXVlbnQuDQogICAgLSAgIEZvciBleGFtcGxlLCBge05vdCBTdXJ2aXZlfSA9PiB7TWFsZSwgM3JkfWAgaW1wbGllcyB0aGF0IGlmDQogICAgICAgIHNvbWVvbmUgZGlkIG5vdCBzdXJ2aXZlLCB0aGV5IGFyZSBsaWtlbHkgYSBtYWxlIGluIDNyZCBjbGFzcy4NCiAgICAtICAgRWFjaCBub2RlIGxvd2VyIGluIHRoZSBsYXR0aWNlIHJlcHJlc2VudHMgcnVsZXMgd2l0aCBzaW1wbGVyDQogICAgICAgIGFudGVjZWRlbnRzLCBsZWFkaW5nIHRvIGJyb2FkZXIgY29uc2VxdWVudHMuDQoNCiMjIyBLZXkgQ29uY2VwdHMgSWxsdXN0cmF0ZWQNCg0KMS4gICoqUnVsZSBHZW5lcmF0aW9uKio6IFRoaXMgbGF0dGljZSBzdHJ1Y3R1cmUgdmlzdWFsbHkgcmVwcmVzZW50cyB0aGUNCiAgICBwcm9jZXNzIG9mIGdlbmVyYXRpbmcgYXNzb2NpYXRpb24gcnVsZXMgZnJvbSBhIGZyZXF1ZW50IGl0ZW1zZXQgYnkNCiAgICBldmFsdWF0aW5nIGFsbCBwb3NzaWJsZSBzdWJzZXQgY29tYmluYXRpb25zIGZvciBhbnRlY2VkZW50cyBhbmQNCiAgICBjb25zZXF1ZW50cy4NCg0KMi4gICoqQW50aS1Nb25vdG9uaWNpdHkgUHJvcGVydHkqKjogQ29uZmlkZW5jZSB0eXBpY2FsbHkgZm9sbG93cyBhbg0KICAgIGFudGktbW9ub3RvbmljIHByb3BlcnR5IGFzIHdlIG1vdmUgZG93biB0aGUgbGF0dGljZS4gVGhpcyBtZWFucyB0aGF0DQogICAgaWYgYSBydWxlIGhpZ2hlciBpbiB0aGUgbGF0dGljZSAoZS5nLiwNCiAgICBgezNyZCwgTm90IFN1cnZpdmV9ID0+IHtNYWxlfWApIGhhcyBsb3cgY29uZmlkZW5jZSwgdGhlbiBhbGwgcnVsZXMNCiAgICBkZXJpdmVkIGZyb20gaXQgYnkgcmVkdWNpbmcgdGhlIGFudGVjZWRlbnQgd2lsbCBoYXZlIGV2ZW4gbG93ZXINCiAgICBjb25maWRlbmNlLiBUaGlzIHByb3BlcnR5IGFsbG93cyB1cyB0byAqKnBydW5lKiogbG93ZXIgY29uZmlkZW5jZQ0KICAgIHJ1bGVzIGZyb20gY29uc2lkZXJhdGlvbi4NCg0KMy4gICoqUHJ1bmluZyBhbmQgRWZmaWNpZW5jeSoqOiBCeSB1c2luZyB0aGUgbGF0dGljZSBzdHJ1Y3R1cmUsIHdlIGNhbg0KICAgIGVmZmVjdGl2ZWx5IHBydW5lIHJ1bGVzIHRoYXQgZG8gbm90IG1lZXQgY29uZmlkZW5jZSBvciBvdGhlcg0KICAgIGV2YWx1YXRpb24gdGhyZXNob2xkcywgc2ltaWxhciB0byBob3cgdGhlIEFwcmlvcmkgcHJpbmNpcGxlIHdvcmtzIGluDQogICAgZnJlcXVlbnQgaXRlbXNldCBnZW5lcmF0aW9uLiBGb3IgZXhhbXBsZSwgaWYNCiAgICBge01hbGUsIE5vdCBTdXJ2aXZlfSA9PiB7M3JkfWAgaGFzIGxvdyBjb25maWRlbmNlLCBhbnkgZnVydGhlciBydWxlcw0KICAgIGdlbmVyYXRlZCBmcm9tIHRoaXMgKHdpdGggZmV3ZXIgYW50ZWNlZGVudHMgYW5kIG1vcmUgaXRlbXMgaW4gdGhlDQogICAgY29uc2VxdWVudCkgY2FuIGJlIGlnbm9yZWQuDQoNCjQuICAqKkFwcGxpY2F0aW9uIG9mIFJ1bGUgRXZhbHVhdGlvbiBNZXRyaWNzKio6DQoNCiAgICAtICAgKipDb25maWRlbmNlKio6IEZvciBlYWNoIHJ1bGUsIHdlIHdvdWxkIGNhbGN1bGF0ZSB0aGUgY29uZmlkZW5jZQ0KICAgICAgICBieSBkZXRlcm1pbmluZyBob3cgb2Z0ZW4gdGhlIGFudGVjZWRlbnQgYXBwZWFycyB3aXRoIHRoZQ0KICAgICAgICBjb25zZXF1ZW50IGluIHRoZSBkYXRhc2V0Lg0KICAgIC0gICAqKkxpZnQgYW5kIEludGVyZXN0Kio6IFRoZXNlIGFkZGl0aW9uYWwgbWV0cmljcyBjb3VsZCBmdXJ0aGVyDQogICAgICAgIGV2YWx1YXRlIHRoZSAiaW50ZXJlc3RpbmduZXNzIiBvZiBlYWNoIHJ1bGUsIGVzcGVjaWFsbHkgZm9yDQogICAgICAgIHJ1bGVzIHRoYXQgc2VlbSBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50IG9yIGFjdGlvbmFibGUuDQoNCiMjIyBTdW1tYXJ5DQoNClRoaXMgbGF0dGljZSB2aXN1YWxpemF0aW9uIGlzIGEgdmFsdWFibGUgdG9vbCBmb3IgKipydWxlIGdlbmVyYXRpb24qKg0KYW5kICoqcHJ1bmluZyoqLiBJdCBzeXN0ZW1hdGljYWxseSByZXByZXNlbnRzIGFsbCBwb3NzaWJsZSBydWxlcyBkZXJpdmVkDQpmcm9tIGEgZnJlcXVlbnQgaXRlbXNldCBhbmQgaGVscHMgYXBwbHkgbWV0cmljcyAobGlrZSBjb25maWRlbmNlIGFuZA0KbGlmdCkgdG8ga2VlcCBvbmx5IHRoZSBtb3N0IGludGVyZXN0aW5nIGFuZCByZWxldmFudCBydWxlcy4gVGhpcw0KdGVjaG5pcXVlIGlzIHBhcnRpY3VsYXJseSBoZWxwZnVsIGluIGNvbXBsZXggZGF0YXNldHMsIGVuYWJsaW5nDQplZmZpY2llbnQgcnVsZSBtaW5pbmcgYW5kIGFzc29jaWF0aW9uIGFuYWx5c2lzLg0K