ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
client/protocol.c
Go to the documentation of this file.
1
76#include "protocol.h"
77#include "main.h"
78#include "../main.h" // Global exit API
79#include "server.h"
80#include "display.h"
81#include "capture.h"
82#include "audio.h"
83#include <ascii-chat/audio/audio.h>
84#include <ascii-chat/audio/analysis.h>
85#include "keepalive.h"
86#include <ascii-chat/thread_pool.h>
87
88#include <ascii-chat/network/packet.h>
89#include <ascii-chat/network/packet_parsing.h>
90#include <ascii-chat/network/packet_parsing.h>
91#include <ascii-chat/network/acip/handlers.h>
92#include <ascii-chat/network/acip/transport.h>
93#include <ascii-chat/network/acip/client.h>
94#include <ascii-chat/network/acip/acds.h>
95#include <ascii-chat/network/webrtc/peer_manager.h>
96#include <ascii-chat/buffer_pool.h>
97#include <ascii-chat/common.h>
98#include <ascii-chat/common/buffer_sizes.h>
99#include <ascii-chat/util/endian.h>
100#include <ascii-chat/util/validation.h>
101#include <ascii-chat/util/endian.h>
102#include <ascii-chat/util/format.h>
103#include <ascii-chat/options/options.h>
104#include <ascii-chat/options/rcu.h> // For RCU-based options access
105#include <ascii-chat/network/crc32.h>
106#include <ascii-chat/util/fps.h>
107#include <ascii-chat/util/time.h>
108#include <ascii-chat/crypto/crypto.h>
109#include <ascii-chat/crypto/handshake/client.h>
110
111// Forward declaration for client crypto functions
112bool crypto_client_is_ready(void);
113const crypto_context_t *crypto_client_get_context(void);
114int crypto_client_decrypt_packet(const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext,
115 size_t plaintext_size, size_t *plaintext_len);
116
117#include "crypto.h"
118#include <ascii-chat/util/time.h>
119
120#include <stdatomic.h>
121#include <string.h>
122#include <time.h>
123#include <stdarg.h>
124#include <stdio.h>
125
126#ifdef _WIN32
127#include <ascii-chat/platform/windows_compat.h>
128#endif
129
130#include <ascii-chat/network/compression.h>
131#include <ascii-chat/network/packet_parsing.h>
132
133#include <errno.h>
134
135/* ============================================================================
136 * Thread State Management
137 * ============================================================================ */
138
156static bool g_data_thread_created = false;
157
166static atomic_bool g_data_thread_exited = false;
167
168/* ============================================================================
169 * Multi-User Client State
170 * ============================================================================ */
171
201typedef struct {
203 uint32_t client_id;
205 char display_name[MAX_DISPLAY_NAME_LEN];
209 time_t last_seen;
211
221static uint32_t g_last_active_count = 0;
222
231static bool g_server_state_initialized = false;
232
242static bool g_should_clear_before_next_frame = false;
243
244/* ============================================================================
245 * Protocol Validation and Error Handling
246 * ============================================================================ */
247
260static void disconnect_server_for_bad_data(const char *format, ...) {
261 va_list args;
262 va_start(args, format);
263
264 char message[BUFFER_SIZE_SMALL];
265 safe_vsnprintf(message, sizeof(message), format, args);
266 va_end(args);
267
268 log_error("Server sent invalid data - disconnecting: %s", message);
269
270 // Close the server connection
273}
274
275/* ============================================================================
276 * Packet Handler Functions
277 * ============================================================================ */
278
296static char *decode_frame_data(const char *frame_data_ptr, size_t frame_data_len, bool is_compressed,
297 uint32_t original_size, uint32_t compressed_size) {
298 return packet_decode_frame_data_malloc(frame_data_ptr, frame_data_len, is_compressed, original_size, compressed_size);
299}
300
322static void handle_ascii_frame_packet(const void *data, size_t len) {
323 if (should_exit()) {
324 return;
325 }
326
327 if (!data || len < sizeof(ascii_frame_packet_t)) {
328 disconnect_server_for_bad_data("ASCII_FRAME payload too small: %zu (min %zu)", len, sizeof(ascii_frame_packet_t));
329 return;
330 }
331
332 // FPS tracking for received ASCII frames using reusable tracker utility
333 static fps_t fps_tracker = {0};
334 static bool fps_tracker_initialized = false;
335
336 // Initialize FPS tracker on first frame
337 if (!fps_tracker_initialized) {
338 int fps = GET_OPTION(fps);
339 int expected_fps = fps > 0 ? ((fps > 144) ? 144 : fps) : DEFAULT_MAX_FPS;
340 fps_init(&fps_tracker, expected_fps, "ASCII_RX");
341 fps_tracker_initialized = true;
342 }
343
344 // Track this frame and detect lag
345 fps_frame_ns(&fps_tracker, time_get_ns(), "ASCII frame");
346
347 // Extract header from the packet
348 ascii_frame_packet_t header;
349 memcpy(&header, data, sizeof(ascii_frame_packet_t));
350
351 // Convert from network byte order
352 header.width = NET_TO_HOST_U32(header.width);
353 header.height = NET_TO_HOST_U32(header.height);
354 header.original_size = NET_TO_HOST_U32(header.original_size);
355 header.compressed_size = NET_TO_HOST_U32(header.compressed_size);
356 header.checksum = NET_TO_HOST_U32(header.checksum);
357 header.flags = NET_TO_HOST_U32(header.flags);
358
359 // Get the frame data (starts after the header)
360 const char *frame_data_ptr = (const char *)data + sizeof(ascii_frame_packet_t);
361 size_t frame_data_len = len - sizeof(ascii_frame_packet_t);
362
363 // Decode frame data (handles both compressed and uncompressed)
364 bool is_compressed = (header.flags & FRAME_FLAG_IS_COMPRESSED) && header.compressed_size > 0;
365 char *frame_data =
366 decode_frame_data(frame_data_ptr, frame_data_len, is_compressed, header.original_size, header.compressed_size);
367 if (!frame_data) {
368 return; // Error already logged by decode_frame_data
369 }
370
371 // Verify checksum
372 uint32_t actual_crc = asciichat_crc32(frame_data, header.original_size);
373 if (actual_crc != header.checksum) {
374 log_error("Frame checksum mismatch: got 0x%x, expected 0x%x (size=%u, first_bytes=%02x%02x%02x%02x)", actual_crc,
375 header.checksum, header.original_size, (unsigned char)frame_data[0], (unsigned char)frame_data[1],
376 (unsigned char)frame_data[2], (unsigned char)frame_data[3]);
377
378 // DEBUG: Try software CRC32 to compare
379 uint32_t sw_crc = asciichat_crc32_sw(frame_data, header.original_size);
380 log_error("Software CRC32: 0x%x (matches: %s)", sw_crc, (sw_crc == header.checksum) ? "YES" : "NO");
381
382 SAFE_FREE(frame_data);
383 return;
384 }
385
386 // Track frame dimension changes
387 static uint32_t last_width = 0;
388 static uint32_t last_height = 0;
389
390 if (header.width > 0 && header.height > 0) {
391 if (header.width != last_width || header.height != last_height) {
392 last_width = header.width;
393 last_height = header.height;
394 }
395 }
396
397 // Handle snapshot mode timing
398 bool take_snapshot = false;
399 if (GET_OPTION(snapshot_mode)) {
400 static uint64_t first_frame_time_ns = 0;
401 static bool first_frame_recorded = false;
402 static int snapshot_frame_count = 0;
403
404 snapshot_frame_count++;
405 log_debug("Snapshot frame %d received", snapshot_frame_count);
406
407 if (!first_frame_recorded) {
408 first_frame_time_ns = time_get_ns();
409 first_frame_recorded = true;
410
411 if (GET_OPTION(snapshot_delay) == 0) {
412 log_debug("Snapshot captured immediately (delay=0)!");
413 take_snapshot = true;
414 signal_exit();
415 } else {
416 log_debug("Snapshot mode: first frame received, waiting %.2f seconds for webcam warmup...",
417 GET_OPTION(snapshot_delay));
418 }
419 } else {
420 uint64_t current_time_ns = time_get_ns();
421 double elapsed = time_ns_to_s(time_elapsed_ns(first_frame_time_ns, current_time_ns));
422
423 if (elapsed >= GET_OPTION(snapshot_delay)) {
424 char duration_str[32];
425 format_duration_s(elapsed, duration_str, sizeof(duration_str));
426 log_debug("Snapshot captured after %s!", duration_str);
427 take_snapshot = true;
428 signal_exit();
429 }
430 }
431 }
432
433 // Check if we need to clear console before rendering this frame
434 // IMPORTANT: We track if this is the first frame to ensure proper initialization
435 static bool first_frame_rendered = false;
436
437 if (!first_frame_rendered) {
438 // Always clear display and disable logging before rendering the first frame
439 // This ensures clean ASCII display regardless of packet arrival order
440 log_debug("First frame - clearing display and disabling terminal logging");
443 first_frame_rendered = true;
444 g_server_state_initialized = true; // Mark as initialized
445 g_should_clear_before_next_frame = false; // Clear any pending clear request
446 log_debug("CLIENT_DISPLAY: Display cleared, ready for ASCII frames");
447 } else if (g_should_clear_before_next_frame) {
448 // Subsequent clear request from server (e.g., after client list changes)
449 log_debug("CLIENT_DISPLAY: Clearing display for layout change");
452 g_should_clear_before_next_frame = false;
453 }
454
455 // Safety check before rendering
456 if (!frame_data || header.original_size == 0) {
457 log_error("Invalid frame data for rendering: frame_data=%p, size=%u", frame_data, header.original_size);
458 if (frame_data) {
459 SAFE_FREE(frame_data);
460 }
461 return;
462 }
463
464 // Client-side FPS limiting for rendering (display)
465 // Server may send at 144fps for high-refresh displays, but this client renders at its requested FPS
466 static uint64_t last_render_time_ns = 0;
467
468 // Don't limit frame rate in snapshot mode - always render the final frame
469 if (!take_snapshot) {
470 // Get the client's desired FPS (what we told the server we can display)
471 int fps = GET_OPTION(fps);
472 int client_display_fps = fps > 0 ? fps : DEFAULT_MAX_FPS;
473 uint64_t render_interval_us = US_PER_SEC_INT / (uint64_t)client_display_fps;
474
475 uint64_t render_time_ns = time_get_ns();
476 uint64_t render_elapsed_us = 0;
477
478 if (last_render_time_ns != 0) {
479 render_elapsed_us = time_ns_to_us(time_elapsed_ns(last_render_time_ns, render_time_ns));
480 }
481
482 // Skip rendering if not enough time has passed (frame rate limiting)
483 if (last_render_time_ns != 0) {
484 if (render_elapsed_us > 0 && render_elapsed_us < render_interval_us) {
485 // Drop this frame to maintain display FPS limit
486 SAFE_FREE(frame_data);
487 return;
488 }
489 }
490
491 // Update last render time
492 last_render_time_ns = render_time_ns;
493 }
494
495 // DEBUG: Periodically log frame stats on client side
496 static int client_frame_counter = 0;
497 client_frame_counter++;
498 if (client_frame_counter % 60 == 1) {
499 // Count lines and check for issues
500 int line_count = 0;
501 size_t frame_len = strlen(frame_data);
502 for (size_t i = 0; i < frame_len; i++) {
503 if (frame_data[i] == '\n')
504 line_count++;
505 }
506 log_debug("CLIENT_FRAME: received %zu bytes, %d newlines, header: %ux%u", frame_len, line_count, header.width,
507 header.height);
508 }
509
510 display_render_frame(frame_data);
511
512 SAFE_FREE(frame_data);
513}
514
527static void handle_audio_packet(const void *data, size_t len) {
528 if (!data || len == 0) {
529 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid audio packet data");
530 return;
531 }
532
533 if (!GET_OPTION(audio_enabled)) {
534 log_warn_every(NS_PER_MS_INT, "Received audio packet but audio is disabled");
535 return;
536 }
537
538 int num_samples = (int)(len / sizeof(float));
539 if (num_samples > AUDIO_SAMPLES_PER_PACKET) {
540 log_warn("Audio packet too large: %d samples", num_samples);
541 return;
542 }
543
544 // Copy data to properly aligned buffer to avoid UndefinedBehaviorSanitizer errors
545 float samples[AUDIO_SAMPLES_PER_PACKET];
546 SAFE_MEMCPY(samples, sizeof(samples), data, len);
547
548 // Process audio through audio subsystem
549 audio_process_received_samples(samples, num_samples);
550
551#ifdef DEBUG_AUDIO
552 log_debug("Processed %d audio samples", num_samples);
553#endif
554}
555
567static void handle_audio_opus_packet(const void *data, size_t len) {
568 START_TIMER("audio_packet_total");
569
570 // Validate parameters
571 if (!data || len == 0) {
572 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid audio opus packet: data=%p, len=%zu", data, len);
573 return;
574 }
575
576 if (!GET_OPTION(audio_enabled)) {
577 log_warn_every(NS_PER_MS_INT, "Received opus audio packet but audio is disabled");
578 return;
579 }
580
581 // Data is raw Opus-encoded frame (no header parsing needed)
582 const uint8_t *opus_data = (const uint8_t *)data;
583
584 // Opus max frame size is 2880 samples (120ms @ 48kHz)
585 float samples[2880];
586
587 START_TIMER("opus_decode");
588 int decoded_samples = audio_decode_opus(opus_data, len, samples, 2880);
589 double decode_ns = STOP_TIMER("opus_decode");
590
591 if (decoded_samples <= 0) {
592 log_warn("Failed to decode Opus audio packet, decoded=%d", decoded_samples);
593 return;
594 }
595
596 // Track received packet for analysis
597 if (GET_OPTION(audio_analysis_enabled)) {
599 }
600
601 // Process decoded audio through audio subsystem
602 START_TIMER("process_samples");
603 audio_process_received_samples(samples, decoded_samples);
604 double process_ns = STOP_TIMER("process_samples");
605
606 double total_ns = STOP_TIMER("audio_packet_total");
607
608 static int timing_count = 0;
609 if (++timing_count % 100 == 0) {
610 log_debug("Audio packet timing #%d: decode=%.2fµs, process=%.2fµs, total=%.2fµs", timing_count, decode_ns / 1000.0,
611 process_ns / 1000.0, total_ns / 1000.0);
612 }
613
614 log_debug_every(LOG_RATE_DEFAULT, "Processed Opus audio: %d decoded samples from %zu byte packet", decoded_samples,
615 len);
616}
617
637static void handle_audio_opus_batch_packet(const void *data, size_t len) {
638 // Validate parameters
639 if (!data || len == 0) {
640 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid opus batch packet: data=%p, len=%zu", data, len);
641 return;
642 }
643
644 if (!GET_OPTION(audio_enabled)) {
645 log_warn_every(NS_PER_MS_INT, "Received opus batch packet but audio is disabled");
646 return;
647 }
648
649 // Parse batch header using av_receive_audio_opus_batch() for consistency
650 const uint8_t *opus_data = NULL;
651 size_t opus_size = 0;
652 const uint16_t *frame_sizes = NULL;
653 int sample_rate = 0;
654 int frame_duration = 0;
655 int frame_count = 0;
656
657 asciichat_error_t result = packet_parse_opus_batch(data, len, &opus_data, &opus_size, &frame_sizes, &sample_rate,
658 &frame_duration, &frame_count);
659
660 if (result != ASCIICHAT_OK) {
661 log_warn("Failed to parse Opus batch packet");
662 return;
663 }
664
665 if (frame_count <= 0 || frame_count > 256 || opus_size == 0) {
666 log_warn("Invalid Opus batch: frame_count=%d, opus_size=%zu", frame_count, opus_size);
667 return;
668 }
669
670 // Calculate samples per frame
671 int samples_per_frame = (sample_rate * frame_duration) / 1000;
672 if (samples_per_frame <= 0 || samples_per_frame > 2880) {
673 log_warn("Invalid Opus frame parameters: samples_per_frame=%d", samples_per_frame);
674 return;
675 }
676
677 // Allocate buffer for all decoded samples
678 size_t max_decoded_samples = (size_t)samples_per_frame * (size_t)frame_count;
679 float *all_samples = SAFE_MALLOC(max_decoded_samples * sizeof(float), float *);
680 if (!all_samples) {
681 SET_ERRNO(ERROR_MEMORY, "Failed to allocate memory for Opus batch decoding");
682 return;
683 }
684
685 // Decode each Opus frame using frame_sizes array
686 int total_decoded_samples = 0;
687 size_t opus_offset = 0;
688
689 for (int i = 0; i < frame_count; i++) {
690 // Get frame size (convert from network byte order)
691 size_t frame_size = (size_t)NET_TO_HOST_U16(frame_sizes[i]);
692
693 if (opus_offset + frame_size > opus_size) {
694 log_warn("Opus batch truncated at frame %d (offset=%zu, frame_size=%zu, total=%zu)", i, opus_offset, frame_size,
695 opus_size);
696 break;
697 }
698
699 // Decode frame through audio pipeline
700 float *frame_buffer = all_samples + total_decoded_samples;
701 int remaining_space = (int)(max_decoded_samples - (size_t)total_decoded_samples);
702 int decoded = audio_decode_opus(opus_data + opus_offset, frame_size, frame_buffer, remaining_space);
703
704 if (decoded <= 0) {
705 log_warn("Failed to decode Opus frame %d in batch, decoded=%d", i, decoded);
706 break;
707 }
708
709 total_decoded_samples += decoded;
710 opus_offset += frame_size;
711 }
712
713 if (total_decoded_samples > 0) {
714 // Track received packet for analysis
715 if (GET_OPTION(audio_analysis_enabled)) {
717 }
718
719 // Process decoded audio through audio subsystem
720 audio_process_received_samples(all_samples, total_decoded_samples);
721
722 log_debug_every(LOG_RATE_DEFAULT, "Processed Opus batch: %d decoded samples from %d frames", total_decoded_samples,
723 frame_count);
724 }
725
726 // Clean up
727 SAFE_FREE(all_samples);
728}
729
730static bool handle_error_message_packet(const void *data, size_t len) {
731 asciichat_error_t remote_error = ASCIICHAT_OK;
732 char message[MAX_ERROR_MESSAGE_LENGTH + 1] = {0};
733
734 asciichat_error_t parse_result = packet_parse_error_message(data, len, &remote_error, message, sizeof(message), NULL);
735 if (parse_result != ASCIICHAT_OK) {
736 log_error("Failed to parse error packet from server: %s", asciichat_error_string(parse_result));
737 return false;
738 }
739
740 log_error("Server reported error %d (%s): %s", remote_error, asciichat_error_string(remote_error), message);
741 log_warn("Server signaled protocol error; closing connection");
744 return true;
745}
746
747static void handle_remote_log_packet(const void *data, size_t len) {
748 log_level_t remote_level = LOG_INFO;
749 remote_log_direction_t direction = REMOTE_LOG_DIRECTION_UNKNOWN;
750 uint16_t flags = 0;
751 char message[MAX_REMOTE_LOG_MESSAGE_LENGTH + 1] = {0};
752
753 asciichat_error_t parse_result =
754 packet_parse_remote_log(data, len, &remote_level, &direction, &flags, message, sizeof(message), NULL);
755 if (parse_result != ASCIICHAT_OK) {
756 log_error("Failed to parse remote log packet from server: %s", asciichat_error_string(parse_result));
757 return;
758 }
759
760 if (direction != REMOTE_LOG_DIRECTION_SERVER_TO_CLIENT) {
761 log_error("Remote log packet direction mismatch (direction=%u)", direction);
762 return;
763 }
764
765 bool truncated = (flags & REMOTE_LOG_FLAG_TRUNCATED) != 0;
766
767 if (truncated) {
768 log_msg(remote_level, __FILE__, __LINE__, __func__, "[REMOTE SERVER] %s [message truncated]", message);
769 } else {
770 log_msg(remote_level, __FILE__, __LINE__, __func__, "[REMOTE SERVER] %s", message);
771 }
772}
773
786static void handle_server_state_packet(const void *data, size_t len) {
787 if (!data || len != sizeof(server_state_packet_t)) {
788 log_error("Invalid server state packet size: %zu", len);
789 return;
790 }
791
792 const server_state_packet_t *state = (const server_state_packet_t *)data;
793
794 // Convert from network byte order
795 uint32_t active_count = NET_TO_HOST_U32(state->active_client_count);
796
797 // Check if connected count changed - if so, set flag to clear console before next frame
798 if (g_server_state_initialized) {
799 if (g_last_active_count != active_count) {
800 log_debug("Active client count changed from %u to %u - will clear console before next frame", g_last_active_count,
801 active_count);
802 g_should_clear_before_next_frame = true;
803 }
804 } else {
805 // First state packet received
806 g_server_state_initialized = true;
807 // Clear terminal before the very first frame
808 g_should_clear_before_next_frame = true;
809 }
810
811 g_last_active_count = active_count;
812}
813
814/* ============================================================================
815 * ACIP Callback Forward Declarations and Structure
816 * ============================================================================ */
817
818// Forward declarations for ACIP callbacks (implemented after this section)
819static void acip_on_ascii_frame(const ascii_frame_packet_t *header, const void *frame_data, size_t data_len, void *ctx);
820static void acip_on_audio(const void *audio_data, size_t audio_len, void *ctx);
821static void acip_on_audio_batch(const audio_batch_packet_t *header, const float *samples, size_t num_samples,
822 void *ctx);
823static void acip_on_audio_opus(const void *opus_data, size_t opus_len, void *ctx);
824static void acip_on_audio_opus_batch(const void *batch_data, size_t batch_len, void *ctx);
825static void acip_on_server_state(const server_state_packet_t *state, void *ctx);
826static void acip_on_error(const error_packet_t *header, const char *message, void *ctx);
827static void acip_on_remote_log(const remote_log_packet_t *header, const char *message, void *ctx);
828static void acip_on_ping(void *ctx);
829static void acip_on_pong(void *ctx);
830static void acip_on_clear_console(void *ctx);
831static void acip_on_crypto_rekey_request(const void *payload, size_t payload_len, void *ctx);
832static void acip_on_crypto_rekey_response(const void *payload, size_t payload_len, void *ctx);
833static void acip_on_webrtc_sdp(const acip_webrtc_sdp_t *sdp, size_t total_len, void *ctx);
834static void acip_on_webrtc_ice(const acip_webrtc_ice_t *ice, size_t total_len, void *ctx);
835static void acip_on_session_joined(const acip_session_joined_t *joined, void *ctx);
836static void acip_on_crypto_key_exchange_init(packet_type_t type, const void *payload, size_t payload_len, void *ctx);
837static void acip_on_crypto_auth_challenge(packet_type_t type, const void *payload, size_t payload_len, void *ctx);
838static void acip_on_crypto_server_auth_resp(packet_type_t type, const void *payload, size_t payload_len, void *ctx);
839static void acip_on_crypto_auth_failed(packet_type_t type, const void *payload, size_t payload_len, void *ctx);
840static void acip_on_crypto_handshake_complete(packet_type_t type, const void *payload, size_t payload_len, void *ctx);
841
848static const acip_client_callbacks_t g_acip_client_callbacks = {
849 .on_ascii_frame = acip_on_ascii_frame,
850 .on_audio = acip_on_audio,
851 .on_audio_batch = acip_on_audio_batch,
852 .on_audio_opus = acip_on_audio_opus,
853 .on_audio_opus_batch = acip_on_audio_opus_batch,
854 .on_server_state = acip_on_server_state,
855 .on_error = acip_on_error,
856 .on_remote_log = acip_on_remote_log,
857 .on_ping = acip_on_ping,
858 .on_pong = acip_on_pong,
859 .on_clear_console = acip_on_clear_console,
860 .on_crypto_rekey_request = acip_on_crypto_rekey_request,
861 .on_crypto_rekey_response = acip_on_crypto_rekey_response,
862 .on_webrtc_sdp = acip_on_webrtc_sdp,
863 .on_webrtc_ice = acip_on_webrtc_ice,
864 .on_session_joined = acip_on_session_joined,
865 .on_crypto_key_exchange_init = acip_on_crypto_key_exchange_init,
866 .on_crypto_auth_challenge = acip_on_crypto_auth_challenge,
867 .on_crypto_server_auth_resp = acip_on_crypto_server_auth_resp,
868 .on_crypto_auth_failed = acip_on_crypto_auth_failed,
869 .on_crypto_handshake_complete = acip_on_crypto_handshake_complete,
870 .app_ctx = NULL};
871
880const acip_client_callbacks_t *protocol_get_acip_callbacks() {
881 return &g_acip_client_callbacks;
882}
883
884/* ============================================================================
885 * Data Reception Thread
886 * ============================================================================ */
887
914static void *data_reception_thread_func(void *arg) {
915 (void)arg;
916
917#ifdef DEBUG_THREADS
918 log_debug("Data reception thread started");
919#endif
920
922 // Main loop: receive and process packets while connection is active
923 // When connection becomes inactive or shutdown is requested, thread exits cleanly
924
925 // Receive and dispatch packet using ACIP transport API
926 // This combines packet reception, decryption, parsing, handler dispatch, and cleanup
927 acip_transport_t *transport = server_connection_get_transport();
928 if (!transport) {
929 log_error("Transport not available, connection lost");
931 break;
932 }
933
934 asciichat_error_t acip_result = acip_client_receive_and_dispatch(transport, &g_acip_client_callbacks);
935
936 // Handle receive/dispatch errors
937 if (acip_result != ASCIICHAT_OK) {
938 // Check error type to determine action
939 asciichat_error_context_t err_ctx;
940 if (HAS_ERRNO(&err_ctx)) {
941 if (err_ctx.code == ERROR_NETWORK) {
942 // Network error or EOF - server disconnected
943 log_debug("Server disconnected (network error): %s", err_ctx.context_message);
945 break;
946 } else if (err_ctx.code == ERROR_CRYPTO) {
947 // Security violation - exit immediately
948 log_error("SECURITY: Server violated encryption policy");
949 log_error("SECURITY: This is a critical security violation - exiting immediately");
950 exit(1);
951 }
952 }
953
954 // Other errors - log warning but continue
955 log_warn("ACIP receive/dispatch failed: %s", asciichat_error_string(acip_result));
956 }
957 }
958
959#ifdef DEBUG_THREADS
960 log_debug("Data reception thread stopped");
961#endif
962
963 atomic_store(&g_data_thread_exited, true);
964
965 // Clean up thread-local error context before exit
967
968 return NULL;
969}
970
971/* ============================================================================
972 * Public Interface Functions
973 * ============================================================================ */
974
986 // Reset protocol state for new connection
987 g_server_state_initialized = false;
988 g_last_active_count = 0;
989 g_should_clear_before_next_frame = false;
990
991 // Reset display state for new connection
993
994 // Send CLIENT_CAPABILITIES packet FIRST before starting any threads
995 // Server expects this as the first packet after crypto handshake
996 log_debug("Sending client capabilities to server...");
997 asciichat_error_t cap_result = threaded_send_terminal_size_with_auto_detect(GET_OPTION(width), GET_OPTION(height));
998 if (cap_result != ASCIICHAT_OK) {
999 log_error("Failed to send client capabilities to server");
1000 return -1;
1001 }
1002 log_debug("Client capabilities sent successfully");
1003
1004 // Send STREAM_START packet with combined stream types BEFORE starting worker threads
1005 // This tells the server what streams to expect before any data arrives
1006 uint32_t stream_types = STREAM_TYPE_VIDEO; // Always have video
1007 if (GET_OPTION(audio_enabled)) {
1008 stream_types |= STREAM_TYPE_AUDIO; // Add audio if enabled
1009 }
1010 log_debug("Sending STREAM_START packet (types=0x%x: %s%s)...", stream_types, "video",
1011 (stream_types & STREAM_TYPE_AUDIO) ? "+audio" : "");
1012 asciichat_error_t stream_result = threaded_send_stream_start_packet(stream_types);
1013 if (stream_result != ASCIICHAT_OK) {
1014 log_error("Failed to send STREAM_START packet");
1015 return -1;
1016 }
1017 log_debug("STREAM_START packet sent successfully");
1018
1019 // Start data reception thread
1020 atomic_store(&g_data_thread_exited, false);
1021 if (thread_pool_spawn(g_client_worker_pool, data_reception_thread_func, NULL, 1, "data_reception") != ASCIICHAT_OK) {
1022 log_error("Failed to spawn data reception thread in worker pool");
1023 LOG_ERRNO_IF_SET("Data reception thread creation failed");
1024 return -1;
1025 }
1026
1027 // Start webcam capture thread
1028 log_debug("Starting webcam capture thread...");
1029 if (capture_start_thread() != 0) {
1030 log_error("Failed to start webcam capture thread");
1031 return -1;
1032 }
1033 log_debug("Webcam capture thread started successfully");
1034
1035 // Start audio capture thread if audio is enabled
1036 log_debug("Starting audio capture thread...");
1037 if (audio_start_thread() != 0) {
1038 log_error("Failed to start audio capture thread");
1039 return -1;
1040 }
1041 log_debug("Audio capture thread started successfully (or skipped if audio disabled)");
1042
1043 // Start keepalive/ping thread to prevent server timeout
1044 log_debug("Starting keepalive/ping thread...");
1045 if (keepalive_start_thread() != 0) {
1046 log_error("Failed to start keepalive/ping thread");
1047 return -1;
1048 }
1049 log_debug("Keepalive/ping thread started successfully");
1050
1051 g_data_thread_created = true;
1052 return 0;
1053}
1054
1064 if (!g_data_thread_created) {
1065 return;
1066 }
1067
1068 // Don't call signal_exit() here - that's for global shutdown only!
1069 // We just want to stop threads for this connection, not exit the entire client
1070
1071 // Shutdown the socket to interrupt any blocking recv() in data thread
1073
1074 // Stop keepalive/ping thread - it checks connection status and will exit
1076
1077 // Stop webcam capture thread
1079
1080 // Stop audio threads if running
1082
1083 // Wait for data reception thread to exit gracefully
1084 int wait_count = 0;
1085 while (wait_count < 20 && !atomic_load(&g_data_thread_exited)) {
1086 platform_sleep_us(100 * US_PER_MS_INT); // 100ms
1087 wait_count++;
1088 }
1089
1090 if (!atomic_load(&g_data_thread_exited)) {
1091 log_warn("Data thread not responding after 2 seconds - will be joined by thread pool");
1092 }
1093
1094 // Join all threads in the client worker pool (in stop_id order)
1095 // This handles the data reception thread and (eventually) all other worker threads
1097 asciichat_error_t result = thread_pool_stop_all(g_client_worker_pool);
1098 if (result != ASCIICHAT_OK) {
1099 log_error("Failed to stop client worker threads");
1100 LOG_ERRNO_IF_SET("Thread pool stop failed");
1101 }
1102 }
1103
1104 g_data_thread_created = false;
1105
1106#ifdef DEBUG_THREADS
1107 log_debug("Data reception thread stopped and joined by thread pool");
1108#endif
1109}
1110
1119 return atomic_load(&g_data_thread_exited) || server_connection_is_lost();
1120}
1121
1122/* ============================================================================
1123 * ACIP Callback Implementations
1124 * ============================================================================ */
1125
1131static void acip_on_ascii_frame(const ascii_frame_packet_t *header, const void *frame_data, size_t data_len,
1132 void *ctx) {
1133 (void)ctx;
1134
1135 // Reconstruct full packet for existing handler (header + data)
1136 // IMPORTANT: header is already in HOST byte order from ACIP layer,
1137 // but handle_ascii_frame_packet() expects NETWORK byte order and does conversion.
1138 // So we need to convert back to network order before passing.
1139 size_t total_len = sizeof(*header) + data_len;
1140 uint8_t *packet = SAFE_MALLOC(total_len, uint8_t *);
1141 if (!packet) {
1142 log_error("Failed to allocate buffer for ASCII frame callback");
1143 return;
1144 }
1145
1146 // Convert header fields back to network byte order for handle_ascii_frame_packet()
1147 ascii_frame_packet_t net_header = *header;
1148 net_header.width = HOST_TO_NET_U32(header->width);
1149 net_header.height = HOST_TO_NET_U32(header->height);
1150 net_header.original_size = HOST_TO_NET_U32(header->original_size);
1151 net_header.compressed_size = HOST_TO_NET_U32(header->compressed_size);
1152 net_header.checksum = HOST_TO_NET_U32(header->checksum);
1153 net_header.flags = HOST_TO_NET_U32(header->flags);
1154
1155 memcpy(packet, &net_header, sizeof(net_header));
1156 memcpy(packet + sizeof(net_header), frame_data, data_len);
1157
1158 handle_ascii_frame_packet(packet, total_len);
1159 SAFE_FREE(packet);
1160}
1161
1165static void acip_on_audio_batch(const audio_batch_packet_t *header, const float *samples, size_t num_samples,
1166 void *ctx) {
1167 (void)ctx;
1168 (void)header;
1169
1170 if (!GET_OPTION(audio_enabled)) {
1171 return;
1172 }
1173
1174 // Process samples directly (already dequantized by ACIP handler)
1175 audio_process_received_samples((float *)samples, (int)num_samples);
1176
1177 if (GET_OPTION(audio_analysis_enabled)) {
1178 // Approximate packet size for analysis
1179 size_t approx_size = sizeof(*header) + (num_samples * sizeof(uint32_t));
1181 }
1182
1183 log_debug_every(LOG_RATE_DEFAULT, "Processed audio batch: %zu samples from server", num_samples);
1184}
1185
1189static void acip_on_audio_opus(const void *opus_data, size_t opus_len, void *ctx) {
1190 (void)ctx;
1191
1192 // Call existing handler directly
1193 handle_audio_opus_packet(opus_data, opus_len);
1194}
1195
1199static void acip_on_server_state(const server_state_packet_t *state, void *ctx) {
1200 (void)ctx;
1201
1202 // Call existing handler directly
1203 handle_server_state_packet(state, sizeof(*state));
1204}
1205
1209static void acip_on_error(const error_packet_t *header, const char *message, void *ctx) {
1210 (void)ctx;
1211
1212 // Reconstruct packet for existing handler
1213 size_t msg_len = message ? strlen(message) : 0;
1214 size_t total_len = sizeof(*header) + msg_len;
1215
1216 uint8_t *packet = SAFE_MALLOC(total_len, uint8_t *);
1217 if (!packet) {
1218 log_error("Failed to allocate buffer for error packet callback");
1219 return;
1220 }
1221
1222 memcpy(packet, header, sizeof(*header));
1223 if (msg_len > 0) {
1224 memcpy(packet + sizeof(*header), message, msg_len);
1225 }
1226
1227 handle_error_message_packet(packet, total_len);
1228 SAFE_FREE(packet);
1229}
1230
1234static void acip_on_ping(void *ctx) {
1235 (void)ctx;
1236
1237 // Respond with PONG
1238 if (threaded_send_pong_packet() < 0) {
1239 log_error("Failed to send PONG response");
1240 }
1241}
1242
1246static void acip_on_audio(const void *audio_data, size_t audio_len, void *ctx) {
1247 (void)ctx;
1248
1249 // Call existing handler directly
1250 handle_audio_packet(audio_data, audio_len);
1251}
1252
1256static void acip_on_audio_opus_batch(const void *batch_data, size_t batch_len, void *ctx) {
1257 (void)ctx;
1258
1259 // Call existing handler directly
1260 handle_audio_opus_batch_packet(batch_data, batch_len);
1261}
1262
1266static void acip_on_remote_log(const remote_log_packet_t *header, const char *message, void *ctx) {
1267 (void)ctx;
1268
1269 // Reconstruct packet for existing handler
1270 size_t msg_len = strlen(message);
1271 size_t total_len = sizeof(*header) + msg_len;
1272
1273 uint8_t *packet = SAFE_MALLOC(total_len, uint8_t *);
1274 if (!packet) {
1275 log_error("Failed to allocate buffer for remote log callback");
1276 return;
1277 }
1278
1279 memcpy(packet, header, sizeof(*header));
1280 memcpy(packet + sizeof(*header), message, msg_len);
1281
1282 handle_remote_log_packet(packet, total_len);
1283 SAFE_FREE(packet);
1284}
1285
1289static void acip_on_pong(void *ctx) {
1290 (void)ctx;
1291 // Pong received - no action needed (server acknowledged our ping)
1292}
1293
1297static void acip_on_clear_console(void *ctx) {
1298 (void)ctx;
1299
1300 // Server requested console clear
1302 log_debug("Console cleared by server");
1303}
1304
1308static void acip_on_crypto_rekey_request(const void *payload, size_t payload_len, void *ctx) {
1309 (void)ctx;
1310
1311 // Process the server's rekey request
1312 asciichat_error_t crypto_result = crypto_client_process_rekey_request(payload, payload_len);
1313 if (crypto_result != ASCIICHAT_OK) {
1314 log_error("Failed to process REKEY_REQUEST: %d", crypto_result);
1315 return;
1316 }
1317
1318 // Send REKEY_RESPONSE
1319 crypto_result = crypto_client_send_rekey_response();
1320 if (crypto_result != ASCIICHAT_OK) {
1321 log_error("Failed to send REKEY_RESPONSE: %d", crypto_result);
1322 }
1323}
1324
1328static void acip_on_crypto_rekey_response(const void *payload, size_t payload_len, void *ctx) {
1329 (void)ctx;
1330
1331 // Process server's response
1332 asciichat_error_t crypto_result = crypto_client_process_rekey_response(payload, payload_len);
1333 if (crypto_result != ASCIICHAT_OK) {
1334 log_error("Failed to process REKEY_RESPONSE: %d", crypto_result);
1335 return;
1336 }
1337
1338 // Send REKEY_COMPLETE
1339 crypto_result = crypto_client_send_rekey_complete();
1340 if (crypto_result != ASCIICHAT_OK) {
1341 log_error("Failed to send REKEY_COMPLETE: %d", crypto_result);
1342 }
1343}
1344
1355static void acip_on_webrtc_sdp(const acip_webrtc_sdp_t *sdp, size_t total_len, void *ctx) {
1356 (void)ctx;
1357 (void)total_len; // Peer manager reads variable data via pointer arithmetic
1358
1359 // Check if WebRTC is initialized
1360 if (!g_peer_manager) {
1361 log_warn("Received WebRTC SDP but peer manager not initialized - ignoring");
1362 return;
1363 }
1364
1365 // Log SDP type for debugging
1366 const char *sdp_type_str = (sdp->sdp_type == 0) ? "offer" : "answer";
1367 log_debug("Received WebRTC SDP %s from participant (session_id=%.8s...)", sdp_type_str,
1368 (const char *)sdp->session_id);
1369
1370 // Handle SDP through peer manager (extracts variable data internally)
1371 asciichat_error_t result = webrtc_peer_manager_handle_sdp(g_peer_manager, sdp);
1372
1373 if (result != ASCIICHAT_OK) {
1374 log_error("Failed to handle WebRTC SDP: %s", asciichat_error_string(result));
1375 }
1376}
1377
1388static void acip_on_webrtc_ice(const acip_webrtc_ice_t *ice, size_t total_len, void *ctx) {
1389 (void)ctx;
1390 (void)total_len; // Peer manager reads variable data via pointer arithmetic
1391
1392 // Check if WebRTC is initialized
1393 if (!g_peer_manager) {
1394 log_warn("Received WebRTC ICE but peer manager not initialized - ignoring");
1395 return;
1396 }
1397
1398 log_debug("Received WebRTC ICE candidate from participant (session_id=%.8s...)", (const char *)ice->session_id);
1399
1400 // Handle ICE through peer manager (extracts variable data internally)
1401 asciichat_error_t result = webrtc_peer_manager_handle_ice(g_peer_manager, ice);
1402
1403 if (result != ASCIICHAT_OK) {
1404 log_error("Failed to handle WebRTC ICE: %s", asciichat_error_string(result));
1405 }
1406}
1407
1428static void acip_on_session_joined(const acip_session_joined_t *joined, void *ctx) {
1429 (void)ctx; // Unused for now, may be used in future for context passing
1430
1431 if (!joined) {
1432 log_error("SESSION_JOINED callback received NULL response");
1433 return;
1434 }
1435
1436 // Check if join was successful
1437 if (!joined->success) {
1438 log_error("ACDS session join failed: error %d: %s", joined->error_code, joined->error_message);
1439 // Connection will timeout waiting for SDP/WebRTC completion and fallback to next stage
1440 return;
1441 }
1442
1443 // Join succeeded - we have session context now
1444 log_debug("ACDS session join succeeded (participant_id=%.8s..., session_type=%s, server=%s:%u)",
1445 (const char *)joined->participant_id, joined->session_type == 1 ? "WebRTC" : "DirectTCP",
1446 joined->server_address, joined->server_port);
1447
1448 // Check if this is a WebRTC session
1449 if (joined->session_type == SESSION_TYPE_WEBRTC) {
1450 // TODO: Phase 3 - Initialize WebRTC connection with TURN credentials
1451 // webrtc_initialize_session(joined->session_id, joined->participant_id,
1452 // joined->turn_username, joined->turn_password);
1453 log_debug("WebRTC session detected - TODO: initialize WebRTC with TURN credentials");
1454 } else {
1455 // Direct TCP - connection is already established or will be established
1456 log_debug("Direct TCP session - using existing connection");
1457 }
1458}
1459
1473static void acip_on_crypto_key_exchange_init(packet_type_t type, const void *payload, size_t payload_len, void *ctx) {
1474 (void)ctx;
1475
1476 log_debug("Received CRYPTO_KEY_EXCHANGE_INIT from server");
1477
1478 acip_transport_t *transport = server_connection_get_transport();
1479 if (!transport) {
1480 log_error("Cannot handle key exchange - no transport available");
1481 return;
1482 }
1483
1484 asciichat_error_t result =
1485 crypto_handshake_client_key_exchange(&g_crypto_ctx, transport, type, (const uint8_t *)payload, payload_len);
1486 if (result != ASCIICHAT_OK) {
1487 log_error("Crypto handshake key exchange failed");
1489 } else {
1490 log_debug("Sent CRYPTO_KEY_EXCHANGE_RESP to server");
1491 }
1492}
1493
1507static void acip_on_crypto_auth_challenge(packet_type_t type, const void *payload, size_t payload_len, void *ctx) {
1508 (void)ctx;
1509
1510 log_debug("Received CRYPTO_AUTH_CHALLENGE from server");
1511
1512 acip_transport_t *transport = server_connection_get_transport();
1513 if (!transport) {
1514 log_error("Cannot handle auth challenge - no transport available");
1515 return;
1516 }
1517
1518 asciichat_error_t result =
1519 crypto_handshake_client_auth_response(&g_crypto_ctx, transport, type, (const uint8_t *)payload, payload_len);
1520 if (result != ASCIICHAT_OK) {
1521 log_error("Crypto handshake auth response failed");
1523 } else {
1524 log_debug("Sent CRYPTO_AUTH_RESPONSE to server");
1525 }
1526}
1527
1541static void acip_on_crypto_server_auth_resp(packet_type_t type, const void *payload, size_t payload_len, void *ctx) {
1542 (void)ctx;
1543
1544 log_debug("Received CRYPTO_SERVER_AUTH_RESP from server");
1545
1546 acip_transport_t *transport = server_connection_get_transport();
1547 if (!transport) {
1548 log_error("Cannot handle server auth response - no transport available");
1549 return;
1550 }
1551
1552 asciichat_error_t result =
1553 crypto_handshake_client_complete(&g_crypto_ctx, transport, type, (const uint8_t *)payload, payload_len);
1554 if (result != ASCIICHAT_OK) {
1555 log_error("Crypto handshake verification failed");
1557 } else {
1558 log_info("Crypto handshake completed successfully (mutual auth)");
1559
1560 // Link crypto context to transport for automatic encryption
1561 transport->crypto_ctx = &g_crypto_ctx.crypto_ctx;
1562 }
1563}
1564
1578static void acip_on_crypto_auth_failed(packet_type_t type, const void *payload, size_t payload_len, void *ctx) {
1579 (void)ctx;
1580 (void)type;
1581
1582 // Extract error message if present
1583 char error_msg[256] = "Unknown error";
1584 if (payload && payload_len > 0) {
1585 size_t msg_len = payload_len < sizeof(error_msg) - 1 ? payload_len : sizeof(error_msg) - 1;
1586 memcpy(error_msg, payload, msg_len);
1587 error_msg[msg_len] = '\0';
1588 }
1589
1590 log_error("Server rejected authentication: %s", error_msg);
1591 log_error("Disconnecting - crypto handshake failed");
1592
1594}
1595
1609static void acip_on_crypto_handshake_complete(packet_type_t type, const void *payload, size_t payload_len, void *ctx) {
1610 (void)ctx;
1611
1612 log_debug("Received CRYPTO_HANDSHAKE_COMPLETE from server");
1613
1614 acip_transport_t *transport = server_connection_get_transport();
1615 if (!transport) {
1616 log_error("Cannot complete handshake - no transport available");
1617 return;
1618 }
1619
1620 asciichat_error_t result =
1621 crypto_handshake_client_complete(&g_crypto_ctx, transport, type, (const uint8_t *)payload, payload_len);
1622 if (result != ASCIICHAT_OK) {
1623 log_error("Crypto handshake completion failed");
1625 } else {
1626 log_info("Crypto handshake completed successfully");
1627
1628 // Link crypto context to transport for automatic encryption
1629 transport->crypto_ctx = &g_crypto_ctx.crypto_ctx;
1630 }
1631}
void audio_analysis_track_received_packet(size_t size)
Definition analysis.c:463
void asciichat_errno_destroy(void)
ascii-chat Client Media Capture Management Interface
struct webrtc_peer_manager * g_peer_manager
Global WebRTC peer manager (legacy compatibility)
thread_pool_t * g_client_worker_pool
Global client worker thread pool.
bool should_exit(void)
Definition main.c:90
uint32_t asciichat_crc32_sw(const void *data, size_t len)
Definition crc32.c:171
ascii-chat Client Display Management Interface
void fps_frame_ns(fps_t *tracker, uint64_t current_time_ns, const char *context)
Definition fps.c:52
void fps_init(fps_t *tracker, int expected_fps, const char *name)
Definition fps.c:32
void audio_process_received_samples(const float *samples, int num_samples)
Process received audio samples from server.
void audio_stop_thread()
Stop audio capture thread.
int audio_decode_opus(const uint8_t *opus_data, size_t opus_len, float *output, int max_samples)
Decode Opus packet using the audio pipeline.
int audio_start_thread()
Start audio capture thread.
int capture_start_thread()
Start capture thread.
void capture_stop_thread()
Stop capture thread.
int threaded_send_pong_packet(void)
Thread-safe pong packet transmission.
bool server_connection_is_active()
Check if server connection is currently active.
void server_connection_shutdown()
Emergency connection shutdown for signal handlers.
asciichat_error_t threaded_send_terminal_size_with_auto_detect(unsigned short width, unsigned short height)
Thread-safe terminal size packet transmission with auto-detection.
acip_transport_t * server_connection_get_transport(void)
Get ACIP transport instance.
bool server_connection_is_lost()
Check if connection loss has been detected.
asciichat_error_t threaded_send_stream_start_packet(uint32_t stream_type)
Thread-safe stream start packet transmission.
void server_connection_lost()
Signal that connection has been lost.
crypto_handshake_context_t g_crypto_ctx
Global crypto handshake context for this client connection.
const crypto_context_t * crypto_client_get_context(void)
Get crypto context for encryption/decryption.
int crypto_client_send_rekey_response(void)
Send REKEY_RESPONSE packet to server.
int crypto_client_decrypt_packet(const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext, size_t plaintext_size, size_t *plaintext_len)
Decrypt a received packet.
bool crypto_client_is_ready(void)
Check if crypto handshake is ready.
int crypto_client_process_rekey_request(const uint8_t *packet, size_t packet_len)
Process received REKEY_REQUEST packet from server.
int crypto_client_process_rekey_response(const uint8_t *packet, size_t packet_len)
Process received REKEY_RESPONSE packet from server.
int crypto_client_send_rekey_complete(void)
Send REKEY_COMPLETE packet to server and commit to new key.
void display_render_frame(const char *frame_data)
Render ASCII frame to display.
void display_full_reset()
Perform full display reset.
void display_reset_for_new_connection()
Reset display state for new connection.
int keepalive_start_thread()
Start keepalive/ping thread.
Definition keepalive.c:233
void keepalive_stop_thread()
Stop keepalive/ping thread.
Definition keepalive.c:259
const acip_client_callbacks_t * protocol_get_acip_callbacks()
Get ACIP client callbacks for packet dispatch.
void protocol_stop_connection()
Stop protocol connection handling.
int protocol_start_connection()
Start protocol connection handling.
bool protocol_connection_lost()
Check if connection has been lost.
ascii-chat Client Connection Keepalive Management Interface
asciichat_error_t crypto_handshake_client_complete(crypto_handshake_context_t *ctx, acip_transport_t *transport, packet_type_t packet_type, const uint8_t *payload, size_t payload_len)
asciichat_error_t crypto_handshake_client_key_exchange(crypto_handshake_context_t *ctx, acip_transport_t *transport, packet_type_t packet_type, const uint8_t *payload, size_t payload_len)
asciichat_error_t crypto_handshake_client_auth_response(crypto_handshake_context_t *ctx, acip_transport_t *transport, packet_type_t packet_type, const uint8_t *payload, size_t payload_len)
asciichat_error_t acip_client_receive_and_dispatch(acip_transport_t *transport, const acip_client_callbacks_t *callbacks)
void log_msg(log_level_t level, const char *file, int line, const char *func, const char *fmt,...)
void log_set_terminal_output(bool enabled)
void signal_exit(void)
Definition main.c:94
action_args_t args
asciichat_error_t packet_parse_remote_log(const void *data, size_t len, log_level_t *out_level, remote_log_direction_t *out_direction, uint16_t *out_flags, char *message_buffer, size_t message_buffer_size, size_t *out_message_length)
Definition packet.c:950
asciichat_error_t packet_parse_error_message(const void *data, size_t len, asciichat_error_t *out_error_code, char *message_buffer, size_t message_buffer_size, size_t *out_message_length)
Definition packet.c:851
asciichat_error_t packet_parse_opus_batch(const void *packet_data, size_t packet_len, const uint8_t **out_opus_data, size_t *out_opus_size, const uint16_t **out_frame_sizes, int *out_sample_rate, int *out_frame_duration, int *out_frame_count)
char * packet_decode_frame_data_malloc(const char *frame_data_ptr, size_t frame_data_len, bool is_compressed, uint32_t original_size, uint32_t compressed_size)
asciichat_error_t webrtc_peer_manager_handle_ice(webrtc_peer_manager_t *manager, const acip_webrtc_ice_t *ice)
asciichat_error_t webrtc_peer_manager_handle_sdp(webrtc_peer_manager_t *manager, const acip_webrtc_sdp_t *sdp)
void platform_sleep_us(unsigned int us)
Server cryptographic operations and per-client handshake management.
ascii-chat Server Mode Entry Point Header
Server packet processing and protocol implementation.
ascii-chat Client Audio Processing Management Interface
Remote client information structure for multi-user client tracking.
time_t last_seen
Timestamp when client was last seen (for timeout detection)
uint32_t client_id
Unique client identifier assigned by server.
bool is_active
Whether client is currently active (sending video/audio)
int safe_vsnprintf(char *buffer, size_t buffer_size, const char *format, va_list ap)
Safe formatted string printing with va_list.
Definition system.c:507
asciichat_error_t thread_pool_spawn(thread_pool_t *pool, void *(*thread_func)(void *), void *thread_arg, int stop_id, const char *thread_name)
Definition thread_pool.c:70
asciichat_error_t thread_pool_stop_all(thread_pool_t *pool)
uint64_t time_get_ns(void)
Definition util/time.c:48
int format_duration_s(double seconds, char *buffer, size_t buffer_size)
Definition util/time.c:363
uint64_t time_elapsed_ns(uint64_t start_ns, uint64_t end_ns)
Definition util/time.c:90