Files
HaloKeymind/scripts/qualify_nrf52_embedded_bootloader.py

71 lines
3.7 KiB
Python

#!/usr/bin/env python3
"""Bind an embedded bootloader claim to the actual packaged application."""
import argparse
import hashlib
import json
from pathlib import Path
import sys
import zipfile
from nrf52_embedded_bootloader import (ROOT, BOOT_SIZE, VERSION, MANIFEST_SHA256,
LEGACY_SHA256, load_manifest, select_profile, download_zip, validate_candidate_zip)
sys.path.insert(0, str(ROOT / "tools/mota"))
from motalib import parse_endf, parse_endf_ident, parse_nrf52_layout, target_id_for_env
def qualify(directory, manifest_path):
directory, manifest_path = Path(directory), Path(manifest_path)
manifest = json.loads(manifest_path.read_text())
if manifest.get("platform") != "NRF52_PLATFORM" or manifest.get("verified") is not True:
raise ValueError("embedded bootloader requires qualified nRF52 firmware")
profile, _ = select_profile(load_manifest(), manifest["target"])
candidate, identity = validate_candidate_zip(download_zip(profile, ROOT / ".pio/otafix-cache"), profile)
resource = directory / "bootloader-migration-resource"
metadata = json.loads((resource / "BootloaderMigrationResource.json").read_text())
if (metadata.get("profile") != profile["id"] or metadata.get("release") != VERSION
or metadata.get("manifest_sha256") != MANIFEST_SHA256
or metadata.get("asset_sha256") != profile["files"]["zip"]["sha256"]
or metadata.get("candidate_sha256") != hashlib.sha256(candidate).hexdigest()
or metadata.get("legacy_sha256") not in (None, LEGACY_SHA256)
or (resource / "BootloaderMigrationResource.bin").read_bytes() != candidate):
raise ValueError("embedded bootloader build resource differs from its pinned release")
with zipfile.ZipFile(directory / "firmware.zip") as package:
entry = json.loads(package.read("manifest.json"))["manifest"]["application"]
application = package.read(entry["bin_file"])
body, _ = parse_endf(application)
app_identity = parse_endf_ident(application)
layout = parse_nrf52_layout(application)
offset = body.find(candidate)
if (not app_identity or app_identity.target_id != target_id_for_env(manifest["target"])
or not layout or layout.app_base != identity.app_base
or len(application) > layout.linked_app_end - layout.app_base
or offset < 0 or offset % 4 or body.find(candidate, offset + 1) >= 0
or offset + BOOT_SIZE > len(body)
or b"BL embedded 2.4.12" not in body):
raise ValueError("packaged application lacks the complete aligned embedded bootloader or activation code")
manifest["embedded_bootloader"] = {
"release": VERSION, "profile": profile["id"],
"candidate_sha256": hashlib.sha256(candidate).hexdigest(),
"asset_sha256": profile["files"]["zip"]["sha256"],
"manifest_sha256": MANIFEST_SHA256,
"legacy_sha256": metadata.get("legacy_sha256"),
"app_offset": offset, "image_bytes": BOOT_SIZE,
"activation": "ota bootloader update",
"automatic_delay_seconds": 70,
}
manifest_path.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n")
return manifest["embedded_bootloader"]
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("directory", type=Path)
parser.add_argument("manifest", type=Path)
args = parser.parse_args()
try:
result = qualify(args.directory, args.manifest)
except (OSError, ValueError, KeyError, zipfile.BadZipFile) as exc:
parser.exit(1, f"Embedded bootloader qualification failed: {exc}\n")
print(f"Verified embedded OTAFIX {result['release']}: {result['profile']}; {result['image_bytes']} bytes")