use std::fmt;
#[derive(Debug)]
pub struct ModbusPacket {
pub transaction_id: u16,
pub protocol_id: u16,
pub length: u16,
pub unit_id: u8,
pub function_code: u8,
pub data: Vec<u8>,
}
impl fmt::Display for ModbusPacket {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Modbus Packet: transaction_id={}, protocol_id={}, length={}, unit_id={}, function_code={}, data={:02X?}",
self.transaction_id, self.protocol_id, self.length, self.unit_id, self.function_code, self.data
)
}
}
fn check_minimum_length(payload: &[u8]) -> Result<(), bool> {
if payload.len() < 8 {
return Err(false);
}
Ok(())
}
fn extract_transaction_id(payload: &[u8]) -> u16 {
u16::from_be_bytes([payload[0], payload[1]])
}
fn extract_protocol_id(payload: &[u8]) -> u16 {
u16::from_be_bytes([payload[2], payload[3]])
}
fn extract_length(payload: &[u8]) -> u16 {
u16::from_be_bytes([payload[4], payload[5]])
}
fn extract_unit_id(payload: &[u8]) -> u8 {
payload[6]
}
fn extract_function_code(payload: &[u8]) -> u8 {
payload[7]
}
fn extract_data(payload: &[u8]) -> Vec<u8> {
payload[8..].to_vec()
}
fn check_protocol_id(protocol_id: u16) -> Result<(), bool> {
if protocol_id != 0x0000 {
return Err(false);
}
Ok(())
}
fn check_function_code(function_code: u8) -> Result<(), bool> {
let valid_function_codes = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x0F, 0x10];
if !valid_function_codes.contains(&function_code) {
return Err(false);
}
Ok(())
}
pub fn parse_modbus_packet(payload: &[u8]) -> Result<ModbusPacket, bool> {
check_minimum_length(payload)?;
let transaction_id = extract_transaction_id(payload);
let protocol_id = extract_protocol_id(payload);
check_protocol_id(protocol_id)?;
let length = extract_length(payload);
let unit_id = extract_unit_id(payload);
let function_code = extract_function_code(payload);
check_function_code(function_code)?;
let data = extract_data(payload);
Ok(ModbusPacket {
transaction_id,
protocol_id,
length,
unit_id,
function_code,
data,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_modbus_packet() {
let modbus_payload = vec![
0x5A, 0x3C, 0x00, 0x00, 0x00, 0x06, 0x11, 0x03, 0x02, 0x04, 0x00, 0x01,
];
match parse_modbus_packet(&modbus_payload) {
Ok(packet) => {
assert_eq!(packet.transaction_id, 0x5A3C);
assert_eq!(packet.protocol_id, 0x0000);
assert_eq!(packet.length, 6);
assert_eq!(packet.unit_id, 0x11);
assert_eq!(packet.function_code, 0x03);
assert_eq!(packet.data, vec![0x02, 0x04, 0x00, 0x01]);
}
Err(_) => panic!("Expected Modbus packet"),
}
let short_payload = vec![0x5A, 0x3C];
match parse_modbus_packet(&short_payload) {
Ok(_) => panic!("Expected non-Modbus packet due to short payload"),
Err(is_modbus) => assert!(!is_modbus),
}
let invalid_protocol_id_payload = vec![
0x5A, 0x3C, 0x00, 0x01, 0x00, 0x06, 0x11, 0x03, 0x02, 0x04, 0x00, 0x01,
];
match parse_modbus_packet(&invalid_protocol_id_payload) {
Ok(_) => panic!("Expected non-Modbus packet due to invalid protocol id"),
Err(is_modbus) => assert!(!is_modbus),
}
let invalid_function_code_payload = vec![
0x5A, 0x3C, 0x00, 0x00, 0x00, 0x06, 0x11, 0xFF, 0x02, 0x04, 0x00, 0x01,
];
match parse_modbus_packet(&invalid_function_code_payload) {
Ok(_) => panic!("Expected non-Modbus packet due to invalid function code"),
Err(is_modbus) => assert!(!is_modbus),
}
}
#[test]
fn test_check_minimum_length() {
assert!(check_minimum_length(&vec![1, 2, 3, 4, 5, 6, 7, 8]).is_ok());
assert!(check_minimum_length(&vec![1, 2, 3, 4, 5, 6, 7]).is_err());
}
#[test]
fn test_extract_transaction_id() {
assert_eq!(
extract_transaction_id(&vec![0x5A, 0x3C, 0x00, 0x00, 0x00, 0x06, 0x11, 0x03]),
0x5A3C
);
}
#[test]
fn test_extract_protocol_id() {
assert_eq!(
extract_protocol_id(&vec![0x5A, 0x3C, 0x00, 0x00, 0x00, 0x06, 0x11, 0x03]),
0x0000
);
}
#[test]
fn test_extract_length() {
assert_eq!(
extract_length(&vec![0x5A, 0x3C, 0x00, 0x00, 0x00, 0x06, 0x11, 0x03]),
6
);
}
#[test]
fn test_extract_unit_id() {
assert_eq!(
extract_unit_id(&vec![0x5A, 0x3C, 0x00, 0x00, 0x00, 0x06, 0x11, 0x03]),
0x11
);
}
#[test]
fn test_extract_function_code() {
assert_eq!(
extract_function_code(&vec![0x5A, 0x3C, 0x00, 0x00, 0x00, 0x06, 0x11, 0x03]),
0x03
);
}
#[test]
fn test_extract_data() {
assert_eq!(
extract_data(&vec![
0x5A, 0x3C, 0x00, 0x00, 0x00, 0x06, 0x11, 0x03, 0x02, 0x04, 0x00, 0x01
]),
vec![0x02, 0x04, 0x00, 0x01]
);
}
}