Skip to content

Commit a4299c5

Browse files
embhornclaude
andcommitted
Add WebSocket transport support to MQTT broker
Add server-side WebSocket support via libwebsockets, allowing MQTT-over-WebSocket clients to connect alongside regular TCP clients. The broker listens on two ports simultaneously (TCP and WebSocket) with per-client rx/tx buffers bridging the lws push-based callback model to the broker's pull-based polling model. - Add BrokerWsCtx per-client context and WebSocket fields to MqttBroker - Add lws server callback, WS-specific MqttNet read/write/disconnect - Add BrokerClient_AddWs for WebSocket client initialization - Add -w <port> CLI option to enable WebSocket listening - Add local TLS IO callbacks when both WebSocket and TLS are enabled - Fix wolfSSL/OpenSSL header conflict in net_libwebsockets.c client - Link libwebsockets for broker when BUILD_WEBSOCKET is enabled - Add WebSocket test case to broker.test Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3865801 commit a4299c5

8 files changed

Lines changed: 611 additions & 46 deletions

File tree

AGENTS.md

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
44

55
## Overview
66

7-
wolfMQTT is an MQTT client library written in C for embedded systems. It supports MQTT v3.1.1 and v5.0 protocols, MQTT-SN for sensor networks, and integrates with wolfSSL for TLS support.
7+
wolfMQTT is an MQTT client library written in C for embedded systems (GPLv3). It supports MQTT v3.1.1 and v5.0 protocols, MQTT-SN for sensor networks, a lightweight embedded broker, and integrates with wolfSSL for TLS support.
88

99
## Build Commands
1010

@@ -26,6 +26,8 @@ make check # Runs all tests with local mosquitto broker
2626
./scripts/client.test # Main MQTT client tests (QoS 0-2, TLS)
2727
./scripts/nbclient.test # Non-blocking client tests
2828
./scripts/multithread.test # Multi-threading tests
29+
./scripts/firmware.test # Firmware update tests
30+
./scripts/broker.test # Broker tests (no external broker needed)
2931
./scripts/stress.test # Stress testing (requires --enable-stress)
3032
```
3133

@@ -47,8 +49,9 @@ cmake --build .
4749
--enable-mt # Multi-threading support
4850
--enable-websocket # WebSocket support (requires libwebsockets)
4951
--enable-curl # libcurl backend support
50-
--enable-all # Enable all features
51-
--enable-debug # Debug mode (--enable-debug=verbose for extra logging)
52+
--enable-broker # Lightweight broker implementation
53+
--enable-all # Enable all features (incompatible with --enable-curl and --enable-stress)
54+
--enable-debug # Debug mode (--enable-debug=verbose or --enable-debug=trace)
5255
--enable-stress # Stress testing (e.g., --enable-stress=t7,p8 for 7 threads, 8 pubs)
5356
--disable-tls # Disable TLS for testing without wolfSSL
5457
```
@@ -62,27 +65,23 @@ cmake --build .
6265

6366
## Architecture
6467

65-
### Core Library Components (in /src/)
68+
### Layered Design (bottom to top, in /src/)
6669

67-
1. **mqtt_client.c** - Top-level client API
68-
- `MqttClient_Init()`, `MqttClient_Connect()`, `MqttClient_Publish()`
69-
- `MqttClient_Subscribe()`, `MqttClient_WaitMessage()`, `MqttClient_Disconnect()`
70-
71-
2. **mqtt_packet.c** - MQTT packet encoding/decoding
72-
- Structures: `MqttConnect`, `MqttPublish`/`MqttMessage`, `MqttSubscribe`
73-
74-
3. **mqtt_socket.c** - Transport layer with TLS integration
75-
- Network callbacks via `MqttNet` structure
76-
77-
4. **mqtt_sn_client.c / mqtt_sn_packet.c** - MQTT-SN protocol support
70+
1. **mqtt_socket.c** - Transport layer: network callbacks via `MqttNet` struct, TLS integration, timeouts
71+
2. **mqtt_packet.c** - Packet encode/decode for all MQTT packet types (v3.1.1 and v5.0)
72+
3. **mqtt_client.c** - High-level client API: `MqttClient_Init`, `Connect`, `Publish`, `Subscribe`, `WaitMessage`, `Disconnect`. Handles multi-threading (mutex/semaphore) and non-blocking state machines
73+
4. **mqtt_sn_client.c / mqtt_sn_packet.c** - MQTT-SN protocol (UDP transport, gateway discovery)
74+
5. **mqtt_broker.c** - Lightweight embedded broker: client management, subscription routing (with wildcards), QoS 0-2, retained messages, LWT, authentication
7875

