Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/broker-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,27 @@ jobs:
cflags: ""
wolfmqtt_opts: "--enable-v5 --enable-broker --enable-max-qos=0"
skip_broker_test: "yes"
- name: "Broker v5 (ordering / Receive Maximum)"
cflags: ""
wolfmqtt_opts: "--enable-broker --enable-v5"
- name: "Broker v5 strict-serial (inflight=1)"
cflags: "-DBROKER_MAX_INFLIGHT_PER_SUB=1"
wolfmqtt_opts: "--enable-broker --enable-v5"
- name: "Broker v5 static memory"
cflags: "-DWOLFMQTT_STATIC_MEMORY"
wolfmqtt_opts: "--enable-broker --enable-v5"
- name: "Broker with persist"
cflags: ""
wolfmqtt_opts: "--enable-broker --enable-v5 --enable-broker-persist"
Comment thread
dgarske marked this conversation as resolved.
- name: "Broker with persist + TLS"
cflags: ""
wolfmqtt_opts: "--enable-broker --enable-v5 --enable-broker-persist --enable-tls"
Comment thread
embhorn marked this conversation as resolved.
- name: "Broker with persist + AES-GCM encryption"
cflags: "-DWOLFMQTT_BROKER_PERSIST_ENCRYPT_DEV_KEY"
wolfmqtt_opts: "--enable-broker --enable-v5 --enable-broker-persist --enable-broker-persist-encrypt"
- name: "Broker with persist + static memory"
cflags: "-DWOLFMQTT_STATIC_MEMORY"
wolfmqtt_opts: "--enable-broker --enable-v5 --enable-broker-persist"

