use crc_any::CRCu16;
use crate::consts::{CHECKSUM_SIZE, SIGNATURE_LENGTH};
use crate::error::{ChecksumError, SignatureError, VersionError};
use crate::io::{AsyncRead, AsyncWrite, Read, Write};
use crate::protocol::header::Header;
use crate::protocol::marker::{
HasCompId, HasMsgId, HasPayload, HasPayloadLen, HasSysId, Sequenced, Unset,
};
use crate::protocol::signature::{Sign, Signature, Signer, SigningConf};
use crate::protocol::{
Checksum, CompatFlags, ComponentId, CrcExtra, FrameBuilder, IncompatFlags, MavLinkVersion,
MavTimestamp, MessageId, Payload, PayloadLength, SecretKey, Sequence, SignatureBytes,
SignedLinkId, SystemId,
};
use crate::prelude::*;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Frame<V: MaybeVersioned> {
pub(super) header: Header<V>,
pub(super) payload: Payload,
pub(super) checksum: Checksum,
pub(super) signature: Option<Signature>,
}
impl Frame<Versionless> {
pub fn builder(
) -> FrameBuilder<Versionless, Unset, Unset, Unset, Unset, Unset, Unset, Unset, Unset> {
FrameBuilder::new()
}
}
impl<V: MaybeVersioned> Frame<V> {
#[inline]
pub fn header(&self) -> &Header<V> {
&self.header
}
#[inline]
pub fn version(&self) -> MavLinkVersion {
self.header.version()
}
#[inline]
pub fn payload_length(&self) -> PayloadLength {
self.header.payload_length()
}
#[inline]
pub fn sequence(&self) -> Sequence {
self.header.sequence()
}
#[inline]
pub fn system_id(&self) -> SystemId {
self.header.system_id()
}
#[inline]
pub fn component_id(&self) -> ComponentId {
self.header.component_id()
}
#[inline]
pub fn message_id(&self) -> MessageId {
self.header.message_id()
}
#[inline]
pub fn payload(&self) -> &Payload {
&self.payload
}
#[inline]
pub fn checksum(&self) -> Checksum {
self.checksum
}
#[inline]
pub fn is_signed(&self) -> bool {
self.signature.is_some()
}
#[inline]
pub fn signature(&self) -> Option<&Signature> {
if self.matches_version(V2) {
self.signature.as_ref()
} else {
None
}
}
pub fn remove_signature(&mut self) {
self.signature = None;
self.header.set_is_signed(false)
}
#[inline]
pub fn size(&self) -> usize {
self.header.size() + self.body_length()
}
#[inline]
pub fn body_length(&self) -> usize {
self.header().body_length()
}
pub fn calculate_crc(&self, crc_extra: CrcExtra) -> Checksum {
let mut crc_calculator = CRCu16::crc16mcrf4cc();
crc_calculator.digest(self.header.serialize().crc_data());
crc_calculator.digest(self.payload.bytes());
crc_calculator.digest(&[crc_extra]);
crc_calculator.get_crc()
}
pub fn validate_checksum<D: Dialect>(&self) -> Result<()> {
let message_info = D::message_info(self.header().message_id())?;
self.validate_checksum_with_crc_extra(message_info.crc_extra())?;
Ok(())
}
pub fn validate_checksum_with_crc_extra(
&self,
crc_extra: CrcExtra,
) -> core::result::Result<(), ChecksumError> {
if self.calculate_crc(crc_extra) != self.checksum {
return Err(ChecksumError);
}
Ok(())
}
pub fn matches_version<Version: Versioned>(
&self,
#[allow(unused_variables)] version: Version,
) -> bool {
Version::matches(self.version())
}
pub fn try_into_versioned<Version: MaybeVersioned>(
self,
) -> core::result::Result<Frame<Version>, VersionError> {
Version::expect(self.version())?;
Ok(Frame {
header: self.header.try_into_versioned::<Version>()?,
payload: self.payload,
checksum: self.checksum,
signature: self.signature,
})
}
pub fn try_to_versioned<Version: MaybeVersioned>(
&self,
) -> core::result::Result<Frame<Version>, VersionError> {
self.clone().try_into_versioned()
}
pub fn into_versionless(self) -> Frame<Versionless> {
Frame {
header: self.header.into_versionless(),
payload: self.payload,
checksum: self.checksum,
signature: self.signature,
}
}
pub fn to_versionless(&self) -> Frame<Versionless> {
self.clone().into_versionless()
}
pub fn into_mav_frame(self) -> MavFrame {
MavFrame::new(self)
}
#[inline]
pub fn decode<D: Dialect>(&self) -> Result<D> {
let message = D::decode(self.payload()).map_err(Error::from)?;
self.validate_checksum_with_crc_extra(message.crc_extra())?;
Ok(message)
}
#[inline]
pub fn decode_message<
'a,
M: mavspec::rust::spec::MessageSpecStatic + TryFrom<&'a Payload, Error = SpecError>,
>(
&'a self,
) -> Result<M> {
self.validate_checksum_with_crc_extra(M::crc_extra())?;
let message = M::try_from(self.payload()).map_err(Error::from)?;
Ok(message)
}
pub fn upgrade_with_crc_extra(&mut self, crc_extra: CrcExtra) {
self.payload.upgrade();
self.header.payload_length = self.payload.length();
self.checksum = self.calculate_crc(crc_extra);
}
pub fn serialize(&self, buf: &mut [u8]) -> core::result::Result<usize, FrameError> {
if buf.len() < self.size() {
return Err(FrameError::FrameBufferIsTooSmall {
expected: self.size(),
actual: buf.len(),
});
}
let header_bytes = self.header.serialize();
let header_bytes_slice = header_bytes.as_slice();
buf[0..header_bytes_slice.len()].copy_from_slice(header_bytes_slice);
let body_bytes = &mut buf[header_bytes_slice.len()..];
self.fill_body_buffer(body_bytes);
Ok(self.size())
}
pub unsafe fn deserialize(buf: &[u8]) -> core::result::Result<Frame<V>, FrameError> {
let header = Header::<V>::deserialize(buf)?;
let frame_length = header.size() + header.body_length();
if buf.len() < frame_length {
return Err(FrameError::FrameBufferIsTooSmall {
expected: frame_length,
actual: buf.len(),
});
}
let body_bytes = &buf[header.size()..];
let frame = Self::from_raw_body(header, body_bytes);
Ok(frame)
}
pub(crate) fn recv<E: Into<Error>, R: Read<E>>(
reader: &mut R,
) -> core::result::Result<Frame<V>, E> {
let header = Header::<V>::recv(reader)?;
let body_length = header.body_length();
#[cfg(feature = "std")]
let mut body_buf = vec![0u8; body_length];
#[cfg(not(feature = "std"))]
let mut body_buf =
[0u8; crate::consts::PAYLOAD_MAX_SIZE + CHECKSUM_SIZE + SIGNATURE_LENGTH];
let body_bytes = &mut body_buf[0..body_length];
reader.read_exact(body_bytes)?;
let frame = Self::from_raw_body(header, body_bytes);
Ok(frame)
}
pub(crate) async fn recv_async<E: Into<Error>, R: AsyncRead<E>>(
reader: &mut R,
) -> core::result::Result<Frame<V>, E> {
let header = Header::<V>::recv_async(reader).await?;
let body_length = header.body_length();
#[cfg(feature = "std")]
let mut body_buf = vec![0u8; body_length];
#[cfg(not(feature = "std"))]
let mut body_buf =
[0u8; crate::consts::PAYLOAD_MAX_SIZE + CHECKSUM_SIZE + SIGNATURE_LENGTH];
let body_bytes = &mut body_buf[0..body_length];
reader.read_exact(body_bytes).await?;
let frame = Self::from_raw_body(header, body_bytes);
Ok(frame)
}
pub(crate) fn send<E: Into<Error>, W: Write<E>>(
&self,
writer: &mut W,
) -> core::result::Result<usize, E> {
let header_bytes_sent = self.header.send(writer)?;
#[cfg(not(feature = "alloc"))]
let mut buf = [0u8; crate::consts::PAYLOAD_MAX_SIZE + SIGNATURE_LENGTH];
#[cfg(feature = "alloc")]
let mut buf = alloc::vec![0u8; self.body_length()];
let body_bytes = &mut buf[..self.body_length()];
self.fill_body_buffer(body_bytes);
writer.write_all(body_bytes)?;
Ok(header_bytes_sent + self.body_length())
}
pub(crate) async fn send_async<E: Into<Error>, W: AsyncWrite<E>>(
&self,
writer: &mut W,
) -> core::result::Result<usize, E> {
let header_bytes_sent = self.header.send_async(writer).await?;
#[cfg(not(feature = "alloc"))]
let mut buf = [0u8; crate::consts::PAYLOAD_MAX_SIZE + SIGNATURE_LENGTH];
#[cfg(feature = "alloc")]
let mut buf = alloc::vec![0u8; self.body_length()];
let body_bytes = &mut buf[..self.body_length()];
self.fill_body_buffer(body_bytes);
writer.write_all(body_bytes).await?;
Ok(header_bytes_sent + self.body_length())
}
fn fill_body_buffer(&self, buf: &mut [u8]) {
let payload_length = self.payload_length() as usize;
let payload_bytes = self.payload.bytes();
let bytes_to_copy = core::cmp::min(buf.len(), payload_bytes.len());
buf[0..bytes_to_copy].copy_from_slice(&payload_bytes[0..bytes_to_copy]);
let checksum_bytes: [u8; 2] = self.checksum.to_le_bytes();
buf[payload_length..payload_length + 2].copy_from_slice(&checksum_bytes);
if let Some(signature) = self.signature {
let signature_bytes: SignatureBytes = signature.to_byte_array();
let sig_start_idx = payload_length + 2;
buf[sig_start_idx..self.body_length()].copy_from_slice(&signature_bytes);
}
}
#[inline]
fn from_raw_body(header: Header<V>, body_bytes: &[u8]) -> Frame<V> {
let payload_bytes = &body_bytes[0..header.payload_length() as usize];
let payload = Payload::new(header.message_id(), payload_bytes, header.version());
let checksum_start = header.payload_length() as usize;
let checksum_bytes = [body_bytes[checksum_start], body_bytes[checksum_start + 1]];
let checksum: Checksum = Checksum::from_le_bytes(checksum_bytes);
let signature: Option<Signature> = if header.is_signed() {
let signature_start = checksum_start + CHECKSUM_SIZE;
let signature_bytes = &body_bytes[signature_start..signature_start + SIGNATURE_LENGTH];
Some(Signature::from_slice(signature_bytes))
} else {
None
};
Frame {
header,
payload,
checksum,
signature,
}
}
}
impl<V: Versioned> Frame<V> {
pub fn to_builder(
&self,
) -> FrameBuilder<
V,
HasPayloadLen,
Sequenced,
HasSysId,
HasCompId,
HasMsgId,
HasPayload,
Unset,
Unset,
> {
FrameBuilder {
header_builder: self.header.to_builder(),
payload: HasPayload(self.payload.clone()),
crc_extra: Unset,
signature: Unset,
}
}
}
impl Frame<V2> {
#[inline]
pub fn incompat_flags(&self) -> IncompatFlags {
self.header.incompat_flags()
}
#[inline]
pub fn compat_flags(&self) -> CompatFlags {
self.header.compat_flags()
}
pub fn link_id(&self) -> Option<SignedLinkId> {
self.signature.map(|sig| sig.link_id)
}
pub fn timestamp(&self) -> Option<MavTimestamp> {
self.signature.map(|sig| sig.timestamp)
}
pub fn add_signature(&mut self, signer: &mut dyn Sign, conf: &SigningConf) -> &mut Self {
conf.apply(self, signer);
self
}
pub fn validate_signature(
&self,
signer: &mut dyn Sign,
key: &SecretKey,
) -> core::result::Result<(), SignatureError> {
let signature = if let Some(signature) = self.signature {
signature
} else {
return Err(SignatureError);
};
let mut signer = Signer::new(signer);
if !signer.validate(&self, &signature, key) {
return Err(SignatureError);
}
Ok(())
}
}
#[cfg(feature = "unsafe")]
impl<V: MaybeVersioned> TryUpdateFrom<&dyn Message> for Frame<V> {
type Error = SpecError;
fn try_update_from(&mut self, value: &dyn Message) -> core::result::Result<(), Self::Error> {
self.check_try_update_from(&value)?;
unsafe { self.update_from_unchecked(value) }
Ok(())
}
fn check_try_update_from(&self, value: &&dyn Message) -> core::result::Result<(), Self::Error> {
value.encode(self.version())?;
Ok(())
}
unsafe fn update_from_unchecked(&mut self, value: &dyn Message) {
let payload = value.encode(self.version()).unwrap();
let crc_extra = value.crc_extra();
self.header.message_id = payload.id();
self.header.payload_length = payload.length();
self.payload = payload;
self.checksum = self.calculate_crc(crc_extra);
self.remove_signature();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "std")]
use std::io::Cursor;
#[cfg(feature = "std")]
use crate::io::{StdIoReader, StdIoWriter};
#[test]
fn crc_calculation_algorithm_accepts_sequential_digests() {
let data = [124, 12, 22, 34, 2, 148, 82, 201, 72, 0, 18, 215, 37, 63u8];
let split_at: usize = data.len() / 2;
let mut crc_calculator_bulk = CRCu16::crc16mcrf4cc();
crc_calculator_bulk.digest(&data);
let mut crc_calculator_seq = CRCu16::crc16mcrf4cc();
crc_calculator_seq.digest(&data[0..split_at]);
crc_calculator_seq.digest(&data[split_at..data.len()]);
assert_eq!(crc_calculator_bulk.get_crc(), crc_calculator_seq.get_crc());
}
#[test]
#[cfg(feature = "std")]
fn multiple_magic_bytes_in_stream_v1() {
use crate::consts::STX_V1;
let seq = STX_V1;
let reader = Cursor::new(vec![
STX_V1, 8, seq, 10, 255, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, ]);
let mut receiver = crate::Receiver::new::<V1>(StdIoReader::new(reader));
let frame = receiver.recv().unwrap();
assert_eq!(frame.payload_length(), 8);
assert_eq!(frame.sequence(), seq);
assert_eq!(frame.system_id(), 10);
assert_eq!(frame.component_id(), 255);
}
#[test]
#[cfg(feature = "std")]
fn multiple_magic_bytes_in_stream_v2() {
use crate::consts::STX_V2;
let seq = STX_V2;
let reader = Cursor::new(vec![
STX_V2, 8, 0, 0, seq, 10, 255, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, ]);
let mut receiver = crate::Receiver::new::<V2>(StdIoReader::new(reader));
let frame = receiver.recv().unwrap();
assert_eq!(frame.payload_length(), 8);
assert_eq!(frame.sequence(), seq);
assert_eq!(frame.system_id(), 10);
assert_eq!(frame.component_id(), 255);
}
#[test]
#[cfg(feature = "std")]
fn test_oversized_v2_payload() {
use crate::consts::STX_V2;
use crate::protocol::{Frame, Versionless};
let payload_length = 3;
let junk_bytes = 3;
let in_buffer = vec![
12, 24, 240, STX_V2, payload_length, 0, 0, 1, 10, 255, 74, 0, 0, 0, 0, 0, 25, 25, ];
let expected_frame_size = in_buffer.len() - 3; let valid_bytes = in_buffer[junk_bytes..].to_vec();
let mut reader = StdIoReader::new(Cursor::new(in_buffer));
let frame = Frame::<Versionless>::recv(&mut reader).unwrap();
assert_eq!(frame.payload_length(), payload_length);
assert_eq!(frame.payload.bytes().len(), 1);
let mut out_buffer = StdIoWriter::new(Cursor::new(vec![]));
let bytes_sent = frame.send(&mut out_buffer).unwrap();
assert_eq!(bytes_sent, expected_frame_size);
let mut out_bytes = vec![0u8; expected_frame_size];
let mut out_buffer = out_buffer.extract();
out_buffer.set_position(0);
std::io::Read::read_exact(&mut out_buffer, out_bytes.as_mut_slice()).unwrap();
assert_eq!(out_bytes, valid_bytes);
}
#[cfg(feature = "dlct-minimal")]
mod dialect_utils {
pub(super) use crate::dialects::minimal as dialect;
pub(super) use crate::dialects::minimal::enums::{
MavAutopilot, MavModeFlag, MavState, MavType,
};
pub(super) use crate::protocol::V1;
pub(super) use dialect::messages::Heartbeat;
pub(super) use super::super::*;
pub(super) fn default_incompat_flags() -> IncompatFlags {
IncompatFlags::BIT_3 | IncompatFlags::BIT_4
}
pub(super) fn default_compat_flags() -> CompatFlags {
CompatFlags::BIT_5 | CompatFlags::BIT_6
}
pub(super) fn default_heartbeat_message() -> Heartbeat {
Heartbeat {
type_: MavType::FixedWing,
autopilot: MavAutopilot::Generic,
base_mode: MavModeFlag::TEST_ENABLED & MavModeFlag::CUSTOM_MODE_ENABLED,
custom_mode: 0,
system_status: MavState::Active,
mavlink_version: dialect::Minimal::version().unwrap_or(0),
}
}
#[cfg(feature = "std")]
pub(super) fn all_zero_heartbeat_message() -> Heartbeat {
Heartbeat {
type_: MavType::Generic, autopilot: MavAutopilot::Generic, base_mode: MavModeFlag::empty(), custom_mode: 0,
system_status: MavState::Uninit, mavlink_version: 0,
}
}
pub(super) fn v1_frame(message: &dyn Message) -> Frame<V1> {
Frame::builder()
.sequence(7)
.system_id(22)
.component_id(17)
.version(V1)
.message(message)
.unwrap()
.build()
}
pub(super) fn v2_frame(message: &dyn Message) -> Frame<V2> {
Frame::builder()
.sequence(7)
.system_id(22)
.component_id(17)
.version(V2)
.incompat_flags(default_incompat_flags())
.compat_flags(default_compat_flags())
.message(message)
.unwrap()
.build()
}
pub(super) fn default_v1_heartbeat_frame() -> Frame<V1> {
let message = default_heartbeat_message();
v1_frame(&message)
}
pub(super) fn default_v2_heartbeat_frame() -> Frame<V2> {
let message = default_heartbeat_message();
v2_frame(&message)
}
}
#[cfg(feature = "dlct-minimal")]
use dialect_utils::*;
#[test]
#[cfg(feature = "dlct-minimal")]
#[cfg(feature = "std")]
fn test_v2_payload_truncation() {
let message = all_zero_heartbeat_message();
let frame = v2_frame(&message);
assert_eq!(frame.payload_length(), 1);
assert_eq!(frame.payload.bytes().len(), 1);
assert_eq!(frame.payload.bytes(), &[0]);
}
#[test]
#[cfg(feature = "dlct-minimal")]
#[cfg(feature = "std")]
fn test_signing() {
use crate::consts::SIGNATURE_SECRET_KEY_LENGTH;
use crate::utils::MavSha256;
let mut frame = default_v2_heartbeat_frame();
let frame = frame.add_signature(
&mut MavSha256::default(),
&SigningConf {
link_id: 0,
timestamp: Default::default(),
secret: [0u8; SIGNATURE_SECRET_KEY_LENGTH].into(),
},
);
assert!(frame.is_signed());
}
#[test]
#[cfg(feature = "dlct-minimal")]
fn test_decoding_to_message() {
let _: dialect::Minimal = default_v2_heartbeat_frame().decode().unwrap();
}
#[test]
#[cfg(feature = "dlct-minimal")]
#[cfg(feature = "std")]
fn test_rebuild_frame() {
use crate::consts::SIGNATURE_SECRET_KEY_LENGTH;
use crate::utils::MavSha256;
let mut frame = default_v2_heartbeat_frame();
frame.add_signature(
&mut MavSha256::default(),
&SigningConf {
link_id: 0,
timestamp: Default::default(),
secret: [0u8; SIGNATURE_SECRET_KEY_LENGTH].into(),
},
);
let updated = frame
.to_builder()
.crc_extra(dialect::messages::heartbeat::spec().crc_extra())
.build();
assert_eq!(updated.sequence(), frame.sequence());
assert_eq!(updated.system_id(), frame.system_id());
assert_eq!(updated.component_id(), frame.component_id());
assert_eq!(updated.payload_length(), frame.payload_length());
assert_eq!(updated.payload().bytes(), frame.payload().bytes());
assert_eq!(updated.checksum(), frame.checksum());
assert_eq!(
updated.incompat_flags().bits(),
default_incompat_flags().bits()
);
assert_eq!(updated.compat_flags().bits(), default_compat_flags().bits());
assert!(!updated.is_signed());
}
#[test]
#[cfg(feature = "dlct-minimal")]
fn test_upgrade_frame() {
let expected = default_v2_heartbeat_frame();
let upgraded = default_v1_heartbeat_frame()
.to_builder()
.crc_extra(dialect::messages::heartbeat::spec().crc_extra())
.upgrade()
.incompat_flags(default_incompat_flags())
.compat_flags(default_compat_flags())
.build();
assert_eq!(upgraded.payload_length(), expected.payload_length());
assert_eq!(upgraded.payload().bytes(), expected.payload().bytes());
assert_eq!(upgraded.checksum(), expected.checksum());
}
#[test]
#[cfg(feature = "dlct-minimal")]
fn test_try_versioned() {
let v1 = default_v1_heartbeat_frame();
assert!(v1.clone().try_into_versioned::<V1>().is_ok());
assert!(v1.clone().try_into_versioned::<V2>().is_err());
assert!(v1.clone().try_into_versioned::<Versionless>().is_ok());
let versionless_v1 = v1.into_versionless();
assert!(versionless_v1.clone().try_into_versioned::<V1>().is_ok());
assert!(versionless_v1.clone().try_into_versioned::<V2>().is_err());
assert!(versionless_v1
.clone()
.try_into_versioned::<Versionless>()
.is_ok());
let v2 = default_v2_heartbeat_frame();
assert!(v2.clone().try_into_versioned::<V1>().is_err());
assert!(v2.clone().try_into_versioned::<V2>().is_ok());
assert!(v2.clone().try_into_versioned::<Versionless>().is_ok());
let versionless_v2 = v2.into_versionless();
assert!(versionless_v2.clone().try_into_versioned::<V1>().is_err());
assert!(versionless_v2.clone().try_into_versioned::<V2>().is_ok());
assert!(versionless_v2
.clone()
.try_into_versioned::<Versionless>()
.is_ok());
}
#[test]
fn serialize_deserialize_v1_frame() {
use crate::consts::STX_V1;
const FRAME_LENGTH: usize = 16;
let sequence = [
21, 21, 21, STX_V1, 8, 42, 10, 255, 31, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 41, 41, 41, ];
let frame_bytes = &sequence[3..FRAME_LENGTH + 3];
let frame_with_extra_bytes = &sequence[3..];
let frame = unsafe { Frame::<V1>::deserialize(frame_bytes) }.unwrap();
assert_eq!(frame.payload_length(), 8);
assert_eq!(frame.sequence(), 42);
assert_eq!(frame.system_id(), 10);
assert_eq!(frame.component_id(), 255);
assert_eq!(frame.message_id(), 31);
assert_eq!(frame.payload.bytes(), &[1, 2, 3, 4, 5, 6, 7, 8]);
assert_eq!(frame.checksum(), 0);
let mut serialized: [u8; FRAME_LENGTH] = [0; FRAME_LENGTH];
frame.serialize(&mut serialized).unwrap();
assert_eq!(serialized, frame_bytes);
let frame = unsafe { Frame::<V1>::deserialize(frame_with_extra_bytes) }.unwrap();
let mut serialized: [u8; FRAME_LENGTH] = [0; FRAME_LENGTH];
frame.serialize(&mut serialized).unwrap();
assert_eq!(serialized, frame_bytes);
assert!(matches!(
unsafe { Frame::<V1>::deserialize(&sequence) },
Err(FrameError::InvalidHeader)
));
}
#[test]
fn serialize_deserialize_v2_frame() {
use crate::consts::STX_V2;
const FRAME_LENGTH: usize = 33;
let sequence = [
21, 21, 21, STX_V2, 8, 1, 0, 42, 10, 255, 2, 1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 41, 41, 41, ];
let frame_bytes = &sequence[3..FRAME_LENGTH + 3];
let frame_with_extra_bytes = &sequence[3..];
let frame = unsafe { Frame::<V2>::deserialize(frame_bytes) }.unwrap();
assert_eq!(frame.payload_length(), 8);
assert_eq!(frame.sequence(), 42);
assert_eq!(frame.system_id(), 10);
assert_eq!(frame.component_id(), 255);
assert_eq!(frame.message_id(), 258);
assert_eq!(frame.payload.bytes(), &[1, 2, 3, 4, 5, 6, 7, 8]);
assert_eq!(frame.checksum(), 0);
assert_eq!(
frame.signature().unwrap().to_byte_array().as_slice(),
&[1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3]
);
let mut serialized: [u8; FRAME_LENGTH] = [0; FRAME_LENGTH];
frame.serialize(&mut serialized).unwrap();
assert_eq!(serialized, frame_bytes);
let frame = unsafe { Frame::<V2>::deserialize(frame_with_extra_bytes) }.unwrap();
let mut serialized: [u8; FRAME_LENGTH] = [0; FRAME_LENGTH];
frame.serialize(&mut serialized).unwrap();
assert_eq!(serialized, frame_bytes);
assert!(matches!(
unsafe { Frame::<V1>::deserialize(&sequence) },
Err(FrameError::InvalidHeader)
));
}
}