Skip to main content

zerodds_cdr/
encode.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! `CdrEncode` / `CdrDecode` Traits + Primitive-Implementierungen
4//! (W1.3).
5//!
6//! Trait-basiert statt freier Funktionen, damit Composite-Typen in W2/W3
7//! ihre Children einheitlich rekursiv-encodieren koennen ohne grosse
8//! Type-Switches.
9//!
10//! # Alignment-Konvention
11//!
12//! - `u8`/`i8`/`bool`: 1
13//! - `u16`/`i16`: 2
14//! - `u32`/`i32`/`f32`/`char`: 4
15//! - `u64`/`i64`/`f64`: 8
16//!
17//! Char wird als XCDR2-`wchar32` (4 Byte) kodiert (OMG-XTypes §7.4.7);
18//! das deckt voll Unicode ab. Die XCDR1-`char`-Variante (1 Byte ASCII)
19//! lebt im separaten `xcdr1`-Modul (siehe Crate-Header).
20
21use crate::buffer::BufferReader;
22use crate::error::DecodeError;
23
24#[cfg(feature = "alloc")]
25use crate::buffer::BufferWriter;
26#[cfg(feature = "alloc")]
27use crate::error::EncodeError;
28
29// ============================================================================
30// Traits
31// ============================================================================
32
33/// Wert kann in einen [`BufferWriter`] enkodiert werden.
34#[cfg(feature = "alloc")]
35pub trait CdrEncode {
36    /// Schreibt diesen Wert in den Writer (alignment-bewusst).
37    ///
38    /// # Errors
39    /// [`EncodeError`].
40    fn encode(&self, writer: &mut BufferWriter) -> Result<(), EncodeError>;
41}
42
43/// Stub-Trait fuer no-alloc Builds: nicht nutzbar, da [`BufferWriter`]
44/// nur mit `alloc`-Feature existiert. Wird mit Slice-basiertem Writer
45/// in Phase 1 nachgereicht.
46#[cfg(not(feature = "alloc"))]
47pub trait CdrEncode {}
48
49/// Wert kann aus einem [`BufferReader`] dekodiert werden.
50pub trait CdrDecode: Sized {
51    /// Liest diesen Wert aus dem Reader (alignment-bewusst).
52    ///
53    /// # Errors
54    /// [`DecodeError`].
55    fn decode(reader: &mut BufferReader<'_>) -> Result<Self, DecodeError>;
56}
57
58// ============================================================================
59// Primitive Encoder/Decoder
60// ============================================================================
61
62#[cfg(feature = "alloc")]
63impl CdrEncode for u8 {
64    fn encode(&self, writer: &mut BufferWriter) -> Result<(), EncodeError> {
65        writer.write_u8(*self)
66    }
67}
68
69impl CdrDecode for u8 {
70    fn decode(reader: &mut BufferReader<'_>) -> Result<Self, DecodeError> {
71        reader.read_u8()
72    }
73}
74
75#[cfg(feature = "alloc")]
76impl CdrEncode for i8 {
77    fn encode(&self, writer: &mut BufferWriter) -> Result<(), EncodeError> {
78        writer.write_u8(*self as u8)
79    }
80}
81
82impl CdrDecode for i8 {
83    fn decode(reader: &mut BufferReader<'_>) -> Result<Self, DecodeError> {
84        Ok(reader.read_u8()? as i8)
85    }
86}
87
88#[cfg(feature = "alloc")]
89impl CdrEncode for u16 {
90    fn encode(&self, writer: &mut BufferWriter) -> Result<(), EncodeError> {
91        writer.write_u16(*self)
92    }
93}
94
95impl CdrDecode for u16 {
96    fn decode(reader: &mut BufferReader<'_>) -> Result<Self, DecodeError> {
97        reader.read_u16()
98    }
99}
100
101#[cfg(feature = "alloc")]
102impl CdrEncode for i16 {
103    fn encode(&self, writer: &mut BufferWriter) -> Result<(), EncodeError> {
104        writer.write_u16(*self as u16)
105    }
106}
107
108impl CdrDecode for i16 {
109    fn decode(reader: &mut BufferReader<'_>) -> Result<Self, DecodeError> {
110        Ok(reader.read_u16()? as i16)
111    }
112}
113
114#[cfg(feature = "alloc")]
115impl CdrEncode for u32 {
116    fn encode(&self, writer: &mut BufferWriter) -> Result<(), EncodeError> {
117        writer.write_u32(*self)
118    }
119}
120
121impl CdrDecode for u32 {
122    fn decode(reader: &mut BufferReader<'_>) -> Result<Self, DecodeError> {
123        reader.read_u32()
124    }
125}
126
127#[cfg(feature = "alloc")]
128impl CdrEncode for i32 {
129    fn encode(&self, writer: &mut BufferWriter) -> Result<(), EncodeError> {
130        writer.write_u32(*self as u32)
131    }
132}
133
134impl CdrDecode for i32 {
135    fn decode(reader: &mut BufferReader<'_>) -> Result<Self, DecodeError> {
136        Ok(reader.read_u32()? as i32)
137    }
138}
139
140#[cfg(feature = "alloc")]
141impl CdrEncode for u64 {
142    fn encode(&self, writer: &mut BufferWriter) -> Result<(), EncodeError> {
143        writer.write_u64(*self)
144    }
145}
146
147impl CdrDecode for u64 {
148    fn decode(reader: &mut BufferReader<'_>) -> Result<Self, DecodeError> {
149        reader.read_u64()
150    }
151}
152
153#[cfg(feature = "alloc")]
154impl CdrEncode for i64 {
155    fn encode(&self, writer: &mut BufferWriter) -> Result<(), EncodeError> {
156        writer.write_u64(*self as u64)
157    }
158}
159
160impl CdrDecode for i64 {
161    fn decode(reader: &mut BufferReader<'_>) -> Result<Self, DecodeError> {
162        Ok(reader.read_u64()? as i64)
163    }
164}
165
166#[cfg(feature = "alloc")]
167impl CdrEncode for f32 {
168    fn encode(&self, writer: &mut BufferWriter) -> Result<(), EncodeError> {
169        writer.write_u32(self.to_bits())
170    }
171}
172
173impl CdrDecode for f32 {
174    fn decode(reader: &mut BufferReader<'_>) -> Result<Self, DecodeError> {
175        Ok(Self::from_bits(reader.read_u32()?))
176    }
177}
178
179#[cfg(feature = "alloc")]
180impl CdrEncode for f64 {
181    fn encode(&self, writer: &mut BufferWriter) -> Result<(), EncodeError> {
182        writer.write_u64(self.to_bits())
183    }
184}
185
186impl CdrDecode for f64 {
187    fn decode(reader: &mut BufferReader<'_>) -> Result<Self, DecodeError> {
188        Ok(Self::from_bits(reader.read_u64()?))
189    }
190}
191
192#[cfg(feature = "alloc")]
193impl CdrEncode for bool {
194    fn encode(&self, writer: &mut BufferWriter) -> Result<(), EncodeError> {
195        writer.write_u8(u8::from(*self))
196    }
197}
198
199impl CdrDecode for bool {
200    fn decode(reader: &mut BufferReader<'_>) -> Result<Self, DecodeError> {
201        let offset = reader.position();
202        let byte = reader.read_u8()?;
203        match byte {
204            0 => Ok(false),
205            1 => Ok(true),
206            other => Err(DecodeError::InvalidBool {
207                value: other,
208                offset,
209            }),
210        }
211    }
212}
213
214#[cfg(feature = "alloc")]
215impl CdrEncode for char {
216    fn encode(&self, writer: &mut BufferWriter) -> Result<(), EncodeError> {
217        // XCDR2 wchar32 (UTF-32 Codepoint).
218        writer.write_u32(*self as u32)
219    }
220}
221
222impl CdrDecode for char {
223    fn decode(reader: &mut BufferReader<'_>) -> Result<Self, DecodeError> {
224        let offset = reader.position();
225        let value = reader.read_u32()?;
226        char::from_u32(value).ok_or(DecodeError::InvalidChar { value, offset })
227    }
228}
229
230#[cfg(test)]
231mod tests {
232    #![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
233    use super::*;
234    use crate::Endianness;
235
236    #[cfg(feature = "alloc")]
237    fn roundtrip_alloc<T>(value: T)
238    where
239        T: CdrEncode + CdrDecode + PartialEq + core::fmt::Debug,
240    {
241        let mut w = BufferWriter::new(Endianness::Little);
242        value.encode(&mut w).expect("encode");
243        let bytes = w.into_bytes();
244        let mut r = BufferReader::new(&bytes, Endianness::Little);
245        let decoded = T::decode(&mut r).expect("decode");
246        assert_eq!(decoded, value);
247        assert_eq!(r.remaining(), 0);
248    }
249
250    #[cfg(feature = "alloc")]
251    fn roundtrip_be_alloc<T>(value: T)
252    where
253        T: CdrEncode + CdrDecode + PartialEq + core::fmt::Debug,
254    {
255        let mut w = BufferWriter::new(Endianness::Big);
256        value.encode(&mut w).expect("encode");
257        let bytes = w.into_bytes();
258        let mut r = BufferReader::new(&bytes, Endianness::Big);
259        let decoded = T::decode(&mut r).expect("decode");
260        assert_eq!(decoded, value);
261    }
262
263    #[cfg(feature = "alloc")]
264    #[test]
265    fn u8_roundtrip() {
266        roundtrip_alloc(0u8);
267        roundtrip_alloc(0xABu8);
268        roundtrip_alloc(0xFFu8);
269    }
270
271    #[cfg(feature = "alloc")]
272    #[test]
273    fn i8_roundtrip() {
274        roundtrip_alloc(0i8);
275        roundtrip_alloc(-1i8);
276        roundtrip_alloc(127i8);
277        roundtrip_alloc(-128i8);
278    }
279
280    #[cfg(feature = "alloc")]
281    #[test]
282    fn u16_roundtrip_le_and_be() {
283        roundtrip_alloc(0x1234u16);
284        roundtrip_be_alloc(0x1234u16);
285        roundtrip_alloc(u16::MAX);
286    }
287
288    #[cfg(feature = "alloc")]
289    #[test]
290    fn i16_roundtrip() {
291        roundtrip_alloc(-1i16);
292        roundtrip_alloc(i16::MIN);
293        roundtrip_alloc(i16::MAX);
294    }
295
296    #[cfg(feature = "alloc")]
297    #[test]
298    fn u32_roundtrip() {
299        roundtrip_alloc(0xDEAD_BEEFu32);
300        roundtrip_be_alloc(0xCAFE_BABEu32);
301    }
302
303    #[cfg(feature = "alloc")]
304    #[test]
305    fn i32_roundtrip() {
306        roundtrip_alloc(-1i32);
307        roundtrip_alloc(i32::MIN);
308        roundtrip_alloc(i32::MAX);
309    }
310
311    #[cfg(feature = "alloc")]
312    #[test]
313    fn u64_roundtrip() {
314        roundtrip_alloc(0x0102_0304_0506_0708u64);
315        roundtrip_be_alloc(0x0102_0304_0506_0708u64);
316    }
317
318    #[cfg(feature = "alloc")]
319    #[test]
320    fn i64_roundtrip() {
321        roundtrip_alloc(-1i64);
322        roundtrip_alloc(i64::MIN);
323        roundtrip_alloc(i64::MAX);
324    }
325
326    #[cfg(feature = "alloc")]
327    #[test]
328    fn f32_roundtrip_normal_values() {
329        roundtrip_alloc(0.0f32);
330        roundtrip_alloc(1.5f32);
331        roundtrip_alloc(-2.5f32);
332        roundtrip_alloc(f32::MIN_POSITIVE);
333    }
334
335    #[cfg(feature = "alloc")]
336    #[test]
337    fn f32_roundtrip_special() {
338        // NaN ist nicht selbst-equal — nutze to_bits-Vergleich.
339        let mut w = BufferWriter::new(Endianness::Little);
340        f32::NAN.encode(&mut w).unwrap();
341        let bytes = w.into_bytes();
342        let mut r = BufferReader::new(&bytes, Endianness::Little);
343        let decoded = f32::decode(&mut r).unwrap();
344        assert!(decoded.is_nan());
345    }
346
347    #[cfg(feature = "alloc")]
348    #[test]
349    fn f64_roundtrip() {
350        roundtrip_alloc(0.0f64);
351        roundtrip_alloc(core::f64::consts::PI);
352        roundtrip_alloc(-1.0e-308f64);
353    }
354
355    #[cfg(feature = "alloc")]
356    #[test]
357    fn bool_roundtrip() {
358        roundtrip_alloc(true);
359        roundtrip_alloc(false);
360    }
361
362    #[test]
363    fn bool_decode_rejects_other_values() {
364        let bytes = [0xFFu8];
365        let mut r = BufferReader::new(&bytes, Endianness::Little);
366        let res = bool::decode(&mut r);
367        assert!(matches!(
368            res,
369            Err(DecodeError::InvalidBool {
370                value: 0xFF,
371                offset: 0
372            })
373        ));
374    }
375
376    #[cfg(feature = "alloc")]
377    #[test]
378    fn char_roundtrip_ascii_and_unicode() {
379        roundtrip_alloc('A');
380        roundtrip_alloc('z');
381        roundtrip_alloc('0');
382        roundtrip_alloc('🦀'); // Unicode-Code-Point ueber BMP
383        roundtrip_alloc('ä');
384    }
385
386    #[test]
387    fn char_decode_rejects_surrogate() {
388        // 0xD800 ist Surrogate-Pair-Marker, kein gueltiger Codepoint.
389        let bytes = [0x00, 0xD8, 0x00, 0x00];
390        let mut r = BufferReader::new(&bytes, Endianness::Little);
391        let res = char::decode(&mut r);
392        assert!(matches!(
393            res,
394            Err(DecodeError::InvalidChar { value: 0xD800, .. })
395        ));
396    }
397
398    #[cfg(feature = "alloc")]
399    #[test]
400    fn primitives_align_correctly_when_mixed() {
401        // u8 + u16 + u32 + u64 → erwartet 16 Byte (1 + 1pad + 2 + 4 + 8)
402        let mut w = BufferWriter::new(Endianness::Little);
403        1u8.encode(&mut w).unwrap();
404        2u16.encode(&mut w).unwrap();
405        3u32.encode(&mut w).unwrap();
406        4u64.encode(&mut w).unwrap();
407        assert_eq!(w.position(), 16);
408
409        let bytes = w.into_bytes();
410        let mut r = BufferReader::new(&bytes, Endianness::Little);
411        assert_eq!(u8::decode(&mut r).unwrap(), 1);
412        assert_eq!(u16::decode(&mut r).unwrap(), 2);
413        assert_eq!(u32::decode(&mut r).unwrap(), 3);
414        assert_eq!(u64::decode(&mut r).unwrap(), 4);
415    }
416}