Files
Embedded-Hacking/0x0017a_ctf/scripts/dec.py
T
Kevin Thomas 35eacd2c0e Course update: lessons, CTF 0x0011a_cb, and documentation
- 0x0011a_cb (Operation Dark Vector): nation-state CTF redesign with an
  AES-128-ECB sealed target and a plaintext launch origin; RP2350 firmware with
  bearing-driven servo, tri-color LEDs, GSV stats, and a realistic no-fix path
- docs: story-driven classified brief, GDB and Ghidra tutorials with deep
  step-throughs, regenerated artifacts and PDFs
- scripts: docstring standard, AES per-student randomizer, telemetry monitor
- week 3 to week 5 lessons: Ghidra patching tutorial, CMSIS-SVD hardware RE,
  double floating-point and GPIO architecture chapters, README structure
2026-09-27 14:18:56 -04:00

945 lines
25 KiB
Python

"""Generate hardened demo artifacts for the RP2350 Ouroboros firmware.
This script writes the same JSON schema used by the Rust demo and also emits
the generated C header consumed by the embedded firmware.
"""
import argparse
import json
import platform
import secrets
import sys
from pathlib import Path
from typing import Optional
DEFAULT_PASSPHRASE = (
"orbit olive ladder marble quartz canyon "
"ripple saddle violet ember walnut falcon"
)
DEFAULT_TEXT = "hello"
DEFAULT_OUTPUT_JSON = "scripts/demo_artifact.json"
DEFAULT_OUTPUT_HEADER = "include/demo_artifact.h"
DEFAULT_MEMORY_KIB = 64
DEFAULT_ITERATIONS = 3
DEFAULT_PARALLELISM = 1
ARTIFACT_FORMAT = "ouroboros-hardened-demo-v1"
_KEY_HELP = "12-word lowercase passphrase"
_TEXT_HELP = "Text to place in payload bytes 1..7"
_OUT_HELP = "Output JSON artifact path"
_HEADER_OUT_HELP = "Output generated C header path"
_FROM_JSON_HELP = (
"Load existing JSON artifact and emit header without re-encrypting"
)
_CHECK_HEADER_HELP = (
"Optional path to compare against generated header and fail if stale"
)
_SALT_HEX_HELP = "Optional fixed 16-byte salt as hex"
_NONCE_HEX_HELP = "Optional fixed 24-byte nonce as hex"
_NO_CRLF_HELP = "Do not append CRLF to payload text"
_LED_OFF_HELP = "Encode LED off instead of on"
_MEMORY_HELP = "Argon2 memory cost in KiB"
_ITERATIONS_HELP = "Argon2 time cost"
_PARALLELISM_HELP = "Argon2 parallel lanes"
_POLICY_ERROR = (
"Hardened mode requires exactly 12 lowercase ASCII words in --key."
)
_PAYLOAD_TOO_LONG = (
"Output text is too long for fixed dispatch "
"(max 7 bytes after CRLF handling)."
)
_INVALID_JSON = "Artifact JSON at {0} is invalid JSON."
_MISMATCH_PREFIX = "Detected a Python native-extension architecture mismatch. "
_REINSTALL_DEPS = ("Recreate this virtual environment with a native Python "
"and reinstall deps:")
_REINSTALL_LINES = (
"rm -rf .venv",
"python3 -m venv .venv",
"source .venv/bin/activate",
"python3 -m pip install -U pip setuptools wheel",
"python3 -m pip install argon2-cffi pynacl",
)
_HEADER_TEMPLATE = """// MIT License
//
// Copyright (c) 2026 Kevin Thomas
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
// This file is generated by scripts/dec.py. Do not edit by hand.
#ifndef DEMO_ARTIFACT_H
#define DEMO_ARTIFACT_H
#include <stdint.h>
#define DEMO_ARTIFACT_FORMAT "{artifact_format}"
#define DEMO_MEMORY_KIB {memory_kib}u
#define DEMO_ITERATIONS {iterations}u
#define DEMO_PARALLELISM {parallelism}u
static const uint8_t DEMO_SALT[16] = {{
{salt_body}
}};
static const uint8_t DEMO_NONCE[24] = {{
{nonce_body}
}};
static const uint8_t DEMO_CIPHERTEXT_AND_TAG[64] = {{
{cipher_body}
}};
#endif // DEMO_ARTIFACT_H
"""
def _raise_dependency_error(package_name, install_hint, exc):
"""Raise a RuntimeError with environment-aware dependency diagnostics.
Parameters
----------
package_name : str
Package common name for the error message.
install_hint : str
Pip install command in the error message.
exc : Exception
Import error observed while loading the native module.
Returns
-------
None
"""
message = "Hardened mode requires {0}. Install with: {1}".format(
package_name, install_hint)
message += _reinstall_message() if _is_arch_mismatch(exc) else ""
raise RuntimeError(message) from exc
def _is_arch_mismatch(exc):
"""Report whether the interpreter likely has a native-extension mismatch.
Parameters
----------
exc : Exception
Import error observed while loading the native module.
Returns
-------
bool
True when the error text matches a native architecture mismatch.
"""
detail = str(exc)
return (
"incompatible architecture" in detail
or "_cffi_backend" in detail
or "mach-o file, but is an incompatible architecture" in detail
)
def _reinstall_message():
"""Build the native-interpreter reinstall diagnostic text.
Parameters
----------
None
Returns
-------
str
Newline-delimited machine and reinstall details, or an empty string.
"""
machine = platform.machine()
head = (_MISMATCH_PREFIX
+ "Current interpreter reports machine=" + machine
+ ", executable=" + sys.executable + ".")
lines = [" {0}".format(item) for item in _REINSTALL_LINES]
return ("\n" + head + "\n" + _REINSTALL_DEPS + "\n"
+ "\n".join(lines))
def _is_policy_compliant(passphrase):
"""Return True when passphrase is exactly 12 lowercase ASCII words.
Parameters
----------
passphrase : str
Candidate operator passphrase.
Returns
-------
bool
True when the passphrase satisfies the gate policy.
"""
words = passphrase.split()
if len(words) != 12:
return False
return all(
word and all(ch.isascii() and ch.islower() for ch in word)
for word in words
)
def _build_payload(text_str, led_on=True, append_crlf=True):
"""Build the fixed 48-byte payload dispatched by the firmware.
Parameters
----------
text_str : str
Console text placed in payload bytes 1..7.
led_on : bool
True turns the LED byte on, False leaves it off.
append_crlf : bool
True appends CRLF to the console text.
Returns
-------
bytes
Fixed 48-byte dispatch payload.
"""
tx_bytes = text_str.encode() + (b"\r\n" if append_crlf else b"")
if len(tx_bytes) > 7:
raise ValueError(_PAYLOAD_TOO_LONG)
payload = bytearray(48)
payload[0] = 1 if led_on else 0
payload[1:1 + len(tx_bytes)] = tx_bytes
return bytes(payload)
def _resolve_salt_nonce(salt, nonce):
"""Confirm or generate the 16-byte salt and 24-byte nonce.
Parameters
----------
salt : bytes or None
Optional fixed salt value.
nonce : bytes or None
Optional fixed nonce value.
Returns
-------
tuple
Confirmed (salt, nonce) byte values.
"""
salt_word = secrets.token_bytes(16) if salt is None else salt
nonce_word = secrets.token_bytes(24) if nonce is None else nonce
if len(salt_word) != 16:
raise ValueError("Hardened salt must be exactly 16 bytes.")
if len(nonce_word) != 24:
raise ValueError("Hardened nonce must be exactly 24 bytes.")
return salt_word, nonce_word
def _optional_hex(value, length, label):
"""Decode an optional hex argument, leaving absent values as None.
Parameters
----------
value : str or None
Hex string supplied on the command line.
length : int
Expected decoded byte length.
label : str
Field name used in validation errors.
Returns
-------
bytes or None
Decoded bytes, or None when value is absent.
"""
return _hex_decode(value, length, label) if value else None
def _load_argon2():
"""Load the Argon2 low-level binding with dependency diagnostics.
Parameters
----------
None
Returns
-------
tuple
Argon2 Type enum and hash_secret_raw callable.
"""
try:
from argon2.low_level import Type, hash_secret_raw
except ImportError as exc:
install_hint = "python3 -m pip install argon2-cffi"
_raise_dependency_error("argon2-cffi", install_hint, exc)
return Type, hash_secret_raw
def _load_nacl_encrypt():
"""Load the XChaCha20-Poly1305 encrypt binding with diagnostics.
Parameters
----------
None
Returns
-------
callable
PyNaCl crypto_aead_xchacha20poly1305_ietf_encrypt function.
"""
try:
from nacl.bindings import (
crypto_aead_xchacha20poly1305_ietf_encrypt as encrypt,
)
except ImportError as exc:
install_hint = "python3 -m pip install pynacl"
_raise_dependency_error("PyNaCl", install_hint, exc)
return encrypt
def _derive_hardened_key(passphrase, salt, memory_kib, iterations,
parallelism):
"""Derive a 32-byte key with Argon2id.
Parameters
----------
passphrase : str
Policy-compliant operator passphrase.
salt : bytes
16-byte Argon2 salt.
memory_kib : int
Argon2 memory cost in KiB.
iterations : int
Argon2 time cost.
parallelism : int
Argon2 parallel lane count.
Returns
-------
bytes
Derived 32-byte key.
"""
arg2_type, hash_secret_raw = _load_argon2()
args = (
passphrase.encode(), salt, iterations, memory_kib,
parallelism, 32, arg2_type,
)
return hash_secret_raw(*args)
def _entry_key(phrase, seed, memory_kib, iterations, parallelism):
"""Derive the entry key word for the hardened artifact.
Parameters
----------
phrase : str
Policy-compliant operator passphrase.
seed : bytes
16-byte Argon2 salt.
memory_kib : int
Argon2 memory cost in KiB.
iterations : int
Argon2 time cost.
parallelism : int
Argon2 parallel lane count.
Returns
-------
bytes
Derived 32-byte entry key.
"""
return _derive_hardened_key(
phrase, seed, memory_kib, iterations, parallelism
)
def _build_key_material(phrase, salt, nonce, memory_kib, iterations,
parallelism):
"""Resolve salt, nonce, and the entry key.
Parameters
----------
phrase : str
Policy-compliant operator passphrase.
salt : bytes or None
Optional fixed salt value.
nonce : bytes or None
Optional fixed nonce value.
memory_kib : int
Argon2 memory cost in KiB.
iterations : int
Argon2 time cost.
parallelism : int
Argon2 parallel lane count.
Returns
-------
tuple
Resolved (salt, nonce, key) values.
"""
seed, nonce_word = _resolve_salt_nonce(salt, nonce)
key = _entry_key(phrase, seed, memory_kib, iterations, parallelism)
return seed, nonce_word, key
def build_hardened_entry(
key_str,
text_str,
led_on=True,
append_crlf=True,
salt: Optional[bytes] = None,
nonce: Optional[bytes] = None,
memory_kib=DEFAULT_MEMORY_KIB,
iterations=DEFAULT_ITERATIONS,
parallelism=DEFAULT_PARALLELISM,
):
"""Build a hardened encrypted entry with Argon2id + XChaCha20-Poly1305.
Parameters
----------
key_str : str
Policy-compliant 12-word operator passphrase.
text_str : str
Console text placed in payload bytes 1..7.
led_on : bool
True turns the LED byte on, False leaves it off.
append_crlf : bool
True appends CRLF to the console text.
salt : bytes or None
Optional fixed 16-byte salt.
nonce : bytes or None
Optional fixed 24-byte nonce.
memory_kib : int
Argon2 memory cost in KiB.
iterations : int
Argon2 time cost.
parallelism : int
Argon2 parallel lane count.
Returns
-------
tuple
Resulting (salt, nonce, ciphertext_and_tag) values.
"""
if not _is_policy_compliant(key_str):
raise ValueError(_POLICY_ERROR)
salt_word, nonce_word, key = _build_key_material(
key_str, salt, nonce, memory_kib, iterations, parallelism)
payload = _build_payload(text_str, led_on, append_crlf)
encrypt = _load_nacl_encrypt()
ciphertext_and_tag = encrypt(payload, b"", nonce_word, key)
return salt_word, nonce_word, ciphertext_and_tag
def _hex_decode(value, expected_len, label):
"""Decode a hex string and validate the expected byte length.
Parameters
----------
value : str
Hex string to decode.
expected_len : int
Required decoded byte length.
label : str
Field name used in validation errors.
Returns
-------
bytes
Decoded bytes of the expected length.
"""
try:
decoded = bytes.fromhex(value)
except ValueError as exc:
raise ValueError("{0} must be valid hex.".format(label)) from exc
if len(decoded) != expected_len:
message = "{0} must decode to exactly {1} bytes.".format(
label, expected_len)
raise ValueError(message)
return decoded
def _artifact_dict(memory_kib, iterations, parallelism, salt, nonce, cipher):
"""Compose the canonical artifact dictionary.
Parameters
----------
memory_kib : int
Argon2 memory cost in KiB.
iterations : int
Argon2 time cost.
parallelism : int
Argon2 parallel lane count.
salt : bytes
16-byte salt.
nonce : bytes
24-byte nonce.
cipher : bytes
64-byte ciphertext and tag.
Returns
-------
dict
Canonical artifact fields with hex-encoded byte values.
"""
return {
"format": ARTIFACT_FORMAT, "memory_kib": memory_kib,
"iterations": iterations, "parallelism": parallelism,
"salt_hex": salt.hex(), "nonce_hex": nonce.hex(),
"ciphertext_and_tag_hex": cipher.hex(),
}
def _format_c_array(data, width=8):
"""Format bytes as an indented C array literal body.
Parameters
----------
data : bytes
Bytes to serialize as a C array.
width : int
Byte values emitted per source line.
Returns
-------
str
Indented, comma-joined C array body.
"""
items = [f"0x{value:02X}u" for value in data]
rows = [", ".join(items[offset:offset + width])
for offset in range(0, len(items), width)]
return ",\n".join(" " + row for row in rows)
def _render_header(artifact):
"""Render the generated firmware header text from the artifact.
Parameters
----------
artifact : dict
Canonical artifact dictionary.
Returns
-------
str
Complete generated C header text.
"""
cipher = bytes.fromhex(artifact["ciphertext_and_tag_hex"])
return _HEADER_TEMPLATE.format(
artifact_format=ARTIFACT_FORMAT, memory_kib=artifact["memory_kib"],
iterations=artifact["iterations"], parallelism=artifact["parallelism"],
salt_body=_format_c_array(bytes.fromhex(artifact["salt_hex"])),
nonce_body=_format_c_array(bytes.fromhex(artifact["nonce_hex"])),
cipher_body=_format_c_array(cipher),
)
def _write_header(path, artifact):
"""Write the generated firmware header from the hardened artifact.
Parameters
----------
path : str
Destination header file path.
artifact : dict
Canonical artifact dictionary.
Returns
-------
Path
Resolved destination header path.
"""
output_path = Path(path)
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(_render_header(artifact), encoding="utf-8")
return output_path.resolve()
def _parse_json_file(path):
"""Load and parse the artifact JSON file.
Parameters
----------
path : str
Artifact JSON file path.
Returns
-------
dict
Parsed JSON document.
"""
raw = Path(path).read_text(encoding="utf-8")
try:
parsed = json.loads(raw)
except json.JSONDecodeError as exc:
raise ValueError(_INVALID_JSON.format(path)) from exc
return parsed
def _validate_artifact_format(parsed):
"""Reject artifact JSON with an unexpected format marker.
Parameters
----------
parsed : dict
Parsed JSON document.
Returns
-------
None
"""
actual = parsed.get("format")
if actual != ARTIFACT_FORMAT:
raise ValueError(
"Artifact format must be '{0}', got '{1}'.".format(
ARTIFACT_FORMAT, actual))
def _parsed_ints(parsed):
"""Parse the integer cost fields from artifact JSON.
Parameters
----------
parsed : dict
Parsed JSON document.
Returns
-------
list
Parsed memory_kib, iterations, and parallelism integer values.
"""
try:
int_names = ("memory_kib", "iterations", "parallelism")
return [int(parsed[name]) for name in int_names]
except (KeyError, TypeError, ValueError) as exc:
raise ValueError(
"Artifact must include integer memory_kib, "
"iterations, and parallelism fields.") from exc
def _artifact_ints(parsed):
"""Unpack the three integer cost fields from artifact JSON.
Parameters
----------
parsed : dict
Parsed JSON document.
Returns
-------
tuple
(memory_kib, iterations, parallelism) integer values.
"""
values = _parsed_ints(parsed)
return values[0], values[1], values[2]
def _artifact_from_parsed(parsed):
"""Reconstruct a canonical artifact dict from parsed JSON.
Parameters
----------
parsed : dict
Parsed JSON document.
Returns
-------
dict
Canonical artifact dictionary.
"""
_validate_artifact_format(parsed)
memory_kib, iterations, parallelism = _artifact_ints(parsed)
cipher_field = "ciphertext_and_tag_hex"
salt = _hex_decode(parsed.get("salt_hex", ""), 16, "salt_hex")
nonce = _hex_decode(parsed.get("nonce_hex", ""), 24, "nonce_hex")
cipher = _hex_decode(parsed.get(cipher_field, ""), 64, cipher_field)
return _artifact_dict(
memory_kib, iterations, parallelism, salt, nonce, cipher)
def _load_artifact_json(path):
"""Load and validate a hardened artifact JSON for header generation.
Parameters
----------
path : str
Artifact JSON file path.
Returns
-------
dict
Canonical artifact dictionary.
"""
parsed = _parse_json_file(path)
return _artifact_from_parsed(parsed)
def _check_header_match(generated_path, expected_path):
"""Fail when the generated header does not match an expected file.
Parameters
----------
generated_path : str
Generated header file path.
expected_path : str
Expected committed header file path.
Returns
-------
None
"""
generated = Path(generated_path).read_text(encoding="utf-8")
expected = Path(expected_path).read_text(encoding="utf-8")
if generated != expected:
raise RuntimeError(
"Generated header does not match committed "
"include/demo_artifact.h. Regenerate and commit "
"updated artifacts with scripts/dec.py.")
def _print_header_check(header_path, expected_path):
"""Print the verified header match result.
Parameters
----------
header_path : str
Generated header file path.
expected_path : str
Expected committed header file path.
Returns
-------
None
"""
_check_header_match(header_path, expected_path)
print("Verified header matches: {0}".format(Path(expected_path).resolve()))
def _parse_args():
"""Parse command-line arguments.
Parameters
----------
None
Returns
-------
argparse.Namespace
Parsed command-line arguments.
"""
parser = argparse.ArgumentParser(description=__doc__)
for argument_group in _ARGUMENT_GROUPS:
for name, kwargs in argument_group:
parser.add_argument(name, **kwargs)
return parser.parse_args()
def _build_from_args(args, salt, nonce):
"""Build the hardened entry from parsed arguments.
Parameters
----------
args : argparse.Namespace
Parsed command-line arguments.
salt : bytes or None
Optional fixed salt value.
nonce : bytes or None
Optional fixed nonce value.
Returns
-------
tuple
Resulting (salt, nonce, ciphertext_and_tag) values.
"""
return build_hardened_entry(
key_str=args.key, text_str=args.text, led_on=not args.led_off,
append_crlf=not args.no_crlf, salt=salt, nonce=nonce,
memory_kib=args.memory_kib, iterations=args.iterations,
parallelism=args.parallelism,
)
def _flush_outputs(args, salt, nonce, cipher):
"""Write the artifact JSON and header, then return the header path.
Parameters
----------
args : argparse.Namespace
Parsed command-line arguments.
salt : bytes
16-byte salt.
nonce : bytes
24-byte nonce.
cipher : bytes
64-byte ciphertext and tag.
Returns
-------
Path
Resolved generated header path.
"""
json_path, artifact = _write_demo_json(
args.out, args.memory_kib, args.iterations, args.parallelism,
salt, nonce, cipher)
header_path = _write_header(args.header_out, artifact)
print("Wrote hardened demo artifact JSON: {0}".format(json_path))
print("Wrote generated firmware header: {0}".format(header_path))
return header_path
def _write_demo_json(path, memory_kib, iterations, parallelism, salt,
nonce, ciphertext_and_tag):
"""Write the hardened JSON artifact consumed by docs and validation.
Parameters
----------
path : str
Destination JSON file path.
memory_kib : int
Argon2 memory cost in KiB.
iterations : int
Argon2 time cost.
parallelism : int
Argon2 parallel lane count.
salt : bytes
16-byte salt.
nonce : bytes
24-byte nonce.
ciphertext_and_tag : bytes
64-byte ciphertext and tag.
Returns
-------
tuple
Resolved (path, artifact) values.
"""
output_path = Path(path)
output_path.parent.mkdir(parents=True, exist_ok=True)
artifact = _artifact_dict(
memory_kib, iterations, parallelism, salt, nonce, ciphertext_and_tag)
text = json.dumps(artifact, indent=2) + "\n"
output_path.write_text(text, encoding="utf-8")
return output_path.resolve(), artifact
def _flush_from_json(args):
"""Write the header from an existing artifact JSON.
Parameters
----------
args : argparse.Namespace
Parsed command-line arguments.
Returns
-------
Path
Resolved generated header path.
"""
artifact = _load_artifact_json(args.from_json)
header_path = _write_header(args.header_out, artifact)
print("Wrote generated firmware header: {0}".format(header_path))
return header_path
def _run_from_json(args):
"""Generate the header only from committed artifact JSON.
Parameters
----------
args : argparse.Namespace
Parsed command-line arguments.
Returns
-------
None
"""
header_path = _flush_from_json(args)
if args.check_header_path:
_print_header_check(header_path, args.check_header_path)
def _run_from_generate(args):
"""Encrypt fresh artifact material, then emit JSON and the header.
Parameters
----------
args : argparse.Namespace
Parsed command-line arguments.
Returns
-------
None
"""
salt = _optional_hex(args.salt_hex, 16, "salt_hex")
nonce = _optional_hex(args.nonce_hex, 24, "nonce_hex")
header_path = _flush_outputs(
args, *_build_from_args(args, salt, nonce),
)
if args.check_header_path:
_print_header_check(header_path, args.check_header_path)
def main():
"""Generate the hardened artifact JSON and C header.
Parameters
----------
None
Returns
-------
None
"""
args = _parse_args()
if args.from_json:
_run_from_json(args)
else:
_run_from_generate(args)
_ARGUMENT_GROUPS = (
(
("--key", dict(default=DEFAULT_PASSPHRASE, help=_KEY_HELP)),
("--salt-hex", dict(help=_SALT_HEX_HELP)),
("--nonce-hex", dict(help=_NONCE_HEX_HELP)),
),
(
("--text", dict(default=DEFAULT_TEXT, help=_TEXT_HELP)),
("--out", dict(default=DEFAULT_OUTPUT_JSON, help=_OUT_HELP)),
("--header-out",
dict(default=DEFAULT_OUTPUT_HEADER, help=_HEADER_OUT_HELP)),
("--from-json", dict(help=_FROM_JSON_HELP)),
("--no-crlf", dict(action="store_true", help=_NO_CRLF_HELP)),
("--led-off", dict(action="store_true", help=_LED_OFF_HELP)),
),
(
("--memory-kib",
dict(type=int, default=DEFAULT_MEMORY_KIB, help=_MEMORY_HELP)),
("--iterations",
dict(type=int, default=DEFAULT_ITERATIONS, help=_ITERATIONS_HELP)),
("--parallelism",
dict(type=int, default=DEFAULT_PARALLELISM,
help=_PARALLELISM_HELP)),
("--check-header-path", dict(help=_CHECK_HEADER_HELP)),
),
)
if __name__ == "__main__":
main()