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//!
39//! As `.dbc` files may contain multiple messages, each of these can be
40//! brought into scope by referencing their name as a type (e.g. `SomeMessage`
41//! as shown above) and this determines what code is generated.  Messages
42//! not referenced will not generate any code.
43//!
44//! For cases where only certain signals within a message are needed, the
45//! `#[dbc_signals]` attribute lets you specify which ones are used.
46//!
47//! See the test cases in this crate for examples of usage.
48//!
49//! # Functionality
50//! * [x] decode signals from PDU
51//! * [ ] encode signals into PDU
52//! - [ ] generate dispatcher for decoding based on ID
53//! - [ ] support multiplexed signals
54//! - [ ] consider scoping generated types to a module
55//!
56//! # License
57//! [LICENSE-MIT]
58//!
59
60//! DBC data derive macro
61extern crate proc_macro;
62use can_dbc::{ByteOrder, MessageId, Signal, ValueType, DBC};
63use proc_macro2::TokenStream;
64use quote::{quote, TokenStreamExt};
65use std::{collections::BTreeMap, fs::read};
66use syn::{
67    parse_macro_input, Attribute, Data, DeriveInput, Expr, Fields, Ident, Lit,
68    Meta, Result, Type,
69};
70
71struct DeriveData<'a> {
72    /// Name of the struct we are deriving for
73    #[allow(dead_code)]
74    name: &'a Ident,
75    /// The parsed DBC file
76    dbc: can_dbc::DBC,
77    /// All of the messages to derive
78    messages: BTreeMap<String, MessageInfo<'a>>,
79}
80
81struct MessageInfo<'a> {
82    ident: &'a Ident,
83    attrs: &'a Vec<Attribute>,
84}
85
86/// Filter signals based on #[dbc_signals] list
87struct SignalFilter {
88    names: Vec<String>,
89}
90
91impl SignalFilter {
92    /// Create a signal filter from a message's attribute
93    fn new(message: &MessageInfo) -> Self {
94        let mut names: Vec<String> = vec![];
95        if let Some(attrs) = parse_attr(message.attrs, "dbc_signals") {
96            let list = attrs.split(",");
97            for name in list {
98                let name = name.trim();
99                names.push(name.to_string());
100            }
101        }
102        Self { names }
103    }
104
105    /// Return whether a signal should be used, i.e. whether it is
106    /// in the filter list or the list is empty
107    fn use_signal(&self, name: impl Into<String>) -> bool {
108        if self.names.is_empty() {
109            return true;
110        }
111        let name = name.into();
112        self.names.contains(&name)
113    }
114}
115
116/// Information about signal within message
117struct SignalInfo<'a> {
118    signal: &'a Signal,
119    ident: Ident,
120    ntype: Ident,
121    utype: Ident,
122    start: usize,
123    width: usize,
124    nwidth: usize,
125    scale: f32,
126    signed: bool,
127}
128
129impl<'a> SignalInfo<'a> {
130    fn new(signal: &'a Signal, message: &MessageInfo) -> Self {
131        // TODO: sanitize and/or change name format
132        let name = signal.name();
133        let signed = matches!(signal.value_type(), ValueType::Signed);
134        let width = *signal.signal_size() as usize;
135        let scale = *signal.factor() as f32;
136
137        // get storage width of signal data
138        let nwidth = match width {
139            1 => 1,
140            2..=8 => 8,
141            9..=16 => 16,
142            17..=32 => 32,
143            _ => 64,
144        };
145
146        let utype = if width == 1 {
147            "bool"
148        } else {
149            &format!("{}{}", if signed { "i" } else { "u" }, nwidth)
150        };
151
152        // get native type for signal
153        let ntype = if scale == 1.0 { utype } else { "f32" };
154
155        Self {
156            signal,
157            ident: Ident::new(name, message.ident.span()),
158            ntype: Ident::new(ntype, message.ident.span()),
159            utype: Ident::new(utype, message.ident.span()),
160            start: *signal.start_bit() as usize,
161            scale,
162            signed,
163            width,
164            nwidth,
165        }
166    }
167
168    /// Generate the code for extracting signal bits
169    fn gen_bits(&self) -> TokenStream {
170        let low = self.start / 8;
171        let left = self.start % 8;
172        let high = (self.start + self.width - 1) / 8;
173        let right = (self.start + self.width) % 8;
174        let utype = &self.utype;
175        let le = self.signal.byte_order() == &ByteOrder::LittleEndian;
176
177        let mut ts = TokenStream::new();
178        if self.width == self.nwidth && left == 0 {
179            // aligned
180            let ext = if le {
181                Ident::new("from_le_bytes", utype.span())
182            } else {
183                Ident::new("from_be_bytes", utype.span())
184            };
185            let tokens = match self.width {
186                8 => quote! {
187                    #utype::#ext([pdu[#low]])
188                },
189                16 => quote! {
190                    #utype::#ext([pdu[#low],
191                                  pdu[#low + 1]])
192                },
193                32 => quote! {
194                    #utype::#ext([pdu[#low + 0],
195                                  pdu[#low + 1],
196                                  pdu[#low + 2],
197                                  pdu[#low + 3]])
198                },
199                // NOTE: this compiles to very small code and does not
200                // involve actually fetching 8 separate bytes; e.g. on
201                // armv7 an `ldrd` to get both 32-bit values followed by
202                // two `rev` instructions to reverse the bytes.
203                64 => quote! {
204                    #utype::#ext([pdu[#low + 0],
205                                  pdu[#low + 1],
206                                  pdu[#low + 2],
207                                  pdu[#low + 3],
208                                  pdu[#low + 4],
209                                  pdu[#low + 5],
210                                  pdu[#low + 6],
211                                  pdu[#low + 7],
212                    ])
213                },
214                _ => unimplemented!(),
215            };
216            ts.append_all(tokens);
217        } else {
218            if le {
219                let count = high - low;
220                for o in 0..=count {
221                    let byte = low + o;
222                    if o == 0 {
223                        // first byte
224                        ts.append_all(quote! {
225                            let v = pdu[#byte] as #utype;
226                        });
227                        if left != 0 {
228                            if count == 0 {
229                                ts.append_all(quote! {
230                                    let v = (v >> #left) & ((1 << #left) - 1);
231                                });
232                            } else {
233                                ts.append_all(quote! {
234                                    let v = v >> #left;
235                                });
236                            }
237                        }
238                    } else {
239                        let shift = (o * 8) - left;
240                        if o == count && right != 0 {
241                            ts.append_all(quote! {
242                                let v = v | (((pdu[#byte]
243                                               & ((1 << #right) - 1))
244                                              as #utype) << #shift);
245                            });
246                        } else {
247                            ts.append_all(quote! {
248                                let v = v | ((pdu[#byte] as #utype) << #shift);
249                            });
250                        }
251                    }
252                }
253            } else {
254                // big-endian
255                let mut rem = self.width;
256                let mut byte = low;
257                while rem > 0 {
258                    if byte == low {
259                        // first byte
260                        ts.append_all(quote! {
261                            let v = pdu[#byte] as #utype;
262                        });
263                        if rem < 8 {
264                            // single byte
265                            let mask = rem - 1;
266                            let shift = left + 1 - rem;
267                            ts.append_all(quote! {
268                                let mask: #utype = (1 << #mask)
269                                    | ((1 << #mask) - 1);
270                                let v = (v >> #shift) & mask;
271                            });
272                            rem = 0;
273                        } else {
274                            // first of multiple bytes
275                            let mask = left;
276                            let shift = rem - left - 1;
277                            if mask < 7 {
278                                ts.append_all(quote! {
279                                    let mask: #utype = (1 << #mask)
280                                        | ((1 << #mask) - 1);
281                                    let v = (v & mask) << #shift;
282                                });
283                            } else {
284                                ts.append_all(quote! {
285                                    let v = v << #shift;
286                                });
287                            }
288                            rem -= left + 1;
289                        }
290                        byte += 1;
291                    } else {
292                        if rem < 8 {
293                            // last byte: take top bits
294                            let shift = 8 - rem;
295                            ts.append_all(quote! {
296                                let v = v |
297                                ((pdu[#byte] as #utype) >> #shift);
298                            });
299                            rem = 0;
300                        } else {
301                            rem -= 8;
302                            ts.append_all(quote! {
303                                let v = v |
304                                ((pdu[#byte] as #utype) << #rem);
305                            });
306                            byte += 1;
307                        }
308                    };
309                }
310            }
311            // perform sign-extension for values with fewer bits than
312            // the storage type
313            if self.signed && self.width < self.nwidth {
314                let mask = self.width - 1;
315                ts.append_all(quote! {
316                    let mask: #utype = (1 << #mask);
317                    let v = if (v & mask) != 0 {
318                        let mask = mask | (mask - 1);
319                        v | !mask
320                    } else {
321                        v
322                    };
323                });
324            }
325            ts.append_all(quote! { v });
326        }
327        quote! { { #ts } }
328    }
329
330    fn gen_decoder(&self) -> TokenStream {
331        let name = &self.ident;
332        if self.width == 1 {
333            // boolean
334            let byte = self.start / 8;
335            let bit = self.start % 8;
336            quote! {
337                self.#name = (pdu[#byte] & (1 << #bit)) != 0;
338            }
339        } else {
340            let value = self.gen_bits();
341            let ntype = &self.ntype;
342            if !self.is_float() {
343                quote! {
344                    self.#name = #value as #ntype;
345                }
346            } else {
347                let scale = self.scale;
348                let offset = *self.signal.offset() as f32;
349                quote! {
350                    self.#name = ((#value as f32) * #scale) + #offset;
351                }
352            }
353        }
354    }
355
356    fn is_float(&self) -> bool {
357        self.scale != 1.0
358    }
359}
360
361impl<'a> DeriveData<'a> {
362    fn from(input: &'a DeriveInput) -> Result<Self> {
363        // load the DBC file
364        let dbc_file = parse_attr(&input.attrs, "dbc_file")
365            .expect("No DBC file specified");
366        let contents = read(&dbc_file).expect("Could not read DBC");
367        let dbc = DBC::from_slice(&contents).expect("Could not parse DBC");
368
369        // gather all of the messages and associated attributes
370        let mut messages: BTreeMap<String, MessageInfo<'_>> =
371            Default::default();
372        match &input.data {
373            Data::Struct(data) => match &data.fields {
374                Fields::Named(fields) => {
375                    for field in &fields.named {
376                        let stype = match &field.ty {
377                            Type::Path(v) => v,
378                            _ => unimplemented!(),
379                        };
380                        let ident = &stype.path.segments[0].ident;
381                        messages.insert(
382                            ident.to_string(),
383                            MessageInfo {
384                                ident,
385                                attrs: &field.attrs,
386                            },
387                        );
388                    }
389                }
390                Fields::Unnamed(_) | Fields::Unit => unimplemented!(),
391            },
392            _ => unimplemented!(),
393        }
394
395        Ok(Self {
396            name: &input.ident,
397            dbc,
398            messages,
399        })
400    }
401
402    fn build(self) -> TokenStream {
403        let mut out = TokenStream::new();
404
405        for (name, message) in self.messages.iter() {
406            let m = self
407                .dbc
408                .messages()
409                .iter()
410                .find(|m| *m.message_name() == *name)
411                .unwrap_or_else(|| panic!("Unknown message {name}"));
412
413            let filter = SignalFilter::new(message);
414
415            let mut signals: Vec<Ident> = vec![];
416            let mut types: Vec<Ident> = vec![];
417            let mut infos: Vec<SignalInfo> = vec![];
418            for s in m.signals().iter() {
419                if !filter.use_signal(s.name()) {
420                    continue;
421                }
422
423                let signal = SignalInfo::new(s, message);
424                signals.push(signal.ident.clone());
425                types.push(signal.ntype.clone());
426                infos.push(signal);
427            }
428
429            let (id, extended) = match *m.message_id() {
430                MessageId::Standard(id) => (id as u32, false),
431                MessageId::Extended(id) => (id, true),
432            };
433
434            let dlc = *m.message_size() as usize;
435            let dlc8 = dlc as u8;
436            let ident = message.ident;
437
438            // build signal decoders
439            let mut decoders = TokenStream::new();
440            for info in infos.iter() {
441                decoders.append_all(info.gen_decoder());
442            }
443
444            out.append_all(quote! {
445                #[allow(dead_code)]
446                #[allow(non_snake_case)]
447                #[derive(Default)]
448                pub struct #ident {
449                    #(
450                        pub #signals: #types
451                    ),*
452                }
453
454                impl #ident {
455                    const ID: u32 = #id;
456                    const DLC: u8 = #dlc8;
457                    const EXTENDED: bool = #extended;
458
459                    pub fn decode(&mut self, pdu: &[u8])
460                                  -> bool {
461                        if pdu.len() != #dlc {
462                            return false
463                        }
464                        #decoders
465                        true
466                    }
467                }
468            });
469        }
470        out
471    }
472}
473
474#[proc_macro_derive(DbcData, attributes(dbc_file, dbc_signals))]
475pub fn dbc_data_derive(
476    input: proc_macro::TokenStream,
477) -> proc_macro::TokenStream {
478    derive_data(&parse_macro_input!(input as DeriveInput))
479        .unwrap_or_else(|err| err.to_compile_error())
480        .into()
481}
482
483fn derive_data(input: &DeriveInput) -> Result<TokenStream> {
484    Ok(DeriveData::from(input)?.build())
485}
486
487fn parse_attr(attrs: &[Attribute], name: &str) -> Option<String> {
488    let attr = attrs
489        .iter()
490        .filter(|a| {
491            a.path().segments.len() == 1 && a.path().segments[0].ident == name
492        })
493        .nth(0)?;
494
495    let expr = match &attr.meta {
496        Meta::NameValue(n) => Some(&n.value),
497        _ => None,
498    };
499
500    match &expr {
501        Some(Expr::Lit(e)) => match &e.lit {
502            Lit::Str(s) => Some(s.value()),
503            _ => None,
504        },
505        _ => None,
506    }
507}