ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
client/main.c
Go to the documentation of this file.
1
66#include "main.h"
67#include "server.h"
68#include "protocol.h"
69#include "crypto.h"
70#include "display.h"
71#include "capture.h"
72#include "audio.h"
73#include "audio/analysis.h"
74#include "video/webcam/webcam.h"
77
79#include "platform/init.h"
80#include "platform/terminal.h"
81#include "platform/symbols.h"
82#include "platform/system.h"
83#include "common.h"
84#include "log/logging.h"
85#include "options/options.h"
86#include "options/rcu.h" // For RCU-based options access
87#include "buffer_pool.h"
88#include "video/palette.h"
89#include "network/network.h"
90#include "network/tcp/client.h"
92#include "network/acip/acds.h"
93#include "network/acip/client.h"
95#include "webrtc.h"
96#include "connection_state.h"
97#include "util/path.h"
98
99#ifndef NDEBUG
100#include "debug/lock.h"
101#ifdef DEBUG_MEMORY
102#include "debug/memory.h"
103#endif
104#endif
105
106#include <signal.h>
107#include <stdlib.h>
108#include <stdio.h>
109#include <stdatomic.h>
110#include <unistd.h>
111#include <fcntl.h>
112
113#ifdef _WIN32
114#include <windows.h>
115#endif
116
117/* ============================================================================
118 * Global State Variables
119 * ============================================================================ */
120
122atomic_bool g_should_exit = false;
123
135
147
154
161 return atomic_load(&g_should_exit);
162}
163
168 atomic_store(&g_should_exit, true);
169}
170
184static bool console_ctrl_handler(console_ctrl_event_t event) {
185 // Handle Ctrl+C, Ctrl+Break, and SIGTERM (CONSOLE_CLOSE)
186 if (event != CONSOLE_CTRL_C && event != CONSOLE_CTRL_BREAK && event != CONSOLE_CLOSE) {
187 return false;
188 }
189
190 // If this is the second Ctrl-C, force exit
191 static _Atomic int ctrl_c_count = 0;
192 int count = atomic_fetch_add(&ctrl_c_count, 1) + 1;
193
194 if (count > 1) {
195#ifdef _WIN32
196 TerminateProcess(GetCurrentProcess(), 1);
197#else
198 _exit(1);
199#endif
200 }
201
202 // Signal all subsystems to shutdown (async-signal-safe operations only)
203 atomic_store(&g_should_exit, true);
204 server_connection_shutdown(); // Only uses atomics and socket_shutdown
205
206 // Let the main thread handle cleanup via atexit handlers.
207 // The webcam_flush() call in capture_stop_thread() will interrupt
208 // any blocking ReadSample() calls, allowing clean shutdown.
209
210 return true; // Event handled
211}
212
222#ifndef _WIN32
223static void sigwinch_handler(int sigwinch) {
224 (void)(sigwinch);
225
226 // Terminal was resized, update dimensions and recalculate aspect ratio
227 // ONLY if both width and height are auto (not manually set)
230
231 // Send new size to server if connected
234 log_warn("Failed to send terminal capabilities to server: %s", network_error_string());
235 } else {
238 }
239 }
240 }
241}
242#else
243// Windows-compatible signal handler (no-op implementation)
244static void sigwinch_handler(int sigwinch) {
245 (void)(sigwinch);
246 log_debug("SIGWINCH received (Windows no-op implementation)");
247}
248#endif
249
257static void shutdown_client() {
258 // Set global shutdown flag to stop all threads
259 atomic_store(&g_should_exit, true);
260
261 // IMPORTANT: Stop all protocol threads BEFORE cleaning up resources
262 // protocol_stop_connection() shuts down the socket to interrupt blocking recv(),
263 // then waits for the data reception thread and capture thread to exit.
264 // This prevents race conditions where threads access freed resources.
266
267 // Destroy client worker thread pool (all threads already stopped by protocol_stop_connection)
271 }
272
273 // Destroy TCP client instance (replaces scattered server.c cleanup)
274 if (g_client) {
276 log_debug("TCP client instance destroyed successfully");
277 }
278
279 // Now safe to cleanup server connection (socket already closed by protocol_stop_connection)
280 // Legacy cleanup - will be removed after full migration to tcp_client
282
283 // Cleanup capture subsystems (capture thread already stopped by protocol_stop_connection)
285
286 // Print audio analysis report if enabled
287 if (GET_OPTION(audio_analysis_enabled)) {
290 }
291
293
294#ifndef NDEBUG
295 // Stop lock debug thread BEFORE display_cleanup() because the debug thread uses
296 // _kbhit()/_getch() on Windows which interact with the console. If we close the
297 // CON handle first, the debug thread can hang on console I/O, blocking process exit.
298 lock_debug_cleanup();
299#endif
300
301 // Cleanup display and terminal state
303
304 // Cleanup core systems
306
307 // Clean up symbol cache (before log_destroy)
308 // This must be called BEFORE log_destroy() as symbol_cache_cleanup() uses log_debug()
309 // Safe to call even if atexit() runs - it's idempotent (checks g_symbol_cache_initialized)
310 // Also called via platform_cleanup() atexit handler, but explicit call ensures proper ordering
312
313 // Clean up binary path cache explicitly
314 // Note: This is also called by platform_cleanup() via atexit(), but it's idempotent
316
317 // Clean up errno context (allocated strings, backtrace symbols)
319
320 // Clean up RCU-based options state
322
323 log_info("Client shutdown complete");
324 log_destroy();
325
326#ifndef NDEBUG
327 // Join the debug thread as the very last thing (after log_destroy since thread may log)
328 lock_debug_cleanup_thread();
329#endif
330}
331
341static int initialize_client_systems(bool shared_init_completed) {
342 if (!shared_init_completed) {
343 // Initialize platform-specific functionality (Winsock, etc)
344 if (platform_init() != 0) {
345 (void)fprintf(stderr, "FATAL: Failed to initialize platform\n");
346 return 1;
347 }
348 (void)atexit(platform_cleanup);
349
350 // Initialize palette based on command line options
351 const options_t *opts = options_get();
352 const char *custom_chars = opts && opts->palette_custom_set ? opts->palette_custom : NULL;
353 palette_type_t palette_type = GET_OPTION(palette_type);
354 if (apply_palette_config(palette_type, custom_chars) != 0) {
355 log_error("Failed to apply palette configuration");
356 return 1;
357 }
358
359 // Initialize logging with appropriate settings
360 char *validated_log_file = NULL;
361 log_level_t log_level = GET_OPTION(log_level);
362 const char *log_file = opts && opts->log_file[0] != '\0' ? opts->log_file : "client.log";
363
364 if (strlen(log_file) > 0) {
365 asciichat_error_t log_path_result = path_validate_user_path(log_file, PATH_ROLE_LOG_FILE, &validated_log_file);
366 if (log_path_result != ASCIICHAT_OK || !validated_log_file || strlen(validated_log_file) == 0) {
367 // Invalid log file path, fall back to default and warn
368 (void)fprintf(stderr, "WARNING: Invalid log file path specified, using default 'client.log'\n");
369 log_init("client.log", log_level, true, true /* use_mmap */);
370 } else {
371 log_init(validated_log_file, log_level, true, true /* use_mmap */);
372 }
373 SAFE_FREE(validated_log_file);
374 } else {
375 log_init("client.log", log_level, true, true /* use_mmap */);
376 }
377
378 // Initialize memory debugging if enabled
379#ifdef DEBUG_MEMORY
380 bool quiet_mode = (GET_OPTION(quiet)) || (GET_OPTION(snapshot_mode));
381 debug_memory_set_quiet_mode(quiet_mode);
382 if (!(GET_OPTION(snapshot_mode))) {
383 (void)atexit(debug_memory_report);
384 }
385#endif
386
387 // Initialize global shared buffer pool
389 (void)atexit(buffer_pool_cleanup_global);
390 }
391
392 // Initialize WebRTC library (required for P2P DataChannel connections)
393 // Must be called regardless of shared_init_completed status
394 asciichat_error_t webrtc_result = webrtc_init();
395 if (webrtc_result != ASCIICHAT_OK) {
396 log_fatal("Failed to initialize WebRTC library: %s", asciichat_error_string(webrtc_result));
397 return ERROR_NETWORK;
398 }
399 (void)atexit(webrtc_cleanup);
400 log_debug("WebRTC library initialized successfully");
401
402 // Initialize client worker thread pool (always needed, even if shared init done)
404 g_client_worker_pool = thread_pool_create("client_workers");
406 log_fatal("Failed to create client worker thread pool");
407 return ERROR_THREAD;
408 }
409 }
410
411 // Ensure logging output is available for connection attempts
414
415 // Initialize display subsystem
416 if (display_init() != 0) {
417 log_fatal("Failed to initialize display subsystem");
418 return ERROR_DISPLAY;
419 }
420
421 // Initialize TCP client instance (replaces scattered server.c globals)
422 if (!g_client) {
424 if (!g_client) {
425 log_fatal("Failed to create TCP client instance");
426 return ERROR_NETWORK;
427 }
428 log_debug("TCP client instance created successfully");
429 }
430
431 // Initialize server connection management (legacy - will be migrated to tcp_client)
432 if (server_connection_init() != 0) {
433 log_fatal("Failed to initialize server connection");
434 return ERROR_NETWORK;
435 }
436
437 // Initialize capture subsystems
438 int capture_result = capture_init();
439 if (capture_result != 0) {
440 log_fatal("Failed to initialize capture subsystem");
441 return capture_result;
442 }
443
444 // Initialize audio if enabled
445 if (GET_OPTION(audio_enabled)) {
446 if (audio_client_init() != 0) {
447 log_fatal("Failed to initialize audio system");
448 return ERROR_AUDIO;
449 }
450
451 // Initialize audio analysis if requested
452 if (GET_OPTION(audio_analysis_enabled)) {
453 if (audio_analysis_init() != 0) {
454 log_warn("Failed to initialize audio analysis");
455 }
456 }
457 }
458
459 return 0;
460}
461
473int client_main(void) {
474 // Dispatcher already printed capabilities, but honor flag defensively
475 if (GET_OPTION(show_capabilities)) {
477 caps = apply_color_mode_override(caps);
479 return 0;
480 }
481
482 // Initialize all client subsystems (shared init already completed)
483 int init_result = initialize_client_systems(true);
484 if (init_result != 0) {
485 // Check if this is a webcam-related error and print help
486 if (init_result == ERROR_WEBCAM || init_result == ERROR_WEBCAM_IN_USE || init_result == ERROR_WEBCAM_PERMISSION) {
487 webcam_print_init_error_help(init_result);
488 FATAL(init_result, "%s", asciichat_error_string(init_result));
489 }
490 // For other errors, just exit with the error code
491 return init_result;
492 }
493
494 // Register cleanup function for graceful shutdown
495 (void)atexit(shutdown_client);
496
497 // Install console control handler for graceful Ctrl+C handling
498 // Uses SetConsoleCtrlHandler on Windows, sigaction on Unix - more reliable than CRT signal()
499 platform_set_console_ctrl_handler(console_ctrl_handler);
500
501 // Install SIGWINCH handler for terminal resize (Unix only, no-op on Windows)
502 platform_signal(SIGWINCH, sigwinch_handler);
503
504#ifndef _WIN32
505 // Ignore SIGPIPE - we'll handle write errors ourselves (not available on Windows)
506 platform_signal(SIGPIPE, SIG_IGN);
507#endif
508
509 // Keep terminal logging enabled so user can see connection attempts
510 // It will be disabled after first successful connection
511 // Note: No initial terminal reset - display will only be cleared when first frame arrives
512
513 // Track if we've ever successfully connected during this session
514 static bool has_ever_connected = false;
515
516 // Startup message only logged to file (terminal output is disabled by default)
517
518 /* ========================================================================
519 * Main Connection Loop
520 * ========================================================================
521 */
522
523 int reconnect_attempt = 0;
524 bool first_connection = true;
525
526 // LAN Discovery: If --scan flag is set, discover servers on local network
527 const options_t *opts = options_get();
528 if (opts && opts->lan_discovery &&
529 (opts->address[0] == '\0' || strcmp(opts->address, "127.0.0.1") == 0 ||
530 strcmp(opts->address, "localhost") == 0)) {
531 log_info("LAN discovery: --scan flag set, querying for available servers");
532
533 discovery_tui_config_t lan_config;
534 memset(&lan_config, 0, sizeof(lan_config));
535 lan_config.timeout_ms = 2000; // Wait up to 2 seconds for responses
536 lan_config.max_servers = 20; // Support up to 20 servers on LAN
537 lan_config.quiet = true; // Quiet during discovery, TUI will show status
538
539 int discovered_count = 0;
540 discovery_tui_server_t *discovered_servers = discovery_tui_query(&lan_config, &discovered_count);
541
542 // Use TUI for server selection
543 int selected_index = discovery_tui_select(discovered_servers, discovered_count);
544
545 if (selected_index < 0) {
546 // User cancelled or no servers found
547 if (discovered_count == 0) {
548 // No servers found - print message and prevent any further output
549 // Lock the terminal so other threads can't write
551
552 fprintf(stderr, "\n");
553 fprintf(stderr, "No ASCII-Chat servers found on the local network.\n");
554 fprintf(stderr, "Use 'ascii-chat client <address>' to connect manually.\n");
555 fflush(stderr);
556
557 // Log to file for debugging
558 log_file_msg("\n");
559 log_file_msg("No ASCII-Chat servers found on the local network.\n");
560 log_file_msg("Use 'ascii-chat client <address>' to connect manually.\n");
561
562 // Redirect stderr and stdout to /dev/null so cleanup handlers can't write to console
563 // This is safe because we've already printed our final message
564 int dev_null = open("/dev/null", O_WRONLY);
565 if (dev_null >= 0) {
566 dup2(dev_null, STDERR_FILENO);
567 dup2(dev_null, STDOUT_FILENO);
568 close(dev_null);
569 }
570
571 // Exit - cleanup handlers will try to write to /dev/null instead of console
572 exit(1);
573 }
574 // User cancelled (had servers to choose from but pressed cancel)
575 log_info("LAN discovery: User cancelled server selection");
576 if (discovered_servers) {
577 discovery_tui_free_results(discovered_servers);
578 }
579 return 1; // User cancelled
580 }
581
582 // Update options with discovered server's address and port
583 discovery_tui_server_t *selected = &discovered_servers[selected_index];
584 const char *selected_address = discovery_tui_get_best_address(selected);
585
586 // We need to modify options, but they're immutable via RCU
587 // Create a new options copy with updated address/port
588 options_t *opts_new = SAFE_MALLOC(sizeof(options_t), options_t *);
589 if (opts_new) {
590 memcpy(opts_new, opts, sizeof(options_t));
591 SAFE_STRNCPY(opts_new->address, selected_address, sizeof(opts_new->address));
592
593 // Format port as string for compatibility
594 char port_str[16];
595 snprintf(port_str, sizeof(port_str), "%u", selected->port);
596 SAFE_STRNCPY(opts_new->port, port_str, sizeof(opts_new->port));
597
598 log_info("LAN discovery: Selected server '%s' at %s:%s", selected->name, opts_new->address, opts_new->port);
599
600 // Note: In a real scenario, we'd update the global options via RCU
601 // For now, we'll use the updated values directly for connection
602 // This is a known limitation - proper RCU update requires more infrastructure
603 }
604
605 discovery_tui_free_results(discovered_servers);
606 }
607
608 // =========================================================================
609 // Connection Fallback Context Setup
610 // =========================================================================
611 //
612 // Initialize connection fallback context for 3-stage connection attempts:
613 // 1. Direct TCP (3s timeout)
614 // 2. WebRTC + STUN (8s timeout)
615 // 3. WebRTC + TURN (15s timeout)
616 //
617 connection_attempt_context_t connection_ctx = {0};
618 asciichat_error_t ctx_init_result =
619 connection_context_init(&connection_ctx,
620 GET_OPTION(prefer_webrtc), // --prefer-webrtc flag
621 GET_OPTION(no_webrtc), // --no-webrtc flag
622 GET_OPTION(webrtc_skip_stun), // --webrtc-skip-stun flag
623 GET_OPTION(webrtc_disable_turn) // --webrtc-disable-turn flag
624 );
625
626 if (ctx_init_result != ASCIICHAT_OK) {
627 log_error("Failed to initialize connection context");
628 return 1;
629 }
630
631 // =========================================================================
632 // PHASE 1: Parallel mDNS + ACDS Session Discovery
633 // =========================================================================
634 //
635 // When a session string is detected (e.g., "swift-river-mountain"),
636 // we perform parallel discovery on both mDNS (local LAN) and ACDS (internet):
637 //
638 // 1. **mDNS Discovery**: Query _ascii-chat._tcp.local for servers with
639 // matching session_string in TXT records (2s timeout)
640 // 2. **ACDS Discovery**: Lookup session on ACDS server (5s timeout)
641 // 3. **Race to Success**: Return first successful result, cancel the other
642 // 4. **Verification**: Check host_pubkey against --server-key or --acds-insecure
643 //
644 // Usage modes:
645 // - `ascii-chat swift-river-mountain` → mDNS-only (local LAN, no verification)
646 // - `ascii-chat --server-key PUBKEY swift-river-mountain` → verified
647 // - `ascii-chat --acds-insecure swift-river-mountain` → parallel without verification
648 //
649 const char *discovered_address = NULL;
650 const char *discovered_port = NULL;
651
652 const options_t *opts_discovery = options_get();
653 const char *session_string =
654 opts_discovery && opts_discovery->session_string[0] != '\0' ? opts_discovery->session_string : "";
655
656 if (session_string[0] != '\0') {
657 log_info("Session string detected: '%s' - performing parallel discovery (mDNS + ACDS)", session_string);
658
659 // Configure discovery coordinator
660 discovery_config_t discovery_cfg;
661 discovery_config_init_defaults(&discovery_cfg);
662
663 // Parse expected server key if provided
664 uint8_t expected_pubkey[32];
665 memset(expected_pubkey, 0, 32);
666 if (opts_discovery && opts_discovery->server_key[0] != '\0') {
667 asciichat_error_t parse_err = hex_to_pubkey(opts_discovery->server_key, expected_pubkey);
668 if (parse_err == ASCIICHAT_OK) {
669 discovery_cfg.expected_pubkey = expected_pubkey;
670 log_info("Server key verification enabled");
671 } else {
672 log_warn("Failed to parse server key - skipping verification");
673 }
674 }
675
676 // Enable insecure mode if requested
677 if (opts_discovery && opts_discovery->acds_insecure) {
678 discovery_cfg.insecure_mode = true;
679 log_warn("ACDS insecure mode enabled - no server key verification");
680 }
681
682 // Configure ACDS connection details
683 if (opts_discovery && opts_discovery->acds_server[0] != '\0') {
684 SAFE_STRNCPY(discovery_cfg.acds_server, opts_discovery->acds_server, sizeof(discovery_cfg.acds_server));
685 }
686 if (opts_discovery && opts_discovery->acds_port > 0) {
687 discovery_cfg.acds_port = (uint16_t)opts_discovery->acds_port;
688 }
689
690 // Set password for session join (use pointer since it persists through discovery)
691 if (opts_discovery && opts_discovery->password[0] != '\0') {
692 discovery_cfg.password = opts_discovery->password; // Safe: opts_discovery from options_get() persists
693 log_debug("Password configured for session join");
694 }
695
696 // Perform parallel discovery
697 discovery_result_t discovery_result;
698 memset(&discovery_result, 0, sizeof(discovery_result));
699 asciichat_error_t discovery_err = discover_session_parallel(session_string, &discovery_cfg, &discovery_result);
700
701 if (discovery_err != ASCIICHAT_OK || !discovery_result.success) {
702 fprintf(stderr, "Error: Failed to discover session '%s'\n", session_string);
703 fprintf(stderr, " - Not found via mDNS (local network)\n");
704 fprintf(stderr, " - Not found via ACDS (discovery server)\n");
705 fprintf(stderr, "\nDid you mean to:\n");
706 fprintf(stderr, " ascii-chat server # Start a new server\n");
707 fprintf(stderr, " ascii-chat client # Connect to localhost\n");
708 fprintf(stderr, " ascii-chat client HOST # Connect to specific host\n");
709 return 1;
710 }
711
712 // Log discovery result
713 const char *source_name = discovery_result.source == DISCOVERY_SOURCE_MDNS ? "mDNS (LAN)" : "ACDS (internet)";
714 log_info("Session discovered via %s: %s:%d", source_name, discovery_result.server_address,
715 discovery_result.server_port);
716
717 // Set discovered address/port for connection
718 // Copy to static buffers since discovery_result goes out of scope after this block
719 static char address_buffer[256];
720 static char port_buffer[8];
721 SAFE_STRNCPY(address_buffer, discovery_result.server_address, sizeof(address_buffer));
722 snprintf(port_buffer, sizeof(port_buffer), "%d", discovery_result.server_port);
723 discovered_address = address_buffer;
724 discovered_port = port_buffer;
725
726 // Populate session context for WebRTC fallback (if discovery came from ACDS)
727 if (discovery_result.source == DISCOVERY_SOURCE_ACDS) {
728 SAFE_STRNCPY(connection_ctx.session_ctx.session_string, session_string,
729 sizeof(connection_ctx.session_ctx.session_string));
730 memcpy(connection_ctx.session_ctx.session_id, discovery_result.session_id, 16);
731 memcpy(connection_ctx.session_ctx.participant_id, discovery_result.participant_id, 16);
732 SAFE_STRNCPY(connection_ctx.session_ctx.server_address, discovery_result.server_address,
733 sizeof(connection_ctx.session_ctx.server_address));
734 connection_ctx.session_ctx.server_port = discovery_result.server_port;
735 log_debug("Populated session context for WebRTC fallback (session='%s')", session_string);
736 }
737
738 log_info("Connecting to server: %s:%d", discovered_address, discovery_result.server_port);
739 }
740
741 const options_t *opts_conn = options_get();
742 while (!should_exit()) {
743 // Handle connection establishment or reconnection
744 const char *address = discovered_address
745 ? discovered_address
746 : (opts_conn && opts_conn->address[0] != '\0' ? opts_conn->address : "localhost");
747 const char *port_str =
748 discovered_port ? discovered_port : (opts_conn && opts_conn->port[0] != '\0' ? opts_conn->port : "27224");
749 int port = atoi(port_str);
750
751 // Update connection context with current attempt number
752 connection_ctx.reconnect_attempt = reconnect_attempt;
753
754 // Get ACDS server configuration from CLI options (defaults: 127.0.0.1:27225)
755 const char *acds_server = GET_OPTION(acds_server);
756 if (!acds_server || acds_server[0] == '\0') {
757 acds_server = "127.0.0.1"; // Fallback if option not set
758 }
759 int acds_port = GET_OPTION(acds_port);
760 if (acds_port <= 0 || acds_port > 65535) {
761 acds_port = 27225; // Fallback to default ACDS port
762 }
763
764 // Attempt connection with 3-stage fallback (TCP → STUN → TURN)
765 asciichat_error_t connection_result =
766 connection_attempt_with_fallback(&connection_ctx, address, (uint16_t)port, acds_server, (uint16_t)acds_port);
767
768 // Check if connection attempt succeeded
769 // Handle the error result appropriately
770 int connection_success = (connection_result == ASCIICHAT_OK) ? 0 : -1;
771
772 if (connection_success != 0) {
773 // TODO Part 5: Handle specific error codes from connection_result
774 // For now, treat all non-OK results as connection failure
775 // Authentication errors would be returned as ERROR_CRYPTO_HANDSHAKE
776
777 // In snapshot mode, exit immediately on connection failure - no retries
778 // Snapshot mode is for quick single-frame captures, not persistent connections
779 if (GET_OPTION(snapshot_mode)) {
780 log_error("Connection failed in snapshot mode - exiting without retry");
781 return 1;
782 }
783
784 // Connection failed - check if we should retry based on --reconnect setting
785 reconnect_attempt++;
786
787 // Get reconnect attempts setting (-1 = unlimited, 0 = no retry, >0 = retry N times)
788 int reconnect_attempts = GET_OPTION(reconnect_attempts);
789
790 // Check reconnection policy
791 if (reconnect_attempts == 0) {
792 // --reconnect off: Exit immediately on first failure
793 log_error("Connection failed (reconnection disabled via --reconnect off)");
794 return 1;
795 } else if (reconnect_attempts > 0 && reconnect_attempt > reconnect_attempts) {
796 // --reconnect N: Exceeded max retry attempts
797 log_error("Connection failed after %d attempts (limit set by --reconnect %d)", reconnect_attempt - 1,
798 reconnect_attempts);
799 return 1;
800 }
801 // else: reconnect_attempts == -1 (auto) means retry forever
802
803 if (has_ever_connected) {
806 } else {
807 // Add newline to separate from ASCII art display for first-time connection failures
808 printf("\n");
809 }
810
811 if (has_ever_connected) {
812 if (reconnect_attempts == -1) {
813 log_info("Reconnection attempt #%d... (unlimited retries)", reconnect_attempt);
814 } else {
815 log_info("Reconnection attempt #%d/%d...", reconnect_attempt, reconnect_attempts);
816 }
817 } else {
818 if (reconnect_attempts == -1) {
819 log_info("Connection attempt #%d... (unlimited retries)", reconnect_attempt);
820 } else {
821 log_info("Connection attempt #%d/%d...", reconnect_attempt, reconnect_attempts);
822 }
823 }
824
825 // Continue retrying based on reconnection policy
826 continue;
827 }
828
829 // Connection successful - reset counters and flags
830 reconnect_attempt = 0;
831 first_connection = false;
832
833 // Integrate the active transport from connection fallback into server connection layer
834 // (Transport is TCP for Stage 1, WebRTC DataChannel for Stages 2/3)
835 if (connection_ctx.active_transport) {
837 log_debug("Active transport integrated into server connection layer");
838 } else {
839 log_error("Connection succeeded but no active transport - this should never happen");
840 continue; // Retry connection
841 }
842
843 // Show appropriate connection message based on whether this is first connection or reconnection
844 if (!has_ever_connected) {
845 log_info("Connected successfully, starting worker threads");
846 has_ever_connected = true;
847 } else {
848 log_info("Reconnected successfully, starting worker threads");
849 }
850
851 // Start all worker threads for this connection
852 if (protocol_start_connection() != 0) {
853 log_error("Failed to start connection protocols");
855
856 // In snapshot mode, exit immediately on protocol failure - no retries
857 if (GET_OPTION(snapshot_mode)) {
858 log_error("Protocol startup failed in snapshot mode - exiting without retry");
859 return 1;
860 }
861
862 continue;
863 }
864
865 // Terminal logging is now disabled - ASCII display can begin cleanly
866 // Don't clear terminal here - let the first frame handler clear it
867 // This prevents clearing the terminal before we're ready to display content
868
869 /* ====================================================================
870 * Connection Monitoring Loop
871 * ====================================================================
872 */
873
874 // Monitor connection health until it breaks or shutdown is requested
876 // Check if any critical threads have exited (indicates connection lost)
878 log_info("Connection lost detected by protocol threads");
879 break;
880 }
881
882 platform_sleep_usec(100 * 1000); // 0.1 second monitoring interval
883 }
884
885 if (should_exit()) {
886 log_info("Shutdown requested, exiting main loop");
887 break;
888 }
889
890 // Connection broken - clean up this connection and prepare for reconnect
891 log_info("Connection lost, cleaning up for reconnection");
892
893 // Re-enable terminal logging when connection is lost for debugging reconnection
894 // (but only if we've ever successfully connected before)
895 if (has_ever_connected) {
896 printf("\n");
898 }
899
902
903 // In snapshot mode, exit immediately on connection loss - no reconnection
904 // Snapshot mode is for quick single-frame captures, not persistent connections
905 if (GET_OPTION(snapshot_mode)) {
906 log_error("Connection lost in snapshot mode - exiting without reconnection");
907 return 1;
908 }
909
910 // Add a brief delay before attempting reconnection to prevent excessive reconnection loops
911 if (has_ever_connected) {
912 log_info("Waiting 1 second before attempting reconnection...");
913 platform_sleep_usec(1000000); // 1 second delay
914 }
915
916 log_info("Cleanup complete, will attempt reconnection");
917 }
918
919 // Cleanup connection context (closes any active transports)
920 connection_context_cleanup(&connection_ctx);
921
922 log_info("ascii-chat client shutting down");
923 return 0;
924}
🔌 Cross-platform abstraction layer umbrella header for ascii-chat
int audio_analysis_init(void)
Initialize audio analysis.
Definition analysis.c:113
void audio_analysis_cleanup(void)
Cleanup audio analysis.
Definition analysis.c:882
void audio_analysis_print_report(void)
Print audio analysis report.
Definition analysis.c:517
Audio Analysis and Debugging Interface.
🗃️ Lock-Free Unified Memory Buffer Pool with Lazy Allocation
ascii-chat Client Media Capture Management Interface
void signal_exit()
Signal client to exit.
tcp_client_t * g_client
Global TCP client instance.
int client_main(void)
Client mode entry point for unified binary.
struct webrtc_peer_manager * g_peer_manager
Global WebRTC peer manager for P2P connections.
thread_pool_t * g_client_worker_pool
Global client worker thread pool.
bool should_exit()
Check if client should exit.
atomic_bool g_should_exit
asciichat_error_t connection_context_init(connection_attempt_context_t *ctx, bool prefer_webrtc, bool no_webrtc, bool webrtc_skip_stun, bool webrtc_disable_turn)
Initialize connection attempt context.
asciichat_error_t connection_attempt_with_fallback(connection_attempt_context_t *ctx, const char *server_address, uint16_t server_port, const char *acds_server, uint16_t acds_port)
Orchestrate connection attempt with automatic fallback.
void connection_context_cleanup(connection_attempt_context_t *ctx)
Cleanup connection attempt context.
🎯 Connection state machine for Phase 3 WebRTC fallback integration
🔍 Memory debugging helpers for tracking allocations in debug builds
asciichat_error_t hex_to_pubkey(const char *hex_str, uint8_t pubkey_out[32])
Convert hex string to Ed25519 public key.
Definition discovery.c:55
void discovery_config_init_defaults(discovery_config_t *config)
Initialize discovery config with defaults.
Definition discovery.c:633
asciichat_error_t discover_session_parallel(const char *session_string, const discovery_config_t *config, discovery_result_t *result)
Look up session in parallel on mDNS and ACDS.
Definition discovery.c:655
Parallel mDNS and ACDS session discovery.
void discovery_tui_free_results(discovery_tui_server_t *servers)
Free results from mDNS discovery.
discovery_tui_server_t * discovery_tui_query(const discovery_tui_config_t *config, int *out_count)
TUI wrapper around core mDNS discovery.
int discovery_tui_select(const discovery_tui_server_t *servers, int count)
TUI-based server selection with formatted display.
const char * discovery_tui_get_best_address(const discovery_tui_server_t *server)
Get best address for a server.
TUI-based service discovery for ascii-chat client.
ascii-chat Client Display Management Interface
void buffer_pool_cleanup_global(void)
void buffer_pool_init_global(void)
int audio_client_init()
Initialize audio subsystem.
void audio_cleanup()
Cleanup audio subsystem.
int capture_init()
Initialize capture subsystem.
Definition capture.c:428
void capture_cleanup()
Cleanup capture subsystem.
Definition capture.c:526
void server_connection_close()
Close the server connection gracefully.
bool server_connection_is_active()
Check if server connection is currently active.
void server_connection_shutdown()
Emergency connection shutdown for signal handlers.
int threaded_send_terminal_size_with_auto_detect(unsigned short width, unsigned short height)
Thread-safe terminal size packet transmission with auto-detection.
void server_connection_set_transport(acip_transport_t *transport)
Set ACIP transport instance from connection fallback.
int server_connection_init()
Initialize the server connection management subsystem.
void server_connection_cleanup()
Cleanup connection management subsystem.
int display_init()
Initialize what is necessary to display ascii frames.
Definition display.c:265
void display_full_reset()
Perform full display reset.
Definition display.c:301
void display_cleanup()
Cleanup display subsystem.
Definition display.c:385
void protocol_stop_connection()
Stop protocol connection handling.
int protocol_start_connection()
Start protocol connection handling.
bool protocol_connection_lost()
Check if connection has been lost.
unsigned short uint16_t
Definition common.h:57
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:358
#define SAFE_FREE(ptr)
Definition common.h:320
#define SAFE_MALLOC(size, cast)
Definition common.h:208
#define FATAL(code,...)
Exit with error code and custom message, with stack trace in debug builds.
Definition common.h:151
unsigned char uint8_t
Definition common.h:56
void asciichat_errno_cleanup(void)
Cleanup error system resources.
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_WEBCAM_IN_USE
Definition error_codes.h:62
@ ERROR_AUDIO
Definition error_codes.h:64
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_WEBCAM_PERMISSION
Definition error_codes.h:63
@ ERROR_DISPLAY
Definition error_codes.h:99
@ ERROR_WEBCAM
Definition error_codes.h:61
@ ERROR_THREAD
Definition error_codes.h:95
void log_truncate_if_large(void)
Manually truncate large log files.
#define log_warn(...)
Log a WARN message.
void log_destroy(void)
Destroy the logging system and close log file.
#define log_error(...)
Log an ERROR message.
#define log_fatal(...)
Log a FATAL message.
void log_init(const char *filename, log_level_t level, bool force_stderr, bool use_mmap)
Initialize the logging system.
bool log_lock_terminal(void)
Lock terminal output for exclusive access by the calling thread.
log_level_t
Logging levels enumeration.
Definition log/logging.h:59
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
#define log_file(...)
File-only logging - writes to log file only, no stderr output.
void log_file_msg(const char *fmt,...)
Log to file only, no stderr output.
void log_set_terminal_output(bool enabled)
Control stderr output to terminal.
const char * network_error_string()
Get human-readable error string for network errors.
Definition network.c:535
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
Definition options.h:644
void update_dimensions_to_terminal_size(options_t *opts)
Update dimensions to current terminal size.
const options_t * options_get(void)
Get current options (lock-free read)
Definition rcu.c:222
palette_type_t
Built-in palette type enumeration.
Definition palette.h:84
int apply_palette_config(palette_type_t type, const char *custom_chars)
Apply palette configuration (set global palette)
Definition palette.c:277
void symbol_cache_cleanup(void)
Clean up the symbol cache and free all resources.
Definition symbols.c:419
signal_handler_t platform_signal(int sig, signal_handler_t handler)
Set a signal handler.
terminal_capabilities_t apply_color_mode_override(terminal_capabilities_t caps)
Apply command-line overrides to detected capabilities.
void platform_cleanup(void)
Cleanup platform-specific subsystems.
bool platform_set_console_ctrl_handler(console_ctrl_handler_t handler)
Register a console control handler (for Ctrl+C, etc.)
void print_terminal_capabilities(const terminal_capabilities_t *caps)
Print terminal capabilities to stdout.
void platform_sleep_usec(unsigned int usec)
High-precision sleep function with microsecond precision.
terminal_capabilities_t detect_terminal_capabilities(void)
Detect terminal capabilities.
console_ctrl_event_t
Console control event types (cross-platform Ctrl+C handling)
Definition system.h:181
void platform_cleanup_binary_path_cache(void)
Cleanup the binary PATH cache.
Definition system.c:261
asciichat_error_t platform_init(void)
Initialize platform-specific subsystems.
@ CONSOLE_CTRL_BREAK
Definition system.h:183
@ CONSOLE_CTRL_C
Definition system.h:182
@ CONSOLE_CLOSE
Definition system.h:184
asciichat_error_t path_validate_user_path(const char *input, path_role_t role, char **normalized_out)
Validate and canonicalize a user-supplied filesystem path.
Definition path.c:498
@ PATH_ROLE_LOG_FILE
Definition path.h:255
void webcam_print_init_error_help(asciichat_error_t error_code)
Print helpful error diagnostics for webcam initialization failures.
Definition webcam.c:229
void webrtc_cleanup(void)
Cleanup WebRTC library resources.
asciichat_error_t webrtc_init(void)
Initialize WebRTC library (libdatachannel)
Platform initialization and static synchronization helpers.
ACIP client-side protocol library.
tcp_client_t * tcp_client_create(void)
Create and initialize TCP client.
void tcp_client_destroy(tcp_client_t **client_ptr)
Destroy TCP client and free resources.
🔒 Lock debugging and deadlock detection system for ascii-chat
📝 Logging API with multiple log levels and terminal output control
ASCII-Chat Discovery Service (ACDS) Protocol Message Formats.
🌐 Core network I/O operations with timeout support
ASCIICHAT_API bool auto_width
ASCIICHAT_API bool auto_height
⚙️ Command-line options parsing and configuration management for ascii-chat
ASCII Palette Management for Video-to-ASCII Conversion.
📂 Path Manipulation Utilities
void options_state_shutdown(void)
Shutdown RCU options system.
Definition rcu.c:197
ascii-chat Server Mode Entry Point Header
ascii-chat Client Audio Processing Management Interface
ascii-chat Client Server Connection Management Interface
Client-side WebRTC signaling callback implementations.
Server cryptographic operations and per-client handshake management.
Server packet processing and protocol implementation.
Master context for connection attempt with fallback.
uint32_t reconnect_attempt
Reconnection attempt number (1st, 2nd, etc.)
acip_transport_t * active_transport
Currently active transport (whichever succeeded)
connection_session_context_t session_ctx
Session context from ACDS.
char session_string[128]
Session string (e.g., "mystic-stone-obelisk")
uint16_t server_port
Server port for connection.
uint8_t session_id[16]
Session UUID (binary)
char server_address[64]
Server IP/hostname for connection.
uint8_t participant_id[16]
Client's participant UUID (binary)
Session discovery configuration.
Definition discovery.h:111
const char * password
Optional session password (NULL if none)
Definition discovery.h:127
uint16_t acds_port
ACDS server port (default: 27225)
Definition discovery.h:118
char acds_server[256]
ACDS server address (e.g., "localhost" or "acds.ascii-chat.com")
Definition discovery.h:117
bool insecure_mode
Allow no verification (–acds-insecure flag)
Definition discovery.h:114
const uint8_t * expected_pubkey
Expected server pubkey (NULL = no verification)
Definition discovery.h:113
Result from session discovery.
Definition discovery.h:85
uint16_t server_port
Server port (typically 27224)
Definition discovery.h:89
uint8_t participant_id[16]
Assigned participant ID (from SESSION_JOIN)
Definition discovery.h:101
uint8_t session_id[16]
ACDS session UUID.
Definition discovery.h:100
char server_address[256]
Server IP or hostname.
Definition discovery.h:88
bool success
Discovery succeeded.
Definition discovery.h:86
enum discovery_result_t::@3 source
Which discovery method found the server.
Configuration for TUI discovery.
int max_servers
Maximum servers to collect (default: 20)
bool quiet
Suppress discovery messages (default: false)
int timeout_ms
Maximum time to wait for responses (default: 2000)
Discovered server information from mDNS.
char name[256]
Service instance name (e.g., "swift-river-canyon")
uint16_t port
Server port number.
Consolidated options structure.
Definition options.h:439
bool acds_insecure
ACDS: skip server key verification (MITM-vulnerable, requires explicit opt-in)
Definition options.h:557
char port[256]
Server port number.
Definition options.h:464
char password[256]
Password string.
Definition options.h:543
bool lan_discovery
Enable LAN service discovery via mDNS (client only)
Definition options.h:481
char acds_server[256]
ACDS server address (default: 127.0.0.1)
Definition options.h:472
char session_string[64]
Session string for ACDS discovery (client only)
Definition options.h:466
char palette_custom[256]
Custom palette characters.
Definition options.h:582
bool palette_custom_set
True if custom palette was set.
Definition options.h:583
char server_key[256]
Expected server public key (client)
Definition options.h:546
int acds_port
ACDS server port (default: 27225)
Definition options.h:473
char log_file[256]
Log file path.
Definition options.h:535
char address[256]
Server address (client) or bind address (server)
Definition options.h:462
TCP client connection and state management.
Complete terminal capabilities structure.
Definition terminal.h:485
Thread pool structure.
WebRTC peer manager structure.
Symbol Resolution Cache for Backtrace Addresses.
Cross-platform system functions interface for ascii-chat.
🖥️ Cross-platform terminal interface for ascii-chat
void thread_pool_destroy(thread_pool_t *pool)
Destroy a thread pool.
Definition thread_pool.c:43
thread_pool_t * thread_pool_create(const char *pool_name)
Create a new thread pool.
Definition thread_pool.c:12