27#if defined(ARDUINO) && PC_ENABLE_MQTT_TLS
29#include <mbedtls/ssl.h>
31static inline void put_u16(uint8_t *p, uint16_t v)
33 p[0] = (uint8_t)(v >> 8);
34 p[1] = (uint8_t)(v & 0xFF);
36static inline uint16_t get_u16(
const uint8_t *p)
38 return (uint16_t)((p[0] << 8) | p[1]);
41static size_t put_field(uint8_t *p,
const uint8_t *data,
size_t len)
43 put_u16(p, (uint16_t)len);
46 memcpy(p + 2, data, len);
50static inline size_t put_str(uint8_t *p,
const char *s)
52 return put_field(p, (
const uint8_t *)s,
57size_t pc_mqtt_encode_remlen(uint8_t *out, uint32_t len)
66 uint8_t
byte = (uint8_t)(len % 128);
77bool pc_mqtt_decode_remlen(
const uint8_t *buf,
size_t avail, uint32_t *value,
size_t *used)
89 v += (uint32_t)(b & 0x7F) * mult;
107static size_t compose(uint8_t *out,
size_t cap, uint8_t byte0,
const uint8_t *body,
size_t blen)
113 size_t rln = pc_mqtt_encode_remlen(rl, (uint32_t)blen);
119 size_t total = 1 + rln + blen;
125 memcpy(out + 1, rl, rln);
129 memcpy(out + 1 + rln, body, blen);
134size_t pc_mqtt_build_connect(uint8_t *out,
size_t cap,
const MqttConnectOpts *opts)
136 if (!out || !opts || !opts->client_id)
143 n += put_str(body + n,
"MQTT");
147 if (opts->clean_session)
151 if (opts->will_topic)
154 flags |= (uint8_t)((opts->will_qos & 0x03) << 3);
155 if (opts->will_retain)
169 put_u16(body + n, opts->keepalive_s);
175 if (opts->will_topic)
177 need += 2 + strnlen(opts->will_topic,
PC_MQTT_BUF_SIZE) + 2 + opts->will_len;
187 if (n + need >
sizeof(body))
192 n += put_str(body + n, opts->client_id);
193 if (opts->will_topic)
195 n += put_str(body + n, opts->will_topic);
196 n += put_field(body + n, opts->will_msg, opts->will_len);
200 n += put_str(body + n, opts->user);
204 n += put_str(body + n, opts->pass);
207 return compose(out, cap, (uint8_t)((uint8_t)MqttType::MQTT_CONNECT << 4), body, n);
210size_t pc_mqtt_build_publish(uint8_t *out,
size_t cap,
const char *topic,
const uint8_t *payload,
size_t payload_len,
211 uint8_t qos, uint16_t packet_id,
bool retain,
bool dup)
213 if (!out || !topic || qos > 2)
219 for (
const char *t = topic; *t; t++)
221 if (*t ==
'+' || *t ==
'#')
227 size_t blen = 2 + tlen + (qos > 0 ? 2 : 0) + payload_len;
229 if (blen >
sizeof(body))
234 n += put_field(body + n, (
const uint8_t *)topic, tlen);
237 put_u16(body + n, packet_id);
242 memcpy(body + n, payload, payload_len);
246 uint8_t f = (uint8_t)((qos & 0x03) << 1);
255 return compose(out, cap, (uint8_t)(((uint8_t)MqttType::MQTT_PUBLISH << 4) | f), body, n);
258size_t pc_mqtt_build_subscribe(uint8_t *out,
size_t cap, uint16_t packet_id,
const char *topic, uint8_t qos)
260 if (!out || !topic || qos > 2)
266 size_t blen = 2 + 2 + tlen + 1;
267 if (blen >
sizeof(body))
272 put_u16(body + n, packet_id);
274 n += put_field(body + n, (
const uint8_t *)topic, tlen);
275 body[n++] = (uint8_t)(qos & 0x03);
276 return compose(out, cap, (uint8_t)(((uint8_t)MqttType::MQTT_SUBSCRIBE << 4) | 0x02), body,
280size_t pc_mqtt_build_unsubscribe(uint8_t *out,
size_t cap, uint16_t packet_id,
const char *topic)
288 size_t blen = 2 + 2 + tlen;
289 if (blen >
sizeof(body))
294 put_u16(body + n, packet_id);
296 n += put_field(body + n, (
const uint8_t *)topic, tlen);
297 return compose(out, cap, (uint8_t)(((uint8_t)MqttType::MQTT_UNSUBSCRIBE << 4) | 0x02), body,
301size_t pc_mqtt_build_ack(uint8_t *out,
size_t cap, MqttType type, uint16_t packet_id)
307 uint8_t f = (type == MqttType::MQTT_PUBREL) ? 0x02 : 0x00;
308 out[0] = (uint8_t)(((uint8_t)type << 4) | f);
310 put_u16(out + 2, packet_id);
314size_t pc_mqtt_build_pingreq(uint8_t *out,
size_t cap)
320 out[0] = (uint8_t)((uint8_t)MqttType::MQTT_PINGREQ << 4);
325size_t pc_mqtt_build_disconnect(uint8_t *out,
size_t cap)
331 out[0] = (uint8_t)((uint8_t)MqttType::MQTT_DISCONNECT << 4);
336bool pc_mqtt_parse_fixed_header(
const uint8_t *buf,
size_t avail, uint8_t *type, uint8_t *flags,
337 uint32_t *remaining_len,
size_t *header_len)
345 if (!pc_mqtt_decode_remlen(buf + 1, avail - 1, &rl, &used))
349 *type = (uint8_t)(buf[0] >> 4);
350 *flags = (uint8_t)(buf[0] & 0x0F);
352 *header_len = 1 + used;
356bool pc_mqtt_parse_publish(
const uint8_t *buf, uint32_t remaining_len, uint8_t flags,
char *topic_out,
size_t topic_cap,
357 size_t *topic_len,
const uint8_t **payload,
size_t *payload_len, uint16_t *packet_id)
359 if (!buf || remaining_len < 2)
363 uint16_t tlen = get_u16(buf);
365 if ((uint32_t)off + tlen > remaining_len)
369 if ((
size_t)tlen + 1 > topic_cap)
374 if (!
pc_utf8_valid(buf + off, tlen) || memchr(buf + off, 0x00, tlen))
378 memcpy(topic_out, buf + off, tlen);
379 topic_out[tlen] =
'\0';
383 uint8_t qos = (uint8_t)((flags >> 1) & 0x03);
391 if ((uint32_t)off + 2 > remaining_len)
395 *packet_id = get_u16(buf + off);
398 *payload = buf + off;
399 *payload_len = remaining_len - off;
403uint16_t pc_mqtt_parse_ack(
const uint8_t *buf, uint32_t remaining_len)
405 if (!buf || remaining_len < 2)
412int pc_mqtt_parse_connack(
const uint8_t *buf, uint32_t remaining_len,
bool *session_present)
414 if (!buf || remaining_len < 2)
420 *session_present = (buf[0] & 0x01) != 0;
425bool pc_mqtt_parse_suback(
const uint8_t *buf, uint32_t remaining_len, uint16_t *packet_id, uint8_t *return_code)
427 if (!buf || remaining_len < 3)
433 *packet_id = get_u16(buf);
437 *return_code = buf[2];
449#define MQ_DBG(...) printf(__VA_ARGS__)
451#define MQ_DBG(...) ((void)0)
470 volatile bool closed;
476 volatile size_t rx_head;
477 volatile size_t rx_tail;
484 uint16_t keepalive_s;
487 uint32_t ping_sent_ms;
488 uint16_t next_pid = 1;
495static MqttCtx s_mqtt;
497static uint16_t next_pid()
499 uint16_t p = s_mqtt.next_pid++;
500 if (s_mqtt.next_pid == 0)
508static inline size_t ring_avail()
510 return (s_mqtt.rx_head +
sizeof(s_mqtt.rx) - s_mqtt.rx_tail) %
sizeof(s_mqtt.rx);
512static inline uint8_t ring_peek(
size_t i)
514 return s_mqtt.rx[(s_mqtt.rx_tail + i) %
sizeof(s_mqtt.rx)];
516static void ring_copy(uint8_t *dst,
size_t n)
518 for (
size_t i = 0; i < n; i++)
520 dst[i] = s_mqtt.rx[(s_mqtt.rx_tail + i) %
sizeof(s_mqtt.rx)];
523static inline void ring_advance(
size_t n)
525 s_mqtt.rx_tail = (s_mqtt.rx_tail + n) %
sizeof(s_mqtt.rx);
531static bool mq_tx_plain(
const uint8_t *data,
size_t len)
539static void mq_pump_plain()
544 size_t freey = (
sizeof(s_mqtt.rx) - 1) - ring_avail();
549 size_t want = freey <
sizeof(tmp) ? freey : sizeof(tmp);
555 s_mqtt.closed =
true;
559 for (
size_t i = 0; i < n; i++)
561 s_mqtt.rx[s_mqtt.rx_head] = tmp[i];
562 s_mqtt.rx_head = (s_mqtt.rx_head + 1) %
sizeof(s_mqtt.rx);
567#if PC_ENABLE_MQTT_TLS
570static int mq_tls_send(
void *ctx,
const unsigned char *buf,
size_t len)
573 size_t cap = len > 0xFFFF ? 0xFFFF : len;
574 return pc_client_send(s_mqtt.cid, buf, cap) ? (int)cap : MBEDTLS_ERR_SSL_WANT_WRITE;
576static int mq_tls_recv(
void *ctx,
unsigned char *buf,
size_t len)
587static void mq_pump_tls()
592 size_t freey = (
sizeof(s_mqtt.rx) - 1) - ring_avail();
597 size_t want = freey <
sizeof(tmp) ? freey : sizeof(tmp);
598 int n = pc_tls_client_session_read(tmp, want);
603 s_mqtt.closed =
true;
607 for (
int i = 0; i < n; i++)
609 s_mqtt.rx[s_mqtt.rx_head] = tmp[i];
610 s_mqtt.rx_head = (s_mqtt.rx_head + 1) %
sizeof(s_mqtt.rx);
617static bool mq_tx(
const uint8_t *data,
size_t len)
620#if PC_ENABLE_MQTT_TLS
623 ok = pc_tls_client_session_write(data, len) == (int)len;
627 ok = mq_tx_plain(data, len);
630 s_mqtt.last_tx_ms = millis();
635static void mq_close()
637#if PC_ENABLE_MQTT_TLS
640 pc_tls_client_session_end();
648 s_mqtt.mqtt_up =
false;
651static int inflight_find(uint16_t pid)
655 if (s_mqtt.inflight[i].state != 0 && s_mqtt.inflight[i].pid == pid)
663static void rxqos2_add(uint16_t pid)
667 if (s_mqtt.rx_qos2[i] == 0)
669 s_mqtt.rx_qos2[i] = pid;
674static bool rxqos2_has(uint16_t pid)
678 if (s_mqtt.rx_qos2[i] == pid)
685static void rxqos2_del(uint16_t pid)
689 if (s_mqtt.rx_qos2[i] == pid)
691 s_mqtt.rx_qos2[i] = 0;
697static void handle_packet(uint8_t type, uint8_t flags,
const uint8_t *body, uint32_t rl)
699 switch ((MqttType)type)
701 case MqttType::MQTT_CONNACK:
702 s_mqtt.connack_code = pc_mqtt_parse_connack(body, rl,
nullptr);
703 if (s_mqtt.connack_code == 0)
705 s_mqtt.mqtt_up =
true;
707 MQ_DBG(
"[mqtt] CONNACK code=%d\n", s_mqtt.connack_code);
709 case MqttType::MQTT_PUBLISH: {
713 const uint8_t *payload;
715 if (!pc_mqtt_parse_publish(body, rl, flags, topic,
sizeof(topic), &tlen, &payload, &plen, &pid))
720 uint8_t qos = (uint8_t)((flags >> 1) & 0x03);
725 s_mqtt.cb(topic, payload, plen);
729 size_t n = pc_mqtt_build_ack(s_mqtt.tx,
sizeof(s_mqtt.tx), MqttType::MQTT_PUBACK, pid);
735 if (!rxqos2_has(pid))
739 s_mqtt.cb(topic, payload, plen);
743 size_t n = pc_mqtt_build_ack(s_mqtt.tx,
sizeof(s_mqtt.tx), MqttType::MQTT_PUBREC, pid);
748 case MqttType::MQTT_PUBACK:
749 case MqttType::MQTT_PUBCOMP:
751 int s = inflight_find(pc_mqtt_parse_ack(body, rl));
754 s_mqtt.inflight[s].state = 0;
758 case MqttType::MQTT_PUBREC:
760 uint16_t pid = pc_mqtt_parse_ack(body, rl);
761 int s = inflight_find(pid);
764 s_mqtt.inflight[s].state = 2;
765 s_mqtt.inflight[s].sent_ms = millis();
767 size_t n = pc_mqtt_build_ack(s_mqtt.tx,
sizeof(s_mqtt.tx), MqttType::MQTT_PUBREL, pid);
771 case MqttType::MQTT_PUBREL:
773 uint16_t pid = pc_mqtt_parse_ack(body, rl);
775 size_t n = pc_mqtt_build_ack(s_mqtt.tx,
sizeof(s_mqtt.tx), MqttType::MQTT_PUBCOMP, pid);
779 case MqttType::MQTT_PINGRESP:
780 s_mqtt.ping_pending =
false;
782 case MqttType::MQTT_SUBACK:
783 case MqttType::MQTT_UNSUBACK:
790static void process_rx()
792#if PC_ENABLE_MQTT_TLS
802 size_t avail = ring_avail();
809 size_t hn = avail < 5 ? avail : 5;
810 for (
size_t i = 0; i < hn; i++)
812 hdr[i] = ring_peek(i);
818 if (!pc_mqtt_parse_fixed_header(hdr, hn, &type, &flags, &rl, &hl))
822 size_t total = hl + rl;
827 if (total >
sizeof(s_mqtt.pkt))
832 ring_copy(s_mqtt.pkt, total);
834 handle_packet(type, flags, s_mqtt.pkt + hl, rl);
838void pc_mqtt_set_message_cb(MqttMessageCb cb)
843bool pc_mqtt_connect(
const char *host, uint16_t port,
bool use_tls,
const MqttConnectOpts *opts)
849#if !PC_ENABLE_MQTT_TLS
857 memset(s_mqtt.inflight, 0,
sizeof(s_mqtt.inflight));
858 memset(s_mqtt.rx_qos2, 0,
sizeof(s_mqtt.rx_qos2));
859 s_mqtt.rx_head = s_mqtt.rx_tail = 0;
860 s_mqtt.closed = s_mqtt.mqtt_up = s_mqtt.ping_pending =
false;
861 s_mqtt.connack_code = -1;
862 s_mqtt.keepalive_s = opts->keepalive_s;
863 s_mqtt.use_tls = use_tls;
865 uint32_t deadline = millis() + 8000;
874#if PC_ENABLE_MQTT_TLS
877 if (!pc_tls_client_session_begin(host, mq_tls_send, mq_tls_recv))
883 while ((h = pc_tls_client_session_handshake()) == 0 && !s_mqtt.closed && (int32_t)(deadline - millis()) > 0)
889 MQ_DBG(
"[mqtt] TLS handshake failed (%d)\n", h);
896 size_t n = pc_mqtt_build_connect(s_mqtt.tx,
sizeof(s_mqtt.tx), opts);
897 if (n == 0 || !mq_tx(s_mqtt.tx, n))
904 while (!s_mqtt.mqtt_up && s_mqtt.connack_code < 0 && !s_mqtt.closed && (int32_t)(deadline - millis()) > 0)
914 s_mqtt.last_tx_ms = millis();
918bool pc_mqtt_publish(
const char *topic,
const uint8_t *payload,
size_t len, uint8_t qos,
bool retain)
920 if (!s_mqtt.mqtt_up || qos > 2)
926 size_t n = pc_mqtt_build_publish(s_mqtt.tx,
sizeof(s_mqtt.tx), topic, payload, len, 0, 0, retain,
false);
927 return n && mq_tx(s_mqtt.tx, n);
930 int slot = inflight_find(0);
935 if (s_mqtt.inflight[i].state == 0)
946 uint16_t pid = next_pid();
947 size_t n = pc_mqtt_build_publish(s_mqtt.inflight[slot].pkt,
sizeof(s_mqtt.inflight[slot].pkt), topic, payload, len,
948 qos, pid, retain,
false);
953 s_mqtt.inflight[slot].pid = pid;
954 s_mqtt.inflight[slot].state = 1;
955 s_mqtt.inflight[slot].len = (uint16_t)n;
956 s_mqtt.inflight[slot].sent_ms = millis();
957 return mq_tx(s_mqtt.inflight[slot].pkt, n);
960bool pc_mqtt_subscribe(
const char *topic, uint8_t qos)
966 size_t n = pc_mqtt_build_subscribe(s_mqtt.tx,
sizeof(s_mqtt.tx), next_pid(), topic, qos);
967 return n && mq_tx(s_mqtt.tx, n);
970bool pc_mqtt_unsubscribe(
const char *topic)
976 size_t n = pc_mqtt_build_unsubscribe(s_mqtt.tx,
sizeof(s_mqtt.tx), next_pid(), topic);
977 return n && mq_tx(s_mqtt.tx, n);
993 uint32_t now = millis();
996 if (s_mqtt.keepalive_s)
998 uint32_t ka = (uint32_t)s_mqtt.keepalive_s * 1000u;
999 if (s_mqtt.ping_pending && (now - s_mqtt.ping_sent_ms) > ka)
1004 if (!s_mqtt.ping_pending && (now - s_mqtt.last_tx_ms) >= ka)
1006 size_t n = pc_mqtt_build_pingreq(s_mqtt.tx,
sizeof(s_mqtt.tx));
1007 if (mq_tx(s_mqtt.tx, n))
1009 s_mqtt.ping_pending =
true;
1010 s_mqtt.ping_sent_ms = now;
1018 if (s_mqtt.inflight[i].state == 0)
1026 if (s_mqtt.inflight[i].state == 1)
1028 s_mqtt.inflight[i].pkt[0] |= 0x08;
1029 mq_tx(s_mqtt.inflight[i].pkt, s_mqtt.inflight[i].len);
1033 size_t n = pc_mqtt_build_ack(s_mqtt.tx,
sizeof(s_mqtt.tx), MqttType::MQTT_PUBREL, s_mqtt.inflight[i].pid);
1034 mq_tx(s_mqtt.tx, n);
1036 s_mqtt.inflight[i].sent_ms = now;
1041bool pc_mqtt_connected()
1043 return s_mqtt.mqtt_up;
1046void pc_mqtt_disconnect()
1048 if (s_mqtt.cid >= 0 && s_mqtt.mqtt_up)
1050 size_t n = pc_mqtt_build_disconnect(s_mqtt.tx,
sizeof(s_mqtt.tx));
1051 mq_tx(s_mqtt.tx, n);
1058void pc_mqtt_set_message_cb(MqttMessageCb)
1061bool pc_mqtt_connect(
const char *, uint16_t,
bool,
const MqttConnectOpts *)
1065bool pc_mqtt_publish(
const char *,
const uint8_t *,
size_t, uint8_t,
bool)
1069bool pc_mqtt_subscribe(
const char *, uint8_t)
1073bool pc_mqtt_unsubscribe(
const char *)
1081bool pc_mqtt_connected()
1085void pc_mqtt_disconnect()
bool pc_client_send(int, const void *, size_t)
Queue len wire bytes for transmission (marshaled tcp_write + output).
size_t pc_client_read(int, uint8_t *, size_t)
Drain up to cap buffered wire bytes into buf; returns the count.
bool pc_client_is_closed(int)
True once the peer closed (FIN) or the connection errored.
int pc_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.
void pc_client_close(int)
Tear down the connection (marshaled) and return the slot to the pool.
Layer 4 outbound TCP client transport - the client-side peer of the (server) transport in tcp....
Pluggable monotonic clock for all library timing.
void pcdelay(uint32_t ms)
Block for at least ms milliseconds - the library's single delay primitive.
Zero-heap MQTT 3.1.1 publish/subscribe client (PC_ENABLE_MQTT).
#define PC_MQTT_BUF_SIZE
MQTT packet buffer size in bytes (bounds one outgoing/incoming packet).
#define PC_MQTT_MAX_INFLIGHT
Outbound QoS 1/2 in-flight slots (unacknowledged messages held for DUP retransmit).
#define PC_MQTT_RX_QOS2_SLOTS
Inbound QoS 2 packet-id de-duplication ring depth (PUBREC-acknowledged, awaiting PUBREL).
#define PC_MQTT_RETRANSMIT_MS
Retransmit timeout (ms) for an unacknowledged in-flight QoS 1/2 message.
#define PC_MQTT_INFLIGHT_BUF
Stored-packet size per in-flight QoS 1/2 slot (caps a retransmittable PUBLISH).
#define PC_MQTT_MAX_TOPIC
Maximum inbound MQTT topic length (including NUL) delivered to the callback.
Deterministic TLS engine: mbedTLS over a static memory pool (PC_ENABLE_TLS).
Strict UTF-8 validation (RFC 3629), one shared copy.
bool pc_utf8_valid(const uint8_t *s, size_t n)
True if [s, s+n) is well-formed UTF-8.