wolfMQTT/BROKER.md

11 KiB

wolfMQTT Broker

wolfMQTT includes a lightweight MQTT broker suitable for embedded and resource-constrained environments. It serves both MQTT v3.1.1 and v5.0 clients, with optional TLS via wolfSSL, optional WebSocket transport, and optional encrypted persistence. The broker uses non-blocking sockets driven by a single select() loop, so it runs without threads.

Features

  • QoS 0, QoS 1, and QoS 2 publish/subscribe (full QoS 2 flow with PUBREC/PUBREL/PUBCOMP)
  • Retained messages
  • Last Will and Testament (LWT), including v5 Will Delay Interval
  • Wildcard subscriptions (+ and #)
  • Username/password authentication
  • MQTT v5 ordering and Receive Maximum (per-subscriber inflight shaping)
  • TLS support (requires wolfSSL with --enable-tls)
  • WebSocket / secure WebSocket transport (requires libwebsockets; see the WebSocket section of the main README.md)
  • Clean session handling with subscription persistence
  • Keep-alive monitoring with automatic client disconnect
  • Unique client ID enforcement (existing session takeover)
  • Optional on-disk persistence of sessions, subscriptions, retained messages, and offline queues, with optional AES-GCM encryption-at-rest
  • Static memory mode (WOLFMQTT_STATIC_MEMORY) for zero-malloc operation

Quick start

With autotools:

./configure --enable-broker
make
./src/mqtt_broker -p 1883

With CMake:

cmake .. -DWOLFMQTT_BROKER=yes
cmake --build .

For TLS:

./configure --enable-broker --enable-tls
make
./src/mqtt_broker -p 8883 -t -A ca-cert.pem -K server-key.pem -c server-cert.pem

Run ./src/mqtt_broker -h to see the options compiled into your build.

Command-line options

usage: mqtt_broker [-p port] [-v level] [-u user] [-P pass]
                   [-t] [-s port] [-V ver] [-c cert] [-K key] [-A ca]
                   [-w port] [-D dir] [-E source]
Option Available when Description
-p <port> always Plain (non-TLS) port (default: 1883)
-v <level> always Log level: 1=error, 2=info (default), 3=debug
-u <user> auth build Username for authentication
-P <pass> auth build Password for authentication
-t TLS build Enable the TLS listener
-s <port> TLS build TLS port (default: 8883)
-V <ver> TLS build TLS version: 12=TLS 1.2, 13=TLS 1.3 (default: auto)
-c <file> TLS build Server certificate file (PEM)
-K <file> TLS build Server private key file (PEM)
-A <file> TLS build CA certificate for mutual TLS (PEM)
-w <port> WebSocket build WebSocket listen port (enables WebSocket)
-D <dir> persist build Persistent storage directory (enables persistence; default /var/lib/wolfmqtt on Linux or /private/var/lib/wolfmqtt on macOS)
-E <source> encrypt + dev-key build Encryption key source. Only dev is recognized, selecting the development hard-coded key. NOT FOR PRODUCTION.

Build options

All broker features are enabled by default and can be disabled at build time to reduce code and memory footprint on constrained platforms.

Feature Autotools CMake Define
Broker support --enable-broker -DWOLFMQTT_BROKER=yes WOLFMQTT_BROKER
Retained messages --disable-broker-retained -DWOLFMQTT_BROKER_RETAINED=no WOLFMQTT_BROKER_NO_RETAINED
Last Will and Testament --disable-broker-will -DWOLFMQTT_BROKER_WILL=no WOLFMQTT_BROKER_NO_WILL
Wildcard subscriptions --disable-broker-wildcards -DWOLFMQTT_BROKER_WILDCARDS=no WOLFMQTT_BROKER_NO_WILDCARDS
Authentication --disable-broker-auth -DWOLFMQTT_BROKER_AUTH=no WOLFMQTT_BROKER_NO_AUTH
Logging --disable-broker-log -DWOLFMQTT_BROKER_LOG=no WOLFMQTT_BROKER_NO_LOG
Plain-text listener --disable-broker-insecure -DWOLFMQTT_BROKER_INSECURE=no WOLFMQTT_BROKER_NO_INSECURE

The maximum QoS the broker negotiates is capped by --enable-max-qos=<0,1,2> (default 2). Setting it to 1 or 0 compiles out the QoS 2 state machine and shrinks the broker.

Static memory tuning

When built with WOLFMQTT_STATIC_MEMORY, the broker uses fixed-size arrays instead of dynamic allocation. The limits below can be overridden via CFLAGS at build time.

Macro Default Description
BROKER_MAX_CLIENTS 8 Maximum concurrent client connections
BROKER_MAX_SUBS 32 Maximum total subscriptions across all clients
BROKER_MAX_RETAINED 16 Maximum retained messages
BROKER_MAX_CLIENT_ID_LEN 64 Maximum client ID length
BROKER_MAX_USERNAME_LEN 64 Maximum username length
BROKER_MAX_PASSWORD_LEN 64 Maximum password length
BROKER_MAX_FILTER_LEN 128 Maximum subscription filter length
BROKER_MAX_TOPIC_LEN 128 Maximum topic name length
BROKER_MAX_PAYLOAD_LEN 4096 Maximum retained message payload
BROKER_MAX_WILL_PAYLOAD_LEN 256 Maximum LWT payload
BROKER_MAX_PENDING_WILLS 4 Maximum queued pending wills
BROKER_MAX_INBOUND_QOS2 16 Concurrent inbound QoS 2 packet IDs per client
BROKER_MAX_STATIC_ORPHAN_SESSIONS BROKER_MAX_CLIENTS Persistent sessions with a bounded offline queue
BROKER_MAX_STATIC_OFFLINE_MSGS_PER_SUB 8 Queued QoS 1/2 messages per static persistent session
BROKER_MAX_STATIC_OFFLINE_DATA_LEN 256 Maximum property and payload bytes per queued message
BROKER_RX_BUF_SZ 4096 Per-client receive buffer size
BROKER_TX_BUF_SZ 4096 Per-client transmit buffer size
BROKER_TIMEOUT_MS 1000 select() timeout
BROKER_LISTEN_BACKLOG 128 Listen queue depth

Retaining a message is best-effort. A RETAIN=1 PUBLISH whose retained copy cannot be stored (the retained table is full at BROKER_MAX_RETAINED, the payload exceeds BROKER_MAX_PAYLOAD_LEN, or an allocation fails) is still delivered to every current subscriber and acknowledged with success at any QoS or protocol level. Only the retained copy is skipped, so a later subscriber will not receive that message until the topic is published again with room to store it. The skipped retained copy is logged broker-side. This matches how servers such as Mosquitto treat a full retained store: live delivery is never sacrificed to retention. Raise BROKER_MAX_RETAINED (or BROKER_MAX_PAYLOAD_LEN) if retained-topic capacity matters for your deployment.

The static offline queue is broker-owned fixed storage. Its dominant RAM cost is approximately sessions * messages * (topic length + data length) bytes, plus queue metadata. A new persistent CONNECT is refused when all session slots are occupied; existing persistent sessions are never evicted to admit it. An MQTT v3.1.1 CleanSession=0 Session reserves its slot until that Client Identifier reconnects with CleanSession=1. With the default limits, the static Session queues add approximately 27 KB to MqttBroker. If a matching QoS 1/2 publication cannot fit in a Session queue, the broker preserves the existing backlog and drops that Session's new delivery without disconnecting it. A connected client whose Session queue is already full is disconnected so it can resume the preserved backlog later. Capacity for one Session does not change the publisher's ACK. Increase BROKER_MAX_STATIC_OFFLINE_DATA_LEN when durable Sessions must retain messages whose property and payload bytes exceed 256.

The per-subscriber inflight window is bounded by BROKER_MAX_INFLIGHT_PER_SUB and, for MQTT v5, the client's Receive Maximum. Define BROKER_MAX_INFLIGHT_PER_SUB=1 to force strict serial delivery.

Persistence

Build with --enable-broker-persist to persist sessions, subscriptions, retained messages, and dynamic-memory offline queues across restarts. Static Session queues are RAM-only, so their queued QoS 1/2 publications do not survive a broker restart. The persistence layer is hook-based: a default POSIX backend stores records as files under the directory given with -D (default /var/lib/wolfmqtt on Linux or /private/var/lib/wolfmqtt on macOS). Embedded targets can supply their own storage backend through MqttBroker_SetPersistHooks().

The POSIX backend validates the store before using it and refuses to start otherwise. The directory given with -D, and each namespace directory beneath it, must be owned by the broker's effective user and must not be group- or other-writable; no component of the path may be a symlink. A directory the broker creates itself satisfies this (mode 0700), but one pre-created by a script or installer inherits the ambient umask - under a relaxed umask such as 002 it comes out 0775 and is rejected. Create it explicitly:

mkdir -p /var/lib/wolfmqtt && chmod 0700 /var/lib/wolfmqtt
Macro Default Description
BROKER_MAX_PERSIST_SESSIONS 64 Dynamic-memory persistent sessions retained across restarts
BROKER_MAX_OFFLINE_MSGS_PER_SUB 32 Offline queue depth per session
WOLFMQTT_BROKER_PERSIST_SCHEMA_VER 5 On-disk record schema version

Encryption at rest

Add --enable-broker-persist-encrypt (requires --enable-broker-persist) to wrap persisted records with wolfCrypt AES-GCM. The key is provided by a derive_key callback that real deployments install via MqttBroker_SetPersistHooks() before starting the broker.

For development and CI only, the CLI can link a fixed-pattern derive_key hook so the AES-GCM round-trip can be exercised without external key management. This is NOT a configure option -- define the macro through CFLAGS:

CFLAGS="-DWOLFMQTT_BROKER_PERSIST_ENCRYPT_DEV_KEY" \
  ./configure --enable-broker --enable-broker-persist --enable-broker-persist-encrypt
make
./src/mqtt_broker -p 1883 -D ./state -E dev

The dev key is a trivially-recoverable hard-coded pattern. Never define WOLFMQTT_BROKER_PERSIST_ENCRYPT_DEV_KEY in a production build, and never pass -E dev in production -- doing so substitutes the fixed key for real key management. Production builds omit the macro entirely, so the -E option and the dev hook are not present in the binary.

Testing

The repository ships an end-to-end broker test harness:

./scripts/broker.test

It builds the client examples (examples/pub-sub/mqtt-pub, examples/pub-sub/mqtt-sub) and mosquitto-based checks against the wolfMQTT broker, covering QoS flows, retained messages, wildcards, persistence round-trips, and AES-GCM encryption (when the dev-key hook is linked). Tests that depend on features not present in the current build are reported as SKIP.

The CONNECT-handler unit test (tests/test_broker_connect) is part of make check and exercises the broker packet path with a mock network layer.

Limitations

The wolfMQTT broker targets embedded and edge use cases. It is intentionally smaller in scope than full-featured server brokers such as Mosquitto or EMQX: there is no clustering, no bridging, no plugin/ACL framework, and no dynamic configuration reload. For large-scale or feature-rich deployments use a dedicated server broker; for a small, auditable, optionally-TLS broker that runs without threads or a heap, wolfMQTT is a good fit.