rusty_modbus_codec/request/
file.rs1use rusty_modbus_types::FunctionCode;
4
5use crate::error::{DecodeError, EncodeError};
6use crate::request::Encode;
7
8const READ_FILE_RECORD_MIN_BYTE_COUNT: usize = 0x07;
9const READ_FILE_RECORD_MAX_BYTE_COUNT: usize = 0xF5;
10const WRITE_FILE_RECORD_MIN_BYTE_COUNT: usize = 0x09;
11const WRITE_FILE_RECORD_MAX_BYTE_COUNT: usize = 0xFB;
12const FILE_RECORD_REFERENCE_TYPE: u8 = 0x06;
13const MIN_FILE_NUMBER: u16 = 0x0001;
14const MAX_RECORD_NUMBER: u16 = 0x270F;
15const RECORD_COUNT: usize = 0x2710;
16
17fn check_file_record_byte_count(
18 byte_count: u8,
19 minimum: usize,
20 maximum: usize,
21) -> Result<(), DecodeError> {
22 let count = usize::from(byte_count);
23 if (minimum..=maximum).contains(&count) {
24 Ok(())
25 } else {
26 Err(DecodeError::ByteCountOutOfRange {
27 count,
28 minimum,
29 maximum,
30 })
31 }
32}
33
34fn check_file_record_range(
35 file_number: u16,
36 record_number: u16,
37 record_length: u16,
38) -> Result<(), DecodeError> {
39 let end = usize::from(record_number)
40 .checked_add(usize::from(record_length))
41 .ok_or(DecodeError::FileRecordOutOfRange {
42 file_number,
43 record_number,
44 record_length,
45 })?;
46 if file_number < MIN_FILE_NUMBER
47 || record_length == 0
48 || record_number > MAX_RECORD_NUMBER
49 || end > RECORD_COUNT
50 {
51 return Err(DecodeError::FileRecordOutOfRange {
52 file_number,
53 record_number,
54 record_length,
55 });
56 }
57 Ok(())
58}
59
60fn check_file_record_range_encode(
61 file_number: u16,
62 record_number: u16,
63 record_length: u16,
64) -> Result<(), EncodeError> {
65 let end = usize::from(record_number)
66 .checked_add(usize::from(record_length))
67 .ok_or(EncodeError::FileRecordOutOfRange {
68 file_number,
69 record_number,
70 record_length,
71 })?;
72 if file_number < MIN_FILE_NUMBER
73 || record_length == 0
74 || record_number > MAX_RECORD_NUMBER
75 || end > RECORD_COUNT
76 {
77 return Err(EncodeError::FileRecordOutOfRange {
78 file_number,
79 record_number,
80 record_length,
81 });
82 }
83 Ok(())
84}
85
86fn validate_read_file_sub_requests(sub_requests: &[u8]) -> Result<(), DecodeError> {
87 if !sub_requests.len().is_multiple_of(7) {
88 return Err(DecodeError::InvalidFileRecordLength {
89 length: sub_requests.len(),
90 });
91 }
92 for chunk in sub_requests.chunks_exact(7) {
93 FileSubRequest::decode(chunk)?;
94 }
95 Ok(())
96}
97
98fn validate_read_file_sub_requests_encode(sub_requests: &[u8]) -> Result<(), EncodeError> {
99 if !sub_requests.len().is_multiple_of(7) {
100 return Err(EncodeError::InvalidFileRecordLength {
101 length: sub_requests.len(),
102 });
103 }
104 for chunk in sub_requests.chunks_exact(7) {
105 validate_file_sub_request_encode(chunk)?;
106 }
107 Ok(())
108}
109
110fn validate_write_file_sub_requests(sub_requests: &[u8]) -> Result<(), DecodeError> {
111 let mut remaining = sub_requests;
112 while !remaining.is_empty() {
113 if remaining.len() < 7 {
114 return Err(DecodeError::InvalidFileRecordLength {
115 length: remaining.len(),
116 });
117 }
118 let sub = FileSubRequest::decode(&remaining[..7])?;
119 let value_bytes = usize::from(sub.record_length) * 2;
120 let group_len = 7 + value_bytes;
121 if remaining.len() < group_len {
122 return Err(DecodeError::ByteCountMismatch {
123 declared: group_len,
124 actual: remaining.len(),
125 });
126 }
127 remaining = &remaining[group_len..];
128 }
129 Ok(())
130}
131
132fn validate_write_file_sub_requests_encode(sub_requests: &[u8]) -> Result<(), EncodeError> {
133 let mut remaining = sub_requests;
134 while !remaining.is_empty() {
135 if remaining.len() < 7 {
136 return Err(EncodeError::InvalidFileRecordLength {
137 length: remaining.len(),
138 });
139 }
140 let sub = validate_file_sub_request_encode(&remaining[..7])?;
141 let value_bytes = usize::from(sub.record_length) * 2;
142 let group_len = 7 + value_bytes;
143 if remaining.len() < group_len {
144 return Err(EncodeError::ByteCountMismatch {
145 declared: group_len,
146 actual: remaining.len(),
147 });
148 }
149 remaining = &remaining[group_len..];
150 }
151 Ok(())
152}
153
154fn validate_file_sub_request_encode(data: &[u8]) -> Result<FileSubRequest, EncodeError> {
155 if data.len() < 7 {
156 return Err(EncodeError::InvalidFileRecordLength { length: data.len() });
157 }
158 let reference_type = data[0];
159 if reference_type != FILE_RECORD_REFERENCE_TYPE {
160 return Err(EncodeError::InvalidReferenceType(reference_type));
161 }
162 let file_number = u16::from_be_bytes([data[1], data[2]]);
163 let record_number = u16::from_be_bytes([data[3], data[4]]);
164 let record_length = u16::from_be_bytes([data[5], data[6]]);
165 check_file_record_range_encode(file_number, record_number, record_length)?;
166 Ok(FileSubRequest {
167 reference_type,
168 file_number,
169 record_number,
170 record_length,
171 })
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq)]
178pub struct FileSubRequest {
179 pub reference_type: u8,
181 pub file_number: u16,
183 pub record_number: u16,
185 pub record_length: u16,
187}
188
189impl FileSubRequest {
190 pub fn decode(data: &[u8]) -> Result<Self, DecodeError> {
200 DecodeError::check_exact_len(data, 7)?;
201 let reference_type = data[0];
202 if reference_type != FILE_RECORD_REFERENCE_TYPE {
203 return Err(DecodeError::InvalidReferenceType(reference_type));
204 }
205 let file_number = u16::from_be_bytes([data[1], data[2]]);
206 let record_number = u16::from_be_bytes([data[3], data[4]]);
207 let record_length = u16::from_be_bytes([data[5], data[6]]);
208 check_file_record_range(file_number, record_number, record_length)?;
209 Ok(Self {
210 reference_type,
211 file_number,
212 record_number,
213 record_length,
214 })
215 }
216}
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq)]
222pub struct ReadFileRecordRequest<'buf> {
223 pub byte_count: u8,
225 pub sub_requests: &'buf [u8],
227}
228
229impl<'buf> ReadFileRecordRequest<'buf> {
230 pub fn decode(data: &'buf [u8]) -> Result<Self, DecodeError> {
238 if data.is_empty() {
239 return Err(DecodeError::Truncated {
240 expected: 1,
241 actual: 0,
242 });
243 }
244 let byte_count = data[0];
245 check_file_record_byte_count(
246 byte_count,
247 READ_FILE_RECORD_MIN_BYTE_COUNT,
248 READ_FILE_RECORD_MAX_BYTE_COUNT,
249 )?;
250 let remaining = data.len() - 1;
251 if byte_count as usize != remaining {
252 return Err(DecodeError::ByteCountMismatch {
253 declared: byte_count as usize,
254 actual: remaining,
255 });
256 }
257 let sub_requests = &data[1..];
258 validate_read_file_sub_requests(sub_requests)?;
259 Ok(Self {
260 byte_count,
261 sub_requests,
262 })
263 }
264}
265
266impl Encode for ReadFileRecordRequest<'_> {
267 fn encode_into(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
268 let len = self.encoded_len();
269 if buf.len() < len {
270 return Err(EncodeError::BufferTooSmall {
271 required: len,
272 available: buf.len(),
273 });
274 }
275 EncodeError::check_byte_count_range(
276 usize::from(self.byte_count),
277 READ_FILE_RECORD_MIN_BYTE_COUNT,
278 READ_FILE_RECORD_MAX_BYTE_COUNT,
279 )?;
280 EncodeError::check_byte_count(usize::from(self.byte_count), self.sub_requests.len())?;
281 validate_read_file_sub_requests_encode(self.sub_requests)?;
282 EncodeError::check_pdu_len(len)?;
283 buf[0] = FunctionCode::ReadFileRecord.code();
284 buf[1] = self.byte_count;
285 buf[2..2 + self.sub_requests.len()].copy_from_slice(self.sub_requests);
286 Ok(len)
287 }
288
289 fn encoded_len(&self) -> usize {
290 2 + self.sub_requests.len()
292 }
293}
294
295#[derive(Debug, Clone, Copy, PartialEq, Eq)]
299pub struct WriteFileRecordRequest<'buf> {
300 pub byte_count: u8,
302 pub sub_requests: &'buf [u8],
304}
305
306impl<'buf> WriteFileRecordRequest<'buf> {
307 pub fn decode(data: &'buf [u8]) -> Result<Self, DecodeError> {
315 if data.is_empty() {
316 return Err(DecodeError::Truncated {
317 expected: 1,
318 actual: 0,
319 });
320 }
321 let byte_count = data[0];
322 check_file_record_byte_count(
323 byte_count,
324 WRITE_FILE_RECORD_MIN_BYTE_COUNT,
325 WRITE_FILE_RECORD_MAX_BYTE_COUNT,
326 )?;
327 let remaining = data.len() - 1;
328 if byte_count as usize != remaining {
329 return Err(DecodeError::ByteCountMismatch {
330 declared: byte_count as usize,
331 actual: remaining,
332 });
333 }
334 let sub_requests = &data[1..];
335 validate_write_file_sub_requests(sub_requests)?;
336 Ok(Self {
337 byte_count,
338 sub_requests,
339 })
340 }
341}
342
343impl Encode for WriteFileRecordRequest<'_> {
344 fn encode_into(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
345 let len = self.encoded_len();
346 if buf.len() < len {
347 return Err(EncodeError::BufferTooSmall {
348 required: len,
349 available: buf.len(),
350 });
351 }
352 EncodeError::check_byte_count_range(
353 usize::from(self.byte_count),
354 WRITE_FILE_RECORD_MIN_BYTE_COUNT,
355 WRITE_FILE_RECORD_MAX_BYTE_COUNT,
356 )?;
357 EncodeError::check_byte_count(usize::from(self.byte_count), self.sub_requests.len())?;
358 validate_write_file_sub_requests_encode(self.sub_requests)?;
359 EncodeError::check_pdu_len(len)?;
360 buf[0] = FunctionCode::WriteFileRecord.code();
361 buf[1] = self.byte_count;
362 buf[2..2 + self.sub_requests.len()].copy_from_slice(self.sub_requests);
363 Ok(len)
364 }
365
366 fn encoded_len(&self) -> usize {
367 2 + self.sub_requests.len()
369 }
370}