ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches

ascii-chat Server Mode Entry Point Header More...

Go to the source code of this file.

Data Structures

struct  server_context_t
 Server context - encapsulates all server state. More...
 

Typedefs

typedef struct server_context_t server_context_t
 Server context - encapsulates all server state.
 

Functions

int server_main (void)
 Server mode entry point for unified binary.
 

Variables

rate_limiter_tg_rate_limiter
 Global connection rate limiter.
 

Detailed Description

ascii-chat Server Mode Entry Point Header

This header exposes the server mode entry point for the unified binary architecture. The unified binary dispatches to server_main() when invoked as ascii-chat server.

Unified Binary Architecture

The ascii-chat application uses a single binary with multiple operating modes:

  • ascii-chat server - Run as server (multi-client connection manager)
  • ascii-chat client - Run as client (connects to server, streams video/audio)

This design provides several benefits:

  • Simplified distribution (single binary to install)
  • Reduced disk space (shared library code)
  • Easier testing (one binary to build and deploy)
  • Consistent versioning across modes

Mode Entry Point Contract

Each mode entry point (server_main, client_main) must:

  • Accept no arguments: int mode_main(void)
  • Options are already parsed by main dispatcher (available via global opt_* variables)
  • Return 0 on success, non-zero error code on failure
  • Perform mode-specific initialization and main loop
  • Perform complete cleanup before returning

Implementation Notes

The server_main() function is the original main() from src/server/main.c, adapted to the new dispatcher pattern. Common initialization (options parsing, logging setup, lock debugging) now happens in src/main.c before dispatch.

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
2025
Version
2.0

Definition in file server/main.h.

Function Documentation

◆ server_main()

int server_main ( void  )

Server mode entry point for unified binary.

This function implements the complete server lifecycle including:

  • Server-specific initialization (crypto, shutdown callback)
  • Network socket setup and binding
  • Main connection accept loop
  • Client lifecycle management
  • Graceful shutdown and cleanup

Options are already parsed by the main dispatcher before this function is called, so they are available via global opt_* variables.

Returns
0 on success, non-zero error code on failure
Example
# Invoked by dispatcher after options are parsed:
ascii-chat server --port 8080
# Options parsed in main.c, then server_main() called

Definition at line 1160 of file server/main.c.

