ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
ice.c
Go to the documentation of this file.
1
11#include "ice.h"
12#include "log/logging.h"
13#include <string.h>
14#include <stdio.h>
15#include <stdlib.h>
16
17/* ============================================================================
18 * Utility Functions
19 * ============================================================================ */
20
22 switch (type) {
24 return "host";
26 return "srflx";
28 return "prflx";
30 return "relay";
31 default:
32 return "unknown";
33 }
34}
35
36const char *ice_protocol_name(ice_protocol_t protocol) {
37 switch (protocol) {
39 return "udp";
41 return "tcp";
42 default:
43 return "unknown";
44 }
45}
46
47/* ============================================================================
48 * ICE Candidate Priority
49 * ============================================================================ */
50
52 // RFC 5245: priority = (2^24 * typePreference) + (2^8 * localPreference) + (256 - componentID)
53
54 // Type preference (higher is better):
55 // - Host: 126 (most preferred, lowest latency)
56 // - SRFLX: 100 (STUN-discovered address)
57 // - PRFLX: 110 (discovered during connectivity checks)
58 // - Relay: 0 (least preferred, uses bandwidth)
59
60 uint8_t type_preference = 0;
61 switch (type) {
63 type_preference = 126;
64 break;
66 type_preference = 100;
67 break;
69 type_preference = 110;
70 break;
72 type_preference = 0;
73 break;
74 default:
75 type_preference = 0;
76 break;
77 }
78
79 uint32_t priority =
80 (((uint32_t)type_preference) << 24) | (((uint32_t)local_preference) << 8) | (256 - (uint32_t)component_id);
81
82 return priority;
83}
84
85/* ============================================================================
86 * ICE Candidate Parsing
87 * ============================================================================ */
88
90 if (!line || !candidate) {
91 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid candidate parse parameters");
92 }
93
94 memset(candidate, 0, sizeof(ice_candidate_t));
95
96 // Parse: foundation component protocol priority ip port typ type [raddr rport] [extensions]
97 // Example: 1 1 udp 2130706431 192.168.1.1 54321 typ host
98
99 char line_copy[512];
100 SAFE_STRNCPY(line_copy, line, sizeof(line_copy));
101
102 char *saveptr = NULL;
103 char *token = NULL;
104
105 // 1. Parse foundation
106 token = strtok_r(line_copy, " ", &saveptr);
107 if (!token) {
108 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing foundation in candidate");
109 }
110 SAFE_STRNCPY(candidate->foundation, token, sizeof(candidate->foundation));
111
112 // 2. Parse component ID
113 token = strtok_r(NULL, " ", &saveptr);
114 if (!token || sscanf(token, "%u", &candidate->component_id) != 1) {
115 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid component ID");
116 }
117
118 // 3. Parse protocol (udp/tcp)
119 token = strtok_r(NULL, " ", &saveptr);
120 if (!token) {
121 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing protocol");
122 }
123 if (strcmp(token, "udp") == 0) {
124 candidate->protocol = ICE_PROTOCOL_UDP;
125 } else if (strcmp(token, "tcp") == 0) {
126 candidate->protocol = ICE_PROTOCOL_TCP;
127 } else {
128 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid protocol: %s", token);
129 }
130
131 // 4. Parse priority
132 token = strtok_r(NULL, " ", &saveptr);
133 if (!token || sscanf(token, "%u", &candidate->priority) != 1) {
134 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid priority");
135 }
136
137 // 5. Parse IP address
138 token = strtok_r(NULL, " ", &saveptr);
139 if (!token) {
140 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing IP address");
141 }
142 SAFE_STRNCPY(candidate->ip_address, token, sizeof(candidate->ip_address));
143
144 // 6. Parse port
145 token = strtok_r(NULL, " ", &saveptr);
146 if (!token || sscanf(token, "%hu", &candidate->port) != 1) {
147 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid port");
148 }
149
150 // 7. Expect "typ" keyword
151 token = strtok_r(NULL, " ", &saveptr);
152 if (!token || strcmp(token, "typ") != 0) {
153 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing 'typ' keyword");
154 }
155
156 // 8. Parse candidate type
157 token = strtok_r(NULL, " ", &saveptr);
158 if (!token) {
159 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing candidate type");
160 }
161
162 if (strcmp(token, "host") == 0) {
163 candidate->type = ICE_CANDIDATE_HOST;
164 } else if (strcmp(token, "srflx") == 0) {
165 candidate->type = ICE_CANDIDATE_SRFLX;
166 } else if (strcmp(token, "prflx") == 0) {
167 candidate->type = ICE_CANDIDATE_PRFLX;
168 } else if (strcmp(token, "relay") == 0) {
169 candidate->type = ICE_CANDIDATE_RELAY;
170 } else {
171 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid candidate type: %s", token);
172 }
173
174 // 9. If srflx/prflx/relay: parse raddr and rport
175 if (candidate->type == ICE_CANDIDATE_SRFLX || candidate->type == ICE_CANDIDATE_PRFLX ||
176 candidate->type == ICE_CANDIDATE_RELAY) {
177 token = strtok_r(NULL, " ", &saveptr);
178 if (token && strcmp(token, "raddr") == 0) {
179 token = strtok_r(NULL, " ", &saveptr);
180 if (!token) {
181 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing raddr value");
182 }
183 SAFE_STRNCPY(candidate->raddr, token, sizeof(candidate->raddr));
184
185 token = strtok_r(NULL, " ", &saveptr);
186 if (!token || strcmp(token, "rport") != 0) {
187 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing 'rport' keyword");
188 }
189
190 token = strtok_r(NULL, " ", &saveptr);
191 if (!token || sscanf(token, "%hu", &candidate->rport) != 1) {
192 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid rport value");
193 }
194 }
195 }
196
197 // 10. Parse any extensions (tcptype for TCP candidates)
198 if (candidate->protocol == ICE_PROTOCOL_TCP) {
199 token = strtok_r(NULL, " ", &saveptr);
200 while (token) {
201 if (strcmp(token, "tcptype") == 0) {
202 token = strtok_r(NULL, " ", &saveptr);
203 if (token) {
204 if (strcmp(token, "active") == 0) {
205 candidate->tcp_type = ICE_TCP_TYPE_ACTIVE;
206 } else if (strcmp(token, "passive") == 0) {
207 candidate->tcp_type = ICE_TCP_TYPE_PASSIVE;
208 } else if (strcmp(token, "so") == 0) {
209 candidate->tcp_type = ICE_TCP_TYPE_SO;
210 }
211 }
212 }
213 token = strtok_r(NULL, " ", &saveptr);
214 }
215 }
216
217 return ASCIICHAT_OK;
218}
219
220asciichat_error_t ice_format_candidate(const ice_candidate_t *candidate, char *line, size_t line_size) {
221 if (!candidate || !line || line_size == 0) {
222 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid candidate format parameters");
223 }
224
225 // Format: foundation component protocol priority ip port typ type [raddr rport] [tcptype type]
226 // Example: 1 1 udp 2130706431 192.168.1.1 54321 typ host
227 // Example with raddr: 1 1 udp 2130706431 203.0.113.45 54321 typ srflx raddr 192.168.1.1 rport 12345
228
229 int written = snprintf(line, line_size, "%s %u %s %u %s %u typ %s", candidate->foundation, candidate->component_id,
230 ice_protocol_name(candidate->protocol), candidate->priority, candidate->ip_address,
231 candidate->port, ice_candidate_type_name(candidate->type));
232
233 if (written < 0 || (size_t)written >= line_size) {
234 return SET_ERRNO(ERROR_BUFFER_FULL, "Candidate line too large for buffer");
235 }
236
237 size_t remaining = line_size - (size_t)written;
238 char *pos = line + written;
239
240 // 2. If srflx/prflx/relay: append raddr and rport
241 if (candidate->type == ICE_CANDIDATE_SRFLX || candidate->type == ICE_CANDIDATE_PRFLX ||
242 candidate->type == ICE_CANDIDATE_RELAY) {
243 if (candidate->raddr[0] != '\0') {
244 int appended = snprintf(pos, remaining, " raddr %s rport %u", candidate->raddr, candidate->rport);
245 if (appended < 0 || (size_t)appended >= remaining) {
246 return SET_ERRNO(ERROR_BUFFER_FULL, "Cannot append raddr/rport to candidate line");
247 }
248 pos += appended;
249 remaining -= (size_t)appended;
250 }
251 }
252
253 // 3. If TCP: append tcptype
254 if (candidate->protocol == ICE_PROTOCOL_TCP) {
255 const char *tcptype_str = "passive"; // default
256 if (candidate->tcp_type == ICE_TCP_TYPE_ACTIVE) {
257 tcptype_str = "active";
258 } else if (candidate->tcp_type == ICE_TCP_TYPE_SO) {
259 tcptype_str = "so";
260 }
261
262 int appended = snprintf(pos, remaining, " tcptype %s", tcptype_str);
263 if (appended < 0 || (size_t)appended >= remaining) {
264 return SET_ERRNO(ERROR_BUFFER_FULL, "Cannot append tcptype to candidate line");
265 }
266 pos += appended;
267 remaining -= (size_t)appended;
268 }
269
270 // 4. Append any extensions
271 if (candidate->extensions[0] != '\0') {
272 int appended = snprintf(pos, remaining, " %s", candidate->extensions);
273 if (appended < 0 || (size_t)appended >= remaining) {
274 return SET_ERRNO(ERROR_BUFFER_FULL, "Cannot append extensions to candidate line");
275 }
276 }
277
278 // 5. Ensure line is null-terminated (snprintf already does this)
279 return ASCIICHAT_OK;
280}
281
282/* ============================================================================
283 * ICE Candidate Gathering
284 * ============================================================================ */
285
287 if (!config) {
288 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid ICE config");
289 }
290
291 if (!config->send_callback) {
292 return SET_ERRNO(ERROR_INVALID_PARAM, "ICE config missing send_callback");
293 }
294
295 log_debug("ICE: Starting candidate gathering (ufrag=%s, pwd=%s)", config->ufrag, config->pwd);
296
297 // NOTE: libdatachannel (juice) handles ICE candidate gathering internally.
298 // This function is a placeholder for the callback-based API.
299 // The actual gathering happens in the peer_manager when you:
300 // 1. Create peer connection with rtcCreatePeerConnection()
301 // 2. Set ice candidate callback with rtcSetIceCandidateCallback()
302 // 3. libdatachannel automatically gathers candidates from:
303 // - Local interfaces (host candidates)
304 // - STUN servers (server-reflexive candidates)
305 // - TURN servers (relay candidates)
306 // 4. When a candidate is found, rtcSetIceCandidateCallback() is called
307 // 5. Application (webrtc.c) formats and sends via ACDS signaling
308 //
309 // This ice_gather_candidates() function may not be called directly -
310 // the gathering happens automatically when peer connection is created.
311 // Keeping this for API compatibility.
312
313 return ASCIICHAT_OK;
314}
315
316/* ============================================================================
317 * ICE Connectivity
318 * ============================================================================ */
319
321 if (!candidate || !mid) {
322 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid remote candidate parameters");
323 }
324
325 // Validate candidate has required fields
326 if (candidate->ip_address[0] == '\0' || candidate->port == 0) {
327 return SET_ERRNO(ERROR_INVALID_PARAM, "Candidate missing IP address or port");
328 }
329
330 log_debug("ICE: Adding remote candidate %s:%u (type=%s, foundation=%s) on mid=%s", candidate->ip_address,
331 candidate->port, ice_candidate_type_name(candidate->type), candidate->foundation, mid);
332
333 // TODO: Implement remote candidate addition to peer connection
334 // Integration point with libdatachannel peer manager:
335 // Steps:
336 // 1. Get current peer connection from global state (webrtc_get_transport() or similar)
337 // 2. Call libdatachannel's addIceCandidate(candidate) function
338 // 3. libdatachannel will:
339 // - Add to remote candidate list
340 // - Begin connectivity checks with local candidates
341 // - Measure RTT, jitter, packet loss
342 // - Select best working pair
343 // 4. When connection succeeds, data channel becomes ready
344
345 // For now, log the candidate and return success
346 // Real implementation will integrate with webrtc_peer_manager_add_remote_candidate()
347 // or similar function that will be called by the signaling relay
348
349 return ASCIICHAT_OK;
350}
351
353 // TODO: Implement connectivity check
354 // This requires integration with libdatachannel peer connection state machine.
355 // For now, return false - will be implemented when peer_manager state tracking is added.
356 // Step 1: Get current peer connection from global state
357 // Step 2: Check if state == CONNECTED or READY
358 // Step 3: Return true if at least one candidate pair has data flowing
359 return false;
360}
361
363 // TODO: Implement selected pair retrieval
364 // This requires integration with libdatachannel peer connection.
365 // For now, return error - will be implemented when peer_manager is integrated.
366 // Steps:
367 // 1. Get current peer connection from global state
368 // 2. Find the candidate pair in "Ready" state (data is flowing)
369 // 3. Copy local candidate if local_candidate != NULL
370 // 4. Copy remote candidate if remote_candidate != NULL
371 // 5. Return error if no pair selected yet
372
373 return SET_ERRNO(ERROR_INVALID_STATE, "No candidate pair selected yet");
374}
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:358
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_INVALID_STATE
@ ERROR_BUFFER_FULL
Definition error_codes.h:97
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_INVALID_PARAM
#define log_debug(...)
Log a DEBUG message.
asciichat_error_t ice_get_selected_pair(ice_candidate_t *local_candidate, ice_candidate_t *remote_candidate)
Get selected candidate pair.
Definition ice.c:362
const char * ice_protocol_name(ice_protocol_t protocol)
Get human-readable protocol name.
Definition ice.c:36
uint32_t ice_calculate_priority(ice_candidate_type_t type, uint16_t local_preference, uint8_t component_id)
Calculate candidate priority.
Definition ice.c:51
bool ice_is_connected(void)
Check ICE connection state.
Definition ice.c:352
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:220
asciichat_error_t ice_add_remote_candidate(const ice_candidate_t *candidate, const char *mid)
Add remote candidate to peer connection.
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:89
const char * ice_candidate_type_name(ice_candidate_type_t type)
Get human-readable candidate type name.
Definition ice.c:21
asciichat_error_t ice_gather_candidates(const ice_config_t *config)
Start ICE candidate gathering.
Definition ice.c:286
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
📝 Logging API with multiple log levels and terminal output control
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