SmarterLink File Transfer Protocol

Files that need to be shared between participants are best served through a Data Store participant: the sender uploads the file to the Data Store, then publishes a SmarterLink event carrying a Location URI pointing to it. Receivers fetch the file independently and at their own pace, keeping interactions decoupled and asynchronous.

This document describes an alternative protocol for transferring files directly over MQTT. It should only be used when no Data Store is available in the deployment, or when the use case genuinely requires a synchronous point-to-point transfer.


Design Principles

  • Files may exceed the broker's maximum message size, so they are split into chunks and reassembled by the receiver.
  • Chunk size is negotiated per transfer so that participants with limited buffer capacity are accommodated.
  • Senders may serve multiple files concurrently using locally unique File Send Descriptors.
  • Receivers may receive multiple files concurrently using locally unique File Receive Descriptors.
  • No MQTT 5 features are used, ensuring compatibility with a wide range of brokers.
  • Integrity verification via hashing is optional, to accommodate low-resource devices that cannot perform hashing. SHA-256 is preferred when hashing is used.

Hashing

Hashing is optional. Some participants (particularly low-resource embedded devices) may not have the processing capacity to compute hashes, and the protocol accommodates this.

Two hashing functions are officially supported:

Function Identifier Notes
SHA-256 SHA-256 Preferred
MD5 MD5 Supported

Other hashing functions may be used if both the sender and receiver support them. Senders advertise their supported functions in MetadataResponse. Receivers select from that list when constructing a TransferRequest. A sender MUST reject a TransferRequest that specifies an algorithm it does not support.


Descriptors

File Send Descriptor (FSD): A named handle assigned by the sender to a file it is serving. Takes the form fs:<id>, where <id> is locally unique to the sender participant. Multiple FSDs may be active concurrently.

File Receive Descriptor (FRD): A named handle assigned by the receiver to manage an incoming transfer. Takes the form fr:<id>, where <id> is locally unique to the receiver participant. Multiple FRDs may be active concurrently.


File Location URI

When a sender produces a file it advertises it by embedding a File Location URI in a SmarterLink event payload (e.g. componentImageAcquired). The URI encodes the full MQTT topic of the FSD using the smarterlink:// scheme:

smarterlink://<full-mqtt-topic-of-the-fsd>

Example: A Data Producer with participant ID cam-01 at station station-a in area-1 / sub-1 / site-x, serving FSD fs:00:

smarterlink://smarter-link/site-x/area-1/sub-1/station-a/producers/~cam-01/fs:00

Receivers strip the smarterlink:// prefix and use the remainder as the MQTT topic root when constructing all FSD request and response topics.


Transfer Flow

Example

Alt text


Topics

Topic Publisher Payload
<fsd>/.get-metadata Receiver MetadataRequest
<fsd>/metadata Sender MetadataResponse
<fsd>/.transfer Receiver TransferRequest
<frd>/accepted Sender TransferAccepted
<frd>/rejected Sender TransferRejected
<frd>/chunks/<index>-<hash> Sender Raw binary

Topics prefixed with . are request topics: a participant publishes to them and expects a response on the corresponding non-dotted topic.

Request topic Response topic
<fsd>/.get-metadata <fsd>/metadata
<fsd>/.transfer <frd>/accepted or <frd>/rejected

Payloads

All negotiation payloads use the standard SmarterLink JSON envelope.

MetadataRequest

No fields beyond version. The receiver publishes this to request file metadata from the sender.

MetadataResponse

Field Type Required Description
version integer yes
filename string no Original filename, if known
size integer yes Total file size in bytes
ttl integer no Seconds the sender will continue serving this FSD. Expiry prevents new transfers from starting but does not abort a transfer already in progress.
maxSupportedChunkSize integer no Maximum chunk size in bytes the sender can produce. Receivers MUST NOT request a larger chunk size.
supportedHashFunctions string[] yes Hashing function identifiers supported by the sender. May be empty if the sender cannot perform hashing.

TransferRequest

Field Type Required Description
version integer yes
responseTopicBase string yes Full MQTT topic base of the receiver's FRD (used as <frd> in all response and chunk topics)
chunkSize integer yes Requested chunk size in bytes
hashingFunction string no Hashing function to use for per-chunk and whole-file integrity verification. This can be included when the sender advertises any supported functions. If omitted, no hashing is performed.
startChunkIndex integer no Zero-based index of the first chunk to send (default: 0). Used to resume an interrupted transfer.
chunkCount integer no Number of chunks to send. If absent, all chunks from startChunkIndex to end of file are sent.

TransferRejected

Field Type Required Description
version integer yes
reason string yes Human-readable description of why the request was rejected (e.g. "unsupported chunk size", "FSD expired")

TransferAccepted

Field Type Required Description
version integer yes
hashes Hash[] yes Whole-file hash(es). Must include an entry for the algorithm specified in TransferRequest.hashingFunction, if one was provided. May include additional algorithms. Empty array if no hashing function was requested.

Hash object:

Field Type Required Description
hashingFunction string yes The algorithm identifier (e.g. SHA-256, MD5)
value string yes Lowercase hex-encoded hash value

File Chunks

Each chunk is published as raw binary (no JSON envelope) to:

<frd>/chunks/<index>-<hash>

where <index> is the zero-based chunk index and <hash> is the lowercase hex-encoded hash of that chunk's bytes using the algorithm from TransferRequest.hashingFunction. If no hashing function was requested, <hash> is an empty string and the topic ends with -.

When hashing is in use, receivers are advised to verify the per-chunk hash and discard any chunk whose hash does not match. Discarded chunks can be re-fetched by issuing a new TransferRequest to the same FSD, using startChunkIndex and chunkCount to target only the missing chunks.


End-of-Transfer Detection

End of transfer is implicit. The receiver computes the expected chunk count from the information exchanged during negotiation:

expectedChunks = ⌈size / chunkSize⌉

adjusted by startChunkIndex and chunkCount from the TransferRequest if present. The transfer is complete when all expected chunks have been received. When hashing is in use, receivers are advised to verify the reassembled file against the whole-file hash provided in TransferAccepted.


Chunk Publishing

Senders MUST publish chunks in sequential index order. Receivers MUST tolerate out-of-order delivery, as sequential delivery is not guaranteed by the broker.

Senders SHOULD rate-limit chunk publishing to avoid saturating the broker. No specific rate is mandated; implementations should make the limit configurable.