use core::fmt;
#[cfg(feature = "alloc")]
use alloc::{vec, vec::Vec};
use super::field::{Address, Control, ControlError};
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct ShortFrame {
control: Control,
address: Address,
}
impl ShortFrame {
pub const START: u8 = 0x10;
pub const STOP: u8 = 0x16;
pub const LEN: usize = 5;
pub const fn new(control: Control, address: Address) -> Result<Self, ShortFrameError> {
if let Err(error) = control.validate_short_frame() {
return Err(ShortFrameError::Control(error));
}
Ok(Self { control, address })
}
#[must_use]
pub const fn control(&self) -> Control {
self.control
}
#[must_use]
pub const fn address(&self) -> Address {
self.address
}
pub fn decode(bytes: &[u8]) -> Result<Self, ShortFrameError> {
if bytes.len() != Self::LEN {
return Err(ShortFrameError::InvalidLength {
actual: bytes.len(),
});
}
if bytes[0] != Self::START {
return Err(ShortFrameError::InvalidStart { actual: bytes[0] });
}
if bytes[4] != Self::STOP {
return Err(ShortFrameError::InvalidStop { actual: bytes[4] });
}
let expected = Self::checksum(bytes[1], bytes[2]);
if bytes[3] != expected {
return Err(ShortFrameError::InvalidChecksum {
expected,
actual: bytes[3],
});
}
Self::new(Control::new(bytes[1]), Address::new(bytes[2]))
}
pub fn encode_into<'a>(&self, output: &'a mut [u8]) -> Result<&'a [u8], ShortFrameError> {
if output.len() < Self::LEN {
return Err(ShortFrameError::OutputTooSmall {
actual: output.len(),
});
}
let control = self.control.value();
let address = self.address.value();
output[..Self::LEN].copy_from_slice(&[
Self::START,
control,
address,
Self::checksum(control, address),
Self::STOP,
]);
Ok(&output[..Self::LEN])
}
#[cfg(feature = "alloc")]
pub fn encode(&self) -> Vec<u8> {
let control = self.control.value();
let address = self.address.value();
vec![
Self::START,
control,
address,
Self::checksum(control, address),
Self::STOP,
]
}
const fn checksum(control: u8, address: u8) -> u8 {
control.wrapping_add(address)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[allow(missing_docs)]
pub enum ShortFrameError {
InvalidLength { actual: usize },
InvalidStart { actual: u8 },
InvalidStop { actual: u8 },
InvalidChecksum { expected: u8, actual: u8 },
Control(ControlError),
OutputTooSmall { actual: usize },
}
impl fmt::Display for ShortFrameError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidLength { actual } => write!(
formatter,
"invalid short frame length: expected 5, got {actual}"
),
Self::InvalidStart { actual } => write!(
formatter,
"invalid short frame start: expected 0x10, got 0x{actual:02x}"
),
Self::InvalidStop { actual } => write!(
formatter,
"invalid short frame stop: expected 0x16, got 0x{actual:02x}"
),
Self::InvalidChecksum { expected, actual } => write!(
formatter,
"invalid short frame checksum: expected 0x{expected:02x}, got 0x{actual:02x}"
),
Self::Control(error) => error.fmt(formatter),
Self::OutputTooSmall { actual } => write!(
formatter,
"short frame output buffer too small: expected 5, got {actual}"
),
}
}
}
impl core::error::Error for ShortFrameError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::Control(error) => Some(error),
_ => None,
}
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
#[cfg(feature = "alloc")]
use alloc::string::ToString;
const FRAME: [u8; 5] = [0x10, 0x40, 0x01, 0x41, 0x16];
#[test]
fn encodes_and_decodes_specification_frame() {
let frame = ShortFrame::new(Control::new(0x40), Address::new(1)).unwrap();
let mut output = [0; 6];
assert_eq!(frame.encode_into(&mut output).unwrap(), FRAME);
assert_eq!(ShortFrame::decode(&FRAME), Ok(frame));
assert_eq!(frame.control(), Control::new(0x40));
assert_eq!(frame.address(), Address::new(1));
}
#[cfg(feature = "alloc")]
#[test]
fn allocates_encoded_frame() {
let frame = ShortFrame::decode(&FRAME).unwrap();
assert_eq!(frame.encode(), FRAME);
}
#[test]
fn validates_constructor_fields() {
assert_eq!(
ShortFrame::new(Control::new(0x53), Address::new(1)),
Err(ShortFrameError::Control(
ControlError::InvalidForShortFrame { value: 0x53 }
))
);
assert!(ShortFrame::new(Control::new(0x40), Address::new(252)).is_ok());
let error = ShortFrameError::Control(ControlError::InvalidForShortFrame { value: 0x53 });
assert!(core::error::Error::source(&error).is_some());
assert!(
core::error::Error::source(&ShortFrameError::InvalidLength { actual: 0 }).is_none()
);
}
#[test]
fn rejects_each_structural_error() {
assert_eq!(
ShortFrame::decode(&FRAME[..4]),
Err(ShortFrameError::InvalidLength { actual: 4 })
);
assert_eq!(
ShortFrame::decode(&[FRAME.as_slice(), &[0]].concat()),
Err(ShortFrameError::InvalidLength { actual: 6 })
);
let mut bytes = FRAME;
bytes[0] = 0;
assert_eq!(
ShortFrame::decode(&bytes),
Err(ShortFrameError::InvalidStart { actual: 0 })
);
bytes = FRAME;
bytes[4] = 0;
assert_eq!(
ShortFrame::decode(&bytes),
Err(ShortFrameError::InvalidStop { actual: 0 })
);
bytes = FRAME;
bytes[3] = 0;
assert_eq!(
ShortFrame::decode(&bytes),
Err(ShortFrameError::InvalidChecksum {
expected: 0x41,
actual: 0
})
);
}
#[test]
fn rejects_semantically_invalid_decoded_fields() {
assert_eq!(
ShortFrame::decode(&[0x10, 0x53, 1, 0x54, 0x16]),
Err(ShortFrameError::Control(
ControlError::InvalidForShortFrame { value: 0x53 }
))
);
assert_eq!(
ShortFrame::decode(&[0x10, 0x40, 252, 0x3c, 0x16])
.unwrap()
.address(),
Address::new(252)
);
}
#[test]
fn wraps_checksum_and_rejects_small_output() {
let frame = ShortFrame::new(Control::new(0x7b), Address::new(255)).unwrap();
let mut output = [0; 5];
assert_eq!(
frame.encode_into(&mut output).unwrap(),
[0x10, 0x7b, 0xff, 0x7a, 0x16]
);
assert_eq!(
frame.encode_into(&mut [0; 4]),
Err(ShortFrameError::OutputTooSmall { actual: 4 })
);
}
#[cfg(feature = "alloc")]
#[test]
fn formats_errors() {
assert_eq!(
ShortFrameError::InvalidLength { actual: 0 }.to_string(),
"invalid short frame length: expected 5, got 0"
);
assert_eq!(
ShortFrameError::InvalidStart { actual: 0 }.to_string(),
"invalid short frame start: expected 0x10, got 0x00"
);
assert_eq!(
ShortFrameError::InvalidStop { actual: 0 }.to_string(),
"invalid short frame stop: expected 0x16, got 0x00"
);
assert_eq!(
ShortFrameError::InvalidChecksum {
expected: 1,
actual: 2
}
.to_string(),
"invalid short frame checksum: expected 0x01, got 0x02"
);
assert_eq!(
ShortFrameError::Control(ControlError::InvalidForShortFrame { value: 0x53 })
.to_string(),
"control value 0x53 is invalid for a short frame"
);
assert_eq!(
ShortFrameError::OutputTooSmall { actual: 4 }.to_string(),
"short frame output buffer too small: expected 5, got 4"
);
}
}