DBC Framework
DBC parsing and CAN signal decoding framework
Loading...
Searching...
No Matches
dbc_statement_handler.cpp
1// SPDX-License-Identifier: MIT
2#include "dbc_statement_handler.h"
3#include "text_utils.h"
4#include "signal_layout.h"
5#include <charconv>
6#include <cmath>
7#include <limits>
8#include <regex>
9#include <sstream>
10#include <stdexcept>
11
12namespace {
13 std::uint32_t ParseDecimal(const std::string &text,
14 std::uint32_t maximum,
15 const char *field) {
16 std::uint32_t value = 0U;
17 const std::from_chars_result parsed = std::from_chars(text.data(), text.data() + text.size(), value);
18 if (text.empty() || parsed.ec != std::errc() ||
19 parsed.ptr != text.data() + text.size() || value > maximum)
20 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
21 return value;
22 }
23
24 double ParsePhysicalValue(const std::string &text, const char *field) {
25 static const std::regex number(
26 R"(^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?$)");
27 if (!std::regex_match(text, number))
28 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
29 std::size_t consumed = 0U;
30 double value;
31 try {
32 value = std::stod(text, &consumed);
33 } catch (const std::exception &) {
34 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
35 }
36 if (consumed != text.size() || !std::isfinite(value))
37 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
38 return value;
39 }
40
41 Multiplexing ParseMultiplexingMarker(const std::string &marker) {
42 Multiplexing result;
43 if (marker == "M")
44 result.role = MultiplexRole::Selector;
45 else if (!marker.empty()) {
46 if (marker[0] != 'm')
47 throw std::runtime_error("Unsupported multiplexing marker.");
48 const std::string value = marker.substr(1U);
49 const std::from_chars_result parsed =
50 std::from_chars(value.data(), value.data() + value.size(), result.value);
51 if (value.empty() || parsed.ec != std::errc() || parsed.ptr != value.data() + value.size())
52 throw std::runtime_error("Invalid or unsupported multiplexing marker.");
53 result.role = MultiplexRole::Branch;
54 }
55
56 return result;
57 }
58
59 RawSignalValue ParseValueKey(const std::string &number) {
60 RawSignalValue key;
61 if (!number.empty() && number[0] == '-') {
62 std::int64_t value = 0;
63 const std::from_chars_result parsed =
64 std::from_chars(number.data(), number.data() + number.size(), value);
65 if (parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
66 throw std::runtime_error("Invalid VAL_ integer: " + number);
67 key = NormalizeValueKey(RawSignalValue(value));
68 } else {
69 std::uint64_t value = 0;
70 const std::from_chars_result parsed =
71 std::from_chars(number.data(), number.data() + number.size(), value);
72 if (number.empty() || parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
73 throw std::runtime_error("Invalid VAL_ integer: " + number);
74 key = value;
75 }
76 return key;
77 }
78
79 std::string UnescapeDescription(const std::string &quoted) {
80 std::string description;
81 for (std::size_t i = 0; i < quoted.size(); ++i) {
82 if (quoted[i] == '\\')
83 ++i;
84 description += quoted[i];
85 }
86 return description;
87 }
88
89 FrameInfo *FindUniqueFrame(DbcDatabase &database, std::uint32_t canId, bool isExtended) {
90 FrameInfo *frame = nullptr;
91 for (FrameInfo &candidate : database.frames) {
92 if (candidate.canId == canId && candidate.isExtended == isExtended) {
93 if (frame)
94 throw std::runtime_error("Ambiguous VAL_ frame reference.");
95 frame = &candidate;
96 }
97 }
98 if (!frame)
99 throw std::runtime_error("Unresolved VAL_ frame reference.");
100 return frame;
101 }
102
103 SignalInfo *FindUniqueSignal(FrameInfo &frame, const std::string &signalName) {
104 SignalInfo *signal = nullptr;
105 for (SignalInfo &candidate : frame.signals) {
106 if (candidate.name == signalName) {
107 if (signal)
108 throw std::runtime_error("Ambiguous VAL_ signal reference: " + signalName);
109 signal = &candidate;
110 }
111 }
112 if (!signal)
113 throw std::runtime_error("Unresolved VAL_ signal reference: " + signalName);
114 return signal;
115 }
116
117 std::uint64_t NextSignalBit(std::uint64_t bit, bool isLittleEndian) {
118 if (isLittleEndian)
119 return bit + 1U;
120 // From bit 0, Motorola continues at bit 7 of the next byte (0 -> 15).
121 return bit % 8U == 0U ? bit + 15U : bit - 1U;
122 }
123
124 FrameInfo *FindFrameById(DbcDatabase &, std::uint32_t, bool);
125 SignalInfo *FindSignalByName(FrameInfo &, const std::string &);
126} // namespace
127
128namespace {
129 std::vector<std::string> SplitReceivers(const std::string &text) {
130 std::vector<std::string> receivers;
131 std::string token;
132 std::istringstream stream(text);
133
134 while (std::getline(stream, token, ',')) {
135 token = dbc_detail::TrimText(token);
136 if (!token.empty())
137 receivers.push_back(token);
138 }
139
140 return receivers;
141 }
142
143 void NormalizeCanId(std::uint32_t rawCanId, std::uint32_t &normalizedCanId, bool &isExtended) {
144 /*
145 * DBC commonly stores extended identifiers with bit 31 set.
146 * Example:
147 * raw id = 0x80000000 | actual_29_bit_id
148 */
149 if ((rawCanId & 0x80000000U) != 0U) {
150 isExtended = true;
151 normalizedCanId = (rawCanId & 0x1FFFFFFFU);
152 } else {
153 isExtended = (rawCanId > 0x7FFU);
154 normalizedCanId = rawCanId;
155 }
156 }
157
158 std::uint32_t TryExtractPgn(std::uint32_t canId, bool isExtended, bool &hasPgn) {
159 hasPgn = false;
160
161 if (!isExtended)
162 return 0U;
163
164 if ((canId & 0x1FFFFFFFU) != canId)
165 return 0U;
166
167 const std::uint32_t pf = (canId >> 16U) & 0xFFU;
168 const std::uint32_t ps = (canId >> 8U) & 0xFFU;
169 const std::uint32_t dp = (canId >> 24U) & 0x01U;
170
171 std::uint32_t pgn = 0U;
172
173 if (pf < 240U)
174 pgn = (dp << 16U) | (pf << 8U);
175 else
176 pgn = (dp << 16U) | (pf << 8U) | ps;
177
178 hasPgn = true;
179 return pgn;
180 }
181
182} // namespace
183
184FrameInfo DbcFrameHandler::ParseFrameLine(const std::string &line) {
185 /*
186 * Example:
187 * BO_ 256 EngineData: 8 EEC1
188 */
189
190 std::istringstream stream(line);
191 std::string keyword;
192 std::string idText;
193 if (!(stream >> keyword >> idText) || keyword != "BO_")
194 throw std::runtime_error("Missing CAN ID in frame definition.");
195
196 const std::uint32_t rawCanId = ParseDecimal(
197 idText, std::numeric_limits<std::uint32_t>::max(), "CAN ID");
198 // Only bit 31 may accompany the actual 29-bit identifier.
199 if ((rawCanId & 0x60000000U) != 0U)
200 throw std::runtime_error("CAN ID exceeds the 29-bit identifier range.");
201
202 std::string nameText;
203 if (!std::getline(stream, nameText, ':') || stream.eof())
204 throw std::runtime_error("Missing ':' in frame definition.");
205 std::istringstream nameStream(nameText);
206 FrameInfo frame;
207 std::string extra;
208 if (!(nameStream >> frame.name) || (nameStream >> extra))
209 throw std::runtime_error("Missing or invalid frame name.");
210
211 std::string lengthText;
212 if (!(stream >> lengthText))
213 throw std::runtime_error("Missing payload length in frame definition.");
214 frame.dlc = static_cast<std::uint8_t>(ParseDecimal(
215 lengthText, std::numeric_limits<std::uint8_t>::max(), "payload length"));
216 if (!(stream >> frame.transmitter))
217 throw std::runtime_error("Missing transmitter in frame definition.");
218 if (stream >> extra)
219 throw std::runtime_error("Unexpected trailing field in frame definition: " + extra);
220
221 NormalizeCanId(rawCanId, frame.canId, frame.isExtended);
222 frame.pgn = TryExtractPgn(frame.canId, frame.isExtended, frame.hasPgn);
223
224 return frame;
225}
226
227SignalInfo DbcSignalHandler::ParseSignalLine(const std::string &line) {
228 // Match the complete statement before converting any numeric fields.
229 enum Field {
230 NameAndMarker = 1, StartBit, BitLength, ByteOrder, Signedness,
231 Factor, Offset, Minimum, Maximum, Unit, Receivers
232 };
233 static const std::regex syntax(
234 // Name and optional multiplexing marker.
235 R"dbc(^SG_[ \t]+([^:]+):[ \t]*)dbc"
236 // Bit position, length, byte order and signedness.
237 R"dbc(([^| \t]+)[ \t]*\|[ \t]*([^@ \t]+)[ \t]*@[ \t]*([01])([+-])[ \t]*)dbc"
238 // Scaling factor and offset.
239 R"dbc(\‍([ \t]*([^, \t]+)[ \t]*,[ \t]*([^ )\t]+)[ \t]*\)[ \t]*)dbc"
240 // Physical range.
241 R"dbc(\[[ \t]*([^| \t]+)[ \t]*\|[ \t]*([^\] \t]+)[ \t]*\][ \t]*)dbc"
242 // Unit and receivers.
243 R"dbc("([^"\r\n]*)"[ \t]+([^ \t,]+(?:[ \t]*,[ \t]*[^ \t,]+)*)[ \t]*$)dbc");
244 std::smatch fields;
245 if (!std::regex_match(line, fields, syntax))
246 throw std::runtime_error("Malformed or unsupported SG_ signal definition.");
247
248 SignalInfo signal;
249 std::istringstream nameFields(fields[NameAndMarker].str());
250 std::string marker;
251 std::string extra;
252 nameFields >> signal.name >> marker;
253 if (signal.name.empty() || (nameFields >> extra))
254 throw std::runtime_error("Invalid signal name or multiplexing marker.");
255 signal.multiplexing = ParseMultiplexingMarker(marker);
256
257 signal.startBit = ParseDecimal(fields[StartBit].str(),
258 std::numeric_limits<std::uint32_t>::max(), "signal start bit");
259 signal.length = ParseDecimal(fields[BitLength].str(), 64U, "signal length");
260 if (signal.length == 0U)
261 throw std::runtime_error("Signal length must be within 1..64 bits.");
262 signal.isLittleEndian = fields[ByteOrder].str() == "1";
263 signal.isSigned = fields[Signedness].str() == "-";
264
265 signal.factor = ParsePhysicalValue(fields[Factor].str(), "signal factor");
266 signal.offset = ParsePhysicalValue(fields[Offset].str(), "signal offset");
267 signal.minimum = ParsePhysicalValue(fields[Minimum].str(), "signal minimum");
268 signal.maximum = ParsePhysicalValue(fields[Maximum].str(), "signal maximum");
269 if (signal.minimum > signal.maximum)
270 throw std::runtime_error("Signal minimum exceeds maximum.");
271 signal.unit = fields[Unit].str();
272 signal.receivers = SplitReceivers(fields[Receivers].str());
273 return signal;
274}
275
276void DbcCommentHandler::ParseCommentLine(const std::string &line, DbcDatabase &database) {
277 /*
278 * Examples:
279 * CM_ BO_ 256 "Frame comment";
280 * CM_ SG_ 256 EngineSpeed "Signal comment";
281 */
282
283 std::istringstream stream(line);
284 std::string token;
285 stream >> token;
286
287 if (token != "CM_")
288 return;
289
290 stream >> token;
291 if (token != "BO_" && token != "SG_")
292 return;
293
294 std::uint32_t rawCanId = 0U;
295 std::uint32_t canId = 0U;
296 bool isExtended = false;
297 std::string signalName;
298 stream >> rawCanId;
299 if (token == "SG_")
300 stream >> signalName;
301 NormalizeCanId(rawCanId, canId, isExtended);
302
303 const std::string::size_type quoteBegin = line.find('"');
304 const std::string::size_type quoteEnd = line.rfind('"');
305 if (quoteBegin == std::string::npos || quoteEnd == std::string::npos || quoteEnd <= quoteBegin)
306 return;
307
308 FrameInfo *frame = FindFrameById(database, canId, isExtended);
309 if (frame == nullptr)
310 return;
311 const std::string comment = line.substr(quoteBegin + 1U, quoteEnd - quoteBegin - 1U);
312 if (token == "BO_") {
313 frame->comment = comment;
314 return;
315 }
316 SignalInfo *signal = FindSignalByName(*frame, signalName);
317 if (signal != nullptr)
318 signal->comment = comment;
319}
320
321namespace {
322 FrameInfo *FindFrameById(DbcDatabase &database,
323 std::uint32_t canId,
324 bool isExtended) {
325 for (FrameInfo &frame : database.frames) {
326 if (frame.canId == canId && frame.isExtended == isExtended)
327 return &frame;
328 }
329
330 return nullptr;
331 }
332
333 SignalInfo *FindSignalByName(FrameInfo &frame, const std::string &signalName) {
334 for (SignalInfo &signal : frame.signals) {
335 if (signal.name == signalName)
336 return &signal;
337 }
338
339 return nullptr;
340 }
341}// namespace
342
343void DbcFrameHandler::Handle(const std::string &line, DbcParseContext &context) const {
344 FrameInfo frame = ParseFrameLine(line);
345 if (FindFrameById(context.database, frame.canId, frame.isExtended) != nullptr)
346 throw std::runtime_error("Duplicate " + std::string(frame.isExtended ? "extended" : "standard") +
347 " frame CAN ID: " + std::to_string(frame.canId));
348 context.database.frames.push_back(std::move(frame));
349 context.currentFrame = &context.database.frames.back();
350 context.signalLines.emplace_back();
351}
352
353void DbcSignalHandler::Handle(const std::string &line, DbcParseContext &context) const {
354 if (context.currentFrame == nullptr)
355 throw std::runtime_error("Signal found before any frame definition.");
356 SignalInfo signal = ParseSignalLine(line);
357 if (FindSignalByName(*context.currentFrame, signal.name) != nullptr)
358 throw std::runtime_error("Duplicate signal name '" + signal.name +
359 "' in frame '" + context.currentFrame->name + "'.");
360 if (!IsValidSignalLayout(signal.startBit, signal.length, signal.isLittleEndian,
361 context.currentFrame->dlc))
362 throw std::runtime_error("Signal exceeds the frame payload length.");
363 context.currentFrame->signals.push_back(std::move(signal));
364 context.signalLines.back().push_back(context.lineNumber);
365}
366
367void DbcCommentHandler::Handle(const std::string &line, DbcParseContext &context) const {
368 ParseCommentLine(line, context.database);
369}
370
371DbcStatementDispatcher::DbcStatementDispatcher() {
372 RegisterType<DbcHeaderHandler>("VERSION");
373 RegisterType<DbcHeaderHandler>("NS_");
374 RegisterType<DbcHeaderHandler>("NS_:");
375 RegisterType<DbcHeaderHandler>("BS_");
376 RegisterType<DbcHeaderHandler>("BS_:");
377 RegisterType<DbcHeaderHandler>("BU_");
378 RegisterType<DbcHeaderHandler>("BU_:");
379 RegisterType<DbcUnsupportedMultiplexingHandler>("SG_MUL_VAL_");
380 RegisterType<DbcFrameHandler>("BO_");
381 RegisterType<DbcSignalHandler>("SG_");
382 RegisterType<DbcCommentHandler>("CM_");
383 RegisterType<DbcValueDescriptionHandler>("VAL_");
384}
385
386void DbcHeaderHandler::Handle(const std::string &, DbcParseContext &) const {}
387
388bool DbcStatementDispatcher::Dispatch(const std::string &line,
389 DbcParseContext &context) const {
390 std::istringstream stream(line);
391 std::string keyword;
392 stream >> keyword;
393 const HandlerMap::const_iterator handler = m_handlers.find(keyword);
394 if (handler == m_handlers.end())
395 return false;
396 handler->second->Handle(line, context);
397 return true;
398}
399
400void DbcValueDescriptionHandler::Handle(const std::string &line, DbcParseContext &context) const {
401 static const std::regex header(R"(^VAL_[ \t]+([^ \t]+)[ \t]+([^ \t;]+)[ \t]+(.*)$)");
402 static const std::regex pair(R"dbc(^([^ \t]+)[ \t]+"((?:[^"\\]|\\["\\])*)")dbc");
403 std::smatch fields;
404 if (!std::regex_match(line, fields, header))
405 throw std::runtime_error("Malformed or unsupported VAL_ statement.");
406 const std::uint32_t rawId = ParseDecimal(fields[1].str(), std::numeric_limits<std::uint32_t>::max(), "VAL_ CAN ID");
407 if ((rawId & 0x60000000U) != 0U)
408 throw std::runtime_error("VAL_ CAN ID exceeds the 29-bit identifier range.");
410 NormalizeCanId(rawId, pending.canId, pending.isExtended);
411 pending.signalName = fields[2].str();
412 pending.lineNumber = context.lineNumber;
413 std::string rest = fields[3].str();
414 while (rest != ";") {
415 if (!std::regex_search(rest, fields, pair))
416 throw std::runtime_error("Malformed VAL_ value/description pair or missing semicolon.");
417 const std::string number = fields[1].str();
418 const RawSignalValue key = ParseValueKey(number);
419 const std::string description = UnescapeDescription(fields[2].str());
420 if (!pending.descriptions.emplace(key, description).second)
421 throw std::runtime_error("Duplicate VAL_ value key: " + number);
422 const std::size_t consumed = static_cast<std::size_t>(fields.length());
423 rest.erase(0, consumed);
424 if (!rest.empty() && rest[0] != ';' && rest[0] != ' ' && rest[0] != '\t')
425 throw std::runtime_error("Missing whitespace between VAL_ pairs.");
426 rest = dbc_detail::TrimText(rest);
427 }
428 if (pending.descriptions.empty())
429 throw std::runtime_error("VAL_ requires at least one value/description pair.");
430 context.pendingValues.push_back(std::move(pending));
431}
432
433void DbcParseContext::ResolveValueDescriptions(const std::string &filePath) {
434 for (const PendingValueDescriptions &pending: pendingValues) {
435 try {
436 FrameInfo *frame = FindUniqueFrame(database, pending.canId, pending.isExtended);
437 SignalInfo *signal = FindUniqueSignal(*frame, pending.signalName);
438 for (const ValueDescriptions::value_type &entry: pending.descriptions) {
439 if (!signal->valueDescriptions.insert(entry).second)
440 throw std::runtime_error("Duplicate VAL_ value key for signal: " + pending.signalName);
441 }
442 } catch (const std::exception &error) {
443 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidValueDescription,
444 filePath, pending.lineNumber, error.what());
445 }
446 }
447}
448
449void DbcUnsupportedMultiplexingHandler::Handle(const std::string &, DbcParseContext &) const {
450 throw std::runtime_error("Extended and nested multiplexing are unsupported.");
451}
452
453namespace {
454 std::vector<std::uint64_t> SignalBits(const SignalInfo &signal) {
455 std::vector<std::uint64_t> bits;
456 std::uint64_t bit = signal.startBit;
457 for (std::uint32_t index = 0U; index < signal.length; ++index) {
458 bits.push_back(bit);
459 bit = NextSignalBit(bit, signal.isLittleEndian);
460 }
461 return bits;
462 }
463
464 bool AreMutuallyExclusive(const SignalInfo &first, const SignalInfo &second) {
465 return first.multiplexing.role == MultiplexRole::Branch &&
466 second.multiplexing.role == MultiplexRole::Branch &&
467 first.multiplexing.value != second.multiplexing.value;
468 }
469
470 std::string ValidateBranch(const SignalInfo &signal, const SignalInfo *selector) {
471 if (signal.multiplexing.role != MultiplexRole::Branch)
472 return {};
473 if (selector == nullptr)
474 return "Multiplexed branch has no selector.";
475 if (selector->length < 64U && signal.multiplexing.value >= (std::uint64_t(1U) << selector->length))
476 return "Branch value exceeds selector raw range.";
477 return {};
478 }
479
480 const SignalInfo *FindSelector(const FrameInfo &frame,
481 const std::vector<std::size_t> &lines,
482 const std::string &filePath) {
483 const SignalInfo *selector = nullptr;
484 for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
485 const SignalInfo &signal = frame.signals[index];
486 if (signal.multiplexing.role != MultiplexRole::Selector)
487 continue;
488 if (selector != nullptr || signal.isSigned)
489 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing,
490 filePath, lines[index],
491 "Multiplexing requires one unsigned selector per frame.");
492 selector = &signal;
493 }
494 return selector;
495 }
496
497 bool SignalsOverlap(const SignalInfo &first, const SignalInfo &second) {
498 const std::vector<std::uint64_t> firstBits = SignalBits(first);
499 const std::vector<std::uint64_t> secondBits = SignalBits(second);
500 for (const std::uint64_t firstBit : firstBits) {
501 for (const std::uint64_t secondBit : secondBits) {
502 if (firstBit == secondBit)
503 return true;
504 }
505 }
506 return false;
507 }
508}
509
510void DbcParseContext::ValidateMultiplexing(const std::string &filePath) const {
511 for (std::size_t frameIndex = 0U; frameIndex < database.frames.size(); ++frameIndex) {
512 const FrameInfo &frame = database.frames[frameIndex];
513 const SignalInfo *selector = FindSelector(frame, signalLines[frameIndex], filePath);
514 for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
515 const SignalInfo &signal = frame.signals[index];
516 std::string error = ValidateBranch(signal, selector);
517 for (std::size_t previous = 0U; previous < index && error.empty(); ++previous) {
518 const SignalInfo &other = frame.signals[previous];
519 if (!AreMutuallyExclusive(signal, other) && SignalsOverlap(signal, other))
520 error = "Signals overlap without mutually exclusive multiplexing conditions.";
521 }
522 if (!error.empty())
523 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing,
524 filePath, signalLines[frameIndex][index], error);
525 }
526 }
527}
bool Dispatch(const std::string &line, DbcParseContext &context) const
ByteOrder
Signal byte order used for runtime decoding.
Parsed DBC content stored in a simple internal form.
std::vector< FrameInfo > frames
Describes one CAN frame from a DBC file.
Definition frame_info.h:24
bool isExtended
Definition frame_info.h:27
bool hasPgn
Definition frame_info.h:29
std::string transmitter
Definition frame_info.h:31
std::uint8_t dlc
Definition frame_info.h:30
std::string name
Definition frame_info.h:25
std::string comment
Definition frame_info.h:32
std::uint32_t pgn
Definition frame_info.h:28
std::uint32_t canId
Definition frame_info.h:26
Describes one signal inside a DBC frame.
Definition signal_info.h:24
std::uint32_t length
Definition signal_info.h:28
std::string unit
Definition signal_info.h:35
std::vector< std::string > receivers
Definition signal_info.h:36
std::string name
Definition signal_info.h:26
ValueDescriptions valueDescriptions
Definition signal_info.h:37
bool isSigned
Definition signal_info.h:30
double offset
Definition signal_info.h:32
std::string comment
Definition signal_info.h:38
std::uint32_t startBit
Definition signal_info.h:27
double minimum
Definition signal_info.h:33
double maximum
Definition signal_info.h:34
double factor
Definition signal_info.h:31
bool isLittleEndian
Definition signal_info.h:29