57#include <ascii-chat/platform/network.h>
72#include <ascii-chat/common.h>
73#include <ascii-chat/util/endian.h>
74#include <ascii-chat/util/ip.h>
75#include <ascii-chat/util/time.h>
76#include <ascii-chat/uthash/uthash.h>
77#include <ascii-chat/platform/abstraction.h>
78#include <ascii-chat/platform/socket.h>
79#include <ascii-chat/platform/init.h>
80#include <ascii-chat/platform/question.h>
81#include <ascii-chat/platform/terminal.h>
82#include <ascii-chat/video/image.h>
83#include <ascii-chat/video/simd/ascii_simd.h>
84#include <ascii-chat/video/simd/common.h>
85#include <ascii-chat/asciichat_errno.h>
86#include <ascii-chat/network/network.h>
87#include <ascii-chat/network/tcp/server.h>
88#include <ascii-chat/network/websocket/server.h>
89#include <ascii-chat/network/acip/acds_client.h>
90#include <ascii-chat/discovery/strings.h>
91#include <ascii-chat/network/webrtc/stun.h>
92#include <ascii-chat/thread_pool.h>
93#include <ascii-chat/options/options.h>
94#include <ascii-chat/options/rcu.h>
95#include <ascii-chat/buffer_pool.h>
96#include <ascii-chat/audio/mixer.h>
97#include <ascii-chat/audio/audio.h>
101#include <ascii-chat/platform/string.h>
102#include <ascii-chat/platform/symbols.h>
103#include <ascii-chat/platform/system.h>
104#include <ascii-chat/crypto/keys.h>
105#include <ascii-chat/crypto/handshake/server.h>
106#include <ascii-chat/crypto/handshake/common.h>
107#include <ascii-chat/network/rate_limit/rate_limit.h>
108#include <ascii-chat/network/mdns/mdns.h>
109#include <ascii-chat/network/mdns/discovery.h>
110#include <ascii-chat/network/errors.h>
111#include <ascii-chat/network/nat/upnp.h>
112#include <ascii-chat/network/webrtc/peer_manager.h>
113#include <ascii-chat/network/webrtc/webrtc.h>
114#include <ascii-chat/network/acip/send.h>
115#include <ascii-chat/network/acip/protocol.h>
116#include <ascii-chat/network/acip/transport.h>
117#include <ascii-chat/network/acip/handlers.h>
118#include <ascii-chat/network/acip/server.h>
119#include <ascii-chat/network/acip/client.h>
120#include <ascii-chat/ui/server_status.h>
121#include <ascii-chat/log/interactive_grep.h>
122#include <ascii-chat/log/json.h>
123#include <ascii-chat/platform/keyboard.h>
124#include <ascii-chat/debug/memory.h>
152static bool check_shutdown(
void) {
211static tcp_server_t g_tcp_server;
221static websocket_server_t g_websocket_server;
228static asciichat_thread_t g_websocket_server_thread;
229static bool g_websocket_server_thread_started =
false;
237static time_t g_server_start_time = 0;
245static uint64_t g_last_status_update = 0;
253static asciichat_thread_t g_status_screen_thread;
263static asciichat_thread_t g_keyboard_thread;
264static _Atomic
bool g_keyboard_thread_running =
false;
269#define KEYBOARD_QUEUE_SIZE 256
271static _Atomic
size_t g_keyboard_queue_head = 0;
272static _Atomic
size_t g_keyboard_queue_tail = 0;
274static bool keyboard_queue_push(keyboard_key_t key) {
275 size_t head = atomic_load(&g_keyboard_queue_head);
277 if (next_head == atomic_load(&g_keyboard_queue_tail)) {
280 g_keyboard_queue[head] = key;
281 atomic_store(&g_keyboard_queue_head, next_head);
285static keyboard_key_t keyboard_queue_pop(
void) {
286 size_t tail = atomic_load(&g_keyboard_queue_tail);
287 if (tail == atomic_load(&g_keyboard_queue_head)) {
290 keyboard_key_t key = g_keyboard_queue[tail];
303static void *keyboard_thread_func(
void *arg) {
305 log_debug(
"Keyboard thread started (polling mode, 100ms interval)");
307 while (atomic_load(&g_keyboard_thread_running)) {
308 keyboard_key_t key = keyboard_read_with_timeout(100);
309 if (key != KEY_NONE) {
310 keyboard_queue_push(key);
314 log_debug(
"Keyboard thread exiting");
324static char g_session_string[64] = {0};
329static bool g_session_is_mdns_only =
false;
340static nat_upnp_context_t *g_upnp_ctx = NULL;
362static acds_client_t *g_acds_client = NULL;
372static acip_transport_t *g_acds_transport = NULL;
380static uint8_t g_server_participant_id[16] = {0};
402static asciichat_thread_t g_acds_receive_thread;
411static bool g_acds_receive_thread_started =
false;
422static asciichat_thread_t g_acds_ping_thread;
431static bool g_acds_ping_thread_started =
false;
439static thread_pool_t *g_server_worker_pool = NULL;
563static void server_handle_sigint(
int sigint) {
573 static _Atomic
int sigint_count = 0;
574 int count = atomic_fetch_add(&sigint_count, 1) + 1;
576 platform_force_exit(1);
588 static const char sigint_msg[] =
"\nSIGINT received - shutting down server...\n";
589 (void)write(STDERR_FILENO, sigint_msg,
sizeof(sigint_msg) - 1);
593 atomic_store(&g_tcp_server.running,
false);
594 if (g_tcp_server.listen_socket != INVALID_SOCKET_VALUE) {
595 socket_close(g_tcp_server.listen_socket);
596 g_tcp_server.listen_socket = INVALID_SOCKET_VALUE;
598 if (g_tcp_server.listen_socket6 != INVALID_SOCKET_VALUE) {
599 socket_close(g_tcp_server.listen_socket6);
600 g_tcp_server.listen_socket6 = INVALID_SOCKET_VALUE;
606 atomic_store(&g_websocket_server.running,
false);
643static asciichat_error_t server_send_sdp(
const uint8_t
session_id[16],
const uint8_t recipient_id[16],
644 const char *sdp_type,
const char *sdp,
void *user_data) {
647 if (!g_acds_transport) {
648 return SET_ERRNO(ERROR_INVALID_STATE,
"ACDS transport not available for SDP relay");
652 size_t sdp_len = strlen(sdp);
653 if (sdp_len == 0 || sdp_len >= 8192) {
654 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid SDP length: %zu", sdp_len);
658 size_t total_len =
sizeof(acip_webrtc_sdp_t) + sdp_len;
659 uint8_t *packet = SAFE_MALLOC(total_len, uint8_t *);
661 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate SDP packet");
665 acip_webrtc_sdp_t *header = (acip_webrtc_sdp_t *)packet;
668 memcpy(header->sender_id, g_server_participant_id, 16);
669 memcpy(header->recipient_id, recipient_id, 16);
670 header->sdp_type = (strcmp(sdp_type,
"offer") == 0) ? 0 : 1;
671 header->sdp_len = HOST_TO_NET_U16((uint16_t)sdp_len);
674 memcpy(packet +
sizeof(acip_webrtc_sdp_t), sdp, sdp_len);
676 log_debug(
"Server sending WebRTC SDP %s to participant (sender=%02x%02x..., recipient=%02x%02x...) via ACDS",
677 sdp_type, g_server_participant_id[0], g_server_participant_id[1], recipient_id[0], recipient_id[1]);
680 asciichat_error_t result =
685 if (result != ASCIICHAT_OK) {
686 return SET_ERRNO(result,
"Failed to send SDP via ACDS");
705static asciichat_error_t server_send_ice(
const uint8_t
session_id[16],
const uint8_t recipient_id[16],
706 const char *candidate,
const char *mid,
void *user_data) {
709 if (!g_acds_transport) {
710 return SET_ERRNO(ERROR_INVALID_STATE,
"ACDS transport not available for ICE relay");
714 size_t candidate_len = strlen(candidate);
715 size_t mid_len = strlen(mid);
716 size_t payload_len = candidate_len + 1 + mid_len + 1;
718 if (payload_len >= 8192) {
719 return SET_ERRNO(ERROR_INVALID_PARAM,
"ICE payload too large: %zu", payload_len);
723 size_t total_len =
sizeof(acip_webrtc_ice_t) + payload_len;
724 uint8_t *packet = SAFE_MALLOC(total_len, uint8_t *);
726 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate ICE packet");
730 acip_webrtc_ice_t *header = (acip_webrtc_ice_t *)packet;
733 memcpy(header->sender_id, g_server_participant_id, 16);
734 memcpy(header->recipient_id, recipient_id, 16);
735 header->candidate_len = HOST_TO_NET_U16((uint16_t)candidate_len);
738 uint8_t *payload = packet +
sizeof(acip_webrtc_ice_t);
739 memcpy(payload, candidate, candidate_len);
740 payload[candidate_len] =
'\0';
741 memcpy(payload + candidate_len + 1, mid, mid_len);
742 payload[candidate_len + 1 + mid_len] =
'\0';
744 log_debug(
"Server sending WebRTC ICE candidate to participant (%.8s..., mid=%s) via ACDS", (
const char *)recipient_id,
746 log_debug(
" [2] Before ACDS send - candidate: '%s' (len=%zu)", candidate, strlen(candidate));
747 log_debug(
" [2] Before ACDS send - mid: '%s' (len=%zu)", mid, strlen(mid));
748 log_debug(
" [2] Before ACDS send - payload_len=%zu, header.candidate_len=%u", payload_len,
749 NET_TO_HOST_U16(header->candidate_len));
752 log_debug(
" [2] Hex dump of payload being sent (first 100 bytes):");
753 for (
size_t i = 0; i < 100 && i < payload_len; i += 16) {
755 char ascii[20] = {0};
756 for (
size_t j = 0; j < 16 && (i + j) < 100 && (i + j) < payload_len; j++) {
757 snprintf(hex + j * 3,
sizeof(hex) - j * 3,
"%02x ", payload[i + j]);
758 ascii[j] = (payload[i + j] >= 32 && payload[i + j] < 127) ? payload[i + j] :
'.';
760 log_debug(
" [%04zx] %-48s %s", i, hex, ascii);
764 asciichat_error_t result =
769 if (result != ASCIICHAT_OK) {
770 return SET_ERRNO(result,
"Failed to send ICE via ACDS");
786static void on_webrtc_transport_ready(acip_transport_t *transport,
const uint8_t
participant_id[16],
void *user_data) {
789 log_error(
"on_webrtc_transport_ready: Invalid parameters");
796 log_debug(
"WebRTC transport ready for participant %.8s...", (
const char *)
participant_id);
799 char participant_str[33];
800 for (
int i = 0; i < 16; i++) {
803 participant_str[32] =
'\0';
809 log_error(
"Failed to add WebRTC client for participant %s", participant_str);
814 log_debug(
"Successfully added WebRTC client ID=%d for participant %s", client_id, participant_str);
827static void on_webrtc_sdp_server(
const acip_webrtc_sdp_t *sdp,
size_t total_len,
void *ctx) {
830 if (!sdp || !g_webrtc_peer_manager) {
831 log_error(
"on_webrtc_sdp_server: Invalid parameters or peer_manager not initialized");
836 uint16_t sdp_len = NET_TO_HOST_U16(sdp->sdp_len);
837 if (total_len <
sizeof(acip_webrtc_sdp_t) + sdp_len) {
838 log_error(
"on_webrtc_sdp_server: Invalid packet length (total=%zu, expected>=%zu)", total_len,
839 sizeof(acip_webrtc_sdp_t) + sdp_len);
844 const char *sdp_type = (sdp->sdp_type == 0) ?
"offer" :
"answer";
846 log_debug(
"Received WebRTC SDP %s from participant %.8s... (len=%u)", sdp_type, (
const char *)sdp->sender_id,
852 if (result != ASCIICHAT_OK) {
853 log_error(
"Failed to handle remote SDP from participant %.8s...: %s", (
const char *)sdp->sender_id,
854 asciichat_error_string(result));
868static void on_webrtc_ice_server(
const acip_webrtc_ice_t *ice,
size_t total_len,
void *ctx) {
871 if (!ice || !g_webrtc_peer_manager) {
872 log_error(
"on_webrtc_ice_server: Invalid parameters or peer_manager not initialized");
877 uint16_t payload_len = NET_TO_HOST_U16(ice->candidate_len);
878 if (total_len <
sizeof(acip_webrtc_ice_t) + payload_len) {
879 log_error(
"on_webrtc_ice_server: Invalid packet length (total=%zu, expected>=%zu)", total_len,
880 sizeof(acip_webrtc_ice_t) + payload_len);
884 log_debug(
"Received WebRTC ICE candidate from participant %.8s...", (
const char *)ice->sender_id);
889 if (result != ASCIICHAT_OK) {
890 log_error(
"Failed to handle remote ICE candidate from participant %.8s...: %s", (
const char *)ice->sender_id,
891 asciichat_error_string(result));
904static void advertise_mdns_with_session(
const char *session_string, uint16_t port) {
906 log_debug(
"mDNS context not initialized, skipping advertisement");
911 char hostname[256] = {0};
912 char session_name[256] =
"ascii-chat-Server";
913 if (gethostname(hostname,
sizeof(hostname) - 1) == 0 && strlen(hostname) > 0) {
914 safe_snprintf(session_name,
sizeof(session_name),
"%s", hostname);
918 char txt_session_string[512];
919 char txt_host_pubkey[512];
920 const char *txt_records[2];
924 safe_snprintf(txt_session_string,
sizeof(txt_session_string),
"session_string=%s", session_string);
925 txt_records[txt_count++] = txt_session_string;
932 safe_snprintf(txt_host_pubkey,
sizeof(txt_host_pubkey),
"host_pubkey=%s", hex_pubkey);
933 txt_records[txt_count++] = txt_host_pubkey;
934 log_debug(
"mDNS: Host pubkey=%s", hex_pubkey);
937 safe_snprintf(txt_host_pubkey,
sizeof(txt_host_pubkey),
"host_pubkey=");
938 for (
int i = 0; i < 32; i++) {
939 safe_snprintf(txt_host_pubkey + strlen(txt_host_pubkey),
sizeof(txt_host_pubkey) - strlen(txt_host_pubkey),
"00");
941 txt_records[txt_count++] = txt_host_pubkey;
942 log_debug(
"mDNS: Encryption disabled, advertising zero pubkey");
945 asciichat_mdns_service_t service = {
946 .name = session_name,
947 .type =
"_ascii-chat._tcp",
950 .txt_records = txt_records,
951 .txt_count = txt_count,
955 if (mdns_advertise_result != ASCIICHAT_OK) {
956 LOG_ERRNO_IF_SET(
"Failed to advertise mDNS service");
957 log_warn(
"mDNS advertising failed - LAN discovery disabled");
961 log_info(
"🌐 mDNS: Server advertised as '%s.local' on LAN", session_name);
962 log_debug(
"mDNS: Service advertised as '%s.local' (name=%s, port=%d, session=%s, txt_count=%d)", service.type,
963 service.name, service.port, session_string, service.txt_count);
976static void *acds_ping_thread(
void *arg) {
979 log_debug(
"ACDS keepalive ping thread started");
982 if (!g_acds_transport) {
983 log_debug(
"ACDS transport destroyed, exiting ping thread");
988 socket_t acds_socket = g_acds_transport->methods->get_socket(g_acds_transport);
989 if (acds_socket != INVALID_SOCKET_VALUE) {
990 asciichat_error_t ping_result =
packet_send(acds_socket, PACKET_TYPE_PING, NULL, 0);
991 if (ping_result == ASCIICHAT_OK) {
992 log_debug(
"ACDS keepalive: Sent periodic PING");
994 log_warn(
"ACDS keepalive: Failed to send PING: %s", asciichat_error_string(ping_result));
1004 log_debug(
"ACDS keepalive ping thread exiting");
1020static void on_acds_ping(
void *ctx) {
1022 log_debug(
"ACDS keepalive: Received PING from ACDS, responding with PONG");
1023 if (g_acds_transport) {
1031static void on_acds_pong(
void *ctx) {
1033 log_debug(
"ACDS keepalive: Received PONG from ACDS server");
1036static void *acds_receive_thread(
void *arg) {
1039 log_debug(
"ACDS receive thread started");
1042 acip_client_callbacks_t callbacks = {
1043 .on_ascii_frame = NULL,
1045 .on_webrtc_sdp = on_webrtc_sdp_server,
1046 .on_webrtc_ice = on_webrtc_ice_server,
1047 .on_session_joined = NULL,
1048 .on_ping = on_acds_ping,
1049 .on_pong = on_acds_pong,
1056 if (!g_acds_transport) {
1057 log_warn(
"ACDS transport is NULL, exiting receive thread");
1063 if (result != ASCIICHAT_OK) {
1065 asciichat_error_context_t err_ctx;
1066 bool has_context = HAS_ERRNO(&err_ctx);
1069 if (result == ERROR_NETWORK_TIMEOUT) {
1077 if (result == ERROR_NETWORK) {
1078 if (has_context && strstr(err_ctx.context_message,
"Failed to receive packet") != NULL) {
1080 log_debug(
"ACDS receive timeout, continuing to wait for packets");
1082 }
else if (has_context && (strstr(err_ctx.context_message,
"EOF") != NULL ||
1083 strstr(err_ctx.context_message,
"closed") != NULL)) {
1085 log_warn(
"ACDS connection closed: %s", err_ctx.context_message);
1089 log_warn(
"ACDS connection error: %s", has_context ? err_ctx.context_message :
"unknown");
1095 log_error(
"ACDS receive error: %s, exiting receive thread", asciichat_error_string(result));
1101 log_debug(
"ACDS receive thread exiting (server shutdown)");
1103 log_warn(
"ACDS receive thread exiting unexpectedly");
1132static void server_handle_sigterm(
int sigterm) {
1138 log_console(LOG_INFO,
"SIGTERM received - shutting down server...");
1142 atomic_store(&g_tcp_server.running,
false);
1143 if (g_tcp_server.listen_socket != INVALID_SOCKET_VALUE) {
1144 socket_close(g_tcp_server.listen_socket);
1145 g_tcp_server.listen_socket = INVALID_SOCKET_VALUE;
1147 if (g_tcp_server.listen_socket6 != INVALID_SOCKET_VALUE) {
1148 socket_close(g_tcp_server.listen_socket6);
1149 g_tcp_server.listen_socket6 = INVALID_SOCKET_VALUE;
1154 atomic_store(&g_websocket_server.running,
false);
1183static void *websocket_server_thread_wrapper(
void *arg) {
1184 websocket_server_t *server = (websocket_server_t *)arg;
1186 if (result != ASCIICHAT_OK) {
1187 log_error(
"WebSocket server thread exited with error");
1192static void *status_screen_thread(
void *arg) {
1195 uint32_t fps = GET_OPTION(fps);
1199 uint64_t frame_interval_us = US_PER_SEC_INT / fps;
1201 log_debug(
"Status screen thread started (target %u FPS)", fps);
1204 bool keyboard_enabled =
false;
1206 log_info(
"Terminal is interactive, initializing keyboard...");
1207 if (keyboard_init() == ASCIICHAT_OK) {
1208 atomic_store(&g_keyboard_thread_running,
true);
1210 keyboard_enabled =
true;
1211 log_info(
"Keyboard thread started - press '/' to activate grep");
1213 log_warn(
"Failed to create keyboard thread");
1214 atomic_store(&g_keyboard_thread_running,
false);
1218 log_warn(
"Failed to initialize keyboard");
1221 log_warn(
"Terminal is NOT interactive, keyboard disabled");
1225 bool skip_next_slash =
false;
1226 bool grep_was_just_cancelled =
false;
1235 grep_was_just_cancelled =
true;
1241 if (keyboard_enabled) {
1243 grep_was_just_cancelled =
false;
1246 keyboard_key_t key = keyboard_queue_pop();
1247 while (key != KEY_NONE && !grep_was_just_cancelled) {
1249 if (skip_next_slash && key ==
'/') {
1250 skip_next_slash =
false;
1251 key = keyboard_queue_pop();
1254 skip_next_slash =
false;
1265 skip_next_slash =
true;
1270 grep_was_just_cancelled =
true;
1276 key = keyboard_queue_pop();
1281 const char *ipv4_address = g_tcp_server.config.ipv4_address;
1282 const char *ipv6_address = g_tcp_server.config.ipv6_address;
1285 server_status_update(&g_tcp_server, g_session_string, ipv4_address, ipv6_address, GET_OPTION(port),
1286 g_server_start_time,
"Server", g_session_is_mdns_only, &g_last_status_update);
1290 uint64_t frame_time = frame_end - frame_start;
1293 if (frame_time < frame_interval_us) {
1299 if (keyboard_enabled) {
1300 log_debug(
"Stopping keyboard thread...");
1301 atomic_store(&g_keyboard_thread_running,
false);
1303 log_debug(
"Keyboard thread stopped");
1307 log_debug(
"Status screen thread exiting");
1329static void *ascii_chat_client_handler(
void *arg) {
1330 tcp_client_context_t *ctx = (tcp_client_context_t *)arg;
1332 log_error(
"Client handler: NULL context");
1339 log_error(
"Client handler: NULL server context");
1340 socket_close(ctx->client_socket);
1345 socket_t client_socket = ctx->client_socket;
1348 char client_ip[INET6_ADDRSTRLEN] = {0};
1354 if (client_port < 0) {
1358 log_debug(
"Client handler started for %s:%d", client_ip, client_port);
1362 bool allowed =
false;
1363 asciichat_error_t rate_check =
1365 if (rate_check != ASCIICHAT_OK || !allowed) {
1375 int client_id =
add_client(server_ctx, client_socket, client_ip, client_port);
1376 if (client_id < 0) {
1386 log_debug(
"Client %d added successfully from %s:%d", client_id, client_ip, client_port);
1391 log_error(
"CRITICAL: Client %d not found after successful add! (not in hash table?)", client_id);
1393 log_debug(
"HANDLER: Client %d found, waiting for disconnect (active=%d)", client_id, atomic_load(&client->active));
1395 while (atomic_load(&client->active) && !atomic_load(server_ctx->
server_should_exit)) {
1397 if (wait_count % 10 == 0) {
1399 log_debug(
"HANDLER: Client %d still active (waited %d seconds), active=%d", client_id, wait_count / 10,
1400 atomic_load(&client->active));
1404 log_info(
"Client %d disconnected from %s:%d (waited %d seconds, active=%d, server_should_exit=%d)", client_id,
1405 client_ip, client_port, wait_count / 10, atomic_load(&client->active),
1411 log_error(
"CRITICAL BUG: Failed to remove client %d from server (potential zombie client leak!)", client_id);
1415 socket_close(client_socket);
1418 log_debug(
"Client handler finished for %s:%d", client_ip, client_port);
1431static void *websocket_client_handler(
void *arg) {
1432 websocket_client_context_t *ctx = (websocket_client_context_t *)arg;
1434 log_error(
"WebSocket client handler: NULL context");
1438 log_info(
"WebSocket client connected from %s:%d", ctx->client_ip, ctx->client_port);
1439 log_debug(
"[WS_HANDLER] ===== ENTRY: WebSocket client handler called for %s:%d =====", ctx->client_ip,
1445 log_error(
"WebSocket client handler: NULL server context");
1446 if (ctx->transport) {
1454 log_debug(
"[WS_HANDLER] STEP 1: Calling add_webrtc_client()...");
1455 int client_id =
add_webrtc_client(server_ctx, ctx->transport, ctx->client_ip,
false);
1456 if (client_id < 0) {
1457 log_error(
"[WS_HANDLER] FAILED: add_webrtc_client returned %d", client_id);
1458 if (ctx->transport) {
1466 ctx->transport = NULL;
1468 log_info(
"WebSocket client %d added successfully", client_id);
1469 log_debug(
"[WS_HANDLER] add_webrtc_client returned client_id=%d, transport ownership transferred", client_id);
1472 log_debug(
"[WS_HANDLER] STEP 2: Finding client structure for ID %d...", client_id);
1475 log_error(
"[WS_HANDLER] FAILED: Client %d not found after successful add", client_id);
1479 log_debug(
"[WS_HANDLER] Found client structure: client=%p, transport=%p, crypto_initialized=%d", (
void *)client,
1480 (
void *)client->transport, client->crypto_initialized);
1485 log_debug(
"[WS_HANDLER] Calling crypto_handshake_init()...");
1486 asciichat_error_t handshake_init_result =
crypto_handshake_init(&client->crypto_handshake_ctx,
true );
1487 if (handshake_init_result != ASCIICHAT_OK) {
1488 log_error(
"[WS_HANDLER] FAILED: crypto_handshake_init returned %d: %s", handshake_init_result,
1489 asciichat_error_string(handshake_init_result));
1494 log_debug(
"[WS_HANDLER] crypto_handshake_init OK, handshake_ctx.state=%d", client->crypto_handshake_ctx.state);
1497 log_debug(
"[WS_HANDLER] Setting crypto_initialized from %d to false...", client->crypto_initialized);
1498 client->crypto_initialized =
false;
1500 log_debug(
"Initialized crypto handshake context for WebSocket client %d", client_id);
1501 log_debug(
"[WS_HANDLER] crypto_initialized now = %d", client->crypto_initialized);
1505 log_info(
"[WS_HANDLER] STEP 3: Calling crypto_handshake_server_start()...");
1506 log_info(
"[WS_HANDLER] Arguments: ctx=%p (state=%d), transport=%p (type=%d)", (
void *)&client->crypto_handshake_ctx,
1507 client->crypto_handshake_ctx.state, (
void *)client->transport,
1508 client->transport ? client->transport->methods->get_type(client->transport) : -1);
1509 asciichat_error_t handshake_start_result =
1511 if (handshake_start_result != ASCIICHAT_OK) {
1512 log_error(
"[WS_HANDLER] FAILED: crypto_handshake_server_start returned %d: %s", handshake_start_result,
1513 asciichat_error_string(handshake_start_result));
1519 log_info(
"Sent CRYPTO_KEY_EXCHANGE_INIT to WebSocket client %d", client_id);
1520 log_info(
"[WS_HANDLER] ===== SUCCESS: Handshake started for client %d =====", client_id);
1524 log_debug(
"[WS_HANDLER] STEP 4: Starting client threads after crypto initialization...");
1526 log_error(
"[WS_HANDLER] FAILED: start_webrtc_client_threads returned error");
1531 log_info(
"[WS_HANDLER] Client threads started successfully for client %d", client_id);
1541 log_debug(
"[WS_HANDLER] STEP 5: Handler thread exiting (client_id=%d, cleanup will happen in receive thread)",
1614static int init_server_crypto(
void) {
1616 if (GET_OPTION(no_encrypt)) {
1617 log_info(
"Encryption: DISABLED (--no-encrypt)");
1623 size_t num_keys = GET_OPTION(num_identity_keys);
1627 log_info(
"Loading %zu identity key(s) for multi-key support...", num_keys);
1629 for (
size_t i = 0; i < num_keys && i < MAX_IDENTITY_KEYS; i++) {
1630 const char *key_path = GET_OPTION(identity_keys[i]);
1632 if (strlen(key_path) == 0) {
1637 bool is_special_key = (strncmp(key_path,
"gpg:", 4) == 0 || strncmp(key_path,
"github:", 7) == 0 ||
1638 strncmp(key_path,
"gitlab:", 7) == 0);
1640 if (!is_special_key) {
1642 log_warn(
"Skipping invalid SSH key file: %s", key_path);
1648 log_debug(
"Loading identity key #%zu: %s", i + 1, key_path);
1650 log_debug(
"Successfully loaded identity key #%zu: %s", i + 1, key_path);
1653 char hex_pubkey[65];
1655 log_debug(
" Key fingerprint: %s", hex_pubkey);
1659 log_warn(
"Failed to parse identity key #%zu: %s (skipping)", i + 1, key_path);
1664 log_error(
"No valid identity keys loaded despite %zu --key flag(s)", num_keys);
1665 SET_ERRNO(ERROR_CRYPTO_KEY,
"No valid identity keys loaded");
1673 }
else if (strlen(GET_OPTION(encrypt_key)) > 0) {
1675 const char *key_path = GET_OPTION(encrypt_key);
1678 bool is_special_key = (strncmp(key_path,
"gpg:", 4) == 0 || strncmp(key_path,
"github:", 7) == 0 ||
1679 strncmp(key_path,
"gitlab:", 7) == 0);
1681 if (!is_special_key) {
1683 SET_ERRNO(ERROR_CRYPTO_KEY,
"Invalid SSH key file: %s", key_path);
1689 log_info(
"Loading key for authentication: %s", key_path);
1691 log_info(
"Successfully loaded server key: %s", key_path);
1697 log_error(
"Failed to parse key: %s\n"
1698 "This may be due to:\n"
1699 " - Wrong password for encrypted key\n"
1700 " - Unsupported key type (only Ed25519 is currently supported)\n"
1701 " - Corrupted key file\n"
1703 "Note: RSA and ECDSA keys are not yet supported\n"
1704 "To generate an Ed25519 key: ssh-keygen -t ed25519\n",
1706 SET_ERRNO(ERROR_CRYPTO_KEY,
"Key parsing failed: %s", key_path);
1709 }
else if (strlen(GET_OPTION(password)) == 0) {
1714 log_info(
"Server running without identity key (simple mode)");
1718 if (strlen(GET_OPTION(client_keys)) > 0) {
1720 SET_ERRNO(ERROR_CRYPTO_KEY,
"Client key parsing failed: %s", GET_OPTION(client_keys));
1736 shutdown_register_callback(check_shutdown);
1748 log_debug(
"Initializing crypto...");
1749 if (init_server_crypto() != 0) {
1751 LOG_ERRNO_IF_SET(
"Crypto initialization failed");
1752 FATAL(ERROR_CRYPTO,
"Crypto initialization failed");
1754 log_debug(
"Crypto initialized successfully");
1758 if (GET_OPTION(enable_keepawake) && GET_OPTION(disable_keepawake)) {
1759 FATAL(ERROR_INVALID_PARAM,
"--keepawake and --no-keepawake are mutually exclusive");
1761 if (GET_OPTION(enable_keepawake)) {
1765 log_info(
"ascii-chat server starting...");
1768 int port = GET_OPTION(port);
1769 if (port < 1 || port > 65535) {
1770 log_error(
"Invalid port configuration: %d", port);
1771 FATAL(ERROR_CONFIG,
"Invalid port configuration: %d", port);
1778 log_debug(
"Setting up simple signal handlers...");
1781 platform_signal(SIGINT, server_handle_sigint);
1783 platform_signal(SIGTERM, server_handle_sigterm);
1786 platform_signal(SIGPIPE, SIG_IGN);
1794 FATAL(ERROR_THREAD,
"Statistics system initialization failed");
1800 if (!g_server_worker_pool) {
1801 LOG_ERRNO_IF_SET(
"Failed to create server worker thread pool");
1802 FATAL(ERROR_MEMORY,
"Failed to create server worker thread pool");
1807 LOG_ERRNO_IF_SET(
"Statistics logger thread creation failed");
1809 log_debug(
"Statistics logger thread started");
1813 log_debug(
"Config check: GET_OPTION(address)='%s', GET_OPTION(address6)='%s'", GET_OPTION(address),
1814 GET_OPTION(address6));
1816 bool ipv4_has_value = (strlen(GET_OPTION(address)) > 0);
1817 bool ipv6_has_value = (strlen(GET_OPTION(address6)) > 0);
1822 log_debug(
"Binding decision: ipv4_has_value=%d, ipv6_has_value=%d, ipv4_is_default=%d, ipv6_is_default=%d",
1823 ipv4_has_value, ipv6_has_value, ipv4_is_default, ipv6_is_default);
1826 bool bind_ipv4 =
false;
1827 bool bind_ipv6 =
false;
1828 const char *ipv4_address = NULL;
1829 const char *ipv6_address = NULL;
1831 if (ipv4_has_value && ipv6_has_value && ipv4_is_default && ipv6_is_default) {
1835 ipv4_address =
"127.0.0.1";
1836 ipv6_address =
"::1";
1837 log_info(
"Default dual-stack: binding to 127.0.0.1 (IPv4) and ::1 (IPv6)");
1838 }
else if (ipv4_has_value && !ipv4_is_default && (!ipv6_has_value || ipv6_is_default)) {
1842 ipv4_address = GET_OPTION(address);
1843 log_info(
"Binding only to IPv4 address: %s", ipv4_address);
1844 }
else if (ipv6_has_value && !ipv6_is_default && (ipv4_is_default || !ipv4_has_value)) {
1848 ipv6_address = GET_OPTION(address6);
1849 log_info(
"Binding only to IPv6 address: %s", ipv6_address);
1854 ipv4_address = ipv4_has_value ? GET_OPTION(address) :
"127.0.0.1";
1855 ipv6_address = ipv6_has_value ? GET_OPTION(address6) :
"::1";
1856 log_info(
"Dual-stack binding: IPv4=%s, IPv6=%s", ipv4_address, ipv6_address);
1874 .session_host = NULL,
1878 tcp_server_config_t tcp_config = {
1880 .ipv4_address = ipv4_address,
1881 .ipv6_address = ipv6_address,
1882 .bind_ipv4 = bind_ipv4,
1883 .bind_ipv6 = bind_ipv6,
1884 .accept_timeout_sec = ACCEPT_TIMEOUT,
1885 .client_handler = ascii_chat_client_handler,
1886 .user_data = &server_ctx,
1887 .status_update_fn = NULL,
1888 .status_update_data = NULL,
1892 memset(&g_tcp_server, 0,
sizeof(g_tcp_server));
1893 asciichat_error_t tcp_init_result =
tcp_server_init(&g_tcp_server, &tcp_config);
1894 if (tcp_init_result != ASCIICHAT_OK) {
1895 FATAL(ERROR_NETWORK,
"Failed to initialize TCP server");
1899 websocket_server_config_t ws_config = {
1900 .port = GET_OPTION(websocket_port),
1901 .client_handler = websocket_client_handler,
1902 .user_data = &server_ctx,
1905 memset(&g_websocket_server, 0,
sizeof(g_websocket_server));
1907 if (ws_init_result != ASCIICHAT_OK) {
1908 log_warn(
"Failed to initialize WebSocket server - browser clients will not be supported");
1910 log_info(
"WebSocket server initialized on port %d", GET_OPTION(websocket_port));
1918 bool upnp_succeeded =
false;
1927 if (GET_OPTION(enable_upnp)) {
1928 asciichat_error_t upnp_result =
nat_upnp_open(port,
"ascii-chat Server", &g_upnp_ctx);
1930 if (upnp_result == ASCIICHAT_OK && g_upnp_ctx) {
1931 char public_addr[22];
1934 safe_snprintf(msg,
sizeof(msg),
"🌐 Public endpoint: %s (direct TCP)", public_addr);
1935 log_console(LOG_INFO, msg);
1936 log_info(
"UPnP: Port mapping successful, public endpoint: %s", public_addr);
1937 upnp_succeeded =
true;
1940 log_info(
"UPnP: Port mapping unavailable or failed - will use WebRTC fallback");
1941 log_console(LOG_INFO,
"📡 Clients behind strict NATs will use WebRTC fallback");
1944 log_debug(
"UPnP: Disabled (use --upnp to enable automatic port mapping)");
1949 FATAL(ERROR_THREAD,
"Failed to initialize client manager rwlock");
1957 log_info(
"Shutdown signal received during initialization, skipping server startup");
1972 log_debug(
"Initializing connection rate limiter...");
1975 LOG_ERRNO_IF_SET(
"Failed to initialize rate limiter");
1977 FATAL(ERROR_MEMORY,
"Failed to create connection rate limiter");
1980 log_info(
"Connection rate limiter initialized (50 connections/min per IP)");
1986 log_debug(
"Initializing audio mixer for per-client audio rendering...");
1989 LOG_ERRNO_IF_SET(
"Failed to initialize audio mixer");
1991 FATAL(ERROR_AUDIO,
"Failed to initialize audio mixer");
1995 log_debug(
"Audio mixer initialized successfully for per-client audio rendering");
2005 log_debug(
"Initializing mDNS for LAN service discovery...");
2008 LOG_ERRNO_IF_SET(
"Failed to initialize mDNS (non-fatal, LAN discovery disabled)");
2009 log_warn(
"mDNS disabled - LAN service discovery will not be available");
2012 log_debug(
"mDNS context initialized, advertisement deferred until session string is ready");
2014 }
else if (GET_OPTION(no_mdns_advertise)) {
2015 log_info(
"mDNS service advertisement disabled via --no-mdns-advertise");
2024 session_host_config_t host_config = {
2026 .ipv4_address = ipv4_address,
2027 .ipv6_address = ipv6_address,
2028 .max_clients = GET_OPTION(max_clients),
2030 .key_path = GET_OPTION(encrypt_key),
2031 .password = GET_OPTION(password),
2039 log_warn(
"Failed to create session_host (discovery mode support disabled)");
2041 log_debug(
"Session host created for discovery mode support");
2062 char session_string[64] = {0};
2063 bool session_is_mdns_only =
false;
2066 if (GET_OPTION(discovery)) {
2071 const char *password = GET_OPTION(password);
2072 bool has_password = password && strlen(password) > 0;
2073 const char *encrypt_key = GET_OPTION(encrypt_key);
2074 bool has_identity = encrypt_key && strlen(encrypt_key) > 0;
2075 bool explicit_expose = GET_OPTION(discovery_expose_ip) != 0;
2078 bool acds_expose_ip_flag =
false;
2080 if (has_password || has_identity) {
2082 acds_expose_ip_flag =
false;
2083 log_plain(
"🔒 ACDS privacy enabled: IP disclosed only after %s verification",
2084 has_password ?
"password" :
"identity");
2085 }
else if (explicit_expose) {
2091 if (is_interactive) {
2092 log_plain_stderr(
"");
2093 log_plain_stderr(
"⚠️ WARNING: You are about to allow PUBLIC IP disclosure!");
2094 log_plain_stderr(
"⚠️ Anyone with the session string will be able to see your IP address.");
2095 log_plain_stderr(
"⚠️ This is NOT RECOMMENDED unless you understand the privacy implications.");
2096 log_plain_stderr(
"");
2099 log_plain_stderr(
"");
2100 log_plain_stderr(
"❌ IP disclosure not confirmed. Server will run WITHOUT discovery service.");
2101 goto skip_acds_session;
2106 acds_expose_ip_flag =
true;
2107 log_plain_stderr(
"");
2108 log_plain_stderr(
"⚠️ Public IP disclosure CONFIRMED");
2109 log_plain_stderr(
"⚠️ Your IP address will be visible to anyone with the session string");
2112 log_plain_stderr(
"❌ Cannot create ACDS session: No security configured!");
2113 log_plain_stderr(
" You must either:");
2114 log_plain_stderr(
" 1. Set a password: --password \"your-secret\"");
2115 log_plain_stderr(
" 2. Use identity key: --key ~/.ssh/id_ed25519");
2116 log_plain_stderr(
" 3. Explicitly allow public IP: --acds-expose-ip (NOT RECOMMENDED)");
2117 log_plain_stderr(
"");
2118 log_plain_stderr(
"Server will run WITHOUT discovery service.");
2119 goto skip_acds_session;
2123 const char *acds_server = GET_OPTION(discovery_server);
2124 uint16_t acds_port = (uint16_t)GET_OPTION(discovery_port);
2126 log_info(
"Attempting to create session on ACDS server at %s:%d...", acds_server, acds_port);
2128 acds_client_config_t acds_config;
2130 SAFE_STRNCPY(acds_config.server_address, acds_server,
sizeof(acds_config.server_address));
2131 acds_config.server_port = acds_port;
2132 acds_config.timeout_ms = 5 * MS_PER_SEC_INT;
2135 g_acds_client = SAFE_MALLOC(
sizeof(acds_client_t), acds_client_t *);
2136 if (!g_acds_client) {
2137 log_error(
"Failed to allocate ACDS client");
2138 goto skip_acds_session;
2142 if (acds_connect_result != ASCIICHAT_OK) {
2143 log_error(
"Failed to connect to ACDS server at %s:%d: %s", acds_server, acds_port,
2144 asciichat_error_string(acds_connect_result));
2145 goto skip_acds_session;
2147 if (acds_connect_result == ASCIICHAT_OK) {
2149 acds_session_create_params_t create_params;
2150 memset(&create_params, 0,
sizeof(create_params));
2155 log_debug(
"Using server identity key for ACDS session");
2159 memset(create_params.identity_pubkey, 0, 32);
2160 log_debug(
"No server identity key - using zero key for ACDS session");
2163 create_params.capabilities = 0x03;
2164 create_params.max_participants = GET_OPTION(max_clients);
2165 log_debug(
"ACDS: max_clients option value = %d", GET_OPTION(max_clients));
2168 create_params.has_password = has_password;
2171 SAFE_STRNCPY(create_params.password, password,
sizeof(create_params.password));
2175 create_params.acds_expose_ip = acds_expose_ip_flag;
2176 log_info(
"DEBUG: Server setting acds_expose_ip=%d (explicit_expose=%d, has_password=%d, has_identity=%d)",
2177 create_params.acds_expose_ip, explicit_expose, has_password, has_identity);
2182 const char *bind_addr = GET_OPTION(address);
2183 bool bind_all_interfaces = (strcmp(bind_addr,
"0.0.0.0") == 0);
2187 if (GET_OPTION(webrtc)) {
2189 create_params.session_type = SESSION_TYPE_WEBRTC;
2190 log_info(
"ACDS session type: WebRTC (explicitly requested via --webrtc)");
2191 }
else if (bind_all_interfaces) {
2193 create_params.session_type = SESSION_TYPE_WEBRTC;
2194 log_info(
"ACDS session type: WebRTC (default for 0.0.0.0 binding, provides NAT-agnostic connections)");
2195 }
else if (upnp_succeeded) {
2197 create_params.session_type = SESSION_TYPE_DIRECT_TCP;
2198 log_info(
"ACDS session type: Direct TCP (UPnP succeeded, server is publicly accessible)");
2201 create_params.session_type = SESSION_TYPE_WEBRTC;
2202 log_info(
"ACDS session type: WebRTC (UPnP failed, server behind NAT)");
2207 if (bind_all_interfaces) {
2208 create_params.server_address[0] =
'\0';
2209 log_debug(
"Bind address is 0.0.0.0, ACDS will auto-detect public IP from connection");
2211 SAFE_STRNCPY(create_params.server_address, bind_addr,
sizeof(create_params.server_address));
2213 create_params.server_port = port;
2216 log_info(
"DEBUG: Before SESSION_CREATE - expose_ip_publicly=%d, server_address='%s' port=%u, session_type=%u",
2217 create_params.acds_expose_ip, create_params.server_address, create_params.server_port,
2218 create_params.session_type);
2221 acds_session_create_result_t create_result;
2222 asciichat_error_t create_err =
acds_session_create(g_acds_client, &create_params, &create_result);
2224 if (create_err == ASCIICHAT_OK) {
2225 SAFE_STRNCPY(session_string, create_result.session_string,
sizeof(session_string));
2226 SAFE_STRNCPY(g_session_string, create_result.session_string,
sizeof(g_session_string));
2227 session_is_mdns_only =
false;
2228 log_info(
"Session created: %s", session_string);
2231 log_debug(
"Server joining session as first participant for WebRTC signaling...");
2232 acds_session_join_params_t join_params = {0};
2233 join_params.session_string = session_string;
2236 memcpy(join_params.identity_pubkey, create_params.identity_pubkey, 32);
2240 join_params.has_password =
true;
2241 SAFE_STRNCPY(join_params.password, password,
sizeof(join_params.password));
2244 acds_session_join_result_t join_result = {0};
2245 asciichat_error_t join_err =
acds_session_join(g_acds_client, &join_params, &join_result);
2246 if (join_err != ASCIICHAT_OK || !join_result.success) {
2247 log_error(
"Failed to join own session: %s (error: %s)", asciichat_error_string(join_err),
2248 join_result.error_message[0] ? join_result.error_message :
"unknown");
2251 log_debug(
"Server joined session successfully (participant_id: %02x%02x...)", join_result.participant_id[0],
2252 join_result.participant_id[1]);
2254 memcpy(g_server_participant_id, join_result.participant_id, 16);
2255 log_debug(
"Stored server participant_id for signaling: %02x%02x...", g_server_participant_id[0],
2256 g_server_participant_id[1]);
2257 memcpy(create_result.session_id, join_result.session_id, 16);
2261 log_debug(
"Server staying connected to ACDS for signaling relay");
2265 if (!g_acds_transport) {
2266 log_error(
"Failed to create ACDS transport wrapper");
2268 log_debug(
"ACDS transport wrapper created for signaling");
2272 if (ping_thread_result != 0) {
2273 log_error(
"Failed to create ACDS ping thread: %d", ping_thread_result);
2275 log_debug(
"ACDS ping thread started to keep connection alive");
2276 g_acds_ping_thread_started =
true;
2281 if (create_params.session_type == SESSION_TYPE_WEBRTC) {
2282 log_debug(
"Initializing WebRTC library and peer manager for session (role=CREATOR)...");
2285 asciichat_error_t webrtc_init_result =
webrtc_init();
2286 if (webrtc_init_result != ASCIICHAT_OK) {
2287 log_error(
"Failed to initialize WebRTC library: %s", asciichat_error_string(webrtc_init_result));
2288 g_webrtc_peer_manager = NULL;
2290 log_debug(
"WebRTC library initialized successfully");
2293 static stun_server_t stun_servers[4] = {0};
2294 static unsigned int g_stun_init_refcount = 0;
2295 static static_mutex_t g_stun_init_mutex = STATIC_MUTEX_INIT;
2296 static int stun_count = 0;
2298 static_mutex_lock(&g_stun_init_mutex);
2299 if (g_stun_init_refcount == 0) {
2300 log_debug(
"Parsing STUN servers from options: '%s'", GET_OPTION(stun_servers));
2302 stun_servers_parse(GET_OPTION(stun_servers), OPT_ENDPOINT_STUN_SERVERS_DEFAULT, stun_servers, 4);
2305 log_debug(
"Parsed %d STUN servers", count);
2306 for (
int i = 0; i < count; i++) {
2307 log_debug(
" STUN[%d]: '%s' (len=%d)", i, stun_servers[i].host, stun_servers[i].host_len);
2310 log_warn(
"Failed to parse STUN servers, using defaults");
2311 stun_count =
stun_servers_parse(OPT_ENDPOINT_STUN_SERVERS_DEFAULT, OPT_ENDPOINT_STUN_SERVERS_DEFAULT,
2313 log_debug(
"Using default STUN servers, count=%d", stun_count);
2314 for (
int i = 0; i < stun_count; i++) {
2315 log_debug(
" STUN[%d]: '%s' (len=%d)", i, stun_servers[i].host, stun_servers[i].host_len);
2318 g_stun_init_refcount = 1;
2320 static_mutex_unlock(&g_stun_init_mutex);
2323 webrtc_peer_manager_config_t pm_config = {
2324 .role = WEBRTC_ROLE_CREATOR,
2325 .stun_servers = stun_servers,
2326 .stun_count = stun_count,
2327 .turn_servers = NULL,
2329 .on_transport_ready = on_webrtc_transport_ready,
2330 .user_data = &server_ctx,
2335 webrtc_signaling_callbacks_t signaling_callbacks = {
2336 .send_sdp = server_send_sdp, .send_ice = server_send_ice, .user_data = NULL};
2339 asciichat_error_t pm_result =
2341 if (pm_result != ASCIICHAT_OK) {
2342 log_error(
"Failed to create WebRTC peer_manager: %s", asciichat_error_string(pm_result));
2343 g_webrtc_peer_manager = NULL;
2345 log_debug(
"WebRTC peer_manager initialized successfully");
2349 if (thread_result != 0) {
2350 log_error(
"Failed to create ACDS receive thread: %d", thread_result);
2353 g_webrtc_peer_manager = NULL;
2355 log_debug(
"ACDS receive thread started for WebRTC signaling relay");
2356 g_acds_receive_thread_started =
true;
2361 log_debug(
"Session type is DIRECT_TCP, skipping WebRTC peer_manager initialization");
2366 advertise_mdns_with_session(session_string, (uint16_t)port);
2368 log_warn(
"Failed to create session on ACDS server (server will run without discovery)");
2370 if (g_acds_client) {
2372 SAFE_FREE(g_acds_client);
2373 g_acds_client = NULL;
2377 log_warn(
"Could not connect to ACDS server at %s:%d (server will run without discovery)", acds_server, acds_port);
2380 log_info(
"ACDS registration disabled (use --acds to enable)");
2386 if (session_string[0] ==
'\0' && g_mdns_ctx) {
2387 log_debug(
"No ACDS session string available, generating random session for mDNS");
2392 log_error(
"Failed to generate session string for mDNS");
2396 log_debug(
"Generated random session string for mDNS: '%s'", session_string);
2399 session_is_mdns_only =
true;
2402 advertise_mdns_with_session(session_string, (uint16_t)port);
2410 if (session_string[0] !=
'\0') {
2411 if (session_is_mdns_only) {
2412 log_plain(
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n📋 Session String: %s (LAN only via "
2413 "mDNS)\n🔗 Share with others on your LAN to join:\n ascii-chat "
2414 "%s\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
2415 session_string, session_string);
2417 log_plain(
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n📋 Session String: %s\n🔗 Share this "
2418 "globally to join:\n ascii-chat %s\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
2419 session_string, session_string);
2424 SAFE_STRNCPY(g_session_string, session_string,
sizeof(g_session_string));
2425 g_session_is_mdns_only = session_is_mdns_only;
2427 log_debug(
"Server entering accept loop (port %d)...", port);
2430 g_server_start_time = time(NULL);
2435 if (GET_OPTION(status_screen)) {
2442 if (GET_OPTION(status_screen)) {
2444 log_error(
"Failed to create status screen thread");
2447 log_debug(
"Status screen thread started");
2451 if (g_websocket_server.context != NULL) {
2452 if (
asciichat_thread_create(&g_websocket_server_thread, websocket_server_thread_wrapper, &g_websocket_server) !=
2454 log_error(
"Failed to create WebSocket server thread");
2456 g_websocket_server_thread_started =
true;
2457 log_info(
"WebSocket server thread started");
2468 if (run_result != ASCIICHAT_OK) {
2469 log_error(
"TCP server exited with error");
2472 log_debug(
"Server accept loop exited");
2479 if (GET_OPTION(status_screen)) {
2480 log_debug(
"Waiting for status screen thread to exit...");
2481 int join_result = asciichat_thread_join_timeout(&g_status_screen_thread, NULL, NS_PER_SEC_INT);
2482 if (join_result != 0) {
2483 log_warn(
"Status screen thread did not exit cleanly (timeout)");
2485 log_debug(
"Status screen thread exited");
2493 log_debug(
"Server shutting down...");
2494 memset(g_session_string, 0,
sizeof(g_session_string));
2507 log_debug(
"Closing all client sockets to unblock receive threads...");
2511 for (
int i = 0; i < MAX_CLIENTS; i++) {
2513 if (atomic_load(&client->client_id) != 0 && client->socket != INVALID_SOCKET_VALUE) {
2514 socket_close(client->socket);
2515 client->socket = INVALID_SOCKET_VALUE;
2520 log_debug(
"Signaling all clients to stop (sockets closed, g_server_should_exit set)...");
2523 if (g_server_worker_pool) {
2525 g_server_worker_pool = NULL;
2526 log_debug(
"Server worker thread pool stopped");
2542 if (g_websocket_server.context != NULL) {
2543 log_debug(
"Shutting down WebSocket server before client cleanup");
2544 atomic_store(&g_websocket_server.running,
false);
2555 if (g_websocket_server_thread_started) {
2557 asciichat_thread_join_timeout(&g_websocket_server_thread, NULL, 2 * NS_PER_SEC_INT);
2558 if (join_result != 0) {
2559 log_warn(
"WebSocket thread did not exit cleanly (timeout), forcing cleanup");
2561 g_websocket_server_thread_started =
false;
2567 log_debug(
"WebSocket server shut down");
2571 log_debug(
"Cleaning up connected clients...");
2573 uint32_t clients_to_remove[MAX_CLIENTS];
2574 int client_count = 0;
2577 for (
int i = 0; i < MAX_CLIENTS; i++) {
2583 if (atomic_load(&client->client_id) == 0) {
2589 uint32_t client_id_snapshot = atomic_load(&client->client_id);
2593 clients_to_remove[client_count++] = client_id_snapshot;
2598 for (
int i = 0; i < client_count; i++) {
2599 if (
remove_client(&server_ctx, clients_to_remove[i]) != 0) {
2600 log_error(
"Failed to remove client %u during shutdown", clients_to_remove[i]);
2608 client_info_t *current_client, *tmp;
2631 log_debug(
"mDNS context shut down");
2651 log_debug(
"Destroying session host");
2663 if (g_acds_ping_thread_started) {
2664 log_debug(
"Joining ACDS ping thread");
2666 g_acds_ping_thread_started =
false;
2667 log_debug(
"ACDS ping thread joined");
2670 if (g_acds_receive_thread_started) {
2671 log_debug(
"Joining ACDS receive thread");
2673 g_acds_receive_thread_started =
false;
2674 log_debug(
"ACDS receive thread joined");
2678 if (g_webrtc_peer_manager) {
2679 log_debug(
"Destroying WebRTC peer manager");
2681 g_webrtc_peer_manager = NULL;
2685 if (g_acds_transport) {
2686 log_debug(
"Destroying ACDS transport wrapper");
2688 g_acds_transport = NULL;
2692 if (g_acds_client) {
2693 log_debug(
"Disconnecting from ACDS server");
2695 SAFE_FREE(g_acds_client);
2696 g_acds_client = NULL;
2731 platform_restore_timer_resolution();
2738 log_info(
"Server shutdown complete");
void acds_client_config_init_defaults(acds_client_config_t *config)
asciichat_error_t acds_session_create(acds_client_t *client, const acds_session_create_params_t *params, acds_session_create_result_t *result)
asciichat_error_t acds_session_join(acds_client_t *client, const acds_session_join_params_t *params, acds_session_join_result_t *result)
asciichat_error_t acds_client_connect(acds_client_t *client, const acds_client_config_t *config)
void acds_client_disconnect(acds_client_t *client)
void ascii_simd_init(void)
void asciichat_error_stats_print(void)
void asciichat_errno_destroy(void)
__thread asciichat_error_context_t asciichat_errno_context
void buffer_pool_cleanup_global(void)
Per-client state management and lifecycle orchestration.
asciichat_error_t crypto_handshake_init(crypto_handshake_context_t *ctx, bool is_server)
asciichat_error_t acds_string_generate(char *output, size_t output_size)
void pubkey_to_hex(const uint8_t pubkey[32], char hex_out[65])
size_t g_num_whitelisted_clients
Number of whitelisted clients.
bool g_server_encryption_enabled
Global flag indicating if server encryption is enabled.
public_key_t g_client_whitelist[MAX_CLIENTS]
Global client public key whitelist.
private_key_t g_server_private_key
Global server private key (first identity key, for backward compatibility)
private_key_t g_server_identity_keys[MAX_IDENTITY_KEYS]
Global server identity keys array (multi-key support)
size_t g_num_server_identity_keys
Number of loaded server identity keys.
void session_host_destroy(session_host_t *host)
session_host_t * session_host_create(const session_host_config_t *config)
void interactive_grep_exit_mode(bool accept)
bool interactive_grep_is_entering(void)
bool interactive_grep_check_signal_cancel(void)
bool interactive_grep_is_entering_atomic(void)
void interactive_grep_signal_cancel(void)
asciichat_error_t interactive_grep_init(void)
asciichat_error_t interactive_grep_handle_key(keyboard_key_t key)
bool interactive_grep_should_handle(int key)
int is_localhost_ipv6(const char *ip)
int is_localhost_ipv4(const char *ip)
asciichat_error_t parse_private_key(const char *key_path, private_key_t *key_out)
asciichat_error_t parse_public_keys(const char *input, public_key_t *keys_out, size_t *num_keys, size_t max_keys)
asciichat_error_t crypto_handshake_server_start(crypto_handshake_context_t *ctx, acip_transport_t *transport)
asciichat_error_t acip_client_receive_and_dispatch(acip_transport_t *transport, const acip_client_callbacks_t *callbacks)
void tcp_server_destroy(tcp_server_t *server)
void tcp_server_reject_client(socket_t socket, const char *reason)
asciichat_error_t tcp_server_init(tcp_server_t *server, const tcp_server_config_t *config)
int tcp_client_context_get_port(const tcp_client_context_t *ctx)
asciichat_error_t tcp_server_run(tcp_server_t *server)
const char * tcp_client_context_get_ip(const tcp_client_context_t *ctx, char *buf, size_t len)
asciichat_error_t webrtc_init(void)
asciichat_error_t websocket_server_init(websocket_server_t *server, const websocket_server_config_t *config)
asciichat_error_t websocket_server_run(websocket_server_t *server)
void websocket_server_destroy(websocket_server_t *server)
void websocket_server_cancel_service(websocket_server_t *server)
void platform_disable_keepawake(void)
asciichat_error_t platform_enable_keepawake(void)
void lock_debug_cleanup_thread(void)
void lock_debug_destroy(void)
void asciichat_mdns_destroy(asciichat_mdns_t *mdns)
asciichat_mdns_t * asciichat_mdns_init(void)
asciichat_error_t asciichat_mdns_advertise(asciichat_mdns_t *mdns, const asciichat_mdns_service_t *service)
mixer_t * mixer_create(int max_sources, int sample_rate)
void mixer_destroy(mixer_t *mixer)
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.
asciichat_error_t webrtc_peer_manager_create(const webrtc_peer_manager_config_t *config, const webrtc_signaling_callbacks_t *signaling_callbacks, webrtc_peer_manager_t **manager_out)
asciichat_error_t webrtc_peer_manager_handle_ice(webrtc_peer_manager_t *manager, const acip_webrtc_ice_t *ice)
asciichat_error_t webrtc_peer_manager_handle_sdp(webrtc_peer_manager_t *manager, const acip_webrtc_sdp_t *sdp)
void webrtc_peer_manager_destroy(webrtc_peer_manager_t *manager)
asciichat_error_t rate_limiter_record(rate_limiter_t *limiter, const char *ip_address, rate_event_type_t event_type)
void rate_limiter_destroy(rate_limiter_t *limiter)
rate_limiter_t * rate_limiter_create_memory(void)
asciichat_error_t rate_limiter_check(rate_limiter_t *limiter, const char *ip_address, rate_event_type_t event_type, const rate_limit_config_t *config, bool *allowed)
void options_state_destroy(void)
asciichat_error_t packet_send_via_transport(acip_transport_t *transport, packet_type_t type, const void *payload, size_t payload_len, uint32_t client_id)
Send packet via transport with proper header (exported for generic wrappers)
rate_limiter_t * g_rate_limiter
Global rate limiter for connection attempts and packet processing.
mixer_t *volatile g_audio_mixer
Global audio mixer instance for multi-client audio processing.
int server_main(void)
Server mode entry point for unified binary.
#define KEYBOARD_QUEUE_SIZE
Thread-safe keyboard queue (lock-free SPSC ring buffer)
atomic_bool g_server_should_exit
Global atomic shutdown flag shared across all threads.
static_cond_t g_shutdown_cond
Global shutdown condition variable for waking blocked threads.
ascii-chat Server Mode Entry Point Header
void server_status_log_destroy(void)
void server_status_log_clear(void)
void server_status_update(tcp_server_t *server, const char *session_string, const char *ipv4_address, const char *ipv6_address, uint16_t port, time_t start_time, const char *mode_name, bool session_is_mdns_only, uint64_t *last_update_ns)
void server_status_log_init(void)
uint8_t participant_id[16]
int start_webrtc_client_threads(server_context_t *server_ctx, uint32_t client_id)
Start threads for a WebRTC client after crypto initialization.
client_info_t * find_client_by_id(uint32_t client_id)
Fast O(1) client lookup by ID using hash table.
int add_client(server_context_t *server_ctx, socket_t socket, const char *client_ip, int port)
rwlock_t g_client_manager_rwlock
Reader-writer lock protecting the global client manager.
int remove_client(server_context_t *server_ctx, uint32_t client_id)
int add_webrtc_client(server_context_t *server_ctx, acip_transport_t *transport, const char *client_ip, bool start_threads)
Register a WebRTC client with the server.
client_manager_t g_client_manager
Global client manager singleton - central coordination point.
asciichat_error_t validate_ssh_key_file(const char *key_path)
server_stats_t g_stats
Global server statistics structure.
void * stats_logger_thread(void *arg)
Main statistics collection and reporting thread function.
int stats_init(void)
Initialize the stats mutex.
mutex_t g_stats_mutex
Mutex protecting global server statistics.
void stats_cleanup(void)
Cleanup the stats mutex.
Server performance statistics tracking.
Multi-client video mixing and ASCII frame generation.
Internal mDNS context structure.
client_info_t * clients_by_id
uthash head pointer for O(1) client_id -> client_info_t* lookups
mutex_t mutex
Legacy mutex (mostly replaced by rwlock)
client_info_t clients[MAX_CLIENTS]
Array of client_info_t structures (backing storage)
Server context - encapsulates all server state.
tcp_server_t * tcp_server
TCP server managing connections.
session_host_t * session_host
Session host for discovery mode support.
atomic_bool * server_should_exit
Shutdown flag.
rate_limiter_t * rate_limiter
Connection and packet rate limiter.
WebRTC peer manager structure.
int stun_servers_parse(const char *csv_servers, const char *default_csv, stun_server_t *out_servers, int max_count)
Parse comma-separated STUN server URLs into stun_server_t array.
void symbol_cache_destroy(void)
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
void platform_cleanup_binary_path_cache(void)
Cleanup the binary PATH cache.
acip_transport_t * acip_tcp_transport_create(socket_t sockfd, crypto_context_t *crypto_ctx)
void acip_transport_destroy(acip_transport_t *transport)
void thread_pool_destroy(thread_pool_t *pool)
thread_pool_t * thread_pool_create(const char *pool_name)
asciichat_error_t thread_pool_spawn(thread_pool_t *pool, void *(*thread_func)(void *), void *thread_arg, int stop_id, const char *thread_name)
int mutex_init(mutex_t *mutex)
int asciichat_thread_create(asciichat_thread_t *thread, void *(*start_routine)(void *), void *arg)
int rwlock_init(rwlock_t *rwlock)
int asciichat_thread_join(asciichat_thread_t *thread, void **retval)
int mutex_destroy(mutex_t *mutex)
asciichat_error_t nat_upnp_open(uint16_t internal_port, const char *description, nat_upnp_context_t **ctx)
asciichat_error_t nat_upnp_get_address(const nat_upnp_context_t *ctx, char *addr, size_t addr_len)
bool platform_prompt_yes_no(const char *question, bool default_yes)
void precalc_rgb_palettes(const float red, const float green, const float blue)
void simd_caches_destroy_all(void)