ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
Connection Management

🔗 Server connection establishment, packet transmission, and state tracking More...

Files

file  server.c
 🌐 Client connection manager: TCP connection, reconnection with exponential backoff, and thread-safe transmission
 
file  server.h
 ascii-chat Client Server Connection Management Interface
 

Functions

int server_connection_init ()
 Initialize the server connection management subsystem.
 
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.
 
bool server_connection_is_active ()
 Check if server connection is currently active.
 
socket_t server_connection_get_socket ()
 Get current socket file descriptor.
 
acip_transport_t * server_connection_get_transport (void)
 Get ACIP transport instance.
 
void server_connection_set_transport (acip_transport_t *transport)
 Set ACIP transport instance from connection fallback.
 
uint32_t server_connection_get_client_id ()
 Get client ID assigned by server.
 
const char * server_connection_get_ip ()
 Get resolved server IP address.
 
void server_connection_set_ip (const char *ip)
 Set the server IP address.
 
void server_connection_close ()
 Close the server connection gracefully.
 
void server_connection_shutdown ()
 Emergency connection shutdown for signal handlers.
 
void server_connection_lost ()
 Signal that connection has been lost.
 
bool server_connection_is_lost ()
 Check if connection loss has been detected.
 
void server_connection_cleanup ()
 Cleanup connection management subsystem.
 
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.
 
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.
 
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.
 
int threaded_send_ping_packet (void)
 Thread-safe ping packet transmission.
 
int threaded_send_pong_packet (void)
 Thread-safe pong packet transmission.
 
asciichat_error_t threaded_send_stream_start_packet (uint32_t stream_type)
 Thread-safe stream start packet 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.
 
void server_connection_set_transport (struct acip_transport *transport)
 Set ACIP transport instance from connection fallback.
 
int server_send_packet (packet_type_t type, const void *data, size_t len)
 Send general packet to server.
 
int server_send_audio (const float *samples, int num_samples)
 Send audio data to server.
 
int server_send_audio_batch (const float *samples, int num_samples, int batch_count)
 Send batched audio data to server.
 
int server_send_terminal_capabilities (unsigned short width, unsigned short height)
 Send terminal capabilities to server.
 
int server_send_ping ()
 Send ping keepalive packet.
 
int server_send_pong ()
 Send pong response packet.
 
int server_send_stream_start (uint32_t stream_type)
 Send stream start notification.
 
int server_send_stream_stop (uint32_t stream_type)
 Send stream stop notification.
 

Variables

crypto_handshake_context_t g_crypto_ctx = {0}
 Per-connection crypto handshake context.
 

Detailed Description

🔗 Server connection establishment, packet transmission, and state tracking

Connection Management

Overview

The connection management subsystem handles TCP socket creation, connection establishment, cryptographic handshake coordination, and thread-safe packet transmission to the server.

Implementation: src/client/server.c, src/client/server.h

Connection State

Global State Variables:

  • g_sockfd: Socket file descriptor
  • g_client_id: Server-assigned client ID
  • g_server_ip: Resolved server IP address
  • g_connection_active: Atomic boolean for connection status
  • g_connection_lost: Atomic boolean for connection loss detection
  • g_send_mutex: Mutex protecting all packet sends

Connection Establishment

Connection Flow

int server_connection_establish(const char *address, int port,
int reconnect_attempt, bool first_connection,
bool has_ever_connected)
{
// 1. Create socket
socket_t sockfd = socket(AF_INET, SOCK_STREAM, 0);
// 2. Resolve address (IPv4/IPv6)
struct addrinfo *result = resolve_address(address, port);
// 3. Connect with timeout
if (connect(sockfd, result->ai_addr, result->ai_addrlen) < 0) {
}
// 4. Perform cryptographic handshake
crypto_handshake_result_t handshake_result =
perform_crypto_handshake_client(sockfd, &crypto_ctx, opt_server_key,
server_ip, opt_client_key);
if (handshake_result != HANDSHAKE_SUCCESS &&
handshake_result != HANDSHAKE_WARNING_NO_CLIENT_AUTH) {
socket_close(sockfd);
return map_handshake_error(handshake_result);
}
// 5. Send client capabilities
send_client_capabilities();
// 6. Update global state
g_sockfd = sockfd;
atomic_store(&g_connection_active, true);
atomic_store(&g_connection_lost, false);
}
@ CONNECTION_ERROR_GENERIC
Generic error (retry allowed)
@ CONNECTION_SUCCESS
Connection established successfully.
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.
int socket_t

Connection Error Codes

Error Types:

  • CONNECTION_SUCCESS (0): Connection established successfully
  • CONNECTION_WARNING_NO_CLIENT_AUTH (1): Connected but server not verifying client
  • CONNECTION_ERROR_GENERIC (-1): Network error (allow retry)
  • CONNECTION_ERROR_AUTH_FAILED (-2): Client authentication failed (no retry)
  • CONNECTION_ERROR_HOST_KEY_FAILED (-3): Server host key verification failed (no retry)

Packet Transmission

Thread-Safe Packet Send

int send_packet_to_server(packet_type_t type, const void *data, size_t data_len,
uint32_t client_id)
{
// Thread-safe send with global mutex
mutex_lock(&g_send_mutex);
// Check connection status
mutex_unlock(&g_send_mutex);
return -1;
}
// Send encrypted packet
int result = send_packet(g_sockfd, &crypto_ctx, type, data, data_len, client_id);
mutex_unlock(&g_send_mutex);
// Detect connection loss on send error
if (result < 0) {
}
return result;
}
bool server_connection_is_active()
Check if server connection is currently active.
void server_connection_lost()
Signal that connection has been lost.
int send_packet(socket_t sockfd, packet_type_t type, const void *data, size_t len)
Send a basic packet without encryption.
Definition packet.c:753

Thread Safety:

  • ALL packet sends MUST go through send_packet_to_server()
  • Global g_send_mutex prevents interleaved packets
  • Socket FD checked under mutex protection
  • Connection loss detected atomically

Connection Loss Detection

Loss Detection Triggers

Connection loss detected by:

  1. Socket write error during send_packet_to_server()
  2. Socket read error during receive_packet() in protocol thread
  3. Keepalive timeout (no pong for 30 seconds)
  4. Decryption failure (corrupted stream)

Loss Handling

{
// Atomic compare-exchange ensures only first detection triggers action
bool expected = true;
if (atomic_compare_exchange_strong(&g_connection_active, &expected, false)) {
// First thread to detect loss
atomic_store(&g_connection_lost, true);
log_warn("Connection lost detected");
}
}

Connection Cleanup

Graceful Close

{
if (g_sockfd != INVALID_SOCKET_VALUE) {
socket_shutdown(g_sockfd, SHUT_RDWR); // Signal shutdown
socket_close(g_sockfd); // Close socket
g_sockfd = INVALID_SOCKET_VALUE;
}
atomic_store(&g_connection_active, false);
g_client_id = 0;
}
void server_connection_close()
Close the server connection gracefully.

Emergency Shutdown

{
// Signal-safe emergency shutdown
if (g_sockfd != INVALID_SOCKET_VALUE) {
socket_close(g_sockfd); // Immediate close (no shutdown)
g_sockfd = INVALID_SOCKET_VALUE;
}
}
void server_connection_shutdown()
Emergency connection shutdown for signal handlers.

Best Practices

DO:

  • Always use send_packet_to_server() for packet transmission
  • Check server_connection_is_active() before operations
  • Use atomic operations for connection state flags
  • Wait for thread exit before reconnection

DON'T:

  • Don't send packets without g_send_mutex
  • Don't close socket while threads may be using it
  • Don't ignore return values from send operations
  • Don't modify g_sockfd directly (use provided functions)
