Skip to main content

rusty_modbus_codec/response/
file.rs

1//! Response types for file record function codes (FC 14, 15).
2
3use crate::error::{DecodeError, EncodeError};
4use crate::request::Encode;
5use rusty_modbus_types::FunctionCode;
6
7const READ_FILE_RECORD_RESPONSE_MIN_BYTE_COUNT: usize = 0x04;
8const READ_FILE_RECORD_RESPONSE_MAX_BYTE_COUNT: usize = 0xFA;
9const WRITE_FILE_RECORD_MIN_BYTE_COUNT: usize = 0x09;
10const WRITE_FILE_RECORD_MAX_BYTE_COUNT: usize = 0xFB;
11const FILE_RECORD_REFERENCE_TYPE: u8 = 0x06;
12const MIN_FILE_NUMBER: u16 = 0x0001;
13const MAX_RECORD_NUMBER: u16 = 0x270F;
14const RECORD_COUNT: usize = 0x2710;
15
16fn check_file_record_byte_count(
17    byte_count: u8,
18    minimum: usize,
19    maximum: usize,
20) -> Result<(), DecodeError> {
21    let count = usize::from(byte_count);
22    if (minimum..=maximum).contains(&count) {
23        Ok(())
24    } else {
25        Err(DecodeError::ByteCountOutOfRange {
26            count,
27            minimum,
28            maximum,
29        })
30    }
31}
32
33fn check_file_record_range(
34    file_number: u16,
35    record_number: u16,
36    record_length: u16,
37) -> Result<(), DecodeError> {
38    let end = usize::from(record_number)
39        .checked_add(usize::from(record_length))
40        .ok_or(DecodeError::FileRecordOutOfRange {
41            file_number,
42            record_number,
43            record_length,
44        })?;
45    if file_number < MIN_FILE_NUMBER
46        || record_length == 0
47        || record_number > MAX_RECORD_NUMBER
48        || end > RECORD_COUNT
49    {
50        return Err(DecodeError::FileRecordOutOfRange {
51            file_number,
52            record_number,
53            record_length,
54        });
55    }
56    Ok(())
57}
58
59fn check_file_record_range_encode(
60    file_number: u16,
61    record_number: u16,
62    record_length: u16,
63) -> Result<(), EncodeError> {
64    let end = usize::from(record_number)
65        .checked_add(usize::from(record_length))
66        .ok_or(EncodeError::FileRecordOutOfRange {
67            file_number,
68            record_number,
69            record_length,
70        })?;
71    if file_number < MIN_FILE_NUMBER
72        || record_length == 0
73        || record_number > MAX_RECORD_NUMBER
74        || end > RECORD_COUNT
75    {
76        return Err(EncodeError::FileRecordOutOfRange {
77            file_number,
78            record_number,
79            record_length,
80        });
81    }
82    Ok(())
83}
84
85fn validate_read_file_response_payload(payload: &[u8]) -> Result<(), DecodeError> {
86    let mut remaining = payload;
87    while !remaining.is_empty() {
88        let response_len = usize::from(remaining[0]);
89        if response_len < 3 || response_len % 2 == 0 {
90            return Err(DecodeError::InvalidFileRecordLength {
91                length: response_len,
92            });
93        }
94        let group_len = 1 + response_len;
95        if remaining.len() < group_len {
96            return Err(DecodeError::ByteCountMismatch {
97                declared: group_len,
98                actual: remaining.len(),
99            });
100        }
101        let reference_type = remaining[1];
102        if reference_type != FILE_RECORD_REFERENCE_TYPE {
103            return Err(DecodeError::InvalidReferenceType(reference_type));
104        }
105        remaining = &remaining[group_len..];
106    }
107    Ok(())
108}
109
110fn validate_read_file_response_payload_encode(payload: &[u8]) -> Result<(), EncodeError> {
111    let mut remaining = payload;
112    while !remaining.is_empty() {
113        let response_len = usize::from(remaining[0]);
114        if response_len < 3 || response_len % 2 == 0 {
115            return Err(EncodeError::InvalidFileRecordLength {
116                length: response_len,
117            });
118        }
119        let group_len = 1 + response_len;
120        if remaining.len() < group_len {
121            return Err(EncodeError::ByteCountMismatch {
122                declared: group_len,
123                actual: remaining.len(),
124            });
125        }
126        let reference_type = remaining[1];
127        if reference_type != FILE_RECORD_REFERENCE_TYPE {
128            return Err(EncodeError::InvalidReferenceType(reference_type));
129        }
130        remaining = &remaining[group_len..];
131    }
132    Ok(())
133}
134
135fn validate_write_file_payload(payload: &[u8]) -> Result<(), DecodeError> {
136    let mut remaining = payload;
137    while !remaining.is_empty() {
138        if remaining.len() < 7 {
139            return Err(DecodeError::InvalidFileRecordLength {
140                length: remaining.len(),
141            });
142        }
143        let reference_type = remaining[0];
144        if reference_type != FILE_RECORD_REFERENCE_TYPE {
145            return Err(DecodeError::InvalidReferenceType(reference_type));
146        }
147        let file_number = u16::from_be_bytes([remaining[1], remaining[2]]);
148        let record_number = u16::from_be_bytes([remaining[3], remaining[4]]);
149        let record_length = u16::from_be_bytes([remaining[5], remaining[6]]);
150        check_file_record_range(file_number, record_number, record_length)?;
151        let group_len = 7 + usize::from(record_length) * 2;
152        if remaining.len() < group_len {
153            return Err(DecodeError::ByteCountMismatch {
154                declared: group_len,
155                actual: remaining.len(),
156            });
157        }
158        remaining = &remaining[group_len..];
159    }
160    Ok(())
161}
162
163fn validate_write_file_payload_encode(payload: &[u8]) -> Result<(), EncodeError> {
164    let mut remaining = payload;
165    while !remaining.is_empty() {
166        if remaining.len() < 7 {
167            return Err(EncodeError::InvalidFileRecordLength {
168                length: remaining.len(),
169            });
170        }
171        let reference_type = remaining[0];
172        if reference_type != FILE_RECORD_REFERENCE_TYPE {
173            return Err(EncodeError::InvalidReferenceType(reference_type));
174        }
175        let file_number = u16::from_be_bytes([remaining[1], remaining[2]]);
176        let record_number = u16::from_be_bytes([remaining[3], remaining[4]]);
177        let record_length = u16::from_be_bytes([remaining[5], remaining[6]]);
178        check_file_record_range_encode(file_number, record_number, record_length)?;
179        let group_len = 7 + usize::from(record_length) * 2;
180        if remaining.len() < group_len {
181            return Err(EncodeError::ByteCountMismatch {
182                declared: group_len,
183                actual: remaining.len(),
184            });
185        }
186        remaining = &remaining[group_len..];
187    }
188    Ok(())
189}
190
191/// Response to a Read File Record request (FC 0x14).
192#[derive(Debug)]
193pub struct ReadFileRecordResponse<'buf> {
194    /// Total number of data bytes that follow.
195    pub byte_count: u8,
196    /// Raw sub-request response data.
197    pub data: &'buf [u8],
198}
199
200impl<'buf> ReadFileRecordResponse<'buf> {
201    /// Decode from the data bytes following the function code.
202    ///
203    /// # Errors
204    ///
205    /// Returns `DecodeError::Truncated` if `data` is too short.
206    /// Returns `DecodeError::ByteCountMismatch` if the declared byte count
207    /// does not match the remaining data length.
208    pub fn decode(data: &'buf [u8]) -> Result<Self, DecodeError> {
209        if data.is_empty() {
210            return Err(DecodeError::Truncated {
211                expected: 1,
212                actual: 0,
213            });
214        }
215        let byte_count = data[0];
216        check_file_record_byte_count(
217            byte_count,
218            READ_FILE_RECORD_RESPONSE_MIN_BYTE_COUNT,
219            READ_FILE_RECORD_RESPONSE_MAX_BYTE_COUNT,
220        )?;
221        let payload = &data[1..];
222        if payload.len() != usize::from(byte_count) {
223            return Err(DecodeError::ByteCountMismatch {
224                declared: usize::from(byte_count),
225                actual: payload.len(),
226            });
227        }
228        validate_read_file_response_payload(payload)?;
229        Ok(Self {
230            byte_count,
231            data: payload,
232        })
233    }
234}
235
236impl Encode for ReadFileRecordResponse<'_> {
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_range(
246            usize::from(self.byte_count),
247            READ_FILE_RECORD_RESPONSE_MIN_BYTE_COUNT,
248            READ_FILE_RECORD_RESPONSE_MAX_BYTE_COUNT,
249        )?;
250        EncodeError::check_byte_count(usize::from(self.byte_count), self.data.len())?;
251        validate_read_file_response_payload_encode(self.data)?;
252        EncodeError::check_pdu_len(len)?;
253        buf[0] = FunctionCode::ReadFileRecord.code();
254        buf[1] = self.byte_count;
255        buf[2..2 + usize::from(self.byte_count)].copy_from_slice(self.data);
256        Ok(len)
257    }
258
259    fn encoded_len(&self) -> usize {
260        1 + 1 + usize::from(self.byte_count)
261    }
262}
263
264/// Response to a Write File Record request (FC 0x15).
265#[derive(Debug)]
266pub struct WriteFileRecordResponse<'buf> {
267    /// Total number of data bytes that follow.
268    pub byte_count: u8,
269    /// Raw sub-request response data.
270    pub data: &'buf [u8],
271}
272
273impl<'buf> WriteFileRecordResponse<'buf> {
274    /// Decode from the data bytes following the function code.
275    ///
276    /// # Errors
277    ///
278    /// Returns `DecodeError::Truncated` if `data` is too short.
279    /// Returns `DecodeError::ByteCountMismatch` if the declared byte count
280    /// does not match the remaining data length.
281    pub fn decode(data: &'buf [u8]) -> Result<Self, DecodeError> {
282        if data.is_empty() {
283            return Err(DecodeError::Truncated {
284                expected: 1,
285                actual: 0,
286            });
287        }
288        let byte_count = data[0];
289        check_file_record_byte_count(
290            byte_count,
291            WRITE_FILE_RECORD_MIN_BYTE_COUNT,
292            WRITE_FILE_RECORD_MAX_BYTE_COUNT,
293        )?;
294        let payload = &data[1..];
295        if payload.len() != usize::from(byte_count) {
296            return Err(DecodeError::ByteCountMismatch {
297                declared: usize::from(byte_count),
298                actual: payload.len(),
299            });
300        }
301        validate_write_file_payload(payload)?;
302        Ok(Self {
303            byte_count,
304            data: payload,
305        })
306    }
307}
308
309impl Encode for WriteFileRecordResponse<'_> {
310    fn encode_into(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
311        let len = self.encoded_len();
312        if buf.len() < len {
313            return Err(EncodeError::BufferTooSmall {
314                required: len,
315                available: buf.len(),
316            });
317        }
318        EncodeError::check_byte_count_range(
319            usize::from(self.byte_count),
320            WRITE_FILE_RECORD_MIN_BYTE_COUNT,
321            WRITE_FILE_RECORD_MAX_BYTE_COUNT,
322        )?;
323        EncodeError::check_byte_count(usize::from(self.byte_count), self.data.len())?;
324        validate_write_file_payload_encode(self.data)?;
325        EncodeError::check_pdu_len(len)?;
326        buf[0] = FunctionCode::WriteFileRecord.code();
327        buf[1] = self.byte_count;
328        buf[2..2 + usize::from(self.byte_count)].copy_from_slice(self.data);
329        Ok(len)
330    }
331
332    fn encoded_len(&self) -> usize {
333        1 + 1 + usize::from(self.byte_count)
334    }
335}