12.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.

12.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.

12.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.

12.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.