1pub const SLOT_HEADER_SIZE: usize = 16;
22
23pub type ReaderMask = u32;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34#[repr(C, align(4))]
35pub struct SlotHeader {
36 pub sequence_number: u32,
38 pub sample_size: u32,
40 pub reader_mask: ReaderMask,
42 pub _reserved: u32,
44}
45
46impl SlotHeader {
47 #[must_use]
49 pub const fn new(sn: u32, sample_size: u32) -> Self {
50 Self {
51 sequence_number: sn,
52 sample_size,
53 reader_mask: 0,
54 _reserved: 0,
55 }
56 }
57
58 #[must_use]
61 pub const fn all_read(&self, active_readers_mask: ReaderMask) -> bool {
62 (self.reader_mask & active_readers_mask) == active_readers_mask
66 }
67
68 pub fn mark_read(&mut self, reader_index: u8) {
70 debug_assert!(reader_index < 32, "max 32 Reader pro Topic");
71 self.reader_mask |= 1u32 << reader_index;
72 }
73
74 #[must_use]
76 pub fn to_bytes_le(&self) -> [u8; SLOT_HEADER_SIZE] {
77 let mut out = [0u8; SLOT_HEADER_SIZE];
78 out[0..4].copy_from_slice(&self.sequence_number.to_le_bytes());
79 out[4..8].copy_from_slice(&self.sample_size.to_le_bytes());
80 out[8..12].copy_from_slice(&self.reader_mask.to_le_bytes());
81 out[12..16].copy_from_slice(&self._reserved.to_le_bytes());
82 out
83 }
84
85 #[must_use]
90 pub fn from_bytes_le(bytes: &[u8]) -> Option<Self> {
91 if bytes.len() < SLOT_HEADER_SIZE {
92 return None;
93 }
94 Some(Self {
95 sequence_number: u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
96 sample_size: u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
97 reader_mask: u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
98 _reserved: u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
99 })
100 }
101}
102
103#[cfg(test)]
104#[allow(clippy::expect_used, clippy::unwrap_used)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn header_size_is_16() {
110 assert_eq!(SLOT_HEADER_SIZE, 16);
111 assert_eq!(core::mem::size_of::<SlotHeader>(), SLOT_HEADER_SIZE);
112 }
113
114 #[test]
115 fn new_header_has_zero_mask() {
116 let h = SlotHeader::new(7, 24);
117 assert_eq!(h.sequence_number, 7);
118 assert_eq!(h.sample_size, 24);
119 assert_eq!(h.reader_mask, 0);
120 }
121
122 #[test]
123 fn mark_read_sets_bit() {
124 let mut h = SlotHeader::new(1, 24);
125 h.mark_read(0);
126 h.mark_read(2);
127 assert_eq!(h.reader_mask, 0b101);
128 }
129
130 #[test]
131 fn all_read_with_two_active_readers() {
132 let mut h = SlotHeader::new(1, 24);
133 let active = 0b011;
135 assert!(!h.all_read(active));
136 h.mark_read(0);
137 assert!(!h.all_read(active));
138 h.mark_read(1);
139 assert!(h.all_read(active));
140 }
141
142 #[test]
143 fn inactive_reader_bits_dont_block() {
144 let mut h = SlotHeader::new(1, 24);
145 let active = 0b001;
148 h.mark_read(0);
149 assert!(h.all_read(active));
150 }
151
152 #[test]
153 fn roundtrip_le() {
154 let h = SlotHeader {
155 sequence_number: 0xAABB_CCDD,
156 sample_size: 24,
157 reader_mask: 0b1111_0000,
158 _reserved: 0,
159 };
160 let bytes = h.to_bytes_le();
161 let h2 = SlotHeader::from_bytes_le(&bytes).expect("decode");
162 assert_eq!(h, h2);
163 }
164
165 #[test]
166 fn from_bytes_too_short_returns_none() {
167 assert!(SlotHeader::from_bytes_le(&[0u8; 15]).is_none());
168 }
169}