use crate::Error;
use alloc::vec::Vec;
use binary_sv2::{Deserialize, Serialize, U24};
use core::convert::TryInto;
use crate::SV2_FRAME_HEADER_SIZE;
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
pub struct Header {
extension_type: u16, msg_type: u8,
msg_length: U24,
}
impl Header {
pub const SIZE: usize = SV2_FRAME_HEADER_SIZE;
const CHANNEL_MSG_MASK: u16 = 0b1000_0000_0000_0000;
#[inline]
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() < Self::SIZE {
return Err(Error::UnexpectedHeaderLength(bytes.len()));
};
let extension_type = u16::from_le_bytes([bytes[0], bytes[1]]);
let msg_type = bytes[2];
let msg_length: U24 = u32::from_le_bytes([bytes[3], bytes[4], bytes[5], 0]).try_into()?;
Ok(Self {
extension_type,
msg_type,
msg_length,
})
}
#[inline]
pub fn write_into(&self, dst: &mut [u8]) -> Result<(), Error> {
if dst.len() < Self::SIZE {
return Err(Error::UnexpectedHeaderLength(dst.len()));
}
dst[0..2].copy_from_slice(&self.extension_type.to_le_bytes());
dst[2] = self.msg_type;
let msg_length: u32 = self.msg_length.into();
dst[3..6].copy_from_slice(&msg_length.to_le_bytes()[..3]);
Ok(())
}
#[inline]
pub fn payload_length(&self) -> usize {
let inner: u32 = self.msg_length.into();
inner as usize
}
#[inline]
pub(crate) fn from_len(
msg_length: usize,
msg_type: u8,
extension_type: u16,
) -> Result<Header, Error> {
let len = u32::try_from(msg_length)
.ok()
.and_then(|len| U24::try_from(len).ok())
.ok_or(Error::PayloadTooLong(msg_length))?;
Ok(Self {
extension_type,
msg_type,
msg_length: len,
})
}
pub fn msg_type(&self) -> u8 {
self.msg_type
}
pub fn ext_type(&self) -> u16 {
self.extension_type
}
pub fn ext_type_without_channel_msg(&self) -> u16 {
self.extension_type & !Self::CHANNEL_MSG_MASK
}
pub fn channel_msg(&self) -> bool {
self.extension_type & Self::CHANNEL_MSG_MASK != 0
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::SV2_FRAME_MAX_PAYLOAD_SIZE;
use alloc::vec;
use quickcheck::{Arbitrary, Gen};
use quickcheck_macros::quickcheck;
#[test]
fn test_header_from_bytes() {
let bytes = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
let header = Header::from_bytes(&bytes).unwrap();
assert_eq!(header.extension_type, 0x0201);
assert_eq!(header.msg_type, 0x03);
assert_eq!(header.msg_length, 0x060504_u32.try_into().unwrap());
}
#[test]
fn test_header_from_len() {
let header = Header::from_len(0x1234, 0x56, 0x789a).unwrap();
assert_eq!(header.extension_type, 0x789a);
assert_eq!(header.msg_type, 0x56);
assert_eq!(header.msg_length, 0x1234_u32.try_into().unwrap());
let extension_type = 0;
let msg_type = 0x1;
let msg_length = 0x1234_u32;
let header = Header::from_len(msg_length as usize, msg_type, extension_type).unwrap();
assert_eq!(header.extension_type, 0);
assert_eq!(header.msg_type, 0x1);
assert_eq!(header.msg_length, 0x1234_u32.try_into().unwrap());
}
#[derive(Debug, Clone)]
struct ValidU24(u32);
impl Arbitrary for ValidU24 {
fn arbitrary(g: &mut Gen) -> Self {
ValidU24(u32::arbitrary(g) % (SV2_FRAME_MAX_PAYLOAD_SIZE as u32 + 1))
}
}
#[quickcheck]
fn prop_header_serialization_roundtrip(
msg_length: ValidU24,
msg_type: u8,
extension_type: u16,
) {
let header = Header::from_len(msg_length.0 as usize, msg_type, extension_type).unwrap();
let mut bytes = vec![0u8; SV2_FRAME_HEADER_SIZE];
if binary_sv2::to_writer(header, &mut bytes[..]).is_err() {
return;
}
let deserialized = Header::from_bytes(&bytes).expect("Failed to deserialize header");
assert_eq!(
deserialized.msg_type(),
msg_type,
"Message type mismatch after roundtrip"
);
assert_eq!(
deserialized.ext_type(),
extension_type,
"Extension type mismatch after roundtrip"
);
let len: u32 = deserialized.msg_length.into();
assert_eq!(len, msg_length.0, "Message length mismatch after roundtrip");
}
#[quickcheck]
fn prop_header_from_bytes_size_requirement(bytes: Vec<u8>) {
let result = Header::from_bytes(&bytes);
if bytes.len() < SV2_FRAME_HEADER_SIZE {
assert!(
matches!(result, Err(Error::UnexpectedHeaderLength(_))),
"Expected UnexpectedHeaderLength error for buffer with {} bytes, got {:?}",
bytes.len(),
result
);
} else {
assert!(
result.is_ok() || !matches!(result, Err(Error::UnexpectedHeaderLength(_))),
"Got unexpected UnexpectedHeaderLength error for sufficient buffer size"
);
}
}
#[quickcheck]
fn prop_header_channel_msg_bit(extension_type: u16, channel_msg: bool) {
let msg_length = 100u32;
let msg_type = 0x01u8;
let adjusted_extension_type = if channel_msg {
extension_type | 0b1000_0000_0000_0000
} else {
extension_type & 0b0111_1111_1111_1111
};
let header =
Header::from_len(msg_length as usize, msg_type, adjusted_extension_type).unwrap();
assert_eq!(
header.channel_msg(),
channel_msg,
"channel_msg() should return {} for extension_type 0x{:04X} with MSB {}",
channel_msg,
adjusted_extension_type,
if channel_msg { "set" } else { "unset" }
);
}
#[quickcheck]
fn prop_header_ext_type_without_channel_msg(extension_type: u16) {
let msg_length = 100u32;
let msg_type = 0x01u8;
let header = Header::from_len(msg_length as usize, msg_type, extension_type).unwrap();
let without_channel = header.ext_type_without_channel_msg();
let expected = extension_type & 0b0111_1111_1111_1111;
assert_eq!(
without_channel, expected,
"ext_type_without_channel_msg() should clear MSB: got 0x{:04X}, expected 0x{:04X}",
without_channel, expected
);
}
#[quickcheck]
fn prop_header_len_consistency(msg_length: ValidU24) {
let header = Header::from_len(msg_length.0 as usize, 0x01, 0x0000).unwrap();
assert_eq!(
header.payload_length(),
msg_length.0 as usize,
"Header len() should match the msg_length used to create it"
);
}
#[quickcheck]
fn prop_header_size_is_constant() {
assert_eq!(
Header::SIZE,
SV2_FRAME_HEADER_SIZE,
"Header::SIZE should equal SV2_FRAME_HEADER_SIZE"
);
assert_eq!(
SV2_FRAME_HEADER_SIZE, 6,
"SV2_FRAME_HEADER_SIZE should be 6 bytes"
);
}
}