use alloc::string::String;
use alloc::vec::Vec;
pub const ZDDSREC_MAGIC: [u8; 4] = *b"ZDDS";
pub const FRAME_MAGIC: u8 = b'F';
pub const ZDDSREC_VERSION: u32 = 1;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum SampleKind {
Alive = 0,
NotAliveDisposed = 1,
NotAliveUnregistered = 2,
}
impl SampleKind {
#[must_use]
pub fn to_u8(self) -> u8 {
self as u8
}
#[must_use]
pub fn from_u8(v: u8) -> Option<Self> {
match v {
0 => Some(Self::Alive),
1 => Some(Self::NotAliveDisposed),
2 => Some(Self::NotAliveUnregistered),
_ => None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ParticipantEntry {
pub guid: [u8; 16],
pub name: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TopicEntry {
pub name: String,
pub type_name: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Header {
pub time_base_unix_ns: i64,
pub participants: Vec<ParticipantEntry>,
pub topics: Vec<TopicEntry>,
}
impl Header {
pub fn write(&self, out: &mut Vec<u8>) {
out.extend_from_slice(&ZDDSREC_MAGIC);
out.extend_from_slice(&ZDDSREC_VERSION.to_le_bytes());
out.extend_from_slice(&self.time_base_unix_ns.to_le_bytes());
out.extend_from_slice(
&u32::try_from(self.participants.len())
.unwrap_or(u32::MAX)
.to_le_bytes(),
);
out.extend_from_slice(
&u32::try_from(self.topics.len())
.unwrap_or(u32::MAX)
.to_le_bytes(),
);
for p in &self.participants {
out.extend_from_slice(&p.guid);
write_string(out, &p.name);
}
for t in &self.topics {
write_string(out, &t.type_name);
write_string(out, &t.name);
}
}
}
fn write_string(out: &mut Vec<u8>, s: &str) {
let bytes = s.as_bytes();
out.extend_from_slice(&u32::try_from(bytes.len()).unwrap_or(u32::MAX).to_le_bytes());
out.extend_from_slice(bytes);
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Frame {
pub timestamp_delta_ns: i64,
pub participant_idx: u32,
pub topic_idx: u32,
pub sample_kind: SampleKind,
pub payload: Vec<u8>,
}
impl Frame {
pub fn write(&self, out: &mut Vec<u8>) {
out.push(FRAME_MAGIC);
out.extend_from_slice(&self.timestamp_delta_ns.to_le_bytes());
out.extend_from_slice(&self.participant_idx.to_le_bytes());
out.extend_from_slice(&self.topic_idx.to_le_bytes());
out.push(self.sample_kind.to_u8());
let len = u32::try_from(self.payload.len()).unwrap_or(u32::MAX);
out.extend_from_slice(&len.to_le_bytes());
out.extend_from_slice(&self.payload);
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FrameView<'a> {
pub timestamp_delta_ns: i64,
pub participant_idx: u32,
pub topic_idx: u32,
pub sample_kind: SampleKind,
pub payload: &'a [u8],
}
impl FrameView<'_> {
#[must_use]
pub fn to_owned(&self) -> Frame {
Frame {
timestamp_delta_ns: self.timestamp_delta_ns,
participant_idx: self.participant_idx,
topic_idx: self.topic_idx,
sample_kind: self.sample_kind,
payload: self.payload.to_vec(),
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)] mod tests {
use super::*;
#[test]
fn sample_kind_roundtrip() {
for k in [
SampleKind::Alive,
SampleKind::NotAliveDisposed,
SampleKind::NotAliveUnregistered,
] {
assert_eq!(SampleKind::from_u8(k.to_u8()), Some(k));
}
assert_eq!(SampleKind::from_u8(99), None);
}
#[test]
fn header_writes_magic_and_version() {
let h = Header {
time_base_unix_ns: 1_700_000_000_000_000_000,
participants: Vec::new(),
topics: Vec::new(),
};
let mut out = Vec::new();
h.write(&mut out);
assert_eq!(&out[0..4], &ZDDSREC_MAGIC);
assert_eq!(
u32::from_le_bytes(out[4..8].try_into().unwrap()),
ZDDSREC_VERSION
);
}
}