Skip to main content

broadcast_common/
error.rs

1//! The common failure kinds of the workspace, defined once.
2//!
3//! Every parser/serializer crate keeps its own `Error` enum for failures that
4//! are specific to its format, and carries each kind below as a single
5//! wrapping variant:
6//!
7//! ```ignore
8//! #[error(transparent)]
9//! BufferTooShort(#[from] broadcast_common::error::BufferTooShort),
10//! ```
11//!
12//! so the struct, its `Display` message and its field names exist once (the
13//! `http` / `aws-smithy-types` pattern: a foundation crate owns the shared
14//! types, family crates wrap them) and `?` converts without hand-written
15//! `From` impls.
16//!
17//! # Field naming
18//!
19//! `what` names the structure or unit being parsed, written or compared
20//! (`"ProgramStreamMap"`, `"SNDU"`); `field` names one spec field inside it
21//! (`"section_length"`). A kind that can only concern a field uses `field`;
22//! one that can concern a whole structure uses `what`.
23//!
24//! # Shape
25//!
26//! Every kind is `Debug + Clone + Copy + PartialEq + Eq`, holds only plain
27//! data and implements `core::error::Error`, so it works in `no_std`. None is
28//! `#[non_exhaustive]`: the fields are public so a parser constructs the value
29//! with a struct literal, and a future field is a deliberate breaking change.
30//!
31//! Kinds whose meaning differs per crate (unknown tag, invalid length, UTF-8,
32//! unexpected table id) are deliberately absent.
33
34use core::fmt;
35
36/// An input buffer held fewer bytes than the structure being parsed needs.
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct BufferTooShort {
39    /// Bytes required to proceed.
40    pub need: usize,
41    /// Bytes actually available.
42    pub have: usize,
43    /// Name of the type or field being parsed.
44    pub what: &'static str,
45}
46
47impl fmt::Display for BufferTooShort {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        write!(
50            f,
51            "buffer too short: need {} bytes, have {} (while parsing {})",
52            self.need, self.have, self.what
53        )
54    }
55}
56
57impl core::error::Error for BufferTooShort {}
58
59/// An output buffer was smaller than the serialized form requires.
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub struct OutputBufferTooSmall {
62    /// Required size.
63    pub need: usize,
64    /// Actual size.
65    pub have: usize,
66}
67
68impl fmt::Display for OutputBufferTooSmall {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        write!(
71            f,
72            "output buffer too small: need {}, have {}",
73            self.need, self.have
74        )
75    }
76}
77
78impl core::error::Error for OutputBufferTooSmall {}
79
80/// A value did not fit the wire field it is written to.
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub struct FieldOverflow {
83    /// Spec name of the field, e.g. "section_length".
84    pub field: &'static str,
85    /// The value that did not fit.
86    pub value: u64,
87    /// The largest value the field can hold.
88    pub max: u64,
89}
90
91impl fmt::Display for FieldOverflow {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        write!(
94            f,
95            "{} = {} exceeds the field maximum {}",
96            self.field, self.value, self.max
97        )
98    }
99}
100
101impl core::error::Error for FieldOverflow {}
102
103/// A CRC-32 carried on the wire disagrees with the one computed over the data.
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub struct CrcMismatch {
106    /// The structure the CRC protects, e.g. "PSM", "SNDU", "section".
107    pub what: &'static str,
108    /// The CRC computed over the received bytes.
109    pub computed: u32,
110    /// The CRC carried in the stream.
111    pub expected: u32,
112}
113
114impl fmt::Display for CrcMismatch {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        write!(
117            f,
118            "{} CRC-32 mismatch: computed {:#010x}, expected {:#010x}",
119            self.what, self.computed, self.expected
120        )
121    }
122}
123
124impl core::error::Error for CrcMismatch {}
125
126/// A fixed marker (TS sync byte, burst sync word, start code) did not match.
127///
128/// `expected` and `found` are widened to `u32` so one kind covers an 8-bit
129/// sync byte, a 16-bit sync word and a 24/32-bit start code.
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub struct InvalidSync {
132    /// The marker's name, e.g. "TS sync byte", "Pa sync word".
133    pub what: &'static str,
134    /// The value the spec fixes.
135    pub expected: u32,
136    /// The value read.
137    pub found: u32,
138}
139
140impl fmt::Display for InvalidSync {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        write!(
143            f,
144            "invalid {}: expected {:#x}, got {:#x}",
145            self.what, self.expected, self.found
146        )
147    }
148}
149
150impl core::error::Error for InvalidSync {}
151
152/// A reserved field did not hold the value the spec requires (all ones, or
153/// zero).
154#[derive(Debug, Clone, Copy, PartialEq, Eq)]
155pub struct ReservedBits {
156    /// Spec name of the reserved field.
157    pub field: &'static str,
158    /// The value the spec requires.
159    pub expected: u64,
160    /// The value read.
161    pub found: u64,
162}
163
164impl fmt::Display for ReservedBits {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        write!(
167            f,
168            "reserved field {} must be {:#x}, found {:#x}",
169            self.field, self.expected, self.found
170        )
171    }
172}
173
174impl core::error::Error for ReservedBits {}
175
176/// A field held a value the spec does not allow.
177#[derive(Debug, Clone, Copy, PartialEq, Eq)]
178pub struct InvalidValue {
179    /// Spec name of the field.
180    pub field: &'static str,
181    /// The value read, widened to `u64`.
182    pub value: u64,
183    /// Why the value is rejected.
184    pub reason: &'static str,
185}
186
187impl fmt::Display for InvalidValue {
188    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189        write!(
190            f,
191            "field {} value {} invalid: {}",
192            self.field, self.value, self.reason
193        )
194    }
195}
196
197impl core::error::Error for InvalidValue {}
198
199/// A field is structurally invalid and has no single offending number (a bad
200/// combination, a missing mandatory part).
201#[derive(Debug, Clone, Copy, PartialEq, Eq)]
202pub struct InvalidField {
203    /// Spec name of the field.
204    pub field: &'static str,
205    /// Why the field is rejected.
206    pub reason: &'static str,
207}
208
209impl fmt::Display for InvalidField {
210    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211        write!(f, "invalid field {}: {}", self.field, self.reason)
212    }
213}
214
215impl core::error::Error for InvalidField {}
216
217/// A format version number this crate does not support.
218#[derive(Debug, Clone, Copy, PartialEq, Eq)]
219pub struct UnsupportedVersion {
220    /// The structure carrying the version, e.g. "RTP", "emsg".
221    pub what: &'static str,
222    /// The version read.
223    pub found: u8,
224}
225
226impl fmt::Display for UnsupportedVersion {
227    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228        write!(f, "unsupported {} version {}", self.what, self.found)
229    }
230}
231
232impl core::error::Error for UnsupportedVersion {}
233
234/// A length field disagrees with the number of bytes actually present or
235/// computed.
236#[derive(Debug, Clone, Copy, PartialEq, Eq)]
237pub struct LengthMismatch {
238    /// The structure whose length is checked.
239    pub what: &'static str,
240    /// The length the header declares.
241    pub declared: usize,
242    /// The length actually found or computed.
243    pub actual: usize,
244}
245
246impl fmt::Display for LengthMismatch {
247    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248        write!(
249            f,
250            "length mismatch for {}: declared {}, actual {}",
251            self.what, self.declared, self.actual
252        )
253    }
254}
255
256impl core::error::Error for LengthMismatch {}
257
258#[cfg(test)]
259mod tests {
260    extern crate alloc;
261    use super::*;
262    use alloc::string::ToString;
263
264    #[test]
265    fn display_messages() {
266        assert_eq!(
267            BufferTooShort {
268                need: 4,
269                have: 1,
270                what: "PAT"
271            }
272            .to_string(),
273            "buffer too short: need 4 bytes, have 1 (while parsing PAT)"
274        );
275        assert_eq!(
276            OutputBufferTooSmall { need: 4, have: 1 }.to_string(),
277            "output buffer too small: need 4, have 1"
278        );
279        assert_eq!(
280            FieldOverflow {
281                field: "section_length",
282                value: 5000,
283                max: 4095
284            }
285            .to_string(),
286            "section_length = 5000 exceeds the field maximum 4095"
287        );
288        assert_eq!(
289            CrcMismatch {
290                what: "PSM",
291                computed: 0xAB,
292                expected: 0xCD
293            }
294            .to_string(),
295            "PSM CRC-32 mismatch: computed 0x000000ab, expected 0x000000cd"
296        );
297        assert_eq!(
298            InvalidSync {
299                what: "TS sync byte",
300                expected: 0x47,
301                found: 0
302            }
303            .to_string(),
304            "invalid TS sync byte: expected 0x47, got 0x0"
305        );
306        assert_eq!(
307            ReservedBits {
308                field: "reserved",
309                expected: 0x7,
310                found: 0x2
311            }
312            .to_string(),
313            "reserved field reserved must be 0x7, found 0x2"
314        );
315        assert_eq!(
316            InvalidValue {
317                field: "version",
318                value: 9,
319                reason: "out of range"
320            }
321            .to_string(),
322            "field version value 9 invalid: out of range"
323        );
324        assert_eq!(
325            InvalidField {
326                field: "pts",
327                reason: "missing"
328            }
329            .to_string(),
330            "invalid field pts: missing"
331        );
332        assert_eq!(
333            UnsupportedVersion {
334                what: "RTP",
335                found: 1
336            }
337            .to_string(),
338            "unsupported RTP version 1"
339        );
340        assert_eq!(
341            LengthMismatch {
342                what: "APDU",
343                declared: 9,
344                actual: 7
345            }
346            .to_string(),
347            "length mismatch for APDU: declared 9, actual 7"
348        );
349    }
350
351    #[test]
352    fn equality_distinguishes_every_field() {
353        let a = CrcMismatch {
354            what: "PSM",
355            computed: 1,
356            expected: 2,
357        };
358        assert_eq!(a, a);
359        assert_ne!(a, CrcMismatch { what: "SNDU", ..a });
360        assert_ne!(a, CrcMismatch { computed: 3, ..a });
361        assert_ne!(a, CrcMismatch { expected: 3, ..a });
362        let b = LengthMismatch {
363            what: "x",
364            declared: 1,
365            actual: 2,
366        };
367        assert_ne!(b, LengthMismatch { declared: 2, ..b });
368        assert_ne!(b, LengthMismatch { actual: 1, ..b });
369    }
370
371    #[test]
372    fn len_reexports_are_the_same_types() {
373        let e: BufferTooShort = crate::len::need_in(&[], 1, "x").unwrap_err();
374        assert_eq!(
375            e,
376            BufferTooShort {
377                need: 1,
378                have: 0,
379                what: "x"
380            }
381        );
382        let o: OutputBufferTooSmall = crate::len::need_out(&[], 1).unwrap_err();
383        assert_eq!(o, OutputBufferTooSmall { need: 1, have: 0 });
384        let _: FieldOverflow = crate::len::fit_bits(256, 8, "f").unwrap_err();
385    }
386
387    #[test]
388    fn kinds_are_core_errors() {
389        fn is_error<E: core::error::Error + Copy + Send + Sync + 'static>() {}
390        is_error::<BufferTooShort>();
391        is_error::<CrcMismatch>();
392        is_error::<InvalidSync>();
393        is_error::<ReservedBits>();
394        is_error::<InvalidValue>();
395        is_error::<InvalidField>();
396        is_error::<UnsupportedVersion>();
397        is_error::<LengthMismatch>();
398    }
399}