ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
protocol.c File Reference

📡 Server packet processor: client communication handling, protocol state management, and packet dispatch More...

Go to the source code of this file.

Macros

#define OPUS_DECODE_STATIC_MAX_SAMPLES   (32 * 960)
 

Functions

void disconnect_client_for_bad_data (client_info_t *client, const char *format,...)
 
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_client_leave_packet (client_info_t *client, const void *data, size_t len)
 Process CLIENT_LEAVE packet - handle clean client disconnect.
 
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_stream_stop_packet (client_info_t *client, const void *data, size_t len)
 Process STREAM_STOP packet - client requests to halt media transmission.
 
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_packet (client_info_t *client, const void *data, size_t len)
 Process AUDIO packet - store single audio sample batch (legacy format)
 
void handle_remote_log_packet_from_client (client_info_t *client, const void *data, size_t len)
 
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_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 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_client_capabilities_packet (client_info_t *client, const void *data, size_t len)
 Process CLIENT_CAPABILITIES packet - configure client-specific rendering.
 
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_ping_packet (client_info_t *client)
 Process PING packet - respond with PONG for keepalive.
 
int send_server_state_to_client (client_info_t *client)
 Send current server state to a specific client.
 
void broadcast_clear_console_to_all_clients (void)
 Signal all active clients to clear their displays before next video frame.
 

Variables

atomic_bool g_server_should_exit
 Global shutdown flag from main.c - used to avoid error spam during shutdown.
 

Detailed Description

📡 Server packet processor: client communication handling, protocol state management, and packet dispatch

CORE RESPONSIBILITIES

  1. Parse and validate incoming packets from clients
  2. Update client state based on received packet data
  3. Coordinate with other modules for media processing
  4. Generate appropriate server responses to client requests
  5. Maintain protocol compliance and packet format standards

PACKET PROCESSING ARCHITECTURE:

The protocol processing follows a clear pattern:

  1. PACKET RECEPTION (in client.c receive thread):
    • Receives raw packet data from socket
    • Validates packet header and CRC
    • Dispatches to appropriate handler function
  2. HANDLER FUNCTION (this module):
    • Validates packet payload structure
    • Updates client state with thread-safe patterns
    • Processes media data (stores in buffers)
    • Generates any necessary responses
  3. RESPONSE GENERATION (via packet queues):
    • Queues response packets for delivery
    • Uses client's outgoing packet queues
    • Send thread delivers responses asynchronously

SUPPORTED PACKET TYPES:

CLIENT LIFECYCLE:

  • PACKET_TYPE_CLIENT_JOIN: Initial client capabilities and identity
  • PACKET_TYPE_CLIENT_LEAVE: Clean disconnect notification
  • PACKET_TYPE_CLIENT_CAPABILITIES: Terminal capabilities and preferences

MEDIA STREAMING:

  • PACKET_TYPE_STREAM_START: Begin sending audio/video
  • PACKET_TYPE_STREAM_STOP: Stop sending audio/video
  • PACKET_TYPE_IMAGE_FRAME: Raw RGB video frame data
  • PACKET_TYPE_AUDIO: Single audio sample packet (legacy)
  • PACKET_TYPE_AUDIO_BATCH: Batched audio samples (efficient)

CONTROL PROTOCOL:

  • PACKET_TYPE_PING: Client keepalive request
  • PACKET_TYPE_PONG: Server keepalive response

THREAD SAFETY AND STATE MANAGEMENT:

CLIENT STATE SYNCHRONIZATION: All client state modifications use the snapshot pattern:

  1. Acquire client->client_state_mutex
  2. Update client state fields
  3. Release mutex immediately
  4. Process using local copies if needed

MEDIA BUFFER COORDINATION: Video frames: Stored in client->incoming_video_buffer (thread-safe) Audio samples: Stored in client->incoming_audio_buffer (lock-free) Both buffers are processed by render threads in render.c

PACKET VALIDATION STRATEGY: All handlers validate:

  • Packet size matches expected structure size
  • Client capabilities permit the operation
  • Buffer pointers are valid before access
  • Network byte order conversion where needed

ERROR HANDLING PHILOSOPHY:

  • Invalid packets are logged but don't disconnect clients
  • Buffer allocation failures are handled gracefully
  • Network errors during responses don't affect client state
  • Shutdown conditions are detected and avoid error spam

INTEGRATION WITH OTHER MODULES:

  • client.c: Called by receive threads, manages client lifecycle
  • render.c: Consumes video/audio data stored by handlers
  • stream.c: Uses client capabilities for frame generation
  • main.c: Provides global state (g_server_should_exit, etc.)

WHY THIS MODULAR DESIGN:

The original server.c mixed protocol handling with connection management and rendering logic, making it difficult to:

  • Add new packet types
  • Modify protocol behavior
  • Debug packet-specific issues
  • Test protocol compliance

This separation provides:

  • Clear protocol specification
  • Easier protocol evolution
  • Better error isolation
  • Improved testability
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025
Version
2.0 (Post-Modularization)
See also
client.c For client receive thread implementation
render.c For media buffer consumption
network.h For Packet Types structure definitions

