Skip to content

feat(mqtt): add Mqtt5ConnectionPropertyConfig — expose MQTT 5.0 CONNECT properties via MqttClientConfiguration - #659

Merged
ivmarkov merged 10 commits into
esp-rs:masterfrom
daniil4udo:feat/mqtt5-connect-properties
Jul 15, 2026
Merged

feat(mqtt): add Mqtt5ConnectionPropertyConfig — expose MQTT 5.0 CONNECT properties via MqttClientConfiguration#659
ivmarkov merged 10 commits into
esp-rs:masterfrom
daniil4udo:feat/mqtt5-connect-properties

Conversation

@daniil4udo

@daniil4udo daniil4udo commented May 11, 2026

Copy link
Copy Markdown
Contributor

Submission Checklist 📝

  • I have updated existing examples or added new ones (if applicable).
  • I have used cargo fmt command to ensure that all changed code is formatted correctly.
  • I have used cargo clippy command to ensure that all changed code passes latest Clippy nightly lints.
  • My changes were added to the CHANGELOG.md in the proper section.

Pull Request Details 📖

Adds MQTT 5.0 CONNECT-property support to EspMqttClient, the type-safe Rust wrapper around the ESP-IDF MQTT component.

The underlying C function esp_mqtt5_client_set_connect_property() cannot safely be called from user code after EspMqttClient::new() returns: by then mqtt_task is running and holds MQTT_API_LOCK across esp_transport_connect(), so the call deadlocks. The only safe window is between esp_mqtt_client_ini() and esp_mqtt_client_start(), which the wrapper now does internally.

Description

  • Adds MqttProtocolVersion::V5 (gated on CONFIG_MQTT_PROTOCOL_5=y), required to negotiate MQTT 5.0 on the wire. MqttProtocolVersion is marked #[non_exhaustive] so future variants do not break downstream match arms.
  • Adds Mqtt5ConnectionPropertyConfig, exposing the CONNECT-time properties: session_expiry_interval, will_delay_interval, receive_maximum, maximum_packet_size, topic_alias_maximum, request_response_info, request_problem_info, message_expiry_interval, payload_format_indicator. Each field is Option<T>; None leaves the property unset so the broker applies the MQTT 5 protocol default (the ESP-IDF encoder skips zero-valued fields).
  • Adds mqtt5_connection_property: Option on MqttClientConfiguration, applied inside the wrapper in the safe init/start window.
  • Returns Err(ESP_ERR_INVALID_ARG) (with an error! log line for discoverability) if mqtt5_connection_property is set without protocol_version: Some(MqttProtocolVersion::V5), replacing the opaque ESP_FAIL the C setter would otherwise return.
  • Enables CONFIG_MQTT_PROTOCOL_5=y in the CI sdkconfig.defaults so the new #[cfg(esp_idf_mqtt_protocol_5)] paths are compile-checked across the build matrix.

All new types and fields are gated on #[cfg(esp_idf_mqtt_protocol_5)], so downstream crates that don't enable MQTT 5 in their
own sdkconfig see no API surface change. The MqttProtocolVersion enum gets #[non_exhaustive] as a one-time defensive addition;
the existing V3_1 and V3_1_1 variants are unchanged on the wire.

Usage:

MqttClientConfiguration {
    protocol_version: Some(MqttProtocolVersion::V5),
    mqtt5_connection_property: Some(Mqtt5ConnectionPropertyConfig {
        session_expiry_interval: Some(60),
        ..Default::default()
    }),
    ..Default::default()
}

Testing

Tested on ESP32-S3 hardware:

  • Built with CONFIG_MQTT_PROTOCOL_5=y and protocol_version: Some(MqttProtocolVersion::V5), set session_expiry_interval and receive_maximum, confirmed via broker logs that the values appear in the CONNECT packet.
  • Built with the default sdkconfig (MQTT 5 disabled), confirmed the new types are not present in the API surface and existing MQTT 3.1.1 code paths compile and run unchanged.
  • Confirmed the pre-flight check: setting mqtt5_connection_property without MqttProtocolVersion::V5 returns ESP_ERR_INVALID_ARG (with a log line) instead of hanging on MQTT_API_LOCK or returning opaque ESP_FAIL.
  • cargo fmt and cargo clippy clean on the changed code.

