dbc_rs/message/
impls.rs

1use super::{Message, Signals};
2use crate::compat::{Comment, Name};
3
4impl Message {
5    pub(crate) fn new(
6        id: u32,
7        name: Name,
8        dlc: u8,
9        sender: Name,
10        signals: Signals,
11        comment: Option<Comment>,
12    ) -> Self {
13        // Validation should have been done prior (by builder or parse)
14        Self {
15            id,
16            name,
17            dlc,
18            sender,
19            signals,
20            comment,
21        }
22    }
23
24    /// Returns the CAN message ID.
25    ///
26    /// This returns the raw CAN ID as it would appear on the bus (11-bit or 29-bit).
27    /// For extended (29-bit) IDs, the internal flag bit is stripped.
28    /// Use [`is_extended()`](Self::is_extended) to check if this is an extended ID.
29    ///
30    /// # Examples
31    ///
32    /// ```rust,no_run
33    /// use dbc_rs::Dbc;
34    ///
35    /// let dbc = Dbc::parse(r#"VERSION "1.0"\n\nBU_: ECM\n\nBO_ 256 EngineData : 8 ECM"#)?;
36    /// let message = dbc.messages().at(0).unwrap();
37    /// assert_eq!(message.id(), 256);
38    /// # Ok::<(), dbc_rs::Error>(())
39    /// ```
40    #[inline]
41    #[must_use = "return value should be used"]
42    pub fn id(&self) -> u32 {
43        self.id & Self::MAX_EXTENDED_ID
44    }
45
46    /// Returns the raw internal ID including any extended ID flag.
47    ///
48    /// This is primarily for internal use. Most users should use [`id()`](Self::id) instead.
49    #[inline]
50    #[must_use = "return value should be used"]
51    pub(crate) fn id_with_flag(&self) -> u32 {
52        self.id
53    }
54
55    /// Returns `true` if this message uses an extended (29-bit) CAN ID.
56    ///
57    /// Standard CAN uses 11-bit identifiers (0-2047), while extended CAN uses 29-bit
58    /// identifiers (0-536870911).
59    ///
60    /// # Examples
61    ///
62    /// ```rust,no_run
63    /// use dbc_rs::Dbc;
64    ///
65    /// // Standard 11-bit ID
66    /// let dbc = Dbc::parse(r#"VERSION "1.0"\n\nBU_: ECM\n\nBO_ 256 EngineData : 8 ECM"#)?;
67    /// let message = dbc.messages().at(0).unwrap();
68    /// assert!(!message.is_extended());
69    ///
70    /// // Extended 29-bit ID (with flag bit set: 0x80000000 | 0x18DAF115)
71    /// let dbc = Dbc::parse(r#"VERSION "1.0"\n\nBU_: ECM\n\nBO_ 2564485397 OBD2 : 8 ECM"#)?;
72    /// let message = dbc.messages().at(0).unwrap();
73    /// assert!(message.is_extended());
74    /// assert_eq!(message.id(), 0x18DAF115);
75    /// # Ok::<(), dbc_rs::Error>(())
76    /// ```
77    #[inline]
78    #[must_use = "return value should be used"]
79    pub fn is_extended(&self) -> bool {
80        (self.id & Self::EXTENDED_ID_FLAG) != 0
81    }
82
83    /// Returns the message name.
84    ///
85    /// # Examples
86    ///
87    /// ```rust,no_run
88    /// use dbc_rs::Dbc;
89    ///
90    /// let dbc = Dbc::parse(r#"VERSION "1.0"\n\nBU_: ECM\n\nBO_ 256 EngineData : 8 ECM"#)?;
91    /// let message = dbc.messages().at(0).unwrap();
92    /// assert_eq!(message.name(), "EngineData");
93    /// # Ok::<(), dbc_rs::Error>(())
94    /// ```
95    #[inline]
96    #[must_use = "return value should be used"]
97    pub fn name(&self) -> &str {
98        self.name.as_str()
99    }
100
101    /// Returns the Data Length Code (DLC) in bytes.
102    ///
103    /// DLC specifies the size of the message payload. For classic CAN, this is 1-8 bytes.
104    /// For CAN FD, this can be up to 64 bytes.
105    ///
106    /// # Examples
107    ///
108    /// ```rust,no_run
109    /// use dbc_rs::Dbc;
110    ///
111    /// let dbc = Dbc::parse(r#"VERSION "1.0"\n\nBU_: ECM\n\nBO_ 256 EngineData : 8 ECM"#)?;
112    /// let message = dbc.messages().at(0).unwrap();
113    /// assert_eq!(message.dlc(), 8);
114    /// # Ok::<(), dbc_rs::Error>(())
115    /// ```
116    #[inline]
117    #[must_use = "return value should be used"]
118    pub fn dlc(&self) -> u8 {
119        self.dlc
120    }
121
122    /// Get the sender node name for this message.
123    ///
124    /// The sender is the node that transmits this message on the CAN bus.
125    ///
126    /// # Examples
127    ///
128    /// ```rust,no_run
129    /// use dbc_rs::Dbc;
130    ///
131    /// let dbc = Dbc::parse(r#"VERSION "1.0"
132    ///
133    /// BU_: ECM TCM
134    ///
135    /// BO_ 256 Engine : 8 ECM
136    ///  SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *
137    /// "#)?;
138    ///
139    /// let message = dbc.messages().iter().next().unwrap();
140    /// assert_eq!(message.sender(), "ECM");
141    /// # Ok::<(), dbc_rs::Error>(())
142    /// ```
143    #[inline]
144    #[must_use = "return value should be used"]
145    pub fn sender(&self) -> &str {
146        self.sender.as_str()
147    }
148
149    /// Returns a reference to the signals collection for this message.
150    ///
151    /// The [`Signals`] collection provides methods to iterate, search, and access signals by index.
152    ///
153    /// # Examples
154    ///
155    /// ```rust,no_run
156    /// use dbc_rs::Dbc;
157    ///
158    /// let dbc = Dbc::parse(r#"VERSION "1.0"
159    ///
160    /// BU_: ECM
161    ///
162    /// BO_ 256 Engine : 8 ECM
163    ///  SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" ECM
164    ///  SG_ Torque : 16|16@1+ (0.1,0) [0|500] "Nm" ECM
165    /// "#)?;
166    ///
167    /// let message = dbc.messages().find("Engine").unwrap();
168    /// let signals = message.signals();
169    /// assert_eq!(signals.len(), 2);
170    /// assert!(signals.find("RPM").is_some());
171    /// # Ok::<(), dbc_rs::Error>(())
172    /// ```
173    #[inline]
174    #[must_use = "return value should be used"]
175    pub fn signals(&self) -> &Signals {
176        &self.signals
177    }
178
179    /// Returns the minimum number of bytes required to decode all signals in this message.
180    ///
181    /// This calculates the actual byte coverage of all signals, which may be less than
182    /// the declared DLC. Use this when validating frame payloads for decoding - the
183    /// payload must have at least this many bytes to decode all signals successfully.
184    ///
185    /// # Examples
186    ///
187    /// ```rust,no_run
188    /// use dbc_rs::Dbc;
189    ///
190    /// let dbc = Dbc::parse(r#"VERSION "1.0"
191    ///
192    /// BU_: ECM
193    ///
194    /// BO_ 256 Engine : 8 ECM
195    ///  SG_ Temp : 0|8@1+ (1,0) [0|255] "" ECM
196    ///  SG_ Pressure : 8|8@1+ (1,0) [0|255] "" ECM
197    /// "#)?;
198    ///
199    /// let message = dbc.messages().find("Engine").unwrap();
200    /// assert_eq!(message.dlc(), 8);              // Declared DLC is 8
201    /// assert_eq!(message.min_bytes_required(), 2); // But signals only need 2 bytes
202    /// # Ok::<(), dbc_rs::Error>(())
203    /// ```
204    #[must_use = "return value should be used"]
205    pub fn min_bytes_required(&self) -> u8 {
206        if self.signals.is_empty() {
207            return 0;
208        }
209
210        let mut max_bit: u16 = 0;
211        for signal in self.signals.iter() {
212            let (_lsb, msb) =
213                Self::bit_range(signal.start_bit(), signal.length(), signal.byte_order());
214            if msb > max_bit {
215                max_bit = msb;
216            }
217        }
218
219        // Convert max bit position to bytes: (max_bit / 8) + 1
220        ((max_bit / 8) + 1) as u8
221    }
222
223    /// Returns the message comment from CM_ BO_ entry, if present.
224    #[inline]
225    #[must_use = "return value should be used"]
226    pub fn comment(&self) -> Option<&str> {
227        self.comment.as_ref().map(|c| c.as_ref())
228    }
229
230    /// Sets the message comment (from CM_ BO_ entry).
231    /// Used internally during parsing when CM_ entries are processed after messages.
232    #[inline]
233    pub(crate) fn set_comment(&mut self, comment: Comment) {
234        self.comment = Some(comment);
235    }
236
237    /// Returns a mutable reference to the signals collection.
238    /// Used internally during parsing when CM_ entries are processed after signals.
239    #[inline]
240    pub(crate) fn signals_mut(&mut self) -> &mut Signals {
241        &mut self.signals
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248    use crate::{Parser, Signal};
249
250    #[test]
251    fn test_message_getters_edge_cases() {
252        // Test with minimum values
253        let data = b"BO_ 0 A : 1 B";
254        let mut parser = Parser::new(data).unwrap();
255        let signals: &[Signal] = &[];
256        let message = Message::parse(&mut parser, signals).unwrap();
257
258        assert_eq!(message.id(), 0);
259        assert_eq!(message.name(), "A");
260        assert_eq!(message.dlc(), 1);
261        assert_eq!(message.sender(), "B");
262    }
263}