Definition in file server/protocol.c.

Macro Definition Documentation

◆ OPUS_DECODE_STATIC_MAX_SAMPLES

#define OPUS_DECODE_STATIC_MAX_SAMPLES   (32 * 960)

Function Documentation

◆ broadcast_clear_console_to_all_clients()

void broadcast_clear_console_to_all_clients ( void  )

Signal all active clients to clear their displays before next video frame.

Sets the needs_display_clear flag for all currently connected and active clients. This is used when the grid layout changes (clients join/leave) to ensure all clients clear their displays before receiving frames with the new layout.

ARCHITECTURE:

  • Uses atomic flag (needs_display_clear) instead of packet queue
  • Send thread checks flag and sends CLEAR_CONSOLE before next video frame
  • Guarantees CLEAR_CONSOLE arrives before new grid layout frame

SYNCHRONIZATION:

  • Acquires g_client_manager_rwlock for reading
  • Uses atomic operations for flag setting (no mutex needed)
  • Thread-safe access to client list

USAGE SCENARIO:

  • Called when active video source count changes
  • Ensures all clients clear before new grid layout is sent
  • Prevents visual artifacts from old content
Note
This function iterates all clients but only flags active ones
Non-blocking - just sets atomic flags

Definition at line 1774 of file server/protocol.c.

1774 {
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}
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
@ ERROR_INVALID_STATE
#define log_warn(...)
Log a WARN message.

References ERROR_INVALID_STATE, log_warn, and SET_ERRNO.

◆ handle_audio_batch_packet()

void handle_audio_batch_packet ( client_info_t client,
const void *  data,
size_t  len 
)

Process AUDIO_BATCH packet - store efficiently batched audio samples.

Handles the optimized audio packet format that bundles multiple sample chunks into a single packet. This reduces packet overhead and improves network efficiency for audio streaming.

PACKET STRUCTURE EXPECTED:

  • audio_batch_packet_t header:
    • uint32_t batch_count: Number of sample chunks in this batch
    • uint32_t total_samples: Total number of float samples
    • uint32_t sample_rate: Samples per second (typically 44100)
    • uint32_t channels: Number of audio channels (1 = mono)
  • float samples[total_samples]: IEEE 754 sample data

PERFORMANCE ADVANTAGES:

  • Reduces packet count by 5-10x compared to single audio packets
  • Lower network overhead and CPU context switching
  • Better burst tolerance with larger buffers

VALIDATION PERFORMED:

  • Header size matches audio_batch_packet_t
  • Total packet size matches header + samples
  • Sample count is within reasonable bounds
  • Client is authorized to send audio

BUFFER MANAGEMENT:

  • Extracts samples from packet payload
  • Stores in client->incoming_audio_buffer (same as single format)
  • Ring buffer automatically handles overflow
  • mixer.c consumes batched samples identically

ERROR HANDLING:

  • Invalid batch headers are logged and packet dropped
  • Oversized batches are rejected (prevents DoS)
  • Buffer allocation failures are handled gracefully
Parameters
clientSource client providing batched audio data
dataPacket payload containing batch header and samples
lenTotal size of packet payload in bytes
Note
This is the preferred audio packet format
Batching is transparent to audio processing pipeline
See also
handle_audio_packet() For legacy single-batch format
AUDIO_BATCH_SAMPLES For maximum batch size constant

Definition at line 1029 of file server/protocol.c.

1029 {
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}
#define NET_TO_HOST_U32(val)
Definition endian.h:86
asciichat_error_t audio_ring_buffer_write(audio_ring_buffer_t *rb, const float *data, int samples)
Write audio samples to ring buffer.
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 int uint32_t
Definition common.h:58
#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 char uint8_t
Definition common.h:56
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:46
@ ERROR_MEMORY
Definition error_codes.h:53
@ ASCIICHAT_OK
Definition error_codes.h:48
#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_error(...)
Log an ERROR message.
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
#define log_info(...)
Log an INFO message.
#define AUDIO_BATCH_SAMPLES
Total samples in audio batch (8192 samples)
Definition packet.h:214
#define VALIDATE_AUDIO_STREAM_ENABLED(client, packet_name)
#define VALIDATE_NONZERO(client, value, value_name, packet_name)
#define VALIDATE_MIN_SIZE(client, len, min_size, packet_name)
#define VALIDATE_NOTNULL_DATA(client, data, packet_name)
void disconnect_client_for_bad_data(client_info_t *client, const char *format,...)
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
atomic_uint client_id
audio_ring_buffer_t * incoming_audio_buffer
atomic_bool is_sending_audio

References ASCIICHAT_OK, AUDIO_BATCH_SAMPLES, audio_dequantize_samples(), audio_parse_batch_header(), audio_ring_buffer_write(), audio_batch_info_t::batch_count, client_info::client_id, disconnect_client_for_bad_data(), ERROR_MEMORY, client_info::incoming_audio_buffer, client_info::is_sending_audio, log_debug_every, log_error, log_info, LOG_RATE_DEFAULT, NET_TO_HOST_U32, SAFE_FREE, SAFE_MALLOC, safe_size_mul, audio_batch_info_t::sample_rate, SET_ERRNO, audio_batch_info_t::total_samples, VALIDATE_AUDIO_STREAM_ENABLED, VALIDATE_MIN_SIZE, VALIDATE_NONZERO, and VALIDATE_NOTNULL_DATA.

