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}