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.
1160 {
1161
1162
1163
1164
1166
1167
1168 log_info(
"Initializing crypto...");
1169 if (init_server_crypto() != 0) {
1170
1173 }
1174 log_info(
"Crypto initialized successfully");
1175
1176
1178
1179 log_info(
"ASCII Chat server starting...");
1180
1181
1183 if (port == INT_MIN) {
1186 }
1187
1190
1191
1192 log_debug(
"Setting up simple signal handlers...");
1193
1194
1196
1198
1199#ifndef _WIN32
1201#else
1203#endif
1204#ifndef _WIN32
1205
1207#endif
1208
1209#ifndef NDEBUG
1210
1211 if (lock_debug_start_thread() != 0) {
1213 }
1214
1217 }
1218#endif
1219
1220
1222 if (!g_server_worker_pool) {
1225 }
1226
1227
1230 } else {
1231 log_info(
"Statistics logger thread started");
1232 }
1233
1234
1235 log_debug(
"Config check: GET_OPTION(address)='%s', GET_OPTION(address6)='%s'",
GET_OPTION(address),
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
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
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
1261 bind_ipv4 = true;
1262 bind_ipv6 = false;
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
1267 bind_ipv4 = false;
1268 bind_ipv6 = true;
1270 log_info(
"Binding only to IPv6 address: %s", ipv6_address);
1271 } else {
1272
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
1281
1295 };
1296
1297
1300 .ipv4_address = ipv4_address,
1301 .ipv6_address = ipv6_address,
1302 .bind_ipv4 = bind_ipv4,
1303 .bind_ipv6 = bind_ipv6,
1305 .client_handler = ascii_chat_client_handler,
1306 .user_data = &server_ctx,
1307 };
1308
1309
1310 memset(&g_tcp_server, 0, sizeof(g_tcp_server));
1314 }
1315
1316
1317
1318
1319
1320
1321 bool upnp_succeeded = false;
1322
1323
1324
1325
1326
1327
1328
1329
1332
1334 char public_addr[22];
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 {
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
1359 }
1360
1361
1362
1363
1364
1366 log_info(
"Shutdown signal received during initialization, skipping server startup");
1367 }
1368
1369
1370
1371
1372
1374
1375
1377
1378
1380 log_debug(
"Initializing connection rate limiter...");
1386 }
1387 } else {
1388 log_info(
"Connection rate limiter initialized (50 connections/min per IP)");
1389 }
1390 }
1391
1392
1394 log_debug(
"Initializing audio mixer for per-client audio rendering...");
1400 }
1401 } else {
1403 log_debug(
"Audio mixer initialized successfully for per-client audio rendering");
1404 }
1405 }
1406 }
1407
1408
1409
1410
1411
1413 log_debug(
"Initializing mDNS for LAN service discovery...");
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 }
1423 log_info(
"mDNS service advertisement disabled via --no-mdns-advertise");
1424 }
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443 char session_string[64] = {0};
1444
1445
1447
1448
1449
1450
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
1458 bool acds_expose_ip_flag = false;
1459
1460 if (has_password || has_identity) {
1461
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
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.");
1472
1475 log_plain_stderr(
"❌ IP disclosure not confirmed. Server will run WITHOUT discovery service.");
1476 goto skip_acds_session;
1477 }
1478
1479
1480 acds_expose_ip_flag = true;
1483 log_plain_stderr(
"⚠️ Your IP address will be visible to anyone with the session string");
1484 } else {
1485
1490 log_plain_stderr(
" 3. Explicitly allow public IP: --acds-expose-ip (NOT RECOMMENDED)");
1493 goto skip_acds_session;
1494 }
1495
1496
1497 const char *acds_server =
GET_OPTION(acds_server);
1499
1500 log_info(
"Attempting to create session on ACDS server at %s:%d...", acds_server, acds_port);
1501
1507
1508
1510 if (!g_acds_client) {
1511 log_error(
"Failed to allocate ACDS client");
1512 goto skip_acds_session;
1513 }
1514
1517
1519 memset(&create_params, 0, sizeof(create_params));
1520
1521
1524 log_debug(
"Using server identity key for ACDS session");
1525 } else {
1526
1527
1529 log_debug(
"No server identity key - using zero key for ACDS session");
1530 }
1531
1534
1535
1537 if (has_password) {
1538
1540 }
1541
1542
1544
1545
1546
1547
1549 bool bind_all_interfaces = (strcmp(bind_addr, "0.0.0.0") == 0);
1550
1552
1554 log_info(
"ACDS session type: WebRTC (explicitly requested via --webrtc)");
1555 } else if (bind_all_interfaces) {
1556
1558 log_info(
"ACDS session type: Direct TCP (bind address 0.0.0.0, server is publicly accessible)");
1559 } else if (upnp_succeeded) {
1560
1562 log_info(
"ACDS session type: Direct TCP (UPnP succeeded, server is publicly accessible)");
1563 } else {
1564
1566 log_info(
"ACDS session type: WebRTC (UPnP failed, server behind NAT)");
1567 }
1568
1569
1570
1571 if (bind_all_interfaces) {
1573 log_debug(
"Bind address is 0.0.0.0, ACDS will auto-detect public IP from connection");
1574 } else {
1576 }
1578
1579
1582
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
1591 log_debug(
"Server joining session as first participant for WebRTC signaling...");
1594
1595
1597
1598
1599 if (has_password) {
1602 }
1603
1607 log_error(
"Failed to join own session: %s (error: %s)", asciichat_error_string(join_err),
1609
1610 } else {
1611 log_info(
"Server joined session successfully (participant_id: %02x%02x...)", join_result.
participant_id[0],
1613
1615 }
1616
1617
1618 log_debug(
"Server staying connected to ACDS for signaling relay");
1619
1620
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
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
1639 log_info(
"Initializing WebRTC peer manager for session (role=CREATOR)...");
1640
1641
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
1651 .stun_servers = stun_servers,
1652 .stun_count = 2,
1653 .turn_servers = NULL,
1654 .turn_count = 0,
1655 .on_transport_ready = on_webrtc_transport_ready,
1656 .user_data = &server_ctx,
1657 .crypto_ctx = NULL
1658 };
1659
1660
1662 .
send_sdp = server_send_sdp, .send_ice = server_send_ice, .user_data = NULL};
1663
1664
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
1675 if (thread_result != 0) {
1676 log_error(
"Failed to create ACDS receive thread: %d", thread_result);
1677
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
1690
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
1695 if (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
1710
1711 if (session_string[0] == '\0' && g_mdns_ctx) {
1712 log_debug(
"No ACDS session string available, generating random session for mDNS");
1713
1714
1715
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
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
1739 advertise_mdns_with_session(session_string, (
uint16_t)port);
1740 }
1741
1742 log_info(
"Server entering accept loop (port %d)...", port);
1743
1744
1745
1746
1747
1748
1749
1752 log_error(
"TCP server exited with error");
1753 }
1754
1755 log_info(
"Server accept loop exited");
1756
1757
1758 log_info(
"Server shutting down...");
1760
1761
1762
1763
1765
1766
1767
1768
1769
1770
1771
1772 log_info(
"Closing all client sockets to unblock receive threads...");
1773
1774
1781 }
1782 }
1784
1785 log_info(
"Signaling all clients to stop (sockets closed, g_server_should_exit set)...");
1786
1787
1788 if (g_server_worker_pool) {
1790 g_server_worker_pool = NULL;
1791 log_info(
"Server worker thread pool stopped");
1792 }
1793
1794
1798 }
1799
1800
1801 log_info(
"Cleaning up connected clients...");
1802
1804 int client_count = 0;
1805
1809
1810
1811
1812
1813 if (atomic_load(&client->
client_id) == 0) {
1814 continue;
1815 }
1816
1817
1818
1820
1821
1822
1823 clients_to_remove[client_count++] = client_id_snapshot;
1824 }
1826
1827
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
1835
1836
1841
1842 }
1844 }
1845
1846
1850 }
1851
1852
1853 if (g_mdns_ctx) {
1855 g_mdns_ctx = NULL;
1856 log_info(
"mDNS context shut down");
1857 }
1858
1859
1862
1863#ifdef NDEBUG
1864
1866#endif
1867
1868#ifndef NDEBUG
1869
1870
1871 lock_debug_cleanup();
1872#endif
1873
1874
1876
1877
1878
1879 if (g_acds_ping_thread_started) {
1882 g_acds_ping_thread_started = false;
1884 }
1885
1886 if (g_acds_receive_thread_started) {
1887 log_debug(
"Joining ACDS receive thread");
1889 g_acds_receive_thread_started = false;
1890 log_debug(
"ACDS receive thread joined");
1891 }
1892
1893
1894 if (g_webrtc_peer_manager) {
1895 log_debug(
"Destroying WebRTC peer manager");
1897 g_webrtc_peer_manager = NULL;
1898 }
1899
1900
1901 if (g_acds_transport) {
1902 log_debug(
"Destroying ACDS transport wrapper");
1904 g_acds_transport = NULL;
1905 }
1906
1907
1908 if (g_acds_client) {
1909 log_debug(
"Disconnecting from ACDS server");
1912 g_acds_client = NULL;
1913 }
1914
1915
1917
1918
1919
1920
1921
1923
1924
1925
1926
1928
1929
1930
1931
1932
1934
1935
1937
1938
1940
1941
1942
1943#ifdef _WIN32
1945 timeEndPeriod(1);
1946#endif
1947
1948#ifndef NDEBUG
1949
1950 lock_debug_cleanup_thread();
1951#endif
1952
1953 log_info(
"Server shutdown complete");
1954
1956
1958
1959
1960
1961 exit(0);
1962}
void acds_client_config_init_defaults(acds_client_config_t *config)
Initialize ACDS client configuration with defaults.
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.
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.
void mixer_destroy(mixer_t *mixer)
Destroy a mixer and free all resources.
void buffer_pool_cleanup_global(void)
#define SAFE_STRNCPY(dst, src, size)
#define SAFE_MALLOC(size, cast)
#define FATAL(code,...)
Exit with error code and custom message, with stack trace in debug builds.
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)
#define MAX_CLIENTS
Maximum possible clients (static array size) - actual runtime limit set by –max-clients (1-32)
#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)
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
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_green
Green weight for luminance calculation.
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.
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.
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.
asciichat_mdns_t * asciichat_mdns_init(void)
Initialize mDNS context.
void rate_limiter_destroy(rate_limiter_t *limiter)
Destroy rate limiter and free resources.
rate_limiter_t * rate_limiter_create_memory(void)
Create in-memory rate limiter.
void options_state_shutdown(void)
Shutdown RCU options system.
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.
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.
ACDS client connection configuration.
char server_address[256]
ACDS server address (e.g., "discovery.ascii.chat" or "127.0.0.1")
uint32_t timeout_ms
Connection timeout in milliseconds.
uint16_t server_port
ACDS server port (default: 27225)
ACDS client connection handle.
socket_t socket
TCP socket to ACDS server.
Session creation request parameters.
uint16_t server_port
Server port where clients should connect.
bool acds_expose_ip
Explicitly allow public IP disclosure (–acds-expose-ip opt-in)
uint8_t max_participants
Maximum participants (1-8)
uint8_t identity_pubkey[32]
Ed25519 public key (host identity)
char server_address[64]
Server address where clients should connect.
bool has_password
Password protection enabled.
uint8_t capabilities
Bit 0: video, Bit 1: audio.
char password[128]
Optional password (if has_password)
uint8_t session_type
acds_session_type_t: 0=DIRECT_TCP (default), 1=WEBRTC
uint8_t session_id[16]
Session UUID.
char session_string[49]
Generated session string (null-terminated)
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.
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.
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.
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.
thread_pool_t * thread_pool_create(const char *pool_name)
Create a new thread pool.
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.
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.
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.