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.h File Reference

Go to the source code of this file.

Data Structures

struct  session_server_like_config
 The useful part of session server like config is in [struct].mdns, [struct].upnp, init_fn, init_user_data: together they describe what is pending, who owns it, and how much data is valid. Keep the object with its parent operation and use the parent cleanup path. More...

Typedefs

typedef asciichat_error_t(* session_server_like_init_fn) (void *user_data)
typedef void(* session_server_like_interrupt_fn) (int sig)
typedef void(* session_server_like_cleanup_fn) (void *user_data)
typedef void(* session_server_like_status_fn) (void *user_data, ui_status_t *out_status)
typedef struct session_server_like_config session_server_like_config_t
 The useful part of session server like config is in [struct].mdns, [struct].upnp, init_fn, init_user_data: together they describe what is pending, who owns it, and how much data is valid. Keep the object with its parent operation and use the parent cleanup path.

Functions

asciichat_error_t session_server_like_run (const session_server_like_config_t *config)
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)
bool session_server_like_shutdown_requested (void)
void session_server_like_request_shutdown (void)

Typedef Documentation

◆ session_server_like_cleanup_fn

typedef void(* session_server_like_cleanup_fn) (void *user_data)

Definition at line 23 of file server_like.h.

◆ session_server_like_config_t

The useful part of session server like config is in [struct].mdns, [struct].upnp, init_fn, init_user_data: together they describe what is pending, who owns it, and how much data is valid. Keep the object with its parent operation and use the parent cleanup path.

◆ session_server_like_init_fn

typedef asciichat_error_t(* session_server_like_init_fn) (void *user_data)

Shared server-mode orchestration. Networking is owned by network_server_t; modes supply one callback set for TCP, WebSocket, and authenticated peers. Listeners are constructed before init_fn and started only after it succeeds. All connections drain before cleanup_fn releases mode dependencies. The interrupt callback only publishes atomic shutdown requests.

Definition at line 19 of file server_like.h.

◆ session_server_like_interrupt_fn

typedef void(* session_server_like_interrupt_fn) (int sig)

Definition at line 21 of file server_like.h.

◆ session_server_like_status_fn

typedef void(* session_server_like_status_fn) (void *user_data, ui_status_t *out_status)

Definition at line 25 of file server_like.h.

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}