use crate::Method;
use crate::state::SessionState;
pub type Result<T> = core::result::Result<T, Error>;
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("failed to parse RTSP {what}")]
MessageParse {
what: MessageKind,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("failed to serialize RTSP message")]
MessageWrite(#[source] rtsp_types::WriteError),
#[error("method {method:?} not valid in state {state:?}")]
MethodNotValidInState {
method: Method,
state: SessionState,
},
#[error("response is missing a CSeq header")]
MissingCSeq,
#[error("invalid Transport header: {0}")]
TransportParse(String),
#[error("cannot serialize header: {0}")]
HeaderSerialize(String),
#[error("invalid Session header: {0}")]
SessionParse(String),
#[error("invalid interleaved frame: {0}")]
InterleavedFrame(String),
#[error("authentication failure")]
Auth(#[from] broadcast_auth::Error),
#[error("authentication failure: 401 without WWW-Authenticate")]
MissingAuthChallenge,
#[error("socket IO error during {context}")]
Io {
context: IoContext,
#[source]
source: std::io::Error,
},
#[error("timed out waiting for {what}")]
Timeout {
what: &'static str,
},
#[error("TLS error: invalid server name {server_name:?}")]
Tls {
server_name: String,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("RTSP session not found (454 {method:?}): the session is gone; re-SETUP")]
SessionNotFound {
method: Method,
},
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IoContext {
Connect,
TlsHandshake,
Read,
Socket,
}
impl IoContext {
pub fn name(&self) -> &'static str {
match self {
Self::Connect => "connect",
Self::TlsHandshake => "TLS handshake",
Self::Read => "read",
Self::Socket => "socket",
}
}
}
impl core::fmt::Display for IoContext {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.name())
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MessageKind {
Request,
Response,
Framing,
}
impl MessageKind {
pub fn name(&self) -> &'static str {
match self {
Self::Request => "request",
Self::Response => "response",
Self::Framing => "message framing",
}
}
}
impl core::fmt::Display for MessageKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.name())
}
}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Error::Io {
context: IoContext::Socket,
source: e,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error as _;
#[test]
fn io_keeps_its_source_and_kind() {
let err = Error::from(std::io::Error::from(std::io::ErrorKind::ConnectionReset));
let src = err
.source()
.and_then(|s| s.downcast_ref::<std::io::Error>());
assert_eq!(
src.map(std::io::Error::kind),
Some(std::io::ErrorKind::ConnectionReset)
);
}
#[test]
fn message_parse_keeps_the_codec_error() {
let err = Error::MessageParse {
what: MessageKind::Response,
source: Box::new(rtsp_types::ParseError::Error),
};
assert!(
err.source()
.is_some_and(|s| s.downcast_ref::<rtsp_types::ParseError>().is_some())
);
}
#[test]
fn message_write_keeps_the_codec_error() {
let io = std::io::Error::from(std::io::ErrorKind::BrokenPipe);
let err = Error::MessageWrite(rtsp_types::WriteError::IoError(io));
assert!(
err.source()
.is_some_and(|s| s.downcast_ref::<rtsp_types::WriteError>().is_some())
);
}
fn chain(err: &Error) -> Vec<String> {
let mut out = vec![err.to_string()];
let mut cur = err.source();
while let Some(e) = cur {
out.push(e.to_string());
cur = e.source();
}
out
}
#[test]
fn no_message_in_a_source_chain_repeats_an_inner_one() {
let io = || std::io::Error::new(std::io::ErrorKind::BrokenPipe, "pipe gone");
let errors = [
Error::MessageParse {
what: MessageKind::Request,
source: Box::new(rtsp_types::ParseError::Error),
},
Error::MessageWrite(rtsp_types::WriteError::IoError(io())),
Error::Io {
context: IoContext::Connect,
source: io(),
},
Error::from(io()),
Error::Tls {
server_name: "bad name".into(),
source: Box::new(io()),
},
Error::Auth(broadcast_auth::Error::ChallengeParse("bad".into())),
];
for err in &errors {
let msgs = chain(err);
let (own, inner) = msgs.split_first().unwrap();
for message in inner {
assert!(
!own.contains(message.as_str()),
"{own:?} repeats {message:?}"
);
}
}
}
#[test]
fn contexts_name_what_was_being_done() {
let tls = Error::Tls {
server_name: "bad name".into(),
source: Box::new(std::io::Error::other("x")),
};
assert!(tls.to_string().contains("bad name"));
let parse = Error::MessageParse {
what: MessageKind::Request,
source: Box::new(rtsp_types::ParseError::Error),
};
assert!(parse.to_string().contains("request"));
assert_eq!(IoContext::TlsHandshake.to_string(), "TLS handshake");
}
}