Referenced by process_decrypted_packet().

◆ handle_audio_packet()

void handle_audio_packet ( client_info_t client,
const void *  data,
size_t  len 
)

Process AUDIO packet - store single audio sample batch (legacy format)

Handles the original audio packet format that sends one batch of float samples per packet. This format is less efficient than AUDIO_BATCH but still supported for backward compatibility.

PACKET STRUCTURE:

  • float samples[len/sizeof(float)] (IEEE 754 format)
  • Sample rate assumed to be 44100 Hz
  • Mono audio (single channel)

PERFORMANCE CHARACTERISTICS:

BUFFER MANAGEMENT:

  • Stores samples in client->incoming_audio_buffer (lock-free ring buffer)
  • Buffer overflow drops oldest samples to maintain real-time behavior
  • mixer.c consumes samples for multi-client audio mixing

STATE VALIDATION:

  • Only processes if client->is_sending_audio is true
  • Requires valid buffer pointer and non-zero length
  • Handles buffer pointer safely during shutdown

ERROR HANDLING:

  • Invalid packets are silently ignored
  • Buffer overflow is handled by ring buffer (drops old data)
  • Graceful shutdown behavior
Parameters
clientSource client providing audio data
dataPacket payload containing float audio samples
lenSize of packet payload in bytes
Note
Prefer AUDIO_BATCH format for better efficiency
Audio processing happens in mixer threads, not here
See also
handle_audio_batch_packet() For efficient batched format
audio_ring_buffer_write() For storage implementation

Definition at line 932 of file server/protocol.c.

932 {
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}
#define AUDIO_SAMPLES_PER_PACKET
Samples per audio packet (256 samples)
Definition packet.h:233
#define VALIDATE_AUDIO_SAMPLE_COUNT(client, num_samples, max_samples, packet_name)
#define VALIDATE_RESOURCE_INITIALIZED(client, resource, resource_name)
#define VALIDATE_AUDIO_ALIGNMENT(client, len, sample_size, packet_name)

References ASCIICHAT_OK, audio_ring_buffer_write(), AUDIO_SAMPLES_PER_PACKET, client_info::incoming_audio_buffer, log_error, VALIDATE_AUDIO_ALIGNMENT, VALIDATE_AUDIO_SAMPLE_COUNT, VALIDATE_AUDIO_STREAM_ENABLED, VALIDATE_NOTNULL_DATA, and VALIDATE_RESOURCE_INITIALIZED.

Referenced by process_decrypted_packet().

◆ handle_client_capabilities_packet()

void handle_client_capabilities_packet ( client_info_t client,
const void *  data,
size_t  len 
)

Process CLIENT_CAPABILITIES packet - configure client-specific rendering.

This packet contains detailed information about the client's terminal capabilities and preferences. The server uses this data to generate appropriately formatted ASCII art and ANSI escape sequences.

PACKET STRUCTURE EXPECTED:

  • terminal_capabilities_packet_t containing:
    • width, height: Terminal dimensions in characters
    • capabilities: Bitmask of terminal features
    • color_level: ANSI color support level (1, 8, 16, 256, 24-bit)
    • color_count: Number of supported colors
    • render_mode: Foreground, background, or half-block rendering
    • term_type: $TERM environment variable value
    • colorterm: $COLORTERM environment variable value
    • utf8_support: Whether terminal supports UTF-8
    • palette_type: ASCII character palette preference
    • palette_custom: Custom character set if PALETTE_CUSTOM

STATE CHANGES PERFORMED:

  • Updates client dimensions (width, height)
  • Stores complete terminal capabilities structure
  • Initializes per-client ASCII palette cache
  • Sets client->has_terminal_caps = true

PALETTE INITIALIZATION: The function performs critical palette setup:

  1. Determines character set based on palette_type
  2. Handles custom palettes if provided
  3. Generates luminance-to-character mapping
  4. Caches results for fast ASCII generation

THREAD SAFETY:

  • All client state updates are mutex-protected
  • Uses client->client_state_mutex for atomicity
  • Safe to call concurrently with render threads

VALIDATION PERFORMED:

  • Packet size matches expected structure
  • String fields are safely copied with bounds checking
  • Palette initialization is verified
  • Network byte order conversion

ERROR HANDLING:

  • Invalid packets are logged and ignored
  • Palette initialization failures use server defaults
  • Missing capabilities default to safe values

INTEGRATION IMPACT:

  • render.c uses capabilities for ASCII generation
  • Color output depends on client's color_level
  • Character selection uses initialized palette
Parameters
clientTarget client whose capabilities are being configured
dataPacket payload containing terminal_capabilities_packet_t
lenSize of packet payload in bytes
Note
This packet is typically sent once after CLIENT_JOIN
Capabilities can be updated during the session
Changes affect all subsequent ASCII frame generation
See also
initialize_client_palette() For Character Palettes setup details
terminal_color_level_name() For color level descriptions

