ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
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 PC_ENABLE_MQTT_SN
12
13#include <string.h>
14
15uint8_t pc_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 {
20 f |= MQTTSN_FLAG_DUP;
21 }
22 f |= (uint8_t)((qos & 0x03) << MQTTSN_FLAG_QOS_SHIFT) & MQTTSN_FLAG_QOS_MASK;
23 if (retain)
24 {
25 f |= MQTTSN_FLAG_RETAIN;
26 }
27 if (will)
28 {
29 f |= MQTTSN_FLAG_WILL;
30 }
31 if (clean)
32 {
33 f |= MQTTSN_FLAG_CLEAN;
34 }
35 f |= (uint8_t)(topic_id_type & MQTTSN_FLAG_TOPICIDTYPE_MASK);
36 return f;
37}
38
39// Write the [Length][MsgType] header for a message carrying @p body_len body octets.
40// Returns the body-start offset (always >= 2) and sets *total to the full message length,
41// or 0 on overflow / a body too large for the 16-bit Length field. The Length field value
42// is the whole message length including the Length field itself (per spec); it is 1 octet
43// when that total is <= 255, otherwise 0x01 + a big-endian uint16.
44static size_t frame_header(uint8_t *buf, size_t cap, uint8_t msg_type, size_t body_len, size_t *total)
45{
46 size_t core = 1 + body_len; // MsgType + body
47 size_t lenfield;
48 size_t t;
49 if (1 + core <= 255)
50 {
51 lenfield = 1;
52 t = 1 + core;
53 }
54 else
55 {
56 lenfield = 3;
57 t = 3 + core;
58 }
59 if (t > 0xFFFF || t > cap)
60 {
61 return 0;
62 }
63 size_t pos = 0;
64 if (lenfield == 1)
65 {
66 buf[pos++] = (uint8_t)t;
67 }
68 else
69 {
70 buf[pos++] = MQTTSN_LEN3_PREFIX;
71 buf[pos++] = (uint8_t)(t >> 8);
72 buf[pos++] = (uint8_t)(t & 0xFF);
73 }
74 buf[pos++] = msg_type;
75 *total = t;
76 return pos;
77}
78
79static void wr16(uint8_t *p, uint16_t v)
80{
81 p[0] = (uint8_t)(v >> 8);
82 p[1] = (uint8_t)(v & 0xFF);
83}
84
85static uint16_t rd16(const uint8_t *p)
86{
87 return (uint16_t)(((uint16_t)p[0] << 8) | p[1]);
88}
89
90size_t pc_mqttsn_build_connect(uint8_t *buf, size_t cap, uint8_t flags, uint16_t duration, const char *client_id)
91{
92 if (!buf || !client_id)
93 {
94 return 0;
95 }
96 size_t idlen = strnlen(client_id, cap);
97 size_t total, p = frame_header(buf, cap, MQTTSN_CONNECT, 1 + 1 + 2 + idlen, &total);
98 if (!p)
99 {
100 return 0;
101 }
102 buf[p++] = flags;
103 buf[p++] = MQTTSN_PROTOCOL_ID;
104 wr16(buf + p, duration);
105 p += 2;
106 memcpy(buf + p, client_id, idlen);
107 return total;
108}
109
110size_t pc_mqttsn_build_register(uint8_t *buf, size_t cap, uint16_t topic_id, uint16_t msg_id, const char *topic_name)
111{
112 if (!buf || !topic_name)
113 {
114 return 0;
115 }
116 size_t nlen = strnlen(topic_name, cap);
117 size_t total, p = frame_header(buf, cap, MQTTSN_REGISTER, 2 + 2 + nlen, &total);
118 if (!p)
119 {
120 return 0;
121 }
122 wr16(buf + p, topic_id);
123 p += 2;
124 wr16(buf + p, msg_id);
125 p += 2;
126 memcpy(buf + p, topic_name, nlen);
127 return total;
128}
129
130size_t pc_mqttsn_build_regack(uint8_t *buf, size_t cap, uint16_t topic_id, uint16_t msg_id, uint8_t ret_code)
131{
132 if (!buf)
133 {
134 return 0;
135 }
136 size_t total, p = frame_header(buf, cap, MQTTSN_REGACK, 2 + 2 + 1, &total);
137 if (!p)
138 {
139 return 0;
140 }
141 wr16(buf + p, topic_id);
142 p += 2;
143 wr16(buf + p, msg_id);
144 p += 2;
145 buf[p] = ret_code;
146 return total;
147}
148
149size_t pc_mqttsn_build_publish(uint8_t *buf, size_t cap, uint8_t flags, uint16_t topic_id, uint16_t msg_id,
150 const uint8_t *data, size_t data_len)
151{
152 if (!buf || (data_len && !data))
153 {
154 return 0;
155 }
156 size_t total, p = frame_header(buf, cap, MQTTSN_PUBLISH, 1 + 2 + 2 + data_len, &total);
157 if (!p)
158 {
159 return 0;
160 }
161 buf[p++] = flags;
162 wr16(buf + p, topic_id);
163 p += 2;
164 wr16(buf + p, msg_id);
165 p += 2;
166 if (data_len)
167 {
168 memcpy(buf + p, data, data_len);
169 }
170 return total;
171}
172
173size_t pc_mqttsn_build_puback(uint8_t *buf, size_t cap, uint16_t topic_id, uint16_t msg_id, uint8_t ret_code)
174{
175 if (!buf)
176 {
177 return 0;
178 }
179 size_t total, p = frame_header(buf, cap, MQTTSN_PUBACK, 2 + 2 + 1, &total);
180 if (!p)
181 {
182 return 0;
183 }
184 wr16(buf + p, topic_id);
185 p += 2;
186 wr16(buf + p, msg_id);
187 p += 2;
188 buf[p] = ret_code;
189 return total;
190}
191
192size_t pc_mqttsn_build_subscribe_name(uint8_t *buf, size_t cap, uint8_t flags, uint16_t msg_id, const char *topic_name)
193{
194 if (!buf || !topic_name)
195 {
196 return 0;
197 }
198 size_t nlen = strnlen(topic_name, cap);
199 size_t total, p = frame_header(buf, cap, MQTTSN_SUBSCRIBE, 1 + 2 + nlen, &total);
200 if (!p)
201 {
202 return 0;
203 }
204 buf[p++] = flags;
205 wr16(buf + p, msg_id);
206 p += 2;
207 memcpy(buf + p, topic_name, nlen);
208 return total;
209}
210
211size_t pc_mqttsn_build_subscribe_id(uint8_t *buf, size_t cap, uint8_t flags, uint16_t msg_id, uint16_t topic_id)
212{
213 if (!buf)
214 {
215 return 0;
216 }
217 size_t total, p = frame_header(buf, cap, MQTTSN_SUBSCRIBE, 1 + 2 + 2, &total);
218 if (!p)
219 {
220 return 0;
221 }
222 buf[p++] = flags;
223 wr16(buf + p, msg_id);
224 p += 2;
225 wr16(buf + p, topic_id);
226 return total;
227}
228
229size_t pc_mqttsn_build_pingreq(uint8_t *buf, size_t cap, const char *client_id)
230{
231 if (!buf)
232 {
233 return 0;
234 }
235 size_t idlen = client_id ? strnlen(client_id, cap) : 0;
236 size_t total, p = frame_header(buf, cap, MQTTSN_PINGREQ, idlen, &total);
237 if (!p)
238 {
239 return 0;
240 }
241 if (idlen)
242 {
243 memcpy(buf + p, client_id, idlen);
244 }
245 return total;
246}
247
248size_t pc_mqttsn_build_disconnect(uint8_t *buf, size_t cap, bool with_duration, uint16_t duration)
249{
250 if (!buf)
251 {
252 return 0;
253 }
254 size_t total, p = frame_header(buf, cap, MQTTSN_DISCONNECT, with_duration ? 2 : 0, &total);
255 if (!p)
256 {
257 return 0;
258 }
259 if (with_duration)
260 {
261 wr16(buf + p, duration);
262 }
263 return total;
264}
265
266size_t pc_mqttsn_build_searchgw(uint8_t *buf, size_t cap, uint8_t radius)
267{
268 if (!buf)
269 {
270 return 0;
271 }
272 size_t total, p = frame_header(buf, cap, MQTTSN_SEARCHGW, 1, &total);
273 if (!p)
274 {
275 return 0;
276 }
277 buf[p] = radius;
278 return total;
279}
280
281bool pc_mqttsn_parse_header(const uint8_t *buf, size_t len, MqttsnHeader *out, size_t *consumed)
282{
283 if (!buf || !out || !consumed || len < 2)
284 {
285 return false;
286 }
287 size_t lenfield;
288 size_t total;
289 if (buf[0] == MQTTSN_LEN3_PREFIX)
290 {
291 if (len < 3)
292 {
293 return false;
294 }
295 total = ((size_t)buf[1] << 8) | buf[2];
296 lenfield = 3;
297 }
298 else
299 {
300 total = buf[0];
301 lenfield = 1;
302 }
303 if (total < lenfield + 1) // must hold the Length field + a MsgType octet
304 {
305 return false;
306 }
307 if (total > len) // message not fully buffered yet
308 {
309 return false;
310 }
311 out->msg_type = buf[lenfield];
312 out->payload = buf + lenfield + 1;
313 out->payload_len = total - lenfield - 1;
314 *consumed = total;
315 return true;
316}
317
318bool pc_mqttsn_parse_connack(const uint8_t *payload, size_t len, uint8_t *ret_code)
319{
320 if (!payload || len < 1)
321 {
322 return false;
323 }
324 if (ret_code)
325 {
326 *ret_code = payload[0];
327 }
328 return true;
329}
330
331bool pc_mqttsn_parse_regack(const uint8_t *payload, size_t len, uint16_t *topic_id, uint16_t *msg_id, uint8_t *ret_code)
332{
333 if (!payload || len < 5)
334 {
335 return false;
336 }
337 if (topic_id)
338 {
339 *topic_id = rd16(payload);
340 }
341 if (msg_id)
342 {
343 *msg_id = rd16(payload + 2);
344 }
345 if (ret_code)
346 {
347 *ret_code = payload[4];
348 }
349 return true;
350}
351
352bool pc_mqttsn_parse_puback(const uint8_t *payload, size_t len, uint16_t *topic_id, uint16_t *msg_id, uint8_t *ret_code)
353{
354 return pc_mqttsn_parse_regack(payload, len, topic_id, msg_id, ret_code); // identical layout
355}
356
357bool pc_mqttsn_parse_suback(const uint8_t *payload, size_t len, uint8_t *flags, uint16_t *topic_id, uint16_t *msg_id,
358 uint8_t *ret_code)
359{
360 if (!payload || len < 6)
361 {
362 return false;
363 }
364 if (flags)
365 {
366 *flags = payload[0];
367 }
368 if (topic_id)
369 {
370 *topic_id = rd16(payload + 1);
371 }
372 if (msg_id)
373 {
374 *msg_id = rd16(payload + 3);
375 }
376 if (ret_code)
377 {
378 *ret_code = payload[5];
379 }
380 return true;
381}
382
383bool pc_mqttsn_parse_publish(const uint8_t *payload, size_t len, uint8_t *flags, uint16_t *topic_id, uint16_t *msg_id,
384 const uint8_t **data, size_t *data_len)
385{
386 if (!payload || len < 5)
387 {
388 return false;
389 }
390 if (flags)
391 {
392 *flags = payload[0];
393 }
394 if (topic_id)
395 {
396 *topic_id = rd16(payload + 1);
397 }
398 if (msg_id)
399 {
400 *msg_id = rd16(payload + 3);
401 }
402 if (data)
403 {
404 *data = payload + 5;
405 }
406 if (data_len)
407 {
408 *data_len = len - 5;
409 }
410 return true;
411}
412
413bool pc_mqttsn_parse_register(const uint8_t *payload, size_t len, uint16_t *topic_id, uint16_t *msg_id,
414 const char **topic_name, size_t *topic_name_len)
415{
416 if (!payload || len < 4)
417 {
418 return false;
419 }
420 if (topic_id)
421 {
422 *topic_id = rd16(payload);
423 }
424 if (msg_id)
425 {
426 *msg_id = rd16(payload + 2);
427 }
428 if (topic_name)
429 {
430 *topic_name = (const char *)(payload + 4);
431 }
432 if (topic_name_len)
433 {
434 *topic_name_len = len - 4;
435 }
436 return true;
437}
438
439#endif // PC_ENABLE_MQTT_SN
MQTT-SN v1.2 wire codec (PC_ENABLE_MQTT_SN) - zero-heap message builder + parser for MQTT for Sensor ...