DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
ws_client.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file ws_client.h
6 * @brief Zero-heap outbound WebSocket client, RFC 6455 (DETWS_ENABLE_WS_CLIENT).
7 *
8 * Connects to a remote WebSocket endpoint (ws://, or wss:// over client-side
9 * mbedTLS) and exchanges text/binary messages - the device as a WebSocket client
10 * to a cloud dashboard or control plane. Split, like the other services, into a
11 * pure host-testable codec and an ESP32-only transport:
12 *
13 * - ws_client_build_handshake / ws_client_accept_for_key /
14 * ws_client_check_response / ws_client_build_frame / ws_client_parse_frame are
15 * pure functions, unit-tested on the host (env:native_ws_client).
16 * - ws_client_connect / ws_client_send_text / ws_client_loop drive the
17 * connection over raw lwIP (wss:// via the shared persistent client TLS
18 * session). No heap; one connection at a time.
19 *
20 * Client frames are always masked (RFC 6455 §5.3); server frames are not. Only
21 * unfragmented messages that fit DETWS_WS_CLIENT_BUF_SIZE are delivered.
22 */
23
24#ifndef DETERMINISTICESPASYNCWEBSERVER_WS_CLIENT_H
25#define DETERMINISTICESPASYNCWEBSERVER_WS_CLIENT_H
26
27#include "ServerConfig.h"
28#include <stddef.h>
29#include <stdint.h>
30
31#if DETWS_ENABLE_WS_CLIENT
32
33/** @brief WebSocket opcodes (RFC 6455 §5.2). */
34enum class WsClientOpcode : uint8_t
35{
36 WSC_OP_CONT = 0x0,
37 WSC_OP_TEXT = 0x1,
38 WSC_OP_BINARY = 0x2,
39 WSC_OP_CLOSE = 0x8,
40 WSC_OP_PING = 0x9,
41 WSC_OP_PONG = 0xA,
42};
43
44// ---------------------------------------------------------------------------
45// Pure codec (host-testable; no sockets, no heap)
46// ---------------------------------------------------------------------------
47
48/**
49 * @brief Compute the expected Sec-WebSocket-Accept for a client key.
50 *
51 * accept = base64(SHA1(key_b64 + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")), per
52 * RFC 6455 §4.2.2. @p out must hold at least 29 bytes (28 + NUL).
53 */
54void ws_client_accept_for_key(const char *key_b64, char *out, size_t out_cap);
55
56/**
57 * @brief Build the client opening handshake (an HTTP/1.1 Upgrade GET).
58 * @param subprotocol requested Sec-WebSocket-Protocol (e.g. "wamp.2.json"), or null/empty to omit the header.
59 * @return bytes written to @p out, or 0 if it would not fit @p cap.
60 */
61size_t ws_client_build_handshake(uint8_t *out, size_t cap, const char *host, const char *path, const char *key_b64,
62 const char *subprotocol);
63
64/**
65 * @brief Validate a server handshake response.
66 * @return true if it is "101 Switching Protocols" and carries
67 * Sec-WebSocket-Accept == @p expected_accept.
68 */
69bool ws_client_check_response(const uint8_t *buf, size_t len, const char *expected_accept);
70
71/**
72 * @brief Build a masked client frame (FIN set) for @p opcode.
73 * @param mask 4-byte masking key (random per frame on the wire).
74 * @return total frame length, or 0 if it would not fit @p cap.
75 */
76size_t ws_client_build_frame(uint8_t *out, size_t cap, WsClientOpcode opcode, const uint8_t *payload, size_t len,
77 const uint8_t mask[4]);
78
79/**
80 * @brief Parse one inbound (server, unmasked) frame at @p buf.
81 * @param payload_off receives the payload offset within @p buf.
82 * @param consumed receives the total frame size (header + payload).
83 * @return true if a complete frame is present in @p avail bytes; false if more
84 * bytes are needed.
85 */
86bool ws_client_parse_frame(const uint8_t *buf, size_t avail, uint8_t *opcode, bool *fin, size_t *payload_off,
87 size_t *payload_len, size_t *consumed);
88
89// ---------------------------------------------------------------------------
90// Transport (ESP32 only; no-ops / false on a host build)
91// ---------------------------------------------------------------------------
92
93/** @brief Callback for an inbound text/binary message (opcode is WsClientOpcode::WSC_OP_TEXT/BINARY). */
94typedef void (*WsClientMessageCb)(uint8_t opcode, const uint8_t *payload, size_t len);
95
96/** @brief Register the inbound-message callback (call before ws_client_connect). */
97void ws_client_on_message(WsClientMessageCb cb);
98
99/**
100 * @brief Connect and complete the WebSocket handshake (blocking).
101 * @return true on a verified 101 upgrade.
102 */
103bool ws_client_connect(const char *host, uint16_t port, bool use_tls, const char *path);
104
105/** @brief Send a UTF-8 text message (masked). @return true if sent. */
106bool ws_client_send_text(const char *text);
107
108/** @brief Send a binary message (masked). @return true if sent. */
109bool ws_client_send_binary(const uint8_t *data, size_t len);
110
111/**
112 * @brief Pump the connection: read inbound frames (dispatching text/binary to the
113 * callback), answer ping with pong, and handle close. Call once per loop().
114 * @return false if the connection has dropped.
115 */
116bool ws_client_loop();
117
118/** @brief True while the WebSocket connection is open. */
119bool ws_client_connected();
120
121/** @brief Send a Close frame and drop the connection. */
122void ws_client_close();
123
124#endif // DETWS_ENABLE_WS_CLIENT
125
126#endif // DETERMINISTICESPASYNCWEBSERVER_WS_CLIENT_H
User-facing configuration for DeterministicESPAsyncWebServer.