From 50d3004edd03a772f4b578ee3b2e9e8b358fd740 Mon Sep 17 00:00:00 2001 From: David Garske Date: Fri, 1 Oct 2021 14:46:17 -0700 Subject: [PATCH 01/13] Template for new broker. --- configure.ac | 17 ++++++++++++++- src/include.am | 7 ++++++ src/mqtt_broker.c | 49 ++++++++++++++++++++++++++++++++++++++++++ src/mqtt_packet.c | 43 ++++++++++++++++++++++++++++++++++++ wolfmqtt/include.am | 1 + wolfmqtt/mqtt_broker.h | 38 ++++++++++++++++++++++++++++++++ wolfmqtt/mqtt_packet.h | 9 ++++++++ 7 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 src/mqtt_broker.c create mode 100644 wolfmqtt/mqtt_broker.h diff --git a/configure.ac b/configure.ac index 3a815eada..541831819 100644 --- a/configure.ac +++ b/configure.ac @@ -304,7 +304,7 @@ then [ ENABLED_PROPCB=$enableval ], [ ENABLED_PROPCB=yes ] ) - + if test "x$ENABLED_PROPCB" = "xyes" then AM_CFLAGS="$AM_CFLAGS -DWOLFMQTT_PROPERTY_CB" @@ -405,6 +405,19 @@ then [AC_MSG_ERROR([libwebsockets not found. Install libwebsockets or use --disable-websocket])]) fi +# Broker +AC_ARG_ENABLE([broker], +[AS_HELP_STRING([--enable-broker],[Enable lightweight broker support (default: disabled)])], +[ ENABLED_BROKER=$enableval ], +[ ENABLED_BROKER=no ] +) + +if test "x$ENABLED_BROKER" = "xyes" +then +AM_CFLAGS="$AM_CFLAGS -DWOLFMQTT_BROKER" +fi + + AM_CONDITIONAL([HAVE_LIBWOLFSSL], [test "x$ENABLED_TLS" = "xyes"]) AM_CONDITIONAL([HAVE_LIBCURL], [test "x$ENABLED_CURL" = "xyes"]) AM_CONDITIONAL([BUILD_STRESS], [test "x$ENABLED_STRESS" != "xno"]) @@ -415,6 +428,8 @@ AM_CONDITIONAL([BUILD_MQTT5], [test "x$ENABLED_MQTTV50" = "xyes"]) AM_CONDITIONAL([BUILD_NONBLOCK], [test "x$ENABLED_NONBLOCK" = "xyes"]) AM_CONDITIONAL([BUILD_MULTITHREAD], [test "x$ENABLED_MULTITHREAD" = "xyes"]) AM_CONDITIONAL([BUILD_WEBSOCKET], [test "x$ENABLED_WEBSOCKET" = "xyes"]) +AM_CONDITIONAL([BUILD_BROKER], [test "x$ENABLED_BROKER" = "xyes"]) + # HARDEN FLAGS diff --git a/src/include.am b/src/include.am index 08f9cd162..f31b4547a 100644 --- a/src/include.am +++ b/src/include.am @@ -19,3 +19,10 @@ src_libwolfmqtt_la_LDFLAGS = ${AM_LDFLAGS} -no-undefined -version-info ${WO src_libwolfmqtt_la_DEPENDENCIES = EXTRA_DIST += + +bin_PROGRAMS += src/mqtt_broker +src_mqtt_broker_SOURCES = src/mqtt_broker.c +src_mqtt_broker_CFLAGS = +src_mqtt_broker_LDFLAGS = -Lsrc +src_mqtt_broker_LDADD = src/libwolfmqtt.la $(LTLIBEVENT) $(LIB_STATIC_ADD) +src_mqtt_broker_DEPENDENCIES = src/libwolfmqtt.la diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c new file mode 100644 index 000000000..cf7ec178f --- /dev/null +++ b/src/mqtt_broker.c @@ -0,0 +1,49 @@ + +/* mqtt_broker.c + * + * Copyright (C) 2006-2021 wolfSSL Inc. + * + * This file is part of wolfMQTT. + * + * wolfMQTT is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfMQTT is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Include the autoconf generated config.h */ +#ifdef HAVE_CONFIG_H + #include +#endif + +#include "wolfmqtt/mqtt_broker.h" + +#ifdef WOLFMQTT_BROKER + +int wolfmqtt_broker(argc, argv) +{ + int ret = 0; + + (void)argc; + (void)argv; + + return ret; +} + +#ifndef NO_MAIN_DRIVER +int main(int argc, char** argv) +{ + return wolfmqtt_broker(argc, argv); +} +#endif + +#endif /* WOLFMQTT_BROKER */ diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index 2fb33db4d..a5ca8d5a4 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -852,6 +852,13 @@ int MqttEncode_Connect(byte *tx_buf, int tx_buf_len, MqttConnect *mc_connect) return header_len + remain_len; } +#ifdef WOLFMQTT_BROKER +int MqttDecode_Connect(byte *rx_buf, int rx_buf_len, MqttConnect *mc_connect) +{ + +} +#endif /* WOLFMQTT_BROKER */ + int MqttDecode_ConnectAck(byte *rx_buf, int rx_buf_len, MqttConnectAck *connect_ack) { @@ -911,6 +918,13 @@ int MqttDecode_ConnectAck(byte *rx_buf, int rx_buf_len, return header_len + remain_len; } +#ifdef WOLFMQTT_BROKER +int MqttEncode_ConnectAck(byte *tx_buf, int tx_buf_len MqttConnectAck *connect_ack) +{ + +} +#endif /* WOLFMQTT_BROKER */ + int MqttEncode_Publish(byte *tx_buf, int tx_buf_len, MqttPublish *publish, byte use_cb) { @@ -1370,6 +1384,13 @@ int MqttEncode_Subscribe(byte *tx_buf, int tx_buf_len, return header_len + remain_len; } +#ifdef WOLFMQTT_BROKER +int MqttDecode_Subscribe(byte *rx_buf, int rx_buf_len, MqttSubscribe *subscribe) +{ + +} +#endif /* WOLFMQTT_BROKER */ + int MqttDecode_SubscribeAck(byte* rx_buf, int rx_buf_len, MqttSubscribeAck *subscribe_ack) { @@ -1523,6 +1544,13 @@ int MqttEncode_Unsubscribe(byte *tx_buf, int tx_buf_len, return header_len + remain_len; } +#ifdef WOLFMQTT_BROKER +int MqttDecode_Unsubscribe(byte *rx_buf, int rx_buf_len, MqttUnsubscribe *unsubscribe) +{ + +} +#endif /* WOLFMQTT_BROKER */ + int MqttDecode_UnsubscribeAck(byte *rx_buf, int rx_buf_len, MqttUnsubscribeAck *unsubscribe_ack) { @@ -1596,6 +1624,13 @@ int MqttDecode_UnsubscribeAck(byte *rx_buf, int rx_buf_len, return header_len + remain_len; } +#ifdef WOLFMQTT_BROKER +int MqttEncode_UnsubscribeAck(byte *tx_buf, int tx_buf_len, MqttUnsubscribeAck *unsubscribe_ack) +{ + +} +#endif /* WOLFMQTT_BROKER */ + int MqttEncode_Ping(byte *tx_buf, int tx_buf_len, MqttPing* ping) { int header_len, remain_len = 0; @@ -1721,6 +1756,14 @@ int MqttEncode_Disconnect(byte *tx_buf, int tx_buf_len, return header_len + remain_len; } +#ifdef WOLFMQTT_BROKER +int MqttDecode_Disconnect(byte *rx_buf, int rx_buf_len, MqttDisconnect* disc) +{ + +} +#endif /* WOLFMQTT_BROKER */ + + #ifdef WOLFMQTT_V5 int MqttDecode_Disconnect(byte *rx_buf, int rx_buf_len, MqttDisconnect *disc) { diff --git a/wolfmqtt/include.am b/wolfmqtt/include.am index 5ce466a97..c325a828b 100644 --- a/wolfmqtt/include.am +++ b/wolfmqtt/include.am @@ -5,6 +5,7 @@ nobase_include_HEADERS+= \ wolfmqtt/version.h \ wolfmqtt/mqtt_types.h \ + wolfmqtt/mqtt_broker.h \ wolfmqtt/mqtt_client.h \ wolfmqtt/mqtt_packet.h \ wolfmqtt/mqtt_socket.h \ diff --git a/wolfmqtt/mqtt_broker.h b/wolfmqtt/mqtt_broker.h new file mode 100644 index 000000000..fd99ce913 --- /dev/null +++ b/wolfmqtt/mqtt_broker.h @@ -0,0 +1,38 @@ +/* mqtt_broker.h + * + * Copyright (C) 2006-2021 wolfSSL Inc. + * + * This file is part of wolfMQTT. + * + * wolfMQTT is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfMQTT is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef WOLFMQTT_BROKER_H +#define WOLFMQTT_BROKER_H + +#ifdef __cplusplus + extern "C" { +#endif + +#ifdef WOLFMQTT_BROKER + + +#endif + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif /* WOLFMQTT_BROKER_H */ diff --git a/wolfmqtt/mqtt_packet.h b/wolfmqtt/mqtt_packet.h index 6b6b90ffe..706a07c4d 100644 --- a/wolfmqtt/mqtt_packet.h +++ b/wolfmqtt/mqtt_packet.h @@ -719,6 +719,15 @@ WOLFMQTT_LOCAL MqttProp* MqttProps_FindType(MqttProp *head, #define MqttPacket_TypeDesc(x) "not compiled in" #endif +#ifdef WOLFMQTT_BROKER +WOLFMQTT_LOCAL int MqttDecode_Connect(byte *rx_buf, int rx_buf_len, MqttConnect *mc_connect); +WOLFMQTT_LOCAL int MqttEncode_ConnectAck(byte *tx_buf, int tx_buf_len MqttConnectAck *connect_ack); +WOLFMQTT_LOCAL int MqttDecode_Subscribe(byte *rx_buf, int rx_buf_len, MqttSubscribe *subscribe); +WOLFMQTT_LOCAL int MqttDecode_Unsubscribe(byte *rx_buf, int rx_buf_len, MqttUnsubscribe *unsubscribe); +WOLFMQTT_LOCAL int MqttEncode_UnsubscribeAck(byte *tx_buf, int tx_buf_len, MqttUnsubscribeAck *unsubscribe_ack); +WOLFMQTT_LOCAL int MqttDecode_Disconnect(byte *rx_buf, int rx_buf_len, MqttDisconnect* disc); +#endif + #ifdef __cplusplus } /* extern "C" */ #endif From 5fa4b491071be4d1f17a45a5706cce229be452ef Mon Sep 17 00:00:00 2001 From: David Garske Date: Fri, 30 Jan 2026 15:40:56 -0800 Subject: [PATCH 02/13] Progress with working Broker. --- .gitignore | 2 + src/include.am | 2 + src/mqtt_broker.c | 1026 +++++++++++++++++++++++++++++++++++++++- src/mqtt_packet.c | 526 +++++++++++++++++++- wolfmqtt/mqtt_broker.h | 1 + wolfmqtt/mqtt_packet.h | 78 +-- 6 files changed, 1592 insertions(+), 43 deletions(-) diff --git a/.gitignore b/.gitignore index 8628c6489..0a60f1e6d 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ lt*.m4 *.cache config.* configure +configure~ libtool libtool.m4 *.log @@ -123,3 +124,4 @@ examples/websocket/websocket_client # Never exclude Espressif config.h files !/IDE/Espressif/**/config.h +src/mqtt_broker diff --git a/src/include.am b/src/include.am index f31b4547a..9d6a2cef4 100644 --- a/src/include.am +++ b/src/include.am @@ -20,9 +20,11 @@ src_libwolfmqtt_la_DEPENDENCIES = EXTRA_DIST += +if BUILD_BROKER bin_PROGRAMS += src/mqtt_broker src_mqtt_broker_SOURCES = src/mqtt_broker.c src_mqtt_broker_CFLAGS = src_mqtt_broker_LDFLAGS = -Lsrc src_mqtt_broker_LDADD = src/libwolfmqtt.la $(LTLIBEVENT) $(LIB_STATIC_ADD) src_mqtt_broker_DEPENDENCIES = src/libwolfmqtt.la +endif diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index cf7ec178f..51a92eba8 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -26,15 +26,1023 @@ #endif #include "wolfmqtt/mqtt_broker.h" +#include "wolfmqtt/mqtt_client.h" +#include "wolfmqtt/mqtt_packet.h" +#include "wolfmqtt/mqtt_socket.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include #ifdef WOLFMQTT_BROKER -int wolfmqtt_broker(argc, argv) +int wolfmqtt_broker(int argc, char** argv); + +typedef struct BrokerClient { + int fd; + byte protocol_level; + char* client_id; + char* username; + char* password; + word16 keep_alive_sec; + time_t last_rx; + MqttNet net; + MqttClient client; + byte* tx_buf; + byte* rx_buf; + int tx_buf_len; + int rx_buf_len; + struct BrokerClient* next; +} BrokerClient; + +typedef struct BrokerSub { + char* filter; + BrokerClient* client; + struct BrokerSub* next; +} BrokerSub; + +#define BROKER_RX_BUF_SZ 4096 +#define BROKER_TX_BUF_SZ 4096 +#define BROKER_TIMEOUT_MS 1000 +#define BROKER_LISTEN_BACKLOG 8 +#define BROKER_LOG_PKT 1 + +static int BrokerSocket_SetNonBlocking(int fd) +{ + int flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) { + return MQTT_CODE_ERROR_SYSTEM; + } + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { + return MQTT_CODE_ERROR_SYSTEM; + } + return MQTT_CODE_SUCCESS; +} + +static int BrokerNetConnect(void* context, const char* host, word16 port, + int timeout_ms) +{ + (void)context; + (void)host; + (void)port; + (void)timeout_ms; + PRINTF("broker: net connect ctx=%p host=%s port=%u timeout=%d", + context, host ? host : "(null)", port, timeout_ms); + return MQTT_CODE_SUCCESS; +} + +static int BrokerNetRead(void* context, byte* buf, int buf_len, int timeout_ms) +{ + BrokerClient* bc = (BrokerClient*)context; + fd_set rfds; + struct timeval tv; + int rc; + + if (bc == NULL || buf == NULL || buf_len <= 0) { + return MQTT_CODE_ERROR_BAD_ARG; + } + + FD_ZERO(&rfds); + FD_SET(bc->fd, &rfds); + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + + rc = select(bc->fd + 1, &rfds, NULL, NULL, &tv); + if (rc == 0) { + return MQTT_CODE_ERROR_TIMEOUT; + } + if (rc < 0) { + return MQTT_CODE_ERROR_NETWORK; + } + + rc = (int)recv(bc->fd, buf, buf_len, 0); + if (rc <= 0) { + if (rc < 0 && (errno == EWOULDBLOCK || errno == EAGAIN)) { + return MQTT_CODE_CONTINUE; + } + PRINTF("broker: recv error fd=%d rc=%d errno=%d", bc->fd, rc, errno); + return MQTT_CODE_ERROR_NETWORK; + } + PRINTF("broker: recv fd=%d len=%d", bc->fd, rc); + return rc; +} + +static int BrokerNetWrite(void* context, const byte* buf, int buf_len, + int timeout_ms) +{ + BrokerClient* bc = (BrokerClient*)context; + fd_set wfds; + struct timeval tv; + int rc; + + if (bc == NULL || buf == NULL || buf_len <= 0) { + return MQTT_CODE_ERROR_BAD_ARG; + } + + FD_ZERO(&wfds); + FD_SET(bc->fd, &wfds); + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + + rc = select(bc->fd + 1, NULL, &wfds, NULL, &tv); + if (rc == 0) { + return MQTT_CODE_ERROR_TIMEOUT; + } + if (rc < 0) { + return MQTT_CODE_ERROR_NETWORK; + } + + rc = (int)send(bc->fd, buf, buf_len, 0); + if (rc <= 0) { + if (rc < 0 && (errno == EWOULDBLOCK || errno == EAGAIN)) { + return MQTT_CODE_CONTINUE; + } + PRINTF("broker: send error fd=%d rc=%d errno=%d", bc->fd, rc, errno); + return MQTT_CODE_ERROR_NETWORK; + } + PRINTF("broker: send fd=%d len=%d", bc->fd, rc); + return rc; +} + +static int BrokerNetDisconnect(void* context) +{ + BrokerClient* bc = (BrokerClient*)context; + if (bc && bc->fd >= 0) { + PRINTF("broker: disconnect fd=%d", bc->fd); + close(bc->fd); + bc->fd = -1; + } + return MQTT_CODE_SUCCESS; +} + +static void BrokerClient_Free(BrokerClient* bc) +{ + if (bc == NULL) { + return; + } + (void)BrokerNetDisconnect(bc); + MqttClient_DeInit(&bc->client); + if (bc->client_id) { + WOLFMQTT_FREE(bc->client_id); + } + if (bc->username) { + WOLFMQTT_FREE(bc->username); + } + if (bc->password) { + WOLFMQTT_FREE(bc->password); + } + if (bc->tx_buf) { + WOLFMQTT_FREE(bc->tx_buf); + } + if (bc->rx_buf) { + WOLFMQTT_FREE(bc->rx_buf); + } + WOLFMQTT_FREE(bc); +} + +static BrokerClient* BrokerClient_Add(BrokerClient** head, int fd) +{ + BrokerClient* bc; + int rc; + + bc = (BrokerClient*)WOLFMQTT_MALLOC(sizeof(BrokerClient)); + if (bc == NULL) { + return NULL; + } + XMEMSET(bc, 0, sizeof(BrokerClient)); + bc->fd = fd; + bc->protocol_level = 0; + bc->keep_alive_sec = 0; + bc->last_rx = time(NULL); + bc->tx_buf_len = BROKER_TX_BUF_SZ; + bc->rx_buf_len = BROKER_RX_BUF_SZ; + bc->tx_buf = (byte*)WOLFMQTT_MALLOC(bc->tx_buf_len); + bc->rx_buf = (byte*)WOLFMQTT_MALLOC(bc->rx_buf_len); + if (bc->tx_buf == NULL || bc->rx_buf == NULL) { + BrokerClient_Free(bc); + return NULL; + } + + bc->net.context = bc; + bc->net.connect = BrokerNetConnect; + bc->net.read = BrokerNetRead; + bc->net.write = BrokerNetWrite; + bc->net.disconnect = BrokerNetDisconnect; + + rc = MqttClient_Init(&bc->client, &bc->net, NULL, + bc->tx_buf, bc->tx_buf_len, bc->rx_buf, bc->rx_buf_len, + BROKER_TIMEOUT_MS); + if (rc != MQTT_CODE_SUCCESS) { + PRINTF("broker: client init failed rc=%d", rc); + BrokerClient_Free(bc); + return NULL; + } + + bc->next = *head; + *head = bc; + return bc; +} + +static void BrokerClient_Remove(BrokerClient** head, BrokerClient* bc) +{ + BrokerClient* cur; + BrokerClient* prev = NULL; + + if (head == NULL || bc == NULL) { + return; + } + cur = *head; + while (cur) { + if (cur == bc) { + if (prev) { + prev->next = cur->next; + } + else { + *head = cur->next; + } + BrokerClient_Free(cur); + return; + } + prev = cur; + cur = cur->next; + } +} + +static void BrokerSubs_RemoveClient(BrokerSub** head, BrokerClient* bc) +{ + BrokerSub* cur = *head; + BrokerSub* prev = NULL; + + while (cur) { + BrokerSub* next = cur->next; + if (cur->client == bc) { + if (prev) { + prev->next = next; + } + else { + *head = next; + } + if (cur->filter) { + WOLFMQTT_FREE(cur->filter); + } + WOLFMQTT_FREE(cur); + } + else { + prev = cur; + } + cur = next; + } +} + +static int BrokerSubs_Add(BrokerSub** head, BrokerClient* bc, + const char* filter, word16 filter_len) +{ + BrokerSub* sub; + + sub = (BrokerSub*)WOLFMQTT_MALLOC(sizeof(BrokerSub)); + if (sub == NULL) { + return MQTT_CODE_ERROR_MEMORY; + } + XMEMSET(sub, 0, sizeof(BrokerSub)); + sub->filter = (char*)WOLFMQTT_MALLOC(filter_len + 1); + if (sub->filter == NULL) { + WOLFMQTT_FREE(sub); + return MQTT_CODE_ERROR_MEMORY; + } + XMEMCPY(sub->filter, filter, filter_len); + sub->filter[filter_len] = '\0'; + sub->client = bc; + sub->next = *head; + *head = sub; + PRINTF("broker: sub add fd=%d filter=%s", bc->fd, sub->filter); + return MQTT_CODE_SUCCESS; +} + +static void BrokerSubs_Remove(BrokerSub** head, BrokerClient* bc, + const char* filter, word16 filter_len) +{ + BrokerSub* cur = *head; + BrokerSub* prev = NULL; + + while (cur) { + BrokerSub* next = cur->next; + if (cur->client == bc && + cur->filter != NULL && + (word16)XSTRLEN(cur->filter) == filter_len && + XMEMCMP(cur->filter, filter, filter_len) == 0) { + if (prev) { + prev->next = next; + } + else { + *head = next; + } + PRINTF("broker: sub remove fd=%d filter=%s", + bc->fd, cur->filter); + WOLFMQTT_FREE(cur->filter); + WOLFMQTT_FREE(cur); + return; + } + prev = cur; + cur = next; + } +} + +static int BrokerTopicMatch(const char* filter, const char* topic) +{ + const char* f = filter; + const char* t = topic; + + if (filter == NULL || topic == NULL) { + return 0; + } + + while (*f && *t) { + if (*f == '#') { + return (f[1] == '\0'); + } + if (*f == '+') { + while (*t && *t != '/') { + t++; + } + f++; + } + else { + if (*f != *t) { + return 0; + } + f++; + t++; + } + if (*t == '/' && *f == '/') { + t++; + f++; + } + else if (*t == '/' || *f == '/') { + return 0; + } + } + + if (*f == '#') { + return (f[1] == '\0'); + } + return (*f == '\0' && *t == '\0'); +} + +static int BrokerSend_PingResp(BrokerClient* bc) +{ + if (bc == NULL) { + return MQTT_CODE_ERROR_BAD_ARG; + } + PRINTF("broker: PINGREQ -> PINGRESP fd=%d", bc->fd); + bc->tx_buf[0] = MQTT_PACKET_TYPE_SET(MQTT_PACKET_TYPE_PING_RESP); + bc->tx_buf[1] = 0; + return MqttPacket_Write(&bc->client, bc->tx_buf, 2); +} + +static int BrokerSend_SubAck(BrokerClient* bc, word16 packet_id, + const byte* return_codes, int return_code_count) +{ + int remain_len; + int pos = 0; + int i; + + if (bc == NULL || return_codes == NULL || return_code_count <= 0) { + return MQTT_CODE_ERROR_BAD_ARG; + } + + PRINTF("broker: SUBACK fd=%d packet_id=%u topics=%d", + bc->fd, packet_id, return_code_count); + remain_len = MQTT_DATA_LEN_SIZE + return_code_count; +#ifdef WOLFMQTT_V5 + if (bc->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) { + remain_len += 1; /* property length (0) */ + } +#endif + + bc->tx_buf[pos++] = MQTT_PACKET_TYPE_SET(MQTT_PACKET_TYPE_SUBSCRIBE_ACK); + pos += MqttEncode_Vbi(&bc->tx_buf[pos], remain_len); + pos += MqttEncode_Num(&bc->tx_buf[pos], packet_id); +#ifdef WOLFMQTT_V5 + if (bc->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) { + bc->tx_buf[pos++] = 0; /* property length */ + } +#endif + for (i = 0; i < return_code_count; i++) { + bc->tx_buf[pos++] = return_codes[i]; + } + + return MqttPacket_Write(&bc->client, bc->tx_buf, pos); +} + +static int BrokerHandle_Connect(BrokerClient* bc, int rx_len) +{ + int rc; + MqttConnect mc; + MqttConnectAck ack; + MqttMessage lwt; + + XMEMSET(&mc, 0, sizeof(mc)); + XMEMSET(&ack, 0, sizeof(ack)); + XMEMSET(&lwt, 0, sizeof(lwt)); + mc.lwt_msg = &lwt; + + PRINTF("broker: CONNECT recv fd=%d len=%d", bc->fd, rx_len); + rc = MqttDecode_Connect(bc->rx_buf, rx_len, &mc); + if (rc < 0) { + PRINTF("broker: CONNECT decode failed rc=%d", rc); + return rc; + } + + if (bc->client_id) { + WOLFMQTT_FREE(bc->client_id); + bc->client_id = NULL; + } + if (mc.client_id) { + word16 id_len = 0; + if (MqttDecode_Num((byte*)mc.client_id - MQTT_DATA_LEN_SIZE, + &id_len, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) { + bc->client_id = (char*)WOLFMQTT_MALLOC(id_len + 1); + if (bc->client_id != NULL) { + XMEMCPY(bc->client_id, mc.client_id, id_len); + bc->client_id[id_len] = '\0'; + } + } + } + bc->protocol_level = mc.protocol_level; + bc->keep_alive_sec = mc.keep_alive_sec; + bc->last_rx = time(NULL); + PRINTF("broker: CONNECT proto=%u clean=%d will=%d", + mc.protocol_level, mc.clean_session, mc.enable_lwt); + + ack.flags = 0; + ack.return_code = MQTT_CONNECT_ACK_CODE_ACCEPTED; +#ifdef WOLFMQTT_V5 + ack.protocol_level = mc.protocol_level; + ack.props = NULL; +#endif + + if (bc->username) { + WOLFMQTT_FREE(bc->username); + bc->username = NULL; + } + if (bc->password) { + WOLFMQTT_FREE(bc->password); + bc->password = NULL; + } + if (mc.username) { + word16 ulen = 0; + if (MqttDecode_Num((byte*)mc.username - MQTT_DATA_LEN_SIZE, + &ulen, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) { + bc->username = (char*)WOLFMQTT_MALLOC(ulen + 1); + if (bc->username) { + XMEMCPY(bc->username, mc.username, ulen); + bc->username[ulen] = '\0'; + } + } + } + if (mc.password) { + word16 plen = 0; + if (MqttDecode_Num((byte*)mc.password - MQTT_DATA_LEN_SIZE, + &plen, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) { + bc->password = (char*)WOLFMQTT_MALLOC(plen + 1); + if (bc->password) { + XMEMCPY(bc->password, mc.password, plen); + bc->password[plen] = '\0'; + } + } + } +#ifdef WOLFMQTT_V5 + ack.protocol_level = mc.protocol_level; + ack.props = NULL; +#endif + rc = MqttEncode_ConnectAck(bc->tx_buf, bc->tx_buf_len, &ack); + if (rc > 0) { + PRINTF("broker: CONNACK send fd=%d", bc->fd); + rc = MqttPacket_Write(&bc->client, bc->tx_buf, rc); + } + +#ifdef WOLFMQTT_V5 + if (mc.props) { + (void)MqttProps_Free(mc.props); + } + if (lwt.props) { + (void)MqttProps_Free(lwt.props); + } +#endif + return rc; +} + +static int BrokerHandle_Subscribe(BrokerClient* bc, int rx_len) +{ + int rc; + int i; + MqttSubscribe sub; + byte return_codes[MAX_MQTT_TOPICS]; + + XMEMSET(&sub, 0, sizeof(sub)); +#ifdef WOLFMQTT_V5 + sub.protocol_level = bc->protocol_level; +#endif + sub.topics = (MqttTopic*)WOLFMQTT_MALLOC(sizeof(MqttTopic) * MAX_MQTT_TOPICS); + if (sub.topics == NULL) { + return MQTT_CODE_ERROR_MEMORY; + } + XMEMSET(sub.topics, 0, sizeof(MqttTopic) * MAX_MQTT_TOPICS); + + PRINTF("broker: SUBSCRIBE recv fd=%d len=%d", bc->fd, rx_len); + rc = MqttDecode_Subscribe(bc->rx_buf, rx_len, &sub); + if (rc < 0) { + PRINTF("broker: SUBSCRIBE decode failed rc=%d", rc); + WOLFMQTT_FREE(sub.topics); + return rc; + } + + for (i = 0; i < sub.topic_count && i < MAX_MQTT_TOPICS; i++) { + return_codes[i] = MQTT_SUBSCRIBE_ACK_CODE_SUCCESS_MAX_QOS0; + } + + rc = BrokerSend_SubAck(bc, sub.packet_id, return_codes, + sub.topic_count); + +#ifdef WOLFMQTT_V5 + if (sub.props) { + (void)MqttProps_Free(sub.props); + } +#endif + WOLFMQTT_FREE(sub.topics); + return rc; +} + +static int BrokerHandle_Unsubscribe(BrokerClient* bc, int rx_len) +{ + int rc; + MqttUnsubscribe unsub; + MqttUnsubscribeAck ack; + + XMEMSET(&unsub, 0, sizeof(unsub)); +#ifdef WOLFMQTT_V5 + unsub.protocol_level = bc->protocol_level; +#endif + unsub.topics = (MqttTopic*)WOLFMQTT_MALLOC(sizeof(MqttTopic) * MAX_MQTT_TOPICS); + if (unsub.topics == NULL) { + return MQTT_CODE_ERROR_MEMORY; + } + XMEMSET(unsub.topics, 0, sizeof(MqttTopic) * MAX_MQTT_TOPICS); + + PRINTF("broker: UNSUBSCRIBE recv fd=%d len=%d", bc->fd, rx_len); + rc = MqttDecode_Unsubscribe(bc->rx_buf, rx_len, &unsub); + if (rc < 0) { + PRINTF("broker: UNSUBSCRIBE decode failed rc=%d", rc); + WOLFMQTT_FREE(unsub.topics); + return rc; + } + + XMEMSET(&ack, 0, sizeof(ack)); + ack.packet_id = unsub.packet_id; +#ifdef WOLFMQTT_V5 + ack.protocol_level = bc->protocol_level; + ack.props = NULL; + ack.reason_codes = NULL; +#endif + rc = MqttEncode_UnsubscribeAck(bc->tx_buf, bc->tx_buf_len, &ack); + if (rc > 0) { + PRINTF("broker: UNSUBACK send fd=%d packet_id=%u", + bc->fd, ack.packet_id); + rc = MqttPacket_Write(&bc->client, bc->tx_buf, rc); + } + +#ifdef WOLFMQTT_V5 + if (unsub.props) { + (void)MqttProps_Free(unsub.props); + } +#endif + WOLFMQTT_FREE(unsub.topics); + return rc; +} + +static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, + BrokerSub* subs) +{ + int rc; + MqttPublish pub; + MqttPublishResp resp; + byte* payload = NULL; + char* topic = NULL; + + XMEMSET(&pub, 0, sizeof(pub)); +#ifdef WOLFMQTT_V5 + pub.protocol_level = bc->protocol_level; +#endif + PRINTF("broker: PUBLISH recv fd=%d len=%d", bc->fd, rx_len); + rc = MqttDecode_Publish(bc->rx_buf, rx_len, &pub); + if (rc < 0) { + PRINTF("broker: PUBLISH decode failed rc=%d", rc); + return rc; + } + + if (pub.topic_name && pub.topic_name_len > 0) { + topic = (char*)WOLFMQTT_MALLOC(pub.topic_name_len + 1); + if (topic != NULL) { + XMEMCPY(topic, pub.topic_name, pub.topic_name_len); + topic[pub.topic_name_len] = '\0'; + } + } + if (pub.total_len > 0 && pub.buffer != NULL) { + payload = (byte*)WOLFMQTT_MALLOC(pub.total_len); + if (payload != NULL) { + XMEMCPY(payload, pub.buffer, pub.total_len); + } + } + + if (topic != NULL && (payload != NULL || pub.total_len == 0)) { + BrokerSub* sub = subs; + while (sub) { + if (sub->client && sub->client->protocol_level != 0 && + sub->filter && BrokerTopicMatch(sub->filter, topic)) { + MqttPublish out_pub; + XMEMSET(&out_pub, 0, sizeof(out_pub)); + out_pub.topic_name = topic; + out_pub.qos = MQTT_QOS_0; + out_pub.retain = pub.retain; + out_pub.duplicate = 0; + out_pub.buffer = payload; + out_pub.total_len = pub.total_len; +#ifdef WOLFMQTT_V5 + out_pub.protocol_level = sub->client->protocol_level; +#endif + rc = MqttEncode_Publish(sub->client->tx_buf, + sub->client->tx_buf_len, + &out_pub, 0); + if (rc > 0) { + PRINTF("broker: PUBLISH fwd fd=%d -> fd=%d topic=%s len=%u", + bc->fd, sub->client->fd, topic ? topic : "(null)", + (unsigned)pub.total_len); + (void)MqttPacket_Write(&sub->client->client, + sub->client->tx_buf, rc); + } + } + sub = sub->next; + } + } + + if (pub.qos == MQTT_QOS_1 || pub.qos == MQTT_QOS_2) { + XMEMSET(&resp, 0, sizeof(resp)); + resp.packet_id = pub.packet_id; +#ifdef WOLFMQTT_V5 + resp.protocol_level = bc->protocol_level; + resp.reason_code = MQTT_REASON_SUCCESS; + resp.props = NULL; +#endif + rc = MqttEncode_PublishResp(bc->tx_buf, bc->tx_buf_len, + (pub.qos == MQTT_QOS_1) ? MQTT_PACKET_TYPE_PUBLISH_ACK : + MQTT_PACKET_TYPE_PUBLISH_REC, &resp); + if (rc > 0) { + PRINTF("broker: PUBRESP send fd=%d qos=%d packet_id=%u", + bc->fd, pub.qos, pub.packet_id); + rc = MqttPacket_Write(&bc->client, bc->tx_buf, rc); + } + } + +#ifdef WOLFMQTT_V5 + if (pub.props) { + (void)MqttProps_Free(pub.props); + } +#endif + if (payload) { + WOLFMQTT_FREE(payload); + } + if (topic) { + WOLFMQTT_FREE(topic); + } + + return rc; +} + +static int BrokerHandle_PublishRel(BrokerClient* bc, int rx_len) +{ + int rc; + MqttPublishResp resp; + + XMEMSET(&resp, 0, sizeof(resp)); +#ifdef WOLFMQTT_V5 + resp.protocol_level = bc->protocol_level; +#endif + PRINTF("broker: PUBLISH_REL recv fd=%d len=%d", bc->fd, rx_len); + rc = MqttDecode_PublishResp(bc->rx_buf, rx_len, MQTT_PACKET_TYPE_PUBLISH_REL, + &resp); + if (rc < 0) { + PRINTF("broker: PUBLISH_REL decode failed rc=%d", rc); + return rc; + } + +#ifdef WOLFMQTT_V5 + resp.reason_code = MQTT_REASON_SUCCESS; + resp.props = NULL; +#endif + rc = MqttEncode_PublishResp(bc->tx_buf, bc->tx_buf_len, + MQTT_PACKET_TYPE_PUBLISH_COMP, &resp); + if (rc > 0) { + PRINTF("broker: PUBCOMP send fd=%d packet_id=%u", + bc->fd, resp.packet_id); + rc = MqttPacket_Write(&bc->client, bc->tx_buf, rc); + } +#ifdef WOLFMQTT_V5 + if (resp.props) { + (void)MqttProps_Free(resp.props); + } +#endif + return rc; +} + +static void BrokerUsage(const char* prog) +{ + PRINTF("usage: %s [-p port] [-u user] [-P pass]", prog); +} + +int wolfmqtt_broker(int argc, char** argv) { int ret = 0; - - (void)argc; - (void)argv; + int listen_fd; + struct sockaddr_in addr; + BrokerClient* clients = NULL; + BrokerSub* subs = NULL; + const char* auth_user = NULL; + const char* auth_pass = NULL; + int port = MQTT_DEFAULT_PORT; + int i; + + for (i = 1; i < argc; i++) { + if (XSTRCMP(argv[i], "-p") == 0 && i + 1 < argc) { + port = XATOI(argv[++i]); + } + else if (XSTRCMP(argv[i], "-u") == 0 && i + 1 < argc) { + auth_user = argv[++i]; + } + else if (XSTRCMP(argv[i], "-P") == 0 && i + 1 < argc) { + auth_pass = argv[++i]; + } + else if (XSTRCMP(argv[i], "-h") == 0) { + BrokerUsage(argv[0]); + return 0; + } + else { + BrokerUsage(argv[0]); + return MQTT_CODE_ERROR_BAD_ARG; + } + } + + listen_fd = socket(AF_INET, SOCK_STREAM, 0); + if (listen_fd < 0) { + PRINTF("broker: socket failed (%d)", errno); + return MQTT_CODE_ERROR_NETWORK; + } + { + int opt = 1; + (void)setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + } + + XMEMSET(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons((word16)port); + + if (BrokerSocket_SetNonBlocking(listen_fd) != MQTT_CODE_SUCCESS) { + PRINTF("broker: set nonblocking failed (%d)", errno); + close(listen_fd); + return MQTT_CODE_ERROR_SYSTEM; + } + + if (bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + PRINTF("broker: bind failed (%d)", errno); + close(listen_fd); + return MQTT_CODE_ERROR_NETWORK; + } + if (listen(listen_fd, BROKER_LISTEN_BACKLOG) < 0) { + PRINTF("broker: listen failed (%d)", errno); + close(listen_fd); + return MQTT_CODE_ERROR_NETWORK; + } + + PRINTF("broker: listening on port %d (no TLS)", port); + if (auth_user || auth_pass) { + PRINTF("broker: auth enabled user=%s", auth_user ? auth_user : "(null)"); + } + + while (1) { + fd_set rfds; + int maxfd = listen_fd; + BrokerClient* bc; + + FD_ZERO(&rfds); + FD_SET(listen_fd, &rfds); + + for (bc = clients; bc; bc = bc->next) { + FD_SET(bc->fd, &rfds); + if (bc->fd > maxfd) { + maxfd = bc->fd; + } + } + + if (select(maxfd + 1, &rfds, NULL, NULL, NULL) < 0) { + PRINTF("broker: select failed (%d)", errno); + ret = MQTT_CODE_ERROR_NETWORK; + break; + } + + if (FD_ISSET(listen_fd, &rfds)) { + int client_fd = accept(listen_fd, NULL, NULL); + if (client_fd >= 0) { + (void)BrokerSocket_SetNonBlocking(client_fd); + PRINTF("broker: accept fd=%d", client_fd); + if (BrokerClient_Add(&clients, client_fd) == NULL) { + PRINTF("broker: accept fd=%d rejected (alloc)", client_fd); + close(client_fd); + } + } + } + + bc = clients; + while (bc) { + BrokerClient* next = bc->next; + if (FD_ISSET(bc->fd, &rfds)) { + int rc = MqttPacket_Read(&bc->client, bc->rx_buf, + bc->rx_buf_len, BROKER_TIMEOUT_MS); + if (rc < 0) { + PRINTF("broker: read failed fd=%d rc=%d", bc->fd, rc); + BrokerSubs_RemoveClient(&subs, bc); + BrokerClient_Remove(&clients, bc); + } + else if (rc > 0) { + byte type = MQTT_PACKET_TYPE_GET(bc->rx_buf[0]); + bc->last_rx = time(NULL); +#if BROKER_LOG_PKT + PRINTF("broker: packet fd=%d type=%u len=%d", + bc->fd, type, rc); +#endif + switch (type) { + case MQTT_PACKET_TYPE_CONNECT: + (void)BrokerHandle_Connect(bc, rc); + if (auth_user || auth_pass) { + int ok = 1; + if (auth_user && (!bc->username || + XSTRCMP(auth_user, bc->username) != 0)) { + ok = 0; + } + if (auth_pass && (!bc->password || + XSTRCMP(auth_pass, bc->password) != 0)) { + ok = 0; + } + if (!ok) { + MqttConnectAck nack; + XMEMSET(&nack, 0, sizeof(nack)); + nack.flags = 0; + nack.return_code = MQTT_CONNECT_ACK_CODE_REFUSED_BAD_USER_PWD; +#ifdef WOLFMQTT_V5 + nack.protocol_level = bc->protocol_level; + nack.props = NULL; +#endif + { + int nrc = MqttEncode_ConnectAck( + bc->tx_buf, bc->tx_buf_len, + &nack); + if (nrc > 0) { + (void)MqttPacket_Write(&bc->client, + bc->tx_buf, nrc); + } + } + PRINTF("broker: auth failed fd=%d", bc->fd); + BrokerSubs_RemoveClient(&subs, bc); + BrokerClient_Remove(&clients, bc); + } + } + break; + case MQTT_PACKET_TYPE_PUBLISH: + (void)BrokerHandle_Publish(bc, rc, subs); + break; + case MQTT_PACKET_TYPE_PUBLISH_REL: + (void)BrokerHandle_PublishRel(bc, rc); + break; + case MQTT_PACKET_TYPE_SUBSCRIBE: + { + int s_rc = BrokerHandle_Subscribe(bc, rc); + if (s_rc >= 0) { + int idx; + MqttSubscribe sub; + XMEMSET(&sub, 0, sizeof(sub)); +#ifdef WOLFMQTT_V5 + sub.protocol_level = bc->protocol_level; +#endif + sub.topics = (MqttTopic*)WOLFMQTT_MALLOC( + sizeof(MqttTopic) * MAX_MQTT_TOPICS); + if (sub.topics) { + XMEMSET(sub.topics, 0, + sizeof(MqttTopic) * MAX_MQTT_TOPICS); + if (MqttDecode_Subscribe(bc->rx_buf, rc, + &sub) >= 0) { + for (idx = 0; idx < sub.topic_count; + idx++) { + const char* f = sub.topics[idx].topic_filter; + word16 flen = 0; + if (f && + MqttDecode_Num((byte*)f - + MQTT_DATA_LEN_SIZE, + &flen, + MQTT_DATA_LEN_SIZE) == + MQTT_DATA_LEN_SIZE) { + (void)BrokerSubs_Add(&subs, bc, + f, flen); + } + } + } +#ifdef WOLFMQTT_V5 + if (sub.props) { + (void)MqttProps_Free(sub.props); + } +#endif + WOLFMQTT_FREE(sub.topics); + } + } + break; + } + case MQTT_PACKET_TYPE_UNSUBSCRIBE: + { + int u_rc = BrokerHandle_Unsubscribe(bc, rc); + if (u_rc >= 0) { + int idx; + MqttUnsubscribe unsub; + XMEMSET(&unsub, 0, sizeof(unsub)); +#ifdef WOLFMQTT_V5 + unsub.protocol_level = bc->protocol_level; +#endif + unsub.topics = (MqttTopic*)WOLFMQTT_MALLOC( + sizeof(MqttTopic) * MAX_MQTT_TOPICS); + if (unsub.topics) { + XMEMSET(unsub.topics, 0, + sizeof(MqttTopic) * MAX_MQTT_TOPICS); + if (MqttDecode_Unsubscribe(bc->rx_buf, rc, + &unsub) >= 0) { + for (idx = 0; idx < unsub.topic_count; + idx++) { + const char* f = unsub.topics[idx].topic_filter; + word16 flen = 0; + if (f && + MqttDecode_Num((byte*)f - + MQTT_DATA_LEN_SIZE, + &flen, + MQTT_DATA_LEN_SIZE) == + MQTT_DATA_LEN_SIZE) { + BrokerSubs_Remove(&subs, bc, + f, flen); + } + } + } +#ifdef WOLFMQTT_V5 + if (unsub.props) { + (void)MqttProps_Free(unsub.props); + } +#endif + WOLFMQTT_FREE(unsub.topics); + } + } + break; + } + case MQTT_PACKET_TYPE_PING_REQ: + (void)BrokerSend_PingResp(bc); + break; + case MQTT_PACKET_TYPE_DISCONNECT: + BrokerSubs_RemoveClient(&subs, bc); + BrokerClient_Remove(&clients, bc); + break; + default: + break; + } + } + } + if (bc && bc->keep_alive_sec > 0) { + time_t now = time(NULL); + if ((now - bc->last_rx) > (time_t)(bc->keep_alive_sec * 2)) { + PRINTF("broker: keepalive timeout fd=%d", bc->fd); + BrokerSubs_RemoveClient(&subs, bc); + BrokerClient_Remove(&clients, bc); + } + } + bc = next; + } + } + + while (clients) { + BrokerSubs_RemoveClient(&subs, clients); + BrokerClient_Remove(&clients, clients); + } + close(listen_fd); return ret; } @@ -46,4 +1054,14 @@ int main(int argc, char** argv) } #endif +#else /* WOLFMQTT_BROKER */ +#ifndef NO_MAIN_DRIVER +int main(int argc, char** argv) +{ + (void)argc; + (void)argv; + PRINTF("broker: not built (configure with --enable-broker)"); + return 0; +} +#endif #endif /* WOLFMQTT_BROKER */ diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index a5ca8d5a4..d4c67c1dd 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -855,7 +855,200 @@ int MqttEncode_Connect(byte *tx_buf, int tx_buf_len, MqttConnect *mc_connect) #ifdef WOLFMQTT_BROKER int MqttDecode_Connect(byte *rx_buf, int rx_buf_len, MqttConnect *mc_connect) { + int header_len, remain_len; + byte *rx_payload; + MqttConnectPacket packet; + word16 protocol_len = 0; + int tmp; +#ifdef WOLFMQTT_V5 + word32 props_len = 0; +#endif + + /* Validate required arguments */ + if (rx_buf == NULL || rx_buf_len <= 0 || mc_connect == NULL) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); + } + + /* Decode fixed header */ + header_len = MqttDecode_FixedHeader(rx_buf, rx_buf_len, &remain_len, + MQTT_PACKET_TYPE_CONNECT, NULL, NULL, NULL); + if (header_len < 0) { + return header_len; + } + if (rx_buf_len < header_len + remain_len) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + rx_payload = &rx_buf[header_len]; + + if ((int)sizeof(MqttConnectPacket) > remain_len || + (rx_buf_len < header_len + (int)sizeof(MqttConnectPacket))) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + + /* Decode variable header */ + XMEMCPY(&packet, rx_payload, sizeof(MqttConnectPacket)); + rx_payload += sizeof(MqttConnectPacket); + + tmp = MqttDecode_Num(packet.protocol_len, &protocol_len, + MQTT_DATA_LEN_SIZE); + if (tmp < 0) { + return tmp; + } + if ((protocol_len != MQTT_CONNECT_PROTOCOL_NAME_LEN) || + (XMEMCMP(packet.protocol_name, MQTT_CONNECT_PROTOCOL_NAME, + MQTT_CONNECT_PROTOCOL_NAME_LEN) != 0)) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_MALFORMED_DATA); + } + + mc_connect->protocol_level = packet.protocol_level; + mc_connect->clean_session = + (packet.flags & MQTT_CONNECT_FLAG_CLEAN_SESSION) ? 1 : 0; + mc_connect->enable_lwt = + (packet.flags & MQTT_CONNECT_FLAG_WILL_FLAG) ? 1 : 0; + mc_connect->username = NULL; + mc_connect->password = NULL; + + tmp = MqttDecode_Num((byte*)&packet.keep_alive, &mc_connect->keep_alive_sec, + MQTT_DATA_LEN_SIZE); + if (tmp < 0) { + return tmp; + } + +#ifdef WOLFMQTT_V5 + mc_connect->props = NULL; + if (mc_connect->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) { + /* Decode Length of Properties */ + if (rx_buf_len < (rx_payload - rx_buf)) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + tmp = MqttDecode_Vbi(rx_payload, &props_len, + (word32)(rx_buf_len - (rx_payload - rx_buf))); + if (tmp < 0) { + return tmp; + } + rx_payload += tmp; + if (props_len > 0) { + /* Decode the Properties */ + tmp = MqttDecode_Props(MQTT_PACKET_TYPE_CONNECT, + &mc_connect->props, rx_payload, + (word32)(rx_buf_len - (rx_payload - rx_buf)), props_len); + if (tmp < 0) { + return tmp; + } + rx_payload += tmp; + } + } +#endif + + /* Decode payload */ + tmp = MqttDecode_String(rx_payload, &mc_connect->client_id, NULL, + (word32)(rx_buf_len - (rx_payload - rx_buf))); + if (tmp < 0) { + return tmp; + } + if ((rx_payload - rx_buf) + tmp > header_len + remain_len) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + rx_payload += tmp; + + if (mc_connect->enable_lwt) { + if (mc_connect->lwt_msg == NULL) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); + } + mc_connect->lwt_msg->qos = + (MqttQoS)MQTT_CONNECT_FLAG_GET_QOS(packet.flags); + mc_connect->lwt_msg->retain = + (packet.flags & MQTT_CONNECT_FLAG_WILL_RETAIN) ? 1 : 0; + +#ifdef WOLFMQTT_V5 + mc_connect->lwt_msg->props = NULL; + if (mc_connect->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) { + word32 lwt_props_len = 0; + int lwt_tmp; + /* Decode Length of LWT Properties */ + if (rx_buf_len < (rx_payload - rx_buf)) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + lwt_tmp = MqttDecode_Vbi(rx_payload, &lwt_props_len, + (word32)(rx_buf_len - (rx_payload - rx_buf))); + if (lwt_tmp < 0) { + return lwt_tmp; + } + rx_payload += lwt_tmp; + if (lwt_props_len > 0) { + /* Decode LWT Properties */ + lwt_tmp = MqttDecode_Props(MQTT_PACKET_TYPE_CONNECT, + &mc_connect->lwt_msg->props, rx_payload, + (word32)(rx_buf_len - (rx_payload - rx_buf)), + lwt_props_len); + if (lwt_tmp < 0) { + return lwt_tmp; + } + rx_payload += lwt_tmp; + } + } +#endif + + tmp = MqttDecode_String(rx_payload, &mc_connect->lwt_msg->topic_name, + &mc_connect->lwt_msg->topic_name_len, + (word32)(rx_buf_len - (rx_payload - rx_buf))); + if (tmp < 0) { + return tmp; + } + if ((rx_payload - rx_buf) + tmp > header_len + remain_len) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + rx_payload += tmp; + + { + word16 lwt_len = 0; + tmp = MqttDecode_Num(rx_payload, &lwt_len, + (word32)(rx_buf_len - (rx_payload - rx_buf))); + if (tmp < 0) { + return tmp; + } + mc_connect->lwt_msg->total_len = lwt_len; + } + rx_payload += tmp; + if ((rx_payload - rx_buf) + + (int)mc_connect->lwt_msg->total_len > header_len + remain_len) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + mc_connect->lwt_msg->buffer = rx_payload; + mc_connect->lwt_msg->buffer_len = mc_connect->lwt_msg->total_len; + mc_connect->lwt_msg->buffer_pos = 0; + rx_payload += mc_connect->lwt_msg->total_len; + } + + if (packet.flags & MQTT_CONNECT_FLAG_USERNAME) { + tmp = MqttDecode_String(rx_payload, &mc_connect->username, NULL, + (word32)(rx_buf_len - (rx_payload - rx_buf))); + if (tmp < 0) { + return tmp; + } + if ((rx_payload - rx_buf) + tmp > header_len + remain_len) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + rx_payload += tmp; + } + + if (packet.flags & MQTT_CONNECT_FLAG_PASSWORD) { + tmp = MqttDecode_String(rx_payload, &mc_connect->password, NULL, + (word32)(rx_buf_len - (rx_payload - rx_buf))); + if (tmp < 0) { + return tmp; + } + if ((rx_payload - rx_buf) + tmp > header_len + remain_len) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + rx_payload += tmp; + } + + (void)rx_payload; + + /* Return total length of packet */ + return header_len + remain_len; } #endif /* WOLFMQTT_BROKER */ @@ -919,9 +1112,68 @@ int MqttDecode_ConnectAck(byte *rx_buf, int rx_buf_len, } #ifdef WOLFMQTT_BROKER -int MqttEncode_ConnectAck(byte *tx_buf, int tx_buf_len MqttConnectAck *connect_ack) +int MqttEncode_ConnectAck(byte *tx_buf, int tx_buf_len, + MqttConnectAck *connect_ack) { + int header_len, remain_len; + byte *tx_payload; +#ifdef WOLFMQTT_V5 + int props_len = 0; +#endif + + /* Validate required arguments */ + if (tx_buf == NULL || connect_ack == NULL) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); + } + + /* Determine packet length */ + remain_len = 2; /* flags + return code */ +#ifdef WOLFMQTT_V5 + if (connect_ack->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) { + /* Determine length of properties */ + props_len = MqttEncode_Props(MQTT_PACKET_TYPE_CONNECT_ACK, + connect_ack->props, NULL); + if (props_len >= 0) { + remain_len += props_len; + /* Determine the length of the "property length" */ + remain_len += MqttEncode_Vbi(NULL, props_len); + } + else + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_PROPERTY); + } +#endif + /* Encode fixed header */ + header_len = MqttEncode_FixedHeader(tx_buf, tx_buf_len, remain_len, + MQTT_PACKET_TYPE_CONNECT_ACK, 0, 0, 0); + if (header_len < 0) { + return header_len; + } + /* Check for buffer room */ + if (tx_buf_len < header_len + remain_len) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + tx_payload = &tx_buf[header_len]; + + /* Encode variable header */ + *tx_payload++ = connect_ack->flags; + *tx_payload++ = connect_ack->return_code; + +#ifdef WOLFMQTT_V5 + if (connect_ack->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) { + /* Encode the property length */ + tx_payload += MqttEncode_Vbi(tx_payload, props_len); + + /* Encode properties */ + tx_payload += MqttEncode_Props(MQTT_PACKET_TYPE_CONNECT_ACK, + connect_ack->props, tx_payload); + } +#endif + + (void)tx_payload; + + /* Return total length of packet */ + return header_len + remain_len; } #endif /* WOLFMQTT_BROKER */ @@ -1387,7 +1639,101 @@ int MqttEncode_Subscribe(byte *tx_buf, int tx_buf_len, #ifdef WOLFMQTT_BROKER int MqttDecode_Subscribe(byte *rx_buf, int rx_buf_len, MqttSubscribe *subscribe) { + int header_len, remain_len; + byte *rx_payload; + byte *rx_end; + + /* Validate required arguments */ + if (rx_buf == NULL || rx_buf_len <= 0 || subscribe == NULL) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); + } + + /* Decode fixed header */ + header_len = MqttDecode_FixedHeader(rx_buf, rx_buf_len, &remain_len, + MQTT_PACKET_TYPE_SUBSCRIBE, NULL, NULL, NULL); + if (header_len < 0) { + return header_len; + } + if (rx_buf_len < header_len + remain_len) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + rx_payload = &rx_buf[header_len]; + rx_end = rx_payload + remain_len; + + /* Decode variable header */ + if (subscribe) { + int tmp; + tmp = MqttDecode_Num(rx_payload, &subscribe->packet_id, + (word32)(rx_buf_len - (rx_payload - rx_buf))); + if (tmp < 0) { + return tmp; + } + rx_payload += tmp; + +#ifdef WOLFMQTT_V5 + subscribe->props = NULL; + if (subscribe->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) { + word32 props_len = 0; + int props_tmp; + /* Decode Length of Properties */ + if (rx_buf_len < (rx_payload - rx_buf)) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + props_tmp = MqttDecode_Vbi(rx_payload, &props_len, + (word32)(rx_buf_len - (rx_payload - rx_buf))); + if (props_tmp < 0) { + return props_tmp; + } + rx_payload += props_tmp; + if (props_len > 0) { + /* Decode the Properties */ + props_tmp = MqttDecode_Props(MQTT_PACKET_TYPE_SUBSCRIBE, + &subscribe->props, rx_payload, + (word32)(rx_buf_len - (rx_payload - rx_buf)), + props_len); + if (props_tmp < 0) { + return props_tmp; + } + rx_payload += props_tmp; + } + } +#endif + + subscribe->topic_count = 0; + if (subscribe->topics == NULL) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); + } + + while (rx_payload < rx_end) { + MqttTopic *topic; + byte options; + if (subscribe->topic_count >= MAX_MQTT_TOPICS) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + topic = &subscribe->topics[subscribe->topic_count]; + tmp = MqttDecode_String(rx_payload, &topic->topic_filter, NULL, + (word32)(rx_end - rx_payload)); + if (tmp < 0) { + return tmp; + } + if (rx_payload + tmp > rx_end) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + rx_payload += tmp; + if (rx_payload >= rx_end) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + options = *rx_payload++; + topic->qos = (MqttQoS)(options & 0x03); + subscribe->topic_count++; + } + } + + (void)rx_payload; + + /* Return total length of packet */ + return header_len + remain_len; } #endif /* WOLFMQTT_BROKER */ @@ -1547,7 +1893,95 @@ int MqttEncode_Unsubscribe(byte *tx_buf, int tx_buf_len, #ifdef WOLFMQTT_BROKER int MqttDecode_Unsubscribe(byte *rx_buf, int rx_buf_len, MqttUnsubscribe *unsubscribe) { + int header_len, remain_len; + byte *rx_payload; + byte *rx_end; + + /* Validate required arguments */ + if (rx_buf == NULL || rx_buf_len <= 0 || unsubscribe == NULL) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); + } + /* Decode fixed header */ + header_len = MqttDecode_FixedHeader(rx_buf, rx_buf_len, &remain_len, + MQTT_PACKET_TYPE_UNSUBSCRIBE, NULL, NULL, NULL); + if (header_len < 0) { + return header_len; + } + if (rx_buf_len < header_len + remain_len) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + rx_payload = &rx_buf[header_len]; + rx_end = rx_payload + remain_len; + + /* Decode variable header */ + if (unsubscribe) { + int tmp; + tmp = MqttDecode_Num(rx_payload, &unsubscribe->packet_id, + (word32)(rx_buf_len - (rx_payload - rx_buf))); + if (tmp < 0) { + return tmp; + } + rx_payload += tmp; + +#ifdef WOLFMQTT_V5 + unsubscribe->props = NULL; + if (unsubscribe->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) { + word32 props_len = 0; + int props_tmp; + + /* Decode Length of Properties */ + if (rx_buf_len < (rx_payload - rx_buf)) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + props_tmp = MqttDecode_Vbi(rx_payload, &props_len, + (word32)(rx_buf_len - (rx_payload - rx_buf))); + if (props_tmp < 0) { + return props_tmp; + } + rx_payload += props_tmp; + if (props_len > 0) { + /* Decode the Properties */ + props_tmp = MqttDecode_Props(MQTT_PACKET_TYPE_UNSUBSCRIBE, + &unsubscribe->props, rx_payload, + (word32)(rx_buf_len - (rx_payload - rx_buf)), + props_len); + if (props_tmp < 0) { + return props_tmp; + } + rx_payload += props_tmp; + } + } +#endif + + unsubscribe->topic_count = 0; + if (unsubscribe->topics == NULL) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); + } + + while (rx_payload < rx_end) { + MqttTopic *topic; + if (unsubscribe->topic_count >= MAX_MQTT_TOPICS) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + topic = &unsubscribe->topics[unsubscribe->topic_count]; + tmp = MqttDecode_String(rx_payload, &topic->topic_filter, NULL, + (word32)(rx_end - rx_payload)); + if (tmp < 0) { + return tmp; + } + if (rx_payload + tmp > rx_end) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + rx_payload += tmp; + unsubscribe->topic_count++; + } + } + + (void)rx_payload; + + /* Return total length of packet */ + return header_len + remain_len; } #endif /* WOLFMQTT_BROKER */ @@ -1627,7 +2061,74 @@ int MqttDecode_UnsubscribeAck(byte *rx_buf, int rx_buf_len, #ifdef WOLFMQTT_BROKER int MqttEncode_UnsubscribeAck(byte *tx_buf, int tx_buf_len, MqttUnsubscribeAck *unsubscribe_ack) { + int header_len, remain_len; + byte *tx_payload; +#ifdef WOLFMQTT_V5 + int props_len = 0; + int reason_code_count = 0; +#endif + /* Validate required arguments */ + if (tx_buf == NULL || unsubscribe_ack == NULL) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); + } + + /* Determine packet length */ + remain_len = MQTT_DATA_LEN_SIZE; /* For packet_id */ +#ifdef WOLFMQTT_V5 + if (unsubscribe_ack->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) { + /* Determine length of properties */ + props_len = MqttEncode_Props(MQTT_PACKET_TYPE_UNSUBSCRIBE_ACK, + unsubscribe_ack->props, NULL); + if (props_len >= 0) { + remain_len += props_len; + /* Determine the length of the "property length" */ + remain_len += MqttEncode_Vbi(NULL, props_len); + } + else + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_PROPERTY); + + if (unsubscribe_ack->reason_codes != NULL) { + reason_code_count = 1; + remain_len += reason_code_count; + } + } +#endif + + /* Encode fixed header */ + header_len = MqttEncode_FixedHeader(tx_buf, tx_buf_len, remain_len, + MQTT_PACKET_TYPE_UNSUBSCRIBE_ACK, 0, 0, 0); + if (header_len < 0) { + return header_len; + } + /* Check for buffer room */ + if (tx_buf_len < header_len + remain_len) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_OUT_OF_BUFFER); + } + tx_payload = &tx_buf[header_len]; + + /* Encode variable header */ + tx_payload += MqttEncode_Num(&tx_buf[header_len], unsubscribe_ack->packet_id); + +#ifdef WOLFMQTT_V5 + if (unsubscribe_ack->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) { + /* Encode the property length */ + tx_payload += MqttEncode_Vbi(tx_payload, props_len); + + /* Encode properties */ + tx_payload += MqttEncode_Props(MQTT_PACKET_TYPE_UNSUBSCRIBE_ACK, + unsubscribe_ack->props, tx_payload); + + if (reason_code_count > 0) { + *tx_payload++ = unsubscribe_ack->reason_codes[0]; + } + } +#endif + + (void)tx_payload; + + /* Return total length of packet */ + return header_len + remain_len; } #endif /* WOLFMQTT_BROKER */ @@ -1756,12 +2257,31 @@ int MqttEncode_Disconnect(byte *tx_buf, int tx_buf_len, return header_len + remain_len; } -#ifdef WOLFMQTT_BROKER +#if defined(WOLFMQTT_BROKER) && !defined(WOLFMQTT_V5) int MqttDecode_Disconnect(byte *rx_buf, int rx_buf_len, MqttDisconnect* disc) { + int header_len, remain_len; + + /* Validate required arguments */ + if (rx_buf == NULL || rx_buf_len <= 0) { + return MQTT_TRACE_ERROR(MQTT_CODE_ERROR_BAD_ARG); + } + + /* Decode fixed header */ + header_len = MqttDecode_FixedHeader(rx_buf, rx_buf_len, &remain_len, + MQTT_PACKET_TYPE_DISCONNECT, NULL, NULL, NULL); + if (header_len < 0) { + return header_len; + } + if (disc) { + /* nothing to decode for v3.1.1 */ + } + + /* Return total length of packet */ + return header_len + remain_len; } -#endif /* WOLFMQTT_BROKER */ +#endif /* WOLFMQTT_BROKER && !WOLFMQTT_V5 */ #ifdef WOLFMQTT_V5 diff --git a/wolfmqtt/mqtt_broker.h b/wolfmqtt/mqtt_broker.h index fd99ce913..520201405 100644 --- a/wolfmqtt/mqtt_broker.h +++ b/wolfmqtt/mqtt_broker.h @@ -28,6 +28,7 @@ #ifdef WOLFMQTT_BROKER +WOLFMQTT_API int wolfmqtt_broker(int argc, char** argv); #endif diff --git a/wolfmqtt/mqtt_packet.h b/wolfmqtt/mqtt_packet.h index 706a07c4d..5e90bb501 100644 --- a/wolfmqtt/mqtt_packet.h +++ b/wolfmqtt/mqtt_packet.h @@ -319,7 +319,7 @@ typedef struct _MqttPendResp { /* CONNECT PACKET */ /* Connect flag bit-mask: Located in byte 8 of the MqttConnect packet */ #define MQTT_CONNECT_FLAG_GET_QOS(flags) \ - (((flags) MQTT_CONNECT_FLAG_WILL_QOS_MASK) >> \ + (((flags) & MQTT_CONNECT_FLAG_WILL_QOS_MASK) >> \ MQTT_CONNECT_FLAG_WILL_QOS_SHIFT) #define MQTT_CONNECT_FLAG_SET_QOS(qos) \ (((qos) << MQTT_CONNECT_FLAG_WILL_QOS_SHIFT) & \ @@ -646,14 +646,14 @@ typedef union _MqttObject { /* MQTT PACKET APPLICATION INTERFACE */ struct _MqttClient; /* Packet Read/Write */ -WOLFMQTT_LOCAL int MqttPacket_Write(struct _MqttClient *client, byte* tx_buf, +WOLFMQTT_API int MqttPacket_Write(struct _MqttClient *client, byte* tx_buf, int tx_buf_len); -WOLFMQTT_LOCAL int MqttPacket_Read(struct _MqttClient *client, byte* rx_buf, +WOLFMQTT_API int MqttPacket_Read(struct _MqttClient *client, byte* rx_buf, int rx_buf_len, int timeout_ms); /* Packet Element Encoders/Decoders */ -WOLFMQTT_LOCAL int MqttDecode_Num(byte* buf, word16 *len, word32 buf_len); -WOLFMQTT_LOCAL int MqttEncode_Num(byte *buf, word16 len); +WOLFMQTT_API int MqttDecode_Num(byte* buf, word16 *len, word32 buf_len); +WOLFMQTT_API int MqttEncode_Num(byte *buf, word16 len); WOLFMQTT_LOCAL int MqttDecode_Int(byte* buf, word32* len); WOLFMQTT_LOCAL int MqttEncode_Int(byte* buf, word32 len); @@ -665,51 +665,51 @@ WOLFMQTT_LOCAL int MqttEncode_String(byte *buf, const char *str); WOLFMQTT_LOCAL int MqttEncode_Data(byte *buf, const byte *data, word16 data_len); -WOLFMQTT_LOCAL int MqttDecode_Vbi(byte *buf, word32 *value, word32 buf_len); -WOLFMQTT_LOCAL int MqttEncode_Vbi(byte *buf, word32 x); +WOLFMQTT_API int MqttDecode_Vbi(byte *buf, word32 *value, word32 buf_len); +WOLFMQTT_API int MqttEncode_Vbi(byte *buf, word32 x); /* Packet Encoders/Decoders */ -WOLFMQTT_LOCAL int MqttEncode_Connect(byte *tx_buf, int tx_buf_len, +WOLFMQTT_API int MqttEncode_Connect(byte *tx_buf, int tx_buf_len, MqttConnect *connect); -WOLFMQTT_LOCAL int MqttDecode_ConnectAck(byte *rx_buf, int rx_buf_len, +WOLFMQTT_API int MqttDecode_ConnectAck(byte *rx_buf, int rx_buf_len, MqttConnectAck *connect_ack); -WOLFMQTT_LOCAL int MqttEncode_Publish(byte *tx_buf, int tx_buf_len, +WOLFMQTT_API int MqttEncode_Publish(byte *tx_buf, int tx_buf_len, MqttPublish *publish, byte use_cb); -WOLFMQTT_LOCAL int MqttDecode_Publish(byte *rx_buf, int rx_buf_len, +WOLFMQTT_API int MqttDecode_Publish(byte *rx_buf, int rx_buf_len, MqttPublish *publish); -WOLFMQTT_LOCAL int MqttEncode_PublishResp(byte* tx_buf, int tx_buf_len, +WOLFMQTT_API int MqttEncode_PublishResp(byte* tx_buf, int tx_buf_len, byte type, MqttPublishResp *publish_resp); -WOLFMQTT_LOCAL int MqttDecode_PublishResp(byte* rx_buf, int rx_buf_len, +WOLFMQTT_API int MqttDecode_PublishResp(byte* rx_buf, int rx_buf_len, byte type, MqttPublishResp *publish_resp); -WOLFMQTT_LOCAL int MqttEncode_Subscribe(byte *tx_buf, int tx_buf_len, +WOLFMQTT_API int MqttEncode_Subscribe(byte *tx_buf, int tx_buf_len, MqttSubscribe *subscribe); -WOLFMQTT_LOCAL int MqttDecode_SubscribeAck(byte* rx_buf, int rx_buf_len, +WOLFMQTT_API int MqttDecode_SubscribeAck(byte* rx_buf, int rx_buf_len, MqttSubscribeAck *subscribe_ack); -WOLFMQTT_LOCAL int MqttEncode_Unsubscribe(byte *tx_buf, int tx_buf_len, +WOLFMQTT_API int MqttEncode_Unsubscribe(byte *tx_buf, int tx_buf_len, MqttUnsubscribe *unsubscribe); -WOLFMQTT_LOCAL int MqttDecode_UnsubscribeAck(byte *rx_buf, int rx_buf_len, +WOLFMQTT_API int MqttDecode_UnsubscribeAck(byte *rx_buf, int rx_buf_len, MqttUnsubscribeAck *unsubscribe_ack); -WOLFMQTT_LOCAL int MqttEncode_Ping(byte *tx_buf, int tx_buf_len, MqttPing* ping); -WOLFMQTT_LOCAL int MqttDecode_Ping(byte *rx_buf, int rx_buf_len, MqttPing* ping); -WOLFMQTT_LOCAL int MqttEncode_Disconnect(byte *tx_buf, int tx_buf_len, +WOLFMQTT_API int MqttEncode_Ping(byte *tx_buf, int tx_buf_len, MqttPing* ping); +WOLFMQTT_API int MqttDecode_Ping(byte *rx_buf, int rx_buf_len, MqttPing* ping); +WOLFMQTT_API int MqttEncode_Disconnect(byte *tx_buf, int tx_buf_len, MqttDisconnect* disc); #ifdef WOLFMQTT_V5 -WOLFMQTT_LOCAL int MqttDecode_Disconnect(byte *rx_buf, int rx_buf_len, +WOLFMQTT_API int MqttDecode_Disconnect(byte *rx_buf, int rx_buf_len, MqttDisconnect *disc); -WOLFMQTT_LOCAL int MqttDecode_Auth(byte *rx_buf, int rx_buf_len, +WOLFMQTT_API int MqttDecode_Auth(byte *rx_buf, int rx_buf_len, MqttAuth *auth); -WOLFMQTT_LOCAL int MqttEncode_Auth(byte *tx_buf, int tx_buf_len, +WOLFMQTT_API int MqttEncode_Auth(byte *tx_buf, int tx_buf_len, MqttAuth *auth); -WOLFMQTT_LOCAL int MqttEncode_Props(MqttPacketType packet, MqttProp* props, +WOLFMQTT_API int MqttEncode_Props(MqttPacketType packet, MqttProp* props, byte* buf); -WOLFMQTT_LOCAL int MqttDecode_Props(MqttPacketType packet, MqttProp** props, +WOLFMQTT_API int MqttDecode_Props(MqttPacketType packet, MqttProp** props, byte* buf, word32 buf_len, word32 prop_len); -WOLFMQTT_LOCAL int MqttProps_Init(void); -WOLFMQTT_LOCAL int MqttProps_ShutDown(void); -WOLFMQTT_LOCAL MqttProp* MqttProps_Add(MqttProp **head); -WOLFMQTT_LOCAL int MqttProps_Free(MqttProp *head); -WOLFMQTT_LOCAL MqttProp* MqttProps_FindType(MqttProp *head, +WOLFMQTT_API int MqttProps_Init(void); +WOLFMQTT_API int MqttProps_ShutDown(void); +WOLFMQTT_API MqttProp* MqttProps_Add(MqttProp **head); +WOLFMQTT_API int MqttProps_Free(MqttProp *head); +WOLFMQTT_API MqttProp* MqttProps_FindType(MqttProp *head, MqttPropertyType type); #endif @@ -720,12 +720,18 @@ WOLFMQTT_LOCAL MqttProp* MqttProps_FindType(MqttProp *head, #endif #ifdef WOLFMQTT_BROKER -WOLFMQTT_LOCAL int MqttDecode_Connect(byte *rx_buf, int rx_buf_len, MqttConnect *mc_connect); -WOLFMQTT_LOCAL int MqttEncode_ConnectAck(byte *tx_buf, int tx_buf_len MqttConnectAck *connect_ack); -WOLFMQTT_LOCAL int MqttDecode_Subscribe(byte *rx_buf, int rx_buf_len, MqttSubscribe *subscribe); -WOLFMQTT_LOCAL int MqttDecode_Unsubscribe(byte *rx_buf, int rx_buf_len, MqttUnsubscribe *unsubscribe); -WOLFMQTT_LOCAL int MqttEncode_UnsubscribeAck(byte *tx_buf, int tx_buf_len, MqttUnsubscribeAck *unsubscribe_ack); -WOLFMQTT_LOCAL int MqttDecode_Disconnect(byte *rx_buf, int rx_buf_len, MqttDisconnect* disc); +WOLFMQTT_API int MqttDecode_Connect(byte *rx_buf, int rx_buf_len, + MqttConnect *mc_connect); +WOLFMQTT_API int MqttEncode_ConnectAck(byte *tx_buf, int tx_buf_len, + MqttConnectAck *connect_ack); +WOLFMQTT_API int MqttDecode_Subscribe(byte *rx_buf, int rx_buf_len, + MqttSubscribe *subscribe); +WOLFMQTT_API int MqttDecode_Unsubscribe(byte *rx_buf, int rx_buf_len, + MqttUnsubscribe *unsubscribe); +WOLFMQTT_API int MqttEncode_UnsubscribeAck(byte *tx_buf, int tx_buf_len, + MqttUnsubscribeAck *unsubscribe_ack); +WOLFMQTT_API int MqttDecode_Disconnect(byte *rx_buf, int rx_buf_len, + MqttDisconnect* disc); #endif #ifdef __cplusplus From 3bf853a623b1037173ab77d2501f82fca8440dba Mon Sep 17 00:00:00 2001 From: David Garske Date: Tue, 3 Feb 2026 15:31:19 -0800 Subject: [PATCH 03/13] Progress with multiple clients and tracking topics and directing publish messages. --- scripts/broker_multi_client_test.sh | 204 +++++++++++++++++++++++++ src/include.am | 3 +- src/mqtt_broker.c | 221 +++++++++++----------------- wolfmqtt/mqtt_broker.h | 2 + wolfmqtt/mqtt_packet.h | 2 + wolfmqtt/mqtt_types.h | 3 + 6 files changed, 301 insertions(+), 134 deletions(-) create mode 100755 scripts/broker_multi_client_test.sh diff --git a/scripts/broker_multi_client_test.sh b/scripts/broker_multi_client_test.sh new file mode 100755 index 000000000..c8097d905 --- /dev/null +++ b/scripts/broker_multi_client_test.sh @@ -0,0 +1,204 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOST="localhost" +PORT="1883" +USER="" +PASS="" +NUM_CLIENTS="2" +READY_TIMEOUT="30" +RUN_SECS="5" +KEEP_LOGS="${KEEP_LOGS:-0}" + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +PUB_BIN="${ROOT_DIR}/examples/pub-sub/mqtt-pub" +SUB_BIN="${ROOT_DIR}/examples/pub-sub/mqtt-sub" + +usage() { + cat <= 2 +if (( NUM_CLIENTS < 2 || NUM_CLIENTS % 2 != 0 )); then + echo "error: -n must be an even number >= 2 (got ${NUM_CLIENTS})" + exit 1 +fi + +NUM_PAIRS=$(( NUM_CLIENTS / 2 )) + +if [[ ! -x "${PUB_BIN}" || ! -x "${SUB_BIN}" ]]; then + echo "error: mqtt-pub or mqtt-sub not found. Build examples first." + exit 1 +fi + +TMP_DIR="$(mktemp -d)" +PIDS=() + +cleanup() { + for pid in "${PIDS[@]:-}"; do + kill "${pid}" 2>/dev/null || true + done + wait 2>/dev/null || true + if [[ "${KEEP_LOGS}" == "1" ]]; then + echo "Logs preserved in ${TMP_DIR}" + else + rm -rf "${TMP_DIR}" + fi +} +trap cleanup EXIT + +auth_args=() +if [[ -n "${USER}" ]]; then auth_args+=(-u "${USER}"); fi +if [[ -n "${PASS}" ]]; then auth_args+=(-w "${PASS}"); fi + +# All subscriber log files (for readiness polling) +SUB_LOGS=() + +start_sub() { + local client_id="$1" + local topic="$2" + local log="$3" + # -T = test mode (disables STDIN capture so background processes work) + # -d = debug output (needed to detect readiness via log polling) + # -i = unique client ID + # stdbuf -oL = line-buffered stdout so grep can detect readiness in logs + stdbuf -oL "${SUB_BIN}" -T -h "${HOST}" -p "${PORT}" -i "${client_id}" \ + -n "${topic}" -q 0 -d "${auth_args[@]}" >"${log}" 2>&1 & + PIDS+=("$!") + SUB_LOGS+=("${log}") +} + +run_pub() { + local client_id="$1" + local topic="$2" + local msg="$3" + # -T = test mode (disables STDIN capture) + # -i = unique client ID + "${PUB_BIN}" -T -h "${HOST}" -p "${PORT}" -i "${client_id}" -n "${topic}" \ + -m "${msg}" -q 0 "${auth_args[@]}" >/dev/null 2>&1 +} + +# Wait until all subscriber logs contain the ready marker +wait_for_subscribers() { + local total="${#SUB_LOGS[@]}" + local elapsed=0 + while (( elapsed < READY_TIMEOUT )); do + local ready_count=0 + for log in "${SUB_LOGS[@]}"; do + if grep -q "MQTT Waiting for message" "${log}" 2>/dev/null; then + (( ready_count++ )) || true + fi + done + if (( ready_count == total )); then + echo "All ${total} subscriber(s) ready after ${elapsed}s" + return 0 + fi + if (( elapsed > 0 && elapsed % 5 == 0 )); then + echo " ... ${ready_count}/${total} subscribers ready (${elapsed}s elapsed)" + fi + sleep 1 + (( elapsed++ )) || true + done + echo "WARNING: Not all subscribers ready after ${READY_TIMEOUT}s (continuing anyway)" + return 0 +} + +echo "Starting ${NUM_CLIENTS} clients (${NUM_PAIRS} pairs)..." + +# --- Start all subscribers --- +for p in $(seq 0 $(( NUM_PAIRS - 1 ))); do + start_sub "sub_${p}_a" "pair/${p}/a" "${TMP_DIR}/sub_${p}_a.log" + start_sub "sub_${p}_b" "pair/${p}/b" "${TMP_DIR}/sub_${p}_b.log" +done + +echo "Waiting for subscribers to connect and subscribe..." +wait_for_subscribers + +# --- Publish: each client publishes to its partner's topic --- +echo "Publishing ${NUM_CLIENTS} messages..." +for p in $(seq 0 $(( NUM_PAIRS - 1 ))); do + # Client A publishes to pair/P/b (B's topic) + run_pub "pub_${p}_a" "pair/${p}/b" "hello_from_${p}_a" + # Client B publishes to pair/P/a (A's topic) + run_pub "pub_${p}_b" "pair/${p}/a" "hello_from_${p}_b" +done + +# --- Wait for message delivery --- +echo "Waiting ${RUN_SECS}s for message delivery..." +sleep "${RUN_SECS}" + +# --- Check results --- +PASS_COUNT=0 +FAIL_COUNT=0 + +check_result() { + local description="$1" + local pattern="$2" + local log="$3" + if grep -q "${pattern}" "${log}" 2>/dev/null; then + (( PASS_COUNT++ )) || true + else + echo "FAIL: ${description}" + (( FAIL_COUNT++ )) || true + fi +} + +echo "" +echo "== Results ==" +for p in $(seq 0 $(( NUM_PAIRS - 1 ))); do + # A subscribed to pair/P/a, should have received hello_from_P_b + check_result "pair ${p} A (sub pair/${p}/a) did not receive hello_from_${p}_b" \ + "hello_from_${p}_b" "${TMP_DIR}/sub_${p}_a.log" + # B subscribed to pair/P/b, should have received hello_from_P_a + check_result "pair ${p} B (sub pair/${p}/b) did not receive hello_from_${p}_a" \ + "hello_from_${p}_a" "${TMP_DIR}/sub_${p}_b.log" +done + +echo "${PASS_COUNT} passed, ${FAIL_COUNT} failed (${NUM_CLIENTS} clients, ${NUM_PAIRS} pairs)" + +if (( FAIL_COUNT > 0 )); then + # Preserve logs on failure regardless of KEEP_LOGS setting + KEEP_LOGS=1 + exit 1 +fi +exit 0 diff --git a/src/include.am b/src/include.am index 9d6a2cef4..14bcb2438 100644 --- a/src/include.am +++ b/src/include.am @@ -23,7 +23,8 @@ EXTRA_DIST += if BUILD_BROKER bin_PROGRAMS += src/mqtt_broker src_mqtt_broker_SOURCES = src/mqtt_broker.c -src_mqtt_broker_CFLAGS = +src_mqtt_broker_CFLAGS = $(AM_CFLAGS) +src_mqtt_broker_CPPFLAGS = $(AM_CPPFLAGS) src_mqtt_broker_LDFLAGS = -Lsrc src_mqtt_broker_LDADD = src/libwolfmqtt.la $(LTLIBEVENT) $(LIB_STATIC_ADD) src_mqtt_broker_DEPENDENCIES = src/libwolfmqtt.la diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index 51a92eba8..07c6bc189 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -44,8 +44,6 @@ #ifdef WOLFMQTT_BROKER -int wolfmqtt_broker(int argc, char** argv); - typedef struct BrokerClient { int fd; byte protocol_level; @@ -69,11 +67,21 @@ typedef struct BrokerSub { struct BrokerSub* next; } BrokerSub; -#define BROKER_RX_BUF_SZ 4096 -#define BROKER_TX_BUF_SZ 4096 -#define BROKER_TIMEOUT_MS 1000 -#define BROKER_LISTEN_BACKLOG 8 -#define BROKER_LOG_PKT 1 +#ifndef BROKER_RX_BUF_SZ + #define BROKER_RX_BUF_SZ 4096 +#endif +#ifndef BROKER_TX_BUF_SZ + #define BROKER_TX_BUF_SZ 4096 +#endif +#ifndef BROKER_TIMEOUT_MS + #define BROKER_TIMEOUT_MS 1000 +#endif +#ifndef BROKER_LISTEN_BACKLOG + #define BROKER_LISTEN_BACKLOG 128 +#endif +#ifndef BROKER_LOG_PKT + #define BROKER_LOG_PKT 1 +#endif static int BrokerSocket_SetNonBlocking(int fd) { @@ -442,7 +450,10 @@ static int BrokerSend_SubAck(BrokerClient* bc, word16 packet_id, return MqttPacket_Write(&bc->client, bc->tx_buf, pos); } -static int BrokerHandle_Connect(BrokerClient* bc, int rx_len) +/* Returns: > 0 success, 0 auth rejected (CONNACK sent with refused), + * < 0 error */ +static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, + const char* auth_user, const char* auth_pass) { int rc; MqttConnect mc; @@ -461,6 +472,7 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len) return rc; } + /* Store client ID */ if (bc->client_id) { WOLFMQTT_FREE(bc->client_id); bc->client_id = NULL; @@ -479,16 +491,11 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len) bc->protocol_level = mc.protocol_level; bc->keep_alive_sec = mc.keep_alive_sec; bc->last_rx = time(NULL); - PRINTF("broker: CONNECT proto=%u clean=%d will=%d", - mc.protocol_level, mc.clean_session, mc.enable_lwt); - - ack.flags = 0; - ack.return_code = MQTT_CONNECT_ACK_CODE_ACCEPTED; -#ifdef WOLFMQTT_V5 - ack.protocol_level = mc.protocol_level; - ack.props = NULL; -#endif + PRINTF("broker: CONNECT proto=%u clean=%d will=%d client_id=%s", + mc.protocol_level, mc.clean_session, mc.enable_lwt, + bc->client_id ? bc->client_id : "(null)"); + /* Store credentials */ if (bc->username) { WOLFMQTT_FREE(bc->username); bc->username = NULL; @@ -519,13 +526,36 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len) } } } + + /* Check auth before sending CONNACK */ + ack.flags = 0; + ack.return_code = MQTT_CONNECT_ACK_CODE_ACCEPTED; #ifdef WOLFMQTT_V5 ack.protocol_level = mc.protocol_level; ack.props = NULL; #endif + + if (auth_user || auth_pass) { + int auth_ok = 1; + if (auth_user && (!bc->username || + XSTRCMP(auth_user, bc->username) != 0)) { + auth_ok = 0; + } + if (auth_pass && (!bc->password || + XSTRCMP(auth_pass, bc->password) != 0)) { + auth_ok = 0; + } + if (!auth_ok) { + PRINTF("broker: auth failed fd=%d user=%s", bc->fd, + bc->username ? bc->username : "(null)"); + ack.return_code = MQTT_CONNECT_ACK_CODE_REFUSED_BAD_USER_PWD; + } + } + rc = MqttEncode_ConnectAck(bc->tx_buf, bc->tx_buf_len, &ack); if (rc > 0) { - PRINTF("broker: CONNACK send fd=%d", bc->fd); + PRINTF("broker: CONNACK send fd=%d code=%d", bc->fd, + ack.return_code); rc = MqttPacket_Write(&bc->client, bc->tx_buf, rc); } @@ -537,10 +567,16 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len) (void)MqttProps_Free(lwt.props); } #endif + + /* Return 0 if auth rejected so caller can disconnect */ + if (ack.return_code != MQTT_CONNECT_ACK_CODE_ACCEPTED) { + return 0; + } return rc; } -static int BrokerHandle_Subscribe(BrokerClient* bc, int rx_len) +static int BrokerHandle_Subscribe(BrokerClient* bc, int rx_len, + BrokerSub** sub_list) { int rc; int i; @@ -565,7 +601,14 @@ static int BrokerHandle_Subscribe(BrokerClient* bc, int rx_len) return rc; } + /* Register subscriptions and build return codes */ for (i = 0; i < sub.topic_count && i < MAX_MQTT_TOPICS; i++) { + const char* f = sub.topics[i].topic_filter; + word16 flen = 0; + if (f && MqttDecode_Num((byte*)f - MQTT_DATA_LEN_SIZE, + &flen, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) { + (void)BrokerSubs_Add(sub_list, bc, f, flen); + } return_codes[i] = MQTT_SUBSCRIBE_ACK_CODE_SUCCESS_MAX_QOS0; } @@ -581,9 +624,11 @@ static int BrokerHandle_Subscribe(BrokerClient* bc, int rx_len) return rc; } -static int BrokerHandle_Unsubscribe(BrokerClient* bc, int rx_len) +static int BrokerHandle_Unsubscribe(BrokerClient* bc, int rx_len, + BrokerSub** sub_list) { int rc; + int i; MqttUnsubscribe unsub; MqttUnsubscribeAck ack; @@ -605,6 +650,16 @@ static int BrokerHandle_Unsubscribe(BrokerClient* bc, int rx_len) return rc; } + /* Remove subscriptions */ + for (i = 0; i < unsub.topic_count && i < MAX_MQTT_TOPICS; i++) { + const char* f = unsub.topics[i].topic_filter; + word16 flen = 0; + if (f && MqttDecode_Num((byte*)f - MQTT_DATA_LEN_SIZE, + &flen, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) { + BrokerSubs_Remove(sub_list, bc, f, flen); + } + } + XMEMSET(&ack, 0, sizeof(ack)); ack.packet_id = unsub.packet_id; #ifdef WOLFMQTT_V5 @@ -879,6 +934,7 @@ int wolfmqtt_broker(int argc, char** argv) PRINTF("broker: read failed fd=%d rc=%d", bc->fd, rc); BrokerSubs_RemoveClient(&subs, bc); BrokerClient_Remove(&clients, bc); + bc = NULL; } else if (rc > 0) { byte type = MQTT_PACKET_TYPE_GET(bc->rx_buf[0]); @@ -889,41 +945,17 @@ int wolfmqtt_broker(int argc, char** argv) #endif switch (type) { case MQTT_PACKET_TYPE_CONNECT: - (void)BrokerHandle_Connect(bc, rc); - if (auth_user || auth_pass) { - int ok = 1; - if (auth_user && (!bc->username || - XSTRCMP(auth_user, bc->username) != 0)) { - ok = 0; - } - if (auth_pass && (!bc->password || - XSTRCMP(auth_pass, bc->password) != 0)) { - ok = 0; - } - if (!ok) { - MqttConnectAck nack; - XMEMSET(&nack, 0, sizeof(nack)); - nack.flags = 0; - nack.return_code = MQTT_CONNECT_ACK_CODE_REFUSED_BAD_USER_PWD; -#ifdef WOLFMQTT_V5 - nack.protocol_level = bc->protocol_level; - nack.props = NULL; -#endif - { - int nrc = MqttEncode_ConnectAck( - bc->tx_buf, bc->tx_buf_len, - &nack); - if (nrc > 0) { - (void)MqttPacket_Write(&bc->client, - bc->tx_buf, nrc); - } - } - PRINTF("broker: auth failed fd=%d", bc->fd); - BrokerSubs_RemoveClient(&subs, bc); - BrokerClient_Remove(&clients, bc); - } + { + int c_rc = BrokerHandle_Connect(bc, rc, + auth_user, auth_pass); + if (c_rc == 0) { + /* Auth rejected, disconnect */ + BrokerSubs_RemoveClient(&subs, bc); + BrokerClient_Remove(&clients, bc); + bc = NULL; } break; + } case MQTT_PACKET_TYPE_PUBLISH: (void)BrokerHandle_Publish(bc, rc, subs); break; @@ -931,95 +963,18 @@ int wolfmqtt_broker(int argc, char** argv) (void)BrokerHandle_PublishRel(bc, rc); break; case MQTT_PACKET_TYPE_SUBSCRIBE: - { - int s_rc = BrokerHandle_Subscribe(bc, rc); - if (s_rc >= 0) { - int idx; - MqttSubscribe sub; - XMEMSET(&sub, 0, sizeof(sub)); -#ifdef WOLFMQTT_V5 - sub.protocol_level = bc->protocol_level; -#endif - sub.topics = (MqttTopic*)WOLFMQTT_MALLOC( - sizeof(MqttTopic) * MAX_MQTT_TOPICS); - if (sub.topics) { - XMEMSET(sub.topics, 0, - sizeof(MqttTopic) * MAX_MQTT_TOPICS); - if (MqttDecode_Subscribe(bc->rx_buf, rc, - &sub) >= 0) { - for (idx = 0; idx < sub.topic_count; - idx++) { - const char* f = sub.topics[idx].topic_filter; - word16 flen = 0; - if (f && - MqttDecode_Num((byte*)f - - MQTT_DATA_LEN_SIZE, - &flen, - MQTT_DATA_LEN_SIZE) == - MQTT_DATA_LEN_SIZE) { - (void)BrokerSubs_Add(&subs, bc, - f, flen); - } - } - } -#ifdef WOLFMQTT_V5 - if (sub.props) { - (void)MqttProps_Free(sub.props); - } -#endif - WOLFMQTT_FREE(sub.topics); - } - } + (void)BrokerHandle_Subscribe(bc, rc, &subs); break; - } case MQTT_PACKET_TYPE_UNSUBSCRIBE: - { - int u_rc = BrokerHandle_Unsubscribe(bc, rc); - if (u_rc >= 0) { - int idx; - MqttUnsubscribe unsub; - XMEMSET(&unsub, 0, sizeof(unsub)); -#ifdef WOLFMQTT_V5 - unsub.protocol_level = bc->protocol_level; -#endif - unsub.topics = (MqttTopic*)WOLFMQTT_MALLOC( - sizeof(MqttTopic) * MAX_MQTT_TOPICS); - if (unsub.topics) { - XMEMSET(unsub.topics, 0, - sizeof(MqttTopic) * MAX_MQTT_TOPICS); - if (MqttDecode_Unsubscribe(bc->rx_buf, rc, - &unsub) >= 0) { - for (idx = 0; idx < unsub.topic_count; - idx++) { - const char* f = unsub.topics[idx].topic_filter; - word16 flen = 0; - if (f && - MqttDecode_Num((byte*)f - - MQTT_DATA_LEN_SIZE, - &flen, - MQTT_DATA_LEN_SIZE) == - MQTT_DATA_LEN_SIZE) { - BrokerSubs_Remove(&subs, bc, - f, flen); - } - } - } -#ifdef WOLFMQTT_V5 - if (unsub.props) { - (void)MqttProps_Free(unsub.props); - } -#endif - WOLFMQTT_FREE(unsub.topics); - } - } + (void)BrokerHandle_Unsubscribe(bc, rc, &subs); break; - } case MQTT_PACKET_TYPE_PING_REQ: (void)BrokerSend_PingResp(bc); break; case MQTT_PACKET_TYPE_DISCONNECT: BrokerSubs_RemoveClient(&subs, bc); BrokerClient_Remove(&clients, bc); + bc = NULL; break; default: break; diff --git a/wolfmqtt/mqtt_broker.h b/wolfmqtt/mqtt_broker.h index 520201405..fcb08ab76 100644 --- a/wolfmqtt/mqtt_broker.h +++ b/wolfmqtt/mqtt_broker.h @@ -22,6 +22,8 @@ #ifndef WOLFMQTT_BROKER_H #define WOLFMQTT_BROKER_H +#include "wolfmqtt/mqtt_types.h" + #ifdef __cplusplus extern "C" { #endif diff --git a/wolfmqtt/mqtt_packet.h b/wolfmqtt/mqtt_packet.h index 5e90bb501..ad0b99f80 100644 --- a/wolfmqtt/mqtt_packet.h +++ b/wolfmqtt/mqtt_packet.h @@ -730,9 +730,11 @@ WOLFMQTT_API int MqttDecode_Unsubscribe(byte *rx_buf, int rx_buf_len, MqttUnsubscribe *unsubscribe); WOLFMQTT_API int MqttEncode_UnsubscribeAck(byte *tx_buf, int tx_buf_len, MqttUnsubscribeAck *unsubscribe_ack); +#ifndef WOLFMQTT_V5 WOLFMQTT_API int MqttDecode_Disconnect(byte *rx_buf, int rx_buf_len, MqttDisconnect* disc); #endif +#endif #ifdef __cplusplus } /* extern "C" */ diff --git a/wolfmqtt/mqtt_types.h b/wolfmqtt/mqtt_types.h index 69de543b4..bed32a553 100644 --- a/wolfmqtt/mqtt_types.h +++ b/wolfmqtt/mqtt_types.h @@ -220,6 +220,9 @@ enum MqttPacketResponseCodes { #ifndef XSTRCHR #define XSTRCHR(s,c) strchr((s),(c)) #endif + #ifndef XSTRCMP + #define XSTRCMP(s1,s2) strcmp((s1),(s2)) + #endif #ifndef XSTRNCMP #define XSTRNCMP(s1,s2,n) strncmp((s1),(s2),(n)) #endif From 79f2701d4665e97743b861df5e80b65a53922ebc Mon Sep 17 00:00:00 2001 From: David Garske Date: Wed, 4 Feb 2026 21:55:36 -0800 Subject: [PATCH 04/13] Portability improvements --- CMakeLists.txt | 17 + configure.ac | 1 + src/mqtt_broker.c | 1068 ++++++++++++++++++++++++++++------------ wolfmqtt/mqtt_broker.h | 178 ++++++- 4 files changed, 949 insertions(+), 315 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fe13be5d5..6b4f64a39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,6 +184,14 @@ if(ENABLE_WEBSOCKET) endif() endif() +# Broker +add_option(WOLFMQTT_BROKER + "Enable lightweight broker support" + "no" "yes;no") +if (WOLFMQTT_BROKER) + list(APPEND WOLFMQTT_DEFINITIONS "-DWOLFMQTT_BROKER") +endif() + # Note: not adding stress option to cmake build as of yet. stress is for # testing only and requires the scripts/ dir to be useful. @@ -286,6 +294,14 @@ if (WOLFMQTT_EXAMPLES) add_mqtt_example(mqtt-sub pub-sub/mqtt-sub.c) endif() +if (WOLFMQTT_BROKER) + add_executable(mqtt_broker src/mqtt_broker.c) + target_link_libraries(mqtt_broker wolfmqtt) + target_include_directories(mqtt_broker PRIVATE + $ + $) +endif() + #################################################### # Installation #################################################### @@ -326,4 +342,5 @@ message("\tExamples: ${ENABLE_EXAMPLES}") message("\tFirmware Examples: ${ENABLE_FIRMWARE_EXAMPLES}") message("\tMultithread: ${ENABLE_MULTITHREAD}") message("\tCurl: ${ENABLE_CURL}") +message("\tBroker: ${WOLFMQTT_BROKER}") message("-----------------------------------------------") diff --git a/configure.ac b/configure.ac index 541831819..38ec113fd 100644 --- a/configure.ac +++ b/configure.ac @@ -557,3 +557,4 @@ echo " * CURL: $ENABLED_CURL" echo " * Multi-thread: $ENABLED_MULTITHREAD" echo " * Stress: $ENABLED_STRESS" echo " * WebSocket: $ENABLED_WEBSOCKET" +echo " * Broker: $ENABLED_BROKER" diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index 07c6bc189..cfb13f75c 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -1,7 +1,6 @@ - /* mqtt_broker.c * - * Copyright (C) 2006-2021 wolfSSL Inc. + * Copyright (C) 2006-2025 wolfSSL Inc. * * This file is part of wolfMQTT. * @@ -30,60 +29,62 @@ #include "wolfmqtt/mqtt_packet.h" #include "wolfmqtt/mqtt_socket.h" -#include #include #include -#include -#include -#include -#include -#include -#include -#include - #ifdef WOLFMQTT_BROKER -typedef struct BrokerClient { - int fd; - byte protocol_level; - char* client_id; - char* username; - char* password; - word16 keep_alive_sec; - time_t last_rx; - MqttNet net; - MqttClient client; - byte* tx_buf; - byte* rx_buf; - int tx_buf_len; - int rx_buf_len; - struct BrokerClient* next; -} BrokerClient; - -typedef struct BrokerSub { - char* filter; - BrokerClient* client; - struct BrokerSub* next; -} BrokerSub; - -#ifndef BROKER_RX_BUF_SZ - #define BROKER_RX_BUF_SZ 4096 -#endif -#ifndef BROKER_TX_BUF_SZ - #define BROKER_TX_BUF_SZ 4096 -#endif -#ifndef BROKER_TIMEOUT_MS - #define BROKER_TIMEOUT_MS 1000 -#endif -#ifndef BROKER_LISTEN_BACKLOG - #define BROKER_LISTEN_BACKLOG 128 +/* -------------------------------------------------------------------------- */ +/* Platform includes - only for default POSIX backend */ +/* -------------------------------------------------------------------------- */ +#ifndef WOLFMQTT_BROKER_CUSTOM_NET + #include + #include + #include + #include + #include + #include + #include + #include +#endif /* !WOLFMQTT_BROKER_CUSTOM_NET */ + +/* -------------------------------------------------------------------------- */ +/* Default time abstraction */ +/* -------------------------------------------------------------------------- */ +#ifndef WOLFMQTT_BROKER_GET_TIME_S + #ifndef WOLFMQTT_BROKER_CUSTOM_NET + #define WOLFMQTT_BROKER_GET_TIME_S() \ + ((WOLFMQTT_BROKER_TIME_T)time(NULL)) + #else + #error "WOLFMQTT_BROKER_CUSTOM_NET requires " \ + "WOLFMQTT_BROKER_GET_TIME_S to be defined" + #endif +#endif + +/* -------------------------------------------------------------------------- */ +/* Default sleep abstraction */ +/* -------------------------------------------------------------------------- */ +#ifndef BROKER_SLEEP_MS + #ifdef USE_WINDOWS_API + #define BROKER_SLEEP_MS(ms) Sleep(ms) + #elif !defined(WOLFMQTT_BROKER_CUSTOM_NET) + #define BROKER_SLEEP_MS(ms) usleep((unsigned)(ms) * 1000) + #else + #error "WOLFMQTT_BROKER_CUSTOM_NET requires " \ + "BROKER_SLEEP_MS to be defined" + #endif #endif + #ifndef BROKER_LOG_PKT #define BROKER_LOG_PKT 1 #endif -static int BrokerSocket_SetNonBlocking(int fd) +/* -------------------------------------------------------------------------- */ +/* Default POSIX network backend */ +/* -------------------------------------------------------------------------- */ +#ifndef WOLFMQTT_BROKER_CUSTOM_NET + +static int BrokerPosix_SetNonBlocking(BROKER_SOCKET_T fd) { int flags = fcntl(fd, F_GETFL, 0); if (flags < 0) { @@ -95,35 +96,87 @@ static int BrokerSocket_SetNonBlocking(int fd) return MQTT_CODE_SUCCESS; } -static int BrokerNetConnect(void* context, const char* host, word16 port, - int timeout_ms) +static int BrokerPosix_Listen(void* ctx, BROKER_SOCKET_T* sock, + word16 port, int backlog) { - (void)context; - (void)host; - (void)port; - (void)timeout_ms; - PRINTF("broker: net connect ctx=%p host=%s port=%u timeout=%d", - context, host ? host : "(null)", port, timeout_ms); + struct sockaddr_in addr; + int opt = 1; + BROKER_SOCKET_T fd; + (void)ctx; + + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + PRINTF("broker: socket failed (%d)", errno); + return MQTT_CODE_ERROR_NETWORK; + } + + (void)setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + if (BrokerPosix_SetNonBlocking(fd) != MQTT_CODE_SUCCESS) { + PRINTF("broker: set nonblocking failed (%d)", errno); + close(fd); + return MQTT_CODE_ERROR_SYSTEM; + } + + XMEMSET(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(port); + + if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + PRINTF("broker: bind failed (%d)", errno); + close(fd); + return MQTT_CODE_ERROR_NETWORK; + } + if (listen(fd, backlog) < 0) { + PRINTF("broker: listen failed (%d)", errno); + close(fd); + return MQTT_CODE_ERROR_NETWORK; + } + + *sock = fd; return MQTT_CODE_SUCCESS; } -static int BrokerNetRead(void* context, byte* buf, int buf_len, int timeout_ms) +static int BrokerPosix_Accept(void* ctx, BROKER_SOCKET_T listen_sock, + BROKER_SOCKET_T* client_sock) +{ + BROKER_SOCKET_T fd; + (void)ctx; + + fd = accept(listen_sock, NULL, NULL); + if (fd < 0) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { + return MQTT_CODE_CONTINUE; + } + return MQTT_CODE_ERROR_NETWORK; + } + if (BrokerPosix_SetNonBlocking(fd) != MQTT_CODE_SUCCESS) { + close(fd); + return MQTT_CODE_ERROR_SYSTEM; + } + *client_sock = fd; + return MQTT_CODE_SUCCESS; +} + +static int BrokerPosix_Read(void* ctx, BROKER_SOCKET_T sock, + byte* buf, int buf_len, int timeout_ms) { - BrokerClient* bc = (BrokerClient*)context; fd_set rfds; struct timeval tv; int rc; + (void)ctx; - if (bc == NULL || buf == NULL || buf_len <= 0) { + if (buf == NULL || buf_len <= 0) { return MQTT_CODE_ERROR_BAD_ARG; } FD_ZERO(&rfds); - FD_SET(bc->fd, &rfds); + FD_SET(sock, &rfds); tv.tv_sec = timeout_ms / 1000; tv.tv_usec = (timeout_ms % 1000) * 1000; - rc = select(bc->fd + 1, &rfds, NULL, NULL, &tv); + rc = select(sock + 1, &rfds, NULL, NULL, &tv); if (rc == 0) { return MQTT_CODE_ERROR_TIMEOUT; } @@ -131,36 +184,36 @@ static int BrokerNetRead(void* context, byte* buf, int buf_len, int timeout_ms) return MQTT_CODE_ERROR_NETWORK; } - rc = (int)recv(bc->fd, buf, buf_len, 0); + rc = (int)recv(sock, buf, (size_t)buf_len, 0); if (rc <= 0) { if (rc < 0 && (errno == EWOULDBLOCK || errno == EAGAIN)) { return MQTT_CODE_CONTINUE; } - PRINTF("broker: recv error fd=%d rc=%d errno=%d", bc->fd, rc, errno); + PRINTF("broker: recv error sock=%d rc=%d errno=%d", + (int)sock, rc, errno); return MQTT_CODE_ERROR_NETWORK; } - PRINTF("broker: recv fd=%d len=%d", bc->fd, rc); return rc; } -static int BrokerNetWrite(void* context, const byte* buf, int buf_len, - int timeout_ms) +static int BrokerPosix_Write(void* ctx, BROKER_SOCKET_T sock, + const byte* buf, int buf_len, int timeout_ms) { - BrokerClient* bc = (BrokerClient*)context; fd_set wfds; struct timeval tv; int rc; + (void)ctx; - if (bc == NULL || buf == NULL || buf_len <= 0) { + if (buf == NULL || buf_len <= 0) { return MQTT_CODE_ERROR_BAD_ARG; } FD_ZERO(&wfds); - FD_SET(bc->fd, &wfds); + FD_SET(sock, &wfds); tv.tv_sec = timeout_ms / 1000; tv.tv_usec = (timeout_ms % 1000) * 1000; - rc = select(bc->fd + 1, NULL, &wfds, NULL, &tv); + rc = select(sock + 1, NULL, &wfds, NULL, &tv); if (rc == 0) { return MQTT_CODE_ERROR_TIMEOUT; } @@ -168,29 +221,95 @@ static int BrokerNetWrite(void* context, const byte* buf, int buf_len, return MQTT_CODE_ERROR_NETWORK; } - rc = (int)send(bc->fd, buf, buf_len, 0); + rc = (int)send(sock, buf, (size_t)buf_len, 0); if (rc <= 0) { if (rc < 0 && (errno == EWOULDBLOCK || errno == EAGAIN)) { return MQTT_CODE_CONTINUE; } - PRINTF("broker: send error fd=%d rc=%d errno=%d", bc->fd, rc, errno); + PRINTF("broker: send error sock=%d rc=%d errno=%d", + (int)sock, rc, errno); return MQTT_CODE_ERROR_NETWORK; } - PRINTF("broker: send fd=%d len=%d", bc->fd, rc); return rc; } +static int BrokerPosix_Close(void* ctx, BROKER_SOCKET_T sock) +{ + (void)ctx; + if (sock != BROKER_SOCKET_INVALID) { + close(sock); + } + return MQTT_CODE_SUCCESS; +} + +int MqttBrokerNet_Init(MqttBrokerNet* net) +{ + if (net == NULL) { + return MQTT_CODE_ERROR_BAD_ARG; + } + XMEMSET(net, 0, sizeof(*net)); + net->listen = BrokerPosix_Listen; + net->accept = BrokerPosix_Accept; + net->read = BrokerPosix_Read; + net->write = BrokerPosix_Write; + net->close = BrokerPosix_Close; + net->ctx = NULL; + return MQTT_CODE_SUCCESS; +} + +#endif /* !WOLFMQTT_BROKER_CUSTOM_NET */ + +/* -------------------------------------------------------------------------- */ +/* Per-client MqttNet callbacks (route through MqttBrokerNet) */ +/* -------------------------------------------------------------------------- */ +static int BrokerNetConnect(void* context, const char* host, word16 port, + int timeout_ms) +{ + /* Server side: connection already established via accept() */ + (void)context; + (void)host; + (void)port; + (void)timeout_ms; + return MQTT_CODE_SUCCESS; +} + +static int BrokerNetRead(void* context, byte* buf, int buf_len, + int timeout_ms) +{ + BrokerClient* bc = (BrokerClient*)context; + if (bc == NULL || bc->broker == NULL || buf == NULL || buf_len <= 0) { + return MQTT_CODE_ERROR_BAD_ARG; + } + return bc->broker->net.read(bc->broker->net.ctx, bc->sock, + buf, buf_len, timeout_ms); +} + +static int BrokerNetWrite(void* context, const byte* buf, int buf_len, + int timeout_ms) +{ + BrokerClient* bc = (BrokerClient*)context; + if (bc == NULL || bc->broker == NULL || buf == NULL || buf_len <= 0) { + return MQTT_CODE_ERROR_BAD_ARG; + } + return bc->broker->net.write(bc->broker->net.ctx, bc->sock, + buf, buf_len, timeout_ms); +} + static int BrokerNetDisconnect(void* context) { BrokerClient* bc = (BrokerClient*)context; - if (bc && bc->fd >= 0) { - PRINTF("broker: disconnect fd=%d", bc->fd); - close(bc->fd); - bc->fd = -1; + if (bc != NULL && bc->broker != NULL && + bc->sock != BROKER_SOCKET_INVALID) { + PRINTF("broker: disconnect sock=%d", (int)bc->sock); + bc->broker->net.close(bc->broker->net.ctx, bc->sock); + bc->sock = BROKER_SOCKET_INVALID; } return MQTT_CODE_SUCCESS; } +/* -------------------------------------------------------------------------- */ +/* Client management */ +/* -------------------------------------------------------------------------- */ static void BrokerClient_Free(BrokerClient* bc) { if (bc == NULL) { @@ -198,6 +317,10 @@ static void BrokerClient_Free(BrokerClient* bc) } (void)BrokerNetDisconnect(bc); MqttClient_DeInit(&bc->client); +#ifdef WOLFMQTT_STATIC_MEMORY + XMEMSET(bc, 0, sizeof(*bc)); + /* in_use is now 0 after memset */ +#else if (bc->client_id) { WOLFMQTT_FREE(bc->client_id); } @@ -214,22 +337,36 @@ static void BrokerClient_Free(BrokerClient* bc) WOLFMQTT_FREE(bc->rx_buf); } WOLFMQTT_FREE(bc); +#endif } -static BrokerClient* BrokerClient_Add(BrokerClient** head, int fd) +static BrokerClient* BrokerClient_Add(MqttBroker* broker, + BROKER_SOCKET_T sock) { - BrokerClient* bc; + BrokerClient* bc = NULL; int rc; +#ifdef WOLFMQTT_STATIC_MEMORY + { + int i; + for (i = 0; i < BROKER_MAX_CLIENTS; i++) { + if (!broker->clients[i].in_use) { + bc = &broker->clients[i]; + break; + } + } + if (bc == NULL) { + return NULL; + } + XMEMSET(bc, 0, sizeof(*bc)); + bc->in_use = 1; + } +#else bc = (BrokerClient*)WOLFMQTT_MALLOC(sizeof(BrokerClient)); if (bc == NULL) { return NULL; } - XMEMSET(bc, 0, sizeof(BrokerClient)); - bc->fd = fd; - bc->protocol_level = 0; - bc->keep_alive_sec = 0; - bc->last_rx = time(NULL); + XMEMSET(bc, 0, sizeof(*bc)); bc->tx_buf_len = BROKER_TX_BUF_SZ; bc->rx_buf_len = BROKER_RX_BUF_SZ; bc->tx_buf = (byte*)WOLFMQTT_MALLOC(bc->tx_buf_len); @@ -238,6 +375,13 @@ static BrokerClient* BrokerClient_Add(BrokerClient** head, int fd) BrokerClient_Free(bc); return NULL; } +#endif + + bc->sock = sock; + bc->broker = broker; + bc->protocol_level = 0; + bc->keep_alive_sec = 0; + bc->last_rx = WOLFMQTT_BROKER_GET_TIME_S(); bc->net.context = bc; bc->net.connect = BrokerNetConnect; @@ -245,48 +389,72 @@ static BrokerClient* BrokerClient_Add(BrokerClient** head, int fd) bc->net.write = BrokerNetWrite; bc->net.disconnect = BrokerNetDisconnect; +#ifdef WOLFMQTT_STATIC_MEMORY + rc = MqttClient_Init(&bc->client, &bc->net, NULL, + bc->tx_buf, BROKER_TX_BUF_SZ, bc->rx_buf, BROKER_RX_BUF_SZ, + BROKER_TIMEOUT_MS); +#else rc = MqttClient_Init(&bc->client, &bc->net, NULL, bc->tx_buf, bc->tx_buf_len, bc->rx_buf, bc->rx_buf_len, BROKER_TIMEOUT_MS); +#endif if (rc != MQTT_CODE_SUCCESS) { PRINTF("broker: client init failed rc=%d", rc); BrokerClient_Free(bc); return NULL; } - bc->next = *head; - *head = bc; +#ifndef WOLFMQTT_STATIC_MEMORY + /* Prepend to linked list */ + bc->next = broker->clients; + broker->clients = bc; +#endif + return bc; } -static void BrokerClient_Remove(BrokerClient** head, BrokerClient* bc) +static void BrokerClient_Remove(MqttBroker* broker, BrokerClient* bc) { - BrokerClient* cur; - BrokerClient* prev = NULL; - - if (head == NULL || bc == NULL) { + if (broker == NULL || bc == NULL) { return; } - cur = *head; - while (cur) { - if (cur == bc) { - if (prev) { - prev->next = cur->next; - } - else { - *head = cur->next; + +#ifndef WOLFMQTT_STATIC_MEMORY + { + BrokerClient* cur = broker->clients; + BrokerClient* prev = NULL; + while (cur) { + if (cur == bc) { + if (prev) { + prev->next = cur->next; + } + else { + broker->clients = cur->next; + } + break; } - BrokerClient_Free(cur); - return; + prev = cur; + cur = cur->next; } - prev = cur; - cur = cur->next; } +#endif + BrokerClient_Free(bc); } -static void BrokerSubs_RemoveClient(BrokerSub** head, BrokerClient* bc) +/* -------------------------------------------------------------------------- */ +/* Subscription management */ +/* -------------------------------------------------------------------------- */ +static void BrokerSubs_RemoveClient(MqttBroker* broker, BrokerClient* bc) { - BrokerSub* cur = *head; +#ifdef WOLFMQTT_STATIC_MEMORY + int i; + for (i = 0; i < BROKER_MAX_SUBS; i++) { + if (broker->subs[i].in_use && broker->subs[i].client == bc) { + XMEMSET(&broker->subs[i], 0, sizeof(BrokerSub)); + } + } +#else + BrokerSub* cur = broker->subs; BrokerSub* prev = NULL; while (cur) { @@ -296,7 +464,7 @@ static void BrokerSubs_RemoveClient(BrokerSub** head, BrokerClient* bc) prev->next = next; } else { - *head = next; + broker->subs = next; } if (cur->filter) { WOLFMQTT_FREE(cur->filter); @@ -308,18 +476,40 @@ static void BrokerSubs_RemoveClient(BrokerSub** head, BrokerClient* bc) } cur = next; } +#endif } -static int BrokerSubs_Add(BrokerSub** head, BrokerClient* bc, +static int BrokerSubs_Add(MqttBroker* broker, BrokerClient* bc, const char* filter, word16 filter_len) { - BrokerSub* sub; + BrokerSub* sub = NULL; +#ifdef WOLFMQTT_STATIC_MEMORY + { + int i; + for (i = 0; i < BROKER_MAX_SUBS; i++) { + if (!broker->subs[i].in_use) { + sub = &broker->subs[i]; + break; + } + } + if (sub == NULL) { + return MQTT_CODE_ERROR_MEMORY; + } + XMEMSET(sub, 0, sizeof(*sub)); + sub->in_use = 1; + if (filter_len >= BROKER_MAX_FILTER_LEN) { + filter_len = BROKER_MAX_FILTER_LEN - 1; + } + XMEMCPY(sub->filter, filter, filter_len); + sub->filter[filter_len] = '\0'; + } +#else sub = (BrokerSub*)WOLFMQTT_MALLOC(sizeof(BrokerSub)); if (sub == NULL) { return MQTT_CODE_ERROR_MEMORY; } - XMEMSET(sub, 0, sizeof(BrokerSub)); + XMEMSET(sub, 0, sizeof(*sub)); sub->filter = (char*)WOLFMQTT_MALLOC(filter_len + 1); if (sub->filter == NULL) { WOLFMQTT_FREE(sub); @@ -327,17 +517,34 @@ static int BrokerSubs_Add(BrokerSub** head, BrokerClient* bc, } XMEMCPY(sub->filter, filter, filter_len); sub->filter[filter_len] = '\0'; + sub->next = broker->subs; + broker->subs = sub; +#endif + sub->client = bc; - sub->next = *head; - *head = sub; - PRINTF("broker: sub add fd=%d filter=%s", bc->fd, sub->filter); + PRINTF("broker: sub add sock=%d filter=%s", (int)bc->sock, sub->filter); return MQTT_CODE_SUCCESS; } -static void BrokerSubs_Remove(BrokerSub** head, BrokerClient* bc, +static void BrokerSubs_Remove(MqttBroker* broker, BrokerClient* bc, const char* filter, word16 filter_len) { - BrokerSub* cur = *head; +#ifdef WOLFMQTT_STATIC_MEMORY + int i; + for (i = 0; i < BROKER_MAX_SUBS; i++) { + BrokerSub* s = &broker->subs[i]; + if (s->in_use && s->client == bc && + s->filter[0] != '\0' && + (word16)XSTRLEN(s->filter) == filter_len && + XMEMCMP(s->filter, filter, filter_len) == 0) { + PRINTF("broker: sub remove sock=%d filter=%s", + (int)bc->sock, s->filter); + XMEMSET(s, 0, sizeof(BrokerSub)); + return; + } + } +#else + BrokerSub* cur = broker->subs; BrokerSub* prev = NULL; while (cur) { @@ -350,10 +557,10 @@ static void BrokerSubs_Remove(BrokerSub** head, BrokerClient* bc, prev->next = next; } else { - *head = next; + broker->subs = next; } - PRINTF("broker: sub remove fd=%d filter=%s", - bc->fd, cur->filter); + PRINTF("broker: sub remove sock=%d filter=%s", + (int)bc->sock, cur->filter); WOLFMQTT_FREE(cur->filter); WOLFMQTT_FREE(cur); return; @@ -361,8 +568,12 @@ static void BrokerSubs_Remove(BrokerSub** head, BrokerClient* bc, prev = cur; cur = next; } +#endif } +/* -------------------------------------------------------------------------- */ +/* Topic matching */ +/* -------------------------------------------------------------------------- */ static int BrokerTopicMatch(const char* filter, const char* topic) { const char* f = filter; @@ -404,15 +615,24 @@ static int BrokerTopicMatch(const char* filter, const char* topic) return (*f == '\0' && *t == '\0'); } +/* -------------------------------------------------------------------------- */ +/* Packet send helpers */ +/* -------------------------------------------------------------------------- */ static int BrokerSend_PingResp(BrokerClient* bc) { if (bc == NULL) { return MQTT_CODE_ERROR_BAD_ARG; } - PRINTF("broker: PINGREQ -> PINGRESP fd=%d", bc->fd); + PRINTF("broker: PINGREQ -> PINGRESP sock=%d", (int)bc->sock); +#ifdef WOLFMQTT_STATIC_MEMORY bc->tx_buf[0] = MQTT_PACKET_TYPE_SET(MQTT_PACKET_TYPE_PING_RESP); bc->tx_buf[1] = 0; return MqttPacket_Write(&bc->client, bc->tx_buf, 2); +#else + bc->tx_buf[0] = MQTT_PACKET_TYPE_SET(MQTT_PACKET_TYPE_PING_RESP); + bc->tx_buf[1] = 0; + return MqttPacket_Write(&bc->client, bc->tx_buf, 2); +#endif } static int BrokerSend_SubAck(BrokerClient* bc, word16 packet_id, @@ -426,8 +646,8 @@ static int BrokerSend_SubAck(BrokerClient* bc, word16 packet_id, return MQTT_CODE_ERROR_BAD_ARG; } - PRINTF("broker: SUBACK fd=%d packet_id=%u topics=%d", - bc->fd, packet_id, return_code_count); + PRINTF("broker: SUBACK sock=%d packet_id=%u topics=%d", + (int)bc->sock, packet_id, return_code_count); remain_len = MQTT_DATA_LEN_SIZE + return_code_count; #ifdef WOLFMQTT_V5 if (bc->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) { @@ -450,10 +670,14 @@ static int BrokerSend_SubAck(BrokerClient* bc, word16 packet_id, return MqttPacket_Write(&bc->client, bc->tx_buf, pos); } +/* -------------------------------------------------------------------------- */ +/* Packet handlers */ +/* -------------------------------------------------------------------------- */ + /* Returns: > 0 success, 0 auth rejected (CONNACK sent with refused), * < 0 error */ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, - const char* auth_user, const char* auth_pass) + MqttBroker* broker) { int rc; MqttConnect mc; @@ -465,7 +689,7 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, XMEMSET(&lwt, 0, sizeof(lwt)); mc.lwt_msg = &lwt; - PRINTF("broker: CONNECT recv fd=%d len=%d", bc->fd, rx_len); + PRINTF("broker: CONNECT recv sock=%d len=%d", (int)bc->sock, rx_len); rc = MqttDecode_Connect(bc->rx_buf, rx_len, &mc); if (rc < 0) { PRINTF("broker: CONNECT decode failed rc=%d", rc); @@ -473,6 +697,20 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, } /* Store client ID */ +#ifdef WOLFMQTT_STATIC_MEMORY + bc->client_id[0] = '\0'; + if (mc.client_id) { + word16 id_len = 0; + if (MqttDecode_Num((byte*)mc.client_id - MQTT_DATA_LEN_SIZE, + &id_len, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) { + if (id_len >= BROKER_MAX_CLIENT_ID_LEN) { + id_len = BROKER_MAX_CLIENT_ID_LEN - 1; + } + XMEMCPY(bc->client_id, mc.client_id, id_len); + bc->client_id[id_len] = '\0'; + } + } +#else if (bc->client_id) { WOLFMQTT_FREE(bc->client_id); bc->client_id = NULL; @@ -488,14 +726,42 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, } } } +#endif + bc->protocol_level = mc.protocol_level; bc->keep_alive_sec = mc.keep_alive_sec; - bc->last_rx = time(NULL); + bc->last_rx = WOLFMQTT_BROKER_GET_TIME_S(); PRINTF("broker: CONNECT proto=%u clean=%d will=%d client_id=%s", mc.protocol_level, mc.clean_session, mc.enable_lwt, - bc->client_id ? bc->client_id : "(null)"); + bc->client_id[0] ? bc->client_id : "(null)"); /* Store credentials */ +#ifdef WOLFMQTT_STATIC_MEMORY + bc->username[0] = '\0'; + if (mc.username) { + word16 ulen = 0; + if (MqttDecode_Num((byte*)mc.username - MQTT_DATA_LEN_SIZE, + &ulen, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) { + if (ulen >= BROKER_MAX_USERNAME_LEN) { + ulen = BROKER_MAX_USERNAME_LEN - 1; + } + XMEMCPY(bc->username, mc.username, ulen); + bc->username[ulen] = '\0'; + } + } + bc->password[0] = '\0'; + if (mc.password) { + word16 plen = 0; + if (MqttDecode_Num((byte*)mc.password - MQTT_DATA_LEN_SIZE, + &plen, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) { + if (plen >= BROKER_MAX_PASSWORD_LEN) { + plen = BROKER_MAX_PASSWORD_LEN - 1; + } + XMEMCPY(bc->password, mc.password, plen); + bc->password[plen] = '\0'; + } + } +#else if (bc->username) { WOLFMQTT_FREE(bc->username); bc->username = NULL; @@ -526,6 +792,7 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, } } } +#endif /* Check auth before sending CONNACK */ ack.flags = 0; @@ -535,26 +802,32 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, ack.props = NULL; #endif - if (auth_user || auth_pass) { + if (broker->auth_user || broker->auth_pass) { int auth_ok = 1; - if (auth_user && (!bc->username || - XSTRCMP(auth_user, bc->username) != 0)) { + if (broker->auth_user && (bc->username[0] == '\0' || + XSTRCMP(broker->auth_user, bc->username) != 0)) { auth_ok = 0; } - if (auth_pass && (!bc->password || - XSTRCMP(auth_pass, bc->password) != 0)) { + if (broker->auth_pass && (bc->password[0] == '\0' || + XSTRCMP(broker->auth_pass, bc->password) != 0)) { auth_ok = 0; } if (!auth_ok) { - PRINTF("broker: auth failed fd=%d user=%s", bc->fd, - bc->username ? bc->username : "(null)"); + PRINTF("broker: auth failed sock=%d user=%s", (int)bc->sock, + bc->username[0] ? bc->username : "(null)"); ack.return_code = MQTT_CONNECT_ACK_CODE_REFUSED_BAD_USER_PWD; } } - rc = MqttEncode_ConnectAck(bc->tx_buf, bc->tx_buf_len, &ack); + rc = MqttEncode_ConnectAck(bc->tx_buf, +#ifdef WOLFMQTT_STATIC_MEMORY + BROKER_TX_BUF_SZ, +#else + bc->tx_buf_len, +#endif + &ack); if (rc > 0) { - PRINTF("broker: CONNACK send fd=%d code=%d", bc->fd, + PRINTF("broker: CONNACK send sock=%d code=%d", (int)bc->sock, ack.return_code); rc = MqttPacket_Write(&bc->client, bc->tx_buf, rc); } @@ -576,7 +849,7 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, } static int BrokerHandle_Subscribe(BrokerClient* bc, int rx_len, - BrokerSub** sub_list) + MqttBroker* broker) { int rc; int i; @@ -587,13 +860,14 @@ static int BrokerHandle_Subscribe(BrokerClient* bc, int rx_len, #ifdef WOLFMQTT_V5 sub.protocol_level = bc->protocol_level; #endif - sub.topics = (MqttTopic*)WOLFMQTT_MALLOC(sizeof(MqttTopic) * MAX_MQTT_TOPICS); + sub.topics = (MqttTopic*)WOLFMQTT_MALLOC( + sizeof(MqttTopic) * MAX_MQTT_TOPICS); if (sub.topics == NULL) { return MQTT_CODE_ERROR_MEMORY; } XMEMSET(sub.topics, 0, sizeof(MqttTopic) * MAX_MQTT_TOPICS); - PRINTF("broker: SUBSCRIBE recv fd=%d len=%d", bc->fd, rx_len); + PRINTF("broker: SUBSCRIBE recv sock=%d len=%d", (int)bc->sock, rx_len); rc = MqttDecode_Subscribe(bc->rx_buf, rx_len, &sub); if (rc < 0) { PRINTF("broker: SUBSCRIBE decode failed rc=%d", rc); @@ -607,7 +881,7 @@ static int BrokerHandle_Subscribe(BrokerClient* bc, int rx_len, word16 flen = 0; if (f && MqttDecode_Num((byte*)f - MQTT_DATA_LEN_SIZE, &flen, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) { - (void)BrokerSubs_Add(sub_list, bc, f, flen); + (void)BrokerSubs_Add(broker, bc, f, flen); } return_codes[i] = MQTT_SUBSCRIBE_ACK_CODE_SUCCESS_MAX_QOS0; } @@ -625,7 +899,7 @@ static int BrokerHandle_Subscribe(BrokerClient* bc, int rx_len, } static int BrokerHandle_Unsubscribe(BrokerClient* bc, int rx_len, - BrokerSub** sub_list) + MqttBroker* broker) { int rc; int i; @@ -636,13 +910,14 @@ static int BrokerHandle_Unsubscribe(BrokerClient* bc, int rx_len, #ifdef WOLFMQTT_V5 unsub.protocol_level = bc->protocol_level; #endif - unsub.topics = (MqttTopic*)WOLFMQTT_MALLOC(sizeof(MqttTopic) * MAX_MQTT_TOPICS); + unsub.topics = (MqttTopic*)WOLFMQTT_MALLOC( + sizeof(MqttTopic) * MAX_MQTT_TOPICS); if (unsub.topics == NULL) { return MQTT_CODE_ERROR_MEMORY; } XMEMSET(unsub.topics, 0, sizeof(MqttTopic) * MAX_MQTT_TOPICS); - PRINTF("broker: UNSUBSCRIBE recv fd=%d len=%d", bc->fd, rx_len); + PRINTF("broker: UNSUBSCRIBE recv sock=%d len=%d", (int)bc->sock, rx_len); rc = MqttDecode_Unsubscribe(bc->rx_buf, rx_len, &unsub); if (rc < 0) { PRINTF("broker: UNSUBSCRIBE decode failed rc=%d", rc); @@ -656,7 +931,7 @@ static int BrokerHandle_Unsubscribe(BrokerClient* bc, int rx_len, word16 flen = 0; if (f && MqttDecode_Num((byte*)f - MQTT_DATA_LEN_SIZE, &flen, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) { - BrokerSubs_Remove(sub_list, bc, f, flen); + BrokerSubs_Remove(broker, bc, f, flen); } } @@ -667,10 +942,16 @@ static int BrokerHandle_Unsubscribe(BrokerClient* bc, int rx_len, ack.props = NULL; ack.reason_codes = NULL; #endif - rc = MqttEncode_UnsubscribeAck(bc->tx_buf, bc->tx_buf_len, &ack); + rc = MqttEncode_UnsubscribeAck(bc->tx_buf, +#ifdef WOLFMQTT_STATIC_MEMORY + BROKER_TX_BUF_SZ, +#else + bc->tx_buf_len, +#endif + &ack); if (rc > 0) { - PRINTF("broker: UNSUBACK send fd=%d packet_id=%u", - bc->fd, ack.packet_id); + PRINTF("broker: UNSUBACK send sock=%d packet_id=%u", + (int)bc->sock, ack.packet_id); rc = MqttPacket_Write(&bc->client, bc->tx_buf, rc); } @@ -684,7 +965,7 @@ static int BrokerHandle_Unsubscribe(BrokerClient* bc, int rx_len, } static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, - BrokerSub* subs) + MqttBroker* broker) { int rc; MqttPublish pub; @@ -696,7 +977,7 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, #ifdef WOLFMQTT_V5 pub.protocol_level = bc->protocol_level; #endif - PRINTF("broker: PUBLISH recv fd=%d len=%d", bc->fd, rx_len); + PRINTF("broker: PUBLISH recv sock=%d len=%d", (int)bc->sock, rx_len); rc = MqttDecode_Publish(bc->rx_buf, rx_len, &pub); if (rc < 0) { PRINTF("broker: PUBLISH decode failed rc=%d", rc); @@ -718,10 +999,17 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, } if (topic != NULL && (payload != NULL || pub.total_len == 0)) { - BrokerSub* sub = subs; - while (sub) { + /* Fan out to matching subscribers */ +#ifdef WOLFMQTT_STATIC_MEMORY + int si; + for (si = 0; si < BROKER_MAX_SUBS; si++) { + BrokerSub* sub = &broker->subs[si]; + if (!sub->in_use) { + continue; + } if (sub->client && sub->client->protocol_level != 0 && - sub->filter && BrokerTopicMatch(sub->filter, topic)) { + sub->filter[0] != '\0' && + BrokerTopicMatch(sub->filter, topic)) { MqttPublish out_pub; XMEMSET(&out_pub, 0, sizeof(out_pub)); out_pub.topic_name = topic; @@ -734,18 +1022,49 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, out_pub.protocol_level = sub->client->protocol_level; #endif rc = MqttEncode_Publish(sub->client->tx_buf, - sub->client->tx_buf_len, - &out_pub, 0); + BROKER_TX_BUF_SZ, &out_pub, 0); if (rc > 0) { - PRINTF("broker: PUBLISH fwd fd=%d -> fd=%d topic=%s len=%u", - bc->fd, sub->client->fd, topic ? topic : "(null)", - (unsigned)pub.total_len); + PRINTF("broker: PUBLISH fwd sock=%d -> sock=%d " + "topic=%s len=%u", + (int)bc->sock, (int)sub->client->sock, + topic, (unsigned)pub.total_len); (void)MqttPacket_Write(&sub->client->client, sub->client->tx_buf, rc); } } - sub = sub->next; } +#else + { + BrokerSub* sub = broker->subs; + while (sub) { + if (sub->client && sub->client->protocol_level != 0 && + sub->filter && BrokerTopicMatch(sub->filter, topic)) { + MqttPublish out_pub; + XMEMSET(&out_pub, 0, sizeof(out_pub)); + out_pub.topic_name = topic; + out_pub.qos = MQTT_QOS_0; + out_pub.retain = pub.retain; + out_pub.duplicate = 0; + out_pub.buffer = payload; + out_pub.total_len = pub.total_len; +#ifdef WOLFMQTT_V5 + out_pub.protocol_level = sub->client->protocol_level; +#endif + rc = MqttEncode_Publish(sub->client->tx_buf, + sub->client->tx_buf_len, &out_pub, 0); + if (rc > 0) { + PRINTF("broker: PUBLISH fwd sock=%d -> sock=%d " + "topic=%s len=%u", + (int)bc->sock, (int)sub->client->sock, + topic, (unsigned)pub.total_len); + (void)MqttPacket_Write(&sub->client->client, + sub->client->tx_buf, rc); + } + } + sub = sub->next; + } + } +#endif } if (pub.qos == MQTT_QOS_1 || pub.qos == MQTT_QOS_2) { @@ -756,12 +1075,17 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, resp.reason_code = MQTT_REASON_SUCCESS; resp.props = NULL; #endif - rc = MqttEncode_PublishResp(bc->tx_buf, bc->tx_buf_len, + rc = MqttEncode_PublishResp(bc->tx_buf, +#ifdef WOLFMQTT_STATIC_MEMORY + BROKER_TX_BUF_SZ, +#else + bc->tx_buf_len, +#endif (pub.qos == MQTT_QOS_1) ? MQTT_PACKET_TYPE_PUBLISH_ACK : MQTT_PACKET_TYPE_PUBLISH_REC, &resp); if (rc > 0) { - PRINTF("broker: PUBRESP send fd=%d qos=%d packet_id=%u", - bc->fd, pub.qos, pub.packet_id); + PRINTF("broker: PUBRESP send sock=%d qos=%d packet_id=%u", + (int)bc->sock, pub.qos, pub.packet_id); rc = MqttPacket_Write(&bc->client, bc->tx_buf, rc); } } @@ -790,9 +1114,9 @@ static int BrokerHandle_PublishRel(BrokerClient* bc, int rx_len) #ifdef WOLFMQTT_V5 resp.protocol_level = bc->protocol_level; #endif - PRINTF("broker: PUBLISH_REL recv fd=%d len=%d", bc->fd, rx_len); - rc = MqttDecode_PublishResp(bc->rx_buf, rx_len, MQTT_PACKET_TYPE_PUBLISH_REL, - &resp); + PRINTF("broker: PUBLISH_REL recv sock=%d len=%d", (int)bc->sock, rx_len); + rc = MqttDecode_PublishResp(bc->rx_buf, rx_len, + MQTT_PACKET_TYPE_PUBLISH_REL, &resp); if (rc < 0) { PRINTF("broker: PUBLISH_REL decode failed rc=%d", rc); return rc; @@ -802,11 +1126,16 @@ static int BrokerHandle_PublishRel(BrokerClient* bc, int rx_len) resp.reason_code = MQTT_REASON_SUCCESS; resp.props = NULL; #endif - rc = MqttEncode_PublishResp(bc->tx_buf, bc->tx_buf_len, + rc = MqttEncode_PublishResp(bc->tx_buf, +#ifdef WOLFMQTT_STATIC_MEMORY + BROKER_TX_BUF_SZ, +#else + bc->tx_buf_len, +#endif MQTT_PACKET_TYPE_PUBLISH_COMP, &resp); if (rc > 0) { - PRINTF("broker: PUBCOMP send fd=%d packet_id=%u", - bc->fd, resp.packet_id); + PRINTF("broker: PUBCOMP send sock=%d packet_id=%u", + (int)bc->sock, resp.packet_id); rc = MqttPacket_Write(&bc->client, bc->tx_buf, rc); } #ifdef WOLFMQTT_V5 @@ -817,189 +1146,302 @@ static int BrokerHandle_PublishRel(BrokerClient* bc, int rx_len) return rc; } -static void BrokerUsage(const char* prog) +/* -------------------------------------------------------------------------- */ +/* Per-client processing (called from Step) */ +/* -------------------------------------------------------------------------- */ +static int BrokerClient_Process(MqttBroker* broker, BrokerClient* bc) { - PRINTF("usage: %s [-p port] [-u user] [-P pass]", prog); -} + int rc; + int activity = 0; -int wolfmqtt_broker(int argc, char** argv) -{ - int ret = 0; - int listen_fd; - struct sockaddr_in addr; - BrokerClient* clients = NULL; - BrokerSub* subs = NULL; - const char* auth_user = NULL; - const char* auth_pass = NULL; - int port = MQTT_DEFAULT_PORT; - int i; + /* Try non-blocking read (timeout=0) */ +#ifdef WOLFMQTT_STATIC_MEMORY + rc = MqttPacket_Read(&bc->client, bc->rx_buf, BROKER_RX_BUF_SZ, 0); +#else + rc = MqttPacket_Read(&bc->client, bc->rx_buf, bc->rx_buf_len, 0); +#endif - for (i = 1; i < argc; i++) { - if (XSTRCMP(argv[i], "-p") == 0 && i + 1 < argc) { - port = XATOI(argv[++i]); - } - else if (XSTRCMP(argv[i], "-u") == 0 && i + 1 < argc) { - auth_user = argv[++i]; - } - else if (XSTRCMP(argv[i], "-P") == 0 && i + 1 < argc) { - auth_pass = argv[++i]; + if (rc == MQTT_CODE_ERROR_TIMEOUT || rc == MQTT_CODE_CONTINUE) { + /* No data available - not an error */ + rc = 0; + } + else if (rc < 0) { + PRINTF("broker: read failed sock=%d rc=%d", (int)bc->sock, rc); + BrokerSubs_RemoveClient(broker, bc); + BrokerClient_Remove(broker, bc); + return 0; + } + + if (rc > 0) { + byte type = MQTT_PACKET_TYPE_GET(bc->rx_buf[0]); + bc->last_rx = WOLFMQTT_BROKER_GET_TIME_S(); + activity = 1; +#if BROKER_LOG_PKT + PRINTF("broker: packet sock=%d type=%u len=%d", + (int)bc->sock, type, rc); +#endif + switch (type) { + case MQTT_PACKET_TYPE_CONNECT: + { + int c_rc = BrokerHandle_Connect(bc, rc, broker); + if (c_rc == 0) { + /* Auth rejected, disconnect */ + BrokerSubs_RemoveClient(broker, bc); + BrokerClient_Remove(broker, bc); + return 0; + } + break; + } + case MQTT_PACKET_TYPE_PUBLISH: + (void)BrokerHandle_Publish(bc, rc, broker); + break; + case MQTT_PACKET_TYPE_PUBLISH_REL: + (void)BrokerHandle_PublishRel(bc, rc); + break; + case MQTT_PACKET_TYPE_SUBSCRIBE: + (void)BrokerHandle_Subscribe(bc, rc, broker); + break; + case MQTT_PACKET_TYPE_UNSUBSCRIBE: + (void)BrokerHandle_Unsubscribe(bc, rc, broker); + break; + case MQTT_PACKET_TYPE_PING_REQ: + (void)BrokerSend_PingResp(bc); + break; + case MQTT_PACKET_TYPE_DISCONNECT: + BrokerSubs_RemoveClient(broker, bc); + BrokerClient_Remove(broker, bc); + return 0; + default: + break; } - else if (XSTRCMP(argv[i], "-h") == 0) { - BrokerUsage(argv[0]); + } + + /* Check keepalive timeout */ + if (bc->keep_alive_sec > 0) { + WOLFMQTT_BROKER_TIME_T now = WOLFMQTT_BROKER_GET_TIME_S(); + if ((now - bc->last_rx) > + (WOLFMQTT_BROKER_TIME_T)(bc->keep_alive_sec * 2)) { + PRINTF("broker: keepalive timeout sock=%d", (int)bc->sock); + BrokerSubs_RemoveClient(broker, bc); + BrokerClient_Remove(broker, bc); return 0; } - else { - BrokerUsage(argv[0]); - return MQTT_CODE_ERROR_BAD_ARG; + } + + return activity; +} + +/* -------------------------------------------------------------------------- */ +/* Public API */ +/* -------------------------------------------------------------------------- */ +int MqttBroker_Init(MqttBroker* broker, MqttBrokerNet* net) +{ + if (broker == NULL || net == NULL) { + return MQTT_CODE_ERROR_BAD_ARG; + } + XMEMSET(broker, 0, sizeof(*broker)); + XMEMCPY(&broker->net, net, sizeof(MqttBrokerNet)); + broker->listen_sock = BROKER_SOCKET_INVALID; + broker->port = MQTT_DEFAULT_PORT; + broker->running = 0; + return MQTT_CODE_SUCCESS; +} + +int MqttBroker_Step(MqttBroker* broker) +{ + int activity = 0; + BROKER_SOCKET_T new_sock = BROKER_SOCKET_INVALID; + int rc; + + if (broker == NULL) { + return MQTT_CODE_ERROR_BAD_ARG; + } + if (!broker->running) { + return MQTT_CODE_SUCCESS; + } + + /* 1. Try to accept a new connection (non-blocking) */ + rc = broker->net.accept(broker->net.ctx, broker->listen_sock, &new_sock); + if (rc == MQTT_CODE_SUCCESS && new_sock != BROKER_SOCKET_INVALID) { + PRINTF("broker: accept sock=%d", (int)new_sock); + if (BrokerClient_Add(broker, new_sock) == NULL) { + PRINTF("broker: accept sock=%d rejected (alloc)", (int)new_sock); + broker->net.close(broker->net.ctx, new_sock); } + activity = 1; } - listen_fd = socket(AF_INET, SOCK_STREAM, 0); - if (listen_fd < 0) { - PRINTF("broker: socket failed (%d)", errno); - return MQTT_CODE_ERROR_NETWORK; + /* 2. Process each client */ +#ifdef WOLFMQTT_STATIC_MEMORY + { + int i; + for (i = 0; i < BROKER_MAX_CLIENTS; i++) { + BrokerClient* bc = &broker->clients[i]; + if (!bc->in_use) { + continue; + } + rc = BrokerClient_Process(broker, bc); + if (rc > 0) { + activity = 1; + } + } } +#else { - int opt = 1; - (void)setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + BrokerClient* bc = broker->clients; + while (bc) { + BrokerClient* next = bc->next; + rc = BrokerClient_Process(broker, bc); + if (rc > 0) { + activity = 1; + } + bc = next; + } } +#endif - XMEMSET(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = htonl(INADDR_ANY); - addr.sin_port = htons((word16)port); + return activity ? MQTT_CODE_SUCCESS : MQTT_CODE_CONTINUE; +} - if (BrokerSocket_SetNonBlocking(listen_fd) != MQTT_CODE_SUCCESS) { - PRINTF("broker: set nonblocking failed (%d)", errno); - close(listen_fd); - return MQTT_CODE_ERROR_SYSTEM; +int MqttBroker_Run(MqttBroker* broker) +{ + int rc; + + if (broker == NULL) { + return MQTT_CODE_ERROR_BAD_ARG; } - if (bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { - PRINTF("broker: bind failed (%d)", errno); - close(listen_fd); - return MQTT_CODE_ERROR_NETWORK; + /* Start listening */ + rc = broker->net.listen(broker->net.ctx, &broker->listen_sock, + broker->port, BROKER_LISTEN_BACKLOG); + if (rc != MQTT_CODE_SUCCESS) { + PRINTF("broker: listen failed rc=%d", rc); + return rc; } - if (listen(listen_fd, BROKER_LISTEN_BACKLOG) < 0) { - PRINTF("broker: listen failed (%d)", errno); - close(listen_fd); - return MQTT_CODE_ERROR_NETWORK; + + PRINTF("broker: listening on port %d (no TLS)", broker->port); + if (broker->auth_user || broker->auth_pass) { + PRINTF("broker: auth enabled user=%s", + broker->auth_user ? broker->auth_user : "(null)"); } - PRINTF("broker: listening on port %d (no TLS)", port); - if (auth_user || auth_pass) { - PRINTF("broker: auth enabled user=%s", auth_user ? auth_user : "(null)"); + broker->running = 1; + while (broker->running) { + rc = MqttBroker_Step(broker); + if (rc == MQTT_CODE_CONTINUE) { + /* Idle - sleep briefly to avoid busy-waiting */ + BROKER_SLEEP_MS(10); + } + else if (rc < 0 && rc != MQTT_CODE_CONTINUE) { + break; + } } - while (1) { - fd_set rfds; - int maxfd = listen_fd; - BrokerClient* bc; + return MQTT_CODE_SUCCESS; +} - FD_ZERO(&rfds); - FD_SET(listen_fd, &rfds); +int MqttBroker_Stop(MqttBroker* broker) +{ + if (broker == NULL) { + return MQTT_CODE_ERROR_BAD_ARG; + } + broker->running = 0; + return MQTT_CODE_SUCCESS; +} - for (bc = clients; bc; bc = bc->next) { - FD_SET(bc->fd, &rfds); - if (bc->fd > maxfd) { - maxfd = bc->fd; +int MqttBroker_Free(MqttBroker* broker) +{ + if (broker == NULL) { + return MQTT_CODE_ERROR_BAD_ARG; + } + + /* Disconnect and free all clients and subscriptions */ +#ifdef WOLFMQTT_STATIC_MEMORY + { + int i; + for (i = 0; i < BROKER_MAX_CLIENTS; i++) { + if (broker->clients[i].in_use) { + BrokerSubs_RemoveClient(broker, &broker->clients[i]); + BrokerClient_Free(&broker->clients[i]); } } + } +#else + while (broker->clients) { + BrokerSubs_RemoveClient(broker, broker->clients); + BrokerClient_Remove(broker, broker->clients); + } +#endif - if (select(maxfd + 1, &rfds, NULL, NULL, NULL) < 0) { - PRINTF("broker: select failed (%d)", errno); - ret = MQTT_CODE_ERROR_NETWORK; - break; - } + /* Close listen socket */ + if (broker->listen_sock != BROKER_SOCKET_INVALID) { + broker->net.close(broker->net.ctx, broker->listen_sock); + broker->listen_sock = BROKER_SOCKET_INVALID; + } - if (FD_ISSET(listen_fd, &rfds)) { - int client_fd = accept(listen_fd, NULL, NULL); - if (client_fd >= 0) { - (void)BrokerSocket_SetNonBlocking(client_fd); - PRINTF("broker: accept fd=%d", client_fd); - if (BrokerClient_Add(&clients, client_fd) == NULL) { - PRINTF("broker: accept fd=%d rejected (alloc)", client_fd); - close(client_fd); - } - } - } + return MQTT_CODE_SUCCESS; +} - bc = clients; - while (bc) { - BrokerClient* next = bc->next; - if (FD_ISSET(bc->fd, &rfds)) { - int rc = MqttPacket_Read(&bc->client, bc->rx_buf, - bc->rx_buf_len, BROKER_TIMEOUT_MS); - if (rc < 0) { - PRINTF("broker: read failed fd=%d rc=%d", bc->fd, rc); - BrokerSubs_RemoveClient(&subs, bc); - BrokerClient_Remove(&clients, bc); - bc = NULL; - } - else if (rc > 0) { - byte type = MQTT_PACKET_TYPE_GET(bc->rx_buf[0]); - bc->last_rx = time(NULL); -#if BROKER_LOG_PKT - PRINTF("broker: packet fd=%d type=%u len=%d", - bc->fd, type, rc); -#endif - switch (type) { - case MQTT_PACKET_TYPE_CONNECT: - { - int c_rc = BrokerHandle_Connect(bc, rc, - auth_user, auth_pass); - if (c_rc == 0) { - /* Auth rejected, disconnect */ - BrokerSubs_RemoveClient(&subs, bc); - BrokerClient_Remove(&clients, bc); - bc = NULL; - } - break; - } - case MQTT_PACKET_TYPE_PUBLISH: - (void)BrokerHandle_Publish(bc, rc, subs); - break; - case MQTT_PACKET_TYPE_PUBLISH_REL: - (void)BrokerHandle_PublishRel(bc, rc); - break; - case MQTT_PACKET_TYPE_SUBSCRIBE: - (void)BrokerHandle_Subscribe(bc, rc, &subs); - break; - case MQTT_PACKET_TYPE_UNSUBSCRIBE: - (void)BrokerHandle_Unsubscribe(bc, rc, &subs); - break; - case MQTT_PACKET_TYPE_PING_REQ: - (void)BrokerSend_PingResp(bc); - break; - case MQTT_PACKET_TYPE_DISCONNECT: - BrokerSubs_RemoveClient(&subs, bc); - BrokerClient_Remove(&clients, bc); - bc = NULL; - break; - default: - break; - } - } - } - if (bc && bc->keep_alive_sec > 0) { - time_t now = time(NULL); - if ((now - bc->last_rx) > (time_t)(bc->keep_alive_sec * 2)) { - PRINTF("broker: keepalive timeout fd=%d", bc->fd); - BrokerSubs_RemoveClient(&subs, bc); - BrokerClient_Remove(&clients, bc); - } - } - bc = next; - } +/* -------------------------------------------------------------------------- */ +/* CLI wrapper */ +/* -------------------------------------------------------------------------- */ +static void BrokerUsage(const char* prog) +{ + PRINTF("usage: %s [-p port] [-u user] [-P pass]", prog); +} + +int wolfmqtt_broker(int argc, char** argv) +{ + int rc; + MqttBroker broker; + MqttBrokerNet net; + int i; + + /* Set stdout to unbuffered for immediate output */ +#ifndef WOLFMQTT_NO_STDIO + setvbuf(stdout, NULL, _IONBF, 0); +#endif + +#ifndef WOLFMQTT_BROKER_CUSTOM_NET + rc = MqttBrokerNet_Init(&net); + if (rc != MQTT_CODE_SUCCESS) { + return rc; + } +#else + XMEMSET(&net, 0, sizeof(net)); + PRINTF("broker: custom net requires callbacks to be set"); + return MQTT_CODE_ERROR_BAD_ARG; +#endif + + rc = MqttBroker_Init(&broker, &net); + if (rc != MQTT_CODE_SUCCESS) { + return rc; } - while (clients) { - BrokerSubs_RemoveClient(&subs, clients); - BrokerClient_Remove(&clients, clients); + /* Parse command line arguments */ + for (i = 1; i < argc; i++) { + if (XSTRCMP(argv[i], "-p") == 0 && i + 1 < argc) { + broker.port = (word16)XATOI(argv[++i]); + } + else if (XSTRCMP(argv[i], "-u") == 0 && i + 1 < argc) { + broker.auth_user = argv[++i]; + } + else if (XSTRCMP(argv[i], "-P") == 0 && i + 1 < argc) { + broker.auth_pass = argv[++i]; + } + else if (XSTRCMP(argv[i], "-h") == 0) { + BrokerUsage(argv[0]); + return 0; + } + else { + BrokerUsage(argv[0]); + return MQTT_CODE_ERROR_BAD_ARG; + } } - close(listen_fd); - return ret; + rc = MqttBroker_Run(&broker); + MqttBroker_Free(&broker); + return rc; } #ifndef NO_MAIN_DRIVER diff --git a/wolfmqtt/mqtt_broker.h b/wolfmqtt/mqtt_broker.h index fcb08ab76..daca37d5a 100644 --- a/wolfmqtt/mqtt_broker.h +++ b/wolfmqtt/mqtt_broker.h @@ -1,6 +1,6 @@ /* mqtt_broker.h * - * Copyright (C) 2006-2021 wolfSSL Inc. + * Copyright (C) 2006-2025 wolfSSL Inc. * * This file is part of wolfMQTT. * @@ -23,6 +23,8 @@ #define WOLFMQTT_BROKER_H #include "wolfmqtt/mqtt_types.h" +#include "wolfmqtt/mqtt_socket.h" +#include "wolfmqtt/mqtt_client.h" #ifdef __cplusplus extern "C" { @@ -30,9 +32,181 @@ #ifdef WOLFMQTT_BROKER -WOLFMQTT_API int wolfmqtt_broker(int argc, char** argv); +/* -------------------------------------------------------------------------- */ +/* Socket type abstraction - override for non-POSIX platforms */ +/* -------------------------------------------------------------------------- */ +#ifndef BROKER_SOCKET_T + #define BROKER_SOCKET_T int +#endif +#ifndef BROKER_SOCKET_INVALID + #define BROKER_SOCKET_INVALID (-1) +#endif + +/* -------------------------------------------------------------------------- */ +/* Time abstraction - override for platforms without time.h */ +/* -------------------------------------------------------------------------- */ +#ifndef WOLFMQTT_BROKER_TIME_T + #define WOLFMQTT_BROKER_TIME_T unsigned long +#endif +/* Note: WOLFMQTT_BROKER_GET_TIME_S() default is defined in mqtt_broker.c + * because it depends on which is only included for POSIX builds. + * Override this macro for custom platforms. */ + +/* -------------------------------------------------------------------------- */ +/* Buffer and limit defaults */ +/* -------------------------------------------------------------------------- */ +#ifndef BROKER_RX_BUF_SZ + #define BROKER_RX_BUF_SZ 4096 +#endif +#ifndef BROKER_TX_BUF_SZ + #define BROKER_TX_BUF_SZ 4096 +#endif +#ifndef BROKER_TIMEOUT_MS + #define BROKER_TIMEOUT_MS 1000 +#endif +#ifndef BROKER_LISTEN_BACKLOG + #define BROKER_LISTEN_BACKLOG 128 +#endif +/* Static allocation limits */ +#ifndef BROKER_MAX_CLIENTS + #define BROKER_MAX_CLIENTS 16 +#endif +#ifndef BROKER_MAX_SUBS + #define BROKER_MAX_SUBS 64 +#endif +#ifndef BROKER_MAX_CLIENT_ID_LEN + #define BROKER_MAX_CLIENT_ID_LEN 64 +#endif +#ifndef BROKER_MAX_USERNAME_LEN + #define BROKER_MAX_USERNAME_LEN 64 +#endif +#ifndef BROKER_MAX_PASSWORD_LEN + #define BROKER_MAX_PASSWORD_LEN 64 #endif +#ifndef BROKER_MAX_FILTER_LEN + #define BROKER_MAX_FILTER_LEN 128 +#endif + +/* -------------------------------------------------------------------------- */ +/* Forward declarations */ +/* -------------------------------------------------------------------------- */ +struct MqttBroker; + +/* -------------------------------------------------------------------------- */ +/* Broker network callback types */ +/* -------------------------------------------------------------------------- */ +typedef int (*MqttBrokerNet_ListenCb)(void* ctx, BROKER_SOCKET_T* sock, + word16 port, int backlog); +typedef int (*MqttBrokerNet_AcceptCb)(void* ctx, BROKER_SOCKET_T listen_sock, + BROKER_SOCKET_T* client_sock); +typedef int (*MqttBrokerNet_ReadCb)(void* ctx, BROKER_SOCKET_T sock, + byte* buf, int buf_len, int timeout_ms); +typedef int (*MqttBrokerNet_WriteCb)(void* ctx, BROKER_SOCKET_T sock, + const byte* buf, int buf_len, int timeout_ms); +typedef int (*MqttBrokerNet_CloseCb)(void* ctx, BROKER_SOCKET_T sock); + +typedef struct MqttBrokerNet { + MqttBrokerNet_ListenCb listen; + MqttBrokerNet_AcceptCb accept; + MqttBrokerNet_ReadCb read; + MqttBrokerNet_WriteCb write; + MqttBrokerNet_CloseCb close; + void* ctx; +} MqttBrokerNet; + +/* -------------------------------------------------------------------------- */ +/* Broker client tracking */ +/* -------------------------------------------------------------------------- */ +typedef struct BrokerClient { +#ifdef WOLFMQTT_STATIC_MEMORY + byte in_use; + char client_id[BROKER_MAX_CLIENT_ID_LEN]; + char username[BROKER_MAX_USERNAME_LEN]; + char password[BROKER_MAX_PASSWORD_LEN]; + byte tx_buf[BROKER_TX_BUF_SZ]; + byte rx_buf[BROKER_RX_BUF_SZ]; +#else + char* client_id; + char* username; + char* password; + byte* tx_buf; + byte* rx_buf; + int tx_buf_len; + int rx_buf_len; + struct BrokerClient* next; +#endif + BROKER_SOCKET_T sock; + byte protocol_level; + word16 keep_alive_sec; + WOLFMQTT_BROKER_TIME_T last_rx; + MqttNet net; + MqttClient client; + struct MqttBroker* broker; /* back-pointer to parent broker context */ +} BrokerClient; + +/* -------------------------------------------------------------------------- */ +/* Broker subscription tracking */ +/* -------------------------------------------------------------------------- */ +typedef struct BrokerSub { +#ifdef WOLFMQTT_STATIC_MEMORY + byte in_use; + char filter[BROKER_MAX_FILTER_LEN]; +#else + char* filter; + struct BrokerSub* next; +#endif + struct BrokerClient* client; +} BrokerSub; + +/* -------------------------------------------------------------------------- */ +/* Broker context */ +/* -------------------------------------------------------------------------- */ +typedef struct MqttBroker { + BROKER_SOCKET_T listen_sock; + word16 port; + int running; + const char* auth_user; + const char* auth_pass; + MqttBrokerNet net; +#ifdef WOLFMQTT_STATIC_MEMORY + BrokerClient clients[BROKER_MAX_CLIENTS]; + BrokerSub subs[BROKER_MAX_SUBS]; +#else + BrokerClient* clients; + BrokerSub* subs; +#endif +} MqttBroker; + +/* -------------------------------------------------------------------------- */ +/* Public API */ +/* -------------------------------------------------------------------------- */ + +/* Initialize the broker context with network callbacks */ +WOLFMQTT_API int MqttBroker_Init(MqttBroker* broker, MqttBrokerNet* net); + +/* Run the broker main loop (blocking) */ +WOLFMQTT_API int MqttBroker_Run(MqttBroker* broker); + +/* Execute a single iteration of the broker loop (for embedded main loops) */ +WOLFMQTT_API int MqttBroker_Step(MqttBroker* broker); + +/* Signal the broker loop to stop */ +WOLFMQTT_API int MqttBroker_Stop(MqttBroker* broker); + +/* Clean up broker resources */ +WOLFMQTT_API int MqttBroker_Free(MqttBroker* broker); + +/* Default POSIX backend initializer. + * Only available when WOLFMQTT_BROKER_CUSTOM_NET is NOT defined. */ +#ifndef WOLFMQTT_BROKER_CUSTOM_NET +WOLFMQTT_API int MqttBrokerNet_Init(MqttBrokerNet* net); +#endif + +/* CLI wrapper - retained for backward compatibility */ +WOLFMQTT_API int wolfmqtt_broker(int argc, char** argv); + +#endif /* WOLFMQTT_BROKER */ #ifdef __cplusplus } /* extern "C" */ From 984cd96921c849bad81625f093e0433e82dc9b2a Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 5 Feb 2026 08:55:16 -0800 Subject: [PATCH 05/13] Support for QoS level 1, retained, LWT, unique client id and no malloc (WOLFMQTT_STATIC_MEMEORY). --- src/mqtt_broker.c | 731 ++++++++++++++++++++++++++++++++++++++++- wolfmqtt/mqtt_broker.h | 38 +++ 2 files changed, 757 insertions(+), 12 deletions(-) diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index cfb13f75c..c0b45c238 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -330,6 +330,12 @@ static void BrokerClient_Free(BrokerClient* bc) if (bc->password) { WOLFMQTT_FREE(bc->password); } + if (bc->will_topic) { + WOLFMQTT_FREE(bc->will_topic); + } + if (bc->will_payload) { + WOLFMQTT_FREE(bc->will_payload); + } if (bc->tx_buf) { WOLFMQTT_FREE(bc->tx_buf); } @@ -480,10 +486,42 @@ static void BrokerSubs_RemoveClient(MqttBroker* broker, BrokerClient* bc) } static int BrokerSubs_Add(MqttBroker* broker, BrokerClient* bc, - const char* filter, word16 filter_len) + const char* filter, word16 filter_len, MqttQoS qos) { BrokerSub* sub = NULL; + /* Check for existing subscription to same filter by same client */ +#ifdef WOLFMQTT_STATIC_MEMORY + { + int i; + for (i = 0; i < BROKER_MAX_SUBS; i++) { + if (broker->subs[i].in_use && broker->subs[i].client == bc && + (word16)XSTRLEN(broker->subs[i].filter) == filter_len && + XMEMCMP(broker->subs[i].filter, filter, filter_len) == 0) { + broker->subs[i].qos = qos; + PRINTF("broker: sub update sock=%d filter=%s qos=%d", + (int)bc->sock, broker->subs[i].filter, qos); + return MQTT_CODE_SUCCESS; + } + } + } +#else + { + BrokerSub* cur = broker->subs; + while (cur) { + if (cur->client == bc && cur->filter != NULL && + (word16)XSTRLEN(cur->filter) == filter_len && + XMEMCMP(cur->filter, filter, filter_len) == 0) { + cur->qos = qos; + PRINTF("broker: sub update sock=%d filter=%s qos=%d", + (int)bc->sock, cur->filter, qos); + return MQTT_CODE_SUCCESS; + } + cur = cur->next; + } + } +#endif + #ifdef WOLFMQTT_STATIC_MEMORY { int i; @@ -522,7 +560,9 @@ static int BrokerSubs_Add(MqttBroker* broker, BrokerClient* bc, #endif sub->client = bc; - PRINTF("broker: sub add sock=%d filter=%s", (int)bc->sock, sub->filter); + sub->qos = qos; + PRINTF("broker: sub add sock=%d filter=%s qos=%d", + (int)bc->sock, sub->filter, qos); return MQTT_CODE_SUCCESS; } @@ -571,6 +611,536 @@ static void BrokerSubs_Remove(MqttBroker* broker, BrokerClient* bc, #endif } +/* -------------------------------------------------------------------------- */ +/* Packet ID generation */ +/* -------------------------------------------------------------------------- */ +static word16 BrokerNextPacketId(MqttBroker* broker) +{ + word16 id = broker->next_packet_id; + broker->next_packet_id++; + if (broker->next_packet_id == 0) { + broker->next_packet_id = 1; /* wrap: skip 0 */ + } + return id; +} + +/* -------------------------------------------------------------------------- */ +/* Client lookup by ID */ +/* -------------------------------------------------------------------------- */ +static BrokerClient* BrokerClient_FindByClientId(MqttBroker* broker, + const char* client_id, BrokerClient* exclude) +{ + if (broker == NULL || client_id == NULL || client_id[0] == '\0') { + return NULL; + } +#ifdef WOLFMQTT_STATIC_MEMORY + { + int i; + for (i = 0; i < BROKER_MAX_CLIENTS; i++) { + BrokerClient* bc = &broker->clients[i]; + if (bc->in_use && bc != exclude && + bc->client_id[0] != '\0' && + XSTRCMP(bc->client_id, client_id) == 0) { + return bc; + } + } + } +#else + { + BrokerClient* bc = broker->clients; + while (bc) { + if (bc != exclude && bc->client_id != NULL && + XSTRCMP(bc->client_id, client_id) == 0) { + return bc; + } + bc = bc->next; + } + } +#endif + return NULL; +} + +/* -------------------------------------------------------------------------- */ +/* Subscription helpers for clean session */ +/* -------------------------------------------------------------------------- */ +static void BrokerSubs_RemoveByClientId(MqttBroker* broker, + const char* client_id) +{ + if (broker == NULL || client_id == NULL || client_id[0] == '\0') { + return; + } +#ifdef WOLFMQTT_STATIC_MEMORY + { + int i; + for (i = 0; i < BROKER_MAX_SUBS; i++) { + BrokerSub* s = &broker->subs[i]; + if (s->in_use && s->client != NULL && + s->client->client_id[0] != '\0' && + XSTRCMP(s->client->client_id, client_id) == 0) { + XMEMSET(s, 0, sizeof(BrokerSub)); + } + } + } +#else + { + BrokerSub* cur = broker->subs; + BrokerSub* prev = NULL; + while (cur) { + BrokerSub* next = cur->next; + if (cur->client != NULL && cur->client->client_id != NULL && + XSTRCMP(cur->client->client_id, client_id) == 0) { + if (prev) { + prev->next = next; + } + else { + broker->subs = next; + } + if (cur->filter) { + WOLFMQTT_FREE(cur->filter); + } + WOLFMQTT_FREE(cur); + } + else { + prev = cur; + } + cur = next; + } + } +#endif +} + +static void BrokerSubs_ReassociateClient(MqttBroker* broker, + const char* client_id, BrokerClient* new_bc) +{ + if (broker == NULL || client_id == NULL || client_id[0] == '\0' || + new_bc == NULL) { + return; + } +#ifdef WOLFMQTT_STATIC_MEMORY + { + int i; + for (i = 0; i < BROKER_MAX_SUBS; i++) { + BrokerSub* s = &broker->subs[i]; + if (s->in_use && s->client != NULL && + s->client->client_id[0] != '\0' && + XSTRCMP(s->client->client_id, client_id) == 0) { + s->client = new_bc; + } + } + } +#else + { + BrokerSub* cur = broker->subs; + while (cur) { + if (cur->client != NULL && cur->client->client_id != NULL && + XSTRCMP(cur->client->client_id, client_id) == 0) { + cur->client = new_bc; + } + cur = cur->next; + } + } +#endif +} + +/* -------------------------------------------------------------------------- */ +/* Retained message management */ +/* -------------------------------------------------------------------------- */ +static int BrokerRetained_Store(MqttBroker* broker, const char* topic, + const byte* payload, word16 payload_len) +{ + BrokerRetainedMsg* msg = NULL; + + if (broker == NULL || topic == NULL) { + return MQTT_CODE_ERROR_BAD_ARG; + } + +#ifdef WOLFMQTT_STATIC_MEMORY + { + int i; + /* Look for existing retained msg on this topic */ + for (i = 0; i < BROKER_MAX_RETAINED; i++) { + if (broker->retained[i].in_use && + XSTRCMP(broker->retained[i].topic, topic) == 0) { + msg = &broker->retained[i]; + break; + } + } + /* If not found, find a free slot */ + if (msg == NULL) { + for (i = 0; i < BROKER_MAX_RETAINED; i++) { + if (!broker->retained[i].in_use) { + msg = &broker->retained[i]; + break; + } + } + } + if (msg == NULL) { + return MQTT_CODE_ERROR_MEMORY; + } + XMEMSET(msg, 0, sizeof(*msg)); + msg->in_use = 1; + { + int tlen = (int)XSTRLEN(topic); + if (tlen >= BROKER_MAX_TOPIC_LEN) { + tlen = BROKER_MAX_TOPIC_LEN - 1; + } + XMEMCPY(msg->topic, topic, (size_t)tlen); + msg->topic[tlen] = '\0'; + } + if (payload_len > 0 && payload != NULL) { + if (payload_len > BROKER_MAX_PAYLOAD_LEN) { + payload_len = BROKER_MAX_PAYLOAD_LEN; + } + XMEMCPY(msg->payload, payload, payload_len); + } + msg->payload_len = payload_len; + } +#else + { + BrokerRetainedMsg* cur = broker->retained; + while (cur) { + if (cur->topic != NULL && XSTRCMP(cur->topic, topic) == 0) { + msg = cur; + break; + } + cur = cur->next; + } + if (msg != NULL) { + /* Replace existing: free old payload */ + if (msg->payload) { + WOLFMQTT_FREE(msg->payload); + msg->payload = NULL; + } + msg->payload_len = 0; + } + else { + /* Allocate new */ + int tlen = (int)XSTRLEN(topic); + msg = (BrokerRetainedMsg*)WOLFMQTT_MALLOC( + sizeof(BrokerRetainedMsg)); + if (msg == NULL) { + return MQTT_CODE_ERROR_MEMORY; + } + XMEMSET(msg, 0, sizeof(*msg)); + msg->topic = (char*)WOLFMQTT_MALLOC((size_t)tlen + 1); + if (msg->topic == NULL) { + WOLFMQTT_FREE(msg); + return MQTT_CODE_ERROR_MEMORY; + } + XMEMCPY(msg->topic, topic, (size_t)tlen); + msg->topic[tlen] = '\0'; + msg->next = broker->retained; + broker->retained = msg; + } + if (payload_len > 0 && payload != NULL) { + msg->payload = (byte*)WOLFMQTT_MALLOC(payload_len); + if (msg->payload == NULL) { + return MQTT_CODE_ERROR_MEMORY; + } + XMEMCPY(msg->payload, payload, payload_len); + } + msg->payload_len = payload_len; + } +#endif + + PRINTF("broker: retained store topic=%s len=%u", topic, + (unsigned)payload_len); + return MQTT_CODE_SUCCESS; +} + +static void BrokerRetained_Delete(MqttBroker* broker, const char* topic) +{ + if (broker == NULL || topic == NULL) { + return; + } +#ifdef WOLFMQTT_STATIC_MEMORY + { + int i; + for (i = 0; i < BROKER_MAX_RETAINED; i++) { + if (broker->retained[i].in_use && + XSTRCMP(broker->retained[i].topic, topic) == 0) { + PRINTF("broker: retained delete topic=%s", topic); + XMEMSET(&broker->retained[i], 0, sizeof(BrokerRetainedMsg)); + return; + } + } + } +#else + { + BrokerRetainedMsg* cur = broker->retained; + BrokerRetainedMsg* prev = NULL; + while (cur) { + BrokerRetainedMsg* next = cur->next; + if (cur->topic != NULL && XSTRCMP(cur->topic, topic) == 0) { + PRINTF("broker: retained delete topic=%s", topic); + if (prev) { + prev->next = next; + } + else { + broker->retained = next; + } + WOLFMQTT_FREE(cur->topic); + if (cur->payload) { + WOLFMQTT_FREE(cur->payload); + } + WOLFMQTT_FREE(cur); + return; + } + prev = cur; + cur = next; + } + } +#endif +} + +static void BrokerRetained_FreeAll(MqttBroker* broker) +{ +#ifdef WOLFMQTT_STATIC_MEMORY + { + int i; + for (i = 0; i < BROKER_MAX_RETAINED; i++) { + XMEMSET(&broker->retained[i], 0, sizeof(BrokerRetainedMsg)); + } + } +#else + { + BrokerRetainedMsg* cur = broker->retained; + while (cur) { + BrokerRetainedMsg* next = cur->next; + if (cur->topic) { + WOLFMQTT_FREE(cur->topic); + } + if (cur->payload) { + WOLFMQTT_FREE(cur->payload); + } + WOLFMQTT_FREE(cur); + cur = next; + } + broker->retained = NULL; + } +#endif +} + +/* -------------------------------------------------------------------------- */ +/* LWT (Last Will and Testament) helpers */ +/* -------------------------------------------------------------------------- */ +static void BrokerClient_ClearWill(BrokerClient* bc) +{ + if (bc == NULL) { + return; + } + bc->has_will = 0; + bc->will_qos = MQTT_QOS_0; + bc->will_retain = 0; + bc->will_payload_len = 0; +#ifdef WOLFMQTT_STATIC_MEMORY + bc->will_topic[0] = '\0'; +#else + if (bc->will_topic) { + WOLFMQTT_FREE(bc->will_topic); + bc->will_topic = NULL; + } + if (bc->will_payload) { + WOLFMQTT_FREE(bc->will_payload); + bc->will_payload = NULL; + } +#endif +} + +/* Forward declaration needed for BrokerClient_PublishWill */ +static int BrokerTopicMatch(const char* filter, const char* topic); + +static void BrokerRetained_DeliverToClient(MqttBroker* broker, + BrokerClient* bc, const char* filter, MqttQoS sub_qos) +{ + if (broker == NULL || bc == NULL || filter == NULL) { + return; + } + (void)sub_qos; /* retained always delivered at QoS 0 in this broker */ + +#ifdef WOLFMQTT_STATIC_MEMORY + { + int i; + for (i = 0; i < BROKER_MAX_RETAINED; i++) { + BrokerRetainedMsg* rm = &broker->retained[i]; + if (!rm->in_use || rm->topic[0] == '\0') { + continue; + } + if (BrokerTopicMatch(filter, rm->topic)) { + MqttPublish out_pub; + int enc_rc; + XMEMSET(&out_pub, 0, sizeof(out_pub)); + out_pub.topic_name = rm->topic; + out_pub.qos = MQTT_QOS_0; + out_pub.retain = 1; + out_pub.duplicate = 0; + out_pub.buffer = (rm->payload_len > 0) ? rm->payload : NULL; + out_pub.total_len = rm->payload_len; +#ifdef WOLFMQTT_V5 + out_pub.protocol_level = bc->protocol_level; +#endif + enc_rc = MqttEncode_Publish(bc->tx_buf, + BROKER_TX_BUF_SZ, &out_pub, 0); + if (enc_rc > 0) { + PRINTF("broker: retained deliver sock=%d topic=%s " + "len=%u", (int)bc->sock, rm->topic, + (unsigned)rm->payload_len); + (void)MqttPacket_Write(&bc->client, bc->tx_buf, enc_rc); + } + } + } + } +#else + { + BrokerRetainedMsg* rm = broker->retained; + while (rm) { + if (rm->topic != NULL && BrokerTopicMatch(filter, rm->topic)) { + MqttPublish out_pub; + int enc_rc; + XMEMSET(&out_pub, 0, sizeof(out_pub)); + out_pub.topic_name = rm->topic; + out_pub.qos = MQTT_QOS_0; + out_pub.retain = 1; + out_pub.duplicate = 0; + out_pub.buffer = (rm->payload_len > 0) ? rm->payload : NULL; + out_pub.total_len = rm->payload_len; +#ifdef WOLFMQTT_V5 + out_pub.protocol_level = bc->protocol_level; +#endif + enc_rc = MqttEncode_Publish(bc->tx_buf, + bc->tx_buf_len, &out_pub, 0); + if (enc_rc > 0) { + PRINTF("broker: retained deliver sock=%d topic=%s " + "len=%u", (int)bc->sock, rm->topic, + (unsigned)rm->payload_len); + (void)MqttPacket_Write(&bc->client, bc->tx_buf, enc_rc); + } + } + rm = rm->next; + } + } +#endif +} + +static void BrokerClient_PublishWill(MqttBroker* broker, BrokerClient* bc) +{ + if (broker == NULL || bc == NULL || !bc->has_will) { + return; + } + +#ifdef WOLFMQTT_STATIC_MEMORY + if (bc->will_topic[0] == '\0') { + return; + } +#else + if (bc->will_topic == NULL) { + return; + } +#endif + + PRINTF("broker: LWT publish sock=%d topic=%s len=%u", + (int)bc->sock, bc->will_topic, (unsigned)bc->will_payload_len); + + /* Handle retain flag on will message */ + if (bc->will_retain) { + if (bc->will_payload_len == 0) { + BrokerRetained_Delete(broker, bc->will_topic); + } + else { + (void)BrokerRetained_Store(broker, bc->will_topic, + bc->will_payload, bc->will_payload_len); + } + } + + /* Fan out to matching subscribers */ +#ifdef WOLFMQTT_STATIC_MEMORY + { + int si; + for (si = 0; si < BROKER_MAX_SUBS; si++) { + BrokerSub* sub = &broker->subs[si]; + if (!sub->in_use || sub->client == NULL || + sub->client->protocol_level == 0 || + sub->filter[0] == '\0') { + continue; + } + if (sub->client != bc && + BrokerTopicMatch(sub->filter, bc->will_topic)) { + MqttPublish out_pub; + MqttQoS eff_qos; + int enc_rc; + XMEMSET(&out_pub, 0, sizeof(out_pub)); + out_pub.topic_name = bc->will_topic; + eff_qos = (bc->will_qos < sub->qos) ? + bc->will_qos : sub->qos; + if (eff_qos > MQTT_QOS_1) { + eff_qos = MQTT_QOS_1; + } + out_pub.qos = eff_qos; + out_pub.retain = 0; + out_pub.duplicate = 0; + out_pub.buffer = (bc->will_payload_len > 0) ? + bc->will_payload : NULL; + out_pub.total_len = bc->will_payload_len; + if (eff_qos >= MQTT_QOS_1) { + out_pub.packet_id = BrokerNextPacketId(broker); + } +#ifdef WOLFMQTT_V5 + out_pub.protocol_level = sub->client->protocol_level; +#endif + enc_rc = MqttEncode_Publish(sub->client->tx_buf, + BROKER_TX_BUF_SZ, &out_pub, 0); + if (enc_rc > 0) { + (void)MqttPacket_Write(&sub->client->client, + sub->client->tx_buf, enc_rc); + } + } + } + } +#else + { + BrokerSub* sub = broker->subs; + while (sub) { + if (sub->client != NULL && sub->client != bc && + sub->client->protocol_level != 0 && + sub->filter != NULL && + BrokerTopicMatch(sub->filter, bc->will_topic)) { + MqttPublish out_pub; + MqttQoS eff_qos; + int enc_rc; + XMEMSET(&out_pub, 0, sizeof(out_pub)); + out_pub.topic_name = bc->will_topic; + eff_qos = (bc->will_qos < sub->qos) ? + bc->will_qos : sub->qos; + if (eff_qos > MQTT_QOS_1) { + eff_qos = MQTT_QOS_1; + } + out_pub.qos = eff_qos; + out_pub.retain = 0; + out_pub.duplicate = 0; + out_pub.buffer = (bc->will_payload_len > 0) ? + bc->will_payload : NULL; + out_pub.total_len = bc->will_payload_len; + if (eff_qos >= MQTT_QOS_1) { + out_pub.packet_id = BrokerNextPacketId(broker); + } +#ifdef WOLFMQTT_V5 + out_pub.protocol_level = sub->client->protocol_level; +#endif + enc_rc = MqttEncode_Publish(sub->client->tx_buf, + sub->client->tx_buf_len, &out_pub, 0); + if (enc_rc > 0) { + (void)MqttPacket_Write(&sub->client->client, + sub->client->tx_buf, enc_rc); + } + } + sub = sub->next; + } + } +#endif + + BrokerClient_ClearWill(bc); +} + /* -------------------------------------------------------------------------- */ /* Topic matching */ /* -------------------------------------------------------------------------- */ @@ -735,6 +1305,86 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, mc.protocol_level, mc.clean_session, mc.enable_lwt, bc->client_id[0] ? bc->client_id : "(null)"); + /* Client ID uniqueness and clean session handling */ + bc->clean_session = mc.clean_session; + if (bc->client_id[0] != '\0') { + BrokerClient* old = BrokerClient_FindByClientId(broker, + bc->client_id, bc); + if (old != NULL) { + PRINTF("broker: duplicate client_id=%s, disconnecting " + "old sock=%d", bc->client_id, (int)old->sock); + /* Publish old client's will on takeover */ +#ifdef WOLFMQTT_V5 + if (old->protocol_level < MQTT_CONNECT_PROTOCOL_LEVEL_5) { + BrokerClient_PublishWill(broker, old); + } + else { + BrokerClient_ClearWill(old); + } +#else + BrokerClient_PublishWill(broker, old); +#endif + if (!mc.clean_session) { + /* Reassociate old client's subs to new client */ + BrokerSubs_ReassociateClient(broker, bc->client_id, bc); + } + BrokerSubs_RemoveClient(broker, old); + BrokerClient_Remove(broker, old); + } + if (mc.clean_session) { + /* Remove any remaining subs for this client_id */ + BrokerSubs_RemoveByClientId(broker, bc->client_id); + } + } + + /* Store Last Will and Testament */ + BrokerClient_ClearWill(bc); + if (mc.enable_lwt && mc.lwt_msg != NULL) { + if (mc.lwt_msg->topic_name != NULL && + mc.lwt_msg->topic_name_len > 0) { +#ifdef WOLFMQTT_STATIC_MEMORY + { + word16 wt_len = mc.lwt_msg->topic_name_len; + if (wt_len >= BROKER_MAX_TOPIC_LEN) { + wt_len = BROKER_MAX_TOPIC_LEN - 1; + } + XMEMCPY(bc->will_topic, mc.lwt_msg->topic_name, wt_len); + bc->will_topic[wt_len] = '\0'; + } +#else + bc->will_topic = (char*)WOLFMQTT_MALLOC( + mc.lwt_msg->topic_name_len + 1); + if (bc->will_topic != NULL) { + XMEMCPY(bc->will_topic, mc.lwt_msg->topic_name, + mc.lwt_msg->topic_name_len); + bc->will_topic[mc.lwt_msg->topic_name_len] = '\0'; + } +#endif + } + if (mc.lwt_msg->total_len > 0 && mc.lwt_msg->buffer != NULL) { + word16 wp_len = (word16)mc.lwt_msg->total_len; +#ifdef WOLFMQTT_STATIC_MEMORY + if (wp_len > BROKER_MAX_PAYLOAD_LEN) { + wp_len = BROKER_MAX_PAYLOAD_LEN; + } + XMEMCPY(bc->will_payload, mc.lwt_msg->buffer, wp_len); +#else + bc->will_payload = (byte*)WOLFMQTT_MALLOC(wp_len); + if (bc->will_payload != NULL) { + XMEMCPY(bc->will_payload, mc.lwt_msg->buffer, wp_len); + } +#endif + bc->will_payload_len = wp_len; + } + bc->will_qos = mc.lwt_msg->qos; + bc->will_retain = mc.lwt_msg->retain; + bc->has_will = 1; + PRINTF("broker: LWT stored sock=%d topic=%s qos=%d retain=%d " + "len=%u", (int)bc->sock, bc->will_topic, + bc->will_qos, bc->will_retain, + (unsigned)bc->will_payload_len); + } + /* Store credentials */ #ifdef WOLFMQTT_STATIC_MEMORY bc->username[0] = '\0'; @@ -879,11 +1529,33 @@ static int BrokerHandle_Subscribe(BrokerClient* bc, int rx_len, for (i = 0; i < sub.topic_count && i < MAX_MQTT_TOPICS; i++) { const char* f = sub.topics[i].topic_filter; word16 flen = 0; + MqttQoS topic_qos = sub.topics[i].qos; + MqttQoS granted_qos; + + /* Cap at QoS 1 for this broker */ + if (topic_qos > MQTT_QOS_1) { + topic_qos = MQTT_QOS_1; + } + granted_qos = topic_qos; + if (f && MqttDecode_Num((byte*)f - MQTT_DATA_LEN_SIZE, &flen, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) { - (void)BrokerSubs_Add(broker, bc, f, flen); + (void)BrokerSubs_Add(broker, bc, f, flen, topic_qos); + + /* Deliver retained messages matching this filter */ + { + char filter_z[BROKER_MAX_FILTER_LEN]; + word16 copy_len = flen; + if (copy_len >= BROKER_MAX_FILTER_LEN) { + copy_len = BROKER_MAX_FILTER_LEN - 1; + } + XMEMCPY(filter_z, f, copy_len); + filter_z[copy_len] = '\0'; + BrokerRetained_DeliverToClient(broker, bc, filter_z, + topic_qos); + } } - return_codes[i] = MQTT_SUBSCRIBE_ACK_CODE_SUCCESS_MAX_QOS0; + return_codes[i] = (byte)granted_qos; } rc = BrokerSend_SubAck(bc, sub.packet_id, return_codes, @@ -998,12 +1670,24 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, } } + /* Handle retained messages */ + if (topic != NULL && pub.retain) { + if (pub.total_len == 0) { + BrokerRetained_Delete(broker, topic); + } + else if (payload != NULL) { + (void)BrokerRetained_Store(broker, topic, payload, + (word16)pub.total_len); + } + } + if (topic != NULL && (payload != NULL || pub.total_len == 0)) { /* Fan out to matching subscribers */ #ifdef WOLFMQTT_STATIC_MEMORY int si; for (si = 0; si < BROKER_MAX_SUBS; si++) { BrokerSub* sub = &broker->subs[si]; + MqttQoS eff_qos; if (!sub->in_use) { continue; } @@ -1013,8 +1697,16 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, MqttPublish out_pub; XMEMSET(&out_pub, 0, sizeof(out_pub)); out_pub.topic_name = topic; - out_pub.qos = MQTT_QOS_0; - out_pub.retain = pub.retain; + /* Effective QoS = min(publish_qos, sub_qos), capped at 1 */ + eff_qos = (pub.qos < sub->qos) ? pub.qos : sub->qos; + if (eff_qos > MQTT_QOS_1) { + eff_qos = MQTT_QOS_1; + } + out_pub.qos = eff_qos; + if (eff_qos >= MQTT_QOS_1) { + out_pub.packet_id = BrokerNextPacketId(broker); + } + out_pub.retain = 0; /* not retained on forward */ out_pub.duplicate = 0; out_pub.buffer = payload; out_pub.total_len = pub.total_len; @@ -1025,9 +1717,9 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, BROKER_TX_BUF_SZ, &out_pub, 0); if (rc > 0) { PRINTF("broker: PUBLISH fwd sock=%d -> sock=%d " - "topic=%s len=%u", + "topic=%s qos=%d len=%u", (int)bc->sock, (int)sub->client->sock, - topic, (unsigned)pub.total_len); + topic, eff_qos, (unsigned)pub.total_len); (void)MqttPacket_Write(&sub->client->client, sub->client->tx_buf, rc); } @@ -1040,10 +1732,18 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, if (sub->client && sub->client->protocol_level != 0 && sub->filter && BrokerTopicMatch(sub->filter, topic)) { MqttPublish out_pub; + MqttQoS eff_qos; XMEMSET(&out_pub, 0, sizeof(out_pub)); out_pub.topic_name = topic; - out_pub.qos = MQTT_QOS_0; - out_pub.retain = pub.retain; + eff_qos = (pub.qos < sub->qos) ? pub.qos : sub->qos; + if (eff_qos > MQTT_QOS_1) { + eff_qos = MQTT_QOS_1; + } + out_pub.qos = eff_qos; + if (eff_qos >= MQTT_QOS_1) { + out_pub.packet_id = BrokerNextPacketId(broker); + } + out_pub.retain = 0; out_pub.duplicate = 0; out_pub.buffer = payload; out_pub.total_len = pub.total_len; @@ -1054,9 +1754,9 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, sub->client->tx_buf_len, &out_pub, 0); if (rc > 0) { PRINTF("broker: PUBLISH fwd sock=%d -> sock=%d " - "topic=%s len=%u", + "topic=%s qos=%d len=%u", (int)bc->sock, (int)sub->client->sock, - topic, (unsigned)pub.total_len); + topic, eff_qos, (unsigned)pub.total_len); (void)MqttPacket_Write(&sub->client->client, sub->client->tx_buf, rc); } @@ -1167,6 +1867,7 @@ static int BrokerClient_Process(MqttBroker* broker, BrokerClient* bc) } else if (rc < 0) { PRINTF("broker: read failed sock=%d rc=%d", (int)bc->sock, rc); + BrokerClient_PublishWill(broker, bc); /* abnormal disconnect */ BrokerSubs_RemoveClient(broker, bc); BrokerClient_Remove(broker, bc); return 0; @@ -1208,6 +1909,7 @@ static int BrokerClient_Process(MqttBroker* broker, BrokerClient* bc) (void)BrokerSend_PingResp(bc); break; case MQTT_PACKET_TYPE_DISCONNECT: + BrokerClient_ClearWill(bc); /* normal disconnect */ BrokerSubs_RemoveClient(broker, bc); BrokerClient_Remove(broker, bc); return 0; @@ -1222,6 +1924,7 @@ static int BrokerClient_Process(MqttBroker* broker, BrokerClient* bc) if ((now - bc->last_rx) > (WOLFMQTT_BROKER_TIME_T)(bc->keep_alive_sec * 2)) { PRINTF("broker: keepalive timeout sock=%d", (int)bc->sock); + BrokerClient_PublishWill(broker, bc); /* abnormal disconnect */ BrokerSubs_RemoveClient(broker, bc); BrokerClient_Remove(broker, bc); return 0; @@ -1244,6 +1947,7 @@ int MqttBroker_Init(MqttBroker* broker, MqttBrokerNet* net) broker->listen_sock = BROKER_SOCKET_INVALID; broker->port = MQTT_DEFAULT_PORT; broker->running = 0; + broker->next_packet_id = 1; return MQTT_CODE_SUCCESS; } @@ -1373,6 +2077,9 @@ int MqttBroker_Free(MqttBroker* broker) } #endif + /* Clean up retained messages */ + BrokerRetained_FreeAll(broker); + /* Close listen socket */ if (broker->listen_sock != BROKER_SOCKET_INVALID) { broker->net.close(broker->net.ctx, broker->listen_sock); diff --git a/wolfmqtt/mqtt_broker.h b/wolfmqtt/mqtt_broker.h index daca37d5a..f89619523 100644 --- a/wolfmqtt/mqtt_broker.h +++ b/wolfmqtt/mqtt_broker.h @@ -87,6 +87,15 @@ #ifndef BROKER_MAX_FILTER_LEN #define BROKER_MAX_FILTER_LEN 128 #endif +#ifndef BROKER_MAX_RETAINED + #define BROKER_MAX_RETAINED 16 +#endif +#ifndef BROKER_MAX_TOPIC_LEN + #define BROKER_MAX_TOPIC_LEN 128 +#endif +#ifndef BROKER_MAX_PAYLOAD_LEN + #define BROKER_MAX_PAYLOAD_LEN 4096 +#endif /* -------------------------------------------------------------------------- */ /* Forward declarations */ @@ -126,6 +135,8 @@ typedef struct BrokerClient { char password[BROKER_MAX_PASSWORD_LEN]; byte tx_buf[BROKER_TX_BUF_SZ]; byte rx_buf[BROKER_RX_BUF_SZ]; + char will_topic[BROKER_MAX_TOPIC_LEN]; + byte will_payload[BROKER_MAX_PAYLOAD_LEN]; #else char* client_id; char* username; @@ -134,12 +145,19 @@ typedef struct BrokerClient { byte* rx_buf; int tx_buf_len; int rx_buf_len; + char* will_topic; + byte* will_payload; struct BrokerClient* next; #endif BROKER_SOCKET_T sock; byte protocol_level; word16 keep_alive_sec; WOLFMQTT_BROKER_TIME_T last_rx; + byte clean_session; + byte has_will; + word16 will_payload_len; + MqttQoS will_qos; + byte will_retain; MqttNet net; MqttClient client; struct MqttBroker* broker; /* back-pointer to parent broker context */ @@ -157,8 +175,25 @@ typedef struct BrokerSub { struct BrokerSub* next; #endif struct BrokerClient* client; + MqttQoS qos; } BrokerSub; +/* -------------------------------------------------------------------------- */ +/* Retained message store */ +/* -------------------------------------------------------------------------- */ +typedef struct BrokerRetainedMsg { +#ifdef WOLFMQTT_STATIC_MEMORY + byte in_use; + char topic[BROKER_MAX_TOPIC_LEN]; + byte payload[BROKER_MAX_PAYLOAD_LEN]; +#else + char* topic; + byte* payload; + struct BrokerRetainedMsg* next; +#endif + word16 payload_len; +} BrokerRetainedMsg; + /* -------------------------------------------------------------------------- */ /* Broker context */ /* -------------------------------------------------------------------------- */ @@ -169,12 +204,15 @@ typedef struct MqttBroker { const char* auth_user; const char* auth_pass; MqttBrokerNet net; + word16 next_packet_id; #ifdef WOLFMQTT_STATIC_MEMORY BrokerClient clients[BROKER_MAX_CLIENTS]; BrokerSub subs[BROKER_MAX_SUBS]; + BrokerRetainedMsg retained[BROKER_MAX_RETAINED]; #else BrokerClient* clients; BrokerSub* subs; + BrokerRetainedMsg* retained; #endif } MqttBroker; From 63b2fc7c0a5907ab736c97427ded20a00e57d21f Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 5 Feb 2026 10:07:27 -0800 Subject: [PATCH 06/13] Improved testing for Broker --- .github/workflows/broker-check.yml | 69 +++++++ scripts/broker.test | 285 ++++++++++++++++++++++++++++ scripts/broker_multi_client_test.sh | 204 -------------------- scripts/include.am | 4 + src/mqtt_broker.c | 12 ++ 5 files changed, 370 insertions(+), 204 deletions(-) create mode 100644 .github/workflows/broker-check.yml create mode 100755 scripts/broker.test delete mode 100755 scripts/broker_multi_client_test.sh diff --git a/.github/workflows/broker-check.yml b/.github/workflows/broker-check.yml new file mode 100644 index 000000000..572a6c6dc --- /dev/null +++ b/.github/workflows/broker-check.yml @@ -0,0 +1,69 @@ +name: Broker Build Test + +on: + push: + branches: [ 'master', 'main', 'release/**' ] + pull_request: + branches: [ '*' ] + +jobs: + build: + + runs-on: ubuntu-22.04 + timeout-minutes: 5 + + strategy: + fail-fast: false + matrix: + include: + - name: "Broker default (dynamic alloc)" + cflags: "" + - name: "Broker static memory" + cflags: "-DWOLFMQTT_STATIC_MEMORY" + + steps: + - name: Install dependencies + run: | + export DEBIAN_FRONTEND=noninteractive + sudo apt-get update + sudo apt-get install -y mosquitto-clients + + - uses: actions/checkout@master + with: + repository: wolfssl/wolfssl + path: wolfssl + - name: wolfssl autogen + working-directory: ./wolfssl + run: ./autogen.sh + - name: wolfssl configure + working-directory: ./wolfssl + run: ./configure --enable-enckeys + - name: wolfssl make + working-directory: ./wolfssl + run: make + - name: wolfssl make install + working-directory: ./wolfssl + run: sudo make install + + - uses: actions/checkout@master + - name: wolfmqtt autogen + run: ./autogen.sh + + - name: "wolfmqtt configure (${{ matrix.name }})" + run: ./configure --enable-broker CFLAGS="${{ matrix.cflags }}" + - name: wolfmqtt make + run: make + + - name: "Run broker tests (${{ matrix.name }})" + run: ./scripts/broker.test + + - name: Show logs on failure + if: failure() || cancelled() + run: | + ls -la /tmp/tmp.* 2>/dev/null || true + for d in /tmp/tmp.*; do + if [ -d "$d" ]; then + echo "=== Logs in $d ===" + cat "$d"/*.log 2>/dev/null || true + fi + done diff --git a/scripts/broker.test b/scripts/broker.test new file mode 100755 index 000000000..6f6c16ab2 --- /dev/null +++ b/scripts/broker.test @@ -0,0 +1,285 @@ +#!/bin/bash + +# wolfMQTT Broker test +# Tests the wolfMQTT broker with pub/sub, retained messages, LWT, QoS 1, +# client ID takeover, and multi-client pairs. + +name="MQTT Broker" +broker_bin="src/mqtt_broker" +pub_bin="examples/pub-sub/mqtt-pub" +sub_bin="examples/pub-sub/mqtt-sub" +no_pid=-1 +broker_pid=$no_pid + +do_cleanup() { + if [ $broker_pid != $no_pid ]; then + kill -9 $broker_pid 2>/dev/null + echo "Killed broker PID $broker_pid" + broker_pid=$no_pid + fi + # Kill any lingering test processes + for pid in "${TEST_PIDS[@]:-}"; do + kill "$pid" 2>/dev/null || true + done + wait 2>/dev/null || true + if [ -n "${TMP_DIR:-}" ] && [ -d "${TMP_DIR}" ]; then + if [ "${KEEP_LOGS:-0}" = "1" ]; then + echo "Logs preserved in ${TMP_DIR}" + else + rm -rf "${TMP_DIR}" + fi + fi + if [ $1 -ne 0 ]; then + exit 1 + fi +} + +# Check for broker binary +[ ! -x ./$broker_bin ] && echo -e "\n\n$broker_bin doesn't exist" && exit 1 + +# Check for pub/sub binaries (needed for multi-client test) +has_pubsub=no +if [ -x ./$pub_bin ] && [ -x ./$sub_bin ]; then + has_pubsub=yes +fi + +# Generate a random port +generate_port() { + if [[ "$OSTYPE" == "linux"* ]]; then + port=$(($(od -An -N2 /dev/urandom) % (65535-49152) + 49152)) + elif [[ "$OSTYPE" == "darwin"* ]]; then + port=$(($(od -An -N2 /dev/random) % (65535-49152) + 49152)) + else + port=11883 + fi + echo "Using port $port" +} + +check_broker() { + timeout 10 sh -c 'until nc -v -z $0 $1; do sleep 1; done' localhost $port +} + +generate_port + +TEST_PIDS=() +TMP_DIR="$(mktemp -d)" +FAIL=0 + +echo "=== wolfMQTT Broker Test Suite ===" + +# Start broker +./$broker_bin -p $port & +broker_pid=$! +echo "Broker PID is $broker_pid" +check_broker + +# --- Test 1: Basic pub/sub with mosquitto_pub/mosquitto_sub --- +echo "" +echo "--- Test 1: Basic pub/sub ---" +if command -v mosquitto_sub >/dev/null 2>&1 && command -v mosquitto_pub >/dev/null 2>&1; then + mosquitto_sub -h 127.0.0.1 -p $port -t "test/hello" -C 1 -W 5 >"${TMP_DIR}/t1_sub.log" 2>&1 & + T1_PID=$! + sleep 1 + mosquitto_pub -h 127.0.0.1 -p $port -t "test/hello" -m "world" + wait $T1_PID 2>/dev/null + if grep -q "world" "${TMP_DIR}/t1_sub.log" 2>/dev/null; then + echo "PASS: Basic pub/sub" + else + echo "FAIL: Basic pub/sub" + FAIL=1 + fi +else + echo "SKIP: mosquitto_pub/mosquitto_sub not found" +fi + +# --- Test 2: Retained messages --- +echo "" +echo "--- Test 2: Retained messages ---" +if command -v mosquitto_sub >/dev/null 2>&1 && command -v mosquitto_pub >/dev/null 2>&1; then + mosquitto_pub -h 127.0.0.1 -p $port -t "test/retained" -m "stored_msg" -r + sleep 1 + mosquitto_sub -h 127.0.0.1 -p $port -t "test/retained" -C 1 -W 5 >"${TMP_DIR}/t2_sub.log" 2>&1 + if grep -q "stored_msg" "${TMP_DIR}/t2_sub.log" 2>/dev/null; then + echo "PASS: Retained message delivered on subscribe" + else + echo "FAIL: Retained message not delivered" + FAIL=1 + fi +else + echo "SKIP: mosquitto_pub/mosquitto_sub not found" +fi + +# --- Test 3: QoS 1 forwarding --- +echo "" +echo "--- Test 3: QoS 1 forwarding ---" +if command -v mosquitto_sub >/dev/null 2>&1 && command -v mosquitto_pub >/dev/null 2>&1; then + mosquitto_sub -h 127.0.0.1 -p $port -t "test/qos1" -q 1 -C 1 -W 5 >"${TMP_DIR}/t3_sub.log" 2>&1 & + T3_PID=$! + sleep 1 + mosquitto_pub -h 127.0.0.1 -p $port -t "test/qos1" -m "qos1_msg" -q 1 + wait $T3_PID 2>/dev/null + if grep -q "qos1_msg" "${TMP_DIR}/t3_sub.log" 2>/dev/null; then + echo "PASS: QoS 1 forwarding" + else + echo "FAIL: QoS 1 forwarding" + FAIL=1 + fi +else + echo "SKIP: mosquitto_pub/mosquitto_sub not found" +fi + +# --- Test 4: Last Will and Testament --- +echo "" +echo "--- Test 4: Last Will and Testament ---" +if command -v mosquitto_sub >/dev/null 2>&1; then + mosquitto_sub -h 127.0.0.1 -p $port -t "test/will" -C 1 -W 10 >"${TMP_DIR}/t4_sub.log" 2>&1 & + T4_SUB_PID=$! + sleep 1 + # Connect a client with a will, then kill it abruptly + mosquitto_sub -h 127.0.0.1 -p $port -t "test/nothing" \ + --will-topic "test/will" --will-payload "client_died" -W 3 >"${TMP_DIR}/t4_will.log" 2>&1 & + T4_WILL_PID=$! + sleep 1 + kill -9 $T4_WILL_PID 2>/dev/null + wait $T4_WILL_PID 2>/dev/null || true + wait $T4_SUB_PID 2>/dev/null || true + if grep -q "client_died" "${TMP_DIR}/t4_sub.log" 2>/dev/null; then + echo "PASS: LWT delivered on abnormal disconnect" + else + echo "FAIL: LWT not delivered" + FAIL=1 + fi +else + echo "SKIP: mosquitto_sub not found" +fi + +# --- Test 5: Client ID takeover --- +echo "" +echo "--- Test 5: Client ID takeover ---" +if command -v mosquitto_sub >/dev/null 2>&1 && command -v mosquitto_pub >/dev/null 2>&1; then + mosquitto_sub -h 127.0.0.1 -p $port -t "test/takeover" -i "takeoverClient" -W 5 >"${TMP_DIR}/t5_old.log" 2>&1 & + T5_OLD_PID=$! + sleep 1 + # Connect second client with same ID - should disconnect old + mosquitto_sub -h 127.0.0.1 -p $port -t "test/takeover" -i "takeoverClient" -C 1 -W 5 >"${TMP_DIR}/t5_new.log" 2>&1 & + T5_NEW_PID=$! + sleep 1 + # Old client should have been disconnected + wait $T5_OLD_PID 2>/dev/null + T5_OLD_EXIT=$? + # Send message to new client + mosquitto_pub -h 127.0.0.1 -p $port -t "test/takeover" -m "after_takeover" + wait $T5_NEW_PID 2>/dev/null || true + if [ $T5_OLD_EXIT -ne 0 ]; then + echo "PASS: Client ID takeover (old client disconnected)" + else + echo "FAIL: Old client was not disconnected" + FAIL=1 + fi +else + echo "SKIP: mosquitto_pub/mosquitto_sub not found" +fi + +# --- Test 6: Multi-client paired pub/sub --- +echo "" +echo "--- Test 6: Multi-client paired pub/sub ---" +if [ "$has_pubsub" = "yes" ]; then + NUM_PAIRS=2 + NUM_CLIENTS=$((NUM_PAIRS * 2)) + SUB_LOGS=() + + start_sub() { + local client_id="$1" + local topic="$2" + local log="$3" + stdbuf -oL ./${sub_bin} -T -h localhost -p ${port} -i "${client_id}" \ + -n "${topic}" -q 0 >"${log}" 2>&1 & + TEST_PIDS+=("$!") + SUB_LOGS+=("${log}") + } + + run_pub() { + local client_id="$1" + local topic="$2" + local msg="$3" + ./${pub_bin} -T -h localhost -p ${port} -i "${client_id}" -n "${topic}" \ + -m "${msg}" -q 0 >/dev/null 2>&1 + } + + # Start subscribers + for p in $(seq 0 $((NUM_PAIRS - 1))); do + start_sub "sub_${p}_a" "pair/${p}/a" "${TMP_DIR}/sub_${p}_a.log" + start_sub "sub_${p}_b" "pair/${p}/b" "${TMP_DIR}/sub_${p}_b.log" + done + + # Wait for subscribers to be ready + echo "Waiting for ${NUM_CLIENTS} subscribers to connect..." + ELAPSED=0 + while [ $ELAPSED -lt 15 ]; do + READY=0 + for log in "${SUB_LOGS[@]}"; do + if grep -q "MQTT Waiting for message" "${log}" 2>/dev/null; then + READY=$((READY + 1)) + fi + done + if [ $READY -eq ${#SUB_LOGS[@]} ]; then + echo "All subscribers ready after ${ELAPSED}s" + break + fi + sleep 1 + ELAPSED=$((ELAPSED + 1)) + done + + # Publish messages + for p in $(seq 0 $((NUM_PAIRS - 1))); do + run_pub "pub_${p}_a" "pair/${p}/b" "hello_from_${p}_a" + run_pub "pub_${p}_b" "pair/${p}/a" "hello_from_${p}_b" + done + + # Wait for delivery + sleep 3 + + # Check results + T6_PASS=0 + T6_FAIL=0 + for p in $(seq 0 $((NUM_PAIRS - 1))); do + if grep -q "hello_from_${p}_b" "${TMP_DIR}/sub_${p}_a.log" 2>/dev/null; then + T6_PASS=$((T6_PASS + 1)) + else + T6_FAIL=$((T6_FAIL + 1)) + fi + if grep -q "hello_from_${p}_a" "${TMP_DIR}/sub_${p}_b.log" 2>/dev/null; then + T6_PASS=$((T6_PASS + 1)) + else + T6_FAIL=$((T6_FAIL + 1)) + fi + done + + # Clean up subscriber processes + for pid in "${TEST_PIDS[@]:-}"; do + kill "$pid" 2>/dev/null || true + wait "$pid" 2>/dev/null || true + done + TEST_PIDS=() + + if [ $T6_FAIL -eq 0 ]; then + echo "PASS: Multi-client pub/sub (${T6_PASS}/${NUM_CLIENTS} messages delivered)" + else + echo "FAIL: Multi-client pub/sub (${T6_FAIL} messages missing)" + FAIL=1 + fi +else + echo "SKIP: mqtt-pub/mqtt-sub not built" +fi + +# --- Summary --- +echo "" +if [ $FAIL -ne 0 ]; then + KEEP_LOGS=1 + echo "$name Tests FAILED" + do_cleanup "-1" +fi + +do_cleanup "0" +echo "$name Tests Passed" +exit 0 diff --git a/scripts/broker_multi_client_test.sh b/scripts/broker_multi_client_test.sh deleted file mode 100755 index c8097d905..000000000 --- a/scripts/broker_multi_client_test.sh +++ /dev/null @@ -1,204 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -HOST="localhost" -PORT="1883" -USER="" -PASS="" -NUM_CLIENTS="2" -READY_TIMEOUT="30" -RUN_SECS="5" -KEEP_LOGS="${KEEP_LOGS:-0}" - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -PUB_BIN="${ROOT_DIR}/examples/pub-sub/mqtt-pub" -SUB_BIN="${ROOT_DIR}/examples/pub-sub/mqtt-sub" - -usage() { - cat <= 2 -if (( NUM_CLIENTS < 2 || NUM_CLIENTS % 2 != 0 )); then - echo "error: -n must be an even number >= 2 (got ${NUM_CLIENTS})" - exit 1 -fi - -NUM_PAIRS=$(( NUM_CLIENTS / 2 )) - -if [[ ! -x "${PUB_BIN}" || ! -x "${SUB_BIN}" ]]; then - echo "error: mqtt-pub or mqtt-sub not found. Build examples first." - exit 1 -fi - -TMP_DIR="$(mktemp -d)" -PIDS=() - -cleanup() { - for pid in "${PIDS[@]:-}"; do - kill "${pid}" 2>/dev/null || true - done - wait 2>/dev/null || true - if [[ "${KEEP_LOGS}" == "1" ]]; then - echo "Logs preserved in ${TMP_DIR}" - else - rm -rf "${TMP_DIR}" - fi -} -trap cleanup EXIT - -auth_args=() -if [[ -n "${USER}" ]]; then auth_args+=(-u "${USER}"); fi -if [[ -n "${PASS}" ]]; then auth_args+=(-w "${PASS}"); fi - -# All subscriber log files (for readiness polling) -SUB_LOGS=() - -start_sub() { - local client_id="$1" - local topic="$2" - local log="$3" - # -T = test mode (disables STDIN capture so background processes work) - # -d = debug output (needed to detect readiness via log polling) - # -i = unique client ID - # stdbuf -oL = line-buffered stdout so grep can detect readiness in logs - stdbuf -oL "${SUB_BIN}" -T -h "${HOST}" -p "${PORT}" -i "${client_id}" \ - -n "${topic}" -q 0 -d "${auth_args[@]}" >"${log}" 2>&1 & - PIDS+=("$!") - SUB_LOGS+=("${log}") -} - -run_pub() { - local client_id="$1" - local topic="$2" - local msg="$3" - # -T = test mode (disables STDIN capture) - # -i = unique client ID - "${PUB_BIN}" -T -h "${HOST}" -p "${PORT}" -i "${client_id}" -n "${topic}" \ - -m "${msg}" -q 0 "${auth_args[@]}" >/dev/null 2>&1 -} - -# Wait until all subscriber logs contain the ready marker -wait_for_subscribers() { - local total="${#SUB_LOGS[@]}" - local elapsed=0 - while (( elapsed < READY_TIMEOUT )); do - local ready_count=0 - for log in "${SUB_LOGS[@]}"; do - if grep -q "MQTT Waiting for message" "${log}" 2>/dev/null; then - (( ready_count++ )) || true - fi - done - if (( ready_count == total )); then - echo "All ${total} subscriber(s) ready after ${elapsed}s" - return 0 - fi - if (( elapsed > 0 && elapsed % 5 == 0 )); then - echo " ... ${ready_count}/${total} subscribers ready (${elapsed}s elapsed)" - fi - sleep 1 - (( elapsed++ )) || true - done - echo "WARNING: Not all subscribers ready after ${READY_TIMEOUT}s (continuing anyway)" - return 0 -} - -echo "Starting ${NUM_CLIENTS} clients (${NUM_PAIRS} pairs)..." - -# --- Start all subscribers --- -for p in $(seq 0 $(( NUM_PAIRS - 1 ))); do - start_sub "sub_${p}_a" "pair/${p}/a" "${TMP_DIR}/sub_${p}_a.log" - start_sub "sub_${p}_b" "pair/${p}/b" "${TMP_DIR}/sub_${p}_b.log" -done - -echo "Waiting for subscribers to connect and subscribe..." -wait_for_subscribers - -# --- Publish: each client publishes to its partner's topic --- -echo "Publishing ${NUM_CLIENTS} messages..." -for p in $(seq 0 $(( NUM_PAIRS - 1 ))); do - # Client A publishes to pair/P/b (B's topic) - run_pub "pub_${p}_a" "pair/${p}/b" "hello_from_${p}_a" - # Client B publishes to pair/P/a (A's topic) - run_pub "pub_${p}_b" "pair/${p}/a" "hello_from_${p}_b" -done - -# --- Wait for message delivery --- -echo "Waiting ${RUN_SECS}s for message delivery..." -sleep "${RUN_SECS}" - -# --- Check results --- -PASS_COUNT=0 -FAIL_COUNT=0 - -check_result() { - local description="$1" - local pattern="$2" - local log="$3" - if grep -q "${pattern}" "${log}" 2>/dev/null; then - (( PASS_COUNT++ )) || true - else - echo "FAIL: ${description}" - (( FAIL_COUNT++ )) || true - fi -} - -echo "" -echo "== Results ==" -for p in $(seq 0 $(( NUM_PAIRS - 1 ))); do - # A subscribed to pair/P/a, should have received hello_from_P_b - check_result "pair ${p} A (sub pair/${p}/a) did not receive hello_from_${p}_b" \ - "hello_from_${p}_b" "${TMP_DIR}/sub_${p}_a.log" - # B subscribed to pair/P/b, should have received hello_from_P_a - check_result "pair ${p} B (sub pair/${p}/b) did not receive hello_from_${p}_a" \ - "hello_from_${p}_a" "${TMP_DIR}/sub_${p}_b.log" -done - -echo "${PASS_COUNT} passed, ${FAIL_COUNT} failed (${NUM_CLIENTS} clients, ${NUM_PAIRS} pairs)" - -if (( FAIL_COUNT > 0 )); then - # Preserve logs on failure regardless of KEEP_LOGS setting - KEEP_LOGS=1 - exit 1 -fi -exit 0 diff --git a/scripts/include.am b/scripts/include.am index 2504880c3..d7036b76e 100644 --- a/scripts/include.am +++ b/scripts/include.am @@ -23,3 +23,7 @@ else dist_noinst_SCRIPTS += scripts/stress.test endif # BUILD_STRESS endif # BUILD_EXAMPLES + +if BUILD_BROKER +dist_noinst_SCRIPTS += scripts/broker.test +endif # BUILD_BROKER diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index c0b45c238..41b104fba 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -1999,6 +1999,18 @@ int MqttBroker_Step(MqttBroker* broker) if (rc > 0) { activity = 1; } + /* BrokerClient_Process may remove another client (e.g. client ID + * takeover), which could free the node that next points to. + * Validate next is still in the linked list before dereferencing */ + if (next != NULL) { + BrokerClient* v = broker->clients; + while (v != NULL && v != next) { + v = v->next; + } + if (v == NULL) { + break; /* next was freed; remaining clients handled next step */ + } + } bc = next; } } From 660246b1972aee1164cd6b8c9246b38d0d6a88f3 Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 5 Feb 2026 11:45:50 -0800 Subject: [PATCH 07/13] Added TLS support to broker --- src/mqtt_broker.c | 190 ++++++++++++++++++++++++++++++++++++++++- wolfmqtt/mqtt_broker.h | 10 +++ 2 files changed, 198 insertions(+), 2 deletions(-) diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index 41b104fba..c0a5bc33d 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -46,6 +46,9 @@ #include #include #include +#ifdef ENABLE_MQTT_TLS + #include +#endif #endif /* !WOLFMQTT_BROKER_CUSTOM_NET */ /* -------------------------------------------------------------------------- */ @@ -257,6 +260,89 @@ int MqttBrokerNet_Init(MqttBrokerNet* net) return MQTT_CODE_SUCCESS; } +#ifdef ENABLE_MQTT_TLS +static int BrokerTls_Init(MqttBroker* broker) +{ + WOLFSSL_CTX* ctx; + int rc; + + rc = wolfSSL_Init(); + if (rc != WOLFSSL_SUCCESS) { + PRINTF("broker: wolfSSL_Init failed %d", rc); + return MQTT_CODE_ERROR_BAD_ARG; + } + + ctx = wolfSSL_CTX_new(wolfSSLv23_server_method()); + if (ctx == NULL) { + PRINTF("broker: wolfSSL_CTX_new failed"); + wolfSSL_Cleanup(); + return MQTT_CODE_ERROR_MEMORY; + } + + /* Load server certificate */ + if (broker->tls_cert == NULL) { + PRINTF("broker: TLS cert not set (-c)"); + wolfSSL_CTX_free(ctx); + wolfSSL_Cleanup(); + return MQTT_CODE_ERROR_BAD_ARG; + } + rc = wolfSSL_CTX_use_certificate_file(ctx, broker->tls_cert, + WOLFSSL_FILETYPE_PEM); + if (rc != WOLFSSL_SUCCESS) { + PRINTF("broker: load cert failed %d (%s)", rc, broker->tls_cert); + wolfSSL_CTX_free(ctx); + wolfSSL_Cleanup(); + return MQTT_CODE_ERROR_BAD_ARG; + } + + /* Load server private key */ + if (broker->tls_key == NULL) { + PRINTF("broker: TLS key not set (-K)"); + wolfSSL_CTX_free(ctx); + wolfSSL_Cleanup(); + return MQTT_CODE_ERROR_BAD_ARG; + } + rc = wolfSSL_CTX_use_PrivateKey_file(ctx, broker->tls_key, + WOLFSSL_FILETYPE_PEM); + if (rc != WOLFSSL_SUCCESS) { + PRINTF("broker: load key failed %d (%s)", rc, broker->tls_key); + wolfSSL_CTX_free(ctx); + wolfSSL_Cleanup(); + return MQTT_CODE_ERROR_BAD_ARG; + } + + /* Set wolfSSL IO callbacks (reuse existing WOLFMQTT_API functions) */ + wolfSSL_CTX_SetIORecv(ctx, MqttSocket_TlsSocketReceive); + wolfSSL_CTX_SetIOSend(ctx, MqttSocket_TlsSocketSend); + + /* Mutual TLS: load CA and require client certificate */ + if (broker->tls_ca != NULL) { + rc = wolfSSL_CTX_load_verify_locations(ctx, broker->tls_ca, NULL); + if (rc != WOLFSSL_SUCCESS) { + PRINTF("broker: load CA failed %d (%s)", rc, broker->tls_ca); + wolfSSL_CTX_free(ctx); + wolfSSL_Cleanup(); + return MQTT_CODE_ERROR_BAD_ARG; + } + wolfSSL_CTX_set_verify(ctx, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + PRINTF("broker: mutual TLS enabled (CA=%s)", broker->tls_ca); + } + + broker->tls_ctx = ctx; + return MQTT_CODE_SUCCESS; +} + +static void BrokerTls_Free(MqttBroker* broker) +{ + if (broker->tls_ctx != NULL) { + wolfSSL_CTX_free(broker->tls_ctx); + broker->tls_ctx = NULL; + } + wolfSSL_Cleanup(); +} +#endif /* ENABLE_MQTT_TLS */ + #endif /* !WOLFMQTT_BROKER_CUSTOM_NET */ /* -------------------------------------------------------------------------- */ @@ -316,6 +402,13 @@ static void BrokerClient_Free(BrokerClient* bc) return; } (void)BrokerNetDisconnect(bc); +#ifdef ENABLE_MQTT_TLS + if (bc->client.tls.ssl) { + wolfSSL_shutdown(bc->client.tls.ssl); + wolfSSL_free(bc->client.tls.ssl); + bc->client.tls.ssl = NULL; + } +#endif MqttClient_DeInit(&bc->client); #ifdef WOLFMQTT_STATIC_MEMORY XMEMSET(bc, 0, sizeof(*bc)); @@ -410,6 +503,27 @@ static BrokerClient* BrokerClient_Add(MqttBroker* broker, return NULL; } +#ifdef ENABLE_MQTT_TLS + if (broker->use_tls && broker->tls_ctx) { + bc->client.tls.ssl = wolfSSL_new(broker->tls_ctx); + if (bc->client.tls.ssl == NULL) { + PRINTF("broker: wolfSSL_new failed sock=%d", (int)sock); + BrokerClient_Free(bc); + return NULL; + } + wolfSSL_SetIOReadCtx(bc->client.tls.ssl, &bc->client); + wolfSSL_SetIOWriteCtx(bc->client.tls.ssl, &bc->client); + MqttClient_Flags(&bc->client, 0, MQTT_CLIENT_FLAG_IS_TLS); + bc->tls_handshake_done = 0; + } + else +#endif + { +#ifdef ENABLE_MQTT_TLS + bc->tls_handshake_done = 1; +#endif + } + #ifndef WOLFMQTT_STATIC_MEMORY /* Prepend to linked list */ bc->next = broker->clients; @@ -1854,6 +1968,34 @@ static int BrokerClient_Process(MqttBroker* broker, BrokerClient* bc) int rc; int activity = 0; +#ifdef ENABLE_MQTT_TLS + /* Complete TLS handshake before processing MQTT packets */ + if (!bc->tls_handshake_done) { + int ret; + bc->client.tls.timeout_ms_read = BROKER_TIMEOUT_MS; + bc->client.tls.timeout_ms_write = BROKER_TIMEOUT_MS; + ret = wolfSSL_accept(bc->client.tls.ssl); + if (ret == WOLFSSL_SUCCESS) { + bc->tls_handshake_done = 1; + PRINTF("broker: TLS handshake done sock=%d %s", + (int)bc->sock, wolfSSL_get_version(bc->client.tls.ssl)); + return 1; /* activity */ + } + else { + int err = wolfSSL_get_error(bc->client.tls.ssl, ret); + if (err == WOLFSSL_ERROR_WANT_READ || + err == WOLFSSL_ERROR_WANT_WRITE) { + return 0; /* handshake in progress */ + } + PRINTF("broker: TLS handshake failed sock=%d err=%d", + (int)bc->sock, err); + BrokerSubs_RemoveClient(broker, bc); + BrokerClient_Remove(broker, bc); + return 0; + } + } +#endif + /* Try non-blocking read (timeout=0) */ #ifdef WOLFMQTT_STATIC_MEMORY rc = MqttPacket_Read(&bc->client, bc->rx_buf, BROKER_RX_BUF_SZ, 0); @@ -2035,7 +2177,20 @@ int MqttBroker_Run(MqttBroker* broker) return rc; } - PRINTF("broker: listening on port %d (no TLS)", broker->port); +#if defined(ENABLE_MQTT_TLS) && !defined(WOLFMQTT_BROKER_CUSTOM_NET) + if (broker->use_tls) { + rc = BrokerTls_Init(broker); + if (rc != MQTT_CODE_SUCCESS) { + PRINTF("broker: TLS init failed rc=%d", rc); + return rc; + } + PRINTF("broker: listening on port %d (TLS)", broker->port); + } + else +#endif + { + PRINTF("broker: listening on port %d (no TLS)", broker->port); + } if (broker->auth_user || broker->auth_pass) { PRINTF("broker: auth enabled user=%s", broker->auth_user ? broker->auth_user : "(null)"); @@ -2092,6 +2247,10 @@ int MqttBroker_Free(MqttBroker* broker) /* Clean up retained messages */ BrokerRetained_FreeAll(broker); +#if defined(ENABLE_MQTT_TLS) && !defined(WOLFMQTT_BROKER_CUSTOM_NET) + BrokerTls_Free(broker); +#endif + /* Close listen socket */ if (broker->listen_sock != BROKER_SOCKET_INVALID) { broker->net.close(broker->net.ctx, broker->listen_sock); @@ -2106,7 +2265,17 @@ int MqttBroker_Free(MqttBroker* broker) /* -------------------------------------------------------------------------- */ static void BrokerUsage(const char* prog) { - PRINTF("usage: %s [-p port] [-u user] [-P pass]", prog); + PRINTF("usage: %s [-p port] [-u user] [-P pass]" +#ifdef ENABLE_MQTT_TLS + " [-t] [-c cert] [-K key] [-A ca]" +#endif + , prog); +#ifdef ENABLE_MQTT_TLS + PRINTF(" -t Enable TLS"); + PRINTF(" -c Server certificate file (PEM)"); + PRINTF(" -K Server private key file (PEM)"); + PRINTF(" -A CA certificate for mutual TLS (PEM)"); +#endif } int wolfmqtt_broker(int argc, char** argv) @@ -2148,6 +2317,23 @@ int wolfmqtt_broker(int argc, char** argv) else if (XSTRCMP(argv[i], "-P") == 0 && i + 1 < argc) { broker.auth_pass = argv[++i]; } +#ifdef ENABLE_MQTT_TLS + else if (XSTRCMP(argv[i], "-t") == 0) { + broker.use_tls = 1; + if (broker.port == MQTT_DEFAULT_PORT) { + broker.port = MQTT_SECURE_PORT; + } + } + else if (XSTRCMP(argv[i], "-c") == 0 && i + 1 < argc) { + broker.tls_cert = argv[++i]; + } + else if (XSTRCMP(argv[i], "-K") == 0 && i + 1 < argc) { + broker.tls_key = argv[++i]; + } + else if (XSTRCMP(argv[i], "-A") == 0 && i + 1 < argc) { + broker.tls_ca = argv[++i]; + } +#endif else if (XSTRCMP(argv[i], "-h") == 0) { BrokerUsage(argv[0]); return 0; diff --git a/wolfmqtt/mqtt_broker.h b/wolfmqtt/mqtt_broker.h index f89619523..187a14a5c 100644 --- a/wolfmqtt/mqtt_broker.h +++ b/wolfmqtt/mqtt_broker.h @@ -161,6 +161,9 @@ typedef struct BrokerClient { MqttNet net; MqttClient client; struct MqttBroker* broker; /* back-pointer to parent broker context */ +#ifdef ENABLE_MQTT_TLS + byte tls_handshake_done; +#endif } BrokerClient; /* -------------------------------------------------------------------------- */ @@ -205,6 +208,13 @@ typedef struct MqttBroker { const char* auth_pass; MqttBrokerNet net; word16 next_packet_id; +#ifdef ENABLE_MQTT_TLS + WOLFSSL_CTX* tls_ctx; + const char* tls_cert; /* Server certificate file path */ + const char* tls_key; /* Server private key file path */ + const char* tls_ca; /* CA cert for mutual auth (optional) */ + byte use_tls; +#endif #ifdef WOLFMQTT_STATIC_MEMORY BrokerClient clients[BROKER_MAX_CLIENTS]; BrokerSub subs[BROKER_MAX_SUBS]; From 98add3f064db697c1742c046e1eefe51f89f3ed6 Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 5 Feb 2026 15:02:37 -0800 Subject: [PATCH 08/13] Testing of Broker with TLS --- .github/workflows/broker-check.yml | 10 +- examples/mqttclient/mqttclient.c | 4 +- examples/pub-sub/mqtt-pub.c | 2 +- scripts/broker.test | 356 ++++++++++++++--------------- src/mqtt_broker.c | 31 ++- wolfmqtt/mqtt_broker.h | 1 + 6 files changed, 207 insertions(+), 197 deletions(-) diff --git a/.github/workflows/broker-check.yml b/.github/workflows/broker-check.yml index 572a6c6dc..7018c42ca 100644 --- a/.github/workflows/broker-check.yml +++ b/.github/workflows/broker-check.yml @@ -18,8 +18,16 @@ jobs: include: - name: "Broker default (dynamic alloc)" cflags: "" + wolfmqtt_opts: "--enable-broker" - name: "Broker static memory" cflags: "-DWOLFMQTT_STATIC_MEMORY" + wolfmqtt_opts: "--enable-broker" + - name: "Broker with TLS" + cflags: "" + wolfmqtt_opts: "--enable-broker --enable-tls" + - name: "Broker with TLS (static memory)" + cflags: "-DWOLFMQTT_STATIC_MEMORY" + wolfmqtt_opts: "--enable-broker --enable-tls" steps: - name: Install dependencies @@ -50,7 +58,7 @@ jobs: run: ./autogen.sh - name: "wolfmqtt configure (${{ matrix.name }})" - run: ./configure --enable-broker CFLAGS="${{ matrix.cflags }}" + run: ./configure ${{ matrix.wolfmqtt_opts }} CFLAGS="${{ matrix.cflags }}" - name: wolfmqtt make run: make diff --git a/examples/mqttclient/mqttclient.c b/examples/mqttclient/mqttclient.c index bcdde2bac..fa8e57de8 100644 --- a/examples/mqttclient/mqttclient.c +++ b/examples/mqttclient/mqttclient.c @@ -453,7 +453,7 @@ int mqttclient_test(MQTTCtx *mqttCtx) /* Publish Topic */ XMEMSET(&mqttCtx->publish, 0, sizeof(MqttPublish)); - mqttCtx->publish.retain = 0; + mqttCtx->publish.retain = mqttCtx->retain; mqttCtx->publish.qos = mqttCtx->qos; mqttCtx->publish.duplicate = 0; mqttCtx->publish.topic_name = mqttCtx->topic_name; @@ -567,7 +567,7 @@ int mqttclient_test(MQTTCtx *mqttCtx) /* Publish Topic */ mqttCtx->stat = WMQ_PUB; XMEMSET(&mqttCtx->publish, 0, sizeof(MqttPublish)); - mqttCtx->publish.retain = 0; + mqttCtx->publish.retain = mqttCtx->retain; mqttCtx->publish.qos = mqttCtx->qos; mqttCtx->publish.duplicate = 0; mqttCtx->publish.topic_name = mqttCtx->topic_name; diff --git a/examples/pub-sub/mqtt-pub.c b/examples/pub-sub/mqtt-pub.c index ecf5ad879..f3ea4f4a9 100644 --- a/examples/pub-sub/mqtt-pub.c +++ b/examples/pub-sub/mqtt-pub.c @@ -330,7 +330,7 @@ int pub_client(MQTTCtx *mqttCtx) /* Publish Topic */ XMEMSET(&mqttCtx->publish, 0, sizeof(MqttPublish)); - mqttCtx->publish.retain = 0; + mqttCtx->publish.retain = mqttCtx->retain; mqttCtx->publish.qos = mqttCtx->qos; mqttCtx->publish.duplicate = 0; mqttCtx->publish.topic_name = mqttCtx->topic_name; diff --git a/scripts/broker.test b/scripts/broker.test index 6f6c16ab2..0d8ef2e8e 100755 --- a/scripts/broker.test +++ b/scripts/broker.test @@ -1,11 +1,10 @@ #!/bin/bash -# wolfMQTT Broker test -# Tests the wolfMQTT broker with pub/sub, retained messages, LWT, QoS 1, -# client ID takeover, and multi-client pairs. +# wolfMQTT Broker Test Suite +# Uses only wolfMQTT tools (mqttclient, mqtt-pub, mqtt-sub) - no external dependencies -name="MQTT Broker" broker_bin="src/mqtt_broker" +client_bin="examples/mqttclient/mqttclient" pub_bin="examples/pub-sub/mqtt-pub" sub_bin="examples/pub-sub/mqtt-sub" no_pid=-1 @@ -14,10 +13,8 @@ broker_pid=$no_pid do_cleanup() { if [ $broker_pid != $no_pid ]; then kill -9 $broker_pid 2>/dev/null - echo "Killed broker PID $broker_pid" broker_pid=$no_pid fi - # Kill any lingering test processes for pid in "${TEST_PIDS[@]:-}"; do kill "$pid" 2>/dev/null || true done @@ -34,16 +31,12 @@ do_cleanup() { fi } -# Check for broker binary -[ ! -x ./$broker_bin ] && echo -e "\n\n$broker_bin doesn't exist" && exit 1 +# Check for required binaries +[ ! -x ./$broker_bin ] && echo "$broker_bin not found" && exit 1 +[ ! -x ./$client_bin ] && echo "$client_bin not found" && exit 1 +[ ! -x ./$pub_bin ] && echo "$pub_bin not found" && exit 1 +[ ! -x ./$sub_bin ] && echo "$sub_bin not found" && exit 1 -# Check for pub/sub binaries (needed for multi-client test) -has_pubsub=no -if [ -x ./$pub_bin ] && [ -x ./$sub_bin ]; then - has_pubsub=yes -fi - -# Generate a random port generate_port() { if [[ "$OSTYPE" == "linux"* ]]; then port=$(($(od -An -N2 /dev/urandom) % (65535-49152) + 49152)) @@ -52,14 +45,25 @@ generate_port() { else port=11883 fi - echo "Using port $port" } check_broker() { - timeout 10 sh -c 'until nc -v -z $0 $1; do sleep 1; done' localhost $port + local check_port=${1:-$port} + timeout 10 sh -c 'until nc -z $0 $1 2>/dev/null; do sleep 1; done' localhost $check_port } -generate_port +# Start broker helper: kills existing broker, generates port, starts new one +start_broker() { + if [ $broker_pid != $no_pid ]; then + kill -9 $broker_pid 2>/dev/null + wait $broker_pid 2>/dev/null || true + broker_pid=$no_pid + fi + generate_port + ./$broker_bin "$@" -p $port & + broker_pid=$! + check_broker +} TEST_PIDS=() TMP_DIR="$(mktemp -d)" @@ -67,219 +71,197 @@ FAIL=0 echo "=== wolfMQTT Broker Test Suite ===" -# Start broker -./$broker_bin -p $port & -broker_pid=$! -echo "Broker PID is $broker_pid" -check_broker +# Start plain broker for tests 1-4 +start_broker -# --- Test 1: Basic pub/sub with mosquitto_pub/mosquitto_sub --- +# --- Test 1: Basic pub/sub --- echo "" echo "--- Test 1: Basic pub/sub ---" -if command -v mosquitto_sub >/dev/null 2>&1 && command -v mosquitto_pub >/dev/null 2>&1; then - mosquitto_sub -h 127.0.0.1 -p $port -t "test/hello" -C 1 -W 5 >"${TMP_DIR}/t1_sub.log" 2>&1 & - T1_PID=$! - sleep 1 - mosquitto_pub -h 127.0.0.1 -p $port -t "test/hello" -m "world" - wait $T1_PID 2>/dev/null - if grep -q "world" "${TMP_DIR}/t1_sub.log" 2>/dev/null; then - echo "PASS: Basic pub/sub" - else - echo "FAIL: Basic pub/sub" - FAIL=1 - fi +./$client_bin -T -h 127.0.0.1 -p $port -n "test/basic" -C 5000 \ + >"${TMP_DIR}/t1.log" 2>&1 +if [ $? -eq 0 ]; then + echo "PASS: Basic pub/sub" else - echo "SKIP: mosquitto_pub/mosquitto_sub not found" + echo "FAIL: Basic pub/sub" + FAIL=1 fi -# --- Test 2: Retained messages --- +# --- Test 2: QoS 1 pub/sub --- echo "" -echo "--- Test 2: Retained messages ---" -if command -v mosquitto_sub >/dev/null 2>&1 && command -v mosquitto_pub >/dev/null 2>&1; then - mosquitto_pub -h 127.0.0.1 -p $port -t "test/retained" -m "stored_msg" -r - sleep 1 - mosquitto_sub -h 127.0.0.1 -p $port -t "test/retained" -C 1 -W 5 >"${TMP_DIR}/t2_sub.log" 2>&1 - if grep -q "stored_msg" "${TMP_DIR}/t2_sub.log" 2>/dev/null; then - echo "PASS: Retained message delivered on subscribe" - else - echo "FAIL: Retained message not delivered" - FAIL=1 - fi +echo "--- Test 2: QoS 1 pub/sub ---" +./$client_bin -T -h 127.0.0.1 -p $port -n "test/qos1" -q 1 -C 5000 \ + >"${TMP_DIR}/t2.log" 2>&1 +if [ $? -eq 0 ]; then + echo "PASS: QoS 1 pub/sub" else - echo "SKIP: mosquitto_pub/mosquitto_sub not found" + echo "FAIL: QoS 1 pub/sub" + FAIL=1 fi -# --- Test 3: QoS 1 forwarding --- +# --- Test 3: Retained message --- echo "" -echo "--- Test 3: QoS 1 forwarding ---" -if command -v mosquitto_sub >/dev/null 2>&1 && command -v mosquitto_pub >/dev/null 2>&1; then - mosquitto_sub -h 127.0.0.1 -p $port -t "test/qos1" -q 1 -C 1 -W 5 >"${TMP_DIR}/t3_sub.log" 2>&1 & - T3_PID=$! - sleep 1 - mosquitto_pub -h 127.0.0.1 -p $port -t "test/qos1" -m "qos1_msg" -q 1 - wait $T3_PID 2>/dev/null - if grep -q "qos1_msg" "${TMP_DIR}/t3_sub.log" 2>/dev/null; then - echo "PASS: QoS 1 forwarding" - else - echo "FAIL: QoS 1 forwarding" - FAIL=1 - fi +echo "--- Test 3: Retained message ---" +# Publish a retained message (-T disables stdin capture for background use) +./$pub_bin -T -h 127.0.0.1 -p $port -n "test/retain" -m "retained_msg" -r \ + >"${TMP_DIR}/t3_pub.log" 2>&1 +sleep 1 +# New subscriber should receive the retained message +timeout 5 ./$sub_bin -T -h 127.0.0.1 -p $port -n "test/retain" \ + >"${TMP_DIR}/t3_sub.log" 2>&1 & +T3_PID=$! +TEST_PIDS+=($T3_PID) +sleep 2 +kill $T3_PID 2>/dev/null +wait $T3_PID 2>/dev/null || true +TEST_PIDS=() +if grep -q "retained_msg" "${TMP_DIR}/t3_sub.log" 2>/dev/null; then + echo "PASS: Retained message" else - echo "SKIP: mosquitto_pub/mosquitto_sub not found" + echo "FAIL: Retained message" + FAIL=1 fi # --- Test 4: Last Will and Testament --- echo "" echo "--- Test 4: Last Will and Testament ---" -if command -v mosquitto_sub >/dev/null 2>&1; then - mosquitto_sub -h 127.0.0.1 -p $port -t "test/will" -C 1 -W 10 >"${TMP_DIR}/t4_sub.log" 2>&1 & - T4_SUB_PID=$! - sleep 1 - # Connect a client with a will, then kill it abruptly - mosquitto_sub -h 127.0.0.1 -p $port -t "test/nothing" \ - --will-topic "test/will" --will-payload "client_died" -W 3 >"${TMP_DIR}/t4_will.log" 2>&1 & - T4_WILL_PID=$! - sleep 1 - kill -9 $T4_WILL_PID 2>/dev/null - wait $T4_WILL_PID 2>/dev/null || true - wait $T4_SUB_PID 2>/dev/null || true - if grep -q "client_died" "${TMP_DIR}/t4_sub.log" 2>/dev/null; then - echo "PASS: LWT delivered on abnormal disconnect" - else - echo "FAIL: LWT not delivered" - FAIL=1 - fi +# LWT topic is hardcoded as "wolfMQTT/example/lwttopic", payload is the client ID +# Use mqtt-sub as the LWT watcher +timeout 10 ./$sub_bin -T -h 127.0.0.1 -p $port -n "wolfMQTT/example/lwttopic" \ + >"${TMP_DIR}/t4_sub.log" 2>&1 & +T4_SUB_PID=$! +TEST_PIDS+=($T4_SUB_PID) +sleep 1 +# Use mqtt-sub with LWT enabled as the client to be killed +# mqtt-sub stays connected waiting for messages (unlike mqttclient which auto-exits) +./$sub_bin -T -h 127.0.0.1 -p $port -n "test/lwt_trigger" -i "lwt_client" -l \ + >"${TMP_DIR}/t4_client.log" 2>&1 & +T4_CLIENT_PID=$! +TEST_PIDS+=($T4_CLIENT_PID) +sleep 2 +kill -9 $T4_CLIENT_PID 2>/dev/null +wait $T4_CLIENT_PID 2>/dev/null || true +sleep 2 +kill $T4_SUB_PID 2>/dev/null +wait $T4_SUB_PID 2>/dev/null || true +TEST_PIDS=() +if grep -q "lwt_client" "${TMP_DIR}/t4_sub.log" 2>/dev/null; then + echo "PASS: LWT delivered on abnormal disconnect" else - echo "SKIP: mosquitto_sub not found" + echo "FAIL: LWT not delivered" + FAIL=1 fi -# --- Test 5: Client ID takeover --- +# --- Test 5: Username/password auth --- echo "" -echo "--- Test 5: Client ID takeover ---" -if command -v mosquitto_sub >/dev/null 2>&1 && command -v mosquitto_pub >/dev/null 2>&1; then - mosquitto_sub -h 127.0.0.1 -p $port -t "test/takeover" -i "takeoverClient" -W 5 >"${TMP_DIR}/t5_old.log" 2>&1 & - T5_OLD_PID=$! - sleep 1 - # Connect second client with same ID - should disconnect old - mosquitto_sub -h 127.0.0.1 -p $port -t "test/takeover" -i "takeoverClient" -C 1 -W 5 >"${TMP_DIR}/t5_new.log" 2>&1 & - T5_NEW_PID=$! - sleep 1 - # Old client should have been disconnected - wait $T5_OLD_PID 2>/dev/null - T5_OLD_EXIT=$? - # Send message to new client - mosquitto_pub -h 127.0.0.1 -p $port -t "test/takeover" -m "after_takeover" - wait $T5_NEW_PID 2>/dev/null || true - if [ $T5_OLD_EXIT -ne 0 ]; then - echo "PASS: Client ID takeover (old client disconnected)" - else - echo "FAIL: Old client was not disconnected" - FAIL=1 - fi -else - echo "SKIP: mosquitto_pub/mosquitto_sub not found" -fi +echo "--- Test 5: Username/password auth ---" +start_broker -u testuser -P testpass -# --- Test 6: Multi-client paired pub/sub --- -echo "" -echo "--- Test 6: Multi-client paired pub/sub ---" -if [ "$has_pubsub" = "yes" ]; then - NUM_PAIRS=2 - NUM_CLIENTS=$((NUM_PAIRS * 2)) - SUB_LOGS=() +# Without credentials - should be rejected (CONNACK return code != 0) +./$client_bin -T -h 127.0.0.1 -p $port -n "test/auth" -C 5000 \ + >"${TMP_DIR}/t5_noauth.log" 2>&1 +# Check that CONNACK shows rejection (return code != 0) +T5_NOAUTH_OK=no +if ! grep -q "Connect Ack: Return Code 0" "${TMP_DIR}/t5_noauth.log" 2>/dev/null; then + T5_NOAUTH_OK=yes +fi - start_sub() { - local client_id="$1" - local topic="$2" - local log="$3" - stdbuf -oL ./${sub_bin} -T -h localhost -p ${port} -i "${client_id}" \ - -n "${topic}" -q 0 >"${log}" 2>&1 & - TEST_PIDS+=("$!") - SUB_LOGS+=("${log}") - } +# With credentials - should succeed (CONNACK return code 0 and Subscribe succeeds) +./$client_bin -T -h 127.0.0.1 -p $port -n "test/auth" -u testuser -w testpass -C 5000 \ + >"${TMP_DIR}/t5_auth.log" 2>&1 +T5_AUTH_OK=no +if grep -q "Connect Ack: Return Code 0" "${TMP_DIR}/t5_auth.log" 2>/dev/null; then + T5_AUTH_OK=yes +fi - run_pub() { - local client_id="$1" - local topic="$2" - local msg="$3" - ./${pub_bin} -T -h localhost -p ${port} -i "${client_id}" -n "${topic}" \ - -m "${msg}" -q 0 >/dev/null 2>&1 - } +if [ "$T5_NOAUTH_OK" = "yes" ] && [ "$T5_AUTH_OK" = "yes" ]; then + echo "PASS: Auth rejection and acceptance" +else + echo "FAIL: Auth test (noauth_rejected=$T5_NOAUTH_OK, auth_accepted=$T5_AUTH_OK)" + FAIL=1 +fi - # Start subscribers - for p in $(seq 0 $((NUM_PAIRS - 1))); do - start_sub "sub_${p}_a" "pair/${p}/a" "${TMP_DIR}/sub_${p}_a.log" - start_sub "sub_${p}_b" "pair/${p}/b" "${TMP_DIR}/sub_${p}_b.log" - done +# --- TLS Tests --- +has_tls=no +if ./$broker_bin -h 2>&1 | grep -q "\-t"; then + has_tls=yes +fi - # Wait for subscribers to be ready - echo "Waiting for ${NUM_CLIENTS} subscribers to connect..." - ELAPSED=0 - while [ $ELAPSED -lt 15 ]; do - READY=0 - for log in "${SUB_LOGS[@]}"; do - if grep -q "MQTT Waiting for message" "${log}" 2>/dev/null; then - READY=$((READY + 1)) - fi - done - if [ $READY -eq ${#SUB_LOGS[@]} ]; then - echo "All subscribers ready after ${ELAPSED}s" - break - fi - sleep 1 - ELAPSED=$((ELAPSED + 1)) - done +if [ "$has_tls" = "yes" ]; then + # --- Test 6: TLS pub/sub --- + echo "" + echo "--- Test 6: TLS pub/sub ---" + start_broker -t \ + -c scripts/broker_test/server-cert.pem \ + -K scripts/broker_test/server-key.pem - # Publish messages - for p in $(seq 0 $((NUM_PAIRS - 1))); do - run_pub "pub_${p}_a" "pair/${p}/b" "hello_from_${p}_a" - run_pub "pub_${p}_b" "pair/${p}/a" "hello_from_${p}_b" - done + ./$client_bin -T -h 127.0.0.1 -p $port -n "test/tls" -t \ + -A scripts/broker_test/ca-cert.pem -C 5000 \ + >"${TMP_DIR}/t6.log" 2>&1 + if [ $? -eq 0 ]; then + echo "PASS: TLS pub/sub" + else + echo "FAIL: TLS pub/sub" + FAIL=1 + fi - # Wait for delivery - sleep 3 + # --- Test 7: Mutual TLS (RSA) --- + echo "" + echo "--- Test 7: Mutual TLS (RSA) ---" + if [ -f certs/client-cert.pem ] && [ -f certs/client-key.pem ]; then + start_broker -t \ + -c scripts/broker_test/server-cert.pem \ + -K scripts/broker_test/server-key.pem \ + -A certs/client-cert.pem - # Check results - T6_PASS=0 - T6_FAIL=0 - for p in $(seq 0 $((NUM_PAIRS - 1))); do - if grep -q "hello_from_${p}_b" "${TMP_DIR}/sub_${p}_a.log" 2>/dev/null; then - T6_PASS=$((T6_PASS + 1)) - else - T6_FAIL=$((T6_FAIL + 1)) - fi - if grep -q "hello_from_${p}_a" "${TMP_DIR}/sub_${p}_b.log" 2>/dev/null; then - T6_PASS=$((T6_PASS + 1)) + ./$client_bin -T -h 127.0.0.1 -p $port -n "test/mtls_rsa" -t \ + -A scripts/broker_test/ca-cert.pem \ + -c certs/client-cert.pem -K certs/client-key.pem -C 5000 \ + >"${TMP_DIR}/t7.log" 2>&1 + if [ $? -eq 0 ]; then + echo "PASS: Mutual TLS (RSA)" else - T6_FAIL=$((T6_FAIL + 1)) + echo "FAIL: Mutual TLS (RSA)" + FAIL=1 fi - done + else + echo "SKIP: RSA client certs not found" + fi - # Clean up subscriber processes - for pid in "${TEST_PIDS[@]:-}"; do - kill "$pid" 2>/dev/null || true - wait "$pid" 2>/dev/null || true - done - TEST_PIDS=() + # --- Test 8: Mutual TLS (ECC) --- + echo "" + echo "--- Test 8: Mutual TLS (ECC) ---" + if [ -f certs/client-ecc-cert.pem ] && [ -f certs/ecc-client-key.pem ]; then + start_broker -t \ + -c scripts/broker_test/server-cert.pem \ + -K scripts/broker_test/server-key.pem \ + -A certs/client-ecc-cert.pem - if [ $T6_FAIL -eq 0 ]; then - echo "PASS: Multi-client pub/sub (${T6_PASS}/${NUM_CLIENTS} messages delivered)" + ./$client_bin -T -h 127.0.0.1 -p $port -n "test/mtls_ecc" -t \ + -A scripts/broker_test/ca-cert.pem \ + -c certs/client-ecc-cert.pem -K certs/ecc-client-key.pem -C 5000 \ + >"${TMP_DIR}/t8.log" 2>&1 + if [ $? -eq 0 ]; then + echo "PASS: Mutual TLS (ECC)" + else + echo "FAIL: Mutual TLS (ECC)" + FAIL=1 + fi else - echo "FAIL: Multi-client pub/sub (${T6_FAIL} messages missing)" - FAIL=1 + echo "SKIP: ECC client certs not found" fi else - echo "SKIP: mqtt-pub/mqtt-sub not built" + echo "" + echo "SKIP: TLS tests (broker not built with TLS support)" fi # --- Summary --- echo "" if [ $FAIL -ne 0 ]; then KEEP_LOGS=1 - echo "$name Tests FAILED" + echo "MQTT Broker Tests FAILED" do_cleanup "-1" fi do_cleanup "0" -echo "$name Tests Passed" +echo "MQTT Broker Tests Passed" exit 0 diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index c0a5bc33d..b5ccf4f8d 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -272,7 +272,17 @@ static int BrokerTls_Init(MqttBroker* broker) return MQTT_CODE_ERROR_BAD_ARG; } - ctx = wolfSSL_CTX_new(wolfSSLv23_server_method()); + /* Select TLS method based on version preference */ + if (broker->tls_version == 12) { + ctx = wolfSSL_CTX_new(wolfTLSv1_2_server_method()); + } + else if (broker->tls_version == 13) { + ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method()); + } + else { + /* Default: auto-negotiate (supports TLS 1.2 and 1.3) */ + ctx = wolfSSL_CTX_new(wolfSSLv23_server_method()); + } if (ctx == NULL) { PRINTF("broker: wolfSSL_CTX_new failed"); wolfSSL_Cleanup(); @@ -404,7 +414,10 @@ static void BrokerClient_Free(BrokerClient* bc) (void)BrokerNetDisconnect(bc); #ifdef ENABLE_MQTT_TLS if (bc->client.tls.ssl) { - wolfSSL_shutdown(bc->client.tls.ssl); + /* Only send close_notify if handshake completed successfully */ + if (bc->tls_handshake_done) { + wolfSSL_shutdown(bc->client.tls.ssl); + } wolfSSL_free(bc->client.tls.ssl); bc->client.tls.ssl = NULL; } @@ -1568,17 +1581,19 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, if (broker->auth_user || broker->auth_pass) { int auth_ok = 1; - if (broker->auth_user && (bc->username[0] == '\0' || + if (broker->auth_user && (bc->username == NULL || + bc->username[0] == '\0' || XSTRCMP(broker->auth_user, bc->username) != 0)) { auth_ok = 0; } - if (broker->auth_pass && (bc->password[0] == '\0' || + if (broker->auth_pass && (bc->password == NULL || + bc->password[0] == '\0' || XSTRCMP(broker->auth_pass, bc->password) != 0)) { auth_ok = 0; } if (!auth_ok) { PRINTF("broker: auth failed sock=%d user=%s", (int)bc->sock, - bc->username[0] ? bc->username : "(null)"); + (bc->username && bc->username[0]) ? bc->username : "(null)"); ack.return_code = MQTT_CONNECT_ACK_CODE_REFUSED_BAD_USER_PWD; } } @@ -2267,11 +2282,12 @@ static void BrokerUsage(const char* prog) { PRINTF("usage: %s [-p port] [-u user] [-P pass]" #ifdef ENABLE_MQTT_TLS - " [-t] [-c cert] [-K key] [-A ca]" + " [-t] [-V ver] [-c cert] [-K key] [-A ca]" #endif , prog); #ifdef ENABLE_MQTT_TLS PRINTF(" -t Enable TLS"); + PRINTF(" -V TLS version: 12=TLS 1.2, 13=TLS 1.3 (default: auto)"); PRINTF(" -c Server certificate file (PEM)"); PRINTF(" -K Server private key file (PEM)"); PRINTF(" -A CA certificate for mutual TLS (PEM)"); @@ -2324,6 +2340,9 @@ int wolfmqtt_broker(int argc, char** argv) broker.port = MQTT_SECURE_PORT; } } + else if (XSTRCMP(argv[i], "-V") == 0 && i + 1 < argc) { + broker.tls_version = (byte)XATOI(argv[++i]); + } else if (XSTRCMP(argv[i], "-c") == 0 && i + 1 < argc) { broker.tls_cert = argv[++i]; } diff --git a/wolfmqtt/mqtt_broker.h b/wolfmqtt/mqtt_broker.h index 187a14a5c..7a470001d 100644 --- a/wolfmqtt/mqtt_broker.h +++ b/wolfmqtt/mqtt_broker.h @@ -214,6 +214,7 @@ typedef struct MqttBroker { const char* tls_key; /* Server private key file path */ const char* tls_ca; /* CA cert for mutual auth (optional) */ byte use_tls; + byte tls_version; /* 0=auto (v23), 12=TLS 1.2, 13=TLS 1.3 */ #endif #ifdef WOLFMQTT_STATIC_MEMORY BrokerClient clients[BROKER_MAX_CLIENTS]; From 0ee53c6ca04053262a59d087ed164b01857236bd Mon Sep 17 00:00:00 2001 From: David Garske Date: Fri, 6 Feb 2026 11:05:48 -0800 Subject: [PATCH 09/13] Broker QoS 2 support, MQTT v5 reason codes, keep-alive enforcement and exit code fixes --- examples/mqttclient/mqttclient.c | 7 +- examples/pub-sub/mqtt-pub.c | 5 +- examples/pub-sub/mqtt-sub.c | 7 +- scripts/broker.test | 106 ++++++++++++------- src/mqtt_broker.c | 171 +++++++++++++++++++++++++++---- 5 files changed, 236 insertions(+), 60 deletions(-) diff --git a/examples/mqttclient/mqttclient.c b/examples/mqttclient/mqttclient.c index fa8e57de8..1fea238b7 100644 --- a/examples/mqttclient/mqttclient.c +++ b/examples/mqttclient/mqttclient.c @@ -625,6 +625,11 @@ int mqttclient_test(MQTTCtx *mqttCtx) mqttCtx->return_code = rc; disconn: + /* Preserve error code through disconnect */ + if (rc != MQTT_CODE_SUCCESS) { + mqttCtx->return_code = rc; + } + /* Disconnect */ XMEMSET(&mqttCtx->disconnect, 0, sizeof(mqttCtx->disconnect)); #ifdef WOLFMQTT_V5 @@ -668,7 +673,7 @@ int mqttclient_test(MQTTCtx *mqttCtx) MqttClient_DeInit(&mqttCtx->client); - return rc; + return mqttCtx->return_code; } diff --git a/examples/pub-sub/mqtt-pub.c b/examples/pub-sub/mqtt-pub.c index f3ea4f4a9..d10c72983 100644 --- a/examples/pub-sub/mqtt-pub.c +++ b/examples/pub-sub/mqtt-pub.c @@ -409,6 +409,9 @@ int pub_client(MQTTCtx *mqttCtx) disconn: + /* Preserve error code through disconnect */ + mqttCtx->return_code = rc; + /* Disconnect */ XMEMSET(&mqttCtx->disconnect, 0, sizeof(mqttCtx->disconnect)); #ifdef WOLFMQTT_V5 @@ -454,7 +457,7 @@ int pub_client(MQTTCtx *mqttCtx) MqttClient_DeInit(&mqttCtx->client); - return rc; + return mqttCtx->return_code; } diff --git a/examples/pub-sub/mqtt-sub.c b/examples/pub-sub/mqtt-sub.c index 158f28105..9c67717e2 100644 --- a/examples/pub-sub/mqtt-sub.c +++ b/examples/pub-sub/mqtt-sub.c @@ -536,6 +536,11 @@ int sub_client(MQTTCtx *mqttCtx) mqttCtx->return_code = rc; disconn: + /* Preserve error code through disconnect */ + if (rc != MQTT_CODE_SUCCESS) { + mqttCtx->return_code = rc; + } + /* Disconnect */ XMEMSET(&mqttCtx->disconnect, 0, sizeof(mqttCtx->disconnect)); #ifdef WOLFMQTT_V5 @@ -581,7 +586,7 @@ int sub_client(MQTTCtx *mqttCtx) MqttClient_DeInit(&mqttCtx->client); - return rc; + return mqttCtx->return_code; } diff --git a/scripts/broker.test b/scripts/broker.test index 0d8ef2e8e..3e192cd4c 100755 --- a/scripts/broker.test +++ b/scripts/broker.test @@ -71,7 +71,7 @@ FAIL=0 echo "=== wolfMQTT Broker Test Suite ===" -# Start plain broker for tests 1-4 +# Start plain broker for tests 1-6 start_broker # --- Test 1: Basic pub/sub --- @@ -98,32 +98,44 @@ else FAIL=1 fi -# --- Test 3: Retained message --- +# --- Test 3: QoS 2 pub/sub --- echo "" -echo "--- Test 3: Retained message ---" +echo "--- Test 3: QoS 2 pub/sub ---" +./$client_bin -T -h 127.0.0.1 -p $port -n "test/qos2" -q 2 -C 5000 \ + >"${TMP_DIR}/t3.log" 2>&1 +if [ $? -eq 0 ]; then + echo "PASS: QoS 2 pub/sub" +else + echo "FAIL: QoS 2 pub/sub" + FAIL=1 +fi + +# --- Test 4: Retained message --- +echo "" +echo "--- Test 4: Retained message ---" # Publish a retained message (-T disables stdin capture for background use) ./$pub_bin -T -h 127.0.0.1 -p $port -n "test/retain" -m "retained_msg" -r \ - >"${TMP_DIR}/t3_pub.log" 2>&1 + >"${TMP_DIR}/t4_pub.log" 2>&1 sleep 1 # New subscriber should receive the retained message timeout 5 ./$sub_bin -T -h 127.0.0.1 -p $port -n "test/retain" \ - >"${TMP_DIR}/t3_sub.log" 2>&1 & -T3_PID=$! -TEST_PIDS+=($T3_PID) + >"${TMP_DIR}/t4_sub.log" 2>&1 & +T4_PID=$! +TEST_PIDS+=($T4_PID) sleep 2 -kill $T3_PID 2>/dev/null -wait $T3_PID 2>/dev/null || true +kill $T4_PID 2>/dev/null +wait $T4_PID 2>/dev/null || true TEST_PIDS=() -if grep -q "retained_msg" "${TMP_DIR}/t3_sub.log" 2>/dev/null; then +if grep -q "retained_msg" "${TMP_DIR}/t4_sub.log" 2>/dev/null; then echo "PASS: Retained message" else echo "FAIL: Retained message" FAIL=1 fi -# --- Test 4: Last Will and Testament --- +# --- Test 5: Last Will and Testament --- echo "" -echo "--- Test 4: Last Will and Testament ---" +echo "--- Test 5: Last Will and Testament ---" # LWT topic is hardcoded as "wolfMQTT/example/lwttopic", payload is the client ID # Use mqtt-sub as the LWT watcher timeout 10 ./$sub_bin -T -h 127.0.0.1 -p $port -n "wolfMQTT/example/lwttopic" \ @@ -151,32 +163,56 @@ else FAIL=1 fi -# --- Test 5: Username/password auth --- +# --- Test 6: Keep-alive timeout enforcement --- echo "" -echo "--- Test 5: Username/password auth ---" +echo "--- Test 6: Keep-alive enforcement ---" +# Subscriber watches the LWT topic +timeout 15 ./$sub_bin -T -h 127.0.0.1 -p $port -n "wolfMQTT/example/lwttopic" \ + >"${TMP_DIR}/t5_sub.log" 2>&1 & +T5_SUB_PID=$! +TEST_PIDS+=($T5_SUB_PID) +sleep 1 +# Client connects with short keepalive (2s) and LWT enabled, then gets frozen +./$sub_bin -T -h 127.0.0.1 -p $port -n "test/ka_trigger" -i "ka_client" -l -k 2 \ + >"${TMP_DIR}/t5_client.log" 2>&1 & +T5_CLIENT_PID=$! +TEST_PIDS+=($T5_CLIENT_PID) +sleep 2 +# Freeze the client so it stops sending PINGREQs +kill -STOP $T5_CLIENT_PID 2>/dev/null +# Wait for broker to detect keepalive timeout (1.5x keep_alive = 3s + margin) +sleep 5 +kill -9 $T5_CLIENT_PID 2>/dev/null +wait $T5_CLIENT_PID 2>/dev/null || true +kill $T5_SUB_PID 2>/dev/null +wait $T5_SUB_PID 2>/dev/null || true +TEST_PIDS=() +if grep -q "ka_client" "${TMP_DIR}/t5_sub.log" 2>/dev/null; then + echo "PASS: Keep-alive enforcement (LWT delivered on timeout)" +else + echo "FAIL: Keep-alive enforcement" + FAIL=1 +fi + +# --- Test 7: Username/password auth --- +echo "" +echo "--- Test 7: Username/password auth ---" start_broker -u testuser -P testpass -# Without credentials - should be rejected (CONNACK return code != 0) +# Without credentials - should be rejected (non-zero exit code) ./$client_bin -T -h 127.0.0.1 -p $port -n "test/auth" -C 5000 \ - >"${TMP_DIR}/t5_noauth.log" 2>&1 -# Check that CONNACK shows rejection (return code != 0) -T5_NOAUTH_OK=no -if ! grep -q "Connect Ack: Return Code 0" "${TMP_DIR}/t5_noauth.log" 2>/dev/null; then - T5_NOAUTH_OK=yes -fi + >"${TMP_DIR}/t6_noauth.log" 2>&1 +T6_NOAUTH_RC=$? -# With credentials - should succeed (CONNACK return code 0 and Subscribe succeeds) +# With credentials - should succeed (exit code 0) ./$client_bin -T -h 127.0.0.1 -p $port -n "test/auth" -u testuser -w testpass -C 5000 \ - >"${TMP_DIR}/t5_auth.log" 2>&1 -T5_AUTH_OK=no -if grep -q "Connect Ack: Return Code 0" "${TMP_DIR}/t5_auth.log" 2>/dev/null; then - T5_AUTH_OK=yes -fi + >"${TMP_DIR}/t6_auth.log" 2>&1 +T6_AUTH_RC=$? -if [ "$T5_NOAUTH_OK" = "yes" ] && [ "$T5_AUTH_OK" = "yes" ]; then +if [ $T6_NOAUTH_RC -ne 0 ] && [ $T6_AUTH_RC -eq 0 ]; then echo "PASS: Auth rejection and acceptance" else - echo "FAIL: Auth test (noauth_rejected=$T5_NOAUTH_OK, auth_accepted=$T5_AUTH_OK)" + echo "FAIL: Auth test (noauth_rc=$T6_NOAUTH_RC, auth_rc=$T6_AUTH_RC)" FAIL=1 fi @@ -187,9 +223,9 @@ if ./$broker_bin -h 2>&1 | grep -q "\-t"; then fi if [ "$has_tls" = "yes" ]; then - # --- Test 6: TLS pub/sub --- + # --- Test 8: TLS pub/sub --- echo "" - echo "--- Test 6: TLS pub/sub ---" + echo "--- Test 8: TLS pub/sub ---" start_broker -t \ -c scripts/broker_test/server-cert.pem \ -K scripts/broker_test/server-key.pem @@ -204,9 +240,9 @@ if [ "$has_tls" = "yes" ]; then FAIL=1 fi - # --- Test 7: Mutual TLS (RSA) --- + # --- Test 9: Mutual TLS (RSA) --- echo "" - echo "--- Test 7: Mutual TLS (RSA) ---" + echo "--- Test 9: Mutual TLS (RSA) ---" if [ -f certs/client-cert.pem ] && [ -f certs/client-key.pem ]; then start_broker -t \ -c scripts/broker_test/server-cert.pem \ @@ -227,9 +263,9 @@ if [ "$has_tls" = "yes" ]; then echo "SKIP: RSA client certs not found" fi - # --- Test 8: Mutual TLS (ECC) --- + # --- Test 10: Mutual TLS (ECC) --- echo "" - echo "--- Test 8: Mutual TLS (ECC) ---" + echo "--- Test 10: Mutual TLS (ECC) ---" if [ -f certs/client-ecc-cert.pem ] && [ -f certs/ecc-client-key.pem ]; then start_broker -t \ -c scripts/broker_test/server-cert.pem \ diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index b5ccf4f8d..8b78dde8e 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -82,6 +82,23 @@ #define BROKER_LOG_PKT 1 #endif +/* Constant-time string comparison to prevent timing attacks on auth. + * Compares all bytes regardless of where differences occur. + * Returns 0 if equal, non-zero if different. */ +static int BrokerStrCompare(const char* a, const char* b) +{ + int result = 0; + int len_a = (int)XSTRLEN(a); + int len_b = (int)XSTRLEN(b); + int len = (len_a < len_b) ? len_a : len_b; + int i; + for (i = 0; i < len; i++) { + result |= (a[i] ^ b[i]); + } + result |= (len_a ^ len_b); + return result; +} + /* -------------------------------------------------------------------------- */ /* Default POSIX network backend */ /* -------------------------------------------------------------------------- */ @@ -1199,9 +1216,6 @@ static void BrokerClient_PublishWill(MqttBroker* broker, BrokerClient* bc) out_pub.topic_name = bc->will_topic; eff_qos = (bc->will_qos < sub->qos) ? bc->will_qos : sub->qos; - if (eff_qos > MQTT_QOS_1) { - eff_qos = MQTT_QOS_1; - } out_pub.qos = eff_qos; out_pub.retain = 0; out_pub.duplicate = 0; @@ -1238,9 +1252,6 @@ static void BrokerClient_PublishWill(MqttBroker* broker, BrokerClient* bc) out_pub.topic_name = bc->will_topic; eff_qos = (bc->will_qos < sub->qos) ? bc->will_qos : sub->qos; - if (eff_qos > MQTT_QOS_1) { - eff_qos = MQTT_QOS_1; - } out_pub.qos = eff_qos; out_pub.retain = 0; out_pub.duplicate = 0; @@ -1367,6 +1378,37 @@ static int BrokerSend_SubAck(BrokerClient* bc, word16 packet_id, return MqttPacket_Write(&bc->client, bc->tx_buf, pos); } +#ifdef WOLFMQTT_V5 +static int BrokerSend_Disconnect(BrokerClient* bc, byte reason_code) +{ + int rc; + MqttDisconnect disc; + + if (bc == NULL || + bc->protocol_level < MQTT_CONNECT_PROTOCOL_LEVEL_5) { + return 0; + } + + XMEMSET(&disc, 0, sizeof(disc)); + disc.protocol_level = bc->protocol_level; + disc.reason_code = reason_code; + + rc = MqttEncode_Disconnect(bc->tx_buf, + #ifdef WOLFMQTT_STATIC_MEMORY + BROKER_TX_BUF_SZ, + #else + bc->tx_buf_len, + #endif + &disc); + if (rc > 0) { + PRINTF("broker: DISCONNECT send sock=%d reason=0x%02x", + (int)bc->sock, reason_code); + rc = MqttPacket_Write(&bc->client, bc->tx_buf, rc); + } + return rc; +} +#endif + /* -------------------------------------------------------------------------- */ /* Packet handlers */ /* -------------------------------------------------------------------------- */ @@ -1446,6 +1488,8 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, BrokerClient_PublishWill(broker, old); } else { + BrokerSend_Disconnect(old, + MQTT_REASON_SESSION_TAKEN_OVER); BrokerClient_ClearWill(old); } #else @@ -1583,18 +1627,27 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, int auth_ok = 1; if (broker->auth_user && (bc->username == NULL || bc->username[0] == '\0' || - XSTRCMP(broker->auth_user, bc->username) != 0)) { + BrokerStrCompare(broker->auth_user, bc->username) != 0)) { auth_ok = 0; } if (broker->auth_pass && (bc->password == NULL || bc->password[0] == '\0' || - XSTRCMP(broker->auth_pass, bc->password) != 0)) { + BrokerStrCompare(broker->auth_pass, bc->password) != 0)) { auth_ok = 0; } if (!auth_ok) { PRINTF("broker: auth failed sock=%d user=%s", (int)bc->sock, (bc->username && bc->username[0]) ? bc->username : "(null)"); - ack.return_code = MQTT_CONNECT_ACK_CODE_REFUSED_BAD_USER_PWD; + #ifdef WOLFMQTT_V5 + if (mc.protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) { + ack.return_code = MQTT_REASON_BAD_USER_OR_PASS; + } + else + #endif + { + ack.return_code = + MQTT_CONNECT_ACK_CODE_REFUSED_BAD_USER_PWD; + } } } @@ -1661,9 +1714,9 @@ static int BrokerHandle_Subscribe(BrokerClient* bc, int rx_len, MqttQoS topic_qos = sub.topics[i].qos; MqttQoS granted_qos; - /* Cap at QoS 1 for this broker */ - if (topic_qos > MQTT_QOS_1) { - topic_qos = MQTT_QOS_1; + /* Cap at QoS 2 */ + if (topic_qos > MQTT_QOS_2) { + topic_qos = MQTT_QOS_2; } granted_qos = topic_qos; @@ -1741,7 +1794,13 @@ static int BrokerHandle_Unsubscribe(BrokerClient* bc, int rx_len, #ifdef WOLFMQTT_V5 ack.protocol_level = bc->protocol_level; ack.props = NULL; - ack.reason_codes = NULL; + if (bc->protocol_level >= MQTT_CONNECT_PROTOCOL_LEVEL_5) { + byte reason = MQTT_REASON_SUCCESS; + ack.reason_codes = &reason; + } + else { + ack.reason_codes = NULL; + } #endif rc = MqttEncode_UnsubscribeAck(bc->tx_buf, #ifdef WOLFMQTT_STATIC_MEMORY @@ -1826,11 +1885,8 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, MqttPublish out_pub; XMEMSET(&out_pub, 0, sizeof(out_pub)); out_pub.topic_name = topic; - /* Effective QoS = min(publish_qos, sub_qos), capped at 1 */ + /* Effective QoS = min(publish_qos, sub_qos) */ eff_qos = (pub.qos < sub->qos) ? pub.qos : sub->qos; - if (eff_qos > MQTT_QOS_1) { - eff_qos = MQTT_QOS_1; - } out_pub.qos = eff_qos; if (eff_qos >= MQTT_QOS_1) { out_pub.packet_id = BrokerNextPacketId(broker); @@ -1865,9 +1921,6 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, XMEMSET(&out_pub, 0, sizeof(out_pub)); out_pub.topic_name = topic; eff_qos = (pub.qos < sub->qos) ? pub.qos : sub->qos; - if (eff_qos > MQTT_QOS_1) { - eff_qos = MQTT_QOS_1; - } out_pub.qos = eff_qos; if (eff_qos >= MQTT_QOS_1) { out_pub.packet_id = BrokerNextPacketId(broker); @@ -1975,6 +2028,49 @@ static int BrokerHandle_PublishRel(BrokerClient* bc, int rx_len) return rc; } +/* Handle PUBREC from subscriber: broker sent QoS 2 PUBLISH, subscriber + * responds with PUBREC, broker sends PUBREL */ +static int BrokerHandle_PublishRec(BrokerClient* bc, int rx_len) +{ + int rc; + MqttPublishResp resp; + + XMEMSET(&resp, 0, sizeof(resp)); +#ifdef WOLFMQTT_V5 + resp.protocol_level = bc->protocol_level; +#endif + PRINTF("broker: PUBLISH_REC recv sock=%d len=%d", (int)bc->sock, rx_len); + rc = MqttDecode_PublishResp(bc->rx_buf, rx_len, + MQTT_PACKET_TYPE_PUBLISH_REC, &resp); + if (rc < 0) { + PRINTF("broker: PUBLISH_REC decode failed rc=%d", rc); + return rc; + } + +#ifdef WOLFMQTT_V5 + resp.reason_code = MQTT_REASON_SUCCESS; + resp.props = NULL; +#endif + rc = MqttEncode_PublishResp(bc->tx_buf, +#ifdef WOLFMQTT_STATIC_MEMORY + BROKER_TX_BUF_SZ, +#else + bc->tx_buf_len, +#endif + MQTT_PACKET_TYPE_PUBLISH_REL, &resp); + if (rc > 0) { + PRINTF("broker: PUBREL send sock=%d packet_id=%u", + (int)bc->sock, resp.packet_id); + rc = MqttPacket_Write(&bc->client, bc->tx_buf, rc); + } +#ifdef WOLFMQTT_V5 + if (resp.props) { + (void)MqttProps_Free(resp.props); + } +#endif + return rc; +} + /* -------------------------------------------------------------------------- */ /* Per-client processing (called from Step) */ /* -------------------------------------------------------------------------- */ @@ -1994,6 +2090,20 @@ static int BrokerClient_Process(MqttBroker* broker, BrokerClient* bc) bc->tls_handshake_done = 1; PRINTF("broker: TLS handshake done sock=%d %s", (int)bc->sock, wolfSSL_get_version(bc->client.tls.ssl)); + /* Log client certificate subject if mutual TLS */ + if (broker->tls_ca != NULL) { + WOLFSSL_X509* peer = wolfSSL_get_peer_certificate( + bc->client.tls.ssl); + if (peer != NULL) { + char subject[256]; + wolfSSL_X509_NAME_oneline( + wolfSSL_X509_get_subject_name(peer), subject, + (int)sizeof(subject)); + PRINTF("broker: TLS client cert sock=%d subject=%s", + (int)bc->sock, subject); + wolfSSL_X509_free(peer); + } + } return 1; /* activity */ } else { @@ -2053,9 +2163,23 @@ static int BrokerClient_Process(MqttBroker* broker, BrokerClient* bc) case MQTT_PACKET_TYPE_PUBLISH: (void)BrokerHandle_Publish(bc, rc, broker); break; + case MQTT_PACKET_TYPE_PUBLISH_ACK: + /* QoS 1 ack from subscriber - delivery complete */ + break; + case MQTT_PACKET_TYPE_PUBLISH_REC: + /* QoS 2 step 2: subscriber sends PUBREC, broker + * responds with PUBREL */ + (void)BrokerHandle_PublishRec(bc, rc); + break; case MQTT_PACKET_TYPE_PUBLISH_REL: + /* QoS 2 step 3: publisher sends PUBREL, broker + * responds with PUBCOMP */ (void)BrokerHandle_PublishRel(bc, rc); break; + case MQTT_PACKET_TYPE_PUBLISH_COMP: + /* QoS 2 step 4: subscriber sends PUBCOMP - delivery + * complete */ + break; case MQTT_PACKET_TYPE_SUBSCRIBE: (void)BrokerHandle_Subscribe(bc, rc, broker); break; @@ -2075,12 +2199,15 @@ static int BrokerClient_Process(MqttBroker* broker, BrokerClient* bc) } } - /* Check keepalive timeout */ + /* Check keepalive timeout (MQTT spec 3.1.2.10: 1.5x keep alive) */ if (bc->keep_alive_sec > 0) { WOLFMQTT_BROKER_TIME_T now = WOLFMQTT_BROKER_GET_TIME_S(); if ((now - bc->last_rx) > - (WOLFMQTT_BROKER_TIME_T)(bc->keep_alive_sec * 2)) { + (WOLFMQTT_BROKER_TIME_T)(bc->keep_alive_sec * 3 / 2)) { PRINTF("broker: keepalive timeout sock=%d", (int)bc->sock); + #ifdef WOLFMQTT_V5 + BrokerSend_Disconnect(bc, MQTT_REASON_KEEP_ALIVE_TIMEOUT); + #endif BrokerClient_PublishWill(broker, bc); /* abnormal disconnect */ BrokerSubs_RemoveClient(broker, bc); BrokerClient_Remove(broker, bc); From b2491c3819801adb9492789d6ba21e68e303656b Mon Sep 17 00:00:00 2001 From: David Garske Date: Fri, 6 Feb 2026 16:07:37 -0800 Subject: [PATCH 10/13] Guard `OPENSSL_EXTRA` APIs for TLS client cert logging, Signal handling, extended tests and v5 property fix --- scripts/broker.test | 181 +++++++++++++++++++++++++++++++++++++++++++- src/mqtt_broker.c | 33 +++++++- src/mqtt_packet.c | 2 +- 3 files changed, 212 insertions(+), 4 deletions(-) diff --git a/scripts/broker.test b/scripts/broker.test index 3e192cd4c..1f1311514 100755 --- a/scripts/broker.test +++ b/scripts/broker.test @@ -12,7 +12,8 @@ broker_pid=$no_pid do_cleanup() { if [ $broker_pid != $no_pid ]; then - kill -9 $broker_pid 2>/dev/null + kill $broker_pid 2>/dev/null + wait $broker_pid 2>/dev/null || true broker_pid=$no_pid fi for pid in "${TEST_PIDS[@]:-}"; do @@ -55,7 +56,7 @@ check_broker() { # Start broker helper: kills existing broker, generates port, starts new one start_broker() { if [ $broker_pid != $no_pid ]; then - kill -9 $broker_pid 2>/dev/null + kill $broker_pid 2>/dev/null wait $broker_pid 2>/dev/null || true broker_pid=$no_pid fi @@ -290,6 +291,182 @@ else echo "SKIP: TLS tests (broker not built with TLS support)" fi +# --- Test 11: Multi-client QoS 2 (separate pub/sub) --- +echo "" +echo "--- Test 11: Multi-client QoS 2 ---" +start_broker +# Subscriber connects with QoS 2 +timeout 10 ./$sub_bin -T -h 127.0.0.1 -p $port -n "test/mc_qos2" -q 2 \ + >"${TMP_DIR}/t11_sub.log" 2>&1 & +T11_SUB_PID=$! +TEST_PIDS+=($T11_SUB_PID) +sleep 1 +# Publisher sends at QoS 2 +./$pub_bin -T -h 127.0.0.1 -p $port -n "test/mc_qos2" -q 2 -m "qos2_multi" \ + >"${TMP_DIR}/t11_pub.log" 2>&1 +sleep 2 +kill $T11_SUB_PID 2>/dev/null +wait $T11_SUB_PID 2>/dev/null || true +TEST_PIDS=() +if grep -q "qos2_multi" "${TMP_DIR}/t11_sub.log" 2>/dev/null; then + echo "PASS: Multi-client QoS 2" +else + echo "FAIL: Multi-client QoS 2" + FAIL=1 +fi + +# --- Test 12: MQTT v5 pub/sub --- +echo "" +echo "--- Test 12: MQTT v5 pub/sub ---" +has_v5=no +if ./$client_bin -h 2>&1 | grep -q "Max packet size"; then + has_v5=yes +fi +if [ "$has_v5" = "yes" ]; then + ./$client_bin -T -h 127.0.0.1 -p $port -n "test/v5" -C 5000 \ + >"${TMP_DIR}/t12.log" 2>&1 + if [ $? -eq 0 ]; then + echo "PASS: MQTT v5 pub/sub" + else + echo "FAIL: MQTT v5 pub/sub" + FAIL=1 + fi +else + echo "SKIP: MQTT v5 (not built with --enable-v5)" +fi + +# --- Test 13: Wildcard subscription --- +echo "" +echo "--- Test 13: Wildcard subscription ---" +# Test both + and # wildcards (use different client IDs) +timeout 10 ./$sub_bin -T -h 127.0.0.1 -p $port -n "test/wild/+" -i "sub_plus" \ + >"${TMP_DIR}/t13_plus.log" 2>&1 & +T13_PLUS_PID=$! +timeout 10 ./$sub_bin -T -h 127.0.0.1 -p $port -n "test/#" -i "sub_hash" \ + >"${TMP_DIR}/t13_hash.log" 2>&1 & +T13_HASH_PID=$! +TEST_PIDS+=($T13_PLUS_PID $T13_HASH_PID) +sleep 1 +# Publish to a topic that matches both wildcards +./$pub_bin -T -h 127.0.0.1 -p $port -n "test/wild/card" -m "wildcard_msg" \ + >"${TMP_DIR}/t13_pub.log" 2>&1 +sleep 2 +kill $T13_PLUS_PID $T13_HASH_PID 2>/dev/null +wait $T13_PLUS_PID 2>/dev/null || true +wait $T13_HASH_PID 2>/dev/null || true +TEST_PIDS=() +T13_PLUS_OK=no +T13_HASH_OK=no +if grep -q "wildcard_msg" "${TMP_DIR}/t13_plus.log" 2>/dev/null; then + T13_PLUS_OK=yes +fi +if grep -q "wildcard_msg" "${TMP_DIR}/t13_hash.log" 2>/dev/null; then + T13_HASH_OK=yes +fi +if [ "$T13_PLUS_OK" = "yes" ] && [ "$T13_HASH_OK" = "yes" ]; then + echo "PASS: Wildcard subscription (+ and #)" +else + echo "FAIL: Wildcard (plus=$T13_PLUS_OK, hash=$T13_HASH_OK)" + FAIL=1 +fi + +# --- Test 14: Client ID takeover --- +echo "" +echo "--- Test 14: Client ID takeover ---" +# First client connects with LWT +timeout 10 ./$sub_bin -T -h 127.0.0.1 -p $port -n "wolfMQTT/example/lwttopic" \ + >"${TMP_DIR}/t14_watcher.log" 2>&1 & +T14_WATCHER_PID=$! +TEST_PIDS+=($T14_WATCHER_PID) +sleep 1 +./$sub_bin -T -h 127.0.0.1 -p $port -n "test/takeover" -i "dup_client" -l \ + >"${TMP_DIR}/t14_old.log" 2>&1 & +T14_OLD_PID=$! +TEST_PIDS+=($T14_OLD_PID) +sleep 2 +# Second client connects with same ID - should take over +./$client_bin -T -h 127.0.0.1 -p $port -n "test/takeover" -i "dup_client" -C 5000 \ + >"${TMP_DIR}/t14_new.log" 2>&1 +T14_NEW_RC=$? +sleep 1 +kill $T14_OLD_PID 2>/dev/null +wait $T14_OLD_PID 2>/dev/null || true +kill $T14_WATCHER_PID 2>/dev/null +wait $T14_WATCHER_PID 2>/dev/null || true +TEST_PIDS=() +if [ $T14_NEW_RC -eq 0 ]; then + echo "PASS: Client ID takeover (new client succeeded)" +else + echo "FAIL: Client ID takeover (new_rc=$T14_NEW_RC)" + FAIL=1 +fi + +# --- Test 15: Max client connections --- +echo "" +echo "--- Test 15: Max client connections ---" +# Connect multiple clients simultaneously to verify broker handles them +T15_PIDS=() +T15_OK=yes +for idx in $(seq 1 8); do + timeout 5 ./$sub_bin -T -h 127.0.0.1 -p $port -n "test/maxcli/$idx" \ + -i "max_client_$idx" >"${TMP_DIR}/t15_$idx.log" 2>&1 & + T15_PIDS+=($!) +done +TEST_PIDS=("${T15_PIDS[@]}") +sleep 2 +# Publish to one topic to verify broker is still responsive +./$client_bin -T -h 127.0.0.1 -p $port -n "test/maxcli/verify" -C 5000 \ + >"${TMP_DIR}/t15_verify.log" 2>&1 +T15_VERIFY_RC=$? +# Cleanup subscribers +for pid in "${T15_PIDS[@]}"; do + kill "$pid" 2>/dev/null + wait "$pid" 2>/dev/null || true +done +TEST_PIDS=() +if [ $T15_VERIFY_RC -eq 0 ]; then + echo "PASS: Max client connections (8 concurrent + pub/sub)" +else + echo "FAIL: Max client connections (verify_rc=$T15_VERIFY_RC)" + FAIL=1 +fi + +# --- Test 16: Graceful shutdown (SIGTERM) --- +echo "" +echo "--- Test 16: Graceful shutdown ---" +# Verify broker exits cleanly on SIGTERM +start_broker +# Connect a client with LWT +timeout 10 ./$sub_bin -T -h 127.0.0.1 -p $port -n "wolfMQTT/example/lwttopic" \ + -i "shutdown_watcher" >"${TMP_DIR}/t16_watcher.log" 2>&1 & +T16_WATCHER_PID=$! +TEST_PIDS+=($T16_WATCHER_PID) +sleep 1 +./$sub_bin -T -h 127.0.0.1 -p $port -n "test/shutdown" -i "shutdown_client" -l \ + >"${TMP_DIR}/t16_client.log" 2>&1 & +T16_CLIENT_PID=$! +TEST_PIDS+=($T16_CLIENT_PID) +sleep 1 +# Send SIGTERM to broker - should shut down gracefully (no LWT) +kill $broker_pid 2>/dev/null +wait $broker_pid 2>/dev/null || true +T16_BROKER_RC=$? +broker_pid=$no_pid +sleep 2 +kill $T16_CLIENT_PID 2>/dev/null +wait $T16_CLIENT_PID 2>/dev/null || true +kill $T16_WATCHER_PID 2>/dev/null +wait $T16_WATCHER_PID 2>/dev/null || true +TEST_PIDS=() +# Broker should exit cleanly (rc=0 or signal exit) +# LWT should NOT be delivered since broker shut down gracefully +if grep -q "shutdown_client" "${TMP_DIR}/t16_watcher.log" 2>/dev/null; then + echo "FAIL: Graceful shutdown (LWT was delivered - not graceful)" + FAIL=1 +else + echo "PASS: Graceful shutdown (no LWT on SIGTERM)" +fi + # --- Summary --- echo "" if [ $FAIL -ne 0 ]; then diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index 8b78dde8e..e25738156 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -2090,7 +2090,8 @@ static int BrokerClient_Process(MqttBroker* broker, BrokerClient* bc) bc->tls_handshake_done = 1; PRINTF("broker: TLS handshake done sock=%d %s", (int)bc->sock, wolfSSL_get_version(bc->client.tls.ssl)); - /* Log client certificate subject if mutual TLS */ + /* Log client certificate subject if mutual TLS (requires wolfSSL OPENSSL_EXTRA) */ +#if defined(OPENSSL_EXTRA) if (broker->tls_ca != NULL) { WOLFSSL_X509* peer = wolfSSL_get_peer_certificate( bc->client.tls.ssl); @@ -2104,6 +2105,12 @@ static int BrokerClient_Process(MqttBroker* broker, BrokerClient* bc) wolfSSL_X509_free(peer); } } +#else + if (broker->tls_ca != NULL) { + PRINTF("broker: TLS client cert sock=%d (mutual TLS)", + (int)bc->sock); + } +#endif return 1; /* activity */ } else { @@ -2421,6 +2428,19 @@ static void BrokerUsage(const char* prog) #endif } +static MqttBroker* g_broker = NULL; + +#if !defined(WOLFMQTT_BROKER_CUSTOM_NET) && !defined(NO_MAIN_DRIVER) +#include +static void broker_signal_handler(int signo) +{ + if (g_broker != NULL) { + PRINTF("broker: received signal %d, shutting down", signo); + MqttBroker_Stop(g_broker); + } +} +#endif + int wolfmqtt_broker(int argc, char** argv) { int rc; @@ -2490,7 +2510,18 @@ int wolfmqtt_broker(int argc, char** argv) } } +#if !defined(WOLFMQTT_BROKER_CUSTOM_NET) && !defined(NO_MAIN_DRIVER) + g_broker = &broker; + signal(SIGINT, broker_signal_handler); + signal(SIGTERM, broker_signal_handler); +#endif + rc = MqttBroker_Run(&broker); + +#if !defined(WOLFMQTT_BROKER_CUSTOM_NET) && !defined(NO_MAIN_DRIVER) + g_broker = NULL; +#endif + MqttBroker_Free(&broker); return rc; } diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index d4c67c1dd..31ff9c4c0 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -78,7 +78,7 @@ static const struct MqttPropMatrix gPropMatrix[] = { { MQTT_PROP_REQ_PROB_INFO, MQTT_DATA_TYPE_BYTE, (1 << MQTT_PACKET_TYPE_CONNECT) }, { MQTT_PROP_WILL_DELAY_INTERVAL, MQTT_DATA_TYPE_INT, - (1 << MQTT_PACKET_TYPE_PUBLISH) }, + (1 << MQTT_PACKET_TYPE_CONNECT) }, { MQTT_PROP_REQ_RESP_INFO, MQTT_DATA_TYPE_BYTE, (1 << MQTT_PACKET_TYPE_CONNECT) }, { MQTT_PROP_RESP_INFO, MQTT_DATA_TYPE_STRING, From 1d8b1bc31b062d5ad97a30117ad92caeeb6e1637 Mon Sep 17 00:00:00 2001 From: David Garske Date: Fri, 6 Feb 2026 16:43:59 -0800 Subject: [PATCH 11/13] Add MQTT v5 will delay interval, message expiry and remove OPENSSL_EXTRA dependency --- scripts/broker.test | 13 +- src/mqtt_broker.c | 399 ++++++++++++++++++++++++++++++++++++----- src/mqtt_packet.c | 1 + wolfmqtt/mqtt_broker.h | 29 +++ 4 files changed, 389 insertions(+), 53 deletions(-) diff --git a/scripts/broker.test b/scripts/broker.test index 1f1311514..60c0763e4 100755 --- a/scripts/broker.test +++ b/scripts/broker.test @@ -139,7 +139,8 @@ echo "" echo "--- Test 5: Last Will and Testament ---" # LWT topic is hardcoded as "wolfMQTT/example/lwttopic", payload is the client ID # Use mqtt-sub as the LWT watcher -timeout 10 ./$sub_bin -T -h 127.0.0.1 -p $port -n "wolfMQTT/example/lwttopic" \ +# Note: v5 clients set Will Delay Interval=5s, so watcher must stay alive long enough +timeout 20 ./$sub_bin -T -h 127.0.0.1 -p $port -n "wolfMQTT/example/lwttopic" \ >"${TMP_DIR}/t4_sub.log" 2>&1 & T4_SUB_PID=$! TEST_PIDS+=($T4_SUB_PID) @@ -153,7 +154,7 @@ TEST_PIDS+=($T4_CLIENT_PID) sleep 2 kill -9 $T4_CLIENT_PID 2>/dev/null wait $T4_CLIENT_PID 2>/dev/null || true -sleep 2 +sleep 8 kill $T4_SUB_PID 2>/dev/null wait $T4_SUB_PID 2>/dev/null || true TEST_PIDS=() @@ -168,7 +169,8 @@ fi echo "" echo "--- Test 6: Keep-alive enforcement ---" # Subscriber watches the LWT topic -timeout 15 ./$sub_bin -T -h 127.0.0.1 -p $port -n "wolfMQTT/example/lwttopic" \ +# Note: v5 clients set Will Delay Interval=5s on top of keepalive timeout +timeout 25 ./$sub_bin -T -h 127.0.0.1 -p $port -n "wolfMQTT/example/lwttopic" \ >"${TMP_DIR}/t5_sub.log" 2>&1 & T5_SUB_PID=$! TEST_PIDS+=($T5_SUB_PID) @@ -181,8 +183,9 @@ TEST_PIDS+=($T5_CLIENT_PID) sleep 2 # Freeze the client so it stops sending PINGREQs kill -STOP $T5_CLIENT_PID 2>/dev/null -# Wait for broker to detect keepalive timeout (1.5x keep_alive = 3s + margin) -sleep 5 +# Wait for broker to detect keepalive timeout (1.5x * 2s = 3s) +# plus v5 will delay interval (5s) + margin +sleep 10 kill -9 $T5_CLIENT_PID 2>/dev/null wait $T5_CLIENT_PID 2>/dev/null || true kill $T5_SUB_PID 2>/dev/null diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index e25738156..6c3bb0aba 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -890,7 +890,7 @@ static void BrokerSubs_ReassociateClient(MqttBroker* broker, /* Retained message management */ /* -------------------------------------------------------------------------- */ static int BrokerRetained_Store(MqttBroker* broker, const char* topic, - const byte* payload, word16 payload_len) + const byte* payload, word16 payload_len, word32 expiry_sec) { BrokerRetainedMsg* msg = NULL; @@ -987,8 +987,11 @@ static int BrokerRetained_Store(MqttBroker* broker, const char* topic, } #endif - PRINTF("broker: retained store topic=%s len=%u", topic, - (unsigned)payload_len); + msg->store_time = WOLFMQTT_BROKER_GET_TIME_S(); + msg->expiry_sec = expiry_sec; + + PRINTF("broker: retained store topic=%s len=%u expiry=%u", topic, + (unsigned)payload_len, (unsigned)expiry_sec); return MQTT_CODE_SUCCESS; } @@ -1076,6 +1079,7 @@ static void BrokerClient_ClearWill(BrokerClient* bc) bc->has_will = 0; bc->will_qos = MQTT_QOS_0; bc->will_retain = 0; + bc->will_delay_sec = 0; bc->will_payload_len = 0; #ifdef WOLFMQTT_STATIC_MEMORY bc->will_topic[0] = '\0'; @@ -1091,8 +1095,229 @@ static void BrokerClient_ClearWill(BrokerClient* bc) #endif } -/* Forward declaration needed for BrokerClient_PublishWill */ +/* -------------------------------------------------------------------------- */ +/* Pending will management (v5 Will Delay Interval) */ +/* -------------------------------------------------------------------------- */ + +/* Add a pending will to be published after delay expires */ +static int BrokerPendingWill_Add(MqttBroker* broker, BrokerClient* bc) +{ + BrokerPendingWill* pw = NULL; + WOLFMQTT_BROKER_TIME_T now = WOLFMQTT_BROKER_GET_TIME_S(); + +#ifdef WOLFMQTT_STATIC_MEMORY + { + int i; + for (i = 0; i < BROKER_MAX_PENDING_WILLS; i++) { + if (!broker->pending_wills[i].in_use) { + pw = &broker->pending_wills[i]; + break; + } + } + if (pw == NULL) { + return MQTT_CODE_ERROR_MEMORY; + } + XMEMSET(pw, 0, sizeof(*pw)); + pw->in_use = 1; + XSTRNCPY(pw->client_id, bc->client_id, BROKER_MAX_CLIENT_ID_LEN - 1); + XSTRNCPY(pw->topic, bc->will_topic, BROKER_MAX_TOPIC_LEN - 1); + if (bc->will_payload_len > 0) { + word16 len = bc->will_payload_len; + if (len > BROKER_MAX_PAYLOAD_LEN) { + len = BROKER_MAX_PAYLOAD_LEN; + } + XMEMCPY(pw->payload, bc->will_payload, len); + pw->payload_len = len; + } + } +#else + pw = (BrokerPendingWill*)WOLFMQTT_MALLOC(sizeof(BrokerPendingWill)); + if (pw == NULL) { + return MQTT_CODE_ERROR_MEMORY; + } + XMEMSET(pw, 0, sizeof(*pw)); + { + int id_len = (int)XSTRLEN(bc->client_id); + pw->client_id = (char*)WOLFMQTT_MALLOC((size_t)id_len + 1); + if (pw->client_id == NULL) { + WOLFMQTT_FREE(pw); + return MQTT_CODE_ERROR_MEMORY; + } + XMEMCPY(pw->client_id, bc->client_id, (size_t)id_len + 1); + } + { + int t_len = (int)XSTRLEN(bc->will_topic); + pw->topic = (char*)WOLFMQTT_MALLOC((size_t)t_len + 1); + if (pw->topic == NULL) { + WOLFMQTT_FREE(pw->client_id); + WOLFMQTT_FREE(pw); + return MQTT_CODE_ERROR_MEMORY; + } + XMEMCPY(pw->topic, bc->will_topic, (size_t)t_len + 1); + } + if (bc->will_payload_len > 0) { + pw->payload = (byte*)WOLFMQTT_MALLOC(bc->will_payload_len); + if (pw->payload == NULL) { + WOLFMQTT_FREE(pw->topic); + WOLFMQTT_FREE(pw->client_id); + WOLFMQTT_FREE(pw); + return MQTT_CODE_ERROR_MEMORY; + } + XMEMCPY(pw->payload, bc->will_payload, bc->will_payload_len); + pw->payload_len = bc->will_payload_len; + } + pw->next = broker->pending_wills; + broker->pending_wills = pw; +#endif + pw->qos = bc->will_qos; + pw->retain = bc->will_retain; + pw->publish_time = now + (WOLFMQTT_BROKER_TIME_T)bc->will_delay_sec; + + PRINTF("broker: will deferred sock=%d client_id=%s delay=%u", + (int)bc->sock, bc->client_id, (unsigned)bc->will_delay_sec); + return MQTT_CODE_SUCCESS; +} + +/* Cancel a pending will for the given client_id (client reconnected) */ +static void BrokerPendingWill_Cancel(MqttBroker* broker, + const char* client_id) +{ + if (broker == NULL || client_id == NULL) { + return; + } +#ifdef WOLFMQTT_STATIC_MEMORY + { + int i; + for (i = 0; i < BROKER_MAX_PENDING_WILLS; i++) { + if (broker->pending_wills[i].in_use && + XSTRCMP(broker->pending_wills[i].client_id, client_id) == 0) { + PRINTF("broker: will cancelled client_id=%s", client_id); + XMEMSET(&broker->pending_wills[i], 0, + sizeof(BrokerPendingWill)); + return; + } + } + } +#else + { + BrokerPendingWill* pw = broker->pending_wills; + BrokerPendingWill* prev = NULL; + while (pw) { + BrokerPendingWill* next = pw->next; + if (pw->client_id != NULL && + XSTRCMP(pw->client_id, client_id) == 0) { + PRINTF("broker: will cancelled client_id=%s", client_id); + if (prev) { + prev->next = next; + } + else { + broker->pending_wills = next; + } + WOLFMQTT_FREE(pw->client_id); + if (pw->topic) WOLFMQTT_FREE(pw->topic); + if (pw->payload) WOLFMQTT_FREE(pw->payload); + WOLFMQTT_FREE(pw); + return; + } + prev = pw; + pw = next; + } + } +#endif +} + +static void BrokerPendingWill_FreeAll(MqttBroker* broker) +{ + if (broker == NULL) { + return; + } +#ifdef WOLFMQTT_STATIC_MEMORY + XMEMSET(broker->pending_wills, 0, sizeof(broker->pending_wills)); +#else + { + BrokerPendingWill* pw = broker->pending_wills; + while (pw) { + BrokerPendingWill* next = pw->next; + if (pw->client_id) WOLFMQTT_FREE(pw->client_id); + if (pw->topic) WOLFMQTT_FREE(pw->topic); + if (pw->payload) WOLFMQTT_FREE(pw->payload); + WOLFMQTT_FREE(pw); + pw = next; + } + broker->pending_wills = NULL; + } +#endif +} + +/* Forward declarations */ static int BrokerTopicMatch(const char* filter, const char* topic); +static void BrokerClient_PublishWillImmediate(MqttBroker* broker, + const char* topic, const byte* payload, word16 payload_len, + MqttQoS qos, byte retain); + +/* Process pending wills - publish any that have expired their delay */ +static int BrokerPendingWill_Process(MqttBroker* broker) +{ + int activity = 0; + WOLFMQTT_BROKER_TIME_T now = WOLFMQTT_BROKER_GET_TIME_S(); + + if (broker == NULL) { + return 0; + } + +#ifdef WOLFMQTT_STATIC_MEMORY + { + int i; + for (i = 0; i < BROKER_MAX_PENDING_WILLS; i++) { + BrokerPendingWill* pw = &broker->pending_wills[i]; + if (!pw->in_use) { + continue; + } + if (now >= pw->publish_time) { + PRINTF("broker: LWT deferred publish client_id=%s topic=%s " + "len=%u", pw->client_id, pw->topic, + (unsigned)pw->payload_len); + BrokerClient_PublishWillImmediate(broker, pw->topic, + pw->payload, pw->payload_len, pw->qos, pw->retain); + XMEMSET(pw, 0, sizeof(BrokerPendingWill)); + activity = 1; + } + } + } +#else + { + BrokerPendingWill* pw = broker->pending_wills; + BrokerPendingWill* prev = NULL; + while (pw) { + BrokerPendingWill* next = pw->next; + if (now >= pw->publish_time) { + PRINTF("broker: LWT deferred publish client_id=%s topic=%s " + "len=%u", pw->client_id, pw->topic, + (unsigned)pw->payload_len); + BrokerClient_PublishWillImmediate(broker, pw->topic, + pw->payload, pw->payload_len, pw->qos, pw->retain); + if (prev) { + prev->next = next; + } + else { + broker->pending_wills = next; + } + if (pw->client_id) WOLFMQTT_FREE(pw->client_id); + if (pw->topic) WOLFMQTT_FREE(pw->topic); + if (pw->payload) WOLFMQTT_FREE(pw->payload); + WOLFMQTT_FREE(pw); + activity = 1; + } + else { + prev = pw; + } + pw = next; + } + } +#endif + + return activity; +} static void BrokerRetained_DeliverToClient(MqttBroker* broker, BrokerClient* bc, const char* filter, MqttQoS sub_qos) @@ -1100,6 +1325,7 @@ static void BrokerRetained_DeliverToClient(MqttBroker* broker, if (broker == NULL || bc == NULL || filter == NULL) { return; } + WOLFMQTT_BROKER_TIME_T now = WOLFMQTT_BROKER_GET_TIME_S(); (void)sub_qos; /* retained always delivered at QoS 0 in this broker */ #ifdef WOLFMQTT_STATIC_MEMORY @@ -1110,6 +1336,13 @@ static void BrokerRetained_DeliverToClient(MqttBroker* broker, if (!rm->in_use || rm->topic[0] == '\0') { continue; } + /* Skip expired messages */ + if (rm->expiry_sec > 0 && + (now - rm->store_time) >= rm->expiry_sec) { + PRINTF("broker: retained expired topic=%s", rm->topic); + XMEMSET(rm, 0, sizeof(BrokerRetainedMsg)); + continue; + } if (BrokerTopicMatch(filter, rm->topic)) { MqttPublish out_pub; int enc_rc; @@ -1137,7 +1370,25 @@ static void BrokerRetained_DeliverToClient(MqttBroker* broker, #else { BrokerRetainedMsg* rm = broker->retained; + BrokerRetainedMsg* rm_prev = NULL; while (rm) { + BrokerRetainedMsg* rm_next = rm->next; + /* Skip and remove expired messages */ + if (rm->expiry_sec > 0 && + (now - rm->store_time) >= rm->expiry_sec) { + PRINTF("broker: retained expired topic=%s", rm->topic); + if (rm_prev) { + rm_prev->next = rm_next; + } + else { + broker->retained = rm_next; + } + if (rm->topic) WOLFMQTT_FREE(rm->topic); + if (rm->payload) WOLFMQTT_FREE(rm->payload); + WOLFMQTT_FREE(rm); + rm = rm_next; + continue; + } if (rm->topic != NULL && BrokerTopicMatch(filter, rm->topic)) { MqttPublish out_pub; int enc_rc; @@ -1160,7 +1411,8 @@ static void BrokerRetained_DeliverToClient(MqttBroker* broker, (void)MqttPacket_Write(&bc->client, bc->tx_buf, enc_rc); } } - rm = rm->next; + rm_prev = rm; + rm = rm_next; } } #endif @@ -1182,17 +1434,41 @@ static void BrokerClient_PublishWill(MqttBroker* broker, BrokerClient* bc) } #endif + /* v5 Will Delay Interval: defer publication */ + if (bc->will_delay_sec > 0) { + if (BrokerPendingWill_Add(broker, bc) == MQTT_CODE_SUCCESS) { + BrokerClient_ClearWill(bc); + return; /* will deferred, not published now */ + } + /* If add failed (out of slots), publish immediately as fallback */ + } + PRINTF("broker: LWT publish sock=%d topic=%s len=%u", (int)bc->sock, bc->will_topic, (unsigned)bc->will_payload_len); + BrokerClient_PublishWillImmediate(broker, bc->will_topic, + bc->will_payload, bc->will_payload_len, bc->will_qos, + bc->will_retain); + BrokerClient_ClearWill(bc); +} + +/* Publish a will message immediately (shared by direct and deferred paths) */ +static void BrokerClient_PublishWillImmediate(MqttBroker* broker, + const char* topic, const byte* payload, word16 payload_len, + MqttQoS qos, byte retain) +{ + if (broker == NULL || topic == NULL) { + return; + } + /* Handle retain flag on will message */ - if (bc->will_retain) { - if (bc->will_payload_len == 0) { - BrokerRetained_Delete(broker, bc->will_topic); + if (retain) { + if (payload_len == 0) { + BrokerRetained_Delete(broker, topic); } else { - (void)BrokerRetained_Store(broker, bc->will_topic, - bc->will_payload, bc->will_payload_len); + (void)BrokerRetained_Store(broker, topic, payload, + payload_len, 0); } } @@ -1207,21 +1483,18 @@ static void BrokerClient_PublishWill(MqttBroker* broker, BrokerClient* bc) sub->filter[0] == '\0') { continue; } - if (sub->client != bc && - BrokerTopicMatch(sub->filter, bc->will_topic)) { + if (BrokerTopicMatch(sub->filter, topic)) { MqttPublish out_pub; MqttQoS eff_qos; int enc_rc; XMEMSET(&out_pub, 0, sizeof(out_pub)); - out_pub.topic_name = bc->will_topic; - eff_qos = (bc->will_qos < sub->qos) ? - bc->will_qos : sub->qos; + out_pub.topic_name = (char*)topic; + eff_qos = (qos < sub->qos) ? qos : sub->qos; out_pub.qos = eff_qos; out_pub.retain = 0; out_pub.duplicate = 0; - out_pub.buffer = (bc->will_payload_len > 0) ? - bc->will_payload : NULL; - out_pub.total_len = bc->will_payload_len; + out_pub.buffer = (payload_len > 0) ? (byte*)payload : NULL; + out_pub.total_len = payload_len; if (eff_qos >= MQTT_QOS_1) { out_pub.packet_id = BrokerNextPacketId(broker); } @@ -1241,23 +1514,21 @@ static void BrokerClient_PublishWill(MqttBroker* broker, BrokerClient* bc) { BrokerSub* sub = broker->subs; while (sub) { - if (sub->client != NULL && sub->client != bc && + if (sub->client != NULL && sub->client->protocol_level != 0 && sub->filter != NULL && - BrokerTopicMatch(sub->filter, bc->will_topic)) { + BrokerTopicMatch(sub->filter, topic)) { MqttPublish out_pub; MqttQoS eff_qos; int enc_rc; XMEMSET(&out_pub, 0, sizeof(out_pub)); - out_pub.topic_name = bc->will_topic; - eff_qos = (bc->will_qos < sub->qos) ? - bc->will_qos : sub->qos; + out_pub.topic_name = (char*)topic; + eff_qos = (qos < sub->qos) ? qos : sub->qos; out_pub.qos = eff_qos; out_pub.retain = 0; out_pub.duplicate = 0; - out_pub.buffer = (bc->will_payload_len > 0) ? - bc->will_payload : NULL; - out_pub.total_len = bc->will_payload_len; + out_pub.buffer = (payload_len > 0) ? (byte*)payload : NULL; + out_pub.total_len = payload_len; if (eff_qos >= MQTT_QOS_1) { out_pub.packet_id = BrokerNextPacketId(broker); } @@ -1275,8 +1546,6 @@ static void BrokerClient_PublishWill(MqttBroker* broker, BrokerClient* bc) } } #endif - - BrokerClient_ClearWill(bc); } /* -------------------------------------------------------------------------- */ @@ -1379,6 +1648,19 @@ static int BrokerSend_SubAck(BrokerClient* bc, word16 packet_id, } #ifdef WOLFMQTT_V5 +/* Find a property by type in a property list */ +static MqttProp* BrokerProps_Find(MqttProp* head, MqttPropertyType type) +{ + MqttProp* prop = head; + while (prop != NULL) { + if (prop->type == type) { + return prop; + } + prop = prop->next; + } + return NULL; +} + static int BrokerSend_Disconnect(BrokerClient* bc, byte reason_code) { int rc; @@ -1477,8 +1759,12 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, /* Client ID uniqueness and clean session handling */ bc->clean_session = mc.clean_session; if (bc->client_id[0] != '\0') { - BrokerClient* old = BrokerClient_FindByClientId(broker, - bc->client_id, bc); + BrokerClient* old; + + /* Cancel any pending will for this client_id (reconnect) */ + BrokerPendingWill_Cancel(broker, bc->client_id); + + old = BrokerClient_FindByClientId(broker, bc->client_id, bc); if (old != NULL) { PRINTF("broker: duplicate client_id=%s, disconnecting " "old sock=%d", bc->client_id, (int)old->sock); @@ -1549,11 +1835,22 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, } bc->will_qos = mc.lwt_msg->qos; bc->will_retain = mc.lwt_msg->retain; + bc->will_delay_sec = 0; +#ifdef WOLFMQTT_V5 + if (mc.lwt_msg->props != NULL) { + MqttProp* prop = BrokerProps_Find(mc.lwt_msg->props, + MQTT_PROP_WILL_DELAY_INTERVAL); + if (prop != NULL) { + bc->will_delay_sec = prop->data_int; + } + } +#endif bc->has_will = 1; PRINTF("broker: LWT stored sock=%d topic=%s qos=%d retain=%d " - "len=%u", (int)bc->sock, bc->will_topic, + "len=%u delay=%u", (int)bc->sock, bc->will_topic, bc->will_qos, bc->will_retain, - (unsigned)bc->will_payload_len); + (unsigned)bc->will_payload_len, + (unsigned)bc->will_delay_sec); } /* Store credentials */ @@ -1864,8 +2161,18 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, BrokerRetained_Delete(broker, topic); } else if (payload != NULL) { + word32 expiry = 0; +#ifdef WOLFMQTT_V5 + if (pub.props != NULL) { + MqttProp* prop = BrokerProps_Find(pub.props, + MQTT_PROP_MSG_EXPIRY_INTERVAL); + if (prop != NULL) { + expiry = prop->data_int; + } + } +#endif (void)BrokerRetained_Store(broker, topic, payload, - (word16)pub.total_len); + (word16)pub.total_len, expiry); } } @@ -2090,27 +2397,17 @@ static int BrokerClient_Process(MqttBroker* broker, BrokerClient* bc) bc->tls_handshake_done = 1; PRINTF("broker: TLS handshake done sock=%d %s", (int)bc->sock, wolfSSL_get_version(bc->client.tls.ssl)); - /* Log client certificate subject if mutual TLS (requires wolfSSL OPENSSL_EXTRA) */ -#if defined(OPENSSL_EXTRA) + /* Log client certificate CN if mutual TLS */ if (broker->tls_ca != NULL) { WOLFSSL_X509* peer = wolfSSL_get_peer_certificate( bc->client.tls.ssl); if (peer != NULL) { - char subject[256]; - wolfSSL_X509_NAME_oneline( - wolfSSL_X509_get_subject_name(peer), subject, - (int)sizeof(subject)); - PRINTF("broker: TLS client cert sock=%d subject=%s", - (int)bc->sock, subject); + char* cn = wolfSSL_X509_get_subjectCN(peer); + PRINTF("broker: TLS client cert sock=%d CN=%s", + (int)bc->sock, cn ? cn : "(unknown)"); wolfSSL_X509_free(peer); } } -#else - if (broker->tls_ca != NULL) { - PRINTF("broker: TLS client cert sock=%d (mutual TLS)", - (int)bc->sock); - } -#endif return 1; /* activity */ } else { @@ -2307,6 +2604,11 @@ int MqttBroker_Step(MqttBroker* broker) } #endif + /* 3. Process pending wills (v5 Will Delay Interval) */ + if (BrokerPendingWill_Process(broker) > 0) { + activity = 1; + } + return activity ? MQTT_CODE_SUCCESS : MQTT_CODE_CONTINUE; } @@ -2393,7 +2695,8 @@ int MqttBroker_Free(MqttBroker* broker) } #endif - /* Clean up retained messages */ + /* Clean up pending wills and retained messages */ + BrokerPendingWill_FreeAll(broker); BrokerRetained_FreeAll(broker); #if defined(ENABLE_MQTT_TLS) && !defined(WOLFMQTT_BROKER_CUSTOM_NET) diff --git a/src/mqtt_packet.c b/src/mqtt_packet.c index 31ff9c4c0..2ec83ffc1 100644 --- a/src/mqtt_packet.c +++ b/src/mqtt_packet.c @@ -38,6 +38,7 @@ static const struct MqttPropMatrix gPropMatrix[] = { { MQTT_PROP_PAYLOAD_FORMAT_IND, MQTT_DATA_TYPE_BYTE, (1 << MQTT_PACKET_TYPE_PUBLISH) }, { MQTT_PROP_MSG_EXPIRY_INTERVAL, MQTT_DATA_TYPE_INT, + (1 << MQTT_PACKET_TYPE_CONNECT) | (1 << MQTT_PACKET_TYPE_PUBLISH) }, { MQTT_PROP_CONTENT_TYPE, MQTT_DATA_TYPE_STRING, (1 << MQTT_PACKET_TYPE_PUBLISH) }, diff --git a/wolfmqtt/mqtt_broker.h b/wolfmqtt/mqtt_broker.h index 7a470001d..7b32d14e0 100644 --- a/wolfmqtt/mqtt_broker.h +++ b/wolfmqtt/mqtt_broker.h @@ -96,6 +96,9 @@ #ifndef BROKER_MAX_PAYLOAD_LEN #define BROKER_MAX_PAYLOAD_LEN 4096 #endif +#ifndef BROKER_MAX_PENDING_WILLS + #define BROKER_MAX_PENDING_WILLS 4 +#endif /* -------------------------------------------------------------------------- */ /* Forward declarations */ @@ -158,6 +161,7 @@ typedef struct BrokerClient { word16 will_payload_len; MqttQoS will_qos; byte will_retain; + word32 will_delay_sec; /* v5 Will Delay Interval (seconds) */ MqttNet net; MqttClient client; struct MqttBroker* broker; /* back-pointer to parent broker context */ @@ -195,8 +199,31 @@ typedef struct BrokerRetainedMsg { struct BrokerRetainedMsg* next; #endif word16 payload_len; + WOLFMQTT_BROKER_TIME_T store_time; /* when stored (seconds) */ + word32 expiry_sec; /* v5 message expiry (0=none) */ } BrokerRetainedMsg; +/* -------------------------------------------------------------------------- */ +/* Pending will messages (v5 Will Delay Interval) */ +/* -------------------------------------------------------------------------- */ +typedef struct BrokerPendingWill { +#ifdef WOLFMQTT_STATIC_MEMORY + byte in_use; + char client_id[BROKER_MAX_CLIENT_ID_LEN]; + char topic[BROKER_MAX_TOPIC_LEN]; + byte payload[BROKER_MAX_PAYLOAD_LEN]; +#else + char* client_id; + char* topic; + byte* payload; + struct BrokerPendingWill* next; +#endif + word16 payload_len; + MqttQoS qos; + byte retain; + WOLFMQTT_BROKER_TIME_T publish_time; /* absolute time to publish */ +} BrokerPendingWill; + /* -------------------------------------------------------------------------- */ /* Broker context */ /* -------------------------------------------------------------------------- */ @@ -220,10 +247,12 @@ typedef struct MqttBroker { BrokerClient clients[BROKER_MAX_CLIENTS]; BrokerSub subs[BROKER_MAX_SUBS]; BrokerRetainedMsg retained[BROKER_MAX_RETAINED]; + BrokerPendingWill pending_wills[BROKER_MAX_PENDING_WILLS]; #else BrokerClient* clients; BrokerSub* subs; BrokerRetainedMsg* retained; + BrokerPendingWill* pending_wills; #endif } MqttBroker; From 90841d0021c756f31c69640d6674779647215db1 Mon Sep 17 00:00:00 2001 From: David Garske Date: Fri, 6 Feb 2026 17:56:30 -0800 Subject: [PATCH 12/13] =?UTF-8?q?Fix=20for=20"error:=20implicit=20declarat?= =?UTF-8?q?ion=20of=20function=20=E2=80=98wolfSSL=5FX509=5Ffree=E2=80=99"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mqtt_broker.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index 6c3bb0aba..e160e7314 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -2397,7 +2397,10 @@ static int BrokerClient_Process(MqttBroker* broker, BrokerClient* bc) bc->tls_handshake_done = 1; PRINTF("broker: TLS handshake done sock=%d %s", (int)bc->sock, wolfSSL_get_version(bc->client.tls.ssl)); - /* Log client certificate CN if mutual TLS */ + /* Log client certificate CN if mutual TLS. + * Requires wolfSSL built with KEEP_PEER_CERT or similar. */ + #if defined(KEEP_PEER_CERT) || defined(OPENSSL_EXTRA) || \ + defined(OPENSSL_EXTRA_X509_SMALL) || defined(SESSION_CERTS) if (broker->tls_ca != NULL) { WOLFSSL_X509* peer = wolfSSL_get_peer_certificate( bc->client.tls.ssl); @@ -2408,6 +2411,7 @@ static int BrokerClient_Process(MqttBroker* broker, BrokerClient* bc) wolfSSL_X509_free(peer); } } + #endif return 1; /* activity */ } else { From 82f89d01d790b1e007e2ec7c4a70818c9a06aaae Mon Sep 17 00:00:00 2001 From: David Garske Date: Fri, 6 Feb 2026 20:08:44 -0800 Subject: [PATCH 13/13] Code size reductions and refactor --- CMakeLists.txt | 28 +++ README.md | 80 +++++++- configure.ac | 41 ++++ scripts/broker.test | 43 +++- src/mqtt_broker.c | 449 ++++++++++++++++++----------------------- wolfmqtt/mqtt_broker.h | 51 ++++- 6 files changed, 428 insertions(+), 264 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b4f64a39..513607c79 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -190,6 +190,34 @@ add_option(WOLFMQTT_BROKER "no" "yes;no") if (WOLFMQTT_BROKER) list(APPEND WOLFMQTT_DEFINITIONS "-DWOLFMQTT_BROKER") + + add_option(WOLFMQTT_BROKER_RETAINED + "Enable broker retained message support" + "yes" "yes;no") + if (NOT WOLFMQTT_BROKER_RETAINED) + list(APPEND WOLFMQTT_DEFINITIONS "-DWOLFMQTT_BROKER_NO_RETAINED") + endif() + + add_option(WOLFMQTT_BROKER_WILL + "Enable broker Last Will and Testament support" + "yes" "yes;no") + if (NOT WOLFMQTT_BROKER_WILL) + list(APPEND WOLFMQTT_DEFINITIONS "-DWOLFMQTT_BROKER_NO_WILL") + endif() + + add_option(WOLFMQTT_BROKER_WILDCARDS + "Enable broker wildcard topic matching" + "yes" "yes;no") + if (NOT WOLFMQTT_BROKER_WILDCARDS) + list(APPEND WOLFMQTT_DEFINITIONS "-DWOLFMQTT_BROKER_NO_WILDCARDS") + endif() + + add_option(WOLFMQTT_BROKER_AUTH + "Enable broker username/password authentication" + "yes" "yes;no") + if (NOT WOLFMQTT_BROKER_AUTH) + list(APPEND WOLFMQTT_DEFINITIONS "-DWOLFMQTT_BROKER_NO_AUTH") + endif() endif() # Note: not adding stress option to cmake build as of yet. stress is for diff --git a/README.md b/README.md index 0658659ad..daf812982 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ We setup an AWS IoT endpoint and testing device certificate for testing. The AWS ### Watson IoT Example This example enables the wolfMQTT client to connect to the IBM Watson Internet of Things (WIOT) Platform. The WIOT Platform has a limited test broker called "Quickstart" that allows non-secure connections to exercise the component. The example is located in `/examples/wiot/`. Works with MQTT v5 support enabled. -**NOTE** The WIOT Platform will be disabled DEC2023. The demo may still be useful for users of IBM Watson IOT. +**NOTE** The WIOT Platform will be disabled DEC2023. The demo may still be useful for users of IBM Watson IOT. ### MQTT-SN Example The Sensor Network client implements the MQTT-SN protocol for low-bandwidth networks. There are several differences from MQTT, including the ability to use a two byte Topic ID instead the full topic during subscribe and publish. The SN client requires an MQTT-SN gateway. The gateway acts as an intermediary between the SN clients and the broker. This client was tested with the Eclipse Paho MQTT-SN Gateway, which connects by default to the public Eclipse broker, much like our wolfMQTT Client example. The address of the gateway must be configured as the host. The example is located in `/examples/sn-client/`. @@ -262,7 +262,7 @@ The initially supported version with full specification support for all features ### MQTT v5.0 Specification Support -The wolfMQTT client supports connecting to v5 enabled brokers when configured with the `--enable-v5` option. +The wolfMQTT client supports connecting to v5 enabled brokers when configured with the `--enable-v5` option. The following v5.0 specification features are supported by the wolfMQTT client: * AUTH packet * User properties @@ -413,6 +413,78 @@ Note: When stress is enabled, the Multithread Example becomes localhost only and will not connect to remote servers. Additionally the test `scripts/stress.test` is added to `make check`, and all other tests are disabled. +## Broker + +wolfMQTT includes a lightweight MQTT broker implementation suitable for embedded and resource-constrained environments. It supports both MQTT v3.1.1 and v5.0 clients, with optional TLS via wolfSSL. + +### Features + +* QoS 0 and QoS 1 publish/subscribe +* Retained messages +* Last Will and Testament (LWT) with v5 Will Delay Interval +* Wildcard subscriptions (`+` and `#`) +* Username/password authentication +* TLS support (requires wolfSSL with `--enable-tls`) +* Clean session handling with subscription persistence +* Keep-alive monitoring with automatic client disconnect +* Unique client ID enforcement (existing session takeover) +* Static memory mode (`WOLFMQTT_STATIC_MEMORY`) for zero-malloc operation + +### Building + +With autotools: + +``` +./configure --enable-broker +make +``` + +With CMake: + +``` +cmake .. -DWOLFMQTT_BROKER=yes +cmake --build . +``` + +### Running + +``` +./src/mqtt_broker -p 1883 +``` + +For TLS: + +``` +./src/mqtt_broker -p 8883 -t -A ca-cert.pem -K server-key.pem -c server-cert.pem +``` + +Run `./src/mqtt_broker -?` to see all available options. + +### Feature Build Options + +All broker features are enabled by default. Individual features can be disabled at build time to reduce code and memory footprint on constrained platforms. + +| Feature | Autotools | CMake | Define | +|---|---|---|---| +| Retained messages | `--disable-broker-retained` | `-DWOLFMQTT_BROKER_RETAINED=no` | `WOLFMQTT_BROKER_NO_RETAINED` | +| Last Will and Testament | `--disable-broker-will` | `-DWOLFMQTT_BROKER_WILL=no` | `WOLFMQTT_BROKER_NO_WILL` | +| Wildcard subscriptions | `--disable-broker-wildcards` | `-DWOLFMQTT_BROKER_WILDCARDS=no` | `WOLFMQTT_BROKER_NO_WILDCARDS` | +| Authentication | `--disable-broker-auth` | `-DWOLFMQTT_BROKER_AUTH=no` | `WOLFMQTT_BROKER_NO_AUTH` | + +### Static Memory Mode + +When built with `WOLFMQTT_STATIC_MEMORY`, the broker uses fixed-size arrays instead of dynamic allocation. Buffer sizes and limits can be tuned via compile-time macros: + +| Macro | Default | Description | +|---|---|---| +| `BROKER_MAX_CLIENTS` | 8 | Maximum concurrent client connections | +| `BROKER_MAX_SUBS` | 32 | Maximum total subscriptions | +| `BROKER_MAX_RETAINED` | 16 | Maximum retained messages | +| `BROKER_RX_BUF_SZ` | 4096 | Per-client receive buffer size | +| `BROKER_TX_BUF_SZ` | 4096 | Per-client transmit buffer size | +| `BROKER_MAX_PAYLOAD_LEN` | 4096 | Maximum retained message payload | +| `BROKER_MAX_WILL_PAYLOAD_LEN` | 256 | Maximum LWT payload size | + ## WebSocket Support wolfMQTT supports MQTT over WebSockets, allowing clients to connect to MQTT brokers through WebSocket endpoints. This is useful for environments where traditional MQTT ports might be blocked or when integrating with web applications. @@ -544,11 +616,11 @@ You can test the wolfMQTT client against public brokers supporting websockets: * Mosquitto unencrypted `./examples/websocket/websocket_client -h test.mosquitto.org -p8080` - + * Mosquitto secure websocket `./examples/websocket/websocket_client -h test.mosquitto.org -p8081 -t` - + * HiveMQ unencrypted `./examples/websocket/websocket_client -h broker.hivemq.com -p8000` diff --git a/configure.ac b/configure.ac index 38ec113fd..f4fd01e28 100644 --- a/configure.ac +++ b/configure.ac @@ -417,6 +417,47 @@ then AM_CFLAGS="$AM_CFLAGS -DWOLFMQTT_BROKER" fi +# Broker feature toggles (only relevant when --enable-broker) +AC_ARG_ENABLE([broker-retained], +[AS_HELP_STRING([--disable-broker-retained],[Disable broker retained message support])], +[ ENABLED_BROKER_RETAINED=$enableval ], +[ ENABLED_BROKER_RETAINED=yes ] +) +if test "x$ENABLED_BROKER_RETAINED" = "xno" +then +AM_CFLAGS="$AM_CFLAGS -DWOLFMQTT_BROKER_NO_RETAINED" +fi + +AC_ARG_ENABLE([broker-will], +[AS_HELP_STRING([--disable-broker-will],[Disable broker Last Will and Testament support])], +[ ENABLED_BROKER_WILL=$enableval ], +[ ENABLED_BROKER_WILL=yes ] +) +if test "x$ENABLED_BROKER_WILL" = "xno" +then +AM_CFLAGS="$AM_CFLAGS -DWOLFMQTT_BROKER_NO_WILL" +fi + +AC_ARG_ENABLE([broker-wildcards], +[AS_HELP_STRING([--disable-broker-wildcards],[Disable broker wildcard subscription support])], +[ ENABLED_BROKER_WILDCARDS=$enableval ], +[ ENABLED_BROKER_WILDCARDS=yes ] +) +if test "x$ENABLED_BROKER_WILDCARDS" = "xno" +then +AM_CFLAGS="$AM_CFLAGS -DWOLFMQTT_BROKER_NO_WILDCARDS" +fi + +AC_ARG_ENABLE([broker-auth], +[AS_HELP_STRING([--disable-broker-auth],[Disable broker username/password authentication])], +[ ENABLED_BROKER_AUTH=$enableval ], +[ ENABLED_BROKER_AUTH=yes ] +) +if test "x$ENABLED_BROKER_AUTH" = "xno" +then +AM_CFLAGS="$AM_CFLAGS -DWOLFMQTT_BROKER_NO_AUTH" +fi + AM_CONDITIONAL([HAVE_LIBWOLFSSL], [test "x$ENABLED_TLS" = "xyes"]) AM_CONDITIONAL([HAVE_LIBCURL], [test "x$ENABLED_CURL" = "xyes"]) diff --git a/scripts/broker.test b/scripts/broker.test index 60c0763e4..617e8141d 100755 --- a/scripts/broker.test +++ b/scripts/broker.test @@ -72,6 +72,17 @@ FAIL=0 echo "=== wolfMQTT Broker Test Suite ===" +# Feature detection via broker help output +broker_features=$(./$broker_bin -h 2>&1 | grep "Features:") +has_auth=no +has_retained=no +has_will=no +has_wildcards=no +echo "$broker_features" | grep -q "auth" && has_auth=yes +echo "$broker_features" | grep -q "retained" && has_retained=yes +echo "$broker_features" | grep -q " will" && has_will=yes +echo "$broker_features" | grep -q "wildcards" && has_wildcards=yes + # Start plain broker for tests 1-6 start_broker @@ -114,6 +125,9 @@ fi # --- Test 4: Retained message --- echo "" echo "--- Test 4: Retained message ---" +if [ "$has_retained" = "no" ]; then + echo "SKIP: Retained message (not built with retained support)" +else # Publish a retained message (-T disables stdin capture for background use) ./$pub_bin -T -h 127.0.0.1 -p $port -n "test/retain" -m "retained_msg" -r \ >"${TMP_DIR}/t4_pub.log" 2>&1 @@ -133,10 +147,14 @@ else echo "FAIL: Retained message" FAIL=1 fi +fi # has_retained # --- Test 5: Last Will and Testament --- echo "" echo "--- Test 5: Last Will and Testament ---" +if [ "$has_will" = "no" ]; then + echo "SKIP: LWT (not built with will support)" +else # LWT topic is hardcoded as "wolfMQTT/example/lwttopic", payload is the client ID # Use mqtt-sub as the LWT watcher # Note: v5 clients set Will Delay Interval=5s, so watcher must stay alive long enough @@ -164,10 +182,14 @@ else echo "FAIL: LWT not delivered" FAIL=1 fi +fi # has_will # --- Test 6: Keep-alive timeout enforcement --- echo "" echo "--- Test 6: Keep-alive enforcement ---" +if [ "$has_will" = "no" ]; then + echo "SKIP: Keep-alive enforcement (not built with will support)" +else # Subscriber watches the LWT topic # Note: v5 clients set Will Delay Interval=5s on top of keepalive timeout timeout 25 ./$sub_bin -T -h 127.0.0.1 -p $port -n "wolfMQTT/example/lwttopic" \ @@ -197,10 +219,14 @@ else echo "FAIL: Keep-alive enforcement" FAIL=1 fi +fi # has_will # --- Test 7: Username/password auth --- echo "" echo "--- Test 7: Username/password auth ---" +if [ "$has_auth" = "no" ]; then + echo "SKIP: Auth (not built with auth support)" +else start_broker -u testuser -P testpass # Without credentials - should be rejected (non-zero exit code) @@ -219,12 +245,11 @@ else echo "FAIL: Auth test (noauth_rc=$T6_NOAUTH_RC, auth_rc=$T6_AUTH_RC)" FAIL=1 fi +fi # has_auth # --- TLS Tests --- has_tls=no -if ./$broker_bin -h 2>&1 | grep -q "\-t"; then - has_tls=yes -fi +echo "$broker_features" | grep -q "tls" && has_tls=yes if [ "$has_tls" = "yes" ]; then # --- Test 8: TLS pub/sub --- @@ -341,6 +366,9 @@ fi # --- Test 13: Wildcard subscription --- echo "" echo "--- Test 13: Wildcard subscription ---" +if [ "$has_wildcards" = "no" ]; then + echo "SKIP: Wildcard subscription (not built with wildcard support)" +else # Test both + and # wildcards (use different client IDs) timeout 10 ./$sub_bin -T -h 127.0.0.1 -p $port -n "test/wild/+" -i "sub_plus" \ >"${TMP_DIR}/t13_plus.log" 2>&1 & @@ -372,6 +400,7 @@ else echo "FAIL: Wildcard (plus=$T13_PLUS_OK, hash=$T13_HASH_OK)" FAIL=1 fi +fi # has_wildcards # --- Test 14: Client ID takeover --- echo "" @@ -410,7 +439,7 @@ echo "--- Test 15: Max client connections ---" # Connect multiple clients simultaneously to verify broker handles them T15_PIDS=() T15_OK=yes -for idx in $(seq 1 8); do +for idx in $(seq 1 6); do timeout 5 ./$sub_bin -T -h 127.0.0.1 -p $port -n "test/maxcli/$idx" \ -i "max_client_$idx" >"${TMP_DIR}/t15_$idx.log" 2>&1 & T15_PIDS+=($!) @@ -428,7 +457,7 @@ for pid in "${T15_PIDS[@]}"; do done TEST_PIDS=() if [ $T15_VERIFY_RC -eq 0 ]; then - echo "PASS: Max client connections (8 concurrent + pub/sub)" + echo "PASS: Max client connections (6 concurrent + pub/sub)" else echo "FAIL: Max client connections (verify_rc=$T15_VERIFY_RC)" FAIL=1 @@ -437,6 +466,9 @@ fi # --- Test 16: Graceful shutdown (SIGTERM) --- echo "" echo "--- Test 16: Graceful shutdown ---" +if [ "$has_will" = "no" ]; then + echo "SKIP: Graceful shutdown (not built with will support)" +else # Verify broker exits cleanly on SIGTERM start_broker # Connect a client with LWT @@ -469,6 +501,7 @@ if grep -q "shutdown_client" "${TMP_DIR}/t16_watcher.log" 2>/dev/null; then else echo "PASS: Graceful shutdown (no LWT on SIGTERM)" fi +fi # has_will # --- Summary --- echo "" diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index e160e7314..b17be7435 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -82,6 +82,38 @@ #define BROKER_LOG_PKT 1 #endif +/* Buffer size accessors - unify static/dynamic code paths */ +#ifdef WOLFMQTT_STATIC_MEMORY + #define BROKER_CLIENT_TX_SZ(bc) BROKER_TX_BUF_SZ + #define BROKER_CLIENT_RX_SZ(bc) BROKER_RX_BUF_SZ +#else + #define BROKER_CLIENT_TX_SZ(bc) ((bc)->tx_buf_len) + #define BROKER_CLIENT_RX_SZ(bc) ((bc)->rx_buf_len) +#endif + +/* String validity check - static arrays vs dynamic pointers */ +#ifdef WOLFMQTT_STATIC_MEMORY + #define BROKER_STR_VALID(s) ((s)[0] != '\0') +#else + #define BROKER_STR_VALID(s) ((s) != NULL) +#endif + +/* No-op stubs when features are compiled out */ +#ifndef WOLFMQTT_BROKER_RETAINED + #define BrokerRetained_Store(b, t, p, l, e) (0) + #define BrokerRetained_Delete(b, t) do {} while(0) + #define BrokerRetained_FreeAll(b) do {} while(0) + #define BrokerRetained_DeliverToClient(b, c, f, q) do {} while(0) +#endif +#ifndef WOLFMQTT_BROKER_WILL + #define BrokerClient_ClearWill(bc) do {} while(0) + #define BrokerClient_PublishWill(b, bc) do {} while(0) + #define BrokerPendingWill_Cancel(b, id) do {} while(0) + #define BrokerPendingWill_Process(b) (0) + #define BrokerPendingWill_FreeAll(b) do {} while(0) +#endif + +#ifdef WOLFMQTT_BROKER_AUTH /* Constant-time string comparison to prevent timing attacks on auth. * Compares all bytes regardless of where differences occur. * Returns 0 if equal, non-zero if different. */ @@ -98,6 +130,45 @@ static int BrokerStrCompare(const char* a, const char* b) result |= (len_a ^ len_b); return result; } +#endif /* WOLFMQTT_BROKER_AUTH */ + +/* Store a string of known length into a BrokerClient field. + * Static mode: copies into fixed-size buffer with truncation. + * Dynamic mode: frees old value, allocates new buffer, copies. */ +#ifdef WOLFMQTT_STATIC_MEMORY +static void BrokerStore_String(char* dst, int max_len, + const char* src, word16 src_len) +{ + if (src_len >= (word16)max_len) { + src_len = (word16)(max_len - 1); + } + XMEMCPY(dst, src, src_len); + dst[src_len] = '\0'; +} +#else +static void BrokerStore_String(char** dst_ptr, + const char* src, word16 src_len) +{ + if (*dst_ptr != NULL) { + WOLFMQTT_FREE(*dst_ptr); + *dst_ptr = NULL; + } + *dst_ptr = (char*)WOLFMQTT_MALLOC(src_len + 1); + if (*dst_ptr != NULL) { + XMEMCPY(*dst_ptr, src, src_len); + (*dst_ptr)[src_len] = '\0'; + } +} +#endif + +/* Wrapper macro to unify static/dynamic calling convention */ +#ifdef WOLFMQTT_STATIC_MEMORY + #define BROKER_STORE_STR(dst, src, len, maxlen) \ + BrokerStore_String(dst, maxlen, src, len) +#else + #define BROKER_STORE_STR(dst, src, len, maxlen) \ + BrokerStore_String(&(dst), src, len) +#endif /* -------------------------------------------------------------------------- */ /* Default POSIX network backend */ @@ -447,18 +518,22 @@ static void BrokerClient_Free(BrokerClient* bc) if (bc->client_id) { WOLFMQTT_FREE(bc->client_id); } +#ifdef WOLFMQTT_BROKER_AUTH if (bc->username) { WOLFMQTT_FREE(bc->username); } if (bc->password) { WOLFMQTT_FREE(bc->password); } +#endif +#ifdef WOLFMQTT_BROKER_WILL if (bc->will_topic) { WOLFMQTT_FREE(bc->will_topic); } if (bc->will_payload) { WOLFMQTT_FREE(bc->will_payload); } +#endif if (bc->tx_buf) { WOLFMQTT_FREE(bc->tx_buf); } @@ -518,15 +593,9 @@ static BrokerClient* BrokerClient_Add(MqttBroker* broker, bc->net.write = BrokerNetWrite; bc->net.disconnect = BrokerNetDisconnect; -#ifdef WOLFMQTT_STATIC_MEMORY - rc = MqttClient_Init(&bc->client, &bc->net, NULL, - bc->tx_buf, BROKER_TX_BUF_SZ, bc->rx_buf, BROKER_RX_BUF_SZ, - BROKER_TIMEOUT_MS); -#else rc = MqttClient_Init(&bc->client, &bc->net, NULL, - bc->tx_buf, bc->tx_buf_len, bc->rx_buf, bc->rx_buf_len, - BROKER_TIMEOUT_MS); -#endif + bc->tx_buf, BROKER_CLIENT_TX_SZ(bc), + bc->rx_buf, BROKER_CLIENT_RX_SZ(bc), BROKER_TIMEOUT_MS); if (rc != MQTT_CODE_SUCCESS) { PRINTF("broker: client init failed rc=%d", rc); BrokerClient_Free(bc); @@ -782,8 +851,8 @@ static BrokerClient* BrokerClient_FindByClientId(MqttBroker* broker, int i; for (i = 0; i < BROKER_MAX_CLIENTS; i++) { BrokerClient* bc = &broker->clients[i]; - if (bc->in_use && bc != exclude && - bc->client_id[0] != '\0' && + if (!bc->in_use) continue; + if (bc != exclude && BROKER_STR_VALID(bc->client_id) && XSTRCMP(bc->client_id, client_id) == 0) { return bc; } @@ -793,7 +862,7 @@ static BrokerClient* BrokerClient_FindByClientId(MqttBroker* broker, { BrokerClient* bc = broker->clients; while (bc) { - if (bc != exclude && bc->client_id != NULL && + if (bc != exclude && BROKER_STR_VALID(bc->client_id) && XSTRCMP(bc->client_id, client_id) == 0) { return bc; } @@ -865,8 +934,8 @@ static void BrokerSubs_ReassociateClient(MqttBroker* broker, int i; for (i = 0; i < BROKER_MAX_SUBS; i++) { BrokerSub* s = &broker->subs[i]; - if (s->in_use && s->client != NULL && - s->client->client_id[0] != '\0' && + if (!s->in_use) continue; + if (s->client != NULL && BROKER_STR_VALID(s->client->client_id) && XSTRCMP(s->client->client_id, client_id) == 0) { s->client = new_bc; } @@ -874,13 +943,13 @@ static void BrokerSubs_ReassociateClient(MqttBroker* broker, } #else { - BrokerSub* cur = broker->subs; - while (cur) { - if (cur->client != NULL && cur->client->client_id != NULL && - XSTRCMP(cur->client->client_id, client_id) == 0) { - cur->client = new_bc; + BrokerSub* s = broker->subs; + while (s) { + if (s->client != NULL && BROKER_STR_VALID(s->client->client_id) && + XSTRCMP(s->client->client_id, client_id) == 0) { + s->client = new_bc; } - cur = cur->next; + s = s->next; } } #endif @@ -889,6 +958,7 @@ static void BrokerSubs_ReassociateClient(MqttBroker* broker, /* -------------------------------------------------------------------------- */ /* Retained message management */ /* -------------------------------------------------------------------------- */ +#ifdef WOLFMQTT_BROKER_RETAINED static int BrokerRetained_Store(MqttBroker* broker, const char* topic, const byte* payload, word16 payload_len, word32 expiry_sec) { @@ -1067,10 +1137,15 @@ static void BrokerRetained_FreeAll(MqttBroker* broker) } #endif } +#endif /* WOLFMQTT_BROKER_RETAINED */ + +/* Forward declaration - used by retained delivery, will publish, and PUBLISH handler */ +static int BrokerTopicMatch(const char* filter, const char* topic); /* -------------------------------------------------------------------------- */ /* LWT (Last Will and Testament) helpers */ /* -------------------------------------------------------------------------- */ +#ifdef WOLFMQTT_BROKER_WILL static void BrokerClient_ClearWill(BrokerClient* bc) { if (bc == NULL) { @@ -1120,11 +1195,13 @@ static int BrokerPendingWill_Add(MqttBroker* broker, BrokerClient* bc) XMEMSET(pw, 0, sizeof(*pw)); pw->in_use = 1; XSTRNCPY(pw->client_id, bc->client_id, BROKER_MAX_CLIENT_ID_LEN - 1); + pw->client_id[BROKER_MAX_CLIENT_ID_LEN - 1] = '\0'; XSTRNCPY(pw->topic, bc->will_topic, BROKER_MAX_TOPIC_LEN - 1); + pw->topic[BROKER_MAX_TOPIC_LEN - 1] = '\0'; if (bc->will_payload_len > 0) { word16 len = bc->will_payload_len; - if (len > BROKER_MAX_PAYLOAD_LEN) { - len = BROKER_MAX_PAYLOAD_LEN; + if (len > BROKER_MAX_WILL_PAYLOAD_LEN) { + len = BROKER_MAX_WILL_PAYLOAD_LEN; } XMEMCPY(pw->payload, bc->will_payload, len); pw->payload_len = len; @@ -1249,11 +1326,11 @@ static void BrokerPendingWill_FreeAll(MqttBroker* broker) #endif } -/* Forward declarations */ -static int BrokerTopicMatch(const char* filter, const char* topic); +#ifdef WOLFMQTT_BROKER_WILL static void BrokerClient_PublishWillImmediate(MqttBroker* broker, const char* topic, const byte* payload, word16 payload_len, MqttQoS qos, byte retain); +#endif /* Process pending wills - publish any that have expired their delay */ static int BrokerPendingWill_Process(MqttBroker* broker) @@ -1318,7 +1395,9 @@ static int BrokerPendingWill_Process(MqttBroker* broker) return activity; } +#endif /* WOLFMQTT_BROKER_WILL */ +#ifdef WOLFMQTT_BROKER_RETAINED static void BrokerRetained_DeliverToClient(MqttBroker* broker, BrokerClient* bc, const char* filter, MqttQoS sub_qos) { @@ -1357,7 +1436,7 @@ static void BrokerRetained_DeliverToClient(MqttBroker* broker, out_pub.protocol_level = bc->protocol_level; #endif enc_rc = MqttEncode_Publish(bc->tx_buf, - BROKER_TX_BUF_SZ, &out_pub, 0); + BROKER_CLIENT_TX_SZ(bc), &out_pub, 0); if (enc_rc > 0) { PRINTF("broker: retained deliver sock=%d topic=%s " "len=%u", (int)bc->sock, rm->topic, @@ -1403,7 +1482,7 @@ static void BrokerRetained_DeliverToClient(MqttBroker* broker, out_pub.protocol_level = bc->protocol_level; #endif enc_rc = MqttEncode_Publish(bc->tx_buf, - bc->tx_buf_len, &out_pub, 0); + BROKER_CLIENT_TX_SZ(bc), &out_pub, 0); if (enc_rc > 0) { PRINTF("broker: retained deliver sock=%d topic=%s " "len=%u", (int)bc->sock, rm->topic, @@ -1417,22 +1496,17 @@ static void BrokerRetained_DeliverToClient(MqttBroker* broker, } #endif } +#endif /* WOLFMQTT_BROKER_RETAINED */ +#ifdef WOLFMQTT_BROKER_WILL static void BrokerClient_PublishWill(MqttBroker* broker, BrokerClient* bc) { if (broker == NULL || bc == NULL || !bc->has_will) { return; } - -#ifdef WOLFMQTT_STATIC_MEMORY - if (bc->will_topic[0] == '\0') { - return; - } -#else - if (bc->will_topic == NULL) { + if (!BROKER_STR_VALID(bc->will_topic)) { return; } -#endif /* v5 Will Delay Interval: defer publication */ if (bc->will_delay_sec > 0) { @@ -1475,48 +1549,17 @@ static void BrokerClient_PublishWillImmediate(MqttBroker* broker, /* Fan out to matching subscribers */ #ifdef WOLFMQTT_STATIC_MEMORY { - int si; - for (si = 0; si < BROKER_MAX_SUBS; si++) { - BrokerSub* sub = &broker->subs[si]; - if (!sub->in_use || sub->client == NULL || - sub->client->protocol_level == 0 || - sub->filter[0] == '\0') { - continue; - } - if (BrokerTopicMatch(sub->filter, topic)) { - MqttPublish out_pub; - MqttQoS eff_qos; - int enc_rc; - XMEMSET(&out_pub, 0, sizeof(out_pub)); - out_pub.topic_name = (char*)topic; - eff_qos = (qos < sub->qos) ? qos : sub->qos; - out_pub.qos = eff_qos; - out_pub.retain = 0; - out_pub.duplicate = 0; - out_pub.buffer = (payload_len > 0) ? (byte*)payload : NULL; - out_pub.total_len = payload_len; - if (eff_qos >= MQTT_QOS_1) { - out_pub.packet_id = BrokerNextPacketId(broker); - } -#ifdef WOLFMQTT_V5 - out_pub.protocol_level = sub->client->protocol_level; -#endif - enc_rc = MqttEncode_Publish(sub->client->tx_buf, - BROKER_TX_BUF_SZ, &out_pub, 0); - if (enc_rc > 0) { - (void)MqttPacket_Write(&sub->client->client, - sub->client->tx_buf, enc_rc); - } - } - } - } + int i; + for (i = 0; i < BROKER_MAX_SUBS; i++) { + BrokerSub* sub = &broker->subs[i]; + if (!sub->in_use) continue; #else { BrokerSub* sub = broker->subs; while (sub) { - if (sub->client != NULL && - sub->client->protocol_level != 0 && - sub->filter != NULL && +#endif + if (sub->client != NULL && sub->client->protocol_level != 0 && + BROKER_STR_VALID(sub->filter) && BrokerTopicMatch(sub->filter, topic)) { MqttPublish out_pub; MqttQoS eff_qos; @@ -1536,21 +1579,24 @@ static void BrokerClient_PublishWillImmediate(MqttBroker* broker, out_pub.protocol_level = sub->client->protocol_level; #endif enc_rc = MqttEncode_Publish(sub->client->tx_buf, - sub->client->tx_buf_len, &out_pub, 0); + BROKER_CLIENT_TX_SZ(sub->client), &out_pub, 0); if (enc_rc > 0) { (void)MqttPacket_Write(&sub->client->client, sub->client->tx_buf, enc_rc); } } +#ifndef WOLFMQTT_STATIC_MEMORY sub = sub->next; +#endif } } -#endif } +#endif /* WOLFMQTT_BROKER_WILL */ /* -------------------------------------------------------------------------- */ /* Topic matching */ /* -------------------------------------------------------------------------- */ +#ifdef WOLFMQTT_BROKER_WILDCARDS static int BrokerTopicMatch(const char* filter, const char* topic) { const char* f = filter; @@ -1591,6 +1637,16 @@ static int BrokerTopicMatch(const char* filter, const char* topic) } return (*f == '\0' && *t == '\0'); } +#else +/* Exact match only when wildcards are disabled */ +static int BrokerTopicMatch(const char* filter, const char* topic) +{ + if (filter == NULL || topic == NULL) { + return 0; + } + return (XSTRCMP(filter, topic) == 0); +} +#endif /* WOLFMQTT_BROKER_WILDCARDS */ /* -------------------------------------------------------------------------- */ /* Packet send helpers */ @@ -1601,15 +1657,9 @@ static int BrokerSend_PingResp(BrokerClient* bc) return MQTT_CODE_ERROR_BAD_ARG; } PRINTF("broker: PINGREQ -> PINGRESP sock=%d", (int)bc->sock); -#ifdef WOLFMQTT_STATIC_MEMORY bc->tx_buf[0] = MQTT_PACKET_TYPE_SET(MQTT_PACKET_TYPE_PING_RESP); bc->tx_buf[1] = 0; return MqttPacket_Write(&bc->client, bc->tx_buf, 2); -#else - bc->tx_buf[0] = MQTT_PACKET_TYPE_SET(MQTT_PACKET_TYPE_PING_RESP); - bc->tx_buf[1] = 0; - return MqttPacket_Write(&bc->client, bc->tx_buf, 2); -#endif } static int BrokerSend_SubAck(BrokerClient* bc, word16 packet_id, @@ -1675,13 +1725,7 @@ static int BrokerSend_Disconnect(BrokerClient* bc, byte reason_code) disc.protocol_level = bc->protocol_level; disc.reason_code = reason_code; - rc = MqttEncode_Disconnect(bc->tx_buf, - #ifdef WOLFMQTT_STATIC_MEMORY - BROKER_TX_BUF_SZ, - #else - bc->tx_buf_len, - #endif - &disc); + rc = MqttEncode_Disconnect(bc->tx_buf, BROKER_CLIENT_TX_SZ(bc), &disc); if (rc > 0) { PRINTF("broker: DISCONNECT send sock=%d reason=0x%02x", (int)bc->sock, reason_code); @@ -1720,45 +1764,26 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, /* Store client ID */ #ifdef WOLFMQTT_STATIC_MEMORY bc->client_id[0] = '\0'; +#endif if (mc.client_id) { word16 id_len = 0; if (MqttDecode_Num((byte*)mc.client_id - MQTT_DATA_LEN_SIZE, &id_len, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) { - if (id_len >= BROKER_MAX_CLIENT_ID_LEN) { - id_len = BROKER_MAX_CLIENT_ID_LEN - 1; - } - XMEMCPY(bc->client_id, mc.client_id, id_len); - bc->client_id[id_len] = '\0'; + BROKER_STORE_STR(bc->client_id, mc.client_id, id_len, + BROKER_MAX_CLIENT_ID_LEN); } } -#else - if (bc->client_id) { - WOLFMQTT_FREE(bc->client_id); - bc->client_id = NULL; - } - if (mc.client_id) { - word16 id_len = 0; - if (MqttDecode_Num((byte*)mc.client_id - MQTT_DATA_LEN_SIZE, - &id_len, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) { - bc->client_id = (char*)WOLFMQTT_MALLOC(id_len + 1); - if (bc->client_id != NULL) { - XMEMCPY(bc->client_id, mc.client_id, id_len); - bc->client_id[id_len] = '\0'; - } - } - } -#endif bc->protocol_level = mc.protocol_level; bc->keep_alive_sec = mc.keep_alive_sec; bc->last_rx = WOLFMQTT_BROKER_GET_TIME_S(); PRINTF("broker: CONNECT proto=%u clean=%d will=%d client_id=%s", mc.protocol_level, mc.clean_session, mc.enable_lwt, - bc->client_id[0] ? bc->client_id : "(null)"); + BROKER_STR_VALID(bc->client_id) ? bc->client_id : "(null)"); /* Client ID uniqueness and clean session handling */ bc->clean_session = mc.clean_session; - if (bc->client_id[0] != '\0') { + if (BROKER_STR_VALID(bc->client_id)) { BrokerClient* old; /* Cancel any pending will for this client_id (reconnect) */ @@ -1796,33 +1821,18 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, /* Store Last Will and Testament */ BrokerClient_ClearWill(bc); +#ifdef WOLFMQTT_BROKER_WILL if (mc.enable_lwt && mc.lwt_msg != NULL) { if (mc.lwt_msg->topic_name != NULL && mc.lwt_msg->topic_name_len > 0) { -#ifdef WOLFMQTT_STATIC_MEMORY - { - word16 wt_len = mc.lwt_msg->topic_name_len; - if (wt_len >= BROKER_MAX_TOPIC_LEN) { - wt_len = BROKER_MAX_TOPIC_LEN - 1; - } - XMEMCPY(bc->will_topic, mc.lwt_msg->topic_name, wt_len); - bc->will_topic[wt_len] = '\0'; - } -#else - bc->will_topic = (char*)WOLFMQTT_MALLOC( - mc.lwt_msg->topic_name_len + 1); - if (bc->will_topic != NULL) { - XMEMCPY(bc->will_topic, mc.lwt_msg->topic_name, - mc.lwt_msg->topic_name_len); - bc->will_topic[mc.lwt_msg->topic_name_len] = '\0'; - } -#endif + BROKER_STORE_STR(bc->will_topic, mc.lwt_msg->topic_name, + mc.lwt_msg->topic_name_len, BROKER_MAX_TOPIC_LEN); } if (mc.lwt_msg->total_len > 0 && mc.lwt_msg->buffer != NULL) { word16 wp_len = (word16)mc.lwt_msg->total_len; #ifdef WOLFMQTT_STATIC_MEMORY - if (wp_len > BROKER_MAX_PAYLOAD_LEN) { - wp_len = BROKER_MAX_PAYLOAD_LEN; + if (wp_len > BROKER_MAX_WILL_PAYLOAD_LEN) { + wp_len = BROKER_MAX_WILL_PAYLOAD_LEN; } XMEMCPY(bc->will_payload, mc.lwt_msg->buffer, wp_len); #else @@ -1852,65 +1862,31 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, (unsigned)bc->will_payload_len, (unsigned)bc->will_delay_sec); } +#endif /* WOLFMQTT_BROKER_WILL */ /* Store credentials */ +#ifdef WOLFMQTT_BROKER_AUTH #ifdef WOLFMQTT_STATIC_MEMORY bc->username[0] = '\0'; - if (mc.username) { - word16 ulen = 0; - if (MqttDecode_Num((byte*)mc.username - MQTT_DATA_LEN_SIZE, - &ulen, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) { - if (ulen >= BROKER_MAX_USERNAME_LEN) { - ulen = BROKER_MAX_USERNAME_LEN - 1; - } - XMEMCPY(bc->username, mc.username, ulen); - bc->username[ulen] = '\0'; - } - } bc->password[0] = '\0'; - if (mc.password) { - word16 plen = 0; - if (MqttDecode_Num((byte*)mc.password - MQTT_DATA_LEN_SIZE, - &plen, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) { - if (plen >= BROKER_MAX_PASSWORD_LEN) { - plen = BROKER_MAX_PASSWORD_LEN - 1; - } - XMEMCPY(bc->password, mc.password, plen); - bc->password[plen] = '\0'; - } - } -#else - if (bc->username) { - WOLFMQTT_FREE(bc->username); - bc->username = NULL; - } - if (bc->password) { - WOLFMQTT_FREE(bc->password); - bc->password = NULL; - } +#endif if (mc.username) { word16 ulen = 0; if (MqttDecode_Num((byte*)mc.username - MQTT_DATA_LEN_SIZE, &ulen, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) { - bc->username = (char*)WOLFMQTT_MALLOC(ulen + 1); - if (bc->username) { - XMEMCPY(bc->username, mc.username, ulen); - bc->username[ulen] = '\0'; - } + BROKER_STORE_STR(bc->username, mc.username, ulen, + BROKER_MAX_USERNAME_LEN); } } if (mc.password) { word16 plen = 0; if (MqttDecode_Num((byte*)mc.password - MQTT_DATA_LEN_SIZE, &plen, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) { - bc->password = (char*)WOLFMQTT_MALLOC(plen + 1); - if (bc->password) { - XMEMCPY(bc->password, mc.password, plen); - bc->password[plen] = '\0'; - } + BROKER_STORE_STR(bc->password, mc.password, plen, + BROKER_MAX_PASSWORD_LEN); } } -#endif +#endif /* WOLFMQTT_BROKER_AUTH */ /* Check auth before sending CONNACK */ ack.flags = 0; @@ -1920,6 +1896,7 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, ack.props = NULL; #endif +#ifdef WOLFMQTT_BROKER_AUTH if (broker->auth_user || broker->auth_pass) { int auth_ok = 1; if (broker->auth_user && (bc->username == NULL || @@ -1947,14 +1924,9 @@ static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, } } } +#endif /* WOLFMQTT_BROKER_AUTH */ - rc = MqttEncode_ConnectAck(bc->tx_buf, -#ifdef WOLFMQTT_STATIC_MEMORY - BROKER_TX_BUF_SZ, -#else - bc->tx_buf_len, -#endif - &ack); + rc = MqttEncode_ConnectAck(bc->tx_buf, BROKER_CLIENT_TX_SZ(bc), &ack); if (rc > 0) { PRINTF("broker: CONNACK send sock=%d code=%d", (int)bc->sock, ack.return_code); @@ -2021,6 +1993,7 @@ static int BrokerHandle_Subscribe(BrokerClient* bc, int rx_len, &flen, MQTT_DATA_LEN_SIZE) == MQTT_DATA_LEN_SIZE) { (void)BrokerSubs_Add(broker, bc, f, flen, topic_qos); +#ifdef WOLFMQTT_BROKER_RETAINED /* Deliver retained messages matching this filter */ { char filter_z[BROKER_MAX_FILTER_LEN]; @@ -2033,6 +2006,7 @@ static int BrokerHandle_Subscribe(BrokerClient* bc, int rx_len, BrokerRetained_DeliverToClient(broker, bc, filter_z, topic_qos); } +#endif } return_codes[i] = (byte)granted_qos; } @@ -2100,12 +2074,7 @@ static int BrokerHandle_Unsubscribe(BrokerClient* bc, int rx_len, } #endif rc = MqttEncode_UnsubscribeAck(bc->tx_buf, -#ifdef WOLFMQTT_STATIC_MEMORY - BROKER_TX_BUF_SZ, -#else - bc->tx_buf_len, -#endif - &ack); + BROKER_CLIENT_TX_SZ(bc), &ack); if (rc > 0) { PRINTF("broker: UNSUBACK send sock=%d packet_id=%u", (int)bc->sock, ack.packet_id); @@ -2155,6 +2124,7 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, } } +#ifdef WOLFMQTT_BROKER_RETAINED /* Handle retained messages */ if (topic != NULL && pub.retain) { if (pub.total_len == 0) { @@ -2175,54 +2145,25 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, (word16)pub.total_len, expiry); } } +#endif /* WOLFMQTT_BROKER_RETAINED */ if (topic != NULL && (payload != NULL || pub.total_len == 0)) { /* Fan out to matching subscribers */ #ifdef WOLFMQTT_STATIC_MEMORY - int si; - for (si = 0; si < BROKER_MAX_SUBS; si++) { - BrokerSub* sub = &broker->subs[si]; - MqttQoS eff_qos; - if (!sub->in_use) { - continue; - } - if (sub->client && sub->client->protocol_level != 0 && - sub->filter[0] != '\0' && - BrokerTopicMatch(sub->filter, topic)) { - MqttPublish out_pub; - XMEMSET(&out_pub, 0, sizeof(out_pub)); - out_pub.topic_name = topic; - /* Effective QoS = min(publish_qos, sub_qos) */ - eff_qos = (pub.qos < sub->qos) ? pub.qos : sub->qos; - out_pub.qos = eff_qos; - if (eff_qos >= MQTT_QOS_1) { - out_pub.packet_id = BrokerNextPacketId(broker); - } - out_pub.retain = 0; /* not retained on forward */ - out_pub.duplicate = 0; - out_pub.buffer = payload; - out_pub.total_len = pub.total_len; -#ifdef WOLFMQTT_V5 - out_pub.protocol_level = sub->client->protocol_level; -#endif - rc = MqttEncode_Publish(sub->client->tx_buf, - BROKER_TX_BUF_SZ, &out_pub, 0); - if (rc > 0) { - PRINTF("broker: PUBLISH fwd sock=%d -> sock=%d " - "topic=%s qos=%d len=%u", - (int)bc->sock, (int)sub->client->sock, - topic, eff_qos, (unsigned)pub.total_len); - (void)MqttPacket_Write(&sub->client->client, - sub->client->tx_buf, rc); - } - } - } + { + int i; + for (i = 0; i < BROKER_MAX_SUBS; i++) { + BrokerSub* sub = &broker->subs[i]; + if (!sub->in_use) continue; #else { BrokerSub* sub = broker->subs; while (sub) { - if (sub->client && sub->client->protocol_level != 0 && - sub->filter && BrokerTopicMatch(sub->filter, topic)) { +#endif + if (sub->client != NULL && + sub->client->protocol_level != 0 && + BROKER_STR_VALID(sub->filter) && + BrokerTopicMatch(sub->filter, topic)) { MqttPublish out_pub; MqttQoS eff_qos; XMEMSET(&out_pub, 0, sizeof(out_pub)); @@ -2240,7 +2181,7 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, out_pub.protocol_level = sub->client->protocol_level; #endif rc = MqttEncode_Publish(sub->client->tx_buf, - sub->client->tx_buf_len, &out_pub, 0); + BROKER_CLIENT_TX_SZ(sub->client), &out_pub, 0); if (rc > 0) { PRINTF("broker: PUBLISH fwd sock=%d -> sock=%d " "topic=%s qos=%d len=%u", @@ -2250,10 +2191,11 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, sub->client->tx_buf, rc); } } +#ifndef WOLFMQTT_STATIC_MEMORY sub = sub->next; +#endif } } -#endif } if (pub.qos == MQTT_QOS_1 || pub.qos == MQTT_QOS_2) { @@ -2264,12 +2206,7 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, resp.reason_code = MQTT_REASON_SUCCESS; resp.props = NULL; #endif - rc = MqttEncode_PublishResp(bc->tx_buf, -#ifdef WOLFMQTT_STATIC_MEMORY - BROKER_TX_BUF_SZ, -#else - bc->tx_buf_len, -#endif + rc = MqttEncode_PublishResp(bc->tx_buf, BROKER_CLIENT_TX_SZ(bc), (pub.qos == MQTT_QOS_1) ? MQTT_PACKET_TYPE_PUBLISH_ACK : MQTT_PACKET_TYPE_PUBLISH_REC, &resp); if (rc > 0) { @@ -2315,12 +2252,7 @@ static int BrokerHandle_PublishRel(BrokerClient* bc, int rx_len) resp.reason_code = MQTT_REASON_SUCCESS; resp.props = NULL; #endif - rc = MqttEncode_PublishResp(bc->tx_buf, -#ifdef WOLFMQTT_STATIC_MEMORY - BROKER_TX_BUF_SZ, -#else - bc->tx_buf_len, -#endif + rc = MqttEncode_PublishResp(bc->tx_buf, BROKER_CLIENT_TX_SZ(bc), MQTT_PACKET_TYPE_PUBLISH_COMP, &resp); if (rc > 0) { PRINTF("broker: PUBCOMP send sock=%d packet_id=%u", @@ -2358,12 +2290,7 @@ static int BrokerHandle_PublishRec(BrokerClient* bc, int rx_len) resp.reason_code = MQTT_REASON_SUCCESS; resp.props = NULL; #endif - rc = MqttEncode_PublishResp(bc->tx_buf, -#ifdef WOLFMQTT_STATIC_MEMORY - BROKER_TX_BUF_SZ, -#else - bc->tx_buf_len, -#endif + rc = MqttEncode_PublishResp(bc->tx_buf, BROKER_CLIENT_TX_SZ(bc), MQTT_PACKET_TYPE_PUBLISH_REL, &resp); if (rc > 0) { PRINTF("broker: PUBREL send sock=%d packet_id=%u", @@ -2430,11 +2357,7 @@ static int BrokerClient_Process(MqttBroker* broker, BrokerClient* bc) #endif /* Try non-blocking read (timeout=0) */ -#ifdef WOLFMQTT_STATIC_MEMORY - rc = MqttPacket_Read(&bc->client, bc->rx_buf, BROKER_RX_BUF_SZ, 0); -#else - rc = MqttPacket_Read(&bc->client, bc->rx_buf, bc->rx_buf_len, 0); -#endif + rc = MqttPacket_Read(&bc->client, bc->rx_buf, BROKER_CLIENT_RX_SZ(bc), 0); if (rc == MQTT_CODE_ERROR_TIMEOUT || rc == MQTT_CODE_CONTINUE) { /* No data available - not an error */ @@ -2646,10 +2569,12 @@ int MqttBroker_Run(MqttBroker* broker) { PRINTF("broker: listening on port %d (no TLS)", broker->port); } +#ifdef WOLFMQTT_BROKER_AUTH if (broker->auth_user || broker->auth_pass) { PRINTF("broker: auth enabled user=%s", broker->auth_user ? broker->auth_user : "(null)"); } +#endif broker->running = 1; while (broker->running) { @@ -2721,7 +2646,10 @@ int MqttBroker_Free(MqttBroker* broker) /* -------------------------------------------------------------------------- */ static void BrokerUsage(const char* prog) { - PRINTF("usage: %s [-p port] [-u user] [-P pass]" + PRINTF("usage: %s [-p port]" +#ifdef WOLFMQTT_BROKER_AUTH + " [-u user] [-P pass]" +#endif #ifdef ENABLE_MQTT_TLS " [-t] [-V ver] [-c cert] [-K key] [-A ca]" #endif @@ -2733,6 +2661,23 @@ static void BrokerUsage(const char* prog) PRINTF(" -K Server private key file (PEM)"); PRINTF(" -A CA certificate for mutual TLS (PEM)"); #endif + PRINTF("Features:" +#ifdef WOLFMQTT_BROKER_RETAINED + " retained" +#endif +#ifdef WOLFMQTT_BROKER_WILL + " will" +#endif +#ifdef WOLFMQTT_BROKER_WILDCARDS + " wildcards" +#endif +#ifdef WOLFMQTT_BROKER_AUTH + " auth" +#endif +#ifdef ENABLE_MQTT_TLS + " tls" +#endif + ); } static MqttBroker* g_broker = NULL; @@ -2781,12 +2726,14 @@ int wolfmqtt_broker(int argc, char** argv) if (XSTRCMP(argv[i], "-p") == 0 && i + 1 < argc) { broker.port = (word16)XATOI(argv[++i]); } +#ifdef WOLFMQTT_BROKER_AUTH else if (XSTRCMP(argv[i], "-u") == 0 && i + 1 < argc) { broker.auth_user = argv[++i]; } else if (XSTRCMP(argv[i], "-P") == 0 && i + 1 < argc) { broker.auth_pass = argv[++i]; } +#endif #ifdef ENABLE_MQTT_TLS else if (XSTRCMP(argv[i], "-t") == 0) { broker.use_tls = 1; diff --git a/wolfmqtt/mqtt_broker.h b/wolfmqtt/mqtt_broker.h index 7b32d14e0..be2558ff1 100644 --- a/wolfmqtt/mqtt_broker.h +++ b/wolfmqtt/mqtt_broker.h @@ -70,10 +70,10 @@ /* Static allocation limits */ #ifndef BROKER_MAX_CLIENTS - #define BROKER_MAX_CLIENTS 16 + #define BROKER_MAX_CLIENTS 8 #endif #ifndef BROKER_MAX_SUBS - #define BROKER_MAX_SUBS 64 + #define BROKER_MAX_SUBS 32 #endif #ifndef BROKER_MAX_CLIENT_ID_LEN #define BROKER_MAX_CLIENT_ID_LEN 64 @@ -96,10 +96,29 @@ #ifndef BROKER_MAX_PAYLOAD_LEN #define BROKER_MAX_PAYLOAD_LEN 4096 #endif +#ifndef BROKER_MAX_WILL_PAYLOAD_LEN + #define BROKER_MAX_WILL_PAYLOAD_LEN 256 +#endif #ifndef BROKER_MAX_PENDING_WILLS #define BROKER_MAX_PENDING_WILLS 4 #endif +/* -------------------------------------------------------------------------- */ +/* Feature toggles (opt-out: define WOLFMQTT_BROKER_NO_xxx to disable) */ +/* -------------------------------------------------------------------------- */ +#ifndef WOLFMQTT_BROKER_NO_RETAINED + #define WOLFMQTT_BROKER_RETAINED +#endif +#ifndef WOLFMQTT_BROKER_NO_WILL + #define WOLFMQTT_BROKER_WILL +#endif +#ifndef WOLFMQTT_BROKER_NO_WILDCARDS + #define WOLFMQTT_BROKER_WILDCARDS +#endif +#ifndef WOLFMQTT_BROKER_NO_AUTH + #define WOLFMQTT_BROKER_AUTH +#endif + /* -------------------------------------------------------------------------- */ /* Forward declarations */ /* -------------------------------------------------------------------------- */ @@ -134,22 +153,30 @@ typedef struct BrokerClient { #ifdef WOLFMQTT_STATIC_MEMORY byte in_use; char client_id[BROKER_MAX_CLIENT_ID_LEN]; +#ifdef WOLFMQTT_BROKER_AUTH char username[BROKER_MAX_USERNAME_LEN]; char password[BROKER_MAX_PASSWORD_LEN]; +#endif byte tx_buf[BROKER_TX_BUF_SZ]; byte rx_buf[BROKER_RX_BUF_SZ]; +#ifdef WOLFMQTT_BROKER_WILL char will_topic[BROKER_MAX_TOPIC_LEN]; - byte will_payload[BROKER_MAX_PAYLOAD_LEN]; + byte will_payload[BROKER_MAX_WILL_PAYLOAD_LEN]; +#endif #else char* client_id; +#ifdef WOLFMQTT_BROKER_AUTH char* username; char* password; +#endif byte* tx_buf; byte* rx_buf; int tx_buf_len; int rx_buf_len; +#ifdef WOLFMQTT_BROKER_WILL char* will_topic; byte* will_payload; +#endif struct BrokerClient* next; #endif BROKER_SOCKET_T sock; @@ -157,11 +184,13 @@ typedef struct BrokerClient { word16 keep_alive_sec; WOLFMQTT_BROKER_TIME_T last_rx; byte clean_session; +#ifdef WOLFMQTT_BROKER_WILL byte has_will; word16 will_payload_len; MqttQoS will_qos; byte will_retain; word32 will_delay_sec; /* v5 Will Delay Interval (seconds) */ +#endif MqttNet net; MqttClient client; struct MqttBroker* broker; /* back-pointer to parent broker context */ @@ -188,6 +217,7 @@ typedef struct BrokerSub { /* -------------------------------------------------------------------------- */ /* Retained message store */ /* -------------------------------------------------------------------------- */ +#ifdef WOLFMQTT_BROKER_RETAINED typedef struct BrokerRetainedMsg { #ifdef WOLFMQTT_STATIC_MEMORY byte in_use; @@ -202,16 +232,18 @@ typedef struct BrokerRetainedMsg { WOLFMQTT_BROKER_TIME_T store_time; /* when stored (seconds) */ word32 expiry_sec; /* v5 message expiry (0=none) */ } BrokerRetainedMsg; +#endif /* WOLFMQTT_BROKER_RETAINED */ /* -------------------------------------------------------------------------- */ /* Pending will messages (v5 Will Delay Interval) */ /* -------------------------------------------------------------------------- */ +#ifdef WOLFMQTT_BROKER_WILL typedef struct BrokerPendingWill { #ifdef WOLFMQTT_STATIC_MEMORY byte in_use; char client_id[BROKER_MAX_CLIENT_ID_LEN]; char topic[BROKER_MAX_TOPIC_LEN]; - byte payload[BROKER_MAX_PAYLOAD_LEN]; + byte payload[BROKER_MAX_WILL_PAYLOAD_LEN]; #else char* client_id; char* topic; @@ -223,6 +255,7 @@ typedef struct BrokerPendingWill { byte retain; WOLFMQTT_BROKER_TIME_T publish_time; /* absolute time to publish */ } BrokerPendingWill; +#endif /* WOLFMQTT_BROKER_WILL */ /* -------------------------------------------------------------------------- */ /* Broker context */ @@ -231,8 +264,10 @@ typedef struct MqttBroker { BROKER_SOCKET_T listen_sock; word16 port; int running; +#ifdef WOLFMQTT_BROKER_AUTH const char* auth_user; const char* auth_pass; +#endif MqttBrokerNet net; word16 next_packet_id; #ifdef ENABLE_MQTT_TLS @@ -246,14 +281,22 @@ typedef struct MqttBroker { #ifdef WOLFMQTT_STATIC_MEMORY BrokerClient clients[BROKER_MAX_CLIENTS]; BrokerSub subs[BROKER_MAX_SUBS]; +#ifdef WOLFMQTT_BROKER_RETAINED BrokerRetainedMsg retained[BROKER_MAX_RETAINED]; +#endif +#ifdef WOLFMQTT_BROKER_WILL BrokerPendingWill pending_wills[BROKER_MAX_PENDING_WILLS]; +#endif #else BrokerClient* clients; BrokerSub* subs; +#ifdef WOLFMQTT_BROKER_RETAINED BrokerRetainedMsg* retained; +#endif +#ifdef WOLFMQTT_BROKER_WILL BrokerPendingWill* pending_wills; #endif +#endif } MqttBroker; /* -------------------------------------------------------------------------- */