Server mode entry point for unified binary.
Options are already parsed by the main dispatcher before this function is called, so they are available via global opt_* variables.
1731 {
1732
1733
1734
1735
1736 shutdown_register_callback(check_shutdown);
1737
1738
1739
1742
1743
1745 }
1746
1747
1748 log_debug("Initializing crypto...");
1749 if (init_server_crypto() != 0) {
1750
1751 LOG_ERRNO_IF_SET("Crypto initialization failed");
1752 FATAL(ERROR_CRYPTO, "Crypto initialization failed");
1753 }
1754 log_debug("Crypto initialized successfully");
1755
1756
1757
1758 if (GET_OPTION(enable_keepawake) && GET_OPTION(disable_keepawake)) {
1759 FATAL(ERROR_INVALID_PARAM, "--keepawake and --no-keepawake are mutually exclusive");
1760 }
1761 if (GET_OPTION(enable_keepawake)) {
1763 }
1764
1765 log_info("ascii-chat server starting...");
1766
1767
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);
1772 }
1773
1776
1777
1778 log_debug("Setting up simple signal handlers...");
1779
1780
1781 platform_signal(SIGINT, server_handle_sigint);
1782
1783 platform_signal(SIGTERM, server_handle_sigterm);
1784#ifndef _WIN32
1785
1786 platform_signal(SIGPIPE, SIG_IGN);
1787
1788#endif
1789
1790#ifndef NDEBUG
1791
1792
1794 FATAL(ERROR_THREAD, "Statistics system initialization failed");
1795 }
1796#endif
1797
1798
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");
1803 }
1804
1805
1807 LOG_ERRNO_IF_SET("Statistics logger thread creation failed");
1808 } else {
1809 log_debug("Statistics logger thread started");
1810 }
1811
1812
1813 log_debug("Config check: GET_OPTION(address)='%s', GET_OPTION(address6)='%s'", GET_OPTION(address),
1814 GET_OPTION(address6));
1815
1816 bool ipv4_has_value = (strlen(GET_OPTION(address)) > 0);
1817 bool ipv6_has_value = (strlen(GET_OPTION(address6)) > 0);
1818
1821
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);
1824
1825
1826 bool bind_ipv4 = false;
1827 bool bind_ipv6 = false;
1828 const char *ipv4_address = NULL;
1829 const char *ipv6_address = NULL;
1830
1831 if (ipv4_has_value && ipv6_has_value && ipv4_is_default && ipv6_is_default) {
1832
1833 bind_ipv4 = true;
1834 bind_ipv6 = true;
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)) {
1839
1840 bind_ipv4 = true;
1841 bind_ipv6 = false;
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)) {
1845
1846 bind_ipv4 = false;
1847 bind_ipv6 = true;
1848 ipv6_address = GET_OPTION(address6);
1849 log_info("Binding only to IPv6 address: %s", ipv6_address);
1850 } else {
1851
1852 bind_ipv4 = true;
1853 bind_ipv6 = true;
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);
1857 }
1858
1859
1860
1874 .session_host = NULL,
1875 };
1876
1877
1878 tcp_server_config_t tcp_config = {
1879 .port = port,
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,
1889 };
1890
1891
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");
1896 }
1897
1898
1899 websocket_server_config_t ws_config = {
1900 .port = GET_OPTION(websocket_port),
1901 .client_handler = websocket_client_handler,
1902 .user_data = &server_ctx,
1903 };
1904
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");
1909 } else {
1910 log_info("WebSocket server initialized on port %d", GET_OPTION(websocket_port));
1911 }
1912
1913
1914
1915
1916
1917
1918 bool upnp_succeeded = false;
1919
1920
1921
1922
1923
1924
1925
1926
1927 if (GET_OPTION(enable_upnp)) {
1928 asciichat_error_t upnp_result =
nat_upnp_open(port,
"ascii-chat Server", &g_upnp_ctx);
1929
1930 if (upnp_result == ASCIICHAT_OK && g_upnp_ctx) {
1931 char public_addr[22];
1933 char msg[256];
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;
1938 }
1939 } else {
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");
1942 }
1943 } else {
1944 log_debug("UPnP: Disabled (use --upnp to enable automatic port mapping)");
1945 }
1946
1947
1949 FATAL(ERROR_THREAD, "Failed to initialize client manager rwlock");
1950 }
1951
1952
1953
1954
1955
1957 log_info("Shutdown signal received during initialization, skipping server startup");
1958 goto cleanup;
1959 }
1960
1961
1962
1963
1964
1966
1967
1969
1970
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");
1978 }
1979 } else {
1980 log_info("Connection rate limiter initialized (50 connections/min per IP)");
1981 }
1982 }
1983
1984
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");
1992 }
1993 } else {
1995 log_debug("Audio mixer initialized successfully for per-client audio rendering");
1996 }
1997 }
1998 }
1999
2000
2001
2002
2003
2005 log_debug("Initializing mDNS for LAN service discovery...");
2007 if (!g_mdns_ctx) {
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");
2010 g_mdns_ctx = NULL;
2011 } else {
2012 log_debug("mDNS context initialized, advertisement deferred until session string is ready");
2013 }
2014 } else if (GET_OPTION(no_mdns_advertise)) {
2015 log_info("mDNS service advertisement disabled via --no-mdns-advertise");
2016 }
2017
2018
2019
2020
2021
2022
2024 session_host_config_t host_config = {
2025 .port = port,
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),
2032 .callbacks = {0},
2033 .user_data = NULL,
2034 };
2035
2038
2039 log_warn("Failed to create session_host (discovery mode support disabled)");
2040 } else {
2041 log_debug("Session host created for discovery mode support");
2042 }
2043 }
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062 char session_string[64] = {0};
2063 bool session_is_mdns_only = false;
2064
2065
2066 if (GET_OPTION(discovery)) {
2067
2068
2069
2070
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;
2076
2077
2078 bool acds_expose_ip_flag = false;
2079
2080 if (has_password || has_identity) {
2081
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) {
2086
2087
2088
2090
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("");
2097
2099 log_plain_stderr("");
2100 log_plain_stderr("❌ IP disclosure not confirmed. Server will run WITHOUT discovery service.");
2101 goto skip_acds_session;
2102 }
2103 }
2104
2105
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");
2110 } else {
2111
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;
2120 }
2121
2122
2123 const char *acds_server = GET_OPTION(discovery_server);
2124 uint16_t acds_port = (uint16_t)GET_OPTION(discovery_port);
2125
2126 log_info("Attempting to create session on ACDS server at %s:%d...", acds_server, acds_port);
2127
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;
2133
2134
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;
2139 }
2140
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;
2146 }
2147 if (acds_connect_result == ASCIICHAT_OK) {
2148
2149 acds_session_create_params_t create_params;
2150 memset(&create_params, 0, sizeof(create_params));
2151
2152
2155 log_debug("Using server identity key for ACDS session");
2156 } else {
2157
2158
2159 memset(create_params.identity_pubkey, 0, 32);
2160 log_debug("No server identity key - using zero key for ACDS session");
2161 }
2162
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));
2166
2167
2168 create_params.has_password = has_password;
2169 if (has_password) {
2170
2171 SAFE_STRNCPY(create_params.password, password, sizeof(create_params.password));
2172 }
2173
2174
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);
2178
2179
2180
2181
2182 const char *bind_addr = GET_OPTION(address);
2183 bool bind_all_interfaces = (strcmp(bind_addr, "0.0.0.0") == 0);
2184
2185
2186
2187 if (GET_OPTION(webrtc)) {
2188
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) {
2192
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) {
2196
2197 create_params.session_type = SESSION_TYPE_DIRECT_TCP;
2198 log_info("ACDS session type: Direct TCP (UPnP succeeded, server is publicly accessible)");
2199 } else {
2200
2201 create_params.session_type = SESSION_TYPE_WEBRTC;
2202 log_info("ACDS session type: WebRTC (UPnP failed, server behind NAT)");
2203 }
2204
2205
2206
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");
2210 } else {
2211 SAFE_STRNCPY(create_params.server_address, bind_addr, sizeof(create_params.server_address));
2212 }
2213 create_params.server_port = port;
2214
2215
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);
2219
2220
2221 acds_session_create_result_t create_result;
2222 asciichat_error_t create_err =
acds_session_create(g_acds_client, &create_params, &create_result);
2223
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);
2229
2230
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;
2234
2235
2236 memcpy(join_params.identity_pubkey, create_params.identity_pubkey, 32);
2237
2238
2239 if (has_password) {
2240 join_params.has_password = true;
2241 SAFE_STRNCPY(join_params.password, password, sizeof(join_params.password));
2242 }
2243
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");
2249
2250 } else {
2251 log_debug("Server joined session successfully (participant_id: %02x%02x...)", join_result.participant_id[0],
2252 join_result.participant_id[1]);
2253
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);
2258 }
2259
2260
2261 log_debug("Server staying connected to ACDS for signaling relay");
2262
2263
2265 if (!g_acds_transport) {
2266 log_error("Failed to create ACDS transport wrapper");
2267 } else {
2268 log_debug("ACDS transport wrapper created for signaling");
2269
2270
2272 if (ping_thread_result != 0) {
2273 log_error("Failed to create ACDS ping thread: %d", ping_thread_result);
2274 } else {
2275 log_debug("ACDS ping thread started to keep connection alive");
2276 g_acds_ping_thread_started = true;
2277 }
2278 }
2279
2280
2281 if (create_params.session_type == SESSION_TYPE_WEBRTC) {
2282 log_debug("Initializing WebRTC library and peer manager for session (role=CREATOR)...");
2283
2284
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;
2289 } else {
2290 log_debug("WebRTC library initialized successfully");
2291
2292
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;
2297
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));
2301 int count =
2302 stun_servers_parse(GET_OPTION(stun_servers), OPT_ENDPOINT_STUN_SERVERS_DEFAULT, stun_servers, 4);
2303 if (count > 0) {
2304 stun_count = count;
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);
2308 }
2309 } else {
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,
2312 stun_servers, 4);
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);
2316 }
2317 }
2318 g_stun_init_refcount = 1;
2319 }
2320 static_mutex_unlock(&g_stun_init_mutex);
2321
2322
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,
2328 .turn_count = 0,
2329 .on_transport_ready = on_webrtc_transport_ready,
2330 .user_data = &server_ctx,
2331 .crypto_ctx = NULL
2332 };
2333
2334
2335 webrtc_signaling_callbacks_t signaling_callbacks = {
2336 .send_sdp = server_send_sdp, .send_ice = server_send_ice, .user_data = NULL};
2337
2338
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;
2344 } else {
2345 log_debug("WebRTC peer_manager initialized successfully");
2346
2347
2349 if (thread_result != 0) {
2350 log_error("Failed to create ACDS receive thread: %d", thread_result);
2351
2353 g_webrtc_peer_manager = NULL;
2354 } else {
2355 log_debug("ACDS receive thread started for WebRTC signaling relay");
2356 g_acds_receive_thread_started = true;
2357 }
2358 }
2359 }
2360 } else {
2361 log_debug("Session type is DIRECT_TCP, skipping WebRTC peer_manager initialization");
2362 }
2363
2364
2365
2366 advertise_mdns_with_session(session_string, (uint16_t)port);
2367 } else {
2368 log_warn("Failed to create session on ACDS server (server will run without discovery)");
2369
2370 if (g_acds_client) {
2372 SAFE_FREE(g_acds_client);
2373 g_acds_client = NULL;
2374 }
2375 }
2376 } else {
2377 log_warn("Could not connect to ACDS server at %s:%d (server will run without discovery)", acds_server, acds_port);
2378 }
2379 } else {
2380 log_info("ACDS registration disabled (use --acds to enable)");
2381 }
2382
2383skip_acds_session:
2384
2385
2386 if (session_string[0] == '\0' && g_mdns_ctx) {
2387 log_debug("No ACDS session string available, generating random session for mDNS");
2388
2389
2390
2392 log_error("Failed to generate session string for mDNS");
2393 return 1;
2394 }
2395
2396 log_debug("Generated random session string for mDNS: '%s'", session_string);
2397
2398
2399 session_is_mdns_only = true;
2400
2401
2402 advertise_mdns_with_session(session_string, (uint16_t)port);
2403 }
2404
2405
2406
2407
2408
2409
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);
2416 } else {
2417 log_plain("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n📋 Session String: %s\n🔗 Share this "
2418 "globally to join:\n ascii-chat %s\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
2419 session_string, session_string);
2420 }
2421 }
2422
2423
2424 SAFE_STRNCPY(g_session_string, session_string, sizeof(g_session_string));
2425 g_session_is_mdns_only = session_is_mdns_only;
2426
2427 log_debug("Server entering accept loop (port %d)...", port);
2428
2429
2430 g_server_start_time = time(NULL);
2432
2433
2434
2435 if (GET_OPTION(status_screen)) {
2438 }
2439
2440
2441
2442 if (GET_OPTION(status_screen)) {
2444 log_error("Failed to create status screen thread");
2445 goto cleanup;
2446 }
2447 log_debug("Status screen thread started");
2448 }
2449
2450
2451 if (g_websocket_server.context != NULL) {
2452 if (
asciichat_thread_create(&g_websocket_server_thread, websocket_server_thread_wrapper, &g_websocket_server) !=
2453 0) {
2454 log_error("Failed to create WebSocket server thread");
2455 } else {
2456 g_websocket_server_thread_started = true;
2457 log_info("WebSocket server thread started");
2458 }
2459 }
2460
2461
2462
2463
2464
2465
2466
2468 if (run_result != ASCIICHAT_OK) {
2469 log_error("TCP server exited with error");
2470 }
2471
2472 log_debug("Server accept loop exited");
2473
2474cleanup:
2475
2477
2478
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)");
2484 } else {
2485 log_debug("Status screen thread exited");
2486 }
2487 }
2488
2489
2491
2492
2493 log_debug("Server shutting down...");
2494 memset(g_session_string, 0, sizeof(g_session_string));
2495
2496
2497
2498
2500
2501
2502
2503
2504
2505
2506
2507 log_debug("Closing all client sockets to unblock receive threads...");
2508
2509
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;
2516 }
2517 }
2519
2520 log_debug("Signaling all clients to stop (sockets closed, g_server_should_exit set)...");
2521
2522
2523 if (g_server_worker_pool) {
2525 g_server_worker_pool = NULL;
2526 log_debug("Server worker thread pool stopped");
2527 }
2528
2529
2533 }
2534
2535
2536
2537
2538
2539
2540
2541
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);
2545
2546
2547
2548
2550
2551
2552
2553
2554
2555 if (g_websocket_server_thread_started) {
2556 int join_result =
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");
2560 }
2561 g_websocket_server_thread_started = false;
2562 }
2563
2564
2565
2567 log_debug("WebSocket server shut down");
2568 }
2569
2570
2571 log_debug("Cleaning up connected clients...");
2572
2573 uint32_t clients_to_remove[MAX_CLIENTS];
2574 int client_count = 0;
2575
2577 for (int i = 0; i < MAX_CLIENTS; i++) {
2579
2580
2581
2582
2583 if (atomic_load(&client->client_id) == 0) {
2584 continue;
2585 }
2586
2587
2588
2589 uint32_t client_id_snapshot = atomic_load(&client->client_id);
2590
2591
2592
2593 clients_to_remove[client_count++] = client_id_snapshot;
2594 }
2596
2597
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]);
2601 }
2602 }
2603
2604
2605
2606
2608 client_info_t *current_client, *tmp;
2611
2612 }
2614 }
2615
2616
2618
2619
2620
2621
2625 }
2626
2627
2628 if (g_mdns_ctx) {
2630 g_mdns_ctx = NULL;
2631 log_debug("mDNS context shut down");
2632 }
2633
2634
2637
2638#ifdef NDEBUG
2639
2641#endif
2642
2643#ifndef NDEBUG
2644
2645
2647#endif
2648
2649
2651 log_debug("Destroying session host");
2654 }
2655
2656
2658
2659
2660
2661
2662
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");
2668 }
2669
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");
2675 }
2676
2677
2678 if (g_webrtc_peer_manager) {
2679 log_debug("Destroying WebRTC peer manager");
2681 g_webrtc_peer_manager = NULL;
2682 }
2683
2684
2685 if (g_acds_transport) {
2686 log_debug("Destroying ACDS transport wrapper");
2688 g_acds_transport = NULL;
2689 }
2690
2691
2692 if (g_acds_client) {
2693 log_debug("Disconnecting from ACDS server");
2695 SAFE_FREE(g_acds_client);
2696 g_acds_client = NULL;
2697 }
2698
2699
2701
2702
2703
2704
2705
2707
2708
2709
2710
2712
2713
2715
2716
2717
2718
2719
2721
2722
2724
2725
2727
2728
2729
2730 socket_cleanup();
2731 platform_restore_timer_resolution();
2732
2733#ifndef NDEBUG
2734
2736#endif
2737
2738 log_info("Server shutdown complete");
2739
2742
2743
2744
2745 exit(0);
2746}
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)
void buffer_pool_cleanup_global(void)
asciichat_error_t acds_string_generate(char *output, size_t output_size)
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)
void session_host_destroy(session_host_t *host)
session_host_t * session_host_create(const session_host_config_t *config)
asciichat_error_t interactive_grep_init(void)
int is_localhost_ipv6(const char *ip)
int is_localhost_ipv4(const char *ip)
void tcp_server_destroy(tcp_server_t *server)
asciichat_error_t tcp_server_init(tcp_server_t *server, const tcp_server_config_t *config)
asciichat_error_t tcp_server_run(tcp_server_t *server)
asciichat_error_t webrtc_init(void)
asciichat_error_t websocket_server_init(websocket_server_t *server, const websocket_server_config_t *config)
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)
mixer_t * mixer_create(int max_sources, int sample_rate)
void mixer_destroy(mixer_t *mixer)
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)
void webrtc_peer_manager_destroy(webrtc_peer_manager_t *manager)
void rate_limiter_destroy(rate_limiter_t *limiter)
rate_limiter_t * rate_limiter_create_memory(void)
void options_state_destroy(void)
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.
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.
void server_status_log_destroy(void)
void server_status_log_clear(void)
void server_status_log_init(void)
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)
client_manager_t g_client_manager
Global client manager singleton - central coordination point.
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.
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.
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)