See also
src/client/server.c
src/client/server.h
Client Overview

Function Documentation

◆ server_connection_cleanup()

void server_connection_cleanup ( )

#include <server.c>

Cleanup connection management subsystem.

Closes any active connection and destroys synchronization objects. Called during client shutdown.

Definition at line 949 of file src/client/server.c.

949 {
950 if (!GET_OPTION(quiet)) {
952 }
954 mutex_destroy(&g_send_mutex);
955}
void log_set_terminal_output(bool enabled)
int mutex_destroy(mutex_t *mutex)
Definition threading.c:21

References log_set_terminal_output(), mutex_destroy(), and server_connection_close().

◆ server_connection_close()

void server_connection_close ( )

#include <server.c>

Close the server connection gracefully.

Close server connection gracefully.

Marks connection as inactive and closes the socket. This function is safe to call multiple times and from multiple threads.

Definition at line 854 of file src/client/server.c.

854 {
855 atomic_store(&g_connection_active, false);
856
857 // Destroy ACIP transport before closing socket
858 if (g_client_transport) {
859 acip_transport_destroy(g_client_transport);
860 g_client_transport = NULL;
861 }
862
863 if (g_sockfd != INVALID_SOCKET_VALUE) {
864 close_socket(g_sockfd);
865 g_sockfd = INVALID_SOCKET_VALUE;
866 }
867
868 g_my_client_id = 0;
869
870 // Cleanup crypto context if encryption was enabled
871 if (g_encryption_enabled) {
873 g_encryption_enabled = false;
874 }
875
876 // Turn ON terminal logging when connection is closed (unless it was disabled with --quiet)
877 if (!GET_OPTION(quiet)) {
879 }
880}
void crypto_handshake_destroy(crypto_handshake_context_t *ctx)
crypto_handshake_context_t g_crypto_ctx
Per-connection crypto handshake context.
#define close_socket
void acip_transport_destroy(acip_transport_t *transport)

References acip_transport_destroy(), close_socket, crypto_handshake_destroy(), g_crypto_ctx, and log_set_terminal_output().

Referenced by server_connection_cleanup().

◆ server_connection_establish()

int server_connection_establish ( const char *  address,
int  port,
int  reconnect_attempt,
bool  first_connection,
bool  has_ever_connected 
)

#include <server.c>

Establish connection to ascii-chat server.

Attempts to connect to the specified server with full capability negotiation. Implements reconnection logic with exponential backoff for failed attempts. On successful connection, performs initial handshake including terminal capabilities and client join protocol.

Parameters
addressServer IP address or hostname
portServer port number
reconnect_attemptCurrent reconnection attempt number (0 for first)
first_connectionTrue if this is the initial connection attempt
has_ever_connectedTrue if a connection was ever successfully established
Returns
0 on success, negative on error
Parameters
addressServer IP address or hostname
portServer port number
reconnect_attemptCurrent reconnection attempt (0 for first)
first_connectionTrue if this is the initial connection
has_ever_connectedTrue if a connection was ever successfully established
Returns
0 on success, negative on error

Definition at line 320 of file src/client/server.c.

