ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
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 "crypto.h"
61#include "crypto/crypto.h"
63
65#include "platform/terminal.h"
66#include "platform/system.h"
67#include "network/packet.h"
68#include "network/network.h"
69#include "network/acip/send.h"
71#include "util/endian.h"
72#include "util/ip.h"
73#include "common.h"
74#include "display.h"
75#include "options/options.h"
76#include "options/rcu.h" // For RCU-based options access
77#include "video/palette.h"
78#include "buffer_pool.h"
79
80#include <string.h>
81#include <stdarg.h>
82#include <time.h>
83#include <sys/types.h>
84#include <stdatomic.h>
85#ifndef _WIN32
86#include <netinet/tcp.h>
87#include <netdb.h> // For getaddrinfo(), gai_strerror()
88#include <arpa/inet.h> // For inet_ntop()
89#else
90#include <ws2tcpip.h> // For getaddrinfo(), gai_strerror(), inet_ntop()
91#endif
92
93// Debug flags
94#define DEBUG_NETWORK 1
95#define DEBUG_THREADS 1
96#define DEBUG_MEMORY 1
97
98/* ============================================================================
99 * Connection State Management
100 * ============================================================================ */
101
110static socket_t g_sockfd = INVALID_SOCKET_VALUE;
111
120static acip_transport_t *g_client_transport = NULL;
121
131static atomic_bool g_connection_active = false;
132
141static atomic_bool g_connection_lost = false;
142
151static atomic_bool g_should_reconnect = false;
152
161static uint32_t g_my_client_id = 0;
162
172static char g_server_ip[256] = {0};
173
183static mutex_t g_send_mutex = {0};
184
185/* ============================================================================
186 * Crypto State
187 * ============================================================================ */
188
199
208static bool g_encryption_enabled = false;
209
210/* ============================================================================
211 * Reconnection Logic
212 * ============================================================================ */
213
215#define MAX_RECONNECT_DELAY (5 * 1000 * 1000)
216
229static unsigned int get_reconnect_delay(unsigned int reconnect_attempt) {
230 // Use integer arithmetic for microsecond calculations
231 // Initial delay: 100,000 us (0.1 seconds)
232 // Additional delay per attempt: 200,000 us (0.2 seconds)
233 unsigned int delay_us = 100000 + (reconnect_attempt - 1) * 200000;
234 if (delay_us > MAX_RECONNECT_DELAY)
235 delay_us = MAX_RECONNECT_DELAY;
236 return delay_us;
237}
238
239/* ============================================================================
240 * Socket Management Functions
241 * ============================================================================ */
242
254static int close_socket(socket_t socketfd) {
255 if (socket_is_valid(socketfd)) {
256 log_debug("Closing socket %d", socketfd);
257
258 if (socket_close(socketfd) != 0) {
259 log_error("Failed to close socket: %s", network_error_string());
260 return -1;
261 }
262
263 // Small delay to ensure socket resources are fully released
264 // This prevents WSA error 10038 on subsequent connections
265 platform_sleep_usec(50000); // 50ms delay
266
267 return 0;
268 }
269
270 return 0; // Socket already closed or invalid
271}
272
273/* ============================================================================
274 * Public Interface Functions
275 * ============================================================================ */
276
288 // Initialize mutex for thread-safe packet sending
289 if (mutex_init(&g_send_mutex) != 0) {
290 log_error("Failed to initialize send mutex");
291 return -1;
292 }
293
294 // Initialize connection state
295 g_sockfd = INVALID_SOCKET_VALUE;
296 g_client_transport = NULL;
297 atomic_store(&g_connection_active, false);
298 atomic_store(&g_connection_lost, false);
299 atomic_store(&g_should_reconnect, false);
300 g_my_client_id = 0;
301
302 return 0;
303}
304
322int server_connection_establish(const char *address, int port, int reconnect_attempt, bool first_connection,
323 bool has_ever_connected) {
324 (void)first_connection; // Currently unused
325 if (!address || port <= 0) {
326 log_error("Invalid address or port parameters");
327 return -1;
328 }
329
330 // Close any existing connection
331 if (g_sockfd != INVALID_SOCKET_VALUE) {
332 close_socket(g_sockfd);
333 g_sockfd = INVALID_SOCKET_VALUE;
334 }
335
336 // Apply reconnection delay if this is a retry
337 if (reconnect_attempt > 0) {
338 unsigned int delay_us = get_reconnect_delay(reconnect_attempt);
339 // Reconnection attempt logged only to file
340 platform_sleep_usec(delay_us);
341
342 // Check if user requested exit during reconnection delay
343 if (should_exit()) {
344 log_debug("Exit requested during reconnection delay");
345 return -1;
346 }
347 } else {
348 // Initial connection logged only to file
349 }
350
351 // Resolve server address using getaddrinfo() for IPv4/IPv6 support
352 // Special handling for localhost: ensure we try both IPv6 (::1) and IPv4 (127.0.0.1)
353 // Many systems only map "localhost" to 127.0.0.1 in /etc/hosts
354 bool is_localhost =
355 (strcmp(address, "localhost") == 0 || strcmp(address, "127.0.0.1") == 0 || strcmp(address, "::1") == 0);
356
357 struct addrinfo hints, *res = NULL, *addr_iter;
358 memset(&hints, 0, sizeof(hints));
359 hints.ai_family = AF_UNSPEC; // Allow IPv4 or IPv6
360 hints.ai_socktype = SOCK_STREAM;
361 if (is_localhost) {
362 hints.ai_flags = AI_NUMERICSERV; // Optimize for localhost
363 }
364
365 char port_str[16];
366 SAFE_SNPRINTF(port_str, sizeof(port_str), "%d", port);
367
368 // For localhost, try IPv6 loopback (::1) first, then fall back to DNS resolution
369 if (is_localhost) {
370 log_debug("Localhost detected - trying IPv6 loopback [::1]:%s first...", port_str);
371 hints.ai_family = AF_INET6;
372 hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
373
374 int ipv6_result = getaddrinfo("::1", port_str, &hints, &res);
375 if (ipv6_result == 0 && res != NULL) {
376 // Try IPv6 loopback connection
377 g_sockfd = socket_create(res->ai_family, res->ai_socktype, res->ai_protocol);
378 if (g_sockfd != INVALID_SOCKET_VALUE) {
379 log_info("Trying IPv6 loopback connection to [::1]:%s...", port_str);
380 if (connect_with_timeout(g_sockfd, res->ai_addr, res->ai_addrlen, CONNECT_TIMEOUT)) {
381 log_debug("Connection successful using IPv6 loopback");
382 SAFE_STRNCPY(g_server_ip, "::1", sizeof(g_server_ip));
383 freeaddrinfo(res);
384 res = NULL; // Prevent double-free at connection_success label
385 goto connection_success;
386 }
387 if (socket_get_error(g_sockfd) != 0) {
388 log_debug("NETWORK_ERROR: %d", (int)socket_get_error(g_sockfd));
389 } else {
390 // log_debug("IPv6 loopback connection failed: %s", network_error_string());
391 }
392 close_socket(g_sockfd);
393 g_sockfd = INVALID_SOCKET_VALUE;
394 }
395 freeaddrinfo(res);
396 res = NULL;
397 }
398
399 // Check if user requested exit (Ctrl-C) before trying IPv4
400 if (should_exit()) {
401 log_debug("Exit requested during connection attempt");
402 return -1;
403 }
404
405 // IPv6 failed, try IPv4 loopback (127.0.0.1)
406 log_debug("IPv6 failed, trying IPv4 loopback 127.0.0.1:%s...", port_str);
407 hints.ai_family = AF_INET;
408
409 int ipv4_result = getaddrinfo("127.0.0.1", port_str, &hints, &res);
410 if (ipv4_result == 0 && res != NULL) {
411 g_sockfd = socket_create(res->ai_family, res->ai_socktype, res->ai_protocol);
412 if (g_sockfd != INVALID_SOCKET_VALUE) {
413 log_info("Trying IPv4 loopback connection to 127.0.0.1:%s...", port_str);
414 if (connect_with_timeout(g_sockfd, res->ai_addr, res->ai_addrlen, CONNECT_TIMEOUT)) {
415 log_debug("Connection successful using IPv4 loopback");
416 SAFE_STRNCPY(g_server_ip, "127.0.0.1", sizeof(g_server_ip));
417 freeaddrinfo(res);
418 res = NULL; // Prevent double-free at connection_success label
419 goto connection_success;
420 }
421 if (socket_get_error(g_sockfd) != 0) {
422 log_debug("NETWORK_ERROR: %d", (int)socket_get_error(g_sockfd));
423 } else {
424 // log_debug("IPv4 loopback connection failed: %s", network_error_string());
425 }
426 close_socket(g_sockfd);
427 g_sockfd = INVALID_SOCKET_VALUE;
428 }
429 freeaddrinfo(res);
430 res = NULL;
431 }
432
433 // Both IPv6 and IPv4 loopback failed for localhost
434 log_warn("Could not connect to localhost using either IPv6 or IPv4 loopback");
435 return -1;
436 }
437
438 // For non-localhost addresses, use standard resolution
439 log_debug("Resolving server address '%s' port %s...", address, port_str);
440 hints.ai_family = AF_UNSPEC;
441 hints.ai_flags = 0;
442 int getaddr_result = getaddrinfo(address, port_str, &hints, &res);
443 if (getaddr_result != 0) {
444 log_error("Failed to resolve server address '%s': %s", address, gai_strerror(getaddr_result));
445 return -1;
446 }
447
448 // Try each address returned by getaddrinfo() until one succeeds
449 // Prefer IPv6 over IPv4: try IPv6 addresses first, then fall back to IPv4
450 for (int address_family = AF_INET6; address_family >= AF_INET; address_family -= (AF_INET6 - AF_INET)) {
451 for (addr_iter = res; addr_iter != NULL; addr_iter = addr_iter->ai_next) {
452 // Skip addresses that don't match current pass (IPv6 first, then IPv4)
453 if (addr_iter->ai_family != address_family) {
454 continue;
455 }
456
457 // Create socket with appropriate address family
458 g_sockfd = socket_create(addr_iter->ai_family, addr_iter->ai_socktype, addr_iter->ai_protocol);
459 if (g_sockfd == INVALID_SOCKET_VALUE) {
460 log_debug("Could not create socket for address family %d: %s", addr_iter->ai_family, network_error_string());
461 continue; // Try next address
462 }
463
464 // Log which address family we're trying
465 if (addr_iter->ai_family == AF_INET) {
466 log_debug("Trying IPv4 connection...");
467 } else if (addr_iter->ai_family == AF_INET6) {
468 log_debug("Trying IPv6 connection...");
469 }
470
471 // Attempt connection with timeout
472 if (connect_with_timeout(g_sockfd, addr_iter->ai_addr, addr_iter->ai_addrlen, CONNECT_TIMEOUT)) {
473 // Connection successful!
474 log_debug("Connection successful using %s", addr_iter->ai_family == AF_INET ? "IPv4"
475 : addr_iter->ai_family == AF_INET6 ? "IPv6"
476 : "unknown protocol");
477
478 // Extract server IP address for known_hosts
479 if (format_ip_address(addr_iter->ai_family, addr_iter->ai_addr, g_server_ip, sizeof(g_server_ip)) ==
480 ASCIICHAT_OK) {
481 log_debug("Resolved server IP: %s", g_server_ip);
482 } else {
483 log_warn("Failed to format server IP address");
484 }
485
486 goto connection_success; // Break out of both loops
487 }
488
489 // Connection failed - close socket and try next address
490 if (socket_get_error(g_sockfd) != 0) {
491 log_debug("NETWORK_ERROR: %d", (int)socket_get_error(g_sockfd));
492 } else {
493 // log_debug("Connection failed: %s", network_error_string());
494 }
495 close_socket(g_sockfd);
496 g_sockfd = INVALID_SOCKET_VALUE;
497 }
498 }
499
500connection_success:
501
502 if (res) {
503 freeaddrinfo(res);
504 }
505
506 // If we exhausted all addresses without success, fail
507 if (g_sockfd == INVALID_SOCKET_VALUE) {
508 log_warn("Could not connect to server %s:%d (tried all addresses)", address, port);
509 return -1;
510 }
511
512 // Connection successful - extract local port for client ID
513 struct sockaddr_storage local_addr = {0};
514 socklen_t addr_len = sizeof(local_addr);
515 if (getsockname(g_sockfd, (struct sockaddr *)&local_addr, &addr_len) == -1) {
516 log_error("Failed to get local socket address: %s", network_error_string());
517 close_socket(g_sockfd);
518 g_sockfd = INVALID_SOCKET_VALUE;
519 return -1;
520 }
521
522 // Extract port from either IPv4 or IPv6 address
523 int local_port = 0;
524 if (((struct sockaddr *)&local_addr)->sa_family == AF_INET) {
525 local_port = NET_TO_HOST_U16(((struct sockaddr_in *)&local_addr)->sin_port);
526 } else if (((struct sockaddr *)&local_addr)->sa_family == AF_INET6) {
527 local_port = NET_TO_HOST_U16(((struct sockaddr_in6 *)&local_addr)->sin6_port);
528 }
529 g_my_client_id = (uint32_t)local_port;
530
531 // Mark connection as active immediately after successful socket connection
532 atomic_store(&g_connection_active, true);
533 atomic_store(&g_connection_lost, false);
534 atomic_store(&g_should_reconnect, false);
535
536 // Initialize crypto BEFORE starting protocol handshake
537 // Note: server IP is already set above in the connection loop
538 log_debug("CLIENT_CONNECT: Calling client_crypto_init()");
539 if (client_crypto_init() != 0) {
540 log_error("Failed to initialize crypto (password required or incorrect)");
541 log_debug("CLIENT_CONNECT: client_crypto_init() failed");
542 close_socket(g_sockfd);
543 g_sockfd = INVALID_SOCKET_VALUE;
544 return CONNECTION_ERROR_AUTH_FAILED; // SSH key password was wrong - no retry
545 }
546
547 // Perform crypto handshake if encryption is enabled
548 log_debug("CLIENT_CONNECT: Calling client_crypto_handshake()");
549 int handshake_result = client_crypto_handshake(g_sockfd);
550 if (handshake_result != 0) {
551 log_error("Crypto handshake failed");
552 log_debug("CLIENT_CONNECT: client_crypto_handshake() failed with code %d", handshake_result);
553 close_socket(g_sockfd);
554 g_sockfd = INVALID_SOCKET_VALUE;
556 "Crypto handshake failed with server - this usually indicates a protocol mismatch or network issue");
557 }
558 log_debug("CLIENT_CONNECT: client_crypto_handshake() succeeded");
559
560 // Create ACIP transport for protocol-agnostic packet sending
561 // The transport wraps the socket with encryption context from the handshake
563 g_client_transport = acip_tcp_transport_create(g_sockfd, (crypto_context_t *)crypto_ctx);
564 if (!g_client_transport) {
565 log_error("Failed to create ACIP transport");
566 close_socket(g_sockfd);
567 g_sockfd = INVALID_SOCKET_VALUE;
568 return -1;
569 }
570 log_debug("CLIENT_CONNECT: Created ACIP transport with crypto context");
571
572 // Turn OFF terminal logging when successfully connected to server
573 // First connection - we'll disable logging after main.c shows the "Connected successfully" message
574 if (!GET_OPTION(snapshot_mode)) {
575 log_debug("Connected to server - terminal logging will be disabled after initial setup");
576 } else {
577 log_debug("Connected to server - terminal logging kept enabled for snapshot mode");
578 }
579
580 // Configure socket options for optimal performance
581 if (socket_set_keepalive(g_sockfd, true) < 0) {
582 log_warn("Failed to set socket keepalive: %s", network_error_string());
583 }
584
585 // Configure socket buffers and TCP_NODELAY for optimal performance
586 asciichat_error_t sock_config_result = socket_configure_buffers(g_sockfd);
587 if (sock_config_result != ASCIICHAT_OK) {
588 log_warn("Failed to configure socket: %s", network_error_string());
589 }
590
591 // Send initial terminal capabilities to server (this may generate debug logs)
593 if (result < 0) {
594 log_error("Failed to send initial capabilities to server: %s", network_error_string());
595 close_socket(g_sockfd);
596 g_sockfd = INVALID_SOCKET_VALUE;
597 return -1;
598 }
599
600 // Now disable terminal logging after capabilities are sent (for reconnections)
601 if (!GET_OPTION(snapshot_mode) && has_ever_connected) {
603 log_debug("Reconnected to server - terminal logging disabled to prevent interference with ASCII display");
604 }
605
606 // Send client join packet for multi-user support
607 uint32_t my_capabilities = CLIENT_CAP_VIDEO; // Basic video capability
608 log_info("GET_OPTION(audio_enabled) = %d (sending CLIENT_JOIN)", GET_OPTION(audio_enabled));
609 if (GET_OPTION(audio_enabled)) {
610 log_info("Adding CLIENT_CAP_AUDIO to capabilities");
611 my_capabilities |= CLIENT_CAP_AUDIO;
612 }
613 if (GET_OPTION(color_mode) != COLOR_MODE_NONE) {
614 my_capabilities |= CLIENT_CAP_COLOR;
615 }
616 if (GET_OPTION(stretch)) {
617 my_capabilities |= CLIENT_CAP_STRETCH;
618 }
619
620 // Generate display name from username + PID
621 const char *display_name = platform_get_username();
622
623 char my_display_name[MAX_DISPLAY_NAME_LEN];
624 int pid = getpid();
625 SAFE_SNPRINTF(my_display_name, sizeof(my_display_name), "%s-%d", display_name, pid);
626
627 if (threaded_send_client_join_packet(my_display_name, my_capabilities) < 0) {
628 log_error("Failed to send client join packet: %s", network_error_string());
629 close_socket(g_sockfd);
630 g_sockfd = INVALID_SOCKET_VALUE;
631 return -1;
632 }
633
634 // Connection already marked as active after socket creation
635
636 return 0;
637}
638
647 return atomic_load(&g_connection_active) && (g_sockfd != INVALID_SOCKET_VALUE);
648}
649
658 return g_sockfd;
659}
660
669 return g_client_transport;
670}
671
683 log_debug("server_connection_set_transport() called with transport=%p", (void *)transport);
684
685 // Clean up any existing transport
686 if (g_client_transport) {
687 log_warn("Replacing existing transport with new fallback transport");
688 acip_transport_destroy(g_client_transport);
689 }
690
691 log_debug("Setting g_client_transport to %p", (void *)transport);
692 g_client_transport = transport;
693
694 // Mark connection as active when transport is set
695 if (transport) {
696 log_debug("Transport is non-NULL, extracting socket...");
697 // Extract socket from transport for backward compatibility with socket-based checks
698 g_sockfd = acip_transport_get_socket(transport);
699 log_debug("Socket extracted: %d", (int)g_sockfd);
700
701 atomic_store(&g_connection_active, true);
702 log_debug("Server connection transport set and marked active (sockfd=%d)", (int)g_sockfd);
703 } else {
704 g_sockfd = INVALID_SOCKET_VALUE;
705 atomic_store(&g_connection_active, false);
706 log_debug("Server connection transport cleared and marked inactive");
707 }
708
709 log_debug("server_connection_set_transport() completed");
710}
711
720 return g_my_client_id;
721}
722
734 return g_server_ip;
735}
736
747void server_connection_set_ip(const char *ip) {
748 if (ip) {
749 SAFE_STRNCPY(g_server_ip, ip, sizeof(g_server_ip));
750 log_debug("Server IP set to: %s", g_server_ip);
751 } else {
752 g_server_ip[0] = '\0';
753 log_debug("Server IP cleared");
754 }
755}
756
766 atomic_store(&g_connection_active, false);
767
768 // Destroy ACIP transport before closing socket
769 if (g_client_transport) {
770 acip_transport_destroy(g_client_transport);
771 g_client_transport = NULL;
772 }
773
774 if (g_sockfd != INVALID_SOCKET_VALUE) {
775 close_socket(g_sockfd);
776 g_sockfd = INVALID_SOCKET_VALUE;
777 }
778
779 g_my_client_id = 0;
780
781 // Cleanup crypto context if encryption was enabled
782 if (g_encryption_enabled) {
784 g_encryption_enabled = false;
785 }
786
787 // Turn ON terminal logging when connection is closed
789}
790
801 // NOTE: This function may be called from:
802 // - Signal handlers on Unix (async-signal-safe context)
803 // - SetConsoleCtrlHandler callback thread on Windows (separate thread context)
804 // Only use atomic operations and simple system calls - NO mutex locks, NO malloc, NO logging.
805
806 atomic_store(&g_connection_active, false);
807 atomic_store(&g_connection_lost, true);
808
809 if (g_sockfd != INVALID_SOCKET_VALUE) {
810 // Only shutdown() the socket to interrupt blocking recv()/send() operations.
811 // Do NOT close() here - on Windows, closing the socket while another thread
812 // is using it is undefined behavior and can cause STATUS_STACK_BUFFER_OVERRUN.
813 // The actual socket close happens in server_connection_close() which is called
814 // from the main thread after worker threads have been joined.
815 socket_shutdown(g_sockfd, SHUT_RDWR);
816 }
817
818 // DO NOT call log_set_terminal_output() here - it uses mutex which is NOT async-signal-safe.
819 // The normal cleanup path in shutdown_client() will handle logging state.
820}
821
831 atomic_store(&g_connection_lost, true);
832 atomic_store(&g_connection_active, false);
833
834 // Turn ON terminal logging when connection is lost
837}
838
847 return atomic_load(&g_connection_lost);
848}
849
863
864/* ============================================================================
865 * Thread Safety Interface
866 * ============================================================================ */
867
868/* ============================================================================
869 * Thread-Safe Wrapper Functions
870 * ============================================================================ */
871
885asciichat_error_t threaded_send_packet(packet_type_t type, const void *data, size_t len) {
886 mutex_lock(&g_send_mutex);
887
888 // Recheck connection status and transport INSIDE the mutex to prevent TOCTOU race
889 if (!atomic_load(&g_connection_active) || !g_client_transport) {
890 mutex_unlock(&g_send_mutex);
891 return SET_ERRNO(ERROR_NETWORK, "Connection not active or transport unavailable");
892 }
893
894 // Use ACIP transport which handles encryption and compression automatically
895 asciichat_error_t result = packet_send_via_transport(g_client_transport, type, data, len);
896
897 mutex_unlock(&g_send_mutex);
898
899 // If send failed due to network error, signal connection loss
900 if (result != ASCIICHAT_OK) {
902 return result;
903 }
904
905 return ASCIICHAT_OK;
906}
907
922int threaded_send_audio_batch_packet(const float *samples, int num_samples, int batch_count) {
923 mutex_lock(&g_send_mutex);
924
925 // Recheck connection status INSIDE the mutex to prevent TOCTOU race
927 if (!atomic_load(&g_connection_active) || sockfd == INVALID_SOCKET_VALUE) {
928 mutex_unlock(&g_send_mutex);
929 return -1;
930 }
931
932 // Get crypto context if encryption is enabled
934 int result = send_audio_batch_packet(sockfd, samples, num_samples, batch_count, (crypto_context_t *)crypto_ctx);
935
936 mutex_unlock(&g_send_mutex);
937
938 // If send failed due to network error, signal connection loss
939 if (result < 0) {
941 }
942
943 return result;
944}
945
960asciichat_error_t threaded_send_audio_opus(const uint8_t *opus_data, size_t opus_size, int sample_rate,
961 int frame_duration) {
962 mutex_lock(&g_send_mutex);
963
964 // Recheck connection status and transport INSIDE the mutex to prevent TOCTOU race
965 if (!atomic_load(&g_connection_active) || !g_client_transport) {
966 mutex_unlock(&g_send_mutex);
967 return SET_ERRNO(ERROR_NETWORK, "Connection not active or transport unavailable");
968 }
969
970 // Build Opus packet with header
971 size_t header_size = 16; // sample_rate (4), frame_duration (4), reserved (8)
972 size_t total_size = header_size + opus_size;
973 void *packet_data = buffer_pool_alloc(NULL, total_size);
974 if (!packet_data) {
975 mutex_unlock(&g_send_mutex);
976 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate buffer for Opus packet: %zu bytes", total_size);
977 }
978
979 // Write header in network byte order
980 uint8_t *buf = (uint8_t *)packet_data;
981 uint32_t sr = HOST_TO_NET_U32((uint32_t)sample_rate);
982 uint32_t fd = HOST_TO_NET_U32((uint32_t)frame_duration);
983 memcpy(buf, &sr, 4);
984 memcpy(buf + 4, &fd, 4);
985 memset(buf + 8, 0, 8); // Reserved
986
987 // Copy Opus data
988 memcpy(buf + header_size, opus_data, opus_size);
989
990 // Send packet via ACIP transport (handles encryption automatically)
991 asciichat_error_t result =
992 packet_send_via_transport(g_client_transport, PACKET_TYPE_AUDIO_OPUS, packet_data, total_size);
993
994 // Clean up
995 buffer_pool_free(NULL, packet_data, total_size);
996 mutex_unlock(&g_send_mutex);
997
998 // If send failed due to network error, signal connection loss
999 if (result != ASCIICHAT_OK) {
1001 return result;
1002 }
1003
1004 return ASCIICHAT_OK;
1005}
1006
1022 const uint16_t *frame_sizes, int frame_count) {
1023 mutex_lock(&g_send_mutex);
1024
1025 // Recheck connection status and transport INSIDE the mutex to prevent TOCTOU race
1026 if (!atomic_load(&g_connection_active) || !g_client_transport) {
1027 mutex_unlock(&g_send_mutex);
1028 return SET_ERRNO(ERROR_NETWORK, "Connection not active or transport unavailable");
1029 }
1030
1031 // Opus uses 20ms frames at 48kHz (960 samples = 20ms)
1032 asciichat_error_t result =
1033 acip_send_audio_opus_batch(g_client_transport, opus_data, opus_size, frame_sizes, frame_count, 48000, 20);
1034
1035 mutex_unlock(&g_send_mutex);
1036
1037 // If send failed due to network error, signal connection loss
1038 if (result != ASCIICHAT_OK) {
1040 return result;
1041 }
1042
1043 return ASCIICHAT_OK;
1044}
1045
1054 // Use threaded_send_packet which handles encryption, mutex locking, and connection state
1055 return threaded_send_packet(PACKET_TYPE_PING, NULL, 0);
1056}
1057
1066 // Use threaded_send_packet which handles encryption, mutex locking, and connection state
1067 return threaded_send_packet(PACKET_TYPE_PONG, NULL, 0);
1068}
1069
1080 if (!atomic_load(&g_connection_active) || sockfd == INVALID_SOCKET_VALUE) {
1081 return -1;
1082 }
1083
1084 // Build STREAM_START packet locally
1085 uint32_t type_data = HOST_TO_NET_U32(stream_type);
1086
1087 // Use threaded_send_packet() which handles encryption
1088 return threaded_send_packet(PACKET_TYPE_STREAM_START, &type_data, sizeof(type_data));
1089}
1090
1104int threaded_send_terminal_size_with_auto_detect(unsigned short width, unsigned short height) {
1105
1107 if (!atomic_load(&g_connection_active) || sockfd == INVALID_SOCKET_VALUE) {
1108 return -1;
1109 }
1110
1111 // Build terminal capabilities packet locally
1112 // Detect terminal capabilities automatically
1114
1115 // Apply user's color mode override
1116 caps = apply_color_mode_override(caps);
1117
1118 // Check if detection was reliable, use fallback only for auto-detection
1119 if (!caps.detection_reliable && GET_OPTION(color_mode) == COLOR_MODE_AUTO) {
1120 log_warn("Terminal capability detection not reliable, using fallback");
1121 SAFE_MEMSET(&caps, sizeof(caps), 0, sizeof(caps));
1123 caps.color_count = 2;
1124 caps.capabilities = 0;
1125 SAFE_STRNCPY(caps.term_type, "unknown", sizeof(caps.term_type));
1126 SAFE_STRNCPY(caps.colorterm, "", sizeof(caps.colorterm));
1127 caps.detection_reliable = 0;
1128 }
1129
1130 // Convert to network packet format with proper byte order
1132 net_packet.capabilities = HOST_TO_NET_U32(caps.capabilities);
1133 net_packet.color_level = HOST_TO_NET_U32(caps.color_level);
1134 net_packet.color_count = HOST_TO_NET_U32(caps.color_count);
1135 net_packet.render_mode = HOST_TO_NET_U32(caps.render_mode);
1136 net_packet.width = HOST_TO_NET_U16(width);
1137 net_packet.height = HOST_TO_NET_U16(height);
1138 net_packet.palette_type = HOST_TO_NET_U32(GET_OPTION(palette_type));
1139 net_packet.utf8_support = HOST_TO_NET_U32(caps.utf8_support ? 1 : 0);
1140
1141 const options_t *opts = options_get();
1142 if (GET_OPTION(palette_type) == PALETTE_CUSTOM && GET_OPTION(palette_custom_set)) {
1143 const char *palette_custom = opts && opts->palette_custom_set ? opts->palette_custom : "";
1144 SAFE_STRNCPY(net_packet.palette_custom, palette_custom, sizeof(net_packet.palette_custom));
1145 net_packet.palette_custom[sizeof(net_packet.palette_custom) - 1] = '\0';
1146 } else {
1147 SAFE_MEMSET(net_packet.palette_custom, sizeof(net_packet.palette_custom), 0, sizeof(net_packet.palette_custom));
1148 }
1149
1150 // Set desired FPS
1151 if (g_max_fps > 0) {
1152 net_packet.desired_fps = (uint8_t)(g_max_fps > 144 ? 144 : g_max_fps);
1153 } else {
1154 net_packet.desired_fps = caps.desired_fps;
1155 }
1156
1157 if (net_packet.desired_fps == 0) {
1158 net_packet.desired_fps = DEFAULT_MAX_FPS;
1159 }
1160
1161 SAFE_STRNCPY(net_packet.term_type, caps.term_type, sizeof(net_packet.term_type));
1162 net_packet.term_type[sizeof(net_packet.term_type) - 1] = '\0';
1163
1164 SAFE_STRNCPY(net_packet.colorterm, caps.colorterm, sizeof(net_packet.colorterm));
1165 net_packet.colorterm[sizeof(net_packet.colorterm) - 1] = '\0';
1166
1167 net_packet.detection_reliable = caps.detection_reliable;
1168 net_packet.utf8_support = GET_OPTION(force_utf8) ? 1 : 0;
1169
1170 SAFE_MEMSET(net_packet.reserved, sizeof(net_packet.reserved), 0, sizeof(net_packet.reserved));
1171
1172 // Use threaded_send_packet() which handles encryption
1173 return threaded_send_packet(PACKET_TYPE_CLIENT_CAPABILITIES, &net_packet, sizeof(net_packet));
1174}
1175
1187int threaded_send_client_join_packet(const char *display_name, uint32_t capabilities) {
1189 if (!atomic_load(&g_connection_active) || sockfd == INVALID_SOCKET_VALUE) {
1190 return -1;
1191 }
1192
1193 // Build CLIENT_JOIN packet locally
1194 client_info_packet_t join_packet;
1195 SAFE_MEMSET(&join_packet, sizeof(join_packet), 0, sizeof(join_packet));
1196 join_packet.client_id = HOST_TO_NET_U32(0); // Will be assigned by server
1197 SAFE_SNPRINTF(join_packet.display_name, MAX_DISPLAY_NAME_LEN, "%s", display_name ? display_name : "Unknown");
1198 join_packet.capabilities = HOST_TO_NET_U32(capabilities);
1199
1200 // Use threaded_send_packet() which handles encryption
1201 int send_result = threaded_send_packet(PACKET_TYPE_CLIENT_JOIN, &join_packet, sizeof(join_packet));
1202 if (send_result == 0) {
1203 mutex_lock(&g_send_mutex);
1204 bool active = atomic_load(&g_connection_active);
1205 socket_t socket_snapshot = g_sockfd;
1206 const crypto_context_t *crypto_ctx = crypto_client_is_ready() ? crypto_client_get_context() : NULL;
1207 if (active && socket_snapshot != INVALID_SOCKET_VALUE) {
1208 (void)log_network_message(
1209 socket_snapshot, (const struct crypto_context_t *)crypto_ctx, LOG_INFO, REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER,
1210 "CLIENT_JOIN sent (display=\"%s\", capabilities=0x%x)", join_packet.display_name, capabilities);
1211 }
1212 mutex_unlock(&g_send_mutex);
1213 }
1214 return send_result;
1215}
πŸ”Œ Cross-platform abstraction layer umbrella header for ascii-chat
πŸ—ƒοΈ Lock-Free Unified Memory Buffer Pool with Lazy Allocation
bool should_exit()
Check if client should exit.
void crypto_handshake_cleanup(crypto_handshake_context_t *ctx)
Cleanup crypto handshake context with secure memory wiping.
Common declarations and data structures for cryptographic handshake.
ascii-chat Client Display Management Interface
πŸ”„ Network byte order conversion helpers
#define HOST_TO_NET_U16(val)
Definition endian.h:101
#define HOST_TO_NET_U32(val)
Definition endian.h:71
#define NET_TO_HOST_U16(val)
Definition endian.h:116
void buffer_pool_free(buffer_pool_t *pool, 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.
int 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.
int threaded_send_stream_start_packet(uint32_t stream_type)
Thread-safe stream start packet transmission.
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_packet(packet_type_t type, const void *data, size_t len)
Thread-safe packet transmission.
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.
void server_connection_lost()
Signal that connection has been lost.
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_init(void)
Initialize client crypto handshake.
int client_crypto_handshake(socket_t socket)
Perform crypto handshake with server.
bool crypto_client_is_ready(void)
Check if crypto handshake is ready.
void display_full_reset()
Perform full display reset.
Definition display.c:301
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:358
#define SAFE_MEMSET(dest, dest_size, ch, count)
Definition common.h:389
#define SAFE_SNPRINTF(buffer, buffer_size,...)
Definition common.h:412
#define FATAL(code,...)
Exit with error code and custom message, with stack trace in debug builds.
Definition common.h:151
unsigned char uint8_t
Definition common.h:56
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:46
@ ERROR_CRYPTO_HANDSHAKE
Definition error_codes.h:91
@ ERROR_NETWORK
Definition error_codes.h:69
@ ERROR_MEMORY
Definition error_codes.h:53
@ ASCIICHAT_OK
Definition error_codes.h:48
#define DEFAULT_MAX_FPS
Default maximum frame rate (frames per second)
Definition limits.h:26
#define MAX_DISPLAY_NAME_LEN
Maximum display name length in characters.
Definition limits.h:20
int g_max_fps
Runtime configurable maximum frame rate (can be overridden via environment or command line)
Definition common.c:30
#define log_warn(...)
Log a WARN message.
#define log_error(...)
Log an ERROR message.
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.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
void log_set_terminal_output(bool enabled)
Control stderr output to terminal.
@ REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER
@ LOG_INFO
Definition log/logging.h:62
uint32_t capabilities
Terminal capabilities bitmask (TERM_CAP_* flags)
Definition packet.h:911
uint32_t color_level
Color level enum value (terminal_color_mode_t)
Definition packet.h:913
uint32_t color_count
Actual color count (16, 256, or 16777216)
Definition packet.h:915
char display_name[MAX_DISPLAY_NAME_LEN]
User display name (null-terminated, max MAX_DISPLAY_NAME_LEN bytes)
Definition packet.h:549
#define CONNECT_TIMEOUT
Connection timeout in seconds (3 seconds)
Definition network.h:98
uint16_t height
Terminal height in characters.
Definition packet.h:921
#define CLIENT_CAP_STRETCH
Client can stretch frames to fill terminal.
Definition packet.h:818
asciichat_error_t send_audio_batch_packet(socket_t sockfd, const float *samples, int num_samples, int batch_count, crypto_context_t *crypto_ctx)
Send a batched audio packet with encryption support.
Definition packet.c:1072
uint8_t reserved[2]
Reserved bytes for alignment (must be zero)
Definition packet.h:937
bool connect_with_timeout(socket_t sockfd, const struct sockaddr *addr, socklen_t addrlen, int timeout_seconds)
Connect with timeout.
Definition network.c:547
#define CLIENT_CAP_COLOR
Client supports color rendering.
Definition packet.h:817
uint32_t render_mode
Render mode enum value (foreground/background/half-block)
Definition packet.h:917
uint16_t width
Terminal width in characters.
Definition packet.h:919
const char * network_error_string()
Get human-readable error string for network errors.
Definition network.c:535
asciichat_error_t socket_configure_buffers(socket_t sockfd)
Configure socket buffers and TCP_NODELAY for optimal performance.
Definition network.c:493
uint8_t desired_fps
Client's desired frame rate (1-144 FPS)
Definition packet.h:935
uint32_t client_id
Unique client identifier (1-9, 0 = server)
Definition packet.h:547
uint8_t detection_reliable
Detection reliability flag (1=reliable detection, 0=best guess)
Definition packet.h:927
char palette_custom[64]
Custom palette characters (if palette_type == PALETTE_CUSTOM)
Definition packet.h:933
char colorterm[32]
$COLORTERM environment variable value (for debugging)
Definition packet.h:925
uint32_t palette_type
Palette type enum value (palette_type_t)
Definition packet.h:931
uint32_t capabilities
Client capabilities bitmask (CLIENT_CAP_VIDEO | CLIENT_CAP_AUDIO | CLIENT_CAP_COLOR | CLIENT_CAP_STRE...
Definition packet.h:552
#define CLIENT_CAP_AUDIO
Client can send/receive audio.
Definition packet.h:816
#define CLIENT_CAP_VIDEO
Client can send/receive video.
Definition packet.h:815
uint32_t utf8_support
UTF-8 support flag (0=no UTF-8, 1=UTF-8 supported)
Definition packet.h:929
char term_type[32]
$TERM environment variable value (for debugging)
Definition packet.h:923
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
Definition options.h:644
#define COLOR_MODE_AUTO
Backward compatibility aliases for color mode enum values.
Definition options.h:156
const options_t * options_get(void)
Get current options (lock-free read)
Definition rcu.c:222
#define COLOR_MODE_NONE
Monochrome mode.
Definition options.h:157
packet_type_t
Network protocol packet type enumeration.
Definition packet.h:281
@ PACKET_TYPE_AUDIO_OPUS
Opus-encoded single audio frame.
Definition packet.h:357
@ PACKET_TYPE_PONG
Keepalive pong response.
Definition packet.h:297
@ PACKET_TYPE_STREAM_START
Client requests to start sending video/audio.
Definition packet.h:304
@ PACKET_TYPE_CLIENT_JOIN
Client announces capability to send media.
Definition packet.h:300
@ PACKET_TYPE_CLIENT_CAPABILITIES
Client reports terminal capabilities.
Definition packet.h:293
@ PACKET_TYPE_PING
Keepalive ping packet.
Definition packet.h:295
@ PALETTE_CUSTOM
User-defined via –palette-chars.
Definition palette.h:96
int socket_shutdown(socket_t sock, int how)
Shutdown socket I/O.
int mutex_init(mutex_t *mutex)
Initialize a mutex.
terminal_capabilities_t apply_color_mode_override(terminal_capabilities_t caps)
Apply command-line overrides to detected capabilities.
int socket_t
Socket handle type (POSIX: int)
Definition socket.h:50
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)
Definition mutex.h:140
pthread_mutex_t mutex_t
Mutex type (POSIX: pthread_mutex_t)
Definition mutex.h:38
#define INVALID_SOCKET_VALUE
Invalid socket value (POSIX: -1)
Definition socket.h:52
int socket_set_keepalive(socket_t sock, bool keepalive)
Set SO_KEEPALIVE socket option.
int socket_close(socket_t sock)
Close a socket.
int socket_get_error(socket_t sock)
Get socket-specific error code.
void platform_sleep_usec(unsigned int usec)
High-precision sleep function with microsecond precision.
terminal_capabilities_t detect_terminal_capabilities(void)
Detect terminal capabilities.
socket_t socket_create(int domain, int type, int protocol)
Create a new socket.
const char * platform_get_username(void)
Get the current username.
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
Definition mutex.h:175
int mutex_destroy(mutex_t *mutex)
Destroy a mutex.
@ TERM_COLOR_NONE
No color support (monochrome terminal)
Definition terminal.h:428
asciichat_error_t format_ip_address(int family, const struct sockaddr *addr, char *output, size_t output_size)
Format IP address from socket address structure.
Definition ip.c:205
#define close_socket
🌍 IP Address Parsing and Formatting Utilities
🌐 Core network I/O operations with timeout support
βš™οΈ Command-line options parsing and configuration management for ascii-chat
Packet protocol implementation with encryption and compression support.
ASCII Palette Management for Video-to-ASCII Conversion.
asciichat_error_t packet_send_via_transport(acip_transport_t *transport, packet_type_t type, const void *payload, size_t payload_len)
Send packet via transport with proper header (exported for generic wrappers)
Definition send.c:40
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:130
ACIP shared/bidirectional packet sending functions.
ascii-chat Server Mode Entry Point Header
#define MAX_RECONNECT_DELAY
ascii-chat Client Server Connection Management Interface
@ CONNECTION_ERROR_AUTH_FAILED
Authentication failure (no retry)
Server cryptographic operations and per-client handshake management.
Transport instance structure.
Definition transport.h:169
Client information packet structure.
Definition packet.h:545
Cryptographic context structure.
Cryptographic handshake context structure.
Consolidated options structure.
Definition options.h:439
Terminal capabilities packet structure (Packet Type 5)
Definition packet.h:909
Complete terminal capabilities structure.
Definition terminal.h:485
terminal_color_mode_t color_level
Detected color support level (terminal_color_mode_t)
Definition terminal.h:487
uint8_t desired_fps
Client's desired frame rate (1-144 FPS)
Definition terminal.h:509
render_mode_t render_mode
Preferred rendering mode (render_mode_t)
Definition terminal.h:497
bool utf8_support
True if terminal supports UTF-8 encoding.
Definition terminal.h:493
uint32_t capabilities
Capability flags bitmask (terminal_capability_flags_t)
Definition terminal.h:489
uint32_t color_count
Maximum number of colors (16, 256, or 16777216)
Definition terminal.h:491
char term_type[64]
$TERM environment variable value (for debugging)
Definition terminal.h:499
char colorterm[64]
$COLORTERM environment variable value (for debugging)
Definition terminal.h:501
bool detection_reliable
True if detection is confident (reliable detection)
Definition terminal.h:495
Cross-platform system functions interface for ascii-chat.
πŸ–₯️ Cross-platform terminal interface for ascii-chat
⏱️ High-precision timing utilities using sokol_time.h and uthash
Transport abstraction layer for ACIP protocol.
acip_transport_t * acip_tcp_transport_create(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.
Common SIMD utilities and structures.