22static inline void put_u16(uint8_t *p, uint16_t v)
24 p[0] = (uint8_t)(v >> 8);
25 p[1] = (uint8_t)(v & 0xFF);
27static inline uint16_t get_u16(
const uint8_t *p)
29 return (uint16_t)((p[0] << 8) | p[1]);
32static size_t put_field(uint8_t *p,
const uint8_t *data,
size_t len)
34 put_u16(p, (uint16_t)len);
36 memcpy(p + 2, data, len);
39static inline size_t put_str(uint8_t *p,
const char *s)
44size_t mqtt_encode_remlen(uint8_t *out, uint32_t len)
51 uint8_t
byte = (uint8_t)(len % 128);
60bool mqtt_decode_remlen(
const uint8_t *buf,
size_t avail, uint32_t *value,
size_t *used)
70 v += (uint32_t)(b & 0x7F) * mult;
88static size_t compose(uint8_t *out,
size_t cap, uint8_t byte0,
const uint8_t *body,
size_t blen)
94 size_t rln = mqtt_encode_remlen(rl, (uint32_t)blen);
97 size_t total = 1 + rln + blen;
101 memcpy(out + 1, rl, rln);
103 memcpy(out + 1 + rln, body, blen);
107size_t mqtt_build_connect(uint8_t *out,
size_t cap,
const MqttConnectOpts *opts)
109 if (!out || !opts || !opts->client_id)
114 n += put_str(body + n,
"MQTT");
118 if (opts->clean_session)
120 if (opts->will_topic)
123 flags |= (uint8_t)((opts->will_qos & 0x03) << 3);
124 if (opts->will_retain)
132 put_u16(body + n, opts->keepalive_s);
138 if (opts->will_topic)
144 if (n + need >
sizeof(body))
147 n += put_str(body + n, opts->client_id);
148 if (opts->will_topic)
150 n += put_str(body + n, opts->will_topic);
151 n += put_field(body + n, opts->will_msg, opts->will_len);
154 n += put_str(body + n, opts->user);
156 n += put_str(body + n, opts->pass);
158 return compose(out, cap, (uint8_t)((uint8_t)MqttType::MQTT_CONNECT << 4), body, n);
161size_t mqtt_build_publish(uint8_t *out,
size_t cap,
const char *topic,
const uint8_t *payload,
size_t payload_len,
162 uint8_t qos, uint16_t packet_id,
bool retain,
bool dup)
164 if (!out || !topic || qos > 2)
168 for (
const char *t = topic; *t; t++)
169 if (*t ==
'+' || *t ==
'#')
172 size_t blen = 2 + tlen + (qos > 0 ? 2 : 0) + payload_len;
174 if (blen >
sizeof(body))
177 n += put_field(body + n, (
const uint8_t *)topic, tlen);
180 put_u16(body + n, packet_id);
184 memcpy(body + n, payload, payload_len);
187 uint8_t f = (uint8_t)((qos & 0x03) << 1);
192 return compose(out, cap, (uint8_t)(((uint8_t)MqttType::MQTT_PUBLISH << 4) | f), body, n);
195size_t mqtt_build_subscribe(uint8_t *out,
size_t cap, uint16_t packet_id,
const char *topic, uint8_t qos)
197 if (!out || !topic || qos > 2)
201 size_t blen = 2 + 2 + tlen + 1;
202 if (blen >
sizeof(body))
205 put_u16(body + n, packet_id);
207 n += put_field(body + n, (
const uint8_t *)topic, tlen);
208 body[n++] = (uint8_t)(qos & 0x03);
209 return compose(out, cap, (uint8_t)(((uint8_t)MqttType::MQTT_SUBSCRIBE << 4) | 0x02), body,
213size_t mqtt_build_unsubscribe(uint8_t *out,
size_t cap, uint16_t packet_id,
const char *topic)
219 size_t blen = 2 + 2 + tlen;
220 if (blen >
sizeof(body))
223 put_u16(body + n, packet_id);
225 n += put_field(body + n, (
const uint8_t *)topic, tlen);
226 return compose(out, cap, (uint8_t)(((uint8_t)MqttType::MQTT_UNSUBSCRIBE << 4) | 0x02), body,
230size_t mqtt_build_ack(uint8_t *out,
size_t cap, MqttType type, uint16_t packet_id)
234 uint8_t f = (type == MqttType::MQTT_PUBREL) ? 0x02 : 0x00;
235 out[0] = (uint8_t)(((uint8_t)type << 4) | f);
237 put_u16(out + 2, packet_id);
241size_t mqtt_build_pingreq(uint8_t *out,
size_t cap)
245 out[0] = (uint8_t)((uint8_t)MqttType::MQTT_PINGREQ << 4);
250size_t mqtt_build_disconnect(uint8_t *out,
size_t cap)
254 out[0] = (uint8_t)((uint8_t)MqttType::MQTT_DISCONNECT << 4);
259bool mqtt_parse_fixed_header(
const uint8_t *buf,
size_t avail, uint8_t *type, uint8_t *flags, uint32_t *remaining_len,
266 if (!mqtt_decode_remlen(buf + 1, avail - 1, &rl, &used))
268 *type = (uint8_t)(buf[0] >> 4);
269 *flags = (uint8_t)(buf[0] & 0x0F);
271 *header_len = 1 + used;
275bool mqtt_parse_publish(
const uint8_t *buf, uint32_t remaining_len, uint8_t flags,
char *topic_out,
size_t topic_cap,
276 size_t *topic_len,
const uint8_t **payload,
size_t *payload_len, uint16_t *packet_id)
278 if (!buf || remaining_len < 2)
280 uint16_t tlen = get_u16(buf);
282 if ((uint32_t)off + tlen > remaining_len)
284 if ((
size_t)tlen + 1 > topic_cap)
287 if (!
det_utf8_valid(buf + off, tlen) || memchr(buf + off, 0x00, tlen))
289 memcpy(topic_out, buf + off, tlen);
290 topic_out[tlen] =
'\0';
294 uint8_t qos = (uint8_t)((flags >> 1) & 0x03);
300 if ((uint32_t)off + 2 > remaining_len)
302 *packet_id = get_u16(buf + off);
305 *payload = buf + off;
306 *payload_len = remaining_len - off;
310uint16_t mqtt_parse_ack(
const uint8_t *buf, uint32_t remaining_len)
312 if (!buf || remaining_len < 2)
317int mqtt_parse_connack(
const uint8_t *buf, uint32_t remaining_len,
bool *session_present)
319 if (!buf || remaining_len < 2)
322 *session_present = (buf[0] & 0x01) != 0;
326bool mqtt_parse_suback(
const uint8_t *buf, uint32_t remaining_len, uint16_t *packet_id, uint8_t *return_code)
328 if (!buf || remaining_len < 3)
331 *packet_id = get_u16(buf);
333 *return_code = buf[2];
346#if DETWS_ENABLE_MQTT_TLS
348#include <mbedtls/ssl.h>
351#ifdef DETWS_MQTT_DEBUG
352#define MQ_DBG(...) printf(__VA_ARGS__)
354#define MQ_DBG(...) ((void)0)
373 volatile bool closed;
379 volatile size_t rx_head;
380 volatile size_t rx_tail;
387 uint16_t keepalive_s;
390 uint32_t ping_sent_ms;
391 uint16_t next_pid = 1;
398static MqttCtx s_mqtt;
400static uint16_t next_pid()
402 uint16_t p = s_mqtt.next_pid++;
403 if (s_mqtt.next_pid == 0)
409static inline size_t ring_avail()
411 return (s_mqtt.rx_head +
sizeof(s_mqtt.rx) - s_mqtt.rx_tail) %
sizeof(s_mqtt.rx);
413static inline uint8_t ring_peek(
size_t i)
415 return s_mqtt.rx[(s_mqtt.rx_tail + i) %
sizeof(s_mqtt.rx)];
417static void ring_copy(uint8_t *dst,
size_t n)
419 for (
size_t i = 0; i < n; i++)
420 dst[i] = s_mqtt.rx[(s_mqtt.rx_tail + i) %
sizeof(s_mqtt.rx)];
422static inline void ring_advance(
size_t n)
424 s_mqtt.rx_tail = (s_mqtt.rx_tail + n) %
sizeof(s_mqtt.rx);
430static bool mq_tx_plain(
const uint8_t *data,
size_t len)
438static void mq_pump_plain()
443 size_t freey = (
sizeof(s_mqtt.rx) - 1) - ring_avail();
446 size_t want = freey <
sizeof(tmp) ? freey : sizeof(tmp);
451 s_mqtt.closed =
true;
454 for (
size_t i = 0; i < n; i++)
456 s_mqtt.rx[s_mqtt.rx_head] = tmp[i];
457 s_mqtt.rx_head = (s_mqtt.rx_head + 1) %
sizeof(s_mqtt.rx);
462#if DETWS_ENABLE_MQTT_TLS
465static int mq_tls_send(
void *ctx,
const unsigned char *buf,
size_t len)
468 size_t cap = len > 0xFFFF ? 0xFFFF : len;
469 return det_client_send(s_mqtt.cid, buf, cap) ? (int)cap : MBEDTLS_ERR_SSL_WANT_WRITE;
471static int mq_tls_recv(
void *ctx,
unsigned char *buf,
size_t len)
480static void mq_pump_tls()
485 size_t freey = (
sizeof(s_mqtt.rx) - 1) - ring_avail();
488 size_t want = freey <
sizeof(tmp) ? freey : sizeof(tmp);
489 int n = det_tls_csess_read(tmp, want);
493 s_mqtt.closed =
true;
496 for (
int i = 0; i < n; i++)
498 s_mqtt.rx[s_mqtt.rx_head] = tmp[i];
499 s_mqtt.rx_head = (s_mqtt.rx_head + 1) %
sizeof(s_mqtt.rx);
506static bool mq_tx(
const uint8_t *data,
size_t len)
509#if DETWS_ENABLE_MQTT_TLS
511 ok = det_tls_csess_write(data, len) == (int)len;
514 ok = mq_tx_plain(data, len);
516 s_mqtt.last_tx_ms = millis();
520static void mq_close()
522#if DETWS_ENABLE_MQTT_TLS
529 s_mqtt.mqtt_up =
false;
532static int inflight_find(uint16_t pid)
535 if (s_mqtt.inflight[i].state != 0 && s_mqtt.inflight[i].pid == pid)
540static void rxqos2_add(uint16_t pid)
543 if (s_mqtt.rx_qos2[i] == 0)
545 s_mqtt.rx_qos2[i] = pid;
549static bool rxqos2_has(uint16_t pid)
552 if (s_mqtt.rx_qos2[i] == pid)
556static void rxqos2_del(uint16_t pid)
559 if (s_mqtt.rx_qos2[i] == pid)
560 s_mqtt.rx_qos2[i] = 0;
564static void handle_packet(uint8_t type, uint8_t flags,
const uint8_t *body, uint32_t rl)
566 switch ((MqttType)type)
568 case MqttType::MQTT_CONNACK:
569 s_mqtt.connack_code = mqtt_parse_connack(body, rl,
nullptr);
570 if (s_mqtt.connack_code == 0)
571 s_mqtt.mqtt_up =
true;
572 MQ_DBG(
"[mqtt] CONNACK code=%d\n", s_mqtt.connack_code);
574 case MqttType::MQTT_PUBLISH: {
578 const uint8_t *payload;
580 if (!mqtt_parse_publish(body, rl, flags, topic,
sizeof(topic), &tlen, &payload, &plen, &pid))
585 uint8_t qos = (uint8_t)((flags >> 1) & 0x03);
589 s_mqtt.cb(topic, payload, plen);
592 size_t n = mqtt_build_ack(s_mqtt.tx,
sizeof(s_mqtt.tx), MqttType::MQTT_PUBACK, pid);
598 if (!rxqos2_has(pid))
601 s_mqtt.cb(topic, payload, plen);
604 size_t n = mqtt_build_ack(s_mqtt.tx,
sizeof(s_mqtt.tx), MqttType::MQTT_PUBREC, pid);
609 case MqttType::MQTT_PUBACK:
610 case MqttType::MQTT_PUBCOMP:
612 int s = inflight_find(mqtt_parse_ack(body, rl));
614 s_mqtt.inflight[s].state = 0;
617 case MqttType::MQTT_PUBREC:
619 uint16_t pid = mqtt_parse_ack(body, rl);
620 int s = inflight_find(pid);
623 s_mqtt.inflight[s].state = 2;
624 s_mqtt.inflight[s].sent_ms = millis();
626 size_t n = mqtt_build_ack(s_mqtt.tx,
sizeof(s_mqtt.tx), MqttType::MQTT_PUBREL, pid);
630 case MqttType::MQTT_PUBREL:
632 uint16_t pid = mqtt_parse_ack(body, rl);
634 size_t n = mqtt_build_ack(s_mqtt.tx,
sizeof(s_mqtt.tx), MqttType::MQTT_PUBCOMP, pid);
638 case MqttType::MQTT_PINGRESP:
639 s_mqtt.ping_pending =
false;
641 case MqttType::MQTT_SUBACK:
642 case MqttType::MQTT_UNSUBACK:
649static void process_rx()
651#if DETWS_ENABLE_MQTT_TLS
659 size_t avail = ring_avail();
664 size_t hn = avail < 5 ? avail : 5;
665 for (
size_t i = 0; i < hn; i++)
666 hdr[i] = ring_peek(i);
671 if (!mqtt_parse_fixed_header(hdr, hn, &type, &flags, &rl, &hl))
673 size_t total = hl + rl;
676 if (total >
sizeof(s_mqtt.pkt))
681 ring_copy(s_mqtt.pkt, total);
683 handle_packet(type, flags, s_mqtt.pkt + hl, rl);
687void mqtt_on_message(MqttMessageCb cb)
692bool mqtt_connect(
const char *host, uint16_t port,
bool use_tls,
const MqttConnectOpts *opts)
696#if !DETWS_ENABLE_MQTT_TLS
702 memset(s_mqtt.inflight, 0,
sizeof(s_mqtt.inflight));
703 memset(s_mqtt.rx_qos2, 0,
sizeof(s_mqtt.rx_qos2));
704 s_mqtt.rx_head = s_mqtt.rx_tail = 0;
705 s_mqtt.closed = s_mqtt.mqtt_up = s_mqtt.ping_pending =
false;
706 s_mqtt.connack_code = -1;
707 s_mqtt.keepalive_s = opts->keepalive_s;
708 s_mqtt.use_tls = use_tls;
710 uint32_t deadline = millis() + 8000;
717#if DETWS_ENABLE_MQTT_TLS
720 if (!det_tls_csess_begin(host, mq_tls_send, mq_tls_recv))
726 while ((h = det_tls_csess_handshake()) == 0 && !s_mqtt.closed && (int32_t)(deadline - millis()) > 0)
730 MQ_DBG(
"[mqtt] TLS handshake failed (%d)\n", h);
737 size_t n = mqtt_build_connect(s_mqtt.tx,
sizeof(s_mqtt.tx), opts);
738 if (n == 0 || !mq_tx(s_mqtt.tx, n))
745 while (!s_mqtt.mqtt_up && s_mqtt.connack_code < 0 && !s_mqtt.closed && (int32_t)(deadline - millis()) > 0)
755 s_mqtt.last_tx_ms = millis();
759bool mqtt_publish(
const char *topic,
const uint8_t *payload,
size_t len, uint8_t qos,
bool retain)
761 if (!s_mqtt.mqtt_up || qos > 2)
765 size_t n = mqtt_build_publish(s_mqtt.tx,
sizeof(s_mqtt.tx), topic, payload, len, 0, 0, retain,
false);
766 return n && mq_tx(s_mqtt.tx, n);
769 int slot = inflight_find(0);
772 if (s_mqtt.inflight[i].state == 0)
779 uint16_t pid = next_pid();
780 size_t n = mqtt_build_publish(s_mqtt.inflight[slot].pkt,
sizeof(s_mqtt.inflight[slot].pkt), topic, payload, len,
781 qos, pid, retain,
false);
784 s_mqtt.inflight[slot].pid = pid;
785 s_mqtt.inflight[slot].state = 1;
786 s_mqtt.inflight[slot].len = (uint16_t)n;
787 s_mqtt.inflight[slot].sent_ms = millis();
788 return mq_tx(s_mqtt.inflight[slot].pkt, n);
791bool mqtt_subscribe(
const char *topic, uint8_t qos)
795 size_t n = mqtt_build_subscribe(s_mqtt.tx,
sizeof(s_mqtt.tx), next_pid(), topic, qos);
796 return n && mq_tx(s_mqtt.tx, n);
799bool mqtt_unsubscribe(
const char *topic)
803 size_t n = mqtt_build_unsubscribe(s_mqtt.tx,
sizeof(s_mqtt.tx), next_pid(), topic);
804 return n && mq_tx(s_mqtt.tx, n);
818 uint32_t now = millis();
821 if (s_mqtt.keepalive_s)
823 uint32_t ka = (uint32_t)s_mqtt.keepalive_s * 1000u;
824 if (s_mqtt.ping_pending && (now - s_mqtt.ping_sent_ms) > ka)
829 if (!s_mqtt.ping_pending && (now - s_mqtt.last_tx_ms) >= ka)
831 size_t n = mqtt_build_pingreq(s_mqtt.tx,
sizeof(s_mqtt.tx));
832 if (mq_tx(s_mqtt.tx, n))
834 s_mqtt.ping_pending =
true;
835 s_mqtt.ping_sent_ms = now;
843 if (s_mqtt.inflight[i].state == 0)
847 if (s_mqtt.inflight[i].state == 1)
849 s_mqtt.inflight[i].pkt[0] |= 0x08;
850 mq_tx(s_mqtt.inflight[i].pkt, s_mqtt.inflight[i].len);
854 size_t n = mqtt_build_ack(s_mqtt.tx,
sizeof(s_mqtt.tx), MqttType::MQTT_PUBREL, s_mqtt.inflight[i].pid);
857 s_mqtt.inflight[i].sent_ms = now;
864 return s_mqtt.mqtt_up;
867void mqtt_disconnect()
869 if (s_mqtt.cid >= 0 && s_mqtt.mqtt_up)
871 size_t n = mqtt_build_disconnect(s_mqtt.tx,
sizeof(s_mqtt.tx));
879void mqtt_on_message(MqttMessageCb)
882bool mqtt_connect(
const char *, uint16_t,
bool,
const MqttConnectOpts *)
886bool mqtt_publish(
const char *,
const uint8_t *,
size_t, uint8_t,
bool)
890bool mqtt_subscribe(
const char *, uint8_t)
894bool mqtt_unsubscribe(
const char *)
906void mqtt_disconnect()
#define DETWS_MQTT_RX_QOS2_SLOTS
Inbound QoS 2 packet-id de-duplication ring depth (PUBREC-acknowledged, awaiting PUBREL).
#define DETWS_MQTT_BUF_SIZE
MQTT packet buffer size in bytes (bounds one outgoing/incoming packet).
#define DETWS_MQTT_INFLIGHT_BUF
Stored-packet size per in-flight QoS 1/2 slot (caps a retransmittable PUBLISH).
#define DETWS_MQTT_RETRANSMIT_MS
Retransmit timeout (ms) for an unacknowledged in-flight QoS 1/2 message.
#define DETWS_MQTT_MAX_INFLIGHT
Outbound QoS 1/2 in-flight slots (unacknowledged messages held for DUP retransmit).
#define DETWS_MQTT_MAX_TOPIC
Maximum inbound MQTT topic length (including NUL) delivered to the callback.
bool det_client_is_closed(int)
True once the peer closed (FIN) or the connection errored.
int det_client_open(const char *, uint16_t, uint32_t)
Resolve host (dotted-quad fast path, else DNS) and connect to host : port, blocking up to timeout_ms.
size_t det_client_read(int, uint8_t *, size_t)
Drain up to cap buffered wire bytes into buf; returns the count.
void det_client_close(int)
Tear down the connection (marshaled) and return the slot to the pool.
bool det_client_send(int, const void *, size_t)
Queue len wire bytes for transmission (marshaled tcp_write + output).
Layer 4 outbound TCP client transport - the client-side peer of the (server) transport in tcp....
Zero-heap MQTT 3.1.1 publish/subscribe client (DETWS_ENABLE_MQTT).
Deterministic TLS engine: mbedTLS over a static memory pool (DETWS_ENABLE_TLS).
Strict UTF-8 validation (RFC 3629), one shared copy.
bool det_utf8_valid(const uint8_t *s, size_t n)
True if [s, s+n) is well-formed UTF-8.