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

  1. 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.

  1. 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.

  1. 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

  1. Frequent Itemset Generation:
    • Generate all itemsets that meet a minimum support threshold, a process that is computationally intensive.
  2. Rule Generation:
    • Use the frequent itemsets to create association rules, ensuring they meet both minimum support and confidence thresholds.
  3. 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

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:

  1. 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.
  2. 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.
  3. 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:
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.

  1. 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.

  2. 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.

  3. 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.

  1. Candidate Itemsets: Start with single frequent items, and combine them iteratively to generate larger itemsets.
  2. Apriori Principle: If an itemset is infrequent, all supersets are also infrequent, reducing search space.
  3. Support Count: Count occurrences of each candidate itemset, pruning those below the minimum support threshold.
  4. 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:
    1. Start with k = 1 for single items and find frequent itemsets.
    2. Generate itemsets of length \(k+1\) from frequent \(k\)-itemsets.
    3. 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:
    1. Count frequent 1-itemsets and filter by minimum support.
    2. 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:

  1. First Pass: Identify frequent 1-itemsets, sorting them by frequency.
  2. FP-Tree Construction: Build a tree where transactions are hierarchically organized, reducing redundancy.
  3. 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

  1. Association Rule Mining identifies co-occurrence patterns, critical in fields from e-commerce to healthcare.
  2. Support and Confidence provide core evaluation metrics, with Lift further refining association strength.
  3. Apriori Principle and FP-Growth enable scalable itemset generation by reducing dataset passes and simplifying comparisons.
  4. FP-Tree offers a compact structure, facilitating recursive pattern discovery without additional database scans.

Part 3

Rules Generation
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) \]

  1. 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 \]
  2. 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)} \]
  3. 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

  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 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

  1. Maximal Itemsets: Largest itemsets that are frequent; no superset is frequent.
  2. 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

  1. Objective Metrics:
    • Calculate support, confidence, lift, and interest using contingency tables.
  2. 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
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

  1. Lift: Indicates the strength of association beyond random chance. \[ \text{Lift}(X \rightarrow Y) = \frac{\text{Confidence}(X \rightarrow Y)}{\text{Support}(Y)} \]

  2. 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

  1. Maximal Itemsets: Largest itemsets that are frequent; no superset is frequent.
  2. 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

  1. Objective Metrics:
    • Calculate support, confidence, lift, and interest using contingency tables.
  2. 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

  1. 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.

  2. 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.

  3. 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.

  4. 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