dbc_data/lib.rs
1//! A derive-macro which produces code to access signals within CAN
2//! messages, as described by a `.dbc` file. The generated code has
3//! very few dependencies: just core primitives and `[u8]` slices, and
4//! is `#[no_std]` compatible.
5//!
6//! # Changelog
7//! [CHANGELOG.md]
8//!
9//! # Example
10//! Given a `.dbc` file containing:
11//!
12//! ```text
13//! BO_ 1023 SomeMessage: 4 Ecu1
14//! SG_ Unsigned16 : 16|16@0+ (1,0) [0|0] "" Vector__XXX
15//! SG_ Unsigned8 : 8|8@1+ (1,0) [0|0] "" Vector__XXX
16//! SG_ Signed8 : 0|8@1- (1,0) [0|0] "" Vector__XXX
17//! ```
18//! The following code can be written to access the fields of the
19//! message:
20//!
21//! ```
22//! pub use dbc_data::*;
23//!
24//! #[derive(DbcData, Default)]
25//! #[dbc_file = "tests/example.dbc"]
26//! struct TestData {
27//! some_message: SomeMessage,
28//! }
29//!
30//! fn test() {
31//! let mut t = TestData::default();
32//!
33//! assert_eq!(SomeMessage::ID, 1023);
34//! assert_eq!(SomeMessage::DLC, 4);
35//! assert!(t.some_message.decode(&[0xFE, 0x34, 0x56, 0x78]));
36//! assert_eq!(t.some_message.Signed8, -2);
37//! assert_eq!(t.some_message.Unsigned8, 0x34);
38//! assert_eq!(t.some_message.Unsigned16, 0x5678); // big-endian
39//! }
40//! ```
41//! See the test cases in this crate for examples of usage.
42//!
43//! # Code Generation
44//! This crate is aimed at embedded systems where typically some
45//! subset of the messages and signals defined in the `.dbc` file are
46//! of interest, and the rest can be ignored for a minimal footpint.
47//! If you need to decode the entire DBC into rich (possibly `std`-dependent)
48//! types to run on a host system, there are other crates for that
49//! such as `dbc_codegen`.
50//!
51//! ## Messages
52//! As `.dbc` files typically contain multiple messages, each of these
53//! can be brought into scope by referencing their name as a type
54//! (e.g. `SomeMessage` as shown above) and this determines what code
55//! is generated. Messages not referenced will not generate any code.
56//!
57//! When a range of message IDs contain the same signals, such as a
58//! series of readings which do not fit into a single message, then
59//! declaring an array will allow that type to be used for all of them.
60//!
61//! # Signals
62//! For cases where only certain signals within a message are needed, the
63//! `#[dbc_signals]` attribute lets you specify which ones are used.
64//!
65//! ## Types
66//! Single-bit signals generate `bool` types, and signals with a scale factor
67//! generate `f32` types. All other signals generate signed or unsigned
68//! native types which are large enough to fit the contained values, e.g.
69//! 13-bit signals will be stored in a `u16` and 17-bit signals will be
70//! stored in a `u32`.
71//!
72//! # Functionality
73//! * Decode signals from PDU into native types
74//! * const definitions for `ID: u32`, `DLC: u8`, `EXTENDED: bool`,
75//! and `CYCLE_TIME: usize` when present
76//! * Encode signal into PDU (except unaligned BE)
77//!
78//! # TODO
79//! * Encode unaligned BE signals
80//! * Generate dispatcher for decoding based on ID (including ranges)
81//! * Enforce that arrays of messages contain the same signals
82//! * Support multiplexed signals
83//! * Emit `enum`s for value-tables, with optional type association
84//! * (Maybe) scope generated types to a module
85//!
86//! # License
87//! [LICENSE-MIT]
88//!
89
90extern crate proc_macro;
91use can_dbc::{
92 AttributeValuedForObjectType, ByteOrder, MessageId, Signal, ValueType, DBC,
93};
94use proc_macro2::TokenStream;
95use quote::{quote, TokenStreamExt};
96use std::{collections::BTreeMap, fs::read};
97use syn::{
98 parse_macro_input, spanned::Spanned, Attribute, Data, DeriveInput, Expr,
99 Field, Fields, Ident, Lit, Meta, Result, Type,
100};
101
102struct DeriveData<'a> {
103 /// Name of the struct we are deriving for
104 #[allow(dead_code)]
105 name: &'a Ident,
106 /// The parsed DBC file
107 dbc: can_dbc::DBC,
108 /// All of the messages to derive
109 messages: BTreeMap<String, MessageInfo<'a>>,
110}
111
112struct MessageInfo<'a> {
113 id: u32,
114 extended: bool,
115 index: usize,
116 ident: &'a Ident,
117 attrs: &'a Vec<Attribute>,
118 cycle_time: Option<usize>,
119}
120
121/// Filter signals based on #[dbc_signals] list
122struct SignalFilter {
123 names: Vec<String>,
124}
125
126impl SignalFilter {
127 /// Create a signal filter from a message's attribute
128 fn new(message: &MessageInfo) -> Self {
129 let mut names: Vec<String> = vec![];
130 if let Some(attrs) = parse_attr(message.attrs, "dbc_signals") {
131 let list = attrs.split(",");
132 for name in list {
133 let name = name.trim();
134 names.push(name.to_string());
135 }
136 }
137 Self { names }
138 }
139
140 /// Return whether a signal should be used, i.e. whether it is
141 /// in the filter list or the list is empty
142 fn use_signal(&self, name: impl Into<String>) -> bool {
143 if self.names.is_empty() {
144 return true;
145 }
146 let name = name.into();
147 self.names.contains(&name)
148 }
149}
150
151/// Information about signal within message
152struct SignalInfo<'a> {
153 signal: &'a Signal,
154 ident: Ident,
155 ntype: Ident,
156 utype: Ident,
157 start: usize,
158 width: usize,
159 nwidth: usize,
160 scale: f32,
161 signed: bool,
162}
163
164impl<'a> SignalInfo<'a> {
165 fn new(signal: &'a Signal, message: &MessageInfo) -> Self {
166 // TODO: sanitize and/or change name format
167 let name = signal.name();
168 let signed = matches!(signal.value_type(), ValueType::Signed);
169 let width = *signal.signal_size() as usize;
170 let scale = *signal.factor() as f32;
171
172 // get storage width of signal data
173 let nwidth = match width {
174 1 => 1,
175 2..=8 => 8,
176 9..=16 => 16,
177 17..=32 => 32,
178 _ => 64,
179 };
180
181 let utype = if width == 1 {
182 "bool"
183 } else {
184 &format!("{}{}", if signed { "i" } else { "u" }, nwidth)
185 };
186
187 // get native type for signal
188 let ntype = if scale == 1.0 { utype } else { "f32" };
189
190 Self {
191 signal,
192 ident: Ident::new(name, message.ident.span()),
193 ntype: Ident::new(ntype, message.ident.span()),
194 utype: Ident::new(utype, message.ident.span()),
195 start: *signal.start_bit() as usize,
196 scale,
197 signed,
198 width,
199 nwidth,
200 }
201 }
202
203 /// Generate the code for extracting signal bits
204 fn extract_bits(&self) -> TokenStream {
205 let low = self.start / 8;
206 let left = self.start % 8;
207 let high = (self.start + self.width - 1) / 8;
208 let right = (self.start + self.width) % 8;
209 let utype = &self.utype;
210 let le = self.signal.byte_order() == &ByteOrder::LittleEndian;
211
212 let mut ts = TokenStream::new();
213 if self.width == self.nwidth && left == 0 {
214 // aligned
215 let ext = if le {
216 Ident::new("from_le_bytes", utype.span())
217 } else {
218 Ident::new("from_be_bytes", utype.span())
219 };
220 let tokens = match self.width {
221 8 => quote! {
222 #utype::#ext([pdu[#low]])
223 },
224 16 => quote! {
225 #utype::#ext([pdu[#low],
226 pdu[#low + 1]])
227 },
228 32 => quote! {
229 #utype::#ext([pdu[#low + 0],
230 pdu[#low + 1],
231 pdu[#low + 2],
232 pdu[#low + 3]])
233 },
234 // NOTE: this compiles to very small code and does not
235 // involve actually fetching 8 separate bytes; e.g. on
236 // armv7 an `ldrd` to get both 32-bit values followed by
237 // two `rev` instructions to reverse the bytes.
238 64 => quote! {
239 #utype::#ext([pdu[#low + 0],
240 pdu[#low + 1],
241 pdu[#low + 2],
242 pdu[#low + 3],
243 pdu[#low + 4],
244 pdu[#low + 5],
245 pdu[#low + 6],
246 pdu[#low + 7],
247 ])
248 },
249 _ => unimplemented!(),
250 };
251 ts.append_all(tokens);
252 } else {
253 if le {
254 let count = high - low;
255 for o in 0..=count {
256 let byte = low + o;
257 if o == 0 {
258 // first byte
259 ts.append_all(quote! {
260 let v = pdu[#byte] as #utype;
261 });
262 if left != 0 {
263 if count == 0 {
264 ts.append_all(quote! {
265 let v = (v >> #left) & ((1 << #left) - 1);
266 });
267 } else {
268 ts.append_all(quote! {
269 let v = v >> #left;
270 });
271 }
272 }
273 } else {
274 let shift = (o * 8) - left;
275 if o == count && right != 0 {
276 ts.append_all(quote! {
277 let v = v | (((pdu[#byte]
278 & ((1 << #right) - 1))
279 as #utype) << #shift);
280 });
281 } else {
282 ts.append_all(quote! {
283 let v = v | ((pdu[#byte] as #utype) << #shift);
284 });
285 }
286 }
287 }
288 } else {
289 // big-endian
290 let mut rem = self.width;
291 let mut byte = low;
292 while rem > 0 {
293 if byte == low {
294 // first byte
295 ts.append_all(quote! {
296 let v = pdu[#byte] as #utype;
297 });
298 if rem < 8 {
299 // single byte
300 let mask = rem - 1;
301 let shift = left + 1 - rem;
302 ts.append_all(quote! {
303 let mask: #utype = (1 << #mask)
304 | ((1 << #mask) - 1);
305 let v = (v >> #shift) & mask;
306 });
307 rem = 0;
308 } else {
309 // first of multiple bytes
310 let mask = left;
311 let shift = rem - left - 1;
312 if mask < 7 {
313 ts.append_all(quote! {
314 let mask: #utype = (1 << #mask)
315 | ((1 << #mask) - 1);
316 let v = (v & mask) << #shift;
317 });
318 } else {
319 ts.append_all(quote! {
320 let v = v << #shift;
321 });
322 }
323 rem -= left + 1;
324 }
325 byte += 1;
326 } else {
327 if rem < 8 {
328 // last byte: take top bits
329 let shift = 8 - rem;
330 ts.append_all(quote! {
331 let v = v |
332 ((pdu[#byte] as #utype) >> #shift);
333 });
334 rem = 0;
335 } else {
336 rem -= 8;
337 ts.append_all(quote! {
338 let v = v |
339 ((pdu[#byte] as #utype) << #rem);
340 });
341 byte += 1;
342 }
343 };
344 }
345 }
346 // perform sign-extension for values with fewer bits than
347 // the storage type
348 if self.signed && self.width < self.nwidth {
349 let mask = self.width - 1;
350 ts.append_all(quote! {
351 let mask: #utype = (1 << #mask);
352 let v = if (v & mask) != 0 {
353 let mask = mask | (mask - 1);
354 v | !mask
355 } else {
356 v
357 };
358 });
359 }
360 ts.append_all(quote! { v });
361 }
362 quote! { { #ts } }
363 }
364
365 fn gen_decoder(&self) -> TokenStream {
366 let name = &self.ident;
367 if self.width == 1 {
368 // boolean
369 let byte = self.start / 8;
370 let bit = self.start % 8;
371 quote! {
372 self.#name = (pdu[#byte] & (1 << #bit)) != 0;
373 }
374 } else {
375 let value = self.extract_bits();
376 let ntype = &self.ntype;
377 if !self.is_float() {
378 quote! {
379 self.#name = #value as #ntype;
380 }
381 } else {
382 let scale = self.scale;
383 let offset = *self.signal.offset() as f32;
384 quote! {
385 self.#name = ((#value as f32) * #scale) + #offset;
386 }
387 }
388 }
389 }
390
391 fn gen_encoder(&self) -> TokenStream {
392 let name = &self.ident;
393 let low = self.start / 8;
394 let mut byte = low;
395 let bit = self.start % 8;
396 if self.width == 1 {
397 // boolean
398 quote! {
399 let mask: u8 = (1 << #bit);
400 if self.#name {
401 pdu[#byte] |= mask;
402 } else {
403 pdu[#byte] &= !mask;
404 }
405 }
406 } else {
407 let utype = &self.utype;
408 let left = self.start % 8;
409 // let right = (self.start + self.width) % 8;
410 let le = self.signal.byte_order() == &ByteOrder::LittleEndian;
411
412 let mut ts = TokenStream::new();
413 if self.is_float() {
414 let scale = self.scale;
415 let offset = self.signal.offset as f32;
416 ts.append_all(quote! {
417 let v = ((self.#name - #offset) / #scale) as #utype;
418 });
419 } else {
420 ts.append_all(quote! {
421 let v = self.#name;
422 });
423 }
424 if le {
425 if self.width == self.nwidth && left == 0 {
426 // aligned little-endian
427 let mut bits = self.nwidth;
428 let mut shift = 0;
429 while bits >= 8 {
430 ts.append_all(quote! {
431 pdu[#byte] = ((v >> #shift) as u8) & 0xff;
432 });
433 bits -= 8;
434 byte += 1;
435 shift += 8;
436 }
437 } else {
438 // unaligned little-endian
439 let mut rem = self.width;
440 let mut lshift = left;
441 let mut rshift = 0;
442 while rem > 0 {
443 if rem < 8 {
444 let mask: u8 = (1 << rem) - 1;
445 let mask = mask << lshift;
446 ts.append_all(quote! {
447 pdu[#byte] = (pdu[#byte] & !#mask) |
448 ((((v >> #rshift) << (#lshift)) as u8) & #mask);
449 });
450 break;
451 }
452
453 if lshift != 0 {
454 let mask: u8 = (1 << (8 - left)) - 1;
455 let mask = mask << lshift;
456 ts.append_all(quote! {
457 pdu[#byte] = (pdu[#byte] & !#mask) |
458 ((((v >> #rshift) << (#lshift)) as u8) & #mask);
459 });
460 } else {
461 ts.append_all(quote! {
462 pdu[#byte] = ((v >> #rshift) & 0xff) as u8;
463 });
464 }
465
466 if byte == low {
467 rem -= 8 - left;
468 rshift += 8 - left;
469 } else {
470 rem -= 8;
471 rshift += 8;
472 }
473 byte += 1;
474 lshift = 0;
475 }
476 }
477 } else {
478 if self.width == self.nwidth && left == 7 {
479 // aligned big-endian
480 let mut bits = self.nwidth;
481 let mut shift = bits - 8;
482 let mut byte = (self.start - 7) / 8;
483 while bits >= 8 {
484 ts.append_all(quote! {
485 pdu[#byte] = ((v >> #shift) as u8) & 0xff;
486 });
487 bits -= 8;
488 byte += 1;
489 if shift >= 8 {
490 shift -= 8;
491 }
492 }
493 } else {
494 // unaligned big-endian
495 // todo!();
496 }
497 }
498 ts
499 }
500 }
501
502 fn is_float(&self) -> bool {
503 self.scale != 1.0
504 }
505}
506
507impl<'a> MessageInfo<'a> {
508 fn new(dbc: &DBC, field: &'a Field) -> Option<Self> {
509 let stype = match &field.ty {
510 Type::Path(v) => v,
511 Type::Array(a) => match *a.elem {
512 // TODO: validate that all signals match in ID range
513 Type::Path(ref v) => v,
514 _ => unimplemented!(),
515 },
516 _ => unimplemented!(),
517 };
518 let ident = &stype.path.segments[0].ident;
519 let name = ident.to_string();
520
521 for (index, message) in dbc.messages().iter().enumerate() {
522 if message.message_name() == &name {
523 let id = message.message_id();
524 let (id32, extended) = match *id {
525 MessageId::Standard(id) => (id as u32, false),
526 MessageId::Extended(id) => (id, true),
527 };
528 let mut cycle_time: Option<usize> = None;
529 for attr in dbc.attribute_values().iter() {
530 let value = attr.attribute_value();
531 use AttributeValuedForObjectType as AV;
532 match value {
533 AV::MessageDefinitionAttributeValue(aid, Some(av)) => {
534 if aid == id
535 && attr.attribute_name() == "GenMsgCycleTime"
536 {
537 cycle_time = Some(Self::attr_value(av));
538 }
539 }
540 _ => {}
541 }
542 }
543
544 return Some(Self {
545 id: id32,
546 extended,
547 index,
548 ident,
549 cycle_time,
550 attrs: &field.attrs,
551 });
552 }
553 }
554 None
555 }
556
557 // TODO: revisit this to handle type conversion better; we
558 // expect that the value fits in a usize for e.g. GenMsgCycleTime
559 fn attr_value(v: &can_dbc::AttributeValue) -> usize {
560 use can_dbc::AttributeValue as AV;
561 match v {
562 AV::AttributeValueU64(x) => *x as usize,
563 AV::AttributeValueI64(x) => *x as usize,
564 AV::AttributeValueF64(x) => *x as usize,
565 AV::AttributeValueCharString(_) => 0usize, // TODO: parse as int?
566 }
567 }
568}
569
570impl<'a> DeriveData<'a> {
571 fn from(input: &'a DeriveInput) -> Result<Self> {
572 // load the DBC file
573 let dbc_file = parse_attr(&input.attrs, "dbc_file")
574 .expect("No DBC file specified");
575 let contents = read(&dbc_file).expect("Could not read DBC");
576 let dbc = match DBC::from_slice(&contents) {
577 Ok(dbc) => dbc,
578 Err(can_dbc::Error::Incomplete(dbc, _)) => {
579 // TODO: emit an actual compiler warning
580 eprintln!(
581 "Warning: DBC load incomplete; some data may be missing"
582 );
583 dbc
584 }
585 Err(_) => {
586 panic!("Unable to parse {dbc_file}");
587 }
588 };
589
590 // gather all of the messages and associated attributes
591 let mut messages: BTreeMap<String, MessageInfo<'_>> =
592 Default::default();
593 match &input.data {
594 Data::Struct(data) => match &data.fields {
595 Fields::Named(fields) => {
596 for field in &fields.named {
597 if let Some(info) = MessageInfo::new(&dbc, field) {
598 messages.insert(info.ident.to_string(), info);
599 } else {
600 return Err(syn::Error::new(
601 field.span(),
602 "Unknown message",
603 ));
604 }
605 }
606 }
607 Fields::Unnamed(_) | Fields::Unit => unimplemented!(),
608 },
609 _ => unimplemented!(),
610 }
611
612 Ok(Self {
613 name: &input.ident,
614 dbc,
615 messages,
616 })
617 }
618
619 fn build(self) -> TokenStream {
620 let mut out = TokenStream::new();
621
622 for (name, message) in self.messages.iter() {
623 let m = self
624 .dbc
625 .messages()
626 .get(message.index)
627 .unwrap_or_else(|| panic!("Unknown message {name}"));
628
629 let filter = SignalFilter::new(message);
630
631 let mut signals: Vec<Ident> = vec![];
632 let mut types: Vec<Ident> = vec![];
633 let mut infos: Vec<SignalInfo> = vec![];
634 for s in m.signals().iter() {
635 if !filter.use_signal(s.name()) {
636 continue;
637 }
638
639 let signal = SignalInfo::new(s, message);
640 signals.push(signal.ident.clone());
641 types.push(signal.ntype.clone());
642 infos.push(signal);
643 }
644
645 let id = message.id;
646 let extended = message.extended;
647
648 let dlc = *m.message_size() as usize;
649 let dlc8 = dlc as u8;
650 let ident = message.ident;
651
652 // build signal decoders and encoders
653 let mut decoders = TokenStream::new();
654 let mut encoders = TokenStream::new();
655 for info in infos.iter() {
656 decoders.append_all(info.gen_decoder());
657 encoders.append_all(info.gen_encoder());
658 }
659 let cycle_time = if let Some(c) = message.cycle_time {
660 quote! {
661 const CYCLE_TIME: usize = #c;
662 }
663 } else {
664 quote! {}
665 };
666
667 out.append_all(quote! {
668 #[allow(dead_code)]
669 #[allow(non_snake_case)]
670 #[allow(non_camel_case_types)]
671 #[derive(Default)]
672 pub struct #ident {
673 #(
674 pub #signals: #types
675 ),*
676 }
677
678 impl #ident {
679 const ID: u32 = #id;
680 const DLC: u8 = #dlc8;
681 const EXTENDED: bool = #extended;
682 #cycle_time
683
684 pub fn decode(&mut self, pdu: &[u8])
685 -> bool {
686 if pdu.len() != #dlc {
687 return false
688 }
689 #decoders
690 true
691 }
692
693 pub fn encode(&mut self, pdu: &mut [u8])
694 -> bool {
695 if pdu.len() != #dlc {
696 return false
697 }
698 #encoders
699 true
700 }
701 }
702
703 impl TryFrom<&[u8]> for #ident {
704 type Error = ();
705 fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
706 let mut pdu = Self::default(); // TODO: elide
707 if pdu.decode(data) {
708 Ok(pdu)
709 } else {
710 Err(())
711 }
712 }
713 }
714 });
715 }
716 out
717 }
718}
719
720#[proc_macro_derive(DbcData, attributes(dbc_file, dbc_signals))]
721pub fn dbc_data_derive(
722 input: proc_macro::TokenStream,
723) -> proc_macro::TokenStream {
724 derive_data(&parse_macro_input!(input as DeriveInput))
725 .unwrap_or_else(|err| err.to_compile_error())
726 .into()
727}
728
729fn derive_data(input: &DeriveInput) -> Result<TokenStream> {
730 Ok(DeriveData::from(input)?.build())
731}
732
733fn parse_attr(attrs: &[Attribute], name: &str) -> Option<String> {
734 let attr = attrs
735 .iter()
736 .filter(|a| {
737 a.path().segments.len() == 1 && a.path().segments[0].ident == name
738 })
739 .nth(0)?;
740
741 let expr = match &attr.meta {
742 Meta::NameValue(n) => Some(&n.value),
743 _ => None,
744 };
745
746 match &expr {
747 Some(Expr::Lit(e)) => match &e.lit {
748 Lit::Str(s) => Some(s.value()),
749 _ => None,
750 },
751 _ => None,
752 }
753}