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