ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
mqtt_sn.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file mqtt_sn.h
6 * @brief The MQTT-SN v1.2 wire codec (PROTOCORE_ENABLE_MQTT_SN).
7 *
8 * The governing document is "MQTT For Sensor Networks (MQTT-SN) Protocol Specification Version 1.2",
9 * by Andy Stanford-Clark and Hong Linh Truong, published by IBM on 14 November 2013. **It is not an
10 * OASIS Standard and it is not an IETF protocol: it carries neither a standard number nor an RFC
11 * number.** OASIS hosts the document for its MQTT Technical Committee's MQTT-SN Subcommittee, whose
12 * own MQTT-SN Version 2.0 work is a separate specification. Every section, table and message name
13 * cited here is from that v1.2 document.
14 *
15 * MQTT-SN carries publish/subscribe over a datagram link for constrained, lossy networks: numeric
16 * topic ids instead of topic names, gateway discovery, and a keep-alive that supports sleeping
17 * clients (sec 2, sec 3). A message is:
18 * @code
19 * [Length][MsgType][Message Variable Part]
20 * @endcode
21 * - Length is 1 octet when the whole message, the Length field included, is at most 255 octets;
22 * otherwise it is 3 octets: ::MQTTSN_LEN3_PREFIX then a big-endian uint16 of that same total
23 * (sec 5.2.1). The 3-octet form reaches 65535 octets.
24 * - MsgType is one octet, from Table 3 (sec 5.2.2).
25 * - TopicId, MsgId and Duration are 2 octets, most significant octet first (sec 5.3.3, sec 5.3.7,
26 * sec 5.3.11). A topic is named by a registered TopicId, a pre-defined TopicId, or a 2-character
27 * short topic name, as the Flags TopicIdType states (sec 5.3.4).
28 *
29 * This is the wire codec only. The gateway connection, the topic registry, and the retransmission
30 * and sleep state (sec 6.13, sec 6.14) belong to the application.
31 *
32 * The module exports one symbol, @ref Mqttsn. Everything in mqtt_sn.c has internal linkage.
33 *
34 * @author Douglas Quigg (dstroy0)
35 * @date 2026
36 */
37
38#ifndef PROTOCORE_MQTT_SN_H
39#define PROTOCORE_MQTT_SN_H
40
41#include "protocore_config.h" // the entry point: protocore_types.h for the widths
42
43#if PROTOCORE_ENABLE_MQTT_SN
44
46
47// ---------------------------------------------------------------------------
48// Literals
49// ---------------------------------------------------------------------------
50
51#define MQTTSN_LEN3_PREFIX 0x01 ///< a first Length octet of 0x01 signals the 3-octet form (sec 5.2.1)
52
53// MsgType values (MQTT-SN v1.2 sec 5.2.2, Table 3). The types this codec neither builds nor parses -
54// WILLTOPICUPD 0x1A, WILLTOPICRESP 0x1B, WILLMSGUPD 0x1C, WILLMSGRESP 0x1D, and the encapsulated
55// message 0xFE - are named by the table but not listed here.
56#define MQTTSN_ADVERTISE 0x00
57#define MQTTSN_SEARCHGW 0x01
58#define MQTTSN_GWINFO 0x02
59#define MQTTSN_CONNECT 0x04
60#define MQTTSN_CONNACK 0x05
61#define MQTTSN_WILLTOPICREQ 0x06
62#define MQTTSN_WILLTOPIC 0x07
63#define MQTTSN_WILLMSGREQ 0x08
64#define MQTTSN_WILLMSG 0x09
65#define MQTTSN_REGISTER 0x0A
66#define MQTTSN_REGACK 0x0B
67#define MQTTSN_PUBLISH 0x0C
68#define MQTTSN_PUBACK 0x0D
69#define MQTTSN_PUBCOMP 0x0E
70#define MQTTSN_PUBREC 0x0F
71#define MQTTSN_PUBREL 0x10
72#define MQTTSN_SUBSCRIBE 0x12
73#define MQTTSN_SUBACK 0x13
74#define MQTTSN_UNSUBSCRIBE 0x14
75#define MQTTSN_UNSUBACK 0x15
76#define MQTTSN_PINGREQ 0x16
77#define MQTTSN_PINGRESP 0x17
78#define MQTTSN_DISCONNECT 0x18
79
80// Flags octet bit layout (MQTT-SN v1.2 sec 5.3.4, Table 4).
81#define MQTTSN_FLAG_DUP 0x80 ///< DUP, bit 7
82#define MQTTSN_FLAG_QOS_MASK 0x60 ///< QoS, bits 6-5
83#define MQTTSN_FLAG_QOS_SHIFT 5 ///< how far QoS sits from bit 0
84#define MQTTSN_FLAG_RETAIN 0x10 ///< Retain, bit 4
85#define MQTTSN_FLAG_WILL 0x08 ///< Will, bit 3
86#define MQTTSN_FLAG_CLEAN 0x04 ///< CleanSession, bit 2
87#define MQTTSN_FLAG_TOPICIDTYPE_MASK 0x03 ///< TopicIdType, bits 1-0
88
89// TopicIdType values, the low two bits of Flags (MQTT-SN v1.2 sec 5.3.4).
90#define MQTTSN_TOPIC_NORMAL 0x00 ///< a registered numeric topic id (REGISTER, sec 6.5)
91#define MQTTSN_TOPIC_PREDEFINED 0x01 ///< a pre-defined numeric topic id (sec 6.7)
92#define MQTTSN_TOPIC_SHORT 0x02 ///< a 2-character short topic name (sec 6.7)
93
94// ReturnCode values (MQTT-SN v1.2 sec 5.3.10, Table 5).
95#define MQTTSN_RC_ACCEPTED 0x00
96#define MQTTSN_RC_CONGESTION 0x01
97#define MQTTSN_RC_INVALID_TOPIC_ID 0x02
98#define MQTTSN_RC_NOT_SUPPORTED 0x03
99
100#define MQTTSN_PROTOCOL_ID 0x01 ///< the CONNECT ProtocolId octet; all other values are reserved (sec 5.3.8)
101
102// ---------------------------------------------------------------------------
103// Typedefs
104// ---------------------------------------------------------------------------
105
106/** @brief MQTT-SN v1.2 sec 5.3.4: the six fields the Flags octet packs. */
107typedef struct
108{
109 proto_bool dup; ///< DUP: the message is a retransmission
110 uint8_t qos; ///< QoS: 0, 1, 2, or 3 for QoS level -1 (sec 6.8)
111 proto_bool retain; ///< Retain
112 proto_bool will; ///< Will: the client asks for Will prompting (CONNECT, sec 5.4.4)
113 proto_bool clean_session; ///< CleanSession (sec 6.3)
114 uint8_t topic_id_type; ///< TopicIdType: MQTTSN_TOPIC_NORMAL, _PREDEFINED or _SHORT
115 uint8_t octet; ///< the packed Flags octet a compose writes and a parse reports
116} MqttsnFlagsArgs;
117
118/** @brief MQTT-SN v1.2 sec 5.3.11, sec 5.3.12: how a message names its topic. */
119typedef struct
120{
121 uint16_t topic_id; ///< TopicId; 0x0000 and 0xFFFF are reserved (sec 5.3.11)
122 const char *topic_name; ///< TopicName a build writes, or where a parse found it (sec 5.3.12)
123 size_t topic_name_len; ///< its octet count as a parse reports it
124} MqttsnTopicArgs;
125
126/** @brief MQTT-SN v1.2 sec 5.3.1, sec 5.3.3, sec 5.3.7, sec 5.3.9, sec 5.3.10: the scalar fields. */
127typedef struct
128{
129 const char *client_id; ///< ClientId, 1 to 23 characters (sec 5.3.1)
130 uint16_t duration; ///< Duration in seconds (sec 5.3.3)
131 proto_bool with_duration; ///< a DISCONNECT carries the sleep Duration (sec 5.4.21, sec 6.14)
132 uint16_t msg_id; ///< MsgId, matching a message to its acknowledgment (sec 5.3.7)
133 uint8_t radius; ///< Radius; 0x00 broadcasts to all nodes (sec 5.3.9)
134 uint8_t return_code; ///< ReturnCode (sec 5.3.10)
135} MqttsnFieldArgs;
136
137/** @brief MQTT-SN v1.2 sec 5.3.2: the Data a PUBLISH carries. */
138typedef struct
139{
140 const uint8_t *data; ///< the application data a build writes, or where a parse found it
141 size_t data_len; ///< its octet count
142} MqttsnDataArgs;
143
144/** @brief MQTT-SN v1.2 sec 5.2: the Length and MsgType header, and the Variable Part behind it. */
145typedef struct
146{
147 uint8_t msg_type; ///< MsgType (sec 5.2.2)
148 const uint8_t *variable; ///< the Message Variable Part, pointing into @c in (sec 5.3)
149 size_t variable_len; ///< its octet count
150} MqttsnHeaderArgs;
151
152/**
153 * @brief The octets a build writes or a parse reads.
154 *
155 * A header parse reads a whole message from @c in; every typed parse below reads the Message
156 * Variable Part the header parse pointed @c header.variable at.
157 */
158typedef struct
159{
160 uint8_t *out; ///< where a build writes the whole message
161 size_t cap; ///< its room
162 const uint8_t *in; ///< the octets a parse reads
163 size_t avail; ///< how many are readable there
164} MqttsnBufArgs;
165
166/**
167 * @brief The MQTT-SN v1.2 wire codec.
168 *
169 * A caller sets the members a call takes, invokes it through ::Mqttsn, and reads the outcome off the
170 * same handle.
171 *
172 * No slot member: every call works on the buffer the caller lends, so no call names a row.
173 *
174 * @var MqttsnNs::flags the six fields the Flags octet packs (sec 5.3.4)
175 * @var MqttsnNs::topic how a message names its topic (sec 5.3.11, sec 5.3.12)
176 * @var MqttsnNs::field ClientId, Duration, MsgId, Radius and ReturnCode (sec 5.3)
177 * @var MqttsnNs::data the Data a PUBLISH carries (sec 5.3.2)
178 * @var MqttsnNs::header the Length and MsgType header a parse read (sec 5.2)
179 * @var MqttsnNs::buf the octets a build writes or a parse reads
180 * @var MqttsnNs::ok a call's true/false outcome
181 * @var MqttsnNs::n
182 * The whole message length: what a build wrote, or what a header parse consumed so the caller can
183 * advance. 0 when a build did not fit @c cap or the message would exceed the 16-bit Length field.
184 * @var MqttsnNs::make_flags
185 * Pack @c flags.dup, @c flags.qos, @c flags.retain, @c flags.will, @c flags.clean_session and
186 * @c flags.topic_id_type into @c flags.octet (sec 5.3.4).
187 * @var MqttsnNs::build_connect
188 * CONNECT: Flags, ProtocolId, Duration, ClientId (sec 5.4.4).
189 * @var MqttsnNs::build_register
190 * REGISTER: TopicId, MsgId, TopicName. A client codes TopicId 0x0000 (sec 5.4.10).
191 * @var MqttsnNs::build_regack
192 * REGACK: TopicId, MsgId, ReturnCode (sec 5.4.11).
193 * @var MqttsnNs::build_publish
194 * PUBLISH: Flags, TopicId, MsgId, Data (sec 5.4.12).
195 * @var MqttsnNs::build_puback
196 * PUBACK: TopicId, MsgId, ReturnCode (sec 5.4.13).
197 * @var MqttsnNs::build_subscribe_name
198 * SUBSCRIBE naming a TopicName: Flags, MsgId, TopicName. @c flags.octet states TopicIdType normal or
199 * short (sec 5.4.15).
200 * @var MqttsnNs::build_subscribe_id
201 * SUBSCRIBE naming a pre-defined TopicId: Flags, MsgId, TopicId (sec 5.4.15).
202 * @var MqttsnNs::build_pingreq
203 * PINGREQ, with the optional ClientId a sleeping client includes when it wakes; a null
204 * @c field.client_id builds the plain keep-alive form (sec 5.4.19, sec 6.14).
205 * @var MqttsnNs::build_disconnect
206 * DISCONNECT, carrying the sleep Duration when @c field.with_duration is set (sec 5.4.21, sec 6.14).
207 * @var MqttsnNs::build_searchgw
208 * SEARCHGW: Radius (sec 5.4.2).
209 * @var MqttsnNs::parse_header
210 * Read the Length and MsgType at the head of @c in into @c header, with @c n reporting the whole
211 * message length so the caller can advance (sec 5.2). False on an incomplete or self-inconsistent
212 * message.
213 * @var MqttsnNs::parse_connack CONNACK: ReturnCode into @c field.return_code (sec 5.4.5)
214 * @var MqttsnNs::parse_regack
215 * REGACK: TopicId, MsgId and ReturnCode into @c topic and @c field (sec 5.4.11).
216 * @var MqttsnNs::parse_puback
217 * PUBACK: the same three fields, which share REGACK's layout (sec 5.4.13).
218 * @var MqttsnNs::parse_suback
219 * SUBACK: Flags, TopicId, MsgId and ReturnCode; the granted QoS is in @c flags.octet (sec 5.4.16).
220 * @var MqttsnNs::parse_publish
221 * PUBLISH: Flags, TopicId, MsgId, and the Data slice into @c data (sec 5.4.12).
222 * @var MqttsnNs::parse_register
223 * REGISTER: TopicId, MsgId, and the TopicName slice into @c topic (sec 5.4.10).
224 *
225 * No storage member: every call works in the buffer the caller lends and holds nothing between
226 * calls.
227 */
228typedef struct
229{
230 MqttsnFlagsArgs flags; ///< the Flags octet's six fields
231 MqttsnTopicArgs topic; ///< how a message names its topic
232 MqttsnFieldArgs field; ///< the scalar fields of the Message Variable Part
233 MqttsnDataArgs data; ///< the Data a PUBLISH carries
234 MqttsnHeaderArgs header; ///< the Length and MsgType header a parse read
235 MqttsnBufArgs buf; ///< the octets a call moves
236 proto_bool ok;
237 size_t n;
238} MqttsnVars;
239
240/** @brief The operands and the outcome. */
241extern MqttsnVars MqttsnV;
242
243/** @brief The entries. */
244typedef struct
245{
246 void (*const make_flags)(uint8_t *work);
247 void (*const build_connect)(uint8_t *work);
248 void (*const build_register)(uint8_t *work);
249 void (*const build_regack)(uint8_t *work);
250 void (*const build_publish)(uint8_t *work);
251 void (*const build_puback)(uint8_t *work);
252 void (*const build_subscribe_name)(uint8_t *work);
253 void (*const build_subscribe_id)(uint8_t *work);
254 void (*const build_pingreq)(uint8_t *work);
255 void (*const build_disconnect)(uint8_t *work);
256 void (*const build_searchgw)(uint8_t *work);
257 void (*const parse_header)(uint8_t *work);
258 void (*const parse_connack)(uint8_t *work);
259 void (*const parse_regack)(uint8_t *work);
260 void (*const parse_puback)(uint8_t *work);
261 void (*const parse_suback)(uint8_t *work);
262 void (*const parse_publish)(uint8_t *work);
263 void (*const parse_register)(uint8_t *work);
264} MqttsnNs;
265
266// What the table binds, defined once in the .c and taking one parameter each: everything
267// else an entry needs is an operand in MqttsnV or a region of the borrow at a fixed offset.
268void protocore_mqttsn_make_flags(uint8_t *work);
269void protocore_mqttsn_build_connect(uint8_t *work);
270void protocore_mqttsn_build_register(uint8_t *work);
271void protocore_mqttsn_build_regack(uint8_t *work);
272void protocore_mqttsn_build_publish(uint8_t *work);
273void protocore_mqttsn_build_puback(uint8_t *work);
274void protocore_mqttsn_build_subscribe_name(uint8_t *work);
275void protocore_mqttsn_build_subscribe_id(uint8_t *work);
276void protocore_mqttsn_build_pingreq(uint8_t *work);
277void protocore_mqttsn_build_disconnect(uint8_t *work);
278void protocore_mqttsn_build_searchgw(uint8_t *work);
279void protocore_mqttsn_parse_header(uint8_t *work);
280void protocore_mqttsn_parse_connack(uint8_t *work);
281void protocore_mqttsn_parse_regack(uint8_t *work);
282void protocore_mqttsn_parse_puback(uint8_t *work);
283void protocore_mqttsn_parse_suback(uint8_t *work);
284void protocore_mqttsn_parse_publish(uint8_t *work);
285void protocore_mqttsn_parse_register(uint8_t *work);
286
287// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
288// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
289// `Mqttsn.make_flags(work)` resolves to a named function and becomes a DIRECT call. An extern table
290// leaves the call indirect and the symbol live at every level, -O2 -flto included.
291static const MqttsnNs Mqttsn __attribute__((unused)) = {
292 .make_flags = protocore_mqttsn_make_flags,
293 .build_connect = protocore_mqttsn_build_connect,
294 .build_register = protocore_mqttsn_build_register,
295 .build_regack = protocore_mqttsn_build_regack,
296 .build_publish = protocore_mqttsn_build_publish,
297 .build_puback = protocore_mqttsn_build_puback,
298 .build_subscribe_name = protocore_mqttsn_build_subscribe_name,
299 .build_subscribe_id = protocore_mqttsn_build_subscribe_id,
300 .build_pingreq = protocore_mqttsn_build_pingreq,
301 .build_disconnect = protocore_mqttsn_build_disconnect,
302 .build_searchgw = protocore_mqttsn_build_searchgw,
303 .parse_header = protocore_mqttsn_parse_header,
304 .parse_connack = protocore_mqttsn_parse_connack,
305 .parse_regack = protocore_mqttsn_parse_regack,
306 .parse_puback = protocore_mqttsn_parse_puback,
307 .parse_suback = protocore_mqttsn_parse_suback,
308 .parse_publish = protocore_mqttsn_parse_publish,
309 .parse_register = protocore_mqttsn_parse_register,
310};
311
313
314#endif // PROTOCORE_ENABLE_MQTT_SN
315
316#endif // PROTOCORE_MQTT_SN_H
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97