11#include <ascii-chat/network/webrtc/ice.h>
12#include <ascii-chat/network/webrtc/webrtc.h>
13#include <ascii-chat/log/logging.h>
14#include <ascii-chat/platform/util.h>
15#include <ascii-chat/util/pcre2.h>
16#include <ascii-chat/util/ip.h>
20#define PCRE2_CODE_UNIT_WIDTH 8
29 case ICE_CANDIDATE_HOST:
31 case ICE_CANDIDATE_SRFLX:
33 case ICE_CANDIDATE_PRFLX:
35 case ICE_CANDIDATE_RELAY:
44 case ICE_PROTOCOL_UDP:
46 case ICE_PROTOCOL_TCP:
73static uint16_t ice_calculate_local_preference_by_ip(
const char *ip_address) {
74 if (!ip_address || ip_address[0] ==
'\0') {
79 if (strcmp(ip_address,
"0.0.0.0") == 0 || strcmp(ip_address,
"::") == 0) {
111 uint8_t type_preference = 0;
113 case ICE_CANDIDATE_HOST:
114 type_preference = 126;
116 case ICE_CANDIDATE_SRFLX:
117 type_preference = 100;
119 case ICE_CANDIDATE_PRFLX:
120 type_preference = 110;
122 case ICE_CANDIDATE_RELAY:
131 (((uint32_t)type_preference) << 24) | (((uint32_t)local_preference) << 8) | (256 - (uint32_t)component_id);
142 uint16_t local_pref = ice_calculate_local_preference_by_ip(candidate->ip_address);
159static const char *ICE_CANDIDATE_PATTERN =
"^([^ ]+)\\s+"
163 "([a-fA-F0-9.:]+)\\s+"
166 "(host|srflx|prflx|relay)"
167 "(?:\\s+raddr\\s+([a-fA-F0-9.:]+))?"
168 "(?:\\s+rport\\s+(\\d+))?"
169 "(?:\\s+tcptype\\s+(active|passive|so))?"
178static pcre2_code *ice_candidate_regex_get(
void) {
179 if (g_ice_candidate_regex == NULL) {
180 g_ice_candidate_regex = asciichat_pcre2_singleton_compile(ICE_CANDIDATE_PATTERN, PCRE2_CASELESS);
195static asciichat_error_t ice_parse_candidate_pcre2(
const char *line, ice_candidate_t *candidate) {
196 if (!line || !candidate) {
197 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid candidate parameters");
201 pcre2_code *regex = ice_candidate_regex_get();
208 pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(regex, NULL);
213 int rc = pcre2_jit_match(regex, (PCRE2_SPTR8)line, strlen(line), 0, 0, match_data, NULL);
217 pcre2_match_data_free(match_data);
218 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid ICE candidate format");
221 memset(candidate, 0,
sizeof(ice_candidate_t));
225 if (foundation && strlen(foundation) <
sizeof(candidate->foundation)) {
226 SAFE_STRNCPY(candidate->foundation, foundation,
sizeof(candidate->foundation));
228 SAFE_FREE(foundation);
231 unsigned long component_id;
233 candidate->component_id = (uint32_t)component_id;
239 if (strcmp(protocol_str,
"udp") == 0) {
240 candidate->protocol = ICE_PROTOCOL_UDP;
241 }
else if (strcmp(protocol_str,
"tcp") == 0) {
242 candidate->protocol = ICE_PROTOCOL_TCP;
244 SAFE_FREE(protocol_str);
245 pcre2_match_data_free(match_data);
246 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid protocol");
248 SAFE_FREE(protocol_str);
252 unsigned long priority;
254 candidate->priority = (uint32_t)priority;
259 if (ip_address && strlen(ip_address) <
sizeof(candidate->ip_address)) {
260 SAFE_STRNCPY(candidate->ip_address, ip_address,
sizeof(candidate->ip_address));
262 SAFE_FREE(ip_address);
267 candidate->port = (uint16_t)port;
273 if (strcmp(type_str,
"host") == 0) {
274 candidate->type = ICE_CANDIDATE_HOST;
275 }
else if (strcmp(type_str,
"srflx") == 0) {
276 candidate->type = ICE_CANDIDATE_SRFLX;
277 }
else if (strcmp(type_str,
"prflx") == 0) {
278 candidate->type = ICE_CANDIDATE_PRFLX;
279 }
else if (strcmp(type_str,
"relay") == 0) {
280 candidate->type = ICE_CANDIDATE_RELAY;
283 pcre2_match_data_free(match_data);
284 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid candidate type");
291 if (raddr && strlen(raddr) <
sizeof(candidate->raddr)) {
292 SAFE_STRNCPY(candidate->raddr, raddr,
sizeof(candidate->raddr));
299 candidate->rport = (uint16_t)rport;
305 if (strcmp(tcptype_str,
"active") == 0) {
306 candidate->tcp_type = ICE_TCP_TYPE_ACTIVE;
307 }
else if (strcmp(tcptype_str,
"passive") == 0) {
308 candidate->tcp_type = ICE_TCP_TYPE_PASSIVE;
309 }
else if (strcmp(tcptype_str,
"so") == 0) {
310 candidate->tcp_type = ICE_TCP_TYPE_SO;
312 SAFE_FREE(tcptype_str);
315 pcre2_match_data_free(match_data);
320 return ice_parse_candidate_pcre2(line, candidate);
324 if (!candidate || !line || line_size == 0) {
325 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid candidate format parameters");
332 int written =
safe_snprintf(line, line_size,
"%s %u %s %u %s %u typ %s", candidate->foundation,
333 candidate->component_id,
ice_protocol_name(candidate->protocol), candidate->priority,
336 if (written < 0 || (
size_t)written >= line_size) {
337 return SET_ERRNO(ERROR_BUFFER_FULL,
"Candidate line too large for buffer");
340 size_t remaining = line_size - (size_t)written;
341 char *pos = line + written;
344 if (candidate->type == ICE_CANDIDATE_SRFLX || candidate->type == ICE_CANDIDATE_PRFLX ||
345 candidate->type == ICE_CANDIDATE_RELAY) {
346 if (candidate->raddr[0] !=
'\0') {
347 int appended =
safe_snprintf(pos, remaining,
" raddr %s rport %u", candidate->raddr, candidate->rport);
348 if (appended < 0 || (
size_t)appended >= remaining) {
349 return SET_ERRNO(ERROR_BUFFER_FULL,
"Cannot append raddr/rport to candidate line");
352 remaining -= (size_t)appended;
357 if (candidate->protocol == ICE_PROTOCOL_TCP) {
358 const char *tcptype_str =
"passive";
359 if (candidate->tcp_type == ICE_TCP_TYPE_ACTIVE) {
360 tcptype_str =
"active";
361 }
else if (candidate->tcp_type == ICE_TCP_TYPE_SO) {
365 int appended =
safe_snprintf(pos, remaining,
" tcptype %s", tcptype_str);
366 if (appended < 0 || (
size_t)appended >= remaining) {
367 return SET_ERRNO(ERROR_BUFFER_FULL,
"Cannot append tcptype to candidate line");
370 remaining -= (size_t)appended;
374 if (candidate->extensions[0] !=
'\0') {
375 int appended =
safe_snprintf(pos, remaining,
" %s", candidate->extensions);
376 if (appended < 0 || (
size_t)appended >= remaining) {
377 return SET_ERRNO(ERROR_BUFFER_FULL,
"Cannot append extensions to candidate line");
391 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid ICE config");
394 if (!config->send_callback) {
395 return SET_ERRNO(ERROR_INVALID_PARAM,
"ICE config missing send_callback");
398 log_debug(
"ICE: Starting candidate gathering (ufrag=%s, pwd=%s)", config->ufrag, config->pwd);
425 if (!pc || !candidate || !mid) {
426 return SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid remote candidate parameters");
430 if (candidate->ip_address[0] ==
'\0' || candidate->port == 0) {
431 return SET_ERRNO(ERROR_INVALID_PARAM,
"Candidate missing IP address or port");
434 log_debug(
"ICE: Adding remote candidate %s:%u (type=%s, foundation=%s) on mid=%s", candidate->ip_address,
438 char candidate_line[512];
439 asciichat_error_t err =
ice_format_candidate(candidate, candidate_line,
sizeof(candidate_line));
440 if (err != ASCIICHAT_OK) {
455 return (state == WEBRTC_STATE_CONNECTED);
460 ice_candidate_t *remote_candidate);
463 ice_candidate_t *remote_candidate) {
uint32_t ice_calculate_priority_for_candidate(const ice_candidate_t *candidate)
const char * ice_protocol_name(ice_protocol_t protocol)
asciichat_error_t ice_get_selected_pair(webrtc_peer_connection_t *pc, ice_candidate_t *local_candidate, ice_candidate_t *remote_candidate)
uint32_t ice_calculate_priority(ice_candidate_type_t type, uint16_t local_preference, uint8_t component_id)
bool ice_is_connected(webrtc_peer_connection_t *pc)
asciichat_error_t ice_format_candidate(const ice_candidate_t *candidate, char *line, size_t line_size)
asciichat_error_t ice_parse_candidate(const char *line, ice_candidate_t *candidate)
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)
asciichat_error_t ice_add_remote_candidate(webrtc_peer_connection_t *pc, const ice_candidate_t *candidate, const char *mid)
asciichat_error_t ice_gather_candidates(const ice_config_t *config)
int is_lan_ipv6(const char *ip)
int is_localhost_ipv6(const char *ip)
int is_internet_ipv4(const char *ip)
int is_lan_ipv4(const char *ip)
int is_localhost_ipv4(const char *ip)
int is_internet_ipv6(const char *ip)
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)
pcre2_code * asciichat_pcre2_singleton_get_code(pcre2_singleton_t *singleton)
Get the compiled pcre2_code from a singleton handle.
char * asciichat_pcre2_extract_group(pcre2_match_data *match_data, int group_num, const char *subject)
Extract numbered capture group as allocated string.
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.
Represents a thread-safe compiled PCRE2 regex singleton.
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.