Skip to main content

rice_proto/
agent.rs

1// Copyright (C) 2024 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 alloc::boxed::Box;
14use alloc::vec::Vec;
15
16use core::error::Error;
17use core::fmt::Display;
18use core::net::SocketAddr;
19use core::sync::atomic::AtomicU64;
20use core::time::Duration;
21
22use stun_proto::Instant;
23use stun_proto::types::data::Data;
24
25use crate::candidate::{ParseCandidateError, TransportType};
26use crate::component::ComponentConnectionState;
27use crate::conncheck::{
28    CheckListSetPollRet, ConnCheckEvent, ConnCheckListSet, RequestRto, SelectedPair,
29};
30use crate::gathering::{GatherPoll, GatheredCandidate};
31use crate::rand::rand_u64;
32use crate::stream::{Stream, StreamMut, StreamState};
33use crate::turn::TurnConfig;
34use stun_proto::agent::{StunError, Transmit};
35use stun_proto::types::message::StunParseError;
36
37use tracing::{info, warn};
38
39/// Errors that can be returned as a result of agent operations.
40#[derive(Debug)]
41pub enum AgentError {
42    /// Failed for an unspecified reason.
43    Failed,
44    /// The specified resource already exists and cannot be added.
45    AlreadyExists,
46    /// The operation is already in progress.
47    AlreadyInProgress,
48    /// The operation is not in progress.
49    NotInProgress,
50    /// Could not find the specified resource.
51    ResourceNotFound,
52    /// The data provided was not in the correct format.
53    Malformed,
54    /// This data is not handled by this implementation.
55    WrongImplementation,
56    /// The connection to the remote has been closed.
57    ConnectionClosed,
58    /// Parsing the STUN message failed.
59    StunParse,
60    /// Writing the STUN message failed.
61    StunWrite,
62    /// Parsing the candidate failed.
63    CandidateParse(ParseCandidateError),
64    /// Data was received that does not match the protocol specifications.
65    ProtocolViolation,
66}
67
68impl Error for AgentError {}
69
70impl Display for AgentError {
71    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
72        write!(f, "{self:?}")
73    }
74}
75
76impl From<ParseCandidateError> for AgentError {
77    fn from(e: ParseCandidateError) -> Self {
78        Self::CandidateParse(e)
79    }
80}
81
82impl From<StunError> for AgentError {
83    fn from(e: StunError) -> Self {
84        match e {
85            StunError::ResourceNotFound => AgentError::ResourceNotFound,
86            StunError::ProtocolViolation => AgentError::ProtocolViolation,
87            StunError::ParseError(_) => AgentError::StunParse,
88            StunError::WriteError(_) => AgentError::StunWrite,
89            StunError::AlreadyInProgress => AgentError::AlreadyInProgress,
90            _ => AgentError::Failed,
91        }
92    }
93}
94
95impl From<StunParseError> for AgentError {
96    fn from(_e: StunParseError) -> Self {
97        Self::StunParse
98    }
99}
100
101/// An ICE agent as specified in RFC 8445
102#[derive(Debug)]
103pub struct Agent {
104    id: u64,
105    pub(crate) checklistset: ConnCheckListSet,
106    pub(crate) stun_servers: Vec<(TransportType, SocketAddr)>,
107    pub(crate) turn_servers: Vec<TurnConfig>,
108    streams: Vec<StreamState>,
109    pub(crate) rto: Option<RequestRto>,
110}
111
112/// A builder for an [`Agent`]
113#[derive(Debug)]
114pub struct AgentBuilder {
115    trickle_ice: bool,
116    controlling: bool,
117    timing_advance: Duration,
118    rto: Option<RequestRto>,
119}
120
121impl Default for AgentBuilder {
122    fn default() -> Self {
123        Self {
124            trickle_ice: false,
125            controlling: false,
126            timing_advance: crate::conncheck::DEFAULT_MINIMUM_SET_TICK,
127            rto: None,
128        }
129    }
130}
131
132impl AgentBuilder {
133    /// Whether candidates can trickle in during ICE negotiation
134    pub fn trickle_ice(mut self, trickle_ice: bool) -> Self {
135        self.trickle_ice = trickle_ice;
136        self
137    }
138
139    /// The initial value of the controlling attribute.  During the ICE negotiation, the
140    /// controlling value may change.
141    pub fn controlling(mut self, controlling: bool) -> Self {
142        self.controlling = controlling;
143        self
144    }
145
146    /// Set the minimum amount of time between subsequent STUN requests sent.
147    ///
148    /// This is known as the Ta value in the ICE specification.
149    ///
150    /// The default value is 50ms.
151    pub fn timing_advance(mut self, ta: Duration) -> Self {
152        self.timing_advance = ta;
153        self
154    }
155
156    /// Configure the default timeouts and retransmissions for each STUN request.
157    ///
158    /// - `initial` - the initial time between consecutive transmissions. If 0, or 1, then only a
159    ///   single request will be performed.
160    /// - `max` - the maximum amount of time between consecutive retransmits.
161    /// - `retransmits` - the total number of transmissions of the request.
162    /// - `final_retransmit_timeout` - the amount of time after the final transmission to wait
163    ///   for a response before considering the request as having timed out.
164    ///
165    /// As specified in RFC 8489, `initial_rto` should be >= 500ms (unless specific information is
166    /// available on the RTT, `max` is `Duration::MAX`, `retransmits` has a default value of 7,
167    /// and `last_retransmit_timeout` should be `16 * initial_rto`.
168    ///
169    /// STUN transactions over TCP will only send a single request and have a timeout of the sum of
170    /// the timeouts of a UDP transaction.
171    pub fn set_request_retransmits(
172        mut self,
173        initial: Duration,
174        max: Duration,
175        retransmits: u32,
176        final_retransmit_timeout: Duration,
177    ) -> Self {
178        self.rto = Some(RequestRto::from_parts(
179            initial,
180            max,
181            retransmits,
182            final_retransmit_timeout,
183        ));
184        self
185    }
186
187    /// Construct a new [`Agent`]
188    pub fn build(self) -> Agent {
189        turn_client_proto::types::debug_init();
190        rice_stun_types::debug_init();
191
192        let id = AGENT_COUNT.fetch_add(1, core::sync::atomic::Ordering::SeqCst);
193        let tie_breaker = rand_u64();
194        let controlling = self.controlling;
195        let mut checklistset = ConnCheckListSet::builder(tie_breaker, controlling)
196            .trickle_ice(self.trickle_ice)
197            .timing_advance(self.timing_advance)
198            .build();
199        if let Some(rto) = self.rto.clone() {
200            checklistset.set_request_retransmits(rto);
201        }
202        Agent {
203            id,
204            checklistset,
205            stun_servers: Vec::new(),
206            turn_servers: Vec::new(),
207            streams: Vec::new(),
208            rto: self.rto,
209        }
210    }
211}
212
213static AGENT_COUNT: AtomicU64 = AtomicU64::new(0);
214
215impl Default for Agent {
216    fn default() -> Self {
217        Agent::builder().build()
218    }
219}
220
221impl Agent {
222    /// Create a new [`AgentBuilder`]
223    pub fn builder() -> AgentBuilder {
224        AgentBuilder::default()
225    }
226
227    /// The identifier for this [`Agent`]
228    pub fn id(&self) -> u64 {
229        self.id
230    }
231
232    /// The minimum amount of time between subsequent STUN requests sent.
233    ///
234    /// This is known as the Ta value in the ICE specification.
235    ///
236    /// The default value is 50ms.
237    pub fn timing_advance(&self) -> Duration {
238        self.checklistset.timing_advance()
239    }
240
241    /// Set the minimum amount of time between subsequent STUN requests sent.
242    ///
243    /// This is known as the Ta value in the ICE specification.
244    ///
245    /// The default value is 50ms.
246    pub fn set_timing_advance(&mut self, ta: Duration) {
247        self.checklistset.set_timing_advance(ta)
248    }
249
250    /// Configure the default timeouts and retransmissions for each STUN request.
251    ///
252    /// - `initial` - the initial time between consecutive transmissions. If 0, or 1, then only a
253    ///   single request will be performed.
254    /// - `max` - the maximum amount of time between consecutive retransmits.
255    /// - `retransmits` - the total number of transmissions of the request.
256    /// - `final_retransmit_timeout` - the amount of time after the final transmission to wait
257    ///   for a response before considering the request as having timed out.
258    ///
259    /// As specified in RFC 8489, `initial_rto` should be >= 500ms (unless specific information is
260    /// available on the RTT, `max` is `Duration::MAX`, `retransmits` has a default value of 7,
261    /// and `last_retransmit_timeout` should be `16 * initial_rto`.
262    ///
263    /// STUN transactions over TCP will only send a single request and have a timeout of the sum of
264    /// the timeouts of a UDP transaction.
265    pub fn set_request_retransmits(
266        &mut self,
267        initial: Duration,
268        max: Duration,
269        retransmits: u32,
270        final_retransmit_timeout: Duration,
271    ) {
272        let rto = RequestRto::from_parts(initial, max, retransmits, final_retransmit_timeout);
273        for stream in self.streams.iter_mut() {
274            stream.set_request_retransmits(rto.clone());
275        }
276        self.checklistset.set_request_retransmits(rto);
277    }
278
279    /// Add a new `Stream` to this agent
280    ///
281    /// # Examples
282    ///
283    /// Add a `Stream`
284    ///
285    /// ```
286    /// # use rice_proto::agent::Agent;
287    /// let mut agent = Agent::default();
288    /// let s = agent.add_stream();
289    /// ```
290    #[tracing::instrument(
291        name = "ice_add_stream",
292        skip(self),
293        fields(
294            ice.id = self.id
295        )
296    )]
297    pub fn add_stream(&mut self) -> usize {
298        let checklist_id = self.checklistset.new_list();
299        let id = self.streams.len();
300        let stream = crate::stream::StreamState::new(id, checklist_id);
301        self.streams.push(stream);
302        id
303    }
304
305    /// Close the agent loop.  Applications should wait for [`Agent::poll`] to return
306    /// [`AgentPoll::Closed`] after calling this function.
307    #[tracing::instrument(
308        name = "ice_close",
309        skip(self),
310        fields(
311            ice.id = self.id
312        )
313    )]
314    pub fn close(&mut self, now: Instant) {
315        info!("closing agent");
316        self.checklistset.close(now);
317    }
318
319    /// The controlling state of this ICE agent.  This value may change throughout the ICE
320    /// negotiation process.
321    pub fn controlling(&self) -> bool {
322        self.checklistset.controlling()
323    }
324
325    /// Add a STUN server by address and transport to use for gathering potential candidates
326    #[tracing::instrument(
327        name = "ice_add_stun_server",
328        skip(self)
329        fields(ice.id = self.id)
330    )]
331    pub fn add_stun_server(&mut self, transport: TransportType, addr: SocketAddr) {
332        info!("Adding stun server");
333        self.stun_servers.push((transport, addr));
334        // TODO: propagate towards the gatherer as required
335    }
336
337    /// The current list of STUN servers used by this [`Agent`]
338    pub fn stun_servers(&self) -> &[(TransportType, SocketAddr)] {
339        &self.stun_servers
340    }
341
342    /// Add a TURN server by address, transport, and credentials to use for gathering potential
343    /// candidates.
344    #[tracing::instrument(
345        name = "ice_add_turn_server",
346        skip(self)
347        fields(ice.id = self.id)
348    )]
349    pub fn add_turn_server(&mut self, config: TurnConfig) {
350        info!("Adding turn server");
351        self.turn_servers.push(config);
352        // TODO: propagate towards the gatherer as required
353    }
354
355    /// The current list of TURN servers used by this [`Agent`]
356    pub fn turn_servers(&self) -> &[TurnConfig] {
357        &self.turn_servers
358    }
359
360    /// Get a [`Stream`] by id.
361    ///
362    /// If the stream does not exist, then `None` will be returned.
363    pub fn stream(&self, id: usize) -> Option<crate::stream::Stream<'_>> {
364        if self.streams.get(id).is_some() {
365            Some(Stream::from_agent(self, id))
366        } else {
367            None
368        }
369    }
370
371    pub(crate) fn stream_state(&self, id: usize) -> Option<&crate::stream::StreamState> {
372        self.streams.get(id)
373    }
374
375    /// Get a [`StreamMut`] by id.  If the stream does not exist, then `None` will be returned.
376    pub fn mut_stream(&mut self, id: usize) -> Option<StreamMut<'_>> {
377        if self.streams.get_mut(id).is_some() {
378            Some(StreamMut::from_agent(self, id))
379        } else {
380            None
381        }
382    }
383
384    pub(crate) fn mut_stream_state(
385        &mut self,
386        id: usize,
387    ) -> Option<&mut crate::stream::StreamState> {
388        self.streams.get_mut(id)
389    }
390
391    /// Poll the [`Agent`] for further progress to be made.  The returned value indicates what the
392    /// application needs to do.
393    #[tracing::instrument(
394        name = "agent_poll",
395        ret
396        skip(self)
397        fields(
398            id = self.id,
399        )
400    )]
401    pub fn poll(&mut self, now: Instant) -> AgentPoll {
402        let mut lowest_wait = None;
403
404        for stream in self.streams.iter_mut() {
405            let stream_id = stream.id();
406            match stream.poll_gather(now) {
407                GatherPoll::AllocateSocket {
408                    component_id,
409                    transport,
410                    local_addr,
411                    remote_addr,
412                } => {
413                    return AgentPoll::AllocateSocket(AgentSocket {
414                        stream_id,
415                        component_id,
416                        transport,
417                        from: local_addr,
418                        to: remote_addr,
419                    });
420                }
421                GatherPoll::WaitUntil(earliest_wait) => {
422                    if let Some(check_wait) = lowest_wait {
423                        if earliest_wait < check_wait {
424                            lowest_wait = Some(earliest_wait);
425                        }
426                    } else {
427                        lowest_wait = Some(earliest_wait);
428                    }
429                }
430                GatherPoll::NewCandidate(candidate) => {
431                    return AgentPoll::GatheredCandidate(AgentGatheredCandidate {
432                        stream_id,
433                        gathered: candidate,
434                    });
435                }
436                GatherPoll::Complete(component_id) => {
437                    return AgentPoll::GatheringComplete(AgentGatheringComplete {
438                        stream_id,
439                        component_id,
440                    });
441                }
442                GatherPoll::Finished => (),
443            }
444        }
445
446        loop {
447            match self.checklistset.poll(now) {
448                CheckListSetPollRet::Closed => return AgentPoll::Closed,
449                CheckListSetPollRet::Completed => continue,
450                CheckListSetPollRet::WaitUntil(earliest_wait) => {
451                    if let Some(check_wait) = lowest_wait {
452                        if earliest_wait < check_wait {
453                            lowest_wait = Some(earliest_wait);
454                        }
455                    } else {
456                        lowest_wait = Some(earliest_wait);
457                    }
458                    break;
459                }
460                CheckListSetPollRet::AllocateSocket {
461                    checklist_id,
462                    component_id: cid,
463                    transport,
464                    local_addr: from,
465                    remote_addr: to,
466                } => {
467                    if let Some(stream) =
468                        self.streams.iter().find(|s| s.checklist_id == checklist_id)
469                    {
470                        return AgentPoll::AllocateSocket(AgentSocket {
471                            stream_id: stream.id(),
472                            component_id: cid,
473                            transport,
474                            from,
475                            to,
476                        });
477                    } else {
478                        warn!("did not find stream for allocate socket {from:?} -> {to:?}");
479                    }
480                }
481                CheckListSetPollRet::RemoveSocket {
482                    checklist_id,
483                    component_id: cid,
484                    transport,
485                    local_addr: from,
486                    remote_addr: to,
487                } => {
488                    if let Some(stream) =
489                        self.streams.iter().find(|s| s.checklist_id == checklist_id)
490                    {
491                        return AgentPoll::RemoveSocket(AgentSocket {
492                            stream_id: stream.id(),
493                            component_id: cid,
494                            transport,
495                            from,
496                            to,
497                        });
498                    } else {
499                        warn!("did not find stream for remove socket {from:?} -> {to:?}");
500                    }
501                }
502                CheckListSetPollRet::Event {
503                    checklist_id,
504                    event: ConnCheckEvent::ComponentState(cid, state),
505                } => {
506                    if let Some(stream) = self
507                        .streams
508                        .iter_mut()
509                        .find(|s| s.checklist_id == checklist_id)
510                    {
511                        if let Some(component) = stream.mut_component_state(cid) {
512                            if component.set_state(state) {
513                                return AgentPoll::ComponentStateChange(
514                                    AgentComponentStateChange {
515                                        stream_id: stream.id(),
516                                        component_id: cid,
517                                        state,
518                                    },
519                                );
520                            }
521                        }
522                    }
523                }
524                CheckListSetPollRet::Event {
525                    checklist_id,
526                    event: ConnCheckEvent::SelectedPair(cid, selected),
527                } => {
528                    if let Some(stream) =
529                        self.streams.iter().find(|s| s.checklist_id == checklist_id)
530                    {
531                        if stream.component_state(cid).is_some() {
532                            return AgentPoll::SelectedPair(AgentSelectedPair {
533                                stream_id: stream.id(),
534                                component_id: cid,
535                                selected,
536                            });
537                        }
538                    }
539                }
540            }
541        }
542
543        AgentPoll::WaitUntil(lowest_wait.unwrap_or_else(|| now + Duration::from_secs(600)))
544    }
545
546    /// Poll for a transmission to be performed.
547    ///
548    /// If not-None, then the provided data must be sent to the peer from the provided socket
549    /// address.
550    pub fn poll_transmit(&mut self, now: Instant) -> Option<AgentTransmit> {
551        for stream in self.streams.iter_mut() {
552            let stream_id = stream.id();
553            if let Some((_component_id, transmit)) = stream.poll_gather_transmit(now) {
554                return Some(AgentTransmit::from_data(stream_id, transmit));
555            }
556        }
557        let transmit = self.checklistset.poll_transmit(now)?;
558        if let Some(stream) = self
559            .streams
560            .iter()
561            .find(|s| s.checklist_id == transmit.checklist_id)
562        {
563            Some(AgentTransmit {
564                stream_id: stream.id(),
565                transmit: transmit.transmit,
566            })
567        } else {
568            warn!(
569                "did not find stream for transmit {:?} -> {:?}",
570                transmit.transmit.from, transmit.transmit.to
571            );
572            None
573        }
574    }
575}
576
577/// Indicates what the caller should do after calling [`Agent::poll`]
578#[derive(Debug)]
579pub enum AgentPoll {
580    /// The Agent is closed.  No further progress will be made.
581    Closed,
582    /// Wait until the specified `Instant` has been reached (or an external event)
583    WaitUntil(Instant),
584    /// Connect using the specified 5-tuple.  Reply (success or failure)
585    /// should be notified using [`StreamMut::allocated_socket`] with the same parameters.
586    AllocateSocket(AgentSocket),
587    /// It is posible to remove the specified 5-tuple. The socket will not be referenced any
588    /// further.
589    RemoveSocket(AgentSocket),
590    /// A new pair has been selected for a component.
591    SelectedPair(AgentSelectedPair),
592    /// A [`Component`](crate::component::Component) has changed state.
593    ComponentStateChange(AgentComponentStateChange),
594    /// A [`Component`](crate::component::Component) has gathered a candidate.
595    GatheredCandidate(AgentGatheredCandidate),
596    /// A [`Component`](crate::component::Component) has completed gathering.
597    GatheringComplete(AgentGatheringComplete),
598}
599
600/// Transmit the data using the specified 5-tuple.
601#[derive(Debug)]
602pub struct AgentTransmit {
603    /// The ICE stream id within the agent.
604    pub stream_id: usize,
605    /// The network 5-tuple and data to send.
606    pub transmit: Transmit<Box<[u8]>>,
607}
608
609impl AgentTransmit {
610    fn from_data(stream_id: usize, transmit: Transmit<Data<'_>>) -> Self {
611        Self {
612            stream_id,
613            transmit: transmit.reinterpret_data(|data| {
614                let Data::Owned(owned) = data.into_owned() else {
615                    unreachable!();
616                };
617                owned.take()
618            }),
619        }
620    }
621}
622
623/// A socket with the specified network 5-tuple.
624#[derive(Debug)]
625pub struct AgentSocket {
626    /// The ICE stream id within the agent.
627    pub stream_id: usize,
628    /// The ICE component id within the stream.
629    pub component_id: usize,
630    /// The transport for the socket.
631    pub transport: TransportType,
632    /// The source address of the socket.
633    pub from: SocketAddr,
634    /// The destination address of the socket.
635    pub to: SocketAddr,
636}
637
638/// A new pair has been selected for a component.
639#[derive(Debug)]
640pub struct AgentSelectedPair {
641    /// The ICE stream id within the agent.
642    pub stream_id: usize,
643    /// The ICE component id within the stream.
644    pub component_id: usize,
645    /// The selceted pair.
646    pub selected: Box<SelectedPair>,
647}
648
649/// A [`Component`](crate::component::Component) has changed state.
650#[derive(Debug)]
651#[repr(C)]
652pub struct AgentComponentStateChange {
653    /// The ICE stream id within the agent.
654    pub stream_id: usize,
655    /// The ICE component id within the stream.
656    pub component_id: usize,
657    /// The new state of the component.
658    pub state: ComponentConnectionState,
659}
660
661/// A [`Component`](crate::component::Component) has gathered a candidate.
662#[derive(Debug)]
663#[repr(C)]
664pub struct AgentGatheredCandidate {
665    /// The ICE stream id within the agent.
666    pub stream_id: usize,
667    /// The gathered candidate.
668    pub gathered: GatheredCandidate,
669}
670
671/// A [`Component`](crate::component::Component) has completed gathering.
672#[derive(Debug)]
673#[repr(C)]
674pub struct AgentGatheringComplete {
675    /// The ICE stream id within the agent.
676    pub stream_id: usize,
677    /// The ICE component id within the stream.
678    pub component_id: usize,
679}
680
681#[cfg(test)]
682mod tests {
683    use super::*;
684
685    #[test]
686    fn controlling() {
687        let _log = crate::tests::test_init_log();
688        let agent = Agent::builder().controlling(true).build();
689        assert!(agent.controlling());
690        let agent = Agent::builder().controlling(false).build();
691        assert!(!agent.controlling());
692    }
693
694    #[test]
695    fn timing_advance() {
696        let _log = crate::tests::test_init_log();
697        let ta = Duration::from_secs(1);
698        let default_ta = Duration::from_millis(50);
699        let mut agent = Agent::default();
700        assert_eq!(agent.timing_advance(), default_ta);
701        agent.set_timing_advance(ta);
702        assert_eq!(agent.timing_advance(), ta);
703        let agent = Agent::builder().timing_advance(ta).build();
704        assert_eq!(agent.timing_advance(), ta);
705    }
706}