321 {
322 (void)first_connection; // Currently unused
323 if (!address || port <= 0) {
324 log_error("Invalid address or port parameters");
325 return -1;
326 }
327
328 // Close any existing connection
329 if (g_sockfd != INVALID_SOCKET_VALUE) {
330 close_socket(g_sockfd);
331 g_sockfd = INVALID_SOCKET_VALUE;
332 }
333
334 // Apply reconnection delay if this is a retry
335 if (reconnect_attempt > 0) {
336 unsigned int delay_us = get_reconnect_delay(reconnect_attempt);
337 // Reconnection attempt logged only to file
338 platform_sleep_us(delay_us);
339
340 // Check if user requested exit during reconnection delay
341 if (should_exit()) {
342 log_debug("Exit requested during reconnection delay");
343 return -1;
344 }
345 } else {
346 // Initial connection logged only to file
347 }
348
349 // Check for WebSocket URL - handle separately from TCP
350 if (url_is_websocket(address)) {
351 // WebSocket connection - bypass TCP socket creation
352 // Use the original address (URL already contains port if specified)
353 const char *ws_url = address;
354
355 // Parse for debug logging
356 url_parts_t url_parts = {0};
357 if (url_parse(address, &url_parts) == ASCIICHAT_OK) {
358 log_info("Connecting via WebSocket: %s (scheme=%s, host=%s, port=%d)", ws_url, url_parts.scheme, url_parts.host,
359 url_parts.port);
360 } else {
361 log_info("Connecting via WebSocket: %s", ws_url);
362 }
363
364 // Initialize crypto if encryption is enabled
365 log_debug("CLIENT_CONNECT: Calling client_crypto_init()");
366 if (client_crypto_init() != 0) {
367 log_error("Failed to initialize crypto (password required or incorrect)");
368 log_debug("CLIENT_CONNECT: client_crypto_init() failed");
369 url_parts_destroy(&url_parts);
371 }
372
373 // Get crypto context for transport
374 const crypto_context_t *crypto_ctx = crypto_client_is_ready() ? crypto_client_get_context() : NULL;
375
376 // Create WebSocket transport (handles connection internally)
377 g_client_transport = acip_websocket_client_transport_create(ws_url, (crypto_context_t *)crypto_ctx);
378 if (!g_client_transport) {
379 log_error("Failed to create WebSocket ACIP transport");
380 url_parts_destroy(&url_parts);
381 return -1;
382 }
383 log_debug("CLIENT_CONNECT: Created WebSocket ACIP transport with crypto context");
384
385 // Set connection as active
386 atomic_store(&g_connection_active, true);
387 atomic_store(&g_connection_lost, false);
388
389 // Send initial terminal capabilities to server
390 int result = threaded_send_terminal_size_with_auto_detect(GET_OPTION(width), GET_OPTION(height));
391 if (result < 0) {
392 log_error("Failed to send initial capabilities to server: %s", network_error_string());
393 acip_transport_destroy(g_client_transport);
394 g_client_transport = NULL;
395 url_parts_destroy(&url_parts);
396 return -1;
397 }
398
399 // Disable terminal logging after initial setup (for non-snapshot mode)
400 if (!GET_OPTION(snapshot_mode) && has_ever_connected) {
402 }
403
404 // Build capabilities flags
405 uint32_t my_capabilities = CLIENT_CAP_VIDEO;
406 log_debug("GET_OPTION(audio_enabled) = %d (sending CLIENT_JOIN)", GET_OPTION(audio_enabled));
407 if (GET_OPTION(audio_enabled)) {
408 log_debug("Adding CLIENT_CAP_AUDIO to capabilities");
409 my_capabilities |= CLIENT_CAP_AUDIO;
410 }
411 if (GET_OPTION(color_mode) != COLOR_MODE_NONE) {
412 my_capabilities |= CLIENT_CAP_COLOR;
413 }
414 if (GET_OPTION(stretch)) {
415 my_capabilities |= CLIENT_CAP_STRETCH;
416 }
417
418 // Generate display name from username + PID
419 const char *display_name = platform_get_username();
420 char my_display_name[MAX_DISPLAY_NAME_LEN];
421 int pid = getpid();
422 SAFE_SNPRINTF(my_display_name, sizeof(my_display_name), "%s-%d", display_name, pid);
423 if (threaded_send_client_join_packet(my_display_name, my_capabilities) < 0) {
424 log_error("Failed to send client join packet: %s", network_error_string());
425 acip_transport_destroy(g_client_transport);
426 g_client_transport = NULL;
427 url_parts_destroy(&url_parts);
428 return -1;
429 }
430
431 log_info("WebSocket connection established successfully");
432 url_parts_destroy(&url_parts);
433 return 0;
434 }
435
436 // Resolve server address using getaddrinfo() for IPv4/IPv6 support
437 // Special handling for localhost: ensure we try both IPv6 (::1) and IPv4 (127.0.0.1)
438 // Many systems only map "localhost" to 127.0.0.1 in /etc/hosts
439 bool is_localhost = (strcmp(address, "localhost") == 0 || is_localhost_ipv4(address) || is_localhost_ipv6(address));
440
441 struct addrinfo hints, *res = NULL, *addr_iter;
442 memset(&hints, 0, sizeof(hints));
443 hints.ai_family = AF_UNSPEC; // Allow IPv4 or IPv6
444 hints.ai_socktype = SOCK_STREAM;
445 if (is_localhost) {
446 hints.ai_flags = AI_NUMERICSERV; // Optimize for localhost
447 }
448
449 char port_str[16];
450 SAFE_SNPRINTF(port_str, sizeof(port_str), "%d", port);
451
452 // For localhost, try IPv6 loopback (::1) first, then fall back to DNS resolution
453 if (is_localhost) {
454 log_debug("Localhost detected - trying IPv6 loopback [::1]:%s first...", port_str);
455 hints.ai_family = AF_INET6;
456 hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
457
458 int ipv6_result = getaddrinfo("::1", port_str, &hints, &res);
459 if (ipv6_result == 0 && res != NULL) {
460 // Try IPv6 loopback connection
461 g_sockfd = socket_create(res->ai_family, res->ai_socktype, res->ai_protocol);
462 if (g_sockfd != INVALID_SOCKET_VALUE) {
463 log_debug("Trying IPv6 loopback connection to [::1]:%s...", port_str);
464 if (connect_with_timeout(g_sockfd, res->ai_addr, res->ai_addrlen, CONNECT_TIMEOUT)) {
465 log_debug("Connection successful using IPv6 loopback");
466 SAFE_STRNCPY(g_server_ip, "::1", sizeof(g_server_ip));
467 freeaddrinfo(res);
468 res = NULL; // Prevent double-free at connection_success label
469 goto connection_success;
470 }
471 if (socket_get_error(g_sockfd) != 0) {
472 log_debug("NETWORK_ERROR: %d", (int)socket_get_error(g_sockfd));
473 } else {
474 // log_debug("IPv6 loopback connection failed: %s", network_error_string());
475 }
476 close_socket(g_sockfd);
477 g_sockfd = INVALID_SOCKET_VALUE;
478 }
479 freeaddrinfo(res);
480 res = NULL;
481 }
482
483 // Check if user requested exit (Ctrl-C) before trying IPv4
484 if (should_exit()) {
485 log_debug("Exit requested during connection attempt");
486 return -1;
487 }
488
489 // IPv6 failed, try IPv4 loopback (127.0.0.1)
490 log_debug("IPv6 failed, trying IPv4 loopback 127.0.0.1:%s...", port_str);
491 hints.ai_family = AF_INET;
492
493 int ipv4_result = getaddrinfo("127.0.0.1", port_str, &hints, &res);
494 if (ipv4_result == 0 && res != NULL) {
495 g_sockfd = socket_create(res->ai_family, res->ai_socktype, res->ai_protocol);
496 if (g_sockfd != INVALID_SOCKET_VALUE) {
497 log_debug("Trying IPv4 loopback connection to 127.0.0.1:%s...", port_str);
498 if (connect_with_timeout(g_sockfd, res->ai_addr, res->ai_addrlen, CONNECT_TIMEOUT)) {
499 log_debug("Connection successful using IPv4 loopback");
500 SAFE_STRNCPY(g_server_ip, "127.0.0.1", sizeof(g_server_ip));
501 freeaddrinfo(res);
502 res = NULL; // Prevent double-free at connection_success label
503 goto connection_success;
504 }
505 if (socket_get_error(g_sockfd) != 0) {
506 log_debug("NETWORK_ERROR: %d", (int)socket_get_error(g_sockfd));
507 } else {
508 // log_debug("IPv4 loopback connection failed: %s", network_error_string());
509 }
510 close_socket(g_sockfd);
511 g_sockfd = INVALID_SOCKET_VALUE;
512 }
513 freeaddrinfo(res);
514 res = NULL;
515 }
516
517 // Both IPv6 and IPv4 loopback failed for localhost
518 log_warn("Could not connect to localhost using either IPv6 or IPv4 loopback");
519 return -1;
520 }
521
522 // For non-localhost addresses, use standard resolution
523 log_debug("Resolving server address '%s' port %s...", address, port_str);
524 hints.ai_family = AF_UNSPEC;
525 hints.ai_flags = 0;
526 int getaddr_result = getaddrinfo(address, port_str, &hints, &res);
527 if (getaddr_result != 0) {
528 log_error("Failed to resolve server address '%s': %s", address, gai_strerror(getaddr_result));
529 return -1;
530 }
531
532 // Try each address returned by getaddrinfo() until one succeeds
533 // Prefer IPv6 over IPv4: try IPv6 addresses first, then fall back to IPv4
534 for (int address_family = AF_INET6; address_family >= AF_INET; address_family -= (AF_INET6 - AF_INET)) {
535 for (addr_iter = res; addr_iter != NULL; addr_iter = addr_iter->ai_next) {
536 // Skip addresses that don't match current pass (IPv6 first, then IPv4)
537 if (addr_iter->ai_family != address_family) {
538 continue;
539 }
540
541 // Create socket with appropriate address family
542 g_sockfd = socket_create(addr_iter->ai_family, addr_iter->ai_socktype, addr_iter->ai_protocol);
543 if (g_sockfd == INVALID_SOCKET_VALUE) {
544 log_debug("Could not create socket for address family %d: %s", addr_iter->ai_family, network_error_string());
545 continue; // Try next address
546 }
547
548 // Log which address family we're trying
549 if (addr_iter->ai_family == AF_INET) {
550 log_debug("Trying IPv4 connection...");
551 } else if (addr_iter->ai_family == AF_INET6) {
552 log_debug("Trying IPv6 connection...");
553 }
554
555 // Attempt connection with timeout
556 if (connect_with_timeout(g_sockfd, addr_iter->ai_addr, addr_iter->ai_addrlen, CONNECT_TIMEOUT)) {
557 // Connection successful!
558 log_debug("Connection successful using %s", addr_iter->ai_family == AF_INET ? "IPv4"
559 : addr_iter->ai_family == AF_INET6 ? "IPv6"
560 : "unknown protocol");
561
562 // Extract server IP address for known_hosts
563 if (format_ip_address(addr_iter->ai_family, addr_iter->ai_addr, g_server_ip, sizeof(g_server_ip)) ==
564 ASCIICHAT_OK) {
565 log_debug("Resolved server IP: %s", g_server_ip);
566 } else {
567 log_warn("Failed to format server IP address");
568 }
569
570 goto connection_success; // Break out of both loops
571 }
572
573 // Connection failed - close socket and try next address
574 if (socket_get_error(g_sockfd) != 0) {
575 log_debug("NETWORK_ERROR: %d", (int)socket_get_error(g_sockfd));
576 } else {
577 // log_debug("Connection failed: %s", network_error_string());
578 }
579 close_socket(g_sockfd);
580 g_sockfd = INVALID_SOCKET_VALUE;
581 }
582 }
583
584connection_success:
585
586 if (res) {
587 freeaddrinfo(res);
588 }
589
590 // If we exhausted all addresses without success, fail
591 if (g_sockfd == INVALID_SOCKET_VALUE) {
592 log_warn("Could not connect to server %s:%d (tried all addresses)", address, port);
593 return -1;
594 }
595
596 // Connection successful - extract local port for client ID
597 struct sockaddr_storage local_addr = {0};
598 socklen_t addr_len = sizeof(local_addr);
599 if (getsockname(g_sockfd, (struct sockaddr *)&local_addr, &addr_len) == -1) {
600 log_error("Failed to get local socket address: %s", network_error_string());
601 close_socket(g_sockfd);
602 g_sockfd = INVALID_SOCKET_VALUE;
603 return -1;
604 }
605
606 // Extract port from either IPv4 or IPv6 address
607 int local_port = 0;
608 if (((struct sockaddr *)&local_addr)->sa_family == AF_INET) {
609 local_port = NET_TO_HOST_U16(((struct sockaddr_in *)&local_addr)->sin_port);
610 } else if (((struct sockaddr *)&local_addr)->sa_family == AF_INET6) {
611 local_port = NET_TO_HOST_U16(((struct sockaddr_in6 *)&local_addr)->sin6_port);
612 }
613 g_my_client_id = (uint32_t)local_port;
614
615 // Mark connection as active immediately after successful socket connection
616 atomic_store(&g_connection_active, true);
617 atomic_store(&g_connection_lost, false);
618 atomic_store(&g_should_reconnect, false);
619
620 // Initialize crypto BEFORE starting protocol handshake
621 // Note: server IP is already set above in the connection loop
622 log_debug("CLIENT_CONNECT: Calling client_crypto_init()");
623 if (client_crypto_init() != 0) {
624 log_error("Failed to initialize crypto (password required or incorrect)");
625 log_debug("CLIENT_CONNECT: client_crypto_init() failed");
626 close_socket(g_sockfd);
627 g_sockfd = INVALID_SOCKET_VALUE;
628 return CONNECTION_ERROR_AUTH_FAILED; // SSH key password was wrong - no retry
629 }
630
631 // Perform crypto handshake if encryption is enabled
632 log_debug("CLIENT_CONNECT: Calling client_crypto_handshake()");
633 int handshake_result = client_crypto_handshake(g_sockfd);
634 if (handshake_result != 0) {
635 log_error("Crypto handshake failed");
636 log_debug("CLIENT_CONNECT: client_crypto_handshake() failed with code %d", handshake_result);
637 close_socket(g_sockfd);
638 g_sockfd = INVALID_SOCKET_VALUE;
639 FATAL(ERROR_CRYPTO_HANDSHAKE,
640 "Crypto handshake failed with server - this usually indicates a protocol mismatch or network issue");
641 }
642 log_debug("CLIENT_CONNECT: client_crypto_handshake() succeeded");
643
644 // Create ACIP transport for protocol-agnostic packet sending
645 // The transport wraps the socket with encryption context from the handshake
646 const crypto_context_t *crypto_ctx = crypto_client_is_ready() ? crypto_client_get_context() : NULL;
647
648 // Create TCP transport (WebSocket is handled earlier in the function)
649 g_client_transport = acip_tcp_transport_create(g_sockfd, (crypto_context_t *)crypto_ctx);
650 if (!g_client_transport) {
651 log_error("Failed to create TCP ACIP transport");
652 close_socket(g_sockfd);
653 g_sockfd = INVALID_SOCKET_VALUE;
654 return -1;
655 }
656 log_debug("CLIENT_CONNECT: Created TCP ACIP transport with crypto context");
657
658 // Turn OFF terminal logging when successfully connected to server
659 // First connection - we'll disable logging after main.c shows the "Connected successfully" message
660 if (!GET_OPTION(snapshot_mode)) {
661 log_debug("Connected to server - terminal logging will be disabled after initial setup");
662 } else {
663 log_debug("Connected to server - terminal logging kept enabled for snapshot mode");
664 }
665
666 // Configure socket options for optimal performance
667 if (socket_set_keepalive(g_sockfd, true) < 0) {
668 log_warn("Failed to set socket keepalive: %s", network_error_string());
669 }
670
671 // Configure socket buffers and TCP_NODELAY for optimal performance
672 asciichat_error_t sock_config_result = socket_configure_buffers(g_sockfd);
673 if (sock_config_result != ASCIICHAT_OK) {
674 log_warn("Failed to configure socket: %s", network_error_string());
675 }
676
677 // Send initial terminal capabilities to server (this may generate debug logs)
678 int result = threaded_send_terminal_size_with_auto_detect(GET_OPTION(width), GET_OPTION(height));
679 if (result < 0) {
680 log_error("Failed to send initial capabilities to server: %s", network_error_string());
681 close_socket(g_sockfd);
682 g_sockfd = INVALID_SOCKET_VALUE;
683 return -1;
684 }
685
686 // Now disable terminal logging after capabilities are sent (for reconnections)
687 if (!GET_OPTION(snapshot_mode) && has_ever_connected) {
689 log_debug("Reconnected to server - terminal logging disabled to prevent interference with ASCII display");
690 }
691
692 // Send client join packet for multi-user support
693 uint32_t my_capabilities = CLIENT_CAP_VIDEO; // Basic video capability
694 log_debug("GET_OPTION(audio_enabled) = %d (sending CLIENT_JOIN)", GET_OPTION(audio_enabled));
695 if (GET_OPTION(audio_enabled)) {
696 log_debug("Adding CLIENT_CAP_AUDIO to capabilities");
697 my_capabilities |= CLIENT_CAP_AUDIO;
698 }
699 if (GET_OPTION(color_mode) != COLOR_MODE_NONE) {
700 my_capabilities |= CLIENT_CAP_COLOR;
701 }
702 if (GET_OPTION(stretch)) {
703 my_capabilities |= CLIENT_CAP_STRETCH;
704 }
705
706 // Generate display name from username + PID
707 const char *display_name = platform_get_username();
708
709 char my_display_name[MAX_DISPLAY_NAME_LEN];
710 int pid = getpid();
711 SAFE_SNPRINTF(my_display_name, sizeof(my_display_name), "%s-%d", display_name, pid);
712
713 if (threaded_send_client_join_packet(my_display_name, my_capabilities) < 0) {
714 log_error("Failed to send client join packet: %s", network_error_string());
715 close_socket(g_sockfd);
716 g_sockfd = INVALID_SOCKET_VALUE;
717 return -1;
718 }
719
720 // Connection already marked as active after socket creation
721
722 return 0;
723}
@ CONNECTION_ERROR_AUTH_FAILED
Authentication failure (no retry)
bool should_exit(void)
Definition main.c:90
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.
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.
int is_localhost_ipv6(const char *ip)
Definition ip.c:1320
int is_localhost_ipv4(const char *ip)
Definition ip.c:1299
asciichat_error_t format_ip_address(int family, const struct sockaddr *addr, char *output, size_t output_size)
Definition ip.c:196
bool connect_with_timeout(socket_t sockfd, const struct sockaddr *addr, socklen_t addrlen, int timeout_seconds)
Connect with timeout.
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.
void platform_sleep_us(unsigned int us)
acip_transport_t * acip_tcp_transport_create(socket_t sockfd, crypto_context_t *crypto_ctx)
void url_parts_destroy(url_parts_t *parts)
Definition url.c:281
bool url_is_websocket(const char *url)
Definition url.c:307
asciichat_error_t url_parse(const char *url, url_parts_t *parts_out)
Definition url.c:166
acip_transport_t * acip_websocket_client_transport_create(const char *url, crypto_context_t *crypto_ctx)
Create WebSocket client transport.

