Skip to main content

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}