Skip to content

feat(052): runtime-neutral MQTT connector - #253

Open
lxsaah wants to merge 20 commits into
mainfrom
feat/platform-agnostic-mqtt-connector
Open

feat(052): runtime-neutral MQTT connector#253
lxsaah wants to merge 20 commits into
mainfrom
feat/platform-agnostic-mqtt-connector

Conversation

@lxsaah

@lxsaah lxsaah commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Wave C of design 052. MQTT is the exception among the connectors and stays that way deliberately — see below — but the embedded backend now takes a caller-supplied transport, which is what makes a FreeRTOS port possible without touching this crate.

Why MQTT keeps two protocol clients

Unifying on mountain-mqtt was considered and rejected: it does not support QoS 2 (Qos2NotSupported; the codec has pubrec/pubrel/pubcomp, the state machine does not use them) and its tokio module has no TLS — while the Tokio connector exposes both. rumqttc cannot ride the neutral layer either: its Transport is a closed enum, so no stream can be injected.

So one MqttConnector<B> over two backends, which is the seam that keeps unification a later, small change — if QoS 2 and a TLS connection land in the fork, deleting Native is a deletion, not a rewrite.

Backend Client QoS TLS
Native rumqttc (std) 0–2 rustls
Embedded<D> mountain-mqtt (no_std) 0–1 embedded-tls

Breaking

The Tokio path is unchangedMqttConnector::new(&url) still. Embassy callers supply the transport:

// mqtt:// — the adapter owns the socket
MqttConnector::new(&url).transport(EmbassyNet::tcp(*stack, MQTT_RX.init(..), MQTT_TX.init(..)))

// mqtts:// — TLS keeps the stack: it resolves DNS itself and owns buffers across sessions
MqttConnector::new(&url).tls(stack, TlsOptions::new(..))

The two new statics are not new RAM: the previous commit already allocated them inside the connector as private StaticCells. They are now visible and sizeable by the caller.

On FreeRTOS the same line is .transport(LwipNet::tcp()), with no edit here.

Worth a look in review

run_with_subscriptions is gone. It binds embassy_net::Stack and cannot take a transport, so injecting one meant giving it up — and with it, reconnect-and-resubscribe. That behaviour is now explicit in transport::run_sessions, one loop for both plain and TLS, extracted from the TLS path that was already running it in production.

The transport seam is mountain-mqtt's own Connection, not core's ByteStream. The client needs receive_if_ready — a non-blocking peek — which ByteStream does not express and a TLS session cannot provide (this crate's own embassy_tls documents why). SocketTransport<D> bridges from core's StreamDialer for adapters whose stream also offers Read + Write + ReadReady, which is the path a new runtime takes.

EmbassyTcpStream gained embedded_io_async::{Read, Write, ReadReady} by delegation — design 052 §6 specified this and wave A had not delivered it.

tests/embassy_broker.rs drives the whole connector against a fake broker over two crossover-wired embassy-net stacks, asserting CONNECT and SUBSCRIBE reach the wire. The subscribe is the property run_with_subscriptions used to own; without it inbound routing dies silently on the first reconnect. CI has no mosquitto, hence the fake.

Not addressed

mqtts:// stays Embassy-only — set_unix_time has no neutral home (design §9). SNTP is likewise unported, since it serves only that path.

Verification

make check clean: 2 023 tests, 0 failures, all stages.

lxsaah and others added 20 commits September 6, 2026 11:07
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`Self::tls` is `embassy-tls`-gated, but `make doc` builds this crate with
`embassy-runtime` only, so the link failed the docs gate in CI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Implemented a new `manager` module to handle per-session broker state, event handling, and message pumping.
- Updated `MqttConnector` to support `Delay` and `StreamDialer` traits for embedded systems.
- Refactored `MqttSink` and `MqttSource` to use action and event channels directly, removing unnecessary wrappers.
- Introduced `ClientDelay` to bridge core's `Delay` with the MQTT client's requirements.
- Enhanced the `run_sessions` function to manage MQTT sessions more effectively, ensuring reconnections and resubscriptions.
- Updated tests to ensure proper session reconnection and resubscription behavior.
- Adjusted Tokio adapter to implement `Delay` for seamless integration with the connector.
…ial support

- Consolidated the MqttConnector structure to allow seamless switching between Native and Embedded backends without separate builders.
- Introduced credential handling in the Native backend, allowing credentials to be set via the MqttConnector interface.
- Updated the Embassy client to streamline the transport setup and improve TLS handling.
- Enhanced tests to verify credential transmission for both backends, ensuring consistent behavior across different configurations.
- Removed deprecated builder patterns and unnecessary complexity in the connector implementation.
- Implement SNTP codec for encoding and parsing SNTP packets, including request and reply handling.
- Introduce TLS transport for the Embassy MQTT client, supporting secure connections with certificate verification.
- Refactor the library structure to separate native and embedded implementations, enhancing modularity.
- Update the MQTT connector to support both plain and secure MQTT connections, with appropriate error handling and logging.
- Introduced a new feature `_test-tls-broker` for testing MQTT over TLS with a pinned self-signed root CA.
- Updated the Makefile to include new test commands for the MQTT connector with TLS.
- Modified `Cargo.toml` to add dependencies for embedded TLS and SNTP.
- Refactored the `EmbeddedTls` struct to use a caller-supplied transport instead of owning the network stack.
- Implemented a new `WallClock` for certificate validity checks in the absence of an RTC.
- Created a new test `tls_broker.rs` to validate the MQTT handshake over TLS.
- Updated the example to demonstrate the use of MQTT over TLS with SNTP for time synchronization.
…ges and enhanced features for std and no_std runtimes
…ated docs

`make doc` runs `cargo doc` per feature leg with `-D warnings`, so an
intra-doc link that resolves on one leg and not another fails the build.
Five such links had crept in:

- `[Embedded]` in the ungated backend table, which only exists with the
  embedded backend
- `[build]` and `[WallClock]`, both private
- `[SntpClock]`, deleted when the TLS clock became the runtime's
- `[sntp]`, now `embassy-tls`-gated while `tls.rs` is `embedded-tls`

The `doc` target only covered `std` and `embassy-runtime`, which is why
only the first two reached CI; add the `embedded`, `embedded-tls` and
`embassy-tls` legs so the rest cannot recur silently.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant