ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches

Server packet processing and protocol implementation. More...

Go to the source code of this file.

Functions

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_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_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_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_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, const void *data, size_t len)
 Handle PING packet - respond with PONG.
 
void handle_pong_packet (client_info_t *client, const void *data, size_t len)
 Handle PONG packet - client acknowledged our PING.
 
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_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_client_leave_packet (client_info_t *client, const void *data, size_t len)
 Process CLIENT_LEAVE packet - handle clean client disconnect.
 
void handle_remote_log_packet_from_client (client_info_t *client, const void *data, size_t len)
 
void disconnect_client_for_bad_data (client_info_t *client, const char *format,...)
 
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.
 

Detailed Description

Server packet processing and protocol implementation.

Definition in file src/server/protocol.h.

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 1954 of file server/protocol.c.

1954 {
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}
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
@ ERROR_INVALID_STATE
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574

References ERROR_INVALID_STATE, log_warn, and SET_ERRNO.

◆ disconnect_client_for_bad_data()

void disconnect_client_for_bad_data ( client_info_t *  client,
const char *  format,
  ... 
)

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

156 {
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}
#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_exchange_bool(atomic_t *a, bool new_value)
Atomically exchange a boolean and return the old value.
Definition atomic.c:303
const crypto_context_t * crypto_handshake_get_context(const crypto_handshake_context_t *ctx)
Get the crypto context for encryption/decryption.
#define BUFFER_SIZE_SMALL
Small buffer size (256 bytes)
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_NETWORK_PROTOCOL
Definition error_codes.h:81
@ ASCIICHAT_OK
Definition error_codes.h:51
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
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
@ REMOTE_LOG_DIRECTION_SERVER_TO_CLIENT
Definition network/log.h:22
void packet_queue_stop(packet_queue_t *queue)
Destroy a packet queue and free all resources.
Definition queue.c:616
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)
int socket_t
action_args_t args
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
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
Transport instance structure.
Definition transport.h:214
crypto_handshake_context_t crypto_handshake_ctx
char client_id[MAX_CLIENT_ID_LEN]
Cryptographic context structure.
#define LOG_ERROR
Definition types.h:42

◆ 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 1206 of file server/protocol.c.

1206 {
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}
bool atomic_load_bool(atomic_t *a)
Atomically load a boolean value.
Definition atomic.c:169
#define NET_TO_HOST_U32(val)
Definition endian.h:81
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:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#define safe_size_mul
Definition common.h:488
unsigned char uint8_t
Definition common.h:56
@ ERROR_MEMORY
Definition error_codes.h:56
#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.
Definition log/log.h:587
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define AUDIO_BATCH_SAMPLES
Total samples in audio batch (8192 samples)
Definition packet.h:216
#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,...)
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
Definition log/log.h:702
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
audio_ring_buffer_t * incoming_audio_buffer

References ASCIICHAT_OK, atomic_load_bool(), 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.

◆ 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 1109 of file server/protocol.c.

1109 {
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}
#define AUDIO_SAMPLES_PER_PACKET
Samples per audio packet (256 samples)
Definition packet.h:235
#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.

◆ 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 1640 of file server/protocol.c.

1640 {
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
1742 client->codec_capabilities_video = NET_TO_HOST_U32(caps->codec_capabilities_video);
1743 client->codec_capabilities_audio = NET_TO_HOST_U32(caps->codec_capabilities_audio);
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,
1758 client->terminal_caps.wants_padding);
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}
#define NET_TO_HOST_U16(val)
Definition endian.h:111
unsigned short uint16_t
Definition common.h:57
#define log_info_client(client, fmt,...)
Server sends INFO log message to client.
Definition network/log.h:69
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
#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
Terminal capabilities packet structure (Packet Type 5)
Definition packet.h:1018
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
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
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
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

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, client_info::codec_capabilities_audio, terminal_capabilities_packet_t::codec_capabilities_audio, client_info::codec_capabilities_video, terminal_capabilities_packet_t::codec_capabilities_video, terminal_capabilities_packet_t::color_count, terminal_capabilities_t::color_count, terminal_capabilities_packet_t::color_filter, terminal_capabilities_t::color_filter, 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(), INVALID_SOCKET_VALUE, log_debug, log_info, log_info_client, log_warn, 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, client_info::socket, 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, terminal_capabilities_packet_t::wants_padding, terminal_capabilities_t::wants_padding, client_info::width, and terminal_capabilities_packet_t::width.

Referenced by add_client().

