ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
server_like.c
Go to the documentation of this file.
1
10#include "server_like.h"
12#include <ascii-chat/common.h>
14#include <ascii-chat/log/log.h>
25#include <signal.h>
26#include <string.h>
27
28/* ============================================================================
29 * Static State
30 * ============================================================================ */
31
32static tcp_server_t g_tcp_server;
33static bool g_tcp_server_initialized = false;
34
35static websocket_server_t g_websocket_server;
36static bool g_websocket_initialized = false;
37static asciichat_thread_t g_websocket_thread;
38static bool g_websocket_thread_started = false;
39
40static asciichat_mdns_t *g_mdns_ctx = NULL;
41static nat_upnp_context_t *g_upnp_ctx = NULL;
42
43static asciichat_thread_t g_status_screen_thread;
44static bool g_status_screen_thread_started = false;
45
47static session_server_like_status_fn g_status_fn = NULL;
48static void *g_status_user_data = NULL;
49
51static const session_server_like_config_t *g_config = NULL;
52
53/* ============================================================================
54 * Signal Handler
55 * ============================================================================ */
56
58static atomic_t g_shutdown_requested = {0};
59
60static session_server_like_interrupt_fn g_interrupt_fn = NULL;
61
62static void server_like_signal_handler(int sig) {
63 atomic_store_u64(&g_shutdown_requested, 1);
64 if (g_interrupt_fn) {
65 g_interrupt_fn(sig);
66 }
67}
68
69/* ============================================================================
70 * Keyboard Queue (lock-free SPSC ring buffer)
71 * ============================================================================ */
72
73#define KEYBOARD_QUEUE_SIZE 256
74static keyboard_key_t g_keyboard_queue[KEYBOARD_QUEUE_SIZE];
75static atomic_t g_keyboard_queue_head = {0};
76static atomic_t g_keyboard_queue_tail = {0};
77
78static bool keyboard_queue_push(keyboard_key_t key) {
79 size_t head = atomic_load_u64(&g_keyboard_queue_head);
80 size_t next_head = (head + 1) % KEYBOARD_QUEUE_SIZE;
81 if (next_head == atomic_load_u64(&g_keyboard_queue_tail)) {
82 return false;
83 }
84 g_keyboard_queue[head] = key;
85 atomic_store_u64(&g_keyboard_queue_head, next_head);
86 return true;
87}
88
89static keyboard_key_t keyboard_queue_pop(void) {
90 size_t tail = atomic_load_u64(&g_keyboard_queue_tail);
91 if (tail == atomic_load_u64(&g_keyboard_queue_head)) {
92 return KEY_NONE;
93 }
94 keyboard_key_t key = g_keyboard_queue[tail];
95 atomic_store_u64(&g_keyboard_queue_tail, (tail + 1) % KEYBOARD_QUEUE_SIZE);
96 return key;
97}
98
99/* ============================================================================
100 * Keyboard Thread
101 * ============================================================================ */
102
103static asciichat_thread_t g_keyboard_thread;
104static atomic_t g_keyboard_thread_running = {0};
105
106static void *keyboard_thread_func(void *arg) {
107 (void)arg;
108 log_debug("Keyboard thread started (polling mode, 100ms interval)");
109
110 while (atomic_load_u64(&g_keyboard_thread_running)) {
112 if (key != KEY_NONE) {
113 keyboard_queue_push(key);
114 }
115 }
116
117 log_debug("Keyboard thread exiting");
118 return NULL;
119}
120
121/* ============================================================================
122 * Status Screen Thread
123 * ============================================================================ */
124
126static atomic_t g_status_should_exit = {0};
127
128static void *status_screen_thread_func(void *arg) {
129 (void)arg;
130
131 uint32_t fps = GET_OPTION(fps);
132 if (fps == 0) {
133 fps = 60;
134 }
135 uint64_t frame_interval_us = US_PER_SEC_INT / fps;
136
137 log_debug("Status screen thread started (target %u FPS)", fps);
138
139 // Redirect stderr to /dev/null to prevent async logs from disrupting display
141
142 // Register keyboard atomics with debug registry
143 static bool keyboard_atomics_registered = false;
144 if (!keyboard_atomics_registered) {
145 NAMED_REGISTER_ATOMIC(&g_keyboard_thread_running, "server_like_keyboard_thread_running", NULL);
146 NAMED_REGISTER_ATOMIC(&g_keyboard_queue_head, "server_like_keyboard_queue_head", NULL);
147 NAMED_REGISTER_ATOMIC(&g_keyboard_queue_tail, "server_like_keyboard_queue_tail", NULL);
148 keyboard_atomics_registered = true;
149 }
150
151 // Start keyboard thread if terminal is interactive
152 bool keyboard_enabled = false;
154 log_info("Terminal is interactive, starting keyboard thread...");
155 atomic_store_u64(&g_keyboard_thread_running, true);
156 if (asciichat_thread_create(&g_keyboard_thread, "keyboard", keyboard_thread_func, NULL) == 0) {
157 keyboard_enabled = true;
158 log_info("Keyboard thread started - press '/' to activate grep");
159 } else {
160 log_warn("Failed to create keyboard thread");
161 atomic_store_u64(&g_keyboard_thread_running, false);
163 }
164 } else {
165 log_warn("Terminal is NOT interactive, keyboard disabled");
166 }
167
168 bool skip_next_slash = false;
169 bool grep_was_just_cancelled = false;
170
171 while (!atomic_load_bool(&g_status_should_exit)) {
173
174 // Check if SIGINT handler cancelled grep mode
177 grep_was_just_cancelled = true;
178 }
179
180 // Process keyboard input
181 if (keyboard_enabled) {
182 grep_was_just_cancelled = false;
183
184 keyboard_key_t key = keyboard_queue_pop();
185 while (key != KEY_NONE && !grep_was_just_cancelled) {
186 if (skip_next_slash && key == '/') {
187 skip_next_slash = false;
188 key = keyboard_queue_pop();
189 continue;
190 }
191 skip_next_slash = false;
192
193 if (log_search_should_handle(key)) {
194 bool was_not_in_grep = !log_search_is_entering();
195 bool was_in_grep = log_search_is_entering();
196
198
199 if (was_not_in_grep && log_search_is_entering() && key == '/') {
200 skip_next_slash = true;
201 }
202
203 if (was_in_grep && !log_search_is_entering()) {
204 grep_was_just_cancelled = true;
205 break;
206 }
207 }
208
209 key = keyboard_queue_pop();
210 }
211 }
212
213 // Call mode's status callback to populate status data
214 // Use direct static pointers instead of g_config to avoid stale reference during shutdown
215 if (g_status_fn) {
216 ui_status_t status;
217 memset(&status, 0, sizeof(status));
218 g_status_fn(g_status_user_data, &status);
220 }
221
222 // Maintain frame rate
224 uint64_t frame_time = frame_end - frame_start;
225 if (frame_time < frame_interval_us) {
226 platform_sleep_us(frame_interval_us - frame_time);
227 }
228 }
229
230 // Stop keyboard thread
231 if (keyboard_enabled) {
232 log_debug("Stopping keyboard thread...");
233 atomic_store_u64(&g_keyboard_thread_running, false);
234 asciichat_thread_join(&g_keyboard_thread, NULL);
235 log_debug("Keyboard thread stopped");
237 }
238
239 platform_stderr_restore(stderr_redirect);
240
241 log_debug("Status screen thread exiting");
242 return NULL;
243}
244
245/* ============================================================================
246 * WebSocket Server Thread
247 * ============================================================================ */
248
249static void *websocket_server_thread_wrapper(void *arg) {
250 websocket_server_t *server = (websocket_server_t *)arg;
252 if (result != ASCIICHAT_OK) {
253 log_error("WebSocket server thread exited with error");
254 }
255 return NULL;
256}
257
258/* ============================================================================
259 * Accessors
260 * ============================================================================ */
261
263 return g_tcp_server_initialized ? &g_tcp_server : NULL;
264}
265
267 return g_mdns_ctx;
268}
269
271 return g_upnp_ctx;
272}
273
275 return g_websocket_thread_started ? &g_websocket_server : NULL;
276}
277
278/* ============================================================================
279 * Helpers
280 * ============================================================================ */
281
282asciichat_error_t session_server_like_mdns_advertise(const char *name, const char *type, uint16_t port,
283 const char **txt, size_t txt_count) {
284 if (!g_mdns_ctx) {
285 return SET_ERRNO(ERROR_INVALID_STATE, "mDNS context not initialized");
286 }
287
288 char hostname[256] = {0};
289 gethostname(hostname, sizeof(hostname) - 1);
290
291 asciichat_mdns_service_t service = {
292 .name = name,
293 .type = type,
294 .host = hostname,
295 .port = port,
296 .txt_records = txt,
297 .txt_count = txt_count,
298 };
299
300 asciichat_error_t result = asciichat_mdns_advertise(g_mdns_ctx, &service);
301 if (result != ASCIICHAT_OK) {
302 LOG_ERRNO_IF_SET("Failed to advertise mDNS service");
303 log_warn("mDNS advertising failed - LAN discovery disabled");
304 } else {
305 log_info("mDNS: Service advertised as '%s' type '%s' (port=%d)", name, type, port);
306 }
307
308 return result;
309}
310
311/* ============================================================================
312 * Accessors (shutdown state and network resources)
313 * ============================================================================ */
314
316 return (bool)atomic_load_u64(&g_shutdown_requested);
317}
318
319/* ============================================================================
320 * Shared Handshake
321 * ============================================================================ */
322
323static asciichat_error_t session_server_like_handshake_impl(crypto_handshake_context_t *ctx,
324 acip_transport_t *transport) {
325 if (!ctx || !transport) {
326 return SET_ERRNO(ERROR_INVALID_PARAM, "handshake context or transport is NULL");
327 }
328
329 asciichat_error_t result;
330
331 // Step 0: Receive PROTOCOL_VERSION from client
332 {
333 packet_type_t pkt_type;
334 void *pkt_data = NULL;
335 size_t pkt_len = 0;
336 void *alloc_buffer = NULL;
337
338 result = packet_receive_via_transport(transport, &pkt_type, &pkt_data, &pkt_len, &alloc_buffer);
339 if (result != ASCIICHAT_OK) {
340 log_warn("Failed to receive PROTOCOL_VERSION from client");
341 return result;
342 }
343
344 if (pkt_type != PACKET_TYPE_PROTOCOL_VERSION) {
345 log_warn("Expected PROTOCOL_VERSION, got packet type %u", pkt_type);
346 buffer_pool_free(NULL, alloc_buffer, 0);
347 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Expected PROTOCOL_VERSION packet");
348 }
349
350 buffer_pool_free(NULL, alloc_buffer, 0);
351 log_debug("Received PROTOCOL_VERSION from client");
352 }
353
354 // Step 1: Send crypto parameters
355 result = crypto_handshake_server_send_parameters(ctx, transport);
356 if (result != ASCIICHAT_OK) {
357 log_warn("Crypto parameters send failed");
358 return result;
359 }
360
361 // Step 2: Key exchange (send server key)
362 result = crypto_handshake_server_start(ctx, transport);
363 if (result != ASCIICHAT_OK) {
364 log_warn("Crypto handshake start failed");
365 return result;
366 }
367
368 // Step 3: Receive client key exchange response, send auth challenge
369 {
370 packet_type_t pkt_type;
371 void *pkt_data = NULL;
372 size_t pkt_len = 0;
373 void *alloc_buffer = NULL;
374
375 result = packet_receive_via_transport(transport, &pkt_type, &pkt_data, &pkt_len, &alloc_buffer);
376 if (result != ASCIICHAT_OK) {
377 log_warn("Failed to receive KEY_EXCHANGE_RESPONSE");
378 return result;
379 }
380
381 result = crypto_handshake_server_auth_challenge(ctx, transport, pkt_type, pkt_data, pkt_len);
382 buffer_pool_free(NULL, alloc_buffer, 0);
383
384 if (result != ASCIICHAT_OK) {
385 log_warn("Crypto auth challenge failed");
386 return result;
387 }
388 }
389
390 // Step 4: Complete handshake (skip if already complete from auth challenge)
391 if (ctx->state != CRYPTO_HANDSHAKE_READY) {
392 packet_type_t pkt_type;
393 void *pkt_data = NULL;
394 size_t pkt_len = 0;
395 void *alloc_buffer = NULL;
396
397 result = packet_receive_via_transport(transport, &pkt_type, &pkt_data, &pkt_len, &alloc_buffer);
398 if (result != ASCIICHAT_OK) {
399 log_warn("Failed to receive AUTH_RESPONSE");
400 return result;
401 }
402
403 result = crypto_handshake_server_complete(ctx, transport, pkt_type, pkt_data, pkt_len);
404 buffer_pool_free(NULL, alloc_buffer, 0);
405
406 if (result != ASCIICHAT_OK) {
407 log_warn("Crypto handshake completion failed");
408 return result;
409 }
410 }
411
412 log_info("Crypto handshake complete");
413 return ASCIICHAT_OK;
414}
415
417 if (!ctx || !transport) {
418 return SET_ERRNO(ERROR_INVALID_PARAM, "handshake context or transport is NULL");
419 }
420
421 // Crypto key generation and signing can exceed the normal one-second packet
422 // polling interval on slower or busy peers. Keep the longer deadline scoped
423 // to the handshake so idle-session polling remains responsive.
424 const uint64_t previous_receive_timeout_ns = transport->receive_timeout_ns;
425 transport->receive_timeout_ns = 30ULL * NS_PER_SEC_INT;
426 asciichat_error_t result = session_server_like_handshake_impl(ctx, transport);
427 transport->receive_timeout_ns = previous_receive_timeout_ns;
428 return result;
429}
430
431/* ============================================================================
432 * Main Entry Point
433 * ============================================================================ */
434
436 if (!config) {
437 return SET_ERRNO(ERROR_INVALID_PARAM, "config is NULL");
438 }
439 if (!config->init_fn) {
440 return SET_ERRNO(ERROR_INVALID_PARAM, "init_fn is NULL");
441 }
442 if (!config->interrupt_fn) {
443 return SET_ERRNO(ERROR_INVALID_PARAM, "interrupt_fn is NULL");
444 }
445 if (!config->tcp_handler) {
446 return SET_ERRNO(ERROR_INVALID_PARAM, "tcp_handler is NULL");
447 }
448
449 g_config = config;
451
452 /* === 1. Keepawake === */
453
454 bool keepawake_enabled = GET_OPTION(enable_keepawake);
455 bool keepawake_disabled = GET_OPTION(disable_keepawake);
456 if (keepawake_enabled && keepawake_disabled) {
457 return SET_ERRNO(ERROR_INVALID_PARAM, "Cannot specify both --keepawake and --no-keepawake");
458 }
459 if (keepawake_enabled) {
461 }
462
463 /* === 2. File descriptor limit === */
464
465 if (config->raise_fd_limit) {
467 }
468
469 /* === 3. Signal handlers === */
470
471 atomic_store_u64(&g_shutdown_requested, 0);
472 g_interrupt_fn = config->interrupt_fn;
473 platform_signal(SIGINT, server_like_signal_handler);
474 platform_signal(SIGTERM, server_like_signal_handler);
475#ifndef _WIN32
476 platform_signal(SIGPIPE, SIG_IGN);
477#endif
478
479 /* === 4. TCP server init === */
480
481 int port = GET_OPTION(port);
482 const char *address = GET_OPTION(address);
483 const char *address6 = GET_OPTION(address6);
484
485 bool ipv4_has_value = (address && strlen(address) > 0);
486 bool ipv6_has_value = (address6 && strlen(address6) > 0);
487
488 tcp_server_config_t tcp_config = {
489 .port = port,
490 .ipv4_address = ipv4_has_value ? address : NULL,
491 .ipv6_address = ipv6_has_value ? address6 : NULL,
492 .bind_ipv4 = ipv4_has_value || !ipv6_has_value,
493 .bind_ipv6 = ipv6_has_value || !ipv4_has_value,
494 .accept_timeout_sec = 1,
495 .client_handler = config->tcp_handler,
496 .user_data = config->tcp_user_data,
497 .status_update_fn = NULL,
498 .status_update_data = NULL,
499 };
500
501 result = tcp_server_init(&g_tcp_server, &tcp_config);
502 if (result != ASCIICHAT_OK) {
503 log_error("Failed to initialize TCP server on port %d", port);
504 goto cleanup;
505 }
506 g_tcp_server_initialized = true;
507 log_info("TCP server initialized on port %d", port);
508
509 /* === 5. UPnP port mapping === */
510
511 if (config->upnp.enabled && GET_OPTION(enable_upnp)) {
512 asciichat_error_t upnp_result = nat_upnp_open(port, config->upnp.description, &g_upnp_ctx);
513
514 if (upnp_result == ASCIICHAT_OK && g_upnp_ctx) {
515 char public_addr[22];
516 if (nat_upnp_get_address(g_upnp_ctx, public_addr, sizeof(public_addr)) == ASCIICHAT_OK) {
517 char msg[256];
518 safe_snprintf(msg, sizeof(msg), "Public endpoint: %s (direct TCP)", public_addr);
519 log_console(LOG_INFO, msg);
520 log_info("UPnP: Port mapping successful, public endpoint: %s", public_addr);
521 }
522 } else {
523 log_info("UPnP: Port mapping unavailable or failed - will use WebRTC fallback");
524 }
525 }
526
527 /* === 6. mDNS === */
528
529 if (config->mdns.enabled) {
530 g_mdns_ctx = asciichat_mdns_init();
531 if (!g_mdns_ctx) {
532 LOG_ERRNO_IF_SET("Failed to initialize mDNS (non-fatal, LAN discovery disabled)");
533 log_warn("mDNS disabled - LAN discovery will not be available");
534 } else if (config->mdns.service_name) {
535 // Immediate advertisement if service_name is provided
537 }
538 }
539
540 /* === 7. WebSocket server construction (thread deferred until after mode init) === */
541
542 if (config->websocket.enabled) {
543 websocket_server_config_t ws_config = {
544 .port = GET_OPTION(websocket_port),
545 .client_handler = config->websocket.handler,
546 .user_data = config->websocket.user_data,
547 .tls_cert_path = GET_OPTION(websocket_tls_cert),
548 .tls_key_path = GET_OPTION(websocket_tls_key),
549 };
550
551 memset(&g_websocket_server, 0, sizeof(g_websocket_server));
552 asciichat_error_t ws_result = websocket_server_init(&g_websocket_server, &ws_config);
553 if (ws_result != ASCIICHAT_OK) {
554 log_warn("Failed to initialize WebSocket server - browser clients will not be supported");
555 } else {
556 g_websocket_initialized = true;
557 log_info("WebSocket server initialized on port %d (event loop deferred until after mode init)",
558 GET_OPTION(websocket_port));
559 }
560 }
561
562 /* === 8. Mode-specific init === */
563
564 result = config->init_fn(config->init_user_data);
565 if (result != ASCIICHAT_OK) {
566 log_error("Mode initialization failed");
567 goto cleanup;
568 }
569
570 /* === 8b. WebSocket event loop thread start (after mode init succeeds) === */
571
572 if (g_websocket_initialized && !atomic_load_u64(&g_shutdown_requested)) {
573 if (asciichat_thread_create(&g_websocket_thread, "websocket_event_loop", websocket_server_thread_wrapper,
574 &g_websocket_server) == 0) {
575 g_websocket_thread_started = true;
576 log_info("WebSocket event loop thread started");
577 } else {
578 log_error("Failed to start WebSocket event loop thread");
579 }
580 }
581
582 /* === 9. Status screen === */
583
584 if (config->status_fn) {
585 bool status_opt = GET_OPTION(status_screen);
586 bool status_explicit = GET_OPTION(status_screen_explicitly_set);
587 if ((status_opt && terminal_is_interactive()) || (status_explicit && status_opt)) {
588 atomic_store_bool(&g_status_should_exit, false);
590
591 // Store callback and user data for status thread (avoids reliance on g_config lifetime)
592 g_status_fn = config->status_fn;
593 g_status_user_data = config->status_user_data;
594
595 if (asciichat_thread_create(&g_status_screen_thread, "status_screen", status_screen_thread_func, NULL) == 0) {
596 g_status_screen_thread_started = true;
597 log_info("Status screen thread started");
598 } else {
599 log_error("Failed to start status screen thread");
601 g_status_fn = NULL;
602 g_status_user_data = NULL;
603 }
604 }
605 }
606
607 /* === 10. TCP accept loop (blocks) === */
608
609 log_info("Server accepting connections on port %d", port);
610 result = tcp_server_run(&g_tcp_server);
611 if (result != ASCIICHAT_OK) {
612 log_error("TCP server exited with error");
613 }
614
615 /* === CLEANUP === */
616
617cleanup:
618 g_config = NULL;
619
620 /* 11. Stop status screen */
621 if (g_status_screen_thread_started) {
622 atomic_store_bool(&g_status_should_exit, true);
623 int join_result = asciichat_thread_join_timeout(&g_status_screen_thread, NULL, 2000 * NS_PER_MS_INT);
624 if (join_result != 0) {
625 log_warn("Status screen thread did not exit within 2s");
626 }
627 g_status_screen_thread_started = false;
628 g_status_fn = NULL;
629 g_status_user_data = NULL;
631 }
632
633 /* 12. Stop WebSocket server (only if thread was started, i.e., init succeeded) */
634 if (g_websocket_thread_started) {
635 atomic_store_bool(&g_websocket_server.running, false);
636 websocket_server_cancel_service(&g_websocket_server);
637 int ws_join = asciichat_thread_join_timeout(&g_websocket_thread, NULL, 500 * NS_PER_MS_INT);
638 if (ws_join != 0) {
639 log_warn("WebSocket event loop thread did not exit within 500ms");
640 }
641 g_websocket_thread_started = false;
642 }
643
644 /* 13. Mode-specific cleanup (only call if config provided) */
645 if (config && config->cleanup_fn) {
646 config->cleanup_fn(config->cleanup_user_data);
647 }
648
649 /* 14. Destroy WebSocket server (only if successfully initialized) */
650 if (g_websocket_initialized) {
651 websocket_server_destroy(&g_websocket_server);
652 g_websocket_initialized = false;
653 log_debug("WebSocket server destroyed");
654 }
655
656 /* 15. Destroy TCP server */
657 if (g_tcp_server_initialized) {
658 tcp_server_destroy(&g_tcp_server);
659 g_tcp_server_initialized = false;
660 log_debug("TCP server destroyed");
661 }
662
663 /* 16. UPnP cleanup */
664 if (g_upnp_ctx) {
665 nat_upnp_close(&g_upnp_ctx);
666 log_debug("UPnP port mapping closed");
667 }
668
669 /* 17. mDNS cleanup */
670 if (g_mdns_ctx) {
671 asciichat_mdns_destroy(g_mdns_ctx);
672 g_mdns_ctx = NULL;
673 log_debug("mDNS context destroyed");
674 }
675
676 /* 18. Keepawake */
677 if (keepawake_enabled) {
679 }
680
681 return result;
682}
Platform abstraction layer umbrella header providing unified cross-platform API.
⚠️‼️ Comprehensive thread-local error context system for ascii-chat
#define LOG_ERRNO_IF_SET(message)
Check if any error occurred and log it if so.
void atomic_store_bool(atomic_t *a, bool value)
Atomically store a boolean value.
Definition atomic.c:177
bool atomic_load_bool(atomic_t *a)
Atomically load a boolean value.
Definition atomic.c:169
void atomic_store_u64(atomic_t *a, uint64_t value)
Atomically store a uint64_t value.
Definition atomic.c:241
uint64_t atomic_load_u64(atomic_t *a)
Atomically load a uint64_t value.
Definition atomic.c:233
🗃️ Lock-Free Unified Memory Buffer Pool with Lazy Allocation
⚙️ Common definitions, error codes, macros, and types shared throughout the application
Named object registry for debugging — log identifiable resource names.
void buffer_pool_free(buffer_pool_t *pool, const void *data, size_t size)
Free a buffer back to the pool (lock-free)
unsigned short uint16_t
Definition common.h:57
unsigned int uint32_t
Definition common.h:58
unsigned long long uint64_t
Definition common.h:59
#define NAMED_REGISTER_ATOMIC(a, name, parent_ptr)
Register an atomic_t with automatic format specifier.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_INVALID_STATE
@ ERROR_NETWORK_PROTOCOL
Definition error_codes.h:81
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
@ CRYPTO_HANDSHAKE_READY
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
#define NS_PER_SEC_INT
Definition time.h:157
#define NS_PER_MS_INT
Definition time.h:156
#define US_PER_SEC_INT
Definition time.h:161
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
packet_type_t
Network protocol packet type enumeration.
Definition packet.h:286
@ PACKET_TYPE_PROTOCOL_VERSION
Protocol version and capabilities negotiation.
Definition packet.h:288
signal_handler_t platform_signal(int sig, signal_handler_t handler)
Set a signal handler.
Definition misc.c:115
int asciichat_thread_join_timeout(asciichat_thread_t *thread, void **retval, uint64_t timeout_ns)
Wait for a thread to complete with timeout.
platform_stderr_redirect_handle_t platform_stderr_redirect_to_null(void)
Redirect stderr to /dev/null temporarily.
void platform_stderr_restore(platform_stderr_redirect_handle_t handle)
Restore stderr from a redirect handle.
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
void platform_disable_keepawake(void)
Disable system sleep prevention (allow OS to sleep)
uint64_t platform_get_monotonic_time_us(void)
Get monotonic time in microseconds.
keyboard_key_t keyboard_read_with_timeout(uint32_t timeout_ms)
Read keyboard input with timeout.
void platform_raise_fd_limit(unsigned int limit)
Raise the file descriptor limit for the current process.
void platform_sleep_us(unsigned int us)
High-precision sleep function with microsecond precision.
void keyboard_destroy(void)
Cleanup keyboard input system and restore terminal.
keyboard_key_t
Unified keyboard key code enumeration.
Definition keyboard.h:54
asciichat_error_t platform_enable_keepawake(void)
Enable system sleep prevention (keepawake mode)
@ KEY_NONE
No key pressed or no input available.
Definition keyboard.h:55
Server-side handshake functions.
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
🎮 Cross-platform keyboard input interface for ascii-chat
asciichat_error_t crypto_handshake_server_send_parameters(crypto_handshake_context_t *ctx, acip_transport_t *transport)
Server: Send CRYPTO_PARAMETERS and set handshake context sizes.
asciichat_error_t crypto_handshake_server_complete(crypto_handshake_context_t *ctx, acip_transport_t *transport, packet_type_t packet_type, const uint8_t *payload, size_t payload_len)
Server: Process auth response and complete handshake.
asciichat_error_t crypto_handshake_server_auth_challenge(crypto_handshake_context_t *ctx, acip_transport_t *transport, packet_type_t packet_type, const uint8_t *payload, size_t payload_len)
Server: Process client's public key and send auth challenge.
asciichat_error_t crypto_handshake_server_start(crypto_handshake_context_t *ctx, acip_transport_t *transport)
Server: Start crypto handshake by sending public key.
void tcp_server_destroy(tcp_server_t *server)
Shutdown TCP server.
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.
asciichat_error_t websocket_server_init(websocket_server_t *server, const websocket_server_config_t *config)
Initialize WebSocket server.
asciichat_error_t websocket_server_run(websocket_server_t *server)
Run WebSocket server event loop.
void websocket_server_destroy(websocket_server_t *server)
Destroy WebSocket server and free resources.
void websocket_server_cancel_service(websocket_server_t *server)
Wake the WebSocket server event loop immediately.
#define asciichat_thread_create(thread_ptr, attr, start_routine, arg)
void * asciichat_thread_t
#define asciichat_thread_join(thread, timeout_ms)
📝 Logging API with multiple log levels and terminal output control
#define log_console(level, message)
Definition log/log.h:1003
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 asciichat_mdns_advertise(asciichat_mdns_t *mdns, const asciichat_mdns_service_t *service)
Advertise a service on the local network.
bool terminal_is_interactive(void)
Check if the session is fully interactive.
bool log_search_check_signal_cancel(void)
Check and clear the signal-cancel flag.
Definition search.c:380
bool log_search_is_entering(void)
Check if currently in input mode (typing pattern)
Definition search.c:385
void log_search_exit_mode(bool accept)
Exit search mode.
Definition search.c:287
bool log_search_should_handle(int key)
Check if a key should be handled by grep module.
Definition search.c:399
asciichat_error_t log_search_handle_key(keyboard_key_t key)
Process keyboard input for grep.
Definition search.c:414
Interactive search filtering for terminal screens.
asciichat_error_t packet_receive_via_transport(acip_transport_t *transport, packet_type_t *type, void **payload, size_t *payload_len, void **alloc_buffer)
Receive a packet via transport with header parsing and validation.
Definition send.c:122
ACIP shared/bidirectional packet sending functions.
websocket_server_t * session_server_like_get_websocket_server(void)
asciichat_error_t session_server_like_handshake(crypto_handshake_context_t *ctx, acip_transport_t *transport)
bool session_server_like_shutdown_requested(void)
asciichat_mdns_t * session_server_like_get_mdns_ctx(void)
tcp_server_t * session_server_like_get_tcp_server(void)
#define KEYBOARD_QUEUE_SIZE
Definition server_like.c:73
asciichat_error_t session_server_like_mdns_advertise(const char *name, const char *type, uint16_t port, const char **txt, size_t txt_count)
asciichat_error_t session_server_like_run(const session_server_like_config_t *config)
nat_upnp_context_t * session_server_like_get_upnp_ctx(void)
Shared initialization, networking, and teardown for server-like modes.
void(* session_server_like_status_fn)(void *user_data, ui_status_t *out_status)
void(* session_server_like_interrupt_fn)(int sig)
Cross-platform crash signal and exception handling.
void ui_status_log_destroy(void)
Cleanup status screen log capture system.
Definition status.c:36
void ui_status_log_init(void)
Initialize status screen log capture system.
Definition status.c:28
bool ui_status_display_interactive(const ui_status_t *status)
Display status screen with keyboard input support Returns true if status screen should continue,...
Definition status.c:237
Transport instance structure.
Definition transport.h:214
uint64_t receive_timeout_ns
Optional TCP receive timeout override; zero uses the normal polling timeout.
Definition transport.h:217
Service information for advertisement.
Internal mDNS context structure.
Atomic value wrapper for integral/boolean types.
Definition atomic.h:76
Cryptographic handshake context structure.
crypto_handshake_state_t state
Handle to UPnP context.
Definition upnp.h:25
Handle for temporary stderr redirection.
Definition system.h:375
struct session_server_like_config::@12 upnp
session_server_like_interrupt_fn interrupt_fn
struct session_server_like_config::@10 websocket
struct session_server_like_config::@11 mdns
tcp_client_handler_fn tcp_handler
session_server_like_init_fn init_fn
session_server_like_cleanup_fn cleanup_fn
session_server_like_status_fn status_fn
websocket_client_handler_fn handler
Status information for server/discovery service modes.
Definition status.h:30
Cross-platform system functions interface for ascii-chat.
🖥️ Cross-platform terminal interface for ascii-chat
#define LOG_INFO
Definition types.h:40
void nat_upnp_close(nat_upnp_context_t **ctx)
Close port mapping and clean up.
Definition upnp.c:276
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:236
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:310