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 std::collections::VecDeque;
27use std::time::Duration;
28
29use futures_util::{FutureExt, 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, IoContext, 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. A single poll of `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: IoContext, source: std::io::Error) -> Error {
126    Error::Io { context, source }
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(IoContext::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 `now_or_never()` poll of `self.framed.next()` per readiness probe
361    /// (NOT `next_event`, whose own `read_idle` timeout would nest a second
362    /// timer around every packet, and NOT `timeout(Duration::ZERO, ..)`, which
363    /// waits for tokio's next 1 ms timer tick): if no bytes are ready it
364    /// returns immediately.
365    ///
366    /// A control response that indicates the session is gone — `454 Session
367    /// Not Found` (RFC 2326 §11.3.2), the usual answer to a `GET_PARAMETER`
368    /// keepalive once the server dropped the session — is surfaced as
369    /// [`Error::SessionNotFound`] rather than discarded, so the send-only
370    /// pusher learns of the loss on its next call instead of only via a later
371    /// write error. Any other response is applied by the codec and dropped
372    /// (a pusher has no outstanding request of its own to correlate). A read
373    /// error (other than a 454) is swallowed: the next `next_event`/
374    /// `recv_interleaved` call surfaces it.
375    ///
376    /// At most [`MAX_DRAIN_ITEMS`] items are consumed per call, so a peer that
377    /// floods interleaved frames cannot make this loop unbounded — the excess
378    /// stays buffered for the next call.
379    pub(crate) async fn drain_inbound(&mut self) -> Result<()> {
380        for _ in 0..MAX_DRAIN_ITEMS {
381            // A single poll, not `timeout(Duration::ZERO, ..)`: tokio rounds a
382            // timer deadline up to its next 1 ms tick, so the zero timeout
383            // parked the task ~1.5 ms on EVERY call when nothing was ready —
384            // capping a pusher at ~650 frames/s (~0.85 MB/s).
385            match self.framed.next().now_or_never() {
386                None => return Ok(()),       // nothing ready — non-blocking
387                Some(None) => return Ok(()), // EOF
388                // A read/decode error is not the push path's to report; the
389                // next real read surfaces it, and `send_interleaved`'s write
390                // is what a pusher must see fail.
391                Some(Some(Err(_))) => return Ok(()),
392                Some(Some(Ok(ClientEvent::Response { method, status, .. }))) => {
393                    if status == rtsp_types::StatusCode::SessionNotFound {
394                        return Err(Error::SessionNotFound { method });
395                    }
396                }
397                Some(Some(Ok(event @ ClientEvent::MediaData { .. }))) => {
398                    if self.pending_media.len() >= MAX_PENDING_MEDIA_FRAMES {
399                        self.pending_media.pop_front();
400                    }
401                    self.pending_media.push_back(event);
402                }
403                // `AuthRetry`/any future variant: nothing to do for a
404                // send-only pusher.
405                Some(Some(Ok(_))) => {}
406            }
407        }
408        // Hit the per-call cap: leave the remainder buffered, do not spin.
409        Ok(())
410    }
411
412    /// Writes an outbound request and reads until the response correlated to
413    /// `cseq` (the `CSeq` just assigned to `request`) arrives, transparently
414    /// completing any Digest `AuthRetry` round-trip.
415    ///
416    /// `cseq` is required (rather than reading it back off the `Response`
417    /// event) because a stray response can otherwise be mistaken for this
418    /// one: if a *previous* `exchange` call's future was dropped (e.g. by a
419    /// caller-side `tokio::time::timeout`) after it had already written its
420    /// request, that request's `Pending` entry stays in the session, and its
421    /// late-arriving response is returned here later unless it's filtered by
422    /// `CSeq` (audit run-09 W7). Such a stray response is simply dropped: no
423    /// one is waiting for it any more.
424    ///
425    /// Every awaited IO is bounded by [`RtspTimeouts`]: a write that the peer
426    /// does not accept is [`Error::Timeout`] `{ what: "write" }`, a response that
427    /// does not complete in `read_idle` is `{ what: "read" }`. Bytes already read
428    /// stay in the framed buffer if this future is dropped, but a caller relying
429    /// on getting *this* response back should not drop and retry: build a fresh
430    /// request instead.
431    ///
432    /// Interleaved media frames that arrive before the response are buffered and
433    /// later returned by [`recv_interleaved`](Self::recv_interleaved).
434    async fn exchange(&mut self, cseq: u32, request: Vec<u8>) -> Result<ClientEvent> {
435        // Tracks whichever CSeq we're currently waiting an answer for: a 401
436        // retry re-signs the SAME logical request under a NEW CSeq (a fresh
437        // `Pending` entry), so once we see our request's `AuthRetry`, we must
438        // switch to waiting for ITS CSeq instead.
439        let mut cseq = cseq;
440        self.write(request).await?;
441        loop {
442            let Some(event) = self.next_event().await? else {
443                return Err(io_err(
444                    IoContext::Read,
445                    std::io::Error::new(
446                        std::io::ErrorKind::UnexpectedEof,
447                        "peer closed connection before response",
448                    ),
449                ));
450            };
451            match event {
452                // Only the response whose CSeq matches the request THIS call
453                // sent answers it; anything else (a duplicate, or a response
454                // for a request a previous, abandoned `exchange` call sent) is
455                // a stray and is dropped. Interleaved media that the codec
456                // decoded in the same read stays queued in the codec for the
457                // next call, so it is not lost (§10.12).
458                ClientEvent::Response { cseq: rcseq, .. } if rcseq == cseq => return Ok(event),
459                ClientEvent::Response { .. } => {}
460                ClientEvent::AuthRetry {
461                    cseq: retry_cseq,
462                    ref request,
463                    ..
464                } => {
465                    // From here on, wait for the retried request's own CSeq.
466                    let retry = request.clone();
467                    self.write(retry).await?;
468                    cseq = retry_cseq;
469                }
470                ClientEvent::MediaData { .. } => {
471                    if self.pending_media.len() >= MAX_PENDING_MEDIA_FRAMES {
472                        self.pending_media.pop_front();
473                    }
474                    self.pending_media.push_back(event);
475                }
476            }
477        }
478    }
479
480    /// Receives the next interleaved media frame ([`ClientEvent::MediaData`]),
481    /// driving the socket until one is available (§10.12).
482    ///
483    /// Returns `Ok(None)` if the peer closes the connection cleanly before a
484    /// frame arrives. Any control responses interleaved with media are consumed
485    /// and their state transitions applied, but not returned here.
486    ///
487    /// While waiting, a `GET_PARAMETER` keepalive is sent at half the session
488    /// timeout ([`ClientSession::poll_timeout`]), so an idle-but-healthy
489    /// stream does not lose its session.
490    pub async fn recv_interleaved(&mut self) -> Result<Option<ClientEvent>> {
491        enum Woke {
492            Frame(Result<Option<ClientEvent>>),
493            Keepalive,
494        }
495        loop {
496            if let Some(event) = self.pending_media.pop_front() {
497                return Ok(Some(event));
498            }
499            let deadline = self
500                .framed
501                .codec()
502                .session
503                .poll_timeout()
504                .map(tokio::time::Instant::from_std);
505            let woke = {
506                let next = self.next_event();
507                tokio::pin!(next);
508                match deadline {
509                    Some(d) => tokio::select! {
510                        r = &mut next => Woke::Frame(r),
511                        _ = tokio::time::sleep_until(d) => Woke::Keepalive,
512                    },
513                    None => Woke::Frame(next.await),
514                }
515            };
516            match woke {
517                Woke::Frame(r) => match r? {
518                    None if self.framed.codec().session.has_buffered_input() => {
519                        return Err(io_err(
520                            IoContext::Read,
521                            std::io::Error::new(
522                                std::io::ErrorKind::UnexpectedEof,
523                                "peer closed connection mid-message",
524                            ),
525                        ));
526                    }
527                    None => return Ok(None),
528                    Some(ev @ ClientEvent::MediaData { .. }) => return Ok(Some(ev)),
529                    Some(_) => {} // control responses are applied by the session; not surfaced here
530                },
531                Woke::Keepalive => {
532                    if let Some(req) = self.framed.codec_mut().session.handle_timeout(now())? {
533                        self.write(req).await?;
534                    }
535                }
536            }
537        }
538    }
539}
540
541#[cfg(feature = "tls")]
542impl AsyncRtspClient<tokio_rustls::client::TlsStream<TcpStream>> {
543    /// Connects an `rtsps://` (TLS) client to `addr`, verifying the server
544    /// against the given `config` and presenting `server_name` for SNI/cert
545    /// validation.
546    ///
547    /// For the public-CA default trust store, build `config` with
548    /// [`default_tls_client_config`]. For a self-signed camera cert, build a
549    /// [`rustls::ClientConfig`] whose root store contains that cert. For the
550    /// `rtsps` default port use `(host, RTSPS_DEFAULT_PORT)`.
551    pub async fn connect_tls<A: tokio::net::ToSocketAddrs>(
552        addr: A,
553        server_name: &str,
554        config: rustls::ClientConfig,
555    ) -> Result<Self> {
556        Self::connect_tls_with(addr, server_name, config, ClientSession::new()).await
557    }
558
559    /// Connects an `rtsps://` (TLS) client to `addr` using a pre-configured
560    /// session (e.g. one carrying [`Credentials`](crate::Credentials) via
561    /// [`ClientSession::with_credentials`]), otherwise identical to
562    /// [`connect_tls`](Self::connect_tls).
563    pub async fn connect_tls_with<A: tokio::net::ToSocketAddrs>(
564        addr: A,
565        server_name: &str,
566        config: rustls::ClientConfig,
567        session: ClientSession,
568    ) -> Result<Self> {
569        Self::connect_tls_with_timeouts(addr, server_name, config, session, RtspTimeouts::default())
570            .await
571    }
572
573    /// Like [`connect_tls_with`](Self::connect_tls_with), with explicit
574    /// [`RtspTimeouts`]: `connect` bounds the TCP connect, `handshake` the TLS
575    /// handshake.
576    pub async fn connect_tls_with_timeouts<A: tokio::net::ToSocketAddrs>(
577        addr: A,
578        server_name: &str,
579        config: rustls::ClientConfig,
580        session: ClientSession,
581        timeouts: RtspTimeouts,
582    ) -> Result<Self> {
583        let addr = addr;
584        connect_tls_via(
585            async move {
586                TcpStream::connect(addr)
587                    .await
588                    .map_err(|e| io_err(IoContext::Connect, e))
589            },
590            server_name,
591            config,
592            session,
593            timeouts,
594        )
595        .await
596    }
597}
598
599/// TLS client setup over any dialled transport: `connect` bounds the dial,
600/// `handshake` the TLS handshake. Generic so the bounds are testable without a network.
601#[cfg(feature = "tls")]
602async fn connect_tls_via<T>(
603    dial: impl std::future::Future<Output = Result<T>>,
604    server_name: &str,
605    config: rustls::ClientConfig,
606    session: ClientSession,
607    timeouts: RtspTimeouts,
608) -> Result<AsyncRtspClient<tokio_rustls::client::TlsStream<T>>>
609where
610    T: AsyncRead + AsyncWrite + Unpin,
611{
612    use std::sync::Arc;
613    use tokio_rustls::TlsConnector;
614
615    let tcp = bounded(timeouts.connect, "connect", dial).await?;
616    let connector = TlsConnector::from(Arc::new(config));
617    let dns = rustls::pki_types::ServerName::try_from(server_name.to_string()).map_err(|e| {
618        Error::Tls {
619            server_name: server_name.to_string(),
620            source: Box::new(e),
621        }
622    })?;
623    let stream = bounded(timeouts.handshake, "handshake", async {
624        connector
625            .connect(dns, tcp)
626            .await
627            .map_err(|e| io_err(IoContext::TlsHandshake, e))
628    })
629    .await?;
630    Ok(AsyncRtspClient::with_stream_timeouts(
631        stream, session, timeouts,
632    ))
633}
634
635/// Builds a [`rustls::ClientConfig`] trusting the `webpki-roots` public-CA
636/// bundle (the default trust store for `rtsps://` to a well-known server).
637///
638/// For a self-signed camera cert, construct the config directly with a root
639/// store containing that cert and pass it to
640/// [`AsyncRtspClient::connect_tls`].
641#[cfg(feature = "tls")]
642pub fn default_tls_client_config() -> rustls::ClientConfig {
643    let mut roots = rustls::RootCertStore::empty();
644    roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
645    // Select the aws-lc-rs provider explicitly rather than via
646    // `ClientConfig::builder()`'s process-global default: another crate in the
647    // same build (e.g. a `reqwest` that pulls `aws-lc-rs`) can put a *second*
648    // `CryptoProvider` in the tree, leaving no unambiguous default and making
649    // the plain builder panic. Choosing the provider here keeps `rtsp-runtime`
650    // working regardless of what else is linked.
651    rustls::ClientConfig::builder_with_provider(std::sync::Arc::new(
652        rustls::crypto::aws_lc_rs::default_provider(),
653    ))
654    .with_safe_default_protocol_versions()
655    .expect("aws-lc-rs provider supports the safe default protocol versions")
656    .with_root_certificates(roots)
657    .with_no_client_auth()
658}
659
660// ===========================================================================
661// Server
662// ===========================================================================
663
664/// An async RTSP server connection that owns a socket and drives a
665/// [`ServerSession`].
666///
667/// Reads requests off the socket (framed by a `tokio_util` codec over
668/// `rtsp_types`), calls [`ServerSession::handle_request`], writes the response
669/// bytes back, and returns the [`ServerEvent`]s. Every awaited IO is bounded by
670/// [`RtspTimeouts`].
671#[derive(Debug)]
672pub struct AsyncRtspServer<S> {
673    framed: Framed<S, ServerCodec>,
674    session: ServerSession,
675    timeouts: RtspTimeouts,
676    first_request_seen: bool,
677    /// Events of a request whose response is still being written (cancel safety).
678    ready: Option<Vec<ServerEvent>>,
679}
680
681impl AsyncRtspServer<TcpStream> {
682    /// Wraps an accepted plain-TCP connection with a fresh [`ServerSession`]
683    /// whose `Session` ids come from the OS random source.
684    ///
685    /// # Panics
686    ///
687    /// When the OS random source is unavailable.
688    pub fn accept(stream: TcpStream) -> Self {
689        Self::with_stream(stream, ServerSession::new(os_session_id))
690    }
691
692    /// Wraps an accepted plain-TCP connection with a pre-configured session.
693    pub fn accept_with(stream: TcpStream, session: ServerSession) -> Self {
694        Self::with_stream(stream, session)
695    }
696}
697
698#[cfg(feature = "tls")]
699impl AsyncRtspServer<tokio_rustls::server::TlsStream<TcpStream>> {
700    /// Performs the TLS handshake over an accepted TCP connection (an
701    /// `rtsps://` server), then wraps the TLS stream with a fresh session.
702    pub async fn accept_tls(stream: TcpStream, config: rustls::ServerConfig) -> Result<Self> {
703        Self::accept_tls_with_timeouts(stream, config, RtspTimeouts::default()).await
704    }
705
706    /// Like [`accept_tls`](Self::accept_tls), with explicit [`RtspTimeouts`]:
707    /// `handshake` bounds the TLS handshake.
708    pub async fn accept_tls_with_timeouts(
709        stream: TcpStream,
710        config: rustls::ServerConfig,
711        timeouts: RtspTimeouts,
712    ) -> Result<Self> {
713        accept_tls_via(stream, config, timeouts).await
714    }
715}
716
717/// TLS server setup over any accepted transport; `handshake` bounds the handshake.
718#[cfg(feature = "tls")]
719async fn accept_tls_via<T>(
720    stream: T,
721    config: rustls::ServerConfig,
722    timeouts: RtspTimeouts,
723) -> Result<AsyncRtspServer<tokio_rustls::server::TlsStream<T>>>
724where
725    T: AsyncRead + AsyncWrite + Unpin,
726{
727    use std::sync::Arc;
728    use tokio_rustls::TlsAcceptor;
729
730    let acceptor = TlsAcceptor::from(Arc::new(config));
731    let tls = bounded(timeouts.handshake, "handshake", async {
732        acceptor
733            .accept(stream)
734            .await
735            .map_err(|e| io_err(IoContext::TlsHandshake, e))
736    })
737    .await?;
738    Ok(AsyncRtspServer::with_stream_timeouts(
739        tls,
740        ServerSession::new(os_session_id),
741        timeouts,
742    ))
743}
744
745/// A `Session` id value from the OS random source (RFC 2326 §3.4).
746fn os_session_id() -> u64 {
747    let mut bytes = [0u8; 8];
748    getrandom::fill(&mut bytes).expect("OS random source unavailable");
749    u64::from_ne_bytes(bytes)
750}
751
752impl<S> AsyncRtspServer<S>
753where
754    S: AsyncRead + AsyncWrite + Unpin,
755{
756    /// Wraps an already-connected stream (plain or TLS) and a session, with the
757    /// default [`RtspTimeouts`].
758    pub fn with_stream(stream: S, session: ServerSession) -> Self {
759        Self::with_stream_timeouts(stream, session, RtspTimeouts::default())
760    }
761
762    /// Wraps an already-connected stream with explicit [`RtspTimeouts`].
763    pub fn with_stream_timeouts(stream: S, session: ServerSession, timeouts: RtspTimeouts) -> Self {
764        AsyncRtspServer {
765            framed: Framed::new(stream, ServerCodec::default()),
766            session,
767            timeouts,
768            first_request_seen: false,
769            ready: None,
770        }
771    }
772
773    /// The timeouts this adapter applies.
774    pub fn timeouts(&self) -> RtspTimeouts {
775        self.timeouts
776    }
777
778    /// The current session state.
779    pub fn state(&self) -> crate::SessionState {
780        self.session.state()
781    }
782
783    /// The allocated session id, once a SETUP has been handled.
784    pub fn session_id(&self) -> Option<&str> {
785        self.session.session_id()
786    }
787
788    /// Mutable access to the underlying stream, for writing raw bytes (e.g.
789    /// deliberately fragmenting an interleaved frame, or sending several frames
790    /// back-to-back) alongside the framed [`send_interleaved`](Self::send_interleaved)
791    /// helper.
792    pub fn stream_mut(&mut self) -> &mut S {
793        self.framed.get_mut()
794    }
795
796    /// Reads the next complete request or interleaved frame, handling a
797    /// request (writing the response back) and returning the produced
798    /// events, or surfacing an interleaved `$`-frame as
799    /// [`ServerEvent::MediaData`].
800    ///
801    /// The FIRST call on a connection waits at most [`RtspTimeouts::handshake`]
802    /// for a complete request; later calls wait at most
803    /// [`RtspTimeouts::read_idle`]. A frame must complete inside that single
804    /// deadline, so a client dripping bytes cannot hold the connection forever.
805    ///
806    /// Cancel-safe: dropping the future while the response is being written
807    /// loses neither the response bytes nor the events; the next call finishes
808    /// the write and returns them.
809    ///
810    /// Returns `Ok(None)` when the peer closes the connection cleanly before a
811    /// full request or frame arrives.
812    pub async fn next_request(&mut self) -> Result<Option<Vec<ServerEvent>>> {
813        let limit = if self.first_request_seen {
814            self.timeouts.read_idle
815        } else {
816            self.timeouts.handshake
817        };
818        loop {
819            bounded(self.timeouts.write, "write", self.framed.flush()).await?;
820            if let Some(events) = self.ready.take() {
821                return Ok(Some(events));
822            }
823            let frame = match tokio::time::timeout(limit, self.framed.next()).await {
824                Err(_) => return Err(Error::Timeout { what: "read" }),
825                Ok(None) => return Ok(None),
826                Ok(Some(frame)) => frame?,
827            };
828            self.first_request_seen = true;
829            match frame {
830                ServerFrame::Media { channel, data } => {
831                    return Ok(Some(vec![ServerEvent::MediaData { channel, data }]));
832                }
833                ServerFrame::Request(bytes) => {
834                    // Synchronous from here to the next await: the session has
835                    // advanced, so its response and events are recorded together;
836                    // a drop during the flush loses neither.
837                    let (response, events) = self.session.handle_request(&bytes)?;
838                    self.framed.write_buffer_mut().extend_from_slice(&response);
839                    self.ready = Some(events);
840                }
841            }
842        }
843    }
844
845    /// Sends an interleaved (`$`-framed) media frame to the client on `channel`
846    /// (§10.12), e.g. an RTP or RTCP packet during PLAY.
847    pub async fn send_interleaved(&mut self, channel: u8, payload: &[u8]) -> Result<()> {
848        let frame = crate::interleaved::InterleavedFrame::new(channel, payload.to_vec());
849        let bytes = frame.to_bytes()?;
850        bounded(self.timeouts.write, "write", self.framed.send(bytes)).await
851    }
852}
853
854#[cfg(all(test, feature = "tls"))]
855mod tls_timeout_tests {
856    //! The rtsps connect / handshake bounds (review: untested). Paused virtual time over
857    //! in-memory pipes; the peer is alive but silent.
858    use super::*;
859    use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
860    use tokio::io::{DuplexStream, duplex};
861
862    fn timeouts() -> RtspTimeouts {
863        RtspTimeouts::default()
864            .with_connect(Duration::from_secs(3))
865            .with_handshake(Duration::from_secs(4))
866    }
867
868    /// Virtual-time hang guard: a missing bound fails instead of hanging the run.
869    async fn guard<F: std::future::Future>(f: F) -> F::Output {
870        tokio::time::timeout(Duration::from_secs(60), f)
871            .await
872            .expect("the connect/handshake bound did not fire")
873    }
874
875    fn server_config() -> rustls::ServerConfig {
876        let cert = test_fixtures::read("tls/localhost-cert.der");
877        let key = test_fixtures::read("tls/localhost-key.der");
878        rustls::ServerConfig::builder_with_provider(std::sync::Arc::new(
879            rustls::crypto::aws_lc_rs::default_provider(),
880        ))
881        .with_safe_default_protocol_versions()
882        .unwrap()
883        .with_no_client_auth()
884        .with_single_cert(
885            vec![CertificateDer::from(cert)],
886            PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(key)),
887        )
888        .unwrap()
889    }
890
891    #[tokio::test(start_paused = true)]
892    async fn a_dial_that_never_completes_times_out_at_connect() {
893        let r = guard(connect_tls_via::<DuplexStream>(
894            std::future::pending(),
895            "localhost",
896            default_tls_client_config(),
897            ClientSession::new(),
898            timeouts(),
899        ))
900        .await;
901        assert!(
902            matches!(r, Err(Error::Timeout { what: "connect" })),
903            "{:?}",
904            r.err()
905        );
906    }
907
908    #[tokio::test(start_paused = true)]
909    async fn a_silent_server_times_out_the_client_tls_handshake() {
910        let (client_io, _server_io) = duplex(4096);
911        let r = guard(connect_tls_via(
912            async { Ok(client_io) },
913            "localhost",
914            default_tls_client_config(),
915            ClientSession::new(),
916            timeouts(),
917        ))
918        .await;
919        assert!(
920            matches!(r, Err(Error::Timeout { what: "handshake" })),
921            "{:?}",
922            r.err()
923        );
924    }
925
926    #[tokio::test(start_paused = true)]
927    async fn a_silent_client_times_out_the_server_tls_handshake() {
928        let (_client_io, server_io) = duplex(4096);
929        let r = guard(accept_tls_via(server_io, server_config(), timeouts())).await;
930        assert!(
931            matches!(r, Err(Error::Timeout { what: "handshake" })),
932            "{:?}",
933            r.err()
934        );
935    }
936}
937
938#[cfg(test)]
939mod tests {
940    use super::os_session_id;
941
942    #[test]
943    fn os_session_id_varies() {
944        let mut seen = std::collections::HashSet::new();
945        for _ in 0..100 {
946            seen.insert(os_session_id());
947        }
948        assert!(
949            seen.len() > 90,
950            "os_session_id did not vary: {}",
951            seen.len()
952        );
953    }
954}