1use crate::error::{ModbusError, ModbusResult};
4pub use crate::handler::{build_exception_pdu, ExceptionCode, ExceptionResponse};
5
6#[repr(u8)]
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum FunctionCode {
10 ReadCoils = 0x01,
11 ReadDiscreteInputs = 0x02,
12 ReadHoldingRegisters = 0x03,
13 ReadInputRegisters = 0x04,
14 WriteSingleCoil = 0x05,
15 WriteSingleRegister = 0x06,
16 WriteMultipleCoils = 0x0F,
17 WriteMultipleRegisters = 0x10,
18 MaskWriteRegister = 0x16,
19 ReadWriteMultipleRegisters = 0x17,
20}
21
22impl TryFrom<u8> for FunctionCode {
23 type Error = ModbusError;
24
25 fn try_from(value: u8) -> Result<Self, Self::Error> {
26 match value {
27 0x01 => Ok(Self::ReadCoils),
28 0x02 => Ok(Self::ReadDiscreteInputs),
29 0x03 => Ok(Self::ReadHoldingRegisters),
30 0x04 => Ok(Self::ReadInputRegisters),
31 0x05 => Ok(Self::WriteSingleCoil),
32 0x06 => Ok(Self::WriteSingleRegister),
33 0x0F => Ok(Self::WriteMultipleCoils),
34 0x10 => Ok(Self::WriteMultipleRegisters),
35 0x16 => Ok(Self::MaskWriteRegister),
36 0x17 => Ok(Self::ReadWriteMultipleRegisters),
37 _ => Err(ModbusError::InvalidFunction(value)),
38 }
39 }
40}
41
42impl From<FunctionCode> for u8 {
43 fn from(value: FunctionCode) -> Self {
44 value as u8
45 }
46}
47
48#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct RequestPdu {
51 bytes: Vec<u8>,
52}
53
54impl RequestPdu {
55 pub fn new(bytes: impl Into<Vec<u8>>) -> ModbusResult<Self> {
57 let bytes = bytes.into();
58 if bytes.is_empty() {
59 return Err(ModbusError::InvalidData(
60 "request PDU must contain at least a function code".to_string(),
61 ));
62 }
63 Ok(Self { bytes })
64 }
65
66 pub fn as_bytes(&self) -> &[u8] {
68 &self.bytes
69 }
70
71 pub fn into_bytes(self) -> Vec<u8> {
73 self.bytes
74 }
75
76 pub fn raw_function_code(&self) -> u8 {
78 self.bytes[0]
79 }
80
81 pub fn function_code(&self) -> ModbusResult<FunctionCode> {
83 FunctionCode::try_from(self.raw_function_code())
84 }
85
86 pub fn semantic_request(&self, is_broadcast: bool) -> Result<SemanticRequest, ExceptionCode> {
88 parse_semantic_request(self.as_bytes(), is_broadcast)
89 }
90}
91
92#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct ResponsePdu {
95 bytes: Vec<u8>,
96}
97
98impl ResponsePdu {
99 pub fn new(bytes: impl Into<Vec<u8>>) -> ModbusResult<Self> {
101 let bytes = bytes.into();
102 if bytes.is_empty() {
103 return Err(ModbusError::InvalidData(
104 "response PDU must contain at least a function code".to_string(),
105 ));
106 }
107 Ok(Self { bytes })
108 }
109
110 pub fn as_bytes(&self) -> &[u8] {
112 &self.bytes
113 }
114
115 pub fn into_bytes(self) -> Vec<u8> {
117 self.bytes
118 }
119
120 pub fn is_exception(&self) -> bool {
122 ExceptionResponse::is_exception(self.as_bytes())
123 }
124}
125
126#[derive(Debug, Clone, PartialEq, Eq)]
128pub enum SemanticRequest {
129 ReadCoils {
130 address: u16,
131 quantity: u16,
132 },
133 ReadDiscreteInputs {
134 address: u16,
135 quantity: u16,
136 },
137 ReadHoldingRegisters {
138 address: u16,
139 quantity: u16,
140 },
141 ReadInputRegisters {
142 address: u16,
143 quantity: u16,
144 },
145 WriteSingleCoil {
146 address: u16,
147 value: bool,
148 },
149 WriteSingleRegister {
150 address: u16,
151 value: u16,
152 },
153 WriteMultipleCoils {
154 address: u16,
155 values: Vec<bool>,
156 },
157 WriteMultipleRegisters {
158 address: u16,
159 values: Vec<u16>,
160 },
161 MaskWriteRegister {
162 address: u16,
163 and_mask: u16,
164 or_mask: u16,
165 },
166 ReadWriteMultipleRegisters {
167 read_address: u16,
168 read_quantity: u16,
169 write_address: u16,
170 values: Vec<u16>,
171 },
172 Custom {
173 function_code: u8,
174 payload: Vec<u8>,
175 },
176}
177
178impl SemanticRequest {
179 pub fn function_code(&self) -> u8 {
181 match self {
182 Self::ReadCoils { .. } => FunctionCode::ReadCoils as u8,
183 Self::ReadDiscreteInputs { .. } => FunctionCode::ReadDiscreteInputs as u8,
184 Self::ReadHoldingRegisters { .. } => FunctionCode::ReadHoldingRegisters as u8,
185 Self::ReadInputRegisters { .. } => FunctionCode::ReadInputRegisters as u8,
186 Self::WriteSingleCoil { .. } => FunctionCode::WriteSingleCoil as u8,
187 Self::WriteSingleRegister { .. } => FunctionCode::WriteSingleRegister as u8,
188 Self::WriteMultipleCoils { .. } => FunctionCode::WriteMultipleCoils as u8,
189 Self::WriteMultipleRegisters { .. } => FunctionCode::WriteMultipleRegisters as u8,
190 Self::MaskWriteRegister { .. } => FunctionCode::MaskWriteRegister as u8,
191 Self::ReadWriteMultipleRegisters { .. } => {
192 FunctionCode::ReadWriteMultipleRegisters as u8
193 }
194 Self::Custom { function_code, .. } => *function_code,
195 }
196 }
197
198 pub fn allows_broadcast(&self) -> bool {
200 matches!(
201 self,
202 Self::WriteSingleCoil { .. }
203 | Self::WriteSingleRegister { .. }
204 | Self::WriteMultipleCoils { .. }
205 | Self::WriteMultipleRegisters { .. }
206 | Self::MaskWriteRegister { .. }
207 )
208 }
209}
210
211#[derive(Debug, Clone, PartialEq, Eq)]
213pub enum SemanticResponse {
214 Bits {
215 function: FunctionCode,
216 values: Vec<bool>,
217 },
218 Registers {
219 function: FunctionCode,
220 values: Vec<u16>,
221 },
222 WriteSingleCoilAck {
223 address: u16,
224 value: bool,
225 },
226 WriteSingleRegisterAck {
227 address: u16,
228 value: u16,
229 },
230 WriteMultipleAck {
231 function: FunctionCode,
232 address: u16,
233 quantity: u16,
234 },
235 MaskWriteAck {
236 address: u16,
237 and_mask: u16,
238 or_mask: u16,
239 },
240 Custom(ResponsePdu),
241}
242
243pub fn parse_semantic_request(
245 pdu: &[u8],
246 is_broadcast: bool,
247) -> Result<SemanticRequest, ExceptionCode> {
248 let function_code = *pdu.first().ok_or(ExceptionCode::IllegalDataValue)?;
249 let request = match FunctionCode::try_from(function_code) {
250 Ok(FunctionCode::ReadCoils) => parse_read_bits(FunctionCode::ReadCoils, pdu)?,
251 Ok(FunctionCode::ReadDiscreteInputs) => {
252 parse_read_bits(FunctionCode::ReadDiscreteInputs, pdu)?
253 }
254 Ok(FunctionCode::ReadHoldingRegisters) => {
255 parse_read_registers(FunctionCode::ReadHoldingRegisters, pdu)?
256 }
257 Ok(FunctionCode::ReadInputRegisters) => {
258 parse_read_registers(FunctionCode::ReadInputRegisters, pdu)?
259 }
260 Ok(FunctionCode::WriteSingleCoil) => parse_write_single_coil(pdu)?,
261 Ok(FunctionCode::WriteSingleRegister) => parse_write_single_register(pdu)?,
262 Ok(FunctionCode::WriteMultipleCoils) => parse_write_multiple_coils(pdu)?,
263 Ok(FunctionCode::WriteMultipleRegisters) => parse_write_multiple_registers(pdu)?,
264 Ok(FunctionCode::MaskWriteRegister) => parse_mask_write_register(pdu)?,
265 Ok(FunctionCode::ReadWriteMultipleRegisters) => parse_read_write_multiple_registers(pdu)?,
266 Err(ModbusError::InvalidFunction(_)) => SemanticRequest::Custom {
267 function_code,
268 payload: pdu[1..].to_vec(),
269 },
270 Err(_) => return Err(ExceptionCode::IllegalFunction),
271 };
272
273 if is_broadcast && !request.allows_broadcast() {
274 return Err(ExceptionCode::IllegalFunction);
275 }
276
277 Ok(request)
278}
279
280impl SemanticResponse {
281 pub fn encode(self) -> ModbusResult<ResponsePdu> {
283 let bytes = match self {
284 Self::Bits { function, values } => {
285 let mut response = vec![function as u8, values.len().div_ceil(8) as u8];
286 response.extend(pack_bits(&values));
287 response
288 }
289 Self::Registers { function, values } => {
290 let mut response = vec![function as u8, (values.len() * 2) as u8];
291 for value in values {
292 response.extend_from_slice(&value.to_be_bytes());
293 }
294 response
295 }
296 Self::WriteSingleCoilAck { address, value } => {
297 let mut response = vec![FunctionCode::WriteSingleCoil as u8];
298 response.extend_from_slice(&address.to_be_bytes());
299 response
300 .extend_from_slice(&(if value { 0xFF00u16 } else { 0x0000u16 }).to_be_bytes());
301 response
302 }
303 Self::WriteSingleRegisterAck { address, value } => {
304 let mut response = vec![FunctionCode::WriteSingleRegister as u8];
305 response.extend_from_slice(&address.to_be_bytes());
306 response.extend_from_slice(&value.to_be_bytes());
307 response
308 }
309 Self::WriteMultipleAck {
310 function,
311 address,
312 quantity,
313 } => {
314 let mut response = vec![function as u8];
315 response.extend_from_slice(&address.to_be_bytes());
316 response.extend_from_slice(&quantity.to_be_bytes());
317 response
318 }
319 Self::MaskWriteAck {
320 address,
321 and_mask,
322 or_mask,
323 } => {
324 let mut response = vec![FunctionCode::MaskWriteRegister as u8];
325 response.extend_from_slice(&address.to_be_bytes());
326 response.extend_from_slice(&and_mask.to_be_bytes());
327 response.extend_from_slice(&or_mask.to_be_bytes());
328 response
329 }
330 Self::Custom(response) => return Ok(response),
331 };
332
333 ResponsePdu::new(bytes)
334 }
335}
336
337fn parse_read_bits(function: FunctionCode, pdu: &[u8]) -> Result<SemanticRequest, ExceptionCode> {
338 ensure_len(pdu, 5)?;
339 let address = u16::from_be_bytes([pdu[1], pdu[2]]);
340 let quantity = u16::from_be_bytes([pdu[3], pdu[4]]);
341 ensure_quantity(quantity, 2000)?;
342 ensure_span(address, quantity)?;
343
344 Ok(match function {
345 FunctionCode::ReadCoils => SemanticRequest::ReadCoils { address, quantity },
346 FunctionCode::ReadDiscreteInputs => {
347 SemanticRequest::ReadDiscreteInputs { address, quantity }
348 }
349 _ => unreachable!("read bit parser only supports FC01/FC02"),
350 })
351}
352
353fn parse_read_registers(
354 function: FunctionCode,
355 pdu: &[u8],
356) -> Result<SemanticRequest, ExceptionCode> {
357 ensure_len(pdu, 5)?;
358 let address = u16::from_be_bytes([pdu[1], pdu[2]]);
359 let quantity = u16::from_be_bytes([pdu[3], pdu[4]]);
360 ensure_quantity(quantity, 125)?;
361 ensure_span(address, quantity)?;
362
363 Ok(match function {
364 FunctionCode::ReadHoldingRegisters => {
365 SemanticRequest::ReadHoldingRegisters { address, quantity }
366 }
367 FunctionCode::ReadInputRegisters => {
368 SemanticRequest::ReadInputRegisters { address, quantity }
369 }
370 _ => unreachable!("read register parser only supports FC03/FC04"),
371 })
372}
373
374fn parse_write_single_coil(pdu: &[u8]) -> Result<SemanticRequest, ExceptionCode> {
375 ensure_len(pdu, 5)?;
376 let address = u16::from_be_bytes([pdu[1], pdu[2]]);
377 let raw_value = u16::from_be_bytes([pdu[3], pdu[4]]);
378 let value = match raw_value {
379 0x0000 => false,
380 0xFF00 => true,
381 _ => return Err(ExceptionCode::IllegalDataValue),
382 };
383
384 Ok(SemanticRequest::WriteSingleCoil { address, value })
385}
386
387fn parse_write_single_register(pdu: &[u8]) -> Result<SemanticRequest, ExceptionCode> {
388 ensure_len(pdu, 5)?;
389 let address = u16::from_be_bytes([pdu[1], pdu[2]]);
390 let value = u16::from_be_bytes([pdu[3], pdu[4]]);
391 Ok(SemanticRequest::WriteSingleRegister { address, value })
392}
393
394fn parse_write_multiple_coils(pdu: &[u8]) -> Result<SemanticRequest, ExceptionCode> {
395 ensure_len(pdu, 6)?;
396 let address = u16::from_be_bytes([pdu[1], pdu[2]]);
397 let quantity = u16::from_be_bytes([pdu[3], pdu[4]]);
398 ensure_quantity(quantity, 1968)?;
399 ensure_span(address, quantity)?;
400
401 let byte_count = pdu[5] as usize;
402 let expected = quantity.div_ceil(8) as usize;
403 if byte_count != expected || pdu.len() < 6 + byte_count {
404 return Err(ExceptionCode::IllegalDataValue);
405 }
406
407 let values = unpack_bits(&pdu[6..6 + byte_count], quantity as usize);
408 Ok(SemanticRequest::WriteMultipleCoils { address, values })
409}
410
411fn parse_write_multiple_registers(pdu: &[u8]) -> Result<SemanticRequest, ExceptionCode> {
412 ensure_len(pdu, 6)?;
413 let address = u16::from_be_bytes([pdu[1], pdu[2]]);
414 let quantity = u16::from_be_bytes([pdu[3], pdu[4]]);
415 ensure_quantity(quantity, 123)?;
416 ensure_span(address, quantity)?;
417
418 let byte_count = pdu[5] as usize;
419 let expected = quantity as usize * 2;
420 if byte_count != expected || pdu.len() < 6 + byte_count {
421 return Err(ExceptionCode::IllegalDataValue);
422 }
423
424 let mut values = Vec::with_capacity(quantity as usize);
425 for chunk in pdu[6..6 + byte_count].chunks_exact(2) {
426 values.push(u16::from_be_bytes([chunk[0], chunk[1]]));
427 }
428
429 Ok(SemanticRequest::WriteMultipleRegisters { address, values })
430}
431
432fn parse_mask_write_register(pdu: &[u8]) -> Result<SemanticRequest, ExceptionCode> {
433 ensure_len(pdu, 7)?;
434 let address = u16::from_be_bytes([pdu[1], pdu[2]]);
435 let and_mask = u16::from_be_bytes([pdu[3], pdu[4]]);
436 let or_mask = u16::from_be_bytes([pdu[5], pdu[6]]);
437 Ok(SemanticRequest::MaskWriteRegister {
438 address,
439 and_mask,
440 or_mask,
441 })
442}
443
444fn parse_read_write_multiple_registers(pdu: &[u8]) -> Result<SemanticRequest, ExceptionCode> {
445 ensure_len(pdu, 10)?;
446 let read_address = u16::from_be_bytes([pdu[1], pdu[2]]);
447 let read_quantity = u16::from_be_bytes([pdu[3], pdu[4]]);
448 ensure_quantity(read_quantity, 125)?;
449 ensure_span(read_address, read_quantity)?;
450
451 let write_address = u16::from_be_bytes([pdu[5], pdu[6]]);
452 let write_quantity = u16::from_be_bytes([pdu[7], pdu[8]]);
453 ensure_quantity(write_quantity, 121)?;
454 ensure_span(write_address, write_quantity)?;
455
456 let byte_count = pdu[9] as usize;
457 let expected = write_quantity as usize * 2;
458 if byte_count != expected || pdu.len() < 10 + byte_count {
459 return Err(ExceptionCode::IllegalDataValue);
460 }
461
462 let mut values = Vec::with_capacity(write_quantity as usize);
463 for chunk in pdu[10..10 + byte_count].chunks_exact(2) {
464 values.push(u16::from_be_bytes([chunk[0], chunk[1]]));
465 }
466
467 Ok(SemanticRequest::ReadWriteMultipleRegisters {
468 read_address,
469 read_quantity,
470 write_address,
471 values,
472 })
473}
474
475fn ensure_len(pdu: &[u8], minimum: usize) -> Result<(), ExceptionCode> {
476 if pdu.len() < minimum {
477 Err(ExceptionCode::IllegalDataValue)
478 } else {
479 Ok(())
480 }
481}
482
483fn ensure_quantity(quantity: u16, maximum: u16) -> Result<(), ExceptionCode> {
484 if quantity == 0 || quantity > maximum {
485 Err(ExceptionCode::IllegalDataValue)
486 } else {
487 Ok(())
488 }
489}
490
491fn ensure_span(address: u16, quantity: u16) -> Result<(), ExceptionCode> {
492 if quantity == 0 {
493 return Err(ExceptionCode::IllegalDataValue);
494 }
495
496 let end = address as u32 + quantity as u32 - 1;
497 if end > u16::MAX as u32 {
498 Err(ExceptionCode::IllegalDataAddress)
499 } else {
500 Ok(())
501 }
502}
503
504fn unpack_bits(data: &[u8], quantity: usize) -> Vec<bool> {
505 let mut values = Vec::with_capacity(quantity);
506 for index in 0..quantity {
507 values.push((data[index / 8] & (1 << (index % 8))) != 0);
508 }
509 values
510}
511
512fn pack_bits(values: &[bool]) -> Vec<u8> {
513 let mut packed = vec![0u8; values.len().div_ceil(8)];
514 for (index, value) in values.iter().enumerate() {
515 if *value {
516 packed[index / 8] |= 1 << (index % 8);
517 }
518 }
519 packed
520}
521
522#[cfg(test)]
523mod tests {
524 use super::{ExceptionCode, FunctionCode, RequestPdu, SemanticRequest, SemanticResponse};
525
526 #[test]
527 fn semantic_parser_enforces_broadcast_legality() {
528 let request =
529 RequestPdu::new([FunctionCode::ReadHoldingRegisters as u8, 0, 0, 0, 1]).unwrap();
530 let error = request.semantic_request(true).unwrap_err();
531 assert_eq!(error, ExceptionCode::IllegalFunction);
532 }
533
534 #[test]
535 fn semantic_parser_accepts_custom_functions() {
536 let request = RequestPdu::new([0x41, 0xAA, 0xBB]).unwrap();
537 let semantic = request.semantic_request(false).unwrap();
538 assert_eq!(
539 semantic,
540 SemanticRequest::Custom {
541 function_code: 0x41,
542 payload: vec![0xAA, 0xBB],
543 }
544 );
545 }
546
547 #[test]
548 fn semantic_response_packs_bits() {
549 let response = SemanticResponse::Bits {
550 function: FunctionCode::ReadCoils,
551 values: vec![true, false, true, true, false, false, false, false, true],
552 }
553 .encode()
554 .unwrap();
555
556 assert_eq!(response.as_bytes(), &[0x01, 0x02, 0x0D, 0x01]);
557 }
558
559 #[test]
560 fn parser_rejects_invalid_register_write_lengths() {
561 let request = RequestPdu::new([0x10, 0x00, 0x02, 0x00, 0x02, 0x03, 0xAA, 0xBB]).unwrap();
562 let error = request.semantic_request(false).unwrap_err();
563 assert_eq!(error, ExceptionCode::IllegalDataValue);
564 }
565}