Skip to main content

rusty_modbus_frame/
owned.rs

1//! Owned (`'static`, `Bytes`-backed) Modbus response types.
2//!
3//! These mirror the borrowed response types in `rusty_modbus_codec::response` but hold
4//! variable-length payloads as `bytes::Bytes` instead of `&'buf [u8]`, making them
5//! suitable for async contexts where the response must outlive the receive buffer.
6
7// from_pdu takes Bytes by value intentionally — callers transfer ownership of the
8// buffer into the owned struct, even though Bytes::slice() only needs &self.
9#![allow(clippy::needless_pass_by_value)]
10
11use bytes::Bytes;
12use rusty_modbus_codec::error::DecodeError;
13use rusty_modbus_codec::response::{
14    EncapsulatedInterfaceResponse, ExceptionResponse, GetCommEventCounterResponse,
15    GetCommEventLogResponse, MaskWriteRegisterResponse, ReadExceptionStatusResponse,
16    ReadFifoQueueResponse, ReadFileRecordResponse, WriteFileRecordResponse,
17    WriteMultipleCoilsResponse, WriteMultipleRegistersResponse, WriteSingleCoilResponse,
18    WriteSingleRegisterResponse,
19};
20use rusty_modbus_types::{DiagnosticSubFunction, FunctionCode, MeiType};
21
22fn pdu_data(pdu: &Bytes) -> Result<&[u8], DecodeError> {
23    if pdu.is_empty() {
24        return Err(DecodeError::Truncated {
25            expected: 1,
26            actual: 0,
27        });
28    }
29    Ok(&pdu[1..])
30}
31
32// ---------------------------------------------------------------------------
33// Owned bit-read responses
34// ---------------------------------------------------------------------------
35
36/// Owned variant of `ReadCoilsResponse` (FC 0x01).
37#[derive(Debug, Clone)]
38pub struct OwnedReadCoilsResponse {
39    /// Number of data bytes that follow.
40    pub byte_count: u8,
41    /// Bit-packed coil status, LSB-first within each byte.
42    pub coil_status: Bytes,
43}
44
45impl OwnedReadCoilsResponse {
46    /// Decode from a full PDU (function-code byte + data).
47    ///
48    /// # Errors
49    ///
50    /// Returns `DecodeError` if the PDU is malformed.
51    pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
52        let data = pdu_data(&pdu)?;
53        if data.is_empty() {
54            return Err(DecodeError::Truncated {
55                expected: 1,
56                actual: 0,
57            });
58        }
59        let byte_count = data[0];
60        let payload = &data[1..];
61        if payload.len() != usize::from(byte_count) {
62            return Err(DecodeError::ByteCountMismatch {
63                declared: usize::from(byte_count),
64                actual: payload.len(),
65            });
66        }
67        let coil_status = pdu.slice(2..2 + usize::from(byte_count));
68        Ok(Self {
69            byte_count,
70            coil_status,
71        })
72    }
73
74    /// Returns the state of the coil at the given zero-based index.
75    ///
76    /// # Panics
77    ///
78    /// Panics if `index` is out of range for the coil status data.
79    #[must_use]
80    pub fn coil(&self, index: usize) -> bool {
81        let byte_idx = index / 8;
82        let bit_idx = index % 8;
83        (self.coil_status[byte_idx] >> bit_idx) & 1 == 1
84    }
85}
86
87/// Owned variant of `ReadDiscreteInputsResponse` (FC 0x02).
88#[derive(Debug, Clone)]
89pub struct OwnedReadDiscreteInputsResponse {
90    /// Number of data bytes that follow.
91    pub byte_count: u8,
92    /// Bit-packed input status, LSB-first within each byte.
93    pub input_status: Bytes,
94}
95
96impl OwnedReadDiscreteInputsResponse {
97    /// Decode from a full PDU (function-code byte + data).
98    ///
99    /// # Errors
100    ///
101    /// Returns `DecodeError` if the PDU is malformed.
102    pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
103        let data = pdu_data(&pdu)?;
104        if data.is_empty() {
105            return Err(DecodeError::Truncated {
106                expected: 1,
107                actual: 0,
108            });
109        }
110        let byte_count = data[0];
111        let payload = &data[1..];
112        if payload.len() != usize::from(byte_count) {
113            return Err(DecodeError::ByteCountMismatch {
114                declared: usize::from(byte_count),
115                actual: payload.len(),
116            });
117        }
118        let input_status = pdu.slice(2..2 + usize::from(byte_count));
119        Ok(Self {
120            byte_count,
121            input_status,
122        })
123    }
124
125    /// Returns the state of the discrete input at the given zero-based index.
126    ///
127    /// # Panics
128    ///
129    /// Panics if `index` is out of range for the input status data.
130    #[must_use]
131    pub fn coil(&self, index: usize) -> bool {
132        let byte_idx = index / 8;
133        let bit_idx = index % 8;
134        (self.input_status[byte_idx] >> bit_idx) & 1 == 1
135    }
136}
137
138// ---------------------------------------------------------------------------
139// Owned register-read responses
140// ---------------------------------------------------------------------------
141
142/// Owned variant of `ReadHoldingRegistersResponse` (FC 0x03).
143#[derive(Debug, Clone)]
144pub struct OwnedReadHoldingRegistersResponse {
145    /// Number of data bytes that follow (should be 2 * register count).
146    pub byte_count: u8,
147    /// Raw register data in big-endian byte order.
148    pub register_data: Bytes,
149}
150
151impl OwnedReadHoldingRegistersResponse {
152    /// Decode from a full PDU (function-code byte + data).
153    ///
154    /// # Errors
155    ///
156    /// Returns `DecodeError` if the PDU is malformed.
157    pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
158        let data = pdu_data(&pdu)?;
159        if data.is_empty() {
160            return Err(DecodeError::Truncated {
161                expected: 1,
162                actual: 0,
163            });
164        }
165        let byte_count = data[0];
166        let payload = &data[1..];
167        if payload.len() != usize::from(byte_count) {
168            return Err(DecodeError::ByteCountMismatch {
169                declared: usize::from(byte_count),
170                actual: payload.len(),
171            });
172        }
173        let register_data = pdu.slice(2..2 + usize::from(byte_count));
174        Ok(Self {
175            byte_count,
176            register_data,
177        })
178    }
179
180    /// Returns the number of registers in this response.
181    #[must_use]
182    pub fn count(&self) -> usize {
183        self.register_data.len() / 2
184    }
185
186    /// Returns the register value at the given zero-based index.
187    ///
188    /// # Panics
189    ///
190    /// Panics if `index` is out of range.
191    #[must_use]
192    pub fn register(&self, index: usize) -> u16 {
193        let off = index * 2;
194        u16::from_be_bytes([self.register_data[off], self.register_data[off + 1]])
195    }
196
197    /// Returns an iterator over all register values.
198    pub fn registers(&self) -> impl Iterator<Item = u16> + '_ {
199        self.register_data
200            .chunks_exact(2)
201            .map(|c| u16::from_be_bytes([c[0], c[1]]))
202    }
203
204    /// Returns the raw register data bytes.
205    #[must_use]
206    pub fn raw(&self) -> &[u8] {
207        &self.register_data
208    }
209}
210
211/// Owned variant of `ReadInputRegistersResponse` (FC 0x04).
212#[derive(Debug, Clone)]
213pub struct OwnedReadInputRegistersResponse {
214    /// Number of data bytes that follow (should be 2 * register count).
215    pub byte_count: u8,
216    /// Raw register data in big-endian byte order.
217    pub register_data: Bytes,
218}
219
220impl OwnedReadInputRegistersResponse {
221    /// Decode from a full PDU (function-code byte + data).
222    ///
223    /// # Errors
224    ///
225    /// Returns `DecodeError` if the PDU is malformed.
226    pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
227        let data = pdu_data(&pdu)?;
228        if data.is_empty() {
229            return Err(DecodeError::Truncated {
230                expected: 1,
231                actual: 0,
232            });
233        }
234        let byte_count = data[0];
235        let payload = &data[1..];
236        if payload.len() != usize::from(byte_count) {
237            return Err(DecodeError::ByteCountMismatch {
238                declared: usize::from(byte_count),
239                actual: payload.len(),
240            });
241        }
242        let register_data = pdu.slice(2..2 + usize::from(byte_count));
243        Ok(Self {
244            byte_count,
245            register_data,
246        })
247    }
248
249    /// Returns the number of registers in this response.
250    #[must_use]
251    pub fn count(&self) -> usize {
252        self.register_data.len() / 2
253    }
254
255    /// Returns the register value at the given zero-based index.
256    ///
257    /// # Panics
258    ///
259    /// Panics if `index` is out of range.
260    #[must_use]
261    pub fn register(&self, index: usize) -> u16 {
262        let off = index * 2;
263        u16::from_be_bytes([self.register_data[off], self.register_data[off + 1]])
264    }
265
266    /// Returns an iterator over all register values.
267    pub fn registers(&self) -> impl Iterator<Item = u16> + '_ {
268        self.register_data
269            .chunks_exact(2)
270            .map(|c| u16::from_be_bytes([c[0], c[1]]))
271    }
272
273    /// Returns the raw register data bytes.
274    #[must_use]
275    pub fn raw(&self) -> &[u8] {
276        &self.register_data
277    }
278}
279
280// ---------------------------------------------------------------------------
281// Owned register read-write response
282// ---------------------------------------------------------------------------
283
284/// Owned variant of `ReadWriteMultipleRegistersResponse` (FC 0x17).
285#[derive(Debug, Clone)]
286pub struct OwnedReadWriteMultipleRegistersResponse {
287    /// Number of data bytes that follow (should be 2 * register count).
288    pub byte_count: u8,
289    /// Raw register data in big-endian byte order.
290    pub register_data: Bytes,
291}
292
293impl OwnedReadWriteMultipleRegistersResponse {
294    /// Decode from a full PDU (function-code byte + data).
295    ///
296    /// # Errors
297    ///
298    /// Returns `DecodeError` if the PDU is malformed.
299    pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
300        let data = pdu_data(&pdu)?;
301        if data.is_empty() {
302            return Err(DecodeError::Truncated {
303                expected: 1,
304                actual: 0,
305            });
306        }
307        let byte_count = data[0];
308        let payload = &data[1..];
309        if payload.len() != usize::from(byte_count) {
310            return Err(DecodeError::ByteCountMismatch {
311                declared: usize::from(byte_count),
312                actual: payload.len(),
313            });
314        }
315        let register_data = pdu.slice(2..2 + usize::from(byte_count));
316        Ok(Self {
317            byte_count,
318            register_data,
319        })
320    }
321
322    /// Returns the number of registers in this response.
323    #[must_use]
324    pub fn count(&self) -> usize {
325        self.register_data.len() / 2
326    }
327
328    /// Returns the register value at the given zero-based index.
329    ///
330    /// # Panics
331    ///
332    /// Panics if `index` is out of range.
333    #[must_use]
334    pub fn register(&self, index: usize) -> u16 {
335        let off = index * 2;
336        u16::from_be_bytes([self.register_data[off], self.register_data[off + 1]])
337    }
338
339    /// Returns an iterator over all register values.
340    pub fn registers(&self) -> impl Iterator<Item = u16> + '_ {
341        self.register_data
342            .chunks_exact(2)
343            .map(|c| u16::from_be_bytes([c[0], c[1]]))
344    }
345
346    /// Returns the raw register data bytes.
347    #[must_use]
348    pub fn raw(&self) -> &[u8] {
349        &self.register_data
350    }
351}
352
353// ---------------------------------------------------------------------------
354// Owned FIFO response
355// ---------------------------------------------------------------------------
356
357/// Owned variant of `ReadFifoQueueResponse` (FC 0x18).
358#[derive(Debug, Clone)]
359pub struct OwnedReadFifoQueueResponse {
360    /// Total number of bytes following this field.
361    pub byte_count: u16,
362    /// Number of FIFO register values (0..=31).
363    pub fifo_count: u16,
364    /// Raw FIFO register data in big-endian byte order.
365    pub fifo_values: Bytes,
366}
367
368impl OwnedReadFifoQueueResponse {
369    /// Decode from a full PDU (function-code byte + data).
370    ///
371    /// # Errors
372    ///
373    /// Returns `DecodeError` if the PDU is malformed.
374    pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
375        let data = pdu_data(&pdu)?;
376        let decoded = ReadFifoQueueResponse::decode(data)?;
377        let fifo_values = pdu.slice(5..5 + decoded.fifo_values.len());
378        Ok(Self {
379            byte_count: decoded.byte_count,
380            fifo_count: decoded.fifo_count,
381            fifo_values,
382        })
383    }
384}
385
386// ---------------------------------------------------------------------------
387// Owned file record responses
388// ---------------------------------------------------------------------------
389
390/// Owned variant of `ReadFileRecordResponse` (FC 0x14).
391#[derive(Debug, Clone)]
392pub struct OwnedReadFileRecordResponse {
393    /// Total number of data bytes that follow.
394    pub byte_count: u8,
395    /// Raw sub-request response data.
396    pub data: Bytes,
397}
398
399impl OwnedReadFileRecordResponse {
400    /// Decode from a full PDU (function-code byte + data).
401    ///
402    /// # Errors
403    ///
404    /// Returns `DecodeError` if the PDU is malformed.
405    pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
406        let data = pdu_data(&pdu)?;
407        let decoded = ReadFileRecordResponse::decode(data)?;
408        let owned_data = pdu.slice(2..2 + decoded.data.len());
409        Ok(Self {
410            byte_count: decoded.byte_count,
411            data: owned_data,
412        })
413    }
414}
415
416/// Owned variant of `WriteFileRecordResponse` (FC 0x15).
417#[derive(Debug, Clone)]
418pub struct OwnedWriteFileRecordResponse {
419    /// Total number of data bytes that follow.
420    pub byte_count: u8,
421    /// Raw sub-request response data.
422    pub data: Bytes,
423}
424
425impl OwnedWriteFileRecordResponse {
426    /// Decode from a full PDU (function-code byte + data).
427    ///
428    /// # Errors
429    ///
430    /// Returns `DecodeError` if the PDU is malformed.
431    pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
432        let data = pdu_data(&pdu)?;
433        let decoded = WriteFileRecordResponse::decode(data)?;
434        let owned_data = pdu.slice(2..2 + decoded.data.len());
435        Ok(Self {
436            byte_count: decoded.byte_count,
437            data: owned_data,
438        })
439    }
440}
441
442// ---------------------------------------------------------------------------
443// Owned diagnostic responses
444// ---------------------------------------------------------------------------
445
446/// Owned variant of `DiagnosticsResponse` (FC 0x08).
447#[derive(Debug, Clone)]
448pub struct OwnedDiagnosticsResponse {
449    /// The diagnostic sub-function code.
450    pub sub_function: DiagnosticSubFunction,
451    /// Diagnostic data bytes.
452    pub data: Bytes,
453}
454
455impl OwnedDiagnosticsResponse {
456    /// Decode from a full PDU (function-code byte + data).
457    ///
458    /// # Errors
459    ///
460    /// Returns `DecodeError` if the PDU is malformed.
461    pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
462        let data = pdu_data(&pdu)?;
463        if data.len() < 2 {
464            return Err(DecodeError::Truncated {
465                expected: 2,
466                actual: data.len(),
467            });
468        }
469        let raw_sub = u16::from_be_bytes([data[0], data[1]]);
470        let sub_function = DiagnosticSubFunction::from_raw(raw_sub)
471            .ok_or(DecodeError::UnknownDiagnosticSubFunction(raw_sub))?;
472        let payload = &data[2..];
473        if !payload.len().is_multiple_of(2) {
474            return Err(DecodeError::InvalidDiagnosticDataLength {
475                length: payload.len(),
476            });
477        }
478        let owned_data = pdu.slice(3..);
479        Ok(Self {
480            sub_function,
481            data: owned_data,
482        })
483    }
484}
485
486/// Owned variant of `GetCommEventLogResponse` (FC 0x0C).
487#[derive(Debug, Clone)]
488pub struct OwnedGetCommEventLogResponse {
489    /// Number of bytes that follow.
490    pub byte_count: u8,
491    /// Status word (0x0000 = ready, 0xFFFF = busy).
492    pub status: u16,
493    /// Event counter value.
494    pub event_count: u16,
495    /// Message counter value.
496    pub message_count: u16,
497    /// Event log bytes.
498    pub events: Bytes,
499}
500
501impl OwnedGetCommEventLogResponse {
502    /// Decode from a full PDU (function-code byte + data).
503    ///
504    /// # Errors
505    ///
506    /// Returns `DecodeError` if the PDU is malformed.
507    pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
508        let data = pdu_data(&pdu)?;
509        let decoded = GetCommEventLogResponse::decode(data)?;
510        let events = pdu.slice(8..8 + decoded.events.len());
511        Ok(Self {
512            byte_count: decoded.byte_count,
513            status: decoded.status,
514            event_count: decoded.event_count,
515            message_count: decoded.message_count,
516            events,
517        })
518    }
519}
520
521/// Owned variant of `ReportServerIdResponse` (FC 0x11).
522#[derive(Debug, Clone)]
523pub struct OwnedReportServerIdResponse {
524    /// Number of data bytes that follow.
525    pub byte_count: u8,
526    /// Device-specific identification data.
527    pub data: Bytes,
528}
529
530impl OwnedReportServerIdResponse {
531    /// Decode from a full PDU (function-code byte + data).
532    ///
533    /// # Errors
534    ///
535    /// Returns `DecodeError` if the PDU is malformed.
536    pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
537        let data = pdu_data(&pdu)?;
538        if data.is_empty() {
539            return Err(DecodeError::Truncated {
540                expected: 1,
541                actual: 0,
542            });
543        }
544        let byte_count = data[0];
545        let payload = &data[1..];
546        if payload.len() != usize::from(byte_count) {
547            return Err(DecodeError::ByteCountMismatch {
548                declared: usize::from(byte_count),
549                actual: payload.len(),
550            });
551        }
552        let owned_data = pdu.slice(2..2 + usize::from(byte_count));
553        Ok(Self {
554            byte_count,
555            data: owned_data,
556        })
557    }
558}
559
560// ---------------------------------------------------------------------------
561// Owned MEI response
562// ---------------------------------------------------------------------------
563
564/// Owned variant of `EncapsulatedInterfaceResponse` (FC 0x2B).
565#[derive(Debug, Clone)]
566pub struct OwnedEncapsulatedInterfaceResponse {
567    /// The MEI type code.
568    pub mei_type: MeiType,
569    /// MEI-specific data bytes.
570    pub data: Bytes,
571}
572
573impl OwnedEncapsulatedInterfaceResponse {
574    /// Decode from a full PDU (function-code byte + data).
575    ///
576    /// # Errors
577    ///
578    /// Returns `DecodeError` if the PDU is malformed.
579    pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
580        let data = pdu_data(&pdu)?;
581        let decoded = EncapsulatedInterfaceResponse::decode(data)?;
582        let owned_data = pdu.slice(2..);
583        Ok(Self {
584            mei_type: decoded.mei_type,
585            data: owned_data,
586        })
587    }
588}
589
590// ---------------------------------------------------------------------------
591// Collected device identification
592// ---------------------------------------------------------------------------
593
594/// Collected device identification from FC 0x2B / MEI 0x0E.
595///
596/// Contains the three mandatory basic objects. Fields are `None` if the
597/// device did not include them.
598#[derive(Debug, Clone, Default)]
599pub struct OwnedDeviceIdentification {
600    /// Vendor Name (object 0x00).
601    pub vendor_name: Option<String>,
602    /// Product Code (object 0x01).
603    pub product_code: Option<String>,
604    /// Major/Minor Revision (object 0x02).
605    pub major_minor_revision: Option<String>,
606}
607
608// ---------------------------------------------------------------------------
609// OwnedResponsePdu dispatch enum
610// ---------------------------------------------------------------------------
611
612/// Owned variant of a Modbus response PDU.
613///
614/// Variable-length responses use `Bytes`-backed owned types; fixed-size (Copy)
615/// responses are used directly from `rusty_modbus_codec::response`.
616#[derive(Debug)]
617pub enum OwnedResponsePdu {
618    /// FC 0x01 -- Read Coils.
619    ReadCoils(OwnedReadCoilsResponse),
620    /// FC 0x02 -- Read Discrete Inputs.
621    ReadDiscreteInputs(OwnedReadDiscreteInputsResponse),
622    /// FC 0x03 -- Read Holding Registers.
623    ReadHoldingRegisters(OwnedReadHoldingRegistersResponse),
624    /// FC 0x04 -- Read Input Registers.
625    ReadInputRegisters(OwnedReadInputRegistersResponse),
626    /// FC 0x05 -- Write Single Coil.
627    WriteSingleCoil(WriteSingleCoilResponse),
628    /// FC 0x06 -- Write Single Register.
629    WriteSingleRegister(WriteSingleRegisterResponse),
630    /// FC 0x07 -- Read Exception Status.
631    ReadExceptionStatus(ReadExceptionStatusResponse),
632    /// FC 0x08 -- Diagnostics.
633    Diagnostics(OwnedDiagnosticsResponse),
634    /// FC 0x0B -- Get Comm Event Counter.
635    GetCommEventCounter(GetCommEventCounterResponse),
636    /// FC 0x0C -- Get Comm Event Log.
637    GetCommEventLog(OwnedGetCommEventLogResponse),
638    /// FC 0x0F -- Write Multiple Coils.
639    WriteMultipleCoils(WriteMultipleCoilsResponse),
640    /// FC 0x10 -- Write Multiple Registers.
641    WriteMultipleRegisters(WriteMultipleRegistersResponse),
642    /// FC 0x11 -- Report Server ID.
643    ReportServerId(OwnedReportServerIdResponse),
644    /// FC 0x14 -- Read File Record.
645    ReadFileRecord(OwnedReadFileRecordResponse),
646    /// FC 0x15 -- Write File Record.
647    WriteFileRecord(OwnedWriteFileRecordResponse),
648    /// FC 0x16 -- Mask Write Register.
649    MaskWriteRegister(MaskWriteRegisterResponse),
650    /// FC 0x17 -- Read/Write Multiple Registers.
651    ReadWriteMultipleRegisters(OwnedReadWriteMultipleRegistersResponse),
652    /// FC 0x18 -- Read FIFO Queue.
653    ReadFifoQueue(OwnedReadFifoQueueResponse),
654    /// FC 0x2B -- Encapsulated Interface Transport.
655    EncapsulatedInterface(OwnedEncapsulatedInterfaceResponse),
656    /// Non-standard / vendor-specific response.
657    Custom(u8, Bytes),
658    /// Exception response (FC | 0x80).
659    Exception(ExceptionResponse),
660}
661
662impl OwnedResponsePdu {
663    /// Decode from a full PDU (`Bytes` starting with the function-code byte).
664    ///
665    /// Dispatches to the appropriate owned type based on the function code.
666    ///
667    /// # Errors
668    ///
669    /// Returns `DecodeError` if the function code is unknown or the payload is
670    /// malformed.
671    pub fn from_pdu(pdu: Bytes) -> Result<Self, DecodeError> {
672        if pdu.is_empty() {
673            return Err(DecodeError::Truncated {
674                expected: 1,
675                actual: 0,
676            });
677        }
678
679        let fc_byte = pdu[0];
680
681        // Check for exception response (high bit set).
682        if FunctionCode::is_exception_response(fc_byte) {
683            let resp = ExceptionResponse::decode(fc_byte, &pdu[1..])?;
684            return Ok(Self::Exception(resp));
685        }
686
687        // Exception-flagged bytes handled above. from_raw returns Some for all
688        // non-exception bytes (known → named, unknown → Custom).
689        let fc = FunctionCode::from_raw(fc_byte).unwrap_or(FunctionCode::Custom(fc_byte));
690
691        let data = &pdu[1..];
692
693        match fc {
694            FunctionCode::ReadCoils => OwnedReadCoilsResponse::from_pdu(pdu).map(Self::ReadCoils),
695            FunctionCode::ReadDiscreteInputs => {
696                OwnedReadDiscreteInputsResponse::from_pdu(pdu).map(Self::ReadDiscreteInputs)
697            }
698            FunctionCode::ReadHoldingRegisters => {
699                OwnedReadHoldingRegistersResponse::from_pdu(pdu).map(Self::ReadHoldingRegisters)
700            }
701            FunctionCode::ReadInputRegisters => {
702                OwnedReadInputRegistersResponse::from_pdu(pdu).map(Self::ReadInputRegisters)
703            }
704            FunctionCode::WriteSingleCoil => {
705                WriteSingleCoilResponse::decode(data).map(Self::WriteSingleCoil)
706            }
707            FunctionCode::WriteSingleRegister => {
708                WriteSingleRegisterResponse::decode(data).map(Self::WriteSingleRegister)
709            }
710            FunctionCode::ReadExceptionStatus => {
711                ReadExceptionStatusResponse::decode(data).map(Self::ReadExceptionStatus)
712            }
713            FunctionCode::Diagnostics => {
714                OwnedDiagnosticsResponse::from_pdu(pdu).map(Self::Diagnostics)
715            }
716            FunctionCode::GetCommEventCounter => {
717                GetCommEventCounterResponse::decode(data).map(Self::GetCommEventCounter)
718            }
719            FunctionCode::GetCommEventLog => {
720                OwnedGetCommEventLogResponse::from_pdu(pdu).map(Self::GetCommEventLog)
721            }
722            FunctionCode::WriteMultipleCoils => {
723                WriteMultipleCoilsResponse::decode(data).map(Self::WriteMultipleCoils)
724            }
725            FunctionCode::WriteMultipleRegisters => {
726                WriteMultipleRegistersResponse::decode(data).map(Self::WriteMultipleRegisters)
727            }
728            FunctionCode::ReportServerId => {
729                OwnedReportServerIdResponse::from_pdu(pdu).map(Self::ReportServerId)
730            }
731            FunctionCode::ReadFileRecord => {
732                OwnedReadFileRecordResponse::from_pdu(pdu).map(Self::ReadFileRecord)
733            }
734            FunctionCode::WriteFileRecord => {
735                OwnedWriteFileRecordResponse::from_pdu(pdu).map(Self::WriteFileRecord)
736            }
737            FunctionCode::MaskWriteRegister => {
738                MaskWriteRegisterResponse::decode(data).map(Self::MaskWriteRegister)
739            }
740            FunctionCode::ReadWriteMultipleRegisters => {
741                OwnedReadWriteMultipleRegistersResponse::from_pdu(pdu)
742                    .map(Self::ReadWriteMultipleRegisters)
743            }
744            FunctionCode::ReadFifoQueue => {
745                OwnedReadFifoQueueResponse::from_pdu(pdu).map(Self::ReadFifoQueue)
746            }
747            FunctionCode::EncapsulatedInterfaceTransport => {
748                OwnedEncapsulatedInterfaceResponse::from_pdu(pdu).map(Self::EncapsulatedInterface)
749            }
750            FunctionCode::Custom(fc) => Ok(Self::Custom(fc, pdu.slice(1..))),
751        }
752    }
753
754    /// The function-code byte carried by this response.
755    ///
756    /// For [`Self::Exception`] this is the exception-flagged value
757    /// (`original_fc | 0x80`); for [`Self::Custom`] it is the raw byte. Used by
758    /// the client to verify the server echoed the requested function code.
759    #[must_use]
760    pub fn function_code(&self) -> u8 {
761        match self {
762            Self::ReadCoils(_) => FunctionCode::ReadCoils.code(),
763            Self::ReadDiscreteInputs(_) => FunctionCode::ReadDiscreteInputs.code(),
764            Self::ReadHoldingRegisters(_) => FunctionCode::ReadHoldingRegisters.code(),
765            Self::ReadInputRegisters(_) => FunctionCode::ReadInputRegisters.code(),
766            Self::WriteSingleCoil(_) => FunctionCode::WriteSingleCoil.code(),
767            Self::WriteSingleRegister(_) => FunctionCode::WriteSingleRegister.code(),
768            Self::ReadExceptionStatus(_) => FunctionCode::ReadExceptionStatus.code(),
769            Self::Diagnostics(_) => FunctionCode::Diagnostics.code(),
770            Self::GetCommEventCounter(_) => FunctionCode::GetCommEventCounter.code(),
771            Self::GetCommEventLog(_) => FunctionCode::GetCommEventLog.code(),
772            Self::WriteMultipleCoils(_) => FunctionCode::WriteMultipleCoils.code(),
773            Self::WriteMultipleRegisters(_) => FunctionCode::WriteMultipleRegisters.code(),
774            Self::ReportServerId(_) => FunctionCode::ReportServerId.code(),
775            Self::ReadFileRecord(_) => FunctionCode::ReadFileRecord.code(),
776            Self::WriteFileRecord(_) => FunctionCode::WriteFileRecord.code(),
777            Self::MaskWriteRegister(_) => FunctionCode::MaskWriteRegister.code(),
778            Self::ReadWriteMultipleRegisters(_) => FunctionCode::ReadWriteMultipleRegisters.code(),
779            Self::ReadFifoQueue(_) => FunctionCode::ReadFifoQueue.code(),
780            Self::EncapsulatedInterface(_) => FunctionCode::EncapsulatedInterfaceTransport.code(),
781            Self::Custom(fc, _) => *fc,
782            Self::Exception(e) => e.function_code.exception_code(),
783        }
784    }
785}