1160 {
1161 // Common initialization (options, logging, lock debugging) now happens in main.c before dispatch
1162 // This function focuses on server-specific initialization
1163
1164 // Register shutdown check callback for library code
1165 shutdown_register_callback(check_shutdown);
1166
1167 // Initialize crypto after logging is ready
1168 log_info("Initializing crypto...");
1169 if (init_server_crypto() != 0) {
1170 // Print detailed error context if available
1171 LOG_ERRNO_IF_SET("Crypto initialization failed");
1172 FATAL(ERROR_CRYPTO, "Crypto initialization failed");
1173 }
1174 log_info("Crypto initialized successfully");
1175
1176 // Handle quiet mode - disable terminal output when GET_OPTION(quiet) is enabled
1178
1179 log_info("ASCII Chat server starting...");
1180
1181 // log_info("SERVER: Options initialized, using log file: %s", log_filename);
1182 int port = strtoint_safe(GET_OPTION(port));
1183 if (port == INT_MIN) {
1184 log_error("Invalid port configuration: %s", GET_OPTION(port));
1185 FATAL(ERROR_CONFIG, "Invalid port configuration: %s", GET_OPTION(port));
1186 }
1187
1190
1191 // Simple signal handling (temporarily disable complex threading signal handling)
1192 log_debug("Setting up simple signal handlers...");
1193
1194 // Handle Ctrl+C for cleanup
1195 platform_signal(SIGINT, sigint_handler);
1196 // Handle termination signal (SIGTERM is defined with limited support on Windows)
1197 platform_signal(SIGTERM, sigterm_handler);
1198 // Handle lock debugging trigger signal
1199#ifndef _WIN32
1200 platform_signal(SIGUSR1, sigusr1_handler);
1201#else
1202 UNUSED(sigusr1_handler);
1203#endif
1204#ifndef _WIN32
1205 // SIGPIPE not supported on Windows
1206 platform_signal(SIGPIPE, SIG_IGN);
1207#endif
1208
1209#ifndef NDEBUG
1210 // Start the lock debug thread (system already initialized earlier)
1211 if (lock_debug_start_thread() != 0) {
1212 FATAL(ERROR_THREAD, "Failed to start lock debug thread");
1213 }
1214 // Initialize statistics system
1215 if (stats_init() != 0) {
1216 FATAL(ERROR_THREAD, "Statistics system initialization failed");
1217 }
1218#endif
1219
1220 // Create background worker thread pool for server operations
1221 g_server_worker_pool = thread_pool_create("server_workers");
1222 if (!g_server_worker_pool) {
1223 LOG_ERRNO_IF_SET("Failed to create server worker thread pool");
1224 FATAL(ERROR_MEMORY, "Failed to create server worker thread pool");
1225 }
1226
1227 // Spawn statistics logging thread in worker pool
1228 if (thread_pool_spawn(g_server_worker_pool, stats_logger_thread, NULL, 0, "stats_logger") != ASCIICHAT_OK) {
1229 LOG_ERRNO_IF_SET("Statistics logger thread creation failed");
1230 } else {
1231 log_info("Statistics logger thread started");
1232 }
1233
1234 // Network setup - Use tcp_server abstraction for dual-stack IPv4/IPv6 binding
1235 log_debug("Config check: GET_OPTION(address)='%s', GET_OPTION(address6)='%s'", GET_OPTION(address),
1236 GET_OPTION(address6));
1237
1238 bool ipv4_has_value = (strlen(GET_OPTION(address)) > 0);
1239 bool ipv6_has_value = (strlen(GET_OPTION(address6)) > 0);
1240 bool ipv4_is_default = (strcmp(GET_OPTION(address), "127.0.0.1") == 0);
1241 bool ipv6_is_default = (strcmp(GET_OPTION(address6), "::1") == 0);
1242
1243 log_debug("Binding decision: ipv4_has_value=%d, ipv6_has_value=%d, ipv4_is_default=%d, ipv6_is_default=%d",
1244 ipv4_has_value, ipv6_has_value, ipv4_is_default, ipv6_is_default);
1245
1246 // Determine bind configuration
1247 bool bind_ipv4 = false;
1248 bool bind_ipv6 = false;
1249 const char *ipv4_address = NULL;
1250 const char *ipv6_address = NULL;
1251
1252 if (ipv4_has_value && ipv6_has_value && ipv4_is_default && ipv6_is_default) {
1253 // Both are defaults: dual-stack with default localhost addresses
1254 bind_ipv4 = true;
1255 bind_ipv6 = true;
1256 ipv4_address = "127.0.0.1";
1257 ipv6_address = "::1";
1258 log_info("Default dual-stack: binding to 127.0.0.1 (IPv4) and ::1 (IPv6)");
1259 } else if (ipv4_has_value && !ipv4_is_default && (!ipv6_has_value || ipv6_is_default)) {
1260 // IPv4 explicitly set, IPv6 is default or empty: bind only IPv4
1261 bind_ipv4 = true;
1262 bind_ipv6 = false;
1263 ipv4_address = GET_OPTION(address);
1264 log_info("Binding only to IPv4 address: %s", ipv4_address);
1265 } else if (ipv6_has_value && !ipv6_is_default && (ipv4_is_default || !ipv4_has_value)) {
1266 // IPv6 explicitly set, IPv4 is default or empty: bind only IPv6
1267 bind_ipv4 = false;
1268 bind_ipv6 = true;
1269 ipv6_address = GET_OPTION(address6);
1270 log_info("Binding only to IPv6 address: %s", ipv6_address);
1271 } else {
1272 // Both explicitly set or one explicit + one default: dual-stack
1273 bind_ipv4 = true;
1274 bind_ipv6 = true;
1275 ipv4_address = ipv4_has_value ? GET_OPTION(address) : "127.0.0.1";
1276 ipv6_address = ipv6_has_value ? GET_OPTION(address6) : "::1";
1277 log_info("Dual-stack binding: IPv4=%s, IPv6=%s", ipv4_address, ipv6_address);
1278 }
1279
1280 // Create server context - encapsulates all server state for passing to client handlers
1281 // This reduces global state and improves modularity by using tcp_server.user_data
1282 server_context_t server_ctx = {
1283 .tcp_server = &g_tcp_server,
1284 .rate_limiter = g_rate_limiter,
1285 .client_manager = &g_client_manager,
1286 .client_manager_rwlock = &g_client_manager_rwlock,
1287 .server_should_exit = &g_server_should_exit,
1288 .audio_mixer = g_audio_mixer,
1289 .stats = &g_stats,
1290 .stats_mutex = &g_stats_mutex,
1291 .encryption_enabled = g_server_encryption_enabled,
1292 .server_private_key = &g_server_private_key,
1293 .client_whitelist = g_client_whitelist,
1294 .num_whitelisted_clients = g_num_whitelisted_clients,
1295 };
1296
1297 // Configure TCP server
1298 tcp_server_config_t tcp_config = {
1299 .port = port,
1300 .ipv4_address = ipv4_address,
1301 .ipv6_address = ipv6_address,
1302 .bind_ipv4 = bind_ipv4,
1303 .bind_ipv6 = bind_ipv6,
1304 .accept_timeout_sec = ACCEPT_TIMEOUT,
1305 .client_handler = ascii_chat_client_handler,
1306 .user_data = &server_ctx, // Pass server context to client handlers
1307 };
1308
1309 // Initialize TCP server (creates and binds sockets)
1310 memset(&g_tcp_server, 0, sizeof(g_tcp_server));
1311 asciichat_error_t tcp_init_result = tcp_server_init(&g_tcp_server, &tcp_config);
1312 if (tcp_init_result != ASCIICHAT_OK) {
1313 FATAL(ERROR_NETWORK, "Failed to initialize TCP server");
1314 }
1315
1316 // =========================================================================
1317 // UPnP Port Mapping (Quick Win for Direct TCP)
1318 // =========================================================================
1319 // Track UPnP success for ACDS session type decision
1320 // If UPnP fails, we need to create a WebRTC session to enable client connectivity
1321 bool upnp_succeeded = false;
1322
1323 // Try to open port via UPnP so direct TCP works for ~70% of home users.
1324 // If this fails, clients fall back to WebRTC automatically - not fatal.
1325 //
1326 // Strategy:
1327 // 1. UPnP (works on ~90% of home routers)
1328 // 2. NAT-PMP fallback (Apple routers)
1329 // 3. If both fail: use ACDS + WebRTC (reliable, but slightly higher latency)
1330 if (GET_OPTION(enable_upnp) && !GET_OPTION(no_upnp)) {
1331 asciichat_error_t upnp_result = nat_upnp_open(port, "ASCII-Chat Server", &g_upnp_ctx);
1332
1333 if (upnp_result == ASCIICHAT_OK && g_upnp_ctx) {
1334 char public_addr[22];
1335 if (nat_upnp_get_address(g_upnp_ctx, public_addr, sizeof(public_addr)) == ASCIICHAT_OK) {
1336 printf("🌐 Public endpoint: %s (direct TCP)\\n", public_addr);
1337 log_info("UPnP: Port mapping successful, public endpoint: %s", public_addr);
1338 upnp_succeeded = true;
1339 }
1340 } else {
1341 log_info("UPnP: Port mapping unavailable or failed - will use WebRTC fallback");
1342 printf("📡 Clients behind strict NATs will use WebRTC fallback\\n");
1343 }
1344 } else {
1345 if (GET_OPTION(no_upnp)) {
1346 log_info("UPnP: Disabled via --no-upnp option");
1347 } else {
1348 log_info("UPnP: Disabled via environment variable or configuration");
1349 }
1350 printf("📡 WebRTC will be used for all clients\\n");
1351 }
1352
1353 struct timespec last_stats_time;
1354 (void)clock_gettime(CLOCK_MONOTONIC, &last_stats_time);
1355
1356 // Initialize synchronization primitives
1358 FATAL(ERROR_THREAD, "Failed to initialize client manager rwlock");
1359 }
1360
1361 // Lock debug system already initialized earlier in main()
1362
1363 // Check if SIGINT was received during initialization
1364 // If so, tcp_server_run() will detect it and exit immediately
1365 if (atomic_load(&g_server_should_exit)) {
1366 log_info("Shutdown signal received during initialization, skipping server startup");
1367 }
1368
1369 // Lock debug thread already started earlier in main()
1370
1371 // NOTE: g_client_manager is already zero-initialized in client.c with = {0}
1372 // We only need to initialize the mutex
1374
1375 // Initialize uthash head pointer for O(1) lookup (uthash requires NULL initialization)
1377
1378 // Initialize connection rate limiter (prevents DoS attacks)
1379 if (!atomic_load(&g_server_should_exit)) {
1380 log_debug("Initializing connection rate limiter...");
1382 if (!g_rate_limiter) {
1383 LOG_ERRNO_IF_SET("Failed to initialize rate limiter");
1384 if (!atomic_load(&g_server_should_exit)) {
1385 FATAL(ERROR_MEMORY, "Failed to create connection rate limiter");
1386 }
1387 } else {
1388 log_info("Connection rate limiter initialized (50 connections/min per IP)");
1389 }
1390 }
1391
1392 // Initialize audio mixer (always enabled on server)
1393 if (!atomic_load(&g_server_should_exit)) {
1394 log_debug("Initializing audio mixer for per-client audio rendering...");
1396 if (!g_audio_mixer) {
1397 LOG_ERRNO_IF_SET("Failed to initialize audio mixer");
1398 if (!atomic_load(&g_server_should_exit)) {
1399 FATAL(ERROR_AUDIO, "Failed to initialize audio mixer");
1400 }
1401 } else {
1402 if (!atomic_load(&g_server_should_exit)) {
1403 log_debug("Audio mixer initialized successfully for per-client audio rendering");
1404 }
1405 }
1406 }
1407
1408 // Initialize mDNS context for LAN service discovery (optional)
1409 // mDNS allows clients on the LAN to discover this server without knowing its IP
1410 // Can be disabled with --no-mdns-advertise
1411 // Note: Actual advertisement is deferred until after ACDS session creation (if --acds is enabled)
1412 if (!atomic_load(&g_server_should_exit) && !GET_OPTION(no_mdns_advertise)) {
1413 log_debug("Initializing mDNS for LAN service discovery...");
1414 g_mdns_ctx = asciichat_mdns_init();
1415 if (!g_mdns_ctx) {
1416 LOG_ERRNO_IF_SET("Failed to initialize mDNS (non-fatal, LAN discovery disabled)");
1417 log_warn("mDNS disabled - LAN service discovery will not be available");
1418 g_mdns_ctx = NULL;
1419 } else {
1420 log_debug("mDNS context initialized, advertisement deferred until session string is ready");
1421 }
1422 } else if (GET_OPTION(no_mdns_advertise)) {
1423 log_info("mDNS service advertisement disabled via --no-mdns-advertise");
1424 }
1425
1426 // ========================================================================
1427 // MAIN CONNECTION LOOP - Delegated to tcp_server
1428 // ========================================================================
1429 //
1430 // The tcp_server module handles:
1431 // 1. Dual-stack IPv4/IPv6 accept loop with select() timeout
1432 // 2. Spawning client_handler threads for each connection
1433 // 3. Responsive shutdown when g_tcp_server.running is set to false
1434 //
1435 // Client lifecycle is managed by ascii_chat_client_handler() which:
1436 // - Performs rate limiting
1437 // - Calls add_client() to initialize structures and spawn workers
1438 // - Blocks until client disconnects
1439 // - Calls remove_client() to cleanup and stop worker threads
1440
1441 // ACDS Session Creation: Register this server with discovery service
1442 // This also determines the session string for mDNS (if --acds is enabled)
1443 char session_string[64] = {0};
1444
1445 // ACDS Registration (conditional on --acds flag)
1446 if (GET_OPTION(acds)) {
1447 // Security Requirement Check (Issue #239):
1448 // Server IP must be protected by password, identity verification, or explicit opt-in
1449
1450 // Auto-detection: Check if password or identity verification is configured
1451 const char *password = GET_OPTION(password);
1452 bool has_password = password && strlen(password) > 0;
1453 const char *encrypt_key = GET_OPTION(encrypt_key);
1454 bool has_identity = encrypt_key && strlen(encrypt_key) > 0;
1455 bool explicit_expose = GET_OPTION(acds_expose_ip) != 0;
1456
1457 // Validate security configuration BEFORE attempting ACDS connection
1458 bool acds_expose_ip_flag = false;
1459
1460 if (has_password || has_identity) {
1461 // Auto-enable privacy: IP revealed only after verification
1462 acds_expose_ip_flag = false;
1463 log_plain("🔒 ACDS privacy enabled: IP disclosed only after %s verification",
1464 has_password ? "password" : "identity");
1465 } else if (explicit_expose) {
1466 // Explicit opt-in to public IP disclosure - requires confirmation
1467 log_plain_stderr("");
1468 log_plain_stderr("⚠️ WARNING: You are about to allow PUBLIC IP disclosure!");
1469 log_plain_stderr("⚠️ Anyone with the session string will be able to see your IP address.");
1470 log_plain_stderr("⚠️ This is NOT RECOMMENDED unless you understand the privacy implications.");
1471 log_plain_stderr("");
1472
1473 if (!platform_prompt_yes_no("Do you want to proceed with public IP disclosure", false)) {
1474 log_plain_stderr("");
1475 log_plain_stderr("❌ IP disclosure not confirmed. Server will run WITHOUT discovery service.");
1476 goto skip_acds_session;
1477 }
1478
1479 // User confirmed - proceed with public IP disclosure
1480 acds_expose_ip_flag = true;
1481 log_plain_stderr("");
1482 log_plain_stderr("⚠️ Public IP disclosure CONFIRMED");
1483 log_plain_stderr("⚠️ Your IP address will be visible to anyone with the session string");
1484 } else {
1485 // Security violation: No password, no identity, no explicit opt-in
1486 log_plain_stderr("❌ Cannot create ACDS session: No security configured!");
1487 log_plain_stderr(" You must either:");
1488 log_plain_stderr(" 1. Set a password: --password \"your-secret\"");
1489 log_plain_stderr(" 2. Use identity key: --key ~/.ssh/id_ed25519");
1490 log_plain_stderr(" 3. Explicitly allow public IP: --acds-expose-ip (NOT RECOMMENDED)");
1491 log_plain_stderr("");
1492 log_plain_stderr("Server will run WITHOUT discovery service.");
1493 goto skip_acds_session;
1494 }
1495
1496 // Security is configured, proceed with ACDS connection
1497 const char *acds_server = GET_OPTION(acds_server);
1498 uint16_t acds_port = (uint16_t)GET_OPTION(acds_port);
1499
1500 log_info("Attempting to create session on ACDS server at %s:%d...", acds_server, acds_port);
1501
1502 acds_client_config_t acds_config;
1504 SAFE_STRNCPY(acds_config.server_address, acds_server, sizeof(acds_config.server_address));
1505 acds_config.server_port = acds_port;
1506 acds_config.timeout_ms = 5000;
1507
1508 // Allocate ACDS client on heap for server lifecycle
1509 g_acds_client = SAFE_MALLOC(sizeof(acds_client_t), acds_client_t *);
1510 if (!g_acds_client) {
1511 log_error("Failed to allocate ACDS client");
1512 goto skip_acds_session;
1513 }
1514
1515 asciichat_error_t acds_connect_result = acds_client_connect(g_acds_client, &acds_config);
1516 if (acds_connect_result == ASCIICHAT_OK) {
1517 // Prepare session creation parameters
1518 acds_session_create_params_t create_params;
1519 memset(&create_params, 0, sizeof(create_params));
1520
1521 // Use server's Ed25519 identity public key if available
1522 if (g_server_encryption_enabled && has_identity) {
1523 memcpy(create_params.identity_pubkey, g_server_private_key.public_key, 32);
1524 log_debug("Using server identity key for ACDS session");
1525 } else {
1526 // No identity key available - use zero key
1527 // ACDS will accept this if identity verification is not required
1528 memset(create_params.identity_pubkey, 0, 32);
1529 log_debug("No server identity key - using zero key for ACDS session");
1530 }
1531
1532 create_params.capabilities = 0x03; // Video + Audio
1533 create_params.max_participants = GET_OPTION(max_clients);
1534
1535 // Set password if configured
1536 create_params.has_password = has_password;
1537 if (has_password) {
1538 // TODO: Hash password with Argon2id
1539 SAFE_STRNCPY(create_params.password, password, sizeof(create_params.password));
1540 }
1541
1542 // Set IP disclosure policy (determined above)
1543 create_params.acds_expose_ip = acds_expose_ip_flag;
1544
1545 // Set session type (Direct TCP or WebRTC)
1546 // Auto-detect: Use WebRTC if UPnP failed OR if explicitly requested via --webrtc
1547 // Exception: If bind address is 0.0.0.0, server is on public IP - use Direct TCP
1548 const char *bind_addr = GET_OPTION(address);
1549 bool bind_all_interfaces = (strcmp(bind_addr, "0.0.0.0") == 0);
1550
1551 if (GET_OPTION(webrtc)) {
1552 // Explicit WebRTC request
1553 create_params.session_type = SESSION_TYPE_WEBRTC;
1554 log_info("ACDS session type: WebRTC (explicitly requested via --webrtc)");
1555 } else if (bind_all_interfaces) {
1556 // Bind to 0.0.0.0 means server is publicly accessible
1557 create_params.session_type = SESSION_TYPE_DIRECT_TCP;
1558 log_info("ACDS session type: Direct TCP (bind address 0.0.0.0, server is publicly accessible)");
1559 } else if (upnp_succeeded) {
1560 // UPnP port mapping worked
1561 create_params.session_type = SESSION_TYPE_DIRECT_TCP;
1562 log_info("ACDS session type: Direct TCP (UPnP succeeded, server is publicly accessible)");
1563 } else {
1564 // UPnP failed and not on public IP - use WebRTC
1565 create_params.session_type = SESSION_TYPE_WEBRTC;
1566 log_info("ACDS session type: WebRTC (UPnP failed, server behind NAT)");
1567 }
1568
1569 // Server connection information (where clients should connect)
1570 // If bind address is 0.0.0.0, leave server_address empty for ACDS to auto-detect public IP
1571 if (bind_all_interfaces) {
1572 create_params.server_address[0] = '\0'; // Empty - ACDS will use connection source IP
1573 log_debug("Bind address is 0.0.0.0, ACDS will auto-detect public IP from connection");
1574 } else {
1575 SAFE_STRNCPY(create_params.server_address, bind_addr, sizeof(create_params.server_address));
1576 }
1577 create_params.server_port = port;
1578
1579 // Create session
1580 acds_session_create_result_t create_result;
1581 asciichat_error_t create_err = acds_session_create(g_acds_client, &create_params, &create_result);
1582
1583 if (create_err == ASCIICHAT_OK) {
1584 SAFE_STRNCPY(session_string, create_result.session_string, sizeof(session_string));
1585 log_plain("✨ Session created successfully!");
1586 log_plain("📋 Session string: %s", session_string);
1587 log_plain("🔗 Share this with others to join:");
1588 log_plain(" ascii-chat %s", session_string);
1589
1590 // Server must join its own session so ACDS can route signaling messages
1591 log_debug("Server joining session as first participant for WebRTC signaling...");
1592 acds_session_join_params_t join_params = {0};
1593 join_params.session_string = session_string;
1594
1595 // Use same identity key as session creation
1596 memcpy(join_params.identity_pubkey, create_params.identity_pubkey, 32);
1597
1598 // Include password if session is password-protected
1599 if (has_password) {
1600 join_params.has_password = true;
1601 SAFE_STRNCPY(join_params.password, password, sizeof(join_params.password));
1602 }
1603
1604 acds_session_join_result_t join_result = {0};
1605 asciichat_error_t join_err = acds_session_join(g_acds_client, &join_params, &join_result);
1606 if (join_err != ASCIICHAT_OK || !join_result.success) {
1607 log_error("Failed to join own session: %s (error: %s)", asciichat_error_string(join_err),
1608 join_result.error_message[0] ? join_result.error_message : "unknown");
1609 // Continue anyway - this is not fatal for Direct TCP sessions
1610 } else {
1611 log_info("Server joined session successfully (participant_id: %02x%02x...)", join_result.participant_id[0],
1612 join_result.participant_id[1]);
1613 // Store participant ID for WebRTC signaling (needed to identify server in SDP/ICE messages)
1614 memcpy(create_result.session_id, join_result.session_id, 16);
1615 }
1616
1617 // Keep ACDS connection alive for WebRTC signaling relay
1618 log_debug("Server staying connected to ACDS for signaling relay");
1619
1620 // Create ACDS transport wrapper for sending signaling packets
1621 g_acds_transport = acip_tcp_transport_create(g_acds_client->socket, NULL);
1622 if (!g_acds_transport) {
1623 log_error("Failed to create ACDS transport wrapper");
1624 } else {
1625 log_debug("ACDS transport wrapper created for signaling");
1626
1627 // Start ACDS ping thread to keep connection alive (for ALL session types)
1628 int ping_thread_result = asciichat_thread_create(&g_acds_ping_thread, acds_ping_thread, NULL);
1629 if (ping_thread_result != 0) {
1630 log_error("Failed to create ACDS ping thread: %d", ping_thread_result);
1631 } else {
1632 log_info("ACDS ping thread started to keep connection alive");
1633 g_acds_ping_thread_started = true;
1634 }
1635 }
1636
1637 // Initialize WebRTC peer_manager if session type is WebRTC
1638 if (create_params.session_type == SESSION_TYPE_WEBRTC) {
1639 log_info("Initializing WebRTC peer manager for session (role=CREATOR)...");
1640
1641 // Configure STUN servers for ICE gathering
1642 stun_server_t stun_servers[2];
1643 stun_servers[0].host_len = (uint8_t)strlen("stun:stun.l.google.com:19302");
1644 SAFE_STRNCPY(stun_servers[0].host, "stun:stun.l.google.com:19302", sizeof(stun_servers[0].host));
1645 stun_servers[1].host_len = (uint8_t)strlen("stun:stun1.l.google.com:19302");
1646 SAFE_STRNCPY(stun_servers[1].host, "stun:stun1.l.google.com:19302", sizeof(stun_servers[1].host));
1647
1648 // Configure peer_manager
1649 webrtc_peer_manager_config_t pm_config = {
1650 .role = WEBRTC_ROLE_CREATOR, // Server accepts offers, generates answers
1651 .stun_servers = stun_servers,
1652 .stun_count = 2,
1653 .turn_servers = NULL, // No TURN for server (clients should have public IP or use TURN)
1654 .turn_count = 0,
1655 .on_transport_ready = on_webrtc_transport_ready,
1656 .user_data = &server_ctx,
1657 .crypto_ctx = NULL // WebRTC handles crypto internally
1658 };
1659
1660 // Configure signaling callbacks for relaying SDP/ICE via ACDS
1661 webrtc_signaling_callbacks_t signaling_callbacks = {
1662 .send_sdp = server_send_sdp, .send_ice = server_send_ice, .user_data = NULL};
1663
1664 // Create peer_manager
1665 asciichat_error_t pm_result =
1666 webrtc_peer_manager_create(&pm_config, &signaling_callbacks, &g_webrtc_peer_manager);
1667 if (pm_result != ASCIICHAT_OK) {
1668 log_error("Failed to create WebRTC peer_manager: %s", asciichat_error_string(pm_result));
1669 g_webrtc_peer_manager = NULL;
1670 } else {
1671 log_info("WebRTC peer_manager initialized successfully");
1672
1673 // Start ACDS receive thread for WebRTC signaling relay
1674 int thread_result = asciichat_thread_create(&g_acds_receive_thread, acds_receive_thread, NULL);
1675 if (thread_result != 0) {
1676 log_error("Failed to create ACDS receive thread: %d", thread_result);
1677 // Cleanup peer_manager since signaling won't work
1678 webrtc_peer_manager_destroy(g_webrtc_peer_manager);
1679 g_webrtc_peer_manager = NULL;
1680 } else {
1681 log_info("ACDS receive thread started for WebRTC signaling relay");
1682 g_acds_receive_thread_started = true;
1683 }
1684 }
1685 } else {
1686 log_debug("Session type is DIRECT_TCP, skipping WebRTC peer_manager initialization");
1687 }
1688
1689 // Advertise mDNS with ACDS session string
1690 // This ensures both mDNS and ACDS discovery return the same session string
1691 advertise_mdns_with_session(session_string, (uint16_t)port);
1692 } else {
1693 log_warn("Failed to create session on ACDS server (server will run without discovery)");
1694 // Clean up failed ACDS client
1695 if (g_acds_client) {
1696 acds_client_disconnect(g_acds_client);
1697 SAFE_FREE(g_acds_client);
1698 g_acds_client = NULL;
1699 }
1700 }
1701 } else {
1702 log_warn("Could not connect to ACDS server at %s:%d (server will run without discovery)", acds_server, acds_port);
1703 }
1704 } else {
1705 log_info("ACDS registration disabled (use --acds to enable)");
1706 }
1707
1708skip_acds_session:
1709 // Fallback: If no session string was set by ACDS (either disabled or failed),
1710 // generate a random session string for mDNS discovery only
1711 if (session_string[0] == '\0' && g_mdns_ctx) {
1712 log_debug("No ACDS session string available, generating random session for mDNS");
1713
1714 // Generate a proper word-word-word session string for mDNS discovery
1715 // This uses simple word lists to create memorable session strings
1716 static const char *adjectives[] = {
1717 "swift", "bright", "gentle", "calm", "bold", "quiet", "happy", "proud",
1718 "quick", "warm", "wise", "true", "safe", "keen", "just", "fair",
1719 };
1720 static const char *nouns[] = {
1721 "river", "mountain", "forest", "ocean", "valley", "peak", "lake", "hill",
1722 "meadow", "canyon", "stream", "sky", "stone", "eagle", "wolf", "bear",
1723 };
1724 static const size_t adj_count = sizeof(adjectives) / sizeof(adjectives[0]);
1725 static const size_t noun_count = sizeof(nouns) / sizeof(nouns[0]);
1726
1727 // Generate random indices for session string (deterministic per server start for testing)
1728 uint32_t seed = (uint32_t)time(NULL) ^ (uint32_t)getpid();
1729 uint32_t adj1_idx = (seed / 1) % adj_count;
1730 uint32_t noun1_idx = (seed / 13) % noun_count;
1731 uint32_t adj2_idx = (seed / 31) % adj_count;
1732
1733 snprintf(session_string, sizeof(session_string), "%s-%s-%s", adjectives[adj1_idx], nouns[noun1_idx],
1734 adjectives[adj2_idx]);
1735
1736 log_info("Generated random session string for mDNS: '%s'", session_string);
1737
1738 // Advertise mDNS with random session string
1739 advertise_mdns_with_session(session_string, (uint16_t)port);
1740 }
1741
1742 log_info("Server entering accept loop (port %d)...", port);
1743
1744 // Run TCP server (blocks until shutdown signal received)
1745 // tcp_server_run() handles:
1746 // - select() on IPv4/IPv6 sockets with timeout
1747 // - accept() new connections
1748 // - Spawn ascii_chat_client_handler() thread for each connection
1749 // - Responsive shutdown when atomic_store(&g_tcp_server.running, false)
1750 asciichat_error_t run_result = tcp_server_run(&g_tcp_server);
1751 if (run_result != ASCIICHAT_OK) {
1752 log_error("TCP server exited with error");
1753 }
1754
1755 log_info("Server accept loop exited");
1756
1757 // Cleanup
1758 log_info("Server shutting down...");
1759 atomic_store(&g_server_should_exit, true);
1760
1761 // Wake up any threads that might be blocked on condition variables
1762 // (like packet queues) to ensure responsive shutdown
1763 // This must happen BEFORE client cleanup to wake up any blocked threads
1764 static_cond_broadcast(&g_shutdown_cond);
1765 // NOTE: Do NOT call cond_destroy() on statically-initialized condition variables
1766 // g_shutdown_cond uses STATIC_COND_INIT which doesn't allocate resources that need cleanup
1767 // Calling cond_destroy() on a static cond is undefined behavior on some platforms
1768
1769 // CRITICAL: Close all client sockets immediately to unblock receive threads
1770 // The signal handler only closed the listening socket, but client receive threads
1771 // are still blocked in recv_with_timeout(). We need to close their sockets to unblock them.
1772 log_info("Closing all client sockets to unblock receive threads...");
1773
1774 // Use write lock since we're modifying client->socket
1776 for (int i = 0; i < MAX_CLIENTS; i++) {
1778 if (atomic_load(&client->client_id) != 0 && client->socket != INVALID_SOCKET_VALUE) {
1779 socket_close(client->socket);
1780 client->socket = INVALID_SOCKET_VALUE;
1781 }
1782 }
1784
1785 log_info("Signaling all clients to stop (sockets closed, g_server_should_exit set)...");
1786
1787 // Stop and destroy server worker thread pool (stats logger, etc.)
1788 if (g_server_worker_pool) {
1789 thread_pool_destroy(g_server_worker_pool);
1790 g_server_worker_pool = NULL;
1791 log_info("Server worker thread pool stopped");
1792 }
1793
1794 // Destroy rate limiter
1795 if (g_rate_limiter) {
1797 g_rate_limiter = NULL;
1798 }
1799
1800 // Clean up all connected clients
1801 log_info("Cleaning up connected clients...");
1802 // FIXED: Simplified to collect client IDs first, then remove them without holding locks
1803 uint32_t clients_to_remove[MAX_CLIENTS];
1804 int client_count = 0;
1805
1807 for (int i = 0; i < MAX_CLIENTS; i++) {
1809
1810 // Only attempt to clean up clients that were actually connected
1811 // (client_id is 0 for uninitialized clients, starts from 1 for connected clients)
1812 // FIXED: Only access mutex for initialized clients to avoid accessing uninitialized mutex
1813 if (atomic_load(&client->client_id) == 0) {
1814 continue; // Skip uninitialized clients
1815 }
1816
1817 // Use snapshot pattern to avoid holding both locks simultaneously
1818 // This prevents deadlock by not acquiring client_state_mutex while holding rwlock
1819 uint32_t client_id_snapshot = atomic_load(&client->client_id); // Atomic read is safe under rwlock
1820
1821 // Clean up ANY client that was allocated, whether active or not
1822 // (disconnected clients may not be active but still have resources)
1823 clients_to_remove[client_count++] = client_id_snapshot;
1824 }
1826
1827 // Remove all clients without holding any locks
1828 for (int i = 0; i < client_count; i++) {
1829 if (remove_client(&server_ctx, clients_to_remove[i]) != 0) {
1830 log_error("Failed to remove client %u during shutdown", clients_to_remove[i]);
1831 }
1832 }
1833
1834 // Clean up hash table
1835 // Clean up uthash table (uthash handles deletion when the last item is removed,
1836 // but we should clear it here just in case there are stragglers)
1838 client_info_t *current_client, *tmp;
1839 HASH_ITER(hh, g_client_manager.clients_by_id, current_client, tmp) {
1840 HASH_DELETE(hh, g_client_manager.clients_by_id, current_client);
1841 // Note: We don't free current_client here because it's part of the clients[] array
1842 }
1844 }
1845
1846 // Clean up audio mixer
1847 if (g_audio_mixer) {
1849 g_audio_mixer = NULL;
1850 }
1851
1852 // Clean up mDNS context
1853 if (g_mdns_ctx) {
1854 asciichat_mdns_shutdown(g_mdns_ctx);
1855 g_mdns_ctx = NULL;
1856 log_info("mDNS context shut down");
1857 }
1858
1859 // Clean up synchronization primitives
1862
1863#ifdef NDEBUG
1864 // Clean up statistics system
1865 stats_cleanup();
1866#endif
1867
1868#ifndef NDEBUG
1869 // Clean up lock debugging system (always, regardless of build type)
1870 // Lock debug records are allocated in debug builds too, so they must be cleaned up
1871 lock_debug_cleanup();
1872#endif
1873
1874 // Shutdown TCP server (closes listen sockets and cleans up)
1875 tcp_server_shutdown(&g_tcp_server);
1876
1877 // Join ACDS threads (if started)
1878 // NOTE: Must be done BEFORE destroying transport to ensure clean shutdown
1879 if (g_acds_ping_thread_started) {
1880 log_debug("Joining ACDS ping thread");
1881 asciichat_thread_join(&g_acds_ping_thread, NULL);
1882 g_acds_ping_thread_started = false;
1883 log_debug("ACDS ping thread joined");
1884 }
1885
1886 if (g_acds_receive_thread_started) {
1887 log_debug("Joining ACDS receive thread");
1888 asciichat_thread_join(&g_acds_receive_thread, NULL);
1889 g_acds_receive_thread_started = false;
1890 log_debug("ACDS receive thread joined");
1891 }
1892
1893 // Clean up WebRTC peer manager (if initialized for ACDS signaling relay)
1894 if (g_webrtc_peer_manager) {
1895 log_debug("Destroying WebRTC peer manager");
1896 webrtc_peer_manager_destroy(g_webrtc_peer_manager);
1897 g_webrtc_peer_manager = NULL;
1898 }
1899
1900 // Clean up ACDS transport wrapper (if created)
1901 if (g_acds_transport) {
1902 log_debug("Destroying ACDS transport wrapper");
1903 acip_transport_destroy(g_acds_transport);
1904 g_acds_transport = NULL;
1905 }
1906
1907 // Disconnect from ACDS server (if connected for WebRTC signaling relay)
1908 if (g_acds_client) {
1909 log_debug("Disconnecting from ACDS server");
1910 acds_client_disconnect(g_acds_client);
1911 SAFE_FREE(g_acds_client);
1912 g_acds_client = NULL;
1913 }
1914
1915 // Clean up SIMD caches
1917
1918 // Clean up symbol cache
1919 // This must be called BEFORE log_destroy() as symbol_cache_cleanup() uses log_debug()
1920 // Safe to call even if atexit() runs - it's idempotent (checks g_symbol_cache_initialized)
1921 // Also called via platform_cleanup() atexit handler, but explicit call ensures proper ordering
1923
1924 // Clean up global buffer pool (explicitly, as atexit may not run on Ctrl-C)
1925 // Note: This is also registered with atexit(), but calling it explicitly is safe (idempotent)
1926 // Safe to call even if atexit() runs - it checks g_global_buffer_pool and sets it to NULL
1928
1929 // Clean up binary path cache explicitly
1930 // Note: This is also called by platform_cleanup() via atexit(), but it's idempotent
1931 // (checks g_cache_initialized and sets it to false, sets g_bin_path_cache to NULL)
1932 // Safe to call even if atexit() runs later
1934
1935 // Clean up errno context (allocated strings, backtrace symbols)
1937
1938 // Clean up RCU-based options state
1940
1941 // Clean up platform-specific resources (Windows: Winsock cleanup, timer restoration)
1942 // POSIX: minimal cleanup (symbol cache already handled above on Windows)
1943#ifdef _WIN32
1945 timeEndPeriod(1); // Restore Windows timer resolution
1946#endif
1947
1948#ifndef NDEBUG
1949 // Join the lock debug thread as one of the very last things before exit
1950 lock_debug_cleanup_thread();
1951#endif
1952
1953 log_info("Server shutdown complete");
1954
1956
1957 log_destroy();
1958
1959 // Use exit() to allow atexit() handlers to run
1960 // Cleanup functions are idempotent (check if initialized first)
1961 exit(0);
1962}
void acds_client_config_init_defaults(acds_client_config_t *config)
Initialize ACDS client configuration with defaults.
Definition acds_client.c:38
asciichat_error_t acds_session_create(acds_client_t *client, const acds_session_create_params_t *params, acds_session_create_result_t *result)
Create a new session on the discovery server.
asciichat_error_t acds_session_join(acds_client_t *client, const acds_session_join_params_t *params, acds_session_join_result_t *result)
Join an existing session.
asciichat_error_t acds_client_connect(acds_client_t *client, const acds_client_config_t *config)
Connect to ACDS server.
Definition acds_client.c:53
void acds_client_disconnect(acds_client_t *client)
Disconnect from ACDS server.
#define LOG_ERRNO_IF_SET(message)
Check if any error occurred and log it if so.
@ SESSION_TYPE_WEBRTC
WebRTC P2P mesh with STUN/TURN relay.
@ SESSION_TYPE_DIRECT_TCP
Direct TCP connection to server IP:port (default)
#define AUDIO_SAMPLE_RATE
Audio sample rate (48kHz professional quality, Opus-compatible)
mixer_t * mixer_create(int max_sources, int sample_rate)
Create a new audio mixer.
Definition mixer.c:218
void mixer_destroy(mixer_t *mixer)
Destroy a mixer and free all resources.
Definition mixer.c:346
void buffer_pool_cleanup_global(void)
unsigned short uint16_t
Definition common.h:57
unsigned int uint32_t
Definition common.h:58
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:358
#define SAFE_FREE(ptr)
Definition common.h:320
#define SAFE_MALLOC(size, cast)
Definition common.h:208
#define FATAL(code,...)
Exit with error code and custom message, with stack trace in debug builds.
Definition common.h:151
unsigned char uint8_t
Definition common.h:56
void asciichat_error_stats_print(void)
Print error statistics to stderr.
void asciichat_errno_cleanup(void)
Cleanup error system resources.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:46
@ ERROR_NETWORK
Definition error_codes.h:69
@ ERROR_AUDIO
Definition error_codes.h:64
@ ERROR_MEMORY
Definition error_codes.h:53
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_CONFIG
Definition error_codes.h:54
@ ERROR_CRYPTO
Definition error_codes.h:88
@ ERROR_THREAD
Definition error_codes.h:95
uint8_t public_key[32]
Definition key_types.h:99
#define MAX_CLIENTS
Maximum possible clients (static array size) - actual runtime limit set by –max-clients (1-32)
Definition limits.h:23
#define log_warn(...)
Log a WARN message.
void log_destroy(void)
Destroy the logging system and close log file.
#define log_error(...)
Log an ERROR message.
#define log_plain_stderr(...)
Plain logging to stderr with newline.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
void log_set_terminal_output(bool enabled)
Control stderr output to terminal.
#define log_plain(...)
Plain logging - writes to both log file and stderr without timestamps or log levels.
#define ACCEPT_TIMEOUT
Accept timeout in seconds (3 seconds)
Definition network.h:128
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
Definition options.h:644
int strtoint_safe(const char *str)
Safely parse string to integer with validation.
const float weight_blue
Blue weight for luminance calculation.
const float weight_red
const float weight_green
Green weight for luminance calculation.
void symbol_cache_cleanup(void)
Clean up the symbol cache and free all resources.
Definition symbols.c:419
signal_handler_t platform_signal(int sig, signal_handler_t handler)
Set a signal handler.
int mutex_init(mutex_t *mutex)
Initialize a mutex.
#define rwlock_wrunlock(lock)
Release a write lock (with debug tracking in debug builds)
Definition rwlock.h:249
bool platform_prompt_yes_no(const char *prompt, bool default_yes)
Prompt the user for a yes/no answer.
int rwlock_destroy(rwlock_t *lock)
Destroy a read-write lock.
void socket_cleanup(void)
Cleanup socket subsystem.
#define INVALID_SOCKET_VALUE
Invalid socket value (POSIX: -1)
Definition socket.h:52
#define rwlock_rdlock(lock)
Acquire a read lock (with debug tracking in debug builds)
Definition rwlock.h:194
int socket_close(socket_t sock)
Close a socket.
#define UNUSED(x)
Suppress unused parameter warnings.
#define rwlock_wrlock(lock)
Acquire a write lock (with debug tracking in debug builds)
Definition rwlock.h:213
int asciichat_thread_join(asciichat_thread_t *thread, void **retval)
Wait for a thread to complete (blocking)
void platform_cleanup_binary_path_cache(void)
Cleanup the binary PATH cache.
Definition system.c:261
int rwlock_init(rwlock_t *lock)
Initialize a read-write lock.
#define rwlock_rdunlock(lock)
Release a read lock (with debug tracking in debug builds)
Definition rwlock.h:231
int asciichat_thread_create(asciichat_thread_t *thread, void *(*func)(void *), void *arg)
Create a new thread.
int mutex_destroy(mutex_t *mutex)
Destroy a mutex.
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.
void shutdown_register_callback(shutdown_check_fn callback)
Register application's shutdown check function.
stun_server_t
Definition stun.h:75
void precalc_rgb_palettes(const float red, const float green, const float blue)
Precalculate RGB palettes with color adjustment.
void simd_caches_destroy_all(void)
Destroy all SIMD caches.
void ascii_simd_init(void)
Initialize SIMD subsystem.
Definition ascii_simd.c:90
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)
Create a WebRTC peer manager.
void webrtc_peer_manager_destroy(webrtc_peer_manager_t *manager)
Destroy peer manager and close all connections.
@ WEBRTC_ROLE_CREATOR
Session creator - accepts offers, generates answers.
asciichat_error_t tcp_server_init(tcp_server_t *server, const tcp_server_config_t *config)
Initialize TCP server.
asciichat_error_t tcp_server_run(tcp_server_t *server)
Run TCP server accept loop.
void tcp_server_shutdown(tcp_server_t *server)
Shutdown TCP server.
void asciichat_mdns_shutdown(asciichat_mdns_t *mdns)
Shutdown mDNS context and cleanup.
Definition mdns.c:61
asciichat_mdns_t * asciichat_mdns_init(void)
Initialize mDNS context.
Definition mdns.c:30
void rate_limiter_destroy(rate_limiter_t *limiter)
Destroy rate limiter and free resources.
Definition rate_limit.c:115
rate_limiter_t * rate_limiter_create_memory(void)
Create in-memory rate limiter.
Definition rate_limit.c:72
void options_state_shutdown(void)
Shutdown RCU options system.
Definition rcu.c:197
rate_limiter_t * g_rate_limiter
Global rate limiter for connection attempts and packet processing.
mixer_t * g_audio_mixer
Global audio mixer instance for multi-client audio processing.
rwlock_t g_client_manager_rwlock
Reader-writer lock protecting the global client manager.
atomic_bool g_server_should_exit
Global atomic shutdown flag shared across all threads.
client_manager_t g_client_manager
Global client manager for signal handler access.
static_cond_t g_shutdown_cond
Global shutdown condition variable for waking blocked threads.
int remove_client(server_context_t *server_ctx, uint32_t client_id)
server_stats_t g_stats
Global server statistics structure.
Definition stats.c:159
void * stats_logger_thread(void *arg)
Main statistics collection and reporting thread function.
Definition stats.c:332
int stats_init(void)
Initialize the stats mutex.
Definition stats.c:184
mutex_t g_stats_mutex
Mutex protecting global server statistics.
Definition stats.c:168
void stats_cleanup(void)
Cleanup the stats mutex.
Definition stats.c:195
ACDS client connection configuration.
Definition acds_client.h:44
char server_address[256]
ACDS server address (e.g., "discovery.ascii.chat" or "127.0.0.1")
Definition acds_client.h:45
uint32_t timeout_ms
Connection timeout in milliseconds.
Definition acds_client.h:47
uint16_t server_port
ACDS server port (default: 27225)
Definition acds_client.h:46
ACDS client connection handle.
Definition acds_client.h:53
socket_t socket
TCP socket to ACDS server.
Definition acds_client.h:55
Session creation request parameters.
Definition acds_client.h:90
uint16_t server_port
Server port where clients should connect.
bool acds_expose_ip
Explicitly allow public IP disclosure (–acds-expose-ip opt-in)
Definition acds_client.h:97
uint8_t max_participants
Maximum participants (1-8)
Definition acds_client.h:94
uint8_t identity_pubkey[32]
Ed25519 public key (host identity)
Definition acds_client.h:91
char server_address[64]
Server address where clients should connect.
bool has_password
Password protection enabled.
Definition acds_client.h:95
uint8_t capabilities
Bit 0: video, Bit 1: audio.
Definition acds_client.h:93
char password[128]
Optional password (if has_password)
Definition acds_client.h:96
uint8_t session_type
acds_session_type_t: 0=DIRECT_TCP (default), 1=WEBRTC
Definition acds_client.h:98
Session creation result.
uint8_t session_id[16]
Session UUID.
char session_string[49]
Generated session string (null-terminated)
Session join parameters.
uint8_t identity_pubkey[32]
Participant's Ed25519 public key.
bool has_password
Password provided.
char password[128]
Password (if has_password)
const char * session_string
Session to join.
Session join result.
char error_message[129]
Error message (if !success, null-terminated)
uint8_t participant_id[16]
Participant UUID (if success)
bool success
Join succeeded.
uint8_t session_id[16]
Session UUID (if success)
Per-client state structure for server-side client management.
atomic_uint client_id
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.
Definition server/main.h:81
tcp_server_t * tcp_server
TCP server managing connections.
Definition server/main.h:83
TCP server configuration.
Peer manager configuration.
webrtc_peer_role_t role
Session role (creator or joiner)
Signaling callbacks for sending SDP/ICE.
webrtc_send_sdp_callback_t send_sdp
Send SDP via ACDS.
void thread_pool_destroy(thread_pool_t *pool)
Destroy a thread pool.
Definition thread_pool.c:43
thread_pool_t * thread_pool_create(const char *pool_name)
Create a new thread pool.
Definition thread_pool.c:12
asciichat_error_t thread_pool_spawn(thread_pool_t *pool, void *(*thread_func)(void *), void *thread_arg, int stop_id, const char *thread_name)
Spawn a worker thread in the pool.
Definition thread_pool.c:65
acip_transport_t * acip_tcp_transport_create(socket_t sockfd, crypto_context_t *crypto_ctx)
Create TCP transport from existing socket.
void acip_transport_destroy(acip_transport_t *transport)
Destroy transport and free all resources.
asciichat_error_t nat_upnp_open(uint16_t internal_port, const char *description, nat_upnp_context_t **ctx)
Discover and open port via UPnP.
Definition upnp.c:249
asciichat_error_t nat_upnp_get_address(const nat_upnp_context_t *ctx, char *addr, size_t addr_len)
Get the public address (IP:port) for advertising to clients.
Definition upnp.c:323

