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}