MQTT v5.0 Platform Architecture

March 25, 2026 · View on GitHub

The mqtt5 platform is organized around a single design principle: keep the protocol core portable, and let each target environment provide its own runtime. A shared no_std protocol crate handles packet parsing, session management, and validation, while three higher-level crates supply the async runtime, transport layer, and platform bindings appropriate for native servers, browser tabs, or command-line tooling.

Crate Organization

Five crates provide platform-specific implementations sharing a common protocol core:

graph TD
    subgraph protocol["mqtt5-protocol (no_std + alloc)"]
        Packets
        Properties
        Session
        Validation
    end

    protocol --> mqtt5["mqtt5<br/>(std + Tokio)<br/>Native broker<br/>TCP/TLS/QUIC"]
    protocol --> wasm["mqtt5-wasm<br/>(WASM + browser)<br/>Browser client<br/>In-tab broker<br/>MessagePort"]
    protocol --> cli["mqttv5-cli<br/>(CLI binary)<br/>pub/sub cmds<br/>broker cmd<br/>acl/passwd"]
    protocol --> conformance["mqtt5-conformance<br/>(test suite)<br/>OASIS spec tests<br/>Raw MQTT client<br/>Test harness"]

mqtt5-protocol (Platform-Agnostic Core)

Platform-agnostic MQTT v5.0 protocol for native, WASM, and embedded targets. Supports no_std environments with alloc.

The packet module defines all MQTT v5.0 packet types (CONNECT, PUBLISH, SUBSCRIBE, etc.), while encoding handles binary wire format including variable-length integers, UTF-8 strings, and binary data. Protocol-level concerns live in protocol/v5, which provides property accessors and reason codes.

Session management spans several submodules: flow_control for QoS pacing, limits for connection constraints and message expiry, queue for priority-aware message queuing with expiry, subscription for subscription state, and topic_alias for alias mapping. The validation module enforces topic rules, namespace constraints, and shared subscription parsing.

The remaining modules provide supporting infrastructure. types defines core domain objects (ConnectOptions, PublishOptions, QoS, Message, WillMessage). connection implements the connection state machine with reconnect config and events. topic_matching handles MQTT wildcard matching, and bridge provides direction, topic mapping, and forwarding evaluation primitives.

Lower-level utilities include error and error_classification (error types and recoverability), keepalive (timeout calculation), flags (CONNECT/CONNACK/PUBLISH flag parsing), qos2 (QoS 2 state machine), packet_id (identifier management), transport (transport trait definition), time (platform-abstracted time for std, WASM, and embedded), and prelude (alloc/std compatibility).

FeatureDescription
std (default)Full std support with thiserror, tracing

For single-core targets, use cfg: rustflags = ["--cfg", "portable_atomic_unsafe_assume_single_core"]

mqtt5 (Native)

Full-featured async client and broker for Linux, macOS, Windows. This is the primary crate for production deployments.

The client provides MqttClient with automatic reconnection and exponential backoff, QoS 0/1/2 with proper flow control, and connection event callbacks. Transport options include TLS via rustls (CA and client certificate support) and QUIC multistream for parallel operations. Enhanced authentication covers SCRAM-SHA-256, JWT, and custom handlers. Supporting modules include callback (subscription callback dispatch), tasks (background packet reader, keepalive, reconnection), and types (ConnectOptions, ConnectionStats).

The broker supports multi-transport operation (TCP, TLS, WebSocket, QUIC on different ports) with pluggable authentication (password with argon2, certificate, JWT, federated JWT) and ACL-based authorization with wildcard topic matching. Broker-to-broker bridging with loop prevention, file-based and in-memory storage backends, and $SYS topics for statistics round out the core feature set.

Additional broker capabilities include session takeover semantics, configuration hot-reload (file watching, SIGHUP via CLI), echo suppression via configurable user property matching, and optional payload codec support (gzip, deflate) behind feature flags. The session module extends protocol-level primitives with async flow control (Tokio semaphores), QUIC stream flow registry, retained message storage, and full session state. Supporting modules include codec (payload compression with CodecRegistry), crypto (TLS certificate verifiers), and optional OpenTelemetry integration.

mqtt5-wasm (WebAssembly)

Client and broker for browser environments. Published to npm as mqtt5-wasm.

