use crate::register::RegisterType;
use super::{ExceptionCode, FunctionHandler, FunctionHandlerExt, HandlerContext};
fn validate_write_quantity(quantity: u16, reg_type: RegisterType) -> Result<(), ExceptionCode> {
if quantity == 0 {
return Err(ExceptionCode::IllegalDataValue);
}
let max = reg_type.max_write_quantity();
if quantity > max {
return Err(ExceptionCode::IllegalDataValue);
}
Ok(())
}
#[derive(Debug, Clone, Copy, Default)]
pub struct WriteSingleCoilHandler;
impl FunctionHandler for WriteSingleCoilHandler {
fn function_code(&self) -> u8 {
0x05
}
fn handle(&self, pdu: &[u8], ctx: &HandlerContext) -> Result<Vec<u8>, ExceptionCode> {
self.validate_pdu_length(pdu)?;
let address = self.parse_address(pdu, 1)?;
let value = u16::from_be_bytes([pdu[3], pdu[4]]);
if value != 0x0000 && value != 0xFF00 {
return Err(ExceptionCode::IllegalDataValue);
}
let coil_value = value == 0xFF00;
ctx.registers
.write_coil(address, coil_value)
.map_err(|_| ExceptionCode::IllegalDataAddress)?;
Ok(pdu.to_vec())
}
fn name(&self) -> &'static str {
"Write Single Coil"
}
fn min_pdu_length(&self) -> usize {
5 }
fn supports_broadcast(&self) -> bool {
true
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct WriteSingleRegisterHandler;
impl FunctionHandler for WriteSingleRegisterHandler {
fn function_code(&self) -> u8 {
0x06
}
fn handle(&self, pdu: &[u8], ctx: &HandlerContext) -> Result<Vec<u8>, ExceptionCode> {
self.validate_pdu_length(pdu)?;
let address = self.parse_address(pdu, 1)?;
let value = u16::from_be_bytes([pdu[3], pdu[4]]);
ctx.registers
.write_holding_register(address, value)
.map_err(|_| ExceptionCode::IllegalDataAddress)?;
Ok(pdu.to_vec())
}
fn name(&self) -> &'static str {
"Write Single Register"
}
fn min_pdu_length(&self) -> usize {
5 }
fn supports_broadcast(&self) -> bool {
true
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct WriteMultipleCoilsHandler;
impl FunctionHandler for WriteMultipleCoilsHandler {
fn function_code(&self) -> u8 {
0x0F
}
fn handle(&self, pdu: &[u8], ctx: &HandlerContext) -> Result<Vec<u8>, ExceptionCode> {
self.validate_pdu_length(pdu)?;
let address = self.parse_address(pdu, 1)?;
let quantity = self.parse_quantity(pdu, 3)?;
validate_write_quantity(quantity, RegisterType::Coil)?;
let expected_bytes = quantity.div_ceil(8) as usize;
let byte_count = pdu[5] as usize;
if byte_count != expected_bytes {
return Err(ExceptionCode::IllegalDataValue);
}
if pdu.len() < 6 + byte_count {
return Err(ExceptionCode::IllegalDataValue);
}
let data = &pdu[6..6 + byte_count];
let mut coils = Vec::with_capacity(quantity as usize);
for i in 0..quantity as usize {
coils.push((data[i / 8] & (1 << (i % 8))) != 0);
}
ctx.registers
.write_coils(address, &coils)
.map_err(|_| ExceptionCode::IllegalDataAddress)?;
let mut response = vec![self.function_code()];
response.extend_from_slice(&address.to_be_bytes());
response.extend_from_slice(&quantity.to_be_bytes());
Ok(response)
}
fn name(&self) -> &'static str {
"Write Multiple Coils"
}
fn min_pdu_length(&self) -> usize {
6 }
fn supports_broadcast(&self) -> bool {
true
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct WriteMultipleRegistersHandler;
impl FunctionHandler for WriteMultipleRegistersHandler {
fn function_code(&self) -> u8 {
0x10
}
fn handle(&self, pdu: &[u8], ctx: &HandlerContext) -> Result<Vec<u8>, ExceptionCode> {
self.validate_pdu_length(pdu)?;
let address = self.parse_address(pdu, 1)?;
let quantity = self.parse_quantity(pdu, 3)?;
validate_write_quantity(quantity, RegisterType::HoldingRegister)?;
let expected_bytes = (quantity * 2) as usize;
let byte_count = pdu[5] as usize;
if byte_count != expected_bytes {
return Err(ExceptionCode::IllegalDataValue);
}
if pdu.len() < 6 + byte_count {
return Err(ExceptionCode::IllegalDataValue);
}
let data = &pdu[6..6 + byte_count];
let mut values = Vec::with_capacity(quantity as usize);
for i in 0..quantity as usize {
values.push(u16::from_be_bytes([data[i * 2], data[i * 2 + 1]]));
}
ctx.registers
.write_holding_registers(address, &values)
.map_err(|_| ExceptionCode::IllegalDataAddress)?;
let mut response = vec![self.function_code()];
response.extend_from_slice(&address.to_be_bytes());
response.extend_from_slice(&quantity.to_be_bytes());
Ok(response)
}
fn name(&self) -> &'static str {
"Write Multiple Registers"
}
fn min_pdu_length(&self) -> usize {
6 }
fn supports_broadcast(&self) -> bool {
true
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ReadWriteMultipleRegistersHandler;
impl FunctionHandler for ReadWriteMultipleRegistersHandler {
fn function_code(&self) -> u8 {
0x17
}
fn handle(&self, pdu: &[u8], ctx: &HandlerContext) -> Result<Vec<u8>, ExceptionCode> {
self.validate_pdu_length(pdu)?;
let read_address = self.parse_address(pdu, 1)?;
let read_quantity = self.parse_quantity(pdu, 3)?;
let write_address = self.parse_address(pdu, 5)?;
let write_quantity = self.parse_quantity(pdu, 7)?;
if read_quantity == 0 || read_quantity > 125 {
return Err(ExceptionCode::IllegalDataValue);
}
if write_quantity == 0 || write_quantity > 121 {
return Err(ExceptionCode::IllegalDataValue);
}
let expected_bytes = (write_quantity * 2) as usize;
let byte_count = pdu[9] as usize;
if byte_count != expected_bytes {
return Err(ExceptionCode::IllegalDataValue);
}
if pdu.len() < 10 + byte_count {
return Err(ExceptionCode::IllegalDataValue);
}
let data = &pdu[10..10 + byte_count];
let mut write_values = Vec::with_capacity(write_quantity as usize);
for i in 0..write_quantity as usize {
write_values.push(u16::from_be_bytes([data[i * 2], data[i * 2 + 1]]));
}
ctx.registers
.write_holding_registers(write_address, &write_values)
.map_err(|_| ExceptionCode::IllegalDataAddress)?;
let read_values = ctx
.registers
.read_holding_registers(read_address, read_quantity)
.map_err(|_| ExceptionCode::IllegalDataAddress)?;
let response_byte_count = (read_quantity * 2) as u8;
let mut response = vec![self.function_code(), response_byte_count];
for value in read_values {
response.extend_from_slice(&value.to_be_bytes());
}
Ok(response)
}
fn name(&self) -> &'static str {
"Read/Write Multiple Registers"
}
fn min_pdu_length(&self) -> usize {
10 }
}
#[derive(Debug, Clone, Copy, Default)]
pub struct MaskWriteRegisterHandler;
impl FunctionHandler for MaskWriteRegisterHandler {
fn function_code(&self) -> u8 {
0x16
}
fn handle(&self, pdu: &[u8], ctx: &HandlerContext) -> Result<Vec<u8>, ExceptionCode> {
self.validate_pdu_length(pdu)?;
let address = self.parse_address(pdu, 1)?;
let and_mask = u16::from_be_bytes([pdu[3], pdu[4]]);
let or_mask = u16::from_be_bytes([pdu[5], pdu[6]]);
ctx.registers
.mask_write_holding_register(address, and_mask, or_mask)
.map_err(|_| ExceptionCode::IllegalDataAddress)?;
Ok(pdu.to_vec())
}
fn name(&self) -> &'static str {
"Mask Write Register"
}
fn min_pdu_length(&self) -> usize {
7 }
fn supports_broadcast(&self) -> bool {
true
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::register::RegisterStore;
use std::sync::Arc;
fn create_context() -> HandlerContext {
let registers = Arc::new(RegisterStore::with_defaults());
HandlerContext::new(1, registers, 1)
}
#[test]
fn test_write_single_coil_on() {
let handler = WriteSingleCoilHandler;
let ctx = create_context();
let pdu = [0x05, 0x00, 0x05, 0xFF, 0x00];
let response = handler.handle(&pdu, &ctx).unwrap();
assert_eq!(response, pdu.to_vec());
let coils = ctx.registers.read_coils(5, 1).unwrap();
assert!(coils[0]);
}
#[test]
fn test_write_single_coil_off() {
let handler = WriteSingleCoilHandler;
let ctx = create_context();
ctx.registers.write_coil(5, true).unwrap();
let pdu = [0x05, 0x00, 0x05, 0x00, 0x00];
let response = handler.handle(&pdu, &ctx).unwrap();
assert_eq!(response, pdu.to_vec());
let coils = ctx.registers.read_coils(5, 1).unwrap();
assert!(!coils[0]);
}
#[test]
fn test_write_single_coil_invalid_value() {
let handler = WriteSingleCoilHandler;
let ctx = create_context();
let pdu = [0x05, 0x00, 0x05, 0x12, 0x34];
let result = handler.handle(&pdu, &ctx);
assert_eq!(result, Err(ExceptionCode::IllegalDataValue));
}
#[test]
fn test_write_single_register() {
let handler = WriteSingleRegisterHandler;
let ctx = create_context();
let pdu = [0x06, 0x00, 0x0A, 0x12, 0x34];
let response = handler.handle(&pdu, &ctx).unwrap();
assert_eq!(response, pdu.to_vec());
let values = ctx.registers.read_holding_registers(10, 1).unwrap();
assert_eq!(values[0], 0x1234);
}
#[test]
fn test_write_multiple_coils() {
let handler = WriteMultipleCoilsHandler;
let ctx = create_context();
let pdu = [0x0F, 0x00, 0x00, 0x00, 0x0A, 0x02, 0xAD, 0x03];
let response = handler.handle(&pdu, &ctx).unwrap();
assert_eq!(response[0], 0x0F);
assert_eq!(u16::from_be_bytes([response[1], response[2]]), 0);
assert_eq!(u16::from_be_bytes([response[3], response[4]]), 10);
let coils = ctx.registers.read_coils(0, 10).unwrap();
assert_eq!(
coils,
vec![true, false, true, true, false, true, false, true, true, true]
);
}
#[test]
fn test_write_multiple_registers() {
let handler = WriteMultipleRegistersHandler;
let ctx = create_context();
let pdu = [
0x10, 0x00, 0x05, 0x00, 0x03, 0x06, 0x00, 0x64, 0x00, 0xC8, 0x01, 0x2C, ];
let response = handler.handle(&pdu, &ctx).unwrap();
assert_eq!(response[0], 0x10);
assert_eq!(u16::from_be_bytes([response[1], response[2]]), 5);
assert_eq!(u16::from_be_bytes([response[3], response[4]]), 3);
let values = ctx.registers.read_holding_registers(5, 3).unwrap();
assert_eq!(values, vec![100, 200, 300]);
}
#[test]
fn test_read_write_multiple_registers() {
let handler = ReadWriteMultipleRegistersHandler;
let ctx = create_context();
ctx.registers
.write_holding_registers(0, &[10, 20, 30])
.unwrap();
let pdu = [
0x17, 0x00, 0x00, 0x00, 0x03, 0x00, 0x0A, 0x00, 0x02, 0x04, 0x01, 0x00, 0x02, 0x00, ];
let response = handler.handle(&pdu, &ctx).unwrap();
assert_eq!(response[0], 0x17);
assert_eq!(response[1], 6); assert_eq!(u16::from_be_bytes([response[2], response[3]]), 10);
assert_eq!(u16::from_be_bytes([response[4], response[5]]), 20);
assert_eq!(u16::from_be_bytes([response[6], response[7]]), 30);
let values = ctx.registers.read_holding_registers(10, 2).unwrap();
assert_eq!(values, vec![256, 512]);
}
#[test]
fn test_mask_write_register_basic() {
let handler = MaskWriteRegisterHandler;
let ctx = create_context();
ctx.registers.write_holding_register(10, 0x00FF).unwrap();
let pdu = [0x16, 0x00, 0x0A, 0xFF, 0x00, 0x00, 0xF0];
let response = handler.handle(&pdu, &ctx).unwrap();
assert_eq!(response, pdu.to_vec());
let values = ctx.registers.read_holding_registers(10, 1).unwrap();
assert_eq!(values[0], 0x00F0);
}
#[test]
fn test_mask_write_register_identity() {
let handler = MaskWriteRegisterHandler;
let ctx = create_context();
ctx.registers.write_holding_register(5, 0xABCD).unwrap();
let pdu = [0x16, 0x00, 0x05, 0xFF, 0xFF, 0x00, 0x00];
handler.handle(&pdu, &ctx).unwrap();
let values = ctx.registers.read_holding_registers(5, 1).unwrap();
assert_eq!(values[0], 0xABCD);
}
#[test]
fn test_mask_write_register_force_all_bits() {
let handler = MaskWriteRegisterHandler;
let ctx = create_context();
ctx.registers.write_holding_register(0, 0x1234).unwrap();
let pdu = [0x16, 0x00, 0x00, 0x00, 0x00, 0xAB, 0xCD];
handler.handle(&pdu, &ctx).unwrap();
let values = ctx.registers.read_holding_registers(0, 1).unwrap();
assert_eq!(values[0], 0xABCD);
}
#[test]
fn test_mask_write_register_clear_low_byte() {
let handler = MaskWriteRegisterHandler;
let ctx = create_context();
ctx.registers.write_holding_register(0, 0xABCD).unwrap();
let pdu = [0x16, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00];
handler.handle(&pdu, &ctx).unwrap();
let values = ctx.registers.read_holding_registers(0, 1).unwrap();
assert_eq!(values[0], 0xAB00);
}
#[test]
fn test_mask_write_register_set_single_bit() {
let handler = MaskWriteRegisterHandler;
let ctx = create_context();
ctx.registers.write_holding_register(0, 0x0000).unwrap();
let pdu = [0x16, 0x00, 0x00, 0xFF, 0xF7, 0x00, 0x08];
handler.handle(&pdu, &ctx).unwrap();
let values = ctx.registers.read_holding_registers(0, 1).unwrap();
assert_eq!(values[0], 0x0008);
}
#[test]
fn test_mask_write_register_echo_response() {
let handler = MaskWriteRegisterHandler;
let ctx = create_context();
let pdu = [0x16, 0x00, 0x04, 0x00, 0xF2, 0x00, 0x25];
let response = handler.handle(&pdu, &ctx).unwrap();
assert_eq!(response, pdu.to_vec());
}
#[test]
fn test_mask_write_register_pdu_too_short() {
let handler = MaskWriteRegisterHandler;
let ctx = create_context();
let pdu = [0x16, 0x00, 0x00, 0xFF, 0xFF, 0x00];
let result = handler.handle(&pdu, &ctx);
assert_eq!(result, Err(ExceptionCode::IllegalDataValue));
}
#[test]
fn test_write_multiple_registers_invalid_byte_count() {
let handler = WriteMultipleRegistersHandler;
let ctx = create_context();
let pdu = [
0x10, 0x00, 0x05, 0x00, 0x03, 0x04, 0x00, 0x64, 0x00, 0xC8,
];
let result = handler.handle(&pdu, &ctx);
assert_eq!(result, Err(ExceptionCode::IllegalDataValue));
}
}