ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
protocol_adapter.c
Go to the documentation of this file.
2
3/* Client mode adapter: worker startup, display/snapshot effects and crypto/control
4 * callbacks. Media parsing and participant state live in session/protocol. */
8
9#include "protocol_adapter.h"
10#include "main.h"
11#include "../main.h" // Global exit API
12#include "server.h"
13#include "display.h"
14#include "capture.h"
15#include "audio.h"
18#include "keepalive.h"
20
29#include <ascii-chat/buffer.h>
30#include <ascii-chat/common.h>
36#include <ascii-chat/options/rcu.h> // For RCU-based options access
37#include <ascii-chat/util/fps.h>
41
42#include "crypto.h"
43
44#include <libplatform/atomic.h>
46#include <string.h>
47#include <stdio.h>
48#include <unistd.h>
49
50#ifdef _WIN32
51#include <libplatform/windows_compat.h>
52#endif
53
54#include <errno.h>
55
56static void handle_ascii_frame_packet(client_connection_t *connection, const ascii_frame_packet_t *frame_header,
57 const char *frame_data) {
58 bool snapshot_should_exit = false;
59 ascii_frame_packet_t header = *frame_header;
60 if (should_exit())
61 return;
62
63 // FPS tracking for received ASCII frames using reusable tracker utility
64
65 // Initialize FPS tracker on first frame
66 if (!connection->frame_fps_tracker_initialized) {
67 int fps = GET_OPTION(fps);
68 int expected_fps = fps > 0 ? ((fps > 144) ? 144 : fps) : DEFAULT_MAX_FPS;
69 fps_init(&connection->frame_fps_tracker, expected_fps, "ASCII_RX");
70 connection->frame_fps_tracker_initialized = true;
71 }
72
73 // Track this frame and detect lag
74 fps_frame_ns(&connection->frame_fps_tracker, time_get_ns(), "ASCII frame");
75
76 // Track frame dimension changes
77
78 if (header.width > 0 && header.height > 0) {
79 if (header.width != connection->frame_last_width || header.height != connection->frame_last_height) {
80 connection->frame_last_width = header.width;
81 connection->frame_last_height = header.height;
82 }
83 }
84
85 // Handle snapshot mode timing
86 if (GET_OPTION(snapshot_mode)) {
87
88 connection->frame_snapshot_frame_count++;
89 log_debug("Snapshot frame %d received", connection->frame_snapshot_frame_count);
90
91 if (!connection->frame_first_frame_recorded) {
92 connection->frame_first_frame_time_ns = time_get_ns();
93 atomic_store_u64(
95 connection->frame_first_frame_time_ns); // Also store globally for access from data_reception_thread
96 log_info("[SNAPSHOT_INIT] SET g_snapshot_first_frame_time_ns=%llu first_frame_time_ns=%llu",
97 (unsigned long long)atomic_load_u64(&connection->snapshot_first_frame_time_ns),
98 (unsigned long long)connection->frame_first_frame_time_ns);
99 connection->frame_first_frame_recorded = true;
100
101 if (GET_OPTION(snapshot_delay) == 0) {
102 log_debug("Snapshot captured immediately (delay=0)!");
103 snapshot_should_exit = true;
104 } else {
105 log_debug("Snapshot mode: first frame received, waiting %.2f seconds for webcam warmup...",
106 GET_OPTION(snapshot_delay));
107 }
108 } else {
109 uint64_t current_time_ns = time_get_ns();
110 double elapsed = time_ns_to_s(time_elapsed_ns(connection->frame_first_frame_time_ns, current_time_ns));
111 double snapshot_delay = GET_OPTION(snapshot_delay);
112
113 // Log every 0.5 seconds to debug timing
114
115 if (current_time_ns - connection->frame_last_debug_log_ns > 500 * 1000 * 1000) { // 500ms
116 log_debug("SNAPSHOT_DELAY_CHECK: elapsed=%.3f target=%.3f first_frame_ns=%llu current_ns=%llu", elapsed,
117 snapshot_delay, (unsigned long long)connection->frame_first_frame_time_ns,
118 (unsigned long long)current_time_ns);
119 connection->frame_last_debug_log_ns = current_time_ns;
120 }
121
122 if (elapsed >= snapshot_delay) {
123 char duration_str[TIME_DURATION_BUFFER_SIZE];
124 time_pretty((uint64_t)(elapsed * 1e9), -1, duration_str, sizeof(duration_str));
125 log_info("Snapshot captured after %s (target=%.3f)", duration_str, snapshot_delay);
126 snapshot_should_exit = true;
127 }
128 }
129 }
130
131 // Check if we need to clear console before rendering this frame
132 // IMPORTANT: We track if this is the first frame to ensure proper initialization
133
134 if (!connection->frame_first_frame_rendered) {
135 // Always clear display and disable logging before rendering the first frame
136 // This ensures clean ASCII display regardless of packet arrival order
137 log_debug("First frame - clearing display and disabling terminal logging");
140 connection->frame_first_frame_rendered = true;
141 connection->protocol.server_state_initialized = true; // Mark as initialized
142 connection->protocol.layout_changed = false; // Clear any pending clear request
143 log_debug("CLIENT_DISPLAY: Display cleared, ready for ASCII frames");
144 } else if (connection->protocol.layout_changed) {
145 // Subsequent clear request from server (e.g., after client list changes)
146 log_debug("CLIENT_DISPLAY: Clearing display for layout change");
149 connection->protocol.layout_changed = false;
150 }
151
152 // Safety check before rendering
153 if (!frame_data || header.original_size == 0) {
154 log_error("Invalid frame data for rendering: frame_data=%p, size=%u", frame_data, header.original_size);
155 return;
156 }
157
158 // The server paces frames using our negotiated desired_fps. Present received
159 // frames without a second arrival-time limiter: network jitter can deliver a
160 // correctly paced frame just before its nominal interval. The UI controller
161 // coalesces pending frames if the terminal cannot keep up.
162
163 // Frame counting now handled in session_display_write_ascii() which is called
164 // by both client and host/discovery modes for consistent metrics
165
166 // Render ASCII art frame (display_render_frame will apply effects like --matrix)
167 log_debug_every(US_PER_SEC_INT, "CALLING_DISPLAY_RENDER: frame_data=%p len=%zu, calling display_render_frame()",
168 frame_data, strlen(frame_data));
169 display_render_frame(connection, frame_data);
170 log_debug_every(US_PER_SEC_INT, "DISPLAY_RENDER_RETURNED: frame was rendered");
171
172 // The frame must be presented before snapshot shutdown is requested. The
173 // reception thread is also responsible for rendering the packet, so exiting
174 // above would discard the very frame that satisfied the snapshot condition.
175 if (snapshot_should_exit)
176 signal_exit();
177}
178
179static bool handle_error_message_packet(client_connection_t *connection, const void *data, size_t len) {
180 asciichat_error_t remote_error = ASCIICHAT_OK;
181 char message[MAX_ERROR_MESSAGE_LENGTH + 1] = {0};
182
183 asciichat_error_t parse_result = packet_parse_error_message(data, len, &remote_error, message, sizeof(message), NULL);
184 if (parse_result != ASCIICHAT_OK) {
185 log_error("Failed to parse error packet from server: %s", asciichat_error_format(NULL, parse_result));
186 return false;
187 }
188
189 remote_error = network_error_public_code(remote_error);
190 log_error("Server reported %s", asciichat_error_format(NULL, remote_error));
192 SET_ERRNO(remote_error, "Server notification: %s", asciichat_error_string(remote_error));
193 if (network_error_action(remote_error) == NETWORK_ERROR_CONTINUE) {
194 log_warn("Server audio unavailable; continuing video");
196 return true;
197 }
198 server_connection_set_remote_error(connection, remote_error);
199 if (network_error_action(remote_error) == NETWORK_ERROR_STOP) {
200 errno_request_exit(remote_error);
201 signal_exit();
202 }
203 log_warn("Server rejected this connection; closing it");
205 server_connection_shutdown(connection);
206 server_connection_lost(connection);
207 return true;
208}
209
210static void handle_remote_log_packet(const void *data, size_t len) {
211 log_level_t remote_level = LOG_INFO;
213 uint16_t flags = 0;
214 char message[MAX_REMOTE_LOG_MESSAGE_LENGTH + 1] = {0};
215
216 asciichat_error_t parse_result =
217 packet_parse_remote_log(data, len, &remote_level, &direction, &flags, message, sizeof(message), NULL);
218 if (parse_result != ASCIICHAT_OK) {
219 log_error("Failed to parse remote log packet from server: %s", asciichat_error_format(NULL, parse_result));
220 return;
221 }
222
223 if (direction != REMOTE_LOG_DIRECTION_SERVER_TO_CLIENT) {
224 log_error("Remote log packet direction mismatch (direction=%u)", direction);
225 return;
226 }
227
228 bool truncated = (flags & REMOTE_LOG_FLAG_TRUNCATED) != 0;
229
230 if (truncated) {
231 log_msg(remote_level, __FILE__, __LINE__, __func__, "[REMOTE SERVER] %s [message truncated]", message);
232 } else {
233 log_msg(remote_level, __FILE__, __LINE__, __func__, "[REMOTE SERVER] %s", message);
234 }
235}
236
237static asciichat_error_t client_present_frame(const ascii_frame_packet_t *header, const char *text, void *context) {
238 handle_ascii_frame_packet(context, header, text);
239 return ASCIICHAT_OK;
240}
241
242static asciichat_error_t client_play_samples(const float *samples, size_t count, void *context) {
243 if (GET_OPTION(audio_enabled))
244 audio_process_received_samples(context, samples, (int)count);
245 return ASCIICHAT_OK;
246}
247
248static asciichat_error_t client_decode_opus(const void *data, size_t length, float *samples, size_t capacity,
249 size_t *decoded, void *context) {
250 *decoded = 0;
251 if (!GET_OPTION(audio_enabled))
252 return ASCIICHAT_OK;
253 int count = audio_decode_opus(context, data, length, samples, (int)capacity);
254 // The audio pipeline provides packet-loss concealment for corrupt frames.
255 if (count <= 0)
256 count = audio_decode_opus(context, NULL, 0, samples, (int)capacity);
257 if (count <= 0)
258 return SET_ERRNO(ERROR_AUDIO, "Failed to decode participant Opus audio");
259 *decoded = (size_t)count;
260 return ASCIICHAT_OK;
261}
262
263static void client_track_audio(size_t length, void *context) {
264 (void)context;
265 if (GET_OPTION(audio_enabled) && GET_OPTION(audio_analysis_enabled))
267}
268
269/* ============================================================================
270 * ACIP Callback Forward Declarations and Structure
271 * ============================================================================ */
272
273// Forward declarations for ACIP callbacks (implemented after this section)
274static void acip_on_error(const error_packet_t *header, const char *message, void *ctx);
275static void acip_on_remote_log(const remote_log_packet_t *header, const char *message, void *ctx);
276static void acip_on_ping(void *ctx);
277static void acip_on_pong(void *ctx);
278static void acip_on_clear_console(void *ctx);
279static void acip_on_webrtc_sdp(const acip_webrtc_sdp_t *sdp, size_t total_len, void *ctx);
280static void acip_on_webrtc_ice(const acip_webrtc_ice_t *ice, size_t total_len, void *ctx);
281static void acip_on_session_joined(const acip_session_joined_t *joined, void *ctx);
282
283static const acip_client_callbacks_t g_acip_client_callbacks = {.on_error = acip_on_error,
284 .on_remote_log = acip_on_remote_log,
285 .on_ping = acip_on_ping,
286 .on_pong = acip_on_pong,
287 .on_clear_console = acip_on_clear_console,
288
289 .on_webrtc_sdp = acip_on_webrtc_sdp,
290 .on_webrtc_ice = acip_on_webrtc_ice,
291 .on_session_joined = acip_on_session_joined,
292 .app_ctx = NULL};
293
294/* ============================================================================
295 * Data Reception Thread
296 * ============================================================================ */
297
298static asciichat_error_t client_connection_packet(network_connection_t *network, packet_type_t type,
299 const void *payload, size_t length, void *arg) {
300 (void)network;
301 client_connection_t *connection = arg;
302 asciichat_error_t result = session_protocol_dispatch(&connection->protocol, type, payload, length);
303 // A recorded rejection already closed the transport. Stop this receive loop
304 // with its public reason instead of reading again and notifying a second EOF.
305 return result != ASCIICHAT_OK ? result : server_connection_get_remote_error(connection);
306}
307static void *data_reception_thread_func(void *arg) {
308 client_connection_t *connection = arg;
309 network_connection_callbacks_t callbacks = {.packet = client_connection_packet};
310 asciichat_error_t result = network_connection_run(connection->network, &callbacks, connection);
311 uint64_t first_frame = atomic_load_u64(&connection->snapshot_first_frame_time_ns);
312 if (GET_OPTION(snapshot_mode) && first_frame &&
313 time_ns_to_s(time_elapsed_ns(first_frame, time_get_ns())) >= GET_OPTION(snapshot_delay)) {
314 signal_exit();
315 } else if (!should_exit()) {
316 if (result == ERROR_CRYPTO) {
317 NOTICE(DANGER, "ENCRYPTION POLICY VIOLATION", "Server violated the encryption policy; closing connection.");
319 signal_exit();
320 }
321 server_connection_lost(connection);
322 }
323 atomic_store_bool(&connection->data_thread_exited, true);
324 return NULL;
325}
326
327/* ============================================================================
328 * Public Interface Functions
329 * ============================================================================ */
330
331// Before the receive worker starts, an admission rejection can race STREAM_START.
332// Preserve the peer's public error instead of reporting only the failed write.
333static asciichat_error_t client_startup_peer_error(client_connection_t *connection, asciichat_error_t send_error) {
334 acip_transport_t *transport = server_connection_get_transport(connection);
335 if (!transport || (send_error != ERROR_NETWORK && send_error != ERROR_NETWORK_CONNECT))
336 return send_error;
337 uint64_t previous_timeout = transport->receive_timeout_ns;
338 uint64_t deadline = time_get_ns() + 500 * NS_PER_MS_INT;
339 asciichat_error_t result = send_error;
340 while (time_get_ns() < deadline && !should_exit()) {
341 transport->receive_timeout_ns = 100 * NS_PER_MS_INT;
342 packet_type_t type = 0;
343 void *payload = NULL, *allocation = NULL;
344 size_t length = 0;
345 bool notifications = errno_notifications_suppress(true);
347 asciichat_error_t received = packet_receive_via_transport(transport, &type, &payload, &length, &allocation);
349 errno_notifications_suppress(notifications);
350 if (received == ASCIICHAT_OK && type == PACKET_TYPE_ERROR_MESSAGE)
351 result = network_error_decode(type, payload, length);
352 buffer_release(allocation);
353 if (result != send_error ||
354 (received != ASCIICHAT_OK && received != ERROR_NETWORK_TIMEOUT && received != ERROR_NETWORK_INCOMPLETE))
355 break;
356 }
357 transport->receive_timeout_ns = previous_timeout;
358 return result;
359}
360
362 log_warn("[FRAME_RECV_INIT] ๐ŸŸข PROTOCOL_START: Starting client protocol initialization");
363
364 // The server can send Opus audio as soon as streaming starts. Initialize the
365 // decoder before either transport's receive worker can dispatch those packets.
366 asciichat_error_t audio_result = audio_connection_init(connection);
367 if (audio_result != ASCIICHAT_OK)
368 return audio_result;
369 if (client_connection_checkpoint("audio-pipeline") != ASCIICHAT_OK)
370 return SET_ERRNO(ERROR_THREAD, "Could not start client protocol workers");
371
372 acip_client_callbacks_t control = g_acip_client_callbacks;
373 control.app_ctx = connection;
375 (session_protocol_media_t){.terminal_output = true,
376 .on_frame = client_present_frame,
377 .on_samples = client_play_samples,
378 .decode_opus = client_decode_opus,
379 .on_audio_packet = client_track_audio,
380 .context = connection,
381 .frame_workspace = &connection->decoded_ascii_workspace},
382 control);
383
384 // Reset display state for new connection
386
387 // Start receiving before the capability/settings exchange. The server may
388 // reply to the initial settings packet immediately, and that reply must not
389 // wait for the worker that dispatches it.
390 atomic_store_bool(&connection->data_thread_exited, false);
391 if (thread_pool_spawn(connection->client_worker_pool, data_reception_thread_func, connection, 1, "data_reception") !=
392 ASCIICHAT_OK) {
393 log_error("[FRAME_RECV_INIT] Failed to start data reception thread");
394 LOG_ERRNO_IF_SET("Data reception thread creation failed");
395 return SET_ERRNO(ERROR_THREAD, "Could not start client protocol workers");
396 }
397
399 return SET_ERRNO(ERROR_THREAD, "Could not start client protocol workers");
400
401 // Send CLIENT_CAPABILITIES packet (after crypto is ready for both TCP and WS)
402 // Server expects this as the first packet after crypto handshake
403 log_debug("[FRAME_RECV_INIT] Sending CLIENT_CAPABILITIES (terminal size)");
404 asciichat_error_t cap_result =
405 threaded_send_terminal_size_with_auto_detect(connection, (int)terminal_get_effective_width(GET_OPTION(width), 80),
406 (int)terminal_get_effective_height(GET_OPTION(height), 24));
407 if (cap_result != ASCIICHAT_OK) {
408 log_error("[FRAME_RECV_INIT] Failed to send terminal size capabilities");
409 return client_startup_peer_error(connection, cap_result);
410 }
411
412 // Capabilities establish the viewer geometry, but renderer preferences are
413 // carried by SETTINGS_SYNC. Send them before STREAM_START so the server
414 // cannot render the first frames from its zero-initialized (luminance)
415 // fallback while the asynchronous settings update is still in flight.
416 options_snapshot_t initial_settings;
417 if (options_read_snapshot(&initial_settings) != ASCIICHAT_OK)
418 return client_startup_peer_error(connection, ERROR_CONFIG);
419 asciichat_error_t settings_result =
420 acip_settings_send(server_connection_get_transport(connection), &initial_settings.values, 1, UINT32_MAX);
421 if (settings_result != ASCIICHAT_OK) {
422 log_error("[FRAME_RECV_INIT] Failed to send initial rendering settings");
423 return client_startup_peer_error(connection, settings_result);
424 }
425
426 // Send STREAM_START packet with combined stream types
427 uint32_t stream_types = STREAM_TYPE_VIDEO; // Always have video
428 if (GET_OPTION(audio_enabled)) {
429 stream_types |= STREAM_TYPE_AUDIO; // Add audio if enabled
430 }
431 log_debug("[FRAME_RECV_INIT] Sending STREAM_START: types=0x%x (video%s)", stream_types,
432 (stream_types & STREAM_TYPE_AUDIO) ? "+audio" : "");
433 asciichat_error_t stream_result = threaded_send_stream_start_packet(connection, stream_types);
434 if (stream_result != ASCIICHAT_OK) {
435 log_error("[FRAME_RECV_INIT] Failed to send stream start");
436 return client_startup_peer_error(connection, stream_result);
437 }
438
439 // Start webcam capture thread
440 log_debug("Starting webcam capture thread...");
441 if (capture_start_thread(connection) != 0) {
442 log_error("Failed to start webcam capture thread");
443 return SET_ERRNO(ERROR_THREAD, "Could not start client protocol workers");
444 }
445 log_debug("Webcam capture thread started successfully");
447 return SET_ERRNO(ERROR_THREAD, "Could not start client protocol workers");
448
449 // Start local media audio only after the connection succeeds. Starting the
450 // worker during client initialization would consume file audio while dialing.
451 audio_context_t *audio_context = connection->audio_context;
452 if (audio_context && audio_context->initialized) {
453 session_capture_set_audio_context(connection->capture_context, audio_context);
455 if (audio_start_duplex(audio_context) != ASCIICHAT_OK) {
456 log_error("Failed to start full-duplex audio");
457 return SET_ERRNO(ERROR_THREAD, "Could not start client protocol workers");
458 }
459 }
460
461 if (client_connection_checkpoint("audio-duplex") != ASCIICHAT_OK)
462 return SET_ERRNO(ERROR_THREAD, "Could not start client protocol workers");
463
464 // Initialize audio sender thread BEFORE starting audio capture
465 // This ensures sender is ready when capture thread starts queueing packets
466 // Must happen after connection succeeds to prevent deadlock if connection fails
467 if (audio_sender_init(connection) != ASCIICHAT_OK)
468 return SET_ERRNO(ERROR_THREAD, "Could not start client protocol workers");
469
470 // Start audio capture thread if audio is enabled
471 log_debug("Starting audio capture thread...");
472 if (audio_start_thread(connection) != 0) {
473 log_error("Failed to start audio capture thread");
474 return SET_ERRNO(ERROR_THREAD, "Could not start client protocol workers");
475 }
476 log_debug("Audio capture thread started successfully (or skipped if audio disabled)");
477
478 if (client_connection_checkpoint("audio-capture") != ASCIICHAT_OK)
479 return SET_ERRNO(ERROR_THREAD, "Could not start client protocol workers");
480
481 // Start keepalive/ping thread to prevent server timeout
482 log_debug("Starting keepalive/ping thread...");
483 if (keepalive_start_thread(connection) != 0) {
484 log_error("Failed to start keepalive/ping thread");
485 return SET_ERRNO(ERROR_THREAD, "Could not start client protocol workers");
486 }
487 log_debug("Keepalive/ping thread started successfully");
488
489 if (client_connection_checkpoint("keepalive") != ASCIICHAT_OK)
490 return SET_ERRNO(ERROR_THREAD, "Could not start client protocol workers");
491 return ASCIICHAT_OK;
492}
493
495 return atomic_load_bool(&connection->data_thread_exited) || server_connection_is_lost(connection);
496}
497
498/* ============================================================================
499 * ACIP Callback Implementations
500 * ============================================================================ */
501
502static void acip_on_error(const error_packet_t *header, const char *message, void *ctx) {
503 client_connection_t *connection = ctx;
504 (void)ctx;
505
506 // Reconstruct packet for existing handler
507 size_t msg_len = message ? strlen(message) : 0;
508 size_t total_len = sizeof(*header) + msg_len;
509
510 uint8_t *packet = SAFE_MALLOC(total_len, uint8_t *);
511 if (!packet) {
512 log_error("Failed to allocate buffer for error packet callback");
513 return;
514 }
515
516 memcpy(packet, header, sizeof(*header));
517 if (msg_len > 0) {
518 // NOLINTNEXTLINE(bugprone-not-null-terminated-result) - binary packet data
519 memcpy(packet + sizeof(*header), message, msg_len);
520 }
521
522 handle_error_message_packet(connection, packet, total_len);
523 SAFE_FREE(packet);
524}
525
526static void acip_on_ping(void *ctx) {
527 client_connection_t *connection = ctx;
528 (void)ctx;
529
530 // Respond with PONG
531 if (threaded_send_pong_packet(connection) != ASCIICHAT_OK) {
532 log_error("Failed to send PONG response");
533 }
534}
535
536static void acip_on_remote_log(const remote_log_packet_t *header, const char *message, void *ctx) {
537 (void)ctx;
538
539 // Reconstruct packet for existing handler
540 size_t msg_len = strlen(message);
541 size_t total_len = sizeof(*header) + msg_len;
542
543 uint8_t *packet = SAFE_MALLOC(total_len, uint8_t *);
544 if (!packet) {
545 log_error("Failed to allocate buffer for remote log callback");
546 return;
547 }
548
549 memcpy(packet, header, sizeof(*header));
550 // NOLINTNEXTLINE(bugprone-not-null-terminated-result) - binary packet data, not string
551 memcpy(packet + sizeof(*header), message, msg_len);
552
553 handle_remote_log_packet(packet, total_len);
554 SAFE_FREE(packet);
555}
556
557static void acip_on_pong(void *ctx) {
558 (void)ctx;
560}
561
562static void acip_on_clear_console(void *ctx) {
563 (void)ctx;
564
565 // Server requested console clear
567 log_debug("Console cleared by server");
568}
569
570static void acip_on_webrtc_sdp(const acip_webrtc_sdp_t *sdp, size_t total_len, void *ctx) {
571 (void)ctx;
572 (void)total_len; // Peer manager reads variable data via pointer arithmetic
573
574 // Check if WebRTC is initialized
575 if (!g_peer_manager) {
576 log_warn("Received WebRTC SDP but peer manager not initialized - ignoring");
577 return;
578 }
579
580 // Log SDP type for debugging
581 const char *sdp_type_str = (sdp->sdp_type == 0) ? "offer" : "answer";
582 log_debug("Received WebRTC SDP %s from participant (session_id=%.8s...)", sdp_type_str,
583 (const char *)sdp->session_id);
584
585 // Handle SDP through peer manager (extracts variable data internally)
587
588 if (result != ASCIICHAT_OK) {
589 log_error("Failed to handle WebRTC SDP: %s", asciichat_error_format(NULL, result));
590 }
591}
592
593static void acip_on_webrtc_ice(const acip_webrtc_ice_t *ice, size_t total_len, void *ctx) {
594 (void)ctx;
595 (void)total_len; // Peer manager reads variable data via pointer arithmetic
596
597 // Check if WebRTC is initialized
598 if (!g_peer_manager) {
599 log_warn("Received WebRTC ICE but peer manager not initialized - ignoring");
600 return;
601 }
602
603 log_debug("Received WebRTC ICE candidate from participant (session_id=%.8s...)", (const char *)ice->session_id);
604
605 // Handle ICE through peer manager (extracts variable data internally)
607
608 if (result != ASCIICHAT_OK) {
609 log_error("Failed to handle WebRTC ICE: %s", asciichat_error_format(NULL, result));
610 }
611}
612
613static void acip_on_session_joined(const acip_session_joined_t *joined, void *ctx) {
614 (void)ctx;
615
616 if (!joined) {
617 log_error("SESSION_JOINED callback received NULL response");
618 return;
619 }
620
621 // Check if join was successful
622 if (!joined->success) {
623 log_error("ACDS session join failed: error %d: %s", joined->error_code, joined->error_message);
624 // Connection will timeout waiting for SDP/WebRTC completion and fallback to next stage
625 return;
626 }
627
628 // Join succeeded - we have session context now
629 log_debug("ACDS session join succeeded (participant_id=%.8s..., session_type=%s, server=%s:%u)",
630 (const char *)joined->participant_id, joined->session_type == 1 ? "WebRTC" : "DirectTCP",
631 joined->server_address, joined->server_port);
632
633 // Check if this is a WebRTC session
634 if (joined->session_type == SESSION_TYPE_WEBRTC) {
635 log_debug("WebRTC session joined; discovery mode owns peer-manager initialization");
636 } else {
637 // Direct TCP - connection is already established or will be established
638 log_debug("Direct TCP session - using existing connection");
639 }
640}
ascii-chat Discovery Service (ACDS) Protocol Message Formats
void audio_analysis_track_received_packet(size_t size)
Track received packet.
Definition analysis.c:470
Audio Analysis and Debugging Interface.
void errno_request_exit(asciichat_error_t code)
void errno_scope_end(asciichat_errno_scope_t scope, asciichat_errno_outcome_t outcome)
bool errno_notifications_suppress(bool suppress)
asciichat_errno_scope_t errno_scope_begin(void)
const char * asciichat_error_format(const char *message, asciichat_error_t code)
#define SET_ERRNO(code,...)
@ ASCIICHAT_ERRNO_HANDLED
#define LOG_ERRNO_IF_SET(message)
#define buffer_release(data)
Definition buffer.h:91
Common buffer size definitions.
int capture_start_thread(client_connection_t *connection)
Start capture thread.
ascii-chat Client Media Capture Management Interface
struct webrtc_peer_manager * g_peer_manager
Client connection session state for session_client_like integration.
ascii-chat Client Mode Entry Point Header
void session_capture_set_audio_context(session_capture_ctx_t *ctx, void *audio_ctx)
Set the audio context for playback control.
asciichat_error_t session_protocol_dispatch(session_protocol_t *protocol, packet_type_t type, const void *payload, size_t length)
void session_protocol_init(session_protocol_t *protocol, acip_transport_t *transport, session_protocol_media_t media, acip_client_callbacks_t control)
โš™๏ธ Common definitions, error codes, macros, and types shared throughout the application
Named object registry for debugging โ€” log identifiable resource names.
๐Ÿ”„ Network byte order conversion helpers
unsigned int height
Definition fft.c:23
unsigned int width
Definition fft.c:23
โฑ๏ธ FPS tracking utility for monitoring frame throughput across all threads
asciichat_error_t audio_start_duplex(audio_context_t *ctx)
Start full-duplex audio (simultaneous capture and playback).
void display_reset_for_new_connection(client_connection_t *connection)
Reset display state for new connection.
void display_full_reset()
Perform full display reset.
void display_render_frame(client_connection_t *connection, const char *frame_data)
Render ASCII frame to display.
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
void fps_frame_ns(fps_t *tracker, uint64_t current_time_ns, const char *context)
Track a frame and detect lag conditions (nanosecond version - PRIMARY).
Definition fps.c:52
unsigned long long uint64_t
Definition common.h:63
unsigned char uint8_t
Definition common.h:60
void fps_init(fps_t *tracker, int expected_fps, const char *name)
Initialize FPS tracker.
Definition fps.c:32
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ERROR_NETWORK_CONNECT
Definition error_codes.h:74
@ ERROR_NETWORK_INCOMPLETE
Definition error_codes.h:90
@ ERROR_NETWORK
Definition error_codes.h:72
@ ERROR_AUDIO
Definition error_codes.h:62
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_CONFIG
Definition error_codes.h:52
@ ERROR_CRYPTO
Definition error_codes.h:93
@ ERROR_NETWORK_TIMEOUT
Definition error_codes.h:75
@ ERROR_THREAD
#define DEFAULT_MAX_FPS
Default maximum frame rate (frames per second).
#define log_warn(...)
Log a WARN message.
Definition log/log.h:621
void log_msg(log_level_t level, const char *file, int line, const char *func, const char *fmt,...)
Log a message at a specific level.
Definition log.c:1196
enum remote_log_direction remote_log_direction_t
Remote log packet direction enumeration.
#define log_error(...)
Log an ERROR message.
Definition log/log.h:634
log_level_t
Logging levels enumeration.
Definition types.h:29
#define log_info(...)
Log an INFO message.
Definition log/log.h:608
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
void log_set_terminal_output(bool enabled)
Control stderr output to terminal.
Definition log.c:746
@ REMOTE_LOG_DIRECTION_SERVER_TO_CLIENT
Definition network/log.h:22
@ REMOTE_LOG_DIRECTION_UNKNOWN
Definition network/log.h:21
acip_session_joined_t
Definition acds.h:479
acip_webrtc_ice_t
Definition acds.h:655
acip_webrtc_sdp_t
Definition acds.h:609
@ SESSION_TYPE_WEBRTC
WebRTC P2P mesh with STUN/TURN relay.
Definition acds.h:151
int time_pretty(uint64_t nanoseconds, int decimals, char *buffer, size_t buffer_size)
Format nanoseconds as a compact duration with configurable sub-minute precision.
Definition time.c:374
#define TIME_DURATION_BUFFER_SIZE
Definition time.h:40
#define NS_PER_MS_INT
Definition time.h:168
uint64_t time_elapsed_ns(uint64_t start_ns, uint64_t end_ns)
Calculate elapsed time with wraparound safety.
Definition time.c:123
#define US_PER_SEC_INT
Definition time.h:173
#define STREAM_TYPE_VIDEO
Video stream.
Definition packet.h:993
#define STREAM_TYPE_AUDIO
Audio stream.
Definition packet.h:994
packet_type_t
Network protocol packet type enumeration.
Definition packet.h:301
#define MAX_REMOTE_LOG_MESSAGE_LENGTH
Maximum remote log message length (512 bytes).
Definition packet.h:149
#define REMOTE_LOG_FLAG_TRUNCATED
Remote log packet flag definitions.
Definition packet.h:776
#define MAX_ERROR_MESSAGE_LENGTH
Maximum error message length (512 bytes).
Definition packet.h:139
@ PACKET_TYPE_ERROR_MESSAGE
Error packet with asciichat_error_t code and human-readable message.
Definition packet.h:374
#define GET_OPTION(field)
Safely get a specific option field (lock-free read).
Definition options.h:1336
struct acip_transport acip_transport_t
Forward declaration of ACIP transport.
Definition host.h:506
void * session_capture_get_media_source(session_capture_ctx_t *ctx)
Get the underlying media source from capture context.
asciichat_error_t webrtc_peer_manager_handle_ice(webrtc_peer_manager_t *manager, const acip_webrtc_ice_t *ice)
Handle incoming ICE candidate from ACDS.
asciichat_error_t webrtc_peer_manager_handle_sdp(webrtc_peer_manager_t *manager, const acip_webrtc_sdp_t *sdp)
Handle incoming SDP message from ACDS.
ACIP protocol packet handlers (transport-agnostic).
๐Ÿ”Š Audio Capture and Playback Interface for ascii-chat
Client-side handshake functions.
ACIP client-side protocol API.
Transport abstraction layer for ACIP protocol.
struct network_connection network_connection_t
Network error handling utilities.
int keepalive_start_thread(client_connection_t *connection)
Start keepalive/ping thread.
Definition keepalive.c:209
ascii-chat Client Connection Keepalive Management Interface
asciichat_error_t acip_settings_send(acip_transport_t *transport, const options_t *values, uint32_t request, uint32_t expected_revision)
asciichat_error_t network_connection_run(network_connection_t *c, const network_connection_callbacks_t *callbacks, void *context)
asciichat_error_t network_error_public_code(asciichat_error_t code)
asciichat_error_t network_error_decode(packet_type_t type, const void *payload, size_t size)
network_error_action_t network_error_action(asciichat_error_t code)
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
Definition log/log.h:749
void signal_exit(void)
Definition main.c:253
bool should_exit(void)
Definition main.c:249
Global application exit and signal handling API.
#define NOTICE(severity, title,...)
Definition notice.h:68
โš™๏ธ Unified options parsing system for ascii-chat with builder pattern and lock-free access
asciichat_error_t packet_parse_remote_log(const void *data, size_t len, log_level_t *out_level, remote_log_direction_t *out_direction, uint16_t *out_flags, char *message_buffer, size_t message_buffer_size, size_t *out_message_length)
Parse a remote log packet payload into components.
Definition packet.c:881
asciichat_error_t packet_parse_error_message(const void *data, size_t len, asciichat_error_t *out_error_code, char *message_buffer, size_t message_buffer_size, size_t *out_message_length)
Parse an error packet payload into components.
Definition packet.c:782
asciichat_error_t client_protocol_start(client_connection_t *connection)
bool client_protocol_connection_lost(client_connection_t *connection)
asciichat_error_t options_read_snapshot(options_snapshot_t *snapshot)
Definition rcu.c:865
asciichat_error_t packet_receive_via_transport(acip_transport_t *transport, packet_type_t *type, void **payload, size_t *payload_len, void **alloc_buffer)
Receive a packet via transport with header parsing and validation.
Definition send.c:200
ACIP shared/bidirectional packet sending functions.
asciichat_error_t audio_connection_init(client_connection_t *connection)
int audio_decode_opus(client_connection_t *connection, const uint8_t *opus_data, size_t opus_len, float *output, int max_samples)
Decode Opus packet using the audio pipeline.
int audio_start_thread(client_connection_t *connection)
Start audio capture thread.
void audio_process_received_samples(client_connection_t *connection, const float *samples, int num_samples)
Process received audio samples from server.
asciichat_error_t audio_sender_init(client_connection_t *connection)
Initialize async audio sender queue and thread.
ascii-chat Client Audio Processing Management Interface
asciichat_error_t client_connection_checkpoint(const char *stage)
struct client_connection client_connection_t
ascii-chat Client Display Management Interface
void server_connection_shutdown(client_connection_t *connection)
Interrupt connection I/O from normal thread context.
asciichat_error_t threaded_send_terminal_size_with_auto_detect(client_connection_t *connection, unsigned short width, unsigned short height)
Thread-safe terminal size packet transmission with auto-detection.
asciichat_error_t threaded_send_stream_start_packet(client_connection_t *connection, uint32_t stream_type)
Thread-safe stream start packet transmission.
void server_connection_set_remote_error(client_connection_t *connection, asciichat_error_t code)
Atomic flag indicating if connection is currently active.
void server_connection_lost(client_connection_t *connection)
Signal that connection has been lost.
asciichat_error_t threaded_send_pong_packet(client_connection_t *connection)
Thread-safe pong packet transmission.
asciichat_error_t server_connection_get_remote_error(client_connection_t *connection)
bool server_connection_is_lost(client_connection_t *connection)
Check if connection loss has been detected.
void server_connection_pong_received(client_connection_t *connection)
Signal that connection has been lost.
acip_transport_t * server_connection_get_transport(client_connection_t *connection)
Get ACIP transport instance.
ascii-chat Client Server Connection Management Interface
๐Ÿ”ค String Manipulation and Shell Escaping Utilities
Client-side packet handler callbacks.
Definition handlers.h:51
void * app_ctx
Application context (passed to all callbacks).
Definition handlers.h:113
uint64_t receive_timeout_ns
Optional TCP receive timeout override; zero uses the normal polling timeout.
The wire metadata for one rendered ASCII frame. width and height describe the terminal grid,...
Definition packet.h:902
When code needs to work with asciichat errno snapshot, it keeps total, copied here instead of scatter...
When code needs to work with audio device info, it keeps index, max_input_channels,...
bool initialized
True if context has been initialized.
void * capture_media_source
Borrowed local media audio; configure before starting duplex.
network_connection_t * network
thread_pool_t * client_worker_pool
atomic_t snapshot_first_frame_time_ns
session_protocol_t protocol
session_capture_ctx_t * capture_context
buffer_workspace_t decoded_ascii_workspace
audio_context_t * audio_context
Error packet structure carrying error code and textual description.
Definition packet.h:767
Callbacks and operations used by network connection callbacks. Packet payloads are borrowed until ret...
Remote log packet structure carrying log level and message text.
Definition packet.h:781
asciichat_error_t thread_pool_spawn(thread_pool_t *pool, void *(*thread_func)(void *), void *thread_arg, int stop_id, const char *thread_name)
Spawn a worker thread in the pool.
๐Ÿงต Generic thread pool abstraction for managing worker threads
โฑ๏ธ High-precision timing utilities using sokol_time.h and uthash
#define LOG_INFO
Definition types.h:40
๐Ÿ“Š String Formatting Utilities
Common validation macros to reduce duplication in protocol handlers.