1use crate::function::{Exception, Function};
4use crate::pdu::Pdu;
5use crate::response::{Coils, Registers};
6
7#[derive(Clone, Copy, Debug)]
32pub enum Request<'a> {
33 ReadCoils {
35 start: u16,
37 quantity: u16,
39 },
40 ReadDiscreteInputs {
42 start: u16,
44 quantity: u16,
46 },
47 ReadHoldingRegisters {
49 start: u16,
51 quantity: u16,
53 },
54 ReadInputRegisters {
56 start: u16,
58 quantity: u16,
60 },
61 WriteSingleCoil {
63 address: u16,
65 on: bool,
67 },
68 WriteSingleRegister {
70 address: u16,
72 value: u16,
74 },
75 WriteMultipleCoils {
77 start: u16,
79 values: Coils<'a>,
81 },
82 WriteMultipleRegisters {
84 start: u16,
86 values: Registers<'a>,
88 },
89}
90
91impl PartialEq for Request<'_> {
92 fn eq(&self, other: &Self) -> bool {
93 use Request::*;
94 match (self, other) {
95 (
96 ReadCoils {
97 start: a,
98 quantity: b,
99 },
100 ReadCoils {
101 start: c,
102 quantity: d,
103 },
104 )
105 | (
106 ReadDiscreteInputs {
107 start: a,
108 quantity: b,
109 },
110 ReadDiscreteInputs {
111 start: c,
112 quantity: d,
113 },
114 )
115 | (
116 ReadHoldingRegisters {
117 start: a,
118 quantity: b,
119 },
120 ReadHoldingRegisters {
121 start: c,
122 quantity: d,
123 },
124 )
125 | (
126 ReadInputRegisters {
127 start: a,
128 quantity: b,
129 },
130 ReadInputRegisters {
131 start: c,
132 quantity: d,
133 },
134 ) => a == c && b == d,
135 (WriteSingleCoil { address: a, on: b }, WriteSingleCoil { address: c, on: d }) => {
136 a == c && b == d
137 }
138 (
139 WriteSingleRegister {
140 address: a,
141 value: b,
142 },
143 WriteSingleRegister {
144 address: c,
145 value: d,
146 },
147 ) => a == c && b == d,
148 (
149 WriteMultipleCoils {
150 start: a,
151 values: b,
152 },
153 WriteMultipleCoils {
154 start: c,
155 values: d,
156 },
157 ) => a == c && b.eq(*d),
158 (
159 WriteMultipleRegisters {
160 start: a,
161 values: b,
162 },
163 WriteMultipleRegisters {
164 start: c,
165 values: d,
166 },
167 ) => a == c && b.eq(*d),
168 _ => false,
169 }
170 }
171}
172
173fn word(bytes: &[u8], at: usize) -> u16 {
174 u16::from_be_bytes([bytes[at], bytes[at + 1]])
175}
176
177impl<'a> Request<'a> {
178 pub fn parse(pdu: &'a [u8]) -> Result<Request<'a>, Exception> {
195 let (&code, data) = pdu.split_first().ok_or(Exception::IllegalFunction)?;
196 let function = Function::from_code(code).ok_or(Exception::IllegalFunction)?;
197 if data.len() < 4 {
198 return Err(Exception::IllegalDataValue);
199 }
200 let first = word(data, 0);
201 let second = word(data, 2);
202 let within = |quantity: u16, most: usize| {
203 if quantity == 0 || usize::from(quantity) > most {
204 Err(Exception::IllegalDataValue)
205 } else {
206 Ok(quantity)
207 }
208 };
209 let fixed = |request: Request<'a>| {
210 if data.len() == 4 {
211 Ok(request)
212 } else {
213 Err(Exception::IllegalDataValue)
214 }
215 };
216 match function {
217 Function::ReadCoils => fixed(Request::ReadCoils {
218 start: first,
219 quantity: within(second, Pdu::MAX_READ_BITS)?,
220 }),
221 Function::ReadDiscreteInputs => fixed(Request::ReadDiscreteInputs {
222 start: first,
223 quantity: within(second, Pdu::MAX_READ_BITS)?,
224 }),
225 Function::ReadHoldingRegisters => fixed(Request::ReadHoldingRegisters {
226 start: first,
227 quantity: within(second, Pdu::MAX_READ_REGISTERS)?,
228 }),
229 Function::ReadInputRegisters => fixed(Request::ReadInputRegisters {
230 start: first,
231 quantity: within(second, Pdu::MAX_READ_REGISTERS)?,
232 }),
233 Function::WriteSingleCoil => {
234 let on = match second {
235 0xFF00 => true,
236 0x0000 => false,
237 _ => return Err(Exception::IllegalDataValue),
238 };
239 fixed(Request::WriteSingleCoil { address: first, on })
240 }
241 Function::WriteSingleRegister => fixed(Request::WriteSingleRegister {
242 address: first,
243 value: second,
244 }),
245 Function::WriteMultipleCoils => {
246 let quantity = within(second, Pdu::MAX_WRITE_COILS)?;
247 let bytes = Self::counted(data, usize::from(quantity).div_ceil(8))?;
248 Ok(Request::WriteMultipleCoils {
249 start: first,
250 values: Coils::over(bytes, usize::from(quantity)),
251 })
252 }
253 Function::WriteMultipleRegisters => {
254 let quantity = within(second, Pdu::MAX_WRITE_REGISTERS)?;
255 let bytes = Self::counted(data, usize::from(quantity) * 2)?;
256 Ok(Request::WriteMultipleRegisters {
257 start: first,
258 values: Registers::over(bytes),
259 })
260 }
261 }
262 }
263
264 fn counted(data: &'a [u8], needed: usize) -> Result<&'a [u8], Exception> {
267 match data.get(4) {
268 Some(&count) if usize::from(count) == needed && data.len() == 5 + needed => {
269 Ok(&data[5..])
270 }
271 _ => Err(Exception::IllegalDataValue),
272 }
273 }
274
275 pub fn function(&self) -> Function {
277 match self {
278 Request::ReadCoils { .. } => Function::ReadCoils,
279 Request::ReadDiscreteInputs { .. } => Function::ReadDiscreteInputs,
280 Request::ReadHoldingRegisters { .. } => Function::ReadHoldingRegisters,
281 Request::ReadInputRegisters { .. } => Function::ReadInputRegisters,
282 Request::WriteSingleCoil { .. } => Function::WriteSingleCoil,
283 Request::WriteSingleRegister { .. } => Function::WriteSingleRegister,
284 Request::WriteMultipleCoils { .. } => Function::WriteMultipleCoils,
285 Request::WriteMultipleRegisters { .. } => Function::WriteMultipleRegisters,
286 }
287 }
288
289 pub fn span(&self) -> (u16, usize) {
296 match *self {
297 Request::ReadCoils { start, quantity }
298 | Request::ReadDiscreteInputs { start, quantity }
299 | Request::ReadHoldingRegisters { start, quantity }
300 | Request::ReadInputRegisters { start, quantity } => (start, usize::from(quantity)),
301 Request::WriteSingleCoil { address, .. }
302 | Request::WriteSingleRegister { address, .. } => (address, 1),
303 Request::WriteMultipleCoils { start, values } => (start, values.len()),
304 Request::WriteMultipleRegisters { start, values } => (start, values.len()),
305 }
306 }
307
308 pub fn reads(&self) -> bool {
310 matches!(
311 self,
312 Request::ReadCoils { .. }
313 | Request::ReadDiscreteInputs { .. }
314 | Request::ReadHoldingRegisters { .. }
315 | Request::ReadInputRegisters { .. }
316 )
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[test]
325 fn the_specification_examples_parse_to_what_they_ask() {
326 let read_coils = Pdu::read_coils(0x0013, 0x0013);
328 assert_eq!(
329 Request::parse(read_coils.as_bytes()),
330 Ok(Request::ReadCoils {
331 start: 0x0013,
332 quantity: 0x0013
333 })
334 );
335 let inputs = Pdu::read_discrete_inputs(0x00C4, 0x0016);
336 assert_eq!(
337 Request::parse(inputs.as_bytes()).map(|request| request.span()),
338 Ok((0x00C4, 22))
339 );
340 let input_registers = Pdu::read_input_registers(0x0008, 1);
341 assert!(Request::parse(input_registers.as_bytes()).is_ok_and(|r| r.reads()));
342 let coil = Pdu::write_single_coil(0x00AC, true);
343 assert_eq!(
344 Request::parse(coil.as_bytes()),
345 Ok(Request::WriteSingleCoil {
346 address: 0x00AC,
347 on: true
348 })
349 );
350 let register = Pdu::write_single_register(0x0001, 0x0003);
351 assert_eq!(
352 Request::parse(register.as_bytes()).map(|r| r.function()),
353 Ok(Function::WriteSingleRegister)
354 );
355 let ten = [
356 true, false, true, true, false, false, true, true, true, false,
357 ];
358 let coils = Pdu::write_multiple_coils(0x0013, &ten).unwrap();
359 match Request::parse(coils.as_bytes()) {
360 Ok(Request::WriteMultipleCoils { start, values }) => {
361 assert_eq!(start, 0x0013);
362 assert!(values.eq(ten));
363 }
364 other => panic!("expected ten coils, got {other:?}"),
365 }
366 let registers = Pdu::write_multiple_registers(0x0001, &[0x000A, 0x0102]).unwrap();
367 match Request::parse(registers.as_bytes()) {
368 Ok(Request::WriteMultipleRegisters { start, values }) => {
369 assert_eq!(start, 0x0001);
370 assert!(values.eq([0x000A, 0x0102]));
371 }
372 other => panic!("expected two registers, got {other:?}"),
373 }
374 }
375
376 #[test]
377 fn a_quantity_outside_the_range_is_an_illegal_value() {
378 for pdu in [
379 Pdu::read_coils(0, 0),
380 Pdu::read_coils(0, 2001),
381 Pdu::read_discrete_inputs(0, 2001),
382 Pdu::read_holding_registers(0, 0),
383 Pdu::read_holding_registers(0, 126),
384 Pdu::read_input_registers(0, 126),
385 ] {
386 assert_eq!(
387 Request::parse(pdu.as_bytes()),
388 Err(Exception::IllegalDataValue),
389 "{:02x?}",
390 pdu.as_bytes()
391 );
392 }
393 assert!(Request::parse(Pdu::read_coils(0, 2000).as_bytes()).is_ok());
394 assert!(Request::parse(Pdu::read_holding_registers(0, 125).as_bytes()).is_ok());
395 }
396
397 #[test]
398 fn a_coil_value_other_than_on_or_off_is_an_illegal_value() {
399 let half = Pdu::raw(0x05, &[0x00, 0xAC, 0x12, 0x34]).unwrap();
401 assert_eq!(
402 Request::parse(half.as_bytes()),
403 Err(Exception::IllegalDataValue)
404 );
405 }
406
407 #[test]
408 fn a_byte_count_that_does_not_match_is_an_illegal_value() {
409 let short = Pdu::raw(0x10, &[0x00, 0x01, 0x00, 0x02, 0x03, 0x00, 0x0A, 0x01]).unwrap();
411 assert_eq!(
412 Request::parse(short.as_bytes()),
413 Err(Exception::IllegalDataValue)
414 );
415 let coils = Pdu::raw(0x0F, &[0x00, 0x13, 0x00, 0x0A, 0x01, 0xCD]).unwrap();
417 assert_eq!(
418 Request::parse(coils.as_bytes()),
419 Err(Exception::IllegalDataValue)
420 );
421 let long = Pdu::raw(0x03, &[0x00, 0x00, 0x00, 0x01, 0x00]).unwrap();
423 assert_eq!(
424 Request::parse(long.as_bytes()),
425 Err(Exception::IllegalDataValue)
426 );
427 }
428
429 #[test]
430 fn an_unknown_function_is_an_illegal_function() {
431 let diagnostics = Pdu::raw(0x08, &[0x00, 0x00, 0xA5, 0x37]).unwrap();
432 assert_eq!(
433 Request::parse(diagnostics.as_bytes()),
434 Err(Exception::IllegalFunction)
435 );
436 assert_eq!(Request::parse(&[]), Err(Exception::IllegalFunction));
437 let truncated = Pdu::raw(0x03, &[0x00]).unwrap();
438 assert_eq!(
439 Request::parse(truncated.as_bytes()),
440 Err(Exception::IllegalDataValue)
441 );
442 }
443}