zerodds_recorder/
format.rs1use alloc::string::String;
7use alloc::vec::Vec;
8
9pub const ZDDSREC_MAGIC: [u8; 4] = *b"ZDDS";
11pub const FRAME_MAGIC: u8 = b'F';
13pub const ZDDSREC_VERSION: u32 = 1;
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18#[repr(u8)]
19pub enum SampleKind {
20 Alive = 0,
22 NotAliveDisposed = 1,
24 NotAliveUnregistered = 2,
26}
27
28impl SampleKind {
29 #[must_use]
31 pub fn to_u8(self) -> u8 {
32 self as u8
33 }
34
35 #[must_use]
37 pub fn from_u8(v: u8) -> Option<Self> {
38 match v {
39 0 => Some(Self::Alive),
40 1 => Some(Self::NotAliveDisposed),
41 2 => Some(Self::NotAliveUnregistered),
42 _ => None,
43 }
44 }
45}
46
47#[derive(Clone, Debug, PartialEq, Eq)]
49pub struct ParticipantEntry {
50 pub guid: [u8; 16],
52 pub name: String,
54}
55
56#[derive(Clone, Debug, PartialEq, Eq)]
58pub struct TopicEntry {
59 pub name: String,
61 pub type_name: String,
63}
64
65#[derive(Clone, Debug, PartialEq, Eq)]
67pub struct Header {
68 pub time_base_unix_ns: i64,
71 pub participants: Vec<ParticipantEntry>,
73 pub topics: Vec<TopicEntry>,
75}
76
77impl Header {
78 pub fn write(&self, out: &mut Vec<u8>) {
80 out.extend_from_slice(&ZDDSREC_MAGIC);
81 out.extend_from_slice(&ZDDSREC_VERSION.to_le_bytes());
82 out.extend_from_slice(&self.time_base_unix_ns.to_le_bytes());
83 out.extend_from_slice(
84 &u32::try_from(self.participants.len())
85 .unwrap_or(u32::MAX)
86 .to_le_bytes(),
87 );
88 out.extend_from_slice(
89 &u32::try_from(self.topics.len())
90 .unwrap_or(u32::MAX)
91 .to_le_bytes(),
92 );
93 for p in &self.participants {
94 out.extend_from_slice(&p.guid);
95 write_string(out, &p.name);
96 }
97 for t in &self.topics {
98 write_string(out, &t.type_name);
99 write_string(out, &t.name);
100 }
101 }
102}
103
104fn write_string(out: &mut Vec<u8>, s: &str) {
105 let bytes = s.as_bytes();
106 out.extend_from_slice(&u32::try_from(bytes.len()).unwrap_or(u32::MAX).to_le_bytes());
107 out.extend_from_slice(bytes);
108}
109
110#[derive(Clone, Debug, PartialEq, Eq)]
112pub struct Frame {
113 pub timestamp_delta_ns: i64,
115 pub participant_idx: u32,
117 pub topic_idx: u32,
119 pub sample_kind: SampleKind,
121 pub payload: Vec<u8>,
123}
124
125impl Frame {
126 pub fn write(&self, out: &mut Vec<u8>) {
128 out.push(FRAME_MAGIC);
129 out.extend_from_slice(&self.timestamp_delta_ns.to_le_bytes());
130 out.extend_from_slice(&self.participant_idx.to_le_bytes());
131 out.extend_from_slice(&self.topic_idx.to_le_bytes());
132 out.push(self.sample_kind.to_u8());
133 let len = u32::try_from(self.payload.len()).unwrap_or(u32::MAX);
134 out.extend_from_slice(&len.to_le_bytes());
135 out.extend_from_slice(&self.payload);
136 }
137}
138
139#[derive(Clone, Debug, PartialEq, Eq)]
141pub struct FrameView<'a> {
142 pub timestamp_delta_ns: i64,
144 pub participant_idx: u32,
146 pub topic_idx: u32,
148 pub sample_kind: SampleKind,
150 pub payload: &'a [u8],
152}
153
154impl FrameView<'_> {
155 #[must_use]
157 pub fn to_owned(&self) -> Frame {
158 Frame {
159 timestamp_delta_ns: self.timestamp_delta_ns,
160 participant_idx: self.participant_idx,
161 topic_idx: self.topic_idx,
162 sample_kind: self.sample_kind,
163 payload: self.payload.to_vec(),
164 }
165 }
166}
167
168#[cfg(test)]
169#[allow(clippy::unwrap_used)] mod tests {
171 use super::*;
172
173 #[test]
174 fn sample_kind_roundtrip() {
175 for k in [
176 SampleKind::Alive,
177 SampleKind::NotAliveDisposed,
178 SampleKind::NotAliveUnregistered,
179 ] {
180 assert_eq!(SampleKind::from_u8(k.to_u8()), Some(k));
181 }
182 assert_eq!(SampleKind::from_u8(99), None);
183 }
184
185 #[test]
186 fn header_writes_magic_and_version() {
187 let h = Header {
188 time_base_unix_ns: 1_700_000_000_000_000_000,
189 participants: Vec::new(),
190 topics: Vec::new(),
191 };
192 let mut out = Vec::new();
193 h.write(&mut out);
194 assert_eq!(&out[0..4], &ZDDSREC_MAGIC);
195 assert_eq!(
196 u32::from_le_bytes(out[4..8].try_into().unwrap()),
197 ZDDSREC_VERSION
198 );
199 }
200}