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}