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