Skip to content

feat(mqtt): add MQTT v5 client5 module with per-message properties - #678

Open
danielmeza wants to merge 14 commits into
esp-rs:masterfrom
danielmeza:feat/mqtt5-client5
Open

feat(mqtt): add MQTT v5 client5 module with per-message properties#678
danielmeza wants to merge 14 commits into
esp-rs:masterfrom
danielmeza:feat/mqtt5-client5

Conversation

@danielmeza

@danielmeza danielmeza commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Thank you for your contribution!

Replaces the closed #590 (add-mqtt-v5-protocol), rebased onto current master (0.52.1, which now includes Mqtt5ConnectionPropertyConfig via #659) and extended with atomic per-message publish/subscribe/unsubscribe property support.

Submission Checklist 📝

  • I have updated existing examples or added new ones (if applicable). — N/A: no example is added, since the feature cannot be compiled on CI until Mqtt protocol v5 definition (rebased onto 0.29 + per-message properties) embedded-svc#87 is released (see Dependencies below)
  • I have used cargo fmt command to ensure that all changed code is formatted correctly. — cargo fmt --check passes
  • I have used cargo clippy command to ensure that all changed code passes latest Clippy nightly lints. — clean
  • My changes were added to the CHANGELOG.md in the proper section. — ### Added

Pull Request Details 📖

Description

Adds src/mqtt/client5.rs, implementing the embedded-svc MQTT v5 client traits (EspMqtt5Client, EspMqtt5Connection) from esp-rs/embedded-svc#87.

The module is behind a new opt-in mqtt_protocol_v5 cargo feature (which implies std) and is additionally gated on esp_idf_mqtt_protocol_5, i.e. CONFIG_MQTT_PROTOCOL_5=y in sdkconfig. It is deliberately not part of std: embedded-svc 0.29.0 as published does not carry the mqtt_protocol_v5 feature yet, so enabling it by default would break every build that resolves embedded-svc from crates.io.

Beyond the original #590, the last commits use the new atomic per-message IDF APIs (esp_mqtt_client_publish5 / subscribe5 / unsubscribe5, espressif/esp-mqtt#343) The property config for a message is passed with that message and applied under the client's API lock, in place of anything staged via esp_mqtt5_client_set_*_property — so there is no window in which another task can overwrite the properties between a set call and the publish, and no reliance on client-scoped state at all. That upstream PR also moved the property validation (share_name, topic_alias against the server maximum, retain_handle range) so it runs on the per-message path too, which backstops the cases this crate does not check itself.

Why per-message properties, per the MQTT 5.0 specification

References are to MQTT Version 5.0, OASIS Standard, 07 March 2019; each link is a section anchor in that published HTML.

MQTT 5.0 carries these properties in the Variable Header of the individual packet:

Only §3.1.2.11 CONNECT Properties are connection-scoped — those remain in MqttClientConfiguration::mqtt5_connection_property from #659.

Concretely: §3.3.2.3.4 Topic Alias says "It is a Protocol Error to include the Topic Alias value more than once"; §3.3.2.3.3 Message Expiry Interval says "the Four Byte value is the lifetime of the Application Message in seconds"; and §3.3.2.3.7 User Property says "The Server MUST send all User Properties unaltered in a PUBLISH packet when forwarding the Application Message to a Client [MQTT-3.3.2-17]". All three describe one Application Message, so the API takes them per call.

Dependencies, and the current CI state

  1. Mqtt protocol v5 definition (rebased onto 0.29 + per-message properties) embedded-svc#87 — the client5 trait definitions and the mqtt_protocol_v5 feature.
  2. feat(mqtt5): add per-message publish/subscribe/unsubscribe property APIs (IDFGH-18158) espressif/esp-mqtt#343 — the C publish5 / enqueue5 / subscribe5 / unsubscribe5 entry points.

