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            for event in events {
197                match event {
198                    ClientEvent::Response { .. } => return Ok(event),
199                    ClientEvent::AuthRetry { ref request, .. } => {
200                        // Write the retried (now-authenticated) request and keep
201                        // reading for its response.
202                        let retry = request.clone();
203                        self.stream
204                            .write_all(&retry)
205                            .await
206                            .map_err(|e| io_err("write auth retry", e))?;
207                        self.stream.flush().await.map_err(|e| io_err("flush", e))?;
208                    }
209                    ClientEvent::MediaData { .. } => self.pending_media.push_back(event),
210                }
211            }
212            // Need more bytes from the socket.
213            self.fill_from_socket().await?;
214        }
215    }
216
217    /// Receives the next interleaved media frame ([`ClientEvent::MediaData`]),
218    /// driving the socket until one is available (§10.12).
219    ///
220    /// Returns `Ok(None)` if the peer closes the connection cleanly before a
221    /// frame arrives. Any control responses interleaved with media are consumed
222    /// and their state transitions applied, but not returned here.
223    pub async fn recv_interleaved(&mut self) -> Result<Option<ClientEvent>> {
224        loop {
225            if let Some(event) = self.pending_media.pop_front() {
226                return Ok(Some(event));
227            }
228            let events = self
229                .session
230                .handle_data(&std::mem::take(&mut self.read_buf))?;
231            for event in events {
232                if matches!(event, ClientEvent::MediaData { .. }) {
233                    self.pending_media.push_back(event);
234                }
235            }
236            if let Some(event) = self.pending_media.pop_front() {
237                return Ok(Some(event));
238            }
239            // No frame decoded yet; read more, treating clean EOF as end-of-stream.
240            let n = self.read_once().await?;
241            if n == 0 {
242                return Ok(None);
243            }
244        }
245    }
246
247    /// Reads one chunk from the socket into `read_buf`, erroring on EOF (used
248    /// where a response is *required*).
249    async fn fill_from_socket(&mut self) -> Result<()> {
250        let n = self.read_once().await?;
251        if n == 0 {
252            return Err(Error::Io("peer closed connection before response".into()));
253        }
254        Ok(())
255    }
256
257    /// Reads one chunk from the socket into `read_buf`; returns the byte count
258    /// (`0` = clean EOF).
259    async fn read_once(&mut self) -> Result<usize> {
260        let mut chunk = [0u8; READ_CHUNK];
261        let n = self
262            .stream
263            .read(&mut chunk)
264            .await
265            .map_err(|e| io_err("read", e))?;
266        self.read_buf.extend_from_slice(&chunk[..n]);
267        Ok(n)
268    }
269}
270
271#[cfg(feature = "tls")]
272impl AsyncRtspClient<tokio_rustls::client::TlsStream<TcpStream>> {
273    /// Connects an `rtsps://` (TLS) client to `addr`, verifying the server
274    /// against the given `config` and presenting `server_name` for SNI/cert
275    /// validation.
276    ///
277    /// For the public-CA default trust store, build `config` with
278    /// [`default_tls_client_config`]. For a self-signed camera cert, build a
279    /// [`rustls::ClientConfig`] whose root store contains that cert. For the
280    /// `rtsps` default port use `(host, RTSPS_DEFAULT_PORT)`.
281    pub async fn connect_tls<A: tokio::net::ToSocketAddrs>(
282        addr: A,
283        server_name: &str,
284        config: rustls::ClientConfig,
285    ) -> Result<Self> {
286        use std::sync::Arc;
287        use tokio_rustls::TlsConnector;
288
289        let tcp = TcpStream::connect(addr)
290            .await
291            .map_err(|e| io_err("connect", e))?;
292        let connector = TlsConnector::from(Arc::new(config));
293        let dns = rustls::pki_types::ServerName::try_from(server_name.to_string())
294            .map_err(|e| Error::Tls(format!("invalid server name {server_name:?}: {e}")))?;
295        let stream = connector
296            .connect(dns, tcp)
297            .await
298            .map_err(|e| io_err("TLS handshake", e))?;
299        Ok(Self::with_stream(stream, ClientSession::new()))
300    }
301}
302
303/// Builds a [`rustls::ClientConfig`] trusting the `webpki-roots` public-CA
304/// bundle (the default trust store for `rtsps://` to a well-known server).
305///
306/// For a self-signed camera cert, construct the config directly with a root
307/// store containing that cert and pass it to
308/// [`AsyncRtspClient::connect_tls`].
309#[cfg(feature = "tls")]
310pub fn default_tls_client_config() -> rustls::ClientConfig {
311    let mut roots = rustls::RootCertStore::empty();
312    roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
313    rustls::ClientConfig::builder()
314        .with_root_certificates(roots)
315        .with_no_client_auth()
316}
317
318// ===========================================================================
319// Server
320// ===========================================================================
321
322/// An async RTSP server connection that owns a socket and drives a
323/// [`ServerSession`].
324///
325/// Reads requests off the socket (buffering partial reads until a full RTSP
326/// message parses), calls [`ServerSession::handle_request`], writes the response
327/// bytes back, and returns the [`ServerEvent`]s.
328#[derive(Debug)]
329pub struct AsyncRtspServer<S> {
330    stream: S,
331    session: ServerSession,
332    read_buf: Vec<u8>,
333}
334
335impl AsyncRtspServer<TcpStream> {
336    /// Wraps an accepted plain-TCP connection with a fresh [`ServerSession`].
337    pub fn accept(stream: TcpStream) -> Self {
338        Self::with_stream(stream, ServerSession::new())
339    }
340
341    /// Wraps an accepted plain-TCP connection with a pre-configured session.
342    pub fn accept_with(stream: TcpStream, session: ServerSession) -> Self {
343        Self::with_stream(stream, session)
344    }
345}
346
347#[cfg(feature = "tls")]
348impl AsyncRtspServer<tokio_rustls::server::TlsStream<TcpStream>> {
349    /// Performs the TLS handshake over an accepted TCP connection (an
350    /// `rtsps://` server), then wraps the TLS stream with a fresh session.
351    pub async fn accept_tls(stream: TcpStream, config: rustls::ServerConfig) -> Result<Self> {
352        use std::sync::Arc;
353        use tokio_rustls::TlsAcceptor;
354
355        let acceptor = TlsAcceptor::from(Arc::new(config));
356        let tls = acceptor
357            .accept(stream)
358            .await
359            .map_err(|e| io_err("TLS handshake", e))?;
360        Ok(Self::with_stream(tls, ServerSession::new()))
361    }
362}
363
364impl<S> AsyncRtspServer<S>
365where
366    S: AsyncRead + AsyncWrite + Unpin,
367{
368    /// Wraps an already-connected stream (plain or TLS) and a session.
369    pub fn with_stream(stream: S, session: ServerSession) -> Self {
370        AsyncRtspServer {
371            stream,
372            session,
373            read_buf: Vec::new(),
374        }
375    }
376
377    /// The current session state.
378    pub fn state(&self) -> crate::SessionState {
379        self.session.state()
380    }
381
382    /// The allocated session id, once a SETUP has been handled.
383    pub fn session_id(&self) -> Option<&str> {
384        self.session.session_id()
385    }
386
387    /// Mutable access to the underlying stream, for writing raw bytes (e.g.
388    /// deliberately fragmenting an interleaved frame, or sending several frames
389    /// back-to-back) alongside the framed [`send_interleaved`](Self::send_interleaved)
390    /// helper.
391    pub fn stream_mut(&mut self) -> &mut S {
392        &mut self.stream
393    }
394
395    /// Reads the next complete request, handles it (writing the response back),
396    /// and returns the produced events.
397    ///
398    /// Returns `Ok(None)` when the peer closes the connection cleanly before a
399    /// full request arrives.
400    pub async fn next_request(&mut self) -> Result<Option<Vec<ServerEvent>>> {
401        loop {
402            // Do we already hold a complete request in the buffer?
403            if let Some(consumed) = complete_request_len(&self.read_buf)? {
404                let request: Vec<u8> = self.read_buf.drain(..consumed).collect();
405                let (response, events) = self.session.handle_request(&request)?;
406                self.stream
407                    .write_all(&response)
408                    .await
409                    .map_err(|e| io_err("write response", e))?;
410                self.stream.flush().await.map_err(|e| io_err("flush", e))?;
411                return Ok(Some(events));
412            }
413            // Need more bytes.
414            let mut chunk = [0u8; READ_CHUNK];
415            let n = self
416                .stream
417                .read(&mut chunk)
418                .await
419                .map_err(|e| io_err("read", e))?;
420            if n == 0 {
421                if self.read_buf.is_empty() {
422                    return Ok(None);
423                }
424                return Err(Error::Io("peer closed connection mid-request".into()));
425            }
426            self.read_buf.extend_from_slice(&chunk[..n]);
427        }
428    }
429
430    /// Sends an interleaved (`$`-framed) media frame to the client on `channel`
431    /// (§10.12), e.g. an RTP or RTCP packet during PLAY.
432    pub async fn send_interleaved(&mut self, channel: u8, payload: &[u8]) -> Result<()> {
433        let frame = crate::interleaved::InterleavedFrame::new(channel, payload.to_vec());
434        let bytes = frame.to_bytes()?;
435        self.stream
436            .write_all(&bytes)
437            .await
438            .map_err(|e| io_err("write interleaved frame", e))?;
439        self.stream.flush().await.map_err(|e| io_err("flush", e))?;
440        Ok(())
441    }
442}
443
444/// Returns the byte length of a complete RTSP request at the front of `buf`, or
445/// `None` if more bytes are needed. Errors on a malformed message.
446///
447/// A leading `$` (interleaved frame) is not a request; this returns an error so
448/// the caller does not silently spin.
449fn complete_request_len(buf: &[u8]) -> Result<Option<usize>> {
450    if buf.is_empty() {
451        return Ok(None);
452    }
453    if buf[0] == MAGIC {
454        return Err(Error::MessageParse(
455            "interleaved '$' frame received where a request was expected".into(),
456        ));
457    }
458    match Message::<Body>::parse(buf) {
459        Ok((_, consumed)) => Ok(Some(consumed)),
460        Err(rtsp_types::ParseError::Incomplete(_)) => Ok(None),
461        Err(rtsp_types::ParseError::Error) => {
462            Err(Error::MessageParse("malformed RTSP request".into()))
463        }
464    }
465}