Skip to main content

zerodds_cdr/
error.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Encoder- und Decoder-Fehler.
4//!
5//! Bewusst getrennt: ein Wert kann nur enkodiert oder dekodiert werden,
6//! aber die Fehler-Kategorien sind unterschiedlich. Encoder-Fehler sind
7//! Buffer-/Format-bezogen; Decoder-Fehler sind zusaetzlich Validierungs-
8//! Fehler (UnexpectedEof, InvalidUtf8, etc.).
9
10use core::fmt;
11
12/// Fehler beim Encoden eines Werts.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum EncodeError {
15    /// Schreib-Buffer zu klein. Bei `Vec<u8>`-basiertem Writer kann das
16    /// nicht passieren; bei fixierten Buffern (no_std + statisches
17    /// Array) sehr wohl.
18    BufferTooSmall {
19        /// Wieviele zusaetzliche Bytes gebraucht worden waeren.
20        needed: usize,
21        /// Wieviele tatsaechlich verfuegbar waren.
22        available: usize,
23    },
24    /// Wert kann nicht encodiert werden — z.B. `String`-Laenge sprengt
25    /// `u32::MAX` (XCDR-Limit).
26    ValueOutOfRange {
27        /// Beschreibung der Verletzung.
28        message: &'static str,
29    },
30    /// Mutable-Encode hat einen non-optional Member ausgelassen
31    /// (XTypes 1.3 §7.4.1.2.3 — "the serialized representation MUST
32    /// contain at least the values of all the non-optional members").
33    MissingNonOptionalMember {
34        /// Member-ID, die fehlte.
35        member_id: u32,
36    },
37}
38
39impl fmt::Display for EncodeError {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        match self {
42            Self::BufferTooSmall { needed, available } => write!(
43                f,
44                "encoder buffer too small: needed {needed} bytes, available {available}"
45            ),
46            Self::ValueOutOfRange { message } => write!(f, "value out of range: {message}"),
47            Self::MissingNonOptionalMember { member_id } => write!(
48                f,
49                "mutable struct missing non-optional member: id={member_id}"
50            ),
51        }
52    }
53}
54
55#[cfg(feature = "std")]
56impl std::error::Error for EncodeError {}
57
58/// Fehler beim Decoden eines Werts.
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub enum DecodeError {
61    /// Eingabe endete vor dem erwarteten Ende.
62    UnexpectedEof {
63        /// Wieviele Bytes noch erwartet wurden.
64        needed: usize,
65        /// Position im Stream, wo das EOF auftrat.
66        offset: usize,
67    },
68    /// UTF-8-Validierung fuer einen String-Wert ist fehlgeschlagen.
69    InvalidUtf8 {
70        /// Position, an der der String begann.
71        offset: usize,
72    },
73    /// Boolean-Byte war weder 0 noch 1 — XCDR-Spec verbietet das.
74    InvalidBool {
75        /// Tatsaechlich gelesenes Byte.
76        value: u8,
77        /// Position des Bytes.
78        offset: usize,
79    },
80    /// Char-Wert ist kein gueltiger Unicode-Codepoint.
81    InvalidChar {
82        /// Tatsaechlich gelesener u32-Wert.
83        value: u32,
84        /// Position.
85        offset: usize,
86    },
87    /// Sequence-/Array-Laenge ueberschreitet Bound oder die noch
88    /// vorhandenen Bytes.
89    LengthExceeded {
90        /// Wieviele Elemente die Laenge ankuendigt.
91        announced: usize,
92        /// Wieviele tatsaechlich noch lesbar sind (best-effort).
93        remaining: usize,
94        /// Position.
95        offset: usize,
96    },
97    /// String-Format-Verletzung (z.B. Null-Terminator fehlt, Laenge 0).
98    InvalidString {
99        /// Position, an der der String begann.
100        offset: usize,
101        /// Kurzbeschreibung (statisch).
102        reason: &'static str,
103    },
104    /// Unbekannter/ungueltiger Enum-Discriminator — wird von Policy-
105    /// Decodern genutzt, die strict-mode operieren (z.B. QoS-Enums).
106    InvalidEnum {
107        /// Enum-Name fuer Debugging (z.B. "DurabilityKind").
108        kind: &'static str,
109        /// Gelesener Discriminator-Wert.
110        value: u32,
111    },
112    /// Mutable-Decode hat einen `must_understand`-Member mit unbekannter
113    /// Member-ID gelesen (XTypes 1.3 §7.4.1.2.3 — Receiver MUSS in dem
114    /// Fall die Message verwerfen).
115    UnknownMustUnderstandMember {
116        /// Member-ID die nicht erkannt wurde.
117        member_id: u32,
118    },
119    /// Mutable-Decode hat einen non-optional Member nicht im Wire
120    /// gefunden (XTypes 1.3 §7.4.1.2.3).
121    MissingNonOptionalMember {
122        /// Member-ID die fehlt.
123        member_id: u32,
124    },
125}
126
127impl fmt::Display for DecodeError {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        match self {
130            Self::UnexpectedEof { needed, offset } => {
131                write!(
132                    f,
133                    "unexpected EOF: needed {needed} more bytes at offset {offset}"
134                )
135            }
136            Self::InvalidUtf8 { offset } => write!(f, "invalid UTF-8 at offset {offset}"),
137            Self::InvalidBool { value, offset } => {
138                write!(f, "invalid bool byte 0x{value:02x} at offset {offset}")
139            }
140            Self::InvalidChar { value, offset } => {
141                write!(f, "invalid char codepoint U+{value:04X} at offset {offset}")
142            }
143            Self::LengthExceeded {
144                announced,
145                remaining,
146                offset,
147            } => write!(
148                f,
149                "length {announced} at offset {offset} exceeds remaining {remaining} bytes"
150            ),
151            Self::InvalidString { offset, reason } => {
152                write!(f, "invalid CDR string at offset {offset}: {reason}")
153            }
154            Self::InvalidEnum { kind, value } => {
155                write!(f, "invalid {kind} discriminator: {value}")
156            }
157            Self::UnknownMustUnderstandMember { member_id } => {
158                write!(f, "unknown must_understand member id: {member_id}")
159            }
160            Self::MissingNonOptionalMember { member_id } => {
161                write!(f, "missing non-optional member id: {member_id}")
162            }
163        }
164    }
165}
166
167#[cfg(feature = "std")]
168impl std::error::Error for DecodeError {}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[cfg(feature = "alloc")]
175    extern crate alloc;
176    #[cfg(feature = "alloc")]
177    use alloc::format;
178
179    #[test]
180    fn encode_error_display_buffer_too_small() {
181        let e = EncodeError::BufferTooSmall {
182            needed: 8,
183            available: 4,
184        };
185        let s = format!("{e}");
186        assert!(s.contains("8"));
187        assert!(s.contains("4"));
188    }
189
190    #[test]
191    fn encode_error_display_value_out_of_range() {
192        let e = EncodeError::ValueOutOfRange {
193            message: "string too long",
194        };
195        assert!(format!("{e}").contains("string too long"));
196    }
197
198    #[test]
199    fn decode_error_display_unexpected_eof() {
200        let e = DecodeError::UnexpectedEof {
201            needed: 4,
202            offset: 12,
203        };
204        let s = format!("{e}");
205        assert!(s.contains("4"));
206        assert!(s.contains("12"));
207    }
208
209    #[test]
210    fn decode_error_display_invalid_utf8() {
211        let e = DecodeError::InvalidUtf8 { offset: 5 };
212        assert!(format!("{e}").contains("5"));
213    }
214
215    #[test]
216    fn decode_error_display_invalid_bool() {
217        let e = DecodeError::InvalidBool {
218            value: 0xff,
219            offset: 0,
220        };
221        assert!(format!("{e}").contains("ff"));
222    }
223
224    #[test]
225    fn decode_error_display_invalid_char() {
226        let e = DecodeError::InvalidChar {
227            value: 0xD800,
228            offset: 0,
229        };
230        assert!(format!("{e}").contains("D800"));
231    }
232
233    #[test]
234    fn decode_error_display_length_exceeded() {
235        let e = DecodeError::LengthExceeded {
236            announced: 100,
237            remaining: 4,
238            offset: 0,
239        };
240        let s = format!("{e}");
241        assert!(s.contains("100"));
242        assert!(s.contains("4"));
243    }
244
245    #[test]
246    fn errors_are_clone_eq() {
247        let e1 = EncodeError::ValueOutOfRange { message: "x" };
248        let e2 = e1.clone();
249        assert_eq!(e1, e2);
250        let d1 = DecodeError::UnexpectedEof {
251            needed: 1,
252            offset: 0,
253        };
254        assert_eq!(d1.clone(), d1);
255    }
256}