Skip to main content

mbus_client/services/
mod.rs

1//! # Modbus Client Services Module
2//!
3//! This module provides the core orchestration logic for a Modbus client. It acts as the
4//! bridge between the high-level application logic and the low-level transport protocols.
5//!
6//! ## Key Components
7//! - [`ClientServices`]: The main entry point for sending Modbus requests. It manages
8//!   transaction state, handles timeouts, and performs automatic retries.
9//! - [`ExpectedResponse`]: A state tracking mechanism that maps outgoing requests to
10//!   incoming responses using Transaction IDs (for TCP) or FIFO ordering (for Serial).
11//! - Sub-services: Specialized modules (coils, registers, etc.) that handle the
12//!   serialization and deserialization of specific Modbus function codes.
13//!
14//! ## Features
15//! - Supports both TCP and Serial (RTU/ASCII) transport types.
16//! - Generic over `TRANSPORT` and `APP` traits for maximum flexibility in different environments.
17//! - Fixed-capacity response tracking using `heapless` for `no_std` compatibility.
18
19#[cfg(feature = "coils")]
20pub mod coil;
21#[cfg(feature = "diagnostics")]
22pub mod diagnostic;
23#[cfg(feature = "discrete-inputs")]
24pub mod discrete_input;
25#[cfg(feature = "fifo")]
26pub mod fifo_queue;
27#[cfg(feature = "file-record")]
28pub mod file_record;
29#[cfg(feature = "registers")]
30pub mod register;
31
32use crate::app::RequestErrorNotifier;
33#[cfg(feature = "diagnostics")]
34use diagnostic::ReadDeviceIdCode;
35use heapless::Vec;
36use mbus_core::data_unit::common::{ModbusMessage, SlaveAddress, derive_length_from_bytes};
37use mbus_core::function_codes::public::EncapsulatedInterfaceType;
38use mbus_core::transport::{UidSaddrFrom, UnitIdOrSlaveAddr};
39use mbus_core::{
40    data_unit::common::{self, MAX_ADU_FRAME_LEN},
41    errors::MbusError,
42    transport::{ModbusConfig, ModbusSerialConfig, TimeKeeper, Transport, TransportType},
43};
44
45#[cfg(feature = "logging")]
46macro_rules! client_log_debug {
47    ($($arg:tt)*) => {
48        log::debug!($($arg)*)
49    };
50}
51
52#[cfg(not(feature = "logging"))]
53macro_rules! client_log_debug {
54    ($($arg:tt)*) => {{
55        let _ = core::format_args!($($arg)*);
56    }};
57}
58
59#[cfg(feature = "logging")]
60macro_rules! client_log_trace {
61    ($($arg:tt)*) => {
62        log::trace!($($arg)*)
63    };
64}
65
66#[cfg(not(feature = "logging"))]
67macro_rules! client_log_trace {
68    ($($arg:tt)*) => {{
69        let _ = core::format_args!($($arg)*);
70    }};
71}
72
73type ResponseHandler<T, A, const N: usize> =
74    fn(&mut ClientServices<T, A, N>, &ExpectedResponse<T, A, N>, &ModbusMessage);
75
76// Compile-time marker: only `[(); 1]` implements this trait.
77#[doc(hidden)]
78pub trait SerialQueueSizeOne {}
79impl SerialQueueSizeOne for [(); 1] {}
80
81/// Convenience alias for serial clients where queue size is always one.
82pub type SerialClientServices<TRANSPORT, APP> = ClientServices<TRANSPORT, APP, 1>;
83
84/// Feature-scoped coils API facade.
85///
86/// This view keeps coil operations grouped under `client.coils()` while reusing the same
87/// underlying `ClientServices` state.
88#[cfg(feature = "coils")]
89pub struct CoilsApi<'a, TRANSPORT, APP, const N: usize> {
90    client: &'a mut ClientServices<TRANSPORT, APP, N>,
91}
92
93#[cfg(feature = "coils")]
94impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
95where
96    TRANSPORT: Transport,
97    APP: ClientCommon + crate::app::CoilResponse,
98{
99    /// Returns a feature-scoped coils facade.
100    pub fn coils(&mut self) -> CoilsApi<'_, TRANSPORT, APP, N> {
101        CoilsApi { client: self }
102    }
103
104    /// Executes multiple coil requests in a single scoped borrow.
105    pub fn with_coils<R>(
106        &mut self,
107        f: impl FnOnce(&mut CoilsApi<'_, TRANSPORT, APP, N>) -> R,
108    ) -> R {
109        let mut api = self.coils();
110        f(&mut api)
111    }
112}
113
114#[cfg(feature = "coils")]
115impl<TRANSPORT, APP, const N: usize> CoilsApi<'_, TRANSPORT, APP, N>
116where
117    TRANSPORT: Transport,
118    APP: ClientCommon + crate::app::CoilResponse,
119{
120    /// Forwards to `ClientServices::read_multiple_coils`.
121    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
122    pub fn read_multiple_coils(
123        &mut self,
124        txn_id: u16,
125        unit_id_slave_addr: UnitIdOrSlaveAddr,
126        address: u16,
127        quantity: u16,
128    ) -> Result<(), MbusError> {
129        self.client
130            .read_multiple_coils(txn_id, unit_id_slave_addr, address, quantity)
131    }
132
133    /// Forwards to `ClientServices::read_single_coil`.
134    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
135    pub fn read_single_coil(
136        &mut self,
137        txn_id: u16,
138        unit_id_slave_addr: UnitIdOrSlaveAddr,
139        address: u16,
140    ) -> Result<(), MbusError> {
141        self.client
142            .read_single_coil(txn_id, unit_id_slave_addr, address)
143    }
144
145    /// Forwards to `ClientServices::write_single_coil`.
146    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
147    pub fn write_single_coil(
148        &mut self,
149        txn_id: u16,
150        unit_id_slave_addr: UnitIdOrSlaveAddr,
151        address: u16,
152        value: bool,
153    ) -> Result<(), MbusError> {
154        self.client
155            .write_single_coil(txn_id, unit_id_slave_addr, address, value)
156    }
157
158    /// Forwards to `ClientServices::write_multiple_coils`.
159    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
160    pub fn write_multiple_coils(
161        &mut self,
162        txn_id: u16,
163        unit_id_slave_addr: UnitIdOrSlaveAddr,
164        address: u16,
165        values: &crate::services::coil::Coils,
166    ) -> Result<(), MbusError> {
167        self.client
168            .write_multiple_coils(txn_id, unit_id_slave_addr, address, values)
169    }
170}
171
172/// Feature-scoped discrete-inputs API facade.
173#[cfg(feature = "discrete-inputs")]
174pub struct DiscreteInputsApi<'a, TRANSPORT, APP, const N: usize> {
175    client: &'a mut ClientServices<TRANSPORT, APP, N>,
176}
177
178#[cfg(feature = "discrete-inputs")]
179impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
180where
181    TRANSPORT: Transport,
182    APP: ClientCommon + crate::app::DiscreteInputResponse,
183{
184    /// Returns a feature-scoped discrete-inputs facade.
185    pub fn discrete_inputs(&mut self) -> DiscreteInputsApi<'_, TRANSPORT, APP, N> {
186        DiscreteInputsApi { client: self }
187    }
188
189    /// Executes multiple discrete-input requests in a single scoped borrow.
190    pub fn with_discrete_inputs<R>(
191        &mut self,
192        f: impl FnOnce(&mut DiscreteInputsApi<'_, TRANSPORT, APP, N>) -> R,
193    ) -> R {
194        let mut api = self.discrete_inputs();
195        f(&mut api)
196    }
197}
198
199#[cfg(feature = "discrete-inputs")]
200impl<TRANSPORT, APP, const N: usize> DiscreteInputsApi<'_, TRANSPORT, APP, N>
201where
202    TRANSPORT: Transport,
203    APP: ClientCommon + crate::app::DiscreteInputResponse,
204{
205    /// Forwards to `ClientServices::read_discrete_inputs`.
206    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
207    pub fn read_discrete_inputs(
208        &mut self,
209        txn_id: u16,
210        unit_id_slave_addr: UnitIdOrSlaveAddr,
211        address: u16,
212        quantity: u16,
213    ) -> Result<(), MbusError> {
214        self.client
215            .read_discrete_inputs(txn_id, unit_id_slave_addr, address, quantity)
216    }
217
218    /// Forwards to `ClientServices::read_single_discrete_input`.
219    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
220    pub fn read_single_discrete_input(
221        &mut self,
222        txn_id: u16,
223        unit_id_slave_addr: UnitIdOrSlaveAddr,
224        address: u16,
225    ) -> Result<(), MbusError> {
226        self.client
227            .read_single_discrete_input(txn_id, unit_id_slave_addr, address)
228    }
229}
230
231/// Feature-scoped registers API facade.
232#[cfg(feature = "registers")]
233pub struct RegistersApi<'a, TRANSPORT, APP, const N: usize> {
234    client: &'a mut ClientServices<TRANSPORT, APP, N>,
235}
236
237#[cfg(feature = "registers")]
238impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
239where
240    TRANSPORT: Transport,
241    APP: ClientCommon + crate::app::RegisterResponse,
242{
243    /// Returns a feature-scoped registers facade.
244    pub fn registers(&mut self) -> RegistersApi<'_, TRANSPORT, APP, N> {
245        RegistersApi { client: self }
246    }
247
248    /// Executes multiple register requests in a single scoped borrow.
249    pub fn with_registers<R>(
250        &mut self,
251        f: impl FnOnce(&mut RegistersApi<'_, TRANSPORT, APP, N>) -> R,
252    ) -> R {
253        let mut api = self.registers();
254        f(&mut api)
255    }
256}
257
258#[cfg(feature = "registers")]
259impl<TRANSPORT, APP, const N: usize> RegistersApi<'_, TRANSPORT, APP, N>
260where
261    TRANSPORT: Transport,
262    APP: ClientCommon + crate::app::RegisterResponse,
263{
264    /// Forwards to `ClientServices::read_holding_registers`.
265    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
266    pub fn read_holding_registers(
267        &mut self,
268        txn_id: u16,
269        unit_id_slave_addr: UnitIdOrSlaveAddr,
270        from_address: u16,
271        quantity: u16,
272    ) -> Result<(), MbusError> {
273        self.client
274            .read_holding_registers(txn_id, unit_id_slave_addr, from_address, quantity)
275    }
276
277    /// Forwards to `ClientServices::read_single_holding_register`.
278    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
279    pub fn read_single_holding_register(
280        &mut self,
281        txn_id: u16,
282        unit_id_slave_addr: UnitIdOrSlaveAddr,
283        address: u16,
284    ) -> Result<(), MbusError> {
285        self.client
286            .read_single_holding_register(txn_id, unit_id_slave_addr, address)
287    }
288
289    /// Forwards to `ClientServices::read_input_registers`.
290    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
291    pub fn read_input_registers(
292        &mut self,
293        txn_id: u16,
294        unit_id_slave_addr: UnitIdOrSlaveAddr,
295        address: u16,
296        quantity: u16,
297    ) -> Result<(), MbusError> {
298        self.client
299            .read_input_registers(txn_id, unit_id_slave_addr, address, quantity)
300    }
301
302    /// Forwards to `ClientServices::read_single_input_register`.
303    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
304    pub fn read_single_input_register(
305        &mut self,
306        txn_id: u16,
307        unit_id_slave_addr: UnitIdOrSlaveAddr,
308        address: u16,
309    ) -> Result<(), MbusError> {
310        self.client
311            .read_single_input_register(txn_id, unit_id_slave_addr, address)
312    }
313
314    /// Forwards to `ClientServices::write_single_register`.
315    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
316    pub fn write_single_register(
317        &mut self,
318        txn_id: u16,
319        unit_id_slave_addr: UnitIdOrSlaveAddr,
320        address: u16,
321        value: u16,
322    ) -> Result<(), MbusError> {
323        self.client
324            .write_single_register(txn_id, unit_id_slave_addr, address, value)
325    }
326
327    /// Forwards to `ClientServices::write_multiple_registers`.
328    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
329    pub fn write_multiple_registers(
330        &mut self,
331        txn_id: u16,
332        unit_id_slave_addr: UnitIdOrSlaveAddr,
333        address: u16,
334        quantity: u16,
335        values: &[u16],
336    ) -> Result<(), MbusError> {
337        self.client
338            .write_multiple_registers(txn_id, unit_id_slave_addr, address, quantity, values)
339    }
340
341    /// Forwards to `ClientServices::read_write_multiple_registers`.
342    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
343    pub fn read_write_multiple_registers(
344        &mut self,
345        txn_id: u16,
346        unit_id_slave_addr: UnitIdOrSlaveAddr,
347        read_address: u16,
348        read_quantity: u16,
349        write_address: u16,
350        write_values: &[u16],
351    ) -> Result<(), MbusError> {
352        self.client.read_write_multiple_registers(
353            txn_id,
354            unit_id_slave_addr,
355            read_address,
356            read_quantity,
357            write_address,
358            write_values,
359        )
360    }
361
362    /// Forwards to `ClientServices::mask_write_register`.
363    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
364    pub fn mask_write_register(
365        &mut self,
366        txn_id: u16,
367        unit_id_slave_addr: UnitIdOrSlaveAddr,
368        address: u16,
369        and_mask: u16,
370        or_mask: u16,
371    ) -> Result<(), MbusError> {
372        self.client
373            .mask_write_register(txn_id, unit_id_slave_addr, address, and_mask, or_mask)
374    }
375}
376
377/// Feature-scoped diagnostics API facade.
378#[cfg(feature = "diagnostics")]
379pub struct DiagnosticApi<'a, TRANSPORT, APP, const N: usize> {
380    client: &'a mut ClientServices<TRANSPORT, APP, N>,
381}
382
383#[cfg(feature = "diagnostics")]
384impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
385where
386    TRANSPORT: Transport,
387    APP: ClientCommon + crate::app::DiagnosticsResponse,
388{
389    /// Returns a feature-scoped diagnostics facade.
390    pub fn diagnostic(&mut self) -> DiagnosticApi<'_, TRANSPORT, APP, N> {
391        DiagnosticApi { client: self }
392    }
393
394    /// Executes multiple diagnostic requests in a single scoped borrow.
395    pub fn with_diagnostic<R>(
396        &mut self,
397        f: impl FnOnce(&mut DiagnosticApi<'_, TRANSPORT, APP, N>) -> R,
398    ) -> R {
399        let mut api = self.diagnostic();
400        f(&mut api)
401    }
402}
403
404#[cfg(feature = "diagnostics")]
405impl<TRANSPORT, APP, const N: usize> DiagnosticApi<'_, TRANSPORT, APP, N>
406where
407    TRANSPORT: Transport,
408    APP: ClientCommon + crate::app::DiagnosticsResponse,
409{
410    /// Forwards to `ClientServices::read_device_identification`.
411    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
412    pub fn read_device_identification(
413        &mut self,
414        txn_id: u16,
415        unit_id_slave_addr: UnitIdOrSlaveAddr,
416        read_device_id_code: crate::services::diagnostic::ReadDeviceIdCode,
417        object_id: crate::services::diagnostic::ObjectId,
418    ) -> Result<(), MbusError> {
419        self.client.read_device_identification(
420            txn_id,
421            unit_id_slave_addr,
422            read_device_id_code,
423            object_id,
424        )
425    }
426
427    /// Forwards to `ClientServices::encapsulated_interface_transport`.
428    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
429    pub fn encapsulated_interface_transport(
430        &mut self,
431        txn_id: u16,
432        unit_id_slave_addr: UnitIdOrSlaveAddr,
433        mei_type: EncapsulatedInterfaceType,
434        data: &[u8],
435    ) -> Result<(), MbusError> {
436        self.client
437            .encapsulated_interface_transport(txn_id, unit_id_slave_addr, mei_type, data)
438    }
439
440    /// Forwards to `ClientServices::read_exception_status`.
441    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
442    pub fn read_exception_status(
443        &mut self,
444        txn_id: u16,
445        unit_id_slave_addr: UnitIdOrSlaveAddr,
446    ) -> Result<(), MbusError> {
447        self.client
448            .read_exception_status(txn_id, unit_id_slave_addr)
449    }
450
451    /// Forwards to `ClientServices::diagnostics`.
452    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
453    pub fn diagnostics(
454        &mut self,
455        txn_id: u16,
456        unit_id_slave_addr: UnitIdOrSlaveAddr,
457        sub_function: mbus_core::function_codes::public::DiagnosticSubFunction,
458        data: &[u16],
459    ) -> Result<(), MbusError> {
460        self.client
461            .diagnostics(txn_id, unit_id_slave_addr, sub_function, data)
462    }
463
464    /// Forwards to `ClientServices::get_comm_event_counter`.
465    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
466    pub fn get_comm_event_counter(
467        &mut self,
468        txn_id: u16,
469        unit_id_slave_addr: UnitIdOrSlaveAddr,
470    ) -> Result<(), MbusError> {
471        self.client
472            .get_comm_event_counter(txn_id, unit_id_slave_addr)
473    }
474
475    /// Forwards to `ClientServices::get_comm_event_log`.
476    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
477    pub fn get_comm_event_log(
478        &mut self,
479        txn_id: u16,
480        unit_id_slave_addr: UnitIdOrSlaveAddr,
481    ) -> Result<(), MbusError> {
482        self.client.get_comm_event_log(txn_id, unit_id_slave_addr)
483    }
484
485    /// Forwards to `ClientServices::report_server_id`.
486    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
487    pub fn report_server_id(
488        &mut self,
489        txn_id: u16,
490        unit_id_slave_addr: UnitIdOrSlaveAddr,
491    ) -> Result<(), MbusError> {
492        self.client.report_server_id(txn_id, unit_id_slave_addr)
493    }
494}
495
496/// Feature-scoped FIFO API facade.
497#[cfg(feature = "fifo")]
498pub struct FifoApi<'a, TRANSPORT, APP, const N: usize> {
499    client: &'a mut ClientServices<TRANSPORT, APP, N>,
500}
501
502#[cfg(feature = "fifo")]
503impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
504where
505    TRANSPORT: Transport,
506    APP: ClientCommon + crate::app::FifoQueueResponse,
507{
508    /// Returns a feature-scoped FIFO facade.
509    pub fn fifo(&mut self) -> FifoApi<'_, TRANSPORT, APP, N> {
510        FifoApi { client: self }
511    }
512
513    /// Executes multiple FIFO requests in a single scoped borrow.
514    pub fn with_fifo<R>(&mut self, f: impl FnOnce(&mut FifoApi<'_, TRANSPORT, APP, N>) -> R) -> R {
515        let mut api = self.fifo();
516        f(&mut api)
517    }
518}
519
520#[cfg(feature = "fifo")]
521impl<TRANSPORT, APP, const N: usize> FifoApi<'_, TRANSPORT, APP, N>
522where
523    TRANSPORT: Transport,
524    APP: ClientCommon + crate::app::FifoQueueResponse,
525{
526    /// Forwards to `ClientServices::read_fifo_queue`.
527    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
528    pub fn read_fifo_queue(
529        &mut self,
530        txn_id: u16,
531        unit_id_slave_addr: UnitIdOrSlaveAddr,
532        address: u16,
533    ) -> Result<(), MbusError> {
534        self.client
535            .read_fifo_queue(txn_id, unit_id_slave_addr, address)
536    }
537}
538
539/// Feature-scoped file-record API facade.
540#[cfg(feature = "file-record")]
541pub struct FileRecordsApi<'a, TRANSPORT, APP, const N: usize> {
542    client: &'a mut ClientServices<TRANSPORT, APP, N>,
543}
544
545#[cfg(feature = "file-record")]
546impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
547where
548    TRANSPORT: Transport,
549    APP: ClientCommon + crate::app::FileRecordResponse,
550{
551    /// Returns a feature-scoped file-record facade.
552    pub fn file_records(&mut self) -> FileRecordsApi<'_, TRANSPORT, APP, N> {
553        FileRecordsApi { client: self }
554    }
555
556    /// Executes multiple file-record requests in a single scoped borrow.
557    pub fn with_file_records<R>(
558        &mut self,
559        f: impl FnOnce(&mut FileRecordsApi<'_, TRANSPORT, APP, N>) -> R,
560    ) -> R {
561        let mut api = self.file_records();
562        f(&mut api)
563    }
564}
565
566#[cfg(feature = "file-record")]
567impl<TRANSPORT, APP, const N: usize> FileRecordsApi<'_, TRANSPORT, APP, N>
568where
569    TRANSPORT: Transport,
570    APP: ClientCommon + crate::app::FileRecordResponse,
571{
572    /// Forwards to `ClientServices::read_file_record`.
573    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
574    pub fn read_file_record(
575        &mut self,
576        txn_id: u16,
577        unit_id_slave_addr: UnitIdOrSlaveAddr,
578        sub_request: &crate::services::file_record::SubRequest,
579    ) -> Result<(), MbusError> {
580        self.client
581            .read_file_record(txn_id, unit_id_slave_addr, sub_request)
582    }
583
584    /// Forwards to `ClientServices::write_file_record`.
585    #[must_use = "request submission errors should be handled; the request may not have been queued/sent"]
586    pub fn write_file_record(
587        &mut self,
588        txn_id: u16,
589        unit_id_slave_addr: UnitIdOrSlaveAddr,
590        sub_request: &crate::services::file_record::SubRequest,
591    ) -> Result<(), MbusError> {
592        self.client
593            .write_file_record(txn_id, unit_id_slave_addr, sub_request)
594    }
595}
596
597/// Internal tracking payload for a Single-address operation.
598#[derive(Debug, Clone, PartialEq, Eq)]
599pub(crate) struct Single {
600    address: u16,
601    value: u16,
602}
603/// Internal tracking payload for a Multiple-address/quantity operation.
604#[derive(Debug, Clone, PartialEq, Eq)]
605pub(crate) struct Multiple {
606    address: u16,
607    quantity: u16,
608}
609/// Internal tracking payload for a Masking operation.
610#[derive(Debug, Clone, PartialEq, Eq)]
611pub(crate) struct Mask {
612    address: u16,
613    and_mask: u16,
614    or_mask: u16,
615}
616/// Internal tracking payload for a Diagnostic/Encapsulated operation.
617#[cfg(feature = "diagnostics")]
618#[derive(Debug, Clone, PartialEq, Eq)]
619pub(crate) struct Diag {
620    device_id_code: ReadDeviceIdCode,
621    encap_type: EncapsulatedInterfaceType,
622}
623
624/// Metadata required to match responses to requests and properly parse the payload.
625#[derive(Debug, Clone, PartialEq, Eq)]
626pub(crate) enum OperationMeta {
627    Other,
628    Single(Single),
629    Multiple(Multiple),
630    Masking(Mask),
631    #[cfg(feature = "diagnostics")]
632    Diag(Diag),
633}
634
635impl OperationMeta {
636    fn address(&self) -> u16 {
637        match self {
638            OperationMeta::Single(s) => s.address,
639            OperationMeta::Multiple(m) => m.address,
640            OperationMeta::Masking(m) => m.address,
641            _ => 0,
642        }
643    }
644
645    fn value(&self) -> u16 {
646        match self {
647            OperationMeta::Single(s) => s.value,
648            _ => 0,
649        }
650    }
651
652    fn quantity(&self) -> u16 {
653        match self {
654            OperationMeta::Single(_) => 1,
655            OperationMeta::Multiple(m) => m.quantity,
656            _ => 0,
657        }
658    }
659
660    fn and_mask(&self) -> u16 {
661        match self {
662            OperationMeta::Masking(m) => m.and_mask,
663            _ => 0,
664        }
665    }
666
667    fn or_mask(&self) -> u16 {
668        match self {
669            OperationMeta::Masking(m) => m.or_mask,
670            _ => 0,
671        }
672    }
673
674    fn is_single(&self) -> bool {
675        matches!(self, OperationMeta::Single(_))
676    }
677
678    fn single_value(&self) -> u16 {
679        match self {
680            OperationMeta::Single(s) => s.value,
681            _ => 0,
682        }
683    }
684
685    fn device_id_code(&self) -> ReadDeviceIdCode {
686        match self {
687            #[cfg(feature = "diagnostics")]
688            OperationMeta::Diag(d) => d.device_id_code,
689            _ => ReadDeviceIdCode::default(),
690        }
691    }
692
693    fn encap_type(&self) -> EncapsulatedInterfaceType {
694        match self {
695            #[cfg(feature = "diagnostics")]
696            OperationMeta::Diag(d) => d.encap_type,
697            _ => EncapsulatedInterfaceType::default(),
698        }
699    }
700}
701
702/// Represents an outstanding request that the client expects a response for.
703///
704/// # Generic Parameters
705/// * `T` - Transport implementor.
706/// * `A` - Application callbacks implementor.
707/// * `N` - Max concurrent requests supported (Queue capacity).
708#[derive(Debug)]
709pub(crate) struct ExpectedResponse<T, A, const N: usize> {
710    /// The Modbus TCP transaction identifier (0 for serial).
711    pub txn_id: u16,
712    /// The destination Modbus Unit ID or Server Address.
713    pub unit_id_or_slave_addr: u8,
714
715    /// The fully compiled Application Data Unit to be sent over the wire.
716    /// Retained in memory to allow automatic `retries` without recompiling.
717    pub original_adu: Vec<u8, MAX_ADU_FRAME_LEN>,
718
719    /// Time stamp when request is posted
720    pub sent_timestamp: u64,
721    /// The number of retries left for this request.
722    pub retries_left: u8,
723    /// Number of retries that have already been sent for this request.
724    pub retry_attempt_index: u8,
725    /// Timestamp when the next retry is eligible to be sent.
726    ///
727    /// `None` means there is no retry currently scheduled and the request is waiting
728    /// for a response to the most recent send.
729    pub next_retry_timestamp: Option<u64>,
730
731    /// Pointer to the specific module's parser/handler function for this operation.
732    pub handler: ResponseHandler<T, A, N>,
733
734    /// Modbus memory context (address/quantity) needed to validate the response.
735    pub operation_meta: OperationMeta,
736}
737
738/// Core client services struct that manages the application logic, transport layer, and
739/// expected responses for Modbus communication.
740/// This is Main entry point for client operations, providing methods to send requests and process responses.
741///
742/// # Type Parameters
743///
744/// * `TRANSPORT` - The transport layer implementation (e.g., TCP or RTU) that handles the physical transmission of Modbus frames.
745/// * `N` - The maximum number of concurrent outstanding requests (capacity of the expected responses queue).
746///   - For TCP, `N` can be > 1 for pipelining.
747///   - For Serial, `N` must be 1 because Modbus serial is half-duplex and supports only one in-flight request.
748/// * `APP` - The application layer that handles processed Modbus responses.
749#[derive(Debug)]
750pub struct ClientServices<TRANSPORT, APP, const N: usize = 1> {
751    /// Application layer that implements the CoilResponse trait, used to handle responses and invoke callbacks.
752    app: APP,
753    /// Transport layer used for sending and receiving Modbus frames. Must implement the Transport trait.
754    transport: TRANSPORT,
755
756    /// Configuration for the modbus client
757    config: ModbusConfig,
758
759    /// A buffer to store the received frame.
760    rxed_frame: Vec<u8, MAX_ADU_FRAME_LEN>,
761
762    /// Heapless circular buffer representing the pipelined requests awaiting responses.
763    expected_responses: Vec<ExpectedResponse<TRANSPORT, APP, N>, N>,
764
765    /// Cached timestamp of the earliest expected response timeout to avoid O(N) iteration on every poll.
766    next_timeout_check: Option<u64>,
767}
768
769/// A marker trait that aggregates the necessary capabilities for a Modbus client application.
770///
771/// Any type implementing `ClientCommon` must be able to:
772/// 1. **Notify** the application when a Modbus request fails ([`RequestErrorNotifier`]).
773/// 2. **Provide** monotonic time in milliseconds to manage timeouts and retries ([`TimeKeeper`]).
774///
775/// This trait simplifies the generic bounds used throughout the `ClientServices` implementation.
776pub trait ClientCommon: RequestErrorNotifier + TimeKeeper {}
777
778impl<T> ClientCommon for T where T: RequestErrorNotifier + TimeKeeper {}
779
780impl<T, APP, const N: usize> ClientServices<T, APP, N>
781where
782    T: Transport,
783    APP: ClientCommon,
784{
785    fn dispatch_response(&mut self, message: &ModbusMessage) {
786        let wire_txn_id = message.transaction_id();
787        let unit_id_or_slave_addr = message.unit_id_or_slave_addr();
788
789        let index = if self.transport.transport_type().is_tcp_type() {
790            self.expected_responses.iter().position(|r| {
791                r.txn_id == wire_txn_id && r.unit_id_or_slave_addr == unit_id_or_slave_addr.into()
792            })
793        } else {
794            self.expected_responses
795                .iter()
796                .position(|r| r.unit_id_or_slave_addr == unit_id_or_slave_addr.into())
797        };
798
799        let expected = match index {
800            // Deliberately use O(1) removal. Request matching uses txn/unit id,
801            // so stable queue order is not required for correctness.
802            Some(i) => self.expected_responses.swap_remove(i),
803            None => {
804                client_log_debug!(
805                    "dropping unmatched response: txn_id={}, unit_id_or_slave_addr={}",
806                    wire_txn_id,
807                    unit_id_or_slave_addr.get()
808                );
809                return;
810            }
811        };
812
813        let request_txn_id = expected.txn_id;
814
815        client_log_trace!(
816            "dispatching response: txn_id={}, unit_id_or_slave_addr={}, queue_len_after_pop={}",
817            request_txn_id,
818            unit_id_or_slave_addr.get(),
819            self.expected_responses.len()
820        );
821
822        // If the Modbus server replied with an exception, notify the application layer
823        // immediately instead of attempting to parse it as a successful response.
824        if let Some(exception_code) = message.pdu().error_code() {
825            client_log_debug!(
826                "modbus exception response: txn_id={}, unit_id_or_slave_addr={}, code=0x{:02X}",
827                request_txn_id,
828                unit_id_or_slave_addr.get(),
829                exception_code
830            );
831            self.app.request_failed(
832                request_txn_id,
833                unit_id_or_slave_addr,
834                MbusError::ModbusException(exception_code),
835            );
836            return;
837        }
838
839        (expected.handler)(self, &expected, message);
840    }
841}
842
843impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
844where
845    TRANSPORT: Transport,
846    TRANSPORT::Error: Into<MbusError>,
847    APP: RequestErrorNotifier + TimeKeeper,
848{
849    /// The main execution loop for the Modbus Client.
850    ///
851    /// This method orchestrates the entire lifecycle of Modbus transactions by performing
852    /// three critical tasks in a non-blocking manner:
853    ///
854    /// ### 1. Data Ingestion & Stream Resynchronization
855    /// It pulls raw bytes from the `TRANSPORT` layer into an internal `rxed_frame` buffer.
856    /// Because Modbus streams (especially Serial) can contain noise or fragmented packets,
857    /// the logic handles:
858    /// * **Fragmentation**: If a partial frame is received, it stays in the buffer until more data arrives.
859    /// * **Pipelining**: If multiple ADUs are received in a single TCP packet, it processes them sequentially.
860    /// * **Noise Recovery**: If the buffer contains garbage that doesn't form a valid Modbus header,
861    ///   it drops bytes one-by-one to "slide" the window and find the next valid start-of-frame.
862    ///
863    /// ### 2. Response Dispatching
864    /// Once a complete ADU is validated (via checksums in RTU or length checks in TCP), it is
865    /// decompiled into a `ModbusMessage`. The client then:
866    /// * Matches the response to an `ExpectedResponse` using the **Transaction ID** (TCP)
867    ///   or **Unit ID/Slave Address** (Serial, where only one request is active at a time).
868    /// * Validates the Function Code and handles Modbus Exceptions (0x80 + FC).
869    /// * Routes the payload to the specific `handler` (e.g., `handle_read_coils_rsp`) which
870    ///   ultimately triggers the user-defined callback in the `APP` layer.
871    ///
872    /// ### 3. Timeout & Retry Management
873    /// The client maintains a queue of "Outstanding Requests". For every poll:
874    /// * It checks if the `current_millis` (provided by `APP`) has exceeded the `sent_timestamp`
875    ///   plus the configured `response_timeout_ms`.
876    /// * **Scheduled Retries**: If a timeout occurs and `retries_left > 0`, the next retry is
877    ///   scheduled using the configured backoff strategy (and optional jitter).
878    /// * Scheduled retries are only sent when the poll loop reaches or passes the scheduled
879    ///   retry timestamp. The client never sleeps or blocks internally.
880    /// * **Connection Loss Handling**: If `recv()` reports a connection-level transport error
881    ///   (or transport reports disconnected state), all pending requests are immediately failed
882    ///   with `MbusError::ConnectionLost` and removed from the queue.
883    /// * **Failure Notification**: If all retries are exhausted, the request is dropped from
884    ///   the queue, and `app.request_failed` is called with `MbusError::NoRetriesLeft`.
885    ///
886    /// ### Performance Note
887    /// This method uses a `next_timeout_check` cache. If the earliest possible timeout is in
888    /// the future, it skips the O(N) scan of the expected responses queue, making it
889    /// highly efficient for high-concurrency TCP scenarios.
890    ///
891    /// # Constraints
892    /// * For **Serial** transports, the queue size `N` **must** be 1 (1 is default) to comply with the
893    ///   half-duplex nature of RS-485/RS-232.
894    /// * For **TCP**, `N` can be larger to support request pipelining.
895    pub fn poll(&mut self) {
896        // 1. Attempt to receive a frame
897        match self.transport.recv() {
898            Ok(frame) => {
899                client_log_trace!("received {} transport bytes", frame.len());
900                if self.rxed_frame.extend_from_slice(frame.as_slice()).is_err() {
901                    // Buffer overflowed without forming a valid frame. Must be noise.
902                    client_log_debug!(
903                        "received frame buffer overflow while appending {} bytes; clearing receive buffer",
904                        frame.len()
905                    );
906                    self.rxed_frame.clear();
907                }
908
909                // Process as many pipelined/concatenated frames as exist in the buffer
910                while !self.rxed_frame.is_empty() {
911                    match self.ingest_frame() {
912                        Ok(consumed) => {
913                            client_log_trace!(
914                                "ingested complete frame consuming {} bytes from rx buffer len {}",
915                                consumed,
916                                self.rxed_frame.len()
917                            );
918                            let len = self.rxed_frame.len();
919                            if consumed < len {
920                                // Shift array to the left to drain processed bytes.
921                                self.rxed_frame.copy_within(consumed.., 0);
922                                self.rxed_frame.truncate(len - consumed);
923                            } else {
924                                self.rxed_frame.clear();
925                            }
926                        }
927                        Err(MbusError::BufferTooSmall) => {
928                            // Reached an incomplete frame, break and wait for more bytes
929                            client_log_trace!(
930                                "incomplete frame in rx buffer; waiting for more bytes (buffer_len={})",
931                                self.rxed_frame.len()
932                            );
933                            break;
934                        }
935                        Err(err) => {
936                            // Garbage or parsing error, drop the first byte and try again to resync the stream
937                            client_log_debug!(
938                                "frame parse/resync event: error={:?}, buffer_len={}; dropping 1 byte",
939                                err,
940                                self.rxed_frame.len()
941                            );
942                            let len = self.rxed_frame.len();
943                            if len > 1 {
944                                self.rxed_frame.copy_within(1.., 0);
945                                self.rxed_frame.truncate(len - 1);
946                            } else {
947                                self.rxed_frame.clear();
948                            }
949                        }
950                    }
951                }
952            }
953            Err(err) => {
954                let recv_error: MbusError = err.into();
955                let is_connection_loss = matches!(
956                    recv_error,
957                    MbusError::ConnectionClosed
958                        | MbusError::ConnectionFailed
959                        | MbusError::ConnectionLost
960                        | MbusError::IoError
961                ) || !self.transport.is_connected();
962
963                if is_connection_loss {
964                    client_log_debug!(
965                        "connection loss detected during poll: error={:?}, pending_requests={}",
966                        recv_error,
967                        self.expected_responses.len()
968                    );
969                    self.fail_all_pending_requests(MbusError::ConnectionLost);
970                    let _ = self.transport.disconnect();
971                    self.rxed_frame.clear();
972                } else {
973                    client_log_trace!("non-fatal recv status during poll: {:?}", recv_error);
974                }
975            }
976        }
977
978        // 2. Check for timed-out requests and handle retries for all outstanding requests
979        self.handle_timeouts();
980    }
981
982    fn fail_all_pending_requests(&mut self, error: MbusError) {
983        let pending_count = self.expected_responses.len();
984        client_log_debug!(
985            "failing {} pending request(s) with error {:?}",
986            pending_count,
987            error
988        );
989        while let Some(response) = self.expected_responses.pop() {
990            self.app.request_failed(
991                response.txn_id,
992                UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
993                error,
994            );
995        }
996        self.next_timeout_check = None;
997    }
998
999    /// Evaluates all pending requests to determine if any have exceeded their response timeout.
1000    ///
1001    /// This method is designed to be efficient:
1002    /// 1. It immediately returns if there are no pending requests.
1003    /// 2. It utilizes a fast-path cache (`next_timeout_check`) to skip an O(N) linear scan if the nearest
1004    ///    timeout in the future hasn't been reached yet.
1005    /// 3. If the cache expires, it iterates linearly over `expected_responses` to check the `sent_timestamp`
1006    ///    against `current_millis`.
1007    /// 4. If a request is timed out and has retries remaining, it schedules a retry timestamp based on
1008    ///    the configured backoff strategy, and optionally applies jitter using an application-provided callback.
1009    /// 5. When the scheduled retry timestamp is reached, it retransmits the original ADU. If the re-send fails,
1010    ///    it is dropped and reported as `SendFailed`.
1011    /// 6. If no retries remain, the request is removed from the pending queue and `NoRetriesLeft` is reported.
1012    /// 7. Finally, it recalculates the `next_timeout_check` state to schedule the next evaluation interval.
1013    fn handle_timeouts(&mut self) {
1014        if self.expected_responses.is_empty() {
1015            self.next_timeout_check = None;
1016            return;
1017        }
1018
1019        let current_millis = self.app.current_millis();
1020
1021        // Fast-path: Skip O(N) iteration if the earliest timeout has not yet been reached
1022        if let Some(check_at) = self.next_timeout_check
1023            && current_millis < check_at
1024        {
1025            client_log_trace!(
1026                "skipping timeout scan until {}, current_millis={}",
1027                check_at,
1028                current_millis
1029            );
1030            return;
1031        }
1032
1033        let response_timeout_ms = self.response_timeout_ms();
1034        let retry_backoff = self.config.retry_backoff_strategy();
1035        let retry_jitter = self.config.retry_jitter_strategy();
1036        let retry_random_fn = self.config.retry_random_fn();
1037        let expected_responses = &mut self.expected_responses;
1038        let mut i = 0;
1039        let mut new_next_check = u64::MAX;
1040
1041        while i < expected_responses.len() {
1042            let expected_response = &mut expected_responses[i];
1043            // First, process already-scheduled retries.
1044            if let Some(retry_at) = expected_response.next_retry_timestamp {
1045                if current_millis >= retry_at {
1046                    client_log_debug!(
1047                        "retry due now: txn_id={}, unit_id_or_slave_addr={}, retry_attempt_index={}, retries_left={}",
1048                        expected_response.txn_id,
1049                        expected_response.unit_id_or_slave_addr,
1050                        expected_response.retry_attempt_index.saturating_add(1),
1051                        expected_response.retries_left
1052                    );
1053                    if let Err(_e) = self.transport.send(&expected_response.original_adu) {
1054                        // Deliberately O(1): response identity is carried in the payload,
1055                        // not by queue position, so preserving insertion order is unnecessary.
1056                        let response = expected_responses.swap_remove(i);
1057                        client_log_debug!(
1058                            "retry send failed: txn_id={}, unit_id_or_slave_addr={}; dropping request",
1059                            response.txn_id,
1060                            response.unit_id_or_slave_addr
1061                        );
1062                        self.app.request_failed(
1063                            response.txn_id,
1064                            UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
1065                            MbusError::SendFailed,
1066                        );
1067                        continue;
1068                    }
1069
1070                    expected_response.retries_left =
1071                        expected_response.retries_left.saturating_sub(1);
1072                    expected_response.retry_attempt_index =
1073                        expected_response.retry_attempt_index.saturating_add(1);
1074                    expected_response.sent_timestamp = current_millis;
1075                    expected_response.next_retry_timestamp = None;
1076
1077                    let expires_at = current_millis.saturating_add(response_timeout_ms);
1078                    if expires_at < new_next_check {
1079                        new_next_check = expires_at;
1080                    }
1081                    i += 1;
1082                    continue;
1083                }
1084
1085                if retry_at < new_next_check {
1086                    new_next_check = retry_at;
1087                }
1088                i += 1;
1089                continue;
1090            }
1091
1092            // Otherwise, the request is waiting for a response to a previous send.
1093            let expires_at = expected_response
1094                .sent_timestamp
1095                .saturating_add(response_timeout_ms);
1096
1097            if current_millis > expires_at {
1098                if expected_response.retries_left == 0 {
1099                    // Deliberately O(1): timeout handling keys off txn/unit id and
1100                    // does not rely on stable ordering inside expected_responses.
1101                    let response = expected_responses.swap_remove(i);
1102                    client_log_debug!(
1103                        "request exhausted retries: txn_id={}, unit_id_or_slave_addr={}",
1104                        response.txn_id,
1105                        response.unit_id_or_slave_addr
1106                    );
1107                    self.app.request_failed(
1108                        response.txn_id,
1109                        UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
1110                        MbusError::NoRetriesLeft,
1111                    );
1112                    continue;
1113                }
1114
1115                let next_attempt = expected_response.retry_attempt_index.saturating_add(1);
1116                let base_delay_ms = retry_backoff.delay_ms_for_retry(next_attempt);
1117                let retry_delay_ms = retry_jitter.apply(base_delay_ms, retry_random_fn) as u64;
1118                let retry_at = current_millis.saturating_add(retry_delay_ms);
1119                expected_response.next_retry_timestamp = Some(retry_at);
1120                client_log_debug!(
1121                    "scheduling retry: txn_id={}, unit_id_or_slave_addr={}, next_attempt={}, delay_ms={}, retry_at={}",
1122                    expected_response.txn_id,
1123                    expected_response.unit_id_or_slave_addr,
1124                    next_attempt,
1125                    retry_delay_ms,
1126                    retry_at
1127                );
1128
1129                // If delay is zero (Immediate strategy), process the newly scheduled retry
1130                // in this same poll cycle without waiting for another call to `poll`.
1131                if retry_delay_ms == 0 {
1132                    client_log_trace!(
1133                        "retry delay is zero; retry will be processed in the same poll cycle for txn_id={}",
1134                        expected_response.txn_id
1135                    );
1136                    continue;
1137                }
1138
1139                if retry_at < new_next_check {
1140                    new_next_check = retry_at;
1141                }
1142                i += 1;
1143                continue;
1144            }
1145
1146            if expires_at < new_next_check {
1147                new_next_check = expires_at;
1148            }
1149            i += 1;
1150        }
1151
1152        if new_next_check != u64::MAX {
1153            self.next_timeout_check = Some(new_next_check);
1154        } else {
1155            self.next_timeout_check = None;
1156        }
1157    }
1158
1159    fn add_an_expectation(
1160        &mut self,
1161        txn_id: u16,
1162        unit_id_slave_addr: UnitIdOrSlaveAddr,
1163        frame: &heapless::Vec<u8, MAX_ADU_FRAME_LEN>,
1164        operation_meta: OperationMeta,
1165        handler: ResponseHandler<TRANSPORT, APP, N>,
1166    ) -> Result<(), MbusError> {
1167        client_log_trace!(
1168            "queueing expected response: txn_id={}, unit_id_or_slave_addr={}, queue_len_before={}",
1169            txn_id,
1170            unit_id_slave_addr.get(),
1171            self.expected_responses.len()
1172        );
1173        self.expected_responses
1174            .push(ExpectedResponse {
1175                txn_id,
1176                unit_id_or_slave_addr: unit_id_slave_addr.get(),
1177                original_adu: frame.clone(),
1178                sent_timestamp: self.app.current_millis(),
1179                retries_left: self.retry_attempts(),
1180                retry_attempt_index: 0,
1181                next_retry_timestamp: None,
1182                handler,
1183                operation_meta,
1184            })
1185            .map_err(|_| MbusError::TooManyRequests)?;
1186        Ok(())
1187    }
1188}
1189
1190/// Implementation of core client services, including methods for sending requests and processing responses.
1191impl<TRANSPORT: Transport, APP: ClientCommon, const N: usize> ClientServices<TRANSPORT, APP, N> {
1192    /// Creates a new instance of ClientServices, connecting to the transport layer with the provided configuration.
1193    pub fn new(
1194        mut transport: TRANSPORT,
1195        app: APP,
1196        config: ModbusConfig,
1197    ) -> Result<Self, MbusError> {
1198        let transport_type = transport.transport_type();
1199        if matches!(
1200            transport_type,
1201            TransportType::StdSerial(_) | TransportType::CustomSerial(_)
1202        ) && N != 1
1203        {
1204            return Err(MbusError::InvalidNumOfExpectedRsps);
1205        }
1206
1207        transport
1208            .connect(&config)
1209            .map_err(|_e| MbusError::ConnectionFailed)?;
1210
1211        client_log_debug!(
1212            "client created with transport_type={:?}, queue_capacity={}",
1213            transport_type,
1214            N
1215        );
1216
1217        Ok(Self {
1218            app,
1219            transport,
1220            rxed_frame: Vec::new(),
1221            config,
1222            expected_responses: Vec::new(),
1223            next_timeout_check: None,
1224        })
1225    }
1226
1227    /// Returns an immutable reference to the application callback handler.
1228    ///
1229    /// This allows observers/tests to inspect application-owned state while keeping
1230    /// the handler instance stable for in-flight requests.
1231    pub fn app(&self) -> &APP {
1232        &self.app
1233    }
1234
1235    /// Returns whether the underlying transport currently considers itself connected.
1236    pub fn is_connected(&self) -> bool {
1237        self.transport.is_connected()
1238    }
1239
1240    /// Re-establishes the underlying transport connection using the existing configuration.
1241    ///
1242    /// Behavior:
1243    /// - Drops all currently pending in-flight requests and reports them as
1244    ///   `MbusError::ConnectionLost`.
1245    /// - Clears any partially received frame bytes.
1246    /// - Calls `transport.disconnect()` (best-effort) followed by `transport.connect(&self.config)`.
1247    ///
1248    /// This method does not automatically re-send dropped requests. The application can requeue
1249    /// requests explicitly after reconnection succeeds.
1250    pub fn reconnect(&mut self) -> Result<(), MbusError>
1251    where
1252        TRANSPORT::Error: Into<MbusError>,
1253    {
1254        client_log_debug!(
1255            "reconnect requested; pending_requests={}",
1256            self.expected_responses.len()
1257        );
1258        self.fail_all_pending_requests(MbusError::ConnectionLost);
1259        self.rxed_frame.clear();
1260        self.next_timeout_check = None;
1261
1262        let _ = self.transport.disconnect();
1263        self.transport.connect(&self.config).map_err(|e| e.into())
1264    }
1265
1266    /// Creates a serial client with a compile-time enforced queue size of exactly 1.
1267    ///
1268    /// This constructor exists to make the serial half-duplex constraint fail at compile time
1269    /// instead of runtime. Any attempt to call this function with `N != 1` fails trait-bound
1270    /// resolution during compilation.
1271    ///
1272    /// Use this constructor when building serial RTU/ASCII clients and prefer
1273    /// [`SerialClientServices`] as the type alias for readability.
1274    pub fn new_serial(
1275        mut transport: TRANSPORT,
1276        app: APP,
1277        config: ModbusSerialConfig,
1278    ) -> Result<Self, MbusError>
1279    where
1280        [(); N]: SerialQueueSizeOne,
1281    {
1282        let transport_type = transport.transport_type();
1283        if !matches!(
1284            transport_type,
1285            TransportType::StdSerial(_) | TransportType::CustomSerial(_)
1286        ) {
1287            return Err(MbusError::InvalidTransport);
1288        }
1289
1290        let config = ModbusConfig::Serial(config);
1291        transport
1292            .connect(&config)
1293            .map_err(|_e| MbusError::ConnectionFailed)?;
1294
1295        client_log_debug!("serial client created with queue_capacity={}", N);
1296
1297        Ok(Self {
1298            app,
1299            transport,
1300            rxed_frame: Vec::new(),
1301            config,
1302            expected_responses: Vec::new(),
1303            next_timeout_check: None,
1304        })
1305    }
1306
1307    /// Returns the configured response timeout in milliseconds.
1308    fn response_timeout_ms(&self) -> u64 {
1309        match &self.config {
1310            ModbusConfig::Tcp(config) => config.response_timeout_ms as u64,
1311            ModbusConfig::Serial(config) => config.response_timeout_ms as u64,
1312        }
1313    }
1314
1315    /// Returns the configured number of retries for outstanding requests.
1316    fn retry_attempts(&self) -> u8 {
1317        match &self.config {
1318            ModbusConfig::Tcp(config) => config.retry_attempts,
1319            ModbusConfig::Serial(config) => config.retry_attempts,
1320        }
1321    }
1322
1323    /// Ingests received Modbus frames from the transport layer.
1324    fn ingest_frame(&mut self) -> Result<usize, MbusError> {
1325        let frame = self.rxed_frame.as_slice();
1326        let transport_type = self.transport.transport_type();
1327
1328        client_log_trace!(
1329            "attempting frame ingest: transport_type={:?}, buffer_len={}",
1330            transport_type,
1331            frame.len()
1332        );
1333
1334        let expected_length = match derive_length_from_bytes(frame, transport_type) {
1335            Some(len) => len,
1336            None => return Err(MbusError::BufferTooSmall),
1337        };
1338
1339        client_log_trace!("derived expected frame length={}", expected_length);
1340
1341        if expected_length > MAX_ADU_FRAME_LEN {
1342            client_log_debug!(
1343                "derived frame length {} exceeds MAX_ADU_FRAME_LEN {}",
1344                expected_length,
1345                MAX_ADU_FRAME_LEN
1346            );
1347            return Err(MbusError::BasicParseError);
1348        }
1349
1350        if self.rxed_frame.len() < expected_length {
1351            return Err(MbusError::BufferTooSmall);
1352        }
1353
1354        let message = match common::decompile_adu_frame(&frame[..expected_length], transport_type) {
1355            Ok(value) => value,
1356            Err(err) => {
1357                client_log_debug!(
1358                    "decompile_adu_frame failed for {} bytes: {:?}",
1359                    expected_length,
1360                    err
1361                );
1362                return Err(err); // Malformed frame or parsing error, frame is dropped.
1363            }
1364        };
1365        use mbus_core::data_unit::common::AdditionalAddress;
1366        use mbus_core::transport::TransportType::*;
1367        let message = match self.transport.transport_type() {
1368            StdTcp | CustomTcp => {
1369                let mbap_header = match message.additional_address() {
1370                    AdditionalAddress::MbapHeader(header) => header,
1371                    _ => return Ok(expected_length),
1372                };
1373                let additional_addr = AdditionalAddress::MbapHeader(*mbap_header);
1374                ModbusMessage::new(additional_addr, message.pdu)
1375            }
1376            StdSerial(_) | CustomSerial(_) => {
1377                let slave_addr = match message.additional_address() {
1378                    AdditionalAddress::SlaveAddress(addr) => addr.address(),
1379                    _ => return Ok(expected_length),
1380                };
1381
1382                let additional_address =
1383                    AdditionalAddress::SlaveAddress(SlaveAddress::new(slave_addr)?);
1384                ModbusMessage::new(additional_address, message.pdu)
1385            }
1386        };
1387
1388        self.dispatch_response(&message);
1389        client_log_trace!("frame dispatch complete for {} bytes", expected_length);
1390
1391        Ok(expected_length)
1392    }
1393}
1394
1395#[cfg(test)]
1396mod tests {
1397    use super::*;
1398    use crate::app::CoilResponse;
1399    use crate::app::DiagnosticsResponse;
1400    use crate::app::DiscreteInputResponse;
1401    use crate::app::FifoQueueResponse;
1402    use crate::app::FileRecordResponse;
1403    use crate::app::RegisterResponse;
1404    use crate::services::coil::Coils;
1405
1406    use crate::services::diagnostic::ConformityLevel;
1407    use crate::services::diagnostic::DeviceIdentificationResponse;
1408    use crate::services::diagnostic::ObjectId;
1409    use crate::services::discrete_input::DiscreteInputs;
1410    use crate::services::fifo_queue::FifoQueue;
1411    use crate::services::file_record::MAX_SUB_REQUESTS_PER_PDU;
1412    use crate::services::file_record::SubRequest;
1413    use crate::services::file_record::SubRequestParams;
1414    use crate::services::register::Registers;
1415    use core::cell::RefCell; // `core::cell::RefCell` is `no_std` compatible
1416    use core::str::FromStr;
1417    use heapless::Deque;
1418    use heapless::Vec;
1419    use mbus_core::errors::MbusError;
1420    use mbus_core::function_codes::public::DiagnosticSubFunction;
1421    use mbus_core::transport::TransportType;
1422    use mbus_core::transport::checksum;
1423    use mbus_core::transport::{
1424        BackoffStrategy, BaudRate, JitterStrategy, ModbusConfig, ModbusSerialConfig,
1425        ModbusTcpConfig, Parity, SerialMode,
1426    };
1427
1428    const MOCK_DEQUE_CAPACITY: usize = 10; // Define a capacity for the mock deques
1429
1430    fn rand_zero() -> u32 {
1431        0
1432    }
1433
1434    fn rand_upper_percent_20() -> u32 {
1435        40
1436    }
1437
1438    fn make_serial_config() -> ModbusSerialConfig {
1439        ModbusSerialConfig {
1440            port_path: heapless::String::<64>::from_str("/dev/ttyUSB0").unwrap(),
1441            mode: SerialMode::Rtu,
1442            baud_rate: BaudRate::Baud19200,
1443            data_bits: mbus_core::transport::DataBits::Eight,
1444            stop_bits: 1,
1445            parity: Parity::Even,
1446            response_timeout_ms: 100,
1447            retry_attempts: 0,
1448            retry_backoff_strategy: BackoffStrategy::Immediate,
1449            retry_jitter_strategy: JitterStrategy::None,
1450            retry_random_fn: None,
1451        }
1452    }
1453
1454    fn make_serial_client() -> ClientServices<MockTransport, MockApp, 1> {
1455        let transport = MockTransport {
1456            transport_type: Some(TransportType::StdSerial(SerialMode::Rtu)),
1457            ..Default::default()
1458        };
1459        let app = MockApp::default();
1460        ClientServices::<MockTransport, MockApp, 1>::new_serial(
1461            transport,
1462            app,
1463            make_serial_config(),
1464        )
1465        .unwrap()
1466    }
1467
1468    fn make_rtu_exception_adu(
1469        unit_id: UnitIdOrSlaveAddr,
1470        function_code: u8,
1471        exception_code: u8,
1472    ) -> Vec<u8, MAX_ADU_FRAME_LEN> {
1473        let mut frame = Vec::new();
1474        frame.push(unit_id.get()).unwrap();
1475        frame.push(function_code | 0x80).unwrap();
1476        frame.push(exception_code).unwrap();
1477        let crc = checksum::crc16(frame.as_slice()).to_le_bytes();
1478        frame.extend_from_slice(&crc).unwrap();
1479        frame
1480    }
1481
1482    // --- Mock Transport Implementation ---
1483    #[derive(Debug, Default)]
1484    struct MockTransport {
1485        pub sent_frames: RefCell<Deque<Vec<u8, MAX_ADU_FRAME_LEN>, MOCK_DEQUE_CAPACITY>>, // Changed to heapless::Deque
1486        pub recv_frames: RefCell<Deque<Vec<u8, MAX_ADU_FRAME_LEN>, MOCK_DEQUE_CAPACITY>>, // Changed to heapless::Deque
1487        pub recv_error: RefCell<Option<MbusError>>,
1488        pub connect_should_fail: bool,
1489        pub send_should_fail: bool,
1490        pub is_connected_flag: RefCell<bool>,
1491        pub transport_type: Option<TransportType>,
1492    }
1493
1494    impl Transport for MockTransport {
1495        type Error = MbusError;
1496
1497        fn connect(&mut self, _config: &ModbusConfig) -> Result<(), Self::Error> {
1498            if self.connect_should_fail {
1499                return Err(MbusError::ConnectionFailed);
1500            }
1501            *self.is_connected_flag.borrow_mut() = true;
1502            Ok(())
1503        }
1504
1505        fn disconnect(&mut self) -> Result<(), Self::Error> {
1506            *self.is_connected_flag.borrow_mut() = false;
1507            Ok(())
1508        }
1509
1510        fn send(&mut self, adu: &[u8]) -> Result<(), Self::Error> {
1511            if self.send_should_fail {
1512                return Err(MbusError::SendFailed);
1513            }
1514            let mut vec_adu = Vec::new();
1515            vec_adu
1516                .extend_from_slice(adu)
1517                .map_err(|_| MbusError::BufferLenMissmatch)?;
1518            self.sent_frames
1519                .borrow_mut()
1520                .push_back(vec_adu)
1521                .map_err(|_| MbusError::BufferLenMissmatch)?;
1522            Ok(())
1523        }
1524
1525        fn recv(&mut self) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, Self::Error> {
1526            if let Some(err) = self.recv_error.borrow_mut().take() {
1527                return Err(err);
1528            }
1529            self.recv_frames
1530                .borrow_mut()
1531                .pop_front()
1532                .ok_or(MbusError::Timeout)
1533        }
1534
1535        fn is_connected(&self) -> bool {
1536            *self.is_connected_flag.borrow()
1537        }
1538
1539        fn transport_type(&self) -> TransportType {
1540            self.transport_type.unwrap_or(TransportType::StdTcp)
1541        }
1542    }
1543
1544    // --- Mock App Implementation ---
1545    #[derive(Debug, Default)]
1546    struct MockApp {
1547        pub received_coil_responses: RefCell<Vec<(u16, UnitIdOrSlaveAddr, Coils), 10>>, // Corrected duplicate
1548        pub received_write_single_coil_responses:
1549            RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, bool), 10>>,
1550        pub received_write_multiple_coils_responses:
1551            RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, u16), 10>>,
1552        pub received_discrete_input_responses:
1553            RefCell<Vec<(u16, UnitIdOrSlaveAddr, DiscreteInputs, u16), 10>>,
1554        pub received_holding_register_responses:
1555            RefCell<Vec<(u16, UnitIdOrSlaveAddr, Registers, u16), 10>>,
1556        pub received_input_register_responses:
1557            RefCell<Vec<(u16, UnitIdOrSlaveAddr, Registers, u16), 10>>,
1558        pub received_write_single_register_responses:
1559            RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, u16), 10>>,
1560        pub received_write_multiple_register_responses:
1561            RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, u16), 10>>,
1562        pub received_read_write_multiple_registers_responses:
1563            RefCell<Vec<(u16, UnitIdOrSlaveAddr, Registers), 10>>,
1564        pub received_mask_write_register_responses: RefCell<Vec<(u16, UnitIdOrSlaveAddr), 10>>,
1565        pub received_read_fifo_queue_responses:
1566            RefCell<Vec<(u16, UnitIdOrSlaveAddr, FifoQueue), 10>>,
1567        pub received_read_file_record_responses: RefCell<
1568            Vec<
1569                (
1570                    u16,
1571                    UnitIdOrSlaveAddr,
1572                    Vec<SubRequestParams, MAX_SUB_REQUESTS_PER_PDU>,
1573                ),
1574                10,
1575            >,
1576        >,
1577        pub received_write_file_record_responses: RefCell<Vec<(u16, UnitIdOrSlaveAddr), 10>>,
1578        pub received_read_device_id_responses:
1579            RefCell<Vec<(u16, UnitIdOrSlaveAddr, DeviceIdentificationResponse), 10>>,
1580        pub failed_requests: RefCell<Vec<(u16, UnitIdOrSlaveAddr, MbusError), 10>>,
1581
1582        pub current_time: RefCell<u64>, // For simulating time in tests
1583    }
1584
1585    impl CoilResponse for MockApp {
1586        fn read_coils_response(
1587            &mut self,
1588            txn_id: u16,
1589            unit_id_slave_addr: UnitIdOrSlaveAddr,
1590            coils: &Coils,
1591        ) {
1592            self.received_coil_responses
1593                .borrow_mut()
1594                .push((txn_id, unit_id_slave_addr, coils.clone()))
1595                .unwrap();
1596        }
1597
1598        fn read_single_coil_response(
1599            &mut self,
1600            txn_id: u16,
1601            unit_id_slave_addr: UnitIdOrSlaveAddr,
1602            address: u16,
1603            value: bool,
1604        ) {
1605            // For single coil, we create a Coils struct with quantity 1 and the single value
1606            let mut values_vec = [0x00, 1];
1607            values_vec[0] = if value { 0x01 } else { 0x00 }; // Store the single bit in a byte
1608            let coils = Coils::new(address, 1)
1609                .unwrap()
1610                .with_values(&values_vec, 1)
1611                .unwrap();
1612            self.received_coil_responses
1613                .borrow_mut()
1614                .push((txn_id, unit_id_slave_addr, coils))
1615                .unwrap();
1616        }
1617
1618        fn write_single_coil_response(
1619            &mut self,
1620            txn_id: u16,
1621            unit_id_slave_addr: UnitIdOrSlaveAddr,
1622            address: u16,
1623            value: bool,
1624        ) {
1625            self.received_write_single_coil_responses
1626                .borrow_mut()
1627                .push((txn_id, unit_id_slave_addr, address, value))
1628                .unwrap();
1629        }
1630
1631        fn write_multiple_coils_response(
1632            &mut self,
1633            txn_id: u16,
1634            unit_id_slave_addr: UnitIdOrSlaveAddr,
1635            address: u16,
1636            quantity: u16,
1637        ) {
1638            self.received_write_multiple_coils_responses
1639                .borrow_mut()
1640                .push((txn_id, unit_id_slave_addr, address, quantity))
1641                .unwrap();
1642        }
1643    }
1644
1645    impl DiscreteInputResponse for MockApp {
1646        fn read_multiple_discrete_inputs_response(
1647            &mut self,
1648            txn_id: u16,
1649            unit_id_slave_addr: UnitIdOrSlaveAddr,
1650            inputs: &DiscreteInputs,
1651        ) {
1652            self.received_discrete_input_responses
1653                .borrow_mut()
1654                .push((
1655                    txn_id,
1656                    unit_id_slave_addr,
1657                    inputs.clone(),
1658                    inputs.quantity(),
1659                ))
1660                .unwrap();
1661        }
1662
1663        fn read_single_discrete_input_response(
1664            &mut self,
1665            txn_id: u16,
1666            unit_id_slave_addr: UnitIdOrSlaveAddr,
1667            address: u16,
1668            value: bool,
1669        ) {
1670            let mut values = [0u8; mbus_core::models::discrete_input::MAX_DISCRETE_INPUT_BYTES];
1671            values[0] = if value { 0x01 } else { 0x00 };
1672            let inputs = DiscreteInputs::new(address, 1)
1673                .unwrap()
1674                .with_values(&values, 1)
1675                .unwrap();
1676            self.received_discrete_input_responses
1677                .borrow_mut()
1678                .push((txn_id, unit_id_slave_addr, inputs, 1))
1679                .unwrap();
1680        }
1681    }
1682
1683    impl RequestErrorNotifier for MockApp {
1684        fn request_failed(
1685            &mut self,
1686            txn_id: u16,
1687            unit_id_slave_addr: UnitIdOrSlaveAddr,
1688            error: MbusError,
1689        ) {
1690            self.failed_requests
1691                .borrow_mut()
1692                .push((txn_id, unit_id_slave_addr, error))
1693                .unwrap();
1694        }
1695    }
1696
1697    impl RegisterResponse for MockApp {
1698        fn read_multiple_holding_registers_response(
1699            &mut self,
1700            txn_id: u16,
1701            unit_id_slave_addr: UnitIdOrSlaveAddr,
1702            registers: &Registers,
1703        ) {
1704            let quantity = registers.quantity();
1705            self.received_holding_register_responses
1706                .borrow_mut()
1707                .push((txn_id, unit_id_slave_addr, registers.clone(), quantity))
1708                .unwrap();
1709        }
1710
1711        fn read_single_input_register_response(
1712            &mut self,
1713            txn_id: u16,
1714            unit_id_slave_addr: UnitIdOrSlaveAddr,
1715            address: u16,
1716            value: u16,
1717        ) {
1718            // Create a temporary slice to load the single register value
1719            let values = [value];
1720            let registers = Registers::new(address, 1)
1721                .unwrap()
1722                .with_values(&values, 1)
1723                .unwrap();
1724            self.received_input_register_responses
1725                .borrow_mut()
1726                .push((txn_id, unit_id_slave_addr, registers, 1))
1727                .unwrap();
1728        }
1729
1730        fn read_single_holding_register_response(
1731            &mut self,
1732            txn_id: u16,
1733            unit_id_slave_addr: UnitIdOrSlaveAddr,
1734            address: u16,
1735            value: u16,
1736        ) {
1737            // Create a temporary slice to load the single register value
1738            let data = [value];
1739            // Initialize Registers with default capacity (MAX_REGISTERS_PER_PDU)
1740            let registers = Registers::new(address, 1)
1741                .unwrap()
1742                .with_values(&data, 1)
1743                .unwrap();
1744
1745            self.received_holding_register_responses
1746                .borrow_mut()
1747                .push((txn_id, unit_id_slave_addr, registers, 1))
1748                .unwrap();
1749        }
1750
1751        fn read_multiple_input_registers_response(
1752            &mut self,
1753            txn_id: u16,
1754            unit_id_slave_addr: UnitIdOrSlaveAddr,
1755            registers: &Registers,
1756        ) {
1757            let quantity = registers.quantity();
1758            self.received_input_register_responses
1759                .borrow_mut()
1760                .push((txn_id, unit_id_slave_addr, registers.clone(), quantity))
1761                .unwrap();
1762        }
1763
1764        fn write_single_register_response(
1765            &mut self,
1766            txn_id: u16,
1767            unit_id_slave_addr: UnitIdOrSlaveAddr,
1768            address: u16,
1769            value: u16,
1770        ) {
1771            self.received_write_single_register_responses
1772                .borrow_mut()
1773                .push((txn_id, unit_id_slave_addr, address, value))
1774                .unwrap();
1775        }
1776
1777        fn write_multiple_registers_response(
1778            &mut self,
1779            txn_id: u16,
1780            unit_id_slave_addr: UnitIdOrSlaveAddr,
1781            address: u16,
1782            quantity: u16,
1783        ) {
1784            self.received_write_multiple_register_responses
1785                .borrow_mut()
1786                .push((txn_id, unit_id_slave_addr, address, quantity))
1787                .unwrap();
1788        }
1789
1790        fn read_write_multiple_registers_response(
1791            &mut self,
1792            txn_id: u16,
1793            unit_id_slave_addr: UnitIdOrSlaveAddr,
1794            registers: &Registers,
1795        ) {
1796            self.received_read_write_multiple_registers_responses
1797                .borrow_mut()
1798                .push((txn_id, unit_id_slave_addr, registers.clone()))
1799                .unwrap();
1800        }
1801
1802        fn mask_write_register_response(
1803            &mut self,
1804            txn_id: u16,
1805            unit_id_slave_addr: UnitIdOrSlaveAddr,
1806        ) {
1807            self.received_mask_write_register_responses
1808                .borrow_mut()
1809                .push((txn_id, unit_id_slave_addr))
1810                .unwrap();
1811        }
1812
1813        fn read_single_register_response(
1814            &mut self,
1815            txn_id: u16,
1816            unit_id_slave_addr: UnitIdOrSlaveAddr,
1817            address: u16,
1818            value: u16,
1819        ) {
1820            // Create a temporary slice to load the single register value
1821            let data = [value];
1822            // Initialize Registers with default capacity (MAX_REGISTERS_PER_PDU)
1823            let registers = Registers::new(address, 1)
1824                .unwrap()
1825                .with_values(&data, 1)
1826                .unwrap();
1827
1828            self.received_holding_register_responses
1829                .borrow_mut()
1830                .push((txn_id, unit_id_slave_addr, registers, 1))
1831                .unwrap();
1832        }
1833    }
1834
1835    impl FifoQueueResponse for MockApp {
1836        fn read_fifo_queue_response(
1837            &mut self,
1838            txn_id: u16,
1839            unit_id_slave_addr: UnitIdOrSlaveAddr,
1840            fifo_queue: &FifoQueue,
1841        ) {
1842            self.received_read_fifo_queue_responses
1843                .borrow_mut()
1844                .push((txn_id, unit_id_slave_addr, fifo_queue.clone()))
1845                .unwrap();
1846        }
1847    }
1848
1849    impl FileRecordResponse for MockApp {
1850        fn read_file_record_response(
1851            &mut self,
1852            txn_id: u16,
1853            unit_id_slave_addr: UnitIdOrSlaveAddr,
1854            data: &[SubRequestParams],
1855        ) {
1856            let mut vec = Vec::new();
1857            vec.extend_from_slice(data).unwrap();
1858            self.received_read_file_record_responses
1859                .borrow_mut()
1860                .push((txn_id, unit_id_slave_addr, vec))
1861                .unwrap();
1862        }
1863        fn write_file_record_response(
1864            &mut self,
1865            txn_id: u16,
1866            unit_id_slave_addr: UnitIdOrSlaveAddr,
1867        ) {
1868            self.received_write_file_record_responses
1869                .borrow_mut()
1870                .push((txn_id, unit_id_slave_addr))
1871                .unwrap();
1872        }
1873    }
1874
1875    impl DiagnosticsResponse for MockApp {
1876        fn read_device_identification_response(
1877            &mut self,
1878            txn_id: u16,
1879            unit_id_slave_addr: UnitIdOrSlaveAddr,
1880            response: &DeviceIdentificationResponse,
1881        ) {
1882            self.received_read_device_id_responses
1883                .borrow_mut()
1884                .push((txn_id, unit_id_slave_addr, response.clone()))
1885                .unwrap();
1886        }
1887
1888        fn encapsulated_interface_transport_response(
1889            &mut self,
1890            _: u16,
1891            _: UnitIdOrSlaveAddr,
1892            _: EncapsulatedInterfaceType,
1893            _: &[u8],
1894        ) {
1895        }
1896
1897        fn diagnostics_response(
1898            &mut self,
1899            _: u16,
1900            _: UnitIdOrSlaveAddr,
1901            _: DiagnosticSubFunction,
1902            _: &[u16],
1903        ) {
1904        }
1905
1906        fn get_comm_event_counter_response(
1907            &mut self,
1908            _: u16,
1909            _: UnitIdOrSlaveAddr,
1910            _: u16,
1911            _: u16,
1912        ) {
1913        }
1914
1915        fn get_comm_event_log_response(
1916            &mut self,
1917            _: u16,
1918            _: UnitIdOrSlaveAddr,
1919            _: u16,
1920            _: u16,
1921            _: u16,
1922            _: &[u8],
1923        ) {
1924        }
1925
1926        fn read_exception_status_response(&mut self, _: u16, _: UnitIdOrSlaveAddr, _: u8) {}
1927
1928        fn report_server_id_response(&mut self, _: u16, _: UnitIdOrSlaveAddr, _: &[u8]) {}
1929    }
1930
1931    impl TimeKeeper for MockApp {
1932        fn current_millis(&self) -> u64 {
1933            *self.current_time.borrow()
1934        }
1935    }
1936
1937    // --- ClientServices Tests ---
1938
1939    /// Test case: `ClientServices::new` successfully connects to the transport.
1940    #[test]
1941    fn test_client_services_new_success() {
1942        let transport = MockTransport::default();
1943        let app = MockApp::default();
1944        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
1945
1946        let client_services =
1947            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config);
1948        assert!(client_services.is_ok());
1949        assert!(client_services.unwrap().transport.is_connected());
1950    }
1951
1952    /// Test case: `ClientServices::new` returns an error if transport connection fails.
1953    #[test]
1954    fn test_client_services_new_connection_failure() {
1955        let mut transport = MockTransport::default();
1956        transport.connect_should_fail = true;
1957        let app = MockApp::default();
1958        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
1959
1960        let client_services =
1961            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config);
1962        assert!(client_services.is_err());
1963        assert_eq!(client_services.unwrap_err(), MbusError::ConnectionFailed);
1964    }
1965
1966    #[test]
1967    fn test_client_services_new_serial_success() {
1968        let transport = MockTransport {
1969            transport_type: Some(TransportType::StdSerial(SerialMode::Rtu)),
1970            ..Default::default()
1971        };
1972        let app = MockApp::default();
1973        let serial_config = ModbusSerialConfig {
1974            port_path: heapless::String::<64>::from_str("/dev/ttyUSB0").unwrap(),
1975            mode: SerialMode::Rtu,
1976            baud_rate: BaudRate::Baud19200,
1977            data_bits: mbus_core::transport::DataBits::Eight,
1978            stop_bits: 1,
1979            parity: Parity::Even,
1980            response_timeout_ms: 1000,
1981            retry_attempts: 1,
1982            retry_backoff_strategy: BackoffStrategy::Immediate,
1983            retry_jitter_strategy: JitterStrategy::None,
1984            retry_random_fn: None,
1985        };
1986
1987        let client_services =
1988            ClientServices::<MockTransport, MockApp, 1>::new_serial(transport, app, serial_config);
1989        assert!(client_services.is_ok());
1990    }
1991
1992    #[test]
1993    fn test_reconnect_success_flushes_pending_requests() {
1994        let transport = MockTransport::default();
1995        let app = MockApp::default();
1996        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
1997        let mut client_services =
1998            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
1999
2000        let unit_id = UnitIdOrSlaveAddr::new(1).unwrap();
2001        client_services.read_single_coil(10, unit_id, 0).unwrap();
2002        assert_eq!(client_services.expected_responses.len(), 1);
2003
2004        let reconnect_result = client_services.reconnect();
2005        assert!(reconnect_result.is_ok());
2006        assert!(client_services.is_connected());
2007        assert!(client_services.expected_responses.is_empty());
2008
2009        let failed_requests = client_services.app().failed_requests.borrow();
2010        assert_eq!(failed_requests.len(), 1);
2011        assert_eq!(failed_requests[0].0, 10);
2012        assert_eq!(failed_requests[0].2, MbusError::ConnectionLost);
2013    }
2014
2015    #[test]
2016    fn test_reconnect_failure_propagates_connect_error() {
2017        let transport = MockTransport::default();
2018        let app = MockApp::default();
2019        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2020        let mut client_services =
2021            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2022
2023        client_services.transport.connect_should_fail = true;
2024        let reconnect_result = client_services.reconnect();
2025
2026        assert!(reconnect_result.is_err());
2027        assert_eq!(reconnect_result.unwrap_err(), MbusError::ConnectionFailed);
2028        assert!(!client_services.is_connected());
2029    }
2030
2031    /// Test case: `read_multiple_coils` sends a valid ADU over the transport.
2032    #[test]
2033    fn test_read_multiple_coils_sends_valid_adu() {
2034        let transport = MockTransport::default();
2035        let app = MockApp::default();
2036        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2037        let mut client_services =
2038            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2039
2040        let txn_id = 0x0001;
2041        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2042        let address = 0x0000;
2043        let quantity = 8;
2044        client_services
2045            .read_multiple_coils(txn_id, unit_id, address, quantity)
2046            .unwrap();
2047
2048        let sent_frames = client_services.transport.sent_frames.borrow();
2049        assert_eq!(sent_frames.len(), 1);
2050        let sent_adu = sent_frames.front().unwrap();
2051
2052        // Expected ADU: TID(0x0001), PID(0x0000), Length(0x0006 = Unit ID + FC + Addr + Qty), UnitID(0x01), FC(0x01), Addr(0x0000), Qty(0x0008)
2053        #[rustfmt::skip]
2054        let expected_adu: [u8; 12] = [
2055            0x00, 0x01, // Transaction ID
2056            0x00, 0x00, // Protocol ID
2057            0x00, 0x06, // Length (1 byte Unit ID + 1 byte FC + 2 bytes Address + 2 bytes Quantity = 6)
2058            0x01,       // Unit ID
2059            0x01,       // Function Code (Read Coils)
2060            0x00, 0x00, // Starting Address
2061            0x00, 0x08, // Quantity of Coils
2062        ];
2063        assert_eq!(sent_adu.as_slice(), &expected_adu);
2064    }
2065
2066    /// Test case: `read_multiple_coils` returns an error for an invalid quantity.
2067    #[test]
2068    fn test_read_multiple_coils_invalid_quantity() {
2069        let transport = MockTransport::default();
2070        let app = MockApp::default();
2071        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2072        let mut client_services =
2073            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2074
2075        let txn_id = 0x0001;
2076        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2077        let address = 0x0000;
2078        let quantity = 0; // Invalid quantity
2079
2080        let result = client_services.read_multiple_coils(txn_id, unit_id, address, quantity); // current_millis() is called internally
2081        assert_eq!(result.unwrap_err(), MbusError::InvalidQuantity);
2082    }
2083
2084    /// Test case: `read_multiple_coils` returns an error if sending fails.
2085    #[test]
2086    fn test_read_multiple_coils_send_failure() {
2087        let mut transport = MockTransport::default();
2088        transport.send_should_fail = true;
2089        let app = MockApp::default();
2090        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2091        let mut client_services =
2092            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2093
2094        let txn_id = 0x0001;
2095        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2096        let address = 0x0000;
2097        let quantity = 8;
2098
2099        let result = client_services.read_multiple_coils(txn_id, unit_id, address, quantity); // current_millis() is called internally
2100        assert_eq!(result.unwrap_err(), MbusError::SendFailed);
2101    }
2102
2103    /// Test case: `ingest_frame` ignores responses with wrong function code.
2104    #[test]
2105    fn test_ingest_frame_wrong_fc() {
2106        let transport = MockTransport::default();
2107        let app = MockApp::default();
2108        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2109        let mut client_services =
2110            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2111
2112        // ADU with FC 0x03 (Read Holding Registers) instead of 0x01 (Read Coils)
2113        let response_adu = [0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x03, 0x01, 0xB3];
2114
2115        client_services
2116            .transport
2117            .recv_frames
2118            .borrow_mut()
2119            .push_back(Vec::from_slice(&response_adu).unwrap())
2120            .unwrap();
2121        client_services.poll();
2122
2123        let received_responses = client_services.app().received_coil_responses.borrow();
2124        assert!(received_responses.is_empty());
2125    }
2126
2127    /// Test case: `ingest_frame` ignores malformed ADUs.
2128    #[test]
2129    fn test_ingest_frame_malformed_adu() {
2130        let transport = MockTransport::default();
2131        let app = MockApp::default();
2132        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2133        let mut client_services =
2134            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2135
2136        // Malformed ADU (too short)
2137        let malformed_adu = [0x01, 0x02, 0x03];
2138
2139        client_services
2140            .transport
2141            .recv_frames
2142            .borrow_mut()
2143            .push_back(Vec::from_slice(&malformed_adu).unwrap())
2144            .unwrap();
2145        client_services.poll();
2146
2147        let received_responses = client_services.app().received_coil_responses.borrow();
2148        assert!(received_responses.is_empty());
2149    }
2150
2151    /// Test case: `ingest_frame` ignores responses for unknown transaction IDs.
2152    #[test]
2153    fn test_ingest_frame_unknown_txn_id() {
2154        let transport = MockTransport::default();
2155        let app = MockApp::default();
2156        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2157        let mut client_services =
2158            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2159
2160        // No request was sent, so no expected response is in the queue.
2161        let response_adu = [0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0xB3];
2162
2163        client_services
2164            .transport
2165            .recv_frames
2166            .borrow_mut()
2167            .push_back(Vec::from_slice(&response_adu).unwrap())
2168            .unwrap();
2169        client_services.poll();
2170
2171        let received_responses = client_services.app().received_coil_responses.borrow();
2172        assert!(received_responses.is_empty());
2173    }
2174
2175    /// Test case: `ingest_frame` ignores responses that fail PDU parsing.
2176    #[test]
2177    fn test_ingest_frame_pdu_parse_failure() {
2178        let transport = MockTransport::default();
2179        let app = MockApp::default();
2180        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2181        let mut client_services =
2182            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2183
2184        let txn_id = 0x0001;
2185        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2186        let address = 0x0000;
2187        let quantity = 8;
2188        client_services
2189            .read_multiple_coils(txn_id, unit_id, address, quantity) // current_millis() is called internally
2190            .unwrap();
2191
2192        // Craft a PDU that will cause `parse_read_coils_response` to fail.
2193        // For example, byte count mismatch: PDU indicates 1 byte of data, but provides 2.
2194        // ADU: TID(0x0001), PID(0x0000), Length(0x0005), UnitID(0x01), FC(0x01), Byte Count(0x01), Data(0xB3, 0x00)
2195        let response_adu = [
2196            0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x01, 0x01, 0x01, 0xB3, 0x00,
2197        ]; // Corrected duplicate
2198
2199        client_services
2200            .transport
2201            .recv_frames
2202            .borrow_mut()
2203            .push_back(Vec::from_slice(&response_adu).unwrap())
2204            .unwrap();
2205        client_services.poll();
2206
2207        let received_responses = client_services.app().received_coil_responses.borrow();
2208        assert!(received_responses.is_empty());
2209        // The expected response should still be removed even if PDU parsing fails.
2210        assert!(client_services.expected_responses.is_empty());
2211    }
2212
2213    /// Test case: `ClientServices` successfully sends a Read Single Coil request and processes a valid response.
2214    #[test]
2215    fn test_client_services_read_single_coil_e2e_success() {
2216        let transport = MockTransport::default();
2217        let app = MockApp::default();
2218        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2219        let mut client_services =
2220            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2221
2222        let txn_id = 0x0002;
2223        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2224        let address = 0x0005;
2225
2226        // 1. Send a Read Single Coil request
2227        client_services // current_millis() is called internally
2228            .read_single_coil(txn_id, unit_id, address)
2229            .unwrap();
2230
2231        // Verify that the request was sent via the mock transport
2232        let sent_adu = client_services
2233            .transport
2234            .sent_frames
2235            .borrow_mut()
2236            .pop_front()
2237            .unwrap();
2238        // Expected ADU for Read Coils (FC 0x01) with quantity 1
2239        #[rustfmt::skip]
2240        let expected_adu: [u8; 12] = [
2241            0x00, 0x02, // Transaction ID
2242            0x00, 0x00, // Protocol ID
2243            0x00, 0x06, // Length (Unit ID + FC + Addr + Qty=1)
2244            0x01,       // Unit ID
2245            0x01,       // Function Code (Read Coils)
2246            0x00, 0x05, // Starting Address
2247            0x00, 0x01, // Quantity of Coils (1)
2248        ];
2249        assert_eq!(sent_adu.as_slice(), &expected_adu);
2250
2251        // 2. Manually construct a valid Read Coils response ADU for a single coil
2252        // Response for reading 1 coil at 0x0005, value: true (0x01)
2253        // ADU: TID(0x0002), PID(0x0000), Length(0x0004), UnitID(0x01), FC(0x01), Byte Count(0x01), Coil Data(0x01)
2254        let response_adu = [0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0x01];
2255
2256        // Simulate receiving the frame
2257        client_services
2258            .transport
2259            .recv_frames
2260            .borrow_mut()
2261            .push_back(Vec::from_slice(&response_adu).unwrap())
2262            .unwrap();
2263        client_services.poll();
2264
2265        // 3. Assert that the MockApp's read_single_coil_response callback was invoked with correct data
2266        let received_responses = client_services.app().received_coil_responses.borrow();
2267        assert_eq!(received_responses.len(), 1);
2268
2269        let (rcv_txn_id, rcv_unit_id, rcv_coils) = &received_responses[0];
2270        let rcv_quantity = rcv_coils.quantity();
2271        assert_eq!(*rcv_txn_id, txn_id);
2272        assert_eq!(*rcv_unit_id, unit_id);
2273        assert_eq!(rcv_coils.from_address(), address);
2274        assert_eq!(rcv_coils.quantity(), 1); // Quantity should be 1
2275        assert_eq!(&rcv_coils.values()[..1], &[0x01]); // Value should be 0x01 for true
2276        assert_eq!(rcv_quantity, 1);
2277
2278        // 4. Assert that the expected response was removed from the queue
2279        assert!(client_services.expected_responses.is_empty());
2280    }
2281
2282    /// Test case: `read_single_coil_request` sends a valid ADU over the transport.
2283    #[test]
2284    fn test_read_single_coil_request_sends_valid_adu() {
2285        let transport = MockTransport::default();
2286        let app = MockApp::default();
2287        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2288        let mut client_services =
2289            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2290
2291        let txn_id = 0x0002;
2292        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2293        let address = 0x0005;
2294
2295        client_services
2296            .read_single_coil(txn_id, unit_id, address) // current_millis() is called internally
2297            .unwrap();
2298
2299        let sent_frames = client_services.transport.sent_frames.borrow();
2300        assert_eq!(sent_frames.len(), 1);
2301        let sent_adu = sent_frames.front().unwrap();
2302
2303        // Expected ADU: TID(0x0002), PID(0x0000), Length(0x0006 = Unit ID + FC + Addr + Qty), UnitID(0x01), FC(0x01), Addr(0x0005), Qty(0x0001)
2304        #[rustfmt::skip]
2305        let expected_adu: [u8; 12] = [
2306            0x00, 0x02, // Transaction ID
2307            0x00, 0x00, // Protocol ID
2308            0x00, 0x06, // Length (1 byte Unit ID + 1 byte FC + 2 bytes Address + 2 bytes Quantity = 6)
2309            0x01,       // Unit ID
2310            0x01,       // Function Code (Read Coils)
2311            0x00, 0x05, // Starting Address
2312            0x00, 0x01, // Quantity of Coils (1)
2313        ];
2314        assert_eq!(sent_adu.as_slice(), &expected_adu);
2315
2316        // Verify that the expected response was recorded with single_read = true
2317        assert_eq!(client_services.expected_responses.len(), 1); // Corrected: Removed duplicate pop_front()
2318        let single_read = client_services.expected_responses[0]
2319            .operation_meta
2320            .is_single();
2321        assert!(single_read);
2322    }
2323
2324    /// Test case: `write_single_coil` sends a valid ADU over the transport.
2325    #[test]
2326    fn test_write_single_coil_sends_valid_adu() {
2327        let transport = MockTransport::default();
2328        let app = MockApp::default();
2329        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2330        let mut client_services =
2331            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2332
2333        let txn_id = 0x0003;
2334        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2335        let address = 0x000A;
2336        let value = true;
2337
2338        client_services
2339            .write_single_coil(txn_id, unit_id, address, value) // current_millis() is called internally
2340            .unwrap();
2341
2342        let sent_frames = client_services.transport.sent_frames.borrow();
2343        assert_eq!(sent_frames.len(), 1);
2344        let sent_adu = sent_frames.front().unwrap();
2345
2346        // Expected ADU: TID(0x0003), PID(0x0000), Length(0x0006), UnitID(0x01), FC(0x05), Addr(0x000A), Value(0xFF00)
2347        #[rustfmt::skip]
2348        let expected_adu: [u8; 12] = [
2349            0x00, 0x03, // Transaction ID
2350            0x00, 0x00, // Protocol ID
2351            0x00, 0x06, // Length (1 byte Unit ID + 1 byte FC + 2 bytes Address + 2 bytes Value = 6)
2352            0x01,       // Unit ID
2353            0x05,       // Function Code (Write Single Coil)
2354            0x00, 0x0A, // Address
2355            0xFF, 0x00, // Value (ON)
2356        ];
2357        assert_eq!(sent_adu.as_slice(), &expected_adu);
2358
2359        // Verify that the expected response was recorded
2360        assert_eq!(client_services.expected_responses.len(), 1);
2361        let expected_address = client_services.expected_responses[0]
2362            .operation_meta
2363            .address();
2364        let expected_value = client_services.expected_responses[0].operation_meta.value() != 0;
2365
2366        assert_eq!(expected_address, address);
2367        assert_eq!(expected_value, value);
2368    }
2369
2370    /// Test case: `ClientServices` successfully sends a Write Single Coil request and processes a valid response.
2371    #[test]
2372    fn test_client_services_write_single_coil_e2e_success() {
2373        let transport = MockTransport::default();
2374        let app = MockApp::default();
2375        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2376        let mut client_services =
2377            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2378
2379        let txn_id = 0x0003;
2380        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2381        let address = 0x000A;
2382        let value = true;
2383
2384        // 1. Send a Write Single Coil request
2385        client_services // current_millis() is called internally
2386            .write_single_coil(txn_id, unit_id, address, value)
2387            .unwrap();
2388
2389        // Verify that the request was sent via the mock transport
2390        let sent_adu = client_services
2391            .transport
2392            .sent_frames
2393            .borrow_mut()
2394            .pop_front()
2395            .unwrap();
2396        #[rustfmt::skip]
2397        let expected_request_adu: [u8; 12] = [
2398            0x00, 0x03, // Transaction ID
2399            0x00, 0x00, // Protocol ID
2400            0x00, 0x06, // Length
2401            0x01,       // Unit ID
2402            0x05,       // Function Code (Write Single Coil)
2403            0x00, 0x0A, // Address
2404            0xFF, 0x00, // Value (ON)
2405        ];
2406        assert_eq!(sent_adu.as_slice(), &expected_request_adu);
2407
2408        // 2. Manually construct a valid Write Single Coil response ADU
2409        // ADU: TID(0x0003), PID(0x0000), Length(0x0006), UnitID(0x01), FC(0x05), Address(0x000A), Value(0xFF00)
2410        let response_adu = [
2411            0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x01, 0x05, 0x00, 0x0A, 0xFF, 0x00,
2412        ];
2413
2414        // Simulate receiving the frame
2415        client_services
2416            .transport
2417            .recv_frames
2418            .borrow_mut()
2419            .push_back(Vec::from_slice(&response_adu).unwrap())
2420            .unwrap();
2421        client_services.poll();
2422
2423        // 3. Assert that the MockApp's write_single_coil_response callback was invoked with correct data
2424        let received_responses = client_services
2425            .app
2426            .received_write_single_coil_responses
2427            .borrow();
2428        assert_eq!(received_responses.len(), 1);
2429
2430        let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_value) = &received_responses[0];
2431        assert_eq!(*rcv_txn_id, txn_id);
2432        assert_eq!(*rcv_unit_id, unit_id);
2433        assert_eq!(*rcv_address, address);
2434        assert_eq!(*rcv_value, value);
2435
2436        // 4. Assert that the expected response was removed from the queue
2437        assert!(client_services.expected_responses.is_empty());
2438    }
2439
2440    /// Test case: `write_multiple_coils` sends a valid ADU over the transport.
2441    #[test]
2442    fn test_write_multiple_coils_sends_valid_adu() {
2443        let transport = MockTransport::default();
2444        let app = MockApp::default();
2445        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2446        let mut client_services =
2447            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2448
2449        let txn_id = 0x0004;
2450        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2451        let address = 0x0000;
2452        let quantity = 10;
2453
2454        // Initialize a Coils instance with alternating true/false values to produce 0x55, 0x01
2455        let mut values = Coils::new(address, quantity).unwrap();
2456        for i in 0..quantity {
2457            values.set_value(address + i, i % 2 == 0).unwrap();
2458        }
2459
2460        client_services
2461            .write_multiple_coils(txn_id, unit_id, address, &values) // current_millis() is called internally
2462            .unwrap();
2463
2464        let sent_frames = client_services.transport.sent_frames.borrow();
2465        assert_eq!(sent_frames.len(), 1);
2466        let sent_adu = sent_frames.front().unwrap();
2467
2468        // Expected ADU: TID(0x0004), PID(0x0000), Length(0x0009), UnitID(0x01), FC(0x0F), Addr(0x0000), Qty(0x000A), Byte Count(0x02), Data(0x55, 0x01)
2469        #[rustfmt::skip]
2470        let expected_adu: [u8; 15] = [
2471            0x00, 0x04, // Transaction ID
2472            0x00, 0x00, // Protocol ID
2473            0x00, 0x09, // Length (1 byte Unit ID + 1 byte FC + 2 bytes Address + 2 bytes Quantity + 1 byte Byte Count + 2 bytes Data = 9)
2474            0x01,       // Unit ID
2475            0x0F,       // Function Code (Write Multiple Coils)
2476            0x00, 0x00, // Address
2477            0x00, 0x0A, // Quantity
2478            0x02,       // Byte Count
2479            0x55, 0x01, // Data
2480        ];
2481        assert_eq!(sent_adu.as_slice(), &expected_adu);
2482
2483        // Verify that the expected response was recorded
2484        assert_eq!(client_services.expected_responses.len(), 1);
2485        let expected_address = client_services.expected_responses[0]
2486            .operation_meta
2487            .address();
2488        let expected_quantity = client_services.expected_responses[0]
2489            .operation_meta
2490            .quantity();
2491        assert_eq!(expected_address, address);
2492        assert_eq!(expected_quantity, quantity);
2493    }
2494
2495    /// Test case: `ClientServices` successfully sends a Write Multiple Coils request and processes a valid response.
2496    #[test]
2497    fn test_client_services_write_multiple_coils_e2e_success() {
2498        let transport = MockTransport::default();
2499        let app = MockApp::default();
2500        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2501        let mut client_services =
2502            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2503
2504        let txn_id = 0x0004;
2505        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2506        let address = 0x0000;
2507        let quantity = 10;
2508
2509        // Initialize a Coils instance with alternating true/false values
2510        let mut values = Coils::new(address, quantity).unwrap();
2511        for i in 0..quantity {
2512            values.set_value(address + i, i % 2 == 0).unwrap();
2513        }
2514
2515        // 1. Send a Write Multiple Coils request
2516        client_services // current_millis() is called internally
2517            .write_multiple_coils(txn_id, unit_id, address, &values)
2518            .unwrap();
2519
2520        // Verify that the request was sent via the mock transport
2521        let sent_adu = client_services
2522            .transport
2523            .sent_frames
2524            .borrow_mut()
2525            .pop_front()
2526            .unwrap();
2527        #[rustfmt::skip]
2528        let expected_request_adu: [u8; 15] = [
2529            0x00, 0x04, // Transaction ID
2530            0x00, 0x00, // Protocol ID
2531            0x00, 0x09, // Length
2532            0x01,       // Unit ID
2533            0x0F,       // Function Code (Write Multiple Coils)
2534            0x00, 0x00, // Address
2535            0x00, 0x0A, // Quantity
2536            0x02,       // Byte Count
2537            0x55, 0x01, // Data
2538        ];
2539        assert_eq!(sent_adu.as_slice(), &expected_request_adu);
2540
2541        // 2. Manually construct a valid Write Multiple Coils response ADU
2542        // ADU: TID(0x0004), PID(0x0000), Length(0x0006), UnitID(0x01), FC(0x0F), Address(0x0000), Quantity(0x000A)
2543        let response_adu = [
2544            0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0A,
2545        ];
2546
2547        // Simulate receiving the frame
2548        client_services
2549            .transport
2550            .recv_frames
2551            .borrow_mut()
2552            .push_back(Vec::from_slice(&response_adu).unwrap())
2553            .unwrap();
2554        client_services.poll();
2555
2556        // 3. Assert that the MockApp's write_multiple_coils_response callback was invoked with correct data
2557        let received_responses = client_services
2558            .app
2559            .received_write_multiple_coils_responses
2560            .borrow();
2561        assert_eq!(received_responses.len(), 1);
2562
2563        let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_quantity) = &received_responses[0];
2564        assert_eq!(*rcv_txn_id, txn_id);
2565        assert_eq!(*rcv_unit_id, unit_id);
2566        assert_eq!(*rcv_address, address);
2567        assert_eq!(*rcv_quantity, quantity);
2568
2569        // 4. Assert that the expected response was removed from the queue
2570        assert!(client_services.expected_responses.is_empty());
2571    }
2572
2573    /// Test case: `ClientServices` successfully sends a Read Coils request and processes a valid response.
2574    #[test]
2575    fn test_client_services_read_coils_e2e_success() {
2576        let transport = MockTransport::default();
2577        let app = MockApp::default();
2578        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2579        let mut client_services =
2580            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2581
2582        let txn_id = 0x0001;
2583        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2584        let address = 0x0000;
2585        let quantity = 8;
2586        client_services
2587            .read_multiple_coils(txn_id, unit_id, address, quantity) // current_millis() is called internally
2588            .unwrap();
2589
2590        // Verify that the request was sent via the mock transport
2591        let sent_adu = client_services
2592            .transport
2593            .sent_frames
2594            .borrow_mut()
2595            .pop_front()
2596            .unwrap(); // Corrected: Removed duplicate pop_front()
2597        // Expected ADU: TID(0x0001), PID(0x0000), Length(0x0006 = Unit ID + FC + Addr + Qty), UnitID(0x01), FC(0x01), Addr(0x0000), Qty(0x0008)
2598        assert_eq!(
2599            sent_adu.as_slice(),
2600            &[
2601                0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x00, 0x00, 0x08
2602            ]
2603        );
2604
2605        // Verify that the expected response was recorded
2606        assert_eq!(client_services.expected_responses.len(), 1); // Corrected: Removed duplicate pop_front()
2607        let from_address = client_services.expected_responses[0]
2608            .operation_meta
2609            .address();
2610        let expected_quantity = client_services.expected_responses[0]
2611            .operation_meta
2612            .quantity();
2613
2614        assert_eq!(expected_quantity, quantity);
2615        assert_eq!(from_address, address);
2616
2617        // 2. Manually construct a valid Read Coils response ADU
2618        // Response for reading 8 coils, values: 10110011 (0xB3)
2619        // ADU: TID(0x0001), PID(0x0000), Length(0x0004 = Unit ID + FC + Byte Count + Coil Data), UnitID(0x01), FC(0x01), Byte Count(0x01), Coil Data(0xB3)
2620        let response_adu = [0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0xB3];
2621
2622        // Simulate receiving the frame
2623        client_services
2624            .transport
2625            .recv_frames
2626            .borrow_mut()
2627            .push_back(Vec::from_slice(&response_adu).unwrap())
2628            .unwrap();
2629        client_services.poll(); // Call poll to ingest frame and process
2630
2631        // Advance time to ensure any potential timeouts are processed (though not expected here)
2632
2633        // 3. Assert that the MockApp's callback was invoked with correct data
2634        let received_responses = client_services.app().received_coil_responses.borrow();
2635        assert_eq!(received_responses.len(), 1);
2636
2637        let (rcv_txn_id, rcv_unit_id, rcv_coils) = &received_responses[0];
2638        let rcv_quantity = rcv_coils.quantity();
2639        assert_eq!(*rcv_txn_id, txn_id);
2640        assert_eq!(*rcv_unit_id, unit_id);
2641        assert_eq!(rcv_coils.from_address(), address);
2642        assert_eq!(rcv_coils.quantity(), quantity);
2643        assert_eq!(&rcv_coils.values()[..1], &[0xB3]);
2644        assert_eq!(rcv_quantity, quantity);
2645
2646        // 4. Assert that the expected response was removed from the queue
2647        assert!(client_services.expected_responses.is_empty());
2648    }
2649
2650    /// Test case: `poll` handles a timed-out request with retries.
2651    #[test]
2652    fn test_client_services_timeout_with_retry() {
2653        let transport = MockTransport::default();
2654        // Simulate no response from the server initially
2655        transport.recv_frames.borrow_mut().clear();
2656        let app = MockApp::default();
2657        let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2658        tcp_config.response_timeout_ms = 100; // Short timeout for testing
2659        tcp_config.retry_attempts = 1; // One retry
2660        let config = ModbusConfig::Tcp(tcp_config);
2661
2662        let mut client_services =
2663            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2664
2665        let txn_id = 0x0005;
2666        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2667        let address = 0x0000;
2668
2669        client_services
2670            .read_single_coil(txn_id, unit_id, address)
2671            .unwrap();
2672
2673        // Advance time past timeout for the first time
2674        *client_services.app().current_time.borrow_mut() = 150;
2675        // Simulate time passing beyond timeout, but with retries left
2676        client_services.poll(); // First timeout, should retry
2677
2678        // Verify that the request was re-sent (2 frames: initial + retry)
2679        assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2680        assert_eq!(client_services.expected_responses.len(), 1); // Still waiting for response
2681        assert_eq!(client_services.expected_responses[0].retries_left, 0); // One retry used
2682
2683        // Advance time past timeout for the second time
2684        *client_services.app().current_time.borrow_mut() = 300;
2685        // Simulate more time passing, exhausting retries
2686        client_services.poll(); // Second timeout, should fail
2687
2688        // Verify that the request is no longer expected and an error was reported
2689        assert!(client_services.expected_responses.is_empty());
2690        // In a real scenario, MockApp::request_failed would be checked.
2691    }
2692
2693    /// Test case: `poll` correctly handles multiple concurrent requests timing out simultaneously.
2694    #[test]
2695    fn test_client_services_concurrent_timeouts() {
2696        let transport = MockTransport::default();
2697        let app = MockApp::default();
2698
2699        // Configure a short timeout and 1 retry for testing purposes
2700        let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2701        tcp_config.response_timeout_ms = 100;
2702        tcp_config.retry_attempts = 1;
2703        let config = ModbusConfig::Tcp(tcp_config);
2704
2705        let mut client_services =
2706            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2707
2708        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2709
2710        // 1. Send two simultaneous requests
2711        client_services
2712            .read_single_coil(1, unit_id, 0x0000)
2713            .unwrap();
2714        client_services
2715            .read_single_coil(2, unit_id, 0x0001)
2716            .unwrap();
2717
2718        // Verify both requests are queued and sent once
2719        assert_eq!(client_services.expected_responses.len(), 2);
2720        assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2721
2722        // 2. Advance time past the timeout threshold for both requests
2723        *client_services.app().current_time.borrow_mut() = 150;
2724
2725        // 3. Poll the client. Both requests should be evaluated, found timed out, and retried.
2726        client_services.poll();
2727
2728        // Verify both requests are STILL in the queue (waiting for retry responses)
2729        assert_eq!(client_services.expected_responses.len(), 2);
2730        assert_eq!(client_services.expected_responses[0].retries_left, 0);
2731        assert_eq!(client_services.expected_responses[1].retries_left, 0);
2732
2733        // Verify both requests were transmitted again (Total sent frames = 2 original + 2 retries = 4)
2734        assert_eq!(client_services.transport.sent_frames.borrow().len(), 4);
2735
2736        // 4. Advance time again past the retry timeout threshold
2737        *client_services.app().current_time.borrow_mut() = 300;
2738
2739        // 5. Poll the client. Both requests should exhaust their retries and be dropped.
2740        client_services.poll();
2741
2742        // Verify the queue is now completely empty
2743        assert!(client_services.expected_responses.is_empty());
2744
2745        // Verify the application was notified of BOTH failures
2746        let failed_requests = client_services.app().failed_requests.borrow();
2747        assert_eq!(failed_requests.len(), 2);
2748
2749        // Ensure both specific transaction IDs were reported as having no retries left
2750        let has_txn_1 = failed_requests
2751            .iter()
2752            .any(|(txn, _, err)| *txn == 1 && *err == MbusError::NoRetriesLeft);
2753        let has_txn_2 = failed_requests
2754            .iter()
2755            .any(|(txn, _, err)| *txn == 2 && *err == MbusError::NoRetriesLeft);
2756        assert!(has_txn_1, "Transaction 1 should have failed");
2757        assert!(has_txn_2, "Transaction 2 should have failed");
2758    }
2759
2760    #[test]
2761    fn test_poll_connection_loss_flushes_pending_requests() {
2762        let transport = MockTransport::default();
2763        let app = MockApp::default();
2764        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2765        let mut client_services =
2766            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2767
2768        let unit_id = UnitIdOrSlaveAddr::new(1).unwrap();
2769        client_services.read_single_coil(1, unit_id, 0).unwrap();
2770        client_services.read_single_coil(2, unit_id, 1).unwrap();
2771        assert_eq!(client_services.expected_responses.len(), 2);
2772
2773        *client_services.transport.is_connected_flag.borrow_mut() = false;
2774        *client_services.transport.recv_error.borrow_mut() = Some(MbusError::ConnectionClosed);
2775
2776        client_services.poll();
2777
2778        assert!(client_services.expected_responses.is_empty());
2779        assert_eq!(client_services.next_timeout_check, None);
2780
2781        let failed_requests = client_services.app().failed_requests.borrow();
2782        assert_eq!(failed_requests.len(), 2);
2783        assert!(
2784            failed_requests
2785                .iter()
2786                .all(|(txn, _, err)| (*txn == 1 || *txn == 2) && *err == MbusError::ConnectionLost)
2787        );
2788    }
2789
2790    #[test]
2791    fn test_fixed_backoff_schedules_and_does_not_retry_early() {
2792        let transport = MockTransport::default();
2793        let app = MockApp::default();
2794        let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2795        tcp_config.response_timeout_ms = 100;
2796        tcp_config.retry_attempts = 1;
2797        tcp_config.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 50 };
2798        let config = ModbusConfig::Tcp(tcp_config);
2799
2800        let mut client_services =
2801            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2802
2803        client_services
2804            .read_single_coil(1, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2805            .unwrap();
2806        assert_eq!(client_services.transport.sent_frames.borrow().len(), 1);
2807
2808        *client_services.app().current_time.borrow_mut() = 101;
2809        client_services.poll();
2810        assert_eq!(client_services.transport.sent_frames.borrow().len(), 1);
2811        assert_eq!(
2812            client_services.expected_responses[0].next_retry_timestamp,
2813            Some(151)
2814        );
2815
2816        *client_services.app().current_time.borrow_mut() = 150;
2817        client_services.poll();
2818        assert_eq!(client_services.transport.sent_frames.borrow().len(), 1);
2819
2820        *client_services.app().current_time.borrow_mut() = 151;
2821        client_services.poll();
2822        assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2823    }
2824
2825    #[test]
2826    fn test_exponential_backoff_growth() {
2827        let transport = MockTransport::default();
2828        let app = MockApp::default();
2829        let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2830        tcp_config.response_timeout_ms = 100;
2831        tcp_config.retry_attempts = 2;
2832        tcp_config.retry_backoff_strategy = BackoffStrategy::Exponential {
2833            base_delay_ms: 50,
2834            max_delay_ms: 500,
2835        };
2836        let config = ModbusConfig::Tcp(tcp_config);
2837
2838        let mut client_services =
2839            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2840
2841        client_services
2842            .read_single_coil(7, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2843            .unwrap();
2844
2845        *client_services.app().current_time.borrow_mut() = 101;
2846        client_services.poll();
2847        assert_eq!(
2848            client_services.expected_responses[0].next_retry_timestamp,
2849            Some(151)
2850        );
2851
2852        *client_services.app().current_time.borrow_mut() = 151;
2853        client_services.poll();
2854        assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2855
2856        *client_services.app().current_time.borrow_mut() = 252;
2857        client_services.poll();
2858        assert_eq!(
2859            client_services.expected_responses[0].next_retry_timestamp,
2860            Some(352)
2861        );
2862
2863        *client_services.app().current_time.borrow_mut() = 352;
2864        client_services.poll();
2865        assert_eq!(client_services.transport.sent_frames.borrow().len(), 3);
2866    }
2867
2868    #[test]
2869    fn test_jitter_bounds_with_random_source_lower_bound() {
2870        let transport = MockTransport::default();
2871        let app = MockApp::default();
2872        let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2873        tcp_config.response_timeout_ms = 100;
2874        tcp_config.retry_attempts = 1;
2875        tcp_config.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 100 };
2876        tcp_config.retry_jitter_strategy = JitterStrategy::Percentage { percent: 20 };
2877        tcp_config.retry_random_fn = Some(rand_zero);
2878        let config = ModbusConfig::Tcp(tcp_config);
2879
2880        let mut client_services =
2881            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2882        client_services
2883            .read_single_coil(10, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2884            .unwrap();
2885
2886        *client_services.app().current_time.borrow_mut() = 101;
2887        client_services.poll();
2888        assert_eq!(
2889            client_services.expected_responses[0].next_retry_timestamp,
2890            Some(181)
2891        );
2892    }
2893
2894    #[test]
2895    fn test_jitter_bounds_with_random_source_upper_bound() {
2896        let transport3 = MockTransport::default();
2897        let app3 = MockApp::default();
2898        let mut tcp_config3 = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2899        tcp_config3.response_timeout_ms = 100;
2900        tcp_config3.retry_attempts = 1;
2901        tcp_config3.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 100 };
2902        tcp_config3.retry_jitter_strategy = JitterStrategy::Percentage { percent: 20 };
2903        tcp_config3.retry_random_fn = Some(rand_upper_percent_20);
2904        let config3 = ModbusConfig::Tcp(tcp_config3);
2905
2906        let mut client_services3 =
2907            ClientServices::<MockTransport, MockApp, 10>::new(transport3, app3, config3).unwrap();
2908        client_services3
2909            .read_single_coil(12, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2910            .unwrap();
2911
2912        *client_services3.app.current_time.borrow_mut() = 101;
2913        client_services3.poll();
2914        assert_eq!(
2915            client_services3.expected_responses[0].next_retry_timestamp,
2916            Some(221)
2917        );
2918    }
2919
2920    #[test]
2921    fn test_jitter_falls_back_without_random_source() {
2922        let transport2 = MockTransport::default();
2923        let app2 = MockApp::default();
2924        let mut tcp_config2 = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2925        tcp_config2.response_timeout_ms = 100;
2926        tcp_config2.retry_attempts = 1;
2927        tcp_config2.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 100 };
2928        tcp_config2.retry_jitter_strategy = JitterStrategy::Percentage { percent: 20 };
2929        tcp_config2.retry_random_fn = None;
2930        let config2 = ModbusConfig::Tcp(tcp_config2);
2931
2932        let mut client_services2 =
2933            ClientServices::<MockTransport, MockApp, 10>::new(transport2, app2, config2).unwrap();
2934        client_services2
2935            .read_single_coil(11, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2936            .unwrap();
2937
2938        *client_services2.app.current_time.borrow_mut() = 101;
2939        client_services2.poll();
2940        assert_eq!(
2941            client_services2.expected_responses[0].next_retry_timestamp,
2942            Some(201)
2943        );
2944    }
2945
2946    #[test]
2947    fn test_serial_retry_scheduling_uses_backoff() {
2948        let transport = MockTransport {
2949            transport_type: Some(TransportType::StdSerial(SerialMode::Rtu)),
2950            ..Default::default()
2951        };
2952        let app = MockApp::default();
2953
2954        let serial_config = ModbusSerialConfig {
2955            port_path: heapless::String::<64>::from_str("/dev/ttyUSB0").unwrap(),
2956            mode: SerialMode::Rtu,
2957            baud_rate: BaudRate::Baud9600,
2958            data_bits: mbus_core::transport::DataBits::Eight,
2959            stop_bits: 1,
2960            parity: Parity::None,
2961            response_timeout_ms: 100,
2962            retry_attempts: 1,
2963            retry_backoff_strategy: BackoffStrategy::Fixed { delay_ms: 25 },
2964            retry_jitter_strategy: JitterStrategy::None,
2965            retry_random_fn: None,
2966        };
2967
2968        let mut client_services = ClientServices::<MockTransport, MockApp, 1>::new(
2969            transport,
2970            app,
2971            ModbusConfig::Serial(serial_config),
2972        )
2973        .unwrap();
2974
2975        client_services
2976            .read_single_coil(1, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2977            .unwrap();
2978
2979        *client_services.app().current_time.borrow_mut() = 101;
2980        client_services.poll();
2981        assert_eq!(
2982            client_services.expected_responses[0].next_retry_timestamp,
2983            Some(126)
2984        );
2985
2986        *client_services.app().current_time.borrow_mut() = 126;
2987        client_services.poll();
2988        assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2989    }
2990
2991    /// Test case: `read_multiple_coils` returns `MbusError::TooManyRequests` when the queue is full.
2992    #[test]
2993    fn test_too_many_requests_error() {
2994        let transport = MockTransport::default();
2995        let app = MockApp::default();
2996        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2997        // Create a client with a small capacity for expected responses
2998        let mut client_services =
2999            ClientServices::<MockTransport, MockApp, 1>::new(transport, app, config).unwrap();
3000
3001        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3002        // Send one request, which should fill the queue
3003        client_services
3004            .read_multiple_coils(1, unit_id, 0, 1)
3005            .unwrap();
3006        assert_eq!(client_services.expected_responses.len(), 1);
3007
3008        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3009        // Attempt to send another request, which should fail due to full queue
3010        let result = client_services.read_multiple_coils(2, unit_id, 0, 1);
3011        assert!(result.is_err());
3012        assert_eq!(result.unwrap_err(), MbusError::TooManyRequests);
3013        assert_eq!(client_services.expected_responses.len(), 1); // Queue size remains 1
3014    }
3015
3016    /// Test case: `read_holding_registers` sends a valid ADU over the transport.
3017    #[test]
3018    fn test_read_holding_registers_sends_valid_adu() {
3019        let transport = MockTransport::default();
3020        let app = MockApp::default();
3021        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3022        let mut client_services =
3023            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3024
3025        let txn_id = 0x0005;
3026        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3027        let address = 0x0000;
3028        let quantity = 2;
3029        client_services
3030            .read_holding_registers(txn_id, unit_id, address, quantity)
3031            .unwrap();
3032
3033        let sent_frames = client_services.transport.sent_frames.borrow();
3034        assert_eq!(sent_frames.len(), 1);
3035        let sent_adu = sent_frames.front().unwrap();
3036
3037        // Expected ADU: TID(0x0005), PID(0x0000), Length(0x0006 = Unit ID + FC + Addr + Qty), UnitID(0x01), FC(0x03), Addr(0x0000), Qty(0x0002)
3038        #[rustfmt::skip]
3039        let expected_adu: [u8; 12] = [
3040            0x00, 0x05, // Transaction ID
3041            0x00, 0x00, // Protocol ID
3042            0x00, 0x06, // Length (1 byte Unit ID + 1 byte FC + 2 bytes Address + 2 bytes Quantity = 6)
3043            0x01,       // Unit ID
3044            0x03,       // Function Code (Read Holding Registers)
3045            0x00, 0x00, // Starting Address
3046            0x00, 0x02, // Quantity of Registers
3047        ];
3048        assert_eq!(sent_adu.as_slice(), &expected_adu);
3049    }
3050
3051    /// Test case: `ClientServices` successfully sends a Read Holding Registers request and processes a valid response.
3052    #[test]
3053    fn test_client_services_read_holding_registers_e2e_success() {
3054        let transport = MockTransport::default();
3055        let app = MockApp::default();
3056        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3057        let mut client_services =
3058            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3059
3060        let txn_id = 0x0005;
3061        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3062        let address = 0x0000;
3063        let quantity = 2;
3064        client_services
3065            .read_holding_registers(txn_id, unit_id, address, quantity)
3066            .unwrap();
3067
3068        // Simulate response
3069        // ADU: TID(0x0005), PID(0x0000), Length(0x0007), UnitID(0x01), FC(0x03), Byte Count(0x04), Data(0x1234, 0x5678)
3070        let response_adu = [
3071            0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x01, 0x03, 0x04, 0x12, 0x34, 0x56, 0x78,
3072        ];
3073        client_services
3074            .transport
3075            .recv_frames
3076            .borrow_mut()
3077            .push_back(Vec::from_slice(&response_adu).unwrap())
3078            .unwrap();
3079        client_services.poll();
3080
3081        let received_responses = client_services
3082            .app
3083            .received_holding_register_responses
3084            .borrow();
3085        assert_eq!(received_responses.len(), 1);
3086        let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3087        assert_eq!(*rcv_txn_id, txn_id);
3088        assert_eq!(*rcv_unit_id, unit_id);
3089        assert_eq!(rcv_registers.from_address(), address);
3090        assert_eq!(rcv_registers.quantity(), quantity);
3091        assert_eq!(&rcv_registers.values()[..2], &[0x1234, 0x5678]);
3092        assert_eq!(*rcv_quantity, quantity);
3093        assert!(client_services.expected_responses.is_empty());
3094    }
3095
3096    /// Test case: `read_input_registers` sends a valid ADU over the transport.
3097    #[test]
3098    fn test_read_input_registers_sends_valid_adu() {
3099        let transport = MockTransport::default();
3100        let app = MockApp::default();
3101        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3102        let mut client_services =
3103            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3104
3105        let txn_id = 0x0006;
3106        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3107        let address = 0x0000;
3108        let quantity = 2;
3109        client_services
3110            .read_input_registers(txn_id, unit_id, address, quantity)
3111            .unwrap();
3112
3113        let sent_frames = client_services.transport.sent_frames.borrow();
3114        assert_eq!(sent_frames.len(), 1);
3115        let sent_adu = sent_frames.front().unwrap();
3116
3117        // Expected ADU: TID(0x0006), PID(0x0000), Length(0x0006 = Unit ID + FC + Addr + Qty), UnitID(0x01), FC(0x04), Addr(0x0000), Qty(0x0002)
3118        #[rustfmt::skip]
3119        let expected_adu: [u8; 12] = [
3120            0x00, 0x06, // Transaction ID
3121            0x00, 0x00, // Protocol ID
3122            0x00, 0x06, // Length (1 byte Unit ID + 1 byte FC + 2 bytes Address + 2 bytes Quantity = 6)
3123            0x01,       // Unit ID
3124            0x04,       // Function Code (Read Input Registers)
3125            0x00, 0x00, // Starting Address
3126            0x00, 0x02, // Quantity of Registers
3127        ];
3128        assert_eq!(sent_adu.as_slice(), &expected_adu);
3129    }
3130
3131    /// Test case: `ClientServices` successfully sends a Read Input Registers request and processes a valid response.
3132    #[test]
3133    fn test_client_services_read_input_registers_e2e_success() {
3134        let transport = MockTransport::default();
3135        let app = MockApp::default();
3136        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3137        let mut client_services =
3138            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3139
3140        let txn_id = 0x0006;
3141        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3142        let address = 0x0000;
3143        let quantity = 2;
3144        client_services
3145            .read_input_registers(txn_id, unit_id, address, quantity)
3146            .unwrap();
3147
3148        // Simulate response
3149        // ADU: TID(0x0006), PID(0x0000), Length(0x0007), UnitID(0x01), FC(0x04), Byte Count(0x04), Data(0xAABB, 0xCCDD)
3150        let response_adu = [
3151            0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x01, 0x04, 0x04, 0xAA, 0xBB, 0xCC, 0xDD,
3152        ];
3153        client_services
3154            .transport
3155            .recv_frames
3156            .borrow_mut()
3157            .push_back(Vec::from_slice(&response_adu).unwrap())
3158            .unwrap();
3159        client_services.poll();
3160
3161        let received_responses = client_services
3162            .app
3163            .received_input_register_responses
3164            .borrow();
3165        assert_eq!(received_responses.len(), 1);
3166        let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3167        assert_eq!(*rcv_txn_id, txn_id);
3168        assert_eq!(*rcv_unit_id, unit_id);
3169        assert_eq!(rcv_registers.from_address(), address);
3170        assert_eq!(rcv_registers.quantity(), quantity);
3171        assert_eq!(&rcv_registers.values()[..2], &[0xAABB, 0xCCDD]);
3172        assert_eq!(*rcv_quantity, quantity);
3173        assert!(client_services.expected_responses.is_empty());
3174    }
3175
3176    /// Test case: `write_single_register` sends a valid ADU over the transport.
3177    #[test]
3178    fn test_write_single_register_sends_valid_adu() {
3179        let transport = MockTransport::default();
3180        let app = MockApp::default();
3181        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3182        let mut client_services =
3183            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3184
3185        let txn_id = 0x0007;
3186        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3187        let address = 0x0001;
3188        let value = 0x1234;
3189        client_services
3190            .write_single_register(txn_id, unit_id, address, value)
3191            .unwrap();
3192
3193        let sent_frames = client_services.transport.sent_frames.borrow();
3194        assert_eq!(sent_frames.len(), 1);
3195        let sent_adu = sent_frames.front().unwrap();
3196
3197        // Expected ADU: TID(0x0007), PID(0x0000), Length(0x0006), UnitID(0x01), FC(0x06), Addr(0x0001), Value(0x1234)
3198        #[rustfmt::skip]
3199        let expected_adu: [u8; 12] = [
3200            0x00, 0x07, // Transaction ID
3201            0x00, 0x00, // Protocol ID
3202            0x00, 0x06, // Length (1 byte Unit ID + 1 byte FC + 2 bytes Address + 2 bytes Value = 6)
3203            0x01,       // Unit ID
3204            0x06,       // Function Code (Write Single Register)
3205            0x00, 0x01, // Address
3206            0x12, 0x34, // Value
3207        ];
3208        assert_eq!(sent_adu.as_slice(), &expected_adu);
3209    }
3210
3211    /// Test case: `ClientServices` successfully sends a Write Single Register request and processes a valid response.
3212    #[test]
3213    fn test_client_services_write_single_register_e2e_success() {
3214        let transport = MockTransport::default();
3215        let app = MockApp::default();
3216        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3217        let mut client_services =
3218            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3219
3220        let txn_id = 0x0007;
3221        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3222        let address = 0x0001;
3223        let value = 0x1234;
3224        client_services
3225            .write_single_register(txn_id, unit_id, address, value)
3226            .unwrap();
3227
3228        // Simulate response
3229        // ADU: TID(0x0007), PID(0x0000), Length(0x0006), UnitID(0x01), FC(0x06), Address(0x0001), Value(0x1234)
3230        let response_adu = [
3231            0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x01, 0x06, 0x00, 0x01, 0x12, 0x34,
3232        ];
3233        client_services
3234            .transport
3235            .recv_frames
3236            .borrow_mut()
3237            .push_back(Vec::from_slice(&response_adu).unwrap())
3238            .unwrap();
3239        client_services.poll();
3240
3241        let received_responses = client_services
3242            .app
3243            .received_write_single_register_responses
3244            .borrow();
3245        assert_eq!(received_responses.len(), 1);
3246        let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_value) = &received_responses[0];
3247        assert_eq!(*rcv_txn_id, txn_id);
3248        assert_eq!(*rcv_unit_id, unit_id);
3249        assert_eq!(*rcv_address, address);
3250        assert_eq!(*rcv_value, value);
3251        assert!(client_services.expected_responses.is_empty());
3252    }
3253
3254    /// Test case: `write_multiple_registers` sends a valid ADU over the transport.
3255    #[test]
3256    fn test_write_multiple_registers_sends_valid_adu() {
3257        let transport = MockTransport::default();
3258        let app = MockApp::default();
3259        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3260        let mut client_services =
3261            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3262
3263        let txn_id = 0x0008;
3264        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3265        let address = 0x0001;
3266        let quantity = 2;
3267        let values = [0x1234, 0x5678];
3268        client_services
3269            .write_multiple_registers(txn_id, unit_id, address, quantity, &values)
3270            .unwrap();
3271
3272        let sent_frames = client_services.transport.sent_frames.borrow();
3273        assert_eq!(sent_frames.len(), 1);
3274        let sent_adu = sent_frames.front().unwrap();
3275
3276        // Expected ADU: TID(0x0008), PID(0x0000), Length(0x0009), UnitID(0x01), FC(0x10), Addr(0x0001), Qty(0x0002), Byte Count(0x04), Data(0x1234, 0x5678)
3277        #[rustfmt::skip]
3278        let expected_adu: [u8; 17] = [ // Total ADU length is 17 bytes
3279            0x00, 0x08, // Transaction ID
3280            0x00, 0x00, // Protocol ID
3281            0x00, 0x0B, // Length (UnitID(1) + PDU(10) = 11)
3282            0x01,       // Unit ID
3283            0x10,       // Function Code (Write Multiple Registers)
3284            0x00, 0x01, // Address
3285            0x00, 0x02, // Quantity
3286            0x04,       // Byte Count
3287            0x12, 0x34, 0x56, 0x78, // Data
3288        ];
3289        assert_eq!(sent_adu.as_slice(), &expected_adu);
3290    }
3291
3292    /// Test case: `ClientServices` successfully sends a Write Multiple Registers request and processes a valid response.
3293    #[test]
3294    fn test_client_services_write_multiple_registers_e2e_success() {
3295        let transport = MockTransport::default();
3296        let app = MockApp::default();
3297        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3298        let mut client_services =
3299            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3300
3301        let txn_id = 0x0008;
3302        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3303        let address = 0x0001;
3304        let quantity = 2;
3305        let values = [0x1234, 0x5678];
3306        client_services
3307            .write_multiple_registers(txn_id, unit_id, address, quantity, &values)
3308            .unwrap();
3309
3310        // Simulate response
3311        // ADU: TID(0x0008), PID(0x0000), Length(0x0006), UnitID(0x01), FC(0x10), Address(0x0001), Quantity(0x0002)
3312        let response_adu = [
3313            0x00, 0x08, 0x00, 0x00, 0x00, 0x06, 0x01, 0x10, 0x00, 0x01, 0x00, 0x02,
3314        ];
3315        client_services
3316            .transport
3317            .recv_frames
3318            .borrow_mut()
3319            .push_back(Vec::from_slice(&response_adu).unwrap())
3320            .unwrap();
3321        client_services.poll();
3322
3323        let received_responses = client_services
3324            .app
3325            .received_write_multiple_register_responses
3326            .borrow();
3327        assert_eq!(received_responses.len(), 1);
3328        let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_quantity) = &received_responses[0];
3329        assert_eq!(*rcv_txn_id, txn_id);
3330        assert_eq!(*rcv_unit_id, unit_id);
3331        assert_eq!(*rcv_address, address);
3332        assert_eq!(*rcv_quantity, quantity);
3333        assert!(client_services.expected_responses.is_empty());
3334    }
3335
3336    /// Test case: `ClientServices` correctly handles a Modbus exception response.
3337    #[test]
3338    fn test_client_services_handles_exception_response() {
3339        let transport = MockTransport::default();
3340        let app = MockApp::default();
3341        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3342        let mut client_services =
3343            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3344
3345        let txn_id = 0x0009;
3346        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3347        let address = 0x0000;
3348        let quantity = 1;
3349
3350        client_services
3351            .read_holding_registers(txn_id, unit_id, address, quantity)
3352            .unwrap();
3353
3354        // Simulate an exception response (e.g., Illegal Data Address)
3355        // FC = 0x83 (0x03 + 0x80), Exception Code = 0x02
3356        let exception_adu = [
3357            0x00, 0x09, // Transaction ID
3358            0x00, 0x00, // Protocol ID
3359            0x00, 0x03, // Length
3360            0x01, // Unit ID
3361            0x83, // Function Code (0x03 + 0x80 Error Mask)
3362            0x02, // Exception Code (Illegal Data Address)
3363        ];
3364        client_services
3365            .transport
3366            .recv_frames
3367            .borrow_mut()
3368            .push_back(Vec::from_slice(&exception_adu).unwrap())
3369            .unwrap();
3370        client_services.poll();
3371
3372        // Verify that no successful response was recorded
3373        assert!(
3374            client_services
3375                .app
3376                .received_holding_register_responses
3377                .borrow()
3378                .is_empty()
3379        );
3380        // Verify that the failure was reported to the app
3381        assert_eq!(client_services.app().failed_requests.borrow().len(), 1);
3382        let (failed_txn, failed_unit, failed_err) =
3383            &client_services.app().failed_requests.borrow()[0];
3384        assert_eq!(*failed_txn, txn_id);
3385        assert_eq!(*failed_unit, unit_id);
3386        assert_eq!(*failed_err, MbusError::ModbusException(0x02));
3387    }
3388
3389    #[test]
3390    fn test_serial_exception_coil_response_fails_immediately_with_request_txn_id() {
3391        let mut client_services = make_serial_client();
3392
3393        let txn_id = 0x2001;
3394        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3395        let mut values = Coils::new(0x0000, 10).unwrap();
3396        values.set_value(0x0000, true).unwrap();
3397        values.set_value(0x0001, false).unwrap();
3398        values.set_value(0x0002, true).unwrap();
3399        values.set_value(0x0003, false).unwrap();
3400        values.set_value(0x0004, true).unwrap();
3401        values.set_value(0x0005, false).unwrap();
3402        values.set_value(0x0006, true).unwrap();
3403        values.set_value(0x0007, false).unwrap();
3404        values.set_value(0x0008, true).unwrap();
3405        values.set_value(0x0009, false).unwrap();
3406
3407        client_services
3408            .write_multiple_coils(txn_id, unit_id, 0x0000, &values)
3409            .unwrap();
3410
3411        let exception_adu = make_rtu_exception_adu(unit_id, 0x0F, 0x01);
3412        client_services
3413            .transport
3414            .recv_frames
3415            .borrow_mut()
3416            .push_back(exception_adu)
3417            .unwrap();
3418
3419        client_services.poll();
3420
3421        let failed = client_services.app().failed_requests.borrow();
3422        assert_eq!(failed.len(), 1);
3423        assert_eq!(failed[0].0, txn_id);
3424        assert_eq!(failed[0].1, unit_id);
3425        assert_eq!(failed[0].2, MbusError::ModbusException(0x01));
3426        assert!(
3427            client_services
3428                .app
3429                .received_write_multiple_coils_responses
3430                .borrow()
3431                .is_empty()
3432        );
3433        assert!(client_services.expected_responses.is_empty());
3434    }
3435
3436    #[test]
3437    fn test_serial_exception_register_response_fails_immediately_with_request_txn_id() {
3438        let mut client_services = make_serial_client();
3439
3440        let txn_id = 0x2002;
3441        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3442        client_services
3443            .read_holding_registers(txn_id, unit_id, 0x0000, 1)
3444            .unwrap();
3445
3446        let exception_adu = make_rtu_exception_adu(unit_id, 0x03, 0x02);
3447        client_services
3448            .transport
3449            .recv_frames
3450            .borrow_mut()
3451            .push_back(exception_adu)
3452            .unwrap();
3453
3454        client_services.poll();
3455
3456        let failed = client_services.app().failed_requests.borrow();
3457        assert_eq!(failed.len(), 1);
3458        assert_eq!(failed[0].0, txn_id);
3459        assert_eq!(failed[0].1, unit_id);
3460        assert_eq!(failed[0].2, MbusError::ModbusException(0x02));
3461        assert!(
3462            client_services
3463                .app
3464                .received_holding_register_responses
3465                .borrow()
3466                .is_empty()
3467        );
3468        assert!(client_services.expected_responses.is_empty());
3469    }
3470
3471    #[test]
3472    fn test_serial_exception_discrete_input_response_fails_immediately_with_request_txn_id() {
3473        let mut client_services = make_serial_client();
3474
3475        let txn_id = 0x2003;
3476        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3477        client_services
3478            .read_discrete_inputs(txn_id, unit_id, 0x0000, 8)
3479            .unwrap();
3480
3481        let exception_adu = make_rtu_exception_adu(unit_id, 0x02, 0x02);
3482        client_services
3483            .transport
3484            .recv_frames
3485            .borrow_mut()
3486            .push_back(exception_adu)
3487            .unwrap();
3488
3489        client_services.poll();
3490
3491        let failed = client_services.app().failed_requests.borrow();
3492        assert_eq!(failed.len(), 1);
3493        assert_eq!(failed[0].0, txn_id);
3494        assert_eq!(failed[0].1, unit_id);
3495        assert_eq!(failed[0].2, MbusError::ModbusException(0x02));
3496        assert!(
3497            client_services
3498                .app
3499                .received_discrete_input_responses
3500                .borrow()
3501                .is_empty()
3502        );
3503        assert!(client_services.expected_responses.is_empty());
3504    }
3505
3506    #[test]
3507    fn test_serial_exception_fifo_response_fails_immediately_with_request_txn_id() {
3508        let mut client_services = make_serial_client();
3509
3510        let txn_id = 0x2004;
3511        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3512        client_services
3513            .read_fifo_queue(txn_id, unit_id, 0x0001)
3514            .unwrap();
3515
3516        let exception_adu = make_rtu_exception_adu(unit_id, 0x18, 0x01);
3517        client_services
3518            .transport
3519            .recv_frames
3520            .borrow_mut()
3521            .push_back(exception_adu)
3522            .unwrap();
3523
3524        client_services.poll();
3525
3526        let failed = client_services.app().failed_requests.borrow();
3527        assert_eq!(failed.len(), 1);
3528        assert_eq!(failed[0].0, txn_id);
3529        assert_eq!(failed[0].1, unit_id);
3530        assert_eq!(failed[0].2, MbusError::ModbusException(0x01));
3531        assert!(
3532            client_services
3533                .app
3534                .received_read_fifo_queue_responses
3535                .borrow()
3536                .is_empty()
3537        );
3538        assert!(client_services.expected_responses.is_empty());
3539    }
3540
3541    #[test]
3542    fn test_serial_exception_file_record_response_fails_immediately_with_request_txn_id() {
3543        let mut client_services = make_serial_client();
3544
3545        let txn_id = 0x2005;
3546        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3547        let mut sub_req = SubRequest::new();
3548        sub_req.add_read_sub_request(4, 1, 2).unwrap();
3549        client_services
3550            .read_file_record(txn_id, unit_id, &sub_req)
3551            .unwrap();
3552
3553        let exception_adu = make_rtu_exception_adu(unit_id, 0x14, 0x02);
3554        client_services
3555            .transport
3556            .recv_frames
3557            .borrow_mut()
3558            .push_back(exception_adu)
3559            .unwrap();
3560
3561        client_services.poll();
3562
3563        let failed = client_services.app().failed_requests.borrow();
3564        assert_eq!(failed.len(), 1);
3565        assert_eq!(failed[0].0, txn_id);
3566        assert_eq!(failed[0].1, unit_id);
3567        assert_eq!(failed[0].2, MbusError::ModbusException(0x02));
3568        assert!(
3569            client_services
3570                .app
3571                .received_read_file_record_responses
3572                .borrow()
3573                .is_empty()
3574        );
3575        assert!(client_services.expected_responses.is_empty());
3576    }
3577
3578    #[test]
3579    fn test_serial_exception_diagnostic_response_fails_immediately_with_request_txn_id() {
3580        let mut client_services = make_serial_client();
3581
3582        let txn_id = 0x2006;
3583        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3584        client_services
3585            .read_device_identification(
3586                txn_id,
3587                unit_id,
3588                ReadDeviceIdCode::Basic,
3589                ObjectId::from(0x00),
3590            )
3591            .unwrap();
3592
3593        let exception_adu = make_rtu_exception_adu(unit_id, 0x2B, 0x01);
3594        client_services
3595            .transport
3596            .recv_frames
3597            .borrow_mut()
3598            .push_back(exception_adu)
3599            .unwrap();
3600
3601        client_services.poll();
3602
3603        let failed = client_services.app().failed_requests.borrow();
3604        assert_eq!(failed.len(), 1);
3605        assert_eq!(failed[0].0, txn_id);
3606        assert_eq!(failed[0].1, unit_id);
3607        assert_eq!(failed[0].2, MbusError::ModbusException(0x01));
3608        assert!(
3609            client_services
3610                .app
3611                .received_read_device_id_responses
3612                .borrow()
3613                .is_empty()
3614        );
3615        assert!(client_services.expected_responses.is_empty());
3616    }
3617
3618    /// Test case: `read_single_holding_register` sends a valid ADU.
3619    #[test]
3620    fn test_read_single_holding_register_sends_valid_adu() {
3621        let transport = MockTransport::default();
3622        let app = MockApp::default();
3623        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3624        let mut client_services =
3625            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3626
3627        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3628        client_services
3629            .read_single_holding_register(10, unit_id, 100)
3630            .unwrap();
3631
3632        let sent_frames = client_services.transport.sent_frames.borrow();
3633        assert_eq!(sent_frames.len(), 1);
3634        let sent_adu = sent_frames.front().unwrap();
3635
3636        #[rustfmt::skip]
3637        let expected_adu: [u8; 12] = [
3638            0x00, 0x0A, // TID
3639            0x00, 0x00, // PID
3640            0x00, 0x06, // Length
3641            0x01,       // Unit ID
3642            0x03,       // FC
3643            0x00, 0x64, // Address
3644            0x00, 0x01, // Quantity
3645        ];
3646        assert_eq!(sent_adu.as_slice(), &expected_adu);
3647
3648        // Verify expected response
3649        assert_eq!(client_services.expected_responses.len(), 1);
3650        let single_read = client_services.expected_responses[0]
3651            .operation_meta
3652            .is_single();
3653        assert!(single_read);
3654    }
3655
3656    /// Test case: `ClientServices` successfully sends and processes a `read_single_holding_register` request.
3657    #[test]
3658    fn test_client_services_read_single_holding_register_e2e_success() {
3659        let transport = MockTransport::default();
3660        let app = MockApp::default();
3661        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3662        let mut client_services =
3663            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3664
3665        let txn_id = 10;
3666        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3667        let address = 100;
3668
3669        client_services
3670            .read_single_holding_register(txn_id, unit_id, address)
3671            .unwrap();
3672
3673        // Simulate response
3674        let response_adu = [
3675            0x00, 0x0A, 0x00, 0x00, 0x00, 0x05, 0x01, 0x03, 0x02, 0x12, 0x34,
3676        ];
3677        client_services
3678            .transport
3679            .recv_frames
3680            .borrow_mut()
3681            .push_back(Vec::from_slice(&response_adu).unwrap())
3682            .unwrap();
3683        client_services.poll();
3684
3685        let received_responses = client_services
3686            .app
3687            .received_holding_register_responses
3688            .borrow();
3689        assert_eq!(received_responses.len(), 1);
3690        let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3691        assert_eq!(*rcv_txn_id, txn_id);
3692        assert_eq!(*rcv_unit_id, unit_id);
3693        assert_eq!(rcv_registers.from_address(), address);
3694        assert_eq!(rcv_registers.quantity(), 1);
3695        assert_eq!(&rcv_registers.values()[..1], &[0x1234]);
3696        assert_eq!(*rcv_quantity, 1);
3697    }
3698
3699    /// Test case: `read_single_input_register` sends a valid ADU.
3700    #[test]
3701    fn test_read_single_input_register_sends_valid_adu() {
3702        let transport = MockTransport::default();
3703        let app = MockApp::default();
3704        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3705        let mut client_services =
3706            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3707
3708        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3709        client_services
3710            .read_single_input_register(10, unit_id, 100)
3711            .unwrap();
3712
3713        let sent_frames = client_services.transport.sent_frames.borrow();
3714        assert_eq!(sent_frames.len(), 1);
3715        let sent_adu = sent_frames.front().unwrap();
3716
3717        #[rustfmt::skip]
3718        let expected_adu: [u8; 12] = [
3719            0x00, 0x0A, // TID
3720            0x00, 0x00, // PID
3721            0x00, 0x06, // Length
3722            0x01,       // Unit ID
3723            0x04,       // FC (Read Input Registers)
3724            0x00, 0x64, // Address
3725            0x00, 0x01, // Quantity
3726        ];
3727        assert_eq!(sent_adu.as_slice(), &expected_adu);
3728
3729        // Verify expected response
3730        assert_eq!(client_services.expected_responses.len(), 1);
3731        let single_read = client_services.expected_responses[0]
3732            .operation_meta
3733            .is_single();
3734        assert!(single_read);
3735    }
3736
3737    /// Test case: `ClientServices` successfully sends and processes a `read_single_input_register` request.
3738    #[test]
3739    fn test_client_services_read_single_input_register_e2e_success() {
3740        let transport = MockTransport::default();
3741        let app = MockApp::default();
3742        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3743        let mut client_services =
3744            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3745
3746        let txn_id = 10;
3747        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3748        let address = 100;
3749
3750        client_services
3751            .read_single_input_register(txn_id, unit_id, address)
3752            .unwrap();
3753
3754        // Simulate response
3755        // ADU: TID(10), PID(0), Len(5), Unit(1), FC(4), ByteCount(2), Data(0x1234)
3756        let response_adu = [
3757            0x00, 0x0A, 0x00, 0x00, 0x00, 0x05, 0x01, 0x04, 0x02, 0x12, 0x34,
3758        ];
3759        client_services
3760            .transport
3761            .recv_frames
3762            .borrow_mut()
3763            .push_back(Vec::from_slice(&response_adu).unwrap())
3764            .unwrap();
3765        client_services.poll();
3766
3767        let received_responses = client_services
3768            .app
3769            .received_input_register_responses
3770            .borrow();
3771        assert_eq!(received_responses.len(), 1);
3772        let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3773        assert_eq!(*rcv_txn_id, txn_id);
3774        assert_eq!(*rcv_unit_id, unit_id);
3775        assert_eq!(rcv_registers.from_address(), address);
3776        assert_eq!(rcv_registers.quantity(), 1);
3777        assert_eq!(&rcv_registers.values()[..1], &[0x1234]);
3778        assert_eq!(*rcv_quantity, 1);
3779    }
3780
3781    /// Test case: `read_write_multiple_registers` sends a valid ADU.
3782    #[test]
3783    fn test_read_write_multiple_registers_sends_valid_adu() {
3784        let transport = MockTransport::default();
3785        let app = MockApp::default();
3786        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3787        let mut client_services =
3788            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3789
3790        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3791        let write_values = [0xAAAA, 0xBBBB];
3792        client_services
3793            .read_write_multiple_registers(11, unit_id, 10, 2, 20, &write_values)
3794            .unwrap();
3795
3796        let sent_frames = client_services.transport.sent_frames.borrow();
3797        assert_eq!(sent_frames.len(), 1);
3798        let sent_adu = sent_frames.front().unwrap();
3799
3800        #[rustfmt::skip]
3801        let expected_adu: [u8; 21] = [
3802            0x00, 0x0B, // TID
3803            0x00, 0x00, // PID
3804            0x00, 0x0F, // Length
3805            0x01,       // Unit ID
3806            0x17,       // FC
3807            0x00, 0x0A, // Read Address
3808            0x00, 0x02, // Read Quantity
3809            0x00, 0x14, // Write Address
3810            0x00, 0x02, // Write Quantity
3811            0x04,       // Write Byte Count
3812            0xAA, 0xAA, // Write Value 1
3813            0xBB, 0xBB, // Write Value 2
3814        ];
3815        assert_eq!(sent_adu.as_slice(), &expected_adu);
3816    }
3817
3818    /// Test case: `mask_write_register` sends a valid ADU.
3819    #[test]
3820    fn test_mask_write_register_sends_valid_adu() {
3821        let transport = MockTransport::default();
3822        let app = MockApp::default();
3823        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3824        let mut client_services =
3825            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3826
3827        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3828        client_services
3829            .mask_write_register(12, unit_id, 30, 0xF0F0, 0x0F0F)
3830            .unwrap();
3831
3832        let sent_frames = client_services.transport.sent_frames.borrow();
3833        assert_eq!(sent_frames.len(), 1);
3834        let sent_adu = sent_frames.front().unwrap();
3835
3836        #[rustfmt::skip]
3837        let expected_adu: [u8; 14] = [
3838            0x00, 0x0C, // TID
3839            0x00, 0x00, // PID
3840            0x00, 0x08, // Length
3841            0x01,       // Unit ID
3842            0x16,       // FC
3843            0x00, 0x1E, // Address
3844            0xF0, 0xF0, // AND mask
3845            0x0F, 0x0F, // OR mask
3846        ];
3847        assert_eq!(sent_adu.as_slice(), &expected_adu);
3848    }
3849
3850    /// Test case: `ClientServices` successfully sends and processes a `read_write_multiple_registers` request.
3851    #[test]
3852    fn test_client_services_read_write_multiple_registers_e2e_success() {
3853        let transport = MockTransport::default();
3854        let app = MockApp::default();
3855        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3856        let mut client_services =
3857            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3858
3859        let txn_id = 11;
3860        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3861        let read_address = 10;
3862        let read_quantity = 2;
3863        let write_address = 20;
3864        let write_values = [0xAAAA, 0xBBBB];
3865
3866        client_services
3867            .read_write_multiple_registers(
3868                txn_id,
3869                unit_id,
3870                read_address,
3871                read_quantity,
3872                write_address,
3873                &write_values,
3874            )
3875            .unwrap();
3876
3877        // Simulate response
3878        let response_adu = [
3879            0x00, 0x0B, 0x00, 0x00, 0x00, 0x07, 0x01, 0x17, 0x04, 0x12, 0x34, 0x56, 0x78,
3880        ];
3881        client_services
3882            .transport
3883            .recv_frames
3884            .borrow_mut()
3885            .push_back(Vec::from_slice(&response_adu).unwrap())
3886            .unwrap();
3887        client_services.poll();
3888
3889        let received_responses = client_services
3890            .app
3891            .received_read_write_multiple_registers_responses
3892            .borrow();
3893        assert_eq!(received_responses.len(), 1);
3894        let (rcv_txn_id, rcv_unit_id, rcv_registers) = &received_responses[0];
3895        assert_eq!(*rcv_txn_id, txn_id);
3896        assert_eq!(*rcv_unit_id, unit_id);
3897        assert_eq!(rcv_registers.from_address(), read_address);
3898        assert_eq!(rcv_registers.quantity(), read_quantity);
3899        assert_eq!(&rcv_registers.values()[..2], &[0x1234, 0x5678]);
3900    }
3901
3902    /// Test case: `ClientServices` successfully sends and processes a `mask_write_register` request.
3903    #[test]
3904    fn test_client_services_mask_write_register_e2e_success() {
3905        let transport = MockTransport::default();
3906        let app = MockApp::default();
3907        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3908        let mut client_services =
3909            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3910
3911        let txn_id = 12;
3912        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3913        let address = 30;
3914        let and_mask = 0xF0F0;
3915        let or_mask = 0x0F0F;
3916
3917        client_services
3918            .mask_write_register(txn_id, unit_id, address, and_mask, or_mask)
3919            .unwrap();
3920
3921        // Simulate response
3922        let response_adu = [
3923            0x00, 0x0C, 0x00, 0x00, 0x00, 0x08, 0x01, 0x16, 0x00, 0x1E, 0xF0, 0xF0, 0x0F, 0x0F,
3924        ];
3925        client_services
3926            .transport
3927            .recv_frames
3928            .borrow_mut()
3929            .push_back(Vec::from_slice(&response_adu).unwrap())
3930            .unwrap();
3931        client_services.poll();
3932
3933        let received_responses = client_services
3934            .app
3935            .received_mask_write_register_responses
3936            .borrow();
3937        assert_eq!(received_responses.len(), 1);
3938        let (rcv_txn_id, rcv_unit_id) = &received_responses[0];
3939        assert_eq!(*rcv_txn_id, txn_id);
3940        assert_eq!(*rcv_unit_id, unit_id);
3941    }
3942
3943    /// Test case: `ClientServices` successfully sends and processes a `read_fifo_queue` request.
3944    #[test]
3945    fn test_client_services_read_fifo_queue_e2e_success() {
3946        let transport = MockTransport::default();
3947        let app = MockApp::default();
3948        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3949        let mut client_services =
3950            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3951
3952        let txn_id = 13;
3953        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3954        let address = 40;
3955
3956        client_services
3957            .read_fifo_queue(txn_id, unit_id, address)
3958            .unwrap();
3959
3960        // Simulate response
3961        #[rustfmt::skip]
3962        let response_adu = [
3963            0x00, 0x0D, // Transaction ID
3964            0x00, 0x00, // Protocol ID
3965            0x00, 0x0A, // Length (Unit ID + PDU)
3966            0x01,       // Unit ID
3967            0x18,       // Function Code (Read FIFO Queue)
3968            0x00, 0x06, // FIFO Byte Count (2 bytes for FIFO Count + 2 * 2 bytes for values)
3969            0x00, 0x02, // FIFO Count (2 registers)
3970            0xAA, 0xAA, // Register Value 1
3971            0xBB, 0xBB, // Register Value 2
3972        ];
3973        client_services
3974            .transport
3975            .recv_frames
3976            .borrow_mut()
3977            .push_back(Vec::from_slice(&response_adu).unwrap())
3978            .unwrap();
3979        client_services.poll();
3980
3981        let received_responses = client_services
3982            .app
3983            .received_read_fifo_queue_responses
3984            .borrow();
3985        assert_eq!(received_responses.len(), 1);
3986        let (rcv_txn_id, rcv_unit_id, rcv_fifo_queue) = &received_responses[0];
3987        assert_eq!(*rcv_txn_id, txn_id);
3988        assert_eq!(*rcv_unit_id, unit_id);
3989        assert_eq!(rcv_fifo_queue.length(), 2);
3990        assert_eq!(&rcv_fifo_queue.queue()[..2], &[0xAAAA, 0xBBBB]);
3991    }
3992
3993    /// Test case: `ClientServices` successfully sends and processes a `read_file_record` request.
3994    #[test]
3995    fn test_client_services_read_file_record_e2e_success() {
3996        let transport = MockTransport::default();
3997        let app = MockApp::default();
3998        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3999        let mut client_services =
4000            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4001
4002        let txn_id = 14;
4003        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4004        let mut sub_req = SubRequest::new();
4005        sub_req.add_read_sub_request(4, 1, 2).unwrap();
4006
4007        client_services
4008            .read_file_record(txn_id, unit_id, &sub_req)
4009            .unwrap();
4010
4011        // Simulate response: FC(20), ByteCount(7), SubReqLen(6), Ref(6), Data(0x1234, 0x5678)
4012        // Note: ByteCount = 1 (SubReqLen) + 1 (Ref) + 4 (Data) + 1 (SubReqLen for next?) No.
4013        // Response format: ByteCount, [Len, Ref, Data...]
4014        // Len = 1 (Ref) + 4 (Data) = 5.
4015        // ByteCount = 1 (Len) + 5 = 6.
4016        let response_adu = [
4017            0x00, 0x0E, 0x00, 0x00, 0x00, 0x09, 0x01, 0x14, 0x06, 0x05, 0x06, 0x12, 0x34, 0x56,
4018            0x78,
4019        ];
4020
4021        client_services
4022            .transport
4023            .recv_frames
4024            .borrow_mut()
4025            .push_back(Vec::from_slice(&response_adu).unwrap())
4026            .unwrap();
4027        client_services.poll();
4028
4029        let received_responses = client_services
4030            .app
4031            .received_read_file_record_responses
4032            .borrow();
4033        assert_eq!(received_responses.len(), 1);
4034        let (rcv_txn_id, rcv_unit_id, rcv_data) = &received_responses[0];
4035        assert_eq!(*rcv_txn_id, txn_id);
4036        assert_eq!(*rcv_unit_id, unit_id);
4037        assert_eq!(rcv_data.len(), 1);
4038        assert_eq!(
4039            rcv_data[0].record_data.as_ref().unwrap().as_slice(),
4040            &[0x1234, 0x5678]
4041        );
4042    }
4043
4044    /// Test case: `ClientServices` successfully sends and processes a `write_file_record` request.
4045    #[test]
4046    fn test_client_services_write_file_record_e2e_success() {
4047        let transport = MockTransport::default();
4048        let app = MockApp::default();
4049        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4050        let mut client_services =
4051            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4052
4053        let txn_id = 15;
4054        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4055        let mut sub_req = SubRequest::new();
4056        let mut data = Vec::new();
4057        data.push(0x1122).unwrap();
4058        sub_req.add_write_sub_request(4, 1, 1, data).unwrap();
4059
4060        client_services
4061            .write_file_record(txn_id, unit_id, &sub_req)
4062            .unwrap();
4063
4064        // Simulate response (Echo of request)
4065        // FC(21), ByteCount(9), Ref(6), File(4), Rec(1), Len(1), Data(0x1122)
4066        let response_adu = [
4067            0x00, 0x0F, 0x00, 0x00, 0x00, 0x0C, 0x01, 0x15, 0x09, 0x06, 0x00, 0x04, 0x00, 0x01,
4068            0x00, 0x01, 0x11, 0x22,
4069        ];
4070
4071        client_services
4072            .transport
4073            .recv_frames
4074            .borrow_mut()
4075            .push_back(Vec::from_slice(&response_adu).unwrap())
4076            .unwrap();
4077        client_services.poll();
4078
4079        let received_responses = client_services
4080            .app
4081            .received_write_file_record_responses
4082            .borrow();
4083        assert_eq!(received_responses.len(), 1);
4084        let (rcv_txn_id, rcv_unit_id) = &received_responses[0];
4085        assert_eq!(*rcv_txn_id, txn_id);
4086        assert_eq!(*rcv_unit_id, unit_id);
4087    }
4088
4089    /// Test case: `ClientServices` successfully sends and processes a `read_discrete_inputs` request.
4090    #[test]
4091    fn test_client_services_read_discrete_inputs_e2e_success() {
4092        let transport = MockTransport::default();
4093        let app = MockApp::default();
4094        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4095        let mut client_services =
4096            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4097
4098        let txn_id = 16;
4099        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4100        let address = 50;
4101        let quantity = 8;
4102
4103        client_services
4104            .read_discrete_inputs(txn_id, unit_id, address, quantity)
4105            .unwrap();
4106
4107        // Simulate response: FC(02), ByteCount(1), Data(0xAA)
4108        let response_adu = [0x00, 0x10, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x01, 0xAA];
4109
4110        client_services
4111            .transport
4112            .recv_frames
4113            .borrow_mut()
4114            .push_back(Vec::from_slice(&response_adu).unwrap())
4115            .unwrap();
4116        client_services.poll();
4117
4118        let received_responses = client_services
4119            .app
4120            .received_discrete_input_responses
4121            .borrow();
4122        assert_eq!(received_responses.len(), 1);
4123        let (rcv_txn_id, rcv_unit_id, rcv_inputs, rcv_quantity) = &received_responses[0];
4124        assert_eq!(*rcv_txn_id, txn_id);
4125        assert_eq!(*rcv_unit_id, unit_id);
4126        assert_eq!(rcv_inputs.from_address(), address);
4127        assert_eq!(rcv_inputs.quantity(), quantity);
4128        assert_eq!(rcv_inputs.values(), &[0xAA]);
4129        assert_eq!(*rcv_quantity, quantity);
4130    }
4131
4132    /// Test case: `ClientServices` successfully sends and processes a `read_single_discrete_input` request.
4133    #[test]
4134    fn test_client_services_read_single_discrete_input_e2e_success() {
4135        let transport = MockTransport::default();
4136        let app = MockApp::default();
4137        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4138        let mut client_services =
4139            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4140
4141        let txn_id = 17;
4142        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4143        let address = 10;
4144
4145        client_services
4146            .read_single_discrete_input(txn_id, unit_id, address)
4147            .unwrap();
4148
4149        // Verify request ADU
4150        let sent_frames = client_services.transport.sent_frames.borrow();
4151        assert_eq!(sent_frames.len(), 1);
4152        // MBAP(7) + PDU(5) = 12 bytes
4153        // MBAP: 00 11 00 00 00 06 01
4154        // PDU: 02 00 0A 00 01
4155        let expected_request = [
4156            0x00, 0x11, 0x00, 0x00, 0x00, 0x06, 0x01, 0x02, 0x00, 0x0A, 0x00, 0x01,
4157        ];
4158        assert_eq!(sent_frames.front().unwrap().as_slice(), &expected_request);
4159        drop(sent_frames);
4160
4161        // Simulate response: FC(02), ByteCount(1), Data(0x01) -> Input ON
4162        let response_adu = [0x00, 0x11, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x01, 0x01];
4163
4164        client_services
4165            .transport
4166            .recv_frames
4167            .borrow_mut()
4168            .push_back(Vec::from_slice(&response_adu).unwrap())
4169            .unwrap();
4170        client_services.poll();
4171
4172        let received_responses = client_services
4173            .app
4174            .received_discrete_input_responses
4175            .borrow();
4176        assert_eq!(received_responses.len(), 1);
4177        let (rcv_txn_id, rcv_unit_id, rcv_inputs, rcv_quantity) = &received_responses[0];
4178        assert_eq!(*rcv_txn_id, txn_id);
4179        assert_eq!(*rcv_unit_id, unit_id);
4180        assert_eq!(rcv_inputs.from_address(), address);
4181        assert_eq!(rcv_inputs.quantity(), 1);
4182        assert_eq!(rcv_inputs.value(address).unwrap(), true);
4183        assert_eq!(*rcv_quantity, 1);
4184    }
4185
4186    /// Test case: `ClientServices` successfully sends and processes a `read_device_identification` request.
4187    #[test]
4188    fn test_client_services_read_device_identification_e2e_success() {
4189        let transport = MockTransport::default();
4190        let app = MockApp::default();
4191        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4192        let mut client_services =
4193            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4194
4195        let txn_id = 20;
4196        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4197        let read_code = ReadDeviceIdCode::Basic;
4198        let object_id = ObjectId::from(0x00);
4199
4200        client_services
4201            .read_device_identification(txn_id, unit_id, read_code, object_id)
4202            .unwrap();
4203
4204        // Verify request ADU
4205        let sent_frames = client_services.transport.sent_frames.borrow();
4206        assert_eq!(sent_frames.len(), 1);
4207        // MBAP(7) + PDU(4) = 11 bytes
4208        // MBAP: 00 14 00 00 00 05 01
4209        // PDU: 2B 0E 01 00
4210        let expected_request = [
4211            0x00, 0x14, 0x00, 0x00, 0x00, 0x05, 0x01, 0x2B, 0x0E, 0x01, 0x00,
4212        ];
4213        assert_eq!(sent_frames.front().unwrap().as_slice(), &expected_request);
4214        drop(sent_frames);
4215
4216        // Simulate response:
4217        // MEI(0E), Code(01), Conf(81), More(00), Next(00), Num(01), Obj0(00), Len(03), Val("Foo")
4218        // PDU Len = 1(MEI) + 1(Code) + 1(Conf) + 1(More) + 1(Next) + 1(Num) + 1(Id) + 1(Len) + 3(Val) = 11
4219        // MBAP Len = 1(Unit) + 1(FC) + 11 = 13
4220        let response_adu = [
4221            0x00, 0x14, 0x00, 0x00, 0x00, 0x0D, 0x01, 0x2B, 0x0E, 0x01, 0x81, 0x00, 0x00, 0x01,
4222            0x00, 0x03, 0x46, 0x6F, 0x6F,
4223        ];
4224
4225        client_services
4226            .transport
4227            .recv_frames
4228            .borrow_mut()
4229            .push_back(Vec::from_slice(&response_adu).unwrap())
4230            .unwrap();
4231        client_services.poll();
4232
4233        let received_responses = client_services
4234            .app
4235            .received_read_device_id_responses
4236            .borrow();
4237        assert_eq!(received_responses.len(), 1);
4238        let (rcv_txn_id, rcv_unit_id, rcv_resp) = &received_responses[0];
4239        assert_eq!(*rcv_txn_id, txn_id);
4240        assert_eq!(*rcv_unit_id, unit_id);
4241        assert_eq!(rcv_resp.read_device_id_code, ReadDeviceIdCode::Basic);
4242        assert_eq!(
4243            rcv_resp.conformity_level,
4244            ConformityLevel::BasicStreamAndIndividual
4245        );
4246        assert_eq!(rcv_resp.number_of_objects, 1);
4247
4248        // Ensure the correct raw bytes were stored for the parsed objects (Id: 0x00, Len: 0x03, Val: "Foo")
4249        assert_eq!(&rcv_resp.objects_data[..5], &[0x00, 0x03, 0x46, 0x6F, 0x6F]);
4250    }
4251
4252    /// Test case: `ClientServices` handles multiple concurrent `read_device_identification` requests.
4253    #[test]
4254    fn test_client_services_read_device_identification_multi_transaction() {
4255        let transport = MockTransport::default();
4256        let app = MockApp::default();
4257        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4258        let mut client_services =
4259            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4260
4261        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4262        // Request 1
4263        let txn_id_1 = 21;
4264        client_services
4265            .read_device_identification(
4266                txn_id_1,
4267                unit_id,
4268                ReadDeviceIdCode::Basic,
4269                ObjectId::from(0x00),
4270            )
4271            .unwrap();
4272
4273        // Request 2
4274        let txn_id_2 = 22;
4275        client_services
4276            .read_device_identification(
4277                txn_id_2,
4278                unit_id,
4279                ReadDeviceIdCode::Regular,
4280                ObjectId::from(0x00),
4281            )
4282            .unwrap();
4283
4284        assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
4285
4286        // Response for Request 2 (Out of order arrival)
4287        // MEI(0E), Code(02), Conf(82), More(00), Next(00), Num(00)
4288        // PDU Len = 6. MBAP Len = 1 + 1 + 6 = 8.
4289        let response_adu_2 = [
4290            0x00, 0x16, 0x00, 0x00, 0x00, 0x08, 0x01, 0x2B, 0x0E, 0x02, 0x82, 0x00, 0x00, 0x00,
4291        ];
4292        client_services
4293            .transport
4294            .recv_frames
4295            .borrow_mut()
4296            .push_back(Vec::from_slice(&response_adu_2).unwrap())
4297            .unwrap();
4298
4299        client_services.poll();
4300
4301        {
4302            let received_responses = client_services
4303                .app
4304                .received_read_device_id_responses
4305                .borrow();
4306            assert_eq!(received_responses.len(), 1);
4307            assert_eq!(received_responses[0].0, txn_id_2);
4308            assert_eq!(
4309                received_responses[0].2.read_device_id_code,
4310                ReadDeviceIdCode::Regular
4311            );
4312        }
4313
4314        // Response for Request 1
4315        // MEI(0E), Code(01), Conf(81), More(00), Next(00), Num(00)
4316        let response_adu_1 = [
4317            0x00, 0x15, 0x00, 0x00, 0x00, 0x08, 0x01, 0x2B, 0x0E, 0x01, 0x81, 0x00, 0x00, 0x00,
4318        ];
4319        client_services
4320            .transport
4321            .recv_frames
4322            .borrow_mut()
4323            .push_back(Vec::from_slice(&response_adu_1).unwrap())
4324            .unwrap();
4325
4326        client_services.poll();
4327
4328        {
4329            let received_responses = client_services
4330                .app
4331                .received_read_device_id_responses
4332                .borrow();
4333            assert_eq!(received_responses.len(), 2);
4334            assert_eq!(received_responses[1].0, txn_id_1);
4335            assert_eq!(
4336                received_responses[1].2.read_device_id_code,
4337                ReadDeviceIdCode::Basic
4338            );
4339        }
4340    }
4341
4342    /// Test case: `ClientServices` rejects a response where the echoed Read Device ID Code does not match the request.
4343    #[test]
4344    fn test_client_services_read_device_identification_mismatch_code() {
4345        let transport = MockTransport::default();
4346        let app = MockApp::default();
4347        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4348        let mut client_services =
4349            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4350
4351        let txn_id = 30;
4352        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4353        // We request BASIC (0x01)
4354        client_services
4355            .read_device_identification(
4356                txn_id,
4357                unit_id,
4358                ReadDeviceIdCode::Basic,
4359                ObjectId::from(0x00),
4360            )
4361            .unwrap();
4362
4363        // Server responds with REGULAR (0x02) - This is a protocol violation or mismatch
4364        // MEI(0E), Code(02), Conf(81), More(00), Next(00), Num(00)
4365        let response_adu = [
4366            0x00, 0x1E, 0x00, 0x00, 0x00, 0x08, 0x01, 0x2B, 0x0E, 0x02, 0x81, 0x00, 0x00, 0x00,
4367        ];
4368
4369        client_services
4370            .transport
4371            .recv_frames
4372            .borrow_mut()
4373            .push_back(Vec::from_slice(&response_adu).unwrap())
4374            .unwrap();
4375
4376        client_services.poll();
4377
4378        // Verify success callback was NOT called
4379        assert!(
4380            client_services
4381                .app
4382                .received_read_device_id_responses
4383                .borrow()
4384                .is_empty()
4385        );
4386
4387        // Verify failure callback WAS called with UnexpectedResponse
4388        let failed = client_services.app().failed_requests.borrow();
4389        assert_eq!(failed.len(), 1);
4390        assert_eq!(failed[0].2, MbusError::InvalidDeviceIdentification);
4391    }
4392
4393    /// Test case: `read_exception_status` sends a valid ADU and processes response.
4394    #[test]
4395    fn test_client_services_read_exception_status_e2e_success() {
4396        let transport = MockTransport::default();
4397        let app = MockApp::default();
4398        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4399        let mut client_services =
4400            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4401
4402        let txn_id = 40;
4403        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4404
4405        let err = client_services.read_exception_status(txn_id, unit_id).err();
4406        // Error is expected since the service only available in serial transport.
4407        assert_eq!(err, Some(MbusError::InvalidTransport));
4408    }
4409
4410    /// Test case: `diagnostics` (Sub-function 00) Query Data sends valid ADU.
4411    #[test]
4412    fn test_client_services_diagnostics_query_data_success() {
4413        let transport = MockTransport::default();
4414        let app = MockApp::default();
4415        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4416        let mut client_services =
4417            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4418
4419        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4420        let data = [0x1234, 0x5678];
4421        let sub_function = DiagnosticSubFunction::ReturnQueryData;
4422        let err = client_services
4423            .diagnostics(50, unit_id, sub_function, &data)
4424            .err();
4425        assert_eq!(err, Some(MbusError::InvalidTransport));
4426    }
4427
4428    /// Test case: `get_comm_event_counter` sends valid ADU.
4429    #[test]
4430    fn test_client_services_get_comm_event_counter_success() {
4431        let transport = MockTransport::default();
4432        let app = MockApp::default();
4433        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4434        let mut client_services =
4435            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4436        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4437        let err = client_services.get_comm_event_counter(60, unit_id).err();
4438
4439        assert_eq!(err, Some(MbusError::InvalidTransport));
4440    }
4441
4442    /// Test case: `report_server_id` sends valid ADU.
4443    #[test]
4444    fn test_client_services_report_server_id_success() {
4445        let transport = MockTransport::default();
4446        let app = MockApp::default();
4447        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4448        let mut client_services =
4449            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4450
4451        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4452        let err = client_services.report_server_id(70, unit_id).err();
4453
4454        assert_eq!(err, Some(MbusError::InvalidTransport));
4455    }
4456
4457    // --- Broadcast Tests ---
4458
4459    /// Test case: Broadcast read multiple coils is not allowed
4460    #[test]
4461    fn test_broadcast_read_multiple_coils_not_allowed() {
4462        let transport = MockTransport::default();
4463        let app = MockApp::default();
4464        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4465        let mut client_services =
4466            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4467
4468        let txn_id = 0x0001;
4469        let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
4470        let address = 0x0000;
4471        let quantity = 8;
4472        let res = client_services.read_multiple_coils(txn_id, unit_id, address, quantity);
4473        assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
4474    }
4475
4476    /// Test case: Broadcast write single coil on TCP is not allowed
4477    #[test]
4478    fn test_broadcast_write_single_coil_tcp_not_allowed() {
4479        let transport = MockTransport::default();
4480        let app = MockApp::default();
4481        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4482        let mut client_services =
4483            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4484
4485        let txn_id = 0x0002;
4486        let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
4487        let res = client_services.write_single_coil(txn_id, unit_id, 0x0000, true);
4488        assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
4489    }
4490
4491    /// Test case: Broadcast write multiple coils on TCP is not allowed
4492    #[test]
4493    fn test_broadcast_write_multiple_coils_tcp_not_allowed() {
4494        let transport = MockTransport::default();
4495        let app = MockApp::default();
4496        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4497        let mut client_services =
4498            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4499
4500        let txn_id = 0x0003;
4501        let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
4502        let mut values = Coils::new(0x0000, 2).unwrap();
4503        values.set_value(0x0000, true).unwrap();
4504        values.set_value(0x0001, false).unwrap();
4505
4506        let res = client_services.write_multiple_coils(txn_id, unit_id, 0x0000, &values);
4507        assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
4508    }
4509
4510    /// Test case: Broadcast read discrete inputs is not allowed
4511    #[test]
4512    fn test_broadcast_read_discrete_inputs_not_allowed() {
4513        let transport = MockTransport::default();
4514        let app = MockApp::default();
4515        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4516        let mut client_services =
4517            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4518
4519        let txn_id = 0x0006;
4520        let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
4521        let res = client_services.read_discrete_inputs(txn_id, unit_id, 0x0000, 2);
4522        assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
4523    }
4524
4525    /// Test case: `poll` clears the internal receive buffer if it overflows with garbage bytes.
4526    /// This simulates a high-noise environment where fragments accumulate beyond `MAX_ADU_FRAME_LEN`.
4527    #[test]
4528    fn test_client_services_clears_buffer_on_overflow() {
4529        let transport = MockTransport::default();
4530        let app = MockApp::default();
4531        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4532        let mut client_services =
4533            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4534
4535        // Fill the internal buffer close to its capacity (MAX_ADU_FRAME_LEN = 513) with unparsable garbage
4536        let initial_garbage = [0xFF; MAX_ADU_FRAME_LEN - 10];
4537        client_services
4538            .rxed_frame
4539            .extend_from_slice(&initial_garbage)
4540            .unwrap();
4541
4542        // Inject another chunk of bytes that will cause an overflow when appended
4543        let chunk = [0xAA; 20];
4544        client_services
4545            .transport
4546            .recv_frames
4547            .borrow_mut()
4548            .push_back(Vec::from_slice(&chunk).unwrap())
4549            .unwrap();
4550
4551        // Poll should attempt to extend the buffer, fail because 503 + 20 > 513, and clear the buffer to recover.
4552        client_services.poll();
4553
4554        assert!(
4555            client_services.rxed_frame.is_empty(),
4556            "Buffer should be cleared on overflow to prevent crashing and recover from stream noise."
4557        );
4558    }
4559}