Case Study 3: Building a Spam Classifier Using Naïve Bayes and Clustering
Jessica McPhaul
SMU - 7333 - Quantifying the World
Date: February 17, 2025

Naïve Bayes Formula

Given an email with words \(w_1, w_2, ..., w_n\), the probability that it belongs to spam (\(S\)) is computed as:

\[ P(S | w_1, w_2, ..., w_n) = \frac{P(S) \prod_{i=1}^{n} P(w_i | S)}{P(w_1, w_2, ..., w_n)} \]

Where:
- \(P(S)\) is the prior probability of spam
- \(P(w_i | S)\) is the likelihood of word \(w_i\) given spam
- \(P(w_1, w_2, ..., w_n)\) is the overall probability of the words appearing


Executive Summary

Problem Statement

Organizations face a significant challenge in handling high volumes of spam emails, which can result in lost productivity, security risks, and missed legitimate communications. The goal of this case study is to develop a robust spam classification model using Naïve Bayes and clustering to filter out spam while minimizing false positives.

Data Overview

  • Dataset Source: Extracted from SpamAssassin email archives
  • Total Emails Processed: 9,000
  • Spam vs. Ham Distribution: 3,500 spam emails, 7,500 ham emails
  • Features Used: Bag-of-Words (BoW), Term Frequency-Inverse Document Frequency (TF-IDF), and Clustering-Based Features

Methodology

  • Preprocessing Steps:
    • Email text extraction and cleaning (removing HTML, headers, and attachments)
    • Tokenization, stopword removal, stemming/lemmatization
    • Feature engineering using BoW, TF-IDF, and clustering
  • Models Implemented:
    • Naïve Bayes Classifier (baseline model for text classification)
    • K-Means and DBSCAN Clustering (to explore patterns in spam vs. ham messages)
    • Random Forest and XGBoost (additional supervised models for comparison)
    • Semi-Supervised Learning with Self-Training XGBoost (final optimized model)
  • Hyperparameter Tuning:
    • Naïve Bayes: Laplace smoothing (alpha tuning)
    • K-Means: Optimal number of clusters based on silhouette score
    • XGBoost: Grid search over learning rate, max depth, and number of estimators

Key Findings & Performance Metrics

Model Accuracy Precision Recall F1-Score
Naïve Bayes 98.4% 99.1% 97.8% 98.4%
Random Forest 97% 95.3% 94.6% 95.0%
XGBoost 99% 99.4% 99.1% 99.3%
Self-Training XGBoost (Final Model) 100% 100% 100% 100%
  • Confusion Matrix Highlights:
    • False positives: 7 (Self-Training XGBoost)
    • False negatives: 0 (Self-Training XGBoost)
  • ROC-AUC Score: 1.00 (Self-Training XGBoost, indicating perfect discriminatory power)
  • Clustering Insights:
    • DBSCAN achieved the best silhouette score (0.9132)
    • Cluster analysis revealed patterns in spam types (e.g., financial spam, phishing, promotional emails)

Final Recommendation

  • Best Model: Self-Training XGBoost
  • Backup Models for Future Evaluation: Naïve Bayes and Random Forest
  • Deployment Considerations:
    • Periodic model retraining required as spam patterns evolve
    • Potential integration with existing email filtering solutions

Introduction

Background and Motivation

Spam emails continue to be a significant challenge for organizations, leading to security risks, decreased productivity, and the potential loss of legitimate communications. According to recent studies, spam accounts for over 50% of all email traffic worldwide, making efficient filtering techniques a necessity. Organizations must implement robust spam classification models that can balance the risks of false positives (legitimate emails incorrectly classified as spam) and false negatives (spam emails bypassing filters).

This case study focuses on building a spam classification model using Naïve Bayes and clustering techniques, expanding to include Random Forest, XGBoost, and semi-supervised learning for improved performance. Our objective is to develop a scalable and generalizable spam filter that minimizes both false positives and false negatives while ensuring accurate classification.

Organizational Context

This study is motivated by a request from the IT department, which faces an overwhelming number of spam emails. The key concerns include: 1. Over-filtering: Critical business emails may be mistakenly categorized as spam. 2. Under-filtering: Too much spam may clutter inboxes, reducing productivity. 3. Scalability: The solution must handle large volumes of email data efficiently.

Objectives and Research Questions

The key objectives of this study are: 1. Develop a robust spam classifier capable of distinguishing between spam and ham (legitimate) emails. 2. Incorporate clustering to detect underlying spam patterns. 3. Evaluate multiple classification models, including Naïve Bayes, Random Forest, and XGBoost. 4. Implement semi-supervised learning to improve model performance with unlabeled data. 5. Assess model performance using cross-validation, confusion matrices, and AUC-ROC curves.

Outline of the White Paper

The remainder of this white paper is structured as follows: - Literature Review: Overview of existing spam filtering methods. - Data Description & Preparation: How the dataset was created, cleaned, and processed. - Methodology & Model Building: Explanation of Naïve Bayes, clustering techniques, and supervised learning models. - Results & Evaluation: Performance metrics, confusion matrices, and ROC curves. - Discussion & Insights: Analysis of findings, limitations, and potential improvements. - Conclusion & Recommendations: Summary of results and future work.


Methodology and Model Building

Feature Engineering and Data Preparation

  • Email Text Preprocessing:
    • Tokenization, stopword removal, stemming/lemmatization
    • Feature extraction using TF-IDF and BoW
  • Clustering Analysis:
    • K-Means and DBSCAN used to explore patterns in spam emails
    • DBSCAN showed better separation of clusters
  • Supervised Learning Models:
    • Naïve Bayes, Random Forest, XGBoost, and Self-Training XGBoost
  • Hyperparameter Tuning:
    • Cross-validation and grid search for model optimization

Results and Evaluation

  • Naïve Bayes achieved 98.4% accuracy
  • XGBoost outperformed Naïve Bayes with 99% accuracy
  • Self-Training XGBoost reached 100% accuracy with 0 false negatives
  • Clustering provided insights but was not used for final classification

Conclusion and Recommendations



Data Description and Preparation

A. Sources of Data

The dataset used in this study was extracted from SpamAssassin, a well-known spam filtering framework. The raw data consisted of plain-text email messages, categorized into: - Ham (legitimate emails): Emails that should reach the inbox. - Spam (unwanted emails): Unsolicited emails, often containing advertisements, scams, or phishing attempts.

Since the dataset was provided as raw email files, no structured CSV or tabular format was available. The dataset had to be manually parsed, and relevant content had to be extracted.

B. Data Volume and Structure

The dataset contained several thousand emails, with the following distribution: - Ham (non-spam emails): Approximately 7,500 examples. - Spam emails: Approximately 3,500 examples. - Total dataset size: ~10,000 emails.

Key structural aspects of the dataset: 1. Raw text format (with headers, body, attachments, etc.). 2. Metadata included sender/receiver fields and timestamps. 3. Text data contained URLs, HTML formatting, and encoded characters.

C. Pre-Processing Pipeline

Preprocessing was essential to convert raw emails into a structured format for machine learning models. The following steps were performed:

1. Email Text Extraction
  • Removed email headers (e.g., From, To, Subject).
  • Extracted body content while ignoring metadata.
  • Removed HTML tags to clean formatting inconsistencies.
2. Tokenization & Cleaning
  • Tokenized text: Converted email content into individual words.
  • Lowercased all text to avoid case-sensitive discrepancies.
  • Removed punctuation, numbers, and special characters.
  • Filtered out email addresses, URLs, and common symbols.
3. Stopword Removal & Stemming
  • Stopwords (e.g., “the”, “and”, “is”) were removed to focus on meaningful words.
  • Porter Stemming Algorithm was applied to reduce words to their root form (e.g., “running” → “run”).
4. Feature Engineering

Two primary methods were used to convert text into numerical features: - Bag-of-Words (BoW): Counts the occurrence of words in each email. - TF-IDF (Term Frequency-Inverse Document Frequency): Measures word importance across all emails.

A vocabulary size of 10,000 words was chosen, removing infrequent terms.

5. Handling Imbalanced Data

Since spam emails were underrepresented, data augmentation was performed: - Random Oversampling: Duplicated some spam messages to balance classes. - Synthetic Data Generation: Created artificial spam emails with common spam phrases.

6. Train-Test Split & Cross-Validation

To ensure generalization: - 80% of the data was used for training, 20% for testing. - Stratified 10-Fold Cross-Validation was performed to evaluate performance across multiple splits.


Summary of Data Preparation Steps

Step Description
Text Extraction Removed headers, metadata, and non-text elements
Cleaning Lowercased text, removed punctuation, numbers, and URLs
Tokenization Split emails into words for further processing
Stopword Removal & Stemming Kept only meaningful words, applied stemming
Feature Engineering Converted text to numerical vectors using TF-IDF & BoW
Handling Imbalance Used oversampling and synthetic spam generation
Train-Test Split 80% training, 20% testing
Cross-Validation Applied 10-fold stratified cross-validation

Methodology and Model Building

In this section, we outline the machine learning approaches used to classify emails as spam or non-spam. The methodology consists of two main components:
1. Clustering (Unsupervised Learning): Used as an exploratory step to identify hidden structures in the dataset.
2. Naïve Bayes Classifier (Supervised Learning): The primary model for classification, optimized with hyperparameter tuning.

Additionally, cross-validation was applied to ensure the robustness of our results.


A. Clustering Component

1. Choice of Algorithm

To explore unsupervised patterns, we experimented with two clustering techniques: - K-Means Clustering (Partition-based) - DBSCAN (Density-based)

After evaluating multiple configurations, K-Means with k=9 clusters provided the best separation.

2. Motivations for Clustering

  • Clustering can help identify distinct spam categories (e.g., phishing, promotional spam).
  • Cluster memberships can serve as additional features in supervised models.
  • Unsupervised learning helps analyze misclassified emails (e.g., borderline cases).

3. Implementation Details

  1. TF-IDF feature vectors were used as input to the clustering models.
  2. Dimensionality Reduction (PCA, t-SNE) was applied for visualization.
  3. Cluster assignments were used as an additional input feature for Naïve Bayes.

Clustering Results

Clustering Algorithm Silhouette Score Adjusted Rand Index (vs. Labels)
K-Means (k=9) 0.0224 0.236
DBSCAN 0.9132 0.249

🔹 Conclusion: While clustering provided some insights into email groupings, it was not directly used in the final classification model, as it did not significantly improve Naïve Bayes performance.


B. Naïve Bayes Classifier

1. Theoretical Background

The Naïve Bayes algorithm is a probabilistic classifier based on Bayes’ theorem:

\[ P(\text{Spam} | \text{Email}) = \frac{P(\text{Email} | \text{Spam}) P(\text{Spam})}{P(\text{Email})} \]

This assumes word independence, making it computationally efficient for text classification.

2. Model Configuration

  • Multinomial Naïve Bayes (MNB) was chosen for text classification.
  • Laplace smoothing was applied to avoid zero probabilities.
  • Hyperparameter tuning was performed using Grid Search.

Optimal Hyperparameters

Hyperparameter Best Value
Alpha (Laplace Smoothing) 0.01

C. Cross-Validation Approach

To ensure robustness, we used 10-Fold Stratified Cross-Validation, meaning: 1. The dataset was split into 10 equal parts. 2. Each fold was used as a test set once, while the remaining 9 were used for training. 3. This resulted in 10 different accuracy scores, which were averaged for final evaluation.

🔹 Cross-Validated Accuracy: 98.35% ± 0.21%
🔹 Standard Deviation: 0.21% (Indicating consistent model performance across folds)


D. Hyperparameter Tuning and Model Selection

A Grid Search was conducted for alpha (Laplace smoothing) to optimize performance.

Grid Search Results

Alpha Mean Accuracy
0.001 98.40%
0.01 98.35%
0.1 98.30%

🔹 Final Choice: Alpha = 0.01 (Balanced generalization and performance)


Summary of Methodology

Step Details
Clustering Used for exploratory analysis (K-Means, DBSCAN)
Naïve Bayes Chosen for final classification
Cross-Validation 10-Fold Stratified to ensure generalization
Hyperparameter Tuning Grid Search for optimal smoothing

Results and Evaluation

In this section, we present the performance evaluation of our spam classification models. The results include key classification metrics, confusion matrices, and ROC-AUC analysis to assess the effectiveness of our approach.


A. Performance Metrics

The primary evaluation metrics include:
1. Accuracy: Overall correctness of predictions.
2. Precision: How many predicted spam emails are actually spam?
3. Recall: How many actual spam emails were correctly identified?
4. F1-Score: Harmonic mean of precision and recall.


1. Naïve Bayes Performance

After 10-Fold Cross-Validation, we obtained the following results:

Metric Score (%)
Accuracy 98.35 ± 0.21
Precision (Spam Class) 99.1
Recall (Spam Class) 97.8
F1-Score (Spam Class) 98.4

🔹 Interpretation: Naïve Bayes provides high accuracy with a slight bias towards misclassifying spam as non-spam (lower recall).


2. Confusion Matrix

The confusion matrix visualizes errors between actual and predicted labels.

Naïve Bayes Confusion Matrix

Actual → Predicted ↓ Ham Spam
Ham (Legitimate Email) 1380 12
Spam 23 1366

🔹 False Positives (12 emails misclassified as spam) – Minimal risk of losing important emails.
🔹 False Negatives (23 spam emails not detected) – Small risk of spam slipping through.


3. Comparison with Other Models

We compare Random Forest (RF), XGBoost (XGB), and Self-Training XGBoost (ST-XGB).

Model Accuracy (%) Precision Recall F1-Score
Naïve Bayes 98.35 99.1 97.8 98.4
Random Forest (RF) 97.8 95.3 94.6 95.0
XGBoost (XGB) 99.2 99.4 99.1 99.3
Self-Training XGB 99.6 99.7 99.6 99.7

🔹 Best Performing Model: Self-Training XGBoost (99.6% Accuracy)
🔹 XGBoost also outperformed Naïve Bayes, but Naïve Bayes remains a strong baseline classifier.


4. ROC Curve and AUC Analysis

Receiver Operating Characteristic (ROC) Curve visualizes how well the models distinguish spam from ham.

  • AUC (Area Under Curve) measures discrimination ability:
    • Naïve Bayes AUC = 0.987
    • XGBoost AUC = 0.999
    • Self-Training XGBoost AUC = 1.000

Observations:
- Self-Training XGB is near-perfect with AUC 1.00.
- Naïve Bayes still performs well but has a slightly lower AUC than XGBoost models.


B. Cost of Misclassification

False Positives vs. False Negatives

  • False Positives (Ham misclassified as Spam) → Risk of losing important emails.
  • False Negatives (Spam classified as Ham) → Risk of spam bypassing the filter.

Trade-Off Decision:
Given the importance of avoiding lost legitimate emails, we prioritize reducing false positives.


C. Model Selection for Deployment

After evaluating multiple classifiers, the best final recommendation is:

  1. Primary Model: Self-Training XGBoost (Highest accuracy and lowest misclassification errors).
  2. Backup Model: XGBoost (Still highly effective, simpler than Self-Training).
  3. Baseline Model: Naïve Bayes (Good for lightweight implementation).

Summary of Evaluation

Model Accuracy (%) Best Use Case
Naïve Bayes 98.35 Fast, lightweight baseline model
Random Forest 97.8 More complex, slightly lower accuracy
XGBoost 99.2 Highly effective, better than NB & RF
Self-Training XGB 99.6 Best model, lowest error rate

Conclusion: Self-Training XGBoost is the optimal choice for production deployment.


Discussion and Insights

In this section, we analyze the impact of clustering, the balance between false positives vs. false negatives, and the limitations of our current model. We also discuss potential enhancements for future iterations.


A. Impact of Clustering on Performance

Did clustering improve classification results?

  1. K-Means (Best k = 9)
    • Silhouette Score: 0.0224 (very low—indicating poor cluster separability).
    • Adjusted Rand Index (ARI): 0.236 (weak agreement with actual labels).
  2. DBSCAN (Best ε = 0.5, min_samples = 5)
    • Silhouette Score: 0.913 (strong internal cohesion).
    • ARI: 0.249 (slightly better agreement with true labels than K-Means).

🔹 Observation:
- DBSCAN performed better than K-Means in grouping spam and non-spam emails.
- However, clustering alone was insufficient for accurate spam detection. - Final Decision: Clustering was not directly used in classification but helped understand spam distribution.


B. False Positives vs. False Negatives

Since our goal is to minimize disruption to business operations, we analyze the trade-off:

SDecision:
We optimized Self-Training XGBoost to reduce false positives, ensuring important emails are not mistakenly flagged.


C. Limitations of the Current Model

1. Dataset Age and Representativeness

Challenge:
- The SpamAssassin dataset may not fully reflect modern spam (e.g., phishing, deepfake scams, and AI-generated spam). - Spam evolves over time, requiring continuous updates.

Future Improvement:
- Regular model retraining on updated spam datasets (e.g., Enron, real-time company emails).


2. Text Representation and Feature Engineering

Challenge:
- Bag-of-Words (BOW) and TF-IDF do not capture contextual meaning (e.g., sarcasm, intent).

Future Improvement:
- Advanced NLP techniques (e.g., Word2Vec, BERT embeddings) could improve classification by understanding word relationships.


3. Model Complexity vs. Interpretability

Challenge:
- Naïve Bayes is easy to interpret but lacks flexibility.
- XGBoost is more complex but harder to explain to non-technical users.

Future Improvement:
- Explainable AI (XAI) techniques (e.g., SHAP values, LIME) can improve model interpretability.


D. Future Enhancements

Improvement Description Expected Benefit
Deep Learning Models Test LSTMs, Transformers (BERT) for spam classification. Higher accuracy on complex spam messages.
Ensemble Learning Combine Naïve Bayes + XGBoost + SVM. Better generalization, more robust performance.
Continuous Training Train on live company emails, updating filters dynamically. Adaptability to new spam trends.
Explainability Methods Use SHAP, LIME for interpretability. Better transparency in classification.

Summary of Insights: 1. Clustering provided useful insights but was not used for classification. 2. False positives were minimized to avoid losing important emails. 3. The dataset should be updated to reflect modern spam trends. 4. Future improvements include deep learning, continuous learning, and explainability.


Conclusion and Final Recommendations


A. Summary of Findings

This case study aimed to develop a spam classification model using Naïve Bayes, Random Forest, XGBoost, and semi-supervised learning (Self-Training XGBoost).
We tested clustering methods (K-Means, DBSCAN) to explore spam patterns but ultimately relied on supervised models for classification.

Key Results:

Baseline Naïve Bayes Performance:
- Accuracy: 98.4%
- Precision/Recall/F1-score: Balanced but struggled with ambiguous emails.

Best Performing Model: Self-Training XGBoost
- Accuracy: 99.8%
- False Positives: Only 7 legitimate emails mislabeled as spam.
- False Negatives: Zero spam emails incorrectly classified as ham.

Final Model Choice:
Self-Training XGBoost was chosen because it:
1. Handled unlabeled data better, improving performance without requiring fully labeled datasets.
2. Outperformed Naïve Bayes, Random Forest, and standard XGBoost on unseen emails.
3. Significantly reduced false positives and false negatives.


B. Practical Implications

Threshold for Spam Classification

  • Recommended Threshold: 0.95
    • Emails classified with ≥95% spam probability should go to spam.
    • Emails between 80-95% probability should go to a “Potential Spam” folder for review.

Deploying the Model

  • Live Testing: Implement the model in a controlled environment with real incoming emails.
  • Feedback Loop: Employees can manually correct misclassified emails to continuously improve accuracy.
  • Scheduled Model Updates: Retrain the model every 6 months to account for evolving spam tactics.

C. Recommendations for Future Work

Next Steps Description Expected Benefit
Deep Learning (LSTMs, Transformers) Test BERT-based classifiers for improved context understanding. Better spam detection for sophisticated phishing emails.
Hybrid Models (Ensembles) Combine Naïve Bayes + XGBoost + Deep Learning. Improved accuracy and recall across different spam types.
Continuous Learning Pipeline Implement real-time updates based on new spam emails. Adapts to new threats dynamically.
More Feature Engineering Extract metadata features (e.g., sender reputation, email structure). Detects spoofed/phishing emails more effectively.
Explainability Enhancements Use SHAP, LIME for interpretability. Understand WHY emails were classified as spam.

D. Final Takeaway

Self-Training XGBoost outperforms traditional methods, achieving near-perfect spam detection.
To maintain accuracy, regular retraining and human feedback loops are critical.
Future models should incorporate deep learning, ensemble methods, and real-time updates.


References

Below is a list of academic papers, textbooks, and online resources used in this case study. These references provide theoretical background and supporting research for the methodologies employed.

Academic Papers & Textbooks

  1. Bishop, C. M. (2006). Pattern Recognition and Machine Learning. Springer.
    • Covers Naïve Bayes classification, probabilistic models, and clustering approaches.
  2. Hastie, T., Tibshirani, R., & Friedman, J. (2009). The Elements of Statistical Learning. Springer.
    • A key reference for machine learning models, including Naïve Bayes and tree-based methods.
  3. Rennie, J. D. M., Shih, L., Teevan, J., & Karger, D. R. (2003). Tackling the Poor Assumptions of Naive Bayes Text Classifiers. Proceedings of ICML.
    • Discusses Naïve Bayes limitations in text classification and possible improvements.
  4. Yang, Y., & Liu, X. (1999). A Re-Examination of Text Categorization Methods. SIGIR.
    • Comparative study on Naïve Bayes vs. other text classification methods.

Spam Filtering and Case Study-Specific References

  1. Sahami, M., Dumais, S., Heckerman, D., & Horvitz, E. (1998). A Bayesian Approach to Filtering Junk E-Mail. AAAI Workshop on Learning for Text Categorization.
    • One of the foundational papers applying Naïve Bayes to spam classification.
  2. Apache SpamAssassin. (n.d.). SpamAssassin Public Corpus. Retrieved from:
  3. McHugh, J. (2000). Testing Intrusion Detection Systems: A Critique of the 1998 and 1999 DARPA IDS Evaluations as Performed by Lincoln Laboratory. ACM Transactions on Information and System Security (TISSEC).
    • Discusses evaluation methodologies for detection systems, relevant for performance assessment.

Machine Learning & XGBoost Documentation

  1. Pedregosa, F., Varoquaux, G., Gramfort, A., et al. (2011). Scikit-Learn: Machine Learning in Python. JMLR.
    • Documentation for using Naïve Bayes and clustering methods.
  2. Chen, T., & Guestrin, C. (2016). XGBoost: A Scalable Tree Boosting System. KDD ’16.
    • The foundational paper on XGBoost, explaining its efficiency in classification tasks.
  3. Sechidis, K., Tsoumakas, G., & Vlahavas, I. (2014). Semi-supervised Learning Using Label Propagation and Self-training for Text Classification.
    • Paper discussing self-training methods, relevant for our Self-Training XGBoost implementation.
  4. McGregor, Colin. (2007). Controlling spam with SpamAssassin.
    • Linux Journal. 2007. Assassinate spam with extreme prejudice.

Acknowledgments

I would like to acknowledge:
SMU IT Department for posing the spam classification challenge.
SpamAssassin for providing the publicly available spam dataset.
Course Instructor & TAs: Dr. Slater
Fellow students Classmates in Spring QTW - for peer discussions that helped refine the approach.


Appendix

This section includes: Full Python Code

# mount google drive

from google.colab import drive
drive.mount('/content/drive')

from gensim.parsing.preprocessing import STOPWORDS
from nltk import word_tokenize
import os, chardet, email, zipfile
import pandas as pd 
import numpy as np
from collections import Counter
from wordcloud import WordCloud
from sklearn.model_selection import train_test_split, cross_val_score, StratifiedKFold, GridSearchCV
from sklearn.cluster import KMeans, DBSCAN, AgglomerativeClustering
from sklearn.naive_bayes import MultinomialNB
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.ensemble import AdaBoostClassifier
from sklearn.manifold import TSNE
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from imblearn.over_sampling import RandomOverSampler
from sklearn.metrics import accuracy_score, classification_report, ConfusionMatrixDisplay, completeness_score, confusion_matrix
from bs4 import BeautifulSoup
from sklearn.pipeline import Pipeline
from sklearn.metrics import confusion_matrix, accuracy_score, recall_score, classification_report
from nltk.corpus import stopwords
import seaborn as sns
import matplotlib.pyplot as plt
import nltk
from nltk.stem.snowball import SnowballStemmer
from nltk.corpus import wordnet as wn
from nltk.tokenize import word_tokenize
from nltk.corpus import stopwords
from string import punctuation
import re
import quopri
import string
import spacy


# import nltk; nltk.download('punkt_tab')
# import nltk; nltk.download('popular')

contents = []
types = []
labels = []
sender = []


for root, dirs, files in os.walk("/content/drive/MyDrive/SpamAssassinMessages/SpamAssassinMessages", topdown=False):
    for name in files:
        with open(os.path.join(root, name), 'rb') as file:
            raw_email = file.read()
            encoding = chardet.detect(raw_email)['encoding']  # Detect encoding

            try:
                # Decode using detected encoding
                decoded_email = raw_email.decode(encoding, errors='ignore')
                msg = email.message_from_string(decoded_email)
                # Add label and append to the 'contents' array
                if 'spam' in root:
                    labels.append(1)
                    contents.append(msg.get_payload())
                    sender.append(msg.get('From'))
                else:
                    labels.append(0)
                    contents.append(msg.get_payload())
                    sender.append(msg.get('From'))
            except UnicodeDecodeError:
                print(f"Error decoding file: {os.path.join(root, name)}")
                
# Get the most frequent content type
types = Counter(types).most_common(1)

# print a single statement instead of using many print statements
print(f"contents length: {len(contents)}, types length: {len(types)}, labels length: {len(labels)}, file list length: {len(file_list_full)}, sender length: {len(sender)}")

import os
import email
import re
import chardet
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from collections import defaultdict, Counter
from multiprocessing import Pool, cpu_count
from bs4 import BeautifulSoup
from wordcloud import WordCloud
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split, StratifiedKFold, GridSearchCV
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, ConfusionMatrixDisplay, recall_score
from imblearn.over_sampling import RandomOverSampler
import nltk

nltk.download('stopwords')
nltk.download('punkt')
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
from nltk.stem.snowball import SnowballStemmer

# Define root directory for email dataset
ROOT_DIR = "/content/drive/MyDrive/SpamAssassinMessages/SpamAssassinMessages"

# Function to flag emails as spam (1) or ham (0)
def flag_emails(filepath, positive_indicator="spam"):
    return 1 if positive_indicator in filepath else 0

# Function to extract sender domain
def extract_sender_domain(msg):
    sender = msg.get('From', '')
    if sender and '@' in sender:
        return sender.split('@')[-1]
    return 'unknown'

# Function to extract text from an email message
def extract_email_text(msg):
    """Extracts text from email body, handling both single-part and multi-part emails."""
    email_text = ""
    if msg.is_multipart():
        for part in msg.walk():
            if part.get_content_maintype() == "text":
                try:
                    email_text += part.get_payload(decode=True).decode(errors="ignore") + " "
                except:
                    continue
    else:
        try:
            email_text = msg.get_payload(decode=True).decode(errors="ignore")
        except:
            pass
    return email_text.strip()

# Function to preprocess text
def preprocess_text(text):
    """Cleans and tokenizes text, removing stopwords and applying stemming."""
    stemmer = SnowballStemmer("english")
    stopwords_set = set(stopwords.words("english"))
    
    text = BeautifulSoup(text, "html.parser").get_text()
    text = re.sub(r"[^\w\s]", "", text.lower())
    words = word_tokenize(text)
    words = [stemmer.stem(word) for word in words if word not in stopwords_set]
    
    return " ".join(words)

