1use rusty_modbus_types::{
11 ExceptionCode, MAX_READ_COILS, MAX_READ_DISCRETE_INPUTS, MAX_READ_REGISTERS,
12 MAX_RW_READ_REGISTERS, MAX_RW_WRITE_REGISTERS, MAX_WRITE_COILS, MAX_WRITE_REGISTERS,
13};
14
15pub fn validate_read_coils(address: u16, quantity: u16) -> Result<(), ExceptionCode> {
24 if quantity == 0 || quantity > MAX_READ_COILS {
25 return Err(ExceptionCode::IllegalDataValue);
26 }
27 validate_address_range(address, quantity)?;
28 Ok(())
29}
30
31pub fn validate_read_discrete_inputs(address: u16, quantity: u16) -> Result<(), ExceptionCode> {
40 if quantity == 0 || quantity > MAX_READ_DISCRETE_INPUTS {
41 return Err(ExceptionCode::IllegalDataValue);
42 }
43 validate_address_range(address, quantity)?;
44 Ok(())
45}
46
47pub fn validate_read_registers(address: u16, quantity: u16) -> Result<(), ExceptionCode> {
56 if quantity == 0 || quantity > MAX_READ_REGISTERS {
57 return Err(ExceptionCode::IllegalDataValue);
58 }
59 validate_address_range(address, quantity)?;
60 Ok(())
61}
62
63pub fn validate_write_single_coil(coil_value: u16) -> Result<(), ExceptionCode> {
71 if coil_value != 0x0000 && coil_value != 0xFF00 {
72 return Err(ExceptionCode::IllegalDataValue);
73 }
74 Ok(())
75}
76
77pub fn validate_write_coils(
86 address: u16,
87 quantity: u16,
88 byte_count: u8,
89) -> Result<(), ExceptionCode> {
90 if quantity == 0 || quantity > MAX_WRITE_COILS {
91 return Err(ExceptionCode::IllegalDataValue);
92 }
93 let expected_bytes = quantity.div_ceil(8);
94 if u16::from(byte_count) != expected_bytes {
95 return Err(ExceptionCode::IllegalDataValue);
96 }
97 validate_address_range(address, quantity)?;
98 Ok(())
99}
100
101pub fn validate_write_registers(
110 address: u16,
111 quantity: u16,
112 byte_count: u8,
113) -> Result<(), ExceptionCode> {
114 if quantity == 0 || quantity > MAX_WRITE_REGISTERS {
115 return Err(ExceptionCode::IllegalDataValue);
116 }
117 if u16::from(byte_count) != quantity * 2 {
118 return Err(ExceptionCode::IllegalDataValue);
119 }
120 validate_address_range(address, quantity)?;
121 Ok(())
122}
123
124pub fn validate_mask_write_address(address: u16) -> Result<(), ExceptionCode> {
133 validate_address_range(address, 1)
134}
135
136pub fn validate_read_write_registers(
145 read_address: u16,
146 read_quantity: u16,
147 write_address: u16,
148 write_quantity: u16,
149 write_byte_count: u8,
150) -> Result<(), ExceptionCode> {
151 if read_quantity == 0 || read_quantity > MAX_RW_READ_REGISTERS {
152 return Err(ExceptionCode::IllegalDataValue);
153 }
154 if write_quantity == 0 || write_quantity > MAX_RW_WRITE_REGISTERS {
155 return Err(ExceptionCode::IllegalDataValue);
156 }
157 if u16::from(write_byte_count) != write_quantity * 2 {
158 return Err(ExceptionCode::IllegalDataValue);
159 }
160 validate_address_range(read_address, read_quantity)?;
161 validate_address_range(write_address, write_quantity)?;
162 Ok(())
163}
164
165fn validate_address_range(address: u16, quantity: u16) -> Result<(), ExceptionCode> {
167 if u32::from(address) + u32::from(quantity) > 0x10000 {
168 return Err(ExceptionCode::IllegalDataAddress);
169 }
170 Ok(())
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
180 fn read_coils_valid() {
181 assert!(validate_read_coils(0, 1).is_ok());
182 assert!(validate_read_coils(0, 2000).is_ok());
183 assert!(validate_read_coils(100, 100).is_ok());
184 }
185
186 #[test]
187 fn read_coils_quantity_zero() {
188 assert_eq!(
189 validate_read_coils(0, 0),
190 Err(ExceptionCode::IllegalDataValue)
191 );
192 }
193
194 #[test]
195 fn read_coils_quantity_too_large() {
196 assert_eq!(
197 validate_read_coils(0, 2001),
198 Err(ExceptionCode::IllegalDataValue)
199 );
200 }
201
202 #[test]
203 fn read_coils_address_overflow() {
204 assert_eq!(
205 validate_read_coils(0xFFFF, 2),
206 Err(ExceptionCode::IllegalDataAddress)
207 );
208 }
209
210 #[test]
211 fn quantity_checked_before_address() {
212 assert_eq!(
215 validate_read_coils(0xFFFF, 5000),
216 Err(ExceptionCode::IllegalDataValue)
217 );
218 }
219
220 #[test]
223 fn read_registers_valid() {
224 assert!(validate_read_registers(0, 1).is_ok());
225 assert!(validate_read_registers(0, 125).is_ok());
226 }
227
228 #[test]
229 fn read_registers_quantity_out_of_range() {
230 assert_eq!(
231 validate_read_registers(0, 126),
232 Err(ExceptionCode::IllegalDataValue)
233 );
234 }
235
236 #[test]
239 fn write_single_coil_valid_on() {
240 assert!(validate_write_single_coil(0xFF00).is_ok());
241 }
242
243 #[test]
244 fn write_single_coil_valid_off() {
245 assert!(validate_write_single_coil(0x0000).is_ok());
246 }
247
248 #[test]
249 fn write_single_coil_invalid_value() {
250 assert_eq!(
251 validate_write_single_coil(0x0001),
252 Err(ExceptionCode::IllegalDataValue)
253 );
254 assert_eq!(
255 validate_write_single_coil(0xFF01),
256 Err(ExceptionCode::IllegalDataValue)
257 );
258 }
259
260 #[test]
263 fn write_coils_valid() {
264 assert!(validate_write_coils(0, 8, 1).is_ok());
265 assert!(validate_write_coils(0, 9, 2).is_ok());
266 assert!(validate_write_coils(0, 1968, 246).is_ok());
267 }
268
269 #[test]
270 fn write_coils_byte_count_mismatch() {
271 assert_eq!(
272 validate_write_coils(0, 8, 2),
273 Err(ExceptionCode::IllegalDataValue)
274 );
275 }
276
277 #[test]
280 fn write_registers_valid() {
281 assert!(validate_write_registers(0, 1, 2).is_ok());
282 assert!(validate_write_registers(0, 123, 246).is_ok());
283 }
284
285 #[test]
286 fn write_registers_byte_count_mismatch() {
287 assert_eq!(
288 validate_write_registers(0, 1, 3),
289 Err(ExceptionCode::IllegalDataValue)
290 );
291 }
292
293 #[test]
296 fn mask_write_address_valid() {
297 assert!(validate_mask_write_address(0).is_ok());
298 assert!(validate_mask_write_address(0xFFFE).is_ok());
299 }
300
301 #[test]
302 fn mask_write_address_max_valid() {
303 assert!(validate_mask_write_address(0xFFFF).is_ok());
305 }
306
307 #[test]
310 fn read_write_registers_valid() {
311 assert!(validate_read_write_registers(0, 1, 0, 1, 2).is_ok());
312 assert!(validate_read_write_registers(0, 125, 0, 121, 242).is_ok());
313 }
314
315 #[test]
316 fn read_write_registers_read_overflow() {
317 assert_eq!(
318 validate_read_write_registers(0xFFFF, 2, 0, 1, 2),
319 Err(ExceptionCode::IllegalDataAddress)
320 );
321 }
322}