83#include <ascii-chat/audio/audio.h>
84#include <ascii-chat/audio/analysis.h>
86#include <ascii-chat/thread_pool.h>
88#include <ascii-chat/network/packet.h>
89#include <ascii-chat/network/packet_parsing.h>
90#include <ascii-chat/network/packet_parsing.h>
91#include <ascii-chat/network/acip/handlers.h>
92#include <ascii-chat/network/acip/transport.h>
93#include <ascii-chat/network/acip/client.h>
94#include <ascii-chat/network/acip/acds.h>
95#include <ascii-chat/network/webrtc/peer_manager.h>
96#include <ascii-chat/buffer_pool.h>
97#include <ascii-chat/common.h>
98#include <ascii-chat/common/buffer_sizes.h>
99#include <ascii-chat/util/endian.h>
100#include <ascii-chat/util/validation.h>
101#include <ascii-chat/util/endian.h>
102#include <ascii-chat/util/format.h>
103#include <ascii-chat/options/options.h>
104#include <ascii-chat/options/rcu.h>
105#include <ascii-chat/network/crc32.h>
106#include <ascii-chat/util/fps.h>
107#include <ascii-chat/util/time.h>
108#include <ascii-chat/crypto/crypto.h>
109#include <ascii-chat/crypto/handshake/client.h>
115 size_t plaintext_size,
size_t *plaintext_len);
118#include <ascii-chat/util/time.h>
120#include <stdatomic.h>
127#include <ascii-chat/platform/windows_compat.h>
130#include <ascii-chat/network/compression.h>
131#include <ascii-chat/network/packet_parsing.h>
156static bool g_data_thread_created =
false;
166static atomic_bool g_data_thread_exited =
false;
205 char display_name[MAX_DISPLAY_NAME_LEN];
221static uint32_t g_last_active_count = 0;
231static bool g_server_state_initialized =
false;
242static bool g_should_clear_before_next_frame =
false;
260static void disconnect_server_for_bad_data(
const char *format, ...) {
262 va_start(
args, format);
264 char message[BUFFER_SIZE_SMALL];
268 log_error(
"Server sent invalid data - disconnecting: %s", message);
296static char *decode_frame_data(
const char *frame_data_ptr,
size_t frame_data_len,
bool is_compressed,
297 uint32_t original_size, uint32_t compressed_size) {
322static void handle_ascii_frame_packet(
const void *data,
size_t len) {
327 if (!data || len <
sizeof(ascii_frame_packet_t)) {
328 disconnect_server_for_bad_data(
"ASCII_FRAME payload too small: %zu (min %zu)", len,
sizeof(ascii_frame_packet_t));
333 static fps_t fps_tracker = {0};
334 static bool fps_tracker_initialized =
false;
337 if (!fps_tracker_initialized) {
338 int fps = GET_OPTION(fps);
339 int expected_fps = fps > 0 ? ((fps > 144) ? 144 : fps) : DEFAULT_MAX_FPS;
340 fps_init(&fps_tracker, expected_fps,
"ASCII_RX");
341 fps_tracker_initialized =
true;
348 ascii_frame_packet_t header;
349 memcpy(&header, data,
sizeof(ascii_frame_packet_t));
352 header.width = NET_TO_HOST_U32(header.width);
353 header.height = NET_TO_HOST_U32(header.height);
354 header.original_size = NET_TO_HOST_U32(header.original_size);
355 header.compressed_size = NET_TO_HOST_U32(header.compressed_size);
356 header.checksum = NET_TO_HOST_U32(header.checksum);
357 header.flags = NET_TO_HOST_U32(header.flags);
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);
364 bool is_compressed = (header.flags & FRAME_FLAG_IS_COMPRESSED) && header.compressed_size > 0;
366 decode_frame_data(frame_data_ptr, frame_data_len, is_compressed, header.original_size, header.compressed_size);
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]);
380 log_error(
"Software CRC32: 0x%x (matches: %s)", sw_crc, (sw_crc == header.checksum) ?
"YES" :
"NO");
382 SAFE_FREE(frame_data);
387 static uint32_t last_width = 0;
388 static uint32_t last_height = 0;
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;
398 bool take_snapshot =
false;
399 if (GET_OPTION(snapshot_mode)) {
400 static uint64_t first_frame_time_ns = 0;
401 static bool first_frame_recorded =
false;
402 static int snapshot_frame_count = 0;
404 snapshot_frame_count++;
405 log_debug(
"Snapshot frame %d received", snapshot_frame_count);
407 if (!first_frame_recorded) {
409 first_frame_recorded =
true;
411 if (GET_OPTION(snapshot_delay) == 0) {
412 log_debug(
"Snapshot captured immediately (delay=0)!");
413 take_snapshot =
true;
416 log_debug(
"Snapshot mode: first frame received, waiting %.2f seconds for webcam warmup...",
417 GET_OPTION(snapshot_delay));
421 double elapsed = time_ns_to_s(
time_elapsed_ns(first_frame_time_ns, current_time_ns));
423 if (elapsed >= GET_OPTION(snapshot_delay)) {
424 char duration_str[32];
426 log_debug(
"Snapshot captured after %s!", duration_str);
427 take_snapshot =
true;
435 static bool first_frame_rendered =
false;
437 if (!first_frame_rendered) {
440 log_debug(
"First frame - clearing display and disabling terminal logging");
443 first_frame_rendered =
true;
444 g_server_state_initialized =
true;
445 g_should_clear_before_next_frame =
false;
446 log_debug(
"CLIENT_DISPLAY: Display cleared, ready for ASCII frames");
447 }
else if (g_should_clear_before_next_frame) {
449 log_debug(
"CLIENT_DISPLAY: Clearing display for layout change");
452 g_should_clear_before_next_frame =
false;
456 if (!frame_data || header.original_size == 0) {
457 log_error(
"Invalid frame data for rendering: frame_data=%p, size=%u", frame_data, header.original_size);
459 SAFE_FREE(frame_data);
466 static uint64_t last_render_time_ns = 0;
469 if (!take_snapshot) {
471 int fps = GET_OPTION(fps);
472 int client_display_fps = fps > 0 ? fps : DEFAULT_MAX_FPS;
473 uint64_t render_interval_us = US_PER_SEC_INT / (uint64_t)client_display_fps;
476 uint64_t render_elapsed_us = 0;
478 if (last_render_time_ns != 0) {
479 render_elapsed_us = time_ns_to_us(
time_elapsed_ns(last_render_time_ns, render_time_ns));
483 if (last_render_time_ns != 0) {
484 if (render_elapsed_us > 0 && render_elapsed_us < render_interval_us) {
486 SAFE_FREE(frame_data);
492 last_render_time_ns = render_time_ns;
496 static int client_frame_counter = 0;
497 client_frame_counter++;
498 if (client_frame_counter % 60 == 1) {
501 size_t frame_len = strlen(frame_data);
502 for (
size_t i = 0; i < frame_len; i++) {
503 if (frame_data[i] ==
'\n')
506 log_debug(
"CLIENT_FRAME: received %zu bytes, %d newlines, header: %ux%u", frame_len, line_count, header.width,
512 SAFE_FREE(frame_data);
527static void handle_audio_packet(
const void *data,
size_t len) {
528 if (!data || len == 0) {
529 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid audio packet data");
533 if (!GET_OPTION(audio_enabled)) {
534 log_warn_every(NS_PER_MS_INT,
"Received audio packet but audio is disabled");
538 int num_samples = (int)(len /
sizeof(
float));
539 if (num_samples > AUDIO_SAMPLES_PER_PACKET) {
540 log_warn(
"Audio packet too large: %d samples", num_samples);
545 float samples[AUDIO_SAMPLES_PER_PACKET];
546 SAFE_MEMCPY(samples,
sizeof(samples), data, len);
552 log_debug(
"Processed %d audio samples", num_samples);
567static void handle_audio_opus_packet(
const void *data,
size_t len) {
568 START_TIMER(
"audio_packet_total");
571 if (!data || len == 0) {
572 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid audio opus packet: data=%p, len=%zu", data, len);
576 if (!GET_OPTION(audio_enabled)) {
577 log_warn_every(NS_PER_MS_INT,
"Received opus audio packet but audio is disabled");
582 const uint8_t *opus_data = (
const uint8_t *)data;
587 START_TIMER(
"opus_decode");
589 double decode_ns = STOP_TIMER(
"opus_decode");
591 if (decoded_samples <= 0) {
592 log_warn(
"Failed to decode Opus audio packet, decoded=%d", decoded_samples);
597 if (GET_OPTION(audio_analysis_enabled)) {
602 START_TIMER(
"process_samples");
604 double process_ns = STOP_TIMER(
"process_samples");
606 double total_ns = STOP_TIMER(
"audio_packet_total");
608 static int timing_count = 0;
609 if (++timing_count % 100 == 0) {
610 log_debug(
"Audio packet timing #%d: decode=%.2fµs, process=%.2fµs, total=%.2fµs", timing_count, decode_ns / 1000.0,
611 process_ns / 1000.0, total_ns / 1000.0);
614 log_debug_every(LOG_RATE_DEFAULT,
"Processed Opus audio: %d decoded samples from %zu byte packet", decoded_samples,
637static void handle_audio_opus_batch_packet(
const void *data,
size_t len) {
639 if (!data || len == 0) {
640 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid opus batch packet: data=%p, len=%zu", data, len);
644 if (!GET_OPTION(audio_enabled)) {
645 log_warn_every(NS_PER_MS_INT,
"Received opus batch packet but audio is disabled");
650 const uint8_t *opus_data = NULL;
651 size_t opus_size = 0;
652 const uint16_t *frame_sizes = NULL;
654 int frame_duration = 0;
657 asciichat_error_t result =
packet_parse_opus_batch(data, len, &opus_data, &opus_size, &frame_sizes, &sample_rate,
658 &frame_duration, &frame_count);
660 if (result != ASCIICHAT_OK) {
661 log_warn(
"Failed to parse Opus batch packet");
665 if (frame_count <= 0 || frame_count > 256 || opus_size == 0) {
666 log_warn(
"Invalid Opus batch: frame_count=%d, opus_size=%zu", frame_count, opus_size);
671 int samples_per_frame = (sample_rate * frame_duration) / 1000;
672 if (samples_per_frame <= 0 || samples_per_frame > 2880) {
673 log_warn(
"Invalid Opus frame parameters: samples_per_frame=%d", samples_per_frame);
678 size_t max_decoded_samples = (size_t)samples_per_frame * (
size_t)frame_count;
679 float *all_samples = SAFE_MALLOC(max_decoded_samples *
sizeof(
float),
float *);
681 SET_ERRNO(ERROR_MEMORY,
"Failed to allocate memory for Opus batch decoding");
686 int total_decoded_samples = 0;
687 size_t opus_offset = 0;
689 for (
int i = 0; i < frame_count; i++) {
691 size_t frame_size = (size_t)NET_TO_HOST_U16(frame_sizes[i]);
693 if (opus_offset + frame_size > opus_size) {
694 log_warn(
"Opus batch truncated at frame %d (offset=%zu, frame_size=%zu, total=%zu)", i, opus_offset, frame_size,
700 float *frame_buffer = all_samples + total_decoded_samples;
701 int remaining_space = (int)(max_decoded_samples - (
size_t)total_decoded_samples);
702 int decoded =
audio_decode_opus(opus_data + opus_offset, frame_size, frame_buffer, remaining_space);
705 log_warn(
"Failed to decode Opus frame %d in batch, decoded=%d", i, decoded);
709 total_decoded_samples += decoded;
710 opus_offset += frame_size;
713 if (total_decoded_samples > 0) {
715 if (GET_OPTION(audio_analysis_enabled)) {
722 log_debug_every(LOG_RATE_DEFAULT,
"Processed Opus batch: %d decoded samples from %d frames", total_decoded_samples,
727 SAFE_FREE(all_samples);
730static bool handle_error_message_packet(
const void *data,
size_t len) {
731 asciichat_error_t remote_error = ASCIICHAT_OK;
732 char message[MAX_ERROR_MESSAGE_LENGTH + 1] = {0};
735 if (parse_result != ASCIICHAT_OK) {
736 log_error(
"Failed to parse error packet from server: %s", asciichat_error_string(parse_result));
740 log_error(
"Server reported error %d (%s): %s", remote_error, asciichat_error_string(remote_error), message);
741 log_warn(
"Server signaled protocol error; closing connection");
747static void handle_remote_log_packet(
const void *data,
size_t len) {
748 log_level_t remote_level = LOG_INFO;
749 remote_log_direction_t direction = REMOTE_LOG_DIRECTION_UNKNOWN;
751 char message[MAX_REMOTE_LOG_MESSAGE_LENGTH + 1] = {0};
753 asciichat_error_t parse_result =
755 if (parse_result != ASCIICHAT_OK) {
756 log_error(
"Failed to parse remote log packet from server: %s", asciichat_error_string(parse_result));
760 if (direction != REMOTE_LOG_DIRECTION_SERVER_TO_CLIENT) {
761 log_error(
"Remote log packet direction mismatch (direction=%u)", direction);
765 bool truncated = (flags & REMOTE_LOG_FLAG_TRUNCATED) != 0;
768 log_msg(remote_level, __FILE__, __LINE__, __func__,
"[REMOTE SERVER] %s [message truncated]", message);
770 log_msg(remote_level, __FILE__, __LINE__, __func__,
"[REMOTE SERVER] %s", message);
786static void handle_server_state_packet(
const void *data,
size_t len) {
787 if (!data || len !=
sizeof(server_state_packet_t)) {
788 log_error(
"Invalid server state packet size: %zu", len);
792 const server_state_packet_t *state = (
const server_state_packet_t *)data;
795 uint32_t active_count = NET_TO_HOST_U32(state->active_client_count);
798 if (g_server_state_initialized) {
799 if (g_last_active_count != active_count) {
800 log_debug(
"Active client count changed from %u to %u - will clear console before next frame", g_last_active_count,
802 g_should_clear_before_next_frame =
true;
806 g_server_state_initialized =
true;
808 g_should_clear_before_next_frame =
true;
811 g_last_active_count = active_count;
819static void acip_on_ascii_frame(
const ascii_frame_packet_t *header,
const void *frame_data,
size_t data_len,
void *ctx);
820static void acip_on_audio(
const void *audio_data,
size_t audio_len,
void *ctx);
821static void acip_on_audio_batch(
const audio_batch_packet_t *header,
const float *samples,
size_t num_samples,
823static void acip_on_audio_opus(
const void *opus_data,
size_t opus_len,
void *ctx);
824static void acip_on_audio_opus_batch(
const void *batch_data,
size_t batch_len,
void *ctx);
825static void acip_on_server_state(
const server_state_packet_t *state,
void *ctx);
826static void acip_on_error(
const error_packet_t *header,
const char *message,
void *ctx);
827static void acip_on_remote_log(
const remote_log_packet_t *header,
const char *message,
void *ctx);
828static void acip_on_ping(
void *ctx);
829static void acip_on_pong(
void *ctx);
830static void acip_on_clear_console(
void *ctx);
831static void acip_on_crypto_rekey_request(
const void *payload,
size_t payload_len,
void *ctx);
832static void acip_on_crypto_rekey_response(
const void *payload,
size_t payload_len,
void *ctx);
833static void acip_on_webrtc_sdp(
const acip_webrtc_sdp_t *sdp,
size_t total_len,
void *ctx);
834static void acip_on_webrtc_ice(
const acip_webrtc_ice_t *ice,
size_t total_len,
void *ctx);
835static void acip_on_session_joined(
const acip_session_joined_t *joined,
void *ctx);
836static void acip_on_crypto_key_exchange_init(packet_type_t type,
const void *payload,
size_t payload_len,
void *ctx);
837static void acip_on_crypto_auth_challenge(packet_type_t type,
const void *payload,
size_t payload_len,
void *ctx);
838static void acip_on_crypto_server_auth_resp(packet_type_t type,
const void *payload,
size_t payload_len,
void *ctx);
839static void acip_on_crypto_auth_failed(packet_type_t type,
const void *payload,
size_t payload_len,
void *ctx);
840static void acip_on_crypto_handshake_complete(packet_type_t type,
const void *payload,
size_t payload_len,
void *ctx);
848static const acip_client_callbacks_t g_acip_client_callbacks = {
849 .on_ascii_frame = acip_on_ascii_frame,
850 .on_audio = acip_on_audio,
851 .on_audio_batch = acip_on_audio_batch,
852 .on_audio_opus = acip_on_audio_opus,
853 .on_audio_opus_batch = acip_on_audio_opus_batch,
854 .on_server_state = acip_on_server_state,
855 .on_error = acip_on_error,
856 .on_remote_log = acip_on_remote_log,
857 .on_ping = acip_on_ping,
858 .on_pong = acip_on_pong,
859 .on_clear_console = acip_on_clear_console,
860 .on_crypto_rekey_request = acip_on_crypto_rekey_request,
861 .on_crypto_rekey_response = acip_on_crypto_rekey_response,
862 .on_webrtc_sdp = acip_on_webrtc_sdp,
863 .on_webrtc_ice = acip_on_webrtc_ice,
864 .on_session_joined = acip_on_session_joined,
865 .on_crypto_key_exchange_init = acip_on_crypto_key_exchange_init,
866 .on_crypto_auth_challenge = acip_on_crypto_auth_challenge,
867 .on_crypto_server_auth_resp = acip_on_crypto_server_auth_resp,
868 .on_crypto_auth_failed = acip_on_crypto_auth_failed,
869 .on_crypto_handshake_complete = acip_on_crypto_handshake_complete,
881 return &g_acip_client_callbacks;
914static void *data_reception_thread_func(
void *arg) {
918 log_debug(
"Data reception thread started");
929 log_error(
"Transport not available, connection lost");
937 if (acip_result != ASCIICHAT_OK) {
939 asciichat_error_context_t err_ctx;
940 if (HAS_ERRNO(&err_ctx)) {
941 if (err_ctx.code == ERROR_NETWORK) {
943 log_debug(
"Server disconnected (network error): %s", err_ctx.context_message);
946 }
else if (err_ctx.code == ERROR_CRYPTO) {
948 log_error(
"SECURITY: Server violated encryption policy");
949 log_error(
"SECURITY: This is a critical security violation - exiting immediately");
955 log_warn(
"ACIP receive/dispatch failed: %s", asciichat_error_string(acip_result));
960 log_debug(
"Data reception thread stopped");
963 atomic_store(&g_data_thread_exited,
true);
987 g_server_state_initialized =
false;
988 g_last_active_count = 0;
989 g_should_clear_before_next_frame =
false;
996 log_debug(
"Sending client capabilities to server...");
998 if (cap_result != ASCIICHAT_OK) {
999 log_error(
"Failed to send client capabilities to server");
1002 log_debug(
"Client capabilities sent successfully");
1006 uint32_t stream_types = STREAM_TYPE_VIDEO;
1007 if (GET_OPTION(audio_enabled)) {
1008 stream_types |= STREAM_TYPE_AUDIO;
1010 log_debug(
"Sending STREAM_START packet (types=0x%x: %s%s)...", stream_types,
"video",
1011 (stream_types & STREAM_TYPE_AUDIO) ?
"+audio" :
"");
1013 if (stream_result != ASCIICHAT_OK) {
1014 log_error(
"Failed to send STREAM_START packet");
1017 log_debug(
"STREAM_START packet sent successfully");
1020 atomic_store(&g_data_thread_exited,
false);
1022 log_error(
"Failed to spawn data reception thread in worker pool");
1023 LOG_ERRNO_IF_SET(
"Data reception thread creation failed");
1028 log_debug(
"Starting webcam capture thread...");
1030 log_error(
"Failed to start webcam capture thread");
1033 log_debug(
"Webcam capture thread started successfully");
1036 log_debug(
"Starting audio capture thread...");
1038 log_error(
"Failed to start audio capture thread");
1041 log_debug(
"Audio capture thread started successfully (or skipped if audio disabled)");
1044 log_debug(
"Starting keepalive/ping thread...");
1046 log_error(
"Failed to start keepalive/ping thread");
1049 log_debug(
"Keepalive/ping thread started successfully");
1051 g_data_thread_created =
true;
1064 if (!g_data_thread_created) {
1085 while (wait_count < 20 && !atomic_load(&g_data_thread_exited)) {
1090 if (!atomic_load(&g_data_thread_exited)) {
1091 log_warn(
"Data thread not responding after 2 seconds - will be joined by thread pool");
1098 if (result != ASCIICHAT_OK) {
1099 log_error(
"Failed to stop client worker threads");
1100 LOG_ERRNO_IF_SET(
"Thread pool stop failed");
1104 g_data_thread_created =
false;
1107 log_debug(
"Data reception thread stopped and joined by thread pool");
1131static void acip_on_ascii_frame(
const ascii_frame_packet_t *header,
const void *frame_data,
size_t data_len,
1139 size_t total_len =
sizeof(*header) + data_len;
1140 uint8_t *packet = SAFE_MALLOC(total_len, uint8_t *);
1142 log_error(
"Failed to allocate buffer for ASCII frame callback");
1147 ascii_frame_packet_t net_header = *header;
1148 net_header.width = HOST_TO_NET_U32(header->width);
1149 net_header.height = HOST_TO_NET_U32(header->height);
1150 net_header.original_size = HOST_TO_NET_U32(header->original_size);
1151 net_header.compressed_size = HOST_TO_NET_U32(header->compressed_size);
1152 net_header.checksum = HOST_TO_NET_U32(header->checksum);
1153 net_header.flags = HOST_TO_NET_U32(header->flags);
1155 memcpy(packet, &net_header,
sizeof(net_header));
1156 memcpy(packet +
sizeof(net_header), frame_data, data_len);
1158 handle_ascii_frame_packet(packet, total_len);
1165static void acip_on_audio_batch(
const audio_batch_packet_t *header,
const float *samples,
size_t num_samples,
1170 if (!GET_OPTION(audio_enabled)) {
1177 if (GET_OPTION(audio_analysis_enabled)) {
1179 size_t approx_size =
sizeof(*header) + (num_samples *
sizeof(uint32_t));
1183 log_debug_every(LOG_RATE_DEFAULT,
"Processed audio batch: %zu samples from server", num_samples);
1189static void acip_on_audio_opus(
const void *opus_data,
size_t opus_len,
void *ctx) {
1193 handle_audio_opus_packet(opus_data, opus_len);
1199static void acip_on_server_state(
const server_state_packet_t *state,
void *ctx) {
1203 handle_server_state_packet(state,
sizeof(*state));
1209static void acip_on_error(
const error_packet_t *header,
const char *message,
void *ctx) {
1213 size_t msg_len = message ? strlen(message) : 0;
1214 size_t total_len =
sizeof(*header) + msg_len;
1216 uint8_t *packet = SAFE_MALLOC(total_len, uint8_t *);
1218 log_error(
"Failed to allocate buffer for error packet callback");
1222 memcpy(packet, header,
sizeof(*header));
1224 memcpy(packet +
sizeof(*header), message, msg_len);
1227 handle_error_message_packet(packet, total_len);
1234static void acip_on_ping(
void *ctx) {
1239 log_error(
"Failed to send PONG response");
1246static void acip_on_audio(
const void *audio_data,
size_t audio_len,
void *ctx) {
1250 handle_audio_packet(audio_data, audio_len);
1256static void acip_on_audio_opus_batch(
const void *batch_data,
size_t batch_len,
void *ctx) {
1260 handle_audio_opus_batch_packet(batch_data, batch_len);
1266static void acip_on_remote_log(
const remote_log_packet_t *header,
const char *message,
void *ctx) {
1270 size_t msg_len = strlen(message);
1271 size_t total_len =
sizeof(*header) + msg_len;
1273 uint8_t *packet = SAFE_MALLOC(total_len, uint8_t *);
1275 log_error(
"Failed to allocate buffer for remote log callback");
1279 memcpy(packet, header,
sizeof(*header));
1280 memcpy(packet +
sizeof(*header), message, msg_len);
1282 handle_remote_log_packet(packet, total_len);
1289static void acip_on_pong(
void *ctx) {
1297static void acip_on_clear_console(
void *ctx) {
1302 log_debug(
"Console cleared by server");
1308static void acip_on_crypto_rekey_request(
const void *payload,
size_t payload_len,
void *ctx) {
1313 if (crypto_result != ASCIICHAT_OK) {
1314 log_error(
"Failed to process REKEY_REQUEST: %d", crypto_result);
1320 if (crypto_result != ASCIICHAT_OK) {
1321 log_error(
"Failed to send REKEY_RESPONSE: %d", crypto_result);
1328static void acip_on_crypto_rekey_response(
const void *payload,
size_t payload_len,
void *ctx) {
1333 if (crypto_result != ASCIICHAT_OK) {
1334 log_error(
"Failed to process REKEY_RESPONSE: %d", crypto_result);
1340 if (crypto_result != ASCIICHAT_OK) {
1341 log_error(
"Failed to send REKEY_COMPLETE: %d", crypto_result);
1355static void acip_on_webrtc_sdp(
const acip_webrtc_sdp_t *sdp,
size_t total_len,
void *ctx) {
1361 log_warn(
"Received WebRTC SDP but peer manager not initialized - ignoring");
1366 const char *sdp_type_str = (sdp->sdp_type == 0) ?
"offer" :
"answer";
1367 log_debug(
"Received WebRTC SDP %s from participant (session_id=%.8s...)", sdp_type_str,
1368 (
const char *)sdp->session_id);
1373 if (result != ASCIICHAT_OK) {
1374 log_error(
"Failed to handle WebRTC SDP: %s", asciichat_error_string(result));
1388static void acip_on_webrtc_ice(
const acip_webrtc_ice_t *ice,
size_t total_len,
void *ctx) {
1394 log_warn(
"Received WebRTC ICE but peer manager not initialized - ignoring");
1398 log_debug(
"Received WebRTC ICE candidate from participant (session_id=%.8s...)", (
const char *)ice->session_id);
1403 if (result != ASCIICHAT_OK) {
1404 log_error(
"Failed to handle WebRTC ICE: %s", asciichat_error_string(result));
1428static void acip_on_session_joined(
const acip_session_joined_t *joined,
void *ctx) {
1432 log_error(
"SESSION_JOINED callback received NULL response");
1437 if (!joined->success) {
1438 log_error(
"ACDS session join failed: error %d: %s", joined->error_code, joined->error_message);
1444 log_debug(
"ACDS session join succeeded (participant_id=%.8s..., session_type=%s, server=%s:%u)",
1445 (
const char *)joined->participant_id, joined->session_type == 1 ?
"WebRTC" :
"DirectTCP",
1446 joined->server_address, joined->server_port);
1449 if (joined->session_type == SESSION_TYPE_WEBRTC) {
1453 log_debug(
"WebRTC session detected - TODO: initialize WebRTC with TURN credentials");
1456 log_debug(
"Direct TCP session - using existing connection");
1473static void acip_on_crypto_key_exchange_init(packet_type_t type,
const void *payload,
size_t payload_len,
void *ctx) {
1476 log_debug(
"Received CRYPTO_KEY_EXCHANGE_INIT from server");
1480 log_error(
"Cannot handle key exchange - no transport available");
1484 asciichat_error_t result =
1486 if (result != ASCIICHAT_OK) {
1487 log_error(
"Crypto handshake key exchange failed");
1490 log_debug(
"Sent CRYPTO_KEY_EXCHANGE_RESP to server");
1507static void acip_on_crypto_auth_challenge(packet_type_t type,
const void *payload,
size_t payload_len,
void *ctx) {
1510 log_debug(
"Received CRYPTO_AUTH_CHALLENGE from server");
1514 log_error(
"Cannot handle auth challenge - no transport available");
1518 asciichat_error_t result =
1520 if (result != ASCIICHAT_OK) {
1521 log_error(
"Crypto handshake auth response failed");
1524 log_debug(
"Sent CRYPTO_AUTH_RESPONSE to server");
1541static void acip_on_crypto_server_auth_resp(packet_type_t type,
const void *payload,
size_t payload_len,
void *ctx) {
1544 log_debug(
"Received CRYPTO_SERVER_AUTH_RESP from server");
1548 log_error(
"Cannot handle server auth response - no transport available");
1552 asciichat_error_t result =
1554 if (result != ASCIICHAT_OK) {
1555 log_error(
"Crypto handshake verification failed");
1558 log_info(
"Crypto handshake completed successfully (mutual auth)");
1578static void acip_on_crypto_auth_failed(packet_type_t type,
const void *payload,
size_t payload_len,
void *ctx) {
1583 char error_msg[256] =
"Unknown error";
1584 if (payload && payload_len > 0) {
1585 size_t msg_len = payload_len <
sizeof(error_msg) - 1 ? payload_len : sizeof(error_msg) - 1;
1586 memcpy(error_msg, payload, msg_len);
1587 error_msg[msg_len] =
'\0';
1590 log_error(
"Server rejected authentication: %s", error_msg);
1591 log_error(
"Disconnecting - crypto handshake failed");
1609static void acip_on_crypto_handshake_complete(packet_type_t type,
const void *payload,
size_t payload_len,
void *ctx) {
1612 log_debug(
"Received CRYPTO_HANDSHAKE_COMPLETE from server");
1616 log_error(
"Cannot complete handshake - no transport available");
1620 asciichat_error_t result =
1622 if (result != ASCIICHAT_OK) {
1623 log_error(
"Crypto handshake completion failed");
1626 log_info(
"Crypto handshake completed successfully");
void audio_analysis_track_received_packet(size_t size)
void asciichat_errno_destroy(void)
ascii-chat Client Media Capture Management Interface
struct webrtc_peer_manager * g_peer_manager
Global WebRTC peer manager (legacy compatibility)
thread_pool_t * g_client_worker_pool
Global client worker thread pool.
uint32_t asciichat_crc32_sw(const void *data, size_t len)
ascii-chat Client Display Management Interface
void fps_frame_ns(fps_t *tracker, uint64_t current_time_ns, const char *context)
void fps_init(fps_t *tracker, int expected_fps, const char *name)
void audio_process_received_samples(const float *samples, int num_samples)
Process received audio samples from server.
void audio_stop_thread()
Stop audio capture thread.
int audio_decode_opus(const uint8_t *opus_data, size_t opus_len, float *output, int max_samples)
Decode Opus packet using the audio pipeline.
int audio_start_thread()
Start audio capture thread.
int capture_start_thread()
Start capture thread.
void capture_stop_thread()
Stop capture thread.
int threaded_send_pong_packet(void)
Thread-safe pong packet transmission.
bool server_connection_is_active()
Check if server connection is currently active.
void server_connection_shutdown()
Emergency connection shutdown for signal handlers.
asciichat_error_t threaded_send_terminal_size_with_auto_detect(unsigned short width, unsigned short height)
Thread-safe terminal size packet transmission with auto-detection.
acip_transport_t * server_connection_get_transport(void)
Get ACIP transport instance.
bool server_connection_is_lost()
Check if connection loss has been detected.
asciichat_error_t threaded_send_stream_start_packet(uint32_t stream_type)
Thread-safe stream start packet transmission.
void server_connection_lost()
Signal that connection has been lost.
crypto_handshake_context_t g_crypto_ctx
Global crypto handshake context for this client connection.
const crypto_context_t * crypto_client_get_context(void)
Get crypto context for encryption/decryption.
int crypto_client_send_rekey_response(void)
Send REKEY_RESPONSE packet to server.
int crypto_client_decrypt_packet(const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext, size_t plaintext_size, size_t *plaintext_len)
Decrypt a received packet.
bool crypto_client_is_ready(void)
Check if crypto handshake is ready.
int crypto_client_process_rekey_request(const uint8_t *packet, size_t packet_len)
Process received REKEY_REQUEST packet from server.
int crypto_client_process_rekey_response(const uint8_t *packet, size_t packet_len)
Process received REKEY_RESPONSE packet from server.
int crypto_client_send_rekey_complete(void)
Send REKEY_COMPLETE packet to server and commit to new key.
void display_render_frame(const char *frame_data)
Render ASCII frame to display.
void display_full_reset()
Perform full display reset.
void display_reset_for_new_connection()
Reset display state for new connection.
int keepalive_start_thread()
Start keepalive/ping thread.
void keepalive_stop_thread()
Stop keepalive/ping thread.
const acip_client_callbacks_t * protocol_get_acip_callbacks()
Get ACIP client callbacks for packet dispatch.
void protocol_stop_connection()
Stop protocol connection handling.
int protocol_start_connection()
Start protocol connection handling.
bool protocol_connection_lost()
Check if connection has been lost.
ascii-chat Client Connection Keepalive Management Interface
asciichat_error_t crypto_handshake_client_complete(crypto_handshake_context_t *ctx, acip_transport_t *transport, packet_type_t packet_type, const uint8_t *payload, size_t payload_len)
asciichat_error_t crypto_handshake_client_key_exchange(crypto_handshake_context_t *ctx, acip_transport_t *transport, packet_type_t packet_type, const uint8_t *payload, size_t payload_len)
asciichat_error_t crypto_handshake_client_auth_response(crypto_handshake_context_t *ctx, acip_transport_t *transport, packet_type_t packet_type, const uint8_t *payload, size_t payload_len)
asciichat_error_t acip_client_receive_and_dispatch(acip_transport_t *transport, const acip_client_callbacks_t *callbacks)
void log_msg(log_level_t level, const char *file, int line, const char *func, const char *fmt,...)
void log_set_terminal_output(bool enabled)
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)
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)
asciichat_error_t packet_parse_opus_batch(const void *packet_data, size_t packet_len, const uint8_t **out_opus_data, size_t *out_opus_size, const uint16_t **out_frame_sizes, int *out_sample_rate, int *out_frame_duration, int *out_frame_count)
char * packet_decode_frame_data_malloc(const char *frame_data_ptr, size_t frame_data_len, bool is_compressed, uint32_t original_size, uint32_t compressed_size)
asciichat_error_t webrtc_peer_manager_handle_ice(webrtc_peer_manager_t *manager, const acip_webrtc_ice_t *ice)
asciichat_error_t webrtc_peer_manager_handle_sdp(webrtc_peer_manager_t *manager, const acip_webrtc_sdp_t *sdp)
Server cryptographic operations and per-client handshake management.
ascii-chat Server Mode Entry Point Header
Server packet processing and protocol implementation.
ascii-chat Client Audio Processing Management Interface
Remote client information structure for multi-user client tracking.
time_t last_seen
Timestamp when client was last seen (for timeout detection)
uint32_t client_id
Unique client identifier assigned by server.
bool is_active
Whether client is currently active (sending video/audio)
int safe_vsnprintf(char *buffer, size_t buffer_size, const char *format, va_list ap)
Safe formatted string printing with va_list.
asciichat_error_t thread_pool_spawn(thread_pool_t *pool, void *(*thread_func)(void *), void *thread_arg, int stop_id, const char *thread_name)
asciichat_error_t thread_pool_stop_all(thread_pool_t *pool)
uint64_t time_get_ns(void)
int format_duration_s(double seconds, char *buffer, size_t buffer_size)
uint64_t time_elapsed_ns(uint64_t start_ns, uint64_t end_ns)