use crate::crc::crc16;
use crate::error::ModbusError;
use crate::function::Exception;
use crate::response::Response;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Adu {
bytes: [u8; Adu::MAX_LEN],
len: usize,
}
impl Adu {
pub const MAX_LEN: usize = 256;
const MIN_LEN: usize = 4;
pub(crate) fn assemble(address: u8, pdu: &[u8]) -> Adu {
let len = 1 + pdu.len() + 2;
let mut bytes = [0u8; Self::MAX_LEN];
bytes[0] = address;
bytes[1..1 + pdu.len()].copy_from_slice(pdu);
let crc = crc16(&bytes[..1 + pdu.len()]);
bytes[1 + pdu.len()..len].copy_from_slice(&crc.to_le_bytes());
Adu { bytes, len }
}
pub fn from_pdu(address: u8, pdu: &[u8]) -> Result<Adu, ModbusError> {
if 1 + pdu.len() + 2 > Self::MAX_LEN {
return Err(ModbusError::FrameTooLong);
}
Ok(Self::assemble(address, pdu))
}
pub fn parse(bytes: &[u8]) -> Result<Adu, ModbusError> {
if bytes.len() < Self::MIN_LEN {
return Err(ModbusError::FrameTooShort);
}
if bytes.len() > Self::MAX_LEN {
return Err(ModbusError::FrameTooLong);
}
let split = bytes.len() - 2;
let expected = crc16(&bytes[..split]);
let found = u16::from_le_bytes([bytes[split], bytes[split + 1]]);
if expected != found {
return Err(ModbusError::CrcMismatch { expected, found });
}
let mut buffer = [0u8; Self::MAX_LEN];
buffer[..bytes.len()].copy_from_slice(bytes);
Ok(Adu {
bytes: buffer,
len: bytes.len(),
})
}
pub fn address(&self) -> u8 {
self.bytes[0]
}
pub fn function_code(&self) -> u8 {
self.bytes[1]
}
pub fn pdu(&self) -> &[u8] {
&self.bytes[1..self.len - 2]
}
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..self.len]
}
pub fn exception(&self) -> Option<Exception> {
self.response().exception()
}
pub fn response(&self) -> Response<'_> {
Response::new(self.pdu())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_pdu_then_parse_round_trips() {
let frame = Adu::from_pdu(0x11, &[0x03, 0x00, 0x6B, 0x00, 0x03]).unwrap();
let parsed = Adu::parse(frame.as_bytes()).unwrap();
assert_eq!(parsed.address(), 0x11);
assert_eq!(parsed.function_code(), 0x03);
assert_eq!(parsed.pdu(), &[0x03, 0x00, 0x6B, 0x00, 0x03]);
}
#[test]
fn parse_accepts_the_spec_request_frame() {
let parsed = Adu::parse(&[0x11, 0x03, 0x00, 0x6B, 0x00, 0x03, 0x76, 0x87]).unwrap();
assert_eq!(parsed.address(), 0x11);
assert_eq!(parsed.pdu(), &[0x03, 0x00, 0x6B, 0x00, 0x03]);
}
#[test]
fn parse_rejects_a_corrupt_crc() {
let result = Adu::parse(&[0x11, 0x03, 0x00, 0x6B, 0x00, 0x03, 0x00, 0x00]);
assert_eq!(
result,
Err(ModbusError::CrcMismatch {
expected: 0x8776,
found: 0x0000
})
);
}
#[test]
fn parse_rejects_a_short_frame() {
assert_eq!(
Adu::parse(&[0x11, 0x03, 0x76]),
Err(ModbusError::FrameTooShort)
);
}
#[test]
fn parse_rejects_an_oversized_frame() {
let frame = [0u8; Adu::MAX_LEN + 1];
assert_eq!(Adu::parse(&frame), Err(ModbusError::FrameTooLong));
}
#[test]
fn an_exception_response_surfaces_its_code() {
let frame = Adu::from_pdu(0x11, &[0x83, 0x02]).unwrap();
let parsed = Adu::parse(frame.as_bytes()).unwrap();
assert_eq!(parsed.exception(), Some(Exception::IllegalDataAddress));
}
#[test]
fn a_normal_response_has_no_exception() {
let frame = Adu::from_pdu(0x11, &[0x03, 0x02, 0x00, 0x64]).unwrap();
assert_eq!(frame.exception(), None);
}
#[test]
fn a_maximum_length_frame_round_trips() {
let pdu = [0xAB; 253];
let frame = Adu::from_pdu(0x01, &pdu).unwrap();
assert_eq!(frame.as_bytes().len(), Adu::MAX_LEN);
let parsed = Adu::parse(frame.as_bytes()).unwrap();
assert_eq!(parsed.pdu(), &pdu[..]);
}
#[test]
fn an_exception_bit_without_a_code_is_not_an_exception() {
let frame = Adu::from_pdu(0x11, &[0x83]).unwrap();
let parsed = Adu::parse(frame.as_bytes()).unwrap();
assert_eq!(parsed.exception(), None);
}
}