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        use std::sync::Arc;
295        use tokio_rustls::TlsConnector;
296
297        let tcp = TcpStream::connect(addr)
298            .await
299            .map_err(|e| io_err("connect", e))?;
300        let connector = TlsConnector::from(Arc::new(config));
301        let dns = rustls::pki_types::ServerName::try_from(server_name.to_string())
302            .map_err(|e| Error::Tls(format!("invalid server name {server_name:?}: {e}")))?;
303        let stream = connector
304            .connect(dns, tcp)
305            .await
306            .map_err(|e| io_err("TLS handshake", e))?;
307        Ok(Self::with_stream(stream, ClientSession::new()))
308    }
309}
310
311/// Builds a [`rustls::ClientConfig`] trusting the `webpki-roots` public-CA
312/// bundle (the default trust store for `rtsps://` to a well-known server).
313///
314/// For a self-signed camera cert, construct the config directly with a root
315/// store containing that cert and pass it to
316/// [`AsyncRtspClient::connect_tls`].
317#[cfg(feature = "tls")]
318pub fn default_tls_client_config() -> rustls::ClientConfig {
319    let mut roots = rustls::RootCertStore::empty();
320    roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
321    rustls::ClientConfig::builder()
322        .with_root_certificates(roots)
323        .with_no_client_auth()
324}
325
326// ===========================================================================
327// Server
328// ===========================================================================
329
330/// An async RTSP server connection that owns a socket and drives a
331/// [`ServerSession`].
332///
333/// Reads requests off the socket (buffering partial reads until a full RTSP
334/// message parses), calls [`ServerSession::handle_request`], writes the response
335/// bytes back, and returns the [`ServerEvent`]s.
336#[derive(Debug)]
337pub struct AsyncRtspServer<S> {
338    stream: S,
339    session: ServerSession,
340    read_buf: Vec<u8>,
341}
342
343impl AsyncRtspServer<TcpStream> {
344    /// Wraps an accepted plain-TCP connection with a fresh [`ServerSession`].
345    pub fn accept(stream: TcpStream) -> Self {
346        Self::with_stream(stream, ServerSession::new())
347    }
348
349    /// Wraps an accepted plain-TCP connection with a pre-configured session.
350    pub fn accept_with(stream: TcpStream, session: ServerSession) -> Self {
351        Self::with_stream(stream, session)
352    }
353}
354
355#[cfg(feature = "tls")]
356impl AsyncRtspServer<tokio_rustls::server::TlsStream<TcpStream>> {
357    /// Performs the TLS handshake over an accepted TCP connection (an
358    /// `rtsps://` server), then wraps the TLS stream with a fresh session.
359    pub async fn accept_tls(stream: TcpStream, config: rustls::ServerConfig) -> Result<Self> {
360        use std::sync::Arc;
361        use tokio_rustls::TlsAcceptor;
362
363        let acceptor = TlsAcceptor::from(Arc::new(config));
364        let tls = acceptor
365            .accept(stream)
366            .await
367            .map_err(|e| io_err("TLS handshake", e))?;
368        Ok(Self::with_stream(tls, ServerSession::new()))
369    }
370}
371
372impl<S> AsyncRtspServer<S>
373where
374    S: AsyncRead + AsyncWrite + Unpin,
375{
376    /// Wraps an already-connected stream (plain or TLS) and a session.
377    pub fn with_stream(stream: S, session: ServerSession) -> Self {
378        AsyncRtspServer {
379            stream,
380            session,
381            read_buf: Vec::new(),
382        }
383    }
384
385    /// The current session state.
386    pub fn state(&self) -> crate::SessionState {
387        self.session.state()
388    }
389
390    /// The allocated session id, once a SETUP has been handled.
391    pub fn session_id(&self) -> Option<&str> {
392        self.session.session_id()
393    }
394
395    /// Mutable access to the underlying stream, for writing raw bytes (e.g.
396    /// deliberately fragmenting an interleaved frame, or sending several frames
397    /// back-to-back) alongside the framed [`send_interleaved`](Self::send_interleaved)
398    /// helper.
399    pub fn stream_mut(&mut self) -> &mut S {
400        &mut self.stream
401    }
402
403    /// Reads the next complete request, handles it (writing the response back),
404    /// and returns the produced events.
405    ///
406    /// Returns `Ok(None)` when the peer closes the connection cleanly before a
407    /// full request arrives.
408    pub async fn next_request(&mut self) -> Result<Option<Vec<ServerEvent>>> {
409        loop {
410            // Do we already hold a complete request in the buffer?
411            if let Some(consumed) = complete_request_len(&self.read_buf)? {
412                let request: Vec<u8> = self.read_buf.drain(..consumed).collect();
413                let (response, events) = self.session.handle_request(&request)?;
414                self.stream
415                    .write_all(&response)
416                    .await
417                    .map_err(|e| io_err("write response", e))?;
418                self.stream.flush().await.map_err(|e| io_err("flush", e))?;
419                return Ok(Some(events));
420            }
421            // Need more bytes.
422            let mut chunk = [0u8; READ_CHUNK];
423            let n = self
424                .stream
425                .read(&mut chunk)
426                .await
427                .map_err(|e| io_err("read", e))?;
428            if n == 0 {
429                if self.read_buf.is_empty() {
430                    return Ok(None);
431                }
432                return Err(Error::Io("peer closed connection mid-request".into()));
433            }
434            self.read_buf.extend_from_slice(&chunk[..n]);
435        }
436    }
437
438    /// Sends an interleaved (`$`-framed) media frame to the client on `channel`
439    /// (§10.12), e.g. an RTP or RTCP packet during PLAY.
440    pub async fn send_interleaved(&mut self, channel: u8, payload: &[u8]) -> Result<()> {
441        let frame = crate::interleaved::InterleavedFrame::new(channel, payload.to_vec());
442        let bytes = frame.to_bytes()?;
443        self.stream
444            .write_all(&bytes)
445            .await
446            .map_err(|e| io_err("write interleaved frame", e))?;
447        self.stream.flush().await.map_err(|e| io_err("flush", e))?;
448        Ok(())
449    }
450}
451
452/// Returns the byte length of a complete RTSP request at the front of `buf`, or
453/// `None` if more bytes are needed. Errors on a malformed message.
454///
455/// A leading `$` (interleaved frame) is not a request; this returns an error so
456/// the caller does not silently spin.
457fn complete_request_len(buf: &[u8]) -> Result<Option<usize>> {
458    if buf.is_empty() {
459        return Ok(None);
460    }
461    if buf[0] == MAGIC {
462        return Err(Error::MessageParse(
463            "interleaved '$' frame received where a request was expected".into(),
464        ));
465    }
466    match Message::<Body>::parse(buf) {
467        Ok((_, consumed)) => Ok(Some(consumed)),
468        Err(rtsp_types::ParseError::Incomplete(_)) => Ok(None),
469        Err(rtsp_types::ParseError::Error) => {
470            Err(Error::MessageParse("malformed RTSP request".into()))
471        }
472    }
473}