ascii-chat 0.11.33
Video chat in your terminal
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 <ascii-chat/atomic.h>
115#include <stdio.h>
116#include <string.h>
117#include <time.h>
118
119#include "main.h"
120#include "protocol.h"
121
122#include "client.h"
123#include "session/h265/server.h"
125#include <ascii-chat/common.h>
132#include <ascii-chat/util/time.h>
149
150static void protocol_cleanup_thread_locals(void) {
151 // Placeholder for thread-local resources owned by the receive thread.
152 // Add cleanup logic here if future protocol changes introduce
153 // thread-local allocations that must be released before disconnecting.
154}
155
156void disconnect_client_for_bad_data(client_info_t *client, const char *format, ...) {
157 if (!client) {
158 return;
159 }
160
161 protocol_cleanup_thread_locals();
162
163 // Atomically set the disconnect flag and get the old value
164 // This ensures only one thread proceeds to disconnect (the first one)
165 bool already_requested = atomic_exchange_bool(&client->protocol_disconnect_requested, true);
166 if (already_requested) {
167 return; // Already disconnecting, don't do it again
168 }
169
170 char reason[BUFFER_SIZE_SMALL] = {0};
171 if (format) {
172 va_list args;
173 va_start(args, format);
174 safe_vsnprintf(reason, sizeof(reason), format, args);
175 va_end(args);
176 } else {
177 SAFE_STRNCPY(reason, "Protocol violation", sizeof(reason));
178 }
179
180 const char *reason_str = reason[0] != '\0' ? reason : "Protocol violation";
181 const char *client_id = client->client_id;
182
183 socket_t socket_snapshot = INVALID_SOCKET_VALUE;
184 const crypto_context_t *crypto_ctx = NULL;
185 acip_transport_t *transport_snapshot = NULL;
186
188 if (client->socket != INVALID_SOCKET_VALUE) {
189 socket_snapshot = client->socket;
190 if (client->crypto_initialized) {
192 }
193 }
195
196 // Get transport reference for WebSocket clients
197 mutex_lock(&client->send_mutex);
198 transport_snapshot = client->transport;
199 mutex_unlock(&client->send_mutex);
200
201 // NOTE: Disconnecting a client due to the client's own bad behavior isn't an
202 // error for us, it's desired behavior for us, so we simply warn and do not
203 // have a need for asciichat_errno here.
204 log_warn("Disconnecting client %u due to protocol violation: %s", client_id, reason_str);
205
206 if (socket_snapshot != INVALID_SOCKET_VALUE) {
207 // Protect socket writes with send_mutex to prevent race with send_thread.
208 // This receive_thread and send_thread both write to same socket.
209 mutex_lock(&client->send_mutex);
210
211 asciichat_error_t log_result =
212 log_network_message(socket_snapshot, (const struct crypto_context_t *)crypto_ctx, LOG_ERROR,
213 REMOTE_LOG_DIRECTION_SERVER_TO_CLIENT, "Protocol violation: %s", reason_str);
214 if (log_result != ASCIICHAT_OK) {
215 log_warn("Failed to send remote log to client %u: %s", client_id, asciichat_error_string(log_result));
216 }
217
218 asciichat_error_t send_result = packet_send_error(socket_snapshot, crypto_ctx, ERROR_NETWORK_PROTOCOL, reason_str);
219 if (send_result != ASCIICHAT_OK) {
220 log_warn("Failed to send error packet to client %u: %s", client_id, asciichat_error_string(send_result));
221 }
222
223 mutex_unlock(&client->send_mutex);
224 } else if (transport_snapshot) {
225 // For WebSocket clients, try to send error via transport
226 log_debug("Sending error to WebSocket client %u via transport", client_id);
227 acip_send_error(transport_snapshot, ERROR_NETWORK_PROTOCOL, reason_str);
228 }
229
230 APP_CALLBACK_VOID(platform_pump_events);
232
233 log_debug("Setting active=false in disconnect_client_for_bad_data (client_id=%u, reason=%s)", client_id, reason_str);
234 atomic_store_bool(&client->active, false);
235 atomic_store_bool(&client->shutting_down, true);
236 atomic_store_bool(&client->send_thread_running, false);
239
240 if (client->audio_queue) {
242 }
243
245 if (client->socket != INVALID_SOCKET_VALUE) {
246 socket_shutdown(client->socket, 2);
247 socket_close(client->socket);
249 }
251}
252
253/* ============================================================================
254 * Client Lifecycle Packet Handlers
255 * ============================================================================
256 */
257
295void handle_client_join_packet(client_info_t *client, const void *data, size_t len) {
296 VALIDATE_PACKET_SIZE(client, data, len, sizeof(client_info_packet_t), "CLIENT_JOIN");
297
298 const client_info_packet_t *join_info = (const client_info_packet_t *)data;
299
300 // Validate display name is present and not just whitespace
301 if (join_info->display_name[0] == '\0') {
302 disconnect_client_for_bad_data(client, "CLIENT_JOIN display_name cannot be empty");
303 return;
304 }
305
306 uint32_t capabilities = NET_TO_HOST_U32(join_info->capabilities);
307
308 // Validate at least one capability flag is set
310 VALIDATE_CAPABILITY_FLAGS(client, capabilities, VALID_CAP_MASK, "CLIENT_JOIN");
311
312 // Validate no unknown capability bits are set
313 VALIDATE_FLAGS_MASK(client, capabilities, VALID_CAP_MASK, "CLIENT_JOIN");
314
316
317 client->can_send_video = (capabilities & CLIENT_CAP_VIDEO) != 0;
318 client->can_send_audio = (capabilities & CLIENT_CAP_AUDIO) != 0;
319 client->wants_stretch = (capabilities & CLIENT_CAP_STRETCH) != 0;
320
321 log_info("Client %u joined: %s (video=%d, audio=%d, stretch=%d)", client->client_id, client->display_name,
322 client->can_send_video, client->can_send_audio, client->wants_stretch);
323
324 // Notify client of successful join (encrypted channel)
325 if (client->socket != INVALID_SOCKET_VALUE) {
326 log_info_client(client, "Joined as '%s' (video=%s, audio=%s)", client->display_name,
327 client->can_send_video ? "yes" : "no", client->can_send_audio ? "yes" : "no");
328 }
329}
330
362void handle_protocol_version_packet(client_info_t *client, const void *data, size_t len) {
363 if (!data) {
364 disconnect_client_for_bad_data(client, "PROTOCOL_VERSION payload missing");
365 return;
366 }
367
368 if (len != sizeof(protocol_version_packet_t)) {
369 disconnect_client_for_bad_data(client, "PROTOCOL_VERSION invalid size: %zu (expected %zu)", len,
371 return;
372 }
373
374 const protocol_version_packet_t *version = (const protocol_version_packet_t *)data;
375 uint16_t client_major = NET_TO_HOST_U16(version->protocol_version);
376 uint16_t client_minor = NET_TO_HOST_U16(version->protocol_revision);
377
378 // Validate major version match (minor version can differ for backward compat)
379 if (client_major != PROTOCOL_VERSION_MAJOR) {
380 log_warn("Client %u protocol version mismatch: client=%u.%u, server=%u.%u", client->client_id, client_major,
382 // Note: We don't disconnect on version mismatch for backward compatibility
383 // Clients may be older or newer than server
384 } else if (client_minor != PROTOCOL_VERSION_MINOR) {
385 log_info("Client %u has different protocol revision: client=%u.%u, server=%u.%u", client->client_id, client_major,
387 }
388
389 // Validate reserved bytes are zero
390 for (size_t i = 0; i < sizeof(version->reserved); i++) {
391 if (version->reserved[i] != 0) {
392 log_warn("Client %u sent non-zero reserved bytes in PROTOCOL_VERSION packet", client->client_id);
393 // Don't disconnect - reserved bytes may be used in future versions
394 break;
395 }
396 }
397
398 // Log supported features
400 log_debug("Client %u supports encryption", client->client_id);
401 }
402 if (version->compression_algorithms != 0) {
403 log_debug("Client %u supports compression: 0x%02x", client->client_id, version->compression_algorithms);
404 }
405 if (version->feature_flags != 0) {
406 uint16_t feature_flags = NET_TO_HOST_U16(version->feature_flags);
407 log_debug("Client %u supports features: 0x%04x", client->client_id, feature_flags);
408 }
409}
410
438void handle_client_leave_packet(client_info_t *client, const void *data, size_t len) {
439 if (!client) {
440 return;
441 }
442
443 const char *client_id = client->client_id;
444
445 if (len == 0) {
446 // Empty reason - client disconnecting without explanation
447 log_info("Client %u sent leave notification (no reason)", client_id);
448 } else if (len <= 256) {
449 // Reason provided - extract and log it
450 if (!data) {
451 SET_ERRNO(ERROR_INVALID_STATE, "Client %u sent leave notification with non-zero length but NULL data", client_id);
452 return;
453 }
454
455 char reason[257] = {0};
456 memcpy(reason, data, len);
457 reason[len] = '\0';
458
459 // Validate reason is printable (handle potential non-UTF8 gracefully)
460 bool all_printable = true;
461 for (size_t i = 0; i < len; i++) {
462 uint8_t c = (uint8_t)reason[i];
463 if (c < 32 && c != '\t' && c != '\n') {
464 all_printable = false;
465 break;
466 }
467 }
468
469 if (all_printable) {
470 log_info("Client %u sent leave notification: %s", client_id, reason);
471 } else {
472 log_info("Client %u sent leave notification (reason contains non-printable characters)", client_id);
473 }
474 } else {
475 // Oversized reason - shouldn't happen with validation.h checks
476 log_warn("Client %u sent oversized leave reason (%zu bytes, max 256)", client_id, len);
477 }
478
479 // Deactivate client to stop processing packets
480 // Sets client->active = false immediately - triggers client cleanup procedures
481 log_debug("Setting active=false in handle_client_leave_packet (client_id=%u)", client_id);
482 atomic_store_bool(&client->active, false);
483
484 // Note: We don't disconnect the client here - that happens when socket closes
485 // This is just a clean notification before disconnect
486}
487
524void handle_stream_start_packet(client_info_t *client, const void *data, size_t len) {
525 VALIDATE_PACKET_SIZE(client, data, len, sizeof(uint32_t), "STREAM_START");
526
527 uint32_t stream_type;
528 memcpy(&stream_type, data, sizeof(uint32_t));
529 stream_type = NET_TO_HOST_U32(stream_type);
530
531 /* Older browser/WASM packet serializers emitted this control payload in
532 * host order while the enclosing ACIP header remained in network order.
533 * Accept that unambiguous two-bit form so a connected browser is not
534 * disconnected before it can send its first frame. */
535 const uint32_t VALID_STREAM_MASK = STREAM_TYPE_VIDEO | STREAM_TYPE_AUDIO;
536 if ((stream_type & VALID_STREAM_MASK) == 0) {
537 uint32_t host_order_stream_type = HOST_TO_NET_U32(stream_type);
538 if ((host_order_stream_type & ~VALID_STREAM_MASK) == 0 && host_order_stream_type != 0) {
539 log_debug("Normalizing legacy host-order STREAM_START flags 0x%x", stream_type);
540 stream_type = host_order_stream_type;
541 }
542 }
543
544 // Validate at least one stream type flag is set
545 VALIDATE_CAPABILITY_FLAGS(client, stream_type, VALID_STREAM_MASK, "STREAM_START");
546
547 // Validate no unknown stream type bits are set
548 VALIDATE_FLAGS_MASK(client, stream_type, VALID_STREAM_MASK, "STREAM_START");
549
550 if (stream_type & STREAM_TYPE_VIDEO) {
551 atomic_store_bool(&client->is_sending_video, true);
552 }
553 if (stream_type & STREAM_TYPE_AUDIO) {
554 atomic_store_bool(&client->is_sending_audio, true);
555
556 // Create Opus decoder for this client if not already created
557 if (!client->opus_decoder) {
559 if (client->opus_decoder) {
560 log_info("Client %u: Opus decoder created (48kHz)", client->client_id);
561 } else {
562 log_error("Client %u: Failed to create Opus decoder", client->client_id);
563 }
564 }
565 }
566
567 if (stream_type & STREAM_TYPE_VIDEO) {
568 log_info("Client %u announced video stream (waiting for first frame)", client->client_id);
569 }
570 if (stream_type & STREAM_TYPE_AUDIO) {
571 log_info("Client %u started audio stream", client->client_id);
572 }
573
574 // Notify client of stream start acknowledgment
575 const char *streams = (stream_type & STREAM_TYPE_VIDEO) && (stream_type & STREAM_TYPE_AUDIO)
576 ? "video+audio"
577 : ((stream_type & STREAM_TYPE_VIDEO) ? "video" : "audio");
578 // Only send remote log to TCP clients (WebSocket clients have invalid socket)
579 if (client->socket != INVALID_SOCKET_VALUE) {
580 log_info_client(client, "Stream started: %s", streams);
581 }
582}
583
619void handle_stream_stop_packet(client_info_t *client, const void *data, size_t len) {
620 VALIDATE_PACKET_SIZE(client, data, len, sizeof(uint32_t), "STREAM_STOP");
621
622 uint32_t stream_type;
623 memcpy(&stream_type, data, sizeof(uint32_t));
624 stream_type = NET_TO_HOST_U32(stream_type);
625
626 // Validate at least one stream type flag is set
627 const uint32_t VALID_STREAM_MASK = STREAM_TYPE_VIDEO | STREAM_TYPE_AUDIO;
628 VALIDATE_CAPABILITY_FLAGS(client, stream_type, VALID_STREAM_MASK, "STREAM_STOP");
629
630 // Validate no unknown stream type bits are set
631 VALIDATE_FLAGS_MASK(client, stream_type, VALID_STREAM_MASK, "STREAM_STOP");
632
633 if (stream_type & STREAM_TYPE_VIDEO) {
634 atomic_store_bool(&client->is_sending_video, false);
635 }
636 if (stream_type & STREAM_TYPE_AUDIO) {
637 atomic_store_bool(&client->is_sending_audio, false);
638 }
639
640 if (stream_type & STREAM_TYPE_VIDEO) {
641 log_info("Client %u stopped video stream", client->client_id);
642 }
643 if (stream_type & STREAM_TYPE_AUDIO) {
644 log_info("Client %u stopped audio stream", client->client_id);
645 }
646
647 // Notify client of stream stop acknowledgment
648 const char *streams = (stream_type & STREAM_TYPE_VIDEO) && (stream_type & STREAM_TYPE_AUDIO)
649 ? "video+audio"
650 : ((stream_type & STREAM_TYPE_VIDEO) ? "video" : "audio");
651 // Only send remote log to TCP clients (WebSocket clients have invalid socket)
652 if (client->socket != INVALID_SOCKET_VALUE) {
653 log_info_client(client, "Stream stopped: %s", streams);
654 }
655}
656
660void handle_ping_packet(client_info_t *client, const void *data, size_t len) {
661 (void)data;
662 (void)len;
663
664 // Serialize the complete PONG write with the sender. TCP packets cannot be
665 // sent concurrently without risking byte-stream interleaving.
666 mutex_lock(&client->send_mutex);
667 if (atomic_load_bool(&client->shutting_down) || !client->transport) {
668 mutex_unlock(&client->send_mutex);
669 return;
670 }
671 acip_transport_t *pong_transport = client->transport;
672 asciichat_error_t pong_result = acip_send_pong(pong_transport);
673 mutex_unlock(&client->send_mutex);
674 if (pong_result != ASCIICHAT_OK) {
675 SET_ERRNO(ERROR_NETWORK, "Failed to send PONG response to client %u: %s", client->client_id,
676 asciichat_error_string(pong_result));
677 }
678}
679
683void handle_pong_packet(client_info_t *client, const void *data, size_t len) {
684 (void)client;
685 (void)data;
686 (void)len;
687 // No action needed - client acknowledged our PING
688}
689
690/* ============================================================================
691 * Media Data Packet Handlers
692 * ============================================================================
693 */
694
749void handle_image_frame_packet(client_info_t *client, void *data, size_t len) {
750 // Handle incoming image data from client
751 // New format: [width:4][height:4][compressed_flag:4][data_size:4][rgb_data:data_size]
752 // Old format: [width:4][height:4][rgb_data:w*h*3] (for backward compatibility)
753 // Use atomic compare-and-swap to avoid race condition - ensures thread-safe auto-enabling of video stream
754
755 log_info("RECV_IMAGE_FRAME: client_id=%u, len=%zu", client->client_id, len);
756
757 // Validate codec capability for RGBA frames
759 log_error("Client %s sent RGBA frame but does not support VIDEO_CODEC_RGBA (capabilities=0x%x)", client->client_id,
761 disconnect_client_for_bad_data(client, "VIDEO_CODEC_RGBA not supported by client (capabilities=0x%x)",
763 return;
764 }
765
766 if (!data || len < sizeof(uint32_t) * 2) {
767 disconnect_client_for_bad_data(client, "IMAGE_FRAME payload too small: %zu bytes", len);
768 return;
769 }
770 bool was_sending_video = atomic_load_bool(&client->is_sending_video);
771 if (!was_sending_video) {
772 // Try to atomically enable video sending
773 // Use atomic_compare_exchange_strong to avoid spurious failures
774 if (atomic_cas_bool(&client->is_sending_video, &was_sending_video, true)) {
775 log_info("Client %u auto-enabled video stream (received IMAGE_FRAME)", client->client_id);
776 // Notify client that their first video frame was received
777 if (client->socket != INVALID_SOCKET_VALUE) {
778 log_info_client(client, "First video frame received - streaming active");
779 }
780 }
781 } else {
782 // Log periodically to confirm we're receiving frames
783 // Use per-client counter protected by client_state_mutex to avoid race conditions
785 client->frames_received_logged++;
786 if (client->frames_received_logged % 25000 == 0) {
787 char pretty[64];
788 format_bytes_pretty(len, pretty, sizeof(pretty));
789 log_debug("Client %u has sent %u IMAGE_FRAME packets (%s)", client->client_id, client->frames_received_logged,
790 pretty);
791 }
793 }
794
795 // Parse image dimensions (use memcpy to avoid unaligned access)
796 uint32_t img_width_net, img_height_net;
797 memcpy(&img_width_net, data, sizeof(uint32_t));
798 memcpy(&img_height_net, (char *)data + sizeof(uint32_t), sizeof(uint32_t));
799 uint32_t img_width = NET_TO_HOST_U32(img_width_net);
800 uint32_t img_height = NET_TO_HOST_U32(img_height_net);
801
802 log_debug("IMAGE_FRAME packet: width=%u, height=%u, payload_len=%zu", img_width, img_height, len);
803
804 // Validate dimensions using image utility functions
805 if (image_validate_dimensions((size_t)img_width, (size_t)img_height) != ASCIICHAT_OK) {
806 log_error("IMAGE_FRAME validation failed for dimensions: %u x %u", img_width, img_height);
807 disconnect_client_for_bad_data(client, "IMAGE_FRAME invalid dimensions");
808 return;
809 }
810
811 // Calculate RGB buffer size with overflow checking
812 size_t rgb_size = 0;
813 if (image_calc_rgb_size((size_t)img_width, (size_t)img_height, &rgb_size) != ASCIICHAT_OK) {
814 disconnect_client_for_bad_data(client, "IMAGE_FRAME buffer size calculation failed");
815 return;
816 }
817
818 // Validate final buffer size against maximum
819 if (image_validate_buffer_size(rgb_size) != ASCIICHAT_OK) {
820 disconnect_client_for_bad_data(client, "IMAGE_FRAME buffer size exceeds maximum");
821 return;
822 }
823
824 // Only support legacy format: [width:4][height:4][rgb_data:w*h*3]
825 if (rgb_size > SIZE_MAX - FRAME_HEADER_SIZE_LEGACY) {
826 char size_str[32];
827 format_bytes_pretty(rgb_size, size_str, sizeof(size_str));
828 disconnect_client_for_bad_data(client, "IMAGE_FRAME legacy packet size overflow: %s", size_str);
829 return;
830 }
831 size_t expected_size = FRAME_HEADER_SIZE_LEGACY + rgb_size;
832
833 if (len != expected_size) {
834 disconnect_client_for_bad_data(client, "IMAGE_FRAME size mismatch: expected %zu bytes got %zu", expected_size, len);
835 return;
836 }
837
838 // Validate legacy format
839 asciichat_error_t validate_result = frame_validate_legacy(len, rgb_size);
840 if (validate_result != ASCIICHAT_OK) {
841 disconnect_client_for_bad_data(client, "IMAGE_FRAME legacy validation failed");
842 return;
843 }
844
845 void *rgb_data = (char *)data + FRAME_HEADER_SIZE_LEGACY;
846 size_t rgb_data_size = rgb_size;
847 bool needs_free = false;
848
849 if (client->incoming_video_buffer) {
850 // Get the write buffer
852
853 if (frame && frame->data) {
854 // Build the packet in the old format for internal storage: [width:4][height:4][rgb_data:w*h*3]
855 // Use frame_check_size_overflow to validate overflow before repacking
857 if (overflow_check != ASCIICHAT_OK) {
858 if (needs_free && rgb_data) {
859 SAFE_FREE(rgb_data);
860 }
861 disconnect_client_for_bad_data(client, "IMAGE_FRAME size overflow while repacking");
862 return;
863 }
864 size_t old_packet_size = FRAME_HEADER_SIZE_LEGACY + rgb_data_size;
865
866 if (old_packet_size <= MAX_FRAME_BUFFER_SIZE) { // Max frame buffer size
867 uint32_t width_net = HOST_TO_NET_U32(img_width);
868 uint32_t height_net = HOST_TO_NET_U32(img_height);
869
870 // Pack in old format for internal consistency
871 memcpy(frame->data, &width_net, sizeof(uint32_t));
872 memcpy((char *)frame->data + sizeof(uint32_t), &height_net, sizeof(uint32_t));
873 memcpy((char *)frame->data + sizeof(uint32_t) * 2, rgb_data, rgb_data_size);
874
875 frame->size = old_packet_size;
876 frame->width = img_width;
877 frame->height = img_height;
878 frame->capture_timestamp_ns = (uint64_t)time(NULL) * NS_PER_SEC_INT;
879 frame->sequence_number = ++client->frames_received;
880
881 // DEBUG: Compute hash of incoming RGB data to detect duplicates
882 uint32_t incoming_rgb_hash = 0;
883 for (size_t i = 0; i < rgb_data_size && i < 1000; i++) {
884 incoming_rgb_hash = (uint32_t)((uint64_t)incoming_rgb_hash * 31 + ((unsigned char *)rgb_data)[i]);
885 }
886
887 // Per-client hash tracking (not static!) to avoid cross-client interference
888 const char *client_id = client->client_id;
889 bool is_new_frame = (incoming_rgb_hash != client->last_received_frame_hash);
890
891 if (is_new_frame) {
892 log_info("RECV_FRAME #%u NEW: Client %u size=%zu dims=%ux%u hash=0x%08x (prev=0x%08x)",
893 client->frames_received, client_id, rgb_data_size, img_width, img_height, incoming_rgb_hash,
895 client->last_received_frame_hash = incoming_rgb_hash;
896 } else {
897 log_info("RECV_FRAME #%u DUP: Client %u size=%zu dims=%ux%u hash=0x%08x", client->frames_received, client_id,
898 rgb_data_size, img_width, img_height, incoming_rgb_hash);
899 }
900
902 } else {
903 if (needs_free && rgb_data) {
904 SAFE_FREE(rgb_data);
905 }
906 disconnect_client_for_bad_data(client, "IMAGE_FRAME repacked frame too large (%zu bytes)", old_packet_size);
907 return;
908 }
909 } else {
910 log_warn("Failed to get write buffer for client %u (frame=%p, frame->data=%p)", client->client_id, (void *)frame,
911 frame ? frame->data : NULL);
912 }
913 } else {
914 // During shutdown, this is expected - don't spam error logs
916 SET_ERRNO(ERROR_INVALID_STATE, "Client %u has no incoming video buffer!", client->client_id);
917 } else {
918 log_debug("Client %u: ignoring video packet during shutdown", client->client_id);
919 }
920 }
921
922 // Clean up decompressed data if allocated
923 if (needs_free && rgb_data) {
924 SAFE_FREE(rgb_data);
925 }
926}
927
951void handle_image_frame_h265_packet(client_info_t *client, const void *data, size_t len) {
952 // Validate codec capability for H.265 frames
954 log_error("Client %s sent H.265 frame but does not support VIDEO_CODEC_H265 (capabilities=0x%x)", client->client_id,
956 disconnect_client_for_bad_data(client, "VIDEO_CODEC_H265 not supported by client (capabilities=0x%x)",
958 return;
959 }
960
961 if (!data || len < 5) {
962 disconnect_client_for_bad_data(client, "H265_FRAME payload too small: %zu bytes (min 5)", len);
963 return;
964 }
965
966 // Auto-enable video stream if not already enabled
967 // Use atomic_exchange to atomically set flag and check old value
968 bool was_sending_video = atomic_exchange_bool(&client->is_sending_video, true);
969 if (!was_sending_video) {
970 log_info("Client %s auto-enabled H.265 video stream (received IMAGE_FRAME_H265)", client->client_id);
971 if (client->socket != INVALID_SOCKET_VALUE) {
972 log_info_client(client, "First H.265 video frame received - streaming active");
973 }
974 }
975
976 uint8_t flags = *(const uint8_t *)data;
977 bool is_keyframe = (flags & 0x01) != 0;
978 bool size_changed = (flags & 0x02) != 0;
979
980 log_debug("H265_FRAME: flags=0x%02x, keyframe=%s, size_change=%s, len=%zu", flags, is_keyframe ? "yes" : "no",
981 size_changed ? "yes" : "no", len);
982
983 if (!client->incoming_video_buffer) {
984 log_error("Client %s has no incoming video buffer for H.265 frames", client->client_id);
985 return;
986 }
987
988 // Get H.265 server context (may be NULL during shutdown)
990 if (!h265_ctx) {
992 log_error("H.265 codec server context not initialized");
993 }
994 return;
995 }
996
997 // Get or create decoder for this client
998 uint32_t client_id_num = 0;
999 char *end = NULL;
1000 unsigned long parsed = strtoul(client->client_id, &end, 10);
1001 if (end != client->client_id && *end == '\0') {
1002 client_id_num = (uint32_t)parsed;
1003 } else {
1004 // Fallback to string hash if can't parse as number
1005 client_id_num = 0;
1006 for (int i = 0; client->client_id[i]; i++) {
1007 client_id_num = (client_id_num * 31 + (unsigned char)client->client_id[i]) & 0xFFFFFFFF;
1008 }
1009 }
1010
1011 h265_server_client_t *client_decoder = h265_server_get_client_decoder(h265_ctx, client_id_num);
1012 if (!client_decoder) {
1013 log_error("Failed to get H.265 decoder for client %s", client->client_id);
1014 disconnect_client_for_bad_data(client, "Failed to initialize H.265 decoder");
1015 return;
1016 }
1017
1018 // Allocate temporary RGBA buffer for decoding
1019 // H.265 frames from client are sent as [flags:u8][width:u16][height:u16][h265_data...]
1020 // Max frame is (256x256 = 65536 pixels) * 4 bytes/pixel = 262KB
1021 uint8_t *rgba_buf = SAFE_MALLOC(512 * 512 * 4, uint8_t *);
1022 if (!rgba_buf) {
1023 log_error("Failed to allocate RGBA buffer for H.265 decoding");
1024 disconnect_client_for_bad_data(client, "Out of memory for H.265 frame decoding");
1025 return;
1026 }
1027
1028 uint16_t frame_width = 0;
1029 uint16_t frame_height = 0;
1030 size_t rgba_size = 512 * 512 * 4;
1031
1032 // Decode H.265 packet to RGBA
1033 asciichat_error_t decode_result = h265_server_decode_and_convert(client_decoder, (const uint8_t *)data, len, rgba_buf,
1034 &frame_width, &frame_height, &rgba_size);
1035
1036 if (decode_result != ASCIICHAT_OK) {
1037 log_error("H.265 decoding failed for client %s: %s", client->client_id, asciichat_error_string(decode_result));
1038 SAFE_FREE(rgba_buf);
1039 disconnect_client_for_bad_data(client, "H.265 frame decode error");
1040 return;
1041 }
1042
1043 // Write decoded frame to video buffer
1045 if (frame && frame->data) {
1046 memcpy(frame->data, rgba_buf, rgba_size);
1047 frame->size = rgba_size;
1048 frame->width = frame_width;
1049 frame->height = frame_height;
1050 frame->is_keyframe = is_keyframe;
1051
1052 // Update frame metadata
1053 frame->receive_timestamp_ns = time_get_realtime_ns();
1054 frame->sequence_number = client->frames_received++;
1055
1056 log_info_every(100, "RECV_H265 #%lu: client_id=%s size=%zu dims=%ux%u keyframe=%s", frame->sequence_number,
1057 client->client_id, rgba_size, frame_width, frame_height, is_keyframe ? "yes" : "no");
1058
1060 } else {
1061 log_warn("Failed to get write buffer for client %s (frame=%p, frame->data=%p)", client->client_id, (void *)frame,
1062 frame ? frame->data : NULL);
1063 }
1064
1065 SAFE_FREE(rgba_buf);
1066}
1067
1109void handle_audio_packet(client_info_t *client, const void *data, size_t len) {
1110 VALIDATE_NOTNULL_DATA(client, data, "AUDIO");
1111 VALIDATE_AUDIO_ALIGNMENT(client, len, sizeof(float), "AUDIO");
1112 VALIDATE_AUDIO_STREAM_ENABLED(client, "AUDIO");
1113
1114 int num_samples = (int)(len / sizeof(float));
1115 VALIDATE_AUDIO_SAMPLE_COUNT(client, num_samples, AUDIO_SAMPLES_PER_PACKET, "AUDIO");
1116 VALIDATE_RESOURCE_INITIALIZED(client, client->incoming_audio_buffer, "audio buffer");
1117
1118 const float *samples = (const float *)data;
1119 asciichat_error_t result = audio_ring_buffer_write(client->incoming_audio_buffer, samples, num_samples);
1120 if (result != ASCIICHAT_OK) {
1121 log_error("Failed to write audio samples to buffer: %s", asciichat_error_string(result));
1122 }
1123}
1124
1125void handle_remote_log_packet_from_client(client_info_t *client, const void *data, size_t len) {
1126 if (!client) {
1127 return;
1128 }
1129
1130 log_level_t remote_level = LOG_INFO;
1132 uint16_t flags = 0;
1133 char message[MAX_REMOTE_LOG_MESSAGE_LENGTH + 1] = {0};
1134
1135 asciichat_error_t parse_result =
1136 packet_parse_remote_log(data, len, &remote_level, &direction, &flags, message, sizeof(message), NULL);
1137 if (parse_result != ASCIICHAT_OK) {
1138 disconnect_client_for_bad_data(client, "Invalid REMOTE_LOG packet: %s", asciichat_error_string(parse_result));
1139 return;
1140 }
1141
1142 if (direction != REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER) {
1143 disconnect_client_for_bad_data(client, "REMOTE_LOG direction mismatch: %u", direction);
1144 return;
1145 }
1146
1147 const bool truncated = (flags & REMOTE_LOG_FLAG_TRUNCATED) != 0;
1148 const char *display_name = client->display_name[0] ? client->display_name : "(unnamed)";
1149 const char *client_id = client->client_id;
1150
1151 if (truncated) {
1152 log_msg(remote_level, __FILE__, __LINE__, __func__, "[REMOTE CLIENT %u \"%s\"] %s [message truncated]", client_id,
1153 display_name, message);
1154 } else {
1155 log_msg(remote_level, __FILE__, __LINE__, __func__, "[REMOTE CLIENT %u \"%s\"] %s", client_id, display_name,
1156 message);
1157 }
1158}
1159
1206void handle_audio_batch_packet(client_info_t *client, const void *data, size_t len) {
1207 // Log every audio batch packet reception
1208 log_debug_every(LOG_RATE_DEFAULT, "Received audio batch packet from client %u (len=%zu, is_sending_audio=%d)",
1209 client->client_id, len, atomic_load_bool(&client->is_sending_audio));
1210
1211 VALIDATE_NOTNULL_DATA(client, data, "AUDIO_BATCH");
1212 VALIDATE_MIN_SIZE(client, len, sizeof(audio_batch_packet_t), "AUDIO_BATCH");
1213 VALIDATE_AUDIO_STREAM_ENABLED(client, "AUDIO_BATCH");
1214
1215 // Parse batch header using utility function
1216 audio_batch_info_t batch_info;
1217 asciichat_error_t parse_result = audio_parse_batch_header(data, len, &batch_info);
1218 if (parse_result != ASCIICHAT_OK) {
1219 disconnect_client_for_bad_data(client, "Failed to parse audio batch header");
1220 return;
1221 }
1222
1223 uint32_t packet_batch_count = batch_info.batch_count;
1224 uint32_t total_samples = batch_info.total_samples;
1225 uint32_t sample_rate = batch_info.sample_rate;
1226
1227 (void)packet_batch_count;
1228 (void)sample_rate;
1229
1230 VALIDATE_NONZERO(client, packet_batch_count, "batch_count", "AUDIO_BATCH");
1231 VALIDATE_NONZERO(client, total_samples, "total_samples", "AUDIO_BATCH");
1232
1233 size_t samples_bytes = 0;
1234 if (safe_size_mul(total_samples, sizeof(uint32_t), &samples_bytes)) {
1235 disconnect_client_for_bad_data(client, "AUDIO_BATCH sample size overflow (samples=%u)", total_samples);
1236 return;
1237 }
1238
1239 size_t expected_size = sizeof(audio_batch_packet_t) + samples_bytes;
1240 if (len != expected_size) {
1241 disconnect_client_for_bad_data(client, "AUDIO_BATCH length mismatch: got %zu expected %zu", len, expected_size);
1242 return;
1243 }
1244
1245 // Bounds check to prevent integer overflow on allocation
1246 // Maximum allowed samples: AUDIO_BATCH_SAMPLES * 2 (2048 samples)
1247 // This prevents total_samples * sizeof(float) from exceeding 8KB
1248 const uint32_t MAX_AUDIO_SAMPLES = AUDIO_BATCH_SAMPLES * 2;
1249 if (total_samples > MAX_AUDIO_SAMPLES) {
1250 disconnect_client_for_bad_data(client, "AUDIO_BATCH too many samples: %u (max: %u)", total_samples,
1251 MAX_AUDIO_SAMPLES);
1252 return;
1253 }
1254
1255 const uint8_t *samples_ptr = (const uint8_t *)data + sizeof(audio_batch_packet_t);
1256
1257 // Safe allocation: total_samples is bounded above, so multiplication won't overflow
1258 size_t alloc_size = (size_t)total_samples * sizeof(float);
1259 float *samples = SAFE_MALLOC(alloc_size, float *);
1260 if (!samples) {
1261 SET_ERRNO(ERROR_MEMORY, "Failed to allocate memory for audio sample conversion");
1262 return;
1263 }
1264
1265 // Use helper function to dequantize samples
1266 asciichat_error_t dq_result = audio_dequantize_samples(samples_ptr, total_samples, samples);
1267 if (dq_result != ASCIICHAT_OK) {
1268 SAFE_FREE(samples);
1269 return;
1270 }
1271
1272#ifndef NDEBUG
1273 static int recv_count = 0;
1274 recv_count++;
1275 if (recv_count % 100 == 0) {
1276 uint32_t raw0 = bytes_read_u32_unaligned(samples_ptr + 0 * sizeof(uint32_t));
1277 uint32_t raw1 = bytes_read_u32_unaligned(samples_ptr + 1 * sizeof(uint32_t));
1278 uint32_t raw2 = bytes_read_u32_unaligned(samples_ptr + 2 * sizeof(uint32_t));
1279 int32_t scaled0 = (int32_t)NET_TO_HOST_U32(raw0);
1280 int32_t scaled1 = (int32_t)NET_TO_HOST_U32(raw1);
1281 int32_t scaled2 = (int32_t)NET_TO_HOST_U32(raw2);
1282 log_info("RECV: network[0]=0x%08x, network[1]=0x%08x, network[2]=0x%08x", raw0, raw1, raw2);
1283 log_info("RECV: scaled[0]=%d, scaled[1]=%d, scaled[2]=%d", scaled0, scaled1, scaled2);
1284 log_info("RECV: samples[0]=%.6f, samples[1]=%.6f, samples[2]=%.6f", samples[0], samples[1], samples[2]);
1285 }
1286#endif
1287
1288 if (client->incoming_audio_buffer) {
1289 asciichat_error_t write_result = audio_ring_buffer_write(client->incoming_audio_buffer, samples, total_samples);
1290 if (write_result != ASCIICHAT_OK) {
1291 log_error("Failed to write decoded audio batch to buffer: %s", asciichat_error_string(write_result));
1292 }
1293 }
1294
1295 SAFE_FREE(samples);
1296}
1297
1333void handle_audio_opus_batch_packet(client_info_t *client, const void *data, size_t len) {
1334 log_debug_every(LOG_RATE_SLOW, "Received Opus audio batch from client %u (len=%zu)", client->client_id, len);
1335
1336 VALIDATE_NOTNULL_DATA(client, data, "AUDIO_OPUS_BATCH");
1337 VALIDATE_AUDIO_STREAM_ENABLED(client, "AUDIO_OPUS_BATCH");
1338 VALIDATE_RESOURCE_INITIALIZED(client, client->opus_decoder, "Opus decoder");
1339
1340 // Parse Opus batch packet
1341 const uint8_t *opus_data = NULL;
1342 size_t opus_size = 0;
1343 const uint16_t *frame_sizes = NULL;
1344 int sample_rate = 0;
1345 int frame_duration = 0;
1346 int frame_count = 0;
1347
1348 asciichat_error_t result = packet_parse_opus_batch(data, len, &opus_data, &opus_size, &frame_sizes, &sample_rate,
1349 &frame_duration, &frame_count);
1350
1351 if (result != ASCIICHAT_OK) {
1352 disconnect_client_for_bad_data(client, "Failed to parse AUDIO_OPUS_BATCH packet");
1353 return;
1354 }
1355
1356 VALIDATE_NONZERO(client, frame_count, "frame_count", "AUDIO_OPUS_BATCH");
1357 VALIDATE_NONZERO(client, opus_size, "opus_size", "AUDIO_OPUS_BATCH");
1358
1359 // Calculate samples per frame (20ms @ 48kHz = 960 samples)
1360 int samples_per_frame = (sample_rate * frame_duration) / 1000;
1361 VALIDATE_RANGE(client, samples_per_frame, 1, 4096, "samples_per_frame", "AUDIO_OPUS_BATCH");
1362
1363 // Use static buffer for common case to avoid malloc in hot path
1364 // Typical batches: 1-32 frames of 960 samples = up to 30,720 samples
1365 // Static buffer holds 32 frames @ 48kHz 20ms = 30,720 samples (120KB)
1366#define OPUS_DECODE_STATIC_MAX_SAMPLES (32 * 960)
1367 // Each client receive thread decodes independently. Sharing this scratch
1368 // buffer lets concurrent clients overwrite one another's PCM before it is
1369 // written to their source rings.
1370 static _Thread_local float static_decode_buffer[OPUS_DECODE_STATIC_MAX_SAMPLES];
1371
1372 size_t total_samples = (size_t)samples_per_frame * (size_t)frame_count;
1373 float *decoded_samples;
1374 bool used_malloc = false;
1375
1376 if (total_samples <= OPUS_DECODE_STATIC_MAX_SAMPLES) {
1377 decoded_samples = static_decode_buffer;
1378 } else {
1379 // Unusual large batch - fall back to malloc
1380 log_warn("Client %u: Large audio batch requires malloc (%zu samples)", client->client_id, total_samples);
1381 decoded_samples = SAFE_MALLOC(total_samples * sizeof(float), float *);
1382 if (!decoded_samples) {
1383 SET_ERRNO(ERROR_MEMORY, "Failed to allocate buffer for Opus decoded samples");
1384 return;
1385 }
1386 used_malloc = true;
1387 }
1388
1389 // Decode each Opus frame using frame_sizes array
1390 int total_decoded = 0;
1391 size_t opus_offset = 0;
1392
1393 for (int i = 0; i < frame_count; i++) {
1394 // Get exact frame size from frame_sizes array (convert from network byte order)
1395 size_t frame_size = (size_t)NET_TO_HOST_U16(frame_sizes[i]);
1396
1397 // DEBUG: Log the actual bytes of each Opus frame
1398 if (frame_size > 0) {
1399 log_debug_every(LOG_RATE_DEFAULT, "Client %u: Opus frame %d: size=%zu, first_bytes=[0x%02x,0x%02x,0x%02x,0x%02x]",
1400 client->client_id, i, frame_size, opus_data[opus_offset] & 0xFF,
1401 frame_size > 1 ? (opus_data[opus_offset + 1] & 0xFF) : 0,
1402 frame_size > 2 ? (opus_data[opus_offset + 2] & 0xFF) : 0,
1403 frame_size > 3 ? (opus_data[opus_offset + 3] & 0xFF) : 0);
1404 }
1405
1406 if (opus_offset + frame_size > opus_size) {
1407 log_error("Client %u: Frame %d size overflow (offset=%zu, frame_size=%zu, total=%zu)", client->client_id, i + 1,
1408 opus_offset, frame_size, opus_size);
1409 if (used_malloc) {
1410 SAFE_FREE(decoded_samples);
1411 }
1412 return;
1413 }
1414
1415 // SECURITY: Bounds check before writing decoded samples to prevent buffer overflow
1416 // An attacker could send malicious Opus frames that decode to more samples than expected
1417 if ((size_t)total_decoded + (size_t)samples_per_frame > total_samples) {
1418 log_error("Client %u: Opus decode would overflow buffer (decoded=%d, frame_samples=%d, max=%zu)",
1419 client->client_id, total_decoded, samples_per_frame, total_samples);
1420 if (used_malloc) {
1421 SAFE_FREE(decoded_samples);
1422 }
1423 return;
1424 }
1425
1426 int decoded_count = opus_codec_decode((opus_codec_t *)client->opus_decoder, &opus_data[opus_offset], frame_size,
1427 &decoded_samples[total_decoded], samples_per_frame);
1428
1429 if (decoded_count < 0) {
1430 log_error("Client %u: Opus decoding failed for frame %d/%d (size=%zu)", client->client_id, i + 1, frame_count,
1431 frame_size);
1432 if (used_malloc) {
1433 SAFE_FREE(decoded_samples);
1434 }
1435 return;
1436 }
1437
1438 total_decoded += decoded_count;
1439 opus_offset += frame_size;
1440 }
1441
1442 log_debug_every(LOG_RATE_DEFAULT, "Client %u: Decoded %d Opus frames -> %d samples", client->client_id, frame_count,
1443 total_decoded);
1444
1445 // DEBUG: Log sample values to detect all-zero issue
1446 static _Thread_local int server_decode_count = 0;
1447 server_decode_count++;
1448 if (total_decoded > 0 && (server_decode_count <= 10 || server_decode_count % 100 == 0)) {
1449 float peak = 0.0f, rms = 0.0f;
1450 for (int i = 0; i < total_decoded && i < 100; i++) {
1451 float abs_val = fabsf(decoded_samples[i]);
1452 if (abs_val > peak)
1453 peak = abs_val;
1454 rms += decoded_samples[i] * decoded_samples[i];
1455 }
1456 rms = sqrtf(rms / (total_decoded > 100 ? 100 : total_decoded));
1457 // Log first 4 bytes of Opus data to compare with client encode
1458 log_info("SERVER OPUS DECODE #%d from client %u: decoded_rms=%.6f, opus_first4=[0x%02x,0x%02x,0x%02x,0x%02x]",
1459 server_decode_count, client->client_id, rms, opus_size > 0 ? opus_data[0] : 0,
1460 opus_size > 1 ? opus_data[1] : 0, opus_size > 2 ? opus_data[2] : 0, opus_size > 3 ? opus_data[3] : 0);
1461 }
1462
1463 // Write decoded samples to client's incoming audio buffer
1464 // Note: audio_ring_buffer_write returns error code, not sample count
1465 // Buffer overflow warnings are logged inside audio_ring_buffer_write if buffer is full
1466 if (client->incoming_audio_buffer && total_decoded > 0) {
1467 asciichat_error_t result = audio_ring_buffer_write(client->incoming_audio_buffer, decoded_samples, total_decoded);
1468 if (result != ASCIICHAT_OK) {
1469 log_error("Client %u: Failed to write decoded audio to buffer: %d", client->client_id, result);
1470 }
1471 }
1472
1473 if (used_malloc) {
1474 SAFE_FREE(decoded_samples);
1475 }
1476}
1477
1496void handle_audio_opus_packet(client_info_t *client, const void *data, size_t len) {
1497 log_debug_every(LOG_RATE_DEFAULT, "Received Opus audio from client %u (len=%zu)", client->client_id, len);
1498
1499 if (VALIDATE_PACKET_NOT_NULL(client, data, "AUDIO_OPUS")) {
1500 return;
1501 }
1502
1503 // Minimum size: 16-byte header + at least 1 byte of Opus data
1504 if (len < 17) {
1505 disconnect_client_for_bad_data(client, "AUDIO_OPUS packet too small: %zu bytes", len);
1506 return;
1507 }
1508
1509 if (!atomic_load_bool(&client->is_sending_audio)) {
1510 disconnect_client_for_bad_data(client, "AUDIO_OPUS received before audio stream enabled");
1511 return;
1512 }
1513
1514 if (!client->opus_decoder) {
1515 disconnect_client_for_bad_data(client, "Opus decoder not initialized");
1516 return;
1517 }
1518
1519 // Parse header (16 bytes) - convert from network byte order
1520 const uint8_t *buf = (const uint8_t *)data;
1521 uint32_t sample_rate_net, frame_duration_net;
1522 memcpy(&sample_rate_net, buf, 4);
1523 memcpy(&frame_duration_net, buf + 4, 4);
1524 uint32_t sample_rate = NET_TO_HOST_U32(sample_rate_net);
1525 uint32_t frame_duration = NET_TO_HOST_U32(frame_duration_net);
1526
1527 // Extract Opus data (after 16-byte header)
1528 const uint8_t *opus_data = buf + 16;
1529 size_t opus_size = len - 16;
1530
1531 // Validate parameters
1532 if (sample_rate == 0 || sample_rate > 192000) {
1533 disconnect_client_for_bad_data(client, "AUDIO_OPUS invalid sample_rate: %u", sample_rate);
1534 return;
1535 }
1536
1537 if (frame_duration == 0 || frame_duration > 120) {
1538 disconnect_client_for_bad_data(client, "AUDIO_OPUS invalid frame_duration: %u ms", frame_duration);
1539 return;
1540 }
1541
1542 // Calculate expected samples per frame
1543 int samples_per_frame = (int)((sample_rate * frame_duration) / 1000);
1544 if (samples_per_frame <= 0 || samples_per_frame > 5760) { // Max 120ms @ 48kHz
1545 disconnect_client_for_bad_data(client, "AUDIO_OPUS invalid samples_per_frame: %d", samples_per_frame);
1546 return;
1547 }
1548
1549 // Decode Opus frame
1550 float decoded_samples[5760]; // Max Opus frame size (120ms @ 48kHz)
1551 int decoded_count =
1552 opus_codec_decode((opus_codec_t *)client->opus_decoder, opus_data, opus_size, decoded_samples, samples_per_frame);
1553
1554 if (decoded_count < 0) {
1555 log_error("Client %u: Opus decoding failed (size=%zu)", client->client_id, opus_size);
1556 return;
1557 }
1558
1559 log_debug_every(LOG_RATE_VERY_FAST, "Client %u: Decoded Opus frame -> %d samples", client->client_id, decoded_count);
1560
1561 // Write decoded samples to client's incoming audio buffer
1562 if (client->incoming_audio_buffer && decoded_count > 0) {
1563 asciichat_error_t write_result =
1564 audio_ring_buffer_write(client->incoming_audio_buffer, decoded_samples, decoded_count);
1565 if (write_result != ASCIICHAT_OK) {
1566 log_error("Failed to write decoded Opus samples to buffer: %s", asciichat_error_string(write_result));
1567 }
1568 }
1569}
1570
1571/* ============================================================================
1572 * Client Configuration Packet Handlers
1573 * ============================================================================
1574 */
1575
1640void handle_client_capabilities_packet(client_info_t *client, const void *data, size_t len) {
1641 const char *client_id = client->client_id;
1642 log_warn("[CAPS_HANDLER] 🟢 CAPS_RECEIVED: client_id=%u, data_ptr=%p, len=%zu bytes", client_id, data, len);
1643
1644 log_debug("[CAPS_HANDLER] Step 1: Validating packet size (expected=%zu, actual=%zu)",
1645 sizeof(terminal_capabilities_packet_t), len);
1646 VALIDATE_PACKET_SIZE(client, data, len, sizeof(terminal_capabilities_packet_t), "CLIENT_CAPABILITIES");
1647 log_debug("[CAPS_HANDLER] ✅ Size validation passed");
1648
1650
1651 // Extract and validate dimensions
1652 uint16_t width = NET_TO_HOST_U16(caps->width);
1653 uint16_t height = NET_TO_HOST_U16(caps->height);
1654 log_warn("[CAPS_HANDLER] 📐 DIMENSIONS: width=%u, height=%u", width, height);
1655
1656 log_debug("[CAPS_HANDLER] Step 2: Validating width (value=%u, must be nonzero)", width);
1657 VALIDATE_NONZERO(client, width, "width", "CLIENT_CAPABILITIES");
1658 log_debug("[CAPS_HANDLER] ✅ Width nonzero check passed");
1659
1660 log_debug("[CAPS_HANDLER] Step 3: Validating height (value=%u, must be nonzero)", height);
1661 VALIDATE_NONZERO(client, height, "height", "CLIENT_CAPABILITIES");
1662 log_debug("[CAPS_HANDLER] ✅ Height nonzero check passed");
1663
1664 log_debug("[CAPS_HANDLER] Step 4: Validating width range (value=%u, range=1-4096)", width);
1665 VALIDATE_RANGE(client, width, 1, 4096, "width", "CLIENT_CAPABILITIES");
1666 log_debug("[CAPS_HANDLER] ✅ Width range check passed");
1667
1668 log_debug("[CAPS_HANDLER] Step 5: Validating height range (value=%u, range=1-4096)", height);
1669 VALIDATE_RANGE(client, height, 1, 4096, "height", "CLIENT_CAPABILITIES");
1670 log_debug("[CAPS_HANDLER] ✅ Height range check passed");
1671
1672 // Extract and validate color level (0=none, 1=16, 2=256, 3=truecolor)
1673 uint32_t color_level = NET_TO_HOST_U32(caps->color_level);
1674 log_debug("[CAPS_HANDLER] Step 6: Validating color_level (value=%u, range=0-3)", color_level);
1675 VALIDATE_RANGE(client, color_level, 0, 3, "color_level", "CLIENT_CAPABILITIES");
1676 log_debug("[CAPS_HANDLER] ✅ Color level check passed");
1677
1678 // Extract and validate render mode (0=foreground, 1=background, 2=half-block)
1679 uint32_t render_mode = NET_TO_HOST_U32(caps->render_mode);
1680 log_debug("[CAPS_HANDLER] Step 7: Validating render_mode (value=%u, range=0-2)", render_mode);
1681 VALIDATE_RANGE(client, render_mode, 0, 2, "render_mode", "CLIENT_CAPABILITIES");
1682 log_debug("[CAPS_HANDLER] ✅ Render mode check passed");
1683
1684 // Extract and validate palette type (0-5 are valid, 5=PALETTE_CUSTOM)
1685 uint32_t palette_type = NET_TO_HOST_U32(caps->palette_type);
1686 log_debug("[CAPS_HANDLER] Step 8: Validating palette_type (value=%u, range=0-5)", palette_type);
1687 VALIDATE_RANGE(client, palette_type, 0, 5, "palette_type", "CLIENT_CAPABILITIES");
1688 log_debug("[CAPS_HANDLER] ✅ Palette type check passed");
1689
1690 // Validate desired FPS (1-144)
1691 log_debug("[CAPS_HANDLER] Step 9: Validating desired_fps (value=%u, range=1-144)", caps->desired_fps);
1692 VALIDATE_RANGE(client, caps->desired_fps, 1, 144, "desired_fps", "CLIENT_CAPABILITIES");
1693 log_debug("[CAPS_HANDLER] ✅ FPS check passed");
1694
1696
1697 client->width = width;
1698 client->height = height;
1699
1700 log_debug("Client %u dimensions: %ux%u, desired_fps=%u", client->client_id, client->width, client->height,
1701 caps->desired_fps);
1702
1704 client->terminal_caps.color_level = color_level;
1706 client->terminal_caps.render_mode = render_mode;
1708 client->terminal_caps.wants_background = (render_mode == RENDER_MODE_BACKGROUND);
1709
1710 SAFE_STRNCPY(client->terminal_caps.term_type, caps->term_type, sizeof(client->terminal_caps.term_type));
1711 SAFE_STRNCPY(client->terminal_caps.colorterm, caps->colorterm, sizeof(client->terminal_caps.colorterm));
1712
1714 client->terminal_caps.palette_type = palette_type;
1716 sizeof(client->terminal_caps.palette_custom));
1717
1718 client->terminal_caps.desired_fps = caps->desired_fps;
1720
1721 // Extract wants_padding flag (1=padding enabled, 0=no padding for snapshot/piped modes)
1722 client->terminal_caps.wants_padding = (caps->wants_padding != 0);
1723
1724 const char *custom_chars =
1727 : NULL;
1728
1730 client->client_palette_chars, &client->client_palette_len,
1731 client->client_luminance_palette) == 0) {
1733 client->client_palette_initialized = true;
1734 log_info("Client %d palette initialized: type=%u, %zu chars, utf8=%u", client->client_id,
1736 } else {
1737 SET_ERRNO(ERROR_INVALID_STATE, "Failed to initialize palette for client %d", client->client_id);
1738 client->client_palette_initialized = false;
1739 }
1740
1741 // Extract and store codec capabilities
1744 log_info("Client %u codec capabilities: video=0x%x audio=0x%x", client->client_id, client->codec_capabilities_video,
1745 client->codec_capabilities_audio);
1746
1747 client->has_terminal_caps = true;
1748
1749 log_info("Client %u capabilities: %ux%u, color_level=%s (%u colors), caps=0x%x, term=%s, colorterm=%s, "
1750 "render_mode=%s, reliable=%s, fps=%u, wants_padding=%d",
1751 client->client_id, client->width, client->height,
1755 ? "half-block"
1756 : (client->terminal_caps.render_mode == RENDER_MODE_BACKGROUND ? "background" : "foreground")),
1757 client->terminal_caps.detection_reliable ? "yes" : "no", client->terminal_caps.desired_fps,
1759
1760 // Send capabilities acknowledgment to client
1761 if (client->socket != INVALID_SOCKET_VALUE) {
1762 log_info_client(client, "Terminal configured: %ux%u, %s, %s mode, %u fps", client->width, client->height,
1765 ? "half-block"
1766 : (client->terminal_caps.render_mode == RENDER_MODE_BACKGROUND ? "background" : "foreground")),
1767 client->terminal_caps.desired_fps);
1768 }
1769
1771
1772 log_warn("[CAPS_HANDLER] ✅ CAPS_COMPLETE: client_id=%u - all validations passed, capabilities stored",
1773 client->client_id);
1774}
1775
1810void handle_size_packet(client_info_t *client, const void *data, size_t len) {
1811 VALIDATE_PACKET_SIZE(client, data, len, sizeof(size_packet_t), "SIZE");
1812
1813 const size_packet_t *size_pkt = (const size_packet_t *)data;
1814
1815 // Extract and validate new dimensions
1816 uint16_t width = NET_TO_HOST_U16(size_pkt->width);
1817 uint16_t height = NET_TO_HOST_U16(size_pkt->height);
1818
1819 VALIDATE_NONZERO(client, width, "width", "SIZE");
1820 VALIDATE_NONZERO(client, height, "height", "SIZE");
1821 VALIDATE_RANGE(client, width, 1, 4096, "width", "SIZE");
1822 VALIDATE_RANGE(client, height, 1, 4096, "height", "SIZE");
1823
1825 client->width = width;
1826 client->height = height;
1828
1829 log_info("Client %u updated terminal size: %ux%u", client->client_id, width, height);
1830}
1831
1832/* ============================================================================
1833 * Protocol Control Packet Handlers
1834 * ============================================================================
1835 */
1836
1837/* ============================================================================
1838 * Protocol Utility Functions
1839 * ============================================================================
1840 */
1841
1885 if (!client) {
1886 return -1;
1887 }
1888
1889 // Count active clients - LOCK OPTIMIZATION: Use atomic reads, no rwlock needed
1890 int active_count = 0;
1891 for (int i = 0; i < MAX_CLIENTS; i++) {
1893 active_count++;
1894 }
1895 }
1896
1897 // Prepare server state packet
1899 state.connected_client_count = active_count;
1900 state.active_client_count = active_count; // For now, all connected are active
1901 memset(state.reserved, 0, sizeof(state.reserved));
1902
1903 // Convert to network byte order
1904 server_state_packet_t net_state;
1907 memset(net_state.reserved, 0, sizeof(net_state.reserved));
1908
1909 // Send server state via ACIP transport
1910 // Protect socket writes with send_mutex to prevent race with send_thread.
1911 mutex_lock(&client->send_mutex);
1912 asciichat_error_t result = acip_send_server_state(client->transport, &net_state);
1913 mutex_unlock(&client->send_mutex);
1914
1915 if (result != ASCIICHAT_OK) {
1916 SET_ERRNO(ERROR_NETWORK, "Failed to send server state to client %u: %s", client->client_id,
1917 asciichat_error_string(result));
1918 return -1;
1919 }
1920
1921 log_debug("Sent server state to client %u: %u connected, %u active", client->client_id, state.connected_client_count,
1922 state.active_client_count);
1923 return 0;
1924}
1925
1951// NOTE: This function is no longer used - CLEAR_CONSOLE is now sent directly
1952// from each client's render thread when it detects a grid layout change.
1953// Keeping this for reference but it should not be called.
1955 SET_ERRNO(ERROR_INVALID_STATE, "broadcast_clear_console_to_all_clients() called - unexpected usage");
1956 log_warn("CLEAR_CONSOLE is now sent from render threads, not broadcast");
1957}
Application-level callbacks for library code.
#define APP_CALLBACK_VOID(callback_name)
void atomic_store_bool(atomic_t *a, bool value)
Atomically store a boolean value.
Definition atomic.c:177
bool atomic_load_bool(atomic_t *a)
Atomically load a boolean value.
Definition atomic.c:169
bool atomic_exchange_bool(atomic_t *a, bool new_value)
Atomically exchange a boolean and return the old value.
Definition atomic.c:303
bool atomic_cas_bool(atomic_t *a, bool *expected, bool new_value)
Atomically compare-and-swap a boolean.
Definition atomic.c:184
⚛️ Atomic operations abstraction layer with debug tracking
Common buffer size definitions.
🔢 Byte-Level Access and Arithmetic Utilities
Video and audio codec definitions and capability bitmasks.
@ VIDEO_CODEC_RGBA
Raw RGBA (4 bytes per pixel, uncompressed)
Definition codecs.h:29
@ VIDEO_CODEC_H265
H.265 (HEVC) video codec.
Definition codecs.h:30
⚙️ Common definitions, error codes, macros, and types shared throughout the application
📦 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:66
#define NET_TO_HOST_U16(val)
Definition endian.h:111
#define NET_TO_HOST_U32(val)
Definition endian.h:81
atomic_t g_should_exit
Global application exit flag (shared across all modes)
Definition globals.c:34
opus_codec_t * opus_codec_create_decoder(int sample_rate)
Create an Opus decoder.
Definition opus.c:74
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.c:143
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.
#define MAX_FRAME_BUFFER_SIZE
Maximum frame buffer size including headers and compression.
#define BUFFER_SIZE_SMALL
Small buffer size (256 bytes)
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:414
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#define safe_size_mul
Definition common.h:488
unsigned long long uint64_t
Definition common.h:59
unsigned char uint8_t
Definition common.h:56
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_INVALID_STATE
@ ERROR_NETWORK
Definition error_codes.h:77
@ ERROR_NETWORK_PROTOCOL
Definition error_codes.h:81
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
#define MAX_CLIENTS
Maximum possible clients (static array size) - actual runtime limit set by –max-clients (1-32)
Definition limits.h:26
#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.
Definition log/log.h:574
void log_msg(log_level_t level, const char *file, int line, const char *func, const char *fmt,...)
Log a message at a specific level.
Definition log/log.c:1019
enum remote_log_direction remote_log_direction_t
Remote log packet direction enumeration.
#define log_info_client(client, fmt,...)
Server sends INFO log message to client.
Definition network/log.h:69
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
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.
Definition log/log.c:1550
log_level_t
Logging levels enumeration.
Definition types.h:29
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
@ REMOTE_LOG_DIRECTION_SERVER_TO_CLIENT
Definition network/log.h:22
@ REMOTE_LOG_DIRECTION_UNKNOWN
Definition network/log.h:21
@ REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER
Definition network/log.h:23
#define VIDEO_CODEC_SUPPORTED(cap, codec)
Check if a video codec is supported in capabilities bitmask.
Definition codecs.h:86
uint64_t time_get_realtime_ns(void)
Get current wall-clock (real) time in nanoseconds.
Definition util/time.c:119
#define NS_PER_SEC_INT
Definition time.h:157
asciichat_error_t frame_validate_legacy(size_t len, size_t expected_rgb_size)
Validate legacy frame format.
#define CLIENT_CAP_STRETCH
Client can stretch frames to fill terminal.
Definition packet.h:926
#define STREAM_TYPE_VIDEO
Video stream.
Definition packet.h:937
#define STREAM_TYPE_AUDIO
Audio stream.
Definition packet.h:938
#define ACIP_CRYPTO_HAS_ENCRYPT(mode)
Check if crypto mode includes payload encryption.
Definition packet.h:1092
#define CLIENT_CAP_COLOR
Client supports color rendering.
Definition packet.h:925
asciichat_error_t frame_check_size_overflow(size_t header_size, size_t data_size)
Check for overflow when adding header to data.
#define REMOTE_LOG_FLAG_TRUNCATED
Remote log packet flag definitions.
Definition packet.h:736
#define CLIENT_CAP_AUDIO
Client can send/receive audio.
Definition packet.h:924
#define CLIENT_CAP_VIDEO
Client can send/receive video.
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_stop(packet_queue_t *queue)
Destroy a packet queue and free all resources.
Definition queue.c:616
#define AUDIO_BATCH_SAMPLES
Total samples in audio batch (8192 samples)
Definition packet.h:216
#define AUDIO_SAMPLES_PER_PACKET
Samples per audio packet (256 samples)
Definition packet.h:235
#define MAX_REMOTE_LOG_MESSAGE_LENGTH
Maximum remote log message length (512 bytes)
Definition packet.h:134
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:299
palette_type_t
Built-in palette type enumeration.
Definition palette.h:84
@ PALETTE_CUSTOM
User-defined via –palette-chars.
Definition palette.h:98
int socket_shutdown(socket_t sock, int how)
Shutdown socket I/O.
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
int safe_vsnprintf(char *buffer, size_t buffer_size, const char *format, va_list ap)
Safe formatted string printing with va_list.
Definition system.c:199
#define INVALID_SOCKET_VALUE
Invalid socket value (POSIX: -1)
Definition socket.h:278
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)
#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 util/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.
int socket_t
🔊 Audio Capture and Playback Interface for ascii-chat
ACIP server-side protocol API.
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)
#define log_info_every(interval_us, fmt,...)
Rate-limited INFO logging.
Definition log/log.h:705
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
Definition log/log.h:702
🔢 Mathematical Utility Functions
Frame validation utilities for IMAGE_FRAME packets.
#define FRAME_HEADER_SIZE_LEGACY
Legacy frame header size (width:4 + height:4)
action_args_t args
Opus audio codec wrapper for real-time encoding/decoding.
asciichat_error_t packet_parse_remote_log(const void *data, size_t len, log_level_t *out_level, remote_log_direction_t *out_direction, uint16_t *out_flags, char *message_buffer, size_t message_buffer_size, size_t *out_message_length)
Parse a remote log packet payload into components.
Definition packet.c:1056
asciichat_error_t packet_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:910
ASCII Palette Management for Video-to-ASCII Conversion.
asciichat_error_t acip_send_error(acip_transport_t *transport, uint32_t error_code, const char *message)
Send error message packet.
Definition send.c:301
asciichat_error_t acip_send_pong(acip_transport_t *transport)
Send pong packet.
Definition send.c:280
ACIP shared/bidirectional packet sending functions.
h265_server_context_t *volatile g_h265_server
Global H.265 codec server context.
ascii-chat Server Mode Entry Point Header
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_pong_packet(client_info_t *client, const void *data, size_t len)
Handle PONG packet - client acknowledged our PING.
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_image_frame_h265_packet(client_info_t *client, const void *data, size_t len)
Process H.265-encoded image frame from client.
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.
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_remote_log_packet_from_client(client_info_t *client, const void *data, size_t len)
void handle_ping_packet(client_info_t *client, const void *data, size_t len)
Handle PING packet - respond with PONG.
int frame
Definition splash.c:99
client_manager_t g_client_manager
Global client manager singleton - central coordination point.
Client-side H.265 media capture and encoding pipeline.
H.265 codec transport protocol definitions.
asciichat_error_t h265_server_decode_and_convert(h265_server_client_t *client, const uint8_t *h265_packet, size_t packet_size, uint8_t *output_rgba, uint16_t *output_width, uint16_t *output_height, size_t *output_size)
h265_server_client_t * h265_server_get_client_decoder(h265_server_context_t *ctx, uint32_t client_id)
Server-side H.265 decoding pipeline for multi-client streams.
struct h265_server_client h265_server_client_t
Transport instance structure.
Definition transport.h:214
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:904
Client information packet structure.
Definition packet.h:653
char display_name[32]
User display name (null-terminated, max MAX_DISPLAY_NAME_LEN bytes)
Definition packet.h:657
uint32_t capabilities
Client capabilities bitmask (CLIENT_CAP_VIDEO | CLIENT_CAP_AUDIO | CLIENT_CAP_COLOR | CLIENT_CAP_STRE...
Definition packet.h:660
Per-client state structure for server-side client management.
terminal_capabilities_t terminal_caps
char display_name[MAX_DISPLAY_NAME_LEN]
crypto_handshake_context_t crypto_handshake_ctx
video_frame_buffer_t * incoming_video_buffer
audio_ring_buffer_t * incoming_audio_buffer
char client_id[MAX_CLIENT_ID_LEN]
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.h:95
Protocol version negotiation packet structure (Packet Type 1)
Definition packet.h:818
uint8_t reserved[7]
Reserved bytes for future expansion (must be zero)
Definition packet.h:832
uint16_t protocol_revision
Minor protocol revision (server can be newer)
Definition packet.h:822
uint16_t protocol_version
Major protocol version (must match for compatibility)
Definition packet.h:820
uint16_t feature_flags
Feature flags bitmask (FEATURE_RLE_ENCODING, etc.)
Definition packet.h:830
uint8_t compression_algorithms
Supported compression algorithms bitmask (COMPRESS_ALGO_*)
Definition packet.h:826
uint8_t supports_encryption
Encryption & authentication mode bitmask (ACIP_CRYPTO_* constants: NONE, ENCRYPT, AUTH,...
Definition packet.h:824
Server state packet structure.
Definition packet.h:706
uint32_t reserved[6]
Reserved fields for future use (must be zero)
Definition packet.h:712
uint32_t active_client_count
Number of clients actively sending video/audio streams.
Definition packet.h:710
uint32_t connected_client_count
Total number of currently connected clients.
Definition packet.h:708
Terminal size update packet.
Definition packet.h:637
uint32_t height
Terminal height in characters.
Definition packet.h:641
uint32_t width
Terminal width in characters.
Definition packet.h:639
Terminal capabilities packet structure (Packet Type 5)
Definition packet.h:1018
uint32_t codec_capabilities_video
Video codec capabilities bitmask (VIDEO_CODEC_CAP_*)
Definition packet.h:1050
uint32_t capabilities
Terminal capabilities bitmask (TERM_CAP_* flags)
Definition packet.h:1020
uint32_t color_level
Color level enum value (terminal_color_mode_t)
Definition packet.h:1022
uint32_t color_count
Actual color count (16, 256, or 16777216)
Definition packet.h:1024
uint16_t height
Terminal height in characters.
Definition packet.h:1030
uint8_t wants_padding
Whether client wants frame padding (1=padding enabled, 0=no padding for snapshot/piped modes)
Definition packet.h:1048
uint32_t render_mode
Render mode enum value (foreground/background/half-block)
Definition packet.h:1026
uint16_t width
Terminal width in characters.
Definition packet.h:1028
uint8_t desired_fps
Client's desired frame rate (1-144 FPS)
Definition packet.h:1044
uint8_t detection_reliable
Detection reliability flag (1=reliable detection, 0=best guess)
Definition packet.h:1036
uint32_t codec_capabilities_audio
Audio codec capabilities bitmask (AUDIO_CODEC_CAP_*)
Definition packet.h:1052
char palette_custom[64]
Custom palette characters (if palette_type == PALETTE_CUSTOM)
Definition packet.h:1042
char colorterm[32]
$COLORTERM environment variable value (for debugging)
Definition packet.h:1034
uint32_t palette_type
Palette type enum value (palette_type_t)
Definition packet.h:1040
uint8_t color_filter
Monochromatic color filter enum value (color_filter_t)
Definition packet.h:1046
uint32_t utf8_support
UTF-8 support flag (0=no UTF-8, 1=UTF-8 supported)
Definition packet.h:1038
char term_type[32]
$TERM environment variable value (for debugging)
Definition packet.h:1032
int palette_type
Palette type enum value (palette_type_t)
Definition terminal.h:729
terminal_color_mode_t color_level
Detected color support level (terminal_color_mode_t)
Definition terminal.h:711
char palette_custom[64]
Custom palette characters (if palette_type == PALETTE_CUSTOM)
Definition terminal.h:731
uint8_t desired_fps
Client's desired frame rate (1-144 FPS)
Definition terminal.h:733
render_mode_t render_mode
Preferred rendering mode (render_mode_t)
Definition terminal.h:721
bool utf8_support
True if terminal supports UTF-8 encoding.
Definition terminal.h:717
bool wants_padding
Whether client wants frame padding (centering) - false for snapshot/piped modes.
Definition terminal.h:737
uint32_t capabilities
Capability flags bitmask (terminal_capability_flags_t)
Definition terminal.h:713
uint32_t color_count
Maximum number of colors (2, 16, 256, or 16777216)
Definition terminal.h:715
color_filter_t color_filter
Monochromatic color filter enum value (color_filter_t)
Definition terminal.h:735
char term_type[64]
$TERM environment variable value (for debugging)
Definition terminal.h:723
char colorterm[64]
$COLORTERM environment variable value (for debugging)
Definition terminal.h:725
bool detection_reliable
True if detection is confident (reliable detection)
Definition terminal.h:719
bool wants_background
True if background colors are preferred.
Definition terminal.h:727
Video frame structure.
Cross-platform system functions interface for ascii-chat.
const char * terminal_color_level_name(terminal_color_mode_t level)
Get name of color level.
color_filter_t
Monochromatic color filter enumeration.
Definition terminal.h:599
@ RENDER_MODE_BACKGROUND
Background colors (block colors)
Definition terminal.h:666
@ RENDER_MODE_HALF_BLOCK
Unicode half-block characters (mixed foreground/background)
Definition terminal.h:668
⏱️ High-precision timing utilities using sokol_time.h and uthash
#define LOG_ERROR
Definition types.h:42
#define LOG_INFO
Definition types.h:40
📊 String Formatting Utilities
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_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.
asciichat_error_t image_validate_dimensions(size_t width, size_t height)
Validate image dimensions (non-zero, within limits)
Definition video.c:12