Skip to content

FileDownloadProtocol

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

File Download Protocol (OKTO)

The File Download OKTO Protocol handles downloading files from a Raft device to a connected client (e.g. via BLE or WebSocket). It uses a batch-acknowledged block transfer with flow control and CRC integrity checking.

Protocol Flow

Start (dfStart)

The client initiates a download by sending a dfStart command:

{
  "cmdName": "dfStart",
  "reqStr": "getFile",
  "fileType": "fs",
  "fileName": "data/log.csv",
  "batchMsgSize": 5000,
  "batchAckSize": 4,
  "crcAt": "end"
}
Field Description
fileName Path to the file on the device filesystem
fileType Source type, typically "fs" for filesystem
batchMsgSize Requested block size in bytes
batchAckSize Number of blocks per batch before requiring an ack
crcAt Optional. Set to "end" to defer CRC computation. Omit for legacy behaviour (CRC at start).

The server responds with:

{
  "req": "dfStart",
  "rslt": "ok",
  "batchMsgSize": 5000,
  "batchAckSize": 4,
  "streamID": 1,
  "fileLen": 45678,
  "crc16": "a1f2"
}
Field Description
batchMsgSize Actual block size (may be clamped by channel limits)
batchAckSize Actual batch ack size
streamID Non-zero session identifier for this transfer. The FILEBLOCK field can represent 1..255; 0 is reserved internally as FILE_STREAM_ID_ANY and is not a valid data-block session ID.
fileLen Total file size in bytes
crc16 Present only when CRC is computed at start (legacy mode or old firmware). CRC-16/CCITT of the entire file, hex-encoded.

Data Blocks

The server sends file data as binary RICREST file-block frames. Blocks are sent in batches of batchAckSize. Each block contains:

  • The allocated streamID from dfStart
  • File position (24-bit byte offset)
  • Block data

The FILEBLOCK header encodes the stream ID and file position as:

[streamID:1 byte][filePos:3 bytes big-endian][payload:N bytes]

Equivalently, the implementation stores the header position word as:

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

Download FILEBLOCKs for a dfStart session must carry the same non-zero streamID returned in the dfStart response. streamID 0 means "unspecified / any" inside the session lookup code and must not be emitted for normal transfer data.

Acknowledgement (dfAck)

After receiving a batch of blocks, the client sends an acknowledgement:

{
  "cmdName": "dfAck",
  "okto": 20000,
  "streamID": 1,
  "rslt": "ok"
}

The okto field tells the server how many bytes have been successfully received. The server uses this to advance its send window. If no ack is received within the timeout, the server retries from the last acknowledged position.

The streamID field must match the active dfStart session being acknowledged.

End (dfEnd)

After receiving all data, the client sends a dfEnd message and waits for the response:

{
  "cmdName": "dfEnd",
  "reqStr": "getFile",
  "fileName": "data/log.csv",
  "fileLen": 45678,
  "streamID": 1
}

The server responds with:

{
  "req": "dfEnd",
  "rslt": "ok",
  "crc16": "a1f2"
}

The crc16 field is present when CRC was deferred to the end ("crcAt": "end" in dfStart). The client verifies the file integrity by computing CRC-16/CCITT over the received data and comparing.

Cancel (dfCancel)

Either side can cancel the transfer:

{
  "cmdName": "dfCancel",
  "reqStr": "getFile",
  "streamID": 1
}

The server also sends a cancel message if it detects a timeout or error, including a reason field.

CRC Computation Modes

The protocol supports two modes for CRC-16/CCITT integrity checking:

Legacy Mode (CRC at Start)

When crcAt is omitted or set to "start" in the dfStart request:

  1. Server reads the entire file to compute CRC before responding to dfStart
  2. CRC is included in the dfStart response
  3. Client stores CRC and verifies after all data is received
  4. dfEnd response does not include CRC

This mode causes a blocking delay at the start of transfer proportional to file size, as the file must be read twice (once for CRC, once for sending).

Deferred Mode (CRC at End)

When crcAt is set to "end" in the dfStart request:

  1. Server responds to dfStart immediately without reading the file for CRC
  2. CRC is computed incrementally as each block is sent during the transfer
  3. CRC is included in the dfEnd response
  4. Client verifies CRC after receiving the dfEnd response

This mode eliminates the blocking delay at transfer start and avoids reading the file twice.

Backward Compatibility

The crcAt field provides seamless backward compatibility between old and new versions of firmware and client libraries:

Firmware Client Behaviour
Old Old Legacy mode. CRC in dfStart response.
Old New New client sends crcAt:"end", old firmware ignores unknown field. CRC still in dfStart response. Client detects CRC in dfStart and uses it.
New Old Old client doesn't send crcAt. New firmware defaults to legacy mode. CRC in dfStart response.
New New Deferred mode. No CRC in dfStart. Incremental CRC in dfEnd response. Non-blocking start.

Flow Control

  • Block timing: Minimum MIN_TIME_BETWEEN_BLOCKS_MS (100ms) between blocks
  • Batch acknowledgement: Server sends batchAckSize blocks then waits for a dfAck
  • Ack timeout: If no ack within BLOCK_MSGS_TIMEOUT_MS (3s), server retries from last acked position
  • Max retries: MAX_BATCH_BLOCK_ACK_RETRIES (10) before cancelling
  • Heap throttling: Blocks are paused when free internal heap drops below HEAP_LOW_WATER_MARK_BYTES (35KB)
  • Overall timeout: DOWNLOAD_FAIL_TIMEOUT_MS (2 hours) to accommodate slow BLE transfers

Implementation

Server-side files in RaftCore/components/comms/:

File Purpose
FileStreamProtocols/FileDownloadOKTOProtocol.h/.cpp Protocol state machine, block sending, ack handling, CRC modes
ProtocolExchange/FileStreamSession.h/.cpp Session management, file chunker access, CRC callback
FileStreamProtocols/FileStreamBase.h Base class with callback type definitions

Client-side files in raftjs/src/:

File Purpose
RaftFileHandler.ts File download orchestration, CRC verification
RaftTypes.ts Response type definitions, including RaftFileStartResp and RaftFileDownloadResult

For upload-style finite and open-ended streams that use ufStart, see Real-Time Streams and ProtocolExchange.

Clone this wiki locally