mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-08-15 15:20:03 +00:00
80 lines
3.2 KiB
Python
80 lines
3.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Validate a same-origin Pyxis web-flasher release directory."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import re
|
|
|
|
|
|
EXPECTED_OFFSETS = {
|
|
"bootloader.bin": 0,
|
|
"partitions.bin": 0x8000,
|
|
"boot_app0.bin": 0xE000,
|
|
"firmware.bin": 0x10000,
|
|
}
|
|
MAX_IMAGE_SIZES = {
|
|
"bootloader.bin": 0x8000,
|
|
"partitions.bin": 0x1000,
|
|
"boot_app0.bin": 0x2000,
|
|
"firmware.bin": 0x300000,
|
|
}
|
|
SHA256_PATTERN = re.compile(r"^[0-9a-f]{64}$")
|
|
COMMIT_PATTERN = re.compile(r"^[0-9a-f]{40}$")
|
|
|
|
|
|
def require(condition: bool, message: str) -> None:
|
|
if not condition:
|
|
raise ValueError(message)
|
|
|
|
|
|
def validate_release(directory: Path, expected_version: str) -> None:
|
|
metadata = json.loads((directory / "pyxis-release.json").read_text())
|
|
require(metadata.get("schema") == 1, "unsupported metadata schema")
|
|
require(metadata.get("version") == expected_version, "release version mismatch")
|
|
require(bool(COMMIT_PATTERN.fullmatch(metadata.get("source_commit", ""))), "invalid source commit")
|
|
require(metadata.get("environment") == "tdeck-release", "release environment mismatch")
|
|
require(metadata.get("chip_family") == "ESP32-S3", "chip family mismatch")
|
|
require(metadata.get("flash_size") == 8 * 1024 * 1024, "flash size mismatch")
|
|
require(metadata.get("persistence_safe") is True, "release is not persistence-safe")
|
|
|
|
descriptors = metadata.get("images")
|
|
require(isinstance(descriptors, dict), "missing image descriptors")
|
|
require(set(descriptors) == set(EXPECTED_OFFSETS), "image descriptor set mismatch")
|
|
|
|
for name, offset in EXPECTED_OFFSETS.items():
|
|
descriptor = descriptors[name]
|
|
require(isinstance(descriptor, dict), f"invalid descriptor for {name}")
|
|
require(descriptor.get("file") == name, f"filename mismatch for {name}")
|
|
require(descriptor.get("offset") == offset, f"offset mismatch for {name}")
|
|
image = (directory / name).read_bytes()
|
|
require(descriptor.get("size") == len(image) and len(image) > 0, f"size mismatch for {name}")
|
|
require(len(image) <= MAX_IMAGE_SIZES[name], f"{name} exceeds its flash region")
|
|
declared_hash = descriptor.get("sha256", "")
|
|
require(bool(SHA256_PATTERN.fullmatch(declared_hash)), f"invalid SHA-256 for {name}")
|
|
require(hashlib.sha256(image).hexdigest() == declared_hash, f"SHA-256 mismatch for {name}")
|
|
|
|
firmware = (directory / "firmware.bin").read_bytes()
|
|
require(len(firmware) <= 0x300000, "firmware exceeds app0 partition")
|
|
require(len(firmware) >= 24 and firmware[0] == 0xE9, "invalid ESP firmware image")
|
|
require(int.from_bytes(firmware[12:14], "little") == 9, "firmware is not ESP32-S3")
|
|
boot_app0 = (directory / "boot_app0.bin").read_bytes()
|
|
require(len(boot_app0) == 0x2000, "boot_app0 size mismatch")
|
|
require(boot_app0[:4] == b"\x01\x00\x00\x00", "invalid boot_app0 selector")
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--directory", type=Path, required=True)
|
|
parser.add_argument("--version", required=True)
|
|
args = parser.parse_args()
|
|
validate_release(args.directory, args.version)
|
|
print(f"validated_web_release={args.version}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|