// SPDX-License-Identifier: MIT #include "dbc_statement_handler.h" #include "text_utils.h" #include "signal_layout.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) { static const std::regex number( R"(^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?$)"); if (!std::regex_match(text, number)) 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; } 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 std::string value = marker.substr(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 = 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) { FrameInfo *frame = nullptr; for (FrameInfo &candidate : database.frames) { if (candidate.canId == canId && candidate.isExtended == isExtended) { if (frame) throw std::runtime_error("Ambiguous VAL_ frame reference."); frame = &candidate; } } if (!frame) throw std::runtime_error("Unresolved VAL_ frame reference."); return frame; } SignalInfo *FindUniqueSignal(FrameInfo &frame, const std::string &signalName) { SignalInfo *signal = nullptr; for (SignalInfo &candidate : frame.signals) { if (candidate.name == signalName) { if (signal) throw std::runtime_error("Ambiguous VAL_ signal reference: " + signalName); signal = &candidate; } } if (!signal) throw std::runtime_error("Unresolved VAL_ 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 { std::vector SplitReceivers(const std::string &text) { std::vector receivers; std::string token; std::istringstream stream(text); while (std::getline(stream, token, ',')) { token = dbc_detail::TrimText(token); if (!token.empty()) receivers.push_back(token); } return receivers; } 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 */ if ((rawCanId & 0x80000000U) != 0U) { isExtended = true; normalizedCanId = (rawCanId & 0x1FFFFFFFU); } else { isExtended = (rawCanId > 0x7FFU); normalizedCanId = rawCanId; } } std::uint32_t TryExtractPgn(std::uint32_t canId, bool isExtended, bool &hasPgn) { hasPgn = false; if (!isExtended) return 0U; if ((canId & 0x1FFFFFFFU) != canId) return 0U; const std::uint32_t pf = (canId >> 16U) & 0xFFU; const std::uint32_t ps = (canId >> 8U) & 0xFFU; const std::uint32_t dp = (canId >> 24U) & 0x01U; std::uint32_t pgn = 0U; if (pf < 240U) pgn = (dp << 16U) | (pf << 8U); else pgn = (dp << 16U) | (pf << 8U) | ps; hasPgn = true; return pgn; } } // 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); frame.pgn = TryExtractPgn(frame.canId, frame.isExtended, frame.hasPgn); return frame; } SignalInfo DbcSignalHandler::ParseSignalLine(const std::string &line) { // Match the complete statement before converting any numeric fields. enum Field { NameAndMarker = 1, StartBit, BitLength, ByteOrder, Signedness, Factor, Offset, Minimum, Maximum, Unit, Receivers }; static const std::regex syntax( // Name and optional multiplexing marker. R"dbc(^SG_[ \t]+([^:]+):[ \t]*)dbc" // Bit position, length, byte order and signedness. R"dbc(([^| \t]+)[ \t]*\|[ \t]*([^@ \t]+)[ \t]*@[ \t]*([01])([+-])[ \t]*)dbc" // Scaling factor and offset. R"dbc(\([ \t]*([^, \t]+)[ \t]*,[ \t]*([^ )\t]+)[ \t]*\)[ \t]*)dbc" // Physical range. R"dbc(\[[ \t]*([^| \t]+)[ \t]*\|[ \t]*([^\] \t]+)[ \t]*\][ \t]*)dbc" // Unit and receivers. R"dbc("([^"\r\n]*)"[ \t]+([^ \t,]+(?:[ \t]*,[ \t]*[^ \t,]+)*)[ \t]*$)dbc"); std::smatch fields; if (!std::regex_match(line, fields, syntax)) throw std::runtime_error("Malformed or unsupported SG_ signal definition."); SignalInfo signal; std::istringstream nameFields(fields[NameAndMarker].str()); 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(fields[StartBit].str(), std::numeric_limits::max(), "signal start bit"); signal.length = ParseDecimal(fields[BitLength].str(), 64U, "signal length"); if (signal.length == 0U) throw std::runtime_error("Signal length must be within 1..64 bits."); signal.isLittleEndian = fields[ByteOrder].str() == "1"; signal.isSigned = fields[Signedness].str() == "-"; signal.factor = ParsePhysicalValue(fields[Factor].str(), "signal factor"); signal.offset = ParsePhysicalValue(fields[Offset].str(), "signal offset"); signal.minimum = ParsePhysicalValue(fields[Minimum].str(), "signal minimum"); signal.maximum = ParsePhysicalValue(fields[Maximum].str(), "signal maximum"); if (signal.minimum > signal.maximum) throw std::runtime_error("Signal minimum exceeds maximum."); signal.unit = fields[Unit].str(); signal.receivers = SplitReceivers(fields[Receivers].str()); 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 (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("CM_"); RegisterType("VAL_"); } 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 DbcUnsupportedMultiplexingHandler::Handle(const std::string &, DbcParseContext &) const { throw std::runtime_error("Extended and nested multiplexing are unsupported."); } 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 AreMutuallyExclusive(const SignalInfo &first, const SignalInfo &second) { return first.multiplexing.role == MultiplexRole::Branch && second.multiplexing.role == MultiplexRole::Branch && first.multiplexing.value != second.multiplexing.value; } std::string ValidateBranch(const SignalInfo &signal, const SignalInfo *selector) { if (signal.multiplexing.role != MultiplexRole::Branch) return {}; if (selector == nullptr) return "Multiplexed branch has no selector."; if (selector->length < 64U && signal.multiplexing.value >= (std::uint64_t(1U) << selector->length)) return "Branch value exceeds selector raw range."; return {}; } const SignalInfo *FindSelector(const FrameInfo &frame, const std::vector &lines, const std::string &filePath) { const SignalInfo *selector = nullptr; for (std::size_t index = 0U; index < frame.signals.size(); ++index) { const SignalInfo &signal = frame.signals[index]; if (signal.multiplexing.role != MultiplexRole::Selector) continue; if (selector != nullptr || signal.isSigned) throw DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing, filePath, lines[index], "Multiplexing requires one unsigned selector per frame."); selector = &signal; } return selector; } 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]; const SignalInfo *selector = FindSelector(frame, signalLines[frameIndex], filePath); for (std::size_t index = 0U; index < frame.signals.size(); ++index) { const SignalInfo &signal = frame.signals[index]; std::string error = ValidateBranch(signal, selector); for (std::size_t previous = 0U; previous < index && error.empty(); ++previous) { const SignalInfo &other = frame.signals[previous]; if (!AreMutuallyExclusive(signal, other) && SignalsOverlap(signal, other)) error = "Signals overlap without mutually exclusive multiplexing conditions."; } if (!error.empty()) throw DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing, filePath, signalLines[frameIndex][index], error); } } }