use crate::register::RegisterType;
use super::{ExceptionCode, FunctionHandler, FunctionHandlerExt, HandlerContext};
fn validate_read_quantity(quantity: u16, reg_type: RegisterType) -> Result<(), ExceptionCode> {
if quantity == 0 {
return Err(ExceptionCode::IllegalDataValue);
}
let max = reg_type.max_read_quantity();
if quantity > max {
return Err(ExceptionCode::IllegalDataValue);
}
Ok(())
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ReadCoilsHandler;
impl FunctionHandler for ReadCoilsHandler {
fn function_code(&self) -> u8 {
0x01
}
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_read_quantity(quantity, RegisterType::Coil)?;
let coils = ctx
.registers
.read_coils(address, quantity)
.map_err(|_| ExceptionCode::IllegalDataAddress)?;
let byte_count = quantity.div_ceil(8) as u8;
let mut response = vec![self.function_code(), byte_count];
let mut bytes = vec![0u8; byte_count as usize];
for (i, &coil) in coils.iter().enumerate() {
if coil {
bytes[i / 8] |= 1 << (i % 8);
}
}
response.extend_from_slice(&bytes);
Ok(response)
}
fn name(&self) -> &'static str {
"Read Coils"
}
fn min_pdu_length(&self) -> usize {
5 }
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ReadDiscreteInputsHandler;
impl FunctionHandler for ReadDiscreteInputsHandler {
fn function_code(&self) -> u8 {
0x02
}
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_read_quantity(quantity, RegisterType::DiscreteInput)?;
let inputs = ctx
.registers
.read_discrete_inputs(address, quantity)
.map_err(|_| ExceptionCode::IllegalDataAddress)?;
let byte_count = quantity.div_ceil(8) as u8;
let mut response = vec![self.function_code(), byte_count];
let mut bytes = vec![0u8; byte_count as usize];
for (i, &input) in inputs.iter().enumerate() {
if input {
bytes[i / 8] |= 1 << (i % 8);
}
}
response.extend_from_slice(&bytes);
Ok(response)
}
fn name(&self) -> &'static str {
"Read Discrete Inputs"
}
fn min_pdu_length(&self) -> usize {
5 }
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ReadHoldingRegistersHandler;
impl FunctionHandler for ReadHoldingRegistersHandler {
fn function_code(&self) -> u8 {
0x03
}
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_read_quantity(quantity, RegisterType::HoldingRegister)?;
let values = ctx
.registers
.read_holding_registers(address, quantity)
.map_err(|_| ExceptionCode::IllegalDataAddress)?;
let byte_count = (quantity * 2) as u8;
let mut response = vec![self.function_code(), byte_count];
for value in values {
response.extend_from_slice(&value.to_be_bytes());
}
Ok(response)
}
fn name(&self) -> &'static str {
"Read Holding Registers"
}
fn min_pdu_length(&self) -> usize {
5 }
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ReadInputRegistersHandler;
impl FunctionHandler for ReadInputRegistersHandler {
fn function_code(&self) -> u8 {
0x04
}
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_read_quantity(quantity, RegisterType::InputRegister)?;
let values = ctx
.registers
.read_input_registers(address, quantity)
.map_err(|_| ExceptionCode::IllegalDataAddress)?;
let byte_count = (quantity * 2) as u8;
let mut response = vec![self.function_code(), byte_count];
for value in values {
response.extend_from_slice(&value.to_be_bytes());
}
Ok(response)
}
fn name(&self) -> &'static str {
"Read Input Registers"
}
fn min_pdu_length(&self) -> usize {
5 }
}
#[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_read_coils() {
let handler = ReadCoilsHandler;
let ctx = create_context();
ctx.registers
.write_coils(0, &[true, false, true, true])
.unwrap();
let pdu = [0x01, 0x00, 0x00, 0x00, 0x04];
let response = handler.handle(&pdu, &ctx).unwrap();
assert_eq!(response[0], 0x01); assert_eq!(response[1], 1); assert_eq!(response[2], 0b00001101); }
#[test]
fn test_read_coils_invalid_quantity() {
let handler = ReadCoilsHandler;
let ctx = create_context();
let pdu = [0x01, 0x00, 0x00, 0x00, 0x00];
let result = handler.handle(&pdu, &ctx);
assert_eq!(result, Err(ExceptionCode::IllegalDataValue));
}
#[test]
fn test_read_holding_registers() {
let handler = ReadHoldingRegistersHandler;
let ctx = create_context();
ctx.registers
.write_holding_registers(0, &[100, 200, 300])
.unwrap();
let pdu = [0x03, 0x00, 0x00, 0x00, 0x03];
let response = handler.handle(&pdu, &ctx).unwrap();
assert_eq!(response[0], 0x03); assert_eq!(response[1], 6); assert_eq!(u16::from_be_bytes([response[2], response[3]]), 100);
assert_eq!(u16::from_be_bytes([response[4], response[5]]), 200);
assert_eq!(u16::from_be_bytes([response[6], response[7]]), 300);
}
#[test]
fn test_read_holding_registers_invalid_address() {
let handler = ReadHoldingRegistersHandler;
let ctx = create_context();
let pdu = [0x03, 0xFF, 0xFF, 0x00, 0x01];
let result = handler.handle(&pdu, &ctx);
assert_eq!(result, Err(ExceptionCode::IllegalDataAddress));
}
#[test]
fn test_read_input_registers() {
let handler = ReadInputRegistersHandler;
let ctx = create_context();
ctx.registers.set_input_register(10, 1234).unwrap();
ctx.registers.set_input_register(11, 5678).unwrap();
let pdu = [0x04, 0x00, 0x0A, 0x00, 0x02];
let response = handler.handle(&pdu, &ctx).unwrap();
assert_eq!(response[0], 0x04); assert_eq!(response[1], 4); assert_eq!(u16::from_be_bytes([response[2], response[3]]), 1234);
assert_eq!(u16::from_be_bytes([response[4], response[5]]), 5678);
}
#[test]
fn test_pdu_too_short() {
let handler = ReadHoldingRegistersHandler;
let ctx = create_context();
let pdu = [0x03, 0x00, 0x00, 0x00];
let result = handler.handle(&pdu, &ctx);
assert_eq!(result, Err(ExceptionCode::IllegalDataValue));
}
}