1use std::borrow::Cow;
6use std::convert::TryFrom;
7use std::io::{Error, ErrorKind};
8
9use bytes::{Buf, BufMut, Bytes, BytesMut};
10use thiserror::Error;
11
12pub const MAX_PDU_SIZE: usize = 253;
15
16pub const MAX_COILS: u16 = 2000;
19
20pub const MAX_REGISTERS: u16 = 125;
24
25#[inline]
28fn safe_u8(val: usize, context: &str) -> Result<u8, Error> {
29 u8::try_from(val).map_err(|_| {
30 Error::new(
31 ErrorKind::InvalidData,
32 format!("{context}: value {val} exceeds u8 range"),
33 )
34 })
35}
36
37fn validate_response_size(rsp: &Response) -> Result<(), Error> {
40 let byte_count = match rsp {
41 Response::ReadCoils(bits) | Response::ReadDiscreteInputs(bits) => bits.len().div_ceil(8),
42 Response::ReadHoldingRegisters(regs)
43 | Response::ReadInputRegisters(regs)
44 | Response::ReadWriteMultipleRegisters(regs) => regs
45 .len()
46 .checked_mul(2)
47 .ok_or_else(|| Error::new(ErrorKind::InvalidData, "response too large"))?,
48 _ => return Ok(()), };
50 safe_u8(byte_count, "response byte count")?;
51 Ok(())
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub struct FunctionCode(u8);
59
60impl FunctionCode {
61 #[inline]
62 pub const fn new(value: u8) -> Self {
63 Self(value)
64 }
65 #[inline]
66 pub const fn value(self) -> u8 {
67 self.0
68 }
69}
70
71#[inline]
78pub(crate) fn is_known_function_code(b: u8) -> bool {
79 let base = b & 0x7F;
81 matches!(base, 1..=6 | 8 | 15 | 16 | 22 | 23)
82}
83
84pub type Address = u16;
88pub type Quantity = u16;
90
91#[derive(Debug, Clone, PartialEq, Eq)]
98#[non_exhaustive]
99pub enum Request<'a> {
100 ReadCoils(Address, Quantity),
101 ReadDiscreteInputs(Address, Quantity),
102 ReadHoldingRegisters(Address, Quantity),
103 ReadInputRegisters(Address, Quantity),
104 WriteSingleCoil(Address, bool),
105 WriteSingleRegister(Address, u16),
106 WriteMultipleCoils(Address, Cow<'a, [bool]>),
107 WriteMultipleRegisters(Address, Cow<'a, [u16]>),
108 ReadWriteMultipleRegisters(Address, Quantity, Address, Cow<'a, [u16]>),
109 MaskWriteRegister(Address, u16, u16),
110 Diagnostic(u16, u16),
114 Disconnect,
115}
116
117impl Request<'_> {
118 pub const fn function_code(&self) -> FunctionCode {
120 use Request::*;
121 match self {
122 ReadCoils(..) => FunctionCode::new(1),
123 ReadDiscreteInputs(..) => FunctionCode::new(2),
124 ReadHoldingRegisters(..) => FunctionCode::new(3),
125 ReadInputRegisters(..) => FunctionCode::new(4),
126 WriteSingleCoil(..) => FunctionCode::new(5),
127 WriteSingleRegister(..) => FunctionCode::new(6),
128 ReadWriteMultipleRegisters(..) => FunctionCode::new(23),
129 WriteMultipleCoils(..) => FunctionCode::new(15),
130 WriteMultipleRegisters(..) => FunctionCode::new(16),
131 MaskWriteRegister(..) => FunctionCode::new(22),
132 Diagnostic(..) => FunctionCode::new(8),
133 Disconnect => FunctionCode::new(0),
134 }
135 }
136
137 pub fn into_owned(self) -> Request<'static> {
140 match self {
141 Request::ReadCoils(a, q) => Request::ReadCoils(a, q),
142 Request::ReadDiscreteInputs(a, q) => Request::ReadDiscreteInputs(a, q),
143 Request::ReadHoldingRegisters(a, q) => Request::ReadHoldingRegisters(a, q),
144 Request::ReadInputRegisters(a, q) => Request::ReadInputRegisters(a, q),
145 Request::WriteSingleCoil(a, v) => Request::WriteSingleCoil(a, v),
146 Request::WriteSingleRegister(a, v) => Request::WriteSingleRegister(a, v),
147 Request::WriteMultipleCoils(a, v) => {
148 Request::WriteMultipleCoils(a, Cow::Owned(v.into_owned()))
149 }
150 Request::WriteMultipleRegisters(a, v) => {
151 Request::WriteMultipleRegisters(a, Cow::Owned(v.into_owned()))
152 }
153 Request::ReadWriteMultipleRegisters(a, q, w, d) => {
154 Request::ReadWriteMultipleRegisters(a, q, w, Cow::Owned(d.into_owned()))
155 }
156 Request::MaskWriteRegister(a, b, c) => Request::MaskWriteRegister(a, b, c),
157 Request::Diagnostic(sf, d) => Request::Diagnostic(sf, d),
158 Request::Disconnect => Request::Disconnect,
159 }
160 }
161}
162
163#[derive(Debug, Clone, PartialEq)]
170#[non_exhaustive]
171pub enum Response {
172 ReadCoils(Vec<bool>),
173 ReadDiscreteInputs(Vec<bool>),
174 ReadHoldingRegisters(Vec<u16>),
175 ReadInputRegisters(Vec<u16>),
176 WriteSingleCoil(Address, bool),
177 WriteSingleRegister(Address, u16),
178 WriteMultipleCoils(Address, u16),
179 WriteMultipleRegisters(Address, u16),
180 ReadWriteMultipleRegisters(Vec<u16>),
181 MaskWriteRegister(Address, u16, u16),
182 Diagnostic(u16, u16),
183 Exception(u8, Exception),
184}
185
186impl Response {
187 pub const fn function_code(&self) -> FunctionCode {
190 use Response::*;
191 match self {
192 ReadCoils(..) => FunctionCode::new(1),
193 ReadDiscreteInputs(..) => FunctionCode::new(2),
194 ReadHoldingRegisters(..) => FunctionCode::new(3),
195 ReadInputRegisters(..) => FunctionCode::new(4),
196 WriteSingleCoil(..) => FunctionCode::new(5),
197 WriteSingleRegister(..) => FunctionCode::new(6),
198 WriteMultipleCoils(..) => FunctionCode::new(15),
199 WriteMultipleRegisters(..) => FunctionCode::new(16),
200 ReadWriteMultipleRegisters(..) => FunctionCode::new(23),
201 MaskWriteRegister(..) => FunctionCode::new(22),
202 Diagnostic(..) => FunctionCode::new(8),
203 Exception(fc, _) => FunctionCode::new(*fc | 0x80),
204 }
205 }
206}
207
208impl From<Response> for crate::error::ModbusError {
211 fn from(rsp: Response) -> Self {
218 match rsp {
219 Response::Exception(fc, ex) => crate::error::ModbusError::exception(fc, u8::from(ex)),
220 other => crate::error::ModbusError::protocol(format!(
221 "unexpected success response in error context: {other:?}"
222 )),
223 }
224 }
225}
226
227#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
233#[repr(u8)]
234#[non_exhaustive]
235pub enum Exception {
236 #[error("Illegal function")]
237 IllegalFunction = 1,
238 #[error("Illegal data address")]
239 IllegalDataAddress = 2,
240 #[error("Illegal data value")]
241 IllegalDataValue = 3,
242 #[error("Server device failure")]
243 ServerDeviceFailure = 4,
244 #[error("Acknowledge")]
245 Acknowledge = 5,
246 #[error("Server device busy")]
247 ServerDeviceBusy = 6,
248 #[error("Negative acknowledge")]
249 NegativeAcknowledge = 7,
250 #[error("Memory parity error")]
251 MemoryParityError = 8,
252 #[error("Gateway path unavailable")]
253 GatewayPathUnavailable = 10,
254 #[error("Gateway target device failed to respond")]
255 GatewayTargetDeviceFailedToRespond = 11,
256 #[error("Custom({0})")]
257 Custom(u8),
258}
259
260impl From<u8> for Exception {
261 fn from(code: u8) -> Self {
262 match code {
263 1 => Exception::IllegalFunction,
264 2 => Exception::IllegalDataAddress,
265 3 => Exception::IllegalDataValue,
266 4 => Exception::ServerDeviceFailure,
267 5 => Exception::Acknowledge,
268 6 => Exception::ServerDeviceBusy,
269 7 => Exception::NegativeAcknowledge,
270 8 => Exception::MemoryParityError,
271 10 => Exception::GatewayPathUnavailable,
272 11 => Exception::GatewayTargetDeviceFailedToRespond,
273 n => Exception::Custom(n),
274 }
275 }
276}
277
278impl From<Exception> for u8 {
279 fn from(e: Exception) -> u8 {
280 match e {
281 Exception::IllegalFunction => 1,
282 Exception::IllegalDataAddress => 2,
283 Exception::IllegalDataValue => 3,
284 Exception::ServerDeviceFailure => 4,
285 Exception::Acknowledge => 5,
286 Exception::ServerDeviceBusy => 6,
287 Exception::NegativeAcknowledge => 7,
288 Exception::MemoryParityError => 8,
289 Exception::GatewayPathUnavailable => 10,
290 Exception::GatewayTargetDeviceFailedToRespond => 11,
291 Exception::Custom(n) => n,
292 }
293 }
294}
295
296#[derive(Debug, Clone, Error)]
302#[error("Modbus exception {exception:?} for function {function:?}")]
303pub struct ExceptionResponse {
304 pub function: FunctionCode,
305 pub exception: Exception,
306}
307
308impl From<ExceptionResponse> for crate::error::ModbusError {
309 fn from(er: ExceptionResponse) -> Self {
310 crate::error::ModbusError::exception(er.function.value(), u8::from(er.exception))
311 }
312}
313
314pub fn encode_request_into(req: &Request<'_>, buf: &mut BytesMut) -> Result<(), Error> {
329 let start = buf.len();
330 encode_request(req, buf)?;
331 let pdu_len = buf.len() - start;
332 if pdu_len > MAX_PDU_SIZE {
333 buf.truncate(start);
334 return Err(Error::new(
335 ErrorKind::InvalidData,
336 format!("PDU size {pdu_len} exceeds Modbus limit of {MAX_PDU_SIZE}"),
337 ));
338 }
339 Ok(())
340}
341
342pub fn encode_response_into(rsp: &Response, buf: &mut BytesMut) -> Result<(), Error> {
349 validate_response_size(rsp)?;
353 let start = buf.len();
354 encode_response(rsp, buf)?;
355 let pdu_len = buf.len() - start;
356 if pdu_len > MAX_PDU_SIZE {
357 buf.truncate(start);
358 return Err(Error::new(
359 ErrorKind::InvalidData,
360 format!("PDU size {pdu_len} exceeds Modbus limit of {MAX_PDU_SIZE}"),
361 ));
362 }
363 Ok(())
364}
365
366impl<'a> TryFrom<Request<'a>> for Bytes {
367 type Error = Error;
368 fn try_from(req: Request<'a>) -> Result<Self, Self::Error> {
369 let mut buf = BytesMut::new();
370 encode_request_into(&req, &mut buf)?;
371 Ok(buf.freeze())
372 }
373}
374
375impl TryFrom<Bytes> for Request<'static> {
376 type Error = Error;
377 fn try_from(mut bytes: Bytes) -> Result<Self, Self::Error> {
378 if bytes.is_empty() {
379 return Err(Error::new(ErrorKind::InvalidData, "empty PDU"));
380 }
381 let fc = bytes[0];
382 bytes.advance(1);
383 decode_request(fc, &mut bytes)
384 }
385}
386
387impl From<Response> for Bytes {
395 fn from(rsp: Response) -> Self {
404 let mut buf = BytesMut::new();
405 let _ = encode_response_into(&rsp, &mut buf);
406 buf.freeze()
407 }
408}
409
410impl TryFrom<Bytes> for Response {
411 type Error = Error;
412 fn try_from(mut bytes: Bytes) -> Result<Self, Self::Error> {
413 if bytes.is_empty() {
414 return Err(Error::new(ErrorKind::InvalidData, "empty PDU"));
415 }
416 let fc = bytes[0];
417 if fc & 0x80 != 0 {
419 if bytes.len() < 2 {
420 return Err(Error::new(
421 ErrorKind::InvalidData,
422 "truncated exception PDU",
423 ));
424 }
425 let exception_code = bytes[1];
426 return Ok(Response::Exception(
427 fc & 0x7f,
428 Exception::from(exception_code),
429 ));
430 }
431 bytes.advance(1);
432 decode_response(fc, &mut bytes)
433 }
434}
435
436#[inline]
439fn push_u16(buf: &mut BytesMut, v: u16) {
440 buf.put_u16(v);
441}
442
443fn encode_request(req: &Request<'_>, buf: &mut BytesMut) -> Result<(), Error> {
444 if matches!(req, Request::Disconnect) {
446 return Ok(());
447 }
448 buf.put_u8(req.function_code().value());
449 match req {
450 Request::ReadCoils(addr, qty)
451 | Request::ReadDiscreteInputs(addr, qty)
452 | Request::ReadHoldingRegisters(addr, qty)
453 | Request::ReadInputRegisters(addr, qty) => {
454 push_u16(buf, *addr);
455 push_u16(buf, *qty);
456 }
457 Request::WriteSingleCoil(addr, value) => {
458 push_u16(buf, *addr);
459 buf.put_u16(if *value { 0xFF00 } else { 0x0000 });
460 }
461 Request::WriteSingleRegister(addr, value) => {
462 push_u16(buf, *addr);
463 push_u16(buf, *value);
464 }
465 Request::WriteMultipleCoils(addr, values) => {
466 push_u16(buf, *addr);
467 push_u16(buf, values.len() as u16);
468 let byte_count = safe_u8(values.len().div_ceil(8), "coil byte count")?;
469 buf.put_u8(byte_count);
470 for chunk in values.chunks(8) {
471 let mut byte = 0u8;
472 for (i, &v) in chunk.iter().enumerate() {
473 if v {
474 byte |= 1 << i;
475 }
476 }
477 buf.put_u8(byte);
478 }
479 }
480 Request::WriteMultipleRegisters(addr, values) => {
481 push_u16(buf, *addr);
482 push_u16(buf, values.len() as u16);
483 let byte_count = safe_u8(values.len() * 2, "write multiple reg byte count")?;
484 buf.put_u8(byte_count);
485 for &v in values.iter() {
486 push_u16(buf, v);
487 }
488 }
489 Request::ReadWriteMultipleRegisters(read_addr, read_qty, write_addr, data) => {
490 push_u16(buf, *read_addr);
491 push_u16(buf, *read_qty);
492 push_u16(buf, *write_addr);
493 push_u16(buf, data.len() as u16);
494 let byte_count = safe_u8(data.len() * 2, "read-write reg byte count")?;
495 buf.put_u8(byte_count);
496 for &v in data.iter() {
497 push_u16(buf, v);
498 }
499 }
500 Request::MaskWriteRegister(addr, and_mask, or_mask) => {
501 push_u16(buf, *addr);
502 push_u16(buf, *and_mask);
503 push_u16(buf, *or_mask);
504 }
505 Request::Diagnostic(sf, data) => {
506 push_u16(buf, *sf);
507 push_u16(buf, *data);
508 }
509 Request::Disconnect => {}
512 }
513 Ok(())
514}
515
516fn decode_request(fc: u8, data: &mut Bytes) -> Result<Request<'static>, Error> {
517 macro_rules! read_u16 {
519 ($data:expr) => {{
520 if $data.remaining() < 2 {
521 return Err(Error::new(ErrorKind::UnexpectedEof, "truncated PDU"));
522 }
523 $data.get_u16()
524 }};
525 }
526 macro_rules! read_u8 {
527 ($data:expr) => {{
528 if $data.remaining() < 1 {
529 return Err(Error::new(ErrorKind::UnexpectedEof, "truncated PDU"));
530 }
531 $data.get_u8()
532 }};
533 }
534
535 Ok(match fc {
536 1 => {
537 let addr = read_u16!(data);
538 let qty = read_u16!(data);
539 if qty == 0 || qty > MAX_COILS {
540 return Err(Error::new(
541 ErrorKind::InvalidData,
542 format!("ReadCoils: qty {qty} not in 1..={MAX_COILS}"),
543 ));
544 }
545 if addr as u32 + qty as u32 > 0x10000 {
546 return Err(Error::new(
547 ErrorKind::InvalidData,
548 format!("ReadCoils: addr {addr} + qty {qty} exceeds 0xFFFF"),
549 ));
550 }
551 Request::ReadCoils(addr, qty)
552 }
553 2 => {
554 let addr = read_u16!(data);
555 let qty = read_u16!(data);
556 if qty == 0 || qty > MAX_COILS {
557 return Err(Error::new(
558 ErrorKind::InvalidData,
559 format!("ReadDiscreteInputs: qty {qty} not in 1..={MAX_COILS}"),
560 ));
561 }
562 if addr as u32 + qty as u32 > 0x10000 {
563 return Err(Error::new(
564 ErrorKind::InvalidData,
565 format!("ReadDiscreteInputs: addr {addr} + qty {qty} exceeds 0xFFFF"),
566 ));
567 }
568 Request::ReadDiscreteInputs(addr, qty)
569 }
570 3 => {
571 let addr = read_u16!(data);
572 let qty = read_u16!(data);
573 if qty == 0 || qty > MAX_REGISTERS {
574 return Err(Error::new(
575 ErrorKind::InvalidData,
576 format!("ReadHoldingRegisters: qty {qty} not in 1..={MAX_REGISTERS}"),
577 ));
578 }
579 if addr as u32 + qty as u32 > 0x10000 {
580 return Err(Error::new(
581 ErrorKind::InvalidData,
582 format!("ReadHoldingRegisters: addr {addr} + qty {qty} exceeds 0xFFFF"),
583 ));
584 }
585 Request::ReadHoldingRegisters(addr, qty)
586 }
587 4 => {
588 let addr = read_u16!(data);
589 let qty = read_u16!(data);
590 if qty == 0 || qty > MAX_REGISTERS {
591 return Err(Error::new(
592 ErrorKind::InvalidData,
593 format!("ReadInputRegisters: qty {qty} not in 1..={MAX_REGISTERS}"),
594 ));
595 }
596 if addr as u32 + qty as u32 > 0x10000 {
597 return Err(Error::new(
598 ErrorKind::InvalidData,
599 format!("ReadInputRegisters: addr {addr} + qty {qty} exceeds 0xFFFF"),
600 ));
601 }
602 Request::ReadInputRegisters(addr, qty)
603 }
604 5 => {
605 let addr = read_u16!(data);
606 let raw = read_u16!(data);
607 let val = match raw {
608 0xFF00 => true,
609 0x0000 => false,
610 other => return Err(Error::new(ErrorKind::InvalidData,
611 format!("WriteSingleCoil: invalid coil value {other:#06X}, expected 0xFF00 or 0x0000"))),
612 };
613 Request::WriteSingleCoil(addr, val)
614 }
615 6 => {
616 let addr = read_u16!(data);
617 let val = read_u16!(data);
618 Request::WriteSingleRegister(addr, val)
619 }
620 15 => {
621 let addr = read_u16!(data);
622 let qty = read_u16!(data);
623 if qty == 0 || qty > MAX_COILS {
624 return Err(Error::new(
625 ErrorKind::InvalidData,
626 format!("WriteMultipleCoils: qty {qty} not in 1..={MAX_COILS}"),
627 ));
628 }
629 if addr as u32 + qty as u32 > 0x10000 {
630 return Err(Error::new(
631 ErrorKind::InvalidData,
632 format!("WriteMultipleCoils: addr {addr} + qty {qty} exceeds 0xFFFF"),
633 ));
634 }
635 let qty = qty as usize;
636 let byte_count = read_u8!(data) as usize;
637 let expected_byte_count = qty.div_ceil(8);
638 if byte_count != expected_byte_count {
639 return Err(Error::new(ErrorKind::InvalidData,
640 format!("WriteMultipleCoils: byte_count {byte_count} != ceil(qty/8) ({expected_byte_count})")));
641 }
642 if data.remaining() < byte_count {
643 return Err(Error::new(ErrorKind::UnexpectedEof, "truncated coil data"));
644 }
645 let mut values = Vec::with_capacity(byte_count * 8);
646 for _ in 0..byte_count {
647 let byte = data.get_u8();
648 for i in 0..8 {
649 values.push(byte & (1 << i) != 0);
650 if values.len() >= qty {
651 break;
652 }
653 }
654 }
655 values.truncate(qty);
656 Request::WriteMultipleCoils(addr, Cow::Owned(values))
657 }
658 16 => {
659 let addr = read_u16!(data);
660 let qty = read_u16!(data);
661 if qty == 0 || qty > MAX_REGISTERS {
662 return Err(Error::new(
663 ErrorKind::InvalidData,
664 format!("WriteMultipleRegisters: qty {qty} not in 1..={MAX_REGISTERS}"),
665 ));
666 }
667 if addr as u32 + qty as u32 > 0x10000 {
668 return Err(Error::new(
669 ErrorKind::InvalidData,
670 format!("WriteMultipleRegisters: addr {addr} + qty {qty} exceeds 0xFFFF"),
671 ));
672 }
673 let qty = qty as usize;
674 let byte_count = read_u8!(data) as usize;
675 if byte_count != qty * 2 {
676 return Err(Error::new(
677 ErrorKind::InvalidData,
678 format!(
679 "WriteMultipleRegisters: byte_count {byte_count} != qty*2 ({})",
680 qty * 2
681 ),
682 ));
683 }
684 if data.remaining() < qty * 2 {
685 return Err(Error::new(
686 ErrorKind::UnexpectedEof,
687 "truncated register data",
688 ));
689 }
690 let mut values = Vec::with_capacity(qty);
691 for _ in 0..qty {
692 values.push(data.get_u16());
693 }
694 Request::WriteMultipleRegisters(addr, Cow::Owned(values))
695 }
696 22 => {
697 let addr = read_u16!(data);
698 let and_mask = read_u16!(data);
699 let or_mask = read_u16!(data);
700 Request::MaskWriteRegister(addr, and_mask, or_mask)
701 }
702 23 => {
703 let read_addr = read_u16!(data);
704 let read_qty = read_u16!(data);
705 if read_qty == 0 || read_qty > MAX_REGISTERS {
706 return Err(Error::new(ErrorKind::InvalidData,
707 format!("ReadWriteMultipleRegisters: read_qty {read_qty} not in 1..={MAX_REGISTERS}")));
708 }
709 if read_addr as u32 + read_qty as u32 > 0x10000 {
710 return Err(Error::new(ErrorKind::InvalidData,
711 format!("ReadWriteMultipleRegisters: read_addr {read_addr} + read_qty {read_qty} exceeds 0xFFFF")));
712 }
713 let write_addr = read_u16!(data);
714 let write_qty = read_u16!(data);
715 if write_qty == 0 || write_qty > MAX_REGISTERS {
716 return Err(Error::new(ErrorKind::InvalidData,
717 format!("ReadWriteMultipleRegisters: write_qty {write_qty} not in 1..={MAX_REGISTERS}")));
718 }
719 if write_addr as u32 + write_qty as u32 > 0x10000 {
720 return Err(Error::new(ErrorKind::InvalidData,
721 format!("ReadWriteMultipleRegisters: write_addr {write_addr} + write_qty {write_qty} exceeds 0xFFFF")));
722 }
723 let write_qty = write_qty as usize;
724 let byte_count = read_u8!(data) as usize;
725 if byte_count != write_qty * 2 {
726 return Err(Error::new(
727 ErrorKind::InvalidData,
728 format!(
729 "ReadWriteMultipleRegisters: byte_count {byte_count} != write_qty*2 ({})",
730 write_qty * 2
731 ),
732 ));
733 }
734 if data.remaining() < write_qty * 2 {
735 return Err(Error::new(
736 ErrorKind::UnexpectedEof,
737 "truncated R/W register data",
738 ));
739 }
740 let mut values = Vec::with_capacity(write_qty);
741 for _ in 0..write_qty {
742 values.push(data.get_u16());
743 }
744 Request::ReadWriteMultipleRegisters(read_addr, read_qty, write_addr, Cow::Owned(values))
745 }
746 8 => {
747 let sf = read_u16!(data);
748 let d = read_u16!(data);
749 Request::Diagnostic(sf, d)
750 }
751 _ => {
752 return Err(Error::new(
753 ErrorKind::InvalidData,
754 format!("unknown function code: {fc:#04X}"),
755 ))
756 }
757 })
758}
759
760fn encode_response(rsp: &Response, buf: &mut BytesMut) -> Result<(), Error> {
761 match rsp {
762 Response::ReadCoils(bits) | Response::ReadDiscreteInputs(bits) => {
763 let byte_count = safe_u8(bits.len().div_ceil(8), "coil byte count")?;
764 buf.put_u8(rsp.function_code().value());
765 buf.put_u8(byte_count);
766 for chunk in bits.chunks(8) {
767 let mut byte = 0u8;
768 for (i, &v) in chunk.iter().enumerate() {
769 if v {
770 byte |= 1 << i;
771 }
772 }
773 buf.put_u8(byte);
774 }
775 }
776 Response::ReadHoldingRegisters(regs) | Response::ReadInputRegisters(regs) => {
777 buf.put_u8(rsp.function_code().value());
778 buf.put_u8(safe_u8(regs.len() * 2, "register byte count")?);
779 for &v in regs {
780 push_u16(buf, v);
781 }
782 }
783 Response::ReadWriteMultipleRegisters(regs) => {
784 buf.put_u8(rsp.function_code().value());
785 buf.put_u8(safe_u8(regs.len() * 2, "register byte count")?);
786 for &v in regs {
787 push_u16(buf, v);
788 }
789 }
790 Response::WriteSingleCoil(addr, val) => {
791 buf.put_u8(rsp.function_code().value());
792 push_u16(buf, *addr);
793 buf.put_u16(if *val { 0xFF00 } else { 0x0000 });
794 }
795 Response::WriteSingleRegister(addr, val) => {
796 buf.put_u8(rsp.function_code().value());
797 push_u16(buf, *addr);
798 push_u16(buf, *val);
799 }
800 Response::WriteMultipleCoils(addr, qty) => {
801 buf.put_u8(rsp.function_code().value());
802 push_u16(buf, *addr);
803 push_u16(buf, *qty);
804 }
805 Response::WriteMultipleRegisters(addr, qty) => {
806 buf.put_u8(rsp.function_code().value());
807 push_u16(buf, *addr);
808 push_u16(buf, *qty);
809 }
810 Response::MaskWriteRegister(addr, and_mask, or_mask) => {
811 buf.put_u8(rsp.function_code().value());
812 push_u16(buf, *addr);
813 push_u16(buf, *and_mask);
814 push_u16(buf, *or_mask);
815 }
816 Response::Diagnostic(sf, data) => {
817 buf.put_u8(rsp.function_code().value());
818 push_u16(buf, *sf);
819 push_u16(buf, *data);
820 }
821 Response::Exception(fc, exception) => {
822 buf.put_u8(*fc | 0x80);
823 buf.put_u8(u8::from(*exception));
824 }
825 }
826 Ok(())
827}
828
829fn decode_response(fc: u8, data: &mut Bytes) -> Result<Response, Error> {
830 macro_rules! read_u16 {
831 ($data:expr) => {{
832 if $data.remaining() < 2 {
833 return Err(Error::new(
834 ErrorKind::UnexpectedEof,
835 "truncated response PDU",
836 ));
837 }
838 $data.get_u16()
839 }};
840 }
841 macro_rules! read_u8 {
842 ($data:expr) => {{
843 if $data.remaining() < 1 {
844 return Err(Error::new(
845 ErrorKind::UnexpectedEof,
846 "truncated response PDU",
847 ));
848 }
849 $data.get_u8()
850 }};
851 }
852
853 Ok(match fc {
854 1 | 2 => {
855 let byte_count = read_u8!(data) as usize;
856 if data.remaining() < byte_count {
857 return Err(Error::new(
858 ErrorKind::UnexpectedEof,
859 "truncated coil response",
860 ));
861 }
862 let mut bits = Vec::with_capacity(byte_count * 8);
863 for _ in 0..byte_count {
864 let byte = data.get_u8();
865 for i in 0..8 {
866 bits.push(byte & (1 << i) != 0);
867 }
868 }
869 if fc == 1 {
870 Response::ReadCoils(bits)
871 } else {
872 Response::ReadDiscreteInputs(bits)
873 }
874 }
875 3 | 4 => {
876 let byte_count = read_u8!(data) as usize;
877 if byte_count % 2 != 0 {
878 return Err(Error::new(
879 ErrorKind::InvalidData,
880 format!("register response: byte_count {byte_count} is not even"),
881 ));
882 }
883 if data.remaining() < byte_count {
884 return Err(Error::new(
885 ErrorKind::UnexpectedEof,
886 "truncated register response",
887 ));
888 }
889 let mut regs = Vec::with_capacity(byte_count / 2);
890 for _ in 0..(byte_count / 2) {
891 regs.push(data.get_u16());
892 }
893 if fc == 3 {
894 Response::ReadHoldingRegisters(regs)
895 } else {
896 Response::ReadInputRegisters(regs)
897 }
898 }
899 5 => {
900 let addr = read_u16!(data);
901 let val = read_u16!(data) == 0xFF00;
902 Response::WriteSingleCoil(addr, val)
903 }
904 6 => {
905 let addr = read_u16!(data);
906 let val = read_u16!(data);
907 Response::WriteSingleRegister(addr, val)
908 }
909 15 => {
910 let addr = read_u16!(data);
911 let qty = read_u16!(data);
912 Response::WriteMultipleCoils(addr, qty)
913 }
914 16 => {
915 let addr = read_u16!(data);
916 let qty = read_u16!(data);
917 Response::WriteMultipleRegisters(addr, qty)
918 }
919 22 => {
920 let addr = read_u16!(data);
921 let and_mask = read_u16!(data);
922 let or_mask = read_u16!(data);
923 Response::MaskWriteRegister(addr, and_mask, or_mask)
924 }
925 23 => {
926 let byte_count = read_u8!(data) as usize;
927 if byte_count % 2 != 0 {
928 return Err(Error::new(
929 ErrorKind::InvalidData,
930 format!(
931 "ReadWriteMultipleRegisters response: byte_count {byte_count} is not even"
932 ),
933 ));
934 }
935 if data.remaining() < byte_count {
936 return Err(Error::new(
937 ErrorKind::UnexpectedEof,
938 "truncated R/W response",
939 ));
940 }
941 let mut regs = Vec::with_capacity(byte_count / 2);
942 for _ in 0..(byte_count / 2) {
943 regs.push(data.get_u16());
944 }
945 Response::ReadWriteMultipleRegisters(regs)
946 }
947 8 => {
948 let sf = read_u16!(data);
949 let d = read_u16!(data);
950 Response::Diagnostic(sf, d)
951 }
952 _ => {
953 return Err(Error::new(
954 ErrorKind::InvalidData,
955 format!("unknown response function code: {fc}"),
956 ))
957 }
958 })
959}
960
961#[cfg(test)]
964mod tests {
965 use super::*;
966
967 #[test]
968 fn known_function_codes_accepted() {
969 for fc in [1u8, 2, 3, 4, 5, 6, 8, 15, 16, 22, 23] {
971 assert!(
972 is_known_function_code(fc),
973 "standard FC {fc} should be known"
974 );
975 }
976 for fc in [
978 0x81u8, 0x82, 0x83, 0x84, 0x85, 0x86, 0x88, 0x8F, 0x90, 0x96, 0x97,
979 ] {
980 assert!(
981 is_known_function_code(fc),
982 "exception FC 0x{fc:02X} should be known"
983 );
984 }
985 }
986
987 #[test]
988 fn unknown_function_codes_rejected() {
989 for fc in [0u8, 7, 9, 14, 18, 24, 0x80, 0x87, 0xFF] {
990 assert!(
991 !is_known_function_code(fc),
992 "unknown FC {fc} should NOT be known"
993 );
994 }
995 }
996}