Skip to main content

rtc_stun/
agent.rs

1//! STUN transaction tracking.
2//!
3//! The agent remembers which requests are outstanding and when each should be considered lost. It
4//! performs no I/O: the caller submits [`ClientAgent`](crate::agent::ClientAgent) commands — start a transaction, hand over
5//! an inbound message, advance time, stop or close — and polls for the resulting [`Event`](crate::agent::Event)s.
6//!
7//! This split is what lets the retransmission schedule be tested without a network, and lets ICE
8//! reuse the same transaction bookkeeping for its connectivity checks.
9#[cfg(test)]
10mod agent_test;
11
12use shared::error::*;
13use std::collections::{HashMap, VecDeque};
14use std::time::Instant;
15
16use crate::message::*;
17
18/// Agent is low-level abstraction over transaction list that
19/// handles concurrency and time outs (via Collect call).
20#[derive(Default)]
21pub struct Agent {
22    /// transactions is map of transactions that are currently
23    /// in progress. Event handling is done in such way when
24    /// transaction is unregistered before AgentTransaction access,
25    /// minimizing mux lock and protecting AgentTransaction from
26    /// data races via unexpected concurrent access.
27    transactions: HashMap<TransactionId, AgentTransaction>,
28    /// all calls are invalid if true
29    closed: bool,
30    /// events queue
31    events_queue: VecDeque<Event>,
32}
33
34/// Event is passed to Handler describing the transaction event.
35/// Do not reuse outside Handler.
36#[derive(Debug)] //Clone
37pub struct Event {
38    /// The transaction this event belongs to.
39    pub id: TransactionId,
40    /// What happened.
41    pub evt: StunEvent,
42}
43
44#[derive(Debug)] //Clone
45/// What became of a STUN transaction.
46pub enum StunEvent {
47    /// The agent was closed, abandoning this transaction.
48    AgentClosed,
49    /// The transaction was stopped by the caller.
50    TransactionStopped,
51    /// The transaction timed out with no response.
52    TransactionTimeOut,
53    /// A response arrived for this transaction.
54    Message(Message),
55}
56
57/// AgentTransaction represents transaction in progress.
58/// Concurrent access is invalid.
59pub(crate) struct AgentTransaction {
60    id: TransactionId,
61    deadline: Instant,
62}
63
64/// AGENT_COLLECT_CAP is initial capacity for Agent.Collect slices,
65/// sufficient to make function zero-alloc in most cases.
66const AGENT_COLLECT_CAP: usize = 100;
67
68/// ClientAgent is Agent implementation that is used by Client to
69/// process transactions.
70#[derive(Debug)]
71pub enum ClientAgent {
72    /// Hand an inbound message to the agent for matching against a transaction.
73    Process(Message),
74    /// Advance time so the agent can expire transactions.
75    Collect(Instant),
76    /// Register a new transaction with its deadline.
77    Start(TransactionId, Instant),
78    /// Abandon a transaction without waiting for its deadline.
79    Stop(TransactionId),
80    /// Close the agent, abandoning every outstanding transaction.
81    Close,
82}
83
84impl Agent {
85    /// new initializes and returns new Agent with provided handler.
86    pub fn new() -> Self {
87        Agent {
88            transactions: HashMap::new(),
89            closed: false,
90            events_queue: VecDeque::new(),
91        }
92    }
93
94    /// Applies an agent command: start, stop, process a message, collect timeouts, or close.
95    ///
96    /// # Errors
97    ///
98    /// Fails if the agent is closed, or a transaction id is already in use.
99    pub fn handle_event(&mut self, client_agent: ClientAgent) -> Result<()> {
100        match client_agent {
101            ClientAgent::Process(message) => self.process(message),
102            ClientAgent::Collect(deadline) => self.collect(deadline),
103            ClientAgent::Start(tid, deadline) => self.start(tid, deadline),
104            ClientAgent::Stop(tid) => self.stop(tid),
105            ClientAgent::Close => self.close(),
106        }
107    }
108
109    /// When the agent next needs [`ClientAgent::Collect`], or `None` with nothing outstanding.
110    pub fn poll_timeout(&mut self) -> Option<Instant> {
111        let mut deadline = None;
112        for transaction in self.transactions.values() {
113            if deadline.is_none() || transaction.deadline < *deadline.as_ref().unwrap() {
114                deadline = Some(transaction.deadline);
115            }
116        }
117        deadline
118    }
119
120    /// The next transaction event, or `None` when there is nothing to report.
121    pub fn poll_event(&mut self) -> Option<Event> {
122        self.events_queue.pop_front()
123    }
124
125    /// process incoming message, synchronously passing it to handler.
126    fn process(&mut self, message: Message) -> Result<()> {
127        if self.closed {
128            return Err(Error::ErrAgentClosed);
129        }
130
131        self.transactions.remove(&message.transaction_id);
132
133        self.events_queue.push_back(Event {
134            id: message.transaction_id,
135            evt: StunEvent::Message(message),
136        });
137
138        Ok(())
139    }
140
141    /// close terminates all transactions with ErrAgentClosed and renders Agent to
142    /// closed state.
143    fn close(&mut self) -> Result<()> {
144        if self.closed {
145            return Err(Error::ErrAgentClosed);
146        }
147
148        for id in self.transactions.keys() {
149            self.events_queue.push_back(Event {
150                id: *id,
151                evt: StunEvent::AgentClosed,
152            });
153        }
154        self.transactions.clear();
155        self.closed = true;
156
157        Ok(())
158    }
159
160    /// start registers transaction with provided id and deadline.
161    /// Could return ErrAgentClosed, ErrTransactionExists.
162    ///
163    /// Agent handler is guaranteed to be eventually called.
164    fn start(&mut self, id: TransactionId, deadline: Instant) -> Result<()> {
165        if self.closed {
166            return Err(Error::ErrAgentClosed);
167        }
168        if self.transactions.contains_key(&id) {
169            return Err(Error::ErrTransactionExists);
170        }
171
172        self.transactions
173            .insert(id, AgentTransaction { id, deadline });
174
175        Ok(())
176    }
177
178    /// stop stops transaction by id with ErrTransactionStopped, blocking
179    /// until handler returns.
180    fn stop(&mut self, id: TransactionId) -> Result<()> {
181        if self.closed {
182            return Err(Error::ErrAgentClosed);
183        }
184
185        let v = self.transactions.remove(&id);
186        if let Some(t) = v {
187            self.events_queue.push_back(Event {
188                id: t.id,
189                evt: StunEvent::TransactionStopped,
190            });
191            Ok(())
192        } else {
193            Err(Error::ErrTransactionNotExists)
194        }
195    }
196
197    /// collect terminates all transactions that have deadline before provided
198    /// time, blocking until all handlers will process ErrTransactionTimeOut.
199    /// Will return ErrAgentClosed if agent is already closed.
200    ///
201    /// It is safe to call Collect concurrently but makes no sense.
202    fn collect(&mut self, deadline: Instant) -> Result<()> {
203        if self.closed {
204            // Doing nothing if agent is closed.
205            // All transactions should be already closed
206            // during Close() call.
207            return Err(Error::ErrAgentClosed);
208        }
209
210        let mut to_remove: Vec<TransactionId> = Vec::with_capacity(AGENT_COLLECT_CAP);
211
212        // Adding all transactions with deadline before gc_time
213        // to toCall and to_remove slices.
214        // No allocs if there are less than AGENT_COLLECT_CAP
215        // timed out transactions.
216        for (id, t) in &self.transactions {
217            if t.deadline < deadline {
218                to_remove.push(*id);
219            }
220        }
221        // Un-registering timed out transactions.
222        for id in &to_remove {
223            self.transactions.remove(id);
224        }
225
226        for id in to_remove {
227            self.events_queue.push_back(Event {
228                id,
229                evt: StunEvent::TransactionTimeOut,
230            });
231        }
232
233        Ok(())
234    }
235}