dbc_rs/nodes/nodes.rs
1use crate::{
2 Parser,
3 error::{ParseError, ParseResult},
4};
5
6/// Iterator over nodes in a Nodes collection
7struct NodesIter<'a, 'b> {
8 nodes: &'b [Option<&'a str>],
9 count: usize,
10 pos: usize,
11}
12
13impl<'a, 'b> Iterator for NodesIter<'a, 'b> {
14 type Item = &'a str;
15
16 #[inline]
17 fn next(&mut self) -> Option<Self::Item> {
18 while self.pos < self.count {
19 let result = self.nodes[self.pos];
20 self.pos += 1;
21 if let Some(node) = result {
22 return Some(node);
23 }
24 }
25 None
26 }
27}
28
29/// Represents a collection of node (ECU) names from a DBC file.
30///
31/// The `BU_` statement in a DBC file lists all nodes (ECUs) on the CAN bus.
32/// This struct stores the node names as borrowed references.
33///
34/// # Examples
35///
36/// ```rust,no_run
37/// use dbc_rs::Dbc;
38///
39/// let dbc = Dbc::parse(r#"VERSION "1.0"
40///
41/// BU_: ECM TCM BCM
42///
43/// BO_ 256 Engine : 8 ECM
44/// SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
45/// "#)?;
46///
47/// // Access nodes
48/// assert_eq!(dbc.nodes().len(), 3);
49/// assert!(dbc.nodes().contains("ECM"));
50/// assert!(dbc.nodes().contains("TCM"));
51///
52/// // Iterate over nodes
53/// for node in dbc.nodes().iter() {
54/// println!("Node: {}", node);
55/// }
56/// # Ok::<(), dbc_rs::Error>(())
57/// ```
58///
59/// # Empty Nodes
60///
61/// A DBC file may have an empty node list (`BU_:` with no nodes):
62///
63/// ```rust,no_run
64/// use dbc_rs::Dbc;
65///
66/// let dbc = Dbc::parse(r#"VERSION "1.0"
67///
68/// BU_:
69///
70/// BO_ 256 Engine : 8 ECM
71/// "#)?;
72///
73/// assert!(dbc.nodes().is_empty());
74/// # Ok::<(), dbc_rs::Error>(())
75/// ```
76///
77/// # DBC Format
78///
79/// In DBC files, nodes are specified on the `BU_` line:
80/// - Format: `BU_: Node1 Node2 Node3 ...`
81/// - Node names are space-separated
82/// - Maximum of 256 nodes (DoS protection)
83/// - Duplicate node names are not allowed (case-sensitive)
84#[derive(Debug, PartialEq, Eq, Hash)]
85pub struct Nodes<'a> {
86 nodes: [Option<&'a str>; crate::MAX_NODES],
87 count: usize,
88}
89
90impl<'a> Default for Nodes<'a> {
91 fn default() -> Self {
92 Self {
93 nodes: [const { None }; crate::MAX_NODES],
94 count: 0,
95 }
96 }
97}
98
99impl<'a> Nodes<'a> {
100 // Shared validation function
101 pub(crate) fn validate_nodes(nodes: &[&str]) -> ParseResult<()> {
102 use crate::error::lang;
103 // Check for too many nodes (DoS protection)
104 if nodes.len() > crate::MAX_NODES {
105 return Err(ParseError::Nodes(crate::error::lang::NODES_TOO_MANY));
106 }
107
108 // Check for duplicate node names (case-sensitive)
109 for (i, node1) in nodes.iter().enumerate() {
110 for node2 in nodes.iter().skip(i + 1) {
111 if *node1 == *node2 {
112 return Err(ParseError::Nodes(lang::NODES_DUPLICATE_NAME));
113 }
114 }
115 }
116 Ok(())
117 }
118
119 #[cfg(any(feature = "alloc", feature = "kernel"))]
120 pub(crate) fn new(nodes: &[&'a str]) -> Self {
121 // Validation should have been done prior (by builder)
122 let mut node_array: [Option<&'a str>; crate::MAX_NODES] =
123 [const { None }; crate::MAX_NODES];
124 let count = nodes.len().min(crate::MAX_NODES);
125 for (i, node) in nodes.iter().take(crate::MAX_NODES).enumerate() {
126 node_array[i] = Some(*node);
127 }
128 Self {
129 nodes: node_array,
130 count,
131 }
132 }
133
134 #[must_use = "parse result should be checked"]
135 pub(crate) fn parse<'b: 'a>(parser: &mut Parser<'b>) -> ParseResult<Self> {
136 // Nodes parsing must always start with "BU_" keyword
137 parser
138 .expect(crate::BU_.as_bytes())
139 .map_err(|_| ParseError::Expected("Expected BU_ keyword"))?;
140
141 // Expect ":" after "BU_"
142 parser
143 .expect(b":")
144 .map_err(|_| ParseError::Expected("Expected colon after BU_"))?;
145
146 // Skip optional whitespace after ":"
147 parser.skip_newlines_and_spaces();
148
149 // Parse node names into fixed-size array
150 let mut node_names: [Option<&'b str>; crate::MAX_NODES] =
151 [const { None }; crate::MAX_NODES];
152 let mut count = 0;
153
154 loop {
155 // Skip whitespace before each node name
156 let _ = parser.skip_whitespace();
157
158 // Try to parse an identifier (node name)
159 // parse_identifier() will fail if we're at EOF
160 match parser.parse_identifier() {
161 Ok(node) => {
162 if count >= crate::MAX_NODES {
163 return Err(ParseError::Nodes(crate::error::lang::NODES_TOO_MANY));
164 }
165 node_names[count] = Some(node);
166 count += 1;
167 }
168 Err(_) => {
169 // No more identifiers, break
170 break;
171 }
172 }
173 }
174
175 if count == 0 {
176 return Ok(Nodes {
177 nodes: [const { None }; crate::MAX_NODES],
178 count: 0,
179 });
180 }
181
182 // Collect valid node names into a slice for validation and construction
183 // We use a stack-allocated array to avoid allocation
184 let mut node_refs: [&'b str; crate::MAX_NODES] = [""; crate::MAX_NODES];
185 for i in 0..count {
186 if let Some(node) = node_names[i] {
187 node_refs[i] = node;
188 }
189 }
190
191 // Validate before construction
192 Self::validate_nodes(&node_refs[..count])?;
193 // Construct directly (validation already done)
194 let mut node_array: [Option<&'a str>; crate::MAX_NODES] =
195 [const { None }; crate::MAX_NODES];
196 for (i, node) in node_refs.iter().take(count).enumerate() {
197 node_array[i] = Some(*node);
198 }
199 Ok(Self {
200 nodes: node_array,
201 count,
202 })
203 }
204
205 /// Returns an iterator over the node names.
206 ///
207 /// # Examples
208 ///
209 /// ```rust,no_run
210 /// use dbc_rs::Dbc;
211 ///
212 /// let dbc = Dbc::parse(r#"VERSION "1.0"
213 ///
214 /// BU_: ECM TCM BCM
215 /// "#)?;
216 ///
217 /// // Iterate over nodes
218 /// let mut iter = dbc.nodes().iter();
219 /// assert_eq!(iter.next(), Some("ECM"));
220 /// assert_eq!(iter.next(), Some("TCM"));
221 /// assert_eq!(iter.next(), Some("BCM"));
222 /// assert_eq!(iter.next(), None);
223 ///
224 /// // Or use in a loop
225 /// for node in dbc.nodes().iter() {
226 /// println!("Node: {}", node);
227 /// }
228 /// # Ok::<(), dbc_rs::Error>(())
229 /// ```
230 #[inline]
231 #[must_use = "iterator is lazy and does nothing unless consumed"]
232 pub fn iter(&self) -> impl Iterator<Item = &'a str> + '_ {
233 NodesIter {
234 nodes: &self.nodes,
235 count: self.count,
236 pos: 0,
237 }
238 }
239
240 /// Checks if a node name is in the list.
241 ///
242 /// The check is case-sensitive.
243 ///
244 /// # Arguments
245 ///
246 /// * `node` - The node name to check
247 ///
248 /// # Examples
249 ///
250 /// ```rust,no_run
251 /// use dbc_rs::Dbc;
252 ///
253 /// let dbc = Dbc::parse(r#"VERSION "1.0"
254 ///
255 /// BU_: ECM TCM
256 /// "#)?;
257 ///
258 /// assert!(dbc.nodes().contains("ECM"));
259 /// assert!(dbc.nodes().contains("TCM"));
260 /// assert!(!dbc.nodes().contains("BCM"));
261 /// assert!(!dbc.nodes().contains("ecm")); // Case-sensitive
262 /// # Ok::<(), dbc_rs::Error>(())
263 /// ```
264 #[inline]
265 #[must_use]
266 pub fn contains(&self, node: &str) -> bool {
267 self.iter().any(|n| n == node)
268 }
269
270 /// Returns the number of nodes in the collection.
271 ///
272 /// # Examples
273 ///
274 /// ```rust,no_run
275 /// use dbc_rs::Dbc;
276 ///
277 /// let dbc = Dbc::parse(r#"VERSION "1.0"
278 ///
279 /// BU_: ECM TCM BCM
280 /// "#)?;
281 ///
282 /// assert_eq!(dbc.nodes().len(), 3);
283 /// # Ok::<(), dbc_rs::Error>(())
284 /// ```
285 #[inline]
286 #[must_use]
287 pub fn len(&self) -> usize {
288 self.count
289 }
290
291 /// Returns `true` if there are no nodes in the collection.
292 ///
293 /// # Examples
294 ///
295 /// ```rust,no_run
296 /// use dbc_rs::Dbc;
297 ///
298 /// // Empty node list
299 /// let dbc = Dbc::parse(r#"VERSION "1.0"
300 ///
301 /// BU_:
302 /// "#)?;
303 /// assert!(dbc.nodes().is_empty());
304 ///
305 /// // With nodes
306 /// let dbc2 = Dbc::parse(r#"VERSION "1.0"
307 ///
308 /// BU_: ECM
309 /// "#)?;
310 /// assert!(!dbc2.nodes().is_empty());
311 /// # Ok::<(), dbc_rs::Error>(())
312 /// ```
313 pub fn is_empty(&self) -> bool {
314 self.count == 0
315 }
316
317 /// Gets a node by index.
318 ///
319 /// Returns `None` if the index is out of bounds.
320 ///
321 /// # Arguments
322 ///
323 /// * `index` - The zero-based index of the node
324 ///
325 /// # Examples
326 ///
327 /// ```rust,no_run
328 /// use dbc_rs::Dbc;
329 ///
330 /// let dbc = Dbc::parse(r#"VERSION "1.0"
331 ///
332 /// BU_: ECM TCM BCM
333 /// "#)?;
334 ///
335 /// assert_eq!(dbc.nodes().at(0), Some("ECM"));
336 /// assert_eq!(dbc.nodes().at(1), Some("TCM"));
337 /// assert_eq!(dbc.nodes().at(2), Some("BCM"));
338 /// assert_eq!(dbc.nodes().at(3), None); // Out of bounds
339 /// # Ok::<(), dbc_rs::Error>(())
340 /// ```
341 #[inline]
342 #[must_use]
343 pub fn at(&self, index: usize) -> Option<&'a str> {
344 if index >= self.count {
345 return None;
346 }
347 self.nodes[index]
348 }
349
350 /// Converts the nodes to their DBC file representation.
351 ///
352 /// Returns a string in the format: `BU_: Node1 Node2 Node3 ...`
353 ///
354 /// # Examples
355 ///
356 /// ```rust,no_run
357 /// use dbc_rs::Dbc;
358 ///
359 /// let dbc = Dbc::parse(r#"VERSION "1.0"
360 ///
361 /// BU_: ECM TCM BCM
362 /// "#)?;
363 ///
364 /// let dbc_string = dbc.nodes().to_dbc_string();
365 /// assert_eq!(dbc_string, "BU_: ECM TCM BCM");
366 /// # Ok::<(), dbc_rs::Error>(())
367 /// ```
368 ///
369 /// # Empty Nodes
370 ///
371 /// Empty node lists are represented as `BU_:`:
372 ///
373 /// ```rust,no_run
374 /// use dbc_rs::Dbc;
375 ///
376 /// let dbc = Dbc::parse(r#"VERSION "1.0"
377 ///
378 /// BU_:
379 /// "#)?;
380 ///
381 /// assert_eq!(dbc.nodes().to_dbc_string(), "BU_:");
382 /// # Ok::<(), dbc_rs::Error>(())
383 /// ```
384 ///
385 /// # Feature Requirements
386 ///
387 /// This method requires the `alloc` feature to be enabled.
388 #[cfg(feature = "alloc")]
389 #[must_use]
390 pub fn to_dbc_string(&self) -> alloc::string::String {
391 use alloc::string::String;
392 let mut result = String::from(crate::BU_);
393 result.push(':');
394 let nodes_str = alloc::format!("{}", self);
395 if !nodes_str.is_empty() {
396 result.push(' ');
397 result.push_str(&nodes_str);
398 }
399 result
400 }
401}
402
403#[cfg(feature = "alloc")]
404impl<'a> core::fmt::Display for Nodes<'a> {
405 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
406 if self.count == 0 {
407 return Ok(());
408 }
409 for (i, node) in self.iter().enumerate() {
410 if i > 0 {
411 write!(f, " ")?;
412 }
413 write!(f, "{}", node)?;
414 }
415 Ok(())
416 }
417}
418
419#[cfg(test)]
420mod tests {
421 #![allow(clippy::float_cmp)]
422 use super::*;
423 use crate::{
424 Parser,
425 error::{ParseError, lang},
426 };
427
428 #[test]
429 fn test_nodes_from_valid_line() {
430 let line = b"BU_: ECM TCM BCM ABS";
431 let mut parser = Parser::new(line).unwrap();
432 let nodes = Nodes::parse(&mut parser).unwrap();
433 let mut iter = nodes.iter();
434 assert_eq!(iter.next(), Some("ECM"));
435 assert_eq!(iter.next(), Some("TCM"));
436 assert_eq!(iter.next(), Some("BCM"));
437 assert_eq!(iter.next(), Some("ABS"));
438 assert_eq!(iter.next(), None);
439 }
440
441 #[test]
442 fn test_nodes_from_single_node() {
443 let line = b"BU_: ONLYONE";
444 let mut parser = Parser::new(line).unwrap();
445 let nodes = Nodes::parse(&mut parser).unwrap();
446 let mut iter = nodes.iter();
447 assert_eq!(iter.next(), Some("ONLYONE"));
448 assert_eq!(iter.next(), None);
449 }
450
451 #[test]
452 fn test_nodes_from_with_extra_spaces() {
453 let line = b"BU_: Node1 Node2 ";
454 let mut parser = Parser::new(line).unwrap();
455 let nodes = Nodes::parse(&mut parser).unwrap();
456 let mut iter = nodes.iter();
457 assert_eq!(iter.next(), Some("Node1"));
458 assert_eq!(iter.next(), Some("Node2"));
459 assert_eq!(iter.next(), None);
460 }
461
462 #[test]
463 fn test_nodes_from_empty_list() {
464 let line = b"BU_:";
465 let mut parser = Parser::new(line).unwrap();
466 let nodes = Nodes::parse(&mut parser).unwrap();
467 assert!(nodes.is_empty());
468 }
469
470 // Note: Builder tests have been moved to nodes_builder.rs
471 // This module only tests Nodes parsing and direct API usage
472
473 #[test]
474 fn test_nodes_parse_duplicate() {
475 let line = b"BU_: ECM TCM ECM";
476 let mut parser = Parser::new(line).unwrap();
477 let result = Nodes::parse(&mut parser);
478 assert!(result.is_err());
479 match result.unwrap_err() {
480 ParseError::Nodes(msg) => assert!(msg == lang::NODES_DUPLICATE_NAME),
481 _ => panic!("Expected ParseError::Nodes"),
482 }
483 }
484
485 // Note: Builder limit tests have been moved to nodes_builder.rs
486}