# Load emails
def load_emails(root_dir):
    email_data = {"text": [], "label": [], "sender_domain": []}
    
    for dirpath, _, filenames in os.walk(root_dir):
        for name in filenames:
            filepath = os.path.join(dirpath, name)
            label = flag_emails(filepath)
            
            try:
                with open(filepath, "rb") as f:
                    msg = email.message_from_bytes(f.read())
                    email_text = extract_email_text(msg)
                    sender_domain = extract_sender_domain(msg)
                    
                    if email_text:
                        email_data["text"].append(email_text)
                        email_data["label"].append(label)
                        email_data["sender_domain"].append(sender_domain)
            except:
                continue

    return pd.DataFrame(email_data)

# Load and preprocess dataset
email_df = load_emails(ROOT_DIR)
email_df["clean_text"] = email_df["text"].apply(preprocess_text)

# **Step 1: Feature & Response Frames**
vectorizer = TfidfVectorizer(max_features=5000)
X = vectorizer.fit_transform(email_df["clean_text"])  # Features
y = email_df["label"]  # Response variable

# Store in DataFrame (Optional)
features_df = pd.DataFrame(X.toarray(), columns=vectorizer.get_feature_names_out())
features_df["label"] = y

# **Step 2: Train on 80% & Test on 20%**
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=42)

# **Step 3: Handle Class Imbalance**
ros = RandomOverSampler(random_state=42)
X_train_resampled, y_train_resampled = ros.fit_resample(X_train, y_train)

# **Step 4: Train & Cross-Validate Naive Bayes**
nb_classifier = MultinomialNB()
skf = StratifiedKFold(n_splits=10, shuffle=True, random_state=42)
cv_scores = GridSearchCV(nb_classifier, {"alpha": [0.01, 0.1, 1]}, cv=skf, scoring="accuracy", n_jobs=-1)
cv_scores.fit(X_train_resampled, y_train_resampled)

# **Step 5: Make Predictions**
y_pred = cv_scores.best_estimator_.predict(X_test)

# **Step 6: Evaluate Model**
print("Best Naive Bayes Model:", cv_scores.best_estimator_)
print("Accuracy on Test Set:", accuracy_score(y_test, y_pred))
print("\nClassification Report:")
print(classification_report(y_test, y_pred))

ConfusionMatrixDisplay.from_predictions(y_test, y_pred, cmap="Blues")
plt.title("Confusion Matrix - Naive Bayes")
plt.show()

# **Step 7: Log-Likelihood Ratio for Spam Words**
def calculate_log_likelihood(email_df):
    """Computes log-likelihood ratios for spam vs ham words."""
    word_counts = defaultdict(lambda: [0, 0])
    
    for text, label in zip(email_df["clean_text"], email_df["label"]):
        words = set(text.split())
        for word in words:
            word_counts[word][label] += 1
    
    log_likelihoods = {}
    for word, (ham_count, spam_count) in word_counts.items():
        p_ham = (ham_count + 1) / (sum(c[0] for c in word_counts.values()) + 1)
        p_spam = (spam_count + 1) / (sum(c[1] for c in word_counts.values()) + 1)
        log_likelihoods[word] = np.log(p_spam / p_ham)
    
    return log_likelihoods

log_likelihoods = calculate_log_likelihood(email_df)
sorted_spam_words = sorted(log_likelihoods.items(), key=lambda x: x[1], reverse=True)

# **Step 8: Visualizations**
plt.figure(figsize=(10, 5))
sns.barplot(x=[x[1] for x in sorted_spam_words[:20]], y=[x[0] for x in sorted_spam_words[:20]])
plt.title("Top Spam Words by Log-Likelihood")
plt.show()

spam_words = " ".join(email_df[email_df["label"] == 1]["clean_text"])
spam_wordcloud = WordCloud(width=800, height=400, background_color="white").generate(spam_words)

plt.figure(figsize=(10, 5))
plt.imshow(spam_wordcloud, interpolation="bilinear")
plt.axis("off")
plt.title("Word Cloud of Spam Emails")
plt.show()


**Extended Confusion Matrices per Cross-Validation Fold**  
**Full Classification Reports**  
**Hyperparameter Tuning Logs**  
**Additional Figures & Tables**  

### **Appendix**  

This appendix contains extended evaluation results, confusion matrices for each cross-validation fold, full classification reports, hyperparameter tuning logs, and additional visualizations.  

---

### **Extended Confusion Matrices for Cross-Validation**  

Below are the confusion matrices for each fold during cross-validation. These matrices provide insight into the number of correctly classified ham and spam emails across multiple train-test splits.  

#### **Confusion Matrices Across 10-Fold Cross-Validation**  


# Grid Search for Naïve Bayes (Run this separately for MultinomialNB to get param_alpha properly:)

from sklearn.model_selection import GridSearchCV
from sklearn.naive_bayes import MultinomialNB
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# Define correct parameter grid for Naïve Bayes
param_grid_nb = {"alpha": np.logspace(-3, 1, 5)}  # Alpha values from 0.001 to 10

# Initialize Naïve Bayes classifier
nb_classifier = MultinomialNB()

# Perform Grid Search with Cross-Validation
grid_search_nb = GridSearchCV(nb_classifier, param_grid_nb, cv=5, scoring="accuracy")
grid_search_nb.fit(X_train, y_train)  # Fit only on NB dataset

# Extract results
alphas = grid_search_nb.cv_results_["param_alpha"].data
mean_scores = grid_search_nb.cv_results_["mean_test_score"]

# Convert to DataFrame
grid_results_df = pd.DataFrame({"Alpha": alphas, "Mean Accuracy": mean_scores})
grid_results_df = grid_results_df.sort_values(by="Alpha")

# *Plot Alpha vs Accuracy**
plt.figure(figsize=(8, 5))
plt.plot(grid_results_df["Alpha"], grid_results_df["Mean Accuracy"], marker="o", linestyle="-", color="b")
plt.xscale("log")
plt.xlabel("Alpha (Laplace Smoothing)")
plt.ylabel("Mean Accuracy")
plt.title("Grid Search: Alpha vs Accuracy (Naïve Bayes)")
plt.grid(True)
plt.show()

# **Heatmap Visualization**
heatmap_data = pd.DataFrame(grid_search_nb.cv_results_["mean_test_score"].reshape(-1, 1),
                            index=grid_search_nb.cv_results_["param_alpha"].data,
                            columns=["Mean Accuracy"])

plt.figure(figsize=(8, 5))
sns.heatmap(heatmap_data, annot=True, cmap="coolwarm", fmt=".4f")
plt.xlabel("Mean Accuracy")
plt.ylabel("Alpha Value")
plt.title("Grid Search Heatmap: Naïve Bayes")
plt.show()

from sklearn.metrics import roc_curve, auc

# Get predicted probabilities
y_prob = final_nb.predict_proba(X_test)[:, 1]  # Probability for spam class

# Compute ROC curve
fpr, tpr, _ = roc_curve(y_test, y_prob)
roc_auc = auc(fpr, tpr)

# Plot ROC Curve
plt.figure(figsize=(8, 5))
plt.plot(fpr, tpr, color="blue", lw=2, label=f"ROC Curve (AUC = {roc_auc:.2f})")
plt.plot([0, 1], [0, 1], linestyle="--", color="gray")  # Random guess line
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.title("ROC Curve: Naïve Bayes Spam Detection")
plt.legend()
plt.grid(True)
plt.show()
 from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay

# Generate confusion matrix
conf_matrix = confusion_matrix(y_test, y_pred)

# Display it
disp = ConfusionMatrixDisplay(conf_matrix, display_labels=["Ham", "Spam"])
disp.plot(cmap="Blues")
plt.title("Confusion Matrix: Naïve Bayes")
plt.show()
 import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.cluster import KMeans, DBSCAN
from sklearn.metrics import silhouette_score, adjusted_rand_score
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE
from imblearn.over_sampling import RandomOverSampler

# Data Augmentation
ros = RandomOverSampler(random_state=42) 
X_augmented, y_augmented = ros.fit_resample(X, y)


# Apply K-Means with the best k from previous tuning
best_k = 9  # Based on previous silhouette tuning
kmeans = KMeans(n_clusters=best_k, random_state=42, n_init=10)
kmeans_labels = kmeans.fit_predict(X_augmented)

# Compute Silhouette Score for K-Means
kmeans_silhouette = silhouette_score(X_augmented, kmeans_labels)

# Apply DBSCAN with best parameters found earlier
best_eps = 0.5  # Example best epsilon found
best_min_samples = 5  # Example best min_samples found
dbscan = DBSCAN(eps=best_eps, min_samples=best_min_samples)
dbscan_labels = dbscan.fit_predict(X_augmented)

# Compute Silhouette Score for DBSCAN (excluding noise points)
valid_points = dbscan_labels != -1  # Ignore noise points
if np.sum(valid_points) > 1:  # Ensure we have enough points to compute silhouette
    dbscan_silhouette = silhouette_score(X_augmented[valid_points], dbscan_labels[valid_points])
else:
    dbscan_silhouette = None

# Compute Adjusted Rand Index to compare clusters vs true labels
kmeans_ari = adjusted_rand_score(y_augmented, kmeans_labels)
dbscan_ari = adjusted_rand_score(y_augmented, dbscan_labels)

# Display Results
silhouette_results = {
    "K-Means Silhouette Score": kmeans_silhouette,
    "DBSCAN Silhouette Score": dbscan_silhouette if dbscan_silhouette else "N/A (too many noise points)",
    "K-Means Adjusted Rand Index (ARI)": kmeans_ari,
    "DBSCAN Adjusted Rand Index (ARI)": dbscan_ari
}

silhouette_results
 # Retry visualization using only PCA (faster than t-SNE)
fig, axes = plt.subplots(1, 2, figsize=(12, 5))

# PCA Projection for K-Means
axes[0].scatter(X_pca[:, 0], X_pca[:, 1], c=kmeans_labels, cmap='coolwarm', alpha=0.7)
axes[0].set_title("PCA: K-Means Clustering")

# PCA Projection for DBSCAN
axes[1].scatter(X_pca[:, 0], X_pca[:, 1], c=dbscan_labels, cmap='coolwarm', alpha=0.7)
axes[1].set_title("PCA: DBSCAN Clustering")

plt.show()
 import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import StratifiedKFold, cross_val_score, train_test_split, GridSearchCV
from sklearn.ensemble import RandomForestClassifier
from xgboost import XGBClassifier
from sklearn.metrics import classification_report, confusion_matrix, ConfusionMatrixDisplay, roc_curve, auc

# Split into Training & Test sets (80% train, 20% test)
X_train, X_test, y_train, y_test = train_test_split(X_augmented, y_augmented, test_size=0.2, stratify=y_augmented, random_state=42)

# Define hyperparameter grids
rf_params = {"n_estimators": [100, 200], "max_depth": [10, 20], "random_state": [42]}
xgb_params = {"n_estimators": [100, 200], "max_depth": [3, 6], "learning_rate": [0.1, 0.01], "random_state": [42]}

# Initialize models
rf = RandomForestClassifier()
xgb = XGBClassifier(use_label_encoder=False, eval_metric="logloss")

# Perform Grid Search with 10-fold CV
cv = StratifiedKFold(n_splits=10, shuffle=True, random_state=42)

rf_grid = GridSearchCV(rf, rf_params, cv=cv, scoring="accuracy", n_jobs=-1)
xgb_grid = GridSearchCV(xgb, xgb_params, cv=cv, scoring="accuracy", n_jobs=-1)

rf_grid.fit(X_train, y_train)
xgb_grid.fit(X_train, y_train)

# Get best models
best_rf = rf_grid.best_estimator_
best_xgb = xgb_grid.best_estimator_

# Evaluate on Test Set
y_pred_rf = best_rf.predict(X_test)
y_pred_xgb = best_xgb.predict(X_test)

# Get accuracy & classification reports
rf_report = classification_report(y_test, y_pred_rf, target_names=["Ham", "Spam"])
xgb_report = classification_report(y_test, y_pred_xgb, target_names=["Ham", "Spam"])

# Compute Confusion Matrices
rf_cm = confusion_matrix(y_test, y_pred_rf)
xgb_cm = confusion_matrix(y_test, y_pred_xgb)

# ROC Curve for Random Forest
y_prob_rf = best_rf.predict_proba(X_test)[:, 1]
fpr_rf, tpr_rf, _ = roc_curve(y_test, y_prob_rf)
roc_auc_rf = auc(fpr_rf, tpr_rf)

# ROC Curve for XGBoost
y_prob_xgb = best_xgb.predict_proba(X_test)[:, 1]
fpr_xgb, tpr_xgb, _ = roc_curve(y_test, y_prob_xgb)
roc_auc_xgb = auc(fpr_xgb, tpr_xgb)

# Display results
results = {
    "Best RF Model": best_rf,
    "Best XGB Model": best_xgb,
    "Random Forest Report": rf_report,
    "XGBoost Report": xgb_report,
    "RF Confusion Matrix": rf_cm,
    "XGB Confusion Matrix": xgb_cm,
    "ROC AUC RF": roc_auc_rf,
    "ROC AUC XGB": roc_auc_xgb
}

# Plot Confusion Matrices
fig, axes = plt.subplots(1, 2, figsize=(12, 5))

ConfusionMatrixDisplay(rf_cm, display_labels=["Ham", "Spam"]).plot(ax=axes[0], cmap="Blues")
axes[0].set_title("Confusion Matrix - Random Forest")

ConfusionMatrixDisplay(xgb_cm, display_labels=["Ham", "Spam"]).plot(ax=axes[1], cmap="Blues")
axes[1].set_title("Confusion Matrix - XGBoost")

plt.show()

# Plot ROC Curves
plt.figure(figsize=(8, 5))
plt.plot(fpr_rf, tpr_rf, label=f"RF ROC Curve (AUC = {roc_auc_rf:.2f})", color="blue")
plt.plot(fpr_xgb, tpr_xgb, label=f"XGB ROC Curve (AUC = {roc_auc_xgb:.2f})", color="red")
plt.plot([0, 1], [0, 1], linestyle="--", color="gray")
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.title("ROC Curve: Random Forest vs XGBoost")
plt.legend()
plt.grid(True)
plt.show()

results
 # Self -training with XGBoost (implement self-training by iteratively adding pseudo-labeled high-confidence predictions.)
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, confusion_matrix, ConfusionMatrixDisplay
from xgboost import XGBClassifier
from sklearn.semi_supervised import SelfTrainingClassifier

# Split into labeled (80%) and unlabeled (20%) data
X_labeled, X_unlabeled, y_labeled, _ = train_test_split(X_augmented, y_augmented, test_size=0.2, random_state=42)

# Initialize XGBoost as the base classifier
xgb_base = XGBClassifier(n_estimators=200, max_depth=6, learning_rate=0.1, random_state=42, eval_metric="logloss")

# Use Self-Training wrapper to add pseudo-labeling
self_training_xgb = SelfTrainingClassifier(xgb_base, criterion="threshold", threshold=0.95, verbose=True)
self_training_xgb.fit(X_labeled, y_labeled)

# Evaluate on the original test set
y_pred_self_training = self_training_xgb.predict(X_test)

# Generate evaluation metrics
class_report_self_training = classification_report(y_test, y_pred_self_training)
conf_matrix_self_training = confusion_matrix(y_test, y_pred_self_training)

# Display results
disp = ConfusionMatrixDisplay(conf_matrix_self_training, display_labels=["Ham", "Spam"])
disp.plot()
plt.title("Confusion Matrix: Self-Training XGBoost")
plt.show()

print("\n Classification Report: Self-Training XGBoost\n", class_report_self_training)
 from sklearn.semi_supervised import LabelSpreading

# Assume we have some unlabeled email data (e.g., new incoming emails)
unlabeled_data = X_test.copy()
unlabeled_labels = np.full(X_test.shape[0], -1)  # -1 means unlabeled
# Combine labeled and unlabeled data
X_combined = np.vstack((X_train.toarray(), unlabeled_data.toarray()))
y_combined = np.hstack((y_train, unlabeled_labels))

# Apply Semi-Supervised Learning (Label Spreading)
semi_supervised_model = LabelSpreading(kernel="rbf", alpha=0.2)
semi_supervised_model.fit(X_combined, y_combined)

# Predict using the semi-supervised model
y_pred_semi = semi_supervised_model.predict(X_test)

# Evaluate performance
semi_report = classification_report(y_test, y_pred_semi, target_names=["Ham", "Spam"])
print("\nSemi-Supervised Learning Classification Report:\n", semi_report)
 from sklearn.model_selection import GridSearchCV

# Define hyperparameter grid
xgb_params = {
    "n_estimators": [50, 100, 200],
    "max_depth": [3, 6, 9],
    "learning_rate": [0.01, 0.1, 0.3]
}

# Perform Grid Search
grid_search_xgb = GridSearchCV(XGBClassifier(use_label_encoder=False, eval_metric="logloss", random_state=42),
                               xgb_params, cv=3, scoring="accuracy", n_jobs=-1)
grid_search_xgb.fit(X_train, y_train)

# Print best parameters
print("Best XGBoost Parameters:", grid_search_xgb.best_params_)

# Evaluate the optimized XGBoost model
best_xgb = grid_search_xgb.best_estimator_
xgb_final_pred = best_xgb.predict(X_test)

xgb_final_report = classification_report(y_test, xgb_final_pred, target_names=["Ham", "Spam"])
print("\nOptimized XGBoost Classification Report:\n", xgb_final_report)
  # Save Final Model Parameters & Best Hyperparameters

 import joblib

# Save the final self-training XGBoost model
joblib.dump(self_training_xgb, "self_training_xgb_model.pkl")

# Save the standard XGBoost model as a backup
joblib.dump(xgb_final, "xgb_backup_model.pkl")

print("Models saved successfully!")


# Save the Best Hyperparameters
# Store hyperparameters in a dictionary
best_hyperparams = {
    "Self-Training XGBoost": {
        "n_estimators": 200,
        "max_depth": 6,
        "learning_rate": 0.1,
        "eval_metric": "logloss",
        "threshold": 0.95,  # Pseudo-labeling confidence threshold
    },
    "XGBoost Backup": {
        "n_estimators": 200,
        "max_depth": 6,
        "learning_rate": 0.1,
        "eval_metric": "logloss",
    }
}

# Save hyperparameters as JSON
import json
with open("best_hyperparameters.json", "w") as f:
    json.dump(best_hyperparams, f, indent=4)

print("Best hyperparameters saved!")
 # Prepare Deployment Plan
from flask import Flask, request, jsonify
import joblib
import numpy as np

# Load the saved model
model = joblib.load("self_training_xgb_model.pkl")

app = Flask(__name__)

@app.route("/predict", methods=["POST"])
def predict():
    data = request.json
    X_input = np.array(data["features"]).reshape(1, -1)
    prediction = model.predict(X_input)
    return jsonify({"prediction": int(prediction[0])})

if __name__ == "__main__":
    app.run(port=5000, debug=True)
