Skip to main content

rtsp_runtime/
io.rs

1//! Async real-socket IO adapter over the sans-IO engine — RFC 2326 transport.
2//!
3//! The sans-IO [`ClientSession`] / [`ServerSession`] engines never touch a
4//! socket: they turn method calls into request/response *bytes* and consume
5//! inbound *bytes* into typed events. This module is the thin layer that
6//! actually moves those bytes over a [`tokio`] socket — it owns the stream,
7//! writes what the session produces, reads the peer's reply (buffering partial
8//! reads until a full RTSP message or interleaved `$`-frame parses, per §10.12),
9//! feeds it back through [`ClientSession::handle_data`] /
10//! [`ServerSession::handle_request`], and returns the resulting events. The
11//! engine stays pure; the adapter is pure plumbing.
12//!
13//! Both the client and server are generic over the stream type
14//! (`S: AsyncRead + AsyncWrite + Unpin`), so the identical driver logic runs over
15//! a plain [`tokio::net::TcpStream`] and over a TLS stream.
16//!
17//! # `rtsp://` vs `rtsps://`
18//!
19//! Plain RTSP (`rtsp://`) is carried over TCP on default port **554**
20//! ([`RTSP_DEFAULT_PORT`]). RTSP-over-TLS (`rtsps://`) wraps the TCP stream in a
21//! TLS session *before* any RTSP is exchanged and uses default port **322**
22//! ([`RTSPS_DEFAULT_PORT`], per the IANA `rtsps` assignment). The TLS entry
23//! points ([`AsyncRtspClient::connect_tls`], [`AsyncRtspServer::accept_tls`]) are
24//! gated behind the `tls` feature; everything else is behind `tokio`.
25
26use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
27use tokio::net::TcpStream;
28
29use rtsp_types::Message;
30
31use crate::client::{ClientEvent, ClientSession};
32use crate::error::{Error, Result};
33use crate::interleaved::MAGIC;
34use crate::server::{ServerEvent, ServerSession};
35use crate::transport::Transport;
36
37/// A message body type: owned bytes.
38type Body = Vec<u8>;
39
40/// Default TCP port for `rtsp://` (RFC 2326 §1 / IANA `rtsp`).
41pub const RTSP_DEFAULT_PORT: u16 = 554;
42
43/// Default TCP port for `rtsps://` (RTSP over TLS; IANA `rtsps`).
44pub const RTSPS_DEFAULT_PORT: u16 = 322;
45
46/// Size of one socket read chunk. Reads accumulate into an internal buffer, so
47/// this only bounds a single `read` syscall, not a message.
48const READ_CHUNK: usize = 8192;
49
50/// Maps a tokio IO error into the crate error type.
51fn io_err(context: &str, e: std::io::Error) -> Error {
52    Error::Io(format!("{context}: {e}"))
53}
54
55// ===========================================================================
56// Client
57// ===========================================================================
58
59/// An async RTSP client that owns a socket and drives a [`ClientSession`].
60///
61/// Each request method (`options`/`describe`/`setup`/`play`/`pause`/`teardown`)
62/// writes the request bytes the session produces, reads the response off the
63/// socket, feeds it through the sans-IO engine, and returns the resulting
64/// [`ClientEvent`]s. A Digest `401` is answered transparently: when the engine
65/// emits [`ClientEvent::AuthRetry`], the adapter writes the retried request and
66/// reads its response before returning, so the caller only sees the final
67/// [`ClientEvent::Response`].
68///
69/// Interleaved media (`$`-framed RTP/RTCP, §10.12) is pulled with
70/// [`recv_interleaved`](Self::recv_interleaved).
71#[derive(Debug)]
72pub struct AsyncRtspClient<S> {
73    stream: S,
74    session: ClientSession,
75    /// Bytes read from the socket but not yet fully parsed (partial message or
76    /// `$`-frame tail), plus any already-decoded events not yet drained.
77    read_buf: Vec<u8>,
78    /// Media events surfaced while awaiting a response (e.g. interleaved frames
79    /// arriving between control messages), buffered for `recv_interleaved`.
80    pending_media: std::collections::VecDeque<ClientEvent>,
81}
82
83impl AsyncRtspClient<TcpStream> {
84    /// Connects a plain-TCP (`rtsp://`) client to `addr`.
85    ///
86    /// `addr` is any [`tokio::net::ToSocketAddrs`]; for the RTSP default port use
87    /// `(host, RTSP_DEFAULT_PORT)`.
88    pub async fn connect<A: tokio::net::ToSocketAddrs>(addr: A) -> Result<Self> {
89        let stream = TcpStream::connect(addr)
90            .await
91            .map_err(|e| io_err("connect", e))?;
92        Ok(Self::with_stream(stream, ClientSession::new()))
93    }
94
95    /// Connects a plain-TCP client to `addr` using a pre-configured session
96    /// (e.g. one carrying [`Credentials`](crate::Credentials)).
97    pub async fn connect_with<A: tokio::net::ToSocketAddrs>(
98        addr: A,
99        session: ClientSession,
100    ) -> Result<Self> {
101        let stream = TcpStream::connect(addr)
102            .await
103            .map_err(|e| io_err("connect", e))?;
104        Ok(Self::with_stream(stream, session))
105    }
106}
107
108impl<S> AsyncRtspClient<S>
109where
110    S: AsyncRead + AsyncWrite + Unpin,
111{
112    /// Wraps an already-connected stream (plain or TLS) and a session.
113    pub fn with_stream(stream: S, session: ClientSession) -> Self {
114        AsyncRtspClient {
115            stream,
116            session,
117            read_buf: Vec::new(),
118            pending_media: std::collections::VecDeque::new(),
119        }
120    }
121
122    /// The current session state.
123    pub fn state(&self) -> crate::SessionState {
124        self.session.state()
125    }
126
127    /// The negotiated session id, once a SETUP response has been processed.
128    pub fn session_id(&self) -> Option<&str> {
129        self.session.session_id()
130    }
131
132    /// Borrows the underlying sans-IO session (read-only inspection).
133    pub fn session(&self) -> &ClientSession {
134        &self.session
135    }
136
137    /// Sends `OPTIONS` and awaits the response.
138    pub async fn options(&mut self, uri: &str) -> Result<ClientEvent> {
139        let bytes = self.session.options(uri)?;
140        self.exchange(bytes).await
141    }
142
143    /// Sends `DESCRIBE` (with `Accept: application/sdp`) and awaits the response.
144    pub async fn describe(&mut self, uri: &str) -> Result<ClientEvent> {
145        let bytes = self.session.describe(uri)?;
146        self.exchange(bytes).await
147    }
148
149    /// Sends `SETUP` carrying `transport` and awaits the response.
150    pub async fn setup(&mut self, uri: &str, transport: &Transport) -> Result<ClientEvent> {
151        let bytes = self.session.setup(uri, transport)?;
152        self.exchange(bytes).await
153    }
154
155    /// Sends `PLAY` and awaits the response.
156    pub async fn play(&mut self, uri: &str) -> Result<ClientEvent> {
157        let bytes = self.session.play(uri)?;
158        self.exchange(bytes).await
159    }
160
161    /// Sends `PAUSE` and awaits the response.
162    pub async fn pause(&mut self, uri: &str) -> Result<ClientEvent> {
163        let bytes = self.session.pause(uri)?;
164        self.exchange(bytes).await
165    }
166
167    /// Sends `TEARDOWN` and awaits the response.
168    pub async fn teardown(&mut self, uri: &str) -> Result<ClientEvent> {
169        let bytes = self.session.teardown(uri)?;
170        self.exchange(bytes).await
171    }
172
173    /// Sends `GET_PARAMETER` (empty body = liveness ping) and awaits the response.
174    pub async fn get_parameter(&mut self, uri: &str, body: &[u8]) -> Result<ClientEvent> {
175        let bytes = self.session.get_parameter(uri, body)?;
176        self.exchange(bytes).await
177    }
178
179    /// Writes an outbound request and reads until the correlated response
180    /// arrives, transparently completing any Digest `AuthRetry` round-trip.
181    ///
182    /// Interleaved media frames that arrive before the response are buffered and
183    /// later returned by [`recv_interleaved`](Self::recv_interleaved).
184    async fn exchange(&mut self, request: Vec<u8>) -> Result<ClientEvent> {
185        self.stream
186            .write_all(&request)
187            .await
188            .map_err(|e| io_err("write request", e))?;
189        self.stream.flush().await.map_err(|e| io_err("flush", e))?;
190
191        loop {
192            // Drain any events already decoded from buffered bytes first.
193            let events = self
194                .session
195                .handle_data(&std::mem::take(&mut self.read_buf))?;
196            let mut response = None;
197            for event in events {
198                match event {
199                    // Hold the response until every event decoded from this same
200                    // read has been processed: interleaved media frames can arrive
201                    // coalesced *after* the response in one TCP segment, and must be
202                    // buffered rather than dropped by an early return (§10.12).
203                    ClientEvent::Response { .. } => response = Some(event),
204                    ClientEvent::AuthRetry { ref request, .. } => {
205                        // Write the retried (now-authenticated) request and keep
206                        // reading for its response.
207                        let retry = request.clone();
208                        self.stream
209                            .write_all(&retry)
210                            .await
211                            .map_err(|e| io_err("write auth retry", e))?;
212                        self.stream.flush().await.map_err(|e| io_err("flush", e))?;
213                    }
214                    ClientEvent::MediaData { .. } => self.pending_media.push_back(event),
215                }
216            }
217            if let Some(response) = response {
218                return Ok(response);
219            }
220            // Need more bytes from the socket.
221            self.fill_from_socket().await?;
222        }
223    }
224
225    /// Receives the next interleaved media frame ([`ClientEvent::MediaData`]),
226    /// driving the socket until one is available (§10.12).
227    ///
228    /// Returns `Ok(None)` if the peer closes the connection cleanly before a
229    /// frame arrives. Any control responses interleaved with media are consumed
230    /// and their state transitions applied, but not returned here.
231    pub async fn recv_interleaved(&mut self) -> Result<Option<ClientEvent>> {
232        loop {
233            if let Some(event) = self.pending_media.pop_front() {
234                return Ok(Some(event));
235            }
236            let events = self
237                .session
238                .handle_data(&std::mem::take(&mut self.read_buf))?;
239            for event in events {
240                if matches!(event, ClientEvent::MediaData { .. }) {
241                    self.pending_media.push_back(event);
242                }
243            }
244            if let Some(event) = self.pending_media.pop_front() {
245                return Ok(Some(event));
246            }
247            // No frame decoded yet; read more, treating clean EOF as end-of-stream.
248            let n = self.read_once().await?;
249            if n == 0 {
250                return Ok(None);
251            }
252        }
253    }
254
255    /// Reads one chunk from the socket into `read_buf`, erroring on EOF (used
256    /// where a response is *required*).
257    async fn fill_from_socket(&mut self) -> Result<()> {
258        let n = self.read_once().await?;
259        if n == 0 {
260            return Err(Error::Io("peer closed connection before response".into()));
261        }
262        Ok(())
263    }
264
265    /// Reads one chunk from the socket into `read_buf`; returns the byte count
266    /// (`0` = clean EOF).
267    async fn read_once(&mut self) -> Result<usize> {
268        let mut chunk = [0u8; READ_CHUNK];
269        let n = self
270            .stream
271            .read(&mut chunk)
272            .await
273            .map_err(|e| io_err("read", e))?;
274        self.read_buf.extend_from_slice(&chunk[..n]);
275        Ok(n)
276    }
277}
278
279#[cfg(feature = "tls")]
280impl AsyncRtspClient<tokio_rustls::client::TlsStream<TcpStream>> {
281    /// Connects an `rtsps://` (TLS) client to `addr`, verifying the server
282    /// against the given `config` and presenting `server_name` for SNI/cert
283    /// validation.
284    ///
285    /// For the public-CA default trust store, build `config` with
286    /// [`default_tls_client_config`]. For a self-signed camera cert, build a
287    /// [`rustls::ClientConfig`] whose root store contains that cert. For the
288    /// `rtsps` default port use `(host, RTSPS_DEFAULT_PORT)`.
289    pub async fn connect_tls<A: tokio::net::ToSocketAddrs>(
290        addr: A,
291        server_name: &str,
292        config: rustls::ClientConfig,
293    ) -> Result<Self> {
294        Self::connect_tls_with(addr, server_name, config, ClientSession::new()).await
295    }
296
297    /// Connects an `rtsps://` (TLS) client to `addr` using a pre-configured
298    /// session (e.g. one carrying [`Credentials`](crate::Credentials) via
299    /// [`ClientSession::with_credentials`]), otherwise identical to
300    /// [`connect_tls`](Self::connect_tls).
301    pub async fn connect_tls_with<A: tokio::net::ToSocketAddrs>(
302        addr: A,
303        server_name: &str,
304        config: rustls::ClientConfig,
305        session: ClientSession,
306    ) -> Result<Self> {
307        use std::sync::Arc;
308        use tokio_rustls::TlsConnector;
309
310        let tcp = TcpStream::connect(addr)
311            .await
312            .map_err(|e| io_err("connect", e))?;
313        let connector = TlsConnector::from(Arc::new(config));
314        let dns = rustls::pki_types::ServerName::try_from(server_name.to_string())
315            .map_err(|e| Error::Tls(format!("invalid server name {server_name:?}: {e}")))?;
316        let stream = connector
317            .connect(dns, tcp)
318            .await
319            .map_err(|e| io_err("TLS handshake", e))?;
320        Ok(Self::with_stream(stream, session))
321    }
322}
323
324/// Builds a [`rustls::ClientConfig`] trusting the `webpki-roots` public-CA
325/// bundle (the default trust store for `rtsps://` to a well-known server).
326///
327/// For a self-signed camera cert, construct the config directly with a root
328/// store containing that cert and pass it to
329/// [`AsyncRtspClient::connect_tls`].
330#[cfg(feature = "tls")]
331pub fn default_tls_client_config() -> rustls::ClientConfig {
332    let mut roots = rustls::RootCertStore::empty();
333    roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
334    // Select the aws-lc-rs provider explicitly rather than via
335    // `ClientConfig::builder()`'s process-global default: another crate in the
336    // same build (e.g. a `reqwest` that pulls `aws-lc-rs`) can put a *second*
337    // `CryptoProvider` in the tree, leaving no unambiguous default and making
338    // the plain builder panic. Choosing the provider here keeps `rtsp-runtime`
339    // working regardless of what else is linked.
340    rustls::ClientConfig::builder_with_provider(std::sync::Arc::new(
341        rustls::crypto::aws_lc_rs::default_provider(),
342    ))
343    .with_safe_default_protocol_versions()
344    .expect("aws-lc-rs provider supports the safe default protocol versions")
345    .with_root_certificates(roots)
346    .with_no_client_auth()
347}
348
349// ===========================================================================
350// Server
351// ===========================================================================
352
353/// An async RTSP server connection that owns a socket and drives a
354/// [`ServerSession`].
355///
356/// Reads requests off the socket (buffering partial reads until a full RTSP
357/// message parses), calls [`ServerSession::handle_request`], writes the response
358/// bytes back, and returns the [`ServerEvent`]s.
359#[derive(Debug)]
360pub struct AsyncRtspServer<S> {
361    stream: S,
362    session: ServerSession,
363    read_buf: Vec<u8>,
364}
365
366impl AsyncRtspServer<TcpStream> {
367    /// Wraps an accepted plain-TCP connection with a fresh [`ServerSession`].
368    pub fn accept(stream: TcpStream) -> Self {
369        Self::with_stream(stream, ServerSession::new())
370    }
371
372    /// Wraps an accepted plain-TCP connection with a pre-configured session.
373    pub fn accept_with(stream: TcpStream, session: ServerSession) -> Self {
374        Self::with_stream(stream, session)
375    }
376}
377
378#[cfg(feature = "tls")]
379impl AsyncRtspServer<tokio_rustls::server::TlsStream<TcpStream>> {
380    /// Performs the TLS handshake over an accepted TCP connection (an
381    /// `rtsps://` server), then wraps the TLS stream with a fresh session.
382    pub async fn accept_tls(stream: TcpStream, config: rustls::ServerConfig) -> Result<Self> {
383        use std::sync::Arc;
384        use tokio_rustls::TlsAcceptor;
385
386        let acceptor = TlsAcceptor::from(Arc::new(config));
387        let tls = acceptor
388            .accept(stream)
389            .await
390            .map_err(|e| io_err("TLS handshake", e))?;
391        Ok(Self::with_stream(tls, ServerSession::new()))
392    }
393}
394
395impl<S> AsyncRtspServer<S>
396where
397    S: AsyncRead + AsyncWrite + Unpin,
398{
399    /// Wraps an already-connected stream (plain or TLS) and a session.
400    pub fn with_stream(stream: S, session: ServerSession) -> Self {
401        AsyncRtspServer {
402            stream,
403            session,
404            read_buf: Vec::new(),
405        }
406    }
407
408    /// The current session state.
409    pub fn state(&self) -> crate::SessionState {
410        self.session.state()
411    }
412
413    /// The allocated session id, once a SETUP has been handled.
414    pub fn session_id(&self) -> Option<&str> {
415        self.session.session_id()
416    }
417
418    /// Mutable access to the underlying stream, for writing raw bytes (e.g.
419    /// deliberately fragmenting an interleaved frame, or sending several frames
420    /// back-to-back) alongside the framed [`send_interleaved`](Self::send_interleaved)
421    /// helper.
422    pub fn stream_mut(&mut self) -> &mut S {
423        &mut self.stream
424    }
425
426    /// Reads the next complete request, handles it (writing the response back),
427    /// and returns the produced events.
428    ///
429    /// Returns `Ok(None)` when the peer closes the connection cleanly before a
430    /// full request arrives.
431    pub async fn next_request(&mut self) -> Result<Option<Vec<ServerEvent>>> {
432        loop {
433            // Do we already hold a complete request in the buffer?
434            if let Some(consumed) = complete_request_len(&self.read_buf)? {
435                let request: Vec<u8> = self.read_buf.drain(..consumed).collect();
436                let (response, events) = self.session.handle_request(&request)?;
437                self.stream
438                    .write_all(&response)
439                    .await
440                    .map_err(|e| io_err("write response", e))?;
441                self.stream.flush().await.map_err(|e| io_err("flush", e))?;
442                return Ok(Some(events));
443            }
444            // Need more bytes.
445            let mut chunk = [0u8; READ_CHUNK];
446            let n = self
447                .stream
448                .read(&mut chunk)
449                .await
450                .map_err(|e| io_err("read", e))?;
451            if n == 0 {
452                if self.read_buf.is_empty() {
453                    return Ok(None);
454                }
455                return Err(Error::Io("peer closed connection mid-request".into()));
456            }
457            self.read_buf.extend_from_slice(&chunk[..n]);
458        }
459    }
460
461    /// Sends an interleaved (`$`-framed) media frame to the client on `channel`
462    /// (§10.12), e.g. an RTP or RTCP packet during PLAY.
463    pub async fn send_interleaved(&mut self, channel: u8, payload: &[u8]) -> Result<()> {
464        let frame = crate::interleaved::InterleavedFrame::new(channel, payload.to_vec());
465        let bytes = frame.to_bytes()?;
466        self.stream
467            .write_all(&bytes)
468            .await
469            .map_err(|e| io_err("write interleaved frame", e))?;
470        self.stream.flush().await.map_err(|e| io_err("flush", e))?;
471        Ok(())
472    }
473}
474
475/// Returns the byte length of a complete RTSP request at the front of `buf`, or
476/// `None` if more bytes are needed. Errors on a malformed message.
477///
478/// A leading `$` (interleaved frame) is not a request; this returns an error so
479/// the caller does not silently spin.
480fn complete_request_len(buf: &[u8]) -> Result<Option<usize>> {
481    if buf.is_empty() {
482        return Ok(None);
483    }
484    if buf[0] == MAGIC {
485        return Err(Error::MessageParse(
486            "interleaved '$' frame received where a request was expected".into(),
487        ));
488    }
489    match Message::<Body>::parse(buf) {
490        Ok((_, consumed)) => Ok(Some(consumed)),
491        Err(rtsp_types::ParseError::Incomplete(_)) => Ok(None),
492        Err(rtsp_types::ParseError::Error) => {
493            Err(Error::MessageParse("malformed RTSP request".into()))
494        }
495    }
496}