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