15.3. The packet format

Every byte that crosses the wire between the cam and the host is part of a packet. A packet starts with a 10-byte header, runs a variable-length payload, and ends with a 4-byte trailing CRC. No other bytes appear on the wire – once the host has seen the 2-byte sync word, the next bytes are a header in this exact sequence.

A horizontal layout of a protocol packet showing the 10-byte header (sync word, sequence number, channel ID, flags, opcode, payload length, header CRC) followed by the variable-length payload and a 4-byte payload CRC.

15.3.1. The header

Ten bytes, packed without padding. Each field:

  • sync – the 16-bit word 0xD5AA in little-endian order. Byte 0 on the wire is 0xAA, byte 1 is 0xD5. A receiver scanning bytes can find the start of a packet by searching for the pair AA D5; anything before it is treated as garbage. The choice of value is deliberate: 0xAA and 0xD5 rarely appear in printable text, and the pair is unlikely to occur by accident in the middle of a payload.

  • seq – one byte. A counter that increments by one for each packet sent on a given direction. The receiver checks that the next packet’s sequence number is the expected one; if not, the reliability layer asks for a retransmit.

  • chan – one byte. The channel ID this packet belongs to. Channels 0..31 are usable; the built-in stdin, stdout, stream, and (optionally) profile channels take fixed IDs the cam reserves.

  • flags – one byte. A bit-field telling the receiver how to interpret the packet:

    • bit 0 ACK – this packet is an acknowledgement of a previous one.

    • bit 1 NAK – this packet rejects a previous one.

    • bit 2 RTX – this packet is a retransmit.

    • bit 3 ACK_REQ – the sender wants this packet acknowledged.

    • bit 4 FRAGMENT – more fragments follow this one in a larger message.

    • bit 5 EVENT – this packet carries a channel event rather than data.

    • bits 6 and 7 are reserved.

  • opcode – one byte. The command or response code. The protocol library reserves opcode ranges by purpose:

    • 0x00..0x0F – protocol commands (SYNC, GET_CAPS, SET_CAPS, STATS, VERSION).

    • 0x10..0x1F – system commands (RESET, BOOT, INFO, EVENT, MEMORY).

    • 0x20..0x2F – channel commands (LIST, POLL, LOCK, UNLOCK, SHAPE, SIZE, READ, WRITE, IOCTL, EVENT).

  • len – two bytes, little-endian. The number of payload bytes that follow the header. A length of zero is legal – many acknowledgements and small commands carry no payload.

  • crc – two bytes. A CRC-16 over the previous eight header bytes. A receiver that gets a header with a bad CRC drops the whole packet without even looking at the payload.

15.3.2. The payload

Zero or more bytes, treated as opaque by the framing layer. What’s in the payload depends on the opcode: for a CHANNEL_READ reply it’s the actual channel data; for a GET_CAPS reply it’s a small fixed structure; for a channel write it’s whatever the host sent.

The maximum payload size depends on the cam’s protocol buffer size (refer to the per-board table in protocol.init()). Messages longer than the cap are split into fragments with the FRAGMENT flag set on all but the last.

15.3.3. The trailing CRC

Four bytes, a CRC-32 over the payload. Catches corruption that the header CRC can’t see, particularly on long payloads where a single-bit error mid-frame would otherwise slip through.

Splitting the integrity check across two CRCs is deliberate. The header CRC protects the framing fields themselves – particularly the payload length. Without a separate header CRC, a single bit flip in the length byte would cause the receiver to read the wrong number of bytes for the payload and desync from the byte stream entirely; with one, a damaged header is rejected outright and the receiver re-scans for the next sync word. The payload CRC then protects the message body as a separate concern, so a bit flip in the data is reported as a corrupt payload rather than mistaken for a framing error.

The format is small enough to walk through byte by byte, and the fact that every packet has the same layout – sync, then header, then payload, then CRC – means a hand-rolled parser fits in a screen of code. That’s why a tiny host implementation in C, Python, or Rust is a weekend project; the protocol library is the maintained Python version on each side.