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}