ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
server_like.c File Reference

Unified lifecycle for server-like modes (server, discovery-service). More...

Go to the source code of this file.

Macros

#define SESSION_SERVER_LIKE_PUBLIC_ADDR_BUFFER_SIZE   22

Functions

network_server_t * session_server_like_get_network_server (void)
asciichat_mdns_t * session_server_like_get_mdns_ctx (void)
nat_upnp_context_t * session_server_like_get_upnp_ctx (void)
asciichat_error_t session_server_like_mdns_advertise (const char *name, const char *type, uint16_t port, const char **txt, size_t txt_count)
void session_server_like_request_shutdown (void)
bool session_server_like_shutdown_requested (void)
asciichat_error_t session_server_like_run (const session_server_like_config_t *config)

Detailed Description

Unified lifecycle for server-like modes (server, discovery-service).

Owns TCP server, WebSocket server, mDNS, UPnP, status screen, keyboard, and signal handling. Modes plug in via callbacks.

Definition in file server_like.c.

Macro Definition Documentation

◆ SESSION_SERVER_LIKE_PUBLIC_ADDR_BUFFER_SIZE

#define SESSION_SERVER_LIKE_PUBLIC_ADDR_BUFFER_SIZE   22

Definition at line 3 of file server_like.c.

Referenced by session_server_like_run().

Function Documentation

◆ session_server_like_get_mdns_ctx()

asciichat_mdns_t * session_server_like_get_mdns_ctx ( void )

Definition at line 172 of file server_like.c.

172 {
173 return g_mdns_ctx;
174}

◆ session_server_like_get_network_server()

network_server_t * session_server_like_get_network_server ( void )

Definition at line 168 of file server_like.c.

168 {
169 return g_network_server;
170}

Referenced by acds_server_init(), and broadcast_server_state_to_all_clients().

◆ session_server_like_get_upnp_ctx()

nat_upnp_context_t * session_server_like_get_upnp_ctx ( void )

Definition at line 176 of file server_like.c.

176 {
177 return g_upnp_ctx;
178}

◆ session_server_like_mdns_advertise()

asciichat_error_t session_server_like_mdns_advertise ( const char * name,
const char * type,
uint16_t port,
const char ** txt,
size_t txt_count )

Definition at line 184 of file server_like.c.

185 {
186 if (!g_mdns_ctx) {
187 return SET_ERRNO(ERROR_INVALID_STATE, "mDNS context not initialized");
188 }
189
190 char hostname[HOSTNAME_BUFFER_SIZE] = {0};
191 gethostname(hostname, sizeof(hostname) - 1);
192
193 asciichat_mdns_service_t service = {
194 .name = name,
195 .type = type,
196 .host = hostname,
197 .port = port,
198 .txt_records = txt,
199 .txt_count = txt_count,
200 };
201
202 asciichat_error_t result = asciichat_mdns_advertise(g_mdns_ctx, &service);
203 if (result != ASCIICHAT_OK) {
204 LOG_ERRNO_IF_SET("Failed to advertise mDNS service");
205 log_warn("mDNS advertising failed - LAN discovery disabled");
206 } else {
207 log_info("mDNS: Service advertised as '%s' type '%s' (port=%d)", name, type, port);
208 }
209
210 return result;
211}
#define SET_ERRNO(code,...)
#define LOG_ERRNO_IF_SET(message)
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ERROR_INVALID_STATE
@ ASCIICHAT_OK
Definition error_codes.h:46
#define HOSTNAME_BUFFER_SIZE
#define log_warn(...)
Log a WARN message.
Definition log/log.h:621
#define log_info(...)
Log an INFO message.
Definition log/log.h:608
asciichat_error_t asciichat_mdns_advertise(asciichat_mdns_t *mdns, const asciichat_mdns_service_t *service)
Advertise a service on the local network.
Service information for advertisement.

References asciichat_mdns_advertise(), ASCIICHAT_OK, ERROR_INVALID_STATE, HOSTNAME_BUFFER_SIZE, LOG_ERRNO_IF_SET, log_info, log_warn, and SET_ERRNO.

Referenced by session_server_like_run().

◆ session_server_like_request_shutdown()

void session_server_like_request_shutdown ( void )

Definition at line 217 of file server_like.c.

217 {
218 server_like_signal_handler(SIGINT);
219}

Referenced by acds_main().

◆ session_server_like_run()

asciichat_error_t session_server_like_run ( const session_server_like_config_t * config)

