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