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

📡 Network packet processing and protocol implementation More...

Files

file  protocol.c
 ðŸ“¡ Server packet processor: client communication handling, protocol state management, and packet dispatch
 
file  protocol.h
 Server packet processing and protocol implementation.
 

Functions

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.
 

Detailed Description

📡 Network packet processing and protocol implementation

Function Documentation

◆ handle_audio_opus_batch_packet()

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

#include <protocol.c>

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 (960 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 Types parsing
opus_codec_decode() For Opus decoding
handle_audio_batch_packet() For raw PCM Audio batch handling

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

1333 {
1334 log_debug_every(LOG_RATE_SLOW, "Received Opus audio batch from client %u (len=%zu)", client->client_id, len);
1335
1336 VALIDATE_NOTNULL_DATA(client, data, "AUDIO_OPUS_BATCH");
1337 VALIDATE_AUDIO_STREAM_ENABLED(client, "AUDIO_OPUS_BATCH");
1338 VALIDATE_RESOURCE_INITIALIZED(client, client->opus_decoder, "Opus decoder");
1339
1340 // Parse Opus batch packet
1341 const uint8_t *opus_data = NULL;
1342 size_t opus_size = 0;
1343 const uint16_t *frame_sizes = NULL;
1344 int sample_rate = 0;
1345 int frame_duration = 0;
1346 int frame_count = 0;
1347
1348 asciichat_error_t result = packet_parse_opus_batch(data, len, &opus_data, &opus_size, &frame_sizes, &sample_rate,
1349 &frame_duration, &frame_count);
1350
1351 if (result != ASCIICHAT_OK) {
1352 disconnect_client_for_bad_data(client, "Failed to parse AUDIO_OPUS_BATCH packet");
1353 return;
1354 }
1355
1356 VALIDATE_NONZERO(client, frame_count, "frame_count", "AUDIO_OPUS_BATCH");
1357 VALIDATE_NONZERO(client, opus_size, "opus_size", "AUDIO_OPUS_BATCH");
1358
1359 // Calculate samples per frame (20ms @ 48kHz = 960 samples)
1360 int samples_per_frame = (sample_rate * frame_duration) / 1000;
1361 VALIDATE_RANGE(client, samples_per_frame, 1, 4096, "samples_per_frame", "AUDIO_OPUS_BATCH");
1362
1363 // Use static buffer for common case to avoid malloc in hot path
1364 // Typical batches: 1-32 frames of 960 samples = up to 30,720 samples
1365 // Static buffer holds 32 frames @ 48kHz 20ms = 30,720 samples (120KB)
1366#define OPUS_DECODE_STATIC_MAX_SAMPLES (32 * 960)
1367 // Each client receive thread decodes independently. Sharing this scratch
1368 // buffer lets concurrent clients overwrite one another's PCM before it is
1369 // written to their source rings.
1370 static _Thread_local float static_decode_buffer[OPUS_DECODE_STATIC_MAX_SAMPLES];
1371
1372 size_t total_samples = (size_t)samples_per_frame * (size_t)frame_count;
1373 float *decoded_samples;
1374 bool used_malloc = false;
1375
1376 if (total_samples <= OPUS_DECODE_STATIC_MAX_SAMPLES) {
1377 decoded_samples = static_decode_buffer;
1378 } else {
1379 // Unusual large batch - fall back to malloc
1380 log_warn("Client %u: Large audio batch requires malloc (%zu samples)", client->client_id, total_samples);
1381 decoded_samples = SAFE_MALLOC(total_samples * sizeof(float), float *);
1382 if (!decoded_samples) {
1383 SET_ERRNO(ERROR_MEMORY, "Failed to allocate buffer for Opus decoded samples");
1384 return;
1385 }
1386 used_malloc = true;
1387 }
1388
1389 // Decode each Opus frame using frame_sizes array
1390 int total_decoded = 0;
1391 size_t opus_offset = 0;
1392
1393 for (int i = 0; i < frame_count; i++) {
1394 // Get exact frame size from frame_sizes array (convert from network byte order)
1395 size_t frame_size = (size_t)NET_TO_HOST_U16(frame_sizes[i]);
1396
1397 // DEBUG: Log the actual bytes of each Opus frame
1398 if (frame_size > 0) {
1399 log_debug_every(LOG_RATE_DEFAULT, "Client %u: Opus frame %d: size=%zu, first_bytes=[0x%02x,0x%02x,0x%02x,0x%02x]",
1400 client->client_id, i, frame_size, opus_data[opus_offset] & 0xFF,
1401 frame_size > 1 ? (opus_data[opus_offset + 1] & 0xFF) : 0,
1402 frame_size > 2 ? (opus_data[opus_offset + 2] & 0xFF) : 0,
1403 frame_size > 3 ? (opus_data[opus_offset + 3] & 0xFF) : 0);
1404 }
1405
1406 if (opus_offset + frame_size > opus_size) {
1407 log_error("Client %u: Frame %d size overflow (offset=%zu, frame_size=%zu, total=%zu)", client->client_id, i + 1,
1408 opus_offset, frame_size, opus_size);
1409 if (used_malloc) {
1410 SAFE_FREE(decoded_samples);
1411 }
1412 return;
1413 }
1414
1415 // SECURITY: Bounds check before writing decoded samples to prevent buffer overflow
1416 // An attacker could send malicious Opus frames that decode to more samples than expected
1417 if ((size_t)total_decoded + (size_t)samples_per_frame > total_samples) {
1418 log_error("Client %u: Opus decode would overflow buffer (decoded=%d, frame_samples=%d, max=%zu)",
1419 client->client_id, total_decoded, samples_per_frame, total_samples);
1420 if (used_malloc) {
1421 SAFE_FREE(decoded_samples);
1422 }
1423 return;
1424 }
1425
1426 int decoded_count = opus_codec_decode((opus_codec_t *)client->opus_decoder, &opus_data[opus_offset], frame_size,
1427 &decoded_samples[total_decoded], samples_per_frame);
1428
1429 if (decoded_count < 0) {
1430 log_error("Client %u: Opus decoding failed for frame %d/%d (size=%zu)", client->client_id, i + 1, frame_count,
1431 frame_size);
1432 if (used_malloc) {
1433 SAFE_FREE(decoded_samples);
1434 }
1435 return;
1436 }
1437
1438 total_decoded += decoded_count;
1439 opus_offset += frame_size;
1440 }
1441
1442 log_debug_every(LOG_RATE_DEFAULT, "Client %u: Decoded %d Opus frames -> %d samples", client->client_id, frame_count,
1443 total_decoded);
1444
1445 // DEBUG: Log sample values to detect all-zero issue
1446 static _Thread_local int server_decode_count = 0;
1447 server_decode_count++;
1448 if (total_decoded > 0 && (server_decode_count <= 10 || server_decode_count % 100 == 0)) {
1449 float peak = 0.0f, rms = 0.0f;
1450 for (int i = 0; i < total_decoded && i < 100; i++) {
1451 float abs_val = fabsf(decoded_samples[i]);
1452 if (abs_val > peak)
1453 peak = abs_val;
1454 rms += decoded_samples[i] * decoded_samples[i];
1455 }
1456 rms = sqrtf(rms / (total_decoded > 100 ? 100 : total_decoded));
1457 // Log first 4 bytes of Opus data to compare with client encode
1458 log_info("SERVER OPUS DECODE #%d from client %u: decoded_rms=%.6f, opus_first4=[0x%02x,0x%02x,0x%02x,0x%02x]",
1459 server_decode_count, client->client_id, rms, opus_size > 0 ? opus_data[0] : 0,
1460 opus_size > 1 ? opus_data[1] : 0, opus_size > 2 ? opus_data[2] : 0, opus_size > 3 ? opus_data[3] : 0);
1461 }
1462
1463 // Write decoded samples to client's incoming audio buffer
1464 // Note: audio_ring_buffer_write returns error code, not sample count
1465 // Buffer overflow warnings are logged inside audio_ring_buffer_write if buffer is full
1466 if (client->incoming_audio_buffer && total_decoded > 0) {
1467 asciichat_error_t result = audio_ring_buffer_write(client->incoming_audio_buffer, decoded_samples, total_decoded);
1468 if (result != ASCIICHAT_OK) {
1469 log_error("Client %u: Failed to write decoded audio to buffer: %d", client->client_id, result);
1470 }
1471 }
1472
1473 if (used_malloc) {
1474 SAFE_FREE(decoded_samples);
1475 }
1476}
#define NET_TO_HOST_U16(val)
Definition endian.h:111
asciichat_error_t audio_ring_buffer_write(audio_ring_buffer_t *rb, const float *data, int samples)
Write audio samples to ring buffer.
int opus_codec_decode(opus_codec_t *codec, const uint8_t *data, size_t data_len, float *out_samples, int out_num_samples)
Decode Opus audio frame.
Definition opus.c:143
unsigned short uint16_t
Definition common.h:57
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
unsigned char uint8_t
Definition common.h:56
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
#define LOG_RATE_SLOW
Log rate limit: 10 seconds (10,000,000 microseconds)
Definition log_rates.h:35
#define LOG_RATE_DEFAULT
Log rate limit: 5 seconds (5,000,000 microseconds) - default for audio/video packets.
Definition log_rates.h:32
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
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_AUDIO_STREAM_ENABLED(client, packet_name)
#define VALIDATE_NONZERO(client, value, value_name, packet_name)
#define VALIDATE_NOTNULL_DATA(client, data, packet_name)
#define VALIDATE_RANGE(client, value, min_val, max_val, value_name, packet_name)
void disconnect_client_for_bad_data(client_info_t *client, const char *format,...)
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
Definition log/log.h:702
#define OPUS_DECODE_STATIC_MAX_SAMPLES
audio_ring_buffer_t * incoming_audio_buffer
char client_id[MAX_CLIENT_ID_LEN]
Opus codec context for encoding or decoding.
Definition opus.h:95

References ASCIICHAT_OK, 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_info, 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, 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 
)

