Skip to main content

rustmod_core/pdu/
request.rs

1use crate::encoding::{Reader, Writer};
2use crate::pdu::FunctionCode;
3use crate::{DecodeError, EncodeError};
4
5const MAX_READ_BITS: u16 = 2000;
6const MAX_READ_REGISTERS: u16 = 125;
7const MAX_WRITE_COILS: u16 = 1968;
8const MAX_WRITE_REGISTERS: u16 = 123;
9const MAX_RW_WRITE_REGISTERS: u16 = 121;
10
11fn validate_quantity(quantity: u16, max: u16) -> Result<(), EncodeError> {
12    if quantity == 0 || quantity > max {
13        return Err(EncodeError::ValueOutOfRange);
14    }
15    Ok(())
16}
17
18fn validate_quantity_decode(quantity: u16, max: u16) -> Result<(), DecodeError> {
19    if quantity == 0 || quantity > max {
20        return Err(DecodeError::InvalidValue);
21    }
22    Ok(())
23}
24
25fn write_header(
26    w: &mut Writer<'_>,
27    function: FunctionCode,
28    start_address: u16,
29    quantity: u16,
30) -> Result<(), EncodeError> {
31    w.write_u8(function.as_u8())?;
32    w.write_be_u16(start_address)?;
33    w.write_be_u16(quantity)?;
34    Ok(())
35}
36
37fn pack_coils(values: &[bool], out: &mut [u8]) {
38    out.fill(0);
39    for (i, value) in values.iter().enumerate() {
40        if *value {
41            out[i / 8] |= 1u8 << (i % 8);
42        }
43    }
44}
45
46/// FC01 Read Coils request.
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub struct ReadCoilsRequest {
49    pub start_address: u16,
50    pub quantity: u16,
51}
52
53impl ReadCoilsRequest {
54    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
55        validate_quantity(self.quantity, MAX_READ_BITS)?;
56        write_header(
57            w,
58            FunctionCode::ReadCoils,
59            self.start_address,
60            self.quantity,
61        )
62    }
63}
64
65/// FC02 Read Discrete Inputs request.
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub struct ReadDiscreteInputsRequest {
68    pub start_address: u16,
69    pub quantity: u16,
70}
71
72impl ReadDiscreteInputsRequest {
73    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
74        validate_quantity(self.quantity, MAX_READ_BITS)?;
75        write_header(
76            w,
77            FunctionCode::ReadDiscreteInputs,
78            self.start_address,
79            self.quantity,
80        )
81    }
82}
83
84/// FC03 Read Holding Registers request.
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub struct ReadHoldingRegistersRequest {
87    pub start_address: u16,
88    pub quantity: u16,
89}
90
91impl ReadHoldingRegistersRequest {
92    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
93        validate_quantity(self.quantity, MAX_READ_REGISTERS)?;
94        write_header(
95            w,
96            FunctionCode::ReadHoldingRegisters,
97            self.start_address,
98            self.quantity,
99        )
100    }
101}
102
103/// FC04 Read Input Registers request.
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub struct ReadInputRegistersRequest {
106    pub start_address: u16,
107    pub quantity: u16,
108}
109
110impl ReadInputRegistersRequest {
111    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
112        validate_quantity(self.quantity, MAX_READ_REGISTERS)?;
113        write_header(
114            w,
115            FunctionCode::ReadInputRegisters,
116            self.start_address,
117            self.quantity,
118        )
119    }
120}
121
122/// FC05 Write Single Coil request.
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub struct WriteSingleCoilRequest {
125    pub address: u16,
126    pub value: bool,
127}
128
129impl WriteSingleCoilRequest {
130    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
131        w.write_u8(FunctionCode::WriteSingleCoil.as_u8())?;
132        w.write_be_u16(self.address)?;
133        w.write_be_u16(if self.value { 0xFF00 } else { 0x0000 })?;
134        Ok(())
135    }
136}
137
138/// FC06 Write Single Register request.
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub struct WriteSingleRegisterRequest {
141    pub address: u16,
142    pub value: u16,
143}
144
145impl WriteSingleRegisterRequest {
146    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
147        w.write_u8(FunctionCode::WriteSingleRegister.as_u8())?;
148        w.write_be_u16(self.address)?;
149        w.write_be_u16(self.value)?;
150        Ok(())
151    }
152}
153
154/// FC15 Write Multiple Coils request (encoding form with borrowed coil slice).
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156pub struct WriteMultipleCoilsRequest<'a> {
157    pub start_address: u16,
158    pub values: &'a [bool],
159}
160
161impl<'a> WriteMultipleCoilsRequest<'a> {
162    pub fn quantity(&self) -> Result<u16, EncodeError> {
163        let quantity: u16 = self
164            .values
165            .len()
166            .try_into()
167            .map_err(|_| EncodeError::ValueOutOfRange)?;
168        validate_quantity(quantity, MAX_WRITE_COILS)?;
169        Ok(quantity)
170    }
171
172    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
173        let quantity = self.quantity()?;
174        let byte_count_usize = self.values.len().div_ceil(8);
175        let byte_count: u8 = byte_count_usize
176            .try_into()
177            .map_err(|_| EncodeError::ValueOutOfRange)?;
178
179        w.write_u8(FunctionCode::WriteMultipleCoils.as_u8())?;
180        w.write_be_u16(self.start_address)?;
181        w.write_be_u16(quantity)?;
182        w.write_u8(byte_count)?;
183
184        let mut packed = [0u8; 246];
185        let used = usize::from(byte_count);
186        pack_coils(self.values, &mut packed[..used]);
187        w.write_all(&packed[..used])?;
188        Ok(())
189    }
190}
191
192/// FC16 Write Multiple Registers request (encoding form with borrowed register slice).
193#[derive(Debug, Clone, Copy, PartialEq, Eq)]
194pub struct WriteMultipleRegistersRequest<'a> {
195    pub start_address: u16,
196    pub values: &'a [u16],
197}
198
199impl<'a> WriteMultipleRegistersRequest<'a> {
200    pub fn quantity(&self) -> Result<u16, EncodeError> {
201        let quantity: u16 = self
202            .values
203            .len()
204            .try_into()
205            .map_err(|_| EncodeError::ValueOutOfRange)?;
206        validate_quantity(quantity, MAX_WRITE_REGISTERS)?;
207        Ok(quantity)
208    }
209
210    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
211        let quantity = self.quantity()?;
212        let byte_count_usize = self.values.len() * 2;
213        let byte_count: u8 = byte_count_usize
214            .try_into()
215            .map_err(|_| EncodeError::ValueOutOfRange)?;
216
217        w.write_u8(FunctionCode::WriteMultipleRegisters.as_u8())?;
218        w.write_be_u16(self.start_address)?;
219        w.write_be_u16(quantity)?;
220        w.write_u8(byte_count)?;
221        for value in self.values {
222            w.write_be_u16(*value)?;
223        }
224        Ok(())
225    }
226}
227
228/// FC22 Mask Write Register request.
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub struct MaskWriteRegisterRequest {
231    pub address: u16,
232    pub and_mask: u16,
233    pub or_mask: u16,
234}
235
236impl MaskWriteRegisterRequest {
237    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
238        w.write_u8(FunctionCode::MaskWriteRegister.as_u8())?;
239        w.write_be_u16(self.address)?;
240        w.write_be_u16(self.and_mask)?;
241        w.write_be_u16(self.or_mask)?;
242        Ok(())
243    }
244}
245
246/// FC23 Read/Write Multiple Registers request (encoding form).
247#[derive(Debug, Clone, Copy, PartialEq, Eq)]
248pub struct ReadWriteMultipleRegistersRequest<'a> {
249    pub read_start_address: u16,
250    pub read_quantity: u16,
251    pub write_start_address: u16,
252    pub values: &'a [u16],
253}
254
255impl<'a> ReadWriteMultipleRegistersRequest<'a> {
256    pub fn write_quantity(&self) -> Result<u16, EncodeError> {
257        let quantity: u16 = self
258            .values
259            .len()
260            .try_into()
261            .map_err(|_| EncodeError::ValueOutOfRange)?;
262        validate_quantity(quantity, MAX_RW_WRITE_REGISTERS)?;
263        Ok(quantity)
264    }
265
266    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
267        validate_quantity(self.read_quantity, MAX_READ_REGISTERS)?;
268        let write_quantity = self.write_quantity()?;
269        let byte_count = u8::try_from(usize::from(write_quantity) * 2)
270            .map_err(|_| EncodeError::ValueOutOfRange)?;
271
272        w.write_u8(FunctionCode::ReadWriteMultipleRegisters.as_u8())?;
273        w.write_be_u16(self.read_start_address)?;
274        w.write_be_u16(self.read_quantity)?;
275        w.write_be_u16(self.write_start_address)?;
276        w.write_be_u16(write_quantity)?;
277        w.write_u8(byte_count)?;
278        for value in self.values {
279            w.write_be_u16(*value)?;
280        }
281        Ok(())
282    }
283}
284
285/// FC07 Read Exception Status request (no payload).
286#[derive(Debug, Clone, Copy, PartialEq, Eq)]
287pub struct ReadExceptionStatusRequest;
288
289impl ReadExceptionStatusRequest {
290    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
291        w.write_u8(FunctionCode::ReadExceptionStatus.as_u8())?;
292        Ok(())
293    }
294}
295
296/// FC08 Diagnostics request.
297#[derive(Debug, Clone, Copy, PartialEq, Eq)]
298pub struct DiagnosticsRequest {
299    pub sub_function: u16,
300    pub data: u16,
301}
302
303impl DiagnosticsRequest {
304    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
305        w.write_u8(FunctionCode::Diagnostics.as_u8())?;
306        w.write_be_u16(self.sub_function)?;
307        w.write_be_u16(self.data)?;
308        Ok(())
309    }
310}
311
312/// FC24 Read FIFO Queue request.
313#[derive(Debug, Clone, Copy, PartialEq, Eq)]
314pub struct ReadFifoQueueRequest {
315    pub fifo_pointer_address: u16,
316}
317
318impl ReadFifoQueueRequest {
319    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
320        w.write_u8(FunctionCode::ReadFifoQueue.as_u8())?;
321        w.write_be_u16(self.fifo_pointer_address)?;
322        Ok(())
323    }
324}
325
326/// A request with a user-defined or unrecognised function code.
327#[derive(Debug, Clone, Copy, PartialEq, Eq)]
328pub struct CustomRequest<'a> {
329    pub function_code: u8,
330    pub data: &'a [u8],
331}
332
333impl<'a> CustomRequest<'a> {
334    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
335        if self.function_code == 0 || FunctionCode::is_exception(self.function_code) {
336            return Err(EncodeError::ValueOutOfRange);
337        }
338        w.write_u8(self.function_code)?;
339        w.write_all(self.data)?;
340        Ok(())
341    }
342}
343
344/// Borrowed decode representation for FC15 payloads.
345#[derive(Debug, Clone, Copy, PartialEq, Eq)]
346pub struct WriteMultipleCoilsRequestData<'a> {
347    pub start_address: u16,
348    pub quantity: u16,
349    pub values_packed: &'a [u8],
350}
351
352impl<'a> WriteMultipleCoilsRequestData<'a> {
353    pub fn coil(&self, index: usize) -> Option<bool> {
354        if index >= usize::from(self.quantity) {
355            return None;
356        }
357        let byte = self.values_packed.get(index / 8)?;
358        Some((byte & (1u8 << (index % 8))) != 0)
359    }
360}
361
362/// Borrowed decode representation for FC16 payloads.
363#[derive(Debug, Clone, Copy, PartialEq, Eq)]
364pub struct WriteMultipleRegistersRequestData<'a> {
365    pub start_address: u16,
366    pub values_bytes: &'a [u8],
367}
368
369impl<'a> WriteMultipleRegistersRequestData<'a> {
370    pub fn quantity(&self) -> usize {
371        self.values_bytes.len() / 2
372    }
373
374    pub fn register(&self, index: usize) -> Option<u16> {
375        let offset = index.checked_mul(2)?;
376        let bytes = self.values_bytes.get(offset..offset + 2)?;
377        Some(u16::from_be_bytes([bytes[0], bytes[1]]))
378    }
379}
380
381/// Borrowed decode representation for FC23 payloads.
382#[derive(Debug, Clone, Copy, PartialEq, Eq)]
383pub struct ReadWriteMultipleRegistersRequestData<'a> {
384    pub read_start_address: u16,
385    pub read_quantity: u16,
386    pub write_start_address: u16,
387    pub values_bytes: &'a [u8],
388}
389
390impl<'a> ReadWriteMultipleRegistersRequestData<'a> {
391    pub fn write_quantity(&self) -> usize {
392        self.values_bytes.len() / 2
393    }
394
395    pub fn register(&self, index: usize) -> Option<u16> {
396        let offset = index.checked_mul(2)?;
397        let bytes = self.values_bytes.get(offset..offset + 2)?;
398        Some(u16::from_be_bytes([bytes[0], bytes[1]]))
399    }
400}
401
402/// Borrowed decode representation for custom/unknown function codes.
403#[derive(Debug, Clone, Copy, PartialEq, Eq)]
404pub struct CustomRequestData<'a> {
405    pub function_code: u8,
406    pub data: &'a [u8],
407}
408
409#[cfg(feature = "alloc")]
410#[derive(Debug, Clone, PartialEq, Eq)]
411pub struct OwnedWriteMultipleCoilsRequest {
412    pub start_address: u16,
413    pub values: alloc::vec::Vec<bool>,
414}
415
416#[cfg(feature = "alloc")]
417impl OwnedWriteMultipleCoilsRequest {
418    pub fn as_borrowed(&self) -> WriteMultipleCoilsRequest<'_> {
419        WriteMultipleCoilsRequest {
420            start_address: self.start_address,
421            values: &self.values,
422        }
423    }
424}
425
426#[cfg(feature = "alloc")]
427#[derive(Debug, Clone, PartialEq, Eq)]
428pub struct OwnedWriteMultipleRegistersRequest {
429    pub start_address: u16,
430    pub values: alloc::vec::Vec<u16>,
431}
432
433#[cfg(feature = "alloc")]
434impl OwnedWriteMultipleRegistersRequest {
435    pub fn as_borrowed(&self) -> WriteMultipleRegistersRequest<'_> {
436        WriteMultipleRegistersRequest {
437            start_address: self.start_address,
438            values: &self.values,
439        }
440    }
441}
442
443/// Modbus request PDU, ready for encoding. Used by the client.
444#[derive(Debug, Clone, Copy, PartialEq, Eq)]
445#[non_exhaustive]
446pub enum Request<'a> {
447    ReadCoils(ReadCoilsRequest),
448    ReadDiscreteInputs(ReadDiscreteInputsRequest),
449    ReadHoldingRegisters(ReadHoldingRegistersRequest),
450    ReadInputRegisters(ReadInputRegistersRequest),
451    WriteSingleCoil(WriteSingleCoilRequest),
452    WriteSingleRegister(WriteSingleRegisterRequest),
453    WriteMultipleCoils(WriteMultipleCoilsRequest<'a>),
454    WriteMultipleRegisters(WriteMultipleRegistersRequest<'a>),
455    MaskWriteRegister(MaskWriteRegisterRequest),
456    ReadWriteMultipleRegisters(ReadWriteMultipleRegistersRequest<'a>),
457    ReadExceptionStatus(ReadExceptionStatusRequest),
458    Diagnostics(DiagnosticsRequest),
459    ReadFifoQueue(ReadFifoQueueRequest),
460    Custom(CustomRequest<'a>),
461}
462
463impl<'a> Request<'a> {
464    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
465        match self {
466            Self::ReadCoils(req) => req.encode(w),
467            Self::ReadDiscreteInputs(req) => req.encode(w),
468            Self::ReadHoldingRegisters(req) => req.encode(w),
469            Self::ReadInputRegisters(req) => req.encode(w),
470            Self::WriteSingleCoil(req) => req.encode(w),
471            Self::WriteSingleRegister(req) => req.encode(w),
472            Self::WriteMultipleCoils(req) => req.encode(w),
473            Self::WriteMultipleRegisters(req) => req.encode(w),
474            Self::MaskWriteRegister(req) => req.encode(w),
475            Self::ReadWriteMultipleRegisters(req) => req.encode(w),
476            Self::ReadExceptionStatus(req) => req.encode(w),
477            Self::Diagnostics(req) => req.encode(w),
478            Self::ReadFifoQueue(req) => req.encode(w),
479            Self::Custom(req) => req.encode(w),
480        }
481    }
482
483    pub fn function_code(&self) -> FunctionCode {
484        match self {
485            Self::ReadCoils(_) => FunctionCode::ReadCoils,
486            Self::ReadDiscreteInputs(_) => FunctionCode::ReadDiscreteInputs,
487            Self::ReadHoldingRegisters(_) => FunctionCode::ReadHoldingRegisters,
488            Self::ReadInputRegisters(_) => FunctionCode::ReadInputRegisters,
489            Self::WriteSingleCoil(_) => FunctionCode::WriteSingleCoil,
490            Self::WriteSingleRegister(_) => FunctionCode::WriteSingleRegister,
491            Self::WriteMultipleCoils(_) => FunctionCode::WriteMultipleCoils,
492            Self::WriteMultipleRegisters(_) => FunctionCode::WriteMultipleRegisters,
493            Self::MaskWriteRegister(_) => FunctionCode::MaskWriteRegister,
494            Self::ReadWriteMultipleRegisters(_) => FunctionCode::ReadWriteMultipleRegisters,
495            Self::ReadExceptionStatus(_) => FunctionCode::ReadExceptionStatus,
496            Self::Diagnostics(_) => FunctionCode::Diagnostics,
497            Self::ReadFifoQueue(_) => FunctionCode::ReadFifoQueue,
498            Self::Custom(req) => FunctionCode::Custom(req.function_code),
499        }
500    }
501}
502
503/// Decoded request model used by simulator/server implementations.
504#[derive(Debug, Clone, Copy, PartialEq, Eq)]
505#[non_exhaustive]
506pub enum DecodedRequest<'a> {
507    ReadCoils(ReadCoilsRequest),
508    ReadDiscreteInputs(ReadDiscreteInputsRequest),
509    ReadHoldingRegisters(ReadHoldingRegistersRequest),
510    ReadInputRegisters(ReadInputRegistersRequest),
511    WriteSingleCoil(WriteSingleCoilRequest),
512    WriteSingleRegister(WriteSingleRegisterRequest),
513    WriteMultipleCoils(WriteMultipleCoilsRequestData<'a>),
514    WriteMultipleRegisters(WriteMultipleRegistersRequestData<'a>),
515    MaskWriteRegister(MaskWriteRegisterRequest),
516    ReadWriteMultipleRegisters(ReadWriteMultipleRegistersRequestData<'a>),
517    ReadExceptionStatus(ReadExceptionStatusRequest),
518    Diagnostics(DiagnosticsRequest),
519    ReadFifoQueue(ReadFifoQueueRequest),
520    Custom(CustomRequestData<'a>),
521}
522
523impl<'a> DecodedRequest<'a> {
524    pub fn function_code(&self) -> FunctionCode {
525        match self {
526            Self::ReadCoils(_) => FunctionCode::ReadCoils,
527            Self::ReadDiscreteInputs(_) => FunctionCode::ReadDiscreteInputs,
528            Self::ReadHoldingRegisters(_) => FunctionCode::ReadHoldingRegisters,
529            Self::ReadInputRegisters(_) => FunctionCode::ReadInputRegisters,
530            Self::WriteSingleCoil(_) => FunctionCode::WriteSingleCoil,
531            Self::WriteSingleRegister(_) => FunctionCode::WriteSingleRegister,
532            Self::WriteMultipleCoils(_) => FunctionCode::WriteMultipleCoils,
533            Self::WriteMultipleRegisters(_) => FunctionCode::WriteMultipleRegisters,
534            Self::MaskWriteRegister(_) => FunctionCode::MaskWriteRegister,
535            Self::ReadWriteMultipleRegisters(_) => FunctionCode::ReadWriteMultipleRegisters,
536            Self::ReadExceptionStatus(_) => FunctionCode::ReadExceptionStatus,
537            Self::Diagnostics(_) => FunctionCode::Diagnostics,
538            Self::ReadFifoQueue(_) => FunctionCode::ReadFifoQueue,
539            Self::Custom(req) => FunctionCode::Custom(req.function_code),
540        }
541    }
542
543    pub fn decode(r: &mut Reader<'a>) -> Result<Self, DecodeError> {
544        let function = FunctionCode::from_u8(r.read_u8()?)?;
545        match function {
546            FunctionCode::ReadCoils => {
547                let start_address = r.read_be_u16()?;
548                let quantity = r.read_be_u16()?;
549                validate_quantity_decode(quantity, MAX_READ_BITS)?;
550                Ok(Self::ReadCoils(ReadCoilsRequest {
551                    start_address,
552                    quantity,
553                }))
554            }
555            FunctionCode::ReadDiscreteInputs => {
556                let start_address = r.read_be_u16()?;
557                let quantity = r.read_be_u16()?;
558                validate_quantity_decode(quantity, MAX_READ_BITS)?;
559                Ok(Self::ReadDiscreteInputs(ReadDiscreteInputsRequest {
560                    start_address,
561                    quantity,
562                }))
563            }
564            FunctionCode::ReadHoldingRegisters => {
565                let start_address = r.read_be_u16()?;
566                let quantity = r.read_be_u16()?;
567                validate_quantity_decode(quantity, MAX_READ_REGISTERS)?;
568                Ok(Self::ReadHoldingRegisters(ReadHoldingRegistersRequest {
569                    start_address,
570                    quantity,
571                }))
572            }
573            FunctionCode::ReadInputRegisters => {
574                let start_address = r.read_be_u16()?;
575                let quantity = r.read_be_u16()?;
576                validate_quantity_decode(quantity, MAX_READ_REGISTERS)?;
577                Ok(Self::ReadInputRegisters(ReadInputRegistersRequest {
578                    start_address,
579                    quantity,
580                }))
581            }
582            FunctionCode::WriteSingleCoil => {
583                let address = r.read_be_u16()?;
584                let raw = r.read_be_u16()?;
585                let value = match raw {
586                    0xFF00 => true,
587                    0x0000 => false,
588                    _ => return Err(DecodeError::InvalidValue),
589                };
590                Ok(Self::WriteSingleCoil(WriteSingleCoilRequest {
591                    address,
592                    value,
593                }))
594            }
595            FunctionCode::WriteSingleRegister => {
596                let address = r.read_be_u16()?;
597                let value = r.read_be_u16()?;
598                Ok(Self::WriteSingleRegister(WriteSingleRegisterRequest {
599                    address,
600                    value,
601                }))
602            }
603            FunctionCode::WriteMultipleCoils => {
604                let start_address = r.read_be_u16()?;
605                let quantity = r.read_be_u16()?;
606                validate_quantity_decode(quantity, MAX_WRITE_COILS)?;
607                let byte_count = usize::from(r.read_u8()?);
608                let expected = usize::from(quantity).div_ceil(8);
609                if byte_count != expected {
610                    return Err(DecodeError::InvalidLength);
611                }
612                let values_packed = r.read_exact(byte_count)?;
613                Ok(Self::WriteMultipleCoils(WriteMultipleCoilsRequestData {
614                    start_address,
615                    quantity,
616                    values_packed,
617                }))
618            }
619            FunctionCode::WriteMultipleRegisters => {
620                let start_address = r.read_be_u16()?;
621                let quantity = r.read_be_u16()?;
622                validate_quantity_decode(quantity, MAX_WRITE_REGISTERS)?;
623                let byte_count = usize::from(r.read_u8()?);
624                let expected = usize::from(quantity) * 2;
625                if byte_count != expected {
626                    return Err(DecodeError::InvalidLength);
627                }
628                let values_bytes = r.read_exact(byte_count)?;
629                Ok(Self::WriteMultipleRegisters(WriteMultipleRegistersRequestData {
630                    start_address,
631                    values_bytes,
632                }))
633            }
634            FunctionCode::MaskWriteRegister => {
635                let address = r.read_be_u16()?;
636                let and_mask = r.read_be_u16()?;
637                let or_mask = r.read_be_u16()?;
638                Ok(Self::MaskWriteRegister(MaskWriteRegisterRequest {
639                    address,
640                    and_mask,
641                    or_mask,
642                }))
643            }
644            FunctionCode::ReadWriteMultipleRegisters => {
645                let read_start_address = r.read_be_u16()?;
646                let read_quantity = r.read_be_u16()?;
647                validate_quantity_decode(read_quantity, MAX_READ_REGISTERS)?;
648                let write_start_address = r.read_be_u16()?;
649                let write_quantity = r.read_be_u16()?;
650                validate_quantity_decode(write_quantity, MAX_RW_WRITE_REGISTERS)?;
651                let byte_count = usize::from(r.read_u8()?);
652                let expected = usize::from(write_quantity) * 2;
653                if byte_count != expected {
654                    return Err(DecodeError::InvalidLength);
655                }
656                let values_bytes = r.read_exact(byte_count)?;
657                Ok(Self::ReadWriteMultipleRegisters(
658                    ReadWriteMultipleRegistersRequestData {
659                        read_start_address,
660                        read_quantity,
661                        write_start_address,
662                        values_bytes,
663                    },
664                ))
665            }
666            FunctionCode::ReadExceptionStatus => {
667                Ok(Self::ReadExceptionStatus(ReadExceptionStatusRequest))
668            }
669            FunctionCode::Diagnostics => {
670                let sub_function = r.read_be_u16()?;
671                let data = r.read_be_u16()?;
672                Ok(Self::Diagnostics(DiagnosticsRequest { sub_function, data }))
673            }
674            FunctionCode::ReadFifoQueue => {
675                let fifo_pointer_address = r.read_be_u16()?;
676                Ok(Self::ReadFifoQueue(ReadFifoQueueRequest {
677                    fifo_pointer_address,
678                }))
679            }
680            FunctionCode::Custom(function_code) => {
681                let data = r.read_exact(r.remaining())?;
682                Ok(Self::Custom(CustomRequestData {
683                    function_code,
684                    data,
685                }))
686            }
687        }
688    }
689}
690
691#[cfg(test)]
692mod tests {
693    use super::{
694        CustomRequest, DecodedRequest, MaskWriteRegisterRequest, ReadHoldingRegistersRequest,
695        ReadWriteMultipleRegistersRequest, Request, WriteMultipleCoilsRequest,
696        WriteMultipleRegistersRequest,
697    };
698    use crate::encoding::{Reader, Writer};
699    use crate::{DecodeError, EncodeError};
700
701    #[test]
702    fn read_holding_validates_quantity() {
703        let mut buf = [0u8; 8];
704        let mut w = Writer::new(&mut buf);
705        let req = ReadHoldingRegistersRequest {
706            start_address: 0,
707            quantity: 0,
708        };
709        assert_eq!(req.encode(&mut w).unwrap_err(), EncodeError::ValueOutOfRange);
710    }
711
712    #[test]
713    fn write_multiple_coils_packs_lsb_first() {
714        let req = WriteMultipleCoilsRequest {
715            start_address: 0x0013,
716            values: &[true, false, true, true, false, false, true, false, true],
717        };
718        let mut buf = [0u8; 32];
719        let mut w = Writer::new(&mut buf);
720        req.encode(&mut w).unwrap();
721        assert_eq!(
722            w.as_written(),
723            &[0x0F, 0x00, 0x13, 0x00, 0x09, 0x02, 0b0100_1101, 0b0000_0001]
724        );
725    }
726
727    #[test]
728    fn write_multiple_registers_rejects_too_many() {
729        let values = [0u16; 124];
730        let req = WriteMultipleRegistersRequest {
731            start_address: 0,
732            values: &values,
733        };
734        let mut buf = [0u8; 300];
735        let mut w = Writer::new(&mut buf);
736        assert_eq!(req.encode(&mut w).unwrap_err(), EncodeError::ValueOutOfRange);
737    }
738
739    #[test]
740    fn enum_dispatch_works() {
741        let req = Request::ReadHoldingRegisters(ReadHoldingRegistersRequest {
742            start_address: 0x006B,
743            quantity: 3,
744        });
745        let mut buf = [0u8; 8];
746        let mut w = Writer::new(&mut buf);
747        req.encode(&mut w).unwrap();
748        assert_eq!(w.as_written(), &[0x03, 0x00, 0x6B, 0x00, 0x03]);
749    }
750
751    #[test]
752    fn decode_fc03_request() {
753        let mut r = Reader::new(&[0x03, 0x00, 0x6B, 0x00, 0x03]);
754        let decoded = DecodedRequest::decode(&mut r).unwrap();
755        assert!(matches!(
756            decoded,
757            DecodedRequest::ReadHoldingRegisters(ReadHoldingRegistersRequest {
758                start_address: 0x006B,
759                quantity: 3
760            })
761        ));
762        assert!(r.is_empty());
763    }
764
765    #[test]
766    fn decode_fc15_request_and_bits() {
767        let mut r = Reader::new(&[0x0F, 0x00, 0x13, 0x00, 0x09, 0x02, 0b0100_1101, 0b0000_0001]);
768        let decoded = DecodedRequest::decode(&mut r).unwrap();
769        match decoded {
770            DecodedRequest::WriteMultipleCoils(req) => {
771                assert_eq!(req.start_address, 0x0013);
772                assert_eq!(req.quantity, 9);
773                assert_eq!(req.coil(0), Some(true));
774                assert_eq!(req.coil(1), Some(false));
775                assert_eq!(req.coil(8), Some(true));
776                assert_eq!(req.coil(9), None);
777            }
778            other => panic!("unexpected variant: {other:?}"),
779        }
780    }
781
782    #[test]
783    fn decode_rejects_invalid_fc16_byte_count() {
784        let mut r = Reader::new(&[0x10, 0x00, 0x00, 0x00, 0x02, 0x03, 0x12, 0x34, 0x56]);
785        assert_eq!(DecodedRequest::decode(&mut r).unwrap_err(), DecodeError::InvalidLength);
786    }
787
788    #[test]
789    fn decode_rejects_invalid_single_coil_value() {
790        let mut r = Reader::new(&[0x05, 0x00, 0x01, 0x12, 0x34]);
791        assert_eq!(DecodedRequest::decode(&mut r).unwrap_err(), DecodeError::InvalidValue);
792    }
793
794    #[test]
795    fn custom_request_roundtrip() {
796        let req = Request::Custom(CustomRequest {
797            function_code: 0x41,
798            data: &[0xAA, 0x55],
799        });
800        let mut buf = [0u8; 8];
801        let mut w = Writer::new(&mut buf);
802        req.encode(&mut w).unwrap();
803        assert_eq!(w.as_written(), &[0x41, 0xAA, 0x55]);
804
805        let mut r = Reader::new(w.as_written());
806        match DecodedRequest::decode(&mut r).unwrap() {
807            DecodedRequest::Custom(decoded) => {
808                assert_eq!(decoded.function_code, 0x41);
809                assert_eq!(decoded.data, &[0xAA, 0x55]);
810            }
811            other => panic!("unexpected variant: {other:?}"),
812        }
813    }
814
815    #[test]
816    fn mask_write_register_roundtrip() {
817        let req = Request::MaskWriteRegister(MaskWriteRegisterRequest {
818            address: 0x0004,
819            and_mask: 0xFF00,
820            or_mask: 0x0012,
821        });
822        let mut buf = [0u8; 16];
823        let mut w = Writer::new(&mut buf);
824        req.encode(&mut w).unwrap();
825        assert_eq!(w.as_written(), &[0x16, 0x00, 0x04, 0xFF, 0x00, 0x00, 0x12]);
826
827        let mut r = Reader::new(w.as_written());
828        match DecodedRequest::decode(&mut r).unwrap() {
829            DecodedRequest::MaskWriteRegister(decoded) => {
830                assert_eq!(decoded.address, 0x0004);
831                assert_eq!(decoded.and_mask, 0xFF00);
832                assert_eq!(decoded.or_mask, 0x0012);
833            }
834            other => panic!("unexpected variant: {other:?}"),
835        }
836    }
837
838    #[test]
839    fn read_write_multiple_registers_roundtrip() {
840        let req = Request::ReadWriteMultipleRegisters(ReadWriteMultipleRegistersRequest {
841            read_start_address: 0x0010,
842            read_quantity: 2,
843            write_start_address: 0x0020,
844            values: &[0x1111, 0x2222],
845        });
846
847        let mut buf = [0u8; 32];
848        let mut w = Writer::new(&mut buf);
849        req.encode(&mut w).unwrap();
850        assert_eq!(
851            w.as_written(),
852            &[0x17, 0x00, 0x10, 0x00, 0x02, 0x00, 0x20, 0x00, 0x02, 0x04, 0x11, 0x11, 0x22, 0x22]
853        );
854
855        let mut r = Reader::new(w.as_written());
856        match DecodedRequest::decode(&mut r).unwrap() {
857            DecodedRequest::ReadWriteMultipleRegisters(decoded) => {
858                assert_eq!(decoded.read_start_address, 0x0010);
859                assert_eq!(decoded.read_quantity, 2);
860                assert_eq!(decoded.write_start_address, 0x0020);
861                assert_eq!(decoded.write_quantity(), 2);
862                assert_eq!(decoded.register(0), Some(0x1111));
863                assert_eq!(decoded.register(1), Some(0x2222));
864            }
865            other => panic!("unexpected variant: {other:?}"),
866        }
867    }
868
869    #[test]
870    fn decode_rejects_invalid_fc23_byte_count() {
871        let mut r = Reader::new(&[0x17, 0x00, 0x10, 0x00, 0x01, 0x00, 0x20, 0x00, 0x01, 0x01, 0x12]);
872        assert_eq!(DecodedRequest::decode(&mut r).unwrap_err(), DecodeError::InvalidLength);
873    }
874}