ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
src/client/server.c
Go to the documentation of this file.
1
58#include "server.h"
59#include "main.h"
60#include "../main.h" // Global exit API
61#include "crypto.h"
65
78#include <ascii-chat/util/ip.h>
80#include <ascii-chat/util/url.h> // For WebSocket URL detection
81#include <ascii-chat/common.h>
82#include "display.h"
84#include <ascii-chat/options/rcu.h> // For RCU-based options access
88
89#include <string.h>
90#include <stdarg.h>
91#include <time.h>
92#include <sys/types.h>
93#include <ascii-chat/atomic.h>
94#include <ascii-chat/debug/named.h> // For NAMED_REGISTER_ATOMIC macro
95
96#include <ascii-chat/platform/network.h> // Consolidates platform-specific network headers (includes TCP options)
97
98// Debug flags
99#define DEBUG_NETWORK 1
100#define DEBUG_THREADS 1
101#define DEBUG_MEMORY 1
102
103/* ============================================================================
104 * Connection State Management
105 * ============================================================================ */
106
115static socket_t g_sockfd = INVALID_SOCKET_VALUE;
116
125static acip_transport_t *g_client_transport = NULL;
126
136static atomic_t g_connection_active = {0};
137
146static atomic_t g_connection_lost = {0};
147
156static atomic_t g_should_reconnect = {0};
157
166static uint32_t g_my_client_id = 0;
167
177static char g_server_ip[256] = {0};
178
187static int g_server_port = 0;
188
198static mutex_t g_send_mutex = {0};
199
200/* ============================================================================
201 * Crypto State
202 * ============================================================================ */
203
214
223
232static bool g_encryption_enabled = false;
233
234/* ============================================================================
235 * Reconnection Logic
236 * ============================================================================ */
237
239#define MAX_RECONNECT_DELAY (5 * 1000 * 1000)
240
253static unsigned int get_reconnect_delay(unsigned int reconnect_attempt) {
254 // Use integer arithmetic for microsecond calculations
255 // Initial delay: 100,000 us (0.1 seconds)
256 // Additional delay per attempt: 200,000 us (0.2 seconds)
257 unsigned int delay_us = 100 * US_PER_MS_INT + (reconnect_attempt - 1) * 200 * US_PER_MS_INT;
258 if (delay_us > MAX_RECONNECT_DELAY)
259 delay_us = MAX_RECONNECT_DELAY;
260 return delay_us;
261}
262
263/* ============================================================================
264 * Socket Management Functions
265 * ============================================================================ */
266
278static int close_socket(socket_t socketfd) {
279 if (socket_is_valid(socketfd)) {
280 log_debug("Closing socket %d", socketfd);
281
282 if (socket_close(socketfd) != 0) {
283 log_error("Failed to close socket: %s", network_error_string());
284 return -1;
285 }
286
287 // Small delay to ensure socket resources are fully released
288 // This prevents WSA error 10038 on subsequent connections
289 platform_sleep_us(50 * US_PER_MS_INT); // 50ms delay
290
291 return 0;
292 }
293
294 return 0; // Socket already closed or invalid
295}
296
297/* ============================================================================
298 * Public Interface Functions
299 * ============================================================================ */
300
312 // Initialize mutex for thread-safe packet sending
313 if (mutex_init(&g_send_mutex, "send") != 0) {
314 log_error("Failed to initialize send mutex");
315 return -1;
316 }
317
318 // Register connection atomics for debug sync state monitoring (one-time registration)
319 static bool connection_atomics_registered = false;
320 if (!connection_atomics_registered) {
321 NAMED_REGISTER_ATOMIC(&g_connection_active, "server_connection_active_flag", NULL);
322 NAMED_REGISTER_ATOMIC(&g_connection_lost, "server_connection_lost_flag", NULL);
323 NAMED_REGISTER_ATOMIC(&g_should_reconnect, "should_attempt_reconnection_flag", NULL);
324 connection_atomics_registered = true;
325 }
326
327 // Initialize connection state
328 g_sockfd = INVALID_SOCKET_VALUE;
329 g_client_transport = NULL;
330 atomic_store_bool(&g_connection_active, false);
331 atomic_store_bool(&g_connection_lost, false);
332 atomic_store_bool(&g_should_reconnect, false);
333 g_my_client_id = 0;
334
335 return 0;
336}
337
355int server_connection_establish(const char *address, int port, int reconnect_attempt, bool first_connection,
356 bool has_ever_connected) {
357 (void)first_connection; // Currently unused
358 if (!address || port <= 0) {
359 log_error("Invalid address or port parameters");
360 return -1;
361 }
362
363 // Close any existing connection
364 if (g_sockfd != INVALID_SOCKET_VALUE) {
365 close_socket(g_sockfd);
366 g_sockfd = INVALID_SOCKET_VALUE;
367 }
368
369 // Apply reconnection delay if this is a retry
370 if (reconnect_attempt > 0) {
371 unsigned int delay_us = get_reconnect_delay(reconnect_attempt);
372 // Reconnection attempt logged only to file
373 platform_sleep_us(delay_us);
374
375 // Check if user requested exit during reconnection delay
376 if (should_exit()) {
377 log_debug("Exit requested during reconnection delay");
378 return -1;
379 }
380 } else {
381 // Initial connection logged only to file
382 }
383
384 connection_endpoint_t endpoint = {0};
385 asciichat_error_t endpoint_result = connection_endpoint_resolve(address, port, &endpoint);
386 if (endpoint_result != ASCIICHAT_OK) {
387 log_error("Failed to resolve connection endpoint: %s", asciichat_error_string(endpoint_result));
388 return -1;
389 }
390
391 // Check for WebSocket URL - handle separately from TCP
392 if (endpoint.protocol == CONNECTION_ENDPOINT_WEBSOCKET) {
393 // WebSocket connection - bypass TCP socket creation
394 // Use the original address (URL already contains port if specified)
395 const char *ws_url = endpoint.input;
396 log_info("Connecting via WebSocket: %s (host=%s, port=%u)", ws_url, endpoint.host, endpoint.port);
397
398 // Set server IP and port for crypto handshake
399 // This ensures the crypto context has proper server address info for known_hosts verification
402 log_debug("CLIENT_CONNECT: Set server IP=%s, port=%u from WebSocket endpoint", endpoint.host, endpoint.port);
403
404 // Initialize crypto if encryption is enabled
405 log_debug("CLIENT_CONNECT: Calling client_crypto_init()");
406 if (client_crypto_init() != 0) {
407 log_error("Failed to initialize crypto (password required or incorrect)");
408 log_debug("CLIENT_CONNECT: client_crypto_init() failed");
410 }
411
412 // Get crypto context for transport
414
415 // Create WebSocket transport through the shared factory.
416 asciichat_error_t ws_result =
417 connection_factory_open("client", ws_url, 0, (crypto_context_t *)crypto_ctx, &g_client_transport, &endpoint);
418 if (ws_result != ASCIICHAT_OK) {
419 log_error("Failed to create WebSocket ACIP transport");
420 return -1;
421 }
422 if (!g_client_transport) {
423 log_error("Failed to create WebSocket ACIP transport");
424 return -1;
425 }
426 log_debug("CLIENT_CONNECT: Created WebSocket ACIP transport with crypto context");
427
428 // Set connection as active
429 atomic_store_bool(&g_connection_active, true);
430 atomic_store_bool(&g_connection_lost, false);
431
432 // CRITICAL: Wait for WebSocket connection to be established before sending
433 // The service thread is running but may not have completed TLS handshake yet
434 // Don't block indefinitely - use timeout loops to allow responsiveness
435 log_debug("CLIENT_CONNECT: Waiting for WebSocket connection to be established...");
436 const int CONNECT_CHECK_ATTEMPTS = 300; // 300 * 50ms = 15 seconds timeout
437 int connect_attempts = 0;
438 while (!acip_transport_is_connected(g_client_transport) && connect_attempts < CONNECT_CHECK_ATTEMPTS) {
439 platform_sleep_us(50000); // Sleep 50ms between checks
440 connect_attempts++;
441 if (connect_attempts % 20 == 0) { // Log every 1 second (20 * 50ms)
442 log_info("CLIENT_CONNECT: Waiting for connection... (%d seconds elapsed)", connect_attempts / 20);
443 }
444 }
445
446 if (!acip_transport_is_connected(g_client_transport)) {
447 log_error("Failed to establish WebSocket connection within 15 seconds");
448 acip_transport_destroy(g_client_transport);
449 g_client_transport = NULL;
450 atomic_store_bool(&g_connection_active, false);
451 return -1;
452 }
453
454 log_info("CLIENT_CONNECT: WebSocket connection established, proceeding with handshake");
455
456 // Send initial terminal capabilities to server
459 if (result < 0) {
460 log_error("Failed to send initial capabilities to server: %s", network_error_string());
461 acip_transport_destroy(g_client_transport);
462 g_client_transport = NULL;
463 return -1;
464 }
465
466 // Disable terminal logging after initial setup (for non-snapshot mode)
467 if (!GET_OPTION(snapshot_mode) && has_ever_connected) {
469 }
470
471 // Build capabilities flags
472 uint32_t my_capabilities = CLIENT_CAP_VIDEO;
473 log_debug("GET_OPTION(audio_enabled) = %d (sending CLIENT_JOIN)", GET_OPTION(audio_enabled));
474 if (GET_OPTION(audio_enabled)) {
475 log_debug("Adding CLIENT_CAP_AUDIO to capabilities");
476 my_capabilities |= CLIENT_CAP_AUDIO;
477 }
478 if (GET_OPTION(color_mode) != COLOR_MODE_NONE) {
479 my_capabilities |= CLIENT_CAP_COLOR;
480 }
481 if (GET_OPTION(stretch)) {
482 my_capabilities |= CLIENT_CAP_STRETCH;
483 }
484
485 // Generate display name from username + PID
486 const char *display_name = platform_get_username();
487 char my_display_name[MAX_DISPLAY_NAME_LEN];
488 int pid = getpid();
489 SAFE_SNPRINTF(my_display_name, sizeof(my_display_name), "%s-%d", display_name, pid);
490 if (threaded_send_client_join_packet(my_display_name, my_capabilities) < 0) {
491 log_error("Failed to send client join packet: %s", network_error_string());
492 acip_transport_destroy(g_client_transport);
493 g_client_transport = NULL;
494 return -1;
495 }
496
497 log_info("WebSocket connection established successfully");
498 return 0;
499 }
500
501 asciichat_error_t connect_result =
502 connection_factory_open("client", address, (uint16_t)port, NULL, &g_client_transport, &endpoint);
503 if (connect_result != ASCIICHAT_OK || !g_client_transport) {
504 log_error("Failed to establish TCP transport: %s", asciichat_error_string(connect_result));
505 return -1;
506 }
507
508 g_sockfd = g_client_transport->methods->get_socket(g_client_transport);
509 if (g_sockfd == INVALID_SOCKET_VALUE) {
510 log_error("TCP transport did not expose a valid socket");
511 acip_transport_destroy(g_client_transport);
512 g_client_transport = NULL;
513 return -1;
514 }
515
516 SAFE_STRNCPY(g_server_ip, endpoint.host, sizeof(g_server_ip));
517 log_debug("CLIENT_CONNECT: Resolved TCP endpoint host=%s port=%u", endpoint.host, endpoint.port);
518
519 // Connection successful - extract local port for client ID
520 struct sockaddr_storage local_addr = {0};
521 socklen_t addr_len = sizeof(local_addr);
522 if (getsockname(g_sockfd, (struct sockaddr *)&local_addr, &addr_len) == -1) {
523 log_error("Failed to get local socket address: %s", network_error_string());
524 close_socket(g_sockfd);
525 g_sockfd = INVALID_SOCKET_VALUE;
526 return -1;
527 }
528
529 // Extract port from either IPv4 or IPv6 address
530 int local_port = 0;
531 if (((struct sockaddr *)&local_addr)->sa_family == AF_INET) {
532 local_port = NET_TO_HOST_U16(((struct sockaddr_in *)&local_addr)->sin_port);
533 } else if (((struct sockaddr *)&local_addr)->sa_family == AF_INET6) {
534 local_port = NET_TO_HOST_U16(((struct sockaddr_in6 *)&local_addr)->sin6_port);
535 }
536 g_my_client_id = (uint32_t)local_port;
537
538 // Mark connection as active immediately after successful socket connection
539 atomic_store_bool(&g_connection_active, true);
540 atomic_store_bool(&g_connection_lost, false);
541 atomic_store_bool(&g_should_reconnect, false);
542
543 // Initialize crypto BEFORE starting protocol handshake
544 // Note: server IP is already set above in the connection loop
545 log_debug("CLIENT_CONNECT: Calling client_crypto_init()");
546 if (client_crypto_init() != 0) {
547 log_error("Failed to initialize crypto (password required or incorrect)");
548 log_debug("CLIENT_CONNECT: client_crypto_init() failed");
549 close_socket(g_sockfd);
550 g_sockfd = INVALID_SOCKET_VALUE;
551 return CONNECTION_ERROR_AUTH_FAILED; // SSH key password was wrong - no retry
552 }
553
554 // Create the persistent TCP transport before handshake.
555 // It starts with NULL crypto; client_crypto_handshake() sets crypto_ctx on success.
556 g_client_transport = acip_tcp_transport_create("client", g_sockfd, NULL);
557 if (!g_client_transport) {
558 log_error("Failed to create TCP ACIP transport");
559 close_socket(g_sockfd);
560 g_sockfd = INVALID_SOCKET_VALUE;
561 return -1;
562 }
563 log_debug("CLIENT_CONNECT: Created TCP ACIP transport (crypto pending handshake)");
564
565 // Perform crypto handshake if encryption is enabled
566 log_debug("CLIENT_CONNECT: Calling client_crypto_handshake()");
567 int handshake_result = client_crypto_handshake(g_client_transport);
568 if (handshake_result != 0) {
569 log_error("Crypto handshake failed");
570 log_debug("CLIENT_CONNECT: client_crypto_handshake() failed with code %d", handshake_result);
571 acip_transport_destroy(g_client_transport);
572 g_client_transport = NULL;
573 close_socket(g_sockfd);
574 g_sockfd = INVALID_SOCKET_VALUE;
576 "Crypto handshake failed with server - this usually indicates a protocol mismatch or network issue");
577 }
578 log_debug("CLIENT_CONNECT: client_crypto_handshake() succeeded");
579
580 // Turn OFF terminal logging when successfully connected to server
581 // First connection - we'll disable logging after main.c shows the "Connected successfully" message
582 if (!GET_OPTION(snapshot_mode)) {
583 log_debug("Connected to server - terminal logging will be disabled after initial setup");
584 } else {
585 log_debug("Connected to server - terminal logging kept enabled for snapshot mode");
586 }
587
588 // Configure socket options for optimal performance
589 if (socket_set_keepalive(g_sockfd, true) < 0) {
590 log_warn("Failed to set socket keepalive: %s", network_error_string());
591 }
592
593 // Configure socket buffers and TCP_NODELAY for optimal performance
594 asciichat_error_t sock_config_result = socket_configure_buffers(g_sockfd);
595 if (sock_config_result != ASCIICHAT_OK) {
596 log_warn("Failed to configure socket: %s", network_error_string());
597 }
598
599 // Send initial terminal capabilities to server (this may generate debug logs)
602 if (result < 0) {
603 log_error("Failed to send initial capabilities to server: %s", network_error_string());
604 close_socket(g_sockfd);
605 g_sockfd = INVALID_SOCKET_VALUE;
606 return -1;
607 }
608
609 // Now disable terminal logging after capabilities are sent (for reconnections)
610 if (!GET_OPTION(snapshot_mode) && has_ever_connected) {
612 log_debug("Reconnected to server - terminal logging disabled to prevent interference with ASCII display");
613 }
614
615 // Send client join packet for multi-user support
616 uint32_t my_capabilities = CLIENT_CAP_VIDEO; // Basic video capability
617 log_debug("GET_OPTION(audio_enabled) = %d (sending CLIENT_JOIN)", GET_OPTION(audio_enabled));
618 if (GET_OPTION(audio_enabled)) {
619 log_debug("Adding CLIENT_CAP_AUDIO to capabilities");
620 my_capabilities |= CLIENT_CAP_AUDIO;
621 }
622 if (GET_OPTION(color_mode) != COLOR_MODE_NONE) {
623 my_capabilities |= CLIENT_CAP_COLOR;
624 }
625 if (GET_OPTION(stretch)) {
626 my_capabilities |= CLIENT_CAP_STRETCH;
627 }
628
629 // Generate display name from username + PID
630 const char *display_name = platform_get_username();
631
632 char my_display_name[MAX_DISPLAY_NAME_LEN];
633 int pid = getpid();
634 SAFE_SNPRINTF(my_display_name, sizeof(my_display_name), "%s-%d", display_name, pid);
635
636 if (threaded_send_client_join_packet(my_display_name, my_capabilities) < 0) {
637 log_error("Failed to send client join packet: %s", network_error_string());
638 close_socket(g_sockfd);
639 g_sockfd = INVALID_SOCKET_VALUE;
640 return -1;
641 }
642
643 // Connection already marked as active after socket creation
644
645 return 0;
646}
647
656 // For TCP: check socket validity
657 // For WebRTC: socket is INVALID_SOCKET_VALUE but transport exists
658 return atomic_load_bool(&g_connection_active) && (g_sockfd != INVALID_SOCKET_VALUE || g_client_transport != NULL);
659}
660
669 return g_sockfd;
670}
671
680 return g_client_transport;
681}
682
694 log_debug("[TRANSPORT_LIFECYCLE] server_connection_set_transport() called with transport=%p", (void *)transport);
695
696 // Clean up any existing transport
697 if (g_client_transport) {
698 log_warn("[TRANSPORT_LIFECYCLE] Replacing existing transport=%p with new transport=%p (potential premature "
699 "destruction?)",
700 (void *)g_client_transport, (void *)transport);
701 log_debug("[TRANSPORT_LIFECYCLE] OLD transport is_connected=%s before destruction",
702 acip_transport_is_connected(g_client_transport) ? "true" : "false");
703 acip_transport_destroy(g_client_transport);
704 log_debug("[TRANSPORT_LIFECYCLE] OLD transport destroyed");
705 }
706
707 log_debug("[TRANSPORT_LIFECYCLE] Setting g_client_transport to %p", (void *)transport);
708 g_client_transport = transport;
709
710 // Mark connection as active when transport is set
711 if (transport) {
712 log_debug("[TRANSPORT_LIFECYCLE] Transport is non-NULL, extracting socket...");
713 g_sockfd = acip_transport_get_socket(transport);
714 log_debug("[TRANSPORT_LIFECYCLE] Socket extracted: %d from transport=%p", (int)g_sockfd, (void *)transport);
715
716 atomic_store_bool(&g_connection_active, true);
717 atomic_store_bool(&g_connection_lost, false); // Reset lost flag for new connection
718 log_debug("[TRANSPORT_LIFECYCLE] Server connection transport set and marked active (transport=%p, sockfd=%d, "
719 "is_connected=%s)",
720 (void *)transport, (int)g_sockfd, acip_transport_is_connected(transport) ? "true" : "false");
721 } else {
722 g_sockfd = INVALID_SOCKET_VALUE;
723 atomic_store_bool(&g_connection_active, false);
724 log_debug("[TRANSPORT_LIFECYCLE] Server connection transport cleared and marked inactive");
725 }
726
727 log_debug("[TRANSPORT_LIFECYCLE] server_connection_set_transport() completed");
728}
729
738 return g_my_client_id;
739}
740
752 return g_server_ip;
753}
754
764void server_connection_set_ip(const char *ip) {
765 if (ip) {
766 SAFE_STRNCPY(g_server_ip, ip, sizeof(g_server_ip));
767 log_debug("Server IP set to: %s", g_server_ip);
768 } else {
769 g_server_ip[0] = '\0';
770 log_debug("Server IP cleared");
771 }
772}
773
784 g_server_port = port;
785 log_debug("Server port set to: %d", g_server_port);
786}
787
798 return g_server_port;
799}
800
810 log_debug("[TRANSPORT_LIFECYCLE] server_connection_close() called");
811
812 // Acquire send mutex to prevent race with threaded_send_image_frame()
813 // This ensures any in-flight sends complete before we destroy the transport
814 mutex_lock(&g_send_mutex);
815 atomic_store_bool(&g_connection_active, false);
816
817 // Destroy ACIP transport before closing socket
818 if (g_client_transport) {
819 log_debug("[TRANSPORT_LIFECYCLE] Destroying transport=%p (is_connected=%s) before closing socket",
820 (void *)g_client_transport, acip_transport_is_connected(g_client_transport) ? "true" : "false");
821 acip_transport_destroy(g_client_transport);
822 g_client_transport = NULL;
823 log_debug("[TRANSPORT_LIFECYCLE] Transport destroyed, g_client_transport set to NULL");
824 }
825
826 mutex_unlock(&g_send_mutex);
827
828 // The socket belongs to the connection handle's TCP/WebSocket owner. The
829 // caller cleans up that owner after protocol workers have stopped; closing
830 // g_sockfd here would close the same descriptor a second time.
831 g_sockfd = INVALID_SOCKET_VALUE;
832
833 g_my_client_id = 0;
834
835 // Cleanup crypto context if encryption was enabled
836 if (g_encryption_enabled) {
837 log_debug("[TRANSPORT_LIFECYCLE] Cleaning up crypto context");
839 g_encryption_enabled = false;
840 }
841
842 // Turn ON terminal logging when connection is closed (unless it was disabled with --quiet)
843 if (!GET_OPTION(quiet)) {
845 }
846 log_debug("[TRANSPORT_LIFECYCLE] server_connection_close() completed");
847}
861static void server_connection_interrupt_websocket_recv(void) {
862 // Access the transport
863 acip_transport_t *transport = g_client_transport;
864 if (!transport || !transport->impl_data) {
865 return; // No transport
866 }
867
868 // Check if this is a WebSocket transport
869 if (transport->methods && transport->methods->get_type) {
870 acip_transport_type_t type = transport->methods->get_type(transport);
871 if (type == ACIP_TRANSPORT_WEBSOCKET) {
872 // For WebSocket, directly signal the recv condition variable to wake
873 // any blocked threads. This is async-signal-safe (doesn't require mutex).
874 // The waiting threads will re-acquire their mutexes and check is_connected.
876
877 // Signal recv condition - wakes threads waiting for fragments
878 // This is safe to call without holding the recv_mutex
879 cond_broadcast(&ws_data->recv_cond);
880 cond_broadcast(&ws_data->state_cond);
881 }
882 }
883}
884
895 // NOTE: This function may be called from:
896 // - Signal handlers on Unix (async-signal-safe context)
897 // - SetConsoleCtrlHandler callback thread on Windows (separate thread context)
898 // Only use atomic operations and simple system calls - NO mutex locks, NO malloc, NO logging.
899
900 atomic_store_bool(&g_connection_active, false);
901 atomic_store_bool(&g_connection_lost, true);
902
903 if (g_sockfd != INVALID_SOCKET_VALUE) {
904 // Only shutdown() the socket to interrupt blocking recv()/send() operations.
905 // Do NOT close() here - on Windows, closing the socket while another thread
906 // is using it is undefined behavior and can cause STATUS_STACK_BUFFER_OVERRUN.
907 // The actual socket close happens in server_connection_close() which is called
908 // from the main thread after worker threads have been joined.
909 socket_shutdown(g_sockfd, SHUT_RDWR);
910 }
911
912 // For WebSocket transport: interrupt blocking recv() calls
913 // The recv() checks is_connected before waiting for data. Setting this to false
914 // will cause any blocked recv() to return immediately with CONNECTION_CLOSED.
915 // We use a function that's async-signal-safe (no mutexes) to do this.
916 server_connection_interrupt_websocket_recv();
917
918 // DO NOT call log_set_terminal_output() here - it uses mutex which is NOT async-signal-safe.
919 // The normal cleanup path in shutdown_client() will handle logging state.
920}
921
931 atomic_store_bool(&g_connection_lost, true);
932 atomic_store_bool(&g_connection_active, false);
933
934 // Don't re-enable terminal logging here - let the splash screen handle it
935 // The reconnection splash will capture and display logs properly
937}
938
947 return atomic_load_bool(&g_connection_lost);
948}
949
959 if (!GET_OPTION(quiet)) {
961 }
963 mutex_destroy(&g_send_mutex);
964}
965
966/* ============================================================================
967 * Thread Safety Interface
968 * ============================================================================ */
969
970/* ============================================================================
971 * Thread-Safe Wrapper Functions
972 * ============================================================================ */
973
987asciichat_error_t threaded_send_packet(packet_type_t type, const void *data, size_t len) {
988 // Lock mutex for entire send operation to prevent concurrent socket writes
989 mutex_lock(&g_send_mutex);
990
991 // Check connection status and get transport reference
992 if (!atomic_load_bool(&g_connection_active) || !g_client_transport) {
993 log_debug("[TRANSPORT_LIFECYCLE] threaded_send_packet() check failed: active=%s, transport=%p",
994 atomic_load_bool(&g_connection_active) ? "true" : "false", (void *)g_client_transport);
995 mutex_unlock(&g_send_mutex);
996 return SET_ERRNO(ERROR_NETWORK, "Connection not active or transport unavailable");
997 }
998
999 // Get transport reference - transport has its own internal synchronization
1000 acip_transport_t *transport = g_client_transport;
1001 log_debug_every(LOG_RATE_SLOW, "[TRANSPORT_LIFECYCLE] threaded_send_packet() using transport=%p, is_connected=%s",
1002 (void *)transport, acip_transport_is_connected(transport) ? "true" : "false");
1003
1004 // Network I/O happens while holding mutex to prevent concurrent socket writes
1005 asciichat_error_t result = packet_send_via_transport(transport, type, data, len, 0);
1006
1007 // Unlock after send completes
1008 mutex_unlock(&g_send_mutex);
1009
1010 // If send failed due to network error, signal connection loss
1011 if (result != ASCIICHAT_OK) {
1012 log_debug("[TRANSPORT_LIFECYCLE] threaded_send_packet() send failed, calling server_connection_lost()");
1014 return result;
1015 }
1016
1017 return ASCIICHAT_OK;
1018}
1019
1034int threaded_send_audio_batch_packet(const float *samples, int num_samples, int batch_count) {
1035 // Lock mutex for entire send operation to prevent concurrent socket writes
1036 mutex_lock(&g_send_mutex);
1037
1038 // Check connection status and get transport reference
1039 if (!atomic_load_bool(&g_connection_active) || !g_client_transport) {
1040 mutex_unlock(&g_send_mutex);
1041 return -1;
1042 }
1043
1044 // Get transport reference - transport has its own internal synchronization
1045 acip_transport_t *transport = g_client_transport;
1046
1047 // Network I/O happens while holding mutex to prevent concurrent socket writes
1048 asciichat_error_t result = acip_send_audio_batch(transport, samples, (uint32_t)num_samples, (uint32_t)batch_count);
1049
1050 // Unlock after send completes
1051 mutex_unlock(&g_send_mutex);
1052
1053 // If send failed due to network error, signal connection loss
1054 if (result != ASCIICHAT_OK) {
1056 return -1;
1057 }
1058
1059 return 0;
1060}
1061
1076asciichat_error_t threaded_send_audio_opus(const uint8_t *opus_data, size_t opus_size, int sample_rate,
1077 int frame_duration) {
1078 // Get transport reference while holding mutex (brief lock)
1079 mutex_lock(&g_send_mutex);
1080
1081 // Check connection status and get transport reference
1082 if (!atomic_load_bool(&g_connection_active) || !g_client_transport) {
1083 mutex_unlock(&g_send_mutex);
1084 return SET_ERRNO(ERROR_NETWORK, "Connection not active or transport unavailable");
1085 }
1086
1087 // Get transport reference - transport has its own internal synchronization
1088 acip_transport_t *transport = g_client_transport;
1089 mutex_unlock(&g_send_mutex);
1090
1091 // Build Opus packet with header (outside mutex - no blocking I/O yet)
1092 size_t header_size = 16; // sample_rate (4), frame_duration (4), reserved (8)
1093 size_t total_size = header_size + opus_size;
1094 void *packet_data = buffer_pool_alloc(NULL, total_size);
1095 if (!packet_data) {
1096 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate buffer for Opus packet: %zu bytes", total_size);
1097 }
1098
1099 // Write header in network byte order
1100 uint8_t *buf = (uint8_t *)packet_data;
1101 uint32_t sr = HOST_TO_NET_U32((uint32_t)sample_rate);
1102 uint32_t fd = HOST_TO_NET_U32((uint32_t)frame_duration);
1103 memcpy(buf, &sr, 4);
1104 memcpy(buf + 4, &fd, 4);
1105 memset(buf + 8, 0, 8); // Reserved
1106
1107 // Copy Opus data
1108 memcpy(buf + header_size, opus_data, opus_size);
1109
1110 // Network I/O happens OUTSIDE the mutex to prevent deadlock on TCP buffer full
1111 asciichat_error_t result =
1112 packet_send_via_transport(transport, PACKET_TYPE_AUDIO_OPUS_BATCH, packet_data, total_size, 0);
1113
1114 // Clean up
1115 buffer_pool_free(NULL, packet_data, total_size);
1116
1117 // If send failed due to network error, signal connection loss
1118 if (result != ASCIICHAT_OK) {
1120 return result;
1121 }
1122
1123 return ASCIICHAT_OK;
1124}
1125
1141 const uint16_t *frame_sizes, int frame_count) {
1142 // Lock mutex for entire send operation to prevent concurrent socket writes
1143 mutex_lock(&g_send_mutex);
1144
1145 // Check connection status and get transport reference
1146 if (!atomic_load_bool(&g_connection_active) || !g_client_transport) {
1147 mutex_unlock(&g_send_mutex);
1148 return SET_ERRNO(ERROR_NETWORK, "Connection not active or transport unavailable");
1149 }
1150
1151 // Get transport reference - transport has its own internal synchronization
1152 acip_transport_t *transport = g_client_transport;
1153
1154 // Network I/O happens while holding mutex to prevent concurrent socket writes
1155 // Opus uses 20ms frames at 48kHz (960 samples = 20ms)
1156 asciichat_error_t result =
1157 acip_send_audio_opus_batch(transport, opus_data, opus_size, frame_sizes, frame_count, 48000, 20);
1158
1159 // Unlock after send completes
1160 mutex_unlock(&g_send_mutex);
1161
1162 // If send failed due to network error, signal connection loss
1163 if (result != ASCIICHAT_OK) {
1165 return result;
1166 }
1167
1168 return ASCIICHAT_OK;
1169}
1170
1185asciichat_error_t threaded_send_image_frame(const void *pixel_data, uint32_t width, uint32_t height,
1186 uint32_t pixel_format) {
1187 if (!pixel_data) {
1188 return SET_ERRNO(ERROR_INVALID_PARAM, "Pixel data is NULL");
1189 }
1190
1191 mutex_lock(&g_send_mutex);
1192
1193 // Get transport reference - may be NULL if connection was lost
1195 if (!transport || !server_connection_is_active()) {
1196 mutex_unlock(&g_send_mutex);
1197 return SET_ERRNO(ERROR_NETWORK, "Transport unavailable");
1198 }
1199
1200 // Send image frame with socket lock held
1201 asciichat_error_t result = acip_send_image_frame(transport, pixel_data, width, height, pixel_format);
1202
1203 // Unlock after send completes
1204 mutex_unlock(&g_send_mutex);
1205
1206 // If send failed due to network error, signal connection loss
1207 if (result != ASCIICHAT_OK) {
1209 return result;
1210 }
1211
1212 return ASCIICHAT_OK;
1213}
1214
1230 if (!pixel_data) {
1231 return SET_ERRNO(ERROR_INVALID_PARAM, "Pixel data is NULL");
1232 }
1233
1234 // Get encoder - may be NULL if not initialized
1235 h265_encoder_t *encoder = g_h265_encoder;
1236 if (!encoder) {
1237 return SET_ERRNO(ERROR_INVALID_STATE, "H.265 encoder not initialized");
1238 }
1239
1240 // Allocate buffer and encode frame WITHOUT holding the send mutex.
1241 // This avoids long critical section that can cause deadlocks with named registry operations.
1242 log_info("[H265_SEND_FRAME_1] Allocating buffer and encoding frame (no mutex held)");
1243 size_t output_buffer_size = 5 + (width * height * 2);
1244 uint8_t *output_buffer = buffer_pool_alloc(NULL, output_buffer_size);
1245 if (!output_buffer) {
1246 log_error("Failed to allocate output buffer for frame");
1247 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate H.265 output buffer: %zu bytes", output_buffer_size);
1248 }
1249
1250 // Encode frame
1251 size_t encoded_size = output_buffer_size - 5;
1252 asciichat_error_t encode_result = h265_encode(encoder, (uint16_t)width, (uint16_t)height, (const uint8_t *)pixel_data,
1253 output_buffer + 5, &encoded_size);
1254 log_info("[H265_SEND_FRAME_2] โœ… Frame encoded, encoded_size=%zu, result=%d", encoded_size, encode_result);
1255
1256 if (encode_result != ASCIICHAT_OK) {
1257 log_error("H.265 encoding failed: %s", asciichat_error_string(encode_result));
1258 buffer_pool_free(NULL, output_buffer, output_buffer_size);
1259 return encode_result;
1260 }
1261
1262 // Prepare packet header: [flags:u8][width:u16][height:u16][h265_data...]
1263 output_buffer[0] = 0; // flags: no flags set by default
1264 output_buffer[1] = (width >> 8) & 0xFF;
1265 output_buffer[2] = width & 0xFF;
1266 output_buffer[3] = (height >> 8) & 0xFF;
1267 output_buffer[4] = height & 0xFF;
1268
1269 size_t total_size = 5 + encoded_size;
1270
1271 // NOW acquire the send mutex just for the actual network transmission
1272 log_info("[H265_SEND_FRAME_3] About to acquire g_send_mutex for network send");
1273 mutex_lock(&g_send_mutex);
1274 log_info("[H265_SEND_FRAME_4] โœ… Acquired g_send_mutex, checking transport");
1275
1276 // Get transport reference - may be NULL if connection was lost
1278 if (!transport || !server_connection_is_active()) {
1279 mutex_unlock(&g_send_mutex);
1280 log_info("[H265_SEND_FRAME_5] Transport unavailable, releasing mutex");
1281 buffer_pool_free(NULL, output_buffer, output_buffer_size);
1282 return SET_ERRNO(ERROR_NETWORK, "Transport unavailable");
1283 }
1284
1285 // Send H.265 frame with socket lock held (short critical section)
1286 log_info("[H265_SEND_FRAME_6] Sending packet via transport (%zu bytes)", total_size);
1287 asciichat_error_t result =
1288 packet_send_via_transport(transport, PACKET_TYPE_IMAGE_FRAME_H265, output_buffer, total_size, 0);
1289 log_info("[H265_SEND_FRAME_7] packet_send_via_transport returned: %d", result);
1290
1291 // Unlock after send completes
1292 mutex_unlock(&g_send_mutex);
1293 log_info("[H265_SEND_FRAME_8] โœ… Released g_send_mutex");
1294
1295 buffer_pool_free(NULL, output_buffer, output_buffer_size);
1296 log_info("[H265_SEND_FRAME_9] โœ… Freed output buffer");
1297
1298 // If send failed due to network error, signal connection loss
1299 if (result != ASCIICHAT_OK) {
1301 return result;
1302 }
1303
1304 return ASCIICHAT_OK;
1305}
1306
1315 // Use threaded_send_packet which handles encryption, mutex locking, and connection state
1316 return threaded_send_packet(PACKET_TYPE_PING, NULL, 0);
1317}
1318
1327 // Use threaded_send_packet which handles encryption, mutex locking, and connection state
1328 return threaded_send_packet(PACKET_TYPE_PONG, NULL, 0);
1329}
1330
1340 // Connection and transport availability is checked by threaded_send_packet()
1341
1342 // Build STREAM_START packet locally
1343 uint32_t type_data = HOST_TO_NET_U32(stream_type);
1344
1345 // Use threaded_send_packet() which handles encryption
1346 return threaded_send_packet(PACKET_TYPE_STREAM_START, &type_data, sizeof(type_data));
1347}
1348
1362asciichat_error_t threaded_send_terminal_size_with_auto_detect(unsigned short width, unsigned short height) {
1363 // Log the dimensions being sent to server (helps debug dimension mismatch issues)
1364 log_debug("Sending terminal size to server: %ux%u (auto_width=%d, auto_height=%d)", width, height,
1366
1367 // Connection and transport availability is checked by threaded_send_packet()
1368
1369 // Build terminal capabilities packet locally
1370 // Detect terminal capabilities automatically
1372
1373 // Set wants_padding based on snapshot mode and TTY status
1374 // Disable padding when:
1375 // - In snapshot mode (one frame and exit)
1376 // - When stdout is not a TTY (piped/redirected output)
1377 // Enable padding for interactive terminal sessions
1378 bool is_snapshot_mode = GET_OPTION(snapshot_mode);
1379 bool is_interactive = terminal_is_interactive();
1380 caps.wants_padding = is_interactive && !is_snapshot_mode;
1381
1382 log_debug("Client capabilities: wants_padding=%d (snapshot=%d, interactive=%d, stdin_tty=%d, stdout_tty=%d)",
1383 caps.wants_padding, is_snapshot_mode, is_interactive, terminal_is_stdin_tty(), terminal_is_stdout_tty());
1384
1385 // Apply user's color mode override
1386 caps = apply_color_mode_override(caps);
1387
1388 // Check if detection was reliable, use fallback only for auto-detection
1389 if (!caps.detection_reliable && GET_OPTION(color_mode) == COLOR_MODE_AUTO) {
1390 log_warn("Terminal capability detection not reliable, using fallback");
1391 SAFE_MEMSET(&caps, sizeof(caps), 0, sizeof(caps));
1393 caps.color_count = 2;
1394 caps.capabilities = 0;
1395 SAFE_STRNCPY(caps.term_type, "unknown", sizeof(caps.term_type));
1396 SAFE_STRNCPY(caps.colorterm, "", sizeof(caps.colorterm));
1397 caps.detection_reliable = 0;
1398 // Preserve wants_padding even in fallback mode
1399 caps.wants_padding = is_interactive && !is_snapshot_mode;
1400 }
1401
1402 // Convert to network packet format with proper byte order
1404 net_packet.capabilities = HOST_TO_NET_U32(caps.capabilities);
1405 net_packet.color_level = HOST_TO_NET_U32(caps.color_level);
1406 net_packet.color_count = HOST_TO_NET_U32(caps.color_count);
1407 net_packet.render_mode = HOST_TO_NET_U32(caps.render_mode);
1408 net_packet.width = HOST_TO_NET_U16(width);
1409 net_packet.height = HOST_TO_NET_U16(height);
1410 net_packet.palette_type = HOST_TO_NET_U32(GET_OPTION(palette_type));
1411 net_packet.utf8_support = HOST_TO_NET_U32(caps.utf8_support ? 1 : 0);
1412
1413 const options_t *opts = options_get();
1414 if (GET_OPTION(palette_type) == PALETTE_CUSTOM && GET_OPTION(palette_custom_set)) {
1415 const char *palette_custom = opts && opts->palette_custom_set ? opts->palette_custom : "";
1416 SAFE_STRNCPY(net_packet.palette_custom, palette_custom, sizeof(net_packet.palette_custom));
1417 net_packet.palette_custom[sizeof(net_packet.palette_custom) - 1] = '\0';
1418 } else {
1419 SAFE_MEMSET(net_packet.palette_custom, sizeof(net_packet.palette_custom), 0, sizeof(net_packet.palette_custom));
1420 }
1421
1422 // Set desired FPS
1423 int fps = GET_OPTION(fps);
1424 if (fps > 0) {
1425 net_packet.desired_fps = (uint8_t)(fps > 144 ? 144 : fps);
1426 } else {
1427 net_packet.desired_fps = caps.desired_fps;
1428 }
1429
1430 if (net_packet.desired_fps == 0) {
1431 net_packet.desired_fps = DEFAULT_MAX_FPS;
1432 }
1433
1434 SAFE_STRNCPY(net_packet.term_type, caps.term_type, sizeof(net_packet.term_type));
1435 net_packet.term_type[sizeof(net_packet.term_type) - 1] = '\0';
1436
1437 SAFE_STRNCPY(net_packet.colorterm, caps.colorterm, sizeof(net_packet.colorterm));
1438 net_packet.colorterm[sizeof(net_packet.colorterm) - 1] = '\0';
1439
1440 net_packet.detection_reliable = caps.detection_reliable;
1441 // Send UTF-8 support flag: true for AUTO (default) and TRUE settings, false for FALSE setting
1442 net_packet.utf8_support = (GET_OPTION(force_utf8) != UTF8_SETTING_FALSE) ? 1 : 0;
1443
1444 // Set wants_padding flag (1=padding enabled, 0=no padding for snapshot/piped modes)
1445 net_packet.wants_padding = caps.wants_padding ? 1 : 0;
1446
1447 // Set codec capabilities (client supports all codecs by default)
1450
1451 // Use threaded_send_packet() which handles encryption
1452 return threaded_send_packet(PACKET_TYPE_CLIENT_CAPABILITIES, &net_packet, sizeof(net_packet));
1453}
1454
1466int threaded_send_client_join_packet(const char *display_name, uint32_t capabilities) {
1467 // Connection and transport availability is checked by threaded_send_packet()
1468
1469 // Build CLIENT_JOIN packet locally
1470 client_info_packet_t join_packet;
1471 SAFE_MEMSET(&join_packet, sizeof(join_packet), 0, sizeof(join_packet));
1472 join_packet.client_id = HOST_TO_NET_U32(0); // Will be assigned by server
1473 SAFE_SNPRINTF(join_packet.display_name, MAX_DISPLAY_NAME_LEN, "%s", display_name ? display_name : "Unknown");
1474 join_packet.capabilities = HOST_TO_NET_U32(capabilities);
1475
1476 // Use threaded_send_packet() which handles encryption
1477 int send_result = threaded_send_packet(PACKET_TYPE_CLIENT_JOIN, &join_packet, sizeof(join_packet));
1478 if (send_result == 0) {
1479 mutex_lock(&g_send_mutex);
1480 bool active = atomic_load_u64(&g_connection_active);
1481 socket_t socket_snapshot = g_sockfd;
1482 const crypto_context_t *crypto_ctx = crypto_client_is_ready() ? crypto_client_get_context() : NULL;
1483 if (active && socket_snapshot != INVALID_SOCKET_VALUE) {
1484 (void)log_network_message(
1485 socket_snapshot, (const struct crypto_context_t *)crypto_ctx, LOG_INFO, REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER,
1486 "CLIENT_JOIN sent (display=\"%s\", capabilities=0x%x)", join_packet.display_name, capabilities);
1487 }
1488 mutex_unlock(&g_send_mutex);
1489 }
1490 return send_result;
1491}
Platform abstraction layer umbrella header providing unified cross-platform API.
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
uint64_t atomic_load_u64(atomic_t *a)
Atomically load a uint64_t value.
Definition atomic.c:233
โš›๏ธ 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
asciichat_error_t h265_encode(h265_encoder_t *encoder, uint16_t width, uint16_t height, const uint8_t *pixel_data, uint8_t *out_data, size_t *out_len)
void h265_encoder_t
asciichat_error_t connection_endpoint_resolve(const char *input, uint16_t default_port, connection_endpoint_t *endpoint_out)
Shared endpoint normalization for TCP and WebSocket connections.
@ CONNECTION_ENDPOINT_WEBSOCKET
asciichat_error_t connection_factory_open(const char *name, const char *input, uint16_t default_port, crypto_context_t *crypto_ctx, acip_transport_t **transport_out, connection_endpoint_t *endpoint_out)
Shared connection factory for TCP and WebSocket endpoints.
void crypto_handshake_destroy(crypto_handshake_context_t *ctx)
Cleanup crypto handshake context with secure memory wiping.
Common declarations and data structures for cryptographic handshake.
Named object registry for debugging โ€” log identifiable resource names.
HEVC/H.265 encoder for ASCII art frames using libx265.
๐Ÿ”„ 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
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)
int threaded_send_pong_packet(void)
Thread-safe pong packet transmission.
void server_connection_close()
Close the server connection gracefully.
bool server_connection_is_active()
Check if server connection is currently active.
void server_connection_shutdown()
Emergency connection shutdown for signal handlers.
asciichat_error_t threaded_send_image_frame(const void *pixel_data, uint32_t width, uint32_t height, uint32_t pixel_format)
Thread-safe image frame transmission.
asciichat_error_t threaded_send_terminal_size_with_auto_detect(unsigned short width, unsigned short height)
Thread-safe terminal size packet transmission with auto-detection.
int threaded_send_client_join_packet(const char *display_name, uint32_t capabilities)
Thread-safe client join packet transmission.
acip_transport_t * server_connection_get_transport(void)
Get ACIP transport instance.
uint32_t server_connection_get_client_id()
Get client ID assigned by server.
socket_t server_connection_get_socket()
Get current socket file descriptor.
asciichat_error_t threaded_send_audio_opus(const uint8_t *opus_data, size_t opus_size, int sample_rate, int frame_duration)
Thread-safe Opus audio frame transmission.
void server_connection_set_transport(acip_transport_t *transport)
Set ACIP transport instance from connection fallback.
void server_connection_set_ip(const char *ip)
Set the server IP address.
int threaded_send_ping_packet(void)
Thread-safe ping packet transmission.
bool server_connection_is_lost()
Check if connection loss has been detected.
int server_connection_init()
Initialize the server connection management subsystem.
asciichat_error_t threaded_send_audio_opus_batch(const uint8_t *opus_data, size_t opus_size, const uint16_t *frame_sizes, int frame_count)
Thread-safe Opus audio batch packet transmission.
asciichat_error_t threaded_send_image_frame_h265(const void *pixel_data, uint32_t width, uint32_t height)
Send H.265-encoded video frame to server (thread-safe)
asciichat_error_t threaded_send_packet(packet_type_t type, const void *data, size_t len)
Thread-safe packet transmission.
void server_connection_set_port(int port)
Set the server port.
int threaded_send_audio_batch_packet(const float *samples, int num_samples, int batch_count)
Thread-safe batched audio packet transmission.
void server_connection_cleanup()
Cleanup connection management subsystem.
asciichat_error_t threaded_send_stream_start_packet(uint32_t stream_type)
Thread-safe stream start packet transmission.
void server_connection_lost()
Signal that connection has been lost.
int server_connection_get_port(void)
Get the server port.
const char * server_connection_get_ip()
Get resolved server IP address.
int server_connection_establish(const char *address, int port, int reconnect_attempt, bool first_connection, bool has_ever_connected)
Establish connection to ascii-chat server.
crypto_handshake_context_t g_crypto_ctx
Per-connection crypto handshake context.
const crypto_context_t * crypto_client_get_context(void)
Get crypto context for encryption/decryption.
int client_crypto_handshake(acip_transport_t *transport)
Perform crypto handshake with server.
int client_crypto_init(void)
Initialize client crypto handshake.
bool crypto_client_is_ready(void)
Check if crypto handshake is ready.
void display_full_reset()
Perform full display reset.
unsigned short uint16_t
Definition common.h:57
unsigned int uint32_t
Definition common.h:58
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define SAFE_MEMSET(dest, dest_size, ch, count)
Definition common.h:469
#define SAFE_SNPRINTF(buffer, buffer_size,...)
Definition common.h:492
#define FATAL(code,...)
Exit with error code and custom message, with stack trace in debug builds.
Definition common.h:158
unsigned char uint8_t
Definition common.h:56
#define NAMED_REGISTER_ATOMIC(a, name, parent_ptr)
Register an atomic_t with automatic format specifier.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_INVALID_STATE
@ ERROR_CRYPTO_HANDSHAKE
Definition error_codes.h:99
@ ERROR_NETWORK
Definition error_codes.h:77
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
#define DEFAULT_MAX_FPS
Default maximum frame rate (frames per second)
Definition limits.h:29
#define MAX_DISPLAY_NAME_LEN
Maximum display name length in characters.
Definition limits.h:20
#define LOG_RATE_SLOW
Log rate limit: 10 seconds (10,000,000 microseconds)
Definition log_rates.h:35
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
asciichat_error_t log_network_message(socket_t sockfd, const struct crypto_context_t *crypto_ctx, log_level_t level, remote_log_direction_t direction, const char *fmt,...)
Send a formatted log message over the network.
Definition log/log.c:1550
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
void log_set_terminal_output(bool enabled)
Control stderr output to terminal.
Definition log/log.c:700
@ REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER
Definition network/log.h:23
#define AUDIO_CODEC_CAP_ALL
Default audio codec capabilities (all codecs supported)
Definition codecs.h:77
#define VIDEO_CODEC_CAP_ALL
Default video codec capabilities (all codecs supported)
Definition codecs.h:50
#define US_PER_MS_INT
Definition time.h:160
#define CLIENT_CAP_STRETCH
Client can stretch frames to fill terminal.
Definition packet.h:926
#define CLIENT_CAP_COLOR
Client supports color rendering.
Definition packet.h:925
const char * network_error_string()
Get human-readable error string for network errors.
asciichat_error_t socket_configure_buffers(socket_t sockfd)
Configure socket buffers and TCP_NODELAY for optimal performance.
#define CLIENT_CAP_AUDIO
Client can send/receive audio.
Definition packet.h:924
#define CLIENT_CAP_VIDEO
Client can send/receive video.
Definition packet.h:923
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
#define COLOR_MODE_AUTO
Backward compatibility aliases for color mode enum values.
const options_t * options_get(void)
Get current options (lock-free read)
Definition rcu.c:496
#define COLOR_MODE_NONE
Monochrome mode.
@ UTF8_SETTING_FALSE
Force UTF-8 OFF: disable UTF-8 support.
packet_type_t
Network protocol packet type enumeration.
Definition packet.h:286
@ PACKET_TYPE_AUDIO_OPUS_BATCH
Batched Opus-encoded audio frames.
Definition packet.h:374
@ 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_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
@ PALETTE_CUSTOM
User-defined via โ€“palette-chars.
Definition palette.h:98
int socket_shutdown(socket_t sock, int how)
Shutdown socket I/O.
bool socket_is_valid(socket_t sock)
Check if a socket handle is valid.
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
int cond_broadcast(cond_t *cond)
Broadcast to a condition variable (wake all waiting threads)
#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
int socket_set_keepalive(socket_t sock, bool keepalive)
Set SO_KEEPALIVE socket option.
int socket_close(socket_t sock)
Close a socket.
void platform_sleep_us(unsigned int us)
High-precision sleep function with microsecond precision.
const char * platform_get_username(void)
Get the current username.
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
int mutex_destroy(mutex_t *mutex)
Destroy a mutex.
Definition threading.c:22
int socket_t
#define close_socket
ACIP client-side protocol API.
โš™๏ธ Unified options parsing system for ascii-chat with builder pattern and lock-free access
๐ŸŒ IP Address Parsing and Formatting Utilities
asciichat_error_t acip_send_image_frame(acip_transport_t *transport, const void *pixel_data, uint32_t width, uint32_t height, uint32_t pixel_format)
Send image frame to server (client โ†’ server)
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
Definition log/log.h:702
terminal_capabilities_t apply_color_mode_override(terminal_capabilities_t caps)
Apply command-line overrides to detected capabilities.
Definition misc.c:29
bool should_exit(void)
Definition misc.c:131
๐ŸŒ Core network I/O operations with timeout support
Internal WebSocket implementation types shared between transport.c and server.c.
bool auto_height
bool auto_width
ASCII Palette Management for Video-to-ASCII Conversion.
Cross-platform network headers and socket definitions.
unsigned short int terminal_get_effective_width(void)
Get effective terminal width with fallback priority.
bool terminal_is_interactive(void)
Check if the session is fully interactive.
bool terminal_is_stdin_tty(void)
Check if stdin is connected to a TTY.
bool terminal_is_stdout_tty(void)
Check if stdout is connected to a TTY.
unsigned short int terminal_get_effective_height(void)
Get effective terminal height with fallback priority.
terminal_capabilities_t detect_terminal_capabilities(void)
Detect terminal capabilities.
asciichat_error_t acip_send_audio_opus_batch(acip_transport_t *transport, const void *opus_data, size_t opus_len, const uint16_t *frame_sizes, uint32_t frame_count, uint32_t sample_rate, uint32_t frame_duration)
Send batched Opus-encoded audio frames.
Definition send.c:223
asciichat_error_t acip_send_audio_batch(acip_transport_t *transport, const float *samples, uint32_t num_samples, uint32_t batch_count)
Send audio batch packet.
Definition send.c:180
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.
ascii-chat Server Mode Entry Point Header
h265_encoder_t *volatile g_h265_encoder
Global H.265 encoder context for client video streaming.
#define MAX_RECONNECT_DELAY
@ CONNECTION_ERROR_AUTH_FAILED
Authentication failure (no retry)
๐Ÿ–ฅ๏ธ Unified terminal display abstraction for session-based rendering
Server cryptographic operations and per-client handshake management.
Server-side H.265 decoding pipeline for multi-client streams.
socket_t(* get_socket)(acip_transport_t *transport)
Get underlying socket (if applicable)
Definition transport.h:175
acip_transport_type_t(* get_type)(acip_transport_t *transport)
Get transport type.
Definition transport.h:164
Transport instance structure.
Definition transport.h:214
void * impl_data
Transport-specific state.
Definition transport.h:218
const acip_transport_methods_t * methods
Method table (virtual functions)
Definition transport.h:215
Atomic value wrapper for integral/boolean types.
Definition atomic.h:76
Client information packet structure.
Definition packet.h:653
char display_name[32]
User display name (null-terminated, max MAX_DISPLAY_NAME_LEN bytes)
Definition packet.h:657
uint32_t client_id
Unique client identifier (1-9, 0 = server)
Definition packet.h:655
uint32_t capabilities
Client capabilities bitmask (CLIENT_CAP_VIDEO | CLIENT_CAP_AUDIO | CLIENT_CAP_COLOR | CLIENT_CAP_STRE...
Definition packet.h:660
connection_endpoint_protocol_t protocol
Cryptographic context structure.
Cryptographic handshake context structure.
Mutex type (POSIX: pthread_mutex_t with debug tracking)
Consolidated options structure.
Terminal capabilities packet structure (Packet Type 5)
Definition packet.h:1018
uint32_t codec_capabilities_video
Video codec capabilities bitmask (VIDEO_CODEC_CAP_*)
Definition packet.h:1050
uint32_t capabilities
Terminal capabilities bitmask (TERM_CAP_* flags)
Definition packet.h:1020
uint32_t color_level
Color level enum value (terminal_color_mode_t)
Definition packet.h:1022
uint32_t color_count
Actual color count (16, 256, or 16777216)
Definition packet.h:1024
uint16_t height
Terminal height in characters.
Definition packet.h:1030
uint8_t wants_padding
Whether client wants frame padding (1=padding enabled, 0=no padding for snapshot/piped modes)
Definition packet.h:1048
uint32_t render_mode
Render mode enum value (foreground/background/half-block)
Definition packet.h:1026
uint16_t width
Terminal width in characters.
Definition packet.h:1028
uint8_t desired_fps
Client's desired frame rate (1-144 FPS)
Definition packet.h:1044
uint8_t detection_reliable
Detection reliability flag (1=reliable detection, 0=best guess)
Definition packet.h:1036
uint32_t codec_capabilities_audio
Audio codec capabilities bitmask (AUDIO_CODEC_CAP_*)
Definition packet.h:1052
char palette_custom[64]
Custom palette characters (if palette_type == PALETTE_CUSTOM)
Definition packet.h:1042
char colorterm[32]
$COLORTERM environment variable value (for debugging)
Definition packet.h:1034
uint32_t palette_type
Palette type enum value (palette_type_t)
Definition packet.h:1040
uint32_t utf8_support
UTF-8 support flag (0=no UTF-8, 1=UTF-8 supported)
Definition packet.h:1038
char term_type[32]
$TERM environment variable value (for debugging)
Definition packet.h:1032
Complete terminal capabilities structure.
Definition terminal.h:709
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 capabilities
Capability flags bitmask (terminal_capability_flags_t)
Definition terminal.h:713
uint32_t color_count
Maximum number of colors (2, 16, 256, or 16777216)
Definition terminal.h:715
char term_type[64]
$TERM environment variable value (for debugging)
Definition terminal.h:723
char colorterm[64]
$COLORTERM environment variable value (for debugging)
Definition terminal.h:725
bool detection_reliable
True if detection is confident (reliable detection)
Definition terminal.h:719
WebSocket transport implementation data.
Cross-platform system functions interface for ascii-chat.
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.
๐Ÿ–ฅ๏ธ Cross-platform terminal interface for ascii-chat
@ 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_type_t
Transport type enumeration.
Definition transport.h:93
@ ACIP_TRANSPORT_WEBSOCKET
WebSocket over TCP.
Definition transport.h:95
#define LOG_INFO
Definition types.h:40
๐ŸŒ Production-Grade URL Validation and Parsing