dbc_rs/dbc/
messages.rs

1use crate::{Error, MAX_MESSAGES, Message, Result, compat::Vec};
2#[cfg(feature = "heapless")]
3use heapless::index_map::FnvIndexMap;
4
5/// Encapsulates the messages array and count for a DBC
6///
7/// Uses `Vec<Message>` for dynamic sizing.
8/// Optionally includes an index for O(1) or O(log n) message lookup by ID.
9#[derive(Debug, Clone, PartialEq)]
10pub struct Messages {
11    messages: Vec<Message, { MAX_MESSAGES }>,
12    // Optional index for fast ID lookup (feature-flagged)
13    #[cfg(feature = "heapless")]
14    id_index: Option<FnvIndexMap<u32, usize, { MAX_MESSAGES }>>,
15    #[cfg(all(feature = "alloc", not(feature = "heapless")))]
16    sorted_indices: Option<alloc::vec::Vec<(u32, usize)>>, // (id, index) pairs sorted by id
17}
18
19impl Messages {
20    /// Create Messages from a slice of messages by cloning them
21    pub(crate) fn new(messages: &[Message]) -> Result<Self> {
22        if let Some(err) = crate::error::check_max_limit(
23            messages.len(),
24            MAX_MESSAGES,
25            Error::message(Error::NODES_TOO_MANY),
26        ) {
27            return Err(err);
28        }
29        let messages_vec: Vec<Message, { MAX_MESSAGES }> = messages.iter().cloned().collect();
30
31        // Build index for fast lookup (if features allow)
32        #[cfg(feature = "heapless")]
33        let id_index = Self::build_heapless_index(&messages_vec);
34        #[cfg(all(feature = "alloc", not(feature = "heapless")))]
35        let sorted_indices = Self::build_sorted_index(&messages_vec);
36
37        Ok(Self {
38            messages: messages_vec,
39            #[cfg(feature = "heapless")]
40            id_index,
41            #[cfg(all(feature = "alloc", not(feature = "heapless")))]
42            sorted_indices,
43        })
44    }
45
46    /// Build heapless index for O(1) lookup (only with heapless feature)
47    #[cfg(feature = "heapless")]
48    fn build_heapless_index(
49        messages: &[Message],
50    ) -> Option<FnvIndexMap<u32, usize, { MAX_MESSAGES }>> {
51        let mut index = FnvIndexMap::new();
52        for (idx, msg) in messages.iter().enumerate() {
53            if index.insert(msg.id(), idx).is_err() {
54                // If we can't insert (capacity full or duplicate), return None
55                // This should not happen in practice if MAX_MESSAGES is correct
56                return None;
57            }
58        }
59        Some(index)
60    }
61
62    /// Build sorted index for O(log n) lookup (only with alloc feature, no heapless)
63    #[cfg(all(feature = "alloc", not(feature = "heapless")))]
64    fn build_sorted_index(messages: &[Message]) -> Option<alloc::vec::Vec<(u32, usize)>> {
65        let mut indices = alloc::vec::Vec::with_capacity(messages.len());
66        for (idx, msg) in messages.iter().enumerate() {
67            indices.push((msg.id(), idx));
68        }
69        // Sort by message ID for binary search
70        indices.sort_by_key(|&(id, _)| id);
71        Some(indices)
72    }
73
74    /// Get an iterator over the messages
75    ///
76    /// # Examples
77    ///
78    /// ```rust,no_run
79    /// use dbc_rs::Dbc;
80    ///
81    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM")?;
82    /// let mut iter = dbc.messages().iter();
83    /// let message = iter.next().unwrap();
84    /// assert_eq!(message.name(), "Engine");
85    /// assert_eq!(message.id(), 256);
86    /// assert!(iter.next().is_none());
87    /// # Ok::<(), dbc_rs::Error>(())
88    /// ```
89    #[inline]
90    #[must_use = "iterator is lazy and does nothing unless consumed"]
91    pub fn iter(&self) -> impl Iterator<Item = &Message> + '_ {
92        self.messages.iter()
93    }
94
95    /// Get the number of messages
96    ///
97    /// # Examples
98    ///
99    /// ```rust,no_run
100    /// use dbc_rs::Dbc;
101    ///
102    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM")?;
103    /// assert_eq!(dbc.messages().len(), 1);
104    /// # Ok::<(), dbc_rs::Error>(())
105    /// ```
106    #[inline]
107    #[must_use = "return value should be used"]
108    pub fn len(&self) -> usize {
109        self.messages.len()
110    }
111
112    /// Returns `true` if there are no messages
113    ///
114    /// # Examples
115    ///
116    /// ```rust,no_run
117    /// use dbc_rs::Dbc;
118    ///
119    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM")?;
120    /// assert!(dbc.messages().is_empty());
121    /// # Ok::<(), dbc_rs::Error>(())
122    /// ```
123    #[inline]
124    #[must_use = "return value should be used"]
125    pub fn is_empty(&self) -> bool {
126        self.len() == 0
127    }
128
129    /// Get a message by index, or None if index is out of bounds
130    ///
131    /// # Examples
132    ///
133    /// ```rust,no_run
134    /// use dbc_rs::Dbc;
135    ///
136    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM")?;
137    /// if let Some(message) = dbc.messages().at(0) {
138    ///     assert_eq!(message.name(), "Engine");
139    /// }
140    /// # Ok::<(), dbc_rs::Error>(())
141    /// ```
142    #[inline]
143    #[must_use = "return value should be used"]
144    pub fn at(&self, index: usize) -> Option<&Message> {
145        self.messages.get(index)
146    }
147
148    /// Find a message by name, or None if not found
149    ///
150    /// # Examples
151    ///
152    /// ```rust,no_run
153    /// use dbc_rs::Dbc;
154    ///
155    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM")?;
156    /// if let Some(message) = dbc.messages().find("Engine") {
157    ///     assert_eq!(message.name(), "Engine");
158    ///     assert_eq!(message.id(), 256);
159    /// }
160    /// # Ok::<(), dbc_rs::Error>(())
161    /// ```
162    #[must_use = "return value should be used"]
163    pub fn find(&self, name: &str) -> Option<&Message> {
164        self.iter().find(|m| m.name() == name)
165    }
166
167    /// Find a message by CAN ID, or None if not found
168    ///
169    /// # Examples
170    ///
171    /// ```rust,no_run
172    /// use dbc_rs::Dbc;
173    ///
174    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM")?;
175    /// if let Some(message) = dbc.messages().find_by_id(256) {
176    ///     assert_eq!(message.name(), "Engine");
177    ///     assert_eq!(message.id(), 256);
178    /// }
179    /// # Ok::<(), dbc_rs::Error>(())
180    /// ```
181    /// Find a message by CAN ID with optimized lookup based on available features.
182    ///
183    /// - With `heapless` feature: O(1) lookup using FnvIndexMap
184    /// - With `alloc` feature (no heapless): O(log n) lookup using binary search on sorted indices
185    /// - Otherwise: O(n) linear search
186    #[inline]
187    #[must_use = "return value should be used"]
188    pub fn find_by_id(&self, id: u32) -> Option<&Message> {
189        #[cfg(feature = "heapless")]
190        {
191            if let Some(ref index) = self.id_index {
192                if let Some(&idx) = index.get(&id) {
193                    return self.messages.get(idx);
194                }
195                return None;
196            }
197        }
198
199        #[cfg(all(feature = "alloc", not(feature = "heapless")))]
200        {
201            if let Some(ref sorted) = self.sorted_indices {
202                // Binary search for O(log n) lookup
203                if let Ok(pos) = sorted.binary_search_by_key(&id, |&(msg_id, _)| msg_id) {
204                    let &(_, idx) = sorted.get(pos)?;
205                    return self.messages.get(idx);
206                }
207                return None;
208            }
209        }
210
211        // Fallback: linear search O(n)
212        // This is used when no alloc/heapless features are enabled
213        self.messages.iter().find(|m| m.id() == id)
214    }
215}