stackforge_core/layer/modbus/
builder.rs1use super::ModbusFrameType;
22use super::crc::{modbus_crc16, modbus_lrc};
23
24#[derive(Debug, Clone)]
31pub struct ModbusBuilder {
32 frame_type: ModbusFrameType,
33 trans_id: u16,
34 proto_id: u16,
35 unit_id: u8,
36 func_code: u8,
37 pdu_data: Vec<u8>,
39 start_addr: Option<u16>,
41 quantity: Option<u16>,
42 output_value: Option<u16>,
43 values: Vec<u16>,
44 coil_values: Vec<bool>,
45 sub_func: Option<u16>,
46 and_mask: Option<u16>,
47 or_mask: Option<u16>,
48 extra_data: Vec<u8>,
50}
51
52impl Default for ModbusBuilder {
53 fn default() -> Self {
54 Self {
55 frame_type: ModbusFrameType::Tcp,
56 trans_id: 0,
57 proto_id: 0,
58 unit_id: 0,
59 func_code: 0,
60 pdu_data: Vec::new(),
61 start_addr: None,
62 quantity: None,
63 output_value: None,
64 values: Vec::new(),
65 coil_values: Vec::new(),
66 sub_func: None,
67 and_mask: None,
68 or_mask: None,
69 extra_data: Vec::new(),
70 }
71 }
72}
73
74impl ModbusBuilder {
75 #[must_use]
77 pub fn new() -> Self {
78 Self::default()
79 }
80
81 #[must_use]
87 pub fn tcp(mut self) -> Self {
88 self.frame_type = ModbusFrameType::Tcp;
89 self
90 }
91
92 #[must_use]
94 pub fn rtu(mut self) -> Self {
95 self.frame_type = ModbusFrameType::Rtu;
96 self
97 }
98
99 #[must_use]
101 pub fn ascii(mut self) -> Self {
102 self.frame_type = ModbusFrameType::Ascii;
103 self
104 }
105
106 #[must_use]
112 pub fn trans_id(mut self, id: u16) -> Self {
113 self.trans_id = id;
114 self
115 }
116
117 #[must_use]
119 pub fn proto_id(mut self, id: u16) -> Self {
120 self.proto_id = id;
121 self
122 }
123
124 #[must_use]
126 pub fn unit_id(mut self, id: u8) -> Self {
127 self.unit_id = id;
128 self
129 }
130
131 #[must_use]
133 pub fn func_code(mut self, fc: u8) -> Self {
134 self.func_code = fc;
135 self
136 }
137
138 #[must_use]
140 pub fn start_addr(mut self, addr: u16) -> Self {
141 self.start_addr = Some(addr);
142 self
143 }
144
145 #[must_use]
147 pub fn quantity(mut self, qty: u16) -> Self {
148 self.quantity = Some(qty);
149 self
150 }
151
152 #[must_use]
154 pub fn output_value(mut self, val: u16) -> Self {
155 self.output_value = Some(val);
156 self
157 }
158
159 #[must_use]
161 pub fn values(mut self, vals: Vec<u16>) -> Self {
162 self.values = vals;
163 self
164 }
165
166 #[must_use]
168 pub fn coil_values(mut self, vals: Vec<bool>) -> Self {
169 self.coil_values = vals;
170 self
171 }
172
173 #[must_use]
175 pub fn sub_func(mut self, sf: u16) -> Self {
176 self.sub_func = Some(sf);
177 self
178 }
179
180 #[must_use]
182 pub fn and_mask(mut self, mask: u16) -> Self {
183 self.and_mask = Some(mask);
184 self
185 }
186
187 #[must_use]
189 pub fn or_mask(mut self, mask: u16) -> Self {
190 self.or_mask = Some(mask);
191 self
192 }
193
194 #[must_use]
197 pub fn pdu_data(mut self, data: Vec<u8>) -> Self {
198 self.pdu_data = data;
199 self
200 }
201
202 #[must_use]
204 pub fn extra_data(mut self, data: Vec<u8>) -> Self {
205 self.extra_data = data;
206 self
207 }
208
209 fn build_pdu(&self) -> Vec<u8> {
215 if !self.pdu_data.is_empty() {
217 return self.pdu_data.clone();
218 }
219
220 let mut pdu = Vec::new();
221
222 match self.func_code {
223 0x01..=0x04 => {
225 let addr = self.start_addr.unwrap_or(0);
226 let qty = self.quantity.unwrap_or(1);
227 pdu.extend_from_slice(&addr.to_be_bytes());
228 pdu.extend_from_slice(&qty.to_be_bytes());
229 },
230 0x05 => {
232 let addr = self.start_addr.unwrap_or(0);
233 let val = self.output_value.unwrap_or(0xFF00);
234 pdu.extend_from_slice(&addr.to_be_bytes());
235 pdu.extend_from_slice(&val.to_be_bytes());
236 },
237 0x06 => {
239 let addr = self.start_addr.unwrap_or(0);
240 let val = self.output_value.unwrap_or(0);
241 pdu.extend_from_slice(&addr.to_be_bytes());
242 pdu.extend_from_slice(&val.to_be_bytes());
243 },
244 0x07 | 0x11 => {
246 },
248 0x08 => {
250 let sf = self.sub_func.unwrap_or(0);
251 pdu.extend_from_slice(&sf.to_be_bytes());
252 pdu.extend_from_slice(&self.extra_data);
253 },
254 0x0B | 0x0C => {
256 },
258 0x0F => {
260 let addr = self.start_addr.unwrap_or(0);
261 let qty = if self.coil_values.is_empty() {
262 self.quantity.unwrap_or(0)
263 } else {
264 self.coil_values.len() as u16
265 };
266 pdu.extend_from_slice(&addr.to_be_bytes());
267 pdu.extend_from_slice(&qty.to_be_bytes());
268
269 if self.coil_values.is_empty() {
270 pdu.push(0); } else {
272 let byte_count = self.coil_values.len().div_ceil(8);
274 pdu.push(byte_count as u8);
275 let mut bytes = vec![0u8; byte_count];
276 for (i, &coil) in self.coil_values.iter().enumerate() {
277 if coil {
278 bytes[i / 8] |= 1 << (i % 8);
279 }
280 }
281 pdu.extend_from_slice(&bytes);
282 }
283 },
284 0x10 => {
286 let addr = self.start_addr.unwrap_or(0);
287 let qty = if self.values.is_empty() {
288 self.quantity.unwrap_or(0)
289 } else {
290 self.values.len() as u16
291 };
292 pdu.extend_from_slice(&addr.to_be_bytes());
293 pdu.extend_from_slice(&qty.to_be_bytes());
294
295 let byte_count = (self.values.len() * 2) as u8;
296 pdu.push(byte_count);
297 for &val in &self.values {
298 pdu.extend_from_slice(&val.to_be_bytes());
299 }
300 },
301 0x16 => {
303 let addr = self.start_addr.unwrap_or(0);
304 let and = self.and_mask.unwrap_or(0xFFFF);
305 let or = self.or_mask.unwrap_or(0x0000);
306 pdu.extend_from_slice(&addr.to_be_bytes());
307 pdu.extend_from_slice(&and.to_be_bytes());
308 pdu.extend_from_slice(&or.to_be_bytes());
309 },
310 0x17 => {
312 let read_addr = self.start_addr.unwrap_or(0);
314 let read_qty = self.quantity.unwrap_or(0);
315 pdu.extend_from_slice(&read_addr.to_be_bytes());
316 pdu.extend_from_slice(&read_qty.to_be_bytes());
317 pdu.extend_from_slice(&self.extra_data);
319 },
320 0x18 => {
322 let addr = self.start_addr.unwrap_or(0);
323 pdu.extend_from_slice(&addr.to_be_bytes());
324 },
325 0x2B => {
327 pdu.extend_from_slice(&self.extra_data);
328 },
329 _ => {
331 pdu.extend_from_slice(&self.extra_data);
332 },
333 }
334
335 pdu
336 }
337
338 #[must_use]
340 pub fn header_size(&self) -> usize {
341 match self.frame_type {
342 ModbusFrameType::Tcp => {
343 7 + 1 + self.build_pdu().len()
345 },
346 ModbusFrameType::Rtu => {
347 1 + 1 + self.build_pdu().len() + 2
349 },
350 ModbusFrameType::Ascii => {
351 let inner_len = 1 + 1 + self.build_pdu().len() + 1; 1 + inner_len * 2 + 2
354 },
355 }
356 }
357
358 #[must_use]
360 pub fn build(&self) -> Vec<u8> {
361 let pdu = self.build_pdu();
362
363 match self.frame_type {
364 ModbusFrameType::Tcp => {
365 let mut buf = Vec::with_capacity(7 + 1 + pdu.len());
366 buf.extend_from_slice(&self.trans_id.to_be_bytes()); buf.extend_from_slice(&self.proto_id.to_be_bytes()); let length = (1 + 1 + pdu.len()) as u16; buf.extend_from_slice(&length.to_be_bytes()); buf.push(self.unit_id); buf.push(self.func_code); buf.extend_from_slice(&pdu); buf
375 },
376 ModbusFrameType::Rtu => {
377 let mut frame = Vec::with_capacity(1 + 1 + pdu.len() + 2);
378 frame.push(self.unit_id); frame.push(self.func_code); frame.extend_from_slice(&pdu); let crc = modbus_crc16(&frame);
382 frame.push((crc & 0xFF) as u8); frame.push((crc >> 8) as u8); frame
385 },
386 ModbusFrameType::Ascii => {
387 let mut inner = Vec::new();
388 inner.push(self.unit_id);
389 inner.push(self.func_code);
390 inner.extend_from_slice(&pdu);
391 let lrc = modbus_lrc(&inner);
392 inner.push(lrc);
393
394 let mut buf = Vec::with_capacity(1 + inner.len() * 2 + 2);
396 buf.push(b':');
397 for &byte in &inner {
398 buf.push(hex_char(byte >> 4));
399 buf.push(hex_char(byte & 0x0F));
400 }
401 buf.push(b'\r');
402 buf.push(b'\n');
403 buf
404 },
405 }
406 }
407}
408
409fn hex_char(nibble: u8) -> u8 {
411 match nibble {
412 0..=9 => b'0' + nibble,
413 10..=15 => b'A' + (nibble - 10),
414 _ => b'?',
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421 use crate::layer::modbus::crc::{verify_crc16, verify_lrc};
422 use crate::layer::modbus::{ModbusLayer, is_modbus_tcp_payload};
423 use crate::layer::{LayerIndex, LayerKind};
424
425 #[test]
426 fn test_read_coils_tcp() {
427 let pkt = ModbusBuilder::new()
428 .trans_id(1)
429 .unit_id(1)
430 .func_code(0x01)
431 .start_addr(0x0000)
432 .quantity(10)
433 .build();
434
435 assert_eq!(pkt.len(), 12);
436 assert!(is_modbus_tcp_payload(&pkt));
437
438 let idx = LayerIndex::new(LayerKind::Modbus, 0, pkt.len());
439 let layer = ModbusLayer::new(idx);
440 assert_eq!(layer.trans_id(&pkt).unwrap(), 1);
441 assert_eq!(layer.unit_id(&pkt).unwrap(), 1);
442 assert_eq!(layer.func_code(&pkt).unwrap(), 0x01);
443 assert_eq!(layer.start_addr(&pkt).unwrap(), 0);
444 assert_eq!(layer.quantity(&pkt).unwrap(), 10);
445 }
446
447 #[test]
448 fn test_write_single_coil_tcp() {
449 let pkt = ModbusBuilder::new()
450 .trans_id(2)
451 .unit_id(1)
452 .func_code(0x05)
453 .start_addr(0x0013)
454 .output_value(0xFF00)
455 .build();
456
457 assert_eq!(pkt.len(), 12);
458
459 let idx = LayerIndex::new(LayerKind::Modbus, 0, pkt.len());
460 let layer = ModbusLayer::new(idx);
461 assert_eq!(layer.func_code(&pkt).unwrap(), 0x05);
462 assert_eq!(layer.start_addr(&pkt).unwrap(), 0x0013);
463 assert_eq!(layer.output_value(&pkt).unwrap(), 0xFF00);
464 }
465
466 #[test]
467 fn test_write_multiple_registers_tcp() {
468 let pkt = ModbusBuilder::new()
469 .trans_id(3)
470 .unit_id(1)
471 .func_code(0x10)
472 .start_addr(0x0001)
473 .values(vec![0x000A, 0x0102])
474 .build();
475
476 let idx = LayerIndex::new(LayerKind::Modbus, 0, pkt.len());
477 let layer = ModbusLayer::new(idx);
478 assert_eq!(layer.func_code(&pkt).unwrap(), 0x10);
479 assert_eq!(layer.start_addr(&pkt).unwrap(), 0x0001);
480 assert_eq!(layer.quantity(&pkt).unwrap(), 2);
482 }
483
484 #[test]
485 fn test_mask_write_register_tcp() {
486 let pkt = ModbusBuilder::new()
487 .trans_id(1)
488 .unit_id(1)
489 .func_code(0x16)
490 .start_addr(0x0004)
491 .and_mask(0x00F2)
492 .or_mask(0x0025)
493 .build();
494
495 let idx = LayerIndex::new(LayerKind::Modbus, 0, pkt.len());
496 let layer = ModbusLayer::new(idx);
497 assert_eq!(layer.func_code(&pkt).unwrap(), 0x16);
498 assert_eq!(layer.ref_addr(&pkt).unwrap(), 0x0004);
499 assert_eq!(layer.and_mask(&pkt).unwrap(), 0x00F2);
500 assert_eq!(layer.or_mask(&pkt).unwrap(), 0x0025);
501 }
502
503 #[test]
504 fn test_rtu_frame() {
505 let pkt = ModbusBuilder::new()
506 .rtu()
507 .unit_id(1)
508 .func_code(0x03)
509 .start_addr(0x0000)
510 .quantity(10)
511 .build();
512
513 assert_eq!(pkt.len(), 8);
515 assert_eq!(pkt[0], 1); assert_eq!(pkt[1], 0x03); assert!(verify_crc16(&pkt));
518 }
519
520 #[test]
521 fn test_ascii_frame() {
522 let pkt = ModbusBuilder::new()
523 .ascii()
524 .unit_id(1)
525 .func_code(0x03)
526 .start_addr(0x0000)
527 .quantity(10)
528 .build();
529
530 assert_eq!(pkt[0], b':');
532 assert_eq!(pkt[pkt.len() - 2], b'\r');
533 assert_eq!(pkt[pkt.len() - 1], b'\n');
534
535 let hex_str = &pkt[1..pkt.len() - 2];
537 let mut decoded = Vec::new();
538 for chunk in hex_str.chunks(2) {
539 let high = from_hex_char(chunk[0]).unwrap();
540 let low = from_hex_char(chunk[1]).unwrap();
541 decoded.push((high << 4) | low);
542 }
543 assert!(verify_lrc(&decoded));
544 }
545
546 #[test]
547 fn test_default_builder() {
548 let pkt = ModbusBuilder::new().build();
549 assert_eq!(pkt.len(), 8);
551 assert_eq!(pkt[7], 0); }
553
554 #[test]
555 fn test_raw_pdu_data() {
556 let pkt = ModbusBuilder::new()
557 .trans_id(1)
558 .unit_id(1)
559 .func_code(0x03)
560 .pdu_data(vec![0x00, 0x00, 0x00, 0x0A])
561 .build();
562
563 let idx = LayerIndex::new(LayerKind::Modbus, 0, pkt.len());
564 let layer = ModbusLayer::new(idx);
565 assert_eq!(layer.func_code(&pkt).unwrap(), 0x03);
566 assert_eq!(layer.start_addr(&pkt).unwrap(), 0x0000);
567 assert_eq!(layer.quantity(&pkt).unwrap(), 0x000A);
568 }
569
570 #[test]
571 fn test_write_multiple_coils_tcp() {
572 let pkt = ModbusBuilder::new()
573 .trans_id(1)
574 .unit_id(1)
575 .func_code(0x0F)
576 .start_addr(0x0013)
577 .coil_values(vec![
578 true, false, true, true, false, false, true, true, true, false,
579 ])
580 .build();
581
582 let idx = LayerIndex::new(LayerKind::Modbus, 0, pkt.len());
583 let layer = ModbusLayer::new(idx);
584 assert_eq!(layer.func_code(&pkt).unwrap(), 0x0F);
585 assert_eq!(layer.start_addr(&pkt).unwrap(), 0x0013);
586 assert_eq!(layer.quantity(&pkt).unwrap(), 10);
588 }
589
590 #[test]
591 fn test_round_trip_read_holding_registers() {
592 let original = ModbusBuilder::new()
593 .trans_id(42)
594 .unit_id(0x11)
595 .func_code(0x03)
596 .start_addr(0x006B)
597 .quantity(3)
598 .build();
599
600 assert!(is_modbus_tcp_payload(&original));
601
602 let idx = LayerIndex::new(LayerKind::Modbus, 0, original.len());
603 let layer = ModbusLayer::new(idx);
604 assert_eq!(layer.trans_id(&original).unwrap(), 42);
605 assert_eq!(layer.unit_id(&original).unwrap(), 0x11);
606 assert_eq!(layer.func_code(&original).unwrap(), 0x03);
607 assert_eq!(layer.start_addr(&original).unwrap(), 0x006B);
608 assert_eq!(layer.quantity(&original).unwrap(), 3);
609 }
610
611 fn from_hex_char(c: u8) -> Option<u8> {
613 match c {
614 b'0'..=b'9' => Some(c - b'0'),
615 b'A'..=b'F' => Some(c - b'A' + 10),
616 b'a'..=b'f' => Some(c - b'a' + 10),
617 _ => None,
618 }
619 }
620}