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

🔍 ASCII-Chat Discovery Service (acds) main entry point More...

Go to the source code of this file.

Functions

int main (int argc, char **argv)
 

Detailed Description

🔍 ASCII-Chat Discovery Service (acds) main entry point

Discovery server for session management and WebRTC signaling using ACIP binary protocol over raw TCP.

Definition in file acds/main.c.

Function Documentation

◆ main()

int main ( int  argc,
char **  argv 
)

Definition at line 55 of file acds/main.c.

55 {
56 asciichat_error_t result;
57
58 // Parse command-line arguments using options module
59 // Note: ACDS is a separate binary, so argv[0] is the program name
60 // We need to insert "acds" mode argument for options parsing
61 char **acds_argv = SAFE_MALLOC((size_t)(argc + 2) * sizeof(char *), char **);
62 if (!acds_argv) {
63 return ERROR_MEMORY;
64 }
65 acds_argv[0] = argv[0];
66 acds_argv[1] = "acds";
67 for (int i = 1; i < argc; i++) {
68 acds_argv[i + 1] = argv[i];
69 }
70 acds_argv[argc + 1] = NULL;
71
72 result = options_init(argc + 1, acds_argv);
73
74 SAFE_FREE(acds_argv);
75 if (result != ASCIICHAT_OK) {
76 return result;
77 }
78
79 // Handle --help and --version early (before shared init)
80 const options_t *opts = options_get();
81 if (opts && opts->help) {
82 usage(stdout, MODE_ACDS);
83 return 0;
84 }
85
86 if (opts && opts->version) {
87 printf("ascii-chat-acds version %s (%s, %s)\n", ASCII_CHAT_VERSION_FULL, ASCII_CHAT_BUILD_TYPE,
88 ASCII_CHAT_BUILD_DATE);
89 return 0;
90 }
91
92 // Initialize shared infrastructure (platform, logging, buffer pool, errno, etc.)
93 // This also registers atexit handlers for cleanup (options, buffer pool, known_hosts, errno)
94 result = asciichat_shared_init("acds.log", false /* is_client */);
95 if (result != ASCIICHAT_OK) {
96 return result;
97 }
98
99 // Register main thread with RCU library before any RCU operations
100 // This is required by liburcu to track the thread in the RCU synchronization scheme
101 rcu_register_thread();
102 log_debug("Main thread registered with RCU library");
103
104 log_info("ASCII-Chat Discovery Service (acds) starting...");
105 log_info("Version: %s (%s, %s)", ASCII_CHAT_VERSION_FULL, ASCII_CHAT_BUILD_TYPE, ASCII_CHAT_BUILD_DATE);
106
107 // Initialize session string generator (libsodium)
108 result = acds_string_init();
109 if (result != ASCIICHAT_OK) {
110 log_error("Failed to initialize session string generator");
111 return result;
112 }
113
114 // Load or generate identity keys
115 uint8_t public_key[32];
116 uint8_t secret_key[64];
117
118 log_info("Loading identity key from %s", opt_acds_key_path);
119 result = acds_identity_load(opt_acds_key_path, public_key, secret_key);
120
121 if (result != ASCIICHAT_OK) {
122 log_info("Identity key not found, generating new key...");
123
124 result = acds_identity_generate(public_key, secret_key);
125 if (result != ASCIICHAT_OK) {
126 log_error("Failed to generate identity key");
127 return result;
128 }
129
130 result = acds_identity_save(opt_acds_key_path, public_key, secret_key);
131 if (result != ASCIICHAT_OK) {
132 log_error("Failed to save identity key to %s", opt_acds_key_path);
133 return result;
134 }
135
136 log_info("Saved new identity key to %s", opt_acds_key_path);
137 }
138
139 // Display server fingerprint
140 char fingerprint[65];
141 acds_identity_fingerprint(public_key, fingerprint);
142 log_info("Discovery server identity: SHA256:%s", fingerprint);
143 printf("🔑 Server fingerprint: SHA256:%s\n", fingerprint);
144
145 // Create config from options for server initialization
146 acds_config_t config;
147 config.port = opt_acds_port;
148 const char *address = opts && opts->address[0] != '\0' ? opts->address : "127.0.0.1";
149 const char *address6 = opts && opts->address6[0] != '\0' ? opts->address6 : "::1";
150 const char *log_file = opts && opts->log_file[0] != '\0' ? opts->log_file : "acds.log";
151 SAFE_STRNCPY(config.address, address, sizeof(config.address));
152 SAFE_STRNCPY(config.address6, address6, sizeof(config.address6));
154 SAFE_STRNCPY(config.key_path, opt_acds_key_path, sizeof(config.key_path));
155 SAFE_STRNCPY(config.log_file, log_file, sizeof(config.log_file));
156 config.log_level = GET_OPTION(log_level);
157 config.require_server_identity = GET_OPTION(require_server_identity) != 0;
158 config.require_client_identity = GET_OPTION(require_client_identity) != 0;
159
160 // Log security policy
161 if (config.require_server_identity) {
162 log_info("Security: Requiring signed identity from servers creating sessions");
163 }
164 if (config.require_client_identity) {
165 log_info("Security: Requiring signed identity from clients joining sessions");
166 }
167
168 // Parse STUN servers from comma-separated list
169 config.stun_count = 0;
170 memset(config.stun_servers, 0, sizeof(config.stun_servers));
171 const char *stun_servers_str = GET_OPTION(stun_servers);
172 if (stun_servers_str && stun_servers_str[0] != '\0') {
173 char stun_copy[OPTIONS_BUFF_SIZE];
174 SAFE_STRNCPY(stun_copy, stun_servers_str, sizeof(stun_copy));
175
176 char *saveptr = NULL;
177 char *token = strtok_r(stun_copy, ",", &saveptr);
178 while (token && config.stun_count < 4) {
179 // Trim whitespace
180 while (*token == ' ' || *token == '\t')
181 token++;
182 size_t len = strlen(token);
183 while (len > 0 && (token[len - 1] == ' ' || token[len - 1] == '\t')) {
184 token[--len] = '\0';
185 }
186
187 if (len > 0 && len < sizeof(config.stun_servers[0].host)) {
188 config.stun_servers[config.stun_count].host_len = (uint8_t)len;
189 SAFE_STRNCPY(config.stun_servers[config.stun_count].host, token,
190 sizeof(config.stun_servers[config.stun_count].host));
191 log_info("Added STUN server: %s", token);
192 config.stun_count++;
193 } else if (len > 0) {
194 log_warn("STUN server URL too long (max 63 chars): %s", token);
195 }
196
197 token = strtok_r(NULL, ",", &saveptr);
198 }
199 }
200
201 // Parse TURN servers from comma-separated list
202 config.turn_count = 0;
203 memset(config.turn_servers, 0, sizeof(config.turn_servers));
204 const char *turn_servers_str = GET_OPTION(turn_servers);
205 const char *turn_username_str = GET_OPTION(turn_username);
206 const char *turn_credential_str = GET_OPTION(turn_credential);
207
208 if (turn_servers_str && turn_servers_str[0] != '\0') {
209 char turn_copy[OPTIONS_BUFF_SIZE];
210 SAFE_STRNCPY(turn_copy, turn_servers_str, sizeof(turn_copy));
211
212 char *saveptr = NULL;
213 char *token = strtok_r(turn_copy, ",", &saveptr);
214 while (token && config.turn_count < 4) {
215 // Trim whitespace
216 while (*token == ' ' || *token == '\t')
217 token++;
218 size_t len = strlen(token);
219 while (len > 0 && (token[len - 1] == ' ' || token[len - 1] == '\t')) {
220 token[--len] = '\0';
221 }
222
223 if (len > 0 && len < sizeof(config.turn_servers[0].url)) {
224 config.turn_servers[config.turn_count].url_len = (uint8_t)len;
225 SAFE_STRNCPY(config.turn_servers[config.turn_count].url, token,
226 sizeof(config.turn_servers[config.turn_count].url));
227
228 // Set username if provided
229 if (turn_username_str && turn_username_str[0] != '\0') {
230 size_t username_len = strlen(turn_username_str);
231 if (username_len < sizeof(config.turn_servers[0].username)) {
232 config.turn_servers[config.turn_count].username_len = (uint8_t)username_len;
233 SAFE_STRNCPY(config.turn_servers[config.turn_count].username, turn_username_str,
234 sizeof(config.turn_servers[config.turn_count].username));
235 }
236 }
237
238 // Set credential if provided
239 if (turn_credential_str && turn_credential_str[0] != '\0') {
240 size_t credential_len = strlen(turn_credential_str);
241 if (credential_len < sizeof(config.turn_servers[0].credential)) {
242 config.turn_servers[config.turn_count].credential_len = (uint8_t)credential_len;
243 SAFE_STRNCPY(config.turn_servers[config.turn_count].credential, turn_credential_str,
244 sizeof(config.turn_servers[config.turn_count].credential));
245 }
246 }
247
248 log_info("Added TURN server: %s (username: %s)", token,
249 turn_username_str && turn_username_str[0] ? turn_username_str : "<none>");
250 config.turn_count++;
251 } else if (len > 0) {
252 log_warn("TURN server URL too long (max 63 chars): %s", token);
253 }
254
255 token = strtok_r(NULL, ",", &saveptr);
256 }
257 }
258
259 // Copy TURN secret for dynamic credential generation
260 const char *turn_secret_str = GET_OPTION(turn_secret);
261 if (turn_secret_str && turn_secret_str[0] != '\0') {
262 SAFE_STRNCPY(config.turn_secret, turn_secret_str, sizeof(config.turn_secret));
263 log_info("TURN dynamic credential generation enabled");
264 } else {
265 config.turn_secret[0] = '\0';
266 }
267
268 // Initialize server
269 acds_server_t server;
270 memset(&server, 0, sizeof(server));
271 g_server = &server;
272
273 result = acds_server_init(&server, &config);
274 if (result != ASCIICHAT_OK) {
275 log_error("Server initialization failed");
276 g_server = NULL;
277 return result;
278 }
279
280 // =========================================================================
281 // UPnP Port Mapping (Quick Win for Direct TCP)
282 // =========================================================================
283 // Try to open port via UPnP so direct TCP works for ~70% of home users.
284 // If this fails, clients fall back to WebRTC automatically - not fatal.
285 //
286 // Strategy:
287 // 1. UPnP (works on ~90% of home routers)
288 // 2. NAT-PMP fallback (Apple routers)
289 // 3. If both fail: use ACDS + WebRTC (reliable, but slightly higher latency)
290 if (GET_OPTION(enable_upnp) && !GET_OPTION(no_upnp)) {
291 asciichat_error_t upnp_result = nat_upnp_open(config.port, "ASCII-Chat ACDS", &g_upnp_ctx);
292
293 if (upnp_result == ASCIICHAT_OK && g_upnp_ctx) {
294 char public_addr[22];
295 if (nat_upnp_get_address(g_upnp_ctx, public_addr, sizeof(public_addr)) == ASCIICHAT_OK) {
296 printf("🌐 Public endpoint: %s (direct TCP)\n", public_addr);
297 log_info("UPnP: Port mapping successful, public endpoint: %s", public_addr);
298 }
299 } else {
300 log_info("UPnP: Port mapping unavailable or failed - will use WebRTC fallback");
301 printf("📡 Clients behind strict NATs will use WebRTC fallback\n");
302 }
303 } else {
304 if (GET_OPTION(no_upnp)) {
305 log_info("UPnP: Disabled via --no-upnp option");
306 } else {
307 log_info("UPnP: Disabled via environment variable or configuration");
308 }
309 printf("📡 WebRTC will be used for all clients\n");
310 }
311
312 // Initialize mDNS for LAN discovery of ACDS server
313 // This allows clients on the local network to discover the discovery service itself
314 log_debug("Initializing mDNS for ACDS LAN service discovery...");
315 g_mdns_ctx = asciichat_mdns_init();
316 if (!g_mdns_ctx) {
317 LOG_ERRNO_IF_SET("Failed to initialize mDNS (non-fatal, LAN discovery disabled)");
318 log_warn("mDNS disabled for ACDS - LAN discovery of discovery service will not be available");
319 } else {
320 // Advertise ACDS service on the LAN
321 // Build hostname if available
322 char hostname[256] = {0};
323 gethostname(hostname, sizeof(hostname) - 1);
324
325 asciichat_mdns_service_t service = {
326 .name = "ASCII-Chat-Discovery-Service",
327 .type = "_ascii-chat-discovery-service._tcp",
328 .host = hostname,
329 .port = config.port,
330 .txt_records = NULL,
331 .txt_count = 0,
332 };
333
334 asciichat_error_t mdns_advertise_result = asciichat_mdns_advertise(g_mdns_ctx, &service);
335 if (mdns_advertise_result != ASCIICHAT_OK) {
336 LOG_ERRNO_IF_SET("Failed to advertise ACDS mDNS service");
337 log_warn("mDNS advertising failed for ACDS - LAN discovery disabled");
338 asciichat_mdns_shutdown(g_mdns_ctx);
339 g_mdns_ctx = NULL;
340 } else {
341 printf("🌐 mDNS: ACDS advertised as '_ascii-chat-discovery-service._tcp.local' on LAN\n");
342 log_info("mDNS: ACDS advertised as '_ascii-chat-discovery-service._tcp.local' (port=%d)", config.port);
343 }
344 }
345
346 // Install signal handlers for clean shutdown
347 signal(SIGINT, signal_handler);
348 signal(SIGTERM, signal_handler);
349
350 // Run server
351 log_info("Discovery server listening on port %d", config.port);
352 printf("🌐 Listening on port %d\n", config.port);
353 printf("📊 Database: %s\n", config.database_path);
354 printf("Press Ctrl+C to stop\n\n");
355
356 result = acds_server_run(&server);
357 if (result != ASCIICHAT_OK) {
358 log_error("Server run failed");
359 }
360
361 // Cleanup
362 log_info("Shutting down discovery server...");
363 acds_server_shutdown(&server);
364 g_server = NULL;
365
366 // Clean up UPnP port mapping
367 if (g_upnp_ctx) {
368 nat_upnp_close(&g_upnp_ctx);
369 log_debug("UPnP port mapping closed");
370 }
371
372 // Clean up mDNS context
373 if (g_mdns_ctx) {
374 asciichat_mdns_shutdown(g_mdns_ctx);
375 g_mdns_ctx = NULL;
376 log_debug("mDNS context shut down");
377 }
378
379 // Unregister main thread from RCU library
380 // This ensures all RCU grace periods are properly finalized before exit
381 log_debug("Unregistering main thread from RCU library");
382 rcu_unregister_thread();
383
384 log_info("Discovery server stopped");
385 return result;
386}
#define LOG_ERRNO_IF_SET(message)
Check if any error occurred and log it if so.
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:358
asciichat_error_t asciichat_shared_init(const char *default_log_filename, bool is_client)
Initialize common subsystems shared by client and server.
Definition common.c:64
#define SAFE_FREE(ptr)
Definition common.h:320
#define SAFE_MALLOC(size, cast)
Definition common.h:208
unsigned char uint8_t
Definition common.h:56
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:46
@ ERROR_MEMORY
Definition error_codes.h:53
@ ASCIICHAT_OK
Definition error_codes.h:48
#define log_warn(...)
Log a WARN message.
#define log_error(...)
Log an ERROR message.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
#define log_file(...)
File-only logging - writes to log file only, no stderr output.
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
Definition options.h:644
char opt_acds_key_path[OPTIONS_BUFF_SIZE]
Ed25519 identity key path (ACDS mode only)
Definition acds.c:42
int opt_acds_port
TCP listen port (ACDS mode only)
Definition acds.c:40
const options_t * options_get(void)
Get current options (lock-free read)
Definition rcu.c:222
#define OPTIONS_BUFF_SIZE
Buffer size for option string values.
Definition options.h:176
asciichat_error_t options_init(int argc, char **argv)
Initialize options by parsing command-line arguments.
Definition options.c:144
void usage(FILE *desc, asciichat_mode_t mode)
Print usage information for client, server, or mirror mode.
char opt_acds_database_path[OPTIONS_BUFF_SIZE]
SQLite database path (ACDS mode only)
Definition acds.c:41
@ MODE_ACDS
Discovery service mode - session management and WebRTC signaling.
Definition options.h:430
asciichat_error_t acds_identity_load(const char *path, uint8_t public_key[32], uint8_t secret_key[64])
Load identity from file.
Definition identity.c:35
asciichat_error_t acds_identity_save(const char *path, const uint8_t public_key[32], const uint8_t secret_key[64])
Save identity to file.
Definition identity.c:138
void acds_identity_fingerprint(const uint8_t public_key[32], char fingerprint[65])
Compute SHA256 fingerprint of public key.
Definition identity.c:181
asciichat_error_t acds_identity_generate(uint8_t public_key[32], uint8_t secret_key[64])
Generate new Ed25519 keypair.
Definition identity.c:21
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
asciichat_error_t asciichat_mdns_advertise(asciichat_mdns_t *mdns, const asciichat_mdns_service_t *service)
Advertise a service on the local network.
Definition mdns.c:75
void acds_server_shutdown(acds_server_t *server)
Shutdown discovery server.
asciichat_error_t acds_server_init(acds_server_t *server, const acds_config_t *config)
Initialize discovery server.
asciichat_error_t acds_server_run(acds_server_t *server)
Run discovery server main loop.
asciichat_error_t acds_string_init(void)
Initialize random number generator for string generation.
Definition strings.c:44
Discovery server configuration.
Definition acds/main.h:71
char address[256]
IPv4 bind address (empty = all interfaces)
Definition acds/main.h:73
turn_server_t turn_servers[4]
TURN server configurations.
Definition acds/main.h:88
char database_path[512]
SQLite database path.
Definition acds/main.h:75
bool require_server_identity
Require servers to provide signed identity when creating sessions.
Definition acds/main.h:79
uint8_t turn_count
Number of configured TURN servers (0-4)
Definition acds/main.h:87
int port
TCP listen port (default 27225)
Definition acds/main.h:72
char turn_secret[256]
Shared secret for TURN credential generation (HMAC-SHA1)
Definition acds/main.h:89
uint8_t stun_count
Number of configured STUN servers (0-4)
Definition acds/main.h:85
char key_path[512]
Ed25519 identity key file path.
Definition acds/main.h:76
char log_file[512]
Log file path (empty = stderr)
Definition acds/main.h:77
char address6[256]
IPv6 bind address (empty = all interfaces)
Definition acds/main.h:74
bool require_client_identity
Require clients to provide signed identity when joining sessions.
Definition acds/main.h:80
stun_server_t stun_servers[4]
STUN server configurations.
Definition acds/main.h:86
log_level_t log_level
Logging verbosity level.
Definition acds/main.h:78
Discovery server state.
Service information for advertisement.
Definition mdns.h:64
const char * name
Definition mdns.h:66
Consolidated options structure.
Definition options.h:439
bool version
Show version information.
Definition options.h:449
char address6[256]
IPv6 bind address (server only)
Definition options.h:463
bool help
Show help message.
Definition options.h:448
char log_file[256]
Log file path.
Definition options.h:535
char address[256]
Server address (client) or bind address (server)
Definition options.h:462
void nat_upnp_close(nat_upnp_context_t **ctx)
Close port mapping and clean up.
Definition upnp.c:289
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 acds_identity_fingerprint(), acds_identity_generate(), acds_identity_load(), acds_identity_save(), acds_server_init(), acds_server_run(), acds_server_shutdown(), acds_string_init(), options_state::address, acds_config_t::address, options_state::address6, acds_config_t::address6, asciichat_mdns_advertise(), asciichat_mdns_init(), asciichat_mdns_shutdown(), ASCIICHAT_OK, asciichat_shared_init(), acds_config_t::database_path, ERROR_MEMORY, GET_OPTION, options_state::help, acds_config_t::key_path, log_debug, LOG_ERRNO_IF_SET, log_error, log_file, options_state::log_file, acds_config_t::log_file, log_info, acds_config_t::log_level, log_warn, MODE_ACDS, asciichat_mdns_service_t::name, nat_upnp_close(), nat_upnp_get_address(), nat_upnp_open(), opt_acds_database_path, opt_acds_key_path, opt_acds_port, OPTIONS_BUFF_SIZE, options_get(), options_init(), acds_config_t::port, acds_config_t::require_client_identity, acds_config_t::require_server_identity, SAFE_FREE, SAFE_MALLOC, SAFE_STRNCPY, acds_config_t::stun_count, acds_config_t::stun_servers, acds_config_t::turn_count, acds_config_t::turn_secret, acds_config_t::turn_servers, usage(), and options_state::version.