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