Definition at line 229 of file server_like.c.

229 {
230 if (!config) {
231 return SET_ERRNO(ERROR_INVALID_PARAM, "config is NULL");
232 }
233 if (!config->init_fn) {
234 return SET_ERRNO(ERROR_INVALID_PARAM, "init_fn is NULL");
235 }
236 if (!config->interrupt_fn) {
237 return SET_ERRNO(ERROR_INVALID_PARAM, "interrupt_fn is NULL");
238 }
239 if (!config->connections.packet) {
240 return SET_ERRNO(ERROR_INVALID_PARAM, "connection packet callback is NULL");
241 }
242
244
245 /* === 1. Keepawake === */
246
247 bool keepawake_enabled = GET_OPTION(enable_keepawake);
248 bool keepawake_disabled = GET_OPTION(disable_keepawake);
249 if (keepawake_enabled && keepawake_disabled) {
250 return SET_ERRNO(ERROR_INVALID_PARAM, "Cannot specify both --keepawake and --no-keepawake");
251 }
252 if (keepawake_enabled) {
254 bool suppressed = errno_notifications_suppress(true);
255 asciichat_error_t keepawake_result = platform_enable_keepawake();
257 if (keepawake_result != ASCIICHAT_OK) {
258 log_warn("Unable to inhibit system sleep: %s", asciichat_error_string(keepawake_result));
259 }
261 }
262
263 /* === 2. File descriptor limit === */
264
265 if (config->raise_fd_limit) {
267 }
268
269 /* === 3. Signal handlers === */
270
271 atomic_store_u64(&g_shutdown_requested, 0);
272 g_interrupt_fn = config->interrupt_fn;
273 platform_signal(SIGINT, server_like_signal_handler);
274 platform_signal(SIGTERM, server_like_signal_handler);
275#ifndef _WIN32
276 platform_signal(SIGPIPE, SIG_IGN);
277#endif
278
279 /* === 4. TCP server init === */
280
281 int port = GET_OPTION(port);
282 const char *address = GET_OPTION(address);
283 const char *address6 = GET_OPTION(address6);
284
285 const char *effective_address = (address && strlen(address) > 0) ? address : "127.0.0.1";
286 const char *effective_address6 = (address6 && strlen(address6) > 0) ? address6 : "::1";
287 bool ipv4_has_value = true;
288
289 result = session_quic_listeners_open(port, &g_quic_endpoint, &g_webtransport_endpoint);
290 if (result != ASCIICHAT_OK)
291 goto cleanup;
292
293 network_server_config_t tcp_config = {
294 .port = port,
295 .ipv4_address = effective_address,
296 .ipv6_address = effective_address6,
297 .bind_ipv4 = true,
298 .bind_ipv6 = true,
299 .callbacks = config->connections,
300 .max_payload = config->max_payload,
301 .partial_timeout_ms = config->partial_timeout_ms,
302 .idle_timeout_ms = config->idle_timeout_ms,
303 .websocket_enabled = config->websocket_enabled,
304 .websocket_port = GET_OPTION(websocket_port),
305 .tls_cert_path = GET_OPTION(tls_cert),
306 .tls_key_path = GET_OPTION(tls_key),
307 .tick = config->tick_fn,
308 .tick_context = config->tick_user_data,
309 .user_data = config->connection_user_data,
310 .quic_endpoint = g_quic_endpoint,
311 .webtransport_endpoint = g_webtransport_endpoint,
312 };
313
314 result = network_server_create(&tcp_config, &g_network_server);
315 if (result != ASCIICHAT_OK) {
316 log_error("Failed to initialize TCP server on port %d", port);
317 goto cleanup;
318 }
319 log_info("TCP server initialized on port %d", port);
320
321 /* === 5. UPnP port mapping === */
322
323 bool upnp_requested = config->upnp.enabled && GET_OPTION(enable_upnp);
324 bool ipv4_loopback = ipv4_has_value && is_localhost_ipv4(address);
325 bool ipv4_listener = tcp_config.bind_ipv4 && !ipv4_loopback;
326 if (upnp_requested && ipv4_listener) {
327 log_info("UPnP status: discovering (TCP port %d)", port);
329 asciichat_error_t upnp_result = nat_upnp_open(port, config->upnp.description, &g_upnp_ctx);
330 if (upnp_result == ASCIICHAT_OK && !nat_upnp_matches_bind_address(g_upnp_ctx, tcp_config.ipv4_address)) {
331 log_warn("NAT: gateway mapping does not target the bound IPv4 listener; removing it");
332 nat_upnp_close(&g_upnp_ctx);
333 upnp_result = ERROR_NETWORK;
334 }
335 if (upnp_result == ASCIICHAT_OK && nat_upnp_is_active(g_upnp_ctx)) {
337 if (nat_upnp_get_address(g_upnp_ctx, public_addr, sizeof(public_addr)) == ASCIICHAT_OK) {
338 log_info("UPnP status: mapped at %s (external reachability unverified)", public_addr);
339 }
340 } else {
341 log_warn("UPnP status: unavailable. Listener remains enabled; for Internet access, "
342 "check router support or manually forward TCP port %d. External reachability is unverified.",
343 port);
344 }
346 } else if (upnp_requested) {
347 log_info("UPnP status: disabled (requires a non-loopback IPv4 listener)");
348 } else {
349 log_info("UPnP status: disabled");
350 if (ipv4_listener && (!ipv4_has_value || strcmp(address, "0.0.0.0") == 0 || is_lan_ipv4(address))) {
351 log_info("For connections from outside your LAN, try --port-forwarding or forward TCP port %d on your router.",
352 port);
353 }
354 }
355
356 /* === 6. mDNS === */
357
358 if (config->mdns.enabled) {
359 g_mdns_ctx = asciichat_mdns_init();
360 if (!g_mdns_ctx) {
361 LOG_ERRNO_IF_SET("Failed to initialize mDNS (non-fatal, LAN discovery disabled)");
362 log_warn("mDNS disabled - LAN discovery will not be available");
363 } else if (config->mdns.service_name) {
364 // Immediate advertisement if service_name is provided
366 }
367 }
368
369 /* === 8. Mode-specific init === */
370
371 result = config->init_fn(config->init_user_data);
372 if (result != ASCIICHAT_OK) {
373 log_error("Mode initialization failed");
374 goto cleanup;
375 }
376
377 /* === 8b. WebSocket event loop thread start (after mode init succeeds) === */
378
379 if (!atomic_load_u64(&g_shutdown_requested)) {
380 result = network_server_start(g_network_server);
381 if (result != ASCIICHAT_OK)
382 goto cleanup;
383 platform_systemd_notify_status("Listener started");
384 platform_systemd_notify_ready();
385 } else {
386 goto cleanup;
387 }
388
389 if (upnp_requested && network_server_websocket_enabled(g_network_server)) {
390 // WebSocket binds independently of the raw TCP listener's address.
391 asciichat_errno_scope_t ws_mapping_scope = errno_scope_begin();
392 if (nat_upnp_open((uint16_t)GET_OPTION(websocket_port), "ascii-chat WebSocket", &g_ws_upnp_ctx) != ASCIICHAT_OK) {
393 log_warn("WebSocket mapping unavailable; manually forward TCP port %d if needed", GET_OPTION(websocket_port));
394 }
395 errno_scope_end(ws_mapping_scope, ASCIICHAT_ERRNO_HANDLED);
396 }
397
398 if (g_upnp_ctx || g_ws_upnp_ctx) {
399 atomic_store_bool(&g_upnp_stop, false);
400 atomic_store_bool(&g_upnp_ready, false);
401 if (platform_thread_create(&g_upnp_thread, "upnp_renewal", upnp_renewal_thread, NULL) == 0) {
402 g_upnp_thread_started = true;
403 } else {
404 log_warn("NAT: could not start lease renewal; removing the temporary mapping");
405 nat_upnp_close(&g_upnp_ctx);
406 nat_upnp_close(&g_ws_upnp_ctx);
407 }
408 }
409
410 // Keep bounded log capture available even when only the stats overlay is used.
412 /* === 9. Status screen === */
413
414 if (config->status_fn) {
415 bool status_opt = GET_OPTION(status_screen);
416 bool status_explicit = GET_OPTION(status_screen_explicitly_set);
417 if ((status_opt && terminal_is_interactive()) || (status_explicit && status_opt)) {
418 atomic_store_bool(&g_status_should_exit, false);
419
420 // Store callback and user data for the lifetime of the status thread.
421 g_status_fn = config->status_fn;
422 g_status_user_data = config->status_user_data;
423
424 if (platform_thread_create(&g_status_screen_thread, "status_screen", status_screen_thread_func, NULL) == 0) {
425 g_status_screen_thread_started = true;
426 log_info("Status screen thread started");
427 } else {
428 log_error("Failed to start status screen thread");
430 g_status_fn = NULL;
431 g_status_user_data = NULL;
432 }
433 }
434 }
435
436 atomic_store_bool(&g_upnp_ready, true);
437
438 /* === 10. TCP accept loop (blocks) === */
439
440 log_info("Server accepting connections on port %d", port);
442 result = network_server_run(g_network_server);
443 if (result != ASCIICHAT_OK) {
444 log_error("TCP server exited with error");
445 }
446
447 /* === CLEANUP === */
448
449cleanup:
450 if (g_upnp_thread_started) {
451 atomic_store_bool(&g_upnp_stop, true);
452 thread_join(&g_upnp_thread, NULL);
453 g_upnp_thread_started = false;
454 }
455
456 // Remove the mapping before mode teardown, which can wait on other services.
457 if (g_upnp_ctx) {
458 nat_upnp_close(&g_upnp_ctx);
459 log_debug("UPnP port mapping closed");
460 }
461
462 nat_upnp_close(&g_ws_upnp_ctx);
463
464 /* 11. Stop status screen */
465 if (g_status_screen_thread_started) {
466 atomic_store_bool(&g_status_should_exit, true);
467 int join_result = thread_join_timeout(&g_status_screen_thread, NULL, 2000 * NS_PER_MS_INT);
468 if (join_result != 0) {
469 log_warn("Status screen thread did not exit within 2s");
470 }
471 g_status_screen_thread_started = false;
472 g_status_fn = NULL;
473 g_status_user_data = NULL;
474 }
476
477 asciichat_error_t stopped = network_server_stop(g_network_server);
478 if (stopped != ASCIICHAT_OK)
479 return stopped;
480
481 /* 13. Mode-specific cleanup (only call if config provided) */
482 if (config && config->cleanup_fn) {
483 config->cleanup_fn(config->cleanup_user_data);
484 }
485
486 network_server_destroy(&g_network_server);
487 stopped = session_quic_listeners_close(&g_quic_endpoint, &g_webtransport_endpoint);
488 if (stopped != ASCIICHAT_OK)
489 return stopped;
490
491 /* 17. mDNS cleanup */
492 if (g_mdns_ctx) {
493 asciichat_mdns_destroy(g_mdns_ctx);
494 g_mdns_ctx = NULL;
495 log_debug("mDNS context destroyed");
496 }
497
498 /* 18. Keepawake */
499 if (keepawake_enabled) {
501 }
502
503 return result;
504}
void errno_scope_end(asciichat_errno_scope_t scope, asciichat_errno_outcome_t outcome)
bool errno_notifications_suppress(bool suppress)
asciichat_errno_scope_t errno_scope_begin(void)
@ ASCIICHAT_ERRNO_HANDLED
unsigned short uint16_t
Definition common.h:61
@ ERROR_NETWORK
Definition error_codes.h:72
@ ERROR_INVALID_PARAM
#define log_error(...)
Log an ERROR message.
Definition log/log.h:634
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
#define NS_PER_MS_INT
Definition time.h:168
#define GET_OPTION(field)
Safely get a specific option field (lock-free read).
Definition options.h:1336
signal_handler_t platform_signal(int sig, signal_handler_t handler)
Set a signal handler.
void platform_disable_keepawake(void)
Disable system sleep prevention (allow OS to sleep).
void platform_raise_fd_limit(unsigned int limit)
Raise the file descriptor limit for the current process.
asciichat_error_t platform_enable_keepawake(void)
Enable system sleep prevention (keepawake mode).
int is_lan_ipv4(const char *ip)
Check if IPv4 address is a private/LAN address.
Definition ip.c:1104
int is_localhost_ipv4(const char *ip)
Check if IPv4 address is localhost.
Definition ip.c:1218
asciichat_error_t network_server_create(const network_server_config_t *config, network_server_t **out)
asciichat_error_t network_server_start(network_server_t *s)
void network_server_destroy(network_server_t **owner)
asciichat_error_t network_server_stop(network_server_t *s)
asciichat_error_t network_server_run(network_server_t *s)
bool network_server_websocket_enabled(network_server_t *s)
void asciichat_mdns_destroy(asciichat_mdns_t *mdns)
Shutdown mDNS context and cleanup.
asciichat_mdns_t * asciichat_mdns_init(void)
Initialize mDNS context.
asciichat_error_t session_quic_listeners_close(quic_endpoint_t **native, quic_endpoint_t **webtransport)
asciichat_error_t session_quic_listeners_open(int port, quic_endpoint_t **native, quic_endpoint_t **webtransport)
#define SESSION_SERVER_LIKE_PUBLIC_ADDR_BUFFER_SIZE
Definition server_like.c:3
asciichat_error_t session_server_like_mdns_advertise(const char *name, const char *type, uint16_t port, const char **txt, size_t txt_count)
void ui_status_log_destroy(void)
Cleanup status screen log capture system.
Definition status.c:55
void ui_status_log_init(void)
Initialize status screen log capture system.
Definition status.c:47
When code needs to work with asciichat errno snapshot, it keeps total, copied here instead of scatter...
asciichat_error_t(* packet)(network_connection_t *, packet_type_t, const void *, size_t, void *)
void(* tick_fn)(void *user_data)
Definition server_like.h:39
struct session_server_like_config::@331157361304265235031377013321160306024244316160 mdns
session_server_like_interrupt_fn interrupt_fn
Definition server_like.h:37
struct session_server_like_config::@022167030105260237161361274212172160377140006150 upnp
network_connection_callbacks_t connections
Definition server_like.h:48
session_server_like_init_fn init_fn
Definition server_like.h:34
session_server_like_cleanup_fn cleanup_fn
Definition server_like.h:42
session_server_like_status_fn status_fn
Definition server_like.h:45
void ui_controller_title_ready(void)
bool nat_upnp_matches_bind_address(const nat_upnp_context_t *ctx, const char *bind_address)
Whether the mapping targets a specific IPv4 bind address, or any wildcard listener.
Definition upnp.c:296
bool nat_upnp_is_active(const nat_upnp_context_t *ctx)
Check if port mapping is still active.
Definition upnp.c:292
void nat_upnp_close(nat_upnp_context_t **ctx)
Close port mapping and clean up.
Definition upnp.c:251
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:203
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:343