Definition at line 1464 of file server/protocol.c.

1464 {
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}
#define NET_TO_HOST_U16(val)
Definition endian.h:116
unsigned short uint16_t
Definition common.h:57
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:358
#define log_info_client(client, fmt,...)
Server sends INFO log message to client.
#define log_debug(...)
Log a DEBUG message.
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
uint16_t height
Terminal height in characters.
Definition packet.h:921
uint32_t render_mode
Render mode enum value (foreground/background/half-block)
Definition packet.h:917
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
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 utf8_support
UTF-8 support flag (0=no UTF-8, 1=UTF-8 supported)
Definition packet.h:929
char term_type[32]
$TERM environment variable value (for debugging)
Definition packet.h:923
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
const char * terminal_color_level_name(terminal_color_mode_t level)
Get name of color level.
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
Definition mutex.h:140
#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 VALIDATE_PACKET_SIZE(client, data, len, expected_size, packet_name)
#define VALIDATE_RANGE(client, value, min_val, max_val, value_name, packet_name)
terminal_capabilities_t terminal_caps
char client_palette_chars[256]
atomic_ushort height
atomic_ushort width
bool client_palette_initialized
mutex_t client_state_mutex
char client_luminance_palette[256]
palette_type_t client_palette_type
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

References terminal_capabilities_packet_t::capabilities, terminal_capabilities_t::capabilities, client_info::client_id, client_info::client_luminance_palette, client_info::client_palette_chars, client_info::client_palette_initialized, client_info::client_palette_len, client_info::client_palette_type, client_info::client_state_mutex, terminal_capabilities_packet_t::color_count, terminal_capabilities_t::color_count, terminal_capabilities_packet_t::color_level, terminal_capabilities_t::color_level, terminal_capabilities_packet_t::colorterm, terminal_capabilities_t::colorterm, terminal_capabilities_packet_t::desired_fps, terminal_capabilities_t::desired_fps, terminal_capabilities_packet_t::detection_reliable, terminal_capabilities_t::detection_reliable, ERROR_INVALID_STATE, client_info::has_terminal_caps, client_info::height, terminal_capabilities_packet_t::height, initialize_client_palette(), log_debug, log_info, log_info_client, mutex_lock, mutex_unlock, NET_TO_HOST_U16, NET_TO_HOST_U32, terminal_capabilities_packet_t::palette_custom, terminal_capabilities_t::palette_custom, PALETTE_CUSTOM, terminal_capabilities_packet_t::palette_type, terminal_capabilities_t::palette_type, terminal_capabilities_packet_t::render_mode, terminal_capabilities_t::render_mode, RENDER_MODE_BACKGROUND, RENDER_MODE_HALF_BLOCK, SAFE_STRNCPY, SET_ERRNO, terminal_capabilities_packet_t::term_type, terminal_capabilities_t::term_type, client_info::terminal_caps, terminal_color_level_name(), terminal_capabilities_packet_t::utf8_support, terminal_capabilities_t::utf8_support, VALIDATE_NONZERO, VALIDATE_PACKET_SIZE, VALIDATE_RANGE, terminal_capabilities_t::wants_background, client_info::width, and terminal_capabilities_packet_t::width.

Referenced by process_decrypted_packet().

◆ handle_client_join_packet()

void handle_client_join_packet ( client_info_t client,
const void *  data,
size_t  len 
)

Process CLIENT_JOIN packet - client announces identity and capabilities.

This is the first substantive packet clients send after establishing a TCP connection. It provides the server with essential information for managing the client throughout its session.

PACKET STRUCTURE EXPECTED:

  • client_info_packet_t containing:
    • display_name: Human-readable client identifier
    • capabilities: Bitmask of CLIENT_CAP_* flags

STATE CHANGES PERFORMED:

  • Updates client->display_name from packet
  • Sets client->can_send_video based on CLIENT_CAP_VIDEO
  • Sets client->can_send_audio based on CLIENT_CAP_AUDIO
  • Sets client->wants_stretch based on CLIENT_CAP_STRETCH

PROTOCOL BEHAVIOR:

  • Does NOT automatically start media streams (requires STREAM_START)
  • Does NOT send CLEAR_CONSOLE to other clients (prevents flicker)
  • Logs client capabilities for debugging

ERROR HANDLING:

  • Silently ignores packets with wrong size
  • Invalid display names are truncated safely
  • Missing capabilities default to false
Parameters
clientTarget client whose state will be updated
dataPacket payload (should be client_info_packet_t)
lenSize of packet payload in bytes
Note
This function should only be called by client receive threads
Client state is already protected by receive thread serialization
See also
handle_stream_start_packet() For enabling media transmission

Definition at line 282 of file server/protocol.c.

