ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
protocol.h File Reference

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_h264_packet (client_info_t *client, const void *data, size_t len)
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)
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.
asciichat_error_t handle_runtime_settings (client_info_t *client, const void *data, size_t length)

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

1821 {
1822 SET_ERRNO(ERROR_INVALID_STATE, "Unexpected console clear broadcast");
1823 log_warn("CLEAR_CONSOLE is now sent from render threads, not broadcast");
1824}
#define SET_ERRNO(code,...)
@ ERROR_INVALID_STATE
#define log_warn(...)
Log a WARN message.
Definition log/log.h:621

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

167 {
168 if (!client) {
169 return;
170 }
171
172 protocol_cleanup_thread_locals();
173
174 // Atomically set the disconnect flag and get the old value
175 // This ensures only one thread proceeds to disconnect (the first one)
176 bool already_requested = atomic_exchange_bool(&client->protocol_disconnect_requested, true);
177 if (already_requested) {
178 return; // Already disconnecting, don't do it again
179 }
180
181 char reason[BUFFER_SIZE_SMALL] = {0};
182 if (format) {
183 va_list args;
184 va_start(args, format);
185 safe_vsnprintf(reason, sizeof(reason), format, args);
186 va_end(args);
187 } else {
188 SAFE_STRNCPY(reason, "Protocol violation", sizeof(reason));
189 }
190
191 const char *reason_str = reason[0] != '\0' ? reason : "Protocol violation";
192 const char *client_id = client->client_id;
193
194 log_warn("Disconnecting client %s due to protocol violation: %s", client_id, reason_str);
195 mutex_lock(&client->send_mutex);
196 if (client->transport)
198 mutex_unlock(&client->send_mutex);
199
200 log_debug("Setting active=false (client_id=%s, reason=%s)", client_id, reason_str);
201 atomic_store_bool(&client->active, false);
202 atomic_store_bool(&client->shutting_down, true);
204 atomic_store_bool(&client->video_render_thread_running, false);
205 atomic_store_bool(&client->audio_render_thread_running, false);
206}
action_args_t args
Definition actions.c:78
#define BUFFER_SIZE_SMALL
Small buffer size (256 bytes).
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:423
@ ERROR_NETWORK_PROTOCOL
Definition error_codes.h:76
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
int safe_vsnprintf(char *buffer, size_t buffer_size, const char *format, va_list ap)
Platform-safe vsnprintf wrapper.
void network_connection_request_stop(network_connection_t *c)
asciichat_error_t acip_send_error(acip_transport_t *transport, uint32_t error_code, const char *message)
Send error message packet.
Definition send.c:415
network_connection_t * network_connection
char client_id[MAX_CLIENT_ID_LEN]

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

1175 {
1176 // Log every audio batch packet reception
1177 log_debug_every(LOG_RATE_DEFAULT, "Received audio batch packet from client %s (len=%zu, is_sending_audio=%d)",
1178 client->client_id, len, atomic_load_bool(&client->is_sending_audio));
1179
1180 VALIDATE_NOTNULL_DATA(client, data, "AUDIO_BATCH");
1181 VALIDATE_MIN_SIZE(client, len, sizeof(audio_batch_packet_t), "AUDIO_BATCH");
1182 VALIDATE_AUDIO_STREAM_ENABLED(client, "AUDIO_BATCH");
1183
1184 // Parse batch header using utility function
1185 audio_batch_info_t batch_info;
1186 asciichat_error_t parse_result = audio_parse_batch_header(data, len, &batch_info);
1187 if (parse_result != ASCIICHAT_OK) {
1188 disconnect_client_for_bad_data(client, "Failed to parse audio batch header");
1189 return;
1190 }
1191
1192 uint32_t packet_batch_count = batch_info.batch_count;
1193 uint32_t total_samples = batch_info.total_samples;
1194 uint32_t sample_rate = batch_info.sample_rate;
1195 if (sample_rate != AUDIO_SAMPLE_RATE || (batch_info.channels != 1 && batch_info.channels != 2) ||
1196 total_samples % batch_info.channels) {
1197 disconnect_client_for_bad_data(client, "Unsupported PCM audio format");
1198 return;
1199 }
1200
1201 (void)packet_batch_count;
1202 (void)sample_rate;
1203
1204 VALIDATE_NONZERO(client, packet_batch_count, "batch_count", "AUDIO_BATCH");
1205 VALIDATE_NONZERO(client, total_samples, "total_samples", "AUDIO_BATCH");
1206
1207 size_t samples_bytes = 0;
1208 if (safe_size_mul(total_samples, sizeof(uint32_t), &samples_bytes)) {
1209 disconnect_client_for_bad_data(client, "AUDIO_BATCH sample size overflow (samples=%u)", total_samples);
1210 return;
1211 }
1212
1213 size_t expected_size = sizeof(audio_batch_packet_t) + samples_bytes;
1214 if (len != expected_size) {
1215 disconnect_client_for_bad_data(client, "AUDIO_BATCH length mismatch: got %zu expected %zu", len, expected_size);
1216 return;
1217 }
1218
1219 // Bounds check to prevent integer overflow on allocation
1220 // Maximum allowed samples: AUDIO_BATCH_SAMPLES * 2 (2048 samples)
1221 // This prevents total_samples * sizeof(float) from exceeding 8KB
1222 const uint32_t MAX_AUDIO_SAMPLES = AUDIO_BATCH_SAMPLES * 2;
1223 if (total_samples > MAX_AUDIO_SAMPLES) {
1224 disconnect_client_for_bad_data(client, "AUDIO_BATCH too many samples: %u (max: %u)", total_samples,
1225 MAX_AUDIO_SAMPLES);
1226 return;
1227 }
1228
1229 const uint8_t *samples_ptr = (const uint8_t *)data + sizeof(audio_batch_packet_t);
1230
1231 // Safe allocation: total_samples is bounded above, so multiplication won't overflow
1232 size_t alloc_size = (size_t)total_samples * AUDIO_CHANNELS * sizeof(float);
1233 float *samples = SAFE_MALLOC(alloc_size, float *);
1234 if (!samples) {
1235 SET_ERRNO(ERROR_MEMORY, "Failed to allocate memory for audio sample conversion");
1236 return;
1237 }
1238
1239 // Use helper function to dequantize samples
1240 asciichat_error_t dq_result = audio_dequantize_samples(samples_ptr, total_samples, samples);
1241 if (dq_result != ASCIICHAT_OK) {
1242 SAFE_FREE(samples);
1243 return;
1244 }
1245
1246#ifndef NDEBUG
1247 static int recv_count = 0;
1248 recv_count++;
1249 if (recv_count % 100 == 0) {
1250 uint32_t raw0 = bytes_read_u32_unaligned(samples_ptr + 0 * sizeof(uint32_t));
1251 uint32_t raw1 = bytes_read_u32_unaligned(samples_ptr + 1 * sizeof(uint32_t));
1252 uint32_t raw2 = bytes_read_u32_unaligned(samples_ptr + 2 * sizeof(uint32_t));
1253 int32_t scaled0 = (int32_t)NET_TO_HOST_U32(raw0);
1254 int32_t scaled1 = (int32_t)NET_TO_HOST_U32(raw1);
1255 int32_t scaled2 = (int32_t)NET_TO_HOST_U32(raw2);
1256 log_debug_every(US_PER_SEC_INT, "RECV: network[0]=0x%08x, network[1]=0x%08x, network[2]=0x%08x", raw0, raw1, raw2);
1257 log_debug_every(US_PER_SEC_INT, "RECV: scaled[0]=%d, scaled[1]=%d, scaled[2]=%d", scaled0, scaled1, scaled2);
1258 log_debug_every(US_PER_SEC_INT, "RECV: samples[0]=%.6f, samples[1]=%.6f, samples[2]=%.6f", samples[0], samples[1],
1259 samples[2]);
1260 }
1261#endif
1262
1263 if (client->incoming_audio_buffer) {
1264 uint32_t frames = total_samples / batch_info.channels;
1265 (void)audio_convert_channels(samples, frames, batch_info.channels, samples, AUDIO_CHANNELS);
1266 asciichat_error_t write_result =
1268 if (write_result != ASCIICHAT_OK) {
1269 log_error("Failed to write decoded audio batch to buffer: %s", asciichat_error_format(NULL, write_result));
1270 }
1271 }
1272
1273 SAFE_FREE(samples);
1274}
const char * asciichat_error_format(const char *message, asciichat_error_t code)
asciichat_error_t audio_convert_channels(const float *input, size_t frames, unsigned input_channels, float *output, unsigned output_channels)
#define AUDIO_SAMPLE_RATE
Definition audio/limits.h:4
#define AUDIO_CHANNELS
Definition audio/limits.h:5
#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:62
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_MALLOC(size, cast)
Definition common.h:265
#define safe_size_mul
Definition common.h:503
unsigned char uint8_t
Definition common.h:60
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ERROR_MEMORY
Definition error_codes.h:51
@ ASCIICHAT_OK
Definition error_codes.h:46
#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:634
#define US_PER_SEC_INT
Definition time.h:173
#define AUDIO_BATCH_SAMPLES
Total samples in audio batch (8192 samples).
Definition packet.h:231
#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:749
Parsed audio batch packet header information.
uint32_t batch_count
Number of audio frames in this batch.
uint32_t channels
Number of channels (1=mono, 2=stereo).
uint32_t sample_rate
Sample rate in Hz (e.g., 48000).
uint32_t total_samples
Total number of samples across all frames.
The metadata prefix for a batch of encoded audio samples. batch_count, total_samples,...
Definition packet.h:960
audio_ring_buffer_t * incoming_audio_buffer

