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}