ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
webrtc/transport.c
Go to the documentation of this file.
1
33#include <ascii-chat/log/log.h>
42#include <string.h>
43
50#define WEBRTC_RECV_QUEUE_SIZE 512
51// Start coalescing well before the 512-message ring fills so a congested peer
52// stays close to live.
53#define WEBRTC_RECV_QUEUE_HIGH_WATER 12
54
55// DataChannel sends are asynchronous. Bound replaceable video so reliable,
56// ordered delivery cannot turn a slow peer into an ever-growing history of
57// obsolete frames.
58#define WEBRTC_SEND_VIDEO_HIGH_WATER (256 * 1024)
59
60// Allow ICE connectivity checks to recover temporarily, then let the owning
61// session tear down and renegotiate a peer that has remained disconnected.
62#define WEBRTC_DISCONNECTED_GRACE_NS (30 * NS_PER_SEC_INT)
63
67typedef struct {
69 size_t len;
71
91
92// =============================================================================
93// DataChannel Callbacks
94// =============================================================================
95
96static bool queued_message_is_media(const webrtc_recv_msg_t *msg) {
97 if (msg->len < sizeof(packet_header_t))
98 return false;
99 packet_header_t header;
100 memcpy(&header, msg->data, sizeof(header));
101 packet_type_t type = NET_TO_HOST_U16(header.type);
102 return type == PACKET_TYPE_IMAGE_FRAME || type == PACKET_TYPE_ASCII_FRAME || type == PACKET_TYPE_AUDIO_BATCH ||
104}
105
106static bool queued_message_is_video(const webrtc_recv_msg_t *msg) {
107 if (msg->len < sizeof(packet_header_t))
108 return false;
109 packet_header_t header;
110 memcpy(&header, msg->data, sizeof(header));
111 packet_type_t type = NET_TO_HOST_U16(header.type);
112 return type == PACKET_TYPE_IMAGE_FRAME || type == PACKET_TYPE_ASCII_FRAME;
113}
114
115static bool queued_message_is_audio(const webrtc_recv_msg_t *msg) {
116 if (msg->len < sizeof(packet_header_t))
117 return false;
118 packet_header_t header;
119 memcpy(&header, msg->data, sizeof(header));
120 packet_type_t type = NET_TO_HOST_U16(header.type);
122}
123
124static void discard_stale_media(webrtc_transport_data_t *wrtc) {
126 size_t count = 0, video_count = 0, audio_count = 0;
127 while (count < WEBRTC_RECV_QUEUE_SIZE && ringbuffer_read(wrtc->recv_queue, &queued[count])) {
128 if (queued_message_is_video(&queued[count]))
129 video_count++;
130 else if (queued_message_is_audio(&queued[count]))
131 audio_count++;
132 count++;
133 }
134 // Video frames are snapshots, so retain the newest one. Preserve a short
135 // recent audio history, but discard stale audio when it would otherwise fill
136 // the queue and prevent new video packets from reaching the receive worker.
137 // Derive the audio allowance from the existing queue high-water mark after
138 // reserving room for control messages and the newest video frame.
139 size_t control_count = count - video_count - audio_count;
140 size_t retained_video_count = video_count > 0 ? 1 : 0;
141 size_t non_audio_count = control_count + retained_video_count;
142 size_t keep_audio_count = non_audio_count < WEBRTC_RECV_QUEUE_HIGH_WATER
143 ? WEBRTC_RECV_QUEUE_HIGH_WATER - non_audio_count
144 : 0;
145 size_t video_to_discard = video_count - retained_video_count;
146 size_t audio_to_discard = audio_count > keep_audio_count ? audio_count - keep_audio_count : 0;
147 size_t dropped_video = 0;
148 size_t dropped_audio = 0;
149 for (size_t i = 0; i < count; i++) {
150 if (video_to_discard && queued_message_is_video(&queued[i])) {
151 buffer_pool_free(NULL, queued[i].data, queued[i].len);
152 video_to_discard--;
153 dropped_video++;
154 } else if (audio_to_discard && queued_message_is_audio(&queued[i])) {
155 buffer_pool_free(NULL, queued[i].data, queued[i].len);
156 audio_to_discard--;
157 dropped_audio++;
158 } else {
159 ringbuffer_write(wrtc->recv_queue, &queued[i]);
160 }
161 }
162 if (dropped_video || dropped_audio) {
164 "WebRTC receive backlog: dropped %zu stale video packets and %zu oldest audio packets",
165 dropped_video, dropped_audio);
166 }
167}
168
172static void webrtc_on_message(webrtc_data_channel_t *channel, const uint8_t *data, size_t len, void *user_data) {
173 (void)channel; // Unused
175
176 if (!wrtc || !data || len == 0) {
177 return;
178 }
179
180 // Ordered DataChannels may split a large ACIP packet across messages.
181 const size_t limit = 8 * 1024 * 1024;
182 mutex_lock(&wrtc->queue_mutex);
183 if (len > limit || wrtc->partial_len > limit - len) {
185 log_error("WebRTC receive buffer exceeded");
186 webrtc_datachannel_close(channel);
187 return;
188 }
189 size_t required = wrtc->partial_len + len;
190 if (required > wrtc->partial_capacity) {
191 size_t capacity = wrtc->partial_capacity ? wrtc->partial_capacity : 16384;
192 while (capacity < required) {
193 capacity *= 2;
194 }
195 if (capacity > limit) {
196 capacity = limit;
197 }
198 wrtc->partial = SAFE_REALLOC(wrtc->partial, capacity, uint8_t *);
199 wrtc->partial_capacity = capacity;
200 }
201 memcpy(wrtc->partial + wrtc->partial_len, data, len);
202 wrtc->partial_len += len;
203 size_t offset = 0;
204 while (wrtc->partial_len - offset >= sizeof(packet_header_t)) {
205 packet_header_t header;
206 memcpy(&header, wrtc->partial + offset, sizeof(header));
207 size_t payload_len = NET_TO_HOST_U32(header.length);
208 if (NET_TO_HOST_U64(header.magic) != PACKET_MAGIC || payload_len > limit - sizeof(header)) {
210 log_error("Invalid ACIP packet on WebRTC DataChannel");
211 webrtc_datachannel_close(channel);
212 return;
213 }
214 size_t packet_len = sizeof(header) + payload_len;
215 if (wrtc->partial_len - offset < packet_len)
216 break;
217 webrtc_recv_msg_t msg = {.data = buffer_pool_alloc(NULL, packet_len), .len = packet_len};
218 if (!msg.data)
219 break;
220 memcpy(msg.data, wrtc->partial + offset, packet_len);
221 if (ringbuffer_is_full(wrtc->recv_queue)) {
222 discard_stale_media(wrtc);
223 }
224 if (!ringbuffer_write(wrtc->recv_queue, &msg)) {
225 bool media = queued_message_is_media(&msg);
226 if (media) {
227 log_warn_every(US_PER_SEC_INT, "WebRTC receive queue saturated; dropping newest media packet");
228 }
229 buffer_pool_free(NULL, msg.data, msg.len);
230 if (media) {
231 offset += packet_len;
232 continue;
233 }
235 log_error("WebRTC receive queue exceeded");
236 webrtc_datachannel_close(channel);
237 return;
238 }
239 // Keep video presentation current and prevent stale audio from filling the
240 // bounded queue ahead of newer media and control packets.
242 discard_stale_media(wrtc);
243 }
244 offset += packet_len;
245 }
246 wrtc->partial_len -= offset;
247 if (offset && wrtc->partial_len)
248 memmove(wrtc->partial, wrtc->partial + offset, wrtc->partial_len);
249 cond_signal(&wrtc->queue_cond);
251}
252
256static void webrtc_on_open(webrtc_data_channel_t *channel, void *user_data) {
257 (void)channel;
259
260 if (!wrtc) {
261 return;
262 }
263
264 mutex_lock(&wrtc->state_mutex);
265 wrtc->is_connected = true;
266 wrtc->data_channel_closed = false;
267 wrtc->disconnected_since_ns = 0;
269
270 log_info("WebRTC DataChannel opened, transport ready");
271}
272
276static void webrtc_on_error(webrtc_data_channel_t *channel, const char *error_msg, void *user_data) {
277 (void)channel;
279
280 log_error("WebRTC DataChannel error: %s", error_msg ? error_msg : "unknown error");
281
282 if (!wrtc) {
283 return;
284 }
285
286 // A DataChannel error can be reported while ICE is recovering. The peer
287 // connection state determines whether the transport is terminal.
289}
290
294static void webrtc_on_close(webrtc_data_channel_t *channel, void *user_data) {
295 (void)channel;
297
298 log_info("WebRTC DataChannel closed");
299
300 if (!wrtc) {
301 return;
302 }
303
304 mutex_lock(&wrtc->state_mutex);
305 wrtc->is_connected = false;
306 wrtc->data_channel_closed = true;
308
309 // Wake any blocking recv() calls
311}
312
313// =============================================================================
314// WebRTC Transport Methods
315// =============================================================================
316
317static bool webrtc_transport_is_connected_impl(webrtc_transport_data_t *wrtc) {
318 if (!wrtc) {
319 return false;
320 }
321
322 if (!wrtc->peer_conn || !wrtc->data_channel) {
323 return false;
324 }
325
326 mutex_lock(&wrtc->state_mutex);
327 bool close_requested = wrtc->close_requested;
328 bool data_channel_closed = wrtc->data_channel_closed;
329 bool connected = wrtc->is_connected;
332 if (close_requested || data_channel_closed) {
333 return false;
334 }
335
336 if (state == WEBRTC_STATE_CONNECTED) {
337 if (!connected || !webrtc_datachannel_is_open(wrtc->data_channel)) {
338 return false;
339 }
340 mutex_lock(&wrtc->state_mutex);
341 wrtc->disconnected_since_ns = 0;
343 return true;
344 }
345
346 if (state != WEBRTC_STATE_DISCONNECTED) {
347 return false;
348 }
349
350 // ICE can briefly enter DISCONNECTED while connectivity checks recover.
351 // Retain the DataChannel for a bounded window so a temporary network change
352 // does not restart the whole session; a stuck peer must still be cleaned up.
353 uint64_t now_ns = time_get_ns();
354 mutex_lock(&wrtc->state_mutex);
355 if (wrtc->disconnected_since_ns == 0) {
356 wrtc->disconnected_since_ns = now_ns;
357 }
358 bool within_grace = now_ns - wrtc->disconnected_since_ns <= WEBRTC_DISCONNECTED_GRACE_NS;
360 return within_grace;
361}
362
363static asciichat_error_t webrtc_send(acip_transport_t *transport, const void *data, size_t len) {
365
366 if (!webrtc_transport_is_connected_impl(wrtc)) {
367 return SET_ERRNO(ERROR_NETWORK, "WebRTC transport not connected");
368 }
369
370 mutex_lock(&wrtc->send_mutex);
371 uint64_t send_start_ns = time_get_ns();
372 size_t buffered_before_send = 0;
373 bool is_video_packet = false;
374 if (len >= sizeof(packet_header_t)) {
375 packet_header_t header;
376 memcpy(&header, data, sizeof(header));
377 if (NET_TO_HOST_U64(header.magic) == PACKET_MAGIC) {
378 uint16_t packet_type = NET_TO_HOST_U16(header.type);
379 is_video_packet = packet_type == PACKET_TYPE_IMAGE_FRAME || packet_type == PACKET_TYPE_ASCII_FRAME;
380 if (is_video_packet) {
381 asciichat_error_t buffered_result =
382 webrtc_datachannel_get_buffered_amount(wrtc->data_channel, &buffered_before_send);
383 if (buffered_result != ASCIICHAT_OK) {
384 buffered_before_send = 0;
385 CLEAR_ERRNO();
386 }
387 }
388 }
389 }
390
391 size_t max_message_size = 16384;
392 asciichat_error_t max_size_result =
394 if (max_size_result != ASCIICHAT_OK || max_message_size == 0) {
395 max_message_size = 16384;
396 CLEAR_ERRNO();
397 }
398 log_info_every(60 * NS_PER_SEC_INT, "WebRTC DataChannel negotiated max message size: %zu bytes",
399 max_message_size);
400
401 // A video packet is a replaceable snapshot. Admit it only when the
402 // DataChannel can hold the complete packet without queuing obsolete
403 // snapshots ahead of a current frame.
404 if (is_video_packet) {
405 size_t high_water = len > WEBRTC_SEND_VIDEO_HIGH_WATER ? len : WEBRTC_SEND_VIDEO_HIGH_WATER;
406 if (buffered_before_send >= high_water || len > high_water - buffered_before_send) {
407 mutex_unlock(&wrtc->send_mutex);
409 "WebRTC outgoing video backlog: dropping replaceable %zu-byte frame (buffered=%zu, limit=%zu)",
410 len, buffered_before_send, high_water);
411 return ASCIICHAT_OK;
412 }
413 }
414
416 for (size_t offset = 0; offset < len;) {
417 size_t chunk = len - offset;
418 if (chunk > max_message_size)
419 chunk = max_message_size;
420 result = webrtc_datachannel_send(wrtc->data_channel, (const uint8_t *)data + offset, chunk);
421 if (result != ASCIICHAT_OK)
422 break;
423 offset += chunk;
424 }
425 mutex_unlock(&wrtc->send_mutex);
426
427 uint64_t send_duration_ns = time_elapsed_ns(send_start_ns, time_get_ns());
428 if (send_duration_ns > 20 * NS_PER_MS_INT) {
429 log_warn_every(US_PER_SEC_INT, "WebRTC DataChannel send blocked %.1fms for %zu-byte packet (buffered=%zu)",
430 (double)send_duration_ns / 1e6, len, buffered_before_send);
431 }
432
433 if (result != ASCIICHAT_OK) {
434 return SET_ERRNO(ERROR_NETWORK, "Failed to send on WebRTC DataChannel");
435 }
436
437 return ASCIICHAT_OK;
438}
439
440static asciichat_error_t webrtc_recv(acip_transport_t *transport, void **buffer, size_t *out_len,
441 void **out_allocated_buffer) {
443
444 mutex_lock(&wrtc->queue_mutex);
445
446 // Block until message arrives or connection closes
447 while (ringbuffer_is_empty(wrtc->recv_queue)) {
448 if (!webrtc_transport_is_connected_impl(wrtc)) {
450 return SET_ERRNO(ERROR_NETWORK, "Connection closed while waiting for data");
451 }
452
453 // Wait for message arrival or connection close
454 // Peer-connection state changes do not necessarily produce a DataChannel
455 // callback. Periodically recheck the peer state so a lost connection can
456 // leave this blocking receive path and let its owner clean up.
457 cond_timedwait(&wrtc->queue_cond, &wrtc->queue_mutex, 250 * NS_PER_MS_INT);
458 }
459
460 // Read message from queue
462 bool success = ringbuffer_read(wrtc->recv_queue, &msg);
464
465 if (!success) {
466 return SET_ERRNO(ERROR_NETWORK, "Failed to read from receive queue");
467 }
468
469 // Return message to caller (caller owns the buffer)
470 *buffer = msg.data;
471 *out_len = msg.len;
472 *out_allocated_buffer = msg.data;
473
474 return ASCIICHAT_OK;
475}
476
477static asciichat_error_t webrtc_close(acip_transport_t *transport) {
479
480 mutex_lock(&wrtc->state_mutex);
481 if (wrtc->close_requested) {
483 return ASCIICHAT_OK; // Already closed
484 }
485
486 wrtc->close_requested = true;
487 wrtc->is_connected = false;
489
490 // Close the peer connection before deleting its DataChannel. libdatachannel
491 // tears down channel state as part of rtcClose(); deleting the channel first
492 // leaves rtcClose() trying to access an ID that no longer exists.
493 if (wrtc->peer_conn) {
495 }
496
497 // Delete the channel after the peer has stopped using it. Its owner will
498 // later destroy the wrapper; we only invalidate the native channel here so
499 // queued callbacks cannot retain this transport as user_data.
500 if (wrtc->data_channel) {
501 webrtc_datachannel_close(wrtc->data_channel);
502 }
503
504 // Wake any blocking recv() calls
506
507 log_debug("WebRTC transport closed");
508 return ASCIICHAT_OK;
509}
510
511static acip_transport_type_t webrtc_get_type(acip_transport_t *transport) {
512 (void)transport;
514}
515
516static socket_t webrtc_get_socket(acip_transport_t *transport) {
517 (void)transport;
518 return INVALID_SOCKET_VALUE; // WebRTC has no underlying socket
519}
520
521static bool webrtc_is_connected(acip_transport_t *transport) {
523 return webrtc_transport_is_connected_impl(wrtc);
524}
525
526// =============================================================================
527// WebRTC Transport Destroy Implementation
528// =============================================================================
529
539static void webrtc_destroy_impl(acip_transport_t *transport) {
540 if (!transport || !transport->impl_data) {
541 return;
542 }
543
545
546 // acip_transport_destroy() only invokes close() for transports that still
547 // report connected. A WebRTC peer can enter DISCONNECTED before destruction,
548 // leaving this transport's callbacks registered on the DataChannel. Deleting
549 // the channel here is the libdatachannel synchronization point: it waits for
550 // scheduled callbacks and prevents new ones before this callback userdata,
551 // queue, and mutexes are released.
552 (void)webrtc_close(transport);
553
554 // Clear receive queue and free buffered messages
555 if (wrtc->recv_queue) {
556 mutex_lock(&wrtc->queue_mutex);
557
559 while (ringbuffer_read(wrtc->recv_queue, &msg)) {
560 if (msg.data) {
561 buffer_pool_free(NULL, msg.data, msg.len);
562 msg.data = NULL;
563 }
564 }
565
568 wrtc->recv_queue = NULL;
569 }
570
571 SAFE_FREE(wrtc->partial);
572 wrtc->partial = NULL;
573 wrtc->partial_len = 0;
574 wrtc->partial_capacity = 0;
575
576 // Destroy synchronization primitives
579 cond_destroy(&wrtc->queue_cond);
581
582 log_debug("Destroyed WebRTC transport resources");
583}
584
585// =============================================================================
586// WebRTC Transport Method Table
587// =============================================================================
588
589static bool webrtc_has_pending_data(acip_transport_t *transport) {
590 if (!transport || !transport->impl_data)
591 return false;
593 mutex_lock(&wrtc->queue_mutex);
594 bool pending = !ringbuffer_is_empty(wrtc->recv_queue);
596 return pending;
597}
598
599static const acip_transport_methods_t webrtc_methods = {
600 .send = webrtc_send,
601 .recv = webrtc_recv,
602 .close = webrtc_close,
603 .get_type = webrtc_get_type,
604 .get_socket = webrtc_get_socket,
605 .is_connected = webrtc_is_connected,
606 .has_pending_data = webrtc_has_pending_data,
607 .destroy_impl = webrtc_destroy_impl,
608};
609
610// =============================================================================
611// WebRTC Transport Creation
612// =============================================================================
613
615 crypto_context_t *crypto_ctx) {
616 if (!peer_conn || !data_channel) {
617 SET_ERRNO(ERROR_INVALID_PARAM, "peer_conn and data_channel are required");
618 return NULL;
619 }
620
621 // Allocate transport structure
623 if (!transport) {
624 SET_ERRNO(ERROR_MEMORY, "Failed to allocate WebRTC transport");
625 return NULL;
626 }
627
628 // Allocate WebRTC-specific data
630 if (!wrtc_data) {
631 SAFE_FREE(transport);
632 SET_ERRNO(ERROR_MEMORY, "Failed to allocate WebRTC transport data");
633 return NULL;
634 }
635
636 // Create receive queue
638 if (!wrtc_data->recv_queue) {
639 SAFE_FREE(wrtc_data);
640 SAFE_FREE(transport);
641 SET_ERRNO(ERROR_MEMORY, "Failed to create receive queue");
642 return NULL;
643 }
644
645 // Initialize synchronization primitives
646 if (mutex_init(&wrtc_data->queue_mutex, "webrtc_queue") != 0) {
647 ringbuffer_destroy(wrtc_data->recv_queue);
648 SAFE_FREE(wrtc_data);
649 SAFE_FREE(transport);
650 SET_ERRNO(ERROR_INTERNAL, "Failed to initialize queue mutex");
651 return NULL;
652 }
653
654 if (cond_init(&wrtc_data->queue_cond, "queue") != 0) {
655 mutex_destroy(&wrtc_data->queue_mutex);
656 ringbuffer_destroy(wrtc_data->recv_queue);
657 SAFE_FREE(wrtc_data);
658 SAFE_FREE(transport);
659 SET_ERRNO(ERROR_INTERNAL, "Failed to initialize queue condition variable");
660 return NULL;
661 }
662
663 if (mutex_init(&wrtc_data->state_mutex, "webrtc_state") != 0) {
664 cond_destroy(&wrtc_data->queue_cond);
665 mutex_destroy(&wrtc_data->queue_mutex);
666 ringbuffer_destroy(wrtc_data->recv_queue);
667 SAFE_FREE(wrtc_data);
668 SAFE_FREE(transport);
669 SET_ERRNO(ERROR_INTERNAL, "Failed to initialize state mutex");
670 return NULL;
671 }
672
673 if (mutex_init(&wrtc_data->send_mutex, "webrtc_send") != 0) {
674 mutex_destroy(&wrtc_data->state_mutex);
675 cond_destroy(&wrtc_data->queue_cond);
676 mutex_destroy(&wrtc_data->queue_mutex);
677 ringbuffer_destroy(wrtc_data->recv_queue);
678 SAFE_FREE(wrtc_data);
679 SAFE_FREE(transport);
680 SET_ERRNO(ERROR_INTERNAL, "Failed to initialize send mutex");
681 return NULL;
682 }
683
684 // Initialize WebRTC data
685 wrtc_data->peer_conn = peer_conn;
686 wrtc_data->data_channel = data_channel;
687 wrtc_data->is_connected = false; // Will be set to true in on_open callback
688
689 // Register DataChannel callbacks
691 .on_open = webrtc_on_open,
692 .on_close = webrtc_on_close,
693 .on_error = webrtc_on_error,
694 .on_message = webrtc_on_message,
695 .user_data = wrtc_data,
696 };
697
698 asciichat_error_t result = webrtc_datachannel_set_callbacks(data_channel, &callbacks);
699 if (result != ASCIICHAT_OK) {
700 mutex_destroy(&wrtc_data->send_mutex);
701 mutex_destroy(&wrtc_data->state_mutex);
702 cond_destroy(&wrtc_data->queue_cond);
703 mutex_destroy(&wrtc_data->queue_mutex);
704 ringbuffer_destroy(wrtc_data->recv_queue);
705 SAFE_FREE(wrtc_data);
706 SAFE_FREE(transport);
707 SET_ERRNO(ERROR_INTERNAL, "Failed to set DataChannel callbacks");
708 return NULL;
709 }
710
711 // IMPORTANT: The transport is always created from peer_manager's on_datachannel_open callback,
712 // which means the DataChannel is ALREADY OPEN when we get here. However, by setting our own
713 // callbacks above (webrtc_datachannel_set_callbacks), we replaced the callbacks that would
714 // have set dc->is_open=true. So we need to manually mark both the transport AND the DataChannel
715 // as open/connected now.
716 //
717 // We cannot rely on webrtc_on_open being called later because:
718 // 1. The DataChannel is already open
719 // 2. libdatachannel won't fire the open event again
720 // 3. Setting callbacks after open doesn't trigger a retroactive open event
721
722 // Mark DataChannel as open (needed for webrtc_datachannel_send() check)
723 webrtc_datachannel_set_open_state(data_channel, true);
724
725 // Mark transport as connected
726 mutex_lock(&wrtc_data->state_mutex);
727 wrtc_data->is_connected = true;
728 mutex_unlock(&wrtc_data->state_mutex);
729 log_debug("Transport and DataChannel marked as connected/open (already open from peer_manager callback)");
730
731 // Initialize transport
732 transport->methods = &webrtc_methods;
733 transport->crypto_ctx = crypto_ctx;
734 transport->impl_data = wrtc_data;
735
736 log_info("Created WebRTC transport (crypto: %s)", crypto_ctx ? "enabled" : "disabled");
737
738 // Register WebRTC transport implementation data with transport as parent
739 NAMED_REGISTER(wrtc_data, "impl", "webrtc_impl", "0x%tx", (uintptr_t)(const void *)(transport));
740
741 return transport;
742}
🗃️ Lock-Free Unified Memory Buffer Pool with Lazy Allocation
Cross-platform condition variable interface for ascii-chat.
Named object registry for debugging — log identifiable resource names.
🔄 Network byte order conversion helpers
#define NET_TO_HOST_U64(val)
Definition endian.h:228
#define NET_TO_HOST_U16(val)
Definition endian.h:111
#define NET_TO_HOST_U32(val)
Definition endian.h:81
void buffer_pool_free(buffer_pool_t *pool, const void *data, size_t size)
Free a buffer back to the pool (lock-free)
void * buffer_pool_alloc(buffer_pool_t *pool, size_t size)
Allocate a buffer from the pool (lock-free fast path)
#define SAFE_REALLOC(ptr, size, cast)
Definition common.h:284
unsigned short uint16_t
Definition common.h:57
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#define SAFE_CALLOC(count, size, cast)
Definition common.h:274
unsigned long long uint64_t
Definition common.h:59
unsigned char uint8_t
Definition common.h:56
#define NAMED_REGISTER(ptr, name, type, fmt, parent_ptr)
Register any pointer with base name, type, format spec, and location (auto-suffix)
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
#define CLEAR_ERRNO()
Clear the current error state.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_NETWORK
Definition error_codes.h:77
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
@ ERROR_INTERNAL
Definition error_codes.h:92
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
uint64_t time_get_ns(void)
Get current monotonic time in nanoseconds.
Definition util/time.c:108
#define NS_PER_SEC_INT
Definition time.h:157
#define NS_PER_MS_INT
Definition time.h:156
uint64_t time_elapsed_ns(uint64_t start_ns, uint64_t end_ns)
Calculate elapsed time with wraparound safety.
Definition util/time.c:150
#define US_PER_SEC_INT
Definition time.h:161
packet_type_t
Network protocol packet type enumeration.
Definition packet.h:286
#define PACKET_MAGIC
Packet magic number (alias for MAGIC_PACKET_VALID)
Definition packet.h:255
@ PACKET_TYPE_AUDIO_OPUS_BATCH
Batched Opus-encoded audio frames.
Definition packet.h:374
@ PACKET_TYPE_IMAGE_FRAME
Complete RGB image with dimensions.
Definition packet.h:363
@ PACKET_TYPE_ASCII_FRAME
Complete ASCII frame with all metadata.
Definition packet.h:361
@ PACKET_TYPE_AUDIO_BATCH
Batched audio packets for efficiency.
Definition packet.h:372
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
int cond_broadcast(cond_t *cond)
Broadcast to a condition variable (wake all waiting threads)
#define INVALID_SOCKET_VALUE
Invalid socket value (POSIX: -1)
Definition socket.h:278
int mutex_init(mutex_t *mutex, const char *name)
Initialize a mutex with a name.
Definition threading.c:16
int cond_signal(cond_t *cond)
Signal a condition variable (wake one waiting thread)
int cond_init(cond_t *cond, const char *name)
Initialize a condition variable with a name.
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
#define cond_timedwait(cond, mutex, timeout_ns)
Wait on a condition variable with timeout (with debug tracking in debug builds)
Definition cond.h:300
int cond_destroy(cond_t *cond)
Destroy a condition variable.
int mutex_destroy(mutex_t *mutex)
Destroy a mutex.
Definition threading.c:22
ringbuffer_t * ringbuffer_create(size_t element_size, size_t capacity)
Create a new ring buffer.
Definition ringbuffer.c:29
size_t ringbuffer_size(const ringbuffer_t *rb)
Get current number of elements in the buffer.
Definition ringbuffer.c:121
bool ringbuffer_is_full(const ringbuffer_t *rb)
Check if buffer is full.
Definition ringbuffer.c:129
void ringbuffer_destroy(ringbuffer_t *rb)
Destroy a ring buffer and free its memory.
Definition ringbuffer.c:53
bool ringbuffer_is_empty(const ringbuffer_t *rb)
Check if buffer is empty.
Definition ringbuffer.c:125
bool ringbuffer_read(ringbuffer_t *rb, void *data)
Try to read an element from the ring buffer (non-blocking)
Definition ringbuffer.c:82
bool ringbuffer_write(ringbuffer_t *rb, const void *data)
Try to write an element to the ring buffer (non-blocking)
Definition ringbuffer.c:60
asciichat_error_t webrtc_datachannel_set_callbacks(webrtc_data_channel_t *dc, const webrtc_datachannel_callbacks_t *callbacks)
Set DataChannel callbacks.
webrtc_state_t webrtc_get_state(webrtc_peer_connection_t *pc)
Get current connection state.
asciichat_error_t webrtc_datachannel_get_max_message_size(webrtc_data_channel_t *dc, size_t *size)
asciichat_error_t webrtc_datachannel_get_buffered_amount(webrtc_data_channel_t *dc, size_t *amount)
void webrtc_peer_connection_close(webrtc_peer_connection_t *pc)
Close a peer connection.
webrtc_state_t
WebRTC connection state.
bool webrtc_datachannel_is_open(webrtc_data_channel_t *dc)
Check if DataChannel is open and ready.
asciichat_error_t webrtc_datachannel_send(webrtc_data_channel_t *dc, const uint8_t *data, size_t size)
Send data over DataChannel.
void webrtc_datachannel_set_open_state(webrtc_data_channel_t *dc, bool is_open)
Set DataChannel open state (internal use)
@ WEBRTC_STATE_DISCONNECTED
Connection lost.
@ WEBRTC_STATE_CONNECTED
DataChannel established and ready.
int socket_t
📝 Logging API with multiple log levels and terminal output control
#define log_info_every(interval_us, fmt,...)
Rate-limited INFO logging.
Definition log/log.h:705
#define log_warn_every(interval_us, fmt,...)
Rate-limited WARN logging.
Definition log/log.h:708
Cross-platform mutex interface for ascii-chat.
Lock-Free Ring Buffer and Frame Buffer Management.
Transport method table (virtual function table)
Definition transport.h:115
asciichat_error_t(* send)(acip_transport_t *transport, const void *data, size_t len)
Send data through this transport.
Definition transport.h:129
Transport instance structure.
Definition transport.h:214
void * impl_data
Transport-specific state.
Definition transport.h:218
const acip_transport_methods_t * methods
Method table (virtual functions)
Definition transport.h:215
crypto_context_t * crypto_ctx
Optional encryption context.
Definition transport.h:216
Condition variable type (POSIX: pthread_cond_t with debug tracking)
Definition cond.h:63
Cryptographic context structure.
Mutex type (POSIX: pthread_mutex_t with debug tracking)
Network packet header structure.
Definition packet.h:598
uint32_t length
Payload data length in bytes (0 for header-only packets)
Definition packet.h:604
uint64_t magic
Magic number (PACKET_MAGIC = 0xA5C11C4A1 "ASCIICHAT" in hex) for packet validation.
Definition packet.h:600
uint16_t type
Packet type (packet_type_t enumeration)
Definition packet.h:602
Lock-free ring buffer structure.
Definition ringbuffer.h:88
WebRTC data channel for sending/receiving messages.
void(* on_open)(webrtc_data_channel_t *dc, void *user_data)
Channel opened.
WebRTC peer connection state.
Receive queue element (variable-length message)
size_t len
Message length in bytes.
uint8_t * data
Message data (allocated, caller must free)
WebRTC transport implementation data.
bool close_requested
Whether this transport has closed its borrowed peer handles.
uint8_t * partial
Incomplete ACIP packet bytes.
webrtc_peer_connection_t * peer_conn
Borrowed peer connection.
uint64_t disconnected_since_ns
Start of the current transient ICE disconnect.
bool is_connected
Connection state.
mutex_t queue_mutex
Protect queue operations.
webrtc_data_channel_t * data_channel
Borrowed data channel.
mutex_t send_mutex
Keep chunks from concurrent packets together.
bool data_channel_closed
DataChannel close is terminal for this transport.
mutex_t state_mutex
Protect state changes.
cond_t queue_cond
Signal when messages arrive.
ringbuffer_t * recv_queue
Receive message queue.
⏱️ High-precision timing utilities using sokol_time.h and uthash
Transport abstraction layer for ACIP protocol.
acip_transport_type_t
Transport type enumeration.
Definition transport.h:93
@ ACIP_TRANSPORT_WEBRTC
WebRTC DataChannel (P2P)
Definition transport.h:96
#define WEBRTC_RECV_QUEUE_HIGH_WATER
#define WEBRTC_SEND_VIDEO_HIGH_WATER
#define WEBRTC_RECV_QUEUE_SIZE
Maximum receive queue size (messages buffered before recv())
acip_transport_t * acip_webrtc_transport_create(webrtc_peer_connection_t *peer_conn, webrtc_data_channel_t *data_channel, crypto_context_t *crypto_ctx)
#define WEBRTC_DISCONNECTED_GRACE_NS