Skip to main content

mbus_core/data_unit/
common.rs

1//! Modbus Common Data Unit Module
2//!
3//! This module defines the core building blocks of the Modbus protocol, including:
4//! - **PDU (Protocol Data Unit)**: The function code and data payload independent of the communication layer.
5//! - **ADU (Application Data Unit)**: The complete frame including addressing and error checking.
6//! - **MBAP Header**: The specific header used for Modbus TCP transactions.
7//! - **Transport Agnostic Logic**: Functions to compile and decompile frames for TCP, RTU, and ASCII modes.
8//!
9//! The module is designed for `no_std` environments, utilizing `heapless` vectors for fixed-capacity
10//! memory management to ensure deterministic behavior in embedded systems.
11
12use crate::errors::MbusError;
13use crate::function_codes::public::FunctionCode;
14use crate::transport::{SerialMode, TransportType, UnitIdOrSlaveAddr, checksum};
15use heapless::Vec;
16
17/// Maximum data length for a PDU (excluding function code)
18pub const MAX_PDU_DATA_LEN: usize = 252;
19
20/// Modbus Protocol Identifier (PID)
21pub const MODBUS_PROTOCOL_ID: u16 = 0x0000;
22
23/// Maximum length of a Modbus TCP/RTU ADU in bytes.
24pub const MAX_ADU_FRAME_LEN_TCP_RTU: usize = 260;
25
26/// Maximum length of a Modbus ASCII ADU in bytes.
27pub const MAX_ADU_FRAME_LEN_ASCII: usize = 513;
28
29/// Maximum ADU frame length used by internal buffers.
30///
31/// - When `serial-ascii` feature is enabled, this is `513` (ASCII upper bound).
32/// - Otherwise this is `260` (TCP/RTU upper bound), reducing stack usage.
33#[cfg(feature = "serial-ascii")]
34pub const MAX_ADU_FRAME_LEN: usize = MAX_ADU_FRAME_LEN_ASCII;
35
36/// Maximum ADU frame length used by internal buffers.
37#[cfg(not(feature = "serial-ascii"))]
38pub const MAX_ADU_FRAME_LEN: usize = MAX_ADU_FRAME_LEN_TCP_RTU;
39
40#[cfg(test)]
41mod frame_len_tests {
42    use super::*;
43
44    #[test]
45    #[cfg(feature = "serial-ascii")]
46    fn test_max_adu_frame_len_ascii_enabled() {
47        assert_eq!(MAX_ADU_FRAME_LEN, 513);
48    }
49
50    #[test]
51    #[cfg(not(feature = "serial-ascii"))]
52    fn test_max_adu_frame_len_ascii_disabled() {
53        assert_eq!(MAX_ADU_FRAME_LEN, 260);
54    }
55}
56
57/// Size of the Modbus Application Protocol (MBAP) Header in bytes.
58pub const MBAP_HEADER_SIZE: usize = 7;
59
60/// Minimum size of a Modbus RTU ADU in bytes (Address + Function Code + CRC).
61pub const MIN_RTU_ADU_LEN: usize = 4;
62
63/// Minimum size of a Modbus ASCII ADU in bytes (Start + Address + Function Code + LRC + End).
64pub const MIN_ASCII_ADU_LEN: usize = 9;
65
66/// Size of the Modbus RTU CRC field in bytes.
67pub const RTU_CRC_SIZE: usize = 2;
68
69/// Offset of the Transaction ID in the MBAP header.
70pub const MBAP_TXN_ID_OFFSET_1B: usize = 0;
71/// Offset of the Transaction ID in the MBAP header.
72pub const MBAP_TXN_ID_OFFSET_2B: usize = MBAP_TXN_ID_OFFSET_1B + 1;
73/// Offset of the Protocol ID in the MBAP header.
74pub const MBAP_PROTO_ID_OFFSET_1B: usize = 2;
75/// Offset of the Protocol ID in the MBAP header.
76pub const MBAP_PROTO_ID_OFFSET_2B: usize = MBAP_PROTO_ID_OFFSET_1B + 1;
77/// Offset of  Length field in the MBAP header.
78pub const MBAP_LENGTH_OFFSET_1B: usize = 4;
79/// Offset of the 2nd byte of the Length field in the MBAP header.
80pub const MBAP_LENGTH_OFFSET_2B: usize = MBAP_LENGTH_OFFSET_1B + 1;
81/// Offset of the Unit ID in the MBAP header.
82pub const MBAP_UNIT_ID_OFFSET: usize = 6;
83
84/// Number of bytes in a Modbus ASCII Start character.
85pub const ASCII_START_SIZE: usize = 1;
86
87/// Number of bytes in a Modbus ASCII End sequence (CR LF).
88pub const ASCII_END_SIZE: usize = 2;
89
90/// Bit mask used to indicate a Modbus exception in the function code byte.
91pub const ERROR_BIT_MASK: u8 = 0x80;
92
93/// Bit mask used to extract the base function code from the function code byte.
94pub const FUNCTION_CODE_MASK: u8 = 0x7F;
95
96/// Offset of the high byte of the starting address in PDU data (for read/write functions).
97pub const PDU_ADDRESS_OFFSET_1B: usize = 0;
98/// Offset of the low byte of the starting address in PDU data.
99pub const PDU_ADDRESS_OFFSET_2B: usize = PDU_ADDRESS_OFFSET_1B + 1;
100/// Offset of the high byte of the quantity/count in PDU data.
101pub const PDU_QUANTITY_OFFSET_1B: usize = 2;
102/// Offset of the low byte of the quantity/count in PDU data.
103pub const PDU_QUANTITY_OFFSET_2B: usize = PDU_QUANTITY_OFFSET_1B + 1;
104
105/// Offset of the high byte of the AND mask in FC16 PDU data.
106pub const PDU_AND_MASK_OFFSET_1B: usize = 2;
107/// Offset of the low byte of the AND mask in FC16 PDU data.
108pub const PDU_AND_MASK_OFFSET_2B: usize = PDU_AND_MASK_OFFSET_1B + 1;
109/// Offset of the high byte of the OR mask in FC16 PDU data.
110pub const PDU_OR_MASK_OFFSET_1B: usize = 4;
111/// Offset of the low byte of the OR mask in FC16 PDU data.
112pub const PDU_OR_MASK_OFFSET_2B: usize = PDU_OR_MASK_OFFSET_1B + 1;
113
114/// Offset of the byte count in FC0F/FC10/FC17 PDU data.
115pub const PDU_BYTE_COUNT_OFFSET: usize = 4;
116
117/// Offset of the write address high byte in FC17 PDU data.
118pub const PDU_FC17_WRITE_ADDRESS_OFFSET_1B: usize = 4;
119/// Offset of the write address low byte in FC17 PDU data.
120pub const PDU_FC17_WRITE_ADDRESS_OFFSET_2B: usize = PDU_FC17_WRITE_ADDRESS_OFFSET_1B + 1;
121/// Offset of the write quantity high byte in FC17 PDU data.
122pub const PDU_FC17_WRITE_QUANTITY_OFFSET_1B: usize = 6;
123/// Offset of the write quantity low byte in FC17 PDU data.
124pub const PDU_FC17_WRITE_QUANTITY_OFFSET_2B: usize = PDU_FC17_WRITE_QUANTITY_OFFSET_1B + 1;
125/// Offset of the write byte count in FC17 PDU data.
126pub const PDU_FC17_WRITE_BYTE_COUNT_OFFSET: usize = 8;
127/// Offset of the write values data in FC17 PDU data.
128pub const PDU_FC17_WRITE_VALUES_OFFSET: usize = 9;
129
130/// Offset of the high byte of the sub-function code in FC08 (Diagnostics) PDU data.
131pub const PDU_SUB_FUNCTION_OFFSET_1B: usize = 0;
132/// Offset of the low byte of the sub-function code in FC08 (Diagnostics) PDU data.
133pub const PDU_SUB_FUNCTION_OFFSET_2B: usize = 1;
134
135/// Offset of the MEI type byte in FC2B (Encapsulated Interface Transport) PDU data.
136pub const PDU_MEI_TYPE_OFFSET: usize = 0;
137/// Offset of the read device ID code byte in a FC2B/MEI 0x0E response.
138pub const PDU_MEI_READ_CODE_OFFSET: usize = 1;
139/// Offset of the conformity level byte in a FC2B/MEI 0x0E response.
140pub const PDU_MEI_CONFORMITY_LEVEL_OFFSET: usize = 2;
141/// Offset of the more-follows byte in a FC2B/MEI 0x0E response.
142pub const PDU_MEI_MORE_FOLLOWS_OFFSET: usize = 3;
143/// Offset of the next object ID byte in a FC2B/MEI 0x0E response.
144pub const PDU_MEI_NEXT_OBJECT_ID_OFFSET: usize = 4;
145/// Offset of the number-of-objects byte in a FC2B/MEI 0x0E response.
146pub const PDU_MEI_NUM_OBJECTS_OFFSET: usize = 5;
147/// Offset of the objects data payload in a FC2B/MEI 0x0E response.
148pub const PDU_MEI_OBJECTS_DATA_OFFSET: usize = 6;
149
150/// Offset of the high byte of FIFO byte count in FC18 (Read FIFO Queue) PDU data.
151pub const PDU_FIFO_BYTE_COUNT_OFFSET_1B: usize = 0;
152/// Offset of the low byte of FIFO byte count in FC18 PDU data.
153pub const PDU_FIFO_BYTE_COUNT_OFFSET_2B: usize = 1;
154/// Offset of the high byte of FIFO count in FC18 PDU data.
155pub const PDU_FIFO_COUNT_OFFSET_1B: usize = 2;
156/// Offset of the low byte of FIFO count in FC18 PDU data.
157pub const PDU_FIFO_COUNT_OFFSET_2B: usize = 3;
158/// Offset of the FIFO values payload in FC18 PDU data.
159pub const PDU_FIFO_VALUES_OFFSET: usize = 4;
160
161/// Checks if the given function code byte indicates an exception (error bit is set).
162///
163/// # Arguments
164/// * `function_code_byte` - The raw function code byte from the PDU.
165///
166/// # Returns
167/// `true` if the highest bit is set, indicating a Modbus exception response.
168#[inline]
169pub fn is_exception_code(function_code_byte: u8) -> bool {
170    function_code_byte & ERROR_BIT_MASK != 0
171}
172
173/// Clears the exception bit from the function code byte to retrieve the base function code.
174///
175/// # Arguments
176/// * `function_code_byte` - The raw function code byte from the PDU.
177///
178/// # Returns
179/// The base function code with the error bit cleared.
180#[inline]
181pub fn clear_exception_bit(function_code_byte: u8) -> u8 {
182    function_code_byte & FUNCTION_CODE_MASK
183}
184
185/// Modbus Protocol Data Unit (PDU).
186///
187/// The PDU is the core of the Modbus message, consisting of a function code
188/// and the associated data payload.
189#[derive(Debug, Clone)]
190pub struct Pdu {
191    /// The Modbus function code identifying the operation.
192    function_code: FunctionCode,
193    /// Optional error code for exception responses (only valid if function_code indicates an error).
194    error_code: Option<u8>,
195    /// The data payload associated with the function code.
196    data: heapless::Vec<u8, MAX_PDU_DATA_LEN>,
197    /// The actual length of the data payload (excluding the function code).
198    data_len: u8,
199}
200
201/// Parsed read-window request fields for FC01/02/03/04.
202#[derive(Debug, Clone, Copy, PartialEq, Eq)]
203pub struct ReadWindow {
204    /// Starting address from request bytes 0-1.
205    pub address: u16,
206    /// Quantity/count from request bytes 2-3.
207    pub quantity: u16,
208}
209
210/// Parsed write-single request fields for FC05/FC06-style PDUs.
211#[derive(Debug, Clone, Copy, PartialEq, Eq)]
212pub struct WriteSingleU16Fields {
213    /// Target address from request bytes 0-1.
214    pub address: u16,
215    /// Raw 16-bit value from request bytes 2-3.
216    pub value: u16,
217}
218
219/// Parsed write-multiple request fields for FC0F/FC10-style PDUs.
220#[derive(Debug, Clone, Copy, PartialEq, Eq)]
221pub struct WriteMultipleFields<'a> {
222    /// Starting address from request bytes 0-1.
223    pub address: u16,
224    /// Requested quantity from request bytes 2-3.
225    pub quantity: u16,
226    /// Byte count field from request byte 4.
227    pub byte_count: u8,
228    /// Value bytes following the byte-count field.
229    pub values: &'a [u8],
230}
231
232/// Parsed FC16 mask-write request fields.
233#[derive(Debug, Clone, Copy, PartialEq, Eq)]
234pub struct MaskWriteRegisterFields {
235    /// Target address from request bytes 0-1.
236    pub address: u16,
237    /// AND mask from request bytes 2-3.
238    pub and_mask: u16,
239    /// OR mask from request bytes 4-5.
240    pub or_mask: u16,
241}
242
243/// Parsed byte-count prefixed payload used by read-style responses.
244#[derive(Debug, Clone, Copy, PartialEq, Eq)]
245pub struct ByteCountPayload<'a> {
246    /// Byte-count prefix at payload byte 0.
247    pub byte_count: u8,
248    /// Payload bytes immediately following the byte count.
249    pub payload: &'a [u8],
250}
251
252/// Parsed FC17 read/write multiple registers request fields.
253#[derive(Debug, Clone, Copy, PartialEq, Eq)]
254pub struct ReadWriteMultipleFields<'a> {
255    /// Read starting address from request bytes 0-1.
256    pub read_address: u16,
257    /// Quantity to read from request bytes 2-3.
258    pub read_quantity: u16,
259    /// Write starting address from request bytes 4-5.
260    pub write_address: u16,
261    /// Quantity to write from request bytes 6-7.
262    pub write_quantity: u16,
263    /// Write byte count from request byte 8.
264    pub write_byte_count: u8,
265    /// Write value bytes following the byte-count field.
266    pub write_values: &'a [u8],
267}
268
269/// Parsed sub-function + even-length payload for FC08 (Diagnostics) responses.
270#[derive(Debug, Clone, Copy, PartialEq, Eq)]
271pub struct SubFunctionPayload<'a> {
272    /// Sub-function code from data bytes 0-1.
273    pub sub_function: u16,
274    /// Even-length payload bytes following the sub-function field.
275    pub payload: &'a [u8],
276}
277
278/// Parsed two-u16 payload for fixed-width responses like FC0B (Get Comm Event Counter).
279#[derive(Debug, Clone, Copy, PartialEq, Eq)]
280pub struct U16PairFields {
281    /// First u16 value from data bytes 0-1.
282    pub first: u16,
283    /// Second u16 value from data bytes 2-3.
284    pub second: u16,
285}
286
287/// Parsed MEI type + data payload for FC2B (Encapsulated Interface Transport) responses.
288#[derive(Debug, Clone, Copy, PartialEq, Eq)]
289pub struct MeiTypePayload<'a> {
290    /// Raw MEI type byte at data offset 0.
291    pub mei_type_byte: u8,
292    /// Payload bytes following the MEI type field.
293    pub payload: &'a [u8],
294}
295
296/// Parsed FIFO queue response payload for FC18 (Read FIFO Queue) responses.
297#[derive(Debug, Clone, Copy, PartialEq, Eq)]
298pub struct FifoPayload<'a> {
299    /// 2-byte FIFO byte count from data bytes 0-1.
300    pub fifo_byte_count: u16,
301    /// 2-byte FIFO count from data bytes 2-3.
302    pub fifo_count: u16,
303    /// Raw value bytes following the FIFO count field (from byte 4 onwards).
304    pub values: &'a [u8],
305}
306
307/// Parsed FC2B / MEI 0x0E (Read Device Identification) response header fields.
308///
309/// All values are stored as raw bytes. Callers are responsible for converting to
310/// domain types (e.g., `ReadDeviceIdCode`, `ConformityLevel`, `ObjectId`) using `TryFrom`.
311#[derive(Debug, Clone, PartialEq, Eq)]
312pub struct ReadDeviceIdPduFields {
313    /// Raw MEI type byte at data offset 0. Expected: 0x0E.
314    pub mei_type_byte: u8,
315    /// Raw read device ID code byte at data offset 1.
316    pub read_device_id_code_byte: u8,
317    /// Raw conformity level byte at data offset 2.
318    pub conformity_level_byte: u8,
319    /// More-follows flag derived from data offset 3 (true when byte == 0xFF).
320    pub more_follows: bool,
321    /// Raw next object ID byte at data offset 4.
322    pub next_object_id_byte: u8,
323    /// Number of objects at data offset 5.
324    pub number_of_objects: u8,
325    /// Raw objects data payload (data[6..]), zero-padded to MAX_PDU_DATA_LEN.
326    pub objects_data: [u8; MAX_PDU_DATA_LEN],
327    /// Number of valid bytes written into `objects_data`.
328    pub payload_len: usize,
329}
330
331/// Modbus TCP Application Data Unit (ADU) Header (MBAP).
332#[derive(Debug, Clone, Copy)]
333pub struct MbapHeader {
334    /// Identification of a Modbus Request/Response transaction.
335    pub transaction_id: u16,
336    /// Protocol Identifier (0 = Modbus protocol).
337    pub protocol_id: u16,
338    /// Number of remaining bytes in the message (Unit ID + PDU).
339    pub length: u16,
340    /// Identification of a remote server on a non-TCP/IP network.
341    pub unit_id: u8,
342}
343
344impl MbapHeader {
345    /// Creates a new `MbapHeader` instance.
346    ///
347    /// # Arguments
348    /// * `transaction_id` - The transaction ID for the Modbus message.
349    /// * `protocol_id` - The protocol identifier (should be 0 for Modbus).
350    /// * `length` - The length of the remaining message (Unit ID + PDU).
351    /// * `unit_id` - The unit identifier for the target slave device.
352    ///
353    /// # Returns
354    /// A new `MbapHeader` instance.
355    pub fn new(transaction_id: u16, length: u16, unit_id: u8) -> Self {
356        Self {
357            transaction_id,
358            protocol_id: 0, /* Must be 0 for Modbus */
359            length,
360            unit_id,
361        }
362    }
363}
364
365/// Represents a Modbus slave address for RTU/ASCII messages.
366#[derive(Debug, Clone, Copy, PartialEq, Eq)]
367pub struct SlaveAddress(u8);
368
369impl SlaveAddress {
370    /// Creates a new `SlaveAddress` instance.
371    pub fn new(address: u8) -> Result<Self, MbusError> {
372        if !(0..=247).contains(&address) {
373            return Err(MbusError::InvalidSlaveAddress);
374        }
375        Ok(Self(address))
376    }
377
378    /// Accessor for the slave address.
379    pub fn address(&self) -> u8 {
380        self.0
381    }
382}
383
384/// Additional address field for Modbus RTU/TCP messages.
385#[derive(Debug, Clone, Copy)]
386#[allow(dead_code)]
387pub enum AdditionalAddress {
388    /// The MBAP header for Modbus TCP messages. This includes Transaction ID, Protocol ID, Length, and Unit ID.
389    MbapHeader(MbapHeader),
390    /// The slave address for Modbus RTU/ASCII messages.
391    SlaveAddress(SlaveAddress),
392}
393
394/// Represents a complete Modbus message, including the additional address, PDU and Error check.
395#[derive(Debug, Clone)]
396pub struct ModbusMessage {
397    /// The MBAP header for Modbus TCP messages.
398    pub additional_address: AdditionalAddress,
399    /// The Protocol Data Unit (PDU) containing the function code and data.
400    pub pdu: Pdu,
401    // Error check (CRC for RTU, LRC for ASCII) would be handled separately based on the transport layer.
402}
403
404impl ModbusMessage {
405    /// Creates a new `ModbusMessage` instance.
406    ///
407    /// # Arguments
408    /// * `additional_address` - The additional address information (MBAP header or slave address).
409    /// * `pdu` - The Protocol Data Unit containing the function code and data.
410    ///
411    /// # Returns
412    /// A new `ModbusMessage` instance.
413    pub fn new(additional_address: AdditionalAddress, pdu: Pdu) -> Self {
414        Self {
415            additional_address,
416            pdu,
417        }
418    }
419
420    /// Accessor for the additional address.
421    pub fn additional_address(&self) -> &AdditionalAddress {
422        &self.additional_address
423    }
424
425    /// Accessor for the Protocol Data Unit (PDU).
426    ///
427    /// The PDU contains the function code and the data payload, which are
428    /// independent of the underlying transport layer (TCP, RTU, or ASCII).
429    ///
430    pub fn pdu(&self) -> &Pdu {
431        &self.pdu
432    }
433
434    /// Extracts the target device identifier from the message.
435    ///
436    /// This method abstracts the difference between TCP (Unit ID) and Serial (Slave Address)
437    /// addressing, returning a unified `UnitIdOrSlaveAddr` type.
438    ///
439    /// # Returns
440    /// A `UnitIdOrSlaveAddr` representing the destination or source device.
441    pub fn unit_id_or_slave_addr(&self) -> UnitIdOrSlaveAddr {
442        match self.additional_address {
443            AdditionalAddress::MbapHeader(header) => match header.unit_id {
444                0 => UnitIdOrSlaveAddr::new_broadcast_address(),
445                unit_id => {
446                    UnitIdOrSlaveAddr::try_from(unit_id).unwrap_or(UnitIdOrSlaveAddr::default())
447                }
448            },
449            AdditionalAddress::SlaveAddress(slave_address) => match slave_address.address() {
450                0 => UnitIdOrSlaveAddr::new_broadcast_address(),
451                address => {
452                    UnitIdOrSlaveAddr::try_from(address).unwrap_or(UnitIdOrSlaveAddr::default())
453                }
454            },
455        }
456    }
457
458    /// Retrieves the transaction identifier for the message.
459    ///
460    /// For TCP messages, this returns the ID from the MBAP header.
461    /// For Serial (RTU/ASCII) messages, this returns 0 as they are inherently synchronous.
462    pub fn transaction_id(&self) -> u16 {
463        match self.additional_address {
464            AdditionalAddress::MbapHeader(header) => header.transaction_id,
465            AdditionalAddress::SlaveAddress(_) => 0,
466        }
467    }
468
469    /// Accessor for the function code from the PDU.
470    pub fn function_code(&self) -> FunctionCode {
471        self.pdu.function_code()
472    }
473
474    /// Accessor for the data payload from the PDU.
475    pub fn data(&self) -> &heapless::Vec<u8, MAX_PDU_DATA_LEN> {
476        self.pdu.data()
477    }
478
479    /// Accessor for the actual length of the data payload.
480    pub fn data_len(&self) -> u8 {
481        self.pdu.data_len()
482    }
483
484    /// Converts the `ModbusMessage` into its byte representation.
485    ///
486    /// This method serializes the additional address (MBAP header or slave address)
487    /// followed by the PDU.
488    ///
489    /// # Returns
490    /// `Ok(Vec<u8, MAX_ADU_LEN>)` containing the ADU bytes, or an `MbusError` if
491    /// the message cannot be serialized.
492    pub fn to_bytes(&self) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
493        let mut adu_bytes = Vec::new();
494
495        match &self.additional_address {
496            AdditionalAddress::MbapHeader(header) => {
497                // MBAP Header: TID (2), PID (2), Length (2), Unit ID (1)
498                adu_bytes
499                    .extend_from_slice(&header.transaction_id.to_be_bytes())
500                    .map_err(|_| MbusError::Unexpected)?;
501                adu_bytes
502                    .extend_from_slice(&header.protocol_id.to_be_bytes())
503                    .map_err(|_| MbusError::Unexpected)?;
504                adu_bytes
505                    .extend_from_slice(&header.length.to_be_bytes())
506                    .map_err(|_| MbusError::Unexpected)?;
507                adu_bytes
508                    .push(header.unit_id)
509                    .map_err(|_| MbusError::Unexpected)?;
510            }
511            AdditionalAddress::SlaveAddress(address) => {
512                adu_bytes
513                    .push(address.address())
514                    .map_err(|_| MbusError::Unexpected)?;
515            }
516        }
517
518        let pdu_bytes = self.pdu.to_bytes()?;
519        adu_bytes
520            .extend_from_slice(&pdu_bytes)
521            .map_err(|_| MbusError::Unexpected)?;
522
523        Ok(adu_bytes)
524    }
525
526    /// Creates a `ModbusMessage` from its byte representation (ADU).
527    ///
528    /// This method parses the MBAP header and the PDU from the given byte slice.
529    ///
530    /// # Arguments
531    /// * `bytes` - A byte slice containing the complete Modbus TCP ADU.
532    ///
533    /// # Returns
534    /// `Ok((ModbusMessage, usize))` containing the parsed message and the number of consumed bytes.
535    pub fn from_bytes(bytes: &[u8]) -> Result<Self, MbusError> {
536        // Minimum ADU length: MBAP header + 1 byte Function Code
537        if bytes.len() < MBAP_HEADER_SIZE + 1 {
538            return Err(MbusError::InvalidAduLength); // Reusing for general invalid length
539        }
540
541        // Parse MBAP Header
542        // Transaction ID: 2 bytes starting at offset 0
543        let transaction_id =
544            u16::from_be_bytes([bytes[MBAP_TXN_ID_OFFSET_1B], bytes[MBAP_TXN_ID_OFFSET_2B]]);
545        // Protocol ID: 2 bytes starting at offset 2
546        let protocol_id = u16::from_be_bytes([
547            bytes[MBAP_PROTO_ID_OFFSET_1B],
548            bytes[MBAP_PROTO_ID_OFFSET_2B],
549        ]);
550        // Length: 2 bytes starting at offset 4
551        let length =
552            u16::from_be_bytes([bytes[MBAP_LENGTH_OFFSET_1B], bytes[MBAP_LENGTH_OFFSET_2B]]);
553        // Unit ID: 1 byte at offset 6
554        let unit_id = bytes[MBAP_UNIT_ID_OFFSET];
555
556        // Validate Protocol Identifier
557        if protocol_id != MODBUS_PROTOCOL_ID {
558            return Err(MbusError::BasicParseError); // Invalid protocol ID
559        }
560
561        // Validate Length field
562        // The length field specifies the number of following bytes, including the Unit ID and PDU.
563        // So, actual_pdu_and_unit_id_len = bytes.len() - 6 (MBAP header without length field)
564        // And length field value should be actual_pdu_and_unit_id_len
565        const INITIAL_FRAME_LEN: usize = MBAP_HEADER_SIZE - 1; // MBAP
566        let expected_total_len_from_header = length as usize + INITIAL_FRAME_LEN; // 6 bytes for TID, PID, Length field itself
567
568        // Ensure we have enough bytes in the buffer to form the complete expected frame
569        if bytes.len() < expected_total_len_from_header {
570            return Err(MbusError::InvalidPduLength);
571        }
572
573        // Slice exactly the frame length indicated by the header to support pipelined streams
574        let frame_bytes = &bytes[..expected_total_len_from_header];
575        // The PDU starts after the MBAP header
576        let pdu_bytes_slice = &frame_bytes[MBAP_HEADER_SIZE..];
577
578        // Parse PDU using the existing Pdu::from_bytes method
579        let pdu = Pdu::from_bytes(pdu_bytes_slice)?;
580
581        let additional_addr = AdditionalAddress::MbapHeader(MbapHeader {
582            transaction_id,
583            protocol_id,
584            length,
585            unit_id,
586        });
587
588        Ok(ModbusMessage::new(additional_addr, pdu))
589    }
590
591    /// Converts the `ModbusMessage` into its ASCII ADU byte representation.
592    ///
593    /// This method serializes the message to binary, calculates the LRC,
594    /// and then encodes the result into Modbus ASCII format (Start ':', Hex, End CR LF).
595    ///
596    /// # Returns
597    /// `Ok(Vec<u8, MAX_ADU_FRAME_LEN>)` containing the ASCII ADU bytes.
598    ///
599    /// # Errors
600    /// Returns `MbusError::BufferTooSmall` if the resulting ASCII frame exceeds `MAX_ADU_FRAME_LEN`.
601    pub fn to_ascii_bytes(&self) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
602        let mut binary_data = self.to_bytes()?;
603
604        // Calculate LRC on Address + PDU
605        let lrc = checksum::lrc(&binary_data);
606
607        // Append LRC to binary data temporarily to iterate over it
608        binary_data
609            .push(lrc)
610            .map_err(|_| MbusError::BufferTooSmall)?;
611
612        let mut ascii_data = Vec::new();
613
614        // Start character ':'
615        ascii_data
616            .push(b':')
617            .map_err(|_| MbusError::BufferTooSmall)?;
618
619        for byte in binary_data {
620            let high = (byte >> 4) & 0x0F;
621            let low = byte & 0x0F;
622
623            ascii_data
624                .push(nibble_to_hex(high))
625                .map_err(|_| MbusError::BufferTooSmall)?;
626            ascii_data
627                .push(nibble_to_hex(low))
628                .map_err(|_| MbusError::BufferTooSmall)?;
629        }
630
631        // End characters CR LF
632        ascii_data
633            .push(b'\r')
634            .map_err(|_| MbusError::BufferTooSmall)?;
635        ascii_data
636            .push(b'\n')
637            .map_err(|_| MbusError::BufferTooSmall)?;
638
639        Ok(ascii_data)
640    }
641
642    /// Creates a `ModbusMessage` from a raw Modbus RTU byte slice.
643    ///
644    /// This method validates the RTU frame by checking the minimum length and
645    /// verifying the 16-bit CRC (Cyclic Redundancy Check).
646    ///
647    /// # Arguments
648    /// * `frame` - A byte slice containing the complete Modbus RTU ADU.
649    ///
650    /// # Returns
651    /// * `Ok(ModbusMessage)` if the CRC is valid and the PDU is correctly parsed.
652    /// * `Err(MbusError)` if the frame is too short, the checksum fails, or the PDU is invalid.
653    pub fn from_rtu_bytes(frame: &[u8]) -> Result<Self, MbusError> {
654        // RTU Frame: [Slave Address (1)] [PDU (N)] [CRC (2)]
655        // Minimum length: MIN_RTU_ADU_LEN (4)
656        if frame.len() < MIN_RTU_ADU_LEN {
657            return Err(MbusError::InvalidAduLength);
658        }
659
660        // The CRC is the last 2 bytes of the frame
661        let data_len = frame.len() - RTU_CRC_SIZE;
662        let data_to_check = &frame[..data_len];
663
664        // Modbus RTU uses Little-Endian for CRC transmission
665        let received_crc = u16::from_le_bytes([frame[data_len], frame[data_len + 1]]);
666        let calculated_crc = checksum::crc16(data_to_check);
667
668        // Verify data integrity
669        if calculated_crc != received_crc {
670            return Err(MbusError::ChecksumError); // CRC Mismatch
671        }
672
673        // Extract Slave Address (1st byte)
674        let slave_address = SlaveAddress::new(frame[0])?;
675
676        // PDU is from byte 1 to end of data (excluding CRC)
677        let pdu_bytes = &data_to_check[1..];
678        let pdu = Pdu::from_bytes(pdu_bytes)?;
679
680        Ok(ModbusMessage::new(
681            AdditionalAddress::SlaveAddress(slave_address),
682            pdu,
683        ))
684    }
685
686    /// Creates a `ModbusMessage` from a raw Modbus ASCII byte slice.
687    ///
688    /// This method performs the following validation and transformation steps:
689    /// 1. Validates the frame structure (starts with ':', ends with "\r\n").
690    /// 2. Decodes the hexadecimal ASCII representation into binary data.
691    /// 3. Verifies the Longitudinal Redundancy Check (LRC) checksum.
692    /// 4. Parses the resulting binary into a `SlaveAddress` and `Pdu`.
693    ///
694    /// # Arguments
695    /// * `frame` - A byte slice containing the complete Modbus ASCII ADU.
696    ///
697    /// # Returns
698    /// * `Ok(ModbusMessage)` if the frame is valid and checksum matches.
699    /// * `Err(MbusError)` for invalid length, malformed hex, or checksum failure.
700    pub fn from_ascii_bytes(frame: &[u8]) -> Result<Self, MbusError> {
701        // ASCII Frame: [Start (:)] [Address (2)] [PDU (N)] [LRC (2)] [End (\r\n)]
702        // Minimum length: MIN_ASCII_ADU_LEN (9)
703        if frame.len() < MIN_ASCII_ADU_LEN {
704            return Err(MbusError::InvalidAduLength);
705        }
706
707        // Check Start and End characters
708        if frame[0] != b':' {
709            return Err(MbusError::BasicParseError); // Missing start char
710        }
711        if frame[frame.len() - 2] != b'\r' || frame[frame.len() - 1] != b'\n' {
712            return Err(MbusError::BasicParseError); // Missing end chars
713        }
714
715        // Extract Hex content (excluding ':' and '\r\n')
716        let hex_content = &frame[ASCII_START_SIZE..frame.len() - ASCII_END_SIZE];
717        if !hex_content.len().is_multiple_of(2) {
718            return Err(MbusError::BasicParseError); // Odd length hex string
719        }
720
721        // Decode Hex to Binary
722        // Max binary length = (513 - 3) / 2 = 255. Using 260 for safety.
723        let mut binary_data: Vec<u8, 260> = Vec::new();
724        for chunk in hex_content.chunks(2) {
725            let byte = hex_pair_to_byte(chunk[0], chunk[1])?;
726            binary_data
727                .push(byte)
728                .map_err(|_| MbusError::BufferTooSmall)?;
729        }
730
731        // Binary structure: [Slave Address (1)] [PDU (N)] [LRC (1)]
732        if binary_data.len() < 2 {
733            return Err(MbusError::InvalidAduLength);
734        }
735
736        let data_len = binary_data.len() - 1;
737        let data_to_check = &binary_data[..data_len];
738        let received_lrc = binary_data[data_len];
739
740        let calculated_lrc = checksum::lrc(data_to_check);
741
742        if calculated_lrc != received_lrc {
743            return Err(MbusError::ChecksumError); // LRC Mismatch
744        }
745
746        let slave_address = SlaveAddress::new(binary_data[0])?;
747        let pdu_bytes = &binary_data[1..data_len];
748        let pdu = Pdu::from_bytes(pdu_bytes)?;
749
750        Ok(ModbusMessage::new(
751            AdditionalAddress::SlaveAddress(slave_address),
752            pdu,
753        ))
754    }
755}
756
757impl Pdu {
758    ///
759    /// Creates a new `Pdu` instance.
760    ///
761    /// # Arguments
762    /// * `function_code` - The Modbus function code.
763    /// * `data` - The data payload (either raw bytes or structured sub-codes).
764    /// * `data_len` - The actual length of the data payload in bytes.
765    ///
766    /// # Returns
767    /// A new `Pdu` instance.
768    pub fn new(
769        function_code: FunctionCode,
770        data: heapless::Vec<u8, MAX_PDU_DATA_LEN>,
771        data_len: u8,
772    ) -> Self {
773        Self {
774            function_code,
775            error_code: None, // Default to None for normal responses; can be set for exceptions
776            data,             // Ensure the heapless::Vec is moved here
777            data_len,
778        }
779    }
780
781    /// Accessor for the function code.
782    pub fn function_code(&self) -> FunctionCode {
783        self.function_code
784    }
785
786    /// Accessor for the data payload.
787    pub fn data(&self) -> &Vec<u8, MAX_PDU_DATA_LEN> {
788        &self.data
789    }
790
791    /// Accessor for the actual length of the data payload.
792    pub fn data_len(&self) -> u8 {
793        self.data_len
794    }
795
796    /// Accessor for the error code from the PDU.
797    pub fn error_code(&self) -> Option<u8> {
798        self.error_code
799    }
800
801    // -------- PDU construction helpers ------------------------------------------------
802
803    /// Builds a PDU with `[address_hi, address_lo, quantity_hi, quantity_lo]` layout (4 bytes).
804    ///
805    /// Used by FC01/02/03/04 requests and as a general address+quantity builder.
806    pub fn build_read_window(
807        fc: FunctionCode,
808        address: u16,
809        quantity: u16,
810    ) -> Result<Self, MbusError> {
811        let mut data: heapless::Vec<u8, MAX_PDU_DATA_LEN> = heapless::Vec::new();
812        data.extend_from_slice(&address.to_be_bytes())
813            .map_err(|_| MbusError::BufferLenMissmatch)?;
814        data.extend_from_slice(&quantity.to_be_bytes())
815            .map_err(|_| MbusError::BufferLenMissmatch)?;
816        Ok(Pdu::new(fc, data, 4))
817    }
818
819    /// Builds a PDU with `[address_hi, address_lo, value_hi, value_lo]` layout (4 bytes).
820    ///
821    /// Used by FC05 and FC06 single-write requests.
822    pub fn build_write_single_u16(
823        fc: FunctionCode,
824        address: u16,
825        value: u16,
826    ) -> Result<Self, MbusError> {
827        let mut data: heapless::Vec<u8, MAX_PDU_DATA_LEN> = heapless::Vec::new();
828        data.extend_from_slice(&address.to_be_bytes())
829            .map_err(|_| MbusError::BufferLenMissmatch)?;
830        data.extend_from_slice(&value.to_be_bytes())
831            .map_err(|_| MbusError::BufferLenMissmatch)?;
832        Ok(Pdu::new(fc, data, 4))
833    }
834
835    /// Builds a PDU with `[address, quantity, byte_count, values...]` layout.
836    ///
837    /// `values` must already be the packed byte representation (coil bits or register word bytes).
838    /// `byte_count` is derived from `values.len()`.
839    ///
840    /// Used by FC0F (Write Multiple Coils) and FC10 (Write Multiple Registers) requests.
841    pub fn build_write_multiple(
842        fc: FunctionCode,
843        address: u16,
844        quantity: u16,
845        values: &[u8],
846    ) -> Result<Self, MbusError> {
847        let byte_count = values.len();
848        if byte_count > u8::MAX as usize {
849            return Err(MbusError::InvalidByteCount);
850        }
851        let data_len = 5 + byte_count;
852        if data_len > MAX_PDU_DATA_LEN {
853            return Err(MbusError::BufferTooSmall);
854        }
855        let mut data: heapless::Vec<u8, MAX_PDU_DATA_LEN> = heapless::Vec::new();
856        data.extend_from_slice(&address.to_be_bytes())
857            .map_err(|_| MbusError::BufferLenMissmatch)?;
858        data.extend_from_slice(&quantity.to_be_bytes())
859            .map_err(|_| MbusError::BufferLenMissmatch)?;
860        data.push(byte_count as u8)
861            .map_err(|_| MbusError::BufferLenMissmatch)?;
862        data.extend_from_slice(values)
863            .map_err(|_| MbusError::BufferLenMissmatch)?;
864        Ok(Pdu::new(fc, data, data_len as u8))
865    }
866
867    /// Builds a PDU with `[address, and_mask, or_mask]` layout (6 bytes).
868    ///
869    /// Used by FC16 (Mask Write Register) requests.
870    #[cfg(feature = "holding-registers")]
871    pub fn build_mask_write_register(
872        address: u16,
873        and_mask: u16,
874        or_mask: u16,
875    ) -> Result<Self, MbusError> {
876        let mut data: heapless::Vec<u8, MAX_PDU_DATA_LEN> = heapless::Vec::new();
877        data.extend_from_slice(&address.to_be_bytes())
878            .map_err(|_| MbusError::BufferLenMissmatch)?;
879        data.extend_from_slice(&and_mask.to_be_bytes())
880            .map_err(|_| MbusError::BufferLenMissmatch)?;
881        data.extend_from_slice(&or_mask.to_be_bytes())
882            .map_err(|_| MbusError::BufferLenMissmatch)?;
883        Ok(Pdu::new(FunctionCode::MaskWriteRegister, data, 6))
884    }
885
886    /// Builds a PDU for FC17 (Read/Write Multiple Registers) requests.
887    ///
888    /// `write_values` must already be the packed byte representation of the register words.
889    /// `write_byte_count` is derived from `write_values.len()`.
890    #[cfg(feature = "holding-registers")]
891    pub fn build_read_write_multiple(
892        read_address: u16,
893        read_quantity: u16,
894        write_address: u16,
895        write_quantity: u16,
896        write_values: &[u8],
897    ) -> Result<Self, MbusError> {
898        let byte_count = write_values.len();
899        if byte_count > u8::MAX as usize {
900            return Err(MbusError::InvalidByteCount);
901        }
902        let data_len = 9 + byte_count;
903        if data_len > MAX_PDU_DATA_LEN {
904            return Err(MbusError::BufferTooSmall);
905        }
906        let mut data: heapless::Vec<u8, MAX_PDU_DATA_LEN> = heapless::Vec::new();
907        data.extend_from_slice(&read_address.to_be_bytes())
908            .map_err(|_| MbusError::BufferLenMissmatch)?;
909        data.extend_from_slice(&read_quantity.to_be_bytes())
910            .map_err(|_| MbusError::BufferLenMissmatch)?;
911        data.extend_from_slice(&write_address.to_be_bytes())
912            .map_err(|_| MbusError::BufferLenMissmatch)?;
913        data.extend_from_slice(&write_quantity.to_be_bytes())
914            .map_err(|_| MbusError::BufferLenMissmatch)?;
915        data.push(byte_count as u8)
916            .map_err(|_| MbusError::BufferLenMissmatch)?;
917        data.extend_from_slice(write_values)
918            .map_err(|_| MbusError::BufferLenMissmatch)?;
919        Ok(Pdu::new(
920            FunctionCode::ReadWriteMultipleRegisters,
921            data,
922            data_len as u8,
923        ))
924    }
925
926    /// Builds a PDU with `[sub_function_hi, sub_function_lo, word0_hi, word0_lo, ...]` layout.
927    ///
928    /// Used by FC08 (Diagnostics) requests.
929    pub fn build_sub_function(
930        fc: FunctionCode,
931        sub_function: u16,
932        words: &[u16],
933    ) -> Result<Self, MbusError> {
934        let data_len = 2 + words.len() * 2;
935        if data_len > MAX_PDU_DATA_LEN {
936            return Err(MbusError::BufferTooSmall);
937        }
938        let mut data: heapless::Vec<u8, MAX_PDU_DATA_LEN> = heapless::Vec::new();
939        data.extend_from_slice(&sub_function.to_be_bytes())
940            .map_err(|_| MbusError::BufferLenMissmatch)?;
941        for &word in words {
942            data.extend_from_slice(&word.to_be_bytes())
943                .map_err(|_| MbusError::BufferLenMissmatch)?;
944        }
945        Ok(Pdu::new(fc, data, data_len as u8))
946    }
947
948    /// Builds a PDU with `[mei_type, payload...]` layout.
949    ///
950    /// Used by FC2B (Encapsulated Interface Transport) requests.
951    pub fn build_mei_type(
952        fc: FunctionCode,
953        mei_type: u8,
954        payload: &[u8],
955    ) -> Result<Self, MbusError> {
956        let data_len = 1 + payload.len();
957        if data_len > MAX_PDU_DATA_LEN {
958            return Err(MbusError::BufferTooSmall);
959        }
960        let mut data: heapless::Vec<u8, MAX_PDU_DATA_LEN> = heapless::Vec::new();
961        data.push(mei_type)
962            .map_err(|_| MbusError::BufferLenMissmatch)?;
963        data.extend_from_slice(payload)
964            .map_err(|_| MbusError::BufferLenMissmatch)?;
965        Ok(Pdu::new(fc, data, data_len as u8))
966    }
967
968    /// Builds a PDU containing a single u16 value `[value_hi, value_lo]` (2 bytes).
969    ///
970    /// Used by FC18 (Read FIFO Queue) request (pointer address).
971    pub fn build_u16_payload(fc: FunctionCode, value: u16) -> Result<Self, MbusError> {
972        let mut data: heapless::Vec<u8, MAX_PDU_DATA_LEN> = heapless::Vec::new();
973        data.extend_from_slice(&value.to_be_bytes())
974            .map_err(|_| MbusError::BufferLenMissmatch)?;
975        Ok(Pdu::new(fc, data, 2))
976    }
977
978    /// Builds a PDU containing a single byte payload `[byte]` (1 byte).
979    ///
980    /// Used for exception response PDUs.
981    pub fn build_byte_payload(fc: FunctionCode, byte: u8) -> Result<Self, MbusError> {
982        let mut data: heapless::Vec<u8, MAX_PDU_DATA_LEN> = heapless::Vec::new();
983        data.push(byte).map_err(|_| MbusError::BufferLenMissmatch)?;
984        Ok(Pdu::new(fc, data, 1))
985    }
986
987    /// Builds a PDU with an empty data payload (0 bytes).
988    ///
989    /// Used by no-argument requests such as FC07, FC0B, FC0C, and FC11.
990    pub fn build_empty(fc: FunctionCode) -> Self {
991        Pdu::new(fc, heapless::Vec::new(), 0)
992    }
993
994    /// Builds a PDU with `[byte_count, payload...]` layout.
995    ///
996    /// `byte_count` is derived from `payload.len()`. Returns `InvalidByteCount`
997    /// if the payload is longer than 255 bytes.
998    ///
999    /// Used by FC01–FC04 read responses, FC11, FC14, FC15, and FC17 responses.
1000    pub fn build_byte_count_payload(fc: FunctionCode, payload: &[u8]) -> Result<Self, MbusError> {
1001        let byte_count = u8::try_from(payload.len()).map_err(|_| MbusError::InvalidByteCount)?;
1002        let data_len = 1 + payload.len();
1003        if data_len > MAX_PDU_DATA_LEN {
1004            return Err(MbusError::BufferTooSmall);
1005        }
1006        let mut data: heapless::Vec<u8, MAX_PDU_DATA_LEN> = heapless::Vec::new();
1007        data.push(byte_count)
1008            .map_err(|_| MbusError::BufferLenMissmatch)?;
1009        data.extend_from_slice(payload)
1010            .map_err(|_| MbusError::BufferLenMissmatch)?;
1011        Ok(Pdu::new(fc, data, data_len as u8))
1012    }
1013
1014    /// Builds a PDU for FC08 (Diagnostics) responses.
1015    ///
1016    /// Response layout: `[sub_function_hi, sub_function_lo, result_hi, result_lo]` (4 bytes).
1017    /// Echoes the sub-function code with a result word.
1018    #[cfg(feature = "diagnostics")]
1019    pub fn build_diagnostics(sub_function: u16, result: u16) -> Result<Self, MbusError> {
1020        let mut data: heapless::Vec<u8, MAX_PDU_DATA_LEN> = heapless::Vec::new();
1021        data.extend_from_slice(&sub_function.to_be_bytes())
1022            .map_err(|_| MbusError::BufferLenMissmatch)?;
1023        data.extend_from_slice(&result.to_be_bytes())
1024            .map_err(|_| MbusError::BufferLenMissmatch)?;
1025        Ok(Pdu::new(FunctionCode::Diagnostics, data, 4))
1026    }
1027
1028    /// Builds a PDU for FC18 (Read FIFO Queue) responses.
1029    ///
1030    /// Response layout: `[byte_count_hi, byte_count_lo, payload...]` where byte_count is u16.
1031    /// `app_payload` is the application data: `[fifo_count_hi, fifo_count_lo, values...]`.
1032    /// Returns `InvalidByteCount` if payload exceeds 65535 bytes.
1033    #[cfg(feature = "fifo")]
1034    pub fn build_fifo_payload(app_payload: &[u8]) -> Result<Self, MbusError> {
1035        let byte_count =
1036            u16::try_from(app_payload.len()).map_err(|_| MbusError::InvalidByteCount)?;
1037        let data_len = 2 + app_payload.len();
1038        if data_len > MAX_PDU_DATA_LEN {
1039            return Err(MbusError::BufferTooSmall);
1040        }
1041        let mut data: heapless::Vec<u8, MAX_PDU_DATA_LEN> = heapless::Vec::new();
1042        data.extend_from_slice(&byte_count.to_be_bytes())
1043            .map_err(|_| MbusError::BufferLenMissmatch)?;
1044        data.extend_from_slice(app_payload)
1045            .map_err(|_| MbusError::BufferLenMissmatch)?;
1046        Ok(Pdu::new(FunctionCode::ReadFifoQueue, data, data_len as u8))
1047    }
1048
1049    // -------- PDU parsing helpers -----------------------------------------------------
1050
1051    /// Reads the standard address + quantity pair from PDU bytes 0-3.
1052    /// Used by multiple function codes (FC01-04 requests, FC0F/10 requests).
1053    #[inline]
1054    fn read_address_quantity_pair(&self) -> Result<(u16, u16), MbusError> {
1055        Ok((
1056            u16::from_be_bytes([
1057                self.data[PDU_ADDRESS_OFFSET_1B],
1058                self.data[PDU_ADDRESS_OFFSET_2B],
1059            ]),
1060            u16::from_be_bytes([
1061                self.data[PDU_QUANTITY_OFFSET_1B],
1062                self.data[PDU_QUANTITY_OFFSET_2B],
1063            ]),
1064        ))
1065    }
1066
1067    /// Parses a read-window request payload into typed fields.
1068    ///
1069    /// Expected PDU data layout: `[address_hi, address_lo, quantity_hi, quantity_lo]`.
1070    pub fn read_window(&self) -> Result<ReadWindow, MbusError> {
1071        if self.data_len != 4 {
1072            return Err(MbusError::InvalidPduLength);
1073        }
1074
1075        let (address, quantity) = self.read_address_quantity_pair()?;
1076        Ok(ReadWindow { address, quantity })
1077    }
1078
1079    /// Parses FC05/FC06-style payloads: address + value.
1080    pub fn write_single_u16_fields(&self) -> Result<WriteSingleU16Fields, MbusError> {
1081        if self.data_len != 4 {
1082            return Err(MbusError::InvalidPduLength);
1083        }
1084
1085        Ok(WriteSingleU16Fields {
1086            address: u16::from_be_bytes([
1087                self.data[PDU_ADDRESS_OFFSET_1B],
1088                self.data[PDU_ADDRESS_OFFSET_2B],
1089            ]),
1090            value: u16::from_be_bytes([
1091                self.data[PDU_QUANTITY_OFFSET_1B],
1092                self.data[PDU_QUANTITY_OFFSET_2B],
1093            ]),
1094        })
1095    }
1096
1097    /// Parses FC0F/FC10-style payloads: address + quantity + byte_count + values.
1098    pub fn write_multiple_fields(&self) -> Result<WriteMultipleFields<'_>, MbusError> {
1099        if self.data_len < 5 {
1100            return Err(MbusError::InvalidPduLength);
1101        }
1102
1103        let (address, quantity) = self.read_address_quantity_pair()?;
1104        let byte_count = self.data[PDU_BYTE_COUNT_OFFSET];
1105        let expected_len = 5usize
1106            .checked_add(byte_count as usize)
1107            .ok_or(MbusError::InvalidByteCount)?;
1108
1109        if self.data_len as usize != expected_len {
1110            return Err(MbusError::InvalidByteCount);
1111        }
1112
1113        Ok(WriteMultipleFields {
1114            address,
1115            quantity,
1116            byte_count,
1117            values: &self.data[5..expected_len],
1118        })
1119    }
1120
1121    /// Parses FC16 mask-write payload: address + and-mask + or-mask.
1122    pub fn mask_write_register_fields(&self) -> Result<MaskWriteRegisterFields, MbusError> {
1123        if self.data_len != 6 {
1124            return Err(MbusError::InvalidPduLength);
1125        }
1126
1127        Ok(MaskWriteRegisterFields {
1128            address: u16::from_be_bytes([
1129                self.data[PDU_ADDRESS_OFFSET_1B],
1130                self.data[PDU_ADDRESS_OFFSET_2B],
1131            ]),
1132            and_mask: u16::from_be_bytes([
1133                self.data[PDU_AND_MASK_OFFSET_1B],
1134                self.data[PDU_AND_MASK_OFFSET_2B],
1135            ]),
1136            or_mask: u16::from_be_bytes([
1137                self.data[PDU_OR_MASK_OFFSET_1B],
1138                self.data[PDU_OR_MASK_OFFSET_2B],
1139            ]),
1140        })
1141    }
1142
1143    /// Parses byte-count prefixed payloads used by read-style responses.
1144    pub fn byte_count_payload(&self) -> Result<ByteCountPayload<'_>, MbusError> {
1145        if self.data_len < 1 {
1146            return Err(MbusError::InvalidPduLength);
1147        }
1148
1149        let byte_count = self.data[0];
1150        let expected_len = 1usize
1151            .checked_add(byte_count as usize)
1152            .ok_or(MbusError::InvalidByteCount)?;
1153        if self.data_len as usize != expected_len {
1154            return Err(MbusError::InvalidByteCount);
1155        }
1156
1157        Ok(ByteCountPayload {
1158            byte_count,
1159            payload: &self.data[1..expected_len],
1160        })
1161    }
1162
1163    /// Parses FC17 read/write multiple registers payload.
1164    pub fn read_write_multiple_fields(&self) -> Result<ReadWriteMultipleFields<'_>, MbusError> {
1165        if (self.data_len as usize) < PDU_FC17_WRITE_VALUES_OFFSET {
1166            return Err(MbusError::InvalidPduLength);
1167        }
1168
1169        let (read_address, read_quantity) = self.read_address_quantity_pair()?;
1170        let write_address = u16::from_be_bytes([
1171            self.data[PDU_FC17_WRITE_ADDRESS_OFFSET_1B],
1172            self.data[PDU_FC17_WRITE_ADDRESS_OFFSET_2B],
1173        ]);
1174        let write_quantity = u16::from_be_bytes([
1175            self.data[PDU_FC17_WRITE_QUANTITY_OFFSET_1B],
1176            self.data[PDU_FC17_WRITE_QUANTITY_OFFSET_2B],
1177        ]);
1178        let write_byte_count = self.data[PDU_FC17_WRITE_BYTE_COUNT_OFFSET];
1179        let expected_len = PDU_FC17_WRITE_VALUES_OFFSET
1180            .checked_add(write_byte_count as usize)
1181            .ok_or(MbusError::InvalidByteCount)?;
1182
1183        if self.data_len as usize != expected_len {
1184            return Err(MbusError::InvalidByteCount);
1185        }
1186
1187        Ok(ReadWriteMultipleFields {
1188            read_address,
1189            read_quantity,
1190            write_address,
1191            write_quantity,
1192            write_byte_count,
1193            write_values: &self.data[PDU_FC17_WRITE_VALUES_OFFSET..expected_len],
1194        })
1195    }
1196
1197    /// Parses a single-byte payload for FC07-style responses. Data must be exactly 1 byte.
1198    pub fn single_byte_payload(&self) -> Result<u8, MbusError> {
1199        if self.data_len != 1 {
1200            return Err(MbusError::InvalidPduLength);
1201        }
1202        Ok(self.data[0])
1203    }
1204
1205    /// Parses FC08-style payloads: a 2-byte sub-function code followed by an even-length
1206    /// sequence of data words. Validates minimum length and even alignment.
1207    pub fn sub_function_payload(&self) -> Result<SubFunctionPayload<'_>, MbusError> {
1208        if self.data_len < 2 {
1209            return Err(MbusError::InvalidPduLength);
1210        }
1211        if !self.data_len.is_multiple_of(2) {
1212            return Err(MbusError::InvalidPduLength);
1213        }
1214        Ok(SubFunctionPayload {
1215            sub_function: u16::from_be_bytes([
1216                self.data[PDU_SUB_FUNCTION_OFFSET_1B],
1217                self.data[PDU_SUB_FUNCTION_OFFSET_2B],
1218            ]),
1219            payload: &self.data[2..self.data_len as usize],
1220        })
1221    }
1222
1223    /// Parses FC0B-style payloads: exactly two consecutive 16-bit values (4 bytes total).
1224    pub fn u16_pair_fields(&self) -> Result<U16PairFields, MbusError> {
1225        if self.data_len != 4 {
1226            return Err(MbusError::InvalidPduLength);
1227        }
1228        Ok(U16PairFields {
1229            first: u16::from_be_bytes([
1230                self.data[PDU_ADDRESS_OFFSET_1B],
1231                self.data[PDU_ADDRESS_OFFSET_2B],
1232            ]),
1233            second: u16::from_be_bytes([
1234                self.data[PDU_QUANTITY_OFFSET_1B],
1235                self.data[PDU_QUANTITY_OFFSET_2B],
1236            ]),
1237        })
1238    }
1239
1240    /// Parses FC2B-style payloads: a 1-byte MEI type followed by variable-length data.
1241    /// Data must have at least 1 byte.
1242    pub fn mei_type_payload(&self) -> Result<MeiTypePayload<'_>, MbusError> {
1243        if self.data_len < 1 {
1244            return Err(MbusError::InvalidPduLength);
1245        }
1246        Ok(MeiTypePayload {
1247            mei_type_byte: self.data[PDU_MEI_TYPE_OFFSET],
1248            payload: &self.data[1..self.data_len as usize],
1249        })
1250    }
1251
1252    /// Parses FC18-style payloads: a 2-byte FIFO byte count, 2-byte FIFO count, and raw
1253    /// value bytes. Data must have at least 4 bytes. Cross-field consistency validation
1254    /// (byte count vs FIFO count) is left to the caller.
1255    pub fn fifo_payload(&self) -> Result<FifoPayload<'_>, MbusError> {
1256        if self.data_len < 4 {
1257            return Err(MbusError::InvalidPduLength);
1258        }
1259        Ok(FifoPayload {
1260            fifo_byte_count: u16::from_be_bytes([
1261                self.data[PDU_FIFO_BYTE_COUNT_OFFSET_1B],
1262                self.data[PDU_FIFO_BYTE_COUNT_OFFSET_2B],
1263            ]),
1264            fifo_count: u16::from_be_bytes([
1265                self.data[PDU_FIFO_COUNT_OFFSET_1B],
1266                self.data[PDU_FIFO_COUNT_OFFSET_2B],
1267            ]),
1268            values: &self.data[PDU_FIFO_VALUES_OFFSET..self.data_len as usize],
1269        })
1270    }
1271
1272    /// Parses FC18 (Read FIFO Queue) request: a single 2-byte FIFO pointer address.
1273    /// Used to extract the pointer address from read FIFO queue requests.
1274    #[cfg(feature = "fifo")]
1275    pub fn fifo_pointer(&self) -> Result<u16, MbusError> {
1276        if self.data_len != 2 {
1277            return Err(MbusError::InvalidPduLength);
1278        }
1279        Ok(u16::from_be_bytes([
1280            self.data[PDU_ADDRESS_OFFSET_1B],
1281            self.data[PDU_ADDRESS_OFFSET_2B],
1282        ]))
1283    }
1284
1285    /// Parses FC08 (Diagnostics) request/response: sub-function code + data word.
1286    /// Returns (sub_function, data_word).
1287    #[cfg(feature = "diagnostics")]
1288    pub fn diagnostics_fields(&self) -> Result<(u16, u16), MbusError> {
1289        if self.data_len < 4 {
1290            return Err(MbusError::InvalidPduLength);
1291        }
1292        let sub_function = u16::from_be_bytes([
1293            self.data[PDU_SUB_FUNCTION_OFFSET_1B],
1294            self.data[PDU_SUB_FUNCTION_OFFSET_2B],
1295        ]);
1296        let data_word = u16::from_be_bytes([self.data[2], self.data[3]]);
1297        Ok((sub_function, data_word))
1298    }
1299
1300    /// Parses FC14 (Read File Record) requests into validated sub-requests.
1301    ///
1302    /// Request PDU data layout:
1303    /// - byte_count (1)
1304    /// - repeated sub-requests (7 bytes each):
1305    ///   - reference_type (1) = 0x06
1306    ///   - file_number (2)
1307    ///   - record_number (2)
1308    ///   - record_length (2)
1309    #[cfg(feature = "file-record")]
1310    pub fn file_record_read_sub_requests(
1311        &self,
1312    ) -> Result<
1313        heapless::Vec<
1314            crate::models::file_record::FileRecordReadSubRequest,
1315            { crate::models::file_record::MAX_SUB_REQUESTS_PER_PDU },
1316        >,
1317        MbusError,
1318    > {
1319        use crate::models::file_record::{FILE_RECORD_REF_TYPE, FileRecordReadSubRequest};
1320
1321        const FILE_RECORD_READ_SUB_REQUEST_LEN: usize = 7;
1322        const FILE_RECORD_MAX_REQUEST_BYTE_COUNT: usize = 245;
1323
1324        let data_len = self.data_len as usize;
1325        if data_len < 1 + FILE_RECORD_READ_SUB_REQUEST_LEN {
1326            return Err(MbusError::InvalidPduLength);
1327        }
1328
1329        let data = self.data.as_slice();
1330        let byte_count = data[0] as usize;
1331        if byte_count != data_len - 1 {
1332            return Err(MbusError::InvalidByteCount);
1333        }
1334        if byte_count > FILE_RECORD_MAX_REQUEST_BYTE_COUNT {
1335            return Err(MbusError::InvalidByteCount);
1336        }
1337        if !byte_count.is_multiple_of(FILE_RECORD_READ_SUB_REQUEST_LEN) {
1338            return Err(MbusError::InvalidByteCount);
1339        }
1340
1341        let mut out: heapless::Vec<
1342            FileRecordReadSubRequest,
1343            { crate::models::file_record::MAX_SUB_REQUESTS_PER_PDU },
1344        > = heapless::Vec::new();
1345        let mut index = 1usize;
1346        while index < data_len {
1347            if data[index] != FILE_RECORD_REF_TYPE {
1348                return Err(MbusError::InvalidValue);
1349            }
1350            let file_number = u16::from_be_bytes([data[index + 1], data[index + 2]]);
1351            let record_number = u16::from_be_bytes([data[index + 3], data[index + 4]]);
1352            let record_length = u16::from_be_bytes([data[index + 5], data[index + 6]]);
1353            if record_length == 0 {
1354                return Err(MbusError::InvalidQuantity);
1355            }
1356
1357            out.push(FileRecordReadSubRequest {
1358                file_number,
1359                record_number,
1360                record_length,
1361            })
1362            .map_err(|_| MbusError::TooManyFileReadSubRequests)?;
1363
1364            index += FILE_RECORD_READ_SUB_REQUEST_LEN;
1365        }
1366
1367        Ok(out)
1368    }
1369
1370    /// Parses FC15 (Write File Record) requests into validated sub-requests.
1371    ///
1372    /// Request PDU data layout:
1373    /// - byte_count (1)
1374    /// - repeated sub-requests:
1375    ///   - reference_type (1) = 0x06
1376    ///   - file_number (2)
1377    ///   - record_number (2)
1378    ///   - record_length (2)
1379    ///   - record_data (record_length * 2)
1380    #[cfg(feature = "file-record")]
1381    pub fn file_record_write_sub_requests<'a>(
1382        &'a self,
1383    ) -> Result<
1384        heapless::Vec<
1385            crate::models::file_record::FileRecordWriteSubRequest<'a>,
1386            { crate::models::file_record::MAX_SUB_REQUESTS_PER_PDU },
1387        >,
1388        MbusError,
1389    > {
1390        use crate::models::file_record::{FILE_RECORD_REF_TYPE, FileRecordWriteSubRequest};
1391
1392        const FILE_RECORD_WRITE_SUB_REQUEST_HEADER_LEN: usize = 7;
1393        const FILE_RECORD_MAX_REQUEST_BYTE_COUNT: usize = 245;
1394
1395        let data_len = self.data_len as usize;
1396        if data_len < 1 + FILE_RECORD_WRITE_SUB_REQUEST_HEADER_LEN + 2 {
1397            return Err(MbusError::InvalidPduLength);
1398        }
1399
1400        let data = self.data.as_slice();
1401        let byte_count = data[0] as usize;
1402        if byte_count != data_len - 1 {
1403            return Err(MbusError::InvalidByteCount);
1404        }
1405        if byte_count > FILE_RECORD_MAX_REQUEST_BYTE_COUNT {
1406            return Err(MbusError::InvalidByteCount);
1407        }
1408
1409        let mut out: heapless::Vec<
1410            FileRecordWriteSubRequest,
1411            { crate::models::file_record::MAX_SUB_REQUESTS_PER_PDU },
1412        > = heapless::Vec::new();
1413        let mut index = 1usize;
1414        while index < data_len {
1415            if index + FILE_RECORD_WRITE_SUB_REQUEST_HEADER_LEN > data_len {
1416                return Err(MbusError::InvalidPduLength);
1417            }
1418            if data[index] != FILE_RECORD_REF_TYPE {
1419                return Err(MbusError::InvalidValue);
1420            }
1421
1422            let file_number = u16::from_be_bytes([data[index + 1], data[index + 2]]);
1423            let record_number = u16::from_be_bytes([data[index + 3], data[index + 4]]);
1424            let record_length = u16::from_be_bytes([data[index + 5], data[index + 6]]);
1425            if record_length == 0 {
1426                return Err(MbusError::InvalidQuantity);
1427            }
1428
1429            let data_bytes_len = record_length as usize * 2;
1430            let end_index = index
1431                .checked_add(FILE_RECORD_WRITE_SUB_REQUEST_HEADER_LEN + data_bytes_len)
1432                .ok_or(MbusError::InvalidByteCount)?;
1433            if end_index > data_len {
1434                return Err(MbusError::InvalidByteCount);
1435            }
1436
1437            out.push(FileRecordWriteSubRequest {
1438                file_number,
1439                record_number,
1440                record_length,
1441                record_data_bytes: &data
1442                    [index + FILE_RECORD_WRITE_SUB_REQUEST_HEADER_LEN..end_index],
1443            })
1444            .map_err(|_| MbusError::TooManyFileReadSubRequests)?;
1445
1446            index = end_index;
1447        }
1448
1449        if out.is_empty() {
1450            return Err(MbusError::InvalidPduLength);
1451        }
1452
1453        Ok(out)
1454    }
1455
1456    /// Parses FC2B / MEI 0x0E (Read Device Identification) response structural fields.
1457    ///
1458    /// Validates the 6-byte header minimum length, walks through all declared objects
1459    /// to confirm their lengths fit within the PDU, and returns raw bytes for each
1460    /// header field. Callers must:
1461    /// - Check `mei_type_byte == EncapsulatedInterfaceType::ReadDeviceIdentification as u8`
1462    /// - Convert `read_device_id_code_byte` and `conformity_level_byte` via `TryFrom`
1463    pub fn read_device_id_fields(&self) -> Result<ReadDeviceIdPduFields, MbusError> {
1464        // Minimum: MEI(1) + ReadCode(1) + Conf(1) + More(1) + NextId(1) + NumObj(1)
1465        if (self.data_len as usize) < PDU_MEI_OBJECTS_DATA_OFFSET {
1466            return Err(MbusError::InvalidPduLength);
1467        }
1468        let data = self.data.as_slice();
1469        let number_of_objects = data[PDU_MEI_NUM_OBJECTS_OFFSET];
1470
1471        // Walk all declared objects to validate structural integrity
1472        let mut offset = PDU_MEI_OBJECTS_DATA_OFFSET;
1473        for _ in 0..number_of_objects as usize {
1474            if offset + 2 > data.len() {
1475                return Err(MbusError::InvalidPduLength);
1476            }
1477            let obj_len = data[offset + 1] as usize;
1478            offset += 2;
1479            if offset + obj_len > data.len() {
1480                return Err(MbusError::InvalidPduLength);
1481            }
1482            offset += obj_len;
1483        }
1484
1485        let payload_len = (self.data_len as usize) - PDU_MEI_OBJECTS_DATA_OFFSET;
1486        if payload_len > MAX_PDU_DATA_LEN {
1487            return Err(MbusError::BufferTooSmall);
1488        }
1489        let mut objects_data = [0u8; MAX_PDU_DATA_LEN];
1490        if payload_len > 0 {
1491            objects_data[..payload_len].copy_from_slice(&data[PDU_MEI_OBJECTS_DATA_OFFSET..]);
1492        }
1493
1494        Ok(ReadDeviceIdPduFields {
1495            mei_type_byte: data[PDU_MEI_TYPE_OFFSET],
1496            read_device_id_code_byte: data[PDU_MEI_READ_CODE_OFFSET],
1497            conformity_level_byte: data[PDU_MEI_CONFORMITY_LEVEL_OFFSET],
1498            more_follows: data[PDU_MEI_MORE_FOLLOWS_OFFSET] == 0xFF,
1499            next_object_id_byte: data[PDU_MEI_NEXT_OBJECT_ID_OFFSET],
1500            number_of_objects,
1501            objects_data,
1502            payload_len,
1503        })
1504    }
1505
1506    /// Converts the PDU into its byte representation.
1507    ///
1508    /// This method serializes the function code and its associated data payload.
1509    /// It uses an `unsafe` block to access the `Data` union, assuming that
1510    /// `self.data.bytes` contains the full data payload and `self.data_len`
1511    /// accurately reflects its length.
1512    ///
1513    /// # Returns
1514    /// `Ok(Vec<u8, 253>)` containing the PDU bytes, or an `MbusError` if
1515    /// the PDU cannot be serialized (e.g., due to buffer overflow).
1516    ///
1517    pub fn to_bytes(&self) -> Result<Vec<u8, 253>, MbusError> {
1518        let mut pdu_bytes = Vec::new(); // Capacity is 253 (1 byte FC + 252 bytes data)
1519        pdu_bytes
1520            .push(self.function_code as u8)
1521            .map_err(|_| MbusError::Unexpected)?; // Function code (1 byte)
1522
1523        pdu_bytes
1524            .extend_from_slice(&self.data.as_slice()[..self.data_len as usize])
1525            .map_err(|_| MbusError::BufferLenMissmatch)?; // Data bytes (variable length)
1526
1527        Ok(pdu_bytes)
1528    }
1529
1530    /// Creates a PDU from its byte representation.
1531    ///
1532    /// This method parses the function code and data payload from the given byte slice.
1533    ///
1534    /// # Arguments
1535    /// * `bytes` - A byte slice containing the PDU (Function Code + Data).
1536    ///
1537    /// # Returns
1538    /// `Ok(Pdu)` if the bytes represent a valid PDU, or an `MbusError` otherwise.
1539    pub fn from_bytes(bytes: &[u8]) -> Result<Self, MbusError> {
1540        if bytes.is_empty() {
1541            return Err(MbusError::InvalidPduLength);
1542        }
1543
1544        let error_code = if is_exception_code(bytes[0]) {
1545            if bytes.len() < 2 {
1546                return Err(MbusError::InvalidPduLength);
1547            }
1548            Some(bytes[1]) // The second byte is the exception code for error responses
1549        } else {
1550            None
1551        };
1552        let function_code = clear_exception_bit(bytes[0]); // Mask out the error bit to get the actual function code
1553
1554        let function_code = FunctionCode::try_from(function_code)?;
1555
1556        let data_slice = &bytes[1..];
1557        let data_len = data_slice.len();
1558
1559        if data_len > MAX_PDU_DATA_LEN {
1560            return Err(MbusError::InvalidPduLength);
1561        }
1562
1563        let mut data = heapless::Vec::new();
1564        data.extend_from_slice(data_slice)
1565            .map_err(|_| MbusError::BufferLenMissmatch)?;
1566
1567        Ok(Pdu {
1568            function_code,
1569            error_code,
1570            data,
1571            data_len: data_len as u8,
1572        })
1573    }
1574}
1575
1576/// Helper to build the ADU from PDU based on transport type.
1577pub fn compile_adu_frame(
1578    txn_id: u16,
1579    unit_id: u8,
1580    pdu: Pdu,
1581    transport_type: TransportType,
1582) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
1583    match transport_type {
1584        TransportType::StdTcp | TransportType::CustomTcp => {
1585            let pdu_bytes_len = pdu.to_bytes()?.len() as u16;
1586            let mbap_header = MbapHeader::new(txn_id, pdu_bytes_len + 1, unit_id);
1587            ModbusMessage::new(AdditionalAddress::MbapHeader(mbap_header), pdu).to_bytes()
1588        }
1589        TransportType::StdSerial(serial_mode) | TransportType::CustomSerial(serial_mode) => {
1590            let slave_address = SlaveAddress(unit_id);
1591            let adu_bytes = match serial_mode {
1592                SerialMode::Rtu => {
1593                    let mut adu_bytes =
1594                        ModbusMessage::new(AdditionalAddress::SlaveAddress(slave_address), pdu)
1595                            .to_bytes()?;
1596                    // Calculate the 16-bit CRC for the Slave Address + PDU.
1597                    let crc16 = checksum::crc16(adu_bytes.as_slice());
1598                    // Modbus RTU transmits CRC in Little-Endian (LSB first) according to the spec.
1599                    let crc_bytes = crc16.to_le_bytes();
1600                    adu_bytes
1601                        .extend_from_slice(&crc_bytes)
1602                        .map_err(|_| MbusError::Unexpected)?;
1603
1604                    adu_bytes
1605                }
1606                SerialMode::Ascii => {
1607                    ModbusMessage::new(AdditionalAddress::SlaveAddress(slave_address), pdu)
1608                        .to_ascii_bytes()?
1609                }
1610            };
1611
1612            Ok(adu_bytes)
1613        }
1614    }
1615}
1616
1617/// Decodes a raw transport frame into a ModbusMessage based on the transport type.
1618pub fn decompile_adu_frame(
1619    frame: &[u8],
1620    transport_type: TransportType,
1621) -> Result<ModbusMessage, MbusError> {
1622    match transport_type {
1623        TransportType::StdTcp | TransportType::CustomTcp => {
1624            // Parse MBAP header and PDU
1625            ModbusMessage::from_bytes(frame)
1626        }
1627        TransportType::StdSerial(serial_mode) | TransportType::CustomSerial(serial_mode) => {
1628            match serial_mode {
1629                SerialMode::Rtu => ModbusMessage::from_rtu_bytes(frame),
1630                SerialMode::Ascii => ModbusMessage::from_ascii_bytes(frame),
1631            }
1632        }
1633    }
1634}
1635
1636/// Derives the expected total length of a Modbus frame from its initial bytes.
1637///
1638/// This function is used by stream-based transports to determine if a complete
1639/// Application Data Unit (ADU) has been received before attempting full decompression.
1640///
1641/// # Arguments
1642/// * `frame` - The raw byte buffer containing the partial or full frame.
1643/// * `transport_type` - The Modbus variant (TCP, RTU, or ASCII).
1644///
1645/// # Returns
1646/// * `Some(usize)` - The calculated total length of the frame if enough metadata is present.
1647/// * `None` - If the buffer is too short to determine the length.
1648pub fn derive_length_from_bytes(frame: &[u8], transport_type: TransportType) -> Option<usize> {
1649    match transport_type {
1650        TransportType::StdTcp | TransportType::CustomTcp => {
1651            // TCP (MBAP) requires at least 6 bytes to read the 'Length' field.
1652            // MBAP structure: TID(2), PID(2), Length(2), UnitID(1)
1653            if frame.len() < 6 {
1654                return None;
1655            }
1656
1657            // In Modbus TCP, the Protocol ID MUST be 0x0000.
1658            // If it's not, this is garbage data. We return a huge length to trigger a parse error
1659            // downstream and force the window to slide and resync.
1660            let protocol_id = u16::from_be_bytes([frame[2], frame[3]]);
1661            if protocol_id != MODBUS_PROTOCOL_ID {
1662                return Some(usize::MAX);
1663            }
1664
1665            // The Length field in MBAP (offset 4) counts all following bytes (UnitID + PDU).
1666            // Total ADU = 6 bytes (Header up to Length) + value of Length field.
1667            let length_field = u16::from_be_bytes([frame[4], frame[5]]) as usize;
1668            Some(6 + length_field)
1669        }
1670        TransportType::StdSerial(SerialMode::Rtu)
1671        | TransportType::CustomSerial(SerialMode::Rtu) => {
1672            if frame.len() < 2 {
1673                return None;
1674            }
1675
1676            let fc = frame[1];
1677
1678            // Exception responses are universally 5 bytes: [ID][FC+0x80][ExcCode][CRC_L][CRC_H]
1679            if is_exception_code(fc) {
1680                return Some(5);
1681            }
1682
1683            // Helper: Opportunistically verifies if a potential frame boundary has a valid CRC.
1684            // This allows us to disambiguate Requests vs Responses dynamically as the stream arrives.
1685            let check_crc = |len: usize| -> bool {
1686                if frame.len() >= len && len >= MIN_RTU_ADU_LEN {
1687                    let data_len = len - RTU_CRC_SIZE;
1688                    let received_crc = u16::from_le_bytes([frame[data_len], frame[data_len + 1]]);
1689                    checksum::crc16(&frame[..data_len]) == received_crc
1690                } else {
1691                    false
1692                }
1693            };
1694
1695            get_byte_count_from_frame(frame, fc, check_crc)
1696        }
1697        TransportType::StdSerial(SerialMode::Ascii)
1698        | TransportType::CustomSerial(SerialMode::Ascii) => {
1699            // ASCII frames are delimited by ':' and '\r\n'.
1700            // We scan for the end-of-frame marker.
1701            if frame.len() < MIN_ASCII_ADU_LEN {
1702                return None;
1703            }
1704
1705            // Fast linear scan for the LF character which terminates the frame
1706            frame.iter().position(|&b| b == b'\n').map(|pos| pos + 1)
1707        }
1708    }
1709}
1710
1711fn get_byte_count_from_frame(
1712    frame: &[u8],
1713    fc: u8,
1714    check_crc: impl Fn(usize) -> bool,
1715) -> Option<usize> {
1716    let mut candidates = heapless::Vec::<usize, 4>::new();
1717    let mut min_needed = usize::MAX;
1718
1719    // Helper function to safely calculate dynamic lengths based on a byte in the frame.
1720    // We pass candidates as a mutable reference to avoid "multiple mutable borrow" errors
1721    // that occur when a closure captures a mutable variable and is called multiple times.
1722    let mut add_dyn = |cands: &mut heapless::Vec<usize, 4>, offset: usize, base: usize| {
1723        if frame.len() > offset {
1724            let _ = cands.push(base + frame[offset] as usize);
1725        } else {
1726            min_needed = core::cmp::min(min_needed, offset + 1);
1727        }
1728    };
1729
1730    // Map structural candidates (Requests and Responses combined) based on Modbus definitions
1731    match fc {
1732        1..=4 => {
1733            let _ = candidates.push(8);
1734            add_dyn(&mut candidates, 2, 5);
1735        }
1736        5 | 6 | 8 => {
1737            let _ = candidates.push(8);
1738        }
1739        7 => {
1740            let _ = candidates.push(4);
1741            let _ = candidates.push(5);
1742        }
1743        11 => {
1744            let _ = candidates.push(4);
1745            let _ = candidates.push(8);
1746        }
1747        12 | 17 => {
1748            let _ = candidates.push(4);
1749            add_dyn(&mut candidates, 2, 5);
1750        }
1751        15 | 16 => {
1752            let _ = candidates.push(8);
1753            add_dyn(&mut candidates, 6, 9);
1754        }
1755        20 | 21 => add_dyn(&mut candidates, 2, 5),
1756        22 => {
1757            let _ = candidates.push(10);
1758        }
1759        23 => {
1760            add_dyn(&mut candidates, 2, 5);
1761            add_dyn(&mut candidates, 10, 13);
1762        }
1763        24 => {
1764            let _ = candidates.push(6);
1765            if frame.len() >= 4 {
1766                let byte_count = u16::from_be_bytes([frame[2], frame[3]]) as usize;
1767                let _ = candidates.push(6 + byte_count);
1768            } else {
1769                min_needed = core::cmp::min(min_needed, 4);
1770            }
1771        }
1772        43 => {
1773            if check_crc(7) {
1774                return Some(7);
1775            }
1776            // Response is unpredictable. Scan opportunistically forwards to support pipelined frames.
1777            for len in MIN_RTU_ADU_LEN..=frame.len() {
1778                if check_crc(len) {
1779                    return Some(len);
1780                }
1781            }
1782            return None;
1783        }
1784        _ => {
1785            for len in MIN_RTU_ADU_LEN..=frame.len() {
1786                if check_crc(len) {
1787                    return Some(len);
1788                }
1789            }
1790            return None;
1791        }
1792    }
1793
1794    // 1. Opportunistic CRC checks to lock in an exact frame boundary
1795    for &len in &candidates {
1796        if check_crc(len) {
1797            return Some(len);
1798        }
1799    }
1800
1801    // 2. If no CRC matched yet, determine the max length we might need to wait for
1802    let max_candidate = candidates.iter().copied().max().unwrap_or(0);
1803    let target = if min_needed != usize::MAX {
1804        core::cmp::max(min_needed, max_candidate)
1805    } else {
1806        max_candidate
1807    };
1808
1809    if target > 0 { Some(target) } else { None }
1810}
1811
1812/// Helper function to convert a 4-bit nibble to its ASCII hex representation.
1813fn nibble_to_hex(nibble: u8) -> u8 {
1814    match nibble {
1815        0..=9 => b'0' + nibble,
1816        10..=15 => b'A' + (nibble - 10),
1817        _ => b'?', // Should not happen for a valid nibble
1818    }
1819}
1820
1821/// Helper function to convert a hex character to its 4-bit nibble value.
1822fn hex_char_to_nibble(c: u8) -> Result<u8, MbusError> {
1823    match c {
1824        b'0'..=b'9' => Ok(c - b'0'),
1825        b'A'..=b'F' => Ok(c - b'A' + 10),
1826        b'a'..=b'f' => Ok(c - b'a' + 10),
1827        _ => Err(MbusError::BasicParseError),
1828    }
1829}
1830
1831/// Helper function to convert two hex characters to a byte.
1832fn hex_pair_to_byte(high: u8, low: u8) -> Result<u8, MbusError> {
1833    let h = hex_char_to_nibble(high)?;
1834    let l = hex_char_to_nibble(low)?;
1835    Ok((h << 4) | l)
1836}
1837
1838#[cfg(test)]
1839mod tests {
1840    use super::*;
1841    use crate::function_codes::public::FunctionCode;
1842    use heapless::Vec;
1843
1844    // --- Tests for Pdu::from_bytes ---
1845
1846    /// Test case: `Pdu::from_bytes` with a PDU that has no data bytes (only FC).
1847    ///
1848    /// Function-code-only PDUs are valid for zero-argument operations such as
1849    /// `ReportServerId` (FC11).
1850    #[test]
1851    fn test_pdu_from_bytes_valid_no_data() {
1852        let bytes = [0x11];
1853        let pdu = Pdu::from_bytes(&bytes).expect("Should parse a function-code-only PDU");
1854
1855        assert_eq!(pdu.function_code, FunctionCode::ReportServerId);
1856        assert_eq!(pdu.data_len, 0);
1857        assert!(pdu.data.is_empty());
1858        assert_eq!(pdu.error_code, None);
1859    }
1860
1861    /// Test case: `Pdu::from_bytes` with a valid `Read Coils` request PDU.
1862    ///
1863    /// This tests parsing a function code followed by address and quantity bytes.
1864    ///
1865    /// Modbus Specification Reference: V1.1b3, Section 6.1 (Read Coils).
1866    #[test]
1867    fn test_pdu_from_bytes_valid_read_coils_request() {
1868        // Read Coils (0x01) request: FC (1 byte) + Starting Address (2 bytes) + Quantity of Coils (2 bytes)
1869        // Example: Read 10 coils starting at address 0x0000
1870        let bytes = [0x01, 0x00, 0x00, 0x00, 0x0A]; // FC, Addr_Hi, Addr_Lo, Qty_Hi, Qty_Lo
1871        let pdu = Pdu::from_bytes(&bytes).expect("Should successfully parse Read Coils request");
1872
1873        assert_eq!(pdu.function_code, FunctionCode::ReadCoils);
1874        assert_eq!(pdu.data_len, 4);
1875        assert_eq!(pdu.data.as_slice(), &[0x00, 0x00, 0x00, 0x0A]);
1876    }
1877
1878    /// Test case: `Pdu::from_bytes` with a valid `Read Holding Registers` response PDU.
1879    ///
1880    /// This tests parsing a function code, a byte count, and then the actual data bytes.
1881    ///
1882    /// Modbus Specification Reference: V1.1b3, Section 6.3 (Read Holding Registers).
1883    #[test]
1884    fn test_pdu_from_bytes_valid_read_holding_registers_response() {
1885        // Read Holding Registers (0x03) response: FC (1 byte) + Byte Count (1 byte) + Data (N bytes)
1886        // Example: Response with 2 registers (4 bytes of data)
1887        let bytes = [0x03, 0x04, 0x12, 0x34, 0x56, 0x78]; // FC, Byte Count, Reg1_Hi, Reg1_Lo, Reg2_Hi, Reg2_Lo
1888        let pdu = Pdu::from_bytes(&bytes)
1889            .expect("Should successfully parse Read Holding Registers response");
1890
1891        assert_eq!(pdu.function_code, FunctionCode::ReadHoldingRegisters);
1892        assert_eq!(pdu.data_len, 5); // Byte Count (0x04) + 4 data bytes
1893        assert_eq!(pdu.data.as_slice(), &[0x04, 0x12, 0x34, 0x56, 0x78]);
1894    }
1895
1896    /// Test case: `Pdu::from_bytes` with a PDU containing the maximum allowed data length.
1897    ///
1898    /// The maximum PDU data length is 252 bytes (total PDU size 253 bytes including FC).
1899    ///
1900    /// Modbus Specification Reference: V1.1b3, Section 4.1 (PDU Size).
1901    #[test]
1902    fn test_pdu_from_bytes_valid_max_data_length() {
1903        // Max PDU data length is 252 bytes.
1904        let mut bytes_vec: Vec<u8, 253> = Vec::new();
1905        let _ = bytes_vec.push(0x03); // Dummy FC (Read Holding Registers)
1906        for i in 0..252 {
1907            let _ = bytes_vec.push(i as u8);
1908        }
1909        let bytes = bytes_vec.as_slice();
1910        let pdu = Pdu::from_bytes(bytes).expect("Should parse valid PDU with max data");
1911
1912        assert_eq!(pdu.function_code, FunctionCode::ReadHoldingRegisters);
1913        assert_eq!(pdu.data_len, 252);
1914        assert_eq!(pdu.data.as_slice(), &bytes[1..]);
1915    }
1916
1917    /// Test case: `Pdu::from_bytes` with an empty byte slice.
1918    ///
1919    /// An empty slice is an invalid PDU as it lacks even a function code.
1920    #[test]
1921    fn test_pdu_from_bytes_empty_slice_error() {
1922        let bytes = [];
1923        let err = Pdu::from_bytes(&bytes).expect_err("Should return error for empty slice");
1924        assert_eq!(err, MbusError::InvalidPduLength);
1925    }
1926
1927    /// Test case: `Pdu::from_bytes` with an invalid or unsupported function code.
1928    ///
1929    /// This checks for `MbusError::UnsupportedFunction` when the function code is not recognized.
1930    /// Modbus Specification Reference: V1.1b3, Section 5.1 (Public Function Code Definition).
1931    #[test]
1932    fn test_pdu_from_bytes_invalid_function_code_error() {
1933        // 0x00 is not a valid public function code
1934        let bytes = [0x00, 0x01, 0x02];
1935        let err = Pdu::from_bytes(&bytes).expect_err("Should return error for invalid FC 0x00");
1936        assert_eq!(err, MbusError::UnsupportedFunction(0x00));
1937
1938        // 0xFF is also not a valid public function code
1939        let bytes = [0xFF, 0x01, 0x02];
1940        let err = Pdu::from_bytes(&bytes).expect_err("Should return error for invalid FC 0xFF");
1941        assert_eq!(err, MbusError::UnsupportedFunction(0x7F));
1942    }
1943
1944    /// Test case: `Pdu::from_bytes` with a data payload exceeding the maximum allowed length.
1945    ///
1946    /// The maximum PDU data length is 252 bytes. This test provides 253 data bytes.
1947    ///
1948    /// Modbus Specification Reference: V1.1b3, Section 4.1 (PDU Size).
1949    #[test]
1950    fn test_pdu_from_bytes_data_too_long_error() {
1951        // PDU data length > 252 bytes (total PDU length > 253 bytes)
1952        let mut bytes_vec: Vec<u8, 254> = Vec::new();
1953        let _ = bytes_vec.push(0x03); // Dummy FC
1954        for i in 0..253 {
1955            // 253 data bytes, which is too many
1956            let _ = bytes_vec.push(i as u8);
1957        }
1958        let bytes = bytes_vec.as_slice();
1959        let err = Pdu::from_bytes(bytes).expect_err("Should return error for too much data");
1960        assert_eq!(err, MbusError::InvalidPduLength);
1961    }
1962
1963    #[test]
1964    fn test_pdu_read_window_parses_expected_fields() {
1965        let pdu = Pdu::from_bytes(&[0x03, 0x12, 0x34, 0x00, 0x02]).expect("valid pdu");
1966        let parsed = pdu.read_window().expect("read window should parse");
1967
1968        assert_eq!(
1969            parsed,
1970            ReadWindow {
1971                address: 0x1234,
1972                quantity: 0x0002
1973            }
1974        );
1975    }
1976
1977    #[test]
1978    fn test_pdu_write_single_u16_fields_parses_expected_fields() {
1979        let pdu = Pdu::from_bytes(&[0x06, 0x01, 0x02, 0xAB, 0xCD]).expect("valid pdu");
1980        let parsed = pdu
1981            .write_single_u16_fields()
1982            .expect("write single fields should parse");
1983
1984        assert_eq!(
1985            parsed,
1986            WriteSingleU16Fields {
1987                address: 0x0102,
1988                value: 0xABCD
1989            }
1990        );
1991    }
1992
1993    #[test]
1994    fn test_pdu_write_multiple_fields_parses_expected_fields() {
1995        let pdu = Pdu::from_bytes(&[0x10, 0x00, 0x20, 0x00, 0x02, 0x04, 0x12, 0x34, 0x56, 0x78])
1996            .expect("valid pdu");
1997        let parsed = pdu
1998            .write_multiple_fields()
1999            .expect("write multiple fields should parse");
2000
2001        assert_eq!(parsed.address, 0x0020);
2002        assert_eq!(parsed.quantity, 0x0002);
2003        assert_eq!(parsed.byte_count, 0x04);
2004        assert_eq!(parsed.values, &[0x12, 0x34, 0x56, 0x78]);
2005    }
2006
2007    #[test]
2008    fn test_pdu_mask_write_register_fields_parses_expected_fields() {
2009        let pdu = Pdu::from_bytes(&[0x16, 0x00, 0x10, 0xFF, 0x00, 0x00, 0x0F]).expect("valid pdu");
2010        let parsed = pdu
2011            .mask_write_register_fields()
2012            .expect("mask write fields should parse");
2013
2014        assert_eq!(
2015            parsed,
2016            MaskWriteRegisterFields {
2017                address: 0x0010,
2018                and_mask: 0xFF00,
2019                or_mask: 0x000F
2020            }
2021        );
2022    }
2023
2024    #[test]
2025    fn test_pdu_byte_count_payload_parses_expected_fields() {
2026        let pdu = Pdu::from_bytes(&[0x03, 0x04, 0x12, 0x34, 0x56, 0x78]).expect("valid pdu");
2027        let parsed = pdu
2028            .byte_count_payload()
2029            .expect("byte-count payload should parse");
2030
2031        assert_eq!(parsed.byte_count, 0x04);
2032        assert_eq!(parsed.payload, &[0x12, 0x34, 0x56, 0x78]);
2033    }
2034
2035    #[test]
2036    fn test_pdu_write_multiple_fields_rejects_mismatched_byte_count() {
2037        let pdu =
2038            Pdu::from_bytes(&[0x10, 0x00, 0x20, 0x00, 0x02, 0x03, 0x12, 0x34]).expect("valid pdu");
2039        let err = pdu
2040            .write_multiple_fields()
2041            .expect_err("byte count mismatch should error");
2042
2043        assert_eq!(err, MbusError::InvalidByteCount);
2044    }
2045
2046    #[test]
2047    fn test_pdu_byte_count_payload_rejects_mismatched_byte_count() {
2048        let pdu = Pdu::from_bytes(&[0x03, 0x03, 0x12, 0x34]).expect("valid pdu");
2049        let err = pdu
2050            .byte_count_payload()
2051            .expect_err("byte count mismatch should error");
2052
2053        assert_eq!(err, MbusError::InvalidByteCount);
2054    }
2055
2056    #[test]
2057    fn test_pdu_read_write_multiple_fields_parses_expected_fields() {
2058        let pdu = Pdu::from_bytes(&[
2059            0x17, 0x00, 0x10, 0x00, 0x02, 0x00, 0x20, 0x00, 0x02, 0x04, 0x12, 0x34, 0x56, 0x78,
2060        ])
2061        .expect("valid pdu");
2062        let parsed = pdu
2063            .read_write_multiple_fields()
2064            .expect("read/write multiple fields should parse");
2065
2066        assert_eq!(parsed.read_address, 0x0010);
2067        assert_eq!(parsed.read_quantity, 0x0002);
2068        assert_eq!(parsed.write_address, 0x0020);
2069        assert_eq!(parsed.write_quantity, 0x0002);
2070        assert_eq!(parsed.write_byte_count, 0x04);
2071        assert_eq!(parsed.write_values, &[0x12, 0x34, 0x56, 0x78]);
2072    }
2073
2074    #[test]
2075    fn test_pdu_read_write_multiple_fields_rejects_short_pdu() {
2076        let pdu = Pdu::from_bytes(&[0x17, 0x00, 0x10, 0x00, 0x02, 0x00]).expect("valid pdu");
2077        let err = pdu
2078            .read_write_multiple_fields()
2079            .expect_err("PDU too short should error");
2080
2081        assert_eq!(err, MbusError::InvalidPduLength);
2082    }
2083
2084    #[test]
2085    fn test_pdu_read_write_multiple_fields_rejects_mismatched_byte_count() {
2086        let pdu = Pdu::from_bytes(&[
2087            0x17, 0x00, 0x10, 0x00, 0x02, 0x00, 0x20, 0x00, 0x02, 0x05, 0x12, 0x34, 0x56, 0x78,
2088        ])
2089        .expect("valid pdu");
2090        let err = pdu
2091            .read_write_multiple_fields()
2092            .expect_err("byte count mismatch should error");
2093
2094        assert_eq!(err, MbusError::InvalidByteCount);
2095    }
2096
2097    // --- Tests for Pdu::to_bytes ---
2098
2099    /// Test case: `Pdu::to_bytes` with a PDU that has no data bytes.
2100    ///
2101    /// This covers function codes like `ReportServerId` (0x11) which consist only of the function code.
2102    ///
2103    /// Modbus Specification Reference: V1.1b3, Section 6.13 (Report Server ID).
2104    #[test]
2105    fn test_pdu_to_bytes_no_data() {
2106        let pdu = Pdu::new(FunctionCode::ReportServerId, Vec::new(), 0);
2107        let bytes = pdu.to_bytes().expect("Should convert PDU to bytes");
2108        assert_eq!(bytes.as_slice(), &[0x11]);
2109    }
2110
2111    /// Test case: `Pdu::to_bytes` with a PDU containing a typical data payload.
2112    ///
2113    /// Example: `Read Coils` request with address and quantity.
2114    ///
2115    /// Modbus Specification Reference: V1.1b3, Section 6.1 (Read Coils).
2116    #[test]
2117    fn test_pdu_to_bytes_with_data() {
2118        let mut data_vec = Vec::new();
2119        data_vec
2120            .extend_from_slice(&[0x00, 0x00, 0x00, 0x0A])
2121            .unwrap(); // Read 10 coils
2122
2123        let pdu = Pdu::new(FunctionCode::ReadCoils, data_vec, 4);
2124        let bytes = pdu.to_bytes().expect("Should convert PDU to bytes");
2125        assert_eq!(bytes.as_slice(), &[0x01, 0x00, 0x00, 0x00, 0x0A]);
2126    }
2127
2128    /// Test case: `Pdu::to_bytes` with a PDU containing the maximum allowed data length.
2129    ///
2130    /// The maximum PDU data length is 252 bytes.
2131    ///
2132    /// Modbus Specification Reference: V1.1b3, Section 4.1 (PDU Size).
2133    #[test]
2134    fn test_pdu_to_bytes_max_data() {
2135        let mut data_vec = Vec::new();
2136        for i in 0..252 {
2137            data_vec.push(i as u8).unwrap();
2138        }
2139
2140        let pdu = Pdu::new(FunctionCode::ReadHoldingRegisters, data_vec, 252);
2141        let bytes = pdu
2142            .to_bytes()
2143            .expect("Should convert PDU to bytes with max data");
2144        let mut expected_bytes_vec: Vec<u8, 253> = Vec::new();
2145        let _ = expected_bytes_vec.push(0x03);
2146        for i in 0..252 {
2147            let _ = expected_bytes_vec.push(i as u8);
2148        }
2149        assert_eq!(bytes.as_slice(), expected_bytes_vec.as_slice());
2150    }
2151
2152    /// Test case: `ModbusMessage::to_bytes` for a Modbus TCP message.
2153    ///
2154    /// Verifies that a `ModbusMessage` with an `MbapHeader` and `Pdu` is correctly
2155    /// serialized into its ADU byte representation.
2156    #[test]
2157    fn test_modbus_message_to_bytes_tcp() {
2158        let mbap_header = MbapHeader {
2159            transaction_id: 0x1234,
2160            protocol_id: 0x0000,
2161            length: 0x0005, // Length of PDU (FC + Data) + Unit ID
2162            unit_id: 0x01,
2163        };
2164
2165        let mut pdu_data_vec: Vec<u8, MAX_PDU_DATA_LEN> = Vec::new();
2166        pdu_data_vec.extend_from_slice(&[0x00, 0x00, 0x00]).unwrap();
2167
2168        let pdu = Pdu::new(
2169            FunctionCode::ReadHoldingRegisters,
2170            pdu_data_vec,
2171            3, // 3 data bytes
2172        );
2173
2174        let modbus_message = ModbusMessage::new(AdditionalAddress::MbapHeader(mbap_header), pdu);
2175        let adu_bytes = modbus_message
2176            .to_bytes()
2177            .expect("Failed to serialize ModbusMessage");
2178
2179        #[rustfmt::skip]
2180        let expected_adu: [u8; 11] = [
2181            0x12, 0x34, // Transaction ID
2182            0x00, 0x00, // Protocol ID
2183            0x00, 0x05, // Length (PDU length (FC + Data) + 1 byte Unit ID = (1 + 3) + 1 = 5)
2184            0x01,       // Unit ID
2185            0x03,       // Function Code (Read Holding Registers)
2186            0x00, 0x00, 0x00, // Data
2187        ];
2188
2189        assert_eq!(adu_bytes.as_slice(), &expected_adu);
2190    }
2191
2192    // --- Tests for FunctionCode::try_from ---
2193    /// Test case: `FunctionCode::try_from` with valid `u8` values.
2194    ///
2195    /// Verifies that known public function codes are correctly converted to their `FunctionCode` enum variants.
2196    /// Modbus Specification Reference: V1.1b3, Section 5.1 (Public Function Code Definition).
2197    #[test]
2198    fn test_function_code_try_from_valid() {
2199        assert_eq!(
2200            FunctionCode::try_from(0x01).unwrap(),
2201            FunctionCode::ReadCoils
2202        );
2203        assert_eq!(
2204            FunctionCode::try_from(0x08).unwrap(),
2205            FunctionCode::Diagnostics
2206        );
2207        assert_eq!(
2208            FunctionCode::try_from(0x2B).unwrap(),
2209            FunctionCode::EncapsulatedInterfaceTransport
2210        );
2211        assert_eq!(
2212            FunctionCode::try_from(0x18).unwrap(),
2213            FunctionCode::ReadFifoQueue
2214        );
2215        assert_eq!(
2216            FunctionCode::try_from(0x11).unwrap(),
2217            FunctionCode::ReportServerId
2218        );
2219    }
2220
2221    /// Test case: `FunctionCode::try_from` with invalid or reserved `u8` values.
2222    ///
2223    /// Verifies that `MbusError::UnsupportedFunction` is returned for unknown or reserved function code bytes.
2224    /// Modbus Specification Reference: V1.1b3, Section 5.1 (Public Function Code Definition).
2225    #[test]
2226    fn test_function_code_try_from_invalid() {
2227        let err = FunctionCode::try_from(0x00).expect_err("Should error for invalid FC 0x00");
2228        assert_eq!(err, MbusError::UnsupportedFunction(0x00));
2229
2230        let err =
2231            FunctionCode::try_from(0x09).expect_err("Should error for invalid FC 0x09 (reserved)");
2232        assert_eq!(err, MbusError::UnsupportedFunction(0x09));
2233
2234        let err = FunctionCode::try_from(0x64)
2235            .expect_err("Should error for invalid FC 0x64 (private range, not public)");
2236        assert_eq!(err, MbusError::UnsupportedFunction(0x64));
2237    }
2238
2239    // --- Round-trip tests (from_bytes -> to_bytes) ---
2240
2241    /// Test case: Round-trip serialization/deserialization for a PDU with data.
2242    ///
2243    /// Converts bytes to PDU and back to bytes, asserting equality with the original.
2244    /// Modbus Specification Reference: General Modbus PDU structure.
2245    #[test]
2246    fn test_pdu_round_trip_with_data() {
2247        let original_bytes = [0x01, 0x00, 0x00, 0x00, 0x0A]; // Read Coils
2248        let pdu = Pdu::from_bytes(&original_bytes).expect("from_bytes failed");
2249        let new_bytes = pdu.to_bytes().expect("to_bytes failed");
2250        assert_eq!(original_bytes.as_slice(), new_bytes.as_slice());
2251    }
2252
2253    /// Test case: Round-trip serialization/deserialization for a PDU with maximum data length.
2254    ///
2255    /// Converts bytes to PDU and back to bytes, asserting equality with the original.
2256    /// Modbus Specification Reference: V1.1b3, Section 4.1 (PDU Size).
2257    #[test]
2258    fn test_pdu_round_trip_max_data() {
2259        let mut original_bytes_vec: Vec<u8, 253> = Vec::new();
2260        let _ = original_bytes_vec.push(0x03); // Read Holding Registers
2261        for i in 0..252 {
2262            let _ = original_bytes_vec.push(i as u8);
2263        }
2264        let original_bytes = original_bytes_vec.as_slice();
2265
2266        let pdu = Pdu::from_bytes(original_bytes).expect("from_bytes failed");
2267        let new_bytes = pdu.to_bytes().expect("to_bytes failed");
2268        assert_eq!(original_bytes, new_bytes.as_slice());
2269    }
2270
2271    // --- Tests for ModbusMessage::to_ascii_bytes ---
2272
2273    /// Test case: `ModbusMessage::to_ascii_bytes` with a valid message.
2274    ///
2275    /// Verifies correct ASCII encoding and LRC calculation.
2276    /// Request: Slave 1, Read Coils (FC 01), Start 0, Qty 10.
2277    /// Binary: 01 01 00 00 00 0A
2278    /// LRC: -(01+01+0A) = -12 = F4
2279    /// ASCII: :01010000000AF4\r\n
2280    #[test]
2281    fn test_modbus_message_to_ascii_bytes_valid() {
2282        let slave_addr = SlaveAddress::new(1).unwrap();
2283        let mut data = Vec::new();
2284        data.extend_from_slice(&[0x00, 0x00, 0x00, 0x0A]).unwrap();
2285        let pdu = Pdu::new(FunctionCode::ReadCoils, data, 4);
2286        let message = ModbusMessage::new(AdditionalAddress::SlaveAddress(slave_addr), pdu);
2287
2288        let ascii_bytes = message
2289            .to_ascii_bytes()
2290            .expect("Failed to convert to ASCII");
2291
2292        let expected = b":01010000000AF4\r\n";
2293        assert_eq!(ascii_bytes.as_slice(), expected);
2294    }
2295
2296    /// Test case: `ModbusMessage::to_ascii_bytes` boundary check.
2297    ///
2298    /// Case 1: Data len 125. Total ASCII = 1 + (1+1+125+1)*2 + 2 = 1 + 256 + 2 = 259.
2299    /// This fits comfortably within MAX_ADU_FRAME_LEN (513).
2300    #[test]
2301    fn test_modbus_message_to_ascii_bytes_max_capacity() {
2302        let slave_addr = SlaveAddress::new(1).unwrap();
2303        let mut data = Vec::new();
2304        for _ in 0..125 {
2305            data.push(0xAA).unwrap();
2306        }
2307        let pdu = Pdu::new(FunctionCode::ReadHoldingRegisters, data, 125);
2308        let message = ModbusMessage::new(AdditionalAddress::SlaveAddress(slave_addr), pdu);
2309
2310        let ascii_bytes = message.to_ascii_bytes().expect("Should fit in buffer");
2311        assert_eq!(ascii_bytes.len(), 259);
2312    }
2313
2314    /// Test case: `ModbusMessage::to_ascii_bytes` with large payload.
2315    ///
2316    /// Case 2: Data len 126. Total ASCII = 1 + (1+1+126+1)*2 + 2 = 1 + 258 + 2 = 261.
2317    /// This should NOT fail with MAX_ADU_FRAME_LEN = 513.
2318    #[test]
2319    fn test_modbus_message_to_ascii_bytes_large_payload() {
2320        let slave_addr = SlaveAddress::new(1).unwrap();
2321        let mut data = Vec::new();
2322        for _ in 0..126 {
2323            data.push(0xAA).unwrap();
2324        }
2325        let pdu = Pdu::new(FunctionCode::ReadHoldingRegisters, data, 126);
2326        let message = ModbusMessage::new(AdditionalAddress::SlaveAddress(slave_addr), pdu);
2327
2328        let ascii_bytes = message.to_ascii_bytes().expect("Should fit in buffer");
2329        assert_eq!(ascii_bytes.len(), 261);
2330    }
2331
2332    // --- Tests for decompile_adu_frame ---
2333
2334    #[test]
2335    fn test_decompile_adu_frame_tcp_valid() {
2336        let frame = [
2337            0x12, 0x34, // TID
2338            0x00, 0x00, // PID
2339            0x00, 0x06, // Length
2340            0x01, // Unit ID
2341            0x03, // FC
2342            0x00, 0x01, 0x00, 0x02, // Data
2343        ];
2344        let msg = decompile_adu_frame(&frame, TransportType::StdTcp)
2345            .expect("Should decode valid TCP frame");
2346        assert_eq!(msg.function_code(), FunctionCode::ReadHoldingRegisters);
2347        if let AdditionalAddress::MbapHeader(header) = msg.additional_address {
2348            assert_eq!(header.transaction_id, 0x1234);
2349        } else {
2350            panic!("Expected MbapHeader");
2351        }
2352    }
2353
2354    #[test]
2355    fn test_decompile_adu_frame_tcp_invalid() {
2356        let frame = [0x00]; // Too short
2357        let err = decompile_adu_frame(&frame, TransportType::StdTcp).expect_err("Should fail");
2358        assert_eq!(err, MbusError::InvalidAduLength);
2359    }
2360
2361    #[test]
2362    fn test_decompile_adu_frame_rtu_valid() {
2363        // Frame: 01 03 00 6B 00 03 74 17 (CRC LE)
2364        let frame = [0x01, 0x03, 0x00, 0x6B, 0x00, 0x03, 0x74, 0x17];
2365        let msg = decompile_adu_frame(&frame, TransportType::StdSerial(SerialMode::Rtu))
2366            .expect("Valid RTU");
2367        assert_eq!(msg.function_code(), FunctionCode::ReadHoldingRegisters);
2368    }
2369
2370    #[test]
2371    fn test_decompile_adu_frame_rtu_too_short() {
2372        let frame = [0x01, 0x02, 0x03];
2373        let err = decompile_adu_frame(&frame, TransportType::StdSerial(SerialMode::Rtu))
2374            .expect_err("Too short");
2375        assert_eq!(err, MbusError::InvalidAduLength);
2376    }
2377
2378    #[test]
2379    fn test_decompile_adu_frame_rtu_crc_mismatch() {
2380        let frame = [0x01, 0x03, 0x00, 0x6B, 0x00, 0x03, 0x00, 0x00]; // Bad CRC
2381        let err = decompile_adu_frame(&frame, TransportType::StdSerial(SerialMode::Rtu))
2382            .expect_err("CRC mismatch");
2383        assert_eq!(err, MbusError::ChecksumError);
2384    }
2385
2386    #[test]
2387    fn test_decompile_adu_frame_ascii_valid() {
2388        // :010300000001FB\r\n
2389        let frame = b":010300000001FB\r\n";
2390        let msg = decompile_adu_frame(frame, TransportType::StdSerial(SerialMode::Ascii))
2391            .expect("Valid ASCII");
2392        assert_eq!(msg.function_code(), FunctionCode::ReadHoldingRegisters);
2393    }
2394
2395    #[test]
2396    fn test_decompile_adu_frame_ascii_too_short() {
2397        let frame = b":123\r\n";
2398        let err = decompile_adu_frame(frame, TransportType::StdSerial(SerialMode::Ascii))
2399            .expect_err("Too short");
2400        assert_eq!(err, MbusError::InvalidAduLength);
2401    }
2402
2403    #[test]
2404    fn test_decompile_adu_frame_ascii_missing_start() {
2405        let frame = b"010300000001FB\r\n";
2406        let err = decompile_adu_frame(frame, TransportType::StdSerial(SerialMode::Ascii))
2407            .expect_err("Missing start");
2408        assert_eq!(err, MbusError::BasicParseError);
2409    }
2410
2411    #[test]
2412    fn test_decompile_adu_frame_ascii_missing_end() {
2413        let frame = b":010300000001FB\r"; // Missing \n
2414        let err = decompile_adu_frame(frame, TransportType::StdSerial(SerialMode::Ascii))
2415            .expect_err("Missing end");
2416        assert_eq!(err, MbusError::BasicParseError);
2417    }
2418
2419    #[test]
2420    fn test_decompile_adu_frame_ascii_odd_hex() {
2421        let frame = b":010300000001F\r\n"; // Odd length hex
2422        let err = decompile_adu_frame(frame, TransportType::StdSerial(SerialMode::Ascii))
2423            .expect_err("Odd hex");
2424        assert_eq!(err, MbusError::BasicParseError);
2425    }
2426
2427    #[test]
2428    fn test_decompile_adu_frame_ascii_lrc_mismatch() {
2429        let frame = b":01030000000100\r\n"; // LRC 00 is wrong, should be FB
2430        let err = decompile_adu_frame(frame, TransportType::StdSerial(SerialMode::Ascii))
2431            .expect_err("LRC mismatch");
2432        assert_eq!(err, MbusError::ChecksumError);
2433    }
2434
2435    #[test]
2436    fn test_decompile_adu_frame_ascii_buffer_overflow() {
2437        // Construct a frame that decodes to 261 bytes.
2438        let mut frame = Vec::<u8, 600>::new();
2439        frame.push(b':').unwrap();
2440        for _ in 0..261 {
2441            frame.extend_from_slice(b"00").unwrap();
2442        }
2443        frame.extend_from_slice(b"\r\n").unwrap();
2444        let err = decompile_adu_frame(&frame, TransportType::StdSerial(SerialMode::Ascii))
2445            .expect_err("Buffer overflow");
2446        assert_eq!(err, MbusError::BufferTooSmall);
2447    }
2448
2449    // --- Tests for derive_length_from_bytes ---
2450
2451    #[test]
2452    fn test_derive_length_tcp() {
2453        // TCP frame requires minimum 6 bytes to read the length offset.
2454        let short_frame = [0x00, 0x01, 0x00, 0x00, 0x00];
2455        assert_eq!(
2456            derive_length_from_bytes(&short_frame, TransportType::StdTcp),
2457            None
2458        );
2459
2460        // TCP MBAP: TID(2) PID(2) LEN(2) = 0x0006. Total length should be 6 + 6 = 12.
2461        let full_frame = [
2462            0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, 0x01,
2463        ];
2464        assert_eq!(
2465            derive_length_from_bytes(&full_frame, TransportType::StdTcp),
2466            Some(12)
2467        );
2468
2469        // TCP MBAP with invalid Protocol ID should return usize::MAX to trigger garbage disposal.
2470        let garbage_frame = [
2471            0x00, 0x01, 0xAA, 0xBB, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, 0x01,
2472        ];
2473        assert_eq!(
2474            derive_length_from_bytes(&garbage_frame, TransportType::StdTcp),
2475            Some(usize::MAX)
2476        );
2477    }
2478
2479    #[test]
2480    fn test_derive_length_rtu_fixed() {
2481        // FC 5 (Write Single Coil) is uniformly 8 bytes for requests and responses.
2482        let request = [0x01, 0x05, 0x00, 0x0A, 0xFF, 0x00, 0x00, 0x00];
2483        assert_eq!(
2484            derive_length_from_bytes(&request, TransportType::StdSerial(SerialMode::Rtu)),
2485            Some(8)
2486        );
2487    }
2488
2489    #[test]
2490    fn test_derive_length_rtu_dynamic() {
2491        // Read Holding Registers Response (FC 03)
2492        // Schema: Address(1) + FC(03) + ByteCount(2) + Data(0x12, 0x34) + CRC(2)
2493        let mut resp = [0x01, 0x03, 0x02, 0x12, 0x34, 0x00, 0x00];
2494        let crc = checksum::crc16(&resp[..5]);
2495        let crc_bytes = crc.to_le_bytes();
2496        resp[5] = crc_bytes[0];
2497        resp[6] = crc_bytes[1];
2498
2499        // Opportunistic CRC should instantly lock on an exact length of 7.
2500        assert_eq!(
2501            derive_length_from_bytes(&resp, TransportType::StdSerial(SerialMode::Rtu)),
2502            Some(7)
2503        );
2504
2505        // A partial frame (4 bytes) should predict up to 8 (max candidate comparison logic).
2506        assert_eq!(
2507            derive_length_from_bytes(&resp[..4], TransportType::StdSerial(SerialMode::Rtu)),
2508            Some(8)
2509        );
2510    }
2511
2512    #[test]
2513    fn test_derive_length_rtu_exception() {
2514        // Exception responses are universally 5 bytes.
2515        let exception = [0x01, 0x81, 0x02, 0x00, 0x00];
2516        assert_eq!(
2517            derive_length_from_bytes(&exception, TransportType::StdSerial(SerialMode::Rtu)),
2518            Some(5)
2519        );
2520    }
2521
2522    #[test]
2523    fn test_derive_length_rtu_forward_scan() {
2524        // Build a frame with unknown/custom Function Code (e.g. 0x44)
2525        let mut custom_frame = [0x01, 0x44, 0xAA, 0xBB, 0x00, 0x00];
2526        let crc = checksum::crc16(&custom_frame[..4]);
2527        let crc_bytes = crc.to_le_bytes();
2528        custom_frame[4] = crc_bytes[0];
2529        custom_frame[5] = crc_bytes[1];
2530
2531        assert_eq!(
2532            derive_length_from_bytes(&custom_frame, TransportType::StdSerial(SerialMode::Rtu)),
2533            Some(6)
2534        );
2535
2536        // Without the valid CRC, it will continuously scan forward but yield None if unmatched.
2537        assert_eq!(
2538            derive_length_from_bytes(
2539                &custom_frame[..4],
2540                TransportType::StdSerial(SerialMode::Rtu)
2541            ),
2542            None
2543        );
2544    }
2545
2546    #[test]
2547    fn test_derive_length_ascii() {
2548        let frame = b":010300000001FB\r\n";
2549        assert_eq!(
2550            derive_length_from_bytes(frame, TransportType::StdSerial(SerialMode::Ascii)),
2551            Some(17)
2552        );
2553
2554        let partial = b":010300000001F";
2555        assert_eq!(
2556            derive_length_from_bytes(partial, TransportType::StdSerial(SerialMode::Ascii)),
2557            None
2558        );
2559    }
2560}