1pub mod context;
2pub mod representable;
3pub mod storage;
4
5use core::slice;
6pub use representable::representations;
7
8#[allow(clippy::wildcard_imports)]
9use crate::consts::*;
10use crate::{calc_crc16, calc_lrc, ErrorKind, ModbusProto, VectorTrait};
11
12macro_rules! tcp_response_set_data_len {
59 ($self: expr, $len:expr) => {
60 if $self.proto == ModbusProto::TcpUdp {
61 $self.response.extend(&($len as u16).to_be_bytes())?;
62 }
63 };
64}
65
66#[derive(Debug)]
67#[cfg_attr(feature = "defmt", derive(defmt::Format))]
68pub struct ModbusFrame<'a, V: VectorTrait<u8>> {
69 pub unit_id: u8,
70 buf: &'a [u8],
71 pub response: &'a mut V,
72 pub proto: ModbusProto,
73 pub processing_required: bool,
75 pub response_required: bool,
77 pub responding_to_fn: u8,
79 pub readonly: bool,
81 pub frame_start: usize,
83 pub func: ModbusFunction,
85 pub reg: u16,
87 pub count: u16,
89 pub error: Option<ModbusErrorCode>,
91}
92
93impl<'a, V: VectorTrait<u8>> ModbusFrame<'a, V> {
94 pub fn new(unit_id: u8, buf: &'a [u8], proto: ModbusProto, response: &'a mut V) -> Self {
95 response.clear();
96 Self {
97 unit_id,
98 buf,
99 proto,
100 response,
101 processing_required: false,
102 readonly: true,
103 response_required: false,
104 responding_to_fn: 0,
105 frame_start: 0,
106 count: 1,
107 reg: 0,
108 func: ModbusFunction::GetCoils,
110 error: None,
112 }
113 }
114 pub fn finalize_response(&mut self) -> Result<(), ErrorKind> {
116 if let Some(err) = self.error {
117 match self.proto {
118 ModbusProto::TcpUdp => {
119 self.response
120 .extend(&[0, 3, self.unit_id, self.responding_to_fn + 0x80, err.byte()])?;
123 }
124 ModbusProto::Rtu | ModbusProto::Ascii => {
125 self.response
126 .extend(&[self.unit_id, self.responding_to_fn + 0x80, err.byte()])?;
128 }
129 }
130 }
131 match self.proto {
132 ModbusProto::Rtu => {
133 let len = self.response.len();
134 if len > u8::MAX as usize {
135 return Err(ErrorKind::OOB);
136 }
137 #[allow(clippy::cast_possible_truncation)]
138 let crc = calc_crc16(self.response.as_slice(), len as u8);
139 self.response.extend(&crc.to_le_bytes())
140 }
141 ModbusProto::Ascii => {
142 let len = self.response.len();
143 if len > u8::MAX as usize {
144 return Err(ErrorKind::OOB);
145 }
146 #[allow(clippy::cast_possible_truncation)]
147 let lrc = calc_lrc(self.response.as_slice(), len as u8);
148 self.response.push(lrc)
149 }
150 ModbusProto::TcpUdp => Ok(()),
151 }
152 }
153 pub fn process_write<C: context::ModbusContext>(
155 &mut self,
156 ctx: &mut C,
157 ) -> Result<(), ErrorKind> {
158 match self.func {
159 ModbusFunction::SetCoil => {
160 if self.buf.len() < self.frame_start + 6 {
163 return Err(ErrorKind::FrameBroken);
164 }
165 let val = match u16::from_be_bytes([
166 self.buf[self.frame_start + 4],
167 self.buf[self.frame_start + 5],
168 ]) {
169 0xff00 => true,
170 0x0000 => false,
171 _ => {
172 self.error = Some(ModbusErrorCode::IllegalDataValue);
173 return Ok(());
174 }
175 };
176 if ctx.set_coil(self.reg, val).is_err() {
177 self.error = Some(ModbusErrorCode::IllegalDataAddress);
178 return Ok(());
179 }
180 tcp_response_set_data_len!(self, 6);
181 self.response
183 .extend(&self.buf[self.frame_start..self.frame_start + 6])
184 }
185 ModbusFunction::SetHolding => {
186 if self.buf.len() < self.frame_start + 6 {
189 return Err(ErrorKind::FrameBroken);
190 }
191 let val = u16::from_be_bytes([
192 self.buf[self.frame_start + 4],
193 self.buf[self.frame_start + 5],
194 ]);
195 if ctx.set_holding(self.reg, val).is_err() {
196 self.error = Some(ModbusErrorCode::IllegalDataAddress);
197 return Ok(());
198 }
199 tcp_response_set_data_len!(self, 6);
200 self.response
202 .extend(&self.buf[self.frame_start..self.frame_start + 6])
203 }
204 ModbusFunction::SetCoilsBulk | ModbusFunction::SetHoldingsBulk => {
205 if self.buf.len() < self.frame_start + 7 {
208 return Err(ErrorKind::FrameBroken);
209 }
210 let bytes = self.buf[self.frame_start + 6];
211 let result = match self.func {
212 ModbusFunction::SetCoilsBulk => ctx.set_coils_from_u8(
213 self.reg,
214 self.count,
215 &self.buf[self.frame_start + 7..self.frame_start + 7 + bytes as usize],
216 ),
217 ModbusFunction::SetHoldingsBulk => ctx.set_holdings_from_u8(
218 self.reg,
219 &self.buf[self.frame_start + 7..self.frame_start + 7 + bytes as usize],
220 ),
221 _ => unreachable!("Matched above"),
222 };
223
224 if result.is_ok() {
225 tcp_response_set_data_len!(self, 6);
226 self.response
228 .extend(&self.buf[self.frame_start..self.frame_start + 6])
229 } else {
230 self.error = Some(ModbusErrorCode::IllegalDataAddress);
231 Ok(())
232 }
233 }
234 ModbusFunction::GetHoldings
235 | ModbusFunction::GetInputs
236 | ModbusFunction::GetCoils
237 | ModbusFunction::GetDiscretes => Err(ErrorKind::ReadCallOnWriteFrame),
238 }
239 }
240 pub fn get_external_write(&mut self) -> Result<Write<'_>, ErrorKind> {
248 match self.func {
249 ModbusFunction::SetCoil => {
250 let val = match u16::from_be_bytes([
253 self.buf[self.frame_start + 4],
254 self.buf[self.frame_start + 5],
255 ]) {
256 0xff00 => Write::Bits(WriteBits {
257 address: self.reg,
258 count: 1,
259 data: slice::from_ref(&1u8),
260 }),
261 0x0000 => Write::Bits(WriteBits {
262 address: self.reg,
263 count: 1,
264 data: slice::from_ref(&0u8),
265 }),
266 _ => {
267 self.set_modbus_error_if_unset(&ErrorKind::IllegalDataValue)?;
268 return Err(ErrorKind::IllegalDataValue);
269 }
270 };
271
272 Ok(val)
273 }
274 ModbusFunction::SetHolding => {
275 let write = Write::Words(WriteWords {
279 address: self.reg,
280 count: 1,
281 data: &self.buf[self.frame_start + 4..self.frame_start + 6],
282 });
283
284 Ok(write)
285 }
286 ModbusFunction::SetCoilsBulk => {
287 let bytes = self.buf[self.frame_start + 6];
290 let data_start = self.frame_start + 7;
291
292 let write = Write::Bits(WriteBits {
293 address: self.reg,
294 count: self.count,
295 data: &self.buf[data_start..data_start + bytes as usize],
296 });
297
298 Ok(write)
299 }
300 ModbusFunction::SetHoldingsBulk => {
301 let bytes = self.buf[self.frame_start + 6];
304 let data_start = self.frame_start + 7;
305
306 let write = Write::Words(WriteWords {
307 address: self.reg,
308 count: self.count,
309 data: &self.buf[data_start..data_start + bytes as usize],
310 });
311
312 Ok(write)
313 }
314 ModbusFunction::GetHoldings
315 | ModbusFunction::GetInputs
316 | ModbusFunction::GetCoils
317 | ModbusFunction::GetDiscretes => Err(ErrorKind::ReadCallOnWriteFrame),
318 }
319 }
320 pub fn process_external_write(
322 &mut self,
323 write_result: Result<(), ErrorKind>,
324 ) -> Result<(), ErrorKind> {
325 match write_result {
326 Ok(()) => {
327 match self.func {
328 ModbusFunction::SetCoil
329 | ModbusFunction::SetHolding
330 | ModbusFunction::SetCoilsBulk
331 | ModbusFunction::SetHoldingsBulk => {
332 tcp_response_set_data_len!(self, 6);
338 self.response
340 .extend(&self.buf[self.frame_start..self.frame_start + 6])
341 }
342 ModbusFunction::GetHoldings
343 | ModbusFunction::GetInputs
344 | ModbusFunction::GetCoils
345 | ModbusFunction::GetDiscretes => Err(ErrorKind::ReadCallOnWriteFrame),
346 }
347 }
348 Err(e) if e.is_modbus_error() => {
349 self.set_modbus_error_if_unset(&e)?;
350 Ok(())
351 }
352 Err(e) => Err(e),
353 }
354 }
355
356 #[allow(clippy::manual_is_multiple_of)]
358 pub fn process_read<C: context::ModbusContext>(&mut self, ctx: &C) -> Result<(), ErrorKind> {
359 match self.func {
360 ModbusFunction::GetCoils | ModbusFunction::GetDiscretes => {
361 let mut data_len = self.count >> 3;
364 if self.count % 8 != 0 {
365 data_len += 1;
366 }
367 tcp_response_set_data_len!(self, data_len + 3);
368 self.response
370 .extend(&self.buf[self.frame_start..self.frame_start + 2])?;
371 if data_len > u16::from(u8::MAX) {
372 return Err(ErrorKind::OOB);
373 }
374 #[allow(clippy::cast_possible_truncation)]
375 self.response.push(data_len as u8)?;
376 let result = match self.func {
377 ModbusFunction::GetCoils => {
378 ctx.get_coils_as_u8(self.reg, self.count, self.response)
379 }
380 ModbusFunction::GetDiscretes => {
381 ctx.get_discretes_as_u8(self.reg, self.count, self.response)
382 }
383 _ => unreachable!("Matched above"),
384 };
385 if let Err(e) = result {
386 if e == ErrorKind::OOBContext {
387 self.response.cut_end(5, 0);
388 self.error = Some(ModbusErrorCode::IllegalDataAddress);
389 Ok(())
390 } else {
391 Err(e)
392 }
393 } else {
394 Ok(())
395 }
396 }
397 ModbusFunction::GetHoldings | ModbusFunction::GetInputs => {
398 let data_len = self.count << 1;
401 tcp_response_set_data_len!(self, data_len + 3);
402 self.response
404 .extend(&self.buf[self.frame_start..self.frame_start + 2])?;
405 if data_len > u16::from(u8::MAX) {
406 return Err(ErrorKind::OOB);
407 }
408 #[allow(clippy::cast_possible_truncation)]
409 self.response.push(data_len as u8)?;
411 let result = match self.func {
412 ModbusFunction::GetHoldings => {
413 ctx.get_holdings_as_u8(self.reg, self.count, self.response)
414 }
415 ModbusFunction::GetInputs => {
416 ctx.get_inputs_as_u8(self.reg, self.count, self.response)
417 }
418 _ => unreachable!("Matched above"),
419 };
420 if let Err(e) = result {
421 if e == ErrorKind::OOBContext {
422 self.response.cut_end(5, 0);
423 self.error = Some(ModbusErrorCode::IllegalDataAddress);
424 Ok(())
425 } else {
426 Err(e)
427 }
428 } else {
429 Ok(())
430 }
431 }
432 ModbusFunction::SetCoil
433 | ModbusFunction::SetHolding
434 | ModbusFunction::SetCoilsBulk
435 | ModbusFunction::SetHoldingsBulk => Err(ErrorKind::WriteCallOnReadFrame),
436 }
437 }
438
439 #[allow(clippy::manual_is_multiple_of)]
447 pub fn get_external_read(&mut self) -> Result<Read<'_>, ErrorKind> {
448 match self.func {
449 ModbusFunction::GetCoils | ModbusFunction::GetDiscretes => {
450 let mut data_len = self.count >> 3;
453 if self.count % 8 != 0 {
454 data_len += 1;
455 }
456 tcp_response_set_data_len!(self, data_len + 3);
457 self.response
459 .extend(&self.buf[self.frame_start..self.frame_start + 2])?;
460 if data_len > u16::from(u8::MAX) {
461 return Err(ErrorKind::OOB);
462 }
463 #[allow(clippy::cast_possible_truncation)]
464 self.response.push(data_len as u8)?;
466
467 let current_length = self.response.len();
469 let new_length = current_length + data_len as usize;
470 self.response.resize(new_length, 0u8)?;
471
472 Ok(Read::Bits(ReadBits {
473 address: self.reg,
474 count: self.count,
475 buf: &mut self.response.as_mut_slice()[current_length..new_length],
476 }))
477 }
478 ModbusFunction::GetHoldings | ModbusFunction::GetInputs => {
479 let data_len = self.count << 1;
482 tcp_response_set_data_len!(self, data_len + 3);
483 self.response
485 .extend(&self.buf[self.frame_start..self.frame_start + 2])?;
486 if data_len > u16::from(u8::MAX) {
487 return Err(ErrorKind::OOB);
488 }
489 #[allow(clippy::cast_possible_truncation)]
490 self.response.push(data_len as u8)?;
492
493 let current_length = self.response.len();
495 let new_length = current_length + data_len as usize;
496 self.response.resize(new_length, 0u8)?;
497
498 Ok(Read::Words(ReadWords {
499 address: self.reg,
500 count: self.count,
501 buf: &mut self.response.as_mut_slice()[current_length..new_length],
502 }))
503 }
504 ModbusFunction::SetCoil
505 | ModbusFunction::SetHolding
506 | ModbusFunction::SetCoilsBulk
507 | ModbusFunction::SetHoldingsBulk => Err(ErrorKind::WriteCallOnReadFrame),
508 }
509 }
510
511 pub fn process_external_read(
513 &mut self,
514 read_result: Result<(), ErrorKind>,
515 ) -> Result<(), ErrorKind> {
516 match read_result {
517 Ok(()) => match self.func {
518 ModbusFunction::GetCoils
519 | ModbusFunction::GetDiscretes
520 | ModbusFunction::GetHoldings
521 | ModbusFunction::GetInputs => Ok(()),
522 ModbusFunction::SetCoil
523 | ModbusFunction::SetHolding
524 | ModbusFunction::SetCoilsBulk
525 | ModbusFunction::SetHoldingsBulk => Err(ErrorKind::WriteCallOnReadFrame),
526 },
527 Err(e) if e.is_modbus_error() => {
528 self.set_modbus_error_if_unset(&e)?;
529 Ok(())
530 }
531 Err(e) => Err(e),
532 }
533 }
534
535 #[allow(clippy::too_many_lines)]
537 pub fn parse(&mut self) -> Result<(), ErrorKind> {
538 if self.proto == ModbusProto::TcpUdp {
539 if self.buf.len() < 6 {
540 return Err(ErrorKind::FrameBroken);
541 }
542 let proto_id = u16::from_be_bytes([self.buf[2], self.buf[3]]);
544 let length = u16::from_be_bytes([self.buf[4], self.buf[5]]);
545 if proto_id != 0 || !(6..=250).contains(&length) {
546 return Err(ErrorKind::FrameBroken);
547 }
548 self.frame_start = 6;
549 }
550 if self.frame_start >= self.buf.len() {
551 return Err(ErrorKind::FrameBroken);
552 }
553 let unit = self.buf[self.frame_start];
554 let broadcast = unit == 0 || unit == 255; if !broadcast && unit != self.unit_id {
556 return Ok(());
557 }
558 if !broadcast && self.proto == ModbusProto::TcpUdp {
559 self.response.extend(&self.buf[0..4])?;
561 }
562 if self.buf.len() < self.frame_start + 2 {
563 return Err(ErrorKind::FrameBroken);
564 }
565
566 self.responding_to_fn = self.buf[self.frame_start + 1];
568 if let Ok(f) = ModbusFunction::try_from(self.buf[self.frame_start + 1]) {
569 self.func = f;
570 } else {
571 if !broadcast {
574 self.response_required = true;
575 self.error = Some(ModbusErrorCode::IllegalFunction);
576 }
577 return Ok(());
578 }
579 macro_rules! check_frame_crc {
580 ($len:expr) => {
581 match self.proto {
582 ModbusProto::TcpUdp => true,
583 ModbusProto::Rtu => {
584 if self.buf.len() < self.frame_start + $len as usize + 2 {
585 return Err(ErrorKind::FrameBroken);
586 }
587 calc_crc16(self.buf, $len)
588 == u16::from_le_bytes([
589 self.buf[self.frame_start + $len as usize],
590 self.buf[self.frame_start + $len as usize + 1],
591 ])
592 }
593 ModbusProto::Ascii => {
594 if self.buf.len() < self.frame_start + $len as usize + 1 {
595 return Err(ErrorKind::FrameBroken);
596 }
597 calc_lrc(self.buf, $len) == self.buf[self.frame_start + $len as usize]
598 }
599 }
600 };
601 }
602 match self.func {
603 ModbusFunction::GetCoils | ModbusFunction::GetDiscretes => {
604 if broadcast {
607 return Ok(());
608 }
609 if self.buf.len() < self.frame_start + 6 {
610 return Err(ErrorKind::FrameBroken);
611 }
612 if !check_frame_crc!(6) {
613 return Err(ErrorKind::FrameCRCError);
614 }
615 self.response_required = true;
616 self.count = u16::from_be_bytes([
617 self.buf[self.frame_start + 4],
618 self.buf[self.frame_start + 5],
619 ]);
620 if self.count > 2000 {
621 self.error = Some(ModbusErrorCode::IllegalDataValue);
622 return Ok(());
623 }
624 self.processing_required = true;
625 self.reg = u16::from_be_bytes([
626 self.buf[self.frame_start + 2],
627 self.buf[self.frame_start + 3],
628 ]);
629 Ok(())
630 }
631 ModbusFunction::GetHoldings | ModbusFunction::GetInputs => {
632 if broadcast {
635 return Ok(());
636 }
637 if self.buf.len() < self.frame_start + 6 {
638 return Err(ErrorKind::FrameBroken);
639 }
640 if !check_frame_crc!(6) {
641 return Err(ErrorKind::FrameCRCError);
642 }
643 self.response_required = true;
644 self.count = u16::from_be_bytes([
645 self.buf[self.frame_start + 4],
646 self.buf[self.frame_start + 5],
647 ]);
648 if self.count > 125 {
649 self.error = Some(ModbusErrorCode::IllegalDataValue);
650 return Ok(());
651 }
652 self.processing_required = true;
653 self.reg = u16::from_be_bytes([
654 self.buf[self.frame_start + 2],
655 self.buf[self.frame_start + 3],
656 ]);
657 Ok(())
658 }
659 ModbusFunction::SetCoil | ModbusFunction::SetHolding => {
660 if self.buf.len() < self.frame_start + 4 {
663 return Err(ErrorKind::FrameBroken);
664 }
665 if !check_frame_crc!(6) {
666 return Err(ErrorKind::FrameCRCError);
667 }
668 if !broadcast {
669 self.response_required = true;
670 }
671 self.count = 1;
672 self.processing_required = true;
673 self.readonly = false;
674 self.reg = u16::from_be_bytes([
675 self.buf[self.frame_start + 2],
676 self.buf[self.frame_start + 3],
677 ]);
678 Ok(())
679 }
680 ModbusFunction::SetCoilsBulk | ModbusFunction::SetHoldingsBulk => {
681 if self.buf.len() < self.frame_start + 7 {
684 return Err(ErrorKind::FrameBroken);
685 }
686 let bytes = self.buf[self.frame_start + 6];
687 if !check_frame_crc!(7 + bytes) {
688 return Err(ErrorKind::FrameCRCError);
689 }
690 if !broadcast {
691 self.response_required = true;
692 }
693 self.count = u16::from_be_bytes([
694 self.buf[self.frame_start + 4],
695 self.buf[self.frame_start + 5],
696 ]);
697 let max_count = match self.func {
698 ModbusFunction::SetCoilsBulk => 1968,
699 ModbusFunction::SetHoldingsBulk => 123,
700 _ => unreachable!("Matched above"),
701 };
702 if self.count > max_count {
703 self.error = Some(ModbusErrorCode::IllegalDataValue);
704 return Ok(());
705 }
706 if bytes > 246 {
707 self.error = Some(ModbusErrorCode::IllegalDataValue);
708 return Ok(());
709 }
710 self.processing_required = true;
711 self.readonly = false;
712 self.reg = u16::from_be_bytes([
713 self.buf[self.frame_start + 2],
714 self.buf[self.frame_start + 3],
715 ]);
716 self.count = u16::from_be_bytes([
717 self.buf[self.frame_start + 4],
718 self.buf[self.frame_start + 5],
719 ]);
720 Ok(())
721 }
722 }
723 }
724
725 pub fn changes(&self) -> Option<Changes> {
729 let reg = self.reg;
730 let count = self.count;
731
732 Some(match self.func {
733 ModbusFunction::SetCoil => Changes::Coils { reg, count: 1 },
734 ModbusFunction::SetCoilsBulk => Changes::Coils { reg, count },
735 ModbusFunction::SetHolding => Changes::Holdings { reg, count: 1 },
736 ModbusFunction::SetHoldingsBulk => Changes::Holdings { reg, count },
737 _ => return None,
738 })
739 }
740
741 pub fn set_modbus_error_if_unset(&mut self, err: &ErrorKind) -> Result<(), ErrorKind> {
748 if self.error.is_none() && err.is_modbus_error() {
749 let len_leave_before_finalize = if self.proto == ModbusProto::TcpUdp {
751 4
752 } else {
753 0
754 };
755
756 self.response.resize(len_leave_before_finalize, 0)?;
757 self.error = Some(err.to_modbus_error()?);
758 }
759 Ok(())
760 }
761}
762
763#[derive(Debug, Copy, Clone, Eq, PartialEq)]
765pub enum Changes {
766 Coils { reg: u16, count: u16 },
767 Holdings { reg: u16, count: u16 },
768}
769
770#[derive(Debug, Copy, Clone, Eq, PartialEq)]
772pub struct WriteBits<'a> {
773 pub address: u16,
774 pub count: u16,
775 pub data: &'a [u8],
776}
777
778#[derive(Debug, Copy, Clone, Eq, PartialEq)]
780pub struct WriteWords<'a> {
781 pub address: u16,
782 pub count: u16,
783 pub data: &'a [u8],
784}
785
786#[derive(Debug, Copy, Clone, Eq, PartialEq)]
788pub enum Write<'a> {
789 Bits(WriteBits<'a>),
790 Words(WriteWords<'a>),
791}
792
793#[derive(Debug, Eq, PartialEq)]
795pub struct ReadBits<'a> {
796 pub address: u16,
797 pub count: u16,
798 pub buf: &'a mut [u8],
799}
800
801#[derive(Debug, Eq, PartialEq)]
803pub struct ReadWords<'a> {
804 pub address: u16,
805 pub count: u16,
806 pub buf: &'a mut [u8],
807}
808
809#[derive(Debug, Eq, PartialEq)]
811pub enum Read<'a> {
812 Bits(ReadBits<'a>),
813 Words(ReadWords<'a>),
814}