ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
server/protocol.c
Go to the documentation of this file.
1
112#include <math.h>
113#include <stdarg.h>
114#include <stdatomic.h>
115#include <stdio.h>
116#include <string.h>
117#include <time.h>
118
119#include "protocol.h"
120#include "client.h"
121#include "common.h"
122#include "util/endian.h"
123#include "util/validation.h"
124#include "util/endian.h"
125#include "util/bytes.h"
126#include "util/image.h"
127#include "video/video_frame.h"
128#include "audio/audio.h"
129#include "video/palette.h"
130#include "video/image.h"
131#include "network/compression.h"
133#include "network/acip/send.h"
134#include "network/acip/server.h"
135#include "util/format.h"
136#include "platform/system.h"
137#include "audio/opus_codec.h"
139#include "network/logging.h"
141
149extern atomic_bool g_server_should_exit;
150
151static void protocol_cleanup_thread_locals(void) {
152 // Placeholder for thread-local resources owned by the receive thread.
153 // Add cleanup logic here if future protocol changes introduce
154 // thread-local allocations that must be released before disconnecting.
155}
156
157void disconnect_client_for_bad_data(client_info_t *client, const char *format, ...) {
158 if (!client) {
159 return;
160 }
161
162 protocol_cleanup_thread_locals();
163
164 bool already_requested = atomic_exchange(&client->protocol_disconnect_requested, true);
165 if (already_requested) {
166 return;
167 }
168
169 char reason[256] = {0};
170 if (format) {
171 va_list args;
172 va_start(args, format);
173 (void)vsnprintf(reason, sizeof(reason), format, args);
174 va_end(args);
175 } else {
176 SAFE_STRNCPY(reason, "Protocol violation", sizeof(reason));
177 }
178
179 const char *reason_str = reason[0] != '\0' ? reason : "Protocol violation";
180 uint32_t client_id = atomic_load(&client->client_id);
181
182 socket_t socket_snapshot = INVALID_SOCKET_VALUE;
183 const crypto_context_t *crypto_ctx = NULL;
184
186 if (client->socket != INVALID_SOCKET_VALUE) {
187 socket_snapshot = client->socket;
188 if (client->crypto_initialized) {
190 }
191 }
193
194 // NOTE: Disconnecting a client due to the client's own bad behavior isn't an
195 // error for us, it's desired behavior for us, so we simply warn and do not
196 // have a need for asciichat_errno here.
197 log_warn("Disconnecting client %u due to protocol violation: %s", client_id, reason_str);
198
199 if (socket_snapshot != INVALID_SOCKET_VALUE) {
200 // CRITICAL: Protect socket writes with send_mutex to prevent race with send_thread
201 // This receive_thread and send_thread both write to same socket
202 mutex_lock(&client->send_mutex);
203
204 asciichat_error_t log_result =
205 log_network_message(socket_snapshot, (const struct crypto_context_t *)crypto_ctx, LOG_ERROR,
206 REMOTE_LOG_DIRECTION_SERVER_TO_CLIENT, "Protocol violation: %s", reason_str);
207 if (log_result != ASCIICHAT_OK) {
208 log_warn("Failed to send remote log to client %u: %s", client_id, asciichat_error_string(log_result));
209 }
210
211 asciichat_error_t send_result = packet_send_error(socket_snapshot, crypto_ctx, ERROR_NETWORK_PROTOCOL, reason_str);
212 if (send_result != ASCIICHAT_OK) {
213 log_warn("Failed to send error packet to client %u: %s", client_id, asciichat_error_string(send_result));
214 }
215
216 mutex_unlock(&client->send_mutex);
217 }
218
220
221 atomic_store(&client->active, false);
222 atomic_store(&client->shutting_down, true);
223 atomic_store(&client->send_thread_running, false);
224 atomic_store(&client->video_render_thread_running, false);
225 atomic_store(&client->audio_render_thread_running, false);
226
227 if (client->audio_queue) {
229 }
230
232 if (client->socket != INVALID_SOCKET_VALUE) {
233 socket_shutdown(client->socket, 2);
234 socket_close(client->socket);
236 }
238}
239
240/* ============================================================================
241 * Client Lifecycle Packet Handlers
242 * ============================================================================
243 */
244
282void handle_client_join_packet(client_info_t *client, const void *data, size_t len) {
283 VALIDATE_PACKET_SIZE(client, data, len, sizeof(client_info_packet_t), "CLIENT_JOIN");
284
285 const client_info_packet_t *join_info = (const client_info_packet_t *)data;
286
287 // Validate display name is present and not just whitespace
288 if (join_info->display_name[0] == '\0') {
289 disconnect_client_for_bad_data(client, "CLIENT_JOIN display_name cannot be empty");
290 return;
291 }
292
293 uint32_t capabilities = NET_TO_HOST_U32(join_info->capabilities);
294
295 // Validate at least one capability flag is set
297 VALIDATE_CAPABILITY_FLAGS(client, capabilities, VALID_CAP_MASK, "CLIENT_JOIN");
298
299 // Validate no unknown capability bits are set
300 VALIDATE_FLAGS_MASK(client, capabilities, VALID_CAP_MASK, "CLIENT_JOIN");
301
303
304 client->can_send_video = (capabilities & CLIENT_CAP_VIDEO) != 0;
305 client->can_send_audio = (capabilities & CLIENT_CAP_AUDIO) != 0;
306 client->wants_stretch = (capabilities & CLIENT_CAP_STRETCH) != 0;
307
308 log_info("Client %u joined: %s (video=%d, audio=%d, stretch=%d)", atomic_load(&client->client_id),
309 client->display_name, client->can_send_video, client->can_send_audio, client->wants_stretch);
310
311 // Notify client of successful join (encrypted channel)
312 log_info_client(client, "Joined as '%s' (video=%s, audio=%s)", client->display_name,
313 client->can_send_video ? "yes" : "no", client->can_send_audio ? "yes" : "no");
314}
315
347void handle_protocol_version_packet(client_info_t *client, const void *data, size_t len) {
348 if (!data) {
349 disconnect_client_for_bad_data(client, "PROTOCOL_VERSION payload missing");
350 return;
351 }
352
353 if (len != sizeof(protocol_version_packet_t)) {
354 disconnect_client_for_bad_data(client, "PROTOCOL_VERSION invalid size: %zu (expected %zu)", len,
356 return;
357 }
358
359 const protocol_version_packet_t *version = (const protocol_version_packet_t *)data;
360 uint16_t client_major = NET_TO_HOST_U16(version->protocol_version);
361 uint16_t client_minor = NET_TO_HOST_U16(version->protocol_revision);
362
363 // Validate major version match (minor version can differ for backward compat)
364 if (client_major != PROTOCOL_VERSION_MAJOR) {
365 log_warn("Client %u protocol version mismatch: client=%u.%u, server=%u.%u", atomic_load(&client->client_id),
366 client_major, client_minor, PROTOCOL_VERSION_MAJOR, PROTOCOL_VERSION_MINOR);
367 // Note: We don't disconnect on version mismatch for backward compatibility
368 // Clients may be older or newer than server
369 } else if (client_minor != PROTOCOL_VERSION_MINOR) {
370 log_info("Client %u has different protocol revision: client=%u.%u, server=%u.%u", atomic_load(&client->client_id),
371 client_major, client_minor, PROTOCOL_VERSION_MAJOR, PROTOCOL_VERSION_MINOR);
372 }
373
374 // Validate reserved bytes are zero
375 for (size_t i = 0; i < sizeof(version->reserved); i++) {
376 if (version->reserved[i] != 0) {
377 log_warn("Client %u sent non-zero reserved bytes in PROTOCOL_VERSION packet", atomic_load(&client->client_id));
378 // Don't disconnect - reserved bytes may be used in future versions
379 break;
380 }
381 }
382
383 // Log supported features
384 if (version->supports_encryption) {
385 log_debug("Client %u supports encryption", atomic_load(&client->client_id));
386 }
387 if (version->compression_algorithms != 0) {
388 log_debug("Client %u supports compression: 0x%02x", atomic_load(&client->client_id),
389 version->compression_algorithms);
390 }
391 if (version->feature_flags != 0) {
392 uint16_t feature_flags = NET_TO_HOST_U16(version->feature_flags);
393 log_debug("Client %u supports features: 0x%04x", atomic_load(&client->client_id), feature_flags);
394 }
395}
396
424void handle_client_leave_packet(client_info_t *client, const void *data, size_t len) {
425 if (!client) {
426 return;
427 }
428
429 uint32_t client_id = atomic_load(&client->client_id);
430
431 if (len == 0) {
432 // Empty reason - client disconnecting without explanation
433 log_info("Client %u sent leave notification (no reason)", client_id);
434 } else if (len <= 256) {
435 // Reason provided - extract and log it
436 if (!data) {
437 SET_ERRNO(ERROR_INVALID_STATE, "Client %u sent leave notification with non-zero length but NULL data", client_id);
438 return;
439 }
440
441 char reason[257] = {0};
442 memcpy(reason, data, len);
443 reason[len] = '\0';
444
445 // Validate reason is printable (handle potential non-UTF8 gracefully)
446 bool all_printable = true;
447 for (size_t i = 0; i < len; i++) {
448 uint8_t c = (uint8_t)reason[i];
449 if (c < 32 && c != '\t' && c != '\n') {
450 all_printable = false;
451 break;
452 }
453 }
454
455 if (all_printable) {
456 log_info("Client %u sent leave notification: %s", client_id, reason);
457 } else {
458 log_info("Client %u sent leave notification (reason contains non-printable characters)", client_id);
459 }
460 } else {
461 // Oversized reason - shouldn't happen with validation.h checks
462 log_warn("Client %u sent oversized leave reason (%zu bytes, max 256)", client_id, len);
463 }
464
465 // Deactivate client to stop processing packets
466 // Sets client->active = false immediately - triggers client cleanup procedures
467 atomic_store(&client->active, false);
468
469 // Note: We don't disconnect the client here - that happens when socket closes
470 // This is just a clean notification before disconnect
471}
472
509void handle_stream_start_packet(client_info_t *client, const void *data, size_t len) {
510 VALIDATE_PACKET_SIZE(client, data, len, sizeof(uint32_t), "STREAM_START");
511
512 uint32_t stream_type_net;
513 memcpy(&stream_type_net, data, sizeof(uint32_t));
514 uint32_t stream_type = NET_TO_HOST_U32(stream_type_net);
515
516 // Validate at least one stream type flag is set
517 const uint32_t VALID_STREAM_MASK = STREAM_TYPE_VIDEO | STREAM_TYPE_AUDIO;
518 VALIDATE_CAPABILITY_FLAGS(client, stream_type, VALID_STREAM_MASK, "STREAM_START");
519
520 // Validate no unknown stream type bits are set
521 VALIDATE_FLAGS_MASK(client, stream_type, VALID_STREAM_MASK, "STREAM_START");
522
523 if (stream_type & STREAM_TYPE_VIDEO) {
524 // Wait for first IMAGE_FRAME before marking sending_video true (avoids race)
525 }
526 if (stream_type & STREAM_TYPE_AUDIO) {
527 atomic_store(&client->is_sending_audio, true);
528
529 // Create Opus decoder for this client if not already created
530 if (!client->opus_decoder) {
532 if (client->opus_decoder) {
533 log_info("Client %u: Opus decoder created (48kHz)", atomic_load(&client->client_id));
534 } else {
535 log_error("Client %u: Failed to create Opus decoder", atomic_load(&client->client_id));
536 }
537 }
538 }
539
540 if (stream_type & STREAM_TYPE_VIDEO) {
541 log_info("Client %u announced video stream (waiting for first frame)", atomic_load(&client->client_id));
542 }
543 if (stream_type & STREAM_TYPE_AUDIO) {
544 log_info("Client %u started audio stream", atomic_load(&client->client_id));
545 }
546
547 // Notify client of stream start acknowledgment
548 const char *streams = (stream_type & STREAM_TYPE_VIDEO) && (stream_type & STREAM_TYPE_AUDIO)
549 ? "video+audio"
550 : ((stream_type & STREAM_TYPE_VIDEO) ? "video" : "audio");
551 log_info_client(client, "Stream started: %s", streams);
552}
553
589void handle_stream_stop_packet(client_info_t *client, const void *data, size_t len) {
590 VALIDATE_PACKET_SIZE(client, data, len, sizeof(uint32_t), "STREAM_STOP");
591
592 uint32_t stream_type_net;
593 memcpy(&stream_type_net, data, sizeof(uint32_t));
594 uint32_t stream_type = NET_TO_HOST_U32(stream_type_net);
595
596 // Validate at least one stream type flag is set
597 const uint32_t VALID_STREAM_MASK = STREAM_TYPE_VIDEO | STREAM_TYPE_AUDIO;
598 VALIDATE_CAPABILITY_FLAGS(client, stream_type, VALID_STREAM_MASK, "STREAM_STOP");
599
600 // Validate no unknown stream type bits are set
601 VALIDATE_FLAGS_MASK(client, stream_type, VALID_STREAM_MASK, "STREAM_STOP");
602
603 if (stream_type & STREAM_TYPE_VIDEO) {
604 atomic_store(&client->is_sending_video, false);
605 }
606 if (stream_type & STREAM_TYPE_AUDIO) {
607 atomic_store(&client->is_sending_audio, false);
608 }
609
610 if (stream_type & STREAM_TYPE_VIDEO) {
611 log_info("Client %u stopped video stream", atomic_load(&client->client_id));
612 }
613 if (stream_type & STREAM_TYPE_AUDIO) {
614 log_info("Client %u stopped audio stream", atomic_load(&client->client_id));
615 }
616
617 // Notify client of stream stop acknowledgment
618 const char *streams = (stream_type & STREAM_TYPE_VIDEO) && (stream_type & STREAM_TYPE_AUDIO)
619 ? "video+audio"
620 : ((stream_type & STREAM_TYPE_VIDEO) ? "video" : "audio");
621 log_info_client(client, "Stream stopped: %s", streams);
622}
623
624/* ============================================================================
625 * Media Data Packet Handlers
626 * ============================================================================
627 */
628
683void handle_image_frame_packet(client_info_t *client, void *data, size_t len) {
684 // Handle incoming image data from client
685 // New format: [width:4][height:4][compressed_flag:4][data_size:4][rgb_data:data_size]
686 // Old format: [width:4][height:4][rgb_data:w*h*3] (for backward compatibility)
687 // Use atomic compare-and-swap to avoid race condition - ensures thread-safe auto-enabling of video stream
688 if (!data || len < sizeof(uint32_t) * 2) {
689 disconnect_client_for_bad_data(client, "IMAGE_FRAME payload too small: %zu bytes", len);
690 return;
691 }
692 bool was_sending_video = atomic_load(&client->is_sending_video);
693 if (!was_sending_video) {
694 // Try to atomically enable video sending
695 // Use atomic_compare_exchange_strong to avoid spurious failures
696 if (atomic_compare_exchange_strong(&client->is_sending_video, &was_sending_video, true)) {
697 log_info("Client %u auto-enabled video stream (received IMAGE_FRAME)", atomic_load(&client->client_id));
698 // Notify client that their first video frame was received
699 log_info_client(client, "First video frame received - streaming active");
700 }
701 } else {
702 // Log periodically to confirm we're receiving frames
703 // Use per-client counter protected by client_state_mutex to avoid race conditions
705 client->frames_received_logged++;
706 if (client->frames_received_logged % 25000 == 0) {
707 char pretty[64];
708 format_bytes_pretty(len, pretty, sizeof(pretty));
709 log_debug("Client %u has sent %u IMAGE_FRAME packets (%s)", atomic_load(&client->client_id),
710 client->frames_received_logged, pretty);
711 }
713 }
714
715 // Parse image dimensions (use memcpy to avoid unaligned access)
716 uint32_t img_width_net, img_height_net;
717 memcpy(&img_width_net, data, sizeof(uint32_t));
718 memcpy(&img_height_net, (char *)data + sizeof(uint32_t), sizeof(uint32_t));
719 uint32_t img_width = NET_TO_HOST_U32(img_width_net);
720 uint32_t img_height = NET_TO_HOST_U32(img_height_net);
721
722 log_debug("IMAGE_FRAME packet: width=%u, height=%u, payload_len=%zu", img_width, img_height, len);
723
724 // Validate dimensions using image utility functions
725 if (image_validate_dimensions((size_t)img_width, (size_t)img_height) != ASCIICHAT_OK) {
726 log_error("IMAGE_FRAME validation failed for dimensions: %u x %u", img_width, img_height);
727 disconnect_client_for_bad_data(client, "IMAGE_FRAME invalid dimensions");
728 return;
729 }
730
731 // Calculate RGB buffer size with overflow checking
732 size_t rgb_size = 0;
733 if (image_calc_rgb_size((size_t)img_width, (size_t)img_height, &rgb_size) != ASCIICHAT_OK) {
734 disconnect_client_for_bad_data(client, "IMAGE_FRAME buffer size calculation failed");
735 return;
736 }
737
738 // Validate final buffer size against maximum
739 if (image_validate_buffer_size(rgb_size) != ASCIICHAT_OK) {
740 disconnect_client_for_bad_data(client, "IMAGE_FRAME buffer size exceeds maximum");
741 return;
742 }
743
744 // Check if this is the new compressed format (has 4 fields) or old format (has 2 fields)
745 const size_t legacy_header_size = sizeof(uint32_t) * 2;
746 if (rgb_size > SIZE_MAX - legacy_header_size) {
747 char size_str[32];
748 format_bytes_pretty(rgb_size, size_str, sizeof(size_str));
749 disconnect_client_for_bad_data(client, "IMAGE_FRAME legacy packet size overflow: %s", size_str);
750 return;
751 }
752 size_t old_format_size = legacy_header_size + rgb_size;
753 bool is_new_format = (len != old_format_size) && (len > sizeof(uint32_t) * 4);
754
755 void *rgb_data = NULL;
756 size_t rgb_data_size = 0;
757 bool needs_free = false;
758
759 if (is_new_format) {
760 // New format: [width:4][height:4][compressed_flag:4][data_size:4][data:data_size]
761 if (len < sizeof(uint32_t) * 4) {
762 disconnect_client_for_bad_data(client, "IMAGE_FRAME new-format header too small (%zu bytes)", len);
763 return;
764 }
765
766 // Use memcpy to avoid unaligned access for compressed_flag and data_size
767 uint32_t compressed_flag_net, data_size_net;
768 memcpy(&compressed_flag_net, (char *)data + sizeof(uint32_t) * 2, sizeof(uint32_t));
769 memcpy(&data_size_net, (char *)data + sizeof(uint32_t) * 3, sizeof(uint32_t));
770 uint32_t compressed_flag = NET_TO_HOST_U32(compressed_flag_net);
771 uint32_t data_size = NET_TO_HOST_U32(data_size_net);
772 void *frame_data = (char *)data + sizeof(uint32_t) * 4;
773
774 const size_t new_header_size = sizeof(uint32_t) * 4;
775 size_t data_size_sz = (size_t)data_size;
776 if (data_size_sz > IMAGE_MAX_PIXELS_SIZE) {
777 char size_str[32];
778 format_bytes_pretty(data_size_sz, size_str, sizeof(size_str));
779 disconnect_client_for_bad_data(client, "IMAGE_FRAME compressed data too large: %s", size_str);
780 return;
781 }
782 if (data_size_sz > SIZE_MAX - new_header_size) {
783 disconnect_client_for_bad_data(client, "IMAGE_FRAME new-format packet size overflow");
784 return;
785 }
786 size_t expected_total = new_header_size + data_size_sz;
787 if (len != expected_total) {
788 disconnect_client_for_bad_data(client, "IMAGE_FRAME new-format length mismatch: expected %zu got %zu",
789 expected_total, len);
790 return;
791 }
792
793 if (compressed_flag) {
794 // Decompress the data
795 rgb_data = SAFE_MALLOC(rgb_size, void *);
796 if (!rgb_data) {
797 SET_ERRNO(ERROR_MEMORY, "Failed to allocate decompression buffer for client %u",
798 atomic_load(&client->client_id));
799 return;
800 }
801
802 asciichat_error_t decompress_result = decompress_data(frame_data, data_size, rgb_data, rgb_size);
803 if (decompress_result != ASCIICHAT_OK) {
804 SAFE_FREE(rgb_data);
805 disconnect_client_for_bad_data(client, "IMAGE_FRAME decompression failure for %zu bytes: %s", data_size_sz,
806 asciichat_error_string(decompress_result));
807 return;
808 }
809
810 rgb_data_size = rgb_size;
811 needs_free = true;
812 } else {
813 // Uncompressed data
814 rgb_data = frame_data;
815 rgb_data_size = data_size;
816 if (rgb_data_size != rgb_size) {
817 disconnect_client_for_bad_data(client, "IMAGE_FRAME uncompressed size mismatch: expected %zu got %zu", rgb_size,
818 rgb_data_size);
819 return;
820 }
821 }
822 } else {
823 // Old format: [width:4][height:4][rgb_data:w*h*3]
824 if (len != old_format_size) {
825 disconnect_client_for_bad_data(client, "IMAGE_FRAME legacy length mismatch: expected %zu got %zu",
826 old_format_size, len);
827 return;
828 }
829 rgb_data = (char *)data + sizeof(uint32_t) * 2;
830 rgb_data_size = rgb_size;
831 }
832
833 if (client->incoming_video_buffer) {
834 // Get the write buffer
836
837 if (frame && frame->data) {
838 // Build the packet in the old format for internal storage: [width:4][height:4][rgb_data:w*h*3]
839 if (rgb_data_size > SIZE_MAX - legacy_header_size) {
840 if (needs_free && rgb_data) {
841 SAFE_FREE(rgb_data);
842 }
843 char size_str[32];
844 format_bytes_pretty(rgb_data_size, size_str, sizeof(size_str));
845 disconnect_client_for_bad_data(client, "IMAGE_FRAME size overflow while repacking: rgb_data_size=%s", size_str);
846 return;
847 }
848 size_t old_packet_size = legacy_header_size + rgb_data_size;
849
850 if (old_packet_size <= 2 * 1024 * 1024) { // Max 2MB frame size
851 uint32_t width_net = HOST_TO_NET_U32(img_width);
852 uint32_t height_net = HOST_TO_NET_U32(img_height);
853
854 // Pack in old format for internal consistency
855 memcpy(frame->data, &width_net, sizeof(uint32_t));
856 memcpy((char *)frame->data + sizeof(uint32_t), &height_net, sizeof(uint32_t));
857 memcpy((char *)frame->data + sizeof(uint32_t) * 2, rgb_data, rgb_data_size);
858
859 frame->size = old_packet_size;
860 frame->width = img_width;
861 frame->height = img_height;
862 frame->capture_timestamp_us = (uint64_t)time(NULL) * 1000000;
863 frame->sequence_number = ++client->frames_received;
865 } else {
866 if (needs_free && rgb_data) {
867 SAFE_FREE(rgb_data);
868 }
869 disconnect_client_for_bad_data(client, "IMAGE_FRAME repacked frame too large (%zu bytes)", old_packet_size);
870 return;
871 }
872 } else {
873 log_warn("Failed to get write buffer for client %u (frame=%p, frame->data=%p)", atomic_load(&client->client_id),
874 (void *)frame, frame ? frame->data : NULL);
875 }
876 } else {
877 // During shutdown, this is expected - don't spam error logs
878 if (!atomic_load(&g_server_should_exit)) {
879 SET_ERRNO(ERROR_INVALID_STATE, "Client %u has no incoming video buffer!", atomic_load(&client->client_id));
880 } else {
881 log_debug("Client %u: ignoring video packet during shutdown", atomic_load(&client->client_id));
882 }
883 }
884
885 // Clean up decompressed data if allocated
886 if (needs_free && rgb_data) {
887 SAFE_FREE(rgb_data);
888 }
889}
890
932void handle_audio_packet(client_info_t *client, const void *data, size_t len) {
933 VALIDATE_NOTNULL_DATA(client, data, "AUDIO");
934 VALIDATE_AUDIO_ALIGNMENT(client, len, sizeof(float), "AUDIO");
935 VALIDATE_AUDIO_STREAM_ENABLED(client, "AUDIO");
936
937 int num_samples = (int)(len / sizeof(float));
938 VALIDATE_AUDIO_SAMPLE_COUNT(client, num_samples, AUDIO_SAMPLES_PER_PACKET, "AUDIO");
939 VALIDATE_RESOURCE_INITIALIZED(client, client->incoming_audio_buffer, "audio buffer");
940
941 const float *samples = (const float *)data;
942 asciichat_error_t result = audio_ring_buffer_write(client->incoming_audio_buffer, samples, num_samples);
943 if (result != ASCIICHAT_OK) {
944 log_error("Failed to write audio samples to buffer: %s", asciichat_error_string(result));
945 }
946}
947
948void handle_remote_log_packet_from_client(client_info_t *client, const void *data, size_t len) {
949 if (!client) {
950 return;
951 }
952
953 log_level_t remote_level = LOG_INFO;
955 uint16_t flags = 0;
956 char message[MAX_REMOTE_LOG_MESSAGE_LENGTH + 1] = {0};
957
958 asciichat_error_t parse_result =
959 packet_parse_remote_log(data, len, &remote_level, &direction, &flags, message, sizeof(message), NULL);
960 if (parse_result != ASCIICHAT_OK) {
961 disconnect_client_for_bad_data(client, "Invalid REMOTE_LOG packet: %s", asciichat_error_string(parse_result));
962 return;
963 }
964
965 if (direction != REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER) {
966 disconnect_client_for_bad_data(client, "REMOTE_LOG direction mismatch: %u", direction);
967 return;
968 }
969
970 const bool truncated = (flags & REMOTE_LOG_FLAG_TRUNCATED) != 0;
971 const char *display_name = client->display_name[0] ? client->display_name : "(unnamed)";
972 uint32_t client_id = atomic_load(&client->client_id);
973
974 if (truncated) {
975 log_msg(remote_level, __FILE__, __LINE__, __func__, "[REMOTE CLIENT %u \"%s\"] %s [message truncated]", client_id,
976 display_name, message);
977 } else {
978 log_msg(remote_level, __FILE__, __LINE__, __func__, "[REMOTE CLIENT %u \"%s\"] %s", client_id, display_name,
979 message);
980 }
981}
982
1029void handle_audio_batch_packet(client_info_t *client, const void *data, size_t len) {
1030 // Log every audio batch packet reception
1031 log_debug_every(LOG_RATE_DEFAULT, "Received audio batch packet from client %u (len=%zu, is_sending_audio=%d)",
1032 atomic_load(&client->client_id), len, atomic_load(&client->is_sending_audio));
1033
1034 VALIDATE_NOTNULL_DATA(client, data, "AUDIO_BATCH");
1035 VALIDATE_MIN_SIZE(client, len, sizeof(audio_batch_packet_t), "AUDIO_BATCH");
1036 VALIDATE_AUDIO_STREAM_ENABLED(client, "AUDIO_BATCH");
1037
1038 // Parse batch header using utility function
1039 audio_batch_info_t batch_info;
1040 asciichat_error_t parse_result = audio_parse_batch_header(data, len, &batch_info);
1041 if (parse_result != ASCIICHAT_OK) {
1042 disconnect_client_for_bad_data(client, "Failed to parse audio batch header");
1043 return;
1044 }
1045
1046 uint32_t packet_batch_count = batch_info.batch_count;
1047 uint32_t total_samples = batch_info.total_samples;
1048 uint32_t sample_rate = batch_info.sample_rate;
1049
1050 (void)packet_batch_count;
1051 (void)sample_rate;
1052
1053 VALIDATE_NONZERO(client, packet_batch_count, "batch_count", "AUDIO_BATCH");
1054 VALIDATE_NONZERO(client, total_samples, "total_samples", "AUDIO_BATCH");
1055
1056 size_t samples_bytes = 0;
1057 if (safe_size_mul(total_samples, sizeof(uint32_t), &samples_bytes)) {
1058 disconnect_client_for_bad_data(client, "AUDIO_BATCH sample size overflow (samples=%u)", total_samples);
1059 return;
1060 }
1061
1062 size_t expected_size = sizeof(audio_batch_packet_t) + samples_bytes;
1063 if (len != expected_size) {
1064 disconnect_client_for_bad_data(client, "AUDIO_BATCH length mismatch: got %zu expected %zu", len, expected_size);
1065 return;
1066 }
1067
1068 // Bounds check to prevent integer overflow on allocation
1069 // Maximum allowed samples: AUDIO_BATCH_SAMPLES * 2 (2048 samples)
1070 // This prevents total_samples * sizeof(float) from exceeding 8KB
1071 const uint32_t MAX_AUDIO_SAMPLES = AUDIO_BATCH_SAMPLES * 2;
1072 if (total_samples > MAX_AUDIO_SAMPLES) {
1073 disconnect_client_for_bad_data(client, "AUDIO_BATCH too many samples: %u (max: %u)", total_samples,
1074 MAX_AUDIO_SAMPLES);
1075 return;
1076 }
1077
1078 const uint8_t *samples_ptr = (const uint8_t *)data + sizeof(audio_batch_packet_t);
1079
1080 // Safe allocation: total_samples is bounded above, so multiplication won't overflow
1081 size_t alloc_size = (size_t)total_samples * sizeof(float);
1082 float *samples = SAFE_MALLOC(alloc_size, float *);
1083 if (!samples) {
1084 SET_ERRNO(ERROR_MEMORY, "Failed to allocate memory for audio sample conversion");
1085 return;
1086 }
1087
1088 // Use helper function to dequantize samples
1089 asciichat_error_t dq_result = audio_dequantize_samples(samples_ptr, total_samples, samples);
1090 if (dq_result != ASCIICHAT_OK) {
1091 SAFE_FREE(samples);
1092 return;
1093 }
1094
1095#ifndef NDEBUG
1096 static int recv_count = 0;
1097 recv_count++;
1098 if (recv_count % 100 == 0) {
1099 uint32_t raw0 = bytes_read_u32_unaligned(samples_ptr + 0 * sizeof(uint32_t));
1100 uint32_t raw1 = bytes_read_u32_unaligned(samples_ptr + 1 * sizeof(uint32_t));
1101 uint32_t raw2 = bytes_read_u32_unaligned(samples_ptr + 2 * sizeof(uint32_t));
1102 int32_t scaled0 = (int32_t)NET_TO_HOST_U32(raw0);
1103 int32_t scaled1 = (int32_t)NET_TO_HOST_U32(raw1);
1104 int32_t scaled2 = (int32_t)NET_TO_HOST_U32(raw2);
1105 log_info("RECV: network[0]=0x%08x, network[1]=0x%08x, network[2]=0x%08x", raw0, raw1, raw2);
1106 log_info("RECV: scaled[0]=%d, scaled[1]=%d, scaled[2]=%d", scaled0, scaled1, scaled2);
1107 log_info("RECV: samples[0]=%.6f, samples[1]=%.6f, samples[2]=%.6f", samples[0], samples[1], samples[2]);
1108 }
1109#endif
1110
1111 if (client->incoming_audio_buffer) {
1112 asciichat_error_t write_result = audio_ring_buffer_write(client->incoming_audio_buffer, samples, total_samples);
1113 if (write_result != ASCIICHAT_OK) {
1114 log_error("Failed to write decoded audio batch to buffer: %s", asciichat_error_string(write_result));
1115 }
1116 }
1117
1118 SAFE_FREE(samples);
1119}
1120
1156void handle_audio_opus_batch_packet(client_info_t *client, const void *data, size_t len) {
1157 log_debug_every(LOG_RATE_SLOW, "Received Opus audio batch from client %u (len=%zu)", atomic_load(&client->client_id),
1158 len);
1159
1160 VALIDATE_NOTNULL_DATA(client, data, "AUDIO_OPUS_BATCH");
1161 VALIDATE_AUDIO_STREAM_ENABLED(client, "AUDIO_OPUS_BATCH");
1162 VALIDATE_RESOURCE_INITIALIZED(client, client->opus_decoder, "Opus decoder");
1163
1164 // Parse Opus batch packet
1165 const uint8_t *opus_data = NULL;
1166 size_t opus_size = 0;
1167 const uint16_t *frame_sizes = NULL;
1168 int sample_rate = 0;
1169 int frame_duration = 0;
1170 int frame_count = 0;
1171
1172 asciichat_error_t result = packet_parse_opus_batch(data, len, &opus_data, &opus_size, &frame_sizes, &sample_rate,
1173 &frame_duration, &frame_count);
1174
1175 if (result != ASCIICHAT_OK) {
1176 disconnect_client_for_bad_data(client, "Failed to parse AUDIO_OPUS_BATCH packet");
1177 return;
1178 }
1179
1180 VALIDATE_NONZERO(client, frame_count, "frame_count", "AUDIO_OPUS_BATCH");
1181 VALIDATE_NONZERO(client, opus_size, "opus_size", "AUDIO_OPUS_BATCH");
1182
1183 // Calculate samples per frame (20ms @ 48kHz = 960 samples)
1184 int samples_per_frame = (sample_rate * frame_duration) / 1000;
1185 VALIDATE_RANGE(client, samples_per_frame, 1, 4096, "samples_per_frame", "AUDIO_OPUS_BATCH");
1186
1187 // Use static buffer for common case to avoid malloc in hot path
1188 // Typical batches: 1-32 frames of 960 samples = up to 30,720 samples
1189 // Static buffer holds 32 frames @ 48kHz 20ms = 30,720 samples (120KB)
1190#define OPUS_DECODE_STATIC_MAX_SAMPLES (32 * 960)
1191 static float static_decode_buffer[OPUS_DECODE_STATIC_MAX_SAMPLES];
1192
1193 size_t total_samples = (size_t)samples_per_frame * (size_t)frame_count;
1194 float *decoded_samples;
1195 bool used_malloc = false;
1196
1197 if (total_samples <= OPUS_DECODE_STATIC_MAX_SAMPLES) {
1198 decoded_samples = static_decode_buffer;
1199 } else {
1200 // Unusual large batch - fall back to malloc
1201 log_warn("Client %u: Large audio batch requires malloc (%zu samples)", atomic_load(&client->client_id),
1202 total_samples);
1203 decoded_samples = SAFE_MALLOC(total_samples * sizeof(float), float *);
1204 if (!decoded_samples) {
1205 SET_ERRNO(ERROR_MEMORY, "Failed to allocate buffer for Opus decoded samples");
1206 return;
1207 }
1208 used_malloc = true;
1209 }
1210
1211 // Decode each Opus frame using frame_sizes array
1212 int total_decoded = 0;
1213 size_t opus_offset = 0;
1214
1215 for (int i = 0; i < frame_count; i++) {
1216 // Get exact frame size from frame_sizes array (convert from network byte order)
1217 size_t frame_size = (size_t)NET_TO_HOST_U16(frame_sizes[i]);
1218
1219 // DEBUG: Log the actual bytes of each Opus frame
1220 if (frame_size > 0) {
1221 log_debug_every(LOG_RATE_DEFAULT, "Client %u: Opus frame %d: size=%zu, first_bytes=[0x%02x,0x%02x,0x%02x,0x%02x]",
1222 atomic_load(&client->client_id), i, frame_size, opus_data[opus_offset] & 0xFF,
1223 frame_size > 1 ? (opus_data[opus_offset + 1] & 0xFF) : 0,
1224 frame_size > 2 ? (opus_data[opus_offset + 2] & 0xFF) : 0,
1225 frame_size > 3 ? (opus_data[opus_offset + 3] & 0xFF) : 0);
1226 }
1227
1228 if (opus_offset + frame_size > opus_size) {
1229 log_error("Client %u: Frame %d size overflow (offset=%zu, frame_size=%zu, total=%zu)",
1230 atomic_load(&client->client_id), i + 1, opus_offset, frame_size, opus_size);
1231 if (used_malloc) {
1232 SAFE_FREE(decoded_samples);
1233 }
1234 return;
1235 }
1236
1237 // SECURITY: Bounds check before writing decoded samples to prevent buffer overflow
1238 // An attacker could send malicious Opus frames that decode to more samples than expected
1239 if ((size_t)total_decoded + (size_t)samples_per_frame > total_samples) {
1240 log_error("Client %u: Opus decode would overflow buffer (decoded=%d, frame_samples=%d, max=%zu)",
1241 atomic_load(&client->client_id), total_decoded, samples_per_frame, total_samples);
1242 if (used_malloc) {
1243 SAFE_FREE(decoded_samples);
1244 }
1245 return;
1246 }
1247
1248 int decoded_count = opus_codec_decode((opus_codec_t *)client->opus_decoder, &opus_data[opus_offset], frame_size,
1249 &decoded_samples[total_decoded], samples_per_frame);
1250
1251 if (decoded_count < 0) {
1252 log_error("Client %u: Opus decoding failed for frame %d/%d (size=%zu)", atomic_load(&client->client_id), i + 1,
1253 frame_count, frame_size);
1254 if (used_malloc) {
1255 SAFE_FREE(decoded_samples);
1256 }
1257 return;
1258 }
1259
1260 total_decoded += decoded_count;
1261 opus_offset += frame_size;
1262 }
1263
1264 log_debug_every(LOG_RATE_DEFAULT, "Client %u: Decoded %d Opus frames -> %d samples", atomic_load(&client->client_id),
1265 frame_count, total_decoded);
1266
1267 // DEBUG: Log sample values to detect all-zero issue
1268 static int server_decode_count = 0;
1269 server_decode_count++;
1270 if (total_decoded > 0 && (server_decode_count <= 10 || server_decode_count % 100 == 0)) {
1271 float peak = 0.0f, rms = 0.0f;
1272 for (int i = 0; i < total_decoded && i < 100; i++) {
1273 float abs_val = fabsf(decoded_samples[i]);
1274 if (abs_val > peak)
1275 peak = abs_val;
1276 rms += decoded_samples[i] * decoded_samples[i];
1277 }
1278 rms = sqrtf(rms / (total_decoded > 100 ? 100 : total_decoded));
1279 // Log first 4 bytes of Opus data to compare with client encode
1280 log_info("SERVER OPUS DECODE #%d from client %u: decoded_rms=%.6f, opus_first4=[0x%02x,0x%02x,0x%02x,0x%02x]",
1281 server_decode_count, atomic_load(&client->client_id), rms, opus_size > 0 ? opus_data[0] : 0,
1282 opus_size > 1 ? opus_data[1] : 0, opus_size > 2 ? opus_data[2] : 0, opus_size > 3 ? opus_data[3] : 0);
1283 }
1284
1285 // Write decoded samples to client's incoming audio buffer
1286 // Note: audio_ring_buffer_write returns error code, not sample count
1287 // Buffer overflow warnings are logged inside audio_ring_buffer_write if buffer is full
1288 if (client->incoming_audio_buffer && total_decoded > 0) {
1289 asciichat_error_t result = audio_ring_buffer_write(client->incoming_audio_buffer, decoded_samples, total_decoded);
1290 if (result != ASCIICHAT_OK) {
1291 log_error("Client %u: Failed to write decoded audio to buffer: %d", atomic_load(&client->client_id), result);
1292 }
1293 }
1294
1295 if (used_malloc) {
1296 SAFE_FREE(decoded_samples);
1297 }
1298}
1299
1318void handle_audio_opus_packet(client_info_t *client, const void *data, size_t len) {
1319 log_debug_every(LOG_RATE_DEFAULT, "Received Opus audio from client %u (len=%zu)", atomic_load(&client->client_id),
1320 len);
1321
1322 if (VALIDATE_PACKET_NOT_NULL(client, data, "AUDIO_OPUS")) {
1323 return;
1324 }
1325
1326 // Minimum size: 16-byte header + at least 1 byte of Opus data
1327 if (len < 17) {
1328 disconnect_client_for_bad_data(client, "AUDIO_OPUS packet too small: %zu bytes", len);
1329 return;
1330 }
1331
1332 if (!atomic_load(&client->is_sending_audio)) {
1333 disconnect_client_for_bad_data(client, "AUDIO_OPUS received before audio stream enabled");
1334 return;
1335 }
1336
1337 if (!client->opus_decoder) {
1338 disconnect_client_for_bad_data(client, "Opus decoder not initialized");
1339 return;
1340 }
1341
1342 // Parse header (16 bytes) - convert from network byte order
1343 const uint8_t *buf = (const uint8_t *)data;
1344 uint32_t sample_rate_net, frame_duration_net;
1345 memcpy(&sample_rate_net, buf, 4);
1346 memcpy(&frame_duration_net, buf + 4, 4);
1347 uint32_t sample_rate = NET_TO_HOST_U32(sample_rate_net);
1348 uint32_t frame_duration = NET_TO_HOST_U32(frame_duration_net);
1349
1350 // Extract Opus data (after 16-byte header)
1351 const uint8_t *opus_data = buf + 16;
1352 size_t opus_size = len - 16;
1353
1354 // Validate parameters
1355 if (sample_rate == 0 || sample_rate > 192000) {
1356 disconnect_client_for_bad_data(client, "AUDIO_OPUS invalid sample_rate: %u", sample_rate);
1357 return;
1358 }
1359
1360 if (frame_duration == 0 || frame_duration > 120) {
1361 disconnect_client_for_bad_data(client, "AUDIO_OPUS invalid frame_duration: %u ms", frame_duration);
1362 return;
1363 }
1364
1365 // Calculate expected samples per frame
1366 int samples_per_frame = (int)((sample_rate * frame_duration) / 1000);
1367 if (samples_per_frame <= 0 || samples_per_frame > 5760) { // Max 120ms @ 48kHz
1368 disconnect_client_for_bad_data(client, "AUDIO_OPUS invalid samples_per_frame: %d", samples_per_frame);
1369 return;
1370 }
1371
1372 // Decode Opus frame
1373 float decoded_samples[5760]; // Max Opus frame size (120ms @ 48kHz)
1374 int decoded_count =
1375 opus_codec_decode((opus_codec_t *)client->opus_decoder, opus_data, opus_size, decoded_samples, samples_per_frame);
1376
1377 if (decoded_count < 0) {
1378 log_error("Client %u: Opus decoding failed (size=%zu)", atomic_load(&client->client_id), opus_size);
1379 return;
1380 }
1381
1382 log_debug_every(LOG_RATE_VERY_FAST, "Client %u: Decoded Opus frame -> %d samples", atomic_load(&client->client_id),
1383 decoded_count);
1384
1385 // Write decoded samples to client's incoming audio buffer
1386 if (client->incoming_audio_buffer && decoded_count > 0) {
1387 asciichat_error_t write_result =
1388 audio_ring_buffer_write(client->incoming_audio_buffer, decoded_samples, decoded_count);
1389 if (write_result != ASCIICHAT_OK) {
1390 log_error("Failed to write decoded Opus samples to buffer: %s", asciichat_error_string(write_result));
1391 }
1392 }
1393}
1394
1395/* ============================================================================
1396 * Client Configuration Packet Handlers
1397 * ============================================================================
1398 */
1399
1464void handle_client_capabilities_packet(client_info_t *client, const void *data, size_t len) {
1465 VALIDATE_PACKET_SIZE(client, data, len, sizeof(terminal_capabilities_packet_t), "CLIENT_CAPABILITIES");
1466
1468
1469 // Extract and validate dimensions
1470 uint16_t width = NET_TO_HOST_U16(caps->width);
1471 uint16_t height = NET_TO_HOST_U16(caps->height);
1472
1473 VALIDATE_NONZERO(client, width, "width", "CLIENT_CAPABILITIES");
1474 VALIDATE_NONZERO(client, height, "height", "CLIENT_CAPABILITIES");
1475 VALIDATE_RANGE(client, width, 1, 4096, "width", "CLIENT_CAPABILITIES");
1476 VALIDATE_RANGE(client, height, 1, 4096, "height", "CLIENT_CAPABILITIES");
1477
1478 // Extract and validate color level (0=none, 1=16, 2=256, 3=truecolor)
1479 uint32_t color_level = NET_TO_HOST_U32(caps->color_level);
1480 VALIDATE_RANGE(client, color_level, 0, 3, "color_level", "CLIENT_CAPABILITIES");
1481
1482 // Extract and validate render mode (0=foreground, 1=background, 2=half-block)
1483 uint32_t render_mode = NET_TO_HOST_U32(caps->render_mode);
1484 VALIDATE_RANGE(client, render_mode, 0, 2, "render_mode", "CLIENT_CAPABILITIES");
1485
1486 // Extract and validate palette type (0-5 are valid, 5=PALETTE_CUSTOM)
1487 uint32_t palette_type = NET_TO_HOST_U32(caps->palette_type);
1488 VALIDATE_RANGE(client, palette_type, 0, 5, "palette_type", "CLIENT_CAPABILITIES");
1489
1490 // Validate desired FPS (1-144)
1491 VALIDATE_RANGE(client, caps->desired_fps, 1, 144, "desired_fps", "CLIENT_CAPABILITIES");
1492
1494
1495 atomic_store(&client->width, width);
1496 atomic_store(&client->height, height);
1497
1498 log_debug("Client %u dimensions: %ux%u, desired_fps=%u", atomic_load(&client->client_id), client->width,
1499 client->height, caps->desired_fps);
1500
1502 client->terminal_caps.color_level = color_level;
1504 client->terminal_caps.render_mode = render_mode;
1506 client->terminal_caps.wants_background = (render_mode == RENDER_MODE_BACKGROUND);
1507
1508 SAFE_STRNCPY(client->terminal_caps.term_type, caps->term_type, sizeof(client->terminal_caps.term_type));
1509 SAFE_STRNCPY(client->terminal_caps.colorterm, caps->colorterm, sizeof(client->terminal_caps.colorterm));
1510
1512 client->terminal_caps.palette_type = palette_type;
1514 sizeof(client->terminal_caps.palette_custom));
1515
1516 client->terminal_caps.desired_fps = caps->desired_fps;
1517
1518 const char *custom_chars =
1521 : NULL;
1522
1524 client->client_palette_chars, &client->client_palette_len,
1525 client->client_luminance_palette) == 0) {
1527 client->client_palette_initialized = true;
1528 log_info("Client %d palette initialized: type=%u, %zu chars, utf8=%u", atomic_load(&client->client_id),
1530 } else {
1531 SET_ERRNO(ERROR_INVALID_STATE, "Failed to initialize palette for client %d", atomic_load(&client->client_id));
1532 client->client_palette_initialized = false;
1533 }
1534
1535 client->has_terminal_caps = true;
1536
1537 log_info("Client %u capabilities: %ux%u, color_level=%s (%u colors), caps=0x%x, term=%s, colorterm=%s, "
1538 "render_mode=%s, reliable=%s, fps=%u",
1539 atomic_load(&client->client_id), client->width, client->height,
1543 ? "half-block"
1544 : (client->terminal_caps.render_mode == RENDER_MODE_BACKGROUND ? "background" : "foreground")),
1545 client->terminal_caps.detection_reliable ? "yes" : "no", client->terminal_caps.desired_fps);
1546
1547 // Send capabilities acknowledgment to client
1548 log_info_client(client, "Terminal configured: %ux%u, %s, %s mode, %u fps", client->width, client->height,
1551 ? "half-block"
1552 : (client->terminal_caps.render_mode == RENDER_MODE_BACKGROUND ? "background" : "foreground")),
1553 client->terminal_caps.desired_fps);
1554
1556}
1557
1592void handle_size_packet(client_info_t *client, const void *data, size_t len) {
1593 VALIDATE_PACKET_SIZE(client, data, len, sizeof(size_packet_t), "SIZE");
1594
1595 const size_packet_t *size_pkt = (const size_packet_t *)data;
1596
1597 // Extract and validate new dimensions
1598 uint16_t width = NET_TO_HOST_U16(size_pkt->width);
1599 uint16_t height = NET_TO_HOST_U16(size_pkt->height);
1600
1601 VALIDATE_NONZERO(client, width, "width", "SIZE");
1602 VALIDATE_NONZERO(client, height, "height", "SIZE");
1603 VALIDATE_RANGE(client, width, 1, 4096, "width", "SIZE");
1604 VALIDATE_RANGE(client, height, 1, 4096, "height", "SIZE");
1605
1607 client->width = width;
1608 client->height = height;
1610
1611 log_info("Client %u updated terminal size: %ux%u", atomic_load(&client->client_id), width, height);
1612}
1613
1614/* ============================================================================
1615 * Protocol Control Packet Handlers
1616 * ============================================================================
1617 */
1618
1652 // PONG responses should be handled directly via socket in send thread
1653 // For now, just log the ping
1654 (void)client;
1655}
1656
1657/* ============================================================================
1658 * Protocol Utility Functions
1659 * ============================================================================
1660 */
1661
1705 if (!client) {
1706 return -1;
1707 }
1708
1709 // Count active clients - LOCK OPTIMIZATION: Use atomic reads, no rwlock needed
1710 int active_count = 0;
1711 for (int i = 0; i < MAX_CLIENTS; i++) {
1712 if (atomic_load(&g_client_manager.clients[i].active)) {
1713 active_count++;
1714 }
1715 }
1716
1717 // Prepare server state packet
1719 state.connected_client_count = active_count;
1720 state.active_client_count = active_count; // For now, all connected are active
1721 memset(state.reserved, 0, sizeof(state.reserved));
1722
1723 // Convert to network byte order
1724 server_state_packet_t net_state;
1727 memset(net_state.reserved, 0, sizeof(net_state.reserved));
1728
1729 // Send server state via ACIP transport
1730 // CRITICAL: Protect socket writes with send_mutex to prevent race with send_thread
1731 mutex_lock(&client->send_mutex);
1732 asciichat_error_t result = acip_send_server_state(client->transport, &net_state);
1733 mutex_unlock(&client->send_mutex);
1734
1735 if (result != ASCIICHAT_OK) {
1736 SET_ERRNO(ERROR_NETWORK, "Failed to send server state to client %u: %s", client->client_id,
1737 asciichat_error_string(result));
1738 return -1;
1739 }
1740
1741 log_debug("Sent server state to client %u: %u connected, %u active", client->client_id, state.connected_client_count,
1742 state.active_client_count);
1743 return 0;
1744}
1745
1771// NOTE: This function is no longer used - CLEAR_CONSOLE is now sent directly
1772// from each client's render thread when it detects a grid layout change.
1773// Keeping this for reference but it should not be called.
1775 SET_ERRNO(ERROR_INVALID_STATE, "broadcast_clear_console_to_all_clients() called - unexpected usage");
1776 log_warn("CLEAR_CONSOLE is now sent from render threads, not broadcast");
1777}
🔢 Byte-Level Access and Arithmetic Utilities
📦 Network Packet Compression Utilities
const crypto_context_t * crypto_handshake_get_context(const crypto_handshake_context_t *ctx)
Get the crypto context for encryption/decryption.
Common declarations and data structures for cryptographic handshake.
🔄 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
opus_codec_t * opus_codec_create_decoder(int sample_rate)
Create an Opus decoder.
Definition opus_codec.c:62
asciichat_error_t audio_ring_buffer_write(audio_ring_buffer_t *rb, const float *data, int samples)
Write audio samples to ring buffer.
int opus_codec_decode(opus_codec_t *codec, const uint8_t *data, size_t data_len, float *out_samples, int out_num_samples)
Decode Opus audio frame.
Definition opus_codec.c:128
asciichat_error_t audio_parse_batch_header(const void *data, size_t len, audio_batch_info_t *out_batch)
Parse an audio batch packet header from raw packet data.
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.
unsigned short uint16_t
Definition common.h:57
unsigned int uint32_t
Definition common.h:58
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:358
#define SAFE_FREE(ptr)
Definition common.h:320
#define SAFE_MALLOC(size, cast)
Definition common.h:208
#define safe_size_mul
Definition common.h:408
unsigned long long uint64_t
Definition common.h:59
unsigned char uint8_t
Definition common.h:56
asciichat_error_t decompress_data(const void *input, size_t input_size, void *output, size_t output_size)
Decompress data using zstd.
Definition compression.c:58
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:46
@ ERROR_INVALID_STATE
@ ERROR_NETWORK
Definition error_codes.h:69
@ ERROR_NETWORK_PROTOCOL
Definition error_codes.h:73
@ ERROR_MEMORY
Definition error_codes.h:53
@ ASCIICHAT_OK
Definition error_codes.h:48
#define MAX_CLIENTS
Maximum possible clients (static array size) - actual runtime limit set by –max-clients (1-32)
Definition limits.h:23
#define MAX_DISPLAY_NAME_LEN
Maximum display name length in characters.
Definition limits.h:20
#define LOG_RATE_VERY_FAST
Log rate limit: 0.1 seconds (100,000 microseconds) - for very high frequency operations.
Definition log_rates.h:23
#define LOG_RATE_SLOW
Log rate limit: 10 seconds (10,000,000 microseconds)
Definition log_rates.h:35
#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_info_client(client, fmt,...)
Server sends INFO log message to client.
#define log_error(...)
Log an ERROR message.
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
asciichat_error_t log_network_message(socket_t sockfd, const struct crypto_context_t *crypto_ctx, log_level_t level, remote_log_direction_t direction, const char *fmt,...)
Send a formatted log message over the network.
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.
@ REMOTE_LOG_DIRECTION_SERVER_TO_CLIENT
@ REMOTE_LOG_DIRECTION_UNKNOWN
@ REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER
@ LOG_ERROR
Definition log/logging.h:64
@ LOG_INFO
Definition log/logging.h:62
uint32_t capabilities
Terminal capabilities bitmask (TERM_CAP_* flags)
Definition packet.h:911
uint32_t color_level
Color level enum value (terminal_color_mode_t)
Definition packet.h:913
uint32_t color_count
Actual color count (16, 256, or 16777216)
Definition packet.h:915
uint32_t reserved[6]
Reserved fields for future use (must be zero)
Definition packet.h:604
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
char display_name[MAX_DISPLAY_NAME_LEN]
User display name (null-terminated, max MAX_DISPLAY_NAME_LEN bytes)
Definition packet.h:549
asciichat_error_t packet_send_error(socket_t sockfd, const crypto_context_t *crypto_ctx, asciichat_error_t error_code, const char *message)
Send an error packet with optional encryption context.
Definition packet.c:807
uint16_t height
Terminal height in characters.
Definition packet.h:921
uint32_t active_client_count
Number of clients actively sending video/audio streams.
Definition packet.h:602
uint8_t reserved[7]
Reserved bytes for future expansion (must be zero)
Definition packet.h:724
uint32_t connected_client_count
Total number of currently connected clients.
Definition packet.h:600
#define CLIENT_CAP_STRETCH
Client can stretch frames to fill terminal.
Definition packet.h:818
#define STREAM_TYPE_VIDEO
Video stream.
Definition packet.h:829
uint32_t height
Terminal height in characters.
Definition packet.h:533
uint16_t protocol_revision
Minor protocol revision (server can be newer)
Definition packet.h:714
uint16_t protocol_version
Major protocol version (must match for compatibility)
Definition packet.h:712
#define STREAM_TYPE_AUDIO
Audio stream.
Definition packet.h:830
#define CLIENT_CAP_COLOR
Client supports color rendering.
Definition packet.h:817
uint32_t render_mode
Render mode enum value (foreground/background/half-block)
Definition packet.h:917
uint16_t feature_flags
Feature flags bitmask (FEATURE_RLE_ENCODING, etc.)
Definition packet.h:722
uint16_t width
Terminal width in characters.
Definition packet.h:919
uint8_t desired_fps
Client's desired frame rate (1-144 FPS)
Definition packet.h:935
uint8_t detection_reliable
Detection reliability flag (1=reliable detection, 0=best guess)
Definition packet.h:927
char palette_custom[64]
Custom palette characters (if palette_type == PALETTE_CUSTOM)
Definition packet.h:933
#define REMOTE_LOG_FLAG_TRUNCATED
Remote log packet flag definitions.
Definition packet.h:628
uint8_t compression_algorithms
Supported compression algorithms bitmask (COMPRESS_ALGO_*)
Definition packet.h:718
uint32_t width
Terminal width in characters.
Definition packet.h:531
char colorterm[32]
$COLORTERM environment variable value (for debugging)
Definition packet.h:925
uint32_t palette_type
Palette type enum value (palette_type_t)
Definition packet.h:931
uint32_t capabilities
Client capabilities bitmask (CLIENT_CAP_VIDEO | CLIENT_CAP_AUDIO | CLIENT_CAP_COLOR | CLIENT_CAP_STRE...
Definition packet.h:552
#define CLIENT_CAP_AUDIO
Client can send/receive audio.
Definition packet.h:816
#define CLIENT_CAP_VIDEO
Client can send/receive video.
Definition packet.h:815
uint32_t utf8_support
UTF-8 support flag (0=no UTF-8, 1=UTF-8 supported)
Definition packet.h:929
uint8_t supports_encryption
Encryption support flag (1=support encryption, 0=plaintext only)
Definition packet.h:716
char term_type[32]
$TERM environment variable value (for debugging)
Definition packet.h:923
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.
void packet_queue_shutdown(packet_queue_t *queue)
Signal queue shutdown (causes dequeue to return NULL)
#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
int initialize_client_palette(palette_type_t palette_type, const char *custom_chars, char client_palette_chars[256], size_t *client_palette_len, char client_luminance_palette[256])
Initialize client palette with full configuration.
Definition palette.c:326
palette_type_t
Built-in palette type enumeration.
Definition palette.h:84
@ PALETTE_CUSTOM
User-defined via –palette-chars.
Definition palette.h:96
int socket_shutdown(socket_t sock, int how)
Shutdown socket I/O.
const char * terminal_color_level_name(terminal_color_mode_t level)
Get name of color level.
int socket_t
Socket handle type (POSIX: int)
Definition socket.h:50
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
Definition mutex.h:140
#define INVALID_SOCKET_VALUE
Invalid socket value (POSIX: -1)
Definition socket.h:52
int socket_close(socket_t sock)
Close a socket.
void platform_sleep_ms(unsigned int ms)
Sleep for a specified number of milliseconds.
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
Definition mutex.h:175
@ RENDER_MODE_BACKGROUND
Background colors (block colors)
Definition terminal.h:471
@ RENDER_MODE_HALF_BLOCK
Unicode half-block characters (mixed foreground/background)
Definition terminal.h:473
#define PROTOCOL_VERSION_MAJOR
Major protocol version number.
#define PROTOCOL_VERSION_MINOR
Minor protocol version number.
void handle_audio_opus_packet(client_info_t *client, const void *data, size_t len)
Process AUDIO_OPUS packet - decode single Opus frame from client.
void handle_audio_opus_batch_packet(client_info_t *client, const void *data, size_t len)
Process AUDIO_OPUS_BATCH packet - efficient Opus-encoded audio batch from client.
void format_bytes_pretty(size_t bytes, char *out, size_t out_capacity)
Format byte count into human-readable string.
Definition format.c:10
#define VALIDATE_PACKET_NOT_NULL(client, data, packet_name)
#define VALIDATE_AUDIO_SAMPLE_COUNT(client, num_samples, max_samples, packet_name)
#define VALIDATE_RESOURCE_INITIALIZED(client, resource, resource_name)
#define VALIDATE_AUDIO_STREAM_ENABLED(client, packet_name)
#define VALIDATE_AUDIO_ALIGNMENT(client, len, sample_size, packet_name)
#define VALIDATE_NONZERO(client, value, value_name, packet_name)
#define VALIDATE_MIN_SIZE(client, len, min_size, packet_name)
#define VALIDATE_PACKET_SIZE(client, data, len, expected_size, packet_name)
#define VALIDATE_NOTNULL_DATA(client, data, packet_name)
#define VALIDATE_FLAGS_MASK(client, flags, valid_mask, packet_name)
#define VALIDATE_RANGE(client, value, min_val, max_val, value_name, packet_name)
void disconnect_client_for_bad_data(client_info_t *client, const char *format,...)
#define VALIDATE_CAPABILITY_FLAGS(client, flags, valid_mask, packet_name)
video_frame_t * video_frame_begin_write(video_frame_buffer_t *vfb)
Writer API: Start writing a new frame.
void video_frame_commit(video_frame_buffer_t *vfb)
Writer API: Commit the frame and swap buffers.
#define IMAGE_MAX_PIXELS_SIZE
Maximum pixel data size in bytes.
🔊 Audio Capture and Playback Interface for ascii-chat
asciichat_error_t acip_send_server_state(acip_transport_t *transport, const server_state_packet_t *state)
Send server state update to client (server → client)
ACIP server-side protocol library.
🔢 Mathematical Utility Functions
Network logging macros and remote log direction enumeration.
Opus audio codec wrapper for real-time encoding/decoding.
Shared packet parsing utilities to eliminate duplication between server and client handlers.
ASCII Palette Management for Video-to-ASCII Conversion.
ACIP shared/bidirectional packet sending functions.
void handle_client_join_packet(client_info_t *client, const void *data, size_t len)
Process CLIENT_JOIN packet - client announces identity and capabilities.
void handle_protocol_version_packet(client_info_t *client, const void *data, size_t len)
Process PROTOCOL_VERSION packet - validate protocol compatibility.
void handle_image_frame_packet(client_info_t *client, void *data, size_t len)
Process IMAGE_FRAME packet - store client's video data for rendering.
void handle_audio_batch_packet(client_info_t *client, const void *data, size_t len)
Process AUDIO_BATCH packet - store efficiently batched audio samples.
void handle_size_packet(client_info_t *client, const void *data, size_t len)
Process terminal size update packet - handle client window resize.
void handle_audio_packet(client_info_t *client, const void *data, size_t len)
Process AUDIO packet - store single audio sample batch (legacy format)
void handle_client_leave_packet(client_info_t *client, const void *data, size_t len)
Process CLIENT_LEAVE packet - handle clean client disconnect.
#define OPUS_DECODE_STATIC_MAX_SAMPLES
void handle_stream_stop_packet(client_info_t *client, const void *data, size_t len)
Process STREAM_STOP packet - client requests to halt media transmission.
int send_server_state_to_client(client_info_t *client)
Send current server state to a specific client.
void handle_client_capabilities_packet(client_info_t *client, const void *data, size_t len)
Process CLIENT_CAPABILITIES packet - configure client-specific rendering.
void broadcast_clear_console_to_all_clients(void)
Signal all active clients to clear their displays before next video frame.
atomic_bool g_server_should_exit
Global shutdown flag from main.c - used to avoid error spam during shutdown.
void handle_stream_start_packet(client_info_t *client, const void *data, size_t len)
Process STREAM_START packet - client requests to begin media transmission.
void handle_ping_packet(client_info_t *client)
Process PING packet - respond with PONG for keepalive.
void handle_remote_log_packet_from_client(client_info_t *client, const void *data, size_t len)
client_manager_t g_client_manager
Global client manager singleton - central coordination point.
Per-client state management and lifecycle orchestration.
Server packet processing and protocol implementation.
Parsed audio batch packet header information.
uint32_t batch_count
Number of audio frames in this batch.
uint32_t sample_rate
Sample rate in Hz (e.g., 48000)
uint32_t total_samples
Total number of samples across all frames.
Audio batch packet structure (Packet Type 28)
Definition packet.h:796
Client information packet structure.
Definition packet.h:545
Per-client state structure for server-side client management.
atomic_bool video_render_thread_running
terminal_capabilities_t terminal_caps
atomic_uint client_id
acip_transport_t * transport
char display_name[MAX_DISPLAY_NAME_LEN]
uint32_t frames_received_logged
uint64_t frames_received
crypto_handshake_context_t crypto_handshake_ctx
char client_palette_chars[256]
atomic_ushort height
video_frame_buffer_t * incoming_video_buffer
atomic_ushort width
bool client_palette_initialized
audio_ring_buffer_t * incoming_audio_buffer
atomic_bool protocol_disconnect_requested
mutex_t client_state_mutex
atomic_bool is_sending_audio
atomic_bool audio_render_thread_running
packet_queue_t * audio_queue
atomic_bool shutting_down
char client_luminance_palette[256]
atomic_bool is_sending_video
palette_type_t client_palette_type
atomic_bool active
atomic_bool send_thread_running
client_info_t clients[MAX_CLIENTS]
Array of client_info_t structures (backing storage)
Cryptographic context structure.
Opus codec context for encoding or decoding.
Definition opus_codec.h:95
Protocol version negotiation packet structure (Packet Type 1)
Definition packet.h:710
Server state packet structure.
Definition packet.h:598
Terminal size update packet.
Definition packet.h:529
Terminal capabilities packet structure (Packet Type 5)
Definition packet.h:909
int palette_type
Palette type enum value (palette_type_t)
Definition terminal.h:505
terminal_color_mode_t color_level
Detected color support level (terminal_color_mode_t)
Definition terminal.h:487
char palette_custom[64]
Custom palette characters (if palette_type == PALETTE_CUSTOM)
Definition terminal.h:507
uint8_t desired_fps
Client's desired frame rate (1-144 FPS)
Definition terminal.h:509
render_mode_t render_mode
Preferred rendering mode (render_mode_t)
Definition terminal.h:497
bool utf8_support
True if terminal supports UTF-8 encoding.
Definition terminal.h:493
uint32_t capabilities
Capability flags bitmask (terminal_capability_flags_t)
Definition terminal.h:489
uint32_t color_count
Maximum number of colors (16, 256, or 16777216)
Definition terminal.h:491
char term_type[64]
$TERM environment variable value (for debugging)
Definition terminal.h:499
char colorterm[64]
$COLORTERM environment variable value (for debugging)
Definition terminal.h:501
bool detection_reliable
True if detection is confident (reliable detection)
Definition terminal.h:495
bool wants_background
True if background colors are preferred.
Definition terminal.h:503
Video frame structure.
uint64_t sequence_number
Frame sequence number (for drop detection)
uint32_t height
Frame height in pixels.
uint64_t capture_timestamp_us
Timestamp when frame was captured (microseconds)
size_t size
Size of frame data in bytes.
uint32_t width
Frame width in pixels.
void * data
Frame data pointer (points to pre-allocated buffer)
Cross-platform system functions interface for ascii-chat.
⏱️ High-precision timing utilities using sokol_time.h and uthash
asciichat_error_t image_validate_buffer_size(size_t requested_size)
Validate buffer size against maximum allocation limit.
Definition util/image.c:115
asciichat_error_t image_validate_dimensions(size_t width, size_t height)
Validate image dimensions (non-zero, within limits)
Definition util/image.c:100
asciichat_error_t image_calc_rgb_size(size_t width, size_t height, size_t *out_size)
Calculate total RGB buffer size from dimensions.
Definition util/image.c:54
🖼️ Safe overflow-checked buffer size calculations for images and video frames
Common validation macros to reduce duplication in protocol handlers.
Image Data Structures and Operations.
Common SIMD utilities and structures.