Skip to main content

rustmod_core/pdu/
response.rs

1use crate::encoding::{Reader, Writer};
2use crate::pdu::{ExceptionResponse, FunctionCode};
3use crate::{DecodeError, EncodeError};
4
5const MAX_READ_COIL_BYTES: usize = 250;
6const MAX_READ_REGISTERS: u16 = 125;
7const MAX_WRITE_COILS: u16 = 1968;
8const MAX_WRITE_REGISTERS: u16 = 123;
9
10fn validate_echo_quantity(quantity: u16, max: u16) -> Result<(), DecodeError> {
11    if quantity == 0 || quantity > max {
12        return Err(DecodeError::InvalidValue);
13    }
14    Ok(())
15}
16
17/// FC01 Read Coils response containing packed coil status bytes.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct ReadCoilsResponse<'a> {
20    /// Packed bit array of coil states (LSB of first byte = first coil).
21    pub coil_status: &'a [u8],
22}
23
24impl<'a> ReadCoilsResponse<'a> {
25    fn decode_body(r: &mut Reader<'a>) -> Result<Self, DecodeError> {
26        let byte_count = usize::from(r.read_u8()?);
27        if byte_count == 0 || byte_count > MAX_READ_COIL_BYTES {
28            return Err(DecodeError::InvalidLength);
29        }
30        let data = r.read_exact(byte_count)?;
31        Ok(Self { coil_status: data })
32    }
33
34    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
35        let byte_count: u8 = self
36            .coil_status
37            .len()
38            .try_into()
39            .map_err(|_| EncodeError::ValueOutOfRange)?;
40        w.write_u8(FunctionCode::ReadCoils.as_u8())?;
41        w.write_u8(byte_count)?;
42        w.write_all(self.coil_status)?;
43        Ok(())
44    }
45
46    pub fn coil(&self, index: usize) -> Option<bool> {
47        let byte = self.coil_status.get(index / 8)?;
48        Some((byte & (1u8 << (index % 8))) != 0)
49    }
50}
51
52/// FC02 Read Discrete Inputs response containing packed input status bytes.
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub struct ReadDiscreteInputsResponse<'a> {
55    /// Packed bit array of discrete input states (LSB of first byte = first input).
56    pub input_status: &'a [u8],
57}
58
59impl<'a> ReadDiscreteInputsResponse<'a> {
60    fn decode_body(r: &mut Reader<'a>) -> Result<Self, DecodeError> {
61        let byte_count = usize::from(r.read_u8()?);
62        if byte_count == 0 || byte_count > MAX_READ_COIL_BYTES {
63            return Err(DecodeError::InvalidLength);
64        }
65        let data = r.read_exact(byte_count)?;
66        Ok(Self { input_status: data })
67    }
68
69    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
70        let byte_count: u8 = self
71            .input_status
72            .len()
73            .try_into()
74            .map_err(|_| EncodeError::ValueOutOfRange)?;
75        w.write_u8(FunctionCode::ReadDiscreteInputs.as_u8())?;
76        w.write_u8(byte_count)?;
77        w.write_all(self.input_status)?;
78        Ok(())
79    }
80
81    pub fn coil(&self, index: usize) -> Option<bool> {
82        let byte = self.input_status.get(index / 8)?;
83        Some((byte & (1u8 << (index % 8))) != 0)
84    }
85}
86
87/// FC03 Read Holding Registers response containing register values as raw big-endian bytes.
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub struct ReadHoldingRegistersResponse<'a> {
90    /// Raw register data as big-endian byte pairs (2 bytes per register).
91    pub data: &'a [u8],
92}
93
94impl<'a> ReadHoldingRegistersResponse<'a> {
95    fn decode_body(r: &mut Reader<'a>) -> Result<Self, DecodeError> {
96        let byte_count = usize::from(r.read_u8()?);
97        if byte_count == 0 || (byte_count % 2) != 0 {
98            return Err(DecodeError::InvalidLength);
99        }
100        if byte_count > usize::from(MAX_READ_REGISTERS) * 2 {
101            return Err(DecodeError::InvalidLength);
102        }
103        let data = r.read_exact(byte_count)?;
104        Ok(Self { data })
105    }
106
107    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
108        if (self.data.len() % 2) != 0 {
109            return Err(EncodeError::InvalidLength);
110        }
111        let byte_count: u8 = self
112            .data
113            .len()
114            .try_into()
115            .map_err(|_| EncodeError::ValueOutOfRange)?;
116        w.write_u8(FunctionCode::ReadHoldingRegisters.as_u8())?;
117        w.write_u8(byte_count)?;
118        w.write_all(self.data)?;
119        Ok(())
120    }
121
122    pub fn register_count(&self) -> usize {
123        self.data.len() / 2
124    }
125
126    pub fn register(&self, index: usize) -> Option<u16> {
127        let offset = index.checked_mul(2)?;
128        let bytes = self.data.get(offset..offset + 2)?;
129        Some(u16::from_be_bytes([bytes[0], bytes[1]]))
130    }
131}
132
133/// FC04 Read Input Registers response containing register values as raw big-endian bytes.
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135pub struct ReadInputRegistersResponse<'a> {
136    /// Raw register data as big-endian byte pairs (2 bytes per register).
137    pub data: &'a [u8],
138}
139
140impl<'a> ReadInputRegistersResponse<'a> {
141    fn decode_body(r: &mut Reader<'a>) -> Result<Self, DecodeError> {
142        let byte_count = usize::from(r.read_u8()?);
143        if byte_count == 0 || (byte_count % 2) != 0 {
144            return Err(DecodeError::InvalidLength);
145        }
146        if byte_count > usize::from(MAX_READ_REGISTERS) * 2 {
147            return Err(DecodeError::InvalidLength);
148        }
149        let data = r.read_exact(byte_count)?;
150        Ok(Self { data })
151    }
152
153    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
154        if (self.data.len() % 2) != 0 {
155            return Err(EncodeError::InvalidLength);
156        }
157        let byte_count: u8 = self
158            .data
159            .len()
160            .try_into()
161            .map_err(|_| EncodeError::ValueOutOfRange)?;
162        w.write_u8(FunctionCode::ReadInputRegisters.as_u8())?;
163        w.write_u8(byte_count)?;
164        w.write_all(self.data)?;
165        Ok(())
166    }
167
168    pub fn register_count(&self) -> usize {
169        self.data.len() / 2
170    }
171
172    pub fn register(&self, index: usize) -> Option<u16> {
173        let offset = index.checked_mul(2)?;
174        let bytes = self.data.get(offset..offset + 2)?;
175        Some(u16::from_be_bytes([bytes[0], bytes[1]]))
176    }
177}
178
179/// FC05 Write Single Coil response echoing the written address and value.
180#[derive(Debug, Clone, Copy, PartialEq, Eq)]
181pub struct WriteSingleCoilResponse {
182    /// Address of the coil that was written.
183    pub address: u16,
184    /// Value written to the coil (`true` = ON, `false` = OFF).
185    pub value: bool,
186}
187
188impl WriteSingleCoilResponse {
189    fn decode_body(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
190        let address = r.read_be_u16()?;
191        let raw = r.read_be_u16()?;
192        let value = match raw {
193            0xFF00 => true,
194            0x0000 => false,
195            _ => return Err(DecodeError::InvalidValue),
196        };
197        Ok(Self { address, value })
198    }
199
200    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
201        w.write_u8(FunctionCode::WriteSingleCoil.as_u8())?;
202        w.write_be_u16(self.address)?;
203        w.write_be_u16(if self.value { 0xFF00 } else { 0x0000 })?;
204        Ok(())
205    }
206}
207
208/// FC06 Write Single Register response echoing the written address and value.
209#[derive(Debug, Clone, Copy, PartialEq, Eq)]
210pub struct WriteSingleRegisterResponse {
211    /// Address of the register that was written.
212    pub address: u16,
213    /// Value written to the register.
214    pub value: u16,
215}
216
217impl WriteSingleRegisterResponse {
218    fn decode_body(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
219        Ok(Self {
220            address: r.read_be_u16()?,
221            value: r.read_be_u16()?,
222        })
223    }
224
225    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
226        w.write_u8(FunctionCode::WriteSingleRegister.as_u8())?;
227        w.write_be_u16(self.address)?;
228        w.write_be_u16(self.value)?;
229        Ok(())
230    }
231}
232
233/// FC15 Write Multiple Coils response echoing the starting address and quantity.
234#[derive(Debug, Clone, Copy, PartialEq, Eq)]
235pub struct WriteMultipleCoilsResponse {
236    /// Starting address of the coils that were written.
237    pub start_address: u16,
238    /// Number of coils written.
239    pub quantity: u16,
240}
241
242impl WriteMultipleCoilsResponse {
243    fn decode_body(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
244        let start_address = r.read_be_u16()?;
245        let quantity = r.read_be_u16()?;
246        validate_echo_quantity(quantity, MAX_WRITE_COILS)?;
247        Ok(Self {
248            start_address,
249            quantity,
250        })
251    }
252
253    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
254        if self.quantity == 0 || self.quantity > MAX_WRITE_COILS {
255            return Err(EncodeError::ValueOutOfRange);
256        }
257        w.write_u8(FunctionCode::WriteMultipleCoils.as_u8())?;
258        w.write_be_u16(self.start_address)?;
259        w.write_be_u16(self.quantity)?;
260        Ok(())
261    }
262}
263
264/// FC16 Write Multiple Registers response echoing the starting address and quantity.
265#[derive(Debug, Clone, Copy, PartialEq, Eq)]
266pub struct WriteMultipleRegistersResponse {
267    /// Starting address of the registers that were written.
268    pub start_address: u16,
269    /// Number of registers written.
270    pub quantity: u16,
271}
272
273impl WriteMultipleRegistersResponse {
274    fn decode_body(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
275        let start_address = r.read_be_u16()?;
276        let quantity = r.read_be_u16()?;
277        validate_echo_quantity(quantity, MAX_WRITE_REGISTERS)?;
278        Ok(Self {
279            start_address,
280            quantity,
281        })
282    }
283
284    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
285        if self.quantity == 0 || self.quantity > MAX_WRITE_REGISTERS {
286            return Err(EncodeError::ValueOutOfRange);
287        }
288        w.write_u8(FunctionCode::WriteMultipleRegisters.as_u8())?;
289        w.write_be_u16(self.start_address)?;
290        w.write_be_u16(self.quantity)?;
291        Ok(())
292    }
293}
294
295/// FC22 Mask Write Register response echoing the address and masks applied.
296///
297/// The server applies the formula: `result = (current AND and_mask) OR (or_mask AND NOT and_mask)`.
298#[derive(Debug, Clone, Copy, PartialEq, Eq)]
299pub struct MaskWriteRegisterResponse {
300    /// Address of the register that was modified.
301    pub address: u16,
302    /// AND mask that was applied.
303    pub and_mask: u16,
304    /// OR mask that was applied.
305    pub or_mask: u16,
306}
307
308impl MaskWriteRegisterResponse {
309    fn decode_body(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
310        Ok(Self {
311            address: r.read_be_u16()?,
312            and_mask: r.read_be_u16()?,
313            or_mask: r.read_be_u16()?,
314        })
315    }
316
317    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
318        w.write_u8(FunctionCode::MaskWriteRegister.as_u8())?;
319        w.write_be_u16(self.address)?;
320        w.write_be_u16(self.and_mask)?;
321        w.write_be_u16(self.or_mask)?;
322        Ok(())
323    }
324}
325
326/// FC23 Read/Write Multiple Registers response containing the read register values.
327#[derive(Debug, Clone, Copy, PartialEq, Eq)]
328pub struct ReadWriteMultipleRegistersResponse<'a> {
329    /// Raw register data as big-endian byte pairs (2 bytes per register).
330    pub data: &'a [u8],
331}
332
333impl<'a> ReadWriteMultipleRegistersResponse<'a> {
334    fn decode_body(r: &mut Reader<'a>) -> Result<Self, DecodeError> {
335        let byte_count = usize::from(r.read_u8()?);
336        if byte_count == 0 || (byte_count % 2) != 0 {
337            return Err(DecodeError::InvalidLength);
338        }
339        if byte_count > usize::from(MAX_READ_REGISTERS) * 2 {
340            return Err(DecodeError::InvalidLength);
341        }
342        let data = r.read_exact(byte_count)?;
343        Ok(Self { data })
344    }
345
346    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
347        if (self.data.len() % 2) != 0 {
348            return Err(EncodeError::InvalidLength);
349        }
350        let byte_count = u8::try_from(self.data.len()).map_err(|_| EncodeError::ValueOutOfRange)?;
351        w.write_u8(FunctionCode::ReadWriteMultipleRegisters.as_u8())?;
352        w.write_u8(byte_count)?;
353        w.write_all(self.data)?;
354        Ok(())
355    }
356
357    pub fn register_count(&self) -> usize {
358        self.data.len() / 2
359    }
360
361    pub fn register(&self, index: usize) -> Option<u16> {
362        let offset = index.checked_mul(2)?;
363        let bytes = self.data.get(offset..offset + 2)?;
364        Some(u16::from_be_bytes([bytes[0], bytes[1]]))
365    }
366}
367
368/// FC07 Read Exception Status response containing 8 exception coil states.
369#[derive(Debug, Clone, Copy, PartialEq, Eq)]
370pub struct ReadExceptionStatusResponse {
371    /// Eight exception coil values packed into a single byte (bit 0 = coil 0).
372    pub data: u8,
373}
374
375impl ReadExceptionStatusResponse {
376    fn decode_body(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
377        Ok(Self { data: r.read_u8()? })
378    }
379
380    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
381        w.write_u8(FunctionCode::ReadExceptionStatus.as_u8())?;
382        w.write_u8(self.data)?;
383        Ok(())
384    }
385}
386
387/// FC08 Diagnostics response echoing the sub-function code and data.
388#[derive(Debug, Clone, Copy, PartialEq, Eq)]
389pub struct DiagnosticsResponse {
390    /// Sub-function code (e.g., 0x0000 for Return Query Data).
391    pub sub_function: u16,
392    /// Diagnostic data word.
393    pub data: u16,
394}
395
396impl DiagnosticsResponse {
397    fn decode_body(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
398        Ok(Self {
399            sub_function: r.read_be_u16()?,
400            data: r.read_be_u16()?,
401        })
402    }
403
404    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
405        w.write_u8(FunctionCode::Diagnostics.as_u8())?;
406        w.write_be_u16(self.sub_function)?;
407        w.write_be_u16(self.data)?;
408        Ok(())
409    }
410}
411
412/// FC24 Read FIFO Queue response containing the queued register values.
413#[derive(Debug, Clone, Copy, PartialEq, Eq)]
414pub struct ReadFifoQueueResponse<'a> {
415    /// Raw FIFO register values as big-endian byte pairs (2 bytes per value).
416    pub fifo_values: &'a [u8],
417}
418
419impl<'a> ReadFifoQueueResponse<'a> {
420    fn decode_body(r: &mut Reader<'a>) -> Result<Self, DecodeError> {
421        let byte_count = usize::from(r.read_be_u16()?);
422        let fifo_count = usize::from(r.read_be_u16()?);
423        let expected = fifo_count * 2;
424        if byte_count != expected + 2 {
425            return Err(DecodeError::InvalidLength);
426        }
427        let fifo_values = r.read_exact(expected)?;
428        Ok(Self { fifo_values })
429    }
430
431    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
432        if (self.fifo_values.len() % 2) != 0 {
433            return Err(EncodeError::InvalidLength);
434        }
435        let fifo_count = self.fifo_values.len() / 2;
436        let byte_count = self.fifo_values.len() + 2;
437        w.write_u8(FunctionCode::ReadFifoQueue.as_u8())?;
438        w.write_be_u16(
439            u16::try_from(byte_count).map_err(|_| EncodeError::ValueOutOfRange)?,
440        )?;
441        w.write_be_u16(u16::try_from(fifo_count).map_err(|_| EncodeError::ValueOutOfRange)?)?;
442        w.write_all(self.fifo_values)?;
443        Ok(())
444    }
445
446    pub fn fifo_count(&self) -> usize {
447        self.fifo_values.len() / 2
448    }
449
450    pub fn value(&self, index: usize) -> Option<u16> {
451        let offset = index.checked_mul(2)?;
452        let bytes = self.fifo_values.get(offset..offset + 2)?;
453        Some(u16::from_be_bytes([bytes[0], bytes[1]]))
454    }
455}
456
457/// Response for a custom (non-standard) function code.
458#[derive(Debug, Clone, Copy, PartialEq, Eq)]
459pub struct CustomResponse<'a> {
460    /// The function code byte.
461    pub function_code: u8,
462    /// Raw response payload following the function code.
463    pub data: &'a [u8],
464}
465
466impl<'a> CustomResponse<'a> {
467    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
468        if self.function_code == 0 || FunctionCode::is_exception(self.function_code) {
469            return Err(EncodeError::ValueOutOfRange);
470        }
471        w.write_u8(self.function_code)?;
472        w.write_all(self.data)?;
473        Ok(())
474    }
475}
476
477/// A decoded Modbus response PDU.
478///
479/// Variant is determined by the function code byte. Use [`Response::decode`] to parse
480/// from a byte buffer and [`Response::encode`] to serialize back.
481#[derive(Debug, Clone, Copy, PartialEq, Eq)]
482#[non_exhaustive]
483pub enum Response<'a> {
484    ReadCoils(ReadCoilsResponse<'a>),
485    ReadDiscreteInputs(ReadDiscreteInputsResponse<'a>),
486    ReadHoldingRegisters(ReadHoldingRegistersResponse<'a>),
487    ReadInputRegisters(ReadInputRegistersResponse<'a>),
488    WriteSingleCoil(WriteSingleCoilResponse),
489    WriteSingleRegister(WriteSingleRegisterResponse),
490    WriteMultipleCoils(WriteMultipleCoilsResponse),
491    WriteMultipleRegisters(WriteMultipleRegistersResponse),
492    MaskWriteRegister(MaskWriteRegisterResponse),
493    ReadWriteMultipleRegisters(ReadWriteMultipleRegistersResponse<'a>),
494    ReadExceptionStatus(ReadExceptionStatusResponse),
495    Diagnostics(DiagnosticsResponse),
496    ReadFifoQueue(ReadFifoQueueResponse<'a>),
497    Custom(CustomResponse<'a>),
498    Exception(ExceptionResponse),
499}
500
501impl<'a> Response<'a> {
502    pub fn decode(r: &mut Reader<'a>) -> Result<Self, DecodeError> {
503        let function_byte = r.read_u8()?;
504        if FunctionCode::is_exception(function_byte) {
505            return Ok(Self::Exception(ExceptionResponse::decode(function_byte, r)?));
506        }
507
508        let fc = FunctionCode::from_u8(function_byte)?;
509        match fc {
510            FunctionCode::ReadCoils => Ok(Self::ReadCoils(ReadCoilsResponse::decode_body(r)?)),
511            FunctionCode::ReadDiscreteInputs => {
512                Ok(Self::ReadDiscreteInputs(ReadDiscreteInputsResponse::decode_body(r)?))
513            }
514            FunctionCode::ReadHoldingRegisters => Ok(Self::ReadHoldingRegisters(
515                ReadHoldingRegistersResponse::decode_body(r)?,
516            )),
517            FunctionCode::ReadInputRegisters => Ok(Self::ReadInputRegisters(
518                ReadInputRegistersResponse::decode_body(r)?,
519            )),
520            FunctionCode::WriteSingleCoil => {
521                Ok(Self::WriteSingleCoil(WriteSingleCoilResponse::decode_body(r)?))
522            }
523            FunctionCode::WriteSingleRegister => Ok(Self::WriteSingleRegister(
524                WriteSingleRegisterResponse::decode_body(r)?,
525            )),
526            FunctionCode::WriteMultipleCoils => Ok(Self::WriteMultipleCoils(
527                WriteMultipleCoilsResponse::decode_body(r)?,
528            )),
529            FunctionCode::WriteMultipleRegisters => Ok(Self::WriteMultipleRegisters(
530                WriteMultipleRegistersResponse::decode_body(r)?,
531            )),
532            FunctionCode::MaskWriteRegister => Ok(Self::MaskWriteRegister(
533                MaskWriteRegisterResponse::decode_body(r)?,
534            )),
535            FunctionCode::ReadWriteMultipleRegisters => Ok(Self::ReadWriteMultipleRegisters(
536                ReadWriteMultipleRegistersResponse::decode_body(r)?,
537            )),
538            FunctionCode::ReadExceptionStatus => Ok(Self::ReadExceptionStatus(
539                ReadExceptionStatusResponse::decode_body(r)?,
540            )),
541            FunctionCode::Diagnostics => {
542                Ok(Self::Diagnostics(DiagnosticsResponse::decode_body(r)?))
543            }
544            FunctionCode::ReadFifoQueue => {
545                Ok(Self::ReadFifoQueue(ReadFifoQueueResponse::decode_body(r)?))
546            }
547            FunctionCode::Custom(function_code) => {
548                let data = r.read_exact(r.remaining())?;
549                Ok(Self::Custom(CustomResponse {
550                    function_code,
551                    data,
552                }))
553            }
554        }
555    }
556
557    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
558        match self {
559            Self::ReadCoils(resp) => resp.encode(w),
560            Self::ReadDiscreteInputs(resp) => resp.encode(w),
561            Self::ReadHoldingRegisters(resp) => resp.encode(w),
562            Self::ReadInputRegisters(resp) => resp.encode(w),
563            Self::WriteSingleCoil(resp) => resp.encode(w),
564            Self::WriteSingleRegister(resp) => resp.encode(w),
565            Self::WriteMultipleCoils(resp) => resp.encode(w),
566            Self::WriteMultipleRegisters(resp) => resp.encode(w),
567            Self::MaskWriteRegister(resp) => resp.encode(w),
568            Self::ReadWriteMultipleRegisters(resp) => resp.encode(w),
569            Self::ReadExceptionStatus(resp) => resp.encode(w),
570            Self::Diagnostics(resp) => resp.encode(w),
571            Self::ReadFifoQueue(resp) => resp.encode(w),
572            Self::Custom(resp) => resp.encode(w),
573            Self::Exception(resp) => resp.encode(w),
574        }
575    }
576}
577
578#[cfg(test)]
579mod tests {
580    use super::{
581        CustomResponse, MaskWriteRegisterResponse, ReadHoldingRegistersResponse,
582        ReadWriteMultipleRegistersResponse, Response, WriteSingleCoilResponse,
583    };
584    use crate::encoding::{Reader, Writer};
585    use crate::pdu::ExceptionCode;
586    use crate::DecodeError;
587
588    #[test]
589    fn register_helpers_work() {
590        let resp = ReadHoldingRegistersResponse {
591            data: &[0x12, 0x34, 0xAB, 0xCD],
592        };
593        assert_eq!(resp.register_count(), 2);
594        assert_eq!(resp.register(0), Some(0x1234));
595        assert_eq!(resp.register(1), Some(0xABCD));
596        assert_eq!(resp.register(2), None);
597    }
598
599    #[test]
600    fn response_decode_exception_unknown_code() {
601        let mut r = Reader::new(&[0x83, 0x19]);
602        match Response::decode(&mut r).unwrap() {
603            Response::Exception(ex) => {
604                assert_eq!(ex.function_code, 0x03);
605                assert_eq!(ex.exception_code, ExceptionCode::Unknown(0x19));
606            }
607            _ => panic!("expected exception"),
608        }
609    }
610
611    #[test]
612    fn write_single_coil_rejects_invalid_payload() {
613        let mut r = Reader::new(&[0x05, 0x00, 0x01, 0x12, 0x34]);
614        assert_eq!(Response::decode(&mut r).unwrap_err(), DecodeError::InvalidValue);
615    }
616
617    #[test]
618    fn enum_encode_roundtrip() {
619        let original = Response::WriteSingleCoil(WriteSingleCoilResponse {
620            address: 0x0007,
621            value: true,
622        });
623        let mut buf = [0u8; 8];
624        let mut w = Writer::new(&mut buf);
625        original.encode(&mut w).unwrap();
626
627        let mut r = Reader::new(w.as_written());
628        let decoded = Response::decode(&mut r).unwrap();
629        assert_eq!(decoded, original);
630    }
631
632    #[test]
633    fn custom_response_roundtrip() {
634        let original = Response::Custom(CustomResponse {
635            function_code: 0x41,
636            data: &[0xAA, 0x55],
637        });
638        let mut buf = [0u8; 8];
639        let mut w = Writer::new(&mut buf);
640        original.encode(&mut w).unwrap();
641        assert_eq!(w.as_written(), &[0x41, 0xAA, 0x55]);
642
643        let mut r = Reader::new(w.as_written());
644        let decoded = Response::decode(&mut r).unwrap();
645        assert_eq!(decoded, original);
646    }
647
648    #[test]
649    fn mask_write_response_roundtrip() {
650        let original = Response::MaskWriteRegister(MaskWriteRegisterResponse {
651            address: 0x0007,
652            and_mask: 0xFF00,
653            or_mask: 0x00A5,
654        });
655        let mut buf = [0u8; 16];
656        let mut w = Writer::new(&mut buf);
657        original.encode(&mut w).unwrap();
658        assert_eq!(w.as_written(), &[0x16, 0x00, 0x07, 0xFF, 0x00, 0x00, 0xA5]);
659
660        let mut r = Reader::new(w.as_written());
661        let decoded = Response::decode(&mut r).unwrap();
662        assert_eq!(decoded, original);
663    }
664
665    #[test]
666    fn read_write_multiple_registers_response_roundtrip() {
667        let original = Response::ReadWriteMultipleRegisters(ReadWriteMultipleRegistersResponse {
668            data: &[0x12, 0x34, 0xAB, 0xCD],
669        });
670        let mut buf = [0u8; 16];
671        let mut w = Writer::new(&mut buf);
672        original.encode(&mut w).unwrap();
673        assert_eq!(w.as_written(), &[0x17, 0x04, 0x12, 0x34, 0xAB, 0xCD]);
674
675        let mut r = Reader::new(w.as_written());
676        match Response::decode(&mut r).unwrap() {
677            Response::ReadWriteMultipleRegisters(resp) => {
678                assert_eq!(resp.register_count(), 2);
679                assert_eq!(resp.register(0), Some(0x1234));
680                assert_eq!(resp.register(1), Some(0xABCD));
681            }
682            other => panic!("unexpected response: {other:?}"),
683        }
684    }
685}