npm install mqtt5-wasm

The WasmMqttClient exposes a JavaScript Promise API using Rc<RefCell<T>> for single-threaded client state. The WasmBroker provides a complete in-browser broker using Arc<RwLock<T>> for state shared via the Tokio single-threaded runtime. Three transports are available: WebSocket, MessagePort, and BroadcastChannel. Optional payload codecs (gzip, deflate) use miniz_oxide.

mqttv5-cli (Command-Line Tool)

Unified CLI for MQTT operations: mqttv5 pub (publish), mqttv5 sub (subscribe), mqttv5 broker (run broker), mqttv5 acl (manage ACLs), mqttv5 passwd (manage passwords), mqttv5 scram (manage SCRAM credentials), and mqttv5 bench (performance benchmarking).

mqtt5-conformance (Specification Test Suite)

OASIS MQTT v5.0 specification conformance test suite (not published to crates.io). The RawMqttClient provides byte-level packet control for protocol edge case testing. A test harness manages broker lifecycle, and manifest-driven organization tracks coverage of the specification's normative statements.

Embedded Target Support

The protocol crate supports embedded targets via no_std:

TargetCommandNotes
Cortex-M4 (ARM)--target thumbv7em-none-eabihfHas hardware atomics
RISC-V (atomics)--target riscv32imac-unknown-none-elfHas atomic extension
ESP32-C3--target riscv32imc-unknown-none-elfConfigure single-core via .cargo/config.toml

For single-core targets without hardware atomics, add to .cargo/config.toml:

[target.riscv32imc-unknown-none-elf]
rustflags = ["--cfg", "portable_atomic_unsafe_assume_single_core"]

Build commands:

cargo make embedded-cortex-m4   # ARM Cortex-M4
cargo make embedded-riscv       # RISC-V with atomics
cargo make embedded-verify      # All embedded targets

Core Architectural Principle: Direct Async/Await

Every operation in the platform is a direct async function call — no event loops, no command channels, no polling. Tokio provides the async runtime (native), and the resulting code is both simpler to debug and more efficient than channel-based architectures.

Client Architecture

Core Components

MqttClient is the main client struct. It holds shared state (transport, session, callbacks) behind Arc<RwLock<T>> for concurrent access, and exposes direct async methods for all MQTT operations.

The transport layer provides async I/O through read_packet() and write_packet() methods, with implementations for TCP, TLS, WebSocket, and QUIC. Three background tasks run concurrently: a packet reader that dispatches incoming packets, a keep-alive task that sends PINGREQ at intervals, and a reconnection task with exponential backoff. TLS configuration stores CA certs and client certificates, applied automatically for mqtts:// URLs with AWS IoT ALPN support.

Data Flow

graph LR
    subgraph Incoming
        N1[Network] --> read["Transport.read_packet()"] --> reader["packet_reader_task"] --> handle["handle_packet()"] --> CB[Callbacks]
    end

    subgraph Outgoing
        CM["Client method"] --> write["Transport.write_packet()"] --> N2[Network]
    end

Error Handling

The client validates acknowledgment reason codes: PUBACK (QoS 1) returns MqttError::PublishFailed(reason_code) on error, PUBREC/PUBCOMP (QoS 2) validates the complete handshake, and authorization failures surface as ReasonCode::NotAuthorized (0x87) from ACL denials.

Broker Architecture

Core Components

MqttBroker manages configuration and lifecycle, spawning one listening task per transport. Server listeners accept connections over TCP (direct accept() loop), TLS (rustls with certificate validation), WebSocket (HTTP upgrade with tokio-tungstenite, path enforcement, Origin validation), and QUIC (quinn endpoint with multistream).

