DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
mqtt_sn.cpp
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_sn.cpp
6 * @brief MQTT-SN v1.2 message builder + parser (pure, host-tested).
7 */
8
10
11#if DETWS_ENABLE_MQTT_SN
12
13#include <string.h>
14
15uint8_t mqttsn_make_flags(bool dup, uint8_t qos, bool retain, bool will, bool clean, uint8_t topic_id_type)
16{
17 uint8_t f = 0;
18 if (dup)
19 f |= MQTTSN_FLAG_DUP;
20 f |= (uint8_t)((qos & 0x03) << MQTTSN_FLAG_QOS_SHIFT) & MQTTSN_FLAG_QOS_MASK;
21 if (retain)
22 f |= MQTTSN_FLAG_RETAIN;
23 if (will)
24 f |= MQTTSN_FLAG_WILL;
25 if (clean)
26 f |= MQTTSN_FLAG_CLEAN;
27 f |= (uint8_t)(topic_id_type & MQTTSN_FLAG_TOPICIDTYPE_MASK);
28 return f;
29}
30
31// Write the [Length][MsgType] header for a message carrying @p body_len body octets.
32// Returns the body-start offset (always >= 2) and sets *total to the full message length,
33// or 0 on overflow / a body too large for the 16-bit Length field. The Length field value
34// is the whole message length including the Length field itself (per spec); it is 1 octet
35// when that total is <= 255, otherwise 0x01 + a big-endian uint16.
36static size_t frame_header(uint8_t *buf, size_t cap, uint8_t msg_type, size_t body_len, size_t *total)
37{
38 size_t core = 1 + body_len; // MsgType + body
39 size_t lenfield;
40 size_t t;
41 if (1 + core <= 255)
42 {
43 lenfield = 1;
44 t = 1 + core;
45 }
46 else
47 {
48 lenfield = 3;
49 t = 3 + core;
50 }
51 if (t > 0xFFFF || t > cap)
52 return 0;
53 size_t pos = 0;
54 if (lenfield == 1)
55 buf[pos++] = (uint8_t)t;
56 else
57 {
58 buf[pos++] = MQTTSN_LEN3_PREFIX;
59 buf[pos++] = (uint8_t)(t >> 8);
60 buf[pos++] = (uint8_t)(t & 0xFF);
61 }
62 buf[pos++] = msg_type;
63 *total = t;
64 return pos;
65}
66
67static void wr16(uint8_t *p, uint16_t v)
68{
69 p[0] = (uint8_t)(v >> 8);
70 p[1] = (uint8_t)(v & 0xFF);
71}
72
73static uint16_t rd16(const uint8_t *p)
74{
75 return (uint16_t)(((uint16_t)p[0] << 8) | p[1]);
76}
77
78size_t mqttsn_build_connect(uint8_t *buf, size_t cap, uint8_t flags, uint16_t duration, const char *client_id)
79{
80 if (!buf || !client_id)
81 return 0;
82 size_t idlen = strnlen(client_id, cap);
83 size_t total, p = frame_header(buf, cap, MQTTSN_CONNECT, 1 + 1 + 2 + idlen, &total);
84 if (!p)
85 return 0;
86 buf[p++] = flags;
87 buf[p++] = MQTTSN_PROTOCOL_ID;
88 wr16(buf + p, duration);
89 p += 2;
90 memcpy(buf + p, client_id, idlen);
91 return total;
92}
93
94size_t mqttsn_build_register(uint8_t *buf, size_t cap, uint16_t topic_id, uint16_t msg_id, const char *topic_name)
95{
96 if (!buf || !topic_name)
97 return 0;
98 size_t nlen = strnlen(topic_name, cap);
99 size_t total, p = frame_header(buf, cap, MQTTSN_REGISTER, 2 + 2 + nlen, &total);
100 if (!p)
101 return 0;
102 wr16(buf + p, topic_id);
103 p += 2;
104 wr16(buf + p, msg_id);
105 p += 2;
106 memcpy(buf + p, topic_name, nlen);
107 return total;
108}
109
110size_t mqttsn_build_regack(uint8_t *buf, size_t cap, uint16_t topic_id, uint16_t msg_id, uint8_t ret_code)
111{
112 if (!buf)
113 return 0;
114 size_t total, p = frame_header(buf, cap, MQTTSN_REGACK, 2 + 2 + 1, &total);
115 if (!p)
116 return 0;
117 wr16(buf + p, topic_id);
118 p += 2;
119 wr16(buf + p, msg_id);
120 p += 2;
121 buf[p] = ret_code;
122 return total;
123}
124
125size_t mqttsn_build_publish(uint8_t *buf, size_t cap, uint8_t flags, uint16_t topic_id, uint16_t msg_id,
126 const uint8_t *data, size_t data_len)
127{
128 if (!buf || (data_len && !data))
129 return 0;
130 size_t total, p = frame_header(buf, cap, MQTTSN_PUBLISH, 1 + 2 + 2 + data_len, &total);
131 if (!p)
132 return 0;
133 buf[p++] = flags;
134 wr16(buf + p, topic_id);
135 p += 2;
136 wr16(buf + p, msg_id);
137 p += 2;
138 if (data_len)
139 memcpy(buf + p, data, data_len);
140 return total;
141}
142
143size_t mqttsn_build_puback(uint8_t *buf, size_t cap, uint16_t topic_id, uint16_t msg_id, uint8_t ret_code)
144{
145 if (!buf)
146 return 0;
147 size_t total, p = frame_header(buf, cap, MQTTSN_PUBACK, 2 + 2 + 1, &total);
148 if (!p)
149 return 0;
150 wr16(buf + p, topic_id);
151 p += 2;
152 wr16(buf + p, msg_id);
153 p += 2;
154 buf[p] = ret_code;
155 return total;
156}
157
158size_t mqttsn_build_subscribe_name(uint8_t *buf, size_t cap, uint8_t flags, uint16_t msg_id, const char *topic_name)
159{
160 if (!buf || !topic_name)
161 return 0;
162 size_t nlen = strnlen(topic_name, cap);
163 size_t total, p = frame_header(buf, cap, MQTTSN_SUBSCRIBE, 1 + 2 + nlen, &total);
164 if (!p)
165 return 0;
166 buf[p++] = flags;
167 wr16(buf + p, msg_id);
168 p += 2;
169 memcpy(buf + p, topic_name, nlen);
170 return total;
171}
172
173size_t mqttsn_build_subscribe_id(uint8_t *buf, size_t cap, uint8_t flags, uint16_t msg_id, uint16_t topic_id)
174{
175 if (!buf)
176 return 0;
177 size_t total, p = frame_header(buf, cap, MQTTSN_SUBSCRIBE, 1 + 2 + 2, &total);
178 if (!p)
179 return 0;
180 buf[p++] = flags;
181 wr16(buf + p, msg_id);
182 p += 2;
183 wr16(buf + p, topic_id);
184 return total;
185}
186
187size_t mqttsn_build_pingreq(uint8_t *buf, size_t cap, const char *client_id)
188{
189 if (!buf)
190 return 0;
191 size_t idlen = client_id ? strnlen(client_id, cap) : 0;
192 size_t total, p = frame_header(buf, cap, MQTTSN_PINGREQ, idlen, &total);
193 if (!p)
194 return 0;
195 if (idlen)
196 memcpy(buf + p, client_id, idlen);
197 return total;
198}
199
200size_t mqttsn_build_disconnect(uint8_t *buf, size_t cap, bool with_duration, uint16_t duration)
201{
202 if (!buf)
203 return 0;
204 size_t total, p = frame_header(buf, cap, MQTTSN_DISCONNECT, with_duration ? 2 : 0, &total);
205 if (!p)
206 return 0;
207 if (with_duration)
208 wr16(buf + p, duration);
209 return total;
210}
211
212size_t mqttsn_build_searchgw(uint8_t *buf, size_t cap, uint8_t radius)
213{
214 if (!buf)
215 return 0;
216 size_t total, p = frame_header(buf, cap, MQTTSN_SEARCHGW, 1, &total);
217 if (!p)
218 return 0;
219 buf[p] = radius;
220 return total;
221}
222
223bool mqttsn_parse_header(const uint8_t *buf, size_t len, MqttsnHeader *out, size_t *consumed)
224{
225 if (!buf || !out || !consumed || len < 2)
226 return false;
227 size_t lenfield;
228 size_t total;
229 if (buf[0] == MQTTSN_LEN3_PREFIX)
230 {
231 if (len < 3)
232 return false;
233 total = ((size_t)buf[1] << 8) | buf[2];
234 lenfield = 3;
235 }
236 else
237 {
238 total = buf[0];
239 lenfield = 1;
240 }
241 if (total < lenfield + 1) // must hold the Length field + a MsgType octet
242 return false;
243 if (total > len) // message not fully buffered yet
244 return false;
245 out->msg_type = buf[lenfield];
246 out->payload = buf + lenfield + 1;
247 out->payload_len = total - lenfield - 1;
248 *consumed = total;
249 return true;
250}
251
252bool mqttsn_parse_connack(const uint8_t *payload, size_t len, uint8_t *ret_code)
253{
254 if (!payload || len < 1)
255 return false;
256 if (ret_code)
257 *ret_code = payload[0];
258 return true;
259}
260
261bool mqttsn_parse_regack(const uint8_t *payload, size_t len, uint16_t *topic_id, uint16_t *msg_id, uint8_t *ret_code)
262{
263 if (!payload || len < 5)
264 return false;
265 if (topic_id)
266 *topic_id = rd16(payload);
267 if (msg_id)
268 *msg_id = rd16(payload + 2);
269 if (ret_code)
270 *ret_code = payload[4];
271 return true;
272}
273
274bool mqttsn_parse_puback(const uint8_t *payload, size_t len, uint16_t *topic_id, uint16_t *msg_id, uint8_t *ret_code)
275{
276 return mqttsn_parse_regack(payload, len, topic_id, msg_id, ret_code); // identical layout
277}
278
279bool mqttsn_parse_suback(const uint8_t *payload, size_t len, uint8_t *flags, uint16_t *topic_id, uint16_t *msg_id,
280 uint8_t *ret_code)
281{
282 if (!payload || len < 6)
283 return false;
284 if (flags)
285 *flags = payload[0];
286 if (topic_id)
287 *topic_id = rd16(payload + 1);
288 if (msg_id)
289 *msg_id = rd16(payload + 3);
290 if (ret_code)
291 *ret_code = payload[5];
292 return true;
293}
294
295bool mqttsn_parse_publish(const uint8_t *payload, size_t len, uint8_t *flags, uint16_t *topic_id, uint16_t *msg_id,
296 const uint8_t **data, size_t *data_len)
297{
298 if (!payload || len < 5)
299 return false;
300 if (flags)
301 *flags = payload[0];
302 if (topic_id)
303 *topic_id = rd16(payload + 1);
304 if (msg_id)
305 *msg_id = rd16(payload + 3);
306 if (data)
307 *data = payload + 5;
308 if (data_len)
309 *data_len = len - 5;
310 return true;
311}
312
313bool mqttsn_parse_register(const uint8_t *payload, size_t len, uint16_t *topic_id, uint16_t *msg_id,
314 const char **topic_name, size_t *topic_name_len)
315{
316 if (!payload || len < 4)
317 return false;
318 if (topic_id)
319 *topic_id = rd16(payload);
320 if (msg_id)
321 *msg_id = rd16(payload + 2);
322 if (topic_name)
323 *topic_name = (const char *)(payload + 4);
324 if (topic_name_len)
325 *topic_name_len = len - 4;
326 return true;
327}
328
329#endif // DETWS_ENABLE_MQTT_SN
MQTT-SN v1.2 wire codec (DETWS_ENABLE_MQTT_SN) - zero-heap message builder + parser for MQTT for Sens...