sipx_call/error.rs
1//! Call errors.
2
3use sipx_sip::{HeaderName, Method};
4use thiserror::Error;
5
6/// What can go wrong establishing or running a call.
7#[derive(Debug, Error)]
8#[non_exhaustive]
9pub enum Error {
10 /// A socket failed.
11 #[error("io: {0}")]
12 Io(#[source] std::io::Error),
13 /// Negotiated media could not be constructed safely.
14 #[error("media: {0}")]
15 Media(#[from] sipx_media::SetupError),
16 /// The transport failed.
17 #[error("transport: {0}")]
18 Transport(#[from] sipx_transport::Error),
19 /// A message could not be built.
20 #[error("build: {0}")]
21 Build(#[from] sipx_sip::error::BuildError),
22 /// A selected authentication service could not attest the outbound caller identity.
23 #[error("caller identity: {0}")]
24 IdentityAuthentication(#[from] sipx_ua::identity::AuthenticationError),
25 /// The SDP could not be read.
26 #[error("sdp: {0}")]
27 Sdp(String),
28 /// A named browser-audio policy boundary refused setup or renegotiation.
29 #[error("browser-audio profile: {0}")]
30 Profile(#[from] sipx_sdp::browser_audio::ProfileError),
31 /// SDP was asked to advertise an unspecified address which no peer can reach.
32 #[error("the advertised media address must not be unspecified")]
33 UnspecifiedMediaAddress,
34 /// DTLS-SRTP was selected in a build that does not contain its handshake implementation.
35 #[error("DTLS-SRTP was selected, but sipx-call was built without its `dtls` feature")]
36 DtlsUnavailable,
37 /// A selected DTLS-SRTP media path could not be keyed.
38 #[error("DTLS-SRTP: {0}")]
39 Dtls(String),
40 /// The DTLS setup exchange selected no role this endpoint can hold.
41 #[error("DTLS setup: {0}")]
42 DtlsSetup(#[from] sipx_sdp::fingerprint::SetupRoleError),
43 /// DTLS-SRTP was selected through an early-dialog API that cannot yet preserve its ordering.
44 #[error("DTLS-SRTP is not available for early media")]
45 DtlsEarlyMedia,
46 /// A running DTLS-SRTP call was asked to renegotiate without a rekeying exchange.
47 #[error("DTLS-SRTP renegotiation is not implemented; the existing call is unchanged")]
48 DtlsRenegotiation,
49 /// An in-dialog description tried to change which sockets carry RTCP.
50 #[error(
51 "RTCP mode change from {current:?} to {proposed:?} is not implemented; the existing call is unchanged"
52 )]
53 RtcpModeChange {
54 /// The mode owned by the running media session.
55 current: sipx_sdp::RtcpMode,
56 /// The mode the new offer/answer exchange selected.
57 proposed: sipx_sdp::RtcpMode,
58 },
59 /// The INVITE got no final response.
60 #[error("no final response to the INVITE")]
61 NoResponse,
62 /// The far end refused.
63 #[error("rejected: {status} {reason}")]
64 Rejected {
65 /// The status code.
66 status: u16,
67 /// Its reason phrase.
68 reason: String,
69 },
70 /// A supported digest challenge returned by an INVITE attempt.
71 ///
72 /// [`crate::dial`] and [`crate::dial_once`] consume this internally when credentials were
73 /// supplied. It can surface from [`crate::dial_early`], whose handle names one INVITE and
74 /// therefore does not silently replace it with a retry.
75 #[error("authentication required: {status} {reason}")]
76 AuthenticationChallenge {
77 /// The 401 or 407 status.
78 status: u16,
79 /// Its reason phrase.
80 reason: String,
81 /// The strongest supported challenge the response offered.
82 challenge: Box<sipx_sip::auth::Challenge>,
83 },
84 /// A 2xx that established no dialog — no `To` tag, or no `Contact` to send to.
85 #[error("the response established no dialog")]
86 NoDialog,
87 /// The caller gave up before the far end answered, and cancelled the invitation.
88 ///
89 /// Distinct from a rejection: nobody refused the call, we stopped waiting for it.
90 #[error("no answer within {0:?}; the invitation was cancelled")]
91 Cancelled(std::time::Duration),
92 /// An invitation was answered after the caller had already withdrawn it (RFC 3261 §9.2).
93 ///
94 /// The other side of [`Self::Cancelled`]: there, *this* stack gave up on an INVITE it sent;
95 /// here, the far end gave up on one it sent us, the invitation was answered `487 Request
96 /// Terminated`, and there is nothing left to accept. Answering anyway would put a `200` on a
97 /// transaction that has already finished and leave this side holding a call the caller does
98 /// not have.
99 #[error("the invitation was cancelled by the caller")]
100 InvitationCancelled,
101 /// A caller-selected signalling-dialog tag was empty, overlong or not a SIP token.
102 ///
103 /// The rejected value is deliberately absent: reflecting arbitrary application or fixture
104 /// bytes in an error is unnecessary and makes it too easy to copy them into a log.
105 #[error("the signalling dialog tag must be a non-empty SIP token of at most 128 bytes")]
106 InvalidDialogTag,
107 /// An originated BYE did not receive a final response inside its caller-owned failure bound.
108 #[error("no final response to the dialog BYE within {0:?}")]
109 SignallingTeardownTimeout(std::time::Duration),
110 /// A final response to an originated BYE did not name that dialog and `CSeq`.
111 ///
112 /// The mismatching values are intentionally absent so an untrusted packet is not reflected
113 /// into logs by displaying this error.
114 #[error("the dialog BYE response did not match its dialog and CSeq")]
115 InvalidDialogResponse,
116 /// An INVITE asked to replace a dialog it did not name, or named one this is not.
117 ///
118 /// Deliberately one error for both. Telling a caller "the Call-ID matched but the tags did
119 /// not" would be telling them how far their guess got.
120 #[error("the Replaces header names no dialog we have")]
121 NoReplaces,
122 /// A transfer was accepted or refused when none had been asked for.
123 #[error("no transfer has been requested on this call")]
124 NoReferral,
125 /// Negotiation produced no usable audio stream.
126 #[error("no codec in common")]
127 NoCommonCodec,
128 /// A `422` refused the session interval we asked for (RFC 4028 §6), naming its own minimum.
129 ///
130 /// Carries the minimum rather than folding into [`Self::Rejected`] because the whole point
131 /// of a 422 is that it is retryable, and only the value it carries makes the retry possible.
132 #[error("the far end requires a session interval of at least {0:?}")]
133 IntervalTooBrief(std::time::Duration),
134 /// The far end never refreshed the session, so it was torn down locally (RFC 4028 §10).
135 #[error("the session expired without a refresh")]
136 SessionExpired,
137 /// A 2xx was asked for while a reliable provisional carrying SDP is unacknowledged.
138 ///
139 /// RFC 3262 §5 makes the delay a MUST, and this is where it is enforced rather than
140 /// silently deferred: a description sent in a provisional that never arrived, followed by a
141 /// 200 that carries none, leaves the caller in a confirmed dialog with no answer at all.
142 /// Keep feeding messages to [`Ringing::on_prack`](crate::Ringing::on_prack) and try again.
143 #[error("the reliable provisional carrying the answer has not been acknowledged")]
144 UnacknowledgedProvisional,
145 /// An invitation was treated as having an early session when it never had one.
146 ///
147 /// On the answering side: either it was rung with `ring` rather than `ring_early`, or it has
148 /// already been answered — a `Ringing` hands its media port and its dialog over exactly once.
149 ///
150 /// On the calling side, from [`Dialing::update`](crate::Dialing::update): the far end has
151 /// established a dialog but has not answered our offer in a reliable provisional, so RFC
152 /// 3311 §5.1 does not yet allow an UPDATE to carry one.
153 /// [`Dialing::has_early_session`](crate::Dialing::has_early_session) is the same question
154 /// asked in advance.
155 #[error("this invitation has no early session to answer")]
156 NoEarlySession,
157 /// A stack-owned or unadmitted method was passed to the application-owned dialog API.
158 #[error("{0} is not an application-owned method on this dialog")]
159 StackOwnedDialogMethod(Method),
160 /// Application content exceeded the retained request-body bound.
161 #[error("application-owned body is {actual} octets; the limit is {limit}")]
162 ApplicationBodyTooLarge {
163 /// Observed body length.
164 actual: usize,
165 /// Maximum retained body length.
166 limit: usize,
167 },
168 /// A non-empty application-owned body did not name its media type.
169 #[error("a non-empty application-owned body requires Content-Type")]
170 ApplicationContentTypeRequired,
171 /// An application tried to supply a dialog- or transaction-owned header.
172 #[error("the stack owns the {0:?} header on in-dialog requests")]
173 ProtectedApplicationHeader(HeaderName),
174 /// An application response must end the transaction rather than being provisional.
175 #[error("application response status {0} is provisional; a final response is required")]
176 ApplicationFinalResponseRequired(u16),
177 /// The request's exactly-once response capability was already claimed.
178 #[error("the application-owned request has already been answered")]
179 ApplicationResponseAlreadySent,
180 /// A response capability could not capture the runtime that owns its transport work.
181 #[error("an application response requires an active Tokio runtime")]
182 ApplicationRuntimeUnavailable,
183 /// An operation requiring a live dialog was attempted after call teardown.
184 #[error("the dialog has ended")]
185 DialogEnded,
186}
187
188/// A call result.
189pub type Result<T> = std::result::Result<T, Error>;