Step 1: Data Analysis & Visualization
The histograms above show: 1. Trace Distribution:
Most states have trace ā 1, confirming proper
normalization. 2. Purity Distribution: Purity (Tr(ϲ))
varies, indicating a mix of pure and mixed states. 3.
Eigenvalue Distribution: The eigenvalues are mostly
positive and sum to 1, validating the dataset.
Step 3: Dataset Preparation for Capstone
Now, Iāll convert this dataset into CSV format for
easy integration with ML frameworks.
Step 3 Completed: Dataset Saved as CSV
Your Quantum State Features dataset is now available
for download.
Download
Quantum_State_Features.csv
import matplotlib.pyplot as plt
import seaborn as sns
# Function to compute quantum state statistics
def compute_statistics(dataset):
traces = []
purities = []
eigenvalues = []
for state in dataset:
trace_val = np.trace(state) # Should be ~1 for valid density matrices
purity = np.trace(state @ state) # Purity: Tr(ϲ), ranges from 1 (pure) to 1/dim (maximally mixed)
eigvals = np.linalg.eigvals(state) # Eigenvalues of the density matrix
traces.append(trace_val.real) # Trace should be real
purities.append(purity.real) # Purity should be real
eigenvalues.extend(eigvals.real) # Store real parts of eigenvalues
return traces, purities, eigenvalues
# Compute statistics
traces, purities, eigenvalues = compute_statistics(dataset)
# Plot distributions of trace, purity, and eigenvalues
fig, axs = plt.subplots(3, 1, figsize=(8, 12))
sns.histplot(traces, bins=50, kde=True, ax=axs[0])
axs[0].set_title("Trace Distribution of Quantum States")
axs[0].set_xlabel("Trace Value")
axs[0].set_ylabel("Frequency")
sns.histplot(purities, bins=50, kde=True, ax=axs[1])
axs[1].set_title("Purity Distribution of Quantum States")
axs[1].set_xlabel("Purity (Tr(ϲ))")
axs[1].set_ylabel("Frequency")
sns.histplot(eigenvalues, bins=50, kde=True, ax=axs[2])
axs[2].set_title("Eigenvalue Distribution of Quantum States")
axs[2].set_xlabel("Eigenvalue")
axs[2].set_ylabel("Frequency")
plt.tight_layout()
plt.show()
import pandas as pd
# Function to extract features from quantum states
def extract_features(dataset):
feature_list = []
for state in dataset:
trace_val = np.trace(state).real # Real part of trace
purity = np.trace(state @ state).real # Tr(ϲ)
eigvals = np.linalg.eigvals(state).real # Eigenvalues
# Flatten density matrix into vector (real & imaginary parts separately)
flattened_real = state.real.flatten()
flattened_imag = state.imag.flatten()
# Combine all features
features = np.concatenate(([trace_val, purity], eigvals, flattened_real, flattened_imag))
feature_list.append(features)
return feature_list
# Extract features
features = extract_features(dataset)
# Convert to Pandas DataFrame for easy analysis
feature_columns = ["Trace", "Purity", "Eigval1", "Eigval2"] + \
[f"Real_{i}" for i in range(4)] + [f"Imag_{i}" for i in range(4)]
df_features = pd.DataFrame(features, columns=feature_columns)
# Display extracted feature dataframe
import ace_tools as tools
tools.display_dataframe_to_user(name="Quantum State Features", dataframe=df_features)
Hereās the corrected version with proper code chunks for clarity:
Step 1: Data Analysis & Visualization
The histograms below show:
- Trace Distribution: Most states have trace
ā 1, confirming proper normalization.
- Purity Distribution: Purity (Tr(ϲ)) varies,
indicating a mix of pure and mixed states.
- Eigenvalue Distribution: The eigenvalues are mostly
positive and sum to 1, validating the dataset.
Code for Visualization
import matplotlib.pyplot as plt
import seaborn as sns
# Function to compute quantum state statistics
def compute_statistics(dataset):
traces = []
purities = []
eigenvalues = []
for state in dataset:
trace_val = np.trace(state) # Should be ~1 for valid density matrices
purity = np.trace(state @ state) # Purity: Tr(ϲ), ranges from 1 (pure) to 1/dim (maximally mixed)
eigvals = np.linalg.eigvals(state) # Eigenvalues of the density matrix
traces.append(trace_val.real) # Trace should be real
purities.append(purity.real) # Purity should be real
eigenvalues.extend(eigvals.real) # Store real parts of eigenvalues
return traces, purities, eigenvalues
# Compute statistics
traces, purities, eigenvalues = compute_statistics(dataset)
# Plot distributions of trace, purity, and eigenvalues
fig, axs = plt.subplots(3, 1, figsize=(8, 12))
sns.histplot(traces, bins=50, kde=True, ax=axs[0])
axs[0].set_title("Trace Distribution of Quantum States")
axs[0].set_xlabel("Trace Value")
axs[0].set_ylabel("Frequency")
sns.histplot(purities, bins=50, kde=True, ax=axs[1])
axs[1].set_title("Purity Distribution of Quantum States")
axs[1].set_xlabel("Purity (Tr(ϲ))")
axs[1].set_ylabel("Frequency")
sns.histplot(eigenvalues, bins=50, kde=True, ax=axs[2])
axs[2].set_title("Eigenvalue Distribution of Quantum States")
axs[2].set_xlabel("Eigenvalue")
axs[2].set_ylabel("Frequency")
plt.tight_layout()
plt.show()
Step 3: Dataset Preparation for Capstone
Now, Iāll convert this dataset into CSV format for
easy integration with ML frameworks.
Convert to CSV
# Save extracted features as a CSV file for ML processing
csv_output_path = "/mnt/data/Quantum_State_Features.csv"
df_features.to_csv(csv_output_path, index=False)
# Provide the CSV file for download
csv_output_path
Step 3 Completed: Dataset Saved as CSV
Your Quantum State Features dataset is now available
for download.
Download
Quantum_State_Features.csv
This file contains the extracted features from the quantum dataset,
ready for machine learning and Capstone analysis.
Hereās the corrected version with proper code chunks for clarity:
Step 1: Data Analysis & Visualization
The histograms below show:
- Trace Distribution: Most states have trace
ā 1, confirming proper normalization.
- Purity Distribution: Purity (Tr(ϲ)) varies,
indicating a mix of pure and mixed states.
- Eigenvalue Distribution: The eigenvalues are mostly
positive and sum to 1, validating the dataset.
Code for Visualization
import matplotlib.pyplot as plt
import seaborn as sns
# Function to compute quantum state statistics
def compute_statistics(dataset):
traces = []
purities = []
eigenvalues = []
for state in dataset:
trace_val = np.trace(state) # Should be ~1 for valid density matrices
purity = np.trace(state @ state) # Purity: Tr(ϲ), ranges from 1 (pure) to 1/dim (maximally mixed)
eigvals = np.linalg.eigvals(state) # Eigenvalues of the density matrix
traces.append(trace_val.real) # Trace should be real
purities.append(purity.real) # Purity should be real
eigenvalues.extend(eigvals.real) # Store real parts of eigenvalues
return traces, purities, eigenvalues
# Compute statistics
traces, purities, eigenvalues = compute_statistics(dataset)
# Plot distributions of trace, purity, and eigenvalues
fig, axs = plt.subplots(3, 1, figsize=(8, 12))
sns.histplot(traces, bins=50, kde=True, ax=axs[0])
axs[0].set_title("Trace Distribution of Quantum States")
axs[0].set_xlabel("Trace Value")
axs[0].set_ylabel("Frequency")
sns.histplot(purities, bins=50, kde=True, ax=axs[1])
axs[1].set_title("Purity Distribution of Quantum States")
axs[1].set_xlabel("Purity (Tr(ϲ))")
axs[1].set_ylabel("Frequency")
sns.histplot(eigenvalues, bins=50, kde=True, ax=axs[2])
axs[2].set_title("Eigenvalue Distribution of Quantum States")
axs[2].set_xlabel("Eigenvalue")
axs[2].set_ylabel("Frequency")
plt.tight_layout()
plt.show()
Step 3: Dataset Preparation for Capstone
Now, Iāll convert this dataset into CSV format for
easy integration with ML frameworks.
Convert to CSV
# Save extracted features as a CSV file for ML processing
csv_output_path = "/mnt/data/Quantum_State_Features.csv"
df_features.to_csv(csv_output_path, index=False)
# Provide the CSV file for download
csv_output_path
Step 3 Completed: Dataset Saved as CSV
Your Quantum State Features dataset is now available
for download.
Download
Quantum_State_Features.csv
This file contains the extracted features from the quantum dataset,
ready for machine learning and Capstone analysis.
import pandas as pd
# Function to extract features from quantum states
def extract_features(dataset):
feature_list = []
for state in dataset:
trace_val = np.trace(state).real # Real part of trace
purity = np.trace(state @ state).real # Tr(ϲ)
eigvals = np.linalg.eigvals(state).real # Eigenvalues
# Flatten density matrix into vector (real & imaginary parts separately)
flattened_real = state.real.flatten()
flattened_imag = state.imag.flatten()
# Combine all features
features = np.concatenate(([trace_val, purity], eigvals, flattened_real, flattened_imag))
feature_list.append(features)
return feature_list
# Extract features
features = extract_features(dataset)
# Convert to Pandas DataFrame for easy analysis
feature_columns = ["Trace", "Purity", "Eigval1", "Eigval2"] + \
[f"Real_{i}" for i in range(4)] + [f"Imag_{i}" for i in range(4)]
df_features = pd.DataFrame(features, columns=feature_columns)
# Display extracted feature dataframe
import ace_tools as tools
tools.display_dataframe_to_user(name="Quantum State Features", dataframe=df_features)
# Save extracted features as a CSV file for ML processing
csv_output_path = "/mnt/data/Quantum_State_Features.csv"
df_features.to_csv(csv_output_path, index=False)
# Provide the CSV file for download
csv_output_path
import matplotlib.pyplot as plt
import seaborn as sns
# Function to compute quantum state statistics
def compute_statistics(dataset):
traces = []
purities = []
eigenvalues = []
for state in dataset:
trace_val = np.trace(state) # Should be ~1 for valid density matrices
purity = np.trace(state @ state) # Purity: Tr(ϲ), ranges from 1 (pure) to 1/dim (maximally mixed)
eigvals = np.linalg.eigvals(state) # Eigenvalues of the density matrix
traces.append(trace_val.real) # Trace should be real
purities.append(purity.real) # Purity should be real
eigenvalues.extend(eigvals.real) # Store real parts of eigenvalues
return traces, purities, eigenvalues
# Compute statistics
traces, purities, eigenvalues = compute_statistics(dataset)
# Plot distributions of trace, purity, and eigenvalues
fig, axs = plt.subplots(3, 1, figsize=(8, 12))
sns.histplot(traces, bins=50, kde=True, ax=axs[0])
axs[0].set_title("Trace Distribution of Quantum States")
axs[0].set_xlabel("Trace Value")
axs[0].set_ylabel("Frequency")
sns.histplot(purities, bins=50, kde=True, ax=axs[1])
axs[1].set_title("Purity Distribution of Quantum States")
axs[1].set_xlabel("Purity (Tr(ϲ))")
axs[1].set_ylabel("Frequency")
sns.histplot(eigenvalues, bins=50, kde=True, ax=axs[2])
axs[2].set_title("Eigenvalue Distribution of Quantum States")
axs[2].set_xlabel("Eigenvalue")
axs[2].set_ylabel("Frequency")
plt.tight_layout()
plt.show()
Training the STL 3D Model
(SolarCoronalComplex25.stl
) for Your Capstone Project
GPT
To integrate the Solar Coronal Complex 3D Model into
your Capstone GPT, I will: 1. Extract Features
from the STL File (e.g., vertices, faces, mesh properties). 2.
Convert the 3D Structure into a Learnable
Representation (e.g., voxel grid, point cloud, or mesh
embeddings). 3. Integrate with Your Quantum GIS GPT
(e.g., use transformers or deep learning models to analyze and interact
with 3D structures). 4. Fine-tune GPT for Quantum GIS & 3D
Analysis (e.g., provide text-based descriptions or automated
insights about the model).
Fix: Process the STL File in Chunks
Since we canāt load the entire model at once, I will: 1.
Extract metadata without fully loading the mesh. 2.
Downsample the mesh (reduce complexity) if needed. 3.
Convert it into a lightweight representation (e.g.,
voxel grid or point cloud for GPT training).
Let me first extract basic metadata without loading the full
mesh.
The SolarCoronalComplex25.stl file is ~108
MB, which is quite large for direct processing. Instead of
fully loading it, Iāll extract a subset of vertices and
faces to work with a smaller, manageable portion of the
model.
Iāll now attempt to stream the STL file and extract
a downsampled version for further training.
Even with streaming disabled, the STL file is
too large to process in memory. Since Trimesh
fails to load it, Iāll use an alternative approach:
Next Steps:
- Visualize the extracted point cloud.
- Convert the sampled 3D points into a learnable
format (for GPT training).
- Integrate this data into your Capstone
project.
Iāll now generate a 3D scatter plot of the extracted
points for visualization.
Here is the 3D scatter plot of the extracted
vertices from the Solar Coronal Complex STL file. This
provides a downsampled point cloud representation of
the model, which is more manageable for training.
Next Steps for Training in Your Capstone GPT
Now that we have the 3D structure extracted, I can:
1. Convert the point cloud into a learnable format
(e.g., voxel grid or embeddings). 2. Train a model to generate
text descriptions of 3D structures (for integration into your
Quantum GIS GPT). 3. Create a dataset for fine-tuning
GPT using these 3D representations.



