mqtt-broker
An MQTT 3.1.1 / MQTT 5.0 broker, implemented in Rust and exposed to
Kotlin, Swift, Python, and other languages via
UniFFI. Reuses the wire-protocol
codec from mqtt-client
(mqtt_client::protocol) instead of duplicating it.
Installation
Published on crates.io as stem-mqtt-broker (not mqtt-broker — kept
consistent with stem-mqtt-client's naming, see that crate's README for
why). The import path is unaffected:
cargo add stem-mqtt-broker
use mqtt_broker::{MqttBroker, MqttBrokerConfig};
Both client and broker bindings ship for Kotlin, Android, Swift, and Python. Every other supported language ships both too — one UniFFI-generated package each. See the root README's Installation section for every supported language.
Layout
src/broker.rs—BrokerState: composes the three subsystems below (only the operations that genuinely span more than one of them live here — will delivery on disconnect, retained-message replay on subscribe) — andMqttBroker, the thin UniFFI-exported handle: bind sockets, spawn per-connection tasks,start()/stop()lifecycle.src/registry.rs—SessionRegistry: owns theclient_id -> Sessionmap, per-client send/queue/QoS-2 bookkeeping, and fan-out (matching a published message to subscribers).src/retain.rs—RetainStore: the retained-message store, and only that.src/events.rs—EventHub: connect/disconnect/publish notifications to the foreign-side listener.src/connection.rs— per-connection actor: reads and decodes packets off the socket, drives the CONNECT handshake, and dispatches PUBLISH/SUBSCRIBE/UNSUBSCRIBE/ack packets for one client. Calls straight into whichever subsystem above owns a given operation.src/session.rs— per-client session state: subscriptions, in-flight QoS 1/2 packet ids, and (for non-clean sessions) the queued message backlog.src/topic.rs— topic name/filter matching (+/#wildcards), independent of everything else.src/ws.rs— MQTT-over-WebSocket transport adapter, so the sameconnection.rslogic drives both raw TCP and WebSocket connections.src/tls.rs— builds atokio_rustls::TlsAcceptorfromBrokerTlsConfig(server cert/key, optional mTLS client-CA) — accepted TLS streams feed into the same genericconnection.rshandler too.src/config.rs—MqttBrokerConfig, the pluggableMqttAuthProvidertrait, and theMqttBrokerEventListenerobserver trait, all exported across the UniFFI boundary.src/bin/mqtt-broker.rs— standalone CLI binary.
Running the CLI binary
cargo run -p stem-mqtt-broker --bin mqtt-broker -- --bind 0.0.0.0 --port 1883
Usage: mqtt-broker [OPTIONS]
Options:
--bind <BIND> Address to bind the listening socket to [default: 0.0.0.0]
--port <PORT> TCP port to listen on [default: 1883]
--ws-port <WS_PORT> Also accept MQTT-over-WebSocket connections on this port (needed for browser clients — see demo/web/)
--allow-anonymous Allow clients to connect without a username/password
--max-clients <MAX_CLIENTS> Maximum simultaneously connected clients (0 = unlimited)
--log-level <LOG_LEVEL> Log verbosity: error, warn, info, debug, trace [default: info]
Browser clients (and anything else that can't open a raw TCP socket) need
--ws-port:
cargo run -p stem-mqtt-broker --bin mqtt-broker -- --ws-port 8083
See demo/ for a full browser-based pub/sub demo webpage
that talks to the broker this way.
Embedding as a library
use mqtt_broker::{MqttBroker, MqttBrokerConfig};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let config = MqttBrokerConfig::new("127.0.0.1", 1883);
let broker = MqttBroker::new(config);
broker.start().await?;
tokio::signal::ctrl_c().await?;
broker.stop().await?;
Ok(())
}
Plug in authentication and observability by implementing the UniFFI-exported traits and registering them on the broker/config:
# use mqtt_broker::MqttAuthProvider;
struct FixedCreds;
impl MqttAuthProvider for FixedCreds {
fn authenticate(&self, _client_id: String, username: Option<String>, password: Option<Vec<u8>>) -> bool {
username.as_deref() == Some("demo") && password.as_deref() == Some(b"demo")
}
}
QoS in the bindings
The broker's bindings are self-contained: QoS in MqttBrokerConfig::max_qos and in the event listener is
the broker's own enum (mqtt_broker::QoS), with lossless From conversions to the client crate's
mqtt_client::QoS. In a language that imports both packages you therefore have two QoS types — use the
broker's for broker configuration/events and the client's for client calls.
Limits and robustness
All of these are fields on MqttBrokerConfig (and CLI flags on mqtt-broker):
| Setting | Default | Effect |
|---|---|---|
max_packet_size / --max-packet-size |
1 MiB | A client announcing a bigger packet is disconnected as soon as the header is read — before the body is buffered. |
max_outbound_queue / --max-outbound-queue |
4096 | Packets queued per client socket; a slow consumer past this has further deliveries dropped (QoS 1/2 are retried) instead of growing memory without bound. |
max_clients / --max-clients |
unlimited | Checked atomically; a client taking over its own session doesn't count against it. |
redelivery_interval_secs |
5 | Resend (DUP=1) interval for unacked QoS 1/2 deliveries. |
session_expiry_secs |
0 (never) | Discard persistent (clean_start = false) sessions — with their subscriptions and queued messages — that stay offline this long, so clients that never return can't leak memory. |
Behaviour worth knowing: start() is all-or-nothing and serialised; stop() closes live
connections (without publishing Last Wills) and keeps persistent sessions; dropping a broker stops
it; a client id taken over by a newer connection never disturbs its replacement; an empty client id
with clean_start = false is refused; SIGTERM shuts the CLI down gracefully. Foreign callbacks that panic or throw
are contained and never kill a connection, and an auth provider that panics means "denied".
Subscriptions and routing
- Topic index: subscriptions live in a topic-level trie, so a PUBLISH finds its subscribers by walking the topic's own levels instead of scanning every session's filters.
- Shared subscriptions:
$share/<group>/<filter>delivers each message to one member of the group (round-robin, preferring connected members); ordinary subscribers still receive everything. Retained messages are not replayed to shared subscriptions (MQTT 5 §4.8.2).
Authentication
MqttAuthProvider— username/password, evaluated once per CONNECT (off the async workers; a panic = denied).MqttEnhancedAuthProvider— MQTT 5 enhanced authentication: a multi-round challenge/response (SCRAM, Kerberos, OAuth, …) during CONNECT. The provider'sstep(client_id, method, data, round)returns anEnhancedAuthStep { outcome: Continue | Success | Failure, data }; the broker relays challenges as AUTH packets and returnsdataon success in the CONNACK. A CONNECT naming a method with no provider registered is refused (0x8C). The matching client side isMqttAuthHandler.
Pull-style events
For runtimes that can't receive callbacks (Dart, Haskell): enable_event_queue(capacity) then
next_event(timeout_ms) returns a BrokerEvent (ClientConnected, ClientDisconnected,
MessagePublished) or None on timeout. Oldest events are dropped when the queue is full.
Foreign-language bindings
Same pattern as mqtt-client — Kotlin, Swift, Python, Go, C#, Java, Dart, Node.js and Haskell
(see the root README for which generators
support which features):
nu ../../scripts/generate_bindings.nu kotlin
Examples
cargo run -p stem-mqtt-broker --example simple_broker # bare-minimum embedded broker
cargo run -p stem-mqtt-broker --example auth_broker # + MqttAuthProvider and MqttBrokerEventListener
Testing
cargo test -p stem-mqtt-broker
tests/integration.rs drives a real MqttClient
against a real MqttBroker over a loopback TCP socket bound to an
OS-assigned ephemeral port (so tests can run in parallel): QoS 0/1/2
publish/subscribe, retained messages, wildcard subscriptions, unsubscribe,
auth accept/reject, and Last-Will delivery on an ungraceful disconnect.
Metadata
Release files for stem-mqtt-broker 0.4.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| stem_mqtt_broker-0.4.1-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details for stem_mqtt_broker-0.4.1-py3-none-win_amd64.whl |
| stem_mqtt_broker-0.4.1-py3-none-manylinux_2_34_x86_64.whl | Python 3 | none | Linux glibc 2.34+ x86-64 | Details for stem_mqtt_broker-0.4.1-py3-none-manylinux_2_34_x86_64.whl |
| stem_mqtt_broker-0.4.1-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details for stem_mqtt_broker-0.4.1-py3-none-macosx_11_0_arm64.whl |
Total release size: 5.2 MB
Release files / stem_mqtt_broker-0.4.1-py3-none-win_amd64.whl
| Download URL | stem_mqtt_broker-0.4.1-py3-none-win_amd64.whl |
|---|---|
| Size | 1.7 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
2d2effd8ef8cbb8ecf43ce617a72e17f79044f997777b257a1defd1d9fb7e5a8
|
|
BLAKE2b-256 checksum How to use checksums |
80d9178315c030ff57d216394c17d7e29b9f06243575ebd7d0c9d208aee0de57
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.
Transparency logRelease files / stem_mqtt_broker-0.4.1-py3-none-manylinux_2_34_x86_64.whl
| Download URL | stem_mqtt_broker-0.4.1-py3-none-manylinux_2_34_x86_64.whl |
|---|---|
| Size | 1.9 MB |
| Tags | Linux glibc 2.34+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
22cc22055806dcfc87673ccee546ac0ffc5bf1ed2db59affb351ed5b1d935c5a
|
|
BLAKE2b-256 checksum How to use checksums |
e637e377582259ae947892b1cbc7cbb5d46f8cd95afae4c536355bc251c80479
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.
Transparency logRelease files / stem_mqtt_broker-0.4.1-py3-none-macosx_11_0_arm64.whl
| Download URL | stem_mqtt_broker-0.4.1-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.7 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
d4e2225872aed6b08e983c1846343fbaa452d961f775440c9fe672c7ceb1e3b4
|
|
BLAKE2b-256 checksum How to use checksums |
54bd8e08b2543f2162a1080a0169c89f237f3505d6c4e6ed581dc7fb8f47d9a4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.
Transparency logSecurity for stem-mqtt-broker 0.4.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 3 files in this release have been uploaded with provenance metadata.
Attestation for stem_mqtt_broker-0.4.1-py3-none-win_amd64.whl
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.