References acip_tcp_transport_create(), acip_transport_destroy(), acip_websocket_client_transport_create(), client_crypto_handshake(), client_crypto_init(), close_socket, connect_with_timeout(), CONNECTION_ERROR_AUTH_FAILED, crypto_client_get_context(), crypto_client_is_ready(), format_ip_address(), is_localhost_ipv4(), is_localhost_ipv6(), log_set_terminal_output(), network_error_string(), platform_sleep_us(), should_exit(), socket_configure_buffers(), threaded_send_client_join_packet(), threaded_send_terminal_size_with_auto_detect(), url_is_websocket(), url_parse(), and url_parts_destroy().

◆ server_connection_get_client_id()

uint32_t server_connection_get_client_id ( )

#include <server.c>

Get client ID assigned by server.

Returns
Client ID (based on local port) or 0 if not connected
Client ID or 0 if not connected

Definition at line 808 of file src/client/server.c.

808 {
809 return g_my_client_id;
810}

◆ server_connection_get_ip()

const char * server_connection_get_ip ( )

#include <server.c>

Get resolved server IP address.

Returns the server's IP address that was resolved during connection establishment. Used for known_hosts verification and logging.

Returns
Server IP address string (IPv4 or IPv6), or empty string if not connected
Server IP address string (IPv4 or IPv6)

