ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
WebRTC Peer Manager

Manage WebRTC peer connections for session participants. More...

Data Structures

struct  webrtc_peer_manager_config_t
 Peer manager configuration. More...
 
struct  webrtc_signaling_callbacks_t
 Signaling callbacks for sending SDP/ICE. More...
 

Typedefs

typedef struct webrtc_peer_manager webrtc_peer_manager_t
 
typedef void(* webrtc_transport_ready_callback_t) (acip_transport_t *transport, const uint8_t participant_id[16], void *user_data)
 Callback when DataChannel is ready and wrapped in ACIP transport.
 
typedef void(* webrtc_gathering_timeout_callback_t) (const uint8_t participant_id[16], uint32_t timeout_ms, uint64_t elapsed_ms, void *user_data)
 Callback when ICE candidate gathering is unusually slow.
 
typedef asciichat_error_t(* webrtc_send_sdp_callback_t) (const uint8_t session_id[16], const uint8_t recipient_id[16], const char *sdp_type, const char *sdp, void *user_data)
 Callback to send SDP via ACDS signaling.
 
typedef asciichat_error_t(* webrtc_send_ice_callback_t) (const uint8_t session_id[16], const uint8_t recipient_id[16], const char *candidate, const char *mid, void *user_data)
 Callback to send ICE candidate via ACDS signaling.
 

Enumerations

enum  webrtc_peer_role_t { WEBRTC_ROLE_CREATOR = 0 , WEBRTC_ROLE_JOINER = 1 }
 Peer connection role (server or client) More...
 

Functions

asciichat_error_t webrtc_peer_manager_create (const webrtc_peer_manager_config_t *config, const webrtc_signaling_callbacks_t *signaling_callbacks, webrtc_peer_manager_t **manager_out)
 Create a WebRTC peer manager.
 
void webrtc_peer_manager_destroy (webrtc_peer_manager_t *manager)
 Destroy peer manager and close all connections.
 
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.
 
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_connect (webrtc_peer_manager_t *manager, const uint8_t session_id[16], const uint8_t participant_id[16])
 Initiate connection to remote peer (joiner role only)
 
bool webrtc_peer_manager_get_state (webrtc_peer_manager_t *manager, const uint8_t participant_id[16], webrtc_state_t *state_out)
 
asciichat_error_t webrtc_peer_manager_remove_peer (webrtc_peer_manager_t *manager, const uint8_t participant_id[16])
 
int webrtc_peer_manager_check_gathering_timeouts (webrtc_peer_manager_t *manager, uint32_t timeout_ms)
 Report slow ICE gathering and remove failed connections.
 

Detailed Description

Manage WebRTC peer connections for session participants.

Typedef Documentation

◆ webrtc_gathering_timeout_callback_t

typedef void(* webrtc_gathering_timeout_callback_t) (const uint8_t participant_id[16], uint32_t timeout_ms, uint64_t elapsed_ms, void *user_data)

#include <peer_manager.h>

Callback when ICE candidate gathering is unusually slow.

Parameters
participant_idRemote participant UUID (16 bytes)
timeout_msConfigured timeout in milliseconds
elapsed_msActual elapsed time in milliseconds
user_dataUser context pointer

Called once when a peer connection's ICE gathering exceeds the configured timeout. Trickle ICE may still connect the peer, so this callback does not imply that the peer connection will be closed.

Definition at line 84 of file peer_manager.h.

◆ webrtc_peer_manager_t

#include <peer_manager.h>

Definition at line 50 of file peer_manager.h.

◆ webrtc_send_ice_callback_t

typedef asciichat_error_t(* webrtc_send_ice_callback_t) (const uint8_t session_id[16], const uint8_t recipient_id[16], const char *candidate, const char *mid, void *user_data)

#include <peer_manager.h>

Callback to send ICE candidate via ACDS signaling.

Parameters
session_idSession UUID (16 bytes)
recipient_idRecipient participant UUID (16 bytes, all zeros for broadcast)
candidateICE candidate string (null-terminated)
midMedia stream ID (null-terminated)
user_dataUser context pointer
Returns
ASCIICHAT_OK on success, error code on failure