#include <protocol.c>

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

1496 {
1497 log_debug_every(LOG_RATE_DEFAULT, "Received Opus audio from client %u (len=%zu)", client->client_id, len);
1498
1499 if (VALIDATE_PACKET_NOT_NULL(client, data, "AUDIO_OPUS")) {
1500 return;
1501 }
1502
1503 // Minimum size: 16-byte header + at least 1 byte of Opus data
1504 if (len < 17) {
1505 disconnect_client_for_bad_data(client, "AUDIO_OPUS packet too small: %zu bytes", len);
1506 return;
1507 }
1508
1509 if (!atomic_load_bool(&client->is_sending_audio)) {
1510 disconnect_client_for_bad_data(client, "AUDIO_OPUS received before audio stream enabled");
1511 return;
1512 }
1513
1514 if (!client->opus_decoder) {
1515 disconnect_client_for_bad_data(client, "Opus decoder not initialized");
1516 return;
1517 }
1518
1519 // Parse header (16 bytes) - convert from network byte order
1520 const uint8_t *buf = (const uint8_t *)data;
1521 uint32_t sample_rate_net, frame_duration_net;
1522 memcpy(&sample_rate_net, buf, 4);
1523 memcpy(&frame_duration_net, buf + 4, 4);
1524 uint32_t sample_rate = NET_TO_HOST_U32(sample_rate_net);
1525 uint32_t frame_duration = NET_TO_HOST_U32(frame_duration_net);
1526
1527 // Extract Opus data (after 16-byte header)
1528 const uint8_t *opus_data = buf + 16;
1529 size_t opus_size = len - 16;
1530
1531 // Validate parameters
1532 if (sample_rate == 0 || sample_rate > 192000) {
1533 disconnect_client_for_bad_data(client, "AUDIO_OPUS invalid sample_rate: %u", sample_rate);
1534 return;
1535 }
1536
1537 if (frame_duration == 0 || frame_duration > 120) {
1538 disconnect_client_for_bad_data(client, "AUDIO_OPUS invalid frame_duration: %u ms", frame_duration);
1539 return;
1540 }
1541
1542 // Calculate expected samples per frame
1543 int samples_per_frame = (int)((sample_rate * frame_duration) / 1000);
1544 if (samples_per_frame <= 0 || samples_per_frame > 5760) { // Max 120ms @ 48kHz
1545 disconnect_client_for_bad_data(client, "AUDIO_OPUS invalid samples_per_frame: %d", samples_per_frame);
1546 return;
1547 }
1548
1549 // Decode Opus frame
1550 float decoded_samples[5760]; // Max Opus frame size (120ms @ 48kHz)
1551 int decoded_count =
1552 opus_codec_decode((opus_codec_t *)client->opus_decoder, opus_data, opus_size, decoded_samples, samples_per_frame);
1553
1554 if (decoded_count < 0) {
1555 log_error("Client %u: Opus decoding failed (size=%zu)", client->client_id, opus_size);
1556 return;
1557 }
1558
1559 log_debug_every(LOG_RATE_VERY_FAST, "Client %u: Decoded Opus frame -> %d samples", client->client_id, decoded_count);
1560
1561 // Write decoded samples to client's incoming audio buffer
1562 if (client->incoming_audio_buffer && decoded_count > 0) {
1563 asciichat_error_t write_result =
1564 audio_ring_buffer_write(client->incoming_audio_buffer, decoded_samples, decoded_count);
1565 if (write_result != ASCIICHAT_OK) {
1566 log_error("Failed to write decoded Opus samples to buffer: %s", asciichat_error_string(write_result));
1567 }
1568 }
1569}
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
unsigned int uint32_t
Definition common.h:58
#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_OK, atomic_load_bool(), 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.