282 {
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}
#define MAX_DISPLAY_NAME_LEN
Maximum display name length in characters.
Definition limits.h:20
char display_name[MAX_DISPLAY_NAME_LEN]
User display name (null-terminated, max MAX_DISPLAY_NAME_LEN bytes)
Definition packet.h:549
#define CLIENT_CAP_STRETCH
Client can stretch frames to fill terminal.
Definition packet.h:818
#define CLIENT_CAP_COLOR
Client supports color rendering.
Definition packet.h:817
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
#define VALIDATE_FLAGS_MASK(client, flags, valid_mask, packet_name)
#define VALIDATE_CAPABILITY_FLAGS(client, flags, valid_mask, packet_name)
Client information packet structure.
Definition packet.h:545
char display_name[MAX_DISPLAY_NAME_LEN]

References client_info::can_send_audio, client_info::can_send_video, client_info_packet_t::capabilities, CLIENT_CAP_AUDIO, CLIENT_CAP_COLOR, CLIENT_CAP_STRETCH, CLIENT_CAP_VIDEO, client_info::client_id, disconnect_client_for_bad_data(), client_info::display_name, client_info_packet_t::display_name, log_info, log_info_client, MAX_DISPLAY_NAME_LEN, NET_TO_HOST_U32, SAFE_STRNCPY, VALIDATE_CAPABILITY_FLAGS, VALIDATE_FLAGS_MASK, VALIDATE_PACKET_SIZE, and client_info::wants_stretch.

Referenced by process_decrypted_packet().

◆ handle_client_leave_packet()

void handle_client_leave_packet ( client_info_t client,
const void *  data,
size_t  len 
)

Process CLIENT_LEAVE packet - handle clean client disconnect.

Clients may send this packet before disconnecting to allow the server to log the disconnect reason and perform clean state management. This is optional but preferred over abrupt disconnects.

PACKET STRUCTURE EXPECTED:

  • Optional string containing disconnect reason (0-256 bytes)

PROTOCOL BEHAVIOR:

  • Client logs the disconnect reason if provided
  • Server continues normal disconnect sequence after receiving packet
  • Client remains responsible for closing socket

ERROR HANDLING:

  • Empty payload is handled gracefully
  • Oversized payloads are rejected
  • Invalid UTF-8 in reason is handled gracefully (logged as-is)
Parameters
clientClient that sent the leave packet
dataPacket payload (optional reason string)
lenSize of packet payload in bytes (0-256)
Note
This handler doesn't trigger immediate disconnect
Actual disconnect occurs when socket closes

Definition at line 424 of file server/protocol.c.

424 {
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}
atomic_bool active

References client_info::active, client_info::client_id, ERROR_INVALID_STATE, log_info, log_warn, and SET_ERRNO.

Referenced by process_decrypted_packet().

◆ handle_image_frame_packet()

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.

This is the most performance-critical packet handler, processing real-time video data from clients. It validates, stores, and tracks video frames for subsequent ASCII conversion and grid layout.

PACKET STRUCTURE EXPECTED:

  • uint32_t width (network byte order)
  • uint32_t height (network byte order)
  • rgb_pixel_t pixels[width * height] (RGB888 format)

PERFORMANCE CHARACTERISTICS:

  • Called at 30fps per active client
  • Uses zero-copy storage when possible
  • Validates packet size before processing
  • Implements frame counting for debug logging

STATE CHANGES PERFORMED:

  • Auto-enables client->is_sending_video if not already set
  • Increments client->frames_received counter
  • Updates client dimensions if changed

BUFFER MANAGEMENT:

  • Stores entire packet (including dimensions) in client->incoming_video_buffer
  • Uses multi-frame ringbuffer for burst handling
  • Buffer overflow drops oldest frames (maintains real-time performance)
  • render.c threads consume frames for ASCII conversion

VALIDATION PERFORMED:

  • Packet size matches width * height * 3 + 8 bytes
  • Width and height are reasonable (prevents memory exhaustion)
  • Buffer pointers are valid before access

ERROR HANDLING:

  • Invalid packets are logged and dropped
  • Buffer overflow is handled gracefully
  • Shutdown conditions don't generate error spam

PERFORMANCE OPTIMIZATIONS:

  • Debug logging is throttled (every 25000 frames)
  • Fast path for common case (valid packet with buffer space)
  • Minimal CPU work in receive thread (storage only)
Parameters
clientSource client providing video data
dataPacket payload containing image dimensions and RGB data
lenTotal size of packet payload in bytes
Note
This function is called by client receive threads at high frequency
Actual ASCII conversion happens in render threads (render.c)
Frame timestamps are added for synchronization purposes
See also
framebuffer_write_multi_frame() For buffer storage implementation
create_mixed_ascii_frame_for_client() For frame consumption

Definition at line 683 of file server/protocol.c.

683 {
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}
#define HOST_TO_NET_U32(val)
Definition endian.h:71
unsigned long long uint64_t
Definition common.h:59
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
void format_bytes_pretty(size_t bytes, char *out, size_t out_capacity)
Format byte count into human-readable string.
Definition format.c:10
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.
atomic_bool g_server_should_exit
Global shutdown flag from main.c - used to avoid error spam during shutdown.
uint32_t frames_received_logged
uint64_t frames_received
video_frame_buffer_t * incoming_video_buffer
atomic_bool is_sending_video
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)
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

