use std::collections::VecDeque;
use std::time::Duration;
use futures_util::{FutureExt, SinkExt, StreamExt};
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::net::TcpStream;
use tokio_util::codec::Framed;
use crate::client::{ClientEvent, ClientSession};
use crate::codec::{ClientCodec, ServerCodec, ServerFrame, bounded};
use crate::error::{Error, IoContext, Result};
use crate::server::{ServerEvent, ServerSession};
use crate::transport::Transport;
pub const RTSP_DEFAULT_PORT: u16 = 554;
pub const RTSPS_DEFAULT_PORT: u16 = 322;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct RtspTimeouts {
pub connect: Duration,
pub handshake: Duration,
pub read_idle: Duration,
pub write: Duration,
}
impl Default for RtspTimeouts {
fn default() -> Self {
Self {
connect: Duration::from_secs(10),
handshake: Duration::from_secs(10),
read_idle: Duration::from_secs(30),
write: Duration::from_secs(10),
}
}
}
impl RtspTimeouts {
#[must_use]
pub fn with_connect(mut self, d: Duration) -> Self {
self.connect = d;
self
}
#[must_use]
pub fn with_handshake(mut self, d: Duration) -> Self {
self.handshake = d;
self
}
#[must_use]
pub fn with_read_idle(mut self, d: Duration) -> Self {
self.read_idle = d;
self
}
#[must_use]
pub fn with_write(mut self, d: Duration) -> Self {
self.write = d;
self
}
}
const MAX_PENDING_MEDIA_FRAMES: usize = 1024;
const MAX_DRAIN_ITEMS: usize = 64;
fn now() -> std::time::Instant {
tokio::time::Instant::now().into_std()
}
fn io_err(context: IoContext, source: std::io::Error) -> Error {
Error::Io { context, source }
}
#[derive(Debug)]
pub struct AsyncRtspClient<S> {
framed: Framed<S, ClientCodec>,
pending_media: VecDeque<ClientEvent>,
timeouts: RtspTimeouts,
}
impl AsyncRtspClient<TcpStream> {
pub async fn connect<A: tokio::net::ToSocketAddrs>(addr: A) -> Result<Self> {
Self::connect_with_timeouts(addr, ClientSession::new(), RtspTimeouts::default()).await
}
pub async fn connect_with<A: tokio::net::ToSocketAddrs>(
addr: A,
session: ClientSession,
) -> Result<Self> {
Self::connect_with_timeouts(addr, session, RtspTimeouts::default()).await
}
pub async fn connect_with_timeouts<A: tokio::net::ToSocketAddrs>(
addr: A,
session: ClientSession,
timeouts: RtspTimeouts,
) -> Result<Self> {
let stream = bounded(timeouts.connect, "connect", async {
TcpStream::connect(addr)
.await
.map_err(|e| io_err(IoContext::Connect, e))
})
.await?;
Ok(Self::with_stream_timeouts(stream, session, timeouts))
}
}
impl<S> AsyncRtspClient<S>
where
S: AsyncRead + AsyncWrite + Unpin,
{
pub fn with_stream(stream: S, session: ClientSession) -> Self {
Self::with_stream_timeouts(stream, session, RtspTimeouts::default())
}
pub fn with_stream_timeouts(stream: S, session: ClientSession, timeouts: RtspTimeouts) -> Self {
AsyncRtspClient {
framed: Framed::new(stream, ClientCodec::new(session)),
pending_media: VecDeque::new(),
timeouts,
}
}
pub fn timeouts(&self) -> RtspTimeouts {
self.timeouts
}
pub fn state(&self) -> crate::SessionState {
self.framed.codec().session.state()
}
pub fn session_id(&self) -> Option<&str> {
self.framed.codec().session.session_id()
}
pub fn session(&self) -> &ClientSession {
&self.framed.codec().session
}
async fn write(&mut self, bytes: Vec<u8>) -> Result<()> {
bounded(self.timeouts.write, "write", self.framed.send(bytes)).await?;
self.framed.codec_mut().session.mark_activity(now());
Ok(())
}
async fn next_event(&mut self) -> Result<Option<ClientEvent>> {
match tokio::time::timeout(self.timeouts.read_idle, self.framed.next()).await {
Err(_) => Err(Error::Timeout { what: "read" }),
Ok(None) => Ok(None),
Ok(Some(r)) => r.map(Some),
}
}
pub async fn options(&mut self, uri: &str) -> Result<ClientEvent> {
let cseq = self.framed.codec().session.peek_next_cseq();
let bytes = self.framed.codec_mut().session.options(uri)?;
self.exchange(cseq, bytes).await
}
pub async fn describe(&mut self, uri: &str) -> Result<ClientEvent> {
let cseq = self.framed.codec().session.peek_next_cseq();
let bytes = self.framed.codec_mut().session.describe(uri)?;
self.exchange(cseq, bytes).await
}
pub async fn setup(&mut self, uri: &str, transport: &Transport) -> Result<ClientEvent> {
let cseq = self.framed.codec().session.peek_next_cseq();
let bytes = self.framed.codec_mut().session.setup(uri, transport)?;
self.exchange(cseq, bytes).await
}
pub async fn play(&mut self, uri: &str) -> Result<ClientEvent> {
let cseq = self.framed.codec().session.peek_next_cseq();
let bytes = self.framed.codec_mut().session.play(uri)?;
self.exchange(cseq, bytes).await
}
pub async fn pause(&mut self, uri: &str) -> Result<ClientEvent> {
let cseq = self.framed.codec().session.peek_next_cseq();
let bytes = self.framed.codec_mut().session.pause(uri)?;
self.exchange(cseq, bytes).await
}
pub async fn teardown(&mut self, uri: &str) -> Result<ClientEvent> {
let cseq = self.framed.codec().session.peek_next_cseq();
let bytes = self.framed.codec_mut().session.teardown(uri)?;
self.exchange(cseq, bytes).await
}
pub async fn get_parameter(&mut self, uri: &str, body: &[u8]) -> Result<ClientEvent> {
let cseq = self.framed.codec().session.peek_next_cseq();
let bytes = self.framed.codec_mut().session.get_parameter(uri, body)?;
self.exchange(cseq, bytes).await
}
pub async fn announce(&mut self, uri: &str, sdp: &str) -> Result<ClientEvent> {
let cseq = self.framed.codec().session.peek_next_cseq();
let bytes = self.framed.codec_mut().session.announce(uri, sdp)?;
self.exchange(cseq, bytes).await
}
pub async fn record(&mut self, uri: &str) -> Result<ClientEvent> {
let cseq = self.framed.codec().session.peek_next_cseq();
let bytes = self.framed.codec_mut().session.record(uri)?;
self.exchange(cseq, bytes).await
}
pub async fn send_interleaved(&mut self, channel: u8, payload: &[u8]) -> Result<()> {
self.poll_keepalive().await?;
self.drain_inbound().await?;
let bytes = crate::interleaved::InterleavedFrame::slice_to_bytes(channel, payload)?;
bounded(self.timeouts.write, "write", self.framed.send(bytes)).await
}
pub async fn poll_keepalive(&mut self) -> Result<()> {
if let Some(req) = self.framed.codec_mut().session.handle_timeout(now())? {
self.write(req).await?;
}
Ok(())
}
pub(crate) async fn drain_inbound(&mut self) -> Result<()> {
for _ in 0..MAX_DRAIN_ITEMS {
match self.framed.next().now_or_never() {
None => return Ok(()), Some(None) => return Ok(()), Some(Some(Err(_))) => return Ok(()),
Some(Some(Ok(ClientEvent::Response { method, status, .. }))) => {
if status == rtsp_types::StatusCode::SessionNotFound {
return Err(Error::SessionNotFound { method });
}
}
Some(Some(Ok(event @ ClientEvent::MediaData { .. }))) => {
if self.pending_media.len() >= MAX_PENDING_MEDIA_FRAMES {
self.pending_media.pop_front();
}
self.pending_media.push_back(event);
}
Some(Some(Ok(_))) => {}
}
}
Ok(())
}
async fn exchange(&mut self, cseq: u32, request: Vec<u8>) -> Result<ClientEvent> {
let mut cseq = cseq;
self.write(request).await?;
loop {
let Some(event) = self.next_event().await? else {
return Err(io_err(
IoContext::Read,
std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"peer closed connection before response",
),
));
};
match event {
ClientEvent::Response { cseq: rcseq, .. } if rcseq == cseq => return Ok(event),
ClientEvent::Response { .. } => {}
ClientEvent::AuthRetry {
cseq: retry_cseq,
ref request,
..
} => {
let retry = request.clone();
self.write(retry).await?;
cseq = retry_cseq;
}
ClientEvent::MediaData { .. } => {
if self.pending_media.len() >= MAX_PENDING_MEDIA_FRAMES {
self.pending_media.pop_front();
}
self.pending_media.push_back(event);
}
}
}
}
pub async fn recv_interleaved(&mut self) -> Result<Option<ClientEvent>> {
enum Woke {
Frame(Result<Option<ClientEvent>>),
Keepalive,
}
loop {
if let Some(event) = self.pending_media.pop_front() {
return Ok(Some(event));
}
let deadline = self
.framed
.codec()
.session
.poll_timeout()
.map(tokio::time::Instant::from_std);
let woke = {
let next = self.next_event();
tokio::pin!(next);
match deadline {
Some(d) => tokio::select! {
r = &mut next => Woke::Frame(r),
_ = tokio::time::sleep_until(d) => Woke::Keepalive,
},
None => Woke::Frame(next.await),
}
};
match woke {
Woke::Frame(r) => match r? {
None if self.framed.codec().session.has_buffered_input() => {
return Err(io_err(
IoContext::Read,
std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"peer closed connection mid-message",
),
));
}
None => return Ok(None),
Some(ev @ ClientEvent::MediaData { .. }) => return Ok(Some(ev)),
Some(_) => {} },
Woke::Keepalive => {
if let Some(req) = self.framed.codec_mut().session.handle_timeout(now())? {
self.write(req).await?;
}
}
}
}
}
}
#[cfg(feature = "tls")]
impl AsyncRtspClient<tokio_rustls::client::TlsStream<TcpStream>> {
pub async fn connect_tls<A: tokio::net::ToSocketAddrs>(
addr: A,
server_name: &str,
config: rustls::ClientConfig,
) -> Result<Self> {
Self::connect_tls_with(addr, server_name, config, ClientSession::new()).await
}
pub async fn connect_tls_with<A: tokio::net::ToSocketAddrs>(
addr: A,
server_name: &str,
config: rustls::ClientConfig,
session: ClientSession,
) -> Result<Self> {
Self::connect_tls_with_timeouts(addr, server_name, config, session, RtspTimeouts::default())
.await
}
pub async fn connect_tls_with_timeouts<A: tokio::net::ToSocketAddrs>(
addr: A,
server_name: &str,
config: rustls::ClientConfig,
session: ClientSession,
timeouts: RtspTimeouts,
) -> Result<Self> {
let addr = addr;
connect_tls_via(
async move {
TcpStream::connect(addr)
.await
.map_err(|e| io_err(IoContext::Connect, e))
},
server_name,
config,
session,
timeouts,
)
.await
}
}
#[cfg(feature = "tls")]
async fn connect_tls_via<T>(
dial: impl std::future::Future<Output = Result<T>>,
server_name: &str,
config: rustls::ClientConfig,
session: ClientSession,
timeouts: RtspTimeouts,
) -> Result<AsyncRtspClient<tokio_rustls::client::TlsStream<T>>>
where
T: AsyncRead + AsyncWrite + Unpin,
{
use std::sync::Arc;
use tokio_rustls::TlsConnector;
let tcp = bounded(timeouts.connect, "connect", dial).await?;
let connector = TlsConnector::from(Arc::new(config));
let dns = rustls::pki_types::ServerName::try_from(server_name.to_string()).map_err(|e| {
Error::Tls {
server_name: server_name.to_string(),
source: Box::new(e),
}
})?;
let stream = bounded(timeouts.handshake, "handshake", async {
connector
.connect(dns, tcp)
.await
.map_err(|e| io_err(IoContext::TlsHandshake, e))
})
.await?;
Ok(AsyncRtspClient::with_stream_timeouts(
stream, session, timeouts,
))
}
#[cfg(feature = "tls")]
pub fn default_tls_client_config() -> rustls::ClientConfig {
let mut roots = rustls::RootCertStore::empty();
roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
rustls::ClientConfig::builder_with_provider(std::sync::Arc::new(
rustls::crypto::aws_lc_rs::default_provider(),
))
.with_safe_default_protocol_versions()
.expect("aws-lc-rs provider supports the safe default protocol versions")
.with_root_certificates(roots)
.with_no_client_auth()
}
#[derive(Debug)]
pub struct AsyncRtspServer<S> {
framed: Framed<S, ServerCodec>,
session: ServerSession,
timeouts: RtspTimeouts,
first_request_seen: bool,
ready: Option<Vec<ServerEvent>>,
}
impl AsyncRtspServer<TcpStream> {
pub fn accept(stream: TcpStream) -> Self {
Self::with_stream(stream, ServerSession::new(os_session_id))
}
pub fn accept_with(stream: TcpStream, session: ServerSession) -> Self {
Self::with_stream(stream, session)
}
}
#[cfg(feature = "tls")]
impl AsyncRtspServer<tokio_rustls::server::TlsStream<TcpStream>> {
pub async fn accept_tls(stream: TcpStream, config: rustls::ServerConfig) -> Result<Self> {
Self::accept_tls_with_timeouts(stream, config, RtspTimeouts::default()).await
}
pub async fn accept_tls_with_timeouts(
stream: TcpStream,
config: rustls::ServerConfig,
timeouts: RtspTimeouts,
) -> Result<Self> {
accept_tls_via(stream, config, timeouts).await
}
}
#[cfg(feature = "tls")]
async fn accept_tls_via<T>(
stream: T,
config: rustls::ServerConfig,
timeouts: RtspTimeouts,
) -> Result<AsyncRtspServer<tokio_rustls::server::TlsStream<T>>>
where
T: AsyncRead + AsyncWrite + Unpin,
{
use std::sync::Arc;
use tokio_rustls::TlsAcceptor;
let acceptor = TlsAcceptor::from(Arc::new(config));
let tls = bounded(timeouts.handshake, "handshake", async {
acceptor
.accept(stream)
.await
.map_err(|e| io_err(IoContext::TlsHandshake, e))
})
.await?;
Ok(AsyncRtspServer::with_stream_timeouts(
tls,
ServerSession::new(os_session_id),
timeouts,
))
}
fn os_session_id() -> u64 {
let mut bytes = [0u8; 8];
getrandom::fill(&mut bytes).expect("OS random source unavailable");
u64::from_ne_bytes(bytes)
}
impl<S> AsyncRtspServer<S>
where
S: AsyncRead + AsyncWrite + Unpin,
{
pub fn with_stream(stream: S, session: ServerSession) -> Self {
Self::with_stream_timeouts(stream, session, RtspTimeouts::default())
}
pub fn with_stream_timeouts(stream: S, session: ServerSession, timeouts: RtspTimeouts) -> Self {
AsyncRtspServer {
framed: Framed::new(stream, ServerCodec::default()),
session,
timeouts,
first_request_seen: false,
ready: None,
}
}
pub fn timeouts(&self) -> RtspTimeouts {
self.timeouts
}
pub fn state(&self) -> crate::SessionState {
self.session.state()
}
pub fn session_id(&self) -> Option<&str> {
self.session.session_id()
}
pub fn stream_mut(&mut self) -> &mut S {
self.framed.get_mut()
}
pub async fn next_request(&mut self) -> Result<Option<Vec<ServerEvent>>> {
let limit = if self.first_request_seen {
self.timeouts.read_idle
} else {
self.timeouts.handshake
};
loop {
bounded(self.timeouts.write, "write", self.framed.flush()).await?;
if let Some(events) = self.ready.take() {
return Ok(Some(events));
}
let frame = match tokio::time::timeout(limit, self.framed.next()).await {
Err(_) => return Err(Error::Timeout { what: "read" }),
Ok(None) => return Ok(None),
Ok(Some(frame)) => frame?,
};
self.first_request_seen = true;
match frame {
ServerFrame::Media { channel, data } => {
return Ok(Some(vec![ServerEvent::MediaData { channel, data }]));
}
ServerFrame::Request(bytes) => {
let (response, events) = self.session.handle_request(&bytes)?;
self.framed.write_buffer_mut().extend_from_slice(&response);
self.ready = Some(events);
}
}
}
}
pub async fn send_interleaved(&mut self, channel: u8, payload: &[u8]) -> Result<()> {
let frame = crate::interleaved::InterleavedFrame::new(channel, payload.to_vec());
let bytes = frame.to_bytes()?;
bounded(self.timeouts.write, "write", self.framed.send(bytes)).await
}
}
#[cfg(all(test, feature = "tls"))]
mod tls_timeout_tests {
use super::*;
use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
use tokio::io::{DuplexStream, duplex};
fn timeouts() -> RtspTimeouts {
RtspTimeouts::default()
.with_connect(Duration::from_secs(3))
.with_handshake(Duration::from_secs(4))
}
async fn guard<F: std::future::Future>(f: F) -> F::Output {
tokio::time::timeout(Duration::from_secs(60), f)
.await
.expect("the connect/handshake bound did not fire")
}
fn server_config() -> rustls::ServerConfig {
let cert = test_fixtures::read("tls/localhost-cert.der");
let key = test_fixtures::read("tls/localhost-key.der");
rustls::ServerConfig::builder_with_provider(std::sync::Arc::new(
rustls::crypto::aws_lc_rs::default_provider(),
))
.with_safe_default_protocol_versions()
.unwrap()
.with_no_client_auth()
.with_single_cert(
vec![CertificateDer::from(cert)],
PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(key)),
)
.unwrap()
}
#[tokio::test(start_paused = true)]
async fn a_dial_that_never_completes_times_out_at_connect() {
let r = guard(connect_tls_via::<DuplexStream>(
std::future::pending(),
"localhost",
default_tls_client_config(),
ClientSession::new(),
timeouts(),
))
.await;
assert!(
matches!(r, Err(Error::Timeout { what: "connect" })),
"{:?}",
r.err()
);
}
#[tokio::test(start_paused = true)]
async fn a_silent_server_times_out_the_client_tls_handshake() {
let (client_io, _server_io) = duplex(4096);
let r = guard(connect_tls_via(
async { Ok(client_io) },
"localhost",
default_tls_client_config(),
ClientSession::new(),
timeouts(),
))
.await;
assert!(
matches!(r, Err(Error::Timeout { what: "handshake" })),
"{:?}",
r.err()
);
}
#[tokio::test(start_paused = true)]
async fn a_silent_client_times_out_the_server_tls_handshake() {
let (_client_io, server_io) = duplex(4096);
let r = guard(accept_tls_via(server_io, server_config(), timeouts())).await;
assert!(
matches!(r, Err(Error::Timeout { what: "handshake" })),
"{:?}",
r.err()
);
}
}
#[cfg(test)]
mod tests {
use super::os_session_id;
#[test]
fn os_session_id_varies() {
let mut seen = std::collections::HashSet::new();
for _ in 0..100 {
seen.insert(os_session_id());
}
assert!(
seen.len() > 90,
"os_session_id did not vary: {}",
seen.len()
);
}
}