@daniil4udo
daniil4udo force-pushed the feat/mqtt5-connect-properties branch from 979b1c2 to fcf8f1d Compare May 12, 2026 04:04
@ivmarkov

Copy link
Copy Markdown
Collaborator

@daniil4udo Looking at the PR details, it seems to contribute MQTT 5.0 support to the EspMqttClient type safe Rust wrapper of the ESP-IDF MQTT component.

However, rather than having a description along those lines, the current description of this PR contains some LLM gibberish which seems to be the last thing it did produce while implementing the MQTT 5.0 support.

Therefore, could you please restore the original PR template (the one available when opening a new PR) and try to fill it in (or let your LLM fill it in). This template is in there for a good reason. Amongst other things, it has a set of check-points you need to do for your PR to be ready for merge.

Adds Mqtt5ConnectionPropertyConfig config struct and a mqtt5_connection_property
field on MqttClientConfiguration. Internally, EspMqttClient::new_raw calls
esp_mqtt5_client_set_connect_property between esp_mqtt_client_init and
esp_mqtt_client_start, eliminating the deadlock where calling the setter
after start blocks the caller on MQTT_API_LOCK held by mqtt_task across
esp_transport_connect() (multi-second TLS handshake).

The struct and field names mirror the C binding
(esp_mqtt5_connection_property_config_t) so reviewers can grep both sides
and see the parity at a glance, matching the existing convention of
nested config sub-structs in this file (e.g. LwtConfiguration).

Backward-compatible: EspMqttClient::new/new_cb/new_nonstatic_cb signatures
are unchanged. Users who do not set mqtt5_connection_property are unaffected.

Also adds MqttProtocolVersion::V5 enum variant (gated on esp_idf_mqtt_protocol_5)
required to negotiate MQTT 5.0 on the wire — the C setter returns ESP_FAIL
if the client was not initialized with protocol_ver = MQTT_PROTOCOL_V_5.
…r on Mqtt5ConnectionPropertyConfig

Completes scalar-field coverage of esp_mqtt5_connection_property_config_t.
The two new fields apply to the Last Will message (LWT) and are only
meaningful when MqttClientConfiguration::lwt is also set.

Remaining unexposed fields are pointer-typed (content_type, response_topic,
correlation_data, user_property, will_user_property) and require additional
lifetime/borrowing design — deferred to a follow-up.
- Add cross-reference doc to EspMqttClient::new pointing to
  mqtt5_connection_property and the V5 protocol version requirement
- Correct maximum_packet_size: None docs (clamps to RX buffer, not unlimited)
- Document request_response_info / request_problem_info Some(false) C-boundary
  limitation (ESP-IDF encoder skips bool properties with value false; both None
  and Some(false) are indistinguishable at the wire level)
- Add 'C binding field: request_resp_info' note on request_response_info
- Replace unsafe { core::mem::zeroed() } with Default::default() (bindgen
  generates a safe Default impl that zero-initialises the struct identically)
… in CI

Adding V5 without #[non_exhaustive] is a breaking change for downstream callers
with exhaustive match arms on MqttProtocolVersion. The attribute lets future
variants be added without a semver bump and requires callers to use a wildcard
arm, which is correct for a protocol-version enum.

Also adds CONFIG_MQTT_PROTOCOL_5=y to .github/configs/sdkconfig.defaults so
the new cfg(esp_idf_mqtt_protocol_5)-gated code is compiled and clippy-checked
in all four CI workflow jobs (ci.yml, ci-esp-idf-next.yml, publish.yml,
publish-dry-run.yml). Previously the new struct, field, and new_raw setter block
were silently skipped in every build.
- Add #[non_exhaustive] to Mqtt5ConnectionPropertyConfig (parallel to
  MqttProtocolVersion) so future scalar fields can be added without
  a semver break for callers using ..Default::default()
