dbc_rs/dbc/
impls.rs

1use super::{ExtMuxIndex, ExtendedMultiplexings, Messages, ValueDescriptionsMap};
2use crate::{
3    BitTiming, Dbc, ExtendedMultiplexing, Nodes, ValueDescriptions, Version, compat::Comment,
4};
5
6impl Dbc {
7    pub(crate) fn new(
8        version: Option<Version>,
9        bit_timing: Option<BitTiming>,
10        nodes: Nodes,
11        messages: Messages,
12        value_descriptions: ValueDescriptionsMap,
13        extended_multiplexing: ExtendedMultiplexings,
14        comment: Option<Comment>,
15    ) -> Self {
16        // Build index for fast extended multiplexing lookup
17        let ext_mux_index = ExtMuxIndex::build(extended_multiplexing.as_slice());
18
19        // Validation should have been done prior (by builder)
20        Self {
21            version,
22            bit_timing,
23            nodes,
24            messages,
25            value_descriptions,
26            extended_multiplexing,
27            ext_mux_index,
28            comment,
29        }
30    }
31
32    /// Get the version of the DBC file
33    ///
34    /// # Examples
35    ///
36    /// ```rust,no_run
37    /// use dbc_rs::Dbc;
38    ///
39    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM")?;
40    /// if let Some(version) = dbc.version() {
41    ///     // Version is available
42    ///     let _ = version.as_str();
43    /// }
44    /// # Ok::<(), dbc_rs::Error>(())
45    /// ```
46    #[inline]
47    #[must_use = "return value should be used"]
48    pub fn version(&self) -> Option<&Version> {
49        self.version.as_ref()
50    }
51
52    /// Get the bit timing configuration
53    ///
54    /// The BS_ section in DBC files specifies CAN bus timing parameters.
55    /// Returns `None` if the BS_ section was empty or not present.
56    /// Returns `Some(&BitTiming)` if timing parameters were specified.
57    ///
58    /// # Examples
59    ///
60    /// ```rust,no_run
61    /// use dbc_rs::Dbc;
62    ///
63    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBS_: 500000 : 1,2\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM")?;
64    /// if let Some(bit_timing) = dbc.bit_timing() {
65    ///     println!("Baudrate: {:?}", bit_timing.baudrate());
66    /// }
67    /// # Ok::<(), dbc_rs::Error>(())
68    /// ```
69    #[inline]
70    #[must_use = "return value should be used"]
71    pub fn bit_timing(&self) -> Option<&BitTiming> {
72        self.bit_timing.as_ref()
73    }
74
75    /// Get a reference to the nodes collection
76    ///
77    /// # Examples
78    ///
79    /// ```rust,no_run
80    /// use dbc_rs::Dbc;
81    ///
82    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM TCM\n\nBO_ 256 Engine : 8 ECM")?;
83    /// let nodes = dbc.nodes();
84    /// assert_eq!(nodes.len(), 2);
85    /// // Iterate over nodes
86    /// let mut iter = nodes.iter();
87    /// assert_eq!(iter.next(), Some("ECM"));
88    /// assert_eq!(iter.next(), Some("TCM"));
89    /// assert_eq!(iter.next(), None);
90    /// # Ok::<(), dbc_rs::Error>(())
91    /// ```
92    #[inline]
93    #[must_use = "return value should be used"]
94    pub fn nodes(&self) -> &Nodes {
95        &self.nodes
96    }
97
98    /// Get a reference to the messages collection
99    ///
100    /// # Examples
101    ///
102    /// ```rust,no_run
103    /// use dbc_rs::Dbc;
104    ///
105    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM")?;
106    /// let messages = dbc.messages();
107    /// assert_eq!(messages.len(), 1);
108    /// let message = messages.at(0).unwrap();
109    /// assert_eq!(message.name(), "Engine");
110    /// assert_eq!(message.id(), 256);
111    /// # Ok::<(), dbc_rs::Error>(())
112    /// ```
113    #[inline]
114    #[must_use = "return value should be used"]
115    pub fn messages(&self) -> &Messages {
116        &self.messages
117    }
118
119    /// Get value descriptions for a specific signal
120    ///
121    /// Value descriptions map numeric signal values to human-readable text.
122    /// Returns `None` if the signal has no value descriptions.
123    ///
124    /// **Global Value Descriptions**: According to the Vector DBC specification,
125    /// a message_id of `-1` (0xFFFFFFFF) in a `VAL_` statement means the value
126    /// descriptions apply to all signals with that name in ANY message. This
127    /// method will first check for a message-specific entry, then fall back to
128    /// a global entry if one exists.
129    ///
130    /// # Examples
131    ///
132    /// ```rust,no_run
133    /// # use dbc_rs::Dbc;
134    /// # 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" ;"#)?;
135    /// if let Some(value_descriptions) = dbc.value_descriptions_for_signal(100, "Gear") {
136    ///     if let Some(desc) = value_descriptions.get(0) {
137    ///         println!("Value 0 means: {}", desc);
138    ///     }
139    /// }
140    /// # Ok::<(), dbc_rs::Error>(())
141    /// ```
142    /// Get a reference to the value descriptions list
143    ///
144    /// # Examples
145    ///
146    /// ```rust,no_run
147    /// use dbc_rs::Dbc;
148    ///
149    /// let dbc = Dbc::parse(r#"VERSION "1.0"
150    ///
151    /// BU_: ECM
152    ///
153    /// BO_ 100 Engine : 8 ECM
154    ///  SG_ Gear : 0|8@1+ (1,0) [0|5] "" *
155    ///
156    /// VAL_ 100 Gear 0 "Park" 1 "Drive" ;"#)?;
157    /// let value_descriptions_list = dbc.value_descriptions();
158    /// assert_eq!(value_descriptions_list.len(), 1);
159    /// # Ok::<(), dbc_rs::Error>(())
160    /// ```
161    #[inline]
162    #[must_use = "return value should be used"]
163    pub fn value_descriptions(&self) -> &ValueDescriptionsMap {
164        &self.value_descriptions
165    }
166
167    #[must_use = "return value should be used"]
168    pub fn value_descriptions_for_signal(
169        &self,
170        message_id: u32,
171        signal_name: &str,
172    ) -> Option<&ValueDescriptions> {
173        self.value_descriptions.for_signal(message_id, signal_name)
174    }
175
176    /// Get all extended multiplexing entries
177    ///
178    /// Returns a reference to all extended multiplexing (SG_MUL_VAL_) entries
179    /// in the DBC file.
180    ///
181    /// # Examples
182    ///
183    /// ```rust,no_run
184    /// use dbc_rs::Dbc;
185    ///
186    /// let dbc = Dbc::parse(r#"VERSION "1.0"
187    ///
188    /// BU_: ECM
189    ///
190    /// BO_ 500 MuxMessage : 8 ECM
191    ///  SG_ Mux1 M : 0|8@1+ (1,0) [0|255] ""
192    ///  SG_ Signal_A m0 : 16|16@1+ (0.1,0) [0|100] ""
193    ///
194    /// SG_MUL_VAL_ 500 Signal_A Mux1 0-5 ;
195    /// "#)?;
196    ///
197    /// let ext_mux = dbc.extended_multiplexing();
198    /// assert_eq!(ext_mux.len(), 1);
199    /// # Ok::<(), dbc_rs::Error>(())
200    /// ```
201    #[inline]
202    #[must_use = "return value should be used"]
203    pub fn extended_multiplexing(&self) -> &[ExtendedMultiplexing] {
204        self.extended_multiplexing.as_slice()
205    }
206
207    /// Get extended multiplexing entries for a specific message
208    ///
209    /// Extended multiplexing (SG_MUL_VAL_) entries define which multiplexer switch values
210    /// activate specific multiplexed signals. This method returns an iterator over
211    /// references to extended multiplexing entries for the given message ID.
212    ///
213    /// # Performance
214    ///
215    /// Returns an iterator of references (zero allocation) instead of cloning entries.
216    /// This is optimized for the decode hot path where extended multiplexing is checked
217    /// on every CAN frame.
218    ///
219    /// # Examples
220    ///
221    /// ```rust,no_run
222    /// use dbc_rs::Dbc;
223    ///
224    /// let dbc = Dbc::parse(r#"VERSION "1.0"
225    ///
226    /// BU_: ECM
227    ///
228    /// BO_ 500 ComplexMux : 8 ECM
229    ///  SG_ Mux1 M : 0|8@1+ (1,0) [0|255] ""
230    ///  SG_ Signal_A m0 : 16|16@1+ (0.1,0) [0|100] ""
231    ///
232    /// SG_MUL_VAL_ 500 Signal_A Mux1 0-5,10-15 ;
233    /// "#)?;
234    /// let extended: Vec<_> = dbc.extended_multiplexing_for_message(500).collect();
235    /// assert_eq!(extended.len(), 1);
236    /// # Ok::<(), dbc_rs::Error>(())
237    /// ```
238    #[inline]
239    #[must_use = "iterator is lazy and does nothing unless consumed"]
240    pub fn extended_multiplexing_for_message(
241        &self,
242        message_id: u32,
243    ) -> impl Iterator<Item = &ExtendedMultiplexing> + '_ {
244        self.extended_multiplexing
245            .iter()
246            .filter(move |ext_mux| ext_mux.message_id() == message_id)
247    }
248
249    /// Returns the database-level comment from CM_ (general comment), if present.
250    ///
251    /// This is the general comment for the entire DBC file, not associated with
252    /// any specific node, message, or signal.
253    ///
254    /// # Examples
255    ///
256    /// ```rust,no_run
257    /// use dbc_rs::Dbc;
258    ///
259    /// let dbc = Dbc::parse(r#"VERSION "1.0"
260    ///
261    /// BU_: ECM
262    ///
263    /// BO_ 256 Engine : 8 ECM
264    ///
265    /// CM_ "CAN database for powertrain";"#)?;
266    /// assert_eq!(dbc.comment(), Some("CAN database for powertrain"));
267    /// # Ok::<(), dbc_rs::Error>(())
268    /// ```
269    #[inline]
270    #[must_use = "return value should be used"]
271    pub fn comment(&self) -> Option<&str> {
272        self.comment.as_ref().map(|c| c.as_ref())
273    }
274
275    /// Returns the comment for a specific node from CM_ BU_ entry, if present.
276    ///
277    /// # Examples
278    ///
279    /// ```rust,no_run
280    /// use dbc_rs::Dbc;
281    ///
282    /// let dbc = Dbc::parse(r#"VERSION "1.0"
283    ///
284    /// BU_: ECM
285    ///
286    /// BO_ 256 Engine : 8 ECM
287    ///
288    /// CM_ BU_ ECM "Engine Control Module";"#)?;
289    /// assert_eq!(dbc.node_comment("ECM"), Some("Engine Control Module"));
290    /// # Ok::<(), dbc_rs::Error>(())
291    /// ```
292    #[inline]
293    #[must_use = "return value should be used"]
294    pub fn node_comment(&self, node_name: &str) -> Option<&str> {
295        self.nodes.node_comment(node_name)
296    }
297}
298
299#[cfg(test)]
300mod tests {
301    use crate::Dbc;
302
303    #[test]
304    fn test_parse_extended_multiplexing() {
305        let dbc = Dbc::parse(
306            r#"VERSION "1.0"
307
308BU_: ECM
309
310BO_ 500 ComplexMux : 8 ECM
311 SG_ Mux1 M : 0|8@1+ (1,0) [0|255] ""
312 SG_ Signal_A m0 : 16|16@1+ (0.1,0) [0|100] "unit" *
313
314SG_MUL_VAL_ 500 Signal_A Mux1 5-10 ;
315"#,
316        )
317        .unwrap();
318
319        let ext_entries: crate::compat::Vec<_, { crate::MAX_EXTENDED_MULTIPLEXING }> =
320            dbc.extended_multiplexing_for_message(500).collect();
321        assert_eq!(
322            ext_entries.len(),
323            1,
324            "Extended multiplexing entry should be parsed"
325        );
326        assert_eq!(ext_entries[0].signal_name(), "Signal_A");
327        assert_eq!(ext_entries[0].multiplexer_switch(), "Mux1");
328        assert_eq!(ext_entries[0].value_ranges(), [(5, 10)]);
329    }
330
331    #[test]
332    fn test_version() {
333        let dbc = Dbc::parse(
334            r#"VERSION "1.0"
335
336BU_: ECM
337
338BO_ 256 Engine : 8 ECM
339"#,
340        )
341        .unwrap();
342        assert_eq!(dbc.version().map(|v| v.as_str()), Some("1.0"));
343    }
344
345    #[test]
346    fn test_nodes() {
347        let dbc = Dbc::parse(
348            r#"VERSION "1.0"
349
350BU_: ECM TCM
351
352BO_ 256 Engine : 8 ECM
353"#,
354        )
355        .unwrap();
356        assert_eq!(dbc.nodes().len(), 2);
357        assert!(dbc.nodes().contains("ECM"));
358        assert!(dbc.nodes().contains("TCM"));
359    }
360
361    #[test]
362    fn test_messages() {
363        let dbc = Dbc::parse(
364            r#"VERSION "1.0"
365
366BU_: ECM
367
368BO_ 256 Engine : 8 ECM
369"#,
370        )
371        .unwrap();
372        assert_eq!(dbc.messages().len(), 1);
373        let message = dbc.messages().at(0).unwrap();
374        assert_eq!(message.name(), "Engine");
375        assert_eq!(message.id(), 256);
376    }
377}