Definition at line 822 of file src/client/server.c.

822 {
823 return g_server_ip;
824}

Referenced by client_crypto_init().

◆ server_connection_get_socket()

socket_t server_connection_get_socket ( )

#include <server.c>

Get current socket file descriptor.

Returns
Socket file descriptor or INVALID_SOCKET_VALUE if disconnected
Socket FD or INVALID_SOCKET_VALUE if disconnected

Definition at line 745 of file src/client/server.c.

745 {
746 return g_sockfd;
747}

Referenced by crypto_client_initiate_rekey(), crypto_client_send_rekey_complete(), and crypto_client_send_rekey_response().

◆ server_connection_get_transport()

struct acip_transport * server_connection_get_transport ( void  )

#include <server.c>

Get ACIP transport instance.

Returns
Transport instance or NULL if not connected

Definition at line 756 of file src/client/server.c.

756 {
757 return g_client_transport;
758}

◆ server_connection_init()

int server_connection_init ( )

#include <server.c>

Initialize the server connection management subsystem.

Initialize server connection management subsystem.

Sets up the send mutex and initializes connection state variables. Must be called once during client startup before any connection attempts.

Returns
0 on success, non-zero on failure
0 on success, negative on error

Definition at line 285 of file src/client/server.c.

285 {
286 // Initialize mutex for thread-safe packet sending
287 if (mutex_init(&g_send_mutex) != 0) {
288 log_error("Failed to initialize send mutex");
289 return -1;
290 }
291
292 // Initialize connection state
293 g_sockfd = INVALID_SOCKET_VALUE;
294 g_client_transport = NULL;
295 atomic_store(&g_connection_active, false);
296 atomic_store(&g_connection_lost, false);
297 atomic_store(&g_should_reconnect, false);
298 g_my_client_id = 0;
299
300 return 0;
301}
int mutex_init(mutex_t *mutex)
Definition threading.c:16

References mutex_init().

◆ server_connection_is_active()

bool server_connection_is_active ( )

#include <server.c>

Check if server connection is currently active.

Returns
true if connection is active, false otherwise

Definition at line 732 of file src/client/server.c.

732 {
733 // For TCP: check socket validity
734 // For WebRTC: socket is INVALID_SOCKET_VALUE but transport exists
735 return atomic_load(&g_connection_active) && (g_sockfd != INVALID_SOCKET_VALUE || g_client_transport != NULL);
736}

◆ server_connection_is_lost()

bool server_connection_is_lost ( )

#include <server.c>

Check if connection loss has been detected.

Check if connection loss was detected.

Returns
true if connection loss was flagged, false otherwise
true if connection lost, false otherwise

Definition at line 937 of file src/client/server.c.

937 {
938 return atomic_load(&g_connection_lost);
939}

Referenced by protocol_connection_lost().

◆ server_connection_lost()

void server_connection_lost ( )

#include <server.c>

Signal that connection has been lost.

Called by other modules (typically protocol handlers) when they detect connection failure. Triggers reconnection logic in main loop.

Definition at line 921 of file src/client/server.c.

921 {
922 atomic_store(&g_connection_lost, true);
923 atomic_store(&g_connection_active, false);
924
925 // Don't re-enable terminal logging here - let the splash screen handle it
926 // The reconnection splash will capture and display logs properly
928}
void display_full_reset()
Perform full display reset.

References display_full_reset().

Referenced by threaded_send_audio_batch_packet(), threaded_send_audio_opus(), threaded_send_audio_opus_batch(), and threaded_send_packet().

◆ server_connection_set_ip()

void server_connection_set_ip ( const char *  ip)

#include <server.c>

Set the server IP address.

Updates the global server IP address. Used by new connection code paths that don't use the legacy server_connect() function.

Parameters
ipServer IP address string
ipServer IP address string

Definition at line 836 of file src/client/server.c.

836 {
837 if (ip) {
838 SAFE_STRNCPY(g_server_ip, ip, sizeof(g_server_ip));
839 log_debug("Server IP set to: %s", g_server_ip);
840 } else {
841 g_server_ip[0] = '\0';
842 log_debug("Server IP cleared");
843 }
844}

Referenced by connection_attempt_tcp().

◆ server_connection_set_transport() [1/2]

void server_connection_set_transport ( acip_transport_t *  transport)

#include <server.c>

Set ACIP transport instance from connection fallback.

Used to integrate the transport from the 3-stage connection fallback orchestrator (TCP → STUN → TURN) into the server connection management layer.

Parameters
transportTransport instance created by connection_attempt_with_fallback()

Definition at line 770 of file src/client/server.c.

770 {
771 log_debug("server_connection_set_transport() called with transport=%p", (void *)transport);
772
773 // Clean up any existing transport
774 if (g_client_transport) {
775 log_warn("Replacing existing transport with new fallback transport");
776 acip_transport_destroy(g_client_transport);
777 }
778
779 log_debug("Setting g_client_transport to %p", (void *)transport);
780 g_client_transport = transport;
781
782 // Mark connection as active when transport is set
783 if (transport) {
784 log_debug("Transport is non-NULL, extracting socket...");
785 // Extract socket from transport for backward compatibility with socket-based checks
786 g_sockfd = acip_transport_get_socket(transport);
787 log_debug("Socket extracted: %d", (int)g_sockfd);
788
789 atomic_store(&g_connection_active, true);
790 atomic_store(&g_connection_lost, false); // Reset lost flag for new connection
791 log_debug("Server connection transport set and marked active (sockfd=%d)", (int)g_sockfd);
792 } else {
793 g_sockfd = INVALID_SOCKET_VALUE;
794 atomic_store(&g_connection_active, false);
795 log_debug("Server connection transport cleared and marked inactive");
796 }
797
798 log_debug("server_connection_set_transport() completed");
799}

References acip_transport_destroy().

◆ server_connection_set_transport() [2/2]

void server_connection_set_transport ( struct acip_transport *  transport)

#include <server.h>

Set ACIP transport instance from connection fallback.

Parameters
transportTransport instance created by connection_attempt_with_fallback()

Used to integrate the transport from the 3-stage connection fallback orchestrator (TCP → STUN → TURN) into the server connection management layer.

◆ server_connection_shutdown()

void server_connection_shutdown ( )

#include <server.c>

Emergency connection shutdown for signal handlers.

Emergency shutdown for signal handlers.

Performs immediate connection shutdown without waiting for graceful close procedures. Uses socket shutdown to interrupt any blocking recv() operations in other threads.

Definition at line 891 of file src/client/server.c.