Each accepted connection spawns a ClientHandler that directly reads and writes packets, manages client session state, and handles the MQTT protocol. The MessageRouter performs subscription matching using MQTT-compliant topic wildcards (+, #), protects system topics ($SYS/# excluded from #), and supports shared subscriptions ($share/group/topic).

The storage backend persists sessions, retained messages, queued messages, and inflight messages. The file-based backend uses percent-encoded filenames with atomic writes and fsync; the memory backend stores everything in-process.

Broker Data Flow

graph LR
    subgraph Connection
        L[Listener] --> accept["accept()"] --> spawn["spawn(ClientHandler)"]
    end

    subgraph Processing
        C[Client] --> rp["read_packet()"] --> hp["handle_packet()"] --> RS[Router / Storage]
    end

    subgraph Routing
        P[Publisher] --> route["Router.route_message()"] --> subs[Subscribers] --> wp["write_packet()"]
    end

Authentication System

Authentication is pluggable via the AuthProvider trait. Basic providers include AllowAllAuthProvider (development), PasswordAuthProvider (file-based with argon2 hashing), and CertificateAuthProvider (TLS peer certificate fingerprint validation, 64-char hex SHA-256). ComprehensiveAuthProvider combines password auth with ACL into a single provider, CompositeAuthProvider chains a primary and fallback provider, and RateLimitedAuthProvider wraps any provider with rate limiting.

Enhanced authentication mechanisms live in a separate module. ScramSha256AuthProvider implements SCRAM-SHA-256 without channel binding (rejects concurrent auth for the same client ID). PlainAuthProvider handles PLAIN over TLS with a pluggable credential store. JwtAuthProvider validates JWT tokens with kid-based verifier selection and mandatory exp/sub claims. FederatedJwtAuthProvider adds multi-issuer support with JWKS auto-refresh and compiled regex claim patterns.

Client-side auth handlers implement the AuthHandler trait: ScramSha256AuthHandler, JwtAuthHandler, and PlainAuthHandler. The MqttClientTrait enables mock testing via MockMqttClient.

Sessions are bound to the authenticated user_id (rejects reconnection from a different user), ACLs are re-checked on session restore (pruning unauthorized subscriptions), and the NoVerification TLS bypass is restricted to pub(crate) scope.

ACL System

Rule-based access control supports wildcard topic matching, separate publish and subscribe permissions, and role-based access control (RBAC). The %u substitution expands to the authenticated username in topic patterns, rejecting usernames that contain +, #, or / to prevent wildcard injection. Topic names are validated on publish after topic alias resolution.

The broker stamps two user properties on every PUBLISH: x-mqtt-sender (authenticated user_id) and x-mqtt-client-id (publisher's MQTT client_id, anti-spoof stripped). ACL files are managed via mqttv5 acl add/remove/list/check.

Bridge Manager

Bridges create broker-to-broker connections where each bridge acts as a client to a remote broker. Topic mappings with prefix transformation control which messages flow in which direction, and loop prevention via bridge headers stops message cycles. Bridges support TLS/mTLS with AWS IoT integration and reconnect with exponential backoff.

Load Balancer (Server Redirect)

The broker can act as a pure connection redirector for horizontal scaling. When load_balancer is configured, the broker never handles MQTT traffic — it only redirects clients to a backend.

On each CONNECT, the broker hashes the client ID (byte-sum modulo backend count) to deterministically select a backend. It responds with a CONNACK containing reason code UseAnotherServer (0x9C) and a ServerReference property set to the backend URL. The client parses the URL and reconnects directly to the backend.

The client-side redirect loop in connect_internal() follows up to 3 hops. The URL scheme in ServerReference determines the transport for the backend connection: mqtt:// for TCP, mqtts:// for TLS, quic:// for QUIC.

BrokerConfig::default()
    .with_load_balancer(LoadBalancerConfig::new(vec![
        "mqtt://backend1:1883".into(),
        "mqtt://backend2:1883".into(),
    ]))

Resource Monitor

The resource monitor tracks connections, bandwidth, and messages, enforcing rate limits and quotas through direct checks rather than monitoring loops.

Event Hooks

Custom event handlers via BrokerEventHandler trait:

HookEvent TypeTrigger
on_client_connectClientConnectEventClient CONNECT packet accepted
on_client_subscribeClientSubscribeEventClient SUBSCRIBE processed
on_client_unsubscribeClientUnsubscribeEventClient UNSUBSCRIBE processed
on_client_publishClientPublishEvent -> PublishActionClient PUBLISH received (includes user_id, response_topic, correlation_data); returns Continue, Handled, or Transform(PublishPacket)
on_client_disconnectClientDisconnectEventClient disconnects (clean or unexpected)
on_retained_setRetainedSetEventRetained message stored or cleared
on_message_deliveredMessageDeliveredEventQoS 1/2 message delivered to subscriber

Usage: BrokerConfig::default().with_event_handler(Arc::new(handler))

QUIC Transport Architecture

QUIC provides MQTT over QUIC (RFC 9000) with multistream support. A single QUIC connection carries a persistent control stream for session management alongside separate data streams for publish traffic.

graph LR
    subgraph Client
        direction TB
    end

    subgraph "QUIC Connection (TLS 1.3)"
        direction TB
        S0["Stream 0 (Control)<br/>CONNECT / CONNACK<br/>SUBSCRIBE / SUBACK<br/>PINGREQ / PINGRESP"]
        S2["Stream 2+ (Data — client initiated)<br/>PUBLISH (QoS 0/1/2)"]
        S3["Stream 3+ (Data — server initiated)<br/>PUBLISH (subscribed messages)"]
    end

    subgraph Broker
        direction TB
    end

    Client <--> S0 <--> Broker
    Client --> S2 --> Broker
    Broker --> S3 --> Client

Stream Strategies

Three strategies control how MQTT packets map to QUIC streams. ControlOnly uses a single bidirectional stream (traditional MQTT behavior). DataPerPublish opens a new stream per QoS 1/2 publish for maximum parallelism. DataPerTopic pools streams by topic with LRU caching for topic isolation without per-message overhead. DataPerSubscription is deprecated (architecturally identical to DataPerTopic).

Connection Migration

QUIC connections survive network address changes (WiFi to cellular, IP reassignment). On the server side, ClientHandler::check_quic_migration() polls Connection::remote_address() after each packet and atomically updates per-IP tracking on change. On the client side, MqttClient::migrate() calls Endpoint::rebind() with a new UDP socket — all streams, sessions, and subscriptions remain valid.

Benefits

  • No head-of-line blocking
  • Parallel QoS flows
  • Built-in TLS 1.3
  • Connection migration for mobile clients

WASM Architecture

Browser environments require different concurrency primitives and transport mechanisms than native platforms.

Adaptations for Browser

The client uses Rc<RefCell<T>> for single-threaded state, while the broker uses Arc<RwLock<T>> (shared via Tokio single-threaded runtime). An async bridge converts Rust futures to JavaScript Promises. File I/O is unavailable, so all storage is memory-only. Browser TLS (wss://) is handled by the browser's WebSocket implementation.

WASM Client

pub struct WasmMqttClient {
    state: Rc<RefCell<ClientState>>
}
  • Connection: connect(url), connect_message_port(port), connect_broadcast_channel(name)
  • Publishing: publish(), publish_qos1(), publish_qos2()
  • Subscription: subscribe_with_callback(topic, callback)
  • Events: on_connect(), on_disconnect(), on_error(), on_connectivity_change()

WASM Broker

The in-browser broker provides full MQTT v5.0 protocol support over MessagePort (for in-tab clients) with memory-only storage. create_client_port() creates a MessageChannel for direct client connections. Bridge support via WasmBridgeManager includes loop prevention.

Browser Transports

Three transport types serve different deployment scenarios. WebSocket connects to an external broker via web_sys::WebSocket. MessagePort communicates with an in-tab broker at zero network overhead. BroadcastChannel enables cross-tab messaging between browser windows.

Telemetry (Optional)

OpenTelemetry integration (behind the opentelemetry feature flag) provides end-to-end distributed tracing across the publish-subscribe pipeline.

graph LR
    Pub[Publisher] -->|inject traceparent<br/>into user properties| PUB["PUBLISH packet<br/>with trace context"]
    PUB --> Broker
    Broker -->|extract context,<br/>create span| Bridge
    Bridge -->|forward properties| Sub[Subscriber]
    Sub -->|extract context| App[Application]

Configuration via TelemetryConfig and BrokerConfig::with_opentelemetry().

Testing Architecture

The platform uses a layered testing strategy. Unit tests exercise individual components directly. Integration tests run full client-broker interactions with real connections. Turmoil tests (behind the turmoil-testing feature) simulate network failures. Property tests via proptest verify protocol invariants across random inputs. Conformance tests in the mqtt5-conformance crate validate OASIS MQTT v5.0 specification compliance using a raw MQTT client for precise packet control.