Skip to main content

nros_serdes/
cdr.rs

1//! CDR encoder/decoder with alignment handling
2
3use crate::{
4    CDR_LE_HEADER,
5    error::{DeserError, SerError},
6};
7
8/// CDR writer for serialization.
9///
10/// Handles alignment and endianness for ROS 2 CDR encoding.
11/// Alignment is computed relative to `origin` — when a 4-byte CDR
12/// header is present, `origin = 4` so that fields align correctly
13/// within the payload portion of the buffer.
14pub struct CdrWriter<'a> {
15    buf: &'a mut [u8],
16    pos: usize,
17    /// Byte offset where payload data begins (0 for raw, 4 after CDR header).
18    /// Alignment padding is calculated as `(pos - origin) % alignment`.
19    origin: usize,
20}
21
22impl<'a> CdrWriter<'a> {
23    /// Create a new CDR writer
24    pub fn new(buf: &'a mut [u8]) -> Self {
25        Self {
26            buf,
27            pos: 0,
28            origin: 0,
29        }
30    }
31
32    /// Create a CDR writer positioned at `pos` bytes into `buf`.
33    ///
34    /// `origin` stays at 0, so alignment is computed relative to the start
35    /// of `buf`. Used by FFI bridges that hand us a `(origin, cursor, end)`
36    /// triple where `buf = origin..end` and the caller's cursor is `pos`.
37    pub fn new_at(buf: &'a mut [u8], pos: usize) -> Result<Self, SerError> {
38        if pos > buf.len() {
39            return Err(SerError::BufferTooSmall);
40        }
41        Ok(Self {
42            buf,
43            pos,
44            origin: 0,
45        })
46    }
47
48    /// Create a new CDR writer with the 4-byte encapsulation header.
49    ///
50    /// Writes `[0x00, 0x01, 0x00, 0x00]` (CDR little-endian) at the start
51    /// and sets `origin = 4` so subsequent alignment is relative to the
52    /// payload, not the header. This is the normal entry point for
53    /// serialising ROS 2 messages.
54    pub fn new_with_header(buf: &'a mut [u8]) -> Result<Self, SerError> {
55        if buf.len() < 4 {
56            return Err(SerError::BufferTooSmall);
57        }
58        buf[0..4].copy_from_slice(&CDR_LE_HEADER);
59        Ok(Self {
60            buf,
61            pos: 4,
62            origin: 4,
63        })
64    }
65
66    /// Get current position in buffer
67    #[inline]
68    pub fn position(&self) -> usize {
69        self.pos
70    }
71
72    /// Get remaining capacity
73    #[inline]
74    pub fn remaining(&self) -> usize {
75        self.buf.len().saturating_sub(self.pos)
76    }
77
78    /// Get the written bytes
79    pub fn as_slice(&self) -> &[u8] {
80        &self.buf[..self.pos]
81    }
82
83    /// Align to the given boundary (relative to origin)
84    #[inline]
85    pub fn align(&mut self, alignment: usize) -> Result<(), SerError> {
86        let offset = self.pos - self.origin;
87        let padding = (alignment - (offset % alignment)) % alignment;
88        if self.remaining() < padding {
89            return Err(SerError::BufferTooSmall);
90        }
91        // Fill padding with zeros
92        for i in 0..padding {
93            self.buf[self.pos + i] = 0;
94        }
95        self.pos += padding;
96        Ok(())
97    }
98
99    /// Write a single byte without alignment
100    #[inline]
101    pub fn write_u8(&mut self, value: u8) -> Result<(), SerError> {
102        if self.remaining() < 1 {
103            return Err(SerError::BufferTooSmall);
104        }
105        self.buf[self.pos] = value;
106        self.pos += 1;
107        Ok(())
108    }
109
110    /// Write a boolean (serialized as a single byte: 0 = false, 1 = true)
111    #[inline]
112    pub fn write_bool(&mut self, value: bool) -> Result<(), SerError> {
113        self.write_u8(value as u8)
114    }
115
116    /// Write i8 without alignment
117    #[inline]
118    pub fn write_i8(&mut self, value: i8) -> Result<(), SerError> {
119        self.write_u8(value as u8)
120    }
121
122    /// Write bytes without alignment
123    #[inline]
124    pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), SerError> {
125        if self.remaining() < bytes.len() {
126            return Err(SerError::BufferTooSmall);
127        }
128        self.buf[self.pos..self.pos + bytes.len()].copy_from_slice(bytes);
129        self.pos += bytes.len();
130        Ok(())
131    }
132
133    /// Write u16 with alignment (little-endian)
134    #[inline]
135    pub fn write_u16(&mut self, value: u16) -> Result<(), SerError> {
136        self.align(2)?;
137        if self.remaining() < 2 {
138            return Err(SerError::BufferTooSmall);
139        }
140        self.buf[self.pos..self.pos + 2].copy_from_slice(&value.to_le_bytes());
141        self.pos += 2;
142        Ok(())
143    }
144
145    /// Write u32 with alignment (little-endian)
146    #[inline]
147    pub fn write_u32(&mut self, value: u32) -> Result<(), SerError> {
148        self.align(4)?;
149        if self.remaining() < 4 {
150            return Err(SerError::BufferTooSmall);
151        }
152        self.buf[self.pos..self.pos + 4].copy_from_slice(&value.to_le_bytes());
153        self.pos += 4;
154        Ok(())
155    }
156
157    /// Write u64 with alignment (little-endian)
158    #[inline]
159    pub fn write_u64(&mut self, value: u64) -> Result<(), SerError> {
160        self.align(8)?;
161        if self.remaining() < 8 {
162            return Err(SerError::BufferTooSmall);
163        }
164        self.buf[self.pos..self.pos + 8].copy_from_slice(&value.to_le_bytes());
165        self.pos += 8;
166        Ok(())
167    }
168
169    /// Write i16 with alignment (little-endian)
170    #[inline]
171    pub fn write_i16(&mut self, value: i16) -> Result<(), SerError> {
172        self.write_u16(value as u16)
173    }
174
175    /// Write i32 with alignment (little-endian)
176    #[inline]
177    pub fn write_i32(&mut self, value: i32) -> Result<(), SerError> {
178        self.write_u32(value as u32)
179    }
180
181    /// Write i64 with alignment (little-endian)
182    #[inline]
183    pub fn write_i64(&mut self, value: i64) -> Result<(), SerError> {
184        self.write_u64(value as u64)
185    }
186
187    /// Write f32 with alignment (little-endian)
188    #[inline]
189    pub fn write_f32(&mut self, value: f32) -> Result<(), SerError> {
190        self.write_u32(value.to_bits())
191    }
192
193    /// Write f64 with alignment (little-endian)
194    #[inline]
195    pub fn write_f64(&mut self, value: f64) -> Result<(), SerError> {
196        self.write_u64(value.to_bits())
197    }
198
199    /// Write a CDR string (4-byte length including null + data + null terminator)
200    pub fn write_string(&mut self, s: &str) -> Result<(), SerError> {
201        let len = s.len() + 1; // Include null terminator
202        if len > u32::MAX as usize {
203            return Err(SerError::StringTooLong);
204        }
205        self.write_u32(len as u32)?;
206        self.write_bytes(s.as_bytes())?;
207        self.write_u8(0)?; // Null terminator
208        Ok(())
209    }
210
211    /// Write a sequence length (4-byte count)
212    #[inline]
213    pub fn write_sequence_len(&mut self, len: usize) -> Result<(), SerError> {
214        if len > u32::MAX as usize {
215            return Err(SerError::SequenceTooLong);
216        }
217        self.write_u32(len as u32)
218    }
219}
220
221/// CDR reader for deserialization
222///
223/// Handles alignment and endianness for CDR decoding.
224pub struct CdrReader<'a> {
225    buf: &'a [u8],
226    pos: usize,
227    origin: usize,
228}
229
230impl<'a> CdrReader<'a> {
231    /// Create a new CDR reader
232    pub fn new(buf: &'a [u8]) -> Self {
233        Self {
234            buf,
235            pos: 0,
236            origin: 0,
237        }
238    }
239
240    /// Create a CDR reader positioned at `pos` bytes into `buf`.
241    ///
242    /// `origin` stays at 0, so alignment is computed relative to the start
243    /// of `buf`. Used by FFI bridges that hand us a `(origin, cursor, end)`
244    /// triple where `buf = origin..end` and the caller's cursor is `pos`.
245    pub fn new_at(buf: &'a [u8], pos: usize) -> Result<Self, DeserError> {
246        if pos > buf.len() {
247            return Err(DeserError::UnexpectedEof);
248        }
249        Ok(Self {
250            buf,
251            pos,
252            origin: 0,
253        })
254    }
255
256    /// Create a new CDR reader, parsing and validating the encapsulation header
257    ///
258    /// Expects a 4-byte CDR header at the start of the buffer.
259    pub fn new_with_header(buf: &'a [u8]) -> Result<Self, DeserError> {
260        if buf.len() < 4 {
261            return Err(DeserError::UnexpectedEof);
262        }
263        // Check for valid CDR header (we only support little-endian for now)
264        if buf[0] != 0x00 || (buf[1] != 0x00 && buf[1] != 0x01) {
265            return Err(DeserError::InvalidHeader);
266        }
267        Ok(Self {
268            buf,
269            pos: 4,
270            origin: 4,
271        })
272    }
273
274    /// Get current position in buffer
275    #[inline]
276    pub fn position(&self) -> usize {
277        self.pos
278    }
279
280    /// Get remaining bytes
281    #[inline]
282    pub fn remaining(&self) -> usize {
283        self.buf.len().saturating_sub(self.pos)
284    }
285
286    /// Check if reader is at end of buffer
287    #[inline]
288    pub fn is_empty(&self) -> bool {
289        self.remaining() == 0
290    }
291
292    /// Align to the given boundary (relative to origin)
293    #[inline]
294    pub fn align(&mut self, alignment: usize) -> Result<(), DeserError> {
295        let offset = self.pos - self.origin;
296        let padding = (alignment - (offset % alignment)) % alignment;
297        if self.remaining() < padding {
298            return Err(DeserError::UnexpectedEof);
299        }
300        self.pos += padding;
301        Ok(())
302    }
303
304    /// Read a single byte without alignment
305    #[inline]
306    pub fn read_u8(&mut self) -> Result<u8, DeserError> {
307        if self.remaining() < 1 {
308            return Err(DeserError::UnexpectedEof);
309        }
310        let value = self.buf[self.pos];
311        self.pos += 1;
312        Ok(value)
313    }
314
315    /// Read a boolean (deserialized from a single byte: 0 = false, non-zero = true)
316    #[inline]
317    pub fn read_bool(&mut self) -> Result<bool, DeserError> {
318        Ok(self.read_u8()? != 0)
319    }
320
321    /// Read i8 without alignment
322    #[inline]
323    pub fn read_i8(&mut self) -> Result<i8, DeserError> {
324        Ok(self.read_u8()? as i8)
325    }
326
327    /// Read bytes without alignment
328    #[inline]
329    pub fn read_bytes(&mut self, len: usize) -> Result<&'a [u8], DeserError> {
330        if self.remaining() < len {
331            return Err(DeserError::UnexpectedEof);
332        }
333        let bytes = &self.buf[self.pos..self.pos + len];
334        self.pos += len;
335        Ok(bytes)
336    }
337
338    /// Read u16 with alignment (little-endian)
339    #[inline]
340    pub fn read_u16(&mut self) -> Result<u16, DeserError> {
341        self.align(2)?;
342        if self.remaining() < 2 {
343            return Err(DeserError::UnexpectedEof);
344        }
345        let value = u16::from_le_bytes([self.buf[self.pos], self.buf[self.pos + 1]]);
346        self.pos += 2;
347        Ok(value)
348    }
349
350    /// Read u32 with alignment (little-endian)
351    #[inline]
352    pub fn read_u32(&mut self) -> Result<u32, DeserError> {
353        self.align(4)?;
354        if self.remaining() < 4 {
355            return Err(DeserError::UnexpectedEof);
356        }
357        let value = u32::from_le_bytes([
358            self.buf[self.pos],
359            self.buf[self.pos + 1],
360            self.buf[self.pos + 2],
361            self.buf[self.pos + 3],
362        ]);
363        self.pos += 4;
364        Ok(value)
365    }
366
367    /// Read u64 with alignment (little-endian)
368    #[inline]
369    pub fn read_u64(&mut self) -> Result<u64, DeserError> {
370        self.align(8)?;
371        if self.remaining() < 8 {
372            return Err(DeserError::UnexpectedEof);
373        }
374        let value = u64::from_le_bytes([
375            self.buf[self.pos],
376            self.buf[self.pos + 1],
377            self.buf[self.pos + 2],
378            self.buf[self.pos + 3],
379            self.buf[self.pos + 4],
380            self.buf[self.pos + 5],
381            self.buf[self.pos + 6],
382            self.buf[self.pos + 7],
383        ]);
384        self.pos += 8;
385        Ok(value)
386    }
387
388    /// Read i16 with alignment (little-endian)
389    #[inline]
390    pub fn read_i16(&mut self) -> Result<i16, DeserError> {
391        Ok(self.read_u16()? as i16)
392    }
393
394    /// Read i32 with alignment (little-endian)
395    #[inline]
396    pub fn read_i32(&mut self) -> Result<i32, DeserError> {
397        Ok(self.read_u32()? as i32)
398    }
399
400    /// Read i64 with alignment (little-endian)
401    #[inline]
402    pub fn read_i64(&mut self) -> Result<i64, DeserError> {
403        Ok(self.read_u64()? as i64)
404    }
405
406    /// Read f32 with alignment (little-endian)
407    #[inline]
408    pub fn read_f32(&mut self) -> Result<f32, DeserError> {
409        Ok(f32::from_bits(self.read_u32()?))
410    }
411
412    /// Read f64 with alignment (little-endian)
413    #[inline]
414    pub fn read_f64(&mut self) -> Result<f64, DeserError> {
415        Ok(f64::from_bits(self.read_u64()?))
416    }
417
418    /// Read a CDR string (4-byte length including null + data + null terminator)
419    ///
420    /// Returns a string slice pointing into the buffer (zero-copy).
421    pub fn read_string(&mut self) -> Result<&'a str, DeserError> {
422        let len = self.read_u32()? as usize;
423        if len == 0 {
424            return Err(DeserError::InvalidData);
425        }
426        if self.remaining() < len {
427            return Err(DeserError::UnexpectedEof);
428        }
429        // Length includes null terminator, so actual string is len - 1 bytes
430        let bytes = &self.buf[self.pos..self.pos + len - 1];
431        self.pos += len;
432        core::str::from_utf8(bytes).map_err(|_| DeserError::InvalidUtf8)
433    }
434
435    /// Read a sequence length (4-byte count)
436    #[inline]
437    pub fn read_sequence_len(&mut self) -> Result<usize, DeserError> {
438        Ok(self.read_u32()? as usize)
439    }
440
441    // ── Borrowed slice readers (zero-copy for primitive sequences) ──
442
443    /// Read a `uint8[]` / `byte[]` sequence as a borrowed slice.
444    ///
445    /// Returns `&'a [u8]` pointing directly into the CDR buffer. Zero-copy.
446    /// Reads the 4-byte length prefix, then returns a slice of that length.
447    pub fn read_slice_u8(&mut self) -> Result<&'a [u8], DeserError> {
448        let len = self.read_u32()? as usize;
449        self.read_bytes(len)
450    }
451
452    /// Read an `int8[]` sequence as a borrowed slice.
453    pub fn read_slice_i8(&mut self) -> Result<&'a [u8], DeserError> {
454        // i8 and u8 have identical CDR encoding (1 byte, no alignment)
455        self.read_slice_u8()
456    }
457
458    /// Read a `bool[]` sequence as a borrowed `&[u8]` slice.
459    ///
460    /// CDR encodes booleans as single bytes (0/1). The returned slice
461    /// contains raw bytes; the caller interprets 0 as false, non-zero as true.
462    pub fn read_slice_bool(&mut self) -> Result<&'a [u8], DeserError> {
463        self.read_slice_u8()
464    }
465
466    /// Read a `uint16[]` sequence, returning raw bytes and element count.
467    ///
468    /// Returns `(byte_slice, element_count)`. The caller must handle
469    /// endianness (CDR uses little-endian). For zero-copy on little-endian
470    /// platforms, the bytes can be cast to `&[u16]` if properly aligned.
471    pub fn read_slice_u16_raw(&mut self) -> Result<(&'a [u8], usize), DeserError> {
472        let len = self.read_u32()? as usize;
473        self.align(2)?;
474        let byte_len = len * 2;
475        let bytes = self.read_bytes(byte_len)?;
476        Ok((bytes, len))
477    }
478
479    /// Read a `uint32[]` sequence, returning raw bytes and element count.
480    pub fn read_slice_u32_raw(&mut self) -> Result<(&'a [u8], usize), DeserError> {
481        let len = self.read_u32()? as usize;
482        self.align(4)?;
483        let byte_len = len * 4;
484        let bytes = self.read_bytes(byte_len)?;
485        Ok((bytes, len))
486    }
487
488    /// Read a `float32[]` sequence, returning raw bytes and element count.
489    pub fn read_slice_f32_raw(&mut self) -> Result<(&'a [u8], usize), DeserError> {
490        let len = self.read_u32()? as usize;
491        self.align(4)?;
492        let byte_len = len * 4;
493        let bytes = self.read_bytes(byte_len)?;
494        Ok((bytes, len))
495    }
496
497    /// Read a `float64[]` sequence, returning raw bytes and element count.
498    pub fn read_slice_f64_raw(&mut self) -> Result<(&'a [u8], usize), DeserError> {
499        let len = self.read_u32()? as usize;
500        self.align(8)?;
501        let byte_len = len * 8;
502        let bytes = self.read_bytes(byte_len)?;
503        Ok((bytes, len))
504    }
505
506    /// Read a `uint64[]` sequence, returning raw bytes and element count.
507    pub fn read_slice_u64_raw(&mut self) -> Result<(&'a [u8], usize), DeserError> {
508        let len = self.read_u32()? as usize;
509        self.align(8)?;
510        let byte_len = len * 8;
511        let bytes = self.read_bytes(byte_len)?;
512        Ok((bytes, len))
513    }
514
515    /// Read a multi-byte numeric sequence (`float32[]`, `uint16[]`, …) as a
516    /// borrowed [`LeSliceView`] — the alignment-agnostic borrowed reader for
517    /// RFC-0033 `borrowed` mode (Phase 229.6, issue 0007).
518    ///
519    /// Unlike a `&'a [T]` cast, this never requires the source buffer to be
520    /// `T`-aligned: the view borrows the raw little-endian bytes zero-copy and
521    /// decodes each element on access via `from_le_bytes`. Reads the 4-byte
522    /// length prefix, aligns the reader to `T` within the CDR stream, then
523    /// returns a view over `len * size_of::<T>()` bytes.
524    pub fn read_le_slice<T: LeDecode>(&mut self) -> Result<LeSliceView<'a, T>, DeserError> {
525        let len = self.read_u32()? as usize;
526        self.align(T::SIZE)?;
527        let byte_len = len * T::SIZE;
528        let bytes = self.read_bytes(byte_len)?;
529        Ok(LeSliceView::new(bytes))
530    }
531}
532
533/// A little-endian-decodable fixed-width numeric element of a borrowed sequence.
534///
535/// Implemented for the multi-byte CDR numeric primitives. Single-byte types
536/// (`u8`/`i8`/`bool`) do not need this — they borrow directly as `&[u8]`.
537pub trait LeDecode: Sized + Copy {
538    /// Encoded width in bytes (CDR little-endian).
539    const SIZE: usize;
540    /// Decode one element from exactly [`SIZE`](Self::SIZE) little-endian bytes.
541    fn from_le(bytes: &[u8]) -> Self;
542}
543
544macro_rules! impl_le_decode {
545    ($($t:ty),+ $(,)?) => {$(
546        impl LeDecode for $t {
547            const SIZE: usize = core::mem::size_of::<$t>();
548            #[inline]
549            fn from_le(bytes: &[u8]) -> Self {
550                let mut buf = [0u8; core::mem::size_of::<$t>()];
551                buf.copy_from_slice(bytes);
552                <$t>::from_le_bytes(buf)
553            }
554        }
555    )+};
556}
557impl_le_decode!(u16, i16, u32, i32, u64, i64, f32, f64);
558
559/// A borrowed, alignment-agnostic view over a CDR little-endian numeric
560/// sequence (RFC-0033 `borrowed` mode). Borrows the raw payload bytes
561/// zero-copy; decodes elements lazily on access, so the source buffer need not
562/// be `T`-aligned. Valid only for the borrow lifetime `'a` (the subscription
563/// callback scope).
564#[derive(Clone, Copy)]
565pub struct LeSliceView<'a, T> {
566    bytes: &'a [u8],
567    _marker: core::marker::PhantomData<fn() -> T>,
568}
569
570impl<'a, T: LeDecode> LeSliceView<'a, T> {
571    /// Wrap raw little-endian payload bytes. `bytes.len()` must be a multiple of
572    /// `T::SIZE` (guaranteed by [`CdrReader::read_le_slice`]).
573    #[inline]
574    pub fn new(bytes: &'a [u8]) -> Self {
575        Self {
576            bytes,
577            _marker: core::marker::PhantomData,
578        }
579    }
580
581    /// Number of elements in the view.
582    #[inline]
583    pub fn len(&self) -> usize {
584        self.bytes.len() / T::SIZE
585    }
586
587    /// Whether the view is empty.
588    #[inline]
589    pub fn is_empty(&self) -> bool {
590        self.bytes.is_empty()
591    }
592
593    /// The raw little-endian payload bytes (zero-copy).
594    #[inline]
595    pub fn as_bytes(&self) -> &'a [u8] {
596        self.bytes
597    }
598
599    /// Decode the element at `index`, or `None` if out of bounds.
600    #[inline]
601    pub fn get(&self, index: usize) -> Option<T> {
602        let start = index.checked_mul(T::SIZE)?;
603        let end = start.checked_add(T::SIZE)?;
604        self.bytes.get(start..end).map(T::from_le)
605    }
606
607    /// Iterate over the decoded elements.
608    #[inline]
609    pub fn iter(&self) -> impl Iterator<Item = T> + 'a {
610        let bytes = self.bytes;
611        (0..bytes.len() / T::SIZE).map(move |i| T::from_le(&bytes[i * T::SIZE..(i + 1) * T::SIZE]))
612    }
613}
614
615#[cfg(test)]
616mod tests {
617    use super::*;
618
619    #[test]
620    fn test_write_read_u8() {
621        let mut buf = [0u8; 16];
622        let mut writer = CdrWriter::new(&mut buf);
623        writer.write_u8(0x42).unwrap();
624        writer.write_u8(0xFF).unwrap();
625
626        let mut reader = CdrReader::new(&buf);
627        assert_eq!(reader.read_u8().unwrap(), 0x42);
628        assert_eq!(reader.read_u8().unwrap(), 0xFF);
629    }
630
631    #[test]
632    fn le_slice_view_decodes_unaligned_f32() {
633        // The whole point of the alignment guard (Phase 229.6): a view can sit
634        // at an odd byte offset and still decode correctly — no `&[f32]` cast.
635        let vals = [1.5f32, -2.25, 3.0e10, 0.0];
636        let mut backing = [0u8; 1 + 4 * 4];
637        backing[0] = 0xAA; // shift the f32 payload to an odd (1-byte) offset.
638        for (i, v) in vals.iter().enumerate() {
639            backing[1 + i * 4..1 + i * 4 + 4].copy_from_slice(&v.to_le_bytes());
640        }
641        let view: LeSliceView<f32> = LeSliceView::new(&backing[1..]);
642        assert_eq!(view.len(), 4);
643        assert!(!view.is_empty());
644        for (i, v) in vals.iter().enumerate() {
645            assert_eq!(view.get(i).unwrap(), *v);
646        }
647        assert_eq!(view.get(4), None);
648        let collected: heapless::Vec<f32, 4> = view.iter().collect();
649        assert_eq!(&collected[..], &vals[..]);
650    }
651
652    #[test]
653    fn read_le_slice_roundtrips_through_cdr() {
654        // Write a `uint16[]` sequence with the CDR writer, read it back as a
655        // borrowed `LeSliceView` — values + count must match.
656        let vals = [10u16, 4000, 65535, 1];
657        let mut buf = [0u8; 64];
658        let written = {
659            let mut w = CdrWriter::new_with_header(&mut buf).unwrap();
660            w.write_sequence_len(vals.len()).unwrap();
661            for v in &vals {
662                w.write_u16(*v).unwrap();
663            }
664            w.position()
665        };
666        let mut reader = CdrReader::new_with_header(&buf[..written]).unwrap();
667        let view = reader.read_le_slice::<u16>().unwrap();
668        assert_eq!(view.len(), vals.len());
669        for (i, v) in vals.iter().enumerate() {
670            assert_eq!(view.get(i).unwrap(), *v);
671        }
672    }
673
674    #[test]
675    fn test_write_read_u32_alignment() {
676        let mut buf = [0u8; 16];
677        let mut writer = CdrWriter::new(&mut buf);
678        writer.write_u8(0x01).unwrap(); // Position 1
679        writer.write_u32(0x12345678).unwrap(); // Should align to position 4
680
681        assert_eq!(writer.position(), 8); // 1 byte + 3 padding + 4 bytes
682
683        let mut reader = CdrReader::new(&buf);
684        assert_eq!(reader.read_u8().unwrap(), 0x01);
685        assert_eq!(reader.read_u32().unwrap(), 0x12345678);
686    }
687
688    #[test]
689    fn test_write_read_string() {
690        let mut buf = [0u8; 32];
691        let mut writer = CdrWriter::new(&mut buf);
692        writer.write_string("Hello").unwrap();
693
694        let mut reader = CdrReader::new(&buf);
695        assert_eq!(reader.read_string().unwrap(), "Hello");
696    }
697
698    #[test]
699    fn test_encapsulation_header() {
700        let mut buf = [0u8; 32];
701        let mut writer = CdrWriter::new_with_header(&mut buf).unwrap();
702        writer.write_u32(42).unwrap();
703
704        assert_eq!(&buf[0..4], &CDR_LE_HEADER);
705
706        let mut reader = CdrReader::new_with_header(&buf).unwrap();
707        assert_eq!(reader.read_u32().unwrap(), 42);
708    }
709
710    #[test]
711    fn test_alignment_with_header() {
712        let mut buf = [0u8; 32];
713        let mut writer = CdrWriter::new_with_header(&mut buf).unwrap();
714        // After header (pos=4, origin=4), write u8 then u32
715        writer.write_u8(0x01).unwrap(); // pos=5
716        writer.write_u32(0xDEADBEEF).unwrap(); // Should align to pos=8
717
718        assert_eq!(writer.position(), 12); // 4 header + 1 byte + 3 padding + 4 bytes
719
720        let mut reader = CdrReader::new_with_header(&buf).unwrap();
721        assert_eq!(reader.read_u8().unwrap(), 0x01);
722        assert_eq!(reader.read_u32().unwrap(), 0xDEADBEEF);
723    }
724}
725
726// =============================================================================
727// Ghost model validation
728// =============================================================================
729
730#[cfg(test)]
731mod ghost_checks {
732    use super::*;
733    use nros_ghost_types::CdrGhost;
734
735    /// Structural check: construct CdrGhost from CdrWriter private fields.
736    /// If a field is renamed or retyped, this fails to compile.
737    fn ghost_from_writer(w: &CdrWriter) -> CdrGhost {
738        CdrGhost {
739            buf_len: w.buf.len(),
740            pos: w.pos,
741            origin: w.origin,
742        }
743    }
744
745    #[test]
746    fn ghost_new_state() {
747        let mut buf = [0u8; 64];
748        let writer = CdrWriter::new(&mut buf);
749        let ghost = ghost_from_writer(&writer);
750        assert_eq!(ghost.pos, 0);
751        assert_eq!(ghost.origin, 0);
752        assert_eq!(ghost.buf_len, 64);
753    }
754
755    #[test]
756    fn ghost_header_origin() {
757        let mut buf = [0u8; 64];
758        let writer = CdrWriter::new_with_header(&mut buf).unwrap();
759        let ghost = ghost_from_writer(&writer);
760        assert_eq!(ghost.pos, 4);
761        assert_eq!(ghost.origin, 4);
762    }
763
764    #[test]
765    fn ghost_position_invariant() {
766        let mut buf = [0u8; 64];
767        let mut writer = CdrWriter::new_with_header(&mut buf).unwrap();
768        writer.write_u32(42).unwrap();
769        let ghost = ghost_from_writer(&writer);
770        // After header: pos + remaining == buf_len
771        assert_eq!(ghost.pos + writer.remaining(), ghost.buf_len);
772    }
773
774    #[test]
775    fn test_read_slice_u8() {
776        let mut buf = [0u8; 64];
777        let mut writer = CdrWriter::new_with_header(&mut buf).unwrap();
778        // Write a uint8[] sequence: [0x10, 0x20, 0x30]
779        writer.write_u32(3).unwrap(); // length
780        writer.write_u8(0x10).unwrap();
781        writer.write_u8(0x20).unwrap();
782        writer.write_u8(0x30).unwrap();
783        let len = writer.position();
784
785        let mut reader = CdrReader::new_with_header(&buf[..len]).unwrap();
786        let slice = reader.read_slice_u8().unwrap();
787        assert_eq!(slice, &[0x10, 0x20, 0x30]);
788    }
789
790    #[test]
791    fn test_read_slice_u8_empty() {
792        let mut buf = [0u8; 64];
793        let mut writer = CdrWriter::new_with_header(&mut buf).unwrap();
794        writer.write_u32(0).unwrap(); // length = 0
795        let len = writer.position();
796
797        let mut reader = CdrReader::new_with_header(&buf[..len]).unwrap();
798        let slice = reader.read_slice_u8().unwrap();
799        assert!(slice.is_empty());
800    }
801
802    #[test]
803    fn test_read_slice_f32_raw() {
804        let mut buf = [0u8; 64];
805        let mut writer = CdrWriter::new_with_header(&mut buf).unwrap();
806        // Write a float32[] sequence: [1.0, 2.5]
807        writer.write_u32(2).unwrap(); // length
808        writer.write_f32(1.0).unwrap();
809        writer.write_f32(2.5).unwrap();
810        let len = writer.position();
811
812        let mut reader = CdrReader::new_with_header(&buf[..len]).unwrap();
813        let (bytes, count) = reader.read_slice_f32_raw().unwrap();
814        assert_eq!(count, 2);
815        assert_eq!(bytes.len(), 8); // 2 × 4 bytes
816        // Verify first element (little-endian f32)
817        assert_eq!(
818            f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
819            1.0
820        );
821    }
822}
823
824// =============================================================================
825// Kani bounded model checking proofs
826// =============================================================================
827
828#[cfg(kani)]
829mod verification {
830    use super::*;
831
832    // ---- Primitive write/read panic-freedom ----
833
834    #[kani::proof]
835    #[kani::unwind(5)]
836    fn cdr_write_u8_no_panic() {
837        let mut buf = [0u8; 8];
838        let mut writer = CdrWriter::new(&mut buf);
839        let val: u8 = kani::any();
840        let _ = writer.write_u8(val);
841    }
842
843    #[kani::proof]
844    #[kani::unwind(5)]
845    fn cdr_write_bool_no_panic() {
846        let mut buf = [0u8; 8];
847        let mut writer = CdrWriter::new(&mut buf);
848        let val: bool = kani::any();
849        let _ = writer.write_bool(val);
850    }
851
852    #[kani::proof]
853    #[kani::unwind(5)]
854    fn cdr_write_i16_no_panic() {
855        let mut buf = [0u8; 16];
856        let mut writer = CdrWriter::new(&mut buf);
857        let val: i16 = kani::any();
858        let _ = writer.write_i16(val);
859    }
860
861    #[kani::proof]
862    #[kani::unwind(5)]
863    fn cdr_write_i32_no_panic() {
864        let mut buf = [0u8; 16];
865        let mut writer = CdrWriter::new(&mut buf);
866        let val: i32 = kani::any();
867        let _ = writer.write_i32(val);
868    }
869
870    #[kani::proof]
871    #[kani::unwind(5)]
872    fn cdr_write_i64_no_panic() {
873        let mut buf = [0u8; 16];
874        let mut writer = CdrWriter::new(&mut buf);
875        let val: i64 = kani::any();
876        let _ = writer.write_i64(val);
877    }
878
879    #[kani::proof]
880    #[kani::unwind(5)]
881    fn cdr_write_f32_no_panic() {
882        let mut buf = [0u8; 16];
883        let mut writer = CdrWriter::new(&mut buf);
884        let val: f32 = kani::any();
885        let _ = writer.write_f32(val);
886    }
887
888    #[kani::proof]
889    #[kani::unwind(5)]
890    fn cdr_write_f64_no_panic() {
891        let mut buf = [0u8; 16];
892        let mut writer = CdrWriter::new(&mut buf);
893        let val: f64 = kani::any();
894        let _ = writer.write_f64(val);
895    }
896
897    // ---- Round-trip correctness: write then read produces the same value ----
898
899    #[kani::proof]
900    #[kani::unwind(5)]
901    fn cdr_roundtrip_u8() {
902        let mut buf = [0u8; 8];
903        let val: u8 = kani::any();
904        let len = {
905            let mut writer = CdrWriter::new(&mut buf);
906            writer.write_u8(val).unwrap();
907            writer.position()
908        };
909        let mut reader = CdrReader::new(&buf[..len]);
910        assert_eq!(reader.read_u8().unwrap(), val);
911    }
912
913    #[kani::proof]
914    #[kani::unwind(5)]
915    fn cdr_roundtrip_bool() {
916        let mut buf = [0u8; 8];
917        let val: bool = kani::any();
918        let len = {
919            let mut writer = CdrWriter::new(&mut buf);
920            writer.write_bool(val).unwrap();
921            writer.position()
922        };
923        let mut reader = CdrReader::new(&buf[..len]);
924        assert_eq!(reader.read_bool().unwrap(), val);
925    }
926
927    #[kani::proof]
928    #[kani::unwind(5)]
929    fn cdr_roundtrip_i16() {
930        let mut buf = [0u8; 16];
931        let val: i16 = kani::any();
932        let len = {
933            let mut writer = CdrWriter::new(&mut buf);
934            writer.write_i16(val).unwrap();
935            writer.position()
936        };
937        let mut reader = CdrReader::new(&buf[..len]);
938        assert_eq!(reader.read_i16().unwrap(), val);
939    }
940
941    #[kani::proof]
942    #[kani::unwind(5)]
943    fn cdr_roundtrip_i32() {
944        let mut buf = [0u8; 16];
945        let val: i32 = kani::any();
946        let len = {
947            let mut writer = CdrWriter::new(&mut buf);
948            writer.write_i32(val).unwrap();
949            writer.position()
950        };
951        let mut reader = CdrReader::new(&buf[..len]);
952        assert_eq!(reader.read_i32().unwrap(), val);
953    }
954
955    #[kani::proof]
956    #[kani::unwind(5)]
957    fn cdr_roundtrip_i64() {
958        let mut buf = [0u8; 16];
959        let val: i64 = kani::any();
960        let len = {
961            let mut writer = CdrWriter::new(&mut buf);
962            writer.write_i64(val).unwrap();
963            writer.position()
964        };
965        let mut reader = CdrReader::new(&buf[..len]);
966        assert_eq!(reader.read_i64().unwrap(), val);
967    }
968
969    #[kani::proof]
970    #[kani::unwind(5)]
971    fn cdr_roundtrip_f32() {
972        let mut buf = [0u8; 16];
973        let val: f32 = kani::any();
974        let len = {
975            let mut writer = CdrWriter::new(&mut buf);
976            writer.write_f32(val).unwrap();
977            writer.position()
978        };
979        let mut reader = CdrReader::new(&buf[..len]);
980        let result = reader.read_f32().unwrap();
981        assert_eq!(val.to_bits(), result.to_bits());
982    }
983
984    #[kani::proof]
985    #[kani::unwind(5)]
986    fn cdr_roundtrip_f64() {
987        let mut buf = [0u8; 16];
988        let val: f64 = kani::any();
989        let len = {
990            let mut writer = CdrWriter::new(&mut buf);
991            writer.write_f64(val).unwrap();
992            writer.position()
993        };
994        let mut reader = CdrReader::new(&buf[..len]);
995        let result = reader.read_f64().unwrap();
996        assert_eq!(val.to_bits(), result.to_bits());
997    }
998
999    // ---- CDR header round-trip ----
1000
1001    #[kani::proof]
1002    #[kani::unwind(5)]
1003    fn cdr_roundtrip_with_header_i32() {
1004        let mut buf = [0u8; 16];
1005        let val: i32 = kani::any();
1006        let len = {
1007            let mut writer = CdrWriter::new_with_header(&mut buf).unwrap();
1008            writer.write_i32(val).unwrap();
1009            writer.position()
1010        };
1011        let mut reader = CdrReader::new_with_header(&buf[..len]).unwrap();
1012        assert_eq!(reader.read_i32().unwrap(), val);
1013    }
1014
1015    // ---- Buffer exhaustion returns Err, never panics ----
1016
1017    #[kani::proof]
1018    #[kani::unwind(5)]
1019    fn cdr_write_buffer_exhaustion_u32() {
1020        let mut buf = [0u8; 3]; // Too small for u32
1021        let mut writer = CdrWriter::new(&mut buf);
1022        let val: u32 = kani::any();
1023        let result = writer.write_u32(val);
1024        assert!(result.is_err());
1025    }
1026
1027    #[kani::proof]
1028    #[kani::unwind(5)]
1029    fn cdr_write_header_buffer_too_small() {
1030        let mut buf = [0u8; 3]; // Too small for 4-byte header
1031        let result = CdrWriter::new_with_header(&mut buf);
1032        assert!(result.is_err());
1033    }
1034
1035    // ---- Deserialization of arbitrary bytes: Ok or Err, never panic ----
1036
1037    #[kani::proof]
1038    #[kani::unwind(5)]
1039    fn cdr_deserialize_arbitrary_bytes_i32() {
1040        let mut buf = [0u8; 8];
1041        buf[0] = kani::any();
1042        buf[1] = kani::any();
1043        buf[2] = kani::any();
1044        buf[3] = kani::any();
1045        buf[4] = kani::any();
1046        buf[5] = kani::any();
1047        buf[6] = kani::any();
1048        buf[7] = kani::any();
1049        let result = CdrReader::new_with_header(&buf);
1050        if let Ok(mut reader) = result {
1051            let _ = reader.read_i32(); // Ok or Err, not panic
1052        }
1053    }
1054
1055    #[kani::proof]
1056    #[kani::unwind(5)]
1057    fn cdr_deserialize_empty_buffer() {
1058        let buf = [0u8; 0];
1059        let mut reader = CdrReader::new(&buf);
1060        assert!(reader.read_u8().is_err());
1061        assert!(reader.read_u32().is_err());
1062    }
1063
1064    // ---- Alignment arithmetic correctness ----
1065
1066    #[kani::proof]
1067    fn cdr_alignment_no_overflow() {
1068        let offset: usize = kani::any();
1069        let alignment: usize = kani::any();
1070        kani::assume(alignment > 0 && alignment <= 8);
1071        kani::assume(offset <= 1024); // Realistic buffer size
1072        let padding = (alignment - (offset % alignment)) % alignment;
1073        let aligned = offset + padding;
1074        assert!(aligned % alignment == 0);
1075        assert!(aligned >= offset);
1076        assert!(aligned < offset + alignment);
1077    }
1078
1079    // ---- Position tracking consistency ----
1080
1081    #[kani::proof]
1082    #[kani::unwind(5)]
1083    fn cdr_writer_position_monotonic() {
1084        let mut buf = [0u8; 32];
1085        let mut writer = CdrWriter::new(&mut buf);
1086        let pos0 = writer.position();
1087
1088        let val: u8 = kani::any();
1089        if writer.write_u8(val).is_ok() {
1090            assert!(writer.position() > pos0);
1091        }
1092    }
1093
1094    #[kani::proof]
1095    #[kani::unwind(5)]
1096    fn cdr_writer_remaining_consistent() {
1097        const BUF_LEN: usize = 32;
1098        let mut buf = [0u8; BUF_LEN];
1099        let mut writer = CdrWriter::new(&mut buf);
1100        assert_eq!(writer.position() + writer.remaining(), BUF_LEN);
1101
1102        let val: u32 = kani::any();
1103        let _ = writer.write_u32(val);
1104        assert_eq!(writer.position() + writer.remaining(), BUF_LEN);
1105    }
1106}