891 {
892 // NOTE: This function may be called from:
893 // - Signal handlers on Unix (async-signal-safe context)
894 // - SetConsoleCtrlHandler callback thread on Windows (separate thread context)
895 // Only use atomic operations and simple system calls - NO mutex locks, NO malloc, NO logging.
896
897 atomic_store(&g_connection_active, false);
898 atomic_store(&g_connection_lost, true);
899
900 if (g_sockfd != INVALID_SOCKET_VALUE) {
901 // Only shutdown() the socket to interrupt blocking recv()/send() operations.
902 // Do NOT close() here - on Windows, closing the socket while another thread
903 // is using it is undefined behavior and can cause STATUS_STACK_BUFFER_OVERRUN.
904 // The actual socket close happens in server_connection_close() which is called
905 // from the main thread after worker threads have been joined.
906 socket_shutdown(g_sockfd, SHUT_RDWR);
907 }
908
909 // DO NOT call log_set_terminal_output() here - it uses mutex which is NOT async-signal-safe.
910 // The normal cleanup path in shutdown_client() will handle logging state.
911}

Referenced by client_main(), and protocol_stop_connection().

◆ server_send_audio()

int server_send_audio ( const float *  samples,
int  num_samples 
)

#include <server.h>

Send audio data to server.

Parameters
samplesAudio sample buffer
num_samplesNumber of samples
Returns
0 on success, negative on error

◆ server_send_audio_batch()

int server_send_audio_batch ( const float *  samples,
int  num_samples,
int  batch_count 
)

#include <server.h>

Send batched audio data to server.

Parameters
samplesBatched audio samples
num_samplesTotal sample count
batch_countNumber of packets in batch
Returns
0 on success, negative on error

◆ server_send_packet()

int server_send_packet ( packet_type_t  type,
const void *  data,
size_t  len 
)

#include <server.h>

Send general packet to server.

Parameters
typePacket type identifier
dataPacket payload
lenPayload length
Returns
0 on success, negative on error

◆ server_send_ping()

int server_send_ping ( )

#include <server.h>

Send ping keepalive packet.

Returns
0 on success, negative on error

◆ server_send_pong()

int server_send_pong ( )

#include <server.h>

Send pong response packet.

Returns
0 on success, negative on error

◆ server_send_stream_start()

int server_send_stream_start ( uint32_t  stream_type)

#include <server.h>

Send stream start notification.

Parameters
stream_typeType of stream (audio/video)
Returns
0 on success, negative on error

◆ server_send_stream_stop()

int server_send_stream_stop ( uint32_t  stream_type)

#include <server.h>

Send stream stop notification.

Parameters
stream_typeType of stream (audio/video)
Returns
0 on success, negative on error

◆ server_send_terminal_capabilities()

int server_send_terminal_capabilities ( unsigned short  width,
unsigned short  height 
)

#include <server.h>

Send terminal capabilities to server.

Parameters
widthTerminal width in characters
heightTerminal height in characters
Returns
0 on success, negative on error

◆ threaded_send_audio_batch_packet()

int threaded_send_audio_batch_packet ( const float *  samples,
int  num_samples,
int  batch_count 
)

#include <server.c>

Thread-safe batched audio packet transmission.

Sends a batched audio packet to the server with proper mutex protection and connection state checking. Automatically handles encryption if crypto is ready.

Parameters
samplesAudio sample buffer containing batched samples
num_samplesTotal number of samples in the batch
batch_countNumber of audio chunks in this batch
Returns
0 on success, negative on error
Parameters
samplesAudio sample buffer containing batched samples
num_samplesTotal number of samples in the batch
batch_countNumber of audio chunks in this batch
Returns
0 on success, negative on error

Definition at line 1018 of file src/client/server.c.

1018 {
1019 // Get transport reference while holding mutex (brief lock)
1020 mutex_lock(&g_send_mutex);
1021
1022 // Check connection status and get transport reference
1023 if (!atomic_load(&g_connection_active) || !g_client_transport) {
1024 mutex_unlock(&g_send_mutex);
1025 return -1;
1026 }
1027
1028 // Get transport reference - transport has its own internal synchronization
1029 acip_transport_t *transport = g_client_transport;
1030 mutex_unlock(&g_send_mutex);
1031
1032 // Network I/O happens OUTSIDE the mutex to prevent deadlock on TCP buffer full
1033 asciichat_error_t result = acip_send_audio_batch(transport, samples, (uint32_t)num_samples, (uint32_t)batch_count);
1034
1035 // If send failed due to network error, signal connection loss
1036 if (result != ASCIICHAT_OK) {
1038 return -1;
1039 }
1040
1041 return 0;
1042}
asciichat_error_t acip_send_audio_batch(acip_transport_t *transport, const float *samples, uint32_t num_samples, uint32_t batch_count)
Definition send.c:115

References acip_send_audio_batch(), and server_connection_lost().

◆ threaded_send_audio_opus()

asciichat_error_t threaded_send_audio_opus ( const uint8_t *  opus_data,
size_t  opus_size,
int  sample_rate,
int  frame_duration 
)

#include <server.c>

Thread-safe Opus audio frame transmission.

Sends a single Opus-encoded audio frame to the server with proper synchronization and encryption support.

Parameters
opus_dataOpus-encoded audio data
opus_sizeSize of encoded frame
sample_rateSample rate in Hz
frame_durationFrame duration in milliseconds
Returns
0 on success, negative on error

Sends a single Opus-encoded audio frame to the server with proper synchronization and encryption support.

Parameters
opus_dataOpus-encoded audio data
opus_sizeSize of encoded frame
sample_rateSample rate in Hz
frame_durationFrame duration in milliseconds
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 1058 of file src/client/server.c.

1059 {
1060 // Get transport reference while holding mutex (brief lock)
1061 mutex_lock(&g_send_mutex);
1062
1063 // Check connection status and get transport reference
1064 if (!atomic_load(&g_connection_active) || !g_client_transport) {
1065 mutex_unlock(&g_send_mutex);
1066 return SET_ERRNO(ERROR_NETWORK, "Connection not active or transport unavailable");
1067 }
1068
1069 // Get transport reference - transport has its own internal synchronization
1070 acip_transport_t *transport = g_client_transport;
1071 mutex_unlock(&g_send_mutex);
1072
1073 // Build Opus packet with header (outside mutex - no blocking I/O yet)
1074 size_t header_size = 16; // sample_rate (4), frame_duration (4), reserved (8)
1075 size_t total_size = header_size + opus_size;
1076 void *packet_data = buffer_pool_alloc(NULL, total_size);
1077 if (!packet_data) {
1078 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate buffer for Opus packet: %zu bytes", total_size);
1079 }
1080
1081 // Write header in network byte order
1082 uint8_t *buf = (uint8_t *)packet_data;
1083 uint32_t sr = HOST_TO_NET_U32((uint32_t)sample_rate);
1084 uint32_t fd = HOST_TO_NET_U32((uint32_t)frame_duration);
1085 memcpy(buf, &sr, 4);
1086 memcpy(buf + 4, &fd, 4);
1087 memset(buf + 8, 0, 8); // Reserved
1088
1089 // Copy Opus data
1090 memcpy(buf + header_size, opus_data, opus_size);
1091
1092 // Network I/O happens OUTSIDE the mutex to prevent deadlock on TCP buffer full
1093 asciichat_error_t result =
1094 packet_send_via_transport(transport, PACKET_TYPE_AUDIO_OPUS_BATCH, packet_data, total_size, 0);
1095
1096 // Clean up
1097 buffer_pool_free(NULL, packet_data, total_size);
1098
1099 // If send failed due to network error, signal connection loss
1100 if (result != ASCIICHAT_OK) {
1102 return result;
1103 }
1104
1105 return ASCIICHAT_OK;
1106}
void buffer_pool_free(buffer_pool_t *pool, void *data, size_t size)
void * buffer_pool_alloc(buffer_pool_t *pool, size_t size)
Definition buffer_pool.c:99
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

