dbc_rs/dbc/
impls.rs

1#[cfg(feature = "attributes")]
2use super::{AttributeDefaultsMap, AttributeDefinitionsMap, AttributeValuesMap};
3use super::{ExtMuxIndex, ExtendedMultiplexings, Messages, ValueDescriptionsMap};
4#[cfg(feature = "attributes")]
5use crate::{AttributeDefinition, AttributeValue};
6use crate::{
7    BitTiming, Dbc, ExtendedMultiplexing, Nodes, ValueDescriptions, Version, compat::Comment,
8};
9
10impl Dbc {
11    #[cfg(feature = "attributes")]
12    #[allow(clippy::too_many_arguments)]
13    pub(crate) fn new(
14        version: Option<Version>,
15        bit_timing: Option<BitTiming>,
16        nodes: Nodes,
17        messages: Messages,
18        value_descriptions: ValueDescriptionsMap,
19        extended_multiplexing: ExtendedMultiplexings,
20        comment: Option<Comment>,
21        attribute_definitions: AttributeDefinitionsMap,
22        attribute_defaults: AttributeDefaultsMap,
23        attribute_values: AttributeValuesMap,
24    ) -> Self {
25        // Build index for fast extended multiplexing lookup
26        let ext_mux_index = ExtMuxIndex::build(extended_multiplexing.as_slice());
27
28        Self {
29            version,
30            bit_timing,
31            nodes,
32            messages,
33            value_descriptions,
34            extended_multiplexing,
35            ext_mux_index,
36            comment,
37            attribute_definitions,
38            attribute_defaults,
39            attribute_values,
40        }
41    }
42
43    #[cfg(not(feature = "attributes"))]
44    pub(crate) fn new(
45        version: Option<Version>,
46        bit_timing: Option<BitTiming>,
47        nodes: Nodes,
48        messages: Messages,
49        value_descriptions: ValueDescriptionsMap,
50        extended_multiplexing: ExtendedMultiplexings,
51        comment: Option<Comment>,
52    ) -> Self {
53        // Build index for fast extended multiplexing lookup
54        let ext_mux_index = ExtMuxIndex::build(extended_multiplexing.as_slice());
55
56        Self {
57            version,
58            bit_timing,
59            nodes,
60            messages,
61            value_descriptions,
62            extended_multiplexing,
63            ext_mux_index,
64            comment,
65        }
66    }
67
68    /// Get the version of the DBC file
69    ///
70    /// # Examples
71    ///
72    /// ```rust,no_run
73    /// use dbc_rs::Dbc;
74    ///
75    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM")?;
76    /// if let Some(version) = dbc.version() {
77    ///     // Version is available
78    ///     let _ = version.as_str();
79    /// }
80    /// # Ok::<(), dbc_rs::Error>(())
81    /// ```
82    #[inline]
83    #[must_use = "return value should be used"]
84    pub fn version(&self) -> Option<&Version> {
85        self.version.as_ref()
86    }
87
88    /// Get the bit timing configuration
89    ///
90    /// The BS_ section in DBC files specifies CAN bus timing parameters.
91    /// Returns `None` if the BS_ section was empty or not present.
92    /// Returns `Some(&BitTiming)` if timing parameters were specified.
93    ///
94    /// # Examples
95    ///
96    /// ```rust,no_run
97    /// use dbc_rs::Dbc;
98    ///
99    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBS_: 500000 : 1,2\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM")?;
100    /// if let Some(bit_timing) = dbc.bit_timing() {
101    ///     println!("Baudrate: {:?}", bit_timing.baudrate());
102    /// }
103    /// # Ok::<(), dbc_rs::Error>(())
104    /// ```
105    #[inline]
106    #[must_use = "return value should be used"]
107    pub fn bit_timing(&self) -> Option<&BitTiming> {
108        self.bit_timing.as_ref()
109    }
110
111    /// Get a reference to the nodes collection
112    ///
113    /// # Examples
114    ///
115    /// ```rust,no_run
116    /// use dbc_rs::Dbc;
117    ///
118    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM TCM\n\nBO_ 256 Engine : 8 ECM")?;
119    /// let nodes = dbc.nodes();
120    /// assert_eq!(nodes.len(), 2);
121    /// // Iterate over nodes
122    /// let mut iter = nodes.iter();
123    /// assert_eq!(iter.next(), Some("ECM"));
124    /// assert_eq!(iter.next(), Some("TCM"));
125    /// assert_eq!(iter.next(), None);
126    /// # Ok::<(), dbc_rs::Error>(())
127    /// ```
128    #[inline]
129    #[must_use = "return value should be used"]
130    pub fn nodes(&self) -> &Nodes {
131        &self.nodes
132    }
133
134    /// Get a reference to the messages collection
135    ///
136    /// # Examples
137    ///
138    /// ```rust,no_run
139    /// use dbc_rs::Dbc;
140    ///
141    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM")?;
142    /// let messages = dbc.messages();
143    /// assert_eq!(messages.len(), 1);
144    /// let message = messages.at(0).unwrap();
145    /// assert_eq!(message.name(), "Engine");
146    /// assert_eq!(message.id(), 256);
147    /// # Ok::<(), dbc_rs::Error>(())
148    /// ```
149    #[inline]
150    #[must_use = "return value should be used"]
151    pub fn messages(&self) -> &Messages {
152        &self.messages
153    }
154
155    /// Get value descriptions for a specific signal
156    ///
157    /// Value descriptions map numeric signal values to human-readable text.
158    /// Returns `None` if the signal has no value descriptions.
159    ///
160    /// **Global Value Descriptions**: According to the Vector DBC specification,
161    /// a message_id of `-1` (0xFFFFFFFF) in a `VAL_` statement means the value
162    /// descriptions apply to all signals with that name in ANY message. This
163    /// method will first check for a message-specific entry, then fall back to
164    /// a global entry if one exists.
165    ///
166    /// # Examples
167    ///
168    /// ```rust,no_run
169    /// # use dbc_rs::Dbc;
170    /// # let dbc = Dbc::parse(r#"VERSION "1.0"\n\nBU_: ECM\n\nBO_ 100 Engine : 8 ECM\n SG_ Gear : 0|8@1+ (1,0) [0|5] "" *\n\nVAL_ 100 Gear 0 "Park" 1 "Reverse" ;"#)?;
171    /// if let Some(value_descriptions) = dbc.value_descriptions_for_signal(100, "Gear") {
172    ///     if let Some(desc) = value_descriptions.get(0) {
173    ///         println!("Value 0 means: {}", desc);
174    ///     }
175    /// }
176    /// # Ok::<(), dbc_rs::Error>(())
177    /// ```
178    /// Get a reference to the value descriptions list
179    ///
180    /// # Examples
181    ///
182    /// ```rust,no_run
183    /// use dbc_rs::Dbc;
184    ///
185    /// let dbc = Dbc::parse(r#"VERSION "1.0"
186    ///
187    /// BU_: ECM
188    ///
189    /// BO_ 100 Engine : 8 ECM
190    ///  SG_ Gear : 0|8@1+ (1,0) [0|5] "" *
191    ///
192    /// VAL_ 100 Gear 0 "Park" 1 "Drive" ;"#)?;
193    /// let value_descriptions_list = dbc.value_descriptions();
194    /// assert_eq!(value_descriptions_list.len(), 1);
195    /// # Ok::<(), dbc_rs::Error>(())
196    /// ```
197    #[inline]
198    #[must_use = "return value should be used"]
199    pub fn value_descriptions(&self) -> &ValueDescriptionsMap {
200        &self.value_descriptions
201    }
202
203    #[must_use = "return value should be used"]
204    pub fn value_descriptions_for_signal(
205        &self,
206        message_id: u32,
207        signal_name: &str,
208    ) -> Option<&ValueDescriptions> {
209        self.value_descriptions.for_signal(message_id, signal_name)
210    }
211
212    /// Get all extended multiplexing entries
213    ///
214    /// Returns a reference to all extended multiplexing (SG_MUL_VAL_) entries
215    /// in the DBC file.
216    ///
217    /// # Examples
218    ///
219    /// ```rust,no_run
220    /// use dbc_rs::Dbc;
221    ///
222    /// let dbc = Dbc::parse(r#"VERSION "1.0"
223    ///
224    /// BU_: ECM
225    ///
226    /// BO_ 500 MuxMessage : 8 ECM
227    ///  SG_ Mux1 M : 0|8@1+ (1,0) [0|255] ""
228    ///  SG_ Signal_A m0 : 16|16@1+ (0.1,0) [0|100] ""
229    ///
230    /// SG_MUL_VAL_ 500 Signal_A Mux1 0-5 ;
231    /// "#)?;
232    ///
233    /// let ext_mux = dbc.extended_multiplexing();
234    /// assert_eq!(ext_mux.len(), 1);
235    /// # Ok::<(), dbc_rs::Error>(())
236    /// ```
237    #[inline]
238    #[must_use = "return value should be used"]
239    pub fn extended_multiplexing(&self) -> &[ExtendedMultiplexing] {
240        self.extended_multiplexing.as_slice()
241    }
242
243    /// Get extended multiplexing entries for a specific message
244    ///
245    /// Extended multiplexing (SG_MUL_VAL_) entries define which multiplexer switch values
246    /// activate specific multiplexed signals. This method returns an iterator over
247    /// references to extended multiplexing entries for the given message ID.
248    ///
249    /// # Performance
250    ///
251    /// Returns an iterator of references (zero allocation) instead of cloning entries.
252    /// This is optimized for the decode hot path where extended multiplexing is checked
253    /// on every CAN frame.
254    ///
255    /// # Examples
256    ///
257    /// ```rust,no_run
258    /// use dbc_rs::Dbc;
259    ///
260    /// let dbc = Dbc::parse(r#"VERSION "1.0"
261    ///
262    /// BU_: ECM
263    ///
264    /// BO_ 500 ComplexMux : 8 ECM
265    ///  SG_ Mux1 M : 0|8@1+ (1,0) [0|255] ""
266    ///  SG_ Signal_A m0 : 16|16@1+ (0.1,0) [0|100] ""
267    ///
268    /// SG_MUL_VAL_ 500 Signal_A Mux1 0-5,10-15 ;
269    /// "#)?;
270    /// let extended: Vec<_> = dbc.extended_multiplexing_for_message(500).collect();
271    /// assert_eq!(extended.len(), 1);
272    /// # Ok::<(), dbc_rs::Error>(())
273    /// ```
274    #[inline]
275    #[must_use = "iterator is lazy and does nothing unless consumed"]
276    pub fn extended_multiplexing_for_message(
277        &self,
278        message_id: u32,
279    ) -> impl Iterator<Item = &ExtendedMultiplexing> + '_ {
280        self.extended_multiplexing
281            .iter()
282            .filter(move |ext_mux| ext_mux.message_id() == message_id)
283    }
284
285    /// Returns the database-level comment from CM_ (general comment), if present.
286    ///
287    /// This is the general comment for the entire DBC file, not associated with
288    /// any specific node, message, or signal.
289    ///
290    /// # Examples
291    ///
292    /// ```rust,no_run
293    /// use dbc_rs::Dbc;
294    ///
295    /// let dbc = Dbc::parse(r#"VERSION "1.0"
296    ///
297    /// BU_: ECM
298    ///
299    /// BO_ 256 Engine : 8 ECM
300    ///
301    /// CM_ "CAN database for powertrain";"#)?;
302    /// assert_eq!(dbc.comment(), Some("CAN database for powertrain"));
303    /// # Ok::<(), dbc_rs::Error>(())
304    /// ```
305    #[inline]
306    #[must_use = "return value should be used"]
307    pub fn comment(&self) -> Option<&str> {
308        self.comment.as_ref().map(|c| c.as_ref())
309    }
310
311    /// Returns the comment for a specific node from CM_ BU_ entry, if present.
312    ///
313    /// # Examples
314    ///
315    /// ```rust,no_run
316    /// use dbc_rs::Dbc;
317    ///
318    /// let dbc = Dbc::parse(r#"VERSION "1.0"
319    ///
320    /// BU_: ECM
321    ///
322    /// BO_ 256 Engine : 8 ECM
323    ///
324    /// CM_ BU_ ECM "Engine Control Module";"#)?;
325    /// assert_eq!(dbc.node_comment("ECM"), Some("Engine Control Module"));
326    /// # Ok::<(), dbc_rs::Error>(())
327    /// ```
328    #[inline]
329    #[must_use = "return value should be used"]
330    pub fn node_comment(&self, node_name: &str) -> Option<&str> {
331        self.nodes.node_comment(node_name)
332    }
333}
334
335// ============================================================================
336// Attribute Access Methods (feature-gated)
337// ============================================================================
338
339#[cfg(feature = "attributes")]
340impl Dbc {
341    /// Get all attribute definitions (BA_DEF_ entries).
342    ///
343    /// # Examples
344    ///
345    /// ```rust,no_run
346    /// use dbc_rs::Dbc;
347    ///
348    /// let dbc = Dbc::parse(r#"VERSION "1.0"
349    ///
350    /// BU_: ECM
351    ///
352    /// BO_ 256 Engine : 8 ECM
353    ///
354    /// BA_DEF_ BO_ "GenMsgCycleTime" INT 0 10000;"#)?;
355    /// assert_eq!(dbc.attribute_definitions().len(), 1);
356    /// # Ok::<(), dbc_rs::Error>(())
357    /// ```
358    #[inline]
359    #[must_use = "return value should be used"]
360    pub fn attribute_definitions(&self) -> &AttributeDefinitionsMap {
361        &self.attribute_definitions
362    }
363
364    /// Get an attribute definition by name.
365    #[inline]
366    #[must_use = "return value should be used"]
367    pub fn attribute_definition(&self, name: &str) -> Option<&AttributeDefinition> {
368        self.attribute_definitions.get(name)
369    }
370
371    /// Get all attribute defaults (BA_DEF_DEF_ entries).
372    #[inline]
373    #[must_use = "return value should be used"]
374    pub fn attribute_defaults(&self) -> &AttributeDefaultsMap {
375        &self.attribute_defaults
376    }
377
378    /// Get the default value for an attribute by name.
379    #[inline]
380    #[must_use = "return value should be used"]
381    pub fn attribute_default(&self, name: &str) -> Option<&AttributeValue> {
382        self.attribute_defaults.get(name)
383    }
384
385    /// Get all attribute values (BA_ entries).
386    #[inline]
387    #[must_use = "return value should be used"]
388    pub fn attribute_values(&self) -> &AttributeValuesMap {
389        &self.attribute_values
390    }
391
392    /// Get a network-level attribute value by name.
393    ///
394    /// # Examples
395    ///
396    /// ```rust,no_run
397    /// use dbc_rs::Dbc;
398    ///
399    /// let dbc = Dbc::parse(r#"VERSION "1.0"
400    ///
401    /// BU_: ECM
402    ///
403    /// BO_ 256 Engine : 8 ECM
404    ///
405    /// BA_DEF_ "BusType" STRING;
406    /// BA_DEF_DEF_ "BusType" "";
407    /// BA_ "BusType" "CAN";"#)?;
408    /// if let Some(value) = dbc.network_attribute("BusType") {
409    ///     assert_eq!(value.as_string(), Some("CAN"));
410    /// }
411    /// # Ok::<(), dbc_rs::Error>(())
412    /// ```
413    #[inline]
414    #[must_use = "return value should be used"]
415    pub fn network_attribute(&self, name: &str) -> Option<&AttributeValue> {
416        self.attribute_values.get_network(name)
417    }
418
419    /// Get a node attribute value by node name and attribute name.
420    #[inline]
421    #[must_use = "return value should be used"]
422    pub fn node_attribute(&self, node_name: &str, attr_name: &str) -> Option<&AttributeValue> {
423        self.attribute_values.get_node(node_name, attr_name)
424    }
425
426    /// Get a message attribute value by message ID and attribute name.
427    ///
428    /// # Examples
429    ///
430    /// ```rust,no_run
431    /// use dbc_rs::Dbc;
432    ///
433    /// let dbc = Dbc::parse(r#"VERSION "1.0"
434    ///
435    /// BU_: ECM
436    ///
437    /// BO_ 256 Engine : 8 ECM
438    ///
439    /// BA_DEF_ BO_ "GenMsgCycleTime" INT 0 10000;
440    /// BA_DEF_DEF_ "GenMsgCycleTime" 0;
441    /// BA_ "GenMsgCycleTime" BO_ 256 100;"#)?;
442    /// if let Some(value) = dbc.message_attribute(256, "GenMsgCycleTime") {
443    ///     assert_eq!(value.as_int(), Some(100));
444    /// }
445    /// # Ok::<(), dbc_rs::Error>(())
446    /// ```
447    #[inline]
448    #[must_use = "return value should be used"]
449    pub fn message_attribute(&self, message_id: u32, attr_name: &str) -> Option<&AttributeValue> {
450        self.attribute_values.get_message(message_id, attr_name)
451    }
452
453    /// Get a signal attribute value by message ID, signal name, and attribute name.
454    #[inline]
455    #[must_use = "return value should be used"]
456    pub fn signal_attribute(
457        &self,
458        message_id: u32,
459        signal_name: &str,
460        attr_name: &str,
461    ) -> Option<&AttributeValue> {
462        self.attribute_values.get_signal(message_id, signal_name, attr_name)
463    }
464
465    /// Get a network attribute value with fallback to default.
466    ///
467    /// First checks for a specific value assignment, then falls back to the
468    /// attribute's default value if no specific assignment exists.
469    #[inline]
470    #[must_use = "return value should be used"]
471    pub fn network_attribute_or_default(&self, name: &str) -> Option<&AttributeValue> {
472        self.network_attribute(name).or_else(|| self.attribute_default(name))
473    }
474
475    /// Get a node attribute value with fallback to default.
476    #[inline]
477    #[must_use = "return value should be used"]
478    pub fn node_attribute_or_default(
479        &self,
480        node_name: &str,
481        attr_name: &str,
482    ) -> Option<&AttributeValue> {
483        self.node_attribute(node_name, attr_name)
484            .or_else(|| self.attribute_default(attr_name))
485    }
486
487    /// Get a message attribute value with fallback to default.
488    #[inline]
489    #[must_use = "return value should be used"]
490    pub fn message_attribute_or_default(
491        &self,
492        message_id: u32,
493        attr_name: &str,
494    ) -> Option<&AttributeValue> {
495        self.message_attribute(message_id, attr_name)
496            .or_else(|| self.attribute_default(attr_name))
497    }
498
499    /// Get a signal attribute value with fallback to default.
500    #[inline]
501    #[must_use = "return value should be used"]
502    pub fn signal_attribute_or_default(
503        &self,
504        message_id: u32,
505        signal_name: &str,
506        attr_name: &str,
507    ) -> Option<&AttributeValue> {
508        self.signal_attribute(message_id, signal_name, attr_name)
509            .or_else(|| self.attribute_default(attr_name))
510    }
511}
512
513#[cfg(test)]
514mod tests {
515    use crate::Dbc;
516
517    #[test]
518    fn test_parse_extended_multiplexing() {
519        let dbc = Dbc::parse(
520            r#"VERSION "1.0"
521
522BU_: ECM
523
524BO_ 500 ComplexMux : 8 ECM
525 SG_ Mux1 M : 0|8@1+ (1,0) [0|255] ""
526 SG_ Signal_A m0 : 16|16@1+ (0.1,0) [0|100] "unit" *
527
528SG_MUL_VAL_ 500 Signal_A Mux1 5-10 ;
529"#,
530        )
531        .unwrap();
532
533        let ext_entries: crate::compat::Vec<_, { crate::MAX_EXTENDED_MULTIPLEXING }> =
534            dbc.extended_multiplexing_for_message(500).collect();
535        assert_eq!(
536            ext_entries.len(),
537            1,
538            "Extended multiplexing entry should be parsed"
539        );
540        assert_eq!(ext_entries[0].signal_name(), "Signal_A");
541        assert_eq!(ext_entries[0].multiplexer_switch(), "Mux1");
542        assert_eq!(ext_entries[0].value_ranges(), [(5, 10)]);
543    }
544
545    #[test]
546    fn test_version() {
547        let dbc = Dbc::parse(
548            r#"VERSION "1.0"
549
550BU_: ECM
551
552BO_ 256 Engine : 8 ECM
553"#,
554        )
555        .unwrap();
556        assert_eq!(dbc.version().map(|v| v.as_str()), Some("1.0"));
557    }
558
559    #[test]
560    fn test_nodes() {
561        let dbc = Dbc::parse(
562            r#"VERSION "1.0"
563
564BU_: ECM TCM
565
566BO_ 256 Engine : 8 ECM
567"#,
568        )
569        .unwrap();
570        assert_eq!(dbc.nodes().len(), 2);
571        assert!(dbc.nodes().contains("ECM"));
572        assert!(dbc.nodes().contains("TCM"));
573    }
574
575    #[test]
576    fn test_messages() {
577        let dbc = Dbc::parse(
578            r#"VERSION "1.0"
579
580BU_: ECM
581
582BO_ 256 Engine : 8 ECM
583"#,
584        )
585        .unwrap();
586        assert_eq!(dbc.messages().len(), 1);
587        let message = dbc.messages().at(0).unwrap();
588        assert_eq!(message.name(), "Engine");
589        assert_eq!(message.id(), 256);
590    }
591}