10#include <ascii-chat/network/websocket/server.h>
11#include <ascii-chat/network/acip/transport.h>
12#include <ascii-chat/log/logging.h>
13#include <ascii-chat/common.h>
14#include <ascii-chat/platform/abstraction.h>
15#include <ascii-chat/ringbuffer.h>
16#include <ascii-chat/buffer_pool.h>
17#include <ascii-chat/platform/mutex.h>
18#include <ascii-chat/platform/cond.h>
19#include <ascii-chat/util/time.h>
20#include <libwebsockets.h>
24#include <sys/socket.h>
25#include <netinet/tcp.h>
26#include <netinet/in.h>
30#include <ascii-chat/network/websocket/internal.h>
54static _Atomic uint64_t g_receive_callback_count = 0;
55static _Atomic uint64_t g_writeable_callback_count = 0;
61static void websocket_lws_log_callback(
int level,
const char *line) {
63 if (level & LLL_ERR) {
64 log_error(
"[LWS] %s", line);
65 }
else if (level & LLL_WARN) {
66 log_warn(
"[LWS] %s", line);
67 }
else if (level & LLL_NOTICE) {
68 log_info(
"[LWS] %s", line);
69 }
else if (level & LLL_INFO) {
70 log_info(
"[LWS] %s", line);
71 }
else if (level & LLL_DEBUG) {
72 log_debug(
"[LWS] %s", line);
81static int websocket_server_callback(
struct lws *wsi,
enum lws_callback_reasons reason,
void *user,
void *in,
84 const char *proto_name = lws_get_protocol(wsi) ? lws_get_protocol(wsi)->name :
"NULL";
87 log_dev(
"๐ด CALLBACK: reason=%d, proto=%s, wsi=%p, len=%zu", reason, proto_name, (
void *)wsi, len);
90 case LWS_CALLBACK_ESTABLISHED: {
93 log_info(
"๐ด๐ด๐ด LWS_CALLBACK_ESTABLISHED FIRED! wsi=%p", (
void *)wsi);
94 log_info(
"[LWS_CALLBACK_ESTABLISHED] WebSocket client connection established at timestamp=%llu",
95 (
unsigned long long)established_ns);
96 log_info(
"WebSocket client connected");
99 const struct lws_protocols *protocol = lws_get_protocol(wsi);
100 log_debug(
"[LWS_CALLBACK_ESTABLISHED] Got protocol: %p", (
void *)protocol);
101 if (!protocol || !protocol->user) {
102 log_error(
"[LWS_CALLBACK_ESTABLISHED] FAILED: Missing protocol user data (protocol=%p, user=%p)",
103 (
void *)protocol, protocol ? protocol->user : NULL);
106 websocket_server_t *server = (websocket_server_t *)protocol->user;
107 log_debug(
"[LWS_CALLBACK_ESTABLISHED] Got server: %p, handler: %p", (
void *)server, (
void *)server->handler);
110 conn_data->
server = server;
119 char client_name[128];
121 lws_get_peer_simple(wsi, client_name,
sizeof(client_name));
122 lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), client_name,
sizeof(client_name), client_ip,
sizeof(client_ip));
124 log_info(
"WebSocket client connected from %s", client_ip);
125 log_debug(
"[LWS_CALLBACK_ESTABLISHED] Client IP: %s", client_ip);
134 int fd = lws_get_socket_fd(wsi);
137 if (setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, &quickack,
sizeof(quickack)) < 0) {
138 log_warn(
"Failed to set TCP_QUICKACK: %s", SAFE_STRERROR(errno));
141 if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &nodelay,
sizeof(nodelay)) < 0) {
142 log_warn(
"Failed to set TCP_NODELAY: %s", SAFE_STRERROR(errno));
149 int actual_rcv = 0, actual_snd = 0;
150 socklen_t optlen =
sizeof(int);
151 getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &actual_rcv, &optlen);
152 getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &actual_snd, &optlen);
153 log_info(
"[WS_SOCKET] TCP_QUICKACK=1, TCP_NODELAY=1, SO_RCVBUF=%d (autotuned), SO_SNDBUF=%d", actual_rcv,
161 log_debug(
"[LWS_CALLBACK_ESTABLISHED] Creating ACIP WebSocket transport...");
164 log_error(
"[LWS_CALLBACK_ESTABLISHED] FAILED: acip_websocket_server_transport_create returned NULL");
167 log_debug(
"[LWS_CALLBACK_ESTABLISHED] Transport created: %p", (
void *)conn_data->
transport);
170 websocket_client_context_t *client_ctx =
171 SAFE_CALLOC(1,
sizeof(websocket_client_context_t), websocket_client_context_t *);
173 log_error(
"Failed to allocate client context");
179 client_ctx->transport = conn_data->
transport;
180 SAFE_STRNCPY(client_ctx->client_ip, client_ip,
sizeof(client_ctx->client_ip));
181 client_ctx->client_port = 0;
182 client_ctx->user_data = server->user_data;
185 log_info(
"๐ด ABOUT TO SPAWN HANDLER THREAD: handler=%p, ctx=%p", (
void *)server->handler, (
void *)client_ctx);
186 log_debug(
"[LWS_CALLBACK_ESTABLISHED] Spawning handler thread (handler=%p, ctx=%p)...", (
void *)server->handler,
189 log_info(
"๐ด asciichat_thread_create returned: %d", handler_create_result);
190 if (handler_create_result != 0) {
191 log_error(
"[LWS_CALLBACK_ESTABLISHED] FAILED: asciichat_thread_create returned error");
192 SAFE_FREE(client_ctx);
199 log_debug(
"[LWS_CALLBACK_ESTABLISHED] Handler thread spawned successfully");
200 log_info(
"โ
โ
โ
ESTABLISHED CALLBACK SUCCESS - handler thread spawned! โ
โ
โ
");
204 case LWS_CALLBACK_CLOSED: {
207 log_info(
"โ
โ
โ
LWS_CALLBACK_CLOSED FIRED - WHY IS CONNECTION CLOSING? โ
โ
โ
");
208 log_info(
"[LWS_CALLBACK_CLOSED] WebSocket client disconnected, wsi=%p, handler_started=%d, timestamp=%llu",
209 (
void *)wsi, conn_data ? conn_data->
handler_started : -1, (unsigned long long)close_callback_start_ns);
212 uint16_t close_code = 0;
213 if (in && len >= 2) {
214 close_code = (uint16_t)((uint8_t *)in)[0] << 8 | ((uint8_t *)in)[1];
215 log_info(
"[LWS_CALLBACK_CLOSED] Close frame received with code=%u (1000=normal, 1001=going away, 1006=abnormal, "
216 "1009=message too big)",
220 "[LWS_CALLBACK_CLOSED] No close frame (in=%p, len=%zu) - connection closed without WebSocket close handshake",
239 acip_transport_t *transport_snapshot = NULL;
241 transport_snapshot = conn_data->
transport;
244 log_debug(
"[LWS_CALLBACK_CLOSED] Closing transport=%p for wsi=%p", (
void *)transport_snapshot, (
void *)wsi);
246 if (transport_snapshot && transport_snapshot->methods) {
247 transport_snapshot->methods->close(transport_snapshot);
254 log_debug(
"[LWS_CALLBACK_CLOSED] Waiting for handler thread to complete...");
257 char join_duration_str[32];
258 format_duration_ns((
double)(join_end_ns - join_start_ns), join_duration_str,
sizeof(join_duration_str));
260 log_info(
"[LWS_CALLBACK_CLOSED] Handler thread completed (join took %s)", join_duration_str);
264 if (transport_snapshot) {
265 log_debug(
"[LWS_CALLBACK_CLOSED] Destroying transport=%p", (
void *)transport_snapshot);
275 char total_duration_str[32];
276 format_duration_ns((
double)(close_callback_end_ns - close_callback_start_ns), total_duration_str,
277 sizeof(total_duration_str));
278 log_info(
"[LWS_CALLBACK_CLOSED] Complete cleanup took %s", total_duration_str);
282 case LWS_CALLBACK_SERVER_WRITEABLE: {
283 uint64_t writeable_callback_start_ns =
time_get_ns();
284 atomic_fetch_add(&g_writeable_callback_count, 1);
285 log_dev_every(4500 * US_PER_MS_INT,
"=== LWS_CALLBACK_SERVER_WRITEABLE FIRED === wsi=%p, timestamp=%llu",
286 (
void *)wsi, (
unsigned long long)writeable_callback_start_ns);
290 log_dev_every(4500 * US_PER_MS_INT,
"SERVER_WRITEABLE: No conn_data");
296 log_dev_every(4500 * US_PER_MS_INT,
"SERVER_WRITEABLE: Cleanup in progress, skipping");
301 acip_transport_t *transport_snapshot = conn_data->
transport;
302 if (!transport_snapshot) {
303 log_dev_every(4500 * US_PER_MS_INT,
"SERVER_WRITEABLE: No transport");
307 websocket_transport_data_t *ws_data = (websocket_transport_data_t *)transport_snapshot->impl_data;
308 if (!ws_data || !ws_data->send_queue) {
309 log_dev_every(4500 * US_PER_MS_INT,
"SERVER_WRITEABLE: No ws_data or send_queue");
321 const size_t FRAGMENT_SIZE = 262144;
327 : (conn_data->pending_send_len - conn_data->pending_send_offset);
331 enum lws_write_protocol flags = lws_write_ws_flags(LWS_WRITE_BINARY, is_start, is_end);
334 size_t required_size = LWS_PRE + chunk_size;
335 if (ws_data->send_buffer_capacity < required_size) {
336 SAFE_FREE(ws_data->send_buffer);
337 ws_data->send_buffer = SAFE_MALLOC(required_size, uint8_t *);
338 if (!ws_data->send_buffer) {
339 log_error(
"Failed to allocate send buffer");
345 ws_data->send_buffer_capacity = required_size;
351 int written = lws_write(wsi, ws_data->send_buffer + LWS_PRE, chunk_size, flags);
353 char write_duration_str[32];
354 format_duration_ns((
double)(write_end_ns - write_start_ns), write_duration_str,
sizeof(write_duration_str));
355 log_dev_every(4500 * US_PER_MS_INT,
"lws_write returned %d bytes in %s (chunk_size=%zu)", written,
356 write_duration_str, chunk_size);
359 log_error(
"Server WebSocket write error: %d at offset %zu/%zu", written, conn_data->
pending_send_offset,
367 if ((
size_t)written != chunk_size) {
368 log_warn(
"Server WebSocket partial write: %d/%zu bytes at offset %zu/%zu", written, chunk_size,
372 lws_callback_on_writable(wsi);
380 log_dev_every(4500 * US_PER_MS_INT,
"SERVER_WRITEABLE: Message fully sent (%zu bytes)",
387 log_dev_every(4500 * US_PER_MS_INT,
388 "SERVER_WRITEABLE: Sent fragment %zu/%zu bytes, requesting another callback",
390 lws_callback_on_writable(wsi);
396 mutex_lock(&ws_data->send_mutex);
398 mutex_unlock(&ws_data->send_mutex);
401 mutex_lock(&ws_data->send_mutex);
402 websocket_recv_msg_t msg;
404 mutex_unlock(&ws_data->send_mutex);
406 if (success && msg.data) {
413 log_dev_every(4500 * US_PER_MS_INT,
">>> SERVER_WRITEABLE: Dequeued message %zu bytes, sending first fragment",
417 size_t chunk_size = (msg.len > FRAGMENT_SIZE) ? FRAGMENT_SIZE : msg.len;
419 int is_end = (chunk_size >= msg.len);
421 enum lws_write_protocol flags = lws_write_ws_flags(LWS_WRITE_BINARY, is_start, is_end);
423 size_t required_size = LWS_PRE + chunk_size;
424 if (ws_data->send_buffer_capacity < required_size) {
425 SAFE_FREE(ws_data->send_buffer);
426 ws_data->send_buffer = SAFE_MALLOC(required_size, uint8_t *);
427 if (!ws_data->send_buffer) {
428 log_error(
"Failed to allocate send buffer");
433 ws_data->send_buffer_capacity = required_size;
436 memcpy(ws_data->send_buffer + LWS_PRE, msg.data, chunk_size);
438 int written = lws_write(wsi, ws_data->send_buffer + LWS_PRE, chunk_size, flags);
440 log_error(
"Server WebSocket write error on first fragment: %d", written);
446 if ((
size_t)written != chunk_size) {
447 log_warn(
"Server WebSocket partial write on first fragment: %d/%zu", written, chunk_size);
454 log_dev_every(4500 * US_PER_MS_INT,
455 ">>> SERVER_WRITEABLE: First fragment sent, requesting callback for next fragment");
456 lws_callback_on_writable(wsi);
458 log_dev_every(4500 * US_PER_MS_INT,
"SERVER_WRITEABLE: Message fully sent in first fragment (%zu bytes)",
464 mutex_lock(&ws_data->send_mutex);
466 lws_callback_on_writable(wsi);
468 mutex_unlock(&ws_data->send_mutex);
476 case LWS_CALLBACK_RECEIVE: {
478 log_dev_every(4500 * US_PER_MS_INT,
"LWS_CALLBACK_RECEIVE: conn_data=%p, transport=%p, len=%zu", (
void *)conn_data,
479 conn_data ? (
void *)conn_data->transport : NULL, len);
482 log_error(
"LWS_CALLBACK_RECEIVE: conn_data is NULL! Need to initialize from ESTABLISHED or handle here");
487 if (conn_data->cleaning_up) {
488 log_debug(
"LWS_CALLBACK_RECEIVE: Cleanup in progress, discarding received data");
493 acip_transport_t *transport_snapshot = conn_data->transport;
494 log_info(
"๐ด [WS_RECEIVE] conn_data=%p transport_snapshot=%p handler_started=%d", (
void *)conn_data,
495 (
void *)transport_snapshot, conn_data ? conn_data->handler_started : -1);
496 if (!transport_snapshot) {
497 log_error(
"LWS_CALLBACK_RECEIVE: transport is NULL! ESTABLISHED never called?");
499 const struct lws_protocols *protocol = lws_get_protocol(wsi);
500 if (!protocol || !protocol->user) {
501 log_error(
"LWS_CALLBACK_RECEIVE: Cannot get protocol or user data for fallback initialization");
505 websocket_server_t *server = (websocket_server_t *)protocol->user;
507 lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), NULL, 0, client_ip,
sizeof(client_ip));
509 log_info(
"LWS_CALLBACK_RECEIVE: Initializing transport as fallback (client_ip=%s)", client_ip);
510 conn_data->server = server;
512 conn_data->handler_started =
false;
513 conn_data->pending_send_data = NULL;
514 conn_data->pending_send_len = 0;
515 conn_data->pending_send_offset = 0;
516 conn_data->has_pending_send =
false;
518 if (!conn_data->transport) {
519 log_error(
"LWS_CALLBACK_RECEIVE: Failed to create transport in fallback");
523 log_debug(
"LWS_CALLBACK_RECEIVE: Spawning handler thread in fallback");
524 websocket_client_context_t *client_ctx =
525 SAFE_CALLOC(1,
sizeof(websocket_client_context_t), websocket_client_context_t *);
527 log_error(
"Failed to allocate client context");
529 conn_data->transport = NULL;
533 client_ctx->transport = conn_data->transport;
534 SAFE_STRNCPY(client_ctx->client_ip, client_ip,
sizeof(client_ctx->client_ip));
535 client_ctx->client_port = 0;
536 client_ctx->user_data = server->user_data;
539 log_error(
"LWS_CALLBACK_RECEIVE: Failed to spawn handler thread");
540 SAFE_FREE(client_ctx);
542 conn_data->transport = NULL;
546 conn_data->handler_started =
true;
547 log_info(
"LWS_CALLBACK_RECEIVE: Handler thread spawned in fallback");
550 transport_snapshot = conn_data->transport;
553 if (!in || len == 0) {
558 if (conn_data && conn_data->cleaning_up) {
559 log_debug(
"RECEIVE: Cleanup in progress, ignoring fragment");
567 websocket_transport_data_t *ws_data = (websocket_transport_data_t *)transport_snapshot->impl_data;
568 if (!ws_data || !ws_data->recv_queue) {
569 log_error(
"WebSocket transport has no implementation data or recv_queue (ws_data=%p, recv_queue=%p)",
570 (
void *)ws_data, ws_data ? (
void *)ws_data->recv_queue : NULL);
575 log_debug(
"[WS_TIMING] callback_enter_ns captured: %llu", (
unsigned long long)callback_enter_ns);
577 bool is_first = lws_is_first_fragment(wsi);
578 bool is_final = lws_is_final_fragment(wsi);
579 log_debug(
"[WS_TIMING] is_first=%d is_final=%d, about to increment callback count", is_first, is_final);
580 log_info(
"[WS_FRAG_DEBUG] === RECEIVE CALLBACK: is_first=%d is_final=%d len=%zu ===", is_first, is_final, len);
582 atomic_fetch_add(&g_receive_callback_count, 1);
583 log_debug(
"[WS_TIMING] incremented callback count");
591 int fd = lws_get_socket_fd(wsi);
594 setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, &quickack,
sizeof(quickack));
601 atomic_store(&g_writeable_callback_count, 0);
602 atomic_store(&g_receive_callback_count, 1);
609 uint64_t frag_num = atomic_load(&g_receive_callback_count);
610 log_info(
"[WS_FRAG] Fragment #%llu: %zu bytes (first=%d final=%d)", (
unsigned long long)frag_num, len, is_first,
615 log_dev_every(4500 * US_PER_MS_INT,
"WebSocket fragment: %zu bytes (first=%d, final=%d)", len, is_first, is_final);
616 if (len > 0 && len <= 256) {
617 const uint8_t *bytes = (
const uint8_t *)in;
620 for (
size_t i = 0; i < len && hex_pos <
sizeof(hex_buf) - 4; i++) {
621 hex_pos += snprintf(hex_buf + hex_pos,
sizeof(hex_buf) - hex_pos,
"%02x ", bytes[i]);
623 log_dev_every(4500 * US_PER_MS_INT,
" Raw bytes: %s", hex_buf);
627 if (is_first && is_final && len >= 18) {
628 const uint8_t *data = (
const uint8_t *)in;
630 uint16_t pkt_type = (data[8] << 8) | data[9];
631 uint32_t pkt_len = (data[10] << 24) | (data[11] << 16) | (data[12] << 8) | data[13];
632 log_dev_every(4500 * US_PER_MS_INT,
"Single-fragment ACIP packet: type=%d (0x%x) len=%u total_size=%zu", pkt_type,
633 pkt_type, pkt_len, len);
642 websocket_recv_msg_t msg;
645 log_error(
"Failed to allocate buffer for fragment (%zu bytes)", len);
649 memcpy(msg.data, in, len);
651 msg.first = is_first;
652 msg.final = is_final;
654 mutex_lock(&ws_data->recv_mutex);
658 if (!ws_data->recv_queue) {
659 log_warn(
"recv_queue was cleared during lock wait, dropping fragment");
660 mutex_unlock(&ws_data->recv_mutex);
667 size_t queue_capacity = ws_data->recv_queue->capacity;
668 size_t queue_free = queue_capacity - queue_current_size;
669 log_dev_every(4500 * US_PER_MS_INT,
"[WS_FLOW] Queue: free=%zu/%zu (used=%zu)", queue_free, queue_capacity,
675 log_warn(
"[WS_FLOW] Receive queue FULL - dropping fragment (len=%zu, first=%d, final=%d)", len, is_first,
679 mutex_unlock(&ws_data->recv_mutex);
684 log_dev(
"[WS_DEBUG] RECEIVE: About to signal recv_cond (queue size=%zu)",
ringbuffer_size(ws_data->recv_queue));
685 cond_signal(&ws_data->recv_cond);
686 log_dev(
"[WS_DEBUG] RECEIVE: Signaled recv_cond");
687 mutex_unlock(&ws_data->recv_mutex);
688 log_dev(
"[WS_DEBUG] RECEIVE: Unlocked recv_mutex");
692 lws_callback_on_writable(wsi);
694 log_info(
"[WS_FRAG] Queued fragment: %zu bytes (first=%d final=%d, total_fragments=%llu)", len, is_first, is_final,
695 (
unsigned long long)atomic_load(&g_receive_callback_count));
700 double callback_dur_us = (double)(callback_exit_ns - callback_enter_ns) / 1e3;
701 if (callback_dur_us > 200) {
702 log_warn(
"[WS_CALLBACK_DURATION] RECEIVE callback took %.1f ยตs (> 200ยตs threshold)", callback_dur_us);
704 log_debug(
"[WS_CALLBACK_DURATION] RECEIVE callback completed in %.1f ยตs (fragment: first=%d final=%d len=%zu)",
705 callback_dur_us, is_first, is_final, len);
707 log_debug(
"[WS_RECEIVE] ===== RECEIVE CALLBACK COMPLETE, returning 0 to continue =====");
708 log_info(
"[WS_RECEIVE_RETURN] Returning 0 from RECEIVE callback (success). fragmented=%d (first=%d final=%d)",
709 (!is_final ? 1 : 0), is_first, is_final);
713 case LWS_CALLBACK_FILTER_HTTP_CONNECTION: {
715 log_info(
"[FILTER_HTTP_CONNECTION] WebSocket upgrade request (allow protocol upgrade)");
719 case LWS_CALLBACK_PROTOCOL_INIT: {
721 log_info(
"[PROTOCOL_INIT] Protocol initialized, proto=%s", proto_name);
725 case LWS_CALLBACK_EVENT_WAIT_CANCELLED: {
729 log_dev_every(4500 * US_PER_MS_INT,
"LWS_CALLBACK_EVENT_WAIT_CANCELLED triggered - requesting writable callbacks");
730 const struct lws_protocols *protocol = lws_get_protocol(wsi);
732 log_dev_every(4500 * US_PER_MS_INT,
"EVENT_WAIT_CANCELLED: Calling lws_callback_on_writable_all_protocol");
733 lws_callback_on_writable_all_protocol(lws_get_context(wsi), protocol);
735 log_error(
"EVENT_WAIT_CANCELLED: No protocol found on wsi");
750static struct lws_protocols websocket_protocols[] = {
753 websocket_server_callback,
762 websocket_server_callback,
769 {NULL, NULL, 0, 0, 0, NULL, 0}
787static const struct lws_extension websocket_extensions[] = {
788 {
"permessage-deflate", lws_extension_callback_pm_deflate,
"permessage-deflate; server_max_window_bits=8"},
792 if (!server || !config || !config->client_handler) {
793 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters");
796 memset(server, 0,
sizeof(*server));
797 server->handler = config->client_handler;
798 server->user_data = config->user_data;
799 server->port = config->port;
800 atomic_store(&server->running,
true);
804 websocket_protocols[0].user = server;
805 websocket_protocols[1].user = server;
808 lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO | LLL_DEBUG, websocket_lws_log_callback);
811 struct lws_context_creation_info info = {0};
812 info.port = config->port;
813 info.protocols = websocket_protocols;
814 info.gid = (gid_t)-1;
815 info.uid = (uid_t)-1;
817 info.extensions = NULL;
823 server->context = lws_create_context(&info);
824 if (!server->context) {
825 return SET_ERRNO(ERROR_NETWORK_BIND,
"Failed to create libwebsockets context");
828 log_info(
"WebSocket server initialized on port %d with static file serving", config->port);
833 if (!server || !server->context) {
834 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid server");
837 log_info(
"WebSocket server starting event loop on port %d", server->port);
840 uint64_t last_service_ns = 0;
841 int service_call_count = 0;
842 while (atomic_load(&server->running)) {
849 if (last_service_ns && service_start_ns - last_service_ns > 30 * US_PER_MS_INT) {
851 double gap_ms = (double)(service_start_ns - last_service_ns) / 1e6;
852 log_info_every(1 * US_PER_MS_INT,
"[LWS_SERVICE_GAP] %.1fms gap between lws_service calls", gap_ms);
854 service_call_count++;
855 log_debug_every(500 * US_PER_MS_INT,
"[LWS_SERVICE] Call #%d, context=%p", service_call_count,
856 (
void *)server->context);
857 int result = lws_service(server->context, 50);
859 log_error(
"libwebsockets service error: %d", result);
864 log_info(
"WebSocket server event loop exited, destroying context from event loop thread");
871 if (server->context) {
872 lws_context_destroy(server->context);
873 server->context = NULL;
876 log_info(
"WebSocket server context destroyed");
881 if (server && server->context) {
882 lws_cancel_service(server->context);
891 atomic_store(&server->running,
false);
896 if (server->context) {
897 log_debug(
"WebSocket context still alive in destroy, cleaning up");
898 lws_context_destroy(server->context);
899 server->context = NULL;
902 log_debug(
"WebSocket server destroyed");
void buffer_pool_free(buffer_pool_t *pool, void *data, size_t size)
void * buffer_pool_alloc(buffer_pool_t *pool, size_t size)
asciichat_error_t websocket_server_init(websocket_server_t *server, const websocket_server_config_t *config)
asciichat_error_t websocket_server_run(websocket_server_t *server)
void websocket_server_destroy(websocket_server_t *server)
void websocket_server_cancel_service(websocket_server_t *server)
size_t ringbuffer_size(const ringbuffer_t *rb)
bool ringbuffer_is_empty(const ringbuffer_t *rb)
bool ringbuffer_read(ringbuffer_t *rb, void *data)
bool ringbuffer_write(ringbuffer_t *rb, const void *data)
Per-connection user data.
asciichat_thread_t handler_thread
Client handler thread.
bool cleaning_up
True if cleanup is in progress (prevents race with remove_client)
bool handler_started
True if handler thread was started.
acip_transport_t * transport
ACIP transport for this connection.
uint8_t * pending_send_data
Current message being sent.
bool has_pending_send
True if there's an in-progress message.
size_t pending_send_len
Total length of message being sent.
websocket_server_t * server
Back-reference to server.
size_t pending_send_offset
Current offset in message (bytes already sent)
void acip_transport_destroy(acip_transport_t *transport)
int asciichat_thread_create(asciichat_thread_t *thread, void *(*start_routine)(void *), void *arg)
int asciichat_thread_join(asciichat_thread_t *thread, void **retval)
uint64_t time_get_ns(void)
int format_duration_ns(double nanoseconds, char *buffer, size_t buffer_size)
acip_transport_t * acip_websocket_server_transport_create(struct lws *wsi, crypto_context_t *crypto_ctx)
Create WebSocket server transport from existing connection.