#!/usr/bin/env python3
import hashlib
import json
import pathlib
import re
import struct
import subprocess
import sys

elf = pathlib.Path(sys.argv[1])
binary = pathlib.Path(sys.argv[2])
map_file = pathlib.Path(sys.argv[3])
objdump = subprocess.check_output(["arm-none-eabi-objdump", "-h", str(elf)], text=True)
main_disassembly = subprocess.check_output(
    ["arm-none-eabi-objdump", "-d", "--disassemble=main", str(elf)], text=True)
safe_boot_disassembly = subprocess.check_output(
    ["arm-none-eabi-objdump", "-d", "--disassemble=drill_safe_boot", str(elf)], text=True)
map_text = map_file.read_text(errors="replace")
data = binary.read_bytes()
size_fields = subprocess.check_output(["arm-none-eabi-size", str(elf)], text=True).splitlines()[-1].split()
text_bytes, data_bytes, bss_bytes = map(int, size_fields[:3])
ram_used_bytes = data_bytes + bss_bytes
if len(data) < 8:
    raise SystemExit("binary is too short for an Arm vector table")
initial_sp, reset_vector = struct.unpack_from("<II", data)
if initial_sp != 0x20005800:
    raise SystemExit(f"unexpected initial SP: 0x{initial_sp:08x}")
if reset_vector & 1 == 0 or not (0x08000000 <= (reset_vector & ~1) < 0x08020000):
    raise SystemExit(f"reset vector outside STM32G431CB flash: 0x{reset_vector:08x}")
required = ("SystemInit", "drill_safe_boot", "drill_safe_gpio_validate",
            "drill_safe_gpio_preload", "drill_safe_gpio_configure_outputs",
            "drv8316_configure_off", "ds_init", "charge_manager_step",
            "charge_registers_init", "main")
missing = [symbol for symbol in required
           if not re.search(r"\b" + re.escape(symbol) + r"\b", map_text)]
if missing:
    raise SystemExit("linked image missing required symbols: " + ", ".join(missing))
if ".isr_vector" not in objdump or ".text" not in objdump:
    raise SystemExit("ELF does not contain vector and text sections")
for symbol, disassembly in (("drill_safe_boot", main_disassembly),
                           ("drill_safe_gpio_preload", safe_boot_disassembly),
                           ("drill_safe_gpio_configure_outputs", safe_boot_disassembly)):
    if symbol not in disassembly:
        raise SystemExit(f"expected safe-boot call missing from disassembly: {symbol}")
print(json.dumps({
    "status": "LINKED_IMAGE_STATIC_CHECK_PASS",
    "target": "STM32G431CBT6 / Cortex-M4",
    "toolchain": subprocess.check_output(["arm-none-eabi-gcc", "--version"], text=True).splitlines()[0],
    "flash_binary_bytes": len(data),
    "flash_capacity_bytes": 131072,
    "ram_used_bytes": ram_used_bytes,
    "ram_capacity_bytes": 22528,
    "initial_sp": f"0x{initial_sp:08x}",
    "reset_vector": f"0x{reset_vector:08x}",
    "linked_policy_symbols": list(required[:-1]),
    "entry_symbol": "main",
    "sha256_bin": hashlib.sha256(data).hexdigest(),
    "sha256_elf": hashlib.sha256(elf.read_bytes()).hexdigest(),
    "physical_hardware_validation": False,
    "safe_gpio_boot_function_linked": True,
    "physical_gpio_levels_verified": False,
    "tim1_initialized": False,
    "motor_power_enabled": False,
    "charge_enabled": False,
    "runtime_control_enabled": False,
}, indent=2))
