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}