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

5.5 KiB

Simplex.Messaging.Protocol

SMP protocol types, commands, command results, encoding/decoding, and transport functions.

Source: Protocol.hs

Protocol spec: 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 and Client.hs. 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). 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.