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.2 Validate the Dataset Format

Before uploading, ensure the JSONL file is correctly formatted. Run:

openai tools fine_tunes.prepare_data -f 3D_GPT_Training_Data.jsonl

If it returns:

[INFO] Validation successful. File is ready for fine-tuning.

You’re good to go!


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

  1. Run the fine-tuning command.
  2. 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

  1. For ArcGIS Users → Use the Python script inside ArcPy Notebooks.
  2. For QGIS Users → Integrate into QGIS Python Console.
  3. 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.


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.1 Choose a Deployment Platform

You can deploy your API using: 1. FastAPI (Python-based, lightweight, great for AI models)
2. Flask (Easier setup, but slower than FastAPI)
3. Cloud Services (AWS, Google Cloud, Azure, etc.)

We’ll start with FastAPI, since it’s faster and more scalable.

📌 Install FastAPI & Uvicorn (for API serving):

pip install fastapi uvicorn openai

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

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 athttps://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=