14#include <ascii-chat/network/webrtc/webrtc.h>
15#include <ascii-chat/asciichat_errno.h>
16#include <ascii-chat/common.h>
17#include <ascii-chat/log/logging.h>
18#include <ascii-chat/platform/init.h>
19#include <ascii-chat/platform/system.h>
40 webrtc_data_channel_t *
dc;
51 webrtc_peer_connection_t *
pc;
58 void (*
user_on_message)(webrtc_data_channel_t *dc,
const uint8_t *data,
size_t len,
69static unsigned int g_webrtc_init_refcount = 0;
70static static_mutex_t g_webrtc_refcount_mutex = STATIC_MUTEX_INIT;
76static void rtc_log_callback(rtcLogLevel level,
const char *message) {
80 log_error(
"[libdatachannel] %s", message);
83 log_warn(
"[libdatachannel] %s", message);
86 log_info(
"[libdatachannel] %s", message);
90 log_debug(
"[libdatachannel] %s", message);
98static void on_datachannel_open_adapter(
int dc_id,
void *user_data);
99static void on_datachannel_closed_adapter(
int dc_id,
void *user_data);
100static void on_datachannel_message_adapter(
int dc_id,
const char *data,
int size,
void *user_data);
101static void on_datachannel_error_adapter(
int dc_id,
const char *error,
void *user_data);
103static void on_state_change_adapter(
int pc_id, rtcState state,
void *user_data) {
105 webrtc_peer_connection_t *pc = (webrtc_peer_connection_t *)user_data;
110 webrtc_state_t new_state;
113 new_state = WEBRTC_STATE_NEW;
116 new_state = WEBRTC_STATE_CONNECTING;
119 new_state = WEBRTC_STATE_CONNECTED;
121 case RTC_DISCONNECTED:
122 new_state = WEBRTC_STATE_DISCONNECTED;
125 new_state = WEBRTC_STATE_FAILED;
128 new_state = WEBRTC_STATE_CLOSED;
131 new_state = WEBRTC_STATE_NEW;
135 pc->state = new_state;
137 if (pc->config.on_state_change) {
138 pc->config.on_state_change(pc, new_state, pc->config.user_data);
142static void on_gathering_state_change_adapter(
int pc_id, rtcGatheringState state,
void *user_data) {
144 webrtc_peer_connection_t *pc = (webrtc_peer_connection_t *)user_data;
149 webrtc_gathering_state_t new_state;
151 case RTC_GATHERING_NEW:
152 new_state = WEBRTC_GATHERING_NEW;
154 case RTC_GATHERING_INPROGRESS:
155 new_state = WEBRTC_GATHERING_GATHERING;
157 if (pc->gathering_state != WEBRTC_GATHERING_GATHERING) {
159 log_debug(
"ICE gathering started at %llu ms", (
unsigned long long)pc->gathering_start_time_ms);
162 case RTC_GATHERING_COMPLETE:
163 new_state = WEBRTC_GATHERING_COMPLETE;
164 if (pc->gathering_start_time_ms > 0) {
166 log_info(
"ICE gathering completed in %llu ms", (
unsigned long long)duration);
170 new_state = WEBRTC_GATHERING_NEW;
174 pc->gathering_state = new_state;
176 if (pc->config.on_gathering_state_change) {
177 pc->config.on_gathering_state_change(pc, new_state, pc->config.user_data);
181static void on_local_description_adapter(
int pc_id,
const char *sdp,
const char *type,
void *user_data) {
183 webrtc_peer_connection_t *pc = (webrtc_peer_connection_t *)user_data;
187 if (pc->config.on_local_description) {
188 pc->config.on_local_description(pc, sdp, type, pc->config.user_data);
192static void on_local_candidate_adapter(
int pc_id,
const char *candidate,
const char *mid,
void *user_data) {
194 webrtc_peer_connection_t *pc = (webrtc_peer_connection_t *)user_data;
198 if (pc->config.on_local_candidate) {
199 pc->config.on_local_candidate(pc, candidate, mid, pc->config.user_data);
203static void on_datachannel_adapter(
int pc_id,
int dc_id,
void *user_data) {
205 log_info(
"on_datachannel_adapter: pc_id=%d, dc_id=%d, user_data=%p", pc_id, dc_id, user_data);
207 webrtc_peer_connection_t *pc = (webrtc_peer_connection_t *)user_data;
209 log_error(
"on_datachannel_adapter: peer connection user_data is NULL!");
213 log_info(
"on_datachannel_adapter: received DataChannel (dc_id=%d) from remote peer", dc_id);
216 webrtc_data_channel_t *dc = SAFE_MALLOC(
sizeof(webrtc_data_channel_t), webrtc_data_channel_t *);
218 log_error(
"Failed to allocate data channel wrapper");
219 rtcDeleteDataChannel(dc_id);
226 log_debug(
"Initialized DataChannel wrapper: dc=%p, is_open=false", (
void *)dc);
232 rtcSetUserPointer(dc_id, dc);
233 rtcSetOpenCallback(dc_id, on_datachannel_open_adapter);
234 rtcSetMessageCallback(dc_id, on_datachannel_message_adapter);
235 rtcSetErrorCallback(dc_id, on_datachannel_error_adapter);
237 log_info(
"Set up callbacks for incoming DataChannel (dc_id=%d, dc=%p)", dc_id, (
void *)dc);
242 if (rtcIsOpen(dc_id)) {
243 log_info(
"DataChannel was already open when received, manually triggering open callback");
244 on_datachannel_open_adapter(dc_id, dc);
248static void on_datachannel_open_adapter(
int dc_id,
void *user_data) {
249 webrtc_data_channel_t *dc = (webrtc_data_channel_t *)user_data;
250 log_info(
"on_datachannel_open_adapter called: dc_id=%d, dc=%p", dc_id, (
void *)dc);
252 log_error(
"on_datachannel_open_adapter: dc is NULL!");
257 log_info(
"DataChannel opened (id=%d, dc=%p), set is_open=true", dc_id, (
void *)dc);
260 if (dc->user_on_open) {
261 log_debug(
"Calling per-channel on_open callback");
262 dc->user_on_open(dc, dc->user_data);
263 }
else if (dc->pc && dc->pc->config.on_datachannel_open) {
264 log_debug(
"Calling user on_datachannel_open callback");
265 dc->pc->config.on_datachannel_open(dc, dc->pc->config.user_data);
267 log_warn(
"No user on_datachannel_open callback registered");
271static void on_datachannel_closed_adapter(
int dc_id,
void *user_data) {
272 webrtc_data_channel_t *dc = (webrtc_data_channel_t *)user_data;
273 log_info(
"on_datachannel_closed_adapter called: dc_id=%d, dc=%p", dc_id, (
void *)dc);
275 log_error(
"on_datachannel_closed_adapter: dc is NULL!");
280 log_info(
"DataChannel closed (id=%d, dc=%p), set is_open=false", dc_id, (
void *)dc);
283 if (dc->user_on_close) {
284 log_debug(
"Calling per-channel on_close callback");
285 dc->user_on_close(dc, dc->user_data);
289static void on_datachannel_message_adapter(
int dc_id,
const char *data,
int size,
void *user_data) {
293 if (size >= 20 && data) {
294 const uint8_t *pkt = (
const uint8_t *)data;
295 log_debug(
"★ LIBDATACHANNEL_RX: dc_id=%d, size=%d, first_20_bytes: %02x%02x%02x%02x %02x%02x%02x%02x "
296 "%02x%02x%02x%02x %02x%02x%02x%02x %02x%02x%02x%02x",
297 dc_id, size, pkt[0], pkt[1], pkt[2], pkt[3], pkt[4], pkt[5], pkt[6], pkt[7], pkt[8], pkt[9], pkt[10],
298 pkt[11], pkt[12], pkt[13], pkt[14], pkt[15], pkt[16], pkt[17], pkt[18], pkt[19]);
300 log_debug(
"★ LIBDATACHANNEL_RX: dc_id=%d, size=%d (no data or too small)", dc_id, size);
303 webrtc_data_channel_t *dc = (webrtc_data_channel_t *)user_data;
305 log_debug(
"★ LIBDATACHANNEL_RX: dc=NULL, dropping message");
310 if (dc->user_on_message) {
311 dc->user_on_message(dc, (
const uint8_t *)data, (
size_t)size, dc->user_data);
312 }
else if (dc->pc && dc->pc->config.on_datachannel_message) {
313 dc->pc->config.on_datachannel_message(dc, (
const uint8_t *)data, (
size_t)size, dc->pc->config.user_data);
317static void on_datachannel_error_adapter(
int dc_id,
const char *error,
void *user_data) {
318 webrtc_data_channel_t *dc = (webrtc_data_channel_t *)user_data;
322 log_error(
"DataChannel error (id=%d): %s", dc_id, error);
325 if (dc->user_on_error) {
326 dc->user_on_error(dc, error, dc->user_data);
327 }
else if (dc->pc && dc->pc->config.on_datachannel_error) {
328 dc->pc->config.on_datachannel_error(dc, error, dc->pc->config.user_data);
344static asciichat_error_t webrtc_ensure_initialized(
void) {
345 static_mutex_lock(&g_webrtc_refcount_mutex);
348 if (g_webrtc_init_refcount > 0) {
349 g_webrtc_init_refcount++;
350 static_mutex_unlock(&g_webrtc_refcount_mutex);
355 rtcInitLogger(RTC_LOG_VERBOSE, rtc_log_callback);
358 g_webrtc_init_refcount = 1;
359 static_mutex_unlock(&g_webrtc_refcount_mutex);
361 log_debug(
"WebRTC library initialized (libdatachannel)");
371static void webrtc_release(
void) {
372 static_mutex_lock(&g_webrtc_refcount_mutex);
373 if (g_webrtc_init_refcount > 0) {
374 g_webrtc_init_refcount--;
375 if (g_webrtc_init_refcount == 0) {
377 log_info(
"WebRTC library cleaned up");
380 static_mutex_unlock(&g_webrtc_refcount_mutex);
384 return webrtc_ensure_initialized();
396 if (!config || !pc_out) {
397 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid config or output parameter");
400 static_mutex_lock(&g_webrtc_refcount_mutex);
401 bool is_initialized = (g_webrtc_init_refcount > 0);
402 static_mutex_unlock(&g_webrtc_refcount_mutex);
404 if (!is_initialized) {
405 return SET_ERRNO(ERROR_INIT,
"WebRTC library not initialized");
409 webrtc_peer_connection_t *pc = SAFE_MALLOC(
sizeof(webrtc_peer_connection_t), webrtc_peer_connection_t *);
411 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate peer connection");
414 pc->config = *config;
415 pc->state = WEBRTC_STATE_NEW;
419 const char **ice_servers = NULL;
420 size_t ice_count = 0;
422 if (config->stun_count > 0 || config->turn_count > 0) {
423 ice_count = config->stun_count + config->turn_count;
424 ice_servers = SAFE_MALLOC(ice_count *
sizeof(
char *),
const char **);
427 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate ICE server list");
431 for (
size_t i = 0; i < config->stun_count; i++) {
432 ice_servers[i] = config->stun_servers[i].host;
436 for (
size_t i = 0; i < config->turn_count; i++) {
437 ice_servers[config->stun_count + i] = config->turn_servers[i].url;
442 rtcConfiguration rtc_config;
443 memset(&rtc_config, 0,
sizeof(rtc_config));
444 rtc_config.iceServers = ice_servers;
445 rtc_config.iceServersCount = (int)ice_count;
446 rtc_config.iceTransportPolicy = RTC_TRANSPORT_POLICY_ALL;
449 int pc_id = rtcCreatePeerConnection(&rtc_config);
453 SAFE_FREE(ice_servers);
458 return SET_ERRNO(ERROR_NETWORK,
"Failed to create peer connection (rtc error %d)", pc_id);
464 pc->gathering_state = WEBRTC_GATHERING_NEW;
465 pc->gathering_start_time_ms = 0;
468 rtcSetUserPointer(pc_id, pc);
469 rtcSetStateChangeCallback(pc_id, on_state_change_adapter);
470 rtcSetGatheringStateChangeCallback(pc_id, on_gathering_state_change_adapter);
471 rtcSetLocalDescriptionCallback(pc_id, on_local_description_adapter);
472 rtcSetLocalCandidateCallback(pc_id, on_local_candidate_adapter);
473 rtcSetDataChannelCallback(pc_id, on_datachannel_adapter);
476 log_debug(
"Created WebRTC peer connection (id=%d)", pc_id);
482 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid peer connection");
493 rtcDeletePeerConnection(pc->rtc_id);
494 log_debug(
"Closed WebRTC peer connection (id=%d)", pc->rtc_id);
501 log_warn(
"Peer connection is NULL");
502 return WEBRTC_STATE_CLOSED;
509 log_warn(
"Peer connection is NULL");
510 return WEBRTC_GATHERING_NEW;
512 return pc->gathering_state;
521 if (pc->gathering_state != WEBRTC_GATHERING_GATHERING) {
526 if (pc->gathering_start_time_ms == 0) {
532 uint64_t elapsed_ms = current_time_ms - pc->gathering_start_time_ms;
534 if (elapsed_ms > timeout_ms) {
535 log_warn(
"ICE gathering timeout: %llu ms elapsed (timeout: %u ms)", (
unsigned long long)elapsed_ms, timeout_ms);
544 log_warn(
"Peer connection is NULL");
547 return pc->config.user_data;
556 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid peer connection");
560 int result = rtcSetLocalDescription(pc->rtc_id, NULL);
561 if (result != RTC_ERR_SUCCESS) {
562 return SET_ERRNO(ERROR_NETWORK,
"Failed to create SDP offer (rtc error %d)", result);
565 log_debug(
"Creating SDP offer (pc_id=%d)", pc->rtc_id);
570 if (!pc || !sdp || !type) {
571 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters");
574 int result = rtcSetRemoteDescription(pc->rtc_id, sdp, type);
575 if (result != RTC_ERR_SUCCESS) {
576 return SET_ERRNO(ERROR_NETWORK,
"Failed to set remote SDP (rtc error %d)", result);
579 log_debug(
"Set remote SDP description (pc_id=%d, type=%s)", pc->rtc_id, type);
588 if (!pc || !candidate) {
589 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters");
592 log_debug(
" [4] Before libdatachannel - candidate: '%s' (len=%zu)", candidate, strlen(candidate));
593 log_debug(
" [4] Before libdatachannel - mid: '%s' (len=%zu)", mid ? mid :
"(null)", mid ? strlen(mid) : 0);
595 int result = rtcAddRemoteCandidate(pc->rtc_id, candidate, mid);
596 if (result != RTC_ERR_SUCCESS) {
597 return SET_ERRNO(ERROR_NETWORK,
"Failed to add remote ICE candidate (rtc error %d)", result);
600 log_debug(
"Added remote ICE candidate (pc_id=%d)", pc->rtc_id);
609 webrtc_data_channel_t **dc_out) {
610 if (!pc || !label || !dc_out) {
611 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters");
615 int dc_id = rtcCreateDataChannel(pc->rtc_id, label);
617 return SET_ERRNO(ERROR_NETWORK,
"Failed to create data channel (rtc error %d)", dc_id);
621 webrtc_data_channel_t *dc = SAFE_MALLOC(
sizeof(webrtc_data_channel_t), webrtc_data_channel_t *);
623 rtcDeleteDataChannel(dc_id);
624 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate data channel wrapper");
632 rtcSetUserPointer(dc_id, dc);
633 rtcSetOpenCallback(dc_id, on_datachannel_open_adapter);
634 rtcSetMessageCallback(dc_id, on_datachannel_message_adapter);
635 rtcSetErrorCallback(dc_id, on_datachannel_error_adapter);
640 log_debug(
"Created DataChannel '%s' (dc_id=%d, pc_id=%d)", label, dc_id, pc->rtc_id);
646 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid parameters");
650 log_error(
"★ WEBRTC_DATACHANNEL_SEND: Channel not open! size=%zu, dc->rtc_id=%d", size, dc ? dc->rtc_id : -1);
651 return SET_ERRNO(ERROR_NETWORK,
"DataChannel not open");
656 const uint8_t *pkt = (
const uint8_t *)data;
657 log_debug(
"★ RTCSENDMESSAGE_BEFORE: dc_id=%d, size=%zu, first_20_bytes: %02x%02x%02x%02x %02x%02x%02x%02x "
658 "%02x%02x%02x%02x %02x%02x%02x%02x %02x%02x%02x%02x",
659 dc->rtc_id, size, pkt[0], pkt[1], pkt[2], pkt[3], pkt[4], pkt[5], pkt[6], pkt[7], pkt[8], pkt[9], pkt[10],
660 pkt[11], pkt[12], pkt[13], pkt[14], pkt[15], pkt[16], pkt[17], pkt[18], pkt[19]);
662 log_debug(
"★ RTCSENDMESSAGE_BEFORE: dc_id=%d, size=%zu (too small to log content)", dc->rtc_id, size);
665 int result = rtcSendMessage(dc->rtc_id, (
const char *)data, (
int)size);
667 log_debug(
"★ RTCSENDMESSAGE_AFTER: dc_id=%d, rtcSendMessage returned %d for size=%zu", dc->rtc_id, result, size);
670 log_error(
"★ WEBRTC_DATACHANNEL_SEND: FAILED with error code %d", result);
671 return SET_ERRNO(ERROR_NETWORK,
"Failed to send data (rtc error %d)", result);
674 log_debug(
"★ WEBRTC_DATACHANNEL_SEND: SUCCESS - sent %zu bytes", size);
680 SET_ERRNO(ERROR_INVALID_PARAM,
"DataChannel is NULL");
688 SET_ERRNO(ERROR_INVALID_PARAM,
"DataChannel is NULL");
691 dc->is_open = is_open;
696 SET_ERRNO(ERROR_INVALID_PARAM,
"DataChannel is NULL");
700 static char label[256];
701 int result = rtcGetDataChannelLabel(dc->rtc_id, label,
sizeof(label));
711 SET_ERRNO(ERROR_INVALID_PARAM,
"DataChannel is NULL");
716 int dc_id = dc->rtc_id;
718 rtcDeleteDataChannel(dc_id);
719 log_debug(
"Closed DataChannel (dc_id=%d)", dc_id);
729 const webrtc_datachannel_callbacks_t *callbacks) {
731 return SET_ERRNO(ERROR_INVALID_PARAM,
"DataChannel is NULL");
735 return SET_ERRNO(ERROR_INVALID_PARAM,
"Callbacks struct is NULL");
739 dc->user_on_open = callbacks->on_open;
740 dc->user_on_close = callbacks->on_close;
741 dc->user_on_error = callbacks->on_error;
742 dc->user_on_message = callbacks->on_message;
743 dc->user_data = callbacks->user_data;
747 if (callbacks->on_open) {
748 rtcSetOpenCallback(dc->rtc_id, on_datachannel_open_adapter);
751 if (callbacks->on_close) {
752 rtcSetClosedCallback(dc->rtc_id, on_datachannel_closed_adapter);
755 if (callbacks->on_error) {
756 rtcSetErrorCallback(dc->rtc_id, on_datachannel_error_adapter);
759 if (callbacks->on_message) {
760 rtcSetMessageCallback(dc->rtc_id, on_datachannel_message_adapter);
764 rtcSetUserPointer(dc->rtc_id, dc);
766 log_debug(
"Set DataChannel callbacks (dc_id=%d)", dc->rtc_id);
772 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid null data channel");
787 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid null peer connectiont");
791 rtcClose(pc->rtc_id);
792 log_debug(
"Closed peer connection (pc_id=%d)", pc->rtc_id);
797 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid null peer connectiont");
802 rtcDeletePeerConnection(pc->rtc_id);
803 log_debug(
"Destroyed peer connection (pc_id=%d)", pc->rtc_id);
819 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid null peer connectiont");
asciichat_error_t webrtc_create_datachannel(webrtc_peer_connection_t *pc, const char *label, webrtc_data_channel_t **dc_out)
void webrtc_close_peer_connection(webrtc_peer_connection_t *pc)
asciichat_error_t webrtc_create_offer(webrtc_peer_connection_t *pc)
asciichat_error_t webrtc_datachannel_set_callbacks(webrtc_data_channel_t *dc, const webrtc_datachannel_callbacks_t *callbacks)
int webrtc_get_rtc_id(webrtc_peer_connection_t *pc)
Get the internal libdatachannel peer connection ID.
asciichat_error_t webrtc_add_remote_candidate(webrtc_peer_connection_t *pc, const char *candidate, const char *mid)
webrtc_state_t webrtc_get_state(webrtc_peer_connection_t *pc)
webrtc_gathering_state_t webrtc_get_gathering_state(webrtc_peer_connection_t *pc)
asciichat_error_t webrtc_init(void)
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)
const char * webrtc_datachannel_get_label(webrtc_data_channel_t *dc)
void * webrtc_get_user_data(webrtc_peer_connection_t *pc)
void webrtc_peer_connection_close(webrtc_peer_connection_t *pc)
void webrtc_destroy(void)
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)
void webrtc_close_datachannel(webrtc_data_channel_t *dc)
bool webrtc_datachannel_is_open(webrtc_data_channel_t *dc)
asciichat_error_t webrtc_datachannel_send(webrtc_data_channel_t *dc, const uint8_t *data, size_t size)
bool webrtc_is_gathering_timed_out(webrtc_peer_connection_t *pc, uint32_t timeout_ms)
void webrtc_datachannel_set_open_state(webrtc_data_channel_t *dc, bool is_open)
WebRTC data channel for sending/receiving messages.
void(* user_on_message)(webrtc_data_channel_t *dc, const uint8_t *data, size_t len, void *user_data)
Message callback.
bool is_open
Channel open state.
void(* user_on_close)(webrtc_data_channel_t *dc, void *user_data)
Close callback.
void(* user_on_open)(webrtc_data_channel_t *dc, void *user_data)
Open callback.
webrtc_peer_connection_t * pc
Parent peer connection.
int rtc_id
libdatachannel data channel ID
void * user_data
User data for per-channel callbacks.
void(* user_on_error)(webrtc_data_channel_t *dc, const char *error, void *user_data)
Error callback.
WebRTC peer connection state.
webrtc_data_channel_t * dc
Primary data channel (if created/received)
uint64_t gathering_start_time_ms
When gathering started (platform_get_time_ms)
int rtc_id
libdatachannel peer connection ID
webrtc_state_t state
Current connection state.
webrtc_gathering_state_t gathering_state
Current ICE gathering state.
webrtc_config_t config
Configuration with callbacks.