use rusty_modbus_types::{
ExceptionCode, MAX_READ_COILS, MAX_READ_DISCRETE_INPUTS, MAX_READ_REGISTERS,
MAX_RW_READ_REGISTERS, MAX_RW_WRITE_REGISTERS, MAX_WRITE_COILS, MAX_WRITE_REGISTERS,
};
pub fn validate_read_coils(address: u16, quantity: u16) -> Result<(), ExceptionCode> {
if quantity == 0 || quantity > MAX_READ_COILS {
return Err(ExceptionCode::IllegalDataValue);
}
validate_address_range(address, quantity)?;
Ok(())
}
pub fn validate_read_discrete_inputs(address: u16, quantity: u16) -> Result<(), ExceptionCode> {
if quantity == 0 || quantity > MAX_READ_DISCRETE_INPUTS {
return Err(ExceptionCode::IllegalDataValue);
}
validate_address_range(address, quantity)?;
Ok(())
}
pub fn validate_read_registers(address: u16, quantity: u16) -> Result<(), ExceptionCode> {
if quantity == 0 || quantity > MAX_READ_REGISTERS {
return Err(ExceptionCode::IllegalDataValue);
}
validate_address_range(address, quantity)?;
Ok(())
}
pub fn validate_write_single_coil(coil_value: u16) -> Result<(), ExceptionCode> {
if coil_value != 0x0000 && coil_value != 0xFF00 {
return Err(ExceptionCode::IllegalDataValue);
}
Ok(())
}
pub fn validate_write_coils(
address: u16,
quantity: u16,
byte_count: u8,
) -> Result<(), ExceptionCode> {
if quantity == 0 || quantity > MAX_WRITE_COILS {
return Err(ExceptionCode::IllegalDataValue);
}
let expected_bytes = quantity.div_ceil(8);
if u16::from(byte_count) != expected_bytes {
return Err(ExceptionCode::IllegalDataValue);
}
validate_address_range(address, quantity)?;
Ok(())
}
pub fn validate_write_registers(
address: u16,
quantity: u16,
byte_count: u8,
) -> Result<(), ExceptionCode> {
if quantity == 0 || quantity > MAX_WRITE_REGISTERS {
return Err(ExceptionCode::IllegalDataValue);
}
if u16::from(byte_count) != quantity * 2 {
return Err(ExceptionCode::IllegalDataValue);
}
validate_address_range(address, quantity)?;
Ok(())
}
pub fn validate_mask_write_address(address: u16) -> Result<(), ExceptionCode> {
validate_address_range(address, 1)
}
pub fn validate_read_write_registers(
read_address: u16,
read_quantity: u16,
write_address: u16,
write_quantity: u16,
write_byte_count: u8,
) -> Result<(), ExceptionCode> {
if read_quantity == 0 || read_quantity > MAX_RW_READ_REGISTERS {
return Err(ExceptionCode::IllegalDataValue);
}
if write_quantity == 0 || write_quantity > MAX_RW_WRITE_REGISTERS {
return Err(ExceptionCode::IllegalDataValue);
}
if u16::from(write_byte_count) != write_quantity * 2 {
return Err(ExceptionCode::IllegalDataValue);
}
validate_address_range(read_address, read_quantity)?;
validate_address_range(write_address, write_quantity)?;
Ok(())
}
fn validate_address_range(address: u16, quantity: u16) -> Result<(), ExceptionCode> {
if u32::from(address) + u32::from(quantity) > 0x10000 {
return Err(ExceptionCode::IllegalDataAddress);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn read_coils_valid() {
assert!(validate_read_coils(0, 1).is_ok());
assert!(validate_read_coils(0, 2000).is_ok());
assert!(validate_read_coils(100, 100).is_ok());
}
#[test]
fn read_coils_quantity_zero() {
assert_eq!(
validate_read_coils(0, 0),
Err(ExceptionCode::IllegalDataValue)
);
}
#[test]
fn read_coils_quantity_too_large() {
assert_eq!(
validate_read_coils(0, 2001),
Err(ExceptionCode::IllegalDataValue)
);
}
#[test]
fn read_coils_address_overflow() {
assert_eq!(
validate_read_coils(0xFFFF, 2),
Err(ExceptionCode::IllegalDataAddress)
);
}
#[test]
fn quantity_checked_before_address() {
assert_eq!(
validate_read_coils(0xFFFF, 5000),
Err(ExceptionCode::IllegalDataValue)
);
}
#[test]
fn read_registers_valid() {
assert!(validate_read_registers(0, 1).is_ok());
assert!(validate_read_registers(0, 125).is_ok());
}
#[test]
fn read_registers_quantity_out_of_range() {
assert_eq!(
validate_read_registers(0, 126),
Err(ExceptionCode::IllegalDataValue)
);
}
#[test]
fn write_single_coil_valid_on() {
assert!(validate_write_single_coil(0xFF00).is_ok());
}
#[test]
fn write_single_coil_valid_off() {
assert!(validate_write_single_coil(0x0000).is_ok());
}
#[test]
fn write_single_coil_invalid_value() {
assert_eq!(
validate_write_single_coil(0x0001),
Err(ExceptionCode::IllegalDataValue)
);
assert_eq!(
validate_write_single_coil(0xFF01),
Err(ExceptionCode::IllegalDataValue)
);
}
#[test]
fn write_coils_valid() {
assert!(validate_write_coils(0, 8, 1).is_ok());
assert!(validate_write_coils(0, 9, 2).is_ok());
assert!(validate_write_coils(0, 1968, 246).is_ok());
}
#[test]
fn write_coils_byte_count_mismatch() {
assert_eq!(
validate_write_coils(0, 8, 2),
Err(ExceptionCode::IllegalDataValue)
);
}
#[test]
fn write_registers_valid() {
assert!(validate_write_registers(0, 1, 2).is_ok());
assert!(validate_write_registers(0, 123, 246).is_ok());
}
#[test]
fn write_registers_byte_count_mismatch() {
assert_eq!(
validate_write_registers(0, 1, 3),
Err(ExceptionCode::IllegalDataValue)
);
}
#[test]
fn mask_write_address_valid() {
assert!(validate_mask_write_address(0).is_ok());
assert!(validate_mask_write_address(0xFFFE).is_ok());
}
#[test]
fn mask_write_address_max_valid() {
assert!(validate_mask_write_address(0xFFFF).is_ok());
}
#[test]
fn read_write_registers_valid() {
assert!(validate_read_write_registers(0, 1, 0, 1, 2).is_ok());
assert!(validate_read_write_registers(0, 125, 0, 121, 242).is_ok());
}
#[test]
fn read_write_registers_read_overflow() {
assert_eq!(
validate_read_write_registers(0xFFFF, 2, 0, 1, 2),
Err(ExceptionCode::IllegalDataAddress)
);
}
}