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    pub fn build_mask_write_register(
871        address: u16,
872        and_mask: u16,
873        or_mask: u16,
874    ) -> Result<Self, MbusError> {
875        let mut data: heapless::Vec<u8, MAX_PDU_DATA_LEN> = heapless::Vec::new();
876        data.extend_from_slice(&address.to_be_bytes())
877            .map_err(|_| MbusError::BufferLenMissmatch)?;
878        data.extend_from_slice(&and_mask.to_be_bytes())
879            .map_err(|_| MbusError::BufferLenMissmatch)?;
880        data.extend_from_slice(&or_mask.to_be_bytes())
881            .map_err(|_| MbusError::BufferLenMissmatch)?;
882        Ok(Pdu::new(FunctionCode::MaskWriteRegister, data, 6))
883    }
884
885    /// Builds a PDU for FC17 (Read/Write Multiple Registers) requests.
886    ///
887    /// `write_values` must already be the packed byte representation of the register words.
888    /// `write_byte_count` is derived from `write_values.len()`.
889    pub fn build_read_write_multiple(
890        read_address: u16,
891        read_quantity: u16,
892        write_address: u16,
893        write_quantity: u16,
894        write_values: &[u8],
895    ) -> Result<Self, MbusError> {
896        let byte_count = write_values.len();
897        if byte_count > u8::MAX as usize {
898            return Err(MbusError::InvalidByteCount);
899        }
900        let data_len = 9 + byte_count;
901        if data_len > MAX_PDU_DATA_LEN {
902            return Err(MbusError::BufferTooSmall);
903        }
904        let mut data: heapless::Vec<u8, MAX_PDU_DATA_LEN> = heapless::Vec::new();
905        data.extend_from_slice(&read_address.to_be_bytes())
906            .map_err(|_| MbusError::BufferLenMissmatch)?;
907        data.extend_from_slice(&read_quantity.to_be_bytes())
908            .map_err(|_| MbusError::BufferLenMissmatch)?;
909        data.extend_from_slice(&write_address.to_be_bytes())
910            .map_err(|_| MbusError::BufferLenMissmatch)?;
911        data.extend_from_slice(&write_quantity.to_be_bytes())
912            .map_err(|_| MbusError::BufferLenMissmatch)?;
913        data.push(byte_count as u8)
914            .map_err(|_| MbusError::BufferLenMissmatch)?;
915        data.extend_from_slice(write_values)
916            .map_err(|_| MbusError::BufferLenMissmatch)?;
917        Ok(Pdu::new(
918            FunctionCode::ReadWriteMultipleRegisters,
919            data,
920            data_len as u8,
921        ))
922    }
923
924    /// Builds a PDU with `[sub_function_hi, sub_function_lo, word0_hi, word0_lo, ...]` layout.
925    ///
926    /// Used by FC08 (Diagnostics) requests.
927    pub fn build_sub_function(
928        fc: FunctionCode,
929        sub_function: u16,
930        words: &[u16],
931    ) -> Result<Self, MbusError> {
932        let data_len = 2 + words.len() * 2;
933        if data_len > MAX_PDU_DATA_LEN {
934            return Err(MbusError::BufferTooSmall);
935        }
936        let mut data: heapless::Vec<u8, MAX_PDU_DATA_LEN> = heapless::Vec::new();
937        data.extend_from_slice(&sub_function.to_be_bytes())
938            .map_err(|_| MbusError::BufferLenMissmatch)?;
939        for &word in words {
940            data.extend_from_slice(&word.to_be_bytes())
941                .map_err(|_| MbusError::BufferLenMissmatch)?;
942        }
943        Ok(Pdu::new(fc, data, data_len as u8))
944    }
945
946    /// Builds a PDU with `[mei_type, payload...]` layout.
947    ///
948    /// Used by FC2B (Encapsulated Interface Transport) requests.
949    pub fn build_mei_type(
950        fc: FunctionCode,
951        mei_type: u8,
952        payload: &[u8],
953    ) -> Result<Self, MbusError> {
954        let data_len = 1 + payload.len();
955        if data_len > MAX_PDU_DATA_LEN {
956            return Err(MbusError::BufferTooSmall);
957        }
958        let mut data: heapless::Vec<u8, MAX_PDU_DATA_LEN> = heapless::Vec::new();
959        data.push(mei_type)
960            .map_err(|_| MbusError::BufferLenMissmatch)?;
961        data.extend_from_slice(payload)
962            .map_err(|_| MbusError::BufferLenMissmatch)?;
963        Ok(Pdu::new(fc, data, data_len as u8))
964    }
965
966    /// Builds a PDU containing a single u16 value `[value_hi, value_lo]` (2 bytes).
967    ///
968    /// Used by FC18 (Read FIFO Queue) request (pointer address).
969    pub fn build_u16_payload(fc: FunctionCode, value: u16) -> Result<Self, MbusError> {
970        let mut data: heapless::Vec<u8, MAX_PDU_DATA_LEN> = heapless::Vec::new();
971        data.extend_from_slice(&value.to_be_bytes())
972            .map_err(|_| MbusError::BufferLenMissmatch)?;
973        Ok(Pdu::new(fc, data, 2))
974    }
975
976    /// Builds a PDU containing a single byte payload `[byte]` (1 byte).
977    ///
978    /// Used for exception response PDUs.
979    pub fn build_byte_payload(fc: FunctionCode, byte: u8) -> Result<Self, MbusError> {
980        let mut data: heapless::Vec<u8, MAX_PDU_DATA_LEN> = heapless::Vec::new();
981        data.push(byte).map_err(|_| MbusError::BufferLenMissmatch)?;
982        Ok(Pdu::new(fc, data, 1))
983    }
984
985    /// Builds a PDU with an empty data payload (0 bytes).
986    ///
987    /// Used by no-argument requests such as FC07, FC0B, FC0C, and FC11.
988    pub fn build_empty(fc: FunctionCode) -> Self {
989        Pdu::new(fc, heapless::Vec::new(), 0)
990    }
991
992    /// Builds a PDU with `[byte_count, payload...]` layout.
993    ///
994    /// `byte_count` is derived from `payload.len()`. Returns `InvalidByteCount`
995    /// if the payload is longer than 255 bytes.
996    ///
997    /// Used by FC01–FC04 read responses, FC11, FC14, FC15, and FC17 responses.
998    pub fn build_byte_count_payload(fc: FunctionCode, payload: &[u8]) -> Result<Self, MbusError> {
999        let byte_count = u8::try_from(payload.len()).map_err(|_| MbusError::InvalidByteCount)?;
1000        let data_len = 1 + payload.len();
1001        if data_len > MAX_PDU_DATA_LEN {
1002            return Err(MbusError::BufferTooSmall);
1003        }
1004        let mut data: heapless::Vec<u8, MAX_PDU_DATA_LEN> = heapless::Vec::new();
1005        data.push(byte_count)
1006            .map_err(|_| MbusError::BufferLenMissmatch)?;
1007        data.extend_from_slice(payload)
1008            .map_err(|_| MbusError::BufferLenMissmatch)?;
1009        Ok(Pdu::new(fc, data, data_len as u8))
1010    }
1011
1012    /// Builds a PDU for FC08 (Diagnostics) responses.
1013    ///
1014    /// Response layout: `[sub_function_hi, sub_function_lo, result_hi, result_lo]` (4 bytes).
1015    /// Echoes the sub-function code with a result word.
1016    #[cfg(feature = "diagnostics")]
1017    pub fn build_diagnostics(sub_function: u16, result: u16) -> Result<Self, MbusError> {
1018        let mut data: heapless::Vec<u8, MAX_PDU_DATA_LEN> = heapless::Vec::new();
1019        data.extend_from_slice(&sub_function.to_be_bytes())
1020            .map_err(|_| MbusError::BufferLenMissmatch)?;
1021        data.extend_from_slice(&result.to_be_bytes())
1022            .map_err(|_| MbusError::BufferLenMissmatch)?;
1023        Ok(Pdu::new(FunctionCode::Diagnostics, data, 4))
1024    }
1025
1026    /// Builds a PDU for FC18 (Read FIFO Queue) responses.
1027    ///
1028    /// Response layout: `[byte_count_hi, byte_count_lo, payload...]` where byte_count is u16.
1029    /// `app_payload` is the application data: `[fifo_count_hi, fifo_count_lo, values...]`.
1030    /// Returns `InvalidByteCount` if payload exceeds 65535 bytes.
1031    #[cfg(feature = "fifo")]
1032    pub fn build_fifo_payload(app_payload: &[u8]) -> Result<Self, MbusError> {
1033        let byte_count =
1034            u16::try_from(app_payload.len()).map_err(|_| MbusError::InvalidByteCount)?;
1035        let data_len = 2 + app_payload.len();
1036        if data_len > MAX_PDU_DATA_LEN {
1037            return Err(MbusError::BufferTooSmall);
1038        }
1039        let mut data: heapless::Vec<u8, MAX_PDU_DATA_LEN> = heapless::Vec::new();
1040        data.extend_from_slice(&byte_count.to_be_bytes())
1041            .map_err(|_| MbusError::BufferLenMissmatch)?;
1042        data.extend_from_slice(app_payload)
1043            .map_err(|_| MbusError::BufferLenMissmatch)?;
1044        Ok(Pdu::new(FunctionCode::ReadFifoQueue, data, data_len as u8))
1045    }
1046
1047    // -------- PDU parsing helpers -----------------------------------------------------
1048
1049    /// Reads the standard address + quantity pair from PDU bytes 0-3.
1050    /// Used by multiple function codes (FC01-04 requests, FC0F/10 requests).
1051    #[inline]
1052    fn read_address_quantity_pair(&self) -> Result<(u16, u16), MbusError> {
1053        Ok((
1054            u16::from_be_bytes([
1055                self.data[PDU_ADDRESS_OFFSET_1B],
1056                self.data[PDU_ADDRESS_OFFSET_2B],
1057            ]),
1058            u16::from_be_bytes([
1059                self.data[PDU_QUANTITY_OFFSET_1B],
1060                self.data[PDU_QUANTITY_OFFSET_2B],
1061            ]),
1062        ))
1063    }
1064
1065    /// Parses a read-window request payload into typed fields.
1066    ///
1067    /// Expected PDU data layout: `[address_hi, address_lo, quantity_hi, quantity_lo]`.
1068    pub fn read_window(&self) -> Result<ReadWindow, MbusError> {
1069        if self.data_len != 4 {
1070            return Err(MbusError::InvalidPduLength);
1071        }
1072
1073        let (address, quantity) = self.read_address_quantity_pair()?;
1074        Ok(ReadWindow { address, quantity })
1075    }
1076
1077    /// Parses FC05/FC06-style payloads: address + value.
1078    pub fn write_single_u16_fields(&self) -> Result<WriteSingleU16Fields, MbusError> {
1079        if self.data_len != 4 {
1080            return Err(MbusError::InvalidPduLength);
1081        }
1082
1083        Ok(WriteSingleU16Fields {
1084            address: u16::from_be_bytes([
1085                self.data[PDU_ADDRESS_OFFSET_1B],
1086                self.data[PDU_ADDRESS_OFFSET_2B],
1087            ]),
1088            value: u16::from_be_bytes([
1089                self.data[PDU_QUANTITY_OFFSET_1B],
1090                self.data[PDU_QUANTITY_OFFSET_2B],
1091            ]),
1092        })
1093    }
1094
1095    /// Parses FC0F/FC10-style payloads: address + quantity + byte_count + values.
1096    pub fn write_multiple_fields(&self) -> Result<WriteMultipleFields<'_>, MbusError> {
1097        if self.data_len < 5 {
1098            return Err(MbusError::InvalidPduLength);
1099        }
1100
1101        let (address, quantity) = self.read_address_quantity_pair()?;
1102        let byte_count = self.data[PDU_BYTE_COUNT_OFFSET];
1103        let expected_len = 5usize
1104            .checked_add(byte_count as usize)
1105            .ok_or(MbusError::InvalidByteCount)?;
1106
1107        if self.data_len as usize != expected_len {
1108            return Err(MbusError::InvalidByteCount);
1109        }
1110
1111        Ok(WriteMultipleFields {
1112            address,
1113            quantity,
1114            byte_count,
1115            values: &self.data[5..expected_len],
1116        })
1117    }
1118
1119    /// Parses FC16 mask-write payload: address + and-mask + or-mask.
1120    pub fn mask_write_register_fields(&self) -> Result<MaskWriteRegisterFields, MbusError> {
1121        if self.data_len != 6 {
1122            return Err(MbusError::InvalidPduLength);
1123        }
1124
1125        Ok(MaskWriteRegisterFields {
1126            address: u16::from_be_bytes([
1127                self.data[PDU_ADDRESS_OFFSET_1B],
1128                self.data[PDU_ADDRESS_OFFSET_2B],
1129            ]),
1130            and_mask: u16::from_be_bytes([
1131                self.data[PDU_AND_MASK_OFFSET_1B],
1132                self.data[PDU_AND_MASK_OFFSET_2B],
1133            ]),
1134            or_mask: u16::from_be_bytes([
1135                self.data[PDU_OR_MASK_OFFSET_1B],
1136                self.data[PDU_OR_MASK_OFFSET_2B],
1137            ]),
1138        })
1139    }
1140
1141    /// Parses byte-count prefixed payloads used by read-style responses.
1142    pub fn byte_count_payload(&self) -> Result<ByteCountPayload<'_>, MbusError> {
1143        if self.data_len < 1 {
1144            return Err(MbusError::InvalidPduLength);
1145        }
1146
1147        let byte_count = self.data[0];
1148        let expected_len = 1usize
1149            .checked_add(byte_count as usize)
1150            .ok_or(MbusError::InvalidByteCount)?;
1151        if self.data_len as usize != expected_len {
1152            return Err(MbusError::InvalidByteCount);
1153        }
1154
1155        Ok(ByteCountPayload {
1156            byte_count,
1157            payload: &self.data[1..expected_len],
1158        })
1159    }
1160
1161    /// Parses FC17 read/write multiple registers payload.
1162    pub fn read_write_multiple_fields(&self) -> Result<ReadWriteMultipleFields<'_>, MbusError> {
1163        if (self.data_len as usize) < PDU_FC17_WRITE_VALUES_OFFSET {
1164            return Err(MbusError::InvalidPduLength);
1165        }
1166
1167        let (read_address, read_quantity) = self.read_address_quantity_pair()?;
1168        let write_address = u16::from_be_bytes([
1169            self.data[PDU_FC17_WRITE_ADDRESS_OFFSET_1B],
1170            self.data[PDU_FC17_WRITE_ADDRESS_OFFSET_2B],
1171        ]);
1172        let write_quantity = u16::from_be_bytes([
1173            self.data[PDU_FC17_WRITE_QUANTITY_OFFSET_1B],
1174            self.data[PDU_FC17_WRITE_QUANTITY_OFFSET_2B],
1175        ]);
1176        let write_byte_count = self.data[PDU_FC17_WRITE_BYTE_COUNT_OFFSET];
1177        let expected_len = PDU_FC17_WRITE_VALUES_OFFSET
1178            .checked_add(write_byte_count as usize)
1179            .ok_or(MbusError::InvalidByteCount)?;
1180
1181        if self.data_len as usize != expected_len {
1182            return Err(MbusError::InvalidByteCount);
1183        }
1184
1185        Ok(ReadWriteMultipleFields {
1186            read_address,
1187            read_quantity,
1188            write_address,
1189            write_quantity,
1190            write_byte_count,
1191            write_values: &self.data[PDU_FC17_WRITE_VALUES_OFFSET..expected_len],
1192        })
1193    }
1194
1195    /// Parses a single-byte payload for FC07-style responses. Data must be exactly 1 byte.
1196    pub fn single_byte_payload(&self) -> Result<u8, MbusError> {
1197        if self.data_len != 1 {
1198            return Err(MbusError::InvalidPduLength);
1199        }
1200        Ok(self.data[0])
1201    }
1202
1203    /// Parses FC08-style payloads: a 2-byte sub-function code followed by an even-length
1204    /// sequence of data words. Validates minimum length and even alignment.
1205    pub fn sub_function_payload(&self) -> Result<SubFunctionPayload<'_>, MbusError> {
1206        if self.data_len < 2 {
1207            return Err(MbusError::InvalidPduLength);
1208        }
1209        if !self.data_len.is_multiple_of(2) {
1210            return Err(MbusError::InvalidPduLength);
1211        }
1212        Ok(SubFunctionPayload {
1213            sub_function: u16::from_be_bytes([
1214                self.data[PDU_SUB_FUNCTION_OFFSET_1B],
1215                self.data[PDU_SUB_FUNCTION_OFFSET_2B],
1216            ]),
1217            payload: &self.data[2..self.data_len as usize],
1218        })
1219    }
1220
1221    /// Parses FC0B-style payloads: exactly two consecutive 16-bit values (4 bytes total).
1222    pub fn u16_pair_fields(&self) -> Result<U16PairFields, MbusError> {
1223        if self.data_len != 4 {
1224            return Err(MbusError::InvalidPduLength);
1225        }
1226        Ok(U16PairFields {
1227            first: u16::from_be_bytes([
1228                self.data[PDU_ADDRESS_OFFSET_1B],
1229                self.data[PDU_ADDRESS_OFFSET_2B],
1230            ]),
1231            second: u16::from_be_bytes([
1232                self.data[PDU_QUANTITY_OFFSET_1B],
1233                self.data[PDU_QUANTITY_OFFSET_2B],
1234            ]),
1235        })
1236    }
1237
1238    /// Parses FC2B-style payloads: a 1-byte MEI type followed by variable-length data.
1239    /// Data must have at least 1 byte.
1240    pub fn mei_type_payload(&self) -> Result<MeiTypePayload<'_>, MbusError> {
1241        if self.data_len < 1 {
1242            return Err(MbusError::InvalidPduLength);
1243        }
1244        Ok(MeiTypePayload {
1245            mei_type_byte: self.data[PDU_MEI_TYPE_OFFSET],
1246            payload: &self.data[1..self.data_len as usize],
1247        })
1248    }
1249
1250    /// Parses FC18-style payloads: a 2-byte FIFO byte count, 2-byte FIFO count, and raw
1251    /// value bytes. Data must have at least 4 bytes. Cross-field consistency validation
1252    /// (byte count vs FIFO count) is left to the caller.
1253    pub fn fifo_payload(&self) -> Result<FifoPayload<'_>, MbusError> {
1254        if self.data_len < 4 {
1255            return Err(MbusError::InvalidPduLength);
1256        }
1257        Ok(FifoPayload {
1258            fifo_byte_count: u16::from_be_bytes([
1259                self.data[PDU_FIFO_BYTE_COUNT_OFFSET_1B],
1260                self.data[PDU_FIFO_BYTE_COUNT_OFFSET_2B],
1261            ]),
1262            fifo_count: u16::from_be_bytes([
1263                self.data[PDU_FIFO_COUNT_OFFSET_1B],
1264                self.data[PDU_FIFO_COUNT_OFFSET_2B],
1265            ]),
1266            values: &self.data[PDU_FIFO_VALUES_OFFSET..self.data_len as usize],
1267        })
1268    }
1269
1270    /// Parses FC18 (Read FIFO Queue) request: a single 2-byte FIFO pointer address.
1271    /// Used to extract the pointer address from read FIFO queue requests.
1272    #[cfg(feature = "fifo")]
1273    pub fn fifo_pointer(&self) -> Result<u16, MbusError> {
1274        if self.data_len != 2 {
1275            return Err(MbusError::InvalidPduLength);
1276        }
1277        Ok(u16::from_be_bytes([
1278            self.data[PDU_ADDRESS_OFFSET_1B],
1279            self.data[PDU_ADDRESS_OFFSET_2B],
1280        ]))
1281    }
1282
1283    /// Parses FC08 (Diagnostics) request/response: sub-function code + data word.
1284    /// Returns (sub_function, data_word).
1285    #[cfg(feature = "diagnostics")]
1286    pub fn diagnostics_fields(&self) -> Result<(u16, u16), MbusError> {
1287        if self.data_len < 4 {
1288            return Err(MbusError::InvalidPduLength);
1289        }
1290        let sub_function = u16::from_be_bytes([
1291            self.data[PDU_SUB_FUNCTION_OFFSET_1B],
1292            self.data[PDU_SUB_FUNCTION_OFFSET_2B],
1293        ]);
1294        let data_word = u16::from_be_bytes([self.data[2], self.data[3]]);
1295        Ok((sub_function, data_word))
1296    }
1297
1298    /// Parses FC14 (Read File Record) requests into validated sub-requests.
1299    ///
1300    /// Request PDU data layout:
1301    /// - byte_count (1)
1302    /// - repeated sub-requests (7 bytes each):
1303    ///   - reference_type (1) = 0x06
1304    ///   - file_number (2)
1305    ///   - record_number (2)
1306    ///   - record_length (2)
1307    #[cfg(feature = "file-record")]
1308    pub fn file_record_read_sub_requests(
1309        &self,
1310    ) -> Result<
1311        heapless::Vec<
1312            crate::models::file_record::FileRecordReadSubRequest,
1313            { crate::models::file_record::MAX_SUB_REQUESTS_PER_PDU },
1314        >,
1315        MbusError,
1316    > {
1317        use crate::models::file_record::{FILE_RECORD_REF_TYPE, FileRecordReadSubRequest};
1318
1319        const FILE_RECORD_READ_SUB_REQUEST_LEN: usize = 7;
1320        const FILE_RECORD_MAX_REQUEST_BYTE_COUNT: usize = 245;
1321
1322        let data_len = self.data_len as usize;
1323        if data_len < 1 + FILE_RECORD_READ_SUB_REQUEST_LEN {
1324            return Err(MbusError::InvalidPduLength);
1325        }
1326
1327        let data = self.data.as_slice();
1328        let byte_count = data[0] as usize;
1329        if byte_count != data_len - 1 {
1330            return Err(MbusError::InvalidByteCount);
1331        }
1332        if byte_count > FILE_RECORD_MAX_REQUEST_BYTE_COUNT {
1333            return Err(MbusError::InvalidByteCount);
1334        }
1335        if !byte_count.is_multiple_of(FILE_RECORD_READ_SUB_REQUEST_LEN) {
1336            return Err(MbusError::InvalidByteCount);
1337        }
1338
1339        let mut out: heapless::Vec<
1340            FileRecordReadSubRequest,
1341            { crate::models::file_record::MAX_SUB_REQUESTS_PER_PDU },
1342        > = heapless::Vec::new();
1343        let mut index = 1usize;
1344        while index < data_len {
1345            if data[index] != FILE_RECORD_REF_TYPE {
1346                return Err(MbusError::InvalidValue);
1347            }
1348            let file_number = u16::from_be_bytes([data[index + 1], data[index + 2]]);
1349            let record_number = u16::from_be_bytes([data[index + 3], data[index + 4]]);
1350            let record_length = u16::from_be_bytes([data[index + 5], data[index + 6]]);
1351            if record_length == 0 {
1352                return Err(MbusError::InvalidQuantity);
1353            }
1354
1355            out.push(FileRecordReadSubRequest {
1356                file_number,
1357                record_number,
1358                record_length,
1359            })
1360            .map_err(|_| MbusError::TooManyFileReadSubRequests)?;
1361
1362            index += FILE_RECORD_READ_SUB_REQUEST_LEN;
1363        }
1364
1365        Ok(out)
1366    }
1367
1368    /// Parses FC15 (Write File Record) requests into validated sub-requests.
1369    ///
1370    /// Request PDU data layout:
1371    /// - byte_count (1)
1372    /// - repeated sub-requests:
1373    ///   - reference_type (1) = 0x06
1374    ///   - file_number (2)
1375    ///   - record_number (2)
1376    ///   - record_length (2)
1377    ///   - record_data (record_length * 2)
1378    #[cfg(feature = "file-record")]
1379    pub fn file_record_write_sub_requests<'a>(
1380        &'a self,
1381    ) -> Result<
1382        heapless::Vec<
1383            crate::models::file_record::FileRecordWriteSubRequest<'a>,
1384            { crate::models::file_record::MAX_SUB_REQUESTS_PER_PDU },
1385        >,
1386        MbusError,
1387    > {
1388        use crate::models::file_record::{FILE_RECORD_REF_TYPE, FileRecordWriteSubRequest};
1389
1390        const FILE_RECORD_WRITE_SUB_REQUEST_HEADER_LEN: usize = 7;
1391        const FILE_RECORD_MAX_REQUEST_BYTE_COUNT: usize = 245;
1392
1393        let data_len = self.data_len as usize;
1394        if data_len < 1 + FILE_RECORD_WRITE_SUB_REQUEST_HEADER_LEN + 2 {
1395            return Err(MbusError::InvalidPduLength);
1396        }
1397
1398        let data = self.data.as_slice();
1399        let byte_count = data[0] as usize;
1400        if byte_count != data_len - 1 {
1401            return Err(MbusError::InvalidByteCount);
1402        }
1403        if byte_count > FILE_RECORD_MAX_REQUEST_BYTE_COUNT {
1404            return Err(MbusError::InvalidByteCount);
1405        }
1406
1407        let mut out: heapless::Vec<
1408            FileRecordWriteSubRequest,
1409            { crate::models::file_record::MAX_SUB_REQUESTS_PER_PDU },
1410        > = heapless::Vec::new();
1411        let mut index = 1usize;
1412        while index < data_len {
1413            if index + FILE_RECORD_WRITE_SUB_REQUEST_HEADER_LEN > data_len {
1414                return Err(MbusError::InvalidPduLength);
1415            }
1416            if data[index] != FILE_RECORD_REF_TYPE {
1417                return Err(MbusError::InvalidValue);
1418            }
1419
1420            let file_number = u16::from_be_bytes([data[index + 1], data[index + 2]]);
1421            let record_number = u16::from_be_bytes([data[index + 3], data[index + 4]]);
1422            let record_length = u16::from_be_bytes([data[index + 5], data[index + 6]]);
1423            if record_length == 0 {
1424                return Err(MbusError::InvalidQuantity);
1425            }
1426
1427            let data_bytes_len = record_length as usize * 2;
1428            let end_index = index
1429                .checked_add(FILE_RECORD_WRITE_SUB_REQUEST_HEADER_LEN + data_bytes_len)
1430                .ok_or(MbusError::InvalidByteCount)?;
1431            if end_index > data_len {
1432                return Err(MbusError::InvalidByteCount);
1433            }
1434
1435            out.push(FileRecordWriteSubRequest {
1436                file_number,
1437                record_number,
1438                record_length,
1439                record_data_bytes: &data
1440                    [index + FILE_RECORD_WRITE_SUB_REQUEST_HEADER_LEN..end_index],
1441            })
1442            .map_err(|_| MbusError::TooManyFileReadSubRequests)?;
1443
1444            index = end_index;
1445        }
1446
1447        if out.is_empty() {
1448            return Err(MbusError::InvalidPduLength);
1449        }
1450
1451        Ok(out)
1452    }
1453
1454    /// Parses FC2B / MEI 0x0E (Read Device Identification) response structural fields.
1455    ///
1456    /// Validates the 6-byte header minimum length, walks through all declared objects
1457    /// to confirm their lengths fit within the PDU, and returns raw bytes for each
1458    /// header field. Callers must:
1459    /// - Check `mei_type_byte == EncapsulatedInterfaceType::ReadDeviceIdentification as u8`
1460    /// - Convert `read_device_id_code_byte` and `conformity_level_byte` via `TryFrom`
1461    pub fn read_device_id_fields(&self) -> Result<ReadDeviceIdPduFields, MbusError> {
1462        // Minimum: MEI(1) + ReadCode(1) + Conf(1) + More(1) + NextId(1) + NumObj(1)
1463        if (self.data_len as usize) < PDU_MEI_OBJECTS_DATA_OFFSET {
1464            return Err(MbusError::InvalidPduLength);
1465        }
1466        let data = self.data.as_slice();
1467        let number_of_objects = data[PDU_MEI_NUM_OBJECTS_OFFSET];
1468
1469        // Walk all declared objects to validate structural integrity
1470        let mut offset = PDU_MEI_OBJECTS_DATA_OFFSET;
1471        for _ in 0..number_of_objects as usize {
1472            if offset + 2 > data.len() {
1473                return Err(MbusError::InvalidPduLength);
1474            }
1475            let obj_len = data[offset + 1] as usize;
1476            offset += 2;
1477            if offset + obj_len > data.len() {
1478                return Err(MbusError::InvalidPduLength);
1479            }
1480            offset += obj_len;
1481        }
1482
1483        let payload_len = (self.data_len as usize) - PDU_MEI_OBJECTS_DATA_OFFSET;
1484        if payload_len > MAX_PDU_DATA_LEN {
1485            return Err(MbusError::BufferTooSmall);
1486        }
1487        let mut objects_data = [0u8; MAX_PDU_DATA_LEN];
1488        if payload_len > 0 {
1489            objects_data[..payload_len].copy_from_slice(&data[PDU_MEI_OBJECTS_DATA_OFFSET..]);
1490        }
1491
1492        Ok(ReadDeviceIdPduFields {
1493            mei_type_byte: data[PDU_MEI_TYPE_OFFSET],
1494            read_device_id_code_byte: data[PDU_MEI_READ_CODE_OFFSET],
1495            conformity_level_byte: data[PDU_MEI_CONFORMITY_LEVEL_OFFSET],
1496            more_follows: data[PDU_MEI_MORE_FOLLOWS_OFFSET] == 0xFF,
1497            next_object_id_byte: data[PDU_MEI_NEXT_OBJECT_ID_OFFSET],
1498            number_of_objects,
1499            objects_data,
1500            payload_len,
1501        })
1502    }
1503
1504    /// Converts the PDU into its byte representation.
1505    ///
1506    /// This method serializes the function code and its associated data payload.
1507    /// It uses an `unsafe` block to access the `Data` union, assuming that
1508    /// `self.data.bytes` contains the full data payload and `self.data_len`
1509    /// accurately reflects its length.
1510    ///
1511    /// # Returns
1512    /// `Ok(Vec<u8, 253>)` containing the PDU bytes, or an `MbusError` if
1513    /// the PDU cannot be serialized (e.g., due to buffer overflow).
1514    ///
1515    pub fn to_bytes(&self) -> Result<Vec<u8, 253>, MbusError> {
1516        let mut pdu_bytes = Vec::new(); // Capacity is 253 (1 byte FC + 252 bytes data)
1517        pdu_bytes
1518            .push(self.function_code as u8)
1519            .map_err(|_| MbusError::Unexpected)?; // Function code (1 byte)
1520
1521        pdu_bytes
1522            .extend_from_slice(&self.data.as_slice()[..self.data_len as usize])
1523            .map_err(|_| MbusError::BufferLenMissmatch)?; // Data bytes (variable length)
1524
1525        Ok(pdu_bytes)
1526    }
1527
1528    /// Creates a PDU from its byte representation.
1529    ///
1530    /// This method parses the function code and data payload from the given byte slice.
1531    ///
1532    /// # Arguments
1533    /// * `bytes` - A byte slice containing the PDU (Function Code + Data).
1534    ///
1535    /// # Returns
1536    /// `Ok(Pdu)` if the bytes represent a valid PDU, or an `MbusError` otherwise.
1537    pub fn from_bytes(bytes: &[u8]) -> Result<Self, MbusError> {
1538        if bytes.is_empty() {
1539            return Err(MbusError::InvalidPduLength);
1540        }
1541
1542        let error_code = if is_exception_code(bytes[0]) {
1543            if bytes.len() < 2 {
1544                return Err(MbusError::InvalidPduLength);
1545            }
1546            Some(bytes[1]) // The second byte is the exception code for error responses
1547        } else {
1548            None
1549        };
1550        let function_code = clear_exception_bit(bytes[0]); // Mask out the error bit to get the actual function code
1551
1552        let function_code = FunctionCode::try_from(function_code)?;
1553
1554        let data_slice = &bytes[1..];
1555        let data_len = data_slice.len();
1556
1557        if data_len > MAX_PDU_DATA_LEN {
1558            return Err(MbusError::InvalidPduLength);
1559        }
1560
1561        let mut data = heapless::Vec::new();
1562        data.extend_from_slice(data_slice)
1563            .map_err(|_| MbusError::BufferLenMissmatch)?;
1564
1565        Ok(Pdu {
1566            function_code,
1567            error_code,
1568            data,
1569            data_len: data_len as u8,
1570        })
1571    }
1572}
1573
1574/// Helper to build the ADU from PDU based on transport type.
1575pub fn compile_adu_frame(
1576    txn_id: u16,
1577    unit_id: u8,
1578    pdu: Pdu,
1579    transport_type: TransportType,
1580) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, MbusError> {
1581    match transport_type {
1582        TransportType::StdTcp | TransportType::CustomTcp => {
1583            let pdu_bytes_len = pdu.to_bytes()?.len() as u16;
1584            let mbap_header = MbapHeader::new(txn_id, pdu_bytes_len + 1, unit_id);
1585            ModbusMessage::new(AdditionalAddress::MbapHeader(mbap_header), pdu).to_bytes()
1586        }
1587        TransportType::StdSerial(serial_mode) | TransportType::CustomSerial(serial_mode) => {
1588            let slave_address = SlaveAddress(unit_id);
1589            let adu_bytes = match serial_mode {
1590                SerialMode::Rtu => {
1591                    let mut adu_bytes =
1592                        ModbusMessage::new(AdditionalAddress::SlaveAddress(slave_address), pdu)
1593                            .to_bytes()?;
1594                    // Calculate the 16-bit CRC for the Slave Address + PDU.
1595                    let crc16 = checksum::crc16(adu_bytes.as_slice());
1596                    // Modbus RTU transmits CRC in Little-Endian (LSB first) according to the spec.
1597                    let crc_bytes = crc16.to_le_bytes();
1598                    adu_bytes
1599                        .extend_from_slice(&crc_bytes)
1600                        .map_err(|_| MbusError::Unexpected)?;
1601
1602                    adu_bytes
1603                }
1604                SerialMode::Ascii => {
1605                    ModbusMessage::new(AdditionalAddress::SlaveAddress(slave_address), pdu)
1606                        .to_ascii_bytes()?
1607                }
1608            };
1609
1610            Ok(adu_bytes)
1611        }
1612    }
1613}
1614
1615/// Decodes a raw transport frame into a ModbusMessage based on the transport type.
1616pub fn decompile_adu_frame(
1617    frame: &[u8],
1618    transport_type: TransportType,
1619) -> Result<ModbusMessage, MbusError> {
1620    match transport_type {
1621        TransportType::StdTcp | TransportType::CustomTcp => {
1622            // Parse MBAP header and PDU
1623            ModbusMessage::from_bytes(frame)
1624        }
1625        TransportType::StdSerial(serial_mode) | TransportType::CustomSerial(serial_mode) => {
1626            match serial_mode {
1627                SerialMode::Rtu => ModbusMessage::from_rtu_bytes(frame),
1628                SerialMode::Ascii => ModbusMessage::from_ascii_bytes(frame),
1629            }
1630        }
1631    }
1632}
1633
1634/// Derives the expected total length of a Modbus frame from its initial bytes.
1635///
1636/// This function is used by stream-based transports to determine if a complete
1637/// Application Data Unit (ADU) has been received before attempting full decompression.
1638///
1639/// # Arguments
1640/// * `frame` - The raw byte buffer containing the partial or full frame.
1641/// * `transport_type` - The Modbus variant (TCP, RTU, or ASCII).
1642///
1643/// # Returns
1644/// * `Some(usize)` - The calculated total length of the frame if enough metadata is present.
1645/// * `None` - If the buffer is too short to determine the length.
1646pub fn derive_length_from_bytes(frame: &[u8], transport_type: TransportType) -> Option<usize> {
1647    match transport_type {
1648        TransportType::StdTcp | TransportType::CustomTcp => {
1649            // TCP (MBAP) requires at least 6 bytes to read the 'Length' field.
1650            // MBAP structure: TID(2), PID(2), Length(2), UnitID(1)
1651            if frame.len() < 6 {
1652                return None;
1653            }
1654
1655            // In Modbus TCP, the Protocol ID MUST be 0x0000.
1656            // If it's not, this is garbage data. We return a huge length to trigger a parse error
1657            // downstream and force the window to slide and resync.
1658            let protocol_id = u16::from_be_bytes([frame[2], frame[3]]);
1659            if protocol_id != MODBUS_PROTOCOL_ID {
1660                return Some(usize::MAX);
1661            }
1662
1663            // The Length field in MBAP (offset 4) counts all following bytes (UnitID + PDU).
1664            // Total ADU = 6 bytes (Header up to Length) + value of Length field.
1665            let length_field = u16::from_be_bytes([frame[4], frame[5]]) as usize;
1666            Some(6 + length_field)
1667        }
1668        TransportType::StdSerial(SerialMode::Rtu)
1669        | TransportType::CustomSerial(SerialMode::Rtu) => {
1670            if frame.len() < 2 {
1671                return None;
1672            }
1673
1674            let fc = frame[1];
1675
1676            // Exception responses are universally 5 bytes: [ID][FC+0x80][ExcCode][CRC_L][CRC_H]
1677            if is_exception_code(fc) {
1678                return Some(5);
1679            }
1680
1681            // Helper: Opportunistically verifies if a potential frame boundary has a valid CRC.
1682            // This allows us to disambiguate Requests vs Responses dynamically as the stream arrives.
1683            let check_crc = |len: usize| -> bool {
1684                if frame.len() >= len && len >= MIN_RTU_ADU_LEN {
1685                    let data_len = len - RTU_CRC_SIZE;
1686                    let received_crc = u16::from_le_bytes([frame[data_len], frame[data_len + 1]]);
1687                    checksum::crc16(&frame[..data_len]) == received_crc
1688                } else {
1689                    false
1690                }
1691            };
1692
1693            get_byte_count_from_frame(frame, fc, check_crc)
1694        }
1695        TransportType::StdSerial(SerialMode::Ascii)
1696        | TransportType::CustomSerial(SerialMode::Ascii) => {
1697            // ASCII frames are delimited by ':' and '\r\n'.
1698            // We scan for the end-of-frame marker.
1699            if frame.len() < MIN_ASCII_ADU_LEN {
1700                return None;
1701            }
1702
1703            // Fast linear scan for the LF character which terminates the frame
1704            frame.iter().position(|&b| b == b'\n').map(|pos| pos + 1)
1705        }
1706    }
1707}
1708
1709fn get_byte_count_from_frame(
1710    frame: &[u8],
1711    fc: u8,
1712    check_crc: impl Fn(usize) -> bool,
1713) -> Option<usize> {
1714    let mut candidates = heapless::Vec::<usize, 4>::new();
1715    let mut min_needed = usize::MAX;
1716
1717    // Helper function to safely calculate dynamic lengths based on a byte in the frame.
1718    // We pass candidates as a mutable reference to avoid "multiple mutable borrow" errors
1719    // that occur when a closure captures a mutable variable and is called multiple times.
1720    let mut add_dyn = |cands: &mut heapless::Vec<usize, 4>, offset: usize, base: usize| {
1721        if frame.len() > offset {
1722            let _ = cands.push(base + frame[offset] as usize);
1723        } else {
1724            min_needed = core::cmp::min(min_needed, offset + 1);
1725        }
1726    };
1727
1728    // Map structural candidates (Requests and Responses combined) based on Modbus definitions
1729    match fc {
1730        1..=4 => {
1731            let _ = candidates.push(8);
1732            add_dyn(&mut candidates, 2, 5);
1733        }
1734        5 | 6 | 8 => {
1735            let _ = candidates.push(8);
1736        }
1737        7 => {
1738            let _ = candidates.push(4);
1739            let _ = candidates.push(5);
1740        }
1741        11 => {
1742            let _ = candidates.push(4);
1743            let _ = candidates.push(8);
1744        }
1745        12 | 17 => {
1746            let _ = candidates.push(4);
1747            add_dyn(&mut candidates, 2, 5);
1748        }
1749        15 | 16 => {
1750            let _ = candidates.push(8);
1751            add_dyn(&mut candidates, 6, 9);
1752        }
1753        20 | 21 => add_dyn(&mut candidates, 2, 5),
1754        22 => {
1755            let _ = candidates.push(10);
1756        }
1757        23 => {
1758            add_dyn(&mut candidates, 2, 5);
1759            add_dyn(&mut candidates, 10, 13);
1760        }
1761        24 => {
1762            let _ = candidates.push(6);
1763            if frame.len() >= 4 {
1764                let byte_count = u16::from_be_bytes([frame[2], frame[3]]) as usize;
1765                let _ = candidates.push(6 + byte_count);
1766            } else {
1767                min_needed = core::cmp::min(min_needed, 4);
1768            }
1769        }
1770        43 => {
1771            if check_crc(7) {
1772                return Some(7);
1773            }
1774            // Response is unpredictable. Scan opportunistically forwards to support pipelined frames.
1775            for len in MIN_RTU_ADU_LEN..=frame.len() {
1776                if check_crc(len) {
1777                    return Some(len);
1778                }
1779            }
1780            return None;
1781        }
1782        _ => {
1783            for len in MIN_RTU_ADU_LEN..=frame.len() {
1784                if check_crc(len) {
1785                    return Some(len);
1786                }
1787            }
1788            return None;
1789        }
1790    }
1791
1792    // 1. Opportunistic CRC checks to lock in an exact frame boundary
1793    for &len in &candidates {
1794        if check_crc(len) {
1795            return Some(len);
1796        }
1797    }
1798
1799    // 2. If no CRC matched yet, determine the max length we might need to wait for
1800    let max_candidate = candidates.iter().copied().max().unwrap_or(0);
1801    let target = if min_needed != usize::MAX {
1802        core::cmp::max(min_needed, max_candidate)
1803    } else {
1804        max_candidate
1805    };
1806
1807    if target > 0 { Some(target) } else { None }
1808}
1809
1810/// Helper function to convert a 4-bit nibble to its ASCII hex representation.
1811fn nibble_to_hex(nibble: u8) -> u8 {
1812    match nibble {
1813        0..=9 => b'0' + nibble,
1814        10..=15 => b'A' + (nibble - 10),
1815        _ => b'?', // Should not happen for a valid nibble
1816    }
1817}
1818
1819/// Helper function to convert a hex character to its 4-bit nibble value.
1820fn hex_char_to_nibble(c: u8) -> Result<u8, MbusError> {
1821    match c {
1822        b'0'..=b'9' => Ok(c - b'0'),
1823        b'A'..=b'F' => Ok(c - b'A' + 10),
1824        b'a'..=b'f' => Ok(c - b'a' + 10),
1825        _ => Err(MbusError::BasicParseError),
1826    }
1827}
1828
1829/// Helper function to convert two hex characters to a byte.
1830fn hex_pair_to_byte(high: u8, low: u8) -> Result<u8, MbusError> {
1831    let h = hex_char_to_nibble(high)?;
1832    let l = hex_char_to_nibble(low)?;
1833    Ok((h << 4) | l)
1834}
1835
1836#[cfg(test)]
1837mod tests {
1838    use super::*;
1839    use crate::function_codes::public::FunctionCode;
1840    use heapless::Vec;
1841
1842    // --- Tests for Pdu::from_bytes ---
1843
1844    /// Test case: `Pdu::from_bytes` with a PDU that has no data bytes (only FC).
1845    ///
1846    /// Function-code-only PDUs are valid for zero-argument operations such as
1847    /// `ReportServerId` (FC11).
1848    #[test]
1849    fn test_pdu_from_bytes_valid_no_data() {
1850        let bytes = [0x11];
1851        let pdu = Pdu::from_bytes(&bytes).expect("Should parse a function-code-only PDU");
1852
1853        assert_eq!(pdu.function_code, FunctionCode::ReportServerId);
1854        assert_eq!(pdu.data_len, 0);
1855        assert!(pdu.data.is_empty());
1856        assert_eq!(pdu.error_code, None);
1857    }
1858
1859    /// Test case: `Pdu::from_bytes` with a valid `Read Coils` request PDU.
1860    ///
1861    /// This tests parsing a function code followed by address and quantity bytes.
1862    ///
1863    /// Modbus Specification Reference: V1.1b3, Section 6.1 (Read Coils).
1864    #[test]
1865    fn test_pdu_from_bytes_valid_read_coils_request() {
1866        // Read Coils (0x01) request: FC (1 byte) + Starting Address (2 bytes) + Quantity of Coils (2 bytes)
1867        // Example: Read 10 coils starting at address 0x0000
1868        let bytes = [0x01, 0x00, 0x00, 0x00, 0x0A]; // FC, Addr_Hi, Addr_Lo, Qty_Hi, Qty_Lo
1869        let pdu = Pdu::from_bytes(&bytes).expect("Should successfully parse Read Coils request");
1870
1871        assert_eq!(pdu.function_code, FunctionCode::ReadCoils);
1872        assert_eq!(pdu.data_len, 4);
1873        assert_eq!(pdu.data.as_slice(), &[0x00, 0x00, 0x00, 0x0A]);
1874    }
1875
1876    /// Test case: `Pdu::from_bytes` with a valid `Read Holding Registers` response PDU.
1877    ///
1878    /// This tests parsing a function code, a byte count, and then the actual data bytes.
1879    ///
1880    /// Modbus Specification Reference: V1.1b3, Section 6.3 (Read Holding Registers).
1881    #[test]
1882    fn test_pdu_from_bytes_valid_read_holding_registers_response() {
1883        // Read Holding Registers (0x03) response: FC (1 byte) + Byte Count (1 byte) + Data (N bytes)
1884        // Example: Response with 2 registers (4 bytes of data)
1885        let bytes = [0x03, 0x04, 0x12, 0x34, 0x56, 0x78]; // FC, Byte Count, Reg1_Hi, Reg1_Lo, Reg2_Hi, Reg2_Lo
1886        let pdu = Pdu::from_bytes(&bytes)
1887            .expect("Should successfully parse Read Holding Registers response");
1888
1889        assert_eq!(pdu.function_code, FunctionCode::ReadHoldingRegisters);
1890        assert_eq!(pdu.data_len, 5); // Byte Count (0x04) + 4 data bytes
1891        assert_eq!(pdu.data.as_slice(), &[0x04, 0x12, 0x34, 0x56, 0x78]);
1892    }
1893
1894    /// Test case: `Pdu::from_bytes` with a PDU containing the maximum allowed data length.
1895    ///
1896    /// The maximum PDU data length is 252 bytes (total PDU size 253 bytes including FC).
1897    ///
1898    /// Modbus Specification Reference: V1.1b3, Section 4.1 (PDU Size).
1899    #[test]
1900    fn test_pdu_from_bytes_valid_max_data_length() {
1901        // Max PDU data length is 252 bytes.
1902        let mut bytes_vec: Vec<u8, 253> = Vec::new();
1903        let _ = bytes_vec.push(0x03); // Dummy FC (Read Holding Registers)
1904        for i in 0..252 {
1905            let _ = bytes_vec.push(i as u8);
1906        }
1907        let bytes = bytes_vec.as_slice();
1908        let pdu = Pdu::from_bytes(bytes).expect("Should parse valid PDU with max data");
1909
1910        assert_eq!(pdu.function_code, FunctionCode::ReadHoldingRegisters);
1911        assert_eq!(pdu.data_len, 252);
1912        assert_eq!(pdu.data.as_slice(), &bytes[1..]);
1913    }
1914
1915    /// Test case: `Pdu::from_bytes` with an empty byte slice.
1916    ///
1917    /// An empty slice is an invalid PDU as it lacks even a function code.
1918    #[test]
1919    fn test_pdu_from_bytes_empty_slice_error() {
1920        let bytes = [];
1921        let err = Pdu::from_bytes(&bytes).expect_err("Should return error for empty slice");
1922        assert_eq!(err, MbusError::InvalidPduLength);
1923    }
1924
1925    /// Test case: `Pdu::from_bytes` with an invalid or unsupported function code.
1926    ///
1927    /// This checks for `MbusError::UnsupportedFunction` when the function code is not recognized.
1928    /// Modbus Specification Reference: V1.1b3, Section 5.1 (Public Function Code Definition).
1929    #[test]
1930    fn test_pdu_from_bytes_invalid_function_code_error() {
1931        // 0x00 is not a valid public function code
1932        let bytes = [0x00, 0x01, 0x02];
1933        let err = Pdu::from_bytes(&bytes).expect_err("Should return error for invalid FC 0x00");
1934        assert_eq!(err, MbusError::UnsupportedFunction(0x00));
1935
1936        // 0xFF is also not a valid public function code
1937        let bytes = [0xFF, 0x01, 0x02];
1938        let err = Pdu::from_bytes(&bytes).expect_err("Should return error for invalid FC 0xFF");
1939        assert_eq!(err, MbusError::UnsupportedFunction(0x7F));
1940    }
1941
1942    /// Test case: `Pdu::from_bytes` with a data payload exceeding the maximum allowed length.
1943    ///
1944    /// The maximum PDU data length is 252 bytes. This test provides 253 data bytes.
1945    ///
1946    /// Modbus Specification Reference: V1.1b3, Section 4.1 (PDU Size).
1947    #[test]
1948    fn test_pdu_from_bytes_data_too_long_error() {
1949        // PDU data length > 252 bytes (total PDU length > 253 bytes)
1950        let mut bytes_vec: Vec<u8, 254> = Vec::new();
1951        let _ = bytes_vec.push(0x03); // Dummy FC
1952        for i in 0..253 {
1953            // 253 data bytes, which is too many
1954            let _ = bytes_vec.push(i as u8);
1955        }
1956        let bytes = bytes_vec.as_slice();
1957        let err = Pdu::from_bytes(bytes).expect_err("Should return error for too much data");
1958        assert_eq!(err, MbusError::InvalidPduLength);
1959    }
1960
1961    #[test]
1962    fn test_pdu_read_window_parses_expected_fields() {
1963        let pdu = Pdu::from_bytes(&[0x03, 0x12, 0x34, 0x00, 0x02]).expect("valid pdu");
1964        let parsed = pdu.read_window().expect("read window should parse");
1965
1966        assert_eq!(
1967            parsed,
1968            ReadWindow {
1969                address: 0x1234,
1970                quantity: 0x0002
1971            }
1972        );
1973    }
1974
1975    #[test]
1976    fn test_pdu_write_single_u16_fields_parses_expected_fields() {
1977        let pdu = Pdu::from_bytes(&[0x06, 0x01, 0x02, 0xAB, 0xCD]).expect("valid pdu");
1978        let parsed = pdu
1979            .write_single_u16_fields()
1980            .expect("write single fields should parse");
1981
1982        assert_eq!(
1983            parsed,
1984            WriteSingleU16Fields {
1985                address: 0x0102,
1986                value: 0xABCD
1987            }
1988        );
1989    }
1990
1991    #[test]
1992    fn test_pdu_write_multiple_fields_parses_expected_fields() {
1993        let pdu = Pdu::from_bytes(&[0x10, 0x00, 0x20, 0x00, 0x02, 0x04, 0x12, 0x34, 0x56, 0x78])
1994            .expect("valid pdu");
1995        let parsed = pdu
1996            .write_multiple_fields()
1997            .expect("write multiple fields should parse");
1998
1999        assert_eq!(parsed.address, 0x0020);
2000        assert_eq!(parsed.quantity, 0x0002);
2001        assert_eq!(parsed.byte_count, 0x04);
2002        assert_eq!(parsed.values, &[0x12, 0x34, 0x56, 0x78]);
2003    }
2004
2005    #[test]
2006    fn test_pdu_mask_write_register_fields_parses_expected_fields() {
2007        let pdu = Pdu::from_bytes(&[0x16, 0x00, 0x10, 0xFF, 0x00, 0x00, 0x0F]).expect("valid pdu");
2008        let parsed = pdu
2009            .mask_write_register_fields()
2010            .expect("mask write fields should parse");
2011
2012        assert_eq!(
2013            parsed,
2014            MaskWriteRegisterFields {
2015                address: 0x0010,
2016                and_mask: 0xFF00,
2017                or_mask: 0x000F
2018            }
2019        );
2020    }
2021
2022    #[test]
2023    fn test_pdu_byte_count_payload_parses_expected_fields() {
2024        let pdu = Pdu::from_bytes(&[0x03, 0x04, 0x12, 0x34, 0x56, 0x78]).expect("valid pdu");
2025        let parsed = pdu
2026            .byte_count_payload()
2027            .expect("byte-count payload should parse");
2028
2029        assert_eq!(parsed.byte_count, 0x04);
2030        assert_eq!(parsed.payload, &[0x12, 0x34, 0x56, 0x78]);
2031    }
2032
2033    #[test]
2034    fn test_pdu_write_multiple_fields_rejects_mismatched_byte_count() {
2035        let pdu =
2036            Pdu::from_bytes(&[0x10, 0x00, 0x20, 0x00, 0x02, 0x03, 0x12, 0x34]).expect("valid pdu");
2037        let err = pdu
2038            .write_multiple_fields()
2039            .expect_err("byte count mismatch should error");
2040
2041        assert_eq!(err, MbusError::InvalidByteCount);
2042    }
2043
2044    #[test]
2045    fn test_pdu_byte_count_payload_rejects_mismatched_byte_count() {
2046        let pdu = Pdu::from_bytes(&[0x03, 0x03, 0x12, 0x34]).expect("valid pdu");
2047        let err = pdu
2048            .byte_count_payload()
2049            .expect_err("byte count mismatch should error");
2050
2051        assert_eq!(err, MbusError::InvalidByteCount);
2052    }
2053
2054    #[test]
2055    fn test_pdu_read_write_multiple_fields_parses_expected_fields() {
2056        let pdu = Pdu::from_bytes(&[
2057            0x17, 0x00, 0x10, 0x00, 0x02, 0x00, 0x20, 0x00, 0x02, 0x04, 0x12, 0x34, 0x56, 0x78,
2058        ])
2059        .expect("valid pdu");
2060        let parsed = pdu
2061            .read_write_multiple_fields()
2062            .expect("read/write multiple fields should parse");
2063
2064        assert_eq!(parsed.read_address, 0x0010);
2065        assert_eq!(parsed.read_quantity, 0x0002);
2066        assert_eq!(parsed.write_address, 0x0020);
2067        assert_eq!(parsed.write_quantity, 0x0002);
2068        assert_eq!(parsed.write_byte_count, 0x04);
2069        assert_eq!(parsed.write_values, &[0x12, 0x34, 0x56, 0x78]);
2070    }
2071
2072    #[test]
2073    fn test_pdu_read_write_multiple_fields_rejects_short_pdu() {
2074        let pdu = Pdu::from_bytes(&[0x17, 0x00, 0x10, 0x00, 0x02, 0x00]).expect("valid pdu");
2075        let err = pdu
2076            .read_write_multiple_fields()
2077            .expect_err("PDU too short should error");
2078
2079        assert_eq!(err, MbusError::InvalidPduLength);
2080    }
2081
2082    #[test]
2083    fn test_pdu_read_write_multiple_fields_rejects_mismatched_byte_count() {
2084        let pdu = Pdu::from_bytes(&[
2085            0x17, 0x00, 0x10, 0x00, 0x02, 0x00, 0x20, 0x00, 0x02, 0x05, 0x12, 0x34, 0x56, 0x78,
2086        ])
2087        .expect("valid pdu");
2088        let err = pdu
2089            .read_write_multiple_fields()
2090            .expect_err("byte count mismatch should error");
2091
2092        assert_eq!(err, MbusError::InvalidByteCount);
2093    }
2094
2095    // --- Tests for Pdu::to_bytes ---
2096
2097    /// Test case: `Pdu::to_bytes` with a PDU that has no data bytes.
2098    ///
2099    /// This covers function codes like `ReportServerId` (0x11) which consist only of the function code.
2100    ///
2101    /// Modbus Specification Reference: V1.1b3, Section 6.13 (Report Server ID).
2102    #[test]
2103    fn test_pdu_to_bytes_no_data() {
2104        let pdu = Pdu::new(FunctionCode::ReportServerId, Vec::new(), 0);
2105        let bytes = pdu.to_bytes().expect("Should convert PDU to bytes");
2106        assert_eq!(bytes.as_slice(), &[0x11]);
2107    }
2108
2109    /// Test case: `Pdu::to_bytes` with a PDU containing a typical data payload.
2110    ///
2111    /// Example: `Read Coils` request with address and quantity.
2112    ///
2113    /// Modbus Specification Reference: V1.1b3, Section 6.1 (Read Coils).
2114    #[test]
2115    fn test_pdu_to_bytes_with_data() {
2116        let mut data_vec = Vec::new();
2117        data_vec
2118            .extend_from_slice(&[0x00, 0x00, 0x00, 0x0A])
2119            .unwrap(); // Read 10 coils
2120
2121        let pdu = Pdu::new(FunctionCode::ReadCoils, data_vec, 4);
2122        let bytes = pdu.to_bytes().expect("Should convert PDU to bytes");
2123        assert_eq!(bytes.as_slice(), &[0x01, 0x00, 0x00, 0x00, 0x0A]);
2124    }
2125
2126    /// Test case: `Pdu::to_bytes` with a PDU containing the maximum allowed data length.
2127    ///
2128    /// The maximum PDU data length is 252 bytes.
2129    ///
2130    /// Modbus Specification Reference: V1.1b3, Section 4.1 (PDU Size).
2131    #[test]
2132    fn test_pdu_to_bytes_max_data() {
2133        let mut data_vec = Vec::new();
2134        for i in 0..252 {
2135            data_vec.push(i as u8).unwrap();
2136        }
2137
2138        let pdu = Pdu::new(FunctionCode::ReadHoldingRegisters, data_vec, 252);
2139        let bytes = pdu
2140            .to_bytes()
2141            .expect("Should convert PDU to bytes with max data");
2142        let mut expected_bytes_vec: Vec<u8, 253> = Vec::new();
2143        let _ = expected_bytes_vec.push(0x03);
2144        for i in 0..252 {
2145            let _ = expected_bytes_vec.push(i as u8);
2146        }
2147        assert_eq!(bytes.as_slice(), expected_bytes_vec.as_slice());
2148    }
2149
2150    /// Test case: `ModbusMessage::to_bytes` for a Modbus TCP message.
2151    ///
2152    /// Verifies that a `ModbusMessage` with an `MbapHeader` and `Pdu` is correctly
2153    /// serialized into its ADU byte representation.
2154    #[test]
2155    fn test_modbus_message_to_bytes_tcp() {
2156        let mbap_header = MbapHeader {
2157            transaction_id: 0x1234,
2158            protocol_id: 0x0000,
2159            length: 0x0005, // Length of PDU (FC + Data) + Unit ID
2160            unit_id: 0x01,
2161        };
2162
2163        let mut pdu_data_vec: Vec<u8, MAX_PDU_DATA_LEN> = Vec::new();
2164        pdu_data_vec.extend_from_slice(&[0x00, 0x00, 0x00]).unwrap();
2165
2166        let pdu = Pdu::new(
2167            FunctionCode::ReadHoldingRegisters,
2168            pdu_data_vec,
2169            3, // 3 data bytes
2170        );
2171
2172        let modbus_message = ModbusMessage::new(AdditionalAddress::MbapHeader(mbap_header), pdu);
2173        let adu_bytes = modbus_message
2174            .to_bytes()
2175            .expect("Failed to serialize ModbusMessage");
2176
2177        #[rustfmt::skip]
2178        let expected_adu: [u8; 11] = [
2179            0x12, 0x34, // Transaction ID
2180            0x00, 0x00, // Protocol ID
2181            0x00, 0x05, // Length (PDU length (FC + Data) + 1 byte Unit ID = (1 + 3) + 1 = 5)
2182            0x01,       // Unit ID
2183            0x03,       // Function Code (Read Holding Registers)
2184            0x00, 0x00, 0x00, // Data
2185        ];
2186
2187        assert_eq!(adu_bytes.as_slice(), &expected_adu);
2188    }
2189
2190    // --- Tests for FunctionCode::try_from ---
2191    /// Test case: `FunctionCode::try_from` with valid `u8` values.
2192    ///
2193    /// Verifies that known public function codes are correctly converted to their `FunctionCode` enum variants.
2194    /// Modbus Specification Reference: V1.1b3, Section 5.1 (Public Function Code Definition).
2195    #[test]
2196    fn test_function_code_try_from_valid() {
2197        assert_eq!(
2198            FunctionCode::try_from(0x01).unwrap(),
2199            FunctionCode::ReadCoils
2200        );
2201        assert_eq!(
2202            FunctionCode::try_from(0x08).unwrap(),
2203            FunctionCode::Diagnostics
2204        );
2205        assert_eq!(
2206            FunctionCode::try_from(0x2B).unwrap(),
2207            FunctionCode::EncapsulatedInterfaceTransport
2208        );
2209        assert_eq!(
2210            FunctionCode::try_from(0x18).unwrap(),
2211            FunctionCode::ReadFifoQueue
2212        );
2213        assert_eq!(
2214            FunctionCode::try_from(0x11).unwrap(),
2215            FunctionCode::ReportServerId
2216        );
2217    }
2218
2219    /// Test case: `FunctionCode::try_from` with invalid or reserved `u8` values.
2220    ///
2221    /// Verifies that `MbusError::UnsupportedFunction` is returned for unknown or reserved function code bytes.
2222    /// Modbus Specification Reference: V1.1b3, Section 5.1 (Public Function Code Definition).
2223    #[test]
2224    fn test_function_code_try_from_invalid() {
2225        let err = FunctionCode::try_from(0x00).expect_err("Should error for invalid FC 0x00");
2226        assert_eq!(err, MbusError::UnsupportedFunction(0x00));
2227
2228        let err =
2229            FunctionCode::try_from(0x09).expect_err("Should error for invalid FC 0x09 (reserved)");
2230        assert_eq!(err, MbusError::UnsupportedFunction(0x09));
2231
2232        let err = FunctionCode::try_from(0x64)
2233            .expect_err("Should error for invalid FC 0x64 (private range, not public)");
2234        assert_eq!(err, MbusError::UnsupportedFunction(0x64));
2235    }
2236
2237    // --- Round-trip tests (from_bytes -> to_bytes) ---
2238
2239    /// Test case: Round-trip serialization/deserialization for a PDU with data.
2240    ///
2241    /// Converts bytes to PDU and back to bytes, asserting equality with the original.
2242    /// Modbus Specification Reference: General Modbus PDU structure.
2243    #[test]
2244    fn test_pdu_round_trip_with_data() {
2245        let original_bytes = [0x01, 0x00, 0x00, 0x00, 0x0A]; // Read Coils
2246        let pdu = Pdu::from_bytes(&original_bytes).expect("from_bytes failed");
2247        let new_bytes = pdu.to_bytes().expect("to_bytes failed");
2248        assert_eq!(original_bytes.as_slice(), new_bytes.as_slice());
2249    }
2250
2251    /// Test case: Round-trip serialization/deserialization for a PDU with maximum data length.
2252    ///
2253    /// Converts bytes to PDU and back to bytes, asserting equality with the original.
2254    /// Modbus Specification Reference: V1.1b3, Section 4.1 (PDU Size).
2255    #[test]
2256    fn test_pdu_round_trip_max_data() {
2257        let mut original_bytes_vec: Vec<u8, 253> = Vec::new();
2258        let _ = original_bytes_vec.push(0x03); // Read Holding Registers
2259        for i in 0..252 {
2260            let _ = original_bytes_vec.push(i as u8);
2261        }
2262        let original_bytes = original_bytes_vec.as_slice();
2263
2264        let pdu = Pdu::from_bytes(original_bytes).expect("from_bytes failed");
2265        let new_bytes = pdu.to_bytes().expect("to_bytes failed");
2266        assert_eq!(original_bytes, new_bytes.as_slice());
2267    }
2268
2269    // --- Tests for ModbusMessage::to_ascii_bytes ---
2270
2271    /// Test case: `ModbusMessage::to_ascii_bytes` with a valid message.
2272    ///
2273    /// Verifies correct ASCII encoding and LRC calculation.
2274    /// Request: Slave 1, Read Coils (FC 01), Start 0, Qty 10.
2275    /// Binary: 01 01 00 00 00 0A
2276    /// LRC: -(01+01+0A) = -12 = F4
2277    /// ASCII: :01010000000AF4\r\n
2278    #[test]
2279    fn test_modbus_message_to_ascii_bytes_valid() {
2280        let slave_addr = SlaveAddress::new(1).unwrap();
2281        let mut data = Vec::new();
2282        data.extend_from_slice(&[0x00, 0x00, 0x00, 0x0A]).unwrap();
2283        let pdu = Pdu::new(FunctionCode::ReadCoils, data, 4);
2284        let message = ModbusMessage::new(AdditionalAddress::SlaveAddress(slave_addr), pdu);
2285
2286        let ascii_bytes = message
2287            .to_ascii_bytes()
2288            .expect("Failed to convert to ASCII");
2289
2290        let expected = b":01010000000AF4\r\n";
2291        assert_eq!(ascii_bytes.as_slice(), expected);
2292    }
2293
2294    /// Test case: `ModbusMessage::to_ascii_bytes` boundary check.
2295    ///
2296    /// Case 1: Data len 125. Total ASCII = 1 + (1+1+125+1)*2 + 2 = 1 + 256 + 2 = 259.
2297    /// This fits comfortably within MAX_ADU_FRAME_LEN (513).
2298    #[test]
2299    fn test_modbus_message_to_ascii_bytes_max_capacity() {
2300        let slave_addr = SlaveAddress::new(1).unwrap();
2301        let mut data = Vec::new();
2302        for _ in 0..125 {
2303            data.push(0xAA).unwrap();
2304        }
2305        let pdu = Pdu::new(FunctionCode::ReadHoldingRegisters, data, 125);
2306        let message = ModbusMessage::new(AdditionalAddress::SlaveAddress(slave_addr), pdu);
2307
2308        let ascii_bytes = message.to_ascii_bytes().expect("Should fit in buffer");
2309        assert_eq!(ascii_bytes.len(), 259);
2310    }
2311
2312    /// Test case: `ModbusMessage::to_ascii_bytes` with large payload.
2313    ///
2314    /// Case 2: Data len 126. Total ASCII = 1 + (1+1+126+1)*2 + 2 = 1 + 258 + 2 = 261.
2315    /// This should NOT fail with MAX_ADU_FRAME_LEN = 513.
2316    #[test]
2317    fn test_modbus_message_to_ascii_bytes_large_payload() {
2318        let slave_addr = SlaveAddress::new(1).unwrap();
2319        let mut data = Vec::new();
2320        for _ in 0..126 {
2321            data.push(0xAA).unwrap();
2322        }
2323        let pdu = Pdu::new(FunctionCode::ReadHoldingRegisters, data, 126);
2324        let message = ModbusMessage::new(AdditionalAddress::SlaveAddress(slave_addr), pdu);
2325
2326        let ascii_bytes = message.to_ascii_bytes().expect("Should fit in buffer");
2327        assert_eq!(ascii_bytes.len(), 261);
2328    }
2329
2330    // --- Tests for decompile_adu_frame ---
2331
2332    #[test]
2333    fn test_decompile_adu_frame_tcp_valid() {
2334        let frame = [
2335            0x12, 0x34, // TID
2336            0x00, 0x00, // PID
2337            0x00, 0x06, // Length
2338            0x01, // Unit ID
2339            0x03, // FC
2340            0x00, 0x01, 0x00, 0x02, // Data
2341        ];
2342        let msg = decompile_adu_frame(&frame, TransportType::StdTcp)
2343            .expect("Should decode valid TCP frame");
2344        assert_eq!(msg.function_code(), FunctionCode::ReadHoldingRegisters);
2345        if let AdditionalAddress::MbapHeader(header) = msg.additional_address {
2346            assert_eq!(header.transaction_id, 0x1234);
2347        } else {
2348            panic!("Expected MbapHeader");
2349        }
2350    }
2351
2352    #[test]
2353    fn test_decompile_adu_frame_tcp_invalid() {
2354        let frame = [0x00]; // Too short
2355        let err = decompile_adu_frame(&frame, TransportType::StdTcp).expect_err("Should fail");
2356        assert_eq!(err, MbusError::InvalidAduLength);
2357    }
2358
2359    #[test]
2360    fn test_decompile_adu_frame_rtu_valid() {
2361        // Frame: 01 03 00 6B 00 03 74 17 (CRC LE)
2362        let frame = [0x01, 0x03, 0x00, 0x6B, 0x00, 0x03, 0x74, 0x17];
2363        let msg = decompile_adu_frame(&frame, TransportType::StdSerial(SerialMode::Rtu))
2364            .expect("Valid RTU");
2365        assert_eq!(msg.function_code(), FunctionCode::ReadHoldingRegisters);
2366    }
2367
2368    #[test]
2369    fn test_decompile_adu_frame_rtu_too_short() {
2370        let frame = [0x01, 0x02, 0x03];
2371        let err = decompile_adu_frame(&frame, TransportType::StdSerial(SerialMode::Rtu))
2372            .expect_err("Too short");
2373        assert_eq!(err, MbusError::InvalidAduLength);
2374    }
2375
2376    #[test]
2377    fn test_decompile_adu_frame_rtu_crc_mismatch() {
2378        let frame = [0x01, 0x03, 0x00, 0x6B, 0x00, 0x03, 0x00, 0x00]; // Bad CRC
2379        let err = decompile_adu_frame(&frame, TransportType::StdSerial(SerialMode::Rtu))
2380            .expect_err("CRC mismatch");
2381        assert_eq!(err, MbusError::ChecksumError);
2382    }
2383
2384    #[test]
2385    fn test_decompile_adu_frame_ascii_valid() {
2386        // :010300000001FB\r\n
2387        let frame = b":010300000001FB\r\n";
2388        let msg = decompile_adu_frame(frame, TransportType::StdSerial(SerialMode::Ascii))
2389            .expect("Valid ASCII");
2390        assert_eq!(msg.function_code(), FunctionCode::ReadHoldingRegisters);
2391    }
2392
2393    #[test]
2394    fn test_decompile_adu_frame_ascii_too_short() {
2395        let frame = b":123\r\n";
2396        let err = decompile_adu_frame(frame, TransportType::StdSerial(SerialMode::Ascii))
2397            .expect_err("Too short");
2398        assert_eq!(err, MbusError::InvalidAduLength);
2399    }
2400
2401    #[test]
2402    fn test_decompile_adu_frame_ascii_missing_start() {
2403        let frame = b"010300000001FB\r\n";
2404        let err = decompile_adu_frame(frame, TransportType::StdSerial(SerialMode::Ascii))
2405            .expect_err("Missing start");
2406        assert_eq!(err, MbusError::BasicParseError);
2407    }
2408
2409    #[test]
2410    fn test_decompile_adu_frame_ascii_missing_end() {
2411        let frame = b":010300000001FB\r"; // Missing \n
2412        let err = decompile_adu_frame(frame, TransportType::StdSerial(SerialMode::Ascii))
2413            .expect_err("Missing end");
2414        assert_eq!(err, MbusError::BasicParseError);
2415    }
2416
2417    #[test]
2418    fn test_decompile_adu_frame_ascii_odd_hex() {
2419        let frame = b":010300000001F\r\n"; // Odd length hex
2420        let err = decompile_adu_frame(frame, TransportType::StdSerial(SerialMode::Ascii))
2421            .expect_err("Odd hex");
2422        assert_eq!(err, MbusError::BasicParseError);
2423    }
2424
2425    #[test]
2426    fn test_decompile_adu_frame_ascii_lrc_mismatch() {
2427        let frame = b":01030000000100\r\n"; // LRC 00 is wrong, should be FB
2428        let err = decompile_adu_frame(frame, TransportType::StdSerial(SerialMode::Ascii))
2429            .expect_err("LRC mismatch");
2430        assert_eq!(err, MbusError::ChecksumError);
2431    }
2432
2433    #[test]
2434    fn test_decompile_adu_frame_ascii_buffer_overflow() {
2435        // Construct a frame that decodes to 261 bytes.
2436        let mut frame = Vec::<u8, 600>::new();
2437        frame.push(b':').unwrap();
2438        for _ in 0..261 {
2439            frame.extend_from_slice(b"00").unwrap();
2440        }
2441        frame.extend_from_slice(b"\r\n").unwrap();
2442        let err = decompile_adu_frame(&frame, TransportType::StdSerial(SerialMode::Ascii))
2443            .expect_err("Buffer overflow");
2444        assert_eq!(err, MbusError::BufferTooSmall);
2445    }
2446
2447    // --- Tests for derive_length_from_bytes ---
2448
2449    #[test]
2450    fn test_derive_length_tcp() {
2451        // TCP frame requires minimum 6 bytes to read the length offset.
2452        let short_frame = [0x00, 0x01, 0x00, 0x00, 0x00];
2453        assert_eq!(
2454            derive_length_from_bytes(&short_frame, TransportType::StdTcp),
2455            None
2456        );
2457
2458        // TCP MBAP: TID(2) PID(2) LEN(2) = 0x0006. Total length should be 6 + 6 = 12.
2459        let full_frame = [
2460            0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, 0x01,
2461        ];
2462        assert_eq!(
2463            derive_length_from_bytes(&full_frame, TransportType::StdTcp),
2464            Some(12)
2465        );
2466
2467        // TCP MBAP with invalid Protocol ID should return usize::MAX to trigger garbage disposal.
2468        let garbage_frame = [
2469            0x00, 0x01, 0xAA, 0xBB, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, 0x01,
2470        ];
2471        assert_eq!(
2472            derive_length_from_bytes(&garbage_frame, TransportType::StdTcp),
2473            Some(usize::MAX)
2474        );
2475    }
2476
2477    #[test]
2478    fn test_derive_length_rtu_fixed() {
2479        // FC 5 (Write Single Coil) is uniformly 8 bytes for requests and responses.
2480        let request = [0x01, 0x05, 0x00, 0x0A, 0xFF, 0x00, 0x00, 0x00];
2481        assert_eq!(
2482            derive_length_from_bytes(&request, TransportType::StdSerial(SerialMode::Rtu)),
2483            Some(8)
2484        );
2485    }
2486
2487    #[test]
2488    fn test_derive_length_rtu_dynamic() {
2489        // Read Holding Registers Response (FC 03)
2490        // Schema: Address(1) + FC(03) + ByteCount(2) + Data(0x12, 0x34) + CRC(2)
2491        let mut resp = [0x01, 0x03, 0x02, 0x12, 0x34, 0x00, 0x00];
2492        let crc = checksum::crc16(&resp[..5]);
2493        let crc_bytes = crc.to_le_bytes();
2494        resp[5] = crc_bytes[0];
2495        resp[6] = crc_bytes[1];
2496
2497        // Opportunistic CRC should instantly lock on an exact length of 7.
2498        assert_eq!(
2499            derive_length_from_bytes(&resp, TransportType::StdSerial(SerialMode::Rtu)),
2500            Some(7)
2501        );
2502
2503        // A partial frame (4 bytes) should predict up to 8 (max candidate comparison logic).
2504        assert_eq!(
2505            derive_length_from_bytes(&resp[..4], TransportType::StdSerial(SerialMode::Rtu)),
2506            Some(8)
2507        );
2508    }
2509
2510    #[test]
2511    fn test_derive_length_rtu_exception() {
2512        // Exception responses are universally 5 bytes.
2513        let exception = [0x01, 0x81, 0x02, 0x00, 0x00];
2514        assert_eq!(
2515            derive_length_from_bytes(&exception, TransportType::StdSerial(SerialMode::Rtu)),
2516            Some(5)
2517        );
2518    }
2519
2520    #[test]
2521    fn test_derive_length_rtu_forward_scan() {
2522        // Build a frame with unknown/custom Function Code (e.g. 0x44)
2523        let mut custom_frame = [0x01, 0x44, 0xAA, 0xBB, 0x00, 0x00];
2524        let crc = checksum::crc16(&custom_frame[..4]);
2525        let crc_bytes = crc.to_le_bytes();
2526        custom_frame[4] = crc_bytes[0];
2527        custom_frame[5] = crc_bytes[1];
2528
2529        assert_eq!(
2530            derive_length_from_bytes(&custom_frame, TransportType::StdSerial(SerialMode::Rtu)),
2531            Some(6)
2532        );
2533
2534        // Without the valid CRC, it will continuously scan forward but yield None if unmatched.
2535        assert_eq!(
2536            derive_length_from_bytes(
2537                &custom_frame[..4],
2538                TransportType::StdSerial(SerialMode::Rtu)
2539            ),
2540            None
2541        );
2542    }
2543
2544    #[test]
2545    fn test_derive_length_ascii() {
2546        let frame = b":010300000001FB\r\n";
2547        assert_eq!(
2548            derive_length_from_bytes(frame, TransportType::StdSerial(SerialMode::Ascii)),
2549            Some(17)
2550        );
2551
2552        let partial = b":010300000001F";
2553        assert_eq!(
2554            derive_length_from_bytes(partial, TransportType::StdSerial(SerialMode::Ascii)),
2555            None
2556        );
2557    }
2558}