Skip to main content

rusty_modbus_codec/response/
diagnostic.rs

1//! Response types for diagnostic function codes (FC 07, 08, 0B, 0C, 11).
2
3use crate::error::{DecodeError, EncodeError};
4use crate::request::Encode;
5use rusty_modbus_types::{DiagnosticSubFunction, FunctionCode};
6
7fn check_diagnostic_data_len_decode(data: &[u8]) -> Result<(), DecodeError> {
8    if data.len().is_multiple_of(2) {
9        Ok(())
10    } else {
11        Err(DecodeError::InvalidDiagnosticDataLength { length: data.len() })
12    }
13}
14
15fn check_diagnostic_data_len_encode(data: &[u8]) -> Result<(), EncodeError> {
16    if data.len().is_multiple_of(2) {
17        Ok(())
18    } else {
19        Err(EncodeError::InvalidDiagnosticDataLength { length: data.len() })
20    }
21}
22
23/// Response to a Read Exception Status request (FC 0x07).
24#[derive(Debug)]
25pub struct ReadExceptionStatusResponse {
26    /// Eight exception status bits packed into one byte.
27    pub status: u8,
28}
29
30impl ReadExceptionStatusResponse {
31    /// Decode from the data bytes following the function code.
32    ///
33    /// # Errors
34    ///
35    /// Returns `DecodeError::Truncated` if `data` is too short.
36    /// Returns `DecodeError::LengthMismatch` if `data` has extra bytes.
37    pub fn decode(data: &[u8]) -> Result<Self, DecodeError> {
38        DecodeError::check_exact_len(data, 1)?;
39        Ok(Self { status: data[0] })
40    }
41}
42
43impl Encode for ReadExceptionStatusResponse {
44    fn encode_into(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
45        let len = self.encoded_len();
46        if buf.len() < len {
47            return Err(EncodeError::BufferTooSmall {
48                required: len,
49                available: buf.len(),
50            });
51        }
52        EncodeError::check_pdu_len(len)?;
53        buf[0] = FunctionCode::ReadExceptionStatus.code();
54        buf[1] = self.status;
55        Ok(len)
56    }
57
58    fn encoded_len(&self) -> usize {
59        1 + 1
60    }
61}
62
63/// Response to a Diagnostics request (FC 0x08).
64///
65/// Echoes the sub-function code and associated data.
66#[derive(Debug)]
67pub struct DiagnosticsResponse<'buf> {
68    /// The diagnostic sub-function code.
69    pub sub_function: DiagnosticSubFunction,
70    /// Diagnostic data bytes.
71    pub data: &'buf [u8],
72}
73
74impl<'buf> DiagnosticsResponse<'buf> {
75    /// Decode from the data bytes following the function code.
76    ///
77    /// # Errors
78    ///
79    /// Returns `DecodeError::Truncated` if `data` is too short.
80    /// Returns `DecodeError::InvalidDiagnosticDataLength` if the payload is not
81    /// an even number of bytes.
82    pub fn decode(data: &'buf [u8]) -> Result<Self, DecodeError> {
83        if data.len() < 2 {
84            return Err(DecodeError::Truncated {
85                expected: 2,
86                actual: data.len(),
87            });
88        }
89        let raw_sub = u16::from_be_bytes([data[0], data[1]]);
90        let sub_function = DiagnosticSubFunction::from_raw(raw_sub)
91            .ok_or(DecodeError::UnknownDiagnosticSubFunction(raw_sub))?;
92        let payload = &data[2..];
93        check_diagnostic_data_len_decode(payload)?;
94        Ok(Self {
95            sub_function,
96            data: payload,
97        })
98    }
99}
100
101impl Encode for DiagnosticsResponse<'_> {
102    fn encode_into(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
103        let len = self.encoded_len();
104        if buf.len() < len {
105            return Err(EncodeError::BufferTooSmall {
106                required: len,
107                available: buf.len(),
108            });
109        }
110        check_diagnostic_data_len_encode(self.data)?;
111        EncodeError::check_pdu_len(len)?;
112        buf[0] = FunctionCode::Diagnostics.code();
113        let sf = self.sub_function.code().to_be_bytes();
114        buf[1] = sf[0];
115        buf[2] = sf[1];
116        buf[3..3 + self.data.len()].copy_from_slice(self.data);
117        Ok(len)
118    }
119
120    fn encoded_len(&self) -> usize {
121        1 + 2 + self.data.len()
122    }
123}
124
125/// Response to a Get Comm Event Counter request (FC 0x0B).
126#[derive(Debug)]
127pub struct GetCommEventCounterResponse {
128    /// Status word (0x0000 = ready, 0xFFFF = busy).
129    pub status: u16,
130    /// Event counter value.
131    pub event_count: u16,
132}
133
134impl GetCommEventCounterResponse {
135    /// Decode from the data bytes following the function code.
136    ///
137    /// # Errors
138    ///
139    /// Returns `DecodeError::Truncated` if `data` is too short.
140    pub fn decode(data: &[u8]) -> Result<Self, DecodeError> {
141        DecodeError::check_exact_len(data, 4)?;
142        let status = u16::from_be_bytes([data[0], data[1]]);
143        let event_count = u16::from_be_bytes([data[2], data[3]]);
144        Ok(Self {
145            status,
146            event_count,
147        })
148    }
149}
150
151impl Encode for GetCommEventCounterResponse {
152    fn encode_into(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
153        let len = self.encoded_len();
154        if buf.len() < len {
155            return Err(EncodeError::BufferTooSmall {
156                required: len,
157                available: buf.len(),
158            });
159        }
160        EncodeError::check_pdu_len(len)?;
161        buf[0] = FunctionCode::GetCommEventCounter.code();
162        let st = self.status.to_be_bytes();
163        buf[1] = st[0];
164        buf[2] = st[1];
165        let ec = self.event_count.to_be_bytes();
166        buf[3] = ec[0];
167        buf[4] = ec[1];
168        Ok(len)
169    }
170
171    fn encoded_len(&self) -> usize {
172        1 + 4
173    }
174}
175
176/// Response to a Get Comm Event Log request (FC 0x0C).
177#[derive(Debug)]
178pub struct GetCommEventLogResponse<'buf> {
179    /// Number of bytes that follow.
180    pub byte_count: u8,
181    /// Status word (0x0000 = ready, 0xFFFF = busy).
182    pub status: u16,
183    /// Event counter value.
184    pub event_count: u16,
185    /// Message counter value.
186    pub message_count: u16,
187    /// Event log bytes.
188    pub events: &'buf [u8],
189}
190
191impl<'buf> GetCommEventLogResponse<'buf> {
192    /// Decode from the data bytes following the function code.
193    ///
194    /// # Errors
195    ///
196    /// Returns `DecodeError::Truncated` if `data` is too short.
197    /// Returns `DecodeError::ByteCountMismatch` if the declared byte count
198    /// does not match the remaining data length.
199    pub fn decode(data: &'buf [u8]) -> Result<Self, DecodeError> {
200        if data.len() < 7 {
201            return Err(DecodeError::Truncated {
202                expected: 7,
203                actual: data.len(),
204            });
205        }
206        let byte_count = data[0];
207        let status = u16::from_be_bytes([data[1], data[2]]);
208        let event_count = u16::from_be_bytes([data[3], data[4]]);
209        let message_count = u16::from_be_bytes([data[5], data[6]]);
210        let events = &data[7..];
211        // byte_count covers status(2) + event_count(2) + message_count(2) + events.
212        // Must be at least 6 to account for the three fixed 16-bit fields.
213        if byte_count < 6 {
214            return Err(DecodeError::ByteCountMismatch {
215                declared: usize::from(byte_count),
216                actual: 6 + events.len(),
217            });
218        }
219        let declared_events_len = usize::from(byte_count) - 6;
220        if events.len() != declared_events_len {
221            return Err(DecodeError::ByteCountMismatch {
222                declared: usize::from(byte_count),
223                actual: 6 + events.len(),
224            });
225        }
226        Ok(Self {
227            byte_count,
228            status,
229            event_count,
230            message_count,
231            events,
232        })
233    }
234}
235
236impl Encode for GetCommEventLogResponse<'_> {
237    fn encode_into(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
238        let len = self.encoded_len();
239        if buf.len() < len {
240            return Err(EncodeError::BufferTooSmall {
241                required: len,
242                available: buf.len(),
243            });
244        }
245        EncodeError::check_byte_count(usize::from(self.byte_count), 6 + self.events.len())?;
246        EncodeError::check_pdu_len(len)?;
247        buf[0] = FunctionCode::GetCommEventLog.code();
248        buf[1] = self.byte_count;
249        let st = self.status.to_be_bytes();
250        buf[2] = st[0];
251        buf[3] = st[1];
252        let ec = self.event_count.to_be_bytes();
253        buf[4] = ec[0];
254        buf[5] = ec[1];
255        let mc = self.message_count.to_be_bytes();
256        buf[6] = mc[0];
257        buf[7] = mc[1];
258        buf[8..8 + self.events.len()].copy_from_slice(self.events);
259        Ok(len)
260    }
261
262    fn encoded_len(&self) -> usize {
263        // FC + byte_count + status(2) + event_count(2) + message_count(2) + events
264        1 + 1 + 6 + self.events.len()
265    }
266}
267
268/// Response to a Report Server ID request (FC 0x11).
269#[derive(Debug)]
270pub struct ReportServerIdResponse<'buf> {
271    /// Number of data bytes that follow.
272    pub byte_count: u8,
273    /// Device-specific identification data.
274    pub data: &'buf [u8],
275}
276
277impl<'buf> ReportServerIdResponse<'buf> {
278    /// Decode from the data bytes following the function code.
279    ///
280    /// # Errors
281    ///
282    /// Returns `DecodeError::Truncated` if `data` is too short.
283    /// Returns `DecodeError::ByteCountMismatch` if the declared byte count
284    /// does not match the remaining data length.
285    pub fn decode(data: &'buf [u8]) -> Result<Self, DecodeError> {
286        if data.is_empty() {
287            return Err(DecodeError::Truncated {
288                expected: 1,
289                actual: 0,
290            });
291        }
292        let byte_count = data[0];
293        let payload = &data[1..];
294        if payload.len() != usize::from(byte_count) {
295            return Err(DecodeError::ByteCountMismatch {
296                declared: usize::from(byte_count),
297                actual: payload.len(),
298            });
299        }
300        Ok(Self {
301            byte_count,
302            data: payload,
303        })
304    }
305}
306
307impl Encode for ReportServerIdResponse<'_> {
308    fn encode_into(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
309        let len = self.encoded_len();
310        if buf.len() < len {
311            return Err(EncodeError::BufferTooSmall {
312                required: len,
313                available: buf.len(),
314            });
315        }
316        EncodeError::check_byte_count(usize::from(self.byte_count), self.data.len())?;
317        EncodeError::check_pdu_len(len)?;
318        buf[0] = FunctionCode::ReportServerId.code();
319        buf[1] = self.byte_count;
320        buf[2..2 + usize::from(self.byte_count)].copy_from_slice(self.data);
321        Ok(len)
322    }
323
324    fn encoded_len(&self) -> usize {
325        1 + 1 + usize::from(self.byte_count)
326    }
327}