use std::io::Result;
use crc16::{State as Crc16, MODBUS};
use tokio_util::{
bytes::Buf,
codec::{Decoder, Encoder},
};
use super::MessageKind;
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Frame {
pub address: u16,
pub msg_id: u8,
pub msg_type: u8,
pub kind: MessageKind,
pub data: Vec<u8>,
}
#[derive(Clone, Debug)]
pub(crate) enum MaybeFrame {
Frame(Frame),
CrcError(u16, u16),
}
#[derive(Clone, Copy)]
pub(crate) struct FrameCodec {}
impl FrameCodec {
const LEN_HEADER: usize = 10;
const LEN_FOOTER: usize = 2;
const LEN_FRAME_MIN: usize = Self::LEN_HEADER + Self::LEN_FOOTER;
const HEADER_MAGIC: u32 = 0x5555_55aa;
pub(crate) fn new() -> Self {
Self {}
}
}
macro_rules! get_ {
( $t:ty, $src:expr, $off:ident ) => {{
let len_ = std::mem::size_of::<$t>();
let res_ = <$t>::from_be_bytes(($src)[$off..$off + len_].try_into().unwrap());
$off += len_;
res_
}};
}
macro_rules! peek_ {
( $t:ty, $src:expr, $off:ident ) => {{
let len_ = std::mem::size_of::<$t>();
let res_ = <$t>::from_be_bytes(($src)[$off..$off + len_].try_into().unwrap());
res_
}};
}
macro_rules! put_ {
( $t:ty, $val:expr, $dst:expr ) => {{
let slc_ = <$t>::to_be_bytes(($val) as $t);
($dst).extend_from_slice(&slc_);
}};
( $t:ty, $val:expr, $dst:expr, $crc:ident ) => {{
let slc_ = <$t>::to_be_bytes(($val) as $t);
($dst).extend_from_slice(&slc_);
($crc).update(&slc_);
}};
}
impl Decoder for FrameCodec {
type Item = MaybeFrame;
type Error = std::io::Error;
fn decode(&mut self, src: &mut tokio_util::bytes::BytesMut) -> Result<Option<Self::Item>> {
let mut p = 0;
loop {
if src.len() < p + std::mem::size_of_val(&Self::HEADER_MAGIC) {
src.reserve(Self::LEN_HEADER - src.len());
return Ok(None);
}
if peek_!(u32, src, p) == Self::HEADER_MAGIC {
src.advance(p);
p = std::mem::size_of_val(&Self::HEADER_MAGIC);
break;
}
p += 1;
}
if src.len() < Self::LEN_HEADER {
src.reserve(Self::LEN_HEADER - src.len());
return Ok(None);
}
let address = get_!(u16, src, p);
let msg_id = get_!(u8, src, p);
let msg_type = get_!(u8, src, p);
let data_len = get_!(u16, src, p) as usize;
let expected_bytes = data_len + Self::LEN_FRAME_MIN;
if src.len() < expected_bytes {
src.reserve(expected_bytes - src.len());
return Ok(None);
}
let data = src.split_to(expected_bytes);
let crc_calculated = Crc16::<MODBUS>::calculate(
&data[std::mem::size_of_val(&Self::HEADER_MAGIC)..Self::LEN_HEADER + data_len],
);
p += data_len;
let crc_expected = get_!(u16, data, p);
assert_eq!(p, expected_bytes);
if crc_calculated != crc_expected {
return Ok(Some(MaybeFrame::CrcError(crc_calculated, crc_expected)));
}
Ok(Some(MaybeFrame::Frame(Frame {
address,
msg_id,
msg_type,
kind: (msg_type, address).into(),
data: data[Self::LEN_HEADER..data.len() - Self::LEN_FOOTER].to_vec(),
})))
}
}
impl Encoder<Frame> for FrameCodec {
type Error = std::io::Error;
fn encode(&mut self, frame: Frame, dst: &mut tokio_util::bytes::BytesMut) -> Result<()> {
if frame.data.len() > u16::MAX as usize {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"data too large",
));
}
if frame.kind == MessageKind::Unknown || !frame.kind.is_valid(frame.msg_type, frame.address)
{
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"invalid message type",
));
}
let data = &frame.data[..];
dst.reserve(Self::LEN_FRAME_MIN + data.len());
put_!(u32, Self::HEADER_MAGIC, dst);
let mut crc = Crc16::<MODBUS>::new();
put_!(u16, frame.address, dst, crc);
put_!(u8, frame.msg_id, dst, crc);
put_!(u8, frame.msg_type, dst, crc);
put_!(u16, data.len(), dst, crc);
dst.extend_from_slice(data);
crc.update(data);
put_!(u16, crc.get(), dst);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::super::tests::data::*;
use super::*;
#[test]
fn test_get_macro() {
let buf = &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xff];
let mut p = 0;
assert_eq!(get_!(u32, buf, p), 0x01020304);
assert_eq!(p, 4);
assert_eq!(get_!(u8, buf, p), 0x05);
assert_eq!(get_!(u16, buf, p), 0x0607);
assert_eq!(get_!(i8, buf, p), -1);
assert_eq!(p, 8);
}
#[test]
fn test_peek_macro() {
let buf = &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xff];
let mut p = 0;
assert_eq!(peek_!(u32, buf, p), 0x01020304);
assert_eq!(p, 0);
assert_eq!(peek_!(u8, buf, p), 0x01);
assert_eq!(peek_!(u16, buf, p), 0x0102);
p = 7;
assert_eq!(peek_!(i8, buf, p), -1);
assert_eq!(p, 7);
}
#[test]
fn test_put_macro() {
let mut buf = Vec::with_capacity(12);
put_!(u32, 0x1234abcd, buf);
let mut crc = Crc16::<MODBUS>::new();
put_!(u32, 0x4321fedc, buf, crc);
put_!(u16, 0xacab, buf, crc);
put_!(u8, 19, buf, crc);
put_!(i8, -18, buf, crc);
let expected = &[
0x12, 0x34, 0xab, 0xcd, 0x43, 0x21, 0xfe, 0xdc, 0xac, 0xab, 0x13, 0xee,
];
assert_eq!(&buf[..], expected);
assert_eq!(crc.get(), Crc16::<MODBUS>::calculate(&expected[4..]));
}
#[test]
fn test_decode_request() {
let mut src = tokio_util::bytes::BytesMut::new();
let mut codec = FrameCodec::new();
assert_matches!(codec.decode(&mut src), Ok(None));
src.extend_from_slice(&MSG_REQ_STATUS_ZONES[..FrameCodec::LEN_HEADER]);
assert_matches!(codec.decode(&mut src), Ok(None));
assert!(src.capacity() >= MSG_REQ_STATUS_ZONES.len());
src.extend_from_slice(&MSG_REQ_STATUS_ZONES[FrameCodec::LEN_HEADER..]);
assert_matches!(codec.decode(&mut src), Ok(Some(MaybeFrame::Frame(frame))) => {
assert_eq!(frame.kind, MessageKind::ControlRequest);
assert_eq!(frame.msg_id, 1);
assert_eq!(frame.data.len(), 8);
});
}
#[test]
fn test_decode_request_badcrc() {
let mut src = tokio_util::bytes::BytesMut::new();
let mut codec = FrameCodec::new();
src.extend_from_slice(
&MSG_REQ_STATUS_ZONES[..MSG_REQ_STATUS_ZONES.len() - FrameCodec::LEN_FOOTER],
);
src.extend_from_slice(&[0xac, 0xab]);
assert_matches!(codec.decode(&mut src), Ok(Some(MaybeFrame::CrcError(calculated, expected))) => {
assert_eq!(expected, 0xacab);
assert_eq!(calculated, u16::from_be_bytes(MSG_REQ_STATUS_ZONES[MSG_REQ_STATUS_ZONES.len()-FrameCodec::LEN_FOOTER..].try_into().unwrap()));
});
}
#[test]
fn test_decode_multiple() {
let mut src = tokio_util::bytes::BytesMut::new();
let mut codec = FrameCodec::new();
src.extend_from_slice(MSG_REQ_STATUS_ZONES);
src.extend_from_slice(&decode(MSG_RESP_AC_CAP));
src.extend_from_slice(MSG_REQ_STATUS_ZONES);
assert_matches!(codec.decode(&mut src), Ok(Some(MaybeFrame::Frame(frame))) => {
assert_eq!(frame.kind, MessageKind::ControlRequest);
assert_eq!(frame.msg_id, 1);
assert_eq!(frame.data.len(), 8);
});
assert_matches!(codec.decode(&mut src), Ok(Some(MaybeFrame::Frame(frame))) => {
assert_eq!(frame.kind, MessageKind::ExtendedResponse);
assert_eq!(frame.msg_id, 1);
assert_eq!(frame.data.len(), 28);
});
assert_matches!(codec.decode(&mut src), Ok(Some(MaybeFrame::Frame(frame))) => {
assert_eq!(frame.kind, MessageKind::ControlRequest);
assert_eq!(frame.msg_id, 1);
assert_eq!(frame.data.len(), 8);
});
}
#[test]
fn test_decode_junk() {
let mut src = tokio_util::bytes::BytesMut::new();
let mut codec = FrameCodec::new();
let junk: &[u8] = &[0x55, 0x55, 0x55, 0xab, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x4c];
src.extend_from_slice(junk);
src.extend_from_slice(MSG_REQ_STATUS_ZONES);
src.extend_from_slice(junk);
src.extend_from_slice(&decode(MSG_RESP_AC_CAP));
assert_matches!(codec.decode(&mut src), Ok(Some(MaybeFrame::Frame(frame))) => {
assert_eq!(frame.kind, MessageKind::ControlRequest);
assert_eq!(frame.msg_id, 1);
assert_eq!(frame.data.len(), 8);
});
assert_matches!(codec.decode(&mut src), Ok(Some(MaybeFrame::Frame(frame))) => {
assert_eq!(frame.kind, MessageKind::ExtendedResponse);
assert_eq!(frame.msg_id, 1);
assert_eq!(frame.data.len(), 28);
});
}
#[test]
fn test_encode_request() {
let address: u16 = 0x80b0;
let msg_type: u8 = 0xc0;
let frame = Frame {
address,
msg_id: 1,
msg_type,
kind: (msg_type, address).into(),
data: MSG_REQ_STATUS_ZONES
[FrameCodec::LEN_HEADER..MSG_REQ_STATUS_ZONES.len() - FrameCodec::LEN_FOOTER]
.to_vec(),
};
let mut dst = tokio_util::bytes::BytesMut::new();
let mut codec = FrameCodec::new();
assert_matches!(codec.encode(frame, &mut dst), Ok(()) => {
assert_eq!(dst.len(), MSG_REQ_STATUS_ZONES.len());
assert_eq!(&dst[..], MSG_REQ_STATUS_ZONES);
});
}
#[test]
fn test_encode_request_eio() {
let address: u16 = 0x80b0;
let msg_type: u8 = 0x1f;
let frame = Frame {
address,
msg_id: 1,
msg_type,
kind: (msg_type, address).into(),
data: MSG_REQ_STATUS_ZONES
[FrameCodec::LEN_HEADER..MSG_REQ_STATUS_ZONES.len() - FrameCodec::LEN_FOOTER]
.to_vec(),
};
let mut dst = tokio_util::bytes::BytesMut::new();
let mut codec = FrameCodec::new();
assert_matches!(codec.encode(frame, &mut dst), Err(eio) => {
assert_eq!(eio.kind(), std::io::ErrorKind::InvalidData);
assert_eq!(eio.to_string(), "invalid message type");
});
}
}