CI is red until (1) lands on embedded-svc master, and will go green with no further change here. Cargo validates dep/feature references in a manifest even when the feature is not activated, so simply naming embedded-svc/mqtt_protocol_v5 fails resolution against the published embedded-svc 0.29.0 — making the feature opt-in is necessary but not sufficient. Following the convention already used in this repo for esp-idf-sys and esp-idf-hal, embedded-svc is added to [patch.crates-io] pointing at esp-rs/embedded-svc master; that entry should be dropped once an embedded-svc release carries the feature.

Verified locally that the patch is the whole of the remaining gap: with the same patch entry pointed at the #87 branch, resolution succeeds.

Testing

Introduces a new `client5` module for MQTT 5.0 protocol support, including an `ErrorReasonCode` enum for MQTT5 error reason codes, conversion implementations, and utility methods. The new module is conditionally compiled with the `esp_idf_mqtt_protocol_5` feature.
Simplifies the conversion from mqtt5_error_reason_code to ErrorReasonCode by switching from TryFrom to From and returning UnspecifiedError for unknown codes. Updates EventProperty construction to include payload_format_indicator and removes unused ErrorType import.
This commit introduces support for MQTT v5 property configuration in subscribe, unsubscribe, and publish operations for both sync and async clients. It adds new methods to handle property configs, user properties, and message metadata, and refactors the client5 module to focus on user property handling. Deprecated or unused error reason code logic is removed, and trait implementations are updated to support the new property features.
Reworks the MQTT v5 client implementation to accept property configuration options as Option types for publish, subscribe, unsubscribe, and disconnect operations. Removes legacy publish_with_config and subscribe_with_config methods from the v3 client, and updates property handling to use new wrapper structs. Improves FFI safety and code clarity by encapsulating property conversions and updating event field extraction.
Replaces direct struct construction and From implementations for MQTT5 property configs with TryFrom<Option<T>> implementations, improving memory safety and lifetime management. Introduces internal ownership of C string buffers and user property lists to prevent premature drops, and ensures proper cleanup via Drop implementations. Adds logging for publish calls and updates method usage to leverage the new property config handling.
The RawCstrs instance in the TryFrom implementation for EspUnsubscribePropertyConfig no longer needs to be mutable, as it is not mutated after creation.
…APIs

Replace the set-then-call pattern (esp_mqtt5_client_set_*_property + esp_mqtt_client_*)
with the new single-call per-message APIs that bake the property config directly
into the packet under the API lock, eliminating the cross-task race.
The `std` feature unconditionally enabled `embedded-svc/mqtt_protocol_v5`,
which does not exist in the released embedded-svc 0.29.0, so every default
build failed to resolve. Move it to a dedicated opt-in `mqtt_protocol_v5`
feature (which implies `std`) and retarget the v5 cfg gates in `mqtt::client`
and `mqtt::client5` at it, so default builds resolve against the released
embedded-svc.
Cargo validates `dep/feature` references in the manifest even when the
feature is not activated, so naming `embedded-svc/mqtt_protocol_v5` fails
resolution against the published embedded-svc 0.29.0 regardless of gating.
Add embedded-svc to the existing sibling-crate patch table, the same way
esp-idf-sys and esp-idf-hal are already handled, so the crate resolves once
esp-rs/embedded-svc#87 lands on master.
SubscribePropertyConfig::share_name and UnsubscribePropertyConfig::share_name
are `Option<&str>`, and a Rust &str is not NUL-terminated. Three sites passed
`s.as_ptr()` straight into the C property structs, where mqtt5_msg_subscribe()
and mqtt5_msg_unsubscribe() call strlen() on it and format it with %s, reading
past the end of the string. esp_mqtt5_client_set_subscribe_property also stores
the pointer rather than copying it, so the bytes have to outlive the call too.

Route all three through the RawCstrs arena, as the subscribe property config
already did, and keep the arena alive across the setter and the following
subscribe/unsubscribe.
@ivmarkov

Copy link
Copy Markdown
Collaborator

@danielmeza Sorry I was AFK the last couple of weeks. Will try to review in the next couple of days.

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.

2 participants