14#include <ascii-chat/network/webrtc/peer_manager.h>
15#include <ascii-chat/network/acip/transport.h>
16#include <ascii-chat/log/logging.h>
17#include <ascii-chat/platform/mutex.h>
18#include <ascii-chat/platform/system.h>
19#include <ascii-chat/util/endian.h>
20#include <ascii-chat/uthash/uthash.h>
29 webrtc_peer_connection_t *
pc;
30 webrtc_data_channel_t *
dc;
78 HASH_DEL(manager->
peers, peer);
97static void on_datachannel_open(webrtc_data_channel_t *dc,
void *user_data) {
100 log_info(
"WebRTC DataChannel opened for participant");
105 log_error(
"No manager found for peer");
113 log_debug(
"Updated peer->dc from DataChannel callback (dc=%p)", (
void *)peer->
dc);
119 log_error(
"Failed to create ACIP transport for WebRTC DataChannel");
123 log_debug(
"Transport created, checking callback: on_transport_ready=%p, user_data=%p",
124 (
void *)manager->
config.on_transport_ready, manager->
config.user_data);
127 if (manager->
config.on_transport_ready) {
128 log_debug(
"Calling on_transport_ready callback");
130 log_debug(
"Callback completed");
133 log_warn(
"No on_transport_ready callback registered, cleaning up transport");
143static void on_local_description(webrtc_peer_connection_t *pc,
const char *sdp,
const char *type,
void *user_data) {
148 if (!manager || !manager->
signaling.send_sdp) {
149 log_error(
"No signaling callback registered for SDP");
153 log_debug(
"on_local_description: peer->session_id=%02x%02x%02x%02x..., peer->participant_id=%02x%02x%02x%02x...",
157 log_debug(
"Sending SDP %s to remote peer via ACDS", type);
159 asciichat_error_t result =
162 if (result != ASCIICHAT_OK) {
163 log_error(
"Failed to send SDP via signaling: %s", asciichat_error_string(result));
170static void on_local_candidate(webrtc_peer_connection_t *pc,
const char *candidate,
const char *mid,
void *user_data) {
175 if (!manager || !manager->
signaling.send_ice) {
176 log_error(
"No signaling callback registered for ICE");
181 if (GET_OPTION(webrtc_skip_host) && strstr(candidate,
"typ host") != NULL) {
182 log_debug(
"Skipping host candidate (--webrtc-skip-host enabled): '%s'", candidate);
186 log_debug(
"Sending ICE candidate to remote peer via ACDS");
187 log_debug(
" [1] libdatachannel gave us candidate: '%s' (len=%zu)", candidate, strlen(candidate));
188 log_debug(
" [1] libdatachannel gave us mid: '%s' (len=%zu)", mid, strlen(mid));
190 asciichat_error_t result =
193 if (result != ASCIICHAT_OK) {
194 log_error(
"Failed to send ICE candidate via signaling: %s", asciichat_error_string(result));
201static void on_state_change(webrtc_peer_connection_t *pc, webrtc_state_t state,
void *user_data) {
205 const char *state_str =
"UNKNOWN";
207 case WEBRTC_STATE_NEW:
210 case WEBRTC_STATE_CONNECTING:
211 state_str =
"CONNECTING";
213 case WEBRTC_STATE_CONNECTED:
214 state_str =
"CONNECTED";
216 case WEBRTC_STATE_DISCONNECTED:
217 state_str =
"DISCONNECTED";
219 case WEBRTC_STATE_FAILED:
220 state_str =
"FAILED";
222 case WEBRTC_STATE_CLOSED:
223 state_str =
"CLOSED";
227 log_debug(
"WebRTC peer connection state: %s", state_str);
243 *peer_out = existing;
250 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate peer entry");
261 webrtc_config_t webrtc_config = {
262 .stun_servers = manager->
config.stun_servers,
263 .stun_count = manager->
config.stun_count,
264 .turn_servers = manager->
config.turn_servers,
265 .turn_count = manager->
config.turn_count,
266 .on_state_change = on_state_change,
267 .on_local_description = on_local_description,
268 .on_local_candidate = on_local_candidate,
269 .on_datachannel_open = on_datachannel_open,
270 .on_datachannel_message = NULL,
271 .on_datachannel_error = NULL,
277 if (result != ASCIICHAT_OK) {
279 return SET_ERRNO(result,
"Failed to create WebRTC peer connection");
283 if (manager->
role == WEBRTC_ROLE_JOINER) {
285 if (result != ASCIICHAT_OK) {
288 return SET_ERRNO(result,
"Failed to create WebRTC data channel");
292 webrtc_datachannel_callbacks_t dc_callbacks = {
293 .on_open = on_datachannel_open,
301 if (result != ASCIICHAT_OK) {
305 return SET_ERRNO(result,
"Failed to set datachannel callbacks");
310 add_peer_locked(manager, peer);
312 log_debug(
"Created WebRTC peer connection for participant (role: %s)",
313 manager->
role == WEBRTC_ROLE_CREATOR ?
"creator" :
"joiner");
324 const webrtc_signaling_callbacks_t *signaling_callbacks,
326 if (!config || !signaling_callbacks || !manager_out) {
327 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters");
330 if (!signaling_callbacks->send_sdp || !signaling_callbacks->send_ice) {
331 return SET_ERRNO(ERROR_INVALID_PARAM,
"Signaling callbacks required");
337 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate peer manager");
340 memcpy(&manager->
config, config,
sizeof(*config));
341 memcpy(&manager->
signaling, signaling_callbacks,
sizeof(*signaling_callbacks));
342 manager->
role = config->role;
343 manager->
peers = NULL;
347 return SET_ERRNO(ERROR_INTERNAL,
"Failed to initialize peers mutex");
350 log_info(
"Created WebRTC peer manager (role: %s)", manager->
role == WEBRTC_ROLE_CREATOR ?
"creator" :
"joiner");
352 *manager_out = manager;
365 HASH_ITER(hh, manager->
peers, peer, tmp) {
366 remove_peer_locked(manager, peer);
374 log_debug(
"Destroyed WebRTC peer manager");
378 if (!manager || !sdp) {
379 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters");
383 const uint8_t *sdp_data = (
const uint8_t *)(sdp + 1);
384 const char *sdp_type = (sdp->sdp_type == 0) ?
"offer" :
"answer";
385 uint16_t sdp_len = NET_TO_HOST_U16(sdp->sdp_len);
388 char *sdp_str = SAFE_MALLOC(sdp_len + 1,
char *);
389 memcpy(sdp_str, sdp_data, sdp_len);
390 sdp_str[sdp_len] =
'\0';
392 log_debug(
"Handling incoming SDP %s from remote peer (len=%u)", sdp_type, sdp_len);
401 if (sdp->sdp_type == 1 && manager->
role == WEBRTC_ROLE_JOINER) {
402 static const uint8_t broadcast_id[16] = {0};
403 peer = find_peer_locked(manager, broadcast_id);
405 log_debug(
"Updating broadcast peer with real participant_id from answer");
407 HASH_DEL(manager->
peers, peer);
415 asciichat_error_t result = create_peer_connection_locked(manager, sdp->session_id, sdp->sender_id, &peer);
416 if (result != ASCIICHAT_OK) {
419 return SET_ERRNO(result,
"Failed to create peer connection for SDP");
428 if (result != ASCIICHAT_OK) {
429 return SET_ERRNO(result,
"Failed to set remote SDP");
434 if (sdp->sdp_type == 0 && manager->
role == WEBRTC_ROLE_CREATOR) {
435 log_debug(
"Offer received, answer will be generated automatically");
442 if (!manager || !ice) {
443 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters");
447 const char *candidate = (
const char *)(ice + 1);
448 size_t candidate_str_len = strlen(candidate);
449 const char *mid = candidate + candidate_str_len + 1;
451 log_debug(
"Handling incoming ICE candidate from remote peer (mid=%s)", mid);
452 log_debug(
" [3] After ACDS recv - candidate: '%s' (len=%zu)", candidate, strlen(candidate));
453 log_debug(
" [3] After ACDS recv - mid: '%s' (len=%zu)", mid, strlen(mid));
454 log_debug(
" [3] After ACDS recv - header.candidate_len=%u", NET_TO_HOST_U16(ice->candidate_len));
457 const uint8_t *payload = (
const uint8_t *)(ice + 1);
458 log_debug(
" [3] Hex dump of payload (first 100 bytes):");
459 for (
int i = 0; i < 100 && i < (int)candidate_str_len + 20; i += 16) {
461 char ascii[20] = {0};
462 for (
int j = 0; j < 16 && (i + j) < 100; j++) {
463 snprintf(hex + j * 3,
sizeof(hex) - j * 3,
"%02x ", payload[i + j]);
464 ascii[j] = (payload[i + j] >= 32 && payload[i + j] < 127) ? payload[i + j] :
'.';
466 log_debug(
" [%04x] %-48s %s", i, hex, ascii);
472 peer_entry_t *peer = find_peer_locked(manager, ice->sender_id);
475 log_warn(
"ICE candidate for unknown peer, ignoring");
483 if (result != ASCIICHAT_OK) {
484 return SET_ERRNO(result,
"Failed to add remote ICE candidate");
493 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters");
496 if (manager->
role != WEBRTC_ROLE_JOINER) {
497 return SET_ERRNO(ERROR_INVALID_PARAM,
"Only joiners can initiate connections");
500 log_debug(
"webrtc_peer_manager_connect: session_id=%02x%02x%02x%02x..., participant_id=%02x%02x%02x%02x...",
509 if (result != ASCIICHAT_OK) {
511 return SET_ERRNO(result,
"Failed to create peer connection");
520 log_info(
"Initiated WebRTC connection to participant (offer auto-created by DataChannel)");
530 int timeout_count = 0;
536 HASH_ITER(hh, manager->
peers, peer, tmp) {
537 if (!peer || !peer->
pc) {
545 log_error(
"ICE gathering timeout for peer (participant_id=%02x%02x%02x%02x..., timeout=%ums, state=%d)",
550 if (manager->
config.on_gathering_timeout) {
555 remove_peer_locked(manager, peer);
558 log_info(
"Closed and removed timed-out peer connection (count: %d)", timeout_count);
564 return timeout_count;
asciichat_error_t webrtc_create_datachannel(webrtc_peer_connection_t *pc, const char *label, webrtc_data_channel_t **dc_out)
asciichat_error_t webrtc_datachannel_set_callbacks(webrtc_data_channel_t *dc, const webrtc_datachannel_callbacks_t *callbacks)
asciichat_error_t webrtc_add_remote_candidate(webrtc_peer_connection_t *pc, const char *candidate, const char *mid)
webrtc_gathering_state_t webrtc_get_gathering_state(webrtc_peer_connection_t *pc)
void webrtc_datachannel_destroy(webrtc_data_channel_t *dc)
asciichat_error_t webrtc_create_peer_connection(const webrtc_config_t *config, webrtc_peer_connection_t **pc_out)
void webrtc_peer_connection_destroy(webrtc_peer_connection_t *pc)
asciichat_error_t webrtc_set_remote_description(webrtc_peer_connection_t *pc, const char *sdp, const char *type)
bool webrtc_is_gathering_timed_out(webrtc_peer_connection_t *pc, uint32_t timeout_ms)
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)
asciichat_error_t webrtc_peer_manager_handle_ice(webrtc_peer_manager_t *manager, const acip_webrtc_ice_t *ice)
asciichat_error_t webrtc_peer_manager_handle_sdp(webrtc_peer_manager_t *manager, const acip_webrtc_sdp_t *sdp)
void webrtc_peer_manager_destroy(webrtc_peer_manager_t *manager)
struct webrtc_peer_manager webrtc_peer_manager_t
WebRTC peer manager structure.
asciichat_error_t webrtc_peer_manager_connect(webrtc_peer_manager_t *manager, const uint8_t session_id[16], const uint8_t participant_id[16])
int webrtc_peer_manager_check_gathering_timeouts(webrtc_peer_manager_t *manager, uint32_t timeout_ms)
uint8_t participant_id[16]
Per-peer connection state.
webrtc_peer_connection_t * pc
WebRTC peer connection.
uint8_t participant_id[16]
Remote participant UUID (hash key)
bool is_connected
DataChannel opened.
struct webrtc_peer_manager * manager
Back-reference to manager.
UT_hash_handle hh
uthash handle
uint8_t session_id[16]
Session UUID.
webrtc_data_channel_t * dc
WebRTC data channel.
WebRTC peer manager structure.
webrtc_peer_role_t role
Session role.
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.
webrtc_signaling_callbacks_t signaling
Signaling callbacks.
void acip_transport_destroy(acip_transport_t *transport)
int mutex_init(mutex_t *mutex)
int mutex_destroy(mutex_t *mutex)
acip_transport_t * acip_webrtc_transport_create(webrtc_peer_connection_t *peer_conn, webrtc_data_channel_t *data_channel, crypto_context_t *crypto_ctx)