37#define WS_ASYNC_MAX_PAYLOAD_BYTES ((uint64_t)(16u * 1024u * 1024u))
38#define WS_ASYNC_MAX_MESSAGE_BYTES ((size_t)(64u * 1024u * 1024u))
42#define WS_ASYNC_PARK_DEADLINE_SEC 30u
44#define WS_ASYNC_READ_CHUNK 16384u
52 "ws async slot state must fit a reactor slot payload");
87 bool terminate_pending;
91static void ws_async_read_ready(
int fd,
void *ctx);
92static void ws_async_write_ready(
int fd,
void *ctx);
97struct ws_async_sweep {
112static void ws_async_sweep_post_cb(
void *ctx);
113static void *ws_async_sweep_watchdog(
void *arg);
148static void ws_async_teardown(cwist_websocket_async *ws) {
151 if (ws->on_close) ws->on_close(ws, ws->user_data);
168static void ws_async_sweep_unlink(cwist_websocket_async *ws) {
173 while (*
pp && *
pp != ws)
pp = &(*pp)->sweep_next;
174 if (*
pp) *
pp = ws->sweep_next;
178 if (
empty) ws_async_sweep_mark_dead(sweep);
200static void ws_async_terminate(cwist_websocket_async *ws) {
202 ws_async_sweep_unlink(ws);
203 bool parked = ws->park_len > ws->park_off;
204 if (ws->read_armed || parked) {
216 ws_async_teardown(ws);
223static uint32_t ws_async_park_deadline(
void) {
240static uint32_t ws_async_idle_timeout_sec(
void) {
241 static _Atomic
int cached = -1;
244 const char *
env =
getenv(
"CWIST_WS_ASYNC_IDLE_TIMEOUT_SEC");
265static void ws_async_touch(cwist_websocket_async *ws) {
282static bool ws_async_idle_reap(cwist_websocket_async *ws) {
283 uint32_t timeout_sec = ws_async_idle_timeout_sec();
284 if (timeout_sec == 0)
return false;
286 if (ws->last_active_sec > 0 && (
now - ws->last_active_sec) > timeout_sec) {
287 ws_async_terminate(ws);
348 const struct timespec slice = {.tv_sec = 0, .tv_nsec = 100000000L};
351 bool drained = (sweep->posts == 0);
359 while (*
pp && *
pp != sweep)
pp = &(*pp)->next;
360 if (*
pp) *
pp = sweep->next;
377static void ws_async_sweep_post_cb(
void *ctx) {
380 uint32_t timeout_sec = ws_async_idle_timeout_sec();
381 if (timeout_sec > 0) {
389 for (cwist_websocket_async *ws = sweep->head; ws; ws = ws->sweep_next) {
390 if (ws->last_active_sec > 0 && (
now - ws->last_active_sec) > timeout_sec) {
403 if (sweep->posts > 0) sweep->posts--;
405 if (
empty) ws_async_sweep_mark_dead(sweep);
410#define WS_ASYNC_SWEEP_SLICE_MS 100u
423static void *ws_async_sweep_watchdog(
void *arg) {
425 const struct timespec slice = {.tv_sec = 0, .tv_nsec = 100000000L};
432 bool dead = sweep->dead;
437 bool dead = sweep->dead;
438 if (!dead) sweep->posts++;
451 ws_async_sweep_watchdog_free(sweep);
467static void ws_async_sweep_link(cwist_websocket_async *ws,
cwist_reactor_t *reactor) {
468 uint32_t timeout_sec = ws_async_idle_timeout_sec();
469 if (timeout_sec == 0)
return;
476 if (s->reactor == reactor && !s->dead) {
479 ws->sweep_next = s->head;
490 sweep->reactor = reactor;
491 sweep->interval_sec = ws_async_sweep_interval(timeout_sec);
495 sweep->node.cb = ws_async_sweep_post_cb;
496 sweep->node.ctx = sweep;
501 ws->sweep_next =
NULL;
513 sweep->next = g_ws_sweeps;
538 head[0] = 0x80 | (opcode & 0x0F);
541 }
else if (len < 65536) {
553 if (!buf)
return NULL;
555 if (len > 0)
memcpy(buf + head_len, data, len);
575static int ws_async_queue_frame(cwist_websocket_async *ws,
cwist_ws_opcode_t opcode,
576 const uint8_t *data,
size_t len) {
579 if (!
frame)
return -1;
581 size_t pending = ws->park_len - ws->park_off;
589 memcpy(
nb, ws->park_buf + ws->park_off, pending);
597 ws->park_buf =
frame;
605 while (ws->park_off < ws->park_len) {
606 ssize_t n = send(ws->fd, ws->park_buf + ws->park_off, ws->park_len - ws->park_off, flags);
616 if (ws->park_off == ws->park_len) {
620 ws->park_off = ws->park_len = 0;
625 ws->park_deadline_sec = ws_async_park_deadline();
626 ws_async_slot_t
slot = {.ws = ws};
644static void ws_async_write_ready(
int fd,
void *ctx) {
645 ws_async_slot_t *
slot = (ws_async_slot_t *)ctx;
652 ws_async_teardown(ws);
656 if (ws_async_idle_reap(ws))
return;
658 while (ws->park_off < ws->park_len) {
659 ssize_t n = send(fd, ws->park_buf + ws->park_off, ws->park_len - ws->park_off, flags);
663 ws->park_deadline_sec = ws_async_park_deadline();
674 ws_async_terminate(ws);
680 ws->park_off = ws->park_len = 0;
682 if (ws->terminate_pending) {
683 ws_async_terminate(ws);
687 if (!ws->read_armed) {
688 ws_async_slot_t
rslot = {.ws = ws};
690 ws->read_armed =
true;
692 ws_async_terminate(ws);
711 const uint8_t *data,
size_t len) {
712 if (!ws || ws->closed || (!data && len > 0))
return -1;
713 return ws_async_queue_frame(ws, opcode, data, len);
726 if (!ws || ws->closed)
return;
730 ws->terminate_pending =
true;
733 if (ws->park_off == ws->park_len) {
737 ws->terminate_pending =
true;
755 if (!ws || ws->closed)
return;
759 ws->terminate_pending =
true;
762 if (ws->park_off == ws->park_len) {
765 ws->terminate_pending =
true;
782static bool ws_async_stash_append(cwist_websocket_async *ws,
const uint8_t *src,
size_t len) {
783 if (len == 0)
return true;
784 size_t need = ws->stash_len + len;
785 if (
need > ws->stash_cap) {
786 size_t new_cap = ws->stash_cap ? ws->stash_cap * 2 : 4096;
789 if (!
nb)
return false;
790 if (ws->stash_len)
memcpy(
nb, ws->stash, ws->stash_len);
795 memcpy(ws->stash + ws->stash_len, src, len);
796 ws->stash_len += len;
811static bool ws_async_frag_append(cwist_websocket_async *ws,
const uint8_t *src,
size_t len) {
812 if (len == 0)
return true;
814 size_t need = ws->frag_len + len + 1;
815 if (
need > ws->frag_cap) {
816 size_t new_cap = ws->frag_cap ? ws->frag_cap * 2 : 4096;
819 if (!
nb)
return false;
820 if (ws->frag_len)
memcpy(
nb, ws->frag_buf, ws->frag_len);
825 memcpy(ws->frag_buf + ws->frag_len, src, len);
848static int ws_async_process_frame(cwist_websocket_async *ws,
bool fin,
cwist_ws_opcode_t opcode,
849 uint8_t *payload,
size_t payload_len) {
858 if (ws->frag_len == 0) {
864 ws->frag_opcode = opcode;
866 if (!ws_async_frag_append(ws, payload, payload_len)) {
884 if (!ws_async_frag_append(ws, payload, payload_len)) {
888 ws->frag_len = ws->frag_cap = 0;
894 payload = ws->frag_buf;
895 payload_len = ws->frag_len;
896 payload[payload_len] =
'\0';
897 opcode = ws->frag_opcode;
901 ws->frag_len = ws->frag_cap = 0;
907 if (payload_len == 1) {
915 ws->terminate_pending =
true;
930 frame->opcode = opcode;
931 frame->payload = payload;
932 frame->payload_len = payload_len;
934 ws->on_message(ws,
frame, ws->user_data);
949static int ws_async_feed(cwist_websocket_async *ws) {
952 while (ws->stash_len - off >= 2) {
953 const uint8_t *base = ws->stash + off;
954 bool fin = (base[0] & 0x80) != 0;
959 if (base[0] & 0x70)
return -1;
962 if (!(base[1] & 0x80))
return -1;
964 uint64_t payload_len = base[1] & 0x7F;
966 if (payload_len == 126) {
967 if (ws->stash_len - off < 4)
break;
970 }
else if (payload_len == 127) {
971 if (ws->stash_len - off < 10)
break;
988 size_t total = hdr + 4 + (
size_t)payload_len;
989 if (ws->stash_len - off < total)
break;
992 const uint8_t *src = base + hdr + 4;
994 if (payload_len > 0) {
996 if (!payload)
return -1;
998 payload[payload_len] =
'\0';
1002 if (ws_async_process_frame(ws, fin, opcode, payload, (
size_t)payload_len) != 0)
return -1;
1005 if (ws->terminate_pending)
break;
1009 memmove(ws->stash, ws->stash + off, ws->stash_len - off);
1010 ws->stash_len -= off;
1032static void ws_async_read_ready(
int fd,
void *ctx) {
1033 ws_async_slot_t *
slot = (ws_async_slot_t *)ctx;
1038 ws->read_armed =
false;
1044 ws_async_teardown(ws);
1051 if (ws_async_idle_reap(ws))
return;
1058 if (!ws_async_stash_append(ws, (
const uint8_t *)buf, (
size_t)
n)) {
1074 if (!
err && ws->stash_len > 0 && ws_async_feed(ws) != 0)
err =
true;
1082 if (
err || eof || ws->terminate_pending) {
1083 ws_async_terminate(ws);
1089 ws_async_terminate(ws);
1092 ws->read_armed =
true;
1121 if (fd < 0 || !reactor || !on_message)
return NULL;
1129 ws->reactor = reactor;
1130 ws->on_message = on_message;
1131 ws->on_close = on_close;
1132 ws->user_data = user_data;
1134 ws->stash_len = ws->stash_cap = 0;
1135 ws->frag_buf =
NULL;
1136 ws->frag_len = ws->frag_cap = 0;
1138 ws->park_buf =
NULL;
1139 ws->park_off = ws->park_len = 0;
1140 ws->park_deadline_sec = 0;
1143 ws->sweep_next =
NULL;
1145 ws->closing =
false;
1146 ws->terminate_pending =
false;
1147 ws->read_armed =
false;
1159 if (!
fail && ws_async_feed(ws) != 0)
fail =
true;
1161 if (
fail || ws->terminate_pending) {
1179 ws_async_sweep_link(ws, reactor);
1180 ws->read_armed =
true;
1182 ws_async_slot_t
slot = {.ws = ws};
1184 ws->read_armed =
false;
1185 ws_async_sweep_unlink(ws);
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
@ CWIST_WS_FRAME_CONTINUATION
#define CWIST_REACTOR_PAYLOAD_SIZE
struct cwist_reactor cwist_reactor_t
bool cwist_reactor_add(cwist_reactor_t *reactor, int fd, cwist_reactor_cb_t cb, const void *payload, size_t payload_size)
bool cwist_reactor_del(cwist_reactor_t *reactor, int fd)
bool cwist_reactor_add_out(cwist_reactor_t *reactor, int fd, cwist_reactor_cb_t cb, const void *payload, size_t payload_size)
#define WS_ASYNC_MAX_MESSAGE_BYTES
struct ws_async_sweep ws_async_sweep_t
int cwist_websocket_async_send(cwist_websocket_async *ws, cwist_ws_opcode_t opcode, const uint8_t *data, size_t len)
Send one complete WebSocket message frame on an async connection.
void cwist_websocket_async_close(cwist_websocket_async *ws)
Initiate the WebSocket close handshake with an empty CLOSE frame.
void cwist_websocket_async_close_code(cwist_websocket_async *ws, uint16_t code)
Initiate the WebSocket close handshake with a CLOSE frame carrying a status code.
#define WS_ASYNC_READ_CHUNK
cwist_websocket_async * cwist_websocket_async_attach_ex(int fd, cwist_reactor_t *reactor, cwist_ws_on_message_t on_message, cwist_ws_on_close_t on_close, void *user_data, const uint8_t *initial, size_t initial_len)
Attach an upgraded, HTTP-connection-owned fd to the reactor as an async WebSocket connection.
bool cwist_websocket_async_attach(int fd, cwist_reactor_t *reactor, cwist_ws_on_message_t on_message, void *user_data, const uint8_t *initial, size_t initial_len)
Attach an upgraded fd to the reactor as an async WebSocket connection (no on_close callback variant).
#define WS_ASYNC_SWEEP_SLICE_MS
#define WS_ASYNC_MAX_PAYLOAD_BYTES
#define WS_ASYNC_PARK_DEADLINE_SEC
Reactor-driven, callback-shaped non-blocking WebSocket API (C1M mode).
struct cwist_websocket_async cwist_websocket_async
void(* cwist_ws_on_message_t)(cwist_websocket_async *ws, cwist_ws_frame *frame, void *user_data)
Callback invoked for each complete WebSocket message on the C1M path.
void(* cwist_ws_on_close_t)(cwist_websocket_async *ws, void *user_data)
Optional teardown hook for the callback-shaped WS path (see cwist_websocket_async_attach_ex in ws_asy...
Zero-latency speculative fast-path writer and P2C worker scheduler.
uint32_t cwist_fast_monotonic_sec(void)
Fast monotonic clock in seconds (vDSO coarse where available).