Skip to main content

edgefirst_schemas/sensor_msgs/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright © 2025 Au-Zone Technologies. All Rights Reserved.
3
4//! ROS 2 `sensor_msgs` message types.
5//!
6//! CdrFixed: `NavSatStatus`, `RegionOfInterest`
7//!
8//! Buffer-backed: `Image`, `CompressedImage`, `Imu`, `NavSatFix`,
9//! `PointCloud2`, `PointField` (with `PointFieldView`), `CameraInfo`
10//!
11//! Pointcloud access: [`pointcloud`] module provides zero-copy
12//! [`DynPointCloud`](pointcloud::DynPointCloud) and
13//! [`PointCloud<P>`](pointcloud::PointCloud) views over PointCloud2 data.
14
15pub mod pointcloud;
16
17use crate::builtin_interfaces::Time;
18use crate::cdr::*;
19use crate::geometry_msgs::{Quaternion, Vector3};
20use crate::std_msgs::Header;
21
22// ── CdrFixed types ──────────────────────────────────────────────────
23
24#[derive(PartialEq, Clone, Copy, Debug)]
25pub struct NavSatStatus {
26    pub status: i8,
27    pub service: u16,
28}
29
30impl CdrFixed for NavSatStatus {
31    // WARNING: position-dependent. 4 bytes only when embedded at an even
32    // CDR-relative offset; 3 bytes at odd relative offsets (no pre-u16 pad).
33    // Do NOT compute post-field positions as `offsets[0] + CDR_SIZE` — use
34    // a cursor-captured offset instead. See CdrFixed trait docs and
35    // NavSatFix::from_cdr for the correct pattern. EDGEAI-1243.
36    const CDR_SIZE: usize = 4; // i8(1) + pad(1) + u16(2)
37    fn read_cdr(cursor: &mut CdrCursor<'_>) -> Result<Self, CdrError> {
38        let status = cursor.read_i8()?;
39        let service = cursor.read_u16()?;
40        Ok(NavSatStatus { status, service })
41    }
42    fn write_cdr(&self, writer: &mut CdrWriter<'_>) {
43        writer.write_i8(self.status);
44        writer.write_u16(self.service);
45    }
46    fn size_cdr(sizer: &mut CdrSizer) {
47        sizer.size_i8();
48        sizer.size_u16();
49    }
50}
51
52#[derive(PartialEq, Clone, Copy, Debug)]
53pub struct RegionOfInterest {
54    pub x_offset: u32,
55    pub y_offset: u32,
56    pub height: u32,
57    pub width: u32,
58    pub do_rectify: bool,
59}
60
61impl CdrFixed for RegionOfInterest {
62    const CDR_SIZE: usize = 17; // 4 x u32(16) + bool(1)
63    fn read_cdr(cursor: &mut CdrCursor<'_>) -> Result<Self, CdrError> {
64        Ok(RegionOfInterest {
65            x_offset: cursor.read_u32()?,
66            y_offset: cursor.read_u32()?,
67            height: cursor.read_u32()?,
68            width: cursor.read_u32()?,
69            do_rectify: cursor.read_bool()?,
70        })
71    }
72    fn write_cdr(&self, writer: &mut CdrWriter<'_>) {
73        writer.write_u32(self.x_offset);
74        writer.write_u32(self.y_offset);
75        writer.write_u32(self.height);
76        writer.write_u32(self.width);
77        writer.write_bool(self.do_rectify);
78    }
79    fn size_cdr(sizer: &mut CdrSizer) {
80        sizer.size_u32();
81        sizer.size_u32();
82        sizer.size_u32();
83        sizer.size_u32();
84        sizer.size_bool();
85    }
86}
87
88// ── Helper arrays ───────────────────────────────────────────────────
89
90fn read_f64_array9(c: &mut CdrCursor<'_>) -> Result<[f64; 9], CdrError> {
91    Ok([
92        c.read_f64()?,
93        c.read_f64()?,
94        c.read_f64()?,
95        c.read_f64()?,
96        c.read_f64()?,
97        c.read_f64()?,
98        c.read_f64()?,
99        c.read_f64()?,
100        c.read_f64()?,
101    ])
102}
103
104fn write_f64_array9(w: &mut CdrWriter<'_>, a: &[f64; 9]) {
105    for v in a {
106        w.write_f64(*v);
107    }
108}
109
110fn size_f64_array9(s: &mut CdrSizer) {
111    for _ in 0..9 {
112        s.size_f64();
113    }
114}
115
116fn read_f64_array12(c: &mut CdrCursor<'_>) -> Result<[f64; 12], CdrError> {
117    Ok([
118        c.read_f64()?,
119        c.read_f64()?,
120        c.read_f64()?,
121        c.read_f64()?,
122        c.read_f64()?,
123        c.read_f64()?,
124        c.read_f64()?,
125        c.read_f64()?,
126        c.read_f64()?,
127        c.read_f64()?,
128        c.read_f64()?,
129        c.read_f64()?,
130    ])
131}
132
133fn write_f64_array12(w: &mut CdrWriter<'_>, a: &[f64; 12]) {
134    for v in a {
135        w.write_f64(*v);
136    }
137}
138
139fn size_f64_array12(s: &mut CdrSizer) {
140    for _ in 0..12 {
141        s.size_f64();
142    }
143}
144
145// ── PointField helpers ──────────────────────────────────────────────
146
147/// Zero-copy view of a PointField element within a CDR sequence.
148pub struct PointFieldView<'a> {
149    pub name: &'a str,
150    pub offset: u32,
151    pub datatype: u8,
152    pub count: u32,
153}
154
155fn scan_point_field_element<'a>(c: &mut CdrCursor<'a>) -> Result<PointFieldView<'a>, CdrError> {
156    let name = c.read_string()?;
157    let offset = c.read_u32()?;
158    let datatype = c.read_u8()?;
159    let count = c.read_u32()?;
160    Ok(PointFieldView {
161        name,
162        offset,
163        datatype,
164        count,
165    })
166}
167
168fn write_point_field_element(w: &mut CdrWriter<'_>, f: &PointFieldView<'_>) {
169    w.write_string(f.name);
170    w.write_u32(f.offset);
171    w.write_u8(f.datatype);
172    w.write_u32(f.count);
173}
174
175fn size_point_field_element(s: &mut CdrSizer, name: &str) {
176    s.size_string(name);
177    s.size_u32();
178    s.size_u8();
179    s.size_u32();
180}
181
182/// Non-allocating iterator over PointField descriptors in a PointCloud2.
183///
184/// Yields [`PointFieldView`] items by parsing the CDR field array on
185/// each call to `next()`. No heap allocation occurs — the iterator
186/// borrows directly from the PointCloud2 buffer.
187///
188/// Implements [`ExactSizeIterator`], so `iter.len()` returns the
189/// remaining field count.
190pub struct PointFieldIter<'a> {
191    cursor: CdrCursor<'a>,
192    remaining: usize,
193}
194
195impl<'a> Iterator for PointFieldIter<'a> {
196    type Item = PointFieldView<'a>;
197
198    fn next(&mut self) -> Option<Self::Item> {
199        if self.remaining == 0 {
200            return None;
201        }
202        self.remaining -= 1;
203        Some(
204            scan_point_field_element(&mut self.cursor)
205                .expect("point field elements validated during from_cdr"),
206        )
207    }
208
209    fn size_hint(&self) -> (usize, Option<usize>) {
210        (self.remaining, Some(self.remaining))
211    }
212}
213
214impl ExactSizeIterator for PointFieldIter<'_> {}
215
216// ── Buffer-backed types ─────────────────────────────────────────────
217
218// ── CompressedImage<B> ──────────────────────────────────────────────
219//
220// CDR layout:
221//   4: stamp (8 bytes)
222//  12: frame_id (string) → offsets[0]
223//   ~: format (string) → offsets[1]
224//   ~: data (byte seq) → offsets[2]
225
226pub struct CompressedImage<B> {
227    buf: B,
228    offsets: [usize; 3],
229}
230
231impl<B> CompressedImage<B> {
232    /// Convert the buffer type without re-parsing the offset table.
233    #[inline]
234    pub fn map_buffer<C>(self, f: impl FnOnce(B) -> C) -> CompressedImage<C> {
235        CompressedImage {
236            buf: f(self.buf),
237            offsets: self.offsets,
238        }
239    }
240}
241
242impl<B: AsRef<[u8]>> CompressedImage<B> {
243    pub fn from_cdr(buf: B) -> Result<Self, CdrError> {
244        let header = Header::<&[u8]>::from_cdr(buf.as_ref())?;
245        let o0 = header.end_offset();
246        let mut c = CdrCursor::resume(buf.as_ref(), o0);
247        let _ = c.read_string()?; // format
248        let o1 = c.offset();
249        let _ = c.read_bytes()?; // data
250        let o2 = c.offset();
251        Ok(CompressedImage {
252            offsets: [o0, o1, o2],
253            buf,
254        })
255    }
256
257    /// Returns a `Header` view (re-parses CDR prefix; prefer `stamp()`/`frame_id()`).
258    pub fn header(&self) -> Header<&[u8]> {
259        Header::from_cdr(self.buf.as_ref()).expect("header bytes validated during from_cdr")
260    }
261    pub fn stamp(&self) -> Time {
262        rd_time(self.buf.as_ref(), CDR_HEADER_SIZE)
263    }
264    pub fn frame_id(&self) -> &str {
265        rd_string(self.buf.as_ref(), CDR_HEADER_SIZE + 8).0
266    }
267    pub fn format(&self) -> &str {
268        rd_string(self.buf.as_ref(), self.offsets[0]).0
269    }
270    pub fn data(&self) -> &[u8] {
271        rd_bytes(self.buf.as_ref(), self.offsets[1]).0
272    }
273    pub fn as_cdr(&self) -> &[u8] {
274        self.buf.as_ref()
275    }
276    pub fn cdr_size(&self) -> usize {
277        self.buf.as_ref().len()
278    }
279    pub fn to_cdr(&self) -> Vec<u8> {
280        self.buf.as_ref().to_vec()
281    }
282}
283
284impl CompressedImage<Vec<u8>> {
285    #[deprecated(
286        since = "3.2.0",
287        note = "use CompressedImage::builder() for allocation-free buffer reuse; CompressedImage::new will be removed in 4.0"
288    )]
289    pub fn new(stamp: Time, frame_id: &str, format: &str, data: &[u8]) -> Result<Self, CdrError> {
290        let mut sizer = CdrSizer::new();
291        Time::size_cdr(&mut sizer);
292        sizer.size_string(frame_id);
293        let o0 = sizer.offset();
294        sizer.size_string(format);
295        let o1 = sizer.offset();
296        sizer.size_bytes(data.len());
297        let o2 = sizer.offset();
298
299        let mut buf = vec![0u8; sizer.size()];
300        let mut w = CdrWriter::new(&mut buf)?;
301        stamp.write_cdr(&mut w);
302        w.write_string(frame_id);
303        w.write_string(format);
304        w.write_bytes(data);
305        w.finish()?;
306
307        Ok(CompressedImage {
308            offsets: [o0, o1, o2],
309            buf,
310        })
311    }
312
313    pub fn into_cdr(self) -> Vec<u8> {
314        self.buf
315    }
316
317    /// Start a new `CompressedImageBuilder` with zero-valued defaults.
318    ///
319    /// Generic in `'a` so the compiler infers it from subsequent setter
320    /// borrows rather than forcing `'static`.
321    pub fn builder<'a>() -> CompressedImageBuilder<'a> {
322        CompressedImageBuilder::new()
323    }
324}
325
326// ── CompressedImageBuilder<'a> ──────────────────────────────────────
327
328/// Builder for `CompressedImage<Vec<u8>>` with buffer-reuse finalizers.
329pub struct CompressedImageBuilder<'a> {
330    stamp: Time,
331    frame_id: std::borrow::Cow<'a, str>,
332    format: std::borrow::Cow<'a, str>,
333    data: &'a [u8],
334}
335
336impl<'a> Default for CompressedImageBuilder<'a> {
337    fn default() -> Self {
338        Self {
339            stamp: Time { sec: 0, nanosec: 0 },
340            frame_id: std::borrow::Cow::Borrowed(""),
341            format: std::borrow::Cow::Borrowed(""),
342            data: &[],
343        }
344    }
345}
346
347impl<'a> CompressedImageBuilder<'a> {
348    pub fn new() -> Self {
349        Self::default()
350    }
351
352    pub fn stamp(&mut self, t: Time) -> &mut Self {
353        self.stamp = t;
354        self
355    }
356    pub fn frame_id(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
357        self.frame_id = s.into();
358        self
359    }
360    pub fn format(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
361        self.format = s.into();
362        self
363    }
364    pub fn data(&mut self, d: &'a [u8]) -> &mut Self {
365        self.data = d;
366        self
367    }
368
369    fn size(&self) -> usize {
370        let mut s = CdrSizer::new();
371        Time::size_cdr(&mut s);
372        s.size_string(&self.frame_id);
373        s.size_string(&self.format);
374        s.size_bytes(self.data.len());
375        s.size()
376    }
377
378    fn write_into(&self, buf: &mut [u8]) -> Result<(), CdrError> {
379        let mut w = CdrWriter::new(buf)?;
380        self.stamp.write_cdr(&mut w);
381        w.write_string(&self.frame_id);
382        w.write_string(&self.format);
383        w.write_bytes(self.data);
384        w.finish()
385    }
386
387    /// Allocate a fresh `Vec<u8>` and return a fully-parsed `CompressedImage<Vec<u8>>`.
388    pub fn build(&self) -> Result<CompressedImage<Vec<u8>>, CdrError> {
389        let mut buf = vec![0u8; self.size()];
390        self.write_into(&mut buf)?;
391        CompressedImage::from_cdr(buf)
392    }
393
394    /// Serialize into the caller's `Vec<u8>`, resizing to exactly the encoded size.
395    pub fn encode_into_vec(&self, buf: &mut Vec<u8>) -> Result<(), CdrError> {
396        buf.resize(self.size(), 0);
397        self.write_into(buf)
398    }
399
400    /// Serialize into `buf` and return bytes written. Errors with `BufferTooShort`
401    /// when `buf` is smaller than the required size; nothing is mutated in that case.
402    pub fn encode_into_slice(&self, buf: &mut [u8]) -> Result<usize, CdrError> {
403        let need = self.size();
404        if buf.len() < need {
405            return Err(CdrError::BufferTooShort {
406                need,
407                have: buf.len(),
408            });
409        }
410        self.write_into(&mut buf[..need])?;
411        Ok(need)
412    }
413}
414
415impl<B: AsRef<[u8]> + AsMut<[u8]>> CompressedImage<B> {
416    pub fn set_stamp(&mut self, t: Time) -> Result<(), CdrError> {
417        let b = self.buf.as_mut();
418        wr_i32(b, CDR_HEADER_SIZE, t.sec)?;
419        wr_u32(b, CDR_HEADER_SIZE + 4, t.nanosec)
420    }
421}
422
423// ── Image<B> ────────────────────────────────────────────────────────
424//
425// CDR layout:
426//   4: stamp (8 bytes)
427//  12: frame_id (string) → offsets[0]
428//   ~: height (u32), width (u32)
429//   ~: encoding (string) → offsets[1]
430//   ~: is_bigendian (u8)
431//   ~: step (u32)
432//   ~: data (byte seq) → offsets[2]
433
434pub struct Image<B> {
435    buf: B,
436    offsets: [usize; 3],
437}
438
439impl<B> Image<B> {
440    /// Convert the buffer type without re-parsing the offset table.
441    #[inline]
442    pub fn map_buffer<C>(self, f: impl FnOnce(B) -> C) -> Image<C> {
443        Image {
444            buf: f(self.buf),
445            offsets: self.offsets,
446        }
447    }
448}
449
450impl<B: AsRef<[u8]>> Image<B> {
451    pub fn from_cdr(buf: B) -> Result<Self, CdrError> {
452        let header = Header::<&[u8]>::from_cdr(buf.as_ref())?;
453        let o0 = header.end_offset();
454        let mut c = CdrCursor::resume(buf.as_ref(), o0);
455        let _ = c.read_u32()?; // height
456        let _ = c.read_u32()?; // width
457        let _ = c.read_string()?; // encoding
458        let o1 = c.offset();
459        let _ = c.read_u8()?; // is_bigendian
460        let _ = c.read_u32()?; // step
461        let _ = c.read_bytes()?; // data
462        let o2 = c.offset();
463        Ok(Image {
464            offsets: [o0, o1, o2],
465            buf,
466        })
467    }
468
469    /// Returns a `Header` view (re-parses CDR prefix; prefer `stamp()`/`frame_id()`).
470    pub fn header(&self) -> Header<&[u8]> {
471        Header::from_cdr(self.buf.as_ref()).expect("header bytes validated during from_cdr")
472    }
473    pub fn stamp(&self) -> Time {
474        rd_time(self.buf.as_ref(), CDR_HEADER_SIZE)
475    }
476    pub fn frame_id(&self) -> &str {
477        rd_string(self.buf.as_ref(), CDR_HEADER_SIZE + 8).0
478    }
479
480    pub fn height(&self) -> u32 {
481        let p = align(self.offsets[0], 4);
482        rd_u32(self.buf.as_ref(), p)
483    }
484
485    pub fn width(&self) -> u32 {
486        let p = align(self.offsets[0], 4) + 4;
487        rd_u32(self.buf.as_ref(), p)
488    }
489
490    pub fn encoding(&self) -> &str {
491        let p = align(self.offsets[0], 4) + 8;
492        rd_string(self.buf.as_ref(), p).0
493    }
494
495    pub fn is_bigendian(&self) -> u8 {
496        rd_u8(self.buf.as_ref(), self.offsets[1])
497    }
498
499    pub fn step(&self) -> u32 {
500        let p = align(self.offsets[1] + 1, 4);
501        rd_u32(self.buf.as_ref(), p)
502    }
503
504    pub fn data(&self) -> &[u8] {
505        let p = align(self.offsets[1] + 1, 4) + 4;
506        rd_bytes(self.buf.as_ref(), p).0
507    }
508
509    pub fn as_cdr(&self) -> &[u8] {
510        self.buf.as_ref()
511    }
512    pub fn cdr_size(&self) -> usize {
513        self.buf.as_ref().len()
514    }
515    pub fn to_cdr(&self) -> Vec<u8> {
516        self.buf.as_ref().to_vec()
517    }
518}
519
520impl Image<Vec<u8>> {
521    #[deprecated(
522        since = "3.2.0",
523        note = "use Image::builder() for allocation-free buffer reuse; Image::new will be removed in 4.0"
524    )]
525    pub fn new(
526        stamp: Time,
527        frame_id: &str,
528        height: u32,
529        width: u32,
530        encoding: &str,
531        is_bigendian: u8,
532        step: u32,
533        data: &[u8],
534    ) -> Result<Self, CdrError> {
535        let mut sizer = CdrSizer::new();
536        Time::size_cdr(&mut sizer);
537        sizer.size_string(frame_id);
538        let o0 = sizer.offset();
539        sizer.size_u32(); // height
540        sizer.size_u32(); // width
541        sizer.size_string(encoding);
542        let o1 = sizer.offset();
543        sizer.size_u8(); // is_bigendian
544        sizer.size_u32(); // step
545        sizer.size_bytes(data.len());
546        let o2 = sizer.offset();
547
548        let mut buf = vec![0u8; sizer.size()];
549        let mut w = CdrWriter::new(&mut buf)?;
550        stamp.write_cdr(&mut w);
551        w.write_string(frame_id);
552        w.write_u32(height);
553        w.write_u32(width);
554        w.write_string(encoding);
555        w.write_u8(is_bigendian);
556        w.write_u32(step);
557        w.write_bytes(data);
558        w.finish()?;
559
560        Ok(Image {
561            offsets: [o0, o1, o2],
562            buf,
563        })
564    }
565
566    pub fn into_cdr(self) -> Vec<u8> {
567        self.buf
568    }
569
570    /// Start a new `ImageBuilder` with zero-valued defaults.
571    ///
572    /// Returned with a generic lifetime parameter `'a` so the compiler
573    /// infers it from subsequent setter calls — `.data(&local_pixels)`
574    /// binds `'a` to the caller's scope without forcing `'static`.
575    pub fn builder<'a>() -> ImageBuilder<'a> {
576        ImageBuilder::new()
577    }
578}
579
580impl<B: AsRef<[u8]> + AsMut<[u8]>> Image<B> {
581    pub fn set_stamp(&mut self, t: Time) -> Result<(), CdrError> {
582        let b = self.buf.as_mut();
583        wr_i32(b, CDR_HEADER_SIZE, t.sec)?;
584        wr_u32(b, CDR_HEADER_SIZE + 4, t.nanosec)
585    }
586
587    pub fn set_height(&mut self, h: u32) -> Result<(), CdrError> {
588        let p = align(self.offsets[0], 4);
589        wr_u32(self.buf.as_mut(), p, h)
590    }
591
592    pub fn set_width(&mut self, w: u32) -> Result<(), CdrError> {
593        let p = align(self.offsets[0], 4) + 4;
594        wr_u32(self.buf.as_mut(), p, w)
595    }
596
597    pub fn set_is_bigendian(&mut self, v: u8) -> Result<(), CdrError> {
598        wr_u8(self.buf.as_mut(), self.offsets[1], v)
599    }
600
601    pub fn set_step(&mut self, v: u32) -> Result<(), CdrError> {
602        let p = align(self.offsets[1] + 1, 4);
603        wr_u32(self.buf.as_mut(), p, v)
604    }
605}
606
607// ── ImageBuilder<'a> ────────────────────────────────────────────────
608
609/// Builder for `Image<Vec<u8>>` with buffer-reuse finalizers.
610///
611/// Strings use `Cow<'a, str>` so that `frame_id("lit")` borrows a `&'static str`
612/// and `frame_id(owned)` takes ownership. Bulk `data` is always borrowed for
613/// zero-copy input semantics; the borrow must remain valid until `build()`,
614/// `encode_into_vec()`, or `encode_into_slice()` is called.
615pub struct ImageBuilder<'a> {
616    stamp: Time,
617    frame_id: std::borrow::Cow<'a, str>,
618    height: u32,
619    width: u32,
620    encoding: std::borrow::Cow<'a, str>,
621    is_bigendian: u8,
622    step: u32,
623    data: &'a [u8],
624}
625
626impl<'a> Default for ImageBuilder<'a> {
627    fn default() -> Self {
628        Self {
629            stamp: Time { sec: 0, nanosec: 0 },
630            frame_id: std::borrow::Cow::Borrowed(""),
631            height: 0,
632            width: 0,
633            encoding: std::borrow::Cow::Borrowed(""),
634            is_bigendian: 0,
635            step: 0,
636            data: &[],
637        }
638    }
639}
640
641impl<'a> ImageBuilder<'a> {
642    pub fn new() -> Self {
643        Self::default()
644    }
645
646    pub fn stamp(&mut self, t: Time) -> &mut Self {
647        self.stamp = t;
648        self
649    }
650    pub fn frame_id(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
651        self.frame_id = s.into();
652        self
653    }
654    pub fn height(&mut self, h: u32) -> &mut Self {
655        self.height = h;
656        self
657    }
658    pub fn width(&mut self, w: u32) -> &mut Self {
659        self.width = w;
660        self
661    }
662    pub fn encoding(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
663        self.encoding = s.into();
664        self
665    }
666    pub fn is_bigendian(&mut self, v: u8) -> &mut Self {
667        self.is_bigendian = v;
668        self
669    }
670    pub fn step(&mut self, v: u32) -> &mut Self {
671        self.step = v;
672        self
673    }
674    pub fn data(&mut self, d: &'a [u8]) -> &mut Self {
675        self.data = d;
676        self
677    }
678
679    fn size(&self) -> usize {
680        let mut s = CdrSizer::new();
681        Time::size_cdr(&mut s);
682        s.size_string(&self.frame_id);
683        s.size_u32(); // height
684        s.size_u32(); // width
685        s.size_string(&self.encoding);
686        s.size_u8(); // is_bigendian
687        s.size_u32(); // step
688        s.size_bytes(self.data.len());
689        s.size()
690    }
691
692    fn write_into(&self, buf: &mut [u8]) -> Result<(), CdrError> {
693        let mut w = CdrWriter::new(buf)?;
694        self.stamp.write_cdr(&mut w);
695        w.write_string(&self.frame_id);
696        w.write_u32(self.height);
697        w.write_u32(self.width);
698        w.write_string(&self.encoding);
699        w.write_u8(self.is_bigendian);
700        w.write_u32(self.step);
701        w.write_bytes(self.data);
702        w.finish()
703    }
704
705    /// Allocate a fresh `Vec<u8>` and return a fully-parsed `Image<Vec<u8>>`.
706    pub fn build(&self) -> Result<Image<Vec<u8>>, CdrError> {
707        let mut buf = vec![0u8; self.size()];
708        self.write_into(&mut buf)?;
709        Image::from_cdr(buf)
710    }
711
712    /// Serialize into the caller's `Vec<u8>`, resizing to exactly the encoded
713    /// size. After return, `buf.len()` is the CDR size and `&buf[..]` is a
714    /// complete CDR message. Reuses existing allocation when capacity suffices.
715    pub fn encode_into_vec(&self, buf: &mut Vec<u8>) -> Result<(), CdrError> {
716        buf.resize(self.size(), 0);
717        self.write_into(buf)
718    }
719
720    /// Serialize into `buf` and return bytes written. Errors with
721    /// `BufferTooShort` when `buf` is smaller than the required size; nothing
722    /// is mutated in that case.
723    pub fn encode_into_slice(&self, buf: &mut [u8]) -> Result<usize, CdrError> {
724        let need = self.size();
725        if buf.len() < need {
726            return Err(CdrError::BufferTooShort {
727                need,
728                have: buf.len(),
729            });
730        }
731        self.write_into(&mut buf[..need])?;
732        Ok(need)
733    }
734}
735
736// ── Imu<B> ──────────────────────────────────────────────────────────
737//
738// CDR layout: Header → offsets[0], then:
739//   Quaternion(32) + [f64;9](72) + Vector3(24) + [f64;9](72)
740//   + Vector3(24) + [f64;9](72) = 296 bytes fixed payload
741
742pub struct Imu<B> {
743    buf: B,
744    offsets: [usize; 1],
745}
746
747impl<B> Imu<B> {
748    /// Convert the buffer type without re-parsing the offset table.
749    #[inline]
750    pub fn map_buffer<C>(self, f: impl FnOnce(B) -> C) -> Imu<C> {
751        Imu {
752            buf: f(self.buf),
753            offsets: self.offsets,
754        }
755    }
756}
757
758impl<B: AsRef<[u8]>> Imu<B> {
759    pub fn from_cdr(buf: B) -> Result<Self, CdrError> {
760        let header = Header::<&[u8]>::from_cdr(buf.as_ref())?;
761        let o0 = header.end_offset();
762        let mut c = CdrCursor::resume(buf.as_ref(), o0);
763        Quaternion::read_cdr(&mut c)?;
764        read_f64_array9(&mut c)?;
765        Vector3::read_cdr(&mut c)?;
766        read_f64_array9(&mut c)?;
767        Vector3::read_cdr(&mut c)?;
768        read_f64_array9(&mut c)?;
769        Ok(Imu { offsets: [o0], buf })
770    }
771
772    /// Returns a `Header` view (re-parses CDR prefix; prefer `stamp()`/`frame_id()`).
773    pub fn header(&self) -> Header<&[u8]> {
774        Header::from_cdr(self.buf.as_ref()).expect("header bytes validated during from_cdr")
775    }
776    pub fn stamp(&self) -> Time {
777        rd_time(self.buf.as_ref(), CDR_HEADER_SIZE)
778    }
779    pub fn frame_id(&self) -> &str {
780        rd_string(self.buf.as_ref(), CDR_HEADER_SIZE + 8).0
781    }
782
783    // Imu fixed layout after orientation (Quaternion=32):
784    //   orientation_cov[9](72), angular_vel(24), angular_vel_cov[9](72),
785    //   linear_acc(24), linear_acc_cov[9](72)
786    fn fixed_base(&self) -> usize {
787        cdr_align(self.offsets[0], 8)
788    }
789
790    pub fn orientation(&self) -> Quaternion {
791        let mut c = CdrCursor::resume(self.buf.as_ref(), self.offsets[0]);
792        Quaternion::read_cdr(&mut c).expect("orientation field validated during from_cdr")
793    }
794
795    pub fn orientation_covariance(&self) -> [f64; 9] {
796        let mut c = CdrCursor::resume(self.buf.as_ref(), self.fixed_base() + 32);
797        read_f64_array9(&mut c).expect("covariance field validated during from_cdr")
798    }
799
800    pub fn angular_velocity(&self) -> Vector3 {
801        let mut c = CdrCursor::resume(self.buf.as_ref(), self.fixed_base() + 104);
802        Vector3::read_cdr(&mut c).expect("vector3 field validated during from_cdr")
803    }
804
805    pub fn angular_velocity_covariance(&self) -> [f64; 9] {
806        let mut c = CdrCursor::resume(self.buf.as_ref(), self.fixed_base() + 128);
807        read_f64_array9(&mut c).expect("covariance field validated during from_cdr")
808    }
809
810    pub fn linear_acceleration(&self) -> Vector3 {
811        let mut c = CdrCursor::resume(self.buf.as_ref(), self.fixed_base() + 200);
812        Vector3::read_cdr(&mut c).expect("vector3 field validated during from_cdr")
813    }
814
815    pub fn linear_acceleration_covariance(&self) -> [f64; 9] {
816        let mut c = CdrCursor::resume(self.buf.as_ref(), self.fixed_base() + 224);
817        read_f64_array9(&mut c).expect("covariance field validated during from_cdr")
818    }
819
820    pub fn as_cdr(&self) -> &[u8] {
821        self.buf.as_ref()
822    }
823    pub fn to_cdr(&self) -> Vec<u8> {
824        self.buf.as_ref().to_vec()
825    }
826}
827
828impl Imu<Vec<u8>> {
829    #[deprecated(
830        since = "3.2.0",
831        note = "use Imu::builder() for allocation-free buffer reuse; Imu::new will be removed in 4.0"
832    )]
833    pub fn new(
834        stamp: Time,
835        frame_id: &str,
836        orientation: Quaternion,
837        orientation_covariance: [f64; 9],
838        angular_velocity: Vector3,
839        angular_velocity_covariance: [f64; 9],
840        linear_acceleration: Vector3,
841        linear_acceleration_covariance: [f64; 9],
842    ) -> Result<Self, CdrError> {
843        let mut sizer = CdrSizer::new();
844        Time::size_cdr(&mut sizer);
845        sizer.size_string(frame_id);
846        let o0 = sizer.offset();
847        Quaternion::size_cdr(&mut sizer);
848        size_f64_array9(&mut sizer);
849        Vector3::size_cdr(&mut sizer);
850        size_f64_array9(&mut sizer);
851        Vector3::size_cdr(&mut sizer);
852        size_f64_array9(&mut sizer);
853
854        let mut buf = vec![0u8; sizer.size()];
855        let mut w = CdrWriter::new(&mut buf)?;
856        stamp.write_cdr(&mut w);
857        w.write_string(frame_id);
858        orientation.write_cdr(&mut w);
859        write_f64_array9(&mut w, &orientation_covariance);
860        angular_velocity.write_cdr(&mut w);
861        write_f64_array9(&mut w, &angular_velocity_covariance);
862        linear_acceleration.write_cdr(&mut w);
863        write_f64_array9(&mut w, &linear_acceleration_covariance);
864        w.finish()?;
865
866        Ok(Imu { offsets: [o0], buf })
867    }
868
869    pub fn into_cdr(self) -> Vec<u8> {
870        self.buf
871    }
872
873    /// Start a new `ImuBuilder` with zero-valued defaults.
874    pub fn builder<'a>() -> ImuBuilder<'a> {
875        ImuBuilder::new()
876    }
877}
878
879// ── ImuBuilder<'a> ──────────────────────────────────────────────────
880
881/// Builder for `Imu<Vec<u8>>` with buffer-reuse finalizers.
882pub struct ImuBuilder<'a> {
883    stamp: Time,
884    frame_id: std::borrow::Cow<'a, str>,
885    orientation: Quaternion,
886    orientation_covariance: [f64; 9],
887    angular_velocity: Vector3,
888    angular_velocity_covariance: [f64; 9],
889    linear_acceleration: Vector3,
890    linear_acceleration_covariance: [f64; 9],
891}
892
893impl<'a> Default for ImuBuilder<'a> {
894    fn default() -> Self {
895        Self {
896            stamp: Time { sec: 0, nanosec: 0 },
897            frame_id: std::borrow::Cow::Borrowed(""),
898            orientation: Quaternion {
899                x: 0.0,
900                y: 0.0,
901                z: 0.0,
902                w: 0.0,
903            },
904            orientation_covariance: [0.0; 9],
905            angular_velocity: Vector3 {
906                x: 0.0,
907                y: 0.0,
908                z: 0.0,
909            },
910            angular_velocity_covariance: [0.0; 9],
911            linear_acceleration: Vector3 {
912                x: 0.0,
913                y: 0.0,
914                z: 0.0,
915            },
916            linear_acceleration_covariance: [0.0; 9],
917        }
918    }
919}
920
921impl<'a> ImuBuilder<'a> {
922    pub fn new() -> Self {
923        Self::default()
924    }
925
926    pub fn stamp(&mut self, t: Time) -> &mut Self {
927        self.stamp = t;
928        self
929    }
930    pub fn frame_id(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
931        self.frame_id = s.into();
932        self
933    }
934    pub fn orientation(&mut self, q: Quaternion) -> &mut Self {
935        self.orientation = q;
936        self
937    }
938    pub fn orientation_covariance(&mut self, c: [f64; 9]) -> &mut Self {
939        self.orientation_covariance = c;
940        self
941    }
942    pub fn angular_velocity(&mut self, v: Vector3) -> &mut Self {
943        self.angular_velocity = v;
944        self
945    }
946    pub fn angular_velocity_covariance(&mut self, c: [f64; 9]) -> &mut Self {
947        self.angular_velocity_covariance = c;
948        self
949    }
950    pub fn linear_acceleration(&mut self, v: Vector3) -> &mut Self {
951        self.linear_acceleration = v;
952        self
953    }
954    pub fn linear_acceleration_covariance(&mut self, c: [f64; 9]) -> &mut Self {
955        self.linear_acceleration_covariance = c;
956        self
957    }
958
959    fn size(&self) -> usize {
960        let mut s = CdrSizer::new();
961        Time::size_cdr(&mut s);
962        s.size_string(&self.frame_id);
963        Quaternion::size_cdr(&mut s);
964        size_f64_array9(&mut s);
965        Vector3::size_cdr(&mut s);
966        size_f64_array9(&mut s);
967        Vector3::size_cdr(&mut s);
968        size_f64_array9(&mut s);
969        s.size()
970    }
971
972    fn write_into(&self, buf: &mut [u8]) -> Result<(), CdrError> {
973        let mut w = CdrWriter::new(buf)?;
974        self.stamp.write_cdr(&mut w);
975        w.write_string(&self.frame_id);
976        self.orientation.write_cdr(&mut w);
977        write_f64_array9(&mut w, &self.orientation_covariance);
978        self.angular_velocity.write_cdr(&mut w);
979        write_f64_array9(&mut w, &self.angular_velocity_covariance);
980        self.linear_acceleration.write_cdr(&mut w);
981        write_f64_array9(&mut w, &self.linear_acceleration_covariance);
982        w.finish()
983    }
984
985    pub fn build(&self) -> Result<Imu<Vec<u8>>, CdrError> {
986        let mut buf = vec![0u8; self.size()];
987        self.write_into(&mut buf)?;
988        Imu::from_cdr(buf)
989    }
990
991    pub fn encode_into_vec(&self, buf: &mut Vec<u8>) -> Result<(), CdrError> {
992        buf.resize(self.size(), 0);
993        self.write_into(buf)
994    }
995
996    pub fn encode_into_slice(&self, buf: &mut [u8]) -> Result<usize, CdrError> {
997        let need = self.size();
998        if buf.len() < need {
999            return Err(CdrError::BufferTooShort {
1000                need,
1001                have: buf.len(),
1002            });
1003        }
1004        self.write_into(&mut buf[..need])?;
1005        Ok(need)
1006    }
1007}
1008
1009impl<B: AsRef<[u8]> + AsMut<[u8]>> Imu<B> {
1010    pub fn set_stamp(&mut self, t: Time) -> Result<(), CdrError> {
1011        let b = self.buf.as_mut();
1012        wr_i32(b, CDR_HEADER_SIZE, t.sec)?;
1013        wr_u32(b, CDR_HEADER_SIZE + 4, t.nanosec)
1014    }
1015
1016    pub fn set_orientation(&mut self, q: Quaternion) -> Result<(), CdrError> {
1017        let p = self.fixed_base();
1018        let b = self.buf.as_mut();
1019        wr_f64(b, p, q.x)?;
1020        wr_f64(b, p + 8, q.y)?;
1021        wr_f64(b, p + 16, q.z)?;
1022        wr_f64(b, p + 24, q.w)
1023    }
1024
1025    pub fn set_orientation_covariance(&mut self, c: [f64; 9]) -> Result<(), CdrError> {
1026        let p = self.fixed_base() + 32;
1027        let b = self.buf.as_mut();
1028        for (i, v) in c.iter().enumerate() {
1029            wr_f64(b, p + i * 8, *v)?;
1030        }
1031        Ok(())
1032    }
1033
1034    pub fn set_angular_velocity(&mut self, v: Vector3) -> Result<(), CdrError> {
1035        let p = self.fixed_base() + 104;
1036        let b = self.buf.as_mut();
1037        wr_f64(b, p, v.x)?;
1038        wr_f64(b, p + 8, v.y)?;
1039        wr_f64(b, p + 16, v.z)
1040    }
1041
1042    pub fn set_angular_velocity_covariance(&mut self, c: [f64; 9]) -> Result<(), CdrError> {
1043        let p = self.fixed_base() + 128;
1044        let b = self.buf.as_mut();
1045        for (i, v) in c.iter().enumerate() {
1046            wr_f64(b, p + i * 8, *v)?;
1047        }
1048        Ok(())
1049    }
1050
1051    pub fn set_linear_acceleration(&mut self, v: Vector3) -> Result<(), CdrError> {
1052        let p = self.fixed_base() + 200;
1053        let b = self.buf.as_mut();
1054        wr_f64(b, p, v.x)?;
1055        wr_f64(b, p + 8, v.y)?;
1056        wr_f64(b, p + 16, v.z)
1057    }
1058
1059    pub fn set_linear_acceleration_covariance(&mut self, c: [f64; 9]) -> Result<(), CdrError> {
1060        let p = self.fixed_base() + 224;
1061        let b = self.buf.as_mut();
1062        for (i, v) in c.iter().enumerate() {
1063            wr_f64(b, p + i * 8, *v)?;
1064        }
1065        Ok(())
1066    }
1067}
1068
1069// ── NavSatFix<B> ────────────────────────────────────────────────────
1070//
1071// CDR layout: Header → offsets[0] (status start), then:
1072//   NavSatStatus (3 or 4 bytes — internal padding depends on start parity)
1073//   + pad to 8 → offsets[1] (latitude start)
1074//   + latitude(f64) + longitude(f64) + altitude(f64)
1075//   + position_covariance([f64;9]) + position_covariance_type(u8)
1076//
1077// `offsets[1]` must be captured from the cursor, not derived from
1078// `offsets[0] + NavSatStatus::CDR_SIZE`, because CDR_SIZE is 4 only when
1079// offsets[0] lands at an even CDR-relative position; at odd relative
1080// positions the `int8`+`uint16` pair occupies 3 bytes (no pre-u16 pad).
1081// See EDGEAI-1243.
1082
1083pub struct NavSatFix<B> {
1084    buf: B,
1085    offsets: [usize; 2],
1086}
1087
1088impl<B> NavSatFix<B> {
1089    /// Convert the buffer type without re-parsing the offset table.
1090    #[inline]
1091    pub fn map_buffer<C>(self, f: impl FnOnce(B) -> C) -> NavSatFix<C> {
1092        NavSatFix {
1093            buf: f(self.buf),
1094            offsets: self.offsets,
1095        }
1096    }
1097}
1098
1099impl<B: AsRef<[u8]>> NavSatFix<B> {
1100    pub fn from_cdr(buf: B) -> Result<Self, CdrError> {
1101        let header = Header::<&[u8]>::from_cdr(buf.as_ref())?;
1102        let o0 = header.end_offset();
1103        let mut c = CdrCursor::resume(buf.as_ref(), o0);
1104        NavSatStatus::read_cdr(&mut c)?;
1105        c.align(8);
1106        let o1 = c.offset();
1107        c.read_f64()?; // latitude
1108        c.read_f64()?; // longitude
1109        c.read_f64()?; // altitude
1110        read_f64_array9(&mut c)?; // position_covariance
1111        c.read_u8()?; // position_covariance_type
1112        Ok(NavSatFix {
1113            offsets: [o0, o1],
1114            buf,
1115        })
1116    }
1117
1118    /// Returns a `Header` view (re-parses CDR prefix; prefer `stamp()`/`frame_id()`).
1119    pub fn header(&self) -> Header<&[u8]> {
1120        Header::from_cdr(self.buf.as_ref()).expect("header bytes validated during from_cdr")
1121    }
1122    pub fn stamp(&self) -> Time {
1123        rd_time(self.buf.as_ref(), CDR_HEADER_SIZE)
1124    }
1125    pub fn frame_id(&self) -> &str {
1126        rd_string(self.buf.as_ref(), CDR_HEADER_SIZE + 8).0
1127    }
1128
1129    pub fn status(&self) -> NavSatStatus {
1130        let mut c = CdrCursor::resume(self.buf.as_ref(), self.offsets[0]);
1131        NavSatStatus::read_cdr(&mut c).expect("status field validated during from_cdr")
1132    }
1133
1134    // NavSatFix fixed layout (starting at offsets[1]):
1135    //   lat(8), lon(8), alt(8), pos_cov[9](72), pos_cov_type(1)
1136    #[inline]
1137    fn fixed_base(&self) -> usize {
1138        self.offsets[1]
1139    }
1140
1141    pub fn latitude(&self) -> f64 {
1142        rd_f64(self.buf.as_ref(), self.fixed_base())
1143    }
1144    pub fn longitude(&self) -> f64 {
1145        rd_f64(self.buf.as_ref(), self.fixed_base() + 8)
1146    }
1147    pub fn altitude(&self) -> f64 {
1148        rd_f64(self.buf.as_ref(), self.fixed_base() + 16)
1149    }
1150
1151    pub fn position_covariance(&self) -> [f64; 9] {
1152        let mut c = CdrCursor::resume(self.buf.as_ref(), self.fixed_base() + 24);
1153        read_f64_array9(&mut c).expect("covariance field validated during from_cdr")
1154    }
1155
1156    pub fn position_covariance_type(&self) -> u8 {
1157        rd_u8(self.buf.as_ref(), self.fixed_base() + 96)
1158    }
1159
1160    pub fn as_cdr(&self) -> &[u8] {
1161        self.buf.as_ref()
1162    }
1163    pub fn to_cdr(&self) -> Vec<u8> {
1164        self.buf.as_ref().to_vec()
1165    }
1166}
1167
1168impl NavSatFix<Vec<u8>> {
1169    #[deprecated(
1170        since = "3.2.0",
1171        note = "use NavSatFix::builder() for allocation-free buffer reuse; NavSatFix::new will be removed in 4.0"
1172    )]
1173    pub fn new(
1174        stamp: Time,
1175        frame_id: &str,
1176        status: NavSatStatus,
1177        latitude: f64,
1178        longitude: f64,
1179        altitude: f64,
1180        position_covariance: [f64; 9],
1181        position_covariance_type: u8,
1182    ) -> Result<Self, CdrError> {
1183        let mut sizer = CdrSizer::new();
1184        Time::size_cdr(&mut sizer);
1185        sizer.size_string(frame_id);
1186        let o0 = sizer.offset();
1187        NavSatStatus::size_cdr(&mut sizer);
1188        sizer.align(8);
1189        let o1 = sizer.offset();
1190        sizer.size_f64(); // latitude
1191        sizer.size_f64(); // longitude
1192        sizer.size_f64(); // altitude
1193        size_f64_array9(&mut sizer);
1194        sizer.size_u8(); // position_covariance_type
1195
1196        let mut buf = vec![0u8; sizer.size()];
1197        let mut w = CdrWriter::new(&mut buf)?;
1198        stamp.write_cdr(&mut w);
1199        w.write_string(frame_id);
1200        status.write_cdr(&mut w);
1201        w.write_f64(latitude);
1202        w.write_f64(longitude);
1203        w.write_f64(altitude);
1204        write_f64_array9(&mut w, &position_covariance);
1205        w.write_u8(position_covariance_type);
1206        w.finish()?;
1207
1208        Ok(NavSatFix {
1209            offsets: [o0, o1],
1210            buf,
1211        })
1212    }
1213
1214    pub fn into_cdr(self) -> Vec<u8> {
1215        self.buf
1216    }
1217
1218    /// Start a new `NavSatFixBuilder` with zero-valued defaults.
1219    pub fn builder<'a>() -> NavSatFixBuilder<'a> {
1220        NavSatFixBuilder::new()
1221    }
1222}
1223
1224// ── NavSatFixBuilder<'a> ────────────────────────────────────────────
1225
1226/// Builder for `NavSatFix<Vec<u8>>` with buffer-reuse finalizers.
1227pub struct NavSatFixBuilder<'a> {
1228    stamp: Time,
1229    frame_id: std::borrow::Cow<'a, str>,
1230    status: NavSatStatus,
1231    latitude: f64,
1232    longitude: f64,
1233    altitude: f64,
1234    position_covariance: [f64; 9],
1235    position_covariance_type: u8,
1236}
1237
1238impl<'a> Default for NavSatFixBuilder<'a> {
1239    fn default() -> Self {
1240        Self {
1241            stamp: Time { sec: 0, nanosec: 0 },
1242            frame_id: std::borrow::Cow::Borrowed(""),
1243            status: NavSatStatus {
1244                status: 0,
1245                service: 0,
1246            },
1247            latitude: 0.0,
1248            longitude: 0.0,
1249            altitude: 0.0,
1250            position_covariance: [0.0; 9],
1251            position_covariance_type: 0,
1252        }
1253    }
1254}
1255
1256impl<'a> NavSatFixBuilder<'a> {
1257    pub fn new() -> Self {
1258        Self::default()
1259    }
1260
1261    pub fn stamp(&mut self, t: Time) -> &mut Self {
1262        self.stamp = t;
1263        self
1264    }
1265    pub fn frame_id(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
1266        self.frame_id = s.into();
1267        self
1268    }
1269    pub fn status(&mut self, s: NavSatStatus) -> &mut Self {
1270        self.status = s;
1271        self
1272    }
1273    pub fn latitude(&mut self, v: f64) -> &mut Self {
1274        self.latitude = v;
1275        self
1276    }
1277    pub fn longitude(&mut self, v: f64) -> &mut Self {
1278        self.longitude = v;
1279        self
1280    }
1281    pub fn altitude(&mut self, v: f64) -> &mut Self {
1282        self.altitude = v;
1283        self
1284    }
1285    pub fn position_covariance(&mut self, c: [f64; 9]) -> &mut Self {
1286        self.position_covariance = c;
1287        self
1288    }
1289    pub fn position_covariance_type(&mut self, v: u8) -> &mut Self {
1290        self.position_covariance_type = v;
1291        self
1292    }
1293
1294    fn size(&self) -> usize {
1295        let mut s = CdrSizer::new();
1296        Time::size_cdr(&mut s);
1297        s.size_string(&self.frame_id);
1298        NavSatStatus::size_cdr(&mut s);
1299        s.align(8);
1300        s.size_f64(); // latitude
1301        s.size_f64(); // longitude
1302        s.size_f64(); // altitude
1303        size_f64_array9(&mut s);
1304        s.size_u8(); // position_covariance_type
1305        s.size()
1306    }
1307
1308    fn write_into(&self, buf: &mut [u8]) -> Result<(), CdrError> {
1309        let mut w = CdrWriter::new(buf)?;
1310        self.stamp.write_cdr(&mut w);
1311        w.write_string(&self.frame_id);
1312        self.status.write_cdr(&mut w);
1313        w.write_f64(self.latitude);
1314        w.write_f64(self.longitude);
1315        w.write_f64(self.altitude);
1316        write_f64_array9(&mut w, &self.position_covariance);
1317        w.write_u8(self.position_covariance_type);
1318        w.finish()
1319    }
1320
1321    pub fn build(&self) -> Result<NavSatFix<Vec<u8>>, CdrError> {
1322        let mut buf = vec![0u8; self.size()];
1323        self.write_into(&mut buf)?;
1324        NavSatFix::from_cdr(buf)
1325    }
1326
1327    pub fn encode_into_vec(&self, buf: &mut Vec<u8>) -> Result<(), CdrError> {
1328        buf.resize(self.size(), 0);
1329        self.write_into(buf)
1330    }
1331
1332    pub fn encode_into_slice(&self, buf: &mut [u8]) -> Result<usize, CdrError> {
1333        let need = self.size();
1334        if buf.len() < need {
1335            return Err(CdrError::BufferTooShort {
1336                need,
1337                have: buf.len(),
1338            });
1339        }
1340        self.write_into(&mut buf[..need])?;
1341        Ok(need)
1342    }
1343}
1344
1345impl<B: AsRef<[u8]> + AsMut<[u8]>> NavSatFix<B> {
1346    pub fn set_stamp(&mut self, t: Time) -> Result<(), CdrError> {
1347        let b = self.buf.as_mut();
1348        wr_i32(b, CDR_HEADER_SIZE, t.sec)?;
1349        wr_u32(b, CDR_HEADER_SIZE + 4, t.nanosec)
1350    }
1351
1352    /// Set the embedded `NavSatStatus`.
1353    ///
1354    /// The u16 `service` field is CDR-aligned to 2 bytes relative to the
1355    /// enclosing message header, so its byte offset depends on the parity
1356    /// of `offsets[0]`. We mirror the reader's cursor logic via
1357    /// [`cdr_align`] — see EDGEAI-1243.
1358    pub fn set_status(&mut self, s: NavSatStatus) -> Result<(), CdrError> {
1359        let status_pos = self.offsets[0];
1360        let service_pos = cdr_align(status_pos + 1, 2);
1361        let b = self.buf.as_mut();
1362        wr_i8(b, status_pos, s.status)?;
1363        wr_u16(b, service_pos, s.service)
1364    }
1365
1366    pub fn set_latitude(&mut self, v: f64) -> Result<(), CdrError> {
1367        let p = self.fixed_base();
1368        wr_f64(self.buf.as_mut(), p, v)
1369    }
1370
1371    pub fn set_longitude(&mut self, v: f64) -> Result<(), CdrError> {
1372        let p = self.fixed_base() + 8;
1373        wr_f64(self.buf.as_mut(), p, v)
1374    }
1375
1376    pub fn set_altitude(&mut self, v: f64) -> Result<(), CdrError> {
1377        let p = self.fixed_base() + 16;
1378        wr_f64(self.buf.as_mut(), p, v)
1379    }
1380
1381    pub fn set_position_covariance(&mut self, c: [f64; 9]) -> Result<(), CdrError> {
1382        let p = self.fixed_base() + 24;
1383        let b = self.buf.as_mut();
1384        for (i, v) in c.iter().enumerate() {
1385            wr_f64(b, p + i * 8, *v)?;
1386        }
1387        Ok(())
1388    }
1389
1390    pub fn set_position_covariance_type(&mut self, v: u8) -> Result<(), CdrError> {
1391        let p = self.fixed_base() + 96;
1392        wr_u8(self.buf.as_mut(), p, v)
1393    }
1394}
1395
1396// ── PointField<B> ───────────────────────────────────────────────────
1397//
1398// CDR layout: name (string) → offsets[0], then offset(u32), datatype(u8), count(u32)
1399
1400pub struct PointField<B> {
1401    buf: B,
1402    offsets: [usize; 1],
1403}
1404
1405impl<B> PointField<B> {
1406    /// Convert the buffer type without re-parsing the offset table.
1407    #[inline]
1408    pub fn map_buffer<C>(self, f: impl FnOnce(B) -> C) -> PointField<C> {
1409        PointField {
1410            buf: f(self.buf),
1411            offsets: self.offsets,
1412        }
1413    }
1414}
1415
1416impl<B: AsRef<[u8]>> PointField<B> {
1417    pub fn from_cdr(buf: B) -> Result<Self, CdrError> {
1418        let mut c = CdrCursor::new(buf.as_ref())?;
1419        let _ = c.read_string()?;
1420        let o0 = c.offset();
1421        c.read_u32()?;
1422        c.read_u8()?;
1423        c.read_u32()?;
1424        Ok(PointField { offsets: [o0], buf })
1425    }
1426
1427    #[inline]
1428    pub fn name(&self) -> &str {
1429        rd_string(self.buf.as_ref(), CDR_HEADER_SIZE).0
1430    }
1431
1432    pub fn offset(&self) -> u32 {
1433        let mut c = CdrCursor::resume(self.buf.as_ref(), self.offsets[0]);
1434        c.read_u32()
1435            .expect("point field element validated during from_cdr")
1436    }
1437
1438    pub fn datatype(&self) -> u8 {
1439        let mut c = CdrCursor::resume(self.buf.as_ref(), self.offsets[0]);
1440        c.read_u32()
1441            .expect("point field element validated during from_cdr"); // skip offset
1442        c.read_u8()
1443            .expect("point field element validated during from_cdr")
1444    }
1445
1446    pub fn count(&self) -> u32 {
1447        let mut c = CdrCursor::resume(self.buf.as_ref(), self.offsets[0]);
1448        c.read_u32()
1449            .expect("point field element validated during from_cdr"); // skip offset
1450        c.read_u8()
1451            .expect("point field element validated during from_cdr"); // skip datatype
1452        c.read_u32()
1453            .expect("point field element validated during from_cdr")
1454    }
1455
1456    #[inline]
1457    pub fn as_cdr(&self) -> &[u8] {
1458        self.buf.as_ref()
1459    }
1460    pub fn to_cdr(&self) -> Vec<u8> {
1461        self.buf.as_ref().to_vec()
1462    }
1463}
1464
1465impl PointField<Vec<u8>> {
1466    #[deprecated(
1467        since = "3.2.0",
1468        note = "use PointField::builder() for allocation-free buffer reuse; PointField::new will be removed in 4.0"
1469    )]
1470    pub fn new(name: &str, offset: u32, datatype: u8, count: u32) -> Result<Self, CdrError> {
1471        let mut sizer = CdrSizer::new();
1472        sizer.size_string(name);
1473        let o0 = sizer.offset();
1474        sizer.size_u32();
1475        sizer.size_u8();
1476        sizer.size_u32();
1477
1478        let mut buf = vec![0u8; sizer.size()];
1479        let mut w = CdrWriter::new(&mut buf)?;
1480        w.write_string(name);
1481        w.write_u32(offset);
1482        w.write_u8(datatype);
1483        w.write_u32(count);
1484        w.finish()?;
1485
1486        Ok(PointField { offsets: [o0], buf })
1487    }
1488
1489    pub fn into_cdr(self) -> Vec<u8> {
1490        self.buf
1491    }
1492
1493    /// Start a new `PointFieldBuilder` with zero-valued defaults.
1494    pub fn builder<'a>() -> PointFieldBuilder<'a> {
1495        PointFieldBuilder::new()
1496    }
1497}
1498
1499// ── PointFieldBuilder<'a> ───────────────────────────────────────────
1500
1501/// Builder for `PointField<Vec<u8>>` with buffer-reuse finalizers.
1502pub struct PointFieldBuilder<'a> {
1503    name: std::borrow::Cow<'a, str>,
1504    offset: u32,
1505    datatype: u8,
1506    count: u32,
1507}
1508
1509impl<'a> Default for PointFieldBuilder<'a> {
1510    fn default() -> Self {
1511        Self {
1512            name: std::borrow::Cow::Borrowed(""),
1513            offset: 0,
1514            datatype: 0,
1515            count: 0,
1516        }
1517    }
1518}
1519
1520impl<'a> PointFieldBuilder<'a> {
1521    pub fn new() -> Self {
1522        Self::default()
1523    }
1524
1525    pub fn name(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
1526        self.name = s.into();
1527        self
1528    }
1529    pub fn offset(&mut self, v: u32) -> &mut Self {
1530        self.offset = v;
1531        self
1532    }
1533    pub fn datatype(&mut self, v: u8) -> &mut Self {
1534        self.datatype = v;
1535        self
1536    }
1537    pub fn count(&mut self, v: u32) -> &mut Self {
1538        self.count = v;
1539        self
1540    }
1541
1542    fn size(&self) -> usize {
1543        let mut s = CdrSizer::new();
1544        s.size_string(&self.name);
1545        s.size_u32();
1546        s.size_u8();
1547        s.size_u32();
1548        s.size()
1549    }
1550
1551    fn write_into(&self, buf: &mut [u8]) -> Result<(), CdrError> {
1552        let mut w = CdrWriter::new(buf)?;
1553        w.write_string(&self.name);
1554        w.write_u32(self.offset);
1555        w.write_u8(self.datatype);
1556        w.write_u32(self.count);
1557        w.finish()
1558    }
1559
1560    pub fn build(&self) -> Result<PointField<Vec<u8>>, CdrError> {
1561        let mut buf = vec![0u8; self.size()];
1562        self.write_into(&mut buf)?;
1563        PointField::from_cdr(buf)
1564    }
1565
1566    pub fn encode_into_vec(&self, buf: &mut Vec<u8>) -> Result<(), CdrError> {
1567        buf.resize(self.size(), 0);
1568        self.write_into(buf)
1569    }
1570
1571    pub fn encode_into_slice(&self, buf: &mut [u8]) -> Result<usize, CdrError> {
1572        let need = self.size();
1573        if buf.len() < need {
1574            return Err(CdrError::BufferTooShort {
1575                need,
1576                have: buf.len(),
1577            });
1578        }
1579        self.write_into(&mut buf[..need])?;
1580        Ok(need)
1581    }
1582}
1583
1584impl<B: AsRef<[u8]> + AsMut<[u8]>> PointField<B> {
1585    /// Byte offset of the `offset` u32 field (4-aligned relative to CDR header).
1586    #[inline]
1587    fn offset_pos(&self) -> usize {
1588        cdr_align(self.offsets[0], 4)
1589    }
1590
1591    pub fn set_offset(&mut self, v: u32) -> Result<(), CdrError> {
1592        let p = self.offset_pos();
1593        wr_u32(self.buf.as_mut(), p, v)
1594    }
1595
1596    pub fn set_datatype(&mut self, v: u8) -> Result<(), CdrError> {
1597        let p = self.offset_pos() + 4;
1598        wr_u8(self.buf.as_mut(), p, v)
1599    }
1600
1601    pub fn set_count(&mut self, v: u32) -> Result<(), CdrError> {
1602        // datatype(u8) followed by count(u32) with 4-byte alignment.
1603        let p = cdr_align(self.offset_pos() + 5, 4);
1604        wr_u32(self.buf.as_mut(), p, v)
1605    }
1606}
1607
1608// ── PointCloud2<B> ──────────────────────────────────────────────────
1609//
1610// CDR layout: Header → offsets[0],
1611//   height(u32), width(u32),
1612//   fields(Vec<PointField>) → offsets[1],
1613//   is_bigendian(bool), point_step(u32), row_step(u32),
1614//   data(Vec<u8>) → offsets[2], is_dense(bool)
1615
1616pub struct PointCloud2<B> {
1617    buf: B,
1618    offsets: [usize; 3],
1619}
1620
1621impl<B> PointCloud2<B> {
1622    /// Convert the buffer type without re-parsing the offset table.
1623    #[inline]
1624    pub fn map_buffer<C>(self, f: impl FnOnce(B) -> C) -> PointCloud2<C> {
1625        PointCloud2 {
1626            buf: f(self.buf),
1627            offsets: self.offsets,
1628        }
1629    }
1630}
1631
1632impl<B: AsRef<[u8]>> PointCloud2<B> {
1633    pub fn from_cdr(buf: B) -> Result<Self, CdrError> {
1634        let header = Header::<&[u8]>::from_cdr(buf.as_ref())?;
1635        let o0 = header.end_offset();
1636        let mut c = CdrCursor::resume(buf.as_ref(), o0);
1637        c.read_u32()?; // height
1638        c.read_u32()?; // width
1639        let raw_fields = c.read_u32()?;
1640        let fields_count = c.check_seq_count(raw_fields, 9)?;
1641        for _ in 0..fields_count {
1642            scan_point_field_element(&mut c)?;
1643        }
1644        let o1 = c.offset();
1645        c.read_bool()?; // is_bigendian
1646        c.read_u32()?; // point_step
1647        c.read_u32()?; // row_step
1648        let _ = c.read_bytes()?; // data
1649        let o2 = c.offset();
1650        c.read_bool()?; // is_dense
1651        Ok(PointCloud2 {
1652            offsets: [o0, o1, o2],
1653            buf,
1654        })
1655    }
1656
1657    /// Returns a `Header` view (re-parses CDR prefix; prefer `stamp()`/`frame_id()`).
1658    pub fn header(&self) -> Header<&[u8]> {
1659        Header::from_cdr(self.buf.as_ref()).expect("header bytes validated during from_cdr")
1660    }
1661    pub fn stamp(&self) -> Time {
1662        rd_time(self.buf.as_ref(), CDR_HEADER_SIZE)
1663    }
1664    pub fn frame_id(&self) -> &str {
1665        rd_string(self.buf.as_ref(), CDR_HEADER_SIZE + 8).0
1666    }
1667
1668    pub fn height(&self) -> u32 {
1669        rd_u32(self.buf.as_ref(), align(self.offsets[0], 4))
1670    }
1671    pub fn width(&self) -> u32 {
1672        rd_u32(self.buf.as_ref(), align(self.offsets[0], 4) + 4)
1673    }
1674    pub fn fields_len(&self) -> u32 {
1675        rd_u32(self.buf.as_ref(), align(self.offsets[0], 4) + 8)
1676    }
1677
1678    pub fn fields(&self) -> Vec<PointFieldView<'_>> {
1679        let b = self.buf.as_ref();
1680        let p = align(self.offsets[0], 4) + 8;
1681        let count = rd_u32(b, p) as usize;
1682        let mut c = CdrCursor::resume(b, p + 4);
1683        (0..count)
1684            .map(|_| {
1685                scan_point_field_element(&mut c)
1686                    .expect("point field elements validated during from_cdr")
1687            })
1688            .collect()
1689    }
1690
1691    /// Non-allocating iterator over the PointField descriptors.
1692    pub fn fields_iter(&self) -> PointFieldIter<'_> {
1693        let b = self.buf.as_ref();
1694        let p = align(self.offsets[0], 4) + 8;
1695        let count = rd_u32(b, p) as usize;
1696        let cursor = CdrCursor::resume(b, p + 4);
1697        PointFieldIter {
1698            cursor,
1699            remaining: count,
1700        }
1701    }
1702
1703    /// Total number of points (height × width).
1704    pub fn point_count(&self) -> usize {
1705        (self.height() as usize) * (self.width() as usize)
1706    }
1707
1708    /// Create a dynamic (runtime-typed) point cloud view over the data buffer.
1709    pub fn as_dyn_cloud(
1710        &self,
1711    ) -> Result<pointcloud::DynPointCloud<'_>, pointcloud::PointCloudError> {
1712        pointcloud::DynPointCloud::from_pointcloud2(self)
1713    }
1714
1715    /// Create a statically-typed point cloud view, validating field layout.
1716    pub fn as_typed_cloud<P: pointcloud::Point>(
1717        &self,
1718    ) -> Result<pointcloud::PointCloud<'_, P>, pointcloud::PointCloudError> {
1719        pointcloud::PointCloud::from_pointcloud2(self)
1720    }
1721
1722    pub fn is_bigendian(&self) -> bool {
1723        rd_bool(self.buf.as_ref(), self.offsets[1])
1724    }
1725    pub fn point_step(&self) -> u32 {
1726        rd_u32(self.buf.as_ref(), align(self.offsets[1] + 1, 4))
1727    }
1728    pub fn row_step(&self) -> u32 {
1729        rd_u32(self.buf.as_ref(), align(self.offsets[1] + 1, 4) + 4)
1730    }
1731
1732    pub fn data(&self) -> &[u8] {
1733        rd_bytes(self.buf.as_ref(), align(self.offsets[1] + 1, 4) + 8).0
1734    }
1735
1736    pub fn is_dense(&self) -> bool {
1737        rd_bool(self.buf.as_ref(), self.offsets[2])
1738    }
1739
1740    pub fn as_cdr(&self) -> &[u8] {
1741        self.buf.as_ref()
1742    }
1743    pub fn to_cdr(&self) -> Vec<u8> {
1744        self.buf.as_ref().to_vec()
1745    }
1746}
1747
1748impl PointCloud2<Vec<u8>> {
1749    #[deprecated(
1750        since = "3.2.0",
1751        note = "use PointCloud2::builder() for allocation-free buffer reuse; PointCloud2::new will be removed in 4.0"
1752    )]
1753    pub fn new(
1754        stamp: Time,
1755        frame_id: &str,
1756        height: u32,
1757        width: u32,
1758        fields: &[PointFieldView<'_>],
1759        is_bigendian: bool,
1760        point_step: u32,
1761        row_step: u32,
1762        data: &[u8],
1763        is_dense: bool,
1764    ) -> Result<Self, CdrError> {
1765        let mut sizer = CdrSizer::new();
1766        Time::size_cdr(&mut sizer);
1767        sizer.size_string(frame_id);
1768        let o0 = sizer.offset();
1769        sizer.size_u32(); // height
1770        sizer.size_u32(); // width
1771        sizer.size_u32(); // fields count
1772        for f in fields {
1773            size_point_field_element(&mut sizer, f.name);
1774        }
1775        let o1 = sizer.offset();
1776        sizer.size_bool(); // is_bigendian
1777        sizer.size_u32(); // point_step
1778        sizer.size_u32(); // row_step
1779        sizer.size_bytes(data.len());
1780        let o2 = sizer.offset();
1781        sizer.size_bool(); // is_dense
1782
1783        let mut buf = vec![0u8; sizer.size()];
1784        let mut w = CdrWriter::new(&mut buf)?;
1785        stamp.write_cdr(&mut w);
1786        w.write_string(frame_id);
1787        w.write_u32(height);
1788        w.write_u32(width);
1789        w.write_u32(fields.len() as u32);
1790        for f in fields {
1791            write_point_field_element(&mut w, f);
1792        }
1793        w.write_bool(is_bigendian);
1794        w.write_u32(point_step);
1795        w.write_u32(row_step);
1796        w.write_bytes(data);
1797        w.write_bool(is_dense);
1798        w.finish()?;
1799
1800        Ok(PointCloud2 {
1801            offsets: [o0, o1, o2],
1802            buf,
1803        })
1804    }
1805
1806    pub fn into_cdr(self) -> Vec<u8> {
1807        self.buf
1808    }
1809
1810    /// Start a new `PointCloud2Builder` with zero-valued defaults.
1811    ///
1812    /// Generic in `'a` so the compiler infers it from subsequent
1813    /// `.fields(...)` / `.data(...)` borrows rather than forcing `'static`.
1814    pub fn builder<'a>() -> PointCloud2Builder<'a> {
1815        PointCloud2Builder::new()
1816    }
1817}
1818
1819// ── PointCloud2Builder<'a> ──────────────────────────────────────────
1820
1821/// Builder for `PointCloud2<Vec<u8>>` with buffer-reuse finalizers.
1822///
1823/// `fields` and `data` are borrowed from caller-owned memory. Each
1824/// `PointFieldView` in the slice borrows its `name` field as well.
1825/// All borrows must remain valid until a finalizer is called.
1826pub struct PointCloud2Builder<'a> {
1827    stamp: Time,
1828    frame_id: std::borrow::Cow<'a, str>,
1829    height: u32,
1830    width: u32,
1831    fields: &'a [PointFieldView<'a>],
1832    is_bigendian: bool,
1833    point_step: u32,
1834    row_step: u32,
1835    data: &'a [u8],
1836    is_dense: bool,
1837}
1838
1839impl<'a> Default for PointCloud2Builder<'a> {
1840    fn default() -> Self {
1841        Self {
1842            stamp: Time { sec: 0, nanosec: 0 },
1843            frame_id: std::borrow::Cow::Borrowed(""),
1844            height: 0,
1845            width: 0,
1846            fields: &[],
1847            is_bigendian: false,
1848            point_step: 0,
1849            row_step: 0,
1850            data: &[],
1851            is_dense: false,
1852        }
1853    }
1854}
1855
1856impl<'a> PointCloud2Builder<'a> {
1857    pub fn new() -> Self {
1858        Self::default()
1859    }
1860
1861    pub fn stamp(&mut self, t: Time) -> &mut Self {
1862        self.stamp = t;
1863        self
1864    }
1865    pub fn frame_id(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
1866        self.frame_id = s.into();
1867        self
1868    }
1869    pub fn height(&mut self, v: u32) -> &mut Self {
1870        self.height = v;
1871        self
1872    }
1873    pub fn width(&mut self, v: u32) -> &mut Self {
1874        self.width = v;
1875        self
1876    }
1877    pub fn fields(&mut self, f: &'a [PointFieldView<'a>]) -> &mut Self {
1878        self.fields = f;
1879        self
1880    }
1881    pub fn is_bigendian(&mut self, v: bool) -> &mut Self {
1882        self.is_bigendian = v;
1883        self
1884    }
1885    pub fn point_step(&mut self, v: u32) -> &mut Self {
1886        self.point_step = v;
1887        self
1888    }
1889    pub fn row_step(&mut self, v: u32) -> &mut Self {
1890        self.row_step = v;
1891        self
1892    }
1893    pub fn data(&mut self, d: &'a [u8]) -> &mut Self {
1894        self.data = d;
1895        self
1896    }
1897    pub fn is_dense(&mut self, v: bool) -> &mut Self {
1898        self.is_dense = v;
1899        self
1900    }
1901
1902    fn size(&self) -> usize {
1903        let mut s = CdrSizer::new();
1904        Time::size_cdr(&mut s);
1905        s.size_string(&self.frame_id);
1906        s.size_u32(); // height
1907        s.size_u32(); // width
1908        s.size_u32(); // fields count
1909        for f in self.fields {
1910            size_point_field_element(&mut s, f.name);
1911        }
1912        s.size_bool(); // is_bigendian
1913        s.size_u32(); // point_step
1914        s.size_u32(); // row_step
1915        s.size_bytes(self.data.len());
1916        s.size_bool(); // is_dense
1917        s.size()
1918    }
1919
1920    fn write_into(&self, buf: &mut [u8]) -> Result<(), CdrError> {
1921        let mut w = CdrWriter::new(buf)?;
1922        self.stamp.write_cdr(&mut w);
1923        w.write_string(&self.frame_id);
1924        w.write_u32(self.height);
1925        w.write_u32(self.width);
1926        w.write_u32(self.fields.len() as u32);
1927        for f in self.fields {
1928            write_point_field_element(&mut w, f);
1929        }
1930        w.write_bool(self.is_bigendian);
1931        w.write_u32(self.point_step);
1932        w.write_u32(self.row_step);
1933        w.write_bytes(self.data);
1934        w.write_bool(self.is_dense);
1935        w.finish()
1936    }
1937
1938    pub fn build(&self) -> Result<PointCloud2<Vec<u8>>, CdrError> {
1939        let mut buf = vec![0u8; self.size()];
1940        self.write_into(&mut buf)?;
1941        PointCloud2::from_cdr(buf)
1942    }
1943
1944    pub fn encode_into_vec(&self, buf: &mut Vec<u8>) -> Result<(), CdrError> {
1945        buf.resize(self.size(), 0);
1946        self.write_into(buf)
1947    }
1948
1949    pub fn encode_into_slice(&self, buf: &mut [u8]) -> Result<usize, CdrError> {
1950        let need = self.size();
1951        if buf.len() < need {
1952            return Err(CdrError::BufferTooShort {
1953                need,
1954                have: buf.len(),
1955            });
1956        }
1957        self.write_into(&mut buf[..need])?;
1958        Ok(need)
1959    }
1960}
1961
1962impl<B: AsRef<[u8]> + AsMut<[u8]>> PointCloud2<B> {
1963    pub fn set_stamp(&mut self, t: Time) -> Result<(), CdrError> {
1964        let b = self.buf.as_mut();
1965        wr_i32(b, CDR_HEADER_SIZE, t.sec)?;
1966        wr_u32(b, CDR_HEADER_SIZE + 4, t.nanosec)
1967    }
1968
1969    pub fn set_height(&mut self, h: u32) -> Result<(), CdrError> {
1970        let p = align(self.offsets[0], 4);
1971        wr_u32(self.buf.as_mut(), p, h)
1972    }
1973
1974    pub fn set_width(&mut self, w: u32) -> Result<(), CdrError> {
1975        let p = align(self.offsets[0], 4) + 4;
1976        wr_u32(self.buf.as_mut(), p, w)
1977    }
1978
1979    pub fn set_is_bigendian(&mut self, v: bool) -> Result<(), CdrError> {
1980        wr_bool(self.buf.as_mut(), self.offsets[1], v)
1981    }
1982
1983    pub fn set_point_step(&mut self, v: u32) -> Result<(), CdrError> {
1984        let p = align(self.offsets[1] + 1, 4);
1985        wr_u32(self.buf.as_mut(), p, v)
1986    }
1987
1988    pub fn set_row_step(&mut self, v: u32) -> Result<(), CdrError> {
1989        let p = align(self.offsets[1] + 1, 4) + 4;
1990        wr_u32(self.buf.as_mut(), p, v)
1991    }
1992
1993    pub fn set_is_dense(&mut self, v: bool) -> Result<(), CdrError> {
1994        wr_bool(self.buf.as_mut(), self.offsets[2], v)
1995    }
1996}
1997
1998// ── CameraInfo<B> ───────────────────────────────────────────────────
1999//
2000// CDR layout: Header → offsets[0],
2001//   height(u32), width(u32), distortion_model(string) → offsets[1],
2002//   d(Vec<f64>) → offsets[2], k[9], r[9], p[12],
2003//   binning_x(u32), binning_y(u32), roi(RegionOfInterest)
2004
2005pub struct CameraInfo<B> {
2006    buf: B,
2007    offsets: [usize; 3],
2008}
2009
2010impl<B> CameraInfo<B> {
2011    /// Convert the buffer type without re-parsing the offset table.
2012    #[inline]
2013    pub fn map_buffer<C>(self, f: impl FnOnce(B) -> C) -> CameraInfo<C> {
2014        CameraInfo {
2015            buf: f(self.buf),
2016            offsets: self.offsets,
2017        }
2018    }
2019}
2020
2021impl<B: AsRef<[u8]>> CameraInfo<B> {
2022    pub fn from_cdr(buf: B) -> Result<Self, CdrError> {
2023        let header = Header::<&[u8]>::from_cdr(buf.as_ref())?;
2024        let o0 = header.end_offset();
2025        let mut c = CdrCursor::resume(buf.as_ref(), o0);
2026        c.read_u32()?; // height
2027        c.read_u32()?; // width
2028        let _ = c.read_string()?; // distortion_model
2029        let o1 = c.offset();
2030        let d_count = c.read_u32()? as usize;
2031        c.skip_seq_8(d_count)?;
2032        let o2 = c.offset();
2033        read_f64_array9(&mut c)?; // k
2034        read_f64_array9(&mut c)?; // r
2035        read_f64_array12(&mut c)?; // p
2036        c.read_u32()?; // binning_x
2037        c.read_u32()?; // binning_y
2038        RegionOfInterest::read_cdr(&mut c)?;
2039        Ok(CameraInfo {
2040            offsets: [o0, o1, o2],
2041            buf,
2042        })
2043    }
2044
2045    /// Returns a `Header` view (re-parses CDR prefix; prefer `stamp()`/`frame_id()`).
2046    pub fn header(&self) -> Header<&[u8]> {
2047        Header::from_cdr(self.buf.as_ref()).expect("header bytes validated during from_cdr")
2048    }
2049    pub fn stamp(&self) -> Time {
2050        rd_time(self.buf.as_ref(), CDR_HEADER_SIZE)
2051    }
2052    pub fn frame_id(&self) -> &str {
2053        rd_string(self.buf.as_ref(), CDR_HEADER_SIZE + 8).0
2054    }
2055
2056    pub fn height(&self) -> u32 {
2057        rd_u32(self.buf.as_ref(), align(self.offsets[0], 4))
2058    }
2059    pub fn width(&self) -> u32 {
2060        rd_u32(self.buf.as_ref(), align(self.offsets[0], 4) + 4)
2061    }
2062
2063    pub fn distortion_model(&self) -> &str {
2064        rd_string(self.buf.as_ref(), align(self.offsets[0], 4) + 8).0
2065    }
2066
2067    /// Number of distortion coefficients.
2068    pub fn d_len(&self) -> usize {
2069        rd_u32(self.buf.as_ref(), align(self.offsets[1], 4)) as usize
2070    }
2071
2072    /// Read the i-th distortion coefficient (zero-copy, on-demand).
2073    pub fn d_get(&self, i: usize) -> f64 {
2074        let start = cdr_align(align(self.offsets[1], 4) + 4, 8);
2075        rd_f64(self.buf.as_ref(), start + i * 8)
2076    }
2077
2078    // Fixed region after d: k[9](72) + r[9](72) + p[12](96) + binning(8) + roi(17)
2079    fn fixed_base(&self) -> usize {
2080        cdr_align(self.offsets[2], 8)
2081    }
2082
2083    pub fn k(&self) -> [f64; 9] {
2084        let mut c = CdrCursor::resume(self.buf.as_ref(), self.fixed_base());
2085        read_f64_array9(&mut c).expect("covariance field validated during from_cdr")
2086    }
2087
2088    pub fn r(&self) -> [f64; 9] {
2089        let mut c = CdrCursor::resume(self.buf.as_ref(), self.fixed_base() + 72);
2090        read_f64_array9(&mut c).expect("covariance field validated during from_cdr")
2091    }
2092
2093    pub fn p(&self) -> [f64; 12] {
2094        let mut c = CdrCursor::resume(self.buf.as_ref(), self.fixed_base() + 144);
2095        read_f64_array12(&mut c).expect("projection matrix validated during from_cdr")
2096    }
2097
2098    pub fn binning_x(&self) -> u32 {
2099        rd_u32(self.buf.as_ref(), self.fixed_base() + 240)
2100    }
2101    pub fn binning_y(&self) -> u32 {
2102        rd_u32(self.buf.as_ref(), self.fixed_base() + 244)
2103    }
2104
2105    pub fn roi(&self) -> RegionOfInterest {
2106        let mut c = CdrCursor::resume(self.buf.as_ref(), self.fixed_base() + 248);
2107        RegionOfInterest::read_cdr(&mut c).expect("roi field validated during from_cdr")
2108    }
2109
2110    pub fn as_cdr(&self) -> &[u8] {
2111        self.buf.as_ref()
2112    }
2113    pub fn to_cdr(&self) -> Vec<u8> {
2114        self.buf.as_ref().to_vec()
2115    }
2116}
2117
2118impl CameraInfo<Vec<u8>> {
2119    #[deprecated(
2120        since = "3.2.0",
2121        note = "use CameraInfo::builder() for allocation-free buffer reuse; CameraInfo::new will be removed in 4.0"
2122    )]
2123    #[allow(clippy::too_many_arguments)]
2124    pub fn new(
2125        stamp: Time,
2126        frame_id: &str,
2127        height: u32,
2128        width: u32,
2129        distortion_model: &str,
2130        d: &[f64],
2131        k: [f64; 9],
2132        r: [f64; 9],
2133        p: [f64; 12],
2134        binning_x: u32,
2135        binning_y: u32,
2136        roi: RegionOfInterest,
2137    ) -> Result<Self, CdrError> {
2138        let mut sizer = CdrSizer::new();
2139        Time::size_cdr(&mut sizer);
2140        sizer.size_string(frame_id);
2141        let o0 = sizer.offset();
2142        sizer.size_u32(); // height
2143        sizer.size_u32(); // width
2144        sizer.size_string(distortion_model);
2145        let o1 = sizer.offset();
2146        sizer.size_u32();
2147        sizer.size_seq_8(d.len());
2148        let o2 = sizer.offset();
2149        size_f64_array9(&mut sizer);
2150        size_f64_array9(&mut sizer);
2151        size_f64_array12(&mut sizer);
2152        sizer.size_u32(); // binning_x
2153        sizer.size_u32(); // binning_y
2154        RegionOfInterest::size_cdr(&mut sizer);
2155
2156        let mut buf = vec![0u8; sizer.size()];
2157        let mut w = CdrWriter::new(&mut buf)?;
2158        stamp.write_cdr(&mut w);
2159        w.write_string(frame_id);
2160        w.write_u32(height);
2161        w.write_u32(width);
2162        w.write_string(distortion_model);
2163        w.write_u32(d.len() as u32);
2164        w.write_slice_f64(d);
2165        write_f64_array9(&mut w, &k);
2166        write_f64_array9(&mut w, &r);
2167        write_f64_array12(&mut w, &p);
2168        w.write_u32(binning_x);
2169        w.write_u32(binning_y);
2170        roi.write_cdr(&mut w);
2171        w.finish()?;
2172
2173        Ok(CameraInfo {
2174            offsets: [o0, o1, o2],
2175            buf,
2176        })
2177    }
2178
2179    pub fn into_cdr(self) -> Vec<u8> {
2180        self.buf
2181    }
2182
2183    /// Start a new `CameraInfoBuilder` with zero-valued defaults.
2184    pub fn builder<'a>() -> CameraInfoBuilder<'a> {
2185        CameraInfoBuilder::new()
2186    }
2187}
2188
2189// ── CameraInfoBuilder<'a> ───────────────────────────────────────────
2190
2191/// Builder for `CameraInfo<Vec<u8>>` with buffer-reuse finalizers.
2192///
2193/// `d` is borrowed for zero-copy input. `k`, `r`, `p` are fixed-size
2194/// arrays stored by value. All other fields are owned/copied.
2195pub struct CameraInfoBuilder<'a> {
2196    stamp: Time,
2197    frame_id: std::borrow::Cow<'a, str>,
2198    height: u32,
2199    width: u32,
2200    distortion_model: std::borrow::Cow<'a, str>,
2201    d: &'a [f64],
2202    k: [f64; 9],
2203    r: [f64; 9],
2204    p: [f64; 12],
2205    binning_x: u32,
2206    binning_y: u32,
2207    roi: RegionOfInterest,
2208}
2209
2210impl<'a> Default for CameraInfoBuilder<'a> {
2211    fn default() -> Self {
2212        Self {
2213            stamp: Time { sec: 0, nanosec: 0 },
2214            frame_id: std::borrow::Cow::Borrowed(""),
2215            height: 0,
2216            width: 0,
2217            distortion_model: std::borrow::Cow::Borrowed(""),
2218            d: &[],
2219            k: [0.0; 9],
2220            r: [0.0; 9],
2221            p: [0.0; 12],
2222            binning_x: 0,
2223            binning_y: 0,
2224            roi: RegionOfInterest {
2225                x_offset: 0,
2226                y_offset: 0,
2227                height: 0,
2228                width: 0,
2229                do_rectify: false,
2230            },
2231        }
2232    }
2233}
2234
2235impl<'a> CameraInfoBuilder<'a> {
2236    pub fn new() -> Self {
2237        Self::default()
2238    }
2239
2240    pub fn stamp(&mut self, t: Time) -> &mut Self {
2241        self.stamp = t;
2242        self
2243    }
2244    pub fn frame_id(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
2245        self.frame_id = s.into();
2246        self
2247    }
2248    pub fn height(&mut self, v: u32) -> &mut Self {
2249        self.height = v;
2250        self
2251    }
2252    pub fn width(&mut self, v: u32) -> &mut Self {
2253        self.width = v;
2254        self
2255    }
2256    pub fn distortion_model(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
2257        self.distortion_model = s.into();
2258        self
2259    }
2260    pub fn d(&mut self, d: &'a [f64]) -> &mut Self {
2261        self.d = d;
2262        self
2263    }
2264    pub fn k(&mut self, k: [f64; 9]) -> &mut Self {
2265        self.k = k;
2266        self
2267    }
2268    pub fn r(&mut self, r: [f64; 9]) -> &mut Self {
2269        self.r = r;
2270        self
2271    }
2272    pub fn p(&mut self, p: [f64; 12]) -> &mut Self {
2273        self.p = p;
2274        self
2275    }
2276    pub fn binning_x(&mut self, v: u32) -> &mut Self {
2277        self.binning_x = v;
2278        self
2279    }
2280    pub fn binning_y(&mut self, v: u32) -> &mut Self {
2281        self.binning_y = v;
2282        self
2283    }
2284    pub fn roi(&mut self, r: RegionOfInterest) -> &mut Self {
2285        self.roi = r;
2286        self
2287    }
2288
2289    fn size(&self) -> usize {
2290        let mut s = CdrSizer::new();
2291        Time::size_cdr(&mut s);
2292        s.size_string(&self.frame_id);
2293        s.size_u32(); // height
2294        s.size_u32(); // width
2295        s.size_string(&self.distortion_model);
2296        s.size_u32();
2297        s.size_seq_8(self.d.len());
2298        size_f64_array9(&mut s);
2299        size_f64_array9(&mut s);
2300        size_f64_array12(&mut s);
2301        s.size_u32(); // binning_x
2302        s.size_u32(); // binning_y
2303        RegionOfInterest::size_cdr(&mut s);
2304        s.size()
2305    }
2306
2307    fn write_into(&self, buf: &mut [u8]) -> Result<(), CdrError> {
2308        let mut w = CdrWriter::new(buf)?;
2309        self.stamp.write_cdr(&mut w);
2310        w.write_string(&self.frame_id);
2311        w.write_u32(self.height);
2312        w.write_u32(self.width);
2313        w.write_string(&self.distortion_model);
2314        w.write_u32(self.d.len() as u32);
2315        w.write_slice_f64(self.d);
2316        write_f64_array9(&mut w, &self.k);
2317        write_f64_array9(&mut w, &self.r);
2318        write_f64_array12(&mut w, &self.p);
2319        w.write_u32(self.binning_x);
2320        w.write_u32(self.binning_y);
2321        self.roi.write_cdr(&mut w);
2322        w.finish()
2323    }
2324
2325    pub fn build(&self) -> Result<CameraInfo<Vec<u8>>, CdrError> {
2326        let mut buf = vec![0u8; self.size()];
2327        self.write_into(&mut buf)?;
2328        CameraInfo::from_cdr(buf)
2329    }
2330
2331    pub fn encode_into_vec(&self, buf: &mut Vec<u8>) -> Result<(), CdrError> {
2332        buf.resize(self.size(), 0);
2333        self.write_into(buf)
2334    }
2335
2336    pub fn encode_into_slice(&self, buf: &mut [u8]) -> Result<usize, CdrError> {
2337        let need = self.size();
2338        if buf.len() < need {
2339            return Err(CdrError::BufferTooShort {
2340                need,
2341                have: buf.len(),
2342            });
2343        }
2344        self.write_into(&mut buf[..need])?;
2345        Ok(need)
2346    }
2347}
2348
2349impl<B: AsRef<[u8]> + AsMut<[u8]>> CameraInfo<B> {
2350    pub fn set_stamp(&mut self, t: Time) -> Result<(), CdrError> {
2351        let b = self.buf.as_mut();
2352        wr_i32(b, CDR_HEADER_SIZE, t.sec)?;
2353        wr_u32(b, CDR_HEADER_SIZE + 4, t.nanosec)
2354    }
2355
2356    pub fn set_height(&mut self, h: u32) -> Result<(), CdrError> {
2357        let p = align(self.offsets[0], 4);
2358        wr_u32(self.buf.as_mut(), p, h)
2359    }
2360
2361    pub fn set_width(&mut self, w: u32) -> Result<(), CdrError> {
2362        let p = align(self.offsets[0], 4) + 4;
2363        wr_u32(self.buf.as_mut(), p, w)
2364    }
2365
2366    pub fn set_k(&mut self, k: [f64; 9]) -> Result<(), CdrError> {
2367        let p = self.fixed_base();
2368        let b = self.buf.as_mut();
2369        for (i, v) in k.iter().enumerate() {
2370            wr_f64(b, p + i * 8, *v)?;
2371        }
2372        Ok(())
2373    }
2374
2375    pub fn set_r(&mut self, r: [f64; 9]) -> Result<(), CdrError> {
2376        let p = self.fixed_base() + 72;
2377        let b = self.buf.as_mut();
2378        for (i, v) in r.iter().enumerate() {
2379            wr_f64(b, p + i * 8, *v)?;
2380        }
2381        Ok(())
2382    }
2383
2384    pub fn set_p(&mut self, p_mat: [f64; 12]) -> Result<(), CdrError> {
2385        let p = self.fixed_base() + 144;
2386        let b = self.buf.as_mut();
2387        for (i, v) in p_mat.iter().enumerate() {
2388            wr_f64(b, p + i * 8, *v)?;
2389        }
2390        Ok(())
2391    }
2392
2393    pub fn set_binning_x(&mut self, v: u32) -> Result<(), CdrError> {
2394        let p = self.fixed_base() + 240;
2395        wr_u32(self.buf.as_mut(), p, v)
2396    }
2397
2398    pub fn set_binning_y(&mut self, v: u32) -> Result<(), CdrError> {
2399        let p = self.fixed_base() + 244;
2400        wr_u32(self.buf.as_mut(), p, v)
2401    }
2402
2403    pub fn set_roi(&mut self, r: RegionOfInterest) -> Result<(), CdrError> {
2404        let p = self.fixed_base() + 248;
2405        let b = self.buf.as_mut();
2406        wr_u32(b, p, r.x_offset)?;
2407        wr_u32(b, p + 4, r.y_offset)?;
2408        wr_u32(b, p + 8, r.height)?;
2409        wr_u32(b, p + 12, r.width)?;
2410        wr_bool(b, p + 16, r.do_rectify)
2411    }
2412}
2413
2414// ── Constants ───────────────────────────────────────────────────────
2415
2416pub mod nav_sat_fix {
2417    pub const COVARIANCE_TYPE_UNKNOWN: u8 = 0;
2418    pub const COVARIANCE_TYPE_APPROXIMATED: u8 = 1;
2419    pub const COVARIANCE_TYPE_DIAGONAL_KNOWN: u8 = 2;
2420    pub const COVARIANCE_TYPE_KNOWN: u8 = 3;
2421}
2422
2423pub mod nav_sat_status {
2424    pub const STATUS_NO_FIX: i8 = -1;
2425    pub const STATUS_FIX: i8 = 0;
2426    pub const STATUS_SBAS_FIX: i8 = 1;
2427    pub const STATUS_GBAS_FIX: i8 = 2;
2428    pub const SERVICE_GPS: u8 = 1;
2429    pub const SERVICE_GLONASS: u8 = 2;
2430    pub const SERVICE_COMPASS: u8 = 4;
2431    pub const SERVICE_GALILEO: u8 = 8;
2432}
2433
2434pub mod point_field {
2435    pub const INT8: u8 = 1;
2436    pub const UINT8: u8 = 2;
2437    pub const INT16: u8 = 3;
2438    pub const UINT16: u8 = 4;
2439    pub const INT32: u8 = 5;
2440    pub const UINT32: u8 = 6;
2441    pub const FLOAT32: u8 = 7;
2442    pub const FLOAT64: u8 = 8;
2443}
2444
2445// ── MagneticField<B> ────────────────────────────────────────────────
2446//
2447// CDR layout: Header → offsets[0] (start of magnetic_field, 8-aligned),
2448//   Vector3 magnetic_field (24 bytes), float64[9] covariance (72 bytes).
2449// offsets[0] is captured from the cursor after Header+align(8) to avoid
2450// the EDGEAI-1243 class of bug.
2451
2452pub struct MagneticField<B> {
2453    buf: B,
2454    offsets: [usize; 1],
2455}
2456
2457impl<B> MagneticField<B> {
2458    /// Convert the buffer type without re-parsing the offset table.
2459    #[inline]
2460    pub fn map_buffer<C>(self, f: impl FnOnce(B) -> C) -> MagneticField<C> {
2461        MagneticField {
2462            buf: f(self.buf),
2463            offsets: self.offsets,
2464        }
2465    }
2466}
2467
2468impl<B: AsRef<[u8]>> MagneticField<B> {
2469    pub fn from_cdr(buf: B) -> Result<Self, CdrError> {
2470        let header = Header::<&[u8]>::from_cdr(buf.as_ref())?;
2471        let pre = header.end_offset();
2472        let mut c = CdrCursor::resume(buf.as_ref(), pre);
2473        c.align(8);
2474        let o0 = c.offset();
2475        Vector3::read_cdr(&mut c)?;
2476        read_f64_array9(&mut c)?;
2477        Ok(MagneticField { offsets: [o0], buf })
2478    }
2479
2480    /// Returns a `Header` view by re-parsing the CDR buffer prefix.
2481    pub fn header(&self) -> Header<&[u8]> {
2482        Header::from_cdr(self.buf.as_ref()).expect("header bytes validated during from_cdr")
2483    }
2484    pub fn stamp(&self) -> Time {
2485        rd_time(self.buf.as_ref(), CDR_HEADER_SIZE)
2486    }
2487    pub fn frame_id(&self) -> &str {
2488        rd_string(self.buf.as_ref(), CDR_HEADER_SIZE + 8).0
2489    }
2490    pub fn magnetic_field(&self) -> Vector3 {
2491        let mut c = CdrCursor::resume(self.buf.as_ref(), self.offsets[0]);
2492        Vector3::read_cdr(&mut c).expect("magnetic_field validated during from_cdr")
2493    }
2494    pub fn magnetic_field_covariance(&self) -> [f64; 9] {
2495        let mut c = CdrCursor::resume(self.buf.as_ref(), self.offsets[0] + 24);
2496        read_f64_array9(&mut c).expect("covariance validated during from_cdr")
2497    }
2498    pub fn as_cdr(&self) -> &[u8] {
2499        self.buf.as_ref()
2500    }
2501    pub fn to_cdr(&self) -> Vec<u8> {
2502        self.buf.as_ref().to_vec()
2503    }
2504}
2505
2506impl MagneticField<Vec<u8>> {
2507    #[deprecated(
2508        since = "3.2.0",
2509        note = "use MagneticField::builder() for allocation-free buffer reuse; MagneticField::new will be removed in 4.0"
2510    )]
2511    pub fn new(
2512        stamp: Time,
2513        frame_id: &str,
2514        magnetic_field: Vector3,
2515        magnetic_field_covariance: [f64; 9],
2516    ) -> Result<Self, CdrError> {
2517        let mut sizer = CdrSizer::new();
2518        Time::size_cdr(&mut sizer);
2519        sizer.size_string(frame_id);
2520        sizer.align(8);
2521        let o0 = sizer.offset();
2522        Vector3::size_cdr(&mut sizer);
2523        size_f64_array9(&mut sizer);
2524
2525        let mut buf = vec![0u8; sizer.size()];
2526        let mut w = CdrWriter::new(&mut buf)?;
2527        stamp.write_cdr(&mut w);
2528        w.write_string(frame_id);
2529        magnetic_field.write_cdr(&mut w);
2530        write_f64_array9(&mut w, &magnetic_field_covariance);
2531        w.finish()?;
2532
2533        Ok(MagneticField { offsets: [o0], buf })
2534    }
2535
2536    pub fn into_cdr(self) -> Vec<u8> {
2537        self.buf
2538    }
2539
2540    /// Start a new `MagneticFieldBuilder` with zero-valued defaults.
2541    pub fn builder<'a>() -> MagneticFieldBuilder<'a> {
2542        MagneticFieldBuilder::new()
2543    }
2544}
2545
2546// ── MagneticFieldBuilder<'a> ────────────────────────────────────────
2547
2548/// Builder for `MagneticField<Vec<u8>>` with buffer-reuse finalizers.
2549pub struct MagneticFieldBuilder<'a> {
2550    stamp: Time,
2551    frame_id: std::borrow::Cow<'a, str>,
2552    magnetic_field: Vector3,
2553    magnetic_field_covariance: [f64; 9],
2554}
2555
2556impl<'a> Default for MagneticFieldBuilder<'a> {
2557    fn default() -> Self {
2558        Self {
2559            stamp: Time { sec: 0, nanosec: 0 },
2560            frame_id: std::borrow::Cow::Borrowed(""),
2561            magnetic_field: Vector3 {
2562                x: 0.0,
2563                y: 0.0,
2564                z: 0.0,
2565            },
2566            magnetic_field_covariance: [0.0; 9],
2567        }
2568    }
2569}
2570
2571impl<'a> MagneticFieldBuilder<'a> {
2572    pub fn new() -> Self {
2573        Self::default()
2574    }
2575
2576    pub fn stamp(&mut self, t: Time) -> &mut Self {
2577        self.stamp = t;
2578        self
2579    }
2580    pub fn frame_id(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
2581        self.frame_id = s.into();
2582        self
2583    }
2584    pub fn magnetic_field(&mut self, v: Vector3) -> &mut Self {
2585        self.magnetic_field = v;
2586        self
2587    }
2588    pub fn magnetic_field_covariance(&mut self, c: [f64; 9]) -> &mut Self {
2589        self.magnetic_field_covariance = c;
2590        self
2591    }
2592
2593    fn size(&self) -> usize {
2594        let mut s = CdrSizer::new();
2595        Time::size_cdr(&mut s);
2596        s.size_string(&self.frame_id);
2597        s.align(8);
2598        Vector3::size_cdr(&mut s);
2599        size_f64_array9(&mut s);
2600        s.size()
2601    }
2602
2603    fn write_into(&self, buf: &mut [u8]) -> Result<(), CdrError> {
2604        let mut w = CdrWriter::new(buf)?;
2605        self.stamp.write_cdr(&mut w);
2606        w.write_string(&self.frame_id);
2607        self.magnetic_field.write_cdr(&mut w);
2608        write_f64_array9(&mut w, &self.magnetic_field_covariance);
2609        w.finish()
2610    }
2611
2612    pub fn build(&self) -> Result<MagneticField<Vec<u8>>, CdrError> {
2613        let mut buf = vec![0u8; self.size()];
2614        self.write_into(&mut buf)?;
2615        MagneticField::from_cdr(buf)
2616    }
2617
2618    pub fn encode_into_vec(&self, buf: &mut Vec<u8>) -> Result<(), CdrError> {
2619        buf.resize(self.size(), 0);
2620        self.write_into(buf)
2621    }
2622
2623    pub fn encode_into_slice(&self, buf: &mut [u8]) -> Result<usize, CdrError> {
2624        let need = self.size();
2625        if buf.len() < need {
2626            return Err(CdrError::BufferTooShort {
2627                need,
2628                have: buf.len(),
2629            });
2630        }
2631        self.write_into(&mut buf[..need])?;
2632        Ok(need)
2633    }
2634}
2635
2636impl<B: AsRef<[u8]> + AsMut<[u8]>> MagneticField<B> {
2637    pub fn set_stamp(&mut self, t: Time) -> Result<(), CdrError> {
2638        let b = self.buf.as_mut();
2639        wr_i32(b, CDR_HEADER_SIZE, t.sec)?;
2640        wr_u32(b, CDR_HEADER_SIZE + 4, t.nanosec)
2641    }
2642
2643    pub fn set_magnetic_field(&mut self, v: Vector3) -> Result<(), CdrError> {
2644        let p = self.offsets[0];
2645        let b = self.buf.as_mut();
2646        wr_f64(b, p, v.x)?;
2647        wr_f64(b, p + 8, v.y)?;
2648        wr_f64(b, p + 16, v.z)
2649    }
2650
2651    pub fn set_magnetic_field_covariance(&mut self, c: [f64; 9]) -> Result<(), CdrError> {
2652        let p = self.offsets[0] + 24;
2653        let b = self.buf.as_mut();
2654        for (i, v) in c.iter().enumerate() {
2655            wr_f64(b, p + i * 8, *v)?;
2656        }
2657        Ok(())
2658    }
2659}
2660
2661// ── FluidPressure<B> ────────────────────────────────────────────────
2662//
2663// CDR layout: Header → offsets[0] (start of fluid_pressure, 8-aligned),
2664//   float64 fluid_pressure (8 bytes), float64 variance (8 bytes).
2665
2666pub struct FluidPressure<B> {
2667    buf: B,
2668    offsets: [usize; 1],
2669}
2670
2671impl<B> FluidPressure<B> {
2672    /// Convert the buffer type without re-parsing the offset table.
2673    #[inline]
2674    pub fn map_buffer<C>(self, f: impl FnOnce(B) -> C) -> FluidPressure<C> {
2675        FluidPressure {
2676            buf: f(self.buf),
2677            offsets: self.offsets,
2678        }
2679    }
2680}
2681
2682impl<B: AsRef<[u8]>> FluidPressure<B> {
2683    pub fn from_cdr(buf: B) -> Result<Self, CdrError> {
2684        let header = Header::<&[u8]>::from_cdr(buf.as_ref())?;
2685        let pre = header.end_offset();
2686        let mut c = CdrCursor::resume(buf.as_ref(), pre);
2687        c.align(8);
2688        let o0 = c.offset();
2689        c.read_f64()?; // fluid_pressure
2690        c.read_f64()?; // variance
2691        Ok(FluidPressure { offsets: [o0], buf })
2692    }
2693
2694    pub fn header(&self) -> Header<&[u8]> {
2695        Header::from_cdr(self.buf.as_ref()).expect("header bytes validated during from_cdr")
2696    }
2697    pub fn stamp(&self) -> Time {
2698        rd_time(self.buf.as_ref(), CDR_HEADER_SIZE)
2699    }
2700    pub fn frame_id(&self) -> &str {
2701        rd_string(self.buf.as_ref(), CDR_HEADER_SIZE + 8).0
2702    }
2703    pub fn fluid_pressure(&self) -> f64 {
2704        rd_f64(self.buf.as_ref(), self.offsets[0])
2705    }
2706    pub fn variance(&self) -> f64 {
2707        rd_f64(self.buf.as_ref(), self.offsets[0] + 8)
2708    }
2709    pub fn as_cdr(&self) -> &[u8] {
2710        self.buf.as_ref()
2711    }
2712    pub fn to_cdr(&self) -> Vec<u8> {
2713        self.buf.as_ref().to_vec()
2714    }
2715}
2716
2717impl FluidPressure<Vec<u8>> {
2718    #[deprecated(
2719        since = "3.2.0",
2720        note = "use FluidPressure::builder() for allocation-free buffer reuse; FluidPressure::new will be removed in 4.0"
2721    )]
2722    pub fn new(
2723        stamp: Time,
2724        frame_id: &str,
2725        fluid_pressure: f64,
2726        variance: f64,
2727    ) -> Result<Self, CdrError> {
2728        let mut sizer = CdrSizer::new();
2729        Time::size_cdr(&mut sizer);
2730        sizer.size_string(frame_id);
2731        sizer.align(8);
2732        let o0 = sizer.offset();
2733        sizer.size_f64();
2734        sizer.size_f64();
2735
2736        let mut buf = vec![0u8; sizer.size()];
2737        let mut w = CdrWriter::new(&mut buf)?;
2738        stamp.write_cdr(&mut w);
2739        w.write_string(frame_id);
2740        w.write_f64(fluid_pressure);
2741        w.write_f64(variance);
2742        w.finish()?;
2743
2744        Ok(FluidPressure { offsets: [o0], buf })
2745    }
2746
2747    pub fn into_cdr(self) -> Vec<u8> {
2748        self.buf
2749    }
2750
2751    /// Start a new `FluidPressureBuilder` with zero-valued defaults.
2752    pub fn builder<'a>() -> FluidPressureBuilder<'a> {
2753        FluidPressureBuilder::new()
2754    }
2755}
2756
2757// ── FluidPressureBuilder<'a> ────────────────────────────────────────
2758
2759/// Builder for `FluidPressure<Vec<u8>>` with buffer-reuse finalizers.
2760pub struct FluidPressureBuilder<'a> {
2761    stamp: Time,
2762    frame_id: std::borrow::Cow<'a, str>,
2763    fluid_pressure: f64,
2764    variance: f64,
2765}
2766
2767impl<'a> Default for FluidPressureBuilder<'a> {
2768    fn default() -> Self {
2769        Self {
2770            stamp: Time { sec: 0, nanosec: 0 },
2771            frame_id: std::borrow::Cow::Borrowed(""),
2772            fluid_pressure: 0.0,
2773            variance: 0.0,
2774        }
2775    }
2776}
2777
2778impl<'a> FluidPressureBuilder<'a> {
2779    pub fn new() -> Self {
2780        Self::default()
2781    }
2782
2783    pub fn stamp(&mut self, t: Time) -> &mut Self {
2784        self.stamp = t;
2785        self
2786    }
2787    pub fn frame_id(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
2788        self.frame_id = s.into();
2789        self
2790    }
2791    pub fn fluid_pressure(&mut self, v: f64) -> &mut Self {
2792        self.fluid_pressure = v;
2793        self
2794    }
2795    pub fn variance(&mut self, v: f64) -> &mut Self {
2796        self.variance = v;
2797        self
2798    }
2799
2800    fn size(&self) -> usize {
2801        let mut s = CdrSizer::new();
2802        Time::size_cdr(&mut s);
2803        s.size_string(&self.frame_id);
2804        s.align(8);
2805        s.size_f64();
2806        s.size_f64();
2807        s.size()
2808    }
2809
2810    fn write_into(&self, buf: &mut [u8]) -> Result<(), CdrError> {
2811        let mut w = CdrWriter::new(buf)?;
2812        self.stamp.write_cdr(&mut w);
2813        w.write_string(&self.frame_id);
2814        w.write_f64(self.fluid_pressure);
2815        w.write_f64(self.variance);
2816        w.finish()
2817    }
2818
2819    pub fn build(&self) -> Result<FluidPressure<Vec<u8>>, CdrError> {
2820        let mut buf = vec![0u8; self.size()];
2821        self.write_into(&mut buf)?;
2822        FluidPressure::from_cdr(buf)
2823    }
2824
2825    pub fn encode_into_vec(&self, buf: &mut Vec<u8>) -> Result<(), CdrError> {
2826        buf.resize(self.size(), 0);
2827        self.write_into(buf)
2828    }
2829
2830    pub fn encode_into_slice(&self, buf: &mut [u8]) -> Result<usize, CdrError> {
2831        let need = self.size();
2832        if buf.len() < need {
2833            return Err(CdrError::BufferTooShort {
2834                need,
2835                have: buf.len(),
2836            });
2837        }
2838        self.write_into(&mut buf[..need])?;
2839        Ok(need)
2840    }
2841}
2842
2843impl<B: AsRef<[u8]> + AsMut<[u8]>> FluidPressure<B> {
2844    pub fn set_stamp(&mut self, t: Time) -> Result<(), CdrError> {
2845        let b = self.buf.as_mut();
2846        wr_i32(b, CDR_HEADER_SIZE, t.sec)?;
2847        wr_u32(b, CDR_HEADER_SIZE + 4, t.nanosec)
2848    }
2849
2850    pub fn set_fluid_pressure(&mut self, v: f64) -> Result<(), CdrError> {
2851        wr_f64(self.buf.as_mut(), self.offsets[0], v)
2852    }
2853
2854    pub fn set_variance(&mut self, v: f64) -> Result<(), CdrError> {
2855        wr_f64(self.buf.as_mut(), self.offsets[0] + 8, v)
2856    }
2857}
2858
2859// ── Temperature<B> ──────────────────────────────────────────────────
2860//
2861// CDR layout: Header → offsets[0] (start of temperature, 8-aligned),
2862//   float64 temperature (8 bytes), float64 variance (8 bytes).
2863
2864pub struct Temperature<B> {
2865    buf: B,
2866    offsets: [usize; 1],
2867}
2868
2869impl<B> Temperature<B> {
2870    /// Convert the buffer type without re-parsing the offset table.
2871    #[inline]
2872    pub fn map_buffer<C>(self, f: impl FnOnce(B) -> C) -> Temperature<C> {
2873        Temperature {
2874            buf: f(self.buf),
2875            offsets: self.offsets,
2876        }
2877    }
2878}
2879
2880impl<B: AsRef<[u8]>> Temperature<B> {
2881    pub fn from_cdr(buf: B) -> Result<Self, CdrError> {
2882        let header = Header::<&[u8]>::from_cdr(buf.as_ref())?;
2883        let pre = header.end_offset();
2884        let mut c = CdrCursor::resume(buf.as_ref(), pre);
2885        c.align(8);
2886        let o0 = c.offset();
2887        c.read_f64()?; // temperature
2888        c.read_f64()?; // variance
2889        Ok(Temperature { offsets: [o0], buf })
2890    }
2891
2892    pub fn header(&self) -> Header<&[u8]> {
2893        Header::from_cdr(self.buf.as_ref()).expect("header bytes validated during from_cdr")
2894    }
2895    pub fn stamp(&self) -> Time {
2896        rd_time(self.buf.as_ref(), CDR_HEADER_SIZE)
2897    }
2898    pub fn frame_id(&self) -> &str {
2899        rd_string(self.buf.as_ref(), CDR_HEADER_SIZE + 8).0
2900    }
2901    pub fn temperature(&self) -> f64 {
2902        rd_f64(self.buf.as_ref(), self.offsets[0])
2903    }
2904    pub fn variance(&self) -> f64 {
2905        rd_f64(self.buf.as_ref(), self.offsets[0] + 8)
2906    }
2907    pub fn as_cdr(&self) -> &[u8] {
2908        self.buf.as_ref()
2909    }
2910    pub fn to_cdr(&self) -> Vec<u8> {
2911        self.buf.as_ref().to_vec()
2912    }
2913}
2914
2915impl Temperature<Vec<u8>> {
2916    #[deprecated(
2917        since = "3.2.0",
2918        note = "use Temperature::builder() for allocation-free buffer reuse; Temperature::new will be removed in 4.0"
2919    )]
2920    pub fn new(
2921        stamp: Time,
2922        frame_id: &str,
2923        temperature: f64,
2924        variance: f64,
2925    ) -> Result<Self, CdrError> {
2926        let mut sizer = CdrSizer::new();
2927        Time::size_cdr(&mut sizer);
2928        sizer.size_string(frame_id);
2929        sizer.align(8);
2930        let o0 = sizer.offset();
2931        sizer.size_f64();
2932        sizer.size_f64();
2933
2934        let mut buf = vec![0u8; sizer.size()];
2935        let mut w = CdrWriter::new(&mut buf)?;
2936        stamp.write_cdr(&mut w);
2937        w.write_string(frame_id);
2938        w.write_f64(temperature);
2939        w.write_f64(variance);
2940        w.finish()?;
2941
2942        Ok(Temperature { offsets: [o0], buf })
2943    }
2944
2945    pub fn into_cdr(self) -> Vec<u8> {
2946        self.buf
2947    }
2948
2949    /// Start a new `TemperatureBuilder` with zero-valued defaults.
2950    pub fn builder<'a>() -> TemperatureBuilder<'a> {
2951        TemperatureBuilder::new()
2952    }
2953}
2954
2955// ── TemperatureBuilder<'a> ──────────────────────────────────────────
2956
2957/// Builder for `Temperature<Vec<u8>>` with buffer-reuse finalizers.
2958pub struct TemperatureBuilder<'a> {
2959    stamp: Time,
2960    frame_id: std::borrow::Cow<'a, str>,
2961    temperature: f64,
2962    variance: f64,
2963}
2964
2965impl<'a> Default for TemperatureBuilder<'a> {
2966    fn default() -> Self {
2967        Self {
2968            stamp: Time { sec: 0, nanosec: 0 },
2969            frame_id: std::borrow::Cow::Borrowed(""),
2970            temperature: 0.0,
2971            variance: 0.0,
2972        }
2973    }
2974}
2975
2976impl<'a> TemperatureBuilder<'a> {
2977    pub fn new() -> Self {
2978        Self::default()
2979    }
2980
2981    pub fn stamp(&mut self, t: Time) -> &mut Self {
2982        self.stamp = t;
2983        self
2984    }
2985    pub fn frame_id(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
2986        self.frame_id = s.into();
2987        self
2988    }
2989    pub fn temperature(&mut self, v: f64) -> &mut Self {
2990        self.temperature = v;
2991        self
2992    }
2993    pub fn variance(&mut self, v: f64) -> &mut Self {
2994        self.variance = v;
2995        self
2996    }
2997
2998    fn size(&self) -> usize {
2999        let mut s = CdrSizer::new();
3000        Time::size_cdr(&mut s);
3001        s.size_string(&self.frame_id);
3002        s.align(8);
3003        s.size_f64();
3004        s.size_f64();
3005        s.size()
3006    }
3007
3008    fn write_into(&self, buf: &mut [u8]) -> Result<(), CdrError> {
3009        let mut w = CdrWriter::new(buf)?;
3010        self.stamp.write_cdr(&mut w);
3011        w.write_string(&self.frame_id);
3012        w.write_f64(self.temperature);
3013        w.write_f64(self.variance);
3014        w.finish()
3015    }
3016
3017    pub fn build(&self) -> Result<Temperature<Vec<u8>>, CdrError> {
3018        let mut buf = vec![0u8; self.size()];
3019        self.write_into(&mut buf)?;
3020        Temperature::from_cdr(buf)
3021    }
3022
3023    pub fn encode_into_vec(&self, buf: &mut Vec<u8>) -> Result<(), CdrError> {
3024        buf.resize(self.size(), 0);
3025        self.write_into(buf)
3026    }
3027
3028    pub fn encode_into_slice(&self, buf: &mut [u8]) -> Result<usize, CdrError> {
3029        let need = self.size();
3030        if buf.len() < need {
3031            return Err(CdrError::BufferTooShort {
3032                need,
3033                have: buf.len(),
3034            });
3035        }
3036        self.write_into(&mut buf[..need])?;
3037        Ok(need)
3038    }
3039}
3040
3041impl<B: AsRef<[u8]> + AsMut<[u8]>> Temperature<B> {
3042    pub fn set_stamp(&mut self, t: Time) -> Result<(), CdrError> {
3043        let b = self.buf.as_mut();
3044        wr_i32(b, CDR_HEADER_SIZE, t.sec)?;
3045        wr_u32(b, CDR_HEADER_SIZE + 4, t.nanosec)
3046    }
3047
3048    pub fn set_temperature(&mut self, v: f64) -> Result<(), CdrError> {
3049        wr_f64(self.buf.as_mut(), self.offsets[0], v)
3050    }
3051
3052    pub fn set_variance(&mut self, v: f64) -> Result<(), CdrError> {
3053        wr_f64(self.buf.as_mut(), self.offsets[0] + 8, v)
3054    }
3055}
3056
3057// ── BatteryState<B> ─────────────────────────────────────────────────
3058//
3059// CDR layout: Header → offsets[0] (start of fixed scalars, 4-aligned),
3060//   float32 voltage, temperature, current, charge, capacity,
3061//   design_capacity, percentage (7×4 = 28 bytes),
3062//   uint8 power_supply_status, power_supply_health,
3063//   power_supply_technology, bool present (4 bytes, no trailing pad
3064//   until next 4-aligned field),
3065//   uint32 count + float32[] cell_voltage → offsets[1] (byte index of
3066//     the seq count u32),
3067//   uint32 count + float32[] cell_temperature → offsets[2],
3068//   string location → offsets[3],
3069//   string serial_number → offsets[4].
3070
3071/// `POWER_SUPPLY_STATUS_*` values for [`BatteryState::power_supply_status`].
3072pub mod power_supply_status {
3073    pub const UNKNOWN: u8 = 0;
3074    pub const CHARGING: u8 = 1;
3075    pub const DISCHARGING: u8 = 2;
3076    pub const NOT_CHARGING: u8 = 3;
3077    pub const FULL: u8 = 4;
3078}
3079
3080/// `POWER_SUPPLY_HEALTH_*` values for [`BatteryState::power_supply_health`].
3081pub mod power_supply_health {
3082    pub const UNKNOWN: u8 = 0;
3083    pub const GOOD: u8 = 1;
3084    pub const OVERHEAT: u8 = 2;
3085    pub const DEAD: u8 = 3;
3086    pub const OVERVOLTAGE: u8 = 4;
3087    pub const UNSPEC_FAILURE: u8 = 5;
3088    pub const COLD: u8 = 6;
3089    pub const WATCHDOG_TIMER_EXPIRE: u8 = 7;
3090    pub const SAFETY_TIMER_EXPIRE: u8 = 8;
3091}
3092
3093/// `POWER_SUPPLY_TECHNOLOGY_*` values for [`BatteryState::power_supply_technology`].
3094pub mod power_supply_technology {
3095    pub const UNKNOWN: u8 = 0;
3096    pub const NIMH: u8 = 1;
3097    pub const LION: u8 = 2;
3098    pub const LIPO: u8 = 3;
3099    pub const LIFE: u8 = 4;
3100    pub const NICD: u8 = 5;
3101    pub const LIMN: u8 = 6;
3102}
3103
3104pub struct BatteryState<B> {
3105    buf: B,
3106    // [0]: voltage start (4-aligned after Header).
3107    // [1]: start of cell_voltage seq-count u32.
3108    // [2]: start of cell_temperature seq-count u32.
3109    // [3]: start of location string.
3110    // [4]: start of serial_number string.
3111    offsets: [usize; 5],
3112}
3113
3114impl<B> BatteryState<B> {
3115    /// Convert the buffer type without re-parsing the offset table.
3116    #[inline]
3117    pub fn map_buffer<C>(self, f: impl FnOnce(B) -> C) -> BatteryState<C> {
3118        BatteryState {
3119            buf: f(self.buf),
3120            offsets: self.offsets,
3121        }
3122    }
3123}
3124
3125impl<B: AsRef<[u8]>> BatteryState<B> {
3126    pub fn from_cdr(buf: B) -> Result<Self, CdrError> {
3127        let header = Header::<&[u8]>::from_cdr(buf.as_ref())?;
3128        let pre = header.end_offset();
3129        let mut c = CdrCursor::resume(buf.as_ref(), pre);
3130        c.align(4);
3131        let o0 = c.offset();
3132        // Seven f32 scalars
3133        for _ in 0..7 {
3134            c.read_f32()?;
3135        }
3136        c.read_u8()?; // power_supply_status
3137        c.read_u8()?; // power_supply_health
3138        c.read_u8()?; // power_supply_technology
3139        c.read_bool()?; // present
3140        let o1 = c.offset();
3141        // f32 = 4 bytes each; hardening check against pathological counts.
3142        let raw = c.read_u32()?;
3143        let cv_len = c.check_seq_count(raw, 4)?;
3144        for _ in 0..cv_len {
3145            c.read_f32()?;
3146        }
3147        let o2 = c.offset();
3148        let raw = c.read_u32()?;
3149        let ct_len = c.check_seq_count(raw, 4)?;
3150        for _ in 0..ct_len {
3151            c.read_f32()?;
3152        }
3153        let o3 = c.offset();
3154        c.read_string()?; // location
3155        let o4 = c.offset();
3156        c.read_string()?; // serial_number
3157        Ok(BatteryState {
3158            offsets: [o0, o1, o2, o3, o4],
3159            buf,
3160        })
3161    }
3162
3163    pub fn header(&self) -> Header<&[u8]> {
3164        Header::from_cdr(self.buf.as_ref()).expect("header bytes validated during from_cdr")
3165    }
3166    pub fn stamp(&self) -> Time {
3167        rd_time(self.buf.as_ref(), CDR_HEADER_SIZE)
3168    }
3169    pub fn frame_id(&self) -> &str {
3170        rd_string(self.buf.as_ref(), CDR_HEADER_SIZE + 8).0
3171    }
3172    pub fn voltage(&self) -> f32 {
3173        rd_f32(self.buf.as_ref(), self.offsets[0])
3174    }
3175    pub fn temperature(&self) -> f32 {
3176        rd_f32(self.buf.as_ref(), self.offsets[0] + 4)
3177    }
3178    pub fn current(&self) -> f32 {
3179        rd_f32(self.buf.as_ref(), self.offsets[0] + 8)
3180    }
3181    pub fn charge(&self) -> f32 {
3182        rd_f32(self.buf.as_ref(), self.offsets[0] + 12)
3183    }
3184    pub fn capacity(&self) -> f32 {
3185        rd_f32(self.buf.as_ref(), self.offsets[0] + 16)
3186    }
3187    pub fn design_capacity(&self) -> f32 {
3188        rd_f32(self.buf.as_ref(), self.offsets[0] + 20)
3189    }
3190    pub fn percentage(&self) -> f32 {
3191        rd_f32(self.buf.as_ref(), self.offsets[0] + 24)
3192    }
3193    pub fn power_supply_status(&self) -> u8 {
3194        rd_u8(self.buf.as_ref(), self.offsets[0] + 28)
3195    }
3196    pub fn power_supply_health(&self) -> u8 {
3197        rd_u8(self.buf.as_ref(), self.offsets[0] + 29)
3198    }
3199    pub fn power_supply_technology(&self) -> u8 {
3200        rd_u8(self.buf.as_ref(), self.offsets[0] + 30)
3201    }
3202    pub fn present(&self) -> bool {
3203        rd_bool(self.buf.as_ref(), self.offsets[0] + 31)
3204    }
3205    pub fn cell_voltage_len(&self) -> u32 {
3206        rd_u32(self.buf.as_ref(), self.offsets[1])
3207    }
3208    /// Byte offset of the `cell_voltage` sequence (u32 count, then elements).
3209    /// Exposed for allocation-free decoders (e.g. FFI).
3210    pub fn cell_voltage_seq_offset(&self) -> usize {
3211        self.offsets[1]
3212    }
3213    pub fn cell_voltage(&self) -> Vec<f32> {
3214        let mut c = CdrCursor::resume(self.buf.as_ref(), self.offsets[1]);
3215        let n = c
3216            .read_u32()
3217            .expect("cell_voltage length validated during from_cdr") as usize;
3218        let mut out = Vec::with_capacity(n);
3219        for _ in 0..n {
3220            out.push(
3221                c.read_f32()
3222                    .expect("cell_voltage element validated during from_cdr"),
3223            );
3224        }
3225        out
3226    }
3227    pub fn cell_temperature_len(&self) -> u32 {
3228        rd_u32(self.buf.as_ref(), self.offsets[2])
3229    }
3230    /// Byte offset of the `cell_temperature` sequence (u32 count, then elements).
3231    /// Exposed for allocation-free decoders (e.g. FFI).
3232    pub fn cell_temperature_seq_offset(&self) -> usize {
3233        self.offsets[2]
3234    }
3235    pub fn cell_temperature(&self) -> Vec<f32> {
3236        let mut c = CdrCursor::resume(self.buf.as_ref(), self.offsets[2]);
3237        let n = c
3238            .read_u32()
3239            .expect("cell_temperature length validated during from_cdr") as usize;
3240        let mut out = Vec::with_capacity(n);
3241        for _ in 0..n {
3242            out.push(
3243                c.read_f32()
3244                    .expect("cell_temperature element validated during from_cdr"),
3245            );
3246        }
3247        out
3248    }
3249    pub fn location(&self) -> &str {
3250        rd_string(self.buf.as_ref(), self.offsets[3]).0
3251    }
3252    pub fn serial_number(&self) -> &str {
3253        rd_string(self.buf.as_ref(), self.offsets[4]).0
3254    }
3255    pub fn as_cdr(&self) -> &[u8] {
3256        self.buf.as_ref()
3257    }
3258    pub fn to_cdr(&self) -> Vec<u8> {
3259        self.buf.as_ref().to_vec()
3260    }
3261}
3262
3263impl BatteryState<Vec<u8>> {
3264    #[deprecated(
3265        since = "3.2.0",
3266        note = "use BatteryState::builder() for allocation-free buffer reuse; BatteryState::new will be removed in 4.0"
3267    )]
3268    #[allow(clippy::too_many_arguments)]
3269    pub fn new(
3270        stamp: Time,
3271        frame_id: &str,
3272        voltage: f32,
3273        temperature: f32,
3274        current: f32,
3275        charge: f32,
3276        capacity: f32,
3277        design_capacity: f32,
3278        percentage: f32,
3279        power_supply_status: u8,
3280        power_supply_health: u8,
3281        power_supply_technology: u8,
3282        present: bool,
3283        cell_voltage: &[f32],
3284        cell_temperature: &[f32],
3285        location: &str,
3286        serial_number: &str,
3287    ) -> Result<Self, CdrError> {
3288        let mut sizer = CdrSizer::new();
3289        Time::size_cdr(&mut sizer);
3290        sizer.size_string(frame_id);
3291        sizer.align(4);
3292        let o0 = sizer.offset();
3293        for _ in 0..7 {
3294            sizer.size_f32();
3295        }
3296        sizer.size_u8();
3297        sizer.size_u8();
3298        sizer.size_u8();
3299        sizer.size_bool();
3300        let o1 = sizer.offset();
3301        sizer.size_u32();
3302        for _ in cell_voltage {
3303            sizer.size_f32();
3304        }
3305        let o2 = sizer.offset();
3306        sizer.size_u32();
3307        for _ in cell_temperature {
3308            sizer.size_f32();
3309        }
3310        let o3 = sizer.offset();
3311        sizer.size_string(location);
3312        let o4 = sizer.offset();
3313        sizer.size_string(serial_number);
3314
3315        let mut buf = vec![0u8; sizer.size()];
3316        let mut w = CdrWriter::new(&mut buf)?;
3317        stamp.write_cdr(&mut w);
3318        w.write_string(frame_id);
3319        w.write_f32(voltage);
3320        w.write_f32(temperature);
3321        w.write_f32(current);
3322        w.write_f32(charge);
3323        w.write_f32(capacity);
3324        w.write_f32(design_capacity);
3325        w.write_f32(percentage);
3326        w.write_u8(power_supply_status);
3327        w.write_u8(power_supply_health);
3328        w.write_u8(power_supply_technology);
3329        w.write_bool(present);
3330        w.write_u32(cell_voltage.len() as u32);
3331        for v in cell_voltage {
3332            w.write_f32(*v);
3333        }
3334        w.write_u32(cell_temperature.len() as u32);
3335        for v in cell_temperature {
3336            w.write_f32(*v);
3337        }
3338        w.write_string(location);
3339        w.write_string(serial_number);
3340        w.finish()?;
3341
3342        Ok(BatteryState {
3343            offsets: [o0, o1, o2, o3, o4],
3344            buf,
3345        })
3346    }
3347
3348    pub fn into_cdr(self) -> Vec<u8> {
3349        self.buf
3350    }
3351
3352    /// Start a new `BatteryStateBuilder` with zero-valued defaults.
3353    pub fn builder<'a>() -> BatteryStateBuilder<'a> {
3354        BatteryStateBuilder::new()
3355    }
3356}
3357
3358// ── BatteryStateBuilder<'a> ─────────────────────────────────────────
3359
3360/// Builder for `BatteryState<Vec<u8>>` with buffer-reuse finalizers.
3361///
3362/// The two `f32` sequences (`cell_voltage`, `cell_temperature`) are
3363/// borrowed for zero-copy input; both strings use `Cow<'a, str>`.
3364pub struct BatteryStateBuilder<'a> {
3365    stamp: Time,
3366    frame_id: std::borrow::Cow<'a, str>,
3367    voltage: f32,
3368    temperature: f32,
3369    current: f32,
3370    charge: f32,
3371    capacity: f32,
3372    design_capacity: f32,
3373    percentage: f32,
3374    power_supply_status: u8,
3375    power_supply_health: u8,
3376    power_supply_technology: u8,
3377    present: bool,
3378    cell_voltage: &'a [f32],
3379    cell_temperature: &'a [f32],
3380    location: std::borrow::Cow<'a, str>,
3381    serial_number: std::borrow::Cow<'a, str>,
3382}
3383
3384impl<'a> Default for BatteryStateBuilder<'a> {
3385    fn default() -> Self {
3386        Self {
3387            stamp: Time { sec: 0, nanosec: 0 },
3388            frame_id: std::borrow::Cow::Borrowed(""),
3389            voltage: 0.0,
3390            temperature: 0.0,
3391            current: 0.0,
3392            charge: 0.0,
3393            capacity: 0.0,
3394            design_capacity: 0.0,
3395            percentage: 0.0,
3396            power_supply_status: 0,
3397            power_supply_health: 0,
3398            power_supply_technology: 0,
3399            present: false,
3400            cell_voltage: &[],
3401            cell_temperature: &[],
3402            location: std::borrow::Cow::Borrowed(""),
3403            serial_number: std::borrow::Cow::Borrowed(""),
3404        }
3405    }
3406}
3407
3408impl<'a> BatteryStateBuilder<'a> {
3409    pub fn new() -> Self {
3410        Self::default()
3411    }
3412
3413    pub fn stamp(&mut self, t: Time) -> &mut Self {
3414        self.stamp = t;
3415        self
3416    }
3417    pub fn frame_id(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
3418        self.frame_id = s.into();
3419        self
3420    }
3421    pub fn voltage(&mut self, v: f32) -> &mut Self {
3422        self.voltage = v;
3423        self
3424    }
3425    pub fn temperature(&mut self, v: f32) -> &mut Self {
3426        self.temperature = v;
3427        self
3428    }
3429    pub fn current(&mut self, v: f32) -> &mut Self {
3430        self.current = v;
3431        self
3432    }
3433    pub fn charge(&mut self, v: f32) -> &mut Self {
3434        self.charge = v;
3435        self
3436    }
3437    pub fn capacity(&mut self, v: f32) -> &mut Self {
3438        self.capacity = v;
3439        self
3440    }
3441    pub fn design_capacity(&mut self, v: f32) -> &mut Self {
3442        self.design_capacity = v;
3443        self
3444    }
3445    pub fn percentage(&mut self, v: f32) -> &mut Self {
3446        self.percentage = v;
3447        self
3448    }
3449    pub fn power_supply_status(&mut self, v: u8) -> &mut Self {
3450        self.power_supply_status = v;
3451        self
3452    }
3453    pub fn power_supply_health(&mut self, v: u8) -> &mut Self {
3454        self.power_supply_health = v;
3455        self
3456    }
3457    pub fn power_supply_technology(&mut self, v: u8) -> &mut Self {
3458        self.power_supply_technology = v;
3459        self
3460    }
3461    pub fn present(&mut self, v: bool) -> &mut Self {
3462        self.present = v;
3463        self
3464    }
3465    pub fn cell_voltage(&mut self, v: &'a [f32]) -> &mut Self {
3466        self.cell_voltage = v;
3467        self
3468    }
3469    pub fn cell_temperature(&mut self, v: &'a [f32]) -> &mut Self {
3470        self.cell_temperature = v;
3471        self
3472    }
3473    pub fn location(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
3474        self.location = s.into();
3475        self
3476    }
3477    pub fn serial_number(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
3478        self.serial_number = s.into();
3479        self
3480    }
3481
3482    fn size(&self) -> usize {
3483        let mut s = CdrSizer::new();
3484        Time::size_cdr(&mut s);
3485        s.size_string(&self.frame_id);
3486        s.align(4);
3487        for _ in 0..7 {
3488            s.size_f32();
3489        }
3490        s.size_u8();
3491        s.size_u8();
3492        s.size_u8();
3493        s.size_bool();
3494        s.size_u32();
3495        for _ in self.cell_voltage {
3496            s.size_f32();
3497        }
3498        s.size_u32();
3499        for _ in self.cell_temperature {
3500            s.size_f32();
3501        }
3502        s.size_string(&self.location);
3503        s.size_string(&self.serial_number);
3504        s.size()
3505    }
3506
3507    fn write_into(&self, buf: &mut [u8]) -> Result<(), CdrError> {
3508        let mut w = CdrWriter::new(buf)?;
3509        self.stamp.write_cdr(&mut w);
3510        w.write_string(&self.frame_id);
3511        w.write_f32(self.voltage);
3512        w.write_f32(self.temperature);
3513        w.write_f32(self.current);
3514        w.write_f32(self.charge);
3515        w.write_f32(self.capacity);
3516        w.write_f32(self.design_capacity);
3517        w.write_f32(self.percentage);
3518        w.write_u8(self.power_supply_status);
3519        w.write_u8(self.power_supply_health);
3520        w.write_u8(self.power_supply_technology);
3521        w.write_bool(self.present);
3522        w.write_u32(self.cell_voltage.len() as u32);
3523        for v in self.cell_voltage {
3524            w.write_f32(*v);
3525        }
3526        w.write_u32(self.cell_temperature.len() as u32);
3527        for v in self.cell_temperature {
3528            w.write_f32(*v);
3529        }
3530        w.write_string(&self.location);
3531        w.write_string(&self.serial_number);
3532        w.finish()
3533    }
3534
3535    pub fn build(&self) -> Result<BatteryState<Vec<u8>>, CdrError> {
3536        let mut buf = vec![0u8; self.size()];
3537        self.write_into(&mut buf)?;
3538        BatteryState::from_cdr(buf)
3539    }
3540
3541    pub fn encode_into_vec(&self, buf: &mut Vec<u8>) -> Result<(), CdrError> {
3542        buf.resize(self.size(), 0);
3543        self.write_into(buf)
3544    }
3545
3546    pub fn encode_into_slice(&self, buf: &mut [u8]) -> Result<usize, CdrError> {
3547        let need = self.size();
3548        if buf.len() < need {
3549            return Err(CdrError::BufferTooShort {
3550                need,
3551                have: buf.len(),
3552            });
3553        }
3554        self.write_into(&mut buf[..need])?;
3555        Ok(need)
3556    }
3557}
3558
3559impl<B: AsRef<[u8]> + AsMut<[u8]>> BatteryState<B> {
3560    pub fn set_stamp(&mut self, t: Time) -> Result<(), CdrError> {
3561        let b = self.buf.as_mut();
3562        wr_i32(b, CDR_HEADER_SIZE, t.sec)?;
3563        wr_u32(b, CDR_HEADER_SIZE + 4, t.nanosec)
3564    }
3565
3566    pub fn set_voltage(&mut self, v: f32) -> Result<(), CdrError> {
3567        wr_f32(self.buf.as_mut(), self.offsets[0], v)
3568    }
3569
3570    pub fn set_temperature(&mut self, v: f32) -> Result<(), CdrError> {
3571        wr_f32(self.buf.as_mut(), self.offsets[0] + 4, v)
3572    }
3573
3574    pub fn set_current(&mut self, v: f32) -> Result<(), CdrError> {
3575        wr_f32(self.buf.as_mut(), self.offsets[0] + 8, v)
3576    }
3577
3578    pub fn set_charge(&mut self, v: f32) -> Result<(), CdrError> {
3579        wr_f32(self.buf.as_mut(), self.offsets[0] + 12, v)
3580    }
3581
3582    pub fn set_capacity(&mut self, v: f32) -> Result<(), CdrError> {
3583        wr_f32(self.buf.as_mut(), self.offsets[0] + 16, v)
3584    }
3585
3586    pub fn set_design_capacity(&mut self, v: f32) -> Result<(), CdrError> {
3587        wr_f32(self.buf.as_mut(), self.offsets[0] + 20, v)
3588    }
3589
3590    pub fn set_percentage(&mut self, v: f32) -> Result<(), CdrError> {
3591        wr_f32(self.buf.as_mut(), self.offsets[0] + 24, v)
3592    }
3593
3594    pub fn set_power_supply_status(&mut self, v: u8) -> Result<(), CdrError> {
3595        wr_u8(self.buf.as_mut(), self.offsets[0] + 28, v)
3596    }
3597
3598    pub fn set_power_supply_health(&mut self, v: u8) -> Result<(), CdrError> {
3599        wr_u8(self.buf.as_mut(), self.offsets[0] + 29, v)
3600    }
3601
3602    pub fn set_power_supply_technology(&mut self, v: u8) -> Result<(), CdrError> {
3603        wr_u8(self.buf.as_mut(), self.offsets[0] + 30, v)
3604    }
3605
3606    pub fn set_present(&mut self, v: bool) -> Result<(), CdrError> {
3607        wr_bool(self.buf.as_mut(), self.offsets[0] + 31, v)
3608    }
3609}
3610
3611// ── Registry ────────────────────────────────────────────────────────
3612
3613/// Check if a type name is supported by this module.
3614pub fn is_type_supported(type_name: &str) -> bool {
3615    matches!(
3616        type_name,
3617        "BatteryState"
3618            | "CameraInfo"
3619            | "CompressedImage"
3620            | "FluidPressure"
3621            | "Image"
3622            | "Imu"
3623            | "MagneticField"
3624            | "NavSatFix"
3625            | "NavSatStatus"
3626            | "PointCloud2"
3627            | "PointField"
3628            | "RegionOfInterest"
3629            | "Temperature"
3630    )
3631}
3632
3633/// List all type schema names in this module.
3634pub fn list_types() -> &'static [&'static str] {
3635    &[
3636        "sensor_msgs/msg/BatteryState",
3637        "sensor_msgs/msg/CameraInfo",
3638        "sensor_msgs/msg/CompressedImage",
3639        "sensor_msgs/msg/FluidPressure",
3640        "sensor_msgs/msg/Image",
3641        "sensor_msgs/msg/Imu",
3642        "sensor_msgs/msg/MagneticField",
3643        "sensor_msgs/msg/NavSatFix",
3644        "sensor_msgs/msg/NavSatStatus",
3645        "sensor_msgs/msg/PointCloud2",
3646        "sensor_msgs/msg/PointField",
3647        "sensor_msgs/msg/RegionOfInterest",
3648        "sensor_msgs/msg/Temperature",
3649    ]
3650}
3651
3652// SchemaType implementations
3653use crate::schema_registry::SchemaType;
3654
3655impl SchemaType for NavSatStatus {
3656    const SCHEMA_NAME: &'static str = "sensor_msgs/msg/NavSatStatus";
3657}
3658
3659impl SchemaType for RegionOfInterest {
3660    const SCHEMA_NAME: &'static str = "sensor_msgs/msg/RegionOfInterest";
3661}
3662
3663#[cfg(test)]
3664#[allow(deprecated)] // Tests exercise Image::new / PointCloud2::new, which are deprecated in 3.2.0 but still supported until 4.0.
3665mod tests {
3666    use super::*;
3667    use crate::builtin_interfaces::Time;
3668    use crate::cdr::{decode_fixed, encode_fixed};
3669    use crate::geometry_msgs::{Quaternion, Vector3};
3670
3671    #[test]
3672    fn compressed_image_roundtrip() {
3673        let img = CompressedImage::new(
3674            Time::new(100, 500_000_000),
3675            "camera",
3676            "jpeg",
3677            &[0xFF, 0xD8, 0xFF],
3678        )
3679        .unwrap();
3680        assert_eq!(img.stamp(), Time::new(100, 500_000_000));
3681        assert_eq!(img.frame_id(), "camera");
3682        assert_eq!(img.format(), "jpeg");
3683        assert_eq!(img.data(), &[0xFF, 0xD8, 0xFF]);
3684
3685        let bytes = img.to_cdr();
3686        let decoded = CompressedImage::from_cdr(bytes).unwrap();
3687        assert_eq!(decoded.format(), "jpeg");
3688        assert_eq!(decoded.data(), &[0xFF, 0xD8, 0xFF]);
3689    }
3690
3691    #[test]
3692    fn image_roundtrip() {
3693        let data = vec![128u8; 1920 * 480];
3694        let img = Image::new(
3695            Time::new(100, 500_000_000),
3696            "camera_optical",
3697            480,
3698            640,
3699            "rgb8",
3700            0,
3701            1920,
3702            &data,
3703        )
3704        .unwrap();
3705        assert_eq!(img.height(), 480);
3706        assert_eq!(img.width(), 640);
3707        assert_eq!(img.encoding(), "rgb8");
3708        assert_eq!(img.is_bigendian(), 0);
3709        assert_eq!(img.step(), 1920);
3710        assert_eq!(img.data().len(), 1920 * 480);
3711
3712        let bytes = img.to_cdr();
3713        let decoded = Image::from_cdr(bytes).unwrap();
3714        assert_eq!(decoded.height(), 480);
3715        assert_eq!(decoded.width(), 640);
3716    }
3717
3718    #[test]
3719    fn imu_roundtrip() {
3720        let imu = Imu::new(
3721            Time::new(100, 0),
3722            "imu_link",
3723            Quaternion {
3724                x: 0.0,
3725                y: 0.0,
3726                z: 0.0,
3727                w: 1.0,
3728            },
3729            [0.0; 9],
3730            Vector3 {
3731                x: 0.1,
3732                y: 0.2,
3733                z: 9.8,
3734            },
3735            [0.0; 9],
3736            Vector3 {
3737                x: 0.0,
3738                y: 0.0,
3739                z: 0.0,
3740            },
3741            [0.0; 9],
3742        )
3743        .unwrap();
3744        assert_eq!(imu.stamp(), Time::new(100, 0));
3745        let bytes = imu.to_cdr();
3746        let decoded = Imu::from_cdr(bytes).unwrap();
3747        assert_eq!(decoded.orientation().w, 1.0);
3748        assert!((decoded.angular_velocity().z - 9.8).abs() < 1e-10);
3749    }
3750
3751    #[test]
3752    fn nav_sat_fix_roundtrip() {
3753        let fix = NavSatFix::new(
3754            Time::new(100, 0),
3755            "gps",
3756            NavSatStatus {
3757                status: 0,
3758                service: 1,
3759            },
3760            45.5017,
3761            -73.5673,
3762            100.0,
3763            [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0],
3764            2,
3765        )
3766        .unwrap();
3767        let bytes = fix.to_cdr();
3768        let decoded = NavSatFix::from_cdr(bytes).unwrap();
3769        assert!((decoded.latitude() - 45.5017).abs() < 1e-10);
3770        assert_eq!(decoded.position_covariance_type(), 2);
3771    }
3772
3773    #[test]
3774    fn nav_sat_status_roundtrip() {
3775        let status = NavSatStatus {
3776            status: 0,
3777            service: 1,
3778        };
3779        let bytes = encode_fixed(&status).unwrap();
3780        let decoded: NavSatStatus = decode_fixed(&bytes).unwrap();
3781        assert_eq!(status, decoded);
3782    }
3783
3784    #[test]
3785    fn region_of_interest_roundtrip() {
3786        let roi = RegionOfInterest {
3787            x_offset: 10,
3788            y_offset: 20,
3789            height: 100,
3790            width: 200,
3791            do_rectify: true,
3792        };
3793        let bytes = encode_fixed(&roi).unwrap();
3794        let decoded: RegionOfInterest = decode_fixed(&bytes).unwrap();
3795        assert_eq!(roi, decoded);
3796    }
3797
3798    #[test]
3799    fn point_cloud2_roundtrip() {
3800        let fields = [
3801            PointFieldView {
3802                name: "x",
3803                offset: 0,
3804                datatype: 7,
3805                count: 1,
3806            },
3807            PointFieldView {
3808                name: "y",
3809                offset: 4,
3810                datatype: 7,
3811                count: 1,
3812            },
3813            PointFieldView {
3814                name: "z",
3815                offset: 8,
3816                datatype: 7,
3817                count: 1,
3818            },
3819        ];
3820        let data = vec![0u8; 12288];
3821        let cloud = PointCloud2::new(
3822            Time::new(100, 0),
3823            "lidar",
3824            1,
3825            1024,
3826            &fields,
3827            false,
3828            12,
3829            12288,
3830            &data,
3831            true,
3832        )
3833        .unwrap();
3834        let bytes = cloud.to_cdr();
3835        let decoded = PointCloud2::from_cdr(bytes).unwrap();
3836        assert_eq!(decoded.height(), 1);
3837        assert_eq!(decoded.width(), 1024);
3838        assert_eq!(decoded.fields_len(), 3);
3839        assert!(decoded.is_dense());
3840    }
3841
3842    #[test]
3843    fn point_cloud2_fields_iter() {
3844        let fields = [
3845            PointFieldView {
3846                name: "x",
3847                offset: 0,
3848                datatype: 7,
3849                count: 1,
3850            },
3851            PointFieldView {
3852                name: "y",
3853                offset: 4,
3854                datatype: 7,
3855                count: 1,
3856            },
3857            PointFieldView {
3858                name: "z",
3859                offset: 8,
3860                datatype: 7,
3861                count: 1,
3862            },
3863        ];
3864        let data = vec![0u8; 36]; // 3 points × 12 bytes
3865        let cloud = PointCloud2::new(
3866            Time::new(100, 0),
3867            "lidar",
3868            1,
3869            3,
3870            &fields,
3871            false,
3872            12,
3873            36,
3874            &data,
3875            true,
3876        )
3877        .unwrap();
3878        let cdr = cloud.to_cdr();
3879        let decoded = PointCloud2::from_cdr(cdr).unwrap();
3880
3881        let iter_fields: Vec<_> = decoded.fields_iter().collect();
3882        assert_eq!(iter_fields.len(), 3);
3883        assert_eq!(iter_fields[0].name, "x");
3884        assert_eq!(iter_fields[1].name, "y");
3885        assert_eq!(iter_fields[2].name, "z");
3886        assert_eq!(iter_fields[0].offset, 0);
3887        assert_eq!(iter_fields[1].offset, 4);
3888        assert_eq!(iter_fields[2].offset, 8);
3889        assert_eq!(decoded.point_count(), 3);
3890    }
3891
3892    #[test]
3893    fn camera_info_roundtrip() {
3894        let roi = RegionOfInterest {
3895            x_offset: 0,
3896            y_offset: 0,
3897            height: 480,
3898            width: 640,
3899            do_rectify: false,
3900        };
3901        let cam = CameraInfo::new(
3902            Time::new(100, 0),
3903            "camera",
3904            480,
3905            640,
3906            "plumb_bob",
3907            &[0.1, -0.2, 0.0, 0.0, 0.0],
3908            [500.0, 0.0, 320.0, 0.0, 500.0, 240.0, 0.0, 0.0, 1.0],
3909            [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0],
3910            [
3911                500.0, 0.0, 320.0, 0.0, 0.0, 500.0, 240.0, 0.0, 0.0, 0.0, 1.0, 0.0,
3912            ],
3913            1,
3914            1,
3915            roi,
3916        )
3917        .unwrap();
3918        let bytes = cam.to_cdr();
3919        let decoded = CameraInfo::from_cdr(bytes).unwrap();
3920        assert_eq!(decoded.height(), 480);
3921        assert_eq!(decoded.width(), 640);
3922        assert_eq!(decoded.distortion_model(), "plumb_bob");
3923        assert_eq!(decoded.d_len(), 5);
3924        assert_eq!(decoded.binning_x(), 1);
3925    }
3926
3927    // EDGEAI-1243 regression: NavSatFix accessors must return the encoded
3928    // values for every valid `frame_id` length, not just the one used in the
3929    // golden fixture. The pre-fix `fixed_base = cdr_align(offsets[0] + 4, 8)`
3930    // formula was wrong whenever the CDR-relative position of `offsets[0]`
3931    // landed at a residue that made `NavSatStatus` occupy 3 bytes rather
3932    // than 4. Sweep frame_id lengths 0..=16 to cover every mod-8 residue.
3933    #[test]
3934    fn navsatfix_accessors_robust_across_frame_id_alignments() {
3935        let lat = 43.6532_f64;
3936        let lon = -79.3832_f64;
3937        let alt = 150.0_f64;
3938        let cov = [0.1, 0.0, 0.0, 0.0, 0.2, 0.0, 0.0, 0.0, 0.3_f64];
3939        let cov_type = 2_u8;
3940        let status = NavSatStatus {
3941            status: 0,
3942            service: 1,
3943        };
3944        for len in 0..=16_usize {
3945            let frame_id: String = "x".repeat(len);
3946            let fix = NavSatFix::new(
3947                Time::new(1, 0),
3948                &frame_id,
3949                status,
3950                lat,
3951                lon,
3952                alt,
3953                cov,
3954                cov_type,
3955            )
3956            .unwrap();
3957            let bytes = fix.to_cdr();
3958            let view = NavSatFix::from_cdr(&bytes[..]).unwrap();
3959            assert_eq!(view.frame_id(), frame_id, "frame_id at len={len}");
3960            let rx_status = view.status();
3961            assert_eq!(rx_status.status, 0, "status.status at len={len}");
3962            assert_eq!(rx_status.service, 1, "status.service at len={len}");
3963            assert_eq!(view.latitude(), lat, "latitude at len={len}");
3964            assert_eq!(view.longitude(), lon, "longitude at len={len}");
3965            assert_eq!(view.altitude(), alt, "altitude at len={len}");
3966            assert_eq!(
3967                view.position_covariance(),
3968                cov,
3969                "position_covariance at len={len}"
3970            );
3971            assert_eq!(
3972                view.position_covariance_type(),
3973                cov_type,
3974                "position_covariance_type at len={len}"
3975            );
3976        }
3977    }
3978
3979    // Direct reproduction of the EDGEAI-1243 bug report: `"gps_link"`
3980    // (8-char frame_id) is the canonical ROS 2 GPS frame and was the
3981    // original trigger. Kept alongside the parametric sweep so the
3982    // connection to the JIRA is explicit.
3983    #[test]
3984    fn navsatfix_gps_link_frame_id_regression() {
3985        let status = NavSatStatus {
3986            status: 0,
3987            service: 1,
3988        };
3989        let fix = NavSatFix::new(
3990            Time::new(0, 0),
3991            "gps_link",
3992            status,
3993            43.6532,
3994            -79.3832,
3995            150.0,
3996            [0.0; 9],
3997            0,
3998        )
3999        .unwrap();
4000        let bytes = fix.to_cdr();
4001        let rx = NavSatFix::from_cdr(&bytes[..]).unwrap();
4002        assert_eq!(rx.latitude(), 43.6532);
4003        assert_eq!(rx.longitude(), -79.3832);
4004        assert_eq!(rx.altitude(), 150.0);
4005    }
4006
4007    /// Guard: in-place scalar setters on every buffer-backed sensor_msgs
4008    /// type must produce byte-identical CDR to the equivalent builder.
4009    #[test]
4010    fn sensor_setter_byte_parity() {
4011        // Imu
4012        let imu_a = Imu::builder()
4013            .stamp(Time::new(100, 500))
4014            .frame_id("imu_link")
4015            .orientation(Quaternion {
4016                x: 0.1,
4017                y: 0.2,
4018                z: 0.3,
4019                w: 0.4,
4020            })
4021            .orientation_covariance([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0])
4022            .angular_velocity(Vector3 {
4023                x: 0.11,
4024                y: 0.22,
4025                z: 0.33,
4026            })
4027            .angular_velocity_covariance([10.0; 9])
4028            .linear_acceleration(Vector3 {
4029                x: 0.44,
4030                y: 0.55,
4031                z: 0.66,
4032            })
4033            .linear_acceleration_covariance([20.0; 9])
4034            .build()
4035            .unwrap();
4036        let mut imu_b = Imu::builder()
4037            .stamp(Time::new(0, 0))
4038            .frame_id("imu_link")
4039            .build()
4040            .unwrap();
4041        imu_b.set_stamp(Time::new(100, 500)).unwrap();
4042        imu_b
4043            .set_orientation(Quaternion {
4044                x: 0.1,
4045                y: 0.2,
4046                z: 0.3,
4047                w: 0.4,
4048            })
4049            .unwrap();
4050        imu_b
4051            .set_orientation_covariance([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0])
4052            .unwrap();
4053        imu_b
4054            .set_angular_velocity(Vector3 {
4055                x: 0.11,
4056                y: 0.22,
4057                z: 0.33,
4058            })
4059            .unwrap();
4060        imu_b.set_angular_velocity_covariance([10.0; 9]).unwrap();
4061        imu_b
4062            .set_linear_acceleration(Vector3 {
4063                x: 0.44,
4064                y: 0.55,
4065                z: 0.66,
4066            })
4067            .unwrap();
4068        imu_b.set_linear_acceleration_covariance([20.0; 9]).unwrap();
4069        assert_eq!(imu_a.as_cdr(), imu_b.as_cdr(), "Imu byte mismatch");
4070
4071        // NavSatFix — exercises the EDGEAI-1243-sensitive NavSatStatus setter.
4072        let nsf_a = NavSatFix::builder()
4073            .stamp(Time::new(5, 6))
4074            .frame_id("gps")
4075            .status(NavSatStatus {
4076                status: 1,
4077                service: 3,
4078            })
4079            .latitude(45.5)
4080            .longitude(-73.6)
4081            .altitude(100.0)
4082            .position_covariance([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])
4083            .position_covariance_type(2)
4084            .build()
4085            .unwrap();
4086        let mut nsf_b = NavSatFix::builder()
4087            .stamp(Time::new(0, 0))
4088            .frame_id("gps")
4089            .build()
4090            .unwrap();
4091        nsf_b.set_stamp(Time::new(5, 6)).unwrap();
4092        nsf_b
4093            .set_status(NavSatStatus {
4094                status: 1,
4095                service: 3,
4096            })
4097            .unwrap();
4098        nsf_b.set_latitude(45.5).unwrap();
4099        nsf_b.set_longitude(-73.6).unwrap();
4100        nsf_b.set_altitude(100.0).unwrap();
4101        nsf_b
4102            .set_position_covariance([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])
4103            .unwrap();
4104        nsf_b.set_position_covariance_type(2).unwrap();
4105        assert_eq!(nsf_a.as_cdr(), nsf_b.as_cdr(), "NavSatFix byte mismatch");
4106
4107        // PointField
4108        let pf_a = PointField::builder()
4109            .name("x")
4110            .offset(16)
4111            .datatype(7)
4112            .count(3)
4113            .build()
4114            .unwrap();
4115        let mut pf_b = PointField::builder().name("x").build().unwrap();
4116        pf_b.set_offset(16).unwrap();
4117        pf_b.set_datatype(7).unwrap();
4118        pf_b.set_count(3).unwrap();
4119        assert_eq!(pf_a.as_cdr(), pf_b.as_cdr(), "PointField byte mismatch");
4120
4121        // PointCloud2
4122        let pc_a = PointCloud2::builder()
4123            .stamp(Time::new(1, 2))
4124            .frame_id("base_link")
4125            .height(480)
4126            .width(640)
4127            .is_bigendian(true)
4128            .point_step(16)
4129            .row_step(16 * 640)
4130            .is_dense(true)
4131            .build()
4132            .unwrap();
4133        let mut pc_b = PointCloud2::builder()
4134            .stamp(Time::new(0, 0))
4135            .frame_id("base_link")
4136            .build()
4137            .unwrap();
4138        pc_b.set_stamp(Time::new(1, 2)).unwrap();
4139        pc_b.set_height(480).unwrap();
4140        pc_b.set_width(640).unwrap();
4141        pc_b.set_is_bigendian(true).unwrap();
4142        pc_b.set_point_step(16).unwrap();
4143        pc_b.set_row_step(16 * 640).unwrap();
4144        pc_b.set_is_dense(true).unwrap();
4145        assert_eq!(pc_a.as_cdr(), pc_b.as_cdr(), "PointCloud2 byte mismatch");
4146
4147        // CameraInfo
4148        let k = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
4149        let r = [10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0];
4150        let p = [
4151            20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0,
4152        ];
4153        let roi = RegionOfInterest {
4154            x_offset: 1,
4155            y_offset: 2,
4156            height: 3,
4157            width: 4,
4158            do_rectify: true,
4159        };
4160        let ci_a = CameraInfo::builder()
4161            .stamp(Time::new(7, 8))
4162            .frame_id("cam")
4163            .height(480)
4164            .width(640)
4165            .distortion_model("plumb_bob")
4166            .d(&[0.1, 0.2])
4167            .k(k)
4168            .r(r)
4169            .p(p)
4170            .binning_x(2)
4171            .binning_y(3)
4172            .roi(roi)
4173            .build()
4174            .unwrap();
4175        let mut ci_b = CameraInfo::builder()
4176            .stamp(Time::new(0, 0))
4177            .frame_id("cam")
4178            .distortion_model("plumb_bob")
4179            .d(&[0.1, 0.2])
4180            .build()
4181            .unwrap();
4182        ci_b.set_stamp(Time::new(7, 8)).unwrap();
4183        ci_b.set_height(480).unwrap();
4184        ci_b.set_width(640).unwrap();
4185        ci_b.set_k(k).unwrap();
4186        ci_b.set_r(r).unwrap();
4187        ci_b.set_p(p).unwrap();
4188        ci_b.set_binning_x(2).unwrap();
4189        ci_b.set_binning_y(3).unwrap();
4190        ci_b.set_roi(roi).unwrap();
4191        assert_eq!(ci_a.as_cdr(), ci_b.as_cdr(), "CameraInfo byte mismatch");
4192
4193        // MagneticField
4194        let mf_a = MagneticField::builder()
4195            .stamp(Time::new(9, 10))
4196            .frame_id("imu")
4197            .magnetic_field(Vector3 {
4198                x: 1.0,
4199                y: 2.0,
4200                z: 3.0,
4201            })
4202            .magnetic_field_covariance([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0])
4203            .build()
4204            .unwrap();
4205        let mut mf_b = MagneticField::builder()
4206            .stamp(Time::new(0, 0))
4207            .frame_id("imu")
4208            .build()
4209            .unwrap();
4210        mf_b.set_stamp(Time::new(9, 10)).unwrap();
4211        mf_b.set_magnetic_field(Vector3 {
4212            x: 1.0,
4213            y: 2.0,
4214            z: 3.0,
4215        })
4216        .unwrap();
4217        mf_b.set_magnetic_field_covariance([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0])
4218            .unwrap();
4219        assert_eq!(mf_a.as_cdr(), mf_b.as_cdr(), "MagneticField byte mismatch");
4220
4221        // FluidPressure
4222        let fp_a = FluidPressure::builder()
4223            .stamp(Time::new(11, 12))
4224            .frame_id("barometer")
4225            .fluid_pressure(101_325.0)
4226            .variance(0.5)
4227            .build()
4228            .unwrap();
4229        let mut fp_b = FluidPressure::builder()
4230            .stamp(Time::new(0, 0))
4231            .frame_id("barometer")
4232            .build()
4233            .unwrap();
4234        fp_b.set_stamp(Time::new(11, 12)).unwrap();
4235        fp_b.set_fluid_pressure(101_325.0).unwrap();
4236        fp_b.set_variance(0.5).unwrap();
4237        assert_eq!(fp_a.as_cdr(), fp_b.as_cdr(), "FluidPressure byte mismatch");
4238
4239        // Temperature
4240        let t_a = Temperature::builder()
4241            .stamp(Time::new(13, 14))
4242            .frame_id("temp")
4243            .temperature(25.5)
4244            .variance(0.1)
4245            .build()
4246            .unwrap();
4247        let mut t_b = Temperature::builder()
4248            .stamp(Time::new(0, 0))
4249            .frame_id("temp")
4250            .build()
4251            .unwrap();
4252        t_b.set_stamp(Time::new(13, 14)).unwrap();
4253        t_b.set_temperature(25.5).unwrap();
4254        t_b.set_variance(0.1).unwrap();
4255        assert_eq!(t_a.as_cdr(), t_b.as_cdr(), "Temperature byte mismatch");
4256
4257        // BatteryState
4258        let bs_a = BatteryState::builder()
4259            .stamp(Time::new(15, 16))
4260            .frame_id("batt")
4261            .voltage(12.1)
4262            .temperature(25.0)
4263            .current(-1.5)
4264            .charge(0.5)
4265            .capacity(2.0)
4266            .design_capacity(2.5)
4267            .percentage(0.75)
4268            .power_supply_status(1)
4269            .power_supply_health(2)
4270            .power_supply_technology(3)
4271            .present(true)
4272            .build()
4273            .unwrap();
4274        let mut bs_b = BatteryState::builder()
4275            .stamp(Time::new(0, 0))
4276            .frame_id("batt")
4277            .build()
4278            .unwrap();
4279        bs_b.set_stamp(Time::new(15, 16)).unwrap();
4280        bs_b.set_voltage(12.1).unwrap();
4281        bs_b.set_temperature(25.0).unwrap();
4282        bs_b.set_current(-1.5).unwrap();
4283        bs_b.set_charge(0.5).unwrap();
4284        bs_b.set_capacity(2.0).unwrap();
4285        bs_b.set_design_capacity(2.5).unwrap();
4286        bs_b.set_percentage(0.75).unwrap();
4287        bs_b.set_power_supply_status(1).unwrap();
4288        bs_b.set_power_supply_health(2).unwrap();
4289        bs_b.set_power_supply_technology(3).unwrap();
4290        bs_b.set_present(true).unwrap();
4291        assert_eq!(bs_a.as_cdr(), bs_b.as_cdr(), "BatteryState byte mismatch");
4292    }
4293}