References ASCIICHAT_OK, video_frame_t::capture_timestamp_us, client_info::client_id, client_info::client_state_mutex, video_frame_t::data, decompress_data(), disconnect_client_for_bad_data(), ERROR_INVALID_STATE, ERROR_MEMORY, format_bytes_pretty(), client_info::frames_received, client_info::frames_received_logged, g_server_should_exit, video_frame_t::height, HOST_TO_NET_U32, image_calc_rgb_size(), IMAGE_MAX_PIXELS_SIZE, image_validate_buffer_size(), image_validate_dimensions(), client_info::incoming_video_buffer, client_info::is_sending_video, log_debug, log_error, log_info, log_info_client, log_warn, mutex_lock, mutex_unlock, NET_TO_HOST_U32, SAFE_FREE, SAFE_MALLOC, video_frame_t::sequence_number, SET_ERRNO, video_frame_t::size, video_frame_begin_write(), video_frame_commit(), and video_frame_t::width.

Referenced by process_decrypted_packet().

◆ handle_ping_packet()

void handle_ping_packet ( client_info_t client)

Process PING packet - respond with PONG for keepalive.

Clients send periodic PING packets to verify the connection is still active. The server responds with a PONG packet to confirm bi-directional connectivity. This prevents network equipment from timing out idle connections.

PACKET STRUCTURE:

  • PING packets have no payload (header only)
  • PONG responses also have no payload

PROTOCOL BEHAVIOR:

  • Every PING must be answered with exactly one PONG
  • PONG is queued in client's video queue for delivery
  • No state changes are made to client

ERROR HANDLING:

  • Queue failures are logged but not fatal
  • Client disconnection during PONG delivery is handled gracefully
  • Excessive PING rate is not rate-limited (client responsibility)

PERFORMANCE CHARACTERISTICS:

  • Very low overhead (no payload processing)
  • Uses existing packet queue infrastructure
  • Send thread delivers PONG asynchronously
Parameters
clientSource client requesting keepalive confirmation
Note
PING/PONG packets help detect network failures
Response is queued, not sent immediately
See also
packet_queue_enqueue() For response delivery mechanism

Definition at line 1651 of file server/protocol.c.

1651 {
1652 // PONG responses should be handled directly via socket in send thread
1653 // For now, just log the ping
1654 (void)client;
1655}

◆ handle_protocol_version_packet()

void handle_protocol_version_packet ( client_info_t client,
const void *  data,
size_t  len 
)

Process PROTOCOL_VERSION packet - validate protocol compatibility.

Clients send this packet to announce their protocol version and capabilities. The server validates that the major version matches and logs any version mismatches for debugging purposes.

PACKET STRUCTURE EXPECTED:

  • protocol_version_packet_t containing:
    • protocol_version: Major version number (must match PROTOCOL_VERSION_MAJOR)
    • protocol_revision: Minor version number
    • supports_encryption: Encryption capability flag
    • compression_algorithms: Supported compression bitmask
    • feature_flags: Optional feature flags

VALIDATION PERFORMED:

  • Packet size matches sizeof(protocol_version_packet_t)
  • Major protocol version matches (PROTOCOL_VERSION_MAJOR)
  • Reserved bytes are zero (future-proofing)

ERROR HANDLING:

  • Version mismatch is logged but NOT fatal (backward compatibility)
  • Invalid packet size triggers disconnect
Parameters
clientClient that sent the packet
dataPacket payload (protocol_version_packet_t)
lenSize of packet payload in bytes
Note
This is typically the first packet in the handshake
Protocol version validation ensures compatibility

Definition at line 347 of file server/protocol.c.

347 {
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}
uint8_t reserved[7]
Reserved bytes for future expansion (must be zero)
Definition packet.h:724
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
uint16_t feature_flags
Feature flags bitmask (FEATURE_RLE_ENCODING, etc.)
Definition packet.h:722
uint8_t compression_algorithms
Supported compression algorithms bitmask (COMPRESS_ALGO_*)
Definition packet.h:718
uint8_t supports_encryption
Encryption support flag (1=support encryption, 0=plaintext only)
Definition packet.h:716
#define PROTOCOL_VERSION_MAJOR
Major protocol version number.
#define PROTOCOL_VERSION_MINOR
Minor protocol version number.
Protocol version negotiation packet structure (Packet Type 1)
Definition packet.h:710

References client_info::client_id, protocol_version_packet_t::compression_algorithms, disconnect_client_for_bad_data(), protocol_version_packet_t::feature_flags, log_debug, log_info, log_warn, NET_TO_HOST_U16, protocol_version_packet_t::protocol_revision, protocol_version_packet_t::protocol_version, PROTOCOL_VERSION_MAJOR, PROTOCOL_VERSION_MINOR, protocol_version_packet_t::reserved, and protocol_version_packet_t::supports_encryption.

Referenced by process_decrypted_packet().

◆ handle_remote_log_packet_from_client()

void handle_remote_log_packet_from_client ( client_info_t client,
const void *  data,
size_t  len 
)

Definition at line 948 of file server/protocol.c.

