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#[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#[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#[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}