wolfMQTT 2.2.0 Released: Automatic Keep-Alive, Session Replay, and Stricter MQTT Conformance

wolfMQTT is wolfSSL’s lightweight MQTT client and broker library, purpose-built for embedded and IoT devices. It is small, portable, and pairs directly with the wolfSSL embedded TLS library for secure MQTT over TLS 1.3. The wolfMQTT 2.2.0 release takes keep-alive scheduling off the application’s plate, replays unacknowledged QoS 1 and QoS 2 messages after a persistent-session reconnect, tightens Packet Identifier and handshake handling to the MQTT specification, and fixes a long list of broker, WebSocket, and build-configuration issues.

What’s New in 2.2.0

  • Automatic client keep-alive: the core client sends PINGREQ on its own schedule.
  • Client session replay: unacknowledged QoS 1 and QoS 2 publishes are re-sent with DUP set after a CleanSession=0 reconnect.
  • Packet Identifier tracking in every build, plus CONNECT ordering and post-DISCONNECT enforcement.
  • Binary passwords via a new password_len field on MqttConnect.
  • Broker fixes for refused CONNACKs, partial writes, and best-effort retained messages.
  • Build fixes for WOLFMQTT_NO_STDIO, WOLFMQTT_CUSTOM_MALLOC, and WOLFMQTT_CUSTOM_STRING ports.

Automatic Client Keep-Alive

Previously every application had to run its own timer and call MqttClient_Ping before the keep-alive interval expired. The client now sends a PINGREQ from MqttClient_WaitMessage once the outbound link has been idle for about three quarters of the negotiated interval, whenever a non-zero keep-alive is set in CONNECT, and it honors an MQTT v5 Server Keep Alive override. In a blocking build an unanswered ping surfaces as MQTT_CODE_ERROR_NETWORK rather than a timeout, so a dead link is distinguishable from an idle one. The time source is the WOLFMQTT_GET_TIME_S() macro (default time(NULL), overridable in user_settings.h); define WOLFMQTT_NO_TIME on clock-less targets to compile the scheduler out. Applications that already send their own pings can disable it at runtime with MQTT_CLIENT_FLAG_NO_AUTO_KEEPALIVE.

Client Session Replay for QoS 1 and QoS 2

Unacknowledged outbound QoS 1 and QoS 2 messages are now kept as session state and re-sent after a CleanSession=0 reconnect that the server answers with Session Present = 1: a PUBLISH goes out again with its original Packet Identifier and DUP set, and a QoS 2 exchange past PUBREC re-sends the PUBREL. Topic and payload are copied into a bounded pool of MQTT_MAX_REPLAY_MSGS entries (4 by default); a message that does not fit, is streamed through a payload callback, or carries v5 properties is still sent but not retained. Replay copies are zeroized before being freed, and session state is bound to the ClientId that created it so a reused client object never replays another session’s messages. Define WOLFMQTT_NO_SESSION_REPLAY to compile the store out.

Packet Identifier and Handshake Enforcement

The client keeps a record of outbound Packet Identifiers still awaiting PUBACK, PUBCOMP, SUBACK, or UNSUBACK in every build, not only under WOLFMQTT_MULTITHREAD, and refuses a new packet that reuses one with MQTT_CODE_ERROR_PACKET_ID. The window is MQTT_MAX_SEND_INFLIGHT (16 by default). The MQTT handshake is tracked separately from the transport: publish, subscribe, unsubscribe, ping, and disconnect return MQTT_CODE_ERROR_STAT before MqttClient_Connect or after MqttClient_Disconnect, and a second CONNECT on the same connection is refused. A v5 CONNECT advertises Receive Maximum set to MQTT_MAX_RECV_QOS2 so a retransmitted PUBLISH cannot be delivered twice, acknowledgements with Packet Identifier 0 close the connection, and the v3.1.1 CONNACK decoder requires a Remaining Length of exactly 2.

Broker, Hardening, and Build Fixes

The built-in broker now fully writes a refused CONNACK before closing, pauses keep-alive monitoring while an accepted CONNACK is still partly written, retransmits a QoS greater than 0 PUBLISH that failed mid-write with DUP set, drops a partly written QoS 0 PUBLISH rather than delivering it twice, and tracks per-subscriber in-flight identifiers in static-memory fan-out. Retaining a message is now best-effort, matching Mosquitto: if a RETAIN=1 PUBLISH cannot be stored, it is still delivered and acknowledged, and only the retained copy is skipped. WebSocket receive callbacks close the connection on a non-binary frame.
wolfSSL’s internal Fenrir tool and Coverity drove further hardening across the client, broker, MQTT v5, and MQTT-SN paths, including credential and payload zeroization and an undersized Length check in SN_Packet_Read. Several porting configurations that previously failed to compile now build cleanly: WOLFMQTT_NO_STDIO (including WOLFMQTT_CUSTOM_PRINTF ports such as MPLAB Harmony), WOLFMQTT_CUSTOM_MALLOC without the allocator macros, WOLFMQTT_CUSTOM_STRING, and a command-line WOLFMQTT_SESSION_ID_TRACK define under -Werror.

API and Behavior Changes to Note When Upgrading

  • Rebuild, do not relink. MqttMsgStat and MqttConnect grew, so sizeof changes for every packet object. MqttClient grows by about 1.3 KB under WOLFMQTT_STATIC_MEMORY at the defaults.
  • Reconnect requires MqttClient_NetDisconnect first; a second MqttClient_Connect on the same connection returns MQTT_CODE_ERROR_STAT.
  • Binary passwords. Set password_len on MqttConnect to send bytes verbatim; leaving it 0 keeps the NUL-terminated behavior.
  • Inbound Topic Alias is rejected. The client advertises Topic Alias Maximum 0, and a nonzero MQTT_PROP_TOPIC_ALIAS_MAX now returns MQTT_CODE_ERROR_PROPERTY from MqttClient_Connect.
  • Write-only publish sees QoS 2 rejections in multithread builds via MQTT_CODE_ERROR_PUBLISH_REJECTED; PUBACK and PUBCOMP rejections are still not detected on that path.
  • Empty ClientId with clean_session 0 is refused for v3.1.1; v5 still allows it.
  • MqttClient_WaitMessage can take up to cmd_timeout_ms longer when it starts a keep-alive ping, so poll with a timeout_ms shorter than the keep-alive interval.

Getting Started

wolfMQTT 2.2.0 is available now on GitHub at https://github.com/wolfSSL/wolfMQTT. Clone or download the release, build with autotools or CMake, and enable TLS by building against wolfSSL. The full list of changes is in the ChangeLog; while you’re there, show us some love and give the wolfMQTT project a Star!
wolfSSL products are dual licensed under GPLv3 and a commercial license, and are backed by the wolfSSL support team. For commercial licensing, FIPS 140-3 validated cryptography, or help integrating wolfMQTT into your embedded or IoT product, contact us at facts@wolfssl.com or licensing@wolfssl.com.

If you have questions about any of the above, please contact us at facts@wolfssl.com or +1 425 245 8247.

Download wolfSSL Now