use std::collections::HashMap;
use std::sync::Arc;
use tracing::{debug, warn};
use crate::core::FunctionCode;
use super::{
ExceptionCode, FunctionHandler, HandlerContext, MaskWriteRegisterHandler, ReadCoilsHandler,
ReadDiscreteInputsHandler, ReadHoldingRegistersHandler, ReadInputRegistersHandler,
ReadWriteMultipleRegistersHandler, WriteMultipleCoilsHandler, WriteMultipleRegistersHandler,
WriteSingleCoilHandler, WriteSingleRegisterHandler,
};
pub struct HandlerRegistry {
handlers: HashMap<u8, Arc<dyn FunctionHandler>>,
}
impl HandlerRegistry {
pub fn new() -> Self {
Self {
handlers: HashMap::new(),
}
}
pub fn with_defaults() -> Self {
let mut registry = Self::new();
registry.register(Box::new(ReadCoilsHandler));
registry.register(Box::new(ReadDiscreteInputsHandler));
registry.register(Box::new(ReadHoldingRegistersHandler));
registry.register(Box::new(ReadInputRegistersHandler));
registry.register(Box::new(WriteSingleCoilHandler));
registry.register(Box::new(WriteSingleRegisterHandler));
registry.register(Box::new(WriteMultipleCoilsHandler));
registry.register(Box::new(WriteMultipleRegistersHandler));
registry.register(Box::new(ReadWriteMultipleRegistersHandler));
registry.register(Box::new(MaskWriteRegisterHandler));
registry
}
pub fn register(
&mut self,
handler: Box<dyn FunctionHandler>,
) -> Option<Arc<dyn FunctionHandler>> {
let fc = handler.function_code();
debug!(
function_code = fc,
name = handler.name(),
"Registering handler"
);
self.handlers.insert(fc, Arc::from(handler))
}
pub fn unregister(&mut self, function_code: u8) -> Option<Arc<dyn FunctionHandler>> {
debug!(function_code, "Unregistering handler");
self.handlers.remove(&function_code)
}
pub fn get(&self, function_code: u8) -> Option<&Arc<dyn FunctionHandler>> {
self.handlers.get(&function_code)
}
pub fn has_handler(&self, function_code: u8) -> bool {
self.handlers.contains_key(&function_code)
}
pub fn function_codes(&self) -> Vec<u8> {
self.handlers.keys().copied().collect()
}
pub fn handler_info(&self) -> Vec<(u8, &'static str)> {
self.handlers
.iter()
.map(|(&fc, h)| (fc, h.name()))
.collect()
}
pub fn dispatch(&self, pdu: &[u8], ctx: &HandlerContext) -> Result<Vec<u8>, ExceptionCode> {
if pdu.is_empty() {
warn!("Empty PDU received");
return Err(ExceptionCode::IllegalFunction);
}
let function_code = pdu[0];
match self.handlers.get(&function_code) {
Some(handler) => {
if ctx.unit_id == 0
&& !function_supports_broadcast(function_code, Some(handler.as_ref()))
{
warn!(function_code, "Broadcast not supported for this function");
return Err(ExceptionCode::IllegalFunction);
}
if pdu.len() < handler.min_pdu_length() {
warn!(
function_code,
pdu_len = pdu.len(),
min_len = handler.min_pdu_length(),
"PDU too short"
);
return Err(ExceptionCode::IllegalDataValue);
}
handler.handle(pdu, ctx)
}
None => {
warn!(function_code, "Unknown function code");
Err(ExceptionCode::IllegalFunction)
}
}
}
pub fn len(&self) -> usize {
self.handlers.len()
}
pub fn is_empty(&self) -> bool {
self.handlers.is_empty()
}
}
pub(crate) fn function_supports_broadcast(
function_code: u8,
handler: Option<&dyn FunctionHandler>,
) -> bool {
if let Some(handler) = handler {
return handler.supports_broadcast();
}
FunctionCode::try_from(function_code)
.map(|function| {
matches!(
function,
FunctionCode::WriteSingleCoil
| FunctionCode::WriteSingleRegister
| FunctionCode::WriteMultipleCoils
| FunctionCode::WriteMultipleRegisters
| FunctionCode::MaskWriteRegister
)
})
.unwrap_or(false)
}
impl Default for HandlerRegistry {
fn default() -> Self {
Self::with_defaults()
}
}
impl std::fmt::Debug for HandlerRegistry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HandlerRegistry")
.field("handlers", &self.handler_info())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::register::RegisterStore;
fn create_context() -> HandlerContext {
let registers = Arc::new(RegisterStore::with_defaults());
HandlerContext::new(1, registers, 1)
}
#[test]
fn test_registry_with_defaults() {
let registry = HandlerRegistry::with_defaults();
assert!(registry.has_handler(0x01)); assert!(registry.has_handler(0x02)); assert!(registry.has_handler(0x03)); assert!(registry.has_handler(0x04)); assert!(registry.has_handler(0x05)); assert!(registry.has_handler(0x06)); assert!(registry.has_handler(0x0F)); assert!(registry.has_handler(0x10)); assert!(registry.has_handler(0x16)); assert!(registry.has_handler(0x17));
assert_eq!(registry.len(), 10);
}
#[test]
fn test_registry_dispatch() {
let registry = HandlerRegistry::with_defaults();
let ctx = create_context();
ctx.registers
.write_holding_registers(0, &[100, 200, 300])
.unwrap();
let pdu = [0x03, 0x00, 0x00, 0x00, 0x03];
let response = registry.dispatch(&pdu, &ctx).unwrap();
assert_eq!(response[0], 0x03);
assert_eq!(response[1], 6);
}
#[test]
fn test_registry_dispatch_unknown_function() {
let registry = HandlerRegistry::with_defaults();
let ctx = create_context();
let pdu = [0xFF, 0x00, 0x00, 0x00, 0x01];
let result = registry.dispatch(&pdu, &ctx);
assert_eq!(result, Err(ExceptionCode::IllegalFunction));
}
#[test]
fn test_registry_dispatch_empty_pdu() {
let registry = HandlerRegistry::with_defaults();
let ctx = create_context();
let result = registry.dispatch(&[], &ctx);
assert_eq!(result, Err(ExceptionCode::IllegalFunction));
}
#[test]
fn test_registry_unregister() {
let mut registry = HandlerRegistry::with_defaults();
let removed = registry.unregister(0x01);
assert!(removed.is_some());
assert!(!registry.has_handler(0x01));
let removed = registry.unregister(0x01);
assert!(removed.is_none());
}
#[test]
fn test_registry_function_codes() {
let registry = HandlerRegistry::with_defaults();
let codes = registry.function_codes();
assert!(codes.contains(&0x01));
assert!(codes.contains(&0x03));
assert!(codes.contains(&0x10));
assert!(codes.contains(&0x16));
assert_eq!(codes.len(), 10);
}
#[test]
fn test_registry_handler_info() {
let registry = HandlerRegistry::with_defaults();
let info = registry.handler_info();
let read_coils = info.iter().find(|(fc, _)| *fc == 0x01);
assert!(read_coils.is_some());
assert_eq!(read_coils.unwrap().1, "Read Coils");
}
struct CustomHandler;
impl FunctionHandler for CustomHandler {
fn function_code(&self) -> u8 {
0x42
}
fn handle(&self, _pdu: &[u8], _ctx: &HandlerContext) -> Result<Vec<u8>, ExceptionCode> {
Ok(vec![0x42, 0x00])
}
fn name(&self) -> &'static str {
"Custom Handler"
}
}
#[test]
fn test_registry_custom_handler() {
let mut registry = HandlerRegistry::new();
registry.register(Box::new(CustomHandler));
let ctx = create_context();
let pdu = [0x42];
let response = registry.dispatch(&pdu, &ctx).unwrap();
assert_eq!(response, vec![0x42, 0x00]);
}
#[test]
fn test_registry_replace_handler() {
let mut registry = HandlerRegistry::with_defaults();
let original_name = registry.get(0x03).map(|h| h.name());
assert_eq!(original_name, Some("Read Holding Registers"));
struct ReplacementHandler;
impl FunctionHandler for ReplacementHandler {
fn function_code(&self) -> u8 {
0x03
}
fn handle(&self, _: &[u8], _: &HandlerContext) -> Result<Vec<u8>, ExceptionCode> {
Ok(vec![0x03, 0xFF])
}
fn name(&self) -> &'static str {
"Replacement"
}
}
let old = registry.register(Box::new(ReplacementHandler));
assert!(old.is_some());
assert_eq!(old.unwrap().name(), "Read Holding Registers");
let new_name = registry.get(0x03).map(|h| h.name());
assert_eq!(new_name, Some("Replacement"));
}
}