ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
handlers.c
Go to the documentation of this file.
1
12
13/* Storage capacities in elements; text buffers include the terminating NUL. */
14#define NETWORK_ACIP_HANDLERS_MSG_BUFFER_SIZE 512
15#define NETWORK_ACIP_HANDLERS_HEX_BUFFER_SIZE 512
16
24#include <ascii-chat/log/log.h>
26#include <ascii-chat/common.h>
28#include <string.h>
29
30// =============================================================================
31// Handler Function Pointer Types
32// =============================================================================
33
34typedef asciichat_error_t (*acip_client_handler_func_t)(const void *payload, size_t payload_len,
35 const acip_client_callbacks_t *callbacks);
36
37typedef asciichat_error_t (*acip_server_handler_func_t)(const void *payload, size_t payload_len, void *client_ctx,
38 const acip_server_callbacks_t *callbacks);
39
40// =============================================================================
41// Packet Type to Handler Index Mapping (O(1) hash table lookup)
42// =============================================================================
43// Open-addressing hash table with linear probing. key=0 means empty slot.
44// Stores handler_idx+1 so that 0 can indicate "not found" after lookup.
45
46#define HANDLER_HASH_SIZE 32 // ~50% load factor for 14-16 entries
47#define CLIENT_HANDLER_COUNT 20 // Added 6 crypto handshake handlers
48#define SERVER_HANDLER_COUNT 21
49
58
59// Hash function: simple modulo
60#define HANDLER_HASH(type) ((type) % HANDLER_HASH_SIZE)
61
62// Lookup function: linear probing with full table scan
63// Must check all HANDLER_HASH_SIZE slots because gaps can exist in the hash table
64static inline int handler_hash_lookup(const handler_hash_entry_t *table, packet_type_t type) {
65 uint32_t h = HANDLER_HASH(type);
66 log_dev_every(4500 * US_PER_MS_INT, "type=%d, hash=%u", type, h);
67 for (int i = 0; i < HANDLER_HASH_SIZE; i++) {
68 uint32_t slot = (h + i) % HANDLER_HASH_SIZE;
69 log_dev_every(4500 * US_PER_MS_INT, " Checking slot %u: key=%d, handler_idx=%d", slot, table[slot].key,
70 table[slot].handler_idx);
71 // Match found - return handler index immediately
72 if (table[slot].key == type) {
73 log_dev_every(4500 * US_PER_MS_INT, " Found match at slot %u, handler_idx=%d", slot, table[slot].handler_idx);
74 return table[slot].handler_idx;
75 }
76 // NOTE: Do NOT return on empty slot! Hash table can have gaps due to collision chains.
77 // Continue probing through entire table to find the key.
78 }
79 log_debug(" No match found after scanning all %u slots", HANDLER_HASH_SIZE);
80 return -1; // Not found after checking all slots
81}
82
83// Client packet type -> handler index hash table
84// Slots computed via linear probing: hash = type % 32
85// clang-format off
86static const handler_hash_entry_t g_client_handler_hash[HANDLER_HASH_SIZE] = {
87 [0] = {PACKET_TYPE_AUDIO_BATCH, 1}, // hash(4000)=0
88 [1] = {PACKET_TYPE_AUDIO_OPUS_BATCH, 2}, // hash(4001)=1
89 [7] = {PACKET_TYPE_CRYPTO_AUTH_CHALLENGE, 15}, // hash(1104)=16, probed->7
90 [8] = {PACKET_TYPE_CRYPTO_SERVER_AUTH_RESP, 16}, // hash(1107)=19, probed->8
91 [9] = {PACKET_TYPE_PING, 6}, // hash(5001)=9
92 [10] = {PACKET_TYPE_PONG, 7}, // hash(5002)=10
93 [11] = {PACKET_TYPE_CRYPTO_AUTH_FAILED, 17}, // hash(1106)=18, probed->11
94 [13] = {PACKET_TYPE_CRYPTO_PARAMETERS, 19}, // hash(1101)=13
95 [14] = {PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE, 18}, // hash(1108)=20, probed->14
96 [15] = {PACKET_TYPE_CLEAR_CONSOLE, 8}, // hash(5007)=15
97 [16] = {PACKET_TYPE_SERVER_STATE, 3}, // hash(5008)=16
98 [17] = {PACKET_TYPE_CRYPTO_REKEY_REQUEST, 9}, // hash(1201)=17
99 [18] = {PACKET_TYPE_CRYPTO_REKEY_RESPONSE, 10}, // hash(1202)=18
100 [19] = {PACKET_TYPE_ERROR_MESSAGE, 4}, // hash(2003)=19
101 [20] = {PACKET_TYPE_REMOTE_LOG, 5}, // hash(2004)=20
102 [21] = {PACKET_TYPE_ACIP_SESSION_JOINED, 13}, // hash(6005)=21
103 [22] = {PACKET_TYPE_CRYPTO_KEY_EXCHANGE_INIT, 14}, // hash(1102)=14, probed->22
104 [24] = {PACKET_TYPE_ASCII_FRAME, 0}, // hash(3000)=24
105 [25] = {PACKET_TYPE_ACIP_WEBRTC_SDP, 11}, // hash(6009)=25
106 [26] = {PACKET_TYPE_ACIP_WEBRTC_ICE, 12}, // hash(6010)=26
107};
108// clang-format on
109
110// Server packet type -> handler index hash table
111// clang-format off
112static const handler_hash_entry_t g_server_handler_hash[HANDLER_HASH_SIZE] = {
113 [0] = {PACKET_TYPE_AUDIO_BATCH, 3}, // hash(4000)=0
114 [1] = {PACKET_TYPE_PROTOCOL_VERSION, 0}, // hash(1)=1
115 [2] = {PACKET_TYPE_AUDIO_OPUS_BATCH, 4}, // hash(4001)=1, probed->2
116 [8] = {PACKET_TYPE_CLIENT_CAPABILITIES, 5}, // hash(5000)=8
117 [9] = {PACKET_TYPE_PING, 6}, // hash(5001)=9
118 [10] = {PACKET_TYPE_PONG, 7}, // hash(5002)=10
119 [11] = {PACKET_TYPE_CLIENT_JOIN, 8}, // hash(5003)=11
120 [12] = {PACKET_TYPE_CLIENT_LEAVE, 9}, // hash(5004)=12
121 [13] = {PACKET_TYPE_STREAM_START, 10}, // hash(5005)=13
122 [14] = {PACKET_TYPE_STREAM_STOP, 11}, // hash(5006)=14
123 [15] = {PACKET_TYPE_CRYPTO_KEY_EXCHANGE_RESP, 17}, // hash(1103)=15
124 [17] = {PACKET_TYPE_CRYPTO_REKEY_REQUEST, 14}, // hash(1201)=17
125 [18] = {PACKET_TYPE_CRYPTO_REKEY_RESPONSE, 15}, // hash(1202)=18
126 [19] = {PACKET_TYPE_ERROR_MESSAGE, 13}, // hash(2003)=19
127 [20] = {PACKET_TYPE_REMOTE_LOG, 12}, // hash(2004)=20
128 [21] = {PACKET_TYPE_CRYPTO_REKEY_COMPLETE, 16}, // hash(1203)=19, probed->21
129 [22] = {PACKET_TYPE_CRYPTO_AUTH_RESPONSE, 18}, // hash(1105)=17, probed->22
130 [23] = {PACKET_TYPE_CRYPTO_NO_ENCRYPTION, 19}, // hash(1109)=21, probed->23
131 [25] = {PACKET_TYPE_IMAGE_FRAME, 1}, // hash(3001)=25
132 [27] = {PACKET_TYPE_IMAGE_FRAME_H264, 20},
133 [26] = {PACKET_TYPE_IMAGE_FRAME_H265, 2}, // hash(3002)=26
134};
135// clang-format on
136
137// =============================================================================
138// Client-Side Packet Handlers
139// =============================================================================
140
141// Forward declarations for client handlers
142static asciichat_error_t handle_client_ascii_frame(const void *payload, size_t payload_len,
143 const acip_client_callbacks_t *callbacks);
144static asciichat_error_t handle_client_audio_batch(const void *payload, size_t payload_len,
145 const acip_client_callbacks_t *callbacks);
146static asciichat_error_t handle_client_audio_opus_batch(const void *payload, size_t payload_len,
147 const acip_client_callbacks_t *callbacks);
148static asciichat_error_t handle_client_server_state(const void *payload, size_t payload_len,
149 const acip_client_callbacks_t *callbacks);
150static asciichat_error_t handle_client_error_message(const void *payload, size_t payload_len,
151 const acip_client_callbacks_t *callbacks);
152static asciichat_error_t handle_client_remote_log(const void *payload, size_t payload_len,
153 const acip_client_callbacks_t *callbacks);
154static asciichat_error_t handle_client_ping(const void *payload, size_t payload_len,
155 const acip_client_callbacks_t *callbacks);
156static asciichat_error_t handle_client_pong(const void *payload, size_t payload_len,
157 const acip_client_callbacks_t *callbacks);
158static asciichat_error_t handle_client_clear_console(const void *payload, size_t payload_len,
159 const acip_client_callbacks_t *callbacks);
160static asciichat_error_t handle_client_webrtc_sdp(const void *payload, size_t payload_len,
161 const acip_client_callbacks_t *callbacks);
162static asciichat_error_t handle_client_webrtc_ice(const void *payload, size_t payload_len,
163 const acip_client_callbacks_t *callbacks);
164static asciichat_error_t handle_client_session_joined(const void *payload, size_t payload_len,
165 const acip_client_callbacks_t *callbacks);
166static asciichat_error_t handle_client_crypto_key_exchange_init(const void *payload, size_t payload_len,
167 const acip_client_callbacks_t *callbacks);
168static asciichat_error_t handle_client_crypto_auth_challenge(const void *payload, size_t payload_len,
169 const acip_client_callbacks_t *callbacks);
170static asciichat_error_t handle_client_crypto_server_auth_resp(const void *payload, size_t payload_len,
171 const acip_client_callbacks_t *callbacks);
172static asciichat_error_t handle_client_crypto_auth_failed(const void *payload, size_t payload_len,
173 const acip_client_callbacks_t *callbacks);
174static asciichat_error_t handle_client_crypto_handshake_complete(const void *payload, size_t payload_len,
175 const acip_client_callbacks_t *callbacks);
176static asciichat_error_t handle_client_crypto_parameters(const void *payload, size_t payload_len,
177 const acip_client_callbacks_t *callbacks);
178
179// Client handler dispatch table (indexed by client_handler_index())
180static const acip_client_handler_func_t g_client_handlers[CLIENT_HANDLER_COUNT] = {
181 handle_client_ascii_frame, // 0
182 handle_client_audio_batch, // 1
183 handle_client_audio_opus_batch, // 2
184 handle_client_server_state, // 3
185 handle_client_error_message, // 4
186 handle_client_remote_log, // 5
187 handle_client_ping, // 6
188 handle_client_pong, // 7
189 handle_client_clear_console, // 8
190 NULL, // 9
191 NULL, // 10
192 handle_client_webrtc_sdp, // 11
193 handle_client_webrtc_ice, // 12
194 handle_client_session_joined, // 13
195 handle_client_crypto_key_exchange_init, // 14
196 handle_client_crypto_auth_challenge, // 15
197 handle_client_crypto_server_auth_resp, // 16
198 handle_client_crypto_auth_failed, // 17
199 handle_client_crypto_handshake_complete, // 18
200 handle_client_crypto_parameters, // 19
201};
202
204 size_t payload_len, const acip_client_callbacks_t *callbacks) {
205 stats_runtime_packet(transport ? transport->stats_peer : NULL, type, payload_len, false, true);
206 if (!transport || !callbacks) {
207 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid transport or callbacks");
208 }
209 (void)transport;
210
211 // ACDS broadcasts participant lifecycle notifications to peers. The generic
212 // client callback set does not expose them, so ignore them while keeping the
213 // signaling receive loop alive for subsequent SDP and ICE packets.
215 return ASCIICHAT_OK;
216 }
217
218 // O(1) dispatch via hash table lookup
219 int idx = handler_hash_lookup(g_client_handler_hash, type);
220 if (idx < 0) {
221 return SET_ERRNO(ERROR_INVALID_PARAM, "Unhandled client packet type: %d", type);
222 }
223
224 return g_client_handlers[idx](payload, payload_len, callbacks);
225}
226
227// =============================================================================
228// Client Handler Implementations (extracted from switch cases)
229// =============================================================================
230
231static asciichat_error_t handle_client_ascii_frame(const void *payload, size_t payload_len,
232 const acip_client_callbacks_t *callbacks) {
233 if (!callbacks->on_ascii_frame) {
234 return ASCIICHAT_OK;
235 }
236
237 if (payload_len < sizeof(ascii_frame_packet_t)) {
238 return SET_ERRNO(ERROR_INVALID_PARAM, "ASCII_FRAME payload too small");
239 }
240
241 // Extract header
243 memcpy(&header, payload, sizeof(header));
244
245 // Convert from network byte order
246 header.width = NET_TO_HOST_U32(header.width);
247 header.height = NET_TO_HOST_U32(header.height);
248 header.original_size = NET_TO_HOST_U32(header.original_size);
249 header.compressed_size = NET_TO_HOST_U32(header.compressed_size);
250 header.checksum = NET_TO_HOST_U32(header.checksum);
251 header.flags = NET_TO_HOST_U32(header.flags);
252
253 // Get frame data (after header)
254 const void *frame_data = (const uint8_t *)payload + sizeof(ascii_frame_packet_t);
255 size_t frame_data_len = payload_len - sizeof(ascii_frame_packet_t);
256
257 // Validate frame dimensions to prevent DoS and buffer overflow attacks
258 if (header.width == 0 || header.height == 0) {
259 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid frame dimensions: %ux%u (width and height must be > 0)",
260 header.width, header.height);
261 }
262
263 // Sanity check: prevent unreasonably large frames (e.g., > 16K resolution)
264 // This protects against resource exhaustion attacks
265 const uint32_t MAX_WIDTH = 16384; // 16K
266 const uint32_t MAX_HEIGHT = 16384; // 16K
267 if (header.width > MAX_WIDTH || header.height > MAX_HEIGHT) {
268 return SET_ERRNO(ERROR_INVALID_PARAM, "Frame dimensions too large: %ux%u (max: %ux%u)", header.width, header.height,
269 MAX_WIDTH, MAX_HEIGHT);
270 }
271
272 log_debug_once("Received first ASCII frame");
273 callbacks->on_ascii_frame(&header, frame_data, frame_data_len, callbacks->app_ctx);
274 return ASCIICHAT_OK;
275}
276
277static asciichat_error_t handle_client_audio_batch(const void *payload, size_t payload_len,
278 const acip_client_callbacks_t *callbacks) {
279 if (!callbacks->on_audio_batch) {
280 return ASCIICHAT_OK;
281 }
282
283 if (payload_len < sizeof(audio_batch_packet_t)) {
284 return SET_ERRNO(ERROR_INVALID_PARAM, "AUDIO_BATCH payload too small");
285 }
286
287 // Parse batch header
288 const audio_batch_packet_t *batch_header = (const audio_batch_packet_t *)payload;
289 uint32_t batch_count = NET_TO_HOST_U32(batch_header->batch_count);
290 uint32_t total_samples = NET_TO_HOST_U32(batch_header->total_samples);
291 uint32_t sample_rate = NET_TO_HOST_U32(batch_header->sample_rate);
292 uint32_t channels = NET_TO_HOST_U32(batch_header->channels);
293
294 // Validate sample rate and channels
295 // Supported sample rates: 8000, 16000, 24000, 32000, 44100, 48000, 96000, 192000 Hz
296 if (sample_rate == 0 || (sample_rate < 8000 || sample_rate > 192000) ||
297 (sample_rate != 8000 && sample_rate != 16000 && sample_rate != 24000 && sample_rate != 32000 &&
298 sample_rate != 44100 && sample_rate != 48000 && sample_rate != 96000 && sample_rate != 192000)) {
299 return SET_ERRNO(
301 "Invalid audio sample rate: %u Hz (expected: 8000, 16000, 24000, 32000, 44100, 48000, 96000, or 192000)",
302 sample_rate);
303 }
304
305 // Validate channel count (1-8 channels supported)
306 if (channels == 0 || channels > 8) {
307 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid audio channel count: %u (expected: 1-8)", channels);
308 }
309
310 // Validate batch count
311 (void)batch_count;
312
313 // Validate size
314 size_t expected_size = sizeof(audio_batch_packet_t) + (total_samples * sizeof(uint32_t));
315 if (payload_len != expected_size) {
316 return SET_ERRNO(ERROR_INVALID_PARAM, "AUDIO_BATCH size mismatch");
317 }
318
319 // Extract quantized samples
320 const uint8_t *samples_ptr = (const uint8_t *)payload + sizeof(audio_batch_packet_t);
321
322 // Dequantize samples to float
323 float *samples = SAFE_MALLOC(total_samples * sizeof(float), float *);
324 if (!samples) {
325 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate audio batch buffer");
326 }
327
328 asciichat_error_t result = audio_dequantize_samples(samples_ptr, total_samples, samples);
329 if (result != ASCIICHAT_OK) {
330 SAFE_FREE(samples);
331 return result;
332 }
333
334 // Copy header for callback
335 audio_batch_packet_t header_copy = *batch_header;
336 header_copy.batch_count = batch_count;
337 header_copy.total_samples = total_samples;
338 header_copy.sample_rate = sample_rate;
339 header_copy.channels = channels;
340
341 callbacks->on_audio_batch(&header_copy, samples, total_samples, callbacks->app_ctx);
342
343 SAFE_FREE(samples);
344 return ASCIICHAT_OK;
345}
346
347static asciichat_error_t handle_client_audio_opus_batch(const void *payload, size_t payload_len,
348 const acip_client_callbacks_t *callbacks) {
349 if (!callbacks->on_audio_opus_batch) {
350 return ASCIICHAT_OK;
351 }
352
353 // Opus batch data (header + sizes + opus frames)
354 callbacks->on_audio_opus_batch(payload, payload_len, callbacks->app_ctx);
355 return ASCIICHAT_OK;
356}
357
358static asciichat_error_t handle_client_server_state(const void *payload, size_t payload_len,
359 const acip_client_callbacks_t *callbacks) {
360 if (!callbacks->on_server_state) {
361 return ASCIICHAT_OK;
362 }
363
364 if (payload_len != sizeof(server_state_packet_t)) {
365 return SET_ERRNO(ERROR_INVALID_PARAM, "SERVER_STATE size mismatch");
366 }
367
368 callbacks->on_server_state((const server_state_packet_t *)payload, callbacks->app_ctx);
369 return ASCIICHAT_OK;
370}
371
372static asciichat_error_t handle_client_error_message(const void *payload, size_t payload_len,
373 const acip_client_callbacks_t *callbacks) {
374 if (!callbacks->on_error) {
375 return ASCIICHAT_OK;
376 }
377
378 if (payload_len < sizeof(error_packet_t)) {
379 return SET_ERRNO(ERROR_INVALID_PARAM, "ERROR_MESSAGE payload too small");
380 }
381
382 const error_packet_t *header = (const error_packet_t *)payload;
383 const char *message = (const char *)payload + sizeof(error_packet_t);
384 size_t msg_len = payload_len - sizeof(error_packet_t);
385
386 // Ensure null-terminated message
387 char msg_buffer[MAX_ERROR_MESSAGE_LENGTH + 1];
388 size_t copy_len = msg_len < MAX_ERROR_MESSAGE_LENGTH ? msg_len : MAX_ERROR_MESSAGE_LENGTH;
389 memcpy(msg_buffer, message, copy_len);
390 msg_buffer[copy_len] = '\0';
391
392 callbacks->on_error(header, msg_buffer, callbacks->app_ctx);
393 return ASCIICHAT_OK;
394}
395
396static asciichat_error_t handle_client_remote_log(const void *payload, size_t payload_len,
397 const acip_client_callbacks_t *callbacks) {
398 if (!callbacks->on_remote_log) {
399 return ASCIICHAT_OK;
400 }
401
402 if (payload_len < sizeof(remote_log_packet_t)) {
403 return SET_ERRNO(ERROR_INVALID_PARAM, "REMOTE_LOG payload too small");
404 }
405
406 const remote_log_packet_t *header = (const remote_log_packet_t *)payload;
407 const char *message = (const char *)payload + sizeof(remote_log_packet_t);
408 size_t msg_len = payload_len - sizeof(remote_log_packet_t);
409
410 // Ensure null-terminated message
412 size_t copy_len = msg_len < sizeof(msg_buffer) - 1 ? msg_len : sizeof(msg_buffer) - 1;
413 memcpy(msg_buffer, message, copy_len);
414 msg_buffer[copy_len] = '\0';
415
416 callbacks->on_remote_log(header, msg_buffer, callbacks->app_ctx);
417 return ASCIICHAT_OK;
418}
419
420static asciichat_error_t handle_client_ping(const void *payload, size_t payload_len,
421 const acip_client_callbacks_t *callbacks) {
422 (void)payload;
423 (void)payload_len;
424
425 if (callbacks->on_ping) {
426 callbacks->on_ping(callbacks->app_ctx);
427 }
428 return ASCIICHAT_OK;
429}
430
431static asciichat_error_t handle_client_pong(const void *payload, size_t payload_len,
432 const acip_client_callbacks_t *callbacks) {
433 (void)payload;
434 (void)payload_len;
435
436 if (callbacks->on_pong) {
437 callbacks->on_pong(callbacks->app_ctx);
438 }
439 return ASCIICHAT_OK;
440}
441
442static asciichat_error_t handle_client_clear_console(const void *payload, size_t payload_len,
443 const acip_client_callbacks_t *callbacks) {
444 (void)payload;
445 (void)payload_len;
446
447 if (callbacks->on_clear_console) {
448 callbacks->on_clear_console(callbacks->app_ctx);
449 }
450 return ASCIICHAT_OK;
451}
452
453static asciichat_error_t handle_client_webrtc_sdp(const void *payload, size_t payload_len,
454 const acip_client_callbacks_t *callbacks) {
455 if (!callbacks->on_webrtc_sdp) {
456 return ASCIICHAT_OK;
457 }
458
459 if (payload_len < sizeof(acip_webrtc_sdp_t)) {
460 return SET_ERRNO(ERROR_INVALID_PARAM, "WEBRTC_SDP payload too small");
461 }
462
463 const acip_webrtc_sdp_t *sdp = (const acip_webrtc_sdp_t *)payload;
464 callbacks->on_webrtc_sdp(sdp, payload_len, callbacks->app_ctx);
465 return ASCIICHAT_OK;
466}
467
468static asciichat_error_t handle_client_webrtc_ice(const void *payload, size_t payload_len,
469 const acip_client_callbacks_t *callbacks) {
470 if (!callbacks->on_webrtc_ice) {
471 return ASCIICHAT_OK;
472 }
473
474 if (payload_len < sizeof(acip_webrtc_ice_t)) {
475 return SET_ERRNO(ERROR_INVALID_PARAM, "WEBRTC_ICE payload too small");
476 }
477
478 const acip_webrtc_ice_t *ice = (const acip_webrtc_ice_t *)payload;
479 callbacks->on_webrtc_ice(ice, payload_len, callbacks->app_ctx);
480 return ASCIICHAT_OK;
481}
482
483static asciichat_error_t handle_client_session_joined(const void *payload, size_t payload_len,
484 const acip_client_callbacks_t *callbacks) {
485 if (!callbacks->on_session_joined) {
486 return ASCIICHAT_OK;
487 }
488
489 if (payload_len < sizeof(acip_session_joined_t)) {
490 return SET_ERRNO(ERROR_INVALID_PARAM, "SESSION_JOINED payload too small (got %zu, need %zu)", payload_len,
491 sizeof(acip_session_joined_t));
492 }
493
494 // Parse the session_joined response
495 const acip_session_joined_t *joined = (const acip_session_joined_t *)payload;
496
497 // Log the result
498 if (joined->success) {
499 log_debug("Session join succeeded: session_id=%.16s, participant_id=%.16s, server=%s:%u, type=%s",
500 (const char *)joined->session_id, (const char *)joined->participant_id, joined->server_address,
501 joined->server_port, joined->session_type == 1 ? "WebRTC" : "DirectTCP");
502 } else {
503 log_warn("Session join failed: error %d: %s", joined->error_code, joined->error_message);
504 }
505
506 // Dispatch to application callback
507 callbacks->on_session_joined(joined, callbacks->app_ctx);
508 return ASCIICHAT_OK;
509}
510
511static asciichat_error_t handle_client_crypto_key_exchange_init(const void *payload, size_t payload_len,
512 const acip_client_callbacks_t *callbacks) {
513 if (!callbacks->on_crypto_key_exchange_init) {
514 return ASCIICHAT_OK;
515 }
516
517 // Dispatch to application callback with packet type
519 callbacks->app_ctx);
520 return ASCIICHAT_OK;
521}
522
523static asciichat_error_t handle_client_crypto_auth_challenge(const void *payload, size_t payload_len,
524 const acip_client_callbacks_t *callbacks) {
525 if (!callbacks->on_crypto_auth_challenge) {
526 return ASCIICHAT_OK;
527 }
528
529 // Dispatch to application callback with packet type
530 callbacks->on_crypto_auth_challenge(PACKET_TYPE_CRYPTO_AUTH_CHALLENGE, payload, payload_len, callbacks->app_ctx);
531 return ASCIICHAT_OK;
532}
533
534static asciichat_error_t handle_client_crypto_server_auth_resp(const void *payload, size_t payload_len,
535 const acip_client_callbacks_t *callbacks) {
536 if (!callbacks->on_crypto_server_auth_resp) {
537 return ASCIICHAT_OK;
538 }
539
540 // Dispatch to application callback with packet type
541 callbacks->on_crypto_server_auth_resp(PACKET_TYPE_CRYPTO_SERVER_AUTH_RESP, payload, payload_len, callbacks->app_ctx);
542 return ASCIICHAT_OK;
543}
544
545static asciichat_error_t handle_client_crypto_auth_failed(const void *payload, size_t payload_len,
546 const acip_client_callbacks_t *callbacks) {
547 if (!callbacks->on_crypto_auth_failed) {
548 return ASCIICHAT_OK;
549 }
550
551 // Dispatch to application callback with packet type
552 callbacks->on_crypto_auth_failed(PACKET_TYPE_CRYPTO_AUTH_FAILED, payload, payload_len, callbacks->app_ctx);
553 return ASCIICHAT_OK;
554}
555
556static asciichat_error_t handle_client_crypto_handshake_complete(const void *payload, size_t payload_len,
557 const acip_client_callbacks_t *callbacks) {
558 if (!callbacks->on_crypto_handshake_complete) {
559 return ASCIICHAT_OK;
560 }
561
562 // Dispatch to application callback with packet type
564 callbacks->app_ctx);
565 return ASCIICHAT_OK;
566}
567
568static asciichat_error_t handle_client_crypto_parameters(const void *payload, size_t payload_len,
569 const acip_client_callbacks_t *callbacks) {
570 if (!callbacks->on_crypto_parameters) {
571 return ASCIICHAT_OK;
572 }
573
574 // Dispatch to application callback with packet type
575 callbacks->on_crypto_parameters(PACKET_TYPE_CRYPTO_PARAMETERS, payload, payload_len, callbacks->app_ctx);
576 return ASCIICHAT_OK;
577}
578
579// =============================================================================
580// Server-Side Packet Handlers
581// =============================================================================
582
583// Forward declarations for server handlers
584static asciichat_error_t handle_server_image_frame(const void *payload, size_t payload_len, void *client_ctx,
585 const acip_server_callbacks_t *callbacks);
586static asciichat_error_t handle_server_image_frame_h264(const void *payload, size_t payload_len, void *client_ctx,
587 const acip_server_callbacks_t *callbacks);
588static asciichat_error_t handle_server_image_frame_h265(const void *payload, size_t payload_len, void *client_ctx,
589 const acip_server_callbacks_t *callbacks);
590static asciichat_error_t handle_server_audio_batch(const void *payload, size_t payload_len, void *client_ctx,
591 const acip_server_callbacks_t *callbacks);
592static asciichat_error_t handle_server_audio_opus_batch(const void *payload, size_t payload_len, void *client_ctx,
593 const acip_server_callbacks_t *callbacks);
594static asciichat_error_t handle_server_client_join(const void *payload, size_t payload_len, void *client_ctx,
595 const acip_server_callbacks_t *callbacks);
596static asciichat_error_t handle_server_client_leave(const void *payload, size_t payload_len, void *client_ctx,
597 const acip_server_callbacks_t *callbacks);
598static asciichat_error_t handle_server_stream_start(const void *payload, size_t payload_len, void *client_ctx,
599 const acip_server_callbacks_t *callbacks);
600static asciichat_error_t handle_server_stream_stop(const void *payload, size_t payload_len, void *client_ctx,
601 const acip_server_callbacks_t *callbacks);
602static asciichat_error_t handle_server_capabilities(const void *payload, size_t payload_len, void *client_ctx,
603 const acip_server_callbacks_t *callbacks);
604static asciichat_error_t handle_server_ping(const void *payload, size_t payload_len, void *client_ctx,
605 const acip_server_callbacks_t *callbacks);
606static asciichat_error_t handle_server_remote_log(const void *payload, size_t payload_len, void *client_ctx,
607 const acip_server_callbacks_t *callbacks);
608static asciichat_error_t handle_server_protocol_version(const void *payload, size_t payload_len, void *client_ctx,
609 const acip_server_callbacks_t *callbacks);
610static asciichat_error_t handle_server_pong(const void *payload, size_t payload_len, void *client_ctx,
611 const acip_server_callbacks_t *callbacks);
612static asciichat_error_t handle_server_error_message(const void *payload, size_t payload_len, void *client_ctx,
613 const acip_server_callbacks_t *callbacks);
614static asciichat_error_t handle_server_crypto_key_exchange_resp(const void *payload, size_t payload_len,
615 void *client_ctx,
616 const acip_server_callbacks_t *callbacks);
617static asciichat_error_t handle_server_crypto_auth_response(const void *payload, size_t payload_len, void *client_ctx,
618 const acip_server_callbacks_t *callbacks);
619static asciichat_error_t handle_server_crypto_no_encryption(const void *payload, size_t payload_len, void *client_ctx,
620 const acip_server_callbacks_t *callbacks);
621
622// Server handler dispatch table (indexed by server_handler_index())
623static const acip_server_handler_func_t g_server_handlers[SERVER_HANDLER_COUNT] = {
624 handle_server_protocol_version, // 0
625 handle_server_image_frame, // 1
626 handle_server_image_frame_h265, // 2
627 handle_server_audio_batch, // 3
628 handle_server_audio_opus_batch, // 4
629 handle_server_capabilities, // 5
630 handle_server_ping, // 6
631 handle_server_pong, // 7
632 handle_server_client_join, // 8
633 handle_server_client_leave, // 9
634 handle_server_stream_start, // 10
635 handle_server_stream_stop, // 11
636 handle_server_remote_log, // 12
637 handle_server_error_message, // 13
638 NULL, // 14
639 NULL, // 15
640 NULL, // 16
641 handle_server_crypto_key_exchange_resp, // 17
642 handle_server_crypto_auth_response, // 18
643 handle_server_crypto_no_encryption, // 19
644 handle_server_image_frame_h264, // 20
645};
646
647// Canonical names for the wire packet types.
649 switch (type) {
651 return "PROTOCOL_VERSION";
653 return "CRYPTO_CLIENT_HELLO";
655 return "CRYPTO_CAPABILITIES";
657 return "CRYPTO_PARAMETERS";
659 return "CRYPTO_KEY_EXCHANGE_INIT";
661 return "CRYPTO_KEY_EXCHANGE_RESP";
663 return "CRYPTO_AUTH_CHALLENGE";
665 return "CRYPTO_AUTH_RESPONSE";
667 return "CRYPTO_AUTH_FAILED";
669 return "CRYPTO_SERVER_AUTH_RESP";
671 return "CRYPTO_HANDSHAKE_COMPLETE";
673 return "CRYPTO_NO_ENCRYPTION";
675 return "ENCRYPTED";
677 return "CRYPTO_REKEY_REQUEST";
679 return "CRYPTO_REKEY_RESPONSE";
681 return "CRYPTO_REKEY_COMPLETE";
683 return "SIZE_MESSAGE";
685 return "AUDIO_MESSAGE";
687 return "TEXT_MESSAGE";
689 return "ERROR_MESSAGE";
691 return "REMOTE_LOG";
693 return "ASCII_FRAME";
695 return "IMAGE_FRAME";
697 return "IMAGE_FRAME_H265";
699 return "IMAGE_FRAME_H264";
701 return "AUDIO_BATCH";
703 return "AUDIO_OPUS_BATCH";
705 return "CLIENT_CAPABILITIES";
706 case PACKET_TYPE_PING:
707 return "PING";
708 case PACKET_TYPE_PONG:
709 return "PONG";
711 return "CLIENT_JOIN";
713 return "CLIENT_LEAVE";
715 return "STREAM_START";
717 return "STREAM_STOP";
719 return "CLEAR_CONSOLE";
721 return "SERVER_STATE";
723 return "ACIP_SESSION_CREATE";
725 return "ACIP_SESSION_CREATED";
727 return "ACIP_SESSION_LOOKUP";
729 return "ACIP_SESSION_INFO";
731 return "ACIP_SESSION_JOIN";
733 return "ACIP_SESSION_JOINED";
735 return "ACIP_SESSION_LEAVE";
737 return "ACIP_SESSION_END";
739 return "ACIP_SESSION_RECONNECT";
741 return "ACIP_WEBRTC_SDP";
743 return "ACIP_WEBRTC_ICE";
745 return "ACIP_STRING_RESERVE";
747 return "ACIP_STRING_RESERVED";
749 return "ACIP_STRING_RENEW";
751 return "ACIP_STRING_RELEASE";
753 return "ACIP_PARTICIPANT_LIST";
755 return "ACIP_RING_COLLECT";
757 return "ACIP_NETWORK_QUALITY";
759 return "ACIP_HOST_ANNOUNCEMENT";
761 return "ACIP_HOST_DESIGNATED";
763 return "ACIP_SETTINGS_SYNC";
765 return "ACIP_SETTINGS_ACK";
767 return "ACIP_HOST_LOST";
769 return "ACIP_FUTURE_HOST_ELECTED";
771 return "ACIP_PARTICIPANT_JOINED";
773 return "ACIP_PARTICIPANT_LEFT";
775 return "ACIP_BANDWIDTH_TEST";
777 return "ACIP_BANDWIDTH_RESULT";
779 return "ACIP_BROADCAST_ACK";
781 return "RING_MEMBERS";
783 return "STATS_COLLECTION_START";
785 return "STATS_UPDATE";
787 return "RING_ELECTION_RESULT";
789 return "STATS_ACK";
791 return "ACIP_DISCOVERY_PING";
793 return "ACIP_ERROR";
794 default:
795 return "UNKNOWN";
796 }
797}
798
800 size_t payload_len, void *client_ctx,
801 const acip_server_callbacks_t *callbacks) {
802 stats_runtime_packet(transport ? transport->stats_peer : NULL, type, payload_len, false, true);
803 if (!transport || !callbacks) {
804 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid transport or callbacks");
805 }
806 (void)transport;
807
808 // DEBUG: Log all packet types received
809 log_dev("ACIP_HANDLE: type=%d payload_len=%zu", type, payload_len);
810
811 // O(1) dispatch via hash table lookup
812 int idx = handler_hash_lookup(g_server_handler_hash, type);
813 if (idx < 0) {
814 log_error("ACIP_HANDLER_NOT_FOUND: No handler for packet type=%d (0x%04x, name=%s)", type, type,
816 return SET_ERRNO(ERROR_INVALID_PARAM, "Unhandled server packet type: %d", type);
817 }
818
819 log_dev("HANDLER_DISPATCH: type=%d (%s), handler_idx=%d, payload=%zu bytes", type, acip_packet_type_name(type), idx,
820 payload_len);
821 asciichat_error_t result = g_server_handlers[idx](payload, payload_len, client_ctx, callbacks);
822
823 if (result != ASCIICHAT_OK) {
824 log_error("❌ [HANDLER_ERROR] Handler failed: type=%d (%s), result=%s", type, acip_packet_type_name(type),
825 asciichat_error_format(NULL, result));
826 } else {
827 log_dev("HANDLER_COMPLETE: type=%d (%s), payload=%zu bytes", type, acip_packet_type_name(type), payload_len);
828 }
829
830 return result;
831}
832
833// =============================================================================
834// Server Handler Implementations (extracted from switch cases)
835// =============================================================================
836
837static asciichat_error_t handle_server_image_frame(const void *payload, size_t payload_len, void *client_ctx,
838 const acip_server_callbacks_t *callbacks) {
839 log_dev("ACIP_IMAGE_FRAME_HANDLER: payload_len=%zu", payload_len);
840
841 if (!callbacks->on_image_frame) {
842 log_warn("ACIP_IMAGE_FRAME_HANDLER: No callback registered for on_image_frame");
843 return ASCIICHAT_OK;
844 }
845
846 log_dev("ACIP_IMAGE_FRAME_HANDLER: Callback registered (header=%zu)", sizeof(image_frame_packet_t));
847
848 if (payload_len < sizeof(uint32_t) * 2) {
849 log_error("ACIP_IMAGE_FRAME_HANDLER: Payload too small: %zu bytes (need at least 8)", payload_len);
850 return SET_ERRNO(ERROR_INVALID_PARAM, "IMAGE_FRAME payload too small: %zu bytes (need at least 8)", payload_len);
851 }
852
853 // Debug: Log raw payload bytes
854 const uint8_t *payload_bytes = (const uint8_t *)payload;
855 log_dev_every(4500 * US_PER_MS_INT, "IMAGE_FRAME payload (%zu bytes):", payload_len);
857 size_t hex_pos = 0;
858 size_t max_bytes = (payload_len < 48) ? payload_len : 48;
859 for (size_t i = 0; i < max_bytes && hex_pos < sizeof(hex_buf) - 4; i++) {
860 hex_pos += snprintf(hex_buf + hex_pos, sizeof(hex_buf) - hex_pos, "%02x ", payload_bytes[i]);
861 }
862 log_dev_every(4500 * US_PER_MS_INT, " First bytes: %s", hex_buf);
863
864 // Older browser clients send [width:u32][height:u32][RGB24 pixels]. Accept
865 // that exact size alongside the current image_frame_packet_t wire header.
866 uint32_t legacy_width_net;
867 uint32_t legacy_height_net;
868 memcpy(&legacy_width_net, payload_bytes, sizeof(legacy_width_net));
869 memcpy(&legacy_height_net, payload_bytes + sizeof(legacy_width_net), sizeof(legacy_height_net));
870 uint32_t legacy_width = NET_TO_HOST_U32(legacy_width_net);
871 uint32_t legacy_height = NET_TO_HOST_U32(legacy_height_net);
872 size_t legacy_pixels = 0;
873 size_t legacy_rgb_size = 0;
874 size_t legacy_payload_size = 0;
875 if (legacy_width > 0 && legacy_height > 0 && legacy_width <= 8192 && legacy_height <= 8192 &&
876 checked_size_mul(legacy_width, legacy_height, &legacy_pixels) == ASCIICHAT_OK &&
877 checked_size_mul(legacy_pixels, 3, &legacy_rgb_size) == ASCIICHAT_OK &&
878 checked_size_add(sizeof(uint32_t) * 2, legacy_rgb_size, &legacy_payload_size) == ASCIICHAT_OK &&
879 payload_len == legacy_payload_size) {
880 image_frame_packet_t legacy_header = {0};
881 legacy_header.width = legacy_width;
882 legacy_header.height = legacy_height;
883 legacy_header.pixel_format = 1; // RGB24
884 callbacks->on_image_frame(&legacy_header, payload_bytes + sizeof(uint32_t) * 2, legacy_rgb_size, client_ctx,
885 callbacks->app_ctx);
886 return ASCIICHAT_OK;
887 }
888
889 if (payload_len < sizeof(image_frame_packet_t)) {
890 return SET_ERRNO(ERROR_INVALID_PARAM, "IMAGE_FRAME payload too small: %zu bytes (need %zu)", payload_len,
891 sizeof(image_frame_packet_t));
892 }
893
894 // Extract header
896 memcpy(&header, payload, sizeof(header));
897
898 // Debug: Log raw header before byte order conversion
899 log_dev_every(4500 * US_PER_MS_INT, "IMAGE_FRAME header (raw network order):");
900 log_dev_every(4500 * US_PER_MS_INT, " width=0x%08x height=0x%08x pixel_format=0x%08x compressed_size=0x%08x",
901 header.width, header.height, header.pixel_format, header.compressed_size);
902
903 // Convert from network byte order
904 header.width = NET_TO_HOST_U32(header.width);
905 header.height = NET_TO_HOST_U32(header.height);
906 header.pixel_format = NET_TO_HOST_U32(header.pixel_format);
907 header.compressed_size = NET_TO_HOST_U32(header.compressed_size);
908 header.checksum = NET_TO_HOST_U32(header.checksum);
909 header.timestamp = NET_TO_HOST_U32(header.timestamp);
910
911 // Debug: Log header after byte order conversion
912 log_dev_every(4500 * US_PER_MS_INT, "IMAGE_FRAME header (after host byte order):");
913 log_dev_every(4500 * US_PER_MS_INT, " width=%u height=%u pixel_format=%u compressed_size=%u", header.width,
914 header.height, header.pixel_format, header.compressed_size);
915
916 // Get pixel data (after header)
917 const void *pixel_data = (const uint8_t *)payload + sizeof(image_frame_packet_t);
918 size_t pixel_data_len = payload_len - sizeof(image_frame_packet_t);
919
920 // Validate frame dimensions to prevent DoS and buffer overflow attacks
921 if (header.width == 0 || header.height == 0) {
922 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid image dimensions: %ux%u (width and height must be > 0)",
923 header.width, header.height);
924 }
925
926 // Sanity check: prevent unreasonably large frames (e.g., > 8K resolution for RGB)
927 // This protects against resource exhaustion attacks
928 const uint32_t MAX_WIDTH = 8192; // 8K for RGB data
929 const uint32_t MAX_HEIGHT = 8192; // 8K for RGB data
930 if (header.width > MAX_WIDTH || header.height > MAX_HEIGHT) {
931 return SET_ERRNO(ERROR_INVALID_PARAM, "Image dimensions too large: %ux%u (max: %ux%u)", header.width, header.height,
932 MAX_WIDTH, MAX_HEIGHT);
933 }
934
935 // Validate pixel format
936 // Valid formats: RGB24 (3), RGBA32 (4), YUV420 (1.5)
937 if (header.pixel_format == 0 || header.pixel_format > 4) {
938 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid pixel format: %u (expected: 1-4)", header.pixel_format);
939 }
940
941 log_dev(
942 "📹 [IMAGE_FRAME_CALLBACK] Invoking on_image_frame callback: %ux%u pixels, format=%u, %zu bytes, client_ctx=%p",
943 header.width, header.height, header.pixel_format, pixel_data_len, client_ctx);
944
945 callbacks->on_image_frame(&header, pixel_data, pixel_data_len, client_ctx, callbacks->app_ctx);
946
947 log_dev("IMAGE_FRAME_DONE");
948 return ASCIICHAT_OK;
949}
950
951static asciichat_error_t handle_server_encoded_frame(const void *payload, size_t payload_len, void *client_ctx,
952 const acip_server_callbacks_t *callbacks, bool h264) {
953 if (!payload || payload_len <= 5)
954 return SET_ERRNO(ERROR_INVALID_PARAM, "Encoded video payload must contain a five-byte header and access unit");
955 const uint8_t *bytes = payload;
956 uint8_t flags = bytes[0];
957 uint16_t width = ((uint16_t)bytes[1] << 8) | bytes[2];
958 uint16_t height = ((uint16_t)bytes[3] << 8) | bytes[4];
959 if (!width || !height || width > 8192 || height > 8192 || (flags & ~3))
960 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid encoded video dimensions or flags");
961 if (h264) {
962 if (callbacks->on_image_frame_h264)
963 callbacks->on_image_frame_h264(width, height, flags, bytes + 5, payload_len - 5, client_ctx, callbacks->app_ctx);
964 } else if (callbacks->on_image_frame_h265) {
965 callbacks->on_image_frame_h265(width, height, flags, bytes + 5, payload_len - 5, client_ctx, callbacks->app_ctx);
966 }
967 return ASCIICHAT_OK;
968}
969
970static asciichat_error_t handle_server_image_frame_h264(const void *payload, size_t payload_len, void *client_ctx,
971 const acip_server_callbacks_t *callbacks) {
972 return handle_server_encoded_frame(payload, payload_len, client_ctx, callbacks, true);
973}
974
975static asciichat_error_t handle_server_image_frame_h265(const void *payload, size_t payload_len, void *client_ctx,
976 const acip_server_callbacks_t *callbacks) {
977 return handle_server_encoded_frame(payload, payload_len, client_ctx, callbacks, false);
978}
979
980static asciichat_error_t handle_server_audio_batch(const void *payload, size_t payload_len, void *client_ctx,
981 const acip_server_callbacks_t *callbacks) {
982 if (!callbacks->on_audio_batch) {
983 return ASCIICHAT_OK;
984 }
985
986 if (payload_len < sizeof(audio_batch_packet_t)) {
987 return SET_ERRNO(ERROR_INVALID_PARAM, "AUDIO_BATCH payload too small");
988 }
989
990 // Parse batch header
991 const audio_batch_packet_t *batch_header = (const audio_batch_packet_t *)payload;
992 uint32_t batch_count = NET_TO_HOST_U32(batch_header->batch_count);
993 uint32_t total_samples = NET_TO_HOST_U32(batch_header->total_samples);
994 uint32_t sample_rate = NET_TO_HOST_U32(batch_header->sample_rate);
995 uint32_t channels = NET_TO_HOST_U32(batch_header->channels);
996
997 // Validate size
998 size_t expected_size = sizeof(audio_batch_packet_t) + (total_samples * sizeof(uint32_t));
999 if (payload_len != expected_size) {
1000 return SET_ERRNO(ERROR_INVALID_PARAM, "AUDIO_BATCH size mismatch");
1001 }
1002
1003 // Extract quantized samples
1004 const uint8_t *samples_ptr = (const uint8_t *)payload + sizeof(audio_batch_packet_t);
1005
1006 // Dequantize samples to float
1007 float *samples = SAFE_MALLOC(total_samples * sizeof(float), float *);
1008 if (!samples) {
1009 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate audio batch buffer");
1010 }
1011
1012 asciichat_error_t result = audio_dequantize_samples(samples_ptr, total_samples, samples);
1013 if (result != ASCIICHAT_OK) {
1014 SAFE_FREE(samples);
1015 return result;
1016 }
1017
1018 // Copy header for callback
1019 audio_batch_packet_t header_copy = *batch_header;
1020 header_copy.batch_count = batch_count;
1021 header_copy.total_samples = total_samples;
1022 header_copy.sample_rate = sample_rate;
1023 header_copy.channels = channels;
1024
1025 callbacks->on_audio_batch(&header_copy, samples, total_samples, client_ctx, callbacks->app_ctx);
1026
1027 SAFE_FREE(samples);
1028 return ASCIICHAT_OK;
1029}
1030
1031static asciichat_error_t handle_server_audio_opus_batch(const void *payload, size_t payload_len, void *client_ctx,
1032 const acip_server_callbacks_t *callbacks) {
1033 if (!callbacks->on_audio_opus_batch) {
1034 return ASCIICHAT_OK;
1035 }
1036
1037 callbacks->on_audio_opus_batch(payload, payload_len, client_ctx, callbacks->app_ctx);
1038 return ASCIICHAT_OK;
1039}
1040
1041static asciichat_error_t handle_server_client_join(const void *payload, size_t payload_len, void *client_ctx,
1042 const acip_server_callbacks_t *callbacks) {
1043 if (!callbacks->on_client_join) {
1044 return ASCIICHAT_OK;
1045 }
1046
1047 callbacks->on_client_join(payload, payload_len, client_ctx, callbacks->app_ctx);
1048 return ASCIICHAT_OK;
1049}
1050
1051static asciichat_error_t handle_server_client_leave(const void *payload, size_t payload_len, void *client_ctx,
1052 const acip_server_callbacks_t *callbacks) {
1053 (void)payload;
1054 (void)payload_len;
1055
1056 if (!callbacks->on_client_leave) {
1057 return ASCIICHAT_OK;
1058 }
1059
1060 callbacks->on_client_leave(client_ctx, callbacks->app_ctx);
1061 return ASCIICHAT_OK;
1062}
1063
1064static asciichat_error_t handle_server_stream_start(const void *payload, size_t payload_len, void *client_ctx,
1065 const acip_server_callbacks_t *callbacks) {
1066 if (!callbacks->on_stream_start) {
1067 return ASCIICHAT_OK;
1068 }
1069
1070 if (payload_len < sizeof(uint32_t)) {
1071 return SET_ERRNO(ERROR_INVALID_PARAM, "STREAM_START payload too small (got %zu, expected %zu)", payload_len,
1072 sizeof(uint32_t));
1073 }
1074
1075 // Read stream_types as uint32_t (network byte order)
1076 uint32_t stream_types_net;
1077 memcpy(&stream_types_net, payload, sizeof(uint32_t));
1078 uint32_t stream_types = NET_TO_HOST_U32(stream_types_net);
1079
1080 callbacks->on_stream_start(stream_types, client_ctx, callbacks->app_ctx);
1081 return ASCIICHAT_OK;
1082}
1083
1084static asciichat_error_t handle_server_stream_stop(const void *payload, size_t payload_len, void *client_ctx,
1085 const acip_server_callbacks_t *callbacks) {
1086 if (!callbacks->on_stream_stop) {
1087 return ASCIICHAT_OK;
1088 }
1089
1090 if (payload_len < sizeof(uint32_t)) {
1091 return SET_ERRNO(ERROR_INVALID_PARAM, "STREAM_STOP payload too small (got %zu, expected %zu)", payload_len,
1092 sizeof(uint32_t));
1093 }
1094
1095 // Read stream_types as uint32_t (network byte order)
1096 uint32_t stream_types_net;
1097 memcpy(&stream_types_net, payload, sizeof(uint32_t));
1098 uint32_t stream_types = NET_TO_HOST_U32(stream_types_net);
1099
1100 callbacks->on_stream_stop(stream_types, client_ctx, callbacks->app_ctx);
1101 return ASCIICHAT_OK;
1102}
1103
1104static asciichat_error_t handle_server_capabilities(const void *payload, size_t payload_len, void *client_ctx,
1105 const acip_server_callbacks_t *callbacks) {
1106 log_debug("payload_len=%zu, client_ctx=%p, callbacks=%p", payload_len, client_ctx, (const void *)callbacks);
1107
1108 if (!callbacks->on_capabilities) {
1109 log_error("on_capabilities callback is NULL!");
1110 return ASCIICHAT_OK;
1111 }
1112
1113 log_error("Calling on_capabilities callback...");
1114 callbacks->on_capabilities(payload, payload_len, client_ctx, callbacks->app_ctx);
1115 log_error("on_capabilities callback returned");
1116 return ASCIICHAT_OK;
1117}
1118
1119static asciichat_error_t handle_server_ping(const void *payload, size_t payload_len, void *client_ctx,
1120 const acip_server_callbacks_t *callbacks) {
1121 (void)payload;
1122 (void)payload_len;
1123
1124 if (callbacks->on_ping) {
1125 callbacks->on_ping(client_ctx, callbacks->app_ctx);
1126 }
1127 return ASCIICHAT_OK;
1128}
1129
1130static asciichat_error_t handle_server_remote_log(const void *payload, size_t payload_len, void *client_ctx,
1131 const acip_server_callbacks_t *callbacks) {
1132 if (!callbacks->on_remote_log) {
1133 return ASCIICHAT_OK;
1134 }
1135
1136 if (payload_len < sizeof(remote_log_packet_t)) {
1137 return SET_ERRNO(ERROR_INVALID_PARAM, "REMOTE_LOG payload too small");
1138 }
1139
1140 const remote_log_packet_t *header = (const remote_log_packet_t *)payload;
1141 const char *message = (const char *)payload + sizeof(remote_log_packet_t);
1142 size_t msg_len = payload_len - sizeof(remote_log_packet_t);
1143
1144 // Ensure null-terminated message
1146 size_t copy_len = msg_len < sizeof(msg_buffer) - 1 ? msg_len : sizeof(msg_buffer) - 1;
1147 memcpy(msg_buffer, message, copy_len);
1148 msg_buffer[copy_len] = '\0';
1149
1150 callbacks->on_remote_log(header, msg_buffer, client_ctx, callbacks->app_ctx);
1151 return ASCIICHAT_OK;
1152}
1153
1154static asciichat_error_t handle_server_protocol_version(const void *payload, size_t payload_len, void *client_ctx,
1155 const acip_server_callbacks_t *callbacks) {
1156 if (!callbacks->on_protocol_version) {
1157 return ASCIICHAT_OK;
1158 }
1159
1160 if (payload_len != sizeof(protocol_version_packet_t)) {
1161 return SET_ERRNO(ERROR_INVALID_PARAM, "PROTOCOL_VERSION size mismatch");
1162 }
1163
1164 callbacks->on_protocol_version((const protocol_version_packet_t *)payload, client_ctx, callbacks->app_ctx);
1165 return ASCIICHAT_OK;
1166}
1167
1168static asciichat_error_t handle_server_pong(const void *payload, size_t payload_len, void *client_ctx,
1169 const acip_server_callbacks_t *callbacks) {
1170 (void)payload;
1171 (void)payload_len;
1172
1173 if (callbacks->on_pong) {
1174 callbacks->on_pong(client_ctx, callbacks->app_ctx);
1175 }
1176 return ASCIICHAT_OK;
1177}
1178
1179static asciichat_error_t handle_server_error_message(const void *payload, size_t payload_len, void *client_ctx,
1180 const acip_server_callbacks_t *callbacks) {
1181 if (!callbacks->on_error) {
1182 return ASCIICHAT_OK;
1183 }
1184
1185 if (payload_len < sizeof(error_packet_t)) {
1186 return SET_ERRNO(ERROR_INVALID_PARAM, "ERROR_MESSAGE payload too small");
1187 }
1188
1189 const error_packet_t *header = (const error_packet_t *)payload;
1190 const char *message = (const char *)payload + sizeof(error_packet_t);
1191 size_t msg_len = payload_len - sizeof(error_packet_t);
1192
1193 // Ensure null-terminated message
1194 char msg_buffer[MAX_ERROR_MESSAGE_LENGTH + 1];
1195 size_t copy_len = msg_len < MAX_ERROR_MESSAGE_LENGTH ? msg_len : MAX_ERROR_MESSAGE_LENGTH;
1196 memcpy(msg_buffer, message, copy_len);
1197 msg_buffer[copy_len] = '\0';
1198
1199 callbacks->on_error(header, msg_buffer, client_ctx, callbacks->app_ctx);
1200 return ASCIICHAT_OK;
1201}
1202
1203static asciichat_error_t handle_server_crypto_key_exchange_resp(const void *payload, size_t payload_len,
1204 void *client_ctx,
1205 const acip_server_callbacks_t *callbacks) {
1206 if (!callbacks->on_crypto_key_exchange_resp) {
1207 return ASCIICHAT_OK;
1208 }
1209
1210 // Dispatch to application callback with packet type
1211 callbacks->on_crypto_key_exchange_resp(PACKET_TYPE_CRYPTO_KEY_EXCHANGE_RESP, payload, payload_len, client_ctx,
1212 callbacks->app_ctx);
1213 return ASCIICHAT_OK;
1214}
1215
1216static asciichat_error_t handle_server_crypto_auth_response(const void *payload, size_t payload_len, void *client_ctx,
1217 const acip_server_callbacks_t *callbacks) {
1218 if (!callbacks->on_crypto_auth_response) {
1219 return ASCIICHAT_OK;
1220 }
1221
1222 // Dispatch to application callback with packet type
1223 callbacks->on_crypto_auth_response(PACKET_TYPE_CRYPTO_AUTH_RESPONSE, payload, payload_len, client_ctx,
1224 callbacks->app_ctx);
1225 return ASCIICHAT_OK;
1226}
1227
1228static asciichat_error_t handle_server_crypto_no_encryption(const void *payload, size_t payload_len, void *client_ctx,
1229 const acip_server_callbacks_t *callbacks) {
1230 if (!callbacks->on_crypto_no_encryption) {
1231 return ASCIICHAT_OK;
1232 }
1233
1234 // Dispatch to application callback with packet type
1235 callbacks->on_crypto_no_encryption(PACKET_TYPE_CRYPTO_NO_ENCRYPTION, payload, payload_len, client_ctx,
1236 callbacks->app_ctx);
1237 return ASCIICHAT_OK;
1238}
ascii-chat Discovery Service (ACDS) Protocol Message Formats
const char * asciichat_error_format(const char *message, asciichat_error_t code)
Synchronized per-thread pending error stacks and value snapshots. SET_ERRNO pushes,...
#define SET_ERRNO(code,...)
⚙️ Common definitions, error codes, macros, and types shared throughout the application
🔄 Network byte order conversion helpers
#define NET_TO_HOST_U32(val)
Definition endian.h:81
unsigned int height
Definition fft.c:23
unsigned int width
Definition fft.c:23
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 short uint16_t
Definition common.h:61
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
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
@ ERROR_INVALID_PARAM
#define log_warn(...)
Log a WARN message.
Definition log/log.h:621
#define log_dev(...)
Log a DEV message (most verbose, development only).
Definition log/log.h:581
#define log_error(...)
Log an ERROR message.
Definition log/log.h:634
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
acip_session_joined_t
Definition acds.h:479
acip_webrtc_ice_t
Definition acds.h:655
acip_webrtc_sdp_t
Definition acds.h:609
#define US_PER_MS_INT
Definition time.h:172
packet_type_t
Network protocol packet type enumeration.
Definition packet.h:301
#define MAX_ERROR_MESSAGE_LENGTH
Maximum error message length (512 bytes).
Definition packet.h:139
@ PACKET_TYPE_CRYPTO_CLIENT_HELLO
Client -> Server: Expected server key fingerprint for multi-key selection (UNENCRYPTED).
Definition packet.h:314
@ PACKET_TYPE_ACIP_WEBRTC_ICE
WebRTC ICE candidate (bidirectional).
Definition packet.h:452
@ PACKET_TYPE_AUDIO_OPUS_BATCH
Batched Opus-encoded audio frames.
Definition packet.h:398
@ PACKET_TYPE_ACIP_PARTICIPANT_JOINED
Participant joined notification (ACDS -> Existing Participants).
Definition packet.h:493
@ PACKET_TYPE_CLIENT_LEAVE
Clean disconnect notification.
Definition packet.h:413
@ PACKET_TYPE_ACIP_SESSION_INFO
Session info response (Discovery Server -> Client).
Definition packet.h:437
@ PACKET_TYPE_IMAGE_FRAME
Complete RGB image with dimensions.
Definition packet.h:385
@ PACKET_TYPE_CRYPTO_AUTH_RESPONSE
Client -> Server: {HMAC[32]} (UNENCRYPTED).
Definition packet.h:332
@ PACKET_TYPE_TEXT_MESSAGE
Text message.
Definition packet.h:372
@ PACKET_TYPE_AUDIO_MESSAGE
Audio message.
Definition packet.h:370
@ PACKET_TYPE_PONG
Keepalive pong response.
Definition packet.h:409
@ PACKET_TYPE_ACIP_SESSION_RECONNECT
Reconnect to session (Client -> Discovery Server).
Definition packet.h:447
@ PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE
Server -> Client: "encryption ready" (UNENCRYPTED).
Definition packet.h:338
@ PACKET_TYPE_ACIP_PARTICIPANT_LIST
Participant list with ring order (ACDS -> Participants).
Definition packet.h:469
@ PACKET_TYPE_STREAM_START
Client requests to start sending video/audio.
Definition packet.h:415
@ PACKET_TYPE_ACIP_SETTINGS_SYNC
Settings sync (Initiator -> Host -> All Participants).
Definition packet.h:485
@ PACKET_TYPE_CRYPTO_KEY_EXCHANGE_INIT
Server -> Client: {server_pubkey[32]} (UNENCRYPTED).
Definition packet.h:326
@ PACKET_TYPE_ACIP_ERROR
Generic error response (Discovery Server -> Client).
Definition packet.h:533
@ PACKET_TYPE_CRYPTO_KEY_EXCHANGE_RESP
Client -> Server: {client_pubkey[32]} (UNENCRYPTED).
Definition packet.h:328
@ PACKET_TYPE_CRYPTO_REKEY_COMPLETE
Initiator -> Responder: Empty (encrypted with NEW key, but still handshake).
Definition packet.h:361
@ PACKET_TYPE_ACIP_NETWORK_QUALITY
NETWORK_QUALITY exchange (unified packet for all quality metrics).
Definition packet.h:479
@ PACKET_TYPE_SERVER_STATE
Server sends current state to clients.
Definition packet.h:421
@ PACKET_TYPE_ACIP_HOST_LOST
Host lost notification - lightweight (Participant -> ACDS).
Definition packet.h:489
@ PACKET_TYPE_ACIP_BROADCAST_ACK
Broadcast acknowledgment for reliable delivery.
Definition packet.h:508
@ PACKET_TYPE_ACIP_HOST_DESIGNATED
Host designated (ACDS -> All Participants).
Definition packet.h:483
@ PACKET_TYPE_ACIP_STRING_RESERVE
Reserve session string (Client -> Discovery Server).
Definition packet.h:455
@ PACKET_TYPE_CRYPTO_AUTH_FAILED
Server -> Client: "authentication failed" (UNENCRYPTED).
Definition packet.h:334
@ PACKET_TYPE_ENCRYPTED
Encrypted packet (after handshake completion).
Definition packet.h:355
@ PACKET_TYPE_CRYPTO_REKEY_REQUEST
Initiator -> Responder: {new_ephemeral_pk[32]} (UNENCRYPTED during rekey).
Definition packet.h:357
@ PACKET_TYPE_SIZE_MESSAGE
Terminal size message.
Definition packet.h:368
@ PACKET_TYPE_REMOTE_LOG
Bidirectional remote logging packet.
Definition packet.h:376
@ PACKET_TYPE_CRYPTO_SERVER_AUTH_RESP
Server -> Client: {HMAC[32]} server proves knowledge (UNENCRYPTED).
Definition packet.h:336
@ PACKET_TYPE_ACIP_HOST_ANNOUNCEMENT
Host announcement (Participant -> ACDS, "I won negotiation").
Definition packet.h:481
@ PACKET_TYPE_PROTOCOL_VERSION
Protocol version and capabilities negotiation.
Definition packet.h:303
@ PACKET_TYPE_ACIP_SETTINGS_ACK
Settings acknowledgment (Participant -> Initiator).
Definition packet.h:487
@ PACKET_TYPE_ACIP_BANDWIDTH_TEST
Bandwidth test request (Client -> ACDS).
Definition packet.h:503
@ PACKET_TYPE_CLEAR_CONSOLE
Server tells client to clear console.
Definition packet.h:419
@ PACKET_TYPE_CRYPTO_NO_ENCRYPTION
Client -> Server: "I want to proceed without encryption" (UNENCRYPTED).
Definition packet.h:340
@ PACKET_TYPE_ACIP_SESSION_CREATE
Create new session (Client -> Discovery Server).
Definition packet.h:431
@ PACKET_TYPE_STATS_UPDATE
Statistics update during collection phase (Participant -> Next Participant).
Definition packet.h:520
@ PACKET_TYPE_ACIP_STRING_RESERVED
String reserved response (Discovery Server -> Client).
Definition packet.h:457
@ PACKET_TYPE_IMAGE_FRAME_H265
H.265-encoded frame from client (HEVC/H.265 codec).
Definition packet.h:387
@ PACKET_TYPE_ACIP_DISCOVERY_PING
Discovery server ping (keepalive).
Definition packet.h:531
@ PACKET_TYPE_CRYPTO_AUTH_CHALLENGE
Server -> Client: {nonce[32]} (UNENCRYPTED).
Definition packet.h:330
@ PACKET_TYPE_ASCII_FRAME
Complete ASCII frame with all metadata.
Definition packet.h:383
@ PACKET_TYPE_CRYPTO_PARAMETERS
Server -> Client: Chosen algorithms + data sizes (UNENCRYPTED).
Definition packet.h:324
@ PACKET_TYPE_ACIP_SESSION_LOOKUP
Lookup session by string (Client -> Discovery Server).
Definition packet.h:435
@ PACKET_TYPE_CRYPTO_CAPABILITIES
Client -> Server: Supported crypto algorithms (UNENCRYPTED).
Definition packet.h:322
@ PACKET_TYPE_RING_ELECTION_RESULT
Final election result (Quorum Leader -> All Participants).
Definition packet.h:522
@ PACKET_TYPE_ACIP_SESSION_LEAVE
Leave session (Client -> Discovery Server).
Definition packet.h:443
@ PACKET_TYPE_ACIP_PARTICIPANT_LEFT
Participant left notification (ACDS -> Remaining Participants).
Definition packet.h:495
@ PACKET_TYPE_ACIP_WEBRTC_SDP
WebRTC SDP offer/answer (bidirectional).
Definition packet.h:450
@ PACKET_TYPE_ACIP_SESSION_JOIN
Join existing session (Client -> Discovery Server).
Definition packet.h:439
@ PACKET_TYPE_ACIP_SESSION_CREATED
Session created response (Discovery Server -> Client).
Definition packet.h:433
@ PACKET_TYPE_STATS_ACK
Acknowledgment of statistics update (Participant -> Sender).
Definition packet.h:524
@ PACKET_TYPE_ACIP_STRING_RELEASE
Release string reservation (Client -> Discovery Server).
Definition packet.h:461
@ PACKET_TYPE_CLIENT_JOIN
Client announces capability to send media.
Definition packet.h:411
@ PACKET_TYPE_CLIENT_CAPABILITIES
Client reports terminal capabilities.
Definition packet.h:405
@ PACKET_TYPE_ACIP_FUTURE_HOST_ELECTED
Future host elected announcement (Quorum Leader -> ACDS -> All Participants).
Definition packet.h:491
@ PACKET_TYPE_IMAGE_FRAME_H264
Definition packet.h:389
@ PACKET_TYPE_ACIP_BANDWIDTH_RESULT
Bandwidth test results (ACDS -> Client).
Definition packet.h:505
@ PACKET_TYPE_ACIP_STRING_RENEW
Renew string reservation (Client -> Discovery Server).
Definition packet.h:459
@ PACKET_TYPE_ERROR_MESSAGE
Error packet with asciichat_error_t code and human-readable message.
Definition packet.h:374
@ PACKET_TYPE_RING_MEMBERS
Ring member list announcement (ACDS -> All Participants).
Definition packet.h:516
@ PACKET_TYPE_PING
Keepalive ping packet.
Definition packet.h:407
@ PACKET_TYPE_AUDIO_BATCH
Batched audio packets for efficiency.
Definition packet.h:396
@ PACKET_TYPE_CRYPTO_REKEY_RESPONSE
Responder -> Initiator: {new_ephemeral_pk[32]} (UNENCRYPTED during rekey).
Definition packet.h:359
@ PACKET_TYPE_ACIP_SESSION_JOINED
Session joined response (Discovery Server -> Client).
Definition packet.h:441
@ PACKET_TYPE_STATS_COLLECTION_START
Start statistics collection phase (Coordinator -> All Participants).
Definition packet.h:518
@ PACKET_TYPE_STREAM_STOP
Client stops sending media.
Definition packet.h:417
@ PACKET_TYPE_ACIP_RING_COLLECT
Ring collect request (Participant -> Next Participant).
Definition packet.h:471
@ PACKET_TYPE_ACIP_SESSION_END
End session (Host -> Discovery Server).
Definition packet.h:445
struct acip_transport acip_transport_t
Forward declaration of ACIP transport.
Definition host.h:506
const char * acip_packet_type_name(uint16_t type)
Get human-readable name for packet type.
Definition handlers.c:648
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
#define NETWORK_ACIP_HANDLERS_HEX_BUFFER_SIZE
Definition handlers.c:15
#define NETWORK_ACIP_HANDLERS_MSG_BUFFER_SIZE
Definition handlers.c:14
asciichat_error_t acip_handle_client_packet(acip_transport_t *transport, packet_type_t type, const void *payload, size_t payload_len, const acip_client_callbacks_t *callbacks)
Client-side packet handler callbacks.
Definition handlers.c:203
asciichat_error_t(* acip_client_handler_func_t)(const void *payload, size_t payload_len, const acip_client_callbacks_t *callbacks)
Definition handlers.c:34
#define HANDLER_HASH(type)
Definition handlers.c:60
asciichat_error_t(* acip_server_handler_func_t)(const void *payload, size_t payload_len, void *client_ctx, const acip_server_callbacks_t *callbacks)
Definition handlers.c:37
#define CLIENT_HANDLER_COUNT
Definition handlers.c:47
#define HANDLER_HASH_SIZE
Definition handlers.c:46
#define SERVER_HANDLER_COUNT
Definition handlers.c:48
ACIP protocol packet handlers (transport-agnostic).
🔊 Audio Capture and Playback Interface for ascii-chat
📝 Logging API with multiple log levels and terminal output control
#define log_debug_once(fmt,...)
Log DEBUG message exactly once.
Definition log/log.h:880
#define log_dev_every(interval_us, fmt,...)
Rate-limited DEV logging.
Definition log/log.h:746
ACIP protocol message structures and helpers.
✅ Safe Integer Arithmetic and Overflow Detection
void stats_runtime_packet(stats_peer_t *peer, unsigned type, size_t payload_bytes, bool sent, bool success)
Definition runtime.c:196
🔤 String Manipulation and Shell Escaping Utilities
Client-side packet handler callbacks.
Definition handlers.h:51
void(* on_clear_console)(void *ctx)
Called when server requests console clear.
Definition handlers.h:83
void(* on_session_joined)(const acip_session_joined_t *joined, void *ctx)
Called when ACDS session join response received (Phase 3 WebRTC integration).
Definition handlers.h:92
void(* on_webrtc_sdp)(const acip_webrtc_sdp_t *sdp, size_t total_len, void *ctx)
Called when WebRTC SDP offer/answer received.
Definition handlers.h:86
void(* on_error)(const error_packet_t *header, const char *message, void *ctx)
Called when error message received.
Definition handlers.h:71
void(* on_crypto_parameters)(packet_type_t type, const void *payload, size_t payload_len, void *ctx)
Called when server sends CRYPTO_PARAMETERS (algorithm negotiation).
Definition handlers.h:95
void(* on_crypto_handshake_complete)(packet_type_t type, const void *payload, size_t payload_len, void *ctx)
Called when server sends HANDSHAKE_COMPLETE (crypto handshake success).
Definition handlers.h:110
void(* on_crypto_auth_challenge)(packet_type_t type, const void *payload, size_t payload_len, void *ctx)
Called when server sends AUTH_CHALLENGE (crypto handshake).
Definition handlers.h:101
void(* on_audio_opus_batch)(const void *batch_data, size_t batch_len, void *ctx)
Called when Opus batch received from server.
Definition handlers.h:65
void(* on_ping)(void *ctx)
Called when ping received (should send pong).
Definition handlers.h:77
void(* on_crypto_key_exchange_init)(packet_type_t type, const void *payload, size_t payload_len, void *ctx)
Called when server sends KEY_EXCHANGE_INIT (crypto handshake).
Definition handlers.h:98
void(* on_pong)(void *ctx)
Called when pong received.
Definition handlers.h:80
void(* on_audio_batch)(const audio_batch_packet_t *header, const float *samples, size_t num_samples, void *ctx)
Called when audio batch received from server.
Definition handlers.h:59
void(* on_webrtc_ice)(const acip_webrtc_ice_t *ice, size_t total_len, void *ctx)
Called when WebRTC ICE candidate received.
Definition handlers.h:89
void(* on_remote_log)(const remote_log_packet_t *header, const char *message, void *ctx)
Called when remote log received.
Definition handlers.h:74
void(* on_ascii_frame)(const ascii_frame_packet_t *header, const void *frame_data, size_t data_len, void *ctx)
Called when ASCII frame received from server.
Definition handlers.h:53
void(* on_crypto_server_auth_resp)(packet_type_t type, const void *payload, size_t payload_len, void *ctx)
Called when server sends SERVER_AUTH_RESP (crypto handshake mutual auth).
Definition handlers.h:104
void * app_ctx
Application context (passed to all callbacks).
Definition handlers.h:113
void(* on_server_state)(const server_state_packet_t *state, void *ctx)
Called when server state update received.
Definition handlers.h:68
void(* on_crypto_auth_failed)(packet_type_t type, const void *payload, size_t payload_len, void *ctx)
Called when server sends AUTH_FAILED (crypto handshake failure).
Definition handlers.h:107
Server-side packet handler callbacks.
Definition handlers.h:144
void(* on_image_frame)(const image_frame_packet_t *header, const void *pixel_data, size_t data_len, void *client_ctx, void *app_ctx)
Called when client sends image frame.
Definition handlers.h:149
void(* on_crypto_auth_response)(packet_type_t type, const void *payload, size_t payload_len, void *client_ctx, void *app_ctx)
Called when client sends AUTH_RESPONSE (crypto handshake).
Definition handlers.h:204
void(* on_capabilities)(const void *cap_data, size_t data_len, void *client_ctx, void *app_ctx)
Called when client sends capabilities.
Definition handlers.h:185
void(* on_pong)(void *client_ctx, void *app_ctx)
Called when pong received from client.
Definition handlers.h:191
void * app_ctx
Application context (passed to all callbacks).
Definition handlers.h:212
void(* on_image_frame_h265)(uint32_t width, uint32_t height, uint8_t flags, const void *h265_data, size_t data_len, void *client_ctx, void *app_ctx)
Called when client sends H.265-encoded image frame.
Definition handlers.h:156
void(* on_image_frame_h264)(uint32_t width, uint32_t height, uint8_t flags, const void *data, size_t data_len, void *client_ctx, void *app_ctx)
Called when client sends H.264-encoded image frame.
Definition handlers.h:153
void(* on_stream_start)(uint32_t stream_types, void *client_ctx, void *app_ctx)
Called when client starts streaming.
Definition handlers.h:179
void(* on_crypto_no_encryption)(packet_type_t type, const void *payload, size_t payload_len, void *client_ctx, void *app_ctx)
Called when client sends NO_ENCRYPTION (crypto handshake rejection).
Definition handlers.h:208
void(* on_ping)(void *client_ctx, void *app_ctx)
Called when ping received (should send pong).
Definition handlers.h:188
void(* on_stream_stop)(uint32_t stream_types, void *client_ctx, void *app_ctx)
Called when client stops streaming.
Definition handlers.h:182
void(* on_client_leave)(void *client_ctx, void *app_ctx)
Called when client leaves.
Definition handlers.h:176
void(* on_remote_log)(const remote_log_packet_t *header, const char *message, void *client_ctx, void *app_ctx)
Called when remote log received from client.
Definition handlers.h:197
void(* on_error)(const error_packet_t *header, const char *message, void *client_ctx, void *app_ctx)
Called when error message received from client.
Definition handlers.h:194
void(* on_client_join)(const void *join_data, size_t data_len, void *client_ctx, void *app_ctx)
Called when client joins.
Definition handlers.h:173
void(* on_audio_opus_batch)(const void *batch_data, size_t batch_len, void *client_ctx, void *app_ctx)
Called when client sends Opus batch.
Definition handlers.h:170
void(* on_audio_batch)(const audio_batch_packet_t *header, const float *samples, size_t num_samples, void *client_ctx, void *app_ctx)
Called when client sends audio batch.
Definition handlers.h:163
void(* on_protocol_version)(const protocol_version_packet_t *version, void *client_ctx, void *app_ctx)
Called when client sends protocol version.
Definition handlers.h:146
void(* on_crypto_key_exchange_resp)(packet_type_t type, const void *payload, size_t payload_len, void *client_ctx, void *app_ctx)
Called when client sends KEY_EXCHANGE_RESP (crypto handshake).
Definition handlers.h:200
stats_peer_t * stats_peer
Owned statistics handle, destroyed with transport.
The wire metadata for one rendered ASCII frame. width and height describe the terminal grid,...
Definition packet.h:902
The metadata prefix for a batch of encoded audio samples. batch_count, total_samples,...
Definition packet.h:960
uint32_t channels
Number of audio channels (1=mono, 2=stereo).
Definition packet.h:968
uint32_t batch_count
Number of audio chunks in this batch (usually AUDIO_BATCH_COUNT = 32).
Definition packet.h:962
uint32_t sample_rate
Sample rate in Hz (e.g., 44100, 48000).
Definition packet.h:966
uint32_t total_samples
Total audio samples across all chunks (typically 8192).
Definition packet.h:964
Error packet structure carrying error code and textual description.
Definition packet.h:767
Hash table entry for packet type to handler mapping.
Definition handlers.c:54
uint8_t handler_idx
Handler index (0-based).
Definition handlers.c:56
packet_type_t key
Packet type (0 = empty slot).
Definition handlers.c:55
Describes one compressed image sent through ACIP. width, height, and pixel_format tell the decoder ho...
Definition packet.h:931
uint32_t pixel_format
Pixel format enum (1=RGB24, 2=RGBA32, 3=BGR24, 4=BGRA32).
Definition packet.h:937
uint32_t height
Image height in pixels.
Definition packet.h:935
uint32_t width
Image width in pixels.
Definition packet.h:933
Protocol version negotiation packet structure (Packet Type 1).
Definition packet.h:860
Remote log packet structure carrying log level and message text.
Definition packet.h:781
Server state packet structure.
Definition packet.h:746