Step 3: Upload the Training Dataset
Now, we will upload the 3D training dataset
(3D_GPT_Training_Data.jsonl
) to OpenAI for
fine-tuning.
3.1 Download the JSONL Training Dataset
If you haven’t downloaded it yet, get it here: 📂 Download
3D_GPT_Training_Data.jsonl
Make sure the file is in your current working
directory (where you will run the commands).
3.3 Upload the Dataset to OpenAI
Now, upload the dataset to OpenAI:
openai api files.create -p fine-tune -f "3D_GPT_Training_Data.jsonl"
This will return a file ID like:
{
"id": "file-XXXXXXXXXXXXXX",
"object": "file",
"bytes": 123456,
"purpose": "fine-tune"
}
Copy the file ID, as you’ll need it for the next
step.
3.4 Check Uploaded Files
To verify the upload:
openai api files.list
If your dataset appears in the list, the upload was successful.
Step 4: Fine-Tune GPT-4 on the 3D Dataset
Now that the dataset has been uploaded, we will fine-tune
GPT-4 to learn from the 3D spatial structure
data.
4.1 Start the Fine-Tuning Process
Run the following command, replacing
file-XXXXXXXXXXXXXX
with your
uploaded dataset’s file ID:
openai api fine_tunes.create -t "file-XXXXXXXXXXXXXX" -m "gpt-4"
This will: - Start fine-tuning a GPT-4 model using
your dataset. - Assign a unique fine-tune ID to track
progress.
4.2 Monitor Training Progress
You can check the status of your fine-tuning job with:
openai api fine_tunes.list
or track a specific fine-tune job:
openai api fine_tunes.get -i "ft-XXXXXXXXXXXXXX"
(Replace ft-XXXXXXXXXXXXXX
with your
fine-tune ID.)
The training process may take several minutes to
hours, depending on OpenAI’s queue and dataset
size.
4.3 Retrieve the Fine-Tuned Model
Once training is complete, you’ll receive a fine-tuned model
ID (e.g., "ft:gpt-4-XXXXXXXXXXXXXX"
).
To confirm that your model is ready:
openai api fine_tunes.results -i "ft-XXXXXXXXXXXXXX"
4.4 Use Your Fine-Tuned Model
After training, you can query your fine-tuned GPT
model with:
openai api completions.create -m "ft:gpt-4-XXXXXXXXXXXXXX" -p "Describe this 3D quantum structure..."
Or, in Python:
import openai
openai.api_key = "your-api-key-here"
response = openai.ChatCompletion.create(
model="ft:gpt-4-XXXXXXXXXXXXXX",
messages=[{"role": "user", "content": "Describe this 3D spatial structure"}]
)
print(response["choices"][0]["message"]["content"])
Next Step
- Run the fine-tuning command.
- Wait for training to complete.
Step 5: Integrate Fine-Tuned GPT into Your Quantum GIS
Framework
Now that your fine-tuned 3D-aware GPT is ready,
let’s integrate it into your Quantum GIS system so it
can: ✅ Analyze 3D spatial data
✅ Generate descriptions of quantum structures
✅ Enhance GIS workflows with AI-driven insights
5.1 Set Up Your Python Environment
If you haven’t already, install the required libraries:
pip install openai numpy pandas trimesh
This ensures you have everything needed to process 3D structures and
interact with OpenAI.
5.2 Load Your Fine-Tuned GPT Model
Now, replace "ft:gpt-4-XXXXXXXXXXXXXX"
with your actual
fine-tuned model ID, and run:
import openai
openai.api_key = "your-api-key-here"
# Function to query the fine-tuned GPT model
def describe_3d_structure():
response = openai.ChatCompletion.create(
model="ft:gpt-4-XXXXXXXXXXXXXX", # Replace with your fine-tuned model ID
messages=[{"role": "user", "content": "Describe this 3D spatial structure"}]
)
return response["choices"][0]["message"]["content"]
# Get a GPT-generated description of a 3D structure
description = describe_3d_structure()
print("GPT-Generated Description of the 3D Structure:")
print(description)
This will query the model to generate a natural
language description of your 3D quantum structure.
5.3 Connect GPT to GIS Data Processing
Now, let’s integrate it with Quantum GIS (QGIS) or
ArcGIS. If you’re working with GIS software,
modify the script to: - Load STL/Point Cloud/Voxel
data. - Send the data to GPT for analysis. - Return
AI-generated insights in your GIS environment.
Example: GPT-Powered GIS Pipeline
import openai
import geopandas as gpd
openai.api_key = "your-api-key-here"
# Load spatial data (example using GeoJSON or Shapefiles)
gis_data = gpd.read_file("your_3d_structure.geojson")
# Convert GIS data into GPT-friendly format (e.g., centroid, bounding box)
gis_features = {
"centroid": gis_data.geometry.centroid[0],
"bounding_box": gis_data.total_bounds
}
# Send GIS data to GPT for analysis
response = openai.ChatCompletion.create(
model="ft:gpt-4-XXXXXXXXXXXXXX",
messages=[{"role": "user", "content": f"Analyze this GIS structure: {gis_features}"}]
)
print("AI-GIS Analysis:")
print(response["choices"][0]["message"]["content"])
5.4 Deploy GPT Inside Your GIS Workflow
- For ArcGIS Users → Use the Python script inside
ArcPy Notebooks.
- For QGIS Users → Integrate into QGIS Python
Console.
- For Web Apps → Connect to a web-based GIS
tool via OpenAI’s API.
Final Steps
✅ Your GPT is now fine-tuned and integrated!
✅ It can analyze and describe 3D GIS and quantum
structures.
✅ You can now automate 3D analysis inside your GIS
pipeline.
next
- Automate this process further? (e.g., batch
processing 3D structures)
- Integrate GPT into a Web App? (for live GIS
analysis)
- Optimize your model further? (improve
responses)
Step 6: Automate 3D Structure Processing with
GPT
Now that your Quantum GIS GPT is fine-tuned and
integrated, let’s automate the batch processing of multiple 3D
structures.
This automation will: ✅ Process multiple STL or point cloud
files automatically
✅ Extract and convert features for GPT analysis
✅ Generate AI-driven insights for each structure in
bulk
6.1 Organizing 3D Structure Files
Before we automate, place all your STL or 3D
point cloud files inside a folder.
For example:
📂 D:/Capstone research/3D_Files/
- structure1.stl
- structure2.stl
- structure3.stl
- …
Step 8: Optimize Your Fine-Tuned GPT Model for Better 3D
Analysis
Now, we will improve your GPT’s accuracy by:
✅ Refining GPT’s understanding of 3D structures
✅ Enhancing training data for better responses
✅ Fine-tuning again with additional datasets
8.1 Identify Weaknesses in the Model
Before optimizing, test your current fine-tuned GPT
model by running:
import openai
openai.api_key = "your-api-key-here"
response = openai.ChatCompletion.create(
model="ft:gpt-4-XXXXXXXXXXXXXX", # Replace with your fine-tuned model ID
messages=[{"role": "user", "content": "Describe this 3D spatial structure"}]
)
print(response["choices"][0]["message"]["content"])
Evaluate the output:
- Is the description detailed enough?
- Does it correctly interpret 3D features?
- Are there errors or inconsistencies?
8.2 Expand Training Data for More Accuracy
To improve GPT’s performance, expand the dataset
with: 1. Additional 3D structures (more STL files) 2.
Real-world GIS use cases (geospatial maps, point
clouds) 3. Multiple descriptions per object (better AI
generalization)
📌 Modify your training dataset
(3D_GPT_Training_Data.jsonl
) to include more
cases:
{"messages": [
{"role": "system", "content": "You are an AI model trained to describe 3D spatial structures."},
{"role": "user", "content": "Describe this 3D structure: Centroid (5.1, 3.2, 8.7), Volume 500.2, Bounding Box (10, 12, 15)."},
{"role": "assistant", "content": "This 3D structure is a complex spatial model with a centroid at (5.1, 3.2, 8.7)..."}
]}
{"messages": [
{"role": "system", "content": "You are an AI model trained to describe 3D spatial structures."},
{"role": "user", "content": "Describe this 3D structure: Centroid (1.3, 4.8, 9.6), Volume 102.5, Bounding Box (5, 8, 10)."},
{"role": "assistant", "content": "This spatial model represents a compact object with evenly distributed mass..."}
]}
8.3 Re-Fine-Tune GPT with the Improved Dataset
Once you have expanded the dataset, re-run:
openai api fine_tunes.create -t "updated_3D_GPT_Training_Data.jsonl" -m "gpt-4"
This will train GPT on the new dataset for more
accurate descriptions.
8.4 Validate the Improved Model
After fine-tuning is complete:
openai api fine_tunes.list
Retrieve the new fine-tuned model ID, then test
it:
response = openai.ChatCompletion.create(
model="ft:gpt-4-XXXXXXXXXXXXXX",
messages=[{"role": "user", "content": "Analyze this 3D GIS structure"}]
)
print(response["choices"][0]["message"]["content"])
✅ Step 8 Completed: Your GPT Model is Now More
Accurate!
Your Quantum GIS GPT is now better
trained on 3D structures and GIS workflows.
Next Steps:
Would you like to: 1. Deploy this as a public API for others
to use? 2. Build a more advanced AI-powered GIS
tool? 3. Optimize the web app for real-world
use?
Step 9: Deploy Your Fine-Tuned GPT Model as a Public
API
Now that your Quantum GIS GPT is optimized, we
will:
✅ Deploy it as a public API for easy access
✅ Enable external applications to use it
✅ Secure the API for real-world use
9.2 Create an API Endpoint for GPT-4
📌 Save this as gpt_3d_api.py
from fastapi import FastAPI, HTTPException
import openai
import os
# Initialize FastAPI
app = FastAPI()
# Set OpenAI API key
openai.api_key = "your-api-key-here"
@app.get("/")
def home():
return {"message": "Welcome to the Quantum GIS GPT API!"}
@app.post("/analyze_3d/")
def analyze_3d_structure(features: dict):
"""
API to analyze 3D structures using fine-tuned GPT.
Send a JSON payload with 3D spatial features.
"""
try:
prompt = (
f"This 3D structure has a centroid at ({features['Centroid_X']:.2f}, "
f"{features['Centroid_Y']:.2f}, {features['Centroid_Z']:.2f}). "
f"The bounding box dimensions are {features['Bounding_Box_X']} x {features['Bounding_Box_Y']} x {features['Bounding_Box_Z']}. "
f"The structure's volume is {features['Volume']:.2f} and its surface area is {features['Surface_Area']:.2f}. "
f"Generate an AI-based description for this 3D object."
)
response = openai.ChatCompletion.create(
model="ft:gpt-4-XXXXXXXXXXXXXX", # Replace with your fine-tuned model ID
messages=[{"role": "user", "content": prompt}]
)
return {"description": response["choices"][0]["message"]["content"]}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
9.3 Run Your API Locally
Once saved, start the API server by running:
uvicorn gpt_3d_api:app --host 0.0.0.0 --port 8000
- The API will run at:
http://localhost:8000
- You can test it by visiting:
http://localhost:8000/docs
(This will open an interactive Swagger UI where you can test your
API.)
9.4 Deploy API on a Cloud Server
To make your API publicly accessible, deploy it
using: - AWS EC2 (uvicorn
server on an
Ubuntu instance) - Google Cloud Run (serverless
deployment) - Azure App Service (PaaS hosting)
For now, I recommend deploying to Render (free and
easy): 1. Create a free account at → https://render.com 2. Click “New
Web Service” → Select your GitHub repo with
gpt_3d_api.py
3. Set Runtime
Environment:
- Python 3.10+
- Start Command:
uvicorn gpt_3d_api:app --host 0.0.0.0 --port $PORT
4.
Deploy and get a public URL → Example:
https://your-3d-gpt-api.onrender.com
5. Test with
Postman or Python:
python import requests api_url = "https://your-3d-gpt-api.onrender.com/analyze_3d/" data = {"Centroid_X": 5.1, "Centroid_Y": 3.2, "Centroid_Z": 8.7, "Volume": 500.2, "Bounding_Box_X": 10, "Bounding_Box_Y": 12, "Bounding_Box_Z": 15} response = requests.post(api_url, json=data) print(response.json())
✅ Step 9 Completed: Your GPT is Now a Public
API
🚀 Your 3D GIS GPT is live and can be used by anyone
via a simple API call!
Step 10: Build an Advanced AI-Powered GIS Tool
LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIyMgKipTdGVwIDM6IFVwbG9hZCB0aGUgVHJhaW5pbmcgRGF0YXNldCoqDQpOb3csIHdlIHdpbGwgdXBsb2FkIHRoZSAqKjNEIHRyYWluaW5nIGRhdGFzZXQgKGAzRF9HUFRfVHJhaW5pbmdfRGF0YS5qc29ubGApKiogdG8gT3BlbkFJIGZvciBmaW5lLXR1bmluZy4NCg0KLS0tDQoNCiMjIyAqKjMuMSBEb3dubG9hZCB0aGUgSlNPTkwgVHJhaW5pbmcgRGF0YXNldCoqDQpJZiB5b3UgaGF2ZW7igJl0IGRvd25sb2FkZWQgaXQgeWV0LCBnZXQgaXQgaGVyZToNCvCfk4IgKipbRG93bmxvYWQgM0RfR1BUX1RyYWluaW5nX0RhdGEuanNvbmxdKHNhbmRib3g6L21udC9kYXRhLzNEX0dQVF9UcmFpbmluZ19EYXRhLmpzb25sKSoqICANCg0KTWFrZSBzdXJlIHRoZSBmaWxlIGlzIGluIHlvdXIgKipjdXJyZW50IHdvcmtpbmcgZGlyZWN0b3J5KiogKHdoZXJlIHlvdSB3aWxsIHJ1biB0aGUgY29tbWFuZHMpLg0KDQotLS0NCg0KIyMjICoqMy4yIFZhbGlkYXRlIHRoZSBEYXRhc2V0IEZvcm1hdCoqDQpCZWZvcmUgdXBsb2FkaW5nLCBlbnN1cmUgdGhlIEpTT05MIGZpbGUgaXMgY29ycmVjdGx5IGZvcm1hdHRlZC4gUnVuOg0KYGBgYmFzaA0Kb3BlbmFpIHRvb2xzIGZpbmVfdHVuZXMucHJlcGFyZV9kYXRhIC1mIDNEX0dQVF9UcmFpbmluZ19EYXRhLmpzb25sDQpgYGANCklmIGl0IHJldHVybnM6DQpgYGANCltJTkZPXSBWYWxpZGF0aW9uIHN1Y2Nlc3NmdWwuIEZpbGUgaXMgcmVhZHkgZm9yIGZpbmUtdHVuaW5nLg0KYGBgDQpZb3UncmUgZ29vZCB0byBnbyENCg0KLS0tDQoNCiMjIyAqKjMuMyBVcGxvYWQgdGhlIERhdGFzZXQgdG8gT3BlbkFJKioNCk5vdywgdXBsb2FkIHRoZSBkYXRhc2V0IHRvIE9wZW5BSToNCmBgYGJhc2gNCm9wZW5haSBhcGkgZmlsZXMuY3JlYXRlIC1wIGZpbmUtdHVuZSAtZiAiM0RfR1BUX1RyYWluaW5nX0RhdGEuanNvbmwiDQpgYGANClRoaXMgd2lsbCByZXR1cm4gYSAqKmZpbGUgSUQqKiBsaWtlOg0KYGBgDQp7DQogICJpZCI6ICJmaWxlLVhYWFhYWFhYWFhYWFhYIiwNCiAgIm9iamVjdCI6ICJmaWxlIiwNCiAgImJ5dGVzIjogMTIzNDU2LA0KICAicHVycG9zZSI6ICJmaW5lLXR1bmUiDQp9DQpgYGANCkNvcHkgdGhlICoqZmlsZSBJRCoqLCBhcyB5b3UnbGwgbmVlZCBpdCBmb3IgdGhlIG5leHQgc3RlcC4NCg0KLS0tDQoNCiMjIyAqKjMuNCBDaGVjayBVcGxvYWRlZCBGaWxlcyoqDQpUbyB2ZXJpZnkgdGhlIHVwbG9hZDoNCmBgYGJhc2gNCm9wZW5haSBhcGkgZmlsZXMubGlzdA0KYGBgDQpJZiB5b3VyIGRhdGFzZXQgYXBwZWFycyBpbiB0aGUgbGlzdCwgdGhlIHVwbG9hZCB3YXMgc3VjY2Vzc2Z1bC4NCg0KLS0tDQoNCg0KIyMjICoqU3RlcCA0OiBGaW5lLVR1bmUgR1BULTQgb24gdGhlIDNEIERhdGFzZXQqKg0KTm93IHRoYXQgdGhlIGRhdGFzZXQgaGFzIGJlZW4gdXBsb2FkZWQsIHdlIHdpbGwgKipmaW5lLXR1bmUgR1BULTQqKiB0byBsZWFybiBmcm9tIHRoZSAqKjNEIHNwYXRpYWwgc3RydWN0dXJlIGRhdGEqKi4NCg0KLS0tDQoNCiMjIyAqKjQuMSBTdGFydCB0aGUgRmluZS1UdW5pbmcgUHJvY2VzcyoqDQpSdW4gdGhlIGZvbGxvd2luZyBjb21tYW5kLCByZXBsYWNpbmcgKipgZmlsZS1YWFhYWFhYWFhYWFhYWGAqKiB3aXRoIHlvdXIgKip1cGxvYWRlZCBkYXRhc2V0J3MgZmlsZSBJRCoqOg0KYGBgYmFzaA0Kb3BlbmFpIGFwaSBmaW5lX3R1bmVzLmNyZWF0ZSAtdCAiZmlsZS1YWFhYWFhYWFhYWFhYWCIgLW0gImdwdC00Ig0KYGBgDQpUaGlzIHdpbGw6DQotIFN0YXJ0IGZpbmUtdHVuaW5nIGEgKipHUFQtNCBtb2RlbCoqIHVzaW5nIHlvdXIgZGF0YXNldC4NCi0gQXNzaWduIGEgdW5pcXVlICoqZmluZS10dW5lIElEKiogdG8gdHJhY2sgcHJvZ3Jlc3MuDQoNCi0tLQ0KDQojIyMgKio0LjIgTW9uaXRvciBUcmFpbmluZyBQcm9ncmVzcyoqDQpZb3UgY2FuIGNoZWNrIHRoZSBzdGF0dXMgb2YgeW91ciBmaW5lLXR1bmluZyBqb2Igd2l0aDoNCmBgYGJhc2gNCm9wZW5haSBhcGkgZmluZV90dW5lcy5saXN0DQpgYGANCm9yIHRyYWNrIGEgc3BlY2lmaWMgZmluZS10dW5lIGpvYjoNCmBgYGJhc2gNCm9wZW5haSBhcGkgZmluZV90dW5lcy5nZXQgLWkgImZ0LVhYWFhYWFhYWFhYWFhYIg0KYGBgDQooUmVwbGFjZSAqKmBmdC1YWFhYWFhYWFhYWFhYWGAqKiB3aXRoIHlvdXIgZmluZS10dW5lIElELikNCg0KVGhlIHRyYWluaW5nIHByb2Nlc3MgbWF5IHRha2UgKipzZXZlcmFsIG1pbnV0ZXMgdG8gaG91cnMqKiwgZGVwZW5kaW5nIG9uICoqT3BlbkFJ4oCZcyBxdWV1ZSBhbmQgZGF0YXNldCBzaXplKiouDQoNCi0tLQ0KDQojIyMgKio0LjMgUmV0cmlldmUgdGhlIEZpbmUtVHVuZWQgTW9kZWwqKg0KT25jZSB0cmFpbmluZyBpcyBjb21wbGV0ZSwgeW914oCZbGwgcmVjZWl2ZSBhICoqZmluZS10dW5lZCBtb2RlbCBJRCoqIChlLmcuLCBgImZ0OmdwdC00LVhYWFhYWFhYWFhYWFhYImApLg0KDQpUbyBjb25maXJtIHRoYXQgeW91ciBtb2RlbCBpcyByZWFkeToNCmBgYGJhc2gNCm9wZW5haSBhcGkgZmluZV90dW5lcy5yZXN1bHRzIC1pICJmdC1YWFhYWFhYWFhYWFhYWCINCmBgYA0KDQotLS0NCg0KIyMjICoqNC40IFVzZSBZb3VyIEZpbmUtVHVuZWQgTW9kZWwqKg0KQWZ0ZXIgdHJhaW5pbmcsIHlvdSBjYW4gKipxdWVyeSB5b3VyIGZpbmUtdHVuZWQgR1BUIG1vZGVsKiogd2l0aDoNCmBgYGJhc2gNCm9wZW5haSBhcGkgY29tcGxldGlvbnMuY3JlYXRlIC1tICJmdDpncHQtNC1YWFhYWFhYWFhYWFhYWCIgLXAgIkRlc2NyaWJlIHRoaXMgM0QgcXVhbnR1bSBzdHJ1Y3R1cmUuLi4iDQpgYGANCk9yLCBpbiAqKlB5dGhvbioqOg0KYGBgcHl0aG9uDQppbXBvcnQgb3BlbmFpDQoNCm9wZW5haS5hcGlfa2V5ID0gInlvdXItYXBpLWtleS1oZXJlIg0KDQpyZXNwb25zZSA9IG9wZW5haS5DaGF0Q29tcGxldGlvbi5jcmVhdGUoDQogICAgbW9kZWw9ImZ0OmdwdC00LVhYWFhYWFhYWFhYWFhYIiwNCiAgICBtZXNzYWdlcz1beyJyb2xlIjogInVzZXIiLCAiY29udGVudCI6ICJEZXNjcmliZSB0aGlzIDNEIHNwYXRpYWwgc3RydWN0dXJlIn1dDQopDQoNCnByaW50KHJlc3BvbnNlWyJjaG9pY2VzIl1bMF1bIm1lc3NhZ2UiXVsiY29udGVudCJdKQ0KYGBgDQotLS0NCg0KIyMjICoqTmV4dCBTdGVwKioNCjEuIFJ1biB0aGUgZmluZS10dW5pbmcgY29tbWFuZC4NCjIuIFdhaXQgZm9yIHRyYWluaW5nIHRvIGNvbXBsZXRlLg0KDQoNCg0KIyMjICoqU3RlcCA1OiBJbnRlZ3JhdGUgRmluZS1UdW5lZCBHUFQgaW50byBZb3VyIFF1YW50dW0gR0lTIEZyYW1ld29yayoqDQpOb3cgdGhhdCB5b3VyIGZpbmUtdHVuZWQgKiozRC1hd2FyZSBHUFQqKiBpcyByZWFkeSwgbGV04oCZcyBpbnRlZ3JhdGUgaXQgaW50byB5b3VyICoqUXVhbnR1bSBHSVMgc3lzdGVtKiogc28gaXQgY2FuOg0K4pyFICoqQW5hbHl6ZSAzRCBzcGF0aWFsIGRhdGEqKiAgDQrinIUgKipHZW5lcmF0ZSBkZXNjcmlwdGlvbnMgb2YgcXVhbnR1bSBzdHJ1Y3R1cmVzKiogIA0K4pyFICoqRW5oYW5jZSBHSVMgd29ya2Zsb3dzIHdpdGggQUktZHJpdmVuIGluc2lnaHRzKiogIA0KDQotLS0NCg0KIyMjICoqNS4xIFNldCBVcCBZb3VyIFB5dGhvbiBFbnZpcm9ubWVudCoqDQpJZiB5b3UgaGF2ZW7igJl0IGFscmVhZHksIGluc3RhbGwgdGhlIHJlcXVpcmVkIGxpYnJhcmllczoNCmBgYGJhc2gNCnBpcCBpbnN0YWxsIG9wZW5haSBudW1weSBwYW5kYXMgdHJpbWVzaA0KYGBgDQpUaGlzIGVuc3VyZXMgeW91IGhhdmUgZXZlcnl0aGluZyBuZWVkZWQgdG8gcHJvY2VzcyAzRCBzdHJ1Y3R1cmVzIGFuZCBpbnRlcmFjdCB3aXRoIE9wZW5BSS4NCg0KLS0tDQoNCiMjIyAqKjUuMiBMb2FkIFlvdXIgRmluZS1UdW5lZCBHUFQgTW9kZWwqKg0KTm93LCByZXBsYWNlIGAiZnQ6Z3B0LTQtWFhYWFhYWFhYWFhYWFgiYCB3aXRoIHlvdXIgYWN0dWFsICoqZmluZS10dW5lZCBtb2RlbCBJRCoqLCBhbmQgcnVuOg0KDQpgYGBweXRob24NCmltcG9ydCBvcGVuYWkNCg0Kb3BlbmFpLmFwaV9rZXkgPSAieW91ci1hcGkta2V5LWhlcmUiDQoNCiMgRnVuY3Rpb24gdG8gcXVlcnkgdGhlIGZpbmUtdHVuZWQgR1BUIG1vZGVsDQpkZWYgZGVzY3JpYmVfM2Rfc3RydWN0dXJlKCk6DQogICAgcmVzcG9uc2UgPSBvcGVuYWkuQ2hhdENvbXBsZXRpb24uY3JlYXRlKA0KICAgICAgICBtb2RlbD0iZnQ6Z3B0LTQtWFhYWFhYWFhYWFhYWFgiLCAgIyBSZXBsYWNlIHdpdGggeW91ciBmaW5lLXR1bmVkIG1vZGVsIElEDQogICAgICAgIG1lc3NhZ2VzPVt7InJvbGUiOiAidXNlciIsICJjb250ZW50IjogIkRlc2NyaWJlIHRoaXMgM0Qgc3BhdGlhbCBzdHJ1Y3R1cmUifV0NCiAgICApDQogICAgcmV0dXJuIHJlc3BvbnNlWyJjaG9pY2VzIl1bMF1bIm1lc3NhZ2UiXVsiY29udGVudCJdDQoNCiMgR2V0IGEgR1BULWdlbmVyYXRlZCBkZXNjcmlwdGlvbiBvZiBhIDNEIHN0cnVjdHVyZQ0KZGVzY3JpcHRpb24gPSBkZXNjcmliZV8zZF9zdHJ1Y3R1cmUoKQ0KcHJpbnQoIkdQVC1HZW5lcmF0ZWQgRGVzY3JpcHRpb24gb2YgdGhlIDNEIFN0cnVjdHVyZToiKQ0KcHJpbnQoZGVzY3JpcHRpb24pDQpgYGANCg0KVGhpcyB3aWxsICoqcXVlcnkgdGhlIG1vZGVsKiogdG8gZ2VuZXJhdGUgYSBuYXR1cmFsIGxhbmd1YWdlIGRlc2NyaXB0aW9uIG9mIHlvdXIgM0QgcXVhbnR1bSBzdHJ1Y3R1cmUuDQoNCi0tLQ0KDQojIyMgKio1LjMgQ29ubmVjdCBHUFQgdG8gR0lTIERhdGEgUHJvY2Vzc2luZyoqDQpOb3csIGxldOKAmXMgaW50ZWdyYXRlIGl0IHdpdGggKipRdWFudHVtIEdJUyAoUUdJUykgb3IgQXJjR0lTKiouIElmIHlvdeKAmXJlIHdvcmtpbmcgd2l0aCAqKkdJUyBzb2Z0d2FyZSoqLCBtb2RpZnkgdGhlIHNjcmlwdCB0bzoNCi0gTG9hZCAqKlNUTC9Qb2ludCBDbG91ZC9Wb3hlbCBkYXRhKiouDQotIFNlbmQgdGhlIGRhdGEgdG8gR1BUIGZvciBhbmFseXNpcy4NCi0gUmV0dXJuIEFJLWdlbmVyYXRlZCBpbnNpZ2h0cyBpbiAqKnlvdXIgR0lTIGVudmlyb25tZW50KiouDQoNCiMjIyMgKipFeGFtcGxlOiBHUFQtUG93ZXJlZCBHSVMgUGlwZWxpbmUqKg0KYGBgcHl0aG9uDQppbXBvcnQgb3BlbmFpDQppbXBvcnQgZ2VvcGFuZGFzIGFzIGdwZA0KDQpvcGVuYWkuYXBpX2tleSA9ICJ5b3VyLWFwaS1rZXktaGVyZSINCg0KIyBMb2FkIHNwYXRpYWwgZGF0YSAoZXhhbXBsZSB1c2luZyBHZW9KU09OIG9yIFNoYXBlZmlsZXMpDQpnaXNfZGF0YSA9IGdwZC5yZWFkX2ZpbGUoInlvdXJfM2Rfc3RydWN0dXJlLmdlb2pzb24iKQ0KDQojIENvbnZlcnQgR0lTIGRhdGEgaW50byBHUFQtZnJpZW5kbHkgZm9ybWF0IChlLmcuLCBjZW50cm9pZCwgYm91bmRpbmcgYm94KQ0KZ2lzX2ZlYXR1cmVzID0gew0KICAgICJjZW50cm9pZCI6IGdpc19kYXRhLmdlb21ldHJ5LmNlbnRyb2lkWzBdLA0KICAgICJib3VuZGluZ19ib3giOiBnaXNfZGF0YS50b3RhbF9ib3VuZHMNCn0NCg0KIyBTZW5kIEdJUyBkYXRhIHRvIEdQVCBmb3IgYW5hbHlzaXMNCnJlc3BvbnNlID0gb3BlbmFpLkNoYXRDb21wbGV0aW9uLmNyZWF0ZSgNCiAgICBtb2RlbD0iZnQ6Z3B0LTQtWFhYWFhYWFhYWFhYWFgiLA0KICAgIG1lc3NhZ2VzPVt7InJvbGUiOiAidXNlciIsICJjb250ZW50IjogZiJBbmFseXplIHRoaXMgR0lTIHN0cnVjdHVyZToge2dpc19mZWF0dXJlc30ifV0NCikNCg0KcHJpbnQoIkFJLUdJUyBBbmFseXNpczoiKQ0KcHJpbnQocmVzcG9uc2VbImNob2ljZXMiXVswXVsibWVzc2FnZSJdWyJjb250ZW50Il0pDQpgYGANCi0tLQ0KDQojIyMgKio1LjQgRGVwbG95IEdQVCBJbnNpZGUgWW91ciBHSVMgV29ya2Zsb3cqKg0KMS4gKipGb3IgQXJjR0lTIFVzZXJzKiog4oaSIFVzZSB0aGUgUHl0aG9uIHNjcmlwdCBpbnNpZGUgKipBcmNQeSBOb3RlYm9va3MqKi4NCjIuICoqRm9yIFFHSVMgVXNlcnMqKiDihpIgSW50ZWdyYXRlIGludG8gKipRR0lTIFB5dGhvbiBDb25zb2xlKiouDQozLiAqKkZvciBXZWIgQXBwcyoqIOKGkiBDb25uZWN0IHRvIGEgKip3ZWItYmFzZWQgR0lTIHRvb2wqKiB2aWEgT3BlbkFJ4oCZcyBBUEkuDQoNCi0tLQ0KDQojIyMgKipGaW5hbCBTdGVwcyoqDQrinIUgKipZb3VyIEdQVCBpcyBub3cgZmluZS10dW5lZCBhbmQgaW50ZWdyYXRlZCEqKiAgDQrinIUgKipJdCBjYW4gYW5hbHl6ZSBhbmQgZGVzY3JpYmUgM0QgR0lTIGFuZCBxdWFudHVtIHN0cnVjdHVyZXMuKiogIA0K4pyFICoqWW91IGNhbiBub3cgYXV0b21hdGUgM0QgYW5hbHlzaXMgaW5zaWRlIHlvdXIgR0lTIHBpcGVsaW5lLioqDQoNCi0tLQ0KDQojIyMgKipuZXh0KioNCjEuICoqQXV0b21hdGUgdGhpcyBwcm9jZXNzIGZ1cnRoZXI/KiogKGUuZy4sIGJhdGNoIHByb2Nlc3NpbmcgM0Qgc3RydWN0dXJlcykNCjIuICoqSW50ZWdyYXRlIEdQVCBpbnRvIGEgV2ViIEFwcD8qKiAoZm9yIGxpdmUgR0lTIGFuYWx5c2lzKQ0KMy4gKipPcHRpbWl6ZSB5b3VyIG1vZGVsIGZ1cnRoZXI/KiogKGltcHJvdmUgcmVzcG9uc2VzKQ0KDQoNCg0KIyMjICoqU3RlcCA2OiBBdXRvbWF0ZSAzRCBTdHJ1Y3R1cmUgUHJvY2Vzc2luZyB3aXRoIEdQVCoqICANCk5vdyB0aGF0IHlvdXIgKipRdWFudHVtIEdJUyBHUFQqKiBpcyBmaW5lLXR1bmVkIGFuZCBpbnRlZ3JhdGVkLCBsZXTigJlzIGF1dG9tYXRlIHRoZSAqKmJhdGNoIHByb2Nlc3Npbmcgb2YgbXVsdGlwbGUgM0Qgc3RydWN0dXJlcyoqLg0KDQpUaGlzIGF1dG9tYXRpb24gd2lsbDoNCuKchSAqKlByb2Nlc3MgbXVsdGlwbGUgU1RMIG9yIHBvaW50IGNsb3VkIGZpbGVzIGF1dG9tYXRpY2FsbHkqKiAgDQrinIUgKipFeHRyYWN0IGFuZCBjb252ZXJ0IGZlYXR1cmVzIGZvciBHUFQgYW5hbHlzaXMqKiAgDQrinIUgKipHZW5lcmF0ZSBBSS1kcml2ZW4gaW5zaWdodHMgZm9yIGVhY2ggc3RydWN0dXJlIGluIGJ1bGsqKiAgDQoNCi0tLQ0KDQojIyMgKio2LjEgT3JnYW5pemluZyAzRCBTdHJ1Y3R1cmUgRmlsZXMqKiAgDQpCZWZvcmUgd2UgYXV0b21hdGUsIHBsYWNlIGFsbCB5b3VyICoqU1RMKiogb3IgKiozRCBwb2ludCBjbG91ZCBmaWxlcyoqIGluc2lkZSBhIGZvbGRlci4gIA0KRm9yIGV4YW1wbGU6ICANCvCfk4IgKipEOi9DYXBzdG9uZSByZXNlYXJjaC8zRF9GaWxlcy8qKiAgDQotIGBzdHJ1Y3R1cmUxLnN0bGAgIA0KLSBgc3RydWN0dXJlMi5zdGxgICANCi0gYHN0cnVjdHVyZTMuc3RsYCAgDQotIC4uLiAgDQoNCg0KDQoNCiMjIyAqKlN0ZXAgODogT3B0aW1pemUgWW91ciBGaW5lLVR1bmVkIEdQVCBNb2RlbCBmb3IgQmV0dGVyIDNEIEFuYWx5c2lzKioNCk5vdywgd2Ugd2lsbCAqKmltcHJvdmUgeW91ciBHUFTigJlzIGFjY3VyYWN5KiogYnk6ICANCuKchSAqKlJlZmluaW5nIEdQVOKAmXMgdW5kZXJzdGFuZGluZyBvZiAzRCBzdHJ1Y3R1cmVzKiogIA0K4pyFICoqRW5oYW5jaW5nIHRyYWluaW5nIGRhdGEgZm9yIGJldHRlciByZXNwb25zZXMqKiAgDQrinIUgKipGaW5lLXR1bmluZyBhZ2FpbiB3aXRoIGFkZGl0aW9uYWwgZGF0YXNldHMqKiAgDQoNCi0tLQ0KDQojIyMgKio4LjEgSWRlbnRpZnkgV2Vha25lc3NlcyBpbiB0aGUgTW9kZWwqKg0KQmVmb3JlIG9wdGltaXppbmcsICoqdGVzdCB5b3VyIGN1cnJlbnQgZmluZS10dW5lZCBHUFQgbW9kZWwqKiBieSBydW5uaW5nOg0KYGBgcHl0aG9uDQppbXBvcnQgb3BlbmFpDQoNCm9wZW5haS5hcGlfa2V5ID0gInlvdXItYXBpLWtleS1oZXJlIg0KDQpyZXNwb25zZSA9IG9wZW5haS5DaGF0Q29tcGxldGlvbi5jcmVhdGUoDQogICAgbW9kZWw9ImZ0OmdwdC00LVhYWFhYWFhYWFhYWFhYIiwgICMgUmVwbGFjZSB3aXRoIHlvdXIgZmluZS10dW5lZCBtb2RlbCBJRA0KICAgIG1lc3NhZ2VzPVt7InJvbGUiOiAidXNlciIsICJjb250ZW50IjogIkRlc2NyaWJlIHRoaXMgM0Qgc3BhdGlhbCBzdHJ1Y3R1cmUifV0NCikNCg0KcHJpbnQocmVzcG9uc2VbImNob2ljZXMiXVswXVsibWVzc2FnZSJdWyJjb250ZW50Il0pDQpgYGANCiMjIyMgKipFdmFsdWF0ZSB0aGUgb3V0cHV0OioqDQotICoqSXMgdGhlIGRlc2NyaXB0aW9uIGRldGFpbGVkIGVub3VnaD8qKg0KLSAqKkRvZXMgaXQgY29ycmVjdGx5IGludGVycHJldCAzRCBmZWF0dXJlcz8qKg0KLSAqKkFyZSB0aGVyZSBlcnJvcnMgb3IgaW5jb25zaXN0ZW5jaWVzPyoqDQoNCi0tLQ0KDQojIyMgKio4LjIgRXhwYW5kIFRyYWluaW5nIERhdGEgZm9yIE1vcmUgQWNjdXJhY3kqKg0KVG8gaW1wcm92ZSBHUFTigJlzIHBlcmZvcm1hbmNlLCAqKmV4cGFuZCB0aGUgZGF0YXNldCoqIHdpdGg6DQoxLiAqKkFkZGl0aW9uYWwgM0Qgc3RydWN0dXJlcyoqIChtb3JlIFNUTCBmaWxlcykNCjIuICoqUmVhbC13b3JsZCBHSVMgdXNlIGNhc2VzKiogKGdlb3NwYXRpYWwgbWFwcywgcG9pbnQgY2xvdWRzKQ0KMy4gKipNdWx0aXBsZSBkZXNjcmlwdGlvbnMgcGVyIG9iamVjdCoqIChiZXR0ZXIgQUkgZ2VuZXJhbGl6YXRpb24pDQoNCvCfk4wgKipNb2RpZnkgeW91ciB0cmFpbmluZyBkYXRhc2V0IChgM0RfR1BUX1RyYWluaW5nX0RhdGEuanNvbmxgKSB0byBpbmNsdWRlIG1vcmUgY2FzZXM6KiogIA0KYGBganNvbg0KeyJtZXNzYWdlcyI6IFsNCiAgeyJyb2xlIjogInN5c3RlbSIsICJjb250ZW50IjogIllvdSBhcmUgYW4gQUkgbW9kZWwgdHJhaW5lZCB0byBkZXNjcmliZSAzRCBzcGF0aWFsIHN0cnVjdHVyZXMuIn0sDQogIHsicm9sZSI6ICJ1c2VyIiwgImNvbnRlbnQiOiAiRGVzY3JpYmUgdGhpcyAzRCBzdHJ1Y3R1cmU6IENlbnRyb2lkICg1LjEsIDMuMiwgOC43KSwgVm9sdW1lIDUwMC4yLCBCb3VuZGluZyBCb3ggKDEwLCAxMiwgMTUpLiJ9LA0KICB7InJvbGUiOiAiYXNzaXN0YW50IiwgImNvbnRlbnQiOiAiVGhpcyAzRCBzdHJ1Y3R1cmUgaXMgYSBjb21wbGV4IHNwYXRpYWwgbW9kZWwgd2l0aCBhIGNlbnRyb2lkIGF0ICg1LjEsIDMuMiwgOC43KS4uLiJ9DQpdfQ0KeyJtZXNzYWdlcyI6IFsNCiAgeyJyb2xlIjogInN5c3RlbSIsICJjb250ZW50IjogIllvdSBhcmUgYW4gQUkgbW9kZWwgdHJhaW5lZCB0byBkZXNjcmliZSAzRCBzcGF0aWFsIHN0cnVjdHVyZXMuIn0sDQogIHsicm9sZSI6ICJ1c2VyIiwgImNvbnRlbnQiOiAiRGVzY3JpYmUgdGhpcyAzRCBzdHJ1Y3R1cmU6IENlbnRyb2lkICgxLjMsIDQuOCwgOS42KSwgVm9sdW1lIDEwMi41LCBCb3VuZGluZyBCb3ggKDUsIDgsIDEwKS4ifSwNCiAgeyJyb2xlIjogImFzc2lzdGFudCIsICJjb250ZW50IjogIlRoaXMgc3BhdGlhbCBtb2RlbCByZXByZXNlbnRzIGEgY29tcGFjdCBvYmplY3Qgd2l0aCBldmVubHkgZGlzdHJpYnV0ZWQgbWFzcy4uLiJ9DQpdfQ0KYGBgDQoNCi0tLQ0KDQojIyMgKio4LjMgUmUtRmluZS1UdW5lIEdQVCB3aXRoIHRoZSBJbXByb3ZlZCBEYXRhc2V0KioNCk9uY2UgeW91IGhhdmUgZXhwYW5kZWQgdGhlIGRhdGFzZXQsIHJlLXJ1bjoNCmBgYGJhc2gNCm9wZW5haSBhcGkgZmluZV90dW5lcy5jcmVhdGUgLXQgInVwZGF0ZWRfM0RfR1BUX1RyYWluaW5nX0RhdGEuanNvbmwiIC1tICJncHQtNCINCmBgYA0KVGhpcyB3aWxsIHRyYWluICoqR1BUIG9uIHRoZSBuZXcgZGF0YXNldCoqIGZvciBtb3JlIGFjY3VyYXRlIGRlc2NyaXB0aW9ucy4NCg0KLS0tDQoNCiMjIyAqKjguNCBWYWxpZGF0ZSB0aGUgSW1wcm92ZWQgTW9kZWwqKg0KQWZ0ZXIgZmluZS10dW5pbmcgaXMgY29tcGxldGU6DQpgYGBiYXNoDQpvcGVuYWkgYXBpIGZpbmVfdHVuZXMubGlzdA0KYGBgDQpSZXRyaWV2ZSB0aGUgKipuZXcgZmluZS10dW5lZCBtb2RlbCBJRCoqLCB0aGVuIHRlc3QgaXQ6DQpgYGBweXRob24NCnJlc3BvbnNlID0gb3BlbmFpLkNoYXRDb21wbGV0aW9uLmNyZWF0ZSgNCiAgICBtb2RlbD0iZnQ6Z3B0LTQtWFhYWFhYWFhYWFhYWFgiLA0KICAgIG1lc3NhZ2VzPVt7InJvbGUiOiAidXNlciIsICJjb250ZW50IjogIkFuYWx5emUgdGhpcyAzRCBHSVMgc3RydWN0dXJlIn1dDQopDQpwcmludChyZXNwb25zZVsiY2hvaWNlcyJdWzBdWyJtZXNzYWdlIl1bImNvbnRlbnQiXSkNCmBgYA0KDQotLS0NCg0KIyMjICoq4pyFIFN0ZXAgOCBDb21wbGV0ZWQ6IFlvdXIgR1BUIE1vZGVsIGlzIE5vdyBNb3JlIEFjY3VyYXRlISoqDQpZb3VyICoqUXVhbnR1bSBHSVMgR1BUKiogaXMgbm93ICoqYmV0dGVyIHRyYWluZWQqKiBvbiAzRCBzdHJ1Y3R1cmVzIGFuZCBHSVMgd29ya2Zsb3dzLg0KDQojIyMgKipOZXh0IFN0ZXBzOioqDQpXb3VsZCB5b3UgbGlrZSB0bzoNCjEuICoqRGVwbG95IHRoaXMgYXMgYSBwdWJsaWMgQVBJIGZvciBvdGhlcnMgdG8gdXNlPyoqDQoyLiAqKkJ1aWxkIGEgbW9yZSBhZHZhbmNlZCBBSS1wb3dlcmVkIEdJUyB0b29sPyoqDQozLiAqKk9wdGltaXplIHRoZSB3ZWIgYXBwIGZvciByZWFsLXdvcmxkIHVzZT8qKg0KDQoNCg0KIyMjICoqU3RlcCA5OiBEZXBsb3kgWW91ciBGaW5lLVR1bmVkIEdQVCBNb2RlbCBhcyBhIFB1YmxpYyBBUEkqKg0KTm93IHRoYXQgeW91ciAqKlF1YW50dW0gR0lTIEdQVCBpcyBvcHRpbWl6ZWQqKiwgd2Ugd2lsbDogIA0K4pyFICoqRGVwbG95IGl0IGFzIGEgcHVibGljIEFQSSoqIGZvciBlYXN5IGFjY2VzcyAgDQrinIUgKipFbmFibGUgZXh0ZXJuYWwgYXBwbGljYXRpb25zIHRvIHVzZSBpdCoqICANCuKchSAqKlNlY3VyZSB0aGUgQVBJIGZvciByZWFsLXdvcmxkIHVzZSoqICANCg0KLS0tDQoNCiMjIyAqKjkuMSBDaG9vc2UgYSBEZXBsb3ltZW50IFBsYXRmb3JtKioNCllvdSBjYW4gZGVwbG95IHlvdXIgQVBJIHVzaW5nOg0KMS4gKipGYXN0QVBJKiogKFB5dGhvbi1iYXNlZCwgbGlnaHR3ZWlnaHQsIGdyZWF0IGZvciBBSSBtb2RlbHMpICANCjIuICoqRmxhc2sqKiAoRWFzaWVyIHNldHVwLCBidXQgc2xvd2VyIHRoYW4gRmFzdEFQSSkgIA0KMy4gKipDbG91ZCBTZXJ2aWNlcyoqIChBV1MsIEdvb2dsZSBDbG91ZCwgQXp1cmUsIGV0Yy4pDQoNCldl4oCZbGwgc3RhcnQgd2l0aCAqKkZhc3RBUEkqKiwgc2luY2UgaXTigJlzICoqZmFzdGVyKiogYW5kIG1vcmUgKipzY2FsYWJsZSoqLg0KDQrwn5OMICoqSW5zdGFsbCBGYXN0QVBJICYgVXZpY29ybioqIChmb3IgQVBJIHNlcnZpbmcpOg0KYGBgYmFzaA0KcGlwIGluc3RhbGwgZmFzdGFwaSB1dmljb3JuIG9wZW5haQ0KYGBgDQoNCi0tLQ0KDQojIyMgKio5LjIgQ3JlYXRlIGFuIEFQSSBFbmRwb2ludCBmb3IgR1BULTQqKg0K8J+TjCAqKlNhdmUgdGhpcyBhcyBgZ3B0XzNkX2FwaS5weWAqKg0KYGBgcHl0aG9uDQpmcm9tIGZhc3RhcGkgaW1wb3J0IEZhc3RBUEksIEhUVFBFeGNlcHRpb24NCmltcG9ydCBvcGVuYWkNCmltcG9ydCBvcw0KDQojIEluaXRpYWxpemUgRmFzdEFQSQ0KYXBwID0gRmFzdEFQSSgpDQoNCiMgU2V0IE9wZW5BSSBBUEkga2V5DQpvcGVuYWkuYXBpX2tleSA9ICJ5b3VyLWFwaS1rZXktaGVyZSINCg0KQGFwcC5nZXQoIi8iKQ0KZGVmIGhvbWUoKToNCiAgICByZXR1cm4geyJtZXNzYWdlIjogIldlbGNvbWUgdG8gdGhlIFF1YW50dW0gR0lTIEdQVCBBUEkhIn0NCg0KQGFwcC5wb3N0KCIvYW5hbHl6ZV8zZC8iKQ0KZGVmIGFuYWx5emVfM2Rfc3RydWN0dXJlKGZlYXR1cmVzOiBkaWN0KToNCiAgICAiIiINCiAgICBBUEkgdG8gYW5hbHl6ZSAzRCBzdHJ1Y3R1cmVzIHVzaW5nIGZpbmUtdHVuZWQgR1BULg0KICAgIFNlbmQgYSBKU09OIHBheWxvYWQgd2l0aCAzRCBzcGF0aWFsIGZlYXR1cmVzLg0KICAgICIiIg0KICAgIHRyeToNCiAgICAgICAgcHJvbXB0ID0gKA0KICAgICAgICAgICAgZiJUaGlzIDNEIHN0cnVjdHVyZSBoYXMgYSBjZW50cm9pZCBhdCAoe2ZlYXR1cmVzWydDZW50cm9pZF9YJ106LjJmfSwgIg0KICAgICAgICAgICAgZiJ7ZmVhdHVyZXNbJ0NlbnRyb2lkX1knXTouMmZ9LCB7ZmVhdHVyZXNbJ0NlbnRyb2lkX1onXTouMmZ9KS4gIg0KICAgICAgICAgICAgZiJUaGUgYm91bmRpbmcgYm94IGRpbWVuc2lvbnMgYXJlIHtmZWF0dXJlc1snQm91bmRpbmdfQm94X1gnXX0geCB7ZmVhdHVyZXNbJ0JvdW5kaW5nX0JveF9ZJ119IHgge2ZlYXR1cmVzWydCb3VuZGluZ19Cb3hfWiddfS4gIg0KICAgICAgICAgICAgZiJUaGUgc3RydWN0dXJlJ3Mgdm9sdW1lIGlzIHtmZWF0dXJlc1snVm9sdW1lJ106LjJmfSBhbmQgaXRzIHN1cmZhY2UgYXJlYSBpcyB7ZmVhdHVyZXNbJ1N1cmZhY2VfQXJlYSddOi4yZn0uICINCiAgICAgICAgICAgIGYiR2VuZXJhdGUgYW4gQUktYmFzZWQgZGVzY3JpcHRpb24gZm9yIHRoaXMgM0Qgb2JqZWN0LiINCiAgICAgICAgKQ0KDQogICAgICAgIHJlc3BvbnNlID0gb3BlbmFpLkNoYXRDb21wbGV0aW9uLmNyZWF0ZSgNCiAgICAgICAgICAgIG1vZGVsPSJmdDpncHQtNC1YWFhYWFhYWFhYWFhYWCIsICAjIFJlcGxhY2Ugd2l0aCB5b3VyIGZpbmUtdHVuZWQgbW9kZWwgSUQNCiAgICAgICAgICAgIG1lc3NhZ2VzPVt7InJvbGUiOiAidXNlciIsICJjb250ZW50IjogcHJvbXB0fV0NCiAgICAgICAgKQ0KICAgICAgICANCiAgICAgICAgcmV0dXJuIHsiZGVzY3JpcHRpb24iOiByZXNwb25zZVsiY2hvaWNlcyJdWzBdWyJtZXNzYWdlIl1bImNvbnRlbnQiXX0NCiAgICANCiAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6DQogICAgICAgIHJhaXNlIEhUVFBFeGNlcHRpb24oc3RhdHVzX2NvZGU9NTAwLCBkZXRhaWw9c3RyKGUpKQ0KDQpgYGANCg0KLS0tDQoNCiMjIyAqKjkuMyBSdW4gWW91ciBBUEkgTG9jYWxseSoqDQpPbmNlIHNhdmVkLCBzdGFydCB0aGUgQVBJIHNlcnZlciBieSBydW5uaW5nOg0KYGBgYmFzaA0KdXZpY29ybiBncHRfM2RfYXBpOmFwcCAtLWhvc3QgMC4wLjAuMCAtLXBvcnQgODAwMA0KYGBgDQotIFRoZSBBUEkgd2lsbCBydW4gYXQ6ICoqYGh0dHA6Ly9sb2NhbGhvc3Q6ODAwMGAqKiAgDQotIFlvdSBjYW4gdGVzdCBpdCBieSB2aXNpdGluZzogKipgaHR0cDovL2xvY2FsaG9zdDo4MDAwL2RvY3NgKiogIA0KICAoVGhpcyB3aWxsIG9wZW4gYW4gaW50ZXJhY3RpdmUgU3dhZ2dlciBVSSB3aGVyZSB5b3UgY2FuIHRlc3QgeW91ciBBUEkuKQ0KDQotLS0NCg0KIyMjICoqOS40IERlcGxveSBBUEkgb24gYSBDbG91ZCBTZXJ2ZXIqKg0KVG8gbWFrZSB5b3VyIEFQSSAqKnB1YmxpY2x5IGFjY2Vzc2libGUqKiwgZGVwbG95IGl0IHVzaW5nOg0KLSAqKkFXUyBFQzIqKiAoYHV2aWNvcm5gIHNlcnZlciBvbiBhbiBVYnVudHUgaW5zdGFuY2UpDQotICoqR29vZ2xlIENsb3VkIFJ1bioqIChzZXJ2ZXJsZXNzIGRlcGxveW1lbnQpDQotICoqQXp1cmUgQXBwIFNlcnZpY2UqKiAoUGFhUyBob3N0aW5nKQ0KDQpGb3Igbm93LCBJIHJlY29tbWVuZCAqKmRlcGxveWluZyB0byBSZW5kZXIgKGZyZWUgYW5kIGVhc3kpKio6DQoxLiAqKkNyZWF0ZSBhIGZyZWUgYWNjb3VudCBhdCoqIOKGkiBbaHR0cHM6Ly9yZW5kZXIuY29tXShodHRwczovL3JlbmRlci5jb20pDQoyLiAqKkNsaWNrIOKAnE5ldyBXZWIgU2VydmljZeKAnSoqIOKGkiBTZWxlY3QgeW91ciBHaXRIdWIgcmVwbyB3aXRoIGBncHRfM2RfYXBpLnB5YA0KMy4gKipTZXQgUnVudGltZSBFbnZpcm9ubWVudDoqKiAgDQogICAtICoqUHl0aG9uIDMuMTArKiogIA0KICAgLSBTdGFydCBDb21tYW5kOiBgdXZpY29ybiBncHRfM2RfYXBpOmFwcCAtLWhvc3QgMC4wLjAuMCAtLXBvcnQgJFBPUlRgDQo0LiAqKkRlcGxveSBhbmQgZ2V0IGEgcHVibGljIFVSTCoqIOKGkiBFeGFtcGxlOiAgDQogICBgYGANCiAgIGh0dHBzOi8veW91ci0zZC1ncHQtYXBpLm9ucmVuZGVyLmNvbQ0KICAgYGBgDQo1LiAqKlRlc3Qgd2l0aCBQb3N0bWFuIG9yIFB5dGhvbioqOg0KICAgYGBgcHl0aG9uDQogICBpbXBvcnQgcmVxdWVzdHMNCiAgIGFwaV91cmwgPSAiaHR0cHM6Ly95b3VyLTNkLWdwdC1hcGkub25yZW5kZXIuY29tL2FuYWx5emVfM2QvIg0KICAgZGF0YSA9IHsiQ2VudHJvaWRfWCI6IDUuMSwgIkNlbnRyb2lkX1kiOiAzLjIsICJDZW50cm9pZF9aIjogOC43LCAiVm9sdW1lIjogNTAwLjIsICJCb3VuZGluZ19Cb3hfWCI6IDEwLCAiQm91bmRpbmdfQm94X1kiOiAxMiwgIkJvdW5kaW5nX0JveF9aIjogMTV9DQogICByZXNwb25zZSA9IHJlcXVlc3RzLnBvc3QoYXBpX3VybCwganNvbj1kYXRhKQ0KICAgcHJpbnQocmVzcG9uc2UuanNvbigpKQ0KICAgYGBgDQoNCi0tLQ0KDQojIyMg4pyFICoqU3RlcCA5IENvbXBsZXRlZDogWW91ciBHUFQgaXMgTm93IGEgUHVibGljIEFQSSoqDQrwn5qAICoqWW91ciAzRCBHSVMgR1BUIGlzIGxpdmUqKiBhbmQgY2FuIGJlIHVzZWQgYnkgYW55b25lIHZpYSAqKmEgc2ltcGxlIEFQSSBjYWxsISoqDQoNCi0tLQ0KIyMjICoqU3RlcCAxMDogQnVpbGQgYW4gQWR2YW5jZWQgQUktUG93ZXJlZCBHSVMgVG9vbCoqDQo=