Skip to main content

rusty_modbus_codec/
decode.rs

1//! Top-level PDU decode dispatchers.
2
3use rusty_modbus_types::{FunctionCode, MAX_PDU_SIZE};
4
5use crate::error::DecodeError;
6use crate::pdu::{PduRef, RequestPdu, ResponsePdu};
7use crate::request::{
8    DiagnosticsRequest, EncapsulatedInterfaceRequest, MaskWriteRegisterRequest, ReadCoilsRequest,
9    ReadDiscreteInputsRequest, ReadFifoQueueRequest, ReadFileRecordRequest,
10    ReadHoldingRegistersRequest, ReadInputRegistersRequest, ReadWriteMultipleRegistersRequest,
11    WriteFileRecordRequest, WriteMultipleCoilsRequest, WriteMultipleRegistersRequest,
12    WriteSingleCoilRequest, WriteSingleRegisterRequest,
13};
14use crate::response::{
15    DiagnosticsResponse, EncapsulatedInterfaceResponse, ExceptionResponse,
16    GetCommEventCounterResponse, GetCommEventLogResponse, MaskWriteRegisterResponse,
17    ReadCoilsResponse, ReadDiscreteInputsResponse, ReadExceptionStatusResponse,
18    ReadFifoQueueResponse, ReadFileRecordResponse, ReadHoldingRegistersResponse,
19    ReadInputRegistersResponse, ReadWriteMultipleRegistersResponse, ReportServerIdResponse,
20    WriteFileRecordResponse, WriteMultipleCoilsResponse, WriteMultipleRegistersResponse,
21    WriteSingleCoilResponse, WriteSingleRegisterResponse,
22};
23
24/// Split a raw PDU into function code + data without further dispatching.
25///
26/// The input slice starts at the function code byte (after MBAP header or RTU address).
27///
28/// # Errors
29///
30/// Returns [`DecodeError::PduTooLarge`] if `pdu` exceeds the Modbus 253-byte
31/// PDU ceiling. Returns [`DecodeError::Truncated`] if the slice is empty.
32pub fn decode_pdu_ref(pdu: &[u8]) -> Result<PduRef<'_>, DecodeError> {
33    if pdu.len() > MAX_PDU_SIZE {
34        return Err(DecodeError::PduTooLarge {
35            length: pdu.len(),
36            maximum: MAX_PDU_SIZE,
37        });
38    }
39    if pdu.is_empty() {
40        return Err(DecodeError::Truncated {
41            expected: 1,
42            actual: 0,
43        });
44    }
45    Ok(PduRef {
46        function_code: pdu[0],
47        data: &pdu[1..],
48    })
49}
50
51/// Decode a raw PDU into a typed request.
52///
53/// The input slice starts at the function code byte.
54///
55/// # Errors
56///
57/// Returns a [`DecodeError`] if the PDU is malformed or the function code is unknown.
58pub fn decode_request(pdu: &[u8]) -> Result<RequestPdu<'_>, DecodeError> {
59    let pdu_ref = decode_pdu_ref(pdu)?;
60    let fc = pdu_ref.function_code;
61    let data = pdu_ref.data;
62
63    let fc_enum = FunctionCode::from_raw(fc).ok_or(DecodeError::UnknownFunctionCode(fc))?;
64
65    match fc_enum {
66        FunctionCode::ReadCoils => ReadCoilsRequest::decode(data).map(RequestPdu::ReadCoils),
67        FunctionCode::ReadDiscreteInputs => {
68            ReadDiscreteInputsRequest::decode(data).map(RequestPdu::ReadDiscreteInputs)
69        }
70        FunctionCode::ReadHoldingRegisters => {
71            ReadHoldingRegistersRequest::decode(data).map(RequestPdu::ReadHoldingRegisters)
72        }
73        FunctionCode::ReadInputRegisters => {
74            ReadInputRegistersRequest::decode(data).map(RequestPdu::ReadInputRegisters)
75        }
76        FunctionCode::WriteSingleCoil => {
77            WriteSingleCoilRequest::decode(data).map(RequestPdu::WriteSingleCoil)
78        }
79        FunctionCode::WriteSingleRegister => {
80            WriteSingleRegisterRequest::decode(data).map(RequestPdu::WriteSingleRegister)
81        }
82        FunctionCode::ReadExceptionStatus => {
83            DecodeError::check_exact_len(data, 0)?;
84            Ok(RequestPdu::ReadExceptionStatus)
85        }
86        FunctionCode::Diagnostics => DiagnosticsRequest::decode(data).map(RequestPdu::Diagnostics),
87        FunctionCode::GetCommEventCounter => {
88            DecodeError::check_exact_len(data, 0)?;
89            Ok(RequestPdu::GetCommEventCounter)
90        }
91        FunctionCode::GetCommEventLog => {
92            DecodeError::check_exact_len(data, 0)?;
93            Ok(RequestPdu::GetCommEventLog)
94        }
95        FunctionCode::WriteMultipleCoils => {
96            WriteMultipleCoilsRequest::decode(data).map(RequestPdu::WriteMultipleCoils)
97        }
98        FunctionCode::WriteMultipleRegisters => {
99            WriteMultipleRegistersRequest::decode(data).map(RequestPdu::WriteMultipleRegisters)
100        }
101        FunctionCode::ReportServerId => {
102            DecodeError::check_exact_len(data, 0)?;
103            Ok(RequestPdu::ReportServerId)
104        }
105        FunctionCode::ReadFileRecord => {
106            ReadFileRecordRequest::decode(data).map(RequestPdu::ReadFileRecord)
107        }
108        FunctionCode::WriteFileRecord => {
109            WriteFileRecordRequest::decode(data).map(RequestPdu::WriteFileRecord)
110        }
111        FunctionCode::MaskWriteRegister => {
112            MaskWriteRegisterRequest::decode(data).map(RequestPdu::MaskWriteRegister)
113        }
114        FunctionCode::ReadWriteMultipleRegisters => ReadWriteMultipleRegistersRequest::decode(data)
115            .map(RequestPdu::ReadWriteMultipleRegisters),
116        FunctionCode::ReadFifoQueue => {
117            ReadFifoQueueRequest::decode(data).map(RequestPdu::ReadFifoQueue)
118        }
119        FunctionCode::EncapsulatedInterfaceTransport => {
120            EncapsulatedInterfaceRequest::decode(data).map(RequestPdu::EncapsulatedInterface)
121        }
122        FunctionCode::Custom(fc) => Ok(RequestPdu::Custom(fc, data)),
123    }
124}
125
126/// Decode a raw PDU into a typed response (or exception response).
127///
128/// The input slice starts at the function code byte.
129///
130/// # Errors
131///
132/// Returns a [`DecodeError`] if the PDU is malformed or the function code is unknown.
133pub fn decode_response(pdu: &[u8]) -> Result<ResponsePdu<'_>, DecodeError> {
134    let pdu_ref = decode_pdu_ref(pdu)?;
135    let fc = pdu_ref.function_code;
136    let data = pdu_ref.data;
137
138    // Check for exception response (MSB set).
139    if FunctionCode::is_exception_response(fc) {
140        return ExceptionResponse::decode(fc, data).map(ResponsePdu::Exception);
141    }
142
143    // Exception-flagged bytes handled above. from_raw returns named variants for
144    // public codes and Custom for nonzero vendor codes.
145    let fc_enum = FunctionCode::from_raw(fc).ok_or(DecodeError::UnknownFunctionCode(fc))?;
146
147    match fc_enum {
148        FunctionCode::ReadCoils => ReadCoilsResponse::decode(data).map(ResponsePdu::ReadCoils),
149        FunctionCode::ReadDiscreteInputs => {
150            ReadDiscreteInputsResponse::decode(data).map(ResponsePdu::ReadDiscreteInputs)
151        }
152        FunctionCode::ReadHoldingRegisters => {
153            ReadHoldingRegistersResponse::decode(data).map(ResponsePdu::ReadHoldingRegisters)
154        }
155        FunctionCode::ReadInputRegisters => {
156            ReadInputRegistersResponse::decode(data).map(ResponsePdu::ReadInputRegisters)
157        }
158        FunctionCode::WriteSingleCoil => {
159            WriteSingleCoilResponse::decode(data).map(ResponsePdu::WriteSingleCoil)
160        }
161        FunctionCode::WriteSingleRegister => {
162            WriteSingleRegisterResponse::decode(data).map(ResponsePdu::WriteSingleRegister)
163        }
164        FunctionCode::ReadExceptionStatus => {
165            ReadExceptionStatusResponse::decode(data).map(ResponsePdu::ReadExceptionStatus)
166        }
167        FunctionCode::Diagnostics => {
168            DiagnosticsResponse::decode(data).map(ResponsePdu::Diagnostics)
169        }
170        FunctionCode::GetCommEventCounter => {
171            GetCommEventCounterResponse::decode(data).map(ResponsePdu::GetCommEventCounter)
172        }
173        FunctionCode::GetCommEventLog => {
174            GetCommEventLogResponse::decode(data).map(ResponsePdu::GetCommEventLog)
175        }
176        FunctionCode::WriteMultipleCoils => {
177            WriteMultipleCoilsResponse::decode(data).map(ResponsePdu::WriteMultipleCoils)
178        }
179        FunctionCode::WriteMultipleRegisters => {
180            WriteMultipleRegistersResponse::decode(data).map(ResponsePdu::WriteMultipleRegisters)
181        }
182        FunctionCode::ReportServerId => {
183            ReportServerIdResponse::decode(data).map(ResponsePdu::ReportServerId)
184        }
185        FunctionCode::ReadFileRecord => {
186            ReadFileRecordResponse::decode(data).map(ResponsePdu::ReadFileRecord)
187        }
188        FunctionCode::WriteFileRecord => {
189            WriteFileRecordResponse::decode(data).map(ResponsePdu::WriteFileRecord)
190        }
191        FunctionCode::MaskWriteRegister => {
192            MaskWriteRegisterResponse::decode(data).map(ResponsePdu::MaskWriteRegister)
193        }
194        FunctionCode::ReadWriteMultipleRegisters => {
195            ReadWriteMultipleRegistersResponse::decode(data)
196                .map(ResponsePdu::ReadWriteMultipleRegisters)
197        }
198        FunctionCode::ReadFifoQueue => {
199            ReadFifoQueueResponse::decode(data).map(ResponsePdu::ReadFifoQueue)
200        }
201        FunctionCode::EncapsulatedInterfaceTransport => {
202            EncapsulatedInterfaceResponse::decode(data).map(ResponsePdu::EncapsulatedInterface)
203        }
204        FunctionCode::Custom(fc) => Ok(ResponsePdu::Custom(fc, data)),
205    }
206}