rusty_modbus_codec/response/
file.rs1use 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#[derive(Debug)]
193pub struct ReadFileRecordResponse<'buf> {
194 pub byte_count: u8,
196 pub data: &'buf [u8],
198}
199
200impl<'buf> ReadFileRecordResponse<'buf> {
201 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#[derive(Debug)]
266pub struct WriteFileRecordResponse<'buf> {
267 pub byte_count: u8,
269 pub data: &'buf [u8],
271}
272
273impl<'buf> WriteFileRecordResponse<'buf> {
274 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}