948 {
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}
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.
log_level_t
Logging levels enumeration.
Definition log/logging.h:59
@ REMOTE_LOG_DIRECTION_UNKNOWN
@ REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER
@ LOG_INFO
Definition log/logging.h:62
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
#define REMOTE_LOG_FLAG_TRUNCATED
Remote log packet flag definitions.
Definition packet.h:628
#define MAX_REMOTE_LOG_MESSAGE_LENGTH
Maximum remote log message length (512 bytes)
Definition packet.h:132

References ASCIICHAT_OK, client_info::client_id, disconnect_client_for_bad_data(), client_info::display_name, LOG_INFO, log_msg(), MAX_REMOTE_LOG_MESSAGE_LENGTH, packet_parse_remote_log(), REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER, REMOTE_LOG_DIRECTION_UNKNOWN, and REMOTE_LOG_FLAG_TRUNCATED.

Referenced by process_decrypted_packet().

◆ handle_size_packet()

void handle_size_packet ( client_info_t client,
const void *  data,
size_t  len 
)

Process terminal size update packet - handle client window resize.

Clients send this packet when their terminal window is resized, allowing the server to adjust ASCII frame dimensions accordingly. This ensures optimal use of the client's display area.

PACKET STRUCTURE EXPECTED:

  • size_packet_t containing:
    • uint16_t width: New terminal width in characters
    • uint16_t height: New terminal height in characters

STATE CHANGES PERFORMED:

  • Updates client->width with new dimensions
  • Updates client->height with new dimensions
  • Thread-safe update using client state mutex

RENDERING IMPACT:

  • Subsequent ASCII frames will use new dimensions
  • Grid layout calculations will incorporate new size
  • No immediate frame regeneration (happens on next cycle)

ERROR HANDLING:

  • Invalid packet sizes are ignored silently
  • Extreme dimensions are accepted (client responsibility)
  • Concurrent updates are handled safely
Parameters
clientTarget client whose terminal was resized
dataPacket payload containing new dimensions
lenSize of packet payload (should be sizeof(size_packet_t))
Note
Changes take effect on the next rendering cycle
No validation of reasonable dimension ranges

Definition at line 1592 of file server/protocol.c.

1592 {
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}
uint32_t height
Terminal height in characters.
Definition packet.h:533
uint32_t width
Terminal width in characters.
Definition packet.h:531
Terminal size update packet.
Definition packet.h:529

References client_info::client_id, client_info::client_state_mutex, client_info::height, size_packet_t::height, log_info, mutex_lock, mutex_unlock, NET_TO_HOST_U16, VALIDATE_NONZERO, VALIDATE_PACKET_SIZE, VALIDATE_RANGE, client_info::width, and size_packet_t::width.

◆ handle_stream_start_packet()

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.

Clients send this packet to indicate they're ready to start sending video and/or audio data. The server updates its internal state to expect and process media packets from this client.

PACKET STRUCTURE EXPECTED:

  • uint32_t stream_type (network byte order)
  • Bitmask containing STREAM_TYPE_VIDEO and/or STREAM_TYPE_AUDIO

STATE CHANGES PERFORMED:

  • VIDEO: Records intention to send video (is_sending_video set by first IMAGE_FRAME)
  • AUDIO: Sets client->is_sending_audio = true if STREAM_TYPE_AUDIO present
  • Enables render threads to include this client in output generation

PROTOCOL BEHAVIOR:

  • Client must have announced capabilities via CLIENT_JOIN first
  • Server will start processing IMAGE_FRAME and AUDIO packets
  • Render threads will begin generating output for this client
  • Grid layout will be recalculated to include this client

ERROR HANDLING:

  • Ignores packets with incorrect size
  • Invalid stream types are silently ignored
  • Graceful handling if client lacks necessary capabilities
Parameters
clientTarget client starting media transmission
dataPacket payload containing stream type flags
lenSize of packet payload (should be sizeof(uint32_t))
Note
Changes take effect immediately for subsequent media packets
Render threads will detect the state change on their next cycle
See also
handle_stream_stop_packet() For stopping media transmission
handle_image_frame_packet() For Video to ASCII Conversion data processing

Definition at line 509 of file server/protocol.c.

509 {
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}
opus_codec_t * opus_codec_create_decoder(int sample_rate)
Create an Opus decoder.
Definition opus_codec.c:62
#define STREAM_TYPE_VIDEO
Video stream.
Definition packet.h:829
#define STREAM_TYPE_AUDIO
Audio stream.
Definition packet.h:830

References client_info::client_id, client_info::is_sending_audio, log_error, log_info, log_info_client, NET_TO_HOST_U32, opus_codec_create_decoder(), client_info::opus_decoder, STREAM_TYPE_AUDIO, STREAM_TYPE_VIDEO, VALIDATE_CAPABILITY_FLAGS, VALIDATE_FLAGS_MASK, and VALIDATE_PACKET_SIZE.

Referenced by process_decrypted_packet().

◆ handle_stream_stop_packet()

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.

Clients send this packet to gracefully stop sending video and/or audio data. The server updates its state to exclude this client from active media processing and grid layout calculations.

