Skip to main content

rustmod_core/pdu/
response.rs

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}