References buffer_pool_alloc(), buffer_pool_free(), packet_send_via_transport(), and server_connection_lost().

◆ threaded_send_audio_opus_batch()

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 
)

#include <server.c>

Thread-safe Opus audio batch packet transmission.

Sends a batch of Opus-encoded audio frames to the server with proper synchronization and encryption support.

Parameters
opus_dataOpus-encoded audio data (multiple frames concatenated)
opus_sizeTotal size of Opus data in bytes
frame_sizesArray of individual frame sizes (variable-length frames)
frame_countNumber of Opus frames in the batch
Returns
0 on success, negative on error

Sends a batch of Opus-encoded audio frames to the server with proper synchronization and encryption support.

Parameters
opus_dataOpus-encoded audio data (multiple frames concatenated)
opus_sizeTotal size of Opus data in bytes
frame_sizesArray of individual frame sizes (variable-length frames)
frame_countNumber of Opus frames in the batch
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 1122 of file src/client/server.c.

1123 {
1124 // Get transport reference while holding mutex (brief lock)
1125 mutex_lock(&g_send_mutex);
1126
1127 // Check connection status and get transport reference
1128 if (!atomic_load(&g_connection_active) || !g_client_transport) {
1129 mutex_unlock(&g_send_mutex);
1130 return SET_ERRNO(ERROR_NETWORK, "Connection not active or transport unavailable");
1131 }
1132
1133 // Get transport reference - transport has its own internal synchronization
1134 acip_transport_t *transport = g_client_transport;
1135 mutex_unlock(&g_send_mutex);
1136
1137 // Network I/O happens OUTSIDE the mutex to prevent deadlock on TCP buffer full
1138 // Opus uses 20ms frames at 48kHz (960 samples = 20ms)
1139 asciichat_error_t result =
1140 acip_send_audio_opus_batch(transport, opus_data, opus_size, frame_sizes, frame_count, 48000, 20);
1141
1142 // If send failed due to network error, signal connection loss
1143 if (result != ASCIICHAT_OK) {
1145 return result;
1146 }
1147
1148 return ASCIICHAT_OK;
1149}
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)
Definition send.c:158

References acip_send_audio_opus_batch(), and server_connection_lost().

◆ threaded_send_client_join_packet()

int threaded_send_client_join_packet ( const char *  display_name,
uint32_t  capabilities 
)

#include <server.c>

Thread-safe client join packet transmission.

Sends client join packet with display name and capabilities to the server.

Parameters
display_nameClient display name
capabilitiesClient capability flags
Returns
0 on success, negative on error
Parameters
display_nameClient display name
capabilitiesClient capability flags
Returns
0 on success, negative on error

Definition at line 1306 of file src/client/server.c.

1306 {
1307 // Connection and transport availability is checked by threaded_send_packet()
1308
1309 // Build CLIENT_JOIN packet locally
1310 client_info_packet_t join_packet;
1311 SAFE_MEMSET(&join_packet, sizeof(join_packet), 0, sizeof(join_packet));
1312 join_packet.client_id = HOST_TO_NET_U32(0); // Will be assigned by server
1313 SAFE_SNPRINTF(join_packet.display_name, MAX_DISPLAY_NAME_LEN, "%s", display_name ? display_name : "Unknown");
1314 join_packet.capabilities = HOST_TO_NET_U32(capabilities);
1315
1316 // Use threaded_send_packet() which handles encryption
1317 int send_result = threaded_send_packet(PACKET_TYPE_CLIENT_JOIN, &join_packet, sizeof(join_packet));
1318 if (send_result == 0) {
1319 mutex_lock(&g_send_mutex);
1320 bool active = atomic_load(&g_connection_active);
1321 socket_t socket_snapshot = g_sockfd;
1322 const crypto_context_t *crypto_ctx = crypto_client_is_ready() ? crypto_client_get_context() : NULL;
1323 if (active && socket_snapshot != INVALID_SOCKET_VALUE) {
1324 (void)log_network_message(
1325 socket_snapshot, (const struct crypto_context_t *)crypto_ctx, LOG_INFO, REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER,
1326 "CLIENT_JOIN sent (display=\"%s\", capabilities=0x%x)", join_packet.display_name, capabilities);
1327 }
1328 mutex_unlock(&g_send_mutex);
1329 }
1330 return send_result;
1331}
asciichat_error_t threaded_send_packet(packet_type_t type, const void *data, size_t len)
Thread-safe packet transmission.
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,...)

References crypto_client_get_context(), crypto_client_is_ready(), log_network_message(), and threaded_send_packet().

Referenced by server_connection_establish().

◆ threaded_send_packet()

asciichat_error_t threaded_send_packet ( packet_type_t  type,
const void *  data,
size_t  len 
)

#include <server.c>

Thread-safe packet transmission.

Sends a packet to the server with proper mutex protection and connection state checking. Automatically handles encryption if crypto is ready.

Parameters
typePacket type identifier
dataPacket payload
lenPayload length
Returns
0 on success, negative on error

Sends a packet to the server with proper mutex protection and connection state checking. Automatically handles encryption if crypto is ready.

Parameters
typePacket type identifier
dataPacket payload
lenPayload length
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 978 of file src/client/server.c.

978 {
979 // Get transport reference while holding mutex (brief lock)
980 mutex_lock(&g_send_mutex);
981
982 // Check connection status and get transport reference
983 if (!atomic_load(&g_connection_active) || !g_client_transport) {
984 mutex_unlock(&g_send_mutex);
985 return SET_ERRNO(ERROR_NETWORK, "Connection not active or transport unavailable");
986 }
987
988 // Get transport reference - transport has its own internal synchronization
989 acip_transport_t *transport = g_client_transport;
990 mutex_unlock(&g_send_mutex);
991
992 // Network I/O happens OUTSIDE the mutex to prevent deadlock on TCP buffer full
993 asciichat_error_t result = packet_send_via_transport(transport, type, data, len, 0);
994
995 // If send failed due to network error, signal connection loss
996 if (result != ASCIICHAT_OK) {
998 return result;
999 }
1000
1001 return ASCIICHAT_OK;
1002}

References packet_send_via_transport(), and server_connection_lost().

Referenced by threaded_send_client_join_packet(), threaded_send_ping_packet(), threaded_send_pong_packet(), threaded_send_stream_start_packet(), and threaded_send_terminal_size_with_auto_detect().

◆ threaded_send_ping_packet()

int threaded_send_ping_packet ( void  )

#include <server.c>

Thread-safe ping packet transmission.

Returns
0 on success, negative on error

Definition at line 1158 of file src/client/server.c.

1158 {
1159 // Use threaded_send_packet which handles encryption, mutex locking, and connection state
1160 return threaded_send_packet(PACKET_TYPE_PING, NULL, 0);
1161}

References threaded_send_packet().

◆ threaded_send_pong_packet()

int threaded_send_pong_packet ( void  )

#include <server.c>

Thread-safe pong packet transmission.

Returns
0 on success, negative on error

Definition at line 1170 of file src/client/server.c.

1170 {
1171 // Use threaded_send_packet which handles encryption, mutex locking, and connection state
1172 return threaded_send_packet(PACKET_TYPE_PONG, NULL, 0);
1173}

References threaded_send_packet().

◆ threaded_send_stream_start_packet()

asciichat_error_t threaded_send_stream_start_packet ( uint32_t  stream_type)

#include <server.c>

Thread-safe stream start packet transmission.

Parameters
stream_typeType of stream (audio/video)
Returns
0 on success, negative on error

Definition at line 1183 of file src/client/server.c.

1183 {
1184 // Connection and transport availability is checked by threaded_send_packet()
1185
1186 // Build STREAM_START packet locally
1187 uint32_t type_data = HOST_TO_NET_U32(stream_type);
1188
1189 // Use threaded_send_packet() which handles encryption
1190 return threaded_send_packet(PACKET_TYPE_STREAM_START, &type_data, sizeof(type_data));
1191}