References asciichat_error_format(), ASCIICHAT_OK, AUDIO_BATCH_SAMPLES, AUDIO_CHANNELS, audio_convert_channels(), audio_dequantize_samples(), audio_parse_batch_header(), audio_ring_buffer_write(), AUDIO_SAMPLE_RATE, audio_batch_info_t::batch_count, audio_batch_info_t::channels, 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_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, US_PER_SEC_INT, VALIDATE_AUDIO_STREAM_ENABLED, VALIDATE_MIN_SIZE, VALIDATE_NONZERO, and VALIDATE_NOTNULL_DATA.

◆ handle_audio_opus_batch_packet()

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.

Handles batched Opus-encoded audio frames sent by the client. This provides ~98% bandwidth reduction compared to raw PCM audio while maintaining excellent audio quality.

PACKET STRUCTURE EXPECTED:

  • opus_batch_header_t (16 bytes):
    • sample_rate (4 bytes)
    • frame_duration (4 bytes)
    • frame_count (4 bytes)
    • reserved (4 bytes)
  • Opus encoded data (variable size, typically ~60 bytes/frame @ 24kbps)

PROCESSING FLOW:

  1. Parse batch header
  2. Decode each Opus frame back to PCM samples (OPUS_FRAME_SAMPLES samples/frame @ 48kHz)
  3. Write decoded samples to client's incoming audio buffer

PERFORMANCE:

  • Bandwidth: ~60 bytes/frame vs ~3840 bytes/frame for raw PCM (64:1 compression)
  • Quality: Excellent at 24 kbps VOIP mode
  • Latency: 20ms frames for real-time audio
Parameters
clientClient that sent the audio packet
dataPacket payload data
lenPacket payload length
See also
av_receive_audio_opus_batch() For packet parsing
opus_codec_decode() For Opus decoding
handle_audio_batch_packet() For raw PCM Audio batch handling

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

1311 {
1312 log_debug_every(LOG_RATE_SLOW, "Received Opus audio batch from client %s (len=%zu)", client->client_id, len);
1313
1314 VALIDATE_NOTNULL_DATA(client, data, "AUDIO_OPUS_BATCH");
1315 VALIDATE_AUDIO_STREAM_ENABLED(client, "AUDIO_OPUS_BATCH");
1316 VALIDATE_RESOURCE_INITIALIZED(client, client->opus_decoder, "Opus decoder");
1317
1318 // Parse Opus batch packet
1319 const uint8_t *opus_data = NULL;
1320 size_t opus_size = 0;
1321 const uint16_t *frame_sizes = NULL;
1322 int sample_rate = 0;
1323 int frame_duration = 0;
1324 int frame_count = 0;
1325
1326 asciichat_error_t result = packet_parse_opus_batch(data, len, &opus_data, &opus_size, &frame_sizes, &sample_rate,
1327 &frame_duration, &frame_count);
1328
1329 if (result != ASCIICHAT_OK) {
1330 disconnect_client_for_bad_data(client, "Failed to parse AUDIO_OPUS_BATCH packet");
1331 return;
1332 }
1333
1334 VALIDATE_NONZERO(client, frame_count, "frame_count", "AUDIO_OPUS_BATCH");
1335 VALIDATE_NONZERO(client, opus_size, "opus_size", "AUDIO_OPUS_BATCH");
1336
1337 // Calculate samples per frame (20ms @ 48kHz = OPUS_FRAME_SAMPLES samples)
1338 int samples_per_frame = (sample_rate * frame_duration) / 1000 * AUDIO_CHANNELS;
1339 VALIDATE_RANGE(client, samples_per_frame, 1, AUDIO_OPUS_MAX_DECODE_SAMPLES * AUDIO_CHANNELS, "samples_per_frame",
1340 "AUDIO_OPUS_BATCH");
1341
1342 // Use static buffer for common case to avoid malloc in hot path
1343 // Typical batches: 1-32 frames of OPUS_FRAME_SAMPLES samples = up to 30,720 samples
1344 // Static buffer holds 32 frames @ 48kHz 20ms = 30,720 samples (120KB)
1345#define OPUS_DECODE_STATIC_MAX_SAMPLES (32 * OPUS_FRAME_SAMPLES * AUDIO_CHANNELS)
1346 // Each client receive thread decodes independently. Sharing this scratch
1347 // buffer lets concurrent clients overwrite one another's PCM before it is
1348 // written to their source rings.
1349 static _Thread_local float static_decode_buffer[OPUS_DECODE_STATIC_MAX_SAMPLES];
1350
1351 size_t total_samples = (size_t)samples_per_frame * (size_t)frame_count;
1352 float *decoded_samples;
1353 bool used_malloc = false;
1354
1355 if (total_samples <= OPUS_DECODE_STATIC_MAX_SAMPLES) {
1356 decoded_samples = static_decode_buffer;
1357 } else {
1358 // Unusual large batch - fall back to malloc
1359 log_warn("Client %s: Large audio batch requires malloc (%zu samples)", client->client_id, total_samples);
1360 decoded_samples = SAFE_MALLOC(total_samples * sizeof(float), float *);
1361 if (!decoded_samples) {
1362 SET_ERRNO(ERROR_MEMORY, "Failed to allocate buffer for Opus decoded samples");
1363 return;
1364 }
1365 used_malloc = true;
1366 }
1367
1368 // Decode each Opus frame using frame_sizes array
1369 int total_decoded = 0;
1370 size_t opus_offset = 0;
1371
1372 for (int i = 0; i < frame_count; i++) {
1373 // Get exact frame size from frame_sizes array (convert from network byte order)
1374 size_t frame_size = (size_t)NET_TO_HOST_U16(frame_sizes[i]);
1375
1376 // DEBUG: Log the actual bytes of each Opus frame
1377 if (frame_size > 0) {
1378 log_debug_every(LOG_RATE_DEFAULT, "Client %s: Opus frame %d: size=%zu, first_bytes=[0x%02x,0x%02x,0x%02x,0x%02x]",
1379 client->client_id, i, frame_size, opus_data[opus_offset] & 0xFF,
1380 frame_size > 1 ? (opus_data[opus_offset + 1] & 0xFF) : 0,
1381 frame_size > 2 ? (opus_data[opus_offset + 2] & 0xFF) : 0,
1382 frame_size > 3 ? (opus_data[opus_offset + 3] & 0xFF) : 0);
1383 }
1384
1385 if (opus_offset + frame_size > opus_size) {
1386 log_error("Client %s: Frame %d size overflow (offset=%zu, frame_size=%zu, total=%zu)", client->client_id, i + 1,
1387 opus_offset, frame_size, opus_size);
1388 if (used_malloc) {
1389 SAFE_FREE(decoded_samples);
1390 }
1391 return;
1392 }
1393
1394 // SECURITY: Bounds check before writing decoded samples to prevent buffer overflow
1395 // An attacker could send malicious Opus frames that decode to more samples than expected
1396 if ((size_t)total_decoded + (size_t)samples_per_frame > total_samples) {
1397 log_error("Client %s: Opus decode would overflow buffer (decoded=%d, frame_samples=%d, max=%zu)",
1398 client->client_id, total_decoded, samples_per_frame, total_samples);
1399 if (used_malloc) {
1400 SAFE_FREE(decoded_samples);
1401 }
1402 return;
1403 }
1404
1405 int decoded_count = opus_codec_decode((opus_codec_t *)client->opus_decoder, &opus_data[opus_offset], frame_size,
1406 &decoded_samples[total_decoded], samples_per_frame);
1407
1408 if (decoded_count < 0) {
1409 log_error("Client %s: Opus decoding failed for frame %d/%d (size=%zu)", client->client_id, i + 1, frame_count,
1410 frame_size);
1411 if (used_malloc) {
1412 SAFE_FREE(decoded_samples);
1413 }
1414 return;
1415 }
1416
1417 total_decoded += decoded_count;
1418 opus_offset += frame_size;
1419 }
1420
1421 log_debug_every(LOG_RATE_DEFAULT, "Client %s: Decoded %d Opus frames -> %d samples", client->client_id, frame_count,
1422 total_decoded);
1423
1424 // DEBUG: Log sample values to detect all-zero issue
1425 static _Thread_local int server_decode_count = 0;
1426 server_decode_count++;
1427 if (total_decoded > 0 && (server_decode_count <= 10 || server_decode_count % 100 == 0)) {
1428 float peak = 0.0f, rms = 0.0f;
1429 for (int i = 0; i < total_decoded && i < 100; i++) {
1430 float abs_val = fabsf(decoded_samples[i]);
1431 if (abs_val > peak)
1432 peak = abs_val;
1433 rms += decoded_samples[i] * decoded_samples[i];
1434 }
1435 rms = sqrtf(rms / (total_decoded > 100 ? 100 : total_decoded));
1436 // Log first 4 bytes of Opus data to compare with client encode
1439 "SERVER OPUS DECODE #%d from client %s: decoded_rms=%.6f, opus_first4=[0x%02x,0x%02x,0x%02x,0x%02x]",
1440 server_decode_count, client->client_id, rms, opus_size > 0 ? opus_data[0] : 0, opus_size > 1 ? opus_data[1] : 0,
1441 opus_size > 2 ? opus_data[2] : 0, opus_size > 3 ? opus_data[3] : 0);
1442 }
1443
1444 // Write decoded samples to client's incoming audio buffer
1445 // Note: audio_ring_buffer_write returns error code, not sample count
1446 // Buffer overflow warnings are logged inside audio_ring_buffer_write if buffer is full
1447 if (client->incoming_audio_buffer && total_decoded > 0) {
1448 asciichat_error_t result = audio_ring_buffer_write(client->incoming_audio_buffer, decoded_samples, total_decoded);
1449 if (result != ASCIICHAT_OK) {
1450 log_error("Client %s: Failed to write decoded audio to buffer: %s", client->client_id,
1451 asciichat_error_format(NULL, result));
1452 }
1453 }
1454
1455 if (used_malloc) {
1456 SAFE_FREE(decoded_samples);
1457 }
1458}
#define AUDIO_OPUS_MAX_DECODE_SAMPLES
#define NET_TO_HOST_U16(val)
Definition endian.h:111
int opus_codec_decode(opus_codec_t *codec, const uint8_t *data, size_t data_len, float *out_samples, int out_num_samples)
Decode Opus audio frame.
Definition opus.c:176
unsigned short uint16_t
Definition common.h:61
#define LOG_RATE_SLOW
Log rate limit: 10 seconds (10,000,000 microseconds).
Definition log_rates.h:35
asciichat_error_t packet_parse_opus_batch(const void *packet_data, size_t packet_len, const uint8_t **out_opus_data, size_t *out_opus_size, const uint16_t **out_frame_sizes, int *out_sample_rate, int *out_frame_duration, int *out_frame_count)
Parse Opus audio batch packet header and extract frame data.
#define VALIDATE_RESOURCE_INITIALIZED(client, resource, resource_name)
#define VALIDATE_RANGE(client, value, min_val, max_val, value_name, packet_name)
#define OPUS_DECODE_STATIC_MAX_SAMPLES
State for one Opus encoding or decoding direction.
Definition opus.h:105

