ascii-chat 0.11.33
Video chat in your terminal
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"
85#include "keepalive.h"
87
97#include <ascii-chat/common.h>
104#include <ascii-chat/options/rcu.h> // For RCU-based options access
106#include <ascii-chat/util/fps.h>
107#include <ascii-chat/util/time.h>
110
111// Forward declaration for client crypto functions
112bool crypto_client_is_ready(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 <ascii-chat/atomic.h>
122#include <string.h>
123#include <time.h>
124#include <stdarg.h>
125#include <stdio.h>
126#include <unistd.h>
127
128#ifdef _WIN32
130#endif
131
134
135#include <errno.h>
136
137/* ============================================================================
138 * Thread State Management
139 * ============================================================================ */
140
158static bool g_data_thread_created = false;
159
168static atomic_t g_data_thread_exited = {0};
169
170/* ============================================================================
171 * Frame Rendering Statistics
172 * ============================================================================ */
173
183static atomic_t g_frames_rendered = {0};
184
185// Snapshot delay tracking: time when first frame was rendered in snapshot mode
186// Used to check if snapshot_delay has elapsed when connection is lost
187// Non-static so it can be accessed from main.c for monitoring
189
190/* ============================================================================
191 * Multi-User Client State
192 * ============================================================================ */
193
223typedef struct {
227 char display_name[MAX_DISPLAY_NAME_LEN];
231 time_t last_seen;
233
243static uint32_t g_last_active_count = 0;
244
253static bool g_server_state_initialized = false;
254
264static bool g_should_clear_before_next_frame = false;
265
266/* ============================================================================
267 * Protocol Validation and Error Handling
268 * ============================================================================ */
269
282static void disconnect_server_for_bad_data(const char *format, ...) {
283 va_list args;
284 va_start(args, format);
285
286 char message[BUFFER_SIZE_SMALL];
287 safe_vsnprintf(message, sizeof(message), format, args);
288 va_end(args);
289
290 log_error("Server sent invalid data - disconnecting: %s", message);
291
292 // Close the server connection
295}
296
297/* ============================================================================
298 * Packet Handler Functions
299 * ============================================================================ */
300
318static char *decode_frame_data(const char *frame_data_ptr, size_t frame_data_len, bool is_compressed,
319 uint32_t original_size, uint32_t compressed_size) {
320 return packet_decode_frame_data_malloc(frame_data_ptr, frame_data_len, is_compressed, original_size, compressed_size);
321}
322
344static void handle_ascii_frame_packet(const void *data, size_t len) {
345 fflush(stdout);
346 static int frame_count = 0;
347 frame_count++;
348 if (frame_count == 1) {
349 }
350
351 if (should_exit()) {
352 return;
353 }
354
355 if (!data || len < sizeof(ascii_frame_packet_t)) {
356 disconnect_server_for_bad_data("ASCII_FRAME payload too small: %zu (min %zu)", len, sizeof(ascii_frame_packet_t));
357 return;
358 }
359
360 // FPS tracking for received ASCII frames using reusable tracker utility
361 static fps_t fps_tracker = {0};
362 static bool fps_tracker_initialized = false;
363
364 // Initialize FPS tracker on first frame
365 if (!fps_tracker_initialized) {
366 int fps = GET_OPTION(fps);
367 int expected_fps = fps > 0 ? ((fps > 144) ? 144 : fps) : DEFAULT_MAX_FPS;
368 fps_init(&fps_tracker, expected_fps, "ASCII_RX");
369 fps_tracker_initialized = true;
370 }
371
372 // Track this frame and detect lag
373 fps_frame_ns(&fps_tracker, time_get_ns(), "ASCII frame");
374
375 // Extract header from the packet
377 memcpy(&header, data, sizeof(ascii_frame_packet_t));
378
379 // Convert from network byte order
380 header.width = NET_TO_HOST_U32(header.width);
381 header.height = NET_TO_HOST_U32(header.height);
384 header.checksum = NET_TO_HOST_U32(header.checksum);
385 header.flags = NET_TO_HOST_U32(header.flags);
386
387 // Get the frame data (starts after the header)
388 const char *frame_data_ptr = (const char *)data + sizeof(ascii_frame_packet_t);
389 size_t frame_data_len = len - sizeof(ascii_frame_packet_t);
390
391 // Decode frame data (handles both compressed and uncompressed)
392 bool is_compressed = (header.flags & FRAME_FLAG_IS_COMPRESSED) && header.compressed_size > 0;
393 char *frame_data =
394 decode_frame_data(frame_data_ptr, frame_data_len, is_compressed, header.original_size, header.compressed_size);
395 if (!frame_data) {
396 return; // Error already logged by decode_frame_data
397 }
398
399 // Verify checksum
400 uint32_t actual_crc = asciichat_crc32(frame_data, header.original_size);
401 if (actual_crc != header.checksum) {
402 log_error("Frame checksum mismatch: got 0x%x, expected 0x%x (size=%u, first_bytes=%02x%02x%02x%02x)", actual_crc,
403 header.checksum, header.original_size, (unsigned char)frame_data[0], (unsigned char)frame_data[1],
404 (unsigned char)frame_data[2], (unsigned char)frame_data[3]);
405
406 // DEBUG: Try software CRC32 to compare
407 uint32_t sw_crc = asciichat_crc32_sw(frame_data, header.original_size);
408 log_error("Software CRC32: 0x%x (matches: %s)", sw_crc, (sw_crc == header.checksum) ? "YES" : "NO");
409
410 SAFE_FREE(frame_data);
411 return;
412 }
413
414 // Track frame dimension changes
415 static uint32_t last_width = 0;
416 static uint32_t last_height = 0;
417
418 if (header.width > 0 && header.height > 0) {
419 if (header.width != last_width || header.height != last_height) {
420 last_width = header.width;
421 last_height = header.height;
422 }
423 }
424
425 // Handle snapshot mode timing
426 bool take_snapshot = false;
427 if (GET_OPTION(snapshot_mode)) {
428 static uint64_t first_frame_time_ns = 0;
429 static bool first_frame_recorded = false;
430 static int snapshot_frame_count = 0;
431
432 snapshot_frame_count++;
433 log_debug("Snapshot frame %d received", snapshot_frame_count);
434
435 if (!first_frame_recorded) {
436 first_frame_time_ns = time_get_ns();
437 g_snapshot_first_frame_time_ns = first_frame_time_ns; // Also store globally for access from data_reception_thread
438 log_info("[SNAPSHOT_INIT] SET g_snapshot_first_frame_time_ns=%llu first_frame_time_ns=%llu",
439 (unsigned long long)g_snapshot_first_frame_time_ns, (unsigned long long)first_frame_time_ns);
440 first_frame_recorded = true;
441
442 if (GET_OPTION(snapshot_delay) == 0) {
443 log_debug("Snapshot captured immediately (delay=0)!");
444 take_snapshot = true;
445 signal_exit();
446 } else {
447 log_debug("Snapshot mode: first frame received, waiting %.2f seconds for webcam warmup...",
448 GET_OPTION(snapshot_delay));
449 }
450 } else {
451 uint64_t current_time_ns = time_get_ns();
452 double elapsed = time_ns_to_s(time_elapsed_ns(first_frame_time_ns, current_time_ns));
453 double snapshot_delay = GET_OPTION(snapshot_delay);
454
455 // Log every 0.5 seconds to debug timing
456 static uint64_t last_debug_log_ns = 0;
457 if (current_time_ns - last_debug_log_ns > 500 * 1000 * 1000) { // 500ms
458 log_info("SNAPSHOT_DELAY_CHECK: elapsed=%.3f target=%.3f first_frame_ns=%llu current_ns=%llu", elapsed,
459 snapshot_delay, (unsigned long long)first_frame_time_ns, (unsigned long long)current_time_ns);
460 last_debug_log_ns = current_time_ns;
461 }
462
463 if (elapsed >= snapshot_delay) {
464 char duration_str[32];
465 time_pretty((uint64_t)(elapsed * 1e9), -1, duration_str, sizeof(duration_str));
466 log_info("๐ŸŽฌ SNAPSHOT TIMEOUT FIRED: Snapshot captured after %s! (target=%.3f) AT handle_ascii_frame_packet",
467 duration_str, snapshot_delay);
468 take_snapshot = true;
469 signal_exit();
470 }
471 }
472 }
473
474 // Check if we need to clear console before rendering this frame
475 // IMPORTANT: We track if this is the first frame to ensure proper initialization
476 static bool first_frame_rendered = false;
477
478 if (!first_frame_rendered) {
479 // Always clear display and disable logging before rendering the first frame
480 // This ensures clean ASCII display regardless of packet arrival order
481 log_debug("First frame - clearing display and disabling terminal logging");
484 first_frame_rendered = true;
485 g_server_state_initialized = true; // Mark as initialized
486 g_should_clear_before_next_frame = false; // Clear any pending clear request
487 log_debug("CLIENT_DISPLAY: Display cleared, ready for ASCII frames");
488 } else if (g_should_clear_before_next_frame) {
489 // Subsequent clear request from server (e.g., after client list changes)
490 log_debug("CLIENT_DISPLAY: Clearing display for layout change");
493 g_should_clear_before_next_frame = false;
494 }
495
496 // Safety check before rendering
497 if (!frame_data || header.original_size == 0) {
498 log_error("Invalid frame data for rendering: frame_data=%p, size=%u", frame_data, header.original_size);
499 if (frame_data) {
500 SAFE_FREE(frame_data);
501 }
502 return;
503 }
504
505 // Client-side FPS limiting for rendering (display)
506 // Server may send at 144fps for high-refresh displays, but this client renders at its requested FPS
507 static uint64_t last_render_time_ns = 0;
508
509 // Don't limit frame rate in snapshot mode - always render the final frame
510 if (!take_snapshot) {
511 // Get the client's desired FPS (what we told the server we can display)
512 int fps = GET_OPTION(fps);
513 int client_display_fps = fps > 0 ? fps : DEFAULT_MAX_FPS;
514 uint64_t render_interval_us = US_PER_SEC_INT / (uint64_t)client_display_fps;
515
516 uint64_t render_time_ns = time_get_ns();
517 uint64_t render_elapsed_us = 0;
518
519 if (last_render_time_ns != 0) {
520 render_elapsed_us = time_ns_to_us(time_elapsed_ns(last_render_time_ns, render_time_ns));
521 }
522
523 // Skip rendering if not enough time has passed (frame rate limiting)
524 if (last_render_time_ns != 0) {
525 if (render_elapsed_us > 0 && render_elapsed_us < render_interval_us) {
526 // Drop this frame to maintain display FPS limit
527 log_info("[FRAME_DROPPED] ๐Ÿ“Š Skipping frame - %lu us elapsed < %lu us interval (want ~%d FPS)",
528 render_elapsed_us, render_interval_us, client_display_fps);
529 SAFE_FREE(frame_data);
530 return;
531 }
532 }
533
534 // Update last render time
535 last_render_time_ns = render_time_ns;
536 }
537
538 // TODO/FIXME: WebSocket FPS performance gap vs TCP
539 // Test commands to verify and compare:
540 // ./scripts/debug-fps.zsh --proto tcp # ~56 FPS (baseline) โœ“
541 // ./scripts/debug-fps.zsh --proto ws # ~49 FPS (gap: 7 FPS, 87% of TCP)
542 // ./scripts/debug-fps.zsh --proto wss # ~49 FPS (matches WS) โœ“
543 //
544 // Root cause: libwebsockets' buffering + service loop timing vs direct TCP socket.
545 // WebSocket is solid; optimization needed in frame dispatch in service thread.
546 //
547 // Investigation areas:
548 // - Optimize lws_service() loop frequency and timing in websocket/transport.c
549 // - Check if frame reassembly or buffering can be more aggressive
550 // - Profile service thread wakeup latency vs actual packet arrival
551 // - Consider frame batching or predictive dispatch in lws callbacks
552 // - Reduce latency between packet arrival and frame processing in service loop
553 //
554 // Success criteria: ws/wss FPS within 2% of TCP using --proto option in debug-fps.zsh
555
556 // Frame counting now handled in session_display_write_ascii() which is called
557 // by both client and host/discovery modes for consistent metrics
558
559 // Render ASCII art frame (display_render_frame will apply effects like --matrix)
560 log_debug("๐ŸŽฌ CALLING_DISPLAY_RENDER: frame_data=%p len=%zu, calling display_render_frame()", frame_data,
561 strlen(frame_data));
562 display_render_frame(frame_data);
563 log_debug("๐ŸŽฌ DISPLAY_RENDER_RETURNED: frame was rendered");
564
565 SAFE_FREE(frame_data);
566}
567
580static void handle_audio_packet(const void *data, size_t len) {
581 if (!data || len == 0) {
582 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid audio packet data");
583 return;
584 }
585
586 if (!GET_OPTION(audio_enabled)) {
587 log_warn_every(NS_PER_MS_INT, "Received audio packet but audio is disabled");
588 return;
589 }
590
591 int num_samples = (int)(len / sizeof(float));
592 if (num_samples > AUDIO_SAMPLES_PER_PACKET) {
593 log_warn("Audio packet too large: %d samples", num_samples);
594 return;
595 }
596
597 // Copy data to properly aligned buffer to avoid UndefinedBehaviorSanitizer errors
598 float samples[AUDIO_SAMPLES_PER_PACKET];
599 SAFE_MEMCPY(samples, sizeof(samples), data, len);
600
601 // Process audio through audio subsystem
602 audio_process_received_samples(samples, num_samples);
603
604#ifdef DEBUG_AUDIO
605 log_debug("Processed %d audio samples", num_samples);
606#endif
607}
608
620static void handle_audio_opus_packet(const void *data, size_t len) {
621 START_TIMER("audio_packet_total");
622
623 // Validate parameters
624 if (!data || len == 0) {
625 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid audio opus packet: data=%p, len=%zu", data, len);
626 return;
627 }
628
629 if (!GET_OPTION(audio_enabled)) {
630 log_warn_every(NS_PER_MS_INT, "Received opus audio packet but audio is disabled");
631 return;
632 }
633
634 // Data is raw Opus-encoded frame (no header parsing needed)
635 const uint8_t *opus_data = (const uint8_t *)data;
636
637 // Opus max frame size is 2880 samples (120ms @ 48kHz)
638 float samples[2880];
639
640 START_TIMER("opus_decode");
641 int decoded_samples = audio_decode_opus(opus_data, len, samples, 2880);
642 double decode_ns = STOP_TIMER("opus_decode");
643
644 if (decoded_samples <= 0) {
645 log_warn("Failed to decode Opus audio packet, decoded=%d", decoded_samples);
646 return;
647 }
648
649 // Track received packet for analysis
650 if (GET_OPTION(audio_analysis_enabled)) {
652 }
653
654 // Process decoded audio through audio subsystem
655 START_TIMER("process_samples");
656 audio_process_received_samples(samples, decoded_samples);
657 double process_ns = STOP_TIMER("process_samples");
658
659 double total_ns = STOP_TIMER("audio_packet_total");
660
661 static int timing_count = 0;
662 if (++timing_count % 100 == 0) {
663 char decode_str[32], process_str[32], total_str[32];
664 time_pretty((uint64_t)decode_ns, -1, decode_str, sizeof(decode_str));
665 time_pretty((uint64_t)process_ns, -1, process_str, sizeof(process_str));
666 time_pretty((uint64_t)total_ns, -1, total_str, sizeof(total_str));
667 log_debug("Audio packet timing #%d: decode=%s, process=%s, total=%s", timing_count, decode_str, process_str,
668 total_str);
669 }
670
671 log_debug_every(LOG_RATE_DEFAULT, "Processed Opus audio: %d decoded samples from %zu byte packet", decoded_samples,
672 len);
673}
674
694static void handle_audio_opus_batch_packet(const void *data, size_t len) {
695 // Validate parameters
696 if (!data || len == 0) {
697 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid opus batch packet: data=%p, len=%zu", data, len);
698 return;
699 }
700
701 // DEBUG: Log first 12 bytes of received Opus batch header
702 if (len >= 12) {
703 const uint8_t *bytes = (const uint8_t *)data;
704 log_info("OPUS_BATCH_RECV_DEBUG: first_bytes=[%02x %02x %02x %02x][%02x %02x %02x %02x][%02x %02x %02x %02x]",
705 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9],
706 bytes[10], bytes[11]);
707
708 // Parse header manually to check values
709 uint32_t sr, fd, fc;
710 memcpy(&sr, bytes, 4);
711 memcpy(&fd, bytes + 4, 4);
712 memcpy(&fc, bytes + 8, 4);
713 log_info("OPUS_BATCH_RECV_PARSED: sample_rate(net)=0x%08x frame_duration(net)=0x%08x frame_count(net)=0x%08x", sr,
714 fd, fc);
715 log_info("OPUS_BATCH_RECV_PARSED: sample_rate=%u frame_duration=%u frame_count=%u", NET_TO_HOST_U32(sr),
717 }
718
719 if (!GET_OPTION(audio_enabled)) {
720 log_warn_every(NS_PER_MS_INT, "Received opus batch packet but audio is disabled");
721 return;
722 }
723
724 // Parse batch header using av_receive_audio_opus_batch() for consistency
725 const uint8_t *opus_data = NULL;
726 size_t opus_size = 0;
727 const uint16_t *frame_sizes = NULL;
728 int sample_rate = 0;
729 int frame_duration = 0;
730 int frame_count = 0;
731
732 asciichat_error_t result = packet_parse_opus_batch(data, len, &opus_data, &opus_size, &frame_sizes, &sample_rate,
733 &frame_duration, &frame_count);
734
735 if (result != ASCIICHAT_OK) {
736 log_warn("Failed to parse Opus batch packet");
737 return;
738 }
739
740 if (frame_count <= 0 || frame_count > 256 || opus_size == 0) {
741 log_warn("Invalid Opus batch: frame_count=%d, opus_size=%zu", frame_count, opus_size);
742 return;
743 }
744
745 // Calculate samples per frame
746 int samples_per_frame = (sample_rate * frame_duration) / 1000;
747 if (samples_per_frame <= 0 || samples_per_frame > 2880) {
748 log_warn("Invalid Opus frame parameters: samples_per_frame=%d", samples_per_frame);
749 return;
750 }
751
752 // Allocate buffer for all decoded samples
753 size_t max_decoded_samples = (size_t)samples_per_frame * (size_t)frame_count;
754 float *all_samples = SAFE_MALLOC(max_decoded_samples * sizeof(float), float *);
755 if (!all_samples) {
756 SET_ERRNO(ERROR_MEMORY, "Failed to allocate memory for Opus batch decoding");
757 return;
758 }
759
760 // Decode each Opus frame using frame_sizes array
761 int total_decoded_samples = 0;
762 size_t opus_offset = 0;
763
764 for (int i = 0; i < frame_count; i++) {
765 // Get frame size (convert from network byte order)
766 size_t frame_size = (size_t)NET_TO_HOST_U16(frame_sizes[i]);
767
768 if (opus_offset + frame_size > opus_size) {
769 log_warn("Opus batch truncated at frame %d (offset=%zu, frame_size=%zu, total=%zu)", i, opus_offset, frame_size,
770 opus_size);
771 break;
772 }
773
774 // Decode frame through audio pipeline
775 float *frame_buffer = all_samples + total_decoded_samples;
776 int remaining_space = (int)(max_decoded_samples - (size_t)total_decoded_samples);
777 int decoded = audio_decode_opus(opus_data + opus_offset, frame_size, frame_buffer, remaining_space);
778
779 if (decoded <= 0) {
780 log_warn("Failed to decode Opus frame %d in batch, decoded=%d", i, decoded);
781 break;
782 }
783
784 total_decoded_samples += decoded;
785 opus_offset += frame_size;
786 }
787
788 if (total_decoded_samples > 0) {
789 // Track received packet for analysis
790 if (GET_OPTION(audio_analysis_enabled)) {
792 }
793
794 // Process decoded audio through audio subsystem
795 audio_process_received_samples(all_samples, total_decoded_samples);
796
797 log_debug_every(LOG_RATE_DEFAULT, "Processed Opus batch: %d decoded samples from %d frames", total_decoded_samples,
798 frame_count);
799 }
800
801 // Clean up
802 SAFE_FREE(all_samples);
803}
804
805static bool handle_error_message_packet(const void *data, size_t len) {
806 asciichat_error_t remote_error = ASCIICHAT_OK;
807 char message[MAX_ERROR_MESSAGE_LENGTH + 1] = {0};
808
809 asciichat_error_t parse_result = packet_parse_error_message(data, len, &remote_error, message, sizeof(message), NULL);
810 if (parse_result != ASCIICHAT_OK) {
811 log_error("Failed to parse error packet from server: %s", asciichat_error_string(parse_result));
812 return false;
813 }
814
815 log_error("Server reported error %d (%s): %s", remote_error, asciichat_error_string(remote_error), message);
816 log_warn("Server signaled protocol error; closing connection");
819 return true;
820}
821
822static void handle_remote_log_packet(const void *data, size_t len) {
823 log_level_t remote_level = LOG_INFO;
825 uint16_t flags = 0;
826 char message[MAX_REMOTE_LOG_MESSAGE_LENGTH + 1] = {0};
827
828 asciichat_error_t parse_result =
829 packet_parse_remote_log(data, len, &remote_level, &direction, &flags, message, sizeof(message), NULL);
830 if (parse_result != ASCIICHAT_OK) {
831 log_error("Failed to parse remote log packet from server: %s", asciichat_error_string(parse_result));
832 return;
833 }
834
835 if (direction != REMOTE_LOG_DIRECTION_SERVER_TO_CLIENT) {
836 log_error("Remote log packet direction mismatch (direction=%u)", direction);
837 return;
838 }
839
840 bool truncated = (flags & REMOTE_LOG_FLAG_TRUNCATED) != 0;
841
842 if (truncated) {
843 log_msg(remote_level, __FILE__, __LINE__, __func__, "[REMOTE SERVER] %s [message truncated]", message);
844 } else {
845 log_msg(remote_level, __FILE__, __LINE__, __func__, "[REMOTE SERVER] %s", message);
846 }
847}
848
861static void handle_server_state_packet(const void *data, size_t len) {
862 if (!data || len != sizeof(server_state_packet_t)) {
863 log_error("Invalid server state packet size: %zu", len);
864 return;
865 }
866
867 const server_state_packet_t *state = (const server_state_packet_t *)data;
868
869 // Convert from network byte order
870 uint32_t active_count = NET_TO_HOST_U32(state->active_client_count);
871
872 // Check if connected count changed - if so, set flag to clear console before next frame
873 if (g_server_state_initialized) {
874 if (g_last_active_count != active_count) {
875 log_debug("Active client count changed from %u to %u - will clear console before next frame", g_last_active_count,
876 active_count);
877 g_should_clear_before_next_frame = true;
878 }
879 } else {
880 // First state packet received
881 g_server_state_initialized = true;
882 // Clear terminal before the very first frame
883 g_should_clear_before_next_frame = true;
884 }
885
886 g_last_active_count = active_count;
887}
888
889/* ============================================================================
890 * ACIP Callback Forward Declarations and Structure
891 * ============================================================================ */
892
893// Forward declarations for ACIP callbacks (implemented after this section)
894static void acip_on_ascii_frame(const ascii_frame_packet_t *header, const void *frame_data, size_t data_len, void *ctx);
895static void acip_on_audio(const void *audio_data, size_t audio_len, void *ctx);
896static void acip_on_audio_batch(const audio_batch_packet_t *header, const float *samples, size_t num_samples,
897 void *ctx);
898static void acip_on_audio_opus(const void *opus_data, size_t opus_len, void *ctx);
899static void acip_on_audio_opus_batch(const void *batch_data, size_t batch_len, void *ctx);
900static void acip_on_server_state(const server_state_packet_t *state, void *ctx);
901static void acip_on_error(const error_packet_t *header, const char *message, void *ctx);
902static void acip_on_remote_log(const remote_log_packet_t *header, const char *message, void *ctx);
903static void acip_on_ping(void *ctx);
904static void acip_on_pong(void *ctx);
905static void acip_on_clear_console(void *ctx);
906static void acip_on_crypto_rekey_request(const void *payload, size_t payload_len, void *ctx);
907static void acip_on_crypto_rekey_response(const void *payload, size_t payload_len, void *ctx);
908static void acip_on_webrtc_sdp(const acip_webrtc_sdp_t *sdp, size_t total_len, void *ctx);
909static void acip_on_webrtc_ice(const acip_webrtc_ice_t *ice, size_t total_len, void *ctx);
910static void acip_on_session_joined(const acip_session_joined_t *joined, void *ctx);
911static void acip_on_crypto_key_exchange_init(packet_type_t type, const void *payload, size_t payload_len, void *ctx);
912static void acip_on_crypto_auth_challenge(packet_type_t type, const void *payload, size_t payload_len, void *ctx);
913static void acip_on_crypto_server_auth_resp(packet_type_t type, const void *payload, size_t payload_len, void *ctx);
914static void acip_on_crypto_auth_failed(packet_type_t type, const void *payload, size_t payload_len, void *ctx);
915static void acip_on_crypto_handshake_complete(packet_type_t type, const void *payload, size_t payload_len, void *ctx);
916static void acip_on_crypto_parameters(packet_type_t type, const void *payload, size_t payload_len, void *ctx);
917
924static const acip_client_callbacks_t g_acip_client_callbacks = {
925 .on_ascii_frame = acip_on_ascii_frame,
926 .on_audio = acip_on_audio,
927 .on_audio_batch = acip_on_audio_batch,
928 .on_audio_opus = acip_on_audio_opus,
929 .on_audio_opus_batch = acip_on_audio_opus_batch,
930 .on_server_state = acip_on_server_state,
931 .on_error = acip_on_error,
932 .on_remote_log = acip_on_remote_log,
933 .on_ping = acip_on_ping,
934 .on_pong = acip_on_pong,
935 .on_clear_console = acip_on_clear_console,
936 .on_crypto_rekey_request = acip_on_crypto_rekey_request,
937 .on_crypto_rekey_response = acip_on_crypto_rekey_response,
938 .on_webrtc_sdp = acip_on_webrtc_sdp,
939 .on_webrtc_ice = acip_on_webrtc_ice,
940 .on_session_joined = acip_on_session_joined,
941 .on_crypto_key_exchange_init = acip_on_crypto_key_exchange_init,
942 .on_crypto_auth_challenge = acip_on_crypto_auth_challenge,
943 .on_crypto_server_auth_resp = acip_on_crypto_server_auth_resp,
944 .on_crypto_auth_failed = acip_on_crypto_auth_failed,
945 .on_crypto_handshake_complete = acip_on_crypto_handshake_complete,
946 .on_crypto_parameters = acip_on_crypto_parameters,
947 .app_ctx = NULL};
948
958 return &g_acip_client_callbacks;
959}
960
961/* ============================================================================
962 * Data Reception Thread
963 * ============================================================================ */
964
991static void *data_reception_thread_func(void *arg) {
992 (void)arg;
993
994 log_warn("[FRAME_RECV_LOOP] ๐Ÿ”„ THREAD_STARTED: Data reception thread active, callbacks initialized");
995
996#ifdef DEBUG_THREADS
997 log_debug("[FRAME_RECV_LOOP] Thread lifecycle tracking enabled");
998#endif
999
1000 int packet_count = 0;
1002 // Main loop: receive and process packets while connection is active
1003 // When connection becomes inactive or shutdown is requested, thread exits cleanly
1004
1005 // Debug: periodically check loop condition
1006 if (packet_count % 50 == 0) {
1007 bool exit_flag = should_exit();
1008 bool conn_active = server_connection_is_active();
1009 if (packet_count > 0) {
1010 log_debug("[FRAME_RECV_LOOP] DEBUG: packet_count=%d, should_exit=%d, server_connection_is_active=%d",
1011 packet_count, exit_flag, conn_active);
1012 }
1013 }
1014
1015 // Receive and dispatch packet using ACIP transport API
1016 // This combines packet reception, decryption, parsing, handler dispatch, and cleanup
1018 if (packet_count < 3) {
1019 }
1020 if (!transport) {
1021 log_error("[FRAME_RECV_LOOP] โŒ NO_TRANSPORT: connection lost, transport not available");
1023 break;
1024 }
1025
1026 log_debug("[FRAME_RECV_LOOP] ๐Ÿ“ฅ RECV_WAITING: awaiting packet #%d from server (transport ready)", packet_count + 1);
1027
1028 if (packet_count == 0) {
1029 }
1030 // Use short 16ms timeout to allow rendering at 60 FPS
1031 // Instead of blocking indefinitely waiting for a packet,
1032 // we timeout quickly so main thread can continue rendering
1033 asciichat_error_t acip_result = acip_client_receive_and_dispatch(transport, &g_acip_client_callbacks);
1034 if (packet_count == 0) {
1035 }
1036
1037 if (acip_result == ASCIICHAT_OK) {
1038 packet_count++;
1039 log_debug("[FRAME_RECV_LOOP] โœ… PACKET_%d_DISPATCHED: callbacks processed successfully", packet_count);
1040 } else if (acip_result == ERROR_NETWORK_TIMEOUT) {
1041 // Network timeouts are expected - socket may have no data available
1042 // Continue looping to allow snapshot timer to expire
1043 log_debug("[FRAME_RECV_LOOP] โฑ๏ธ TIMEOUT: No data available, retrying (packets received: %d)", packet_count);
1044 continue;
1045 } else {
1046 // Handle receive/dispatch errors - ALWAYS exit on network errors
1047 // NOTE: Log to file even if terminal logging is disabled (important for debugging)
1048 log_warn("[FRAME_RECV_LOOP] โš ๏ธ RECV_ERROR_BEFORE_SNAPSHOT_CHECK: acip_result=%d: %s (after %d packets received)",
1049 acip_result, asciichat_error_string(acip_result), packet_count);
1050
1051 // Before breaking due to error, check if snapshot_delay timer has fired in snapshot mode
1052 bool should_break_now = true;
1053 if (GET_OPTION(snapshot_mode) && g_snapshot_first_frame_time_ns > 0) {
1054 uint64_t current_time_ns = time_get_ns();
1055 double elapsed = time_ns_to_s(time_elapsed_ns(g_snapshot_first_frame_time_ns, current_time_ns));
1056 double snapshot_delay = GET_OPTION(snapshot_delay);
1057
1058 if (elapsed >= snapshot_delay) {
1059 char duration_str[32];
1060 time_pretty((uint64_t)(elapsed * 1e9), -1, duration_str, sizeof(duration_str));
1061 log_info("๐ŸŽฌ SNAPSHOT TIMEOUT FIRED (ON_ERROR): Snapshot captured after %s! (target=%.3f)", duration_str,
1062 snapshot_delay);
1063 signal_exit();
1064 should_break_now = false; // Don't disconnect, let normal shutdown handle it
1065 }
1066 }
1067
1068 if (should_break_now) {
1069 // Network errors (ERROR_NETWORK, ERROR_NETWORK_PROTOCOL, etc) always disconnect
1070 if (acip_result == ERROR_NETWORK || acip_result == ERROR_NETWORK_PROTOCOL) {
1071 log_warn("[FRAME_RECV_LOOP] โš ๏ธ NETWORK_ERROR: Server disconnected after %d packets, exiting loop",
1072 packet_count);
1074 break;
1075 }
1076
1077 // Check errno context for additional error details
1079 if (HAS_ERRNO(&err_ctx)) {
1080 if (err_ctx.code == ERROR_CRYPTO) {
1081 // Security violation - exit immediately
1082 log_error("[FRAME_RECV_LOOP] โŒ SECURITY_VIOLATION: Server crypto policy violated - EXITING");
1083 log_error("SECURITY: This is a critical security violation - exiting immediately");
1084 exit(1);
1085 }
1086 }
1087
1088 // Other errors - still disconnect to prevent infinite loop
1089 log_error("[FRAME_RECV_LOOP] โŒ RECV_FAILED: packet #%d failed after %d packets, disconnecting",
1090 packet_count + 1, packet_count);
1092 break;
1093 }
1094 }
1095 }
1096
1097 // Log why the loop exited (DEBUG to avoid affecting timing)
1098 bool final_exit_flag = should_exit();
1099 bool final_conn_active = server_connection_is_active();
1100 log_debug("[FRAME_RECV_LOOP] โน๏ธ LOOP_EXIT: should_exit=%d server_connection_is_active=%d (packets_received=%d)",
1101 final_exit_flag, final_conn_active, packet_count);
1102
1103 // Before exiting due to connection loss, check if snapshot_delay timer has fired
1104 // If it has, take the snapshot and exit cleanly instead of abandoning the snapshot
1105 if (GET_OPTION(snapshot_mode)) {
1106 log_debug("[FRAME_RECV_LOOP] โฐ SNAPSHOT_CHECK_ON_EXIT: snapshot_mode enabled, g_snapshot_first_frame_time_ns=%llu",
1107 (unsigned long long)g_snapshot_first_frame_time_ns);
1108
1110 uint64_t current_time_ns = time_get_ns();
1111 double elapsed = time_ns_to_s(time_elapsed_ns(g_snapshot_first_frame_time_ns, current_time_ns));
1112 double snapshot_delay = GET_OPTION(snapshot_delay);
1113
1114 log_debug("[FRAME_RECV_LOOP] โฐ SNAPSHOT_CHECK_DETAILS: elapsed=%.3f target=%.3f", elapsed, snapshot_delay);
1115
1116 if (elapsed >= snapshot_delay) {
1117 char duration_str[32];
1118 time_pretty((uint64_t)(elapsed * 1e9), -1, duration_str, sizeof(duration_str));
1119 log_info("๐ŸŽฌ SNAPSHOT TIMEOUT FIRED (ON_CONNECTION_LOSS): Snapshot captured after %s! (target=%.3f)",
1120 duration_str, snapshot_delay);
1121 signal_exit();
1122 }
1123 }
1124 }
1125
1126 log_warn("[FRAME_RECV_LOOP] ๐Ÿ”ด THREAD_EXITING: received %d packets total, connection inactive or shutdown requested",
1127 packet_count);
1128
1129#ifdef DEBUG_THREADS
1130 log_debug("[FRAME_RECV_LOOP] Thread lifecycle tracking - exit");
1131#endif
1132
1133 atomic_store_bool(&g_data_thread_exited, true);
1134
1135 // Clean up thread-local error context before exit
1137
1138 log_warn("[FRAME_RECV_LOOP] โœ… THREAD_CLEANUP: error context destroyed, thread terminating");
1139 return NULL;
1140}
1141
1142/* ============================================================================
1143 * Public Interface Functions
1144 * ============================================================================ */
1145
1157 log_warn("[FRAME_RECV_INIT] ๐ŸŸข PROTOCOL_START: Starting client protocol initialization");
1158
1159 // Register protocol atomics with named debug registry
1160 static bool protocol_atomics_registered = false;
1161 if (!protocol_atomics_registered) {
1162 NAMED_REGISTER_ATOMIC(&g_data_thread_exited, "protocol_data_reception_thread_exit_confirmation", NULL);
1163 NAMED_REGISTER_ATOMIC(&g_frames_rendered, "protocol_frames_successfully_rendered", NULL);
1164 protocol_atomics_registered = true;
1165 }
1166
1167 // Reset protocol state for new connection
1168 g_server_state_initialized = false;
1169 g_last_active_count = 0;
1170 g_should_clear_before_next_frame = false;
1171
1172 log_info("[FRAME_RECV_INIT] โœ… STATE_RESET: server_initialized=false, active_count=0, clear_flag=false");
1173
1174 // Reset display state for new connection
1176
1177 // Check if we're on a WebSocket transport (crypto handshake is async via ACIP callbacks)
1178 // For WS/WSS, we must start data_reception FIRST so it can handle the crypto handshake,
1179 // then wait for crypto to complete before sending CAPABILITIES/STREAM_START.
1180 // For TCP, crypto is already done synchronously before this function is called.
1182 bool is_websocket = init_transport && acip_transport_get_type(init_transport) == ACIP_TRANSPORT_WEBSOCKET;
1183
1184 if (is_websocket) {
1185 // WebSocket: start data reception thread FIRST to handle async crypto handshake.
1186 // TCP does crypto synchronously in connection_attempt.c, but WS crypto flows
1187 // asynchronously via ACIP callbacks in the data_reception thread.
1188 log_debug("[FRAME_RECV_INIT] WS: Starting data_reception for async crypto handshake");
1189 atomic_store_bool(&g_data_thread_exited, false);
1190 if (thread_pool_spawn(g_client_worker_pool, data_reception_thread_func, NULL, 1, "data_reception") !=
1191 ASCIICHAT_OK) {
1192 log_error("[FRAME_RECV_INIT] Failed to start data reception thread");
1193 LOG_ERRNO_IF_SET("Data reception thread creation failed");
1194 return -1;
1195 }
1196 log_debug("[FRAME_RECV_INIT] WS: Data thread spawned, waiting for crypto handshake");
1197
1198 // Wait for crypto handshake to complete (CRYPTO_PARAMETERS โ†’ KEY_EXCHANGE โ†’ HANDSHAKE_COMPLETE)
1199 // Skip waiting if encryption is disabled (no_encrypt mode)
1200 if (!GET_OPTION(no_encrypt)) {
1201 const int MAX_CRYPTO_WAIT_MS = 10000; // 10 seconds
1202 const int POLL_INTERVAL_MS = 10;
1203 int waited_ms = 0;
1205 waited_ms < MAX_CRYPTO_WAIT_MS) {
1206 platform_sleep_us(POLL_INTERVAL_MS * 1000);
1207 waited_ms += POLL_INTERVAL_MS;
1208 if (waited_ms % 1000 == 0) {
1209 log_info("[FRAME_RECV_INIT] WS_CRYPTO: Waiting for handshake... (%d ms elapsed)", waited_ms);
1210 }
1211 }
1212
1213 if (!crypto_client_is_ready()) {
1215 log_error("[FRAME_RECV_INIT] WS: Connection lost during crypto handshake");
1216 } else {
1217 log_error("[FRAME_RECV_INIT] WS: Crypto handshake timed out after %d ms", MAX_CRYPTO_WAIT_MS);
1218 }
1219 return -1;
1220 }
1221 log_info("[FRAME_RECV_INIT] WS: Crypto handshake completed after %d ms", waited_ms);
1222 } else {
1223 log_info("[FRAME_RECV_INIT] WS: Encryption disabled, skipping handshake wait");
1224 }
1225 }
1226
1227 // Send CLIENT_CAPABILITIES packet (after crypto is ready for both TCP and WS)
1228 // Server expects this as the first packet after crypto handshake
1229 log_debug("[FRAME_RECV_INIT] Sending CLIENT_CAPABILITIES (terminal size)");
1232 if (cap_result != ASCIICHAT_OK) {
1233 log_error("[FRAME_RECV_INIT] Failed to send terminal size capabilities");
1234 return -1;
1235 }
1236
1237 // Send STREAM_START packet with combined stream types
1238 uint32_t stream_types = STREAM_TYPE_VIDEO; // Always have video
1239 if (GET_OPTION(audio_enabled)) {
1240 stream_types |= STREAM_TYPE_AUDIO; // Add audio if enabled
1241 }
1242 log_debug("[FRAME_RECV_INIT] Sending STREAM_START: types=0x%x (video%s)", stream_types,
1243 (stream_types & STREAM_TYPE_AUDIO) ? "+audio" : "");
1244 asciichat_error_t stream_result = threaded_send_stream_start_packet(stream_types);
1245 if (stream_result != ASCIICHAT_OK) {
1246 log_error("[FRAME_RECV_INIT] Failed to send stream start");
1247 return -1;
1248 }
1249
1250 // Start data reception thread (TCP only - WS already started it above for crypto)
1251 if (!is_websocket) {
1252 log_debug("[FRAME_RECV_INIT] TCP: Starting data reception thread");
1253 atomic_store_bool(&g_data_thread_exited, false);
1254 if (thread_pool_spawn(g_client_worker_pool, data_reception_thread_func, NULL, 1, "data_reception") !=
1255 ASCIICHAT_OK) {
1256 log_error("[FRAME_RECV_INIT] Failed to start data reception thread");
1257 LOG_ERRNO_IF_SET("Data reception thread creation failed");
1258 return -1;
1259 }
1260 }
1261
1262 // Start webcam capture thread
1263 log_debug("Starting webcam capture thread...");
1264 if (capture_start_thread() != 0) {
1265 log_error("Failed to start webcam capture thread");
1266 return -1;
1267 }
1268 log_debug("Webcam capture thread started successfully");
1269
1270 // Start local media audio only after the connection succeeds. Starting the
1271 // worker during client initialization would consume file audio while dialing.
1272 audio_context_t *audio_context = audio_get_context();
1273 if (audio_context && audio_context->initialized) {
1275 if (audio_start_duplex(audio_context) != ASCIICHAT_OK) {
1276 log_error("Failed to start full-duplex audio");
1277 return -1;
1278 }
1279 }
1280
1281 // Initialize audio sender thread BEFORE starting audio capture
1282 // This ensures sender is ready when capture thread starts queueing packets
1283 // Must happen after connection succeeds to prevent deadlock if connection fails
1285
1286 // Start audio capture thread if audio is enabled
1287 log_debug("Starting audio capture thread...");
1288 if (audio_start_thread() != 0) {
1289 log_error("Failed to start audio capture thread");
1290 return -1;
1291 }
1292 log_debug("Audio capture thread started successfully (or skipped if audio disabled)");
1293
1294 // Start keepalive/ping thread to prevent server timeout
1295 log_debug("Starting keepalive/ping thread...");
1296 if (keepalive_start_thread() != 0) {
1297 log_error("Failed to start keepalive/ping thread");
1298 return -1;
1299 }
1300 log_debug("Keepalive/ping thread started successfully");
1301
1302 g_data_thread_created = true;
1303 return 0;
1304}
1305
1315 // Log final frame count before shutting down (always log for diagnostic purposes)
1316 int final_frame_count = atomic_load_u64(&g_frames_rendered);
1317 log_info("๐Ÿ“Š CLIENT SESSION STATS: %d unique frames rendered during connection", final_frame_count);
1318
1319 log_debug("[PROTOCOL_STOP] 1. Starting protocol_stop_connection");
1320
1321 // Stop snapshot capture before flushing, then use the shared connection shutdown
1322 // to wake and join the audio sender and reception threads.
1323 if (GET_OPTION(snapshot_mode)) {
1324 log_debug("[PROTOCOL_STOP] Snapshot mode: stopping capture thread before flushing H.265 encoder");
1325 // The capture thread owns the encoder while it is producing frames. Join it
1326 // before flushing so shutdown cannot destroy encoder state underneath an
1327 // in-flight encode operation.
1329
1330 // Flush the encoder to output any buffered frames before exiting
1331 if (g_h265_encoder) {
1332 // Allocate buffer large enough for worst-case H.265 encoded frame
1333 // Using 320x240 (typical test resolution) * 2x multiplier + header
1334 size_t flush_buf_size = 5 + (320 * 240 * 2);
1335 uint8_t *flush_buf = SAFE_MALLOC(flush_buf_size, uint8_t *);
1336 size_t flush_size = flush_buf_size;
1337 asciichat_error_t flush_result = h265_encoder_flush(g_h265_encoder, flush_buf, &flush_size);
1338 if (flush_result == ASCIICHAT_OK && flush_size > 0) {
1339 log_info("[PROTOCOL_STOP] Flushed H.265 encoder: got %zu bytes of buffered frames", flush_size);
1340 // Send the flushed frame if we got one
1342 if (transport && server_connection_is_active()) {
1343 uint32_t width = 320, height = 240; // TODO: track actual dimensions
1344 log_debug("[PROTOCOL_STOP] Sending flushed frame: %ux%u, %zu bytes", width, height, flush_size);
1345 }
1346 }
1347 SAFE_FREE(flush_buf);
1348 }
1349 }
1350
1351 // Don't call signal_exit() here - that's for global shutdown only!
1352 // We just want to stop threads for this connection, not exit the entire client
1353
1354 // Shutdown the socket FIRST to interrupt any blocking network operations in worker threads
1355 // This must happen BEFORE audio_stop_thread() so audio sender thread unblocks from network send
1356 log_debug("[PROTOCOL_STOP] 2. About to call server_connection_shutdown");
1358 log_debug("[PROTOCOL_STOP] 3. server_connection_shutdown() returned");
1359
1360 // Signal audio sender thread to exit
1361 // Must happen after socket shutdown so any blocked network calls fail
1362 // Snapshot recordings also transmit audio and must stop the sender.
1363 log_debug("[PROTOCOL_STOP] 4. About to call audio_stop_thread");
1365 log_debug("[PROTOCOL_STOP] 5. audio_stop_thread() returned");
1366
1367 // Early return if data thread was never created (e.g., mirror mode)
1368 // In mirror mode, we only need to stop the audio sender (done above)
1369 if (!g_data_thread_created) {
1370 log_debug("[PROTOCOL_STOP] 6. Data thread not created, returning");
1371 return;
1372 }
1373
1374 // Stop keepalive/ping thread - it checks connection status and will exit
1375 log_debug("[PROTOCOL_STOP] 7. About to call keepalive_stop_thread");
1377 log_debug("[PROTOCOL_STOP] 8. keepalive_stop_thread() returned");
1378
1379 // Stop webcam capture thread
1380 log_debug("[PROTOCOL_STOP] 9. About to call capture_stop_thread");
1382 log_debug("[PROTOCOL_STOP] 10. capture_stop_thread() returned");
1383
1384 // Signal the audio sender while its queue synchronization objects are still valid.
1386
1387 // Wait for data reception thread to exit gracefully
1388 log_debug("[PROTOCOL_STOP] 11. Waiting for data thread to exit");
1389 // Thread checks should_exit() every read cycle (typically <1-5ms), so timeout can be much shorter
1390 int wait_count = 0;
1391 while (wait_count < 5 && !atomic_load_bool(&g_data_thread_exited)) {
1392 platform_sleep_us(10 * US_PER_MS_INT); // 10ms * 5 = 50ms max wait
1393 wait_count++;
1394 }
1395
1396 if (!atomic_load_bool(&g_data_thread_exited)) {
1397 log_warn("Data thread not responding after 50ms - will be joined by thread pool");
1398 }
1399 log_debug("[PROTOCOL_STOP] 12. Data thread wait complete");
1400
1401 // Join all threads in the client worker pool (in stop_id order)
1402 // This handles the data reception thread and (eventually) all other worker threads
1403 log_debug("[PROTOCOL_STOP] 13. About to call thread_pool_stop_all");
1406 if (result != ASCIICHAT_OK) {
1407 log_error("Failed to stop client worker threads");
1408 LOG_ERRNO_IF_SET("Thread pool stop failed");
1409 }
1410 }
1412 log_debug("[PROTOCOL_STOP] 14. thread_pool_stop_all() returned");
1413
1414 g_data_thread_created = false;
1415
1416#ifdef DEBUG_THREADS
1417 log_debug("Data reception thread stopped and joined by thread pool");
1418#endif
1419 log_debug("[PROTOCOL_STOP] 15. protocol_stop_connection complete");
1420}
1421
1430 return atomic_load_bool(&g_data_thread_exited) || server_connection_is_lost();
1431}
1432
1433/* ============================================================================
1434 * ACIP Callback Implementations
1435 * ============================================================================ */
1436
1442static void acip_on_ascii_frame(const ascii_frame_packet_t *header, const void *frame_data, size_t data_len,
1443 void *ctx) {
1444 (void)ctx;
1445
1446 log_info("[FRAME_RECV_CALLBACK] ๐ŸŽฌ FRAME_RECEIVED: width=%u, height=%u, data_len=%zu bytes, flags=0x%x",
1447 header->width, header->height, data_len, header->flags);
1448
1449 // Reconstruct full packet for existing handler (header + data)
1450 // IMPORTANT: header is already in HOST byte order from ACIP layer,
1451 // but handle_ascii_frame_packet() expects NETWORK byte order and does conversion.
1452 // So we need to convert back to network order before passing.
1453 size_t total_len = sizeof(*header) + data_len;
1454 log_debug("[FRAME_RECV_CALLBACK] ๐Ÿ“ฆ FRAME_SIZE: header=%zu + data=%zu = total=%zu", sizeof(*header), data_len,
1455 total_len);
1456
1457 uint8_t *packet = SAFE_MALLOC(total_len, uint8_t *);
1458 if (!packet) {
1459 log_error("[FRAME_RECV_CALLBACK] โŒ ALLOC_FAILED: cannot allocate %zu bytes for frame", total_len);
1460 return;
1461 }
1462
1463 log_debug("[FRAME_RECV_CALLBACK] โœ… BUFFER_ALLOCATED: packet=%p, capacity=%zu", (void *)packet, total_len);
1464
1465 // Convert header fields back to network byte order for handle_ascii_frame_packet()
1466 ascii_frame_packet_t net_header = *header;
1467 net_header.width = HOST_TO_NET_U32(header->width);
1468 net_header.height = HOST_TO_NET_U32(header->height);
1469 net_header.original_size = HOST_TO_NET_U32(header->original_size);
1470 net_header.compressed_size = HOST_TO_NET_U32(header->compressed_size);
1471 net_header.checksum = HOST_TO_NET_U32(header->checksum);
1472 net_header.flags = HOST_TO_NET_U32(header->flags);
1473
1474 log_debug("[FRAME_RECV_CALLBACK] ๐Ÿ”„ BYTE_ORDER_CONVERSION: converting header to network byte order");
1475
1476 memcpy(packet, &net_header, sizeof(net_header));
1477 memcpy(packet + sizeof(net_header), frame_data, data_len);
1478
1479 log_info("[FRAME_RECV_CALLBACK] ๐Ÿ“ฅ FRAME_DISPATCH: calling handle_ascii_frame_packet() with %zu bytes", total_len);
1480 handle_ascii_frame_packet(packet, total_len);
1481 log_info("[FRAME_RECV_CALLBACK] โœ… FRAME_DISPATCH_COMPLETE: frame processing finished");
1482
1483 SAFE_FREE(packet);
1484 log_debug("[FRAME_RECV_CALLBACK] ๐Ÿ—‘๏ธ BUFFER_FREED: frame callback cleanup complete");
1485}
1486
1490static void acip_on_audio_batch(const audio_batch_packet_t *header, const float *samples, size_t num_samples,
1491 void *ctx) {
1492 (void)ctx;
1493 (void)header;
1494
1495 if (!GET_OPTION(audio_enabled)) {
1496 return;
1497 }
1498
1499 // Process samples directly (already dequantized by ACIP handler)
1500 audio_process_received_samples((float *)samples, (int)num_samples);
1501
1502 if (GET_OPTION(audio_analysis_enabled)) {
1503 // Approximate packet size for analysis
1504 size_t approx_size = sizeof(*header) + (num_samples * sizeof(uint32_t));
1506 }
1507
1508 log_debug_every(LOG_RATE_DEFAULT, "Processed audio batch: %zu samples from server", num_samples);
1509}
1510
1514static void acip_on_audio_opus(const void *opus_data, size_t opus_len, void *ctx) {
1515 (void)ctx;
1516
1517 // Call existing handler directly
1518 handle_audio_opus_packet(opus_data, opus_len);
1519}
1520
1524static void acip_on_server_state(const server_state_packet_t *state, void *ctx) {
1525 (void)ctx;
1526
1527 // Call existing handler directly
1528 handle_server_state_packet(state, sizeof(*state));
1529}
1530
1534static void acip_on_error(const error_packet_t *header, const char *message, void *ctx) {
1535 (void)ctx;
1536
1537 // Reconstruct packet for existing handler
1538 size_t msg_len = message ? strlen(message) : 0;
1539 size_t total_len = sizeof(*header) + msg_len;
1540
1541 uint8_t *packet = SAFE_MALLOC(total_len, uint8_t *);
1542 if (!packet) {
1543 log_error("Failed to allocate buffer for error packet callback");
1544 return;
1545 }
1546
1547 memcpy(packet, header, sizeof(*header));
1548 if (msg_len > 0) {
1549 // NOLINTNEXTLINE(bugprone-not-null-terminated-result) - binary packet data
1550 memcpy(packet + sizeof(*header), message, msg_len);
1551 }
1552
1553 handle_error_message_packet(packet, total_len);
1554 SAFE_FREE(packet);
1555}
1556
1560static void acip_on_ping(void *ctx) {
1561 (void)ctx;
1562
1563 // Respond with PONG
1564 if (threaded_send_pong_packet() < 0) {
1565 log_error("Failed to send PONG response");
1566 }
1567}
1568
1572static void acip_on_audio(const void *audio_data, size_t audio_len, void *ctx) {
1573 (void)ctx;
1574
1575 // Call existing handler directly
1576 handle_audio_packet(audio_data, audio_len);
1577}
1578
1582static void acip_on_audio_opus_batch(const void *batch_data, size_t batch_len, void *ctx) {
1583 (void)ctx;
1584
1585 // Call existing handler directly
1586 handle_audio_opus_batch_packet(batch_data, batch_len);
1587}
1588
1592static void acip_on_remote_log(const remote_log_packet_t *header, const char *message, void *ctx) {
1593 (void)ctx;
1594
1595 // Reconstruct packet for existing handler
1596 size_t msg_len = strlen(message);
1597 size_t total_len = sizeof(*header) + msg_len;
1598
1599 uint8_t *packet = SAFE_MALLOC(total_len, uint8_t *);
1600 if (!packet) {
1601 log_error("Failed to allocate buffer for remote log callback");
1602 return;
1603 }
1604
1605 memcpy(packet, header, sizeof(*header));
1606 // NOLINTNEXTLINE(bugprone-not-null-terminated-result) - binary packet data, not string
1607 memcpy(packet + sizeof(*header), message, msg_len);
1608
1609 handle_remote_log_packet(packet, total_len);
1610 SAFE_FREE(packet);
1611}
1612
1616static void acip_on_pong(void *ctx) {
1617 (void)ctx;
1618 // Pong received - no action needed (server acknowledged our ping)
1619}
1620
1624static void acip_on_clear_console(void *ctx) {
1625 (void)ctx;
1626
1627 // Server requested console clear
1629 log_debug("Console cleared by server");
1630}
1631
1635static void acip_on_crypto_rekey_request(const void *payload, size_t payload_len, void *ctx) {
1636 (void)ctx;
1637
1638 // Process the server's rekey request
1639 asciichat_error_t crypto_result = crypto_client_process_rekey_request(payload, payload_len);
1640 if (crypto_result != ASCIICHAT_OK) {
1641 log_error("Failed to process REKEY_REQUEST: %d", crypto_result);
1642 return;
1643 }
1644
1645 // Send REKEY_RESPONSE
1646 crypto_result = crypto_client_send_rekey_response();
1647 if (crypto_result != ASCIICHAT_OK) {
1648 log_error("Failed to send REKEY_RESPONSE: %d", crypto_result);
1649 }
1650}
1651
1655static void acip_on_crypto_rekey_response(const void *payload, size_t payload_len, void *ctx) {
1656 (void)ctx;
1657
1658 // Process server's response
1659 asciichat_error_t crypto_result = crypto_client_process_rekey_response(payload, payload_len);
1660 if (crypto_result != ASCIICHAT_OK) {
1661 log_error("Failed to process REKEY_RESPONSE: %d", crypto_result);
1662 return;
1663 }
1664
1665 // Send REKEY_COMPLETE
1666 crypto_result = crypto_client_send_rekey_complete();
1667 if (crypto_result != ASCIICHAT_OK) {
1668 log_error("Failed to send REKEY_COMPLETE: %d", crypto_result);
1669 }
1670}
1671
1682static void acip_on_webrtc_sdp(const acip_webrtc_sdp_t *sdp, size_t total_len, void *ctx) {
1683 (void)ctx;
1684 (void)total_len; // Peer manager reads variable data via pointer arithmetic
1685
1686 // Check if WebRTC is initialized
1687 if (!g_peer_manager) {
1688 log_warn("Received WebRTC SDP but peer manager not initialized - ignoring");
1689 return;
1690 }
1691
1692 // Log SDP type for debugging
1693 const char *sdp_type_str = (sdp->sdp_type == 0) ? "offer" : "answer";
1694 log_debug("Received WebRTC SDP %s from participant (session_id=%.8s...)", sdp_type_str,
1695 (const char *)sdp->session_id);
1696
1697 // Handle SDP through peer manager (extracts variable data internally)
1699
1700 if (result != ASCIICHAT_OK) {
1701 log_error("Failed to handle WebRTC SDP: %s", asciichat_error_string(result));
1702 }
1703}
1704
1715static void acip_on_webrtc_ice(const acip_webrtc_ice_t *ice, size_t total_len, void *ctx) {
1716 (void)ctx;
1717 (void)total_len; // Peer manager reads variable data via pointer arithmetic
1718
1719 // Check if WebRTC is initialized
1720 if (!g_peer_manager) {
1721 log_warn("Received WebRTC ICE but peer manager not initialized - ignoring");
1722 return;
1723 }
1724
1725 log_debug("Received WebRTC ICE candidate from participant (session_id=%.8s...)", (const char *)ice->session_id);
1726
1727 // Handle ICE through peer manager (extracts variable data internally)
1729
1730 if (result != ASCIICHAT_OK) {
1731 log_error("Failed to handle WebRTC ICE: %s", asciichat_error_string(result));
1732 }
1733}
1734
1755static void acip_on_session_joined(const acip_session_joined_t *joined, void *ctx) {
1756 (void)ctx; // Unused for now, may be used in future for context passing
1757
1758 if (!joined) {
1759 log_error("SESSION_JOINED callback received NULL response");
1760 return;
1761 }
1762
1763 // Check if join was successful
1764 if (!joined->success) {
1765 log_error("ACDS session join failed: error %d: %s", joined->error_code, joined->error_message);
1766 // Connection will timeout waiting for SDP/WebRTC completion and fallback to next stage
1767 return;
1768 }
1769
1770 // Join succeeded - we have session context now
1771 log_debug("ACDS session join succeeded (participant_id=%.8s..., session_type=%s, server=%s:%u)",
1772 (const char *)joined->participant_id, joined->session_type == 1 ? "WebRTC" : "DirectTCP",
1773 joined->server_address, joined->server_port);
1774
1775 // Check if this is a WebRTC session
1776 if (joined->session_type == SESSION_TYPE_WEBRTC) {
1777 // TODO: Phase 3 - Initialize WebRTC connection with TURN credentials
1778 // webrtc_initialize_session(joined->session_id, joined->participant_id,
1779 // joined->turn_username, joined->turn_password);
1780 log_debug("WebRTC session detected - TODO: initialize WebRTC with TURN credentials");
1781 } else {
1782 // Direct TCP - connection is already established or will be established
1783 log_debug("Direct TCP session - using existing connection");
1784 }
1785}
1786
1800static void acip_on_crypto_key_exchange_init(packet_type_t type, const void *payload, size_t payload_len, void *ctx) {
1801 (void)ctx;
1802
1803 log_debug("Received CRYPTO_KEY_EXCHANGE_INIT from server");
1804
1806 if (!transport) {
1807 log_error("Cannot handle key exchange - no transport available");
1808 return;
1809 }
1810
1811 asciichat_error_t result =
1812 crypto_handshake_client_key_exchange(&g_crypto_ctx, transport, type, (const uint8_t *)payload, payload_len);
1813 if (result != ASCIICHAT_OK) {
1814 log_error("Crypto handshake key exchange failed");
1816 } else {
1817 log_debug("Sent CRYPTO_KEY_EXCHANGE_RESP to server");
1818 }
1819}
1820
1834static void acip_on_crypto_auth_challenge(packet_type_t type, const void *payload, size_t payload_len, void *ctx) {
1835 (void)ctx;
1836
1837 log_debug("Received CRYPTO_AUTH_CHALLENGE from server");
1838
1840 if (!transport) {
1841 log_error("Cannot handle auth challenge - no transport available");
1842 return;
1843 }
1844
1845 asciichat_error_t result =
1846 crypto_handshake_client_auth_response(&g_crypto_ctx, transport, type, (const uint8_t *)payload, payload_len);
1847 if (result != ASCIICHAT_OK) {
1848 log_error("Crypto handshake auth response failed");
1850 } else {
1851 log_debug("Sent CRYPTO_AUTH_RESPONSE to server");
1852 }
1853}
1854
1868static void acip_on_crypto_server_auth_resp(packet_type_t type, const void *payload, size_t payload_len, void *ctx) {
1869 (void)ctx;
1870
1871 log_debug("Received CRYPTO_SERVER_AUTH_RESP from server");
1872
1874 if (!transport) {
1875 log_error("Cannot handle server auth response - no transport available");
1876 return;
1877 }
1878
1879 asciichat_error_t result =
1880 crypto_handshake_client_complete(&g_crypto_ctx, transport, type, (const uint8_t *)payload, payload_len);
1881 if (result != ASCIICHAT_OK) {
1882 log_error("Crypto handshake verification failed");
1884 } else {
1885 log_info("Crypto handshake completed successfully (mutual auth)");
1886
1887 // Link crypto context to transport for automatic encryption
1888 transport->crypto_ctx = &g_crypto_ctx.crypto_ctx;
1889 }
1890}
1891
1905static void acip_on_crypto_auth_failed(packet_type_t type, const void *payload, size_t payload_len, void *ctx) {
1906 (void)ctx;
1907 (void)type;
1908
1909 // Extract error message if present
1910 char error_msg[256] = "Unknown error";
1911 if (payload && payload_len > 0) {
1912 size_t msg_len = payload_len < sizeof(error_msg) - 1 ? payload_len : sizeof(error_msg) - 1;
1913 memcpy(error_msg, payload, msg_len);
1914 error_msg[msg_len] = '\0';
1915 }
1916
1917 log_error("Server rejected authentication: %s", error_msg);
1918 log_error("Disconnecting - crypto handshake failed");
1919
1921}
1922
1936static void acip_on_crypto_handshake_complete(packet_type_t type, const void *payload, size_t payload_len, void *ctx) {
1937 (void)ctx;
1938
1939 log_debug("Received CRYPTO_HANDSHAKE_COMPLETE from server");
1940
1942 if (!transport) {
1943 log_error("Cannot complete handshake - no transport available");
1944 return;
1945 }
1946
1947 asciichat_error_t result =
1948 crypto_handshake_client_complete(&g_crypto_ctx, transport, type, (const uint8_t *)payload, payload_len);
1949 if (result != ASCIICHAT_OK) {
1950 log_error("Crypto handshake completion failed");
1952 } else {
1953 log_info("Crypto handshake completed successfully");
1954
1955 // Link crypto context to transport for automatic encryption
1956 transport->crypto_ctx = &g_crypto_ctx.crypto_ctx;
1957 }
1958}
1959
1967static void acip_on_crypto_parameters(packet_type_t type, const void *payload, size_t payload_len, void *ctx) {
1968 (void)ctx;
1969 (void)type;
1970
1971 log_debug("Received CRYPTO_PARAMETERS from server (payload_len=%zu)", payload_len);
1972
1973 if (payload_len != sizeof(crypto_parameters_packet_t)) {
1974 log_error("Invalid CRYPTO_PARAMETERS size: %zu (expected %zu)", payload_len, sizeof(crypto_parameters_packet_t));
1976 return;
1977 }
1978
1979 crypto_parameters_packet_t server_params;
1980 memcpy(&server_params, payload, sizeof(crypto_parameters_packet_t));
1981
1982 // Log parameters (fields are still in network byte order for set_parameters)
1983 uint16_t kex_pubkey_size = NET_TO_HOST_U16(server_params.kex_public_key_size);
1984 uint16_t auth_pubkey_size = NET_TO_HOST_U16(server_params.auth_public_key_size);
1985 uint16_t signature_size = NET_TO_HOST_U16(server_params.signature_size);
1986 uint16_t shared_secret_size = NET_TO_HOST_U16(server_params.shared_secret_size);
1987
1988 log_debug("Server crypto parameters: KEX=%u, Auth=%u, Cipher=%u (key_size=%u, auth_size=%u, sig_size=%u, "
1989 "secret_size=%u, verification=%u)",
1990 server_params.selected_kex, server_params.selected_auth, server_params.selected_cipher, kex_pubkey_size,
1991 auth_pubkey_size, signature_size, shared_secret_size, server_params.verification_enabled);
1992
1993 // Set the crypto parameters in the handshake context so KEY_EXCHANGE_INIT knows sizes
1995 if (result != ASCIICHAT_OK) {
1996 log_error("Failed to set crypto parameters: %d", result);
1998 return;
1999 }
2000
2001 // Store verification flag
2002 if (server_params.verification_enabled) {
2005 log_info("Server will verify client identity (whitelist enabled)");
2006 }
2007
2008 log_info("CRYPTO_PARAMETERS processed - ready for KEY_EXCHANGE_INIT");
2009}
ascii-chat Discovery Service (ACDS) Protocol Message Formats
void audio_analysis_track_received_packet(size_t size)
Track received packet.
Definition analysis.c:469
Audio Analysis and Debugging Interface.
#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
uint64_t atomic_load_u64(atomic_t *a)
Atomically load a uint64_t value.
Definition atomic.c:233
โš›๏ธ Atomic operations abstraction layer with debug tracking
๐Ÿ—ƒ๏ธ Lock-Free Unified Memory Buffer Pool with Lazy Allocation
Common buffer size definitions.
void * capture_get_media_source(void)
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.
h265_encoder_t *volatile g_h265_encoder
Global H.265 encoder context for client video streaming.
uint64_t g_snapshot_first_frame_time_ns
๐Ÿ“น Unified media capture abstraction for session-based video sources
โš™๏ธ Common definitions, error codes, macros, and types shared throughout the application
asciichat_error_t h265_encoder_flush(h265_encoder_t *encoder, uint8_t *out_data, size_t *out_len)
asciichat_error_t thread_pool_stop_all(void *pool)
Stop all threads in the pool in stop_id order.
asciichat_error_t thread_pool_spawn(void *pool, void *(*thread_func)(void *), void *thread_arg, int stop_id, const char *name)
Spawn a worker thread in the pool.
๐Ÿ“ฆ Network Packet Compression Utilities
Hardware-Accelerated CRC32 Checksum Computation.
asciichat_error_t crypto_handshake_set_parameters(crypto_handshake_context_t *ctx, const crypto_parameters_packet_t *params)
Set crypto parameters from crypto_parameters_packet_t.
Named object registry for debugging โ€” log identifiable resource names.
๐Ÿ”„ Network byte order conversion helpers
#define HOST_TO_NET_U32(val)
Definition endian.h:66
#define NET_TO_HOST_U16(val)
Definition endian.h:111
#define NET_TO_HOST_U32(val)
Definition endian.h:81
Cross-platform error handling utilities.
โฑ๏ธ FPS tracking utility for monitoring frame throughput across all threads
acip_session_joined_t
Definition acds.h:474
acip_webrtc_ice_t
Definition acds.h:650
acip_webrtc_sdp_t
Definition acds.h:604
@ SESSION_TYPE_WEBRTC
WebRTC P2P mesh with STUN/TURN relay.
Definition acds.h:146
asciichat_error_t audio_start_duplex(audio_context_t *ctx)
Start full-duplex audio (simultaneous capture and playback)
#define BUFFER_SIZE_SMALL
Small buffer size (256 bytes)
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.
void audio_sender_init(void)
Initialize async audio sender queue and 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.
audio_context_t * audio_get_context(void)
Get the global audio context for use by other subsystems.
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:236
void keepalive_stop_thread()
Stop keepalive/ping thread.
Definition keepalive.c:269
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.
unsigned short uint16_t
Definition common.h:57
unsigned int uint32_t
Definition common.h:58
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
void fps_frame_ns(fps_t *tracker, uint64_t current_time_ns, const char *context)
Track a frame and detect lag conditions (nanosecond version - PRIMARY)
Definition fps.c:52
unsigned long long uint64_t
Definition common.h:59
unsigned char uint8_t
Definition common.h:56
#define SAFE_MEMCPY(dest, dest_size, src, count)
Definition common.h:468
void fps_init(fps_t *tracker, int expected_fps, const char *name)
Initialize FPS tracker.
Definition fps.c:32
#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.
#define HAS_ERRNO(var)
Check if an error occurred and get full context.
void asciichat_errno_destroy(void)
Cleanup error system resources.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_NETWORK
Definition error_codes.h:77
@ ERROR_NETWORK_PROTOCOL
Definition error_codes.h:81
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_CRYPTO
Definition error_codes.h:96
@ ERROR_INVALID_PARAM
@ ERROR_NETWORK_TIMEOUT
Definition error_codes.h:80
#define DEFAULT_MAX_FPS
Default maximum frame rate (frames per second)
Definition limits.h:29
#define MAX_DISPLAY_NAME_LEN
Maximum display name length in characters.
Definition limits.h:20
#define LOG_RATE_DEFAULT
Log rate limit: 5 seconds (5,000,000 microseconds) - default for audio/video packets.
Definition log_rates.h:32
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
void log_msg(log_level_t level, const char *file, int line, const char *func, const char *fmt,...)
Log a message at a specific level.
Definition log/log.c:1019
enum remote_log_direction remote_log_direction_t
Remote log packet direction enumeration.
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
log_level_t
Logging levels enumeration.
Definition types.h:29
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
void log_set_terminal_output(bool enabled)
Control stderr output to terminal.
Definition log/log.c:700
@ REMOTE_LOG_DIRECTION_SERVER_TO_CLIENT
Definition network/log.h:22
@ REMOTE_LOG_DIRECTION_UNKNOWN
Definition network/log.h:21
uint64_t time_get_ns(void)
Get current monotonic time in nanoseconds.
Definition util/time.c:108
int time_pretty(uint64_t nanoseconds, int decimals, char *buffer, size_t buffer_size)
Format nanoseconds as pretty duration with spaces and configurable precision.
Definition util/time.c:424
#define START_TIMER(name_fmt,...)
Start a timer with formatted name.
Definition time.h:333
#define NS_PER_MS_INT
Definition time.h:156
uint64_t time_elapsed_ns(uint64_t start_ns, uint64_t end_ns)
Calculate elapsed time with wraparound safety.
Definition util/time.c:150
#define US_PER_MS_INT
Definition time.h:160
#define STOP_TIMER(name_fmt,...)
Stop a timer with formatted name and return elapsed time.
Definition time.h:403
#define US_PER_SEC_INT
Definition time.h:161
#define STREAM_TYPE_VIDEO
Video stream.
Definition packet.h:937
#define STREAM_TYPE_AUDIO
Audio stream.
Definition packet.h:938
#define REMOTE_LOG_FLAG_TRUNCATED
Remote log packet flag definitions.
Definition packet.h:736
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
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)
Parse Opus audio batch packet header and extract frame data.
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)
Decode frame data (malloc version for client handlers)
#define AUDIO_SAMPLES_PER_PACKET
Samples per audio packet (256 samples)
Definition packet.h:235
packet_type_t
Network protocol packet type enumeration.
Definition packet.h:286
#define MAX_REMOTE_LOG_MESSAGE_LENGTH
Maximum remote log message length (512 bytes)
Definition packet.h:134
#define MAX_ERROR_MESSAGE_LENGTH
Maximum error message length (512 bytes)
Definition packet.h:124
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:199
void platform_sleep_us(unsigned int us)
High-precision sleep function with microsecond precision.
#define FRAME_FLAG_IS_COMPRESSED
Frame data is compressed.
uint32_t asciichat_crc32_sw(const void *data, size_t len)
Compute CRC32 checksum using software implementation only.
Definition crc32.c:171
#define asciichat_crc32(data, len)
Main CRC32 dispatcher macro - use this in application code.
Definition crc32.h:144
asciichat_error_t webrtc_peer_manager_handle_ice(webrtc_peer_manager_t *manager, const acip_webrtc_ice_t *ice)
Handle incoming ICE candidate from ACDS.
asciichat_error_t webrtc_peer_manager_handle_sdp(webrtc_peer_manager_t *manager, const acip_webrtc_sdp_t *sdp)
Handle incoming SDP message from ACDS.
ACIP protocol packet handlers (transport-agnostic)
๐Ÿ”Š Audio Capture and Playback Interface for ascii-chat
Client-side handshake functions.
ACIP client-side protocol API.
โš™๏ธ Unified options parsing system for ascii-chat with builder pattern and lock-free access
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)
Client: Wait for handshake complete confirmation.
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)
Client: Process server's public key and send our public key.
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)
Client: Process auth challenge and send response.
asciichat_error_t acip_client_receive_and_dispatch(acip_transport_t *transport, const acip_client_callbacks_t *callbacks)
Receive packet from server and dispatch to callbacks.
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
Definition log/log.h:702
#define log_warn_every(interval_us, fmt,...)
Rate-limited WARN logging.
Definition log/log.h:708
void signal_exit(void)
Definition misc.c:129
bool should_exit(void)
Definition misc.c:131
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)
Parse a remote log packet payload into components.
Definition packet.c:1056
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)
Parse an error packet payload into components.
Definition packet.c:957
unsigned short int terminal_get_effective_width(void)
Get effective terminal width with fallback priority.
unsigned short int terminal_get_effective_height(void)
Get effective terminal height with fallback priority.
ascii-chat Server Mode Entry Point Header
void audio_sender_cleanup(void)
Cleanup async audio sender.
void audio_sender_finalize(void)
๐Ÿ”Š Session-level audio coordination wrapper
๐Ÿ–ฅ๏ธ Unified terminal display abstraction for session-based rendering
Server cryptographic operations and per-client handshake management.
H.265 codec transport protocol definitions.
Server-side H.265 decoding pipeline for multi-client streams.
Client-side packet handler callbacks.
Definition handlers.h:51
void(* on_ascii_frame)(const ascii_frame_packet_t *header, const void *frame_data, size_t data_len, void *ctx)
Called when ASCII frame received from server.
Definition handlers.h:53
Transport instance structure.
Definition transport.h:214
crypto_context_t * crypto_ctx
Optional encryption context.
Definition transport.h:216
ASCII frame packet structure (Packet Type 2)
Definition packet.h:848
uint32_t width
Terminal width in characters.
Definition packet.h:850
uint32_t checksum
CRC32 checksum of original ASCII data.
Definition packet.h:858
uint32_t original_size
Size of original uncompressed ASCII data in bytes.
Definition packet.h:854
uint32_t height
Terminal height in characters.
Definition packet.h:852
uint32_t compressed_size
Size of compressed data (0 = not compressed)
Definition packet.h:856
uint32_t flags
Frame flags bitmask (HAS_COLOR, IS_COMPRESSED, etc.)
Definition packet.h:860
Error context structure.
asciichat_error_t code
Error code (asciichat_error_t enum value)
Atomic value wrapper for integral/boolean types.
Definition atomic.h:76
Audio batch packet structure (Packet Type 28)
Definition packet.h:904
Audio context for full-duplex capture and playback.
bool initialized
True if context has been initialized.
void * capture_media_source
Borrowed local media audio; configure before starting duplex.
Cryptographic context structure.
Crypto parameters packet structure (Packet Type 15)
Definition packet.h:981
uint8_t selected_kex
Selected key exchange algorithm (KEX_ALGO_*)
Definition packet.h:983
uint8_t selected_cipher
Selected cipher algorithm (CIPHER_ALGO_*)
Definition packet.h:987
uint16_t auth_public_key_size
Authentication public key size in bytes (e.g., 32 for Ed25519, 1952 for Dilithium3)
Definition packet.h:993
uint8_t selected_auth
Selected authentication algorithm (AUTH_ALGO_*)
Definition packet.h:985
uint8_t verification_enabled
Server verification enabled flag (1=enabled, 0=disabled)
Definition packet.h:989
uint16_t signature_size
Signature size in bytes (e.g., 64 for Ed25519, 3309 for Dilithium3)
Definition packet.h:995
uint16_t shared_secret_size
Shared secret size in bytes (e.g., 32 for X25519)
Definition packet.h:997
uint16_t kex_public_key_size
Key exchange public key size in bytes (e.g., 32 for X25519, 1568 for Kyber1024)
Definition packet.h:991
Error packet structure carrying error code and textual description.
Definition packet.h:727
FPS tracking state.
Definition fps.h:52
Frame buffer structure.
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)
Remote log packet structure carrying log level and message text.
Definition packet.h:741
Server state packet structure.
Definition packet.h:706
uint32_t active_client_count
Number of clients actively sending video/audio streams.
Definition packet.h:710
๐Ÿงต Generic thread pool abstraction for managing worker threads
โฑ๏ธ High-precision timing utilities using sokol_time.h and uthash
Transport abstraction layer for ACIP protocol.
@ ACIP_TRANSPORT_WEBSOCKET
WebSocket over TCP.
Definition transport.h:95
#define LOG_INFO
Definition types.h:40
๐Ÿ“Š String Formatting Utilities
Common validation macros to reduce duplication in protocol handlers.
Wrapper for windows.h with C23 alignment compatibility.