ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
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 1167 of file server/protocol.c.

1167 {
1168 log_debug_every(LOG_RATE_SLOW, "Received Opus audio batch from client %u (len=%zu)", atomic_load(&client->client_id),
1169 len);
1170
1171 VALIDATE_NOTNULL_DATA(client, data, "AUDIO_OPUS_BATCH");
1172 VALIDATE_AUDIO_STREAM_ENABLED(client, "AUDIO_OPUS_BATCH");
1173 VALIDATE_RESOURCE_INITIALIZED(client, client->opus_decoder, "Opus decoder");
1174
1175 // Parse Opus batch packet
1176 const uint8_t *opus_data = NULL;
1177 size_t opus_size = 0;
1178 const uint16_t *frame_sizes = NULL;
1179 int sample_rate = 0;
1180 int frame_duration = 0;
1181 int frame_count = 0;
1182
1183 asciichat_error_t result = packet_parse_opus_batch(data, len, &opus_data, &opus_size, &frame_sizes, &sample_rate,
1184 &frame_duration, &frame_count);
1185
1186 if (result != ASCIICHAT_OK) {
1187 disconnect_client_for_bad_data(client, "Failed to parse AUDIO_OPUS_BATCH packet");
1188 return;
1189 }
1190
1191 VALIDATE_NONZERO(client, frame_count, "frame_count", "AUDIO_OPUS_BATCH");
1192 VALIDATE_NONZERO(client, opus_size, "opus_size", "AUDIO_OPUS_BATCH");
1193
1194 // Calculate samples per frame (20ms @ 48kHz = 960 samples)
1195 int samples_per_frame = (sample_rate * frame_duration) / 1000;
1196 VALIDATE_RANGE(client, samples_per_frame, 1, 4096, "samples_per_frame", "AUDIO_OPUS_BATCH");
1197
1198 // Use static buffer for common case to avoid malloc in hot path
1199 // Typical batches: 1-32 frames of 960 samples = up to 30,720 samples
1200 // Static buffer holds 32 frames @ 48kHz 20ms = 30,720 samples (120KB)
1201#define OPUS_DECODE_STATIC_MAX_SAMPLES (32 * 960)
1202 static float static_decode_buffer[OPUS_DECODE_STATIC_MAX_SAMPLES];
1203
1204 size_t total_samples = (size_t)samples_per_frame * (size_t)frame_count;
1205 float *decoded_samples;
1206 bool used_malloc = false;
1207
1208 if (total_samples <= OPUS_DECODE_STATIC_MAX_SAMPLES) {
1209 decoded_samples = static_decode_buffer;
1210 } else {
1211 // Unusual large batch - fall back to malloc
1212 log_warn("Client %u: Large audio batch requires malloc (%zu samples)", atomic_load(&client->client_id),
1213 total_samples);
1214 decoded_samples = SAFE_MALLOC(total_samples * sizeof(float), float *);
1215 if (!decoded_samples) {
1216 SET_ERRNO(ERROR_MEMORY, "Failed to allocate buffer for Opus decoded samples");
1217 return;
1218 }
1219 used_malloc = true;
1220 }
1221
1222 // Decode each Opus frame using frame_sizes array
1223 int total_decoded = 0;
1224 size_t opus_offset = 0;
1225
1226 for (int i = 0; i < frame_count; i++) {
1227 // Get exact frame size from frame_sizes array (convert from network byte order)
1228 size_t frame_size = (size_t)NET_TO_HOST_U16(frame_sizes[i]);
1229
1230 // DEBUG: Log the actual bytes of each Opus frame
1231 if (frame_size > 0) {
1232 log_debug_every(LOG_RATE_DEFAULT, "Client %u: Opus frame %d: size=%zu, first_bytes=[0x%02x,0x%02x,0x%02x,0x%02x]",
1233 atomic_load(&client->client_id), i, frame_size, opus_data[opus_offset] & 0xFF,
1234 frame_size > 1 ? (opus_data[opus_offset + 1] & 0xFF) : 0,
1235 frame_size > 2 ? (opus_data[opus_offset + 2] & 0xFF) : 0,
1236 frame_size > 3 ? (opus_data[opus_offset + 3] & 0xFF) : 0);
1237 }
1238
1239 if (opus_offset + frame_size > opus_size) {
1240 log_error("Client %u: Frame %d size overflow (offset=%zu, frame_size=%zu, total=%zu)",
1241 atomic_load(&client->client_id), i + 1, opus_offset, frame_size, opus_size);
1242 if (used_malloc) {
1243 SAFE_FREE(decoded_samples);
1244 }
1245 return;
1246 }
1247
1248 // SECURITY: Bounds check before writing decoded samples to prevent buffer overflow
1249 // An attacker could send malicious Opus frames that decode to more samples than expected
1250 if ((size_t)total_decoded + (size_t)samples_per_frame > total_samples) {
1251 log_error("Client %u: Opus decode would overflow buffer (decoded=%d, frame_samples=%d, max=%zu)",
1252 atomic_load(&client->client_id), total_decoded, samples_per_frame, total_samples);
1253 if (used_malloc) {
1254 SAFE_FREE(decoded_samples);
1255 }
1256 return;
1257 }
1258
1259 int decoded_count = opus_codec_decode((opus_codec_t *)client->opus_decoder, &opus_data[opus_offset], frame_size,
1260 &decoded_samples[total_decoded], samples_per_frame);
1261
1262 if (decoded_count < 0) {
1263 log_error("Client %u: Opus decoding failed for frame %d/%d (size=%zu)", atomic_load(&client->client_id), i + 1,
1264 frame_count, frame_size);
1265 if (used_malloc) {
1266 SAFE_FREE(decoded_samples);
1267 }
1268 return;
1269 }
1270
1271 total_decoded += decoded_count;
1272 opus_offset += frame_size;
1273 }
1274
1275 log_debug_every(LOG_RATE_DEFAULT, "Client %u: Decoded %d Opus frames -> %d samples", atomic_load(&client->client_id),
1276 frame_count, total_decoded);
1277
1278 // DEBUG: Log sample values to detect all-zero issue
1279 static int server_decode_count = 0;
1280 server_decode_count++;
1281 if (total_decoded > 0 && (server_decode_count <= 10 || server_decode_count % 100 == 0)) {
1282 float peak = 0.0f, rms = 0.0f;
1283 for (int i = 0; i < total_decoded && i < 100; i++) {
1284 float abs_val = fabsf(decoded_samples[i]);
1285 if (abs_val > peak)
1286 peak = abs_val;
1287 rms += decoded_samples[i] * decoded_samples[i];
1288 }
1289 rms = sqrtf(rms / (total_decoded > 100 ? 100 : total_decoded));
1290 // Log first 4 bytes of Opus data to compare with client encode
1291 log_info("SERVER OPUS DECODE #%d from client %u: decoded_rms=%.6f, opus_first4=[0x%02x,0x%02x,0x%02x,0x%02x]",
1292 server_decode_count, atomic_load(&client->client_id), rms, opus_size > 0 ? opus_data[0] : 0,
1293 opus_size > 1 ? opus_data[1] : 0, opus_size > 2 ? opus_data[2] : 0, opus_size > 3 ? opus_data[3] : 0);
1294 }
1295
1296 // Write decoded samples to client's incoming audio buffer
1297 // Note: audio_ring_buffer_write returns error code, not sample count
1298 // Buffer overflow warnings are logged inside audio_ring_buffer_write if buffer is full
1299 if (client->incoming_audio_buffer && total_decoded > 0) {
1300 asciichat_error_t result = audio_ring_buffer_write(client->incoming_audio_buffer, decoded_samples, total_decoded);
1301 if (result != ASCIICHAT_OK) {
1302 log_error("Client %u: Failed to write decoded audio to buffer: %d", atomic_load(&client->client_id), result);
1303 }
1304 }
1305
1306 if (used_malloc) {
1307 SAFE_FREE(decoded_samples);
1308 }
1309}
asciichat_error_t audio_ring_buffer_write(audio_ring_buffer_t *rb, const float *data, int samples)
int opus_codec_decode(opus_codec_t *codec, const uint8_t *data, size_t data_len, float *out_samples, int out_num_samples)
Definition opus_codec.c:128
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)
#define OPUS_DECODE_STATIC_MAX_SAMPLES
void disconnect_client_for_bad_data(client_info_t *client, const char *format,...)

