Files
dbc/core/dbc_statement_handler.cpp
T
2026-10-23 20:53:05 -04:00

592 lines
26 KiB
C++

// SPDX-License-Identifier: MIT
#include "dbc_statement_handler.h"
#include "j1939.h"
#include "multiplex_graph.h"
#include "signal_layout.h"
#include "text_utils.h"
#include <charconv>
#include <cmath>
#include <limits>
#include <regex>
#include <sstream>
#include <stdexcept>
namespace {
std::uint32_t ParseDecimal(const std::string &text,
std::uint32_t maximum,
const char *field) {
std::uint32_t value = 0U;
const std::from_chars_result parsed = std::from_chars(text.data(), text.data() + text.size(), value);
if (text.empty() || parsed.ec != std::errc() ||
parsed.ptr != text.data() + text.size() || value > maximum)
throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
return value;
}
std::uint32_t ParseCanId(const std::string &text) {
const std::uint32_t rawId = ParseDecimal(
text, std::numeric_limits<std::uint32_t>::max(), "CAN ID");
// Only bit 31 may accompany the actual 29-bit identifier.
if ((rawId & 0x60000000U) != 0U)
throw std::runtime_error("CAN ID exceeds the 29-bit identifier range.");
return rawId;
}
double ParsePhysicalValue(const std::string &text, const char *field) {
if (text.empty() || (text[0] == '+' &&
(text.size() == 1U || text[1] == '+' || text[1] == '-')))
throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
// Floating-point from_chars is locale-independent and does not accept
// a leading '+', which the DBC numeric grammar permits.
const char *begin = text.data();
const char *end = begin + text.size();
if (*begin == '+')
++begin;
double value = 0.0;
const std::from_chars_result parsed =
std::from_chars(begin, end, value, std::chars_format::general);
// Keep the existing rejection of conversion underflow into subnormal values.
if (parsed.ec != std::errc() || parsed.ptr != end || !std::isfinite(value) ||
std::fpclassify(value) == FP_SUBNORMAL)
throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
return value;
}
// Iterative scanning keeps stack use independent of the statement length.
class SignalTokens {
public:
SignalTokens(const std::string &text, std::size_t position) : text(text), position(position) {}
void SkipSpaces() {
while (position < text.size() && (text[position] == ' ' || text[position] == '\t'))
++position;
}
char Character() {
if (position == text.size())
throw std::runtime_error("Incomplete SG_ signal definition.");
return text[position++];
}
void Expect(char expected) {
SkipSpaces();
if (Character() != expected)
throw std::runtime_error("Malformed SG_ signal definition.");
}
std::string Until(char delimiter) {
const std::size_t end = text.find(delimiter, position);
if (end == std::string::npos)
throw std::runtime_error("Missing delimiter in SG_ signal definition.");
const std::string value = text.substr(position, end - position);
position = end + 1U;
return value;
}
std::vector<std::string> Receivers() {
if (position == text.size() || (text[position] != ' ' && text[position] != '\t'))
throw std::runtime_error("Missing receiver separator in SG_ signal definition.");
std::vector<std::string> receivers;
for (;;) {
const std::size_t end = text.find(',', position);
const std::string receiver = dbc_detail::TrimText(text.substr(position, end - position));
if (receiver.empty() || receiver.find_first_of(" \t\r\n") != std::string::npos)
throw std::runtime_error("Invalid SG_ receiver list.");
receivers.push_back(receiver);
if (end == std::string::npos)
return receivers;
position = end + 1U;
}
}
private:
const std::string &text;
std::size_t position = 0U;
};
Multiplexing ParseMultiplexingMarker(const std::string &marker) {
Multiplexing result;
if (marker == "M")
result.role = MultiplexRole::Selector;
else if (!marker.empty()) {
if (marker[0] != 'm')
throw std::runtime_error("Unsupported multiplexing marker.");
const bool nested = marker.back() == 'M';
const std::string value = marker.substr(1U, marker.size() - (nested ? 2U : 1U));
const std::from_chars_result parsed =
std::from_chars(value.data(), value.data() + value.size(), result.value);
if (value.empty() || parsed.ec != std::errc() || parsed.ptr != value.data() + value.size())
throw std::runtime_error("Invalid or unsupported multiplexing marker.");
result.role = nested ? MultiplexRole::BranchSelector : MultiplexRole::Branch;
}
return result;
}
RawSignalValue ParseValueKey(const std::string &number) {
RawSignalValue key;
if (!number.empty() && number[0] == '-') {
std::int64_t value = 0;
const std::from_chars_result parsed =
std::from_chars(number.data(), number.data() + number.size(), value);
if (parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
throw std::runtime_error("Invalid VAL_ integer: " + number);
key = NormalizeValueKey(RawSignalValue(value));
} else {
std::uint64_t value = 0;
const std::from_chars_result parsed =
std::from_chars(number.data(), number.data() + number.size(), value);
if (number.empty() || parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
throw std::runtime_error("Invalid VAL_ integer: " + number);
key = value;
}
return key;
}
std::string UnescapeDescription(const std::string &quoted) {
std::string description;
for (std::size_t i = 0; i < quoted.size(); ++i) {
if (quoted[i] == '\\')
++i;
description += quoted[i];
}
return description;
}
FrameInfo *FindUniqueFrame(DbcDatabase &database, std::uint32_t canId, bool isExtended, const std::string &statement = "VAL_") {
FrameInfo *frame = nullptr;
for (FrameInfo &candidate : database.frames) {
if (candidate.canId == canId && candidate.isExtended == isExtended) {
if (frame)
throw std::runtime_error("Ambiguous " + statement + " frame reference.");
frame = &candidate;
}
}
if (!frame)
throw std::runtime_error("Unresolved " + statement + " frame reference.");
return frame;
}
SignalInfo *FindUniqueSignal(FrameInfo &frame, const std::string &signalName, const std::string &statement = "VAL_") {
SignalInfo *signal = nullptr;
for (SignalInfo &candidate : frame.signals) {
if (candidate.name == signalName) {
if (signal)
throw std::runtime_error("Ambiguous " + statement + " signal reference: " + signalName);
signal = &candidate;
}
}
if (!signal)
throw std::runtime_error("Unresolved " + statement + " signal reference: " + signalName);
return signal;
}
std::uint64_t NextSignalBit(std::uint64_t bit, bool isLittleEndian) {
if (isLittleEndian)
return bit + 1U;
// From bit 0, Motorola continues at bit 7 of the next byte (0 -> 15).
return bit % 8U == 0U ? bit + 15U : bit - 1U;
}
FrameInfo *FindFrameById(DbcDatabase &, std::uint32_t, bool);
SignalInfo *FindSignalByName(FrameInfo &, const std::string &);
} // namespace
namespace {
void NormalizeCanId(std::uint32_t rawCanId, std::uint32_t &normalizedCanId, bool &isExtended) {
/*
* DBC commonly stores extended identifiers with bit 31 set.
* Example:
* raw id = 0x80000000 | actual_29_bit_id
*/
isExtended = (rawCanId & 0x80000000U) != 0U || rawCanId > 0x7FFU;
normalizedCanId = rawCanId & 0x1FFFFFFFU;
}
} // namespace
FrameInfo DbcFrameHandler::ParseFrameLine(const std::string &line) {
/*
* Example:
* BO_ 256 EngineData: 8 EEC1
*/
std::istringstream stream(line);
std::string keyword;
std::string idText;
if (!(stream >> keyword >> idText) || keyword != "BO_")
throw std::runtime_error("Missing CAN ID in frame definition.");
const std::uint32_t rawCanId = ParseCanId(idText);
std::string nameText;
if (!std::getline(stream, nameText, ':') || stream.eof())
throw std::runtime_error("Missing ':' in frame definition.");
std::istringstream nameStream(nameText);
FrameInfo frame;
std::string extra;
if (!(nameStream >> frame.name) || (nameStream >> extra))
throw std::runtime_error("Missing or invalid frame name.");
std::string lengthText;
if (!(stream >> lengthText))
throw std::runtime_error("Missing payload length in frame definition.");
frame.dlc = static_cast<std::uint8_t>(ParseDecimal(
lengthText, std::numeric_limits<std::uint8_t>::max(), "payload length"));
if (!(stream >> frame.transmitter))
throw std::runtime_error("Missing transmitter in frame definition.");
if (stream >> extra)
throw std::runtime_error("Unexpected trailing field in frame definition: " + extra);
NormalizeCanId(rawCanId, frame.canId, frame.isExtended);
return frame;
}
SignalInfo DbcSignalHandler::ParseSignalLine(const std::string &line) {
if (line.size() < 4U || line.compare(0U, 3U, "SG_") != 0 ||
(line[3] != ' ' && line[3] != '\t'))
throw std::runtime_error("Malformed SG_ signal definition.");
SignalTokens tokens(line, 3U);
SignalInfo signal;
std::istringstream nameFields(tokens.Until(':'));
std::string marker;
std::string extra;
nameFields >> signal.name >> marker;
if (signal.name.empty() || (nameFields >> extra))
throw std::runtime_error("Invalid signal name or multiplexing marker.");
signal.multiplexing = ParseMultiplexingMarker(marker);
signal.startBit = ParseDecimal(dbc_detail::TrimText(tokens.Until('|')),
std::numeric_limits<std::uint32_t>::max(), "signal start bit");
signal.length = ParseDecimal(dbc_detail::TrimText(tokens.Until('@')), 64U, "signal length");
if (signal.length == 0U)
throw std::runtime_error("Signal length must be within 1..64 bits.");
tokens.SkipSpaces();
const char byteOrder = tokens.Character();
const char signedness = tokens.Character();
if ((byteOrder != '0' && byteOrder != '1') || (signedness != '+' && signedness != '-'))
throw std::runtime_error("Invalid SG_ byte order or signedness.");
signal.isLittleEndian = byteOrder == '1';
signal.isSigned = signedness == '-';
tokens.Expect('(');
signal.factor = ParsePhysicalValue(dbc_detail::TrimText(tokens.Until(',')), "signal factor");
signal.offset = ParsePhysicalValue(dbc_detail::TrimText(tokens.Until(')')), "signal offset");
tokens.Expect('[');
signal.minimum = ParsePhysicalValue(dbc_detail::TrimText(tokens.Until('|')), "signal minimum");
signal.maximum = ParsePhysicalValue(dbc_detail::TrimText(tokens.Until(']')), "signal maximum");
if (signal.minimum > signal.maximum)
throw std::runtime_error("Signal minimum exceeds maximum.");
tokens.Expect('"');
signal.unit = tokens.Until('"');
if (signal.unit.find_first_of("\r\n") != std::string::npos)
throw std::runtime_error("Invalid newline in SG_ unit.");
signal.receivers = tokens.Receivers();
return signal;
}
namespace {
FrameInfo *FindFrameById(DbcDatabase &database,
std::uint32_t canId,
bool isExtended) {
for (FrameInfo &frame : database.frames) {
if (frame.canId == canId && frame.isExtended == isExtended)
return &frame;
}
return nullptr;
}
SignalInfo *FindSignalByName(FrameInfo &frame, const std::string &signalName) {
for (SignalInfo &signal : frame.signals) {
if (signal.name == signalName)
return &signal;
}
return nullptr;
}
}// namespace
void DbcFrameHandler::Handle(const std::string &line, DbcParseContext &context) const {
FrameInfo frame = ParseFrameLine(line);
if (context.enableJ1939 && frame.isExtended) {
frame.hasPgn = true;
frame.pgn = ExtractJ1939Pgn(frame.canId);
}
if (FindFrameById(context.database, frame.canId, frame.isExtended) != nullptr)
throw std::runtime_error("Duplicate " + std::string(frame.isExtended ? "extended" : "standard") +
" frame CAN ID: " + std::to_string(frame.canId));
context.database.frames.push_back(std::move(frame));
context.currentFrame = &context.database.frames.back();
context.signalLines.emplace_back();
}
void DbcSignalHandler::Handle(const std::string &line, DbcParseContext &context) const {
if (context.currentFrame == nullptr)
throw std::runtime_error("Signal found before any frame definition.");
SignalInfo signal = ParseSignalLine(line);
if (FindSignalByName(*context.currentFrame, signal.name) != nullptr)
throw std::runtime_error("Duplicate signal name '" + signal.name +
"' in frame '" + context.currentFrame->name + "'.");
if (!IsValidSignalLayout(signal.startBit, signal.length, signal.isLittleEndian,
context.currentFrame->dlc))
throw std::runtime_error("Signal exceeds the frame payload length.");
context.currentFrame->signals.push_back(std::move(signal));
context.signalLines.back().push_back(context.lineNumber);
}
void DbcCommentHandler::Handle(const std::string &line, DbcParseContext &context) const {
std::istringstream stream(line);
std::string keyword;
std::string target;
stream >> keyword >> target;
if (keyword != "CM_" || (target != "BO_" && target != "SG_"))
return;
PendingComment pending;
pending.isSignal = target == "SG_";
pending.lineNumber = context.lineNumber;
std::string idText;
if (!(stream >> idText))
throw std::runtime_error("Missing CAN ID in CM_ statement.");
NormalizeCanId(ParseCanId(idText), pending.canId, pending.isExtended);
if (pending.isSignal && (!(stream >> pending.signalName) ||
pending.signalName.find_first_of("\";") != std::string::npos))
throw std::runtime_error("Missing or invalid signal name in CM_ statement.");
stream >> std::ws;
if (stream.get() != '"')
throw std::runtime_error("Missing opening quote in CM_ statement.");
bool closed = false;
char character;
while (stream.get(character)) {
if (character == '"') {
closed = true;
break;
}
if (character == '\\') {
if (!stream.get(character) || (character != '"' && character != '\\'))
throw std::runtime_error("Invalid escape in CM_ statement.");
}
pending.comment += character;
}
if (!closed)
throw std::runtime_error("Unterminated CM_ comment.");
stream >> std::ws;
if (stream.get() != ';')
throw std::runtime_error("Missing semicolon in CM_ statement.");
stream >> std::ws;
if (!stream.eof())
throw std::runtime_error("Unexpected trailing text in CM_ statement.");
context.pendingComments.push_back(std::move(pending));
}
void DbcParseContext::ResolveComments(const std::string &filePath) {
for (const PendingComment &pending: pendingComments) {
FrameInfo *frame = FindFrameById(database, pending.canId, pending.isExtended);
if (!frame)
throw DbcDiagnosticError(DbcDiagnosticCode::InvalidStatement, filePath,
pending.lineNumber, "Unresolved CM_ frame reference.");
if (pending.isSignal) {
SignalInfo *signal = FindSignalByName(*frame, pending.signalName);
if (!signal)
throw DbcDiagnosticError(DbcDiagnosticCode::InvalidStatement, filePath,
pending.lineNumber, "Unresolved CM_ signal reference: " + pending.signalName);
signal->comment = pending.comment;
} else {
frame->comment = pending.comment;
}
}
}
DbcStatementDispatcher::DbcStatementDispatcher() {
RegisterType<DbcHeaderHandler>("VERSION");
RegisterType<DbcHeaderHandler>("NS_");
RegisterType<DbcHeaderHandler>("NS_:");
RegisterType<DbcHeaderHandler>("BS_");
RegisterType<DbcHeaderHandler>("BS_:");
RegisterType<DbcHeaderHandler>("BU_");
RegisterType<DbcHeaderHandler>("BU_:");
RegisterType<DbcMultiplexingHandler>("SG_MUL_VAL_");
RegisterType<DbcFrameHandler>("BO_");
RegisterType<DbcSignalHandler>("SG_");
RegisterType<DbcUnsupportedSignalTypeHandler>("SIG_VALTYPE_");
RegisterType<DbcCommentHandler>("CM_");
RegisterType<DbcValueDescriptionHandler>("VAL_");
RegisterType<DbcAttributeHandler>("BA_DEF_");
RegisterType<DbcAttributeHandler>("BA_DEF_DEF_");
RegisterType<DbcAttributeHandler>("BA_");
}
void DbcUnsupportedSignalTypeHandler::Handle(const std::string &, DbcParseContext &) const {
throw std::runtime_error("SIG_VALTYPE_ declarations are unsupported; refusing potentially incorrect integer decoding.");
}
void DbcHeaderHandler::Handle(const std::string &, DbcParseContext &) const {}
bool DbcStatementDispatcher::Dispatch(const std::string &line,
DbcParseContext &context) const {
std::istringstream stream(line);
std::string keyword;
stream >> keyword;
const HandlerMap::const_iterator handler = m_handlers.find(keyword);
if (handler == m_handlers.end())
return false;
handler->second->Handle(line, context);
return true;
}
void DbcValueDescriptionHandler::Handle(const std::string &line, DbcParseContext &context) const {
static const std::regex header(R"(^VAL_[ \t]+([^ \t]+)[ \t]+([^ \t;]+)[ \t]+(.*)$)");
static const std::regex pair(R"dbc(^([^ \t]+)[ \t]+"((?:[^"\\]|\\["\\])*)")dbc");
std::smatch fields;
if (!std::regex_match(line, fields, header))
throw std::runtime_error("Malformed or unsupported VAL_ statement.");
const std::uint32_t rawId = ParseDecimal(fields[1].str(), std::numeric_limits<std::uint32_t>::max(), "VAL_ CAN ID");
if ((rawId & 0x60000000U) != 0U)
throw std::runtime_error("VAL_ CAN ID exceeds the 29-bit identifier range.");
PendingValueDescriptions pending;
NormalizeCanId(rawId, pending.canId, pending.isExtended);
pending.signalName = fields[2].str();
pending.lineNumber = context.lineNumber;
std::string rest = fields[3].str();
while (rest != ";") {
if (!std::regex_search(rest, fields, pair))
throw std::runtime_error("Malformed VAL_ value/description pair or missing semicolon.");
const std::string number = fields[1].str();
const RawSignalValue key = ParseValueKey(number);
const std::string description = UnescapeDescription(fields[2].str());
if (!pending.descriptions.emplace(key, description).second)
throw std::runtime_error("Duplicate VAL_ value key: " + number);
const std::size_t consumed = static_cast<std::size_t>(fields.length());
rest.erase(0, consumed);
if (!rest.empty() && rest[0] != ';' && rest[0] != ' ' && rest[0] != '\t')
throw std::runtime_error("Missing whitespace between VAL_ pairs.");
rest = dbc_detail::TrimText(rest);
}
if (pending.descriptions.empty())
throw std::runtime_error("VAL_ requires at least one value/description pair.");
context.pendingValues.push_back(std::move(pending));
}
void DbcParseContext::ResolveValueDescriptions(const std::string &filePath) {
for (const PendingValueDescriptions &pending: pendingValues) {
try {
FrameInfo *frame = FindUniqueFrame(database, pending.canId, pending.isExtended);
SignalInfo *signal = FindUniqueSignal(*frame, pending.signalName);
for (const ValueDescriptions::value_type &entry: pending.descriptions) {
if (!signal->valueDescriptions.insert(entry).second)
throw std::runtime_error("Duplicate VAL_ value key for signal: " + pending.signalName);
}
} catch (const std::exception &error) {
throw DbcDiagnosticError(DbcDiagnosticCode::InvalidValueDescription,
filePath, pending.lineNumber, error.what());
}
}
}
void DbcMultiplexingHandler::Handle(const std::string &line, DbcParseContext &context) const {
static const std::regex syntax(R"(^SG_MUL_VAL_[ \t]+([0-9]+)[ \t]+([^ \t;]+)[ \t]+([^ \t;]+)[ \t]+(.+);[ \t]*$)");
static const std::regex rangeSyntax(R"(^[ \t]*([0-9]+)[ \t]*-[ \t]*([0-9]+)[ \t]*$)");
std::smatch fields;
if (!std::regex_match(line, fields, syntax))
throw std::runtime_error("Malformed SG_MUL_VAL_ statement.");
PendingMultiplexing pending;
NormalizeCanId(ParseCanId(fields[1].str()), pending.canId, pending.isExtended);
pending.signalName = fields[2].str();
pending.selectorName = fields[3].str();
pending.lineNumber = context.lineNumber;
const std::string list = fields[4].str();
std::size_t begin = 0U;
for (;;) {
const std::size_t comma = list.find(',', begin);
const std::string item = list.substr(begin, comma == std::string::npos ? comma : comma - begin);
if (!std::regex_match(item, fields, rangeSyntax))
throw std::runtime_error("Malformed multiplex range.");
MultiplexRange range;
const std::string lower = fields[1].str();
const std::string upper = fields[2].str();
const std::from_chars_result low = std::from_chars(lower.data(), lower.data() + lower.size(), range.minimum);
const std::from_chars_result high = std::from_chars(upper.data(), upper.data() + upper.size(), range.maximum);
if (low.ec != std::errc() || high.ec != std::errc() || range.minimum > range.maximum)
throw std::runtime_error("Invalid multiplex range.");
pending.ranges.push_back(range);
if (comma == std::string::npos)
break;
begin = comma + 1U;
}
context.pendingMultiplexing.push_back(std::move(pending));
}
void DbcParseContext::ResolveMultiplexing(const std::string &filePath) {
for (const PendingMultiplexing &pending: pendingMultiplexing) {
try {
FrameInfo *frame = FindUniqueFrame(database, pending.canId, pending.isExtended, "SG_MUL_VAL_");
SignalInfo *signal = FindUniqueSignal(*frame, pending.signalName, "SG_MUL_VAL_");
SignalInfo *selector = FindUniqueSignal(*frame, pending.selectorName, "SG_MUL_VAL_");
if (!IsMultiplexSelector(selector->multiplexing.role) || selector->isSigned)
throw std::runtime_error("Referenced signal must be an unsigned multiplex selector.");
if (!signal->multiplexing.selector.empty() && signal->multiplexing.selector != pending.selectorName)
throw std::runtime_error("Multiple parent selectors for one signal are unsupported.");
for (const MultiplexRange &range: pending.ranges) {
if (selector->length < 64U && range.maximum >= (std::uint64_t(1U) << selector->length))
throw std::runtime_error("Branch value exceeds selector raw range.");
}
signal->multiplexing.selector = pending.selectorName;
signal->multiplexing.ranges.insert(signal->multiplexing.ranges.end(), pending.ranges.begin(), pending.ranges.end());
if (!IsMultiplexSelector(signal->multiplexing.role))
signal->multiplexing.role = MultiplexRole::Branch;
else
signal->multiplexing.role = MultiplexRole::BranchSelector;
} catch (const std::exception &error) {
throw DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing, filePath, pending.lineNumber, error.what());
}
}
}
namespace {
std::vector<std::uint64_t> SignalBits(const SignalInfo &signal) {
std::vector<std::uint64_t> bits;
std::uint64_t bit = signal.startBit;
for (std::uint32_t index = 0U; index < signal.length; ++index) {
bits.push_back(bit);
bit = NextSignalBit(bit, signal.isLittleEndian);
}
return bits;
}
bool SignalsOverlap(const SignalInfo &first, const SignalInfo &second) {
const std::vector<std::uint64_t> firstBits = SignalBits(first);
const std::vector<std::uint64_t> secondBits = SignalBits(second);
for (const std::uint64_t firstBit : firstBits) {
for (const std::uint64_t secondBit : secondBits) {
if (firstBit == secondBit)
return true;
}
}
return false;
}
}
void DbcParseContext::ValidateMultiplexing(const std::string &filePath) const {
for (std::size_t frameIndex = 0U; frameIndex < database.frames.size(); ++frameIndex) {
const FrameInfo &frame = database.frames[frameIndex];
try {
const dbc_detail::MultiplexGraph graph(frame.signals);
for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
const SignalInfo &signal = frame.signals[index];
if (IsMultiplexSelector(signal.multiplexing.role) && signal.isSigned)
throw dbc_detail::MultiplexError(index, "Multiplex selectors must be unsigned.");
for (std::size_t previous = 0U; previous < index; ++previous) {
if (!graph.AreMutuallyExclusive(index, previous) && SignalsOverlap(signal, frame.signals[previous]))
throw dbc_detail::MultiplexError(index, "Signals overlap without mutually exclusive multiplexing conditions.");
}
}
} catch (const dbc_detail::MultiplexError &error) {
throw DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing, filePath,
signalLines[frameIndex][error.index], error.what());
}
}
}