Skip to main content

oms_modbus/
slave_store.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//!
3//! In-memory Modbus slave register store.
4//!
5//! Holding registers and input registers use `RwLock<Vec<u16>>` for O(1)
6//! indexed access. Reading 125 consecutive registers is a single lock
7//! acquisition + memcpy — orders of magnitude faster than per-register
8//! hash lookups.
9//!
10//! Coils and discrete inputs use a sparse `Vec<u8>` (bit-packed) since
11//! their address space is typically sparse and 65536 bits = 8KB is trivial.
12
13use std::sync::{Arc, RwLock};
14
15use async_trait::async_trait;
16
17use crate::frame::{Exception, Request, Response};
18use crate::server::Service;
19
20/// Default size for register vectors (grow on demand).
21const DEFAULT_REG_COUNT: usize = 1024;
22/// In-memory register store. Holding/input registers use contiguous
23/// `Vec<u16>` with `RwLock` for fast indexed access. Coils and discrete
24/// inputs use bit-packed `Vec<u8>` for memory efficiency.
25///
26/// # Example
27///
28/// ```no_run
29/// use std::sync::Arc;
30/// use oms_modbus::SlaveStore;
31///
32/// let store = Arc::new(SlaveStore::with_holding_registers(&[
33///     (0, 100),   // address 0 = 100
34///     (1, 200),   // address 1 = 200
35/// ]));
36/// store.write_coil(0, true);
37///
38/// assert_eq!(store.read_holding_register(0), 100);
39/// assert!(store.read_coil(0));
40/// ```
41pub struct SlaveStore {
42    coils: RwLock<Vec<u8>>,
43    discrete_inputs: RwLock<Vec<u8>>,
44    holding_registers: RwLock<Vec<u16>>,
45    input_registers: RwLock<Vec<u16>>,
46}
47
48impl Default for SlaveStore {
49    fn default() -> Self {
50        Self {
51            coils: RwLock::new(vec![0u8; DEFAULT_REG_COUNT / 8]),
52            discrete_inputs: RwLock::new(vec![0u8; DEFAULT_REG_COUNT / 8]),
53            holding_registers: RwLock::new(vec![0u16; DEFAULT_REG_COUNT]),
54            input_registers: RwLock::new(vec![0u16; DEFAULT_REG_COUNT]),
55        }
56    }
57}
58
59impl SlaveStore {
60    /// Create an empty store with default capacity (1024 registers).
61    pub fn new() -> Self {
62        Self::default()
63    }
64
65    /// Pre-populate holding registers from `(address, value)` pairs.
66    /// Automatically grows the backing store if addresses exceed the default size.
67    pub fn with_holding_registers(regs: &[(u16, u16)]) -> Self {
68        let store = Self::new();
69        let max_addr = regs.iter().map(|&(a, _)| a as usize).max().unwrap_or(0);
70        {
71            let mut hr = store
72                .holding_registers
73                .write()
74                .unwrap_or_else(|e| e.into_inner());
75            if hr.len() <= max_addr {
76                hr.resize(max_addr + 1, 0);
77            }
78            for &(addr, val) in regs {
79                hr[addr as usize] = val;
80            }
81        }
82        store
83    }
84
85    // ── Private helpers ─────────────────────────────────────────────────
86
87    /// Read a single bit from a bit-packed `Vec<u8>`.
88    fn read_bit(lock: &RwLock<Vec<u8>>, addr: u16) -> bool {
89        let bytes = lock.read().unwrap_or_else(|e| e.into_inner());
90        let byte_idx = addr as usize / 8;
91        let bit_idx = addr as usize % 8;
92        bytes
93            .get(byte_idx)
94            .map(|b| (b >> bit_idx) & 1 != 0)
95            .unwrap_or(false)
96    }
97
98    /// Write a single bit to a bit-packed `Vec<u8>`, growing if needed.
99    fn write_bit(lock: &RwLock<Vec<u8>>, addr: u16, value: bool) {
100        let mut bytes = lock.write().unwrap_or_else(|e| e.into_inner());
101        let byte_idx = addr as usize / 8;
102        if byte_idx >= bytes.len() {
103            bytes.resize(byte_idx + 1, 0);
104        }
105        if value {
106            bytes[byte_idx] |= 1 << (addr as usize % 8);
107        } else {
108            bytes[byte_idx] &= !(1 << (addr as usize % 8));
109        }
110    }
111
112    /// Batch-read consecutive bits from a bit-packed `Vec<u8>`.
113    /// Returns `false` for addresses beyond the store (sparse allocation).
114    fn read_bits(lock: &RwLock<Vec<u8>>, addr: u16, count: u16) -> Vec<bool> {
115        let bytes = lock.read().unwrap_or_else(|e| e.into_inner());
116        let mut result = Vec::with_capacity(count as usize);
117        for i in 0..count {
118            let a = addr as usize + i as usize;
119            let byte_idx = a / 8;
120            let bit_idx = a % 8;
121            result.push(
122                bytes
123                    .get(byte_idx)
124                    .map(|b| (b >> bit_idx) & 1 != 0)
125                    .unwrap_or(false),
126            );
127        }
128        result
129    }
130
131    /// Batch-read consecutive registers from a `Vec<u16>`.
132    fn read_registers(lock: &RwLock<Vec<u16>>, addr: u16, count: u16) -> Vec<u16> {
133        let regs = lock.read().unwrap_or_else(|e| e.into_inner());
134        let start = addr as usize;
135        if start >= regs.len() {
136            return vec![0u16; count as usize];
137        }
138        let end = (start + count as usize).min(regs.len());
139        let mut result = vec![0u16; count as usize];
140        result[..end - start].copy_from_slice(&regs[start..end]);
141        result
142    }
143
144    /// Write a single register to a `Vec<u16>`, growing if needed.
145    fn write_register(lock: &RwLock<Vec<u16>>, addr: u16, value: u16) {
146        let mut regs = lock.write().unwrap_or_else(|e| e.into_inner());
147        if addr as usize >= regs.len() {
148            regs.resize(addr as usize + 1, 0);
149        }
150        regs[addr as usize] = value;
151    }
152
153    // ── Coils ────────────────────────────────────────────────────────
154
155    /// Read a single coil. Returns `false` for uninitialized addresses.
156    pub fn read_coil(&self, addr: u16) -> bool {
157        Self::read_bit(&self.coils, addr)
158    }
159
160    /// Write a single coil.
161    pub fn write_coil(&self, addr: u16, value: bool) {
162        Self::write_bit(&self.coils, addr, value);
163    }
164
165    /// Batch-read consecutive coils (single lock acquisition).
166    pub fn read_coils_range(&self, addr: u16, count: u16) -> Vec<bool> {
167        Self::read_bits(&self.coils, addr, count)
168    }
169
170    // ── Discrete inputs ──────────────────────────────────────────────
171
172    /// Read a single discrete input. Returns `false` for uninitialized addresses.
173    pub fn read_discrete_input(&self, addr: u16) -> bool {
174        Self::read_bit(&self.discrete_inputs, addr)
175    }
176
177    /// Write a discrete input value. This violates the Modbus spec (discrete
178    /// inputs are read-only on real devices) but is provided for simulation
179    /// and testing scenarios.
180    pub fn write_discrete_input(&self, addr: u16, value: bool) {
181        Self::write_bit(&self.discrete_inputs, addr, value);
182    }
183
184    /// Batch-read consecutive discrete inputs (single lock acquisition).
185    pub fn read_discrete_inputs_range(&self, addr: u16, count: u16) -> Vec<bool> {
186        Self::read_bits(&self.discrete_inputs, addr, count)
187    }
188
189    // ── Holding registers ────────────────────────────────────────────
190
191    /// Read a single holding register. Returns `0` for uninitialized addresses.
192    pub fn read_holding_register(&self, addr: u16) -> u16 {
193        let hr = self
194            .holding_registers
195            .read()
196            .unwrap_or_else(|e| e.into_inner());
197        hr.get(addr as usize).copied().unwrap_or(0)
198    }
199
200    /// Batch-read consecutive holding registers (single lock + memcpy).
201    pub fn read_holding_range(&self, addr: u16, count: u16) -> Vec<u16> {
202        Self::read_registers(&self.holding_registers, addr, count)
203    }
204
205    /// Write a single holding register. Grows the backing store if needed.
206    pub fn write_holding_register(&self, addr: u16, value: u16) {
207        Self::write_register(&self.holding_registers, addr, value);
208    }
209
210    // ── Input registers ──────────────────────────────────────────────
211
212    /// Read a single input register. Returns `0` for uninitialized addresses.
213    pub fn read_input_register(&self, addr: u16) -> u16 {
214        let ir = self
215            .input_registers
216            .read()
217            .unwrap_or_else(|e| e.into_inner());
218        ir.get(addr as usize).copied().unwrap_or(0)
219    }
220
221    /// Batch-read consecutive input registers (single lock + memcpy).
222    pub fn read_input_range(&self, addr: u16, count: u16) -> Vec<u16> {
223        Self::read_registers(&self.input_registers, addr, count)
224    }
225
226    /// Write an input register value. This violates the Modbus spec (input
227    /// registers are read-only on real devices) but is provided for simulation
228    /// and testing scenarios.
229    pub fn write_input_register(&self, addr: u16, value: u16) {
230        Self::write_register(&self.input_registers, addr, value);
231    }
232}
233
234#[async_trait]
235impl Service for Arc<SlaveStore> {
236    async fn call(&self, request: Request<'_>) -> Result<Response, Exception> {
237        let store = self.as_ref();
238        Ok(match request {
239            Request::ReadCoils(addr, cnt) => Response::ReadCoils(store.read_coils_range(addr, cnt)),
240            Request::ReadDiscreteInputs(addr, cnt) => {
241                Response::ReadDiscreteInputs(store.read_discrete_inputs_range(addr, cnt))
242            }
243            Request::ReadHoldingRegisters(addr, cnt) => {
244                Response::ReadHoldingRegisters(store.read_holding_range(addr, cnt))
245            }
246            Request::ReadInputRegisters(addr, cnt) => {
247                Response::ReadInputRegisters(store.read_input_range(addr, cnt))
248            }
249            Request::WriteSingleCoil(addr, value) => {
250                store.write_coil(addr, value);
251                Response::WriteSingleCoil(addr, value)
252            }
253            Request::WriteSingleRegister(addr, value) => {
254                store.write_holding_register(addr, value);
255                Response::WriteSingleRegister(addr, value)
256            }
257            Request::WriteMultipleCoils(addr, values) => {
258                // Inline bit writes (not write_coil) — single lock for the batch
259                let mut coils = store.coils.write().unwrap_or_else(|e| e.into_inner());
260                for (i, &v) in values.iter().enumerate() {
261                    let a = addr as usize + i;
262                    let byte_idx = a / 8;
263                    let bit_idx = a % 8;
264                    if byte_idx >= coils.len() {
265                        coils.resize(byte_idx + 1, 0);
266                    }
267                    if v {
268                        coils[byte_idx] |= 1 << bit_idx;
269                    } else {
270                        coils[byte_idx] &= !(1 << bit_idx);
271                    }
272                }
273                drop(coils);
274                Response::WriteMultipleCoils(addr, values.len() as u16)
275            }
276            Request::WriteMultipleRegisters(addr, values) => {
277                // Inline register writes (not write_holding_register) — single lock
278                let mut hr = store
279                    .holding_registers
280                    .write()
281                    .unwrap_or_else(|e| e.into_inner());
282                for (i, &v) in values.iter().enumerate() {
283                    let a = addr as usize + i;
284                    if a >= hr.len() {
285                        hr.resize(a + 1, 0);
286                    }
287                    hr[a] = v;
288                }
289                drop(hr);
290                Response::WriteMultipleRegisters(addr, values.len() as u16)
291            }
292            Request::MaskWriteRegister(addr, and_mask, or_mask) => {
293                // Atomic RMW: single write lock for the entire operation
294                let mut hr = store
295                    .holding_registers
296                    .write()
297                    .unwrap_or_else(|e| e.into_inner());
298                let a = addr as usize;
299                if a >= hr.len() {
300                    hr.resize(a + 1, 0);
301                }
302                let current = hr[a];
303                hr[a] = (current & and_mask) | (or_mask & !and_mask);
304                Response::MaskWriteRegister(addr, and_mask, or_mask)
305            }
306            Request::ReadWriteMultipleRegisters(read_addr, read_qty, write_addr, values) => {
307                // FC=23 is NOT atomic: the read lock is released before the
308                // write lock is acquired. A concurrent writer between the two
309                // can modify the register range, so the returned read values
310                // may be stale. This is acceptable per the Modbus spec (which
311                // does not require atomicity for function code 23) and matches
312                // the behavior of common industrial devices.
313                let regs = store.read_holding_range(read_addr, read_qty);
314                // Inline writes — single lock (not write_holding_register loop)
315                let mut hr = store
316                    .holding_registers
317                    .write()
318                    .unwrap_or_else(|e| e.into_inner());
319                for (i, &v) in values.iter().enumerate() {
320                    let a = write_addr as usize + i;
321                    if a >= hr.len() {
322                        hr.resize(a + 1, 0);
323                    }
324                    hr[a] = v;
325                }
326                drop(hr);
327                Response::ReadWriteMultipleRegisters(regs)
328            }
329            Request::Diagnostic(sf, data) => {
330                match sf {
331                    // 0x0000: Return Query Data — echo back
332                    0x0000 => Response::Diagnostic(sf, data),
333                    // 0x000A: Clear Counters and Diagnostic Register
334                    0x000A => Response::Diagnostic(sf, 0x0000),
335                    // 0x000B-0x000E: Return counters (not tracked → return 0)
336                    0x000B..=0x000E => Response::Diagnostic(sf, 0x0000),
337                    _ => return Err(Exception::IllegalDataValue),
338                }
339            }
340            _ => return Err(Exception::IllegalFunction),
341        })
342    }
343}