References asciichat_error_format(), ASCIICHAT_OK, AUDIO_CHANNELS, AUDIO_OPUS_MAX_DECODE_SAMPLES, audio_ring_buffer_write(), client_info::client_id, disconnect_client_for_bad_data(), ERROR_MEMORY, client_info::incoming_audio_buffer, log_debug_every, log_error, LOG_RATE_DEFAULT, LOG_RATE_SLOW, log_warn, NET_TO_HOST_U16, opus_codec_decode(), OPUS_DECODE_STATIC_MAX_SAMPLES, client_info::opus_decoder, packet_parse_opus_batch(), SAFE_FREE, SAFE_MALLOC, SET_ERRNO, US_PER_SEC_INT, VALIDATE_AUDIO_STREAM_ENABLED, VALIDATE_NONZERO, VALIDATE_NOTNULL_DATA, VALIDATE_RANGE, and VALIDATE_RESOURCE_INITIALIZED.

◆ handle_audio_opus_packet()

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.

Handles single Opus-encoded audio frames sent by clients. The packet format includes a 16-byte header followed by the Opus-encoded data.

PACKET STRUCTURE:

  • Offset 0: sample_rate (uint32_t, native byte order - bug in client)
  • Offset 4: frame_duration (uint32_t, native byte order - bug in client)
  • Offset 8: reserved (8 bytes)
  • Offset 16: Opus-encoded audio data
Parameters
clientClient info structure
dataPacket payload
lenPacket length

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

1478 {
1479 log_debug_every(LOG_RATE_DEFAULT, "Received Opus audio from client %s (len=%zu)", client->client_id, len);
1480
1481 if (VALIDATE_PACKET_NOT_NULL(client, data, "AUDIO_OPUS")) {
1482 return;
1483 }
1484
1485 // Minimum size: 16-byte header + at least 1 byte of Opus data
1486 if (len < 17) {
1487 disconnect_client_for_bad_data(client, "AUDIO_OPUS packet too small: %zu bytes", len);
1488 return;
1489 }
1490
1491 if (!atomic_load_bool(&client->is_sending_audio)) {
1492 disconnect_client_for_bad_data(client, "AUDIO_OPUS received before audio stream enabled");
1493 return;
1494 }
1495
1496 if (!client->opus_decoder) {
1497 disconnect_client_for_bad_data(client, "Opus decoder not initialized");
1498 return;
1499 }
1500
1501 // Parse header (16 bytes) - convert from network byte order
1502 const uint8_t *buf = (const uint8_t *)data;
1503 uint32_t sample_rate_net, frame_duration_net;
1504 memcpy(&sample_rate_net, buf, 4);
1505 memcpy(&frame_duration_net, buf + 4, 4);
1506 uint32_t sample_rate = NET_TO_HOST_U32(sample_rate_net);
1507 uint32_t frame_duration = NET_TO_HOST_U32(frame_duration_net);
1508
1509 // Extract Opus data (after 16-byte header)
1510 const uint8_t *opus_data = buf + 16;
1511 size_t opus_size = len - 16;
1512
1513 // Validate parameters
1514 if (sample_rate == 0 || sample_rate > 192000) {
1515 disconnect_client_for_bad_data(client, "AUDIO_OPUS invalid sample_rate: %u", sample_rate);
1516 return;
1517 }
1518
1519 if (frame_duration == 0 || frame_duration > 120) {
1520 disconnect_client_for_bad_data(client, "AUDIO_OPUS invalid frame_duration: %u ms", frame_duration);
1521 return;
1522 }
1523
1524 // Calculate expected samples per frame
1525 int samples_per_frame = (int)((sample_rate * frame_duration) / 1000) * AUDIO_CHANNELS;
1526 if (samples_per_frame <= 0 ||
1527 samples_per_frame > AUDIO_OPUS_MAX_DECODE_SAMPLES * AUDIO_CHANNELS) { // Max 120ms @ 48kHz
1528 disconnect_client_for_bad_data(client, "AUDIO_OPUS invalid samples_per_frame: %d", samples_per_frame);
1529 return;
1530 }
1531
1532 // Decode Opus frame
1533 float decoded_samples[AUDIO_OPUS_MAX_DECODE_SAMPLES * AUDIO_CHANNELS]; // Max Opus frame size (120ms @ 48kHz)
1534 int decoded_count =
1535 opus_codec_decode((opus_codec_t *)client->opus_decoder, opus_data, opus_size, decoded_samples, samples_per_frame);
1536
1537 if (decoded_count < 0) {
1538 log_error("Client %s: Opus decoding failed (size=%zu)", client->client_id, opus_size);
1539 return;
1540 }
1541
1542 log_debug_every(LOG_RATE_VERY_FAST, "Client %s: Decoded Opus frame -> %d samples", client->client_id, decoded_count);
1543
1544 // Write decoded samples to client's incoming audio buffer
1545 if (client->incoming_audio_buffer && decoded_count > 0) {
1546 asciichat_error_t write_result =
1547 audio_ring_buffer_write(client->incoming_audio_buffer, decoded_samples, decoded_count);
1548 if (write_result != ASCIICHAT_OK) {
1549 log_error("Failed to write decoded Opus samples to buffer: %s", asciichat_error_format(NULL, write_result));
1550 }
1551 }
1552}
#define LOG_RATE_VERY_FAST
Log rate limit: 0.1 seconds (100,000 microseconds) - for very high frequency operations.
Definition log_rates.h:23
#define VALIDATE_PACKET_NOT_NULL(client, data, packet_name)

References asciichat_error_format(), ASCIICHAT_OK, AUDIO_CHANNELS, AUDIO_OPUS_MAX_DECODE_SAMPLES, audio_ring_buffer_write(), client_info::client_id, disconnect_client_for_bad_data(), client_info::incoming_audio_buffer, client_info::is_sending_audio, log_debug_every, log_error, LOG_RATE_DEFAULT, LOG_RATE_VERY_FAST, NET_TO_HOST_U32, opus_codec_decode(), client_info::opus_decoder, and VALIDATE_PACKET_NOT_NULL.

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

