use std::borrow::Cow;
use std::fmt::{self, Display, Formatter};
use std::io::{BufRead, BufReader, Read, Write};
use std::str;
use derive_more::{Display, LowerHex, UpperHex};
use lazy_static::lazy_static;
use num_traits::Num;
use regex::bytes::Regex;
use thiserror::Error;
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum FrameError {
#[error("Maximum data length is {} bytes, got {}", max, actual)]
DataTooLong {
max: u8,
actual: usize,
},
#[error("Failed reading/writing a frame of data")]
Io {
#[from]
source: std::io::Error,
},
#[error("Failed to parse invalid Intel HEX [{}] into a Frame", string_for_error(data))]
InvalidFrame {
data: Vec<u8>,
},
#[error(
"Frame data [{}] didn't match declared length: Expected {}, got {}",
string_for_error(data),
expected,
actual
)]
FrameDataMismatch {
data: Vec<u8>,
expected: usize,
actual: usize,
},
#[error(
"Frame checksum for [{}] didn't match declared checksum: Expected 0x{:X}, got 0x{:X}",
string_for_error(data),
expected,
actual
)]
BadChecksum {
data: Vec<u8>,
expected: u8,
actual: u8,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Frame<'a> {
address: Address,
message_type: MsgType,
data: Data<'a>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Display, LowerHex, UpperHex)]
pub struct MsgType(pub u8);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Display, LowerHex, UpperHex)]
pub struct Address(pub u16);
impl<'a> Frame<'a> {
pub fn new(address: Address, message_type: MsgType, data: Data<'a>) -> Self {
Frame {
address,
message_type,
data,
}
}
pub fn message_type(&self) -> MsgType {
self.message_type
}
pub fn address(&self) -> Address {
self.address
}
pub fn data(&self) -> &Cow<'a, [u8]> {
&self.data.0
}
pub fn into_data(self) -> Data<'a> {
self.data
}
pub fn to_bytes(&self) -> Vec<u8> {
const HEX_DIGITS: &[u8] = b"0123456789ABCDEF";
let mut payload = self.payload();
let checksum = checksum(&payload);
payload.push(checksum);
let payload = payload;
let mut output = Vec::<u8>::with_capacity(1 + 2 * payload.len() + 2);
output.push(b':');
for byte in &payload {
output.push(HEX_DIGITS[(byte >> 4) as usize]);
output.push(HEX_DIGITS[(byte & 0x0F) as usize]);
}
assert_eq!(output.len(), output.capacity() - 2);
output
}
pub fn to_bytes_with_newline(&self) -> Vec<u8> {
let mut output = self.to_bytes();
output.extend_from_slice(b"\r\n");
assert_eq!(output.len(), output.capacity());
output
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, FrameError> {
lazy_static! {
static ref RE: Regex = Regex::new(r"(?x)
^: # Colon marks beginning of frame
(?P<data_len>[[:xdigit:]]{2}) # 2 hex digits for data length
(?P<address>[[:xdigit:]]{4}) # 4 hex digits for address
(?P<message_type>[[:xdigit:]]{2}) # 2 hex digits for message type
(?P<data>(?:[[:xdigit:]]{2})*) # Zero or more groups of 2 hex digits for data
(?P<checksum>[[:xdigit:]]{2}) # 2 hex digits for checksum
(?:\r\n)?$ # Optional newline sequence
").unwrap(); }
let captures = RE
.captures(bytes)
.ok_or_else(|| FrameError::InvalidFrame { data: bytes.into() })?;
let data_len = parse_hex::<u8>(captures.name("data_len").unwrap().as_bytes());
let address = parse_hex::<u16>(captures.name("address").unwrap().as_bytes());
let message_type = parse_hex::<u8>(captures.name("message_type").unwrap().as_bytes());
let data_bytes = captures.name("data").unwrap().as_bytes();
let provided_checksum = parse_hex::<u8>(captures.name("checksum").unwrap().as_bytes());
let data = data_bytes.chunks(2).map(parse_hex::<u8>).collect::<Vec<_>>();
if data.len() != data_len as usize {
return Err(FrameError::FrameDataMismatch {
data: bytes.into(),
expected: data_len as usize,
actual: data.len(),
});
}
let frame = Frame::new(Address(address), MsgType(message_type), Data::try_new(data)?);
let payload = frame.payload();
let computed_checksum = checksum(&payload);
if computed_checksum != provided_checksum {
return Err(FrameError::BadChecksum {
data: bytes.into(),
expected: provided_checksum,
actual: computed_checksum,
});
}
Ok(frame)
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<(), FrameError> {
writer.write_all(&self.to_bytes_with_newline())?;
Ok(())
}
pub fn read<R: Read>(mut reader: &mut R) -> Result<Self, FrameError> {
let mut buf_reader = BufReader::with_capacity(1, &mut reader);
let mut data = Vec::<u8>::new();
let _ = buf_reader.read_until(b'\n', &mut data)?;
let frame = Frame::from_bytes(&data)?;
Ok(frame)
}
fn payload(&self) -> Vec<u8> {
let mut payload = Vec::<u8>::with_capacity(5 + self.data.0.len());
payload.push(self.data.0.len() as u8);
payload.push((self.address.0 >> 8) as u8);
payload.push(self.address.0 as u8);
payload.push(self.message_type.0);
payload.extend_from_slice(&self.data.0);
assert_eq!(payload.len(), payload.capacity() - 1);
payload
}
}
impl Display for Frame<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Type {:02X} | Addr {:04X}", self.message_type.0, self.address.0)?;
if self.data.0.len() > 0 {
write!(f, " | Data ")?;
for byte in self.data.0.iter() {
write!(f, "{:02X} ", byte)?;
}
}
Ok(())
}
}
fn parse_hex<T: Num>(bytes: &[u8]) -> T
where
<T as Num>::FromStrRadixErr: 'static + ::std::error::Error,
{
let string = str::from_utf8(bytes).unwrap();
T::from_str_radix(string, 16).unwrap()
}
fn string_for_error(bytes: &[u8]) -> String {
String::from_utf8_lossy(bytes).trim().to_string()
}
fn checksum(bytes: &[u8]) -> u8 {
bytes.iter().fold(0, |acc, &b| acc.wrapping_sub(b))
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Data<'a>(Cow<'a, [u8]>);
impl<'a> Data<'a> {
pub fn try_new<T: Into<Cow<'a, [u8]>>>(data: T) -> Result<Self, FrameError> {
let data: Cow<'a, [u8]> = data.into();
if data.len() > 0xFF {
return Err(FrameError::DataTooLong {
max: 0xFF,
actual: data.len(),
});
}
Ok(Data(data))
}
pub fn get(&self) -> &Cow<'a, [u8]> {
&self.0
}
}
macro_rules! impl_from_array_ref_with_length {
($length:expr) => {
impl From<&'static [u8; $length]> for Data<'static> {
fn from(value: &'static [u8; $length]) -> Data<'static> {
Data::try_new(&value[..]).unwrap()
}
}
};
}
impl_from_array_ref_with_length!(0);
impl_from_array_ref_with_length!(1);
impl_from_array_ref_with_length!(2);
impl_from_array_ref_with_length!(3);
impl_from_array_ref_with_length!(4);
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error;
#[test]
fn roundtrip_simple_frame() -> Result<(), Box<dyn Error>> {
let frame = Frame::new(Address(0x7F), MsgType(0x02), Data::from(&[0xFF]));
let encoded = frame.to_bytes();
let decoded = Frame::from_bytes(&encoded)?;
assert_eq!(b":01007F02FF7F", encoded.as_slice());
assert_eq!(frame, decoded);
Ok(())
}
#[test]
fn roundtrip_complex_frame() -> Result<(), Box<dyn Error>> {
let data = Data::try_new(vec![
0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x7F, 0x06, 0x0C, 0x18, 0x7F, 0x7F, 0x00,
])?;
let frame = Frame::new(Address(0x00), MsgType(0x00), data);
let encoded = frame.to_bytes();
let decoded = Frame::from_bytes(&encoded)?;
assert_eq!(&b":1000000001100000000000007F7F060C187F7F00B9"[..], encoded.as_slice());
assert_eq!(frame, decoded);
Ok(())
}
#[test]
fn roundtrip_complex_frame_newline() -> Result<(), Box<dyn Error>> {
let data = Data::try_new(vec![
0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x7F, 0x06, 0x0C, 0x18, 0x7F, 0x7F, 0x00,
])?;
let frame = Frame::new(Address(0x00), MsgType(0x00), data);
let encoded = frame.to_bytes_with_newline();
let decoded = Frame::from_bytes(&encoded)?;
assert_eq!(&b":1000000001100000000000007F7F060C187F7F00B9\r\n"[..], encoded.as_slice());
assert_eq!(frame, decoded);
Ok(())
}
#[test]
fn roundtrip_empty_data() -> Result<(), Box<dyn Error>> {
let frame = Frame::new(Address(0x2B), MsgType(0xA9), Data::from(&[]));
let encoded = frame.to_bytes();
let decoded = Frame::from_bytes(&encoded)?;
assert_eq!(b":00002BA92C", encoded.as_slice());
assert_eq!(frame, decoded);
Ok(())
}
#[test]
fn data_length_over_255_rejected() {
let error = Data::try_new(vec![0; 256]).unwrap_err();
assert!(matches!(
error,
FrameError::DataTooLong {
max: 255,
actual: 256,
..
}
));
}
#[test]
fn newline_accepted() -> Result<(), Box<dyn Error>> {
let decoded = Frame::from_bytes(b":01007F02FF7F\r\n")?;
assert_eq!(Frame::new(Address(0x7F), MsgType(0x02), Data::from(&[0xFF])), decoded);
Ok(())
}
#[test]
fn bad_checksum_detected() {
let error = Frame::from_bytes(b":01007F02FF7E").unwrap_err();
assert!(matches!(
error,
FrameError::BadChecksum {
expected: 0x7E,
actual: 0x7F,
..
}
));
}
#[test]
fn extra_data_detected() {
let error = Frame::from_bytes(b":00007F02007F").unwrap_err();
assert!(matches!(
error,
FrameError::FrameDataMismatch {
expected: 0,
actual: 1,
..
}
));
}
#[test]
fn missing_data_detected() {
let error = Frame::from_bytes(b":01007F027E").unwrap_err();
assert!(matches!(
error,
FrameError::FrameDataMismatch {
expected: 1,
actual: 0,
..
}
));
}
#[test]
fn invalid_format_detected() {
let error = Frame::from_bytes(b":01").unwrap_err();
assert!(matches!(error, FrameError::InvalidFrame { .. }));
}
#[test]
fn garbage_detected() {
let error = Frame::from_bytes(b"asdgdfg").unwrap_err();
assert!(matches!(error, FrameError::InvalidFrame { .. }));
}
#[test]
fn bad_char_detected() {
let error = Frame::from_bytes(b":01007F020z7E").unwrap_err();
assert!(matches!(error, FrameError::InvalidFrame { .. }));
}
#[test]
fn missing_char_detected() {
let error = Frame::from_bytes(b":01007F0207E").unwrap_err();
assert!(matches!(error, FrameError::InvalidFrame { .. }));
}
#[test]
fn leading_chars_detected() {
let error = Frame::from_bytes(b"abc:01007F02FF7Fa").unwrap_err();
assert!(matches!(error, FrameError::InvalidFrame { .. }));
}
#[test]
fn trailing_chars_detected() {
let error = Frame::from_bytes(b":01007F02FF7Fabc").unwrap_err();
assert!(matches!(error, FrameError::InvalidFrame { .. }));
}
#[test]
fn parsed_lifetime_independent() -> Result<(), Box<dyn Error>> {
let decoded = {
let string = b":01007F02FF7F".to_owned();
Frame::from_bytes(&string)?
};
assert_eq!(Frame::new(Address(0x7F), MsgType(0x02), Data::from(&[0xFF])), decoded);
Ok(())
}
#[test]
fn getters() {
let frame = Frame::new(Address(0x7F), MsgType(0x02), Data::from(&[0xFF]));
assert_eq!(frame.message_type(), MsgType(0x02));
assert_eq!(frame.address(), Address(0x7F));
assert_eq!(frame.data(), &vec![0xFFu8]);
}
#[test]
fn write() -> Result<(), Box<dyn Error>> {
let frame = Frame::new(Address(0x7F), MsgType(0x02), Data::from(&[0xFF]));
let mut output = Vec::new();
frame.write(&mut output)?;
assert_eq!(b":01007F02FF7F\r\n", output.as_slice());
Ok(())
}
#[test]
fn read() -> Result<(), Box<dyn Error>> {
let mut buffer = &b":01007F02FF7F\r\n"[..];
let frame = Frame::read(&mut buffer)?;
assert_eq!(Frame::new(Address(0x7F), MsgType(0x02), Data::from(&[0xFF])), frame);
Ok(())
}
#[test]
fn display() {
let frame = Frame::new(Address(0x7F), MsgType(0x02), Data::from(&[0xFF, 0xCB]));
let display = format!("{}", frame);
assert_eq!("Type 02 | Addr 007F | Data FF CB", display.trim());
}
}