LS0tDQp0aXRsZTogIkpNY1BoYXVsX0Nhc2VTdHVkeTNfU3BhbUFzc2Fzc2luIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQplZGl0b3Jfb3B0aW9uczogDQogIG1hcmtkb3duOiANCiAgICB3cmFwOiBzZW50ZW5jZQ0KLS0tDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQoqKkNhc2UgU3R1ZHkgMzogQnVpbGRpbmcgYSBTcGFtIENsYXNzaWZpZXIgVXNpbmcgTmHDr3ZlIEJheWVzIGFuZCBDbHVzdGVyaW5nKipcDQoqKkplc3NpY2EgTWNQaGF1bCoqXA0KKipTTVUgLSA3MzMzIC0gUXVhbnRpZnlpbmcgdGhlIFdvcmxkKipcDQoqKkRhdGU6IEZlYnJ1YXJ5IDE3LCAyMDI1KioNCg0KWyFbXSgpXShodHRwczovL3VwbG9hZC53aWtpbWVkaWEub3JnL3dpa2lwZWRpYS9jb21tb25zLzIvMmUvU3BhbUFzc2Fzc2luX0xvZ28ucG5nKQ0KDQohW10oaW1hZ2VzL2NsaXBib2FyZC0xMzI5ODUwMjgzLnBuZykNCg0KIyMjICoqTmHDr3ZlIEJheWVzIEZvcm11bGEqKg0KDQpHaXZlbiBhbiBlbWFpbCB3aXRoIHdvcmRzICR3XzEsIHdfMiwgLi4uLCB3X24kLCB0aGUgcHJvYmFiaWxpdHkgdGhhdCBpdCBiZWxvbmdzIHRvIHNwYW0gKCRTJCkgaXMgY29tcHV0ZWQgYXM6DQoNCiQkDQpQKFMgfCB3XzEsIHdfMiwgLi4uLCB3X24pID0gXGZyYWN7UChTKSBccHJvZF97aT0xfV57bn0gUCh3X2kgfCBTKX17UCh3XzEsIHdfMiwgLi4uLCB3X24pfQ0KJCQNCg0KV2hlcmU6XA0KLSAkUChTKSQgaXMgdGhlIHByaW9yIHByb2JhYmlsaXR5IG9mIHNwYW1cDQotICRQKHdfaSB8IFMpJCBpcyB0aGUgbGlrZWxpaG9vZCBvZiB3b3JkICR3X2kkIGdpdmVuIHNwYW1cDQotICRQKHdfMSwgd18yLCAuLi4sIHdfbikkIGlzIHRoZSBvdmVyYWxsIHByb2JhYmlsaXR5IG9mIHRoZSB3b3JkcyBhcHBlYXJpbmcNCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCg0KDQojIyMgKipFeGVjdXRpdmUgU3VtbWFyeSoqDQoNCiMjIyMgKipQcm9ibGVtIFN0YXRlbWVudCoqDQpPcmdhbml6YXRpb25zIGZhY2UgYSBzaWduaWZpY2FudCBjaGFsbGVuZ2UgaW4gaGFuZGxpbmcgaGlnaCB2b2x1bWVzIG9mIHNwYW0gZW1haWxzLCB3aGljaCBjYW4gcmVzdWx0IGluIGxvc3QgcHJvZHVjdGl2aXR5LCBzZWN1cml0eSByaXNrcywgYW5kIG1pc3NlZCBsZWdpdGltYXRlIGNvbW11bmljYXRpb25zLiBUaGUgZ29hbCBvZiB0aGlzIGNhc2Ugc3R1ZHkgaXMgdG8gZGV2ZWxvcCBhIHJvYnVzdCBzcGFtIGNsYXNzaWZpY2F0aW9uIG1vZGVsIHVzaW5nICoqTmHDr3ZlIEJheWVzIGFuZCBjbHVzdGVyaW5nKiogdG8gZmlsdGVyIG91dCBzcGFtIHdoaWxlIG1pbmltaXppbmcgZmFsc2UgcG9zaXRpdmVzLg0KDQojIyMjICoqRGF0YSBPdmVydmlldyoqDQotICoqRGF0YXNldCBTb3VyY2U6KiogRXh0cmFjdGVkIGZyb20gU3BhbUFzc2Fzc2luIGVtYWlsIGFyY2hpdmVzICANCi0gKipUb3RhbCBFbWFpbHMgUHJvY2Vzc2VkOioqIDksMDAwICANCi0gKipTcGFtIHZzLiBIYW0gRGlzdHJpYnV0aW9uOioqIDMsNTAwIHNwYW0gZW1haWxzLCA3LDUwMCBoYW0gZW1haWxzICANCi0gKipGZWF0dXJlcyBVc2VkOioqIEJhZy1vZi1Xb3JkcyAoQm9XKSwgVGVybSBGcmVxdWVuY3ktSW52ZXJzZSBEb2N1bWVudCBGcmVxdWVuY3kgKFRGLUlERiksIGFuZCBDbHVzdGVyaW5nLUJhc2VkIEZlYXR1cmVzICANCg0KIyMjIyAqKk1ldGhvZG9sb2d5KioNCi0gKipQcmVwcm9jZXNzaW5nIFN0ZXBzOioqDQogIC0gRW1haWwgdGV4dCBleHRyYWN0aW9uIGFuZCBjbGVhbmluZyAocmVtb3ZpbmcgSFRNTCwgaGVhZGVycywgYW5kIGF0dGFjaG1lbnRzKQ0KICAtIFRva2VuaXphdGlvbiwgc3RvcHdvcmQgcmVtb3ZhbCwgc3RlbW1pbmcvbGVtbWF0aXphdGlvbg0KICAtIEZlYXR1cmUgZW5naW5lZXJpbmcgdXNpbmcgQm9XLCBURi1JREYsIGFuZCBjbHVzdGVyaW5nDQotICoqTW9kZWxzIEltcGxlbWVudGVkOioqDQogIC0gKipOYcOvdmUgQmF5ZXMgQ2xhc3NpZmllcioqIChiYXNlbGluZSBtb2RlbCBmb3IgdGV4dCBjbGFzc2lmaWNhdGlvbikNCiAgLSAqKkstTWVhbnMgYW5kIERCU0NBTiBDbHVzdGVyaW5nKiogKHRvIGV4cGxvcmUgcGF0dGVybnMgaW4gc3BhbSB2cy4gaGFtIG1lc3NhZ2VzKQ0KICAtICoqUmFuZG9tIEZvcmVzdCBhbmQgWEdCb29zdCoqIChhZGRpdGlvbmFsIHN1cGVydmlzZWQgbW9kZWxzIGZvciBjb21wYXJpc29uKQ0KICAtICoqU2VtaS1TdXBlcnZpc2VkIExlYXJuaW5nIHdpdGggU2VsZi1UcmFpbmluZyBYR0Jvb3N0KiogKGZpbmFsIG9wdGltaXplZCBtb2RlbCkNCi0gKipIeXBlcnBhcmFtZXRlciBUdW5pbmc6KioNCiAgLSAqKk5hw692ZSBCYXllczoqKiBMYXBsYWNlIHNtb290aGluZyAoYWxwaGEgdHVuaW5nKQ0KICAtICoqSy1NZWFuczoqKiBPcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycyBiYXNlZCBvbiBzaWxob3VldHRlIHNjb3JlDQogIC0gKipYR0Jvb3N0OioqIEdyaWQgc2VhcmNoIG92ZXIgbGVhcm5pbmcgcmF0ZSwgbWF4IGRlcHRoLCBhbmQgbnVtYmVyIG9mIGVzdGltYXRvcnMNCg0KIyMjIyAqKktleSBGaW5kaW5ncyAmIFBlcmZvcm1hbmNlIE1ldHJpY3MqKg0KDQp8ICoqTW9kZWwqKiB8ICoqQWNjdXJhY3kqKiB8ICoqUHJlY2lzaW9uKiogfCAqKlJlY2FsbCoqIHwgKipGMS1TY29yZSoqIHwNCnwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS18DQp8IE5hw692ZSBCYXllcyB8IDk4LjQlIHwgOTkuMSUgfCA5Ny44JSB8IDk4LjQlIHwNCnwgUmFuZG9tIEZvcmVzdCB8IDk3JSB8IDk1LjMlIHwgOTQuNiUgfCA5NS4wJSB8DQp8IFhHQm9vc3QgfCA5OSUgfCA5OS40JSB8IDk5LjElIHwgOTkuMyUgfA0KfCAqKlNlbGYtVHJhaW5pbmcgWEdCb29zdCoqIChGaW5hbCBNb2RlbCkgfCAqKjEwMCUqKiB8ICoqMTAwJSoqIHwgKioxMDAlKiogfCAqKjEwMCUqKiB8DQoNCi0gKipDb25mdXNpb24gTWF0cml4IEhpZ2hsaWdodHM6KioNCiAgLSBGYWxzZSBwb3NpdGl2ZXM6IDcgKFNlbGYtVHJhaW5pbmcgWEdCb29zdCkNCiAgLSBGYWxzZSBuZWdhdGl2ZXM6IDAgKFNlbGYtVHJhaW5pbmcgWEdCb29zdCkNCi0gKipST0MtQVVDIFNjb3JlOioqIDEuMDAgKFNlbGYtVHJhaW5pbmcgWEdCb29zdCwgaW5kaWNhdGluZyBwZXJmZWN0IGRpc2NyaW1pbmF0b3J5IHBvd2VyKQ0KLSAqKkNsdXN0ZXJpbmcgSW5zaWdodHM6KioNCiAgLSBEQlNDQU4gYWNoaWV2ZWQgdGhlIGJlc3Qgc2lsaG91ZXR0ZSBzY29yZSAoKiowLjkxMzIqKikNCiAgLSBDbHVzdGVyIGFuYWx5c2lzIHJldmVhbGVkIHBhdHRlcm5zIGluIHNwYW0gdHlwZXMgKGUuZy4sIGZpbmFuY2lhbCBzcGFtLCBwaGlzaGluZywgcHJvbW90aW9uYWwgZW1haWxzKQ0KDQojIyMjICoqRmluYWwgUmVjb21tZW5kYXRpb24qKg0KLSAqKkJlc3QgTW9kZWw6KiogU2VsZi1UcmFpbmluZyBYR0Jvb3N0DQotICoqQmFja3VwIE1vZGVscyBmb3IgRnV0dXJlIEV2YWx1YXRpb246KiogTmHDr3ZlIEJheWVzIGFuZCBSYW5kb20gRm9yZXN0DQotICoqRGVwbG95bWVudCBDb25zaWRlcmF0aW9uczoqKg0KICAtIFBlcmlvZGljIG1vZGVsIHJldHJhaW5pbmcgcmVxdWlyZWQgYXMgc3BhbSBwYXR0ZXJucyBldm9sdmUNCiAgLSBQb3RlbnRpYWwgaW50ZWdyYXRpb24gd2l0aCBleGlzdGluZyBlbWFpbCBmaWx0ZXJpbmcgc29sdXRpb25zDQoNCi0tLQ0KDQojIyMgKipJbnRyb2R1Y3Rpb24qKg0KDQojIyMjICoqQmFja2dyb3VuZCBhbmQgTW90aXZhdGlvbioqDQpTcGFtIGVtYWlscyBjb250aW51ZSB0byBiZSBhIHNpZ25pZmljYW50IGNoYWxsZW5nZSBmb3Igb3JnYW5pemF0aW9ucywgbGVhZGluZyB0byBzZWN1cml0eSByaXNrcywgZGVjcmVhc2VkIHByb2R1Y3Rpdml0eSwgYW5kIHRoZSBwb3RlbnRpYWwgbG9zcyBvZiBsZWdpdGltYXRlIGNvbW11bmljYXRpb25zLiBBY2NvcmRpbmcgdG8gcmVjZW50IHN0dWRpZXMsIHNwYW0gYWNjb3VudHMgZm9yICoqb3ZlciA1MCUqKiBvZiBhbGwgZW1haWwgdHJhZmZpYyB3b3JsZHdpZGUsIG1ha2luZyBlZmZpY2llbnQgZmlsdGVyaW5nIHRlY2huaXF1ZXMgYSBuZWNlc3NpdHkuIE9yZ2FuaXphdGlvbnMgbXVzdCBpbXBsZW1lbnQgcm9idXN0IHNwYW0gY2xhc3NpZmljYXRpb24gbW9kZWxzIHRoYXQgY2FuIGJhbGFuY2UgdGhlIHJpc2tzIG9mICoqZmFsc2UgcG9zaXRpdmVzKiogKGxlZ2l0aW1hdGUgZW1haWxzIGluY29ycmVjdGx5IGNsYXNzaWZpZWQgYXMgc3BhbSkgYW5kICoqZmFsc2UgbmVnYXRpdmVzKiogKHNwYW0gZW1haWxzIGJ5cGFzc2luZyBmaWx0ZXJzKS4NCg0KVGhpcyBjYXNlIHN0dWR5IGZvY3VzZXMgb24gYnVpbGRpbmcgYSAqKnNwYW0gY2xhc3NpZmljYXRpb24gbW9kZWwgdXNpbmcgTmHDr3ZlIEJheWVzIGFuZCBjbHVzdGVyaW5nIHRlY2huaXF1ZXMqKiwgZXhwYW5kaW5nIHRvIGluY2x1ZGUgKipSYW5kb20gRm9yZXN0LCBYR0Jvb3N0LCBhbmQgc2VtaS1zdXBlcnZpc2VkIGxlYXJuaW5nKiogZm9yIGltcHJvdmVkIHBlcmZvcm1hbmNlLiBPdXIgb2JqZWN0aXZlIGlzIHRvIGRldmVsb3AgYSBzY2FsYWJsZSBhbmQgZ2VuZXJhbGl6YWJsZSBzcGFtIGZpbHRlciB0aGF0IG1pbmltaXplcyBib3RoIGZhbHNlIHBvc2l0aXZlcyBhbmQgZmFsc2UgbmVnYXRpdmVzIHdoaWxlIGVuc3VyaW5nIGFjY3VyYXRlIGNsYXNzaWZpY2F0aW9uLg0KDQojIyMjICoqT3JnYW5pemF0aW9uYWwgQ29udGV4dCoqDQpUaGlzIHN0dWR5IGlzIG1vdGl2YXRlZCBieSBhIHJlcXVlc3QgZnJvbSB0aGUgKipJVCBkZXBhcnRtZW50KiosIHdoaWNoIGZhY2VzIGFuIG92ZXJ3aGVsbWluZyBudW1iZXIgb2Ygc3BhbSBlbWFpbHMuIFRoZSBrZXkgY29uY2VybnMgaW5jbHVkZToNCjEuICoqT3Zlci1maWx0ZXJpbmc6KiogQ3JpdGljYWwgYnVzaW5lc3MgZW1haWxzIG1heSBiZSBtaXN0YWtlbmx5IGNhdGVnb3JpemVkIGFzIHNwYW0uDQoyLiAqKlVuZGVyLWZpbHRlcmluZzoqKiBUb28gbXVjaCBzcGFtIG1heSBjbHV0dGVyIGluYm94ZXMsIHJlZHVjaW5nIHByb2R1Y3Rpdml0eS4NCjMuICoqU2NhbGFiaWxpdHk6KiogVGhlIHNvbHV0aW9uIG11c3QgaGFuZGxlIGxhcmdlIHZvbHVtZXMgb2YgZW1haWwgZGF0YSBlZmZpY2llbnRseS4NCg0KIyMjIyAqKk9iamVjdGl2ZXMgYW5kIFJlc2VhcmNoIFF1ZXN0aW9ucyoqDQpUaGUga2V5IG9iamVjdGl2ZXMgb2YgdGhpcyBzdHVkeSBhcmU6DQoxLiAqKkRldmVsb3AgYSByb2J1c3Qgc3BhbSBjbGFzc2lmaWVyKiogY2FwYWJsZSBvZiBkaXN0aW5ndWlzaGluZyBiZXR3ZWVuIHNwYW0gYW5kIGhhbSAobGVnaXRpbWF0ZSkgZW1haWxzLg0KMi4gKipJbmNvcnBvcmF0ZSBjbHVzdGVyaW5nKiogdG8gZGV0ZWN0IHVuZGVybHlpbmcgc3BhbSBwYXR0ZXJucy4NCjMuICoqRXZhbHVhdGUgbXVsdGlwbGUgY2xhc3NpZmljYXRpb24gbW9kZWxzKiosIGluY2x1ZGluZyBOYcOvdmUgQmF5ZXMsIFJhbmRvbSBGb3Jlc3QsIGFuZCBYR0Jvb3N0Lg0KNC4gKipJbXBsZW1lbnQgc2VtaS1zdXBlcnZpc2VkIGxlYXJuaW5nKiogdG8gaW1wcm92ZSBtb2RlbCBwZXJmb3JtYW5jZSB3aXRoIHVubGFiZWxlZCBkYXRhLg0KNS4gKipBc3Nlc3MgbW9kZWwgcGVyZm9ybWFuY2UgdXNpbmcgY3Jvc3MtdmFsaWRhdGlvbioqLCBjb25mdXNpb24gbWF0cmljZXMsIGFuZCBBVUMtUk9DIGN1cnZlcy4NCg0KIyMjIyAqKk91dGxpbmUgb2YgdGhlIFdoaXRlIFBhcGVyKioNClRoZSByZW1haW5kZXIgb2YgdGhpcyB3aGl0ZSBwYXBlciBpcyBzdHJ1Y3R1cmVkIGFzIGZvbGxvd3M6DQotICoqTGl0ZXJhdHVyZSBSZXZpZXc6KiogT3ZlcnZpZXcgb2YgZXhpc3Rpbmcgc3BhbSBmaWx0ZXJpbmcgbWV0aG9kcy4NCi0gKipEYXRhIERlc2NyaXB0aW9uICYgUHJlcGFyYXRpb246KiogSG93IHRoZSBkYXRhc2V0IHdhcyBjcmVhdGVkLCBjbGVhbmVkLCBhbmQgcHJvY2Vzc2VkLg0KLSAqKk1ldGhvZG9sb2d5ICYgTW9kZWwgQnVpbGRpbmc6KiogRXhwbGFuYXRpb24gb2YgTmHDr3ZlIEJheWVzLCBjbHVzdGVyaW5nIHRlY2huaXF1ZXMsIGFuZCBzdXBlcnZpc2VkIGxlYXJuaW5nIG1vZGVscy4NCi0gKipSZXN1bHRzICYgRXZhbHVhdGlvbjoqKiBQZXJmb3JtYW5jZSBtZXRyaWNzLCBjb25mdXNpb24gbWF0cmljZXMsIGFuZCBST0MgY3VydmVzLg0KLSAqKkRpc2N1c3Npb24gJiBJbnNpZ2h0czoqKiBBbmFseXNpcyBvZiBmaW5kaW5ncywgbGltaXRhdGlvbnMsIGFuZCBwb3RlbnRpYWwgaW1wcm92ZW1lbnRzLg0KLSAqKkNvbmNsdXNpb24gJiBSZWNvbW1lbmRhdGlvbnM6KiogU3VtbWFyeSBvZiByZXN1bHRzIGFuZCBmdXR1cmUgd29yay4NCg0KDQoNCi0tLQ0KDQojIyMgKipNZXRob2RvbG9neSBhbmQgTW9kZWwgQnVpbGRpbmcqKg0KDQojIyMjICoqRmVhdHVyZSBFbmdpbmVlcmluZyBhbmQgRGF0YSBQcmVwYXJhdGlvbioqDQotICoqRW1haWwgVGV4dCBQcmVwcm9jZXNzaW5nOioqDQogIC0gVG9rZW5pemF0aW9uLCBzdG9wd29yZCByZW1vdmFsLCBzdGVtbWluZy9sZW1tYXRpemF0aW9uDQogIC0gRmVhdHVyZSBleHRyYWN0aW9uIHVzaW5nIFRGLUlERiBhbmQgQm9XDQotICoqQ2x1c3RlcmluZyBBbmFseXNpczoqKg0KICAtIEstTWVhbnMgYW5kIERCU0NBTiB1c2VkIHRvIGV4cGxvcmUgcGF0dGVybnMgaW4gc3BhbSBlbWFpbHMNCiAgLSBEQlNDQU4gc2hvd2VkIGJldHRlciBzZXBhcmF0aW9uIG9mIGNsdXN0ZXJzDQotICoqU3VwZXJ2aXNlZCBMZWFybmluZyBNb2RlbHM6KioNCiAgLSBOYcOvdmUgQmF5ZXMsIFJhbmRvbSBGb3Jlc3QsIFhHQm9vc3QsIGFuZCBTZWxmLVRyYWluaW5nIFhHQm9vc3QNCi0gKipIeXBlcnBhcmFtZXRlciBUdW5pbmc6KioNCiAgLSBDcm9zcy12YWxpZGF0aW9uIGFuZCBncmlkIHNlYXJjaCBmb3IgbW9kZWwgb3B0aW1pemF0aW9uDQoNCiMjIyMgKipSZXN1bHRzIGFuZCBFdmFsdWF0aW9uKioNCi0gKipOYcOvdmUgQmF5ZXMgYWNoaWV2ZWQgOTguNCUgYWNjdXJhY3kqKg0KLSAqKlhHQm9vc3Qgb3V0cGVyZm9ybWVkIE5hw692ZSBCYXllcyB3aXRoIDk5JSBhY2N1cmFjeSoqDQotICoqU2VsZi1UcmFpbmluZyBYR0Jvb3N0IHJlYWNoZWQgMTAwJSBhY2N1cmFjeSB3aXRoIDAgZmFsc2UgbmVnYXRpdmVzKioNCi0gKipDbHVzdGVyaW5nIHByb3ZpZGVkIGluc2lnaHRzIGJ1dCB3YXMgbm90IHVzZWQgZm9yIGZpbmFsIGNsYXNzaWZpY2F0aW9uKioNCg0KLS0tDQoNCiMjIyAqKkNvbmNsdXNpb24gYW5kIFJlY29tbWVuZGF0aW9ucyoqDQotICoqU2VsZi1UcmFpbmluZyBYR0Jvb3N0IGlzIHRoZSBiZXN0IG1vZGVsKiogd2l0aCAxMDAlIGFjY3VyYWN5Lg0KLSAqKlhHQm9vc3QgaXMgYSBzdHJvbmcgYWx0ZXJuYXRpdmUqKiBmb3IgZGVwbG95bWVudCBpZiBzZW1pLXN1cGVydmlzZWQgbGVhcm5pbmcgaXMgbm90IGZlYXNpYmxlLg0KLSAqKlJlZ3VsYXIgcmV0cmFpbmluZyBpcyBuZWNlc3NhcnkqKiBhcyBzcGFtIHBhdHRlcm5zIGV2b2x2ZS4NCi0gKipGdXR1cmUgd29yazoqKiBFeHBsb3JpbmcgZGVlcCBsZWFybmluZyBtb2RlbHMgbGlrZSB0cmFuc2Zvcm1lcnMgZm9yIGltcHJvdmVkIHNwYW0gZGV0ZWN0aW9uLg0KDQotLS0NCg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KDQoNCiMjIyAqKkxpdGVyYXR1cmUgUmV2aWV3IC8gUmVsYXRlZCBXb3JrKioNCg0KIyMjIyAqKkV4aXN0aW5nIFNwYW0gRGV0ZWN0aW9uIFRlY2huaXF1ZXMqKg0KDQpTcGFtIGZpbHRlcmluZyBoYXMgYmVlbiBhIGNyaXRpY2FsIGFyZWEgb2YgcmVzZWFyY2ggaW4gKiptYWNoaW5lIGxlYXJuaW5nKiogYW5kICoqbmF0dXJhbCBsYW5ndWFnZSBwcm9jZXNzaW5nIChOTFApKiogZm9yIGRlY2FkZXMuDQpWYXJpb3VzIG1ldGhvZHMgaGF2ZSBiZWVuIGRldmVsb3BlZCB0byBjbGFzc2lmeSBlbWFpbHMgYXMgc3BhbSBvciBoYW0sIGJyb2FkbHkgY2F0ZWdvcml6ZWQgaW50byAqKnJ1bGUtYmFzZWQgZmlsdGVyaW5nLCBzdXBlcnZpc2VkIGxlYXJuaW5nLCBhbmQgdW5zdXBlcnZpc2VkIGxlYXJuaW5nKiogYXBwcm9hY2hlcy4NCg0KMS4gICoqUnVsZS1CYXNlZCBGaWx0ZXJpbmcgKEhldXJpc3RpYyBBcHByb2FjaGVzKSoqDQogICAgLSAgIEVhcmx5IHNwYW0gZmlsdGVycywgc3VjaCBhcyAqKlNwYW1Bc3Nhc3NpbioqLCByZWxpZWQgb24gKiptYW51YWxseSBjcmFmdGVkIHJ1bGVzKiogdG8gaWRlbnRpZnkgc3BhbS4gVGhlc2UgcnVsZXMgZXZhbHVhdGVkICoqd29yZCBmcmVxdWVuY3ksIGJsYWNrbGlzdHMsIGFuZCBtZXRhZGF0YSoqLg0KICAgIC0gICBBbHRob3VnaCBlZmZlY3RpdmUgZm9yIHByZWRlZmluZWQgc3BhbSBwYXR0ZXJucywgcnVsZS1iYXNlZCBzeXN0ZW1zIHN0cnVnZ2xlZCB3aXRoICoqYWRhcHRhYmlsaXR5KiogdG8gbmV3IHNwYW0gdHJlbmRzLg0KMi4gICoqU3VwZXJ2aXNlZCBMZWFybmluZy1CYXNlZCBBcHByb2FjaGVzKioNCiAgICAtICAgKipOYcOvdmUgQmF5ZXMgKE5CKSoqOiBBICoqcHJvYmFiaWxpc3RpYyBjbGFzc2lmaWVyKiogdGhhdCBhc3N1bWVzIGZlYXR1cmUgaW5kZXBlbmRlbmNlIGFuZCBjYWxjdWxhdGVzIHRoZSBsaWtlbGlob29kIG9mIGFuIGVtYWlsIGJlaW5nIHNwYW0gYmFzZWQgb24gd29yZCBwcm9iYWJpbGl0aWVzLg0KICAgIC0gICAqKlN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzIChTVk1zKSoqOiBBIHN0cm9uZyBjbGFzc2lmaWVyIGZvciBoaWdoLWRpbWVuc2lvbmFsIHRleHQgZGF0YSwgb2Z0ZW4gb3V0cGVyZm9ybWluZyBOYcOvdmUgQmF5ZXMgaW4gcHJlY2lzaW9uIGFuZCByZWNhbGwuDQogICAgLSAgICoqUmFuZG9tIEZvcmVzdCAmIFhHQm9vc3QqKjogRW5zZW1ibGUgbWV0aG9kcyB0aGF0IGFnZ3JlZ2F0ZSBtdWx0aXBsZSBkZWNpc2lvbiB0cmVlcyBmb3IgaW1wcm92ZWQgYWNjdXJhY3kgYW5kIGZlYXR1cmUgaW1wb3J0YW5jZSBldmFsdWF0aW9uLg0KICAgIC0gICAqKkRlZXAgTGVhcm5pbmcgQXBwcm9hY2hlcyoqOiBNb2RlbHMgbGlrZSAqKlJlY3VycmVudCBOZXVyYWwgTmV0d29ya3MgKFJOTnMpKiogYW5kICoqVHJhbnNmb3JtZXJzIChlLmcuLCBCRVJUKSoqIGhhdmUgcmVjZW50bHkgc2hvd24gc3RhdGUtb2YtdGhlLWFydCBwZXJmb3JtYW5jZSBpbiBzcGFtIGNsYXNzaWZpY2F0aW9uLg0KMy4gICoqVW5zdXBlcnZpc2VkIExlYXJuaW5nIGZvciBTcGFtIERldGVjdGlvbioqDQogICAgLSAgICoqQ2x1c3RlcmluZyAoSy1NZWFucywgREJTQ0FOLCBIaWVyYXJjaGljYWwpKio6IEdyb3VwcyBzaW1pbGFyIGVtYWlscyB0b2dldGhlciB3aXRob3V0IHByZWRlZmluZWQgbGFiZWxzLiBUaGlzIGlzIHVzZWZ1bCBmb3IgZGlzY292ZXJpbmcgKipwcmV2aW91c2x5IHVuc2VlbiBzcGFtIHBhdHRlcm5zKiouDQogICAgLSAgICoqU2VtaS1TdXBlcnZpc2VkIExlYXJuaW5nKio6IFRlY2huaXF1ZXMgbGlrZSAqKlNlbGYtVHJhaW5pbmcgYW5kIExhYmVsIFByb3BhZ2F0aW9uKiogZW5hYmxlIHRyYWluaW5nIG1vZGVscyB3aXRoIGEgbWl4IG9mIGxhYmVsZWQgYW5kIHVubGFiZWxlZCBkYXRhIHRvIGltcHJvdmUgZ2VuZXJhbGl6YXRpb24uDQoNCiMjIyMgKipOYcOvdmUgQmF5ZXMgZm9yIFNwYW0gRGV0ZWN0aW9uKioNCg0KTmHDr3ZlIEJheWVzIGlzIGEgcG9wdWxhciBzcGFtIGRldGVjdGlvbiBtb2RlbCBkdWUgdG8gaXRzOiAxLg0KKipGYXN0IFRyYWluaW5nICYgSW5mZXJlbmNlKiog4oCTIFdvcmtzIGVmZmljaWVudGx5IGV2ZW4gb24gbGFyZ2UgZGF0YXNldHMuDQoyLg0KKipSb2J1c3RuZXNzIHRvIEhpZ2gtRGltZW5zaW9uYWwgRGF0YSoqIOKAkyBIYW5kbGVzIHRleHQgZGF0YSB3ZWxsIHVzaW5nICoqVEYtSURGKiogb3IgKipCYWctb2YtV29yZHMgKEJvVykqKiByZXByZXNlbnRhdGlvbnMuDQozLg0KKipTdHJvbmcgVGhlb3JldGljYWwgRm91bmRhdGlvbioqIOKAkyBCYXNlZCBvbiAqKkJheWVz4oCZIFRoZW9yZW0qKiwgYXNzdW1pbmcgZmVhdHVyZSBpbmRlcGVuZGVuY2U6DQoNCiQkDQpQKFNwYW0gfCBFbWFpbCkgPSBcZnJhY3tQKEVtYWlsIHwgU3BhbSkgXGNkb3QgUChTcGFtKX17UChFbWFpbCl9DQokJA0KDQpEZXNwaXRlIGl0cyBhZHZhbnRhZ2VzLCBOYcOvdmUgQmF5ZXMgY2FuIHN0cnVnZ2xlIHdpdGg6IC0gKipXb3JkIGRlcGVuZGVuY2llcyoqIChhc3N1bXB0aW9uIG9mIGZlYXR1cmUgaW5kZXBlbmRlbmNlIGlzIG9mdGVuIHVucmVhbGlzdGljKS4NCi0gKipIYW5kbGluZyBvZiByYXJlIHdvcmRzKiogKG1pdGlnYXRlZCB1c2luZyAqKkxhcGxhY2Ugc21vb3RoaW5nKiopLg0KDQojIyMjICoqQ2x1c3RlcmluZyBpbiBTcGFtIERldGVjdGlvbioqDQoNCkNsdXN0ZXJpbmcgbWV0aG9kcyBsaWtlICoqSy1NZWFucyoqIGFuZCAqKkRCU0NBTioqIGNhbjogMS4NCioqRGV0ZWN0IGhpZGRlbiBzcGFtIHBhdHRlcm5zKiogYnkgZ3JvdXBpbmcgc2ltaWxhciBlbWFpbHMuDQoyLg0KKipQcm92aWRlIGFkZGl0aW9uYWwgZmVhdHVyZXMqKiBmb3Igc3VwZXJ2aXNlZCBsZWFybmluZyBtb2RlbHMuDQozLg0KKipBaWQgaW4gc2VtaS1zdXBlcnZpc2VkIGxlYXJuaW5nKiogYnkgcHNldWRvLWxhYmVsaW5nIHVubGFiZWxlZCBkYXRhLg0KDQpDaGFsbGVuZ2VzIGluY2x1ZGU6IC0gKipDaG9vc2luZyB0aGUgb3B0aW1hbCBudW1iZXIgb2YgY2x1c3RlcnMgKGsgZm9yIEstTWVhbnMpKiogLSAqKkhhbmRsaW5nIGhpZ2gtZGltZW5zaW9uYWwgdGV4dCBkYXRhKiogKFBDQSBvciB0LVNORSBjYW4gaGVscCB2aXN1YWxpemUgY2x1c3RlcnMpLg0KDQojIyMjICoqSW50ZWdyYXRpb24gb2YgQ2x1c3RlcmluZyAmIE5hw692ZSBCYXllcyoqDQoNClNldmVyYWwgc3R1ZGllcyBzdWdnZXN0IGNvbWJpbmluZyBjbHVzdGVyaW5nIHdpdGggTmHDr3ZlIEJheWVzOiAtIENsdXN0ZXJpbmcgY2FuICoqcHJlLWxhYmVsIGRhdGEqKiBmb3IgTmHDr3ZlIEJheWVzIHRyYWluaW5nLg0KLSBOYcOvdmUgQmF5ZXMgY2FuIGJlICoqZW5oYW5jZWQgd2l0aCBjbHVzdGVyLWJhc2VkIGZlYXR1cmVzKiouDQotIFNlbWktc3VwZXJ2aXNlZCBsZWFybmluZyB2aWEgKipMYWJlbCBQcm9wYWdhdGlvbioqIGNhbiBpbXByb3ZlIGFjY3VyYWN5IHdoZW4gbGFiZWxlZCBkYXRhIGlzIGxpbWl0ZWQuDQoNCiMjIyMgKipDb25jbHVzaW9uIGZyb20gUmVsYXRlZCBXb3JrKioNCg0KLSAgICoqTmHDr3ZlIEJheWVzIHJlbWFpbnMgYSBzdHJvbmcgYmFzZWxpbmUqKiBmb3Igc3BhbSBkZXRlY3Rpb24uDQotICAgKipDbHVzdGVyaW5nIGNhbiBwcm92aWRlIGluc2lnaHRzIGFuZCBmZWF0dXJlcyoqIGZvciBiZXR0ZXIgY2xhc3NpZmljYXRpb24uDQotICAgKipBZHZhbmNlZCBtb2RlbHMgKFhHQm9vc3QsIFNlbGYtVHJhaW5pbmcsIExhYmVsIFByb3BhZ2F0aW9uKSoqIGltcHJvdmUgc3BhbSBkZXRlY3Rpb24gYnkgbGV2ZXJhZ2luZyAqKmVuc2VtYmxlIGxlYXJuaW5nIGFuZCBzZW1pLXN1cGVydmlzZWQgbWV0aG9kcyoqLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjICoqRGF0YSBEZXNjcmlwdGlvbiBhbmQgUHJlcGFyYXRpb24qKg0KDQojIyMjICoqQS4gU291cmNlcyBvZiBEYXRhKioNCg0KVGhlIGRhdGFzZXQgdXNlZCBpbiB0aGlzIHN0dWR5IHdhcyBleHRyYWN0ZWQgZnJvbSAqKlNwYW1Bc3Nhc3NpbioqLCBhIHdlbGwta25vd24gc3BhbSBmaWx0ZXJpbmcgZnJhbWV3b3JrLg0KVGhlIHJhdyBkYXRhIGNvbnNpc3RlZCBvZiAqKnBsYWluLXRleHQgZW1haWwgbWVzc2FnZXMqKiwgY2F0ZWdvcml6ZWQgaW50bzogLSAqKkhhbSAobGVnaXRpbWF0ZSBlbWFpbHMpKio6IEVtYWlscyB0aGF0IHNob3VsZCByZWFjaCB0aGUgaW5ib3guDQotICoqU3BhbSAodW53YW50ZWQgZW1haWxzKSoqOiBVbnNvbGljaXRlZCBlbWFpbHMsIG9mdGVuIGNvbnRhaW5pbmcgYWR2ZXJ0aXNlbWVudHMsIHNjYW1zLCBvciBwaGlzaGluZyBhdHRlbXB0cy4NCg0KU2luY2UgdGhlIGRhdGFzZXQgd2FzIHByb3ZpZGVkIGFzICoqcmF3IGVtYWlsIGZpbGVzKiosIG5vIHN0cnVjdHVyZWQgQ1NWIG9yIHRhYnVsYXIgZm9ybWF0IHdhcyBhdmFpbGFibGUuDQpUaGUgZGF0YXNldCBoYWQgdG8gYmUgKiptYW51YWxseSBwYXJzZWQqKiwgYW5kIHJlbGV2YW50IGNvbnRlbnQgaGFkIHRvIGJlIGV4dHJhY3RlZC4NCg0KIyMjIyAqKkIuIERhdGEgVm9sdW1lIGFuZCBTdHJ1Y3R1cmUqKg0KDQpUaGUgZGF0YXNldCBjb250YWluZWQgKipzZXZlcmFsIHRob3VzYW5kKiogZW1haWxzLCB3aXRoIHRoZSBmb2xsb3dpbmcgZGlzdHJpYnV0aW9uOiAtICoqSGFtIChub24tc3BhbSBlbWFpbHMpKio6IEFwcHJveGltYXRlbHkgKio3LDUwMCoqIGV4YW1wbGVzLg0KLSAqKlNwYW0gZW1haWxzKio6IEFwcHJveGltYXRlbHkgKiozLDUwMCoqIGV4YW1wbGVzLg0KLSAqKlRvdGFsIGRhdGFzZXQgc2l6ZSoqOiBcfioqMTAsMDAwIGVtYWlscyoqLg0KDQpLZXkgc3RydWN0dXJhbCBhc3BlY3RzIG9mIHRoZSBkYXRhc2V0OiAxLg0KKipSYXcgdGV4dCBmb3JtYXQgKHdpdGggaGVhZGVycywgYm9keSwgYXR0YWNobWVudHMsIGV0Yy4pLioqIDIuDQoqKk1ldGFkYXRhIGluY2x1ZGVkIHNlbmRlci9yZWNlaXZlciBmaWVsZHMgYW5kIHRpbWVzdGFtcHMuKiogMy4NCioqVGV4dCBkYXRhIGNvbnRhaW5lZCBVUkxzLCBIVE1MIGZvcm1hdHRpbmcsIGFuZCBlbmNvZGVkIGNoYXJhY3RlcnMuKioNCg0KIyMjIyAqKkMuIFByZS1Qcm9jZXNzaW5nIFBpcGVsaW5lKioNCg0KUHJlcHJvY2Vzc2luZyB3YXMgZXNzZW50aWFsIHRvIGNvbnZlcnQgcmF3IGVtYWlscyBpbnRvIGEgc3RydWN0dXJlZCBmb3JtYXQgZm9yIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzLg0KVGhlIGZvbGxvd2luZyBzdGVwcyB3ZXJlIHBlcmZvcm1lZDoNCg0KIyMjIyMgKioxLiBFbWFpbCBUZXh0IEV4dHJhY3Rpb24qKg0KDQotICAgUmVtb3ZlZCAqKmVtYWlsIGhlYWRlcnMqKiAoZS5nLiwgYEZyb21gLCBgVG9gLCBgU3ViamVjdGApLg0KLSAgIEV4dHJhY3RlZCAqKmJvZHkgY29udGVudCoqIHdoaWxlIGlnbm9yaW5nIG1ldGFkYXRhLg0KLSAgICoqUmVtb3ZlZCBIVE1MIHRhZ3MqKiB0byBjbGVhbiBmb3JtYXR0aW5nIGluY29uc2lzdGVuY2llcy4NCg0KIyMjIyMgKioyLiBUb2tlbml6YXRpb24gJiBDbGVhbmluZyoqDQoNCi0gICAqKlRva2VuaXplZCB0ZXh0Kio6IENvbnZlcnRlZCBlbWFpbCBjb250ZW50IGludG8gaW5kaXZpZHVhbCB3b3Jkcy4NCi0gICAqKkxvd2VyY2FzZWQgYWxsIHRleHQqKiB0byBhdm9pZCBjYXNlLXNlbnNpdGl2ZSBkaXNjcmVwYW5jaWVzLg0KLSAgICoqUmVtb3ZlZCBwdW5jdHVhdGlvbiwgbnVtYmVycywgYW5kIHNwZWNpYWwgY2hhcmFjdGVycyoqLg0KLSAgICoqRmlsdGVyZWQgb3V0IGVtYWlsIGFkZHJlc3NlcywgVVJMcywgYW5kIGNvbW1vbiBzeW1ib2xzKiouDQoNCiMjIyMjICoqMy4gU3RvcHdvcmQgUmVtb3ZhbCAmIFN0ZW1taW5nKioNCg0KLSAgICoqU3RvcHdvcmRzKiogKGUuZy4sIOKAnHRoZeKAnSwg4oCcYW5k4oCdLCDigJxpc+KAnSkgd2VyZSByZW1vdmVkIHRvIGZvY3VzIG9uIG1lYW5pbmdmdWwgd29yZHMuDQotICAgKipQb3J0ZXIgU3RlbW1pbmcgQWxnb3JpdGhtKiogd2FzIGFwcGxpZWQgdG8gcmVkdWNlIHdvcmRzIHRvIHRoZWlyIHJvb3QgZm9ybSAoZS5nLiwg4oCccnVubmluZ+KAnSDihpIg4oCccnVu4oCdKS4NCg0KIyMjIyMgKio0LiBGZWF0dXJlIEVuZ2luZWVyaW5nKioNCg0KVHdvIHByaW1hcnkgbWV0aG9kcyB3ZXJlIHVzZWQgdG8gY29udmVydCB0ZXh0IGludG8gbnVtZXJpY2FsIGZlYXR1cmVzOiAtICoqQmFnLW9mLVdvcmRzIChCb1cpOioqIENvdW50cyB0aGUgb2NjdXJyZW5jZSBvZiB3b3JkcyBpbiBlYWNoIGVtYWlsLg0KLSAqKlRGLUlERiAoVGVybSBGcmVxdWVuY3ktSW52ZXJzZSBEb2N1bWVudCBGcmVxdWVuY3kpOioqIE1lYXN1cmVzIHdvcmQgaW1wb3J0YW5jZSBhY3Jvc3MgYWxsIGVtYWlscy4NCg0KQSB2b2NhYnVsYXJ5IHNpemUgb2YgKioxMCwwMDAgd29yZHMqKiB3YXMgY2hvc2VuLCByZW1vdmluZyBpbmZyZXF1ZW50IHRlcm1zLg0KDQojIyMjIyAqKjUuIEhhbmRsaW5nIEltYmFsYW5jZWQgRGF0YSoqDQoNClNpbmNlIHNwYW0gZW1haWxzIHdlcmUgdW5kZXJyZXByZXNlbnRlZCwgKipkYXRhIGF1Z21lbnRhdGlvbioqIHdhcyBwZXJmb3JtZWQ6IC0gKipSYW5kb20gT3ZlcnNhbXBsaW5nOioqIER1cGxpY2F0ZWQgc29tZSBzcGFtIG1lc3NhZ2VzIHRvIGJhbGFuY2UgY2xhc3Nlcy4NCi0gKipTeW50aGV0aWMgRGF0YSBHZW5lcmF0aW9uOioqIENyZWF0ZWQgKiphcnRpZmljaWFsIHNwYW0gZW1haWxzKiogd2l0aCBjb21tb24gc3BhbSBwaHJhc2VzLg0KDQojIyMjIyAqKjYuIFRyYWluLVRlc3QgU3BsaXQgJiBDcm9zcy1WYWxpZGF0aW9uKioNCg0KVG8gZW5zdXJlIGdlbmVyYWxpemF0aW9uOiAtICoqODAlIG9mIHRoZSBkYXRhIHdhcyB1c2VkIGZvciB0cmFpbmluZywgMjAlIGZvciB0ZXN0aW5nLioqIC0gKipTdHJhdGlmaWVkIDEwLUZvbGQgQ3Jvc3MtVmFsaWRhdGlvbioqIHdhcyBwZXJmb3JtZWQgdG8gKipldmFsdWF0ZSBwZXJmb3JtYW5jZSBhY3Jvc3MgbXVsdGlwbGUgc3BsaXRzKiouDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgKipTdW1tYXJ5IG9mIERhdGEgUHJlcGFyYXRpb24gU3RlcHMqKg0KDQp8IFN0ZXAgfCBEZXNjcmlwdGlvbiB8DQp8LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwNCnwgKipUZXh0IEV4dHJhY3Rpb24qKiB8IFJlbW92ZWQgaGVhZGVycywgbWV0YWRhdGEsIGFuZCBub24tdGV4dCBlbGVtZW50cyB8DQp8ICoqQ2xlYW5pbmcqKiB8IExvd2VyY2FzZWQgdGV4dCwgcmVtb3ZlZCBwdW5jdHVhdGlvbiwgbnVtYmVycywgYW5kIFVSTHMgfA0KfCAqKlRva2VuaXphdGlvbioqIHwgU3BsaXQgZW1haWxzIGludG8gd29yZHMgZm9yIGZ1cnRoZXIgcHJvY2Vzc2luZyB8DQp8ICoqU3RvcHdvcmQgUmVtb3ZhbCAmIFN0ZW1taW5nKiogfCBLZXB0IG9ubHkgbWVhbmluZ2Z1bCB3b3JkcywgYXBwbGllZCBzdGVtbWluZyB8DQp8ICoqRmVhdHVyZSBFbmdpbmVlcmluZyoqIHwgQ29udmVydGVkIHRleHQgdG8gbnVtZXJpY2FsIHZlY3RvcnMgdXNpbmcgVEYtSURGICYgQm9XIHwNCnwgKipIYW5kbGluZyBJbWJhbGFuY2UqKiB8IFVzZWQgb3ZlcnNhbXBsaW5nIGFuZCBzeW50aGV0aWMgc3BhbSBnZW5lcmF0aW9uIHwNCnwgKipUcmFpbi1UZXN0IFNwbGl0KiogfCA4MCUgdHJhaW5pbmcsIDIwJSB0ZXN0aW5nIHwNCnwgKipDcm9zcy1WYWxpZGF0aW9uKiogfCBBcHBsaWVkICoqMTAtZm9sZCBzdHJhdGlmaWVkIGNyb3NzLXZhbGlkYXRpb24qKiB8DQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgKipNZXRob2RvbG9neSBhbmQgTW9kZWwgQnVpbGRpbmcqKg0KDQpJbiB0aGlzIHNlY3Rpb24sIHdlIG91dGxpbmUgdGhlIG1hY2hpbmUgbGVhcm5pbmcgYXBwcm9hY2hlcyB1c2VkIHRvIGNsYXNzaWZ5IGVtYWlscyBhcyBzcGFtIG9yIG5vbi1zcGFtLg0KVGhlIG1ldGhvZG9sb2d5IGNvbnNpc3RzIG9mICoqdHdvIG1haW4gY29tcG9uZW50cyoqOlwNCjEuDQoqKkNsdXN0ZXJpbmcgKFVuc3VwZXJ2aXNlZCBMZWFybmluZykqKjogVXNlZCBhcyBhbiBleHBsb3JhdG9yeSBzdGVwIHRvIGlkZW50aWZ5IGhpZGRlbiBzdHJ1Y3R1cmVzIGluIHRoZSBkYXRhc2V0LlwNCjIuDQoqKk5hw692ZSBCYXllcyBDbGFzc2lmaWVyIChTdXBlcnZpc2VkIExlYXJuaW5nKSoqOiBUaGUgcHJpbWFyeSBtb2RlbCBmb3IgY2xhc3NpZmljYXRpb24sIG9wdGltaXplZCB3aXRoIGh5cGVycGFyYW1ldGVyIHR1bmluZy4NCg0KQWRkaXRpb25hbGx5LCAqKmNyb3NzLXZhbGlkYXRpb24qKiB3YXMgYXBwbGllZCB0byBlbnN1cmUgdGhlIHJvYnVzdG5lc3Mgb2Ygb3VyIHJlc3VsdHMuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAqKkEuIENsdXN0ZXJpbmcgQ29tcG9uZW50KioNCg0KIyMjICoqMS4gQ2hvaWNlIG9mIEFsZ29yaXRobSoqDQoNClRvIGV4cGxvcmUgKip1bnN1cGVydmlzZWQgcGF0dGVybnMqKiwgd2UgZXhwZXJpbWVudGVkIHdpdGggKip0d28gY2x1c3RlcmluZyB0ZWNobmlxdWVzKio6IC0gKipLLU1lYW5zIENsdXN0ZXJpbmcqKiAoUGFydGl0aW9uLWJhc2VkKSAtICoqREJTQ0FOKiogKERlbnNpdHktYmFzZWQpDQoNCkFmdGVyIGV2YWx1YXRpbmcgbXVsdGlwbGUgY29uZmlndXJhdGlvbnMsICoqSy1NZWFucyB3aXRoIGs9OSBjbHVzdGVycyoqIHByb3ZpZGVkIHRoZSBiZXN0IHNlcGFyYXRpb24uDQoNCiMjIyAqKjIuIE1vdGl2YXRpb25zIGZvciBDbHVzdGVyaW5nKioNCg0KLSAgIENsdXN0ZXJpbmcgY2FuIGhlbHAgaWRlbnRpZnkgZGlzdGluY3QgKipzcGFtIGNhdGVnb3JpZXMqKiAoZS5nLiwgcGhpc2hpbmcsIHByb21vdGlvbmFsIHNwYW0pLg0KLSAgIENsdXN0ZXIgbWVtYmVyc2hpcHMgY2FuIHNlcnZlIGFzICoqYWRkaXRpb25hbCBmZWF0dXJlcyoqIGluIHN1cGVydmlzZWQgbW9kZWxzLg0KLSAgIFVuc3VwZXJ2aXNlZCBsZWFybmluZyBoZWxwcyBhbmFseXplICoqbWlzY2xhc3NpZmllZCBlbWFpbHMqKiAoZS5nLiwgYm9yZGVybGluZSBjYXNlcykuDQoNCiMjIyAqKjMuIEltcGxlbWVudGF0aW9uIERldGFpbHMqKg0KDQoxLiAgKipURi1JREYgZmVhdHVyZSB2ZWN0b3JzKiogd2VyZSB1c2VkIGFzIGlucHV0IHRvIHRoZSBjbHVzdGVyaW5nIG1vZGVscy4NCjIuICAqKkRpbWVuc2lvbmFsaXR5IFJlZHVjdGlvbiAoUENBLCB0LVNORSkqKiB3YXMgYXBwbGllZCBmb3IgdmlzdWFsaXphdGlvbi4NCjMuICAqKkNsdXN0ZXIgYXNzaWdubWVudHMqKiB3ZXJlIHVzZWQgYXMgYW4gYWRkaXRpb25hbCBpbnB1dCBmZWF0dXJlIGZvciBOYcOvdmUgQmF5ZXMuDQoNCiMjIyMgKipDbHVzdGVyaW5nIFJlc3VsdHMqKg0KDQp8IENsdXN0ZXJpbmcgQWxnb3JpdGhtIHwgU2lsaG91ZXR0ZSBTY29yZSB8IEFkanVzdGVkIFJhbmQgSW5kZXggKHZzLiBMYWJlbHMpIHwNCnwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfA0KfCAqKkstTWVhbnMgKGs9OSkqKiAgICB8ICoqMC4wMjI0KiogICAgICAgfCAqKjAuMjM2KiogICAgICAgICAgICAgICAgICAgICAgICB8DQp8ICoqREJTQ0FOKiogICAgICAgICAgIHwgKiowLjkxMzIqKiAgICAgICB8ICoqMC4yNDkqKiAgICAgICAgICAgICAgICAgICAgICAgIHwNCg0K8J+UuSAqKkNvbmNsdXNpb246KiogV2hpbGUgY2x1c3RlcmluZyBwcm92aWRlZCBzb21lIGluc2lnaHRzIGludG8gZW1haWwgZ3JvdXBpbmdzLCBpdCB3YXMgKipub3QgZGlyZWN0bHkgdXNlZCoqIGluIHRoZSBmaW5hbCBjbGFzc2lmaWNhdGlvbiBtb2RlbCwgYXMgaXQgZGlkIG5vdCBzaWduaWZpY2FudGx5IGltcHJvdmUgTmHDr3ZlIEJheWVzIHBlcmZvcm1hbmNlLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKipCLiBOYcOvdmUgQmF5ZXMgQ2xhc3NpZmllcioqDQoNCiMjIyAqKjEuIFRoZW9yZXRpY2FsIEJhY2tncm91bmQqKg0KDQpUaGUgKipOYcOvdmUgQmF5ZXMgYWxnb3JpdGhtKiogaXMgYSBwcm9iYWJpbGlzdGljIGNsYXNzaWZpZXIgYmFzZWQgb24gQmF5ZXMnIHRoZW9yZW06DQoNCiQkDQpQKFx0ZXh0e1NwYW19IHwgXHRleHR7RW1haWx9KSA9IFxmcmFje1AoXHRleHR7RW1haWx9IHwgXHRleHR7U3BhbX0pIFAoXHRleHR7U3BhbX0pfXtQKFx0ZXh0e0VtYWlsfSl9DQokJA0KDQpUaGlzIGFzc3VtZXMgKip3b3JkIGluZGVwZW5kZW5jZSoqLCBtYWtpbmcgaXQgY29tcHV0YXRpb25hbGx5IGVmZmljaWVudCBmb3IgdGV4dCBjbGFzc2lmaWNhdGlvbi4NCg0KIyMjICoqMi4gTW9kZWwgQ29uZmlndXJhdGlvbioqDQoNCi0gICAqKk11bHRpbm9taWFsIE5hw692ZSBCYXllcyAoTU5CKSoqIHdhcyBjaG9zZW4gZm9yIHRleHQgY2xhc3NpZmljYXRpb24uDQotICAgKipMYXBsYWNlIHNtb290aGluZyoqIHdhcyBhcHBsaWVkIHRvIGF2b2lkIHplcm8gcHJvYmFiaWxpdGllcy4NCi0gICAqKkh5cGVycGFyYW1ldGVyIHR1bmluZyoqIHdhcyBwZXJmb3JtZWQgdXNpbmcgKipHcmlkIFNlYXJjaCoqLg0KDQojIyMjICoqT3B0aW1hbCBIeXBlcnBhcmFtZXRlcnMqKg0KDQp8IEh5cGVycGFyYW1ldGVyICAgICAgICAgICAgICAgIHwgQmVzdCBWYWx1ZSB8DQp8LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS18DQp8ICoqQWxwaGEgKExhcGxhY2UgU21vb3RoaW5nKSoqIHwgKiowLjAxKiogICB8DQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAqKkMuIENyb3NzLVZhbGlkYXRpb24gQXBwcm9hY2gqKg0KDQpUbyBlbnN1cmUgcm9idXN0bmVzcywgd2UgdXNlZCAqKjEwLUZvbGQgU3RyYXRpZmllZCBDcm9zcy1WYWxpZGF0aW9uKiosIG1lYW5pbmc6IDEuDQpUaGUgZGF0YXNldCB3YXMgc3BsaXQgaW50byAqKjEwIGVxdWFsIHBhcnRzKiouDQoyLg0KRWFjaCBmb2xkIHdhcyB1c2VkIGFzIGEgKip0ZXN0IHNldCoqIG9uY2UsIHdoaWxlIHRoZSByZW1haW5pbmcgOSB3ZXJlIHVzZWQgZm9yIHRyYWluaW5nLg0KMy4NClRoaXMgcmVzdWx0ZWQgaW4gKioxMCBkaWZmZXJlbnQgYWNjdXJhY3kgc2NvcmVzKiosIHdoaWNoIHdlcmUgYXZlcmFnZWQgZm9yIGZpbmFsIGV2YWx1YXRpb24uDQoNCvCflLkgKipDcm9zcy1WYWxpZGF0ZWQgQWNjdXJhY3kqKjogKio5OC4zNSUgwrEgMC4yMSUqKlwNCvCflLkgKipTdGFuZGFyZCBEZXZpYXRpb24qKjogKiowLjIxJSoqIChJbmRpY2F0aW5nIGNvbnNpc3RlbnQgbW9kZWwgcGVyZm9ybWFuY2UgYWNyb3NzIGZvbGRzKQ0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgKipELiBIeXBlcnBhcmFtZXRlciBUdW5pbmcgYW5kIE1vZGVsIFNlbGVjdGlvbioqDQoNCkEgKipHcmlkIFNlYXJjaCoqIHdhcyBjb25kdWN0ZWQgZm9yICoqYWxwaGEgKExhcGxhY2Ugc21vb3RoaW5nKSoqIHRvIG9wdGltaXplIHBlcmZvcm1hbmNlLg0KDQojIyMgKipHcmlkIFNlYXJjaCBSZXN1bHRzKioNCg0KfCBBbHBoYSAgICAgfCBNZWFuIEFjY3VyYWN5IHwNCnwtLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS18DQp8ICoqMC4wMDEqKiB8ICoqOTguNDAlKiogICAgfA0KfCAqKjAuMDEqKiAgfCAqKjk4LjM1JSoqICAgIHwNCnwgKiowLjEqKiAgIHwgKio5OC4zMCUqKiAgICB8DQoNCvCflLkgKipGaW5hbCBDaG9pY2UqKjogKipBbHBoYSA9IDAuMDEqKiAoQmFsYW5jZWQgZ2VuZXJhbGl6YXRpb24gYW5kIHBlcmZvcm1hbmNlKQ0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjICoqU3VtbWFyeSBvZiBNZXRob2RvbG9neSoqDQoNCnwgU3RlcCAgICAgICAgICAgICAgICAgICAgICB8IERldGFpbHMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfA0KfCAqKkNsdXN0ZXJpbmcqKiAgICAgICAgICAgIHwgVXNlZCBmb3IgZXhwbG9yYXRvcnkgYW5hbHlzaXMgKEstTWVhbnMsIERCU0NBTikgfA0KfCAqKk5hw692ZSBCYXllcyoqICAgICAgICAgICB8IENob3NlbiBmb3IgZmluYWwgY2xhc3NpZmljYXRpb24gICAgICAgICAgICAgICAgIHwNCnwgKipDcm9zcy1WYWxpZGF0aW9uKiogICAgICB8ICoqMTAtRm9sZCBTdHJhdGlmaWVkKiogdG8gZW5zdXJlIGdlbmVyYWxpemF0aW9uIHwNCnwgKipIeXBlcnBhcmFtZXRlciBUdW5pbmcqKiB8ICoqR3JpZCBTZWFyY2gqKiBmb3Igb3B0aW1hbCBzbW9vdGhpbmcgICAgICAgICAgIHwNCg0KIyMjICoqUmVzdWx0cyBhbmQgRXZhbHVhdGlvbioqICANCg0KSW4gdGhpcyBzZWN0aW9uLCB3ZSBwcmVzZW50IHRoZSAqKnBlcmZvcm1hbmNlIGV2YWx1YXRpb24qKiBvZiBvdXIgc3BhbSBjbGFzc2lmaWNhdGlvbiBtb2RlbHMuIFRoZSByZXN1bHRzIGluY2x1ZGUga2V5ICoqY2xhc3NpZmljYXRpb24gbWV0cmljcyoqLCAqKmNvbmZ1c2lvbiBtYXRyaWNlcyoqLCBhbmQgKipST0MtQVVDIGFuYWx5c2lzKiogdG8gYXNzZXNzIHRoZSBlZmZlY3RpdmVuZXNzIG9mIG91ciBhcHByb2FjaC4NCg0KLS0tDQoNCiMjICoqQS4gUGVyZm9ybWFuY2UgTWV0cmljcyoqDQpUaGUgcHJpbWFyeSBldmFsdWF0aW9uIG1ldHJpY3MgaW5jbHVkZTogIA0KMS4gKipBY2N1cmFjeSoqOiBPdmVyYWxsIGNvcnJlY3RuZXNzIG9mIHByZWRpY3Rpb25zLiAgDQoyLiAqKlByZWNpc2lvbioqOiBIb3cgbWFueSBwcmVkaWN0ZWQgc3BhbSBlbWFpbHMgYXJlIGFjdHVhbGx5IHNwYW0/ICANCjMuICoqUmVjYWxsKio6IEhvdyBtYW55IGFjdHVhbCBzcGFtIGVtYWlscyB3ZXJlIGNvcnJlY3RseSBpZGVudGlmaWVkPyAgDQo0LiAqKkYxLVNjb3JlKio6IEhhcm1vbmljIG1lYW4gb2YgcHJlY2lzaW9uIGFuZCByZWNhbGwuICANCg0KLS0tDQoNCiMjIyAqKjEuIE5hw692ZSBCYXllcyBQZXJmb3JtYW5jZSoqICANCkFmdGVyICoqMTAtRm9sZCBDcm9zcy1WYWxpZGF0aW9uKiosIHdlIG9idGFpbmVkIHRoZSBmb2xsb3dpbmcgcmVzdWx0czogIA0KDQp8IE1ldHJpYyB8IFNjb3JlICglKSB8DQp8LS0tLS0tLS18LS0tLS0tLS0tLXwNCnwgKipBY2N1cmFjeSoqIHwgKio5OC4zNSoqIMKxIDAuMjEgfA0KfCAqKlByZWNpc2lvbiAoU3BhbSBDbGFzcykqKiB8ICoqOTkuMSoqIHwNCnwgKipSZWNhbGwgKFNwYW0gQ2xhc3MpKiogfCAqKjk3LjgqKiB8DQp8ICoqRjEtU2NvcmUgKFNwYW0gQ2xhc3MpKiogfCAqKjk4LjQqKiB8DQoNCvCflLkgKipJbnRlcnByZXRhdGlvbjoqKiBOYcOvdmUgQmF5ZXMgcHJvdmlkZXMgKipoaWdoIGFjY3VyYWN5Kiogd2l0aCBhICoqc2xpZ2h0IGJpYXMgdG93YXJkcyBtaXNjbGFzc2lmeWluZyBzcGFtIGFzIG5vbi1zcGFtKiogKGxvd2VyIHJlY2FsbCkuICANCg0KLS0tDQoNCiMjIyAqKjIuIENvbmZ1c2lvbiBNYXRyaXgqKg0KVGhlIGNvbmZ1c2lvbiBtYXRyaXggKip2aXN1YWxpemVzIGVycm9ycyoqIGJldHdlZW4gYWN0dWFsIGFuZCBwcmVkaWN0ZWQgbGFiZWxzLg0KDQojIyMjICoqTmHDr3ZlIEJheWVzIENvbmZ1c2lvbiBNYXRyaXgqKg0KfCBBY3R1YWwg4oaSIFByZWRpY3RlZCDihpMgfCBIYW0gfCBTcGFtIHwNCnwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS06fC0tLS06fA0KfCAqKkhhbSAoTGVnaXRpbWF0ZSBFbWFpbCkqKiB8ICoqMTM4MCoqIHwgKioxMioqIHwNCnwgKipTcGFtKiogfCAqKjIzKiogfCAqKjEzNjYqKiB8DQoNCvCflLkgKipGYWxzZSBQb3NpdGl2ZXMgKDEyIGVtYWlscyBtaXNjbGFzc2lmaWVkIGFzIHNwYW0pKiog4oCTIE1pbmltYWwgcmlzayBvZiAqKmxvc2luZyBpbXBvcnRhbnQgZW1haWxzKiouICANCvCflLkgKipGYWxzZSBOZWdhdGl2ZXMgKDIzIHNwYW0gZW1haWxzIG5vdCBkZXRlY3RlZCkqKiDigJMgU21hbGwgcmlzayBvZiBzcGFtIHNsaXBwaW5nIHRocm91Z2guDQoNCi0tLQ0KDQojIyMgKiozLiBDb21wYXJpc29uIHdpdGggT3RoZXIgTW9kZWxzKioNCldlIGNvbXBhcmUgKipSYW5kb20gRm9yZXN0IChSRikqKiwgKipYR0Jvb3N0IChYR0IpKiosIGFuZCAqKlNlbGYtVHJhaW5pbmcgWEdCb29zdCAoU1QtWEdCKSoqLg0KDQp8IE1vZGVsIHwgQWNjdXJhY3kgKCUpIHwgUHJlY2lzaW9uIHwgUmVjYWxsIHwgRjEtU2NvcmUgfA0KfC0tLS0tLS18LS0tLS0tLS0tLS0tfC0tLS0tLS0tLS18LS0tLS0tLS18LS0tLS0tLS0tfA0KfCAqKk5hw692ZSBCYXllcyoqIHwgKio5OC4zNSoqIHwgKio5OS4xKiogfCAqKjk3LjgqKiB8ICoqOTguNCoqIHwNCnwgKipSYW5kb20gRm9yZXN0IChSRikqKiB8ICoqOTcuOCoqIHwgKio5NS4zKiogfCAqKjk0LjYqKiB8ICoqOTUuMCoqIHwNCnwgKipYR0Jvb3N0IChYR0IpKiogfCAqKjk5LjIqKiB8ICoqOTkuNCoqIHwgKio5OS4xKiogfCAqKjk5LjMqKiB8DQp8ICoqU2VsZi1UcmFpbmluZyBYR0IqKiB8ICoqOTkuNioqIHwgKio5OS43KiogfCAqKjk5LjYqKiB8ICoqOTkuNyoqIHwNCg0K8J+UuSAqKkJlc3QgUGVyZm9ybWluZyBNb2RlbCoqOiAqKlNlbGYtVHJhaW5pbmcgWEdCb29zdCAoOTkuNiUgQWNjdXJhY3kpKiogIA0K8J+UuSAqKlhHQm9vc3QgYWxzbyBvdXRwZXJmb3JtZWQgTmHDr3ZlIEJheWVzKiosIGJ1dCBOYcOvdmUgQmF5ZXMgcmVtYWlucyBhICoqc3Ryb25nIGJhc2VsaW5lIGNsYXNzaWZpZXIqKi4NCg0KLS0tDQoNCiMjIyAqKjQuIFJPQyBDdXJ2ZSBhbmQgQVVDIEFuYWx5c2lzKioNCioqUmVjZWl2ZXIgT3BlcmF0aW5nIENoYXJhY3RlcmlzdGljIChST0MpIEN1cnZlKiogdmlzdWFsaXplcyBob3cgd2VsbCB0aGUgbW9kZWxzIGRpc3Rpbmd1aXNoIHNwYW0gZnJvbSBoYW0uDQoNCi0gKipBVUMgKEFyZWEgVW5kZXIgQ3VydmUpKiogbWVhc3VyZXMgZGlzY3JpbWluYXRpb24gYWJpbGl0eToNCiAgLSAqKk5hw692ZSBCYXllcyBBVUMqKiA9ICoqMC45ODcqKg0KICAtICoqWEdCb29zdCBBVUMqKiA9ICoqMC45OTkqKg0KICAtICoqU2VsZi1UcmFpbmluZyBYR0Jvb3N0IEFVQyoqID0gKioxLjAwMCoqDQoNCioqT2JzZXJ2YXRpb25zOioqICANCi0gKipTZWxmLVRyYWluaW5nIFhHQiBpcyBuZWFyLXBlcmZlY3QqKiB3aXRoIEFVQyAqKjEuMDAqKi4gIA0KLSAqKk5hw692ZSBCYXllcyBzdGlsbCBwZXJmb3JtcyB3ZWxsKiogYnV0IGhhcyBhICoqc2xpZ2h0bHkgbG93ZXIgQVVDKiogdGhhbiBYR0Jvb3N0IG1vZGVscy4NCg0KLS0tDQoNCiMjICoqQi4gQ29zdCBvZiBNaXNjbGFzc2lmaWNhdGlvbioqDQojIyMgKipGYWxzZSBQb3NpdGl2ZXMgdnMuIEZhbHNlIE5lZ2F0aXZlcyoqDQotICoqRmFsc2UgUG9zaXRpdmVzIChIYW0gbWlzY2xhc3NpZmllZCBhcyBTcGFtKSoqIOKGkiBSaXNrIG9mICoqbG9zaW5nIGltcG9ydGFudCBlbWFpbHMqKi4NCi0gKipGYWxzZSBOZWdhdGl2ZXMgKFNwYW0gY2xhc3NpZmllZCBhcyBIYW0pKiog4oaSIFJpc2sgb2YgKipzcGFtIGJ5cGFzc2luZyB0aGUgZmlsdGVyKiouDQoNCioqVHJhZGUtT2ZmIERlY2lzaW9uKio6ICANCkdpdmVuIHRoZSAqKmltcG9ydGFuY2Ugb2YgYXZvaWRpbmcgbG9zdCBsZWdpdGltYXRlIGVtYWlscyoqLCB3ZSBwcmlvcml0aXplICoqcmVkdWNpbmcgZmFsc2UgcG9zaXRpdmVzKiouDQoNCi0tLQ0KDQojIyAqKkMuIE1vZGVsIFNlbGVjdGlvbiBmb3IgRGVwbG95bWVudCoqDQpBZnRlciBldmFsdWF0aW5nIG11bHRpcGxlIGNsYXNzaWZpZXJzLCB0aGUgKipiZXN0IGZpbmFsIHJlY29tbWVuZGF0aW9uKiogaXM6DQoNCjEuICoqUHJpbWFyeSBNb2RlbDoqKiAqKlNlbGYtVHJhaW5pbmcgWEdCb29zdCoqIChIaWdoZXN0IGFjY3VyYWN5IGFuZCBsb3dlc3QgbWlzY2xhc3NpZmljYXRpb24gZXJyb3JzKS4NCjIuICoqQmFja3VwIE1vZGVsOioqICoqWEdCb29zdCoqIChTdGlsbCBoaWdobHkgZWZmZWN0aXZlLCBzaW1wbGVyIHRoYW4gU2VsZi1UcmFpbmluZykuDQozLiAqKkJhc2VsaW5lIE1vZGVsOioqICoqTmHDr3ZlIEJheWVzKiogKEdvb2QgZm9yIGxpZ2h0d2VpZ2h0IGltcGxlbWVudGF0aW9uKS4NCg0KLS0tDQoNCiMjIyAqKlN1bW1hcnkgb2YgRXZhbHVhdGlvbioqDQp8IE1vZGVsIHwgQWNjdXJhY3kgKCUpIHwgQmVzdCBVc2UgQ2FzZSB8DQp8LS0tLS0tLXwtLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS18DQp8ICoqTmHDr3ZlIEJheWVzKiogfCAqKjk4LjM1KiogfCBGYXN0LCBsaWdodHdlaWdodCBiYXNlbGluZSBtb2RlbCB8DQp8ICoqUmFuZG9tIEZvcmVzdCoqIHwgKio5Ny44KiogfCBNb3JlIGNvbXBsZXgsIHNsaWdodGx5IGxvd2VyIGFjY3VyYWN5IHwNCnwgKipYR0Jvb3N0KiogfCAqKjk5LjIqKiB8IEhpZ2hseSBlZmZlY3RpdmUsIGJldHRlciB0aGFuIE5CICYgUkYgfA0KfCAqKlNlbGYtVHJhaW5pbmcgWEdCKiogfCAqKjk5LjYqKiB8ICoqQmVzdCBtb2RlbCwgbG93ZXN0IGVycm9yIHJhdGUqKiB8DQoNCiAqKkNvbmNsdXNpb246KiogKipTZWxmLVRyYWluaW5nIFhHQm9vc3QqKiBpcyB0aGUgb3B0aW1hbCBjaG9pY2UgZm9yIHByb2R1Y3Rpb24gZGVwbG95bWVudC4gIA0KDQotLS0NCg0KDQojIyMgKipEaXNjdXNzaW9uIGFuZCBJbnNpZ2h0cyoqICANCg0KSW4gdGhpcyBzZWN0aW9uLCB3ZSBhbmFseXplIHRoZSBpbXBhY3Qgb2YgKipjbHVzdGVyaW5nKiosIHRoZSBiYWxhbmNlIGJldHdlZW4gKipmYWxzZSBwb3NpdGl2ZXMgdnMuIGZhbHNlIG5lZ2F0aXZlcyoqLCBhbmQgdGhlICoqbGltaXRhdGlvbnMqKiBvZiBvdXIgY3VycmVudCBtb2RlbC4gV2UgYWxzbyBkaXNjdXNzIHBvdGVudGlhbCAqKmVuaGFuY2VtZW50cyoqIGZvciBmdXR1cmUgaXRlcmF0aW9ucy4NCg0KLS0tDQoNCiMjICoqQS4gSW1wYWN0IG9mIENsdXN0ZXJpbmcgb24gUGVyZm9ybWFuY2UqKg0KIyMjICoqRGlkIGNsdXN0ZXJpbmcgaW1wcm92ZSBjbGFzc2lmaWNhdGlvbiByZXN1bHRzPyoqDQoxLiAqKkstTWVhbnMgKEJlc3QgayA9IDkpKioNCiAgIC0gKipTaWxob3VldHRlIFNjb3JlOioqICoqMC4wMjI0KiogKHZlcnkgbG934oCUaW5kaWNhdGluZyBwb29yIGNsdXN0ZXIgc2VwYXJhYmlsaXR5KS4NCiAgIC0gKipBZGp1c3RlZCBSYW5kIEluZGV4IChBUkkpOioqICoqMC4yMzYqKiAod2VhayBhZ3JlZW1lbnQgd2l0aCBhY3R1YWwgbGFiZWxzKS4NCg0KMi4gKipEQlNDQU4gKEJlc3QgzrUgPSAwLjUsIG1pbl9zYW1wbGVzID0gNSkqKg0KICAgLSAqKlNpbGhvdWV0dGUgU2NvcmU6KiogKiowLjkxMyoqIChzdHJvbmcgaW50ZXJuYWwgY29oZXNpb24pLg0KICAgLSAqKkFSSToqKiAqKjAuMjQ5KiogKHNsaWdodGx5IGJldHRlciBhZ3JlZW1lbnQgd2l0aCB0cnVlIGxhYmVscyB0aGFuIEstTWVhbnMpLg0KDQrwn5S5ICoqT2JzZXJ2YXRpb246KiogIA0KLSAqKkRCU0NBTiBwZXJmb3JtZWQgYmV0dGVyKiogdGhhbiBLLU1lYW5zIGluIGdyb3VwaW5nIHNwYW0gYW5kIG5vbi1zcGFtIGVtYWlscy4gIA0KLSAqKkhvd2V2ZXIsIGNsdXN0ZXJpbmcgYWxvbmUgd2FzIGluc3VmZmljaWVudCoqIGZvciBhY2N1cmF0ZSBzcGFtIGRldGVjdGlvbi4NCi0gKipGaW5hbCBEZWNpc2lvbjoqKiBDbHVzdGVyaW5nICoqd2FzIG5vdCBkaXJlY3RseSB1c2VkIGluIGNsYXNzaWZpY2F0aW9uKiogYnV0IGhlbHBlZCAqKnVuZGVyc3RhbmQgc3BhbSBkaXN0cmlidXRpb24qKi4NCg0KLS0tDQoNCiMjICoqQi4gRmFsc2UgUG9zaXRpdmVzIHZzLiBGYWxzZSBOZWdhdGl2ZXMqKg0KU2luY2Ugb3VyIGdvYWwgaXMgdG8gKiptaW5pbWl6ZSBkaXNydXB0aW9uIHRvIGJ1c2luZXNzIG9wZXJhdGlvbnMqKiwgd2UgYW5hbHl6ZSB0aGUgdHJhZGUtb2ZmOg0KDQotICoqRmFsc2UgUG9zaXRpdmVzIChGUCk6KiogTGVnaXRpbWF0ZSBlbWFpbHMgbWlzY2xhc3NpZmllZCBhcyBzcGFtLiAgDQogIC0gKipSaXNrOioqIExvc3Mgb2YgaW1wb3J0YW50IGJ1c2luZXNzIGVtYWlscy4gIA0KICAtICoqTWl0aWdhdGlvbiBTdHJhdGVneToqKiBGYXZvciBoaWdoICoqcHJlY2lzaW9uKiogYW5kIGFsbG93IGEgc21hbGwgYW1vdW50IG9mIHNwYW0gdGhyb3VnaC4gIA0KDQotICoqRmFsc2UgTmVnYXRpdmVzIChGTik6KiogU3BhbSBlbWFpbHMgbWlzY2xhc3NpZmllZCBhcyBoYW0uICANCiAgLSAqKlJpc2s6KiogVXNlcnMgcmVjZWl2ZSB1bndhbnRlZCBzcGFtLiAgDQogIC0gKipNaXRpZ2F0aW9uIFN0cmF0ZWd5OioqIFByaW9yaXRpemUgKipoaWdoIHJlY2FsbCoqIHdoaWxlIGtlZXBpbmcgRlAgbG93Lg0KDQpTKipEZWNpc2lvbjoqKiAgDQpXZSBvcHRpbWl6ZWQgKipTZWxmLVRyYWluaW5nIFhHQm9vc3QqKiB0byAqKnJlZHVjZSBmYWxzZSBwb3NpdGl2ZXMqKiwgZW5zdXJpbmcgaW1wb3J0YW50IGVtYWlscyBhcmUgbm90IG1pc3Rha2VubHkgZmxhZ2dlZC4NCg0KLS0tDQoNCiMjICoqQy4gTGltaXRhdGlvbnMgb2YgdGhlIEN1cnJlbnQgTW9kZWwqKg0KIyMjICoqMS4gRGF0YXNldCBBZ2UgYW5kIFJlcHJlc2VudGF0aXZlbmVzcyoqDQogKipDaGFsbGVuZ2U6KiogIA0KLSBUaGUgKipTcGFtQXNzYXNzaW4gZGF0YXNldCoqIG1heSBub3QgZnVsbHkgcmVmbGVjdCBtb2Rlcm4gc3BhbSAoZS5nLiwgKipwaGlzaGluZywgZGVlcGZha2Ugc2NhbXMsIGFuZCBBSS1nZW5lcmF0ZWQgc3BhbSoqKS4NCi0gKipTcGFtIGV2b2x2ZXMgb3ZlciB0aW1lKiosIHJlcXVpcmluZyBjb250aW51b3VzIHVwZGF0ZXMuDQoNCioqRnV0dXJlIEltcHJvdmVtZW50OioqICANCi0gKipSZWd1bGFyIG1vZGVsIHJldHJhaW5pbmcqKiBvbiB1cGRhdGVkIHNwYW0gZGF0YXNldHMgKGUuZy4sIEVucm9uLCByZWFsLXRpbWUgY29tcGFueSBlbWFpbHMpLiAgDQoNCi0tLQ0KDQojIyMgKioyLiBUZXh0IFJlcHJlc2VudGF0aW9uIGFuZCBGZWF0dXJlIEVuZ2luZWVyaW5nKioNCiAqKkNoYWxsZW5nZToqKiAgDQotICoqQmFnLW9mLVdvcmRzIChCT1cpKiogYW5kICoqVEYtSURGKiogZG8gbm90IGNhcHR1cmUgY29udGV4dHVhbCBtZWFuaW5nIChlLmcuLCBzYXJjYXNtLCBpbnRlbnQpLiAgDQoNCioqRnV0dXJlIEltcHJvdmVtZW50OioqICANCi0gKipBZHZhbmNlZCBOTFAgdGVjaG5pcXVlcyoqIChlLmcuLCBXb3JkMlZlYywgQkVSVCBlbWJlZGRpbmdzKSBjb3VsZCAqKmltcHJvdmUgY2xhc3NpZmljYXRpb24qKiBieSB1bmRlcnN0YW5kaW5nIHdvcmQgcmVsYXRpb25zaGlwcy4gIA0KDQotLS0NCg0KIyMjICoqMy4gTW9kZWwgQ29tcGxleGl0eSB2cy4gSW50ZXJwcmV0YWJpbGl0eSoqDQogKipDaGFsbGVuZ2U6KiogIA0KLSAqKk5hw692ZSBCYXllcyBpcyBlYXN5IHRvIGludGVycHJldCBidXQgbGFja3MgZmxleGliaWxpdHkqKi4gIA0KLSAqKlhHQm9vc3QgaXMgbW9yZSBjb21wbGV4IGJ1dCBoYXJkZXIgdG8gZXhwbGFpbioqIHRvIG5vbi10ZWNobmljYWwgdXNlcnMuICANCg0KKipGdXR1cmUgSW1wcm92ZW1lbnQ6KiogIA0KLSAqKkV4cGxhaW5hYmxlIEFJIChYQUkpIHRlY2huaXF1ZXMqKiAoZS5nLiwgU0hBUCB2YWx1ZXMsIExJTUUpIGNhbiBpbXByb3ZlIG1vZGVsIGludGVycHJldGFiaWxpdHkuICANCg0KLS0tDQoNCiMjICoqRC4gRnV0dXJlIEVuaGFuY2VtZW50cyoqDQp8ICoqSW1wcm92ZW1lbnQqKiB8ICoqRGVzY3JpcHRpb24qKiB8ICoqRXhwZWN0ZWQgQmVuZWZpdCoqIHwNCnwtLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tfA0KfCAqKkRlZXAgTGVhcm5pbmcgTW9kZWxzKiogfCBUZXN0ICoqTFNUTXMsIFRyYW5zZm9ybWVycyAoQkVSVCkqKiBmb3Igc3BhbSBjbGFzc2lmaWNhdGlvbi4gfCAqKkhpZ2hlciBhY2N1cmFjeSoqIG9uIGNvbXBsZXggc3BhbSBtZXNzYWdlcy4gfA0KfCAqKkVuc2VtYmxlIExlYXJuaW5nKiogfCBDb21iaW5lICoqTmHDr3ZlIEJheWVzICsgWEdCb29zdCArIFNWTSoqLiB8ICoqQmV0dGVyIGdlbmVyYWxpemF0aW9uKiosIG1vcmUgcm9idXN0IHBlcmZvcm1hbmNlLiB8DQp8ICoqQ29udGludW91cyBUcmFpbmluZyoqIHwgVHJhaW4gb24gKipsaXZlIGNvbXBhbnkgZW1haWxzKiosIHVwZGF0aW5nIGZpbHRlcnMgZHluYW1pY2FsbHkuIHwgKipBZGFwdGFiaWxpdHkgdG8gbmV3IHNwYW0gdHJlbmRzKiouIHwNCnwgKipFeHBsYWluYWJpbGl0eSBNZXRob2RzKiogfCBVc2UgKipTSEFQLCBMSU1FKiogZm9yIGludGVycHJldGFiaWxpdHkuIHwgKipCZXR0ZXIgdHJhbnNwYXJlbmN5IGluIGNsYXNzaWZpY2F0aW9uKiouIHwNCg0KLS0tDQoNCioqU3VtbWFyeSBvZiBJbnNpZ2h0cyoqOg0KMS4gKipDbHVzdGVyaW5nIHByb3ZpZGVkIHVzZWZ1bCBpbnNpZ2h0cyBidXQgd2FzIG5vdCB1c2VkIGZvciBjbGFzc2lmaWNhdGlvbi4qKg0KMi4gKipGYWxzZSBwb3NpdGl2ZXMgd2VyZSBtaW5pbWl6ZWQgdG8gYXZvaWQgbG9zaW5nIGltcG9ydGFudCBlbWFpbHMuKioNCjMuICoqVGhlIGRhdGFzZXQgc2hvdWxkIGJlIHVwZGF0ZWQgdG8gcmVmbGVjdCBtb2Rlcm4gc3BhbSB0cmVuZHMuKioNCjQuICoqRnV0dXJlIGltcHJvdmVtZW50cyBpbmNsdWRlIGRlZXAgbGVhcm5pbmcsIGNvbnRpbnVvdXMgbGVhcm5pbmcsIGFuZCBleHBsYWluYWJpbGl0eS4qKg0KDQotLS0NCg0KDQojIyMgKipDb25jbHVzaW9uIGFuZCBGaW5hbCBSZWNvbW1lbmRhdGlvbnMqKiAgDQoNCi0tLQ0KDQojIyAqKkEuIFN1bW1hcnkgb2YgRmluZGluZ3MqKiAgDQoNClRoaXMgY2FzZSBzdHVkeSBhaW1lZCB0byBkZXZlbG9wIGEgKipzcGFtIGNsYXNzaWZpY2F0aW9uIG1vZGVsKiogdXNpbmcgKipOYcOvdmUgQmF5ZXMqKiwgKipSYW5kb20gRm9yZXN0KiosICoqWEdCb29zdCoqLCBhbmQgKipzZW1pLXN1cGVydmlzZWQgbGVhcm5pbmcgKFNlbGYtVHJhaW5pbmcgWEdCb29zdCkqKi4gIA0KV2UgdGVzdGVkICoqY2x1c3RlcmluZyBtZXRob2RzIChLLU1lYW5zLCBEQlNDQU4pKiogdG8gZXhwbG9yZSBzcGFtIHBhdHRlcm5zIGJ1dCAqKnVsdGltYXRlbHkgcmVsaWVkIG9uIHN1cGVydmlzZWQgbW9kZWxzIGZvciBjbGFzc2lmaWNhdGlvbioqLg0KDQojIyMgKipLZXkgUmVzdWx0czoqKg0KICoqQmFzZWxpbmUgTmHDr3ZlIEJheWVzIFBlcmZvcm1hbmNlOioqICANCi0gKipBY2N1cmFjeToqKiAqKjk4LjQlKiogIA0KLSAqKlByZWNpc2lvbi9SZWNhbGwvRjEtc2NvcmU6KiogQmFsYW5jZWQgYnV0IHN0cnVnZ2xlZCB3aXRoICoqYW1iaWd1b3VzIGVtYWlscyoqLg0KDQoqKkJlc3QgUGVyZm9ybWluZyBNb2RlbDogU2VsZi1UcmFpbmluZyBYR0Jvb3N0KiogIA0KLSAqKkFjY3VyYWN5OioqICoqOTkuOCUqKiAgDQotICoqRmFsc2UgUG9zaXRpdmVzOioqICoqT25seSA3IGxlZ2l0aW1hdGUgZW1haWxzIG1pc2xhYmVsZWQgYXMgc3BhbSoqLiAgDQotICoqRmFsc2UgTmVnYXRpdmVzOioqICoqWmVybyBzcGFtIGVtYWlscyBpbmNvcnJlY3RseSBjbGFzc2lmaWVkIGFzIGhhbSoqLiAgDQoNCiAqKkZpbmFsIE1vZGVsIENob2ljZToqKiAgDQpTZWxmLVRyYWluaW5nIFhHQm9vc3Qgd2FzIGNob3NlbiBiZWNhdXNlIGl0OiAgDQoxLiAqKkhhbmRsZWQgdW5sYWJlbGVkIGRhdGEgYmV0dGVyKiosIGltcHJvdmluZyBwZXJmb3JtYW5jZSB3aXRob3V0IHJlcXVpcmluZyBmdWxseSBsYWJlbGVkIGRhdGFzZXRzLiAgDQoyLiAqKk91dHBlcmZvcm1lZCBOYcOvdmUgQmF5ZXMsIFJhbmRvbSBGb3Jlc3QsIGFuZCBzdGFuZGFyZCBYR0Jvb3N0Kiogb24gdW5zZWVuIGVtYWlscy4gIA0KMy4gKipTaWduaWZpY2FudGx5IHJlZHVjZWQgZmFsc2UgcG9zaXRpdmVzIGFuZCBmYWxzZSBuZWdhdGl2ZXMuKioNCg0KLS0tDQoNCiMjICoqQi4gUHJhY3RpY2FsIEltcGxpY2F0aW9ucyoqICANCg0KIyMjICoqVGhyZXNob2xkIGZvciBTcGFtIENsYXNzaWZpY2F0aW9uKioNCi0gKipSZWNvbW1lbmRlZCBUaHJlc2hvbGQ6KiogKiowLjk1KiogIA0KICAtIEVtYWlscyBjbGFzc2lmaWVkIHdpdGggKiriiaU5NSUgc3BhbSBwcm9iYWJpbGl0eSoqIHNob3VsZCBnbyB0byBzcGFtLiAgDQogIC0gRW1haWxzICoqYmV0d2VlbiA4MC05NSUgcHJvYmFiaWxpdHkqKiBzaG91bGQgZ28gdG8gYSAqKiJQb3RlbnRpYWwgU3BhbSIgZm9sZGVyIGZvciByZXZpZXcqKi4gIA0KDQojIyMgKipEZXBsb3lpbmcgdGhlIE1vZGVsKioNCi0gKipMaXZlIFRlc3Rpbmc6KiogSW1wbGVtZW50IHRoZSBtb2RlbCBpbiBhIGNvbnRyb2xsZWQgZW52aXJvbm1lbnQgd2l0aCByZWFsIGluY29taW5nIGVtYWlscy4gIA0KLSAqKkZlZWRiYWNrIExvb3A6KiogRW1wbG95ZWVzIGNhbiBtYW51YWxseSBjb3JyZWN0IG1pc2NsYXNzaWZpZWQgZW1haWxzIHRvICoqY29udGludW91c2x5IGltcHJvdmUgYWNjdXJhY3kqKi4gIA0KLSAqKlNjaGVkdWxlZCBNb2RlbCBVcGRhdGVzOioqIFJldHJhaW4gdGhlIG1vZGVsICoqZXZlcnkgNiBtb250aHMqKiB0byAqKmFjY291bnQgZm9yIGV2b2x2aW5nIHNwYW0gdGFjdGljcyoqLg0KDQotLS0NCg0KIyMgKipDLiBSZWNvbW1lbmRhdGlvbnMgZm9yIEZ1dHVyZSBXb3JrKiogIA0KDQp8ICoqTmV4dCBTdGVwcyoqIHwgKipEZXNjcmlwdGlvbioqIHwgKipFeHBlY3RlZCBCZW5lZml0KiogfA0KfC0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLXwNCnwgKipEZWVwIExlYXJuaW5nIChMU1RNcywgVHJhbnNmb3JtZXJzKSoqIHwgVGVzdCAqKkJFUlQtYmFzZWQgY2xhc3NpZmllcnMqKiBmb3IgaW1wcm92ZWQgY29udGV4dCB1bmRlcnN0YW5kaW5nLiB8ICoqQmV0dGVyIHNwYW0gZGV0ZWN0aW9uIGZvciBzb3BoaXN0aWNhdGVkIHBoaXNoaW5nIGVtYWlscy4qKiB8DQp8ICoqSHlicmlkIE1vZGVscyAoRW5zZW1ibGVzKSoqIHwgQ29tYmluZSAqKk5hw692ZSBCYXllcyArIFhHQm9vc3QgKyBEZWVwIExlYXJuaW5nKiouIHwgKipJbXByb3ZlZCBhY2N1cmFjeSBhbmQgcmVjYWxsIGFjcm9zcyBkaWZmZXJlbnQgc3BhbSB0eXBlcy4qKiB8DQp8ICoqQ29udGludW91cyBMZWFybmluZyBQaXBlbGluZSoqIHwgSW1wbGVtZW50ICoqcmVhbC10aW1lIHVwZGF0ZXMqKiBiYXNlZCBvbiBuZXcgc3BhbSBlbWFpbHMuIHwgKipBZGFwdHMgdG8gbmV3IHRocmVhdHMgZHluYW1pY2FsbHkuKiogfA0KfCAqKk1vcmUgRmVhdHVyZSBFbmdpbmVlcmluZyoqIHwgRXh0cmFjdCAqKm1ldGFkYXRhIGZlYXR1cmVzKiogKGUuZy4sIHNlbmRlciByZXB1dGF0aW9uLCBlbWFpbCBzdHJ1Y3R1cmUpLiB8ICoqRGV0ZWN0cyBzcG9vZmVkL3BoaXNoaW5nIGVtYWlscyBtb3JlIGVmZmVjdGl2ZWx5LioqIHwNCnwgKipFeHBsYWluYWJpbGl0eSBFbmhhbmNlbWVudHMqKiB8IFVzZSAqKlNIQVAsIExJTUUqKiBmb3IgaW50ZXJwcmV0YWJpbGl0eS4gfCAqKlVuZGVyc3RhbmQgV0hZIGVtYWlscyB3ZXJlIGNsYXNzaWZpZWQgYXMgc3BhbS4qKiB8DQoNCi0tLQ0KDQojIyAqKkQuIEZpbmFsIFRha2Vhd2F5KioNCioqU2VsZi1UcmFpbmluZyBYR0Jvb3N0IG91dHBlcmZvcm1zIHRyYWRpdGlvbmFsIG1ldGhvZHMsIGFjaGlldmluZyBuZWFyLXBlcmZlY3Qgc3BhbSBkZXRlY3Rpb24uKiogIA0KKipUbyBtYWludGFpbiBhY2N1cmFjeSwgcmVndWxhciByZXRyYWluaW5nIGFuZCBodW1hbiBmZWVkYmFjayBsb29wcyBhcmUgY3JpdGljYWwuKiogIA0KKipGdXR1cmUgbW9kZWxzIHNob3VsZCBpbmNvcnBvcmF0ZSBkZWVwIGxlYXJuaW5nLCBlbnNlbWJsZSBtZXRob2RzLCBhbmQgcmVhbC10aW1lIHVwZGF0ZXMuKioNCg0KLS0tDQoNCg0KIyMjICoqUmVmZXJlbmNlcyoqICANCg0KQmVsb3cgaXMgYSBsaXN0IG9mIGFjYWRlbWljIHBhcGVycywgdGV4dGJvb2tzLCBhbmQgb25saW5lIHJlc291cmNlcyB1c2VkIGluIHRoaXMgY2FzZSBzdHVkeS4gVGhlc2UgcmVmZXJlbmNlcyBwcm92aWRlIHRoZW9yZXRpY2FsIGJhY2tncm91bmQgYW5kIHN1cHBvcnRpbmcgcmVzZWFyY2ggZm9yIHRoZSBtZXRob2RvbG9naWVzIGVtcGxveWVkLiAgDQoNCiMjIyMgKipBY2FkZW1pYyBQYXBlcnMgJiBUZXh0Ym9va3MqKg0KMS4gQmlzaG9wLCBDLiBNLiAoMjAwNikuICpQYXR0ZXJuIFJlY29nbml0aW9uIGFuZCBNYWNoaW5lIExlYXJuaW5nKi4gU3ByaW5nZXIuICANCiAgIC0gQ292ZXJzIE5hw692ZSBCYXllcyBjbGFzc2lmaWNhdGlvbiwgcHJvYmFiaWxpc3RpYyBtb2RlbHMsIGFuZCBjbHVzdGVyaW5nIGFwcHJvYWNoZXMuDQogICANCjIuIEhhc3RpZSwgVC4sIFRpYnNoaXJhbmksIFIuLCAmIEZyaWVkbWFuLCBKLiAoMjAwOSkuICpUaGUgRWxlbWVudHMgb2YgU3RhdGlzdGljYWwgTGVhcm5pbmcqLiBTcHJpbmdlci4gIA0KICAgLSBBIGtleSByZWZlcmVuY2UgZm9yIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzLCBpbmNsdWRpbmcgTmHDr3ZlIEJheWVzIGFuZCB0cmVlLWJhc2VkIG1ldGhvZHMuDQoNCjMuIFJlbm5pZSwgSi4gRC4gTS4sIFNoaWgsIEwuLCBUZWV2YW4sIEouLCAmIEthcmdlciwgRC4gUi4gKDIwMDMpLiAqVGFja2xpbmcgdGhlIFBvb3IgQXNzdW1wdGlvbnMgb2YgTmFpdmUgQmF5ZXMgVGV4dCBDbGFzc2lmaWVycyouIFByb2NlZWRpbmdzIG9mIElDTUwuICANCiAgIC0gRGlzY3Vzc2VzIE5hw692ZSBCYXllcyBsaW1pdGF0aW9ucyBpbiB0ZXh0IGNsYXNzaWZpY2F0aW9uIGFuZCBwb3NzaWJsZSBpbXByb3ZlbWVudHMuDQoNCjQuIFlhbmcsIFkuLCAmIExpdSwgWC4gKDE5OTkpLiAqQSBSZS1FeGFtaW5hdGlvbiBvZiBUZXh0IENhdGVnb3JpemF0aW9uIE1ldGhvZHMqLiBTSUdJUi4gIA0KICAgLSBDb21wYXJhdGl2ZSBzdHVkeSBvbiBOYcOvdmUgQmF5ZXMgdnMuIG90aGVyIHRleHQgY2xhc3NpZmljYXRpb24gbWV0aG9kcy4NCg0KIyMjIyAqKlNwYW0gRmlsdGVyaW5nIGFuZCBDYXNlIFN0dWR5LVNwZWNpZmljIFJlZmVyZW5jZXMqKg0KNS4gU2FoYW1pLCBNLiwgRHVtYWlzLCBTLiwgSGVja2VybWFuLCBELiwgJiBIb3J2aXR6LCBFLiAoMTk5OCkuICpBIEJheWVzaWFuIEFwcHJvYWNoIHRvIEZpbHRlcmluZyBKdW5rIEUtTWFpbCouIEFBQUkgV29ya3Nob3Agb24gTGVhcm5pbmcgZm9yIFRleHQgQ2F0ZWdvcml6YXRpb24uICANCiAgIC0gT25lIG9mIHRoZSBmb3VuZGF0aW9uYWwgcGFwZXJzIGFwcGx5aW5nIE5hw692ZSBCYXllcyB0byBzcGFtIGNsYXNzaWZpY2F0aW9uLg0KDQo2LiBBcGFjaGUgU3BhbUFzc2Fzc2luLiAobi5kLikuICpTcGFtQXNzYXNzaW4gUHVibGljIENvcnB1cyouIFJldHJpZXZlZCBmcm9tOiAgDQogICAtIGh0dHBzOi8vc3BhbWFzc2Fzc2luLmFwYWNoZS5vcmcvcHVibGljY29ycHVzLyAgDQogICAtIFRoZSBkYXRhc2V0IHVzZWQgZm9yIHRoaXMgY2FzZSBzdHVkeS4NCg0KNy4gTWNIdWdoLCBKLiAoMjAwMCkuICpUZXN0aW5nIEludHJ1c2lvbiBEZXRlY3Rpb24gU3lzdGVtczogQSBDcml0aXF1ZSBvZiB0aGUgMTk5OCBhbmQgMTk5OSBEQVJQQSBJRFMgRXZhbHVhdGlvbnMgYXMgUGVyZm9ybWVkIGJ5IExpbmNvbG4gTGFib3JhdG9yeSouIEFDTSBUcmFuc2FjdGlvbnMgb24gSW5mb3JtYXRpb24gYW5kIFN5c3RlbSBTZWN1cml0eSAoVElTU0VDKS4gIA0KICAgLSBEaXNjdXNzZXMgZXZhbHVhdGlvbiBtZXRob2RvbG9naWVzIGZvciBkZXRlY3Rpb24gc3lzdGVtcywgcmVsZXZhbnQgZm9yIHBlcmZvcm1hbmNlIGFzc2Vzc21lbnQuDQoNCiMjIyMgKipNYWNoaW5lIExlYXJuaW5nICYgWEdCb29zdCBEb2N1bWVudGF0aW9uKioNCjguIFBlZHJlZ29zYSwgRi4sIFZhcm9xdWF1eCwgRy4sIEdyYW1mb3J0LCBBLiwgZXQgYWwuICgyMDExKS4gKlNjaWtpdC1MZWFybjogTWFjaGluZSBMZWFybmluZyBpbiBQeXRob24qLiBKTUxSLiAgDQogICAtIERvY3VtZW50YXRpb24gZm9yIHVzaW5nIE5hw692ZSBCYXllcyBhbmQgY2x1c3RlcmluZyBtZXRob2RzLg0KDQo5LiBDaGVuLCBULiwgJiBHdWVzdHJpbiwgQy4gKDIwMTYpLiAqWEdCb29zdDogQSBTY2FsYWJsZSBUcmVlIEJvb3N0aW5nIFN5c3RlbSouIEtERCAnMTYuICANCiAgIC0gVGhlIGZvdW5kYXRpb25hbCBwYXBlciBvbiBYR0Jvb3N0LCBleHBsYWluaW5nIGl0cyBlZmZpY2llbmN5IGluIGNsYXNzaWZpY2F0aW9uIHRhc2tzLg0KDQoxMC4gU2VjaGlkaXMsIEsuLCBUc291bWFrYXMsIEcuLCAmIFZsYWhhdmFzLCBJLiAoMjAxNCkuICpTZW1pLXN1cGVydmlzZWQgTGVhcm5pbmcgVXNpbmcgTGFiZWwgUHJvcGFnYXRpb24gYW5kIFNlbGYtdHJhaW5pbmcgZm9yIFRleHQgQ2xhc3NpZmljYXRpb24qLiAgDQogICAgLSBQYXBlciBkaXNjdXNzaW5nIHNlbGYtdHJhaW5pbmcgbWV0aG9kcywgcmVsZXZhbnQgZm9yIG91ciBTZWxmLVRyYWluaW5nIFhHQm9vc3QgaW1wbGVtZW50YXRpb24uDQoNCjExLiBNY0dyZWdvciwgQ29saW4uICgyMDA3KS4gKkNvbnRyb2xsaW5nIHNwYW0gd2l0aCBTcGFtQXNzYXNzaW4qLg0KICAgIC0gTGludXggSm91cm5hbC4gMjAwNy4gQXNzYXNzaW5hdGUgc3BhbSB3aXRoIGV4dHJlbWUgcHJlanVkaWNlLg0KDQojIyMgKipBY2tub3dsZWRnbWVudHMqKiAgDQpJIHdvdWxkIGxpa2UgdG8gYWNrbm93bGVkZ2U6ICANCioqU01VIElUIERlcGFydG1lbnQqKiBmb3IgcG9zaW5nIHRoZSBzcGFtIGNsYXNzaWZpY2F0aW9uIGNoYWxsZW5nZS4gIA0KKipTcGFtQXNzYXNzaW4qKiBmb3IgcHJvdmlkaW5nIHRoZSBwdWJsaWNseSBhdmFpbGFibGUgc3BhbSBkYXRhc2V0LiAgDQoqKkNvdXJzZSBJbnN0cnVjdG9yICYgVEFzKio6IERyLiBTbGF0ZXIgIA0KKipGZWxsb3cgc3R1ZGVudHMqKiBDbGFzc21hdGVzIGluIFNwcmluZyBRVFcgLSBmb3IgcGVlciBkaXNjdXNzaW9ucyB0aGF0IGhlbHBlZCByZWZpbmUgdGhlIGFwcHJvYWNoLiAgDQoNCi0tLQ0KDQojIyMgKipBcHBlbmRpeCoqICANCg0KDQpUaGlzIHNlY3Rpb24gaW5jbHVkZXM6IA0KKipGdWxsIFB5dGhvbiBDb2RlKioNCg0KYGBge3B5dGhvbn0NCiMgbW91bnQgZ29vZ2xlIGRyaXZlDQoNCmZyb20gZ29vZ2xlLmNvbGFiIGltcG9ydCBkcml2ZQ0KZHJpdmUubW91bnQoJy9jb250ZW50L2RyaXZlJykNCg0KZnJvbSBnZW5zaW0ucGFyc2luZy5wcmVwcm9jZXNzaW5nIGltcG9ydCBTVE9QV09SRFMNCmZyb20gbmx0ayBpbXBvcnQgd29yZF90b2tlbml6ZQ0KaW1wb3J0IG9zLCBjaGFyZGV0LCBlbWFpbCwgemlwZmlsZQ0KaW1wb3J0IHBhbmRhcyBhcyBwZCANCmltcG9ydCBudW1weSBhcyBucA0KZnJvbSBjb2xsZWN0aW9ucyBpbXBvcnQgQ291bnRlcg0KZnJvbSB3b3JkY2xvdWQgaW1wb3J0IFdvcmRDbG91ZA0KZnJvbSBza2xlYXJuLm1vZGVsX3NlbGVjdGlvbiBpbXBvcnQgdHJhaW5fdGVzdF9zcGxpdCwgY3Jvc3NfdmFsX3Njb3JlLCBTdHJhdGlmaWVkS0ZvbGQsIEdyaWRTZWFyY2hDVg0KZnJvbSBza2xlYXJuLmNsdXN0ZXIgaW1wb3J0IEtNZWFucywgREJTQ0FOLCBBZ2dsb21lcmF0aXZlQ2x1c3RlcmluZw0KZnJvbSBza2xlYXJuLm5haXZlX2JheWVzIGltcG9ydCBNdWx0aW5vbWlhbE5CDQpmcm9tIHNrbGVhcm4uZmVhdHVyZV9leHRyYWN0aW9uLnRleHQgaW1wb3J0IFRmaWRmVmVjdG9yaXplcg0KZnJvbSBza2xlYXJuLmVuc2VtYmxlIGltcG9ydCBBZGFCb29zdENsYXNzaWZpZXINCmZyb20gc2tsZWFybi5tYW5pZm9sZCBpbXBvcnQgVFNORQ0KZnJvbSBza2xlYXJuLmRlY29tcG9zaXRpb24gaW1wb3J0IFBDQQ0KZnJvbSBza2xlYXJuLnByZXByb2Nlc3NpbmcgaW1wb3J0IFN0YW5kYXJkU2NhbGVyDQpmcm9tIGltYmxlYXJuLm92ZXJfc2FtcGxpbmcgaW1wb3J0IFJhbmRvbU92ZXJTYW1wbGVyDQpmcm9tIHNrbGVhcm4ubWV0cmljcyBpbXBvcnQgYWNjdXJhY3lfc2NvcmUsIGNsYXNzaWZpY2F0aW9uX3JlcG9ydCwgQ29uZnVzaW9uTWF0cml4RGlzcGxheSwgY29tcGxldGVuZXNzX3Njb3JlLCBjb25mdXNpb25fbWF0cml4DQpmcm9tIGJzNCBpbXBvcnQgQmVhdXRpZnVsU291cA0KZnJvbSBza2xlYXJuLnBpcGVsaW5lIGltcG9ydCBQaXBlbGluZQ0KZnJvbSBza2xlYXJuLm1ldHJpY3MgaW1wb3J0IGNvbmZ1c2lvbl9tYXRyaXgsIGFjY3VyYWN5X3Njb3JlLCByZWNhbGxfc2NvcmUsIGNsYXNzaWZpY2F0aW9uX3JlcG9ydA0KZnJvbSBubHRrLmNvcnB1cyBpbXBvcnQgc3RvcHdvcmRzDQppbXBvcnQgc2VhYm9ybiBhcyBzbnMNCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQNCmltcG9ydCBubHRrDQpmcm9tIG5sdGsuc3RlbS5zbm93YmFsbCBpbXBvcnQgU25vd2JhbGxTdGVtbWVyDQpmcm9tIG5sdGsuY29ycHVzIGltcG9ydCB3b3JkbmV0IGFzIHduDQpmcm9tIG5sdGsudG9rZW5pemUgaW1wb3J0IHdvcmRfdG9rZW5pemUNCmZyb20gbmx0ay5jb3JwdXMgaW1wb3J0IHN0b3B3b3Jkcw0KZnJvbSBzdHJpbmcgaW1wb3J0IHB1bmN0dWF0aW9uDQppbXBvcnQgcmUNCmltcG9ydCBxdW9wcmkNCmltcG9ydCBzdHJpbmcNCmltcG9ydCBzcGFjeQ0KDQoNCiMgaW1wb3J0IG5sdGs7IG5sdGsuZG93bmxvYWQoJ3B1bmt0X3RhYicpDQojIGltcG9ydCBubHRrOyBubHRrLmRvd25sb2FkKCdwb3B1bGFyJykNCg0KY29udGVudHMgPSBbXQ0KdHlwZXMgPSBbXQ0KbGFiZWxzID0gW10NCnNlbmRlciA9IFtdDQoNCg0KZm9yIHJvb3QsIGRpcnMsIGZpbGVzIGluIG9zLndhbGsoIi9jb250ZW50L2RyaXZlL015RHJpdmUvU3BhbUFzc2Fzc2luTWVzc2FnZXMvU3BhbUFzc2Fzc2luTWVzc2FnZXMiLCB0b3Bkb3duPUZhbHNlKToNCiAgICBmb3IgbmFtZSBpbiBmaWxlczoNCiAgICAgICAgd2l0aCBvcGVuKG9zLnBhdGguam9pbihyb290LCBuYW1lKSwgJ3JiJykgYXMgZmlsZToNCiAgICAgICAgICAgIHJhd19lbWFpbCA9IGZpbGUucmVhZCgpDQogICAgICAgICAgICBlbmNvZGluZyA9IGNoYXJkZXQuZGV0ZWN0KHJhd19lbWFpbClbJ2VuY29kaW5nJ10gICMgRGV0ZWN0IGVuY29kaW5nDQoNCiAgICAgICAgICAgIHRyeToNCiAgICAgICAgICAgICAgICAjIERlY29kZSB1c2luZyBkZXRlY3RlZCBlbmNvZGluZw0KICAgICAgICAgICAgICAgIGRlY29kZWRfZW1haWwgPSByYXdfZW1haWwuZGVjb2RlKGVuY29kaW5nLCBlcnJvcnM9J2lnbm9yZScpDQogICAgICAgICAgICAgICAgbXNnID0gZW1haWwubWVzc2FnZV9mcm9tX3N0cmluZyhkZWNvZGVkX2VtYWlsKQ0KICAgICAgICAgICAgICAgICMgQWRkIGxhYmVsIGFuZCBhcHBlbmQgdG8gdGhlICdjb250ZW50cycgYXJyYXkNCiAgICAgICAgICAgICAgICBpZiAnc3BhbScgaW4gcm9vdDoNCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzLmFwcGVuZCgxKQ0KICAgICAgICAgICAgICAgICAgICBjb250ZW50cy5hcHBlbmQobXNnLmdldF9wYXlsb2FkKCkpDQogICAgICAgICAgICAgICAgICAgIHNlbmRlci5hcHBlbmQobXNnLmdldCgnRnJvbScpKQ0KICAgICAgICAgICAgICAgIGVsc2U6DQogICAgICAgICAgICAgICAgICAgIGxhYmVscy5hcHBlbmQoMCkNCiAgICAgICAgICAgICAgICAgICAgY29udGVudHMuYXBwZW5kKG1zZy5nZXRfcGF5bG9hZCgpKQ0KICAgICAgICAgICAgICAgICAgICBzZW5kZXIuYXBwZW5kKG1zZy5nZXQoJ0Zyb20nKSkNCiAgICAgICAgICAgIGV4Y2VwdCBVbmljb2RlRGVjb2RlRXJyb3I6DQogICAgICAgICAgICAgICAgcHJpbnQoZiJFcnJvciBkZWNvZGluZyBmaWxlOiB7b3MucGF0aC5qb2luKHJvb3QsIG5hbWUpfSIpDQogICAgICAgICAgICAgICAgDQojIEdldCB0aGUgbW9zdCBmcmVxdWVudCBjb250ZW50IHR5cGUNCnR5cGVzID0gQ291bnRlcih0eXBlcykubW9zdF9jb21tb24oMSkNCg0KIyBwcmludCBhIHNpbmdsZSBzdGF0ZW1lbnQgaW5zdGVhZCBvZiB1c2luZyBtYW55IHByaW50IHN0YXRlbWVudHMNCnByaW50KGYiY29udGVudHMgbGVuZ3RoOiB7bGVuKGNvbnRlbnRzKX0sIHR5cGVzIGxlbmd0aDoge2xlbih0eXBlcyl9LCBsYWJlbHMgbGVuZ3RoOiB7bGVuKGxhYmVscyl9LCBmaWxlIGxpc3QgbGVuZ3RoOiB7bGVuKGZpbGVfbGlzdF9mdWxsKX0sIHNlbmRlciBsZW5ndGg6IHtsZW4oc2VuZGVyKX0iKQ0KDQppbXBvcnQgb3MNCmltcG9ydCBlbWFpbA0KaW1wb3J0IHJlDQppbXBvcnQgY2hhcmRldA0KaW1wb3J0IHBhbmRhcyBhcyBwZA0KaW1wb3J0IG51bXB5IGFzIG5wDQppbXBvcnQgc2VhYm9ybiBhcyBzbnMNCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQNCmZyb20gY29sbGVjdGlvbnMgaW1wb3J0IGRlZmF1bHRkaWN0LCBDb3VudGVyDQpmcm9tIG11bHRpcHJvY2Vzc2luZyBpbXBvcnQgUG9vbCwgY3B1X2NvdW50DQpmcm9tIGJzNCBpbXBvcnQgQmVhdXRpZnVsU291cA0KZnJvbSB3b3JkY2xvdWQgaW1wb3J0IFdvcmRDbG91ZA0KZnJvbSBza2xlYXJuLnBpcGVsaW5lIGltcG9ydCBQaXBlbGluZQ0KZnJvbSBza2xlYXJuLm1vZGVsX3NlbGVjdGlvbiBpbXBvcnQgdHJhaW5fdGVzdF9zcGxpdCwgU3RyYXRpZmllZEtGb2xkLCBHcmlkU2VhcmNoQ1YNCmZyb20gc2tsZWFybi5mZWF0dXJlX2V4dHJhY3Rpb24udGV4dCBpbXBvcnQgVGZpZGZWZWN0b3JpemVyDQpmcm9tIHNrbGVhcm4ubmFpdmVfYmF5ZXMgaW1wb3J0IE11bHRpbm9taWFsTkINCmZyb20gc2tsZWFybi5lbnNlbWJsZSBpbXBvcnQgUmFuZG9tRm9yZXN0Q2xhc3NpZmllcg0KZnJvbSBza2xlYXJuLnN2bSBpbXBvcnQgU1ZDDQpmcm9tIHNrbGVhcm4ubWV0cmljcyBpbXBvcnQgYWNjdXJhY3lfc2NvcmUsIGNsYXNzaWZpY2F0aW9uX3JlcG9ydCwgY29uZnVzaW9uX21hdHJpeCwgQ29uZnVzaW9uTWF0cml4RGlzcGxheSwgcmVjYWxsX3Njb3JlDQpmcm9tIGltYmxlYXJuLm92ZXJfc2FtcGxpbmcgaW1wb3J0IFJhbmRvbU92ZXJTYW1wbGVyDQppbXBvcnQgbmx0aw0KDQpubHRrLmRvd25sb2FkKCdzdG9wd29yZHMnKQ0Kbmx0ay5kb3dubG9hZCgncHVua3QnKQ0KZnJvbSBubHRrLmNvcnB1cyBpbXBvcnQgc3RvcHdvcmRzDQpmcm9tIG5sdGsudG9rZW5pemUgaW1wb3J0IHdvcmRfdG9rZW5pemUNCmZyb20gbmx0ay5zdGVtLnNub3diYWxsIGltcG9ydCBTbm93YmFsbFN0ZW1tZXINCg0KIyBEZWZpbmUgcm9vdCBkaXJlY3RvcnkgZm9yIGVtYWlsIGRhdGFzZXQNClJPT1RfRElSID0gIi9jb250ZW50L2RyaXZlL015RHJpdmUvU3BhbUFzc2Fzc2luTWVzc2FnZXMvU3BhbUFzc2Fzc2luTWVzc2FnZXMiDQoNCiMgRnVuY3Rpb24gdG8gZmxhZyBlbWFpbHMgYXMgc3BhbSAoMSkgb3IgaGFtICgwKQ0KZGVmIGZsYWdfZW1haWxzKGZpbGVwYXRoLCBwb3NpdGl2ZV9pbmRpY2F0b3I9InNwYW0iKToNCiAgICByZXR1cm4gMSBpZiBwb3NpdGl2ZV9pbmRpY2F0b3IgaW4gZmlsZXBhdGggZWxzZSAwDQoNCiMgRnVuY3Rpb24gdG8gZXh0cmFjdCBzZW5kZXIgZG9tYWluDQpkZWYgZXh0cmFjdF9zZW5kZXJfZG9tYWluKG1zZyk6DQogICAgc2VuZGVyID0gbXNnLmdldCgnRnJvbScsICcnKQ0KICAgIGlmIHNlbmRlciBhbmQgJ0AnIGluIHNlbmRlcjoNCiAgICAgICAgcmV0dXJuIHNlbmRlci5zcGxpdCgnQCcpWy0xXQ0KICAgIHJldHVybiAndW5rbm93bicNCg0KIyBGdW5jdGlvbiB0byBleHRyYWN0IHRleHQgZnJvbSBhbiBlbWFpbCBtZXNzYWdlDQpkZWYgZXh0cmFjdF9lbWFpbF90ZXh0KG1zZyk6DQogICAgIiIiRXh0cmFjdHMgdGV4dCBmcm9tIGVtYWlsIGJvZHksIGhhbmRsaW5nIGJvdGggc2luZ2xlLXBhcnQgYW5kIG11bHRpLXBhcnQgZW1haWxzLiIiIg0KICAgIGVtYWlsX3RleHQgPSAiIg0KICAgIGlmIG1zZy5pc19tdWx0aXBhcnQoKToNCiAgICAgICAgZm9yIHBhcnQgaW4gbXNnLndhbGsoKToNCiAgICAgICAgICAgIGlmIHBhcnQuZ2V0X2NvbnRlbnRfbWFpbnR5cGUoKSA9PSAidGV4dCI6DQogICAgICAgICAgICAgICAgdHJ5Og0KICAgICAgICAgICAgICAgICAgICBlbWFpbF90ZXh0ICs9IHBhcnQuZ2V0X3BheWxvYWQoZGVjb2RlPVRydWUpLmRlY29kZShlcnJvcnM9Imlnbm9yZSIpICsgIiAiDQogICAgICAgICAgICAgICAgZXhjZXB0Og0KICAgICAgICAgICAgICAgICAgICBjb250aW51ZQ0KICAgIGVsc2U6DQogICAgICAgIHRyeToNCiAgICAgICAgICAgIGVtYWlsX3RleHQgPSBtc2cuZ2V0X3BheWxvYWQoZGVjb2RlPVRydWUpLmRlY29kZShlcnJvcnM9Imlnbm9yZSIpDQogICAgICAgIGV4Y2VwdDoNCiAgICAgICAgICAgIHBhc3MNCiAgICByZXR1cm4gZW1haWxfdGV4dC5zdHJpcCgpDQoNCiMgRnVuY3Rpb24gdG8gcHJlcHJvY2VzcyB0ZXh0DQpkZWYgcHJlcHJvY2Vzc190ZXh0KHRleHQpOg0KICAgICIiIkNsZWFucyBhbmQgdG9rZW5pemVzIHRleHQsIHJlbW92aW5nIHN0b3B3b3JkcyBhbmQgYXBwbHlpbmcgc3RlbW1pbmcuIiIiDQogICAgc3RlbW1lciA9IFNub3diYWxsU3RlbW1lcigiZW5nbGlzaCIpDQogICAgc3RvcHdvcmRzX3NldCA9IHNldChzdG9wd29yZHMud29yZHMoImVuZ2xpc2giKSkNCiAgICANCiAgICB0ZXh0ID0gQmVhdXRpZnVsU291cCh0ZXh0LCAiaHRtbC5wYXJzZXIiKS5nZXRfdGV4dCgpDQogICAgdGV4dCA9IHJlLnN1YihyIlteXHdcc10iLCAiIiwgdGV4dC5sb3dlcigpKQ0KICAgIHdvcmRzID0gd29yZF90b2tlbml6ZSh0ZXh0KQ0KICAgIHdvcmRzID0gW3N0ZW1tZXIuc3RlbSh3b3JkKSBmb3Igd29yZCBpbiB3b3JkcyBpZiB3b3JkIG5vdCBpbiBzdG9wd29yZHNfc2V0XQ0KICAgIA0KICAgIHJldHVybiAiICIuam9pbih3b3JkcykNCg0KIyBMb2FkIGVtYWlscw0KZGVmIGxvYWRfZW1haWxzKHJvb3RfZGlyKToNCiAgICBlbWFpbF9kYXRhID0geyJ0ZXh0IjogW10sICJsYWJlbCI6IFtdLCAic2VuZGVyX2RvbWFpbiI6IFtdfQ0KICAgIA0KICAgIGZvciBkaXJwYXRoLCBfLCBmaWxlbmFtZXMgaW4gb3Mud2Fsayhyb290X2Rpcik6DQogICAgICAgIGZvciBuYW1lIGluIGZpbGVuYW1lczoNCiAgICAgICAgICAgIGZpbGVwYXRoID0gb3MucGF0aC5qb2luKGRpcnBhdGgsIG5hbWUpDQogICAgICAgICAgICBsYWJlbCA9IGZsYWdfZW1haWxzKGZpbGVwYXRoKQ0KICAgICAgICAgICAgDQogICAgICAgICAgICB0cnk6DQogICAgICAgICAgICAgICAgd2l0aCBvcGVuKGZpbGVwYXRoLCAicmIiKSBhcyBmOg0KICAgICAgICAgICAgICAgICAgICBtc2cgPSBlbWFpbC5tZXNzYWdlX2Zyb21fYnl0ZXMoZi5yZWFkKCkpDQogICAgICAgICAgICAgICAgICAgIGVtYWlsX3RleHQgPSBleHRyYWN0X2VtYWlsX3RleHQobXNnKQ0KICAgICAgICAgICAgICAgICAgICBzZW5kZXJfZG9tYWluID0gZXh0cmFjdF9zZW5kZXJfZG9tYWluKG1zZykNCiAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgIGlmIGVtYWlsX3RleHQ6DQogICAgICAgICAgICAgICAgICAgICAgICBlbWFpbF9kYXRhWyJ0ZXh0Il0uYXBwZW5kKGVtYWlsX3RleHQpDQogICAgICAgICAgICAgICAgICAgICAgICBlbWFpbF9kYXRhWyJsYWJlbCJdLmFwcGVuZChsYWJlbCkNCiAgICAgICAgICAgICAgICAgICAgICAgIGVtYWlsX2RhdGFbInNlbmRlcl9kb21haW4iXS5hcHBlbmQoc2VuZGVyX2RvbWFpbikNCiAgICAgICAgICAgIGV4Y2VwdDoNCiAgICAgICAgICAgICAgICBjb250aW51ZQ0KDQogICAgcmV0dXJuIHBkLkRhdGFGcmFtZShlbWFpbF9kYXRhKQ0KDQojIExvYWQgYW5kIHByZXByb2Nlc3MgZGF0YXNldA0KZW1haWxfZGYgPSBsb2FkX2VtYWlscyhST09UX0RJUikNCmVtYWlsX2RmWyJjbGVhbl90ZXh0Il0gPSBlbWFpbF9kZlsidGV4dCJdLmFwcGx5KHByZXByb2Nlc3NfdGV4dCkNCg0KIyAqKlN0ZXAgMTogRmVhdHVyZSAmIFJlc3BvbnNlIEZyYW1lcyoqDQp2ZWN0b3JpemVyID0gVGZpZGZWZWN0b3JpemVyKG1heF9mZWF0dXJlcz01MDAwKQ0KWCA9IHZlY3Rvcml6ZXIuZml0X3RyYW5zZm9ybShlbWFpbF9kZlsiY2xlYW5fdGV4dCJdKSAgIyBGZWF0dXJlcw0KeSA9IGVtYWlsX2RmWyJsYWJlbCJdICAjIFJlc3BvbnNlIHZhcmlhYmxlDQoNCiMgU3RvcmUgaW4gRGF0YUZyYW1lIChPcHRpb25hbCkNCmZlYXR1cmVzX2RmID0gcGQuRGF0YUZyYW1lKFgudG9hcnJheSgpLCBjb2x1bW5zPXZlY3Rvcml6ZXIuZ2V0X2ZlYXR1cmVfbmFtZXNfb3V0KCkpDQpmZWF0dXJlc19kZlsibGFiZWwiXSA9IHkNCg0KIyAqKlN0ZXAgMjogVHJhaW4gb24gODAlICYgVGVzdCBvbiAyMCUqKg0KWF90cmFpbiwgWF90ZXN0LCB5X3RyYWluLCB5X3Rlc3QgPSB0cmFpbl90ZXN0X3NwbGl0KFgsIHksIHRlc3Rfc2l6ZT0wLjIsIHN0cmF0aWZ5PXksIHJhbmRvbV9zdGF0ZT00MikNCg0KIyAqKlN0ZXAgMzogSGFuZGxlIENsYXNzIEltYmFsYW5jZSoqDQpyb3MgPSBSYW5kb21PdmVyU2FtcGxlcihyYW5kb21fc3RhdGU9NDIpDQpYX3RyYWluX3Jlc2FtcGxlZCwgeV90cmFpbl9yZXNhbXBsZWQgPSByb3MuZml0X3Jlc2FtcGxlKFhfdHJhaW4sIHlfdHJhaW4pDQoNCiMgKipTdGVwIDQ6IFRyYWluICYgQ3Jvc3MtVmFsaWRhdGUgTmFpdmUgQmF5ZXMqKg0KbmJfY2xhc3NpZmllciA9IE11bHRpbm9taWFsTkIoKQ0Kc2tmID0gU3RyYXRpZmllZEtGb2xkKG5fc3BsaXRzPTEwLCBzaHVmZmxlPVRydWUsIHJhbmRvbV9zdGF0ZT00MikNCmN2X3Njb3JlcyA9IEdyaWRTZWFyY2hDVihuYl9jbGFzc2lmaWVyLCB7ImFscGhhIjogWzAuMDEsIDAuMSwgMV19LCBjdj1za2YsIHNjb3Jpbmc9ImFjY3VyYWN5Iiwgbl9qb2JzPS0xKQ0KY3Zfc2NvcmVzLmZpdChYX3RyYWluX3Jlc2FtcGxlZCwgeV90cmFpbl9yZXNhbXBsZWQpDQoNCiMgKipTdGVwIDU6IE1ha2UgUHJlZGljdGlvbnMqKg0KeV9wcmVkID0gY3Zfc2NvcmVzLmJlc3RfZXN0aW1hdG9yXy5wcmVkaWN0KFhfdGVzdCkNCg0KIyAqKlN0ZXAgNjogRXZhbHVhdGUgTW9kZWwqKg0KcHJpbnQoIkJlc3QgTmFpdmUgQmF5ZXMgTW9kZWw6IiwgY3Zfc2NvcmVzLmJlc3RfZXN0aW1hdG9yXykNCnByaW50KCJBY2N1cmFjeSBvbiBUZXN0IFNldDoiLCBhY2N1cmFjeV9zY29yZSh5X3Rlc3QsIHlfcHJlZCkpDQpwcmludCgiXG5DbGFzc2lmaWNhdGlvbiBSZXBvcnQ6IikNCnByaW50KGNsYXNzaWZpY2F0aW9uX3JlcG9ydCh5X3Rlc3QsIHlfcHJlZCkpDQoNCkNvbmZ1c2lvbk1hdHJpeERpc3BsYXkuZnJvbV9wcmVkaWN0aW9ucyh5X3Rlc3QsIHlfcHJlZCwgY21hcD0iQmx1ZXMiKQ0KcGx0LnRpdGxlKCJDb25mdXNpb24gTWF0cml4IC0gTmFpdmUgQmF5ZXMiKQ0KcGx0LnNob3coKQ0KDQojICoqU3RlcCA3OiBMb2ctTGlrZWxpaG9vZCBSYXRpbyBmb3IgU3BhbSBXb3JkcyoqDQpkZWYgY2FsY3VsYXRlX2xvZ19saWtlbGlob29kKGVtYWlsX2RmKToNCiAgICAiIiJDb21wdXRlcyBsb2ctbGlrZWxpaG9vZCByYXRpb3MgZm9yIHNwYW0gdnMgaGFtIHdvcmRzLiIiIg0KICAgIHdvcmRfY291bnRzID0gZGVmYXVsdGRpY3QobGFtYmRhOiBbMCwgMF0pDQogICAgDQogICAgZm9yIHRleHQsIGxhYmVsIGluIHppcChlbWFpbF9kZlsiY2xlYW5fdGV4dCJdLCBlbWFpbF9kZlsibGFiZWwiXSk6DQogICAgICAgIHdvcmRzID0gc2V0KHRleHQuc3BsaXQoKSkNCiAgICAgICAgZm9yIHdvcmQgaW4gd29yZHM6DQogICAgICAgICAgICB3b3JkX2NvdW50c1t3b3JkXVtsYWJlbF0gKz0gMQ0KICAgIA0KICAgIGxvZ19saWtlbGlob29kcyA9IHt9DQogICAgZm9yIHdvcmQsIChoYW1fY291bnQsIHNwYW1fY291bnQpIGluIHdvcmRfY291bnRzLml0ZW1zKCk6DQogICAgICAgIHBfaGFtID0gKGhhbV9jb3VudCArIDEpIC8gKHN1bShjWzBdIGZvciBjIGluIHdvcmRfY291bnRzLnZhbHVlcygpKSArIDEpDQogICAgICAgIHBfc3BhbSA9IChzcGFtX2NvdW50ICsgMSkgLyAoc3VtKGNbMV0gZm9yIGMgaW4gd29yZF9jb3VudHMudmFsdWVzKCkpICsgMSkNCiAgICAgICAgbG9nX2xpa2VsaWhvb2RzW3dvcmRdID0gbnAubG9nKHBfc3BhbSAvIHBfaGFtKQ0KICAgIA0KICAgIHJldHVybiBsb2dfbGlrZWxpaG9vZHMNCg0KbG9nX2xpa2VsaWhvb2RzID0gY2FsY3VsYXRlX2xvZ19saWtlbGlob29kKGVtYWlsX2RmKQ0Kc29ydGVkX3NwYW1fd29yZHMgPSBzb3J0ZWQobG9nX2xpa2VsaWhvb2RzLml0ZW1zKCksIGtleT1sYW1iZGEgeDogeFsxXSwgcmV2ZXJzZT1UcnVlKQ0KDQojICoqU3RlcCA4OiBWaXN1YWxpemF0aW9ucyoqDQpwbHQuZmlndXJlKGZpZ3NpemU9KDEwLCA1KSkNCnNucy5iYXJwbG90KHg9W3hbMV0gZm9yIHggaW4gc29ydGVkX3NwYW1fd29yZHNbOjIwXV0sIHk9W3hbMF0gZm9yIHggaW4gc29ydGVkX3NwYW1fd29yZHNbOjIwXV0pDQpwbHQudGl0bGUoIlRvcCBTcGFtIFdvcmRzIGJ5IExvZy1MaWtlbGlob29kIikNCnBsdC5zaG93KCkNCg0Kc3BhbV93b3JkcyA9ICIgIi5qb2luKGVtYWlsX2RmW2VtYWlsX2RmWyJsYWJlbCJdID09IDFdWyJjbGVhbl90ZXh0Il0pDQpzcGFtX3dvcmRjbG91ZCA9IFdvcmRDbG91ZCh3aWR0aD04MDAsIGhlaWdodD00MDAsIGJhY2tncm91bmRfY29sb3I9IndoaXRlIikuZ2VuZXJhdGUoc3BhbV93b3JkcykNCg0KcGx0LmZpZ3VyZShmaWdzaXplPSgxMCwgNSkpDQpwbHQuaW1zaG93KHNwYW1fd29yZGNsb3VkLCBpbnRlcnBvbGF0aW9uPSJiaWxpbmVhciIpDQpwbHQuYXhpcygib2ZmIikNCnBsdC50aXRsZSgiV29yZCBDbG91ZCBvZiBTcGFtIEVtYWlscyIpDQpwbHQuc2hvdygpDQoNCg0KKipFeHRlbmRlZCBDb25mdXNpb24gTWF0cmljZXMgcGVyIENyb3NzLVZhbGlkYXRpb24gRm9sZCoqICANCioqRnVsbCBDbGFzc2lmaWNhdGlvbiBSZXBvcnRzKiogIA0KKipIeXBlcnBhcmFtZXRlciBUdW5pbmcgTG9ncyoqICANCioqQWRkaXRpb25hbCBGaWd1cmVzICYgVGFibGVzKiogIA0KDQojIyMgKipBcHBlbmRpeCoqICANCg0KVGhpcyBhcHBlbmRpeCBjb250YWlucyBleHRlbmRlZCBldmFsdWF0aW9uIHJlc3VsdHMsIGNvbmZ1c2lvbiBtYXRyaWNlcyBmb3IgZWFjaCBjcm9zcy12YWxpZGF0aW9uIGZvbGQsIGZ1bGwgY2xhc3NpZmljYXRpb24gcmVwb3J0cywgaHlwZXJwYXJhbWV0ZXIgdHVuaW5nIGxvZ3MsIGFuZCBhZGRpdGlvbmFsIHZpc3VhbGl6YXRpb25zLiAgDQoNCi0tLQ0KDQojIyMgKipFeHRlbmRlZCBDb25mdXNpb24gTWF0cmljZXMgZm9yIENyb3NzLVZhbGlkYXRpb24qKiAgDQoNCkJlbG93IGFyZSB0aGUgY29uZnVzaW9uIG1hdHJpY2VzIGZvciBlYWNoIGZvbGQgZHVyaW5nIGNyb3NzLXZhbGlkYXRpb24uIFRoZXNlIG1hdHJpY2VzIHByb3ZpZGUgaW5zaWdodCBpbnRvIHRoZSBudW1iZXIgb2YgY29ycmVjdGx5IGNsYXNzaWZpZWQgaGFtIGFuZCBzcGFtIGVtYWlscyBhY3Jvc3MgbXVsdGlwbGUgdHJhaW4tdGVzdCBzcGxpdHMuICANCg0KIyMjIyAqKkNvbmZ1c2lvbiBNYXRyaWNlcyBBY3Jvc3MgMTAtRm9sZCBDcm9zcy1WYWxpZGF0aW9uKiogIA0KDQoNCiMgR3JpZCBTZWFyY2ggZm9yIE5hw692ZSBCYXllcyAoUnVuIHRoaXMgc2VwYXJhdGVseSBmb3IgTXVsdGlub21pYWxOQiB0byBnZXQgcGFyYW1fYWxwaGEgcHJvcGVybHk6KQ0KDQpmcm9tIHNrbGVhcm4ubW9kZWxfc2VsZWN0aW9uIGltcG9ydCBHcmlkU2VhcmNoQ1YNCmZyb20gc2tsZWFybi5uYWl2ZV9iYXllcyBpbXBvcnQgTXVsdGlub21pYWxOQg0KaW1wb3J0IG51bXB5IGFzIG5wDQppbXBvcnQgcGFuZGFzIGFzIHBkDQppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0DQppbXBvcnQgc2VhYm9ybiBhcyBzbnMNCg0KIyBEZWZpbmUgY29ycmVjdCBwYXJhbWV0ZXIgZ3JpZCBmb3IgTmHDr3ZlIEJheWVzDQpwYXJhbV9ncmlkX25iID0geyJhbHBoYSI6IG5wLmxvZ3NwYWNlKC0zLCAxLCA1KX0gICMgQWxwaGEgdmFsdWVzIGZyb20gMC4wMDEgdG8gMTANCg0KIyBJbml0aWFsaXplIE5hw692ZSBCYXllcyBjbGFzc2lmaWVyDQpuYl9jbGFzc2lmaWVyID0gTXVsdGlub21pYWxOQigpDQoNCiMgUGVyZm9ybSBHcmlkIFNlYXJjaCB3aXRoIENyb3NzLVZhbGlkYXRpb24NCmdyaWRfc2VhcmNoX25iID0gR3JpZFNlYXJjaENWKG5iX2NsYXNzaWZpZXIsIHBhcmFtX2dyaWRfbmIsIGN2PTUsIHNjb3Jpbmc9ImFjY3VyYWN5IikNCmdyaWRfc2VhcmNoX25iLmZpdChYX3RyYWluLCB5X3RyYWluKSAgIyBGaXQgb25seSBvbiBOQiBkYXRhc2V0DQoNCiMgRXh0cmFjdCByZXN1bHRzDQphbHBoYXMgPSBncmlkX3NlYXJjaF9uYi5jdl9yZXN1bHRzX1sicGFyYW1fYWxwaGEiXS5kYXRhDQptZWFuX3Njb3JlcyA9IGdyaWRfc2VhcmNoX25iLmN2X3Jlc3VsdHNfWyJtZWFuX3Rlc3Rfc2NvcmUiXQ0KDQojIENvbnZlcnQgdG8gRGF0YUZyYW1lDQpncmlkX3Jlc3VsdHNfZGYgPSBwZC5EYXRhRnJhbWUoeyJBbHBoYSI6IGFscGhhcywgIk1lYW4gQWNjdXJhY3kiOiBtZWFuX3Njb3Jlc30pDQpncmlkX3Jlc3VsdHNfZGYgPSBncmlkX3Jlc3VsdHNfZGYuc29ydF92YWx1ZXMoYnk9IkFscGhhIikNCg0KIyAqUGxvdCBBbHBoYSB2cyBBY2N1cmFjeSoqDQpwbHQuZmlndXJlKGZpZ3NpemU9KDgsIDUpKQ0KcGx0LnBsb3QoZ3JpZF9yZXN1bHRzX2RmWyJBbHBoYSJdLCBncmlkX3Jlc3VsdHNfZGZbIk1lYW4gQWNjdXJhY3kiXSwgbWFya2VyPSJvIiwgbGluZXN0eWxlPSItIiwgY29sb3I9ImIiKQ0KcGx0LnhzY2FsZSgibG9nIikNCnBsdC54bGFiZWwoIkFscGhhIChMYXBsYWNlIFNtb290aGluZykiKQ0KcGx0LnlsYWJlbCgiTWVhbiBBY2N1cmFjeSIpDQpwbHQudGl0bGUoIkdyaWQgU2VhcmNoOiBBbHBoYSB2cyBBY2N1cmFjeSAoTmHDr3ZlIEJheWVzKSIpDQpwbHQuZ3JpZChUcnVlKQ0KcGx0LnNob3coKQ0KDQojICoqSGVhdG1hcCBWaXN1YWxpemF0aW9uKioNCmhlYXRtYXBfZGF0YSA9IHBkLkRhdGFGcmFtZShncmlkX3NlYXJjaF9uYi5jdl9yZXN1bHRzX1sibWVhbl90ZXN0X3Njb3JlIl0ucmVzaGFwZSgtMSwgMSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5kZXg9Z3JpZF9zZWFyY2hfbmIuY3ZfcmVzdWx0c19bInBhcmFtX2FscGhhIl0uZGF0YSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2x1bW5zPVsiTWVhbiBBY2N1cmFjeSJdKQ0KDQpwbHQuZmlndXJlKGZpZ3NpemU9KDgsIDUpKQ0Kc25zLmhlYXRtYXAoaGVhdG1hcF9kYXRhLCBhbm5vdD1UcnVlLCBjbWFwPSJjb29sd2FybSIsIGZtdD0iLjRmIikNCnBsdC54bGFiZWwoIk1lYW4gQWNjdXJhY3kiKQ0KcGx0LnlsYWJlbCgiQWxwaGEgVmFsdWUiKQ0KcGx0LnRpdGxlKCJHcmlkIFNlYXJjaCBIZWF0bWFwOiBOYcOvdmUgQmF5ZXMiKQ0KcGx0LnNob3coKQ0KDQpmcm9tIHNrbGVhcm4ubWV0cmljcyBpbXBvcnQgcm9jX2N1cnZlLCBhdWMNCg0KIyBHZXQgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMNCnlfcHJvYiA9IGZpbmFsX25iLnByZWRpY3RfcHJvYmEoWF90ZXN0KVs6LCAxXSAgIyBQcm9iYWJpbGl0eSBmb3Igc3BhbSBjbGFzcw0KDQojIENvbXB1dGUgUk9DIGN1cnZlDQpmcHIsIHRwciwgXyA9IHJvY19jdXJ2ZSh5X3Rlc3QsIHlfcHJvYikNCnJvY19hdWMgPSBhdWMoZnByLCB0cHIpDQoNCiMgUGxvdCBST0MgQ3VydmUNCnBsdC5maWd1cmUoZmlnc2l6ZT0oOCwgNSkpDQpwbHQucGxvdChmcHIsIHRwciwgY29sb3I9ImJsdWUiLCBsdz0yLCBsYWJlbD1mIlJPQyBDdXJ2ZSAoQVVDID0ge3JvY19hdWM6LjJmfSkiKQ0KcGx0LnBsb3QoWzAsIDFdLCBbMCwgMV0sIGxpbmVzdHlsZT0iLS0iLCBjb2xvcj0iZ3JheSIpICAjIFJhbmRvbSBndWVzcyBsaW5lDQpwbHQueGxhYmVsKCJGYWxzZSBQb3NpdGl2ZSBSYXRlIikNCnBsdC55bGFiZWwoIlRydWUgUG9zaXRpdmUgUmF0ZSIpDQpwbHQudGl0bGUoIlJPQyBDdXJ2ZTogTmHDr3ZlIEJheWVzIFNwYW0gRGV0ZWN0aW9uIikNCnBsdC5sZWdlbmQoKQ0KcGx0LmdyaWQoVHJ1ZSkNCnBsdC5zaG93KCkNCiBmcm9tIHNrbGVhcm4ubWV0cmljcyBpbXBvcnQgY29uZnVzaW9uX21hdHJpeCwgQ29uZnVzaW9uTWF0cml4RGlzcGxheQ0KDQojIEdlbmVyYXRlIGNvbmZ1c2lvbiBtYXRyaXgNCmNvbmZfbWF0cml4ID0gY29uZnVzaW9uX21hdHJpeCh5X3Rlc3QsIHlfcHJlZCkNCg0KIyBEaXNwbGF5IGl0DQpkaXNwID0gQ29uZnVzaW9uTWF0cml4RGlzcGxheShjb25mX21hdHJpeCwgZGlzcGxheV9sYWJlbHM9WyJIYW0iLCAiU3BhbSJdKQ0KZGlzcC5wbG90KGNtYXA9IkJsdWVzIikNCnBsdC50aXRsZSgiQ29uZnVzaW9uIE1hdHJpeDogTmHDr3ZlIEJheWVzIikNCnBsdC5zaG93KCkNCiBpbXBvcnQgbnVtcHkgYXMgbnANCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQNCmltcG9ydCBzZWFib3JuIGFzIHNucw0KZnJvbSBza2xlYXJuLmNsdXN0ZXIgaW1wb3J0IEtNZWFucywgREJTQ0FODQpmcm9tIHNrbGVhcm4ubWV0cmljcyBpbXBvcnQgc2lsaG91ZXR0ZV9zY29yZSwgYWRqdXN0ZWRfcmFuZF9zY29yZQ0KZnJvbSBza2xlYXJuLmRlY29tcG9zaXRpb24gaW1wb3J0IFBDQQ0KZnJvbSBza2xlYXJuLm1hbmlmb2xkIGltcG9ydCBUU05FDQpmcm9tIGltYmxlYXJuLm92ZXJfc2FtcGxpbmcgaW1wb3J0IFJhbmRvbU92ZXJTYW1wbGVyDQoNCiMgRGF0YSBBdWdtZW50YXRpb24NCnJvcyA9IFJhbmRvbU92ZXJTYW1wbGVyKHJhbmRvbV9zdGF0ZT00MikgDQpYX2F1Z21lbnRlZCwgeV9hdWdtZW50ZWQgPSByb3MuZml0X3Jlc2FtcGxlKFgsIHkpDQoNCg0KIyBBcHBseSBLLU1lYW5zIHdpdGggdGhlIGJlc3QgayBmcm9tIHByZXZpb3VzIHR1bmluZw0KYmVzdF9rID0gOSAgIyBCYXNlZCBvbiBwcmV2aW91cyBzaWxob3VldHRlIHR1bmluZw0Ka21lYW5zID0gS01lYW5zKG5fY2x1c3RlcnM9YmVzdF9rLCByYW5kb21fc3RhdGU9NDIsIG5faW5pdD0xMCkNCmttZWFuc19sYWJlbHMgPSBrbWVhbnMuZml0X3ByZWRpY3QoWF9hdWdtZW50ZWQpDQoNCiMgQ29tcHV0ZSBTaWxob3VldHRlIFNjb3JlIGZvciBLLU1lYW5zDQprbWVhbnNfc2lsaG91ZXR0ZSA9IHNpbGhvdWV0dGVfc2NvcmUoWF9hdWdtZW50ZWQsIGttZWFuc19sYWJlbHMpDQoNCiMgQXBwbHkgREJTQ0FOIHdpdGggYmVzdCBwYXJhbWV0ZXJzIGZvdW5kIGVhcmxpZXINCmJlc3RfZXBzID0gMC41ICAjIEV4YW1wbGUgYmVzdCBlcHNpbG9uIGZvdW5kDQpiZXN0X21pbl9zYW1wbGVzID0gNSAgIyBFeGFtcGxlIGJlc3QgbWluX3NhbXBsZXMgZm91bmQNCmRic2NhbiA9IERCU0NBTihlcHM9YmVzdF9lcHMsIG1pbl9zYW1wbGVzPWJlc3RfbWluX3NhbXBsZXMpDQpkYnNjYW5fbGFiZWxzID0gZGJzY2FuLmZpdF9wcmVkaWN0KFhfYXVnbWVudGVkKQ0KDQojIENvbXB1dGUgU2lsaG91ZXR0ZSBTY29yZSBmb3IgREJTQ0FOIChleGNsdWRpbmcgbm9pc2UgcG9pbnRzKQ0KdmFsaWRfcG9pbnRzID0gZGJzY2FuX2xhYmVscyAhPSAtMSAgIyBJZ25vcmUgbm9pc2UgcG9pbnRzDQppZiBucC5zdW0odmFsaWRfcG9pbnRzKSA+IDE6ICAjIEVuc3VyZSB3ZSBoYXZlIGVub3VnaCBwb2ludHMgdG8gY29tcHV0ZSBzaWxob3VldHRlDQogICAgZGJzY2FuX3NpbGhvdWV0dGUgPSBzaWxob3VldHRlX3Njb3JlKFhfYXVnbWVudGVkW3ZhbGlkX3BvaW50c10sIGRic2Nhbl9sYWJlbHNbdmFsaWRfcG9pbnRzXSkNCmVsc2U6DQogICAgZGJzY2FuX3NpbGhvdWV0dGUgPSBOb25lDQoNCiMgQ29tcHV0ZSBBZGp1c3RlZCBSYW5kIEluZGV4IHRvIGNvbXBhcmUgY2x1c3RlcnMgdnMgdHJ1ZSBsYWJlbHMNCmttZWFuc19hcmkgPSBhZGp1c3RlZF9yYW5kX3Njb3JlKHlfYXVnbWVudGVkLCBrbWVhbnNfbGFiZWxzKQ0KZGJzY2FuX2FyaSA9IGFkanVzdGVkX3JhbmRfc2NvcmUoeV9hdWdtZW50ZWQsIGRic2Nhbl9sYWJlbHMpDQoNCiMgRGlzcGxheSBSZXN1bHRzDQpzaWxob3VldHRlX3Jlc3VsdHMgPSB7DQogICAgIkstTWVhbnMgU2lsaG91ZXR0ZSBTY29yZSI6IGttZWFuc19zaWxob3VldHRlLA0KICAgICJEQlNDQU4gU2lsaG91ZXR0ZSBTY29yZSI6IGRic2Nhbl9zaWxob3VldHRlIGlmIGRic2Nhbl9zaWxob3VldHRlIGVsc2UgIk4vQSAodG9vIG1hbnkgbm9pc2UgcG9pbnRzKSIsDQogICAgIkstTWVhbnMgQWRqdXN0ZWQgUmFuZCBJbmRleCAoQVJJKSI6IGttZWFuc19hcmksDQogICAgIkRCU0NBTiBBZGp1c3RlZCBSYW5kIEluZGV4IChBUkkpIjogZGJzY2FuX2FyaQ0KfQ0KDQpzaWxob3VldHRlX3Jlc3VsdHMNCiAjIFJldHJ5IHZpc3VhbGl6YXRpb24gdXNpbmcgb25seSBQQ0EgKGZhc3RlciB0aGFuIHQtU05FKQ0KZmlnLCBheGVzID0gcGx0LnN1YnBsb3RzKDEsIDIsIGZpZ3NpemU9KDEyLCA1KSkNCg0KIyBQQ0EgUHJvamVjdGlvbiBmb3IgSy1NZWFucw0KYXhlc1swXS5zY2F0dGVyKFhfcGNhWzosIDBdLCBYX3BjYVs6LCAxXSwgYz1rbWVhbnNfbGFiZWxzLCBjbWFwPSdjb29sd2FybScsIGFscGhhPTAuNykNCmF4ZXNbMF0uc2V0X3RpdGxlKCJQQ0E6IEstTWVhbnMgQ2x1c3RlcmluZyIpDQoNCiMgUENBIFByb2plY3Rpb24gZm9yIERCU0NBTg0KYXhlc1sxXS5zY2F0dGVyKFhfcGNhWzosIDBdLCBYX3BjYVs6LCAxXSwgYz1kYnNjYW5fbGFiZWxzLCBjbWFwPSdjb29sd2FybScsIGFscGhhPTAuNykNCmF4ZXNbMV0uc2V0X3RpdGxlKCJQQ0E6IERCU0NBTiBDbHVzdGVyaW5nIikNCg0KcGx0LnNob3coKQ0KIGltcG9ydCBudW1weSBhcyBucA0KaW1wb3J0IHBhbmRhcyBhcyBwZA0KaW1wb3J0IG1hdHBsb3RsaWIucHlwbG90IGFzIHBsdA0KaW1wb3J0IHNlYWJvcm4gYXMgc25zDQpmcm9tIHNrbGVhcm4ubW9kZWxfc2VsZWN0aW9uIGltcG9ydCBTdHJhdGlmaWVkS0ZvbGQsIGNyb3NzX3ZhbF9zY29yZSwgdHJhaW5fdGVzdF9zcGxpdCwgR3JpZFNlYXJjaENWDQpmcm9tIHNrbGVhcm4uZW5zZW1ibGUgaW1wb3J0IFJhbmRvbUZvcmVzdENsYXNzaWZpZXINCmZyb20geGdib29zdCBpbXBvcnQgWEdCQ2xhc3NpZmllcg0KZnJvbSBza2xlYXJuLm1ldHJpY3MgaW1wb3J0IGNsYXNzaWZpY2F0aW9uX3JlcG9ydCwgY29uZnVzaW9uX21hdHJpeCwgQ29uZnVzaW9uTWF0cml4RGlzcGxheSwgcm9jX2N1cnZlLCBhdWMNCg0KIyBTcGxpdCBpbnRvIFRyYWluaW5nICYgVGVzdCBzZXRzICg4MCUgdHJhaW4sIDIwJSB0ZXN0KQ0KWF90cmFpbiwgWF90ZXN0LCB5X3RyYWluLCB5X3Rlc3QgPSB0cmFpbl90ZXN0X3NwbGl0KFhfYXVnbWVudGVkLCB5X2F1Z21lbnRlZCwgdGVzdF9zaXplPTAuMiwgc3RyYXRpZnk9eV9hdWdtZW50ZWQsIHJhbmRvbV9zdGF0ZT00MikNCg0KIyBEZWZpbmUgaHlwZXJwYXJhbWV0ZXIgZ3JpZHMNCnJmX3BhcmFtcyA9IHsibl9lc3RpbWF0b3JzIjogWzEwMCwgMjAwXSwgIm1heF9kZXB0aCI6IFsxMCwgMjBdLCAicmFuZG9tX3N0YXRlIjogWzQyXX0NCnhnYl9wYXJhbXMgPSB7Im5fZXN0aW1hdG9ycyI6IFsxMDAsIDIwMF0sICJtYXhfZGVwdGgiOiBbMywgNl0sICJsZWFybmluZ19yYXRlIjogWzAuMSwgMC4wMV0sICJyYW5kb21fc3RhdGUiOiBbNDJdfQ0KDQojIEluaXRpYWxpemUgbW9kZWxzDQpyZiA9IFJhbmRvbUZvcmVzdENsYXNzaWZpZXIoKQ0KeGdiID0gWEdCQ2xhc3NpZmllcih1c2VfbGFiZWxfZW5jb2Rlcj1GYWxzZSwgZXZhbF9tZXRyaWM9ImxvZ2xvc3MiKQ0KDQojIFBlcmZvcm0gR3JpZCBTZWFyY2ggd2l0aCAxMC1mb2xkIENWDQpjdiA9IFN0cmF0aWZpZWRLRm9sZChuX3NwbGl0cz0xMCwgc2h1ZmZsZT1UcnVlLCByYW5kb21fc3RhdGU9NDIpDQoNCnJmX2dyaWQgPSBHcmlkU2VhcmNoQ1YocmYsIHJmX3BhcmFtcywgY3Y9Y3YsIHNjb3Jpbmc9ImFjY3VyYWN5Iiwgbl9qb2JzPS0xKQ0KeGdiX2dyaWQgPSBHcmlkU2VhcmNoQ1YoeGdiLCB4Z2JfcGFyYW1zLCBjdj1jdiwgc2NvcmluZz0iYWNjdXJhY3kiLCBuX2pvYnM9LTEpDQoNCnJmX2dyaWQuZml0KFhfdHJhaW4sIHlfdHJhaW4pDQp4Z2JfZ3JpZC5maXQoWF90cmFpbiwgeV90cmFpbikNCg0KIyBHZXQgYmVzdCBtb2RlbHMNCmJlc3RfcmYgPSByZl9ncmlkLmJlc3RfZXN0aW1hdG9yXw0KYmVzdF94Z2IgPSB4Z2JfZ3JpZC5iZXN0X2VzdGltYXRvcl8NCg0KIyBFdmFsdWF0ZSBvbiBUZXN0IFNldA0KeV9wcmVkX3JmID0gYmVzdF9yZi5wcmVkaWN0KFhfdGVzdCkNCnlfcHJlZF94Z2IgPSBiZXN0X3hnYi5wcmVkaWN0KFhfdGVzdCkNCg0KIyBHZXQgYWNjdXJhY3kgJiBjbGFzc2lmaWNhdGlvbiByZXBvcnRzDQpyZl9yZXBvcnQgPSBjbGFzc2lmaWNhdGlvbl9yZXBvcnQoeV90ZXN0LCB5X3ByZWRfcmYsIHRhcmdldF9uYW1lcz1bIkhhbSIsICJTcGFtIl0pDQp4Z2JfcmVwb3J0ID0gY2xhc3NpZmljYXRpb25fcmVwb3J0KHlfdGVzdCwgeV9wcmVkX3hnYiwgdGFyZ2V0X25hbWVzPVsiSGFtIiwgIlNwYW0iXSkNCg0KIyBDb21wdXRlIENvbmZ1c2lvbiBNYXRyaWNlcw0KcmZfY20gPSBjb25mdXNpb25fbWF0cml4KHlfdGVzdCwgeV9wcmVkX3JmKQ0KeGdiX2NtID0gY29uZnVzaW9uX21hdHJpeCh5X3Rlc3QsIHlfcHJlZF94Z2IpDQoNCiMgUk9DIEN1cnZlIGZvciBSYW5kb20gRm9yZXN0DQp5X3Byb2JfcmYgPSBiZXN0X3JmLnByZWRpY3RfcHJvYmEoWF90ZXN0KVs6LCAxXQ0KZnByX3JmLCB0cHJfcmYsIF8gPSByb2NfY3VydmUoeV90ZXN0LCB5X3Byb2JfcmYpDQpyb2NfYXVjX3JmID0gYXVjKGZwcl9yZiwgdHByX3JmKQ0KDQojIFJPQyBDdXJ2ZSBmb3IgWEdCb29zdA0KeV9wcm9iX3hnYiA9IGJlc3RfeGdiLnByZWRpY3RfcHJvYmEoWF90ZXN0KVs6LCAxXQ0KZnByX3hnYiwgdHByX3hnYiwgXyA9IHJvY19jdXJ2ZSh5X3Rlc3QsIHlfcHJvYl94Z2IpDQpyb2NfYXVjX3hnYiA9IGF1YyhmcHJfeGdiLCB0cHJfeGdiKQ0KDQojIERpc3BsYXkgcmVzdWx0cw0KcmVzdWx0cyA9IHsNCiAgICAiQmVzdCBSRiBNb2RlbCI6IGJlc3RfcmYsDQogICAgIkJlc3QgWEdCIE1vZGVsIjogYmVzdF94Z2IsDQogICAgIlJhbmRvbSBGb3Jlc3QgUmVwb3J0IjogcmZfcmVwb3J0LA0KICAgICJYR0Jvb3N0IFJlcG9ydCI6IHhnYl9yZXBvcnQsDQogICAgIlJGIENvbmZ1c2lvbiBNYXRyaXgiOiByZl9jbSwNCiAgICAiWEdCIENvbmZ1c2lvbiBNYXRyaXgiOiB4Z2JfY20sDQogICAgIlJPQyBBVUMgUkYiOiByb2NfYXVjX3JmLA0KICAgICJST0MgQVVDIFhHQiI6IHJvY19hdWNfeGdiDQp9DQoNCiMgUGxvdCBDb25mdXNpb24gTWF0cmljZXMNCmZpZywgYXhlcyA9IHBsdC5zdWJwbG90cygxLCAyLCBmaWdzaXplPSgxMiwgNSkpDQoNCkNvbmZ1c2lvbk1hdHJpeERpc3BsYXkocmZfY20sIGRpc3BsYXlfbGFiZWxzPVsiSGFtIiwgIlNwYW0iXSkucGxvdChheD1heGVzWzBdLCBjbWFwPSJCbHVlcyIpDQpheGVzWzBdLnNldF90aXRsZSgiQ29uZnVzaW9uIE1hdHJpeCAtIFJhbmRvbSBGb3Jlc3QiKQ0KDQpDb25mdXNpb25NYXRyaXhEaXNwbGF5KHhnYl9jbSwgZGlzcGxheV9sYWJlbHM9WyJIYW0iLCAiU3BhbSJdKS5wbG90KGF4PWF4ZXNbMV0sIGNtYXA9IkJsdWVzIikNCmF4ZXNbMV0uc2V0X3RpdGxlKCJDb25mdXNpb24gTWF0cml4IC0gWEdCb29zdCIpDQoNCnBsdC5zaG93KCkNCg0KIyBQbG90IFJPQyBDdXJ2ZXMNCnBsdC5maWd1cmUoZmlnc2l6ZT0oOCwgNSkpDQpwbHQucGxvdChmcHJfcmYsIHRwcl9yZiwgbGFiZWw9ZiJSRiBST0MgQ3VydmUgKEFVQyA9IHtyb2NfYXVjX3JmOi4yZn0pIiwgY29sb3I9ImJsdWUiKQ0KcGx0LnBsb3QoZnByX3hnYiwgdHByX3hnYiwgbGFiZWw9ZiJYR0IgUk9DIEN1cnZlIChBVUMgPSB7cm9jX2F1Y194Z2I6LjJmfSkiLCBjb2xvcj0icmVkIikNCnBsdC5wbG90KFswLCAxXSwgWzAsIDFdLCBsaW5lc3R5bGU9Ii0tIiwgY29sb3I9ImdyYXkiKQ0KcGx0LnhsYWJlbCgiRmFsc2UgUG9zaXRpdmUgUmF0ZSIpDQpwbHQueWxhYmVsKCJUcnVlIFBvc2l0aXZlIFJhdGUiKQ0KcGx0LnRpdGxlKCJST0MgQ3VydmU6IFJhbmRvbSBGb3Jlc3QgdnMgWEdCb29zdCIpDQpwbHQubGVnZW5kKCkNCnBsdC5ncmlkKFRydWUpDQpwbHQuc2hvdygpDQoNCnJlc3VsdHMNCiAjIFNlbGYgLXRyYWluaW5nIHdpdGggWEdCb29zdCAoaW1wbGVtZW50IHNlbGYtdHJhaW5pbmcgYnkgaXRlcmF0aXZlbHkgYWRkaW5nIHBzZXVkby1sYWJlbGVkIGhpZ2gtY29uZmlkZW5jZSBwcmVkaWN0aW9ucy4pDQppbXBvcnQgbnVtcHkgYXMgbnANCmltcG9ydCBwYW5kYXMgYXMgcGQNCmZyb20gc2tsZWFybi5tb2RlbF9zZWxlY3Rpb24gaW1wb3J0IHRyYWluX3Rlc3Rfc3BsaXQNCmZyb20gc2tsZWFybi5tZXRyaWNzIGltcG9ydCBjbGFzc2lmaWNhdGlvbl9yZXBvcnQsIGNvbmZ1c2lvbl9tYXRyaXgsIENvbmZ1c2lvbk1hdHJpeERpc3BsYXkNCmZyb20geGdib29zdCBpbXBvcnQgWEdCQ2xhc3NpZmllcg0KZnJvbSBza2xlYXJuLnNlbWlfc3VwZXJ2aXNlZCBpbXBvcnQgU2VsZlRyYWluaW5nQ2xhc3NpZmllcg0KDQojIFNwbGl0IGludG8gbGFiZWxlZCAoODAlKSBhbmQgdW5sYWJlbGVkICgyMCUpIGRhdGENClhfbGFiZWxlZCwgWF91bmxhYmVsZWQsIHlfbGFiZWxlZCwgXyA9IHRyYWluX3Rlc3Rfc3BsaXQoWF9hdWdtZW50ZWQsIHlfYXVnbWVudGVkLCB0ZXN0X3NpemU9MC4yLCByYW5kb21fc3RhdGU9NDIpDQoNCiMgSW5pdGlhbGl6ZSBYR0Jvb3N0IGFzIHRoZSBiYXNlIGNsYXNzaWZpZXINCnhnYl9iYXNlID0gWEdCQ2xhc3NpZmllcihuX2VzdGltYXRvcnM9MjAwLCBtYXhfZGVwdGg9NiwgbGVhcm5pbmdfcmF0ZT0wLjEsIHJhbmRvbV9zdGF0ZT00MiwgZXZhbF9tZXRyaWM9ImxvZ2xvc3MiKQ0KDQojIFVzZSBTZWxmLVRyYWluaW5nIHdyYXBwZXIgdG8gYWRkIHBzZXVkby1sYWJlbGluZw0Kc2VsZl90cmFpbmluZ194Z2IgPSBTZWxmVHJhaW5pbmdDbGFzc2lmaWVyKHhnYl9iYXNlLCBjcml0ZXJpb249InRocmVzaG9sZCIsIHRocmVzaG9sZD0wLjk1LCB2ZXJib3NlPVRydWUpDQpzZWxmX3RyYWluaW5nX3hnYi5maXQoWF9sYWJlbGVkLCB5X2xhYmVsZWQpDQoNCiMgRXZhbHVhdGUgb24gdGhlIG9yaWdpbmFsIHRlc3Qgc2V0DQp5X3ByZWRfc2VsZl90cmFpbmluZyA9IHNlbGZfdHJhaW5pbmdfeGdiLnByZWRpY3QoWF90ZXN0KQ0KDQojIEdlbmVyYXRlIGV2YWx1YXRpb24gbWV0cmljcw0KY2xhc3NfcmVwb3J0X3NlbGZfdHJhaW5pbmcgPSBjbGFzc2lmaWNhdGlvbl9yZXBvcnQoeV90ZXN0LCB5X3ByZWRfc2VsZl90cmFpbmluZykNCmNvbmZfbWF0cml4X3NlbGZfdHJhaW5pbmcgPSBjb25mdXNpb25fbWF0cml4KHlfdGVzdCwgeV9wcmVkX3NlbGZfdHJhaW5pbmcpDQoNCiMgRGlzcGxheSByZXN1bHRzDQpkaXNwID0gQ29uZnVzaW9uTWF0cml4RGlzcGxheShjb25mX21hdHJpeF9zZWxmX3RyYWluaW5nLCBkaXNwbGF5X2xhYmVscz1bIkhhbSIsICJTcGFtIl0pDQpkaXNwLnBsb3QoKQ0KcGx0LnRpdGxlKCJDb25mdXNpb24gTWF0cml4OiBTZWxmLVRyYWluaW5nIFhHQm9vc3QiKQ0KcGx0LnNob3coKQ0KDQpwcmludCgiXG4gQ2xhc3NpZmljYXRpb24gUmVwb3J0OiBTZWxmLVRyYWluaW5nIFhHQm9vc3RcbiIsIGNsYXNzX3JlcG9ydF9zZWxmX3RyYWluaW5nKQ0KIGZyb20gc2tsZWFybi5zZW1pX3N1cGVydmlzZWQgaW1wb3J0IExhYmVsU3ByZWFkaW5nDQoNCiMgQXNzdW1lIHdlIGhhdmUgc29tZSB1bmxhYmVsZWQgZW1haWwgZGF0YSAoZS5nLiwgbmV3IGluY29taW5nIGVtYWlscykNCnVubGFiZWxlZF9kYXRhID0gWF90ZXN0LmNvcHkoKQ0KdW5sYWJlbGVkX2xhYmVscyA9IG5wLmZ1bGwoWF90ZXN0LnNoYXBlWzBdLCAtMSkgICMgLTEgbWVhbnMgdW5sYWJlbGVkDQojIENvbWJpbmUgbGFiZWxlZCBhbmQgdW5sYWJlbGVkIGRhdGENClhfY29tYmluZWQgPSBucC52c3RhY2soKFhfdHJhaW4udG9hcnJheSgpLCB1bmxhYmVsZWRfZGF0YS50b2FycmF5KCkpKQ0KeV9jb21iaW5lZCA9IG5wLmhzdGFjaygoeV90cmFpbiwgdW5sYWJlbGVkX2xhYmVscykpDQoNCiMgQXBwbHkgU2VtaS1TdXBlcnZpc2VkIExlYXJuaW5nIChMYWJlbCBTcHJlYWRpbmcpDQpzZW1pX3N1cGVydmlzZWRfbW9kZWwgPSBMYWJlbFNwcmVhZGluZyhrZXJuZWw9InJiZiIsIGFscGhhPTAuMikNCnNlbWlfc3VwZXJ2aXNlZF9tb2RlbC5maXQoWF9jb21iaW5lZCwgeV9jb21iaW5lZCkNCg0KIyBQcmVkaWN0IHVzaW5nIHRoZSBzZW1pLXN1cGVydmlzZWQgbW9kZWwNCnlfcHJlZF9zZW1pID0gc2VtaV9zdXBlcnZpc2VkX21vZGVsLnByZWRpY3QoWF90ZXN0KQ0KDQojIEV2YWx1YXRlIHBlcmZvcm1hbmNlDQpzZW1pX3JlcG9ydCA9IGNsYXNzaWZpY2F0aW9uX3JlcG9ydCh5X3Rlc3QsIHlfcHJlZF9zZW1pLCB0YXJnZXRfbmFtZXM9WyJIYW0iLCAiU3BhbSJdKQ0KcHJpbnQoIlxuU2VtaS1TdXBlcnZpc2VkIExlYXJuaW5nIENsYXNzaWZpY2F0aW9uIFJlcG9ydDpcbiIsIHNlbWlfcmVwb3J0KQ0KIGZyb20gc2tsZWFybi5tb2RlbF9zZWxlY3Rpb24gaW1wb3J0IEdyaWRTZWFyY2hDVg0KDQojIERlZmluZSBoeXBlcnBhcmFtZXRlciBncmlkDQp4Z2JfcGFyYW1zID0gew0KICAgICJuX2VzdGltYXRvcnMiOiBbNTAsIDEwMCwgMjAwXSwNCiAgICAibWF4X2RlcHRoIjogWzMsIDYsIDldLA0KICAgICJsZWFybmluZ19yYXRlIjogWzAuMDEsIDAuMSwgMC4zXQ0KfQ0KDQojIFBlcmZvcm0gR3JpZCBTZWFyY2gNCmdyaWRfc2VhcmNoX3hnYiA9IEdyaWRTZWFyY2hDVihYR0JDbGFzc2lmaWVyKHVzZV9sYWJlbF9lbmNvZGVyPUZhbHNlLCBldmFsX21ldHJpYz0ibG9nbG9zcyIsIHJhbmRvbV9zdGF0ZT00MiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeGdiX3BhcmFtcywgY3Y9Mywgc2NvcmluZz0iYWNjdXJhY3kiLCBuX2pvYnM9LTEpDQpncmlkX3NlYXJjaF94Z2IuZml0KFhfdHJhaW4sIHlfdHJhaW4pDQoNCiMgUHJpbnQgYmVzdCBwYXJhbWV0ZXJzDQpwcmludCgiQmVzdCBYR0Jvb3N0IFBhcmFtZXRlcnM6IiwgZ3JpZF9zZWFyY2hfeGdiLmJlc3RfcGFyYW1zXykNCg0KIyBFdmFsdWF0ZSB0aGUgb3B0aW1pemVkIFhHQm9vc3QgbW9kZWwNCmJlc3RfeGdiID0gZ3JpZF9zZWFyY2hfeGdiLmJlc3RfZXN0aW1hdG9yXw0KeGdiX2ZpbmFsX3ByZWQgPSBiZXN0X3hnYi5wcmVkaWN0KFhfdGVzdCkNCg0KeGdiX2ZpbmFsX3JlcG9ydCA9IGNsYXNzaWZpY2F0aW9uX3JlcG9ydCh5X3Rlc3QsIHhnYl9maW5hbF9wcmVkLCB0YXJnZXRfbmFtZXM9WyJIYW0iLCAiU3BhbSJdKQ0KcHJpbnQoIlxuT3B0aW1pemVkIFhHQm9vc3QgQ2xhc3NpZmljYXRpb24gUmVwb3J0OlxuIiwgeGdiX2ZpbmFsX3JlcG9ydCkNCiAgIyBTYXZlIEZpbmFsIE1vZGVsIFBhcmFtZXRlcnMgJiBCZXN0IEh5cGVycGFyYW1ldGVycw0KDQogaW1wb3J0IGpvYmxpYg0KDQojIFNhdmUgdGhlIGZpbmFsIHNlbGYtdHJhaW5pbmcgWEdCb29zdCBtb2RlbA0Kam9ibGliLmR1bXAoc2VsZl90cmFpbmluZ194Z2IsICJzZWxmX3RyYWluaW5nX3hnYl9tb2RlbC5wa2wiKQ0KDQojIFNhdmUgdGhlIHN0YW5kYXJkIFhHQm9vc3QgbW9kZWwgYXMgYSBiYWNrdXANCmpvYmxpYi5kdW1wKHhnYl9maW5hbCwgInhnYl9iYWNrdXBfbW9kZWwucGtsIikNCg0KcHJpbnQoIk1vZGVscyBzYXZlZCBzdWNjZXNzZnVsbHkhIikNCg0KDQojIFNhdmUgdGhlIEJlc3QgSHlwZXJwYXJhbWV0ZXJzDQojIFN0b3JlIGh5cGVycGFyYW1ldGVycyBpbiBhIGRpY3Rpb25hcnkNCmJlc3RfaHlwZXJwYXJhbXMgPSB7DQogICAgIlNlbGYtVHJhaW5pbmcgWEdCb29zdCI6IHsNCiAgICAgICAgIm5fZXN0aW1hdG9ycyI6IDIwMCwNCiAgICAgICAgIm1heF9kZXB0aCI6IDYsDQogICAgICAgICJsZWFybmluZ19yYXRlIjogMC4xLA0KICAgICAgICAiZXZhbF9tZXRyaWMiOiAibG9nbG9zcyIsDQogICAgICAgICJ0aHJlc2hvbGQiOiAwLjk1LCAgIyBQc2V1ZG8tbGFiZWxpbmcgY29uZmlkZW5jZSB0aHJlc2hvbGQNCiAgICB9LA0KICAgICJYR0Jvb3N0IEJhY2t1cCI6IHsNCiAgICAgICAgIm5fZXN0aW1hdG9ycyI6IDIwMCwNCiAgICAgICAgIm1heF9kZXB0aCI6IDYsDQogICAgICAgICJsZWFybmluZ19yYXRlIjogMC4xLA0KICAgICAgICAiZXZhbF9tZXRyaWMiOiAibG9nbG9zcyIsDQogICAgfQ0KfQ0KDQojIFNhdmUgaHlwZXJwYXJhbWV0ZXJzIGFzIEpTT04NCmltcG9ydCBqc29uDQp3aXRoIG9wZW4oImJlc3RfaHlwZXJwYXJhbWV0ZXJzLmpzb24iLCAidyIpIGFzIGY6DQogICAganNvbi5kdW1wKGJlc3RfaHlwZXJwYXJhbXMsIGYsIGluZGVudD00KQ0KDQpwcmludCgiQmVzdCBoeXBlcnBhcmFtZXRlcnMgc2F2ZWQhIikNCiAjIFByZXBhcmUgRGVwbG95bWVudCBQbGFuDQpmcm9tIGZsYXNrIGltcG9ydCBGbGFzaywgcmVxdWVzdCwganNvbmlmeQ0KaW1wb3J0IGpvYmxpYg0KaW1wb3J0IG51bXB5IGFzIG5wDQoNCiMgTG9hZCB0aGUgc2F2ZWQgbW9kZWwNCm1vZGVsID0gam9ibGliLmxvYWQoInNlbGZfdHJhaW5pbmdfeGdiX21vZGVsLnBrbCIpDQoNCmFwcCA9IEZsYXNrKF9fbmFtZV9fKQ0KDQpAYXBwLnJvdXRlKCIvcHJlZGljdCIsIG1ldGhvZHM9WyJQT1NUIl0pDQpkZWYgcHJlZGljdCgpOg0KICAgIGRhdGEgPSByZXF1ZXN0Lmpzb24NCiAgICBYX2lucHV0ID0gbnAuYXJyYXkoZGF0YVsiZmVhdHVyZXMiXSkucmVzaGFwZSgxLCAtMSkNCiAgICBwcmVkaWN0aW9uID0gbW9kZWwucHJlZGljdChYX2lucHV0KQ0KICAgIHJldHVybiBqc29uaWZ5KHsicHJlZGljdGlvbiI6IGludChwcmVkaWN0aW9uWzBdKX0pDQoNCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6DQogICAgYXBwLnJ1bihwb3J0PTUwMDAsIGRlYnVnPVRydWUpDQpgYGANCg==