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