1use std::collections::HashMap;
4
5use parking_lot::RwLock;
6use rusty_modbus_types::{DiagnosticSubFunction, ExceptionCode};
7
8use crate::file_record::{self, MAX_RECORD_NUMBER, MIN_FILE_NUMBER, RECORD_COUNT};
9
10use super::{
11 DataStore, MAX_DIAGNOSTIC_RESPONSE_DATA_LEN, MAX_FILE_RECORD_REGISTERS, MAX_SERVER_ID_BYTES,
12 bits::BitTable, pack_registers_be, validate_packed_coils, validate_register_values_be,
13};
14
15pub const MAX_TABLE_SIZE: usize = 65_536;
17
18#[derive(Debug, Clone)]
20pub struct StoreConfig {
21 pub coil_count: usize,
23 pub discrete_input_count: usize,
25 pub holding_register_count: usize,
27 pub input_register_count: usize,
29}
30
31impl Default for StoreConfig {
32 fn default() -> Self {
33 Self {
34 coil_count: 65536,
35 discrete_input_count: 65536,
36 holding_register_count: 65536,
37 input_register_count: 65536,
38 }
39 }
40}
41
42impl StoreConfig {
43 pub fn validate(&self) -> Result<(), StoreError> {
50 validate_table_size("coils", self.coil_count)?;
51 validate_table_size("discrete_inputs", self.discrete_input_count)?;
52 validate_table_size("holding_registers", self.holding_register_count)?;
53 validate_table_size("input_registers", self.input_register_count)?;
54 Ok(())
55 }
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
60pub enum StoreError {
61 #[error("{table} table size {count} exceeds Modbus address space ({max})")]
63 TableTooLarge {
64 table: &'static str,
66 count: usize,
68 max: usize,
70 },
71 #[error("{table} address {address} is outside configured table size {len}")]
73 AddressOutOfRange {
74 table: &'static str,
76 address: u16,
78 len: usize,
80 },
81 #[error("file number {file_number} is outside Modbus file range ({minimum}..=65535)")]
83 FileNumberOutOfRange {
84 file_number: u16,
86 minimum: u16,
88 },
89 #[error("file record {record_number} is outside Modbus file record range (0..={maximum})")]
91 FileRecordOutOfRange {
92 record_number: u16,
94 maximum: u16,
96 },
97}
98
99pub struct InMemoryStore {
101 coils: RwLock<BitTable>,
102 discrete_inputs: RwLock<BitTable>,
103 holding_registers: RwLock<Vec<u16>>,
104 input_registers: RwLock<Vec<u16>>,
105 files: RwLock<HashMap<u16, Vec<u16>>>,
108 fifo_queues: RwLock<HashMap<u16, Vec<u16>>>,
110 exception_status: RwLock<u8>,
112 server_id: RwLock<Vec<u8>>,
114}
115
116impl InMemoryStore {
117 #[must_use]
124 pub fn new(config: StoreConfig) -> Self {
125 Self::try_new(config).expect("StoreConfig should fit the Modbus address space")
126 }
127
128 pub fn try_new(config: StoreConfig) -> Result<Self, StoreError> {
134 config.validate()?;
135 Ok(Self {
136 coils: RwLock::new(BitTable::new(config.coil_count)),
137 discrete_inputs: RwLock::new(BitTable::new(config.discrete_input_count)),
138 holding_registers: RwLock::new(vec![0u16; config.holding_register_count]),
139 input_registers: RwLock::new(vec![0u16; config.input_register_count]),
140 files: RwLock::new(HashMap::new()),
141 fifo_queues: RwLock::new(HashMap::new()),
142 exception_status: RwLock::new(0),
143 server_id: RwLock::new(b"rusty-modbus\xFF".to_vec()),
144 })
145 }
146
147 pub fn set_input_register(&self, address: u16, value: u16) -> Result<(), StoreError> {
154 let mut regs = self.input_registers.write();
155 let index = check_setup_address("input_registers", address, regs.len())?;
156 regs[index] = value;
157 Ok(())
158 }
159
160 pub fn set_discrete_input(&self, address: u16, value: bool) -> Result<(), StoreError> {
167 let mut inputs = self.discrete_inputs.write();
168 let index = check_setup_address("discrete_inputs", address, inputs.len())?;
169 inputs.set(index, value);
170 Ok(())
171 }
172
173 pub fn set_holding_register(&self, address: u16, value: u16) -> Result<(), StoreError> {
180 let mut regs = self.holding_registers.write();
181 let index = check_setup_address("holding_registers", address, regs.len())?;
182 regs[index] = value;
183 Ok(())
184 }
185
186 pub fn set_coil(&self, address: u16, value: bool) -> Result<(), StoreError> {
193 let mut coils = self.coils.write();
194 let index = check_setup_address("coils", address, coils.len())?;
195 coils.set(index, value);
196 Ok(())
197 }
198
199 pub fn set_file_record(
207 &self,
208 file_number: u16,
209 record_number: u16,
210 value: u16,
211 ) -> Result<(), StoreError> {
212 check_setup_file_record(file_number, record_number)?;
213 let mut files = self.files.write();
214 let file = files.entry(file_number).or_default();
215 let idx = usize::from(record_number);
216 if idx >= file.len() {
217 file.resize(idx + 1, 0);
218 }
219 file[idx] = value;
220 Ok(())
221 }
222
223 pub fn set_fifo_queue(&self, address: u16, values: Vec<u16>) {
225 self.fifo_queues.write().insert(address, values);
226 }
227
228 pub fn set_exception_status(&self, status: u8) {
230 *self.exception_status.write() = status;
231 }
232
233 pub fn set_server_id(&self, data: Vec<u8>) {
235 *self.server_id.write() = data;
236 }
237}
238
239impl std::fmt::Debug for InMemoryStore {
240 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241 f.debug_struct("InMemoryStore")
242 .field("coils", &self.coils.read().len())
243 .field("holding_registers", &self.holding_registers.read().len())
244 .finish_non_exhaustive()
245 }
246}
247
248fn check_range(address: u16, quantity: usize, max: usize) -> Result<(), ExceptionCode> {
249 let end = usize::from(address)
250 .checked_add(quantity)
251 .ok_or(ExceptionCode::IllegalDataAddress)?;
252 if end > max {
253 return Err(ExceptionCode::IllegalDataAddress);
254 }
255 Ok(())
256}
257
258fn validate_table_size(table: &'static str, count: usize) -> Result<(), StoreError> {
259 if count > MAX_TABLE_SIZE {
260 return Err(StoreError::TableTooLarge {
261 table,
262 count,
263 max: MAX_TABLE_SIZE,
264 });
265 }
266 Ok(())
267}
268
269fn check_setup_address(table: &'static str, address: u16, len: usize) -> Result<usize, StoreError> {
270 let index = usize::from(address);
271 if index >= len {
272 return Err(StoreError::AddressOutOfRange {
273 table,
274 address,
275 len,
276 });
277 }
278 Ok(index)
279}
280
281fn check_setup_file_record(file_number: u16, record_number: u16) -> Result<(), StoreError> {
282 if file_number < MIN_FILE_NUMBER {
283 return Err(StoreError::FileNumberOutOfRange {
284 file_number,
285 minimum: MIN_FILE_NUMBER,
286 });
287 }
288 if usize::from(record_number) >= RECORD_COUNT {
289 return Err(StoreError::FileRecordOutOfRange {
290 record_number,
291 maximum: MAX_RECORD_NUMBER,
292 });
293 }
294 Ok(())
295}
296
297impl DataStore for InMemoryStore {
298 async fn read_coils(
299 &self,
300 address: u16,
301 quantity: u16,
302 buf: &mut [bool],
303 ) -> Result<usize, ExceptionCode> {
304 let coils = self.coils.read();
305 coils.read_bits(address, quantity, buf)
306 }
307
308 async fn read_coils_packed(
309 &self,
310 address: u16,
311 quantity: u16,
312 out: &mut [u8],
313 ) -> Result<usize, ExceptionCode> {
314 let coils = self.coils.read();
315 coils.read_packed(address, quantity, out)
316 }
317
318 async fn write_coil(&self, address: u16, value: bool) -> Result<(), ExceptionCode> {
319 let mut coils = self.coils.write();
320 check_range(address, 1, coils.len())?;
321 coils.set(usize::from(address), value);
322 Ok(())
323 }
324
325 async fn write_coils(&self, address: u16, values: &[bool]) -> Result<(), ExceptionCode> {
326 let mut coils = self.coils.write();
327 coils.write_bits(address, values)
328 }
329
330 async fn write_coils_packed(
331 &self,
332 address: u16,
333 quantity: u16,
334 packed_values: &[u8],
335 ) -> Result<(), ExceptionCode> {
336 let quantity = validate_packed_coils(quantity, packed_values)?;
337 let mut coils = self.coils.write();
338 coils.write_packed(address, quantity, packed_values)
339 }
340
341 async fn read_discrete_inputs(
342 &self,
343 address: u16,
344 quantity: u16,
345 buf: &mut [bool],
346 ) -> Result<usize, ExceptionCode> {
347 let inputs = self.discrete_inputs.read();
348 inputs.read_bits(address, quantity, buf)
349 }
350
351 async fn read_discrete_inputs_packed(
352 &self,
353 address: u16,
354 quantity: u16,
355 out: &mut [u8],
356 ) -> Result<usize, ExceptionCode> {
357 let inputs = self.discrete_inputs.read();
358 inputs.read_packed(address, quantity, out)
359 }
360
361 async fn read_holding_registers(
362 &self,
363 address: u16,
364 quantity: u16,
365 buf: &mut [u16],
366 ) -> Result<usize, ExceptionCode> {
367 let regs = self.holding_registers.read();
368 check_range(address, usize::from(quantity), regs.len())?;
369 let start = address as usize;
370 let qty = quantity as usize;
371 buf[..qty].copy_from_slice(®s[start..start + qty]);
372 Ok(qty)
373 }
374
375 async fn read_holding_registers_be(
376 &self,
377 address: u16,
378 quantity: u16,
379 out: &mut [u8],
380 ) -> Result<usize, ExceptionCode> {
381 let regs = self.holding_registers.read();
382 check_range(address, usize::from(quantity), regs.len())?;
383 let start = address as usize;
384 let qty = quantity as usize;
385 pack_registers_be(®s[start..start + qty], out)?;
386 Ok(qty)
387 }
388
389 async fn write_register(&self, address: u16, value: u16) -> Result<(), ExceptionCode> {
390 let mut regs = self.holding_registers.write();
391 check_range(address, 1, regs.len())?;
392 regs[address as usize] = value;
393 Ok(())
394 }
395
396 async fn write_registers(&self, address: u16, values: &[u16]) -> Result<(), ExceptionCode> {
397 let mut regs = self.holding_registers.write();
398 check_range(address, values.len(), regs.len())?;
399 let start = address as usize;
400 regs[start..start + values.len()].copy_from_slice(values);
401 Ok(())
402 }
403
404 async fn write_registers_be(
405 &self,
406 address: u16,
407 quantity: u16,
408 value_bytes: &[u8],
409 ) -> Result<(), ExceptionCode> {
410 let quantity = validate_register_values_be(quantity, value_bytes)?;
411 let mut regs = self.holding_registers.write();
412 check_range(address, quantity, regs.len())?;
413 let start = address as usize;
414 for (slot, chunk) in regs[start..start + quantity]
415 .iter_mut()
416 .zip(value_bytes.chunks_exact(2))
417 {
418 *slot = u16::from_be_bytes([chunk[0], chunk[1]]);
419 }
420 Ok(())
421 }
422
423 async fn read_input_registers(
424 &self,
425 address: u16,
426 quantity: u16,
427 buf: &mut [u16],
428 ) -> Result<usize, ExceptionCode> {
429 let regs = self.input_registers.read();
430 check_range(address, usize::from(quantity), regs.len())?;
431 let start = address as usize;
432 let qty = quantity as usize;
433 buf[..qty].copy_from_slice(®s[start..start + qty]);
434 Ok(qty)
435 }
436
437 async fn read_input_registers_be(
438 &self,
439 address: u16,
440 quantity: u16,
441 out: &mut [u8],
442 ) -> Result<usize, ExceptionCode> {
443 let regs = self.input_registers.read();
444 check_range(address, usize::from(quantity), regs.len())?;
445 let start = address as usize;
446 let qty = quantity as usize;
447 pack_registers_be(®s[start..start + qty], out)?;
448 Ok(qty)
449 }
450
451 async fn read_file_record(
452 &self,
453 file_number: u16,
454 record_number: u16,
455 record_length: u16,
456 buf: &mut [u16],
457 ) -> Result<usize, ExceptionCode> {
458 file_record::validate_range(file_number, record_number, usize::from(record_length))?;
459 let files = self.files.read();
460 let file = files
461 .get(&file_number)
462 .ok_or(ExceptionCode::IllegalDataAddress)?;
463 let start = usize::from(record_number);
464 let len = usize::from(record_length);
465 let end = start
466 .checked_add(len)
467 .ok_or(ExceptionCode::IllegalDataAddress)?;
468 if end > file.len() || len > buf.len() {
471 return Err(ExceptionCode::IllegalDataAddress);
472 }
473 buf[..len].copy_from_slice(&file[start..end]);
474 Ok(len)
475 }
476
477 async fn read_file_record_be(
478 &self,
479 file_number: u16,
480 record_number: u16,
481 record_length: u16,
482 out: &mut [u8],
483 ) -> Result<usize, ExceptionCode> {
484 let len = usize::from(record_length);
485 file_record::validate_range(file_number, record_number, len)?;
486 if len > MAX_FILE_RECORD_REGISTERS {
487 return Err(ExceptionCode::IllegalDataAddress);
488 }
489 let files = self.files.read();
490 let file = files
491 .get(&file_number)
492 .ok_or(ExceptionCode::IllegalDataAddress)?;
493 let start = usize::from(record_number);
494 let end = start
495 .checked_add(len)
496 .ok_or(ExceptionCode::IllegalDataAddress)?;
497 if end > file.len() {
498 return Err(ExceptionCode::IllegalDataAddress);
499 }
500 pack_registers_be(&file[start..end], out)?;
501 Ok(len)
502 }
503
504 async fn write_file_record(
505 &self,
506 file_number: u16,
507 record_number: u16,
508 values: &[u16],
509 ) -> Result<(), ExceptionCode> {
510 file_record::validate_range(file_number, record_number, values.len())?;
511 let mut files = self.files.write();
512 let file = files.entry(file_number).or_default();
513 let start = usize::from(record_number);
514 let end = start
515 .checked_add(values.len())
516 .ok_or(ExceptionCode::IllegalDataAddress)?;
517 if end > file.len() {
520 file.resize(end, 0);
521 }
522 file[start..end].copy_from_slice(values);
523 Ok(())
524 }
525
526 async fn write_file_record_be(
527 &self,
528 file_number: u16,
529 record_number: u16,
530 record_length: u16,
531 value_bytes: &[u8],
532 ) -> Result<(), ExceptionCode> {
533 let len = usize::from(record_length);
534 if value_bytes.len() != len * 2 {
535 return Err(ExceptionCode::IllegalDataValue);
536 }
537 file_record::validate_range(file_number, record_number, len)?;
538 let mut files = self.files.write();
539 let file = files.entry(file_number).or_default();
540 let start = usize::from(record_number);
541 let end = start
542 .checked_add(len)
543 .ok_or(ExceptionCode::IllegalDataAddress)?;
544 if end > file.len() {
545 file.resize(end, 0);
546 }
547 for (slot, chunk) in file[start..end].iter_mut().zip(value_bytes.chunks_exact(2)) {
548 *slot = u16::from_be_bytes([chunk[0], chunk[1]]);
549 }
550 Ok(())
551 }
552
553 async fn read_fifo_queue(&self, address: u16) -> Result<Vec<u16>, ExceptionCode> {
554 self.fifo_queues
556 .read()
557 .get(&address)
558 .cloned()
559 .ok_or(ExceptionCode::IllegalDataAddress)
560 }
561
562 async fn read_fifo_queue_be(
563 &self,
564 address: u16,
565 out: &mut [u8],
566 ) -> Result<usize, ExceptionCode> {
567 let queues = self.fifo_queues.read();
568 let values = queues
569 .get(&address)
570 .ok_or(ExceptionCode::IllegalDataAddress)?;
571 if values.len() > usize::from(rusty_modbus_types::MAX_FIFO_VALUES) {
572 return Err(ExceptionCode::IllegalDataValue);
573 }
574 pack_registers_be(values, out)?;
575 Ok(values.len())
576 }
577
578 async fn read_exception_status(&self) -> Result<u8, ExceptionCode> {
579 Ok(*self.exception_status.read())
580 }
581
582 async fn report_server_id(&self) -> Result<Vec<u8>, ExceptionCode> {
583 Ok(self.server_id.read().clone())
584 }
585
586 async fn append_server_id(&self, out: &mut Vec<u8>) -> Result<usize, ExceptionCode> {
587 let server_id = self.server_id.read();
588 if server_id.len() > MAX_SERVER_ID_BYTES {
589 return Err(ExceptionCode::ServerDeviceFailure);
590 }
591 out.extend_from_slice(&server_id);
592 Ok(server_id.len())
593 }
594
595 async fn append_diagnostic_response(
596 &self,
597 sub_function: DiagnosticSubFunction,
598 data: &[u8],
599 out: &mut Vec<u8>,
600 ) -> Result<Option<usize>, ExceptionCode> {
601 match sub_function {
602 DiagnosticSubFunction::ReturnQueryData => {
603 if data.len() > MAX_DIAGNOSTIC_RESPONSE_DATA_LEN {
604 return Err(ExceptionCode::ServerDeviceFailure);
605 }
606 out.extend_from_slice(data);
607 Ok(Some(data.len()))
608 }
609 _ => Err(ExceptionCode::IllegalFunction),
610 }
611 }
612}