Skip to main content

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