Files
simplexmq/spec/modules/Simplex/Messaging/Protocol.md
Evgeny @ SimpleX Chat 1cc4d98dd0 terms 2
2026-03-13 17:56:14 +00:00

69 lines
5.5 KiB
Markdown

# Simplex.Messaging.Protocol
> SMP protocol types, commands, command results, encoding/decoding, and transport functions.
**Source**: [`Protocol.hs`](../../../../src/Simplex/Messaging/Protocol.hs)
**Protocol spec**: [`protocol/simplex-messaging.md`](../../../../protocol/simplex-messaging.md) — SimpleX Messaging Protocol.
## Overview
This module defines the SMP protocol's type-level structure, wire encoding, and transport batching. It does not implement the router or client — those are in [Server.hs](./Server.md) and [Client.hs](./Client.md). The protocol spec governs the command semantics; this doc focuses on non-obvious implementation choices.
## Two separate version scopes
SMP client protocol version (`SMPClientVersion`, 4 versions) is separate from SMP relay protocol version (`SMPVersion`, up to version 19, defined in [Transport.hs](./Transport.md)). The client version governs client-to-client concerns (binary encoding, multi-host addresses, SKEY command, short links). The relay version governs client-to-router wire format, transport encryption, and command availability. See comment above `SMPClientVersion` data declaration for version history.
## maxMessageLength — version-dependent
`maxMessageLength` returns three different sizes depending on the relay version:
- v11+ (`encryptedBlockSMPVersion`): 16048
- v9+ (`sendingProxySMPVersion`): 16064
- older: 16088
The source has `TODO v6.0 remove dependency on version`. The type-level `MaxMessageLen` is fixed at 16088 with `TODO v7.0 change to 16048`.
## Type-level party system
10 `Party` constructors with `SParty` singletons, `PartyI` typeclass, and three constraint type families (`QueueParty`, `BatchParty`, `ServiceParty`). Invalid party usage produces compile-time errors via the `(Int ~ Bool, TypeError ...)` trick — the unsatisfiable `Int ~ Bool` constraint forces GHC to emit the `TypeError` message.
## IdsHash — reversible XOR for state drift monitoring
`IdsHash` uses `BS.zipWith xor` as its `Semigroup`. `queueIdHash` computes MD5 of the queue ID (16 bytes). `mempty` is 16 zero bytes. See comment on `subtractServiceSubs` for the reversibility property. `mconcat` is optimized to avoid repeated pack/unpack per step.
## TransmissionAuth — size-based type discrimination
`decodeTAuthBytes` distinguishes authenticator from signature by checking `B.length s == C.cbAuthenticatorSize`. This is a trap: if `cbAuthenticatorSize` ever coincides with a valid signature encoding size, the discrimination breaks. See comment on `tEncodeAuth` for the backward compatibility note (the encoding is backwards compatible with v6 that used `Maybe C.ASignature`).
## Service signature — state-dependent parser contract
In `transmissionP`, the service signature is only parsed when `serviceAuth` is true AND the authenticator is non-empty (`not (B.null authenticator)`). This means the parser's behavior depends on earlier parsed state — the service signature field is conditionally present on the wire. If a future change makes the authenticator always non-empty (or always empty), it silently changes whether service signatures are parsed.
## transmissionP / implySessId
When `implySessId` is `True`, the session ID is not transmitted on the wire — `transmissionP` sets `sessId` to `""` and prepends the local `sessionId` to the `authorized` bytes for verification. In `tDecodeServer`/`tDecodeClient`, session ID check is bypassed when `implySessId` is `True`.
## batchTransmissions_ — constraints and ordering
See comment for the 19-byte overhead calculation (pad size + transmission count + auth tag). Maximum 255 transmissions per batch (single-byte count). Uses `foldr` with `(:)` accumulation, which preserves original transmission order within each batch.
## ClientMsgEnvelope — two-layer message format
`ClientMsgEnvelope` has a `PubHeader` (client protocol version + optional X25519 DH key) and an encrypted body. The decrypted body is a `ClientMessage` containing a `PrivHeader` with prefix-based type discrimination: `"K"` for `PHConfirmation` (includes public auth key), `"_"` for `PHEmpty`.
## MsgFlags — forward-compatible parsing
The `MsgFlags` parser consumes the `notification` Bool then calls `A.takeTill (== ' ')` to swallow any remaining flag data. See comment on `MsgFlags` encoding for the 7-byte size constraint. Future flags added after `notification` are silently consumed and discarded by old clients.
## BrokerErrorType NETWORK — detail loss
The `NETWORK` variant of `BrokerErrorType` encodes as just `"NETWORK"` (detail dropped), with `TODO once all upgrade` comment. The parser falls back to `NEFailedError` when the `NetworkError` detail can't be parsed (`_smpP <|> pure NEFailedError`). This means a newer router's detailed network error is seen as `NEFailedError` by older clients.
## Version-dependent encoding — scope
`encodeProtocol` for both `Command` and `BrokerMsg` uses extensive version-conditional encoding. `NEW` has four encoding paths, `IDS` has five. All encoding paths for `IDS` must maintain the same field ordering — this is an implicit contract between encoder and decoder with no compile-time enforcement.
## SUBS/NSUBS — asymmetric defaulting
When the router parses `SUBS`/`NSUBS` from a client using a version older than `rcvServiceSMPVersion`, both count and hash default (`-1` and `mempty`). For the result side (`SOKS`/`ENDS` via `serviceRespP`), count is still parsed from the wire — only hash defaults to `mempty`. This asymmetry means command-side and result-side parsing have different fallback behavior for the same version boundary.