1use alloc::collections::BTreeMap;
4use alloc::vec::Vec;
5
6use crate::adu::Adu;
7use crate::error::ModbusError;
8use crate::function::Exception;
9use crate::pdu::Pdu;
10use crate::request::Request;
11
12pub const BROADCAST: u8 = 0;
14
15pub const UNITS: core::ops::RangeInclusive<u8> = 1..=247;
17
18#[derive(Clone, Debug, PartialEq, Eq)]
49pub struct Server {
50 unit: u8,
51 coils: BTreeMap<u16, bool>,
52 discrete_inputs: BTreeMap<u16, bool>,
53 holding_registers: BTreeMap<u16, u16>,
54 input_registers: BTreeMap<u16, u16>,
55 served: usize,
56}
57
58fn fill<T: Copy>(table: &mut BTreeMap<u16, T>, start: u16, values: &[T]) {
59 for (address, &value) in (start..=u16::MAX).zip(values) {
60 table.insert(address, value);
61 }
62}
63
64fn read<T: Copy>(table: &BTreeMap<u16, T>, start: u16, count: usize) -> Vec<T> {
65 (start..=u16::MAX)
66 .take(count)
67 .filter_map(|address| table.get(&address).copied())
68 .collect()
69}
70
71fn holds<T>(table: &BTreeMap<u16, T>, start: u16, count: usize) -> bool {
72 let last = usize::from(start) + count - 1;
73 last <= usize::from(u16::MAX) && (start..=last as u16).all(|a| table.contains_key(&a))
74}
75
76impl Server {
77 pub fn new(unit: u8) -> Result<Server, ModbusError> {
92 if !UNITS.contains(&unit) {
93 return Err(ModbusError::UnitOutOfRange { unit });
94 }
95 Ok(Server {
96 unit,
97 coils: BTreeMap::new(),
98 discrete_inputs: BTreeMap::new(),
99 holding_registers: BTreeMap::new(),
100 input_registers: BTreeMap::new(),
101 served: 0,
102 })
103 }
104
105 #[must_use]
116 pub fn with_coils(mut self, start: u16, values: &[bool]) -> Server {
117 fill(&mut self.coils, start, values);
118 self
119 }
120
121 #[must_use]
132 pub fn with_discrete_inputs(mut self, start: u16, values: &[bool]) -> Server {
133 fill(&mut self.discrete_inputs, start, values);
134 self
135 }
136
137 #[must_use]
148 pub fn with_holding_registers(mut self, start: u16, values: &[u16]) -> Server {
149 fill(&mut self.holding_registers, start, values);
150 self
151 }
152
153 #[must_use]
164 pub fn with_input_registers(mut self, start: u16, values: &[u16]) -> Server {
165 fill(&mut self.input_registers, start, values);
166 self
167 }
168
169 pub fn set_holding_registers(&mut self, start: u16, values: &[u16]) {
177 fill(&mut self.holding_registers, start, values);
178 }
179
180 pub fn set_input_registers(&mut self, start: u16, values: &[u16]) {
187 fill(&mut self.input_registers, start, values);
188 }
189
190 pub fn set_coils(&mut self, start: u16, values: &[bool]) {
197 fill(&mut self.coils, start, values);
198 }
199
200 pub fn set_discrete_inputs(&mut self, start: u16, values: &[bool]) {
207 fill(&mut self.discrete_inputs, start, values);
208 }
209
210 pub fn coil(&self, address: u16) -> Option<bool> {
212 self.coils.get(&address).copied()
213 }
214
215 pub fn discrete_input(&self, address: u16) -> Option<bool> {
217 self.discrete_inputs.get(&address).copied()
218 }
219
220 pub fn holding_register(&self, address: u16) -> Option<u16> {
222 self.holding_registers.get(&address).copied()
223 }
224
225 pub fn input_register(&self, address: u16) -> Option<u16> {
227 self.input_registers.get(&address).copied()
228 }
229
230 pub fn unit(&self) -> u8 {
232 self.unit
233 }
234
235 pub fn served(&self) -> usize {
238 self.served
239 }
240
241 pub fn serve(&mut self, pdu: &[u8]) -> Pdu {
251 let function = pdu.first().copied().unwrap_or(0);
252 let request = match Request::parse(pdu) {
253 Ok(request) => request,
254 Err(exception) => return Pdu::exception(function, exception),
255 };
256 let (start, count) = request.span();
257 let present = match request {
258 Request::ReadCoils { .. }
259 | Request::WriteSingleCoil { .. }
260 | Request::WriteMultipleCoils { .. } => holds(&self.coils, start, count),
261 Request::ReadDiscreteInputs { .. } => holds(&self.discrete_inputs, start, count),
262 Request::ReadHoldingRegisters { .. }
263 | Request::WriteSingleRegister { .. }
264 | Request::WriteMultipleRegisters { .. } => {
265 holds(&self.holding_registers, start, count)
266 }
267 Request::ReadInputRegisters { .. } => holds(&self.input_registers, start, count),
268 };
269 if !present {
270 return Pdu::exception(function, Exception::IllegalDataAddress);
271 }
272 self.served += 1;
273 let quantity = count as u16;
274 let reply = match request {
275 Request::ReadCoils { .. } => Pdu::read_coils_reply(&read(&self.coils, start, count)),
276 Request::ReadDiscreteInputs { .. } => {
277 Pdu::read_discrete_inputs_reply(&read(&self.discrete_inputs, start, count))
278 }
279 Request::ReadHoldingRegisters { .. } => {
280 Pdu::read_holding_registers_reply(&read(&self.holding_registers, start, count))
281 }
282 Request::ReadInputRegisters { .. } => {
283 Pdu::read_input_registers_reply(&read(&self.input_registers, start, count))
284 }
285 Request::WriteSingleCoil { address, on } => {
286 self.coils.insert(address, on);
287 Ok(Pdu::write_single_coil(address, on))
288 }
289 Request::WriteSingleRegister { address, value } => {
290 self.holding_registers.insert(address, value);
291 Ok(Pdu::write_single_register(address, value))
292 }
293 Request::WriteMultipleCoils { values, .. } => {
294 for (address, on) in (start..=u16::MAX).zip(values) {
295 self.coils.insert(address, on);
296 }
297 Ok(Pdu::write_multiple_coils_reply(start, quantity))
298 }
299 Request::WriteMultipleRegisters { values, .. } => {
300 for (address, value) in (start..=u16::MAX).zip(values) {
301 self.holding_registers.insert(address, value);
302 }
303 Ok(Pdu::write_multiple_registers_reply(start, quantity))
304 }
305 };
306 reply.unwrap_or_else(|_| Pdu::exception(function, Exception::ServerDeviceFailure))
307 }
308
309 pub fn answer(&mut self, frame: &[u8]) -> Option<Adu> {
320 let adu = Adu::parse(frame).ok()?;
321 if adu.address() == BROADCAST {
322 if Request::parse(adu.pdu()).is_ok_and(|request| !request.reads()) {
323 self.serve(adu.pdu());
324 }
325 return None;
326 }
327 if adu.address() != self.unit {
328 return None;
329 }
330 Some(self.serve(adu.pdu()).to_adu(self.unit))
331 }
332}
333
334#[cfg(feature = "port")]
336impl pamoja_hal::port::Peer for Server {
337 fn receive(&mut self, bytes: &[u8]) -> Vec<u8> {
338 self.answer(bytes)
339 .map(|adu| adu.as_bytes().to_vec())
340 .unwrap_or_default()
341 }
342}
343
344#[cfg(feature = "port")]
372#[derive(Clone, Debug, Default)]
373pub struct Line {
374 servers: Vec<std::sync::Arc<std::sync::Mutex<Server>>>,
375}
376
377#[cfg(feature = "port")]
378impl Line {
379 pub fn new() -> Line {
381 Line::default()
382 }
383
384 pub fn attach(&mut self, server: Server) -> std::sync::Arc<std::sync::Mutex<Server>> {
394 let shared = std::sync::Arc::new(std::sync::Mutex::new(server));
395 self.servers.push(std::sync::Arc::clone(&shared));
396 shared
397 }
398
399 pub fn attach_shared(&mut self, server: std::sync::Arc<std::sync::Mutex<Server>>) {
405 self.servers.push(server);
406 }
407
408 pub fn units(&self) -> Vec<u8> {
410 self.servers
411 .iter()
412 .map(|server| lock(server).unit())
413 .collect()
414 }
415}
416
417#[cfg(feature = "port")]
418fn lock(server: &std::sync::Mutex<Server>) -> std::sync::MutexGuard<'_, Server> {
419 server
420 .lock()
421 .unwrap_or_else(std::sync::PoisonError::into_inner)
422}
423
424#[cfg(feature = "port")]
425impl pamoja_hal::port::Peer for Line {
426 fn receive(&mut self, bytes: &[u8]) -> Vec<u8> {
427 let mut replies = Vec::new();
428 for server in &self.servers {
429 if let Some(reply) = lock(server).answer(bytes) {
430 replies.extend_from_slice(reply.as_bytes());
431 }
432 }
433 replies
434 }
435}
436
437#[cfg(test)]
438mod tests {
439 use super::*;
440 use crate::Response;
441
442 fn reply(server: &mut Server, request: Pdu) -> Pdu {
443 server.serve(request.as_bytes())
444 }
445
446 #[test]
447 fn it_answers_each_specification_example_with_the_frame_the_specification_shows() {
448 let coils = [
450 true, false, true, true, false, false, true, true, true, true, false, true, false,
451 true, true, false, true, false, true,
452 ];
453 let mut device = Server::new(1)
454 .unwrap()
455 .with_coils(0x0013, &coils)
456 .with_holding_registers(0x006B, &[0x022B, 0x0000, 0x0064])
457 .with_input_registers(0x0008, &[0x000A])
458 .with_holding_registers(0x0001, &[0, 0]);
459 assert_eq!(
460 reply(&mut device, Pdu::read_coils(0x0013, 0x0013)).as_bytes(),
461 &[0x01, 0x03, 0xCD, 0x6B, 0x05]
462 );
463 let inputs = [
465 false, false, true, true, false, true, false, true, true, true, false, true, true,
466 false, true, true, true, false, true, false, true, true,
467 ];
468 let mut device = device.with_discrete_inputs(0x00C4, &inputs);
469 assert_eq!(
470 reply(&mut device, Pdu::read_discrete_inputs(0x00C4, 0x0016)).as_bytes(),
471 &[0x02, 0x03, 0xAC, 0xDB, 0x35]
472 );
473 assert_eq!(
475 reply(&mut device, Pdu::read_holding_registers(0x006B, 3)).as_bytes(),
476 &[0x03, 0x06, 0x02, 0x2B, 0x00, 0x00, 0x00, 0x64]
477 );
478 assert_eq!(
479 reply(&mut device, Pdu::read_input_registers(0x0008, 1)).as_bytes(),
480 &[0x04, 0x02, 0x00, 0x0A]
481 );
482 let on = Pdu::write_single_coil(0x00AC, true);
484 let mut device = device.with_coils(0x00AC, &[false]);
485 assert_eq!(reply(&mut device, on).as_bytes(), on.as_bytes());
486 assert_eq!(device.coil(0x00AC), Some(true));
487 let ten = [
489 true, false, true, true, false, false, true, true, true, false,
490 ];
491 let many = Pdu::write_multiple_coils(0x0013, &ten).unwrap();
492 assert_eq!(
493 reply(&mut device, many).as_bytes(),
494 &[0x0F, 0x00, 0x13, 0x00, 0x0A]
495 );
496 let single = Pdu::write_single_register(0x0001, 0x0003);
498 assert_eq!(reply(&mut device, single).as_bytes(), single.as_bytes());
499 let two = Pdu::write_multiple_registers(0x0001, &[0x000A, 0x0102]).unwrap();
500 assert_eq!(
501 reply(&mut device, two).as_bytes(),
502 &[0x10, 0x00, 0x01, 0x00, 0x02]
503 );
504 assert_eq!(device.holding_register(0x0002), Some(0x0102));
505 assert_eq!(device.coil(0x0013 + 9), Some(false));
506 assert_eq!(device.served(), 8);
507 }
508
509 #[test]
510 fn it_refuses_in_the_order_the_state_diagrams_check() {
511 let mut device = Server::new(1).unwrap().with_holding_registers(0, &[0; 100]);
512 assert_eq!(
514 reply(&mut device, Pdu::read_coils(0x04A1, 1)).as_bytes(),
515 &[0x81, 0x02]
516 );
517 assert!(
520 Response::new(reply(&mut device, Pdu::read_holding_registers(96, 4)).as_bytes())
521 .exception()
522 .is_none()
523 );
524 assert_eq!(
525 reply(&mut device, Pdu::read_holding_registers(96, 5)).as_bytes(),
526 &[0x83, 0x02]
527 );
528 assert_eq!(
530 reply(&mut device, Pdu::read_holding_registers(5_000, 126)).as_bytes(),
531 &[0x83, 0x03]
532 );
533 let diagnostics = Pdu::raw(0x08, &[0x00, 0x00, 0xA5, 0x37]).unwrap();
535 assert_eq!(reply(&mut device, diagnostics).as_bytes(), &[0x88, 0x01]);
536 assert_eq!(
538 reply(&mut device, Pdu::read_holding_registers(0xFFFF, 2)).as_bytes(),
539 &[0x83, 0x02]
540 );
541 assert_eq!(device.served(), 1, "only the one good read was carried out");
542 }
543
544 #[test]
545 fn a_frame_gets_an_answer_only_when_it_should() {
546 let mut device = Server::new(17)
547 .unwrap()
548 .with_holding_registers(107, &[2301, 418, 0]);
549 let request = Pdu::read_holding_registers(107, 1).to_adu(17);
550 let answer = device.answer(request.as_bytes()).unwrap();
551 assert_eq!(answer.address(), 17);
552
553 let other = Pdu::read_holding_registers(107, 1).to_adu(18);
555 assert!(device.answer(other.as_bytes()).is_none());
556 let mut mangled = request.as_bytes().to_vec();
557 mangled[3] ^= 0x01;
558 assert!(device.answer(&mangled).is_none());
559
560 let write = Pdu::write_single_register(108, 500).to_adu(BROADCAST);
562 assert!(device.answer(write.as_bytes()).is_none());
563 assert_eq!(device.holding_register(108), Some(500));
564 let read_all = Pdu::read_holding_registers(107, 1).to_adu(BROADCAST);
565 assert!(device.answer(read_all.as_bytes()).is_none());
566 assert_eq!(device.served(), 2);
567 }
568
569 #[test]
570 fn a_unit_outside_one_to_247_is_refused() {
571 for unit in [0, 248, 255] {
572 assert_eq!(Server::new(unit), Err(ModbusError::UnitOutOfRange { unit }));
573 }
574 assert!(Server::new(247).is_ok());
575 }
576
577 #[test]
578 fn many_coils_and_discrete_inputs_round_trip() {
579 let ten = [
580 true, false, true, true, false, false, true, true, true, false,
581 ];
582 let mut device = Server::new(1)
583 .unwrap()
584 .with_coils(0x0013, &[false; 10])
585 .with_discrete_inputs(0x00C4, &[true, false, true]);
586 let write = Pdu::write_multiple_coils(0x0013, &ten).unwrap();
587 assert_eq!(
588 reply(&mut device, write).as_bytes(),
589 &[0x0F, 0x00, 0x13, 0x00, 0x0A]
590 );
591 let back = reply(&mut device, Pdu::read_coils(0x0013, 10));
592 assert!(Response::new(back.as_bytes()).coils(10).unwrap().eq(ten));
593 let inputs = reply(&mut device, Pdu::read_discrete_inputs(0x00C4, 3));
594 assert_eq!(inputs.as_bytes(), &[0x02, 0x01, 0x05]);
595 }
596}