dbc_rs/nodes/nodes.rs
1use crate::{
2 Parser,
3 error::{ParseError, ParseResult, messages},
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::Version(messages::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::Version(lang::NODES_DUPLICATE_NAME));
113 }
114 }
115 }
116 Ok(())
117 }
118
119 #[allow(dead_code)] // Only used by builders (std-only)
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 // Expect "BU_:" keyword
137 // Note: When called from Dbc::parse, find_next_keyword already advanced past "BU_",
138 // so we try to expect "BU_" first, and if that fails, we're already past it and just expect ":"
139 if parser.expect(crate::BU_.as_bytes()).is_ok() {
140 // Successfully consumed "BU_", now expect ":"
141 parser
142 .expect(b":")
143 .map_err(|_| ParseError::Expected("Expected colon after BU_"))?;
144 } else {
145 // Already past "BU_" from find_next_keyword
146 // find_next_keyword advances to right after "BU_", which should be at ":" or whitespace
147 // Try to expect ":" - if it fails, skip whitespace and try again
148 if parser.expect(b":").is_err() {
149 // Not at ":", skip whitespace and try again
150 parser.skip_newlines_and_spaces();
151 parser
152 .expect(b":")
153 .map_err(|_| ParseError::Expected("Expected colon after BU_"))?;
154 }
155 }
156
157 // Skip optional whitespace after ":"
158 parser.skip_newlines_and_spaces();
159
160 // Parse node names into fixed-size array
161 let mut node_names: [Option<&'b str>; crate::MAX_NODES] =
162 [const { None }; crate::MAX_NODES];
163 let mut count = 0;
164
165 loop {
166 // Skip whitespace before each node name
167 let _ = parser.skip_whitespace();
168
169 // Try to parse an identifier (node name)
170 // parse_identifier() will fail if we're at EOF
171 match parser.parse_identifier() {
172 Ok(node) => {
173 if count >= crate::MAX_NODES {
174 return Err(ParseError::Version(messages::NODES_TOO_MANY));
175 }
176 node_names[count] = Some(node);
177 count += 1;
178 }
179 Err(_) => {
180 // No more identifiers, break
181 break;
182 }
183 }
184 }
185
186 if count == 0 {
187 return Ok(Nodes {
188 nodes: [const { None }; crate::MAX_NODES],
189 count: 0,
190 });
191 }
192
193 // Collect valid node names into a slice for validation and construction
194 // We use a stack-allocated array to avoid allocation
195 let mut node_refs: [&'b str; crate::MAX_NODES] = [""; crate::MAX_NODES];
196 for i in 0..count {
197 if let Some(node) = node_names[i] {
198 node_refs[i] = node;
199 }
200 }
201
202 // Validate before construction
203 Self::validate_nodes(&node_refs[..count])?;
204 // Construct directly (validation already done)
205 let mut node_array: [Option<&'a str>; crate::MAX_NODES] =
206 [const { None }; crate::MAX_NODES];
207 for (i, node) in node_refs.iter().take(count).enumerate() {
208 node_array[i] = Some(*node);
209 }
210 Ok(Self {
211 nodes: node_array,
212 count,
213 })
214 }
215
216 /// Returns an iterator over the node names.
217 ///
218 /// # Examples
219 ///
220 /// ```rust,no_run
221 /// use dbc_rs::Dbc;
222 ///
223 /// let dbc = Dbc::parse(r#"VERSION "1.0"
224 ///
225 /// BU_: ECM TCM BCM
226 /// "#)?;
227 ///
228 /// // Iterate over nodes
229 /// let mut iter = dbc.nodes().iter();
230 /// assert_eq!(iter.next(), Some("ECM"));
231 /// assert_eq!(iter.next(), Some("TCM"));
232 /// assert_eq!(iter.next(), Some("BCM"));
233 /// assert_eq!(iter.next(), None);
234 ///
235 /// // Or use in a loop
236 /// for node in dbc.nodes().iter() {
237 /// println!("Node: {}", node);
238 /// }
239 /// # Ok::<(), dbc_rs::Error>(())
240 /// ```
241 #[inline]
242 #[must_use = "iterator is lazy and does nothing unless consumed"]
243 pub fn iter(&self) -> impl Iterator<Item = &'a str> + '_ {
244 NodesIter {
245 nodes: &self.nodes,
246 count: self.count,
247 pos: 0,
248 }
249 }
250
251 /// Checks if a node name is in the list.
252 ///
253 /// The check is case-sensitive.
254 ///
255 /// # Arguments
256 ///
257 /// * `node` - The node name to check
258 ///
259 /// # Examples
260 ///
261 /// ```rust,no_run
262 /// use dbc_rs::Dbc;
263 ///
264 /// let dbc = Dbc::parse(r#"VERSION "1.0"
265 ///
266 /// BU_: ECM TCM
267 /// "#)?;
268 ///
269 /// assert!(dbc.nodes().contains("ECM"));
270 /// assert!(dbc.nodes().contains("TCM"));
271 /// assert!(!dbc.nodes().contains("BCM"));
272 /// assert!(!dbc.nodes().contains("ecm")); // Case-sensitive
273 /// # Ok::<(), dbc_rs::Error>(())
274 /// ```
275 #[inline]
276 #[must_use]
277 pub fn contains(&self, node: &str) -> bool {
278 self.iter().any(|n| n == node)
279 }
280
281 /// Returns the number of nodes in the collection.
282 ///
283 /// # Examples
284 ///
285 /// ```rust,no_run
286 /// use dbc_rs::Dbc;
287 ///
288 /// let dbc = Dbc::parse(r#"VERSION "1.0"
289 ///
290 /// BU_: ECM TCM BCM
291 /// "#)?;
292 ///
293 /// assert_eq!(dbc.nodes().len(), 3);
294 /// # Ok::<(), dbc_rs::Error>(())
295 /// ```
296 #[inline]
297 #[must_use]
298 pub fn len(&self) -> usize {
299 self.count
300 }
301
302 /// Returns `true` if there are no nodes in the collection.
303 ///
304 /// # Examples
305 ///
306 /// ```rust,no_run
307 /// use dbc_rs::Dbc;
308 ///
309 /// // Empty node list
310 /// let dbc = Dbc::parse(r#"VERSION "1.0"
311 ///
312 /// BU_:
313 /// "#)?;
314 /// assert!(dbc.nodes().is_empty());
315 ///
316 /// // With nodes
317 /// let dbc2 = Dbc::parse(r#"VERSION "1.0"
318 ///
319 /// BU_: ECM
320 /// "#)?;
321 /// assert!(!dbc2.nodes().is_empty());
322 /// # Ok::<(), dbc_rs::Error>(())
323 /// ```
324 pub fn is_empty(&self) -> bool {
325 self.count == 0
326 }
327
328 /// Gets a node by index.
329 ///
330 /// Returns `None` if the index is out of bounds.
331 ///
332 /// # Arguments
333 ///
334 /// * `index` - The zero-based index of the node
335 ///
336 /// # Examples
337 ///
338 /// ```rust,no_run
339 /// use dbc_rs::Dbc;
340 ///
341 /// let dbc = Dbc::parse(r#"VERSION "1.0"
342 ///
343 /// BU_: ECM TCM BCM
344 /// "#)?;
345 ///
346 /// assert_eq!(dbc.nodes().at(0), Some("ECM"));
347 /// assert_eq!(dbc.nodes().at(1), Some("TCM"));
348 /// assert_eq!(dbc.nodes().at(2), Some("BCM"));
349 /// assert_eq!(dbc.nodes().at(3), None); // Out of bounds
350 /// # Ok::<(), dbc_rs::Error>(())
351 /// ```
352 #[inline]
353 #[must_use]
354 pub fn at(&self, index: usize) -> Option<&'a str> {
355 if index >= self.count {
356 return None;
357 }
358 self.nodes[index]
359 }
360
361 /// Converts the nodes to their DBC file representation.
362 ///
363 /// Returns a string in the format: `BU_: Node1 Node2 Node3 ...`
364 ///
365 /// # Examples
366 ///
367 /// ```rust,no_run
368 /// use dbc_rs::Dbc;
369 ///
370 /// let dbc = Dbc::parse(r#"VERSION "1.0"
371 ///
372 /// BU_: ECM TCM BCM
373 /// "#)?;
374 ///
375 /// let dbc_string = dbc.nodes().to_dbc_string();
376 /// assert_eq!(dbc_string, "BU_: ECM TCM BCM");
377 /// # Ok::<(), dbc_rs::Error>(())
378 /// ```
379 ///
380 /// # Empty Nodes
381 ///
382 /// Empty node lists are represented as `BU_:`:
383 ///
384 /// ```rust,no_run
385 /// use dbc_rs::Dbc;
386 ///
387 /// let dbc = Dbc::parse(r#"VERSION "1.0"
388 ///
389 /// BU_:
390 /// "#)?;
391 ///
392 /// assert_eq!(dbc.nodes().to_dbc_string(), "BU_:");
393 /// # Ok::<(), dbc_rs::Error>(())
394 /// ```
395 ///
396 /// # Feature Requirements
397 ///
398 /// This method requires the `alloc` feature to be enabled.
399 #[cfg(feature = "alloc")]
400 #[must_use]
401 pub fn to_dbc_string(&self) -> alloc::string::String {
402 use alloc::string::String;
403 let mut result = String::from(crate::BU_);
404 result.push(':');
405 let nodes_str = alloc::format!("{}", self);
406 if !nodes_str.is_empty() {
407 result.push(' ');
408 result.push_str(&nodes_str);
409 }
410 result
411 }
412}
413
414#[cfg(feature = "alloc")]
415impl<'a> core::fmt::Display for Nodes<'a> {
416 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
417 if self.count == 0 {
418 return Ok(());
419 }
420 for (i, node) in self.iter().enumerate() {
421 if i > 0 {
422 write!(f, " ")?;
423 }
424 write!(f, "{}", node)?;
425 }
426 Ok(())
427 }
428}
429
430#[cfg(test)]
431mod tests {
432 #![allow(clippy::float_cmp)]
433 use super::*;
434 use crate::{
435 Parser,
436 error::{ParseError, lang},
437 };
438
439 #[test]
440 fn test_nodes_from_valid_line() {
441 let line = b"BU_: ECM TCM BCM ABS";
442 let mut parser = Parser::new(line).unwrap();
443 let nodes = Nodes::parse(&mut parser).unwrap();
444 let mut iter = nodes.iter();
445 assert_eq!(iter.next(), Some("ECM"));
446 assert_eq!(iter.next(), Some("TCM"));
447 assert_eq!(iter.next(), Some("BCM"));
448 assert_eq!(iter.next(), Some("ABS"));
449 assert_eq!(iter.next(), None);
450 }
451
452 #[test]
453 fn test_nodes_from_single_node() {
454 let line = b"BU_: ONLYONE";
455 let mut parser = Parser::new(line).unwrap();
456 let nodes = Nodes::parse(&mut parser).unwrap();
457 let mut iter = nodes.iter();
458 assert_eq!(iter.next(), Some("ONLYONE"));
459 assert_eq!(iter.next(), None);
460 }
461
462 #[test]
463 fn test_nodes_from_with_extra_spaces() {
464 let line = b"BU_: Node1 Node2 ";
465 let mut parser = Parser::new(line).unwrap();
466 let nodes = Nodes::parse(&mut parser).unwrap();
467 let mut iter = nodes.iter();
468 assert_eq!(iter.next(), Some("Node1"));
469 assert_eq!(iter.next(), Some("Node2"));
470 assert_eq!(iter.next(), None);
471 }
472
473 #[test]
474 fn test_nodes_from_empty_list() {
475 let line = b"BU_:";
476 let mut parser = Parser::new(line).unwrap();
477 let nodes = Nodes::parse(&mut parser).unwrap();
478 assert!(nodes.is_empty());
479 }
480
481 // Note: Builder tests have been moved to nodes_builder.rs
482 // This module only tests Nodes parsing and direct API usage
483
484 #[test]
485 fn test_nodes_parse_duplicate() {
486 let line = b"BU_: ECM TCM ECM";
487 let mut parser = Parser::new(line).unwrap();
488 let result = Nodes::parse(&mut parser);
489 assert!(result.is_err());
490 match result.unwrap_err() {
491 ParseError::Version(msg) => assert!(msg == lang::NODES_DUPLICATE_NAME),
492 _ => panic!("Expected ParseError::Version"),
493 }
494 }
495
496 // Note: Builder limit tests have been moved to nodes_builder.rs
497}