// 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 #include #include #include #include #include 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::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 Receivers() { if (position == text.size() || (text[position] != ' ' && text[position] != '\t')) throw std::runtime_error("Missing receiver separator in SG_ signal definition."); std::vector 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 "ed) { 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(ParseDecimal( lengthText, std::numeric_limits::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::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("VERSION"); RegisterType("NS_"); RegisterType("NS_:"); RegisterType("BS_"); RegisterType("BS_:"); RegisterType("BU_"); RegisterType("BU_:"); RegisterType("SG_MUL_VAL_"); RegisterType("BO_"); RegisterType("SG_"); RegisterType("SIG_VALTYPE_"); RegisterType("CM_"); RegisterType("VAL_"); RegisterType("BA_DEF_"); RegisterType("BA_DEF_DEF_"); RegisterType("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::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(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 SignalBits(const SignalInfo &signal) { std::vector 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 firstBits = SignalBits(first); const std::vector 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()); } } }