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}