References threaded_send_packet().

Referenced by audio_start_thread(), capture_start_thread(), and protocol_start_connection().

◆ threaded_send_terminal_size_with_auto_detect()

asciichat_error_t threaded_send_terminal_size_with_auto_detect ( unsigned short  width,
unsigned short  height 
)

#include <server.c>

Thread-safe terminal size packet transmission with auto-detection.

Sends terminal capabilities packet to the server including terminal size, color capabilities, and rendering preferences. Auto-detects terminal capabilities if not explicitly specified.

Parameters
widthTerminal width in characters
heightTerminal height in characters
Returns
0 on success, negative on error
Parameters
widthTerminal width in characters
heightTerminal height in characters
Returns
0 on success, negative on error

Definition at line 1206 of file src/client/server.c.

1206 {
1207 // Log the dimensions being sent to server (helps debug dimension mismatch issues)
1208 log_debug("Sending terminal size to server: %ux%u (auto_width=%d, auto_height=%d)", width, height,
1209 GET_OPTION(auto_width), GET_OPTION(auto_height));
1210
1211 // Connection and transport availability is checked by threaded_send_packet()
1212
1213 // Build terminal capabilities packet locally
1214 // Detect terminal capabilities automatically
1215 terminal_capabilities_t caps = detect_terminal_capabilities();
1216
1217 // Set wants_padding based on snapshot mode and TTY status
1218 // Disable padding when:
1219 // - In snapshot mode (one frame and exit)
1220 // - When stdout is not a TTY (piped/redirected output)
1221 // Enable padding for interactive terminal sessions
1222 bool is_snapshot_mode = GET_OPTION(snapshot_mode);
1223 bool is_interactive = terminal_is_interactive();
1224 caps.wants_padding = is_interactive && !is_snapshot_mode;
1225
1226 log_debug("Client capabilities: wants_padding=%d (snapshot=%d, interactive=%d, stdin_tty=%d, stdout_tty=%d)",
1227 caps.wants_padding, is_snapshot_mode, is_interactive, terminal_is_stdin_tty(), terminal_is_stdout_tty());
1228
1229 // Apply user's color mode override
1230 caps = apply_color_mode_override(caps);
1231
1232 // Check if detection was reliable, use fallback only for auto-detection
1233 if (!caps.detection_reliable && GET_OPTION(color_mode) == COLOR_MODE_AUTO) {
1234 log_warn("Terminal capability detection not reliable, using fallback");
1235 SAFE_MEMSET(&caps, sizeof(caps), 0, sizeof(caps));
1236 caps.color_level = TERM_COLOR_NONE;
1237 caps.color_count = 2;
1238 caps.capabilities = 0;
1239 SAFE_STRNCPY(caps.term_type, "unknown", sizeof(caps.term_type));
1240 SAFE_STRNCPY(caps.colorterm, "", sizeof(caps.colorterm));
1241 caps.detection_reliable = 0;
1242 // Preserve wants_padding even in fallback mode
1243 caps.wants_padding = is_interactive && !is_snapshot_mode;
1244 }
1245
1246 // Convert to network packet format with proper byte order
1247 terminal_capabilities_packet_t net_packet;
1248 net_packet.capabilities = HOST_TO_NET_U32(caps.capabilities);
1249 net_packet.color_level = HOST_TO_NET_U32(caps.color_level);
1250 net_packet.color_count = HOST_TO_NET_U32(caps.color_count);
1251 net_packet.render_mode = HOST_TO_NET_U32(caps.render_mode);
1252 net_packet.width = HOST_TO_NET_U16(width);
1253 net_packet.height = HOST_TO_NET_U16(height);
1254 net_packet.palette_type = HOST_TO_NET_U32(GET_OPTION(palette_type));
1255 net_packet.utf8_support = HOST_TO_NET_U32(caps.utf8_support ? 1 : 0);
1256
1257 const options_t *opts = options_get();
1258 if (GET_OPTION(palette_type) == PALETTE_CUSTOM && GET_OPTION(palette_custom_set)) {
1259 const char *palette_custom = opts && opts->palette_custom_set ? opts->palette_custom : "";
1260 SAFE_STRNCPY(net_packet.palette_custom, palette_custom, sizeof(net_packet.palette_custom));
1261 net_packet.palette_custom[sizeof(net_packet.palette_custom) - 1] = '\0';
1262 } else {
1263 SAFE_MEMSET(net_packet.palette_custom, sizeof(net_packet.palette_custom), 0, sizeof(net_packet.palette_custom));
1264 }
1265
1266 // Set desired FPS
1267 int fps = GET_OPTION(fps);
1268 if (fps > 0) {
1269 net_packet.desired_fps = (uint8_t)(fps > 144 ? 144 : fps);
1270 } else {
1271 net_packet.desired_fps = caps.desired_fps;
1272 }
1273
1274 if (net_packet.desired_fps == 0) {
1275 net_packet.desired_fps = DEFAULT_MAX_FPS;
1276 }
1277
1278 SAFE_STRNCPY(net_packet.term_type, caps.term_type, sizeof(net_packet.term_type));
1279 net_packet.term_type[sizeof(net_packet.term_type) - 1] = '\0';
1280
1281 SAFE_STRNCPY(net_packet.colorterm, caps.colorterm, sizeof(net_packet.colorterm));
1282 net_packet.colorterm[sizeof(net_packet.colorterm) - 1] = '\0';
1283
1284 net_packet.detection_reliable = caps.detection_reliable;
1285 // Send UTF-8 support flag: true for AUTO (default) and TRUE settings, false for FALSE setting
1286 net_packet.utf8_support = (GET_OPTION(force_utf8) != UTF8_SETTING_FALSE) ? 1 : 0;
1287
1288 // Set wants_padding flag (1=padding enabled, 0=no padding for snapshot/piped modes)
1289 net_packet.wants_padding = caps.wants_padding ? 1 : 0;
1290
1291 // Use threaded_send_packet() which handles encryption
1292 return threaded_send_packet(PACKET_TYPE_CLIENT_CAPABILITIES, &net_packet, sizeof(net_packet));
1293}
ASCIICHAT_API bool auto_width
ASCIICHAT_API bool auto_height
bool terminal_is_interactive(void)
bool terminal_is_stdin_tty(void)
bool terminal_is_stdout_tty(void)
terminal_capabilities_t detect_terminal_capabilities(void)
const options_t * options_get(void)
Definition rcu.c:347

References auto_height, auto_width, detect_terminal_capabilities(), options_get(), terminal_is_interactive(), terminal_is_stdin_tty(), terminal_is_stdout_tty(), and threaded_send_packet().

Referenced by protocol_start_connection(), and server_connection_establish().

Variable Documentation

◆ g_crypto_ctx

crypto_handshake_context_t g_crypto_ctx = {0}

#include <server.c>

Per-connection crypto handshake context.

Global crypto handshake context for this client connection.

Maintains the cryptographic state for the current connection, including key exchange state, encryption keys, and handshake progress.

Note
This is not static because it may be accessed from crypto.c

Definition at line 196 of file src/client/server.c.

196{0};

Referenced by client_crypto_handshake(), client_crypto_init(), crypto_client_cleanup(), crypto_client_decrypt_packet(), crypto_client_encrypt_packet(), crypto_client_get_context(), crypto_client_initiate_rekey(), crypto_client_is_ready(), crypto_client_process_rekey_request(), crypto_client_process_rekey_response(), crypto_client_send_rekey_complete(), crypto_client_send_rekey_response(), crypto_client_should_rekey(), and server_connection_close().