dbc_rs/dbc/
impls.rs

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