Implementation should send PACKET_TYPE_ACIP_WEBRTC_ICE via ACDS.

Definition at line 131 of file peer_manager.h.

◆ webrtc_send_sdp_callback_t

typedef asciichat_error_t(* webrtc_send_sdp_callback_t) (const uint8_t session_id[16], const uint8_t recipient_id[16], const char *sdp_type, const char *sdp, void *user_data)

#include <peer_manager.h>

Callback to send SDP via ACDS signaling.

Parameters
session_idSession UUID (16 bytes)
recipient_idRecipient participant UUID (16 bytes, all zeros for broadcast)
sdp_typeSDP type ("offer" or "answer")
sdpSDP string (null-terminated)
user_dataUser context pointer
Returns
ASCIICHAT_OK on success, error code on failure

Implementation should send PACKET_TYPE_ACIP_WEBRTC_SDP via ACDS.

Definition at line 117 of file peer_manager.h.

◆ webrtc_transport_ready_callback_t

typedef void(* webrtc_transport_ready_callback_t) (acip_transport_t *transport, const uint8_t participant_id[16], void *user_data)

#include <peer_manager.h>

Callback when DataChannel is ready and wrapped in ACIP transport.

Parameters
transportACIP transport wrapping the WebRTC DataChannel
participant_idRemote participant UUID (16 bytes)
user_dataUser context pointer

The callback receives ownership of the transport and must either:

Definition at line 70 of file peer_manager.h.

Enumeration Type Documentation

◆ webrtc_peer_role_t

#include <peer_manager.h>

Peer connection role (server or client)

Enumerator
WEBRTC_ROLE_CREATOR 

Session creator - accepts offers, generates answers.

WEBRTC_ROLE_JOINER 

Session joiner - generates offers, receives answers.

Definition at line 55 of file peer_manager.h.

