// SPDX-License-Identifier: MIT #include "dbc_statement_handler.h" #include #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 auto 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; } /** * @brief Remove leading and trailing spaces. * @param text Input text. * @return Trimmed text. */ std::string TrimText(const std::string &text) { std::string::size_type begin = 0U; while ((begin < text.size()) && std::isspace(static_cast(text[begin]))) ++begin; std::string::size_type end = text.size(); while ((end > begin) && std::isspace(static_cast(text[end - 1U]))) --end; return text.substr(begin, end - begin); } 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 = 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 = ParseDecimal( idText, std::numeric_limits::max(), "CAN ID"); // Only bit 31 may accompany the actual 29-bit identifier. if ((rawCanId & 0x60000000U) != 0U) throw std::runtime_error("CAN ID exceeds the 29-bit identifier range."); 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. static const std::regex syntax( R"dbc(^SG_[ \t]+([^:]+):[ \t]*([^| \t]+)[ \t]*\|[ \t]*([^@ \t]+)[ \t]*@[ \t]*([01])([+-])[ \t]*\([ \t]*([^, \t]+)[ \t]*,[ \t]*([^ )\t]+)[ \t]*\)[ \t]*\[[ \t]*([^| \t]+)[ \t]*\|[ \t]*([^\] \t]+)[ \t]*\][ \t]*"([^"\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; signal.name = TrimText(fields[1].str()); if (signal.name.empty() || signal.name.find_first_of(" \t\r\n") != std::string::npos) throw std::runtime_error("Invalid signal name or unsupported multiplexing syntax."); signal.startBit = ParseDecimal(fields[2].str(), std::numeric_limits::max(), "signal start bit"); signal.length = ParseDecimal(fields[3].str(), 64U, "signal length"); if (signal.length == 0U) throw std::runtime_error("Signal length must be within 1..64 bits."); signal.isLittleEndian = fields[4].str() == "1"; signal.isSigned = fields[5].str() == "-"; const auto parsePhysical = [](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); std::size_t consumed = 0U; double value; try { value = std::stod(text, &consumed); } catch (const std::exception &) { throw std::runtime_error(std::string("Invalid ") + field + ": " + text); } if (consumed != text.size() || !std::isfinite(value)) throw std::runtime_error(std::string("Invalid ") + field + ": " + text); return value; }; signal.factor = parsePhysical(fields[6].str(), "signal factor"); signal.offset = parsePhysical(fields[7].str(), "signal offset"); signal.minimum = parsePhysical(fields[8].str(), "signal minimum"); signal.maximum = parsePhysical(fields[9].str(), "signal maximum"); if (signal.minimum > signal.maximum) throw std::runtime_error("Signal minimum exceeds maximum."); signal.unit = fields[10].str(); signal.receivers = SplitReceivers(fields[11].str()); return signal; } void DbcCommentHandler::ParseCommentLine(const std::string &line, DbcDatabase &database) { /* * Examples: * CM_ BO_ 256 "Frame comment"; * CM_ SG_ 256 EngineSpeed "Signal comment"; */ std::istringstream stream(line); std::string token; stream >> token; if (token != "CM_") return; stream >> token; if (token == "BO_") { std::uint32_t rawCanId = 0U; std::uint32_t canId = 0U; bool isExtended = false; stream >> rawCanId; NormalizeCanId(rawCanId, canId, isExtended); const std::string::size_type quoteBegin = line.find('"'); const std::string::size_type quoteEnd = line.rfind('"'); if ((quoteBegin == std::string::npos) || (quoteEnd == std::string::npos) || (quoteEnd <= quoteBegin)) return; FrameInfo *frame = FindFrameById(database, canId, isExtended); if (frame != nullptr) frame->comment = line.substr(quoteBegin + 1U, quoteEnd - quoteBegin - 1U); } else if (token == "SG_") { std::uint32_t rawCanId = 0U; std::uint32_t canId = 0U; bool isExtended = false; std::string signalName; stream >> rawCanId; stream >> signalName; NormalizeCanId(rawCanId, canId, isExtended); const std::string::size_type quoteBegin = line.find('"'); const std::string::size_type quoteEnd = line.rfind('"'); if ((quoteBegin == std::string::npos) || (quoteEnd == std::string::npos) || (quoteEnd <= quoteBegin)) return; FrameInfo *frame = FindFrameById(database, canId, isExtended); if (frame != nullptr) { SignalInfo *signal = FindSignalByName(*frame, signalName); if (signal != nullptr) signal->comment = line.substr(quoteBegin + 1U, quoteEnd - quoteBegin - 1U); } } } namespace { FrameInfo *FindFrameById(DbcDatabase &database, std::uint32_t canId, bool isExtended) { for (std::size_t index = 0U; index < database.frames.size(); ++index) { if ((database.frames[index].canId == canId) && (database.frames[index].isExtended == isExtended)) return &database.frames[index]; } return nullptr; } SignalInfo *FindSignalByName(FrameInfo &frame, const std::string &signalName) { for (std::size_t index = 0U; index < frame.signals.size(); ++index) { if (frame.signals[index].name == signalName) return &frame.signals[index]; } return nullptr; } }// namespace void DbcFrameHandler::Handle(const std::string &line, DbcParseContext &context) const { context.database.frames.push_back(ParseFrameLine(line)); context.currentFrame = &context.database.frames.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); const std::uint64_t payloadBits = static_cast(context.currentFrame->dlc) * 8U; std::uint64_t bit = signal.startBit; for (std::uint32_t index = 0U; index < signal.length; ++index) { if (bit >= payloadBits) throw std::runtime_error("Signal exceeds the frame payload length."); if (signal.isLittleEndian) ++bit; else bit = (bit % 8U == 0U) ? bit + 15U : bit - 1U; } context.currentFrame->signals.push_back(std::move(signal)); } void DbcCommentHandler::Handle(const std::string &line, DbcParseContext &context) const { if (line.compare(0U, 4U, "CM_ ") == 0) ParseCommentLine(line, context.database); } DbcStatementDispatcher::DbcStatementDispatcher() { RegisterType("BO_"); RegisterType("SG_"); RegisterType("CM_"); } bool DbcStatementDispatcher::Dispatch(const std::string &line, DbcParseContext &context) const { std::istringstream stream(line); std::string keyword; stream >> keyword; const auto handler = m_handlers.find(keyword); if (handler == m_handlers.end()) return false; handler->second->Handle(line, context); return true; }