1#![no_std]
2#![allow(non_snake_case)]
3pub trait RegisterRead {
6 fn get(&self, addr: u16) -> u16;
7 fn is_valid(&self, addr: u16) -> bool;
8}
9
10pub trait RegisterWrite {
12 fn set_reg(&mut self, addr: u16, val: u16);
13 fn set_qty(&mut self, qty: u16);
14 fn get_qty(&mut self) -> usize;
15}
16
17pub trait BitRead {
19 fn get(&self, addr: u16) -> bool;
20 fn is_valid(&self, addr: u16) -> bool;
21}
22
23pub trait BitWrite {
25 fn set_bit(&mut self, addr: u16, val: bool);
26 fn set_qty(&mut self, qty: u16);
27 fn get_qty(&mut self) -> usize;
28}
29
30pub struct Coil<const N: usize> {
32 regs: [bool; N],
33 qty: usize,
34}
35impl<const N: usize> Coil<N> {
36 pub const fn new() -> Self {
37 Self {
38 regs: [false; N],
39 qty: 0,
40 }
41 }
42
43 pub fn set_bit(&mut self, addr: u16, val: bool) {
44 let i = addr as usize;
45 if i < N {
46 self.regs[i] = val;
47 }
48 }
49
50 pub fn set_qty(&mut self, qty: u16) {
51 let i = qty as usize;
52 if i < N {
53 self.qty = i;
54 }
55 self.qty = N - 1;
57 }
58
59 pub fn get_qty(&mut self) -> usize {
60 self.qty
61 }
62
63 pub fn as_bits(&self) -> &[bool] {
64 &self.regs
65 }
66}
67
68impl<const N: usize> BitRead for Coil<N> {
69 fn get(&self, addr: u16) -> bool {
70 let i = addr as usize;
71 if i < N {
72 self.regs[i]
73 } else {
74 false
75 }
76 }
77 fn is_valid(&self, addr: u16) -> bool {
78 (addr as usize) < N
79 }
80}
81
82impl<const N: usize> BitWrite for Coil<N> {
83 fn set_bit(&mut self, addr: u16, val: bool) {
84 Coil::set_bit(self, addr, val);
85 }
86
87 fn set_qty(&mut self, qty: u16) {
88 Coil::set_qty(self, qty);
89 }
90
91 fn get_qty(&mut self) -> usize {
92 Coil::get_qty(self)
93 }
94}
95
96pub struct Ists<const N: usize> {
98 regs: [bool; N],
99}
100impl<const N: usize> Ists<N> {
101 pub const fn new() -> Self {
102 Self { regs: [false; N] }
103 }
104 pub fn set_bit(&mut self, addr: u16, val: bool) {
105 let i = addr as usize;
106 if i < N {
107 self.regs[i] = val;
108 }
109 }
110}
111impl<const N: usize> BitRead for Ists<N> {
112 fn get(&self, addr: u16) -> bool {
113 let i = addr as usize;
114 if i < N {
115 self.regs[i]
116 } else {
117 false
118 }
119 }
120 fn is_valid(&self, addr: u16) -> bool {
121 (addr as usize) < N
122 }
123}
124
125pub struct Ireg<const N: usize> {
127 regs: [u16; N],
128}
129impl<const N: usize> Ireg<N> {
130 pub const fn new() -> Self {
131 Self { regs: [0; N] }
132 }
133 pub fn set(&mut self, addr: u16, val: u16) {
134 let i = addr as usize;
135 if i < N {
136 self.regs[i] = val;
137 }
138 }
139}
140impl<const N: usize> RegisterRead for Ireg<N> {
141 fn get(&self, addr: u16) -> u16 {
142 let i = addr as usize;
143 if i < N {
144 self.regs[i]
145 } else {
146 0
147 }
148 }
149 fn is_valid(&self, addr: u16) -> bool {
150 (addr as usize) < N
151 }
152}
153
154pub struct Hreg<const N: usize> {
156 regs: [u16; N],
157 qty: usize,
158}
159
160impl<const N: usize> Hreg<N> {
161 pub const fn new() -> Self {
162 Self {
163 regs: [0; N],
164 qty: 0,
165 }
166 }
167
168 pub fn set(&mut self, addr: u16, val: u16) {
169 let i = addr as usize;
170 if i < N {
171 self.regs[i] = val;
172 }
173 }
174
175 pub fn set_qty(&mut self, qty: u16) {
176 let i = qty as usize;
177 if i < N {
178 self.qty = i;
179 }
180 self.qty = N - 1;
182 }
183
184 pub fn get_qty(&mut self) -> usize {
185 self.qty
186 }
187
188 pub fn as_slice(&self) -> &[u16] {
189 &self.regs
190 }
191}
192
193impl<const N: usize> RegisterRead for Hreg<N> {
195 fn get(&self, addr: u16) -> u16 {
196 let i = addr as usize;
197 if i < N {
198 self.regs[i]
199 } else {
200 0
201 }
202 }
203 fn is_valid(&self, addr: u16) -> bool {
204 (addr as usize) < N
205 }
206}
207
208impl<const N: usize> RegisterWrite for Hreg<N> {
209 fn set_reg(&mut self, addr: u16, val: u16) {
210 Hreg::set(self, addr, val);
211 }
212 fn set_qty(&mut self, qty: u16) {
213 Hreg::set_qty(self, qty);
214 }
215 fn get_qty(&mut self) -> usize {
216 Hreg::get_qty(self)
217 }
218}
219
220static mut SEED: u32 = 0x1234_5678;
226
227#[inline]
228fn xorshift32_next() -> u32 {
229 unsafe {
231 let mut x = SEED;
232 x ^= x << 13;
233 x ^= x >> 17;
234 x ^= x << 5;
235 SEED = x;
236 x
237 }
238}
239
240pub fn Random(start_val: u16, end_val: u16) -> u16 {
242 let (lo, hi) = if start_val <= end_val {
243 (start_val, end_val)
244 } else {
245 (end_val, start_val)
246 };
247
248 let span = (hi as u32).wrapping_sub(lo as u32).wrapping_add(1);
249 let r = xorshift32_next() % span;
250 (lo as u32 + r) as u16
251}
252
253pub mod exc {
254 pub const ILLEGAL_FUNCTION: u8 = 0x01;
255 pub const ILLEGAL_DATA_ADDRESS: u8 = 0x02;
256 pub const ILLEGAL_DATA_VALUE: u8 = 0x03;
257}
258
259pub fn crc16_modbus(data: &[u8]) -> u16 {
261 let mut crc: u16 = 0xFFFF;
262 for &b in data {
263 crc ^= b as u16;
264 for _ in 0..8 {
265 if (crc & 0x0001) != 0 {
266 crc = (crc >> 1) ^ 0xA001;
267 } else {
268 crc >>= 1;
269 }
270 }
271 }
272 crc
273}
274
275pub fn build_resp_bit_reads<const MAX_QTY: usize, B: BitRead>(
282 out: &mut [u8],
283 unit_id: u8,
284 func: u8, start_addr: u16,
286 quantity: u16,
287 bits: &B,
288) -> usize {
289 let qty = quantity as usize;
290 let byte_cnt = (qty + 7) / 8;
291
292 out[0] = unit_id;
293 out[1] = func;
294 out[2] = byte_cnt as u8;
295
296 for i in 0..byte_cnt {
298 out[3 + i] = 0;
299 }
300
301 for j in 0..qty {
302 let addr = start_addr.wrapping_add(j as u16);
303 let bit = bits.get(addr);
304 if bit {
305 let byte_i = j / 8;
306 let bit_i = j % 8;
307 out[3 + byte_i] |= 1u8 << bit_i; }
309 }
310
311 let body_len = 3 + byte_cnt;
312 let crc = crc16_modbus(&out[..body_len]);
313 out[body_len] = (crc & 0xFF) as u8;
314 out[body_len + 1] = (crc >> 8) as u8;
315 body_len + 2
316}
317
318pub fn build_exception_resp<const BUF: usize>(
320 out: &mut [u8; BUF],
321 unit_id: u8,
322 function_exception: u8, exception_code: u8,
324) -> usize {
325 out[0] = unit_id;
326 out[1] = function_exception;
327 out[2] = exception_code;
328
329 let crc = crc16_modbus(&out[..3]);
330 out[3] = (crc & 0xFF) as u8;
331 out[4] = (crc >> 8) as u8;
332 5
333}
334
335pub struct ModbusCtx<'a, H, I, C, D> {
337 pub holdings: &'a mut H, pub inputs: &'a mut I, pub coils: &'a mut C, pub ists: &'a mut D, }
342
343impl<'a, H, I, C, D> ModbusCtx<'a, H, I, C, D>
344where
345 H: RegisterRead + RegisterWrite,
346 I: RegisterRead,
347 C: BitRead + BitWrite,
348 D: BitRead,
349{
350 pub fn pharse_pdu<const MAX_QTY: usize>(
355 &mut self,
356 req8: &[u8; 8],
357 out_tx: &mut [u8],
358 out_exc: &mut [u8; 5],
359 ) -> usize {
360 let unit_id = req8[0];
361 let func = req8[1];
362
363 let expected_crc = u16::from_le_bytes([req8[6], req8[7]]);
365 let calc_crc = crc16_modbus(&req8[..6]);
366 if expected_crc != calc_crc {
367 return build_exception_resp_fixed::<5>(
369 out_exc,
370 unit_id,
371 func | 0x80,
372 exc::ILLEGAL_DATA_VALUE,
373 );
374 }
375
376 let start_addr = u16::from_be_bytes([req8[2], req8[3]]);
378 let quantity = u16::from_be_bytes([req8[4], req8[5]]);
379
380 match func {
381 0x01 => {
382 if quantity == 0 || (quantity as usize) > MAX_QTY {
384 return build_exception_resp_fixed::<5>(
385 out_exc,
386 unit_id,
387 func | 0x80,
388 exc::ILLEGAL_DATA_VALUE,
389 );
390 }
391 let end = start_addr.wrapping_add(quantity.saturating_sub(1));
393 if !self.coils.is_valid(start_addr) || !self.coils.is_valid(end) {
394 return build_exception_resp_fixed::<5>(
395 out_exc,
396 unit_id,
397 func | 0x80,
398 exc::ILLEGAL_DATA_ADDRESS,
399 );
400 }
401 build_resp_bit_reads::<MAX_QTY, _>(
402 out_tx, unit_id, 0x01, start_addr, quantity, self.coils,
403 )
404 }
405
406 0x02 => {
407 if quantity == 0 || (quantity as usize) > MAX_QTY {
409 return build_exception_resp_fixed::<5>(
410 out_exc,
411 unit_id,
412 func | 0x80,
413 exc::ILLEGAL_DATA_VALUE,
414 );
415 }
416 let end = start_addr.wrapping_add(quantity.saturating_sub(1));
418 if !self.ists.is_valid(start_addr) || !self.ists.is_valid(end) {
419 return build_exception_resp_fixed::<5>(
420 out_exc,
421 unit_id,
422 func | 0x80,
423 exc::ILLEGAL_DATA_ADDRESS,
424 );
425 }
426 build_resp_bit_reads::<MAX_QTY, _>(
427 out_tx, unit_id, 0x02, start_addr, quantity, self.ists,
428 )
429 }
430
431 0x03 => {
432 if quantity == 0 || (quantity as usize) > MAX_QTY {
434 return build_exception_resp_fixed::<5>(
435 out_exc,
436 unit_id,
437 func | 0x80,
438 exc::ILLEGAL_DATA_VALUE,
439 );
440 }
441 let end = start_addr.wrapping_add(quantity.saturating_sub(1));
443 if !self.holdings.is_valid(start_addr) || !self.holdings.is_valid(end) {
444 return build_exception_resp_fixed::<5>(
445 out_exc,
446 unit_id,
447 func | 0x80,
448 exc::ILLEGAL_DATA_ADDRESS,
449 );
450 }
451 build_resp_regs::<MAX_QTY, _>(
452 out_tx,
453 unit_id,
454 0x03,
455 start_addr,
456 quantity,
457 self.holdings,
458 )
459 }
460
461 0x04 => {
462 if quantity == 0 || (quantity as usize) > MAX_QTY {
464 return build_exception_resp_fixed::<5>(
465 out_exc,
466 unit_id,
467 func | 0x80,
468 exc::ILLEGAL_DATA_VALUE,
469 );
470 }
471 let end = start_addr.wrapping_add(quantity.saturating_sub(1));
473 if !self.inputs.is_valid(start_addr) || !self.inputs.is_valid(end) {
474 return build_exception_resp_fixed::<5>(
475 out_exc,
476 unit_id,
477 func | 0x80,
478 exc::ILLEGAL_DATA_ADDRESS,
479 );
480 }
481 build_resp_regs::<MAX_QTY, _>(
482 out_tx,
483 unit_id,
484 0x04,
485 start_addr,
486 quantity,
487 self.inputs,
488 )
489 }
490
491 0x05 => {
492 let coil_value = quantity;
494
495 let bit = match coil_value {
496 0xFF00 => true,
497 0x0000 => false,
498 _ => {
499 return build_exception_resp_fixed::<5>(
500 out_exc,
501 unit_id,
502 0x05 | 0x80,
503 exc::ILLEGAL_DATA_VALUE,
504 );
505 }
506 };
507
508 if !self.coils.is_valid(start_addr) {
509 return build_exception_resp_fixed::<5>(
510 out_exc,
511 unit_id,
512 0x05 | 0x80,
513 exc::ILLEGAL_DATA_ADDRESS,
514 );
515 }
516
517 self.coils.set_bit(start_addr, bit);
518
519 out_tx[0] = unit_id;
521 out_tx[1] = 0x05;
522 out_tx[2] = (start_addr >> 8) as u8;
523 out_tx[3] = (start_addr & 0xFF) as u8;
524 out_tx[4] = (coil_value >> 8) as u8;
525 out_tx[5] = (coil_value & 0xFF) as u8;
526
527 let crc = crc16_modbus(&out_tx[..6]);
528 out_tx[6] = (crc & 0xFF) as u8;
529 out_tx[7] = (crc >> 8) as u8;
530 8
531 }
532
533 0x06 => {
534 let reg_value = quantity;
536
537 if !self.holdings.is_valid(start_addr) {
538 return build_exception_resp_fixed::<5>(
539 out_exc,
540 unit_id,
541 0x06 | 0x80,
542 exc::ILLEGAL_DATA_ADDRESS,
543 );
544 }
545
546 self.holdings.set_reg(start_addr, reg_value);
547
548 out_tx[0] = unit_id;
550 out_tx[1] = 0x06;
551 out_tx[2] = (start_addr >> 8) as u8;
552 out_tx[3] = (start_addr & 0xFF) as u8;
553 out_tx[4] = (reg_value >> 8) as u8;
554 out_tx[5] = (reg_value & 0xFF) as u8;
555
556 let crc = crc16_modbus(&out_tx[..6]);
557 out_tx[6] = (crc & 0xFF) as u8;
558 out_tx[7] = (crc >> 8) as u8;
559 8
560 }
561
562 _ => {
563 build_exception_resp_fixed::<5>(
565 out_exc,
566 unit_id,
567 func | 0x80,
568 exc::ILLEGAL_FUNCTION,
569 )
570 }
571 }
572 }
573
574 pub fn pharse_frame<const MAX_QTY: usize>(
578 &mut self,
579 req: &[u8],
580 out_tx: &mut [u8],
581 out_exc: &mut [u8; 5],
582 ) -> usize {
583 if req.len() < 8 {
585 return build_exception_resp_fixed::<5>(
586 out_exc,
587 req.get(0).copied().unwrap_or(0),
588 0x80, exc::ILLEGAL_DATA_VALUE,
590 );
591 }
592
593 let unit_id = req[0];
594 let func = req[1];
595
596 let expected_crc = u16::from_le_bytes([req[req.len() - 2], req[req.len() - 1]]);
598 let calc_crc = crc16_modbus(&req[..req.len() - 2]);
599 if expected_crc != calc_crc {
600 return build_exception_resp_fixed::<5>(
601 out_exc,
602 unit_id,
603 func | 0x80,
604 exc::ILLEGAL_DATA_VALUE,
605 );
606 }
607
608 let start_addr = u16::from_be_bytes([req[2], req[3]]);
610 let quantity = u16::from_be_bytes([req[4], req[5]]);
611
612 match func {
614 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 => {
615 if req.len() != 8 {
616 return build_exception_resp_fixed::<5>(
617 out_exc,
618 unit_id,
619 func | 0x80,
620 exc::ILLEGAL_DATA_VALUE,
621 );
622 }
623 let req8: [u8; 8] = req[..8].try_into().unwrap();
624 return self.pharse_pdu::<MAX_QTY>(&req8, out_tx, out_exc);
625 }
626
627 0x0F => {
628 if req.len() < 9 {
632 return build_exception_resp_fixed::<5>(
633 out_exc,
634 unit_id,
635 func | 0x80,
636 exc::ILLEGAL_DATA_VALUE,
637 );
638 }
639
640 let byte_count = req[6] as usize;
641 let expected_len = 9 + byte_count;
642 if req.len() != expected_len {
643 return build_exception_resp_fixed::<5>(
644 out_exc,
645 unit_id,
646 func | 0x80,
647 exc::ILLEGAL_DATA_VALUE,
648 );
649 }
650
651 if quantity == 0 || (quantity as usize) > MAX_QTY {
653 return build_exception_resp_fixed::<5>(
654 out_exc,
655 unit_id,
656 func | 0x80,
657 exc::ILLEGAL_DATA_VALUE,
658 );
659 }
660
661 let end_addr = start_addr.wrapping_add(quantity.saturating_sub(1));
663 if !self.coils.is_valid(start_addr) || !self.coils.is_valid(end_addr) {
664 return build_exception_resp_fixed::<5>(
665 out_exc,
666 unit_id,
667 func | 0x80,
668 exc::ILLEGAL_DATA_ADDRESS,
669 );
670 }
671
672 let min_byte_cnt = (quantity as usize + 7) / 8;
674 if byte_count != min_byte_cnt {
675 return build_exception_resp_fixed::<5>(
676 out_exc,
677 unit_id,
678 func | 0x80,
679 exc::ILLEGAL_DATA_VALUE,
680 );
681 }
682
683 self.coils.set_qty(quantity);
685
686 let coils_bytes = &req[7..7 + byte_count];
687
688 for j in 0..quantity as usize {
690 let addr = start_addr.wrapping_add(j as u16);
691 let byte_i = j / 8;
692 let bit_i = j % 8;
693 let bit = ((coils_bytes[byte_i] >> bit_i) & 0x01) != 0;
694 self.coils.set_bit(addr, bit);
695 }
696
697 out_tx[0] = unit_id;
700 out_tx[1] = 0x0F;
701 out_tx[2] = (start_addr >> 8) as u8;
702 out_tx[3] = (start_addr & 0xFF) as u8;
703 out_tx[4] = (quantity >> 8) as u8;
704 out_tx[5] = (quantity & 0xFF) as u8;
705
706 let crc = crc16_modbus(&out_tx[..6]);
707 out_tx[6] = (crc & 0xFF) as u8;
708 out_tx[7] = (crc >> 8) as u8;
709 8
710 }
711
712 0x10 => {
713 if req.len() < 9 {
716 return build_exception_resp_fixed::<5>(
717 out_exc,
718 unit_id,
719 func | 0x80,
720 exc::ILLEGAL_DATA_VALUE,
721 );
722 }
723
724 let byte_count = req[6] as usize;
725 let expected_len = 9 + byte_count;
726 if req.len() != expected_len {
727 return build_exception_resp_fixed::<5>(
728 out_exc,
729 unit_id,
730 func | 0x80,
731 exc::ILLEGAL_DATA_VALUE,
732 );
733 }
734
735 if quantity == 0 || (quantity as usize) > MAX_QTY {
736 return build_exception_resp_fixed::<5>(
737 out_exc,
738 unit_id,
739 func | 0x80,
740 exc::ILLEGAL_DATA_VALUE,
741 );
742 }
743
744 let end_addr = start_addr.wrapping_add(quantity.saturating_sub(1));
745 if !self.holdings.is_valid(start_addr) || !self.holdings.is_valid(end_addr) {
746 return build_exception_resp_fixed::<5>(
747 out_exc,
748 unit_id,
749 func | 0x80,
750 exc::ILLEGAL_DATA_ADDRESS,
751 );
752 }
753
754 if byte_count != (quantity as usize) * 2 {
756 return build_exception_resp_fixed::<5>(
757 out_exc,
758 unit_id,
759 func | 0x80,
760 exc::ILLEGAL_DATA_VALUE,
761 );
762 }
763
764 self.holdings.set_qty(quantity);
766
767 let reg_bytes = &req[7..7 + byte_count];
768
769 for j in 0..quantity as usize {
771 let addr = start_addr.wrapping_add(j as u16);
772 let base = j * 2;
773 let v = u16::from_be_bytes([reg_bytes[base], reg_bytes[base + 1]]);
774 self.holdings.set_reg(addr, v);
775 }
776
777 out_tx[0] = unit_id;
779 out_tx[1] = 0x10;
780 out_tx[2] = (start_addr >> 8) as u8;
781 out_tx[3] = (start_addr & 0xFF) as u8;
782 out_tx[4] = (quantity >> 8) as u8;
783 out_tx[5] = (quantity & 0xFF) as u8;
784
785 let crc = crc16_modbus(&out_tx[..6]);
786 out_tx[6] = (crc & 0xFF) as u8;
787 out_tx[7] = (crc >> 8) as u8;
788 8
789 }
790
791 _ => build_exception_resp_fixed::<5>(
792 out_exc,
793 unit_id,
794 func | 0x80,
795 exc::ILLEGAL_FUNCTION,
796 ),
797 }
798 }
799}
800
801fn build_exception_resp_fixed<const BUF: usize>(
803 out_exc: &mut [u8; 5],
804 unit_id: u8,
805 function_exception: u8,
806 exception_code: u8,
807) -> usize {
808 out_exc[0] = unit_id;
809 out_exc[1] = function_exception;
810 out_exc[2] = exception_code;
811
812 let crc = crc16_modbus(&out_exc[..3]);
813 out_exc[3] = (crc & 0xFF) as u8;
814 out_exc[4] = (crc >> 8) as u8;
815 5
816}
817fn build_resp_regs<const MAX_QTY: usize, R: RegisterRead>(
820 out_tx: &mut [u8],
821 unit_id: u8,
822 func: u8,
823 start_addr: u16,
824 quantity: u16,
825 regs: &R,
826) -> usize {
827 let qty = quantity as usize;
828
829 out_tx[0] = unit_id;
830 out_tx[1] = func;
831 out_tx[2] = (qty as u8) * 2;
832
833 for i in 0..qty {
834 let addr = start_addr.wrapping_add(i as u16);
835 let v = regs.get(addr);
836 let base = 3 + i * 2;
837 out_tx[base] = (v >> 8) as u8;
838 out_tx[base + 1] = (v & 0xFF) as u8;
839 }
840
841 let body_len = 3 + qty * 2;
842 let crc = crc16_modbus(&out_tx[..body_len]);
843 out_tx[body_len] = (crc & 0xFF) as u8;
844 out_tx[body_len + 1] = (crc >> 8) as u8;
845
846 body_len + 2
847}
848
849#[inline]
852pub fn FrameLen4Func(func: u8, buf: &[u8], cur_len: usize) -> Option<usize> {
853 match func {
855 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 => return Some(8),
856
857 0x0F | 0x10 => {
859 if cur_len < 7 {
860 return None; }
862 let byte_count = buf[6] as usize;
863 return Some(9 + byte_count);
864 }
865 _ => return None,
866 }
867}