Skip to main content

rtc_stun/
client.rs

1//! A Sans-I/O STUN client.
2//!
3//! Sends Binding requests and matches the responses, applying the retransmission schedule the RFC
4//! specifies (an initial RTO, doubling per retry) so a lost request on a UDP path is retried
5//! rather than lost. Build one with [`ClientBuilder`](crate::client::ClientBuilder); drive it with datagrams and time.
6use bytes::BytesMut;
7use shared::error::*;
8use std::collections::{HashMap, VecDeque};
9use std::io::BufReader;
10use std::net::SocketAddr;
11use std::ops::Add;
12use std::time::{Duration, Instant};
13
14use crate::agent::*;
15use crate::message::*;
16use shared::{TaggedBytesMut, TransportContext, TransportMessage, TransportProtocol};
17
18const DEFAULT_TIMEOUT_RATE: Duration = Duration::from_millis(5);
19const DEFAULT_RTO: Duration = Duration::from_millis(300);
20const DEFAULT_MAX_ATTEMPTS: u32 = 7;
21const DEFAULT_MAX_BUFFER_SIZE: usize = 8;
22
23/// ClientTransaction represents transaction in progress.
24/// If transaction is succeed or failed, f will be called
25/// provided by event.
26/// Concurrent access is invalid.
27#[derive(Debug, Clone)]
28pub struct ClientTransaction {
29    id: TransactionId,
30    attempt: u32,
31    start: Instant,
32    rto: Duration,
33    raw: Vec<u8>,
34}
35
36impl ClientTransaction {
37    pub(crate) fn next_timeout(&self, now: Instant) -> Instant {
38        now.add((self.attempt + 1) * self.rto)
39    }
40}
41
42struct ClientSettings {
43    buffer_size: usize,
44    rto: Duration,
45    rto_rate: Duration,
46    max_attempts: u32,
47    closed: bool,
48}
49
50impl Default for ClientSettings {
51    fn default() -> Self {
52        ClientSettings {
53            buffer_size: DEFAULT_MAX_BUFFER_SIZE,
54            rto: DEFAULT_RTO,
55            rto_rate: DEFAULT_TIMEOUT_RATE,
56            max_attempts: DEFAULT_MAX_ATTEMPTS,
57            closed: false,
58        }
59    }
60}
61
62#[derive(Default)]
63/// Builds a [`Client`] with a chosen transaction timeout, retransmission schedule and
64/// handler.
65pub struct ClientBuilder {
66    settings: ClientSettings,
67}
68
69impl ClientBuilder {
70    /// with_rto sets client RTO as defined in STUN RFC.
71    pub fn with_rto(mut self, rto: Duration) -> Self {
72        self.settings.rto = rto;
73        self
74    }
75
76    /// with_timeout_rate sets RTO timer minimum resolution.
77    pub fn with_timeout_rate(mut self, d: Duration) -> Self {
78        self.settings.rto_rate = d;
79        self
80    }
81
82    /// with_buffer_size sets buffer size.
83    pub fn with_buffer_size(mut self, buffer_size: usize) -> Self {
84        self.settings.buffer_size = buffer_size;
85        self
86    }
87
88    /// with_no_retransmit disables retransmissions and sets RTO to
89    /// DEFAULT_MAX_ATTEMPTS * DEFAULT_RTO which will be effectively time out
90    /// if not set.
91    /// Useful for TCP connections where transport handles RTO.
92    pub fn with_no_retransmit(mut self) -> Self {
93        self.settings.max_attempts = 0;
94        if self.settings.rto == Duration::from_secs(0) {
95            self.settings.rto = DEFAULT_MAX_ATTEMPTS * DEFAULT_RTO;
96        }
97        self
98    }
99
100    /// A builder with the RFC's default timings.
101    pub fn new() -> Self {
102        ClientBuilder {
103            settings: ClientSettings::default(),
104        }
105    }
106
107    /// Builds the client for the given local and remote addresses.
108    ///
109    /// # Errors
110    ///
111    /// Fails if the configured timings are inconsistent.
112    pub fn build(
113        self,
114        local: SocketAddr,
115        remote: SocketAddr,
116        protocol: TransportProtocol,
117    ) -> Result<Client> {
118        Ok(Client::new(local, remote, protocol, self.settings))
119    }
120}
121
122/// Client simulates "connection" to STUN server.
123pub struct Client {
124    local: SocketAddr,
125    remote: SocketAddr,
126    transport_protocol: TransportProtocol,
127    agent: Agent,
128    settings: ClientSettings,
129    transactions: HashMap<TransactionId, ClientTransaction>,
130    transmits: VecDeque<TransportMessage<BytesMut>>,
131}
132
133impl Client {
134    fn new(
135        local: SocketAddr,
136        remote: SocketAddr,
137        transport_protocol: TransportProtocol,
138        settings: ClientSettings,
139    ) -> Self {
140        Self {
141            local,
142            remote,
143            transport_protocol,
144            agent: Agent::new(),
145            settings,
146            transactions: HashMap::new(),
147            transmits: VecDeque::new(),
148        }
149    }
150
151    /// The address this client sends from.
152    pub fn local_addr(&self) -> SocketAddr {
153        self.local
154    }
155
156    /// The STUN server this client talks to.
157    pub fn peer_addr(&self) -> SocketAddr {
158        self.remote
159    }
160}
161
162impl sansio::Protocol<TaggedBytesMut, Message, ()> for Client {
163    type Rout = ();
164    type Wout = TaggedBytesMut;
165    type Eout = StunEvent;
166    type Error = Error;
167    type Time = Instant;
168
169    fn handle_read(&mut self, msg: TaggedBytesMut) -> Result<()> {
170        let mut stun_msg = Message::new();
171        let mut reader = BufReader::new(&msg.message[..]);
172        stun_msg.read_from(&mut reader)?;
173        self.agent.handle_event(ClientAgent::Process(stun_msg))
174    }
175
176    fn poll_read(&mut self) -> Option<Self::Rout> {
177        None
178    }
179
180    fn handle_write(&mut self, m: Message) -> Result<()> {
181        if self.settings.closed {
182            return Err(Error::ErrClientClosed);
183        }
184
185        let payload = BytesMut::from(&m.raw[..]);
186
187        let ct = ClientTransaction {
188            id: m.transaction_id,
189            attempt: 0,
190            start: Instant::now(),
191            rto: self.settings.rto,
192            raw: m.raw,
193        };
194        let deadline = ct.next_timeout(ct.start);
195        self.transactions.entry(ct.id).or_insert(ct);
196        self.agent
197            .handle_event(ClientAgent::Start(m.transaction_id, deadline))?;
198
199        self.transmits.push_back(TransportMessage {
200            now: Instant::now(),
201            transport: TransportContext {
202                local_addr: self.local,
203                peer_addr: self.remote,
204                ecn: None,
205                transport_protocol: self.transport_protocol,
206            },
207            message: payload,
208        });
209
210        Ok(())
211    }
212
213    /// Returns packets to transmit
214    ///
215    /// It should be polled for transmit after:
216    /// - the application performed some I/O
217    /// - a call was made to `handle_read`
218    /// - a call was made to `handle_write`
219    /// - a call was made to `handle_timeout`
220    fn poll_write(&mut self) -> Option<Self::Wout> {
221        self.transmits.pop_front()
222    }
223
224    fn poll_event(&mut self) -> Option<Self::Eout> {
225        while let Some(event) = self.agent.poll_event() {
226            let mut ct = if self.transactions.contains_key(&event.id) {
227                self.transactions.remove(&event.id).unwrap()
228            } else {
229                continue;
230            };
231
232            if let StunEvent::Message(_) = &event.evt {
233                return Some(event.evt);
234            }
235            if ct.attempt >= self.settings.max_attempts {
236                return Some(event.evt);
237            }
238
239            // Doing re-transmission.
240            ct.attempt += 1;
241
242            let payload = BytesMut::from(&ct.raw[..]);
243            let timeout = ct.next_timeout(Instant::now());
244            let id = ct.id;
245
246            // Starting client transaction.
247            self.transactions.entry(ct.id).or_insert(ct);
248
249            // Starting agent transaction.
250            if self
251                .agent
252                .handle_event(ClientAgent::Start(id, timeout))
253                .is_err()
254            {
255                self.transactions.remove(&id);
256                return Some(event.evt);
257            }
258
259            // Writing message to connection again.
260            self.transmits.push_back(TransportMessage {
261                now: Instant::now(),
262                transport: TransportContext {
263                    local_addr: self.local,
264                    peer_addr: self.remote,
265                    ecn: None,
266                    transport_protocol: self.transport_protocol,
267                },
268                message: payload,
269            });
270        }
271
272        None
273    }
274
275    fn poll_timeout(&mut self) -> Option<Self::Time> {
276        self.agent.poll_timeout()
277    }
278
279    fn handle_timeout(&mut self, now: Instant) -> Result<()> {
280        self.agent.handle_event(ClientAgent::Collect(now))
281    }
282
283    fn close(&mut self) -> Result<()> {
284        if self.settings.closed {
285            return Err(Error::ErrClientClosed);
286        }
287        self.settings.closed = true;
288        self.agent.handle_event(ClientAgent::Close)
289    }
290}