"""Synthetic ninlight OTA images: 56-byte W600 header + gzip stream + 512-byte NINL trailer. The layout mirrors ninlight-firmware/tools/mkimage/nl_mkimage.py and was checked against a real image (spec ยง2). Tests build their own images instead of copying firmware binaries. """ from __future__ import annotations import gzip import hashlib CONTENT_WITH_SSID = b"\x00boot\x00ninIOT\x00ninlight-%s\x00" CONTENT_WITHOUT_SSID = b"\x00boot\x00ninlight-%s\x00" def build_payload(content: bytes) -> bytes: return b"\xab" * 56 + gzip.compress(content, mtime=0) def build_image( device_type: str = "gu10_rgb", version: str = "0.3.8-47-g71a57", *, content: bytes = CONTENT_WITH_SSID, payload: bytes | None = None, magic: bytes = b"NINL", fmt: int = 1, corrupt_hash: bool = False, ) -> bytes: body = payload if payload is not None else build_payload(content) trailer = bytearray(b"\xff" * 512) trailer[0:4] = magic trailer[4] = fmt trailer[5:21] = device_type.encode().ljust(16, b"\x00") trailer[21:37] = version.encode().ljust(16, b"\x00") trailer[37:69] = bytes(32) if corrupt_hash else hashlib.sha256(body).digest() return body + bytes(trailer)