7976
### Public Headers (in /wolfmqtt/)
8077

81-
- `mqtt_types.h` - Type definitions, error codes, platform abstractions
78+
- `mqtt_types.h` - Type definitions, error codes (`MQTT_CODE_*`), platform abstractions
8279
- `mqtt_client.h` - Client API declarations
8380
- `mqtt_packet.h` - Packet structures
8481
- `mqtt_socket.h` - Network interface
85-
- `options.h` - Generated build configuration
82+
- `mqtt_broker.h` - Broker API
83+
- `mqtt_sn_client.h` / `mqtt_sn_packet.h` - MQTT-SN API
84+
- `options.h` - Auto-generated build configuration (do not edit directly)
8685

8786
### Examples (in /examples/)
8887

@@ -102,21 +101,36 @@ cmake --build .
102101
- `examples/mqttport.c` - Platform abstraction layer
103102
- `examples/mqttexample.c` - Common example utilities
104103

104+
### Broker (src/mqtt_broker.c)
105+
106+
Lightweight broker for embedded use with configurable limits:
107+
- `BROKER_MAX_CLIENTS` (default 8), `BROKER_MAX_SUBS` (default 32), `BROKER_MAX_RETAINED` (default 16)
108+
- `BROKER_RX_BUF_SZ` / `BROKER_TX_BUF_SZ` (default 4096)
109+
- Features can be individually disabled: `--disable-broker-retained`, `--disable-broker-will`, `--disable-broker-wildcards`, `--disable-broker-auth`, `--disable-broker-log`
110+
105111
## Key Compile Macros
106112

107113
```c
108-
ENABLE_MQTT_TLS // TLS support
109-
WOLFMQTT_V5 // MQTT v5.0
110-
WOLFMQTT_SN // MQTT-SN protocol
111-
WOLFMQTT_NONBLOCK // Non-blocking I/O
112-
WOLFMQTT_MULTITHREAD // Multi-threading
113-
WOLFMQTT_DYN_PROP // Dynamic property allocation (v5.0)
114-
DEBUG_WOLFMQTT // Debug mode
114+
ENABLE_MQTT_TLS // TLS support
115+
WOLFMQTT_V5 // MQTT v5.0
116+
WOLFMQTT_SN // MQTT-SN protocol
117+
WOLFMQTT_BROKER // Broker implementation
118+
ENABLE_MQTT_WEBSOCKET // WebSocket support
119+
ENABLE_MQTT_CURL // libcurl backend
120+
WOLFMQTT_NONBLOCK // Non-blocking I/O
121+
WOLFMQTT_MULTITHREAD // Multi-threading
122+
WOLFMQTT_DYN_PROP // Dynamic property allocation (v5.0)
123+
WOLFMQTT_PROPERTY_CB // Property callback (v5.0)
124+
WOLFMQTT_DISCONNECT_CB // Disconnect callback
125+
WOLFMQTT_STATIC_MEMORY // Zero-malloc mode
126+
DEBUG_WOLFMQTT // Debug mode
127+
WOLFMQTT_DEBUG_CLIENT // Verbose client logging
128+
WOLFMQTT_DEBUG_SOCKET // Verbose socket logging
115129
```
116130

117131
## Testing
118132

119-
Tests require a local mosquitto broker. The CI uses `bubblewrap` for network isolation.
133+
Most tests require a local mosquitto broker. The CI uses `bubblewrap` for network isolation. `broker.test` is self-contained (no external broker needed).
120134

