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