rice_c/
agent.rs

1// Copyright (C) 2025 Matthew Waters <matthew@centricular.com>
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! ICE Agent implementation as specified in RFC 8445
10
11use crate::{candidate::TransportType, mut_override, stream::Stream};
12
13use sans_io_time::Instant;
14
15/// An ICE agent as specified in RFC 8445
16#[derive(Debug)]
17pub struct Agent {
18    ffi: *mut crate::ffi::RiceAgent,
19}
20
21unsafe impl Send for Agent {}
22unsafe impl Sync for Agent {}
23
24impl Clone for Agent {
25    fn clone(&self) -> Self {
26        Self {
27            ffi: unsafe { crate::ffi::rice_agent_ref(self.ffi) },
28        }
29    }
30}
31
32impl Drop for Agent {
33    fn drop(&mut self) {
34        unsafe { crate::ffi::rice_agent_unref(self.ffi) }
35    }
36}
37
38impl Default for Agent {
39    fn default() -> Self {
40        Agent::builder().build()
41    }
42}
43
44impl Agent {
45    pub(crate) fn from_c_full(ffi: *mut crate::ffi::RiceAgent) -> Self {
46        Self { ffi }
47    }
48
49    /// Create a new [`AgentBuilder`]
50    pub fn builder() -> AgentBuilder {
51        AgentBuilder::default()
52    }
53
54    /// A process-unique identifier for this agent.
55    pub fn id(&self) -> u64 {
56        unsafe { crate::ffi::rice_agent_id(self.ffi) }
57    }
58
59    /// Add a new `Stream` to this agent
60    ///
61    /// # Examples
62    ///
63    /// Add a `Stream`
64    ///
65    /// ```
66    /// # use rice_c::agent::Agent;
67    /// let agent = Agent::default();
68    /// let s = agent.add_stream();
69    /// ```
70    pub fn add_stream(&self) -> crate::stream::Stream {
71        unsafe { Stream::from_c_full(crate::ffi::rice_agent_add_stream(self.ffi)) }
72    }
73
74    /// Retrieve a [`Stream`] by its ID from this [`Agent`].
75    pub fn stream(&self, id: usize) -> Option<crate::stream::Stream> {
76        let ret = unsafe { crate::ffi::rice_agent_get_stream(self.ffi, id) };
77        if ret.is_null() {
78            None
79        } else {
80            Some(crate::stream::Stream::from_c_full(ret))
81        }
82    }
83
84    /// Close the agent loop.  Applications should wait for [`Agent::poll`] to return
85    /// [`AgentPoll::Closed`] after calling this function.
86    pub fn close(&self, now: Instant) {
87        unsafe { crate::ffi::rice_agent_close(self.ffi, now.as_nanos()) }
88    }
89
90    /// The controlling state of this ICE agent.  This value may change throughout the ICE
91    /// negotiation process.
92    pub fn controlling(&self) -> bool {
93        unsafe { crate::ffi::rice_agent_get_controlling(self.ffi) }
94    }
95
96    /// Add a STUN server by address and transport to use for gathering potential candidates
97    pub fn add_stun_server(
98        &self,
99        transport: crate::candidate::TransportType,
100        addr: crate::Address,
101    ) {
102        unsafe { crate::ffi::rice_agent_add_stun_server(self.ffi, transport.into(), addr.as_c()) }
103    }
104
105    /// Poll the [`Agent`] for further progress to be made.
106    ///
107    /// The returned value indicates what the application needs to do.
108    pub fn poll(&self, now: Instant) -> AgentPoll {
109        let mut ret = crate::ffi::RiceAgentPoll {
110            tag: crate::ffi::RICE_AGENT_POLL_CLOSED,
111            field1: crate::ffi::RiceAgentPoll__bindgen_ty_1 {
112                field1: core::mem::ManuallyDrop::new(
113                    crate::ffi::RiceAgentPoll__bindgen_ty_1__bindgen_ty_1 {
114                        wait_until_nanos: 0,
115                    },
116                ),
117            },
118        };
119
120        unsafe {
121            crate::ffi::rice_agent_poll_init(&mut ret);
122            crate::ffi::rice_agent_poll(self.ffi, now.as_nanos(), &mut ret);
123        }
124
125        AgentPoll::from_c_full(ret)
126    }
127
128    /// Poll for a transmission to be performed.
129    ///
130    /// If not-None, then the provided data must be sent to the peer from the provided socket
131    /// address.
132    pub fn poll_transmit(&self, now: Instant) -> Option<AgentTransmit> {
133        let mut ret = crate::ffi::RiceTransmit {
134            stream_id: 0,
135            transport: crate::ffi::RICE_TRANSPORT_TYPE_UDP,
136            from: core::ptr::null(),
137            to: core::ptr::null(),
138            data: crate::ffi::RiceDataImpl {
139                ptr: core::ptr::null_mut(),
140                size: 0,
141            },
142        };
143        unsafe { crate::ffi::rice_agent_poll_transmit(self.ffi, now.as_nanos(), &mut ret) }
144        if ret.from.is_null() || ret.to.is_null() {
145            return None;
146        }
147        Some(AgentTransmit::from_c_full(ret))
148    }
149    // TODO: stun_servers(), add_turn_server(), turn_servers(), stream()
150}
151
152/// A builder for an [`Agent`]
153#[derive(Debug, Default)]
154pub struct AgentBuilder {
155    trickle_ice: bool,
156    controlling: bool,
157}
158
159impl AgentBuilder {
160    /// Whether candidates can trickle in during ICE negotiation
161    pub fn trickle_ice(mut self, trickle_ice: bool) -> Self {
162        self.trickle_ice = trickle_ice;
163        self
164    }
165
166    /// The initial value of the controlling attribute.  During the ICE negotiation, the
167    /// controlling value may change.
168    pub fn controlling(mut self, controlling: bool) -> Self {
169        self.controlling = controlling;
170        self
171    }
172
173    /// Construct a new [`Agent`]
174    pub fn build(self) -> Agent {
175        Agent {
176            ffi: unsafe { crate::ffi::rice_agent_new(self.controlling, self.trickle_ice) },
177        }
178    }
179}
180
181/// Indicates what the caller should do after calling [`Agent::poll`]
182#[derive(Debug, Default)]
183pub enum AgentPoll {
184    /// The Agent is closed.  No further progress will be made.
185    #[default]
186    Closed,
187    /// Wait until the specified `Instant` has been reached (or an external event)
188    WaitUntilNanos(i64),
189    /// Connect from the specified interface to the specified address.  Reply (success or failure)
190    /// should be notified using [`Stream::allocated_socket`] with the same parameters.
191    AllocateSocket(AgentSocket),
192    /// It is posible to remove the specified 5-tuple. The socket will not be referenced any
193    /// further.
194    RemoveSocket(AgentSocket),
195    /// A new pair has been selected for a component.
196    SelectedPair(AgentSelectedPair),
197    /// A [`Component`](crate::component::Component) has changed state.
198    ComponentStateChange(AgentComponentStateChange),
199    /// A [`Component`](crate::component::Component) has gathered a candidate.
200    GatheredCandidate(AgentGatheredCandidate),
201    /// A [`Component`](crate::component::Component) has completed gathering.
202    GatheringComplete(AgentGatheringComplete),
203}
204
205impl AgentPoll {
206    fn from_c_full(mut ffi: crate::ffi::RiceAgentPoll) -> Self {
207        unsafe {
208            match ffi.tag {
209                crate::ffi::RICE_AGENT_POLL_CLOSED => Self::Closed,
210                crate::ffi::RICE_AGENT_POLL_WAIT_UNTIL_NANOS => Self::WaitUntilNanos(
211                    core::mem::ManuallyDrop::into_inner(ffi.field1.field1).wait_until_nanos,
212                ),
213                crate::ffi::RICE_AGENT_POLL_ALLOCATE_SOCKET => {
214                    let ty = core::mem::ManuallyDrop::into_inner(ffi.field1.field2).allocate_socket;
215                    Self::AllocateSocket(AgentSocket {
216                        stream_id: ty.stream_id,
217                        component_id: ty.component_id,
218                        transport: ty.transport.into(),
219                        from: crate::Address::from_c_full(mut_override(ty.from)),
220                        to: crate::Address::from_c_full(mut_override(ty.to)),
221                    })
222                }
223                crate::ffi::RICE_AGENT_POLL_REMOVE_SOCKET => {
224                    let ty = core::mem::ManuallyDrop::into_inner(ffi.field1.field3).remove_socket;
225                    ffi.tag = crate::ffi::RICE_AGENT_POLL_CLOSED;
226                    Self::RemoveSocket(AgentSocket {
227                        stream_id: ty.stream_id,
228                        component_id: ty.component_id,
229                        transport: ty.transport.into(),
230                        from: crate::Address::from_c_full(mut_override(ty.from)),
231                        to: crate::Address::from_c_full(mut_override(ty.to)),
232                    })
233                }
234                crate::ffi::RICE_AGENT_POLL_SELECTED_PAIR => {
235                    let mut ty =
236                        core::mem::ManuallyDrop::into_inner(ffi.field1.field4).selected_pair;
237                    let local = crate::candidate::Candidate::from_c_none(&ty.local);
238                    let remote = crate::candidate::Candidate::from_c_none(&ty.remote);
239                    crate::ffi::rice_candidate_clear(&mut ty.local);
240                    crate::ffi::rice_candidate_clear(&mut ty.remote);
241                    ffi.tag = crate::ffi::RICE_AGENT_POLL_CLOSED;
242                    let turn = if !ty.local_turn_local_addr.is_null()
243                        && !ty.local_turn_remote_addr.is_null()
244                    {
245                        Some(SelectedTurn {
246                            transport: ty.local_turn_transport.into(),
247                            local_addr: crate::Address::from_c_none(ty.local_turn_local_addr),
248                            remote_addr: crate::Address::from_c_none(ty.local_turn_remote_addr),
249                        })
250                    } else {
251                        None
252                    };
253                    crate::ffi::rice_address_free(mut_override(ty.local_turn_local_addr));
254                    ty.local_turn_local_addr = core::ptr::null_mut();
255                    crate::ffi::rice_address_free(mut_override(ty.local_turn_remote_addr));
256                    ty.local_turn_remote_addr = core::ptr::null_mut();
257                    Self::SelectedPair(AgentSelectedPair {
258                        stream_id: ty.stream_id,
259                        component_id: ty.component_id,
260                        local,
261                        remote,
262                        turn,
263                    })
264                }
265                crate::ffi::RICE_AGENT_POLL_COMPONENT_STATE_CHANGE => {
266                    let ty = core::mem::ManuallyDrop::into_inner(ffi.field1.field5)
267                        .component_state_change;
268                    Self::ComponentStateChange(AgentComponentStateChange {
269                        stream_id: ty.stream_id,
270                        component_id: ty.component_id,
271                        state: crate::component::ComponentConnectionState::from_c(ty.state),
272                    })
273                }
274                crate::ffi::RICE_AGENT_POLL_GATHERED_CANDIDATE => {
275                    let ty =
276                        core::mem::ManuallyDrop::into_inner(ffi.field1.field6).gathered_candidate;
277                    let stream_id = ty.stream_id;
278                    let gathered = crate::stream::GatheredCandidate::from_c_full(ty.gathered);
279                    ffi.tag = crate::ffi::RICE_AGENT_POLL_CLOSED;
280                    Self::GatheredCandidate(AgentGatheredCandidate {
281                        stream_id,
282                        gathered,
283                    })
284                }
285                crate::ffi::RICE_AGENT_POLL_GATHERING_COMPLETE => {
286                    let ty =
287                        core::mem::ManuallyDrop::into_inner(ffi.field1.field7).gathering_complete;
288                    Self::GatheringComplete(AgentGatheringComplete {
289                        stream_id: ty.stream_id,
290                        component_id: ty.component_id,
291                    })
292                }
293                tag => panic!("Unkown AgentPoll value {tag:x?}"),
294            }
295        }
296    }
297}
298
299impl Drop for AgentPoll {
300    fn drop(&mut self) {
301        unsafe {
302            if let Self::GatheredCandidate(gathered) = self {
303                let mut ret = crate::ffi::RiceAgentPoll {
304                    tag: crate::ffi::RICE_AGENT_POLL_GATHERED_CANDIDATE,
305                    field1: crate::ffi::RiceAgentPoll__bindgen_ty_1 {
306                        field6: core::mem::ManuallyDrop::new(
307                            crate::ffi::RiceAgentPoll__bindgen_ty_1__bindgen_ty_6 {
308                                gathered_candidate: crate::ffi::RiceAgentGatheredCandidate {
309                                    stream_id: gathered.stream_id,
310                                    gathered: crate::stream::GatheredCandidate::take(
311                                        &mut gathered.gathered,
312                                    )
313                                    .ffi,
314                                },
315                            },
316                        ),
317                    },
318                };
319                crate::ffi::rice_agent_poll_clear(&raw mut ret);
320            }
321        }
322    }
323}
324
325/// Transmit the data using the specified 5-tuple.
326#[derive(Debug)]
327pub struct AgentTransmit {
328    /// The ICE stream id.
329    pub stream_id: usize,
330    /// The socket to send the data from.
331    pub from: crate::Address,
332    /// The network address to send the data to.
333    pub to: crate::Address,
334    /// The transport to send the data over.
335    pub transport: crate::candidate::TransportType,
336    /// The data to send.
337    pub data: &'static [u8],
338}
339
340impl AgentTransmit {
341    pub(crate) fn from_c_full(ffi: crate::ffi::RiceTransmit) -> Self {
342        unsafe {
343            let data = ffi.data.ptr;
344            let len = ffi.data.size;
345            let data = core::slice::from_raw_parts(data, len);
346            AgentTransmit {
347                stream_id: ffi.stream_id,
348                from: crate::Address::from_c_full(mut_override(ffi.from)),
349                to: crate::Address::from_c_full(mut_override(ffi.to)),
350                transport: ffi.transport.into(),
351                data,
352            }
353        }
354    }
355}
356
357impl Drop for AgentTransmit {
358    fn drop(&mut self) {
359        unsafe {
360            let mut transmit = crate::ffi::RiceTransmit {
361                stream_id: self.stream_id,
362                from: core::ptr::null_mut(),
363                to: core::ptr::null_mut(),
364                transport: self.transport.into(),
365                data: crate::ffi::RiceDataImpl::to_c(self.data),
366            };
367            crate::ffi::rice_transmit_clear(&mut transmit);
368        }
369    }
370}
371
372/// A socket with the specified network 5-tuple.
373#[derive(Debug)]
374pub struct AgentSocket {
375    /// The ICE stream id.
376    pub stream_id: usize,
377    /// The ICE component id.
378    pub component_id: usize,
379    /// The transport.
380    pub transport: crate::candidate::TransportType,
381    /// The socket source address.
382    pub from: crate::Address,
383    /// The socket destination address.
384    pub to: crate::Address,
385}
386
387/// A new pair has been selected for a component.
388#[derive(Debug)]
389pub struct AgentSelectedPair {
390    /// The ICE stream id within the agent.
391    pub stream_id: usize,
392    /// The ICE component id within the stream.
393    pub component_id: usize,
394    /// The local candidate that has been selected.
395    pub local: crate::candidate::Candidate,
396    /// The remote candidate that has been selected.
397    pub remote: crate::candidate::Candidate,
398    /// The selected local candidate TURN connection (if any).
399    pub turn: Option<SelectedTurn>,
400}
401
402/// The selected TURN server socket parameters.
403#[derive(Debug)]
404pub struct SelectedTurn {
405    /// The transport.
406    pub transport: TransportType,
407    /// The local address.
408    pub local_addr: crate::Address,
409    /// The remote address.
410    pub remote_addr: crate::Address,
411}
412
413/// A [`Component`](crate::component::Component) has changed state.
414#[derive(Debug)]
415#[repr(C)]
416pub struct AgentComponentStateChange {
417    /// The ICE stream id.
418    pub stream_id: usize,
419    /// The ICE component id.
420    pub component_id: usize,
421    /// The new state of the component.
422    pub state: crate::component::ComponentConnectionState,
423}
424
425/// A [`Component`](crate::component::Component) has gathered a candidate.
426#[derive(Debug)]
427#[repr(C)]
428pub struct AgentGatheredCandidate {
429    /// The ICE stream id.
430    pub stream_id: usize,
431    /// The gathered candidate.
432    pub gathered: crate::stream::GatheredCandidate,
433}
434
435/// A [`Component`](crate::component::Component) has completed gathering.
436#[derive(Debug)]
437#[repr(C)]
438pub struct AgentGatheringComplete {
439    /// The ICE stream id.
440    pub stream_id: usize,
441    /// The ICE component id.
442    pub component_id: usize,
443}
444
445/// Errors that can be returned as a result of agent operations.
446#[derive(Debug, Copy, Clone, PartialEq, Eq)]
447#[repr(i32)]
448pub enum AgentError {
449    /// The operation failed for an unspecified reason.
450    Failed = crate::ffi::RICE_ERROR_FAILED,
451    /// A required resource was not found.
452    ResourceNotFound = crate::ffi::RICE_ERROR_RESOURCE_NOT_FOUND,
453    /// The operation is already in progress.
454    AlreadyInProgress = crate::ffi::RICE_ERROR_ALREADY_IN_PROGRESS,
455}
456
457impl core::fmt::Display for AgentError {
458    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
459        match self {
460            Self::Failed => write!(f, "Failed"),
461            Self::ResourceNotFound => write!(f, "Resource Not Found"),
462            Self::AlreadyInProgress => write!(f, "Already In Progress"),
463        }
464    }
465}
466
467impl AgentError {
468    pub(crate) fn from_c(value: crate::ffi::RiceError) -> Result<(), AgentError> {
469        match value {
470            crate::ffi::RICE_ERROR_SUCCESS => Ok(()),
471            crate::ffi::RICE_ERROR_FAILED => Err(AgentError::Failed),
472            crate::ffi::RICE_ERROR_RESOURCE_NOT_FOUND => Err(AgentError::ResourceNotFound),
473            crate::ffi::RICE_ERROR_ALREADY_IN_PROGRESS => Err(AgentError::AlreadyInProgress),
474            val => panic!("unknown RiceError value {val:x?}"),
475        }
476    }
477}