References ASCIICHAT_ERRNO_HANDLED, asciichat_mdns_destroy(), asciichat_mdns_init(), ASCIICHAT_OK, network_server_config_t::bind_ipv4, session_server_like_config::cleanup_fn, session_server_like_config::cleanup_user_data, session_server_like_config::connection_user_data, session_server_like_config::connections, session_server_like_config::description, session_server_like_config::enabled, errno_notifications_suppress(), errno_scope_begin(), errno_scope_end(), ERROR_INVALID_PARAM, ERROR_NETWORK, session_server_like_config::fd_limit_target, GET_OPTION, session_server_like_config::idle_timeout_ms, session_server_like_config::init_fn, session_server_like_config::init_user_data, session_server_like_config::interrupt_fn, network_server_config_t::ipv4_address, is_lan_ipv4(), is_localhost_ipv4(), log_debug, LOG_ERRNO_IF_SET, log_error, log_info, log_warn, session_server_like_config::max_payload, session_server_like_config::mdns, nat_upnp_close(), nat_upnp_get_address(), nat_upnp_is_active(), nat_upnp_matches_bind_address(), nat_upnp_open(), network_server_create(), network_server_destroy(), network_server_run(), network_server_start(), network_server_stop(), network_server_websocket_enabled(), NS_PER_MS_INT, network_connection_callbacks_t::packet, session_server_like_config::partial_timeout_ms, platform_disable_keepawake(), platform_enable_keepawake(), platform_raise_fd_limit(), platform_signal(), session_server_like_config::raise_fd_limit, session_server_like_config::service_name, session_server_like_config::service_type, session_quic_listeners_close(), session_quic_listeners_open(), session_server_like_mdns_advertise(), SESSION_SERVER_LIKE_PUBLIC_ADDR_BUFFER_SIZE, SET_ERRNO, session_server_like_config::status_fn, session_server_like_config::status_user_data, session_server_like_config::tick_fn, session_server_like_config::tick_user_data, ui_controller_title_ready(), ui_status_log_destroy(), ui_status_log_init(), session_server_like_config::upnp, and session_server_like_config::websocket_enabled.

Referenced by acds_main(), and server_main().

◆ session_server_like_shutdown_requested()

bool session_server_like_shutdown_requested ( void )

Definition at line 221 of file server_like.c.

221 {
222 return (bool)atomic_load_u64(&g_shutdown_requested);
223}