rvoip_sip/errors.rs
1//! Error and `Result` types for the `rvoip-sip` session layer.
2//!
3//! [`SessionError`] is the crate-wide error enum returned by the public API
4//! surfaces (`Endpoint`, `StreamPeer`, `CallbackPeer`, `UnifiedCoordinator`,
5//! `SessionHandle`). [`Result`] is the `Result<T, SessionError>` alias used
6//! throughout the crate.
7
8use thiserror::Error;
9
10/// Convenience alias for `Result<T, SessionError>` used across the crate's API.
11pub type Result<T> = std::result::Result<T, SessionError>;
12
13/// Errors returned by the `rvoip-sip` session layer.
14#[derive(Debug, Error)]
15pub enum SessionError {
16 /// No session with the given identifier exists in the registry.
17 #[error("Session not found: {0}")]
18 SessionNotFound(String),
19
20 /// A requested state-machine transition is not legal from the current state.
21 #[error("Invalid state transition: {0}")]
22 InvalidTransition(String),
23
24 /// An error originating in the dialog layer (`rvoip-sip-dialog`).
25 #[error("Dialog error: {0}")]
26 DialogError(String),
27
28 /// An error originating in the media layer (`rvoip-media-core`).
29 #[error("Media error: {0}")]
30 MediaError(String),
31
32 /// SIP signalling succeeded but wiring media to the negotiated session failed.
33 #[error("Media integration error: {reason}")]
34 MediaIntegration {
35 /// Human-readable description of the media-integration failure.
36 reason: String,
37 },
38
39 /// SDP offer/answer negotiation failed (no common codec, malformed SDP, etc.).
40 #[error("SDP negotiation failed: {0}")]
41 SDPNegotiationFailed(String),
42
43 /// Invalid or inconsistent configuration supplied to a builder or coordinator.
44 #[error("Configuration error: {0}")]
45 ConfigurationError(String),
46
47 /// Configuration error (legacy alias of [`SessionError::ConfigurationError`]).
48 #[error("Config error: {0}")]
49 ConfigError(String),
50
51 /// An application-supplied argument was malformed or out of range.
52 #[error("Invalid input: {0}")]
53 InvalidInput(String),
54
55 /// An operation did not complete within its allotted time.
56 #[error("Timeout: {0}")]
57 Timeout(String),
58
59 /// A transport-level network error occurred.
60 #[error("Network error: {0}")]
61 NetworkError(String),
62
63 /// A SIP protocol violation was detected.
64 #[error("Protocol error: {0}")]
65 ProtocolError(String),
66
67 /// RFC 3262 — the remote peer did not advertise `Supported: 100rel` on the
68 /// INVITE, so we cannot send a reliable 183 Session Progress. Raised by
69 /// `send_early_media`. Today we fail fast; a future `send_progress(sdp)`
70 /// API could fall back to an unreliable 183.
71 #[error("peer did not advertise 100rel; cannot send reliable 183")]
72 UnreliableProvisionalsNotSupported,
73
74 /// RFC 3261 §22.2 — the server challenged our INVITE with 401/407 but the
75 /// session has no credentials on file. Set credentials via
76 /// `StreamPeerBuilder::with_credentials` (per-peer default) or
77 /// `control.invite(...).with_credentials(...)` (per-call).
78 #[error("server challenged INVITE but no credentials are on file")]
79 MissingCredentialsForInviteAuth,
80
81 /// RFC 3261 §22.2 — the server challenged an outbound request with 401/407
82 /// but the request flow has no credentials on file.
83 #[error("server challenged {method} but no credentials are on file")]
84 MissingCredentialsForRequestAuth {
85 /// SIP method that was challenged.
86 method: rvoip_sip_core::Method,
87 },
88
89 /// RFC 3261 §22.2 — INVITE auth has already been retried once and the
90 /// server challenged again. Prevents loops against a broken server or
91 /// wrong credentials.
92 #[error("INVITE auth retry limit exceeded")]
93 InviteAuthRetryExhausted,
94
95 /// RFC 3261 §22.2 — outbound request auth has already been retried once and
96 /// the server challenged again.
97 #[error("{method} auth retry limit exceeded")]
98 RequestAuthRetryExhausted {
99 /// SIP method whose auth retry limit was exceeded.
100 method: rvoip_sip_core::Method,
101 },
102
103 /// An unexpected internal invariant was violated.
104 #[error("Internal error: {0}")]
105 InternalError(String),
106
107 /// An underlying `std::io` error (transport sockets, file I/O).
108 #[error("IO error: {0}")]
109 IoError(#[from] std::io::Error),
110
111 /// The requested capability is recognized but not yet implemented.
112 #[error("Not implemented: {0}")]
113 NotImplemented(String),
114
115 /// A call transfer (REFER flow) failed.
116 #[error("Transfer failed: {0}")]
117 TransferFailed(String),
118
119 /// Authentication failed or could not be completed.
120 #[error("Authentication error: {0}")]
121 AuthError(String),
122
123 /// A REGISTER flow failed after any supported retry path.
124 #[error("Registration failed: {0}")]
125 RegistrationFailed(String),
126
127 /// A flattened/stringly error from a lower layer that has no dedicated variant.
128 #[error("Other error: {0}")]
129 Other(String),
130
131 /// SIP_API_DESIGN_2 §8 — a builder setter or `with_header` call
132 /// staged a header that violates the per-method policy. The most
133 /// common case is staging a stack-managed name (Call-ID, CSeq,
134 /// Via, Max-Forwards) or a method-shaped name that has a
135 /// dedicated setter (e.g. Authorization -> `with_credentials` / `with_auth`).
136 #[error("header policy violation on {method}: {header} — {reason}")]
137 HeaderPolicy {
138 /// SIP method whose per-method header policy was violated.
139 method: rvoip_sip_core::Method,
140 /// The offending header name.
141 header: rvoip_sip_core::types::headers::HeaderName,
142 /// Why the header was rejected.
143 reason: crate::api::headers::ViolationReason,
144 },
145
146 /// SIP_API_DESIGN_2 §8 — `HeaderPolicy::validate_outbound`
147 /// reported one or more required application-supplied headers
148 /// were missing for the chosen method.
149 #[error("required application header(s) missing for {method}: {names:?}")]
150 MissingRequiredHeader {
151 /// SIP method that requires the missing header(s).
152 method: rvoip_sip_core::Method,
153 /// The required header names that were not supplied.
154 names: Vec<rvoip_sip_core::types::headers::HeaderName>,
155 },
156
157 /// SIP_API_DESIGN_2 §7.3 invariant #5 — a second `.send()` was
158 /// attempted on the same session for a method whose
159 /// `pending_<method>_options` stash slot is still occupied by an
160 /// in-flight prior `.send()`. Wait for the first future to
161 /// complete (or drop cleanly) before starting another of the
162 /// same method.
163 #[error("another {method} is already in flight on this session")]
164 Conflict {
165 /// SIP method whose in-flight `.send()` blocks a second concurrent send.
166 method: rvoip_sip_core::Method,
167 },
168}
169
170impl From<crate::api::headers::HeaderPolicyViolation> for SessionError {
171 fn from(v: crate::api::headers::HeaderPolicyViolation) -> Self {
172 SessionError::HeaderPolicy {
173 method: v.method,
174 header: v.header,
175 reason: v.reason,
176 }
177 }
178}
179
180impl SessionError {
181 /// True if this error means "the session is already gone from the
182 /// registry" — covers both the typed `SessionNotFound` variant and the
183 /// stringly-wrapped `Other("Session not found: …")` form that falls out
184 /// of the `From<Box<dyn Error>>` flatteners above.
185 ///
186 /// Useful for fire-and-forget teardown paths (e.g. `SessionHandle::hangup`)
187 /// that race against a natural call-ended cleanup: if the race is lost,
188 /// the goal is already achieved and the error should be silent.
189 pub fn is_session_gone(&self) -> bool {
190 matches!(self, SessionError::SessionNotFound(_))
191 || matches!(self, SessionError::Other(msg) if msg.starts_with("Session not found"))
192 || matches!(self, SessionError::Other(msg) if msg.starts_with("Session ") && msg.ends_with(" not found"))
193 }
194}
195
196impl From<Box<dyn std::error::Error>> for SessionError {
197 fn from(err: Box<dyn std::error::Error>) -> Self {
198 SessionError::Other(err.to_string())
199 }
200}
201
202impl From<Box<dyn std::error::Error + Send + Sync>> for SessionError {
203 fn from(err: Box<dyn std::error::Error + Send + Sync>) -> Self {
204 SessionError::Other(err.to_string())
205 }
206}
207
208impl From<rvoip_auth_core::AuthError> for SessionError {
209 fn from(err: rvoip_auth_core::AuthError) -> Self {
210 SessionError::AuthError(err.to_string())
211 }
212}