ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
ice.c
Go to the documentation of this file.
1
13#include <ascii-chat/log/log.h>
15#include <ascii-chat/util/ip.h>
16#include <string.h>
17#include <stdio.h>
18#include <stdlib.h>
19#define PCRE2_CODE_UNIT_WIDTH 8
20#include <pcre2.h>
21
22/* ============================================================================
23 * Utility Functions
24 * ============================================================================ */
25
27 switch (type) {
29 return "host";
31 return "srflx";
33 return "prflx";
35 return "relay";
36 default:
37 return "unknown";
38 }
39}
40
41const char *ice_protocol_name(ice_protocol_t protocol) {
42 switch (protocol) {
44 return "udp";
46 return "tcp";
47 default:
48 return "unknown";
49 }
50}
51
52/* ============================================================================
53 * ICE Candidate Priority
54 * ============================================================================ */
55
72static uint16_t ice_calculate_local_preference_by_ip(const char *ip_address) {
73 if (!ip_address || ip_address[0] == '\0') {
74 return 0; // Invalid/empty
75 }
76
77 // Check for wildcard addresses (should not appear in candidates, but handle gracefully)
78 if (strcmp(ip_address, "0.0.0.0") == 0 || strcmp(ip_address, "::") == 0) {
79 return 0; // Invalid candidate
80 }
81
82 // LAN addresses: Highest priority (same network = lowest latency)
83 if (is_lan_ipv4(ip_address) || is_lan_ipv6(ip_address)) {
84 return 65535;
85 }
86
87 // Localhost: High priority but less useful than LAN for peer-to-peer
88 if (is_localhost_ipv4(ip_address) || is_localhost_ipv6(ip_address)) {
89 return 65000;
90 }
91
92 // Internet (public): Medium priority
93 if (is_internet_ipv4(ip_address) || is_internet_ipv6(ip_address)) {
94 return 32768;
95 }
96
97 // Unknown/unclassified: Lowest priority
98 return 0;
99}
100
102 // RFC 5245: priority = (2^24 * typePreference) + (2^8 * localPreference) + (256 - componentID)
103
104 // Type preference (higher is better):
105 // - Host: 126 (most preferred, lowest latency)
106 // - SRFLX: 100 (STUN-discovered address)
107 // - PRFLX: 110 (discovered during connectivity checks)
108 // - Relay: 0 (least preferred, uses bandwidth)
109
110 uint8_t type_preference = 0;
111 switch (type) {
113 type_preference = 126;
114 break;
116 type_preference = 100;
117 break;
119 type_preference = 110;
120 break;
122 default:
123 type_preference = 0;
124 break;
125 }
126
127 uint32_t priority =
128 (((uint32_t)type_preference) << 24) | (((uint32_t)local_preference) << 8) | (256 - (uint32_t)component_id);
129
130 return priority;
131}
132
134 if (!candidate) {
135 return 0;
136 }
137
138 // Calculate local preference based on IP classification
139 uint16_t local_pref = ice_calculate_local_preference_by_ip(candidate->ip_address);
140
141 // Use standard RFC 5245 priority formula with IP-aware local preference
142 return ice_calculate_priority(candidate->type, local_pref, (uint8_t)candidate->component_id);
143}
144
145/* ============================================================================
146 * ICE Candidate Parsing with PCRE2
147 * ============================================================================ */
148
156static const char *ICE_CANDIDATE_PATTERN = "^([^ ]+)\\s+" // 1: foundation
157 "(\\d+)\\s+" // 2: component_id
158 "(udp|tcp)\\s+" // 3: protocol
159 "(\\d+)\\s+" // 4: priority
160 "([a-fA-F0-9.:]+)\\s+" // 5: ip_address (IPv4 or IPv6)
161 "(\\d+)\\s+" // 6: port
162 "typ\\s+" // literal "typ"
163 "(host|srflx|prflx|relay)" // 7: candidate_type
164 "(?:\\s+raddr\\s+([a-fA-F0-9.:]+))?" // 8: optional raddr
165 "(?:\\s+rport\\s+(\\d+))?" // 9: optional rport
166 "(?:\\s+tcptype\\s+(active|passive|so))?" // 10: optional tcptype
167 "\\s*$";
168
169static pcre2_singleton_t *g_ice_candidate_regex = NULL;
170
175static pcre2_code *ice_candidate_regex_get(void) {
176 if (g_ice_candidate_regex == NULL) {
177 g_ice_candidate_regex = asciichat_pcre2_singleton_compile(ICE_CANDIDATE_PATTERN, PCRE2_CASELESS);
178 }
179 return asciichat_pcre2_singleton_get_code(g_ice_candidate_regex);
180}
181
192static asciichat_error_t ice_parse_candidate_pcre2(const char *line, ice_candidate_t *candidate) {
193 if (!line || !candidate) {
194 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid candidate parameters");
195 }
196
197 // Get compiled regex (lazy initialization)
198 pcre2_code *regex = ice_candidate_regex_get();
199
200 // If PCRE2 not available, return error to fall back to manual parser
201 if (!regex) {
202 return ERROR_MEMORY; // Signal to use fallback
203 }
204
205 pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(regex, NULL);
206 if (!match_data) {
207 return ERROR_MEMORY;
208 }
209
210 int rc = pcre2_jit_match(regex, (PCRE2_SPTR8)line, strlen(line), 0, 0, match_data, NULL);
211
212 if (rc < 7) {
213 // Must have at least foundation, component, protocol, priority, ip, port, type (7 groups)
214 pcre2_match_data_free(match_data);
215 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid ICE candidate format");
216 }
217
218 memset(candidate, 0, sizeof(ice_candidate_t));
219
220 // Extract foundation (group 1)
221 char *foundation = asciichat_pcre2_extract_group(match_data, 1, line);
222 if (foundation && strlen(foundation) < sizeof(candidate->foundation)) {
223 SAFE_STRNCPY(candidate->foundation, foundation, sizeof(candidate->foundation));
224 }
225 SAFE_FREE(foundation);
226
227 // Extract component_id (group 2)
228 unsigned long component_id;
229 if (asciichat_pcre2_extract_group_ulong(match_data, 2, line, &component_id)) {
230 candidate->component_id = (uint32_t)component_id;
231 }
232
233 // Extract protocol (group 3)
234 char *protocol_str = asciichat_pcre2_extract_group(match_data, 3, line);
235 if (protocol_str) {
236 if (strcmp(protocol_str, "udp") == 0) {
237 candidate->protocol = ICE_PROTOCOL_UDP;
238 } else if (strcmp(protocol_str, "tcp") == 0) {
239 candidate->protocol = ICE_PROTOCOL_TCP;
240 } else {
241 SAFE_FREE(protocol_str);
242 pcre2_match_data_free(match_data);
243 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid protocol");
244 }
245 SAFE_FREE(protocol_str);
246 }
247
248 // Extract priority (group 4)
249 unsigned long priority;
250 if (asciichat_pcre2_extract_group_ulong(match_data, 4, line, &priority)) {
251 candidate->priority = (uint32_t)priority;
252 }
253
254 // Extract IP address (group 5)
255 char *ip_address = asciichat_pcre2_extract_group(match_data, 5, line);
256 if (ip_address && strlen(ip_address) < sizeof(candidate->ip_address)) {
257 SAFE_STRNCPY(candidate->ip_address, ip_address, sizeof(candidate->ip_address));
258 }
259 SAFE_FREE(ip_address);
260
261 // Extract port (group 6)
262 unsigned long port;
263 if (asciichat_pcre2_extract_group_ulong(match_data, 6, line, &port)) {
264 candidate->port = (uint16_t)port;
265 }
266
267 // Extract candidate type (group 7)
268 char *type_str = asciichat_pcre2_extract_group(match_data, 7, line);
269 if (type_str) {
270 if (strcmp(type_str, "host") == 0) {
271 candidate->type = ICE_CANDIDATE_HOST;
272 } else if (strcmp(type_str, "srflx") == 0) {
273 candidate->type = ICE_CANDIDATE_SRFLX;
274 } else if (strcmp(type_str, "prflx") == 0) {
275 candidate->type = ICE_CANDIDATE_PRFLX;
276 } else if (strcmp(type_str, "relay") == 0) {
277 candidate->type = ICE_CANDIDATE_RELAY;
278 } else {
279 SAFE_FREE(type_str);
280 pcre2_match_data_free(match_data);
281 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid candidate type");
282 }
283 SAFE_FREE(type_str);
284 }
285
286 // Extract optional raddr (group 8)
287 char *raddr = asciichat_pcre2_extract_group(match_data, 8, line);
288 if (raddr && strlen(raddr) < sizeof(candidate->raddr)) {
289 SAFE_STRNCPY(candidate->raddr, raddr, sizeof(candidate->raddr));
290 }
291 SAFE_FREE(raddr);
292
293 // Extract optional rport (group 9)
294 unsigned long rport;
295 if (asciichat_pcre2_extract_group_ulong(match_data, 9, line, &rport)) {
296 candidate->rport = (uint16_t)rport;
297 }
298
299 // Extract optional tcptype (group 10)
300 char *tcptype_str = asciichat_pcre2_extract_group(match_data, 10, line);
301 if (tcptype_str) {
302 if (strcmp(tcptype_str, "active") == 0) {
303 candidate->tcp_type = ICE_TCP_TYPE_ACTIVE;
304 } else if (strcmp(tcptype_str, "passive") == 0) {
305 candidate->tcp_type = ICE_TCP_TYPE_PASSIVE;
306 } else if (strcmp(tcptype_str, "so") == 0) {
307 candidate->tcp_type = ICE_TCP_TYPE_SO;
308 }
309 SAFE_FREE(tcptype_str);
310 }
311
312 pcre2_match_data_free(match_data);
313 return ASCIICHAT_OK;
314}
315
317 return ice_parse_candidate_pcre2(line, candidate);
318}
319
320asciichat_error_t ice_format_candidate(const ice_candidate_t *candidate, char *line, size_t line_size) {
321 if (!candidate || !line || line_size == 0) {
322 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid candidate format parameters");
323 }
324
325 // Format: foundation component protocol priority ip port typ type [raddr rport] [tcptype type]
326 // Example: 1 1 udp 2130706431 192.168.1.1 54321 typ host
327 // Example with raddr: 1 1 udp 2130706431 203.0.113.45 54321 typ srflx raddr 192.168.1.1 rport 12345
328
329 int written = safe_snprintf(line, line_size, "%s %u %s %u %s %u typ %s", candidate->foundation,
330 candidate->component_id, ice_protocol_name(candidate->protocol), candidate->priority,
331 candidate->ip_address, candidate->port, ice_candidate_type_name(candidate->type));
332
333 if (written < 0 || (size_t)written >= line_size) {
334 return SET_ERRNO(ERROR_BUFFER_FULL, "Candidate line too large for buffer");
335 }
336
337 size_t remaining = line_size - (size_t)written;
338 char *pos = line + written;
339
340 // 2. If srflx/prflx/relay: append raddr and rport
341 if (candidate->type == ICE_CANDIDATE_SRFLX || candidate->type == ICE_CANDIDATE_PRFLX ||
342 candidate->type == ICE_CANDIDATE_RELAY) {
343 if (candidate->raddr[0] != '\0') {
344 int appended = safe_snprintf(pos, remaining, " raddr %s rport %u", candidate->raddr, candidate->rport);
345 if (appended < 0 || (size_t)appended >= remaining) {
346 return SET_ERRNO(ERROR_BUFFER_FULL, "Cannot append raddr/rport to candidate line");
347 }
348 pos += appended;
349 remaining -= (size_t)appended;
350 }
351 }
352
353 // 3. If TCP: append tcptype
354 if (candidate->protocol == ICE_PROTOCOL_TCP) {
355 const char *tcptype_str = "passive"; // default
356 if (candidate->tcp_type == ICE_TCP_TYPE_ACTIVE) {
357 tcptype_str = "active";
358 } else if (candidate->tcp_type == ICE_TCP_TYPE_SO) {
359 tcptype_str = "so";
360 }
361
362 int appended = safe_snprintf(pos, remaining, " tcptype %s", tcptype_str);
363 if (appended < 0 || (size_t)appended >= remaining) {
364 return SET_ERRNO(ERROR_BUFFER_FULL, "Cannot append tcptype to candidate line");
365 }
366 pos += appended;
367 remaining -= (size_t)appended;
368 }
369
370 // 4. Append any extensions
371 if (candidate->extensions[0] != '\0') {
372 int appended = safe_snprintf(pos, remaining, " %s", candidate->extensions);
373 if (appended < 0 || (size_t)appended >= remaining) {
374 return SET_ERRNO(ERROR_BUFFER_FULL, "Cannot append extensions to candidate line");
375 }
376 }
377
378 // 5. Ensure line is null-terminated (snprintf already does this)
379 return ASCIICHAT_OK;
380}
381
382/* ============================================================================
383 * ICE Candidate Gathering
384 * ============================================================================ */
385
387 if (!config) {
388 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid ICE config");
389 }
390
391 if (!config->send_callback) {
392 return SET_ERRNO(ERROR_INVALID_PARAM, "ICE config missing send_callback");
393 }
394
395 log_debug("ICE: Starting candidate gathering (ufrag=%s, pwd=%s)", config->ufrag, config->pwd);
396
397 // NOTE: libdatachannel (juice) handles ICE candidate gathering internally.
398 // This function is a placeholder for the callback-based API.
399 // The actual gathering happens in the peer_manager when you:
400 // 1. Create peer connection with rtcCreatePeerConnection()
401 // 2. Set ice candidate callback with rtcSetIceCandidateCallback()
402 // 3. libdatachannel automatically gathers candidates from:
403 // - Local interfaces (host candidates)
404 // - STUN servers (server-reflexive candidates)
405 // - TURN servers (relay candidates)
406 // 4. When a candidate is found, rtcSetIceCandidateCallback() is called
407 // 5. Application (webrtc.c) formats and sends via ACDS signaling
408 //
409 // This ice_gather_candidates() function may not be called directly -
410 // the gathering happens automatically when peer connection is created.
411 // Keeping this for API compatibility.
412
413 return ASCIICHAT_OK;
414}
415
416/* ============================================================================
417 * ICE Connectivity
418 * ============================================================================ */
419
421 const char *mid) {
422 if (!pc || !candidate || !mid) {
423 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid remote candidate parameters");
424 }
425
426 // Validate candidate has required fields
427 if (candidate->ip_address[0] == '\0' || candidate->port == 0) {
428 return SET_ERRNO(ERROR_INVALID_PARAM, "Candidate missing IP address or port");
429 }
430
431 log_debug("ICE: Adding remote candidate %s:%u (type=%s, foundation=%s) on mid=%s", candidate->ip_address,
432 candidate->port, ice_candidate_type_name(candidate->type), candidate->foundation, mid);
433
434 // Format candidate to SDP attribute line for libdatachannel
435 char candidate_line[512];
436 asciichat_error_t err = ice_format_candidate(candidate, candidate_line, sizeof(candidate_line));
437 if (err != ASCIICHAT_OK) {
438 return err;
439 }
440
441 // Delegate to WebRTC layer
442 return webrtc_add_remote_candidate(pc, candidate_line, mid);
443}
444
446 if (!pc) {
447 return false;
448 }
449
450 // Check peer connection state
452 return (state == WEBRTC_STATE_CONNECTED);
453}
454
455// Forward declaration of C++ implementation (in ice_selected_pair.cpp)
457 ice_candidate_t *remote_candidate);
458
460 ice_candidate_t *remote_candidate) {
461 // Delegate to C++ implementation which uses libdatachannel's C++ API
462 return ice_get_selected_pair_impl(pc, local_candidate, remote_candidate);
463}
unsigned short uint16_t
Definition common.h:57
unsigned int uint32_t
Definition common.h:58
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define SAFE_FREE(ptr)
Definition common.h:376
unsigned char uint8_t
Definition common.h:56
#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
@ ERROR_BUFFER_FULL
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
int is_lan_ipv6(const char *ip)
Check if IPv6 address is a private/LAN address.
Definition ip.c:1216
int is_localhost_ipv6(const char *ip)
Check if IPv6 address is localhost.
Definition ip.c:1320
int is_internet_ipv4(const char *ip)
Check if IPv4 address is a public internet address.
Definition ip.c:1408
int is_lan_ipv4(const char *ip)
Check if IPv4 address is a private/LAN address.
Definition ip.c:1185
int is_localhost_ipv4(const char *ip)
Check if IPv4 address is localhost.
Definition ip.c:1299
int is_internet_ipv6(const char *ip)
Check if IPv6 address is a public internet address.
Definition ip.c:1490
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.
webrtc_state_t
WebRTC connection state.
@ WEBRTC_STATE_CONNECTED
DataChannel established and ready.
uint32_t ice_calculate_priority_for_candidate(const ice_candidate_t *candidate)
Calculate candidate priority with IP-aware local preference.
Definition ice.c:133
const char * ice_protocol_name(ice_protocol_t protocol)
Get human-readable protocol name.
Definition ice.c:41
asciichat_error_t ice_get_selected_pair(webrtc_peer_connection_t *pc, ice_candidate_t *local_candidate, ice_candidate_t *remote_candidate)
Get selected candidate pair.
Definition ice.c:459
uint32_t ice_calculate_priority(ice_candidate_type_t type, uint16_t local_preference, uint8_t component_id)
Calculate candidate priority.
Definition ice.c:101
bool ice_is_connected(webrtc_peer_connection_t *pc)
Check ICE connection state.
Definition ice.c:445
asciichat_error_t ice_format_candidate(const ice_candidate_t *candidate, char *line, size_t line_size)
Format ICE candidate to attribute string.
Definition ice.c:320
asciichat_error_t ice_parse_candidate(const char *line, ice_candidate_t *candidate)
Parse ICE candidate from attribute string.
Definition ice.c:316
asciichat_error_t ice_get_selected_pair_impl(webrtc_peer_connection_t *pc, ice_candidate_t *local_candidate, ice_candidate_t *remote_candidate)
Get selected ICE candidate pair (C++ implementation)
const char * ice_candidate_type_name(ice_candidate_type_t type)
Get human-readable candidate type name.
Definition ice.c:26
asciichat_error_t ice_add_remote_candidate(webrtc_peer_connection_t *pc, const ice_candidate_t *candidate, const char *mid)
Add remote candidate to peer connection.
Definition ice.c:420
asciichat_error_t ice_gather_candidates(const ice_config_t *config)
Start ICE candidate gathering.
Definition ice.c:386
ICE (Interactive Connectivity Establishment) for WebRTC.
ice_protocol_t
ICE candidate transport protocol.
Definition ice.h:84
@ ICE_PROTOCOL_UDP
Definition ice.h:85
@ ICE_PROTOCOL_TCP
Definition ice.h:86
ice_candidate_type_t
ICE candidate type enumeration (RFC 5245)
Definition ice.h:74
@ ICE_CANDIDATE_SRFLX
Server-reflexive (NAT-discovered via STUN)
Definition ice.h:76
@ ICE_CANDIDATE_HOST
Host candidate (local IP address)
Definition ice.h:75
@ ICE_CANDIDATE_RELAY
Relay candidate (TURN server)
Definition ice.h:78
@ ICE_CANDIDATE_PRFLX
Peer-reflexive (discovered during checks)
Definition ice.h:77
@ ICE_TCP_TYPE_SO
Simultaneous open.
Definition ice.h:95
@ ICE_TCP_TYPE_ACTIVE
Actively opens connection.
Definition ice.h:93
@ ICE_TCP_TYPE_PASSIVE
Passively waits for connection.
Definition ice.h:94
🌍 IP Address Parsing and Formatting Utilities
📝 Logging API with multiple log levels and terminal output control
pcre2_code * asciichat_pcre2_singleton_get_code(pcre2_singleton_t *singleton)
Get the compiled pcre2_code from a singleton handle.
Definition pcre2.c:98
char * asciichat_pcre2_extract_group(pcre2_match_data *match_data, int group_num, const char *subject)
Extract numbered capture group as allocated string.
Definition pcre2.c:308
bool asciichat_pcre2_extract_group_ulong(pcre2_match_data *match_data, int group_num, const char *subject, unsigned long *out_value)
Extract numbered capture group and convert to unsigned long.
Definition pcre2.c:361
pcre2_singleton_t * asciichat_pcre2_singleton_compile(const char *pattern, uint32_t flags)
Compile and cache a PCRE2 regex pattern with thread-safe singleton semantics.
Definition pcre2.c:47
🔍 Centralized PCRE2 singleton pattern for efficient regex compilation
Single ICE candidate for connectivity.
Definition ice.h:111
char ip_address[64]
IP address (IPv4 or IPv6)
Definition ice.h:119
char foundation[32]
Unique identifier for candidate (for pairing)
Definition ice.h:113
uint16_t port
Port number.
Definition ice.h:120
uint32_t component_id
Component (1=RTP, 2=RTCP; usually 1)
Definition ice.h:114
ice_candidate_type_t type
host, srflx, prflx, or relay
Definition ice.h:123
uint32_t priority
Candidate priority (used for preference)
Definition ice.h:116
uint16_t rport
Related port.
Definition ice.h:127
ice_protocol_t protocol
UDP or TCP.
Definition ice.h:115
char extensions[256]
Additional extensions (e.g., "tcptype passive")
Definition ice.h:133
ice_tcp_type_t tcp_type
active, passive, so
Definition ice.h:130
char raddr[64]
Related address (for srflx/prflx)
Definition ice.h:126
ICE gathering configuration.
Definition ice.h:157
const char * ufrag
Username fragment for ICE (from offer)
Definition ice.h:158
ice_send_candidate_callback_t send_callback
Called for each gathered candidate.
Definition ice.h:160
const char * pwd
Password for ICE (from offer)
Definition ice.h:159
Represents a thread-safe compiled PCRE2 regex singleton.
Definition pcre2.c:22
WebRTC peer connection state.