◆ 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 295 of file server/protocol.c.

295 {
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}
#define MAX_DISPLAY_NAME_LEN
Maximum display name length in characters.
Definition limits.h:20
#define CLIENT_CAP_STRETCH
Client can stretch frames to fill terminal.
Definition packet.h:926
#define CLIENT_CAP_COLOR
Client supports color rendering.
Definition packet.h:925
#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
#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: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
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_packet_t::display_name, client_info::display_name, INVALID_SOCKET_VALUE, log_info, log_info_client, MAX_DISPLAY_NAME_LEN, NET_TO_HOST_U32, SAFE_STRNCPY, client_info::socket, VALIDATE_CAPABILITY_FLAGS, VALIDATE_FLAGS_MASK, VALIDATE_PACKET_SIZE, and client_info::wants_stretch.

◆ 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 438 of file server/protocol.c.

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

References client_info::active, atomic_store_bool(), client_info::client_id, ERROR_INVALID_STATE, log_debug, log_info, log_warn, and SET_ERRNO.

◆ handle_image_frame_h265_packet()

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

Process H.265-encoded image frame from client.

Handles incoming H.265/HEVC-encoded video frames from clients. Frames are decoded to ASCII art and added to the compositing pipeline.

FLOW:

  1. Validate packet structure and minimum size
  2. Auto-enable video stream on first frame
  3. Decode H.265 packet to ASCII grid
  4. Convert ASCII to frame buffer format for rendering
  5. Store in client's incoming_video_buffer
Parameters
clientClient connection info
dataH.265 packet payload (includes header with frame dimensions)
lenPayload size in bytes

PACKET FORMAT: [flags: u8][width: u16][height: u16][x265_data...]

  • flags: H265_FLAG_KEYFRAME, H265_FLAG_SIZE_CHANGE
  • width/height: Frame dimensions in characters
  • x265_data: H.265-encoded frame data

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

951 {
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}
@ VIDEO_CODEC_H265
H.265 (HEVC) video codec.
Definition codecs.h:30
atomic_t g_should_exit
Global application exit flag (shared across all modes)
Definition globals.c:34
#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
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 log_info_every(interval_us, fmt,...)
Rate-limited INFO logging.
Definition log/log.h:705
h265_server_context_t *volatile g_h265_server
Global H.265 codec server context.
int frame
Definition splash.c:99
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)
struct h265_server_client h265_server_client_t
video_frame_buffer_t * incoming_video_buffer
Video frame structure.

References ASCIICHAT_OK, atomic_exchange_bool(), atomic_load_bool(), client_info::client_id, client_info::codec_capabilities_video, disconnect_client_for_bad_data(), frame, client_info::frames_received, g_h265_server, g_should_exit, h265_server_decode_and_convert(), h265_server_get_client_decoder(), client_info::incoming_video_buffer, INVALID_SOCKET_VALUE, client_info::is_sending_video, log_debug, log_error, log_info, log_info_client, log_info_every, log_warn, SAFE_FREE, SAFE_MALLOC, client_info::socket, time_get_realtime_ns(), VIDEO_CODEC_H265, VIDEO_CODEC_SUPPORTED, video_frame_begin_write(), and video_frame_commit().

◆ 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 749 of file server/protocol.c.

749 {
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}
bool atomic_cas_bool(atomic_t *a, bool *expected, bool new_value)
Atomically compare-and-swap a boolean.
Definition atomic.c:184
@ VIDEO_CODEC_RGBA
Raw RGBA (4 bytes per pixel, uncompressed)
Definition codecs.h:29
#define HOST_TO_NET_U32(val)
Definition endian.h:66
#define MAX_FRAME_BUFFER_SIZE
Maximum frame buffer size including headers and compression.
unsigned long long uint64_t
Definition common.h:59
#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.
asciichat_error_t frame_check_size_overflow(size_t header_size, size_t data_size)
Check for overflow when adding header to data.
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 FRAME_HEADER_SIZE_LEGACY
Legacy frame header size (width:4 + height:4)
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
asciichat_error_t image_validate_dimensions(size_t width, size_t height)
Validate image dimensions (non-zero, within limits)
Definition video.c:12

