Skip to main content

hdds_micro/cdr/
mod.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2// Copyright (c) 2025-2026 naskel.com
3
4//! CDR Micro - Lightweight CDR encoder/decoder for embedded
5//!
6//! Implements a minimal subset of CDR2 (Common Data Representation v2.0)
7//! with fixed buffers and no heap allocations.
8//!
9//! ## Supported Types
10//!
11//! - Primitives: u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, bool
12//! - Fixed-size arrays: [T; N]
13//! - Strings: &str (with length prefix)
14//!
15//! ## Limitations
16//!
17//! - No sequences (unbounded arrays) - use fixed arrays instead
18//! - No optional fields - all fields are required
19//! - Little-endian only (for simplicity on embedded targets)
20
21use crate::error::{Error, Result};
22
23/// CDR Encoder with fixed buffer
24///
25/// # Example
26///
27/// ```ignore
28/// let mut buf = [0u8; 256];
29/// let mut encoder = CdrEncoder::new(&mut buf);
30///
31/// encoder.encode_u32(42)?;
32/// encoder.encode_string("hello")?;
33///
34/// let bytes = encoder.finish();
35/// ```
36pub struct CdrEncoder<'a> {
37    buf: &'a mut [u8],
38    pos: usize,
39}
40
41impl<'a> CdrEncoder<'a> {
42    /// Create a new CDR encoder
43    pub fn new(buf: &'a mut [u8]) -> Self {
44        Self { buf, pos: 0 }
45    }
46
47    /// Get current position
48    pub const fn position(&self) -> usize {
49        self.pos
50    }
51
52    /// Finish encoding and return written bytes
53    pub fn finish(self) -> &'a [u8] {
54        &self.buf[0..self.pos]
55    }
56
57    /// Align to boundary (CDR alignment rules)
58    fn align(&mut self, alignment: usize) -> Result<()> {
59        let remainder = self.pos % alignment;
60        if remainder != 0 {
61            let padding = alignment - remainder;
62            if self.pos + padding > self.buf.len() {
63                return Err(Error::BufferTooSmall);
64            }
65            // Zero-fill padding
66            for i in 0..padding {
67                self.buf[self.pos + i] = 0;
68            }
69            self.pos += padding;
70        }
71        Ok(())
72    }
73
74    /// Write bytes
75    fn write_bytes(&mut self, bytes: &[u8]) -> Result<()> {
76        if self.pos + bytes.len() > self.buf.len() {
77            return Err(Error::BufferTooSmall);
78        }
79        self.buf[self.pos..self.pos + bytes.len()].copy_from_slice(bytes);
80        self.pos += bytes.len();
81        Ok(())
82    }
83
84    /// Encode u8
85    pub fn encode_u8(&mut self, value: u8) -> Result<()> {
86        self.write_bytes(&[value])
87    }
88
89    /// Encode i8
90    pub fn encode_i8(&mut self, value: i8) -> Result<()> {
91        self.write_bytes(&[value as u8])
92    }
93
94    /// Encode bool
95    pub fn encode_bool(&mut self, value: bool) -> Result<()> {
96        self.encode_u8(if value { 1 } else { 0 })
97    }
98
99    /// Encode u16
100    pub fn encode_u16(&mut self, value: u16) -> Result<()> {
101        self.align(2)?;
102        self.write_bytes(&value.to_le_bytes())
103    }
104
105    /// Encode i16
106    pub fn encode_i16(&mut self, value: i16) -> Result<()> {
107        self.align(2)?;
108        self.write_bytes(&value.to_le_bytes())
109    }
110
111    /// Encode u32
112    pub fn encode_u32(&mut self, value: u32) -> Result<()> {
113        self.align(4)?;
114        self.write_bytes(&value.to_le_bytes())
115    }
116
117    /// Encode i32
118    pub fn encode_i32(&mut self, value: i32) -> Result<()> {
119        self.align(4)?;
120        self.write_bytes(&value.to_le_bytes())
121    }
122
123    /// Encode u64
124    pub fn encode_u64(&mut self, value: u64) -> Result<()> {
125        self.align(8)?;
126        self.write_bytes(&value.to_le_bytes())
127    }
128
129    /// Encode i64
130    pub fn encode_i64(&mut self, value: i64) -> Result<()> {
131        self.align(8)?;
132        self.write_bytes(&value.to_le_bytes())
133    }
134
135    /// Encode f32
136    pub fn encode_f32(&mut self, value: f32) -> Result<()> {
137        self.align(4)?;
138        self.write_bytes(&value.to_le_bytes())
139    }
140
141    /// Encode f64
142    pub fn encode_f64(&mut self, value: f64) -> Result<()> {
143        self.align(8)?;
144        self.write_bytes(&value.to_le_bytes())
145    }
146
147    /// Encode string (length-prefixed)
148    pub fn encode_string(&mut self, value: &str) -> Result<()> {
149        let bytes = value.as_bytes();
150        let len = bytes.len() as u32;
151
152        // Encode length (including null terminator)
153        self.encode_u32(len + 1)?;
154
155        // Encode string bytes
156        self.write_bytes(bytes)?;
157
158        // Null terminator
159        self.encode_u8(0)?;
160
161        Ok(())
162    }
163
164    /// Encode byte array
165    pub fn encode_bytes(&mut self, bytes: &[u8]) -> Result<()> {
166        self.write_bytes(bytes)
167    }
168
169    /// Encode sequence length prefix (u32)
170    ///
171    /// Used for encoding bounded sequences/arrays with variable length.
172    pub fn encode_seq_len(&mut self, len: usize) -> Result<()> {
173        self.encode_u32(len as u32)
174    }
175}
176
177/// CDR Decoder with fixed buffer
178///
179/// # Example
180///
181/// ```ignore
182/// let mut decoder = CdrDecoder::new(&buf);
183///
184/// let value: u32 = decoder.decode_u32()?;
185/// let text = decoder.decode_string_borrowed()?;
186/// ```
187pub struct CdrDecoder<'a> {
188    buf: &'a [u8],
189    pos: usize,
190}
191
192impl<'a> CdrDecoder<'a> {
193    /// Create a new CDR decoder
194    pub const fn new(buf: &'a [u8]) -> Self {
195        Self { buf, pos: 0 }
196    }
197
198    /// Get current position
199    pub const fn position(&self) -> usize {
200        self.pos
201    }
202
203    /// Get remaining bytes
204    pub const fn remaining(&self) -> usize {
205        self.buf.len() - self.pos
206    }
207
208    /// Align to boundary
209    fn align(&mut self, alignment: usize) -> Result<()> {
210        let remainder = self.pos % alignment;
211        if remainder != 0 {
212            let padding = alignment - remainder;
213            if self.pos + padding > self.buf.len() {
214                return Err(Error::BufferTooSmall);
215            }
216            self.pos += padding;
217        }
218        Ok(())
219    }
220
221    /// Read bytes
222    fn read_bytes(&mut self, count: usize) -> Result<&'a [u8]> {
223        if self.pos + count > self.buf.len() {
224            return Err(Error::BufferTooSmall);
225        }
226        let bytes = &self.buf[self.pos..self.pos + count];
227        self.pos += count;
228        Ok(bytes)
229    }
230
231    /// Decode u8
232    pub fn decode_u8(&mut self) -> Result<u8> {
233        let bytes = self.read_bytes(1)?;
234        Ok(bytes[0])
235    }
236
237    /// Decode i8
238    pub fn decode_i8(&mut self) -> Result<i8> {
239        Ok(self.decode_u8()? as i8)
240    }
241
242    /// Decode bool
243    pub fn decode_bool(&mut self) -> Result<bool> {
244        Ok(self.decode_u8()? != 0)
245    }
246
247    /// Decode u16
248    pub fn decode_u16(&mut self) -> Result<u16> {
249        self.align(2)?;
250        let bytes = self.read_bytes(2)?;
251        let mut arr = [0u8; 2];
252        arr.copy_from_slice(bytes);
253        Ok(u16::from_le_bytes(arr))
254    }
255
256    /// Decode i16
257    pub fn decode_i16(&mut self) -> Result<i16> {
258        self.align(2)?;
259        let bytes = self.read_bytes(2)?;
260        let mut arr = [0u8; 2];
261        arr.copy_from_slice(bytes);
262        Ok(i16::from_le_bytes(arr))
263    }
264
265    /// Decode u32
266    pub fn decode_u32(&mut self) -> Result<u32> {
267        self.align(4)?;
268        let bytes = self.read_bytes(4)?;
269        let mut arr = [0u8; 4];
270        arr.copy_from_slice(bytes);
271        Ok(u32::from_le_bytes(arr))
272    }
273
274    /// Decode i32
275    pub fn decode_i32(&mut self) -> Result<i32> {
276        self.align(4)?;
277        let bytes = self.read_bytes(4)?;
278        let mut arr = [0u8; 4];
279        arr.copy_from_slice(bytes);
280        Ok(i32::from_le_bytes(arr))
281    }
282
283    /// Decode u64
284    pub fn decode_u64(&mut self) -> Result<u64> {
285        self.align(8)?;
286        let bytes = self.read_bytes(8)?;
287        let mut arr = [0u8; 8];
288        arr.copy_from_slice(bytes);
289        Ok(u64::from_le_bytes(arr))
290    }
291
292    /// Decode i64
293    pub fn decode_i64(&mut self) -> Result<i64> {
294        self.align(8)?;
295        let bytes = self.read_bytes(8)?;
296        let mut arr = [0u8; 8];
297        arr.copy_from_slice(bytes);
298        Ok(i64::from_le_bytes(arr))
299    }
300
301    /// Decode f32
302    pub fn decode_f32(&mut self) -> Result<f32> {
303        self.align(4)?;
304        let bytes = self.read_bytes(4)?;
305        let mut arr = [0u8; 4];
306        arr.copy_from_slice(bytes);
307        Ok(f32::from_le_bytes(arr))
308    }
309
310    /// Decode f64
311    pub fn decode_f64(&mut self) -> Result<f64> {
312        self.align(8)?;
313        let bytes = self.read_bytes(8)?;
314        let mut arr = [0u8; 8];
315        arr.copy_from_slice(bytes);
316        Ok(f64::from_le_bytes(arr))
317    }
318
319    /// Decode string (borrowed, zero-copy)
320    ///
321    /// Returns &str pointing to buffer (without null terminator)
322    pub fn decode_string_borrowed(&mut self) -> Result<&'a str> {
323        // Decode length (includes null terminator)
324        let len = self.decode_u32()? as usize;
325
326        if len == 0 {
327            return Err(Error::DecodingError);
328        }
329
330        // Read string bytes (without null terminator)
331        let bytes = self.read_bytes(len - 1)?;
332
333        // Skip null terminator
334        self.decode_u8()?;
335
336        // Convert to &str
337        core::str::from_utf8(bytes).map_err(|_| Error::DecodingError)
338    }
339
340    /// Decode byte array (borrowed)
341    pub fn decode_bytes(&mut self, count: usize) -> Result<&'a [u8]> {
342        self.read_bytes(count)
343    }
344
345    /// Decode sequence length prefix (u32)
346    ///
347    /// Used for decoding bounded sequences/arrays with variable length.
348    pub fn decode_seq_len(&mut self) -> Result<usize> {
349        Ok(self.decode_u32()? as usize)
350    }
351
352    /// Decode string into owned heapless::String
353    ///
354    /// Returns a heapless::String with capacity N. Fails if the decoded
355    /// string is longer than N bytes.
356    #[cfg(feature = "heapless")]
357    pub fn decode_string<const N: usize>(&mut self) -> Result<heapless::String<N>> {
358        let s = self.decode_string_borrowed()?;
359        heapless::String::try_from(s).map_err(|_| Error::BufferTooSmall)
360    }
361}
362
363#[cfg(test)]
364mod tests {
365    use super::*;
366
367    #[test]
368    fn test_encode_decode_u32() {
369        let mut buf = [0u8; 16];
370        let mut encoder = CdrEncoder::new(&mut buf);
371        encoder.encode_u32(0x1234_5678).unwrap();
372
373        let bytes = encoder.finish();
374        let mut decoder = CdrDecoder::new(bytes);
375        let value = decoder.decode_u32().unwrap();
376
377        assert_eq!(value, 0x1234_5678);
378    }
379
380    #[test]
381    fn test_encode_decode_string() {
382        let mut buf = [0u8; 64];
383        let mut encoder = CdrEncoder::new(&mut buf);
384        encoder.encode_string("hello").unwrap();
385
386        let bytes = encoder.finish();
387        let mut decoder = CdrDecoder::new(bytes);
388        let value = decoder.decode_string_borrowed().unwrap();
389
390        assert_eq!(value, "hello");
391    }
392
393    #[test]
394    fn test_alignment() {
395        let mut buf = [0u8; 64];
396        let mut encoder = CdrEncoder::new(&mut buf);
397
398        encoder.encode_u8(0x11).unwrap(); // pos = 1
399        encoder.encode_u32(0x2222_2222).unwrap(); // should align to 4, then write
400
401        assert_eq!(encoder.position(), 8); // 1 + 3 (padding) + 4
402    }
403
404    #[test]
405    fn test_mixed_types() {
406        let mut buf = [0u8; 128];
407        let mut encoder = CdrEncoder::new(&mut buf);
408
409        encoder.encode_bool(true).unwrap();
410        encoder.encode_i16(-42).unwrap();
411        encoder.encode_f32(2.72).unwrap();
412        encoder.encode_string("test").unwrap();
413
414        let bytes = encoder.finish();
415        let mut decoder = CdrDecoder::new(bytes);
416
417        assert!(decoder.decode_bool().unwrap());
418        assert_eq!(decoder.decode_i16().unwrap(), -42);
419        assert!((decoder.decode_f32().unwrap() - 2.72).abs() < 0.01);
420        assert_eq!(decoder.decode_string_borrowed().unwrap(), "test");
421    }
422
423    #[test]
424    fn test_buffer_too_small() {
425        let mut buf = [0u8; 2];
426        let mut encoder = CdrEncoder::new(&mut buf);
427
428        let result = encoder.encode_u32(42);
429        assert_eq!(result, Err(Error::BufferTooSmall));
430    }
431}