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}