diff --git a/.github/workflows/broker-check.yml b/.github/workflows/broker-check.yml new file mode 100644 index 00000000..7018c42c --- /dev/null +++ b/.github/workflows/broker-check.yml @@ -0,0 +1,77 @@ +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: "" + 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 + 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 ${{ matrix.wolfmqtt_opts }} 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/.gitignore b/.gitignore index 8628c648..0a60f1e6 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/CMakeLists.txt b/CMakeLists.txt index fe13be5d..513607c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,6 +184,42 @@ 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") + + 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 # testing only and requires the scripts/ dir to be useful. @@ -286,6 +322,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 +370,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/README.md b/README.md index 0658659a..daf81298 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 3a815ead..f4fd01e2 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,60 @@ 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 + +# 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"]) AM_CONDITIONAL([BUILD_STRESS], [test "x$ENABLED_STRESS" != "xno"]) @@ -415,6 +469,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 @@ -542,3 +598,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/examples/mqttclient/mqttclient.c b/examples/mqttclient/mqttclient.c index bcdde2ba..1fea238b 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; @@ -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 ecf5ad87..d10c7298 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; @@ -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 158f2810..9c67717e 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 new file mode 100755 index 00000000..617e8141 --- /dev/null +++ b/scripts/broker.test @@ -0,0 +1,516 @@ +#!/bin/bash + +# wolfMQTT Broker Test Suite +# Uses only wolfMQTT tools (mqttclient, mqtt-pub, mqtt-sub) - no external dependencies + +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 +broker_pid=$no_pid + +do_cleanup() { + if [ $broker_pid != $no_pid ]; then + kill $broker_pid 2>/dev/null + wait $broker_pid 2>/dev/null || true + broker_pid=$no_pid + fi + 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 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 + +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 +} + +check_broker() { + local check_port=${1:-$port} + timeout 10 sh -c 'until nc -z $0 $1 2>/dev/null; do sleep 1; done' localhost $check_port +} + +# Start broker helper: kills existing broker, generates port, starts new one +start_broker() { + if [ $broker_pid != $no_pid ]; then + kill $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)" +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 + +# --- Test 1: Basic pub/sub --- +echo "" +echo "--- Test 1: Basic pub/sub ---" +./$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 "FAIL: Basic pub/sub" + FAIL=1 +fi + +# --- Test 2: QoS 1 pub/sub --- +echo "" +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 "FAIL: QoS 1 pub/sub" + FAIL=1 +fi + +# --- Test 3: QoS 2 pub/sub --- +echo "" +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 ---" +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 +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}/t4_sub.log" 2>&1 & +T4_PID=$! +TEST_PIDS+=($T4_PID) +sleep 2 +kill $T4_PID 2>/dev/null +wait $T4_PID 2>/dev/null || true +TEST_PIDS=() +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 +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 +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) +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 8 +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 "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" \ + >"${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 * 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 +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 +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) +./$client_bin -T -h 127.0.0.1 -p $port -n "test/auth" -C 5000 \ + >"${TMP_DIR}/t6_noauth.log" 2>&1 +T6_NOAUTH_RC=$? + +# 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}/t6_auth.log" 2>&1 +T6_AUTH_RC=$? + +if [ $T6_NOAUTH_RC -ne 0 ] && [ $T6_AUTH_RC -eq 0 ]; then + echo "PASS: Auth rejection and acceptance" +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 +echo "$broker_features" | grep -q "tls" && has_tls=yes + +if [ "$has_tls" = "yes" ]; then + # --- Test 8: TLS pub/sub --- + echo "" + echo "--- Test 8: TLS pub/sub ---" + start_broker -t \ + -c scripts/broker_test/server-cert.pem \ + -K scripts/broker_test/server-key.pem + + ./$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 + + # --- Test 9: Mutual TLS (RSA) --- + echo "" + 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 \ + -K scripts/broker_test/server-key.pem \ + -A certs/client-cert.pem + + ./$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 + echo "FAIL: Mutual TLS (RSA)" + FAIL=1 + fi + else + echo "SKIP: RSA client certs not found" + fi + + # --- Test 10: Mutual TLS (ECC) --- + echo "" + 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 \ + -K scripts/broker_test/server-key.pem \ + -A certs/client-ecc-cert.pem + + ./$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 "SKIP: ECC client certs not found" + fi +else + echo "" + 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 ---" +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 & +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 +fi # has_wildcards + +# --- 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 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+=($!) +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 (6 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 ---" +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 +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 +fi # has_will + +# --- Summary --- +echo "" +if [ $FAIL -ne 0 ]; then + KEEP_LOGS=1 + echo "MQTT Broker Tests FAILED" + do_cleanup "-1" +fi + +do_cleanup "0" +echo "MQTT Broker Tests Passed" +exit 0 diff --git a/scripts/include.am b/scripts/include.am index 2504880c..d7036b76 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/include.am b/src/include.am index 08f9cd16..14bcb243 100644 --- a/src/include.am +++ b/src/include.am @@ -19,3 +19,13 @@ src_libwolfmqtt_la_LDFLAGS = ${AM_LDFLAGS} -no-undefined -version-info ${WO 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 = $(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 +endif diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c new file mode 100644 index 00000000..b17be743 --- /dev/null +++ b/src/mqtt_broker.c @@ -0,0 +1,2800 @@ +/* mqtt_broker.c + * + * Copyright (C) 2006-2025 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" +#include "wolfmqtt/mqtt_client.h" +#include "wolfmqtt/mqtt_packet.h" +#include "wolfmqtt/mqtt_socket.h" + +#include +#include + +#ifdef WOLFMQTT_BROKER + +/* -------------------------------------------------------------------------- */ +/* Platform includes - only for default POSIX backend */ +/* -------------------------------------------------------------------------- */ +#ifndef WOLFMQTT_BROKER_CUSTOM_NET + #include + #include + #include + #include + #include + #include + #include + #include +#ifdef ENABLE_MQTT_TLS + #include +#endif +#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 + +/* 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. */ +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; +} +#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 */ +/* -------------------------------------------------------------------------- */ +#ifndef WOLFMQTT_BROKER_CUSTOM_NET + +static int BrokerPosix_SetNonBlocking(BROKER_SOCKET_T 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 BrokerPosix_Listen(void* ctx, BROKER_SOCKET_T* sock, + word16 port, int backlog) +{ + 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 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) +{ + fd_set rfds; + struct timeval tv; + int rc; + (void)ctx; + + if (buf == NULL || buf_len <= 0) { + return MQTT_CODE_ERROR_BAD_ARG; + } + + FD_ZERO(&rfds); + FD_SET(sock, &rfds); + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + + rc = select(sock + 1, &rfds, NULL, NULL, &tv); + if (rc == 0) { + return MQTT_CODE_ERROR_TIMEOUT; + } + if (rc < 0) { + return MQTT_CODE_ERROR_NETWORK; + } + + 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 sock=%d rc=%d errno=%d", + (int)sock, rc, errno); + return MQTT_CODE_ERROR_NETWORK; + } + return rc; +} + +static int BrokerPosix_Write(void* ctx, BROKER_SOCKET_T sock, + const byte* buf, int buf_len, int timeout_ms) +{ + fd_set wfds; + struct timeval tv; + int rc; + (void)ctx; + + if (buf == NULL || buf_len <= 0) { + return MQTT_CODE_ERROR_BAD_ARG; + } + + FD_ZERO(&wfds); + FD_SET(sock, &wfds); + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + + rc = select(sock + 1, NULL, &wfds, NULL, &tv); + if (rc == 0) { + return MQTT_CODE_ERROR_TIMEOUT; + } + if (rc < 0) { + return MQTT_CODE_ERROR_NETWORK; + } + + 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 sock=%d rc=%d errno=%d", + (int)sock, rc, errno); + return MQTT_CODE_ERROR_NETWORK; + } + 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; +} + +#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; + } + + /* 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(); + 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 */ + +/* -------------------------------------------------------------------------- */ +/* 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 != 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) { + return; + } + (void)BrokerNetDisconnect(bc); +#ifdef ENABLE_MQTT_TLS + if (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; + } +#endif + 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); + } +#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); + } + if (bc->rx_buf) { + WOLFMQTT_FREE(bc->rx_buf); + } + WOLFMQTT_FREE(bc); +#endif +} + +static BrokerClient* BrokerClient_Add(MqttBroker* broker, + BROKER_SOCKET_T sock) +{ + 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(*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); + bc->rx_buf = (byte*)WOLFMQTT_MALLOC(bc->rx_buf_len); + if (bc->tx_buf == NULL || bc->rx_buf == NULL) { + 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; + bc->net.read = BrokerNetRead; + bc->net.write = BrokerNetWrite; + bc->net.disconnect = BrokerNetDisconnect; + + rc = MqttClient_Init(&bc->client, &bc->net, NULL, + 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); + 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; + broker->clients = bc; +#endif + + return bc; +} + +static void BrokerClient_Remove(MqttBroker* broker, BrokerClient* bc) +{ + if (broker == NULL || bc == NULL) { + return; + } + +#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; + } + prev = cur; + cur = cur->next; + } + } +#endif + BrokerClient_Free(bc); +} + +/* -------------------------------------------------------------------------- */ +/* Subscription management */ +/* -------------------------------------------------------------------------- */ +static void BrokerSubs_RemoveClient(MqttBroker* broker, BrokerClient* bc) +{ +#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) { + BrokerSub* next = cur->next; + if (cur->client == bc) { + 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 int BrokerSubs_Add(MqttBroker* broker, BrokerClient* bc, + 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; + 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(*sub)); + 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->next = broker->subs; + broker->subs = sub; +#endif + + sub->client = bc; + sub->qos = qos; + PRINTF("broker: sub add sock=%d filter=%s qos=%d", + (int)bc->sock, sub->filter, qos); + return MQTT_CODE_SUCCESS; +} + +static void BrokerSubs_Remove(MqttBroker* broker, BrokerClient* bc, + const char* filter, word16 filter_len) +{ +#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) { + 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 { + broker->subs = next; + } + PRINTF("broker: sub remove sock=%d filter=%s", + (int)bc->sock, cur->filter); + WOLFMQTT_FREE(cur->filter); + WOLFMQTT_FREE(cur); + return; + } + prev = cur; + cur = next; + } +#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) continue; + if (bc != exclude && BROKER_STR_VALID(bc->client_id) && + XSTRCMP(bc->client_id, client_id) == 0) { + return bc; + } + } + } +#else + { + BrokerClient* bc = broker->clients; + while (bc) { + if (bc != exclude && BROKER_STR_VALID(bc->client_id) && + 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) continue; + if (s->client != NULL && BROKER_STR_VALID(s->client->client_id) && + XSTRCMP(s->client->client_id, client_id) == 0) { + s->client = new_bc; + } + } + } +#else + { + 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; + } + s = s->next; + } + } +#endif +} + +/* -------------------------------------------------------------------------- */ +/* 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) +{ + 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 + + 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; +} + +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 +} +#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) { + return; + } + 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'; +#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 +} + +/* -------------------------------------------------------------------------- */ +/* 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); + 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_WILL_PAYLOAD_LEN) { + len = BROKER_MAX_WILL_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 +} + +#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) +{ + 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; +} +#endif /* WOLFMQTT_BROKER_WILL */ + +#ifdef WOLFMQTT_BROKER_RETAINED +static void BrokerRetained_DeliverToClient(MqttBroker* broker, + BrokerClient* bc, const char* filter, MqttQoS sub_qos) +{ + 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 + { + int i; + for (i = 0; i < BROKER_MAX_RETAINED; i++) { + BrokerRetainedMsg* rm = &broker->retained[i]; + 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; + 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_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, + (unsigned)rm->payload_len); + (void)MqttPacket_Write(&bc->client, bc->tx_buf, enc_rc); + } + } + } + } +#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; + 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_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, + (unsigned)rm->payload_len); + (void)MqttPacket_Write(&bc->client, bc->tx_buf, enc_rc); + } + } + rm_prev = rm; + rm = rm_next; + } + } +#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; + } + if (!BROKER_STR_VALID(bc->will_topic)) { + return; + } + + /* 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 (retain) { + if (payload_len == 0) { + BrokerRetained_Delete(broker, topic); + } + else { + (void)BrokerRetained_Store(broker, topic, payload, + payload_len, 0); + } + } + + /* Fan out to matching subscribers */ +#ifdef WOLFMQTT_STATIC_MEMORY + { + 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) { +#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; + 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_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 /* WOLFMQTT_BROKER_WILL */ + +/* -------------------------------------------------------------------------- */ +/* Topic matching */ +/* -------------------------------------------------------------------------- */ +#ifdef WOLFMQTT_BROKER_WILDCARDS +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'); +} +#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 */ +/* -------------------------------------------------------------------------- */ +static int BrokerSend_PingResp(BrokerClient* bc) +{ + if (bc == NULL) { + return MQTT_CODE_ERROR_BAD_ARG; + } + PRINTF("broker: PINGREQ -> PINGRESP sock=%d", (int)bc->sock); + 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 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) { + 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); +} + +#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; + 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, BROKER_CLIENT_TX_SZ(bc), &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 */ +/* -------------------------------------------------------------------------- */ + +/* Returns: > 0 success, 0 auth rejected (CONNACK sent with refused), + * < 0 error */ +static int BrokerHandle_Connect(BrokerClient* bc, int rx_len, + MqttBroker* broker) +{ + 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 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); + return rc; + } + + /* 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) { + BROKER_STORE_STR(bc->client_id, mc.client_id, id_len, + BROKER_MAX_CLIENT_ID_LEN); + } + } + + 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, + BROKER_STR_VALID(bc->client_id) ? bc->client_id : "(null)"); + + /* Client ID uniqueness and clean session handling */ + bc->clean_session = mc.clean_session; + if (BROKER_STR_VALID(bc->client_id)) { + 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); + /* Publish old client's will on takeover */ +#ifdef WOLFMQTT_V5 + if (old->protocol_level < MQTT_CONNECT_PROTOCOL_LEVEL_5) { + BrokerClient_PublishWill(broker, old); + } + else { + BrokerSend_Disconnect(old, + MQTT_REASON_SESSION_TAKEN_OVER); + 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); +#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) { + 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_WILL_PAYLOAD_LEN) { + wp_len = BROKER_MAX_WILL_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->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 delay=%u", (int)bc->sock, bc->will_topic, + bc->will_qos, bc->will_retain, + (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'; + bc->password[0] = '\0'; +#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) { + 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) { + BROKER_STORE_STR(bc->password, mc.password, plen, + BROKER_MAX_PASSWORD_LEN); + } + } +#endif /* WOLFMQTT_BROKER_AUTH */ + + /* 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 + +#ifdef WOLFMQTT_BROKER_AUTH + if (broker->auth_user || broker->auth_pass) { + int auth_ok = 1; + if (broker->auth_user && (bc->username == NULL || + bc->username[0] == '\0' || + BrokerStrCompare(broker->auth_user, bc->username) != 0)) { + auth_ok = 0; + } + if (broker->auth_pass && (bc->password == NULL || + bc->password[0] == '\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)"); + #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; + } + } + } +#endif /* WOLFMQTT_BROKER_AUTH */ + + 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); + 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 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, + MqttBroker* broker) +{ + 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 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); + WOLFMQTT_FREE(sub.topics); + 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; + MqttQoS topic_qos = sub.topics[i].qos; + MqttQoS granted_qos; + + /* Cap at QoS 2 */ + if (topic_qos > MQTT_QOS_2) { + topic_qos = MQTT_QOS_2; + } + 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, topic_qos); + +#ifdef WOLFMQTT_BROKER_RETAINED + /* 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); + } +#endif + } + return_codes[i] = (byte)granted_qos; + } + + 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, + MqttBroker* broker) +{ + int rc; + int i; + 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 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); + WOLFMQTT_FREE(unsub.topics); + 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(broker, bc, f, flen); + } + } + + XMEMSET(&ack, 0, sizeof(ack)); + ack.packet_id = unsub.packet_id; +#ifdef WOLFMQTT_V5 + ack.protocol_level = bc->protocol_level; + ack.props = 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, + BROKER_CLIENT_TX_SZ(bc), &ack); + if (rc > 0) { + PRINTF("broker: UNSUBACK send sock=%d packet_id=%u", + (int)bc->sock, 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, + MqttBroker* broker) +{ + 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 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); + 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); + } + } + +#ifdef WOLFMQTT_BROKER_RETAINED + /* Handle retained messages */ + if (topic != NULL && pub.retain) { + if (pub.total_len == 0) { + 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, expiry); + } + } +#endif /* WOLFMQTT_BROKER_RETAINED */ + + if (topic != NULL && (payload != NULL || pub.total_len == 0)) { + /* Fan out to matching subscribers */ +#ifdef WOLFMQTT_STATIC_MEMORY + { + 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) { +#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)); + out_pub.topic_name = topic; + 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; + 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_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", + (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); + } + } +#ifndef WOLFMQTT_STATIC_MEMORY + sub = sub->next; +#endif + } + } + } + + 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, BROKER_CLIENT_TX_SZ(bc), + (pub.qos == MQTT_QOS_1) ? MQTT_PACKET_TYPE_PUBLISH_ACK : + MQTT_PACKET_TYPE_PUBLISH_REC, &resp); + if (rc > 0) { + 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); + } + } + +#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 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; + } + +#ifdef WOLFMQTT_V5 + resp.reason_code = MQTT_REASON_SUCCESS; + resp.props = NULL; +#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", + (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; +} + +/* 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, BROKER_CLIENT_TX_SZ(bc), + 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) */ +/* -------------------------------------------------------------------------- */ +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)); + /* 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); + if (peer != NULL) { + 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); + } + } + #endif + 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) */ + 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 */ + rc = 0; + } + 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; + } + + 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_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; + 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: + BrokerClient_ClearWill(bc); /* normal disconnect */ + BrokerSubs_RemoveClient(broker, bc); + BrokerClient_Remove(broker, bc); + return 0; + default: + break; + } + } + + /* 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 * 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); + return 0; + } + } + + 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; + broker->next_packet_id = 1; + 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; + } + + /* 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 + { + BrokerClient* bc = broker->clients; + while (bc) { + BrokerClient* next = bc->next; + rc = BrokerClient_Process(broker, bc); + 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; + } + } +#endif + + /* 3. Process pending wills (v5 Will Delay Interval) */ + if (BrokerPendingWill_Process(broker) > 0) { + activity = 1; + } + + return activity ? MQTT_CODE_SUCCESS : MQTT_CODE_CONTINUE; +} + +int MqttBroker_Run(MqttBroker* broker) +{ + int rc; + + if (broker == NULL) { + return MQTT_CODE_ERROR_BAD_ARG; + } + + /* 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 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); + } +#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) { + 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; + } + } + + return MQTT_CODE_SUCCESS; +} + +int MqttBroker_Stop(MqttBroker* broker) +{ + if (broker == NULL) { + return MQTT_CODE_ERROR_BAD_ARG; + } + broker->running = 0; + return MQTT_CODE_SUCCESS; +} + +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 + + /* Clean up pending wills and retained messages */ + BrokerPendingWill_FreeAll(broker); + 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); + broker->listen_sock = BROKER_SOCKET_INVALID; + } + + return MQTT_CODE_SUCCESS; +} + +/* -------------------------------------------------------------------------- */ +/* CLI wrapper */ +/* -------------------------------------------------------------------------- */ +static void BrokerUsage(const char* prog) +{ + 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 + , 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)"); +#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; + +#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; + 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; + } + + /* 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]); + } +#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; + if (broker.port == MQTT_DEFAULT_PORT) { + 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]; + } + 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; + } + else { + BrokerUsage(argv[0]); + return MQTT_CODE_ERROR_BAD_ARG; + } + } + +#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; +} + +#ifndef NO_MAIN_DRIVER +int main(int argc, char** argv) +{ + return wolfmqtt_broker(argc, 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 2fb33db4..2ec83ffc 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) }, @@ -78,7 +79,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, @@ -852,6 +853,206 @@ 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) +{ + 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 */ + int MqttDecode_ConnectAck(byte *rx_buf, int rx_buf_len, MqttConnectAck *connect_ack) { @@ -911,6 +1112,72 @@ 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) +{ + 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 */ + int MqttEncode_Publish(byte *tx_buf, int tx_buf_len, MqttPublish *publish, byte use_cb) { @@ -1370,6 +1637,107 @@ 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) +{ + 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 */ + int MqttDecode_SubscribeAck(byte* rx_buf, int rx_buf_len, MqttSubscribeAck *subscribe_ack) { @@ -1523,6 +1891,101 @@ 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) +{ + 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 */ + int MqttDecode_UnsubscribeAck(byte *rx_buf, int rx_buf_len, MqttUnsubscribeAck *unsubscribe_ack) { @@ -1596,6 +2059,80 @@ 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) +{ + 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 */ + int MqttEncode_Ping(byte *tx_buf, int tx_buf_len, MqttPing* ping) { int header_len, remain_len = 0; @@ -1721,6 +2258,33 @@ int MqttEncode_Disconnect(byte *tx_buf, int tx_buf_len, return header_len + remain_len; } +#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 && !WOLFMQTT_V5 */ + + #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 5ce466a9..c325a828 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 00000000..be2558ff --- /dev/null +++ b/wolfmqtt/mqtt_broker.h @@ -0,0 +1,336 @@ +/* mqtt_broker.h + * + * Copyright (C) 2006-2025 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 + +#include "wolfmqtt/mqtt_types.h" +#include "wolfmqtt/mqtt_socket.h" +#include "wolfmqtt/mqtt_client.h" + +#ifdef __cplusplus + extern "C" { +#endif + +#ifdef WOLFMQTT_BROKER + +/* -------------------------------------------------------------------------- */ +/* 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 8 +#endif +#ifndef BROKER_MAX_SUBS + #define BROKER_MAX_SUBS 32 +#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 +#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 +#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 */ +/* -------------------------------------------------------------------------- */ +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]; +#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_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; + byte protocol_level; + 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 */ +#ifdef ENABLE_MQTT_TLS + byte tls_handshake_done; +#endif +} 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; + MqttQoS qos; +} BrokerSub; + +/* -------------------------------------------------------------------------- */ +/* Retained message store */ +/* -------------------------------------------------------------------------- */ +#ifdef WOLFMQTT_BROKER_RETAINED +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; + 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_WILL_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; +#endif /* WOLFMQTT_BROKER_WILL */ + +/* -------------------------------------------------------------------------- */ +/* Broker context */ +/* -------------------------------------------------------------------------- */ +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 + 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; + byte tls_version; /* 0=auto (v23), 12=TLS 1.2, 13=TLS 1.3 */ +#endif +#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; + +/* -------------------------------------------------------------------------- */ +/* 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" */ +#endif + +#endif /* WOLFMQTT_BROKER_H */ diff --git a/wolfmqtt/mqtt_packet.h b/wolfmqtt/mqtt_packet.h index 6b6b90ff..ad0b99f8 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 @@ -719,6 +719,23 @@ WOLFMQTT_LOCAL MqttProp* MqttProps_FindType(MqttProp *head, #define MqttPacket_TypeDesc(x) "not compiled in" #endif +#ifdef WOLFMQTT_BROKER +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); +#ifndef WOLFMQTT_V5 +WOLFMQTT_API int MqttDecode_Disconnect(byte *rx_buf, int rx_buf_len, + MqttDisconnect* disc); +#endif +#endif + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/wolfmqtt/mqtt_types.h b/wolfmqtt/mqtt_types.h index 69de543b..bed32a55 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