steps:
- name: Install dependencies
Expand Down
35 changes: 33 additions & 2 deletions .github/workflows/macos-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,36 @@ jobs:
- name: Show logs on failure
if: failure() || cancelled()
run: |
cat test-suite.log
cat scripts/*.log
# Copy broker.test tmp dirs ($TMPDIR/tmp.* on macOS, e.g.
# /var/folders/.../T/tmp.XXXXXX) into the workspace so the
# next step can upload them as an artifact. Globbing the
# real /var/folders path directly trips over unreadable
# macOS LaunchServices files.
mkdir -p ci-logs
[ -f test-suite.log ] && cp test-suite.log ci-logs/ || true
cp scripts/*.log ci-logs/ 2>/dev/null || true
for d in "${TMPDIR%/}"/tmp.* /tmp/tmp.*; do
[ -d "$d" ] || continue
base=$(basename "$d")
mkdir -p "ci-logs/$base"
cp "$d"/*.log "ci-logs/$base/" 2>/dev/null || true
done
echo "=== test-suite.log ==="
cat test-suite.log 2>/dev/null || true
echo "=== scripts/*.log ==="
cat scripts/*.log 2>/dev/null || true
echo "=== ci-logs/tmp.*/*.log (broker.test per-test logs) ==="
for f in ci-logs/tmp.*/*.log; do
[ -f "$f" ] || continue
echo "--- $f ---"
cat "$f"
done

- name: Upload broker.test logs on failure
if: failure() || cancelled()
uses: actions/upload-artifact@v4
with:
name: broker-test-logs-macos
path: ci-logs/
if-no-files-found: ignore
retention-days: 7
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,5 @@ src/mqtt_broker

tests/fuzz/broker_fuzz
tests/fuzz/corpus/
tests/test_broker_connect
tests/unit_tests
152 changes: 152 additions & 0 deletions BROKER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# 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](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:

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

With CMake:

```sh
cmake .. -DWOLFMQTT_BROKER=yes
cmake --build .
```

For TLS:

```sh
./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`) |
| `-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_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 |

With dynamic memory the per-subscriber inflight window is derived at runtime, bounded by `BROKER_MIN_INFLIGHT_PER_SUB` (default 8) and `BROKER_MAX_INFLIGHT_PER_SUB`. Define `BROKER_MAX_INFLIGHT_PER_SUB=1` to force strict serial delivery (one inflight QoS 1/2 message per subscriber).

## Persistence

Build with `--enable-broker-persist` to persist sessions, subscriptions, retained messages, and offline queues across restarts. The persistence layer is hook-based: a default POSIX backend stores records as files under the directory given with `-D` (default `/var/lib/wolfmqtt`). Embedded targets can supply their own storage backend through `MqttBroker_SetPersistHooks()`.

| Macro | Default | Description |
|---|---|---|
| `BROKER_MAX_PERSIST_SESSIONS` | 64 | Persistent sessions retained across restarts |
| `BROKER_MAX_OFFLINE_MSGS_PER_SUB` | 32 | Offline queue depth per session |
| `WOLFMQTT_BROKER_PERSIST_SCHEMA_VER` | 3 | 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:

```sh
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:

```sh
./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.
64 changes: 2 additions & 62 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,75 +418,15 @@ is added to `make check`, and all other tests are disabled.

## Broker

wolfMQTT includes a lightweight MQTT broker implementation suitable for embedded and resource-constrained environments. It supports both MQTT v3.1.1 and v5.0 clients, with optional TLS via wolfSSL.

### 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) with v5 Will Delay Interval
* Wildcard subscriptions (`+` and `#`)
* Username/password authentication
* TLS support (requires wolfSSL with `--enable-tls`)
* Clean session handling with subscription persistence
* Keep-alive monitoring with automatic client disconnect
* Unique client ID enforcement (existing session takeover)
* Static memory mode (`WOLFMQTT_STATIC_MEMORY`) for zero-malloc operation

### Building

With autotools:
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, WebSocket transport, and encrypted persistence.

```
./configure --enable-broker
make
```

With CMake:

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

### Running

```
./src/mqtt_broker -p 1883
```

For TLS:

```
./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 all available options.

### Feature Build Options

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

| Feature | Autotools | CMake | Define |
|---|---|---|---|
| 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` |

### Static Memory Mode

When built with `WOLFMQTT_STATIC_MEMORY`, the broker uses fixed-size arrays instead of dynamic allocation. Buffer sizes and limits can be tuned via compile-time macros:

| Macro | Default | Description |
|---|---|---|
| `BROKER_MAX_CLIENTS` | 8 | Maximum concurrent client connections |
| `BROKER_MAX_SUBS` | 32 | Maximum total subscriptions |
| `BROKER_MAX_RETAINED` | 16 | Maximum retained messages |
| `BROKER_RX_BUF_SZ` | 4096 | Per-client receive buffer size |
| `BROKER_TX_BUF_SZ` | 4096 | Per-client transmit buffer size |
| `BROKER_MAX_PAYLOAD_LEN` | 4096 | Maximum retained message payload |
| `BROKER_MAX_WILL_PAYLOAD_LEN` | 256 | Maximum LWT payload size |
See [BROKER.md](BROKER.md) for the full feature list, command-line options, build options, static-memory tuning, and persistence/encryption details.

## WebSocket Support

Expand Down
40 changes: 40 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,46 @@ then
AM_CFLAGS="$AM_CFLAGS -DWOLFMQTT_BROKER_NO_INSECURE"
fi

# Broker persistent storage (sessions, subs, retained, offline queue).
Comment thread
dgarske marked this conversation as resolved.
# Opt-in; off by default. Adds the hook-based persistence layer plus a
# default POSIX backend.
AC_ARG_ENABLE([broker-persist],
[AS_HELP_STRING([--enable-broker-persist],[Enable broker persistent storage via callback hooks (default: disabled)])],
[ ENABLED_BROKER_PERSIST=$enableval ],
[ ENABLED_BROKER_PERSIST=no ]
)
if test "x$ENABLED_BROKER_PERSIST" = "xyes"
then
if test "x$ENABLED_BROKER" != "xyes"
then
AC_MSG_ERROR([--enable-broker-persist requires --enable-broker])
fi
AM_CFLAGS="$AM_CFLAGS -DWOLFMQTT_BROKER_PERSIST"
fi

# Optional encryption-at-rest for persisted records using wolfCrypt AES-GCM.
# Requires --enable-broker-persist; off by default.
AC_ARG_ENABLE([broker-persist-encrypt],
[AS_HELP_STRING([--enable-broker-persist-encrypt],[Encrypt persisted records with AES-GCM (default: disabled, requires --enable-broker-persist)])],
[ ENABLED_BROKER_PERSIST_ENCRYPT=$enableval ],
[ ENABLED_BROKER_PERSIST_ENCRYPT=no ]
)
if test "x$ENABLED_BROKER_PERSIST_ENCRYPT" = "xyes"
then
if test "x$ENABLED_BROKER_PERSIST" != "xyes"
then
AC_MSG_ERROR([--enable-broker-persist-encrypt requires --enable-broker-persist])
fi
AM_CFLAGS="$AM_CFLAGS -DWOLFMQTT_BROKER_PERSIST_ENCRYPT"
fi

# Note: the development-only fixed-pattern derive_key hook for the CLI
# broker (required to use "-E dev" on encrypt builds) is not a configure
# option. Define WOLFMQTT_BROKER_PERSIST_ENCRYPT_DEV_KEY via CFLAGS to
# link it. Never enable it in a production build - the resulting binary
# contains a trivially-recoverable AES-GCM key generator. Real
# deployments install a derive_key hook via MqttBroker_SetPersistHooks.


AM_CONDITIONAL([HAVE_LIBWOLFSSL], [test "x$ENABLED_TLS" = "xyes"])
AM_CONDITIONAL([HAVE_LIBCURL], [test "x$ENABLED_CURL" = "xyes"])
Expand Down
7 changes: 7 additions & 0 deletions examples/pub-sub/mqtt-sub.c
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,13 @@ int sub_client(MQTTCtx *mqttCtx)
break;
}
}
#ifdef WOLFMQTT_NONBLOCK
else if (rc == MQTT_CODE_CONTINUE) {
/* Non-blocking: no data yet, keep polling. mqtt_check_timeout()
* above will convert this to MQTT_CODE_ERROR_TIMEOUT after the
* inactivity window, which drives the keep-alive ping branch. */
}
#endif
else if (rc != MQTT_CODE_SUCCESS) {
/* There was an error */
PRINTF("MQTT Message Wait: %s (%d)",
Expand Down
Loading
Loading