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 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 == 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                    txn_id,
807                    unit_id_or_slave_addr.get()
808                );
809                return;
810            }
811        };
812
813        client_log_trace!(
814            "dispatching response: txn_id={}, unit_id_or_slave_addr={}, queue_len_after_pop={}",
815            txn_id,
816            unit_id_or_slave_addr.get(),
817            self.expected_responses.len()
818        );
819
820        // If the Modbus server replied with an exception, notify the application layer
821        // immediately instead of attempting to parse it as a successful response.
822        if let Some(exception_code) = message.pdu().error_code() {
823            client_log_debug!(
824                "modbus exception response: txn_id={}, unit_id_or_slave_addr={}, code=0x{:02X}",
825                txn_id,
826                unit_id_or_slave_addr.get(),
827                exception_code
828            );
829            self.app.request_failed(
830                txn_id,
831                unit_id_or_slave_addr,
832                MbusError::ModbusException(exception_code),
833            );
834            return;
835        }
836
837        (expected.handler)(self, &expected, message);
838    }
839}
840
841impl<TRANSPORT, APP, const N: usize> ClientServices<TRANSPORT, APP, N>
842where
843    TRANSPORT: Transport,
844    TRANSPORT::Error: Into<MbusError>,
845    APP: RequestErrorNotifier + TimeKeeper,
846{
847    /// The main execution loop for the Modbus Client.
848    ///
849    /// This method orchestrates the entire lifecycle of Modbus transactions by performing
850    /// three critical tasks in a non-blocking manner:
851    ///
852    /// ### 1. Data Ingestion & Stream Resynchronization
853    /// It pulls raw bytes from the `TRANSPORT` layer into an internal `rxed_frame` buffer.
854    /// Because Modbus streams (especially Serial) can contain noise or fragmented packets,
855    /// the logic handles:
856    /// * **Fragmentation**: If a partial frame is received, it stays in the buffer until more data arrives.
857    /// * **Pipelining**: If multiple ADUs are received in a single TCP packet, it processes them sequentially.
858    /// * **Noise Recovery**: If the buffer contains garbage that doesn't form a valid Modbus header,
859    ///   it drops bytes one-by-one to "slide" the window and find the next valid start-of-frame.
860    ///
861    /// ### 2. Response Dispatching
862    /// Once a complete ADU is validated (via checksums in RTU or length checks in TCP), it is
863    /// decompiled into a `ModbusMessage`. The client then:
864    /// * Matches the response to an `ExpectedResponse` using the **Transaction ID** (TCP)
865    ///   or **Unit ID/Slave Address** (Serial, where only one request is active at a time).
866    /// * Validates the Function Code and handles Modbus Exceptions (0x80 + FC).
867    /// * Routes the payload to the specific `handler` (e.g., `handle_read_coils_rsp`) which
868    ///   ultimately triggers the user-defined callback in the `APP` layer.
869    ///
870    /// ### 3. Timeout & Retry Management
871    /// The client maintains a queue of "Outstanding Requests". For every poll:
872    /// * It checks if the `current_millis` (provided by `APP`) has exceeded the `sent_timestamp`
873    ///   plus the configured `response_timeout_ms`.
874    /// * **Scheduled Retries**: If a timeout occurs and `retries_left > 0`, the next retry is
875    ///   scheduled using the configured backoff strategy (and optional jitter).
876    /// * Scheduled retries are only sent when the poll loop reaches or passes the scheduled
877    ///   retry timestamp. The client never sleeps or blocks internally.
878    /// * **Connection Loss Handling**: If `recv()` reports a connection-level transport error
879    ///   (or transport reports disconnected state), all pending requests are immediately failed
880    ///   with `MbusError::ConnectionLost` and removed from the queue.
881    /// * **Failure Notification**: If all retries are exhausted, the request is dropped from
882    ///   the queue, and `app.request_failed` is called with `MbusError::NoRetriesLeft`.
883    ///
884    /// ### Performance Note
885    /// This method uses a `next_timeout_check` cache. If the earliest possible timeout is in
886    /// the future, it skips the O(N) scan of the expected responses queue, making it
887    /// highly efficient for high-concurrency TCP scenarios.
888    ///
889    /// # Constraints
890    /// * For **Serial** transports, the queue size `N` **must** be 1 (1 is default) to comply with the
891    ///   half-duplex nature of RS-485/RS-232.
892    /// * For **TCP**, `N` can be larger to support request pipelining.
893    pub fn poll(&mut self) {
894        // 1. Attempt to receive a frame
895        match self.transport.recv() {
896            Ok(frame) => {
897                client_log_trace!("received {} transport bytes", frame.len());
898                if self.rxed_frame.extend_from_slice(frame.as_slice()).is_err() {
899                    // Buffer overflowed without forming a valid frame. Must be noise.
900                    client_log_debug!(
901                        "received frame buffer overflow while appending {} bytes; clearing receive buffer",
902                        frame.len()
903                    );
904                    self.rxed_frame.clear();
905                }
906
907                // Process as many pipelined/concatenated frames as exist in the buffer
908                while !self.rxed_frame.is_empty() {
909                    match self.ingest_frame() {
910                        Ok(consumed) => {
911                            client_log_trace!(
912                                "ingested complete frame consuming {} bytes from rx buffer len {}",
913                                consumed,
914                                self.rxed_frame.len()
915                            );
916                            let len = self.rxed_frame.len();
917                            if consumed < len {
918                                // Shift array to the left to drain processed bytes.
919                                self.rxed_frame.copy_within(consumed.., 0);
920                                self.rxed_frame.truncate(len - consumed);
921                            } else {
922                                self.rxed_frame.clear();
923                            }
924                        }
925                        Err(MbusError::BufferTooSmall) => {
926                            // Reached an incomplete frame, break and wait for more bytes
927                            client_log_trace!(
928                                "incomplete frame in rx buffer; waiting for more bytes (buffer_len={})",
929                                self.rxed_frame.len()
930                            );
931                            break;
932                        }
933                        Err(err) => {
934                            // Garbage or parsing error, drop the first byte and try again to resync the stream
935                            client_log_debug!(
936                                "frame parse/resync event: error={:?}, buffer_len={}; dropping 1 byte",
937                                err,
938                                self.rxed_frame.len()
939                            );
940                            let len = self.rxed_frame.len();
941                            if len > 1 {
942                                self.rxed_frame.copy_within(1.., 0);
943                                self.rxed_frame.truncate(len - 1);
944                            } else {
945                                self.rxed_frame.clear();
946                            }
947                        }
948                    }
949                }
950            }
951            Err(err) => {
952                let recv_error: MbusError = err.into();
953                let is_connection_loss = matches!(
954                    recv_error,
955                    MbusError::ConnectionClosed
956                        | MbusError::ConnectionFailed
957                        | MbusError::ConnectionLost
958                        | MbusError::IoError
959                ) || !self.transport.is_connected();
960
961                if is_connection_loss {
962                    client_log_debug!(
963                        "connection loss detected during poll: error={:?}, pending_requests={}",
964                        recv_error,
965                        self.expected_responses.len()
966                    );
967                    self.fail_all_pending_requests(MbusError::ConnectionLost);
968                    let _ = self.transport.disconnect();
969                    self.rxed_frame.clear();
970                } else {
971                    client_log_trace!("non-fatal recv status during poll: {:?}", recv_error);
972                }
973            }
974        }
975
976        // 2. Check for timed-out requests and handle retries for all outstanding requests
977        self.handle_timeouts();
978    }
979
980    fn fail_all_pending_requests(&mut self, error: MbusError) {
981        let pending_count = self.expected_responses.len();
982        client_log_debug!(
983            "failing {} pending request(s) with error {:?}",
984            pending_count,
985            error
986        );
987        while let Some(response) = self.expected_responses.pop() {
988            self.app.request_failed(
989                response.txn_id,
990                UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
991                error,
992            );
993        }
994        self.next_timeout_check = None;
995    }
996
997    /// Evaluates all pending requests to determine if any have exceeded their response timeout.
998    ///
999    /// This method is designed to be efficient:
1000    /// 1. It immediately returns if there are no pending requests.
1001    /// 2. It utilizes a fast-path cache (`next_timeout_check`) to skip an O(N) linear scan if the nearest
1002    ///    timeout in the future hasn't been reached yet.
1003    /// 3. If the cache expires, it iterates linearly over `expected_responses` to check the `sent_timestamp`
1004    ///    against `current_millis`.
1005    /// 4. If a request is timed out and has retries remaining, it schedules a retry timestamp based on
1006    ///    the configured backoff strategy, and optionally applies jitter using an application-provided callback.
1007    /// 5. When the scheduled retry timestamp is reached, it retransmits the original ADU. If the re-send fails,
1008    ///    it is dropped and reported as `SendFailed`.
1009    /// 6. If no retries remain, the request is removed from the pending queue and `NoRetriesLeft` is reported.
1010    /// 7. Finally, it recalculates the `next_timeout_check` state to schedule the next evaluation interval.
1011    fn handle_timeouts(&mut self) {
1012        if self.expected_responses.is_empty() {
1013            self.next_timeout_check = None;
1014            return;
1015        }
1016
1017        let current_millis = self.app.current_millis();
1018
1019        // Fast-path: Skip O(N) iteration if the earliest timeout has not yet been reached
1020        if let Some(check_at) = self.next_timeout_check
1021            && current_millis < check_at
1022        {
1023            client_log_trace!(
1024                "skipping timeout scan until {}, current_millis={}",
1025                check_at,
1026                current_millis
1027            );
1028            return;
1029        }
1030
1031        let response_timeout_ms = self.response_timeout_ms();
1032        let retry_backoff = self.config.retry_backoff_strategy();
1033        let retry_jitter = self.config.retry_jitter_strategy();
1034        let retry_random_fn = self.config.retry_random_fn();
1035        let expected_responses = &mut self.expected_responses;
1036        let mut i = 0;
1037        let mut new_next_check = u64::MAX;
1038
1039        while i < expected_responses.len() {
1040            let expected_response = &mut expected_responses[i];
1041            // First, process already-scheduled retries.
1042            if let Some(retry_at) = expected_response.next_retry_timestamp {
1043                if current_millis >= retry_at {
1044                    client_log_debug!(
1045                        "retry due now: txn_id={}, unit_id_or_slave_addr={}, retry_attempt_index={}, retries_left={}",
1046                        expected_response.txn_id,
1047                        expected_response.unit_id_or_slave_addr,
1048                        expected_response.retry_attempt_index.saturating_add(1),
1049                        expected_response.retries_left
1050                    );
1051                    if let Err(_e) = self.transport.send(&expected_response.original_adu) {
1052                        // Deliberately O(1): response identity is carried in the payload,
1053                        // not by queue position, so preserving insertion order is unnecessary.
1054                        let response = expected_responses.swap_remove(i);
1055                        client_log_debug!(
1056                            "retry send failed: txn_id={}, unit_id_or_slave_addr={}; dropping request",
1057                            response.txn_id,
1058                            response.unit_id_or_slave_addr
1059                        );
1060                        self.app.request_failed(
1061                            response.txn_id,
1062                            UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
1063                            MbusError::SendFailed,
1064                        );
1065                        continue;
1066                    }
1067
1068                    expected_response.retries_left =
1069                        expected_response.retries_left.saturating_sub(1);
1070                    expected_response.retry_attempt_index =
1071                        expected_response.retry_attempt_index.saturating_add(1);
1072                    expected_response.sent_timestamp = current_millis;
1073                    expected_response.next_retry_timestamp = None;
1074
1075                    let expires_at = current_millis.saturating_add(response_timeout_ms);
1076                    if expires_at < new_next_check {
1077                        new_next_check = expires_at;
1078                    }
1079                    i += 1;
1080                    continue;
1081                }
1082
1083                if retry_at < new_next_check {
1084                    new_next_check = retry_at;
1085                }
1086                i += 1;
1087                continue;
1088            }
1089
1090            // Otherwise, the request is waiting for a response to a previous send.
1091            let expires_at = expected_response
1092                .sent_timestamp
1093                .saturating_add(response_timeout_ms);
1094
1095            if current_millis > expires_at {
1096                if expected_response.retries_left == 0 {
1097                    // Deliberately O(1): timeout handling keys off txn/unit id and
1098                    // does not rely on stable ordering inside expected_responses.
1099                    let response = expected_responses.swap_remove(i);
1100                    client_log_debug!(
1101                        "request exhausted retries: txn_id={}, unit_id_or_slave_addr={}",
1102                        response.txn_id,
1103                        response.unit_id_or_slave_addr
1104                    );
1105                    self.app.request_failed(
1106                        response.txn_id,
1107                        UnitIdOrSlaveAddr::from_u8(response.unit_id_or_slave_addr),
1108                        MbusError::NoRetriesLeft,
1109                    );
1110                    continue;
1111                }
1112
1113                let next_attempt = expected_response.retry_attempt_index.saturating_add(1);
1114                let base_delay_ms = retry_backoff.delay_ms_for_retry(next_attempt);
1115                let retry_delay_ms = retry_jitter.apply(base_delay_ms, retry_random_fn) as u64;
1116                let retry_at = current_millis.saturating_add(retry_delay_ms);
1117                expected_response.next_retry_timestamp = Some(retry_at);
1118                client_log_debug!(
1119                    "scheduling retry: txn_id={}, unit_id_or_slave_addr={}, next_attempt={}, delay_ms={}, retry_at={}",
1120                    expected_response.txn_id,
1121                    expected_response.unit_id_or_slave_addr,
1122                    next_attempt,
1123                    retry_delay_ms,
1124                    retry_at
1125                );
1126
1127                // If delay is zero (Immediate strategy), process the newly scheduled retry
1128                // in this same poll cycle without waiting for another call to `poll`.
1129                if retry_delay_ms == 0 {
1130                    client_log_trace!(
1131                        "retry delay is zero; retry will be processed in the same poll cycle for txn_id={}",
1132                        expected_response.txn_id
1133                    );
1134                    continue;
1135                }
1136
1137                if retry_at < new_next_check {
1138                    new_next_check = retry_at;
1139                }
1140                i += 1;
1141                continue;
1142            }
1143
1144            if expires_at < new_next_check {
1145                new_next_check = expires_at;
1146            }
1147            i += 1;
1148        }
1149
1150        if new_next_check != u64::MAX {
1151            self.next_timeout_check = Some(new_next_check);
1152        } else {
1153            self.next_timeout_check = None;
1154        }
1155    }
1156
1157    fn add_an_expectation(
1158        &mut self,
1159        txn_id: u16,
1160        unit_id_slave_addr: UnitIdOrSlaveAddr,
1161        frame: &heapless::Vec<u8, MAX_ADU_FRAME_LEN>,
1162        operation_meta: OperationMeta,
1163        handler: ResponseHandler<TRANSPORT, APP, N>,
1164    ) -> Result<(), MbusError> {
1165        client_log_trace!(
1166            "queueing expected response: txn_id={}, unit_id_or_slave_addr={}, queue_len_before={}",
1167            txn_id,
1168            unit_id_slave_addr.get(),
1169            self.expected_responses.len()
1170        );
1171        self.expected_responses
1172            .push(ExpectedResponse {
1173                txn_id,
1174                unit_id_or_slave_addr: unit_id_slave_addr.get(),
1175                original_adu: frame.clone(),
1176                sent_timestamp: self.app.current_millis(),
1177                retries_left: self.retry_attempts(),
1178                retry_attempt_index: 0,
1179                next_retry_timestamp: None,
1180                handler,
1181                operation_meta,
1182            })
1183            .map_err(|_| MbusError::TooManyRequests)?;
1184        Ok(())
1185    }
1186}
1187
1188/// Implementation of core client services, including methods for sending requests and processing responses.
1189impl<TRANSPORT: Transport, APP: ClientCommon, const N: usize> ClientServices<TRANSPORT, APP, N> {
1190    /// Creates a new instance of ClientServices, connecting to the transport layer with the provided configuration.
1191    pub fn new(
1192        mut transport: TRANSPORT,
1193        app: APP,
1194        config: ModbusConfig,
1195    ) -> Result<Self, MbusError> {
1196        let transport_type = transport.transport_type();
1197        if matches!(
1198            transport_type,
1199            TransportType::StdSerial(_) | TransportType::CustomSerial(_)
1200        ) && N != 1
1201        {
1202            return Err(MbusError::InvalidNumOfExpectedRsps);
1203        }
1204
1205        transport
1206            .connect(&config)
1207            .map_err(|_e| MbusError::ConnectionFailed)?;
1208
1209        client_log_debug!(
1210            "client created with transport_type={:?}, queue_capacity={}",
1211            transport_type,
1212            N
1213        );
1214
1215        Ok(Self {
1216            app,
1217            transport,
1218            rxed_frame: Vec::new(),
1219            config,
1220            expected_responses: Vec::new(),
1221            next_timeout_check: None,
1222        })
1223    }
1224
1225    /// Returns an immutable reference to the application callback handler.
1226    ///
1227    /// This allows observers/tests to inspect application-owned state while keeping
1228    /// the handler instance stable for in-flight requests.
1229    pub fn app(&self) -> &APP {
1230        &self.app
1231    }
1232
1233    /// Returns whether the underlying transport currently considers itself connected.
1234    pub fn is_connected(&self) -> bool {
1235        self.transport.is_connected()
1236    }
1237
1238    /// Re-establishes the underlying transport connection using the existing configuration.
1239    ///
1240    /// Behavior:
1241    /// - Drops all currently pending in-flight requests and reports them as
1242    ///   `MbusError::ConnectionLost`.
1243    /// - Clears any partially received frame bytes.
1244    /// - Calls `transport.disconnect()` (best-effort) followed by `transport.connect(&self.config)`.
1245    ///
1246    /// This method does not automatically re-send dropped requests. The application can requeue
1247    /// requests explicitly after reconnection succeeds.
1248    pub fn reconnect(&mut self) -> Result<(), MbusError>
1249    where
1250        TRANSPORT::Error: Into<MbusError>,
1251    {
1252        client_log_debug!(
1253            "reconnect requested; pending_requests={}",
1254            self.expected_responses.len()
1255        );
1256        self.fail_all_pending_requests(MbusError::ConnectionLost);
1257        self.rxed_frame.clear();
1258        self.next_timeout_check = None;
1259
1260        let _ = self.transport.disconnect();
1261        self.transport.connect(&self.config).map_err(|e| e.into())
1262    }
1263
1264    /// Creates a serial client with a compile-time enforced queue size of exactly 1.
1265    ///
1266    /// This constructor exists to make the serial half-duplex constraint fail at compile time
1267    /// instead of runtime. Any attempt to call this function with `N != 1` fails trait-bound
1268    /// resolution during compilation.
1269    ///
1270    /// Use this constructor when building serial RTU/ASCII clients and prefer
1271    /// [`SerialClientServices`] as the type alias for readability.
1272    pub fn new_serial(
1273        mut transport: TRANSPORT,
1274        app: APP,
1275        config: ModbusSerialConfig,
1276    ) -> Result<Self, MbusError>
1277    where
1278        [(); N]: SerialQueueSizeOne,
1279    {
1280        let transport_type = transport.transport_type();
1281        if !matches!(
1282            transport_type,
1283            TransportType::StdSerial(_) | TransportType::CustomSerial(_)
1284        ) {
1285            return Err(MbusError::InvalidTransport);
1286        }
1287
1288        let config = ModbusConfig::Serial(config);
1289        transport
1290            .connect(&config)
1291            .map_err(|_e| MbusError::ConnectionFailed)?;
1292
1293        client_log_debug!("serial client created with queue_capacity={}", N);
1294
1295        Ok(Self {
1296            app,
1297            transport,
1298            rxed_frame: Vec::new(),
1299            config,
1300            expected_responses: Vec::new(),
1301            next_timeout_check: None,
1302        })
1303    }
1304
1305    /// Returns the configured response timeout in milliseconds.
1306    fn response_timeout_ms(&self) -> u64 {
1307        match &self.config {
1308            ModbusConfig::Tcp(config) => config.response_timeout_ms as u64,
1309            ModbusConfig::Serial(config) => config.response_timeout_ms as u64,
1310        }
1311    }
1312
1313    /// Returns the configured number of retries for outstanding requests.
1314    fn retry_attempts(&self) -> u8 {
1315        match &self.config {
1316            ModbusConfig::Tcp(config) => config.retry_attempts,
1317            ModbusConfig::Serial(config) => config.retry_attempts,
1318        }
1319    }
1320
1321    /// Ingests received Modbus frames from the transport layer.
1322    fn ingest_frame(&mut self) -> Result<usize, MbusError> {
1323        let frame = self.rxed_frame.as_slice();
1324        let transport_type = self.transport.transport_type();
1325
1326        client_log_trace!(
1327            "attempting frame ingest: transport_type={:?}, buffer_len={}",
1328            transport_type,
1329            frame.len()
1330        );
1331
1332        let expected_length = match derive_length_from_bytes(frame, transport_type) {
1333            Some(len) => len,
1334            None => return Err(MbusError::BufferTooSmall),
1335        };
1336
1337        client_log_trace!("derived expected frame length={}", expected_length);
1338
1339        if expected_length > MAX_ADU_FRAME_LEN {
1340            client_log_debug!(
1341                "derived frame length {} exceeds MAX_ADU_FRAME_LEN {}",
1342                expected_length,
1343                MAX_ADU_FRAME_LEN
1344            );
1345            return Err(MbusError::BasicParseError);
1346        }
1347
1348        if self.rxed_frame.len() < expected_length {
1349            return Err(MbusError::BufferTooSmall);
1350        }
1351
1352        let message = match common::decompile_adu_frame(&frame[..expected_length], transport_type) {
1353            Ok(value) => value,
1354            Err(err) => {
1355                client_log_debug!(
1356                    "decompile_adu_frame failed for {} bytes: {:?}",
1357                    expected_length,
1358                    err
1359                );
1360                return Err(err); // Malformed frame or parsing error, frame is dropped.
1361            }
1362        };
1363        use mbus_core::data_unit::common::AdditionalAddress;
1364        use mbus_core::transport::TransportType::*;
1365        let message = match self.transport.transport_type() {
1366            StdTcp | CustomTcp => {
1367                let mbap_header = match message.additional_address() {
1368                    AdditionalAddress::MbapHeader(header) => header,
1369                    _ => return Ok(expected_length),
1370                };
1371                let additional_addr = AdditionalAddress::MbapHeader(*mbap_header);
1372                ModbusMessage::new(additional_addr, message.pdu)
1373            }
1374            StdSerial(_) | CustomSerial(_) => {
1375                let slave_addr = match message.additional_address() {
1376                    AdditionalAddress::SlaveAddress(addr) => addr.address(),
1377                    _ => return Ok(expected_length),
1378                };
1379
1380                let additional_address =
1381                    AdditionalAddress::SlaveAddress(SlaveAddress::new(slave_addr)?);
1382                ModbusMessage::new(additional_address, message.pdu)
1383            }
1384        };
1385
1386        self.dispatch_response(&message);
1387        client_log_trace!("frame dispatch complete for {} bytes", expected_length);
1388
1389        Ok(expected_length)
1390    }
1391}
1392
1393#[cfg(test)]
1394mod tests {
1395    use super::*;
1396    use crate::app::CoilResponse;
1397    use crate::app::DiagnosticsResponse;
1398    use crate::app::DiscreteInputResponse;
1399    use crate::app::FifoQueueResponse;
1400    use crate::app::FileRecordResponse;
1401    use crate::app::RegisterResponse;
1402    use crate::services::coil::Coils;
1403
1404    use crate::services::diagnostic::ConformityLevel;
1405    use crate::services::diagnostic::DeviceIdentificationResponse;
1406    use crate::services::diagnostic::ObjectId;
1407    use crate::services::discrete_input::DiscreteInputs;
1408    use crate::services::fifo_queue::FifoQueue;
1409    use crate::services::file_record::MAX_SUB_REQUESTS_PER_PDU;
1410    use crate::services::file_record::SubRequest;
1411    use crate::services::file_record::SubRequestParams;
1412    use crate::services::register::Registers;
1413    use core::cell::RefCell; // `core::cell::RefCell` is `no_std` compatible
1414    use core::str::FromStr;
1415    use heapless::Deque;
1416    use heapless::Vec;
1417    use mbus_core::errors::MbusError;
1418    use mbus_core::function_codes::public::DiagnosticSubFunction;
1419    use mbus_core::transport::TransportType;
1420    use mbus_core::transport::{
1421        BackoffStrategy, BaudRate, JitterStrategy, ModbusConfig, ModbusSerialConfig,
1422        ModbusTcpConfig, Parity, SerialMode,
1423    };
1424
1425    const MOCK_DEQUE_CAPACITY: usize = 10; // Define a capacity for the mock deques
1426
1427    fn rand_zero() -> u32 {
1428        0
1429    }
1430
1431    fn rand_upper_percent_20() -> u32 {
1432        40
1433    }
1434
1435    // --- Mock Transport Implementation ---
1436    #[derive(Debug, Default)]
1437    struct MockTransport {
1438        pub sent_frames: RefCell<Deque<Vec<u8, MAX_ADU_FRAME_LEN>, MOCK_DEQUE_CAPACITY>>, // Changed to heapless::Deque
1439        pub recv_frames: RefCell<Deque<Vec<u8, MAX_ADU_FRAME_LEN>, MOCK_DEQUE_CAPACITY>>, // Changed to heapless::Deque
1440        pub recv_error: RefCell<Option<MbusError>>,
1441        pub connect_should_fail: bool,
1442        pub send_should_fail: bool,
1443        pub is_connected_flag: RefCell<bool>,
1444        pub transport_type: Option<TransportType>,
1445    }
1446
1447    impl Transport for MockTransport {
1448        type Error = MbusError;
1449
1450        fn connect(&mut self, _config: &ModbusConfig) -> Result<(), Self::Error> {
1451            if self.connect_should_fail {
1452                return Err(MbusError::ConnectionFailed);
1453            }
1454            *self.is_connected_flag.borrow_mut() = true;
1455            Ok(())
1456        }
1457
1458        fn disconnect(&mut self) -> Result<(), Self::Error> {
1459            *self.is_connected_flag.borrow_mut() = false;
1460            Ok(())
1461        }
1462
1463        fn send(&mut self, adu: &[u8]) -> Result<(), Self::Error> {
1464            if self.send_should_fail {
1465                return Err(MbusError::SendFailed);
1466            }
1467            let mut vec_adu = Vec::new();
1468            vec_adu
1469                .extend_from_slice(adu)
1470                .map_err(|_| MbusError::BufferLenMissmatch)?;
1471            self.sent_frames
1472                .borrow_mut()
1473                .push_back(vec_adu)
1474                .map_err(|_| MbusError::BufferLenMissmatch)?;
1475            Ok(())
1476        }
1477
1478        fn recv(&mut self) -> Result<Vec<u8, MAX_ADU_FRAME_LEN>, Self::Error> {
1479            if let Some(err) = self.recv_error.borrow_mut().take() {
1480                return Err(err);
1481            }
1482            self.recv_frames
1483                .borrow_mut()
1484                .pop_front()
1485                .ok_or(MbusError::Timeout)
1486        }
1487
1488        fn is_connected(&self) -> bool {
1489            *self.is_connected_flag.borrow()
1490        }
1491
1492        fn transport_type(&self) -> TransportType {
1493            self.transport_type.unwrap_or(TransportType::StdTcp)
1494        }
1495    }
1496
1497    // --- Mock App Implementation ---
1498    #[derive(Debug, Default)]
1499    struct MockApp {
1500        pub received_coil_responses: RefCell<Vec<(u16, UnitIdOrSlaveAddr, Coils), 10>>, // Corrected duplicate
1501        pub received_write_single_coil_responses:
1502            RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, bool), 10>>,
1503        pub received_write_multiple_coils_responses:
1504            RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, u16), 10>>,
1505        pub received_discrete_input_responses:
1506            RefCell<Vec<(u16, UnitIdOrSlaveAddr, DiscreteInputs, u16), 10>>,
1507        pub received_holding_register_responses:
1508            RefCell<Vec<(u16, UnitIdOrSlaveAddr, Registers, u16), 10>>,
1509        pub received_input_register_responses:
1510            RefCell<Vec<(u16, UnitIdOrSlaveAddr, Registers, u16), 10>>,
1511        pub received_write_single_register_responses:
1512            RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, u16), 10>>,
1513        pub received_write_multiple_register_responses:
1514            RefCell<Vec<(u16, UnitIdOrSlaveAddr, u16, u16), 10>>,
1515        pub received_read_write_multiple_registers_responses:
1516            RefCell<Vec<(u16, UnitIdOrSlaveAddr, Registers), 10>>,
1517        pub received_mask_write_register_responses: RefCell<Vec<(u16, UnitIdOrSlaveAddr), 10>>,
1518        pub received_read_fifo_queue_responses:
1519            RefCell<Vec<(u16, UnitIdOrSlaveAddr, FifoQueue), 10>>,
1520        pub received_read_file_record_responses: RefCell<
1521            Vec<
1522                (
1523                    u16,
1524                    UnitIdOrSlaveAddr,
1525                    Vec<SubRequestParams, MAX_SUB_REQUESTS_PER_PDU>,
1526                ),
1527                10,
1528            >,
1529        >,
1530        pub received_write_file_record_responses: RefCell<Vec<(u16, UnitIdOrSlaveAddr), 10>>,
1531        pub received_read_device_id_responses:
1532            RefCell<Vec<(u16, UnitIdOrSlaveAddr, DeviceIdentificationResponse), 10>>,
1533        pub failed_requests: RefCell<Vec<(u16, UnitIdOrSlaveAddr, MbusError), 10>>,
1534
1535        pub current_time: RefCell<u64>, // For simulating time in tests
1536    }
1537
1538    impl CoilResponse for MockApp {
1539        fn read_coils_response(
1540            &mut self,
1541            txn_id: u16,
1542            unit_id_slave_addr: UnitIdOrSlaveAddr,
1543            coils: &Coils,
1544        ) {
1545            self.received_coil_responses
1546                .borrow_mut()
1547                .push((txn_id, unit_id_slave_addr, coils.clone()))
1548                .unwrap();
1549        }
1550
1551        fn read_single_coil_response(
1552            &mut self,
1553            txn_id: u16,
1554            unit_id_slave_addr: UnitIdOrSlaveAddr,
1555            address: u16,
1556            value: bool,
1557        ) {
1558            // For single coil, we create a Coils struct with quantity 1 and the single value
1559            let mut values_vec = [0x00, 1];
1560            values_vec[0] = if value { 0x01 } else { 0x00 }; // Store the single bit in a byte
1561            let coils = Coils::new(address, 1)
1562                .unwrap()
1563                .with_values(&values_vec, 1)
1564                .unwrap();
1565            self.received_coil_responses
1566                .borrow_mut()
1567                .push((txn_id, unit_id_slave_addr, coils))
1568                .unwrap();
1569        }
1570
1571        fn write_single_coil_response(
1572            &mut self,
1573            txn_id: u16,
1574            unit_id_slave_addr: UnitIdOrSlaveAddr,
1575            address: u16,
1576            value: bool,
1577        ) {
1578            self.received_write_single_coil_responses
1579                .borrow_mut()
1580                .push((txn_id, unit_id_slave_addr, address, value))
1581                .unwrap();
1582        }
1583
1584        fn write_multiple_coils_response(
1585            &mut self,
1586            txn_id: u16,
1587            unit_id_slave_addr: UnitIdOrSlaveAddr,
1588            address: u16,
1589            quantity: u16,
1590        ) {
1591            self.received_write_multiple_coils_responses
1592                .borrow_mut()
1593                .push((txn_id, unit_id_slave_addr, address, quantity))
1594                .unwrap();
1595        }
1596    }
1597
1598    impl DiscreteInputResponse for MockApp {
1599        fn read_multiple_discrete_inputs_response(
1600            &mut self,
1601            txn_id: u16,
1602            unit_id_slave_addr: UnitIdOrSlaveAddr,
1603            inputs: &DiscreteInputs,
1604        ) {
1605            self.received_discrete_input_responses
1606                .borrow_mut()
1607                .push((
1608                    txn_id,
1609                    unit_id_slave_addr,
1610                    inputs.clone(),
1611                    inputs.quantity(),
1612                ))
1613                .unwrap();
1614        }
1615
1616        fn read_single_discrete_input_response(
1617            &mut self,
1618            txn_id: u16,
1619            unit_id_slave_addr: UnitIdOrSlaveAddr,
1620            address: u16,
1621            value: bool,
1622        ) {
1623            let mut values = [0u8; mbus_core::models::discrete_input::MAX_DISCRETE_INPUT_BYTES];
1624            values[0] = if value { 0x01 } else { 0x00 };
1625            let inputs = DiscreteInputs::new(address, 1)
1626                .unwrap()
1627                .with_values(&values, 1)
1628                .unwrap();
1629            self.received_discrete_input_responses
1630                .borrow_mut()
1631                .push((txn_id, unit_id_slave_addr, inputs, 1))
1632                .unwrap();
1633        }
1634    }
1635
1636    impl RequestErrorNotifier for MockApp {
1637        fn request_failed(
1638            &mut self,
1639            txn_id: u16,
1640            unit_id_slave_addr: UnitIdOrSlaveAddr,
1641            error: MbusError,
1642        ) {
1643            self.failed_requests
1644                .borrow_mut()
1645                .push((txn_id, unit_id_slave_addr, error))
1646                .unwrap();
1647        }
1648    }
1649
1650    impl RegisterResponse for MockApp {
1651        fn read_multiple_holding_registers_response(
1652            &mut self,
1653            txn_id: u16,
1654            unit_id_slave_addr: UnitIdOrSlaveAddr,
1655            registers: &Registers,
1656        ) {
1657            let quantity = registers.quantity();
1658            self.received_holding_register_responses
1659                .borrow_mut()
1660                .push((txn_id, unit_id_slave_addr, registers.clone(), quantity))
1661                .unwrap();
1662        }
1663
1664        fn read_single_input_register_response(
1665            &mut self,
1666            txn_id: u16,
1667            unit_id_slave_addr: UnitIdOrSlaveAddr,
1668            address: u16,
1669            value: u16,
1670        ) {
1671            // Create a temporary slice to load the single register value
1672            let values = [value];
1673            let registers = Registers::new(address, 1)
1674                .unwrap()
1675                .with_values(&values, 1)
1676                .unwrap();
1677            self.received_input_register_responses
1678                .borrow_mut()
1679                .push((txn_id, unit_id_slave_addr, registers, 1))
1680                .unwrap();
1681        }
1682
1683        fn read_single_holding_register_response(
1684            &mut self,
1685            txn_id: u16,
1686            unit_id_slave_addr: UnitIdOrSlaveAddr,
1687            address: u16,
1688            value: u16,
1689        ) {
1690            // Create a temporary slice to load the single register value
1691            let data = [value];
1692            // Initialize Registers with default capacity (MAX_REGISTERS_PER_PDU)
1693            let registers = Registers::new(address, 1)
1694                .unwrap()
1695                .with_values(&data, 1)
1696                .unwrap();
1697
1698            self.received_holding_register_responses
1699                .borrow_mut()
1700                .push((txn_id, unit_id_slave_addr, registers, 1))
1701                .unwrap();
1702        }
1703
1704        fn read_multiple_input_registers_response(
1705            &mut self,
1706            txn_id: u16,
1707            unit_id_slave_addr: UnitIdOrSlaveAddr,
1708            registers: &Registers,
1709        ) {
1710            let quantity = registers.quantity();
1711            self.received_input_register_responses
1712                .borrow_mut()
1713                .push((txn_id, unit_id_slave_addr, registers.clone(), quantity))
1714                .unwrap();
1715        }
1716
1717        fn write_single_register_response(
1718            &mut self,
1719            txn_id: u16,
1720            unit_id_slave_addr: UnitIdOrSlaveAddr,
1721            address: u16,
1722            value: u16,
1723        ) {
1724            self.received_write_single_register_responses
1725                .borrow_mut()
1726                .push((txn_id, unit_id_slave_addr, address, value))
1727                .unwrap();
1728        }
1729
1730        fn write_multiple_registers_response(
1731            &mut self,
1732            txn_id: u16,
1733            unit_id_slave_addr: UnitIdOrSlaveAddr,
1734            address: u16,
1735            quantity: u16,
1736        ) {
1737            self.received_write_multiple_register_responses
1738                .borrow_mut()
1739                .push((txn_id, unit_id_slave_addr, address, quantity))
1740                .unwrap();
1741        }
1742
1743        fn read_write_multiple_registers_response(
1744            &mut self,
1745            txn_id: u16,
1746            unit_id_slave_addr: UnitIdOrSlaveAddr,
1747            registers: &Registers,
1748        ) {
1749            self.received_read_write_multiple_registers_responses
1750                .borrow_mut()
1751                .push((txn_id, unit_id_slave_addr, registers.clone()))
1752                .unwrap();
1753        }
1754
1755        fn mask_write_register_response(
1756            &mut self,
1757            txn_id: u16,
1758            unit_id_slave_addr: UnitIdOrSlaveAddr,
1759        ) {
1760            self.received_mask_write_register_responses
1761                .borrow_mut()
1762                .push((txn_id, unit_id_slave_addr))
1763                .unwrap();
1764        }
1765
1766        fn read_single_register_response(
1767            &mut self,
1768            txn_id: u16,
1769            unit_id_slave_addr: UnitIdOrSlaveAddr,
1770            address: u16,
1771            value: u16,
1772        ) {
1773            // Create a temporary slice to load the single register value
1774            let data = [value];
1775            // Initialize Registers with default capacity (MAX_REGISTERS_PER_PDU)
1776            let registers = Registers::new(address, 1)
1777                .unwrap()
1778                .with_values(&data, 1)
1779                .unwrap();
1780
1781            self.received_holding_register_responses
1782                .borrow_mut()
1783                .push((txn_id, unit_id_slave_addr, registers, 1))
1784                .unwrap();
1785        }
1786    }
1787
1788    impl FifoQueueResponse for MockApp {
1789        fn read_fifo_queue_response(
1790            &mut self,
1791            txn_id: u16,
1792            unit_id_slave_addr: UnitIdOrSlaveAddr,
1793            fifo_queue: &FifoQueue,
1794        ) {
1795            self.received_read_fifo_queue_responses
1796                .borrow_mut()
1797                .push((txn_id, unit_id_slave_addr, fifo_queue.clone()))
1798                .unwrap();
1799        }
1800    }
1801
1802    impl FileRecordResponse for MockApp {
1803        fn read_file_record_response(
1804            &mut self,
1805            txn_id: u16,
1806            unit_id_slave_addr: UnitIdOrSlaveAddr,
1807            data: &[SubRequestParams],
1808        ) {
1809            let mut vec = Vec::new();
1810            vec.extend_from_slice(data).unwrap();
1811            self.received_read_file_record_responses
1812                .borrow_mut()
1813                .push((txn_id, unit_id_slave_addr, vec))
1814                .unwrap();
1815        }
1816        fn write_file_record_response(
1817            &mut self,
1818            txn_id: u16,
1819            unit_id_slave_addr: UnitIdOrSlaveAddr,
1820        ) {
1821            self.received_write_file_record_responses
1822                .borrow_mut()
1823                .push((txn_id, unit_id_slave_addr))
1824                .unwrap();
1825        }
1826    }
1827
1828    impl DiagnosticsResponse for MockApp {
1829        fn read_device_identification_response(
1830            &mut self,
1831            txn_id: u16,
1832            unit_id_slave_addr: UnitIdOrSlaveAddr,
1833            response: &DeviceIdentificationResponse,
1834        ) {
1835            self.received_read_device_id_responses
1836                .borrow_mut()
1837                .push((txn_id, unit_id_slave_addr, response.clone()))
1838                .unwrap();
1839        }
1840
1841        fn encapsulated_interface_transport_response(
1842            &mut self,
1843            _: u16,
1844            _: UnitIdOrSlaveAddr,
1845            _: EncapsulatedInterfaceType,
1846            _: &[u8],
1847        ) {
1848        }
1849
1850        fn diagnostics_response(
1851            &mut self,
1852            _: u16,
1853            _: UnitIdOrSlaveAddr,
1854            _: DiagnosticSubFunction,
1855            _: &[u16],
1856        ) {
1857        }
1858
1859        fn get_comm_event_counter_response(
1860            &mut self,
1861            _: u16,
1862            _: UnitIdOrSlaveAddr,
1863            _: u16,
1864            _: u16,
1865        ) {
1866        }
1867
1868        fn get_comm_event_log_response(
1869            &mut self,
1870            _: u16,
1871            _: UnitIdOrSlaveAddr,
1872            _: u16,
1873            _: u16,
1874            _: u16,
1875            _: &[u8],
1876        ) {
1877        }
1878
1879        fn read_exception_status_response(&mut self, _: u16, _: UnitIdOrSlaveAddr, _: u8) {}
1880
1881        fn report_server_id_response(&mut self, _: u16, _: UnitIdOrSlaveAddr, _: &[u8]) {}
1882    }
1883
1884    impl TimeKeeper for MockApp {
1885        fn current_millis(&self) -> u64 {
1886            *self.current_time.borrow()
1887        }
1888    }
1889
1890    // --- ClientServices Tests ---
1891
1892    /// Test case: `ClientServices::new` successfully connects to the transport.
1893    #[test]
1894    fn test_client_services_new_success() {
1895        let transport = MockTransport::default();
1896        let app = MockApp::default();
1897        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
1898
1899        let client_services =
1900            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config);
1901        assert!(client_services.is_ok());
1902        assert!(client_services.unwrap().transport.is_connected());
1903    }
1904
1905    /// Test case: `ClientServices::new` returns an error if transport connection fails.
1906    #[test]
1907    fn test_client_services_new_connection_failure() {
1908        let mut transport = MockTransport::default();
1909        transport.connect_should_fail = true;
1910        let app = MockApp::default();
1911        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
1912
1913        let client_services =
1914            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config);
1915        assert!(client_services.is_err());
1916        assert_eq!(client_services.unwrap_err(), MbusError::ConnectionFailed);
1917    }
1918
1919    #[test]
1920    fn test_client_services_new_serial_success() {
1921        let transport = MockTransport {
1922            transport_type: Some(TransportType::StdSerial(SerialMode::Rtu)),
1923            ..Default::default()
1924        };
1925        let app = MockApp::default();
1926        let serial_config = ModbusSerialConfig {
1927            port_path: heapless::String::<64>::from_str("/dev/ttyUSB0").unwrap(),
1928            mode: SerialMode::Rtu,
1929            baud_rate: BaudRate::Baud19200,
1930            data_bits: mbus_core::transport::DataBits::Eight,
1931            stop_bits: 1,
1932            parity: Parity::Even,
1933            response_timeout_ms: 1000,
1934            retry_attempts: 1,
1935            retry_backoff_strategy: BackoffStrategy::Immediate,
1936            retry_jitter_strategy: JitterStrategy::None,
1937            retry_random_fn: None,
1938        };
1939
1940        let client_services =
1941            ClientServices::<MockTransport, MockApp, 1>::new_serial(transport, app, serial_config);
1942        assert!(client_services.is_ok());
1943    }
1944
1945    #[test]
1946    fn test_reconnect_success_flushes_pending_requests() {
1947        let transport = MockTransport::default();
1948        let app = MockApp::default();
1949        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
1950        let mut client_services =
1951            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
1952
1953        let unit_id = UnitIdOrSlaveAddr::new(1).unwrap();
1954        client_services.read_single_coil(10, unit_id, 0).unwrap();
1955        assert_eq!(client_services.expected_responses.len(), 1);
1956
1957        let reconnect_result = client_services.reconnect();
1958        assert!(reconnect_result.is_ok());
1959        assert!(client_services.is_connected());
1960        assert!(client_services.expected_responses.is_empty());
1961
1962        let failed_requests = client_services.app().failed_requests.borrow();
1963        assert_eq!(failed_requests.len(), 1);
1964        assert_eq!(failed_requests[0].0, 10);
1965        assert_eq!(failed_requests[0].2, MbusError::ConnectionLost);
1966    }
1967
1968    #[test]
1969    fn test_reconnect_failure_propagates_connect_error() {
1970        let transport = MockTransport::default();
1971        let app = MockApp::default();
1972        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
1973        let mut client_services =
1974            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
1975
1976        client_services.transport.connect_should_fail = true;
1977        let reconnect_result = client_services.reconnect();
1978
1979        assert!(reconnect_result.is_err());
1980        assert_eq!(reconnect_result.unwrap_err(), MbusError::ConnectionFailed);
1981        assert!(!client_services.is_connected());
1982    }
1983
1984    /// Test case: `read_multiple_coils` sends a valid ADU over the transport.
1985    #[test]
1986    fn test_read_multiple_coils_sends_valid_adu() {
1987        let transport = MockTransport::default();
1988        let app = MockApp::default();
1989        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
1990        let mut client_services =
1991            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
1992
1993        let txn_id = 0x0001;
1994        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
1995        let address = 0x0000;
1996        let quantity = 8;
1997        client_services
1998            .read_multiple_coils(txn_id, unit_id, address, quantity)
1999            .unwrap();
2000
2001        let sent_frames = client_services.transport.sent_frames.borrow();
2002        assert_eq!(sent_frames.len(), 1);
2003        let sent_adu = sent_frames.front().unwrap();
2004
2005        // Expected ADU: TID(0x0001), PID(0x0000), Length(0x0006 = Unit ID + FC + Addr + Qty), UnitID(0x01), FC(0x01), Addr(0x0000), Qty(0x0008)
2006        #[rustfmt::skip]
2007        let expected_adu: [u8; 12] = [
2008            0x00, 0x01, // Transaction ID
2009            0x00, 0x00, // Protocol ID
2010            0x00, 0x06, // Length (1 byte Unit ID + 1 byte FC + 2 bytes Address + 2 bytes Quantity = 6)
2011            0x01,       // Unit ID
2012            0x01,       // Function Code (Read Coils)
2013            0x00, 0x00, // Starting Address
2014            0x00, 0x08, // Quantity of Coils
2015        ];
2016        assert_eq!(sent_adu.as_slice(), &expected_adu);
2017    }
2018
2019    /// Test case: `read_multiple_coils` returns an error for an invalid quantity.
2020    #[test]
2021    fn test_read_multiple_coils_invalid_quantity() {
2022        let transport = MockTransport::default();
2023        let app = MockApp::default();
2024        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2025        let mut client_services =
2026            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2027
2028        let txn_id = 0x0001;
2029        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2030        let address = 0x0000;
2031        let quantity = 0; // Invalid quantity
2032
2033        let result = client_services.read_multiple_coils(txn_id, unit_id, address, quantity); // current_millis() is called internally
2034        assert_eq!(result.unwrap_err(), MbusError::InvalidQuantity);
2035    }
2036
2037    /// Test case: `read_multiple_coils` returns an error if sending fails.
2038    #[test]
2039    fn test_read_multiple_coils_send_failure() {
2040        let mut transport = MockTransport::default();
2041        transport.send_should_fail = true;
2042        let app = MockApp::default();
2043        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2044        let mut client_services =
2045            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2046
2047        let txn_id = 0x0001;
2048        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2049        let address = 0x0000;
2050        let quantity = 8;
2051
2052        let result = client_services.read_multiple_coils(txn_id, unit_id, address, quantity); // current_millis() is called internally
2053        assert_eq!(result.unwrap_err(), MbusError::SendFailed);
2054    }
2055
2056    /// Test case: `ingest_frame` ignores responses with wrong function code.
2057    #[test]
2058    fn test_ingest_frame_wrong_fc() {
2059        let transport = MockTransport::default();
2060        let app = MockApp::default();
2061        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2062        let mut client_services =
2063            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2064
2065        // ADU with FC 0x03 (Read Holding Registers) instead of 0x01 (Read Coils)
2066        let response_adu = [0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x03, 0x01, 0xB3];
2067
2068        client_services
2069            .transport
2070            .recv_frames
2071            .borrow_mut()
2072            .push_back(Vec::from_slice(&response_adu).unwrap())
2073            .unwrap();
2074        client_services.poll();
2075
2076        let received_responses = client_services.app().received_coil_responses.borrow();
2077        assert!(received_responses.is_empty());
2078    }
2079
2080    /// Test case: `ingest_frame` ignores malformed ADUs.
2081    #[test]
2082    fn test_ingest_frame_malformed_adu() {
2083        let transport = MockTransport::default();
2084        let app = MockApp::default();
2085        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2086        let mut client_services =
2087            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2088
2089        // Malformed ADU (too short)
2090        let malformed_adu = [0x01, 0x02, 0x03];
2091
2092        client_services
2093            .transport
2094            .recv_frames
2095            .borrow_mut()
2096            .push_back(Vec::from_slice(&malformed_adu).unwrap())
2097            .unwrap();
2098        client_services.poll();
2099
2100        let received_responses = client_services.app().received_coil_responses.borrow();
2101        assert!(received_responses.is_empty());
2102    }
2103
2104    /// Test case: `ingest_frame` ignores responses for unknown transaction IDs.
2105    #[test]
2106    fn test_ingest_frame_unknown_txn_id() {
2107        let transport = MockTransport::default();
2108        let app = MockApp::default();
2109        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2110        let mut client_services =
2111            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2112
2113        // No request was sent, so no expected response is in the queue.
2114        let response_adu = [0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0xB3];
2115
2116        client_services
2117            .transport
2118            .recv_frames
2119            .borrow_mut()
2120            .push_back(Vec::from_slice(&response_adu).unwrap())
2121            .unwrap();
2122        client_services.poll();
2123
2124        let received_responses = client_services.app().received_coil_responses.borrow();
2125        assert!(received_responses.is_empty());
2126    }
2127
2128    /// Test case: `ingest_frame` ignores responses that fail PDU parsing.
2129    #[test]
2130    fn test_ingest_frame_pdu_parse_failure() {
2131        let transport = MockTransport::default();
2132        let app = MockApp::default();
2133        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2134        let mut client_services =
2135            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2136
2137        let txn_id = 0x0001;
2138        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2139        let address = 0x0000;
2140        let quantity = 8;
2141        client_services
2142            .read_multiple_coils(txn_id, unit_id, address, quantity) // current_millis() is called internally
2143            .unwrap();
2144
2145        // Craft a PDU that will cause `parse_read_coils_response` to fail.
2146        // For example, byte count mismatch: PDU indicates 1 byte of data, but provides 2.
2147        // ADU: TID(0x0001), PID(0x0000), Length(0x0005), UnitID(0x01), FC(0x01), Byte Count(0x01), Data(0xB3, 0x00)
2148        let response_adu = [
2149            0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x01, 0x01, 0x01, 0xB3, 0x00,
2150        ]; // Corrected duplicate
2151
2152        client_services
2153            .transport
2154            .recv_frames
2155            .borrow_mut()
2156            .push_back(Vec::from_slice(&response_adu).unwrap())
2157            .unwrap();
2158        client_services.poll();
2159
2160        let received_responses = client_services.app().received_coil_responses.borrow();
2161        assert!(received_responses.is_empty());
2162        // The expected response should still be removed even if PDU parsing fails.
2163        assert!(client_services.expected_responses.is_empty());
2164    }
2165
2166    /// Test case: `ClientServices` successfully sends a Read Single Coil request and processes a valid response.
2167    #[test]
2168    fn test_client_services_read_single_coil_e2e_success() {
2169        let transport = MockTransport::default();
2170        let app = MockApp::default();
2171        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2172        let mut client_services =
2173            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2174
2175        let txn_id = 0x0002;
2176        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2177        let address = 0x0005;
2178
2179        // 1. Send a Read Single Coil request
2180        client_services // current_millis() is called internally
2181            .read_single_coil(txn_id, unit_id, address)
2182            .unwrap();
2183
2184        // Verify that the request was sent via the mock transport
2185        let sent_adu = client_services
2186            .transport
2187            .sent_frames
2188            .borrow_mut()
2189            .pop_front()
2190            .unwrap();
2191        // Expected ADU for Read Coils (FC 0x01) with quantity 1
2192        #[rustfmt::skip]
2193        let expected_adu: [u8; 12] = [
2194            0x00, 0x02, // Transaction ID
2195            0x00, 0x00, // Protocol ID
2196            0x00, 0x06, // Length (Unit ID + FC + Addr + Qty=1)
2197            0x01,       // Unit ID
2198            0x01,       // Function Code (Read Coils)
2199            0x00, 0x05, // Starting Address
2200            0x00, 0x01, // Quantity of Coils (1)
2201        ];
2202        assert_eq!(sent_adu.as_slice(), &expected_adu);
2203
2204        // 2. Manually construct a valid Read Coils response ADU for a single coil
2205        // Response for reading 1 coil at 0x0005, value: true (0x01)
2206        // ADU: TID(0x0002), PID(0x0000), Length(0x0004), UnitID(0x01), FC(0x01), Byte Count(0x01), Coil Data(0x01)
2207        let response_adu = [0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0x01];
2208
2209        // Simulate receiving the frame
2210        client_services
2211            .transport
2212            .recv_frames
2213            .borrow_mut()
2214            .push_back(Vec::from_slice(&response_adu).unwrap())
2215            .unwrap();
2216        client_services.poll();
2217
2218        // 3. Assert that the MockApp's read_single_coil_response callback was invoked with correct data
2219        let received_responses = client_services.app().received_coil_responses.borrow();
2220        assert_eq!(received_responses.len(), 1);
2221
2222        let (rcv_txn_id, rcv_unit_id, rcv_coils) = &received_responses[0];
2223        let rcv_quantity = rcv_coils.quantity();
2224        assert_eq!(*rcv_txn_id, txn_id);
2225        assert_eq!(*rcv_unit_id, unit_id);
2226        assert_eq!(rcv_coils.from_address(), address);
2227        assert_eq!(rcv_coils.quantity(), 1); // Quantity should be 1
2228        assert_eq!(&rcv_coils.values()[..1], &[0x01]); // Value should be 0x01 for true
2229        assert_eq!(rcv_quantity, 1);
2230
2231        // 4. Assert that the expected response was removed from the queue
2232        assert!(client_services.expected_responses.is_empty());
2233    }
2234
2235    /// Test case: `read_single_coil_request` sends a valid ADU over the transport.
2236    #[test]
2237    fn test_read_single_coil_request_sends_valid_adu() {
2238        let transport = MockTransport::default();
2239        let app = MockApp::default();
2240        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2241        let mut client_services =
2242            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2243
2244        let txn_id = 0x0002;
2245        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2246        let address = 0x0005;
2247
2248        client_services
2249            .read_single_coil(txn_id, unit_id, address) // current_millis() is called internally
2250            .unwrap();
2251
2252        let sent_frames = client_services.transport.sent_frames.borrow();
2253        assert_eq!(sent_frames.len(), 1);
2254        let sent_adu = sent_frames.front().unwrap();
2255
2256        // Expected ADU: TID(0x0002), PID(0x0000), Length(0x0006 = Unit ID + FC + Addr + Qty), UnitID(0x01), FC(0x01), Addr(0x0005), Qty(0x0001)
2257        #[rustfmt::skip]
2258        let expected_adu: [u8; 12] = [
2259            0x00, 0x02, // Transaction ID
2260            0x00, 0x00, // Protocol ID
2261            0x00, 0x06, // Length (1 byte Unit ID + 1 byte FC + 2 bytes Address + 2 bytes Quantity = 6)
2262            0x01,       // Unit ID
2263            0x01,       // Function Code (Read Coils)
2264            0x00, 0x05, // Starting Address
2265            0x00, 0x01, // Quantity of Coils (1)
2266        ];
2267        assert_eq!(sent_adu.as_slice(), &expected_adu);
2268
2269        // Verify that the expected response was recorded with single_read = true
2270        assert_eq!(client_services.expected_responses.len(), 1); // Corrected: Removed duplicate pop_front()
2271        let single_read = client_services.expected_responses[0]
2272            .operation_meta
2273            .is_single();
2274        assert!(single_read);
2275    }
2276
2277    /// Test case: `write_single_coil` sends a valid ADU over the transport.
2278    #[test]
2279    fn test_write_single_coil_sends_valid_adu() {
2280        let transport = MockTransport::default();
2281        let app = MockApp::default();
2282        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2283        let mut client_services =
2284            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2285
2286        let txn_id = 0x0003;
2287        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2288        let address = 0x000A;
2289        let value = true;
2290
2291        client_services
2292            .write_single_coil(txn_id, unit_id, address, value) // current_millis() is called internally
2293            .unwrap();
2294
2295        let sent_frames = client_services.transport.sent_frames.borrow();
2296        assert_eq!(sent_frames.len(), 1);
2297        let sent_adu = sent_frames.front().unwrap();
2298
2299        // Expected ADU: TID(0x0003), PID(0x0000), Length(0x0006), UnitID(0x01), FC(0x05), Addr(0x000A), Value(0xFF00)
2300        #[rustfmt::skip]
2301        let expected_adu: [u8; 12] = [
2302            0x00, 0x03, // Transaction ID
2303            0x00, 0x00, // Protocol ID
2304            0x00, 0x06, // Length (1 byte Unit ID + 1 byte FC + 2 bytes Address + 2 bytes Value = 6)
2305            0x01,       // Unit ID
2306            0x05,       // Function Code (Write Single Coil)
2307            0x00, 0x0A, // Address
2308            0xFF, 0x00, // Value (ON)
2309        ];
2310        assert_eq!(sent_adu.as_slice(), &expected_adu);
2311
2312        // Verify that the expected response was recorded
2313        assert_eq!(client_services.expected_responses.len(), 1);
2314        let expected_address = client_services.expected_responses[0]
2315            .operation_meta
2316            .address();
2317        let expected_value = client_services.expected_responses[0].operation_meta.value() != 0;
2318
2319        assert_eq!(expected_address, address);
2320        assert_eq!(expected_value, value);
2321    }
2322
2323    /// Test case: `ClientServices` successfully sends a Write Single Coil request and processes a valid response.
2324    #[test]
2325    fn test_client_services_write_single_coil_e2e_success() {
2326        let transport = MockTransport::default();
2327        let app = MockApp::default();
2328        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2329        let mut client_services =
2330            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2331
2332        let txn_id = 0x0003;
2333        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2334        let address = 0x000A;
2335        let value = true;
2336
2337        // 1. Send a Write Single Coil request
2338        client_services // current_millis() is called internally
2339            .write_single_coil(txn_id, unit_id, address, value)
2340            .unwrap();
2341
2342        // Verify that the request was sent via the mock transport
2343        let sent_adu = client_services
2344            .transport
2345            .sent_frames
2346            .borrow_mut()
2347            .pop_front()
2348            .unwrap();
2349        #[rustfmt::skip]
2350        let expected_request_adu: [u8; 12] = [
2351            0x00, 0x03, // Transaction ID
2352            0x00, 0x00, // Protocol ID
2353            0x00, 0x06, // Length
2354            0x01,       // Unit ID
2355            0x05,       // Function Code (Write Single Coil)
2356            0x00, 0x0A, // Address
2357            0xFF, 0x00, // Value (ON)
2358        ];
2359        assert_eq!(sent_adu.as_slice(), &expected_request_adu);
2360
2361        // 2. Manually construct a valid Write Single Coil response ADU
2362        // ADU: TID(0x0003), PID(0x0000), Length(0x0006), UnitID(0x01), FC(0x05), Address(0x000A), Value(0xFF00)
2363        let response_adu = [
2364            0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x01, 0x05, 0x00, 0x0A, 0xFF, 0x00,
2365        ];
2366
2367        // Simulate receiving the frame
2368        client_services
2369            .transport
2370            .recv_frames
2371            .borrow_mut()
2372            .push_back(Vec::from_slice(&response_adu).unwrap())
2373            .unwrap();
2374        client_services.poll();
2375
2376        // 3. Assert that the MockApp's write_single_coil_response callback was invoked with correct data
2377        let received_responses = client_services
2378            .app
2379            .received_write_single_coil_responses
2380            .borrow();
2381        assert_eq!(received_responses.len(), 1);
2382
2383        let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_value) = &received_responses[0];
2384        assert_eq!(*rcv_txn_id, txn_id);
2385        assert_eq!(*rcv_unit_id, unit_id);
2386        assert_eq!(*rcv_address, address);
2387        assert_eq!(*rcv_value, value);
2388
2389        // 4. Assert that the expected response was removed from the queue
2390        assert!(client_services.expected_responses.is_empty());
2391    }
2392
2393    /// Test case: `write_multiple_coils` sends a valid ADU over the transport.
2394    #[test]
2395    fn test_write_multiple_coils_sends_valid_adu() {
2396        let transport = MockTransport::default();
2397        let app = MockApp::default();
2398        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2399        let mut client_services =
2400            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2401
2402        let txn_id = 0x0004;
2403        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2404        let address = 0x0000;
2405        let quantity = 10;
2406
2407        // Initialize a Coils instance with alternating true/false values to produce 0x55, 0x01
2408        let mut values = Coils::new(address, quantity).unwrap();
2409        for i in 0..quantity {
2410            values.set_value(address + i, i % 2 == 0).unwrap();
2411        }
2412
2413        client_services
2414            .write_multiple_coils(txn_id, unit_id, address, &values) // current_millis() is called internally
2415            .unwrap();
2416
2417        let sent_frames = client_services.transport.sent_frames.borrow();
2418        assert_eq!(sent_frames.len(), 1);
2419        let sent_adu = sent_frames.front().unwrap();
2420
2421        // Expected ADU: TID(0x0004), PID(0x0000), Length(0x0009), UnitID(0x01), FC(0x0F), Addr(0x0000), Qty(0x000A), Byte Count(0x02), Data(0x55, 0x01)
2422        #[rustfmt::skip]
2423        let expected_adu: [u8; 15] = [
2424            0x00, 0x04, // Transaction ID
2425            0x00, 0x00, // Protocol ID
2426            0x00, 0x09, // Length (1 byte Unit ID + 1 byte FC + 2 bytes Address + 2 bytes Quantity + 1 byte Byte Count + 2 bytes Data = 9)
2427            0x01,       // Unit ID
2428            0x0F,       // Function Code (Write Multiple Coils)
2429            0x00, 0x00, // Address
2430            0x00, 0x0A, // Quantity
2431            0x02,       // Byte Count
2432            0x55, 0x01, // Data
2433        ];
2434        assert_eq!(sent_adu.as_slice(), &expected_adu);
2435
2436        // Verify that the expected response was recorded
2437        assert_eq!(client_services.expected_responses.len(), 1);
2438        let expected_address = client_services.expected_responses[0]
2439            .operation_meta
2440            .address();
2441        let expected_quantity = client_services.expected_responses[0]
2442            .operation_meta
2443            .quantity();
2444        assert_eq!(expected_address, address);
2445        assert_eq!(expected_quantity, quantity);
2446    }
2447
2448    /// Test case: `ClientServices` successfully sends a Write Multiple Coils request and processes a valid response.
2449    #[test]
2450    fn test_client_services_write_multiple_coils_e2e_success() {
2451        let transport = MockTransport::default();
2452        let app = MockApp::default();
2453        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2454        let mut client_services =
2455            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2456
2457        let txn_id = 0x0004;
2458        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2459        let address = 0x0000;
2460        let quantity = 10;
2461
2462        // Initialize a Coils instance with alternating true/false values
2463        let mut values = Coils::new(address, quantity).unwrap();
2464        for i in 0..quantity {
2465            values.set_value(address + i, i % 2 == 0).unwrap();
2466        }
2467
2468        // 1. Send a Write Multiple Coils request
2469        client_services // current_millis() is called internally
2470            .write_multiple_coils(txn_id, unit_id, address, &values)
2471            .unwrap();
2472
2473        // Verify that the request was sent via the mock transport
2474        let sent_adu = client_services
2475            .transport
2476            .sent_frames
2477            .borrow_mut()
2478            .pop_front()
2479            .unwrap();
2480        #[rustfmt::skip]
2481        let expected_request_adu: [u8; 15] = [
2482            0x00, 0x04, // Transaction ID
2483            0x00, 0x00, // Protocol ID
2484            0x00, 0x09, // Length
2485            0x01,       // Unit ID
2486            0x0F,       // Function Code (Write Multiple Coils)
2487            0x00, 0x00, // Address
2488            0x00, 0x0A, // Quantity
2489            0x02,       // Byte Count
2490            0x55, 0x01, // Data
2491        ];
2492        assert_eq!(sent_adu.as_slice(), &expected_request_adu);
2493
2494        // 2. Manually construct a valid Write Multiple Coils response ADU
2495        // ADU: TID(0x0004), PID(0x0000), Length(0x0006), UnitID(0x01), FC(0x0F), Address(0x0000), Quantity(0x000A)
2496        let response_adu = [
2497            0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0A,
2498        ];
2499
2500        // Simulate receiving the frame
2501        client_services
2502            .transport
2503            .recv_frames
2504            .borrow_mut()
2505            .push_back(Vec::from_slice(&response_adu).unwrap())
2506            .unwrap();
2507        client_services.poll();
2508
2509        // 3. Assert that the MockApp's write_multiple_coils_response callback was invoked with correct data
2510        let received_responses = client_services
2511            .app
2512            .received_write_multiple_coils_responses
2513            .borrow();
2514        assert_eq!(received_responses.len(), 1);
2515
2516        let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_quantity) = &received_responses[0];
2517        assert_eq!(*rcv_txn_id, txn_id);
2518        assert_eq!(*rcv_unit_id, unit_id);
2519        assert_eq!(*rcv_address, address);
2520        assert_eq!(*rcv_quantity, quantity);
2521
2522        // 4. Assert that the expected response was removed from the queue
2523        assert!(client_services.expected_responses.is_empty());
2524    }
2525
2526    /// Test case: `ClientServices` successfully sends a Read Coils request and processes a valid response.
2527    #[test]
2528    fn test_client_services_read_coils_e2e_success() {
2529        let transport = MockTransport::default();
2530        let app = MockApp::default();
2531        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2532        let mut client_services =
2533            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2534
2535        let txn_id = 0x0001;
2536        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2537        let address = 0x0000;
2538        let quantity = 8;
2539        client_services
2540            .read_multiple_coils(txn_id, unit_id, address, quantity) // current_millis() is called internally
2541            .unwrap();
2542
2543        // Verify that the request was sent via the mock transport
2544        let sent_adu = client_services
2545            .transport
2546            .sent_frames
2547            .borrow_mut()
2548            .pop_front()
2549            .unwrap(); // Corrected: Removed duplicate pop_front()
2550        // Expected ADU: TID(0x0001), PID(0x0000), Length(0x0006 = Unit ID + FC + Addr + Qty), UnitID(0x01), FC(0x01), Addr(0x0000), Qty(0x0008)
2551        assert_eq!(
2552            sent_adu.as_slice(),
2553            &[
2554                0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x00, 0x00, 0x08
2555            ]
2556        );
2557
2558        // Verify that the expected response was recorded
2559        assert_eq!(client_services.expected_responses.len(), 1); // Corrected: Removed duplicate pop_front()
2560        let from_address = client_services.expected_responses[0]
2561            .operation_meta
2562            .address();
2563        let expected_quantity = client_services.expected_responses[0]
2564            .operation_meta
2565            .quantity();
2566
2567        assert_eq!(expected_quantity, quantity);
2568        assert_eq!(from_address, address);
2569
2570        // 2. Manually construct a valid Read Coils response ADU
2571        // Response for reading 8 coils, values: 10110011 (0xB3)
2572        // ADU: TID(0x0001), PID(0x0000), Length(0x0004 = Unit ID + FC + Byte Count + Coil Data), UnitID(0x01), FC(0x01), Byte Count(0x01), Coil Data(0xB3)
2573        let response_adu = [0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0xB3];
2574
2575        // Simulate receiving the frame
2576        client_services
2577            .transport
2578            .recv_frames
2579            .borrow_mut()
2580            .push_back(Vec::from_slice(&response_adu).unwrap())
2581            .unwrap();
2582        client_services.poll(); // Call poll to ingest frame and process
2583
2584        // Advance time to ensure any potential timeouts are processed (though not expected here)
2585
2586        // 3. Assert that the MockApp's callback was invoked with correct data
2587        let received_responses = client_services.app().received_coil_responses.borrow();
2588        assert_eq!(received_responses.len(), 1);
2589
2590        let (rcv_txn_id, rcv_unit_id, rcv_coils) = &received_responses[0];
2591        let rcv_quantity = rcv_coils.quantity();
2592        assert_eq!(*rcv_txn_id, txn_id);
2593        assert_eq!(*rcv_unit_id, unit_id);
2594        assert_eq!(rcv_coils.from_address(), address);
2595        assert_eq!(rcv_coils.quantity(), quantity);
2596        assert_eq!(&rcv_coils.values()[..1], &[0xB3]);
2597        assert_eq!(rcv_quantity, quantity);
2598
2599        // 4. Assert that the expected response was removed from the queue
2600        assert!(client_services.expected_responses.is_empty());
2601    }
2602
2603    /// Test case: `poll` handles a timed-out request with retries.
2604    #[test]
2605    fn test_client_services_timeout_with_retry() {
2606        let transport = MockTransport::default();
2607        // Simulate no response from the server initially
2608        transport.recv_frames.borrow_mut().clear();
2609        let app = MockApp::default();
2610        let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2611        tcp_config.response_timeout_ms = 100; // Short timeout for testing
2612        tcp_config.retry_attempts = 1; // One retry
2613        let config = ModbusConfig::Tcp(tcp_config);
2614
2615        let mut client_services =
2616            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2617
2618        let txn_id = 0x0005;
2619        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2620        let address = 0x0000;
2621
2622        client_services
2623            .read_single_coil(txn_id, unit_id, address)
2624            .unwrap();
2625
2626        // Advance time past timeout for the first time
2627        *client_services.app().current_time.borrow_mut() = 150;
2628        // Simulate time passing beyond timeout, but with retries left
2629        client_services.poll(); // First timeout, should retry
2630
2631        // Verify that the request was re-sent (2 frames: initial + retry)
2632        assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2633        assert_eq!(client_services.expected_responses.len(), 1); // Still waiting for response
2634        assert_eq!(client_services.expected_responses[0].retries_left, 0); // One retry used
2635
2636        // Advance time past timeout for the second time
2637        *client_services.app().current_time.borrow_mut() = 300;
2638        // Simulate more time passing, exhausting retries
2639        client_services.poll(); // Second timeout, should fail
2640
2641        // Verify that the request is no longer expected and an error was reported
2642        assert!(client_services.expected_responses.is_empty());
2643        // In a real scenario, MockApp::request_failed would be checked.
2644    }
2645
2646    /// Test case: `poll` correctly handles multiple concurrent requests timing out simultaneously.
2647    #[test]
2648    fn test_client_services_concurrent_timeouts() {
2649        let transport = MockTransport::default();
2650        let app = MockApp::default();
2651
2652        // Configure a short timeout and 1 retry for testing purposes
2653        let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2654        tcp_config.response_timeout_ms = 100;
2655        tcp_config.retry_attempts = 1;
2656        let config = ModbusConfig::Tcp(tcp_config);
2657
2658        let mut client_services =
2659            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2660
2661        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2662
2663        // 1. Send two simultaneous requests
2664        client_services
2665            .read_single_coil(1, unit_id, 0x0000)
2666            .unwrap();
2667        client_services
2668            .read_single_coil(2, unit_id, 0x0001)
2669            .unwrap();
2670
2671        // Verify both requests are queued and sent once
2672        assert_eq!(client_services.expected_responses.len(), 2);
2673        assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2674
2675        // 2. Advance time past the timeout threshold for both requests
2676        *client_services.app().current_time.borrow_mut() = 150;
2677
2678        // 3. Poll the client. Both requests should be evaluated, found timed out, and retried.
2679        client_services.poll();
2680
2681        // Verify both requests are STILL in the queue (waiting for retry responses)
2682        assert_eq!(client_services.expected_responses.len(), 2);
2683        assert_eq!(client_services.expected_responses[0].retries_left, 0);
2684        assert_eq!(client_services.expected_responses[1].retries_left, 0);
2685
2686        // Verify both requests were transmitted again (Total sent frames = 2 original + 2 retries = 4)
2687        assert_eq!(client_services.transport.sent_frames.borrow().len(), 4);
2688
2689        // 4. Advance time again past the retry timeout threshold
2690        *client_services.app().current_time.borrow_mut() = 300;
2691
2692        // 5. Poll the client. Both requests should exhaust their retries and be dropped.
2693        client_services.poll();
2694
2695        // Verify the queue is now completely empty
2696        assert!(client_services.expected_responses.is_empty());
2697
2698        // Verify the application was notified of BOTH failures
2699        let failed_requests = client_services.app().failed_requests.borrow();
2700        assert_eq!(failed_requests.len(), 2);
2701
2702        // Ensure both specific transaction IDs were reported as having no retries left
2703        let has_txn_1 = failed_requests
2704            .iter()
2705            .any(|(txn, _, err)| *txn == 1 && *err == MbusError::NoRetriesLeft);
2706        let has_txn_2 = failed_requests
2707            .iter()
2708            .any(|(txn, _, err)| *txn == 2 && *err == MbusError::NoRetriesLeft);
2709        assert!(has_txn_1, "Transaction 1 should have failed");
2710        assert!(has_txn_2, "Transaction 2 should have failed");
2711    }
2712
2713    #[test]
2714    fn test_poll_connection_loss_flushes_pending_requests() {
2715        let transport = MockTransport::default();
2716        let app = MockApp::default();
2717        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2718        let mut client_services =
2719            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2720
2721        let unit_id = UnitIdOrSlaveAddr::new(1).unwrap();
2722        client_services.read_single_coil(1, unit_id, 0).unwrap();
2723        client_services.read_single_coil(2, unit_id, 1).unwrap();
2724        assert_eq!(client_services.expected_responses.len(), 2);
2725
2726        *client_services.transport.is_connected_flag.borrow_mut() = false;
2727        *client_services.transport.recv_error.borrow_mut() = Some(MbusError::ConnectionClosed);
2728
2729        client_services.poll();
2730
2731        assert!(client_services.expected_responses.is_empty());
2732        assert_eq!(client_services.next_timeout_check, None);
2733
2734        let failed_requests = client_services.app().failed_requests.borrow();
2735        assert_eq!(failed_requests.len(), 2);
2736        assert!(
2737            failed_requests
2738                .iter()
2739                .all(|(txn, _, err)| (*txn == 1 || *txn == 2) && *err == MbusError::ConnectionLost)
2740        );
2741    }
2742
2743    #[test]
2744    fn test_fixed_backoff_schedules_and_does_not_retry_early() {
2745        let transport = MockTransport::default();
2746        let app = MockApp::default();
2747        let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2748        tcp_config.response_timeout_ms = 100;
2749        tcp_config.retry_attempts = 1;
2750        tcp_config.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 50 };
2751        let config = ModbusConfig::Tcp(tcp_config);
2752
2753        let mut client_services =
2754            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2755
2756        client_services
2757            .read_single_coil(1, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2758            .unwrap();
2759        assert_eq!(client_services.transport.sent_frames.borrow().len(), 1);
2760
2761        *client_services.app().current_time.borrow_mut() = 101;
2762        client_services.poll();
2763        assert_eq!(client_services.transport.sent_frames.borrow().len(), 1);
2764        assert_eq!(
2765            client_services.expected_responses[0].next_retry_timestamp,
2766            Some(151)
2767        );
2768
2769        *client_services.app().current_time.borrow_mut() = 150;
2770        client_services.poll();
2771        assert_eq!(client_services.transport.sent_frames.borrow().len(), 1);
2772
2773        *client_services.app().current_time.borrow_mut() = 151;
2774        client_services.poll();
2775        assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2776    }
2777
2778    #[test]
2779    fn test_exponential_backoff_growth() {
2780        let transport = MockTransport::default();
2781        let app = MockApp::default();
2782        let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2783        tcp_config.response_timeout_ms = 100;
2784        tcp_config.retry_attempts = 2;
2785        tcp_config.retry_backoff_strategy = BackoffStrategy::Exponential {
2786            base_delay_ms: 50,
2787            max_delay_ms: 500,
2788        };
2789        let config = ModbusConfig::Tcp(tcp_config);
2790
2791        let mut client_services =
2792            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2793
2794        client_services
2795            .read_single_coil(7, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2796            .unwrap();
2797
2798        *client_services.app().current_time.borrow_mut() = 101;
2799        client_services.poll();
2800        assert_eq!(
2801            client_services.expected_responses[0].next_retry_timestamp,
2802            Some(151)
2803        );
2804
2805        *client_services.app().current_time.borrow_mut() = 151;
2806        client_services.poll();
2807        assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2808
2809        *client_services.app().current_time.borrow_mut() = 252;
2810        client_services.poll();
2811        assert_eq!(
2812            client_services.expected_responses[0].next_retry_timestamp,
2813            Some(352)
2814        );
2815
2816        *client_services.app().current_time.borrow_mut() = 352;
2817        client_services.poll();
2818        assert_eq!(client_services.transport.sent_frames.borrow().len(), 3);
2819    }
2820
2821    #[test]
2822    fn test_jitter_bounds_with_random_source_lower_bound() {
2823        let transport = MockTransport::default();
2824        let app = MockApp::default();
2825        let mut tcp_config = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2826        tcp_config.response_timeout_ms = 100;
2827        tcp_config.retry_attempts = 1;
2828        tcp_config.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 100 };
2829        tcp_config.retry_jitter_strategy = JitterStrategy::Percentage { percent: 20 };
2830        tcp_config.retry_random_fn = Some(rand_zero);
2831        let config = ModbusConfig::Tcp(tcp_config);
2832
2833        let mut client_services =
2834            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2835        client_services
2836            .read_single_coil(10, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2837            .unwrap();
2838
2839        *client_services.app().current_time.borrow_mut() = 101;
2840        client_services.poll();
2841        assert_eq!(
2842            client_services.expected_responses[0].next_retry_timestamp,
2843            Some(181)
2844        );
2845    }
2846
2847    #[test]
2848    fn test_jitter_bounds_with_random_source_upper_bound() {
2849        let transport3 = MockTransport::default();
2850        let app3 = MockApp::default();
2851        let mut tcp_config3 = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2852        tcp_config3.response_timeout_ms = 100;
2853        tcp_config3.retry_attempts = 1;
2854        tcp_config3.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 100 };
2855        tcp_config3.retry_jitter_strategy = JitterStrategy::Percentage { percent: 20 };
2856        tcp_config3.retry_random_fn = Some(rand_upper_percent_20);
2857        let config3 = ModbusConfig::Tcp(tcp_config3);
2858
2859        let mut client_services3 =
2860            ClientServices::<MockTransport, MockApp, 10>::new(transport3, app3, config3).unwrap();
2861        client_services3
2862            .read_single_coil(12, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2863            .unwrap();
2864
2865        *client_services3.app.current_time.borrow_mut() = 101;
2866        client_services3.poll();
2867        assert_eq!(
2868            client_services3.expected_responses[0].next_retry_timestamp,
2869            Some(221)
2870        );
2871    }
2872
2873    #[test]
2874    fn test_jitter_falls_back_without_random_source() {
2875        let transport2 = MockTransport::default();
2876        let app2 = MockApp::default();
2877        let mut tcp_config2 = ModbusTcpConfig::new("127.0.0.1", 502).unwrap();
2878        tcp_config2.response_timeout_ms = 100;
2879        tcp_config2.retry_attempts = 1;
2880        tcp_config2.retry_backoff_strategy = BackoffStrategy::Fixed { delay_ms: 100 };
2881        tcp_config2.retry_jitter_strategy = JitterStrategy::Percentage { percent: 20 };
2882        tcp_config2.retry_random_fn = None;
2883        let config2 = ModbusConfig::Tcp(tcp_config2);
2884
2885        let mut client_services2 =
2886            ClientServices::<MockTransport, MockApp, 10>::new(transport2, app2, config2).unwrap();
2887        client_services2
2888            .read_single_coil(11, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2889            .unwrap();
2890
2891        *client_services2.app.current_time.borrow_mut() = 101;
2892        client_services2.poll();
2893        assert_eq!(
2894            client_services2.expected_responses[0].next_retry_timestamp,
2895            Some(201)
2896        );
2897    }
2898
2899    #[test]
2900    fn test_serial_retry_scheduling_uses_backoff() {
2901        let transport = MockTransport {
2902            transport_type: Some(TransportType::StdSerial(SerialMode::Rtu)),
2903            ..Default::default()
2904        };
2905        let app = MockApp::default();
2906
2907        let serial_config = ModbusSerialConfig {
2908            port_path: heapless::String::<64>::from_str("/dev/ttyUSB0").unwrap(),
2909            mode: SerialMode::Rtu,
2910            baud_rate: BaudRate::Baud9600,
2911            data_bits: mbus_core::transport::DataBits::Eight,
2912            stop_bits: 1,
2913            parity: Parity::None,
2914            response_timeout_ms: 100,
2915            retry_attempts: 1,
2916            retry_backoff_strategy: BackoffStrategy::Fixed { delay_ms: 25 },
2917            retry_jitter_strategy: JitterStrategy::None,
2918            retry_random_fn: None,
2919        };
2920
2921        let mut client_services = ClientServices::<MockTransport, MockApp, 1>::new(
2922            transport,
2923            app,
2924            ModbusConfig::Serial(serial_config),
2925        )
2926        .unwrap();
2927
2928        client_services
2929            .read_single_coil(1, UnitIdOrSlaveAddr::new(1).unwrap(), 0)
2930            .unwrap();
2931
2932        *client_services.app().current_time.borrow_mut() = 101;
2933        client_services.poll();
2934        assert_eq!(
2935            client_services.expected_responses[0].next_retry_timestamp,
2936            Some(126)
2937        );
2938
2939        *client_services.app().current_time.borrow_mut() = 126;
2940        client_services.poll();
2941        assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
2942    }
2943
2944    /// Test case: `read_multiple_coils` returns `MbusError::TooManyRequests` when the queue is full.
2945    #[test]
2946    fn test_too_many_requests_error() {
2947        let transport = MockTransport::default();
2948        let app = MockApp::default();
2949        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2950        // Create a client with a small capacity for expected responses
2951        let mut client_services =
2952            ClientServices::<MockTransport, MockApp, 1>::new(transport, app, config).unwrap();
2953
2954        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2955        // Send one request, which should fill the queue
2956        client_services
2957            .read_multiple_coils(1, unit_id, 0, 1)
2958            .unwrap();
2959        assert_eq!(client_services.expected_responses.len(), 1);
2960
2961        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2962        // Attempt to send another request, which should fail due to full queue
2963        let result = client_services.read_multiple_coils(2, unit_id, 0, 1);
2964        assert!(result.is_err());
2965        assert_eq!(result.unwrap_err(), MbusError::TooManyRequests);
2966        assert_eq!(client_services.expected_responses.len(), 1); // Queue size remains 1
2967    }
2968
2969    /// Test case: `read_holding_registers` sends a valid ADU over the transport.
2970    #[test]
2971    fn test_read_holding_registers_sends_valid_adu() {
2972        let transport = MockTransport::default();
2973        let app = MockApp::default();
2974        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
2975        let mut client_services =
2976            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
2977
2978        let txn_id = 0x0005;
2979        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
2980        let address = 0x0000;
2981        let quantity = 2;
2982        client_services
2983            .read_holding_registers(txn_id, unit_id, address, quantity)
2984            .unwrap();
2985
2986        let sent_frames = client_services.transport.sent_frames.borrow();
2987        assert_eq!(sent_frames.len(), 1);
2988        let sent_adu = sent_frames.front().unwrap();
2989
2990        // Expected ADU: TID(0x0005), PID(0x0000), Length(0x0006 = Unit ID + FC + Addr + Qty), UnitID(0x01), FC(0x03), Addr(0x0000), Qty(0x0002)
2991        #[rustfmt::skip]
2992        let expected_adu: [u8; 12] = [
2993            0x00, 0x05, // Transaction ID
2994            0x00, 0x00, // Protocol ID
2995            0x00, 0x06, // Length (1 byte Unit ID + 1 byte FC + 2 bytes Address + 2 bytes Quantity = 6)
2996            0x01,       // Unit ID
2997            0x03,       // Function Code (Read Holding Registers)
2998            0x00, 0x00, // Starting Address
2999            0x00, 0x02, // Quantity of Registers
3000        ];
3001        assert_eq!(sent_adu.as_slice(), &expected_adu);
3002    }
3003
3004    /// Test case: `ClientServices` successfully sends a Read Holding Registers request and processes a valid response.
3005    #[test]
3006    fn test_client_services_read_holding_registers_e2e_success() {
3007        let transport = MockTransport::default();
3008        let app = MockApp::default();
3009        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3010        let mut client_services =
3011            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3012
3013        let txn_id = 0x0005;
3014        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3015        let address = 0x0000;
3016        let quantity = 2;
3017        client_services
3018            .read_holding_registers(txn_id, unit_id, address, quantity)
3019            .unwrap();
3020
3021        // Simulate response
3022        // ADU: TID(0x0005), PID(0x0000), Length(0x0007), UnitID(0x01), FC(0x03), Byte Count(0x04), Data(0x1234, 0x5678)
3023        let response_adu = [
3024            0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x01, 0x03, 0x04, 0x12, 0x34, 0x56, 0x78,
3025        ];
3026        client_services
3027            .transport
3028            .recv_frames
3029            .borrow_mut()
3030            .push_back(Vec::from_slice(&response_adu).unwrap())
3031            .unwrap();
3032        client_services.poll();
3033
3034        let received_responses = client_services
3035            .app
3036            .received_holding_register_responses
3037            .borrow();
3038        assert_eq!(received_responses.len(), 1);
3039        let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3040        assert_eq!(*rcv_txn_id, txn_id);
3041        assert_eq!(*rcv_unit_id, unit_id);
3042        assert_eq!(rcv_registers.from_address(), address);
3043        assert_eq!(rcv_registers.quantity(), quantity);
3044        assert_eq!(&rcv_registers.values()[..2], &[0x1234, 0x5678]);
3045        assert_eq!(*rcv_quantity, quantity);
3046        assert!(client_services.expected_responses.is_empty());
3047    }
3048
3049    /// Test case: `read_input_registers` sends a valid ADU over the transport.
3050    #[test]
3051    fn test_read_input_registers_sends_valid_adu() {
3052        let transport = MockTransport::default();
3053        let app = MockApp::default();
3054        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3055        let mut client_services =
3056            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3057
3058        let txn_id = 0x0006;
3059        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3060        let address = 0x0000;
3061        let quantity = 2;
3062        client_services
3063            .read_input_registers(txn_id, unit_id, address, quantity)
3064            .unwrap();
3065
3066        let sent_frames = client_services.transport.sent_frames.borrow();
3067        assert_eq!(sent_frames.len(), 1);
3068        let sent_adu = sent_frames.front().unwrap();
3069
3070        // Expected ADU: TID(0x0006), PID(0x0000), Length(0x0006 = Unit ID + FC + Addr + Qty), UnitID(0x01), FC(0x04), Addr(0x0000), Qty(0x0002)
3071        #[rustfmt::skip]
3072        let expected_adu: [u8; 12] = [
3073            0x00, 0x06, // Transaction ID
3074            0x00, 0x00, // Protocol ID
3075            0x00, 0x06, // Length (1 byte Unit ID + 1 byte FC + 2 bytes Address + 2 bytes Quantity = 6)
3076            0x01,       // Unit ID
3077            0x04,       // Function Code (Read Input Registers)
3078            0x00, 0x00, // Starting Address
3079            0x00, 0x02, // Quantity of Registers
3080        ];
3081        assert_eq!(sent_adu.as_slice(), &expected_adu);
3082    }
3083
3084    /// Test case: `ClientServices` successfully sends a Read Input Registers request and processes a valid response.
3085    #[test]
3086    fn test_client_services_read_input_registers_e2e_success() {
3087        let transport = MockTransport::default();
3088        let app = MockApp::default();
3089        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3090        let mut client_services =
3091            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3092
3093        let txn_id = 0x0006;
3094        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3095        let address = 0x0000;
3096        let quantity = 2;
3097        client_services
3098            .read_input_registers(txn_id, unit_id, address, quantity)
3099            .unwrap();
3100
3101        // Simulate response
3102        // ADU: TID(0x0006), PID(0x0000), Length(0x0007), UnitID(0x01), FC(0x04), Byte Count(0x04), Data(0xAABB, 0xCCDD)
3103        let response_adu = [
3104            0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x01, 0x04, 0x04, 0xAA, 0xBB, 0xCC, 0xDD,
3105        ];
3106        client_services
3107            .transport
3108            .recv_frames
3109            .borrow_mut()
3110            .push_back(Vec::from_slice(&response_adu).unwrap())
3111            .unwrap();
3112        client_services.poll();
3113
3114        let received_responses = client_services
3115            .app
3116            .received_input_register_responses
3117            .borrow();
3118        assert_eq!(received_responses.len(), 1);
3119        let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3120        assert_eq!(*rcv_txn_id, txn_id);
3121        assert_eq!(*rcv_unit_id, unit_id);
3122        assert_eq!(rcv_registers.from_address(), address);
3123        assert_eq!(rcv_registers.quantity(), quantity);
3124        assert_eq!(&rcv_registers.values()[..2], &[0xAABB, 0xCCDD]);
3125        assert_eq!(*rcv_quantity, quantity);
3126        assert!(client_services.expected_responses.is_empty());
3127    }
3128
3129    /// Test case: `write_single_register` sends a valid ADU over the transport.
3130    #[test]
3131    fn test_write_single_register_sends_valid_adu() {
3132        let transport = MockTransport::default();
3133        let app = MockApp::default();
3134        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3135        let mut client_services =
3136            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3137
3138        let txn_id = 0x0007;
3139        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3140        let address = 0x0001;
3141        let value = 0x1234;
3142        client_services
3143            .write_single_register(txn_id, unit_id, address, value)
3144            .unwrap();
3145
3146        let sent_frames = client_services.transport.sent_frames.borrow();
3147        assert_eq!(sent_frames.len(), 1);
3148        let sent_adu = sent_frames.front().unwrap();
3149
3150        // Expected ADU: TID(0x0007), PID(0x0000), Length(0x0006), UnitID(0x01), FC(0x06), Addr(0x0001), Value(0x1234)
3151        #[rustfmt::skip]
3152        let expected_adu: [u8; 12] = [
3153            0x00, 0x07, // Transaction ID
3154            0x00, 0x00, // Protocol ID
3155            0x00, 0x06, // Length (1 byte Unit ID + 1 byte FC + 2 bytes Address + 2 bytes Value = 6)
3156            0x01,       // Unit ID
3157            0x06,       // Function Code (Write Single Register)
3158            0x00, 0x01, // Address
3159            0x12, 0x34, // Value
3160        ];
3161        assert_eq!(sent_adu.as_slice(), &expected_adu);
3162    }
3163
3164    /// Test case: `ClientServices` successfully sends a Write Single Register request and processes a valid response.
3165    #[test]
3166    fn test_client_services_write_single_register_e2e_success() {
3167        let transport = MockTransport::default();
3168        let app = MockApp::default();
3169        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3170        let mut client_services =
3171            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3172
3173        let txn_id = 0x0007;
3174        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3175        let address = 0x0001;
3176        let value = 0x1234;
3177        client_services
3178            .write_single_register(txn_id, unit_id, address, value)
3179            .unwrap();
3180
3181        // Simulate response
3182        // ADU: TID(0x0007), PID(0x0000), Length(0x0006), UnitID(0x01), FC(0x06), Address(0x0001), Value(0x1234)
3183        let response_adu = [
3184            0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x01, 0x06, 0x00, 0x01, 0x12, 0x34,
3185        ];
3186        client_services
3187            .transport
3188            .recv_frames
3189            .borrow_mut()
3190            .push_back(Vec::from_slice(&response_adu).unwrap())
3191            .unwrap();
3192        client_services.poll();
3193
3194        let received_responses = client_services
3195            .app
3196            .received_write_single_register_responses
3197            .borrow();
3198        assert_eq!(received_responses.len(), 1);
3199        let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_value) = &received_responses[0];
3200        assert_eq!(*rcv_txn_id, txn_id);
3201        assert_eq!(*rcv_unit_id, unit_id);
3202        assert_eq!(*rcv_address, address);
3203        assert_eq!(*rcv_value, value);
3204        assert!(client_services.expected_responses.is_empty());
3205    }
3206
3207    /// Test case: `write_multiple_registers` sends a valid ADU over the transport.
3208    #[test]
3209    fn test_write_multiple_registers_sends_valid_adu() {
3210        let transport = MockTransport::default();
3211        let app = MockApp::default();
3212        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3213        let mut client_services =
3214            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3215
3216        let txn_id = 0x0008;
3217        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3218        let address = 0x0001;
3219        let quantity = 2;
3220        let values = [0x1234, 0x5678];
3221        client_services
3222            .write_multiple_registers(txn_id, unit_id, address, quantity, &values)
3223            .unwrap();
3224
3225        let sent_frames = client_services.transport.sent_frames.borrow();
3226        assert_eq!(sent_frames.len(), 1);
3227        let sent_adu = sent_frames.front().unwrap();
3228
3229        // Expected ADU: TID(0x0008), PID(0x0000), Length(0x0009), UnitID(0x01), FC(0x10), Addr(0x0001), Qty(0x0002), Byte Count(0x04), Data(0x1234, 0x5678)
3230        #[rustfmt::skip]
3231        let expected_adu: [u8; 17] = [ // Total ADU length is 17 bytes
3232            0x00, 0x08, // Transaction ID
3233            0x00, 0x00, // Protocol ID
3234            0x00, 0x0B, // Length (UnitID(1) + PDU(10) = 11)
3235            0x01,       // Unit ID
3236            0x10,       // Function Code (Write Multiple Registers)
3237            0x00, 0x01, // Address
3238            0x00, 0x02, // Quantity
3239            0x04,       // Byte Count
3240            0x12, 0x34, 0x56, 0x78, // Data
3241        ];
3242        assert_eq!(sent_adu.as_slice(), &expected_adu);
3243    }
3244
3245    /// Test case: `ClientServices` successfully sends a Write Multiple Registers request and processes a valid response.
3246    #[test]
3247    fn test_client_services_write_multiple_registers_e2e_success() {
3248        let transport = MockTransport::default();
3249        let app = MockApp::default();
3250        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3251        let mut client_services =
3252            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3253
3254        let txn_id = 0x0008;
3255        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3256        let address = 0x0001;
3257        let quantity = 2;
3258        let values = [0x1234, 0x5678];
3259        client_services
3260            .write_multiple_registers(txn_id, unit_id, address, quantity, &values)
3261            .unwrap();
3262
3263        // Simulate response
3264        // ADU: TID(0x0008), PID(0x0000), Length(0x0006), UnitID(0x01), FC(0x10), Address(0x0001), Quantity(0x0002)
3265        let response_adu = [
3266            0x00, 0x08, 0x00, 0x00, 0x00, 0x06, 0x01, 0x10, 0x00, 0x01, 0x00, 0x02,
3267        ];
3268        client_services
3269            .transport
3270            .recv_frames
3271            .borrow_mut()
3272            .push_back(Vec::from_slice(&response_adu).unwrap())
3273            .unwrap();
3274        client_services.poll();
3275
3276        let received_responses = client_services
3277            .app
3278            .received_write_multiple_register_responses
3279            .borrow();
3280        assert_eq!(received_responses.len(), 1);
3281        let (rcv_txn_id, rcv_unit_id, rcv_address, rcv_quantity) = &received_responses[0];
3282        assert_eq!(*rcv_txn_id, txn_id);
3283        assert_eq!(*rcv_unit_id, unit_id);
3284        assert_eq!(*rcv_address, address);
3285        assert_eq!(*rcv_quantity, quantity);
3286        assert!(client_services.expected_responses.is_empty());
3287    }
3288
3289    /// Test case: `ClientServices` correctly handles a Modbus exception response.
3290    #[test]
3291    fn test_client_services_handles_exception_response() {
3292        let transport = MockTransport::default();
3293        let app = MockApp::default();
3294        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3295        let mut client_services =
3296            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3297
3298        let txn_id = 0x0009;
3299        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3300        let address = 0x0000;
3301        let quantity = 1;
3302
3303        client_services
3304            .read_holding_registers(txn_id, unit_id, address, quantity)
3305            .unwrap();
3306
3307        // Simulate an exception response (e.g., Illegal Data Address)
3308        // FC = 0x83 (0x03 + 0x80), Exception Code = 0x02
3309        let exception_adu = [
3310            0x00, 0x09, // Transaction ID
3311            0x00, 0x00, // Protocol ID
3312            0x00, 0x03, // Length
3313            0x01, // Unit ID
3314            0x83, // Function Code (0x03 + 0x80 Error Mask)
3315            0x02, // Exception Code (Illegal Data Address)
3316        ];
3317        client_services
3318            .transport
3319            .recv_frames
3320            .borrow_mut()
3321            .push_back(Vec::from_slice(&exception_adu).unwrap())
3322            .unwrap();
3323        client_services.poll();
3324
3325        // Verify that no successful response was recorded
3326        assert!(
3327            client_services
3328                .app
3329                .received_holding_register_responses
3330                .borrow()
3331                .is_empty()
3332        );
3333        // Verify that the failure was reported to the app
3334        assert_eq!(client_services.app().failed_requests.borrow().len(), 1);
3335        let (failed_txn, failed_unit, failed_err) =
3336            &client_services.app().failed_requests.borrow()[0];
3337        assert_eq!(*failed_txn, txn_id);
3338        assert_eq!(*failed_unit, unit_id);
3339        assert_eq!(*failed_err, MbusError::ModbusException(0x02));
3340    }
3341
3342    /// Test case: `read_single_holding_register` sends a valid ADU.
3343    #[test]
3344    fn test_read_single_holding_register_sends_valid_adu() {
3345        let transport = MockTransport::default();
3346        let app = MockApp::default();
3347        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3348        let mut client_services =
3349            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3350
3351        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3352        client_services
3353            .read_single_holding_register(10, unit_id, 100)
3354            .unwrap();
3355
3356        let sent_frames = client_services.transport.sent_frames.borrow();
3357        assert_eq!(sent_frames.len(), 1);
3358        let sent_adu = sent_frames.front().unwrap();
3359
3360        #[rustfmt::skip]
3361        let expected_adu: [u8; 12] = [
3362            0x00, 0x0A, // TID
3363            0x00, 0x00, // PID
3364            0x00, 0x06, // Length
3365            0x01,       // Unit ID
3366            0x03,       // FC
3367            0x00, 0x64, // Address
3368            0x00, 0x01, // Quantity
3369        ];
3370        assert_eq!(sent_adu.as_slice(), &expected_adu);
3371
3372        // Verify expected response
3373        assert_eq!(client_services.expected_responses.len(), 1);
3374        let single_read = client_services.expected_responses[0]
3375            .operation_meta
3376            .is_single();
3377        assert!(single_read);
3378    }
3379
3380    /// Test case: `ClientServices` successfully sends and processes a `read_single_holding_register` request.
3381    #[test]
3382    fn test_client_services_read_single_holding_register_e2e_success() {
3383        let transport = MockTransport::default();
3384        let app = MockApp::default();
3385        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3386        let mut client_services =
3387            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3388
3389        let txn_id = 10;
3390        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3391        let address = 100;
3392
3393        client_services
3394            .read_single_holding_register(txn_id, unit_id, address)
3395            .unwrap();
3396
3397        // Simulate response
3398        let response_adu = [
3399            0x00, 0x0A, 0x00, 0x00, 0x00, 0x05, 0x01, 0x03, 0x02, 0x12, 0x34,
3400        ];
3401        client_services
3402            .transport
3403            .recv_frames
3404            .borrow_mut()
3405            .push_back(Vec::from_slice(&response_adu).unwrap())
3406            .unwrap();
3407        client_services.poll();
3408
3409        let received_responses = client_services
3410            .app
3411            .received_holding_register_responses
3412            .borrow();
3413        assert_eq!(received_responses.len(), 1);
3414        let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3415        assert_eq!(*rcv_txn_id, txn_id);
3416        assert_eq!(*rcv_unit_id, unit_id);
3417        assert_eq!(rcv_registers.from_address(), address);
3418        assert_eq!(rcv_registers.quantity(), 1);
3419        assert_eq!(&rcv_registers.values()[..1], &[0x1234]);
3420        assert_eq!(*rcv_quantity, 1);
3421    }
3422
3423    /// Test case: `read_single_input_register` sends a valid ADU.
3424    #[test]
3425    fn test_read_single_input_register_sends_valid_adu() {
3426        let transport = MockTransport::default();
3427        let app = MockApp::default();
3428        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3429        let mut client_services =
3430            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3431
3432        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3433        client_services
3434            .read_single_input_register(10, unit_id, 100)
3435            .unwrap();
3436
3437        let sent_frames = client_services.transport.sent_frames.borrow();
3438        assert_eq!(sent_frames.len(), 1);
3439        let sent_adu = sent_frames.front().unwrap();
3440
3441        #[rustfmt::skip]
3442        let expected_adu: [u8; 12] = [
3443            0x00, 0x0A, // TID
3444            0x00, 0x00, // PID
3445            0x00, 0x06, // Length
3446            0x01,       // Unit ID
3447            0x04,       // FC (Read Input Registers)
3448            0x00, 0x64, // Address
3449            0x00, 0x01, // Quantity
3450        ];
3451        assert_eq!(sent_adu.as_slice(), &expected_adu);
3452
3453        // Verify expected response
3454        assert_eq!(client_services.expected_responses.len(), 1);
3455        let single_read = client_services.expected_responses[0]
3456            .operation_meta
3457            .is_single();
3458        assert!(single_read);
3459    }
3460
3461    /// Test case: `ClientServices` successfully sends and processes a `read_single_input_register` request.
3462    #[test]
3463    fn test_client_services_read_single_input_register_e2e_success() {
3464        let transport = MockTransport::default();
3465        let app = MockApp::default();
3466        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3467        let mut client_services =
3468            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3469
3470        let txn_id = 10;
3471        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3472        let address = 100;
3473
3474        client_services
3475            .read_single_input_register(txn_id, unit_id, address)
3476            .unwrap();
3477
3478        // Simulate response
3479        // ADU: TID(10), PID(0), Len(5), Unit(1), FC(4), ByteCount(2), Data(0x1234)
3480        let response_adu = [
3481            0x00, 0x0A, 0x00, 0x00, 0x00, 0x05, 0x01, 0x04, 0x02, 0x12, 0x34,
3482        ];
3483        client_services
3484            .transport
3485            .recv_frames
3486            .borrow_mut()
3487            .push_back(Vec::from_slice(&response_adu).unwrap())
3488            .unwrap();
3489        client_services.poll();
3490
3491        let received_responses = client_services
3492            .app
3493            .received_input_register_responses
3494            .borrow();
3495        assert_eq!(received_responses.len(), 1);
3496        let (rcv_txn_id, rcv_unit_id, rcv_registers, rcv_quantity) = &received_responses[0];
3497        assert_eq!(*rcv_txn_id, txn_id);
3498        assert_eq!(*rcv_unit_id, unit_id);
3499        assert_eq!(rcv_registers.from_address(), address);
3500        assert_eq!(rcv_registers.quantity(), 1);
3501        assert_eq!(&rcv_registers.values()[..1], &[0x1234]);
3502        assert_eq!(*rcv_quantity, 1);
3503    }
3504
3505    /// Test case: `read_write_multiple_registers` sends a valid ADU.
3506    #[test]
3507    fn test_read_write_multiple_registers_sends_valid_adu() {
3508        let transport = MockTransport::default();
3509        let app = MockApp::default();
3510        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3511        let mut client_services =
3512            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3513
3514        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3515        let write_values = [0xAAAA, 0xBBBB];
3516        client_services
3517            .read_write_multiple_registers(11, unit_id, 10, 2, 20, &write_values)
3518            .unwrap();
3519
3520        let sent_frames = client_services.transport.sent_frames.borrow();
3521        assert_eq!(sent_frames.len(), 1);
3522        let sent_adu = sent_frames.front().unwrap();
3523
3524        #[rustfmt::skip]
3525        let expected_adu: [u8; 21] = [
3526            0x00, 0x0B, // TID
3527            0x00, 0x00, // PID
3528            0x00, 0x0F, // Length
3529            0x01,       // Unit ID
3530            0x17,       // FC
3531            0x00, 0x0A, // Read Address
3532            0x00, 0x02, // Read Quantity
3533            0x00, 0x14, // Write Address
3534            0x00, 0x02, // Write Quantity
3535            0x04,       // Write Byte Count
3536            0xAA, 0xAA, // Write Value 1
3537            0xBB, 0xBB, // Write Value 2
3538        ];
3539        assert_eq!(sent_adu.as_slice(), &expected_adu);
3540    }
3541
3542    /// Test case: `mask_write_register` sends a valid ADU.
3543    #[test]
3544    fn test_mask_write_register_sends_valid_adu() {
3545        let transport = MockTransport::default();
3546        let app = MockApp::default();
3547        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3548        let mut client_services =
3549            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3550
3551        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3552        client_services
3553            .mask_write_register(12, unit_id, 30, 0xF0F0, 0x0F0F)
3554            .unwrap();
3555
3556        let sent_frames = client_services.transport.sent_frames.borrow();
3557        assert_eq!(sent_frames.len(), 1);
3558        let sent_adu = sent_frames.front().unwrap();
3559
3560        #[rustfmt::skip]
3561        let expected_adu: [u8; 14] = [
3562            0x00, 0x0C, // TID
3563            0x00, 0x00, // PID
3564            0x00, 0x08, // Length
3565            0x01,       // Unit ID
3566            0x16,       // FC
3567            0x00, 0x1E, // Address
3568            0xF0, 0xF0, // AND mask
3569            0x0F, 0x0F, // OR mask
3570        ];
3571        assert_eq!(sent_adu.as_slice(), &expected_adu);
3572    }
3573
3574    /// Test case: `ClientServices` successfully sends and processes a `read_write_multiple_registers` request.
3575    #[test]
3576    fn test_client_services_read_write_multiple_registers_e2e_success() {
3577        let transport = MockTransport::default();
3578        let app = MockApp::default();
3579        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3580        let mut client_services =
3581            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3582
3583        let txn_id = 11;
3584        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3585        let read_address = 10;
3586        let read_quantity = 2;
3587        let write_address = 20;
3588        let write_values = [0xAAAA, 0xBBBB];
3589
3590        client_services
3591            .read_write_multiple_registers(
3592                txn_id,
3593                unit_id,
3594                read_address,
3595                read_quantity,
3596                write_address,
3597                &write_values,
3598            )
3599            .unwrap();
3600
3601        // Simulate response
3602        let response_adu = [
3603            0x00, 0x0B, 0x00, 0x00, 0x00, 0x07, 0x01, 0x17, 0x04, 0x12, 0x34, 0x56, 0x78,
3604        ];
3605        client_services
3606            .transport
3607            .recv_frames
3608            .borrow_mut()
3609            .push_back(Vec::from_slice(&response_adu).unwrap())
3610            .unwrap();
3611        client_services.poll();
3612
3613        let received_responses = client_services
3614            .app
3615            .received_read_write_multiple_registers_responses
3616            .borrow();
3617        assert_eq!(received_responses.len(), 1);
3618        let (rcv_txn_id, rcv_unit_id, rcv_registers) = &received_responses[0];
3619        assert_eq!(*rcv_txn_id, txn_id);
3620        assert_eq!(*rcv_unit_id, unit_id);
3621        assert_eq!(rcv_registers.from_address(), read_address);
3622        assert_eq!(rcv_registers.quantity(), read_quantity);
3623        assert_eq!(&rcv_registers.values()[..2], &[0x1234, 0x5678]);
3624    }
3625
3626    /// Test case: `ClientServices` successfully sends and processes a `mask_write_register` request.
3627    #[test]
3628    fn test_client_services_mask_write_register_e2e_success() {
3629        let transport = MockTransport::default();
3630        let app = MockApp::default();
3631        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3632        let mut client_services =
3633            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3634
3635        let txn_id = 12;
3636        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3637        let address = 30;
3638        let and_mask = 0xF0F0;
3639        let or_mask = 0x0F0F;
3640
3641        client_services
3642            .mask_write_register(txn_id, unit_id, address, and_mask, or_mask)
3643            .unwrap();
3644
3645        // Simulate response
3646        let response_adu = [
3647            0x00, 0x0C, 0x00, 0x00, 0x00, 0x08, 0x01, 0x16, 0x00, 0x1E, 0xF0, 0xF0, 0x0F, 0x0F,
3648        ];
3649        client_services
3650            .transport
3651            .recv_frames
3652            .borrow_mut()
3653            .push_back(Vec::from_slice(&response_adu).unwrap())
3654            .unwrap();
3655        client_services.poll();
3656
3657        let received_responses = client_services
3658            .app
3659            .received_mask_write_register_responses
3660            .borrow();
3661        assert_eq!(received_responses.len(), 1);
3662        let (rcv_txn_id, rcv_unit_id) = &received_responses[0];
3663        assert_eq!(*rcv_txn_id, txn_id);
3664        assert_eq!(*rcv_unit_id, unit_id);
3665    }
3666
3667    /// Test case: `ClientServices` successfully sends and processes a `read_fifo_queue` request.
3668    #[test]
3669    fn test_client_services_read_fifo_queue_e2e_success() {
3670        let transport = MockTransport::default();
3671        let app = MockApp::default();
3672        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3673        let mut client_services =
3674            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3675
3676        let txn_id = 13;
3677        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3678        let address = 40;
3679
3680        client_services
3681            .read_fifo_queue(txn_id, unit_id, address)
3682            .unwrap();
3683
3684        // Simulate response
3685        #[rustfmt::skip]
3686        let response_adu = [
3687            0x00, 0x0D, // Transaction ID
3688            0x00, 0x00, // Protocol ID
3689            0x00, 0x0A, // Length (Unit ID + PDU)
3690            0x01,       // Unit ID
3691            0x18,       // Function Code (Read FIFO Queue)
3692            0x00, 0x06, // FIFO Byte Count (2 bytes for FIFO Count + 2 * 2 bytes for values)
3693            0x00, 0x02, // FIFO Count (2 registers)
3694            0xAA, 0xAA, // Register Value 1
3695            0xBB, 0xBB, // Register Value 2
3696        ];
3697        client_services
3698            .transport
3699            .recv_frames
3700            .borrow_mut()
3701            .push_back(Vec::from_slice(&response_adu).unwrap())
3702            .unwrap();
3703        client_services.poll();
3704
3705        let received_responses = client_services
3706            .app
3707            .received_read_fifo_queue_responses
3708            .borrow();
3709        assert_eq!(received_responses.len(), 1);
3710        let (rcv_txn_id, rcv_unit_id, rcv_fifo_queue) = &received_responses[0];
3711        assert_eq!(*rcv_txn_id, txn_id);
3712        assert_eq!(*rcv_unit_id, unit_id);
3713        assert_eq!(rcv_fifo_queue.length(), 2);
3714        assert_eq!(&rcv_fifo_queue.queue()[..2], &[0xAAAA, 0xBBBB]);
3715    }
3716
3717    /// Test case: `ClientServices` successfully sends and processes a `read_file_record` request.
3718    #[test]
3719    fn test_client_services_read_file_record_e2e_success() {
3720        let transport = MockTransport::default();
3721        let app = MockApp::default();
3722        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3723        let mut client_services =
3724            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3725
3726        let txn_id = 14;
3727        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3728        let mut sub_req = SubRequest::new();
3729        sub_req.add_read_sub_request(4, 1, 2).unwrap();
3730
3731        client_services
3732            .read_file_record(txn_id, unit_id, &sub_req)
3733            .unwrap();
3734
3735        // Simulate response: FC(20), ByteCount(7), SubReqLen(6), Ref(6), Data(0x1234, 0x5678)
3736        // Note: ByteCount = 1 (SubReqLen) + 1 (Ref) + 4 (Data) + 1 (SubReqLen for next?) No.
3737        // Response format: ByteCount, [Len, Ref, Data...]
3738        // Len = 1 (Ref) + 4 (Data) = 5.
3739        // ByteCount = 1 (Len) + 5 = 6.
3740        let response_adu = [
3741            0x00, 0x0E, 0x00, 0x00, 0x00, 0x09, 0x01, 0x14, 0x06, 0x05, 0x06, 0x12, 0x34, 0x56,
3742            0x78,
3743        ];
3744
3745        client_services
3746            .transport
3747            .recv_frames
3748            .borrow_mut()
3749            .push_back(Vec::from_slice(&response_adu).unwrap())
3750            .unwrap();
3751        client_services.poll();
3752
3753        let received_responses = client_services
3754            .app
3755            .received_read_file_record_responses
3756            .borrow();
3757        assert_eq!(received_responses.len(), 1);
3758        let (rcv_txn_id, rcv_unit_id, rcv_data) = &received_responses[0];
3759        assert_eq!(*rcv_txn_id, txn_id);
3760        assert_eq!(*rcv_unit_id, unit_id);
3761        assert_eq!(rcv_data.len(), 1);
3762        assert_eq!(
3763            rcv_data[0].record_data.as_ref().unwrap().as_slice(),
3764            &[0x1234, 0x5678]
3765        );
3766    }
3767
3768    /// Test case: `ClientServices` successfully sends and processes a `write_file_record` request.
3769    #[test]
3770    fn test_client_services_write_file_record_e2e_success() {
3771        let transport = MockTransport::default();
3772        let app = MockApp::default();
3773        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3774        let mut client_services =
3775            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3776
3777        let txn_id = 15;
3778        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3779        let mut sub_req = SubRequest::new();
3780        let mut data = Vec::new();
3781        data.push(0x1122).unwrap();
3782        sub_req.add_write_sub_request(4, 1, 1, data).unwrap();
3783
3784        client_services
3785            .write_file_record(txn_id, unit_id, &sub_req)
3786            .unwrap();
3787
3788        // Simulate response (Echo of request)
3789        // FC(21), ByteCount(9), Ref(6), File(4), Rec(1), Len(1), Data(0x1122)
3790        let response_adu = [
3791            0x00, 0x0F, 0x00, 0x00, 0x00, 0x0C, 0x01, 0x15, 0x09, 0x06, 0x00, 0x04, 0x00, 0x01,
3792            0x00, 0x01, 0x11, 0x22,
3793        ];
3794
3795        client_services
3796            .transport
3797            .recv_frames
3798            .borrow_mut()
3799            .push_back(Vec::from_slice(&response_adu).unwrap())
3800            .unwrap();
3801        client_services.poll();
3802
3803        let received_responses = client_services
3804            .app
3805            .received_write_file_record_responses
3806            .borrow();
3807        assert_eq!(received_responses.len(), 1);
3808        let (rcv_txn_id, rcv_unit_id) = &received_responses[0];
3809        assert_eq!(*rcv_txn_id, txn_id);
3810        assert_eq!(*rcv_unit_id, unit_id);
3811    }
3812
3813    /// Test case: `ClientServices` successfully sends and processes a `read_discrete_inputs` request.
3814    #[test]
3815    fn test_client_services_read_discrete_inputs_e2e_success() {
3816        let transport = MockTransport::default();
3817        let app = MockApp::default();
3818        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3819        let mut client_services =
3820            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3821
3822        let txn_id = 16;
3823        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3824        let address = 50;
3825        let quantity = 8;
3826
3827        client_services
3828            .read_discrete_inputs(txn_id, unit_id, address, quantity)
3829            .unwrap();
3830
3831        // Simulate response: FC(02), ByteCount(1), Data(0xAA)
3832        let response_adu = [0x00, 0x10, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x01, 0xAA];
3833
3834        client_services
3835            .transport
3836            .recv_frames
3837            .borrow_mut()
3838            .push_back(Vec::from_slice(&response_adu).unwrap())
3839            .unwrap();
3840        client_services.poll();
3841
3842        let received_responses = client_services
3843            .app
3844            .received_discrete_input_responses
3845            .borrow();
3846        assert_eq!(received_responses.len(), 1);
3847        let (rcv_txn_id, rcv_unit_id, rcv_inputs, rcv_quantity) = &received_responses[0];
3848        assert_eq!(*rcv_txn_id, txn_id);
3849        assert_eq!(*rcv_unit_id, unit_id);
3850        assert_eq!(rcv_inputs.from_address(), address);
3851        assert_eq!(rcv_inputs.quantity(), quantity);
3852        assert_eq!(rcv_inputs.values(), &[0xAA]);
3853        assert_eq!(*rcv_quantity, quantity);
3854    }
3855
3856    /// Test case: `ClientServices` successfully sends and processes a `read_single_discrete_input` request.
3857    #[test]
3858    fn test_client_services_read_single_discrete_input_e2e_success() {
3859        let transport = MockTransport::default();
3860        let app = MockApp::default();
3861        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3862        let mut client_services =
3863            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3864
3865        let txn_id = 17;
3866        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3867        let address = 10;
3868
3869        client_services
3870            .read_single_discrete_input(txn_id, unit_id, address)
3871            .unwrap();
3872
3873        // Verify request ADU
3874        let sent_frames = client_services.transport.sent_frames.borrow();
3875        assert_eq!(sent_frames.len(), 1);
3876        // MBAP(7) + PDU(5) = 12 bytes
3877        // MBAP: 00 11 00 00 00 06 01
3878        // PDU: 02 00 0A 00 01
3879        let expected_request = [
3880            0x00, 0x11, 0x00, 0x00, 0x00, 0x06, 0x01, 0x02, 0x00, 0x0A, 0x00, 0x01,
3881        ];
3882        assert_eq!(sent_frames.front().unwrap().as_slice(), &expected_request);
3883        drop(sent_frames);
3884
3885        // Simulate response: FC(02), ByteCount(1), Data(0x01) -> Input ON
3886        let response_adu = [0x00, 0x11, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x01, 0x01];
3887
3888        client_services
3889            .transport
3890            .recv_frames
3891            .borrow_mut()
3892            .push_back(Vec::from_slice(&response_adu).unwrap())
3893            .unwrap();
3894        client_services.poll();
3895
3896        let received_responses = client_services
3897            .app
3898            .received_discrete_input_responses
3899            .borrow();
3900        assert_eq!(received_responses.len(), 1);
3901        let (rcv_txn_id, rcv_unit_id, rcv_inputs, rcv_quantity) = &received_responses[0];
3902        assert_eq!(*rcv_txn_id, txn_id);
3903        assert_eq!(*rcv_unit_id, unit_id);
3904        assert_eq!(rcv_inputs.from_address(), address);
3905        assert_eq!(rcv_inputs.quantity(), 1);
3906        assert_eq!(rcv_inputs.value(address).unwrap(), true);
3907        assert_eq!(*rcv_quantity, 1);
3908    }
3909
3910    /// Test case: `ClientServices` successfully sends and processes a `read_device_identification` request.
3911    #[test]
3912    fn test_client_services_read_device_identification_e2e_success() {
3913        let transport = MockTransport::default();
3914        let app = MockApp::default();
3915        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3916        let mut client_services =
3917            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3918
3919        let txn_id = 20;
3920        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3921        let read_code = ReadDeviceIdCode::Basic;
3922        let object_id = ObjectId::from(0x00);
3923
3924        client_services
3925            .read_device_identification(txn_id, unit_id, read_code, object_id)
3926            .unwrap();
3927
3928        // Verify request ADU
3929        let sent_frames = client_services.transport.sent_frames.borrow();
3930        assert_eq!(sent_frames.len(), 1);
3931        // MBAP(7) + PDU(4) = 11 bytes
3932        // MBAP: 00 14 00 00 00 05 01
3933        // PDU: 2B 0E 01 00
3934        let expected_request = [
3935            0x00, 0x14, 0x00, 0x00, 0x00, 0x05, 0x01, 0x2B, 0x0E, 0x01, 0x00,
3936        ];
3937        assert_eq!(sent_frames.front().unwrap().as_slice(), &expected_request);
3938        drop(sent_frames);
3939
3940        // Simulate response:
3941        // MEI(0E), Code(01), Conf(81), More(00), Next(00), Num(01), Obj0(00), Len(03), Val("Foo")
3942        // PDU Len = 1(MEI) + 1(Code) + 1(Conf) + 1(More) + 1(Next) + 1(Num) + 1(Id) + 1(Len) + 3(Val) = 11
3943        // MBAP Len = 1(Unit) + 1(FC) + 11 = 13
3944        let response_adu = [
3945            0x00, 0x14, 0x00, 0x00, 0x00, 0x0D, 0x01, 0x2B, 0x0E, 0x01, 0x81, 0x00, 0x00, 0x01,
3946            0x00, 0x03, 0x46, 0x6F, 0x6F,
3947        ];
3948
3949        client_services
3950            .transport
3951            .recv_frames
3952            .borrow_mut()
3953            .push_back(Vec::from_slice(&response_adu).unwrap())
3954            .unwrap();
3955        client_services.poll();
3956
3957        let received_responses = client_services
3958            .app
3959            .received_read_device_id_responses
3960            .borrow();
3961        assert_eq!(received_responses.len(), 1);
3962        let (rcv_txn_id, rcv_unit_id, rcv_resp) = &received_responses[0];
3963        assert_eq!(*rcv_txn_id, txn_id);
3964        assert_eq!(*rcv_unit_id, unit_id);
3965        assert_eq!(rcv_resp.read_device_id_code, ReadDeviceIdCode::Basic);
3966        assert_eq!(
3967            rcv_resp.conformity_level,
3968            ConformityLevel::BasicStreamAndIndividual
3969        );
3970        assert_eq!(rcv_resp.number_of_objects, 1);
3971
3972        // Ensure the correct raw bytes were stored for the parsed objects (Id: 0x00, Len: 0x03, Val: "Foo")
3973        assert_eq!(&rcv_resp.objects_data[..5], &[0x00, 0x03, 0x46, 0x6F, 0x6F]);
3974    }
3975
3976    /// Test case: `ClientServices` handles multiple concurrent `read_device_identification` requests.
3977    #[test]
3978    fn test_client_services_read_device_identification_multi_transaction() {
3979        let transport = MockTransport::default();
3980        let app = MockApp::default();
3981        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
3982        let mut client_services =
3983            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
3984
3985        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
3986        // Request 1
3987        let txn_id_1 = 21;
3988        client_services
3989            .read_device_identification(
3990                txn_id_1,
3991                unit_id,
3992                ReadDeviceIdCode::Basic,
3993                ObjectId::from(0x00),
3994            )
3995            .unwrap();
3996
3997        // Request 2
3998        let txn_id_2 = 22;
3999        client_services
4000            .read_device_identification(
4001                txn_id_2,
4002                unit_id,
4003                ReadDeviceIdCode::Regular,
4004                ObjectId::from(0x00),
4005            )
4006            .unwrap();
4007
4008        assert_eq!(client_services.transport.sent_frames.borrow().len(), 2);
4009
4010        // Response for Request 2 (Out of order arrival)
4011        // MEI(0E), Code(02), Conf(82), More(00), Next(00), Num(00)
4012        // PDU Len = 6. MBAP Len = 1 + 1 + 6 = 8.
4013        let response_adu_2 = [
4014            0x00, 0x16, 0x00, 0x00, 0x00, 0x08, 0x01, 0x2B, 0x0E, 0x02, 0x82, 0x00, 0x00, 0x00,
4015        ];
4016        client_services
4017            .transport
4018            .recv_frames
4019            .borrow_mut()
4020            .push_back(Vec::from_slice(&response_adu_2).unwrap())
4021            .unwrap();
4022
4023        client_services.poll();
4024
4025        {
4026            let received_responses = client_services
4027                .app
4028                .received_read_device_id_responses
4029                .borrow();
4030            assert_eq!(received_responses.len(), 1);
4031            assert_eq!(received_responses[0].0, txn_id_2);
4032            assert_eq!(
4033                received_responses[0].2.read_device_id_code,
4034                ReadDeviceIdCode::Regular
4035            );
4036        }
4037
4038        // Response for Request 1
4039        // MEI(0E), Code(01), Conf(81), More(00), Next(00), Num(00)
4040        let response_adu_1 = [
4041            0x00, 0x15, 0x00, 0x00, 0x00, 0x08, 0x01, 0x2B, 0x0E, 0x01, 0x81, 0x00, 0x00, 0x00,
4042        ];
4043        client_services
4044            .transport
4045            .recv_frames
4046            .borrow_mut()
4047            .push_back(Vec::from_slice(&response_adu_1).unwrap())
4048            .unwrap();
4049
4050        client_services.poll();
4051
4052        {
4053            let received_responses = client_services
4054                .app
4055                .received_read_device_id_responses
4056                .borrow();
4057            assert_eq!(received_responses.len(), 2);
4058            assert_eq!(received_responses[1].0, txn_id_1);
4059            assert_eq!(
4060                received_responses[1].2.read_device_id_code,
4061                ReadDeviceIdCode::Basic
4062            );
4063        }
4064    }
4065
4066    /// Test case: `ClientServices` rejects a response where the echoed Read Device ID Code does not match the request.
4067    #[test]
4068    fn test_client_services_read_device_identification_mismatch_code() {
4069        let transport = MockTransport::default();
4070        let app = MockApp::default();
4071        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4072        let mut client_services =
4073            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4074
4075        let txn_id = 30;
4076        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4077        // We request BASIC (0x01)
4078        client_services
4079            .read_device_identification(
4080                txn_id,
4081                unit_id,
4082                ReadDeviceIdCode::Basic,
4083                ObjectId::from(0x00),
4084            )
4085            .unwrap();
4086
4087        // Server responds with REGULAR (0x02) - This is a protocol violation or mismatch
4088        // MEI(0E), Code(02), Conf(81), More(00), Next(00), Num(00)
4089        let response_adu = [
4090            0x00, 0x1E, 0x00, 0x00, 0x00, 0x08, 0x01, 0x2B, 0x0E, 0x02, 0x81, 0x00, 0x00, 0x00,
4091        ];
4092
4093        client_services
4094            .transport
4095            .recv_frames
4096            .borrow_mut()
4097            .push_back(Vec::from_slice(&response_adu).unwrap())
4098            .unwrap();
4099
4100        client_services.poll();
4101
4102        // Verify success callback was NOT called
4103        assert!(
4104            client_services
4105                .app
4106                .received_read_device_id_responses
4107                .borrow()
4108                .is_empty()
4109        );
4110
4111        // Verify failure callback WAS called with UnexpectedResponse
4112        let failed = client_services.app().failed_requests.borrow();
4113        assert_eq!(failed.len(), 1);
4114        assert_eq!(failed[0].2, MbusError::InvalidDeviceIdentification);
4115    }
4116
4117    /// Test case: `read_exception_status` sends a valid ADU and processes response.
4118    #[test]
4119    fn test_client_services_read_exception_status_e2e_success() {
4120        let transport = MockTransport::default();
4121        let app = MockApp::default();
4122        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4123        let mut client_services =
4124            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4125
4126        let txn_id = 40;
4127        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4128
4129        let err = client_services.read_exception_status(txn_id, unit_id).err();
4130        // Error is expected since the service only available in serial transport.
4131        assert_eq!(err, Some(MbusError::InvalidTransport));
4132    }
4133
4134    /// Test case: `diagnostics` (Sub-function 00) Query Data sends valid ADU.
4135    #[test]
4136    fn test_client_services_diagnostics_query_data_success() {
4137        let transport = MockTransport::default();
4138        let app = MockApp::default();
4139        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4140        let mut client_services =
4141            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4142
4143        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4144        let data = [0x1234, 0x5678];
4145        let sub_function = DiagnosticSubFunction::ReturnQueryData;
4146        let err = client_services
4147            .diagnostics(50, unit_id, sub_function, &data)
4148            .err();
4149        assert_eq!(err, Some(MbusError::InvalidTransport));
4150    }
4151
4152    /// Test case: `get_comm_event_counter` sends valid ADU.
4153    #[test]
4154    fn test_client_services_get_comm_event_counter_success() {
4155        let transport = MockTransport::default();
4156        let app = MockApp::default();
4157        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4158        let mut client_services =
4159            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4160        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4161        let err = client_services.get_comm_event_counter(60, unit_id).err();
4162
4163        assert_eq!(err, Some(MbusError::InvalidTransport));
4164    }
4165
4166    /// Test case: `report_server_id` sends valid ADU.
4167    #[test]
4168    fn test_client_services_report_server_id_success() {
4169        let transport = MockTransport::default();
4170        let app = MockApp::default();
4171        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4172        let mut client_services =
4173            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4174
4175        let unit_id = UnitIdOrSlaveAddr::new(0x01).unwrap();
4176        let err = client_services.report_server_id(70, unit_id).err();
4177
4178        assert_eq!(err, Some(MbusError::InvalidTransport));
4179    }
4180
4181    // --- Broadcast Tests ---
4182
4183    /// Test case: Broadcast read multiple coils is not allowed
4184    #[test]
4185    fn test_broadcast_read_multiple_coils_not_allowed() {
4186        let transport = MockTransport::default();
4187        let app = MockApp::default();
4188        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4189        let mut client_services =
4190            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4191
4192        let txn_id = 0x0001;
4193        let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
4194        let address = 0x0000;
4195        let quantity = 8;
4196        let res = client_services.read_multiple_coils(txn_id, unit_id, address, quantity);
4197        assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
4198    }
4199
4200    /// Test case: Broadcast write single coil on TCP is not allowed
4201    #[test]
4202    fn test_broadcast_write_single_coil_tcp_not_allowed() {
4203        let transport = MockTransport::default();
4204        let app = MockApp::default();
4205        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4206        let mut client_services =
4207            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4208
4209        let txn_id = 0x0002;
4210        let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
4211        let res = client_services.write_single_coil(txn_id, unit_id, 0x0000, true);
4212        assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
4213    }
4214
4215    /// Test case: Broadcast write multiple coils on TCP is not allowed
4216    #[test]
4217    fn test_broadcast_write_multiple_coils_tcp_not_allowed() {
4218        let transport = MockTransport::default();
4219        let app = MockApp::default();
4220        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4221        let mut client_services =
4222            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4223
4224        let txn_id = 0x0003;
4225        let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
4226        let mut values = Coils::new(0x0000, 2).unwrap();
4227        values.set_value(0x0000, true).unwrap();
4228        values.set_value(0x0001, false).unwrap();
4229
4230        let res = client_services.write_multiple_coils(txn_id, unit_id, 0x0000, &values);
4231        assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
4232    }
4233
4234    /// Test case: Broadcast read discrete inputs is not allowed
4235    #[test]
4236    fn test_broadcast_read_discrete_inputs_not_allowed() {
4237        let transport = MockTransport::default();
4238        let app = MockApp::default();
4239        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4240        let mut client_services =
4241            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4242
4243        let txn_id = 0x0006;
4244        let unit_id = UnitIdOrSlaveAddr::new_broadcast_address();
4245        let res = client_services.read_discrete_inputs(txn_id, unit_id, 0x0000, 2);
4246        assert_eq!(res.unwrap_err(), MbusError::BroadcastNotAllowed);
4247    }
4248
4249    /// Test case: `poll` clears the internal receive buffer if it overflows with garbage bytes.
4250    /// This simulates a high-noise environment where fragments accumulate beyond `MAX_ADU_FRAME_LEN`.
4251    #[test]
4252    fn test_client_services_clears_buffer_on_overflow() {
4253        let transport = MockTransport::default();
4254        let app = MockApp::default();
4255        let config = ModbusConfig::Tcp(ModbusTcpConfig::new("127.0.0.1", 502).unwrap());
4256        let mut client_services =
4257            ClientServices::<MockTransport, MockApp, 10>::new(transport, app, config).unwrap();
4258
4259        // Fill the internal buffer close to its capacity (MAX_ADU_FRAME_LEN = 513) with unparsable garbage
4260        let initial_garbage = [0xFF; MAX_ADU_FRAME_LEN - 10];
4261        client_services
4262            .rxed_frame
4263            .extend_from_slice(&initial_garbage)
4264            .unwrap();
4265
4266        // Inject another chunk of bytes that will cause an overflow when appended
4267        let chunk = [0xAA; 20];
4268        client_services
4269            .transport
4270            .recv_frames
4271            .borrow_mut()
4272            .push_back(Vec::from_slice(&chunk).unwrap())
4273            .unwrap();
4274
4275        // Poll should attempt to extend the buffer, fail because 503 + 20 > 513, and clear the buffer to recover.
4276        client_services.poll();
4277
4278        assert!(
4279            client_services.rxed_frame.is_empty(),
4280            "Buffer should be cleared on overflow to prevent crashing and recover from stream noise."
4281        );
4282    }
4283}