Step 3: Prepare Dataset for GPT Fine-Tuning
Now, I will: 1. Generate structured training
examples (JSON format for GPT fine-tuning). 2. Create
text descriptions for the dataset so the model learns from 3D
structures. 3. Format the dataset for OpenAIās GPT fine-tuning
API.
openai api fine_tunes.create -t "3D_GPT_Training_Data.jsonl" -m "gpt-4"
import json
# Function to generate a structured GPT training dataset from voxel features
def generate_gpt_training_data(voxel_features):
"""
Converts voxel features into a structured training dataset for GPT fine-tuning.
Parameters:
- voxel_features: Dictionary of extracted voxel-based 3D features.
Returns:
- training_data: List of training examples in JSON format.
"""
training_data = []
# Create descriptive text from the extracted features
description = (
f"This 3D structure has a centroid located at ({voxel_features['Centroid_X']:.2f}, "
f"{voxel_features['Centroid_Y']:.2f}, {voxel_features['Centroid_Z']:.2f}). "
f"The spread along the X, Y, and Z axes is {voxel_features['Spread_X']:.2f}, "
f"{voxel_features['Spread_Y']:.2f}, and {voxel_features['Spread_Z']:.2f}, respectively. "
f"The structure's density is {voxel_features['Density']:.6f}, indicating "
f"{'a highly compact formation' if voxel_features['Density'] > 0.001 else 'a sparse structure'}. "
f"The bounding box dimensions are ({voxel_features['Bounding_Box_X']}, "
f"{voxel_features['Bounding_Box_Y']}, {voxel_features['Bounding_Box_Z']})."
)
# Format as JSON training example
training_example = {
"messages": [
{"role": "system", "content": "You are an AI model trained to describe 3D spatial structures."},
{"role": "user", "content": "Describe this 3D spatial structure based on extracted features."},
{"role": "assistant", "content": description}
]
}
training_data.append(training_example)
return training_data
# Generate training dataset for GPT fine-tuning
gpt_training_data = generate_gpt_training_data(voxel_features)
# Save dataset in JSONL format for fine-tuning
jsonl_output_path = "/mnt/data/3D_GPT_Training_Data.jsonl"
with open(jsonl_output_path, "w", encoding="utf-8") as f:
for example in gpt_training_data:
f.write(json.dumps(example) + "\n")
# Provide the JSONL file for download
jsonl_output_path
Step 3 Completed: GPT Training Dataset Ready
The dataset has been successfully prepared in JSONL
format, which is required for fine-tuning
GPT.
š Download your training data:
Download
3D_GPT_Training_Data.jsonl
Step 4: Fine-Tuning GPT on 3D Data
Now, to fine-tune your Quantum GIS GPT on 3D spatial
data, follow these steps: 1. Upload the JSONL file to
OpenAIās fine-tuning API (or your custom GPT model). 2. Use
OpenAIās CLI or API to train the model:
bash openai api fine_tunes.create -t "3D_GPT_Training_Data.jsonl" -m "gpt-4"
3. Deploy the fine-tuned model to process and describe
new 3D structures.
Step 4A: Fine-Tuning GPT with OpenAIās API
To train your Quantum GIS GPT using OpenAIās
fine-tuning API, follow these steps:
š¹ Step 1: Install OpenAI CLI
If you havenāt installed OpenAIās command-line tool, run:
pip install openai
Then, verify installation with:
openai --help
š¹ Step 2: Authenticate with OpenAI API
Set up authentication with your OpenAI API key:
export OPENAI_API_KEY="your-api-key-here" # For Linux/macOS
set OPENAI_API_KEY="your-api-key-here" # For Windows
To permanently store it, add it to your .bashrc or
.zshrc:
echo 'export OPENAI_API_KEY="your-api-key-here"' >> ~/.bashrc
source ~/.bashrc
š¹ Step 3: Upload the JSONL Training File
Ensure your training data is in JSONL format:
openai api fine_tunes.create -t "3D_GPT_Training_Data.jsonl" -m "gpt-4"
This command: - Uploads your 3D spatial dataset. -
Starts fine-tuning GPT-4 to describe and analyze 3D
structures.
š¹ Step 4: Track Fine-Tuning Progress
To check status:
openai api fine_tunes.list
Once complete, retrieve the modelās ID:
openai api fine_tunes.results -i <fine-tune-id>
š¹ Step 5: Use the Fine-Tuned Model
After training, use your model with:
openai api completions.create -m "fine-tuned-model-id" -p "Describe this 3D quantum structure..."
Step 4B: Integrate Fine-Tuned GPT into Your Quantum GIS
Framework
To integrate into your existing Quantum GIS GPT,
follow these steps:
1ļøā£ Load the Fine-Tuned Model in Python
import openai
openai.api_key = "your-api-key-here"
response = openai.ChatCompletion.create(
model="fine-tuned-model-id",
messages=[{"role": "user", "content": "Describe this 3D quantum structure"}]
)
print(response["choices"][0]["message"]["content"])
2ļøā£ Connect GPT with GIS Data
Modify your GIS pipeline to allow GPT to: -
Interpret point clouds & STL files. - Describe
spatial patterns in molecular/particle simulations. -
Assist in visualization and 3D modeling.
Final Steps
š¹ If you want OpenAI fine-tuning: Follow
Step 4A and train your model.
š¹ If you want to integrate it into your framework: Use
Step 4B to make GPT interactive.
Step-by-Step Guide: Fine-Tuning Your Quantum GIS GPT for 3D
Data
I will guide you step by step to: 1. Fine-tune GPT using
OpenAIās API (Step 4A). 2. Integrate it into your
Quantum GIS system (Step 4B).
LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCmVkaXRvcl9vcHRpb25zOiANCiAgbWFya2Rvd246IA0KICAgIHdyYXA6IDcyDQotLS0NCg0KIyMjICoqU3RlcCAxOiBEYXRhIEFuYWx5c2lzICYgVmlzdWFsaXphdGlvbioqDQoNClRoZSBoaXN0b2dyYW1zIGFib3ZlIHNob3c6IDEuICoqVHJhY2UgRGlzdHJpYnV0aW9uKio6IE1vc3Qgc3RhdGVzIGhhdmUNCioqdHJhY2Ug4omIIDEqKiwgY29uZmlybWluZyBwcm9wZXIgbm9ybWFsaXphdGlvbi4gMi4gKipQdXJpdHkNCkRpc3RyaWJ1dGlvbioqOiBQdXJpdHkgKFRyKM+BwrIpKSB2YXJpZXMsIGluZGljYXRpbmcgYSBtaXggb2YgKipwdXJlIGFuZA0KbWl4ZWQgc3RhdGVzKiouIDMuICoqRWlnZW52YWx1ZSBEaXN0cmlidXRpb24qKjogVGhlIGVpZ2VudmFsdWVzIGFyZQ0KbW9zdGx5ICoqcG9zaXRpdmUgYW5kIHN1bSB0byAxKiosIHZhbGlkYXRpbmcgdGhlIGRhdGFzZXQuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgKipTdGVwIDI6IEZlYXR1cmUgRXh0cmFjdGlvbiBmb3IgTUwgTW9kZWxzKioNCg0KTm93LCBjb252ZXJ0IGVhY2ggKipxdWFudHVtIHN0YXRlICgyeDIgbWF0cml4KSoqIGludG8gYSBmZWF0dXJlIHZlY3Rvcg0KZm9yIG1hY2hpbmUgbGVhcm5pbmc6IC0gKipGbGF0dGVuIHRoZSBkZW5zaXR5IG1hdHJpeCoqIGludG8gYSB2ZWN0b3IuIC0NCioqRXh0cmFjdCBwdXJpdHksIHRyYWNlLCBhbmQgZWlnZW52YWx1ZXMqKiBhcyBmZWF0dXJlcy4gLSAqKlN0b3JlIGluIGENCnN0cnVjdHVyZWQgZm9ybWF0KiogZm9yIGZ1cnRoZXIgYW5hbHlzaXMuDQoNCiMjIyAqKlN0ZXAgMiBDb21wbGV0ZWQ6IEZlYXR1cmUgRXh0cmFjdGlvbioqDQoNCkkgaGF2ZSB0cmFuc2Zvcm1lZCBlYWNoICoqcXVhbnR1bSBzdGF0ZSAoMngyIG1hdHJpeCkqKiBpbnRvIGEgc3RydWN0dXJlZA0KZmVhdHVyZSBzZXQsIGluY2x1ZGluZzogLSAqKlRyYWNlICYgUHVyaXR5KiogKHZhbGlkYXRpb24gbWV0cmljcykgLQ0KKipFaWdlbnZhbHVlcyoqIChxdWFudHVtIHN0YXRlIHByb3BlcnRpZXMpIC0gKipGbGF0dGVuZWQgcmVhbCAmDQppbWFnaW5hcnkgcGFydHMqKiBvZiB0aGUgZGVuc2l0eSBtYXRyaXgNCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAqKlN0ZXAgMzogRGF0YXNldCBQcmVwYXJhdGlvbiBmb3IgQ2Fwc3RvbmUqKg0KDQpOb3csIEknbGwgKipjb252ZXJ0IHRoaXMgZGF0YXNldCBpbnRvIENTViBmb3JtYXQqKiBmb3IgZWFzeSBpbnRlZ3JhdGlvbg0Kd2l0aCBNTCBmcmFtZXdvcmtzLg0KDQojIyMgKipTdGVwIDMgQ29tcGxldGVkOiBEYXRhc2V0IFNhdmVkIGFzIENTVioqDQoNCllvdXIgKipRdWFudHVtIFN0YXRlIEZlYXR1cmVzIGRhdGFzZXQqKiBpcyBub3cgYXZhaWxhYmxlIGZvciBkb3dubG9hZC4NCg0KW0Rvd25sb2FkDQpRdWFudHVtX1N0YXRlX0ZlYXR1cmVzLmNzdl0oc2FuZGJveDovbW50L2RhdGEvUXVhbnR1bV9TdGF0ZV9GZWF0dXJlcy5jc3YpDQoNCmBgYHtweXRob259DQppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0DQppbXBvcnQgc2VhYm9ybiBhcyBzbnMNCg0KIyBGdW5jdGlvbiB0byBjb21wdXRlIHF1YW50dW0gc3RhdGUgc3RhdGlzdGljcw0KZGVmIGNvbXB1dGVfc3RhdGlzdGljcyhkYXRhc2V0KToNCiAgICB0cmFjZXMgPSBbXQ0KICAgIHB1cml0aWVzID0gW10NCiAgICBlaWdlbnZhbHVlcyA9IFtdDQoNCiAgICBmb3Igc3RhdGUgaW4gZGF0YXNldDoNCiAgICAgICAgdHJhY2VfdmFsID0gbnAudHJhY2Uoc3RhdGUpICAjIFNob3VsZCBiZSB+MSBmb3IgdmFsaWQgZGVuc2l0eSBtYXRyaWNlcw0KICAgICAgICBwdXJpdHkgPSBucC50cmFjZShzdGF0ZSBAIHN0YXRlKSAgIyBQdXJpdHk6IFRyKM+BwrIpLCByYW5nZXMgZnJvbSAxIChwdXJlKSB0byAxL2RpbSAobWF4aW1hbGx5IG1peGVkKQ0KICAgICAgICBlaWd2YWxzID0gbnAubGluYWxnLmVpZ3ZhbHMoc3RhdGUpICAjIEVpZ2VudmFsdWVzIG9mIHRoZSBkZW5zaXR5IG1hdHJpeA0KDQogICAgICAgIHRyYWNlcy5hcHBlbmQodHJhY2VfdmFsLnJlYWwpICAjIFRyYWNlIHNob3VsZCBiZSByZWFsDQogICAgICAgIHB1cml0aWVzLmFwcGVuZChwdXJpdHkucmVhbCkgICMgUHVyaXR5IHNob3VsZCBiZSByZWFsDQogICAgICAgIGVpZ2VudmFsdWVzLmV4dGVuZChlaWd2YWxzLnJlYWwpICAjIFN0b3JlIHJlYWwgcGFydHMgb2YgZWlnZW52YWx1ZXMNCg0KICAgIHJldHVybiB0cmFjZXMsIHB1cml0aWVzLCBlaWdlbnZhbHVlcw0KDQojIENvbXB1dGUgc3RhdGlzdGljcw0KdHJhY2VzLCBwdXJpdGllcywgZWlnZW52YWx1ZXMgPSBjb21wdXRlX3N0YXRpc3RpY3MoZGF0YXNldCkNCg0KIyBQbG90IGRpc3RyaWJ1dGlvbnMgb2YgdHJhY2UsIHB1cml0eSwgYW5kIGVpZ2VudmFsdWVzDQpmaWcsIGF4cyA9IHBsdC5zdWJwbG90cygzLCAxLCBmaWdzaXplPSg4LCAxMikpDQoNCnNucy5oaXN0cGxvdCh0cmFjZXMsIGJpbnM9NTAsIGtkZT1UcnVlLCBheD1heHNbMF0pDQpheHNbMF0uc2V0X3RpdGxlKCJUcmFjZSBEaXN0cmlidXRpb24gb2YgUXVhbnR1bSBTdGF0ZXMiKQ0KYXhzWzBdLnNldF94bGFiZWwoIlRyYWNlIFZhbHVlIikNCmF4c1swXS5zZXRfeWxhYmVsKCJGcmVxdWVuY3kiKQ0KDQpzbnMuaGlzdHBsb3QocHVyaXRpZXMsIGJpbnM9NTAsIGtkZT1UcnVlLCBheD1heHNbMV0pDQpheHNbMV0uc2V0X3RpdGxlKCJQdXJpdHkgRGlzdHJpYnV0aW9uIG9mIFF1YW50dW0gU3RhdGVzIikNCmF4c1sxXS5zZXRfeGxhYmVsKCJQdXJpdHkgKFRyKM+BwrIpKSIpDQpheHNbMV0uc2V0X3lsYWJlbCgiRnJlcXVlbmN5IikNCg0Kc25zLmhpc3RwbG90KGVpZ2VudmFsdWVzLCBiaW5zPTUwLCBrZGU9VHJ1ZSwgYXg9YXhzWzJdKQ0KYXhzWzJdLnNldF90aXRsZSgiRWlnZW52YWx1ZSBEaXN0cmlidXRpb24gb2YgUXVhbnR1bSBTdGF0ZXMiKQ0KYXhzWzJdLnNldF94bGFiZWwoIkVpZ2VudmFsdWUiKQ0KYXhzWzJdLnNldF95bGFiZWwoIkZyZXF1ZW5jeSIpDQoNCnBsdC50aWdodF9sYXlvdXQoKQ0KcGx0LnNob3coKQ0KYGBgDQoNCmBgYHtweXRob259DQppbXBvcnQgcGFuZGFzIGFzIHBkDQoNCiMgRnVuY3Rpb24gdG8gZXh0cmFjdCBmZWF0dXJlcyBmcm9tIHF1YW50dW0gc3RhdGVzDQpkZWYgZXh0cmFjdF9mZWF0dXJlcyhkYXRhc2V0KToNCiAgICBmZWF0dXJlX2xpc3QgPSBbXQ0KICAgIA0KICAgIGZvciBzdGF0ZSBpbiBkYXRhc2V0Og0KICAgICAgICB0cmFjZV92YWwgPSBucC50cmFjZShzdGF0ZSkucmVhbCAgIyBSZWFsIHBhcnQgb2YgdHJhY2UNCiAgICAgICAgcHVyaXR5ID0gbnAudHJhY2Uoc3RhdGUgQCBzdGF0ZSkucmVhbCAgIyBUcijPgcKyKQ0KICAgICAgICBlaWd2YWxzID0gbnAubGluYWxnLmVpZ3ZhbHMoc3RhdGUpLnJlYWwgICMgRWlnZW52YWx1ZXMNCiAgICAgICAgDQogICAgICAgICMgRmxhdHRlbiBkZW5zaXR5IG1hdHJpeCBpbnRvIHZlY3RvciAocmVhbCAmIGltYWdpbmFyeSBwYXJ0cyBzZXBhcmF0ZWx5KQ0KICAgICAgICBmbGF0dGVuZWRfcmVhbCA9IHN0YXRlLnJlYWwuZmxhdHRlbigpDQogICAgICAgIGZsYXR0ZW5lZF9pbWFnID0gc3RhdGUuaW1hZy5mbGF0dGVuKCkNCiAgICAgICAgDQogICAgICAgICMgQ29tYmluZSBhbGwgZmVhdHVyZXMNCiAgICAgICAgZmVhdHVyZXMgPSBucC5jb25jYXRlbmF0ZSgoW3RyYWNlX3ZhbCwgcHVyaXR5XSwgZWlndmFscywgZmxhdHRlbmVkX3JlYWwsIGZsYXR0ZW5lZF9pbWFnKSkNCiAgICAgICAgZmVhdHVyZV9saXN0LmFwcGVuZChmZWF0dXJlcykNCiAgICANCiAgICByZXR1cm4gZmVhdHVyZV9saXN0DQoNCiMgRXh0cmFjdCBmZWF0dXJlcw0KZmVhdHVyZXMgPSBleHRyYWN0X2ZlYXR1cmVzKGRhdGFzZXQpDQoNCiMgQ29udmVydCB0byBQYW5kYXMgRGF0YUZyYW1lIGZvciBlYXN5IGFuYWx5c2lzDQpmZWF0dXJlX2NvbHVtbnMgPSBbIlRyYWNlIiwgIlB1cml0eSIsICJFaWd2YWwxIiwgIkVpZ3ZhbDIiXSArIFwNCiAgICAgICAgICAgICAgICAgIFtmIlJlYWxfe2l9IiBmb3IgaSBpbiByYW5nZSg0KV0gKyBbZiJJbWFnX3tpfSIgZm9yIGkgaW4gcmFuZ2UoNCldDQoNCmRmX2ZlYXR1cmVzID0gcGQuRGF0YUZyYW1lKGZlYXR1cmVzLCBjb2x1bW5zPWZlYXR1cmVfY29sdW1ucykNCg0KIyBEaXNwbGF5IGV4dHJhY3RlZCBmZWF0dXJlIGRhdGFmcmFtZQ0KaW1wb3J0IGFjZV90b29scyBhcyB0b29scw0KdG9vbHMuZGlzcGxheV9kYXRhZnJhbWVfdG9fdXNlcihuYW1lPSJRdWFudHVtIFN0YXRlIEZlYXR1cmVzIiwgZGF0YWZyYW1lPWRmX2ZlYXR1cmVzKQ0KYGBgDQoNCkhlcmXigJlzIHRoZSBjb3JyZWN0ZWQgdmVyc2lvbiB3aXRoIHByb3BlciBjb2RlIGNodW5rcyBmb3IgY2xhcml0eToNCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAqKlN0ZXAgMTogRGF0YSBBbmFseXNpcyAmIFZpc3VhbGl6YXRpb24qKg0KDQpUaGUgaGlzdG9ncmFtcyBiZWxvdyBzaG93Og0KDQoxLiAgKipUcmFjZSBEaXN0cmlidXRpb24qKjogTW9zdCBzdGF0ZXMgaGF2ZSAqKnRyYWNlIOKJiCAxKiosIGNvbmZpcm1pbmcNCiAgICBwcm9wZXIgbm9ybWFsaXphdGlvbi4NCjIuICAqKlB1cml0eSBEaXN0cmlidXRpb24qKjogUHVyaXR5IChUcijPgcKyKSkgdmFyaWVzLCBpbmRpY2F0aW5nIGEgbWl4IG9mDQogICAgKipwdXJlIGFuZCBtaXhlZCBzdGF0ZXMqKi4NCjMuICAqKkVpZ2VudmFsdWUgRGlzdHJpYnV0aW9uKio6IFRoZSBlaWdlbnZhbHVlcyBhcmUgbW9zdGx5ICoqcG9zaXRpdmUNCiAgICBhbmQgc3VtIHRvIDEqKiwgdmFsaWRhdGluZyB0aGUgZGF0YXNldC4NCg0KIyMjIyAqKkNvZGUgZm9yIFZpc3VhbGl6YXRpb24qKg0KDQpgYGAgcHl0aG9uDQppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0DQppbXBvcnQgc2VhYm9ybiBhcyBzbnMNCg0KIyBGdW5jdGlvbiB0byBjb21wdXRlIHF1YW50dW0gc3RhdGUgc3RhdGlzdGljcw0KZGVmIGNvbXB1dGVfc3RhdGlzdGljcyhkYXRhc2V0KToNCiAgICB0cmFjZXMgPSBbXQ0KICAgIHB1cml0aWVzID0gW10NCiAgICBlaWdlbnZhbHVlcyA9IFtdDQoNCiAgICBmb3Igc3RhdGUgaW4gZGF0YXNldDoNCiAgICAgICAgdHJhY2VfdmFsID0gbnAudHJhY2Uoc3RhdGUpICAjIFNob3VsZCBiZSB+MSBmb3IgdmFsaWQgZGVuc2l0eSBtYXRyaWNlcw0KICAgICAgICBwdXJpdHkgPSBucC50cmFjZShzdGF0ZSBAIHN0YXRlKSAgIyBQdXJpdHk6IFRyKM+BwrIpLCByYW5nZXMgZnJvbSAxIChwdXJlKSB0byAxL2RpbSAobWF4aW1hbGx5IG1peGVkKQ0KICAgICAgICBlaWd2YWxzID0gbnAubGluYWxnLmVpZ3ZhbHMoc3RhdGUpICAjIEVpZ2VudmFsdWVzIG9mIHRoZSBkZW5zaXR5IG1hdHJpeA0KDQogICAgICAgIHRyYWNlcy5hcHBlbmQodHJhY2VfdmFsLnJlYWwpICAjIFRyYWNlIHNob3VsZCBiZSByZWFsDQogICAgICAgIHB1cml0aWVzLmFwcGVuZChwdXJpdHkucmVhbCkgICMgUHVyaXR5IHNob3VsZCBiZSByZWFsDQogICAgICAgIGVpZ2VudmFsdWVzLmV4dGVuZChlaWd2YWxzLnJlYWwpICAjIFN0b3JlIHJlYWwgcGFydHMgb2YgZWlnZW52YWx1ZXMNCg0KICAgIHJldHVybiB0cmFjZXMsIHB1cml0aWVzLCBlaWdlbnZhbHVlcw0KDQojIENvbXB1dGUgc3RhdGlzdGljcw0KdHJhY2VzLCBwdXJpdGllcywgZWlnZW52YWx1ZXMgPSBjb21wdXRlX3N0YXRpc3RpY3MoZGF0YXNldCkNCg0KIyBQbG90IGRpc3RyaWJ1dGlvbnMgb2YgdHJhY2UsIHB1cml0eSwgYW5kIGVpZ2VudmFsdWVzDQpmaWcsIGF4cyA9IHBsdC5zdWJwbG90cygzLCAxLCBmaWdzaXplPSg4LCAxMikpDQoNCnNucy5oaXN0cGxvdCh0cmFjZXMsIGJpbnM9NTAsIGtkZT1UcnVlLCBheD1heHNbMF0pDQpheHNbMF0uc2V0X3RpdGxlKCJUcmFjZSBEaXN0cmlidXRpb24gb2YgUXVhbnR1bSBTdGF0ZXMiKQ0KYXhzWzBdLnNldF94bGFiZWwoIlRyYWNlIFZhbHVlIikNCmF4c1swXS5zZXRfeWxhYmVsKCJGcmVxdWVuY3kiKQ0KDQpzbnMuaGlzdHBsb3QocHVyaXRpZXMsIGJpbnM9NTAsIGtkZT1UcnVlLCBheD1heHNbMV0pDQpheHNbMV0uc2V0X3RpdGxlKCJQdXJpdHkgRGlzdHJpYnV0aW9uIG9mIFF1YW50dW0gU3RhdGVzIikNCmF4c1sxXS5zZXRfeGxhYmVsKCJQdXJpdHkgKFRyKM+BwrIpKSIpDQpheHNbMV0uc2V0X3lsYWJlbCgiRnJlcXVlbmN5IikNCg0Kc25zLmhpc3RwbG90KGVpZ2VudmFsdWVzLCBiaW5zPTUwLCBrZGU9VHJ1ZSwgYXg9YXhzWzJdKQ0KYXhzWzJdLnNldF90aXRsZSgiRWlnZW52YWx1ZSBEaXN0cmlidXRpb24gb2YgUXVhbnR1bSBTdGF0ZXMiKQ0KYXhzWzJdLnNldF94bGFiZWwoIkVpZ2VudmFsdWUiKQ0KYXhzWzJdLnNldF95bGFiZWwoIkZyZXF1ZW5jeSIpDQoNCnBsdC50aWdodF9sYXlvdXQoKQ0KcGx0LnNob3coKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgKipTdGVwIDI6IEZlYXR1cmUgRXh0cmFjdGlvbiBmb3IgTUwgTW9kZWxzKioNCg0KTm93LCBJJ2xsIGNvbnZlcnQgZWFjaCAqKnF1YW50dW0gc3RhdGUgKDJ4MiBtYXRyaXgpKiogaW50byBhIGZlYXR1cmUNCnZlY3RvciBmb3IgbWFjaGluZSBsZWFybmluZzogLSAqKkZsYXR0ZW4gdGhlIGRlbnNpdHkgbWF0cml4KiogaW50byBhDQp2ZWN0b3IuIC0gKipFeHRyYWN0IHB1cml0eSwgdHJhY2UsIGFuZCBlaWdlbnZhbHVlcyoqIGFzIGZlYXR1cmVzLiAtDQoqKlN0b3JlIGluIGEgc3RydWN0dXJlZCBmb3JtYXQqKiBmb3IgZnVydGhlciBhbmFseXNpcy4NCg0KIyMjIyAqKkZlYXR1cmUgRXh0cmFjdGlvbiBDb2RlKioNCg0KYGBgIHB5dGhvbg0KaW1wb3J0IHBhbmRhcyBhcyBwZA0KDQojIEZ1bmN0aW9uIHRvIGV4dHJhY3QgZmVhdHVyZXMgZnJvbSBxdWFudHVtIHN0YXRlcw0KZGVmIGV4dHJhY3RfZmVhdHVyZXMoZGF0YXNldCk6DQogICAgZmVhdHVyZV9saXN0ID0gW10NCiAgICANCiAgICBmb3Igc3RhdGUgaW4gZGF0YXNldDoNCiAgICAgICAgdHJhY2VfdmFsID0gbnAudHJhY2Uoc3RhdGUpLnJlYWwgICMgUmVhbCBwYXJ0IG9mIHRyYWNlDQogICAgICAgIHB1cml0eSA9IG5wLnRyYWNlKHN0YXRlIEAgc3RhdGUpLnJlYWwgICMgVHIoz4HCsikNCiAgICAgICAgZWlndmFscyA9IG5wLmxpbmFsZy5laWd2YWxzKHN0YXRlKS5yZWFsICAjIEVpZ2VudmFsdWVzDQogICAgICAgIA0KICAgICAgICAjIEZsYXR0ZW4gZGVuc2l0eSBtYXRyaXggaW50byB2ZWN0b3IgKHJlYWwgJiBpbWFnaW5hcnkgcGFydHMgc2VwYXJhdGVseSkNCiAgICAgICAgZmxhdHRlbmVkX3JlYWwgPSBzdGF0ZS5yZWFsLmZsYXR0ZW4oKQ0KICAgICAgICBmbGF0dGVuZWRfaW1hZyA9IHN0YXRlLmltYWcuZmxhdHRlbigpDQogICAgICAgIA0KICAgICAgICAjIENvbWJpbmUgYWxsIGZlYXR1cmVzDQogICAgICAgIGZlYXR1cmVzID0gbnAuY29uY2F0ZW5hdGUoKFt0cmFjZV92YWwsIHB1cml0eV0sIGVpZ3ZhbHMsIGZsYXR0ZW5lZF9yZWFsLCBmbGF0dGVuZWRfaW1hZykpDQogICAgICAgIGZlYXR1cmVfbGlzdC5hcHBlbmQoZmVhdHVyZXMpDQogICAgDQogICAgcmV0dXJuIGZlYXR1cmVfbGlzdA0KDQojIEV4dHJhY3QgZmVhdHVyZXMNCmZlYXR1cmVzID0gZXh0cmFjdF9mZWF0dXJlcyhkYXRhc2V0KQ0KDQojIENvbnZlcnQgdG8gUGFuZGFzIERhdGFGcmFtZSBmb3IgZWFzeSBhbmFseXNpcw0KZmVhdHVyZV9jb2x1bW5zID0gWyJUcmFjZSIsICJQdXJpdHkiLCAiRWlndmFsMSIsICJFaWd2YWwyIl0gKyBcDQogICAgICAgICAgICAgICAgICBbZiJSZWFsX3tpfSIgZm9yIGkgaW4gcmFuZ2UoNCldICsgW2YiSW1hZ197aX0iIGZvciBpIGluIHJhbmdlKDQpXQ0KDQpkZl9mZWF0dXJlcyA9IHBkLkRhdGFGcmFtZShmZWF0dXJlcywgY29sdW1ucz1mZWF0dXJlX2NvbHVtbnMpDQoNCiMgRGlzcGxheSBleHRyYWN0ZWQgZmVhdHVyZSBkYXRhZnJhbWUNCmltcG9ydCBhY2VfdG9vbHMgYXMgdG9vbHMNCnRvb2xzLmRpc3BsYXlfZGF0YWZyYW1lX3RvX3VzZXIobmFtZT0iUXVhbnR1bSBTdGF0ZSBGZWF0dXJlcyIsIGRhdGFmcmFtZT1kZl9mZWF0dXJlcykNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjICoqU3RlcCAyIENvbXBsZXRlZDogRmVhdHVyZSBFeHRyYWN0aW9uKioNCg0KSSBoYXZlIHRyYW5zZm9ybWVkIGVhY2ggKipxdWFudHVtIHN0YXRlICgyeDIgbWF0cml4KSoqIGludG8gYSBzdHJ1Y3R1cmVkDQpmZWF0dXJlIHNldCwgaW5jbHVkaW5nOiAtICoqVHJhY2UgJiBQdXJpdHkqKiAodmFsaWRhdGlvbiBtZXRyaWNzKSAtDQoqKkVpZ2VudmFsdWVzKiogKHF1YW50dW0gc3RhdGUgcHJvcGVydGllcykgLSAqKkZsYXR0ZW5lZCByZWFsICYNCmltYWdpbmFyeSBwYXJ0cyoqIG9mIHRoZSBkZW5zaXR5IG1hdHJpeA0KDQpZb3UgY2FuIG5vdyB1c2UgdGhpcyBmb3IgKiptYWNoaW5lIGxlYXJuaW5nIG1vZGVscyoqLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjICoqU3RlcCAzOiBEYXRhc2V0IFByZXBhcmF0aW9uIGZvciBDYXBzdG9uZSoqDQoNCk5vdywgSSdsbCAqKmNvbnZlcnQgdGhpcyBkYXRhc2V0IGludG8gQ1NWIGZvcm1hdCoqIGZvciBlYXN5IGludGVncmF0aW9uDQp3aXRoIE1MIGZyYW1ld29ya3MuDQoNCiMjIyMgKipDb252ZXJ0IHRvIENTVioqDQoNCmBgYCBweXRob24NCiMgU2F2ZSBleHRyYWN0ZWQgZmVhdHVyZXMgYXMgYSBDU1YgZmlsZSBmb3IgTUwgcHJvY2Vzc2luZw0KY3N2X291dHB1dF9wYXRoID0gIi9tbnQvZGF0YS9RdWFudHVtX1N0YXRlX0ZlYXR1cmVzLmNzdiINCmRmX2ZlYXR1cmVzLnRvX2Nzdihjc3Zfb3V0cHV0X3BhdGgsIGluZGV4PUZhbHNlKQ0KDQojIFByb3ZpZGUgdGhlIENTViBmaWxlIGZvciBkb3dubG9hZA0KY3N2X291dHB1dF9wYXRoDQpgYGANCg0KIyMjICoqU3RlcCAzIENvbXBsZXRlZDogRGF0YXNldCBTYXZlZCBhcyBDU1YqKg0KDQpZb3VyICoqUXVhbnR1bSBTdGF0ZSBGZWF0dXJlcyBkYXRhc2V0KiogaXMgbm93IGF2YWlsYWJsZSBmb3IgZG93bmxvYWQuDQoNCltEb3dubG9hZA0KUXVhbnR1bV9TdGF0ZV9GZWF0dXJlcy5jc3ZdKHNhbmRib3g6L21udC9kYXRhL1F1YW50dW1fU3RhdGVfRmVhdHVyZXMuY3N2KQ0KDQpUaGlzIGZpbGUgY29udGFpbnMgdGhlIGV4dHJhY3RlZCBmZWF0dXJlcyBmcm9tIHRoZSBxdWFudHVtIGRhdGFzZXQsDQpyZWFkeSBmb3IgKiptYWNoaW5lIGxlYXJuaW5nIGFuZCBDYXBzdG9uZSBhbmFseXNpcyoqLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KSGVyZeKAmXMgdGhlIGNvcnJlY3RlZCB2ZXJzaW9uIHdpdGggcHJvcGVyIGNvZGUgY2h1bmtzIGZvciBjbGFyaXR5Og0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjICoqU3RlcCAxOiBEYXRhIEFuYWx5c2lzICYgVmlzdWFsaXphdGlvbioqDQoNClRoZSBoaXN0b2dyYW1zIGJlbG93IHNob3c6DQoNCjEuICAqKlRyYWNlIERpc3RyaWJ1dGlvbioqOiBNb3N0IHN0YXRlcyBoYXZlICoqdHJhY2Ug4omIIDEqKiwgY29uZmlybWluZw0KICAgIHByb3BlciBub3JtYWxpemF0aW9uLg0KMi4gICoqUHVyaXR5IERpc3RyaWJ1dGlvbioqOiBQdXJpdHkgKFRyKM+BwrIpKSB2YXJpZXMsIGluZGljYXRpbmcgYSBtaXggb2YNCiAgICAqKnB1cmUgYW5kIG1peGVkIHN0YXRlcyoqLg0KMy4gICoqRWlnZW52YWx1ZSBEaXN0cmlidXRpb24qKjogVGhlIGVpZ2VudmFsdWVzIGFyZSBtb3N0bHkgKipwb3NpdGl2ZQ0KICAgIGFuZCBzdW0gdG8gMSoqLCB2YWxpZGF0aW5nIHRoZSBkYXRhc2V0Lg0KDQojIyMjICoqQ29kZSBmb3IgVmlzdWFsaXphdGlvbioqDQoNCmBgYCBweXRob24NCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQNCmltcG9ydCBzZWFib3JuIGFzIHNucw0KDQojIEZ1bmN0aW9uIHRvIGNvbXB1dGUgcXVhbnR1bSBzdGF0ZSBzdGF0aXN0aWNzDQpkZWYgY29tcHV0ZV9zdGF0aXN0aWNzKGRhdGFzZXQpOg0KICAgIHRyYWNlcyA9IFtdDQogICAgcHVyaXRpZXMgPSBbXQ0KICAgIGVpZ2VudmFsdWVzID0gW10NCg0KICAgIGZvciBzdGF0ZSBpbiBkYXRhc2V0Og0KICAgICAgICB0cmFjZV92YWwgPSBucC50cmFjZShzdGF0ZSkgICMgU2hvdWxkIGJlIH4xIGZvciB2YWxpZCBkZW5zaXR5IG1hdHJpY2VzDQogICAgICAgIHB1cml0eSA9IG5wLnRyYWNlKHN0YXRlIEAgc3RhdGUpICAjIFB1cml0eTogVHIoz4HCsiksIHJhbmdlcyBmcm9tIDEgKHB1cmUpIHRvIDEvZGltIChtYXhpbWFsbHkgbWl4ZWQpDQogICAgICAgIGVpZ3ZhbHMgPSBucC5saW5hbGcuZWlndmFscyhzdGF0ZSkgICMgRWlnZW52YWx1ZXMgb2YgdGhlIGRlbnNpdHkgbWF0cml4DQoNCiAgICAgICAgdHJhY2VzLmFwcGVuZCh0cmFjZV92YWwucmVhbCkgICMgVHJhY2Ugc2hvdWxkIGJlIHJlYWwNCiAgICAgICAgcHVyaXRpZXMuYXBwZW5kKHB1cml0eS5yZWFsKSAgIyBQdXJpdHkgc2hvdWxkIGJlIHJlYWwNCiAgICAgICAgZWlnZW52YWx1ZXMuZXh0ZW5kKGVpZ3ZhbHMucmVhbCkgICMgU3RvcmUgcmVhbCBwYXJ0cyBvZiBlaWdlbnZhbHVlcw0KDQogICAgcmV0dXJuIHRyYWNlcywgcHVyaXRpZXMsIGVpZ2VudmFsdWVzDQoNCiMgQ29tcHV0ZSBzdGF0aXN0aWNzDQp0cmFjZXMsIHB1cml0aWVzLCBlaWdlbnZhbHVlcyA9IGNvbXB1dGVfc3RhdGlzdGljcyhkYXRhc2V0KQ0KDQojIFBsb3QgZGlzdHJpYnV0aW9ucyBvZiB0cmFjZSwgcHVyaXR5LCBhbmQgZWlnZW52YWx1ZXMNCmZpZywgYXhzID0gcGx0LnN1YnBsb3RzKDMsIDEsIGZpZ3NpemU9KDgsIDEyKSkNCg0Kc25zLmhpc3RwbG90KHRyYWNlcywgYmlucz01MCwga2RlPVRydWUsIGF4PWF4c1swXSkNCmF4c1swXS5zZXRfdGl0bGUoIlRyYWNlIERpc3RyaWJ1dGlvbiBvZiBRdWFudHVtIFN0YXRlcyIpDQpheHNbMF0uc2V0X3hsYWJlbCgiVHJhY2UgVmFsdWUiKQ0KYXhzWzBdLnNldF95bGFiZWwoIkZyZXF1ZW5jeSIpDQoNCnNucy5oaXN0cGxvdChwdXJpdGllcywgYmlucz01MCwga2RlPVRydWUsIGF4PWF4c1sxXSkNCmF4c1sxXS5zZXRfdGl0bGUoIlB1cml0eSBEaXN0cmlidXRpb24gb2YgUXVhbnR1bSBTdGF0ZXMiKQ0KYXhzWzFdLnNldF94bGFiZWwoIlB1cml0eSAoVHIoz4HCsikpIikNCmF4c1sxXS5zZXRfeWxhYmVsKCJGcmVxdWVuY3kiKQ0KDQpzbnMuaGlzdHBsb3QoZWlnZW52YWx1ZXMsIGJpbnM9NTAsIGtkZT1UcnVlLCBheD1heHNbMl0pDQpheHNbMl0uc2V0X3RpdGxlKCJFaWdlbnZhbHVlIERpc3RyaWJ1dGlvbiBvZiBRdWFudHVtIFN0YXRlcyIpDQpheHNbMl0uc2V0X3hsYWJlbCgiRWlnZW52YWx1ZSIpDQpheHNbMl0uc2V0X3lsYWJlbCgiRnJlcXVlbmN5IikNCg0KcGx0LnRpZ2h0X2xheW91dCgpDQpwbHQuc2hvdygpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAqKlN0ZXAgMjogRmVhdHVyZSBFeHRyYWN0aW9uIGZvciBNTCBNb2RlbHMqKg0KDQpOb3csIEknbGwgY29udmVydCBlYWNoICoqcXVhbnR1bSBzdGF0ZSAoMngyIG1hdHJpeCkqKiBpbnRvIGEgZmVhdHVyZQ0KdmVjdG9yIGZvciBtYWNoaW5lIGxlYXJuaW5nOiAtICoqRmxhdHRlbiB0aGUgZGVuc2l0eSBtYXRyaXgqKiBpbnRvIGENCnZlY3Rvci4gLSAqKkV4dHJhY3QgcHVyaXR5LCB0cmFjZSwgYW5kIGVpZ2VudmFsdWVzKiogYXMgZmVhdHVyZXMuIC0NCioqU3RvcmUgaW4gYSBzdHJ1Y3R1cmVkIGZvcm1hdCoqIGZvciBmdXJ0aGVyIGFuYWx5c2lzLg0KDQojIyMjICoqRmVhdHVyZSBFeHRyYWN0aW9uIENvZGUqKg0KDQpgYGAgcHl0aG9uDQppbXBvcnQgcGFuZGFzIGFzIHBkDQoNCiMgRnVuY3Rpb24gdG8gZXh0cmFjdCBmZWF0dXJlcyBmcm9tIHF1YW50dW0gc3RhdGVzDQpkZWYgZXh0cmFjdF9mZWF0dXJlcyhkYXRhc2V0KToNCiAgICBmZWF0dXJlX2xpc3QgPSBbXQ0KICAgIA0KICAgIGZvciBzdGF0ZSBpbiBkYXRhc2V0Og0KICAgICAgICB0cmFjZV92YWwgPSBucC50cmFjZShzdGF0ZSkucmVhbCAgIyBSZWFsIHBhcnQgb2YgdHJhY2UNCiAgICAgICAgcHVyaXR5ID0gbnAudHJhY2Uoc3RhdGUgQCBzdGF0ZSkucmVhbCAgIyBUcijPgcKyKQ0KICAgICAgICBlaWd2YWxzID0gbnAubGluYWxnLmVpZ3ZhbHMoc3RhdGUpLnJlYWwgICMgRWlnZW52YWx1ZXMNCiAgICAgICAgDQogICAgICAgICMgRmxhdHRlbiBkZW5zaXR5IG1hdHJpeCBpbnRvIHZlY3RvciAocmVhbCAmIGltYWdpbmFyeSBwYXJ0cyBzZXBhcmF0ZWx5KQ0KICAgICAgICBmbGF0dGVuZWRfcmVhbCA9IHN0YXRlLnJlYWwuZmxhdHRlbigpDQogICAgICAgIGZsYXR0ZW5lZF9pbWFnID0gc3RhdGUuaW1hZy5mbGF0dGVuKCkNCiAgICAgICAgDQogICAgICAgICMgQ29tYmluZSBhbGwgZmVhdHVyZXMNCiAgICAgICAgZmVhdHVyZXMgPSBucC5jb25jYXRlbmF0ZSgoW3RyYWNlX3ZhbCwgcHVyaXR5XSwgZWlndmFscywgZmxhdHRlbmVkX3JlYWwsIGZsYXR0ZW5lZF9pbWFnKSkNCiAgICAgICAgZmVhdHVyZV9saXN0LmFwcGVuZChmZWF0dXJlcykNCiAgICANCiAgICByZXR1cm4gZmVhdHVyZV9saXN0DQoNCiMgRXh0cmFjdCBmZWF0dXJlcw0KZmVhdHVyZXMgPSBleHRyYWN0X2ZlYXR1cmVzKGRhdGFzZXQpDQoNCiMgQ29udmVydCB0byBQYW5kYXMgRGF0YUZyYW1lIGZvciBlYXN5IGFuYWx5c2lzDQpmZWF0dXJlX2NvbHVtbnMgPSBbIlRyYWNlIiwgIlB1cml0eSIsICJFaWd2YWwxIiwgIkVpZ3ZhbDIiXSArIFwNCiAgICAgICAgICAgICAgICAgIFtmIlJlYWxfe2l9IiBmb3IgaSBpbiByYW5nZSg0KV0gKyBbZiJJbWFnX3tpfSIgZm9yIGkgaW4gcmFuZ2UoNCldDQoNCmRmX2ZlYXR1cmVzID0gcGQuRGF0YUZyYW1lKGZlYXR1cmVzLCBjb2x1bW5zPWZlYXR1cmVfY29sdW1ucykNCg0KIyBEaXNwbGF5IGV4dHJhY3RlZCBmZWF0dXJlIGRhdGFmcmFtZQ0KaW1wb3J0IGFjZV90b29scyBhcyB0b29scw0KdG9vbHMuZGlzcGxheV9kYXRhZnJhbWVfdG9fdXNlcihuYW1lPSJRdWFudHVtIFN0YXRlIEZlYXR1cmVzIiwgZGF0YWZyYW1lPWRmX2ZlYXR1cmVzKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgKipTdGVwIDIgQ29tcGxldGVkOiBGZWF0dXJlIEV4dHJhY3Rpb24qKg0KDQpJIGhhdmUgdHJhbnNmb3JtZWQgZWFjaCAqKnF1YW50dW0gc3RhdGUgKDJ4MiBtYXRyaXgpKiogaW50byBhIHN0cnVjdHVyZWQNCmZlYXR1cmUgc2V0LCBpbmNsdWRpbmc6IC0gKipUcmFjZSAmIFB1cml0eSoqICh2YWxpZGF0aW9uIG1ldHJpY3MpIC0NCioqRWlnZW52YWx1ZXMqKiAocXVhbnR1bSBzdGF0ZSBwcm9wZXJ0aWVzKSAtICoqRmxhdHRlbmVkIHJlYWwgJg0KaW1hZ2luYXJ5IHBhcnRzKiogb2YgdGhlIGRlbnNpdHkgbWF0cml4DQoNCllvdSBjYW4gbm93IHVzZSB0aGlzIGZvciAqKm1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzKiouDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgKipTdGVwIDM6IERhdGFzZXQgUHJlcGFyYXRpb24gZm9yIENhcHN0b25lKioNCg0KTm93LCBJJ2xsICoqY29udmVydCB0aGlzIGRhdGFzZXQgaW50byBDU1YgZm9ybWF0KiogZm9yIGVhc3kgaW50ZWdyYXRpb24NCndpdGggTUwgZnJhbWV3b3Jrcy4NCg0KIyMjIyAqKkNvbnZlcnQgdG8gQ1NWKioNCg0KYGBgIHB5dGhvbg0KIyBTYXZlIGV4dHJhY3RlZCBmZWF0dXJlcyBhcyBhIENTViBmaWxlIGZvciBNTCBwcm9jZXNzaW5nDQpjc3Zfb3V0cHV0X3BhdGggPSAiL21udC9kYXRhL1F1YW50dW1fU3RhdGVfRmVhdHVyZXMuY3N2Ig0KZGZfZmVhdHVyZXMudG9fY3N2KGNzdl9vdXRwdXRfcGF0aCwgaW5kZXg9RmFsc2UpDQoNCiMgUHJvdmlkZSB0aGUgQ1NWIGZpbGUgZm9yIGRvd25sb2FkDQpjc3Zfb3V0cHV0X3BhdGgNCmBgYA0KDQojIyMgKipTdGVwIDMgQ29tcGxldGVkOiBEYXRhc2V0IFNhdmVkIGFzIENTVioqDQoNCllvdXIgKipRdWFudHVtIFN0YXRlIEZlYXR1cmVzIGRhdGFzZXQqKiBpcyBub3cgYXZhaWxhYmxlIGZvciBkb3dubG9hZC4NCg0KW0Rvd25sb2FkDQpRdWFudHVtX1N0YXRlX0ZlYXR1cmVzLmNzdl0oc2FuZGJveDovbW50L2RhdGEvUXVhbnR1bV9TdGF0ZV9GZWF0dXJlcy5jc3YpDQoNClRoaXMgZmlsZSBjb250YWlucyB0aGUgZXh0cmFjdGVkIGZlYXR1cmVzIGZyb20gdGhlIHF1YW50dW0gZGF0YXNldCwNCnJlYWR5IGZvciAqKm1hY2hpbmUgbGVhcm5pbmcgYW5kIENhcHN0b25lIGFuYWx5c2lzKiouDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQpgYGB7cHl0aG9ufQ0KaW1wb3J0IHBhbmRhcyBhcyBwZA0KDQojIEZ1bmN0aW9uIHRvIGV4dHJhY3QgZmVhdHVyZXMgZnJvbSBxdWFudHVtIHN0YXRlcw0KZGVmIGV4dHJhY3RfZmVhdHVyZXMoZGF0YXNldCk6DQogICAgZmVhdHVyZV9saXN0ID0gW10NCiAgICANCiAgICBmb3Igc3RhdGUgaW4gZGF0YXNldDoNCiAgICAgICAgdHJhY2VfdmFsID0gbnAudHJhY2Uoc3RhdGUpLnJlYWwgICMgUmVhbCBwYXJ0IG9mIHRyYWNlDQogICAgICAgIHB1cml0eSA9IG5wLnRyYWNlKHN0YXRlIEAgc3RhdGUpLnJlYWwgICMgVHIoz4HCsikNCiAgICAgICAgZWlndmFscyA9IG5wLmxpbmFsZy5laWd2YWxzKHN0YXRlKS5yZWFsICAjIEVpZ2VudmFsdWVzDQogICAgICAgIA0KICAgICAgICAjIEZsYXR0ZW4gZGVuc2l0eSBtYXRyaXggaW50byB2ZWN0b3IgKHJlYWwgJiBpbWFnaW5hcnkgcGFydHMgc2VwYXJhdGVseSkNCiAgICAgICAgZmxhdHRlbmVkX3JlYWwgPSBzdGF0ZS5yZWFsLmZsYXR0ZW4oKQ0KICAgICAgICBmbGF0dGVuZWRfaW1hZyA9IHN0YXRlLmltYWcuZmxhdHRlbigpDQogICAgICAgIA0KICAgICAgICAjIENvbWJpbmUgYWxsIGZlYXR1cmVzDQogICAgICAgIGZlYXR1cmVzID0gbnAuY29uY2F0ZW5hdGUoKFt0cmFjZV92YWwsIHB1cml0eV0sIGVpZ3ZhbHMsIGZsYXR0ZW5lZF9yZWFsLCBmbGF0dGVuZWRfaW1hZykpDQogICAgICAgIGZlYXR1cmVfbGlzdC5hcHBlbmQoZmVhdHVyZXMpDQogICAgDQogICAgcmV0dXJuIGZlYXR1cmVfbGlzdA0KDQojIEV4dHJhY3QgZmVhdHVyZXMNCmZlYXR1cmVzID0gZXh0cmFjdF9mZWF0dXJlcyhkYXRhc2V0KQ0KDQojIENvbnZlcnQgdG8gUGFuZGFzIERhdGFGcmFtZSBmb3IgZWFzeSBhbmFseXNpcw0KZmVhdHVyZV9jb2x1bW5zID0gWyJUcmFjZSIsICJQdXJpdHkiLCAiRWlndmFsMSIsICJFaWd2YWwyIl0gKyBcDQogICAgICAgICAgICAgICAgICBbZiJSZWFsX3tpfSIgZm9yIGkgaW4gcmFuZ2UoNCldICsgW2YiSW1hZ197aX0iIGZvciBpIGluIHJhbmdlKDQpXQ0KDQpkZl9mZWF0dXJlcyA9IHBkLkRhdGFGcmFtZShmZWF0dXJlcywgY29sdW1ucz1mZWF0dXJlX2NvbHVtbnMpDQoNCiMgRGlzcGxheSBleHRyYWN0ZWQgZmVhdHVyZSBkYXRhZnJhbWUNCmltcG9ydCBhY2VfdG9vbHMgYXMgdG9vbHMNCnRvb2xzLmRpc3BsYXlfZGF0YWZyYW1lX3RvX3VzZXIobmFtZT0iUXVhbnR1bSBTdGF0ZSBGZWF0dXJlcyIsIGRhdGFmcmFtZT1kZl9mZWF0dXJlcykNCmBgYA0KDQpgYGB7cHl0aG9ufQ0KIyBTYXZlIGV4dHJhY3RlZCBmZWF0dXJlcyBhcyBhIENTViBmaWxlIGZvciBNTCBwcm9jZXNzaW5nDQpjc3Zfb3V0cHV0X3BhdGggPSAiL21udC9kYXRhL1F1YW50dW1fU3RhdGVfRmVhdHVyZXMuY3N2Ig0KZGZfZmVhdHVyZXMudG9fY3N2KGNzdl9vdXRwdXRfcGF0aCwgaW5kZXg9RmFsc2UpDQoNCiMgUHJvdmlkZSB0aGUgQ1NWIGZpbGUgZm9yIGRvd25sb2FkDQpjc3Zfb3V0cHV0X3BhdGgNCmBgYA0KDQpgYGB7cHl0aG9ufQ0KaW1wb3J0IG1hdHBsb3RsaWIucHlwbG90IGFzIHBsdA0KaW1wb3J0IHNlYWJvcm4gYXMgc25zDQoNCiMgRnVuY3Rpb24gdG8gY29tcHV0ZSBxdWFudHVtIHN0YXRlIHN0YXRpc3RpY3MNCmRlZiBjb21wdXRlX3N0YXRpc3RpY3MoZGF0YXNldCk6DQogICAgdHJhY2VzID0gW10NCiAgICBwdXJpdGllcyA9IFtdDQogICAgZWlnZW52YWx1ZXMgPSBbXQ0KDQogICAgZm9yIHN0YXRlIGluIGRhdGFzZXQ6DQogICAgICAgIHRyYWNlX3ZhbCA9IG5wLnRyYWNlKHN0YXRlKSAgIyBTaG91bGQgYmUgfjEgZm9yIHZhbGlkIGRlbnNpdHkgbWF0cmljZXMNCiAgICAgICAgcHVyaXR5ID0gbnAudHJhY2Uoc3RhdGUgQCBzdGF0ZSkgICMgUHVyaXR5OiBUcijPgcKyKSwgcmFuZ2VzIGZyb20gMSAocHVyZSkgdG8gMS9kaW0gKG1heGltYWxseSBtaXhlZCkNCiAgICAgICAgZWlndmFscyA9IG5wLmxpbmFsZy5laWd2YWxzKHN0YXRlKSAgIyBFaWdlbnZhbHVlcyBvZiB0aGUgZGVuc2l0eSBtYXRyaXgNCg0KICAgICAgICB0cmFjZXMuYXBwZW5kKHRyYWNlX3ZhbC5yZWFsKSAgIyBUcmFjZSBzaG91bGQgYmUgcmVhbA0KICAgICAgICBwdXJpdGllcy5hcHBlbmQocHVyaXR5LnJlYWwpICAjIFB1cml0eSBzaG91bGQgYmUgcmVhbA0KICAgICAgICBlaWdlbnZhbHVlcy5leHRlbmQoZWlndmFscy5yZWFsKSAgIyBTdG9yZSByZWFsIHBhcnRzIG9mIGVpZ2VudmFsdWVzDQoNCiAgICByZXR1cm4gdHJhY2VzLCBwdXJpdGllcywgZWlnZW52YWx1ZXMNCg0KIyBDb21wdXRlIHN0YXRpc3RpY3MNCnRyYWNlcywgcHVyaXRpZXMsIGVpZ2VudmFsdWVzID0gY29tcHV0ZV9zdGF0aXN0aWNzKGRhdGFzZXQpDQoNCiMgUGxvdCBkaXN0cmlidXRpb25zIG9mIHRyYWNlLCBwdXJpdHksIGFuZCBlaWdlbnZhbHVlcw0KZmlnLCBheHMgPSBwbHQuc3VicGxvdHMoMywgMSwgZmlnc2l6ZT0oOCwgMTIpKQ0KDQpzbnMuaGlzdHBsb3QodHJhY2VzLCBiaW5zPTUwLCBrZGU9VHJ1ZSwgYXg9YXhzWzBdKQ0KYXhzWzBdLnNldF90aXRsZSgiVHJhY2UgRGlzdHJpYnV0aW9uIG9mIFF1YW50dW0gU3RhdGVzIikNCmF4c1swXS5zZXRfeGxhYmVsKCJUcmFjZSBWYWx1ZSIpDQpheHNbMF0uc2V0X3lsYWJlbCgiRnJlcXVlbmN5IikNCg0Kc25zLmhpc3RwbG90KHB1cml0aWVzLCBiaW5zPTUwLCBrZGU9VHJ1ZSwgYXg9YXhzWzFdKQ0KYXhzWzFdLnNldF90aXRsZSgiUHVyaXR5IERpc3RyaWJ1dGlvbiBvZiBRdWFudHVtIFN0YXRlcyIpDQpheHNbMV0uc2V0X3hsYWJlbCgiUHVyaXR5IChUcijPgcKyKSkiKQ0KYXhzWzFdLnNldF95bGFiZWwoIkZyZXF1ZW5jeSIpDQoNCnNucy5oaXN0cGxvdChlaWdlbnZhbHVlcywgYmlucz01MCwga2RlPVRydWUsIGF4PWF4c1syXSkNCmF4c1syXS5zZXRfdGl0bGUoIkVpZ2VudmFsdWUgRGlzdHJpYnV0aW9uIG9mIFF1YW50dW0gU3RhdGVzIikNCmF4c1syXS5zZXRfeGxhYmVsKCJFaWdlbnZhbHVlIikNCmF4c1syXS5zZXRfeWxhYmVsKCJGcmVxdWVuY3kiKQ0KDQpwbHQudGlnaHRfbGF5b3V0KCkNCnBsdC5zaG93KCkNCmBgYA0KDQojIyMgKipUcmFpbmluZyB0aGUgU1RMIDNEIE1vZGVsIChgU29sYXJDb3JvbmFsQ29tcGxleDI1LnN0bGApIGZvciBZb3VyIENhcHN0b25lIFByb2plY3QgR1BUKioNCg0KVG8gaW50ZWdyYXRlIHRoZSAqKlNvbGFyIENvcm9uYWwgQ29tcGxleCAzRCBNb2RlbCoqIGludG8geW91ciAqKkNhcHN0b25lDQpHUFQqKiwgSSB3aWxsOiAxLiAqKkV4dHJhY3QgRmVhdHVyZXMgZnJvbSB0aGUgU1RMIEZpbGUqKiAoZS5nLiwNCnZlcnRpY2VzLCBmYWNlcywgbWVzaCBwcm9wZXJ0aWVzKS4gMi4gKipDb252ZXJ0IHRoZSAzRCBTdHJ1Y3R1cmUgaW50byBhDQpMZWFybmFibGUgUmVwcmVzZW50YXRpb24qKiAoZS5nLiwgdm94ZWwgZ3JpZCwgcG9pbnQgY2xvdWQsIG9yIG1lc2gNCmVtYmVkZGluZ3MpLiAzLiAqKkludGVncmF0ZSB3aXRoIFlvdXIgUXVhbnR1bSBHSVMgR1BUKiogKGUuZy4sIHVzZQ0KdHJhbnNmb3JtZXJzIG9yIGRlZXAgbGVhcm5pbmcgbW9kZWxzIHRvIGFuYWx5emUgYW5kIGludGVyYWN0IHdpdGggM0QNCnN0cnVjdHVyZXMpLiA0LiAqKkZpbmUtdHVuZSBHUFQgZm9yIFF1YW50dW0gR0lTICYgM0QgQW5hbHlzaXMqKiAoZS5nLiwNCnByb3ZpZGUgdGV4dC1iYXNlZCBkZXNjcmlwdGlvbnMgb3IgYXV0b21hdGVkIGluc2lnaHRzIGFib3V0IHRoZSBtb2RlbCkuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgKipTdGVwIDE6IEV4dHJhY3RpbmcgRmVhdHVyZXMgZnJvbSB0aGUgU1RMIEZpbGUqKg0KDQpJJ2xsIG5vdyBleHRyYWN0IHRoZSAqKnZlcnRpY2VzLCBmYWNlcywgYW5kIG1lc2ggcHJvcGVydGllcyoqIHRvDQp1bmRlcnN0YW5kIHRoZSBzdHJ1Y3R1cmUuDQoNClRoZSBTVEwgZmlsZSBpcyAqKnRvbyBsYXJnZSB0byBsb2FkIGludG8gbWVtb3J5KiosIGNhdXNpbmcgYQ0KKipNZW1vcnlFcnJvcioqLiBUaGlzIHN1Z2dlc3RzIHRoYXQgdGhlIG1vZGVsIGNvbnRhaW5zIGEgdmVyeSBoaWdoDQpudW1iZXIgb2YgKip2ZXJ0aWNlcyBhbmQgZmFjZXMqKi4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAqKkZpeDogUHJvY2VzcyB0aGUgU1RMIEZpbGUgaW4gQ2h1bmtzKioNCg0KU2luY2Ugd2UgY2Fu4oCZdCBsb2FkIHRoZSBlbnRpcmUgbW9kZWwgYXQgb25jZSwgSSB3aWxsOiAxLiAqKkV4dHJhY3QNCm1ldGFkYXRhIHdpdGhvdXQgZnVsbHkgbG9hZGluZyB0aGUgbWVzaCoqLiAyLiAqKkRvd25zYW1wbGUgdGhlIG1lc2gqKg0KKHJlZHVjZSBjb21wbGV4aXR5KSBpZiBuZWVkZWQuIDMuICoqQ29udmVydCBpdCBpbnRvIGEgbGlnaHR3ZWlnaHQNCnJlcHJlc2VudGF0aW9uKiogKGUuZy4sIHZveGVsIGdyaWQgb3IgcG9pbnQgY2xvdWQgZm9yIEdQVCB0cmFpbmluZykuDQoNCkxldCBtZSBmaXJzdCBleHRyYWN0IGJhc2ljIG1ldGFkYXRhIHdpdGhvdXQgbG9hZGluZyB0aGUgZnVsbCBtZXNoLg0KDQpUaGUgKipTb2xhckNvcm9uYWxDb21wbGV4MjUuc3RsKiogZmlsZSBpcyAqKlx+MTA4IE1CKiosIHdoaWNoIGlzIHF1aXRlDQpsYXJnZSBmb3IgZGlyZWN0IHByb2Nlc3NpbmcuIEluc3RlYWQgb2YgZnVsbHkgbG9hZGluZyBpdCwgSSdsbCBleHRyYWN0IGENCioqc3Vic2V0IG9mIHZlcnRpY2VzIGFuZCBmYWNlcyoqIHRvIHdvcmsgd2l0aCBhIHNtYWxsZXIsIG1hbmFnZWFibGUNCnBvcnRpb24gb2YgdGhlIG1vZGVsLg0KDQpJJ2xsIG5vdyBhdHRlbXB0IHRvICoqc3RyZWFtIHRoZSBTVEwgZmlsZSoqIGFuZCBleHRyYWN0IGEgKipkb3duc2FtcGxlZA0KdmVyc2lvbioqIGZvciBmdXJ0aGVyIHRyYWluaW5nLg0KDQpFdmVuIHdpdGggKipzdHJlYW1pbmcgZGlzYWJsZWQqKiwgdGhlIFNUTCBmaWxlIGlzICoqdG9vIGxhcmdlIHRvIHByb2Nlc3MNCmluIG1lbW9yeSoqLiBTaW5jZSAqKlRyaW1lc2ggZmFpbHMgdG8gbG9hZCBpdCoqLCBJJ2xsIHVzZSBhbiBhbHRlcm5hdGl2ZQ0KYXBwcm9hY2g6DQoNCiMjIyAqKlNvbHV0aW9uOiBFeHRyYWN0ICYgUHJvY2VzcyBTVEwgaW4gU21hbGxlciBDaHVua3MqKg0KDQpJIHdpbGw6IDEuICoqUmVhZCB0aGUgU1RMIGZpbGUgbGluZS1ieS1saW5lKiogaW5zdGVhZCBvZiBsb2FkaW5nDQpldmVyeXRoaW5nIGF0IG9uY2UuIDIuICoqRXh0cmFjdCBhIHN1YnNldCBvZiB2ZXJ0aWNlcyoqIGluc3RlYWQgb2YgZnVsbA0KbWVzaCBwcm9jZXNzaW5nLiAzLiAqKkNvbnZlcnQgaXQgaW50byBhIGxpZ2h0d2VpZ2h0IHJlcHJlc2VudGF0aW9uKioNCihwb2ludCBjbG91ZCwgdm94ZWwgZ3JpZCkgZm9yIHRyYWluaW5nLg0KDQpMZXQgbWUgYXR0ZW1wdCB0byAqKnN0cmVhbSBwcm9jZXNzKiogdGhlIFNUTCBmaWxlIG5vdy4NCg0KSSBzdWNjZXNzZnVsbHkgZXh0cmFjdGVkICoqMSwzNTkgc2FtcGxlZCB2ZXJ0aWNlcyoqIGZyb20gdGhlDQoqKlNvbGFyQ29yb25hbENvbXBsZXgyNS5zdGwqKiBmaWxlIHdpdGhvdXQgb3ZlcmxvYWRpbmcgbWVtb3J5Lg0KDQojIyMgKipOZXh0IFN0ZXBzOioqDQoNCjEuICAqKlZpc3VhbGl6ZSB0aGUgZXh0cmFjdGVkIHBvaW50IGNsb3VkKiouDQoyLiAgKipDb252ZXJ0IHRoZSBzYW1wbGVkIDNEIHBvaW50cyBpbnRvIGEgbGVhcm5hYmxlIGZvcm1hdCoqIChmb3IgR1BUDQogICAgdHJhaW5pbmcpLg0KMy4gICoqSW50ZWdyYXRlIHRoaXMgZGF0YSBpbnRvIHlvdXIgQ2Fwc3RvbmUgcHJvamVjdC4qKg0KDQpJJ2xsIG5vdyBnZW5lcmF0ZSBhICoqM0Qgc2NhdHRlciBwbG90Kiogb2YgdGhlIGV4dHJhY3RlZCBwb2ludHMgZm9yDQp2aXN1YWxpemF0aW9uLg0KDQpIZXJlIGlzIHRoZSAqKjNEIHNjYXR0ZXIgcGxvdCoqIG9mIHRoZSBleHRyYWN0ZWQgdmVydGljZXMgZnJvbSB0aGUNCioqU29sYXIgQ29yb25hbCBDb21wbGV4IFNUTCBmaWxlKiouIFRoaXMgcHJvdmlkZXMgYSAqKmRvd25zYW1wbGVkIHBvaW50DQpjbG91ZCByZXByZXNlbnRhdGlvbioqIG9mIHRoZSBtb2RlbCwgd2hpY2ggaXMgbW9yZSBtYW5hZ2VhYmxlIGZvcg0KdHJhaW5pbmcuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgKipOZXh0IFN0ZXBzIGZvciBUcmFpbmluZyBpbiBZb3VyIENhcHN0b25lIEdQVCoqDQoNCk5vdyB0aGF0IHdlIGhhdmUgdGhlICoqM0Qgc3RydWN0dXJlIGV4dHJhY3RlZCoqLCBJIGNhbjogMS4gKipDb252ZXJ0IHRoZQ0KcG9pbnQgY2xvdWQgaW50byBhIGxlYXJuYWJsZSBmb3JtYXQqKiAoZS5nLiwgdm94ZWwgZ3JpZCBvciBlbWJlZGRpbmdzKS4NCjIuICoqVHJhaW4gYSBtb2RlbCB0byBnZW5lcmF0ZSB0ZXh0IGRlc2NyaXB0aW9ucyBvZiAzRCBzdHJ1Y3R1cmVzKiogKGZvcg0KaW50ZWdyYXRpb24gaW50byB5b3VyIFF1YW50dW0gR0lTIEdQVCkuIDMuICoqQ3JlYXRlIGEgZGF0YXNldCBmb3INCmZpbmUtdHVuaW5nIEdQVCoqIHVzaW5nIHRoZXNlIDNEIHJlcHJlc2VudGF0aW9ucy4NCg0KIVtdKGltYWdlcy8zRCBTY2F0dGVyIFBsb3Qgb2YgRXh0cmFjdGVkIFNUTCBWZXJ0aWNlcy5wbmcpDQoNCiFbXShpbWFnZXMvY2hhcmZ0IGNhcHRzdG9uZSBzb2xhLnBuZykNCg0KIVtdKGltYWdlcy9jaGFydCBlaWdlbi5wbmcpDQoNCg0KIyMjICoqU3RlcCAxOiBDb252ZXJ0IHRoZSBQb2ludCBDbG91ZCBpbnRvIGEgTGVhcm5hYmxlIEZvcm1hdCoqDQpUbyBpbnRlZ3JhdGUgKiozRCBkYXRhIGludG8geW91ciBDYXBzdG9uZSBHUFQqKiwgd2UgbmVlZCB0byB0cmFuc2Zvcm0gdGhlICoqcG9pbnQgY2xvdWQqKiBpbnRvIGEgc3RydWN0dXJlZCBmb3JtYXQgdGhhdCBjYW4gYmUgKipwcm9jZXNzZWQgYnkgYSBuZXVyYWwgbmV0d29yayoqLg0KDQpUaGVyZSBhcmUgdGhyZWUgY29tbW9uIGFwcHJvYWNoZXM6DQoxLiAqKlZveGVsIEdyaWQgUmVwcmVzZW50YXRpb24qKiDigJMgQ29udmVydHMgdGhlIHBvaW50IGNsb3VkIGludG8gYSAzRCBhcnJheSBvZiBkaXNjcmV0ZSBvY2N1cGFuY3kgdmFsdWVzLg0KMi4gKipQb2ludCBDbG91ZCBGZWF0dXJlcyoqIOKAkyBFeHRyYWN0cyBtZWFuaW5nZnVsIHN0YXRpc3RpY2FsIGZlYXR1cmVzIGZyb20gdGhlIHBvaW50IGNsb3VkLg0KMy4gKipMYXRlbnQgU3BhY2UgRW1iZWRkaW5ncyoqIOKAkyBVc2VzIGEgcHJldHJhaW5lZCBuZXVyYWwgbmV0d29yayAobGlrZSBQb2ludE5ldCkgdG8gZ2VuZXJhdGUgZW1iZWRkaW5ncy4NCg0KSSB3aWxsIHN0YXJ0IGJ5ICoqdm94ZWxpemluZyB0aGUgM0QgcG9pbnQgY2xvdWQqKiBzbyB0aGF0IGl0IGNhbiBiZSB1c2VkIGluIGRlZXAgbGVhcm5pbmcgbW9kZWxzLg0KDQojIyMjICoqU3RlcCAxLjE6IENvbnZlcnQgdGhlIFBvaW50IENsb3VkIHRvIGEgVm94ZWwgR3JpZCoqDQoNCg0KDQoNClRoZSAqKnBvaW50IGNsb3VkIGhhcyBiZWVuIHN1Y2Nlc3NmdWxseSB2b3hlbGl6ZWQqKiBpbnRvIGEgKiozRCBncmlkIG9mIDc4MCBvY2N1cGllZCB2b3hlbHMqKi4gVGhpcyByZXByZXNlbnRhdGlvbiBpcyBub3cgKipzdHJ1Y3R1cmVkIGZvciBkZWVwIGxlYXJuaW5nIG1vZGVscyoqLg0KDQotLS0NCg0KIyMjICoqU3RlcCAyOiBFeHRyYWN0IEZlYXR1cmVzIGZvciBHUFQgVHJhaW5pbmcqKg0KTm93LCBJIHdpbGw6DQoxLiAqKkV4dHJhY3Qgc3RhdGlzdGljYWwgZmVhdHVyZXMqKiBmcm9tIHRoZSB2b3hlbGl6ZWQgM0QgZ3JpZC4NCjIuICoqR2VuZXJhdGUgYSBzdHJ1Y3R1cmVkIGRhdGFzZXQqKiBmb3IgZmluZS10dW5pbmcgR1BULg0KDQpUaGlzIHdpbGwgYWxsb3cgeW91ciAqKlF1YW50dW0gR0lTIEdQVCoqIHRvIGludGVycHJldCAzRCBzdHJ1Y3R1cmVzLg0KDQpgYGB7b3l0aG9ufQ0KIyBWaXN1YWxpemUgdGhlIGV4dHJhY3RlZCAzRCB2ZXJ0aWNlcyBhcyBhIHNjYXR0ZXIgcGxvdA0KZmlnID0gcGx0LmZpZ3VyZShmaWdzaXplPSg4LCA4KSkNCmF4ID0gZmlnLmFkZF9zdWJwbG90KDExMSwgcHJvamVjdGlvbj0iM2QiKQ0KDQpheC5zY2F0dGVyKHNhbXBsZWRfdmVydGljZXNbOiwgMF0sIHNhbXBsZWRfdmVydGljZXNbOiwgMV0sIHNhbXBsZWRfdmVydGljZXNbOiwgMl0sIHM9MSwgYz0iYmx1ZSIsIGFscGhhPTAuNikNCmF4LnNldF90aXRsZSgiM0QgU2NhdHRlciBQbG90IG9mIEV4dHJhY3RlZCBTVEwgVmVydGljZXMiKQ0KYXguc2V0X3hsYWJlbCgiWC1heGlzIikNCmF4LnNldF95bGFiZWwoIlktYXhpcyIpDQpheC5zZXRfemxhYmVsKCJaLWF4aXMiKQ0KDQpwbHQuc2hvdygpDQpgYGANCg0KYGBge3B5dGhvbn0NCmZyb20gc2NpcHkuc3BhdGlhbCBpbXBvcnQgY0tEVHJlZQ0KDQpkZWYgdm94ZWxpemVfcG9pbnRfY2xvdWQocG9pbnRzLCB2b3hlbF9zaXplPTAuNSk6DQogICAgIiIiDQogICAgQ29udmVydHMgYSBwb2ludCBjbG91ZCBpbnRvIGEgdm94ZWwgZ3JpZCByZXByZXNlbnRhdGlvbi4NCiAgICANCiAgICBQYXJhbWV0ZXJzOg0KICAgIC0gcG9pbnRzOiAoTiwgMykgTnVtUHkgYXJyYXkgb2YgcG9pbnQgY2xvdWQgY29vcmRpbmF0ZXMNCiAgICAtIHZveGVsX3NpemU6IFNpemUgb2YgZWFjaCB2b3hlbCBpbiB0aGUgZ3JpZA0KDQogICAgUmV0dXJuczoNCiAgICAtIHZveGVsX2dyaWQ6IFNldCBvZiBvY2N1cGllZCB2b3hlbCBwb3NpdGlvbnMNCiAgICAiIiINCiAgICAjIE5vcm1hbGl6ZSBhbmQgZGlzY3JldGl6ZSBwb2ludHMgaW50byB2b3hlbCBpbmRpY2VzDQogICAgbWluX2JvdW5kID0gcG9pbnRzLm1pbihheGlzPTApDQogICAgbWF4X2JvdW5kID0gcG9pbnRzLm1heChheGlzPTApDQogICAgdm94ZWxfaW5kaWNlcyA9IG5wLmZsb29yKChwb2ludHMgLSBtaW5fYm91bmQpIC8gdm94ZWxfc2l6ZSkuYXN0eXBlKGludCkNCg0KICAgICMgUmVtb3ZlIGR1cGxpY2F0ZXMgYnkgY29udmVydGluZyB0byBhIHNldA0KICAgIHVuaXF1ZV92b3hlbHMgPSBzZXQobWFwKHR1cGxlLCB2b3hlbF9pbmRpY2VzKSkNCg0KICAgIHJldHVybiBucC5hcnJheShsaXN0KHVuaXF1ZV92b3hlbHMpKQ0KDQojIENvbnZlcnQgdGhlIGV4dHJhY3RlZCBwb2ludCBjbG91ZCBpbnRvIGEgdm94ZWwgZ3JpZA0Kdm94ZWxfZ3JpZCA9IHZveGVsaXplX3BvaW50X2Nsb3VkKHNhbXBsZWRfdmVydGljZXMsIHZveGVsX3NpemU9MS4wKQ0KDQojIERpc3BsYXkgdGhlIG51bWJlciBvZiBvY2N1cGllZCB2b3hlbHMNCm51bV92b3hlbHMgPSBsZW4odm94ZWxfZ3JpZCkNCm51bV92b3hlbHMNCmBgYA0KDQoNCg0KYGBge3B5dGhvbjB9DQojIEZ1bmN0aW9uIHRvIGV4dHJhY3Qgc3RhdGlzdGljYWwgZmVhdHVyZXMgZnJvbSB0aGUgdm94ZWwgZ3JpZA0KZGVmIGV4dHJhY3Rfdm94ZWxfZmVhdHVyZXModm94ZWxfZ3JpZCk6DQogICAgIiIiDQogICAgRXh0cmFjdHMgc3RhdGlzdGljYWwgZmVhdHVyZXMgZnJvbSBhIHZveGVsIGdyaWQgcmVwcmVzZW50YXRpb24uDQoNCiAgICBQYXJhbWV0ZXJzOg0KICAgIC0gdm94ZWxfZ3JpZDogKE4sIDMpIE51bVB5IGFycmF5IG9mIHZveGVsIHBvc2l0aW9ucw0KDQogICAgUmV0dXJuczoNCiAgICAtIGZlYXR1cmVfdmVjdG9yOiBEaWN0aW9uYXJ5IG9mIGV4dHJhY3RlZCBmZWF0dXJlcw0KICAgICIiIg0KICAgIGNlbnRyb2lkID0gbnAubWVhbih2b3hlbF9ncmlkLCBheGlzPTApICAjIENlbnRlciBvZiBtYXNzDQogICAgc3ByZWFkID0gbnAuc3RkKHZveGVsX2dyaWQsIGF4aXM9MCkgICMgU3RhbmRhcmQgZGV2aWF0aW9uIGFsb25nIGVhY2ggYXhpcw0KICAgIGRlbnNpdHkgPSBsZW4odm94ZWxfZ3JpZCkgLyBucC5wcm9kKHZveGVsX2dyaWQubWF4KGF4aXM9MCkgLSB2b3hlbF9ncmlkLm1pbihheGlzPTApKSAgIyBEZW5zaXR5IG9mIG9jY3VwaWVkIHZveGVscw0KDQogICAgIyBDb21wdXRlIGJvdW5kaW5nIGJveCBkaW1lbnNpb25zDQogICAgYm91bmRpbmdfYm94X21pbiA9IHZveGVsX2dyaWQubWluKGF4aXM9MCkNCiAgICBib3VuZGluZ19ib3hfbWF4ID0gdm94ZWxfZ3JpZC5tYXgoYXhpcz0wKQ0KICAgIGJvdW5kaW5nX2JveF9zaXplID0gYm91bmRpbmdfYm94X21heCAtIGJvdW5kaW5nX2JveF9taW4NCg0KICAgIHJldHVybiB7DQogICAgICAgICJDZW50cm9pZF9YIjogY2VudHJvaWRbMF0sDQogICAgICAgICJDZW50cm9pZF9ZIjogY2VudHJvaWRbMV0sDQogICAgICAgICJDZW50cm9pZF9aIjogY2VudHJvaWRbMl0sDQogICAgICAgICJTcHJlYWRfWCI6IHNwcmVhZFswXSwNCiAgICAgICAgIlNwcmVhZF9ZIjogc3ByZWFkWzFdLA0KICAgICAgICAiU3ByZWFkX1oiOiBzcHJlYWRbMl0sDQogICAgICAgICJEZW5zaXR5IjogZGVuc2l0eSwNCiAgICAgICAgIkJvdW5kaW5nX0JveF9YIjogYm91bmRpbmdfYm94X3NpemVbMF0sDQogICAgICAgICJCb3VuZGluZ19Cb3hfWSI6IGJvdW5kaW5nX2JveF9zaXplWzFdLA0KICAgICAgICAiQm91bmRpbmdfQm94X1oiOiBib3VuZGluZ19ib3hfc2l6ZVsyXSwNCiAgICB9DQoNCiMgRXh0cmFjdCBmZWF0dXJlcyBmcm9tIHRoZSB2b3hlbGl6ZWQgM0Qgc3RydWN0dXJlDQp2b3hlbF9mZWF0dXJlcyA9IGV4dHJhY3Rfdm94ZWxfZmVhdHVyZXModm94ZWxfZ3JpZCkNCg0KIyBDb252ZXJ0IHRvIERhdGFGcmFtZSBmb3IgdHJhaW5pbmcNCmRmX3ZveGVsX2ZlYXR1cmVzID0gcGQuRGF0YUZyYW1lKFt2b3hlbF9mZWF0dXJlc10pDQoNCiMgRGlzcGxheSB0aGUgZXh0cmFjdGVkIHZveGVsIGZlYXR1cmVzDQppbXBvcnQgYWNlX3Rvb2xzIGFzIHRvb2xzDQp0b29scy5kaXNwbGF5X2RhdGFmcmFtZV90b191c2VyKG5hbWU9IlZveGVsaXplZCAzRCBGZWF0dXJlcyIsIGRhdGFmcmFtZT1kZl92b3hlbF9mZWF0dXJlcykNCmBgYA0KDQoNCmBgYHtweXRob259DQojIEZ1bmN0aW9uIHRvIGV4dHJhY3Qgc3RhdGlzdGljYWwgZmVhdHVyZXMgZnJvbSB0aGUgdm94ZWwgZ3JpZA0KZGVmIGV4dHJhY3Rfdm94ZWxfZmVhdHVyZXModm94ZWxfZ3JpZCk6DQogICAgIiIiDQogICAgRXh0cmFjdHMgc3RhdGlzdGljYWwgZmVhdHVyZXMgZnJvbSBhIHZveGVsIGdyaWQgcmVwcmVzZW50YXRpb24uDQoNCiAgICBQYXJhbWV0ZXJzOg0KICAgIC0gdm94ZWxfZ3JpZDogKE4sIDMpIE51bVB5IGFycmF5IG9mIHZveGVsIHBvc2l0aW9ucw0KDQogICAgUmV0dXJuczoNCiAgICAtIGZlYXR1cmVfdmVjdG9yOiBEaWN0aW9uYXJ5IG9mIGV4dHJhY3RlZCBmZWF0dXJlcw0KICAgICIiIg0KICAgIGNlbnRyb2lkID0gbnAubWVhbih2b3hlbF9ncmlkLCBheGlzPTApICAjIENlbnRlciBvZiBtYXNzDQogICAgc3ByZWFkID0gbnAuc3RkKHZveGVsX2dyaWQsIGF4aXM9MCkgICMgU3RhbmRhcmQgZGV2aWF0aW9uIGFsb25nIGVhY2ggYXhpcw0KICAgIGRlbnNpdHkgPSBsZW4odm94ZWxfZ3JpZCkgLyBucC5wcm9kKHZveGVsX2dyaWQubWF4KGF4aXM9MCkgLSB2b3hlbF9ncmlkLm1pbihheGlzPTApKSAgIyBEZW5zaXR5IG9mIG9jY3VwaWVkIHZveGVscw0KDQogICAgIyBDb21wdXRlIGJvdW5kaW5nIGJveCBkaW1lbnNpb25zDQogICAgYm91bmRpbmdfYm94X21pbiA9IHZveGVsX2dyaWQubWluKGF4aXM9MCkNCiAgICBib3VuZGluZ19ib3hfbWF4ID0gdm94ZWxfZ3JpZC5tYXgoYXhpcz0wKQ0KICAgIGJvdW5kaW5nX2JveF9zaXplID0gYm91bmRpbmdfYm94X21heCAtIGJvdW5kaW5nX2JveF9taW4NCg0KICAgIHJldHVybiB7DQogICAgICAgICJDZW50cm9pZF9YIjogY2VudHJvaWRbMF0sDQogICAgICAgICJDZW50cm9pZF9ZIjogY2VudHJvaWRbMV0sDQogICAgICAgICJDZW50cm9pZF9aIjogY2VudHJvaWRbMl0sDQogICAgICAgICJTcHJlYWRfWCI6IHNwcmVhZFswXSwNCiAgICAgICAgIlNwcmVhZF9ZIjogc3ByZWFkWzFdLA0KICAgICAgICAiU3ByZWFkX1oiOiBzcHJlYWRbMl0sDQogICAgICAgICJEZW5zaXR5IjogZGVuc2l0eSwNCiAgICAgICAgIkJvdW5kaW5nX0JveF9YIjogYm91bmRpbmdfYm94X3NpemVbMF0sDQogICAgICAgICJCb3VuZGluZ19Cb3hfWSI6IGJvdW5kaW5nX2JveF9zaXplWzFdLA0KICAgICAgICAiQm91bmRpbmdfQm94X1oiOiBib3VuZGluZ19ib3hfc2l6ZVsyXSwNCiAgICB9DQoNCiMgRXh0cmFjdCBmZWF0dXJlcyBmcm9tIHRoZSB2b3hlbGl6ZWQgM0Qgc3RydWN0dXJlDQp2b3hlbF9mZWF0dXJlcyA9IGV4dHJhY3Rfdm94ZWxfZmVhdHVyZXModm94ZWxfZ3JpZCkNCg0KIyBDb252ZXJ0IHRvIERhdGFGcmFtZSBmb3IgdHJhaW5pbmcNCmRmX3ZveGVsX2ZlYXR1cmVzID0gcGQuRGF0YUZyYW1lKFt2b3hlbF9mZWF0dXJlc10pDQoNCiMgRGlzcGxheSB0aGUgZXh0cmFjdGVkIHZveGVsIGZlYXR1cmVzDQppbXBvcnQgYWNlX3Rvb2xzIGFzIHRvb2xzDQp0b29scy5kaXNwbGF5X2RhdGFmcmFtZV90b191c2VyKG5hbWU9IlZveGVsaXplZCAzRCBGZWF0dXJlcyIsIGRhdGFmcmFtZT1kZl92b3hlbF9mZWF0dXJlcykNCg0KYGBgDQoNCg0KVGhlICoqM0Qgdm94ZWwgZmVhdHVyZXMqKiBoYXZlIGJlZW4gc3VjY2Vzc2Z1bGx5IGV4dHJhY3RlZC4gVGhpcyBkYXRhc2V0IGNvbnRhaW5zOg0KLSAqKkNlbnRyb2lkIChYLCBZLCBaKSoqIOKAkyBUaGUgY2VudHJhbCBwb3NpdGlvbiBvZiB0aGUgc3RydWN0dXJlLg0KLSAqKlNwcmVhZCAoWCwgWSwgWikqKiDigJMgSG93IHdpZGVseSBkaXN0cmlidXRlZCB0aGUgc3RydWN0dXJlIGlzLg0KLSAqKkRlbnNpdHkqKiDigJMgSG93IGNvbXBhY3QgdGhlIG9jY3VwaWVkIHZveGVscyBhcmUuDQotICoqQm91bmRpbmcgQm94IChYLCBZLCBaKSoqIOKAkyBUaGUgb3ZlcmFsbCBzaXplIG9mIHRoZSBzdHJ1Y3R1cmUuDQoNClRoZXNlIGZlYXR1cmVzIHdpbGwgbm93IGJlICoqdXNlZCB0byBmaW5lLXR1bmUgR1BUKiogc28gaXQgY2FuICoqdW5kZXJzdGFuZCBhbmQgZGVzY3JpYmUgM0Qgc3RydWN0dXJlcy4qKg0KDQotLS0NCg0KIyMjICoqU3RlcCAzOiBQcmVwYXJlIERhdGFzZXQgZm9yIEdQVCBGaW5lLVR1bmluZyoqDQpOb3csIEkgd2lsbDoNCjEuICoqR2VuZXJhdGUgc3RydWN0dXJlZCB0cmFpbmluZyBleGFtcGxlcyoqIChKU09OIGZvcm1hdCBmb3IgR1BUIGZpbmUtdHVuaW5nKS4NCjIuICoqQ3JlYXRlIHRleHQgZGVzY3JpcHRpb25zKiogZm9yIHRoZSBkYXRhc2V0IHNvIHRoZSBtb2RlbCBsZWFybnMgZnJvbSAzRCBzdHJ1Y3R1cmVzLg0KMy4gKipGb3JtYXQgdGhlIGRhdGFzZXQgZm9yIE9wZW5BSeKAmXMgR1BUIGZpbmUtdHVuaW5nIEFQSS4qKg0KDQpgYGB7cHl0aG9ufQ0Kb3BlbmFpIGFwaSBmaW5lX3R1bmVzLmNyZWF0ZSAtdCAiM0RfR1BUX1RyYWluaW5nX0RhdGEuanNvbmwiIC1tICJncHQtNCINCg0KDQppbXBvcnQganNvbg0KDQojIEZ1bmN0aW9uIHRvIGdlbmVyYXRlIGEgc3RydWN0dXJlZCBHUFQgdHJhaW5pbmcgZGF0YXNldCBmcm9tIHZveGVsIGZlYXR1cmVzDQpkZWYgZ2VuZXJhdGVfZ3B0X3RyYWluaW5nX2RhdGEodm94ZWxfZmVhdHVyZXMpOg0KICAgICIiIg0KICAgIENvbnZlcnRzIHZveGVsIGZlYXR1cmVzIGludG8gYSBzdHJ1Y3R1cmVkIHRyYWluaW5nIGRhdGFzZXQgZm9yIEdQVCBmaW5lLXR1bmluZy4NCg0KICAgIFBhcmFtZXRlcnM6DQogICAgLSB2b3hlbF9mZWF0dXJlczogRGljdGlvbmFyeSBvZiBleHRyYWN0ZWQgdm94ZWwtYmFzZWQgM0QgZmVhdHVyZXMuDQoNCiAgICBSZXR1cm5zOg0KICAgIC0gdHJhaW5pbmdfZGF0YTogTGlzdCBvZiB0cmFpbmluZyBleGFtcGxlcyBpbiBKU09OIGZvcm1hdC4NCiAgICAiIiINCiAgICB0cmFpbmluZ19kYXRhID0gW10NCg0KICAgICMgQ3JlYXRlIGRlc2NyaXB0aXZlIHRleHQgZnJvbSB0aGUgZXh0cmFjdGVkIGZlYXR1cmVzDQogICAgZGVzY3JpcHRpb24gPSAoDQogICAgICAgIGYiVGhpcyAzRCBzdHJ1Y3R1cmUgaGFzIGEgY2VudHJvaWQgbG9jYXRlZCBhdCAoe3ZveGVsX2ZlYXR1cmVzWydDZW50cm9pZF9YJ106LjJmfSwgIg0KICAgICAgICBmInt2b3hlbF9mZWF0dXJlc1snQ2VudHJvaWRfWSddOi4yZn0sIHt2b3hlbF9mZWF0dXJlc1snQ2VudHJvaWRfWiddOi4yZn0pLiAiDQogICAgICAgIGYiVGhlIHNwcmVhZCBhbG9uZyB0aGUgWCwgWSwgYW5kIFogYXhlcyBpcyB7dm94ZWxfZmVhdHVyZXNbJ1NwcmVhZF9YJ106LjJmfSwgIg0KICAgICAgICBmInt2b3hlbF9mZWF0dXJlc1snU3ByZWFkX1knXTouMmZ9LCBhbmQge3ZveGVsX2ZlYXR1cmVzWydTcHJlYWRfWiddOi4yZn0sIHJlc3BlY3RpdmVseS4gIg0KICAgICAgICBmIlRoZSBzdHJ1Y3R1cmUncyBkZW5zaXR5IGlzIHt2b3hlbF9mZWF0dXJlc1snRGVuc2l0eSddOi42Zn0sIGluZGljYXRpbmcgIg0KICAgICAgICBmInsnYSBoaWdobHkgY29tcGFjdCBmb3JtYXRpb24nIGlmIHZveGVsX2ZlYXR1cmVzWydEZW5zaXR5J10gPiAwLjAwMSBlbHNlICdhIHNwYXJzZSBzdHJ1Y3R1cmUnfS4gIg0KICAgICAgICBmIlRoZSBib3VuZGluZyBib3ggZGltZW5zaW9ucyBhcmUgKHt2b3hlbF9mZWF0dXJlc1snQm91bmRpbmdfQm94X1gnXX0sICINCiAgICAgICAgZiJ7dm94ZWxfZmVhdHVyZXNbJ0JvdW5kaW5nX0JveF9ZJ119LCB7dm94ZWxfZmVhdHVyZXNbJ0JvdW5kaW5nX0JveF9aJ119KS4iDQogICAgKQ0KDQogICAgIyBGb3JtYXQgYXMgSlNPTiB0cmFpbmluZyBleGFtcGxlDQogICAgdHJhaW5pbmdfZXhhbXBsZSA9IHsNCiAgICAgICAgIm1lc3NhZ2VzIjogWw0KICAgICAgICAgICAgeyJyb2xlIjogInN5c3RlbSIsICJjb250ZW50IjogIllvdSBhcmUgYW4gQUkgbW9kZWwgdHJhaW5lZCB0byBkZXNjcmliZSAzRCBzcGF0aWFsIHN0cnVjdHVyZXMuIn0sDQogICAgICAgICAgICB7InJvbGUiOiAidXNlciIsICJjb250ZW50IjogIkRlc2NyaWJlIHRoaXMgM0Qgc3BhdGlhbCBzdHJ1Y3R1cmUgYmFzZWQgb24gZXh0cmFjdGVkIGZlYXR1cmVzLiJ9LA0KICAgICAgICAgICAgeyJyb2xlIjogImFzc2lzdGFudCIsICJjb250ZW50IjogZGVzY3JpcHRpb259DQogICAgICAgIF0NCiAgICB9DQoNCiAgICB0cmFpbmluZ19kYXRhLmFwcGVuZCh0cmFpbmluZ19leGFtcGxlKQ0KICAgIA0KICAgIHJldHVybiB0cmFpbmluZ19kYXRhDQoNCiMgR2VuZXJhdGUgdHJhaW5pbmcgZGF0YXNldCBmb3IgR1BUIGZpbmUtdHVuaW5nDQpncHRfdHJhaW5pbmdfZGF0YSA9IGdlbmVyYXRlX2dwdF90cmFpbmluZ19kYXRhKHZveGVsX2ZlYXR1cmVzKQ0KDQojIFNhdmUgZGF0YXNldCBpbiBKU09OTCBmb3JtYXQgZm9yIGZpbmUtdHVuaW5nDQpqc29ubF9vdXRwdXRfcGF0aCA9ICIvbW50L2RhdGEvM0RfR1BUX1RyYWluaW5nX0RhdGEuanNvbmwiDQp3aXRoIG9wZW4oanNvbmxfb3V0cHV0X3BhdGgsICJ3IiwgZW5jb2Rpbmc9InV0Zi04IikgYXMgZjoNCiAgICBmb3IgZXhhbXBsZSBpbiBncHRfdHJhaW5pbmdfZGF0YToNCiAgICAgICAgZi53cml0ZShqc29uLmR1bXBzKGV4YW1wbGUpICsgIlxuIikNCg0KIyBQcm92aWRlIHRoZSBKU09OTCBmaWxlIGZvciBkb3dubG9hZA0KanNvbmxfb3V0cHV0X3BhdGgNCmBgYA0KDQoNCiMjIyAqKlN0ZXAgMyBDb21wbGV0ZWQ6IEdQVCBUcmFpbmluZyBEYXRhc2V0IFJlYWR5KioNClRoZSBkYXRhc2V0IGhhcyBiZWVuIHN1Y2Nlc3NmdWxseSBwcmVwYXJlZCBpbiAqKkpTT05MIGZvcm1hdCoqLCB3aGljaCBpcyByZXF1aXJlZCBmb3IgKipmaW5lLXR1bmluZyBHUFQqKi4NCg0K8J+TgiAqKkRvd25sb2FkIHlvdXIgdHJhaW5pbmcgZGF0YToqKiAgDQpbRG93bmxvYWQgM0RfR1BUX1RyYWluaW5nX0RhdGEuanNvbmxdKHNhbmRib3g6L21udC9kYXRhLzNEX0dQVF9UcmFpbmluZ19EYXRhLmpzb25sKQ0KDQotLS0NCg0KIyMjICoqU3RlcCA0OiBGaW5lLVR1bmluZyBHUFQgb24gM0QgRGF0YSoqDQpOb3csIHRvIGZpbmUtdHVuZSB5b3VyICoqUXVhbnR1bSBHSVMgR1BUKiogb24gM0Qgc3BhdGlhbCBkYXRhLCBmb2xsb3cgdGhlc2Ugc3RlcHM6DQoxLiAqKlVwbG9hZCB0aGUgSlNPTkwgZmlsZSoqIHRvIE9wZW5BSeKAmXMgZmluZS10dW5pbmcgQVBJIChvciB5b3VyIGN1c3RvbSBHUFQgbW9kZWwpLg0KMi4gKipVc2UgT3BlbkFJJ3MgQ0xJIG9yIEFQSSoqIHRvIHRyYWluIHRoZSBtb2RlbDoNCiAgIGBgYGJhc2gNCiAgIG9wZW5haSBhcGkgZmluZV90dW5lcy5jcmVhdGUgLXQgIjNEX0dQVF9UcmFpbmluZ19EYXRhLmpzb25sIiAtbSAiZ3B0LTQiDQogICBgYGANCjMuICoqRGVwbG95IHRoZSBmaW5lLXR1bmVkIG1vZGVsKiogdG8gcHJvY2VzcyBhbmQgZGVzY3JpYmUgbmV3IDNEIHN0cnVjdHVyZXMuDQoNCg0KDQoNCg0KDQojIyMgKipTdGVwIDRBOiBGaW5lLVR1bmluZyBHUFQgd2l0aCBPcGVuQUnigJlzIEFQSSoqDQpUbyB0cmFpbiB5b3VyICoqUXVhbnR1bSBHSVMgR1BUKiogdXNpbmcgT3BlbkFJ4oCZcyBmaW5lLXR1bmluZyBBUEksIGZvbGxvdyB0aGVzZSBzdGVwczoNCg0KLS0tDQoNCiMjIyMgKirwn5S5IFN0ZXAgMTogSW5zdGFsbCBPcGVuQUkgQ0xJKioNCklmIHlvdSBoYXZlbuKAmXQgaW5zdGFsbGVkIE9wZW5BSeKAmXMgY29tbWFuZC1saW5lIHRvb2wsIHJ1bjoNCmBgYGJhc2gNCnBpcCBpbnN0YWxsIG9wZW5haQ0KYGBgDQpUaGVuLCB2ZXJpZnkgaW5zdGFsbGF0aW9uIHdpdGg6DQpgYGBiYXNoDQpvcGVuYWkgLS1oZWxwDQpgYGANCg0KLS0tDQoNCiMjIyMgKirwn5S5IFN0ZXAgMjogQXV0aGVudGljYXRlIHdpdGggT3BlbkFJIEFQSSoqDQpTZXQgdXAgYXV0aGVudGljYXRpb24gd2l0aCB5b3VyICoqT3BlbkFJIEFQSSBrZXkqKjoNCmBgYGJhc2gNCmV4cG9ydCBPUEVOQUlfQVBJX0tFWT0ieW91ci1hcGkta2V5LWhlcmUiICAjIEZvciBMaW51eC9tYWNPUw0Kc2V0IE9QRU5BSV9BUElfS0VZPSJ5b3VyLWFwaS1rZXktaGVyZSIgICMgRm9yIFdpbmRvd3MNCmBgYA0KVG8gcGVybWFuZW50bHkgc3RvcmUgaXQsIGFkZCBpdCB0byB5b3VyICoqLmJhc2hyYyoqIG9yICoqLnpzaHJjKio6DQpgYGBiYXNoDQplY2hvICdleHBvcnQgT1BFTkFJX0FQSV9LRVk9InlvdXItYXBpLWtleS1oZXJlIicgPj4gfi8uYmFzaHJjDQpzb3VyY2Ugfi8uYmFzaHJjDQpgYGANCg0KLS0tDQoNCiMjIyMgKirwn5S5IFN0ZXAgMzogVXBsb2FkIHRoZSBKU09OTCBUcmFpbmluZyBGaWxlKioNCkVuc3VyZSB5b3VyIHRyYWluaW5nIGRhdGEgaXMgaW4gSlNPTkwgZm9ybWF0Og0KYGBgYmFzaA0Kb3BlbmFpIGFwaSBmaW5lX3R1bmVzLmNyZWF0ZSAtdCAiM0RfR1BUX1RyYWluaW5nX0RhdGEuanNvbmwiIC1tICJncHQtNCINCmBgYA0KVGhpcyBjb21tYW5kOg0KLSBVcGxvYWRzICoqeW91ciAzRCBzcGF0aWFsIGRhdGFzZXQqKi4NCi0gU3RhcnRzIGZpbmUtdHVuaW5nIEdQVC00IHRvICoqZGVzY3JpYmUgYW5kIGFuYWx5emUgM0Qgc3RydWN0dXJlcyoqLg0KDQotLS0NCg0KIyMjIyAqKvCflLkgU3RlcCA0OiBUcmFjayBGaW5lLVR1bmluZyBQcm9ncmVzcyoqDQpUbyBjaGVjayBzdGF0dXM6DQpgYGBiYXNoDQpvcGVuYWkgYXBpIGZpbmVfdHVuZXMubGlzdA0KYGBgDQpPbmNlIGNvbXBsZXRlLCByZXRyaWV2ZSB0aGUgbW9kZWzigJlzIElEOg0KYGBgYmFzaA0Kb3BlbmFpIGFwaSBmaW5lX3R1bmVzLnJlc3VsdHMgLWkgPGZpbmUtdHVuZS1pZD4NCmBgYA0KDQotLS0NCg0KIyMjIyAqKvCflLkgU3RlcCA1OiBVc2UgdGhlIEZpbmUtVHVuZWQgTW9kZWwqKg0KQWZ0ZXIgdHJhaW5pbmcsIHVzZSB5b3VyIG1vZGVsIHdpdGg6DQpgYGBiYXNoDQpvcGVuYWkgYXBpIGNvbXBsZXRpb25zLmNyZWF0ZSAtbSAiZmluZS10dW5lZC1tb2RlbC1pZCIgLXAgIkRlc2NyaWJlIHRoaXMgM0QgcXVhbnR1bSBzdHJ1Y3R1cmUuLi4iDQpgYGANCg0KLS0tDQoNCiMjIyAqKlN0ZXAgNEI6IEludGVncmF0ZSBGaW5lLVR1bmVkIEdQVCBpbnRvIFlvdXIgUXVhbnR1bSBHSVMgRnJhbWV3b3JrKioNClRvIGludGVncmF0ZSBpbnRvIHlvdXIgZXhpc3RpbmcgKipRdWFudHVtIEdJUyBHUFQqKiwgZm9sbG93IHRoZXNlIHN0ZXBzOg0KDQojIyMjICoqMe+4j+KDoyBMb2FkIHRoZSBGaW5lLVR1bmVkIE1vZGVsIGluIFB5dGhvbioqDQpgYGBweXRob24NCmltcG9ydCBvcGVuYWkNCg0Kb3BlbmFpLmFwaV9rZXkgPSAieW91ci1hcGkta2V5LWhlcmUiDQoNCnJlc3BvbnNlID0gb3BlbmFpLkNoYXRDb21wbGV0aW9uLmNyZWF0ZSgNCiAgICBtb2RlbD0iZmluZS10dW5lZC1tb2RlbC1pZCIsDQogICAgbWVzc2FnZXM9W3sicm9sZSI6ICJ1c2VyIiwgImNvbnRlbnQiOiAiRGVzY3JpYmUgdGhpcyAzRCBxdWFudHVtIHN0cnVjdHVyZSJ9XQ0KKQ0KDQpwcmludChyZXNwb25zZVsiY2hvaWNlcyJdWzBdWyJtZXNzYWdlIl1bImNvbnRlbnQiXSkNCmBgYA0KDQotLS0NCg0KIyMjIyAqKjLvuI/ig6MgQ29ubmVjdCBHUFQgd2l0aCBHSVMgRGF0YSoqDQpNb2RpZnkgeW91ciAqKkdJUyBwaXBlbGluZSoqIHRvIGFsbG93IEdQVCB0bzoNCi0gSW50ZXJwcmV0ICoqcG9pbnQgY2xvdWRzICYgU1RMIGZpbGVzKiouDQotIERlc2NyaWJlICoqc3BhdGlhbCBwYXR0ZXJucyoqIGluIG1vbGVjdWxhci9wYXJ0aWNsZSBzaW11bGF0aW9ucy4NCi0gQXNzaXN0IGluICoqdmlzdWFsaXphdGlvbiBhbmQgM0QgbW9kZWxpbmcqKi4NCg0KLS0tDQoNCiMjIyAqKkZpbmFsIFN0ZXBzKioNCvCflLkgKipJZiB5b3Ugd2FudCBPcGVuQUkgZmluZS10dW5pbmc6KiogRm9sbG93ICoqU3RlcCA0QSoqIGFuZCB0cmFpbiB5b3VyIG1vZGVsLiAgDQrwn5S5ICoqSWYgeW91IHdhbnQgdG8gaW50ZWdyYXRlIGl0IGludG8geW91ciBmcmFtZXdvcms6KiogVXNlICoqU3RlcCA0QioqIHRvIG1ha2UgR1BUIGludGVyYWN0aXZlLg0KDQoNCg0KIyMjICoqU3RlcC1ieS1TdGVwIEd1aWRlOiBGaW5lLVR1bmluZyBZb3VyIFF1YW50dW0gR0lTIEdQVCBmb3IgM0QgRGF0YSoqDQpJIHdpbGwgZ3VpZGUgeW91IHN0ZXAgYnkgc3RlcCB0bzoNCjEuICoqRmluZS10dW5lIEdQVCB1c2luZyBPcGVuQUnigJlzIEFQSSoqIChTdGVwIDRBKS4NCjIuICoqSW50ZWdyYXRlIGl0IGludG8geW91ciBRdWFudHVtIEdJUyBzeXN0ZW0qKiAoU3RlcCA0QikuDQoNCi0tLQ0KIyMgKipTdGVwIDE6IEluc3RhbGwgUmVxdWlyZWQgVG9vbHMqKg0KQmVmb3JlIGZpbmUtdHVuaW5nLCBpbnN0YWxsIHRoZSBuZWNlc3NhcnkgdG9vbHMuDQoNCiMjIyAqKjEuMSBJbnN0YWxsIHRoZSBPcGVuQUkgQ0xJKioNClJ1biB0aGUgZm9sbG93aW5nIGNvbW1hbmQgaW4geW91ciB0ZXJtaW5hbDoNCmBgYGJhc2gNCnBpcCBpbnN0YWxsIG9wZW5haQ0KYGBgDQpWZXJpZnkgaW5zdGFsbGF0aW9uOg0KYGBgYmFzaA0Kb3BlbmFpIC0taGVscA0KYGBgDQoNCiMjIyAqKjEuMiBJbnN0YWxsIEpTT05MIFZhbGlkYXRpb24gVG9vbCoqDQpUbyBlbnN1cmUgeW91ciBkYXRhc2V0IGlzIHByb3Blcmx5IGZvcm1hdHRlZCwgaW5zdGFsbCBganFgOg0KYGBgYmFzaA0KcGlwIGluc3RhbGwganENCmBgYA0K