References ASCIICHAT_OK, atomic_cas_bool(), atomic_load_bool(), client_info::client_id, client_info::client_state_mutex, client_info::codec_capabilities_video, disconnect_client_for_bad_data(), ERROR_INVALID_STATE, format_bytes_pretty(), frame, frame_check_size_overflow(), FRAME_HEADER_SIZE_LEGACY, frame_validate_legacy(), client_info::frames_received, client_info::frames_received_logged, g_should_exit, HOST_TO_NET_U32, image_calc_rgb_size(), image_validate_buffer_size(), image_validate_dimensions(), client_info::incoming_video_buffer, INVALID_SOCKET_VALUE, client_info::is_sending_video, client_info::last_received_frame_hash, log_debug, log_error, log_info, log_info_client, log_warn, MAX_FRAME_BUFFER_SIZE, mutex_lock, mutex_unlock, NET_TO_HOST_U32, NS_PER_SEC_INT, SAFE_FREE, SET_ERRNO, client_info::socket, VIDEO_CODEC_RGBA, VIDEO_CODEC_SUPPORTED, video_frame_begin_write(), and video_frame_commit().

◆ handle_ping_packet()

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

Handle PING packet - respond with PONG.

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

660 {
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}
@ ERROR_NETWORK
Definition error_codes.h:77
asciichat_error_t acip_send_pong(acip_transport_t *transport)
Send pong packet.
Definition send.c:280

References acip_send_pong(), ASCIICHAT_OK, atomic_load_bool(), client_info::client_id, ERROR_NETWORK, mutex_lock, mutex_unlock, client_info::send_mutex, SET_ERRNO, client_info::shutting_down, and client_info::transport.

◆ handle_pong_packet()

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

Handle PONG packet - client acknowledged our PING.

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

683 {
684 (void)client;
685 (void)data;
686 (void)len;
687 // No action needed - client acknowledged our PING
688}

◆ 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 362 of file server/protocol.c.

362 {
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}
#define ACIP_CRYPTO_HAS_ENCRYPT(mode)
Check if crypto mode includes payload encryption.
Definition packet.h:1092
#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: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

References ACIP_CRYPTO_HAS_ENCRYPT, 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.

◆ 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 1125 of file server/protocol.c.

1125 {
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}
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.
log_level_t
Logging levels enumeration.
Definition types.h:29
@ REMOTE_LOG_DIRECTION_UNKNOWN
Definition network/log.h:21
@ REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER
Definition network/log.h:23
#define REMOTE_LOG_FLAG_TRUNCATED
Remote log packet flag definitions.
Definition packet.h:736
#define MAX_REMOTE_LOG_MESSAGE_LENGTH
Maximum remote log message length (512 bytes)
Definition packet.h:134
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
#define LOG_INFO
Definition types.h:40

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.

◆ 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 1810 of file server/protocol.c.

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

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 524 of file server/protocol.c.

524 {
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}
opus_codec_t * opus_codec_create_decoder(int sample_rate)
Create an Opus decoder.
Definition opus.c:74
#define STREAM_TYPE_VIDEO
Video stream.
Definition packet.h:937
#define STREAM_TYPE_AUDIO
Audio stream.
Definition packet.h:938

References atomic_store_bool(), client_info::client_id, HOST_TO_NET_U32, INVALID_SOCKET_VALUE, client_info::is_sending_audio, client_info::is_sending_video, log_debug, log_error, log_info, log_info_client, NET_TO_HOST_U32, opus_codec_create_decoder(), client_info::opus_decoder, client_info::socket, STREAM_TYPE_AUDIO, STREAM_TYPE_VIDEO, VALIDATE_CAPABILITY_FLAGS, VALIDATE_FLAGS_MASK, and VALIDATE_PACKET_SIZE.

◆ 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 619 of file server/protocol.c.

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

References atomic_store_bool(), client_info::client_id, INVALID_SOCKET_VALUE, client_info::is_sending_audio, client_info::is_sending_video, log_info, log_info_client, NET_TO_HOST_U32, client_info::socket, STREAM_TYPE_AUDIO, STREAM_TYPE_VIDEO, VALIDATE_CAPABILITY_FLAGS, VALIDATE_FLAGS_MASK, and VALIDATE_PACKET_SIZE.

◆ 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 1884 of file server/protocol.c.

1884 {
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}
#define MAX_CLIENTS
Maximum possible clients (static array size) - actual runtime limit set by –max-clients (1-32)
Definition limits.h:26
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.
client_info_t clients[MAX_CLIENTS]
Array of client_info_t structures (backing storage)
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

References acip_send_server_state(), client_info::active, server_state_packet_t::active_client_count, ASCIICHAT_OK, atomic_load_bool(), 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.

Referenced by add_webrtc_client().