ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
lib/network/webrtc/webrtc.c
Go to the documentation of this file.
1
15#include "common.h"
16#include "log/logging.h"
17
18#include <string.h>
19#include <rtc/rtc.h>
20
21// ============================================================================
22// Internal Structures
23// ============================================================================
24
31
37
38// ============================================================================
39// Global State
40// ============================================================================
41
42static bool g_webrtc_initialized = false;
43
44// ============================================================================
45// libdatachannel Callback Adapters
46// ============================================================================
47
48static void rtc_log_callback(rtcLogLevel level, const char *message) {
49 switch (level) {
50 case RTC_LOG_FATAL:
51 case RTC_LOG_ERROR:
52 log_error("[libdatachannel] %s", message);
53 break;
54 case RTC_LOG_WARNING:
55 log_warn("[libdatachannel] %s", message);
56 break;
57 case RTC_LOG_INFO:
58 log_info("[libdatachannel] %s", message);
59 break;
60 case RTC_LOG_DEBUG:
61 case RTC_LOG_VERBOSE:
62 log_debug("[libdatachannel] %s", message);
63 break;
64 default:
65 break;
66 }
67}
68
69static void on_state_change_adapter(int pc_id, rtcState state, void *user_data) {
70 (void)pc_id; // Unused - we get peer connection from user_data
72 if (!pc)
73 return;
74
75 // Map libdatachannel state to our enum
76 webrtc_state_t new_state;
77 switch (state) {
78 case RTC_NEW:
79 new_state = WEBRTC_STATE_NEW;
80 break;
81 case RTC_CONNECTING:
82 new_state = WEBRTC_STATE_CONNECTING;
83 break;
84 case RTC_CONNECTED:
85 new_state = WEBRTC_STATE_CONNECTED;
86 break;
87 case RTC_DISCONNECTED:
88 new_state = WEBRTC_STATE_DISCONNECTED;
89 break;
90 case RTC_FAILED:
91 new_state = WEBRTC_STATE_FAILED;
92 break;
93 case RTC_CLOSED:
94 new_state = WEBRTC_STATE_CLOSED;
95 break;
96 default:
97 new_state = WEBRTC_STATE_NEW;
98 break;
99 }
100
101 pc->state = new_state;
102
103 if (pc->config.on_state_change) {
104 pc->config.on_state_change(pc, new_state, pc->config.user_data);
105 }
106}
107
108static void on_local_description_adapter(int pc_id, const char *sdp, const char *type, void *user_data) {
109 (void)pc_id; // Unused - we get peer connection from user_data
111 if (!pc)
112 return;
113
115 pc->config.on_local_description(pc, sdp, type, pc->config.user_data);
116 }
117}
118
119static void on_local_candidate_adapter(int pc_id, const char *candidate, const char *mid, void *user_data) {
120 (void)pc_id; // Unused - we get peer connection from user_data
122 if (!pc)
123 return;
124
125 if (pc->config.on_local_candidate) {
126 pc->config.on_local_candidate(pc, candidate, mid, pc->config.user_data);
127 }
128}
129
130static void on_datachannel_adapter(int pc_id, int dc_id, void *user_data) {
131 (void)pc_id; // Unused - we get peer connection from user_data
133 if (!pc)
134 return;
135
136 // Allocate data channel wrapper
138 if (!dc) {
139 log_error("Failed to allocate data channel wrapper");
140 rtcDeleteDataChannel(dc_id);
141 return;
142 }
143
144 dc->rtc_id = dc_id;
145 dc->pc = pc;
146 dc->is_open = false;
147
148 // Store in peer connection
149 pc->dc = dc;
150
151 // Set up data channel callbacks (will be done when we receive open event)
152 log_debug("Received DataChannel (id=%d) from remote peer", dc_id);
153}
154
155static void on_datachannel_open_adapter(int dc_id, void *user_data) {
157 if (!dc)
158 return;
159
160 dc->is_open = true;
161 log_debug("DataChannel opened (id=%d)", dc_id);
162
163 if (dc->pc && dc->pc->config.on_datachannel_open) {
165 }
166}
167
168static void on_datachannel_message_adapter(int dc_id, const char *data, int size, void *user_data) {
169 (void)dc_id; // Unused - we get data channel from user_data
171 if (!dc)
172 return;
173
174 if (dc->pc && dc->pc->config.on_datachannel_message) {
175 dc->pc->config.on_datachannel_message(dc, (const uint8_t *)data, (size_t)size, dc->pc->config.user_data);
176 }
177}
178
179static void on_datachannel_error_adapter(int dc_id, const char *error, void *user_data) {
181 if (!dc)
182 return;
183
184 log_error("DataChannel error (id=%d): %s", dc_id, error);
185
186 if (dc->pc && dc->pc->config.on_datachannel_error) {
187 dc->pc->config.on_datachannel_error(dc, error, dc->pc->config.user_data);
188 }
189}
190
191// ============================================================================
192// Initialization and Cleanup
193// ============================================================================
194
196 if (g_webrtc_initialized) {
197 return ASCIICHAT_OK; // Already initialized
198 }
199
200 // Initialize libdatachannel logger
201 rtcInitLogger(RTC_LOG_INFO, rtc_log_callback);
202
203 // Preload global resources to avoid lazy-loading delays
204 rtcPreload();
205
206 g_webrtc_initialized = true;
207 log_info("WebRTC library initialized (libdatachannel)");
208 return ASCIICHAT_OK;
209}
210
211void webrtc_cleanup(void) {
212 if (!g_webrtc_initialized) {
213 return;
214 }
215
216 rtcCleanup();
217 g_webrtc_initialized = false;
218 log_info("WebRTC library cleaned up");
219}
220
221// ============================================================================
222// Peer Connection Management
223// ============================================================================
224
226 if (!config || !pc_out) {
227 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid config or output parameter");
228 }
229
230 if (!g_webrtc_initialized) {
231 return SET_ERRNO(ERROR_INIT, "WebRTC library not initialized");
232 }
233
234 // Allocate peer connection wrapper
236 if (!pc) {
237 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate peer connection");
238 }
239
240 pc->config = *config; // Copy config
242 pc->dc = NULL;
243
244 // Build ICE server list for libdatachannel
245 const char **ice_servers = NULL;
246 size_t ice_count = 0;
247
248 if (config->stun_count > 0 || config->turn_count > 0) {
249 ice_count = config->stun_count + config->turn_count;
250 ice_servers = SAFE_MALLOC(ice_count * sizeof(char *), const char **);
251 if (!ice_servers) {
252 SAFE_FREE(pc);
253 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate ICE server list");
254 }
255
256 // Add STUN servers
257 for (size_t i = 0; i < config->stun_count; i++) {
258 ice_servers[i] = config->stun_servers[i].host;
259 }
260
261 // Add TURN servers
262 for (size_t i = 0; i < config->turn_count; i++) {
263 ice_servers[config->stun_count + i] = config->turn_servers[i].url;
264 }
265 }
266
267 // Create libdatachannel configuration
268 rtcConfiguration rtc_config;
269 memset(&rtc_config, 0, sizeof(rtc_config));
270 rtc_config.iceServers = ice_servers;
271 rtc_config.iceServersCount = (int)ice_count;
272 rtc_config.iceTransportPolicy = RTC_TRANSPORT_POLICY_ALL;
273
274 // Create peer connection
275 int pc_id = rtcCreatePeerConnection(&rtc_config);
276
277 // Free ICE server list (libdatachannel makes a copy)
278 if (ice_servers) {
279 SAFE_FREE(ice_servers);
280 }
281
282 if (pc_id < 0) {
283 SAFE_FREE(pc);
284 return SET_ERRNO(ERROR_NETWORK, "Failed to create peer connection (rtc error %d)", pc_id);
285 }
286
287 pc->rtc_id = pc_id;
288
289 // Set up callbacks
290 rtcSetUserPointer(pc_id, pc);
291 rtcSetStateChangeCallback(pc_id, on_state_change_adapter);
292 rtcSetLocalDescriptionCallback(pc_id, on_local_description_adapter);
293 rtcSetLocalCandidateCallback(pc_id, on_local_candidate_adapter);
294 rtcSetDataChannelCallback(pc_id, on_datachannel_adapter);
295
296 *pc_out = pc;
297 log_debug("Created WebRTC peer connection (id=%d)", pc_id);
298 return ASCIICHAT_OK;
299}
300
302 if (!pc) {
303 return;
304 }
305
306 // Close data channel if exists
307 if (pc->dc) {
309 pc->dc = NULL;
310 }
311
312 // Close peer connection
313 rtcDeletePeerConnection(pc->rtc_id);
314 log_debug("Closed WebRTC peer connection (id=%d)", pc->rtc_id);
315
316 SAFE_FREE(pc);
317}
318
320 if (!pc) {
321 return WEBRTC_STATE_CLOSED;
322 }
323 return pc->state;
324}
325
327 if (!pc) {
328 return NULL;
329 }
330 return pc->config.user_data;
331}
332
333// ============================================================================
334// SDP Offer/Answer Exchange
335// ============================================================================
336
338 if (!pc) {
339 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid peer connection");
340 }
341
342 // Set local description with NULL type to trigger offer generation
343 int result = rtcSetLocalDescription(pc->rtc_id, NULL);
344 if (result != RTC_ERR_SUCCESS) {
345 return SET_ERRNO(ERROR_NETWORK, "Failed to create SDP offer (rtc error %d)", result);
346 }
347
348 log_debug("Creating SDP offer (pc_id=%d)", pc->rtc_id);
349 return ASCIICHAT_OK;
350}
351
353 if (!pc || !sdp || !type) {
354 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
355 }
356
357 int result = rtcSetRemoteDescription(pc->rtc_id, sdp, type);
358 if (result != RTC_ERR_SUCCESS) {
359 return SET_ERRNO(ERROR_NETWORK, "Failed to set remote SDP (rtc error %d)", result);
360 }
361
362 log_debug("Set remote SDP description (pc_id=%d, type=%s)", pc->rtc_id, type);
363 return ASCIICHAT_OK;
364}
365
366// ============================================================================
367// ICE Candidate Exchange
368// ============================================================================
369
370asciichat_error_t webrtc_add_remote_candidate(webrtc_peer_connection_t *pc, const char *candidate, const char *mid) {
371 if (!pc || !candidate) {
372 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
373 }
374
375 int result = rtcAddRemoteCandidate(pc->rtc_id, candidate, mid);
376 if (result != RTC_ERR_SUCCESS) {
377 return SET_ERRNO(ERROR_NETWORK, "Failed to add remote ICE candidate (rtc error %d)", result);
378 }
379
380 log_debug("Added remote ICE candidate (pc_id=%d)", pc->rtc_id);
381 return ASCIICHAT_OK;
382}
383
384// ============================================================================
385// DataChannel Management
386// ============================================================================
387
389 webrtc_data_channel_t **dc_out) {
390 if (!pc || !label || !dc_out) {
391 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
392 }
393
394 // Create data channel
395 int dc_id = rtcCreateDataChannel(pc->rtc_id, label);
396 if (dc_id < 0) {
397 return SET_ERRNO(ERROR_NETWORK, "Failed to create data channel (rtc error %d)", dc_id);
398 }
399
400 // Allocate wrapper
402 if (!dc) {
403 rtcDeleteDataChannel(dc_id);
404 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate data channel wrapper");
405 }
406
407 dc->rtc_id = dc_id;
408 dc->pc = pc;
409 dc->is_open = false;
410
411 // Set up callbacks
412 rtcSetUserPointer(dc_id, dc);
413 rtcSetOpenCallback(dc_id, on_datachannel_open_adapter);
414 rtcSetMessageCallback(dc_id, on_datachannel_message_adapter);
415 rtcSetErrorCallback(dc_id, on_datachannel_error_adapter);
416
417 pc->dc = dc;
418 *dc_out = dc;
419
420 log_debug("Created DataChannel '%s' (dc_id=%d, pc_id=%d)", label, dc_id, pc->rtc_id);
421 return ASCIICHAT_OK;
422}
423
425 if (!dc || !data) {
426 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
427 }
428
429 if (!dc->is_open) {
430 return SET_ERRNO(ERROR_NETWORK, "DataChannel not open");
431 }
432
433 int result = rtcSendMessage(dc->rtc_id, (const char *)data, (int)size);
434 if (result < 0) {
435 return SET_ERRNO(ERROR_NETWORK, "Failed to send data (rtc error %d)", result);
436 }
437
438 return ASCIICHAT_OK;
439}
440
442 return dc && dc->is_open;
443}
444
446 if (!dc) {
447 return NULL;
448 }
449
450 static char label[256];
451 int result = rtcGetDataChannelLabel(dc->rtc_id, label, sizeof(label));
452 if (result < 0) {
453 return NULL;
454 }
455
456 return label;
457}
458
460 if (!dc) {
461 return;
462 }
463
464 rtcDeleteDataChannel(dc->rtc_id);
465 log_debug("Closed DataChannel (dc_id=%d)", dc->rtc_id);
466
467 SAFE_FREE(dc);
468}
469
470// =============================================================================
471// DataChannel Callbacks
472// =============================================================================
473
475 const webrtc_datachannel_callbacks_t *callbacks) {
476 if (!dc) {
477 return SET_ERRNO(ERROR_INVALID_PARAM, "DataChannel is NULL");
478 }
479
480 if (!callbacks) {
481 return SET_ERRNO(ERROR_INVALID_PARAM, "Callbacks struct is NULL");
482 }
483
484 // Store callbacks in DataChannel structure
485 // NOTE: Intentional function pointer casts for libdatachannel API compatibility
486#pragma clang diagnostic push
487#pragma clang diagnostic ignored "-Wcast-function-type-mismatch"
488 if (callbacks->on_open) {
489 rtcSetOpenCallback(dc->rtc_id, (rtcOpenCallbackFunc)callbacks->on_open);
490 }
491
492 if (callbacks->on_close) {
493 rtcSetClosedCallback(dc->rtc_id, (rtcClosedCallbackFunc)callbacks->on_close);
494 }
495
496 if (callbacks->on_error) {
497 rtcSetErrorCallback(dc->rtc_id, (rtcErrorCallbackFunc)callbacks->on_error);
498 }
499
500 if (callbacks->on_message) {
501 rtcSetMessageCallback(dc->rtc_id, (rtcMessageCallbackFunc)callbacks->on_message);
502 }
503#pragma clang diagnostic pop
504
505 // Store user_data for callbacks (libdatachannel passes this to callbacks)
506 if (callbacks->user_data) {
507 rtcSetUserPointer(dc->rtc_id, callbacks->user_data);
508 }
509
510 log_debug("Set DataChannel callbacks (dc_id=%d)", dc->rtc_id);
511 return ASCIICHAT_OK;
512}
513
515 if (!dc) {
516 return;
517 }
518
519 // Close if still open, then free
521 // Note: webrtc_close_datachannel already calls SAFE_FREE(dc), so we're done
522}
523
524// =============================================================================
525// Peer Connection Lifecycle
526// =============================================================================
527
529 if (!pc) {
530 return;
531 }
532
533 rtcClose(pc->rtc_id);
534 log_debug("Closed peer connection (pc_id=%d)", pc->rtc_id);
535}
536
538 if (!pc) {
539 return;
540 }
541
542 // Close and delete peer connection
543 rtcDeletePeerConnection(pc->rtc_id);
544 log_debug("Destroyed peer connection (pc_id=%d)", pc->rtc_id);
545
546 SAFE_FREE(pc);
547}
#define SAFE_FREE(ptr)
Definition common.h:320
#define SAFE_MALLOC(size, cast)
Definition common.h:208
unsigned char uint8_t
Definition common.h:56
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:46
@ ERROR_NETWORK
Definition error_codes.h:69
@ ERROR_MEMORY
Definition error_codes.h:53
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_INIT
Definition error_codes.h:58
@ ERROR_INVALID_PARAM
#define log_warn(...)
Log a WARN message.
#define log_error(...)
Log an ERROR message.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
asciichat_error_t webrtc_create_datachannel(webrtc_peer_connection_t *pc, const char *label, webrtc_data_channel_t **dc_out)
Create a DataChannel (for connection initiator)
void webrtc_close_peer_connection(webrtc_peer_connection_t *pc)
Close and destroy a peer connection.
void webrtc_cleanup(void)
Cleanup WebRTC library resources.
asciichat_error_t webrtc_create_offer(webrtc_peer_connection_t *pc)
Create and set local SDP offer (for connection initiator)
asciichat_error_t webrtc_datachannel_set_callbacks(webrtc_data_channel_t *dc, const webrtc_datachannel_callbacks_t *callbacks)
Set DataChannel callbacks.
asciichat_error_t webrtc_add_remote_candidate(webrtc_peer_connection_t *pc, const char *candidate, const char *mid)
Add remote ICE candidate.
webrtc_state_t webrtc_get_state(webrtc_peer_connection_t *pc)
Get current connection state.
asciichat_error_t webrtc_init(void)
Initialize WebRTC library (libdatachannel)
void webrtc_datachannel_destroy(webrtc_data_channel_t *dc)
Destroy a DataChannel and free resources.
asciichat_error_t webrtc_create_peer_connection(const webrtc_config_t *config, webrtc_peer_connection_t **pc_out)
Create a new WebRTC peer connection.
const char * webrtc_datachannel_get_label(webrtc_data_channel_t *dc)
Get DataChannel label.
void * webrtc_get_user_data(webrtc_peer_connection_t *pc)
Get user data pointer from connection.
void webrtc_peer_connection_close(webrtc_peer_connection_t *pc)
Close a peer connection.
webrtc_state_t
WebRTC connection state.
void webrtc_peer_connection_destroy(webrtc_peer_connection_t *pc)
Destroy a peer connection and free resources.
asciichat_error_t webrtc_set_remote_description(webrtc_peer_connection_t *pc, const char *sdp, const char *type)
Set remote SDP offer/answer.
void webrtc_close_datachannel(webrtc_data_channel_t *dc)
Close a DataChannel.
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.
@ WEBRTC_STATE_NEW
Connection created but not started.
@ WEBRTC_STATE_DISCONNECTED
Connection lost.
@ WEBRTC_STATE_FAILED
Connection failed (fatal)
@ WEBRTC_STATE_CLOSED
Connection closed cleanly.
@ WEBRTC_STATE_CONNECTING
ICE gathering/connection in progress.
@ WEBRTC_STATE_CONNECTED
DataChannel established and ready.
📝 Logging API with multiple log levels and terminal output control
WebRTC configuration.
webrtc_datachannel_message_callback_t on_datachannel_message
webrtc_local_description_callback_t on_local_description
webrtc_local_candidate_callback_t on_local_candidate
size_t stun_count
Number of STUN servers.
webrtc_state_callback_t on_state_change
webrtc_datachannel_error_callback_t on_datachannel_error
turn_server_t * turn_servers
Array of TURN servers.
stun_server_t * stun_servers
Array of STUN servers.
void * user_data
Passed to all callbacks.
size_t turn_count
Number of TURN servers.
webrtc_datachannel_open_callback_t on_datachannel_open
bool is_open
Channel open state.
webrtc_peer_connection_t * pc
Parent peer connection.
int rtc_id
libdatachannel data channel ID
DataChannel callback structure.
void(* on_close)(webrtc_data_channel_t *dc, void *user_data)
Channel closed.
void(* on_error)(webrtc_data_channel_t *dc, const char *error, void *user_data)
Error occurred.
void * user_data
Passed to all callbacks.
void(* on_open)(webrtc_data_channel_t *dc, void *user_data)
Channel opened.
void(* on_message)(webrtc_data_channel_t *dc, const uint8_t *data, size_t len, void *user_data)
Message received.
webrtc_data_channel_t * dc
Primary data channel (if created/received)
int rtc_id
libdatachannel peer connection ID
webrtc_state_t state
Current connection state.
webrtc_config_t config
Configuration with callbacks.