55 {
webrtc_peer_role_t
Peer connection role (server or client)
@ WEBRTC_ROLE_JOINER
Session joiner - generates offers, receives answers.
@ WEBRTC_ROLE_CREATOR
Session creator - accepts offers, generates answers.

Function Documentation

◆ webrtc_peer_manager_check_gathering_timeouts()

int webrtc_peer_manager_check_gathering_timeouts ( webrtc_peer_manager_t *  manager,
uint32_t  timeout_ms 
)

#include <peer_manager.h>

Report slow ICE gathering and remove failed connections.

Parameters
managerPeer manager
timeout_msTimeout threshold in milliseconds
Returns
Number of failed or closed peer connections removed

Iterates through all active peer connections and checks if ICE gathering has exceeded the specified timeout or the connection has failed or closed. Slow gathering is reported once but left active because trickle ICE may still establish the connection. Failed or closed connections are removed.

This should be called periodically (e.g., every 100ms) during connection establishment to detect and handle gathering failures.

Note
Thread-safe - uses internal locking

Definition at line 754 of file peer_manager.c.

754 {
755 if (!manager) {
756 return 0;
757 }
758
759 int timeout_count = 0;
760 peer_entry_t *peer = NULL, *tmp = NULL;
761
762 mutex_lock(&manager->signaling_mutex);
763 mutex_lock(&manager->peers_mutex);
764
765 // Iterate through all peers and check for gathering timeout
766 HASH_ITER(hh, manager->peers, peer, tmp) {
767 if (!peer || !peer->pc) {
768 continue;
769 }
770
771 // Trickle ICE can connect before gathering completes. Report a slow gather,
772 // but keep the peer alive until libdatachannel reports a failed connection.
773 webrtc_state_t connection_state = webrtc_get_state(peer->pc);
774 bool gathering_timed_out = webrtc_is_gathering_timed_out(peer->pc, timeout_ms);
775 if (gathering_timed_out && !peer->gathering_timeout_reported) {
776 peer->gathering_timeout_reported = true;
777 if (manager->config.on_gathering_timeout) {
778 manager->config.on_gathering_timeout(peer->participant_id, timeout_ms, timeout_ms, manager->config.user_data);
779 }
780 log_warn("ICE gathering exceeded %ums; keeping peer alive while trickle ICE continues", timeout_ms);
781 }
782
783 if (connection_state == WEBRTC_STATE_FAILED || connection_state == WEBRTC_STATE_CLOSED) {
785
786 log_error("WebRTC negotiation failed for peer (participant_id=%02x%02x%02x%02x..., timeout=%ums, "
787 "gathering=%d, connection=%d)",
788 peer->participant_id[0], peer->participant_id[1], peer->participant_id[2], peer->participant_id[3],
789 timeout_ms, state, connection_state);
790
791 // Remove and close peers whose ICE connection has actually failed.
792 remove_peer_locked(manager, peer);
793 timeout_count++;
794
795 log_info("Closed and removed timed-out peer connection (count: %d)", timeout_count);
796 }
797 }
798
799 mutex_unlock(&manager->peers_mutex);
800 mutex_unlock(&manager->signaling_mutex);
801
802 return timeout_count;
803}
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#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 mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
webrtc_state_t webrtc_get_state(webrtc_peer_connection_t *pc)
Get current connection state.
webrtc_gathering_state_t webrtc_get_gathering_state(webrtc_peer_connection_t *pc)
Get current ICE gathering state.
webrtc_gathering_state_t
ICE gathering state.
webrtc_state_t
WebRTC connection state.
bool webrtc_is_gathering_timed_out(webrtc_peer_connection_t *pc, uint32_t timeout_ms)
Check if ICE gathering has timed out.
@ WEBRTC_STATE_FAILED
Connection failed (fatal)
@ WEBRTC_STATE_CLOSED
Connection closed cleanly.
Per-peer connection state.
webrtc_peer_connection_t * pc
WebRTC peer connection.
uint8_t participant_id[16]
Remote participant UUID (hash key)
bool gathering_timeout_reported
ICE gathering timed out while trickle candidates may still connect.
void * user_data
Passed to callbacks.
webrtc_gathering_timeout_callback_t on_gathering_timeout
Called when ICE gathering times out.
mutex_t peers_mutex
Protect peers hash table.
peer_entry_t * peers
Hash table of peer connections.
webrtc_peer_manager_config_t config
Manager configuration.
mutex_t signaling_mutex
Serialize SDP and ICE updates for each peer.

References config, peer_entry_t::gathering_timeout_reported, log_error, log_info, log_warn, mutex_lock, mutex_unlock, webrtc_peer_manager_config_t::on_gathering_timeout, peer_entry_t::participant_id, peer_entry_t::pc, peers, peers_mutex, signaling_mutex, webrtc_peer_manager_config_t::user_data, webrtc_get_gathering_state(), webrtc_get_state(), webrtc_is_gathering_timed_out(), WEBRTC_STATE_CLOSED, and WEBRTC_STATE_FAILED.

Referenced by discovery_session_process().

◆ webrtc_peer_manager_connect()

asciichat_error_t webrtc_peer_manager_connect ( webrtc_peer_manager_t *  manager,
const uint8_t  session_id[16],
const uint8_t  participant_id[16] 
)

#include <peer_manager.h>

Initiate connection to remote peer (joiner role only)

Parameters
managerPeer manager
session_idSession UUID (16 bytes)
participant_idRemote participant UUID to connect to (16 bytes)
Returns
ASCIICHAT_OK on success, error code on failure

Creates peer connection, generates SDP offer, and sends via signaling. Only valid for WEBRTC_ROLE_JOINER (session joiners initiate connections).

Creator role uses webrtc_peer_manager_handle_sdp() to accept offers instead.

Definition at line 684 of file peer_manager.c.

685 {
686 if (!manager || !session_id || !participant_id) {
687 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
688 }
689
690 if (manager->role != WEBRTC_ROLE_JOINER) {
691 return SET_ERRNO(ERROR_INVALID_PARAM, "Only joiners can initiate connections");
692 }
693
694 log_debug("webrtc_peer_manager_connect: session_id=%02x%02x%02x%02x..., participant_id=%02x%02x%02x%02x...",
697
698 mutex_lock(&manager->peers_mutex);
699
700 // Create peer connection
701 peer_entry_t *peer;
702 asciichat_error_t result = create_peer_connection_locked(manager, session_id, participant_id, &peer);
703 if (result != ASCIICHAT_OK) {
704 mutex_unlock(&manager->peers_mutex);
705 return SET_ERRNO(result, "Failed to create peer connection");
706 }
707
708 mutex_unlock(&manager->peers_mutex);
709
710 // Note: SDP offer is automatically created by libdatachannel when rtcCreateDataChannel() is called
711 // The on_local_description callback will be triggered automatically with the offer
712 // No need to manually call webrtc_create_offer() - doing so causes "Unexpected local description" error
713
714 log_info("Initiated WebRTC connection to participant (offer auto-created by DataChannel)");
715
716 return ASCIICHAT_OK;
717}
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
uint8_t session_id[16]
uint8_t participant_id[16]
webrtc_peer_role_t role
Session role.

References ASCIICHAT_OK, ERROR_INVALID_PARAM, log_debug, log_info, mutex_lock, mutex_unlock, participant_id, peers_mutex, role, session_id, SET_ERRNO, and WEBRTC_ROLE_JOINER.

Referenced by discovery_session_process().

◆ webrtc_peer_manager_create()

asciichat_error_t webrtc_peer_manager_create ( const webrtc_peer_manager_config_t *  config,
const webrtc_signaling_callbacks_t *  signaling_callbacks,
webrtc_peer_manager_t **  manager_out 
)

#include <peer_manager.h>

Create a WebRTC peer manager.

Parameters
configManager configuration
signaling_callbacksCallbacks for sending SDP/ICE
manager_outOutput parameter for manager handle
Returns
ASCIICHAT_OK on success, error code on failure

Creates a manager for WebRTC peer connections. The manager handles:

  • Peer connection creation and lifecycle
  • SDP offer/answer generation and processing
  • ICE candidate exchange
  • ACIP transport wrapping when DataChannel opens

Definition at line 431 of file peer_manager.c.

433 {
434 if (!config || !signaling_callbacks || !manager_out) {
435 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
436 }
437 *manager_out = NULL;
438
439 if (!signaling_callbacks->send_sdp || !signaling_callbacks->send_ice) {
440 return SET_ERRNO(ERROR_INVALID_PARAM, "Signaling callbacks required");
441 }
442 if (config->relay_only && config->turn_count == 0) {
443 return SET_ERRNO(ERROR_INVALID_PARAM, "Relay only requires a TURN server with credentials");
444 }
445
446 // Allocate manager
448 if (!manager) {
449 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate peer manager");
450 }
451
452 memcpy(&manager->config, config, sizeof(*config));
453 memcpy(&manager->signaling, signaling_callbacks, sizeof(*signaling_callbacks));
454 manager->role = config->role;
455 manager->peers = NULL;
456 manager->pending_ice = NULL;
457 manager->pending_ice_count = 0;
458
459 if (mutex_init(&manager->peers_mutex, "peers") != 0) {
460 SAFE_FREE(manager);
461 return SET_ERRNO(ERROR_INTERNAL, "Failed to initialize peers mutex");
462 }
463 if (mutex_init(&manager->signaling_mutex, "webrtc_signaling") != 0) {
464 mutex_destroy(&manager->peers_mutex);
465 SAFE_FREE(manager);
466 return SET_ERRNO(ERROR_INTERNAL, "Failed to initialize WebRTC signaling mutex");
467 }
468
469 log_info("Created WebRTC peer manager (role: %s)", manager->role == WEBRTC_ROLE_CREATOR ? "creator" : "joiner");
470
471 *manager_out = manager;
472 return ASCIICHAT_OK;
473}
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
@ ERROR_MEMORY
Definition error_codes.h:56
@ ERROR_INTERNAL
Definition error_codes.h:92
int mutex_init(mutex_t *mutex, const char *name)
Initialize a mutex with a name.
Definition threading.c:16
int mutex_destroy(mutex_t *mutex)
Destroy a mutex.
Definition threading.c:22
webrtc_peer_role_t role
Session role (creator or joiner)
size_t turn_count
Number of TURN servers.
bool relay_only
Restrict ICE to TURN relay candidates.
WebRTC peer manager structure.
pending_ice_candidate_t * pending_ice
ICE delivered before the peer's remote SDP.
webrtc_signaling_callbacks_t signaling
Signaling callbacks.
webrtc_send_sdp_callback_t send_sdp
Send SDP via ACDS.
webrtc_send_ice_callback_t send_ice
Send ICE via ACDS.

References ASCIICHAT_OK, config, ERROR_INTERNAL, ERROR_INVALID_PARAM, ERROR_MEMORY, log_info, mutex_destroy(), mutex_init(), peers, peers_mutex, pending_ice, pending_ice_count, webrtc_peer_manager_config_t::relay_only, webrtc_peer_manager_config_t::role, role, SAFE_FREE, SAFE_MALLOC, webrtc_signaling_callbacks_t::send_ice, webrtc_signaling_callbacks_t::send_sdp, SET_ERRNO, signaling, signaling_mutex, webrtc_peer_manager_config_t::turn_count, and WEBRTC_ROLE_CREATOR.

◆ webrtc_peer_manager_destroy()

void webrtc_peer_manager_destroy ( webrtc_peer_manager_t *  manager)

#include <peer_manager.h>

Destroy peer manager and close all connections.

Parameters
managerPeer manager to destroy

Closes all active peer connections gracefully and frees resources. Safe to call with NULL pointer.

Definition at line 475 of file peer_manager.c.

475 {
476 if (!manager) {
477 return;
478 }
479
480 mutex_lock(&manager->signaling_mutex);
481 mutex_lock(&manager->peers_mutex);
482
483 // Close all peer connections
484 peer_entry_t *peer = NULL, *tmp = NULL;
485 HASH_ITER(hh, manager->peers, peer, tmp) {
486 remove_peer_locked(manager, peer);
487 }
488
489 mutex_unlock(&manager->peers_mutex);
490 pending_ice_candidate_t *pending = manager->pending_ice;
491 while (pending) {
492 pending_ice_candidate_t *next = pending->next;
493 free_pending_ice_candidate(pending);
494 pending = next;
495 }
496 mutex_unlock(&manager->signaling_mutex);
498 mutex_destroy(&manager->peers_mutex);
499
500 SAFE_FREE(manager);
501
502 log_debug("Destroyed WebRTC peer manager");
503}
struct pending_ice_candidate * next

References log_debug, mutex_destroy(), mutex_lock, mutex_unlock, pending_ice_candidate::next, peers, peers_mutex, pending_ice, SAFE_FREE, and signaling_mutex.

Referenced by discovery_session_destroy(), and discovery_session_process().

◆ webrtc_peer_manager_get_state()

bool webrtc_peer_manager_get_state ( webrtc_peer_manager_t *  manager,
const uint8_t  participant_id[16],
webrtc_state_t *  state_out 
)

#include <peer_manager.h>

Return the current state for a participant's peer connection.

Definition at line 719 of file peer_manager.c.

720 {
721 if (!manager || !participant_id || !state_out) {
722 return false;
723 }
724
725 mutex_lock(&manager->peers_mutex);
726 peer_entry_t *peer = find_peer_locked(manager, participant_id);
727 bool found = peer && peer->pc;
728 if (found) {
729 *state_out = webrtc_get_state(peer->pc);
730 }
731 mutex_unlock(&manager->peers_mutex);
732 return found;
733}

References mutex_lock, mutex_unlock, participant_id, peer_entry_t::pc, peers_mutex, and webrtc_get_state().

Referenced by discovery_session_process().

◆ webrtc_peer_manager_handle_ice()

asciichat_error_t webrtc_peer_manager_handle_ice ( webrtc_peer_manager_t *  manager,
const acip_webrtc_ice_t *  ice 
)

#include <peer_manager.h>

Handle incoming ICE candidate from ACDS.

Parameters
managerPeer manager
iceICE candidate message received from ACDS
Returns
ASCIICHAT_OK on success, error code on failure

Adds remote ICE candidate to the appropriate peer connection. If peer connection doesn't exist yet, queues candidate for later.

Definition at line 626 of file peer_manager.c.

626 {
627 if (!manager || !ice) {
628 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
629 }
630
631 // Extract ICE candidate and mid (both are null-terminated in the packet)
632 const char *candidate = (const char *)(ice + 1); // After header
633 size_t candidate_str_len = strlen(candidate);
634 const char *mid = candidate + candidate_str_len + 1; // After candidate + null terminator
635
636 log_debug("Handling incoming ICE candidate from remote peer (mid=%s)", mid);
637 log_debug(" [3] After ACDS recv - candidate: '%s' (len=%zu)", candidate, strlen(candidate));
638 log_debug(" [3] After ACDS recv - mid: '%s' (len=%zu)", mid, strlen(mid));
639 log_debug(" [3] After ACDS recv - header.candidate_len=%u", NET_TO_HOST_U16(ice->candidate_len));
640
641 // Hex dump first 100 bytes of payload for debugging
642 const uint8_t *payload = (const uint8_t *)(ice + 1);
643 log_debug(" [3] Hex dump of payload (first 100 bytes):");
644 for (int i = 0; i < 100 && i < (int)candidate_str_len + 20; i += 16) {
645 char hex[64] = {0};
646 char ascii[20] = {0};
647 for (int j = 0; j < 16 && (i + j) < 100; j++) {
648 snprintf(hex + j * 3, sizeof(hex) - j * 3, "%02x ", payload[i + j]);
649 ascii[j] = (payload[i + j] >= 32 && payload[i + j] < 127) ? payload[i + j] : '.';
650 }
651 log_debug(" [%04x] %-48s %s", i, hex, ascii);
652 }
653
654 // Do not pass a candidate to libdatachannel until a preceding SDP packet has
655 // finished installing the remote ICE transport.
656 mutex_lock(&manager->signaling_mutex);
657 mutex_lock(&manager->peers_mutex);
658
659 // Find peer connection
660 peer_entry_t *peer = find_peer_locked(manager, ice->sender_id);
661 if (!peer || !peer->remote_description_set) {
662 mutex_unlock(&manager->peers_mutex);
663 asciichat_error_t result = queue_pending_ice_candidate(manager, ice->sender_id, candidate, mid);
664 mutex_unlock(&manager->signaling_mutex);
665 if (result == ASCIICHAT_OK) {
666 log_debug("Queued ICE candidate received before remote SDP was installed");
667 }
668 return result;
669 }
670
671 mutex_unlock(&manager->peers_mutex);
672
673 // Add remote ICE candidate
674 asciichat_error_t result = webrtc_add_remote_candidate(peer->pc, candidate, mid);
675 if (result != ASCIICHAT_OK) {
676 mutex_unlock(&manager->signaling_mutex);
677 return SET_ERRNO(result, "Failed to add remote ICE candidate");
678 }
679
680 mutex_unlock(&manager->signaling_mutex);
681 return ASCIICHAT_OK;
682}
#define NET_TO_HOST_U16(val)
Definition endian.h:111
unsigned char uint8_t
Definition common.h:56
asciichat_error_t webrtc_add_remote_candidate(webrtc_peer_connection_t *pc, const char *candidate, const char *mid)
Add remote ICE candidate.
bool remote_description_set
libdatachannel has installed the remote SDP and ICE transport

References ASCIICHAT_OK, ERROR_INVALID_PARAM, log_debug, mutex_lock, mutex_unlock, NET_TO_HOST_U16, peer_entry_t::pc, peers_mutex, peer_entry_t::remote_description_set, SET_ERRNO, signaling_mutex, and webrtc_add_remote_candidate().

◆ webrtc_peer_manager_handle_sdp()

asciichat_error_t webrtc_peer_manager_handle_sdp ( webrtc_peer_manager_t *  manager,
const acip_webrtc_sdp_t *  sdp 
)

#include <peer_manager.h>

Handle incoming SDP message from ACDS.

Parameters
managerPeer manager
sdpSDP message received from ACDS
Returns
ASCIICHAT_OK on success, error code on failure

Processes SDP offer or answer from remote peer:

  • Creator role + offer: Create peer connection, set remote SDP, generate answer
  • Joiner role + answer: Set remote SDP on existing peer connection

Sends response SDP via signaling callbacks.

Definition at line 505 of file peer_manager.c.

505 {
506 if (!manager || !sdp) {
507 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
508 }
509
510 // Extract SDP string and type
511 const uint8_t *sdp_data = (const uint8_t *)(sdp + 1); // After header
512 const char *sdp_type = (sdp->sdp_type == 0) ? "offer" : "answer";
513 uint16_t sdp_len = NET_TO_HOST_U16(sdp->sdp_len);
514
515 // ACDS can deliver ICE immediately after SDP. Keep both operations ordered
516 // until libdatachannel has installed the remote description and ICE transport.
517 mutex_lock(&manager->signaling_mutex);
518
519 // Allocate null-terminated buffer for SDP string (libdatachannel requires C string)
520 char *sdp_str = SAFE_MALLOC(sdp_len + 1, char *);
521 if (!sdp_str) {
522 mutex_unlock(&manager->signaling_mutex);
523 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate remote SDP buffer");
524 }
525 memcpy(sdp_str, sdp_data, sdp_len);
526 sdp_str[sdp_len] = '\0'; // Null-terminate
527
528 char incoming_ice_ufrag[257] = {0};
529 bool has_incoming_ice_ufrag = extract_ice_ufrag(sdp_str, incoming_ice_ufrag, sizeof(incoming_ice_ufrag));
530
531 log_debug("Handling incoming SDP %s from remote peer (len=%u)", sdp_type, sdp_len);
532
533 mutex_lock(&manager->peers_mutex);
534
535 // Find or create peer connection
536 peer_entry_t *peer;
537
538 // Special case: If receiving an answer and we're a joiner, we may have created
539 // a peer with broadcast ID (00000000...) and need to update it to the real sender_id
540 if (sdp->sdp_type == 1 && manager->role == WEBRTC_ROLE_JOINER) {
541 static const uint8_t broadcast_id[16] = {0};
542 peer = find_peer_locked(manager, broadcast_id);
543 if (peer) {
544 log_debug("Updating broadcast peer with real participant_id from answer");
545 // Remove from hash with old ID
546 HASH_DEL(manager->peers, peer);
547 // Update to real participant_id
548 memcpy(peer->participant_id, sdp->sender_id, 16);
549 // Re-add with new ID
550 HASH_ADD(hh, manager->peers, participant_id, 16, peer);
551 }
552 }
553
554 // libdatachannel cannot apply an ICE restart to an existing peer connection.
555 // Repeated signaling can also redeliver the same offer, which must not be
556 // mistaken for a new ICE generation or applied twice.
557 if (sdp->sdp_type == 0 && manager->role == WEBRTC_ROLE_CREATOR) {
558 peer_entry_t *existing = find_peer_locked(manager, sdp->sender_id);
559 if (existing && existing->remote_sdp_attempted && existing->pc) {
560 webrtc_state_t state = webrtc_get_state(existing->pc);
561 bool comparable_ice_generation = has_incoming_ice_ufrag && existing->remote_ice_ufrag[0] != '\0';
562 bool new_ice_generation = comparable_ice_generation &&
563 strcmp(incoming_ice_ufrag, existing->remote_ice_ufrag) != 0;
564 bool terminal_state = state == WEBRTC_STATE_FAILED || state == WEBRTC_STATE_CLOSED;
565 if (!terminal_state && comparable_ice_generation && !new_ice_generation && existing->remote_description_set) {
566 log_debug("Ignoring duplicate WebRTC offer for existing ICE generation");
567 mutex_unlock(&manager->peers_mutex);
568 SAFE_FREE(sdp_str);
569 mutex_unlock(&manager->signaling_mutex);
570 return ASCIICHAT_OK;
571 }
572 if (terminal_state || new_ice_generation) {
573 log_info("Replacing WebRTC peer for %s", new_ice_generation ? "new ICE generation" : "terminal connection state");
574 remove_peer_locked(manager, existing);
575 }
576 }
577 }
578
579 asciichat_error_t result = create_peer_connection_locked(manager, sdp->session_id, sdp->sender_id, &peer);
580 if (result != ASCIICHAT_OK) {
581 mutex_unlock(&manager->peers_mutex);
582 SAFE_FREE(sdp_str);
583 mutex_unlock(&manager->signaling_mutex);
584 return SET_ERRNO(result, "Failed to create peer connection for SDP");
585 }
586
587 if (sdp->sdp_type == 0 && manager->role == WEBRTC_ROLE_CREATOR) {
588 peer->remote_sdp_attempted = true;
589 }
590
591 mutex_unlock(&manager->peers_mutex);
592
593 // Set remote SDP
594 result = webrtc_set_remote_description(peer->pc, sdp_str, sdp_type);
595 SAFE_FREE(sdp_str); // Free after use
596
597 if (result != ASCIICHAT_OK) {
598 mutex_unlock(&manager->signaling_mutex);
599 return SET_ERRNO(result, "Failed to set remote SDP");
600 }
601
602 // A peer entry can be created before its offer arrives (for example when
603 // ACDS reports a participant join). Do not treat that entry as ready for
604 // trickle ICE until libdatachannel has accepted its remote description.
605 peer->remote_description_set = true;
606 if (sdp->sdp_type == 0 && has_incoming_ice_ufrag) {
607 memcpy(peer->remote_ice_ufrag, incoming_ice_ufrag, strlen(incoming_ice_ufrag) + 1);
608 }
609
610 result = apply_pending_ice_candidates(manager, peer);
611 if (result != ASCIICHAT_OK) {
612 mutex_unlock(&manager->signaling_mutex);
613 return SET_ERRNO(result, "Failed to apply ICE candidates queued before remote SDP");
614 }
615
616 // If this is an offer and we're the creator, generate answer automatically
617 // (libdatachannel triggers on_local_description callback with answer)
618 if (sdp->sdp_type == 0 && manager->role == WEBRTC_ROLE_CREATOR) {
619 log_debug("Offer received, answer will be generated automatically");
620 }
621
622 mutex_unlock(&manager->signaling_mutex);
623 return ASCIICHAT_OK;
624}
unsigned short uint16_t
Definition common.h:57
asciichat_error_t webrtc_set_remote_description(webrtc_peer_connection_t *pc, const char *sdp, const char *type)
Set remote SDP offer/answer.
char remote_ice_ufrag[257]
ICE generation currently installed on this peer.
bool remote_sdp_attempted
A remote description was applied or attempted.

References ASCIICHAT_OK, ERROR_INVALID_PARAM, ERROR_MEMORY, log_debug, log_info, mutex_lock, mutex_unlock, NET_TO_HOST_U16, peer_entry_t::participant_id, participant_id, peer_entry_t::pc, peers, peers_mutex, peer_entry_t::remote_description_set, peer_entry_t::remote_ice_ufrag, peer_entry_t::remote_sdp_attempted, role, SAFE_FREE, SAFE_MALLOC, SET_ERRNO, signaling_mutex, webrtc_get_state(), WEBRTC_ROLE_CREATOR, WEBRTC_ROLE_JOINER, webrtc_set_remote_description(), WEBRTC_STATE_CLOSED, and WEBRTC_STATE_FAILED.

◆ webrtc_peer_manager_remove_peer()

asciichat_error_t webrtc_peer_manager_remove_peer ( webrtc_peer_manager_t *  manager,
const uint8_t  participant_id[16] 
)

#include <peer_manager.h>

Definition at line 735 of file peer_manager.c.

736 {
737 if (!manager || !participant_id) {
738 return SET_ERRNO(ERROR_INVALID_PARAM, "Peer manager and participant ID are required");
739 }
740
741 // SDP and ICE handlers retain peer pointers after releasing peers_mutex.
742 // Serialize removal with those handlers before freeing the peer wrappers.
743 mutex_lock(&manager->signaling_mutex);
744 mutex_lock(&manager->peers_mutex);
745 peer_entry_t *peer = find_peer_locked(manager, participant_id);
746 if (peer) {
747 remove_peer_locked(manager, peer);
748 }
749 mutex_unlock(&manager->peers_mutex);
750 mutex_unlock(&manager->signaling_mutex);
751 return ASCIICHAT_OK;
752}

References ASCIICHAT_OK, ERROR_INVALID_PARAM, mutex_lock, mutex_unlock, participant_id, peers_mutex, SET_ERRNO, and signaling_mutex.

Referenced by discovery_session_process().