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        // PDU Data: FIFO Byte Count (2 bytes), FIFO Count (2 bytes), N * Register Value (2 bytes each)
38        let fp = pdu.fifo_payload()?;
39        let fifo_byte_count = fp.fifo_byte_count as usize;
40        let fifo_count = fp.fifo_count as usize;
41
42        // The total values length should be fifo_byte_count - 2 (excluding the FIFO count field).
43        if fp.values.len() + 2 != fifo_byte_count {
44            return Err(MbusError::InvalidAduLength);
45        }
46
47        // The byte count should be 2 bytes for the FIFO count field, plus 2 bytes for each register.
48        if fifo_byte_count != 2 + fifo_count * 2 {
49            return Err(MbusError::ParseError);
50        }
51
52        if fifo_count > MAX_FIFO_QUEUE_COUNT_PER_PDU {
53            return Err(MbusError::BufferLenMissmatch);
54        }
55
56        let mut values = [0u16; MAX_FIFO_QUEUE_COUNT_PER_PDU];
57        let mut index = 0;
58        for chunk in fp.values.chunks_exact(2) {
59            if index >= MAX_FIFO_QUEUE_COUNT_PER_PDU {
60                return Err(MbusError::BufferLenMissmatch);
61            }
62            values[index] = u16::from_be_bytes([chunk[0], chunk[1]]);
63            index += 1;
64        }
65
66        if index != fifo_count {
67            return Err(MbusError::ParseError);
68        }
69
70        Ok((values, fifo_count))
71    }
72}
73
74impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
75where
76    TRANSPORT: Transport,
77    APP: ClientCommon + FifoQueueResponse,
78{
79    /// Orchestrates the processing of a Read FIFO Queue response.
80    ///
81    /// This method decompiles the PDU, validates the content, and notifies the
82    /// application layer of success or failure.
83    pub(super) fn handle_read_fifo_queue_response(
84        &mut self,
85        ctx: &ExpectedResponse<TRANSPORT, APP, N>,
86        message: &ModbusMessage,
87    ) {
88        let pdu = message.pdu();
89        let function_code = pdu.function_code();
90        let transaction_id = ctx.txn_id;
91        let unit_id_or_slave_addr = message.unit_id_or_slave_addr();
92
93        let register_rsp = match fifo_queue::service::ServiceDecompiler::handle_read_fifo_queue_rsp(
94            function_code,
95            pdu,
96        ) {
97            Ok(register_response) => register_response,
98            Err(e) => {
99                self.app
100                    .request_failed(transaction_id, unit_id_or_slave_addr, e);
101                return;
102            }
103        };
104
105        self.app
106            .read_fifo_queue_response(transaction_id, unit_id_or_slave_addr, &register_rsp);
107    }
108}