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}