ascii-chat 0.6.0
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 "server.h"
79#include "display.h"
80#include "capture.h"
81#include "audio.h"
82#include "audio/audio.h"
83#include "audio/analysis.h"
84#include "keepalive.h"
85#include "thread_pool.h"
86
87#include "network/packet.h"
92#include "network/acip/client.h"
93#include "network/acip/acds.h"
95#include "buffer_pool.h"
96#include "common.h"
97#include "util/endian.h"
98#include "util/validation.h"
99#include "util/endian.h"
100#include "util/format.h"
101#include "options/options.h"
102#include "options/rcu.h" // For RCU-based options access
103#include "network/crc32.h"
104#include "util/fps.h"
105#include "crypto/crypto.h"
106
107// Forward declaration for client crypto functions
108bool crypto_client_is_ready(void);
110int crypto_client_decrypt_packet(const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext,
111 size_t plaintext_size, size_t *plaintext_len);
112
113#include "crypto.h"
114#include "util/time.h"
115
116#include <stdatomic.h>
117#include <string.h>
118#include <time.h>
119#include <stdarg.h>
120#include <stdio.h>
121
122#ifdef _WIN32
124#endif
125
126#include "network/compression.h"
128
129#include <errno.h>
130
131/* ============================================================================
132 * Thread State Management
133 * ============================================================================ */
134
143__attribute__((unused)) static asciichat_thread_t g_data_thread;
144
153static bool g_data_thread_created = false;
154
163static atomic_bool g_data_thread_exited = false;
164
165/* ============================================================================
166 * Multi-User Client State
167 * ============================================================================ */
168
198typedef struct {
200 uint32_t client_id;
202 char display_name[MAX_DISPLAY_NAME_LEN];
204 bool is_active;
206 time_t last_seen;
208
218static uint32_t g_last_active_count = 0;
219
228static bool g_server_state_initialized = false;
229
239static bool g_should_clear_before_next_frame = false;
240
241/* ============================================================================
242 * Protocol Validation and Error Handling
243 * ============================================================================ */
244
257static void disconnect_server_for_bad_data(const char *format, ...) {
258 va_list args;
259 va_start(args, format);
260
261 char message[256];
262 vsnprintf(message, sizeof(message), format, args);
263 va_end(args);
264
265 log_error("Server sent invalid data - disconnecting: %s", message);
266
267 // Close the server connection
270}
271
272/* ============================================================================
273 * Packet Handler Functions
274 * ============================================================================ */
275
293static char *decode_frame_data(const char *frame_data_ptr, size_t frame_data_len, bool is_compressed,
294 uint32_t original_size, uint32_t compressed_size) {
295 return packet_decode_frame_data_malloc(frame_data_ptr, frame_data_len, is_compressed, original_size, compressed_size);
296}
297
319static void handle_ascii_frame_packet(const void *data, size_t len) {
320 if (should_exit()) {
321 return;
322 }
323
324 if (!data || len < sizeof(ascii_frame_packet_t)) {
325 disconnect_server_for_bad_data("ASCII_FRAME payload too small: %zu (min %zu)", len, sizeof(ascii_frame_packet_t));
326 return;
327 }
328
329 // FPS tracking for received ASCII frames using reusable tracker utility
330 static fps_t fps_tracker = {0};
331 static bool fps_tracker_initialized = false;
332
333 // Initialize FPS tracker on first frame
334 if (!fps_tracker_initialized) {
335 extern int g_max_fps; // From common.c
336 int expected_fps = g_max_fps > 0 ? ((g_max_fps > 144) ? 144 : g_max_fps) : DEFAULT_MAX_FPS;
337 fps_init(&fps_tracker, expected_fps, "ASCII_RX");
338 fps_tracker_initialized = true;
339 }
340
341 struct timespec current_time;
342 (void)clock_gettime(CLOCK_MONOTONIC, &current_time);
343
344 // Track this frame and detect lag
345 fps_frame(&fps_tracker, &current_time, "ASCII frame");
346
347 // Extract header from the packet
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);
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 // Use high-resolution monotonic clock instead of time(NULL) to avoid 1-second precision issues
401 static struct timespec first_frame_time = {0};
402 static bool first_frame_recorded = false;
403 static int snapshot_frame_count = 0;
404
405 snapshot_frame_count++;
406 // DEBUG: Log every frame received (even when terminal output is disabled)
407 log_debug("Snapshot frame %d received", snapshot_frame_count);
408
409 if (!first_frame_recorded) {
410 (void)clock_gettime(CLOCK_MONOTONIC, &first_frame_time);
411 first_frame_recorded = true;
412
413 // If delay is 0, take snapshot immediately on first frame
414 if (GET_OPTION(snapshot_delay) == 0) {
415 log_info("Snapshot captured immediately (delay=0)!");
416 take_snapshot = true;
417 signal_exit();
418 } else {
419 log_info("Snapshot mode: first frame received, waiting %.2f seconds for webcam warmup...",
420 GET_OPTION(snapshot_delay));
421 }
422 } else {
423 struct timespec current_time;
424 (void)clock_gettime(CLOCK_MONOTONIC, &current_time);
425
426 // Calculate elapsed time in seconds with microsecond precision
427 double elapsed = (double)(current_time.tv_sec - first_frame_time.tv_sec) +
428 (double)(current_time.tv_nsec - first_frame_time.tv_nsec) / 1000000000.0;
429
430 if (elapsed >= GET_OPTION(snapshot_delay)) {
431 char duration_str[32];
432 format_duration_s(elapsed, duration_str, sizeof(duration_str));
433 log_info("Snapshot captured after %s!", duration_str);
434 take_snapshot = true;
435 signal_exit();
436 }
437 }
438 }
439
440 // Check if we need to clear console before rendering this frame
441 // IMPORTANT: We track if this is the first frame to ensure proper initialization
442 static bool first_frame_rendered = false;
443
444 if (!first_frame_rendered) {
445 // Always clear display and disable logging before rendering the first frame
446 // This ensures clean ASCII display regardless of packet arrival order
447 log_info("First frame - clearing display and disabling terminal logging");
450 first_frame_rendered = true;
451 g_server_state_initialized = true; // Mark as initialized
452 g_should_clear_before_next_frame = false; // Clear any pending clear request
453 log_debug("CLIENT_DISPLAY: Display cleared, ready for ASCII frames");
454 } else if (g_should_clear_before_next_frame) {
455 // Subsequent clear request from server (e.g., after client list changes)
456 log_debug("CLIENT_DISPLAY: Clearing display for layout change");
459 g_should_clear_before_next_frame = false;
460 }
461
462 // Safety check before rendering
463 if (!frame_data || header.original_size == 0) {
464 log_error("Invalid frame data for rendering: frame_data=%p, size=%u", frame_data, header.original_size);
465 if (frame_data) {
466 SAFE_FREE(frame_data);
467 }
468 return;
469 }
470
471 // Client-side FPS limiting for rendering (display)
472 // Server may send at 144fps for high-refresh displays, but this client renders at its requested FPS
473 static struct timespec last_render_time = {0, 0};
474
475 // Don't limit frame rate in snapshot mode - always render the final frame
476 if (!take_snapshot) {
477 // Get the client's desired FPS (what we told the server we can display)
478 int client_display_fps = MAX_FPS; // This respects the --fps command line flag
479 // Use microseconds for precision - avoid integer division loss
480 uint64_t render_interval_us = 1000000ULL / (uint64_t)client_display_fps;
481
482 struct timespec render_time;
483 (void)clock_gettime(CLOCK_MONOTONIC, &render_time);
484
485 // Calculate elapsed time since last render in microseconds (high precision)
486 uint64_t render_elapsed_us = 0;
487 if (last_render_time.tv_sec != 0 || last_render_time.tv_nsec != 0) {
488 int64_t sec_diff = (int64_t)render_time.tv_sec - (int64_t)last_render_time.tv_sec;
489 int64_t nsec_diff = (int64_t)render_time.tv_nsec - (int64_t)last_render_time.tv_nsec;
490
491 // Handle nanosecond underflow by borrowing from seconds
492 if (nsec_diff < 0) {
493 sec_diff -= 1;
494 nsec_diff += 1000000000LL; // Add 1 second worth of nanoseconds
495 }
496
497 // Convert to microseconds (now both values are properly normalized)
498 // sec_diff should be >= 0 for forward time progression
499 if (sec_diff >= 0) {
500 render_elapsed_us = (uint64_t)sec_diff * 1000000ULL + (uint64_t)(nsec_diff / 1000);
501 }
502 // If sec_diff is negative, time went backwards - treat as 0 elapsed
503 }
504
505 // Skip rendering if not enough time has passed (frame rate limiting)
506 if (last_render_time.tv_sec != 0 || last_render_time.tv_nsec != 0) {
507 if (render_elapsed_us > 0 && render_elapsed_us < render_interval_us) {
508 // Drop this frame to maintain display FPS limit
509 SAFE_FREE(frame_data);
510 return;
511 }
512 }
513
514 // Update last render time
515 last_render_time = current_time;
516 }
517
518 display_render_frame(frame_data, take_snapshot);
519
520 SAFE_FREE(frame_data);
521}
522
535static void handle_audio_packet(const void *data, size_t len) {
536 if (!data || len == 0) {
537 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid audio packet data");
538 return;
539 }
540
541 if (!GET_OPTION(audio_enabled)) {
542 log_warn_every(1000000, "Received audio packet but audio is disabled");
543 return;
544 }
545
546 int num_samples = (int)(len / sizeof(float));
547 if (num_samples > AUDIO_SAMPLES_PER_PACKET) {
548 log_warn("Audio packet too large: %d samples", num_samples);
549 return;
550 }
551
552 // Copy data to properly aligned buffer to avoid UndefinedBehaviorSanitizer errors
553 float samples[AUDIO_SAMPLES_PER_PACKET];
554 SAFE_MEMCPY(samples, sizeof(samples), data, len);
555
556 // Process audio through audio subsystem
557 audio_process_received_samples(samples, num_samples);
558
559#ifdef DEBUG_AUDIO
560 log_debug("Processed %d audio samples", num_samples);
561#endif
562}
563
576__attribute__((unused)) static void handle_audio_batch_packet(const void *data, size_t len) {
577 if (!data) {
578 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid audio batch packet data");
579 return;
580 }
581
582 if (!GET_OPTION(audio_enabled)) {
583 log_warn_every(1000000, "Received audio batch packet but audio is disabled");
584 return;
585 }
586
587 if (len < sizeof(audio_batch_packet_t)) {
588 log_warn("Audio batch packet too small: %zu bytes", len);
589 return;
590 }
591
592 // Parse batch header
593 const audio_batch_packet_t *batch_header = (const audio_batch_packet_t *)data;
594 uint32_t batch_count = NET_TO_HOST_U32(batch_header->batch_count);
595 uint32_t total_samples = NET_TO_HOST_U32(batch_header->total_samples);
596 uint32_t sample_rate = NET_TO_HOST_U32(batch_header->sample_rate);
597 uint32_t channels = NET_TO_HOST_U32(batch_header->channels);
598
599 (void)batch_count;
600 (void)sample_rate;
601 (void)channels;
602
603 if (batch_count == 0 || total_samples == 0) {
604 log_warn("Empty audio batch: batch_count=%u, total_samples=%u", batch_count, total_samples);
605 return;
606 }
607
608 // Validate packet size
609 size_t expected_size = sizeof(audio_batch_packet_t) + (total_samples * sizeof(uint32_t));
610 if (len != expected_size) {
611 log_warn("Audio batch size mismatch: got %zu expected %zu", len, expected_size);
612 return;
613 }
614
615 if (total_samples > AUDIO_BATCH_SAMPLES * 2) {
616 log_warn("Audio batch too large: %u samples", total_samples);
617 return;
618 }
619
620 // Extract quantized samples (uint32_t network byte order)
621 const uint8_t *samples_ptr = (const uint8_t *)data + sizeof(audio_batch_packet_t);
622
623 // Convert quantized samples to float
624 float *samples = SAFE_MALLOC(total_samples * sizeof(float), float *);
625 if (!samples) {
626 SET_ERRNO(ERROR_MEMORY, "Failed to allocate memory for audio batch conversion");
627 return;
628 }
629
630 // Use helper function to dequantize samples
631 asciichat_error_t dq_result = audio_dequantize_samples(samples_ptr, total_samples, samples);
632 if (dq_result != ASCIICHAT_OK) {
633 SAFE_FREE(samples);
634 return;
635 }
636
637 // Track received packet for analysis
638 if (GET_OPTION(audio_analysis_enabled)) {
640 }
641
642 // Process through audio subsystem
643 audio_process_received_samples(samples, (int)total_samples);
644
645 // Clean up
646 SAFE_FREE(samples);
647
648 log_debug_every(LOG_RATE_DEFAULT, "Processed audio batch: %u samples from server", total_samples);
649}
650
662static void handle_audio_opus_packet(const void *data, size_t len) {
663 // Validate parameters
664 if (!data || len == 0) {
665 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid audio opus packet: data=%p, len=%zu", data, len);
666 return;
667 }
668
669 if (!GET_OPTION(audio_enabled)) {
670 log_warn_every(1000000, "Received opus audio packet but audio is disabled");
671 return;
672 }
673
674 // Data is raw Opus-encoded frame (no header parsing needed)
675 const uint8_t *opus_data = (const uint8_t *)data;
676
677 // Opus max frame size is 2880 samples (120ms @ 48kHz)
678 float samples[2880];
679 int decoded_samples = audio_decode_opus(opus_data, len, samples, 2880);
680
681 if (decoded_samples <= 0) {
682 log_warn("Failed to decode Opus audio packet, decoded=%d", decoded_samples);
683 return;
684 }
685
686 // Track received packet for analysis
687 if (GET_OPTION(audio_analysis_enabled)) {
689 }
690
691 // Process decoded audio through audio subsystem
692 audio_process_received_samples(samples, decoded_samples);
693
694 log_debug_every(LOG_RATE_DEFAULT, "Processed Opus audio: %d decoded samples from %zu byte packet", decoded_samples,
695 len);
696}
697
717static void handle_audio_opus_batch_packet(const void *data, size_t len) {
718 // Validate parameters
719 if (!data || len == 0) {
720 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid opus batch packet: data=%p, len=%zu", data, len);
721 return;
722 }
723
724 if (!GET_OPTION(audio_enabled)) {
725 log_warn_every(1000000, "Received opus batch packet but audio is disabled");
726 return;
727 }
728
729 // Parse batch header using av_receive_audio_opus_batch() for consistency
730 const uint8_t *opus_data = NULL;
731 size_t opus_size = 0;
732 const uint16_t *frame_sizes = NULL;
733 int sample_rate = 0;
734 int frame_duration = 0;
735 int frame_count = 0;
736
737 asciichat_error_t result = packet_parse_opus_batch(data, len, &opus_data, &opus_size, &frame_sizes, &sample_rate,
738 &frame_duration, &frame_count);
739
740 if (result != ASCIICHAT_OK) {
741 log_warn("Failed to parse Opus batch packet");
742 return;
743 }
744
745 if (frame_count <= 0 || frame_count > 256 || opus_size == 0) {
746 log_warn("Invalid Opus batch: frame_count=%d, opus_size=%zu", frame_count, opus_size);
747 return;
748 }
749
750 // Calculate samples per frame
751 int samples_per_frame = (sample_rate * frame_duration) / 1000;
752 if (samples_per_frame <= 0 || samples_per_frame > 2880) {
753 log_warn("Invalid Opus frame parameters: samples_per_frame=%d", samples_per_frame);
754 return;
755 }
756
757 // Allocate buffer for all decoded samples
758 size_t max_decoded_samples = (size_t)samples_per_frame * (size_t)frame_count;
759 float *all_samples = SAFE_MALLOC(max_decoded_samples * sizeof(float), float *);
760 if (!all_samples) {
761 SET_ERRNO(ERROR_MEMORY, "Failed to allocate memory for Opus batch decoding");
762 return;
763 }
764
765 // Decode each Opus frame using frame_sizes array
766 int total_decoded_samples = 0;
767 size_t opus_offset = 0;
768
769 for (int i = 0; i < frame_count; i++) {
770 // Get frame size (convert from network byte order)
771 size_t frame_size = (size_t)NET_TO_HOST_U16(frame_sizes[i]);
772
773 if (opus_offset + frame_size > opus_size) {
774 log_warn("Opus batch truncated at frame %d (offset=%zu, frame_size=%zu, total=%zu)", i, opus_offset, frame_size,
775 opus_size);
776 break;
777 }
778
779 // Decode frame through audio pipeline
780 float *frame_buffer = all_samples + total_decoded_samples;
781 int remaining_space = (int)(max_decoded_samples - (size_t)total_decoded_samples);
782 int decoded = audio_decode_opus(opus_data + opus_offset, frame_size, frame_buffer, remaining_space);
783
784 if (decoded <= 0) {
785 log_warn("Failed to decode Opus frame %d in batch, decoded=%d", i, decoded);
786 break;
787 }
788
789 total_decoded_samples += decoded;
790 opus_offset += frame_size;
791 }
792
793 if (total_decoded_samples > 0) {
794 // Track received packet for analysis
795 if (GET_OPTION(audio_analysis_enabled)) {
797 }
798
799 // Process decoded audio through audio subsystem
800 audio_process_received_samples(all_samples, total_decoded_samples);
801
802 log_debug_every(LOG_RATE_DEFAULT, "Processed Opus batch: %d decoded samples from %d frames", total_decoded_samples,
803 frame_count);
804 }
805
806 // Clean up
807 SAFE_FREE(all_samples);
808}
809
810static bool handle_error_message_packet(const void *data, size_t len) {
811 asciichat_error_t remote_error = ASCIICHAT_OK;
812 char message[MAX_ERROR_MESSAGE_LENGTH + 1] = {0};
813
814 asciichat_error_t parse_result = packet_parse_error_message(data, len, &remote_error, message, sizeof(message), NULL);
815 if (parse_result != ASCIICHAT_OK) {
816 log_error("Failed to parse error packet from server: %s", asciichat_error_string(parse_result));
817 return false;
818 }
819
820 log_error("Server reported error %d (%s): %s", remote_error, asciichat_error_string(remote_error), message);
821 log_warn("Server signaled protocol error; closing connection");
824 return true;
825}
826
827static void handle_remote_log_packet(const void *data, size_t len) {
828 log_level_t remote_level = LOG_INFO;
830 uint16_t flags = 0;
831 char message[MAX_REMOTE_LOG_MESSAGE_LENGTH + 1] = {0};
832
833 asciichat_error_t parse_result =
834 packet_parse_remote_log(data, len, &remote_level, &direction, &flags, message, sizeof(message), NULL);
835 if (parse_result != ASCIICHAT_OK) {
836 log_error("Failed to parse remote log packet from server: %s", asciichat_error_string(parse_result));
837 return;
838 }
839
840 if (direction != REMOTE_LOG_DIRECTION_SERVER_TO_CLIENT) {
841 log_error("Remote log packet direction mismatch (direction=%u)", direction);
842 return;
843 }
844
845 bool truncated = (flags & REMOTE_LOG_FLAG_TRUNCATED) != 0;
846
847 if (truncated) {
848 log_msg(remote_level, __FILE__, __LINE__, __func__, "[REMOTE SERVER] %s [message truncated]", message);
849 } else {
850 log_msg(remote_level, __FILE__, __LINE__, __func__, "[REMOTE SERVER] %s", message);
851 }
852}
853
866static void handle_server_state_packet(const void *data, size_t len) {
867 if (!data || len != sizeof(server_state_packet_t)) {
868 log_error("Invalid server state packet size: %zu", len);
869 return;
870 }
871
872 const server_state_packet_t *state = (const server_state_packet_t *)data;
873
874 // Convert from network byte order
875 uint32_t active_count = NET_TO_HOST_U32(state->active_client_count);
876
877 // Check if connected count changed - if so, set flag to clear console before next frame
878 if (g_server_state_initialized) {
879 if (g_last_active_count != active_count) {
880 log_info("Active client count changed from %u to %u - will clear console before next frame", g_last_active_count,
881 active_count);
882 g_should_clear_before_next_frame = true;
883 }
884 } else {
885 // First state packet received
886 g_server_state_initialized = true;
887 // Clear terminal before the very first frame
888 g_should_clear_before_next_frame = true;
889 }
890
891 g_last_active_count = active_count;
892}
893
894/* ============================================================================
895 * ACIP Callback Forward Declarations and Structure
896 * ============================================================================ */
897
898// Forward declarations for ACIP callbacks (implemented after this section)
899static void acip_on_ascii_frame(const ascii_frame_packet_t *header, const void *frame_data, size_t data_len, void *ctx);
900static void acip_on_audio(const void *audio_data, size_t audio_len, void *ctx);
901static void acip_on_audio_batch(const audio_batch_packet_t *header, const float *samples, size_t num_samples,
902 void *ctx);
903static void acip_on_audio_opus(const void *opus_data, size_t opus_len, void *ctx);
904static void acip_on_audio_opus_batch(const void *batch_data, size_t batch_len, void *ctx);
905static void acip_on_server_state(const server_state_packet_t *state, void *ctx);
906static void acip_on_error(const error_packet_t *header, const char *message, void *ctx);
907static void acip_on_remote_log(const remote_log_packet_t *header, const char *message, void *ctx);
908static void acip_on_ping(void *ctx);
909static void acip_on_pong(void *ctx);
910static void acip_on_clear_console(void *ctx);
911static void acip_on_crypto_rekey_request(const void *payload, size_t payload_len, void *ctx);
912static void acip_on_crypto_rekey_response(const void *payload, size_t payload_len, void *ctx);
913static void acip_on_webrtc_sdp(const acip_webrtc_sdp_t *sdp, size_t total_len, void *ctx);
914static void acip_on_webrtc_ice(const acip_webrtc_ice_t *ice, size_t total_len, void *ctx);
915static void acip_on_session_joined(const acip_session_joined_t *joined, void *ctx);
916
923static const acip_client_callbacks_t g_acip_client_callbacks = {.on_ascii_frame = acip_on_ascii_frame,
924 .on_audio = acip_on_audio,
925 .on_audio_batch = acip_on_audio_batch,
926 .on_audio_opus = acip_on_audio_opus,
927 .on_audio_opus_batch = acip_on_audio_opus_batch,
928 .on_server_state = acip_on_server_state,
929 .on_error = acip_on_error,
930 .on_remote_log = acip_on_remote_log,
931 .on_ping = acip_on_ping,
932 .on_pong = acip_on_pong,
933 .on_clear_console = acip_on_clear_console,
934 .on_crypto_rekey_request = acip_on_crypto_rekey_request,
935 .on_crypto_rekey_response =
936 acip_on_crypto_rekey_response,
937 .on_webrtc_sdp = acip_on_webrtc_sdp,
938 .on_webrtc_ice = acip_on_webrtc_ice,
939 .on_session_joined = acip_on_session_joined,
940 .app_ctx = NULL};
941
951 return &g_acip_client_callbacks;
952}
953
954/* ============================================================================
955 * Data Reception Thread
956 * ============================================================================ */
957
984static void *data_reception_thread_func(void *arg) {
985 (void)arg;
986
987#ifdef DEBUG_THREADS
988 log_debug("Data reception thread started");
989#endif
990
991 while (!should_exit()) {
993
995 // Use rate-limited logging instead of logging every 10ms
996 log_debug_every(1000000, "Waiting for socket connection"); // Max once per second
997 platform_sleep_usec(10 * 1000);
998 continue;
999 }
1000
1001 // Receive and dispatch packet using ACIP transport API
1002 // This combines packet reception, decryption, parsing, handler dispatch, and cleanup
1004 if (!transport) {
1005 log_error("Transport not available, connection lost");
1007 break;
1008 }
1009
1010 asciichat_error_t acip_result = acip_client_receive_and_dispatch(transport, &g_acip_client_callbacks);
1011
1012 // Handle receive/dispatch errors
1013 if (acip_result != ASCIICHAT_OK) {
1014 // Check error type to determine action
1016 if (HAS_ERRNO(&err_ctx)) {
1017 if (err_ctx.code == ERROR_NETWORK) {
1018 // Network error or EOF - server disconnected
1019 log_debug("Server disconnected (network error): %s", err_ctx.context_message);
1021 break;
1022 } else if (err_ctx.code == ERROR_CRYPTO) {
1023 // Security violation - exit immediately
1024 log_error("SECURITY: Server violated encryption policy");
1025 log_error("SECURITY: This is a critical security violation - exiting immediately");
1026 exit(1);
1027 }
1028 }
1029
1030 // Other errors - log warning but continue
1031 log_warn("ACIP receive/dispatch failed: %s", asciichat_error_string(acip_result));
1032 }
1033 }
1034
1035#ifdef DEBUG_THREADS
1036 log_debug("Data reception thread stopped");
1037#endif
1038
1039 atomic_store(&g_data_thread_exited, true);
1040
1041 // Clean up thread-local error context before exit
1043
1044 return NULL;
1045}
1046
1047/* ============================================================================
1048 * Public Interface Functions
1049 * ============================================================================ */
1050
1062 // Reset protocol state for new connection
1063 g_server_state_initialized = false;
1064 g_last_active_count = 0;
1065 g_should_clear_before_next_frame = false;
1066
1067 // Reset display state for new connection
1069
1070 // Send CLIENT_CAPABILITIES packet FIRST before starting any threads
1071 // Server expects this as the first packet after crypto handshake
1072 log_info("Sending client capabilities to server...");
1074 log_error("Failed to send client capabilities to server");
1075 return -1;
1076 }
1077 log_info("Client capabilities sent successfully");
1078
1079 // Send STREAM_START packet with combined stream types BEFORE starting worker threads
1080 // This tells the server what streams to expect before any data arrives
1081 uint32_t stream_types = STREAM_TYPE_VIDEO; // Always have video
1082 if (GET_OPTION(audio_enabled)) {
1083 stream_types |= STREAM_TYPE_AUDIO; // Add audio if enabled
1084 }
1085 log_info("Sending STREAM_START packet (types=0x%x: %s%s)...", stream_types, "video",
1086 (stream_types & STREAM_TYPE_AUDIO) ? "+audio" : "");
1087 if (threaded_send_stream_start_packet(stream_types) < 0) {
1088 log_error("Failed to send STREAM_START packet");
1089 return -1;
1090 }
1091 log_info("STREAM_START packet sent successfully");
1092
1093 // Start data reception thread
1094 atomic_store(&g_data_thread_exited, false);
1095 if (thread_pool_spawn(g_client_worker_pool, data_reception_thread_func, NULL, 1, "data_reception") != ASCIICHAT_OK) {
1096 log_error("Failed to spawn data reception thread in worker pool");
1097 LOG_ERRNO_IF_SET("Data reception thread creation failed");
1098 return -1;
1099 }
1100
1101 // Start webcam capture thread
1102 log_info("Starting webcam capture thread...");
1103 if (capture_start_thread() != 0) {
1104 log_error("Failed to start webcam capture thread");
1105 return -1;
1106 }
1107 log_info("Webcam capture thread started successfully");
1108
1109 // Start audio capture thread if audio is enabled
1110 log_info("Starting audio capture thread...");
1111 if (audio_start_thread() != 0) {
1112 log_error("Failed to start audio capture thread");
1113 return -1;
1114 }
1115 log_info("Audio capture thread started successfully (or skipped if audio disabled)");
1116
1117 // Start keepalive/ping thread to prevent server timeout
1118 log_info("Starting keepalive/ping thread...");
1119 if (keepalive_start_thread() != 0) {
1120 log_error("Failed to start keepalive/ping thread");
1121 return -1;
1122 }
1123 log_info("Keepalive/ping thread started successfully");
1124
1125 g_data_thread_created = true;
1126 return 0;
1127}
1128
1138 if (!g_data_thread_created) {
1139 return;
1140 }
1141
1142 // Don't call signal_exit() here - that's for global shutdown only!
1143 // We just want to stop threads for this connection, not exit the entire client
1144
1145 // Shutdown the socket to interrupt any blocking recv() in data thread
1147
1148 // Stop keepalive/ping thread - it checks connection status and will exit
1150
1151 // Stop webcam capture thread
1153
1154 // Stop audio threads if running
1156
1157 // Wait for data reception thread to exit gracefully
1158 int wait_count = 0;
1159 while (wait_count < 20 && !atomic_load(&g_data_thread_exited)) {
1160 platform_sleep_usec(100000); // 100ms
1161 wait_count++;
1162 }
1163
1164 if (!atomic_load(&g_data_thread_exited)) {
1165 log_warn("Data thread not responding after 2 seconds - will be joined by thread pool");
1166 }
1167
1168 // Join all threads in the client worker pool (in stop_id order)
1169 // This handles the data reception thread and (eventually) all other worker threads
1172 if (result != ASCIICHAT_OK) {
1173 log_error("Failed to stop client worker threads");
1174 LOG_ERRNO_IF_SET("Thread pool stop failed");
1175 }
1176 }
1177
1178 g_data_thread_created = false;
1179
1180#ifdef DEBUG_THREADS
1181 log_info("Data reception thread stopped and joined by thread pool");
1182#endif
1183}
1184
1193 return atomic_load(&g_data_thread_exited) || server_connection_is_lost();
1194}
1195
1196/* ============================================================================
1197 * ACIP Callback Implementations
1198 * ============================================================================ */
1199
1205static void acip_on_ascii_frame(const ascii_frame_packet_t *header, const void *frame_data, size_t data_len,
1206 void *ctx) {
1207 (void)ctx;
1208
1209 // Reconstruct full packet for existing handler (header + data)
1210 // IMPORTANT: header is already in HOST byte order from ACIP layer,
1211 // but handle_ascii_frame_packet() expects NETWORK byte order and does conversion.
1212 // So we need to convert back to network order before passing.
1213 size_t total_len = sizeof(*header) + data_len;
1214 uint8_t *packet = buffer_pool_alloc(NULL, total_len);
1215 if (!packet) {
1216 log_error("Failed to allocate buffer for ASCII frame callback");
1217 return;
1218 }
1219
1220 // Convert header fields back to network byte order for handle_ascii_frame_packet()
1221 ascii_frame_packet_t net_header = *header;
1222 net_header.width = HOST_TO_NET_U32(header->width);
1223 net_header.height = HOST_TO_NET_U32(header->height);
1224 net_header.original_size = HOST_TO_NET_U32(header->original_size);
1225 net_header.compressed_size = HOST_TO_NET_U32(header->compressed_size);
1226 net_header.checksum = HOST_TO_NET_U32(header->checksum);
1227 net_header.flags = HOST_TO_NET_U32(header->flags);
1228
1229 memcpy(packet, &net_header, sizeof(net_header));
1230 memcpy(packet + sizeof(net_header), frame_data, data_len);
1231
1232 handle_ascii_frame_packet(packet, total_len);
1233 buffer_pool_free(NULL, packet, total_len);
1234}
1235
1239static void acip_on_audio_batch(const audio_batch_packet_t *header, const float *samples, size_t num_samples,
1240 void *ctx) {
1241 (void)ctx;
1242 (void)header;
1243
1244 if (!GET_OPTION(audio_enabled)) {
1245 return;
1246 }
1247
1248 // Process samples directly (already dequantized by ACIP handler)
1249 audio_process_received_samples((float *)samples, (int)num_samples);
1250
1251 if (GET_OPTION(audio_analysis_enabled)) {
1252 // Approximate packet size for analysis
1253 size_t approx_size = sizeof(*header) + (num_samples * sizeof(uint32_t));
1255 }
1256
1257 log_debug_every(LOG_RATE_DEFAULT, "Processed audio batch: %zu samples from server", num_samples);
1258}
1259
1263static void acip_on_audio_opus(const void *opus_data, size_t opus_len, void *ctx) {
1264 (void)ctx;
1265
1266 // Call existing handler directly
1267 handle_audio_opus_packet(opus_data, opus_len);
1268}
1269
1273static void acip_on_server_state(const server_state_packet_t *state, void *ctx) {
1274 (void)ctx;
1275
1276 // Call existing handler directly
1277 handle_server_state_packet(state, sizeof(*state));
1278}
1279
1283static void acip_on_error(const error_packet_t *header, const char *message, void *ctx) {
1284 (void)ctx;
1285
1286 // Reconstruct packet for existing handler
1287 size_t msg_len = message ? strlen(message) : 0;
1288 size_t total_len = sizeof(*header) + msg_len;
1289
1290 uint8_t *packet = buffer_pool_alloc(NULL, total_len);
1291 if (!packet) {
1292 log_error("Failed to allocate buffer for error packet callback");
1293 return;
1294 }
1295
1296 memcpy(packet, header, sizeof(*header));
1297 if (msg_len > 0) {
1298 memcpy(packet + sizeof(*header), message, msg_len);
1299 }
1300
1301 handle_error_message_packet(packet, total_len);
1302 buffer_pool_free(NULL, packet, total_len);
1303}
1304
1308static void acip_on_ping(void *ctx) {
1309 (void)ctx;
1310
1311 // Respond with PONG
1312 if (threaded_send_pong_packet() < 0) {
1313 log_error("Failed to send PONG response");
1314 }
1315}
1316
1320static void acip_on_audio(const void *audio_data, size_t audio_len, void *ctx) {
1321 (void)ctx;
1322
1323 // Call existing handler directly
1324 handle_audio_packet(audio_data, audio_len);
1325}
1326
1330static void acip_on_audio_opus_batch(const void *batch_data, size_t batch_len, void *ctx) {
1331 (void)ctx;
1332
1333 // Call existing handler directly
1334 handle_audio_opus_batch_packet(batch_data, batch_len);
1335}
1336
1340static void acip_on_remote_log(const remote_log_packet_t *header, const char *message, void *ctx) {
1341 (void)ctx;
1342
1343 // Reconstruct packet for existing handler
1344 size_t msg_len = strlen(message);
1345 size_t total_len = sizeof(*header) + msg_len;
1346
1347 uint8_t *packet = buffer_pool_alloc(NULL, total_len);
1348 if (!packet) {
1349 log_error("Failed to allocate buffer for remote log callback");
1350 return;
1351 }
1352
1353 memcpy(packet, header, sizeof(*header));
1354 memcpy(packet + sizeof(*header), message, msg_len);
1355
1356 handle_remote_log_packet(packet, total_len);
1357 buffer_pool_free(NULL, packet, total_len);
1358}
1359
1363static void acip_on_pong(void *ctx) {
1364 (void)ctx;
1365 // Pong received - no action needed (server acknowledged our ping)
1366}
1367
1371static void acip_on_clear_console(void *ctx) {
1372 (void)ctx;
1373
1374 // Server requested console clear
1376 log_info("Console cleared by server");
1377}
1378
1382static void acip_on_crypto_rekey_request(const void *payload, size_t payload_len, void *ctx) {
1383 (void)ctx;
1384
1385 // Process the server's rekey request
1386 asciichat_error_t crypto_result = crypto_client_process_rekey_request(payload, payload_len);
1387 if (crypto_result != ASCIICHAT_OK) {
1388 log_error("Failed to process REKEY_REQUEST: %d", crypto_result);
1389 return;
1390 }
1391
1392 // Send REKEY_RESPONSE
1393 crypto_result = crypto_client_send_rekey_response();
1394 if (crypto_result != ASCIICHAT_OK) {
1395 log_error("Failed to send REKEY_RESPONSE: %d", crypto_result);
1396 }
1397}
1398
1402static void acip_on_crypto_rekey_response(const void *payload, size_t payload_len, void *ctx) {
1403 (void)ctx;
1404
1405 // Process server's response
1406 asciichat_error_t crypto_result = crypto_client_process_rekey_response(payload, payload_len);
1407 if (crypto_result != ASCIICHAT_OK) {
1408 log_error("Failed to process REKEY_RESPONSE: %d", crypto_result);
1409 return;
1410 }
1411
1412 // Send REKEY_COMPLETE
1413 crypto_result = crypto_client_send_rekey_complete();
1414 if (crypto_result != ASCIICHAT_OK) {
1415 log_error("Failed to send REKEY_COMPLETE: %d", crypto_result);
1416 }
1417}
1418
1429static void acip_on_webrtc_sdp(const acip_webrtc_sdp_t *sdp, size_t total_len, void *ctx) {
1430 (void)ctx;
1431 (void)total_len; // Peer manager reads variable data via pointer arithmetic
1432
1433 // Check if WebRTC is initialized
1434 if (!g_peer_manager) {
1435 log_warn("Received WebRTC SDP but peer manager not initialized - ignoring");
1436 return;
1437 }
1438
1439 // Log SDP type for debugging
1440 const char *sdp_type_str = (sdp->sdp_type == 0) ? "offer" : "answer";
1441 log_info("Received WebRTC SDP %s from participant (session_id=%.8s...)", sdp_type_str, (const char *)sdp->session_id);
1442
1443 // Handle SDP through peer manager (extracts variable data internally)
1445
1446 if (result != ASCIICHAT_OK) {
1447 log_error("Failed to handle WebRTC SDP: %s", asciichat_error_string(result));
1448 }
1449}
1450
1461static void acip_on_webrtc_ice(const acip_webrtc_ice_t *ice, size_t total_len, void *ctx) {
1462 (void)ctx;
1463 (void)total_len; // Peer manager reads variable data via pointer arithmetic
1464
1465 // Check if WebRTC is initialized
1466 if (!g_peer_manager) {
1467 log_warn("Received WebRTC ICE but peer manager not initialized - ignoring");
1468 return;
1469 }
1470
1471 log_debug("Received WebRTC ICE candidate from participant (session_id=%.8s...)", (const char *)ice->session_id);
1472
1473 // Handle ICE through peer manager (extracts variable data internally)
1475
1476 if (result != ASCIICHAT_OK) {
1477 log_error("Failed to handle WebRTC ICE: %s", asciichat_error_string(result));
1478 }
1479}
1480
1501static void acip_on_session_joined(const acip_session_joined_t *joined, void *ctx) {
1502 (void)ctx; // Unused for now, may be used in future for context passing
1503
1504 if (!joined) {
1505 log_error("SESSION_JOINED callback received NULL response");
1506 return;
1507 }
1508
1509 // Check if join was successful
1510 if (!joined->success) {
1511 log_error("ACDS session join failed: error %d: %s", joined->error_code, joined->error_message);
1512 // Connection will timeout waiting for SDP/WebRTC completion and fallback to next stage
1513 return;
1514 }
1515
1516 // Join succeeded - we have session context now
1517 log_info("ACDS session join succeeded (participant_id=%.8s..., session_type=%s, server=%s:%u)",
1518 (const char *)joined->participant_id, joined->session_type == 1 ? "WebRTC" : "DirectTCP",
1519 joined->server_address, joined->server_port);
1520
1521 // Check if this is a WebRTC session
1522 if (joined->session_type == SESSION_TYPE_WEBRTC) {
1523 // TODO: Phase 3 - Initialize WebRTC connection with TURN credentials
1524 // webrtc_initialize_session(joined->session_id, joined->participant_id,
1525 // joined->turn_username, joined->turn_password);
1526 log_info("WebRTC session detected - TODO: initialize WebRTC with TURN credentials");
1527 } else {
1528 // Direct TCP - connection is already established or will be established
1529 log_info("Direct TCP session - using existing connection");
1530 }
1531}
void audio_analysis_track_received_packet(size_t size)
Track received packet.
Definition analysis.c:466
Audio Analysis and Debugging Interface.
#define LOG_ERRNO_IF_SET(message)
Check if any error occurred and log it if so.
๐Ÿ—ƒ๏ธ Lock-Free Unified Memory Buffer Pool with Lazy Allocation
ascii-chat Client Media Capture Management Interface
void signal_exit()
Signal client to exit.
struct webrtc_peer_manager * g_peer_manager
Global WebRTC peer manager for P2P connections.
thread_pool_t * g_client_worker_pool
Global client worker thread pool.
bool should_exit()
Check if client should exit.
remote_client_info_t
๐Ÿ“ฆ Network Packet Compression Utilities
Hardware-Accelerated CRC32 Checksum Computation.
ascii-chat Client Display Management Interface
๐Ÿ”„ Network byte order conversion helpers
#define HOST_TO_NET_U32(val)
Definition endian.h:71
#define NET_TO_HOST_U16(val)
Definition endian.h:116
#define NET_TO_HOST_U32(val)
Definition endian.h:86
๐Ÿ“Š String Formatting Utilities
โฑ๏ธ FPS tracking utility for monitoring frame throughput across all threads
acip_session_joined_t
acip_webrtc_ice_t
acip_webrtc_sdp_t
@ SESSION_TYPE_WEBRTC
WebRTC P2P mesh with STUN/TURN relay.
asciichat_error_t audio_dequantize_samples(const uint8_t *samples_ptr, uint32_t total_samples, float *out_samples)
Dequantize network audio samples from int32 to float.
void buffer_pool_free(buffer_pool_t *pool, void *data, size_t size)
Free a buffer back to the pool (lock-free)
void * buffer_pool_alloc(buffer_pool_t *pool, size_t size)
Allocate a buffer from the pool (lock-free fast path)
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.
Definition capture.c:449
void capture_stop_thread()
Stop capture thread.
Definition capture.c:481
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.
int 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.
int threaded_send_stream_start_packet(uint32_t stream_type)
Thread-safe stream start packet transmission.
socket_t server_connection_get_socket()
Get current socket file descriptor.
bool server_connection_is_lost()
Check if connection loss has been detected.
void server_connection_lost()
Signal that connection has been lost.
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_full_reset()
Perform full display reset.
Definition display.c:301
void display_render_frame(const char *frame_data, bool is_snapshot_frame)
Render ASCII frame to display.
Definition display.c:353
void display_reset_for_new_connection()
Reset display state for new connection.
Definition display.c:315
int keepalive_start_thread()
Start keepalive/ping thread.
Definition keepalive.c:234
void keepalive_stop_thread()
Stop keepalive/ping thread.
Definition keepalive.c:260
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:320
#define SAFE_MALLOC(size, cast)
Definition common.h:208
void fps_frame(fps_t *tracker, const struct timespec *current_time, const char *context)
Track a frame and detect lag conditions.
Definition fps.c:57
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:388
void fps_init(fps_t *tracker, int expected_fps, const char *name)
Initialize FPS tracker.
Definition fps.c:36
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
#define HAS_ERRNO(var)
Check if an error occurred and get full context.
void asciichat_errno_cleanup(void)
Cleanup error system resources.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:46
@ ERROR_NETWORK
Definition error_codes.h:69
@ ERROR_MEMORY
Definition error_codes.h:53
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_CRYPTO
Definition error_codes.h:88
@ ERROR_INVALID_PARAM
#define DEFAULT_MAX_FPS
Default maximum frame rate (frames per second)
Definition limits.h:26
#define MAX_DISPLAY_NAME_LEN
Maximum display name length in characters.
Definition limits.h:20
#define MAX_FPS
Maximum frame rate macro (uses g_max_fps if set, otherwise DEFAULT_MAX_FPS)
Definition limits.h:32
int g_max_fps
Runtime configurable maximum frame rate (can be overridden via environment or command line)
Definition common.c:30
#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.
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.
enum remote_log_direction remote_log_direction_t
Remote log packet direction enumeration.
#define log_error(...)
Log an ERROR message.
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
log_level_t
Logging levels enumeration.
Definition log/logging.h:59
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
#define log_warn_every(interval_us, fmt,...)
Rate-limited WARN logging.
void log_set_terminal_output(bool enabled)
Control stderr output to terminal.
@ REMOTE_LOG_DIRECTION_SERVER_TO_CLIENT
@ REMOTE_LOG_DIRECTION_UNKNOWN
@ LOG_INFO
Definition log/logging.h:62
int format_duration_s(double seconds, char *buffer, size_t buffer_size)
Format seconds as human-readable duration string.
Definition time.c:275
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:953
uint32_t width
Terminal width in characters.
Definition packet.h:742
uint32_t active_client_count
Number of clients actively sending video/audio streams.
Definition packet.h:602
uint32_t checksum
CRC32 checksum of original ASCII data.
Definition packet.h:750
uint32_t original_size
Size of original uncompressed ASCII data in bytes.
Definition packet.h:746
uint32_t height
Terminal height in characters.
Definition packet.h:744
#define STREAM_TYPE_VIDEO
Video stream.
Definition packet.h:829
uint32_t compressed_size
Size of compressed data (0 = not compressed)
Definition packet.h:748
#define STREAM_TYPE_AUDIO
Audio stream.
Definition packet.h:830
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:854
uint32_t channels
Number of audio channels (1=mono, 2=stereo)
Definition packet.h:804
uint32_t batch_count
Number of audio chunks in this batch (usually AUDIO_BATCH_COUNT = 32)
Definition packet.h:798
uint32_t flags
Frame flags bitmask (HAS_COLOR, IS_COMPRESSED, etc.)
Definition packet.h:752
uint32_t sample_rate
Sample rate in Hz (e.g., 44100, 48000)
Definition packet.h:802
uint32_t total_samples
Total audio samples across all chunks (typically 8192)
Definition packet.h:800
#define REMOTE_LOG_FLAG_TRUNCATED
Remote log packet flag definitions.
Definition packet.h:628
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
Definition options.h:644
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_BATCH_SAMPLES
Total samples in audio batch (8192 samples)
Definition packet.h:214
#define AUDIO_SAMPLES_PER_PACKET
Samples per audio packet (256 samples)
Definition packet.h:233
#define MAX_REMOTE_LOG_MESSAGE_LENGTH
Maximum remote log message length (512 bytes)
Definition packet.h:132
#define MAX_ERROR_MESSAGE_LENGTH
Maximum error message length (512 bytes)
Definition packet.h:122
int socket_t
Socket handle type (POSIX: int)
Definition socket.h:50
#define INVALID_SOCKET_VALUE
Invalid socket value (POSIX: -1)
Definition socket.h:52
void platform_sleep_usec(unsigned int usec)
High-precision sleep function with microsecond precision.
pthread_t asciichat_thread_t
Thread handle type (POSIX: pthread_t)
#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:178
#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)
ascii-chat Client Connection Keepalive Management Interface
๐Ÿ”Š Audio Capture and Playback Interface for ascii-chat
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.
ACIP client-side protocol library.
ASCII-Chat Discovery Service (ACDS) Protocol Message Formats.
โš™๏ธ Command-line options parsing and configuration management for ascii-chat
Packet protocol implementation with encryption and compression support.
Shared packet parsing utilities to eliminate duplication between server and client handlers.
ascii-chat Server Mode Entry Point Header
void handle_audio_batch_packet(client_info_t *client, const void *data, size_t len)
Process AUDIO_BATCH packet - store efficiently batched audio samples.
ascii-chat Client Audio Processing Management Interface
ascii-chat Client Server Connection Management Interface
Server cryptographic operations and per-client handshake management.
Server packet processing and protocol implementation.
RGB pixel structure.
Definition video/image.h:80
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:169
ASCII frame packet structure (Packet Type 2)
Definition packet.h:740
Error context structure.
char * context_message
Optional custom message (dynamically allocated, owned by system)
asciichat_error_t code
Error code (asciichat_error_t enum value)
Audio batch packet structure (Packet Type 28)
Definition packet.h:796
Cryptographic context structure.
Error packet structure carrying error code and textual description.
Definition packet.h:619
FPS tracking state.
Definition fps.h:51
Remote log packet structure carrying log level and message text.
Definition packet.h:633
Server state packet structure.
Definition packet.h:598
asciichat_error_t thread_pool_spawn(thread_pool_t *pool, void *(*thread_func)(void *), void *thread_arg, int stop_id, const char *thread_name)
Spawn a worker thread in the pool.
Definition thread_pool.c:65
asciichat_error_t thread_pool_stop_all(thread_pool_t *pool)
Stop all threads in the pool in stop_id order.
๐Ÿงต Generic thread pool abstraction for managing worker threads
โฑ๏ธ High-precision timing utilities using sokol_time.h and uthash
Transport abstraction layer for ACIP protocol.
Common validation macros to reduce duplication in protocol handlers.
Wrapper for windows.h with C23 alignment compatibility.