1use crate::encoding::{Reader, Writer};
2use crate::pdu::{ExceptionResponse, FunctionCode};
3use crate::{DecodeError, EncodeError};
4
5const MAX_READ_REGISTERS: u16 = 125;
6const MAX_WRITE_COILS: u16 = 1968;
7const MAX_WRITE_REGISTERS: u16 = 123;
8
9fn validate_echo_quantity(quantity: u16, max: u16) -> Result<(), DecodeError> {
10 if quantity == 0 || quantity > max {
11 return Err(DecodeError::InvalidValue);
12 }
13 Ok(())
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct ReadCoilsResponse<'a> {
18 pub coil_status: &'a [u8],
19}
20
21impl<'a> ReadCoilsResponse<'a> {
22 fn decode_body(r: &mut Reader<'a>) -> Result<Self, DecodeError> {
23 let byte_count = usize::from(r.read_u8()?);
24 if byte_count == 0 {
25 return Err(DecodeError::InvalidLength);
26 }
27 let data = r.read_exact(byte_count)?;
28 Ok(Self { coil_status: data })
29 }
30
31 pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
32 let byte_count: u8 = self
33 .coil_status
34 .len()
35 .try_into()
36 .map_err(|_| EncodeError::ValueOutOfRange)?;
37 w.write_u8(FunctionCode::ReadCoils.as_u8())?;
38 w.write_u8(byte_count)?;
39 w.write_all(self.coil_status)?;
40 Ok(())
41 }
42
43 pub fn coil(&self, index: usize) -> Option<bool> {
44 let byte = self.coil_status.get(index / 8)?;
45 Some((byte & (1u8 << (index % 8))) != 0)
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub struct ReadDiscreteInputsResponse<'a> {
51 pub input_status: &'a [u8],
52}
53
54impl<'a> ReadDiscreteInputsResponse<'a> {
55 fn decode_body(r: &mut Reader<'a>) -> Result<Self, DecodeError> {
56 let byte_count = usize::from(r.read_u8()?);
57 if byte_count == 0 {
58 return Err(DecodeError::InvalidLength);
59 }
60 let data = r.read_exact(byte_count)?;
61 Ok(Self { input_status: data })
62 }
63
64 pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
65 let byte_count: u8 = self
66 .input_status
67 .len()
68 .try_into()
69 .map_err(|_| EncodeError::ValueOutOfRange)?;
70 w.write_u8(FunctionCode::ReadDiscreteInputs.as_u8())?;
71 w.write_u8(byte_count)?;
72 w.write_all(self.input_status)?;
73 Ok(())
74 }
75
76 pub fn coil(&self, index: usize) -> Option<bool> {
77 let byte = self.input_status.get(index / 8)?;
78 Some((byte & (1u8 << (index % 8))) != 0)
79 }
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub struct ReadHoldingRegistersResponse<'a> {
84 pub data: &'a [u8],
85}
86
87impl<'a> ReadHoldingRegistersResponse<'a> {
88 fn decode_body(r: &mut Reader<'a>) -> Result<Self, DecodeError> {
89 let byte_count = usize::from(r.read_u8()?);
90 if byte_count == 0 || (byte_count % 2) != 0 {
91 return Err(DecodeError::InvalidLength);
92 }
93 if byte_count > usize::from(MAX_READ_REGISTERS) * 2 {
94 return Err(DecodeError::InvalidLength);
95 }
96 let data = r.read_exact(byte_count)?;
97 Ok(Self { data })
98 }
99
100 pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
101 if (self.data.len() % 2) != 0 {
102 return Err(EncodeError::InvalidLength);
103 }
104 let byte_count: u8 = self
105 .data
106 .len()
107 .try_into()
108 .map_err(|_| EncodeError::ValueOutOfRange)?;
109 w.write_u8(FunctionCode::ReadHoldingRegisters.as_u8())?;
110 w.write_u8(byte_count)?;
111 w.write_all(self.data)?;
112 Ok(())
113 }
114
115 pub fn register_count(&self) -> usize {
116 self.data.len() / 2
117 }
118
119 pub fn register(&self, index: usize) -> Option<u16> {
120 let offset = index.checked_mul(2)?;
121 let bytes = self.data.get(offset..offset + 2)?;
122 Some(u16::from_be_bytes([bytes[0], bytes[1]]))
123 }
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127pub struct ReadInputRegistersResponse<'a> {
128 pub data: &'a [u8],
129}
130
131impl<'a> ReadInputRegistersResponse<'a> {
132 fn decode_body(r: &mut Reader<'a>) -> Result<Self, DecodeError> {
133 let byte_count = usize::from(r.read_u8()?);
134 if byte_count == 0 || (byte_count % 2) != 0 {
135 return Err(DecodeError::InvalidLength);
136 }
137 if byte_count > usize::from(MAX_READ_REGISTERS) * 2 {
138 return Err(DecodeError::InvalidLength);
139 }
140 let data = r.read_exact(byte_count)?;
141 Ok(Self { data })
142 }
143
144 pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
145 if (self.data.len() % 2) != 0 {
146 return Err(EncodeError::InvalidLength);
147 }
148 let byte_count: u8 = self
149 .data
150 .len()
151 .try_into()
152 .map_err(|_| EncodeError::ValueOutOfRange)?;
153 w.write_u8(FunctionCode::ReadInputRegisters.as_u8())?;
154 w.write_u8(byte_count)?;
155 w.write_all(self.data)?;
156 Ok(())
157 }
158
159 pub fn register_count(&self) -> usize {
160 self.data.len() / 2
161 }
162
163 pub fn register(&self, index: usize) -> Option<u16> {
164 let offset = index.checked_mul(2)?;
165 let bytes = self.data.get(offset..offset + 2)?;
166 Some(u16::from_be_bytes([bytes[0], bytes[1]]))
167 }
168}
169
170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub struct WriteSingleCoilResponse {
172 pub address: u16,
173 pub value: bool,
174}
175
176impl WriteSingleCoilResponse {
177 fn decode_body(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
178 let address = r.read_be_u16()?;
179 let raw = r.read_be_u16()?;
180 let value = match raw {
181 0xFF00 => true,
182 0x0000 => false,
183 _ => return Err(DecodeError::InvalidValue),
184 };
185 Ok(Self { address, value })
186 }
187
188 pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
189 w.write_u8(FunctionCode::WriteSingleCoil.as_u8())?;
190 w.write_be_u16(self.address)?;
191 w.write_be_u16(if self.value { 0xFF00 } else { 0x0000 })?;
192 Ok(())
193 }
194}
195
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub struct WriteSingleRegisterResponse {
198 pub address: u16,
199 pub value: u16,
200}
201
202impl WriteSingleRegisterResponse {
203 fn decode_body(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
204 Ok(Self {
205 address: r.read_be_u16()?,
206 value: r.read_be_u16()?,
207 })
208 }
209
210 pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
211 w.write_u8(FunctionCode::WriteSingleRegister.as_u8())?;
212 w.write_be_u16(self.address)?;
213 w.write_be_u16(self.value)?;
214 Ok(())
215 }
216}
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq)]
219pub struct WriteMultipleCoilsResponse {
220 pub start_address: u16,
221 pub quantity: u16,
222}
223
224impl WriteMultipleCoilsResponse {
225 fn decode_body(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
226 let start_address = r.read_be_u16()?;
227 let quantity = r.read_be_u16()?;
228 validate_echo_quantity(quantity, MAX_WRITE_COILS)?;
229 Ok(Self {
230 start_address,
231 quantity,
232 })
233 }
234
235 pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
236 if self.quantity == 0 || self.quantity > MAX_WRITE_COILS {
237 return Err(EncodeError::ValueOutOfRange);
238 }
239 w.write_u8(FunctionCode::WriteMultipleCoils.as_u8())?;
240 w.write_be_u16(self.start_address)?;
241 w.write_be_u16(self.quantity)?;
242 Ok(())
243 }
244}
245
246#[derive(Debug, Clone, Copy, PartialEq, Eq)]
247pub struct WriteMultipleRegistersResponse {
248 pub start_address: u16,
249 pub quantity: u16,
250}
251
252impl WriteMultipleRegistersResponse {
253 fn decode_body(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
254 let start_address = r.read_be_u16()?;
255 let quantity = r.read_be_u16()?;
256 validate_echo_quantity(quantity, MAX_WRITE_REGISTERS)?;
257 Ok(Self {
258 start_address,
259 quantity,
260 })
261 }
262
263 pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
264 if self.quantity == 0 || self.quantity > MAX_WRITE_REGISTERS {
265 return Err(EncodeError::ValueOutOfRange);
266 }
267 w.write_u8(FunctionCode::WriteMultipleRegisters.as_u8())?;
268 w.write_be_u16(self.start_address)?;
269 w.write_be_u16(self.quantity)?;
270 Ok(())
271 }
272}
273
274#[derive(Debug, Clone, Copy, PartialEq, Eq)]
275pub struct MaskWriteRegisterResponse {
276 pub address: u16,
277 pub and_mask: u16,
278 pub or_mask: u16,
279}
280
281impl MaskWriteRegisterResponse {
282 fn decode_body(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
283 Ok(Self {
284 address: r.read_be_u16()?,
285 and_mask: r.read_be_u16()?,
286 or_mask: r.read_be_u16()?,
287 })
288 }
289
290 pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
291 w.write_u8(FunctionCode::MaskWriteRegister.as_u8())?;
292 w.write_be_u16(self.address)?;
293 w.write_be_u16(self.and_mask)?;
294 w.write_be_u16(self.or_mask)?;
295 Ok(())
296 }
297}
298
299#[derive(Debug, Clone, Copy, PartialEq, Eq)]
300pub struct ReadWriteMultipleRegistersResponse<'a> {
301 pub data: &'a [u8],
302}
303
304impl<'a> ReadWriteMultipleRegistersResponse<'a> {
305 fn decode_body(r: &mut Reader<'a>) -> Result<Self, DecodeError> {
306 let byte_count = usize::from(r.read_u8()?);
307 if byte_count == 0 || (byte_count % 2) != 0 {
308 return Err(DecodeError::InvalidLength);
309 }
310 if byte_count > usize::from(MAX_READ_REGISTERS) * 2 {
311 return Err(DecodeError::InvalidLength);
312 }
313 let data = r.read_exact(byte_count)?;
314 Ok(Self { data })
315 }
316
317 pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
318 if (self.data.len() % 2) != 0 {
319 return Err(EncodeError::InvalidLength);
320 }
321 let byte_count = u8::try_from(self.data.len()).map_err(|_| EncodeError::ValueOutOfRange)?;
322 w.write_u8(FunctionCode::ReadWriteMultipleRegisters.as_u8())?;
323 w.write_u8(byte_count)?;
324 w.write_all(self.data)?;
325 Ok(())
326 }
327
328 pub fn register_count(&self) -> usize {
329 self.data.len() / 2
330 }
331
332 pub fn register(&self, index: usize) -> Option<u16> {
333 let offset = index.checked_mul(2)?;
334 let bytes = self.data.get(offset..offset + 2)?;
335 Some(u16::from_be_bytes([bytes[0], bytes[1]]))
336 }
337}
338
339#[derive(Debug, Clone, Copy, PartialEq, Eq)]
340pub struct CustomResponse<'a> {
341 pub function_code: u8,
342 pub data: &'a [u8],
343}
344
345impl<'a> CustomResponse<'a> {
346 pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
347 if self.function_code == 0 || FunctionCode::is_exception(self.function_code) {
348 return Err(EncodeError::ValueOutOfRange);
349 }
350 w.write_u8(self.function_code)?;
351 w.write_all(self.data)?;
352 Ok(())
353 }
354}
355
356#[derive(Debug, Clone, Copy, PartialEq, Eq)]
357pub enum Response<'a> {
358 ReadCoils(ReadCoilsResponse<'a>),
359 ReadDiscreteInputs(ReadDiscreteInputsResponse<'a>),
360 ReadHoldingRegisters(ReadHoldingRegistersResponse<'a>),
361 ReadInputRegisters(ReadInputRegistersResponse<'a>),
362 WriteSingleCoil(WriteSingleCoilResponse),
363 WriteSingleRegister(WriteSingleRegisterResponse),
364 WriteMultipleCoils(WriteMultipleCoilsResponse),
365 WriteMultipleRegisters(WriteMultipleRegistersResponse),
366 MaskWriteRegister(MaskWriteRegisterResponse),
367 ReadWriteMultipleRegisters(ReadWriteMultipleRegistersResponse<'a>),
368 Custom(CustomResponse<'a>),
369 Exception(ExceptionResponse),
370}
371
372impl<'a> Response<'a> {
373 pub fn decode(r: &mut Reader<'a>) -> Result<Self, DecodeError> {
374 let function_byte = r.read_u8()?;
375 if FunctionCode::is_exception(function_byte) {
376 return Ok(Self::Exception(ExceptionResponse::decode(function_byte, r)?));
377 }
378
379 let fc = FunctionCode::from_u8(function_byte)?;
380 match fc {
381 FunctionCode::ReadCoils => Ok(Self::ReadCoils(ReadCoilsResponse::decode_body(r)?)),
382 FunctionCode::ReadDiscreteInputs => {
383 Ok(Self::ReadDiscreteInputs(ReadDiscreteInputsResponse::decode_body(r)?))
384 }
385 FunctionCode::ReadHoldingRegisters => Ok(Self::ReadHoldingRegisters(
386 ReadHoldingRegistersResponse::decode_body(r)?,
387 )),
388 FunctionCode::ReadInputRegisters => Ok(Self::ReadInputRegisters(
389 ReadInputRegistersResponse::decode_body(r)?,
390 )),
391 FunctionCode::WriteSingleCoil => {
392 Ok(Self::WriteSingleCoil(WriteSingleCoilResponse::decode_body(r)?))
393 }
394 FunctionCode::WriteSingleRegister => Ok(Self::WriteSingleRegister(
395 WriteSingleRegisterResponse::decode_body(r)?,
396 )),
397 FunctionCode::WriteMultipleCoils => Ok(Self::WriteMultipleCoils(
398 WriteMultipleCoilsResponse::decode_body(r)?,
399 )),
400 FunctionCode::WriteMultipleRegisters => Ok(Self::WriteMultipleRegisters(
401 WriteMultipleRegistersResponse::decode_body(r)?,
402 )),
403 FunctionCode::MaskWriteRegister => Ok(Self::MaskWriteRegister(
404 MaskWriteRegisterResponse::decode_body(r)?,
405 )),
406 FunctionCode::ReadWriteMultipleRegisters => Ok(Self::ReadWriteMultipleRegisters(
407 ReadWriteMultipleRegistersResponse::decode_body(r)?,
408 )),
409 FunctionCode::Custom(function_code) => {
410 let data = r.read_exact(r.remaining())?;
411 Ok(Self::Custom(CustomResponse {
412 function_code,
413 data,
414 }))
415 }
416 }
417 }
418
419 pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
420 match self {
421 Self::ReadCoils(resp) => resp.encode(w),
422 Self::ReadDiscreteInputs(resp) => resp.encode(w),
423 Self::ReadHoldingRegisters(resp) => resp.encode(w),
424 Self::ReadInputRegisters(resp) => resp.encode(w),
425 Self::WriteSingleCoil(resp) => resp.encode(w),
426 Self::WriteSingleRegister(resp) => resp.encode(w),
427 Self::WriteMultipleCoils(resp) => resp.encode(w),
428 Self::WriteMultipleRegisters(resp) => resp.encode(w),
429 Self::MaskWriteRegister(resp) => resp.encode(w),
430 Self::ReadWriteMultipleRegisters(resp) => resp.encode(w),
431 Self::Custom(resp) => resp.encode(w),
432 Self::Exception(resp) => resp.encode(w),
433 }
434 }
435}
436
437#[cfg(test)]
438mod tests {
439 use super::{
440 CustomResponse, MaskWriteRegisterResponse, ReadHoldingRegistersResponse,
441 ReadWriteMultipleRegistersResponse, Response, WriteSingleCoilResponse,
442 };
443 use crate::encoding::{Reader, Writer};
444 use crate::pdu::ExceptionCode;
445 use crate::DecodeError;
446
447 #[test]
448 fn register_helpers_work() {
449 let resp = ReadHoldingRegistersResponse {
450 data: &[0x12, 0x34, 0xAB, 0xCD],
451 };
452 assert_eq!(resp.register_count(), 2);
453 assert_eq!(resp.register(0), Some(0x1234));
454 assert_eq!(resp.register(1), Some(0xABCD));
455 assert_eq!(resp.register(2), None);
456 }
457
458 #[test]
459 fn response_decode_exception_unknown_code() {
460 let mut r = Reader::new(&[0x83, 0x19]);
461 match Response::decode(&mut r).unwrap() {
462 Response::Exception(ex) => {
463 assert_eq!(ex.function_code, 0x03);
464 assert_eq!(ex.exception_code, ExceptionCode::Unknown(0x19));
465 }
466 _ => panic!("expected exception"),
467 }
468 }
469
470 #[test]
471 fn write_single_coil_rejects_invalid_payload() {
472 let mut r = Reader::new(&[0x05, 0x00, 0x01, 0x12, 0x34]);
473 assert_eq!(Response::decode(&mut r).unwrap_err(), DecodeError::InvalidValue);
474 }
475
476 #[test]
477 fn enum_encode_roundtrip() {
478 let original = Response::WriteSingleCoil(WriteSingleCoilResponse {
479 address: 0x0007,
480 value: true,
481 });
482 let mut buf = [0u8; 8];
483 let mut w = Writer::new(&mut buf);
484 original.encode(&mut w).unwrap();
485
486 let mut r = Reader::new(w.as_written());
487 let decoded = Response::decode(&mut r).unwrap();
488 assert_eq!(decoded, original);
489 }
490
491 #[test]
492 fn custom_response_roundtrip() {
493 let original = Response::Custom(CustomResponse {
494 function_code: 0x41,
495 data: &[0xAA, 0x55],
496 });
497 let mut buf = [0u8; 8];
498 let mut w = Writer::new(&mut buf);
499 original.encode(&mut w).unwrap();
500 assert_eq!(w.as_written(), &[0x41, 0xAA, 0x55]);
501
502 let mut r = Reader::new(w.as_written());
503 let decoded = Response::decode(&mut r).unwrap();
504 assert_eq!(decoded, original);
505 }
506
507 #[test]
508 fn mask_write_response_roundtrip() {
509 let original = Response::MaskWriteRegister(MaskWriteRegisterResponse {
510 address: 0x0007,
511 and_mask: 0xFF00,
512 or_mask: 0x00A5,
513 });
514 let mut buf = [0u8; 16];
515 let mut w = Writer::new(&mut buf);
516 original.encode(&mut w).unwrap();
517 assert_eq!(w.as_written(), &[0x16, 0x00, 0x07, 0xFF, 0x00, 0x00, 0xA5]);
518
519 let mut r = Reader::new(w.as_written());
520 let decoded = Response::decode(&mut r).unwrap();
521 assert_eq!(decoded, original);
522 }
523
524 #[test]
525 fn read_write_multiple_registers_response_roundtrip() {
526 let original = Response::ReadWriteMultipleRegisters(ReadWriteMultipleRegistersResponse {
527 data: &[0x12, 0x34, 0xAB, 0xCD],
528 });
529 let mut buf = [0u8; 16];
530 let mut w = Writer::new(&mut buf);
531 original.encode(&mut w).unwrap();
532 assert_eq!(w.as_written(), &[0x17, 0x04, 0x12, 0x34, 0xAB, 0xCD]);
533
534 let mut r = Reader::new(w.as_written());
535 match Response::decode(&mut r).unwrap() {
536 Response::ReadWriteMultipleRegisters(resp) => {
537 assert_eq!(resp.register_count(), 2);
538 assert_eq!(resp.register(0), Some(0x1234));
539 assert_eq!(resp.register(1), Some(0xABCD));
540 }
541 other => panic!("unexpected response: {other:?}"),
542 }
543 }
544}