dbc_rs/dbc/
impls.rs

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