Skip to main content

fmi_ls_bus/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4use std::mem;
5
6use fmi_sys::ls_bus;
7
8#[cfg(feature = "can")]
9#[cfg_attr(docsrs, doc(cfg(feature = "can")))]
10pub mod can;
11#[cfg(test)]
12mod tests;
13
14#[derive(Debug, thiserror::Error)]
15pub enum FmiLsBusError {
16    #[error("Buffer overflow: not enough space in buffer")]
17    BufferOverflow,
18    #[error("Invalid variant code: {0}")]
19    InvalidVariant(u32),
20    #[error("Invalid operation code or size mismatch: {0}")]
21    InvalidOperation(ls_bus::fmi3LsBusOperationCode),
22}
23
24pub trait LsBusOperation<'a>: Sized {
25    /// Transmit the operation by writing it into the provided LS-BUS buffer slice.
26    /// Returns the number of bytes written.
27    fn transmit(self, buffer: &mut [u8]) -> Result<usize, FmiLsBusError>;
28
29    fn read_next_operation(
30        buffer: &'a [u8],
31        read_pos: &mut usize,
32    ) -> Result<Option<Self>, FmiLsBusError>;
33}
34
35/// A Rust wrapper around FMI-LS-BUS buffer operations.
36///
37/// This provides a safe, ergonomic interface that operates on external buffers
38/// and is binary compatible with the C `fmi3LsBusUtilBufferInfo` structure.
39///
40/// The struct tracks read and write positions while the actual buffer data
41/// is owned externally.
42#[derive(Debug, Default)]
43pub struct FmiLsBus {
44    pub read_pos: usize,
45    pub write_pos: usize,
46}
47
48impl FmiLsBus {
49    /// Create a new LS-BUS helper. The buffer itself is provided externally.
50    pub fn new() -> Self {
51        Self {
52            read_pos: 0,
53            write_pos: 0,
54        }
55    }
56
57    /// Reset the buffer to empty state by setting length and read position to 0.
58    ///
59    /// Equivalent to `FMI3_LS_BUS_BUFFER_INFO_RESET`.
60    pub fn reset(&mut self) {
61        self.read_pos = 0;
62        self.write_pos = 0;
63    }
64
65    /// Get the start address of the buffer data.
66    ///
67    /// Equivalent to `FMI3_LS_BUS_BUFFER_START`.
68    pub fn start(buffer: &[u8]) -> *const u8 {
69        buffer.as_ptr()
70    }
71
72    pub fn write_operation<'a, OP: LsBusOperation<'a>>(
73        &mut self,
74        op: OP,
75        buffer: &mut [u8],
76    ) -> Result<(), FmiLsBusError> {
77        // Get the slice starting at the current write position
78        let available_slice = &mut buffer[self.write_pos..];
79        let bytes_written = op.transmit(available_slice)?;
80        self.write_pos += bytes_written;
81        Ok(())
82    }
83
84    /// Write raw data to the buffer, replacing existing content.
85    ///
86    /// Equivalent to `FMI3_LS_BUS_BUFFER_WRITE`.
87    pub fn write(&mut self, buffer: &mut [u8], data: &[u8]) -> Result<(), FmiLsBusError> {
88        if data.len() <= buffer.len() {
89            let copy_len = data.len();
90            buffer[..copy_len].copy_from_slice(data);
91            self.read_pos = 0;
92            self.write_pos = copy_len;
93            Ok(())
94        } else {
95            Err(FmiLsBusError::BufferOverflow)
96        }
97    }
98
99    /// Peek at the next operation's opcode and length without advancing the read position.
100    pub fn peek_next_operation(
101        &self,
102        buffer: &[u8],
103    ) -> Option<(ls_bus::fmi3LsBusOperationCode, usize)> {
104        let remaining = buffer.len() - self.read_pos;
105
106        // Need at least header size
107        if remaining < mem::size_of::<ls_bus::fmi3LsBusOperationHeader>() {
108            return None;
109        }
110
111        // Read header
112        let header_bytes = &buffer
113            [self.read_pos..self.read_pos + mem::size_of::<ls_bus::fmi3LsBusOperationHeader>()];
114        let header = unsafe {
115            std::ptr::read_unaligned(
116                header_bytes.as_ptr() as *const ls_bus::fmi3LsBusOperationHeader
117            )
118        };
119
120        Some((header.opCode, header.length as usize))
121    }
122
123    /// Read the next operation from the buffer.
124    ///
125    /// Returns `Some(op)` if an operation is available,
126    /// or `None` if no complete operation is available.
127    ///
128    /// This is equivalent to `FMI3_LS_BUS_READ_NEXT_OPERATION`.
129    pub fn read_next_operation<'a, OP: LsBusOperation<'a>>(
130        &mut self,
131        buffer: &'a [u8],
132    ) -> Result<Option<OP>, FmiLsBusError> {
133        OP::read_next_operation(buffer, &mut self.read_pos)
134    }
135}