ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
lib/network/tcp/client.c
Go to the documentation of this file.
1
20#include "network/tcp/client.h"
21#include "common.h"
22#include "log/logging.h"
24#include "platform/socket.h"
25#include "platform/system.h"
26#include "platform/terminal.h"
27#include "network/packet.h"
28#include "network/network.h"
29#include "util/endian.h"
30#include "util/ip.h"
31#include "asciichat_errno.h"
32#include "buffer_pool.h"
33#include "options/options.h"
35
36#include <string.h>
37#include <time.h>
38#include <stdatomic.h>
39
40#ifndef _WIN32
41#include <netinet/tcp.h>
42#include <netdb.h>
43#include <arpa/inet.h>
44#include <unistd.h>
45#else
46#include <ws2tcpip.h>
47#include <process.h>
48#define getpid _getpid
49#endif
50
51/* ============================================================================
52 * Connection Lifecycle Management
53 * ============================================================================ */
54
56#define MAX_RECONNECT_DELAY (5 * 1000 * 1000)
57
63static unsigned int get_reconnect_delay(unsigned int attempt) {
64 unsigned int delay_us = 100000 + (attempt - 1) * 200000;
65 return (delay_us > MAX_RECONNECT_DELAY) ? MAX_RECONNECT_DELAY : delay_us;
66}
67
71static int close_socket_safe(socket_t sockfd) {
72 if (!socket_is_valid(sockfd)) {
73 return 0; // Already closed
74 }
75
76 log_debug("Closing socket %d", sockfd);
77
78 if (socket_close(sockfd) != 0) {
79 log_error("Failed to close socket: %s", network_error_string());
80 return -1;
81 }
82
83 // Small delay to ensure socket resources are released
84 // Prevents WSA error 10038 on Windows
85 platform_sleep_usec(50000); // 50ms
86
87 return 0;
88}
89
97 if (!client) {
98 log_error("Failed to allocate tcp_client_t");
99 return NULL;
100 }
101
102 // Zero-initialize all fields
103 memset(client, 0, sizeof(*client));
104
105 /* Connection State */
107 atomic_store(&client->connection_active, false);
108 atomic_store(&client->connection_lost, false);
109 atomic_store(&client->should_reconnect, false);
110 client->my_client_id = 0;
111 memset(client->server_ip, 0, sizeof(client->server_ip));
112 client->encryption_enabled = false;
113
114 // Initialize send mutex
115 if (mutex_init(&client->send_mutex) != 0) {
116 log_error("Failed to initialize send mutex");
117 SAFE_FREE(client);
118 return NULL;
119 }
120
121 /* Audio State */
122 memset(&client->audio_ctx, 0, sizeof(client->audio_ctx));
123 memset(client->audio_send_queue, 0, sizeof(client->audio_send_queue));
124 client->audio_send_queue_head = 0;
125 client->audio_send_queue_tail = 0;
126 client->audio_send_queue_initialized = false;
127 atomic_store(&client->audio_sender_should_exit, false);
128
129 // Initialize audio queue mutex and condition variable
130 if (mutex_init(&client->audio_send_queue_mutex) != 0) {
131 log_error("Failed to initialize audio queue mutex");
132 mutex_destroy(&client->send_mutex);
133 SAFE_FREE(client);
134 return NULL;
135 }
136
137 if (cond_init(&client->audio_send_queue_cond) != 0) {
138 log_error("Failed to initialize audio queue cond");
140 mutex_destroy(&client->send_mutex);
141 SAFE_FREE(client);
142 return NULL;
143 }
144
145 client->audio_capture_thread_created = false;
146 client->audio_sender_thread_created = false;
147 atomic_store(&client->audio_capture_thread_exited, false);
148
149 /* Protocol State */
150 client->data_thread_created = false;
151 atomic_store(&client->data_thread_exited, false);
152 client->last_active_count = 0;
153 client->server_state_initialized = false;
154 client->should_clear_before_next_frame = false;
155
156 /* Capture State */
157 client->capture_thread_created = false;
158 atomic_store(&client->capture_thread_exited, false);
159
160 /* Keepalive State */
161 client->ping_thread_created = false;
162 atomic_store(&client->ping_thread_exited, false);
163
164 /* Display State */
165 client->has_tty = false;
166 atomic_store(&client->is_first_frame_of_connection, true);
167 memset(&client->tty_info, 0, sizeof(client->tty_info));
168
169 /* Crypto State */
170 memset(&client->crypto_ctx, 0, sizeof(client->crypto_ctx));
171 client->crypto_initialized = false;
172
173 log_debug("TCP client created successfully");
174 return client;
175}
176
183 if (!client_ptr || !*client_ptr) {
184 return;
185 }
186
187 tcp_client_t *client = *client_ptr;
188
189#ifndef NDEBUG
190 // Debug: verify all threads have exited
191 if (client->audio_capture_thread_created && !atomic_load(&client->audio_capture_thread_exited)) {
192 log_warn("Destroying client while audio capture thread may still be running");
193 }
194 if (client->data_thread_created && !atomic_load(&client->data_thread_exited)) {
195 log_warn("Destroying client while data thread may still be running");
196 }
197 if (client->capture_thread_created && !atomic_load(&client->capture_thread_exited)) {
198 log_warn("Destroying client while capture thread may still be running");
199 }
200 if (client->ping_thread_created && !atomic_load(&client->ping_thread_exited)) {
201 log_warn("Destroying client while ping thread may still be running");
202 }
203#endif
204
205 // Close socket if still open
206 if (socket_is_valid(client->sockfd)) {
207 close_socket_safe(client->sockfd);
209 }
210
211 // Destroy synchronization primitives
212 mutex_destroy(&client->send_mutex);
215
216 // Free client structure
217 SAFE_FREE(client);
218 *client_ptr = NULL;
219
220 log_debug("TCP client destroyed");
221}
222
223/* ============================================================================
224 * Connection State Queries
225 * ============================================================================ */
226
231 if (!client)
232 return false;
233 return atomic_load(&client->connection_active);
234}
235
239bool tcp_client_is_lost(const tcp_client_t *client) {
240 if (!client)
241 return false;
242 return atomic_load(&client->connection_lost);
243}
244
249 return client ? client->sockfd : INVALID_SOCKET_VALUE;
250}
251
256 return client ? client->my_client_id : 0;
257}
258
259/* ============================================================================
260 * Connection Control
261 * ============================================================================ */
262
267 if (!client)
268 return;
269
270 if (!atomic_load(&client->connection_lost)) {
271 atomic_store(&client->connection_lost, true);
272 atomic_store(&client->connection_active, false);
273 log_info("Connection lost signaled");
274 }
275}
276
281 if (!client)
282 return;
283
284 log_debug("Closing client connection");
285
286 // Mark connection as inactive
287 atomic_store(&client->connection_active, false);
288
289 // Close socket
290 if (socket_is_valid(client->sockfd)) {
291 close_socket_safe(client->sockfd);
293 }
294
295 // Reset client ID
296 client->my_client_id = 0;
297}
298
303 if (!client)
304 return;
305
306 atomic_store(&client->connection_active, false);
307
308 // Shutdown socket for reading/writing to interrupt blocking calls
309 if (socket_is_valid(client->sockfd)) {
310 socket_shutdown(client->sockfd, SHUT_RDWR);
311 }
312}
313
318 if (!client)
319 return;
320
321 // Close connection
322 tcp_client_close(client);
323
324 // Reset state flags
325 atomic_store(&client->connection_lost, false);
326 atomic_store(&client->should_reconnect, false);
327 memset(client->server_ip, 0, sizeof(client->server_ip));
328}
329
330/* ============================================================================
331 * Thread-Safe Packet Transmission
332 * ============================================================================ */
333
340int tcp_client_send_packet(tcp_client_t *client, packet_type_t type, const void *data, size_t len) {
341 if (!client) {
342 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL client");
343 }
344
345 if (!atomic_load(&client->connection_active)) {
346 return SET_ERRNO(ERROR_NETWORK, "Connection not active");
347 }
348
349 // Acquire send mutex for thread-safe transmission
350 mutex_lock(&client->send_mutex);
351
352 // Determine if encryption should be used
353 crypto_context_t *crypto_ctx = NULL;
355 crypto_ctx = crypto_handshake_get_context(&client->crypto_ctx);
356 }
357
358 // Send packet (encrypted if crypto context available)
359 asciichat_error_t result = send_packet_secure(client->sockfd, type, data, len, crypto_ctx);
360
361 mutex_unlock(&client->send_mutex);
362
363 if (result != ASCIICHAT_OK) {
364 log_debug("Failed to send packet type %d: %s", type, asciichat_error_string(result));
365 return -1;
366 }
367
368 return 0;
369}
370
375 if (!client)
376 return -1;
377 return tcp_client_send_packet(client, PACKET_TYPE_PING, NULL, 0);
378}
379
384 if (!client)
385 return -1;
386 return tcp_client_send_packet(client, PACKET_TYPE_PONG, NULL, 0);
387}
388
389/* ============================================================================
390 * Connection Establishment (to be implemented - migrated from server.c)
391 * ============================================================================ */
392
411int tcp_client_connect(tcp_client_t *client, const char *address, int port, int reconnect_attempt,
412 bool first_connection, bool has_ever_connected) {
413 (void)first_connection; // Currently unused
414 (void)has_ever_connected; // Currently unused
415
416 if (!client || !address || port <= 0) {
417 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid client, address, or port");
418 }
419
420 // Close any existing connection
421 if (socket_is_valid(client->sockfd)) {
422 close_socket_safe(client->sockfd);
424 }
425
426 // Apply reconnection delay if this is a retry
427 if (reconnect_attempt > 0) {
428 unsigned int delay_us = get_reconnect_delay(reconnect_attempt);
429 platform_sleep_usec(delay_us);
430 }
431
432 // Resolve server address using getaddrinfo() for IPv4/IPv6 support
433 // Special handling for localhost: ensure we try both IPv6 (::1) and IPv4 (127.0.0.1)
434 bool is_localhost =
435 (strcmp(address, "localhost") == 0 || strcmp(address, "127.0.0.1") == 0 || strcmp(address, "::1") == 0);
436
437 struct addrinfo hints, *res = NULL, *addr_iter;
438 memset(&hints, 0, sizeof(hints));
439 hints.ai_family = AF_UNSPEC; // Allow IPv4 or IPv6
440 hints.ai_socktype = SOCK_STREAM;
441 if (is_localhost) {
442 hints.ai_flags = AI_NUMERICSERV; // Optimize for localhost
443 }
444
445 char port_str[16];
446 SAFE_SNPRINTF(port_str, sizeof(port_str), "%d", port);
447
448 // For localhost, try IPv6 loopback (::1) first, then fall back to IPv4
449 if (is_localhost) {
450 log_debug("Localhost detected - trying IPv6 loopback [::1]:%s first...", port_str);
451 hints.ai_family = AF_INET6;
452 hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
453
454 int ipv6_result = getaddrinfo("::1", port_str, &hints, &res);
455 if (ipv6_result == 0 && res != NULL) {
456 // Try IPv6 loopback connection
457 client->sockfd = socket_create(res->ai_family, res->ai_socktype, res->ai_protocol);
458 if (client->sockfd != INVALID_SOCKET_VALUE) {
459 log_info("Trying IPv6 loopback connection to [::1]:%s...", port_str);
460 if (connect_with_timeout(client->sockfd, res->ai_addr, res->ai_addrlen, CONNECT_TIMEOUT)) {
461 log_debug("Connection successful using IPv6 loopback");
462 SAFE_STRNCPY(client->server_ip, "::1", sizeof(client->server_ip));
463 freeaddrinfo(res);
464 res = NULL; // Prevent double-free at connection_success label
465 goto connection_success;
466 }
467 close_socket_safe(client->sockfd);
469 }
470 freeaddrinfo(res);
471 res = NULL;
472 }
473
474 // IPv6 failed, try IPv4 loopback (127.0.0.1)
475 log_debug("IPv6 failed, trying IPv4 loopback 127.0.0.1:%s...", port_str);
476 hints.ai_family = AF_INET;
477
478 int ipv4_result = getaddrinfo("127.0.0.1", port_str, &hints, &res);
479 if (ipv4_result == 0 && res != NULL) {
480 client->sockfd = socket_create(res->ai_family, res->ai_socktype, res->ai_protocol);
481 if (client->sockfd != INVALID_SOCKET_VALUE) {
482 log_info("Trying IPv4 loopback connection to 127.0.0.1:%s...", port_str);
483 if (connect_with_timeout(client->sockfd, res->ai_addr, res->ai_addrlen, CONNECT_TIMEOUT)) {
484 log_debug("Connection successful using IPv4 loopback");
485 SAFE_STRNCPY(client->server_ip, "127.0.0.1", sizeof(client->server_ip));
486 freeaddrinfo(res);
487 res = NULL;
488 goto connection_success;
489 }
490 close_socket_safe(client->sockfd);
492 }
493 freeaddrinfo(res);
494 res = NULL;
495 }
496
497 // Both IPv6 and IPv4 loopback failed for localhost
498 log_warn("Could not connect to localhost using either IPv6 or IPv4 loopback");
499 return -1;
500 }
501
502 // For non-localhost addresses, use standard resolution
503 log_debug("Resolving server address '%s' port %s...", address, port_str);
504 hints.ai_family = AF_UNSPEC;
505 hints.ai_flags = 0;
506 int getaddr_result = getaddrinfo(address, port_str, &hints, &res);
507 if (getaddr_result != 0) {
508 log_error("Failed to resolve server address '%s': %s", address, gai_strerror(getaddr_result));
509 return -1;
510 }
511
512 // Try each address returned by getaddrinfo() - prefer IPv6, fall back to IPv4
513 for (int address_family = AF_INET6; address_family >= AF_INET; address_family -= (AF_INET6 - AF_INET)) {
514 for (addr_iter = res; addr_iter != NULL; addr_iter = addr_iter->ai_next) {
515 if (addr_iter->ai_family != address_family) {
516 continue;
517 }
518
519 client->sockfd = socket_create(addr_iter->ai_family, addr_iter->ai_socktype, addr_iter->ai_protocol);
520 if (client->sockfd == INVALID_SOCKET_VALUE) {
521 continue;
522 }
523
524 if (addr_iter->ai_family == AF_INET) {
525 log_debug("Trying IPv4 connection...");
526 } else if (addr_iter->ai_family == AF_INET6) {
527 log_debug("Trying IPv6 connection...");
528 }
529
530 if (connect_with_timeout(client->sockfd, addr_iter->ai_addr, addr_iter->ai_addrlen, CONNECT_TIMEOUT)) {
531 log_debug("Connection successful using %s", addr_iter->ai_family == AF_INET ? "IPv4"
532 : addr_iter->ai_family == AF_INET6 ? "IPv6"
533 : "unknown protocol");
534
535 // Extract server IP address for known_hosts
536 if (format_ip_address(addr_iter->ai_family, addr_iter->ai_addr, client->server_ip, sizeof(client->server_ip)) ==
537 ASCIICHAT_OK) {
538 log_debug("Resolved server IP: %s", client->server_ip);
539 } else {
540 log_warn("Failed to format server IP address");
541 }
542
543 goto connection_success;
544 }
545
546 close_socket_safe(client->sockfd);
548 }
549 }
550
551connection_success:
552
553 if (res) {
554 freeaddrinfo(res);
555 }
556
557 // If we exhausted all addresses without success, fail
558 if (client->sockfd == INVALID_SOCKET_VALUE) {
559 log_warn("Could not connect to server %s:%d (tried all addresses)", address, port);
560 return -1;
561 }
562
563 // Extract local port for client ID
564 struct sockaddr_storage local_addr = {0};
565 socklen_t addr_len = sizeof(local_addr);
566 if (getsockname(client->sockfd, (struct sockaddr *)&local_addr, &addr_len) == -1) {
567 log_error("Failed to get local socket address: %s", network_error_string());
568 close_socket_safe(client->sockfd);
570 return -1;
571 }
572
573 // Extract port from either IPv4 or IPv6 address
574 int local_port = 0;
575 if (((struct sockaddr *)&local_addr)->sa_family == AF_INET) {
576 local_port = NET_TO_HOST_U16(((struct sockaddr_in *)&local_addr)->sin_port);
577 } else if (((struct sockaddr *)&local_addr)->sa_family == AF_INET6) {
578 local_port = NET_TO_HOST_U16(((struct sockaddr_in6 *)&local_addr)->sin6_port);
579 }
580 client->my_client_id = (uint32_t)local_port;
581
582 // Mark connection as active
583 atomic_store(&client->connection_active, true);
584 atomic_store(&client->connection_lost, false);
585 atomic_store(&client->should_reconnect, false);
586
587 // Initialize crypto (application must set crypto_initialized flag)
588 // This is done outside this function by calling client_crypto_init()
589
590 // Configure socket options
591 if (socket_set_keepalive(client->sockfd, true) < 0) {
592 log_warn("Failed to set socket keepalive: %s", network_error_string());
593 }
594
595 asciichat_error_t sock_config_result = socket_configure_buffers(client->sockfd);
596 if (sock_config_result != ASCIICHAT_OK) {
597 log_warn("Failed to configure socket: %s", network_error_string());
598 }
599
600 log_debug("Connection established successfully to %s:%d (client_id=%u)", address, port, client->my_client_id);
601 return 0;
602}
603
604/* ============================================================================
605 * Advanced Packet Sending Functions
606 * ============================================================================ */
607
618int tcp_client_send_audio_opus(tcp_client_t *client, const uint8_t *opus_data, size_t opus_size, int sample_rate,
619 int frame_duration) {
620 if (!client || !opus_data) {
621 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL client or opus_data");
622 }
623
624 if (!atomic_load(&client->connection_active)) {
625 return SET_ERRNO(ERROR_NETWORK, "Connection not active");
626 }
627
628 mutex_lock(&client->send_mutex);
629
630 // Recheck connection status inside mutex to prevent TOCTOU race
631 if (!atomic_load(&client->connection_active) || client->sockfd == INVALID_SOCKET_VALUE) {
632 mutex_unlock(&client->send_mutex);
633 return SET_ERRNO(ERROR_NETWORK, "Connection not active");
634 }
635
636 // Get crypto context if encryption is enabled
637 crypto_context_t *crypto_ctx = NULL;
639 crypto_ctx = crypto_handshake_get_context(&client->crypto_ctx);
640 }
641
642 // Build Opus packet with header
643 size_t header_size = 16; // sample_rate (4), frame_duration (4), reserved (8)
644 size_t total_size = header_size + opus_size;
645 void *packet_data = buffer_pool_alloc(NULL, total_size);
646 if (!packet_data) {
647 mutex_unlock(&client->send_mutex);
648 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate buffer for Opus packet: %zu bytes", total_size);
649 }
650
651 // Write header in network byte order
652 uint8_t *buf = (uint8_t *)packet_data;
653 uint32_t sr = HOST_TO_NET_U32((uint32_t)sample_rate);
654 uint32_t fd = HOST_TO_NET_U32((uint32_t)frame_duration);
655 memcpy(buf, &sr, 4);
656 memcpy(buf + 4, &fd, 4);
657 memset(buf + 8, 0, 8); // Reserved
658
659 // Copy Opus data
660 memcpy(buf + header_size, opus_data, opus_size);
661
662 // Send packet with encryption if available
663 asciichat_error_t result;
664 if (crypto_ctx) {
665 result = send_packet_secure(client->sockfd, PACKET_TYPE_AUDIO_OPUS, packet_data, total_size, crypto_ctx);
666 } else {
667 result = packet_send(client->sockfd, PACKET_TYPE_AUDIO_OPUS, packet_data, total_size);
668 }
669
670 buffer_pool_free(NULL, packet_data, total_size);
671 mutex_unlock(&client->send_mutex);
672
673 if (result != ASCIICHAT_OK) {
675 }
676
677 return result;
678}
679
690int tcp_client_send_audio_opus_batch(tcp_client_t *client, const uint8_t *opus_data, size_t opus_size,
691 const uint16_t *frame_sizes, int frame_count) {
692 if (!client || !opus_data || !frame_sizes) {
693 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL client, opus_data, or frame_sizes");
694 }
695
696 if (!atomic_load(&client->connection_active)) {
697 return SET_ERRNO(ERROR_NETWORK, "Connection not active");
698 }
699
700 mutex_lock(&client->send_mutex);
701
702 if (!atomic_load(&client->connection_active) || client->sockfd == INVALID_SOCKET_VALUE) {
703 mutex_unlock(&client->send_mutex);
704 return -1;
705 }
706
707 crypto_context_t *crypto_ctx = NULL;
709 crypto_ctx = crypto_handshake_get_context(&client->crypto_ctx);
710 }
711
712 // Opus uses 20ms frames at 48kHz
713 int result =
714 av_send_audio_opus_batch(client->sockfd, opus_data, opus_size, frame_sizes, 48000, 20, frame_count, crypto_ctx);
715
716 mutex_unlock(&client->send_mutex);
717
718 if (result < 0) {
720 }
721
722 return result;
723}
724
733int tcp_client_send_terminal_capabilities(tcp_client_t *client, unsigned short width, unsigned short height) {
734 if (!client) {
735 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL client");
736 }
737
738 if (!atomic_load(&client->connection_active) || client->sockfd == INVALID_SOCKET_VALUE) {
739 return -1;
740 }
741
742 // Detect terminal capabilities automatically
744
745 // Apply user's color mode override
746 caps = apply_color_mode_override(caps);
747
748 // Check if detection was reliable, use fallback only for auto-detection
749 if (!caps.detection_reliable && (int)GET_OPTION(color_mode) == COLOR_MODE_AUTO) {
750 log_warn("Terminal capability detection not reliable, using fallback");
751 SAFE_MEMSET(&caps, sizeof(caps), 0, sizeof(caps));
753 caps.color_count = 2;
754 caps.capabilities = 0;
755 SAFE_STRNCPY(caps.term_type, "unknown", sizeof(caps.term_type));
756 SAFE_STRNCPY(caps.colorterm, "", sizeof(caps.colorterm));
757 caps.detection_reliable = 0;
758 }
759
760 // Convert to network packet format
762 net_packet.capabilities = HOST_TO_NET_U32(caps.capabilities);
763 net_packet.color_level = HOST_TO_NET_U32(caps.color_level);
764 net_packet.color_count = HOST_TO_NET_U32(caps.color_count);
765 net_packet.render_mode = HOST_TO_NET_U32(caps.render_mode);
766 net_packet.width = HOST_TO_NET_U16(width);
767 net_packet.height = HOST_TO_NET_U16(height);
768 net_packet.palette_type = HOST_TO_NET_U32(GET_OPTION(palette_type));
769 net_packet.utf8_support = HOST_TO_NET_U32(caps.utf8_support ? 1 : 0);
770
771 const options_t *opts = options_get();
772 if (GET_OPTION(palette_type) == PALETTE_CUSTOM && GET_OPTION(palette_custom_set)) {
773 const char *palette_custom = opts && opts->palette_custom_set ? opts->palette_custom : "";
774 SAFE_STRNCPY(net_packet.palette_custom, palette_custom, sizeof(net_packet.palette_custom));
775 net_packet.palette_custom[sizeof(net_packet.palette_custom) - 1] = '\0';
776 } else {
777 SAFE_MEMSET(net_packet.palette_custom, sizeof(net_packet.palette_custom), 0, sizeof(net_packet.palette_custom));
778 }
779
780 // Set desired FPS (from global g_max_fps if available, otherwise from caps)
781 extern int g_max_fps; // Will be passed via options in future refactoring
782 if (g_max_fps > 0) {
783 net_packet.desired_fps = (uint8_t)(g_max_fps > 144 ? 144 : g_max_fps);
784 } else {
785 net_packet.desired_fps = caps.desired_fps;
786 }
787
788 if (net_packet.desired_fps == 0) {
789 net_packet.desired_fps = DEFAULT_MAX_FPS;
790 }
791
792 SAFE_STRNCPY(net_packet.term_type, caps.term_type, sizeof(net_packet.term_type));
793 net_packet.term_type[sizeof(net_packet.term_type) - 1] = '\0';
794
795 SAFE_STRNCPY(net_packet.colorterm, caps.colorterm, sizeof(net_packet.colorterm));
796 net_packet.colorterm[sizeof(net_packet.colorterm) - 1] = '\0';
797
798 net_packet.detection_reliable = caps.detection_reliable;
799 net_packet.utf8_support = GET_OPTION(force_utf8) ? 1 : 0;
800
801 SAFE_MEMSET(net_packet.reserved, sizeof(net_packet.reserved), 0, sizeof(net_packet.reserved));
802
803 return tcp_client_send_packet(client, PACKET_TYPE_CLIENT_CAPABILITIES, &net_packet, sizeof(net_packet));
804}
805
814int tcp_client_send_join(tcp_client_t *client, const char *display_name, uint32_t capabilities) {
815 if (!client) {
816 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL client");
817 }
818
819 if (!atomic_load(&client->connection_active) || client->sockfd == INVALID_SOCKET_VALUE) {
820 return -1;
821 }
822
823 // Build CLIENT_JOIN packet
824 client_info_packet_t join_packet;
825 SAFE_MEMSET(&join_packet, sizeof(join_packet), 0, sizeof(join_packet));
826 join_packet.client_id = HOST_TO_NET_U32(0); // Will be assigned by server
827 SAFE_SNPRINTF(join_packet.display_name, MAX_DISPLAY_NAME_LEN, "%s", display_name ? display_name : "Unknown");
828 join_packet.capabilities = HOST_TO_NET_U32(capabilities);
829
830 int send_result = tcp_client_send_packet(client, PACKET_TYPE_CLIENT_JOIN, &join_packet, sizeof(join_packet));
831 if (send_result == 0) {
832 mutex_lock(&client->send_mutex);
833 bool active = atomic_load(&client->connection_active);
834 socket_t socket_snapshot = client->sockfd;
835 crypto_context_t *crypto_ctx = NULL;
837 crypto_ctx = crypto_handshake_get_context(&client->crypto_ctx);
838 }
839 if (active && socket_snapshot != INVALID_SOCKET_VALUE) {
841 socket_snapshot, (const struct crypto_context_t *)crypto_ctx, LOG_INFO, REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER,
842 "CLIENT_JOIN sent (display=\"%s\", capabilities=0x%x)", join_packet.display_name, capabilities);
843 }
844 mutex_unlock(&client->send_mutex);
845 }
846 return send_result;
847}
848
857 if (!client) {
858 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL client");
859 }
860
861 if (!atomic_load(&client->connection_active) || client->sockfd == INVALID_SOCKET_VALUE) {
862 return -1;
863 }
864
865 uint32_t type_data = HOST_TO_NET_U32(stream_type);
866 return tcp_client_send_packet(client, PACKET_TYPE_STREAM_START, &type_data, sizeof(type_data));
867}
868
878int tcp_client_send_audio_batch(tcp_client_t *client, const float *samples, int num_samples, int batch_count) {
879 if (!client || !samples) {
880 return SET_ERRNO(ERROR_INVALID_PARAM, "NULL client or samples");
881 }
882
883 if (!atomic_load(&client->connection_active)) {
884 return SET_ERRNO(ERROR_NETWORK, "Connection not active");
885 }
886
887 mutex_lock(&client->send_mutex);
888
889 if (!atomic_load(&client->connection_active) || client->sockfd == INVALID_SOCKET_VALUE) {
890 mutex_unlock(&client->send_mutex);
891 return -1;
892 }
893
894 crypto_context_t *crypto_ctx = NULL;
896 crypto_ctx = crypto_handshake_get_context(&client->crypto_ctx);
897 }
898
899 int result = send_audio_batch_packet(client->sockfd, samples, num_samples, batch_count, crypto_ctx);
900
901 mutex_unlock(&client->send_mutex);
902
903 if (result < 0) {
905 }
906
907 return result;
908}
๐Ÿ”Œ Cross-platform abstraction layer umbrella header for ascii-chat
โš ๏ธโ€ผ๏ธ Error and/or exit() when things go bad.
๐Ÿ—ƒ๏ธ Lock-Free Unified Memory Buffer Pool with Lazy Allocation
const crypto_context_t * crypto_handshake_get_context(const crypto_handshake_context_t *ctx)
Get the crypto context for encryption/decryption.
bool crypto_handshake_is_ready(const crypto_handshake_context_t *ctx)
Check if handshake is complete and encryption is ready.
Common declarations and data structures for cryptographic handshake.
๐Ÿ”„ 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)
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_FREE(ptr)
Definition common.h:320
#define SAFE_MEMSET(dest, dest_size, ch, count)
Definition common.h:389
#define SAFE_MALLOC(size, cast)
Definition common.h:208
#define SAFE_SNPRINTF(buffer, buffer_size,...)
Definition common.h:412
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_NETWORK
Definition error_codes.h:69
@ ERROR_MEMORY
Definition error_codes.h:53
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_INVALID_PARAM
#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.
@ REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER
@ LOG_INFO
Definition log/logging.h:62
asciichat_error_t send_packet_secure(socket_t sockfd, packet_type_t type, const void *data, size_t len, crypto_context_t *crypto_ctx)
Send a packet with encryption and compression support.
Definition packet.c:433
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
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
asciichat_error_t av_send_audio_opus_batch(socket_t sockfd, const uint8_t *opus_data, size_t opus_size, const uint16_t *frame_sizes, int sample_rate, int frame_duration, int frame_count, crypto_context_t *crypto_ctx)
Send Opus-encoded audio batch packet with encryption support.
Definition packet.c:1136
bool connect_with_timeout(socket_t sockfd, const struct sockaddr *addr, socklen_t addrlen, int timeout_seconds)
Connect with timeout.
Definition network.c:547
uint32_t render_mode
Render mode enum value (foreground/background/half-block)
Definition packet.h:917
asciichat_error_t packet_send(socket_t sockfd, packet_type_t type, const void *data, size_t len)
Send a packet with proper header and CRC32.
Definition packet.c:291
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
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
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 cond_init(cond_t *cond)
Initialize a condition variable.
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
#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.
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.
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
Definition mutex.h:175
int cond_destroy(cond_t *cond)
Destroy a condition variable.
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
๐ŸŒ IP Address Parsing and Formatting Utilities
tcp_client_t * tcp_client_create(void)
Create and initialize TCP client.
int tcp_client_send_stream_start(tcp_client_t *client, uint32_t stream_type)
Send stream start packet.
socket_t tcp_client_get_socket(const tcp_client_t *client)
Get current socket descriptor.
bool tcp_client_is_active(const tcp_client_t *client)
Check if connection is currently active.
int tcp_client_send_pong(tcp_client_t *client)
Send pong packet.
int tcp_client_connect(tcp_client_t *client, const char *address, int port, int reconnect_attempt, bool first_connection, bool has_ever_connected)
Establish TCP connection to server.
int tcp_client_send_join(tcp_client_t *client, const char *display_name, uint32_t capabilities)
Send client join packet.
void tcp_client_close(tcp_client_t *client)
Close connection gracefully.
int tcp_client_send_ping(tcp_client_t *client)
Send ping packet.
int tcp_client_send_audio_batch(tcp_client_t *client, const float *samples, int num_samples, int batch_count)
Send audio batch packet.
int tcp_client_send_audio_opus(tcp_client_t *client, const uint8_t *opus_data, size_t opus_size, int sample_rate, int frame_duration)
Send Opus-encoded audio frame.
bool tcp_client_is_lost(const tcp_client_t *client)
Check if connection was lost.
#define MAX_RECONNECT_DELAY
void tcp_client_shutdown(tcp_client_t *client)
Shutdown connection forcefully (for signal handlers)
int tcp_client_send_terminal_capabilities(tcp_client_t *client, unsigned short width, unsigned short height)
Send terminal capabilities packet.
int tcp_client_send_audio_opus_batch(tcp_client_t *client, const uint8_t *opus_data, size_t opus_size, const uint16_t *frame_sizes, int frame_count)
Send Opus audio batch packet.
uint32_t tcp_client_get_id(const tcp_client_t *client)
Get client ID assigned by server.
void tcp_client_cleanup(tcp_client_t *client)
Cleanup connection resources.
void tcp_client_signal_lost(tcp_client_t *client)
Signal that connection was lost (triggers reconnection)
void tcp_client_destroy(tcp_client_t **client_ptr)
Destroy TCP client and free resources.
int tcp_client_send_packet(tcp_client_t *client, packet_type_t type, const void *data, size_t len)
Send packet with thread-safe mutex protection.
๐Ÿ“ Logging API with multiple log levels and terminal output control
๐ŸŒ 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.
Cross-platform process execution utilities.
Cross-platform socket interface for ascii-chat.
Client information packet structure.
Definition packet.h:545
Cryptographic context structure.
Consolidated options structure.
Definition options.h:439
TCP client connection and state management.
crypto_handshake_context_t crypto_ctx
atomic_bool connection_active
atomic_bool should_reconnect
atomic_bool ping_thread_exited
atomic_bool audio_capture_thread_exited
atomic_bool audio_sender_should_exit
atomic_bool connection_lost
audio_context_t audio_ctx
atomic_bool capture_thread_exited
tcp_client_audio_packet_t audio_send_queue[256]
atomic_bool is_first_frame_of_connection
atomic_bool data_thread_exited
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
Common SIMD utilities and structures.