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 std::collections::VecDeque;
27use std::time::Duration;
28
29use futures_util::{SinkExt, StreamExt};
30use tokio::io::{AsyncRead, AsyncWrite};
31use tokio::net::TcpStream;
32use tokio_util::codec::Framed;
33
34use crate::client::{ClientEvent, ClientSession};
35use crate::codec::{ClientCodec, ServerCodec, ServerFrame, bounded};
36use crate::error::{Error, Result};
37use crate::server::{ServerEvent, ServerSession};
38use crate::transport::Transport;
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/// Explicit bounds on every awaited IO of the RTSP adapters (W1 SP1.3): no wait
47/// is unbounded.
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49#[non_exhaustive]
50pub struct RtspTimeouts {
51 /// TCP connect. Default 10 s.
52 pub connect: Duration,
53 /// TLS handshake (rtsps) / first complete request on a server connection. Default 10 s.
54 pub handshake: Duration,
55 /// Longest wait for the next complete frame (a response, a request, an interleaved frame).
56 /// It bounds the whole frame, not each byte, so a peer dripping one byte at a time still
57 /// times out. Default 30 s.
58 pub read_idle: Duration,
59 /// Longest wait for a write to be accepted by the socket. Default 10 s.
60 pub write: Duration,
61}
62
63impl Default for RtspTimeouts {
64 fn default() -> Self {
65 Self {
66 connect: Duration::from_secs(10),
67 handshake: Duration::from_secs(10),
68 read_idle: Duration::from_secs(30),
69 write: Duration::from_secs(10),
70 }
71 }
72}
73
74impl RtspTimeouts {
75 /// Sets [`connect`](Self::connect).
76 #[must_use]
77 pub fn with_connect(mut self, d: Duration) -> Self {
78 self.connect = d;
79 self
80 }
81 /// Sets [`handshake`](Self::handshake).
82 #[must_use]
83 pub fn with_handshake(mut self, d: Duration) -> Self {
84 self.handshake = d;
85 self
86 }
87 /// Sets [`read_idle`](Self::read_idle).
88 #[must_use]
89 pub fn with_read_idle(mut self, d: Duration) -> Self {
90 self.read_idle = d;
91 self
92 }
93 /// Sets [`write`](Self::write).
94 #[must_use]
95 pub fn with_write(mut self, d: Duration) -> Self {
96 self.write = d;
97 self
98 }
99}
100
101/// Maximum interleaved [`ClientEvent::MediaData`] frames held in
102/// `pending_media` awaiting [`AsyncRtspClient::recv_interleaved`]. A camera
103/// that never answers a `GET_PARAMETER` (or any other control request) while
104/// still streaming media would otherwise grow this queue without bound while
105/// [`AsyncRtspClient::exchange`] keeps reading and waiting for the response
106/// (audit run-09 W7). Once full, the oldest frame is dropped — for a live
107/// stream a fresher sample is worth more than one still queued.
108const MAX_PENDING_MEDIA_FRAMES: usize = 1024;
109
110/// Maximum items [`AsyncRtspClient::drain_inbound`] will consume from the
111/// socket in a single call. `timeout(Duration::ZERO, framed.next())` returns
112/// `Ready` for every already-buffered or readable item, so without this cap a
113/// peer streaming interleaved frames faster than we parse (or a hostile
114/// server answering a RECORD session) would keep `drain_inbound` looping
115/// forever, starving the pusher's writes and the keepalive. Whatever is left
116/// after this many items stays in the socket buffer for the next read.
117const MAX_DRAIN_ITEMS: usize = 64;
118
119/// The current instant as a `std` one, virtual-time aware under tokio's paused clock.
120fn now() -> std::time::Instant {
121 tokio::time::Instant::now().into_std()
122}
123
124/// Maps a tokio IO error into the crate error type.
125fn io_err(context: &str, e: std::io::Error) -> Error {
126 Error::Io(format!("{context}: {e}"))
127}
128
129// ===========================================================================
130// Client
131// ===========================================================================
132
133/// An async RTSP client that owns a socket and drives a [`ClientSession`].
134///
135/// Each request method (`options`/`describe`/`setup`/`play`/`pause`/`teardown`)
136/// writes the request bytes the session produces, reads the response off the
137/// socket, feeds it through the sans-IO engine, and returns the resulting
138/// [`ClientEvent`]s. A Digest `401` is answered transparently: when the engine
139/// emits [`ClientEvent::AuthRetry`], the adapter writes the retried request and
140/// reads its response before returning, so the caller only sees the final
141/// [`ClientEvent::Response`].
142///
143/// Interleaved media (`$`-framed RTP/RTCP, §10.12) is pulled with
144/// [`recv_interleaved`](Self::recv_interleaved).
145#[derive(Debug)]
146pub struct AsyncRtspClient<S> {
147 framed: Framed<S, ClientCodec>,
148 /// Media events surfaced while awaiting a response (e.g. interleaved frames
149 /// arriving between control messages), buffered for `recv_interleaved`.
150 pending_media: VecDeque<ClientEvent>,
151 timeouts: RtspTimeouts,
152}
153
154impl AsyncRtspClient<TcpStream> {
155 /// Connects a plain-TCP (`rtsp://`) client to `addr`.
156 ///
157 /// `addr` is any [`tokio::net::ToSocketAddrs`]; for the RTSP default port use
158 /// `(host, RTSP_DEFAULT_PORT)`.
159 pub async fn connect<A: tokio::net::ToSocketAddrs>(addr: A) -> Result<Self> {
160 Self::connect_with_timeouts(addr, ClientSession::new(), RtspTimeouts::default()).await
161 }
162
163 /// Connects a plain-TCP client to `addr` using a pre-configured session
164 /// (e.g. one carrying [`Credentials`](crate::Credentials)).
165 pub async fn connect_with<A: tokio::net::ToSocketAddrs>(
166 addr: A,
167 session: ClientSession,
168 ) -> Result<Self> {
169 Self::connect_with_timeouts(addr, session, RtspTimeouts::default()).await
170 }
171
172 /// Like [`connect_with`](Self::connect_with), with explicit [`RtspTimeouts`].
173 pub async fn connect_with_timeouts<A: tokio::net::ToSocketAddrs>(
174 addr: A,
175 session: ClientSession,
176 timeouts: RtspTimeouts,
177 ) -> Result<Self> {
178 let stream = bounded(timeouts.connect, "connect", async {
179 TcpStream::connect(addr)
180 .await
181 .map_err(|e| io_err("connect", e))
182 })
183 .await?;
184 Ok(Self::with_stream_timeouts(stream, session, timeouts))
185 }
186}
187
188impl<S> AsyncRtspClient<S>
189where
190 S: AsyncRead + AsyncWrite + Unpin,
191{
192 /// Wraps an already-connected stream (plain or TLS) and a session, with the
193 /// default [`RtspTimeouts`].
194 pub fn with_stream(stream: S, session: ClientSession) -> Self {
195 Self::with_stream_timeouts(stream, session, RtspTimeouts::default())
196 }
197
198 /// Wraps an already-connected stream with explicit [`RtspTimeouts`].
199 pub fn with_stream_timeouts(stream: S, session: ClientSession, timeouts: RtspTimeouts) -> Self {
200 AsyncRtspClient {
201 framed: Framed::new(stream, ClientCodec::new(session)),
202 pending_media: VecDeque::new(),
203 timeouts,
204 }
205 }
206
207 /// The timeouts this adapter applies.
208 pub fn timeouts(&self) -> RtspTimeouts {
209 self.timeouts
210 }
211
212 /// The current session state.
213 pub fn state(&self) -> crate::SessionState {
214 self.framed.codec().session.state()
215 }
216
217 /// The negotiated session id, once a SETUP response has been processed.
218 pub fn session_id(&self) -> Option<&str> {
219 self.framed.codec().session.session_id()
220 }
221
222 /// Borrows the underlying sans-IO session (read-only inspection).
223 pub fn session(&self) -> &ClientSession {
224 &self.framed.codec().session
225 }
226
227 async fn write(&mut self, bytes: Vec<u8>) -> Result<()> {
228 bounded(self.timeouts.write, "write", self.framed.send(bytes)).await?;
229 self.framed.codec_mut().session.mark_activity(now());
230 Ok(())
231 }
232
233 /// The next decoded event; `Ok(None)` = clean EOF. One deadline covers the WHOLE
234 /// frame (see [`RtspTimeouts::read_idle`]).
235 async fn next_event(&mut self) -> Result<Option<ClientEvent>> {
236 // Inbound events (media, responses) deliberately do NOT count as keepalive
237 // activity: the server's session timeout is refreshed by RTSP *requests*
238 // (RFC 2326 §12.37), so a busy interleaved stream must not postpone the
239 // GET_PARAMETER keepalive.
240 match tokio::time::timeout(self.timeouts.read_idle, self.framed.next()).await {
241 Err(_) => Err(Error::Timeout { what: "read" }),
242 Ok(None) => Ok(None),
243 Ok(Some(r)) => r.map(Some),
244 }
245 }
246
247 /// Sends `OPTIONS` and awaits the response.
248 pub async fn options(&mut self, uri: &str) -> Result<ClientEvent> {
249 let cseq = self.framed.codec().session.peek_next_cseq();
250 let bytes = self.framed.codec_mut().session.options(uri)?;
251 self.exchange(cseq, bytes).await
252 }
253
254 /// Sends `DESCRIBE` (with `Accept: application/sdp`) and awaits the response.
255 pub async fn describe(&mut self, uri: &str) -> Result<ClientEvent> {
256 let cseq = self.framed.codec().session.peek_next_cseq();
257 let bytes = self.framed.codec_mut().session.describe(uri)?;
258 self.exchange(cseq, bytes).await
259 }
260
261 /// Sends `SETUP` carrying `transport` and awaits the response.
262 pub async fn setup(&mut self, uri: &str, transport: &Transport) -> Result<ClientEvent> {
263 let cseq = self.framed.codec().session.peek_next_cseq();
264 let bytes = self.framed.codec_mut().session.setup(uri, transport)?;
265 self.exchange(cseq, bytes).await
266 }
267
268 /// Sends `PLAY` and awaits the response.
269 pub async fn play(&mut self, uri: &str) -> Result<ClientEvent> {
270 let cseq = self.framed.codec().session.peek_next_cseq();
271 let bytes = self.framed.codec_mut().session.play(uri)?;
272 self.exchange(cseq, bytes).await
273 }
274
275 /// Sends `PAUSE` and awaits the response.
276 pub async fn pause(&mut self, uri: &str) -> Result<ClientEvent> {
277 let cseq = self.framed.codec().session.peek_next_cseq();
278 let bytes = self.framed.codec_mut().session.pause(uri)?;
279 self.exchange(cseq, bytes).await
280 }
281
282 /// Sends `TEARDOWN` and awaits the response.
283 pub async fn teardown(&mut self, uri: &str) -> Result<ClientEvent> {
284 let cseq = self.framed.codec().session.peek_next_cseq();
285 let bytes = self.framed.codec_mut().session.teardown(uri)?;
286 self.exchange(cseq, bytes).await
287 }
288
289 /// Sends `GET_PARAMETER` (empty body = liveness ping) and awaits the response.
290 pub async fn get_parameter(&mut self, uri: &str, body: &[u8]) -> Result<ClientEvent> {
291 let cseq = self.framed.codec().session.peek_next_cseq();
292 let bytes = self.framed.codec_mut().session.get_parameter(uri, body)?;
293 self.exchange(cseq, bytes).await
294 }
295
296 /// Sends `ANNOUNCE` carrying `sdp` and awaits the response (RFC 2326
297 /// §10.3) — the first request of an RTSP push (`ANNOUNCE` -> `SETUP` ->
298 /// [`record`](Self::record)).
299 pub async fn announce(&mut self, uri: &str, sdp: &str) -> Result<ClientEvent> {
300 let cseq = self.framed.codec().session.peek_next_cseq();
301 let bytes = self.framed.codec_mut().session.announce(uri, sdp)?;
302 self.exchange(cseq, bytes).await
303 }
304
305 /// Sends `RECORD` and awaits the response (RFC 2326 §10.11) — starts the
306 /// media push negotiated by a preceding [`announce`](Self::announce) +
307 /// [`setup`](Self::setup).
308 pub async fn record(&mut self, uri: &str) -> Result<ClientEvent> {
309 let cseq = self.framed.codec().session.peek_next_cseq();
310 let bytes = self.framed.codec_mut().session.record(uri)?;
311 self.exchange(cseq, bytes).await
312 }
313
314 /// Sends `payload` as an interleaved (`$`-framed) media packet on `channel`
315 /// (§10.12) — the mirror of the server's
316 /// [`AsyncRtspServer::send_interleaved`], for an RTSP push that delivers
317 /// media interleaved over the control connection. The frame is built by
318 /// [`crate::interleaved::InterleavedFrame::new`] and the write is bounded
319 /// by [`RtspTimeouts::write`].
320 ///
321 /// Before the write this drives the push keepalive (see
322 /// [`poll_keepalive`](Self::poll_keepalive)) and drains any server→client
323 /// bytes already buffered, so a SEND-ONLY pusher that only ever calls this
324 /// method still refreshes its session and does not let the peer's
325 /// responses/notifications fill the socket buffer (RFC 2326 §12.37).
326 ///
327 /// **A `Timeout`/cancelled write leaves a partial `$` frame in the codec's
328 /// write buffer and the connection is dead** — an interleaved frame cannot
329 /// be resynchronised mid-flight, so the caller must reconnect rather than
330 /// retry. Bytes already read stay buffered.
331 pub async fn send_interleaved(&mut self, channel: u8, payload: &[u8]) -> Result<()> {
332 self.poll_keepalive().await?;
333 self.drain_inbound().await?;
334 let bytes = crate::interleaved::InterleavedFrame::slice_to_bytes(channel, payload)?;
335 bounded(self.timeouts.write, "write", self.framed.send(bytes)).await
336 }
337
338 /// Drive the RTSP keepalive for a SEND-ONLY pusher: if the session's
339 /// keepalive deadline ([`ClientSession::poll_timeout`]) has passed, write a
340 /// `GET_PARAMETER` liveness ping now. A no-op otherwise. Honours the
341 /// crate's rule that **only requests we send** count as keepalive activity
342 /// (inbound media/responses never do — see `next_event`), and the same
343 /// half-timeout floor inside `poll_timeout`.
344 ///
345 /// [`send_interleaved`](Self::send_interleaved) calls this automatically;
346 /// call it directly from a pusher loop that sends media by any other path
347 /// (e.g. a separate RTCP/`send` split), or when the media flow is idle.
348 pub async fn poll_keepalive(&mut self) -> Result<()> {
349 if let Some(req) = self.framed.codec_mut().session.handle_timeout(now())? {
350 self.write(req).await?;
351 }
352 Ok(())
353 }
354
355 /// Non-blockingly read whatever the peer has already sent (control
356 /// responses and interleaved media), applying session state and queueing
357 /// media frames — so a send-only pusher keeps the socket drained without
358 /// parking on a read.
359 ///
360 /// One `timeout(Duration::ZERO, self.framed.next())` per readiness probe
361 /// (NOT `next_event`, whose own `read_idle` timeout would nest a second
362 /// timer around every packet): if no bytes are ready it returns
363 /// immediately.
364 ///
365 /// A control response that indicates the session is gone — `454 Session
366 /// Not Found` (RFC 2326 §11.3.2), the usual answer to a `GET_PARAMETER`
367 /// keepalive once the server dropped the session — is surfaced as
368 /// [`Error::SessionNotFound`] rather than discarded, so the send-only
369 /// pusher learns of the loss on its next call instead of only via a later
370 /// write error. Any other response is applied by the codec and dropped
371 /// (a pusher has no outstanding request of its own to correlate). A read
372 /// error (other than a 454) is swallowed: the next `next_event`/
373 /// `recv_interleaved` call surfaces it.
374 ///
375 /// At most [`MAX_DRAIN_ITEMS`] items are consumed per call, so a peer that
376 /// floods interleaved frames cannot make this loop unbounded — the excess
377 /// stays buffered for the next call.
378 pub(crate) async fn drain_inbound(&mut self) -> Result<()> {
379 for _ in 0..MAX_DRAIN_ITEMS {
380 match tokio::time::timeout(Duration::ZERO, self.framed.next()).await {
381 Err(_) => return Ok(()), // nothing ready — non-blocking
382 Ok(None) => return Ok(()), // EOF
383 // A read/decode error is not the push path's to report; the
384 // next real read surfaces it, and `send_interleaved`'s write
385 // is what a pusher must see fail.
386 Ok(Some(Err(_))) => return Ok(()),
387 Ok(Some(Ok(ClientEvent::Response { method, status, .. }))) => {
388 if status == rtsp_types::StatusCode::SessionNotFound {
389 return Err(Error::SessionNotFound { method });
390 }
391 }
392 Ok(Some(Ok(event @ ClientEvent::MediaData { .. }))) => {
393 if self.pending_media.len() >= MAX_PENDING_MEDIA_FRAMES {
394 self.pending_media.pop_front();
395 }
396 self.pending_media.push_back(event);
397 }
398 // `AuthRetry`/any future variant: nothing to do for a
399 // send-only pusher.
400 Ok(Some(Ok(_))) => {}
401 }
402 }
403 // Hit the per-call cap: leave the remainder buffered, do not spin.
404 Ok(())
405 }
406
407 /// Writes an outbound request and reads until the response correlated to
408 /// `cseq` (the `CSeq` just assigned to `request`) arrives, transparently
409 /// completing any Digest `AuthRetry` round-trip.
410 ///
411 /// `cseq` is required (rather than reading it back off the `Response`
412 /// event) because a stray response can otherwise be mistaken for this
413 /// one: if a *previous* `exchange` call's future was dropped (e.g. by a
414 /// caller-side `tokio::time::timeout`) after it had already written its
415 /// request, that request's `Pending` entry stays in the session, and its
416 /// late-arriving response is returned here later unless it's filtered by
417 /// `CSeq` (audit run-09 W7). Such a stray response is simply dropped: no
418 /// one is waiting for it any more.
419 ///
420 /// Every awaited IO is bounded by [`RtspTimeouts`]: a write that the peer
421 /// does not accept is [`Error::Timeout`] `{ what: "write" }`, a response that
422 /// does not complete in `read_idle` is `{ what: "read" }`. Bytes already read
423 /// stay in the framed buffer if this future is dropped, but a caller relying
424 /// on getting *this* response back should not drop and retry: build a fresh
425 /// request instead.
426 ///
427 /// Interleaved media frames that arrive before the response are buffered and
428 /// later returned by [`recv_interleaved`](Self::recv_interleaved).
429 async fn exchange(&mut self, cseq: u32, request: Vec<u8>) -> Result<ClientEvent> {
430 // Tracks whichever CSeq we're currently waiting an answer for: a 401
431 // retry re-signs the SAME logical request under a NEW CSeq (a fresh
432 // `Pending` entry), so once we see our request's `AuthRetry`, we must
433 // switch to waiting for ITS CSeq instead.
434 let mut cseq = cseq;
435 self.write(request).await?;
436 loop {
437 let Some(event) = self.next_event().await? else {
438 return Err(Error::Io("peer closed connection before response".into()));
439 };
440 match event {
441 // Only the response whose CSeq matches the request THIS call
442 // sent answers it; anything else (a duplicate, or a response
443 // for a request a previous, abandoned `exchange` call sent) is
444 // a stray and is dropped. Interleaved media that the codec
445 // decoded in the same read stays queued in the codec for the
446 // next call, so it is not lost (§10.12).
447 ClientEvent::Response { cseq: rcseq, .. } if rcseq == cseq => return Ok(event),
448 ClientEvent::Response { .. } => {}
449 ClientEvent::AuthRetry {
450 cseq: retry_cseq,
451 ref request,
452 ..
453 } => {
454 // Write the retried (now-authenticated) request and wait
455 // for ITS CSeq from here on.
456 let retry = request.clone();
457 self.write(retry).await?;
458 cseq = retry_cseq;
459 }
460 ClientEvent::MediaData { .. } => {
461 if self.pending_media.len() >= MAX_PENDING_MEDIA_FRAMES {
462 self.pending_media.pop_front();
463 }
464 self.pending_media.push_back(event);
465 }
466 }
467 }
468 }
469
470 /// Receives the next interleaved media frame ([`ClientEvent::MediaData`]),
471 /// driving the socket until one is available (§10.12).
472 ///
473 /// Returns `Ok(None)` if the peer closes the connection cleanly before a
474 /// frame arrives. Any control responses interleaved with media are consumed
475 /// and their state transitions applied, but not returned here.
476 ///
477 /// While waiting, a `GET_PARAMETER` keepalive is sent at half the session
478 /// timeout ([`ClientSession::poll_timeout`]), so an idle-but-healthy
479 /// stream does not lose its session.
480 pub async fn recv_interleaved(&mut self) -> Result<Option<ClientEvent>> {
481 enum Woke {
482 Frame(Result<Option<ClientEvent>>),
483 Keepalive,
484 }
485 loop {
486 if let Some(event) = self.pending_media.pop_front() {
487 return Ok(Some(event));
488 }
489 let deadline = self
490 .framed
491 .codec()
492 .session
493 .poll_timeout()
494 .map(tokio::time::Instant::from_std);
495 let woke = {
496 let next = self.next_event();
497 tokio::pin!(next);
498 match deadline {
499 Some(d) => tokio::select! {
500 r = &mut next => Woke::Frame(r),
501 _ = tokio::time::sleep_until(d) => Woke::Keepalive,
502 },
503 None => Woke::Frame(next.await),
504 }
505 };
506 match woke {
507 Woke::Frame(r) => match r? {
508 None if self.framed.codec().session.has_buffered_input() => {
509 return Err(Error::Io("peer closed connection mid-message".into()));
510 }
511 None => return Ok(None),
512 Some(ev @ ClientEvent::MediaData { .. }) => return Ok(Some(ev)),
513 Some(_) => {} // control responses are applied by the session; not surfaced here
514 },
515 Woke::Keepalive => {
516 if let Some(req) = self.framed.codec_mut().session.handle_timeout(now())? {
517 self.write(req).await?;
518 }
519 }
520 }
521 }
522 }
523}
524
525#[cfg(feature = "tls")]
526impl AsyncRtspClient<tokio_rustls::client::TlsStream<TcpStream>> {
527 /// Connects an `rtsps://` (TLS) client to `addr`, verifying the server
528 /// against the given `config` and presenting `server_name` for SNI/cert
529 /// validation.
530 ///
531 /// For the public-CA default trust store, build `config` with
532 /// [`default_tls_client_config`]. For a self-signed camera cert, build a
533 /// [`rustls::ClientConfig`] whose root store contains that cert. For the
534 /// `rtsps` default port use `(host, RTSPS_DEFAULT_PORT)`.
535 pub async fn connect_tls<A: tokio::net::ToSocketAddrs>(
536 addr: A,
537 server_name: &str,
538 config: rustls::ClientConfig,
539 ) -> Result<Self> {
540 Self::connect_tls_with(addr, server_name, config, ClientSession::new()).await
541 }
542
543 /// Connects an `rtsps://` (TLS) client to `addr` using a pre-configured
544 /// session (e.g. one carrying [`Credentials`](crate::Credentials) via
545 /// [`ClientSession::with_credentials`]), otherwise identical to
546 /// [`connect_tls`](Self::connect_tls).
547 pub async fn connect_tls_with<A: tokio::net::ToSocketAddrs>(
548 addr: A,
549 server_name: &str,
550 config: rustls::ClientConfig,
551 session: ClientSession,
552 ) -> Result<Self> {
553 Self::connect_tls_with_timeouts(addr, server_name, config, session, RtspTimeouts::default())
554 .await
555 }
556
557 /// Like [`connect_tls_with`](Self::connect_tls_with), with explicit
558 /// [`RtspTimeouts`]: `connect` bounds the TCP connect, `handshake` the TLS
559 /// handshake.
560 pub async fn connect_tls_with_timeouts<A: tokio::net::ToSocketAddrs>(
561 addr: A,
562 server_name: &str,
563 config: rustls::ClientConfig,
564 session: ClientSession,
565 timeouts: RtspTimeouts,
566 ) -> Result<Self> {
567 let addr = addr;
568 connect_tls_via(
569 async move {
570 TcpStream::connect(addr)
571 .await
572 .map_err(|e| io_err("connect", e))
573 },
574 server_name,
575 config,
576 session,
577 timeouts,
578 )
579 .await
580 }
581}
582
583/// TLS client setup over any dialled transport: `connect` bounds the dial,
584/// `handshake` the TLS handshake. Generic so the bounds are testable without a network.
585#[cfg(feature = "tls")]
586async fn connect_tls_via<T>(
587 dial: impl std::future::Future<Output = Result<T>>,
588 server_name: &str,
589 config: rustls::ClientConfig,
590 session: ClientSession,
591 timeouts: RtspTimeouts,
592) -> Result<AsyncRtspClient<tokio_rustls::client::TlsStream<T>>>
593where
594 T: AsyncRead + AsyncWrite + Unpin,
595{
596 use std::sync::Arc;
597 use tokio_rustls::TlsConnector;
598
599 let tcp = bounded(timeouts.connect, "connect", dial).await?;
600 let connector = TlsConnector::from(Arc::new(config));
601 let dns = rustls::pki_types::ServerName::try_from(server_name.to_string())
602 .map_err(|e| Error::Tls(format!("invalid server name {server_name:?}: {e}")))?;
603 let stream = bounded(timeouts.handshake, "handshake", async {
604 connector
605 .connect(dns, tcp)
606 .await
607 .map_err(|e| io_err("TLS handshake", e))
608 })
609 .await?;
610 Ok(AsyncRtspClient::with_stream_timeouts(
611 stream, session, timeouts,
612 ))
613}
614
615/// Builds a [`rustls::ClientConfig`] trusting the `webpki-roots` public-CA
616/// bundle (the default trust store for `rtsps://` to a well-known server).
617///
618/// For a self-signed camera cert, construct the config directly with a root
619/// store containing that cert and pass it to
620/// [`AsyncRtspClient::connect_tls`].
621#[cfg(feature = "tls")]
622pub fn default_tls_client_config() -> rustls::ClientConfig {
623 let mut roots = rustls::RootCertStore::empty();
624 roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
625 // Select the aws-lc-rs provider explicitly rather than via
626 // `ClientConfig::builder()`'s process-global default: another crate in the
627 // same build (e.g. a `reqwest` that pulls `aws-lc-rs`) can put a *second*
628 // `CryptoProvider` in the tree, leaving no unambiguous default and making
629 // the plain builder panic. Choosing the provider here keeps `rtsp-runtime`
630 // working regardless of what else is linked.
631 rustls::ClientConfig::builder_with_provider(std::sync::Arc::new(
632 rustls::crypto::aws_lc_rs::default_provider(),
633 ))
634 .with_safe_default_protocol_versions()
635 .expect("aws-lc-rs provider supports the safe default protocol versions")
636 .with_root_certificates(roots)
637 .with_no_client_auth()
638}
639
640// ===========================================================================
641// Server
642// ===========================================================================
643
644/// An async RTSP server connection that owns a socket and drives a
645/// [`ServerSession`].
646///
647/// Reads requests off the socket (framed by a `tokio_util` codec over
648/// `rtsp_types`), calls [`ServerSession::handle_request`], writes the response
649/// bytes back, and returns the [`ServerEvent`]s. Every awaited IO is bounded by
650/// [`RtspTimeouts`].
651#[derive(Debug)]
652pub struct AsyncRtspServer<S> {
653 framed: Framed<S, ServerCodec>,
654 session: ServerSession,
655 timeouts: RtspTimeouts,
656 first_request_seen: bool,
657 /// Events of a request whose response is still being written (cancel safety).
658 ready: Option<Vec<ServerEvent>>,
659}
660
661impl AsyncRtspServer<TcpStream> {
662 /// Wraps an accepted plain-TCP connection with a fresh [`ServerSession`]
663 /// whose `Session` ids come from the OS random source.
664 ///
665 /// # Panics
666 ///
667 /// When the OS random source is unavailable.
668 pub fn accept(stream: TcpStream) -> Self {
669 Self::with_stream(stream, ServerSession::new(os_session_id))
670 }
671
672 /// Wraps an accepted plain-TCP connection with a pre-configured session.
673 pub fn accept_with(stream: TcpStream, session: ServerSession) -> Self {
674 Self::with_stream(stream, session)
675 }
676
677 /// Like [`accept_with`](Self::accept_with), with explicit [`RtspTimeouts`].
678 pub fn accept_with_timeouts(
679 stream: TcpStream,
680 session: ServerSession,
681 timeouts: RtspTimeouts,
682 ) -> Self {
683 Self::with_stream_timeouts(stream, session, timeouts)
684 }
685}
686
687#[cfg(feature = "tls")]
688impl AsyncRtspServer<tokio_rustls::server::TlsStream<TcpStream>> {
689 /// Performs the TLS handshake over an accepted TCP connection (an
690 /// `rtsps://` server), then wraps the TLS stream with a fresh session.
691 pub async fn accept_tls(stream: TcpStream, config: rustls::ServerConfig) -> Result<Self> {
692 Self::accept_tls_with_timeouts(stream, config, RtspTimeouts::default()).await
693 }
694
695 /// Like [`accept_tls`](Self::accept_tls), with explicit [`RtspTimeouts`]:
696 /// `handshake` bounds the TLS handshake.
697 pub async fn accept_tls_with_timeouts(
698 stream: TcpStream,
699 config: rustls::ServerConfig,
700 timeouts: RtspTimeouts,
701 ) -> Result<Self> {
702 accept_tls_via(stream, config, timeouts).await
703 }
704}
705
706/// TLS server setup over any accepted transport; `handshake` bounds the handshake.
707#[cfg(feature = "tls")]
708async fn accept_tls_via<T>(
709 stream: T,
710 config: rustls::ServerConfig,
711 timeouts: RtspTimeouts,
712) -> Result<AsyncRtspServer<tokio_rustls::server::TlsStream<T>>>
713where
714 T: AsyncRead + AsyncWrite + Unpin,
715{
716 use std::sync::Arc;
717 use tokio_rustls::TlsAcceptor;
718
719 let acceptor = TlsAcceptor::from(Arc::new(config));
720 let tls = bounded(timeouts.handshake, "handshake", async {
721 acceptor
722 .accept(stream)
723 .await
724 .map_err(|e| io_err("TLS handshake", e))
725 })
726 .await?;
727 Ok(AsyncRtspServer::with_stream_timeouts(
728 tls,
729 ServerSession::new(os_session_id),
730 timeouts,
731 ))
732}
733
734/// A `Session` id value from the OS random source (RFC 2326 §3.4).
735fn os_session_id() -> u64 {
736 let mut bytes = [0u8; 8];
737 getrandom::getrandom(&mut bytes).expect("OS random source unavailable");
738 u64::from_ne_bytes(bytes)
739}
740
741impl<S> AsyncRtspServer<S>
742where
743 S: AsyncRead + AsyncWrite + Unpin,
744{
745 /// Wraps an already-connected stream (plain or TLS) and a session, with the
746 /// default [`RtspTimeouts`].
747 pub fn with_stream(stream: S, session: ServerSession) -> Self {
748 Self::with_stream_timeouts(stream, session, RtspTimeouts::default())
749 }
750
751 /// Wraps an already-connected stream with explicit [`RtspTimeouts`].
752 pub fn with_stream_timeouts(stream: S, session: ServerSession, timeouts: RtspTimeouts) -> Self {
753 AsyncRtspServer {
754 framed: Framed::new(stream, ServerCodec::default()),
755 session,
756 timeouts,
757 first_request_seen: false,
758 ready: None,
759 }
760 }
761
762 /// The timeouts this adapter applies.
763 pub fn timeouts(&self) -> RtspTimeouts {
764 self.timeouts
765 }
766
767 /// The current session state.
768 pub fn state(&self) -> crate::SessionState {
769 self.session.state()
770 }
771
772 /// The allocated session id, once a SETUP has been handled.
773 pub fn session_id(&self) -> Option<&str> {
774 self.session.session_id()
775 }
776
777 /// Mutable access to the underlying stream, for writing raw bytes (e.g.
778 /// deliberately fragmenting an interleaved frame, or sending several frames
779 /// back-to-back) alongside the framed [`send_interleaved`](Self::send_interleaved)
780 /// helper.
781 pub fn stream_mut(&mut self) -> &mut S {
782 self.framed.get_mut()
783 }
784
785 /// Reads the next complete request or interleaved frame, handling a
786 /// request (writing the response back) and returning the produced
787 /// events, or surfacing an interleaved `$`-frame as
788 /// [`ServerEvent::MediaData`].
789 ///
790 /// The FIRST call on a connection waits at most [`RtspTimeouts::handshake`]
791 /// for a complete request; later calls wait at most
792 /// [`RtspTimeouts::read_idle`]. A frame must complete inside that single
793 /// deadline, so a client dripping bytes cannot hold the connection forever.
794 ///
795 /// Cancel-safe: dropping the future while the response is being written
796 /// loses neither the response bytes nor the events; the next call finishes
797 /// the write and returns them.
798 ///
799 /// Returns `Ok(None)` when the peer closes the connection cleanly before a
800 /// full request or frame arrives.
801 pub async fn next_request(&mut self) -> Result<Option<Vec<ServerEvent>>> {
802 let limit = if self.first_request_seen {
803 self.timeouts.read_idle
804 } else {
805 self.timeouts.handshake
806 };
807 loop {
808 // 1. Finish any response write a cancelled earlier call left in the
809 // framed write buffer (never re-sent: `flush` drains it once).
810 bounded(self.timeouts.write, "write", self.framed.flush()).await?;
811 // 2. Hand out the events of a request whose response is now fully written.
812 if let Some(events) = self.ready.take() {
813 return Ok(Some(events));
814 }
815 // 3. Read the next frame under ONE deadline.
816 let frame = match tokio::time::timeout(limit, self.framed.next()).await {
817 Err(_) => return Err(Error::Timeout { what: "read" }),
818 Ok(None) => return Ok(None),
819 Ok(Some(frame)) => frame?,
820 };
821 self.first_request_seen = true;
822 match frame {
823 ServerFrame::Media { channel, data } => {
824 return Ok(Some(vec![ServerEvent::MediaData { channel, data }]));
825 }
826 ServerFrame::Request(bytes) => {
827 // Synchronous from here to the next await: the session has
828 // advanced, so its response and events are recorded together;
829 // a drop during the flush loses neither.
830 let (response, events) = self.session.handle_request(&bytes)?;
831 self.framed.write_buffer_mut().extend_from_slice(&response);
832 self.ready = Some(events);
833 }
834 }
835 }
836 }
837
838 /// Sends an interleaved (`$`-framed) media frame to the client on `channel`
839 /// (§10.12), e.g. an RTP or RTCP packet during PLAY.
840 pub async fn send_interleaved(&mut self, channel: u8, payload: &[u8]) -> Result<()> {
841 let frame = crate::interleaved::InterleavedFrame::new(channel, payload.to_vec());
842 let bytes = frame.to_bytes()?;
843 bounded(self.timeouts.write, "write", self.framed.send(bytes)).await
844 }
845}
846
847#[cfg(all(test, feature = "tls"))]
848mod tls_timeout_tests {
849 //! The rtsps connect / handshake bounds (review: untested). Paused virtual time over
850 //! in-memory pipes; the peer is alive but silent.
851 use super::*;
852 use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
853 use tokio::io::{DuplexStream, duplex};
854
855 fn timeouts() -> RtspTimeouts {
856 RtspTimeouts::default()
857 .with_connect(Duration::from_secs(3))
858 .with_handshake(Duration::from_secs(4))
859 }
860
861 /// Virtual-time hang guard: a missing bound fails instead of hanging the run.
862 async fn guard<F: std::future::Future>(f: F) -> F::Output {
863 tokio::time::timeout(Duration::from_secs(60), f)
864 .await
865 .expect("the connect/handshake bound did not fire")
866 }
867
868 fn server_config() -> rustls::ServerConfig {
869 let cert = include_bytes!("../tests/fixtures/localhost-cert.der").to_vec();
870 let key = include_bytes!("../tests/fixtures/localhost-key.der").to_vec();
871 rustls::ServerConfig::builder_with_provider(std::sync::Arc::new(
872 rustls::crypto::aws_lc_rs::default_provider(),
873 ))
874 .with_safe_default_protocol_versions()
875 .unwrap()
876 .with_no_client_auth()
877 .with_single_cert(
878 vec![CertificateDer::from(cert)],
879 PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(key)),
880 )
881 .unwrap()
882 }
883
884 #[tokio::test(start_paused = true)]
885 async fn a_dial_that_never_completes_times_out_at_connect() {
886 let r = guard(connect_tls_via::<DuplexStream>(
887 std::future::pending(),
888 "localhost",
889 default_tls_client_config(),
890 ClientSession::new(),
891 timeouts(),
892 ))
893 .await;
894 assert!(
895 matches!(r, Err(Error::Timeout { what: "connect" })),
896 "{:?}",
897 r.err()
898 );
899 }
900
901 #[tokio::test(start_paused = true)]
902 async fn a_silent_server_times_out_the_client_tls_handshake() {
903 let (client_io, _server_io) = duplex(4096);
904 let r = guard(connect_tls_via(
905 async { Ok(client_io) },
906 "localhost",
907 default_tls_client_config(),
908 ClientSession::new(),
909 timeouts(),
910 ))
911 .await;
912 assert!(
913 matches!(r, Err(Error::Timeout { what: "handshake" })),
914 "{:?}",
915 r.err()
916 );
917 }
918
919 #[tokio::test(start_paused = true)]
920 async fn a_silent_client_times_out_the_server_tls_handshake() {
921 let (_client_io, server_io) = duplex(4096);
922 let r = guard(accept_tls_via(server_io, server_config(), timeouts())).await;
923 assert!(
924 matches!(r, Err(Error::Timeout { what: "handshake" })),
925 "{:?}",
926 r.err()
927 );
928 }
929}