1use crate::Method;
12use crate::state::SessionState;
13
14pub type Result<T> = core::result::Result<T, Error>;
16
17#[non_exhaustive]
19#[derive(Debug, thiserror::Error)]
20pub enum Error {
21 #[error("failed to parse RTSP {what}")]
24 MessageParse {
25 what: MessageKind,
27 #[source]
29 source: Box<dyn std::error::Error + Send + Sync>,
30 },
31
32 #[error("failed to serialize RTSP message")]
34 MessageWrite(#[source] rtsp_types::WriteError),
35
36 #[error("method {method:?} not valid in state {state:?}")]
40 MethodNotValidInState {
41 method: Method,
43 state: SessionState,
45 },
46
47 #[error("response is missing a CSeq header")]
49 MissingCSeq,
50
51 #[error("invalid Transport header: {0}")]
53 TransportParse(String),
54
55 #[error("cannot serialize header: {0}")]
58 HeaderSerialize(String),
59
60 #[error("invalid Session header: {0}")]
62 SessionParse(String),
63
64 #[error("invalid interleaved frame: {0}")]
66 InterleavedFrame(String),
67
68 #[error("authentication failure")]
71 Auth(#[from] broadcast_auth::Error),
72
73 #[error("authentication failure: 401 without WWW-Authenticate")]
76 MissingAuthChallenge,
77
78 #[error("socket IO error during {context}")]
80 Io {
81 context: IoContext,
83 #[source]
85 source: std::io::Error,
86 },
87
88 #[error("timed out waiting for {what}")]
90 Timeout {
91 what: &'static str,
93 },
94
95 #[error("TLS error: invalid server name {server_name:?}")]
98 Tls {
99 server_name: String,
101 #[source]
103 source: Box<dyn std::error::Error + Send + Sync>,
104 },
105
106 #[error("RTSP session not found (454 {method:?}): the session is gone; re-SETUP")]
112 SessionNotFound {
113 method: Method,
115 },
116}
117
118#[non_exhaustive]
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum IoContext {
122 Connect,
124 TlsHandshake,
126 Read,
128 Socket,
130}
131
132impl IoContext {
133 pub fn name(&self) -> &'static str {
135 match self {
136 Self::Connect => "connect",
137 Self::TlsHandshake => "TLS handshake",
138 Self::Read => "read",
139 Self::Socket => "socket",
140 }
141 }
142}
143
144impl core::fmt::Display for IoContext {
145 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
146 f.write_str(self.name())
147 }
148}
149
150#[non_exhaustive]
152#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153pub enum MessageKind {
154 Request,
156 Response,
158 Framing,
160}
161
162impl MessageKind {
163 pub fn name(&self) -> &'static str {
165 match self {
166 Self::Request => "request",
167 Self::Response => "response",
168 Self::Framing => "message framing",
169 }
170 }
171}
172
173impl core::fmt::Display for MessageKind {
174 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
175 f.write_str(self.name())
176 }
177}
178
179impl From<std::io::Error> for Error {
180 fn from(e: std::io::Error) -> Self {
181 Error::Io {
182 context: IoContext::Socket,
183 source: e,
184 }
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191 use std::error::Error as _;
192
193 #[test]
194 fn io_keeps_its_source_and_kind() {
195 let err = Error::from(std::io::Error::from(std::io::ErrorKind::ConnectionReset));
196 let src = err
197 .source()
198 .and_then(|s| s.downcast_ref::<std::io::Error>());
199 assert_eq!(
200 src.map(std::io::Error::kind),
201 Some(std::io::ErrorKind::ConnectionReset)
202 );
203 }
204
205 #[test]
206 fn message_parse_keeps_the_codec_error() {
207 let err = Error::MessageParse {
208 what: MessageKind::Response,
209 source: Box::new(rtsp_types::ParseError::Error),
210 };
211 assert!(
212 err.source()
213 .is_some_and(|s| s.downcast_ref::<rtsp_types::ParseError>().is_some())
214 );
215 }
216
217 #[test]
218 fn message_write_keeps_the_codec_error() {
219 let io = std::io::Error::from(std::io::ErrorKind::BrokenPipe);
220 let err = Error::MessageWrite(rtsp_types::WriteError::IoError(io));
221 assert!(
222 err.source()
223 .is_some_and(|s| s.downcast_ref::<rtsp_types::WriteError>().is_some())
224 );
225 }
226
227 fn chain(err: &Error) -> Vec<String> {
228 let mut out = vec![err.to_string()];
229 let mut cur = err.source();
230 while let Some(e) = cur {
231 out.push(e.to_string());
232 cur = e.source();
233 }
234 out
235 }
236
237 #[test]
238 fn no_message_in_a_source_chain_repeats_an_inner_one() {
239 let io = || std::io::Error::new(std::io::ErrorKind::BrokenPipe, "pipe gone");
240 let errors = [
241 Error::MessageParse {
242 what: MessageKind::Request,
243 source: Box::new(rtsp_types::ParseError::Error),
244 },
245 Error::MessageWrite(rtsp_types::WriteError::IoError(io())),
246 Error::Io {
247 context: IoContext::Connect,
248 source: io(),
249 },
250 Error::from(io()),
251 Error::Tls {
252 server_name: "bad name".into(),
253 source: Box::new(io()),
254 },
255 Error::Auth(broadcast_auth::Error::ChallengeParse("bad".into())),
256 ];
257 for err in &errors {
258 let msgs = chain(err);
259 let (own, inner) = msgs.split_first().unwrap();
260 for message in inner {
261 assert!(
262 !own.contains(message.as_str()),
263 "{own:?} repeats {message:?}"
264 );
265 }
266 }
267 }
268
269 #[test]
270 fn contexts_name_what_was_being_done() {
271 let tls = Error::Tls {
272 server_name: "bad name".into(),
273 source: Box::new(std::io::Error::other("x")),
274 };
275 assert!(tls.to_string().contains("bad name"));
276 let parse = Error::MessageParse {
277 what: MessageKind::Request,
278 source: Box::new(rtsp_types::ParseError::Error),
279 };
280 assert!(parse.to_string().contains("request"));
281 assert_eq!(IoContext::TlsHandshake.to_string(), "TLS handshake");
282 }
283}