Skip to main content

mbus_client/services/fifo_queue/
response.rs

1//! # Modbus FIFO Queue Response Handling
2//!
3//! This module provides the logic for parsing and dispatching responses related to
4//! Modbus Read FIFO Queue (Function Code 0x18).
5//!
6//! ## Responsibilities
7//! - **Parsing**: Validates PDU structure, function codes, and byte counts for FIFO responses.
8//! - **De-encapsulation**: Extracts the 16-bit register values from the Modbus PDU.
9//! - **Dispatching**: Routes the parsed data to the application layer via the `FifoQueueResponse` trait.
10//!
11//! ## Architecture
12//! - `ResponseParser`: Contains low-level logic to transform raw PDU bytes into a list of register values.
13//! - `ClientServices` implementation: Orchestrates the high-level handling, converting the PDU
14//!   into a `FifoQueue` model and triggering the application callback.
15
16use crate::app::FifoQueueResponse;
17use crate::services::fifo_queue::MAX_FIFO_QUEUE_COUNT_PER_PDU;
18use crate::services::{ClientCommon, ClientServices, ExpectedResponse, fifo_queue};
19use mbus_core::{
20    data_unit::common::{ModbusMessage, Pdu},
21    errors::MbusError,
22    function_codes::public::FunctionCode,
23    transport::Transport,
24};
25
26pub(super) struct ResponseParser;
27
28impl ResponseParser {
29    /// Internal parser for Read FIFO Queue response PDUs (FC 0x18).
30    pub(super) fn parse_read_fifo_queue_response(
31        pdu: &Pdu,
32    ) -> Result<([u16; MAX_FIFO_QUEUE_COUNT_PER_PDU], usize), MbusError> {
33        if pdu.function_code() != FunctionCode::ReadFifoQueue {
34            return Err(MbusError::InvalidFunctionCode);
35        }
36
37        let data = pdu.data().as_slice();
38        // PDU Data: FIFO Byte Count (2 bytes), FIFO Count (2 bytes), N * Register Value (2 bytes each)
39        if data.len() < 4 {
40            return Err(MbusError::InvalidPduLength);
41        }
42
43        let fifo_byte_count = u16::from_be_bytes([data[0], data[1]]) as usize;
44        // The total data length should be 2 (for the byte count field itself) + fifo_byte_count.
45        if data.len() != 2 + fifo_byte_count {
46            return Err(MbusError::InvalidAduLength);
47        }
48
49        let fifo_count = u16::from_be_bytes([data[2], data[3]]) as usize;
50
51        // The byte count should be 2 bytes for the FIFO count field, plus 2 bytes for each register.
52        if fifo_byte_count != 2 + fifo_count * 2 {
53            return Err(MbusError::ParseError);
54        }
55
56        if fifo_count > MAX_FIFO_QUEUE_COUNT_PER_PDU {
57            return Err(MbusError::BufferLenMissmatch);
58        }
59
60        let mut values = [0u16; MAX_FIFO_QUEUE_COUNT_PER_PDU];
61        let mut index = 0;
62        for chunk in data[4..].chunks_exact(2) {
63            if index >= MAX_FIFO_QUEUE_COUNT_PER_PDU {
64                return Err(MbusError::BufferLenMissmatch);
65            }
66            values[index] = u16::from_be_bytes([chunk[0], chunk[1]]);
67            index += 1;
68        }
69
70        if index != fifo_count {
71            return Err(MbusError::ParseError);
72        }
73
74        Ok((values, fifo_count))
75    }
76}
77
78impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
79where
80    TRANSPORT: Transport,
81    APP: ClientCommon + FifoQueueResponse,
82{
83    /// Orchestrates the processing of a Read FIFO Queue response.
84    ///
85    /// This method decompiles the PDU, validates the content, and notifies the
86    /// application layer of success or failure.
87    pub(super) fn handle_read_fifo_queue_response(
88        &mut self,
89        ctx: &ExpectedResponse<TRANSPORT, APP, N>,
90        message: &ModbusMessage,
91    ) {
92        let pdu = message.pdu();
93        let function_code = pdu.function_code();
94        let transaction_id = ctx.txn_id;
95        let unit_id_or_slave_addr = message.unit_id_or_slave_addr();
96
97        let register_rsp = match fifo_queue::service::ServiceDecompiler::handle_read_fifo_queue_rsp(
98            function_code,
99            pdu,
100        ) {
101            Ok(register_response) => register_response,
102            Err(e) => {
103                self.app
104                    .request_failed(transaction_id, unit_id_or_slave_addr, e);
105                return;
106            }
107        };
108
109        self.app
110            .read_fifo_queue_response(transaction_id, unit_id_or_slave_addr, &register_rsp);
111    }
112}