commaai/openpilot · error · ValueError

invalid wrapped firmware length or magic

Error message

invalid wrapped firmware length or magic

What it means

ValueError from validate_image(): the file's total length does not equal declared_body_length + 10, or the magic byte at offset 4+body_len is not 0xA5. Together these prove the file was truncated, padded, or is not a wrapped chestnut image at all. The length check runs first, so a bad length masks the magic check.

Source

Thrown at openpilot/system/hardware/chestnut/flash.py:257

    self.write_enable()
    self.transaction(0x20, addr)
    self.wait_write_done()

  def program(self, addr, data):
    self.write_buffer(data + bytes((-len(data)) % 4))
    self.write_enable()
    self.transaction(0x02, addr, len(data), mode=1)
    self.wait_write_done()


def validate_image(data):
  if len(data) < 10:
    raise ValueError("wrapped firmware is too short")
  body_len = int.from_bytes(data[:4], "little")
  if body_len > MAX_CODE_SIZE:
    raise ValueError(f"wrapped firmware body exceeds {MAX_CODE_SIZE} bytes")
  if len(data) != body_len + 10 or data[4 + body_len] != 0xA5:
    raise ValueError("invalid wrapped firmware length or magic")
  body = data[4:4 + body_len]
  if data[5 + body_len] != sum(body) & 0xFF:
    raise ValueError("invalid wrapped firmware checksum")
  if data[6 + body_len:] != zlib.crc32(body).to_bytes(4, "little"):
    raise ValueError("invalid wrapped firmware CRC")


def image_product(image):
  match = re.search(rb"custom [0-9a-f]{8}-CLEAN", image)
  if match is None:
    raise ValueError("no product string in wrapped firmware")
  return match.group().decode()


def reconnect(flash):
  attempt = 0
  while True:
    attempt += 1

View on GitHub (pinned to 516ec1e682)

Solutions

  1. Verify the size math: len(file) must be exactly int.from_bytes(file[:4],'little') + 10; if off, re-transfer the file in binary mode
  2. Regenerate the wrapped image with the project's packaging step rather than constructing it by hand
  3. If hand-wrapping, place magic 0xA5, checksum byte, then CRC32 exactly at offsets body_len+4, body_len+5, body_len+6..9
  4. Compare the sha256 of the artifact against the build's recorded hash to detect transfer corruption

Example fix

# canonical layout
# [0:4] u32le body_len
# [4:4+body_len] body
# [4+body_len] 0xA5
# [5+body_len] sum(body) & 0xFF
# [6+body_len:10+body_len] crc32(body) u32le
assert len(wrapped) == struct.unpack_from('<I', wrapped)[0] + 10
Defensive patterns

Strategy: validation

Validate before calling

import struct

def wrapper_framing_ok(data: bytes) -> bool:
    if len(data) < 10:
        return False
    body_len = struct.unpack_from('<I', data)[0]
    return len(data) == body_len + 10 and data[4 + body_len] == 0xA5

Prevention

When it happens

Trigger: validate_image(data) where len(data) != body_len + 10 (truncated download, extra trailing bytes, wrong file) or where data[4+body_len] != 0xA5 (magic marker missing - the wrapper was not created by the standard packaging step).

Common situations: Interrupted download or partial write of firmware_wrapped.bin; a newline or CRLF appended to the binary (e.g. text-mode transfer); passing a raw firmware bin that lacks the 10-byte wrapper; a stale wrapper format from an older packaging script without the magic byte.

Related errors


AI-assisted analysis of commaai/openpilot@516ec1e682 (2026-08-15). Data as JSON: /api/errors/bb66d1b1fcec970a. Report an issue: GitHub.