Wire: [0xAA] [0xAA] [LEN] [STUFFED_DATA]
Unstuffed: [USER_ID] [MESSAGE...] [CRC_HI] [CRC_LO]
LEN— length of stuffed data (1-256)CRC16-CCITT— polynomial0x1021, initial0xFFFF, computed overUSER_ID + MESSAGE
Escape byte 0xAB is used to avoid sync byte collisions in the payload:
| Raw byte | Encoded as |
|---|---|
0xAA |
0xAB 0x00 |
0xAB |
0xAB 0x01 |
Message payloads are encrypted with ChaCha20-Poly1305 AEAD (RFC 8439):
MESSAGE = [NONCE (12 bytes)] [CIPHERTEXT (n bytes)] [TAG (16 bytes)]
- Nonce is generated per message (counter-based on board, random on client)
- Tag authenticates the ciphertext; messages with invalid tags are dropped
- Detect two consecutive
0xAAsync bytes - Read length byte
- Collect
LENbytes of stuffed data - Unstuff, verify CRC, deliver message
- Timeout: drop partial frame after ~1 second of inactivity
- Prepend
USER_IDto message - Compute CRC16 over
USER_ID + MESSAGE - Append CRC (big-endian)
- Byte-stuff the result
- Send
[0xAA] [0xAA] [LEN] [STUFFED_DATA]
| ID | Role |
|---|---|
0x00 |
Board |
0x02 |
Client |
The client filters frames containing its own user ID to suppress echo.