1use std::collections::HashMap;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14#[repr(u8)]
15pub enum FunctionCode {
16 ReadCoils = 0x01,
17 ReadDiscreteInputs = 0x02,
18 ReadHoldingRegisters = 0x03,
19 ReadInputRegisters = 0x04,
20 WriteSingleCoil = 0x05,
21 WriteSingleRegister = 0x06,
22 WriteMultipleCoils = 0x0F,
23 WriteMultipleRegisters = 0x10,
24 ReadDeviceIdentification = 0x2B,
25}
26
27impl FunctionCode {
28 pub fn as_u8(self) -> u8 {
30 self as u8
31 }
32
33 pub fn from_u8(v: u8) -> Option<Self> {
35 match v {
36 0x01 => Some(Self::ReadCoils),
37 0x02 => Some(Self::ReadDiscreteInputs),
38 0x03 => Some(Self::ReadHoldingRegisters),
39 0x04 => Some(Self::ReadInputRegisters),
40 0x05 => Some(Self::WriteSingleCoil),
41 0x06 => Some(Self::WriteSingleRegister),
42 0x0F => Some(Self::WriteMultipleCoils),
43 0x10 => Some(Self::WriteMultipleRegisters),
44 0x2B => Some(Self::ReadDeviceIdentification),
45 _ => None,
46 }
47 }
48
49 pub fn name(self) -> &'static str {
51 match self {
52 Self::ReadCoils => "ReadCoils",
53 Self::ReadDiscreteInputs => "ReadDiscreteInputs",
54 Self::ReadHoldingRegisters => "ReadHoldingRegisters",
55 Self::ReadInputRegisters => "ReadInputRegisters",
56 Self::WriteSingleCoil => "WriteSingleCoil",
57 Self::WriteSingleRegister => "WriteSingleRegister",
58 Self::WriteMultipleCoils => "WriteMultipleCoils",
59 Self::WriteMultipleRegisters => "WriteMultipleRegisters",
60 Self::ReadDeviceIdentification => "ReadDeviceIdentification",
61 }
62 }
63}
64
65#[derive(Debug, Clone)]
71pub struct FunctionCodeRequest {
72 pub code: u8,
74 pub data: Vec<u8>,
76}
77
78#[derive(Debug, Clone)]
80pub struct FunctionCodeResponse {
81 pub code: u8,
83 pub data: Vec<u8>,
85 pub is_error: bool,
87}
88
89pub trait FunctionCodeHandler: Send + Sync {
95 fn handle(&self, req: &FunctionCodeRequest) -> FunctionCodeResponse;
97
98 fn function_code(&self) -> u8;
100
101 fn description(&self) -> &str;
103}
104
105pub struct DispatchTable {
111 handlers: HashMap<u8, Box<dyn FunctionCodeHandler>>,
112}
113
114impl Default for DispatchTable {
115 fn default() -> Self {
116 Self::new()
117 }
118}
119
120impl DispatchTable {
121 pub fn new() -> Self {
123 DispatchTable {
124 handlers: HashMap::new(),
125 }
126 }
127
128 pub fn with_defaults() -> Self {
131 let mut table = Self::new();
132 let codes = [
133 FunctionCode::ReadCoils,
134 FunctionCode::ReadDiscreteInputs,
135 FunctionCode::ReadHoldingRegisters,
136 FunctionCode::ReadInputRegisters,
137 FunctionCode::WriteSingleCoil,
138 FunctionCode::WriteSingleRegister,
139 FunctionCode::WriteMultipleCoils,
140 FunctionCode::WriteMultipleRegisters,
141 FunctionCode::ReadDeviceIdentification,
142 ];
143 for fc in codes {
144 table.register(Box::new(EchoHandler { fc: fc.as_u8() }));
145 }
146 table
147 }
148
149 pub fn register(&mut self, handler: Box<dyn FunctionCodeHandler>) {
151 self.handlers.insert(handler.function_code(), handler);
152 }
153
154 pub fn dispatch(&self, req: &FunctionCodeRequest) -> FunctionCodeResponse {
159 if let Some(handler) = self.handlers.get(&req.code) {
160 handler.handle(req)
161 } else {
162 Self::error_response(req, 0x01) }
164 }
165
166 pub fn supported_codes(&self) -> Vec<u8> {
168 let mut codes: Vec<u8> = self.handlers.keys().copied().collect();
169 codes.sort_unstable();
170 codes
171 }
172
173 pub fn is_supported(&self, code: u8) -> bool {
175 self.handlers.contains_key(&code)
176 }
177
178 pub fn handler_count(&self) -> usize {
180 self.handlers.len()
181 }
182
183 pub fn error_response(req: &FunctionCodeRequest, error_code: u8) -> FunctionCodeResponse {
186 FunctionCodeResponse {
187 code: req.code | 0x80,
188 data: vec![error_code],
189 is_error: true,
190 }
191 }
192}
193
194pub struct EchoHandler {
201 pub fc: u8,
203}
204
205impl FunctionCodeHandler for EchoHandler {
206 fn handle(&self, req: &FunctionCodeRequest) -> FunctionCodeResponse {
207 FunctionCodeResponse {
208 code: req.code,
209 data: req.data.clone(),
210 is_error: false,
211 }
212 }
213
214 fn function_code(&self) -> u8 {
215 self.fc
216 }
217
218 fn description(&self) -> &str {
219 "Echo handler (returns request data)"
220 }
221}
222
223#[cfg(test)]
228mod tests {
229 use super::*;
230
231 fn req(code: u8, data: &[u8]) -> FunctionCodeRequest {
232 FunctionCodeRequest {
233 code,
234 data: data.to_vec(),
235 }
236 }
237
238 #[test]
241 fn test_fc_read_coils_value() {
242 assert_eq!(FunctionCode::ReadCoils.as_u8(), 0x01);
243 }
244
245 #[test]
246 fn test_fc_read_discrete_inputs_value() {
247 assert_eq!(FunctionCode::ReadDiscreteInputs.as_u8(), 0x02);
248 }
249
250 #[test]
251 fn test_fc_read_holding_registers_value() {
252 assert_eq!(FunctionCode::ReadHoldingRegisters.as_u8(), 0x03);
253 }
254
255 #[test]
256 fn test_fc_read_input_registers_value() {
257 assert_eq!(FunctionCode::ReadInputRegisters.as_u8(), 0x04);
258 }
259
260 #[test]
261 fn test_fc_write_single_coil_value() {
262 assert_eq!(FunctionCode::WriteSingleCoil.as_u8(), 0x05);
263 }
264
265 #[test]
266 fn test_fc_write_single_register_value() {
267 assert_eq!(FunctionCode::WriteSingleRegister.as_u8(), 0x06);
268 }
269
270 #[test]
271 fn test_fc_write_multiple_coils_value() {
272 assert_eq!(FunctionCode::WriteMultipleCoils.as_u8(), 0x0F);
273 }
274
275 #[test]
276 fn test_fc_write_multiple_registers_value() {
277 assert_eq!(FunctionCode::WriteMultipleRegisters.as_u8(), 0x10);
278 }
279
280 #[test]
281 fn test_fc_read_device_identification_value() {
282 assert_eq!(FunctionCode::ReadDeviceIdentification.as_u8(), 0x2B);
283 }
284
285 #[test]
286 fn test_fc_from_u8_valid() {
287 assert_eq!(FunctionCode::from_u8(0x01), Some(FunctionCode::ReadCoils));
288 assert_eq!(
289 FunctionCode::from_u8(0x10),
290 Some(FunctionCode::WriteMultipleRegisters)
291 );
292 }
293
294 #[test]
295 fn test_fc_from_u8_invalid() {
296 assert_eq!(FunctionCode::from_u8(0x00), None);
297 assert_eq!(FunctionCode::from_u8(0xFF), None);
298 }
299
300 #[test]
303 fn test_dispatch_table_new_empty() {
304 let table = DispatchTable::new();
305 assert_eq!(table.handler_count(), 0);
306 }
307
308 #[test]
309 fn test_with_defaults_has_nine_handlers() {
310 let table = DispatchTable::with_defaults();
311 assert_eq!(table.handler_count(), 9);
312 }
313
314 #[test]
315 fn test_is_supported_true_for_defaults() {
316 let table = DispatchTable::with_defaults();
317 assert!(table.is_supported(0x01));
318 assert!(table.is_supported(0x2B));
319 }
320
321 #[test]
322 fn test_is_supported_false_for_unknown() {
323 let table = DispatchTable::with_defaults();
324 assert!(!table.is_supported(0x99));
325 }
326
327 #[test]
328 fn test_supported_codes_sorted() {
329 let table = DispatchTable::with_defaults();
330 let codes = table.supported_codes();
331 let mut sorted = codes.clone();
332 sorted.sort_unstable();
333 assert_eq!(codes, sorted);
334 }
335
336 #[test]
337 fn test_dispatch_known_fc_returns_non_error() {
338 let table = DispatchTable::with_defaults();
339 let r = req(0x01, &[0x00, 0x00, 0x00, 0x10]);
340 let resp = table.dispatch(&r);
341 assert!(!resp.is_error);
342 assert_eq!(resp.code, 0x01);
343 }
344
345 #[test]
346 fn test_dispatch_unknown_fc_returns_error() {
347 let table = DispatchTable::with_defaults();
348 let r = req(0x99, &[]);
349 let resp = table.dispatch(&r);
350 assert!(resp.is_error);
351 assert_eq!(resp.code, 0x99 | 0x80);
353 }
354
355 #[test]
356 fn test_error_response_sets_high_bit() {
357 let r = req(0x03, &[0x00, 0x10]);
358 let resp = DispatchTable::error_response(&r, 0x02);
359 assert_eq!(resp.code, 0x83); assert!(resp.is_error);
361 assert_eq!(resp.data, vec![0x02]);
362 }
363
364 #[test]
365 fn test_error_response_error_code_in_data() {
366 let r = req(0x01, &[]);
367 let resp = DispatchTable::error_response(&r, 0x03);
368 assert_eq!(resp.data, vec![0x03]);
369 }
370
371 #[test]
372 fn test_register_replaces_existing_handler() {
373 let mut table = DispatchTable::new();
374 table.register(Box::new(EchoHandler { fc: 0x01 }));
375 assert_eq!(table.handler_count(), 1);
376
377 struct AlwaysErrorHandler;
379 impl FunctionCodeHandler for AlwaysErrorHandler {
380 fn handle(&self, req: &FunctionCodeRequest) -> FunctionCodeResponse {
381 DispatchTable::error_response(req, 0x04)
382 }
383 fn function_code(&self) -> u8 {
384 0x01
385 }
386 fn description(&self) -> &str {
387 "always error"
388 }
389 }
390 table.register(Box::new(AlwaysErrorHandler));
391 assert_eq!(table.handler_count(), 1);
393
394 let r = req(0x01, &[]);
395 let resp = table.dispatch(&r);
396 assert!(resp.is_error);
398 }
399
400 #[test]
403 fn test_echo_handler_echoes_data() {
404 let handler = EchoHandler { fc: 0x03 };
405 let r = req(0x03, &[0xAA, 0xBB, 0xCC]);
406 let resp = handler.handle(&r);
407 assert_eq!(resp.data, vec![0xAA, 0xBB, 0xCC]);
408 assert!(!resp.is_error);
409 }
410
411 #[test]
412 fn test_echo_handler_function_code() {
413 let handler = EchoHandler { fc: 0x06 };
414 assert_eq!(handler.function_code(), 0x06);
415 }
416
417 #[test]
418 fn test_echo_handler_description_non_empty() {
419 let handler = EchoHandler { fc: 0x01 };
420 assert!(!handler.description().is_empty());
421 }
422
423 #[test]
424 fn test_echo_handler_empty_data() {
425 let handler = EchoHandler { fc: 0x01 };
426 let r = req(0x01, &[]);
427 let resp = handler.handle(&r);
428 assert!(resp.data.is_empty());
429 }
430
431 #[test]
432 fn test_handler_count_after_multiple_registers() {
433 let mut table = DispatchTable::new();
434 table.register(Box::new(EchoHandler { fc: 0x01 }));
435 table.register(Box::new(EchoHandler { fc: 0x02 }));
436 table.register(Box::new(EchoHandler { fc: 0x03 }));
437 assert_eq!(table.handler_count(), 3);
438 }
439
440 #[test]
441 fn test_dispatch_fc0f_multiple_coils() {
442 let table = DispatchTable::with_defaults();
443 let r = req(0x0F, &[0x00, 0x10, 0x00, 0x03, 0x01, 0x05]);
444 let resp = table.dispatch(&r);
445 assert!(!resp.is_error);
446 assert_eq!(resp.code, 0x0F);
447 }
448
449 #[test]
450 fn test_dispatch_fc10_multiple_registers() {
451 let table = DispatchTable::with_defaults();
452 let r = req(
453 0x10,
454 &[0x00, 0x01, 0x00, 0x02, 0x04, 0x00, 0x0A, 0x01, 0x02],
455 );
456 let resp = table.dispatch(&r);
457 assert!(!resp.is_error);
458 }
459
460 #[test]
461 fn test_dispatch_fc2b_device_identification() {
462 let table = DispatchTable::with_defaults();
463 let r = req(0x2B, &[0x0E, 0x01, 0x00]);
464 let resp = table.dispatch(&r);
465 assert!(!resp.is_error);
466 assert_eq!(resp.code, 0x2B);
467 }
468
469 #[test]
470 fn test_default_dispatch_table() {
471 let table = DispatchTable::default();
472 assert_eq!(table.handler_count(), 0);
473 }
474
475 #[test]
476 fn test_fc_name() {
477 assert_eq!(FunctionCode::ReadCoils.name(), "ReadCoils");
478 assert_eq!(
479 FunctionCode::WriteMultipleRegisters.name(),
480 "WriteMultipleRegisters"
481 );
482 }
483
484 #[test]
485 fn test_supported_codes_contains_all_default_fcs() {
486 let table = DispatchTable::with_defaults();
487 let codes = table.supported_codes();
488 for fc in &[0x01u8, 0x02, 0x03, 0x04, 0x05, 0x06, 0x0F, 0x10, 0x2B] {
489 assert!(codes.contains(fc), "Missing FC 0x{:02X}", fc);
490 }
491 }
492}