Client mode entry point for unified binary.
473 {
474
479 return 0;
480 }
481
482
483 int init_result = initialize_client_systems(true);
484 if (init_result != 0) {
485
488 FATAL(init_result,
"%s", asciichat_error_string(init_result));
489 }
490
491 return init_result;
492 }
493
494
495 (void)atexit(shutdown_client);
496
497
498
500
501
503
504#ifndef _WIN32
505
507#endif
508
509
510
511
512
513
514 static bool has_ever_connected = false;
515
516
517
518
519
520
521
522
523 int reconnect_attempt = 0;
524 bool first_connection = true;
525
526
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
534 memset(&lan_config, 0, sizeof(lan_config));
537 lan_config.
quiet =
true;
538
539 int discovered_count = 0;
541
542
544
545 if (selected_index < 0) {
546
547 if (discovered_count == 0) {
548
549
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
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
563
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
572 exit(1);
573 }
574
575 log_info(
"LAN discovery: User cancelled server selection");
576 if (discovered_servers) {
578 }
579 return 1;
580 }
581
582
585
586
587
589 if (opts_new) {
590 memcpy(opts_new, opts,
sizeof(
options_t));
592
593
594 char port_str[16];
595 snprintf(port_str,
sizeof(port_str),
"%u", selected->
port);
597
599
600
601
602
603 }
604
606 }
607
608
609
610
611
612
613
614
615
616
624 );
625
627 log_error(
"Failed to initialize connection context");
628 return 1;
629 }
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649 const char *discovered_address = NULL;
650 const char *discovered_port = NULL;
651
653 const char *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
662
663
665 memset(expected_pubkey, 0, 32);
666 if (opts_discovery && opts_discovery->
server_key[0] !=
'\0') {
670 log_info(
"Server key verification enabled");
671 } else {
672 log_warn(
"Failed to parse server key - skipping verification");
673 }
674 }
675
676
679 log_warn(
"ACDS insecure mode enabled - no server key verification");
680 }
681
682
683 if (opts_discovery && opts_discovery->
acds_server[0] !=
'\0') {
685 }
686 if (opts_discovery && opts_discovery->
acds_port > 0) {
688 }
689
690
691 if (opts_discovery && opts_discovery->
password[0] !=
'\0') {
693 log_debug(
"Password configured for session join");
694 }
695
696
698 memset(&discovery_result, 0, sizeof(discovery_result));
700
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
713 const char *source_name = discovery_result.
source == DISCOVERY_SOURCE_MDNS ?
"mDNS (LAN)" :
"ACDS (internet)";
716
717
718
719 static char address_buffer[256];
720 static char port_buffer[8];
722 snprintf(port_buffer,
sizeof(port_buffer),
"%d", discovery_result.
server_port);
723 discovered_address = address_buffer;
724 discovered_port = port_buffer;
725
726
727 if (discovery_result.
source == DISCOVERY_SOURCE_ACDS) {
735 log_debug(
"Populated session context for WebRTC fallback (session='%s')", session_string);
736 }
737
739 }
740
743
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
753
754
755 const char *acds_server =
GET_OPTION(acds_server);
756 if (!acds_server || acds_server[0] == '\0') {
757 acds_server = "127.0.0.1";
758 }
760 if (acds_port <= 0 || acds_port > 65535) {
761 acds_port = 27225;
762 }
763
764
767
768
769
770 int connection_success = (connection_result ==
ASCIICHAT_OK) ? 0 : -1;
771
772 if (connection_success != 0) {
773
774
775
776
777
778
780 log_error(
"Connection failed in snapshot mode - exiting without retry");
781 return 1;
782 }
783
784
785 reconnect_attempt++;
786
787
788 int reconnect_attempts =
GET_OPTION(reconnect_attempts);
789
790
791 if (reconnect_attempts == 0) {
792
793 log_error(
"Connection failed (reconnection disabled via --reconnect off)");
794 return 1;
795 } else if (reconnect_attempts > 0 && reconnect_attempt > reconnect_attempts) {
796
797 log_error(
"Connection failed after %d attempts (limit set by --reconnect %d)", reconnect_attempt - 1,
798 reconnect_attempts);
799 return 1;
800 }
801
802
803 if (has_ever_connected) {
806 } else {
807
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
826 continue;
827 }
828
829
830 reconnect_attempt = 0;
831 first_connection = false;
832
833
834
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;
841 }
842
843
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
853 log_error(
"Failed to start connection protocols");
855
856
858 log_error(
"Protocol startup failed in snapshot mode - exiting without retry");
859 return 1;
860 }
861
862 continue;
863 }
864
865
866
867
868
869
870
871
872
873
874
876
878 log_info(
"Connection lost detected by protocol threads");
879 break;
880 }
881
883 }
884
886 log_info(
"Shutdown requested, exiting main loop");
887 break;
888 }
889
890
891 log_info(
"Connection lost, cleaning up for reconnection");
892
893
894
895 if (has_ever_connected) {
896 printf("\n");
898 }
899
902
903
904
906 log_error(
"Connection lost in snapshot mode - exiting without reconnection");
907 return 1;
908 }
909
910
911 if (has_ever_connected) {
912 log_info(
"Waiting 1 second before attempting reconnection...");
914 }
915
916 log_info(
"Cleanup complete, will attempt reconnection");
917 }
918
919
921
922 log_info(
"ascii-chat client shutting down");
923 return 0;
924}
bool should_exit()
Check if client 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.
asciichat_error_t hex_to_pubkey(const char *hex_str, uint8_t pubkey_out[32])
Convert hex string to Ed25519 public key.
void discovery_config_init_defaults(discovery_config_t *config)
Initialize discovery config with defaults.
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.
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.
void server_connection_close()
Close the server connection gracefully.
bool server_connection_is_active()
Check if server connection is currently active.
void server_connection_set_transport(acip_transport_t *transport)
Set ACIP transport instance from connection fallback.
void display_full_reset()
Perform full display reset.
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.
#define SAFE_STRNCPY(dst, src, size)
#define SAFE_MALLOC(size, cast)
#define FATAL(code,...)
Exit with error code and custom message, with stack trace in debug builds.
asciichat_error_t
Error and exit codes - unified status values (0-255)
@ ERROR_WEBCAM_PERMISSION
#define log_warn(...)
Log a WARN message.
#define log_error(...)
Log an ERROR message.
bool log_lock_terminal(void)
Lock terminal output for exclusive access by the calling thread.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
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.
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
const options_t * options_get(void)
Get current options (lock-free read)
void webcam_print_init_error_help(asciichat_error_t error_code)
Print helpful error diagnostics for webcam initialization failures.
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.
const char * password
Optional session password (NULL if none)
uint16_t acds_port
ACDS server port (default: 27225)
char acds_server[256]
ACDS server address (e.g., "localhost" or "acds.ascii-chat.com")
bool insecure_mode
Allow no verification (–acds-insecure flag)
const uint8_t * expected_pubkey
Expected server pubkey (NULL = no verification)
Result from session discovery.
uint16_t server_port
Server port (typically 27224)
uint8_t participant_id[16]
Assigned participant ID (from SESSION_JOIN)
uint8_t session_id[16]
ACDS session UUID.
char server_address[256]
Server IP or hostname.
bool success
Discovery succeeded.
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.
bool acds_insecure
ACDS: skip server key verification (MITM-vulnerable, requires explicit opt-in)
char port[256]
Server port number.
char password[256]
Password string.
bool lan_discovery
Enable LAN service discovery via mDNS (client only)
char acds_server[256]
ACDS server address (default: 127.0.0.1)
char session_string[64]
Session string for ACDS discovery (client only)
char server_key[256]
Expected server public key (client)
int acds_port
ACDS server port (default: 27225)
char address[256]
Server address (client) or bind address (server)
Complete terminal capabilities structure.