Skip to main content

edgefirst_schemas/
edgefirst_msgs.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright © 2025 Au-Zone Technologies. All Rights Reserved.
3
4//! EdgeFirst custom message types for perception pipelines.
5//!
6//! CdrFixed: `Date`
7//!
8//! Buffer-backed: `Mask` (`MaskView`), `DmaBuffer`, `LocalTime`,
9//! `RadarCube`, `RadarInfo`, `Track`, `DetectBox` (`DetectBoxView`),
10//! `Detect`, `Model`, `ModelInfo`
11
12use crate::builtin_interfaces::{Duration, Time};
13use crate::cdr::*;
14use crate::std_msgs::Header;
15
16// ── CdrFixed types ──────────────────────────────────────────────────
17
18#[derive(PartialEq, Clone, Copy, Debug)]
19pub struct Date {
20    pub year: u16,
21    pub month: u8,
22    pub day: u8,
23}
24
25impl CdrFixed for Date {
26    const CDR_SIZE: usize = 4; // u16(2) + u8(1) + u8(1)
27    fn read_cdr(cursor: &mut CdrCursor<'_>) -> Result<Self, CdrError> {
28        Ok(Date {
29            year: cursor.read_u16()?,
30            month: cursor.read_u8()?,
31            day: cursor.read_u8()?,
32        })
33    }
34    fn write_cdr(&self, writer: &mut CdrWriter<'_>) {
35        writer.write_u16(self.year);
36        writer.write_u8(self.month);
37        writer.write_u8(self.day);
38    }
39    fn size_cdr(sizer: &mut CdrSizer) {
40        sizer.size_u16();
41        sizer.size_u8();
42        sizer.size_u8();
43    }
44}
45
46// ── Constants ───────────────────────────────────────────────────────
47
48pub mod radar_cube_dimension {
49    pub const UNDEFINED: u8 = 0;
50    pub const RANGE: u8 = 1;
51    pub const DOPPLER: u8 = 2;
52    pub const AZIMUTH: u8 = 3;
53    pub const ELEVATION: u8 = 4;
54    pub const RXCHANNEL: u8 = 5;
55    pub const SEQUENCE: u8 = 6;
56}
57
58pub mod model_info {
59    pub const RAW: u8 = 0;
60    pub const INT8: u8 = 1;
61    pub const UINT8: u8 = 2;
62    pub const INT16: u8 = 3;
63    pub const UINT16: u8 = 4;
64    pub const FLOAT16: u8 = 5;
65    pub const INT32: u8 = 6;
66    pub const UINT32: u8 = 7;
67    pub const FLOAT32: u8 = 8;
68    pub const INT64: u8 = 9;
69    pub const UINT64: u8 = 10;
70    pub const FLOAT64: u8 = 11;
71    pub const STRING: u8 = 12;
72}
73
74// ── Buffer-backed types ─────────────────────────────────────────────
75
76// ── Mask<B> — edgefirst_msgs/msg/Mask ───────────────────────────────
77//
78// CDR layout:
79//   4: height (u32), width (u32), length (u32)
80//  16: encoding (string) → offsets[0]
81//   ~: mask (byte seq) → offsets[1]
82//   ~: boxed (bool)
83
84pub struct Mask<B> {
85    buf: B,
86    offsets: [usize; 2],
87}
88
89impl<B> Mask<B> {
90    /// Convert the buffer type without re-parsing the offset table.
91    #[inline]
92    pub fn map_buffer<C>(self, f: impl FnOnce(B) -> C) -> Mask<C> {
93        Mask {
94            buf: f(self.buf),
95            offsets: self.offsets,
96        }
97    }
98}
99
100impl<B: AsRef<[u8]>> Mask<B> {
101    pub fn from_cdr(buf: B) -> Result<Self, CdrError> {
102        let mut c = CdrCursor::new(buf.as_ref())?;
103        let _ = c.read_u32()?; // height
104        let _ = c.read_u32()?; // width
105        let _ = c.read_u32()?; // length
106        let _ = c.read_string()?; // encoding
107        let o0 = c.offset();
108        let _ = c.read_bytes()?; // mask
109        let o1 = c.offset();
110        let _ = c.read_bool()?; // boxed
111        Ok(Mask {
112            offsets: [o0, o1],
113            buf,
114        })
115    }
116
117    #[inline]
118    pub fn height(&self) -> u32 {
119        rd_u32(self.buf.as_ref(), CDR_HEADER_SIZE)
120    }
121
122    #[inline]
123    pub fn width(&self) -> u32 {
124        rd_u32(self.buf.as_ref(), CDR_HEADER_SIZE + 4)
125    }
126
127    #[inline]
128    pub fn length(&self) -> u32 {
129        rd_u32(self.buf.as_ref(), CDR_HEADER_SIZE + 8)
130    }
131
132    #[inline]
133    pub fn encoding(&self) -> &str {
134        rd_string(self.buf.as_ref(), CDR_HEADER_SIZE + 12).0
135    }
136
137    #[inline]
138    pub fn mask_data(&self) -> &[u8] {
139        rd_bytes(self.buf.as_ref(), self.offsets[0]).0
140    }
141
142    #[inline]
143    pub fn boxed(&self) -> bool {
144        rd_bool(self.buf.as_ref(), self.offsets[1])
145    }
146
147    #[inline]
148    pub fn as_cdr(&self) -> &[u8] {
149        self.buf.as_ref()
150    }
151
152    #[inline]
153    pub fn cdr_size(&self) -> usize {
154        self.buf.as_ref().len()
155    }
156
157    pub fn to_cdr(&self) -> Vec<u8> {
158        self.buf.as_ref().to_vec()
159    }
160}
161
162impl Mask<Vec<u8>> {
163    #[deprecated(
164        since = "3.2.0",
165        note = "use Mask::builder() for allocation-free buffer reuse; Mask::new will be removed in 4.0"
166    )]
167    pub fn new(
168        height: u32,
169        width: u32,
170        length: u32,
171        encoding: &str,
172        mask: &[u8],
173        boxed: bool,
174    ) -> Result<Self, CdrError> {
175        let mut sizer = CdrSizer::new();
176        sizer.size_u32(); // height
177        sizer.size_u32(); // width
178        sizer.size_u32(); // length
179        sizer.size_string(encoding);
180        let o0 = sizer.offset();
181        sizer.size_bytes(mask.len());
182        let o1 = sizer.offset();
183        sizer.size_bool();
184
185        let mut buf = vec![0u8; sizer.size()];
186        let mut w = CdrWriter::new(&mut buf)?;
187        w.write_u32(height);
188        w.write_u32(width);
189        w.write_u32(length);
190        w.write_string(encoding);
191        w.write_bytes(mask);
192        w.write_bool(boxed);
193        w.finish()?;
194
195        Ok(Mask {
196            offsets: [o0, o1],
197            buf,
198        })
199    }
200
201    pub fn into_cdr(self) -> Vec<u8> {
202        self.buf
203    }
204
205    /// Start a new `MaskBuilder` with zero-valued defaults.
206    pub fn builder<'a>() -> MaskBuilder<'a> {
207        MaskBuilder::new()
208    }
209}
210
211// ── MaskBuilder<'a> ─────────────────────────────────────────────────
212
213/// Builder for `Mask<Vec<u8>>` with buffer-reuse finalizers.
214///
215/// `encoding` is `Cow<'a, str>` so string literals borrow and owned strings
216/// move in; `mask` borrows from caller memory. All borrows must remain valid
217/// until `build()`, `encode_into_vec()`, or `encode_into_slice()` is called.
218pub struct MaskBuilder<'a> {
219    height: u32,
220    width: u32,
221    length: u32,
222    encoding: std::borrow::Cow<'a, str>,
223    mask: &'a [u8],
224    boxed: bool,
225}
226
227impl<'a> Default for MaskBuilder<'a> {
228    fn default() -> Self {
229        Self {
230            height: 0,
231            width: 0,
232            length: 0,
233            encoding: std::borrow::Cow::Borrowed(""),
234            mask: &[],
235            boxed: false,
236        }
237    }
238}
239
240impl<'a> MaskBuilder<'a> {
241    pub fn new() -> Self {
242        Self::default()
243    }
244
245    pub fn height(&mut self, v: u32) -> &mut Self {
246        self.height = v;
247        self
248    }
249    pub fn width(&mut self, v: u32) -> &mut Self {
250        self.width = v;
251        self
252    }
253    pub fn length(&mut self, v: u32) -> &mut Self {
254        self.length = v;
255        self
256    }
257    pub fn encoding(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
258        self.encoding = s.into();
259        self
260    }
261    pub fn mask(&mut self, m: &'a [u8]) -> &mut Self {
262        self.mask = m;
263        self
264    }
265    pub fn boxed(&mut self, v: bool) -> &mut Self {
266        self.boxed = v;
267        self
268    }
269
270    fn size(&self) -> usize {
271        let mut s = CdrSizer::new();
272        s.size_u32(); // height
273        s.size_u32(); // width
274        s.size_u32(); // length
275        s.size_string(&self.encoding);
276        s.size_bytes(self.mask.len());
277        s.size_bool();
278        s.size()
279    }
280
281    fn write_into(&self, buf: &mut [u8]) -> Result<(), CdrError> {
282        let mut w = CdrWriter::new(buf)?;
283        w.write_u32(self.height);
284        w.write_u32(self.width);
285        w.write_u32(self.length);
286        w.write_string(&self.encoding);
287        w.write_bytes(self.mask);
288        w.write_bool(self.boxed);
289        w.finish()
290    }
291
292    pub fn build(&self) -> Result<Mask<Vec<u8>>, CdrError> {
293        let mut buf = vec![0u8; self.size()];
294        self.write_into(&mut buf)?;
295        Mask::from_cdr(buf)
296    }
297
298    pub fn encode_into_vec(&self, buf: &mut Vec<u8>) -> Result<(), CdrError> {
299        buf.resize(self.size(), 0);
300        self.write_into(buf)
301    }
302
303    pub fn encode_into_slice(&self, buf: &mut [u8]) -> Result<usize, CdrError> {
304        let need = self.size();
305        if buf.len() < need {
306            return Err(CdrError::BufferTooShort {
307                need,
308                have: buf.len(),
309            });
310        }
311        self.write_into(&mut buf[..need])?;
312        Ok(need)
313    }
314}
315
316impl Mask<&'static [u8]> {
317    /// Parse a standalone Mask CDR buffer and return a `'static`-lifetimed view.
318    ///
319    /// This is an FFI-oriented helper. Unlike the field-by-field copy path via
320    /// `Mask::from_cdr(...)` followed by accessor methods, the returned
321    /// `MaskView<'static>` borrows directly from the input buffer with the
322    /// buffer's native `'static` lifetime — no `mem::transmute` is required at
323    /// the FFI layer to widen method-returned references whose lifetimes are
324    /// tied to a temporary `&self`.
325    ///
326    /// The parse is a single pass: `scan_mask_element` consumes the fields
327    /// and produces the view directly, so there is no double-validation cost.
328    pub(crate) fn from_cdr_as_view(buf: &'static [u8]) -> Result<MaskView<'static>, CdrError> {
329        let mut c = CdrCursor::new(buf)?;
330        scan_mask_element(&mut c)
331    }
332}
333
334impl<B: AsRef<[u8]> + AsMut<[u8]>> Mask<B> {
335    pub fn set_height(&mut self, h: u32) -> Result<(), CdrError> {
336        wr_u32(self.buf.as_mut(), CDR_HEADER_SIZE, h)
337    }
338
339    pub fn set_width(&mut self, w: u32) -> Result<(), CdrError> {
340        wr_u32(self.buf.as_mut(), CDR_HEADER_SIZE + 4, w)
341    }
342
343    pub fn set_length(&mut self, l: u32) -> Result<(), CdrError> {
344        wr_u32(self.buf.as_mut(), CDR_HEADER_SIZE + 8, l)
345    }
346
347    pub fn set_boxed(&mut self, v: bool) -> Result<(), CdrError> {
348        wr_u8(self.buf.as_mut(), self.offsets[1], v as u8)
349    }
350}
351
352/// Zero-copy view of a Mask element within a CDR sequence.
353#[derive(Copy, Clone, Debug)]
354pub struct MaskView<'a> {
355    pub height: u32,
356    pub width: u32,
357    pub length: u32,
358    pub encoding: &'a str,
359    pub mask: &'a [u8],
360    pub boxed: bool,
361}
362
363pub(crate) fn scan_mask_element<'a>(c: &mut CdrCursor<'a>) -> Result<MaskView<'a>, CdrError> {
364    let height = c.read_u32()?;
365    let width = c.read_u32()?;
366    let length = c.read_u32()?;
367    let encoding = c.read_string()?;
368    let mask = c.read_bytes()?;
369    let boxed = c.read_bool()?;
370    Ok(MaskView {
371        height,
372        width,
373        length,
374        encoding,
375        mask,
376        boxed,
377    })
378}
379
380pub(crate) fn write_mask_element(w: &mut CdrWriter<'_>, m: &MaskView<'_>) {
381    w.write_u32(m.height);
382    w.write_u32(m.width);
383    w.write_u32(m.length);
384    w.write_string(m.encoding);
385    w.write_bytes(m.mask);
386    w.write_bool(m.boxed);
387}
388
389pub(crate) fn size_mask_element(s: &mut CdrSizer, encoding: &str, mask_len: usize) {
390    s.size_u32();
391    s.size_u32();
392    s.size_u32();
393    s.size_string(encoding);
394    s.size_bytes(mask_len);
395    s.size_bool();
396}
397
398// ── DmaBuffer<B> — edgefirst_msgs/msg/DmaBuffer ────────────────────
399//
400// CDR layout: Header → offsets[0], then:
401//   pid(u32) + fd(i32) + width(u32) + height(u32)
402//   + stride(u32) + fourcc(u32) + length(u32) = 28 bytes
403//
404// DEPRECATED since 3.1.0: use CameraFrame instead. Will be removed in 4.0.0.
405
406#[deprecated(
407    since = "3.1.0",
408    note = "Use CameraFrame / CameraPlane for multi-plane support, colorimetry, \
409            GPU fences, and off-device bridging. DmaBuffer will be removed in 4.0.0."
410)]
411pub struct DmaBuffer<B> {
412    buf: B,
413    offsets: [usize; 1],
414}
415
416#[allow(deprecated)]
417impl<B> DmaBuffer<B> {
418    /// Convert the buffer type without re-parsing the offset table.
419    #[inline]
420    pub fn map_buffer<C>(self, f: impl FnOnce(B) -> C) -> DmaBuffer<C> {
421        DmaBuffer {
422            buf: f(self.buf),
423            offsets: self.offsets,
424        }
425    }
426}
427
428// The DmaBuffer impls remain until 4.0.0; allow(deprecated) here so the
429// crate's own use of the deprecated struct (fields, methods) compiles
430// cleanly. User code still gets the deprecation warning.
431#[allow(deprecated)]
432impl<B: AsRef<[u8]>> DmaBuffer<B> {
433    pub fn from_cdr(buf: B) -> Result<Self, CdrError> {
434        let header = Header::<&[u8]>::from_cdr(buf.as_ref())?;
435        let o0 = header.end_offset();
436        let mut c = CdrCursor::resume(buf.as_ref(), o0);
437        for _ in 0..7 {
438            c.read_u32()?;
439        }
440        Ok(DmaBuffer { offsets: [o0], buf })
441    }
442
443    /// Returns a `Header` view (re-parses CDR prefix; prefer `stamp()`/`frame_id()`).
444    pub fn header(&self) -> Header<&[u8]> {
445        Header::from_cdr(self.buf.as_ref()).expect("header bytes validated during from_cdr")
446    }
447
448    #[inline]
449    pub fn stamp(&self) -> Time {
450        rd_time(self.buf.as_ref(), CDR_HEADER_SIZE)
451    }
452
453    #[inline]
454    pub fn frame_id(&self) -> &str {
455        rd_string(self.buf.as_ref(), CDR_HEADER_SIZE + 8).0
456    }
457
458    #[inline]
459    pub fn pid(&self) -> u32 {
460        let p = align(self.offsets[0], 4);
461        rd_u32(self.buf.as_ref(), p)
462    }
463
464    #[inline]
465    pub fn fd(&self) -> i32 {
466        let p = align(self.offsets[0], 4) + 4;
467        rd_i32(self.buf.as_ref(), p)
468    }
469
470    #[inline]
471    pub fn width(&self) -> u32 {
472        let p = align(self.offsets[0], 4) + 8;
473        rd_u32(self.buf.as_ref(), p)
474    }
475
476    #[inline]
477    pub fn height(&self) -> u32 {
478        let p = align(self.offsets[0], 4) + 12;
479        rd_u32(self.buf.as_ref(), p)
480    }
481
482    #[inline]
483    pub fn stride(&self) -> u32 {
484        let p = align(self.offsets[0], 4) + 16;
485        rd_u32(self.buf.as_ref(), p)
486    }
487
488    #[inline]
489    pub fn fourcc(&self) -> u32 {
490        let p = align(self.offsets[0], 4) + 20;
491        rd_u32(self.buf.as_ref(), p)
492    }
493
494    #[inline]
495    pub fn length(&self) -> u32 {
496        let p = align(self.offsets[0], 4) + 24;
497        rd_u32(self.buf.as_ref(), p)
498    }
499
500    #[inline]
501    pub fn as_cdr(&self) -> &[u8] {
502        self.buf.as_ref()
503    }
504
505    pub fn to_cdr(&self) -> Vec<u8> {
506        self.buf.as_ref().to_vec()
507    }
508}
509
510#[allow(deprecated)]
511impl DmaBuffer<Vec<u8>> {
512    pub fn new(
513        stamp: Time,
514        frame_id: &str,
515        pid: u32,
516        fd: i32,
517        width: u32,
518        height: u32,
519        stride: u32,
520        fourcc: u32,
521        length: u32,
522    ) -> Result<Self, CdrError> {
523        let mut sizer = CdrSizer::new();
524        Time::size_cdr(&mut sizer);
525        sizer.size_string(frame_id);
526        let o0 = sizer.offset();
527        for _ in 0..7 {
528            sizer.size_u32();
529        }
530
531        let mut buf = vec![0u8; sizer.size()];
532        let mut w = CdrWriter::new(&mut buf)?;
533        stamp.write_cdr(&mut w);
534        w.write_string(frame_id);
535        w.write_u32(pid);
536        w.write_i32(fd);
537        w.write_u32(width);
538        w.write_u32(height);
539        w.write_u32(stride);
540        w.write_u32(fourcc);
541        w.write_u32(length);
542        w.finish()?;
543
544        Ok(DmaBuffer { offsets: [o0], buf })
545    }
546
547    pub fn into_cdr(self) -> Vec<u8> {
548        self.buf
549    }
550}
551
552// ── LocalTime<B> — edgefirst_msgs/msg/LocalTime ────────────────────
553//
554// CDR layout: Header → offsets[0], then:
555//   Date(4) + pad(2) + Time(8) + timezone(i16) = fixed payload
556
557pub struct LocalTime<B> {
558    buf: B,
559    offsets: [usize; 1],
560}
561
562impl<B> LocalTime<B> {
563    /// Convert the buffer type without re-parsing the offset table.
564    #[inline]
565    pub fn map_buffer<C>(self, f: impl FnOnce(B) -> C) -> LocalTime<C> {
566        LocalTime {
567            buf: f(self.buf),
568            offsets: self.offsets,
569        }
570    }
571}
572
573impl<B: AsRef<[u8]>> LocalTime<B> {
574    pub fn from_cdr(buf: B) -> Result<Self, CdrError> {
575        let header = Header::<&[u8]>::from_cdr(buf.as_ref())?;
576        let o0 = header.end_offset();
577        let mut c = CdrCursor::resume(buf.as_ref(), o0);
578        Date::read_cdr(&mut c)?;
579        Time::read_cdr(&mut c)?;
580        c.read_i16()?; // timezone
581        Ok(LocalTime { offsets: [o0], buf })
582    }
583
584    /// Returns a `Header` view (re-parses CDR prefix; prefer `stamp()`/`frame_id()`).
585    pub fn header(&self) -> Header<&[u8]> {
586        Header::from_cdr(self.buf.as_ref()).expect("header bytes validated during from_cdr")
587    }
588
589    #[inline]
590    pub fn stamp(&self) -> Time {
591        rd_time(self.buf.as_ref(), CDR_HEADER_SIZE)
592    }
593
594    #[inline]
595    pub fn frame_id(&self) -> &str {
596        rd_string(self.buf.as_ref(), CDR_HEADER_SIZE + 8).0
597    }
598
599    pub fn date(&self) -> Date {
600        let mut c = CdrCursor::resume(self.buf.as_ref(), self.offsets[0]);
601        Date::read_cdr(&mut c).expect("date field validated during from_cdr")
602    }
603
604    pub fn time(&self) -> Time {
605        let mut c = CdrCursor::resume(self.buf.as_ref(), self.offsets[0]);
606        Date::read_cdr(&mut c).expect("date field validated during from_cdr");
607        Time::read_cdr(&mut c).expect("time field validated during from_cdr")
608    }
609
610    pub fn timezone(&self) -> i16 {
611        let mut c = CdrCursor::resume(self.buf.as_ref(), self.offsets[0]);
612        Date::read_cdr(&mut c).expect("date field validated during from_cdr");
613        Time::read_cdr(&mut c).expect("time field validated during from_cdr");
614        c.read_i16()
615            .expect("timezone field validated during from_cdr")
616    }
617
618    #[inline]
619    pub fn as_cdr(&self) -> &[u8] {
620        self.buf.as_ref()
621    }
622
623    pub fn to_cdr(&self) -> Vec<u8> {
624        self.buf.as_ref().to_vec()
625    }
626}
627
628impl LocalTime<Vec<u8>> {
629    #[deprecated(
630        since = "3.2.0",
631        note = "use LocalTime::builder() for allocation-free buffer reuse; LocalTime::new will be removed in 4.0"
632    )]
633    pub fn new(
634        stamp: Time,
635        frame_id: &str,
636        date: Date,
637        time: Time,
638        timezone: i16,
639    ) -> Result<Self, CdrError> {
640        let mut sizer = CdrSizer::new();
641        Time::size_cdr(&mut sizer);
642        sizer.size_string(frame_id);
643        let o0 = sizer.offset();
644        Date::size_cdr(&mut sizer);
645        Time::size_cdr(&mut sizer);
646        sizer.size_i16();
647
648        let mut buf = vec![0u8; sizer.size()];
649        let mut w = CdrWriter::new(&mut buf)?;
650        stamp.write_cdr(&mut w);
651        w.write_string(frame_id);
652        date.write_cdr(&mut w);
653        time.write_cdr(&mut w);
654        w.write_i16(timezone);
655        w.finish()?;
656
657        Ok(LocalTime { offsets: [o0], buf })
658    }
659
660    pub fn into_cdr(self) -> Vec<u8> {
661        self.buf
662    }
663
664    /// Start a new `LocalTimeBuilder` with zero-valued defaults.
665    pub fn builder<'a>() -> LocalTimeBuilder<'a> {
666        LocalTimeBuilder::new()
667    }
668}
669
670// ── LocalTimeBuilder<'a> ────────────────────────────────────────────
671
672/// Builder for `LocalTime<Vec<u8>>` with buffer-reuse finalizers.
673pub struct LocalTimeBuilder<'a> {
674    stamp: Time,
675    frame_id: std::borrow::Cow<'a, str>,
676    date: Date,
677    time: Time,
678    timezone: i16,
679}
680
681impl<'a> Default for LocalTimeBuilder<'a> {
682    fn default() -> Self {
683        Self {
684            stamp: Time { sec: 0, nanosec: 0 },
685            frame_id: std::borrow::Cow::Borrowed(""),
686            date: Date {
687                year: 0,
688                month: 0,
689                day: 0,
690            },
691            time: Time { sec: 0, nanosec: 0 },
692            timezone: 0,
693        }
694    }
695}
696
697impl<'a> LocalTimeBuilder<'a> {
698    pub fn new() -> Self {
699        Self::default()
700    }
701
702    pub fn stamp(&mut self, t: Time) -> &mut Self {
703        self.stamp = t;
704        self
705    }
706    pub fn frame_id(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
707        self.frame_id = s.into();
708        self
709    }
710    pub fn date(&mut self, d: Date) -> &mut Self {
711        self.date = d;
712        self
713    }
714    pub fn time(&mut self, t: Time) -> &mut Self {
715        self.time = t;
716        self
717    }
718    pub fn timezone(&mut self, v: i16) -> &mut Self {
719        self.timezone = v;
720        self
721    }
722
723    fn size(&self) -> usize {
724        let mut s = CdrSizer::new();
725        Time::size_cdr(&mut s);
726        s.size_string(&self.frame_id);
727        Date::size_cdr(&mut s);
728        Time::size_cdr(&mut s);
729        s.size_i16();
730        s.size()
731    }
732
733    fn write_into(&self, buf: &mut [u8]) -> Result<(), CdrError> {
734        let mut w = CdrWriter::new(buf)?;
735        self.stamp.write_cdr(&mut w);
736        w.write_string(&self.frame_id);
737        self.date.write_cdr(&mut w);
738        self.time.write_cdr(&mut w);
739        w.write_i16(self.timezone);
740        w.finish()
741    }
742
743    pub fn build(&self) -> Result<LocalTime<Vec<u8>>, CdrError> {
744        let mut buf = vec![0u8; self.size()];
745        self.write_into(&mut buf)?;
746        LocalTime::from_cdr(buf)
747    }
748
749    pub fn encode_into_vec(&self, buf: &mut Vec<u8>) -> Result<(), CdrError> {
750        buf.resize(self.size(), 0);
751        self.write_into(buf)
752    }
753
754    pub fn encode_into_slice(&self, buf: &mut [u8]) -> Result<usize, CdrError> {
755        let need = self.size();
756        if buf.len() < need {
757            return Err(CdrError::BufferTooShort {
758                need,
759                have: buf.len(),
760            });
761        }
762        self.write_into(&mut buf[..need])?;
763        Ok(need)
764    }
765}
766
767impl<B: AsRef<[u8]> + AsMut<[u8]>> LocalTime<B> {
768    pub fn set_stamp(&mut self, t: Time) -> Result<(), CdrError> {
769        let b = self.buf.as_mut();
770        wr_i32(b, CDR_HEADER_SIZE, t.sec)?;
771        wr_u32(b, CDR_HEADER_SIZE + 4, t.nanosec)
772    }
773
774    pub fn set_date(&mut self, d: Date) -> Result<(), CdrError> {
775        let b = self.buf.as_mut();
776        // Date starts with u16 year — align(2) from offsets[0].
777        let p = cdr_align(self.offsets[0], 2);
778        wr_u16(b, p, d.year)?;
779        wr_u8(b, p + 2, d.month)?;
780        wr_u8(b, p + 3, d.day)
781    }
782
783    pub fn set_time(&mut self, t: Time) -> Result<(), CdrError> {
784        let b = self.buf.as_mut();
785        // Date is 4 bytes at 2-aligned position; Time starts with i32
786        // and needs 4-alignment — this may require padding after Date.
787        let date_start = cdr_align(self.offsets[0], 2);
788        let time_start = cdr_align(date_start + 4, 4);
789        wr_i32(b, time_start, t.sec)?;
790        wr_u32(b, time_start + 4, t.nanosec)
791    }
792
793    pub fn set_timezone(&mut self, v: i16) -> Result<(), CdrError> {
794        // timezone is i16 after Time (8 bytes, 4-aligned) — needs 2-align.
795        let date_start = cdr_align(self.offsets[0], 2);
796        let time_start = cdr_align(date_start + 4, 4);
797        let tz_pos = cdr_align(time_start + 8, 2);
798        wr_i16(self.buf.as_mut(), tz_pos, v)
799    }
800}
801
802// ── RadarCube<B> — edgefirst_msgs/msg/RadarCube ─────────────────────
803//
804// CDR layout: Header → offsets[0],
805//   timestamp(u64), layout(Vec<u8>) → offsets[1],
806//   shape(Vec<u16>) → offsets[2], scales(Vec<f32>) → offsets[3],
807//   cube(Vec<i16>) → offsets[4], is_complex(bool)
808
809pub struct RadarCube<B> {
810    buf: B,
811    offsets: [usize; 5],
812}
813
814impl<B> RadarCube<B> {
815    /// Convert the buffer type without re-parsing the offset table.
816    #[inline]
817    pub fn map_buffer<C>(self, f: impl FnOnce(B) -> C) -> RadarCube<C> {
818        RadarCube {
819            buf: f(self.buf),
820            offsets: self.offsets,
821        }
822    }
823}
824
825impl<B: AsRef<[u8]>> RadarCube<B> {
826    pub fn from_cdr(buf: B) -> Result<Self, CdrError> {
827        let header = Header::<&[u8]>::from_cdr(buf.as_ref())?;
828        let o0 = header.end_offset();
829        let mut c = CdrCursor::resume(buf.as_ref(), o0);
830        c.read_u64()?; // timestamp
831        let layout_count = c.read_u32()? as usize;
832        c.skip(layout_count)?;
833        let o1 = c.offset();
834        let shape_count = c.read_u32()? as usize;
835        c.skip_seq_2(shape_count)?;
836        let o2 = c.offset();
837        let scales_count = c.read_u32()? as usize;
838        c.skip_seq_4(scales_count)?;
839        let o3 = c.offset();
840        let cube_count = c.read_u32()? as usize;
841        c.skip_seq_2(cube_count)?;
842        let o4 = c.offset();
843        c.read_bool()?;
844        Ok(RadarCube {
845            offsets: [o0, o1, o2, o3, o4],
846            buf,
847        })
848    }
849
850    #[inline]
851    /// Returns a `Header` view by re-parsing the CDR buffer prefix.
852    /// Prefer `stamp()` / `frame_id()` for direct O(1) field access.
853    pub fn header(&self) -> Header<&[u8]> {
854        Header::from_cdr(self.buf.as_ref()).expect("header bytes validated during from_cdr")
855    }
856    #[inline]
857    pub fn stamp(&self) -> Time {
858        rd_time(self.buf.as_ref(), CDR_HEADER_SIZE)
859    }
860    #[inline]
861    pub fn frame_id(&self) -> &str {
862        rd_string(self.buf.as_ref(), CDR_HEADER_SIZE + 8).0
863    }
864
865    pub fn timestamp(&self) -> u64 {
866        let p = cdr_align(self.offsets[0], 8);
867        rd_u64(self.buf.as_ref(), p)
868    }
869
870    pub fn layout(&self) -> &[u8] {
871        let b = self.buf.as_ref();
872        let p = align(cdr_align(self.offsets[0], 8) + 8, 4);
873        let count = rd_u32(b, p) as usize;
874        &b[p + 4..p + 4 + count]
875    }
876
877    pub fn shape(&self) -> &[u16] {
878        let b = self.buf.as_ref();
879        let p = align(self.offsets[1], 4);
880        let count = rd_u32(b, p) as usize;
881        rd_slice_u16(b, align(p + 4, 2), count)
882    }
883
884    pub fn scales(&self) -> &[f32] {
885        let b = self.buf.as_ref();
886        let p = align(self.offsets[2], 4);
887        let count = rd_u32(b, p) as usize;
888        rd_slice_f32(b, align(p + 4, 4), count)
889    }
890
891    /// Zero-copy view of the radar cube data as `&[i16]`.
892    pub fn cube(&self) -> &[i16] {
893        let b = self.buf.as_ref();
894        let p = align(self.offsets[3], 4);
895        let count = rd_u32(b, p) as usize;
896        rd_slice_i16(b, align(p + 4, 2), count)
897    }
898
899    pub fn cube_raw(&self) -> &[u8] {
900        let b = self.buf.as_ref();
901        let p = align(self.offsets[3], 4);
902        let count = rd_u32(b, p) as usize;
903        &b[p + 4..p + 4 + count * 2]
904    }
905
906    pub fn cube_len(&self) -> u32 {
907        rd_u32(self.buf.as_ref(), align(self.offsets[3], 4))
908    }
909
910    pub fn is_complex(&self) -> bool {
911        rd_bool(self.buf.as_ref(), self.offsets[4])
912    }
913
914    #[inline]
915    pub fn as_cdr(&self) -> &[u8] {
916        self.buf.as_ref()
917    }
918    pub fn to_cdr(&self) -> Vec<u8> {
919        self.buf.as_ref().to_vec()
920    }
921}
922
923impl RadarCube<Vec<u8>> {
924    #[deprecated(
925        since = "3.2.0",
926        note = "use RadarCube::builder() for allocation-free buffer reuse; RadarCube::new will be removed in 4.0"
927    )]
928    #[allow(clippy::too_many_arguments)]
929    pub fn new(
930        stamp: Time,
931        frame_id: &str,
932        timestamp: u64,
933        layout: &[u8],
934        shape: &[u16],
935        scales: &[f32],
936        cube: &[i16],
937        is_complex: bool,
938    ) -> Result<Self, CdrError> {
939        let mut sizer = CdrSizer::new();
940        Time::size_cdr(&mut sizer);
941        sizer.size_string(frame_id);
942        let o0 = sizer.offset();
943        sizer.size_u64();
944        sizer.size_bytes(layout.len());
945        let o1 = sizer.offset();
946        sizer.size_u32();
947        sizer.size_seq_2(shape.len());
948        let o2 = sizer.offset();
949        sizer.size_u32();
950        sizer.size_seq_4(scales.len());
951        let o3 = sizer.offset();
952        sizer.size_u32();
953        sizer.size_seq_2(cube.len());
954        let o4 = sizer.offset();
955        sizer.size_bool();
956
957        let mut buf = vec![0u8; sizer.size()];
958        let mut w = CdrWriter::new(&mut buf)?;
959        stamp.write_cdr(&mut w);
960        w.write_string(frame_id);
961        w.write_u64(timestamp);
962        w.write_bytes(layout);
963        w.write_u32(shape.len() as u32);
964        w.write_slice_u16(shape);
965        w.write_u32(scales.len() as u32);
966        w.write_slice_f32(scales);
967        w.write_u32(cube.len() as u32);
968        w.write_slice_i16(cube);
969        w.write_bool(is_complex);
970        w.finish()?;
971
972        Ok(RadarCube {
973            offsets: [o0, o1, o2, o3, o4],
974            buf,
975        })
976    }
977
978    pub fn into_cdr(self) -> Vec<u8> {
979        self.buf
980    }
981
982    /// Start a new `RadarCubeBuilder` with zero-valued defaults.
983    pub fn builder<'a>() -> RadarCubeBuilder<'a> {
984        RadarCubeBuilder::new()
985    }
986}
987
988// ── RadarCubeBuilder<'a> ────────────────────────────────────────────
989
990/// Builder for `RadarCube<Vec<u8>>` with buffer-reuse finalizers.
991///
992/// The variable-length arrays (`layout`, `shape`, `scales`, `cube`) are
993/// borrowed from caller memory. All borrows must remain valid until
994/// `build()`, `encode_into_vec()`, or `encode_into_slice()` is called.
995pub struct RadarCubeBuilder<'a> {
996    stamp: Time,
997    frame_id: std::borrow::Cow<'a, str>,
998    timestamp: u64,
999    layout: &'a [u8],
1000    shape: &'a [u16],
1001    scales: &'a [f32],
1002    cube: &'a [i16],
1003    is_complex: bool,
1004}
1005
1006impl<'a> Default for RadarCubeBuilder<'a> {
1007    fn default() -> Self {
1008        Self {
1009            stamp: Time { sec: 0, nanosec: 0 },
1010            frame_id: std::borrow::Cow::Borrowed(""),
1011            timestamp: 0,
1012            layout: &[],
1013            shape: &[],
1014            scales: &[],
1015            cube: &[],
1016            is_complex: false,
1017        }
1018    }
1019}
1020
1021impl<'a> RadarCubeBuilder<'a> {
1022    pub fn new() -> Self {
1023        Self::default()
1024    }
1025
1026    pub fn stamp(&mut self, t: Time) -> &mut Self {
1027        self.stamp = t;
1028        self
1029    }
1030    pub fn frame_id(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
1031        self.frame_id = s.into();
1032        self
1033    }
1034    pub fn timestamp(&mut self, v: u64) -> &mut Self {
1035        self.timestamp = v;
1036        self
1037    }
1038    pub fn layout(&mut self, v: &'a [u8]) -> &mut Self {
1039        self.layout = v;
1040        self
1041    }
1042    pub fn shape(&mut self, v: &'a [u16]) -> &mut Self {
1043        self.shape = v;
1044        self
1045    }
1046    pub fn scales(&mut self, v: &'a [f32]) -> &mut Self {
1047        self.scales = v;
1048        self
1049    }
1050    pub fn cube(&mut self, v: &'a [i16]) -> &mut Self {
1051        self.cube = v;
1052        self
1053    }
1054    pub fn is_complex(&mut self, v: bool) -> &mut Self {
1055        self.is_complex = v;
1056        self
1057    }
1058
1059    fn size(&self) -> usize {
1060        let mut s = CdrSizer::new();
1061        Time::size_cdr(&mut s);
1062        s.size_string(&self.frame_id);
1063        s.size_u64();
1064        s.size_bytes(self.layout.len());
1065        s.size_u32();
1066        s.size_seq_2(self.shape.len());
1067        s.size_u32();
1068        s.size_seq_4(self.scales.len());
1069        s.size_u32();
1070        s.size_seq_2(self.cube.len());
1071        s.size_bool();
1072        s.size()
1073    }
1074
1075    fn write_into(&self, buf: &mut [u8]) -> Result<(), CdrError> {
1076        let mut w = CdrWriter::new(buf)?;
1077        self.stamp.write_cdr(&mut w);
1078        w.write_string(&self.frame_id);
1079        w.write_u64(self.timestamp);
1080        w.write_bytes(self.layout);
1081        w.write_u32(self.shape.len() as u32);
1082        w.write_slice_u16(self.shape);
1083        w.write_u32(self.scales.len() as u32);
1084        w.write_slice_f32(self.scales);
1085        w.write_u32(self.cube.len() as u32);
1086        w.write_slice_i16(self.cube);
1087        w.write_bool(self.is_complex);
1088        w.finish()
1089    }
1090
1091    pub fn build(&self) -> Result<RadarCube<Vec<u8>>, CdrError> {
1092        let mut buf = vec![0u8; self.size()];
1093        self.write_into(&mut buf)?;
1094        RadarCube::from_cdr(buf)
1095    }
1096
1097    pub fn encode_into_vec(&self, buf: &mut Vec<u8>) -> Result<(), CdrError> {
1098        buf.resize(self.size(), 0);
1099        self.write_into(buf)
1100    }
1101
1102    pub fn encode_into_slice(&self, buf: &mut [u8]) -> Result<usize, CdrError> {
1103        let need = self.size();
1104        if buf.len() < need {
1105            return Err(CdrError::BufferTooShort {
1106                need,
1107                have: buf.len(),
1108            });
1109        }
1110        self.write_into(&mut buf[..need])?;
1111        Ok(need)
1112    }
1113}
1114
1115impl<B: AsRef<[u8]> + AsMut<[u8]>> RadarCube<B> {
1116    pub fn set_stamp(&mut self, t: Time) -> Result<(), CdrError> {
1117        let b = self.buf.as_mut();
1118        wr_i32(b, CDR_HEADER_SIZE, t.sec)?;
1119        wr_u32(b, CDR_HEADER_SIZE + 4, t.nanosec)
1120    }
1121
1122    pub fn set_timestamp(&mut self, v: u64) -> Result<(), CdrError> {
1123        let p = cdr_align(self.offsets[0], 8);
1124        wr_u64(self.buf.as_mut(), p, v)
1125    }
1126
1127    pub fn set_is_complex(&mut self, v: bool) -> Result<(), CdrError> {
1128        wr_bool(self.buf.as_mut(), self.offsets[4], v)
1129    }
1130}
1131
1132// ── RadarInfo<B> — edgefirst_msgs/msg/RadarInfo ─────────────────────
1133//
1134// CDR layout: Header → offsets[0],
1135//   center_frequency → offsets[1], frequency_sweep → offsets[2],
1136//   range_toggle → offsets[3], detection_sensitivity → offsets[4],
1137//   cube(bool)
1138
1139pub struct RadarInfo<B> {
1140    buf: B,
1141    offsets: [usize; 5],
1142}
1143
1144impl<B> RadarInfo<B> {
1145    /// Convert the buffer type without re-parsing the offset table.
1146    #[inline]
1147    pub fn map_buffer<C>(self, f: impl FnOnce(B) -> C) -> RadarInfo<C> {
1148        RadarInfo {
1149            buf: f(self.buf),
1150            offsets: self.offsets,
1151        }
1152    }
1153}
1154
1155impl<B: AsRef<[u8]>> RadarInfo<B> {
1156    pub fn from_cdr(buf: B) -> Result<Self, CdrError> {
1157        let header = Header::<&[u8]>::from_cdr(buf.as_ref())?;
1158        let o0 = header.end_offset();
1159        let mut c = CdrCursor::resume(buf.as_ref(), o0);
1160        let _ = c.read_string()?;
1161        let o1 = c.offset();
1162        let _ = c.read_string()?;
1163        let o2 = c.offset();
1164        let _ = c.read_string()?;
1165        let o3 = c.offset();
1166        let _ = c.read_string()?;
1167        let o4 = c.offset();
1168        c.read_bool()?;
1169        Ok(RadarInfo {
1170            offsets: [o0, o1, o2, o3, o4],
1171            buf,
1172        })
1173    }
1174
1175    #[inline]
1176    /// Returns a `Header` view by re-parsing the CDR buffer prefix.
1177    /// Prefer `stamp()` / `frame_id()` for direct O(1) field access.
1178    pub fn header(&self) -> Header<&[u8]> {
1179        Header::from_cdr(self.buf.as_ref()).expect("header bytes validated during from_cdr")
1180    }
1181    #[inline]
1182    pub fn stamp(&self) -> Time {
1183        rd_time(self.buf.as_ref(), CDR_HEADER_SIZE)
1184    }
1185    #[inline]
1186    pub fn frame_id(&self) -> &str {
1187        rd_string(self.buf.as_ref(), CDR_HEADER_SIZE + 8).0
1188    }
1189    #[inline]
1190    pub fn center_frequency(&self) -> &str {
1191        rd_string(self.buf.as_ref(), self.offsets[0]).0
1192    }
1193    #[inline]
1194    pub fn frequency_sweep(&self) -> &str {
1195        rd_string(self.buf.as_ref(), self.offsets[1]).0
1196    }
1197    #[inline]
1198    pub fn range_toggle(&self) -> &str {
1199        rd_string(self.buf.as_ref(), self.offsets[2]).0
1200    }
1201    #[inline]
1202    pub fn detection_sensitivity(&self) -> &str {
1203        rd_string(self.buf.as_ref(), self.offsets[3]).0
1204    }
1205    #[inline]
1206    pub fn cube(&self) -> bool {
1207        rd_bool(self.buf.as_ref(), self.offsets[4])
1208    }
1209
1210    #[inline]
1211    pub fn as_cdr(&self) -> &[u8] {
1212        self.buf.as_ref()
1213    }
1214    pub fn to_cdr(&self) -> Vec<u8> {
1215        self.buf.as_ref().to_vec()
1216    }
1217}
1218
1219impl RadarInfo<Vec<u8>> {
1220    #[deprecated(
1221        since = "3.2.0",
1222        note = "use RadarInfo::builder() for allocation-free buffer reuse; RadarInfo::new will be removed in 4.0"
1223    )]
1224    pub fn new(
1225        stamp: Time,
1226        frame_id: &str,
1227        center_frequency: &str,
1228        frequency_sweep: &str,
1229        range_toggle: &str,
1230        detection_sensitivity: &str,
1231        cube: bool,
1232    ) -> Result<Self, CdrError> {
1233        let mut sizer = CdrSizer::new();
1234        Time::size_cdr(&mut sizer);
1235        sizer.size_string(frame_id);
1236        let o0 = sizer.offset();
1237        sizer.size_string(center_frequency);
1238        let o1 = sizer.offset();
1239        sizer.size_string(frequency_sweep);
1240        let o2 = sizer.offset();
1241        sizer.size_string(range_toggle);
1242        let o3 = sizer.offset();
1243        sizer.size_string(detection_sensitivity);
1244        let o4 = sizer.offset();
1245        sizer.size_bool();
1246
1247        let mut buf = vec![0u8; sizer.size()];
1248        let mut w = CdrWriter::new(&mut buf)?;
1249        stamp.write_cdr(&mut w);
1250        w.write_string(frame_id);
1251        w.write_string(center_frequency);
1252        w.write_string(frequency_sweep);
1253        w.write_string(range_toggle);
1254        w.write_string(detection_sensitivity);
1255        w.write_bool(cube);
1256        w.finish()?;
1257
1258        Ok(RadarInfo {
1259            offsets: [o0, o1, o2, o3, o4],
1260            buf,
1261        })
1262    }
1263
1264    pub fn into_cdr(self) -> Vec<u8> {
1265        self.buf
1266    }
1267
1268    /// Start a new `RadarInfoBuilder` with zero-valued defaults.
1269    pub fn builder<'a>() -> RadarInfoBuilder<'a> {
1270        RadarInfoBuilder::new()
1271    }
1272}
1273
1274// ── RadarInfoBuilder<'a> ────────────────────────────────────────────
1275
1276/// Builder for `RadarInfo<Vec<u8>>` with buffer-reuse finalizers.
1277pub struct RadarInfoBuilder<'a> {
1278    stamp: Time,
1279    frame_id: std::borrow::Cow<'a, str>,
1280    center_frequency: std::borrow::Cow<'a, str>,
1281    frequency_sweep: std::borrow::Cow<'a, str>,
1282    range_toggle: std::borrow::Cow<'a, str>,
1283    detection_sensitivity: std::borrow::Cow<'a, str>,
1284    cube: bool,
1285}
1286
1287impl<'a> Default for RadarInfoBuilder<'a> {
1288    fn default() -> Self {
1289        Self {
1290            stamp: Time { sec: 0, nanosec: 0 },
1291            frame_id: std::borrow::Cow::Borrowed(""),
1292            center_frequency: std::borrow::Cow::Borrowed(""),
1293            frequency_sweep: std::borrow::Cow::Borrowed(""),
1294            range_toggle: std::borrow::Cow::Borrowed(""),
1295            detection_sensitivity: std::borrow::Cow::Borrowed(""),
1296            cube: false,
1297        }
1298    }
1299}
1300
1301impl<'a> RadarInfoBuilder<'a> {
1302    pub fn new() -> Self {
1303        Self::default()
1304    }
1305
1306    pub fn stamp(&mut self, t: Time) -> &mut Self {
1307        self.stamp = t;
1308        self
1309    }
1310    pub fn frame_id(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
1311        self.frame_id = s.into();
1312        self
1313    }
1314    pub fn center_frequency(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
1315        self.center_frequency = s.into();
1316        self
1317    }
1318    pub fn frequency_sweep(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
1319        self.frequency_sweep = s.into();
1320        self
1321    }
1322    pub fn range_toggle(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
1323        self.range_toggle = s.into();
1324        self
1325    }
1326    pub fn detection_sensitivity(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
1327        self.detection_sensitivity = s.into();
1328        self
1329    }
1330    pub fn cube(&mut self, v: bool) -> &mut Self {
1331        self.cube = v;
1332        self
1333    }
1334
1335    fn size(&self) -> usize {
1336        let mut s = CdrSizer::new();
1337        Time::size_cdr(&mut s);
1338        s.size_string(&self.frame_id);
1339        s.size_string(&self.center_frequency);
1340        s.size_string(&self.frequency_sweep);
1341        s.size_string(&self.range_toggle);
1342        s.size_string(&self.detection_sensitivity);
1343        s.size_bool();
1344        s.size()
1345    }
1346
1347    fn write_into(&self, buf: &mut [u8]) -> Result<(), CdrError> {
1348        let mut w = CdrWriter::new(buf)?;
1349        self.stamp.write_cdr(&mut w);
1350        w.write_string(&self.frame_id);
1351        w.write_string(&self.center_frequency);
1352        w.write_string(&self.frequency_sweep);
1353        w.write_string(&self.range_toggle);
1354        w.write_string(&self.detection_sensitivity);
1355        w.write_bool(self.cube);
1356        w.finish()
1357    }
1358
1359    pub fn build(&self) -> Result<RadarInfo<Vec<u8>>, CdrError> {
1360        let mut buf = vec![0u8; self.size()];
1361        self.write_into(&mut buf)?;
1362        RadarInfo::from_cdr(buf)
1363    }
1364
1365    pub fn encode_into_vec(&self, buf: &mut Vec<u8>) -> Result<(), CdrError> {
1366        buf.resize(self.size(), 0);
1367        self.write_into(buf)
1368    }
1369
1370    pub fn encode_into_slice(&self, buf: &mut [u8]) -> Result<usize, CdrError> {
1371        let need = self.size();
1372        if buf.len() < need {
1373            return Err(CdrError::BufferTooShort {
1374                need,
1375                have: buf.len(),
1376            });
1377        }
1378        self.write_into(&mut buf[..need])?;
1379        Ok(need)
1380    }
1381}
1382
1383impl<B: AsRef<[u8]> + AsMut<[u8]>> RadarInfo<B> {
1384    pub fn set_stamp(&mut self, t: Time) -> Result<(), CdrError> {
1385        let b = self.buf.as_mut();
1386        wr_i32(b, CDR_HEADER_SIZE, t.sec)?;
1387        wr_u32(b, CDR_HEADER_SIZE + 4, t.nanosec)
1388    }
1389
1390    pub fn set_cube(&mut self, v: bool) -> Result<(), CdrError> {
1391        wr_bool(self.buf.as_mut(), self.offsets[4], v)
1392    }
1393}
1394
1395// ── Track<B> — edgefirst_msgs/msg/Track ─────────────────────────────
1396//
1397// CDR layout: id(string) → offsets[0], lifetime(i32), created(Time)
1398
1399pub struct Track<B> {
1400    buf: B,
1401    offsets: [usize; 1],
1402}
1403
1404impl<B> Track<B> {
1405    /// Convert the buffer type without re-parsing the offset table.
1406    #[inline]
1407    pub fn map_buffer<C>(self, f: impl FnOnce(B) -> C) -> Track<C> {
1408        Track {
1409            buf: f(self.buf),
1410            offsets: self.offsets,
1411        }
1412    }
1413}
1414
1415impl<B: AsRef<[u8]>> Track<B> {
1416    pub fn from_cdr(buf: B) -> Result<Self, CdrError> {
1417        let mut c = CdrCursor::new(buf.as_ref())?;
1418        let _ = c.read_string()?;
1419        let o0 = c.offset();
1420        c.read_i32()?;
1421        Time::read_cdr(&mut c)?;
1422        Ok(Track { offsets: [o0], buf })
1423    }
1424
1425    #[inline]
1426    pub fn id(&self) -> &str {
1427        rd_string(self.buf.as_ref(), CDR_HEADER_SIZE).0
1428    }
1429
1430    pub fn lifetime(&self) -> i32 {
1431        rd_i32(self.buf.as_ref(), align(self.offsets[0], 4))
1432    }
1433
1434    pub fn created(&self) -> Time {
1435        rd_time(self.buf.as_ref(), align(self.offsets[0], 4) + 4)
1436    }
1437
1438    #[inline]
1439    pub fn as_cdr(&self) -> &[u8] {
1440        self.buf.as_ref()
1441    }
1442    pub fn to_cdr(&self) -> Vec<u8> {
1443        self.buf.as_ref().to_vec()
1444    }
1445}
1446
1447impl Track<Vec<u8>> {
1448    #[deprecated(
1449        since = "3.2.0",
1450        note = "use Track::builder() for allocation-free buffer reuse; Track::new will be removed in 4.0"
1451    )]
1452    pub fn new(id: &str, lifetime: i32, created: Time) -> Result<Self, CdrError> {
1453        let mut sizer = CdrSizer::new();
1454        sizer.size_string(id);
1455        let o0 = sizer.offset();
1456        sizer.size_i32();
1457        Time::size_cdr(&mut sizer);
1458
1459        let mut buf = vec![0u8; sizer.size()];
1460        let mut w = CdrWriter::new(&mut buf)?;
1461        w.write_string(id);
1462        w.write_i32(lifetime);
1463        created.write_cdr(&mut w);
1464        w.finish()?;
1465
1466        Ok(Track { offsets: [o0], buf })
1467    }
1468
1469    pub fn into_cdr(self) -> Vec<u8> {
1470        self.buf
1471    }
1472
1473    /// Start a new `TrackBuilder` with zero-valued defaults.
1474    ///
1475    /// Note: `Track` is a standalone message without a `Header` (no
1476    /// `stamp`/`frame_id` prefix) — the builder only exposes the three
1477    /// schema fields `id`, `lifetime`, and `created`.
1478    pub fn builder<'a>() -> TrackBuilder<'a> {
1479        TrackBuilder::new()
1480    }
1481}
1482
1483// ── TrackBuilder<'a> ────────────────────────────────────────────────
1484
1485/// Builder for `Track<Vec<u8>>` with buffer-reuse finalizers.
1486///
1487/// `Track` has no `Header` — it is a standalone sequence element-style
1488/// message whose CDR payload is just `(id, lifetime, created)`.
1489pub struct TrackBuilder<'a> {
1490    id: std::borrow::Cow<'a, str>,
1491    lifetime: i32,
1492    created: Time,
1493}
1494
1495impl<'a> Default for TrackBuilder<'a> {
1496    fn default() -> Self {
1497        Self {
1498            id: std::borrow::Cow::Borrowed(""),
1499            lifetime: 0,
1500            created: Time { sec: 0, nanosec: 0 },
1501        }
1502    }
1503}
1504
1505impl<'a> TrackBuilder<'a> {
1506    pub fn new() -> Self {
1507        Self::default()
1508    }
1509
1510    pub fn id(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
1511        self.id = s.into();
1512        self
1513    }
1514    pub fn lifetime(&mut self, v: i32) -> &mut Self {
1515        self.lifetime = v;
1516        self
1517    }
1518    pub fn created(&mut self, t: Time) -> &mut Self {
1519        self.created = t;
1520        self
1521    }
1522
1523    fn size(&self) -> usize {
1524        let mut s = CdrSizer::new();
1525        s.size_string(&self.id);
1526        s.size_i32();
1527        Time::size_cdr(&mut s);
1528        s.size()
1529    }
1530
1531    fn write_into(&self, buf: &mut [u8]) -> Result<(), CdrError> {
1532        let mut w = CdrWriter::new(buf)?;
1533        w.write_string(&self.id);
1534        w.write_i32(self.lifetime);
1535        self.created.write_cdr(&mut w);
1536        w.finish()
1537    }
1538
1539    pub fn build(&self) -> Result<Track<Vec<u8>>, CdrError> {
1540        let mut buf = vec![0u8; self.size()];
1541        self.write_into(&mut buf)?;
1542        Track::from_cdr(buf)
1543    }
1544
1545    pub fn encode_into_vec(&self, buf: &mut Vec<u8>) -> Result<(), CdrError> {
1546        buf.resize(self.size(), 0);
1547        self.write_into(buf)
1548    }
1549
1550    pub fn encode_into_slice(&self, buf: &mut [u8]) -> Result<usize, CdrError> {
1551        let need = self.size();
1552        if buf.len() < need {
1553            return Err(CdrError::BufferTooShort {
1554                need,
1555                have: buf.len(),
1556            });
1557        }
1558        self.write_into(&mut buf[..need])?;
1559        Ok(need)
1560    }
1561}
1562
1563impl<B: AsRef<[u8]> + AsMut<[u8]>> Track<B> {
1564    pub fn set_lifetime(&mut self, v: i32) -> Result<(), CdrError> {
1565        wr_i32(self.buf.as_mut(), align(self.offsets[0], 4), v)
1566    }
1567
1568    pub fn set_created(&mut self, t: Time) -> Result<(), CdrError> {
1569        let b = self.buf.as_mut();
1570        let p = align(self.offsets[0], 4) + 4;
1571        wr_i32(b, p, t.sec)?;
1572        wr_u32(b, p + 4, t.nanosec)
1573    }
1574}
1575
1576// ── DetectBox<B> — edgefirst_msgs/msg/Box ───────────────────────────
1577//
1578// Named DetectBox to avoid conflict with std::boxed::Box.
1579// CDR layout: center_x(f32), center_y(f32), width(f32), height(f32),
1580//   label(string) → offsets[0], score(f32), distance(f32), speed(f32),
1581//   track.id(string) → offsets[1], track.lifetime(i32), track.created(Time)
1582
1583pub struct DetectBox<B> {
1584    buf: B,
1585    offsets: [usize; 2],
1586}
1587
1588impl<B> DetectBox<B> {
1589    /// Convert the buffer type without re-parsing the offset table.
1590    #[inline]
1591    pub fn map_buffer<C>(self, f: impl FnOnce(B) -> C) -> DetectBox<C> {
1592        DetectBox {
1593            buf: f(self.buf),
1594            offsets: self.offsets,
1595        }
1596    }
1597}
1598
1599/// Zero-copy view of a Box element within a CDR sequence.
1600#[derive(Copy, Clone, Debug)]
1601pub struct DetectBoxView<'a> {
1602    pub center_x: f32,
1603    pub center_y: f32,
1604    pub width: f32,
1605    pub height: f32,
1606    pub label: &'a str,
1607    pub score: f32,
1608    pub distance: f32,
1609    pub speed: f32,
1610    pub track_id: &'a str,
1611    pub track_lifetime: i32,
1612    pub track_created: Time,
1613}
1614
1615pub(crate) fn scan_box_element<'a>(c: &mut CdrCursor<'a>) -> Result<DetectBoxView<'a>, CdrError> {
1616    let center_x = c.read_f32()?;
1617    let center_y = c.read_f32()?;
1618    let width = c.read_f32()?;
1619    let height = c.read_f32()?;
1620    let label = c.read_string()?;
1621    let score = c.read_f32()?;
1622    let distance = c.read_f32()?;
1623    let speed = c.read_f32()?;
1624    let track_id = c.read_string()?;
1625    let track_lifetime = c.read_i32()?;
1626    let track_created = Time::read_cdr(c)?;
1627    Ok(DetectBoxView {
1628        center_x,
1629        center_y,
1630        width,
1631        height,
1632        label,
1633        score,
1634        distance,
1635        speed,
1636        track_id,
1637        track_lifetime,
1638        track_created,
1639    })
1640}
1641
1642pub(crate) fn write_box_element(w: &mut CdrWriter<'_>, b: &DetectBoxView<'_>) {
1643    w.write_f32(b.center_x);
1644    w.write_f32(b.center_y);
1645    w.write_f32(b.width);
1646    w.write_f32(b.height);
1647    w.write_string(b.label);
1648    w.write_f32(b.score);
1649    w.write_f32(b.distance);
1650    w.write_f32(b.speed);
1651    w.write_string(b.track_id);
1652    w.write_i32(b.track_lifetime);
1653    b.track_created.write_cdr(w);
1654}
1655
1656pub(crate) fn size_box_element(s: &mut CdrSizer, label: &str, track_id: &str) {
1657    s.size_f32();
1658    s.size_f32();
1659    s.size_f32();
1660    s.size_f32(); // center_x/y, width, height
1661    s.size_string(label);
1662    s.size_f32();
1663    s.size_f32();
1664    s.size_f32(); // score, distance, speed
1665    s.size_string(track_id);
1666    s.size_i32();
1667    Time::size_cdr(s);
1668}
1669
1670impl DetectBox<&'static [u8]> {
1671    /// Parse a standalone DetectBox CDR buffer and return a `'static`-lifetimed
1672    /// view.
1673    ///
1674    /// This is an FFI-oriented helper. Unlike the field-by-field copy path via
1675    /// `DetectBox::from_cdr(...)` followed by accessor methods, the returned
1676    /// `DetectBoxView<'static>` borrows directly from the input buffer with the
1677    /// buffer's native `'static` lifetime — no `mem::transmute` is required at
1678    /// the FFI layer to widen method-returned references whose lifetimes are
1679    /// tied to a temporary `&self`.
1680    ///
1681    /// The parse is a single pass: `scan_box_element` consumes the fields and
1682    /// produces the view directly, so there is no double-validation cost.
1683    pub(crate) fn from_cdr_as_view(buf: &'static [u8]) -> Result<DetectBoxView<'static>, CdrError> {
1684        let mut c = CdrCursor::new(buf)?;
1685        scan_box_element(&mut c)
1686    }
1687}
1688
1689impl<B: AsRef<[u8]>> DetectBox<B> {
1690    pub fn from_cdr(buf: B) -> Result<Self, CdrError> {
1691        let mut c = CdrCursor::new(buf.as_ref())?;
1692        c.read_f32()?;
1693        c.read_f32()?;
1694        c.read_f32()?;
1695        c.read_f32()?;
1696        let _ = c.read_string()?;
1697        let o0 = c.offset();
1698        c.read_f32()?;
1699        c.read_f32()?;
1700        c.read_f32()?;
1701        let _ = c.read_string()?;
1702        let o1 = c.offset();
1703        c.read_i32()?;
1704        Time::read_cdr(&mut c)?;
1705        Ok(DetectBox {
1706            offsets: [o0, o1],
1707            buf,
1708        })
1709    }
1710
1711    #[inline]
1712    pub fn center_x(&self) -> f32 {
1713        rd_f32(self.buf.as_ref(), CDR_HEADER_SIZE)
1714    }
1715    #[inline]
1716    pub fn center_y(&self) -> f32 {
1717        rd_f32(self.buf.as_ref(), CDR_HEADER_SIZE + 4)
1718    }
1719    #[inline]
1720    pub fn width(&self) -> f32 {
1721        rd_f32(self.buf.as_ref(), CDR_HEADER_SIZE + 8)
1722    }
1723    #[inline]
1724    pub fn height(&self) -> f32 {
1725        rd_f32(self.buf.as_ref(), CDR_HEADER_SIZE + 12)
1726    }
1727
1728    pub fn label(&self) -> &str {
1729        rd_string(self.buf.as_ref(), CDR_HEADER_SIZE + 16).0
1730    }
1731
1732    pub fn score(&self) -> f32 {
1733        rd_f32(self.buf.as_ref(), align(self.offsets[0], 4))
1734    }
1735    pub fn distance(&self) -> f32 {
1736        rd_f32(self.buf.as_ref(), align(self.offsets[0], 4) + 4)
1737    }
1738    pub fn speed(&self) -> f32 {
1739        rd_f32(self.buf.as_ref(), align(self.offsets[0], 4) + 8)
1740    }
1741
1742    pub fn track_id(&self) -> &str {
1743        rd_string(self.buf.as_ref(), align(self.offsets[0], 4) + 12).0
1744    }
1745
1746    pub fn track_lifetime(&self) -> i32 {
1747        rd_i32(self.buf.as_ref(), align(self.offsets[1], 4))
1748    }
1749
1750    pub fn track_created(&self) -> Time {
1751        rd_time(self.buf.as_ref(), align(self.offsets[1], 4) + 4)
1752    }
1753
1754    #[inline]
1755    pub fn as_cdr(&self) -> &[u8] {
1756        self.buf.as_ref()
1757    }
1758    pub fn to_cdr(&self) -> Vec<u8> {
1759        self.buf.as_ref().to_vec()
1760    }
1761}
1762
1763impl DetectBox<Vec<u8>> {
1764    #[deprecated(
1765        since = "3.2.0",
1766        note = "use DetectBox::builder() for allocation-free buffer reuse; DetectBox::new will be removed in 4.0"
1767    )]
1768    #[allow(clippy::too_many_arguments)]
1769    pub fn new(
1770        center_x: f32,
1771        center_y: f32,
1772        width: f32,
1773        height: f32,
1774        label: &str,
1775        score: f32,
1776        distance: f32,
1777        speed: f32,
1778        track_id: &str,
1779        track_lifetime: i32,
1780        track_created: Time,
1781    ) -> Result<Self, CdrError> {
1782        let mut sizer = CdrSizer::new();
1783        sizer.size_f32();
1784        sizer.size_f32();
1785        sizer.size_f32();
1786        sizer.size_f32();
1787        sizer.size_string(label);
1788        let o0 = sizer.offset();
1789        sizer.size_f32();
1790        sizer.size_f32();
1791        sizer.size_f32();
1792        sizer.size_string(track_id);
1793        let o1 = sizer.offset();
1794        sizer.size_i32();
1795        Time::size_cdr(&mut sizer);
1796
1797        let mut buf = vec![0u8; sizer.size()];
1798        let mut w = CdrWriter::new(&mut buf)?;
1799        w.write_f32(center_x);
1800        w.write_f32(center_y);
1801        w.write_f32(width);
1802        w.write_f32(height);
1803        w.write_string(label);
1804        w.write_f32(score);
1805        w.write_f32(distance);
1806        w.write_f32(speed);
1807        w.write_string(track_id);
1808        w.write_i32(track_lifetime);
1809        track_created.write_cdr(&mut w);
1810        w.finish()?;
1811
1812        Ok(DetectBox {
1813            offsets: [o0, o1],
1814            buf,
1815        })
1816    }
1817
1818    pub fn into_cdr(self) -> Vec<u8> {
1819        self.buf
1820    }
1821
1822    /// Start a new `DetectBoxBuilder` with zero-valued defaults.
1823    pub fn builder<'a>() -> DetectBoxBuilder<'a> {
1824        DetectBoxBuilder::new()
1825    }
1826}
1827
1828// ── DetectBoxBuilder<'a> ────────────────────────────────────────────
1829
1830/// Builder for `DetectBox<Vec<u8>>` with buffer-reuse finalizers.
1831///
1832/// Mirrors `DetectBoxView` — the same shape used inside sequences in
1833/// `Detect` and `Model`.
1834pub struct DetectBoxBuilder<'a> {
1835    center_x: f32,
1836    center_y: f32,
1837    width: f32,
1838    height: f32,
1839    label: std::borrow::Cow<'a, str>,
1840    score: f32,
1841    distance: f32,
1842    speed: f32,
1843    track_id: std::borrow::Cow<'a, str>,
1844    track_lifetime: i32,
1845    track_created: Time,
1846}
1847
1848impl<'a> Default for DetectBoxBuilder<'a> {
1849    fn default() -> Self {
1850        Self {
1851            center_x: 0.0,
1852            center_y: 0.0,
1853            width: 0.0,
1854            height: 0.0,
1855            label: std::borrow::Cow::Borrowed(""),
1856            score: 0.0,
1857            distance: 0.0,
1858            speed: 0.0,
1859            track_id: std::borrow::Cow::Borrowed(""),
1860            track_lifetime: 0,
1861            track_created: Time { sec: 0, nanosec: 0 },
1862        }
1863    }
1864}
1865
1866impl<'a> DetectBoxBuilder<'a> {
1867    pub fn new() -> Self {
1868        Self::default()
1869    }
1870
1871    pub fn center_x(&mut self, v: f32) -> &mut Self {
1872        self.center_x = v;
1873        self
1874    }
1875    pub fn center_y(&mut self, v: f32) -> &mut Self {
1876        self.center_y = v;
1877        self
1878    }
1879    pub fn width(&mut self, v: f32) -> &mut Self {
1880        self.width = v;
1881        self
1882    }
1883    pub fn height(&mut self, v: f32) -> &mut Self {
1884        self.height = v;
1885        self
1886    }
1887    pub fn label(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
1888        self.label = s.into();
1889        self
1890    }
1891    pub fn score(&mut self, v: f32) -> &mut Self {
1892        self.score = v;
1893        self
1894    }
1895    pub fn distance(&mut self, v: f32) -> &mut Self {
1896        self.distance = v;
1897        self
1898    }
1899    pub fn speed(&mut self, v: f32) -> &mut Self {
1900        self.speed = v;
1901        self
1902    }
1903    pub fn track_id(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
1904        self.track_id = s.into();
1905        self
1906    }
1907    pub fn track_lifetime(&mut self, v: i32) -> &mut Self {
1908        self.track_lifetime = v;
1909        self
1910    }
1911    pub fn track_created(&mut self, t: Time) -> &mut Self {
1912        self.track_created = t;
1913        self
1914    }
1915
1916    fn size(&self) -> usize {
1917        let mut s = CdrSizer::new();
1918        size_box_element(&mut s, &self.label, &self.track_id);
1919        s.size()
1920    }
1921
1922    fn write_into(&self, buf: &mut [u8]) -> Result<(), CdrError> {
1923        let mut w = CdrWriter::new(buf)?;
1924        let view = DetectBoxView {
1925            center_x: self.center_x,
1926            center_y: self.center_y,
1927            width: self.width,
1928            height: self.height,
1929            label: &self.label,
1930            score: self.score,
1931            distance: self.distance,
1932            speed: self.speed,
1933            track_id: &self.track_id,
1934            track_lifetime: self.track_lifetime,
1935            track_created: self.track_created,
1936        };
1937        write_box_element(&mut w, &view);
1938        w.finish()
1939    }
1940
1941    pub fn build(&self) -> Result<DetectBox<Vec<u8>>, CdrError> {
1942        let mut buf = vec![0u8; self.size()];
1943        self.write_into(&mut buf)?;
1944        DetectBox::from_cdr(buf)
1945    }
1946
1947    pub fn encode_into_vec(&self, buf: &mut Vec<u8>) -> Result<(), CdrError> {
1948        buf.resize(self.size(), 0);
1949        self.write_into(buf)
1950    }
1951
1952    pub fn encode_into_slice(&self, buf: &mut [u8]) -> Result<usize, CdrError> {
1953        let need = self.size();
1954        if buf.len() < need {
1955            return Err(CdrError::BufferTooShort {
1956                need,
1957                have: buf.len(),
1958            });
1959        }
1960        self.write_into(&mut buf[..need])?;
1961        Ok(need)
1962    }
1963}
1964
1965impl<B: AsRef<[u8]> + AsMut<[u8]>> DetectBox<B> {
1966    pub fn set_center_x(&mut self, v: f32) -> Result<(), CdrError> {
1967        wr_f32(self.buf.as_mut(), CDR_HEADER_SIZE, v)
1968    }
1969
1970    pub fn set_center_y(&mut self, v: f32) -> Result<(), CdrError> {
1971        wr_f32(self.buf.as_mut(), CDR_HEADER_SIZE + 4, v)
1972    }
1973
1974    pub fn set_width(&mut self, v: f32) -> Result<(), CdrError> {
1975        wr_f32(self.buf.as_mut(), CDR_HEADER_SIZE + 8, v)
1976    }
1977
1978    pub fn set_height(&mut self, v: f32) -> Result<(), CdrError> {
1979        wr_f32(self.buf.as_mut(), CDR_HEADER_SIZE + 12, v)
1980    }
1981
1982    pub fn set_score(&mut self, v: f32) -> Result<(), CdrError> {
1983        wr_f32(self.buf.as_mut(), align(self.offsets[0], 4), v)
1984    }
1985
1986    pub fn set_distance(&mut self, v: f32) -> Result<(), CdrError> {
1987        wr_f32(self.buf.as_mut(), align(self.offsets[0], 4) + 4, v)
1988    }
1989
1990    pub fn set_speed(&mut self, v: f32) -> Result<(), CdrError> {
1991        wr_f32(self.buf.as_mut(), align(self.offsets[0], 4) + 8, v)
1992    }
1993
1994    pub fn set_track_lifetime(&mut self, v: i32) -> Result<(), CdrError> {
1995        wr_i32(self.buf.as_mut(), align(self.offsets[1], 4), v)
1996    }
1997
1998    pub fn set_track_created(&mut self, t: Time) -> Result<(), CdrError> {
1999        let b = self.buf.as_mut();
2000        let p = align(self.offsets[1], 4) + 4;
2001        wr_i32(b, p, t.sec)?;
2002        wr_u32(b, p + 4, t.nanosec)
2003    }
2004}
2005
2006// ── Detect<B> — edgefirst_msgs/msg/Detect ───────────────────────────
2007//
2008// CDR layout: Header → offsets[0],
2009//   input_timestamp(Time), model_time(Time), output_time(Time),
2010//   boxes(Vec<Box>) → offsets[1]
2011
2012pub struct Detect<B> {
2013    buf: B,
2014    offsets: [usize; 2],
2015}
2016
2017impl<B> Detect<B> {
2018    /// Convert the buffer type without re-parsing the offset table.
2019    #[inline]
2020    pub fn map_buffer<C>(self, f: impl FnOnce(B) -> C) -> Detect<C> {
2021        Detect {
2022            buf: f(self.buf),
2023            offsets: self.offsets,
2024        }
2025    }
2026}
2027
2028impl<B: AsRef<[u8]>> Detect<B> {
2029    pub fn from_cdr(buf: B) -> Result<Self, CdrError> {
2030        let header = Header::<&[u8]>::from_cdr(buf.as_ref())?;
2031        let o0 = header.end_offset();
2032        let mut c = CdrCursor::resume(buf.as_ref(), o0);
2033        Time::read_cdr(&mut c)?; // input_timestamp
2034        Time::read_cdr(&mut c)?; // model_time
2035        Time::read_cdr(&mut c)?; // output_time
2036        let raw_count = c.read_u32()?;
2037        let count = c.check_seq_count(raw_count, 24)?;
2038        for _ in 0..count {
2039            scan_box_element(&mut c)?;
2040        }
2041        let o1 = c.offset();
2042        Ok(Detect {
2043            offsets: [o0, o1],
2044            buf,
2045        })
2046    }
2047
2048    #[inline]
2049    /// Returns a `Header` view by re-parsing the CDR buffer prefix.
2050    /// Prefer `stamp()` / `frame_id()` for direct O(1) field access.
2051    pub fn header(&self) -> Header<&[u8]> {
2052        Header::from_cdr(self.buf.as_ref()).expect("header bytes validated during from_cdr")
2053    }
2054    #[inline]
2055    pub fn stamp(&self) -> Time {
2056        rd_time(self.buf.as_ref(), CDR_HEADER_SIZE)
2057    }
2058    #[inline]
2059    pub fn frame_id(&self) -> &str {
2060        rd_string(self.buf.as_ref(), CDR_HEADER_SIZE + 8).0
2061    }
2062
2063    pub fn input_timestamp(&self) -> Time {
2064        let p = align(self.offsets[0], 4);
2065        rd_time(self.buf.as_ref(), p)
2066    }
2067
2068    pub fn model_time(&self) -> Time {
2069        rd_time(self.buf.as_ref(), align(self.offsets[0], 4) + 8)
2070    }
2071
2072    pub fn output_time(&self) -> Time {
2073        rd_time(self.buf.as_ref(), align(self.offsets[0], 4) + 16)
2074    }
2075
2076    pub fn boxes_len(&self) -> u32 {
2077        rd_u32(self.buf.as_ref(), align(self.offsets[0], 4) + 24)
2078    }
2079
2080    pub fn boxes(&self) -> Vec<DetectBoxView<'_>> {
2081        let b = self.buf.as_ref();
2082        let p = align(self.offsets[0], 4) + 24;
2083        let count = rd_u32(b, p) as usize;
2084        let mut c = CdrCursor::resume(b, p + 4);
2085        (0..count)
2086            .map(|_| scan_box_element(&mut c).expect("box elements validated during from_cdr"))
2087            .collect()
2088    }
2089
2090    #[inline]
2091    pub fn as_cdr(&self) -> &[u8] {
2092        self.buf.as_ref()
2093    }
2094    pub fn to_cdr(&self) -> Vec<u8> {
2095        self.buf.as_ref().to_vec()
2096    }
2097}
2098
2099impl Detect<&'static [u8]> {
2100    /// Parse a Detect message and simultaneously collect the box views
2101    /// encountered during validation, avoiding a second parse pass in the
2102    /// FFI layer.
2103    ///
2104    /// The views in the returned `Vec` naturally have `'static` lifetime
2105    /// because they borrow from the `&'static [u8]` buffer. No unsafe
2106    /// transmute is required.
2107    ///
2108    /// This is a crate-private helper used by the FFI layer to avoid the
2109    /// cost of a second walk in `inner.boxes()` after `from_cdr`.
2110    pub(crate) fn from_cdr_collect_boxes(
2111        buf: &'static [u8],
2112    ) -> Result<(Self, Vec<DetectBoxView<'static>>), CdrError> {
2113        let header = Header::<&[u8]>::from_cdr(buf)?;
2114        let o0 = header.end_offset();
2115        let mut c = CdrCursor::resume(buf, o0);
2116        Time::read_cdr(&mut c)?; // input_timestamp
2117        Time::read_cdr(&mut c)?; // model_time
2118        Time::read_cdr(&mut c)?; // output_time
2119        let raw_count = c.read_u32()?;
2120        let count = c.check_seq_count(raw_count, 24)?;
2121        let mut box_views = Vec::with_capacity(count);
2122        for _ in 0..count {
2123            box_views.push(scan_box_element(&mut c)?);
2124        }
2125        let o1 = c.offset();
2126        Ok((
2127            Detect {
2128                offsets: [o0, o1],
2129                buf,
2130            },
2131            box_views,
2132        ))
2133    }
2134}
2135
2136impl Detect<Vec<u8>> {
2137    #[deprecated(
2138        since = "3.2.0",
2139        note = "use Detect::builder() for allocation-free buffer reuse; Detect::new will be removed in 4.0"
2140    )]
2141    pub fn new(
2142        stamp: Time,
2143        frame_id: &str,
2144        input_timestamp: Time,
2145        model_time: Time,
2146        output_time: Time,
2147        boxes: &[DetectBoxView<'_>],
2148    ) -> Result<Self, CdrError> {
2149        let mut sizer = CdrSizer::new();
2150        Time::size_cdr(&mut sizer);
2151        sizer.size_string(frame_id);
2152        let o0 = sizer.offset();
2153        Time::size_cdr(&mut sizer);
2154        Time::size_cdr(&mut sizer);
2155        Time::size_cdr(&mut sizer);
2156        sizer.size_u32();
2157        for b in boxes {
2158            size_box_element(&mut sizer, b.label, b.track_id);
2159        }
2160        let o1 = sizer.offset();
2161
2162        let mut buf = vec![0u8; sizer.size()];
2163        let mut w = CdrWriter::new(&mut buf)?;
2164        stamp.write_cdr(&mut w);
2165        w.write_string(frame_id);
2166        input_timestamp.write_cdr(&mut w);
2167        model_time.write_cdr(&mut w);
2168        output_time.write_cdr(&mut w);
2169        w.write_u32(boxes.len() as u32);
2170        for b in boxes {
2171            write_box_element(&mut w, b);
2172        }
2173        w.finish()?;
2174
2175        Ok(Detect {
2176            offsets: [o0, o1],
2177            buf,
2178        })
2179    }
2180
2181    pub fn into_cdr(self) -> Vec<u8> {
2182        self.buf
2183    }
2184
2185    /// Start a new `DetectBuilder` with zero-valued defaults.
2186    pub fn builder<'a>() -> DetectBuilder<'a> {
2187        DetectBuilder::new()
2188    }
2189}
2190
2191// ── DetectBuilder<'a> ───────────────────────────────────────────────
2192
2193/// Builder for `Detect<Vec<u8>>` with buffer-reuse finalizers.
2194///
2195/// `boxes` is borrowed from a caller-owned slice for the lifetime of the
2196/// builder. Each `DetectBoxView` itself borrows `label` and `track_id`
2197/// from caller memory — all borrows must remain valid until `build()`,
2198/// `encode_into_vec()`, or `encode_into_slice()` is called.
2199pub struct DetectBuilder<'a> {
2200    stamp: Time,
2201    frame_id: std::borrow::Cow<'a, str>,
2202    input_timestamp: Time,
2203    model_time: Time,
2204    output_time: Time,
2205    boxes: &'a [DetectBoxView<'a>],
2206}
2207
2208impl<'a> Default for DetectBuilder<'a> {
2209    fn default() -> Self {
2210        Self {
2211            stamp: Time { sec: 0, nanosec: 0 },
2212            frame_id: std::borrow::Cow::Borrowed(""),
2213            input_timestamp: Time { sec: 0, nanosec: 0 },
2214            model_time: Time { sec: 0, nanosec: 0 },
2215            output_time: Time { sec: 0, nanosec: 0 },
2216            boxes: &[],
2217        }
2218    }
2219}
2220
2221impl<'a> DetectBuilder<'a> {
2222    pub fn new() -> Self {
2223        Self::default()
2224    }
2225
2226    pub fn stamp(&mut self, t: Time) -> &mut Self {
2227        self.stamp = t;
2228        self
2229    }
2230    pub fn frame_id(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
2231        self.frame_id = s.into();
2232        self
2233    }
2234    pub fn input_timestamp(&mut self, t: Time) -> &mut Self {
2235        self.input_timestamp = t;
2236        self
2237    }
2238    pub fn model_time(&mut self, t: Time) -> &mut Self {
2239        self.model_time = t;
2240        self
2241    }
2242    pub fn output_time(&mut self, t: Time) -> &mut Self {
2243        self.output_time = t;
2244        self
2245    }
2246    pub fn boxes(&mut self, b: &'a [DetectBoxView<'a>]) -> &mut Self {
2247        self.boxes = b;
2248        self
2249    }
2250
2251    fn size(&self) -> usize {
2252        let mut s = CdrSizer::new();
2253        Time::size_cdr(&mut s);
2254        s.size_string(&self.frame_id);
2255        Time::size_cdr(&mut s);
2256        Time::size_cdr(&mut s);
2257        Time::size_cdr(&mut s);
2258        s.size_u32();
2259        for b in self.boxes {
2260            size_box_element(&mut s, b.label, b.track_id);
2261        }
2262        s.size()
2263    }
2264
2265    fn write_into(&self, buf: &mut [u8]) -> Result<(), CdrError> {
2266        let mut w = CdrWriter::new(buf)?;
2267        self.stamp.write_cdr(&mut w);
2268        w.write_string(&self.frame_id);
2269        self.input_timestamp.write_cdr(&mut w);
2270        self.model_time.write_cdr(&mut w);
2271        self.output_time.write_cdr(&mut w);
2272        w.write_u32(self.boxes.len() as u32);
2273        for b in self.boxes {
2274            write_box_element(&mut w, b);
2275        }
2276        w.finish()
2277    }
2278
2279    pub fn build(&self) -> Result<Detect<Vec<u8>>, CdrError> {
2280        let mut buf = vec![0u8; self.size()];
2281        self.write_into(&mut buf)?;
2282        Detect::from_cdr(buf)
2283    }
2284
2285    pub fn encode_into_vec(&self, buf: &mut Vec<u8>) -> Result<(), CdrError> {
2286        buf.resize(self.size(), 0);
2287        self.write_into(buf)
2288    }
2289
2290    pub fn encode_into_slice(&self, buf: &mut [u8]) -> Result<usize, CdrError> {
2291        let need = self.size();
2292        if buf.len() < need {
2293            return Err(CdrError::BufferTooShort {
2294                need,
2295                have: buf.len(),
2296            });
2297        }
2298        self.write_into(&mut buf[..need])?;
2299        Ok(need)
2300    }
2301}
2302
2303impl<B: AsRef<[u8]> + AsMut<[u8]>> Detect<B> {
2304    pub fn set_stamp(&mut self, t: Time) -> Result<(), CdrError> {
2305        let b = self.buf.as_mut();
2306        wr_i32(b, CDR_HEADER_SIZE, t.sec)?;
2307        wr_u32(b, CDR_HEADER_SIZE + 4, t.nanosec)
2308    }
2309
2310    pub fn set_input_timestamp(&mut self, t: Time) -> Result<(), CdrError> {
2311        let b = self.buf.as_mut();
2312        let p = align(self.offsets[0], 4);
2313        wr_i32(b, p, t.sec)?;
2314        wr_u32(b, p + 4, t.nanosec)
2315    }
2316
2317    pub fn set_model_time(&mut self, t: Time) -> Result<(), CdrError> {
2318        let b = self.buf.as_mut();
2319        let p = align(self.offsets[0], 4) + 8;
2320        wr_i32(b, p, t.sec)?;
2321        wr_u32(b, p + 4, t.nanosec)
2322    }
2323
2324    pub fn set_output_time(&mut self, t: Time) -> Result<(), CdrError> {
2325        let b = self.buf.as_mut();
2326        let p = align(self.offsets[0], 4) + 16;
2327        wr_i32(b, p, t.sec)?;
2328        wr_u32(b, p + 4, t.nanosec)
2329    }
2330}
2331
2332// ── CameraFrame / CameraPlane — edgefirst_msgs/msg/CameraFrame ──────
2333//
2334// CameraFrame CDR layout:
2335//   Header → offsets[0], then
2336//     sequence(u64) + pid(u32) + width(u32) + height(u32)
2337//     + format(string) + color_space(string) + color_transfer(string)
2338//     + color_encoding(string) + color_range(string)
2339//     + fence_fd(i32)
2340//     + planes(seq<CameraPlane>) → offsets[1]
2341//
2342// CameraPlane element layout (variable-sized due to trailing data[]):
2343//   fd(i32) + offset(u32) + stride(u32) + size(u32) + used(u32) + data(seq<u8>)
2344
2345/// Zero-copy view of a single CameraPlane element, borrowed from a CDR buffer.
2346///
2347/// `fd == -1` signals that the plane's bytes are inlined in `data`; any other
2348/// negative fd is invalid. When `fd >= 0`, `data` must be empty.
2349#[derive(Copy, Clone, Debug)]
2350pub struct CameraPlaneView<'a> {
2351    pub fd: i32,
2352    pub offset: u32,
2353    pub stride: u32,
2354    pub size: u32,
2355    pub used: u32,
2356    pub data: &'a [u8],
2357}
2358
2359pub(crate) fn scan_plane_element<'a>(
2360    c: &mut CdrCursor<'a>,
2361) -> Result<CameraPlaneView<'a>, CdrError> {
2362    let fd = c.read_i32()?;
2363    let offset = c.read_u32()?;
2364    let stride = c.read_u32()?;
2365    let size = c.read_u32()?;
2366    let used = c.read_u32()?;
2367    let data = c.read_bytes()?;
2368    Ok(CameraPlaneView {
2369        fd,
2370        offset,
2371        stride,
2372        size,
2373        used,
2374        data,
2375    })
2376}
2377
2378pub(crate) fn write_plane_element(w: &mut CdrWriter<'_>, p: &CameraPlaneView<'_>) {
2379    w.write_i32(p.fd);
2380    w.write_u32(p.offset);
2381    w.write_u32(p.stride);
2382    w.write_u32(p.size);
2383    w.write_u32(p.used);
2384    w.write_bytes(p.data);
2385}
2386
2387pub(crate) fn size_plane_element(s: &mut CdrSizer, data_len: usize) {
2388    s.size_i32();
2389    s.size_u32();
2390    s.size_u32();
2391    s.size_u32();
2392    s.size_u32();
2393    s.size_bytes(data_len);
2394}
2395
2396/// Validate a CameraPlane against the schema contract (see CameraPlane.msg).
2397///
2398/// Contract:
2399///   - `fd >= -1` (only `-1` is a valid negative value; other negatives invalid)
2400///   - `used <= size`
2401///   - `fd >= 0`  => `data` empty (bytes live in DMA-BUF, not inlined)
2402///   - `fd == -1` => `size as usize == data.len()` (inlined: size describes data)
2403pub(crate) fn validate_plane(
2404    fd: i32,
2405    size: u32,
2406    used: u32,
2407    data_len: usize,
2408) -> Result<(), CdrError> {
2409    if fd < -1
2410        || used > size
2411        || (fd >= 0 && data_len != 0)
2412        || (fd == -1 && size as usize != data_len)
2413    {
2414        return Err(CdrError::InvalidHeader);
2415    }
2416    Ok(())
2417}
2418
2419/// Multi-plane video frame reference message.
2420///
2421/// Replaces the single-plane `DmaBuffer` with a schema that supports planar
2422/// formats (NV12, I420, planar RGB NCHW), hardware codec bitstreams (H.264
2423/// with `used` < `size`), GPU fence synchronization, and off-device bridging
2424/// via inlined per-plane bytes.
2425///
2426/// # Example
2427///
2428/// ```
2429/// use edgefirst_schemas::edgefirst_msgs::{CameraFrame, CameraPlaneView};
2430/// use edgefirst_schemas::builtin_interfaces::Time;
2431///
2432/// let y = CameraPlaneView {
2433///     fd: 42, offset: 0, stride: 1920,
2434///     size: 2_073_600, used: 2_073_600, data: &[],
2435/// };
2436/// let uv = CameraPlaneView {
2437///     fd: 42, offset: 2_073_600, stride: 1920,
2438///     size: 1_036_800, used: 1_036_800, data: &[],
2439/// };
2440/// let cf = CameraFrame::new(
2441///     Time::new(1, 0), "cam0",
2442///     /*seq*/ 1, /*pid*/ 1234, /*w*/ 1920, /*h*/ 1080,
2443///     "NV12", "bt709", "bt709", "bt709", "limited",
2444///     /*fence_fd*/ -1, &[y, uv],
2445/// ).unwrap();
2446/// let view = CameraFrame::<&[u8]>::from_cdr(cf.as_cdr()).unwrap();
2447/// assert_eq!(view.format(), "NV12");
2448/// assert_eq!(view.planes().len(), 2);
2449/// ```
2450pub struct CameraFrame<B> {
2451    buf: B,
2452    // [0]: after Header (start of `seq`).
2453    // [1]: position of the `planes` sequence-count u32 prefix (the field
2454    // immediately after fence_fd). Caching this avoids rescanning the five
2455    // variable-length colorimetry strings on every `planes()`/`num_planes()`
2456    // call — important for high-frame-rate consumers.
2457    offsets: [usize; 2],
2458}
2459
2460impl<B> CameraFrame<B> {
2461    /// Convert the buffer type without re-parsing the offset table.
2462    #[inline]
2463    pub fn map_buffer<C>(self, f: impl FnOnce(B) -> C) -> CameraFrame<C> {
2464        CameraFrame {
2465            buf: f(self.buf),
2466            offsets: self.offsets,
2467        }
2468    }
2469}
2470
2471impl<B: AsRef<[u8]>> CameraFrame<B> {
2472    pub fn from_cdr(buf: B) -> Result<Self, CdrError> {
2473        let header = Header::<&[u8]>::from_cdr(buf.as_ref())?;
2474        let o0 = header.end_offset();
2475        let mut c = CdrCursor::resume(buf.as_ref(), o0);
2476        c.read_u64()?; // seq
2477        c.read_u32()?; // pid
2478        let width = c.read_u32()?;
2479        let height = c.read_u32()?;
2480        c.read_string()?; // format
2481        c.read_string()?; // color_space
2482        c.read_string()?; // color_transfer
2483        c.read_string()?; // color_encoding
2484        c.read_string()?; // color_range
2485        c.read_i32()?; // fence_fd
2486        let planes_pos = c.offset();
2487        let raw_count = c.read_u32()?;
2488        // min plane size: 5×u32 + 4-byte data seq count = 24 bytes
2489        let count = c.check_seq_count(raw_count, 24)?;
2490        for _ in 0..count {
2491            let plane = scan_plane_element(&mut c)?;
2492            validate_plane(plane.fd, plane.size, plane.used, plane.data.len())?;
2493        }
2494
2495        if width == 0 || height == 0 {
2496            return Err(CdrError::InvalidHeader);
2497        }
2498
2499        Ok(CameraFrame {
2500            offsets: [o0, planes_pos],
2501            buf,
2502        })
2503    }
2504
2505    #[inline]
2506    /// Returns a `Header` view by re-parsing the CDR buffer prefix.
2507    /// Prefer `stamp()` / `frame_id()` for direct O(1) field access.
2508    pub fn header(&self) -> Header<&[u8]> {
2509        Header::from_cdr(self.buf.as_ref()).expect("header bytes validated during from_cdr")
2510    }
2511    #[inline]
2512    pub fn stamp(&self) -> Time {
2513        rd_time(self.buf.as_ref(), CDR_HEADER_SIZE)
2514    }
2515    #[inline]
2516    pub fn frame_id(&self) -> &str {
2517        rd_string(self.buf.as_ref(), CDR_HEADER_SIZE + 8).0
2518    }
2519
2520    #[inline]
2521    pub fn seq(&self) -> u64 {
2522        // u64 needs 8-byte alignment relative to CDR data start.
2523        rd_u64(self.buf.as_ref(), cdr_align(self.offsets[0], 8))
2524    }
2525    #[inline]
2526    pub fn pid(&self) -> u32 {
2527        rd_u32(self.buf.as_ref(), cdr_align(self.offsets[0], 8) + 8)
2528    }
2529    #[inline]
2530    pub fn width(&self) -> u32 {
2531        rd_u32(self.buf.as_ref(), cdr_align(self.offsets[0], 8) + 12)
2532    }
2533    #[inline]
2534    pub fn height(&self) -> u32 {
2535        rd_u32(self.buf.as_ref(), cdr_align(self.offsets[0], 8) + 16)
2536    }
2537
2538    fn strings_start(&self) -> usize {
2539        // Position of `format` string length prefix.
2540        cdr_align(self.offsets[0], 8) + 20
2541    }
2542
2543    /// Walk format + 4 color strings, returning each string and the fence_fd
2544    /// that follows. String accessors unavoidably re-walk preceding strings
2545    /// because CDR string lengths are variable; plane access uses the cached
2546    /// `offsets[1]` and does not hit this path.
2547    fn scan_strings_and_fence(&self) -> (&str, &str, &str, &str, &str, i32) {
2548        let b = self.buf.as_ref();
2549        let (format, p1) = rd_string(b, self.strings_start());
2550        let (cs, p2) = rd_string(b, p1);
2551        let (ct, p3) = rd_string(b, p2);
2552        let (ce, p4) = rd_string(b, p3);
2553        let (cr, p5) = rd_string(b, p4);
2554        let fence_fd = rd_i32(b, align(p5, 4));
2555        (format, cs, ct, ce, cr, fence_fd)
2556    }
2557
2558    #[inline]
2559    pub fn format(&self) -> &str {
2560        self.scan_strings_and_fence().0
2561    }
2562    #[inline]
2563    pub fn color_space(&self) -> &str {
2564        self.scan_strings_and_fence().1
2565    }
2566    #[inline]
2567    pub fn color_transfer(&self) -> &str {
2568        self.scan_strings_and_fence().2
2569    }
2570    #[inline]
2571    pub fn color_encoding(&self) -> &str {
2572        self.scan_strings_and_fence().3
2573    }
2574    #[inline]
2575    pub fn color_range(&self) -> &str {
2576        self.scan_strings_and_fence().4
2577    }
2578    #[inline]
2579    pub fn fence_fd(&self) -> i32 {
2580        self.scan_strings_and_fence().5
2581    }
2582
2583    /// Number of planes in the sequence. O(1) via cached `offsets[1]`.
2584    #[inline]
2585    pub fn num_planes(&self) -> u32 {
2586        rd_u32(self.buf.as_ref(), self.offsets[1])
2587    }
2588
2589    /// Collect all plane views by walking the CDR sequence. O(n_planes) via
2590    /// cached `offsets[1]` — does not rescan the colorimetry strings.
2591    pub fn planes(&self) -> Vec<CameraPlaneView<'_>> {
2592        let b = self.buf.as_ref();
2593        let count = rd_u32(b, self.offsets[1]) as usize;
2594        let mut c = CdrCursor::resume(b, self.offsets[1] + 4);
2595        (0..count)
2596            .map(|_| scan_plane_element(&mut c).expect("planes validated during from_cdr"))
2597            .collect()
2598    }
2599
2600    #[inline]
2601    pub fn as_cdr(&self) -> &[u8] {
2602        self.buf.as_ref()
2603    }
2604    pub fn to_cdr(&self) -> Vec<u8> {
2605        self.buf.as_ref().to_vec()
2606    }
2607}
2608
2609impl CameraFrame<&'static [u8]> {
2610    /// Parse and simultaneously collect plane views for the FFI layer,
2611    /// avoiding a second walk after `from_cdr`. Mirrors `Detect::from_cdr_collect_boxes`.
2612    pub(crate) fn from_cdr_collect_planes(
2613        buf: &'static [u8],
2614    ) -> Result<(Self, Vec<CameraPlaneView<'static>>), CdrError> {
2615        let header = Header::<&[u8]>::from_cdr(buf)?;
2616        let o0 = header.end_offset();
2617        let mut c = CdrCursor::resume(buf, o0);
2618        c.read_u64()?;
2619        c.read_u32()?;
2620        let width = c.read_u32()?;
2621        let height = c.read_u32()?;
2622        c.read_string()?;
2623        c.read_string()?;
2624        c.read_string()?;
2625        c.read_string()?;
2626        c.read_string()?;
2627        c.read_i32()?;
2628        let planes_pos = c.offset();
2629        let raw_count = c.read_u32()?;
2630        let count = c.check_seq_count(raw_count, 24)?;
2631        let mut planes = Vec::with_capacity(count);
2632        for _ in 0..count {
2633            let plane = scan_plane_element(&mut c)?;
2634            validate_plane(plane.fd, plane.size, plane.used, plane.data.len())?;
2635            planes.push(plane);
2636        }
2637
2638        if width == 0 || height == 0 {
2639            return Err(CdrError::InvalidHeader);
2640        }
2641
2642        Ok((
2643            CameraFrame {
2644                offsets: [o0, planes_pos],
2645                buf,
2646            },
2647            planes,
2648        ))
2649    }
2650}
2651
2652impl CameraFrame<Vec<u8>> {
2653    /// Build a new CameraFrame, serializing its fields into a fresh CDR buffer.
2654    ///
2655    /// Enforces the schema contracts:
2656    /// - `width > 0` and `height > 0`
2657    /// - `plane.used <= plane.size`
2658    /// - `plane.fd >= -1` (only -1 is a valid negative sentinel)
2659    /// - when `plane.fd >= 0`, `plane.data` must be empty
2660    /// - when `plane.fd == -1` (inlined), `plane.size as usize == plane.data.len()`
2661    #[deprecated(
2662        since = "3.2.0",
2663        note = "use CameraFrame::builder() for allocation-free buffer reuse; CameraFrame::new will be removed in 4.0"
2664    )]
2665    #[allow(clippy::too_many_arguments)]
2666    pub fn new(
2667        stamp: Time,
2668        frame_id: &str,
2669        seq: u64,
2670        pid: u32,
2671        width: u32,
2672        height: u32,
2673        format: &str,
2674        color_space: &str,
2675        color_transfer: &str,
2676        color_encoding: &str,
2677        color_range: &str,
2678        fence_fd: i32,
2679        planes: &[CameraPlaneView<'_>],
2680    ) -> Result<Self, CdrError> {
2681        if width == 0 || height == 0 {
2682            return Err(CdrError::InvalidHeader);
2683        }
2684        for p in planes {
2685            validate_plane(p.fd, p.size, p.used, p.data.len())?;
2686        }
2687
2688        let mut sizer = CdrSizer::new();
2689        Time::size_cdr(&mut sizer);
2690        sizer.size_string(frame_id);
2691        let o0 = sizer.offset();
2692        sizer.size_u64();
2693        sizer.size_u32();
2694        sizer.size_u32();
2695        sizer.size_u32();
2696        sizer.size_string(format);
2697        sizer.size_string(color_space);
2698        sizer.size_string(color_transfer);
2699        sizer.size_string(color_encoding);
2700        sizer.size_string(color_range);
2701        sizer.size_i32();
2702        let planes_pos = sizer.offset();
2703        sizer.size_u32();
2704        for p in planes {
2705            size_plane_element(&mut sizer, p.data.len());
2706        }
2707
2708        let mut buf = vec![0u8; sizer.size()];
2709        let mut w = CdrWriter::new(&mut buf)?;
2710        stamp.write_cdr(&mut w);
2711        w.write_string(frame_id);
2712        w.write_u64(seq);
2713        w.write_u32(pid);
2714        w.write_u32(width);
2715        w.write_u32(height);
2716        w.write_string(format);
2717        w.write_string(color_space);
2718        w.write_string(color_transfer);
2719        w.write_string(color_encoding);
2720        w.write_string(color_range);
2721        w.write_i32(fence_fd);
2722        w.write_u32(planes.len() as u32);
2723        for p in planes {
2724            write_plane_element(&mut w, p);
2725        }
2726        w.finish()?;
2727
2728        Ok(CameraFrame {
2729            offsets: [o0, planes_pos],
2730            buf,
2731        })
2732    }
2733
2734    pub fn into_cdr(self) -> Vec<u8> {
2735        self.buf
2736    }
2737
2738    /// Start a new `CameraFrameBuilder` with zero-valued defaults and
2739    /// `fence_fd = -1` (the "no fence" sentinel).
2740    ///
2741    /// Generic in `'a` so the compiler infers it from subsequent
2742    /// `.planes(...)` borrows rather than forcing `'static`.
2743    pub fn builder<'a>() -> CameraFrameBuilder<'a> {
2744        CameraFrameBuilder::new()
2745    }
2746}
2747
2748// ── CameraFrameBuilder<'a> ──────────────────────────────────────────
2749
2750/// Builder for `CameraFrame<Vec<u8>>` with buffer-reuse finalizers.
2751///
2752/// `planes` is borrowed from a caller-owned slice for the lifetime of the
2753/// builder. Each `CameraPlaneView` in that slice itself borrows its `data`
2754/// from caller memory — all borrows must remain valid until `build()`,
2755/// `encode_into_vec()`, or `encode_into_slice()` is called.
2756pub struct CameraFrameBuilder<'a> {
2757    stamp: Time,
2758    frame_id: std::borrow::Cow<'a, str>,
2759    seq: u64,
2760    pid: u32,
2761    width: u32,
2762    height: u32,
2763    format: std::borrow::Cow<'a, str>,
2764    color_space: std::borrow::Cow<'a, str>,
2765    color_transfer: std::borrow::Cow<'a, str>,
2766    color_encoding: std::borrow::Cow<'a, str>,
2767    color_range: std::borrow::Cow<'a, str>,
2768    fence_fd: i32,
2769    planes: &'a [CameraPlaneView<'a>],
2770}
2771
2772impl<'a> Default for CameraFrameBuilder<'a> {
2773    fn default() -> Self {
2774        Self {
2775            stamp: Time { sec: 0, nanosec: 0 },
2776            frame_id: std::borrow::Cow::Borrowed(""),
2777            seq: 0,
2778            pid: 0,
2779            width: 0,
2780            height: 0,
2781            format: std::borrow::Cow::Borrowed(""),
2782            color_space: std::borrow::Cow::Borrowed(""),
2783            color_transfer: std::borrow::Cow::Borrowed(""),
2784            color_encoding: std::borrow::Cow::Borrowed(""),
2785            color_range: std::borrow::Cow::Borrowed(""),
2786            fence_fd: -1,
2787            planes: &[],
2788        }
2789    }
2790}
2791
2792impl<'a> CameraFrameBuilder<'a> {
2793    pub fn new() -> Self {
2794        Self::default()
2795    }
2796
2797    pub fn stamp(&mut self, t: Time) -> &mut Self {
2798        self.stamp = t;
2799        self
2800    }
2801    pub fn frame_id(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
2802        self.frame_id = s.into();
2803        self
2804    }
2805    pub fn seq(&mut self, v: u64) -> &mut Self {
2806        self.seq = v;
2807        self
2808    }
2809    pub fn pid(&mut self, v: u32) -> &mut Self {
2810        self.pid = v;
2811        self
2812    }
2813    pub fn width(&mut self, v: u32) -> &mut Self {
2814        self.width = v;
2815        self
2816    }
2817    pub fn height(&mut self, v: u32) -> &mut Self {
2818        self.height = v;
2819        self
2820    }
2821    pub fn format(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
2822        self.format = s.into();
2823        self
2824    }
2825    pub fn color_space(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
2826        self.color_space = s.into();
2827        self
2828    }
2829    pub fn color_transfer(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
2830        self.color_transfer = s.into();
2831        self
2832    }
2833    pub fn color_encoding(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
2834        self.color_encoding = s.into();
2835        self
2836    }
2837    pub fn color_range(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
2838        self.color_range = s.into();
2839        self
2840    }
2841    pub fn fence_fd(&mut self, v: i32) -> &mut Self {
2842        self.fence_fd = v;
2843        self
2844    }
2845    pub fn planes(&mut self, p: &'a [CameraPlaneView<'a>]) -> &mut Self {
2846        self.planes = p;
2847        self
2848    }
2849
2850    fn validate(&self) -> Result<(), CdrError> {
2851        if self.width == 0 || self.height == 0 {
2852            return Err(CdrError::InvalidHeader);
2853        }
2854        for p in self.planes {
2855            validate_plane(p.fd, p.size, p.used, p.data.len())?;
2856        }
2857        Ok(())
2858    }
2859
2860    fn size(&self) -> usize {
2861        let mut s = CdrSizer::new();
2862        Time::size_cdr(&mut s);
2863        s.size_string(&self.frame_id);
2864        s.size_u64(); // seq
2865        s.size_u32(); // pid
2866        s.size_u32(); // width
2867        s.size_u32(); // height
2868        s.size_string(&self.format);
2869        s.size_string(&self.color_space);
2870        s.size_string(&self.color_transfer);
2871        s.size_string(&self.color_encoding);
2872        s.size_string(&self.color_range);
2873        s.size_i32(); // fence_fd
2874        s.size_u32(); // planes count
2875        for p in self.planes {
2876            size_plane_element(&mut s, p.data.len());
2877        }
2878        s.size()
2879    }
2880
2881    fn write_into(&self, buf: &mut [u8]) -> Result<(), CdrError> {
2882        let mut w = CdrWriter::new(buf)?;
2883        self.stamp.write_cdr(&mut w);
2884        w.write_string(&self.frame_id);
2885        w.write_u64(self.seq);
2886        w.write_u32(self.pid);
2887        w.write_u32(self.width);
2888        w.write_u32(self.height);
2889        w.write_string(&self.format);
2890        w.write_string(&self.color_space);
2891        w.write_string(&self.color_transfer);
2892        w.write_string(&self.color_encoding);
2893        w.write_string(&self.color_range);
2894        w.write_i32(self.fence_fd);
2895        w.write_u32(self.planes.len() as u32);
2896        for p in self.planes {
2897            write_plane_element(&mut w, p);
2898        }
2899        w.finish()
2900    }
2901
2902    pub fn build(&self) -> Result<CameraFrame<Vec<u8>>, CdrError> {
2903        self.validate()?;
2904        let mut buf = vec![0u8; self.size()];
2905        self.write_into(&mut buf)?;
2906        CameraFrame::from_cdr(buf)
2907    }
2908
2909    pub fn encode_into_vec(&self, buf: &mut Vec<u8>) -> Result<(), CdrError> {
2910        self.validate()?;
2911        buf.resize(self.size(), 0);
2912        self.write_into(buf)
2913    }
2914
2915    pub fn encode_into_slice(&self, buf: &mut [u8]) -> Result<usize, CdrError> {
2916        self.validate()?;
2917        let need = self.size();
2918        if buf.len() < need {
2919            return Err(CdrError::BufferTooShort {
2920                need,
2921                have: buf.len(),
2922            });
2923        }
2924        self.write_into(&mut buf[..need])?;
2925        Ok(need)
2926    }
2927}
2928
2929impl<B: AsRef<[u8]> + AsMut<[u8]>> CameraFrame<B> {
2930    pub fn set_stamp(&mut self, t: Time) -> Result<(), CdrError> {
2931        let b = self.buf.as_mut();
2932        wr_i32(b, CDR_HEADER_SIZE, t.sec)?;
2933        wr_u32(b, CDR_HEADER_SIZE + 4, t.nanosec)
2934    }
2935
2936    pub fn set_seq(&mut self, v: u64) -> Result<(), CdrError> {
2937        let p = cdr_align(self.offsets[0], 8);
2938        wr_u64(self.buf.as_mut(), p, v)
2939    }
2940
2941    pub fn set_pid(&mut self, v: u32) -> Result<(), CdrError> {
2942        let p = cdr_align(self.offsets[0], 8) + 8;
2943        wr_u32(self.buf.as_mut(), p, v)
2944    }
2945
2946    pub fn set_width(&mut self, v: u32) -> Result<(), CdrError> {
2947        let p = cdr_align(self.offsets[0], 8) + 12;
2948        wr_u32(self.buf.as_mut(), p, v)
2949    }
2950
2951    pub fn set_height(&mut self, v: u32) -> Result<(), CdrError> {
2952        let p = cdr_align(self.offsets[0], 8) + 16;
2953        wr_u32(self.buf.as_mut(), p, v)
2954    }
2955
2956    /// Update `fence_fd` in place.
2957    ///
2958    /// This field follows five variable-length colorimetry strings, so the
2959    /// in-place write must re-walk those strings to find the fence position
2960    /// (same cost as the getter). Scalar fields before the strings remain
2961    /// O(1) writes via constant offsets.
2962    pub fn set_fence_fd(&mut self, v: i32) -> Result<(), CdrError> {
2963        let strings_start = cdr_align(self.offsets[0], 8) + 20;
2964        let b = self.buf.as_ref();
2965        let (_, p1) = rd_string(b, strings_start);
2966        let (_, p2) = rd_string(b, p1);
2967        let (_, p3) = rd_string(b, p2);
2968        let (_, p4) = rd_string(b, p3);
2969        let (_, p5) = rd_string(b, p4);
2970        let pos = align(p5, 4);
2971        wr_i32(self.buf.as_mut(), pos, v)
2972    }
2973}
2974
2975// ── Model<B> — edgefirst_msgs/msg/Model ─────────────────────────────
2976//
2977// CDR layout: Header → offsets[0],
2978//   input_time(Duration), model_time(Duration),
2979//   output_time(Duration), decode_time(Duration),
2980//   boxes(Vec<Box>) → offsets[1], masks(Vec<Mask>) → offsets[2]
2981
2982pub struct Model<B> {
2983    buf: B,
2984    offsets: [usize; 3],
2985}
2986
2987impl<B> Model<B> {
2988    /// Convert the buffer type without re-parsing the offset table.
2989    #[inline]
2990    pub fn map_buffer<C>(self, f: impl FnOnce(B) -> C) -> Model<C> {
2991        Model {
2992            buf: f(self.buf),
2993            offsets: self.offsets,
2994        }
2995    }
2996}
2997
2998impl<B: AsRef<[u8]>> Model<B> {
2999    pub fn from_cdr(buf: B) -> Result<Self, CdrError> {
3000        let header = Header::<&[u8]>::from_cdr(buf.as_ref())?;
3001        let o0 = header.end_offset();
3002        let mut c = CdrCursor::resume(buf.as_ref(), o0);
3003        Duration::read_cdr(&mut c)?;
3004        Duration::read_cdr(&mut c)?;
3005        Duration::read_cdr(&mut c)?;
3006        Duration::read_cdr(&mut c)?;
3007        let raw_boxes = c.read_u32()?;
3008        let boxes_count = c.check_seq_count(raw_boxes, 24)?;
3009        for _ in 0..boxes_count {
3010            scan_box_element(&mut c)?;
3011        }
3012        let o1 = c.offset();
3013        let raw_masks = c.read_u32()?;
3014        let masks_count = c.check_seq_count(raw_masks, 13)?;
3015        for _ in 0..masks_count {
3016            scan_mask_element(&mut c)?;
3017        }
3018        let o2 = c.offset();
3019        Ok(Model {
3020            offsets: [o0, o1, o2],
3021            buf,
3022        })
3023    }
3024
3025    #[inline]
3026    /// Returns a `Header` view by re-parsing the CDR buffer prefix.
3027    /// Prefer `stamp()` / `frame_id()` for direct O(1) field access.
3028    pub fn header(&self) -> Header<&[u8]> {
3029        Header::from_cdr(self.buf.as_ref()).expect("header bytes validated during from_cdr")
3030    }
3031    #[inline]
3032    pub fn stamp(&self) -> Time {
3033        rd_time(self.buf.as_ref(), CDR_HEADER_SIZE)
3034    }
3035    #[inline]
3036    pub fn frame_id(&self) -> &str {
3037        rd_string(self.buf.as_ref(), CDR_HEADER_SIZE + 8).0
3038    }
3039
3040    pub fn input_time(&self) -> Duration {
3041        rd_duration(self.buf.as_ref(), align(self.offsets[0], 4))
3042    }
3043
3044    pub fn model_time(&self) -> Duration {
3045        rd_duration(self.buf.as_ref(), align(self.offsets[0], 4) + 8)
3046    }
3047
3048    pub fn output_time(&self) -> Duration {
3049        rd_duration(self.buf.as_ref(), align(self.offsets[0], 4) + 16)
3050    }
3051
3052    pub fn decode_time(&self) -> Duration {
3053        rd_duration(self.buf.as_ref(), align(self.offsets[0], 4) + 24)
3054    }
3055
3056    pub fn boxes_len(&self) -> u32 {
3057        rd_u32(self.buf.as_ref(), align(self.offsets[0], 4) + 32)
3058    }
3059
3060    pub fn boxes(&self) -> Vec<DetectBoxView<'_>> {
3061        let b = self.buf.as_ref();
3062        let p = align(self.offsets[0], 4) + 32;
3063        let count = rd_u32(b, p) as usize;
3064        let mut c = CdrCursor::resume(b, p + 4);
3065        (0..count)
3066            .map(|_| scan_box_element(&mut c).expect("box elements validated during from_cdr"))
3067            .collect()
3068    }
3069
3070    pub fn masks_len(&self) -> u32 {
3071        rd_u32(self.buf.as_ref(), align(self.offsets[1], 4))
3072    }
3073
3074    pub fn masks(&self) -> Vec<MaskView<'_>> {
3075        let b = self.buf.as_ref();
3076        let p = align(self.offsets[1], 4);
3077        let count = rd_u32(b, p) as usize;
3078        let mut c = CdrCursor::resume(b, p + 4);
3079        (0..count)
3080            .map(|_| scan_mask_element(&mut c).expect("mask elements validated during from_cdr"))
3081            .collect()
3082    }
3083
3084    #[inline]
3085    pub fn as_cdr(&self) -> &[u8] {
3086        self.buf.as_ref()
3087    }
3088    pub fn to_cdr(&self) -> Vec<u8> {
3089        self.buf.as_ref().to_vec()
3090    }
3091}
3092
3093impl Model<&'static [u8]> {
3094    /// Parse a Model message and simultaneously collect the box and mask views
3095    /// encountered during validation, avoiding a second parse pass in the
3096    /// FFI layer.
3097    ///
3098    /// The views in the returned `Vec`s naturally have `'static` lifetime
3099    /// because they borrow from the `&'static [u8]` buffer. No unsafe
3100    /// transmute is required.
3101    ///
3102    /// This is a crate-private helper used by the FFI layer to avoid the
3103    /// cost of a second walk in `inner.boxes()` / `inner.masks()` after
3104    /// `from_cdr`.
3105    pub(crate) fn from_cdr_collect_children(
3106        buf: &'static [u8],
3107    ) -> Result<(Self, Vec<DetectBoxView<'static>>, Vec<MaskView<'static>>), CdrError> {
3108        let header = Header::<&[u8]>::from_cdr(buf)?;
3109        let o0 = header.end_offset();
3110        let mut c = CdrCursor::resume(buf, o0);
3111        Duration::read_cdr(&mut c)?;
3112        Duration::read_cdr(&mut c)?;
3113        Duration::read_cdr(&mut c)?;
3114        Duration::read_cdr(&mut c)?;
3115        let raw_boxes = c.read_u32()?;
3116        let boxes_count = c.check_seq_count(raw_boxes, 24)?;
3117        let mut box_views = Vec::with_capacity(boxes_count);
3118        for _ in 0..boxes_count {
3119            box_views.push(scan_box_element(&mut c)?);
3120        }
3121        let o1 = c.offset();
3122        let raw_masks = c.read_u32()?;
3123        let masks_count = c.check_seq_count(raw_masks, 13)?;
3124        let mut mask_views = Vec::with_capacity(masks_count);
3125        for _ in 0..masks_count {
3126            mask_views.push(scan_mask_element(&mut c)?);
3127        }
3128        let o2 = c.offset();
3129        Ok((
3130            Model {
3131                offsets: [o0, o1, o2],
3132                buf,
3133            },
3134            box_views,
3135            mask_views,
3136        ))
3137    }
3138}
3139
3140impl Model<Vec<u8>> {
3141    #[deprecated(
3142        since = "3.2.0",
3143        note = "use Model::builder() for allocation-free buffer reuse; Model::new will be removed in 4.0"
3144    )]
3145    #[allow(clippy::too_many_arguments)]
3146    pub fn new(
3147        stamp: Time,
3148        frame_id: &str,
3149        input_time: Duration,
3150        model_time: Duration,
3151        output_time: Duration,
3152        decode_time: Duration,
3153        boxes: &[DetectBoxView<'_>],
3154        masks: &[MaskView<'_>],
3155    ) -> Result<Self, CdrError> {
3156        let mut sizer = CdrSizer::new();
3157        Time::size_cdr(&mut sizer);
3158        sizer.size_string(frame_id);
3159        let o0 = sizer.offset();
3160        Duration::size_cdr(&mut sizer);
3161        Duration::size_cdr(&mut sizer);
3162        Duration::size_cdr(&mut sizer);
3163        Duration::size_cdr(&mut sizer);
3164        sizer.size_u32();
3165        for b in boxes {
3166            size_box_element(&mut sizer, b.label, b.track_id);
3167        }
3168        let o1 = sizer.offset();
3169        sizer.size_u32();
3170        for m in masks {
3171            size_mask_element(&mut sizer, m.encoding, m.mask.len());
3172        }
3173        let o2 = sizer.offset();
3174
3175        let mut buf = vec![0u8; sizer.size()];
3176        let mut w = CdrWriter::new(&mut buf)?;
3177        stamp.write_cdr(&mut w);
3178        w.write_string(frame_id);
3179        input_time.write_cdr(&mut w);
3180        model_time.write_cdr(&mut w);
3181        output_time.write_cdr(&mut w);
3182        decode_time.write_cdr(&mut w);
3183        w.write_u32(boxes.len() as u32);
3184        for b in boxes {
3185            write_box_element(&mut w, b);
3186        }
3187        w.write_u32(masks.len() as u32);
3188        for m in masks {
3189            write_mask_element(&mut w, m);
3190        }
3191        w.finish()?;
3192
3193        Ok(Model {
3194            offsets: [o0, o1, o2],
3195            buf,
3196        })
3197    }
3198
3199    pub fn into_cdr(self) -> Vec<u8> {
3200        self.buf
3201    }
3202
3203    /// Start a new `ModelBuilder` with zero-valued defaults.
3204    pub fn builder<'a>() -> ModelBuilder<'a> {
3205        ModelBuilder::new()
3206    }
3207}
3208
3209// ── ModelBuilder<'a> ────────────────────────────────────────────────
3210
3211/// Builder for `Model<Vec<u8>>` with buffer-reuse finalizers.
3212///
3213/// `boxes` and `masks` are borrowed from caller-owned slices. Each view
3214/// inside those slices itself borrows strings/byte-data from caller
3215/// memory — all borrows must remain valid until `build()`,
3216/// `encode_into_vec()`, or `encode_into_slice()` is called.
3217pub struct ModelBuilder<'a> {
3218    stamp: Time,
3219    frame_id: std::borrow::Cow<'a, str>,
3220    input_time: Duration,
3221    model_time: Duration,
3222    output_time: Duration,
3223    decode_time: Duration,
3224    boxes: &'a [DetectBoxView<'a>],
3225    masks: &'a [MaskView<'a>],
3226}
3227
3228impl<'a> Default for ModelBuilder<'a> {
3229    fn default() -> Self {
3230        Self {
3231            stamp: Time { sec: 0, nanosec: 0 },
3232            frame_id: std::borrow::Cow::Borrowed(""),
3233            input_time: Duration { sec: 0, nanosec: 0 },
3234            model_time: Duration { sec: 0, nanosec: 0 },
3235            output_time: Duration { sec: 0, nanosec: 0 },
3236            decode_time: Duration { sec: 0, nanosec: 0 },
3237            boxes: &[],
3238            masks: &[],
3239        }
3240    }
3241}
3242
3243impl<'a> ModelBuilder<'a> {
3244    pub fn new() -> Self {
3245        Self::default()
3246    }
3247
3248    pub fn stamp(&mut self, t: Time) -> &mut Self {
3249        self.stamp = t;
3250        self
3251    }
3252    pub fn frame_id(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
3253        self.frame_id = s.into();
3254        self
3255    }
3256    pub fn input_time(&mut self, d: Duration) -> &mut Self {
3257        self.input_time = d;
3258        self
3259    }
3260    pub fn model_time(&mut self, d: Duration) -> &mut Self {
3261        self.model_time = d;
3262        self
3263    }
3264    pub fn output_time(&mut self, d: Duration) -> &mut Self {
3265        self.output_time = d;
3266        self
3267    }
3268    pub fn decode_time(&mut self, d: Duration) -> &mut Self {
3269        self.decode_time = d;
3270        self
3271    }
3272    pub fn boxes(&mut self, b: &'a [DetectBoxView<'a>]) -> &mut Self {
3273        self.boxes = b;
3274        self
3275    }
3276    pub fn masks(&mut self, m: &'a [MaskView<'a>]) -> &mut Self {
3277        self.masks = m;
3278        self
3279    }
3280
3281    fn size(&self) -> usize {
3282        let mut s = CdrSizer::new();
3283        Time::size_cdr(&mut s);
3284        s.size_string(&self.frame_id);
3285        Duration::size_cdr(&mut s);
3286        Duration::size_cdr(&mut s);
3287        Duration::size_cdr(&mut s);
3288        Duration::size_cdr(&mut s);
3289        s.size_u32();
3290        for b in self.boxes {
3291            size_box_element(&mut s, b.label, b.track_id);
3292        }
3293        s.size_u32();
3294        for m in self.masks {
3295            size_mask_element(&mut s, m.encoding, m.mask.len());
3296        }
3297        s.size()
3298    }
3299
3300    fn write_into(&self, buf: &mut [u8]) -> Result<(), CdrError> {
3301        let mut w = CdrWriter::new(buf)?;
3302        self.stamp.write_cdr(&mut w);
3303        w.write_string(&self.frame_id);
3304        self.input_time.write_cdr(&mut w);
3305        self.model_time.write_cdr(&mut w);
3306        self.output_time.write_cdr(&mut w);
3307        self.decode_time.write_cdr(&mut w);
3308        w.write_u32(self.boxes.len() as u32);
3309        for b in self.boxes {
3310            write_box_element(&mut w, b);
3311        }
3312        w.write_u32(self.masks.len() as u32);
3313        for m in self.masks {
3314            write_mask_element(&mut w, m);
3315        }
3316        w.finish()
3317    }
3318
3319    pub fn build(&self) -> Result<Model<Vec<u8>>, CdrError> {
3320        let mut buf = vec![0u8; self.size()];
3321        self.write_into(&mut buf)?;
3322        Model::from_cdr(buf)
3323    }
3324
3325    pub fn encode_into_vec(&self, buf: &mut Vec<u8>) -> Result<(), CdrError> {
3326        buf.resize(self.size(), 0);
3327        self.write_into(buf)
3328    }
3329
3330    pub fn encode_into_slice(&self, buf: &mut [u8]) -> Result<usize, CdrError> {
3331        let need = self.size();
3332        if buf.len() < need {
3333            return Err(CdrError::BufferTooShort {
3334                need,
3335                have: buf.len(),
3336            });
3337        }
3338        self.write_into(&mut buf[..need])?;
3339        Ok(need)
3340    }
3341}
3342
3343impl<B: AsRef<[u8]> + AsMut<[u8]>> Model<B> {
3344    pub fn set_stamp(&mut self, t: Time) -> Result<(), CdrError> {
3345        let b = self.buf.as_mut();
3346        wr_i32(b, CDR_HEADER_SIZE, t.sec)?;
3347        wr_u32(b, CDR_HEADER_SIZE + 4, t.nanosec)
3348    }
3349
3350    pub fn set_input_time(&mut self, d: Duration) -> Result<(), CdrError> {
3351        let b = self.buf.as_mut();
3352        let p = align(self.offsets[0], 4);
3353        wr_i32(b, p, d.sec)?;
3354        wr_u32(b, p + 4, d.nanosec)
3355    }
3356
3357    pub fn set_model_time(&mut self, d: Duration) -> Result<(), CdrError> {
3358        let b = self.buf.as_mut();
3359        let p = align(self.offsets[0], 4) + 8;
3360        wr_i32(b, p, d.sec)?;
3361        wr_u32(b, p + 4, d.nanosec)
3362    }
3363
3364    pub fn set_output_time(&mut self, d: Duration) -> Result<(), CdrError> {
3365        let b = self.buf.as_mut();
3366        let p = align(self.offsets[0], 4) + 16;
3367        wr_i32(b, p, d.sec)?;
3368        wr_u32(b, p + 4, d.nanosec)
3369    }
3370
3371    pub fn set_decode_time(&mut self, d: Duration) -> Result<(), CdrError> {
3372        let b = self.buf.as_mut();
3373        let p = align(self.offsets[0], 4) + 24;
3374        wr_i32(b, p, d.sec)?;
3375        wr_u32(b, p + 4, d.nanosec)
3376    }
3377}
3378
3379// ── ModelInfo<B> — edgefirst_msgs/msg/ModelInfo ─────────────────────
3380//
3381// CDR layout: Header → offsets[0],
3382//   input_shape(Vec<u32>) → offsets[1], input_type(u8),
3383//   output_shape(Vec<u32>) → offsets[2], output_type(u8),
3384//   labels(Vec<String>) → offsets[3],
3385//   model_type(string) → offsets[4], model_format(string) → offsets[5],
3386//   model_name(string) → offsets[6]
3387
3388pub struct ModelInfo<B> {
3389    buf: B,
3390    offsets: [usize; 6],
3391}
3392
3393impl<B> ModelInfo<B> {
3394    /// Convert the buffer type without re-parsing the offset table.
3395    #[inline]
3396    pub fn map_buffer<C>(self, f: impl FnOnce(B) -> C) -> ModelInfo<C> {
3397        ModelInfo {
3398            buf: f(self.buf),
3399            offsets: self.offsets,
3400        }
3401    }
3402}
3403
3404impl<B: AsRef<[u8]>> ModelInfo<B> {
3405    pub fn from_cdr(buf: B) -> Result<Self, CdrError> {
3406        let header = Header::<&[u8]>::from_cdr(buf.as_ref())?;
3407        let o0 = header.end_offset();
3408        let mut c = CdrCursor::resume(buf.as_ref(), o0);
3409        let is_count = c.read_u32()? as usize;
3410        c.skip_seq_4(is_count)?;
3411        let o1 = c.offset();
3412        c.read_u8()?; // input_type
3413        let os_count = c.read_u32()? as usize;
3414        c.skip_seq_4(os_count)?;
3415        let o2 = c.offset();
3416        c.read_u8()?; // output_type
3417        let raw_lab = c.read_u32()?;
3418        let lab_count = c.check_seq_count(raw_lab, 5)?;
3419        for _ in 0..lab_count {
3420            c.read_string()?;
3421        }
3422        let o3 = c.offset();
3423        let _ = c.read_string()?;
3424        let o4 = c.offset();
3425        let _ = c.read_string()?;
3426        let o5 = c.offset();
3427        let _ = c.read_string()?;
3428        Ok(ModelInfo {
3429            offsets: [o0, o1, o2, o3, o4, o5],
3430            buf,
3431        })
3432    }
3433
3434    #[inline]
3435    /// Returns a `Header` view by re-parsing the CDR buffer prefix.
3436    /// Prefer `stamp()` / `frame_id()` for direct O(1) field access.
3437    pub fn header(&self) -> Header<&[u8]> {
3438        Header::from_cdr(self.buf.as_ref()).expect("header bytes validated during from_cdr")
3439    }
3440    #[inline]
3441    pub fn stamp(&self) -> Time {
3442        rd_time(self.buf.as_ref(), CDR_HEADER_SIZE)
3443    }
3444    #[inline]
3445    pub fn frame_id(&self) -> &str {
3446        rd_string(self.buf.as_ref(), CDR_HEADER_SIZE + 8).0
3447    }
3448
3449    pub fn input_shape(&self) -> &[u32] {
3450        let b = self.buf.as_ref();
3451        let p = align(self.offsets[0], 4);
3452        let count = rd_u32(b, p) as usize;
3453        rd_slice_u32(b, p + 4, count)
3454    }
3455
3456    pub fn input_type(&self) -> u8 {
3457        rd_u8(self.buf.as_ref(), self.offsets[1])
3458    }
3459
3460    pub fn output_shape(&self) -> &[u32] {
3461        let b = self.buf.as_ref();
3462        let p = align(self.offsets[1] + 1, 4);
3463        let count = rd_u32(b, p) as usize;
3464        rd_slice_u32(b, p + 4, count)
3465    }
3466
3467    pub fn output_type(&self) -> u8 {
3468        rd_u8(self.buf.as_ref(), self.offsets[2])
3469    }
3470
3471    pub fn labels(&self) -> Vec<&str> {
3472        let mut c = CdrCursor::resume(self.buf.as_ref(), self.offsets[2] + 1);
3473        let count = c.read_u32().expect("label data validated during from_cdr") as usize;
3474        (0..count)
3475            .map(|_| {
3476                c.read_string()
3477                    .expect("label data validated during from_cdr")
3478            })
3479            .collect()
3480    }
3481
3482    pub fn labels_len(&self) -> u32 {
3483        let mut c = CdrCursor::resume(self.buf.as_ref(), self.offsets[2] + 1);
3484        c.read_u32().expect("label data validated during from_cdr")
3485    }
3486
3487    #[inline]
3488    pub fn model_type(&self) -> &str {
3489        rd_string(self.buf.as_ref(), self.offsets[3]).0
3490    }
3491    #[inline]
3492    pub fn model_format(&self) -> &str {
3493        rd_string(self.buf.as_ref(), self.offsets[4]).0
3494    }
3495    #[inline]
3496    pub fn model_name(&self) -> &str {
3497        rd_string(self.buf.as_ref(), self.offsets[5]).0
3498    }
3499
3500    #[inline]
3501    pub fn as_cdr(&self) -> &[u8] {
3502        self.buf.as_ref()
3503    }
3504    pub fn to_cdr(&self) -> Vec<u8> {
3505        self.buf.as_ref().to_vec()
3506    }
3507}
3508
3509impl ModelInfo<Vec<u8>> {
3510    #[deprecated(
3511        since = "3.2.0",
3512        note = "use ModelInfo::builder() for allocation-free buffer reuse; ModelInfo::new will be removed in 4.0"
3513    )]
3514    #[allow(clippy::too_many_arguments)]
3515    pub fn new(
3516        stamp: Time,
3517        frame_id: &str,
3518        input_shape: &[u32],
3519        input_type: u8,
3520        output_shape: &[u32],
3521        output_type: u8,
3522        labels: &[&str],
3523        model_type: &str,
3524        model_format: &str,
3525        model_name: &str,
3526    ) -> Result<Self, CdrError> {
3527        let mut sizer = CdrSizer::new();
3528        Time::size_cdr(&mut sizer);
3529        sizer.size_string(frame_id);
3530        let o0 = sizer.offset();
3531        sizer.size_u32();
3532        sizer.size_seq_4(input_shape.len());
3533        let o1 = sizer.offset();
3534        sizer.size_u8();
3535        sizer.size_u32();
3536        sizer.size_seq_4(output_shape.len());
3537        let o2 = sizer.offset();
3538        sizer.size_u8();
3539        sizer.size_u32();
3540        for l in labels {
3541            sizer.size_string(l);
3542        }
3543        let o3 = sizer.offset();
3544        sizer.size_string(model_type);
3545        let o4 = sizer.offset();
3546        sizer.size_string(model_format);
3547        let o5 = sizer.offset();
3548        sizer.size_string(model_name);
3549
3550        let mut buf = vec![0u8; sizer.size()];
3551        let mut w = CdrWriter::new(&mut buf)?;
3552        stamp.write_cdr(&mut w);
3553        w.write_string(frame_id);
3554        w.write_u32(input_shape.len() as u32);
3555        w.write_slice_u32(input_shape);
3556        w.write_u8(input_type);
3557        w.write_u32(output_shape.len() as u32);
3558        w.write_slice_u32(output_shape);
3559        w.write_u8(output_type);
3560        w.write_u32(labels.len() as u32);
3561        for l in labels {
3562            w.write_string(l);
3563        }
3564        w.write_string(model_type);
3565        w.write_string(model_format);
3566        w.write_string(model_name);
3567        w.finish()?;
3568
3569        Ok(ModelInfo {
3570            offsets: [o0, o1, o2, o3, o4, o5],
3571            buf,
3572        })
3573    }
3574
3575    pub fn into_cdr(self) -> Vec<u8> {
3576        self.buf
3577    }
3578
3579    /// Start a new `ModelInfoBuilder` with zero-valued defaults.
3580    pub fn builder<'a>() -> ModelInfoBuilder<'a> {
3581        ModelInfoBuilder::new()
3582    }
3583}
3584
3585// ── ModelInfoBuilder<'a> ────────────────────────────────────────────
3586
3587/// Builder for `ModelInfo<Vec<u8>>` with buffer-reuse finalizers.
3588///
3589/// `labels` is borrowed as `&'a [&'a str]` so string literals or caller-
3590/// owned string slices flow through without copy.
3591pub struct ModelInfoBuilder<'a> {
3592    stamp: Time,
3593    frame_id: std::borrow::Cow<'a, str>,
3594    input_shape: &'a [u32],
3595    input_type: u8,
3596    output_shape: &'a [u32],
3597    output_type: u8,
3598    labels: &'a [&'a str],
3599    model_type: std::borrow::Cow<'a, str>,
3600    model_format: std::borrow::Cow<'a, str>,
3601    model_name: std::borrow::Cow<'a, str>,
3602}
3603
3604impl<'a> Default for ModelInfoBuilder<'a> {
3605    fn default() -> Self {
3606        Self {
3607            stamp: Time { sec: 0, nanosec: 0 },
3608            frame_id: std::borrow::Cow::Borrowed(""),
3609            input_shape: &[],
3610            input_type: 0,
3611            output_shape: &[],
3612            output_type: 0,
3613            labels: &[],
3614            model_type: std::borrow::Cow::Borrowed(""),
3615            model_format: std::borrow::Cow::Borrowed(""),
3616            model_name: std::borrow::Cow::Borrowed(""),
3617        }
3618    }
3619}
3620
3621impl<'a> ModelInfoBuilder<'a> {
3622    pub fn new() -> Self {
3623        Self::default()
3624    }
3625
3626    pub fn stamp(&mut self, t: Time) -> &mut Self {
3627        self.stamp = t;
3628        self
3629    }
3630    pub fn frame_id(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
3631        self.frame_id = s.into();
3632        self
3633    }
3634    pub fn input_shape(&mut self, v: &'a [u32]) -> &mut Self {
3635        self.input_shape = v;
3636        self
3637    }
3638    pub fn input_type(&mut self, v: u8) -> &mut Self {
3639        self.input_type = v;
3640        self
3641    }
3642    pub fn output_shape(&mut self, v: &'a [u32]) -> &mut Self {
3643        self.output_shape = v;
3644        self
3645    }
3646    pub fn output_type(&mut self, v: u8) -> &mut Self {
3647        self.output_type = v;
3648        self
3649    }
3650    pub fn labels(&mut self, v: &'a [&'a str]) -> &mut Self {
3651        self.labels = v;
3652        self
3653    }
3654    pub fn model_type(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
3655        self.model_type = s.into();
3656        self
3657    }
3658    pub fn model_format(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
3659        self.model_format = s.into();
3660        self
3661    }
3662    pub fn model_name(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
3663        self.model_name = s.into();
3664        self
3665    }
3666
3667    fn size(&self) -> usize {
3668        let mut s = CdrSizer::new();
3669        Time::size_cdr(&mut s);
3670        s.size_string(&self.frame_id);
3671        s.size_u32();
3672        s.size_seq_4(self.input_shape.len());
3673        s.size_u8();
3674        s.size_u32();
3675        s.size_seq_4(self.output_shape.len());
3676        s.size_u8();
3677        s.size_u32();
3678        for l in self.labels {
3679            s.size_string(l);
3680        }
3681        s.size_string(&self.model_type);
3682        s.size_string(&self.model_format);
3683        s.size_string(&self.model_name);
3684        s.size()
3685    }
3686
3687    fn write_into(&self, buf: &mut [u8]) -> Result<(), CdrError> {
3688        let mut w = CdrWriter::new(buf)?;
3689        self.stamp.write_cdr(&mut w);
3690        w.write_string(&self.frame_id);
3691        w.write_u32(self.input_shape.len() as u32);
3692        w.write_slice_u32(self.input_shape);
3693        w.write_u8(self.input_type);
3694        w.write_u32(self.output_shape.len() as u32);
3695        w.write_slice_u32(self.output_shape);
3696        w.write_u8(self.output_type);
3697        w.write_u32(self.labels.len() as u32);
3698        for l in self.labels {
3699            w.write_string(l);
3700        }
3701        w.write_string(&self.model_type);
3702        w.write_string(&self.model_format);
3703        w.write_string(&self.model_name);
3704        w.finish()
3705    }
3706
3707    pub fn build(&self) -> Result<ModelInfo<Vec<u8>>, CdrError> {
3708        let mut buf = vec![0u8; self.size()];
3709        self.write_into(&mut buf)?;
3710        ModelInfo::from_cdr(buf)
3711    }
3712
3713    pub fn encode_into_vec(&self, buf: &mut Vec<u8>) -> Result<(), CdrError> {
3714        buf.resize(self.size(), 0);
3715        self.write_into(buf)
3716    }
3717
3718    pub fn encode_into_slice(&self, buf: &mut [u8]) -> Result<usize, CdrError> {
3719        let need = self.size();
3720        if buf.len() < need {
3721            return Err(CdrError::BufferTooShort {
3722                need,
3723                have: buf.len(),
3724            });
3725        }
3726        self.write_into(&mut buf[..need])?;
3727        Ok(need)
3728    }
3729}
3730
3731impl<B: AsRef<[u8]> + AsMut<[u8]>> ModelInfo<B> {
3732    pub fn set_stamp(&mut self, t: Time) -> Result<(), CdrError> {
3733        let b = self.buf.as_mut();
3734        wr_i32(b, CDR_HEADER_SIZE, t.sec)?;
3735        wr_u32(b, CDR_HEADER_SIZE + 4, t.nanosec)
3736    }
3737
3738    pub fn set_input_type(&mut self, v: u8) -> Result<(), CdrError> {
3739        wr_u8(self.buf.as_mut(), self.offsets[1], v)
3740    }
3741
3742    pub fn set_output_type(&mut self, v: u8) -> Result<(), CdrError> {
3743        wr_u8(self.buf.as_mut(), self.offsets[2], v)
3744    }
3745}
3746
3747// ── Vibration<B> ────────────────────────────────────────────────────
3748//
3749// CDR layout: Header → pad to 8 → offsets[0] (Vector3 vibration start),
3750//   Vector3 vibration (24 bytes),
3751//   float32 band_lower_hz, float32 band_upper_hz,
3752//   uint8 measurement_type, uint8 unit,
3753//   pad to 4 → uint32 count + uint32[] clipping.
3754//
3755// offsets contains a single cached value:
3756//   offsets[0] = aligned start of `vibration`.
3757//
3758// All remaining fields are accessed at fixed compile-time-constant
3759// deltas from offsets[0] (including the clipping sequence count/data)
3760// because fields are ordered by descending alignment, sidestepping the
3761// EDGEAI-1243 class of bug entirely.
3762
3763/// `measurement_type` enum values for [`Vibration`].
3764pub mod vibration_measurement {
3765    pub const UNKNOWN: u8 = 0;
3766    pub const RMS: u8 = 1;
3767    pub const PEAK: u8 = 2;
3768    pub const PEAK_TO_PEAK: u8 = 3;
3769}
3770
3771/// `unit` enum values for [`Vibration`].
3772pub mod vibration_unit {
3773    pub const UNKNOWN: u8 = 0;
3774    pub const ACCEL_M_PER_S2: u8 = 1;
3775    pub const ACCEL_G: u8 = 2;
3776    pub const VELOCITY_MM_PER_S: u8 = 3;
3777    pub const DISPLACEMENT_UM: u8 = 4;
3778    pub const VELOCITY_IN_PER_S: u8 = 5;
3779    pub const DISPLACEMENT_MIL: u8 = 6;
3780}
3781
3782pub struct Vibration<B> {
3783    buf: B,
3784    // offsets[0]: Vector3 `vibration` start (8-aligned after Header).
3785    //
3786    // Fields laid out by descending alignment (Vector3 → f32 → u8 → seq),
3787    // so every subsequent field sits at a compile-time-constant delta
3788    // from offsets[0]:
3789    //
3790    //   vibration           offsets[0]       (24 B)
3791    //   band_lower_hz       offsets[0] + 24  (f32)
3792    //   band_upper_hz       offsets[0] + 28  (f32)
3793    //   measurement_type    offsets[0] + 32  (u8)
3794    //   unit                offsets[0] + 33  (u8)
3795    //   [ 2 bytes constant pad to 4-align ]
3796    //   clipping seq-count  offsets[0] + 36  (u32)
3797    //
3798    // The 2-byte pad between `unit` and `clipping` is invariant because
3799    // offsets[0] is 8-aligned (hence 4-aligned relative to CDR payload
3800    // start). No position-dependent padding anywhere.
3801    offsets: [usize; 1],
3802}
3803
3804impl<B> Vibration<B> {
3805    /// Convert the buffer type without re-parsing the offset table.
3806    #[inline]
3807    pub fn map_buffer<C>(self, f: impl FnOnce(B) -> C) -> Vibration<C> {
3808        Vibration {
3809            buf: f(self.buf),
3810            offsets: self.offsets,
3811        }
3812    }
3813}
3814
3815impl<B: AsRef<[u8]>> Vibration<B> {
3816    pub fn from_cdr(buf: B) -> Result<Self, CdrError> {
3817        use crate::geometry_msgs::Vector3;
3818        let header = crate::std_msgs::Header::<&[u8]>::from_cdr(buf.as_ref())?;
3819        let pre = header.end_offset();
3820        let mut c = CdrCursor::resume(buf.as_ref(), pre);
3821        c.align(8);
3822        let o0 = c.offset();
3823        Vector3::read_cdr(&mut c)?;
3824        c.read_f32()?; // band_lower_hz
3825        c.read_f32()?; // band_upper_hz
3826        c.read_u8()?; // measurement_type
3827        c.read_u8()?; // unit
3828        c.align(4);
3829        // u32 = 4 bytes each; hardening check against pathological counts.
3830        let raw = c.read_u32()?;
3831        let n = c.check_seq_count(raw, 4)?;
3832        for _ in 0..n {
3833            c.read_u32()?;
3834        }
3835        Ok(Vibration { offsets: [o0], buf })
3836    }
3837
3838    /// Returns a `Header` view by re-parsing the CDR buffer prefix.
3839    pub fn header(&self) -> crate::std_msgs::Header<&[u8]> {
3840        crate::std_msgs::Header::from_cdr(self.buf.as_ref())
3841            .expect("header bytes validated during from_cdr")
3842    }
3843    pub fn stamp(&self) -> crate::builtin_interfaces::Time {
3844        rd_time(self.buf.as_ref(), CDR_HEADER_SIZE)
3845    }
3846    pub fn frame_id(&self) -> &str {
3847        rd_string(self.buf.as_ref(), CDR_HEADER_SIZE + 8).0
3848    }
3849    pub fn vibration(&self) -> crate::geometry_msgs::Vector3 {
3850        let mut c = CdrCursor::resume(self.buf.as_ref(), self.offsets[0]);
3851        crate::geometry_msgs::Vector3::read_cdr(&mut c)
3852            .expect("vibration validated during from_cdr")
3853    }
3854    pub fn band_lower_hz(&self) -> f32 {
3855        rd_f32(self.buf.as_ref(), self.offsets[0] + 24)
3856    }
3857    pub fn band_upper_hz(&self) -> f32 {
3858        rd_f32(self.buf.as_ref(), self.offsets[0] + 28)
3859    }
3860    pub fn measurement_type(&self) -> u8 {
3861        rd_u8(self.buf.as_ref(), self.offsets[0] + 32)
3862    }
3863    pub fn unit(&self) -> u8 {
3864        rd_u8(self.buf.as_ref(), self.offsets[0] + 33)
3865    }
3866    pub fn clipping_len(&self) -> u32 {
3867        rd_u32(self.buf.as_ref(), self.offsets[0] + 36)
3868    }
3869    /// Byte offset of the `clipping` sequence (u32 count, then elements).
3870    /// Exposed for allocation-free decoders (e.g. FFI).
3871    pub fn clipping_seq_offset(&self) -> usize {
3872        self.offsets[0] + 36
3873    }
3874    pub fn clipping(&self) -> Vec<u32> {
3875        let mut c = CdrCursor::resume(self.buf.as_ref(), self.offsets[0] + 36);
3876        let n = c
3877            .read_u32()
3878            .expect("clipping length validated during from_cdr") as usize;
3879        let mut out = Vec::with_capacity(n);
3880        for _ in 0..n {
3881            out.push(
3882                c.read_u32()
3883                    .expect("clipping element validated during from_cdr"),
3884            );
3885        }
3886        out
3887    }
3888    pub fn as_cdr(&self) -> &[u8] {
3889        self.buf.as_ref()
3890    }
3891    pub fn to_cdr(&self) -> Vec<u8> {
3892        self.buf.as_ref().to_vec()
3893    }
3894}
3895
3896impl Vibration<Vec<u8>> {
3897    #[deprecated(
3898        since = "3.2.0",
3899        note = "use Vibration::builder() for allocation-free buffer reuse; Vibration::new will be removed in 4.0"
3900    )]
3901    #[allow(clippy::too_many_arguments)]
3902    pub fn new(
3903        stamp: crate::builtin_interfaces::Time,
3904        frame_id: &str,
3905        measurement_type: u8,
3906        unit: u8,
3907        band_lower_hz: f32,
3908        band_upper_hz: f32,
3909        vibration: crate::geometry_msgs::Vector3,
3910        clipping: &[u32],
3911    ) -> Result<Self, CdrError> {
3912        use crate::builtin_interfaces::Time;
3913        use crate::geometry_msgs::Vector3;
3914        let mut sizer = CdrSizer::new();
3915        Time::size_cdr(&mut sizer);
3916        sizer.size_string(frame_id);
3917        sizer.align(8);
3918        let o0 = sizer.offset();
3919        Vector3::size_cdr(&mut sizer);
3920        sizer.size_f32();
3921        sizer.size_f32();
3922        sizer.size_u8();
3923        sizer.size_u8();
3924        sizer.align(4);
3925        sizer.size_u32();
3926        for _ in clipping {
3927            sizer.size_u32();
3928        }
3929
3930        let mut buf = vec![0u8; sizer.size()];
3931        let mut w = CdrWriter::new(&mut buf)?;
3932        stamp.write_cdr(&mut w);
3933        w.write_string(frame_id);
3934        vibration.write_cdr(&mut w);
3935        w.write_f32(band_lower_hz);
3936        w.write_f32(band_upper_hz);
3937        w.write_u8(measurement_type);
3938        w.write_u8(unit);
3939        w.write_u32(clipping.len() as u32);
3940        for v in clipping {
3941            w.write_u32(*v);
3942        }
3943        w.finish()?;
3944
3945        Ok(Vibration { offsets: [o0], buf })
3946    }
3947
3948    pub fn into_cdr(self) -> Vec<u8> {
3949        self.buf
3950    }
3951
3952    /// Start a new `VibrationBuilder` with zero-valued defaults.
3953    pub fn builder<'a>() -> VibrationBuilder<'a> {
3954        VibrationBuilder::new()
3955    }
3956}
3957
3958// ── VibrationBuilder<'a> ────────────────────────────────────────────
3959
3960/// Builder for `Vibration<Vec<u8>>` with buffer-reuse finalizers.
3961///
3962/// `clipping` is borrowed from a caller-owned slice of 32-bit sample
3963/// indices; the borrow must remain valid until `build()`,
3964/// `encode_into_vec()`, or `encode_into_slice()` is called.
3965pub struct VibrationBuilder<'a> {
3966    stamp: crate::builtin_interfaces::Time,
3967    frame_id: std::borrow::Cow<'a, str>,
3968    measurement_type: u8,
3969    unit: u8,
3970    band_lower_hz: f32,
3971    band_upper_hz: f32,
3972    vibration: crate::geometry_msgs::Vector3,
3973    clipping: &'a [u32],
3974}
3975
3976impl<'a> Default for VibrationBuilder<'a> {
3977    fn default() -> Self {
3978        Self {
3979            stamp: crate::builtin_interfaces::Time { sec: 0, nanosec: 0 },
3980            frame_id: std::borrow::Cow::Borrowed(""),
3981            measurement_type: 0,
3982            unit: 0,
3983            band_lower_hz: 0.0,
3984            band_upper_hz: 0.0,
3985            vibration: crate::geometry_msgs::Vector3 {
3986                x: 0.0,
3987                y: 0.0,
3988                z: 0.0,
3989            },
3990            clipping: &[],
3991        }
3992    }
3993}
3994
3995impl<'a> VibrationBuilder<'a> {
3996    pub fn new() -> Self {
3997        Self::default()
3998    }
3999
4000    pub fn stamp(&mut self, t: crate::builtin_interfaces::Time) -> &mut Self {
4001        self.stamp = t;
4002        self
4003    }
4004    pub fn frame_id(&mut self, s: impl Into<std::borrow::Cow<'a, str>>) -> &mut Self {
4005        self.frame_id = s.into();
4006        self
4007    }
4008    pub fn measurement_type(&mut self, v: u8) -> &mut Self {
4009        self.measurement_type = v;
4010        self
4011    }
4012    pub fn unit(&mut self, v: u8) -> &mut Self {
4013        self.unit = v;
4014        self
4015    }
4016    pub fn band_lower_hz(&mut self, v: f32) -> &mut Self {
4017        self.band_lower_hz = v;
4018        self
4019    }
4020    pub fn band_upper_hz(&mut self, v: f32) -> &mut Self {
4021        self.band_upper_hz = v;
4022        self
4023    }
4024    pub fn vibration(&mut self, v: crate::geometry_msgs::Vector3) -> &mut Self {
4025        self.vibration = v;
4026        self
4027    }
4028    pub fn clipping(&mut self, v: &'a [u32]) -> &mut Self {
4029        self.clipping = v;
4030        self
4031    }
4032
4033    fn size(&self) -> usize {
4034        use crate::builtin_interfaces::Time;
4035        use crate::geometry_msgs::Vector3;
4036        let mut s = CdrSizer::new();
4037        Time::size_cdr(&mut s);
4038        s.size_string(&self.frame_id);
4039        s.align(8);
4040        Vector3::size_cdr(&mut s);
4041        s.size_f32();
4042        s.size_f32();
4043        s.size_u8();
4044        s.size_u8();
4045        s.align(4);
4046        s.size_u32();
4047        for _ in self.clipping {
4048            s.size_u32();
4049        }
4050        s.size()
4051    }
4052
4053    fn write_into(&self, buf: &mut [u8]) -> Result<(), CdrError> {
4054        let mut w = CdrWriter::new(buf)?;
4055        self.stamp.write_cdr(&mut w);
4056        w.write_string(&self.frame_id);
4057        self.vibration.write_cdr(&mut w);
4058        w.write_f32(self.band_lower_hz);
4059        w.write_f32(self.band_upper_hz);
4060        w.write_u8(self.measurement_type);
4061        w.write_u8(self.unit);
4062        w.write_u32(self.clipping.len() as u32);
4063        for v in self.clipping {
4064            w.write_u32(*v);
4065        }
4066        w.finish()
4067    }
4068
4069    pub fn build(&self) -> Result<Vibration<Vec<u8>>, CdrError> {
4070        let mut buf = vec![0u8; self.size()];
4071        self.write_into(&mut buf)?;
4072        Vibration::from_cdr(buf)
4073    }
4074
4075    pub fn encode_into_vec(&self, buf: &mut Vec<u8>) -> Result<(), CdrError> {
4076        buf.resize(self.size(), 0);
4077        self.write_into(buf)
4078    }
4079
4080    pub fn encode_into_slice(&self, buf: &mut [u8]) -> Result<usize, CdrError> {
4081        let need = self.size();
4082        if buf.len() < need {
4083            return Err(CdrError::BufferTooShort {
4084                need,
4085                have: buf.len(),
4086            });
4087        }
4088        self.write_into(&mut buf[..need])?;
4089        Ok(need)
4090    }
4091}
4092
4093impl<B: AsRef<[u8]> + AsMut<[u8]>> Vibration<B> {
4094    pub fn set_stamp(&mut self, t: crate::builtin_interfaces::Time) -> Result<(), CdrError> {
4095        let b = self.buf.as_mut();
4096        wr_i32(b, CDR_HEADER_SIZE, t.sec)?;
4097        wr_u32(b, CDR_HEADER_SIZE + 4, t.nanosec)
4098    }
4099
4100    pub fn set_vibration(&mut self, v: crate::geometry_msgs::Vector3) -> Result<(), CdrError> {
4101        let b = self.buf.as_mut();
4102        let p = self.offsets[0];
4103        wr_f64(b, p, v.x)?;
4104        wr_f64(b, p + 8, v.y)?;
4105        wr_f64(b, p + 16, v.z)
4106    }
4107
4108    pub fn set_band_lower_hz(&mut self, v: f32) -> Result<(), CdrError> {
4109        wr_f32(self.buf.as_mut(), self.offsets[0] + 24, v)
4110    }
4111
4112    pub fn set_band_upper_hz(&mut self, v: f32) -> Result<(), CdrError> {
4113        wr_f32(self.buf.as_mut(), self.offsets[0] + 28, v)
4114    }
4115
4116    pub fn set_measurement_type(&mut self, v: u8) -> Result<(), CdrError> {
4117        wr_u8(self.buf.as_mut(), self.offsets[0] + 32, v)
4118    }
4119
4120    pub fn set_unit(&mut self, v: u8) -> Result<(), CdrError> {
4121        wr_u8(self.buf.as_mut(), self.offsets[0] + 33, v)
4122    }
4123}
4124
4125// ── Registry ────────────────────────────────────────────────────────
4126
4127/// Check if a type name is supported by this module.
4128pub fn is_type_supported(type_name: &str) -> bool {
4129    matches!(
4130        type_name,
4131        "Box"
4132            | "CameraFrame"
4133            | "CameraPlane"
4134            | "Date"
4135            | "Detect"
4136            | "DmaBuffer"
4137            | "LocalTime"
4138            | "Mask"
4139            | "Model"
4140            | "ModelInfo"
4141            | "RadarCube"
4142            | "RadarInfo"
4143            | "Track"
4144            | "Vibration"
4145    )
4146}
4147
4148/// List all type schema names in this module.
4149pub fn list_types() -> &'static [&'static str] {
4150    &[
4151        "edgefirst_msgs/msg/Box",
4152        "edgefirst_msgs/msg/CameraFrame",
4153        "edgefirst_msgs/msg/CameraPlane",
4154        "edgefirst_msgs/msg/Date",
4155        "edgefirst_msgs/msg/Detect",
4156        "edgefirst_msgs/msg/DmaBuffer",
4157        "edgefirst_msgs/msg/LocalTime",
4158        "edgefirst_msgs/msg/Mask",
4159        "edgefirst_msgs/msg/Model",
4160        "edgefirst_msgs/msg/ModelInfo",
4161        "edgefirst_msgs/msg/RadarCube",
4162        "edgefirst_msgs/msg/RadarInfo",
4163        "edgefirst_msgs/msg/Track",
4164        "edgefirst_msgs/msg/Vibration",
4165    ]
4166}
4167
4168// SchemaType implementations
4169use crate::schema_registry::SchemaType;
4170
4171impl SchemaType for Date {
4172    const SCHEMA_NAME: &'static str = "edgefirst_msgs/msg/Date";
4173}
4174
4175#[cfg(test)]
4176#[allow(deprecated)] // Tests exercise CameraFrame::new, which is deprecated in 3.2.0 but still supported until 4.0.
4177mod tests {
4178    use super::*;
4179    use crate::builtin_interfaces::Time;
4180    use crate::cdr::{decode_fixed, encode_fixed};
4181
4182    #[test]
4183    fn date_roundtrip() {
4184        let cases = [
4185            (2025, 1, 27, "typical"),
4186            (2000, 12, 31, "end of year"),
4187            (1970, 1, 1, "unix epoch"),
4188        ];
4189        for (year, month, day, name) in cases {
4190            let date = Date { year, month, day };
4191            let bytes = encode_fixed(&date).unwrap();
4192            let decoded: Date = decode_fixed(&bytes).unwrap();
4193            assert_eq!(date, decoded, "failed for case: {}", name);
4194        }
4195    }
4196
4197    #[test]
4198    fn mask_roundtrip() {
4199        let mask = Mask::new(480, 640, 0, "", &vec![0u8; 480 * 640], false).unwrap();
4200        assert_eq!(mask.height(), 480);
4201        assert_eq!(mask.width(), 640);
4202        assert_eq!(mask.length(), 0);
4203        assert_eq!(mask.encoding(), "");
4204        assert_eq!(mask.mask_data().len(), 480 * 640);
4205        assert!(!mask.boxed());
4206
4207        let bytes = mask.to_cdr();
4208        let decoded = Mask::from_cdr(bytes).unwrap();
4209        assert_eq!(decoded.height(), 480);
4210        assert_eq!(decoded.width(), 640);
4211
4212        // Compressed mask
4213        let compressed = Mask::new(1080, 1920, 5, "zstd", &[1, 2, 3, 4, 5], true).unwrap();
4214        assert_eq!(compressed.encoding(), "zstd");
4215        assert_eq!(compressed.mask_data(), &[1, 2, 3, 4, 5]);
4216        assert!(compressed.boxed());
4217
4218        let bytes = compressed.to_cdr();
4219        let decoded = Mask::from_cdr(bytes).unwrap();
4220        assert_eq!(decoded.encoding(), "zstd");
4221        assert!(decoded.boxed());
4222    }
4223
4224    #[test]
4225    #[allow(deprecated)]
4226    fn dmabuf_roundtrip() {
4227        let dmabuf = DmaBuffer::new(
4228            Time::new(100, 0),
4229            "camera",
4230            12345,
4231            42,
4232            1920,
4233            1080,
4234            1920 * 3,
4235            0x34325247,
4236            1920 * 1080 * 3,
4237        )
4238        .unwrap();
4239        assert_eq!(dmabuf.stamp(), Time::new(100, 0));
4240        assert_eq!(dmabuf.frame_id(), "camera");
4241        assert_eq!(dmabuf.pid(), 12345);
4242        assert_eq!(dmabuf.fd(), 42);
4243        assert_eq!(dmabuf.width(), 1920);
4244        assert_eq!(dmabuf.height(), 1080);
4245        assert_eq!(dmabuf.stride(), 1920 * 3);
4246        assert_eq!(dmabuf.fourcc(), 0x34325247);
4247        assert_eq!(dmabuf.length(), 1920 * 1080 * 3);
4248
4249        let bytes = dmabuf.to_cdr();
4250        let decoded = DmaBuffer::from_cdr(bytes).unwrap();
4251        assert_eq!(decoded.pid(), 12345);
4252        assert_eq!(decoded.fd(), 42);
4253    }
4254
4255    #[test]
4256    fn camera_frame_roundtrip_empty() {
4257        let cf = CameraFrame::new(
4258            Time::new(1, 0),
4259            "cam0",
4260            42,
4261            1234,
4262            1920,
4263            1080,
4264            "NV12",
4265            "bt709",
4266            "bt709",
4267            "bt709",
4268            "limited",
4269            -1,
4270            &[],
4271        )
4272        .unwrap();
4273        assert_eq!(cf.seq(), 42);
4274        assert_eq!(cf.pid(), 1234);
4275        assert_eq!(cf.width(), 1920);
4276        assert_eq!(cf.height(), 1080);
4277        assert_eq!(cf.format(), "NV12");
4278        assert_eq!(cf.color_space(), "bt709");
4279        assert_eq!(cf.color_range(), "limited");
4280        assert_eq!(cf.fence_fd(), -1);
4281        assert_eq!(cf.num_planes(), 0);
4282
4283        let bytes = cf.to_cdr();
4284        let decoded = CameraFrame::<&[u8]>::from_cdr(&bytes[..]).unwrap();
4285        assert_eq!(decoded.seq(), 42);
4286        assert_eq!(decoded.format(), "NV12");
4287        assert_eq!(decoded.num_planes(), 0);
4288    }
4289
4290    #[test]
4291    fn camera_frame_roundtrip_two_planes() {
4292        let y = CameraPlaneView {
4293            fd: 42,
4294            offset: 0,
4295            stride: 1920,
4296            size: 2_073_600,
4297            used: 2_073_600,
4298            data: &[],
4299        };
4300        let uv = CameraPlaneView {
4301            fd: 42,
4302            offset: 2_073_600,
4303            stride: 1920,
4304            size: 1_036_800,
4305            used: 1_036_800,
4306            data: &[],
4307        };
4308        let cf = CameraFrame::new(
4309            Time::new(2, 0),
4310            "cam0",
4311            100,
4312            1234,
4313            1920,
4314            1080,
4315            "NV12",
4316            "bt709",
4317            "bt709",
4318            "bt709",
4319            "limited",
4320            77,
4321            &[y, uv],
4322        )
4323        .unwrap();
4324
4325        let bytes = cf.to_cdr();
4326        let decoded = CameraFrame::<&[u8]>::from_cdr(&bytes[..]).unwrap();
4327        assert_eq!(decoded.fence_fd(), 77);
4328        assert_eq!(decoded.num_planes(), 2);
4329        let planes = decoded.planes();
4330        assert_eq!(planes.len(), 2);
4331        assert_eq!(planes[0].fd, 42);
4332        assert_eq!(planes[0].offset, 0);
4333        assert_eq!(planes[1].offset, 2_073_600);
4334        assert_eq!(planes[0].used, planes[0].size);
4335    }
4336
4337    #[test]
4338    fn camera_frame_inlined_data_roundtrip() {
4339        let data: Vec<u8> = (0..32u8).collect();
4340        let plane = CameraPlaneView {
4341            fd: -1,
4342            offset: 0,
4343            stride: 16,
4344            size: 32,
4345            used: 32,
4346            data: &data,
4347        };
4348        let cf = CameraFrame::new(
4349            Time::new(3, 0),
4350            "bridge",
4351            1,
4352            0,
4353            2,
4354            16,
4355            "rgb8",
4356            "srgb",
4357            "srgb",
4358            "",
4359            "full",
4360            -1,
4361            &[plane],
4362        )
4363        .unwrap();
4364        let decoded = CameraFrame::<&[u8]>::from_cdr(cf.as_cdr()).unwrap();
4365        let planes = decoded.planes();
4366        assert_eq!(planes[0].fd, -1);
4367        assert_eq!(planes[0].data.len(), 32);
4368        assert_eq!(planes[0].data[0], 0);
4369        assert_eq!(planes[0].data[31], 31);
4370    }
4371
4372    #[test]
4373    fn camera_frame_contract_rejections() {
4374        let stamp = Time::new(0, 0);
4375        // Zero width rejected
4376        assert!(CameraFrame::new(stamp, "c", 0, 0, 0, 1, "rgb8", "", "", "", "", -1, &[]).is_err());
4377        // Zero height rejected
4378        assert!(CameraFrame::new(stamp, "c", 0, 0, 1, 0, "rgb8", "", "", "", "", -1, &[]).is_err());
4379        // used > size rejected
4380        let bad_plane = CameraPlaneView {
4381            fd: 1,
4382            offset: 0,
4383            stride: 1,
4384            size: 1,
4385            used: 2,
4386            data: &[],
4387        };
4388        assert!(CameraFrame::new(
4389            stamp,
4390            "c",
4391            0,
4392            0,
4393            1,
4394            1,
4395            "rgb8",
4396            "",
4397            "",
4398            "",
4399            "",
4400            -1,
4401            &[bad_plane]
4402        )
4403        .is_err());
4404        // fd < -1 rejected
4405        let bad_fd = CameraPlaneView {
4406            fd: -5,
4407            offset: 0,
4408            stride: 1,
4409            size: 1,
4410            used: 1,
4411            data: &[],
4412        };
4413        assert!(CameraFrame::new(
4414            stamp,
4415            "c",
4416            0,
4417            0,
4418            1,
4419            1,
4420            "rgb8",
4421            "",
4422            "",
4423            "",
4424            "",
4425            -1,
4426            &[bad_fd]
4427        )
4428        .is_err());
4429        // fd >= 0 with non-empty data rejected
4430        let data = vec![1u8];
4431        let both = CameraPlaneView {
4432            fd: 5,
4433            offset: 0,
4434            stride: 1,
4435            size: 1,
4436            used: 1,
4437            data: &data,
4438        };
4439        assert!(
4440            CameraFrame::new(stamp, "c", 0, 0, 1, 1, "rgb8", "", "", "", "", -1, &[both]).is_err()
4441        );
4442    }
4443
4444    #[test]
4445    fn camera_frame_rejects_wrong_endianness() {
4446        // Build a valid single-plane frame, flip the CDR endianness marker
4447        // from little-endian (0x0001) to big-endian (0x0100) and confirm
4448        // from_cdr rejects it with an error rather than decoding garbage.
4449        let stamp = Time::new(1, 0);
4450        let plane = CameraPlaneView {
4451            fd: 1,
4452            offset: 0,
4453            stride: 1,
4454            size: 1,
4455            used: 1,
4456            data: &[],
4457        };
4458        let cf = CameraFrame::new(
4459            stamp,
4460            "cam",
4461            0,
4462            0,
4463            1,
4464            1,
4465            "rgb8",
4466            "",
4467            "",
4468            "",
4469            "",
4470            -1,
4471            &[plane],
4472        )
4473        .unwrap();
4474        let mut bytes = cf.to_cdr();
4475        // CDR header: [0]=repr-id hi, [1]=repr-id lo, [2..4]=options.
4476        // Valid LE payload encoding uses 0x00 0x01; invert to 0x00 0x00 (BE).
4477        bytes[1] = 0x00;
4478        assert!(CameraFrame::<&[u8]>::from_cdr(&bytes).is_err());
4479    }
4480
4481    #[test]
4482    fn camera_frame_decoder_rejects_fd_below_minus_one() {
4483        // Build a valid single-plane frame (fd=1, size=1, used=1, data=[]) so
4484        // the plane occupies the last 24 bytes of the CDR buffer. Flip `fd`
4485        // to -5 and confirm the decoder's new contract check rejects it.
4486        let stamp = Time::new(1, 0);
4487        let plane = CameraPlaneView {
4488            fd: 1,
4489            offset: 0,
4490            stride: 1,
4491            size: 1,
4492            used: 1,
4493            data: &[],
4494        };
4495        let cf =
4496            CameraFrame::new(stamp, "c", 0, 0, 1, 1, "rgb8", "", "", "", "", -1, &[plane]).unwrap();
4497        let mut bytes = cf.to_cdr();
4498        let fd_off = bytes.len() - 24;
4499        bytes[fd_off..fd_off + 4].copy_from_slice(&(-5i32).to_le_bytes());
4500        assert!(CameraFrame::<&[u8]>::from_cdr(&bytes).is_err());
4501    }
4502
4503    #[test]
4504    fn camera_frame_decoder_rejects_used_greater_than_size() {
4505        // Same base frame; `used` is at [len-8 .. len-4] for the 24-byte
4506        // trailing plane block. Overwrite with a value exceeding `size`.
4507        let stamp = Time::new(1, 0);
4508        let plane = CameraPlaneView {
4509            fd: 1,
4510            offset: 0,
4511            stride: 1,
4512            size: 1,
4513            used: 1,
4514            data: &[],
4515        };
4516        let cf =
4517            CameraFrame::new(stamp, "c", 0, 0, 1, 1, "rgb8", "", "", "", "", -1, &[plane]).unwrap();
4518        let mut bytes = cf.to_cdr();
4519        let used_off = bytes.len() - 8;
4520        bytes[used_off..used_off + 4].copy_from_slice(&99u32.to_le_bytes());
4521        assert!(CameraFrame::<&[u8]>::from_cdr(&bytes).is_err());
4522    }
4523
4524    #[test]
4525    fn camera_frame_rejects_inlined_size_mismatch() {
4526        // fd == -1 requires size as usize == data.len() per CameraPlane.msg.
4527        // `new` must reject the mismatch.
4528        let data = [0x42u8, 0x43, 0x44, 0x45];
4529        let plane = CameraPlaneView {
4530            fd: -1,
4531            offset: 0,
4532            stride: 0,
4533            size: 99, // does not match data.len() == 4
4534            used: 4,
4535            data: &data,
4536        };
4537        assert!(CameraFrame::new(
4538            Time::new(0, 0),
4539            "c",
4540            0,
4541            0,
4542            1,
4543            1,
4544            "rgb8",
4545            "",
4546            "",
4547            "",
4548            "",
4549            -1,
4550            &[plane]
4551        )
4552        .is_err());
4553    }
4554
4555    #[test]
4556    fn camera_frame_decoder_rejects_inlined_size_mismatch() {
4557        // Start with a valid inlined-data plane (fd=-1, size==data.len()), then
4558        // mutate `size` in the encoded CDR to break the invariant and confirm
4559        // from_cdr rejects it.
4560        let data = [0x42u8, 0x43, 0x44, 0x45];
4561        let plane = CameraPlaneView {
4562            fd: -1,
4563            offset: 0,
4564            stride: 0xDEADBEEF,
4565            size: 4,
4566            used: 4,
4567            data: &data,
4568        };
4569        let cf = CameraFrame::new(
4570            Time::new(1, 0),
4571            "c",
4572            0,
4573            0,
4574            1,
4575            1,
4576            "rgb8",
4577            "",
4578            "",
4579            "",
4580            "",
4581            -1,
4582            &[plane],
4583        )
4584        .unwrap();
4585        let mut bytes = cf.to_cdr();
4586        let needle = 0xDEADBEEFu32.to_le_bytes();
4587        let stride_off = bytes
4588            .windows(4)
4589            .position(|w| w == needle)
4590            .expect("stride sentinel");
4591        // size is the u32 immediately after stride.
4592        let size_off = stride_off + 4;
4593        bytes[size_off..size_off + 4].copy_from_slice(&99u32.to_le_bytes());
4594        assert!(CameraFrame::<&[u8]>::from_cdr(&bytes).is_err());
4595    }
4596
4597    #[test]
4598    fn camera_frame_decoder_rejects_positive_fd_with_inline_data() {
4599        // Start with a valid inlined-data plane (fd=-1, data non-empty) so
4600        // `new` accepts it, then mutate `fd` to a non-negative value to
4601        // trigger the decoder's `fd >= 0 && data non-empty` rejection.
4602        // Uses a distinctive `stride` sentinel to locate the plane.
4603        let stamp = Time::new(1, 0);
4604        let data = [0x42u8, 0x43, 0x44, 0x45];
4605        let plane = CameraPlaneView {
4606            fd: -1,
4607            offset: 0,
4608            stride: 0xDEADBEEF,
4609            size: 4,
4610            used: 4,
4611            data: &data,
4612        };
4613        let cf =
4614            CameraFrame::new(stamp, "c", 0, 0, 1, 1, "rgb8", "", "", "", "", -1, &[plane]).unwrap();
4615        let mut bytes = cf.to_cdr();
4616        let needle = 0xDEADBEEFu32.to_le_bytes();
4617        let stride_off = bytes
4618            .windows(4)
4619            .position(|w| w == needle)
4620            .expect("stride sentinel");
4621        let fd_off = stride_off - 8;
4622        bytes[fd_off..fd_off + 4].copy_from_slice(&5i32.to_le_bytes());
4623        assert!(CameraFrame::<&[u8]>::from_cdr(&bytes).is_err());
4624    }
4625
4626    #[test]
4627    fn camera_frame_registered_in_type_list() {
4628        assert!(is_type_supported("CameraFrame"));
4629        assert!(is_type_supported("CameraPlane"));
4630        assert!(list_types().contains(&"edgefirst_msgs/msg/CameraFrame"));
4631        assert!(list_types().contains(&"edgefirst_msgs/msg/CameraPlane"));
4632    }
4633
4634    #[test]
4635    fn local_time_roundtrip() {
4636        let lt = LocalTime::new(
4637            Time::new(0, 0),
4638            "clock",
4639            Date {
4640                year: 2025,
4641                month: 1,
4642                day: 27,
4643            },
4644            Time::new(43200, 0),
4645            -300,
4646        )
4647        .unwrap();
4648        assert_eq!(lt.frame_id(), "clock");
4649        assert_eq!(
4650            lt.date(),
4651            Date {
4652                year: 2025,
4653                month: 1,
4654                day: 27
4655            }
4656        );
4657        assert_eq!(lt.time(), Time::new(43200, 0));
4658        assert_eq!(lt.timezone(), -300);
4659
4660        let bytes = lt.to_cdr();
4661        let decoded = LocalTime::from_cdr(bytes).unwrap();
4662        assert_eq!(
4663            decoded.date(),
4664            Date {
4665                year: 2025,
4666                month: 1,
4667                day: 27
4668            }
4669        );
4670        assert_eq!(decoded.timezone(), -300);
4671    }
4672
4673    #[test]
4674    fn radar_cube_roundtrip() {
4675        let cube = RadarCube::new(
4676            Time::new(1234567890, 123456789),
4677            "radar",
4678            1234567890123456,
4679            &[6, 1, 5, 2],
4680            &[16, 256, 4, 64],
4681            &[1.0, 2.5, 1.0, 0.5],
4682            &[100, 200, -100, -200],
4683            true,
4684        )
4685        .unwrap();
4686        assert_eq!(cube.stamp(), Time::new(1234567890, 123456789));
4687        assert_eq!(cube.frame_id(), "radar");
4688        assert_eq!(cube.timestamp(), 1234567890123456);
4689        assert_eq!(cube.layout(), &[6, 1, 5, 2]);
4690        assert_eq!(cube.shape(), vec![16, 256, 4, 64]);
4691        assert_eq!(cube.scales(), vec![1.0, 2.5, 1.0, 0.5]);
4692        assert!(cube.is_complex());
4693
4694        let bytes = cube.to_cdr();
4695        let decoded = RadarCube::from_cdr(bytes).unwrap();
4696        assert_eq!(decoded.timestamp(), 1234567890123456);
4697        assert_eq!(decoded.layout(), &[6, 1, 5, 2]);
4698        assert!(decoded.is_complex());
4699    }
4700
4701    #[test]
4702    fn radar_info_roundtrip() {
4703        let info = RadarInfo::new(
4704            Time::new(0, 0),
4705            "radar",
4706            "77GHz",
4707            "short",
4708            "off",
4709            "high",
4710            true,
4711        )
4712        .unwrap();
4713        assert_eq!(info.center_frequency(), "77GHz");
4714        assert_eq!(info.frequency_sweep(), "short");
4715        assert_eq!(info.range_toggle(), "off");
4716        assert_eq!(info.detection_sensitivity(), "high");
4717        assert!(info.cube());
4718
4719        let bytes = info.to_cdr();
4720        let decoded = RadarInfo::from_cdr(bytes).unwrap();
4721        assert_eq!(decoded.center_frequency(), "77GHz");
4722        assert!(decoded.cube());
4723    }
4724
4725    #[test]
4726    fn detect_roundtrip() {
4727        // Empty detections
4728        let empty = Detect::new(
4729            Time::new(0, 0),
4730            "",
4731            Time::new(0, 0),
4732            Time::new(0, 0),
4733            Time::new(0, 0),
4734            &[],
4735        )
4736        .unwrap();
4737        assert_eq!(empty.boxes_len(), 0);
4738
4739        let bytes = empty.to_cdr();
4740        let decoded = Detect::from_cdr(bytes).unwrap();
4741        assert_eq!(decoded.boxes_len(), 0);
4742
4743        // With detections
4744        let boxes = [DetectBoxView {
4745            center_x: 0.5,
4746            center_y: 0.5,
4747            width: 0.1,
4748            height: 0.2,
4749            label: "car",
4750            score: 0.98,
4751            distance: 10.0,
4752            speed: 5.0,
4753            track_id: "t1",
4754            track_lifetime: 5,
4755            track_created: Time::new(95, 0),
4756        }];
4757        let detect = Detect::new(
4758            Time::new(100, 500_000_000),
4759            "camera",
4760            Time::new(100, 400_000_000),
4761            Time::new(0, 50_000_000),
4762            Time::new(100, 500_000_000),
4763            &boxes,
4764        )
4765        .unwrap();
4766        assert_eq!(detect.boxes_len(), 1);
4767        let b = detect.boxes();
4768        assert_eq!(b[0].label, "car");
4769        assert_eq!(b[0].score, 0.98);
4770
4771        let bytes = detect.to_cdr();
4772        let decoded = Detect::from_cdr(bytes).unwrap();
4773        assert_eq!(decoded.boxes_len(), 1);
4774        let b = decoded.boxes();
4775        assert_eq!(b[0].label, "car");
4776    }
4777
4778    #[test]
4779    fn detect_multi_box_varying_strings() {
4780        let boxes = [
4781            DetectBoxView {
4782                center_x: 0.1,
4783                center_y: 0.2,
4784                width: 0.5,
4785                height: 0.6,
4786                label: "a",
4787                score: 0.95,
4788                distance: 5.0,
4789                speed: 1.0,
4790                track_id: "t",
4791                track_lifetime: 1,
4792                track_created: Time::new(1, 0),
4793            },
4794            DetectBoxView {
4795                center_x: 0.3,
4796                center_y: 0.4,
4797                width: 0.2,
4798                height: 0.3,
4799                label: "person",
4800                score: 0.87,
4801                distance: 12.0,
4802                speed: 3.0,
4803                track_id: "track_long_id",
4804                track_lifetime: 10,
4805                track_created: Time::new(2, 0),
4806            },
4807            DetectBoxView {
4808                center_x: 0.7,
4809                center_y: 0.8,
4810                width: 0.1,
4811                height: 0.1,
4812                label: "ab",
4813                score: 0.50,
4814                distance: 0.0,
4815                speed: 0.0,
4816                track_id: "abc",
4817                track_lifetime: 0,
4818                track_created: Time::new(0, 0),
4819            },
4820        ];
4821        let detect = Detect::new(
4822            Time::new(100, 0),
4823            "camera",
4824            Time::new(99, 0),
4825            Time::new(0, 50_000_000),
4826            Time::new(100, 0),
4827            &boxes,
4828        )
4829        .unwrap();
4830        assert_eq!(detect.boxes_len(), 3);
4831        let decoded_boxes = detect.boxes();
4832        assert_eq!(decoded_boxes[0].label, "a");
4833        assert_eq!(decoded_boxes[0].track_id, "t");
4834        assert_eq!(decoded_boxes[1].label, "person");
4835        assert_eq!(decoded_boxes[1].track_id, "track_long_id");
4836        assert_eq!(decoded_boxes[2].label, "ab");
4837        assert_eq!(decoded_boxes[2].track_id, "abc");
4838
4839        let bytes = detect.to_cdr();
4840        let decoded = Detect::from_cdr(bytes).unwrap();
4841        assert_eq!(decoded.boxes_len(), 3);
4842        let b = decoded.boxes();
4843        assert_eq!(b[0].label, "a");
4844        assert_eq!(b[0].track_id, "t");
4845        assert_eq!(b[0].score, 0.95);
4846        assert_eq!(b[1].label, "person");
4847        assert_eq!(b[1].track_id, "track_long_id");
4848        assert_eq!(b[1].track_lifetime, 10);
4849        assert_eq!(b[2].label, "ab");
4850        assert_eq!(b[2].track_id, "abc");
4851    }
4852
4853    #[test]
4854    fn model_roundtrip() {
4855        let model = Model::new(
4856            Time::new(0, 0),
4857            "model",
4858            Duration::new(0, 1_000_000),
4859            Duration::new(0, 5_000_000),
4860            Duration::new(0, 500_000),
4861            Duration::new(0, 2_000_000),
4862            &[],
4863            &[],
4864        )
4865        .unwrap();
4866        assert_eq!(model.input_time(), Duration::new(0, 1_000_000));
4867        assert_eq!(model.boxes_len(), 0);
4868        assert_eq!(model.masks_len(), 0);
4869
4870        let bytes = model.to_cdr();
4871        let decoded = Model::from_cdr(bytes).unwrap();
4872        assert_eq!(decoded.input_time(), Duration::new(0, 1_000_000));
4873    }
4874
4875    #[test]
4876    fn model_info_roundtrip() {
4877        let info = ModelInfo::new(
4878            Time::new(0, 0),
4879            "",
4880            &[1, 3, 640, 640],
4881            8,
4882            &[1, 25200, 85],
4883            8,
4884            &["person", "car"],
4885            "yolov8",
4886            "onnx",
4887            "yolov8n",
4888        )
4889        .unwrap();
4890        assert_eq!(info.input_shape(), vec![1, 3, 640, 640]);
4891        assert_eq!(info.input_type(), 8);
4892        assert_eq!(info.output_shape(), vec![1, 25200, 85]);
4893        assert_eq!(info.output_type(), 8);
4894        assert_eq!(info.labels(), vec!["person", "car"]);
4895        assert_eq!(info.model_type(), "yolov8");
4896        assert_eq!(info.model_format(), "onnx");
4897        assert_eq!(info.model_name(), "yolov8n");
4898
4899        let bytes = info.to_cdr();
4900        let decoded = ModelInfo::from_cdr(bytes).unwrap();
4901        assert_eq!(decoded.input_shape(), vec![1, 3, 640, 640]);
4902        assert_eq!(decoded.labels(), vec!["person", "car"]);
4903        assert_eq!(decoded.model_name(), "yolov8n");
4904    }
4905
4906    #[test]
4907    fn model_info_empty_labels() {
4908        let info = ModelInfo::new(
4909            Time::new(1, 0),
4910            "cam",
4911            &[1, 3, 224, 224],
4912            8,
4913            &[1, 10],
4914            8,
4915            &[],
4916            "classifier",
4917            "onnx",
4918            "mobilenet",
4919        )
4920        .unwrap();
4921        assert_eq!(info.labels(), Vec::<&str>::new());
4922        assert_eq!(info.input_shape(), &[1, 3, 224, 224]);
4923        assert_eq!(info.output_shape(), &[1, 10]);
4924        assert_eq!(info.model_type(), "classifier");
4925        assert_eq!(info.model_format(), "onnx");
4926        assert_eq!(info.model_name(), "mobilenet");
4927
4928        let bytes = info.to_cdr();
4929        let decoded = ModelInfo::from_cdr(bytes).unwrap();
4930        assert_eq!(decoded.labels(), Vec::<&str>::new());
4931        assert_eq!(decoded.model_type(), "classifier");
4932        assert_eq!(decoded.model_name(), "mobilenet");
4933    }
4934
4935    #[test]
4936    fn model_info_single_empty_label() {
4937        let info = ModelInfo::new(
4938            Time::new(0, 0),
4939            "",
4940            &[1],
4941            0,
4942            &[1],
4943            0,
4944            &[""],
4945            "det",
4946            "tflite",
4947            "m",
4948        )
4949        .unwrap();
4950        assert_eq!(info.labels(), vec![""]);
4951
4952        let bytes = info.to_cdr();
4953        let decoded = ModelInfo::from_cdr(bytes).unwrap();
4954        assert_eq!(decoded.labels(), vec![""]);
4955        assert_eq!(decoded.model_type(), "det");
4956    }
4957
4958    #[test]
4959    fn model_info_alignment_stressing_labels() {
4960        let info = ModelInfo::new(
4961            Time::new(0, 0),
4962            "f",
4963            &[1, 3, 320, 320],
4964            8,
4965            &[1, 100, 6],
4966            8,
4967            &["a", "ab", "abc", "abcd", "abcde"],
4968            "object_detection",
4969            "DeepViewRT",
4970            "yolov8n",
4971        )
4972        .unwrap();
4973        assert_eq!(info.labels(), vec!["a", "ab", "abc", "abcd", "abcde"]);
4974        assert_eq!(info.input_shape(), &[1, 3, 320, 320]);
4975        assert_eq!(info.model_type(), "object_detection");
4976
4977        let bytes = info.to_cdr();
4978        let decoded = ModelInfo::from_cdr(bytes).unwrap();
4979        assert_eq!(decoded.labels(), vec!["a", "ab", "abc", "abcd", "abcde"]);
4980        assert_eq!(decoded.input_shape(), &[1, 3, 320, 320]);
4981        assert_eq!(decoded.model_type(), "object_detection");
4982        assert_eq!(decoded.model_name(), "yolov8n");
4983    }
4984
4985    #[test]
4986    fn model_info_many_labels() {
4987        let label_strs: Vec<String> = (0..80).map(|i| format!("class_{i}")).collect();
4988        let labels: Vec<&str> = label_strs.iter().map(|s| s.as_str()).collect();
4989        let info = ModelInfo::new(
4990            Time::new(0, 0),
4991            "cam0",
4992            &[1, 3, 640, 640],
4993            8,
4994            &[1, 84, 8400],
4995            8,
4996            &labels,
4997            "object_detection",
4998            "DeepViewRT",
4999            "yolov8n",
5000        )
5001        .unwrap();
5002        assert_eq!(info.labels().len(), 80);
5003        assert_eq!(info.labels()[0], "class_0");
5004        assert_eq!(info.labels()[79], "class_79");
5005
5006        let bytes = info.to_cdr();
5007        let decoded = ModelInfo::from_cdr(bytes).unwrap();
5008        assert_eq!(decoded.labels().len(), 80);
5009        assert_eq!(decoded.labels()[0], "class_0");
5010        assert_eq!(decoded.labels()[79], "class_79");
5011        assert_eq!(decoded.model_name(), "yolov8n");
5012    }
5013
5014    #[test]
5015    fn model_info_empty_shapes() {
5016        let info = ModelInfo::new(
5017            Time::new(0, 0),
5018            "",
5019            &[],
5020            0,
5021            &[],
5022            0,
5023            &["label"],
5024            "type",
5025            "format",
5026            "name",
5027        )
5028        .unwrap();
5029        assert_eq!(info.input_shape(), &[] as &[u32]);
5030        assert_eq!(info.output_shape(), &[] as &[u32]);
5031        assert_eq!(info.labels(), vec!["label"]);
5032
5033        let bytes = info.to_cdr();
5034        let decoded = ModelInfo::from_cdr(bytes).unwrap();
5035        assert_eq!(decoded.input_shape(), &[] as &[u32]);
5036        assert_eq!(decoded.output_shape(), &[] as &[u32]);
5037        assert_eq!(decoded.labels(), vec!["label"]);
5038    }
5039
5040    #[test]
5041    fn track_roundtrip() {
5042        let track = Track::new("t1", 5, Time::new(95, 0)).unwrap();
5043        assert_eq!(track.id(), "t1");
5044        assert_eq!(track.lifetime(), 5);
5045        assert_eq!(track.created(), Time::new(95, 0));
5046
5047        let bytes = track.to_cdr();
5048        let decoded = Track::from_cdr(bytes).unwrap();
5049        assert_eq!(decoded.id(), "t1");
5050        assert_eq!(decoded.lifetime(), 5);
5051    }
5052
5053    #[test]
5054    fn detect_box_roundtrip() {
5055        let b = DetectBox::new(
5056            0.5,
5057            0.5,
5058            0.1,
5059            0.2,
5060            "car",
5061            0.98,
5062            10.0,
5063            5.0,
5064            "t1",
5065            5,
5066            Time::new(95, 0),
5067        )
5068        .unwrap();
5069        assert_eq!(b.center_x(), 0.5);
5070        assert_eq!(b.label(), "car");
5071        assert_eq!(b.score(), 0.98);
5072        assert_eq!(b.track_id(), "t1");
5073
5074        let bytes = b.to_cdr();
5075        let decoded = DetectBox::from_cdr(bytes).unwrap();
5076        assert_eq!(decoded.label(), "car");
5077        assert_eq!(decoded.track_id(), "t1");
5078    }
5079
5080    #[test]
5081    fn detect_box_empty_strings() {
5082        let b = DetectBox::new(
5083            0.5,
5084            0.5,
5085            0.1,
5086            0.2,
5087            "",
5088            0.0,
5089            0.0,
5090            0.0,
5091            "",
5092            0,
5093            Time::new(0, 0),
5094        )
5095        .unwrap();
5096        assert_eq!(b.label(), "");
5097        assert_eq!(b.track_id(), "");
5098        assert_eq!(b.center_x(), 0.5);
5099
5100        let bytes = b.to_cdr();
5101        let decoded = DetectBox::from_cdr(bytes).unwrap();
5102        assert_eq!(decoded.label(), "");
5103        assert_eq!(decoded.track_id(), "");
5104        assert_eq!(decoded.score(), 0.0);
5105    }
5106
5107    /// Verify that `Detect::from_cdr_collect_boxes` produces box views with
5108    /// fields identical to those returned by the two-pass
5109    /// `Detect::from_cdr(…).boxes()` path.
5110    #[test]
5111    fn detect_from_cdr_collect_boxes_matches_boxes() {
5112        static BYTES: &[u8] = include_bytes!("../testdata/cdr/edgefirst_msgs/Detect_multi.cdr");
5113
5114        // Path 1: legacy two-pass (from_cdr then .boxes()).
5115        let detect_ref = Detect::from_cdr(BYTES).expect("reference decode");
5116        let boxes_ref = detect_ref.boxes();
5117
5118        // Path 2: single-pass collect helper.
5119        let (detect_new, boxes_new) =
5120            Detect::from_cdr_collect_boxes(BYTES).expect("collect decode");
5121
5122        // Parent-level fields must agree.
5123        assert_eq!(detect_ref.stamp().sec, detect_new.stamp().sec);
5124        assert_eq!(detect_ref.stamp().nanosec, detect_new.stamp().nanosec);
5125        assert_eq!(detect_ref.frame_id(), detect_new.frame_id());
5126        assert_eq!(detect_ref.boxes_len(), detect_new.boxes_len());
5127
5128        // Both paths must yield the same number of box views.
5129        assert_eq!(boxes_ref.len(), boxes_new.len());
5130
5131        // Every field in every box must be identical.
5132        for (i, (a, b)) in boxes_ref.iter().zip(boxes_new.iter()).enumerate() {
5133            assert_eq!(a.center_x, b.center_x, "box[{i}].center_x");
5134            assert_eq!(a.center_y, b.center_y, "box[{i}].center_y");
5135            assert_eq!(a.width, b.width, "box[{i}].width");
5136            assert_eq!(a.height, b.height, "box[{i}].height");
5137            assert_eq!(a.label, b.label, "box[{i}].label");
5138            assert_eq!(a.score, b.score, "box[{i}].score");
5139            assert_eq!(a.distance, b.distance, "box[{i}].distance");
5140            assert_eq!(a.speed, b.speed, "box[{i}].speed");
5141            assert_eq!(a.track_id, b.track_id, "box[{i}].track_id");
5142            assert_eq!(
5143                a.track_lifetime, b.track_lifetime,
5144                "box[{i}].track_lifetime"
5145            );
5146            assert_eq!(
5147                a.track_created.sec, b.track_created.sec,
5148                "box[{i}].track_created.sec"
5149            );
5150            assert_eq!(
5151                a.track_created.nanosec, b.track_created.nanosec,
5152                "box[{i}].track_created.nanosec"
5153            );
5154        }
5155    }
5156
5157    /// Verify that `Model::from_cdr_collect_children` produces box and mask
5158    /// views with fields identical to those returned by the two-pass
5159    /// `Model::from_cdr(…).boxes()` / `.masks()` path.
5160    #[test]
5161    fn model_from_cdr_collect_children_matches_boxes_and_masks() {
5162        static BYTES: &[u8] = include_bytes!("../testdata/cdr/edgefirst_msgs/Model.cdr");
5163
5164        // Path 1: legacy two-pass.
5165        let model_ref = Model::from_cdr(BYTES).expect("reference decode");
5166        let boxes_ref = model_ref.boxes();
5167        let masks_ref = model_ref.masks();
5168
5169        // Path 2: single-pass collect helper.
5170        let (model_new, boxes_new, masks_new) =
5171            Model::from_cdr_collect_children(BYTES).expect("collect decode");
5172
5173        // Parent-level fields must agree.
5174        assert_eq!(model_ref.stamp().sec, model_new.stamp().sec);
5175        assert_eq!(model_ref.stamp().nanosec, model_new.stamp().nanosec);
5176        assert_eq!(model_ref.frame_id(), model_new.frame_id());
5177        assert_eq!(model_ref.boxes_len(), model_new.boxes_len());
5178        assert_eq!(model_ref.masks_len(), model_new.masks_len());
5179
5180        // Box views.
5181        assert_eq!(boxes_ref.len(), boxes_new.len());
5182        for (i, (a, b)) in boxes_ref.iter().zip(boxes_new.iter()).enumerate() {
5183            assert_eq!(a.center_x, b.center_x, "box[{i}].center_x");
5184            assert_eq!(a.center_y, b.center_y, "box[{i}].center_y");
5185            assert_eq!(a.width, b.width, "box[{i}].width");
5186            assert_eq!(a.height, b.height, "box[{i}].height");
5187            assert_eq!(a.label, b.label, "box[{i}].label");
5188            assert_eq!(a.score, b.score, "box[{i}].score");
5189            assert_eq!(a.distance, b.distance, "box[{i}].distance");
5190            assert_eq!(a.speed, b.speed, "box[{i}].speed");
5191            assert_eq!(a.track_id, b.track_id, "box[{i}].track_id");
5192            assert_eq!(
5193                a.track_lifetime, b.track_lifetime,
5194                "box[{i}].track_lifetime"
5195            );
5196            assert_eq!(
5197                a.track_created.sec, b.track_created.sec,
5198                "box[{i}].track_created.sec"
5199            );
5200            assert_eq!(
5201                a.track_created.nanosec, b.track_created.nanosec,
5202                "box[{i}].track_created.nanosec"
5203            );
5204        }
5205
5206        // Mask views.
5207        assert_eq!(masks_ref.len(), masks_new.len());
5208        for (i, (a, b)) in masks_ref.iter().zip(masks_new.iter()).enumerate() {
5209            assert_eq!(a.height, b.height, "mask[{i}].height");
5210            assert_eq!(a.width, b.width, "mask[{i}].width");
5211            assert_eq!(a.length, b.length, "mask[{i}].length");
5212            assert_eq!(a.encoding, b.encoding, "mask[{i}].encoding");
5213            assert_eq!(a.mask, b.mask, "mask[{i}].mask");
5214            assert_eq!(a.boxed, b.boxed, "mask[{i}].boxed");
5215        }
5216    }
5217}