ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
network/acip/handlers.c
Go to the documentation of this file.
1
13#include <ascii-chat/network/acip/handlers.h>
14#include <ascii-chat/network/acip/messages.h>
15#include <ascii-chat/network/acip/acds.h>
16#include <ascii-chat/network/packet.h>
17#include <ascii-chat/network/packet_parsing.h>
18#include <ascii-chat/audio/audio.h>
19#include <ascii-chat/util/endian.h>
20#include <ascii-chat/log/logging.h>
21#include <ascii-chat/asciichat_errno.h>
22#include <ascii-chat/common.h>
23#include <string.h>
24
25// =============================================================================
26// Handler Function Pointer Types
27// =============================================================================
28
29typedef asciichat_error_t (*acip_client_handler_func_t)(const void *payload, size_t payload_len,
30 const acip_client_callbacks_t *callbacks);
31
32typedef asciichat_error_t (*acip_server_handler_func_t)(const void *payload, size_t payload_len, void *client_ctx,
33 const acip_server_callbacks_t *callbacks);
34
35// =============================================================================
36// Packet Type to Handler Index Mapping (O(1) hash table lookup)
37// =============================================================================
38// Open-addressing hash table with linear probing. key=0 means empty slot.
39// Stores handler_idx+1 so that 0 can indicate "not found" after lookup.
40
41#define HANDLER_HASH_SIZE 32 // ~50% load factor for 14-16 entries
42#define CLIENT_HANDLER_COUNT 19 // Added 5 crypto handshake handlers
43#define SERVER_HANDLER_COUNT 19 // Added 3 crypto handshake handlers
44
48typedef struct {
49 packet_type_t key;
50 uint8_t handler_idx;
52
53// Hash function: simple modulo
54#define HANDLER_HASH(type) ((type) % HANDLER_HASH_SIZE)
55
56// Lookup function: linear probing, returns handler index or -1 if not found
57static inline int handler_hash_lookup(const handler_hash_entry_t *table, packet_type_t type) {
58 uint32_t h = HANDLER_HASH(type);
59 log_dev_every(4500 * US_PER_MS_INT, "HANDLER_HASH_LOOKUP: type=%d, hash=%u", type, h);
60 for (int i = 0; i < HANDLER_HASH_SIZE; i++) {
61 uint32_t slot = (h + i) % HANDLER_HASH_SIZE;
62 log_dev_every(4500 * US_PER_MS_INT, " Checking slot %u: key=%d, handler_idx=%d", slot, table[slot].key,
63 table[slot].handler_idx);
64 if (table[slot].key == 0) {
65 log_dev_every(4500 * US_PER_MS_INT, " Empty slot found - packet type %d not in hash table", type);
66 return -1; // empty slot = not found
67 }
68 if (table[slot].key == type) {
69 log_dev_every(4500 * US_PER_MS_INT, " Found match at slot %u, handler_idx=%d", slot, table[slot].handler_idx);
70 return table[slot].handler_idx;
71 }
72 }
73 log_debug(" No match found after checking all slots");
74 return -1;
75}
76
77// Client packet type -> handler index hash table
78// Slots computed via linear probing: hash = type % 32
79// clang-format off
80static const handler_hash_entry_t g_client_handler_hash[HANDLER_HASH_SIZE] = {
81 [0] = {PACKET_TYPE_AUDIO_BATCH, 1}, // hash(4000)=0
82 [1] = {PACKET_TYPE_AUDIO_OPUS_BATCH, 2}, // hash(4001)=1
83 [6] = {PACKET_TYPE_CRYPTO_KEY_EXCHANGE_INIT, 14}, // hash(1102)=6
84 [7] = {PACKET_TYPE_CRYPTO_AUTH_CHALLENGE, 15}, // hash(1104)=7, probed from hash(1104)=16
85 [8] = {PACKET_TYPE_CRYPTO_SERVER_AUTH_RESP, 16}, // hash(1107)=11, probed->8
86 [9] = {PACKET_TYPE_PING, 6}, // hash(5001)=9
87 [10] = {PACKET_TYPE_PONG, 7}, // hash(5002)=10
88 [11] = {PACKET_TYPE_CRYPTO_AUTH_FAILED, 17}, // hash(1108)=12, probed->11
89 [14] = {PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE, 18}, // hash(1106)=10, probed->14
90 [15] = {PACKET_TYPE_CLEAR_CONSOLE, 8}, // hash(5007)=15
91 [16] = {PACKET_TYPE_SERVER_STATE, 3}, // hash(5008)=16
92 [17] = {PACKET_TYPE_CRYPTO_REKEY_REQUEST, 9}, // hash(1201)=17
93 [18] = {PACKET_TYPE_CRYPTO_REKEY_RESPONSE, 10}, // hash(1202)=18
94 [19] = {PACKET_TYPE_ERROR_MESSAGE, 4}, // hash(2003)=19
95 [20] = {PACKET_TYPE_REMOTE_LOG, 5}, // hash(2004)=20
96 [21] = {PACKET_TYPE_ACIP_SESSION_JOINED, 13}, // hash(6005)=21
97 [24] = {PACKET_TYPE_ASCII_FRAME, 0}, // hash(3000)=24
98 [25] = {PACKET_TYPE_ACIP_WEBRTC_SDP, 11}, // hash(6009)=25
99 [26] = {PACKET_TYPE_ACIP_WEBRTC_ICE, 12}, // hash(6010)=26
100};
101// clang-format on
102
103// Server packet type -> handler index hash table
104// clang-format off
105static const handler_hash_entry_t g_server_handler_hash[HANDLER_HASH_SIZE] = {
106 [0] = {PACKET_TYPE_AUDIO_BATCH, 2}, // hash(4000)=0
107 [1] = {PACKET_TYPE_PROTOCOL_VERSION, 0}, // hash(1)=1
108 [2] = {PACKET_TYPE_AUDIO_OPUS_BATCH, 3}, // hash(4001)=1, probed->2
109 [8] = {PACKET_TYPE_CLIENT_CAPABILITIES, 4}, // hash(5000)=8
110 [9] = {PACKET_TYPE_PING, 5}, // hash(5001)=9
111 [10] = {PACKET_TYPE_PONG, 6}, // hash(5002)=10
112 [11] = {PACKET_TYPE_CLIENT_JOIN, 7}, // hash(5003)=11
113 [12] = {PACKET_TYPE_CLIENT_LEAVE, 8}, // hash(5004)=12
114 [13] = {PACKET_TYPE_STREAM_START, 9}, // hash(5005)=13
115 [14] = {PACKET_TYPE_STREAM_STOP, 10}, // hash(5006)=14
116 [15] = {PACKET_TYPE_CRYPTO_KEY_EXCHANGE_RESP, 16}, // hash(1103)=15
117 [17] = {PACKET_TYPE_CRYPTO_REKEY_REQUEST, 13}, // hash(1201)=17
118 [18] = {PACKET_TYPE_CRYPTO_REKEY_RESPONSE, 14}, // hash(1202)=18
119 [19] = {PACKET_TYPE_ERROR_MESSAGE, 12}, // hash(2003)=19
120 [20] = {PACKET_TYPE_REMOTE_LOG, 11}, // hash(2004)=20
121 [21] = {PACKET_TYPE_CRYPTO_REKEY_COMPLETE, 15}, // hash(1203)=19, probed->21
122 [22] = {PACKET_TYPE_CRYPTO_AUTH_RESPONSE, 17}, // hash(1105)=17, probed->22
123 [23] = {PACKET_TYPE_CRYPTO_NO_ENCRYPTION, 18}, // hash(1109)=21, probed->23
124 [25] = {PACKET_TYPE_IMAGE_FRAME, 1}, // hash(3001)=25
125};
126// clang-format on
127
128// =============================================================================
129// Client-Side Packet Handlers
130// =============================================================================
131
132// Forward declarations for client handlers
133static asciichat_error_t handle_client_ascii_frame(const void *payload, size_t payload_len,
134 const acip_client_callbacks_t *callbacks);
135static asciichat_error_t handle_client_audio_batch(const void *payload, size_t payload_len,
136 const acip_client_callbacks_t *callbacks);
137static asciichat_error_t handle_client_audio_opus_batch(const void *payload, size_t payload_len,
138 const acip_client_callbacks_t *callbacks);
139static asciichat_error_t handle_client_server_state(const void *payload, size_t payload_len,
140 const acip_client_callbacks_t *callbacks);
141static asciichat_error_t handle_client_error_message(const void *payload, size_t payload_len,
142 const acip_client_callbacks_t *callbacks);
143static asciichat_error_t handle_client_remote_log(const void *payload, size_t payload_len,
144 const acip_client_callbacks_t *callbacks);
145static asciichat_error_t handle_client_ping(const void *payload, size_t payload_len,
146 const acip_client_callbacks_t *callbacks);
147static asciichat_error_t handle_client_pong(const void *payload, size_t payload_len,
148 const acip_client_callbacks_t *callbacks);
149static asciichat_error_t handle_client_clear_console(const void *payload, size_t payload_len,
150 const acip_client_callbacks_t *callbacks);
151static asciichat_error_t handle_client_crypto_rekey_request(const void *payload, size_t payload_len,
152 const acip_client_callbacks_t *callbacks);
153static asciichat_error_t handle_client_crypto_rekey_response(const void *payload, size_t payload_len,
154 const acip_client_callbacks_t *callbacks);
155static asciichat_error_t handle_client_webrtc_sdp(const void *payload, size_t payload_len,
156 const acip_client_callbacks_t *callbacks);
157static asciichat_error_t handle_client_webrtc_ice(const void *payload, size_t payload_len,
158 const acip_client_callbacks_t *callbacks);
159static asciichat_error_t handle_client_session_joined(const void *payload, size_t payload_len,
160 const acip_client_callbacks_t *callbacks);
161static asciichat_error_t handle_client_crypto_key_exchange_init(const void *payload, size_t payload_len,
162 const acip_client_callbacks_t *callbacks);
163static asciichat_error_t handle_client_crypto_auth_challenge(const void *payload, size_t payload_len,
164 const acip_client_callbacks_t *callbacks);
165static asciichat_error_t handle_client_crypto_server_auth_resp(const void *payload, size_t payload_len,
166 const acip_client_callbacks_t *callbacks);
167static asciichat_error_t handle_client_crypto_auth_failed(const void *payload, size_t payload_len,
168 const acip_client_callbacks_t *callbacks);
169static asciichat_error_t handle_client_crypto_handshake_complete(const void *payload, size_t payload_len,
170 const acip_client_callbacks_t *callbacks);
171
172// Client handler dispatch table (indexed by client_handler_index())
173static const acip_client_handler_func_t g_client_handlers[CLIENT_HANDLER_COUNT] = {
174 handle_client_ascii_frame, // 0
175 handle_client_audio_batch, // 1
176 handle_client_audio_opus_batch, // 2
177 handle_client_server_state, // 3
178 handle_client_error_message, // 4
179 handle_client_remote_log, // 5
180 handle_client_ping, // 6
181 handle_client_pong, // 7
182 handle_client_clear_console, // 8
183 handle_client_crypto_rekey_request, // 9
184 handle_client_crypto_rekey_response, // 10
185 handle_client_webrtc_sdp, // 11
186 handle_client_webrtc_ice, // 12
187 handle_client_session_joined, // 13
188 handle_client_crypto_key_exchange_init, // 14
189 handle_client_crypto_auth_challenge, // 15
190 handle_client_crypto_server_auth_resp, // 16
191 handle_client_crypto_auth_failed, // 17
192 handle_client_crypto_handshake_complete, // 18
193};
194
195asciichat_error_t acip_handle_client_packet(acip_transport_t *transport, packet_type_t type, const void *payload,
196 size_t payload_len, const acip_client_callbacks_t *callbacks) {
197 if (!transport || !callbacks) {
198 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid transport or callbacks");
199 }
200 (void)transport;
201
202 // O(1) dispatch via hash table lookup
203 int idx = handler_hash_lookup(g_client_handler_hash, type);
204 if (idx < 0) {
205 return SET_ERRNO(ERROR_INVALID_PARAM, "Unhandled client packet type: %d", type);
206 }
207
208 return g_client_handlers[idx](payload, payload_len, callbacks);
209}
210
211// =============================================================================
212// Client Handler Implementations (extracted from switch cases)
213// =============================================================================
214
215static asciichat_error_t handle_client_ascii_frame(const void *payload, size_t payload_len,
216 const acip_client_callbacks_t *callbacks) {
217 if (!callbacks->on_ascii_frame) {
218 return ASCIICHAT_OK;
219 }
220
221 if (payload_len < sizeof(ascii_frame_packet_t)) {
222 return SET_ERRNO(ERROR_INVALID_PARAM, "ASCII_FRAME payload too small");
223 }
224
225 // Extract header
226 ascii_frame_packet_t header;
227 memcpy(&header, payload, sizeof(header));
228
229 // Convert from network byte order
230 header.width = NET_TO_HOST_U32(header.width);
231 header.height = NET_TO_HOST_U32(header.height);
232 header.original_size = NET_TO_HOST_U32(header.original_size);
233 header.compressed_size = NET_TO_HOST_U32(header.compressed_size);
234 header.checksum = NET_TO_HOST_U32(header.checksum);
235 header.flags = NET_TO_HOST_U32(header.flags);
236
237 // Get frame data (after header)
238 const void *frame_data = (const uint8_t *)payload + sizeof(ascii_frame_packet_t);
239 size_t frame_data_len = payload_len - sizeof(ascii_frame_packet_t);
240
241 // Validate frame dimensions to prevent DoS and buffer overflow attacks
242 if (header.width == 0 || header.height == 0) {
243 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid frame dimensions: %ux%u (width and height must be > 0)",
244 header.width, header.height);
245 }
246
247 // Sanity check: prevent unreasonably large frames (e.g., > 16K resolution)
248 // This protects against resource exhaustion attacks
249 const uint32_t MAX_WIDTH = 16384; // 16K
250 const uint32_t MAX_HEIGHT = 16384; // 16K
251 if (header.width > MAX_WIDTH || header.height > MAX_HEIGHT) {
252 return SET_ERRNO(ERROR_INVALID_PARAM, "Frame dimensions too large: %ux%u (max: %ux%u)", header.width, header.height,
253 MAX_WIDTH, MAX_HEIGHT);
254 }
255
256 callbacks->on_ascii_frame(&header, frame_data, frame_data_len, callbacks->app_ctx);
257 return ASCIICHAT_OK;
258}
259
260static asciichat_error_t handle_client_audio_batch(const void *payload, size_t payload_len,
261 const acip_client_callbacks_t *callbacks) {
262 if (!callbacks->on_audio_batch) {
263 return ASCIICHAT_OK;
264 }
265
266 if (payload_len < sizeof(audio_batch_packet_t)) {
267 return SET_ERRNO(ERROR_INVALID_PARAM, "AUDIO_BATCH payload too small");
268 }
269
270 // Parse batch header
271 const audio_batch_packet_t *batch_header = (const audio_batch_packet_t *)payload;
272 uint32_t batch_count = NET_TO_HOST_U32(batch_header->batch_count);
273 uint32_t total_samples = NET_TO_HOST_U32(batch_header->total_samples);
274 uint32_t sample_rate = NET_TO_HOST_U32(batch_header->sample_rate);
275 uint32_t channels = NET_TO_HOST_U32(batch_header->channels);
276
277 // Validate sample rate and channels
278 // Supported sample rates: 8000, 16000, 24000, 32000, 44100, 48000, 96000, 192000 Hz
279 if (sample_rate == 0 || (sample_rate < 8000 || sample_rate > 192000) ||
280 (sample_rate != 8000 && sample_rate != 16000 && sample_rate != 24000 && sample_rate != 32000 &&
281 sample_rate != 44100 && sample_rate != 48000 && sample_rate != 96000 && sample_rate != 192000)) {
282 return SET_ERRNO(
283 ERROR_INVALID_PARAM,
284 "Invalid audio sample rate: %u Hz (expected: 8000, 16000, 24000, 32000, 44100, 48000, 96000, or 192000)",
285 sample_rate);
286 }
287
288 // Validate channel count (1-8 channels supported)
289 if (channels == 0 || channels > 8) {
290 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid audio channel count: %u (expected: 1-8)", channels);
291 }
292
293 // Validate batch count
294 (void)batch_count;
295
296 // Validate size
297 size_t expected_size = sizeof(audio_batch_packet_t) + (total_samples * sizeof(uint32_t));
298 if (payload_len != expected_size) {
299 return SET_ERRNO(ERROR_INVALID_PARAM, "AUDIO_BATCH size mismatch");
300 }
301
302 // Extract quantized samples
303 const uint8_t *samples_ptr = (const uint8_t *)payload + sizeof(audio_batch_packet_t);
304
305 // Dequantize samples to float
306 float *samples = SAFE_MALLOC(total_samples * sizeof(float), float *);
307 if (!samples) {
308 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate audio batch buffer");
309 }
310
311 asciichat_error_t result = audio_dequantize_samples(samples_ptr, total_samples, samples);
312 if (result != ASCIICHAT_OK) {
313 SAFE_FREE(samples);
314 return result;
315 }
316
317 // Copy header for callback
318 audio_batch_packet_t header_copy = *batch_header;
319 header_copy.batch_count = batch_count;
320 header_copy.total_samples = total_samples;
321 header_copy.sample_rate = sample_rate;
322 header_copy.channels = channels;
323
324 callbacks->on_audio_batch(&header_copy, samples, total_samples, callbacks->app_ctx);
325
326 SAFE_FREE(samples);
327 return ASCIICHAT_OK;
328}
329
330static asciichat_error_t handle_client_audio_opus_batch(const void *payload, size_t payload_len,
331 const acip_client_callbacks_t *callbacks) {
332 if (!callbacks->on_audio_opus_batch) {
333 return ASCIICHAT_OK;
334 }
335
336 // Opus batch data (header + sizes + opus frames)
337 callbacks->on_audio_opus_batch(payload, payload_len, callbacks->app_ctx);
338 return ASCIICHAT_OK;
339}
340
341static asciichat_error_t handle_client_server_state(const void *payload, size_t payload_len,
342 const acip_client_callbacks_t *callbacks) {
343 if (!callbacks->on_server_state) {
344 return ASCIICHAT_OK;
345 }
346
347 if (payload_len != sizeof(server_state_packet_t)) {
348 return SET_ERRNO(ERROR_INVALID_PARAM, "SERVER_STATE size mismatch");
349 }
350
351 callbacks->on_server_state((const server_state_packet_t *)payload, callbacks->app_ctx);
352 return ASCIICHAT_OK;
353}
354
355static asciichat_error_t handle_client_error_message(const void *payload, size_t payload_len,
356 const acip_client_callbacks_t *callbacks) {
357 if (!callbacks->on_error) {
358 return ASCIICHAT_OK;
359 }
360
361 if (payload_len < sizeof(error_packet_t)) {
362 return SET_ERRNO(ERROR_INVALID_PARAM, "ERROR_MESSAGE payload too small");
363 }
364
365 const error_packet_t *header = (const error_packet_t *)payload;
366 const char *message = (const char *)payload + sizeof(error_packet_t);
367 size_t msg_len = payload_len - sizeof(error_packet_t);
368
369 // Ensure null-terminated message
370 char msg_buffer[MAX_ERROR_MESSAGE_LENGTH + 1];
371 size_t copy_len = msg_len < MAX_ERROR_MESSAGE_LENGTH ? msg_len : MAX_ERROR_MESSAGE_LENGTH;
372 memcpy(msg_buffer, message, copy_len);
373 msg_buffer[copy_len] = '\0';
374
375 callbacks->on_error(header, msg_buffer, callbacks->app_ctx);
376 return ASCIICHAT_OK;
377}
378
379static asciichat_error_t handle_client_remote_log(const void *payload, size_t payload_len,
380 const acip_client_callbacks_t *callbacks) {
381 if (!callbacks->on_remote_log) {
382 return ASCIICHAT_OK;
383 }
384
385 if (payload_len < sizeof(remote_log_packet_t)) {
386 return SET_ERRNO(ERROR_INVALID_PARAM, "REMOTE_LOG payload too small");
387 }
388
389 const remote_log_packet_t *header = (const remote_log_packet_t *)payload;
390 const char *message = (const char *)payload + sizeof(remote_log_packet_t);
391 size_t msg_len = payload_len - sizeof(remote_log_packet_t);
392
393 // Ensure null-terminated message
394 char msg_buffer[512];
395 size_t copy_len = msg_len < sizeof(msg_buffer) - 1 ? msg_len : sizeof(msg_buffer) - 1;
396 memcpy(msg_buffer, message, copy_len);
397 msg_buffer[copy_len] = '\0';
398
399 callbacks->on_remote_log(header, msg_buffer, callbacks->app_ctx);
400 return ASCIICHAT_OK;
401}
402
403static asciichat_error_t handle_client_ping(const void *payload, size_t payload_len,
404 const acip_client_callbacks_t *callbacks) {
405 (void)payload;
406 (void)payload_len;
407
408 if (callbacks->on_ping) {
409 callbacks->on_ping(callbacks->app_ctx);
410 }
411 return ASCIICHAT_OK;
412}
413
414static asciichat_error_t handle_client_pong(const void *payload, size_t payload_len,
415 const acip_client_callbacks_t *callbacks) {
416 (void)payload;
417 (void)payload_len;
418
419 if (callbacks->on_pong) {
420 callbacks->on_pong(callbacks->app_ctx);
421 }
422 return ASCIICHAT_OK;
423}
424
425static asciichat_error_t handle_client_clear_console(const void *payload, size_t payload_len,
426 const acip_client_callbacks_t *callbacks) {
427 (void)payload;
428 (void)payload_len;
429
430 if (callbacks->on_clear_console) {
431 callbacks->on_clear_console(callbacks->app_ctx);
432 }
433 return ASCIICHAT_OK;
434}
435
436static asciichat_error_t handle_client_crypto_rekey_request(const void *payload, size_t payload_len,
437 const acip_client_callbacks_t *callbacks) {
438 if (callbacks->on_crypto_rekey_request) {
439 callbacks->on_crypto_rekey_request(payload, payload_len, callbacks->app_ctx);
440 }
441 return ASCIICHAT_OK;
442}
443
444static asciichat_error_t handle_client_crypto_rekey_response(const void *payload, size_t payload_len,
445 const acip_client_callbacks_t *callbacks) {
446 if (callbacks->on_crypto_rekey_response) {
447 callbacks->on_crypto_rekey_response(payload, payload_len, callbacks->app_ctx);
448 }
449 return ASCIICHAT_OK;
450}
451
452static asciichat_error_t handle_client_webrtc_sdp(const void *payload, size_t payload_len,
453 const acip_client_callbacks_t *callbacks) {
454 if (!callbacks->on_webrtc_sdp) {
455 return ASCIICHAT_OK;
456 }
457
458 if (payload_len < sizeof(acip_webrtc_sdp_t)) {
459 return SET_ERRNO(ERROR_INVALID_PARAM, "WEBRTC_SDP payload too small");
460 }
461
462 const acip_webrtc_sdp_t *sdp = (const acip_webrtc_sdp_t *)payload;
463 callbacks->on_webrtc_sdp(sdp, payload_len, callbacks->app_ctx);
464 return ASCIICHAT_OK;
465}
466
467static asciichat_error_t handle_client_webrtc_ice(const void *payload, size_t payload_len,
468 const acip_client_callbacks_t *callbacks) {
469 if (!callbacks->on_webrtc_ice) {
470 return ASCIICHAT_OK;
471 }
472
473 if (payload_len < sizeof(acip_webrtc_ice_t)) {
474 return SET_ERRNO(ERROR_INVALID_PARAM, "WEBRTC_ICE payload too small");
475 }
476
477 const acip_webrtc_ice_t *ice = (const acip_webrtc_ice_t *)payload;
478 callbacks->on_webrtc_ice(ice, payload_len, callbacks->app_ctx);
479 return ASCIICHAT_OK;
480}
481
482static asciichat_error_t handle_client_session_joined(const void *payload, size_t payload_len,
483 const acip_client_callbacks_t *callbacks) {
484 if (!callbacks->on_session_joined) {
485 return ASCIICHAT_OK;
486 }
487
488 if (payload_len < sizeof(acip_session_joined_t)) {
489 return SET_ERRNO(ERROR_INVALID_PARAM, "SESSION_JOINED payload too small (got %zu, need %zu)", payload_len,
490 sizeof(acip_session_joined_t));
491 }
492
493 // Parse the session_joined response
494 const acip_session_joined_t *joined = (const acip_session_joined_t *)payload;
495
496 // Log the result
497 if (joined->success) {
498 log_debug("Session join succeeded: session_id=%.16s, participant_id=%.16s, server=%s:%u, type=%s",
499 (const char *)joined->session_id, (const char *)joined->participant_id, joined->server_address,
500 joined->server_port, joined->session_type == 1 ? "WebRTC" : "DirectTCP");
501 } else {
502 log_warn("Session join failed: error %d: %s", joined->error_code, joined->error_message);
503 }
504
505 // Dispatch to application callback
506 callbacks->on_session_joined(joined, callbacks->app_ctx);
507 return ASCIICHAT_OK;
508}
509
510static asciichat_error_t handle_client_crypto_key_exchange_init(const void *payload, size_t payload_len,
511 const acip_client_callbacks_t *callbacks) {
512 if (!callbacks->on_crypto_key_exchange_init) {
513 return ASCIICHAT_OK;
514 }
515
516 // Dispatch to application callback with packet type
517 callbacks->on_crypto_key_exchange_init(PACKET_TYPE_CRYPTO_KEY_EXCHANGE_INIT, payload, payload_len,
518 callbacks->app_ctx);
519 return ASCIICHAT_OK;
520}
521
522static asciichat_error_t handle_client_crypto_auth_challenge(const void *payload, size_t payload_len,
523 const acip_client_callbacks_t *callbacks) {
524 if (!callbacks->on_crypto_auth_challenge) {
525 return ASCIICHAT_OK;
526 }
527
528 // Dispatch to application callback with packet type
529 callbacks->on_crypto_auth_challenge(PACKET_TYPE_CRYPTO_AUTH_CHALLENGE, payload, payload_len, callbacks->app_ctx);
530 return ASCIICHAT_OK;
531}
532
533static asciichat_error_t handle_client_crypto_server_auth_resp(const void *payload, size_t payload_len,
534 const acip_client_callbacks_t *callbacks) {
535 if (!callbacks->on_crypto_server_auth_resp) {
536 return ASCIICHAT_OK;
537 }
538
539 // Dispatch to application callback with packet type
540 callbacks->on_crypto_server_auth_resp(PACKET_TYPE_CRYPTO_SERVER_AUTH_RESP, payload, payload_len, callbacks->app_ctx);
541 return ASCIICHAT_OK;
542}
543
544static asciichat_error_t handle_client_crypto_auth_failed(const void *payload, size_t payload_len,
545 const acip_client_callbacks_t *callbacks) {
546 if (!callbacks->on_crypto_auth_failed) {
547 return ASCIICHAT_OK;
548 }
549
550 // Dispatch to application callback with packet type
551 callbacks->on_crypto_auth_failed(PACKET_TYPE_CRYPTO_AUTH_FAILED, payload, payload_len, callbacks->app_ctx);
552 return ASCIICHAT_OK;
553}
554
555static asciichat_error_t handle_client_crypto_handshake_complete(const void *payload, size_t payload_len,
556 const acip_client_callbacks_t *callbacks) {
557 if (!callbacks->on_crypto_handshake_complete) {
558 return ASCIICHAT_OK;
559 }
560
561 // Dispatch to application callback with packet type
562 callbacks->on_crypto_handshake_complete(PACKET_TYPE_CRYPTO_HANDSHAKE_COMPLETE, payload, payload_len,
563 callbacks->app_ctx);
564 return ASCIICHAT_OK;
565}
566
567// =============================================================================
568// Server-Side Packet Handlers
569// =============================================================================
570
571// Forward declarations for server handlers
572static asciichat_error_t handle_server_image_frame(const void *payload, size_t payload_len, void *client_ctx,
573 const acip_server_callbacks_t *callbacks);
574static asciichat_error_t handle_server_audio_batch(const void *payload, size_t payload_len, void *client_ctx,
575 const acip_server_callbacks_t *callbacks);
576static asciichat_error_t handle_server_audio_opus_batch(const void *payload, size_t payload_len, void *client_ctx,
577 const acip_server_callbacks_t *callbacks);
578static asciichat_error_t handle_server_client_join(const void *payload, size_t payload_len, void *client_ctx,
579 const acip_server_callbacks_t *callbacks);
580static asciichat_error_t handle_server_client_leave(const void *payload, size_t payload_len, void *client_ctx,
581 const acip_server_callbacks_t *callbacks);
582static asciichat_error_t handle_server_stream_start(const void *payload, size_t payload_len, void *client_ctx,
583 const acip_server_callbacks_t *callbacks);
584static asciichat_error_t handle_server_stream_stop(const void *payload, size_t payload_len, void *client_ctx,
585 const acip_server_callbacks_t *callbacks);
586static asciichat_error_t handle_server_capabilities(const void *payload, size_t payload_len, void *client_ctx,
587 const acip_server_callbacks_t *callbacks);
588static asciichat_error_t handle_server_ping(const void *payload, size_t payload_len, void *client_ctx,
589 const acip_server_callbacks_t *callbacks);
590static asciichat_error_t handle_server_remote_log(const void *payload, size_t payload_len, void *client_ctx,
591 const acip_server_callbacks_t *callbacks);
592static asciichat_error_t handle_server_protocol_version(const void *payload, size_t payload_len, void *client_ctx,
593 const acip_server_callbacks_t *callbacks);
594static asciichat_error_t handle_server_pong(const void *payload, size_t payload_len, void *client_ctx,
595 const acip_server_callbacks_t *callbacks);
596static asciichat_error_t handle_server_error_message(const void *payload, size_t payload_len, void *client_ctx,
597 const acip_server_callbacks_t *callbacks);
598static asciichat_error_t handle_server_crypto_rekey_request(const void *payload, size_t payload_len, void *client_ctx,
599 const acip_server_callbacks_t *callbacks);
600static asciichat_error_t handle_server_crypto_rekey_response(const void *payload, size_t payload_len, void *client_ctx,
601 const acip_server_callbacks_t *callbacks);
602static asciichat_error_t handle_server_crypto_rekey_complete(const void *payload, size_t payload_len, void *client_ctx,
603 const acip_server_callbacks_t *callbacks);
604static asciichat_error_t handle_server_crypto_key_exchange_resp(const void *payload, size_t payload_len,
605 void *client_ctx,
606 const acip_server_callbacks_t *callbacks);
607static asciichat_error_t handle_server_crypto_auth_response(const void *payload, size_t payload_len, void *client_ctx,
608 const acip_server_callbacks_t *callbacks);
609static asciichat_error_t handle_server_crypto_no_encryption(const void *payload, size_t payload_len, void *client_ctx,
610 const acip_server_callbacks_t *callbacks);
611
612// Server handler dispatch table (indexed by server_handler_index())
613static const acip_server_handler_func_t g_server_handlers[SERVER_HANDLER_COUNT] = {
614 handle_server_protocol_version, // 0
615 handle_server_image_frame, // 1
616 handle_server_audio_batch, // 2
617 handle_server_audio_opus_batch, // 3
618 handle_server_capabilities, // 4
619 handle_server_ping, // 5
620 handle_server_pong, // 6
621 handle_server_client_join, // 7
622 handle_server_client_leave, // 8
623 handle_server_stream_start, // 9
624 handle_server_stream_stop, // 10
625 handle_server_remote_log, // 11
626 handle_server_error_message, // 12
627 handle_server_crypto_rekey_request, // 13
628 handle_server_crypto_rekey_response, // 14
629 handle_server_crypto_rekey_complete, // 15
630 handle_server_crypto_key_exchange_resp, // 16
631 handle_server_crypto_auth_response, // 17
632 handle_server_crypto_no_encryption, // 18
633};
634
635asciichat_error_t acip_handle_server_packet(acip_transport_t *transport, packet_type_t type, const void *payload,
636 size_t payload_len, void *client_ctx,
637 const acip_server_callbacks_t *callbacks) {
638 if (!transport || !callbacks) {
639 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid transport or callbacks");
640 }
641 (void)transport;
642
643 // DEBUG: Log all packet types received
644 log_info("ACIP_HANDLE: Received packet type=%d (0x%04x), payload_len=%zu", type, type, payload_len);
645
646 // O(1) dispatch via hash table lookup
647 int idx = handler_hash_lookup(g_server_handler_hash, type);
648 if (idx < 0) {
649 log_error("ACIP_HANDLER_NOT_FOUND: No handler for packet type=%d (0x%04x)", type, type);
650 return SET_ERRNO(ERROR_INVALID_PARAM, "Unhandled server packet type: %d", type);
651 }
652
653 log_info("ACIP_DISPATCH_HANDLER: type=%d, handler_idx=%d, calling handler...", type, idx);
654 asciichat_error_t result = g_server_handlers[idx](payload, payload_len, client_ctx, callbacks);
655 log_info("ACIP_HANDLER_RESULT: type=%d, result=%d", type, result);
656 return result;
657}
658
659// =============================================================================
660// Server Handler Implementations (extracted from switch cases)
661// =============================================================================
662
663static asciichat_error_t handle_server_image_frame(const void *payload, size_t payload_len, void *client_ctx,
664 const acip_server_callbacks_t *callbacks) {
665 log_info("ACIP_IMAGE_FRAME_HANDLER: Received IMAGE_FRAME packet, payload_len=%zu, client_ctx=%p", payload_len,
666 client_ctx);
667
668 if (!callbacks->on_image_frame) {
669 log_warn("ACIP_IMAGE_FRAME_HANDLER: No callback registered for on_image_frame");
670 return ASCIICHAT_OK;
671 }
672
673 log_info("ACIP_IMAGE_FRAME_HANDLER: Callback is registered, checking payload size (need %zu bytes)",
674 sizeof(image_frame_packet_t));
675
676 if (payload_len < sizeof(image_frame_packet_t)) {
677 log_error("ACIP_IMAGE_FRAME_HANDLER: Payload too small: %zu bytes (need %zu)", payload_len,
678 sizeof(image_frame_packet_t));
679 return SET_ERRNO(ERROR_INVALID_PARAM, "IMAGE_FRAME payload too small: %zu bytes (need %zu)", payload_len,
680 sizeof(image_frame_packet_t));
681 }
682
683 // Debug: Log raw payload bytes
684 const uint8_t *payload_bytes = (const uint8_t *)payload;
685 log_dev_every(4500 * US_PER_MS_INT, "IMAGE_FRAME payload (%zu bytes):", payload_len);
686 char hex_buf[512];
687 size_t hex_pos = 0;
688 size_t max_bytes = (payload_len < 48) ? payload_len : 48;
689 for (size_t i = 0; i < max_bytes && hex_pos < sizeof(hex_buf) - 4; i++) {
690 hex_pos += snprintf(hex_buf + hex_pos, sizeof(hex_buf) - hex_pos, "%02x ", payload_bytes[i]);
691 }
692 log_dev_every(4500 * US_PER_MS_INT, " First bytes: %s", hex_buf);
693
694 // Extract header
695 image_frame_packet_t header;
696 memcpy(&header, payload, sizeof(header));
697
698 // Debug: Log raw header before byte order conversion
699 log_dev_every(4500 * US_PER_MS_INT, "IMAGE_FRAME header (raw network order):");
700 log_dev_every(4500 * US_PER_MS_INT, " width=0x%08x height=0x%08x pixel_format=0x%08x compressed_size=0x%08x",
701 header.width, header.height, header.pixel_format, header.compressed_size);
702
703 // Convert from network byte order
704 header.width = NET_TO_HOST_U32(header.width);
705 header.height = NET_TO_HOST_U32(header.height);
706 header.pixel_format = NET_TO_HOST_U32(header.pixel_format);
707 header.compressed_size = NET_TO_HOST_U32(header.compressed_size);
708 header.checksum = NET_TO_HOST_U32(header.checksum);
709 header.timestamp = NET_TO_HOST_U32(header.timestamp);
710
711 // Debug: Log header after byte order conversion
712 log_dev_every(4500 * US_PER_MS_INT, "IMAGE_FRAME header (after host byte order):");
713 log_dev_every(4500 * US_PER_MS_INT, " width=%u height=%u pixel_format=%u compressed_size=%u", header.width,
714 header.height, header.pixel_format, header.compressed_size);
715
716 // Get pixel data (after header)
717 const void *pixel_data = (const uint8_t *)payload + sizeof(image_frame_packet_t);
718 size_t pixel_data_len = payload_len - sizeof(image_frame_packet_t);
719
720 // Validate frame dimensions to prevent DoS and buffer overflow attacks
721 if (header.width == 0 || header.height == 0) {
722 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid image dimensions: %ux%u (width and height must be > 0)",
723 header.width, header.height);
724 }
725
726 // Sanity check: prevent unreasonably large frames (e.g., > 8K resolution for RGB)
727 // This protects against resource exhaustion attacks
728 const uint32_t MAX_WIDTH = 8192; // 8K for RGB data
729 const uint32_t MAX_HEIGHT = 8192; // 8K for RGB data
730 if (header.width > MAX_WIDTH || header.height > MAX_HEIGHT) {
731 return SET_ERRNO(ERROR_INVALID_PARAM, "Image dimensions too large: %ux%u (max: %ux%u)", header.width, header.height,
732 MAX_WIDTH, MAX_HEIGHT);
733 }
734
735 // Validate pixel format
736 // Valid formats: RGB24 (3), RGBA32 (4), YUV420 (1.5)
737 if (header.pixel_format == 0 || header.pixel_format > 4) {
738 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid pixel format: %u (expected: 1-4)", header.pixel_format);
739 }
740
741 callbacks->on_image_frame(&header, pixel_data, pixel_data_len, client_ctx, callbacks->app_ctx);
742 return ASCIICHAT_OK;
743}
744
745static asciichat_error_t handle_server_audio_batch(const void *payload, size_t payload_len, void *client_ctx,
746 const acip_server_callbacks_t *callbacks) {
747 if (!callbacks->on_audio_batch) {
748 return ASCIICHAT_OK;
749 }
750
751 if (payload_len < sizeof(audio_batch_packet_t)) {
752 return SET_ERRNO(ERROR_INVALID_PARAM, "AUDIO_BATCH payload too small");
753 }
754
755 // Parse batch header
756 const audio_batch_packet_t *batch_header = (const audio_batch_packet_t *)payload;
757 uint32_t batch_count = NET_TO_HOST_U32(batch_header->batch_count);
758 uint32_t total_samples = NET_TO_HOST_U32(batch_header->total_samples);
759 uint32_t sample_rate = NET_TO_HOST_U32(batch_header->sample_rate);
760 uint32_t channels = NET_TO_HOST_U32(batch_header->channels);
761
762 // Validate size
763 size_t expected_size = sizeof(audio_batch_packet_t) + (total_samples * sizeof(uint32_t));
764 if (payload_len != expected_size) {
765 return SET_ERRNO(ERROR_INVALID_PARAM, "AUDIO_BATCH size mismatch");
766 }
767
768 // Extract quantized samples
769 const uint8_t *samples_ptr = (const uint8_t *)payload + sizeof(audio_batch_packet_t);
770
771 // Dequantize samples to float
772 float *samples = SAFE_MALLOC(total_samples * sizeof(float), float *);
773 if (!samples) {
774 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate audio batch buffer");
775 }
776
777 asciichat_error_t result = audio_dequantize_samples(samples_ptr, total_samples, samples);
778 if (result != ASCIICHAT_OK) {
779 SAFE_FREE(samples);
780 return result;
781 }
782
783 // Copy header for callback
784 audio_batch_packet_t header_copy = *batch_header;
785 header_copy.batch_count = batch_count;
786 header_copy.total_samples = total_samples;
787 header_copy.sample_rate = sample_rate;
788 header_copy.channels = channels;
789
790 callbacks->on_audio_batch(&header_copy, samples, total_samples, client_ctx, callbacks->app_ctx);
791
792 SAFE_FREE(samples);
793 return ASCIICHAT_OK;
794}
795
796static asciichat_error_t handle_server_audio_opus_batch(const void *payload, size_t payload_len, void *client_ctx,
797 const acip_server_callbacks_t *callbacks) {
798 if (!callbacks->on_audio_opus_batch) {
799 return ASCIICHAT_OK;
800 }
801
802 callbacks->on_audio_opus_batch(payload, payload_len, client_ctx, callbacks->app_ctx);
803 return ASCIICHAT_OK;
804}
805
806static asciichat_error_t handle_server_client_join(const void *payload, size_t payload_len, void *client_ctx,
807 const acip_server_callbacks_t *callbacks) {
808 if (!callbacks->on_client_join) {
809 return ASCIICHAT_OK;
810 }
811
812 callbacks->on_client_join(payload, payload_len, client_ctx, callbacks->app_ctx);
813 return ASCIICHAT_OK;
814}
815
816static asciichat_error_t handle_server_client_leave(const void *payload, size_t payload_len, void *client_ctx,
817 const acip_server_callbacks_t *callbacks) {
818 (void)payload;
819 (void)payload_len;
820
821 if (!callbacks->on_client_leave) {
822 return ASCIICHAT_OK;
823 }
824
825 callbacks->on_client_leave(client_ctx, callbacks->app_ctx);
826 return ASCIICHAT_OK;
827}
828
829static asciichat_error_t handle_server_stream_start(const void *payload, size_t payload_len, void *client_ctx,
830 const acip_server_callbacks_t *callbacks) {
831 if (!callbacks->on_stream_start) {
832 return ASCIICHAT_OK;
833 }
834
835 if (payload_len < sizeof(uint32_t)) {
836 return SET_ERRNO(ERROR_INVALID_PARAM, "STREAM_START payload too small (got %zu, expected %zu)", payload_len,
837 sizeof(uint32_t));
838 }
839
840 // Read stream_types as uint32_t (network byte order)
841 uint32_t stream_types_net;
842 memcpy(&stream_types_net, payload, sizeof(uint32_t));
843 uint32_t stream_types = NET_TO_HOST_U32(stream_types_net);
844
845 callbacks->on_stream_start(stream_types, client_ctx, callbacks->app_ctx);
846 return ASCIICHAT_OK;
847}
848
849static asciichat_error_t handle_server_stream_stop(const void *payload, size_t payload_len, void *client_ctx,
850 const acip_server_callbacks_t *callbacks) {
851 if (!callbacks->on_stream_stop) {
852 return ASCIICHAT_OK;
853 }
854
855 if (payload_len < sizeof(uint32_t)) {
856 return SET_ERRNO(ERROR_INVALID_PARAM, "STREAM_STOP payload too small (got %zu, expected %zu)", payload_len,
857 sizeof(uint32_t));
858 }
859
860 // Read stream_types as uint32_t (network byte order)
861 uint32_t stream_types_net;
862 memcpy(&stream_types_net, payload, sizeof(uint32_t));
863 uint32_t stream_types = NET_TO_HOST_U32(stream_types_net);
864
865 callbacks->on_stream_stop(stream_types, client_ctx, callbacks->app_ctx);
866 return ASCIICHAT_OK;
867}
868
869static asciichat_error_t handle_server_capabilities(const void *payload, size_t payload_len, void *client_ctx,
870 const acip_server_callbacks_t *callbacks) {
871 log_debug("handle_server_capabilities: payload_len=%zu, client_ctx=%p, callbacks=%p", payload_len, client_ctx,
872 (const void *)callbacks);
873
874 if (!callbacks->on_capabilities) {
875 log_error("on_capabilities callback is NULL!");
876 return ASCIICHAT_OK;
877 }
878
879 log_error("Calling on_capabilities callback...");
880 callbacks->on_capabilities(payload, payload_len, client_ctx, callbacks->app_ctx);
881 log_error("on_capabilities callback returned");
882 return ASCIICHAT_OK;
883}
884
885static asciichat_error_t handle_server_ping(const void *payload, size_t payload_len, void *client_ctx,
886 const acip_server_callbacks_t *callbacks) {
887 (void)payload;
888 (void)payload_len;
889
890 if (callbacks->on_ping) {
891 callbacks->on_ping(client_ctx, callbacks->app_ctx);
892 }
893 return ASCIICHAT_OK;
894}
895
896static asciichat_error_t handle_server_remote_log(const void *payload, size_t payload_len, void *client_ctx,
897 const acip_server_callbacks_t *callbacks) {
898 if (!callbacks->on_remote_log) {
899 return ASCIICHAT_OK;
900 }
901
902 if (payload_len < sizeof(remote_log_packet_t)) {
903 return SET_ERRNO(ERROR_INVALID_PARAM, "REMOTE_LOG payload too small");
904 }
905
906 const remote_log_packet_t *header = (const remote_log_packet_t *)payload;
907 const char *message = (const char *)payload + sizeof(remote_log_packet_t);
908 size_t msg_len = payload_len - sizeof(remote_log_packet_t);
909
910 // Ensure null-terminated message
911 char msg_buffer[512];
912 size_t copy_len = msg_len < sizeof(msg_buffer) - 1 ? msg_len : sizeof(msg_buffer) - 1;
913 memcpy(msg_buffer, message, copy_len);
914 msg_buffer[copy_len] = '\0';
915
916 callbacks->on_remote_log(header, msg_buffer, client_ctx, callbacks->app_ctx);
917 return ASCIICHAT_OK;
918}
919
920static asciichat_error_t handle_server_protocol_version(const void *payload, size_t payload_len, void *client_ctx,
921 const acip_server_callbacks_t *callbacks) {
922 if (!callbacks->on_protocol_version) {
923 return ASCIICHAT_OK;
924 }
925
926 if (payload_len != sizeof(protocol_version_packet_t)) {
927 return SET_ERRNO(ERROR_INVALID_PARAM, "PROTOCOL_VERSION size mismatch");
928 }
929
930 callbacks->on_protocol_version((const protocol_version_packet_t *)payload, client_ctx, callbacks->app_ctx);
931 return ASCIICHAT_OK;
932}
933
934static asciichat_error_t handle_server_pong(const void *payload, size_t payload_len, void *client_ctx,
935 const acip_server_callbacks_t *callbacks) {
936 (void)payload;
937 (void)payload_len;
938
939 if (callbacks->on_pong) {
940 callbacks->on_pong(client_ctx, callbacks->app_ctx);
941 }
942 return ASCIICHAT_OK;
943}
944
945static asciichat_error_t handle_server_error_message(const void *payload, size_t payload_len, void *client_ctx,
946 const acip_server_callbacks_t *callbacks) {
947 if (!callbacks->on_error) {
948 return ASCIICHAT_OK;
949 }
950
951 if (payload_len < sizeof(error_packet_t)) {
952 return SET_ERRNO(ERROR_INVALID_PARAM, "ERROR_MESSAGE payload too small");
953 }
954
955 const error_packet_t *header = (const error_packet_t *)payload;
956 const char *message = (const char *)payload + sizeof(error_packet_t);
957 size_t msg_len = payload_len - sizeof(error_packet_t);
958
959 // Ensure null-terminated message
960 char msg_buffer[MAX_ERROR_MESSAGE_LENGTH + 1];
961 size_t copy_len = msg_len < MAX_ERROR_MESSAGE_LENGTH ? msg_len : MAX_ERROR_MESSAGE_LENGTH;
962 memcpy(msg_buffer, message, copy_len);
963 msg_buffer[copy_len] = '\0';
964
965 callbacks->on_error(header, msg_buffer, client_ctx, callbacks->app_ctx);
966 return ASCIICHAT_OK;
967}
968
969static asciichat_error_t handle_server_crypto_rekey_request(const void *payload, size_t payload_len, void *client_ctx,
970 const acip_server_callbacks_t *callbacks) {
971 if (callbacks->on_crypto_rekey_request) {
972 callbacks->on_crypto_rekey_request(payload, payload_len, client_ctx, callbacks->app_ctx);
973 }
974 return ASCIICHAT_OK;
975}
976
977static asciichat_error_t handle_server_crypto_rekey_response(const void *payload, size_t payload_len, void *client_ctx,
978 const acip_server_callbacks_t *callbacks) {
979 if (callbacks->on_crypto_rekey_response) {
980 callbacks->on_crypto_rekey_response(payload, payload_len, client_ctx, callbacks->app_ctx);
981 }
982 return ASCIICHAT_OK;
983}
984
985static asciichat_error_t handle_server_crypto_rekey_complete(const void *payload, size_t payload_len, void *client_ctx,
986 const acip_server_callbacks_t *callbacks) {
987 if (callbacks->on_crypto_rekey_complete) {
988 callbacks->on_crypto_rekey_complete(payload, payload_len, client_ctx, callbacks->app_ctx);
989 }
990 return ASCIICHAT_OK;
991}
992
993static asciichat_error_t handle_server_crypto_key_exchange_resp(const void *payload, size_t payload_len,
994 void *client_ctx,
995 const acip_server_callbacks_t *callbacks) {
996 if (!callbacks->on_crypto_key_exchange_resp) {
997 return ASCIICHAT_OK;
998 }
999
1000 // Dispatch to application callback with packet type
1001 callbacks->on_crypto_key_exchange_resp(PACKET_TYPE_CRYPTO_KEY_EXCHANGE_RESP, payload, payload_len, client_ctx,
1002 callbacks->app_ctx);
1003 return ASCIICHAT_OK;
1004}
1005
1006static asciichat_error_t handle_server_crypto_auth_response(const void *payload, size_t payload_len, void *client_ctx,
1007 const acip_server_callbacks_t *callbacks) {
1008 if (!callbacks->on_crypto_auth_response) {
1009 return ASCIICHAT_OK;
1010 }
1011
1012 // Dispatch to application callback with packet type
1013 callbacks->on_crypto_auth_response(PACKET_TYPE_CRYPTO_AUTH_RESPONSE, payload, payload_len, client_ctx,
1014 callbacks->app_ctx);
1015 return ASCIICHAT_OK;
1016}
1017
1018static asciichat_error_t handle_server_crypto_no_encryption(const void *payload, size_t payload_len, void *client_ctx,
1019 const acip_server_callbacks_t *callbacks) {
1020 if (!callbacks->on_crypto_no_encryption) {
1021 return ASCIICHAT_OK;
1022 }
1023
1024 // Dispatch to application callback with packet type
1025 callbacks->on_crypto_no_encryption(PACKET_TYPE_CRYPTO_NO_ENCRYPTION, payload, payload_len, client_ctx,
1026 callbacks->app_ctx);
1027 return ASCIICHAT_OK;
1028}
asciichat_error_t audio_dequantize_samples(const uint8_t *samples_ptr, uint32_t total_samples, float *out_samples)
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)
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)
asciichat_error_t(* acip_client_handler_func_t)(const void *payload, size_t payload_len, const acip_client_callbacks_t *callbacks)
#define HANDLER_HASH(type)
asciichat_error_t(* acip_server_handler_func_t)(const void *payload, size_t payload_len, void *client_ctx, const acip_server_callbacks_t *callbacks)
#define CLIENT_HANDLER_COUNT
#define HANDLER_HASH_SIZE
#define SERVER_HANDLER_COUNT
Hash table entry for packet type to handler mapping.
uint8_t handler_idx
Handler index (0-based)
packet_type_t key
Packet type (0 = empty slot)