- Add pre-flight Rust check: if mqtt5_connection_property is Some but
  protocol_version != Some(MqttProtocolVersion::V5), return
  Err(ESP_ERR_INVALID_ARG) before the FFI call, replacing the opaque
  ESP_FAIL(259) that the C setter would have returned
- Document Some(0) = None C-boundary limitation for session_expiry_interval,
  will_delay_interval, receive_maximum, topic_alias_maximum (ESP-IDF encoder
  skips zero-valued numeric properties; explicit zero is indistinguishable
  from the protocol default)
- Revert CONFIG_MQTT_PROTOCOL_5=y from .github/configs/sdkconfig.defaults:
  enabling MQTT5 is downstream's responsibility; the library CI should not
  impose it on all builds
#[non_exhaustive] prevents struct literal construction from outside the
defining crate (E0639), which defeats the purpose of a user-facing config
struct. It is correct on MqttProtocolVersion (enum variants) but wrong here.
Forward-compat for the struct is achieved by the idiomatic ..Default::default()
pattern, which callers already use.
@daniil4udo
daniil4udo force-pushed the feat/mqtt5-connect-properties branch from 0ece158 to a566d28 Compare May 23, 2026 08:22
Maintainer feedback: the prior comments and changelog wording read as
LLM-generated noise. Tighten to the file's existing terse style:

- Drop multi-line doc paragraphs on MqttProtocolVersion::V5,
  Mqtt5ConnectionPropertyConfig, its fields, and the new config field
- Drop the per-field commentary; field names are self-describing
- Replace nine if-let assignment statements with a single struct literal
  using Option::unwrap_or and ..Default::default()
- Drop the long SAFETY block; one short comment explains the init/start
  ordering constraint
- Collapse the two MQTT 5.0 CHANGELOG entries to single lines
- Replace `unwrap_or(0/false)` ladder with conditional assignment from
  `Default::default()`. `None` no longer silently becomes `0`/`false` at
  the C boundary; unset fields stay at the C struct's zero default,
  which the ESP-IDF encoder skips so the broker applies the MQTT 5
  protocol default. Avoids surprises on fields whose protocol default
  differs from zero (e.g. Request Problem Information defaults to 1)
- Log an explanatory `error!` line before returning `ESP_ERR_INVALID_ARG`
  when `mqtt5_connection_property` is set without `protocol_version =
  Some(V5)`, so the misconfiguration is discoverable without grepping
  ESP-IDF error codes
- Restore minimal per-field doc comments giving units and the MQTT 5
  spec section, plus a struct-level note that fields may be added in
  minor releases (callers should use `..Default::default()`)
- Enable `CONFIG_MQTT_PROTOCOL_5=y` in CI sdkconfig.defaults so the new
  `#[cfg(esp_idf_mqtt_protocol_5)]` paths are compile-checked
- Add a CONNECT-properties usage snippet to the CHANGELOG entry
@daniil4udo

Copy link
Copy Markdown
Contributor Author

@ivmarkov thank you for the feedback, fix it.
Beside the standard checks also ran it against real hardware in the personal project to check.

@ivmarkov

Copy link
Copy Markdown
Collaborator

@copilot resolve the merge conflicts in this pull request

@ivmarkov

Copy link
Copy Markdown
Collaborator

@ivmarkov thank you for the feedback, fix it. Beside the standard checks also ran it against real hardware in the personal project to check.

Right, I completely managed to forget about this PR :-(

Anyways. I think it is genuinely useful. what it is missing a bit is the MQTT5's support for user-define connection properties, but I guess even without it, it is good for merging.

Thanks a lot!

@ivmarkov
ivmarkov merged commit 27b061a into esp-rs:master Jul 15, 2026
21 of 26 checks passed
@ivmarkov ivmarkov mentioned this pull request Jul 25, 2026
4 tasks
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