1use std::collections::HashMap;
15use std::path::{Path, PathBuf};
16use std::sync::Arc;
17
18use crate::formats::SpecError;
19
20pub const MAX_FILE: u64 = 64 << 20;
22const MAX_MESSAGES: usize = 65_536;
24const MAX_SIGNALS: usize = 4096;
25const MAX_VALUES: usize = 65_536;
26const MAX_STATEMENT: usize = 1 << 20;
28
29#[derive(Debug, Clone, PartialEq)]
31pub struct Signal {
32 pub name: String,
33 pub start: u32,
34 pub length: u32,
35 pub big_endian: bool,
37 pub signed: bool,
38 pub factor: f64,
39 pub offset: f64,
40 pub unit: String,
41 pub mux: Mux,
42 pub float: u8,
44 pub values: Arc<std::collections::BTreeMap<i64, String>>,
46 pub comment: Option<String>,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum Mux {
52 Plain,
53 Multiplexer,
55 When(u64),
57}
58
59#[derive(Debug, Clone, PartialEq)]
61pub struct Message {
62 pub id: u32,
63 pub extended: bool,
64 pub name: String,
65 pub dlc: u32,
66 pub signals: Vec<Signal>,
67 pub comment: Option<String>,
68}
69
70#[derive(Debug, Clone, PartialEq)]
72pub struct Dbc {
73 pub name: String,
74 pub path: Option<PathBuf>,
75 pub interface: Option<String>,
77 pub messages: Vec<Message>,
78 pub notes: Vec<String>,
80}
81
82impl Dbc {
83 pub fn applies(&self, interface: &str) -> bool {
85 self.interface.as_deref().is_none_or(|i| i == interface)
86 }
87}
88
89fn error(path: Option<&Path>, line: usize, message: impl Into<String>) -> SpecError {
90 SpecError {
91 path: path.map(Path::to_path_buf),
92 line,
93 column: if line > 0 { 1 } else { 0 },
94 message: message.into(),
95 }
96}
97
98pub fn is_dbc_toml(text: &str) -> bool {
100 text.parse::<toml::Table>()
101 .is_ok_and(|t| t.get("kind").and_then(|k| k.as_str()) == Some("dbc"))
102}
103
104pub fn load(path: &Path) -> Result<Option<Dbc>, SpecError> {
107 let read = |path: &Path| -> Result<String, SpecError> {
108 let size = std::fs::metadata(path)
109 .map_err(|e| {
110 error(
111 Some(path),
112 0,
113 format!(
114 "could not read it. {}",
115 crate::error_display::user_message_from_io(&e, None)
116 ),
117 )
118 })?
119 .len();
120 if size > MAX_FILE {
121 return Err(error(
122 Some(path),
123 0,
124 format!(
125 "{} MiB is past the {} MiB a DBC dictionary may be",
126 size >> 20,
127 MAX_FILE >> 20
128 ),
129 ));
130 }
131 let bytes = std::fs::read(path).map_err(|e| {
132 error(
133 Some(path),
134 0,
135 format!(
136 "could not read it. {}",
137 crate::error_display::user_message_from_io(&e, None)
138 ),
139 )
140 })?;
141 Ok(match String::from_utf8(bytes) {
143 Ok(text) => text,
144 Err(e) => e.into_bytes().iter().map(|&b| char::from(b)).collect(),
145 })
146 };
147 let toml = path
148 .extension()
149 .is_some_and(|e| e.eq_ignore_ascii_case("toml"));
150 if !toml {
151 let text = read(path)?;
152 let name = path
153 .file_stem()
154 .map(|s| s.to_string_lossy().into_owned())
155 .unwrap_or_default();
156 return parse(&text, &name, Some(path)).map(Some);
157 }
158 let text = read(path)?;
159 if !is_dbc_toml(&text) {
160 return Ok(None);
161 }
162 let table: toml::Table = text
163 .parse()
164 .map_err(|e| error(Some(path), 0, format!("not TOML: {e}")))?;
165 let file = table.get("file").and_then(|f| f.as_str()).ok_or_else(|| {
166 error(
167 Some(path),
168 0,
169 "kind = \"dbc\" names its .dbc file with file = \"...\"",
170 )
171 })?;
172 let interface = match table.get("match") {
173 None => None,
174 Some(toml::Value::Table(m)) => match m.get("interface").or_else(|| m.get("bus")) {
175 Some(toml::Value::String(s)) => Some(s.clone()),
176 _ => {
177 return Err(error(Some(path), 0, "[match] takes interface = \"can0\""));
178 }
179 },
180 Some(_) => return Err(error(Some(path), 0, "match is a table: [match]")),
181 };
182 let dbc_path = crate::config::expand_path(file);
183 let dbc_path = if dbc_path.is_relative() {
184 path.parent().unwrap_or(Path::new("")).join(dbc_path)
185 } else {
186 dbc_path
187 };
188 let name = table
189 .get("name")
190 .and_then(|n| n.as_str())
191 .map(str::to_string)
192 .unwrap_or_else(|| {
193 path.file_stem()
194 .map(|s| s.to_string_lossy().into_owned())
195 .unwrap_or_default()
196 });
197 let text = read(&dbc_path)?;
198 let mut dbc = parse(&text, &name, Some(&dbc_path))?;
199 dbc.interface = interface;
200 Ok(Some(dbc))
201}
202
203#[derive(Debug, Clone, PartialEq)]
205enum Token {
206 Word(String),
207 Str(String),
208 Punct(char),
209}
210
211fn tokens(text: &str) -> Vec<Token> {
212 let mut out = Vec::new();
213 let mut chars = text.char_indices().peekable();
214 while let Some((i, c)) = chars.next() {
215 if c.is_whitespace() {
216 continue;
217 }
218 if c == '"' {
219 let mut s = String::new();
220 let mut escaped = false;
221 for (_, c) in chars.by_ref() {
222 if escaped {
223 s.push(c);
224 escaped = false;
225 } else if c == '\\' {
226 escaped = true;
227 } else if c == '"' {
228 break;
229 } else if s.len() < 1 << 16 {
230 s.push(c);
231 }
232 }
233 out.push(Token::Str(s));
234 } else if ":|@(),[];".contains(c) {
235 out.push(Token::Punct(c));
236 } else {
237 let mut end = i + c.len_utf8();
238 while let Some(&(j, d)) = chars.peek() {
239 if d.is_whitespace() || d == '"' || ":|@(),[];".contains(d) {
240 break;
241 }
242 end = j + d.len_utf8();
243 chars.next();
244 }
245 out.push(Token::Word(text[i..end].to_string()));
246 }
247 }
248 out
249}
250
251const STATEMENTS: [&str; 7] = [
253 "VAL_",
254 "CM_",
255 "SIG_VALTYPE_",
256 "SG_MUL_VAL_",
257 "BA_",
258 "BA_DEF_",
259 "BA_DEF_DEF_",
260];
261
262pub fn parse(text: &str, name: &str, path: Option<&Path>) -> Result<Dbc, SpecError> {
264 let mut dbc = Dbc {
265 name: name.to_string(),
266 path: path.map(Path::to_path_buf),
267 interface: None,
268 messages: Vec::new(),
269 notes: Vec::new(),
270 };
271 let mut by_id: HashMap<(u32, bool), usize> = HashMap::new();
272 let mut values: Vec<(u32, String, std::collections::BTreeMap<i64, String>)> = Vec::new();
273 let mut floats: Vec<(u32, String, u8)> = Vec::new();
274 let mut comments: Vec<(u32, Option<String>, String)> = Vec::new();
275 let mut extended_mux = false;
276 let mut lines = text.lines().enumerate();
277 while let Some((n, line)) = lines.next() {
278 let trimmed = line.trim_start();
279 let keyword = trimmed.split_whitespace().next().unwrap_or("");
280 match keyword {
281 "BO_" => {
282 let t = tokens(trimmed);
283 let (Some(Token::Word(raw)), Some(Token::Word(msg)), Some(Token::Punct(':'))) =
285 (t.get(1), t.get(2), t.get(3))
286 else {
287 return Err(error(path, n + 1, "BO_ is BO_ id name: dlc sender"));
288 };
289 let raw: u32 = raw
290 .parse()
291 .map_err(|_| error(path, n + 1, format!("BO_ id {raw} is not a number")))?;
292 let dlc = match t.get(4) {
293 Some(Token::Word(d)) => d.parse().unwrap_or(8),
294 _ => 8,
295 };
296 if raw == 0xC000_0000 {
298 continue;
299 }
300 if dbc.messages.len() >= MAX_MESSAGES {
301 return Err(error(
302 path,
303 n + 1,
304 format!("more than {MAX_MESSAGES} messages"),
305 ));
306 }
307 let extended = raw & 0x8000_0000 != 0;
308 let id = raw & 0x1FFF_FFFF;
309 by_id.insert((raw, false), dbc.messages.len());
310 dbc.messages.push(Message {
311 id,
312 extended,
313 name: msg.clone(),
314 dlc,
315 signals: Vec::new(),
316 comment: None,
317 });
318 }
319 "SG_" => {
320 let signal = parse_signal(&tokens(trimmed))
321 .ok_or_else(|| error(path, n + 1, "SG_ is SG_ name [M|mN] : start|length@order+/- (factor,offset) [min|max] \"unit\" receivers"))?;
322 let Some(message) = dbc.messages.last_mut() else {
323 return Err(error(path, n + 1, "SG_ before any BO_"));
324 };
325 if message.signals.len() >= MAX_SIGNALS {
326 return Err(error(
327 path,
328 n + 1,
329 format!("more than {MAX_SIGNALS} signals in {}", message.name),
330 ));
331 }
332 message.signals.push(signal);
333 }
334 k if STATEMENTS.contains(&k) => {
335 if trimmed.split_whitespace().nth(1).is_none() {
337 continue;
338 }
339 let mut statement = trimmed.to_string();
340 while !ends_statement(&statement) {
341 let Some((_, next)) = lines.next() else {
342 break;
343 };
344 if statement.len() > MAX_STATEMENT {
345 return Err(error(path, n + 1, format!("a {k} statement does not end")));
346 }
347 statement.push('\n');
348 statement.push_str(next);
349 }
350 let t = tokens(&statement);
351 match k {
352 "VAL_" => {
353 if let (Some(Token::Word(id)), Some(Token::Word(sig))) =
354 (t.get(1), t.get(2))
355 && let Ok(id) = id.parse::<u32>()
356 {
357 let mut map = std::collections::BTreeMap::new();
358 let mut i = 3;
359 while let (Some(Token::Word(v)), Some(Token::Str(label))) =
360 (t.get(i), t.get(i + 1))
361 {
362 if let Ok(v) = v
363 .parse::<i64>()
364 .or_else(|_| v.parse::<f64>().map(|f| f as i64))
365 {
366 map.insert(v, label.clone());
367 }
368 if map.len() >= MAX_VALUES {
369 break;
370 }
371 i += 2;
372 }
373 values.push((id, sig.clone(), map));
374 }
375 }
376 "SIG_VALTYPE_" => {
377 if let (Some(Token::Word(id)), Some(Token::Word(sig))) =
378 (t.get(1), t.get(2))
379 && let Ok(id) = id.parse::<u32>()
380 && let Some(Token::Word(kind)) =
381 t.iter().skip(3).find(|t| matches!(t, Token::Word(_)))
382 && let Ok(kind) = kind.parse::<u8>()
383 {
384 floats.push((id, sig.clone(), kind));
385 }
386 }
387 "CM_" => match (t.get(1), t.get(2), t.get(3), t.get(4)) {
388 (
389 Some(Token::Word(w)),
390 Some(Token::Word(id)),
391 Some(Token::Word(sig)),
392 Some(Token::Str(c)),
393 ) if w == "SG_" => {
394 if let Ok(id) = id.parse() {
395 comments.push((id, Some(sig.clone()), c.clone()));
396 }
397 }
398 (Some(Token::Word(w)), Some(Token::Word(id)), Some(Token::Str(c)), _)
399 if w == "BO_" =>
400 {
401 if let Ok(id) = id.parse() {
402 comments.push((id, None, c.clone()));
403 }
404 }
405 _ => {}
406 },
407 "SG_MUL_VAL_" => extended_mux = true,
408 _ => {}
409 }
410 }
411 _ => {}
412 }
413 }
414 for (raw, sig, map) in values {
415 if let Some(&m) = by_id.get(&(raw, false))
416 && let Some(s) = dbc.messages[m].signals.iter_mut().find(|s| s.name == sig)
417 {
418 s.values = Arc::new(map);
419 }
420 }
421 for (raw, sig, kind) in floats {
422 if let Some(&m) = by_id.get(&(raw, false))
423 && let Some(s) = dbc.messages[m].signals.iter_mut().find(|s| s.name == sig)
424 && (kind == 1 && s.length == 32 || kind == 2 && s.length == 64)
425 {
426 s.float = kind;
427 }
428 }
429 for (raw, sig, comment) in comments {
430 if let Some(&m) = by_id.get(&(raw, false)) {
431 match sig {
432 Some(sig) => {
433 if let Some(s) = dbc.messages[m].signals.iter_mut().find(|s| s.name == sig) {
434 s.comment = Some(comment);
435 }
436 }
437 None => dbc.messages[m].comment = Some(comment),
438 }
439 }
440 }
441 if extended_mux {
442 dbc.notes.push(format!(
443 "{}: extended multiplexing (SG_MUL_VAL_) not read {} signals follow the message's one multiplexer",
444 dbc.name,
445 crate::glyphs::get().middot
446 ));
447 }
448 Ok(dbc)
449}
450
451fn ends_statement(statement: &str) -> bool {
453 let mut quoted = false;
454 let mut escaped = false;
455 for c in statement.chars() {
456 match c {
457 _ if escaped => escaped = false,
458 '\\' if quoted => escaped = true,
459 '"' => quoted = !quoted,
460 ';' if !quoted => return true,
461 _ => {}
462 }
463 }
464 false
465}
466
467fn parse_signal(t: &[Token]) -> Option<Signal> {
468 let word = |i: usize| match t.get(i) {
469 Some(Token::Word(w)) => Some(w.as_str()),
470 _ => None,
471 };
472 let punct = |i: usize, c: char| t.get(i) == Some(&Token::Punct(c));
473 let name = word(1)?.to_string();
474 let (mux, at) = if punct(2, ':') {
475 (Mux::Plain, 3)
476 } else {
477 let m = word(2)?;
478 if !punct(3, ':') {
479 return None;
480 }
481 let mux = if m == "M" {
482 Mux::Multiplexer
483 } else {
484 let v = m.strip_prefix('m')?;
486 Mux::When(v.trim_end_matches('M').parse().ok()?)
487 };
488 (mux, 4)
489 };
490 let start: u32 = word(at)?.parse().ok()?;
491 if !punct(at + 1, '|') {
492 return None;
493 }
494 let length: u32 = word(at + 2)?.parse().ok()?;
495 if !punct(at + 3, '@') {
496 return None;
497 }
498 let order = word(at + 4)?;
499 let big_endian = order.starts_with('0');
500 let signed = order.ends_with('-');
501 if !punct(at + 5, '(') || !punct(at + 7, ',') || !punct(at + 9, ')') {
502 return None;
503 }
504 let factor: f64 = word(at + 6)?.parse().ok()?;
505 let offset: f64 = word(at + 8)?.parse().ok()?;
506 let unit = t[at + 10..]
508 .iter()
509 .find_map(|t| match t {
510 Token::Str(s) => Some(s.clone()),
511 _ => None,
512 })
513 .unwrap_or_default();
514 if length == 0 || length > 64 || start >= 512 * 8 || !factor.is_finite() || !offset.is_finite()
515 {
516 return None;
517 }
518 Some(Signal {
519 name,
520 start,
521 length,
522 big_endian,
523 signed,
524 factor,
525 offset,
526 unit,
527 mux,
528 float: 0,
529 values: Arc::default(),
530 comment: None,
531 })
532}
533
534pub fn raw(signal: &Signal, data: &[u8]) -> Option<u64> {
536 let len = signal.length as usize;
537 let mut value: u64 = 0;
538 if signal.big_endian {
539 let mut pos = signal.start as usize;
543 for _ in 0..len {
544 let byte = *data.get(pos / 8)?;
545 let bit = (byte >> (pos % 8)) & 1;
546 value = (value << 1) | u64::from(bit);
547 if pos.is_multiple_of(8) {
548 pos += 15;
549 } else {
550 pos -= 1;
551 }
552 }
553 } else {
554 for i in 0..len {
556 let pos = signal.start as usize + i;
557 let byte = *data.get(pos / 8)?;
558 value |= u64::from((byte >> (pos % 8)) & 1) << i;
559 }
560 }
561 Some(value)
562}
563
564pub fn integer(signal: &Signal, data: &[u8]) -> Option<i128> {
566 let bits = raw(signal, data)?;
567 let len = signal.length;
568 Some(if signal.signed && len < 64 && bits >> (len - 1) & 1 == 1 {
569 i128::from(bits) - (1i128 << len)
570 } else if signal.signed && len == 64 {
571 i128::from(bits as i64)
572 } else {
573 i128::from(bits)
574 })
575}
576
577pub fn physical(signal: &Signal, data: &[u8]) -> Option<f64> {
580 let bits = raw(signal, data)?;
581 let value = match signal.float {
582 1 => f64::from(f32::from_bits(bits as u32)),
583 2 => f64::from_bits(bits),
584 _ => integer(signal, data)? as f64,
585 };
586 Some(value * signal.factor + signal.offset)
587}
588
589pub fn present(signal: &Signal, mux: Option<u64>) -> bool {
591 match signal.mux {
592 Mux::When(v) => mux == Some(v),
593 _ => true,
594 }
595}
596
597#[cfg(test)]
598mod tests {
599 use super::*;
600
601 #[test]
602 fn messages_signals_values_and_comments() {
603 let dbc = parse(crate::tests::fixtures::DBC, "car", None).unwrap();
604 assert_eq!(dbc.messages.len(), 3);
605 let body = &dbc.messages[1];
606 assert!(body.extended);
607 assert_eq!(body.id, 0x18FEF1FE);
608 let engine = &dbc.messages[0];
609 assert_eq!(engine.signals[1].offset, -40.0);
610 assert!(engine.signals[1].signed);
611 assert_eq!(
612 engine.signals[2].values.get(&1).map(String::as_str),
613 Some("First")
614 );
615 assert_eq!(
616 engine.signals[0].comment.as_deref(),
617 Some("Engine speed;\nover two lines")
618 );
619 let muxed = &dbc.messages[2];
620 assert_eq!(muxed.signals[0].mux, Mux::Multiplexer);
621 assert_eq!(muxed.signals[2].mux, Mux::When(2));
622 assert_eq!(muxed.signals[3].float, 1);
623 }
624
625 #[test]
626 fn intel_motorola_signed_and_float() {
627 let dbc = parse(crate::tests::fixtures::DBC, "car", None).unwrap();
628 let engine = &dbc.messages[0];
629 let data = [0x40, 0x1F, 0xF6, 0x02, 0, 0, 0, 0];
631 assert_eq!(physical(&engine.signals[0], &data), Some(1000.0));
632 assert_eq!(physical(&engine.signals[1], &data), Some(-50.0));
633 assert_eq!(integer(&engine.signals[2], &data), Some(2));
634 let body = &dbc.messages[1];
635 let data = [0x12, 0x34, 0xFF, 0xF0, 0, 0, 0, 0];
638 let p = physical(&body.signals[0], &data).unwrap();
639 assert!((p - 466.0).abs() < 1e-9);
640 assert_eq!(integer(&body.signals[1], &data), Some(-1));
641 let muxed = &dbc.messages[2];
642 let mut data = [1u8, 0x10, 0x27, 0, 0, 0, 0, 0];
643 data[3..7].copy_from_slice(&1.5f32.to_le_bytes());
644 assert_eq!(physical(&muxed.signals[3], &data), Some(1.5));
645 assert!(present(&muxed.signals[1], Some(1)));
646 assert!(!present(&muxed.signals[2], Some(1)));
647 assert_eq!(raw(&engine.signals[0], &[0x40]), None);
649 }
650
651 #[test]
652 fn bad_statements_are_errors_with_their_line() {
653 let e = parse("BO_ x ENGINE: 8 ECU\n", "car", None).unwrap_err();
654 assert_eq!(e.line, 1);
655 let e = parse("BO_ 1 A: 8 X\n SG_ broken\n", "car", None).unwrap_err();
656 assert_eq!(e.line, 2);
657 assert!(parse(" SG_ S : 0|8@1+ (1,0) [0|0] \"\" X\n", "car", None).is_err());
658 assert!(parse("CM_ \"never ends", "car", None).is_ok());
660 }
661}