1074 {
1075 VALIDATE_NOTNULL_DATA(client, data, "AUDIO");
1076 VALIDATE_AUDIO_ALIGNMENT(client, len, sizeof(float), "AUDIO");
1077 VALIDATE_AUDIO_STREAM_ENABLED(client, "AUDIO");
1078
1079 int num_samples = (int)(len / sizeof(float));
1080 VALIDATE_AUDIO_SAMPLE_COUNT(client, num_samples, AUDIO_SAMPLES_PER_PACKET, "AUDIO");
1081 VALIDATE_RESOURCE_INITIALIZED(client, client->incoming_audio_buffer, "audio buffer");
1082
1083 const float *samples = (const float *)data;
1085 float *stereo = SAFE_MALLOC((size_t)num_samples * AUDIO_CHANNELS * sizeof(float), float *);
1086 if (audio_convert_channels(samples, (size_t)num_samples, 1, stereo, AUDIO_CHANNELS) == ASCIICHAT_OK)
1087 result = audio_ring_buffer_write(client->incoming_audio_buffer, stereo, num_samples * AUDIO_CHANNELS);
1088 SAFE_FREE(stereo);
1089 if (result != ASCIICHAT_OK) {
1090 log_error("Failed to write audio samples to buffer: %s", asciichat_error_format(NULL, result));
1091 }
1092}
#define AUDIO_SAMPLES_PER_PACKET
Samples per audio packet (256 samples).
Definition packet.h:250
#define VALIDATE_AUDIO_SAMPLE_COUNT(client, num_samples, max_samples, packet_name)
#define VALIDATE_AUDIO_ALIGNMENT(client, len, sample_size, packet_name)

References asciichat_error_format(), ASCIICHAT_OK, AUDIO_CHANNELS, audio_convert_channels(), audio_ring_buffer_write(), AUDIO_SAMPLES_PER_PACKET, client_info::incoming_audio_buffer, log_error, SAFE_FREE, SAFE_MALLOC, 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 )

Bootstrap a viewer's rendering state from its initial terminal capabilities. Later advertisements update codecs while SETTINGS_SYNC owns live preferences. Publish under the client mutex, then send the initial effective settings unlocked.

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

1583 {
1584 if (!data || len != sizeof(terminal_capabilities_packet_t)) {
1585 disconnect_client_for_bad_data(client, "Invalid capabilities size");
1586 return;
1587 }
1589 memcpy(&packet, data, sizeof(packet));
1590 mutex_lock(&client->client_state_mutex);
1593 // Encoder changes advertise codec support without overwriting live preferences.
1594 if (client->has_terminal_caps) {
1595 mutex_unlock(&client->client_state_mutex);
1596 return;
1597 }
1600 if (result == ASCIICHAT_OK) {
1601 publish_render_settings(client);
1602 log_info("Client %s rendering configured: %ux%u, color=%u, palette=%u, mode=%u, fps=%u", client->client_id,
1603 client->width, client->height, client->terminal_caps.color_level, client->terminal_caps.palette_type,
1604 client->terminal_caps.render_mode, client->terminal_caps.desired_fps);
1605 }
1606 mutex_unlock(&client->client_state_mutex);
1607 if (result != ASCIICHAT_OK)
1608 disconnect_client_for_bad_data(client, "Invalid render capabilities");
1609 else {
1611 size_t query_length;
1612 acip_settings_header_t header = {.schema = 1, .expected_revision = UINT32_MAX};
1613 if (acip_settings_encode(&header, "", query, sizeof(query), &query_length) == ASCIICHAT_OK)
1614 (void)handle_runtime_settings(client, query, query_length);
1615 }
1616}
#define DEFAULT_MAX_FPS
Default maximum frame rate (frames per second).
#define log_info(...)
Log an INFO message.
Definition log/log.h:608
#define GET_OPTION(field)
Safely get a specific option field (lock-free read).
Definition options.h:1336
asciichat_error_t acip_render_settings_capabilities(acip_render_settings_t *state, const terminal_capabilities_packet_t *packet)
asciichat_error_t acip_settings_encode(const acip_settings_header_t *header, const char *json, uint8_t *buffer, size_t capacity, size_t *length)
asciichat_error_t handle_runtime_settings(client_info_t *client, const void *data, size_t length)
acip settings header keeps the values that move through the subsystem: schema, request,...
terminal_capabilities_t terminal_caps
acip_render_settings_t render_settings
Terminal capabilities packet structure (Packet Type 5).
Definition packet.h:1074
uint32_t codec_capabilities_video
Video codec capabilities bitmask (VIDEO_CODEC_CAP_*).
Definition packet.h:1106
uint32_t codec_capabilities_audio
Audio codec capabilities bitmask (AUDIO_CODEC_CAP_*).
Definition packet.h:1108

References acip_render_settings_capabilities(), acip_settings_encode(), ACIP_SETTINGS_HEADER_SIZE, ASCIICHAT_OK, client_info::client_id, 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, DEFAULT_MAX_FPS, disconnect_client_for_bad_data(), acip_render_settings_t::fps_limit, GET_OPTION, handle_runtime_settings(), client_info::has_terminal_caps, client_info::height, log_info, NET_TO_HOST_U32, client_info::render_settings, client_info::terminal_caps, and client_info::width.

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

250 {
251 VALIDATE_PACKET_SIZE(client, data, len, sizeof(client_info_packet_t), "CLIENT_JOIN");
252
253 const client_info_packet_t *join_info = (const client_info_packet_t *)data;
254
255 // Validate display name is present and not just whitespace
256 if (join_info->display_name[0] == '\0') {
257 disconnect_client_for_bad_data(client, "CLIENT_JOIN display_name cannot be empty");
258 return;
259 }
260
261 uint32_t capabilities = NET_TO_HOST_U32(join_info->capabilities);
262
263 // Validate at least one capability flag is set
265 VALIDATE_CAPABILITY_FLAGS(client, capabilities, VALID_CAP_MASK, "CLIENT_JOIN");
266
267 // Validate no unknown capability bits are set
268 VALIDATE_FLAGS_MASK(client, capabilities, VALID_CAP_MASK, "CLIENT_JOIN");
269
271
272 client->can_send_video = (capabilities & CLIENT_CAP_VIDEO) != 0;
273 client->can_send_audio = (capabilities & CLIENT_CAP_AUDIO) != 0;
274 client->wants_stretch = (capabilities & CLIENT_CAP_STRETCH) != 0;
275
276 log_info("Client %s joined: %s (video=%d, audio=%d, stretch=%d)", client->client_id, client->display_name,
277 client->can_send_video, client->can_send_audio, client->wants_stretch);
278
279 // Notify client of successful join (encrypted channel)
280 if (client->transport != NULL) {
281 log_info_client(client, "Joined as '%s' (video=%s, audio=%s)", client->display_name,
282 client->can_send_video ? "yes" : "no", client->can_send_audio ? "yes" : "no");
283 }
284}
#define MAX_DISPLAY_NAME_LEN
Maximum display name length in characters.
#define log_info_client(client, fmt,...)
Server sends INFO log message to client.
Definition network/log.h:67
#define CLIENT_CAP_STRETCH
Client can stretch frames to fill terminal.
Definition packet.h:982
#define CLIENT_CAP_COLOR
Client supports color rendering.
Definition packet.h:981
#define CLIENT_CAP_AUDIO
Client can send/receive audio.
Definition packet.h:980
#define CLIENT_CAP_VIDEO
Client can send/receive video.
Definition packet.h:979
#define VALIDATE_PACKET_SIZE(client, data, len, expected_size, packet_name)
#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:692
char display_name[32]
User display name (null-terminated, max MAX_DISPLAY_NAME_LEN bytes).
Definition packet.h:696
uint32_t capabilities
Client capabilities bitmask (CLIENT_CAP_VIDEO | CLIENT_CAP_AUDIO | CLIENT_CAP_COLOR | CLIENT_CAP_STRE...
Definition packet.h:699
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, client_info::transport, 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 deactivates the client and requests transport shutdown
  • The normal connection-closed callback performs final removal

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
The transport shutdown request is asynchronous; final removal occurs from the connection-closed callback.

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

397 {
398 if (!client) {
399 return;
400 }
401
402 const char *client_id = client->client_id;
403
404 if (len == 0) {
405 // Empty reason - client disconnecting without explanation
406 log_info("Client %s sent leave notification (no reason)", client_id);
407 } else if (len <= 256) {
408 // Reason provided - extract and log it
409 if (!data) {
410 SET_ERRNO(ERROR_INVALID_STATE, "Client %s sent leave notification with non-zero length but NULL data", client_id);
411 return;
412 }
413
414 char reason[SERVER_PROTOCOL_REASON_BUFFER_SIZE] = {0};
415 memcpy(reason, data, len);
416 reason[len] = '\0';
417
418 // Validate reason is printable (handle potential non-UTF8 gracefully)
419 bool all_printable = true;
420 for (size_t i = 0; i < len; i++) {
421 uint8_t c = (uint8_t)reason[i];
422 if (c < 32 && c != '\t' && c != '\n') {
423 all_printable = false;
424 break;
425 }
426 }
427
428 if (all_printable) {
429 log_info("Client %s sent leave notification: %s", client_id, reason);
430 } else {
431 log_info("Client %s sent leave notification (reason contains non-printable characters)", client_id);
432 }
433 } else {
434 // Oversized reason - shouldn't happen with validation.h checks
435 log_warn("Client %s sent oversized leave reason (%zu bytes, max 256)", client_id, len);
436 }
437
438 // Deactivate client to stop processing packets
439 // Sets client->active = false immediately - triggers client cleanup procedures
440 log_debug("Setting active=false (client_id=%s)", client_id);
441 atomic_store_bool(&client->active, false);
442
443 // Request transport shutdown immediately. This keeps the server's client
444 // accounting from depending on the peer closing its socket after sending
445 // CLIENT_LEAVE. Final resource removal remains serialized in the normal
446 // connection-closed callback.
447 if (client->network_connection) {
449 }
450}
#define SERVER_PROTOCOL_REASON_BUFFER_SIZE

References client_info::active, client_info::client_id, ERROR_INVALID_STATE, log_debug, log_info, log_warn, client_info::network_connection, network_connection_request_stop(), SERVER_PROTOCOL_REASON_BUFFER_SIZE, and SET_ERRNO.

◆ handle_image_frame_h264_packet()

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

Definition at line 1252 of file src/server/client.c.

1252 {
1254 client, &g_acip_server_callbacks);
1255 if (result != ASCIICHAT_OK)
1256 disconnect_client_for_bad_data(client, "Invalid H.264 packet");
1257}
@ PACKET_TYPE_IMAGE_FRAME_H264
Definition packet.h:389
asciichat_error_t acip_handle_server_packet(acip_transport_t *transport, packet_type_t type, const void *payload, size_t payload_len, void *client_ctx, const acip_server_callbacks_t *callbacks)
Handle incoming packet on server side.
Definition handlers.c:799