References audio_ring_buffer_write(), disconnect_client_for_bad_data(), opus_codec_decode(), OPUS_DECODE_STATIC_MAX_SAMPLES, and packet_parse_opus_batch().

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

1329 {
1330 log_debug_every(LOG_RATE_DEFAULT, "Received Opus audio from client %u (len=%zu)", atomic_load(&client->client_id),
1331 len);
1332
1333 if (VALIDATE_PACKET_NOT_NULL(client, data, "AUDIO_OPUS")) {
1334 return;
1335 }
1336
1337 // Minimum size: 16-byte header + at least 1 byte of Opus data
1338 if (len < 17) {
1339 disconnect_client_for_bad_data(client, "AUDIO_OPUS packet too small: %zu bytes", len);
1340 return;
1341 }
1342
1343 if (!atomic_load(&client->is_sending_audio)) {
1344 disconnect_client_for_bad_data(client, "AUDIO_OPUS received before audio stream enabled");
1345 return;
1346 }
1347
1348 if (!client->opus_decoder) {
1349 disconnect_client_for_bad_data(client, "Opus decoder not initialized");
1350 return;
1351 }
1352
1353 // Parse header (16 bytes) - convert from network byte order
1354 const uint8_t *buf = (const uint8_t *)data;
1355 uint32_t sample_rate_net, frame_duration_net;
1356 memcpy(&sample_rate_net, buf, 4);
1357 memcpy(&frame_duration_net, buf + 4, 4);
1358 uint32_t sample_rate = NET_TO_HOST_U32(sample_rate_net);
1359 uint32_t frame_duration = NET_TO_HOST_U32(frame_duration_net);
1360
1361 // Extract Opus data (after 16-byte header)
1362 const uint8_t *opus_data = buf + 16;
1363 size_t opus_size = len - 16;
1364
1365 // Validate parameters
1366 if (sample_rate == 0 || sample_rate > 192000) {
1367 disconnect_client_for_bad_data(client, "AUDIO_OPUS invalid sample_rate: %u", sample_rate);
1368 return;
1369 }
1370
1371 if (frame_duration == 0 || frame_duration > 120) {
1372 disconnect_client_for_bad_data(client, "AUDIO_OPUS invalid frame_duration: %u ms", frame_duration);
1373 return;
1374 }
1375
1376 // Calculate expected samples per frame
1377 int samples_per_frame = (int)((sample_rate * frame_duration) / 1000);
1378 if (samples_per_frame <= 0 || samples_per_frame > 5760) { // Max 120ms @ 48kHz
1379 disconnect_client_for_bad_data(client, "AUDIO_OPUS invalid samples_per_frame: %d", samples_per_frame);
1380 return;
1381 }
1382
1383 // Decode Opus frame
1384 float decoded_samples[5760]; // Max Opus frame size (120ms @ 48kHz)
1385 int decoded_count =
1386 opus_codec_decode((opus_codec_t *)client->opus_decoder, opus_data, opus_size, decoded_samples, samples_per_frame);
1387
1388 if (decoded_count < 0) {
1389 log_error("Client %u: Opus decoding failed (size=%zu)", atomic_load(&client->client_id), opus_size);
1390 return;
1391 }
1392
1393 log_debug_every(LOG_RATE_VERY_FAST, "Client %u: Decoded Opus frame -> %d samples", atomic_load(&client->client_id),
1394 decoded_count);
1395
1396 // Write decoded samples to client's incoming audio buffer
1397 if (client->incoming_audio_buffer && decoded_count > 0) {
1398 asciichat_error_t write_result =
1399 audio_ring_buffer_write(client->incoming_audio_buffer, decoded_samples, decoded_count);
1400 if (write_result != ASCIICHAT_OK) {
1401 log_error("Failed to write decoded Opus samples to buffer: %s", asciichat_error_string(write_result));
1402 }
1403 }
1404}

References audio_ring_buffer_write(), disconnect_client_for_bad_data(), and opus_codec_decode().