References ACCEPT_TIMEOUT, acds_client_config_init_defaults(), acds_client_connect(), acds_client_disconnect(), acds_session_create_params_t::acds_expose_ip, acds_session_create(), acds_session_join(), acip_tcp_transport_create(), acip_transport_destroy(), ascii_simd_init(), asciichat_errno_cleanup(), asciichat_error_stats_print(), asciichat_mdns_init(), asciichat_mdns_shutdown(), ASCIICHAT_OK, asciichat_thread_create(), asciichat_thread_join(), AUDIO_SAMPLE_RATE, buffer_pool_cleanup_global(), acds_session_create_params_t::capabilities, client_info::client_id, client_manager_t::clients, client_manager_t::clients_by_id, ERROR_AUDIO, ERROR_CONFIG, ERROR_CRYPTO, ERROR_MEMORY, acds_session_join_result_t::error_message, ERROR_NETWORK, ERROR_THREAD, FATAL, g_audio_mixer, g_client_manager, g_client_manager_rwlock, g_client_whitelist, g_num_whitelisted_clients, g_rate_limiter, g_server_encryption_enabled, g_server_private_key, g_server_should_exit, g_shutdown_cond, g_stats, g_stats_mutex, GET_OPTION, acds_session_create_params_t::has_password, acds_session_join_params_t::has_password, acds_session_create_params_t::identity_pubkey, acds_session_join_params_t::identity_pubkey, INVALID_SOCKET_VALUE, log_debug, log_destroy(), LOG_ERRNO_IF_SET, log_error, log_info, log_plain, log_plain_stderr, log_set_terminal_output(), log_warn, MAX_CLIENTS, acds_session_create_params_t::max_participants, mixer_create(), mixer_destroy(), client_manager_t::mutex, mutex_destroy(), mutex_init(), nat_upnp_get_address(), nat_upnp_open(), options_state_shutdown(), acds_session_join_result_t::participant_id, acds_session_create_params_t::password, acds_session_join_params_t::password, platform_cleanup_binary_path_cache(), platform_prompt_yes_no(), platform_signal(), tcp_server_config_t::port, precalc_rgb_palettes(), private_key_t::public_key, rate_limiter_create_memory(), rate_limiter_destroy(), remove_client(), webrtc_peer_manager_config_t::role, rwlock_destroy(), rwlock_init(), rwlock_rdlock, rwlock_rdunlock, rwlock_wrlock, rwlock_wrunlock, SAFE_FREE, SAFE_MALLOC, SAFE_STRNCPY, webrtc_signaling_callbacks_t::send_sdp, acds_client_config_t::server_address, acds_session_create_params_t::server_address, acds_client_config_t::server_port, acds_session_create_params_t::server_port, acds_session_create_result_t::session_id, acds_session_join_result_t::session_id, acds_session_create_result_t::session_string, acds_session_join_params_t::session_string, acds_session_create_params_t::session_type, SESSION_TYPE_DIRECT_TCP, SESSION_TYPE_WEBRTC, shutdown_register_callback(), simd_caches_destroy_all(), acds_client_t::socket, client_info::socket, socket_cleanup(), socket_close(), stats_cleanup(), stats_init(), stats_logger_thread(), strtoint_safe(), stun_server_t, acds_session_join_result_t::success, symbol_cache_cleanup(), server_context_t::tcp_server, tcp_server_init(), tcp_server_run(), tcp_server_shutdown(), thread_pool_create(), thread_pool_destroy(), thread_pool_spawn(), acds_client_config_t::timeout_ms, UNUSED, webrtc_peer_manager_create(), webrtc_peer_manager_destroy(), WEBRTC_ROLE_CREATOR, weight_blue, weight_green, and weight_red.

Variable Documentation

◆ g_rate_limiter

rate_limiter_t* g_rate_limiter
extern

Global connection rate limiter.

Shared rate limiter instance used by packet handlers to prevent DoS attacks. Tracks connection attempts and packet rates per IP address.

Global connection rate limiter.

In-memory rate limiter to prevent connection flooding and DoS attacks. Tracks connection attempts and packet rates per IP address with configurable limits.

Default limits (from rate_limit.c):

  • RATE_EVENT_CONNECTION: 50 connections per 60 seconds
  • RATE_EVENT_IMAGE_FRAME: 144 FPS (8640 frames/min)
  • RATE_EVENT_AUDIO: 172 FPS (10320 packets/min)
  • RATE_EVENT_PING: 2 Hz (120 pings/min)
  • RATE_EVENT_CLIENT_JOIN: 10 joins per 60 seconds
  • RATE_EVENT_CONTROL: 100 control packets per 60 seconds

THREAD SAFETY: The rate limiter is thread-safe and can be used concurrently from the main accept loop and packet handlers without external synchronization.

See also
main.h for extern declaration

Definition at line 188 of file server/main.c.

Referenced by process_decrypted_packet(), and server_main().