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.
15.3.1. The header¶
Ten bytes, packed without padding. Each field:
sync– the 16-bit word0xD5AAin little-endian order. Byte 0 on the wire is0xAA, byte 1 is0xD5. A receiver scanning bytes can find the start of a packet by searching for the pairAA D5; anything before it is treated as garbage. The choice of value is deliberate:0xAAand0xD5rarely 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-instdin,stdout,stream, and (optionally)profilechannels 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.