ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
src/server/client.c
Go to the documentation of this file.
1
103#include <ascii-chat/atomic.h>
104#include <stdio.h>
105#include <string.h>
106#include <time.h>
107#include <errno.h>
108#ifndef _WIN32
109#include <netinet/tcp.h>
110#endif
111
112#include "client.h"
113#include "main.h"
114#include "protocol.h"
115#include "render.h"
116#include "stream.h"
117#include "crypto.h"
123#include <ascii-chat/common.h>
129#include <ascii-chat/options/rcu.h> // For RCU-based options access
144#include <ascii-chat/uthash.h>
147#include <ascii-chat/util/time.h>
153#include <ascii-chat/log/io.h>
154
155// FFmpeg headers for H.265 decoding
156#include <libavcodec/avcodec.h>
157#include <libavutil/frame.h>
158#include <libavutil/imgutils.h>
159#include <libavutil/pixfmt.h>
160#include <libswscale/swscale.h>
161
162// Debug flags
163#define DEBUG_NETWORK 1
164#define DEBUG_THREADS 1
165#define DEBUG_MEMORY 1
166
167// =============================================================================
168// Packet Handler Dispatch (O(1) hash table lookup)
169// =============================================================================
170
171typedef void (*client_packet_handler_t)(client_info_t *client, const void *data, size_t len);
172
173#define CLIENT_DISPATCH_HASH_SIZE 32
174#define CLIENT_DISPATCH_HANDLER_COUNT 13
175
183
184#define CLIENT_DISPATCH_HASH(type) ((type) % CLIENT_DISPATCH_HASH_SIZE)
185
186static inline int client_dispatch_hash_lookup(const client_dispatch_entry_t *table, packet_type_t type) {
188 for (int i = 0; i < CLIENT_DISPATCH_HASH_SIZE; i++) {
189 uint32_t slot = (h + i) % CLIENT_DISPATCH_HASH_SIZE;
190 if (table[slot].key == 0)
191 return -1;
192 if (table[slot].key == type)
193 return table[slot].handler_idx;
194 }
195 return -1;
196}
197
198// Handler array (indexed by hash lookup result)
199static const client_packet_handler_t g_client_dispatch_handlers[CLIENT_DISPATCH_HANDLER_COUNT] = {
213};
214
215// Hash table mapping packet type -> handler index
216// clang-format off
217static const client_dispatch_entry_t g_client_dispatch_hash[CLIENT_DISPATCH_HASH_SIZE] = {
218 [0] = {PACKET_TYPE_AUDIO_BATCH, 2}, // hash(4000)=0
219 [1] = {PACKET_TYPE_PROTOCOL_VERSION, 0}, // hash(1)=1
220 [2] = {PACKET_TYPE_AUDIO_OPUS_BATCH, 3}, // hash(4001)=1, probed->2
221 [8] = {PACKET_TYPE_CLIENT_CAPABILITIES, 8}, // hash(5000)=8
222 [9] = {PACKET_TYPE_PING, 9}, // hash(5001)=9
223 [10] = {PACKET_TYPE_PONG, 10}, // hash(5002)=10
224 [11] = {PACKET_TYPE_CLIENT_JOIN, 4}, // hash(5003)=11
225 [12] = {PACKET_TYPE_CLIENT_LEAVE, 5}, // hash(5004)=12
226 [13] = {PACKET_TYPE_STREAM_START, 6}, // hash(5005)=13
227 [14] = {PACKET_TYPE_STREAM_STOP, 7}, // hash(5006)=14
228 [20] = {PACKET_TYPE_REMOTE_LOG, 11}, // hash(2004)=20
229 [25] = {PACKET_TYPE_IMAGE_FRAME, 1}, // hash(3001)=25
230 [26] = {PACKET_TYPE_IMAGE_FRAME_H265, 12}, // hash(3002)=26
231};
232// clang-format on
233
234// Forward declarations for static helper functions
235static inline void cleanup_client_all_buffers(client_info_t *client);
236
251// NOLINTNEXTLINE(misc-unused-function) - Used for debug sync inspection, may be disabled in some builds
252static void register_client_info_atomics(client_info_t *client) {
253 if (!client || !client->client_id[0]) {
254 return;
255 }
256
257 // Synchronization primitives (mutexes, condition variables)
258 NAMED_REGISTER_MUTEX(&client->client_state_mutex, "client_state_mutex", (uintptr_t)(const void *)(client));
259 NAMED_REGISTER_MUTEX(&client->send_mutex, "send_mutex", (uintptr_t)(const void *)(client));
260 NAMED_REGISTER_COND(&client->dispatch_queue_cond, "dispatch_queue_cond", (uintptr_t)(const void *)(client));
261
262 // Child structures (buffers, queues) - register with client as parent for hierarchical naming
263 if (client->incoming_video_buffer) {
264 NAMED_REGISTER_VIDEO_FRAME_BUFFER(client->incoming_video_buffer, "incoming_video_buffer",
265 (uintptr_t)(const void *)(client));
266 }
267 if (client->outgoing_video_buffer) {
268 NAMED_REGISTER_VIDEO_FRAME_BUFFER(client->outgoing_video_buffer, "outgoing_video_buffer",
269 (uintptr_t)(const void *)(client));
270 }
271 if (client->incoming_audio_buffer) {
272 NAMED_REGISTER_AUDIO_RINGBUF(client->incoming_audio_buffer, "incoming_audio_buffer",
273 (uintptr_t)(const void *)(client));
274 }
275 if (client->audio_queue) {
276 NAMED_REGISTER_PACKET_QUEUE(client->audio_queue, "audio_queue", (uintptr_t)(const void *)(client));
277 }
278 if (client->received_packet_queue) {
279 NAMED_REGISTER_PACKET_QUEUE(client->received_packet_queue, "received_packet_queue",
280 (uintptr_t)(const void *)(client));
281 }
282 // Crypto context is always present (embedded in client_info_t)
283 NAMED_REGISTER_CRYPTO_CONTEXT(&client->crypto_handshake_ctx, "crypto_handshake_ctx",
284 (uintptr_t)(const void *)(client));
285
286 // Video streaming state
287 NAMED_REGISTER_ATOMIC(&client->is_sending_video, "is_sending_video", (uintptr_t)(const void *)(client));
288
289 // Audio streaming state
290 NAMED_REGISTER_ATOMIC(&client->is_sending_audio, "is_sending_audio", (uintptr_t)(const void *)(client));
291
292 // Connection state
293 NAMED_REGISTER_ATOMIC(&client->active, "active", (uintptr_t)(const void *)(client));
294 NAMED_REGISTER_ATOMIC(&client->shutting_down, "shutting_down", (uintptr_t)(const void *)(client));
295 NAMED_REGISTER_ATOMIC(&client->protocol_disconnect_requested, "protocol_disconnect_requested",
296 (uintptr_t)(const void *)(client));
297
298 // Thread management flags
299 NAMED_REGISTER_ATOMIC(&client->dispatch_thread_running, "dispatch_thread_running", (uintptr_t)(const void *)(client));
300 NAMED_REGISTER_ATOMIC(&client->send_thread_running, "send_thread_running", (uintptr_t)(const void *)(client));
301 NAMED_REGISTER_ATOMIC(&client->video_render_thread_running, "video_render_thread_running",
302 (uintptr_t)(const void *)(client));
303 NAMED_REGISTER_ATOMIC(&client->audio_render_thread_running, "audio_render_thread_running",
304 (uintptr_t)(const void *)(client));
305
306 // Frame source tracking
307 NAMED_REGISTER_ATOMIC(&client->last_rendered_grid_sources, "last_rendered_grid_sources_count",
308 (uintptr_t)(const void *)(client));
309 NAMED_REGISTER_ATOMIC(&client->last_sent_grid_sources, "last_sent_grid_sources_count",
310 (uintptr_t)(const void *)(client));
311 NAMED_REGISTER_ATOMIC(&client->frames_sent_count, "total_frames_sent_count", (uintptr_t)(const void *)(client));
312}
313
314static void handle_client_error_packet(client_info_t *client, const void *data, size_t len) {
315 asciichat_error_t reported_error = ASCIICHAT_OK;
316 char message[MAX_ERROR_MESSAGE_LENGTH + 1] = {0};
317
318 asciichat_error_t parse_result =
319 packet_parse_error_message(data, len, &reported_error, message, sizeof(message), NULL);
320 const char *client_id = client ? client->client_id : "unknown";
321
322 if (parse_result != ASCIICHAT_OK) {
323 log_warn("Failed to parse error packet from client %s: %s", client_id, asciichat_error_string(parse_result));
324 return;
325 }
326
327 log_error("Client %s reported error %d (%s): %s", client_id, reported_error, asciichat_error_string(reported_error),
328 message);
329}
330
348
363
366
367// Forward declarations for internal functions
368// client_receive_thread is implemented below
369void *client_send_thread_func(void *arg);
370void *client_dispatch_thread(void *arg);
372static int start_client_threads(server_context_t *server_ctx, client_info_t *client,
373 bool is_tcp);
374
375static void initialize_default_render_capabilities(client_info_t *client) {
376 if (!client) {
377 return;
378 }
379
380 // Keep safe defaults for early control packets, but do not mark capabilities
381 // complete. The render thread must wait for the browser's actual dimensions
382 // and requested frame rate instead of flooding a just-opened DataChannel at
383 // the fallback rate.
384 client->width = 64;
385 client->height = 16;
387 client->terminal_caps.color_count = 2;
391 client->terminal_caps.wants_padding = false;
392 client->terminal_caps.utf8_support = false;
393 client->terminal_caps.detection_reliable = false;
397 client->client_luminance_palette) == 0;
398 client->has_terminal_caps = false;
399}
400
401/* ============================================================================
402 * Client Lookup Functions
403 * ============================================================================
404 */
405
429client_info_t *find_client_by_id(const char *client_id) {
430 if (!client_id || client_id[0] == '\0') {
431 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid client ID");
432 return NULL;
433 }
434
435 // Protect uthash lookup with read lock to prevent concurrent access issues
437
438 client_info_t *result = NULL;
439 HASH_FIND_STR(g_client_manager.clients_by_id, client_id, result);
440
442
443 if (!result) {
444 log_warn("Client not found for ID %s", client_id);
445 }
446
447 return result;
448}
449
475
476 for (int i = 0; i < MAX_CLIENTS; i++) {
480 return client;
481 }
482 }
483
485 return NULL;
486}
487
488/* ============================================================================
489 * Client Management Functions
490 * ============================================================================
491 */
492
499static void configure_client_socket(socket_t socket, const char *client_id) {
500 // Enable TCP keepalive to detect dead connections
501 asciichat_error_t keepalive_result = set_socket_keepalive(socket);
502 if (keepalive_result != ASCIICHAT_OK) {
503 log_warn("Failed to set socket keepalive for client %s: %s", client_id, asciichat_error_string(keepalive_result));
504 }
505
506 // Set socket buffer sizes for large data transmission
507 const int SOCKET_SEND_BUFFER_SIZE = 1024 * 1024; // 1MB send buffer
508 const int SOCKET_RECV_BUFFER_SIZE = 1024 * 1024; // 1MB receive buffer
509
510 if (socket_setsockopt(socket, SOL_SOCKET, SO_SNDBUF, &SOCKET_SEND_BUFFER_SIZE, sizeof(SOCKET_SEND_BUFFER_SIZE)) < 0) {
511 log_warn("Failed to set send buffer size for client %s: %s", client_id, network_error_string());
512 }
513
514 if (socket_setsockopt(socket, SOL_SOCKET, SO_RCVBUF, &SOCKET_RECV_BUFFER_SIZE, sizeof(SOCKET_RECV_BUFFER_SIZE)) < 0) {
515 log_warn("Failed to set receive buffer size for client %s: %s", client_id, network_error_string());
516 }
517
518 // Enable TCP_NODELAY to reduce latency for large packets (disables Nagle algorithm)
519 const int TCP_NODELAY_VALUE = 1;
520 if (socket_setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, &TCP_NODELAY_VALUE, sizeof(TCP_NODELAY_VALUE)) < 0) {
521 log_warn("Failed to set TCP_NODELAY for client %s: %s", client_id, network_error_string());
522 }
523}
524
542static int start_client_threads(server_context_t *server_ctx, client_info_t *client, bool is_tcp) {
543 if (!server_ctx || !client) {
544 SET_ERRNO(ERROR_INVALID_PARAM, "server_ctx or client is NULL");
545 return -1;
546 }
547
548 const char *client_id = client->client_id;
549 log_info("★ START_CLIENT_THREADS: client_id=%s is_tcp=%d (about to create %s threads)", client_id, is_tcp,
550 is_tcp ? "TCP" : "WebRTC/WebSocket");
551 char thread_name[64];
552 asciichat_error_t result;
553
554 // Step 0: Send initial server state BEFORE spawning receive thread
555 // This prevents use-after-free if the receive thread immediately errors and calls remove_client()
556 if (send_server_state_to_client(client) != 0) {
557 log_warn("Failed to send initial server state to client %s", client_id);
558 }
559
560 // Get current client count for state packet
562 uint32_t connected_count = g_client_manager.client_count;
564
566 state.connected_client_count = connected_count;
567 state.active_client_count = 0; // Will be updated by broadcast thread
568 memset(state.reserved, 0, sizeof(state.reserved));
569
570 // Convert to network byte order
571 server_state_packet_t net_state;
574 memset(net_state.reserved, 0, sizeof(net_state.reserved));
575
576 // Send initial server state via ACIP transport
577 asciichat_error_t packet_result = acip_send_server_state(client->transport, &net_state);
578 if (packet_result != ASCIICHAT_OK) {
579 log_warn("Failed to send initial server state to client %s: %s", client_id, asciichat_error_string(packet_result));
580 } else {
581 log_debug("Sent initial server state to client %s: %u connected clients", client_id, state.connected_client_count);
582 }
583
584 // Step 1: Create receive thread
585 if (is_tcp) {
586 safe_snprintf(thread_name, sizeof(thread_name), "receive_%s", client->client_id);
587 result =
588 tcp_server_spawn_thread(server_ctx->tcp_server, client->socket, client_receive_thread, client, 1, thread_name);
589 } else {
590 safe_snprintf(thread_name, sizeof(thread_name), "receive_%s", client->client_id);
591 log_debug("THREAD_CREATE: WebRTC client %s", client->client_id);
592 log_debug(" client=%p, func=%p, &receive_thread=%p", (void *)client, (void *)client_receive_thread,
593 (void *)&client->receive_thread);
594 log_debug(" Pre-create: receive_thread value=%p", (void *)(uintptr_t)client->receive_thread);
595 result = asciichat_thread_create(&client->receive_thread, thread_name, client_receive_thread, client);
596 log_debug(" Post-create: result=%d, receive_thread value=%p", result, (void *)(uintptr_t)client->receive_thread);
597 }
598
599 if (result != ASCIICHAT_OK) {
600 log_error("Failed to create receive thread for %s client %s: %s", is_tcp ? "TCP" : "WebRTC", client_id,
601 asciichat_error_string(result));
602 remove_client(server_ctx, client_id);
603 return -1;
604 }
605 log_debug("Created receive thread for %s client %s", is_tcp ? "TCP" : "WebRTC", client_id);
606
607 // Step 1b: Create async dispatch thread (processes queued packets)
608 // This decouples receive from dispatch to prevent backpressure on the socket
609 if (!is_tcp) { // Only for WebRTC/WebSocket clients that need async dispatch
610 safe_snprintf(thread_name, sizeof(thread_name), "dispatch_%s", client->client_id);
611 atomic_store_bool(&client->dispatch_thread_running, true);
612 result = asciichat_thread_create(&client->dispatch_thread, thread_name, client_dispatch_thread, client);
613 if (result != ASCIICHAT_OK) {
614 log_error("Failed to create dispatch thread for client %s: %s", client->client_id,
615 asciichat_error_string(result));
616 remove_client(server_ctx, client->client_id);
617 return -1;
618 }
619 log_debug("Created async dispatch thread for client %s", client->client_id);
620 }
621
622 // Step 2: Create render threads BEFORE send thread
623 // This ensures the render threads generate the first frame before the send thread tries to read it
624 log_info("★★★ CLIENT SETUP: About to create render threads for client %s", client->client_id);
625 if (create_client_render_threads(server_ctx, client) != 0) {
626 log_error("Failed to create render threads for client %s", client->client_id);
627 remove_client(server_ctx, client->client_id);
628 return -1;
629 }
630 log_info("★★★ CLIENT SETUP: Successfully created render threads for client %s", client->client_id);
631
632 // Step 3: Create send thread AFTER render threads are running
633 if (is_tcp) {
634 safe_snprintf(thread_name, sizeof(thread_name), "send_%s", client->client_id);
635 result = tcp_server_spawn_thread(server_ctx->tcp_server, client->socket, client_send_thread_func, client, 3,
636 thread_name);
637 } else {
638 safe_snprintf(thread_name, sizeof(thread_name), "send_%s", client->client_id);
639 result = asciichat_thread_create(&client->send_thread, thread_name, client_send_thread_func, client);
640 }
641
642 if (result != ASCIICHAT_OK) {
643 log_error("Failed to create send thread for %s client %s: %s", is_tcp ? "TCP" : "WebRTC", client_id,
644 asciichat_error_string(result));
645 remove_client(server_ctx, client_id);
646 return -1;
647 }
648 log_debug("Created send thread for %s client %s", is_tcp ? "TCP" : "WebRTC", client_id);
649
650 // Step 4: Broadcast server state to ALL clients AFTER the new client is fully set up
652
653 return 0;
654}
655
656client_info_t *add_client(server_context_t *server_ctx, socket_t socket, const char *client_ip, int port) {
657 log_info("[TCP_ADD_CLIENT] ENTER: client_ip=%s, port=%d", client_ip, port);
658 // Find empty slot WITHOUT holding the global lock
659 // We'll re-verify under lock after allocations complete
660 int slot = -1;
661 int existing_count = 0;
662 for (int i = 0; i < MAX_CLIENTS; i++) {
663 if (slot == -1 && g_client_manager.clients[i].client_id[0] == '\0') {
664 slot = i; // Take first available slot
665 }
667 existing_count++;
668 }
669 }
670
671 // Quick pre-check before expensive allocations
672 log_info("[TCP_DBG] SLOT_CHECK: existing_count=%d, max_clients=%d, slot=%d", existing_count, GET_OPTION(max_clients),
673 slot);
674 if (existing_count >= GET_OPTION(max_clients) || slot == -1) {
675 const char *reject_msg = "SERVER_FULL: Maximum client limit reached\n";
676 ssize_t send_result = socket_send(socket, reject_msg, strlen(reject_msg), 0);
677 if (send_result < 0) {
678 log_warn("Failed to send rejection message to client: %s", SAFE_STRERROR(errno));
679 }
680 return NULL;
681 }
682
683 // NOW acquire the lock early for generating unique client name
684 log_info("[TCP_DBG] LOCK_ACQUIRE_START");
686 log_info("[TCP_DBG] LOCK_ACQUIRED");
687
688 // Generate unique client ID (noun only, without counter or port)
689 char new_client_id[MAX_CLIENT_ID_LEN];
690 log_info("[TCP_DBG] GENERATE_ID_START");
691 if (generate_client_id(new_client_id, sizeof(new_client_id)) != 0) {
693 log_error("Failed to generate unique client ID");
694 return NULL;
695 }
696 log_info("[TCP_DBG] GENERATE_ID_DONE: id=%s", new_client_id);
697
698 log_info("[TCP_DBG] LOCK_RELEASE_START");
700 log_info("[TCP_DBG] LOCK_RELEASED");
701
702 // DO EXPENSIVE ALLOCATIONS OUTSIDE THE LOCK
703 // This prevents blocking frame processing during client initialization
704 log_info("[TCP_DBG] VIDEO_CREATE_START: About to create video frame buffer for client_id=%s", new_client_id);
705 video_frame_buffer_t *incoming_video_buffer = video_frame_buffer_create(new_client_id);
706 log_info("[TCP_DBG] VIDEO_CREATE_DONE: Video buffer creation returned %p", (void *)incoming_video_buffer);
707 if (!incoming_video_buffer) {
708 SET_ERRNO(ERROR_MEMORY, "Failed to create video buffer for client %s", new_client_id);
709 log_error("Failed to create video buffer for client %s", new_client_id);
710 return NULL;
711 }
712
713 log_info("[TCP_DBG] VIDEO_DONE: About to create audio buffer");
715 log_info("[TCP_DBG] AUDIO_DONE: Audio buffer created");
716 if (!incoming_audio_buffer) {
717 SET_ERRNO(ERROR_MEMORY, "Failed to create audio buffer for client %s", new_client_id);
718 log_error("Failed to create audio buffer for client %s", new_client_id);
719 video_frame_buffer_destroy(incoming_video_buffer);
720 return NULL;
721 }
722
723 log_info("[TCP_DBG] AUDIO_QUEUE_START: Creating audio queue");
724 // Keep the per-client queue allocation independent. Registering a 1000-node
725 // pool for every client can block a second client while the first client's
726 // debug registrations are being retired during disconnect.
727 packet_queue_t *audio_queue = packet_queue_create(500);
728 log_info("[TCP_DBG] AUDIO_QUEUE_DONE");
729 if (!audio_queue) {
730 LOG_ERRNO_IF_SET("Failed to create audio queue for client");
731 audio_ring_buffer_destroy(incoming_audio_buffer);
732 video_frame_buffer_destroy(incoming_video_buffer);
733 return NULL;
734 }
735
736 log_info("[TCP_DBG] OUTGOING_VID_START: Creating outgoing video buffer");
737 video_frame_buffer_t *outgoing_video_buffer = video_frame_buffer_create(new_client_id);
738 log_info("[TCP_DBG] OUTGOING_VID_DONE");
739 if (!outgoing_video_buffer) {
740 LOG_ERRNO_IF_SET("Failed to create outgoing video buffer for client");
741 packet_queue_destroy(audio_queue);
742 audio_ring_buffer_destroy(incoming_audio_buffer);
743 video_frame_buffer_destroy(incoming_video_buffer);
744 return NULL;
745 }
746
747 void *send_buffer = SAFE_MALLOC_ALIGNED(MAX_FRAME_BUFFER_SIZE, 64, void *);
748 if (!send_buffer) {
749 log_error("Failed to allocate send buffer for client %s", new_client_id);
750 video_frame_buffer_destroy(outgoing_video_buffer);
751 packet_queue_destroy(audio_queue);
752 audio_ring_buffer_destroy(incoming_audio_buffer);
753 video_frame_buffer_destroy(incoming_video_buffer);
754 return NULL;
755 }
756
757 // NOW acquire the lock for the critical section: slot assignment + registration
758 log_info("[TCP_DBG] LOCK_START: About to acquire client manager write lock");
760 log_info("[TCP_DBG] LOCK_ACQUIRED: Client manager lock acquired");
761 log_info("[TCP_DBG] SLOT_RECHECK: About to check if slot is still available");
762
763 // Re-check slot availability under lock (another thread might have taken it)
764 if (g_client_manager.clients[slot].client_id[0] != '\0') {
766 SAFE_FREE(send_buffer);
767 video_frame_buffer_destroy(outgoing_video_buffer);
768 packet_queue_destroy(audio_queue);
769 audio_ring_buffer_destroy(incoming_audio_buffer);
770 video_frame_buffer_destroy(incoming_video_buffer);
771
772 const char *reject_msg = "SERVER_FULL: Slot reassigned, try again\n";
773 socket_send(socket, reject_msg, strlen(reject_msg), 0);
774 return NULL;
775 }
776
777 // Now we have exclusive access to the slot - do the actual registration
778 client_info_t *client = &g_client_manager.clients[slot];
779 memset(client, 0, sizeof(client_info_t));
780
781 // Initialize codec capabilities to zero (no codecs supported until CLIENT_CAPABILITIES received)
782 client->codec_capabilities_video = 0;
783 client->codec_capabilities_audio = 0;
784
785 client->socket = socket;
786 client->is_tcp_client = true;
787 SAFE_STRNCPY(client->client_id, new_client_id, sizeof(client->client_id) - 1);
788 SAFE_STRNCPY(client->client_ip, client_ip, sizeof(client->client_ip) - 1);
789 client->port = port;
790 atomic_store_bool(&client->active, true);
791 client->server_ctx = server_ctx; // Store server context for cleanup
792 atomic_store_bool(&client->shutting_down, false);
795 client->connected_at = time(NULL);
796
797 memset(&client->crypto_handshake_ctx, 0, sizeof(client->crypto_handshake_ctx));
798 client->crypto_initialized = false;
799 client->transport_encrypted = false;
800
801 client->pending_packet_type = 0;
802 client->pending_packet_payload = NULL;
803 client->pending_packet_length = 0;
804
805 // Assign pre-allocated buffers
806 client->incoming_video_buffer = incoming_video_buffer;
807 client->incoming_audio_buffer = incoming_audio_buffer;
808 client->audio_queue = audio_queue;
809 client->outgoing_video_buffer = outgoing_video_buffer;
810 client->send_buffer = send_buffer;
812
813 // Generate unique noun-based client name with transport type and port
814 // Note: We're holding the write lock here, so it's safe to iterate existing clients
815 log_info("[TCP_DBG] BEFORE_GENERATE_CLIENT_NAME");
817 true /* is_tcp */) != 0) {
818 // Fallback to numeric name if generation fails
819 safe_snprintf(client->display_name, sizeof(client->display_name), "client_%u (tcp:%d)", new_client_id, port);
820 }
821 log_info("[TCP_DBG] AFTER_GENERATE_CLIENT_NAME: display_name=%s", client->display_name);
822
823 // Register client with named debug system using the generated name
824 (void)NAMED_REGISTER_CLIENT(client, client->display_name, NULL);
825
826 log_info("Added new client %s from %s:%d (socket=%d, slot=%d)", new_client_id, client_ip, port, socket, slot);
827 log_debug("Client slot assigned: client_id=%s assigned to slot %d, socket=%d", new_client_id, slot, socket);
828
829 // Register socket with tcp_server
830 asciichat_error_t reg_result = tcp_server_add_client(server_ctx->tcp_server, socket, client);
831 if (reg_result != ASCIICHAT_OK) {
832 SET_ERRNO(ERROR_INTERNAL, "Failed to register client socket with tcp_server");
833 log_error("Failed to register client %s socket with tcp_server", new_client_id);
834 // Don't unlock here - error_cleanup will do it
835 goto error_cleanup;
836 }
837
839 log_debug("Client count updated: now %d clients (added client_id=%s to slot %d)", g_client_manager.client_count,
840 new_client_id, slot);
841
842 HASH_ADD_STR(g_client_manager.clients_by_id, client_id, client);
843 log_debug("Added client %s to uthash table", new_client_id);
844
846
847 // Configure socket OUTSIDE lock
848 configure_client_socket(socket, new_client_id);
849
850 // Initialize mutexes OUTSIDE lock BEFORE registering them
851 // register_client_info_atomics needs these mutexes to be initialized first
852 if (mutex_init(&client->client_state_mutex, "client_state") != 0) {
853 log_error("Failed to initialize client state mutex for client %s", new_client_id);
854 remove_client(server_ctx, new_client_id);
855 return NULL;
856 }
857
858 if (mutex_init(&client->send_mutex, "client_send") != 0) {
859 log_error("Failed to initialize send mutex for client %s", new_client_id);
860 remove_client(server_ctx, new_client_id);
861 return NULL;
862 }
863
864 // Register all atomic fields for debug tracking - DISABLED to prevent deadlock
865 // register_client_info_atomics() and audio_ring_buffer_register_atomics() call
866 // NAMED_REGISTER which tries to acquire WRITE lock on named_registry.entries_lock
867 // The debug_sync thread holds READ lock while printing, causing reader-writer deadlock
868 // These are debug-only features not critical for functionality
869 // log_info("[TCP_DBG] BEFORE_REGISTER_CLIENT_ATOMICS");
870 // register_client_info_atomics(client);
871 // log_info("[TCP_DBG] AFTER_REGISTER_CLIENT_ATOMICS");
872
873 // Audio buffer atomics registration disabled - same deadlock reason
874 // log_info("[TCP_DBG] AUDIO_BUFFER_REGISTER_ATOMICS_START: client=%p", (void *)client);
875 // if (client->incoming_audio_buffer) {
876 // audio_ring_buffer_register_atomics(client->incoming_audio_buffer, new_client_id);
877 // log_info("[TCP_DBG] AUDIO_BUFFER_REGISTER_ATOMICS_DONE");
878 // } else {
879 // log_info("[TCP_DBG] AUDIO_BUFFER_REGISTER_ATOMICS_SKIPPED: incoming_audio_buffer is NULL");
880 // }
881
882 // Register with audio mixer OUTSIDE lock
883 // CRITICAL: Do this BEFORE crypto handshake to avoid deadlock with mixer thread
884 log_info("[TCP_DBG] MIXER_ADD_SOURCE_START: g_audio_mixer=%p, client->incoming_audio_buffer=%p",
885 (void *)g_audio_mixer, (void *)client->incoming_audio_buffer);
886 if (g_audio_mixer && client->incoming_audio_buffer) {
887 log_info("[TCP_DBG] MIXER_ADD_SOURCE_CALLING: client_id=%s", new_client_id);
888 if (mixer_add_source(g_audio_mixer, new_client_id, client->incoming_audio_buffer) < 0) {
889 log_warn("Failed to add client %s to audio mixer", new_client_id);
890 } else {
891 log_info("[TCP_DBG] MIXER_ADD_SOURCE_SUCCESS: client_id=%s", new_client_id);
892#ifdef DEBUG_AUDIO
893 log_debug("Added client %s to audio mixer", new_client_id);
894#endif
895 }
896 } else {
897 log_info("[TCP_DBG] MIXER_ADD_SOURCE_SKIPPED: conditions not met");
898 }
899 log_info("[TCP_DBG] MIXER_ADD_SOURCE_DONE");
900
901 // Create the client's persistent transport early (before handshake).
902 // It starts with NULL crypto; server_crypto_handshake() sets crypto_ctx after success.
903 client->transport = acip_tcp_transport_create(new_client_id, socket, NULL);
904 if (!client->transport) {
905 log_error("Failed to create ACIP transport for client %s", new_client_id);
906 if (remove_client(server_ctx, new_client_id) != 0) {
907 log_error("Failed to remove client after transport creation failure");
908 }
909 return NULL;
910 }
911 log_debug("Created ACIP transport for client %s (crypto pending handshake)", new_client_id);
912
913 // Perform crypto handshake before starting threads.
914 // This ensures the handshake uses the socket directly without interference from receive thread.
915 // The handshake uses client->transport and sets its crypto_ctx on success.
916 log_info("[TCP_DBG] CRYPTO_INIT_START: About to call server_crypto_init()");
917 if (server_crypto_init() == 0) {
918 log_info("[TCP_DBG] CRYPTO_INIT_DONE: server_crypto_init() returned 0");
919 // Set timeout for crypto handshake to prevent indefinite blocking
920 // This prevents clients from connecting but never completing the handshake
921 const uint64_t HANDSHAKE_TIMEOUT_NS = 30ULL * NS_PER_SEC_INT;
922 asciichat_error_t timeout_result = set_socket_timeout(socket, HANDSHAKE_TIMEOUT_NS);
923 if (timeout_result != ASCIICHAT_OK) {
924 log_warn("Failed to set handshake timeout for client %s: %s", new_client_id,
925 asciichat_error_string(timeout_result));
926 // Continue anyway - timeout is a safety feature, not critical
927 }
928
929 log_info("[TCP_DBG] SERVER_CRYPTO_HANDSHAKE_START: About to call server_crypto_handshake()");
930 int crypto_result = server_crypto_handshake(client);
931 log_info("[TCP_DBG] SERVER_CRYPTO_HANDSHAKE_DONE: result=%d", crypto_result);
932 if (crypto_result != 0) {
933 log_error("Crypto handshake failed for client %s: %s", new_client_id, network_error_string());
934 if (remove_client(server_ctx, new_client_id) != 0) {
935 log_error("Failed to remove client after crypto handshake failure");
936 }
937 return NULL;
938 }
939
940 // Clear socket timeout after handshake completes successfully
941 // This allows normal operation without timeouts on data transfer
942 log_info("[TCP_DBG] CLEAR_SOCKET_TIMEOUT_START");
943 asciichat_error_t clear_timeout_result = set_socket_timeout(socket, 0);
944 if (clear_timeout_result != ASCIICHAT_OK) {
945 log_warn("Failed to clear handshake timeout for client %s: %s", new_client_id,
946 asciichat_error_string(clear_timeout_result));
947 // Continue anyway - we can still communicate even with timeout set
948 }
949
950 log_debug("Crypto handshake completed successfully for client %s", new_client_id);
951
952 // After handshake completes, the client immediately sends PACKET_TYPE_CLIENT_CAPABILITIES
953 // We must read and process this packet BEFORE starting the receive thread to avoid a race condition
954 // where the packet arrives but no thread is listening for it.
955 //
956 // SPECIAL CASE: If client used --no-encrypt, we already received this packet during the handshake
957 // attempt and stored it in pending_packet_*. Use that instead of receiving a new one.
958 packet_envelope_t envelope;
959 bool used_pending_packet = false;
960
961 if (client->pending_packet_payload) {
962 // Client used --no-encrypt mode - use the packet we already received
963 log_info("Client %s using --no-encrypt mode - processing pending packet type %u", client->client_id,
964 client->pending_packet_type);
965 envelope.type = client->pending_packet_type;
966 envelope.data = client->pending_packet_payload;
967 envelope.len = client->pending_packet_length;
968 envelope.allocated_buffer = client->pending_packet_payload; // Will be freed below
969 envelope.allocated_size = client->pending_packet_length;
970 used_pending_packet = true;
971
972 // Clear pending packet fields
973 client->pending_packet_type = 0;
974 client->pending_packet_payload = NULL;
975 client->pending_packet_length = 0;
976 } else {
977 // Normal encrypted mode - receive capabilities packet
978 log_debug("Waiting for initial capabilities packet from client %s", client->client_id);
979
980 // Protect crypto context access with client state mutex
982 const crypto_context_t *crypto_ctx = crypto_server_get_context(client->client_id);
983
984 // Use per-client crypto state to determine enforcement
985 // At this point, handshake is complete, so crypto_initialized=true and handshake is ready
986 bool enforce_encryption = !GET_OPTION(no_encrypt) && client->crypto_initialized &&
988
989 packet_recv_result_t result = receive_packet_secure(socket, (void *)crypto_ctx, enforce_encryption, &envelope);
991
992 if (result != PACKET_RECV_SUCCESS) {
993 log_error("Failed to receive initial capabilities packet from client %s: result=%d", client->client_id, result);
994 if (envelope.allocated_buffer) {
995 buffer_pool_free(NULL, envelope.allocated_buffer, envelope.allocated_size);
996 }
997 if (remove_client(server_ctx, client->client_id) != 0) {
998 log_error("Failed to remove client after crypto handshake failure");
999 }
1000 return NULL;
1001 }
1002 }
1003
1004 if (envelope.type != PACKET_TYPE_CLIENT_CAPABILITIES) {
1005 log_error("Expected PACKET_TYPE_CLIENT_CAPABILITIES but got packet type %d from client %s", envelope.type,
1006 client->client_id);
1007 if (envelope.allocated_buffer) {
1008 buffer_pool_free(NULL, envelope.allocated_buffer, envelope.allocated_size);
1009 }
1010 if (remove_client(server_ctx, client->client_id) != 0) {
1011 log_error("Failed to remove client after crypto handshake failure");
1012 }
1013 return NULL;
1014 }
1015
1016 // The unencrypted socket receive path retains the wire header in its
1017 // allocation. Packet handlers always consume payload-only data, matching
1018 // the encrypted and ACIP-dispatch paths.
1019 if (envelope.data == envelope.allocated_buffer) {
1020 const packet_header_t *header = (const packet_header_t *)envelope.allocated_buffer;
1021 envelope.type = (packet_type_t)NET_TO_HOST_U16(header->type);
1022 envelope.data = (uint8_t *)envelope.allocated_buffer + sizeof(*header);
1023 envelope.len = NET_TO_HOST_U32(header->length);
1024 }
1025
1026 // Process the capabilities packet directly
1027 log_debug("Processing initial capabilities packet from client %s (from %s)", client->client_id,
1028 used_pending_packet ? "pending packet" : "network");
1029 log_info("INITIAL_CAPABILITIES: client_id=%s source=%s type=%u payload_len=%zu allocated_size=%zu data=%p",
1030 client->client_id, used_pending_packet ? "pending" : "network", envelope.type, envelope.len,
1031 envelope.allocated_size, envelope.data);
1032 handle_client_capabilities_packet(client, envelope.data, envelope.len);
1033
1034 // Free the packet data
1035 if (envelope.allocated_buffer) {
1036 buffer_pool_free(NULL, envelope.allocated_buffer, envelope.allocated_size);
1037 }
1038 log_debug("Successfully received and processed initial capabilities for client %s", client->client_id);
1039 }
1040
1041 // Start all client threads in the correct order (unified path for TCP and WebRTC)
1042 // This creates: receive thread -> render threads -> send thread
1043 // The render threads MUST be created before send thread to avoid the race condition
1044 // where send thread reads empty frames before render thread generates the first real frame
1045 char client_id_snapshot[MAX_CLIENT_ID_LEN];
1046 SAFE_STRNCPY(client_id_snapshot, client->client_id, sizeof(client_id_snapshot) - 1);
1047 if (start_client_threads(server_ctx, client, true) != 0) {
1048 log_error("Failed to start threads for TCP client %s", client_id_snapshot);
1049 // Client is already in hash table - use remove_client for proper cleanup
1050 remove_client(server_ctx, client_id_snapshot);
1051 return NULL;
1052 }
1053 log_debug("Successfully created render threads for client %s", client_id_snapshot);
1054
1055 // Register client with session_host (for discovery mode support)
1056 if (server_ctx->session_host) {
1057 client->session_client_id = session_host_add_client(server_ctx->session_host, socket, client_ip, port);
1058 if (client->session_client_id == 0) {
1059 log_warn("Failed to register client %s with session_host", client_id_snapshot);
1060 } else {
1061 log_debug("Client %s registered with session_host as %u", client_id_snapshot, client->session_client_id);
1062 }
1063 }
1064
1065 // Broadcast server state to ALL clients AFTER the new client is fully set up
1066 // This notifies all clients (including the new one) about the updated grid
1067 log_info("[TCP_DBG] BROADCAST_SERVER_STATE_START: About to broadcast to all clients");
1069 log_info("[TCP_DBG] BROADCAST_SERVER_STATE_DONE");
1070
1071 // Register client atomics for debug sync inspection
1072 register_client_info_atomics(client);
1073
1074 log_info("[TCP_DBG] ADD_CLIENT_RETURNING: client_id=%s, client=%p", client->client_id, (void *)client);
1075 return client;
1076
1077error_cleanup:
1078 // Clean up all partially allocated resources
1079 // NOTE: This label is reached when allocation or initialization fails BEFORE
1080 // the client is added to the hash table. Don't call remove_client() here.
1081 cleanup_client_all_buffers(client);
1083 return NULL;
1084}
1085
1109client_info_t *add_webrtc_client(server_context_t *server_ctx, acip_transport_t *transport, const char *client_ip,
1110 bool start_threads) {
1111 if (!server_ctx || !transport || !client_ip) {
1112 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters to add_webrtc_client");
1113 return NULL;
1114 }
1115
1117
1118 // Find empty slot - this is the authoritative check
1119 int slot = -1;
1120 int existing_count = 0;
1121 for (int i = 0; i < MAX_CLIENTS; i++) {
1122 if (slot == -1 && g_client_manager.clients[i].client_id[0] == '\0') {
1123 slot = i; // Take first available slot
1124 }
1125 // Count only active clients
1127 existing_count++;
1128 }
1129 }
1130
1131 // Check if we've hit the configured max-clients limit (not the array size)
1132 if (existing_count >= GET_OPTION(max_clients)) {
1134 SET_ERRNO(ERROR_RESOURCE_EXHAUSTED, "Maximum client limit reached (%d/%d active clients)", existing_count,
1135 GET_OPTION(max_clients));
1136 log_error("Maximum client limit reached (%d/%d active clients)", existing_count, GET_OPTION(max_clients));
1137 return NULL;
1138 }
1139
1140 if (slot == -1) {
1142 SET_ERRNO(ERROR_RESOURCE_EXHAUSTED, "No available client slots (all %d array slots are in use)", MAX_CLIENTS);
1143 log_error("No available client slots (all %d array slots are in use)", MAX_CLIENTS);
1144 return NULL;
1145 }
1146
1147 // Update client_count to match actual count before adding new client
1148 g_client_manager.client_count = existing_count;
1149
1150 // Generate unique client ID for WebRTC client (noun only, without counter or port)
1151 char new_client_id[MAX_CLIENT_ID_LEN];
1152 if (generate_client_id(new_client_id, sizeof(new_client_id)) != 0) {
1154 log_error("Failed to generate unique client ID for WebRTC client");
1155 return NULL;
1156 }
1157
1158 // Initialize client
1159 client_info_t *client = &g_client_manager.clients[slot];
1160
1161 // Free any existing buffers from previous client in this slot
1162 if (client->incoming_video_buffer) {
1164 client->incoming_video_buffer = NULL;
1165 }
1166 if (client->outgoing_video_buffer) {
1168 client->outgoing_video_buffer = NULL;
1169 }
1170 if (client->incoming_audio_buffer) {
1172 client->incoming_audio_buffer = NULL;
1173 }
1174 if (client->send_buffer) {
1175 SAFE_FREE(client->send_buffer);
1176 client->send_buffer = NULL;
1177 }
1178
1179 memset(client, 0, sizeof(client_info_t));
1180
1181 // Set up WebRTC-specific fields
1182 client->socket = INVALID_SOCKET_VALUE; // WebRTC has no traditional socket
1183 client->is_tcp_client = false; // WebRTC client - threads managed directly
1184 client->transport_encrypted = acip_transport_get_type(transport) == ACIP_TRANSPORT_WEBRTC;
1185 client->transport = transport; // Use provided transport
1186 SAFE_STRNCPY(client->client_id, new_client_id, sizeof(client->client_id) - 1);
1187 SAFE_STRNCPY(client->client_ip, client_ip, sizeof(client->client_ip) - 1);
1188 client->port = 0; // WebRTC doesn't use port numbers
1189 atomic_store_bool(&client->active, true);
1190 client->server_ctx = server_ctx; // Store server context for receive thread cleanup
1191 log_info("Added new WebRTC client %s from %s (transport=%p, slot=%d)", new_client_id, client_ip, transport, slot);
1192 atomic_store_bool(&client->shutting_down, false);
1193 atomic_store_u64(&client->last_rendered_grid_sources, 0); // Render thread updates this
1194 atomic_store_u64(&client->last_sent_grid_sources, 0); // Send thread updates this
1195 log_debug("WebRTC client slot assigned: client_id=%s assigned to slot %d", client->client_id, slot);
1196 client->connected_at = time(NULL);
1197
1198 // Initialize crypto context for this client
1199 memset(&client->crypto_handshake_ctx, 0, sizeof(client->crypto_handshake_ctx));
1200 log_debug("[ADD_WEBRTC_CLIENT] Setting crypto_initialized=false (will be set to true after handshake completes)");
1201 // Do NOT set crypto_initialized = true here! The handshake must complete first.
1202 // For WebSocket clients: websocket_client_handler will perform the handshake
1203 // For WebRTC clients: ACDS signaling may have already done crypto (future enhancement)
1204 client->crypto_initialized = false;
1205
1206 // Initialize pending packet storage (unused for WebRTC, but keep for consistency)
1207 client->pending_packet_type = 0;
1208 client->pending_packet_payload = NULL;
1209 client->pending_packet_length = 0;
1210
1211 // Use noun-based client name for display_name as well
1212 SAFE_STRNCPY(client->display_name, new_client_id, sizeof(client->display_name) - 1);
1213
1214 // Create individual video buffer for this client using modern double-buffering
1215 client->incoming_video_buffer = video_frame_buffer_create(new_client_id);
1216 if (!client->incoming_video_buffer) {
1217 SET_ERRNO(ERROR_MEMORY, "Failed to create video buffer for WebRTC client %s", new_client_id);
1218 log_error("Failed to create video buffer for WebRTC client %s", new_client_id);
1219 goto error_cleanup_webrtc;
1220 }
1221
1222 // Create individual audio buffer for this client
1224 if (!client->incoming_audio_buffer) {
1225 SET_ERRNO(ERROR_MEMORY, "Failed to create audio buffer for WebRTC client %s", new_client_id);
1226 log_error("Failed to create audio buffer for WebRTC client %s", new_client_id);
1227 goto error_cleanup_webrtc;
1228 }
1229
1230 // Create packet queues for outgoing data
1231 client->audio_queue = packet_queue_create_with_pools(500, 1000, false);
1232 if (!client->audio_queue) {
1233 LOG_ERRNO_IF_SET("Failed to create audio queue for WebRTC client");
1234 goto error_cleanup_webrtc;
1235 }
1236
1237 // Create packet queue for async dispatch: received packets waiting to be processed
1238 // Capacity of 100 packets allows buffering of ~10-50 frames (depending on fragmentation)
1239 client->received_packet_queue = packet_queue_create_with_pools(100, 500, false);
1240 if (!client->received_packet_queue) {
1241 LOG_ERRNO_IF_SET("Failed to create received packet queue for client");
1242 goto error_cleanup_webrtc;
1243 }
1244
1245 // Create outgoing video buffer for ASCII frames (double buffered, no dropping)
1246 client->outgoing_video_buffer = video_frame_buffer_create(new_client_id);
1247 if (!client->outgoing_video_buffer) {
1248 LOG_ERRNO_IF_SET("Failed to create outgoing video buffer for WebRTC client");
1249 goto error_cleanup_webrtc;
1250 }
1251
1252 // Pre-allocate send buffer to avoid malloc/free in send thread (prevents deadlocks)
1253 client->send_buffer_size = MAX_FRAME_BUFFER_SIZE; // 2MB should handle largest frames
1254 client->send_buffer = SAFE_MALLOC_ALIGNED(client->send_buffer_size, 64, void *);
1255 if (!client->send_buffer) {
1256 log_error("Failed to allocate send buffer for WebRTC client %s", new_client_id);
1257 goto error_cleanup_webrtc;
1258 }
1259
1260 g_client_manager.client_count = existing_count + 1; // We just added a client
1261 log_debug("Client count updated: now %d clients (added WebRTC client_id=%s to slot %d)",
1262 g_client_manager.client_count, new_client_id, slot);
1263
1264 // Add client to uthash table for O(1) lookup
1265 HASH_ADD_STR(g_client_manager.clients_by_id, client_id, client);
1266 log_debug("Added WebRTC client %s to uthash table", new_client_id);
1267
1268 // Release the write lock IMMEDIATELY after adding to hash table
1269 // All subsequent operations (mutex init, mixer registration) don't need global client manager lock
1271
1272 // Register this client's audio buffer with the mixer
1273 if (g_audio_mixer && client->incoming_audio_buffer) {
1274 if (mixer_add_source(g_audio_mixer, new_client_id, client->incoming_audio_buffer) < 0) {
1275 log_warn("Failed to add WebRTC client %s to audio mixer", new_client_id);
1276 } else {
1277#ifdef DEBUG_AUDIO
1278 log_debug("Added WebRTC client %s to audio mixer", new_client_id);
1279#endif
1280 }
1281 }
1282
1283 // Initialize mutexes BEFORE creating any threads to prevent race conditions
1284 if (mutex_init(&client->client_state_mutex, "client_state") != 0) {
1285 log_error("Failed to initialize client state mutex for WebRTC client %s", new_client_id);
1286 // Client is already in hash table - use remove_client for proper cleanup
1287 remove_client(server_ctx, new_client_id);
1288 return NULL;
1289 }
1290
1291 initialize_default_render_capabilities(client);
1292
1293 // Initialize send mutex to protect concurrent socket writes
1294 if (mutex_init(&client->send_mutex, "client_send") != 0) {
1295 log_error("Failed to initialize send mutex for WebRTC client %s", new_client_id);
1296 // Client is already in hash table - use remove_client for proper cleanup
1297 remove_client(server_ctx, client->client_id);
1298 return NULL;
1299 }
1300
1301 // Initialize condition variable for dispatch thread wake-up
1302 if (cond_init(&client->dispatch_queue_cond, "dispatch_queue_cond") != 0) {
1303 log_error("Failed to initialize dispatch queue condition variable for WebRTC client %s", new_client_id);
1304 // Client is already in hash table - use remove_client for proper cleanup
1305 remove_client(server_ctx, client->client_id);
1306 return NULL;
1307 }
1308
1309 // For WebRTC clients, the capabilities packet will be received by the receive thread
1310 // when it starts. Unlike TCP clients where we handle it synchronously in add_client(),
1311 // WebRTC uses the transport abstraction which handles packet reception automatically.
1312 log_debug("WebRTC client %s initialized - receive thread will process capabilities", new_client_id);
1313
1314 // Send initial server state to the new client
1315 if (send_server_state_to_client(client) != 0) {
1316 log_warn("Failed to send initial server state to WebRTC client %s", new_client_id);
1317 } else {
1318#ifdef DEBUG_NETWORK
1319 log_info("Sent initial server state to WebRTC client %s", new_client_id);
1320#endif
1321 }
1322
1323 // Send initial server state via ACIP transport
1325 uint32_t connected_count = g_client_manager.client_count;
1327
1329 state.connected_client_count = connected_count;
1330 state.active_client_count = 0; // Will be updated by broadcast thread
1331 memset(state.reserved, 0, sizeof(state.reserved));
1332
1333 // Convert to network byte order
1334 server_state_packet_t net_state;
1337 memset(net_state.reserved, 0, sizeof(net_state.reserved));
1338
1339 asciichat_error_t packet_send_result = acip_send_server_state(client->transport, &net_state);
1340 if (packet_send_result != ASCIICHAT_OK) {
1341 log_warn("Failed to send initial server state to WebRTC client %s: %s", new_client_id,
1342 asciichat_error_string(packet_send_result));
1343 } else {
1344 log_debug("Sent initial server state to WebRTC client %s: %u connected clients", new_client_id,
1346 }
1347
1348 // Register client with session_host (for discovery mode support)
1349 // WebRTC clients use INVALID_SOCKET_VALUE since they don't have a TCP socket
1350 if (server_ctx->session_host) {
1351 client->session_client_id = session_host_add_client(server_ctx->session_host, INVALID_SOCKET_VALUE, client_ip, 0);
1352 if (client->session_client_id == 0) {
1353 log_warn("Failed to register WebRTC client %s with session_host", new_client_id);
1354 } else {
1355 log_debug("WebRTC client %s registered with session_host as %u", new_client_id, client->session_client_id);
1356 }
1357 }
1358
1359 // Broadcast server state to ALL clients AFTER the new client is fully set up
1360 // This notifies all clients (including the new one) about the updated grid
1362
1363 // The browser can send CLIENT_CAPABILITIES and STREAM_START immediately when
1364 // its DataChannel opens. Start media threads only after this client is fully
1365 // registered and all initial control traffic is complete; the WebRTC
1366 // transport queues those incoming packets until the receive thread begins.
1367 // WebSocket callers still defer this until their crypto setup is complete.
1368 if (start_threads) {
1369 log_debug("[ADD_WEBRTC_CLIENT] Starting client threads after initialization for %s", new_client_id);
1370 if (start_client_threads(server_ctx, client, false) != 0) {
1371 log_error("Failed to start threads for WebRTC client %s", new_client_id);
1372 return NULL;
1373 }
1374 } else {
1375 log_debug("[ADD_WEBRTC_CLIENT] Deferring client thread startup for %s until crypto setup", new_client_id);
1376 }
1377
1378 return client;
1379
1380error_cleanup_webrtc:
1381 // Clean up all partially allocated resources for WebRTC client
1382 // NOTE: This label is reached when allocation or initialization fails BEFORE
1383 // the client is added to the hash table. Don't call remove_client() here.
1384 cleanup_client_all_buffers(client);
1386 return NULL;
1387}
1388
1389int remove_client(server_context_t *server_ctx, const char *client_id) {
1390 if (!server_ctx) {
1391 SET_ERRNO(ERROR_INVALID_PARAM, "Cannot remove client %s: NULL server_ctx", client_id);
1392 return -1;
1393 }
1394
1395 // Phase 1: Mark client inactive and prepare for cleanup while holding write lock
1396 client_info_t *target_client = NULL;
1397 char display_name_copy[MAX_DISPLAY_NAME_LEN];
1398 socket_t client_socket = INVALID_SOCKET_VALUE; // Save socket for thread cleanup
1399
1400 log_debug("SOCKET_DEBUG: Attempting to remove client %s", client_id);
1402
1403 for (int i = 0; i < MAX_CLIENTS; i++) {
1405 if (strcmp(client->client_id, client_id) == 0 && client->client_id[0] != '\0') {
1406 // Check if already being removed by another thread
1407 // This prevents double-free and use-after-free crashes during concurrent cleanup
1408 if (atomic_load_bool(&client->shutting_down)) {
1410 log_debug("Client %s already being removed by another thread, skipping", client_id);
1411 return 0; // Return success - removal is in progress
1412 }
1413 // Mark as shutting down and inactive immediately to stop new operations
1414 log_debug("Setting active=false in remove_client (client_id=%s, socket=%d)", client_id, client->socket);
1415 log_info("Removing client %s (socket=%d) - marking inactive and clearing video flags", client_id, client->socket);
1416 atomic_store_bool(&client->shutting_down, true);
1417 atomic_store_bool(&client->active, false);
1418 atomic_store_bool(&client->is_sending_video, false);
1419 atomic_store_bool(&client->is_sending_audio, false);
1420 target_client = client;
1421
1422 // Store display name before clearing
1423 SAFE_STRNCPY(display_name_copy, client->display_name, MAX_DISPLAY_NAME_LEN - 1);
1424
1425 // Save socket for tcp_server_stop_client_threads(). Keep the descriptor
1426 // open until thread cleanup completes so the OS cannot reuse its fd for
1427 // a new client while this client's cleanup is still in progress.
1429 client_socket = client->socket; // Save socket for thread cleanup
1431
1432 // Wake blocked receive/send operations without closing the descriptor.
1433 // Closing here would allow a concurrently accepted client to reuse the
1434 // same descriptor before the old client's worker threads finish.
1435 if (client_socket != INVALID_SOCKET_VALUE) {
1436 (void)socket_shutdown(client_socket, SHUT_RDWR);
1437 }
1438
1439 // Shutdown packet queues to unblock send thread
1440 if (client->audio_queue) {
1442 }
1443 // Video now uses double buffer, no queue to shutdown
1444
1445 break;
1446 }
1447 }
1448
1449 // If client not found, unlock and return
1450 if (!target_client) {
1452 log_warn("Cannot remove client %s: not found", client_id);
1453 return -1;
1454 }
1455
1456 // Unregister client from session_host (for discovery mode support)
1457 // NOTE: Client may not be registered if crypto handshake failed before session_host registration
1458 if (server_ctx->session_host && target_client->session_client_id != 0) {
1459 asciichat_error_t session_result =
1460 session_host_remove_client(server_ctx->session_host, target_client->session_client_id);
1461 if (session_result != ASCIICHAT_OK) {
1462 // ERROR_NOT_FOUND (91) is expected if client failed crypto before being registered with session_host
1463 if (session_result == ERROR_NOT_FOUND) {
1464 log_debug("Client %s not found in session_host (likely failed crypto before registration)", client_id);
1465 } else {
1466 log_warn("Failed to unregister client %s from session_host: %s", client_id,
1467 asciichat_error_string(session_result));
1468 }
1469 } else {
1470 log_debug("Client %s unregistered from session_host", client_id);
1471 }
1472 }
1473
1474 // Release write lock before joining threads.
1475 // This prevents deadlock with render threads that need read locks.
1477
1478 // Phase 2: Stop all client threads
1479 // For TCP clients: use tcp_server thread pool management
1480 // For WebRTC clients: manually join threads (no socket-based thread pool)
1481 // Use is_tcp_client flag, not socket value - socket may already be INVALID_SOCKET_VALUE
1482 // even for TCP clients if it was closed earlier during cleanup.
1483 bool receive_thread_is_current = false;
1484 log_debug("Stopping all threads for client %s (socket %d, is_tcp=%d)", client_id, client_socket,
1485 target_client ? target_client->is_tcp_client : -1);
1486
1487 if (target_client && target_client->is_tcp_client) {
1488 // TCP client: use tcp_server thread pool
1489 // This joins threads in stop_id order: receive(1), render(2), send(3)
1490 // Use saved client_socket for lookup (tcp_server needs original socket as key)
1491 if (client_socket != INVALID_SOCKET_VALUE) {
1492 asciichat_error_t stop_result = tcp_server_stop_client_threads(server_ctx->tcp_server, client_socket);
1493 if (stop_result != ASCIICHAT_OK) {
1494 log_warn("Failed to stop threads for TCP client %s: error %d", client_id, stop_result);
1495 // Continue with cleanup even if thread stopping failed
1496 }
1497 } else {
1498 log_debug("TCP client %s socket already closed, threads should have already exited", client_id);
1499 }
1500 } else if (target_client) {
1501 // WebRTC client: manually join threads
1502 log_debug("Stopping WebRTC client %s threads (receive and send)", client_id);
1503
1504 // Join receive thread (but skip if called from the receive thread itself to avoid deadlock)
1505 thread_id_t current_thread_id = asciichat_thread_self();
1506 receive_thread_is_current = asciichat_thread_equal(current_thread_id, target_client->receive_thread_id);
1507 if (receive_thread_is_current) {
1508 log_debug("remove_client() called from receive thread for client %s, skipping self-join", client_id);
1509 } else {
1510 void *recv_result = NULL;
1511 asciichat_error_t recv_join_result = asciichat_thread_join(&target_client->receive_thread, &recv_result);
1512 if (recv_join_result != ASCIICHAT_OK) {
1513 log_warn("Failed to join receive thread for WebRTC client %s: error %d", client_id, recv_join_result);
1514 } else {
1515 log_debug("Joined receive thread for WebRTC client %s", client_id);
1516 }
1517 }
1518
1519 // Join dispatch thread (BEFORE destroying packet queues)
1520 if (asciichat_thread_is_initialized(&target_client->dispatch_thread)) {
1521 atomic_store_bool(&target_client->dispatch_thread_running, false);
1522 void *dispatch_result = NULL;
1523 asciichat_error_t dispatch_join_result = asciichat_thread_join(&target_client->dispatch_thread, &dispatch_result);
1524 if (dispatch_join_result != ASCIICHAT_OK) {
1525 log_warn("Failed to join dispatch thread for WebRTC client %s: error %d", client_id, dispatch_join_result);
1526 } else {
1527 log_debug("Joined dispatch thread for WebRTC client %s", client_id);
1528 }
1529 }
1530
1531 // Join send thread
1532 void *send_result = NULL;
1533 asciichat_error_t send_join_result = asciichat_thread_join(&target_client->send_thread, &send_result);
1534 if (send_join_result != ASCIICHAT_OK) {
1535 log_warn("Failed to join send thread for WebRTC client %s: error %d", client_id, send_join_result);
1536 } else {
1537 log_debug("Joined send thread for WebRTC client %s", client_id);
1538 }
1539
1540 // Render workers access the client buffers and synchronization primitives.
1541 // Join them before those resources are destroyed or the client slot is reused.
1542 stop_client_render_threads(target_client);
1543 }
1544
1545 // Destroy ACIP transport before closing socket
1546 // For WebSocket clients: LWS_CALLBACK_CLOSED already closed and destroyed the transport
1547 // Trying to destroy it again causes heap-use-after-free since LWS callbacks might still fire
1548 // For TCP clients: transport is ours to clean up
1549 if (target_client && target_client->transport && target_client->is_tcp_client) {
1550 acip_transport_destroy(target_client->transport);
1551 target_client->transport = NULL;
1552 log_debug("Destroyed ACIP transport for TCP client %s", client_id);
1553 } else if (target_client && target_client->transport && !target_client->is_tcp_client) {
1554 // WebSocket client - just NULL it out, LWS_CALLBACK_CLOSED already destroyed it
1555 target_client->transport = NULL;
1556 log_debug("Skipped transport destruction for WebSocket client %s (LWS already destroyed)", client_id);
1557 }
1558
1559 // Now safe to close the socket (threads are stopped)
1560 if (client_socket != INVALID_SOCKET_VALUE) {
1561 log_debug("SOCKET_DEBUG: Closing socket %d for client %s after thread cleanup", client_socket, client_id);
1562 socket_close(client_socket);
1563 }
1564
1565 // Phase 3: Clean up resources with write lock
1567
1568 // Re-validate target_client pointer after reacquiring lock.
1569 // Another thread might have invalidated the pointer while we had the lock released.
1570 if (target_client) {
1571 // Verify client_id still matches and client is still in shutting_down state
1572 bool still_shutting_down = atomic_load_bool(&target_client->shutting_down);
1573 if (strcmp(target_client->client_id, client_id) != 0 || !still_shutting_down) {
1574 log_warn("Client %s pointer invalidated during thread cleanup (id=%s, shutting_down=%d)", client_id,
1575 target_client->client_id, still_shutting_down);
1577 return 0; // Another thread completed the cleanup
1578 }
1579 }
1580
1581 // Mark socket as closed in client structure
1582 if (target_client && target_client->socket != INVALID_SOCKET_VALUE) {
1583 mutex_lock(&target_client->client_state_mutex);
1584 target_client->socket = INVALID_SOCKET_VALUE;
1585 mutex_unlock(&target_client->client_state_mutex);
1586 log_debug("SOCKET_DEBUG: Client %s socket set to INVALID", target_client->client_id);
1587 }
1588
1589 // Use the dedicated cleanup function to ensure all resources are freed
1590 cleanup_client_all_buffers(target_client);
1591
1592 // Remove from audio mixer
1593 if (g_audio_mixer) {
1595#ifdef DEBUG_AUDIO
1596 log_debug("Removed client %s from audio mixer", client_id);
1597#endif
1598 }
1599
1600 // Remove from uthash table
1601 // Verify client is actually in the hash table before deleting.
1602 // Another thread might have already removed it.
1603 if (target_client) {
1604 client_info_t *hash_entry = NULL;
1605 HASH_FIND_STR(g_client_manager.clients_by_id, target_client->client_id, hash_entry);
1606 if (hash_entry == target_client) {
1607 HASH_DELETE(hh, g_client_manager.clients_by_id, target_client);
1608 log_debug("Removed client %s from uthash table", client_id);
1609 } else {
1610 log_warn("Client %s already removed from hash table by another thread (found=%p, expected=%p)", client_id,
1611 (void *)hash_entry, (void *)target_client);
1612 }
1613 } else {
1614 log_warn("Failed to remove client %s from hash table (client not found)", client_id);
1615 }
1616
1617 // Cleanup crypto context for this client
1618 if (target_client->crypto_initialized) {
1620 target_client->crypto_initialized = false;
1621 log_debug("Crypto context cleaned up for client %s", client_id);
1622 }
1623
1624 // Verify all threads have actually exited before resetting client_id.
1625 // Threads that are still starting (at RtlUserThreadStart) haven't checked client_id yet.
1626 // We must ensure threads are fully joined before zeroing the client struct.
1627 // Use exponential backoff for thread termination verification
1628 int retry_count = 0;
1629 const int max_retries = 5;
1630 while (retry_count < max_retries && (asciichat_thread_is_initialized(&target_client->send_thread) ||
1631 (!receive_thread_is_current &&
1635 // Exponential backoff: 10ms, 20ms, 40ms, 80ms, 160ms
1636 uint32_t delay_ms = 10 * (1 << retry_count);
1637 log_warn("Client %s: Some threads still appear initialized (attempt %d/%d), waiting %ums", client_id,
1638 retry_count + 1, max_retries, delay_ms);
1639 platform_sleep_us(delay_ms * 1000);
1640 retry_count++;
1641 }
1642
1643 if (retry_count == max_retries) {
1644 log_error("Client %s: Threads did not terminate after %d retries, proceeding with cleanup anyway", client_id,
1645 max_retries);
1646 }
1647
1648 // Only reset client_id to 0 AFTER confirming threads are joined
1649 // This prevents threads that are starting from accessing a zeroed client struct
1650 // Reset client_id to NULL before destroying mutexes to prevent race conditions.
1651 // This ensures worker threads can detect shutdown and exit before the mutex is destroyed.
1652 // If we destroy the mutex first, threads might try to access a destroyed mutex.
1653 target_client->client_id[0] = '\0'; // Clear the client_id string
1654
1655 // Wait for threads to observe the client_id reset
1656 // Use sufficient delay for memory visibility across all CPU cores
1657 platform_sleep_us(5 * US_PER_MS_INT); // 5ms delay for memory barrier propagation
1658
1659 // Destroy mutexes and condition variables
1660 // IMPORTANT: Always destroy these even if threads didn't join properly
1661 // to prevent issues when the slot is reused
1662 mutex_destroy(&target_client->client_state_mutex);
1663 mutex_destroy(&target_client->send_mutex);
1664 cond_destroy(&target_client->dispatch_queue_cond);
1665
1666 // Clear client structure
1667 // NOTE: After memset, the mutex handles are zeroed but the OS resources
1668 // have been released by the destroy calls above
1669 memset(target_client, 0, sizeof(client_info_t));
1670
1671 // Recalculate client count
1672 int remaining_count = 0;
1673 for (int j = 0; j < MAX_CLIENTS; j++) {
1674 if (g_client_manager.clients[j].client_id[0] != '\0') {
1675 remaining_count++;
1676 }
1677 }
1678 g_client_manager.client_count = remaining_count;
1679
1680 log_debug("Client removed: client_id=%s (%s) removed, remaining clients: %d", client_id, display_name_copy,
1681 remaining_count);
1682
1684
1685 // Broadcast updated state
1687
1688 return 0;
1689}
1690
1691/* ============================================================================
1692 * Client Thread Functions
1693 * ============================================================================
1694 */
1695
1696// Forward declaration for ACIP server callbacks (defined later in file)
1697static const acip_server_callbacks_t g_acip_server_callbacks;
1698
1706void *client_dispatch_thread(void *arg) {
1707 client_info_t *client = (client_info_t *)arg;
1708
1709 if (!client) {
1710 log_error("Invalid client info in dispatch thread (NULL pointer)");
1711 return NULL;
1712 }
1713
1714 const char *client_id = client->client_id;
1715 log_info("DISPATCH_THREAD: Started for client %s", client_id);
1716
1717 uint64_t dispatch_loop_count = 0;
1718 queued_packet_t *pending_packets[128] = {0};
1719 size_t pending_count = 0;
1720 size_t pending_index = 0;
1721
1723 dispatch_loop_count++;
1724 // Try to dequeue next packet (non-blocking)
1725 // Use try_dequeue to avoid blocking - allows checking exit flag frequently
1726 uint64_t dequeue_start = time_get_ns();
1728 if (pending_index == pending_count) {
1729 pending_count = 0;
1730 pending_index = 0;
1731 queued_packet_t *packet;
1732 while (pending_count < 128 && (packet = packet_queue_try_dequeue(client->received_packet_queue))) {
1733 pending_packets[pending_count++] = packet;
1734 }
1735 // Audio packets interleave with raw video. Coalesce snapshots across audio
1736 // without crossing control or encoded-frame boundaries.
1737 bool newer_image = false;
1738 for (size_t i = pending_count; i > 0; i--) {
1739 packet_type_t type = NET_TO_HOST_U16(pending_packets[i - 1]->header.type);
1740 if (type == PACKET_TYPE_IMAGE_FRAME) {
1741 if (newer_image) {
1742 packet_queue_free_packet(pending_packets[i - 1]);
1743 pending_packets[i - 1] = NULL;
1744 }
1745 newer_image = true;
1746 } else if (type != PACKET_TYPE_AUDIO_BATCH && type != PACKET_TYPE_AUDIO_OPUS_BATCH) {
1747 newer_image = false;
1748 }
1749 }
1750 }
1751 while (pending_index < pending_count && !pending_packets[pending_index]) pending_index++;
1752 queued_packet_t *queued_pkt = pending_index < pending_count ? pending_packets[pending_index++] : NULL;
1753 uint64_t dequeue_end = time_get_ns();
1754
1755 if (!queued_pkt) {
1756 // Queue was empty, wait for signal from receive thread or timeout
1757 // Use 100ms timeout to handle cases where signal is missed due to race conditions
1758 log_dev_every(5 * US_PER_MS_INT, "🔄 DISPATCH_LOOP[%llu]: Queue empty after %.1fμs, waiting for signal",
1759 (unsigned long long)dispatch_loop_count, (dequeue_end - dequeue_start) / 1000.0);
1760 cond_timedwait(&client->dispatch_queue_cond, &client->client_state_mutex, 100 * 1000 * 1000); // 100ms timeout
1762 continue;
1763 }
1765
1766 log_dev("DISPATCH_THREAD[%s]: dequeued %zu-byte packet in %.1fμs", client_id, queued_pkt->data_len,
1767 (dequeue_end - dequeue_start) / 1000.0);
1768
1769 // Process the dequeued packet
1770 // The queued packet contains the complete ACIP packet (header + payload) from websocket_recv()
1771 const packet_header_t *header = (const packet_header_t *)queued_pkt->data;
1772 size_t total_len = queued_pkt->data_len;
1773 uint8_t *payload = (uint8_t *)header + sizeof(packet_header_t);
1774 size_t payload_len = 0;
1775
1776 log_dev("DISPATCH_THREAD[%s]: processing %zu-byte packet", client_id, total_len);
1777
1778 if (total_len < sizeof(packet_header_t)) {
1779 log_error("🔴 DISPATCH_THREAD[%s]: Packet too small (%zu < %zu), DROPPING", client_id, total_len,
1780 sizeof(packet_header_t));
1781 packet_queue_free_packet(queued_pkt);
1782 continue;
1783 }
1784
1785 {
1786 packet_type_t packet_type = (packet_type_t)NET_TO_HOST_U16(header->type);
1787 payload_len = NET_TO_HOST_U32(header->length);
1788
1789 log_dev("DISPATCH_THREAD[%s]: type=%d payload_len=%u total_len=%zu", client_id, packet_type, payload_len,
1790 total_len);
1791
1792 // Handle PACKET_TYPE_ENCRYPTED from WebSocket clients that encrypt at application layer
1793 // This mirrors the decryption logic in acip_server_receive_and_dispatch()
1794 if (packet_type == PACKET_TYPE_ENCRYPTED && client->transport && client->transport->crypto_ctx) {
1795 log_dev("DISPATCH_THREAD[%s]: decrypting packet", client_id);
1796
1797 uint8_t *ciphertext = payload;
1798 size_t ciphertext_len = payload_len;
1799
1800 // Decrypt to get inner plaintext packet (header + payload)
1801 size_t plaintext_size = ciphertext_len + 1024;
1802 uint8_t *plaintext = SAFE_MALLOC(plaintext_size, uint8_t *);
1803 if (!plaintext) {
1804 log_error("🔴 DISPATCH_THREAD[%s]: Failed to allocate plaintext buffer for decryption", client_id);
1805 packet_queue_free_packet(queued_pkt);
1806 continue;
1807 }
1808
1809 size_t plaintext_len;
1810 crypto_result_t crypto_result = crypto_decrypt(client->transport->crypto_ctx, ciphertext, ciphertext_len,
1811 plaintext, plaintext_size, &plaintext_len);
1812
1813 if (crypto_result != CRYPTO_OK) {
1814 log_error("🔴 DISPATCH_THREAD[%s]: Failed to decrypt packet: %s", client_id,
1815 crypto_result_to_string(crypto_result));
1816 SAFE_FREE(plaintext);
1817 packet_queue_free_packet(queued_pkt);
1818 continue;
1819 }
1820
1821 if (plaintext_len < sizeof(packet_header_t)) {
1822 log_error("🔴 DISPATCH_THREAD[%s]: Decrypted packet too small: %zu < %zu", client_id, plaintext_len,
1823 sizeof(packet_header_t));
1824 SAFE_FREE(plaintext);
1825 packet_queue_free_packet(queued_pkt);
1826 continue;
1827 }
1828
1829 // Parse the inner (decrypted) header
1830 const packet_header_t *inner_header = (const packet_header_t *)plaintext;
1831 packet_type = (packet_type_t)NET_TO_HOST_U16(inner_header->type);
1832 payload_len = NET_TO_HOST_U32(inner_header->length);
1833 payload = plaintext + sizeof(packet_header_t);
1834
1835 log_dev("DISPATCH_THREAD[%s]: decrypted type=%d payload_len=%u", client_id, packet_type, payload_len);
1836
1837 // Dispatch the decrypted packet
1838 if (client->transport) {
1839 log_dev("DISPATCH_THREAD[%s]: dispatching type=%d payload_len=%u", client_id, packet_type, payload_len);
1840 asciichat_error_t dispatch_result = acip_handle_server_packet(client->transport, packet_type, payload,
1841 payload_len, client, &g_acip_server_callbacks);
1842
1843 if (dispatch_result != ASCIICHAT_OK) {
1844 log_error("🔴 DISPATCH_THREAD[%s]: Handler failed for decrypted packet type=%d: %s", client_id, packet_type,
1845 asciichat_error_string(dispatch_result));
1846 } else {
1847 log_dev("DISPATCH_THREAD[%s]: dispatched decrypted type=%d", client_id, packet_type);
1848 }
1849 } else {
1850 log_error("🔴 DISPATCH_THREAD[%s]: Cannot dispatch decrypted packet - transport is NULL", client_id);
1851 }
1852
1853 // Free the decrypted buffer
1854 SAFE_FREE(plaintext);
1855 } else {
1856 // Not encrypted or no crypto context - dispatch as-is
1857 if (client->transport) {
1858 log_dev("DISPATCH_THREAD[%s]: dispatching type=%d payload_len=%zu", client_id, packet_type, payload_len);
1859 asciichat_error_t dispatch_result = acip_handle_server_packet(client->transport, packet_type, payload,
1860 payload_len, client, &g_acip_server_callbacks);
1861
1862 if (dispatch_result != ASCIICHAT_OK) {
1863 log_error("🔴 DISPATCH_THREAD[%s]: Handler failed for packet type=%d: %s", client_id, packet_type,
1864 asciichat_error_string(dispatch_result));
1865 } else {
1866 log_dev("DISPATCH_THREAD[%s]: dispatched type=%d", client_id, packet_type);
1867 }
1868 } else {
1869 log_error("🔴 DISPATCH_THREAD[%s]: Cannot dispatch packet - transport is NULL", client_id);
1870 }
1871 }
1872 }
1873
1874 // Free the queued packet
1875 packet_queue_free_packet(queued_pkt);
1876 }
1877
1878 while (pending_index < pending_count) {
1879 packet_queue_free_packet(pending_packets[pending_index++]);
1880 }
1881 log_info("DISPATCH_THREAD: Exiting for client %s", client_id);
1882 return NULL;
1883}
1884
1885void *client_receive_thread(void *arg) {
1886 // Log thread startup
1887 log_debug("RECV_THREAD: Thread function entered, arg=%p", arg);
1888
1889 client_info_t *client = (client_info_t *)arg;
1890
1891 // Validate client pointer immediately before any access.
1892 // This prevents crashes if remove_client() has zeroed the client struct
1893 // while the thread was still starting at RtlUserThreadStart.
1894 if (!client) {
1895 log_error("Invalid client info in receive thread (NULL pointer)");
1896 return NULL;
1897 }
1898
1899 // Save this thread's ID so remove_client() can detect self-joins
1901
1902 log_debug("RECV_THREAD_DEBUG: Thread started, client=%p, client_id=%s, is_tcp=%d", (void *)client, client->client_id,
1903 client->is_tcp_client);
1904
1906 log_debug("Receive thread for client %s exiting before start (protocol disconnect requested)", client->client_id);
1907 return NULL;
1908 }
1909
1910 // Check if client_id is empty (client struct has been zeroed by remove_client)
1911 // This must be checked BEFORE accessing any client fields
1912 if (client->client_id[0] == 0) {
1913 log_debug("Receive thread: client_id is empty, client struct may have been zeroed, exiting");
1914 return NULL;
1915 }
1916
1917 // Additional validation: check socket is valid
1918 // For TCP clients, validate socket. WebRTC clients use DataChannel (no socket)
1919 if (client->is_tcp_client && client->socket == INVALID_SOCKET_VALUE) {
1920 log_error("Invalid client socket in receive thread");
1921 return NULL;
1922 }
1923
1924 // NOTE: Atomic registration disabled
1925 // register_client_info_atomics() and audio_ring_buffer_register_atomics() attempt
1926 // to acquire WRITE lock on named_registry.entries_lock, which conflicts with debug_sync
1927 // thread holding READ lock during printing, causing deadlock.
1928 // These are debug-only features, not critical for functionality.
1929 // The atomics work fine without being registered in the debug system.
1930
1931 // Enable thread cancellation for clean shutdown
1932 // Thread cancellation not available in platform abstraction
1933 // Threads should exit when g_should_exit is set
1934
1935 log_debug("Started receive thread for client %s (%s)", client->client_id, client->display_name);
1936
1937 // Main receive loop - processes packets from transport
1938 // For TCP clients: receives from socket
1939 // For WebRTC clients: receives from transport ringbuffer (via ACDS signaling)
1940
1941 log_info("RECV_THREAD_LOOP_START: client_id=%s, is_tcp=%d, transport=%p, active=%d", client->client_id,
1942 client->is_tcp_client, (void *)client->transport, atomic_load_bool(&client->active));
1943
1944 while (!atomic_load_bool(&g_should_exit) && atomic_load_bool(&client->active)) {
1945 // For TCP clients, check socket validity
1946 // For WebRTC clients, continue even if no socket (transport handles everything)
1947 if (client->is_tcp_client && client->socket == INVALID_SOCKET_VALUE) {
1948 log_debug("TCP client %s has invalid socket, exiting receive thread", client->client_id);
1949 break;
1950 }
1951
1952 // Check client_id is still valid before accessing transport.
1953 // This prevents accessing freed memory if remove_client() has zeroed the client struct.
1954 if (client->client_id[0] == 0) {
1955 log_debug("Client client_id reset, exiting receive thread");
1956 break;
1957 }
1958
1959 // Receive packet (without dispatching) - decouple from dispatch for async processing
1960 // For WebRTC clients with async dispatch, we queue packets instead of processing immediately
1961 // This prevents backpressure on the network socket
1962
1963 if (client->is_tcp_client) {
1964 // TCP clients: use original synchronous dispatch
1965 asciichat_error_t acip_result =
1966 acip_server_receive_and_dispatch(client->transport, client, &g_acip_server_callbacks);
1967
1968 // Check if shutdown was requested during the network call
1970 log_debug("RECV_EXIT: Server shutdown requested, breaking loop");
1971 break;
1972 }
1973
1974 // Handle receive errors
1975 if (acip_result != ASCIICHAT_OK) {
1977 if (HAS_ERRNO(&err_ctx)) {
1978 log_error("🔴 ACIP error for client %s: code=%u msg=%s", client->client_id, err_ctx.code,
1979 err_ctx.context_message);
1980 if (err_ctx.code == ERROR_NETWORK) {
1981 log_debug("Client %s disconnected (network error): %s", client->client_id, err_ctx.context_message);
1982 break;
1983 } else if (err_ctx.code == ERROR_CRYPTO) {
1985 client, "SECURITY VIOLATION: Unencrypted packet when encryption required - terminating connection");
1987 break;
1988 }
1989 }
1990 log_warn("ACIP error for TCP client %s: %s (disconnecting)", client->client_id,
1991 asciichat_error_string(acip_result));
1992 break;
1993 }
1994 } else {
1995 // WebRTC/WebSocket clients: async dispatch - receive packet and queue for async processing
1996 void *packet_data = NULL;
1997 void *allocated_buffer = NULL;
1998 size_t packet_len = 0;
1999
2000 // Snapshot transport pointer to prevent use-after-free
2001 // Transport can be destroyed by WebSocket callback while we're receiving
2002 acip_transport_t *transport_snapshot = NULL;
2003 mutex_lock(&client->send_mutex);
2004 if (!client->transport || atomic_load_bool(&client->shutting_down)) {
2005 mutex_unlock(&client->send_mutex);
2006 log_debug("RECV_THREAD[%s]: Transport destroyed or client shutting down, exiting receive loop",
2007 client->client_id);
2008 break;
2009 }
2010 transport_snapshot = client->transport;
2011 mutex_unlock(&client->send_mutex);
2012
2013 log_debug("🔍 RECV_THREAD[%s]: About to call transport->recv() (transport=%p)", client->client_id,
2014 (void *)transport_snapshot);
2015
2016 asciichat_error_t recv_result =
2017 transport_snapshot->methods->recv(transport_snapshot, &packet_data, &packet_len, &allocated_buffer);
2018
2019 // Check error BEFORE logging - logging system may clear thread-local errno
2020 if (recv_result != ASCIICHAT_OK) {
2022 if (HAS_ERRNO(&err_ctx)) {
2023 // Check for reassembly timeout (fragments arriving slowly)
2024 // This is NOT a connection failure - safe to retry
2025 if ((err_ctx.code == ERROR_NETWORK) && err_ctx.context_message &&
2026 strstr(err_ctx.context_message, "reassembly timeout")) {
2027 // Fragments are arriving slowly - this is normal, retry without disconnecting
2028 log_dev_every(100000, "Client %s: fragment reassembly timeout, retrying in 10ms", client->client_id);
2029 APP_CALLBACK_VOID(platform_pump_events);
2030 platform_sleep_ms(10); // Sleep 10ms to allow fragments to arrive
2031 continue; // Retry without disconnecting
2032 }
2033
2034 if (err_ctx.code == ERROR_NETWORK) {
2035 log_debug("Client %s disconnected (network error): %s", client->client_id, err_ctx.context_message);
2036 break;
2037 }
2038 }
2039 log_warn("Receive failed for WebRTC client %s: %s (disconnecting)", client->client_id,
2040 asciichat_error_string(recv_result));
2041 break;
2042 }
2043
2044 log_dev("RECV_THREAD[%s]: recv result=%d packet_len=%zu", client->client_id, recv_result, packet_len);
2045
2046 // Validate received packet before queueing
2047 if (packet_len < sizeof(packet_header_t)) {
2048 log_warn("RECV_THREAD[%s]: Received packet too small (%zu < %zu), dropping", client->client_id, packet_len,
2049 sizeof(packet_header_t));
2050 if (allocated_buffer) {
2051 buffer_pool_free(NULL, allocated_buffer, packet_len);
2052 }
2053 continue;
2054 }
2055
2056 // Queue the received packet for async dispatch
2057 // This prevents the receive thread from blocking on dispatch
2058 log_dev("RECV_THREAD[%s]: queueing %zu-byte packet", client->client_id, packet_len);
2059
2060 // Extract packet type from the header to preserve it when queueing
2061 const packet_header_t *pkt_header = (const packet_header_t *)allocated_buffer;
2062 packet_type_t pkt_type = (packet_type_t)NET_TO_HOST_U16(pkt_header->type);
2063 uint32_t payload_len = NET_TO_HOST_U32(pkt_header->length);
2064
2065 log_dev("RECV_THREAD[%s]: type=%d payload_len=%u total_len=%zu", client->client_id, pkt_type, payload_len,
2066 packet_len);
2067
2068 // Build a complete packet to queue (header + payload)
2069 // The entire buffer (allocated_buffer) contains the full packet
2070 // The queue copies the packet; release the transport buffer afterward.
2071 uint32_t client_id_hash = fnv1a_hash_string(client->client_id);
2072 // Pair queue publication and notification with the dispatch wait lock.
2074 int enqueue_result = packet_queue_enqueue(client->received_packet_queue, pkt_type, allocated_buffer, packet_len,
2075 client_id_hash, true);
2076 if (enqueue_result >= 0)
2079 buffer_pool_free(NULL, allocated_buffer, packet_len);
2080
2081 if (enqueue_result < 0) {
2082 log_error("🔴 RECV_THREAD[%s]: Failed to queue received packet (queue full?) - DROPPING FRAME",
2083 client->client_id);
2084 } else {
2085 log_dev("RECV_THREAD[%s]: queued type=%d len=%zu", client->client_id, pkt_type, packet_len);
2086 }
2087 }
2088 }
2089
2090 // Mark client as inactive and stop all threads
2091 // Must stop render threads when client disconnects.
2092 // OPTIMIZED: Use atomic operations for thread control flags (lock-free)
2093 char client_id_snapshot[MAX_CLIENT_ID_LEN] = {0};
2094 SAFE_STRNCPY(client_id_snapshot, client->client_id, sizeof(client_id_snapshot) - 1);
2095 bool is_tcp_client = client->is_tcp_client;
2096 server_context_t *server_ctx = (server_context_t *)client->server_ctx;
2097 log_debug("Setting active=false in receive_thread_fn (client_id=%s, exiting receive loop)", client_id_snapshot);
2098 atomic_store_bool(&client->active, false);
2099 atomic_store_bool(&client->send_thread_running, false);
2102
2103 // Call remove_client() to trigger cleanup
2104 // Safe to call from receive thread now: remove_client() detects self-join via thread IDs
2105 // and skips the receive thread join when called from the receive thread itself
2106 log_debug("Receive thread for client %s calling remove_client() for cleanup", client_id_snapshot);
2107 if (server_ctx && !is_tcp_client) {
2108 if (remove_client(server_ctx, client_id_snapshot) != 0) {
2109 log_warn("Failed to remove client %s from receive thread cleanup", client_id_snapshot);
2110 }
2111 } else if (!server_ctx) {
2112 log_error("Receive thread for client %s: server_ctx is NULL, cannot call remove_client()", client_id_snapshot);
2113 }
2114
2115 log_debug("Receive thread for client %s terminated", client_id_snapshot);
2116
2117 // Clean up thread-local error context before exit
2119
2120 return NULL;
2121}
2122
2123// Thread function to handle sending data to a specific client
2124#define MAX_AUDIO_BATCH 3
2125#define AUDIO_OPUS_BATCH_HEADER_SIZE 16
2126#define AUDIO_OPUS_FRAME_MAX_BYTES 1024
2127
2128static asciichat_error_t combine_audio_opus_packets(queued_packet_t **packets, int packet_count, uint8_t *payload,
2129 size_t payload_capacity, size_t *payload_length) {
2130 if (!packets || packet_count < 1 || !payload || !payload_length) {
2131 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid arguments while combining Opus audio packets");
2132 }
2133
2134 size_t opus_bytes = 0;
2135 uint16_t frame_sizes[MAX_AUDIO_BATCH];
2136 for (int i = 0; i < packet_count; i++) {
2137 queued_packet_t *packet = packets[i];
2138 if (!packet || !packet->data || packet->data_len < AUDIO_OPUS_BATCH_HEADER_SIZE + sizeof(uint16_t) ||
2140 return SET_ERRNO(ERROR_INVALID_FRAME, "Invalid queued Opus audio packet");
2141 }
2142
2143 const uint8_t *data = packet->data;
2144 uint32_t sample_rate_net, frame_duration_net, frame_count_net;
2145 uint16_t frame_size_net;
2146 memcpy(&sample_rate_net, data, sizeof(sample_rate_net));
2147 memcpy(&frame_duration_net, data + sizeof(uint32_t), sizeof(frame_duration_net));
2148 memcpy(&frame_count_net, data + 2 * sizeof(uint32_t), sizeof(frame_count_net));
2149 memcpy(&frame_size_net, data + AUDIO_OPUS_BATCH_HEADER_SIZE, sizeof(frame_size_net));
2150
2151 uint32_t sample_rate = NET_TO_HOST_U32(sample_rate_net);
2152 uint32_t frame_duration = NET_TO_HOST_U32(frame_duration_net);
2153 uint32_t frame_count = NET_TO_HOST_U32(frame_count_net);
2154 uint16_t frame_size = NET_TO_HOST_U16(frame_size_net);
2155 if (sample_rate != 48000 || frame_duration != 20 || frame_count != 1 || frame_size == 0 ||
2157 sizeof(uint16_t) + frame_size) {
2158 return SET_ERRNO(ERROR_INVALID_FRAME, "Unexpected queued Opus audio batch format");
2159 }
2160 if (opus_bytes > payload_capacity || frame_size > payload_capacity - opus_bytes) {
2161 return SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Combined Opus audio packet exceeds buffer capacity");
2162 }
2163 frame_sizes[i] = frame_size;
2164 opus_bytes += frame_size;
2165 }
2166
2167 size_t frame_sizes_bytes = (size_t)packet_count * sizeof(uint16_t);
2168 size_t opus_offset = AUDIO_OPUS_BATCH_HEADER_SIZE + frame_sizes_bytes;
2169 if (opus_offset > payload_capacity || opus_bytes > payload_capacity - opus_offset) {
2170 return SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Combined Opus audio packet exceeds buffer capacity");
2171 }
2172
2173 uint32_t sample_rate_net = HOST_TO_NET_U32(48000);
2174 uint32_t frame_duration_net = HOST_TO_NET_U32(20);
2175 uint32_t frame_count_net = HOST_TO_NET_U32((uint32_t)packet_count);
2176 uint32_t reserved = 0;
2177 memcpy(payload, &sample_rate_net, sizeof(sample_rate_net));
2178 memcpy(payload + sizeof(uint32_t), &frame_duration_net, sizeof(frame_duration_net));
2179 memcpy(payload + 2 * sizeof(uint32_t), &frame_count_net, sizeof(frame_count_net));
2180 memcpy(payload + 3 * sizeof(uint32_t), &reserved, sizeof(reserved));
2181
2182 size_t output_offset = opus_offset;
2183 for (int i = 0; i < packet_count; i++) {
2184 uint16_t frame_size_net = HOST_TO_NET_U16(frame_sizes[i]);
2185 memcpy(payload + AUDIO_OPUS_BATCH_HEADER_SIZE + (size_t)i * sizeof(uint16_t), &frame_size_net,
2186 sizeof(frame_size_net));
2187 memcpy(payload + output_offset, (const uint8_t *)packets[i]->data + AUDIO_OPUS_BATCH_HEADER_SIZE + sizeof(uint16_t),
2188 frame_sizes[i]);
2189 output_offset += frame_sizes[i];
2190 }
2191
2192 *payload_length = output_offset;
2193 return ASCIICHAT_OK;
2194}
2195
2196void *client_send_thread_func(void *arg) {
2197 client_info_t *client = (client_info_t *)arg;
2198
2199 // Validate client pointer immediately before any access.
2200 // This prevents crashes if remove_client() has zeroed the client struct
2201 // while the thread was still starting at RtlUserThreadStart.
2202 if (!client) {
2203 log_error("Invalid client info in send thread (NULL pointer)");
2204 return NULL;
2205 }
2206
2208
2209 // Check if client_id is empty (client struct has been zeroed by remove_client)
2210 // This must be checked BEFORE accessing any client fields
2211 if (client->client_id[0] == 0) {
2213 "Send thread: client_id is empty, client struct may have been zeroed, exiting");
2214 return NULL;
2215 }
2216
2217 // Additional validation: check socket OR transport is valid
2218 // For TCP clients: socket is valid
2219 // For WebRTC clients: socket is INVALID_SOCKET_VALUE but transport is valid
2220 mutex_lock(&client->send_mutex);
2221 bool has_socket = (client->socket != INVALID_SOCKET_VALUE);
2222 bool has_transport = (client->transport != NULL);
2223 mutex_unlock(&client->send_mutex);
2224
2225 log_info("SEND_THREAD_VALIDATION: client_id=%s socket_valid=%d transport_valid=%d transport_ptr=%p",
2226 client->client_id, has_socket, has_transport, (void *)client->transport);
2227
2228 if (!has_socket && !has_transport) {
2229 log_error("Invalid client connection in send thread (no socket or transport)");
2230 return NULL;
2231 }
2232
2233 log_info("Started send thread for client %s (%s)", client->client_id, client->display_name);
2234
2235 // Mark thread as running
2236 atomic_store_bool(&client->send_thread_running, true);
2237
2238 log_info("SEND_THREAD_LOOP_START: client_id=%s active=%d shutting_down=%d running=%d", client->client_id,
2241
2242 // Track timing for video frame sends
2243 uint64_t last_video_send_time = 0;
2244 const uint64_t video_send_interval_us = 16666; // 60fps = ~16.67ms
2245 const int target_video_fps = client->has_terminal_caps && client->terminal_caps.desired_fps > 0
2246 ? client->terminal_caps.desired_fps
2248
2249 // High-frequency audio loop - separate from video frame loop
2250 // to ensure audio packets are sent immediately, not rate-limited by video
2251// A video deadline must still be reachable while audio is active. Sending a
2252// deep audio batch on the same ordered WebRTC channel lets one client hold
2253// video (and the next audio update) behind several synchronous sends.
2254 int loop_iteration_count = 0;
2255 bool transport_failed = false;
2256 uint64_t video_rate_window_start_ns = time_get_ns();
2257 unsigned long video_rate_window_start_count = atomic_load_u64(&client->frames_sent_count);
2260 loop_iteration_count++;
2261 bool sent_something = false;
2262 uint64_t loop_start_ns = time_get_ns();
2263 log_dev_every(5000 * US_PER_MS_INT, "[SEND_LOOP_%d] START: client=%s", loop_iteration_count, client->client_id);
2264
2265 // PRIORITY: Drain all queued audio packets before video
2266 // Audio must not be rate-limited by video frame sending (16.67ms)
2267 queued_packet_t *audio_packets[MAX_AUDIO_BATCH];
2268 int audio_packet_count = 0;
2269
2270 if (client->audio_queue) {
2271 // Try to dequeue multiple audio packets
2272 for (int i = 0; i < MAX_AUDIO_BATCH; i++) {
2273 audio_packets[i] = packet_queue_try_dequeue(client->audio_queue);
2274 if (audio_packets[i]) {
2275 audio_packet_count++;
2276 } else {
2277 break; // No more packets available
2278 }
2279 }
2280 if (audio_packet_count > 0) {
2281 log_dev_every(4500 * US_PER_MS_INT, "SEND_AUDIO: client=%s dequeued=%d packets", client->client_id,
2282 audio_packet_count);
2283 }
2284 } else {
2285 log_warn("Send thread: audio_queue is NULL for client %s", client->client_id);
2286 }
2287
2288 // Send batched audio if we have packets
2289 if (audio_packet_count > 0) {
2290 // Protect crypto field access with mutex
2292 bool crypto_ready = client->transport_encrypted || GET_OPTION(no_encrypt) ||
2295
2296 // Audio packets cannot be sent until crypto handshake completes (protocol requirement)
2297 if (!crypto_ready) {
2298 log_dev_every(1000 * US_PER_MS_INT, "SKIP_AUDIO: client=%s handshake not ready, dropping %d audio packets",
2299 client->client_id, audio_packet_count);
2300 // Drop audio packets until handshake completes
2301 for (int i = 0; i < audio_packet_count; i++) {
2302 packet_queue_free_packet(audio_packets[i]);
2303 }
2304 audio_packet_count = 0;
2305 }
2306
2308
2309 // Only send audio if packets survived crypto check (count > 0 after potential drop)
2310 if (audio_packet_count > 0) {
2311 uint8_t combined_audio_payload[AUDIO_OPUS_BATCH_HEADER_SIZE + MAX_AUDIO_BATCH *
2313 const void *audio_payload = audio_packets[0]->data;
2314 size_t audio_payload_length = audio_packets[0]->data_len;
2315 if (audio_packet_count > 1) {
2316 result = combine_audio_opus_packets(audio_packets, audio_packet_count, combined_audio_payload,
2317 sizeof(combined_audio_payload), &audio_payload_length);
2318 if (result == ASCIICHAT_OK) {
2319 audio_payload = combined_audio_payload;
2320 }
2321 }
2322
2323 mutex_lock(&client->send_mutex);
2324 acip_transport_t *transport = client->transport;
2325 bool stopping = atomic_load_bool(&client->shutting_down) || !transport;
2326 if (stopping) {
2327 mutex_unlock(&client->send_mutex);
2328 result = ERROR_NETWORK;
2329 } else if (result != ASCIICHAT_OK) {
2330 mutex_unlock(&client->send_mutex);
2331 } else {
2332 uint32_t client_id_hash = fnv1a_hash_string(client->client_id);
2333 result = packet_send_via_transport(transport, PACKET_TYPE_AUDIO_OPUS_BATCH, audio_payload,
2334 audio_payload_length, client_id_hash);
2335 mutex_unlock(&client->send_mutex);
2336 }
2337 } // End of if (audio_packet_count > 0)
2338
2339 // Free all audio packets
2340 for (int i = 0; i < audio_packet_count; i++) {
2341 packet_queue_free_packet(audio_packets[i]);
2342 }
2343
2344 if (result != ASCIICHAT_OK) {
2346 log_error("Failed to send audio to client %s: %s", client->client_id, asciichat_error_string(result));
2347 }
2348 // Network errors corrupt the TCP stream - must disconnect immediately
2349 if (result == ERROR_NETWORK) {
2350 transport_failed = true;
2351 log_error("CLOSING_CLIENT_ON_NETWORK_ERROR: client_id=%s due to audio send failure (corrupted stream)",
2352 client->client_id);
2353 break; // Exit send loop to trigger cleanup
2354 }
2355 log_warn("SKIP_AUDIO_ERROR: client_id=%s result=%d (continuing to send video)", client->client_id, result);
2356 // Continue sending video for non-network errors (audio is optional for browser clients)
2357 }
2358
2359 sent_something = true;
2360 uint64_t audio_done_ns = time_get_ns();
2361 char audio_elapsed_str[32];
2362 time_pretty(audio_done_ns - loop_start_ns, -1, audio_elapsed_str, sizeof(audio_elapsed_str));
2363 log_dev_every(5000 * US_PER_MS_INT, "[SEND_LOOP_%d] AUDIO_SENT: took %s", loop_iteration_count,
2364 audio_elapsed_str);
2365
2366 } else {
2367 // No audio packets - brief sleep to avoid busy-looping, then check for other tasks
2368 log_dev_every(5000 * US_PER_MS_INT, "[SEND_LOOP_%d] NO_AUDIO: sleeping 1ms", loop_iteration_count);
2369 platform_sleep_us(1 * US_PER_MS_INT); // 1ms - enough for audio render thread to queue more packets
2370
2371 // Check if session rekeying should be triggered
2373 bool should_rekey = !GET_OPTION(no_encrypt) && client->crypto_initialized &&
2377
2378 if (should_rekey) {
2379 log_debug("Rekey threshold reached for client %s, initiating session rekey", client->client_id);
2381 // Get transport reference briefly to avoid deadlock on transport buffers
2382 mutex_lock(&client->send_mutex);
2383 if (atomic_load_bool(&client->shutting_down) || !client->transport) {
2384 mutex_unlock(&client->send_mutex);
2386 log_warn("BREAK_REKEY: client_id=%s shutting_down=%d transport=%p", client->client_id,
2387 atomic_load_bool(&client->shutting_down), (void *)client->transport);
2388 break; // Client is shutting down, exit thread
2389 }
2390 acip_transport_t *rekey_transport = client->transport;
2391 mutex_unlock(&client->send_mutex);
2392
2393 // Network I/O happens OUTSIDE the send_mutex (client_state_mutex still held for crypto state)
2394 asciichat_error_t result = crypto_handshake_rekey_request(&client->crypto_handshake_ctx, rekey_transport);
2396
2397 if (result != ASCIICHAT_OK) {
2398 log_error("Failed to send REKEY_REQUEST to client %s: %d", client->client_id, result);
2399 } else {
2400 log_debug("Sent REKEY_REQUEST to client %s", client->client_id);
2401 // Notify client that session rekeying has been initiated (old keys still active)
2402 log_info_client(client, "Session rekey initiated - rotating encryption keys");
2403 }
2404 }
2405 }
2406
2407 // Leave the newest frame available until the send deadline. Consuming it
2408 // before pacing is ready discards frames when render and send clocks drift.
2409 uint64_t pacing_time_us = time_ns_to_us(time_get_ns());
2410 if (pacing_time_us - last_video_send_time < video_send_interval_us) {
2412 continue;
2413 }
2414 uint64_t video_check_ns = time_get_ns();
2415 if (!client->outgoing_video_buffer) {
2416 // Buffer has been destroyed (client is shutting down).
2417 // Exit cleanly instead of looping forever trying to access freed memory.
2418 log_warn("⚠️ Send thread exiting: outgoing_video_buffer is NULL for client %s (client shutting down?)",
2419 client->client_id);
2420 break;
2421 }
2422
2423 // Check the frame deadline before consuming the latest-frame flag.
2424 uint64_t current_time_ns = time_get_ns();
2425 uint64_t current_time_us = time_ns_to_us(current_time_ns);
2426 uint64_t time_since_last_send_us = current_time_us - last_video_send_time;
2428 "Send thread timing check: time_since_last=%llu us, interval=%llu us, should_send=%d",
2429 (unsigned long long)time_since_last_send_us, (unsigned long long)video_send_interval_us,
2430 (time_since_last_send_us >= video_send_interval_us));
2431
2432 if (current_time_us - last_video_send_time >= video_send_interval_us) {
2433 video_frame_buffer_t *outgoing_buffer = client->outgoing_video_buffer;
2434 const video_frame_t *frame = NULL;
2435 char *frame_data_snapshot = NULL;
2436 size_t frame_size = 0;
2437 uint32_t width = 0;
2438 uint32_t height = 0;
2439
2440 // The frame pointer is borrowed only until the next commit. Keep the
2441 // swap mutex held through the copy so a slow network send cannot read a
2442 // buffer after the render thread has reused it.
2443 mutex_lock(&outgoing_buffer->swap_mutex);
2444 if (atomic_load_bool(&outgoing_buffer->new_frame_available)) {
2445 frame = outgoing_buffer->front_buffer;
2446 if (frame && frame->data && frame->size > 0 &&
2447 frame->size <= outgoing_buffer->allocated_buffer_size) {
2448 frame_size = frame->size;
2449 width = frame->width;
2450 height = frame->height;
2451 frame_data_snapshot = SAFE_MALLOC(frame_size, char *);
2452 if (frame_data_snapshot) {
2453 memcpy(frame_data_snapshot, frame->data, frame_size);
2454 atomic_store_bool(&outgoing_buffer->new_frame_available, false);
2455 }
2456 }
2457 }
2458 mutex_unlock(&outgoing_buffer->swap_mutex);
2459
2460 uint64_t frame_get_ns = time_get_ns();
2461 char frame_get_elapsed_str[32];
2462 time_pretty(frame_get_ns - video_check_ns, -1, frame_get_elapsed_str, sizeof(frame_get_elapsed_str));
2463 log_dev_every(5000 * US_PER_MS_INT, "[SEND_LOOP_%d] VIDEO_SNAPSHOT: took %s, frame=%p, bytes=%zu",
2464 loop_iteration_count, frame_get_elapsed_str, (const void *)frame, frame_size);
2465
2466 if (!frame_data_snapshot) {
2467 // The render thread may not have committed a frame yet, or the latest
2468 // frame could not be copied. Keep the sender alive and try again.
2469 log_dev_every(LOG_RATE_SLOW, "Send thread: no snapshot ready for client %s", client->client_id);
2471 continue;
2472 }
2473
2474 log_dev_every(5000 * US_PER_MS_INT, "SEND_TIME_READY: client_id=%s time_since=%llu interval=%llu",
2475 client->client_id, (unsigned long long)time_since_last_send_us,
2476 (unsigned long long)video_send_interval_us);
2477 uint64_t frame_start_ns = time_get_ns();
2478
2479 // GRID LAYOUT CHANGE: Check if render thread has buffered a frame with different source count
2480 // If so, send CLEAR_CONSOLE before sending the new frame
2481 int rendered_sources = (int)atomic_load_u64(&client->last_rendered_grid_sources);
2482 int sent_sources = (int)atomic_load_u64(&client->last_sent_grid_sources);
2483
2484 if (rendered_sources != sent_sources && rendered_sources > 0) {
2485 // Grid layout changed! Send CLEAR_CONSOLE before next frame using ACIP transport
2486 // Keep the send lock until the complete ACIP packet is written. TCP is
2487 // a byte stream, so a PONG or state update from another thread must not
2488 // interleave with this packet.
2489 mutex_lock(&client->send_mutex);
2490 if (atomic_load_bool(&client->shutting_down) || !client->transport) {
2491 mutex_unlock(&client->send_mutex);
2492 SAFE_FREE(frame_data_snapshot);
2493 log_warn("BREAK_CLEAR_CONSOLE: client_id=%s shutting_down=%d transport=%p", client->client_id,
2494 atomic_load_bool(&client->shutting_down), (void *)client->transport);
2495 break; // Client is shutting down, exit thread
2496 }
2497 acip_transport_t *clear_transport = client->transport;
2498 acip_send_clear_console(clear_transport);
2499 mutex_unlock(&client->send_mutex);
2500 log_debug_every(LOG_RATE_FAST, "Client %s: Sent CLEAR_CONSOLE (grid changed %d → %d sources)",
2501 client->client_id, sent_sources, rendered_sources);
2502 atomic_store_u64(&client->last_sent_grid_sources, rendered_sources);
2503 sent_something = true;
2504 }
2505
2507 "🎯 FRAME_VALIDATION: client_id=%s frame=%p, frame->data=%p, frame->size=%zu", client->client_id,
2508 (const void *)frame, (void *)frame_data_snapshot, frame_size);
2509
2510 log_info_every(5000 * US_PER_MS_INT, "✅ FRAME_DATA_OK: client_id=%s data=%p", client->client_id,
2511 (void *)frame_data_snapshot);
2512 log_info_every(5000 * US_PER_MS_INT, "✅ FRAME_SIZE_OK: client_id=%s size=%zu", client->client_id, frame_size);
2513
2514 const char *frame_data = frame_data_snapshot;
2515 uint64_t step1_ns = time_get_ns();
2516 uint64_t step2_ns = time_get_ns();
2517 uint64_t step3_ns = time_get_ns();
2518 uint64_t step4_ns = time_get_ns();
2519
2520 // Check if crypto handshake is complete before sending (prevents sending to unauthenticated clients)
2522 bool crypto_ready = client->transport_encrypted || GET_OPTION(no_encrypt) ||
2525
2527 "🔐 CRYPTO_CHECK: client_id=%s crypto_ready=%d crypto_initialized=%d no_encrypt=%d",
2528 client->client_id, crypto_ready, client->crypto_initialized, GET_OPTION(no_encrypt));
2529
2530 if (!crypto_ready) {
2531 log_info("⚠️ SKIP_SEND_CRYPTO: client_id=%s crypto_initialized=%d no_encrypt=%d", client->client_id,
2532 client->crypto_initialized, GET_OPTION(no_encrypt));
2533 SAFE_FREE(frame_data_snapshot);
2534 continue; // Skip this frame, will try again on next loop iteration
2535 }
2536 log_info_every(5000 * US_PER_MS_INT, "✅ CRYPTO_READY: client_id=%s about to send frame", client->client_id);
2537
2538 // ACIP transport handles header building, CRC32, and encryption. Keep
2539 // send_mutex for the complete write so packets from the receive thread
2540 // cannot splice into this TCP packet.
2542 "Send thread: About to send frame to client %s (width=%u, height=%u, size=%zu, data=%p)",
2543 client->client_id, width, height, frame_size, (void *)frame_data);
2544
2545 // Log first 32 bytes of frame data to verify we can access it
2546 if (frame_data && frame_size > 0) {
2548 "FRAME_DATA_HEX: client=%s first_bytes=[%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x "
2549 "%02x %02x %02x %02x %02x]",
2550 client->client_id, ((uint8_t *)frame_data)[0], ((uint8_t *)frame_data)[1],
2551 ((uint8_t *)frame_data)[2], ((uint8_t *)frame_data)[3], ((uint8_t *)frame_data)[4],
2552 ((uint8_t *)frame_data)[5], ((uint8_t *)frame_data)[6], ((uint8_t *)frame_data)[7],
2553 ((uint8_t *)frame_data)[8], ((uint8_t *)frame_data)[9], ((uint8_t *)frame_data)[10],
2554 ((uint8_t *)frame_data)[11], ((uint8_t *)frame_data)[12], ((uint8_t *)frame_data)[13],
2555 ((uint8_t *)frame_data)[14], ((uint8_t *)frame_data)[15]);
2556 }
2557
2558 mutex_lock(&client->send_mutex);
2559 if (atomic_load_bool(&client->shutting_down) || !client->transport) {
2560 mutex_unlock(&client->send_mutex);
2561 SAFE_FREE(frame_data_snapshot);
2562 log_warn("BREAK_FRAME_SEND: client_id=%s shutting_down=%d transport=%p loop_iter=%d", client->client_id,
2563 atomic_load_bool(&client->shutting_down), (void *)client->transport, loop_iteration_count);
2564 break; // Client is shutting down, exit thread
2565 }
2566 acip_transport_t *frame_transport = client->transport;
2567 log_dev_every(4500 * US_PER_MS_INT, "SEND_ASCII_FRAME: client_id=%s tcp=%d size=%zu width=%u height=%u",
2568 client->client_id, client->is_tcp_client, frame_size, width, height);
2569 uint64_t send_start_ns = time_get_ns();
2570 log_dev_every(4500 * US_PER_MS_INT, "[SEND_LOOP_%d] FRAME_SEND_START: client_id=%s size=%zu",
2571 loop_iteration_count, client->client_id, frame_size);
2572 asciichat_error_t send_result =
2573 acip_send_ascii_frame(frame_transport, frame_data, frame_size, width, height, client->client_id);
2574 mutex_unlock(&client->send_mutex);
2575 SAFE_FREE(frame_data_snapshot);
2576 uint64_t send_end_ns = time_get_ns();
2577 char send_elapsed_str[32];
2578 time_pretty(send_end_ns - send_start_ns, -1, send_elapsed_str, sizeof(send_elapsed_str));
2579 log_dev_every(4500 * US_PER_MS_INT, "[SEND_LOOP_%d] FRAME_SEND_END: client_id=%s took %s result=%d",
2580 loop_iteration_count, client->client_id, send_elapsed_str, send_result);
2581 uint64_t step5_ns = time_get_ns();
2582
2583 if (send_result != ASCIICHAT_OK) {
2584 transport_failed = send_result == ERROR_NETWORK;
2586 SET_ERRNO(ERROR_NETWORK, "Failed to send video frame to client %s: %s", client->client_id,
2587 asciichat_error_string(send_result));
2588 }
2589 log_error("SEND_FRAME_FAILED: client_id=%s result=%d message=%s loop_iter=%d", client->client_id, send_result,
2590 asciichat_error_string(send_result), loop_iteration_count);
2591 log_warn("BREAK_SEND_ERROR: client_id=%s (frame send failed)", client->client_id);
2592 break;
2593 }
2594
2595 log_dev_every(4500 * US_PER_MS_INT, "SEND_FRAME_SUCCESS: client_id=%s size=%zu", client->client_id, frame_size);
2596
2597 // Increment frame counter for metrics.
2598 unsigned long frame_count = atomic_fetch_add_u64(&client->frames_sent_count, 1) + 1;
2599 log_dev_every(4500 * US_PER_MS_INT, "FRAME_SENT: client_id=%s frame_num=%lu size=%zu", client->client_id,
2600 frame_count, frame_size);
2601 uint64_t rate_now_ns = time_get_ns();
2602 uint64_t rate_elapsed_ns = rate_now_ns - video_rate_window_start_ns;
2603 if (rate_elapsed_ns >= NS_PER_SEC_INT) {
2604 unsigned long rate_frames = frame_count - video_rate_window_start_count;
2605 double delivery_fps = (double)rate_frames * NS_PER_SEC_INT / (double)rate_elapsed_ns;
2606 if (delivery_fps < (double)target_video_fps * 0.9) {
2607 log_warn("Server ASCII delivery lagging client=%s fps=%.1f target=%d frames=%lu elapsed=%.2fs",
2608 client->client_id, delivery_fps, target_video_fps, rate_frames,
2609 (double)rate_elapsed_ns / (double)NS_PER_SEC_INT);
2610 } else {
2611 log_info("Server ASCII delivery client=%s fps=%.1f target=%d frames=%lu elapsed=%.2fs", client->client_id,
2612 delivery_fps, target_video_fps, rate_frames,
2613 (double)rate_elapsed_ns / (double)NS_PER_SEC_INT);
2614 }
2615 video_rate_window_start_ns = rate_now_ns;
2616 video_rate_window_start_count = frame_count;
2617 }
2618
2619 sent_something = true;
2620 last_video_send_time = last_video_send_time == 0
2621 ? current_time_us
2622 : current_time_us - (current_time_us - last_video_send_time) % video_send_interval_us;
2623
2624 uint64_t frame_end_ns = time_get_ns();
2625 uint64_t frame_time_us = time_ns_to_us(time_elapsed_ns(frame_start_ns, frame_end_ns));
2626 if (frame_time_us > 15 * US_PER_MS_INT) { // Log if sending a frame takes > 15ms (encryption adds ~5-6ms)
2627 uint64_t step1_us = time_ns_to_us(time_elapsed_ns(frame_start_ns, step1_ns));
2628 uint64_t step2_us = time_ns_to_us(time_elapsed_ns(step1_ns, step2_ns));
2629 uint64_t step3_us = time_ns_to_us(time_elapsed_ns(step2_ns, step3_ns));
2630 uint64_t step4_us = time_ns_to_us(time_elapsed_ns(step3_ns, step4_ns));
2631 uint64_t step5_us = time_ns_to_us(time_elapsed_ns(step4_ns, step5_ns));
2634 "SEND_THREAD: Frame send took %.2fms for client %s | Snapshot: %.2fms | Memcpy: %.2fms | CRC32: %.2fms | "
2635 "Header: %.2fms | send_packet_secure: %.2fms",
2636 frame_time_us / 1000.0, client->client_id, step1_us / 1000.0, step2_us / 1000.0, step3_us / 1000.0,
2637 step4_us / 1000.0, step5_us / 1000.0);
2638 }
2639 }
2640
2641 // If we didn't send anything, sleep briefly to prevent busy waiting
2642 if (!sent_something) {
2643 log_dev_every(5000 * US_PER_MS_INT, "[SEND_LOOP_%d] IDLE_SLEEP: nothing sent", loop_iteration_count);
2644 platform_sleep_us(1 * US_PER_MS_INT); // 1ms sleep
2645 }
2646 uint64_t loop_end_ns = time_get_ns();
2647 uint64_t loop_elapsed_ns = loop_end_ns - loop_start_ns;
2648 if (loop_elapsed_ns > 10 * NS_PER_MS) {
2649 char loop_elapsed_str[32];
2650 time_pretty(loop_elapsed_ns, -1, loop_elapsed_str, sizeof(loop_elapsed_str));
2651 log_warn_every(LOG_RATE_DEFAULT, "SEND_LOOP: took %s (client=%s)", loop_elapsed_str, client->client_id);
2652 }
2653 }
2654
2655 // Log why the send thread exited
2656 log_warn("Send thread exit conditions - client_id=%s g_should_exit=%d shutting_down=%d active=%d "
2657 "send_thread_running=%d",
2660
2661 // Mark thread as stopped
2662 atomic_store_bool(&client->send_thread_running, false);
2663
2664 // The receive loop can remain alive while the media send path has already
2665 // discovered a dead WebRTC DataChannel. Retire the whole client here so its
2666 // render workers cannot keep producing media for a peer that is gone.
2667 if (transport_failed && !client->is_tcp_client && !atomic_load_bool(&g_should_exit) &&
2668 !atomic_load_bool(&client->shutting_down)) {
2669 char client_id_snapshot[MAX_CLIENT_ID_LEN] = {0};
2670 SAFE_STRNCPY(client_id_snapshot, client->client_id, sizeof(client_id_snapshot) - 1);
2671 server_context_t *server_ctx = (server_context_t *)client->server_ctx;
2672 if (server_ctx && client_id_snapshot[0] != '\0') {
2673 log_info("Removing WebRTC client %s after terminal send failure", client_id_snapshot);
2674 (void)remove_client(server_ctx, client_id_snapshot);
2675 }
2676 }
2677
2678 log_debug("Send thread for client %s terminated", client->client_id);
2679
2680 // Clean up thread-local error context before exit
2682
2683 return NULL;
2684}
2685
2686/* ============================================================================
2687 * Broadcast Functions
2688 * ============================================================================
2689 */
2690
2691// Broadcast server state to all connected clients
2693 // SNAPSHOT PATTERN: Collect client data while holding lock, then release before network I/O
2694 typedef struct {
2695 socket_t socket;
2696 char client_id[MAX_CLIENT_ID_LEN];
2697 const crypto_context_t *crypto_ctx;
2698 } client_snapshot_t;
2699
2700 client_snapshot_t client_snapshots[MAX_CLIENTS];
2701 int snapshot_count = 0;
2702 int active_video_count = 0;
2703
2704 uint64_t lock_start_ns = time_get_ns();
2706 uint64_t lock_end_ns = time_get_ns();
2707 uint64_t lock_time_ns = time_elapsed_ns(lock_start_ns, lock_end_ns);
2708 if (lock_time_ns > 1 * NS_PER_MS_INT) {
2709 char duration_str[32];
2710 time_pretty((uint64_t)((double)lock_time_ns), -1, duration_str, sizeof(duration_str));
2711 log_warn("broadcast_server_state: rwlock_rdlock took %s", duration_str);
2712 }
2713
2714 // Count active clients and snapshot client data while holding lock
2715 // Use atomic_load for all atomic fields to prevent data races.
2716 for (int i = 0; i < MAX_CLIENTS; i++) {
2717 bool is_active = atomic_load_bool(&g_client_manager.clients[i].active);
2719 active_video_count++;
2720 }
2721 if (is_active && g_client_manager.clients[i].socket != INVALID_SOCKET_VALUE) {
2722 // Skip clients that haven't completed crypto handshake yet
2723 // Check both crypto_initialized AND crypto context (defense in depth)
2724 if (!GET_OPTION(no_encrypt) && !g_client_manager.clients[i].crypto_initialized) {
2725 log_debug("Skipping server_state broadcast to client %s: crypto handshake not complete",
2727 continue;
2728 }
2729
2730 // Get crypto context (may be NULL if --no-encrypt)
2731 const crypto_context_t *crypto_ctx =
2733 if (!GET_OPTION(no_encrypt) && !crypto_ctx) {
2734 // Skip clients without valid crypto context
2735 log_debug("Skipping server_state broadcast to client %s: no crypto context",
2737 continue;
2738 }
2739
2740 client_snapshots[snapshot_count].socket = g_client_manager.clients[i].socket;
2741 SAFE_STRNCPY(client_snapshots[snapshot_count].client_id, g_client_manager.clients[i].client_id,
2742 sizeof(client_snapshots[snapshot_count].client_id) - 1);
2743 client_snapshots[snapshot_count].crypto_ctx = crypto_ctx;
2744 snapshot_count++;
2745 }
2746 }
2747
2748 // Prepare server state packet while still holding lock (fast operation)
2751 state.active_client_count = active_video_count;
2752 memset(state.reserved, 0, sizeof(state.reserved));
2753
2754 // Convert to network byte order
2755 server_state_packet_t net_state;
2758 memset(net_state.reserved, 0, sizeof(net_state.reserved));
2759
2760 // Release lock BEFORE sending (snapshot pattern)
2761 // Sending while holding lock blocks all client operations
2762 uint64_t lock_held_final_ns = time_get_ns();
2763 uint64_t lock_held_ns = time_elapsed_ns(lock_start_ns, lock_held_final_ns);
2765
2766 // Send to all clients AFTER releasing the lock
2767 // This prevents blocking other threads during network I/O
2768 for (int i = 0; i < snapshot_count; i++) {
2770 "BROADCAST_DEBUG: Sending SERVER_STATE to client %s (socket %d) with crypto_ctx=%p",
2771 client_snapshots[i].client_id, client_snapshots[i].socket, (void *)client_snapshots[i].crypto_ctx);
2772
2773 // Protect socket write with per-client send_mutex.
2774 client_info_t *target = find_client_by_id(client_snapshots[i].client_id);
2775 if (target) {
2776 // IMPORTANT: Verify client_id matches expected value - prevents use-after-free
2777 // if client was removed and replaced with another client in same slot
2778 if (strcmp(target->client_id, client_snapshots[i].client_id) != 0) {
2779 log_warn("Client %s ID mismatch during broadcast (found %s), skipping send", client_snapshots[i].client_id,
2780 target->client_id);
2781 continue;
2782 }
2783
2784 mutex_lock(&target->send_mutex);
2785
2786 // Double-check client_id again after acquiring mutex (stronger protection)
2787 if (strcmp(target->client_id, client_snapshots[i].client_id) != 0) {
2788 mutex_unlock(&target->send_mutex);
2789 log_warn("Client %s was removed during broadcast send (now %s), skipping", client_snapshots[i].client_id,
2790 target->client_id);
2791 continue;
2792 }
2793
2794 // Send via ACIP transport
2795 asciichat_error_t result = acip_send_server_state(target->transport, &net_state);
2796 mutex_unlock(&target->send_mutex);
2797
2798 if (result != ASCIICHAT_OK) {
2799 log_error("Failed to send server state to client %s: %s", client_snapshots[i].client_id,
2800 asciichat_error_string(result));
2801 } else {
2802 log_debug_every(5000 * US_PER_MS_INT, "Sent server state to client %s: %u connected, %u active",
2803 client_snapshots[i].client_id, state.connected_client_count, state.active_client_count);
2804 }
2805 } else {
2806 log_warn("Client %s removed before broadcast send could complete", client_snapshots[i].client_id);
2807 }
2808 }
2809
2810 if (lock_held_ns > 1 * NS_PER_MS_INT) {
2811 char duration_str[32];
2812 time_pretty((uint64_t)((double)lock_held_ns), -1, duration_str, sizeof(duration_str));
2813 log_warn("broadcast_server_state: rwlock held for %s (includes network I/O)", duration_str);
2814 }
2815}
2816
2817/* ============================================================================
2818 * Helper Functions
2819 * ============================================================================
2820 */
2821
2832int start_webrtc_client_threads(server_context_t *server_ctx, const char *client_id) {
2833 if (!server_ctx) {
2834 SET_ERRNO(ERROR_INVALID_PARAM, "Server context is NULL");
2835 return -1;
2836 }
2837
2838 client_info_t *client = find_client_by_id(client_id);
2839 if (!client) {
2840 SET_ERRNO(ERROR_NOT_FOUND, "Client %s not found", client_id);
2841 return -1;
2842 }
2843
2844 log_debug("Starting threads for WebRTC client %s...", client_id);
2845 return start_client_threads(server_ctx, client, false);
2846}
2847
2849 if (!client) {
2850 SET_ERRNO(ERROR_INVALID_PARAM, "Client is NULL");
2851 return;
2852 }
2853
2854 // Signal threads to stop
2855 log_debug("Setting active=false in stop_client_threads (client_id=%s)", client->client_id);
2856 atomic_store_bool(&client->active, false);
2857 atomic_store_bool(&client->send_thread_running, false);
2858
2859 // Wait for threads to finish
2861 asciichat_thread_join(&client->send_thread, NULL);
2862 }
2864 asciichat_thread_join(&client->receive_thread, NULL);
2865 }
2866 // For async dispatch: stop dispatch thread if running
2869 asciichat_thread_join(&client->dispatch_thread, NULL);
2870 }
2871}
2872
2874 if (!client) {
2875 SET_ERRNO(ERROR_INVALID_PARAM, "Client is NULL");
2876 return;
2877 }
2878
2879 if (client->digital_rain) {
2881 client->digital_rain = NULL;
2882 }
2883
2884 if (client->incoming_video_buffer) {
2886 client->incoming_video_buffer = NULL;
2887 }
2888
2889 // Clean up outgoing video buffer (for ASCII frames)
2890 if (client->outgoing_video_buffer) {
2892 client->outgoing_video_buffer = NULL;
2893 }
2894
2895 // Clean up pre-allocated send buffer
2896 if (client->send_buffer) {
2897 SAFE_FREE(client->send_buffer);
2898 client->send_buffer = NULL;
2899 client->send_buffer_size = 0;
2900 }
2901
2902 if (client->incoming_audio_buffer) {
2904 client->incoming_audio_buffer = NULL;
2905 }
2906
2907 // Clean up Opus decoder
2908 if (client->opus_decoder) {
2910 client->opus_decoder = NULL;
2911 }
2912
2913 // Clean up H.265 decoder (persistent per-client decoder)
2914 if (client->h265_decoder_ctx) {
2915 avcodec_free_context((AVCodecContext **)&client->h265_decoder_ctx);
2916 client->h265_decoder_ctx = NULL;
2917 }
2918 if (client->h265_decode_frame) {
2919 av_frame_free((AVFrame **)&client->h265_decode_frame);
2920 client->h265_decode_frame = NULL;
2921 }
2922 if (client->h265_rgb_frame) {
2923 av_frame_free((AVFrame **)&client->h265_rgb_frame);
2924 client->h265_rgb_frame = NULL;
2925 }
2926 if (client->h265_sws_ctx) {
2927 sws_freeContext(client->h265_sws_ctx);
2928 client->h265_sws_ctx = NULL;
2929 }
2930}
2931
2933 if (!client)
2934 return;
2935
2936 if (client->audio_queue) {
2938 client->audio_queue = NULL;
2939 }
2940
2941 // Async dispatch: clean up received packet queue
2942 if (client->received_packet_queue) {
2944 client->received_packet_queue = NULL;
2945 }
2946
2947 // Video now uses double buffer, cleaned up in cleanup_client_media_buffers
2948}
2949
2959static inline void cleanup_client_all_buffers(client_info_t *client) {
2962}
2963
2974int process_encrypted_packet(client_info_t *client, packet_type_t *type, void **data, size_t *len,
2975 uint32_t *sender_id) {
2976 if (!crypto_server_is_ready(client->client_id)) {
2977 log_error("Received encrypted packet but crypto not ready for client %s", client->client_id);
2978 buffer_pool_free(NULL, *data, *len);
2979 *data = NULL;
2980 return -1;
2981 }
2982
2983 // Store original allocation size before it gets modified
2984 size_t original_alloc_size = *len;
2985 void *decrypted_data = buffer_pool_alloc(NULL, original_alloc_size);
2986 size_t decrypted_len;
2987 int decrypt_result = crypto_server_decrypt_packet(client->client_id, (const uint8_t *)*data, *len,
2988 (uint8_t *)decrypted_data, original_alloc_size, &decrypted_len);
2989
2990 if (decrypt_result != 0) {
2991 SET_ERRNO(ERROR_CRYPTO, "Failed to process encrypted packet from client %s (result=%d)", client->client_id,
2992 decrypt_result);
2993 buffer_pool_free(NULL, *data, original_alloc_size);
2994 buffer_pool_free(NULL, decrypted_data, original_alloc_size);
2995 *data = NULL;
2996 return -1;
2997 }
2998
2999 // Replace encrypted data with decrypted data
3000 // Use original allocation size for freeing the encrypted buffer
3001 buffer_pool_free(NULL, *data, original_alloc_size);
3002
3003 *data = decrypted_data;
3004 *len = decrypted_len;
3005
3006 // Now process the decrypted packet by parsing its header
3007 if (*len < sizeof(packet_header_t)) {
3008 SET_ERRNO(ERROR_CRYPTO, "Decrypted packet too small for header from client %s", client->client_id);
3009 buffer_pool_free(NULL, *data, *len);
3010 *data = NULL;
3011 return -1;
3012 }
3013
3014 packet_header_t *header = (packet_header_t *)*data;
3015 *type = (packet_type_t)NET_TO_HOST_U16(header->type);
3016 *sender_id = NET_TO_HOST_U32(header->client_id);
3017
3018 // Adjust data pointer to skip header
3019 *data = (uint8_t *)*data + sizeof(packet_header_t);
3020 *len -= sizeof(packet_header_t);
3021
3022 return 0;
3023}
3024
3025/* ============================================================================
3026 * ACIP Server Callback Wrappers
3027 * ============================================================================ */
3028
3029// Forward declarations for ACIP server callbacks
3030static void acip_server_on_protocol_version(const protocol_version_packet_t *version, void *client_ctx, void *app_ctx);
3031static void acip_server_on_image_frame(const image_frame_packet_t *header, const void *pixel_data, size_t data_len,
3032 void *client_ctx, void *app_ctx);
3033static void acip_server_on_image_frame_h265(uint32_t width, uint32_t height, uint8_t flags, const void *h265_data,
3034 size_t data_len, void *client_ctx, void *app_ctx);
3035static void acip_server_on_audio(const void *audio_data, size_t audio_len, void *client_ctx, void *app_ctx);
3036static void acip_server_on_audio_batch(const audio_batch_packet_t *header, const float *samples, size_t num_samples,
3037 void *client_ctx, void *app_ctx);
3038static void acip_server_on_audio_opus(const void *opus_data, size_t opus_len, void *client_ctx, void *app_ctx);
3039static void acip_server_on_audio_opus_batch(const void *batch_data, size_t batch_len, void *client_ctx, void *app_ctx);
3040static void acip_server_on_client_join(const void *join_data, size_t data_len, void *client_ctx, void *app_ctx);
3041static void acip_server_on_client_leave(void *client_ctx, void *app_ctx);
3042static void acip_server_on_stream_start(uint32_t stream_types, void *client_ctx, void *app_ctx);
3043static void acip_server_on_stream_stop(uint32_t stream_types, void *client_ctx, void *app_ctx);
3044static void acip_server_on_capabilities(const void *cap_data, size_t data_len, void *client_ctx, void *app_ctx);
3045static void acip_server_on_ping(void *client_ctx, void *app_ctx);
3046static void acip_server_on_pong(void *client_ctx, void *app_ctx);
3047static void acip_server_on_error(const error_packet_t *header, const char *message, void *client_ctx, void *app_ctx);
3048static void acip_server_on_remote_log(const remote_log_packet_t *header, const char *message, void *client_ctx,
3049 void *app_ctx);
3050static void acip_server_on_crypto_rekey_request(const void *payload, size_t payload_len, void *client_ctx,
3051 void *app_ctx);
3052static void acip_server_on_crypto_rekey_response(const void *payload, size_t payload_len, void *client_ctx,
3053 void *app_ctx);
3054static void acip_server_on_crypto_rekey_complete(const void *payload, size_t payload_len, void *client_ctx,
3055 void *app_ctx);
3056static void acip_server_on_crypto_key_exchange_resp(packet_type_t type, const void *payload, size_t payload_len,
3057 void *client_ctx, void *app_ctx);
3058static void acip_server_on_crypto_auth_response(packet_type_t type, const void *payload, size_t payload_len,
3059 void *client_ctx, void *app_ctx);
3060static void acip_server_on_crypto_no_encryption(packet_type_t type, const void *payload, size_t payload_len,
3061 void *client_ctx, void *app_ctx);
3062
3069static const acip_server_callbacks_t g_acip_server_callbacks = {
3070 .on_protocol_version = acip_server_on_protocol_version,
3071 .on_image_frame = acip_server_on_image_frame,
3072 .on_image_frame_h265 = acip_server_on_image_frame_h265,
3073 .on_audio = acip_server_on_audio,
3074 .on_audio_batch = acip_server_on_audio_batch,
3075 .on_audio_opus = acip_server_on_audio_opus,
3076 .on_audio_opus_batch = acip_server_on_audio_opus_batch,
3077 .on_client_join = acip_server_on_client_join,
3078 .on_client_leave = acip_server_on_client_leave,
3079 .on_stream_start = acip_server_on_stream_start,
3080 .on_stream_stop = acip_server_on_stream_stop,
3081 .on_capabilities = acip_server_on_capabilities,
3082 .on_ping = acip_server_on_ping,
3083 .on_pong = acip_server_on_pong,
3084 .on_error = acip_server_on_error,
3085 .on_remote_log = acip_server_on_remote_log,
3086 .on_crypto_rekey_request = acip_server_on_crypto_rekey_request,
3087 .on_crypto_rekey_response = acip_server_on_crypto_rekey_response,
3088 .on_crypto_rekey_complete = acip_server_on_crypto_rekey_complete,
3089 .on_crypto_key_exchange_resp = acip_server_on_crypto_key_exchange_resp,
3090 .on_crypto_auth_response = acip_server_on_crypto_auth_response,
3091 .on_crypto_no_encryption = acip_server_on_crypto_no_encryption,
3092 .app_ctx = NULL // Not used - client context passed per-call
3093};
3094
3095// Callback implementations (delegate to existing handlers)
3096
3097static void acip_server_on_protocol_version(const protocol_version_packet_t *version, void *client_ctx, void *app_ctx) {
3098 // TODO: Use app_ctx for context-aware protocol handling or metrics collection in future versions
3099 (void)app_ctx;
3100 client_info_t *client = (client_info_t *)client_ctx;
3101 handle_protocol_version_packet(client, (void *)version, sizeof(*version));
3102}
3103
3104static void acip_server_on_image_frame(const image_frame_packet_t *header, const void *pixel_data, size_t data_len,
3105 void *client_ctx, void *app_ctx) {
3106 (void)app_ctx;
3107 client_info_t *client = (client_info_t *)client_ctx;
3108
3109 // Reject empty or missing RGB payloads before diagnostics sample the first
3110 // pixel. Malformed frames must not turn a logging path into a server crash.
3111 if (!header || !client || !pixel_data || data_len < 3) {
3112 log_warn("Ignoring empty or invalid WebRTC image frame payload (data_len=%zu)", data_len);
3113 return;
3114 }
3115
3116 log_dev("CALLBACK_IMAGE_FRAME: client_id=%s width=%u height=%u pixel_format=%u compressed_size=%u data_len=%zu",
3117 client->client_id, header->width, header->height, header->pixel_format, header->compressed_size, data_len);
3118
3119 size_t center_pixel_offset = ((size_t)(header->height / 2) * header->width + header->width / 2) * 3;
3120 const uint8_t *first_pixel = pixel_data;
3121 const uint8_t *center_pixel = center_pixel_offset + 3 <= data_len ? (const uint8_t *)pixel_data + center_pixel_offset
3122 : first_pixel;
3124 "WebRTC pixel sample client=%s size=%ux%u bytes=%zu first=%02x%02x%02x center=%02x%02x%02x",
3125 client->client_id, header->width, header->height, data_len, first_pixel[0], first_pixel[1],
3126 first_pixel[2], center_pixel[0], center_pixel[1], center_pixel[2]);
3127
3128 // Validate frame dimensions to prevent DoS and buffer overflow attacks
3129 if (header->width == 0 || header->height == 0) {
3130 log_error("Invalid image dimensions: %ux%u (width and height must be > 0)", header->width, header->height);
3131 disconnect_client_for_bad_data(client, "IMAGE_FRAME invalid dimensions");
3132 return;
3133 }
3134
3135 const uint32_t MAX_WIDTH = 8192;
3136 const uint32_t MAX_HEIGHT = 8192;
3137 if (header->width > MAX_WIDTH || header->height > MAX_HEIGHT) {
3138 log_error("Image dimensions too large: %ux%u (max: %ux%u)", header->width, header->height, MAX_WIDTH, MAX_HEIGHT);
3139 disconnect_client_for_bad_data(client, "IMAGE_FRAME dimensions too large");
3140 return;
3141 }
3142
3143 // Auto-enable video stream if not already enabled
3144 // Use atomic_exchange to atomically set flag and check old value
3145 bool was_sending_video = atomic_exchange_bool(&client->is_sending_video, true);
3146 if (!was_sending_video) {
3147 log_info("Client %s auto-enabled video stream (received IMAGE_FRAME)", client->client_id);
3148 log_info_client(client, "First video frame received - streaming active");
3149 }
3150
3151 // Measure frame arrival at the server independently of composition and
3152 // delivery so low FPS can be attributed to the publishing client or server.
3153 uint64_t receive_now_ns = time_get_ns();
3154 double receive_fps = 0.0;
3155 uint32_t received_in_window = 0;
3156 uint64_t receive_window_ns = 0;
3158 client->frames_received_logged++;
3159 if (client->video_receive_rate_window_ns == 0) {
3160 client->video_receive_rate_window_ns = receive_now_ns;
3161 }
3163 receive_window_ns = receive_now_ns - client->video_receive_rate_window_ns;
3164 if (receive_window_ns >= NS_PER_SEC_INT) {
3165 received_in_window = client->video_receive_rate_window_frames;
3166 receive_fps = (double)received_in_window * NS_PER_SEC_INT / (double)receive_window_ns;
3167 client->video_receive_rate_window_ns = receive_now_ns;
3169 }
3170 bool log_frame_count = client->frames_received_logged % 25000 == 0;
3171 uint32_t total_received = client->frames_received_logged;
3173 if (log_frame_count) {
3174 char pretty[64];
3175 format_bytes_pretty(data_len, pretty, sizeof(pretty));
3176 log_debug("Client %s has sent %u IMAGE_FRAME packets (%s)", client->client_id, total_received, pretty);
3177 }
3178 if (received_in_window > 0) {
3179 log_info("Server image ingress client=%s fps=%.1f frames=%u elapsed=%.2fs", client->client_id, receive_fps,
3180 received_in_window, (double)receive_window_ns / (double)NS_PER_SEC_INT);
3181 }
3182
3183 // Sample a small prefix for duplicate-frame diagnostics; hashing every pixel
3184 // here repeats work performed by the image conversion path on every frame.
3185 uint32_t incoming_pixel_hash = 2166136261u;
3186 size_t hash_len = data_len < 1000 ? data_len : 1000;
3187 for (size_t i = 0; i < hash_len; i++) {
3188 incoming_pixel_hash = (incoming_pixel_hash ^ ((const uint8_t *)pixel_data)[i]) * 16777619u;
3189 }
3190
3191 // Per-client hash tracking to detect duplicate frames
3192 bool is_new_frame = (incoming_pixel_hash != client->last_received_frame_hash);
3193
3194 // Inspect first few pixels of incoming frame
3195 uint32_t first_pixel_rgb = 0;
3196 if (data_len >= 3) {
3197 first_pixel_rgb = ((uint32_t)((unsigned char *)pixel_data)[0] << 16) |
3198 ((uint32_t)((unsigned char *)pixel_data)[1] << 8) | (uint32_t)((unsigned char *)pixel_data)[2];
3199 }
3200 if (is_new_frame) {
3201 log_dev("RECV_FRAME #%u NEW: client=%s dimensions=%ux%u pixel_size=%zu hash=0x%08x first_rgb=0x%06x prev=0x%08x",
3202 client->frames_received, client->client_id, header->width, header->height, data_len, incoming_pixel_hash,
3203 first_pixel_rgb, client->last_received_frame_hash);
3204 client->last_received_frame_hash = incoming_pixel_hash;
3205 } else {
3206 log_dev("RECV_FRAME #%u DUP: client=%s dimensions=%ux%u pixel_size=%zu hash=0x%08x first_rgb=0x%06x",
3207 client->frames_received, client->client_id, header->width, header->height, data_len, incoming_pixel_hash,
3208 first_pixel_rgb);
3209 }
3210 if (client->frames_received_logged > 0 && client->frames_received_logged % 60 == 0) {
3211 log_info("Incoming video signature client=%s frames=%u state=%s hash=0x%08x size=%ux%u bytes=%zu",
3212 client->client_id, client->frames_received_logged, is_new_frame ? "changing" : "duplicate",
3213 incoming_pixel_hash, header->width, header->height, data_len);
3214 }
3215
3216 // Store frame data to incoming_video_buffer
3217 // pixel_data points to websocket reassembly buffer which will be freed after callback returns,
3218 // so we must copy it immediately into the persistent frame buffer (which is pre-allocated to 2MB)
3219 if (client->incoming_video_buffer) {
3221 log_dev("VFB: client=%s allocated_size=%zu active=%d", client->client_id, vfb->allocated_buffer_size, vfb->active);
3222
3224 if (frame && frame->data && data_len > 0) {
3225 // Debug: Check frame buffer capacity
3226 size_t buffer_capacity = client->incoming_video_buffer->allocated_buffer_size;
3227 size_t total_size = sizeof(uint32_t) * 2 + data_len;
3228
3229 log_dev("FRAME_STORAGE: client=%s capacity=%zu data_len=%zu total=%zu", client->client_id, buffer_capacity,
3230 data_len, total_size);
3231
3232 // CRITICAL: Bounds check BEFORE memcpy to prevent heap-buffer-overflow
3233 // If buffer is too small, skip frame storage instead of overflowing
3234 // NOTE: Use >= not > because we need one free byte after the last byte
3235 if (total_size >= buffer_capacity) {
3236 log_error("FRAME_STORAGE_OVERFLOW_PREVENTED: client=%s, need=%zu bytes, capacity=%zu bytes (cannot store "
3237 "frame, would overflow at byte %zu)",
3238 client->client_id, total_size, buffer_capacity, buffer_capacity);
3239 disconnect_client_for_bad_data(client, "FRAME_STORAGE_BUFFER_TOO_SMALL");
3240 return;
3241 }
3242
3243 // Copy frame data: width (4B) + height (4B) + pixel data
3244 uint32_t width_net = HOST_TO_NET_U32(header->width);
3245 uint32_t height_net = HOST_TO_NET_U32(header->height);
3246
3247 memcpy(frame->data, &width_net, sizeof(uint32_t));
3248 memcpy((char *)frame->data + sizeof(uint32_t), &height_net, sizeof(uint32_t));
3249 memcpy((char *)frame->data + sizeof(uint32_t) * 2, pixel_data, data_len);
3250
3251 frame->size = total_size;
3252 frame->width = header->width;
3253 frame->height = header->height;
3254 frame->capture_timestamp_ns = time_get_ns();
3255 frame->sequence_number = ++client->frames_received;
3256
3258 log_debug("Frame committed: client %s, seq=%u, size=%zu", client->client_id, frame->sequence_number, total_size);
3259 } else {
3260 log_warn("FRAME_STORE_SKIP: client=%s, frame=%p, frame->data=%p, data_len=%zu", client->client_id, (void *)frame,
3261 frame ? (void *)frame->data : NULL, data_len);
3262 }
3263 } else {
3264 log_warn("NO_INCOMING_VIDEO_BUFFER: client=%s", client->client_id);
3265 }
3266
3267}
3268
3274static int h265_decoder_ensure_initialized(client_info_t *client) {
3275 if (client->h265_decoder_ctx) {
3276 return 0; // Already initialized
3277 }
3278
3279 const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_HEVC);
3280 if (!codec) {
3281 log_error("H.265 decoder codec not found");
3282 return -1;
3283 }
3284
3285 AVCodecContext *dec_ctx = avcodec_alloc_context3(codec);
3286 if (!dec_ctx) {
3287 log_error("Failed to allocate H.265 decoder context");
3288 return -1;
3289 }
3290
3291 if (avcodec_open2(dec_ctx, codec, NULL) < 0) {
3292 log_error("Failed to open H.265 decoder");
3293 avcodec_free_context(&dec_ctx);
3294 return -1;
3295 }
3296
3297 AVFrame *frame = av_frame_alloc();
3298 AVFrame *rgb_frame = av_frame_alloc();
3299
3300 if (!frame || !rgb_frame) {
3301 log_error("Failed to allocate H.265 frames");
3302 if (frame)
3303 av_frame_free(&frame);
3304 if (rgb_frame)
3305 av_frame_free(&rgb_frame);
3306 avcodec_free_context(&dec_ctx);
3307 return -1;
3308 }
3309
3310 client->h265_decoder_ctx = dec_ctx;
3311 client->h265_decode_frame = frame;
3312 client->h265_rgb_frame = rgb_frame;
3313
3314 log_info("H.265 decoder initialized for client %s", client->client_id);
3315 return 0;
3316}
3317
3318static void acip_server_on_image_frame_h265(uint32_t width, uint32_t height, uint8_t flags, const void *h265_data,
3319 size_t data_len, void *client_ctx, void *app_ctx) {
3320 (void)app_ctx;
3321 (void)flags; // H.265 flags not used yet
3322 uint64_t callback_start_ns = time_get_ns();
3323 client_info_t *client = (client_info_t *)client_ctx;
3324
3325 log_dev("CALLBACK_IMAGE_FRAME_H265: client_id=%s, width=%u, height=%u, h265_data_len=%zu", client->client_id, width,
3326 height, data_len);
3327
3328 // Validate frame dimensions to prevent DoS and buffer overflow attacks
3329 if (width == 0 || height == 0) {
3330 log_error("Invalid H.265 frame dimensions: %ux%u (width and height must be > 0)", width, height);
3331 disconnect_client_for_bad_data(client, "IMAGE_FRAME_H265 invalid dimensions");
3332 return;
3333 }
3334
3335 const uint32_t MAX_WIDTH = 8192;
3336 const uint32_t MAX_HEIGHT = 8192;
3337 if (width > MAX_WIDTH || height > MAX_HEIGHT) {
3338 log_error("H.265 frame dimensions too large: %ux%u (max: %ux%u)", width, height, MAX_WIDTH, MAX_HEIGHT);
3339 disconnect_client_for_bad_data(client, "IMAGE_FRAME_H265 dimensions too large");
3340 return;
3341 }
3342
3343 // Auto-enable video stream if not already enabled
3344 // Use atomic_exchange to atomically set flag and check old value
3345 bool was_sending_video = atomic_exchange_bool(&client->is_sending_video, true);
3346 if (!was_sending_video) {
3347 log_info("Client %s auto-enabled video stream (received IMAGE_FRAME_H265)", client->client_id);
3348 log_info_client(client, "First H.265 video frame received - streaming active");
3349 } else {
3350 // Log periodically
3352 client->frames_received_logged++;
3353 if (client->frames_received_logged % 25000 == 0) {
3354 char pretty[64];
3355 format_bytes_pretty(data_len, pretty, sizeof(pretty));
3356 log_debug("Client %s has sent %u IMAGE_FRAME_H265 packets (%s)", client->client_id,
3357 client->frames_received_logged, pretty);
3358 }
3360 }
3361
3362 // H.265 frames need to be decoded before being added to the video mixer
3363 if (data_len == 0) {
3364 log_debug("SKIP_H265_EMPTY: client=%s, width=%ux%u, no encoded data", client->client_id, width, height);
3365 uint64_t callback_end_ns = time_get_ns();
3366 char cb_duration_str[32];
3367 time_pretty((uint64_t)((double)(callback_end_ns - callback_start_ns)), -1, cb_duration_str,
3368 sizeof(cb_duration_str));
3369 log_info("[WS_TIMING] on_image_frame_h265 callback took %s (data_len=%zu)", cb_duration_str, data_len);
3370 return;
3371 }
3372
3373 // H.265 frame with actual data - decode it
3374 log_info("H265_FRAME_WITH_DATA: client=%s, %ux%u, %zu bytes - DECODING", client->client_id, width, height, data_len);
3375
3376 // Initialize persistent decoder if not already done
3377 if (h265_decoder_ensure_initialized(client) < 0) {
3378 uint64_t callback_end_ns = time_get_ns();
3379 char cb_duration_str[32];
3380 time_pretty((uint64_t)((double)(callback_end_ns - callback_start_ns)), -1, cb_duration_str,
3381 sizeof(cb_duration_str));
3382 log_info("[WS_TIMING] on_image_frame_h265 callback took %s (decoder init failed)", cb_duration_str);
3383 return;
3384 }
3385
3386 AVCodecContext *dec_ctx = (AVCodecContext *)client->h265_decoder_ctx;
3387 AVFrame *frame = (AVFrame *)client->h265_decode_frame;
3388 AVFrame *rgb_frame = (AVFrame *)client->h265_rgb_frame;
3389
3390 // Allocate packet for this frame
3391 AVPacket *pkt = av_packet_alloc();
3392 if (!pkt) {
3393 log_error("Failed to allocate packet");
3394 uint64_t callback_end_ns = time_get_ns();
3395 char cb_duration_str[32];
3396 time_pretty((uint64_t)((double)(callback_end_ns - callback_start_ns)), -1, cb_duration_str,
3397 sizeof(cb_duration_str));
3398 log_info("[WS_TIMING] on_image_frame_h265 callback took %s (pkt alloc failed)", cb_duration_str);
3399 return;
3400 }
3401
3402 // Copy H.265 data into packet (network buffer will be reused after callback returns)
3403 uint8_t *pkt_data = SAFE_MALLOC(data_len, uint8_t *);
3404 if (!pkt_data) {
3405 log_error("Failed to allocate packet data buffer");
3406 av_packet_free(&pkt);
3407 av_frame_free(&frame);
3408 av_frame_free(&rgb_frame);
3409 avcodec_free_context(&dec_ctx);
3410 uint64_t callback_end_ns = time_get_ns();
3411 char cb_duration_str[32];
3412 time_pretty((uint64_t)((double)(callback_end_ns - callback_start_ns)), -1, cb_duration_str,
3413 sizeof(cb_duration_str));
3414 log_info("[WS_TIMING] on_image_frame_h265 callback took %s (packet alloc failed)", cb_duration_str);
3415 return;
3416 }
3417
3418 memcpy(pkt_data, h265_data, data_len);
3419 pkt->data = pkt_data;
3420 pkt->size = (int)data_len;
3421
3422 int send_ret = avcodec_send_packet(dec_ctx, pkt);
3423 if (send_ret < 0) {
3424 char err_buf[128];
3425 av_strerror(send_ret, err_buf, sizeof(err_buf));
3426 log_error("H.265 avcodec_send_packet failed: %s (ret=%d)", err_buf, send_ret);
3427 SAFE_FREE(pkt_data);
3428 av_packet_free(&pkt);
3429 // DON'T free frame, rgb_frame, dec_ctx - they're persistent per-client
3430 uint64_t callback_end_ns = time_get_ns();
3431 char cb_duration_str[32];
3432 time_pretty((uint64_t)((double)(callback_end_ns - callback_start_ns)), -1, cb_duration_str,
3433 sizeof(cb_duration_str));
3434 log_info("[WS_TIMING] on_image_frame_h265 callback took %s (send_packet error)", cb_duration_str);
3435 return;
3436 }
3437
3438 int recv_ret = avcodec_receive_frame(dec_ctx, frame);
3439 if (recv_ret == AVERROR(EAGAIN)) {
3440 // EAGAIN is normal - decoder is buffering frames internally
3441 log_dev("H.265 decoder buffering frame (EAGAIN), will output on next frame");
3442 SAFE_FREE(pkt_data);
3443 av_packet_free(&pkt);
3444 // DON'T free frame, rgb_frame, dec_ctx - they're persistent per-client
3445 uint64_t callback_end_ns = time_get_ns();
3446 char cb_duration_str[32];
3447 time_pretty((uint64_t)((double)(callback_end_ns - callback_start_ns)), -1, cb_duration_str,
3448 sizeof(cb_duration_str));
3449 log_info("[WS_TIMING] on_image_frame_h265 callback took %s (decoder buffering)", cb_duration_str);
3450 return;
3451 }
3452 if (recv_ret < 0) {
3453 char err_buf[128];
3454 av_strerror(recv_ret, err_buf, sizeof(err_buf));
3455 log_error("H.265 avcodec_receive_frame failed: %s (ret=%d)", err_buf, recv_ret);
3456 SAFE_FREE(pkt_data);
3457 av_packet_free(&pkt);
3458 // DON'T free frame, rgb_frame, dec_ctx - they're persistent per-client
3459 uint64_t callback_end_ns = time_get_ns();
3460 char cb_duration_str[32];
3461 time_pretty((uint64_t)((double)(callback_end_ns - callback_start_ns)), -1, cb_duration_str,
3462 sizeof(cb_duration_str));
3463 log_info("[WS_TIMING] on_image_frame_h265 callback took %s (receive_frame error)", cb_duration_str);
3464 return;
3465 }
3466
3467 // Convert YUV to RGB
3468 // Reuse or create persistent color converter if needed
3469 struct SwsContext *sws_ctx = (struct SwsContext *)client->h265_sws_ctx;
3470 if (!sws_ctx || sws_ctx == NULL) {
3471 LOG_IO("swscaler", {
3472 sws_ctx = sws_getContext(frame->width, frame->height, (int)frame->format, frame->width, frame->height,
3473 AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
3474 });
3475 if (!sws_ctx) {
3476 log_error("Failed to create color converter");
3477 SAFE_FREE(pkt_data);
3478 av_packet_free(&pkt);
3479 // DON'T free frame, rgb_frame, dec_ctx - they're persistent per-client
3480 uint64_t callback_end_ns = time_get_ns();
3481 char cb_duration_str[32];
3482 time_pretty((uint64_t)((double)(callback_end_ns - callback_start_ns)), -1, cb_duration_str,
3483 sizeof(cb_duration_str));
3484 log_info("[WS_TIMING] on_image_frame_h265 callback took %s (sws failed)", cb_duration_str);
3485 return;
3486 }
3487 client->h265_sws_ctx = sws_ctx; // Cache it for reuse
3488 }
3489
3490 int rgb_buf_size = av_image_get_buffer_size(AV_PIX_FMT_RGB24, frame->width, frame->height, 1);
3491 uint8_t *rgb_buffer = SAFE_MALLOC(rgb_buf_size, uint8_t *);
3492
3493 if (!rgb_buffer) {
3494 log_error("Failed to allocate RGB buffer");
3495 SAFE_FREE(pkt_data);
3496 av_packet_free(&pkt);
3497 // DON'T free sws_ctx, frame, rgb_frame, dec_ctx - they're persistent per-client
3498 uint64_t callback_end_ns = time_get_ns();
3499 char cb_duration_str[32];
3500 time_pretty((uint64_t)((double)(callback_end_ns - callback_start_ns)), -1, cb_duration_str,
3501 sizeof(cb_duration_str));
3502 log_info("[WS_TIMING] on_image_frame_h265 callback took %s (rgb alloc failed)", cb_duration_str);
3503 return;
3504 }
3505
3506 av_image_fill_arrays(rgb_frame->data, rgb_frame->linesize, rgb_buffer, AV_PIX_FMT_RGB24, frame->width, frame->height,
3507 1);
3508
3509 if (sws_scale(sws_ctx, (const uint8_t *const *)frame->data, frame->linesize, 0, frame->height, rgb_frame->data,
3510 rgb_frame->linesize) < 0) {
3511 log_error("Color conversion failed");
3512 SAFE_FREE(rgb_buffer);
3513 SAFE_FREE(pkt_data);
3514 av_packet_free(&pkt);
3515 // DON'T free sws_ctx, frame, rgb_frame, dec_ctx - they're persistent per-client
3516 uint64_t callback_end_ns = time_get_ns();
3517 char cb_duration_str[32];
3518 time_pretty((uint64_t)((double)(callback_end_ns - callback_start_ns)), -1, cb_duration_str,
3519 sizeof(cb_duration_str));
3520 log_info("[WS_TIMING] on_image_frame_h265 callback took %s (scale failed)", cb_duration_str);
3521 return;
3522 }
3523
3524 log_debug("H265_DECODE: Successfully converted to RGB (%d bytes)", rgb_buf_size);
3525
3526 // Add decoded frame to mixer
3527 if (client->incoming_video_buffer) {
3529 if (vf && vf->data) {
3530 size_t total_size = sizeof(uint32_t) * 2 + rgb_buf_size;
3531 size_t buffer_capacity = client->incoming_video_buffer->allocated_buffer_size;
3532
3533 if (total_size <= buffer_capacity) {
3534 uint32_t width_net = HOST_TO_NET_U32(width);
3535 uint32_t height_net = HOST_TO_NET_U32(height);
3536
3537 memcpy(vf->data, &width_net, sizeof(uint32_t));
3538 memcpy((char *)vf->data + sizeof(uint32_t), &height_net, sizeof(uint32_t));
3539 memcpy((char *)vf->data + sizeof(uint32_t) * 2, rgb_buffer, rgb_buf_size);
3540
3541 vf->size = total_size;
3542 vf->width = width;
3543 vf->height = height;
3545 vf->sequence_number = ++client->frames_received;
3546
3548 log_info("H265_DECODE: Frame committed: seq=%u, %ux%u", vf->sequence_number, width, height);
3549 } else {
3550 log_error("H265_DECODE: Frame too large - %zu > %zu bytes, skipping", total_size, buffer_capacity);
3551 }
3552 }
3553 }
3554
3555 // Clean up temporary buffers
3556 // DON'T free sws_ctx, frame, rgb_frame, dec_ctx - they're persistent per-client
3557 SAFE_FREE(rgb_buffer);
3558 SAFE_FREE(pkt_data);
3559 av_packet_free(&pkt);
3560
3561 uint64_t callback_end_ns = time_get_ns();
3562 char cb_duration_str[32];
3563 time_pretty((uint64_t)((double)(callback_end_ns - callback_start_ns)), -1, cb_duration_str, sizeof(cb_duration_str));
3564 log_info("[WS_TIMING] on_image_frame_h265 callback took %s (SUCCESS)", cb_duration_str);
3565}
3566
3567static void acip_server_on_audio(const void *audio_data, size_t audio_len, void *client_ctx, void *app_ctx) {
3568 (void)app_ctx;
3569 client_info_t *client = (client_info_t *)client_ctx;
3570 handle_audio_packet(client, (void *)audio_data, audio_len);
3571}
3572
3573static void acip_server_on_audio_batch(const audio_batch_packet_t *header, const float *samples, size_t num_samples,
3574 void *client_ctx, void *app_ctx) {
3575 (void)app_ctx;
3576 (void)header; // Header info not needed - ACIP already validated
3577 client_info_t *client = (client_info_t *)client_ctx;
3578
3579 // ACIP handler already dequantized samples - write directly to audio buffer
3580 // This is more efficient than calling the existing handler which would re-dequantize
3581 log_debug_every(LOG_RATE_DEFAULT, "Received audio batch from client %s (samples=%zu, is_sending_audio=%d)",
3582 client->client_id, num_samples, atomic_load_bool(&client->is_sending_audio));
3583
3584 if (!atomic_load_bool(&client->is_sending_audio)) {
3585 log_debug("Ignoring audio batch - client %s not in audio streaming mode", client->client_id);
3586 return;
3587 }
3588
3589 if (client->incoming_audio_buffer) {
3590 asciichat_error_t write_result =
3591 audio_ring_buffer_write(client->incoming_audio_buffer, (float *)samples, num_samples);
3592 if (write_result != ASCIICHAT_OK) {
3593 log_error("Failed to write decoded audio batch to buffer: %s", asciichat_error_string(write_result));
3594 }
3595 }
3596}
3597
3598static void acip_server_on_audio_opus(const void *opus_data, size_t opus_len, void *client_ctx, void *app_ctx) {
3599 (void)app_ctx;
3600 client_info_t *client = (client_info_t *)client_ctx;
3601
3602 // Special handling: Convert single-frame Opus to batch format
3603 // This maintains compatibility with existing server-side Opus batch processing
3604
3605 if (opus_len < 16) {
3606 log_warn("AUDIO_OPUS packet too small: %zu bytes", opus_len);
3607 return;
3608 }
3609
3610 const uint8_t *payload = (const uint8_t *)opus_data;
3611 // Use unaligned read helpers - network data may not be aligned
3612 int sample_rate = (int)NET_TO_HOST_U32(read_u32_unaligned(payload));
3613 int frame_duration = (int)NET_TO_HOST_U32(read_u32_unaligned(payload + 4));
3614 // Reserved bytes at offset 8-15
3615 size_t actual_opus_size = opus_len - 16;
3616
3617 if (actual_opus_size > 0 && actual_opus_size <= 1024 && sample_rate == 48000 && frame_duration == 20) {
3618 // Create a synthetic Opus batch packet (frame_count=1)
3619 uint8_t batch_buffer[1024 + 20]; // Max Opus + header
3620 uint8_t *batch_ptr = batch_buffer;
3621
3622 // Write batch header (batch_buffer is stack-aligned, writes are safe)
3623 write_u32_unaligned(batch_ptr, HOST_TO_NET_U32((uint32_t)sample_rate));
3624 batch_ptr += 4;
3625 write_u32_unaligned(batch_ptr, HOST_TO_NET_U32((uint32_t)frame_duration));
3626 batch_ptr += 4;
3627 write_u32_unaligned(batch_ptr, HOST_TO_NET_U32(1)); // frame_count = 1
3628 batch_ptr += 4;
3629 memset(batch_ptr, 0, 4); // reserved
3630 batch_ptr += 4;
3631
3632 // Write frame size
3633 write_u16_unaligned(batch_ptr, HOST_TO_NET_U16((uint16_t)actual_opus_size));
3634 batch_ptr += 2;
3635
3636 // Write Opus data
3637 memcpy(batch_ptr, payload + 16, actual_opus_size);
3638 batch_ptr += actual_opus_size;
3639
3640 // Process as batch packet
3641 size_t batch_size = (size_t)(batch_ptr - batch_buffer);
3642 handle_audio_opus_batch_packet(client, batch_buffer, batch_size);
3643 }
3644}
3645
3646static void acip_server_on_audio_opus_batch(const void *batch_data, size_t batch_len, void *client_ctx, void *app_ctx) {
3647 (void)app_ctx;
3648 client_info_t *client = (client_info_t *)client_ctx;
3649 handle_audio_opus_batch_packet(client, (void *)batch_data, batch_len);
3650}
3651
3652static void acip_server_on_client_join(const void *join_data, size_t data_len, void *client_ctx, void *app_ctx) {
3653 (void)app_ctx;
3654 client_info_t *client = (client_info_t *)client_ctx;
3655 handle_client_join_packet(client, (void *)join_data, data_len);
3656}
3657
3658static void acip_server_on_client_leave(void *client_ctx, void *app_ctx) {
3659 (void)app_ctx;
3660 client_info_t *client = (client_info_t *)client_ctx;
3661 handle_client_leave_packet(client, NULL, 0);
3662}
3663
3664static void acip_server_on_stream_start(uint32_t stream_types, void *client_ctx, void *app_ctx) {
3665 (void)app_ctx;
3666 client_info_t *client = (client_info_t *)client_ctx;
3667 // The ACIP parser has already converted these flags to host byte order.
3668 // The shared packet handler consumes the network-order payload used by the
3669 // TCP packet dispatcher, so restore that representation at this boundary.
3670 uint32_t stream_types_net = HOST_TO_NET_U32(stream_types);
3671 handle_stream_start_packet(client, &stream_types_net, sizeof(stream_types_net));
3672}
3673
3674static void acip_server_on_stream_stop(uint32_t stream_types, void *client_ctx, void *app_ctx) {
3675 (void)app_ctx;
3676 client_info_t *client = (client_info_t *)client_ctx;
3677 uint32_t stream_types_net = HOST_TO_NET_U32(stream_types);
3678 handle_stream_stop_packet(client, &stream_types_net, sizeof(stream_types_net));
3679}
3680
3681static void acip_server_on_capabilities(const void *cap_data, size_t data_len, void *client_ctx, void *app_ctx) {
3682 (void)app_ctx;
3683 client_info_t *client = (client_info_t *)client_ctx;
3684 handle_client_capabilities_packet(client, (void *)cap_data, data_len);
3685}
3686
3687static void acip_server_on_ping(void *client_ctx, void *app_ctx) {
3688 (void)app_ctx;
3689 client_info_t *client = (client_info_t *)client_ctx;
3690
3691 // Respond with PONG using ACIP transport
3692 // Keep the lock through the packet write: this handler runs on the receive
3693 // thread and otherwise races the video/audio sender on TCP.
3694 mutex_lock(&client->send_mutex);
3695 if (atomic_load_bool(&client->shutting_down) || !client->transport) {
3696 mutex_unlock(&client->send_mutex);
3697 return; // Client is shutting down, skip pong
3698 }
3699 acip_transport_t *pong_transport = client->transport;
3700 asciichat_error_t pong_result = acip_send_pong(pong_transport);
3701 mutex_unlock(&client->send_mutex);
3702
3703 if (pong_result != ASCIICHAT_OK) {
3704 SET_ERRNO(ERROR_NETWORK, "Failed to send PONG response to client %s: %s", client->client_id,
3705 asciichat_error_string(pong_result));
3706 }
3707}
3708
3709static void acip_server_on_pong(void *client_ctx, void *app_ctx) {
3710 (void)client_ctx;
3711 (void)app_ctx;
3712 // Client acknowledged our PING - no action needed
3713}
3714
3715static void acip_server_on_error(const error_packet_t *header, const char *message, void *client_ctx, void *app_ctx) {
3716 (void)app_ctx;
3717 client_info_t *client = (client_info_t *)client_ctx;
3718
3719 // Reconstruct full packet for existing handler
3720 size_t msg_len = strlen(message);
3721 size_t total_len = sizeof(*header) + msg_len;
3722 uint8_t *full_packet = SAFE_MALLOC(total_len, uint8_t *);
3723 if (!full_packet) {
3724 log_error("Failed to allocate buffer for ERROR_MESSAGE reconstruction");
3725 return;
3726 }
3727
3728 memcpy(full_packet, header, sizeof(*header));
3729 // NOLINTNEXTLINE(bugprone-not-null-terminated-result) - binary packet data, not string
3730 memcpy(full_packet + sizeof(*header), message, msg_len);
3731
3732 handle_client_error_packet(client, full_packet, total_len);
3733 SAFE_FREE(full_packet);
3734}
3735
3736static void acip_server_on_remote_log(const remote_log_packet_t *header, const char *message, void *client_ctx,
3737 void *app_ctx) {
3738 (void)app_ctx;
3739 client_info_t *client = (client_info_t *)client_ctx;
3740
3741 // Reconstruct full packet for existing handler
3742 size_t msg_len = strlen(message);
3743 size_t total_len = sizeof(*header) + msg_len;
3744 uint8_t *full_packet = SAFE_MALLOC(total_len, uint8_t *);
3745 if (!full_packet) {
3746 log_error("Failed to allocate buffer for REMOTE_LOG reconstruction");
3747 return;
3748 }
3749
3750 memcpy(full_packet, header, sizeof(*header));
3751 // NOLINTNEXTLINE(bugprone-not-null-terminated-result) - binary packet data, not string
3752 memcpy(full_packet + sizeof(*header), message, msg_len);
3753
3754 handle_remote_log_packet_from_client(client, full_packet, total_len);
3755 SAFE_FREE(full_packet);
3756}
3757
3758static void acip_server_on_crypto_rekey_request(const void *payload, size_t payload_len, void *client_ctx,
3759 void *app_ctx) {
3760 (void)app_ctx;
3761 client_info_t *client = (client_info_t *)client_ctx;
3762
3763 log_debug("Received REKEY_REQUEST from client %s", client->client_id);
3764
3765 // Process the client's rekey request
3767 asciichat_error_t crypto_result =
3768 crypto_handshake_process_rekey_request(&client->crypto_handshake_ctx, (void *)payload, payload_len);
3770
3771 if (crypto_result != ASCIICHAT_OK) {
3772 log_error("Failed to process REKEY_REQUEST from client %s: %d", client->client_id, crypto_result);
3773 return;
3774 }
3775
3776 // Send REKEY_RESPONSE
3778 // Get transport reference briefly to avoid deadlock on transport buffers
3779 mutex_lock(&client->send_mutex);
3780 if (atomic_load_bool(&client->shutting_down) || !client->transport) {
3781 mutex_unlock(&client->send_mutex);
3783 return; // Client is shutting down
3784 }
3785 acip_transport_t *rekey_transport = client->transport;
3786 mutex_unlock(&client->send_mutex);
3787
3788 // Network I/O happens OUTSIDE the send_mutex (client_state_mutex still held for crypto state)
3789 crypto_result = crypto_handshake_rekey_response(&client->crypto_handshake_ctx, rekey_transport);
3791
3792 if (crypto_result != ASCIICHAT_OK) {
3793 log_error("Failed to send REKEY_RESPONSE to client %s: %d", client->client_id, crypto_result);
3794 } else {
3795 log_debug("Sent REKEY_RESPONSE to client %s", client->client_id);
3796 }
3797}
3798
3799static void acip_server_on_crypto_rekey_response(const void *payload, size_t payload_len, void *client_ctx,
3800 void *app_ctx) {
3801 (void)app_ctx;
3802 client_info_t *client = (client_info_t *)client_ctx;
3803
3804 log_debug("Received REKEY_RESPONSE from client %s", client->client_id);
3805
3806 // Process the client's rekey response
3808 asciichat_error_t crypto_result =
3809 crypto_handshake_process_rekey_response(&client->crypto_handshake_ctx, (void *)payload, payload_len);
3811
3812 if (crypto_result != ASCIICHAT_OK) {
3813 log_error("Failed to process REKEY_RESPONSE from client %s: %d", client->client_id, crypto_result);
3814 return;
3815 }
3816
3817 // Send REKEY_COMPLETE to confirm and activate new key
3819 // Get transport reference briefly to avoid deadlock on transport buffers
3820 mutex_lock(&client->send_mutex);
3821 if (atomic_load_bool(&client->shutting_down) || !client->transport) {
3822 mutex_unlock(&client->send_mutex);
3824 return; // Client is shutting down
3825 }
3826 acip_transport_t *complete_transport = client->transport;
3827 mutex_unlock(&client->send_mutex);
3828
3829 // Network I/O happens OUTSIDE the send_mutex (client_state_mutex still held for crypto state)
3830 crypto_result = crypto_handshake_rekey_complete(&client->crypto_handshake_ctx, complete_transport);
3832
3833 if (crypto_result != ASCIICHAT_OK) {
3834 log_error("Failed to send REKEY_COMPLETE to client %s: %d", client->client_id, crypto_result);
3835 } else {
3836 log_debug("Sent REKEY_COMPLETE to client %s - session rekeying complete", client->client_id);
3837 }
3838}
3839
3840static void acip_server_on_crypto_rekey_complete(const void *payload, size_t payload_len, void *client_ctx,
3841 void *app_ctx) {
3842 (void)app_ctx;
3843 client_info_t *client = (client_info_t *)client_ctx;
3844
3845 log_debug("Received REKEY_COMPLETE from client %s", client->client_id);
3846
3847 // Process and commit to new key
3849 asciichat_error_t crypto_result =
3850 crypto_handshake_process_rekey_complete(&client->crypto_handshake_ctx, (void *)payload, payload_len);
3852
3853 if (crypto_result != ASCIICHAT_OK) {
3854 log_error("Failed to process REKEY_COMPLETE from client %s: %d", client->client_id, crypto_result);
3855 } else {
3856 log_debug("Session rekeying completed successfully with client %s", client->client_id);
3857 // Notify client that rekeying is complete (new keys now active on both sides)
3858 log_info_client(client, "Session rekey complete - new encryption keys active");
3859 }
3860}
3861
3862static void acip_server_on_crypto_key_exchange_resp(packet_type_t type, const void *payload, size_t payload_len,
3863 void *client_ctx, void *app_ctx) {
3864 (void)app_ctx;
3865 client_info_t *client = (client_info_t *)client_ctx;
3866
3867 log_debug("Received CRYPTO_KEY_EXCHANGE_RESP from client %s", client->client_id);
3868
3869 // Call refactored handshake function from Phase 2
3871 type, payload, payload_len);
3872
3873 if (result != ASCIICHAT_OK) {
3874 log_error("Crypto handshake auth challenge failed for client %s", client->client_id);
3875 disconnect_client_for_bad_data(client, "Crypto handshake auth challenge failed");
3876 } else {
3877 // Check if handshake completed (no-auth flow) or if AUTH_CHALLENGE was sent (with-auth flow)
3879 // No-auth flow: handshake complete, HANDSHAKE_COMPLETE was sent
3880 log_info("Crypto handshake completed successfully for client %s (no authentication)", client->client_id);
3881 client->crypto_initialized = true;
3883 } else {
3884 // With-auth flow: AUTH_CHALLENGE was sent, waiting for AUTH_RESPONSE
3885 log_debug("Sent AUTH_CHALLENGE to client %s", client->client_id);
3886 }
3887 }
3888}
3889
3890static void acip_server_on_crypto_auth_response(packet_type_t type, const void *payload, size_t payload_len,
3891 void *client_ctx, void *app_ctx) {
3892 (void)app_ctx;
3893 client_info_t *client = (client_info_t *)client_ctx;
3894
3895 log_debug("Received CRYPTO_AUTH_RESPONSE from client %s", client->client_id);
3896
3897 // Call refactored handshake function from Phase 2
3898 asciichat_error_t result =
3899 crypto_handshake_server_complete(&client->crypto_handshake_ctx, client->transport, type, payload, payload_len);
3900
3901 if (result != ASCIICHAT_OK) {
3902 log_error("Crypto handshake complete failed for client %s", client->client_id);
3903 disconnect_client_for_bad_data(client, "Crypto handshake complete failed");
3904 } else {
3905 log_info("Crypto handshake completed successfully for client %s", client->client_id);
3906 log_error("[CRYPTO_SETUP] Setting crypto context for client %s: transport=%p, crypto_ctx=%p", client->client_id,
3907 (void *)client->transport, (void *)&client->crypto_handshake_ctx.crypto_ctx);
3908 client->crypto_initialized = true;
3910 log_error("[CRYPTO_SETUP] Crypto context SET: transport->crypto_ctx=%p", (void *)client->transport->crypto_ctx);
3911 }
3912}
3913
3914static void acip_server_on_crypto_no_encryption(packet_type_t type, const void *payload, size_t payload_len,
3915 void *client_ctx, void *app_ctx) {
3916 (void)app_ctx;
3917 (void)type;
3918 (void)payload;
3919 (void)payload_len;
3920 client_info_t *client = (client_info_t *)client_ctx;
3921
3922 log_error("Client %s sent NO_ENCRYPTION - encryption mode mismatch", client->client_id);
3923 disconnect_client_for_bad_data(client, "Encryption mode mismatch - server requires encryption");
3924}
3925
3934void process_decrypted_packet(client_info_t *client, packet_type_t type, void *data, size_t len) {
3935 if (type == 5000) { // CLIENT_CAPABILITIES
3936 log_debug("CLIENT: client_id=%s, data=%p, len=%zu", client->client_id, data, len);
3937 }
3938
3939 // Rate limiting: Check and record packet-specific rate limits
3940 if (g_rate_limiter) {
3941 if (!check_and_record_packet_rate_limit(g_rate_limiter, client->client_ip, client->socket, type)) {
3942 // Rate limit exceeded - error response already sent by utility function
3943 return;
3944 }
3945 }
3946
3947 // O(1) dispatch via hash table lookup
3948 int idx = client_dispatch_hash_lookup(g_client_dispatch_hash, type);
3949 if (type == 5000 || type == 3001) {
3950 log_error("DISPATCH_LOOKUP: type=%d, idx=%d (len=%zu)", type, idx, len);
3951 }
3952 if (idx < 0) {
3953 disconnect_client_for_bad_data(client, "Unknown packet type: %d (len=%zu)", type, len);
3954 return;
3955 }
3956
3957 if (type == 5000 || type == 3001) {
3958 log_error("DISPATCH_HANDLER: type=%d, calling handler[%d]...", type, idx);
3959 }
3960 g_client_dispatch_handlers[idx](client, data, len);
3961 if (type == 5000 || type == 3001) {
3962 log_error("DISPATCH_HANDLER: type=%d, handler returned", type);
3963 }
3964}
Platform abstraction layer umbrella header providing unified cross-platform API.
Application-level callbacks for library code.
#define APP_CALLBACK_VOID(callback_name)
⚠️‼️ Comprehensive thread-local error context system for ascii-chat
#define LOG_ERRNO_IF_SET(message)
Check if any error occurred and log it if so.
uint64_t atomic_fetch_add_u64(atomic_t *a, uint64_t delta)
Atomically add to a uint64_t and return the previous value.
Definition atomic.c:248
void atomic_store_bool(atomic_t *a, bool value)
Atomically store a boolean value.
Definition atomic.c:177
bool atomic_load_bool(atomic_t *a)
Atomically load a boolean value.
Definition atomic.c:169
void atomic_store_u64(atomic_t *a, uint64_t value)
Atomically store a uint64_t value.
Definition atomic.c:241
uint64_t atomic_load_u64(atomic_t *a)
Atomically load a uint64_t value.
Definition atomic.c:233
bool atomic_exchange_bool(atomic_t *a, bool new_value)
Atomically exchange a boolean and return the old value.
Definition atomic.c:303
⚛️ Atomic operations abstraction layer with debug tracking
🗃️ Lock-Free Unified Memory Buffer Pool with Lazy Allocation
Video and audio codec definitions and capability bitmasks.
⚙️ Common definitions, error codes, macros, and types shared throughout the application
Hardware-Accelerated CRC32 Checksum Computation.
asciichat_error_t crypto_handshake_rekey_complete(crypto_handshake_context_t *ctx, acip_transport_t *transport)
Send REKEY_COMPLETE packet (initiator side)
asciichat_error_t crypto_handshake_rekey_response(crypto_handshake_context_t *ctx, acip_transport_t *transport)
Send REKEY_RESPONSE packet (responder side)
const crypto_context_t * crypto_handshake_get_context(const crypto_handshake_context_t *ctx)
Get the crypto context for encryption/decryption.
void crypto_handshake_destroy(crypto_handshake_context_t *ctx)
Cleanup crypto handshake context with secure memory wiping.
asciichat_error_t crypto_handshake_process_rekey_request(crypto_handshake_context_t *ctx, const uint8_t *packet, size_t packet_len)
Process received REKEY_REQUEST packet (responder side)
bool crypto_handshake_is_ready(const crypto_handshake_context_t *ctx)
Check if handshake is complete and encryption is ready.
bool crypto_handshake_should_rekey(const crypto_handshake_context_t *ctx)
Check if rekeying should be triggered for this handshake context.
asciichat_error_t crypto_handshake_process_rekey_response(crypto_handshake_context_t *ctx, const uint8_t *packet, size_t packet_len)
Process received REKEY_RESPONSE packet (initiator side)
asciichat_error_t crypto_handshake_process_rekey_complete(crypto_handshake_context_t *ctx, const uint8_t *packet, size_t packet_len)
Process received REKEY_COMPLETE packet (responder side)
asciichat_error_t crypto_handshake_rekey_request(crypto_handshake_context_t *ctx, acip_transport_t *transport)
Send REKEY_REQUEST packet (initiator side)
Common declarations and data structures for cryptographic handshake.
Named object registry for debugging — log identifiable resource names.
🔄 Network byte order conversion helpers
#define HOST_TO_NET_U16(val)
Definition endian.h:96
#define HOST_TO_NET_U32(val)
Definition endian.h:66
#define NET_TO_HOST_U16(val)
Definition endian.h:111
#define NET_TO_HOST_U32(val)
Definition endian.h:81
Cross-platform error handling utilities.
bool check_and_record_packet_rate_limit(rate_limiter_t *rate_limiter, const char *client_ip, socket_t client_socket, packet_type_t packet_type)
Map packet type to rate event type and check rate limit.
Definition errors.c:54
Network error handling utilities.
atomic_t g_should_exit
Global application exit flag (shared across all modes)
Definition globals.c:34
asciichat_error_t audio_ring_buffer_write(audio_ring_buffer_t *rb, const float *data, int samples)
Write audio samples to ring buffer.
void audio_ring_buffer_destroy(audio_ring_buffer_t *rb)
Destroy an audio ring buffer.
void mixer_remove_source(mixer_t *mixer, const char *client_id)
Remove an audio source from the mixer.
Definition mixer.c:477
audio_ring_buffer_t * audio_ring_buffer_create_for_capture(void)
Create a new audio ring buffer for capture (without jitter buffering)
void opus_codec_destroy(opus_codec_t *codec)
Destroy an Opus codec instance.
Definition opus.c:230
int mixer_add_source(mixer_t *mixer, const char *client_id, audio_ring_buffer_t *buffer)
Add an audio source to the mixer.
Definition mixer.c:424
void buffer_pool_free(buffer_pool_t *pool, const void *data, size_t size)
Free a buffer back to the pool (lock-free)
void * buffer_pool_alloc(buffer_pool_t *pool, size_t size)
Allocate a buffer from the pool (lock-free fast path)
#define MAX_FRAME_BUFFER_SIZE
Maximum frame buffer size including headers and compression.
unsigned short uint16_t
Definition common.h:57
#define SAFE_MALLOC_ALIGNED(size, alignment, cast)
Definition common.h:349
unsigned int uint32_t
Definition common.h:58
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#define SAFE_STRERROR(errnum)
Definition common.h:465
#define read_u32_unaligned
Definition common.h:485
unsigned long long uint64_t
Definition common.h:59
unsigned char uint8_t
Definition common.h:56
#define write_u32_unaligned
Definition common.h:487
#define write_u16_unaligned
Definition common.h:486
const char * crypto_result_to_string(crypto_result_t result)
Convert crypto result to human-readable string.
crypto_result_t
Cryptographic operation result codes.
crypto_result_t crypto_decrypt(crypto_context_t *ctx, const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext_out, size_t plaintext_out_size, size_t *plaintext_len_out)
Decrypt data using XSalsa20-Poly1305.
#define NAMED_REGISTER_ATOMIC(a, name, parent_ptr)
Register an atomic_t with automatic format specifier.
#define NAMED_REGISTER_PACKET_QUEUE(queue, name, parent_ptr)
Register a packet queue with automatic format specifier.
#define NAMED_REGISTER_CLIENT(client, name, parent_ptr)
Register a client connection with automatic format specifier.
#define NAMED_REGISTER_CRYPTO_CONTEXT(ctx, name, parent_ptr)
Register a crypto context with automatic format specifier.
#define NAMED_REGISTER_VIDEO_FRAME_BUFFER(buf, name, parent_ptr)
Register a video frame buffer with automatic format specifier.
#define NAMED_REGISTER_COND(cond, name, parent_ptr)
Register a condition variable with automatic format specifier.
#define NAMED_REGISTER_AUDIO_RINGBUF(buf, name, parent_ptr)
Register an audio ring buffer with automatic format specifier.
#define NAMED_REGISTER_MUTEX(mutex, name, parent_ptr)
Register a mutex with automatic format specifier.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
#define HAS_ERRNO(var)
Check if an error occurred and get full context.
void asciichat_errno_destroy(void)
Cleanup error system resources.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_NETWORK
Definition error_codes.h:77
@ ERROR_RESOURCE_EXHAUSTED
@ ERROR_NOT_FOUND
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_FRAME
@ ERROR_CRYPTO
Definition error_codes.h:96
@ ERROR_INVALID_PARAM
@ ERROR_BUFFER_OVERFLOW
@ ERROR_INTERNAL
Definition error_codes.h:92
@ CRYPTO_HANDSHAKE_READY
#define MAX_CLIENTS
Maximum possible clients (static array size) - actual runtime limit set by –max-clients (1-32)
Definition limits.h:26
#define MAX_DISPLAY_NAME_LEN
Maximum display name length in characters.
Definition limits.h:20
#define MAX_CLIENT_ID_LEN
Maximum client ID length - format: "noun.N (transport:port)" e.g., "mountain.0 (tcp:48036)".
Definition limits.h:23
#define LOG_RATE_FAST
Log rate limit: 1 second (1,000,000 microseconds)
Definition log_rates.h:26
#define LOG_RATE_SLOW
Log rate limit: 10 seconds (10,000,000 microseconds)
Definition log_rates.h:35
#define LOG_RATE_DEFAULT
Log rate limit: 5 seconds (5,000,000 microseconds) - default for audio/video packets.
Definition log_rates.h:32
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_dev(...)
Log a DEV message (most verbose, development only)
Definition log/log.h:534
#define log_info_client(client, fmt,...)
Server sends INFO log message to client.
Definition network/log.h:69
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
#define log_error_client(client, fmt,...)
Server sends ERROR log message to client.
Definition network/log.h:75
uint64_t time_get_ns(void)
Get current monotonic time in nanoseconds.
Definition util/time.c:108
#define NS_PER_SEC_INT
Definition time.h:157
int time_pretty(uint64_t nanoseconds, int decimals, char *buffer, size_t buffer_size)
Format nanoseconds as pretty duration with spaces and configurable precision.
Definition util/time.c:424
#define NS_PER_MS_INT
Definition time.h:156
#define NS_PER_MS
Definition time.h:147
uint64_t time_elapsed_ns(uint64_t start_ns, uint64_t end_ns)
Calculate elapsed time with wraparound safety.
Definition util/time.c:150
#define US_PER_MS_INT
Definition time.h:160
asciichat_error_t set_socket_timeout(socket_t sockfd, uint64_t timeout_ns)
Set socket timeout.
asciichat_error_t set_socket_keepalive(socket_t sockfd)
Set socket keepalive.
packet_recv_result_t receive_packet_secure(socket_t sockfd, void *crypto_ctx, bool enforce_encryption, packet_envelope_t *envelope)
Receive a packet with decryption and decompression support.
Definition packet.c:569
const char * network_error_string()
Get human-readable error string for network errors.
packet_recv_result_t
Packet reception result codes.
Definition packet.h:1152
@ PACKET_RECV_SUCCESS
Packet received successfully.
Definition packet.h:1154
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
void packet_queue_stop(packet_queue_t *queue)
Destroy a packet queue and free all resources.
Definition queue.c:616
packet_queue_t * packet_queue_create(size_t max_size)
Create a new packet queue.
Definition queue.c:125
packet_queue_t * packet_queue_create_with_pools(size_t max_size, size_t node_pool_size, bool use_buffer_pool)
Create a packet queue with both node and buffer pools.
Definition queue.c:133
size_t data_len
Length of payload data in bytes.
Definition queue.h:126
void packet_queue_free_packet(queued_packet_t *packet)
Free a dequeued packet.
Definition queue.c:568
int packet_queue_enqueue(packet_queue_t *queue, packet_type_t type, const void *data, size_t data_len, uint32_t client_id, bool copy_data)
Enqueue a packet into the queue.
Definition queue.c:209
packet_header_t header
Complete packet header (already in network byte order)
Definition queue.h:122
queued_packet_t * packet_queue_try_dequeue(packet_queue_t *queue)
Try to dequeue a packet without blocking.
Definition queue.c:484
void packet_queue_destroy(packet_queue_t *queue)
Signal queue shutdown (causes dequeue to return NULL)
Definition queue.c:173
void * data
Packet payload data (can be NULL for header-only packets)
Definition queue.h:124
packet_type_t
Network protocol packet type enumeration.
Definition packet.h:286
#define MAX_ERROR_MESSAGE_LENGTH
Maximum error message length (512 bytes)
Definition packet.h:124
@ PACKET_TYPE_AUDIO_OPUS_BATCH
Batched Opus-encoded audio frames.
Definition packet.h:374
@ PACKET_TYPE_CLIENT_LEAVE
Clean disconnect notification.
Definition packet.h:389
@ PACKET_TYPE_IMAGE_FRAME
Complete RGB image with dimensions.
Definition packet.h:363
@ PACKET_TYPE_PONG
Keepalive pong response.
Definition packet.h:385
@ PACKET_TYPE_STREAM_START
Client requests to start sending video/audio.
Definition packet.h:391
@ PACKET_TYPE_ENCRYPTED
Encrypted packet (after handshake completion)
Definition packet.h:333
@ PACKET_TYPE_REMOTE_LOG
Bidirectional remote logging packet.
Definition packet.h:354
@ PACKET_TYPE_PROTOCOL_VERSION
Protocol version and capabilities negotiation.
Definition packet.h:288
@ PACKET_TYPE_IMAGE_FRAME_H265
H.265-encoded frame from client (HEVC/H.265 codec)
Definition packet.h:365
@ PACKET_TYPE_CLIENT_JOIN
Client announces capability to send media.
Definition packet.h:387
@ PACKET_TYPE_CLIENT_CAPABILITIES
Client reports terminal capabilities.
Definition packet.h:381
@ PACKET_TYPE_PING
Keepalive ping packet.
Definition packet.h:383
@ PACKET_TYPE_AUDIO_BATCH
Batched audio packets for efficiency.
Definition packet.h:372
@ PACKET_TYPE_STREAM_STOP
Client stops sending media.
Definition packet.h:393
int initialize_client_palette(palette_type_t palette_type, const char *custom_chars, char client_palette_chars[256], size_t *client_palette_len, char client_luminance_palette[256])
Initialize client palette with full configuration.
Definition palette.c:299
@ PALETTE_STANDARD
Standard ASCII palette: " ...',;:clodxkO0KXNWM".
Definition palette.h:88
int socket_shutdown(socket_t sock, int how)
Shutdown socket I/O.
#define rwlock_wrunlock(lock)
Release a write lock (with debug tracking in debug builds)
Definition rwlock.h:349
int socket_setsockopt(socket_t sock, int level, int optname, const void *optval, socklen_t optlen)
Set socket option.
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
#define INVALID_SOCKET_VALUE
Invalid socket value (POSIX: -1)
Definition socket.h:278
int mutex_init(mutex_t *mutex, const char *name)
Initialize a mutex with a name.
Definition threading.c:16
#define rwlock_rdlock(lock)
Acquire a read lock (with debug tracking in debug builds)
Definition rwlock.h:294
int socket_close(socket_t sock)
Close a socket.
int cond_signal(cond_t *cond)
Signal a condition variable (wake one waiting thread)
ssize_t socket_send(socket_t sock, const void *buf, size_t len, int flags)
Send data on a socket.
bool asciichat_thread_is_initialized(asciichat_thread_t *thread)
Check if a thread handle has been initialized.
int cond_init(cond_t *cond, const char *name)
Initialize a condition variable with a name.
#define rwlock_wrlock(lock)
Acquire a write lock (with debug tracking in debug builds)
Definition rwlock.h:313
void platform_sleep_ms(unsigned int ms)
Sleep for a specified number of milliseconds.
void platform_sleep_us(unsigned int us)
High-precision sleep function with microsecond precision.
int errno
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
#define cond_timedwait(cond, mutex, timeout_ns)
Wait on a condition variable with timeout (with debug tracking in debug builds)
Definition cond.h:300
#define rwlock_rdunlock(lock)
Release a read lock (with debug tracking in debug builds)
Definition rwlock.h:331
#define EAGAIN
int cond_destroy(cond_t *cond)
Destroy a condition variable.
int mutex_destroy(mutex_t *mutex)
Destroy a mutex.
Definition threading.c:22
void handle_audio_opus_batch_packet(client_info_t *client, const void *data, size_t len)
Process AUDIO_OPUS_BATCH packet - efficient Opus-encoded audio batch from client.
uint32_t session_host_add_client(session_host_t *host, socket_t socket, const char *ip, int port)
Add a client from an accepted socket.
Definition host.c:1157
asciichat_error_t session_host_remove_client(session_host_t *host, uint32_t client_id)
Remove a client by ID.
Definition host.c:1391
void format_bytes_pretty(size_t bytes, char *out, size_t out_capacity)
Format byte count into human-readable string.
Definition util/format.c:10
void disconnect_client_for_bad_data(client_info_t *client, const char *format,...)
void video_frame_buffer_destroy(video_frame_buffer_t *vfb)
Destroy frame buffer and free all resources.
video_frame_t * video_frame_begin_write(video_frame_buffer_t *vfb)
Writer API: Start writing a new frame.
video_frame_buffer_t * video_frame_buffer_create(const char *client_id)
Create a double-buffered video frame manager.
Definition video_frame.c:17
void video_frame_commit(video_frame_buffer_t *vfb)
Writer API: Commit the frame and swap buffers.
void digital_rain_destroy(digital_rain_t *rain)
Destroy digital rain effect context.
ACIP protocol packet handlers (transport-agnostic)
int socket_t
🔊 Audio Capture and Playback Interface for ascii-chat
Server-side handshake functions.
ACIP server-side protocol API.
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
Capture stdout/stderr and redirect to logging system.
#define LOG_IO(prefix, block)
Capture output from a code block and log it.
Definition io.h:81
asciichat_error_t crypto_handshake_server_complete(crypto_handshake_context_t *ctx, acip_transport_t *transport, packet_type_t packet_type, const uint8_t *payload, size_t payload_len)
Server: Process auth response and complete handshake.
asciichat_error_t crypto_handshake_server_auth_challenge(crypto_handshake_context_t *ctx, acip_transport_t *transport, packet_type_t packet_type, const uint8_t *payload, size_t payload_len)
Server: Process client's public key and send auth challenge.
asciichat_error_t acip_send_server_state(acip_transport_t *transport, const server_state_packet_t *state)
Send server state update to client (server → client)
asciichat_error_t acip_server_receive_and_dispatch(acip_transport_t *transport, void *client_ctx, const acip_server_callbacks_t *callbacks)
Receive packet from client and dispatch to callbacks.
asciichat_error_t acip_send_ascii_frame(acip_transport_t *transport, const char *frame_data, size_t frame_size, uint32_t width, uint32_t height, const char *client_id)
Send ASCII frame to client (server → client)
asciichat_error_t acip_send_clear_console(acip_transport_t *transport)
Send clear console command to client (server → client)
asciichat_error_t tcp_server_spawn_thread(tcp_server_t *server, socket_t client_socket, void *(*thread_func)(void *), void *thread_arg, int stop_id, const char *thread_name)
Spawn a worker thread for a client.
asciichat_error_t tcp_server_stop_client_threads(tcp_server_t *server, socket_t client_socket)
Stop all threads for a client in stop_id order.
asciichat_error_t tcp_server_add_client(tcp_server_t *server, socket_t socket, void *client_data)
Add client to registry.
#define asciichat_thread_create(thread_ptr, attr, start_routine, arg)
#define asciichat_thread_self()
#define asciichat_thread_equal(t1, t2)
#define asciichat_thread_join(thread, timeout_ms)
#define log_info_every(interval_us, fmt,...)
Rate-limited INFO logging.
Definition log/log.h:705
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
Definition log/log.h:702
#define log_warn_every(interval_us, fmt,...)
Rate-limited WARN logging.
Definition log/log.h:708
#define log_dev_every(interval_us, fmt,...)
Rate-limited DEV logging.
Definition log/log.h:699
Multi-Source Audio Mixing and Processing System.
asciichat_error_t acip_handle_server_packet(acip_transport_t *transport, packet_type_t type, const void *payload, size_t payload_len, void *client_ctx, const acip_server_callbacks_t *callbacks)
Handle incoming packet on server side.
🌐 Core network I/O operations with timeout support
int generate_client_id(char *buffer, size_t buffer_size)
Generate a unique client ID (noun only, no counter or port)
Definition nouns.c:780
int generate_client_name(char *buffer, size_t buffer_size, void *existing_clients_hash, int port, bool is_tcp)
Generate a unique client name from the nouns wordlist.
Definition nouns.c:809
Wordlist and utilities for generating friendly session and client names.
Opus audio codec wrapper for real-time encoding/decoding.
asciichat_error_t packet_parse_error_message(const void *data, size_t len, asciichat_error_t *out_error_code, char *message_buffer, size_t message_buffer_size, size_t *out_message_length)
Parse an error packet payload into components.
Definition packet.c:957
ASCII Palette Management for Video-to-ASCII Conversion.
Cross-platform string operations.
asciichat_error_t acip_send_pong(acip_transport_t *transport)
Send pong packet.
Definition send.c:280
asciichat_error_t packet_send_via_transport(acip_transport_t *transport, packet_type_t type, const void *payload, size_t payload_len, uint32_t client_id)
Send packet via transport with proper header (exported for generic wrappers)
Definition send.c:41
ACIP shared/bidirectional packet sending functions.
rate_limiter_t * g_rate_limiter
Global rate limiter for connection attempts and packet processing.
mixer_t *volatile g_audio_mixer
Global audio mixer instance for multi-client audio processing.
ascii-chat Server Mode Entry Point Header
void handle_client_join_packet(client_info_t *client, const void *data, size_t len)
Process CLIENT_JOIN packet - client announces identity and capabilities.
void handle_pong_packet(client_info_t *client, const void *data, size_t len)
Handle PONG packet - client acknowledged our PING.
void handle_protocol_version_packet(client_info_t *client, const void *data, size_t len)
Process PROTOCOL_VERSION packet - validate protocol compatibility.
void handle_image_frame_packet(client_info_t *client, void *data, size_t len)
Process IMAGE_FRAME packet - store client's video data for rendering.
void handle_audio_batch_packet(client_info_t *client, const void *data, size_t len)
Process AUDIO_BATCH packet - store efficiently batched audio samples.
void handle_image_frame_h265_packet(client_info_t *client, const void *data, size_t len)
Process H.265-encoded image frame from client.
void handle_audio_packet(client_info_t *client, const void *data, size_t len)
Process AUDIO packet - store single audio sample batch (legacy format)
void handle_client_leave_packet(client_info_t *client, const void *data, size_t len)
Process CLIENT_LEAVE packet - handle clean client disconnect.
void handle_stream_stop_packet(client_info_t *client, const void *data, size_t len)
Process STREAM_STOP packet - client requests to halt media transmission.
int send_server_state_to_client(client_info_t *client)
Send current server state to a specific client.
void handle_client_capabilities_packet(client_info_t *client, const void *data, size_t len)
Process CLIENT_CAPABILITIES packet - configure client-specific rendering.
void handle_stream_start_packet(client_info_t *client, const void *data, size_t len)
Process STREAM_START packet - client requests to begin media transmission.
void handle_remote_log_packet_from_client(client_info_t *client, const void *data, size_t len)
void handle_ping_packet(client_info_t *client, const void *data, size_t len)
Handle PING packet - respond with PONG.
void stop_client_render_threads(client_info_t *client)
Stop and cleanup per-client rendering threads.
int create_client_render_threads(server_context_t *server_ctx, client_info_t *client)
Create and initialize per-client rendering threads.
Per-client rendering threads with rate limiting.
#define VIDEO_RENDER_FPS
Cross-platform socket interface for ascii-chat.
int frame
Definition splash.c:99
#define AUDIO_OPUS_BATCH_HEADER_SIZE
void * client_send_thread_func(void *arg)
Client packet send thread.
#define CLIENT_DISPATCH_HANDLER_COUNT
client_info_t * add_client(server_context_t *server_ctx, socket_t socket, const char *client_ip, int port)
void cleanup_client_media_buffers(client_info_t *client)
void process_decrypted_packet(client_info_t *client, packet_type_t type, void *data, size_t len)
void cleanup_client_packet_queues(client_info_t *client)
rwlock_t g_client_manager_rwlock
Reader-writer lock protecting the global client manager.
client_info_t * add_webrtc_client(server_context_t *server_ctx, acip_transport_t *transport, const char *client_ip, bool start_threads)
Register a WebRTC client with the server.
void * client_receive_thread(void *arg)
void stop_client_threads(client_info_t *client)
bool g_client_manager_rwlock_initialized
#define AUDIO_OPUS_FRAME_MAX_BYTES
void(* client_packet_handler_t)(client_info_t *client, const void *data, size_t len)
client_info_t * find_client_by_socket(socket_t socket)
Find client by socket descriptor using linear search.
int process_encrypted_packet(client_info_t *client, packet_type_t *type, void **data, size_t *len, uint32_t *sender_id)
#define MAX_AUDIO_BATCH
int start_webrtc_client_threads(server_context_t *server_ctx, const char *client_id)
Start threads for a WebRTC client after crypto initialization.
void * client_dispatch_thread(void *arg)
Async dispatch thread for WebRTC clients.
client_info_t * find_client_by_id(const char *client_id)
Fast O(1) client lookup by ID using hash table.
client_manager_t g_client_manager
Global client manager singleton - central coordination point.
int remove_client(server_context_t *server_ctx, const char *client_id)
#define CLIENT_DISPATCH_HASH(type)
void broadcast_server_state_to_all_clients(void)
Notify all clients of state changes.
#define CLIENT_DISPATCH_HASH_SIZE
bool crypto_server_is_ready(const char *client_id)
int crypto_server_decrypt_packet(const char *client_id, const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext, size_t plaintext_size, size_t *plaintext_len)
const crypto_context_t * crypto_server_get_context(const char *client_id)
int server_crypto_init(void)
int server_crypto_handshake(client_info_t *client)
Server cryptographic operations and per-client handshake management.
Client-side H.265 media capture and encoding pipeline.
H.265 codec transport protocol definitions.
Multi-client video mixing and ASCII frame generation.
Server-side packet handler callbacks.
Definition handlers.h:148
void(* on_protocol_version)(const protocol_version_packet_t *version, void *client_ctx, void *app_ctx)
Called when client sends protocol version.
Definition handlers.h:150
asciichat_error_t(* recv)(acip_transport_t *transport, void **buffer, size_t *out_len, void **out_allocated_buffer)
Receive data from this transport.
Definition transport.h:145
Transport instance structure.
Definition transport.h:214
const acip_transport_methods_t * methods
Method table (virtual functions)
Definition transport.h:215
crypto_context_t * crypto_ctx
Optional encryption context.
Definition transport.h:216
Error context structure.
char * context_message
Optional custom message (dynamically allocated, owned by system)
asciichat_error_t code
Error code (asciichat_error_t enum value)
Audio batch packet structure (Packet Type 28)
Definition packet.h:904
Audio ring buffer for real-time audio streaming.
Definition ringbuffer.h:205
Hash table entry for client packet dispatch.
uint8_t handler_idx
Handler index (0-based)
packet_type_t key
Packet type (0 = empty slot)
Per-client state structure for server-side client management.
bool transport_encrypted
TLS or DTLS already encrypts this client's transport.
terminal_capabilities_t terminal_caps
char display_name[MAX_DISPLAY_NAME_LEN]
asciichat_thread_t audio_render_thread
packet_queue_t * received_packet_queue
video_frame_buffer_t * outgoing_video_buffer
asciichat_thread_t dispatch_thread
crypto_handshake_context_t crypto_handshake_ctx
video_frame_buffer_t * incoming_video_buffer
audio_ring_buffer_t * incoming_audio_buffer
asciichat_thread_t receive_thread
char client_ip[INET_ADDRSTRLEN]
asciichat_thread_t video_render_thread
char client_id[MAX_CLIENT_ID_LEN]
Global client manager structure for server-side client coordination.
client_info_t * clients_by_id
uthash head pointer for O(1) client_id -> client_info_t* lookups
client_info_t clients[MAX_CLIENTS]
Array of client_info_t structures (backing storage)
int client_count
Current number of active clients.
Cryptographic context structure.
crypto_handshake_state_t state
Error packet structure carrying error code and textual description.
Definition packet.h:727
Image frame packet structure (Packet Type 3)
Definition packet.h:876
uint32_t pixel_format
Pixel format enum (1=RGB24, 2=RGBA32, 3=BGR24, 4=BGRA32)
Definition packet.h:882
uint32_t compressed_size
Compressed data size (0 = not compressed, >0 = compressed)
Definition packet.h:884
uint32_t height
Image height in pixels.
Definition packet.h:880
uint32_t width
Image width in pixels.
Definition packet.h:878
Opus codec context for encoding or decoding.
Definition opus.h:95
Packet envelope containing received packet data.
Definition packet.h:1129
void * allocated_buffer
Buffer that needs to be freed by caller (may be NULL if not allocated)
Definition packet.h:1139
size_t allocated_size
Size of allocated buffer in bytes.
Definition packet.h:1141
size_t len
Length of payload data in bytes.
Definition packet.h:1135
void * data
Packet payload data (decrypted and decompressed if applicable)
Definition packet.h:1133
packet_type_t type
Packet type (from packet_types.h)
Definition packet.h:1131
Network packet header structure.
Definition packet.h:598
uint32_t client_id
Client ID (0 = server, >0 = client identifier)
Definition packet.h:608
uint32_t length
Payload data length in bytes (0 for header-only packets)
Definition packet.h:604
uint16_t type
Packet type (packet_type_t enumeration)
Definition packet.h:602
Thread-safe packet queue for producer-consumer communication.
Definition queue.h:211
Protocol version negotiation packet structure (Packet Type 1)
Definition packet.h:818
Single packet ready to send (header already in network byte order)
Definition queue.h:120
Remote log packet structure carrying log level and message text.
Definition packet.h:741
Read-write lock type (POSIX: pthread_rwlock_t with debug tracking)
Definition rwlock.h:61
Server context - encapsulates all server state.
tcp_server_t * tcp_server
TCP server managing connections.
session_host_t * session_host
Session host for discovery mode support.
Server state packet structure.
Definition packet.h:706
uint32_t reserved[6]
Reserved fields for future use (must be zero)
Definition packet.h:712
uint32_t active_client_count
Number of clients actively sending video/audio streams.
Definition packet.h:710
uint32_t connected_client_count
Total number of currently connected clients.
Definition packet.h:708
int palette_type
Palette type enum value (palette_type_t)
Definition terminal.h:729
terminal_color_mode_t color_level
Detected color support level (terminal_color_mode_t)
Definition terminal.h:711
uint8_t desired_fps
Client's desired frame rate (1-144 FPS)
Definition terminal.h:733
render_mode_t render_mode
Preferred rendering mode (render_mode_t)
Definition terminal.h:721
bool utf8_support
True if terminal supports UTF-8 encoding.
Definition terminal.h:717
bool wants_padding
Whether client wants frame padding (centering) - false for snapshot/piped modes.
Definition terminal.h:737
uint32_t color_count
Maximum number of colors (2, 16, 256, or 16777216)
Definition terminal.h:715
bool detection_reliable
True if detection is confident (reliable detection)
Definition terminal.h:719
Video frame buffer manager.
bool active
True if buffer is active (receiving frames)
mutex_t swap_mutex
Brief mutex for pointer swap only.
atomic_t new_frame_available
Atomic flag: true when new frame available.
size_t allocated_buffer_size
Size of allocated data buffers (for cleanup)
video_frame_t * front_buffer
Currently displayed frame (reader owns)
Video frame structure.
uint64_t sequence_number
Frame sequence number (for drop detection)
uint32_t height
Frame height in pixels.
size_t size
Size of frame data in bytes.
uint32_t width
Frame width in pixels.
uint64_t capture_timestamp_ns
Timestamp when frame was captured (nanoseconds)
void * data
Frame data pointer (points to pre-allocated buffer)
acip_transport_t * acip_tcp_transport_create(const char *name, socket_t sockfd, crypto_context_t *crypto_ctx)
Create TCP transport from existing socket.
void acip_transport_destroy(acip_transport_t *transport)
Destroy transport and free all resources.
@ RENDER_MODE_FOREGROUND
Foreground colors only (text color)
Definition terminal.h:664
@ TERM_COLOR_NONE
No color support (monochrome terminal)
Definition terminal.h:582
⏱️ High-precision timing utilities using sokol_time.h and uthash
Transport abstraction layer for ACIP protocol.
@ ACIP_TRANSPORT_WEBRTC
WebRTC DataChannel (P2P)
Definition transport.h:96
📊 String Formatting Utilities
🔤 String Manipulation and Shell Escaping Utilities