DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
mqtt.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file mqtt.h
6 * @brief Zero-heap MQTT 3.1.1 publish/subscribe client (DETWS_ENABLE_MQTT).
7 *
8 * A full, persistent outbound client for IoT messaging: connect to a broker with
9 * an optional Last-Will and credentials, PUBLISH and SUBSCRIBE / UNSUBSCRIBE at
10 * QoS 0, 1, or 2 (complete acknowledgement flows in both directions, with bounded
11 * in-flight retransmit), receive messages via a callback, and keep the session
12 * alive. Split, like the other services, into a pure host-testable codec and an
13 * ESP32-only transport:
14 *
15 * - mqtt_build_* / mqtt_parse_* / mqtt_*_remlen are pure packet functions,
16 * unit-tested on the host (env:native_mqtt).
17 * - mqtt_connect() / mqtt_publish() / mqtt_subscribe() / mqtt_loop() resolve the
18 * broker (DNS), open a raw lwIP TCP connection (mqtts:// via client-side
19 * mbedTLS over the shared static arena), and drive the session. No heap; one
20 * broker connection at a time.
21 *
22 * QoS 2 uses the four-packet PUBLISH/PUBREC/PUBREL/PUBCOMP exchange; outbound
23 * QoS 1/2 messages are held in a fixed in-flight pool and retransmitted (DUP) on
24 * timeout until acknowledged. Inbound QoS 2 is de-duplicated by packet id.
25 */
26
27#ifndef DETERMINISTICESPASYNCWEBSERVER_MQTT_H
28#define DETERMINISTICESPASYNCWEBSERVER_MQTT_H
29
30#include "ServerConfig.h"
31#include <stddef.h>
32#include <stdint.h>
33
34#if DETWS_ENABLE_MQTT
35
36/** @brief MQTT control packet types (high nibble of byte 0), MQTT 3.1.1 §2.2.1. */
37enum class MqttType : uint8_t
38{
39 MQTT_CONNECT = 1,
40 MQTT_CONNACK = 2,
41 MQTT_PUBLISH = 3,
42 MQTT_PUBACK = 4,
43 MQTT_PUBREC = 5,
44 MQTT_PUBREL = 6,
45 MQTT_PUBCOMP = 7,
46 MQTT_SUBSCRIBE = 8,
47 MQTT_SUBACK = 9,
48 MQTT_UNSUBSCRIBE = 10,
49 MQTT_UNSUBACK = 11,
50 MQTT_PINGREQ = 12,
51 MQTT_PINGRESP = 13,
52 MQTT_DISCONNECT = 14,
53};
54
55/** @brief CONNECT options (credentials, keep-alive, clean session, Last-Will). */
56struct MqttConnectOpts
57{
58 const char *client_id; ///< Client identifier (required; may be "" for a broker-assigned id).
59 const char *user; ///< Username, or nullptr for none.
60 const char *pass; ///< Password, or nullptr for none.
61 uint16_t keepalive_s; ///< Keep-alive seconds (0 disables).
62 bool clean_session; ///< Clean Session flag.
63 const char *will_topic; ///< Last-Will topic, or nullptr for no will.
64 const uint8_t *will_msg; ///< Last-Will payload (may be nullptr when will_len is 0).
65 size_t will_len; ///< Last-Will payload length.
66 uint8_t will_qos; ///< Last-Will QoS (0-2).
67 bool will_retain; ///< Last-Will retain flag.
68};
69
70// ---------------------------------------------------------------------------
71// Pure codec (host-testable; no sockets, no heap)
72// ---------------------------------------------------------------------------
73
74/**
75 * @brief Encode an MQTT Remaining Length field (variable-length, 1-4 bytes).
76 * @return number of bytes written to @p out (1-4), or 0 if @p len exceeds the
77 * 268,435,455 maximum.
78 */
79size_t mqtt_encode_remlen(uint8_t *out, uint32_t len);
80
81/**
82 * @brief Decode a Remaining Length field from @p buf (up to @p avail bytes).
83 * @param value receives the decoded length.
84 * @param used receives the number of bytes consumed (1-4).
85 * @return true on success; false if the field is incomplete or malformed (>4 bytes).
86 */
87bool mqtt_decode_remlen(const uint8_t *buf, size_t avail, uint32_t *value, size_t *used);
88
89/**
90 * @brief Build a CONNECT packet from @p opts.
91 * @return total packet length written to @p out, or 0 if it would not fit @p cap.
92 */
93size_t mqtt_build_connect(uint8_t *out, size_t cap, const MqttConnectOpts *opts);
94
95/**
96 * @brief Build a PUBLISH packet (@p qos 0/1/2; @p packet_id used only when qos>0;
97 * set @p dup for a retransmission).
98 * @return total packet length, or 0 if it would not fit @p cap.
99 */
100size_t mqtt_build_publish(uint8_t *out, size_t cap, const char *topic, const uint8_t *payload, size_t payload_len,
101 uint8_t qos, uint16_t packet_id, bool retain, bool dup);
102
103/** @brief Build a SUBSCRIBE packet for a single topic filter at @p qos. */
104size_t mqtt_build_subscribe(uint8_t *out, size_t cap, uint16_t packet_id, const char *topic, uint8_t qos);
105
106/** @brief Build an UNSUBSCRIBE packet for a single topic filter. */
107size_t mqtt_build_unsubscribe(uint8_t *out, size_t cap, uint16_t packet_id, const char *topic);
108
109/**
110 * @brief Build a 4-byte acknowledgement packet (PUBACK / PUBREC / PUBREL / PUBCOMP)
111 * carrying @p packet_id. (PUBREL sets the required flags 0x62.)
112 */
113size_t mqtt_build_ack(uint8_t *out, size_t cap, MqttType type, uint16_t packet_id);
114
115/** @brief Build a 2-byte PINGREQ. */
116size_t mqtt_build_pingreq(uint8_t *out, size_t cap);
117
118/** @brief Build a 2-byte DISCONNECT. */
119size_t mqtt_build_disconnect(uint8_t *out, size_t cap);
120
121/**
122 * @brief Parse a fixed header at @p buf (type/flags + Remaining Length).
123 * @param header_len receives the fixed-header size (1 + remlen-field bytes).
124 * @return true if a complete fixed header is present in @p avail bytes.
125 */
126bool mqtt_parse_fixed_header(const uint8_t *buf, size_t avail, uint8_t *type, uint8_t *flags, uint32_t *remaining_len,
127 size_t *header_len);
128
129/**
130 * @brief Parse a PUBLISH variable header + payload (the @p remaining_len bytes
131 * that follow the fixed header), copying the topic into @p topic_out.
132 *
133 * @param flags the fixed-header flags (low nibble); bits 1-2 carry QoS.
134 * @param payload receives a pointer into @p buf at the payload start.
135 * @param packet_id receives the packet id (QoS>0 only; 0 for QoS 0).
136 * @return true on success; false if malformed or the topic overflows @p topic_cap.
137 */
138bool mqtt_parse_publish(const uint8_t *buf, uint32_t remaining_len, uint8_t flags, char *topic_out, size_t topic_cap,
139 size_t *topic_len, const uint8_t **payload, size_t *payload_len, uint16_t *packet_id);
140
141/**
142 * @brief Read the 2-byte packet id from a PUBACK/PUBREC/PUBREL/PUBCOMP/UNSUBACK
143 * body (the @p remaining_len bytes after the fixed header).
144 * @return the packet id, or 0 if malformed (a real id is never 0).
145 */
146uint16_t mqtt_parse_ack(const uint8_t *buf, uint32_t remaining_len);
147
148/**
149 * @brief Read a CONNACK from its @p remaining_len bytes.
150 * @param session_present receives the Session Present flag (may be nullptr).
151 * @return the return code (0 = Connection Accepted), or -1 if malformed.
152 */
153int mqtt_parse_connack(const uint8_t *buf, uint32_t remaining_len, bool *session_present);
154
155/**
156 * @brief Read a SUBACK from its @p remaining_len bytes.
157 * @param packet_id receives the packet id.
158 * @param return_code receives the first granted-QoS / failure (0x80) byte.
159 * @return true on success.
160 */
161bool mqtt_parse_suback(const uint8_t *buf, uint32_t remaining_len, uint16_t *packet_id, uint8_t *return_code);
162
163// ---------------------------------------------------------------------------
164// Transport (ESP32 only; the calls are no-ops / false on a host build)
165// ---------------------------------------------------------------------------
166
167/** @brief Callback for an inbound PUBLISH delivered to a subscription. */
168typedef void (*MqttMessageCb)(const char *topic, const uint8_t *payload, size_t len);
169
170/** @brief Register the inbound-message callback (call before mqtt_connect). */
171void mqtt_on_message(MqttMessageCb cb);
172
173/**
174 * @brief Connect to a broker and complete the MQTT handshake (blocking).
175 *
176 * Resolves @p host, opens TCP (TLS when @p use_tls and DETWS_ENABLE_MQTT_TLS),
177 * sends CONNECT (from @p opts) and waits for an accepted CONNACK.
178 * @return true on an accepted connection.
179 */
180bool mqtt_connect(const char *host, uint16_t port, bool use_tls, const MqttConnectOpts *opts);
181
182/** @brief Publish @p payload to @p topic at @p qos (0/1/2). @return true if accepted. */
183bool mqtt_publish(const char *topic, const uint8_t *payload, size_t len, uint8_t qos, bool retain);
184
185/** @brief Subscribe to @p topic at @p qos (0/1/2). @return true if the SUBSCRIBE was sent. */
186bool mqtt_subscribe(const char *topic, uint8_t qos);
187
188/** @brief Unsubscribe from @p topic. @return true if the UNSUBSCRIBE was sent. */
189bool mqtt_unsubscribe(const char *topic);
190
191/**
192 * @brief Pump the connection: read inbound packets (dispatching PUBLISH to the
193 * callback and running the QoS 1/2 acknowledgement flows), retransmit
194 * unacked outbound QoS 1/2 messages, and send a keep-alive PINGREQ when
195 * due. Call once per loop(). @return false if the connection has dropped.
196 */
197bool mqtt_loop();
198
199/** @brief True while connected to the broker. */
200bool mqtt_connected();
201
202/** @brief Send DISCONNECT and close the connection. */
203void mqtt_disconnect();
204
205#endif // DETWS_ENABLE_MQTT
206
207#endif // DETERMINISTICESPASYNCWEBSERVER_MQTT_H
User-facing configuration for DeterministicESPAsyncWebServer.