PACKET STRUCTURE EXPECTED:

  • uint32_t stream_type (network byte order)
  • Bitmask containing STREAM_TYPE_VIDEO and/or STREAM_TYPE_AUDIO

STATE CHANGES PERFORMED:

  • Sets client->is_sending_video = false if STREAM_TYPE_VIDEO present
  • Sets client->is_sending_audio = false if STREAM_TYPE_AUDIO present
  • Render threads will stop including this client in output

PROTOCOL BEHAVIOR:

  • Client remains connected but won't appear in video grid
  • Existing buffered media from this client will still be processed
  • Grid layout recalculates to exclude this client
  • Client can restart streaming with STREAM_START packet

ERROR HANDLING:

  • Ignores packets with incorrect size
  • Invalid stream types are silently ignored
  • Safe to call multiple times or when not streaming
Parameters
clientTarget client stopping media transmission
dataPacket payload containing stream type flags
lenSize of packet payload (should be sizeof(uint32_t))
Note
Changes take effect immediately
Render threads will detect the state change on their next cycle
See also
handle_stream_start_packet() For starting media transmission

Definition at line 589 of file server/protocol.c.

589 {
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}

References client_info::client_id, client_info::is_sending_audio, client_info::is_sending_video, log_info, log_info_client, NET_TO_HOST_U32, STREAM_TYPE_AUDIO, STREAM_TYPE_VIDEO, VALIDATE_CAPABILITY_FLAGS, VALIDATE_FLAGS_MASK, and VALIDATE_PACKET_SIZE.

Referenced by process_decrypted_packet().

◆ send_server_state_to_client()

int send_server_state_to_client ( client_info_t client)

Send current server state to a specific client.

Generates and queues a SERVER_STATE packet containing information about the current number of connected and active clients. This helps clients understand the multi-user environment and adjust their behavior accordingly.

PACKET CONTENT GENERATED:

  • server_state_packet_t containing:
    • connected_client_count: Total clients connected to server
    • active_client_count: Clients actively sending video/audio
    • reserved: Padding for future extensions

USAGE SCENARIOS:

  • Initial state after client joins server
  • Periodic updates when client count changes
  • Response to client requests for server information

IMPLEMENTATION DETAILS:

  • Counts active clients by scanning global client manager
  • Converts data to network byte order before queuing
  • Uses client's video queue for delivery
  • Non-blocking operation (queues for later delivery)

THREAD SAFETY:

  • Uses reader lock on global client manager
  • Safe to call from any thread
  • Atomic snapshot of client counts

ERROR HANDLING:

  • Returns -1 if client or queue is invalid
  • Queue overflow handled by packet_queue_enqueue()
  • No side effects on failure
Parameters
clientTarget client to receive server state information
Returns
0 on successful queuing, -1 on error
Note
Packet delivery happens asynchronously via send thread
Client count may change between queuing and delivery
See also
broadcast_server_state_to_all_clients() For multi-client updates

Definition at line 1704 of file server/protocol.c.

1704 {
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}
@ ERROR_NETWORK
Definition error_codes.h:69
#define MAX_CLIENTS
Maximum possible clients (static array size) - actual runtime limit set by –max-clients (1-32)
Definition limits.h:23
uint32_t reserved[6]
Reserved fields for future use (must be zero)
Definition packet.h:604
uint32_t active_client_count
Number of clients actively sending video/audio streams.
Definition packet.h:602
uint32_t connected_client_count
Total number of currently connected clients.
Definition packet.h:600
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)
client_manager_t g_client_manager
Global client manager singleton - central coordination point.
acip_transport_t * transport
client_info_t clients[MAX_CLIENTS]
Array of client_info_t structures (backing storage)
Server state packet structure.
Definition packet.h:598

References acip_send_server_state(), client_info::active, server_state_packet_t::active_client_count, ASCIICHAT_OK, client_info::client_id, client_manager_t::clients, server_state_packet_t::connected_client_count, ERROR_NETWORK, g_client_manager, HOST_TO_NET_U32, log_debug, MAX_CLIENTS, mutex_lock, mutex_unlock, server_state_packet_t::reserved, client_info::send_mutex, SET_ERRNO, and client_info::transport.

Variable Documentation

◆ g_server_should_exit

atomic_bool g_server_should_exit
extern

Global shutdown flag from main.c - used to avoid error spam during shutdown.

When the server is shutting down, certain packet processing errors become expected (e.g., buffer allocation failures, queue shutdowns). This flag helps handlers distinguish between genuine errors and shutdown conditions.

Global shutdown flag from main.c - used to avoid error spam during shutdown.

Global shutdown flag from main.c.

This flag is the primary coordination mechanism for clean server shutdown. It's atomic to ensure thread-safe access without mutexes, as it's checked frequently in tight loops across all worker threads.

USAGE PATTERN:

  • Set to true by signal handlers (SIGINT/SIGTERM) or main loop on error
  • Checked by all worker threads to know when to exit gracefully
  • Must be atomic to prevent race conditions during shutdown cascade

Definition at line 135 of file server/main.c.

Referenced by handle_image_frame_packet().