Skip to content

RICRESTProtocol

Rob Dobson edited this page May 4, 2026 · 2 revisions

RICREST Protocol

RICREST is Raft's transport-agnostic message envelope that carries REST-style requests, responses and binary stream blocks over non-HTTP links (BLE, serial, sockets). On the wire it is normally wrapped by one of the framing protocols RICSerial, RICFrame or RICJSON; the RICREST payload itself is the same in all three.

For higher-level flow see Communications Stack Overview and ProtocolExchange.

Frame layers

   ┌────────────────────────────────────────────────────────────────┐
   │  Outer framing : RICSerial (HDLC) | RICFrame (len-prefixed) |  │
   │                  RICJSON (raw JSON)                            │
   └────────────────────────────────────────────────────────────────┘
                                  │
                                  ▼
   ┌────────────────────────────────────────────────────────────────┐
   │  CommsChannelMsg header                                        │
   │   - msg number (1 byte for RICFrame; managed by RICSerial HDLC)│
   │   - protocol code  (RICREST, BridgeRICREST, RawCmdFrame, ...)  │
   │   - type code      (Command, Response, Publish, Report)        │
   └────────────────────────────────────────────────────────────────┘
                                  │
                                  ▼
   ┌────────────────────────────────────────────────────────────────┐
   │  RICREST element  (only when protocol == RICREST)              │
   │   - 1 byte element code                                        │
   │   - element-specific payload                                   │
   └────────────────────────────────────────────────────────────────┘

Outer framings

RICSerial framing

ProtocolRICSerial uses MiniHDLC to deframe a byte stream:

  • 0x7E is the FLAG byte that delimits frames.
  • 0x7D is the ESCAPE byte; the byte that follows is XOR'd with 0x20.
  • Each frame ends with a CRC.
  • The bytes inside the frame are the CommsChannelMsg header followed by its payload (see below).

This is the framing used over UART and BLE GATT characteristics, where the byte stream has no inherent message boundary.

RICFrame framing

ProtocolRICFrame is used where the transport already provides framing (TCP socket, WebSocket binary frame). It does not add HDLC byte-stuffing or a CRC. Each frame is just:

byte 0   : msgNumber (0..255)
byte 1   : (msgTypeCode << 6) | (msgProtocolCode & 0x3f)
bytes 2+ : payload (= RICREST element when protocol == RICREST)

msgTypeCode is 0=COMMAND, 1=RESPONSE, 2=PUBLISH, 3=REPORT. msgProtocolCode is the CommsMsgProtocol enum (2=RICREST, 3=BridgeRICREST, 0x3e=RawCmdFrame, 0x3f=None, 0=ROSSerial).

RICJSON framing

ProtocolRICJSON carries pure JSON with no envelope. The JSON document itself encodes whatever metadata it needs and is delivered to the application as a CommsChannelMsg with the JSON in the payload.

CommsChannelMsg header (RICSerial)

When carried inside a RICSerial HDLC frame, the first two bytes are the same as RICFrame's header:

byte 0   : msgNumber
byte 1   : (msgTypeCode << 6) | (msgProtocolCode & 0x3f)
bytes 2+ : payload

So at the API level RICSerial and RICFrame produce identical CommsChannelMsg objects — they differ only in how the bytes are framed for the underlying transport.

RICREST element format

When msgProtocolCode == MSG_PROTOCOL_RICREST (2), the payload begins with a one-byte element code:

Code Name Inbound role Outbound role
0 URL Request as URL string (endpoint/path?query=...).
1 CMDRESPJSON Request as JSON { "reqStr": "...", … }. Response wrapping the endpoint's JSON output.
2 BODY Body chunk for a prior URL request (POST-style data).
3 COMMAND_FRAME JSON { "cmdName": "...", … } flattened into a query string.
4 FILEBLOCK A binary chunk of a file/stream session. A binary chunk of a file/stream session.

URL (code 0)

byte 0     : 0x00
bytes 1..n : ASCII URL, e.g.  "subscription?action=update&topic=devjson&rateHz=10"

The whole tail of the message is the URL as a UTF-8 string (max length capped by RICRESTMsg::getMaxRestBodySize() — 5000 without PSRAM, 200000 with).

CMDRESPJSON (code 1)

byte 0     : 0x01
bytes 1..n : ASCII JSON object

Inbound: the JSON contains a reqStr field (and optionally other fields). The framework treats reqStr as if it were a URL element.

Outbound: the JSON is the endpoint's response document, e.g.

{"req":"subscription","rslt":"ok"}

The outbound message is marked as a response (msg type code 1) and reuses the original message's msgNumber so the requester can match request to response.

BODY (code 2)

byte 0       : 0x02
bytes 1..4   : bufferPos      (uint32, big-endian)   ← offset of this chunk in the overall body
bytes 5..8   : totalBytes     (uint32, big-endian)   ← total body length
bytes 9..n   : binary payload

Used to deliver a multi-chunk request body to a previously-registered URL endpoint.

COMMAND_FRAME (code 3)

byte 0     : 0x03
bytes 1..n : ASCII JSON object with at least a "cmdName" field

The JSON is converted into a request string of the form cmdName?<flattened-fields> and dispatched via the same path as a URL element. This is convenient for senders that prefer to encode all parameters as JSON instead of a URL.

FILEBLOCK (code 4)

byte 0       : 0x04
byte 1       : streamID         (uint8, non-zero session ID)
bytes 2..4   : filePos / offset (24-bit unsigned integer, big-endian)
bytes 5..n   : binary payload

Used by the file/stream session machinery (see File Download Protocol, Real-Time Streams, and the upload/firmware-update flows owned by ProtocolExchange).

The streamID byte identifies the active file/stream session allocated by ufStart or dfStart. It is an 8-bit field, so non-zero session IDs are representable in the range 1..255. streamID value 0 is reserved internally as FILE_STREAM_ID_ANY ("unspecified / match by other criteria") and must not be used for normal FILEBLOCK data belonging to an allocated session.

The stream ID and position are stored as one big-endian 32-bit word in code:

(streamID << 24) | (filePos & 0x00ffffff)

This limits the per-block file/stream position field to 24 bits. Protocol implementations that need larger transfers must handle that at the session/protocol layer rather than placing position bits in the top byte.

Note — the precise byte offsets above match the constants in RICRESTMsg.h (RICREST_BODY_*, RICREST_FILEBLOCK_*). For exact, copy-pasteable values see that header. If you are implementing a non-Raft client, refer directly to the decode() and encode() functions in RICRESTMsg.cpp rather than transcribing the table.

When to use which framing

Transport Recommended framing
BLE GATT characteristic RICSerial (HDLC)
UART / RS-232 RICSerial (HDLC)
TCP socket RICFrame
WebSocket (binary) RICFrame
WebSocket (text-only) RICJSON
HTTP REST none — use the URL directly via RestAPIEndpointManager

Message numbering and matching

For request/response correlation:

  • The sender allocates a msgNumber in 0..255 (rolling).
  • The framework's response path calls endpointMsg.setAsResponse(cmdMsg) which copies the channelID and msgNumber from the original request.
  • Receivers match responses to requests on (channelID, msgNumber, msgTypeCode == RESPONSE).

Asynchronous messages (publishes, reports) use MSG_TYPE_PUBLISH / MSG_TYPE_REPORT and a different msgNumber stream.

Related

Clone this wiki locally