12#if DETWS_ENABLE_WS_CLIENT
23static const char WS_MAGIC[] =
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
25void ws_client_accept_for_key(
const char *key_b64,
char *out,
size_t out_cap)
27 if (!out || out_cap == 0)
33 size_t klen = strnlen(key_b64,
sizeof(concat));
34 size_t mlen =
sizeof(WS_MAGIC) - 1;
35 if (klen + mlen >=
sizeof(concat))
37 memcpy(concat, key_b64, klen);
38 memcpy(concat + klen, WS_MAGIC, mlen);
40 sha1((
const uint8_t *)concat, klen + mlen, digest);
46size_t ws_client_build_handshake(uint8_t *out,
size_t cap,
const char *host,
const char *path,
const char *key_b64,
47 const char *subprotocol)
49 if (!out || !host || !path || !key_b64)
54 if (subprotocol && subprotocol[0])
55 n = snprintf((
char *)out, cap,
58 "Upgrade: websocket\r\n"
59 "Connection: Upgrade\r\n"
60 "Sec-WebSocket-Key: %s\r\n"
61 "Sec-WebSocket-Protocol: %s\r\n"
62 "Sec-WebSocket-Version: 13\r\n\r\n",
63 path, host, key_b64, subprotocol);
65 n = snprintf((
char *)out, cap,
68 "Upgrade: websocket\r\n"
69 "Connection: Upgrade\r\n"
70 "Sec-WebSocket-Key: %s\r\n"
71 "Sec-WebSocket-Version: 13\r\n\r\n",
73 if (n < 0 || (
size_t)n >= cap)
80static const char *find_header(
const uint8_t *buf,
size_t len,
const char *name,
size_t *vlen)
82 size_t nlen = strnlen(name, len + 1);
83 const uint8_t *p = buf;
84 const uint8_t *end = buf + len;
85 while (p + nlen + 1 < end)
87 if (strncasecmp((
const char *)p, name, nlen) == 0 && p[nlen] ==
':')
89 const uint8_t *v = p + nlen + 1;
90 while (v < end && (*v ==
' ' || *v ==
'\t'))
93 while (e < end && *e !=
'\r' && *e !=
'\n')
95 *vlen = (size_t)(e - v);
96 return (
const char *)v;
98 while (p < end && *p !=
'\n')
106bool ws_client_check_response(
const uint8_t *buf,
size_t len,
const char *expected_accept)
108 if (!buf || len < 12 || !expected_accept)
111 const uint8_t *eol = (
const uint8_t *)memchr(buf,
'\n', len);
115 for (
const uint8_t *q = buf; q + 3 < eol; q++)
116 if (q[0] ==
'1' && q[1] ==
'0' && q[2] ==
'1')
124 const char *acc = find_header(buf, len,
"Sec-WebSocket-Accept", &vlen);
127 return vlen == strnlen(expected_accept, vlen + 1) && memcmp(acc, expected_accept, vlen) == 0;
130size_t ws_client_build_frame(uint8_t *out,
size_t cap, WsClientOpcode opcode,
const uint8_t *payload,
size_t len,
131 const uint8_t mask[4])
136 if (len >= 126 && len < 65536)
138 else if (len >= 65536)
144 out[i++] = (uint8_t)(0x80 |
static_cast<uint8_t
>(opcode));
146 out[i++] = (uint8_t)(0x80 | len);
147 else if (len < 65536)
149 out[i++] = 0x80 | 126;
150 out[i++] = (uint8_t)(len >> 8);
151 out[i++] = (uint8_t)(len & 0xFF);
155 out[i++] = 0x80 | 127;
156 for (
int s = 56; s >= 0; s -= 8)
157 out[i++] = (uint8_t)((uint64_t)len >> s);
159 memcpy(out + i, mask, 4);
161 for (
size_t j = 0; j < len; j++)
162 out[i + j] = (uint8_t)(payload[j] ^ mask[j & 3]);
166bool ws_client_parse_frame(
const uint8_t *buf,
size_t avail, uint8_t *opcode,
bool *fin,
size_t *payload_off,
167 size_t *payload_len,
size_t *consumed)
169 if (!buf || avail < 2)
173 bool masked = (b1 & 0x80) != 0;
174 uint64_t len = b1 & 0x7F;
180 len = ((uint64_t)buf[off] << 8) | buf[off + 1];
188 for (
int s = 0; s < 8; s++)
189 v = (v << 8) | buf[off + s];
197 if (avail < off + (
size_t)len)
199 *opcode = (uint8_t)(b0 & 0x0F);
200 *fin = (b0 & 0x80) != 0;
202 *payload_len = (size_t)len;
203 *consumed = off + (size_t)len;
215#include <esp_system.h>
217#if DETWS_ENABLE_WS_CLIENT_TLS
219#include <mbedtls/ssl.h>
222#ifdef DETWS_WS_CLIENT_DEBUG
223#define WSC_DBG(...) printf(__VA_ARGS__)
225#define WSC_DBG(...) ((void)0)
232 WsClientMessageCb cb;
234 volatile bool closed;
241 volatile size_t rx_head;
242 volatile size_t rx_tail;
251static WsClientCtx s_wsc;
253static inline size_t ring_avail()
255 return (s_wsc.rx_head +
sizeof(s_wsc.rx) - s_wsc.rx_tail) %
sizeof(s_wsc.rx);
257static inline uint8_t ring_peek(
size_t i)
259 return s_wsc.rx[(s_wsc.rx_tail + i) %
sizeof(s_wsc.rx)];
261static inline void ring_advance(
size_t n)
263 s_wsc.rx_tail = (s_wsc.rx_tail + n) %
sizeof(s_wsc.rx);
265static void ring_copy(uint8_t *dst,
size_t n)
267 for (
size_t i = 0; i < n; i++)
268 dst[i] = s_wsc.rx[(s_wsc.rx_tail + i) %
sizeof(s_wsc.rx)];
273static bool ws_tx_plain(
const uint8_t *data,
size_t len)
279static void ws_pump_plain()
284 size_t freey = (
sizeof(s_wsc.rx) - 1) - ring_avail();
287 size_t want = freey <
sizeof(tmp) ? freey : sizeof(tmp);
295 for (
size_t i = 0; i < n; i++)
297 s_wsc.rx[s_wsc.rx_head] = tmp[i];
298 s_wsc.rx_head = (s_wsc.rx_head + 1) %
sizeof(s_wsc.rx);
303#if DETWS_ENABLE_WS_CLIENT_TLS
306static int ws_tls_send(
void *ctx,
const unsigned char *buf,
size_t len)
309 size_t cap = len > 0xFFFF ? 0xFFFF : len;
310 return det_client_send(s_wsc.cid, buf, cap) ? (int)cap : MBEDTLS_ERR_SSL_WANT_WRITE;
312static int ws_tls_recv(
void *ctx,
unsigned char *buf,
size_t len)
320static void ws_pump_tls()
325 size_t freey = (
sizeof(s_wsc.rx) - 1) - ring_avail();
328 size_t want = freey <
sizeof(tmp) ? freey : sizeof(tmp);
329 int n = det_tls_csess_read(tmp, want);
336 for (
int i = 0; i < n; i++)
338 s_wsc.rx[s_wsc.rx_head] = tmp[i];
339 s_wsc.rx_head = (s_wsc.rx_head + 1) %
sizeof(s_wsc.rx);
346static bool ws_tx(
const uint8_t *data,
size_t len)
348#if DETWS_ENABLE_WS_CLIENT_TLS
350 return det_tls_csess_write(data, len) == (int)len;
352 return ws_tx_plain(data, len);
356static bool ws_send_frame(WsClientOpcode opcode,
const uint8_t *payload,
size_t len)
361 esp_fill_random(mask, 4);
362 size_t n = ws_client_build_frame(s_wsc.tx,
sizeof(s_wsc.tx), opcode, payload, len, mask);
363 return n && ws_tx(s_wsc.tx, n);
366static void ws_close_tcp()
368#if DETWS_ENABLE_WS_CLIENT_TLS
378static void deliver(uint8_t op,
const uint8_t *payload,
size_t len)
380 if (s_wsc.cb && (op == (uint8_t)WsClientOpcode::WSC_OP_TEXT || op == (uint8_t)WsClientOpcode::WSC_OP_BINARY))
381 s_wsc.cb(op, payload, len);
385static void handle_frame(uint8_t op,
bool fin,
const uint8_t *payload,
size_t len)
387 switch ((WsClientOpcode)op)
389 case WsClientOpcode::WSC_OP_TEXT:
390 case WsClientOpcode::WSC_OP_BINARY:
393 deliver(op, payload, len);
398 s_wsc.msg_len = len <
sizeof(s_wsc.msg) ? len : sizeof(s_wsc.msg);
399 memcpy(s_wsc.msg, payload, s_wsc.msg_len);
402 case WsClientOpcode::WSC_OP_CONT:
403 if (s_wsc.msg_len + len <=
sizeof(s_wsc.msg))
405 memcpy(s_wsc.msg + s_wsc.msg_len, payload, len);
406 s_wsc.msg_len += len;
410 deliver(s_wsc.msg_op, s_wsc.msg, s_wsc.msg_len);
414 case WsClientOpcode::WSC_OP_PING:
417 case WsClientOpcode::WSC_OP_CLOSE:
421 case WsClientOpcode::WSC_OP_PONG:
427static void process_rx()
429#if DETWS_ENABLE_WS_CLIENT_TLS
437 size_t avail = ring_avail();
443 size_t hn = avail <
sizeof(hdr) ? avail : sizeof(hdr);
444 for (
size_t i = 0; i < hn; i++)
445 hdr[i] = ring_peek(i);
451 if (!ws_client_parse_frame(hdr, avail, &op, &fin, &off, &plen, &consumed))
453 if (consumed >
sizeof(s_wsc.pkt))
455 ring_advance(consumed);
458 ring_copy(s_wsc.pkt, consumed);
459 ring_advance(consumed);
460 handle_frame(op, fin, s_wsc.pkt + off, plen);
464void ws_client_on_message(WsClientMessageCb cb)
469bool ws_client_connect(
const char *host, uint16_t port,
bool use_tls,
const char *path)
473#if !DETWS_ENABLE_WS_CLIENT_TLS
477 s_wsc.rx_head = s_wsc.rx_tail = 0;
478 s_wsc.closed = s_wsc.ws_up =
false;
480 s_wsc.use_tls = use_tls;
482 uint32_t deadline = millis() + 8000;
488 WSC_DBG(
"[wsc] det_client_open failed (%d)\n", s_wsc.cid);
492#if DETWS_ENABLE_WS_CLIENT_TLS
495 if (!det_tls_csess_begin(host, ws_tls_send, ws_tls_recv))
497 WSC_DBG(
"[wsc] csess_begin failed\n");
502 while ((h = det_tls_csess_handshake()) == 0 && !s_wsc.closed && (int32_t)(deadline - millis()) > 0)
506 WSC_DBG(
"[wsc] TLS handshake h=%d closed=%d\n", h, (
int)s_wsc.closed);
510 WSC_DBG(
"[wsc] TLS handshake ok\n");
516 esp_fill_random(keyraw,
sizeof(keyraw));
520 ws_client_accept_for_key(key_b64, expect,
sizeof(expect));
522 size_t n = ws_client_build_handshake(s_wsc.tx,
sizeof(s_wsc.tx), host, path, key_b64,
nullptr);
523 if (n == 0 || !ws_tx(s_wsc.tx, n))
533 while (!done && !s_wsc.closed && (int32_t)(deadline - millis()) > 0)
535#if DETWS_ENABLE_WS_CLIENT_TLS
541 while (ring_avail() > 0 && hl <
sizeof(hs))
543 hs[hl++] = ring_peek(0);
545 if (hl >= 4 && hs[hl - 4] ==
'\r' && hs[hl - 3] ==
'\n' && hs[hl - 2] ==
'\r' && hs[hl - 1] ==
'\n')
554 if (!done || !ws_client_check_response(hs, hl, expect))
556 WSC_DBG(
"[wsc] handshake fail done=%d hl=%u resp:\n%.*s\n", (
int)done, (
unsigned)hl, (
int)hl, (
const char *)hs);
564bool ws_client_send_text(
const char *text)
566 return ws_send_frame(WsClientOpcode::WSC_OP_TEXT, (
const uint8_t *)text,
569bool ws_client_send_binary(
const uint8_t *data,
size_t len)
571 return ws_send_frame(WsClientOpcode::WSC_OP_BINARY, data, len);
587bool ws_client_connected()
592void ws_client_close()
601void ws_client_on_message(WsClientMessageCb)
604bool ws_client_connect(
const char *, uint16_t,
bool,
const char *)
608bool ws_client_send_text(
const char *)
612bool ws_client_send_binary(
const uint8_t *,
size_t)
620bool ws_client_connected()
624void ws_client_close()
#define DETWS_WS_CLIENT_BUF_SIZE
WebSocket client send/receive buffer size in bytes (bounds one frame).
void base64_encode(const uint8_t *src, size_t src_len, char *dst)
Encode src_len bytes of src as Base64.
bool det_client_is_closed(int)
True once the peer closed (FIN) or the connection errored.
int det_client_open(const char *, uint16_t, uint32_t)
Resolve host (dotted-quad fast path, else DNS) and connect to host : port, blocking up to timeout_ms.
size_t det_client_read(int, uint8_t *, size_t)
Drain up to cap buffered wire bytes into buf; returns the count.
void det_client_close(int)
Tear down the connection (marshaled) and return the slot to the pool.
bool det_client_send(int, const void *, size_t)
Queue len wire bytes for transmission (marshaled tcp_write + output).
Layer 4 outbound TCP client transport - the client-side peer of the (server) transport in tcp....
void sha1(const uint8_t *data, size_t len, uint8_t digest[SHA1_DIGEST_LEN])
Compute a SHA-1 digest over an arbitrary byte buffer.
Software SHA-1 implementation - no platform dependencies.
#define SHA1_DIGEST_LEN
SHA-1 digest length in bytes.
Deterministic TLS engine: mbedTLS over a static memory pool (DETWS_ENABLE_TLS).
bool ws_send_frame(WsConn *ws, WsOpcode opcode, const uint8_t *payload, uint16_t len)
Send a WebSocket frame to the client.
Zero-heap outbound WebSocket client, RFC 6455 (DETWS_ENABLE_WS_CLIENT).