References acip_handle_server_packet(), ASCIICHAT_OK, disconnect_client_for_bad_data(), PACKET_TYPE_IMAGE_FRAME_H264, and client_info::transport.

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

914 {
915 // Validate codec capability for H.265 frames
917 log_error("Client %s sent H.265 frame but does not support VIDEO_CODEC_H265 (capabilities=0x%x)", client->client_id,
919 disconnect_client_for_bad_data(client, "VIDEO_CODEC_H265 not supported by client (capabilities=0x%x)",
921 return;
922 }
923
924 if (!data || len < 5) {
925 disconnect_client_for_bad_data(client, "H265_FRAME payload too small: %zu bytes (min 5)", len);
926 return;
927 }
928
929 // Auto-enable video stream if not already enabled
930 // Use atomic_exchange to atomically set flag and check old value
931 bool was_sending_video = atomic_exchange_bool(&client->is_sending_video, true);
932 if (!was_sending_video) {
933 log_info("Client %s auto-enabled H.265 video stream (received IMAGE_FRAME_H265)", client->client_id);
934 if (client->transport != NULL) {
935 log_info_client(client, "First H.265 video frame received - streaming active");
936 }
937 }
938
939 uint8_t flags = *(const uint8_t *)data;
940 bool is_keyframe = (flags & 0x01) != 0;
941 bool size_changed = (flags & 0x02) != 0;
942
943 log_debug_every(US_PER_SEC_INT, "H265_FRAME: flags=0x%02x, keyframe=%s, size_change=%s, len=%zu", flags,
944 is_keyframe ? "yes" : "no", size_changed ? "yes" : "no", len);
945
946 if (!client->incoming_video_buffer) {
947 log_error("Client %s has no incoming video buffer for H.265 frames", client->client_id);
948 return;
949 }
950
951 // Get H.265 server context (may be NULL during shutdown)
953 if (!h265_ctx) {
954 if (!atomic_load_bool(&g_should_exit)) {
955 log_error("H.265 codec server context not initialized");
956 }
957 return;
958 }
959
960 // Get or create decoder for this client
961 uint32_t client_id_num = 0;
962 char *end = NULL;
963 unsigned long parsed = strtoul(client->client_id, &end, 10);
964 if (end != client->client_id && *end == '\0') {
965 client_id_num = (uint32_t)parsed;
966 } else {
967 // Fallback to string hash if can't parse as number
968 client_id_num = 0;
969 for (int i = 0; client->client_id[i]; i++) {
970 client_id_num = (uint32_t)((uint64_t)client_id_num * 31 + (unsigned char)client->client_id[i]);
971 }
972 }
973
974 h265_server_client_t *client_decoder = h265_server_get_client_decoder(h265_ctx, client_id_num);
975 if (!client_decoder) {
976 log_error("Failed to get H.265 decoder for client %s", client->client_id);
977 disconnect_client_for_bad_data(client, "Failed to initialize H.265 decoder");
978 return;
979 }
980
981 // Allocate temporary RGBA buffer for decoding
982 // H.265 frames from client are sent as [flags:u8][width:u16][height:u16][h265_data...]
983 // Existing scratch budget for a 512-by-512 RGBA image.
985 if (!rgba_buf) {
986 log_error("Failed to allocate RGBA buffer for H.265 decoding");
987 disconnect_client_for_bad_data(client, "Out of memory for H.265 frame decoding");
988 return;
989 }
990
991 uint16_t frame_width = 0;
992 uint16_t frame_height = 0;
993 size_t rgba_size = SERVER_H265_RGBA_BUFFER_SIZE;
994
995 // Decode H.265 packet to RGBA
996 asciichat_error_t decode_result = h265_server_decode_and_convert(client_decoder, (const uint8_t *)data, len, rgba_buf,
997 &frame_width, &frame_height, &rgba_size);
998
999 if (decode_result != ASCIICHAT_OK) {
1000 log_error("H.265 decoding failed for client %s: %s", client->client_id,
1001 asciichat_error_format(NULL, decode_result));
1002 SAFE_FREE(rgba_buf);
1003 disconnect_client_for_bad_data(client, "H.265 frame decode error");
1004 return;
1005 }
1006
1007 // Write decoded frame to video buffer
1009 if (frame && frame->data) {
1010 memcpy(frame->data, rgba_buf, rgba_size);
1011 frame->size = rgba_size;
1012 frame->width = frame_width;
1013 frame->height = frame_height;
1014 frame->is_keyframe = is_keyframe;
1015
1016 // Update frame metadata
1017 frame->receive_timestamp_ns = time_get_realtime_ns();
1018 frame->sequence_number = client->frames_received++;
1019
1020 log_debug_every(US_PER_SEC_INT, "RECV_H265 #%lu: client_id=%s size=%zu dims=%ux%u keyframe=%s",
1021 frame->sequence_number, client->client_id, rgba_size, frame_width, frame_height,
1022 is_keyframe ? "yes" : "no");
1023
1025 } else {
1026 log_warn("Failed to get write buffer for client %s (frame=%p, frame->data=%p)", client->client_id, (void *)frame,
1027 frame ? frame->data : NULL);
1028 }
1029
1030 SAFE_FREE(rgba_buf);
1031}
@ 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
unsigned long long uint64_t
Definition common.h:63
#define VIDEO_CODEC_SUPPORTED(cap, codec)
Check if a video codec is supported in capabilities bitmask.
Definition codecs.h:89
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.
h265_server_context_t *volatile g_h265_server
Global H.265 codec server context.
#define SERVER_H265_RGBA_BUFFER_SIZE
struct h265_server_context h265_server_context_t
The useful part of h265 client decoder is in client_id, decoder, last_width, last_height: together th...
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
When code needs to work with simple frame swap, it keeps current_frame, frame_a, frame_b,...
uint64_t sequence_number
Frame sequence number (for drop detection).
uint32_t height
Frame height in pixels.
bool is_keyframe
True if this is a keyframe (important frame).
size_t size
Size of frame data in bytes.
uint64_t receive_timestamp_ns
Timestamp when frame was received (nanoseconds).
uint32_t width
Frame width in pixels.
void * data
Frame data pointer (points to pre-allocated buffer).

References asciichat_error_format(), ASCIICHAT_OK, client_info::client_id, client_info::codec_capabilities_video, video_frame_t::data, disconnect_client_for_bad_data(), client_info::frames_received, g_h265_server, g_should_exit, h265_server_decode_and_convert(), h265_server_get_client_decoder(), video_frame_t::height, client_info::incoming_video_buffer, video_frame_t::is_keyframe, client_info::is_sending_video, log_debug_every, log_error, log_info, log_info_client, log_warn, video_frame_t::receive_timestamp_ns, SAFE_FREE, SAFE_MALLOC, video_frame_t::sequence_number, SERVER_H265_RGBA_BUFFER_SIZE, video_frame_t::size, client_info::transport, US_PER_SEC_INT, VIDEO_CODEC_H265, VIDEO_CODEC_SUPPORTED, video_frame_begin_write(), video_frame_commit(), and video_frame_t::width.

◆ 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:

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 Owned Buffers storage implementation
create_mixed_ascii_frame_for_client() For frame consumption

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

711 {
712 // Handle incoming image data from client
713 // New format: [width:4][height:4][compressed_flag:4][data_size:4][rgb_data:data_size]
714 // Old format: [width:4][height:4][rgb_data:w*h*3] (for backward compatibility)
715 // Use atomic compare-and-swap to avoid race condition - ensures thread-safe auto-enabling of video stream
716
717 log_debug_every(US_PER_SEC_INT, "RECV_IMAGE_FRAME: client_id=%s, len=%zu", client->client_id, len);
718
719 // Validate codec capability for RGBA frames
721 log_error("Client %s sent RGBA frame but does not support VIDEO_CODEC_RGBA (capabilities=0x%x)", client->client_id,
723 disconnect_client_for_bad_data(client, "VIDEO_CODEC_RGBA not supported by client (capabilities=0x%x)",
725 return;
726 }
727
728 if (!data || len < sizeof(uint32_t) * 2) {
729 disconnect_client_for_bad_data(client, "IMAGE_FRAME payload too small: %zu bytes", len);
730 return;
731 }
732 bool was_sending_video = atomic_load_bool(&client->is_sending_video);
733 if (!was_sending_video) {
734 // Try to atomically enable video sending
735 // Use atomic_compare_exchange_strong to avoid spurious failures
736 if (atomic_cas_bool(&client->is_sending_video, &was_sending_video, true)) {
737 log_info("Client %s auto-enabled video stream (received IMAGE_FRAME)", client->client_id);
738 // Notify client that their first video frame was received
739 if (client->transport != NULL) {
740 log_info_client(client, "First video frame received - streaming active");
741 }
742 }
743 } else {
744 // Log periodically to confirm we're receiving frames
745 // Use per-client counter protected by client_state_mutex to avoid race conditions
746 mutex_lock(&client->client_state_mutex);
747 client->frames_received_logged++;
748 if (client->frames_received_logged % 25000 == 0) {
750 format_bytes_pretty(len, pretty, sizeof(pretty));
751 log_debug("Client %s has sent %u IMAGE_FRAME packets (%s)", client->client_id, client->frames_received_logged,
752 pretty);
753 }
754 mutex_unlock(&client->client_state_mutex);
755 }
756
757 // Parse image dimensions (use memcpy to avoid unaligned access)
758 uint32_t img_width_net, img_height_net;
759 memcpy(&img_width_net, data, sizeof(uint32_t));
760 memcpy(&img_height_net, (char *)data + sizeof(uint32_t), sizeof(uint32_t));
761 uint32_t img_width = NET_TO_HOST_U32(img_width_net);
762 uint32_t img_height = NET_TO_HOST_U32(img_height_net);
763
764 log_debug_every(US_PER_SEC_INT, "IMAGE_FRAME packet: width=%u, height=%u, payload_len=%zu", img_width, img_height,
765 len);
766
767 // Validate dimensions using image utility functions
768 if (image_validate_dimensions((size_t)img_width, (size_t)img_height) != ASCIICHAT_OK) {
769 log_error("IMAGE_FRAME validation failed for dimensions: %u x %u", img_width, img_height);
770 disconnect_client_for_bad_data(client, "IMAGE_FRAME invalid dimensions");
771 return;
772 }
773
774 // Calculate RGB buffer size with overflow checking
775 size_t rgb_size = 0;
776 if (image_calc_rgb_size((size_t)img_width, (size_t)img_height, &rgb_size) != ASCIICHAT_OK) {
777 disconnect_client_for_bad_data(client, "IMAGE_FRAME buffer size calculation failed");
778 return;
779 }
780
781 // Validate final buffer size against maximum
782 if (image_validate_buffer_size(rgb_size) != ASCIICHAT_OK) {
783 disconnect_client_for_bad_data(client, "IMAGE_FRAME buffer size exceeds maximum");
784 return;
785 }
786
787 // Only support legacy format: [width:4][height:4][rgb_data:w*h*3]
788 if (rgb_size > SIZE_MAX - FRAME_HEADER_SIZE_LEGACY) {
790 format_bytes_pretty(rgb_size, size_str, sizeof(size_str));
791 disconnect_client_for_bad_data(client, "IMAGE_FRAME legacy packet size overflow: %s", size_str);
792 return;
793 }
794 size_t expected_size = FRAME_HEADER_SIZE_LEGACY + rgb_size;
795
796 if (len != expected_size) {
797 disconnect_client_for_bad_data(client, "IMAGE_FRAME size mismatch: expected %zu bytes got %zu", expected_size, len);
798 return;
799 }
800
801 // Validate legacy format
802 asciichat_error_t validate_result = frame_validate_legacy(len, rgb_size);
803 if (validate_result != ASCIICHAT_OK) {
804 disconnect_client_for_bad_data(client, "IMAGE_FRAME legacy validation failed");
805 return;
806 }
807
808 void *rgb_data = (char *)data + FRAME_HEADER_SIZE_LEGACY;
809 size_t rgb_data_size = rgb_size;
810 bool needs_free = false;
811
812 if (client->incoming_video_buffer) {
813 // Get the write buffer
815
816 if (frame && frame->data) {
817 // Build the packet in the old format for internal storage: [width:4][height:4][rgb_data:w*h*3]
818 // Use frame_check_size_overflow to validate overflow before repacking
820 if (overflow_check != ASCIICHAT_OK) {
821 if (needs_free && rgb_data) {
822 SAFE_FREE(rgb_data);
823 }
824 disconnect_client_for_bad_data(client, "IMAGE_FRAME size overflow while repacking");
825 return;
826 }
827 size_t old_packet_size = FRAME_HEADER_SIZE_LEGACY + rgb_data_size;
828
829 if (old_packet_size <= MAX_FRAME_BUFFER_SIZE) { // Max frame buffer size
830 uint32_t width_net = HOST_TO_NET_U32(img_width);
831 uint32_t height_net = HOST_TO_NET_U32(img_height);
832
833 // Pack in old format for internal consistency
834 memcpy(frame->data, &width_net, sizeof(uint32_t));
835 memcpy((char *)frame->data + sizeof(uint32_t), &height_net, sizeof(uint32_t));
836 memcpy((char *)frame->data + sizeof(uint32_t) * 2, rgb_data, rgb_data_size);
837
838 frame->size = old_packet_size;
839 frame->width = img_width;
840 frame->height = img_height;
841 frame->capture_timestamp_ns = (uint64_t)time(NULL) * NS_PER_SEC_INT;
842 frame->sequence_number = ++client->frames_received;
843
844 // DEBUG: Compute hash of incoming RGB data to detect duplicates
845 uint32_t incoming_rgb_hash = 0;
846 for (size_t i = 0; i < rgb_data_size && i < 1000; i++) {
847 incoming_rgb_hash = (uint32_t)((uint64_t)incoming_rgb_hash * 31 + ((unsigned char *)rgb_data)[i]);
848 }
849
850 // Per-client hash tracking (not static!) to avoid cross-client interference
851 const char *client_id = client->client_id;
852 bool is_new_frame = (incoming_rgb_hash != client->last_received_frame_hash);
853
854 if (is_new_frame) {
855 log_debug_every(US_PER_SEC_INT, "RECV_FRAME #%u NEW: Client %s size=%zu dims=%ux%u hash=0x%08x (prev=0x%08x)",
856 client->frames_received, client_id, rgb_data_size, img_width, img_height, incoming_rgb_hash,
858 client->last_received_frame_hash = incoming_rgb_hash;
859 } else {
860 log_debug_every(US_PER_SEC_INT, "RECV_FRAME #%u DUP: Client %s size=%zu dims=%ux%u hash=0x%08x",
861 client->frames_received, client_id, rgb_data_size, img_width, img_height, incoming_rgb_hash);
862 }
863
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 %s (frame=%p, frame->data=%p)", client->client_id, (void *)frame,
874 frame ? frame->data : NULL);
875 }
876 } else {
877 // During shutdown, this is expected - don't spam error logs
878 if (!atomic_load_bool(&g_should_exit)) {
879 SET_ERRNO(ERROR_INVALID_STATE, "Client %s has no incoming video buffer!", client->client_id);
880 } else {
881 log_debug("Client %s: ignoring video packet during shutdown", 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}
@ 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.
#define NS_PER_SEC_INT
Definition time.h:169
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:11
#define FRAME_HEADER_SIZE_LEGACY
Legacy frame header size (width:4 + height:4).
#define SERVER_PROTOCOL_SIZE_BUFFER_SIZE
#define SERVER_PROTOCOL_PRETTY_BUFFER_SIZE
uint64_t capture_timestamp_ns
Timestamp when frame was captured (nanoseconds).
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_ns, client_info::client_id, client_info::client_state_mutex, client_info::codec_capabilities_video, video_frame_t::data, disconnect_client_for_bad_data(), ERROR_INVALID_STATE, format_bytes_pretty(), frame_check_size_overflow(), FRAME_HEADER_SIZE_LEGACY, frame_validate_legacy(), client_info::frames_received, client_info::frames_received_logged, g_should_exit, video_frame_t::height, HOST_TO_NET_U32, image_calc_rgb_size(), image_validate_buffer_size(), image_validate_dimensions(), client_info::incoming_video_buffer, client_info::is_sending_video, client_info::last_received_frame_hash, log_debug, log_debug_every, log_error, log_info, log_info_client, log_warn, MAX_FRAME_BUFFER_SIZE, NET_TO_HOST_U32, NS_PER_SEC_INT, SAFE_FREE, video_frame_t::sequence_number, SERVER_PROTOCOL_PRETTY_BUFFER_SIZE, SERVER_PROTOCOL_SIZE_BUFFER_SIZE, SET_ERRNO, video_frame_t::size, client_info::transport, US_PER_SEC_INT, VIDEO_CODEC_RGBA, VIDEO_CODEC_SUPPORTED, video_frame_begin_write(), video_frame_commit(), and video_frame_t::width.

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

622 {
623 (void)data;
624 (void)len;
625
626 // Serialize the complete PONG write with the sender. TCP packets cannot be
627 // sent concurrently without risking byte-stream interleaving.
628 mutex_lock(&client->send_mutex);
629 if (atomic_load_bool(&client->shutting_down) || !client->transport) {
630 mutex_unlock(&client->send_mutex);
631 return;
632 }
633 acip_transport_t *pong_transport = client->transport;
634 asciichat_error_t pong_result = acip_send_pong(pong_transport);
635 mutex_unlock(&client->send_mutex);
636 if (pong_result != ASCIICHAT_OK) {
637 SET_ERRNO(ERROR_NETWORK, "Failed to send PONG response to client %s: %s", client->client_id,
638 asciichat_error_string(pong_result));
639 }
640}
@ ERROR_NETWORK
Definition error_codes.h:72
struct acip_transport acip_transport_t
Forward declaration of ACIP transport.
Definition host.h:506
asciichat_error_t acip_send_pong(acip_transport_t *transport)
Send pong packet.
Definition send.c:395

References acip_send_pong(), ASCIICHAT_OK, client_info::client_id, ERROR_NETWORK, 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 645 of file server/protocol.c.

645 {
646 (void)client;
647 (void)data;
648 (void)len;
649 // No action needed - client acknowledged our PING
650}

◆ 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
    • protocol_patch: Patch 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 318 of file server/protocol.c.

318 {
319 if (!data) {
320 disconnect_client_for_bad_data(client, "PROTOCOL_VERSION payload missing");
321 return;
322 }
323
324 if (len != sizeof(protocol_version_packet_t)) {
325 disconnect_client_for_bad_data(client, "PROTOCOL_VERSION invalid size: %zu (expected %zu)", len,
327 return;
328 }
329
330 const protocol_version_packet_t *version = (const protocol_version_packet_t *)data;
331 uint16_t client_major = NET_TO_HOST_U16(version->protocol_version);
332 uint16_t client_minor = NET_TO_HOST_U16(version->protocol_revision);
333 uint16_t client_patch = NET_TO_HOST_U16(version->protocol_patch);
334
335 // Validate major version match (minor version can differ for backward compat)
336 if (client_major != PROTOCOL_VERSION_MAJOR) {
337 log_warn("Client %s protocol version mismatch: client=%u.%u.%u, server=%u.%u.%u", client->client_id, client_major,
339 // Note: We don't disconnect on version mismatch for backward compatibility
340 // Clients may be older or newer than server
341 } else if (client_minor != PROTOCOL_VERSION_MINOR) {
342 log_info("Client %s has different protocol revision: client=%u.%u.%u, server=%u.%u.%u", client->client_id,
343 client_major, client_minor, client_patch, PROTOCOL_VERSION_MAJOR, PROTOCOL_VERSION_MINOR,
345 }
346
347 // Validate reserved bytes are zero
348 for (size_t i = 0; i < sizeof(version->reserved); i++) {
349 if (version->reserved[i] != 0) {
350 log_warn("Client %s sent non-zero reserved bytes in PROTOCOL_VERSION packet", client->client_id);
351 // Don't disconnect - reserved bytes may be used in future versions
352 break;
353 }
354 }
355
356 // Log supported features
358 log_debug("Client %s supports encryption", client->client_id);
359 }
360 if (version->compression_algorithms != 0) {
361 log_debug("Client %s supports compression: 0x%02x", client->client_id, version->compression_algorithms);
362 }
363 if (version->feature_flags != 0) {
364 uint16_t feature_flags = NET_TO_HOST_U16(version->feature_flags);
365 atomic_store_bool(&client->supports_recoverable_errors, (feature_flags & PROTOCOL_FEATURE_RECOVERABLE_ERRORS) != 0);
366 log_debug("Client %s supports features: 0x%04x", client->client_id, feature_flags);
367 }
368}
#define ACIP_CRYPTO_HAS_ENCRYPT(mode)
Check if crypto mode includes payload encryption.
Definition packet.h:1148
#define PROTOCOL_FEATURE_RECOVERABLE_ERRORS
Definition packet.h:844
#define PROTOCOL_VERSION_MAJOR
Major protocol version number.
#define PROTOCOL_VERSION_MINOR
Minor protocol version number.
#define PROTOCOL_VERSION_PATCH
Patch protocol version number.
Protocol version negotiation packet structure (Packet Type 1).
Definition packet.h:860
uint16_t protocol_patch
Patch protocol revision.
Definition packet.h:866
uint16_t protocol_revision
Minor protocol revision (server can be newer).
Definition packet.h:864
uint16_t protocol_version
Major protocol version (must match for compatibility).
Definition packet.h:862
uint8_t reserved[5]
Reserved bytes for future expansion (must be zero).
Definition packet.h:876
uint16_t feature_flags
Feature flags bitmask (FEATURE_RLE_ENCODING, etc.).
Definition packet.h:874
uint8_t compression_algorithms
Supported compression algorithms bitmask (COMPRESS_ALGO_*).
Definition packet.h:870
uint8_t supports_encryption
Encryption & authentication mode bitmask (ACIP_CRYPTO_* constants: NONE, ENCRYPT, AUTH,...
Definition packet.h:868

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_FEATURE_RECOVERABLE_ERRORS, protocol_version_packet_t::protocol_patch, protocol_version_packet_t::protocol_revision, protocol_version_packet_t::protocol_version, PROTOCOL_VERSION_MAJOR, PROTOCOL_VERSION_MINOR, PROTOCOL_VERSION_PATCH, protocol_version_packet_t::reserved, protocol_version_packet_t::supports_encryption, and client_info::supports_recoverable_errors.

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

1094 {
1095 if (!client) {
1096 return;
1097 }
1098
1099 log_level_t remote_level = LOG_INFO;
1101 uint16_t flags = 0;
1102 char message[MAX_REMOTE_LOG_MESSAGE_LENGTH + 1] = {0};
1103
1104 asciichat_error_t parse_result =
1105 packet_parse_remote_log(data, len, &remote_level, &direction, &flags, message, sizeof(message), NULL);
1106 if (parse_result != ASCIICHAT_OK) {
1107 disconnect_client_for_bad_data(client, "Invalid REMOTE_LOG packet: %s", asciichat_error_string(parse_result));
1108 return;
1109 }
1110
1111 if (direction != REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER) {
1112 disconnect_client_for_bad_data(client, "REMOTE_LOG direction mismatch: %u", direction);
1113 return;
1114 }
1115
1116 const bool truncated = (flags & REMOTE_LOG_FLAG_TRUNCATED) != 0;
1117 const char *display_name = client->display_name[0] ? client->display_name : "(unnamed)";
1118 const char *client_id = client->client_id;
1119
1120 if (truncated) {
1121 log_msg(remote_level, __FILE__, __LINE__, __func__, "[REMOTE CLIENT %s \"%s\"] %s [message truncated]", client_id,
1122 display_name, message);
1123 } else {
1124 log_msg(remote_level, __FILE__, __LINE__, __func__, "[REMOTE CLIENT %s \"%s\"] %s", client_id, display_name,
1125 message);
1126 }
1127}
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.c:1196
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 MAX_REMOTE_LOG_MESSAGE_LENGTH
Maximum remote log message length (512 bytes).
Definition packet.h:149
#define REMOTE_LOG_FLAG_TRUNCATED
Remote log packet flag definitions.
Definition packet.h:776
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:881
#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_runtime_settings()

asciichat_error_t handle_runtime_settings ( client_info_t * client,
const void * data,
size_t length )

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

1618 {
1620 size_t reply_length = 0;
1621 mutex_lock(&client->client_state_mutex);
1622 if (!client->has_terminal_caps || atomic_load_bool(&client->shutting_down)) {
1623 mutex_unlock(&client->client_state_mutex);
1624 return SET_ERRNO(ERROR_INVALID_STATE, "Settings require an active bootstrapped viewer");
1625 }
1626 asciichat_error_t result =
1627 acip_settings_request(&client->render_settings, data, length, reply, sizeof(reply), &reply_length);
1628 if (result == ASCIICHAT_OK)
1629 publish_render_settings(client);
1630 mutex_unlock(&client->client_state_mutex);
1631 if (result == ASCIICHAT_OK) {
1632 // Teardown takes this mutex before releasing the transport.
1633 mutex_lock(&client->send_mutex);
1634 if (atomic_load_bool(&client->shutting_down) || !client->transport)
1635 result = SET_ERRNO(ERROR_INVALID_STATE, "Viewer is shutting down");
1636 else
1637 result = packet_send_via_transport(client->transport, PACKET_TYPE_ACIP_SETTINGS_ACK, reply, reply_length, 0);
1638 mutex_unlock(&client->send_mutex);
1639 }
1640 return result;
1641}
@ PACKET_TYPE_ACIP_SETTINGS_ACK
Settings acknowledgment (Participant -> Initiator).
Definition packet.h:487
asciichat_error_t acip_settings_request(acip_render_settings_t *state, const void *payload, size_t length, uint8_t *reply, size_t capacity, size_t *reply_length)
asciichat_error_t packet_send_via_transport(acip_transport_t *transport, packet_type_t type, const void *payload, size_t payload_len, uint32_t client_id)
Definition send.c:190

References ACIP_SETTINGS_MAX_SIZE, acip_settings_request(), ASCIICHAT_OK, client_info::client_state_mutex, ERROR_INVALID_STATE, client_info::has_terminal_caps, packet_send_via_transport(), PACKET_TYPE_ACIP_SETTINGS_ACK, client_info::render_settings, client_info::send_mutex, SET_ERRNO, client_info::shutting_down, and client_info::transport.

Referenced by handle_client_capabilities_packet(), and server_dispatch_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:

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

1677 {
1678 VALIDATE_PACKET_SIZE(client, data, len, sizeof(size_packet_t), "SIZE");
1679
1680 const size_packet_t *size_pkt = (const size_packet_t *)data;
1681
1682 // Extract and validate new dimensions
1683 uint16_t width = NET_TO_HOST_U16(size_pkt->width);
1685
1686 VALIDATE_NONZERO(client, width, "width", "SIZE");
1687 VALIDATE_NONZERO(client, height, "height", "SIZE");
1688 VALIDATE_RANGE(client, width, 1, 4096, "width", "SIZE");
1689 VALIDATE_RANGE(client, height, 1, 4096, "height", "SIZE");
1690
1691 mutex_lock(&client->client_state_mutex);
1692 client->width = width;
1693 client->height = height;
1694 mutex_unlock(&client->client_state_mutex);
1695
1696 log_info("Client %s updated terminal size: %ux%u", client->client_id, width, height);
1697}
unsigned int height
Definition fft.c:23
unsigned int width
Definition fft.c:23
Terminal size update packet.
Definition packet.h:676
uint32_t height
Terminal height in characters.
Definition packet.h:680
uint32_t width
Terminal width in characters.
Definition packet.h:678

References client_info::client_id, client_info::client_state_mutex, client_info::height, height, size_packet_t::height, log_info, NET_TO_HOST_U16, VALIDATE_NONZERO, VALIDATE_PACKET_SIZE, VALIDATE_RANGE, client_info::width, size_packet_t::width, and 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 488 of file server/protocol.c.

488 {
489 VALIDATE_PACKET_SIZE(client, data, len, sizeof(uint32_t), "STREAM_START");
490
491 uint32_t stream_type;
492 memcpy(&stream_type, data, sizeof(uint32_t));
493 stream_type = NET_TO_HOST_U32(stream_type);
494
495 /* Older browser/WASM packet serializers emitted this control payload in
496 * host order while the enclosing ACIP header remained in network order.
497 * Accept that unambiguous two-bit form so a connected browser is not
498 * disconnected before it can send its first frame. */
499 const uint32_t VALID_STREAM_MASK = STREAM_TYPE_VIDEO | STREAM_TYPE_AUDIO;
500 if ((stream_type & VALID_STREAM_MASK) == 0) {
501 uint32_t host_order_stream_type = HOST_TO_NET_U32(stream_type);
502 if ((host_order_stream_type & ~VALID_STREAM_MASK) == 0 && host_order_stream_type != 0) {
503 log_debug("Normalizing legacy host-order STREAM_START flags 0x%x", stream_type);
504 stream_type = host_order_stream_type;
505 }
506 }
507
508 // Validate at least one stream type flag is set
509 VALIDATE_CAPABILITY_FLAGS(client, stream_type, VALID_STREAM_MASK, "STREAM_START");
510
511 // Validate no unknown stream type bits are set
512 VALIDATE_FLAGS_MASK(client, stream_type, VALID_STREAM_MASK, "STREAM_START");
513
514 if (stream_type & STREAM_TYPE_VIDEO) {
515 atomic_store_bool(&client->is_sending_video, true);
516 }
517 if (stream_type & STREAM_TYPE_AUDIO) {
518 atomic_store_bool(&client->is_sending_audio, true);
519
520 // Create Opus decoder for this client if not already created
521 if (!client->opus_decoder) {
523 if (client->opus_decoder) {
524 log_info("Client %s: Opus decoder created (48kHz)", client->client_id);
525 } else {
526 log_error("Client %s: Failed to create Opus decoder", client->client_id);
527 }
528 }
529 }
530
531 if (stream_type & STREAM_TYPE_VIDEO) {
532 log_info("Client %s announced video stream (waiting for first frame)", client->client_id);
533 }
534 if (stream_type & STREAM_TYPE_AUDIO) {
535 log_info("Client %s started audio stream", client->client_id);
536 }
537
538 // Notify client of stream start acknowledgment
539 const char *streams = (stream_type & STREAM_TYPE_VIDEO) && (stream_type & STREAM_TYPE_AUDIO)
540 ? "video+audio"
541 : ((stream_type & STREAM_TYPE_VIDEO) ? "video" : "audio");
542 if (client->transport != NULL) {
543 log_info_client(client, "Stream started: %s", streams);
544 }
545}
opus_codec_t * opus_codec_create_decoder_channels(int sample_rate, int channels)
Definition opus.c:92
#define STREAM_TYPE_VIDEO
Video stream.
Definition packet.h:993
#define STREAM_TYPE_AUDIO
Audio stream.
Definition packet.h:994

References AUDIO_CHANNELS, AUDIO_SAMPLE_RATE, client_info::client_id, HOST_TO_NET_U32, 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_channels(), client_info::opus_decoder, STREAM_TYPE_AUDIO, STREAM_TYPE_VIDEO, client_info::transport, 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 582 of file server/protocol.c.

582 {
583 VALIDATE_PACKET_SIZE(client, data, len, sizeof(uint32_t), "STREAM_STOP");
584
585 uint32_t stream_type;
586 memcpy(&stream_type, data, sizeof(uint32_t));
587 stream_type = NET_TO_HOST_U32(stream_type);
588
589 // Validate at least one stream type flag is set
590 const uint32_t VALID_STREAM_MASK = STREAM_TYPE_VIDEO | STREAM_TYPE_AUDIO;
591 VALIDATE_CAPABILITY_FLAGS(client, stream_type, VALID_STREAM_MASK, "STREAM_STOP");
592
593 // Validate no unknown stream type bits are set
594 VALIDATE_FLAGS_MASK(client, stream_type, VALID_STREAM_MASK, "STREAM_STOP");
595
596 if (stream_type & STREAM_TYPE_VIDEO) {
597 atomic_store_bool(&client->is_sending_video, false);
598 }
599 if (stream_type & STREAM_TYPE_AUDIO) {
600 atomic_store_bool(&client->is_sending_audio, false);
601 }
602
603 if (stream_type & STREAM_TYPE_VIDEO) {
604 log_info("Client %s stopped video stream", client->client_id);
605 }
606 if (stream_type & STREAM_TYPE_AUDIO) {
607 log_info("Client %s stopped audio stream", client->client_id);
608 }
609
610 // Notify client of stream stop acknowledgment
611 const char *streams = (stream_type & STREAM_TYPE_VIDEO) && (stream_type & STREAM_TYPE_AUDIO)
612 ? "video+audio"
613 : ((stream_type & STREAM_TYPE_VIDEO) ? "video" : "audio");
614 if (client->transport != NULL) {
615 log_info_client(client, "Stream stopped: %s", streams);
616 }
617}

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, client_info::transport, 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_push_copy()
  • 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 1751 of file server/protocol.c.

1751 {
1752 if (!client) {
1753 return -1;
1754 }
1755
1756 // Count active clients - LOCK OPTIMIZATION: Use atomic reads, no rwlock needed
1757 int active_count = 0;
1758 for (int i = 0; i < MAX_CLIENTS; i++) {
1759 if (atomic_load_bool(&g_client_manager.clients[i].active)) {
1760 active_count++;
1761 }
1762 }
1763
1764 // Prepare server state packet
1766 state.connected_client_count = active_count;
1767 state.active_client_count = active_count; // For now, all connected are active
1768 memset(state.reserved, 0, sizeof(state.reserved));
1769
1770 // Convert to network byte order
1771 server_state_packet_t net_state;
1772 net_state.connected_client_count = HOST_TO_NET_U32(state.connected_client_count);
1773 net_state.active_client_count = HOST_TO_NET_U32(state.active_client_count);
1774 memset(net_state.reserved, 0, sizeof(net_state.reserved));
1775
1776 // Send server state via ACIP transport
1777 // Protect socket writes with send_mutex to prevent race with send_thread.
1778 mutex_lock(&client->send_mutex);
1779 asciichat_error_t result = acip_send_server_state(client->transport, &net_state);
1780 mutex_unlock(&client->send_mutex);
1781
1782 if (result != ASCIICHAT_OK) {
1783 SET_ERRNO(ERROR_NETWORK, "Failed to send server state to client %s: %s", client->client_id,
1784 asciichat_error_string(result));
1785 return -1;
1786 }
1787
1788 log_debug("Sent server state to client %s: %u connected, %u active", client->client_id, state.connected_client_count,
1789 state.active_client_count);
1790 return 0;
1791}
#define MAX_CLIENTS
Maximum possible clients (static array size) - actual runtime limit set by –max-clients (1-128).
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
Hash table entry for client packet dispatch.
Server state packet structure.
Definition packet.h:746
uint32_t reserved[6]
Reserved fields for future use (must be zero).
Definition packet.h:752
uint32_t active_client_count
Number of clients actively sending video/audio streams.
Definition packet.h:750
uint32_t connected_client_count
Total number of currently connected clients.
Definition packet.h:748

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