sipx_sip/error.rs
1//! Error types.
2//!
3//! Every rejection names what was wrong. A single opaque `Invalid` variant is not good
4//! enough: the transaction layer chooses between 400, 413 and 505 based on which fault this
5//! was, and an operator reading a log needs to know which byte offended.
6
7use thiserror::Error;
8
9/// A message that could not be built.
10///
11/// Every one of these means a caller tried to put something into a message that would have
12/// changed its structure — the header-injection family. They are errors rather than silent
13/// escaping because a caller that supplies a CRLF in a display name has a bug, and hiding it
14/// helps nobody.
15#[derive(Debug, Clone, PartialEq, Eq, Error)]
16#[non_exhaustive]
17pub enum BuildError {
18 /// A response cannot be routed or correlated because the request omitted a header every
19 /// response must copy (RFC 3261 §8.2.6.1 and §8.2.6.2).
20 #[error("request is missing required response header {header}")]
21 MissingRequiredResponseHeader {
22 /// The missing header's canonical name.
23 header: &'static str,
24 },
25 /// A character that would end a line or terminate a string, in a field that must not
26 /// contain one.
27 #[error("illegal byte {byte:#04x} at offset {offset} in {field}")]
28 IllegalCharacter {
29 /// Which field.
30 field: &'static str,
31 /// Where in the value.
32 offset: usize,
33 /// The offending byte.
34 byte: u8,
35 },
36 /// A field that must be a single token is not one.
37 #[error("{field} is not a token")]
38 NotAToken {
39 /// Which field.
40 field: &'static str,
41 },
42}
43
44/// A message that could not be framed or whose structure is malformed.
45///
46/// Structural only: a message whose *headers* are bad still parses (see [`HeaderError`]).
47/// The transaction layer maps these onto a response status, which is why each variant says
48/// what went wrong rather than merely that something did.
49#[derive(Debug, Clone, PartialEq, Eq, Error)]
50#[non_exhaustive]
51pub enum ParseError {
52 /// The request or status line is malformed. Answer 400.
53 #[error("malformed start line: {0}")]
54 StartLine(#[from] StartLineError),
55 /// A header field line is malformed. Answer 400.
56 #[error("malformed header field on line {line}: {kind}")]
57 HeaderSyntax {
58 /// Which line of the header section, counting the start line as line 1.
59 line: usize,
60 /// What was wrong with it.
61 kind: HeaderSyntaxError,
62 },
63 /// The message body cannot be delimited. Answer 400; on a stream transport the framing
64 /// is unrecoverable and the connection must be closed.
65 #[error("cannot frame message body: {0}")]
66 Framing(#[from] FramingError),
67 /// A configured limit was exceeded. Answer 413 for body limits.
68 #[error("{limit} limit exceeded ({value})")]
69 Limit {
70 /// Which limit.
71 limit: LimitKind,
72 /// The value that exceeded it.
73 value: usize,
74 },
75 /// Not enough bytes yet. Only ever returned internally by the stream parser; callers see
76 /// it as "no message completed".
77 #[error("incomplete message")]
78 Incomplete,
79}
80
81/// What was wrong with a start line.
82#[derive(Debug, Clone, PartialEq, Eq, Error)]
83#[non_exhaustive]
84pub enum StartLineError {
85 /// The message is empty, or the start line is.
86 #[error("empty")]
87 Empty,
88 /// A request line did not have exactly three space-separated elements. Covers multiple
89 /// spaces between elements and a trailing space (RFC 4475 §3.1.2.9, §3.1.2.10).
90 #[error("a request line must have exactly three space-separated elements")]
91 RequestLineShape,
92 /// The method is not a token.
93 #[error("method is not a token")]
94 Method,
95 /// The Request-URI did not parse — including when it is wrapped in `<>`
96 /// (RFC 4475 §3.1.2.7) or contains whitespace (§3.1.2.8).
97 #[error("bad Request-URI: {0}")]
98 Uri(#[from] UriError),
99 /// A status line had no status code.
100 #[error("status line has no status code")]
101 MissingStatusCode,
102 /// The status code is not exactly three digits in `100..=699` (RFC 4475 §3.1.2.19).
103 #[error("status code is not three digits in 100..=699")]
104 StatusCode,
105}
106
107/// What was wrong with a header field line.
108#[derive(Debug, Clone, PartialEq, Eq, Error)]
109#[non_exhaustive]
110pub enum HeaderSyntaxError {
111 /// No colon separating name from value.
112 #[error("no colon")]
113 MissingColon,
114 /// The field name is empty.
115 #[error("empty field name")]
116 EmptyName,
117 /// The field name contains a character outside the `token` set.
118 #[error("field name is not a token")]
119 NameNotToken,
120 /// A bare CR or LF where a CRLF was required.
121 ///
122 /// sipx never accepts a bare LF as a line terminator: two elements disagreeing about
123 /// where a message ends is how a body becomes a second request.
124 #[error("bare CR or LF")]
125 BareNewline,
126 /// The first line of the header section begins with whitespace, so it continues a header
127 /// that does not exist.
128 #[error("header section begins with a continuation line")]
129 LeadingFold,
130}
131
132/// Why a body could not be delimited.
133#[derive(Debug, Clone, PartialEq, Eq, Error)]
134#[non_exhaustive]
135pub enum FramingError {
136 /// No blank line terminating the header section.
137 #[error("no blank line after headers")]
138 NoHeaderTerminator,
139 /// More than one `Content-Length`. Rejected even when the values agree: two elements
140 /// having computed the same length is not worth a second code path (RFC 4475 §3.3.9).
141 #[error("repeated Content-Length")]
142 ContentLengthRepeated,
143 /// `Content-Length` is empty, signed, or not a decimal number. Never converted to a
144 /// number that could be negative, and never used as a length (RFC 4475 §3.1.2.3).
145 #[error("Content-Length is not a decimal number")]
146 ContentLengthMalformed,
147 /// `Content-Length` is larger than the octets actually present (RFC 4475 §3.1.2.2).
148 #[error("Content-Length exceeds the octets present")]
149 BodyTruncated,
150 /// A stream transport requires `Content-Length`; without it the stream cannot be cut into
151 /// messages (RFC 3261 §20.14).
152 #[error("Content-Length is required on stream transports")]
153 ContentLengthRequired,
154}
155
156/// Which limit was exceeded.
157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158pub enum LimitKind {
159 /// Total message size.
160 MessageBytes,
161 /// Declared or actual body size.
162 BodyBytes,
163 /// Number of header fields.
164 Headers,
165 /// Size of a single header field.
166 HeaderBytes,
167 /// Number of continuation lines in one header field.
168 FoldingLines,
169}
170
171impl std::fmt::Display for LimitKind {
172 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173 let name = match self {
174 Self::MessageBytes => "message size",
175 Self::BodyBytes => "body size",
176 Self::Headers => "header count",
177 Self::HeaderBytes => "header size",
178 Self::FoldingLines => "folding line count",
179 };
180 f.write_str(name)
181 }
182}
183
184/// A URI that could not be parsed.
185#[derive(Debug, Clone, PartialEq, Eq, Error)]
186#[non_exhaustive]
187pub enum UriError {
188 /// No scheme, or a scheme that is not a token followed by `:`.
189 #[error("missing or malformed URI scheme")]
190 Scheme,
191 /// A `sip:` or `sips:` URI with no host.
192 #[error("URI has no host")]
193 EmptyHost,
194 /// The host contains a character no host may contain.
195 #[error("invalid character in host")]
196 Host,
197 /// The port is not one to five digits, or exceeds 65535.
198 #[error("invalid port")]
199 Port,
200 /// An IPv6 reference missing its closing bracket.
201 #[error("unterminated IPv6 reference")]
202 Ipv6Reference,
203 /// A character illegal anywhere in a URI: whitespace, a control character, or one of
204 /// `<`, `>`, `"`.
205 #[error("illegal character in URI")]
206 IllegalCharacter,
207 /// A `%` not followed by two hex digits.
208 #[error("malformed percent escape")]
209 PercentEscape,
210 /// A SIP user part is empty or contains a byte outside RFC 3261's `user` production.
211 #[error("invalid SIP URI user part")]
212 User,
213 /// A parsed or replacement `tel:` subscriber is empty or falls outside RFC 3966's global and
214 /// local telephone-subscriber productions.
215 #[error("invalid tel URI telephone-subscriber")]
216 TelephoneSubscriber,
217 /// A parsed message's retained URI span no longer points inside its retained wire bytes.
218 #[error("retained URI span is inconsistent with its wire representation")]
219 RetainedSpan,
220 /// A parameter or header with an empty name.
221 #[error("empty parameter name")]
222 EmptyParameterName,
223 /// A uri-parameter name that appears more than once. RFC 3261 §19.1.1: "any given
224 /// parameter-name MUST NOT appear more than once." URI headers may repeat; only the
225 /// `;` list is policed.
226 #[error("repeated uri-parameter name")]
227 DuplicateParameterName,
228}
229
230/// A header whose value could not be parsed, or whose value is out of range.
231///
232/// Distinct from a parse error: the message framed correctly and this one header is bad. A
233/// proxy may still forward such a message; only a party that needs to *read* the header has
234/// a problem.
235#[derive(Debug, Clone, PartialEq, Eq, Error)]
236#[non_exhaustive]
237pub enum HeaderError {
238 /// The value does not match the header's grammar.
239 #[error("malformed {header} header")]
240 Syntax {
241 /// The header that failed to parse.
242 header: &'static str,
243 },
244 /// The value parses but falls outside the range the RFC permits — a `CSeq` above
245 /// 2^31-1, a `Max-Forwards` above 255, a status code outside 100..=699.
246 #[error("{header} value out of range")]
247 OutOfRange {
248 /// The header whose value was out of range.
249 header: &'static str,
250 },
251 /// A URI inside the header did not parse.
252 #[error("invalid URI in {header} header: {source}")]
253 Uri {
254 /// The header carrying the URI.
255 header: &'static str,
256 /// Why the URI was rejected.
257 #[source]
258 source: UriError,
259 },
260 /// A quoted string with no closing quote.
261 #[error("unterminated quoted string in {header} header")]
262 UnterminatedQuotedString {
263 /// The header carrying the unterminated string.
264 header: &'static str,
265 },
266}
267
268/// Why a parser-owned address value could not be edited losslessly.
269#[derive(Debug, Clone, PartialEq, Eq, Error)]
270#[non_exhaustive]
271pub enum AddressEditError {
272 /// The field does not use one of the address grammars exposed by this operation.
273 #[error("header does not have a supported address grammar")]
274 UnsupportedHeader,
275 /// At least one row did not match the field's shared address grammar.
276 #[error("malformed address field: {0}")]
277 Malformed(#[source] HeaderError),
278 /// The flattened, zero-based value index does not exist.
279 #[error("address value index {index} is out of range")]
280 IndexOutOfRange {
281 /// The requested flattened value index.
282 index: usize,
283 },
284 /// The replacement URI did not survive serialization as a valid URI.
285 #[error("invalid replacement URI: {0}")]
286 InvalidUri(#[source] UriError),
287 /// A replacement display name contains a header-delimiting control byte.
288 #[error("replacement display name contains a control byte")]
289 InvalidDisplayName,
290}
291
292/// Why a parser-owned Warning agent could not be edited losslessly.
293#[derive(Debug, Clone, PartialEq, Eq, Error)]
294#[non_exhaustive]
295pub enum WarningEditError {
296 /// At least one row did not match the complete Warning field grammar.
297 #[error("malformed Warning field: {0}")]
298 Malformed(#[source] HeaderError),
299 /// The flattened, zero-based Warning value index does not exist.
300 #[error("Warning value index {index} is out of range")]
301 IndexOutOfRange {
302 /// The requested flattened value index.
303 index: usize,
304 },
305 /// The replacement is empty or is not one RFC 3261 token.
306 #[error("replacement Warning pseudonym is not a token")]
307 InvalidPseudonym,
308}