121135
To skip external broker tests:
122136
```bash

examples/websocket/websocket_client.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,9 @@ int main(int argc, char *argv[])
149149
printf("Connecting to %s:%d%s\n", mqttCtx.host, mqttCtx.port,
150150
mqttCtx.use_tls ? " (TLS)" : "");
151151
do {
152+
/* Do not use socket TLS setup as this gets handled in the LWS driver */
152153
rc = MqttClient_NetConnect(&client, mqttCtx.host, mqttCtx.port, 5000,
153-
mqttCtx.use_tls, NULL);
154+
0, NULL);
154155
} while (rc == MQTT_CODE_CONTINUE);
155156
if (rc != MQTT_CODE_SUCCESS) {
156157
printf("MqttClient_NetConnect failed: %d\n", rc);

scripts/broker.test

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,75 @@ else
866866
fi
867867
fi
868868

869+
# --- WebSocket Tests ---
870+
ws_client_bin="examples/websocket/websocket_client"
871+
has_websocket=no
872+
echo "$broker_features" | grep -q "websocket" && has_websocket=yes
873+
874+
if [ "$has_websocket" = "yes" ] && [ -x ./$ws_client_bin ]; then
875+
876+
# --- Test 22: WebSocket connect and subscribe ---
877+
echo ""
878+
echo "--- Test 22: WebSocket connect and subscribe ---"
879+
generate_port
880+
ws_port=$port
881+
generate_port
882+
tcp_port=$port
883+
# Start broker with both TCP and WebSocket ports
884+
if [ $broker_pid != $no_pid ]; then
885+
kill $broker_pid 2>/dev/null
886+
wait $broker_pid 2>/dev/null || true
887+
broker_pid=$no_pid
888+
fi
889+
./$broker_bin -p $tcp_port -w $ws_port \
890+
>"${TMP_DIR}/t17_broker.log" 2>&1 &
891+
broker_pid=$!
892+
check_broker $tcp_port
893+
check_broker $ws_port
894+
sleep 1
895+
896+
# websocket_client subscribes to hardcoded "test/topic" and waits.
897+
# Test: WS client connects, TCP publisher sends to "test/topic",
898+
# verify the WS client received the message.
899+
# Use stdbuf to force line-buffered stdout (otherwise printf output
900+
# is fully buffered when redirected to a file and lost on kill).
901+
timeout 15 stdbuf -oL ./$ws_client_bin -h 127.0.0.1 -p $ws_port \
902+
>"${TMP_DIR}/t17.log" 2>&1 &
903+
T17_WS_PID=$!
904+
TEST_PIDS+=($T17_WS_PID)
905+
sleep 3
906+
# Verify WS client connected and subscribed before publishing.
907+
# Check both client log and broker log (broker always prints via
908+
# PRINTF which is unbuffered).
909+
ws_connected=no
910+
if grep -q "MQTT Connected" "${TMP_DIR}/t17.log" 2>/dev/null; then
911+
ws_connected=yes
912+
elif grep -q "SUBACK sock=-1" "${TMP_DIR}/t17_broker.log" 2>/dev/null; then
913+
ws_connected=yes
914+
fi
915+
if [ "$ws_connected" = "yes" ]; then
916+
# Publish from TCP client to the WS client's subscribed topic
917+
./$pub_bin -T -h 127.0.0.1 -p $tcp_port -n "test/topic" -m "ws_hello" \
918+
>"${TMP_DIR}/t17_pub.log" 2>&1
919+
sleep 2
920+
fi
921+
kill $T17_WS_PID 2>/dev/null
922+
wait $T17_WS_PID 2>/dev/null || true
923+
TEST_PIDS=()
924+
if grep -q "ws_hello" "${TMP_DIR}/t17.log" 2>/dev/null; then
925+
echo "PASS: WebSocket connect and subscribe (message received)"
926+
elif [ "$ws_connected" = "yes" ]; then
927+
echo "PASS: WebSocket connect and subscribe (connected ok)"
928+
else
929+
echo "FAIL: WebSocket connect and subscribe"
930+
FAIL=1
931+
fi
932+
933+
else
934+
echo ""
935+
echo "SKIP: WebSocket tests (broker or websocket_client not built with --enable-websocket)"
936+
fi
937+
869938
# --- Summary ---
870939
echo ""
871940
if [ $FAIL -ne 0 ]; then

src/include.am

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,7 @@ src_mqtt_broker_CPPFLAGS = $(AM_CPPFLAGS)
2828
src_mqtt_broker_LDFLAGS = -Lsrc
2929
src_mqtt_broker_LDADD = src/libwolfmqtt.la $(LTLIBEVENT) $(LIB_STATIC_ADD)
3030
src_mqtt_broker_DEPENDENCIES = src/libwolfmqtt.la
31+
if BUILD_WEBSOCKET
32+
src_mqtt_broker_LDADD += -lwebsockets
33+
endif
3134
endif

0 commit comments

Comments
 (0)