channels_packet/
header.rs

1//! [`Header`] and helper types.
2
3use core::borrow::Borrow;
4use core::fmt;
5use core::ops::{Deref, Range};
6
7use crate::checksum::Checksum;
8use crate::flags::Flags;
9use crate::payload::Payload;
10use crate::seq::{FrameNum, FrameNumSequence};
11use crate::util::Error;
12use crate::wants::Wants;
13
14const VERSION_MASK: u64 = 0x0000_0000_0000_00ff;
15const VERSION_SHIFT: u32 = 0;
16
17const FLAGS_MASK: u64 = 0x0000_0000_0000_0300;
18const FLAGS_SHIFT: u32 = 8;
19
20const FRAME_NUM_MASK: u64 = 0x0000_0000_0000_fc00;
21const FRAME_NUM_SHIFT: u32 = 10;
22
23const CHECKSUM_FIELD: Range<usize> = 2..4;
24
25const DATA_LEN_MASK: u64 = 0xffff_ffff_0000_0000;
26const DATA_LEN_SHIFT: u32 = 32;
27
28/// Header of a frame.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct Header {
31	/// Frame flags.
32	pub flags: Flags,
33	/// Frame number.
34	pub frame_num: FrameNum,
35	/// Data length.
36	pub data_len: u32,
37}
38
39impl Header {
40	const VERSION: u8 = 0x42;
41
42	/// Size of the header in bytes.
43	pub const SIZE: usize = 8;
44
45	/// Create a new [`Builder`] for [`Header`].
46	#[inline]
47	pub const fn builder() -> Builder {
48		Builder {
49			flags: Flags::empty(),
50			frame_num: FrameNum::new(0),
51			data_len: 0,
52		}
53	}
54
55	/// Convert the header to its byte representation.
56	#[inline]
57	#[must_use]
58	#[allow(clippy::cast_lossless)]
59	pub fn to_bytes(self) -> HeaderBytes {
60		let x = ((Header::VERSION as u64) << VERSION_SHIFT) // version
61			| ((0u8 as u64) << FLAGS_SHIFT) // flags
62			| ((self.frame_num.get() as u64) << FRAME_NUM_SHIFT) // frame_num
63			| ((self.data_len as u64) << DATA_LEN_SHIFT); // data_len
64
65		let mut data = u64::to_le_bytes(x);
66
67		let checksum = Checksum::checksum(&data);
68		data[CHECKSUM_FIELD].copy_from_slice(&checksum.to_ne_bytes());
69
70		HeaderBytes { data }
71	}
72}
73
74/// Errors when parsing a header.
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
76pub enum HeaderError {
77	/// The checksum did not verify correctly.
78	InvalidChecksum,
79	/// Parsed header was of a different version.
80	VersionMismatch,
81}
82
83impl fmt::Display for HeaderError {
84	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85		match *self {
86			Self::InvalidChecksum => f.write_str("invalid checksum"),
87			Self::VersionMismatch => f.write_str("version mismatch"),
88		}
89	}
90}
91
92impl Error for HeaderError {}
93
94impl Header {
95	/// Try to parse a header from `bytes`.
96	///
97	/// This method will try to parse a header from the first bytes of `bytes`. Returns:
98	///
99	/// - `Ok(None)` if `bytes` does not contain enough data to form a header,
100	/// - `Ok(Some(header))` if a header could be successfully parsed from bytes,
101	/// - `Err(...)` if there was an error while parsing the header
102	#[must_use = "unused parse result"]
103	#[allow(
104		clippy::missing_panics_doc,
105		clippy::cast_possible_truncation
106	)]
107	pub fn try_parse(
108		bytes: &[u8],
109	) -> Result<Result<Self, Wants>, HeaderError> {
110		let Some(hdr_bytes) = bytes.get(..Self::SIZE) else {
111			return Ok(Err(Wants(Self::SIZE - bytes.len())));
112		};
113
114		let hdr_bytes: &[u8; Self::SIZE] = hdr_bytes
115			.try_into()
116			.expect("cast header slice to array failed");
117
118		let hdr = u64::from_le_bytes(*hdr_bytes);
119
120		let version = ((hdr & VERSION_MASK) >> VERSION_SHIFT) as u8;
121
122		let flags = Flags::from_bits(
123			((hdr & FLAGS_MASK) >> FLAGS_SHIFT) as u8,
124		);
125
126		let frame_num = FrameNum::new(
127			((hdr & FRAME_NUM_MASK) >> FRAME_NUM_SHIFT) as u8,
128		);
129
130		let data_len =
131			((hdr & DATA_LEN_MASK) >> DATA_LEN_SHIFT) as u32;
132
133		if version != Self::VERSION {
134			return Err(HeaderError::VersionMismatch);
135		}
136
137		if Checksum::checksum(hdr_bytes) != 0 {
138			return Err(HeaderError::InvalidChecksum);
139		}
140
141		Ok(Ok(Self { flags, frame_num, data_len }))
142	}
143}
144
145/// A builder for [`Header`].
146#[allow(missing_debug_implementations)]
147#[must_use = "builders don't do anything unless you build them"]
148pub struct Builder {
149	flags: Flags,
150	frame_num: FrameNum,
151	data_len: u32,
152}
153
154impl Builder {
155	/// Set the flags of the frame.
156	#[inline]
157	pub const fn flags(mut self, flags: Flags) -> Self {
158		self.flags = flags;
159		self
160	}
161
162	/// Set the frame number.
163	#[inline]
164	pub const fn frame_num(mut self, frame_num: FrameNum) -> Self {
165		self.frame_num = frame_num;
166		self
167	}
168
169	/// Set the frame number from next one in `seq`.
170	///
171	/// This method will [`advance()`] `seq`.
172	///
173	/// [`advance()`]: FrameNumSequence::advance
174	#[inline]
175	pub fn frame_num_from_seq(
176		self,
177		seq: &mut FrameNumSequence,
178	) -> Self {
179		self.frame_num(seq.advance())
180	}
181
182	/// Set the length of the frame's payload.
183	#[inline]
184	pub const fn data_len(mut self, data_len: u32) -> Self {
185		self.data_len = data_len;
186		self
187	}
188
189	/// Set the length of frame's payload from `payload`.
190	#[inline]
191	pub fn data_len_from_payload<T: AsRef<[u8]>>(
192		self,
193		payload: &Payload<T>,
194	) -> Self {
195		self.data_len(payload.length())
196	}
197
198	/// Build the header.
199	#[inline]
200	#[must_use]
201	pub const fn build(self) -> Header {
202		let Self { flags, frame_num, data_len } = self;
203		Header { flags, frame_num, data_len }
204	}
205}
206
207/// The byte representation of a [`Header`].
208#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
209pub struct HeaderBytes {
210	data: [u8; Header::SIZE],
211}
212
213impl HeaderBytes {
214	const fn as_slice(&self) -> &[u8] {
215		self.data.as_slice()
216	}
217}
218
219impl Borrow<[u8]> for HeaderBytes {
220	#[inline]
221	fn borrow(&self) -> &[u8] {
222		self.as_slice()
223	}
224}
225
226impl AsRef<[u8]> for HeaderBytes {
227	#[inline]
228	fn as_ref(&self) -> &[u8] {
229		self.as_slice()
230	}
231}
232
233impl Deref for HeaderBytes {
234	type Target = [u8];
235
236	#[inline]
237	fn deref(&self) -> &Self::Target {
238		self.as_slice()
239	}
240}
241
242#[cfg(test)]
243#[allow(clippy::unusual_byte_groupings)]
244mod tests {
245	use super::*;
246
247	struct Vector {
248		header: Header,
249		bytes: [u8; Header::SIZE],
250	}
251
252	#[rustfmt::skip]
253    static TEST_VECTORS: &[Vector] = &[
254        Vector {
255            header: Header {
256                flags: Flags::empty(),
257                frame_num: FrameNum::new(32),
258                data_len: 0,
259            },
260            bytes: [0x42, 0b100000_00, 0xbd, 0x7f, 0, 0, 0, 0],
261        },
262        Vector {
263            header: Header {
264                flags: Flags::empty(),
265                frame_num: FrameNum::new(34),
266                data_len: 42,
267            },
268            bytes: [0x42, 0b100010_00, 0x93, 0x77, 42, 0, 0, 0],
269        },
270        Vector {
271            header: Header {
272                flags: Flags::empty(),
273                frame_num: FrameNum::new(23),
274                data_len: 0xffff,
275            },
276            bytes: [0x42, 0b010111_00, 0xbd, 0xa3, 0xff, 0xff, 0, 0],
277        },
278        Vector {
279            header: Header {
280                flags: Flags::empty(),
281                data_len: 0x0001_0000,
282                frame_num: FrameNum::new(5),
283            },
284            bytes: [0x42, 0b000101_00, 0xbc, 0xeb, 0x00, 0x00, 0x01, 0x00],
285        },
286        Vector {
287            header: Header {
288                flags: Flags::empty(),
289                data_len: 0xffff_ffff,
290                frame_num: FrameNum::new(14),
291            },
292            bytes: [0x42, 0b001110_00, 0xbd, 0xc7, 0xff, 0xff, 0xff, 0xff],
293        },
294    ];
295
296	macro_rules! tests {
297        ($($test_vector_idx:expr => $test_encode:ident, $test_decode:ident,)*) => {
298            $(
299                #[test]
300                fn $test_encode() {
301                    let Vector { header, bytes } = TEST_VECTORS[$test_vector_idx];
302                    let buf = header.to_bytes();
303                    assert_eq!(buf.as_ref(), bytes);
304                }
305
306                #[test]
307                fn $test_decode() {
308                    let Vector { header, bytes } = TEST_VECTORS[$test_vector_idx];
309                    let x = Header::try_parse(&bytes).unwrap().unwrap();
310                    assert_eq!(header, x);
311                }
312            )*
313        };
314    }
315
316	tests! {
317		0 => test_header_encode_no_payload,           test_header_decode_no_payload,
318		1 => test_header_encode_small_payload,        test_header_decode_small_payload,
319		2 => test_header_encode_small_medium_payload, test_header_decode_small_medium_payload,
320		3 => test_header_encode_medium_payload,       test_header_decode_medium_payload,
321		4 => test_header_encode_medium_large_payload, test_header_decode_medium_large_payload,
322	}
323
324	#[test]
325	fn test_header_decode_invalid_version() {
326		let bytes: &[u8] =
327			&[0x43, 0b000000_00, 0x00, 0x00, 0, 0, 0, 0];
328		assert_eq!(
329			Header::try_parse(bytes),
330			Err(HeaderError::VersionMismatch)
331		);
332	}
333
334	#[test]
335	fn test_header_decode_invalid_checksum() {
336		let bytes: &[u8] =
337			&[0x42, 0b000100_01, 0xCC, 0xCC, 0x23, 0x00, 0x00, 0x00];
338		assert_eq!(
339			Header::try_parse(bytes),
340			Err(HeaderError::InvalidChecksum)
341		);
342	}
343
344	#[test]
345	fn test_header_decode_not_enough() {
346		const HEADERS: &[&[u8]] = &[
347			&[],
348			&[0x42],
349			&[0x42, 0b010101_01],
350			&[0x42, 0b010101_01, 0xCC],
351			&[0x42, 0b010101_01, 0xCC, 0xCC],
352			&[0x42, 0b010101_01, 0xCC, 0xCC, 0x00],
353			&[0x42, 0b010101_11, 0xCC, 0xCC, 0x00, 0x00],
354			&[0x42, 0b010101_11, 0xCC, 0xCC, 0x00, 0x00, 0x00],
355		];
356
357		HEADERS.iter().copied().for_each(|bytes| {
358			assert!(
359				matches!(Header::try_parse(bytes), Ok(Err(_))),
360				"fail"
361			);
362		});
363	}
364}