This commit is contained in:
deeaitch committed 2026-10-05 21:41:19 -04:00
1 parent 0067cb4ddd
commit f17ddb6aad
104 files changed
+1928 -889

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
INCLUDEPATH += $$PWD
DEPENDPATH += $$PWD
# Consumers can override this when building separately from the root project.
isEmpty(DBC_CORE_BUILD_DIR): DBC_CORE_BUILD_DIR = $$clean_path($$OUT_PWD/../core)
LIBS += -L$$DBC_CORE_BUILD_DIR -ldbc_core
win32-msvc*: DBC_CORE_LIBRARY = $$DBC_CORE_BUILD_DIR/dbc_core.lib
else: DBC_CORE_LIBRARY = $$DBC_CORE_BUILD_DIR/libdbc_core.a
PRE_TARGETDEPS += $$DBC_CORE_LIBRARY
+6
View File
@@ -0,0 +1,6 @@
TEMPLATE = lib
TARGET = dbc_core
CONFIG += staticlib c++17
CONFIG -= qt
include(core_files.pri)
CONFIG -= debug_and_release debug_and_release_target
+20
View File
@@ -0,0 +1,20 @@
SOURCES += \
$$PWD/dbc_decode_builder.cpp \
$$PWD/dbc_decoder.cpp \
$$PWD/dbc_parser.cpp \
$$PWD/dbc_statement_handler.cpp \
$$PWD/dbc_tree_builder.cpp \
$$PWD/tree_node.cpp
HEADERS += \
$$PWD/dbc_database.h \
$$PWD/dbc_decode_builder.h \
$$PWD/dbc_decoder.h \
$$PWD/dbc_parser.h \
$$PWD/dbc_statement_handler.h \
$$PWD/dbc_tree_builder.h \
$$PWD/decode_database.h \
$$PWD/frame_info.h \
$$PWD/signal_info.h \
$$PWD/tree_node.h \
$$PWD/typefactory.h
+26
View File
@@ -0,0 +1,26 @@
/**
* @file dbc_database.h
* @brief
*
* Created: 2026-03-13
* Author: Deeaitch (Dim. Himro)
*
* Licensed under the MIT License.
* See LICENSE file in the project root for full license text.
*/
#ifndef DBC_DATABASE_H
#define DBC_DATABASE_H
#include <vector>
#include "frame_info.h"
/**
* @brief Parsed DBC content stored in a simple internal form.
*/
struct DbcDatabase {
std::vector<FrameInfo> frames; /**< All frames found in the DBC file. */
};
#endif /* DBC_DATABASE_H */
+56
View File
@@ -0,0 +1,56 @@
/**
* @file dbc_decode_builder.cpp
* @brief
*
* Created: 2026-03-13
* Author: Deeaitch (Dim. Himro)
*
* Licensed under the MIT License.
* See LICENSE file in the project root for full license text.
*/
#include "dbc_decode_builder.h"
DecodeDatabase DbcDecodeBuilder::Build(const DbcDatabase &source) const {
DecodeDatabase result;
for (std::size_t frameIndex = 0U; frameIndex < source.frames.size(); ++frameIndex) {
const FrameInfo &sourceFrame = source.frames[frameIndex];
DecodeFrame targetFrame;
targetFrame.name = sourceFrame.name;
targetFrame.canId = sourceFrame.canId;
targetFrame.isExtended = sourceFrame.isExtended;
targetFrame.dlc = sourceFrame.dlc;
targetFrame.pgn = sourceFrame.pgn;
targetFrame.hasPgn = sourceFrame.hasPgn;
targetFrame.transmitter = sourceFrame.transmitter;
targetFrame.comment = sourceFrame.comment;
for (std::size_t signalIndex = 0U; signalIndex < sourceFrame.signals.size(); ++signalIndex) {
const SignalInfo &sourceSignal = sourceFrame.signals[signalIndex];
DecodeSignal targetSignal;
targetSignal.name = sourceSignal.name;
targetSignal.startBit = sourceSignal.startBit;
targetSignal.length = sourceSignal.length;
targetSignal.byteOrder = sourceSignal.isLittleEndian ? ByteOrder::Intel : ByteOrder::Motorola;
targetSignal.valueType = sourceSignal.isSigned ? ValueType::Signed : ValueType::Unsigned;
targetSignal.factor = sourceSignal.factor;
targetSignal.offset = sourceSignal.offset;
targetSignal.minimum = sourceSignal.minimum;
targetSignal.maximum = sourceSignal.maximum;
targetSignal.unit = sourceSignal.unit;
targetSignal.receivers = sourceSignal.receivers;
targetSignal.comment = sourceSignal.comment;
targetFrame.signals.push_back(targetSignal);
}
result.frames.push_back(targetFrame);
result.frameIndexByKey[FrameKey(targetFrame.canId, targetFrame.isExtended)] =
result.frames.size() - 1U;
}
return result;
}
+31
View File
@@ -0,0 +1,31 @@
/**
* @file dbc_decode_builder.h
* @brief
*
* Created: 2026-03-13
* Author: Deeaitch (Dim. Himro)
*
* Licensed under the MIT License.
* See LICENSE file in the project root for full license text.
*/
#ifndef DBC_DECODE_BUILDER_H
#define DBC_DECODE_BUILDER_H
#include "dbc_database.h"
#include "decode_database.h"
/**
* @brief Converts parsed DBC data into runtime decode database.
*/
class DbcDecodeBuilder {
public:
/**
* @brief Build runtime decode database.
* @param source Parsed DBC database.
* @return Runtime-ready decode database.
*/
DecodeDatabase Build(const DbcDatabase &source) const;
};
#endif /* DBC_DECODE_BUILDER_H */
+158
View File
@@ -0,0 +1,158 @@
/**
* @file dbc_decoder.cpp
* @brief
*
* Created: 2026-03-13
* Author: Deeaitch (Dim. Himro)
*
* Licensed under the MIT License.
* See LICENSE file in the project root for full license text.
*/
#include "dbc_decoder.h"
const DecodeFrame *DbcDecoder::FindFrame(const DecodeDatabase &database,
std::uint32_t canId,
bool isExtended) const {
const FrameKey key(canId, isExtended);
const std::unordered_map<FrameKey, std::size_t, FrameKeyHasher>::const_iterator it =
database.frameIndexByKey.find(key);
if (it == database.frameIndexByKey.end())
return nullptr;
const std::size_t index = it->second;
if (index >= database.frames.size())
return nullptr;
return &database.frames[index];
}
DecodedFrameValue DbcDecoder::Decode(const DecodeDatabase &database,
const RawCanFrame &frame) const {
DecodedFrameValue result;
const DecodeFrame *definition = FindFrame(database, frame.canId, frame.isExtended);
if (definition == nullptr)
return result;
result.definition = definition;
result.valid = true;
for (std::size_t signalIndex = 0U; signalIndex < definition->signals.size(); ++signalIndex) {
const DecodeSignal &signal = definition->signals[signalIndex];
DecodedSignalValue decoded;
std::uint64_t unsignedValue = 0U;
decoded.definition = &signal;
if (!ExtractUnsigned(frame.data, signal, unsignedValue)) {
decoded.valid = false;
result.signals.push_back(decoded);
continue;
}
if (signal.valueType == ValueType::Signed)
decoded.rawValue = SignExtend(unsignedValue, signal.length);
else
decoded.rawValue = unsignedValue;
double numericValue;
if (const auto *signedValue = std::get_if<std::int64_t>(&decoded.rawValue))
numericValue = static_cast<double>(*signedValue);
else
numericValue = static_cast<double>(std::get<std::uint64_t>(decoded.rawValue));
decoded.physicalValue = numericValue * signal.factor + signal.offset;
decoded.valid = true;
result.signals.push_back(decoded);
}
return result;
}
bool DbcDecoder::ExtractUnsigned(const std::vector<std::uint8_t> &data,
const DecodeSignal &signal,
std::uint64_t &value) {
if ((signal.length == 0U) || (signal.length > 64U))
return false;
if (signal.byteOrder == ByteOrder::Intel)
return ExtractIntel(data, signal.startBit, signal.length, value);
return ExtractMotorola(data, signal.startBit, signal.length, value);
}
bool DbcDecoder::ExtractIntel(const std::vector<std::uint8_t> &data,
std::uint32_t startBit,
std::uint32_t length,
std::uint64_t &value) {
value = 0U;
for (std::uint32_t bitIndex = 0U; bitIndex < length; ++bitIndex) {
const std::uint32_t absoluteBit = startBit + bitIndex;
const std::uint32_t byteIndex = absoluteBit / 8U;
const std::uint32_t bitInByte = absoluteBit % 8U;
if (byteIndex >= data.size())
return false;
const std::uint64_t bitValue =
(static_cast<std::uint64_t>((data[byteIndex] >> bitInByte) & 0x01U) << bitIndex);
value |= bitValue;
}
return true;
}
bool DbcDecoder::ExtractMotorola(const std::vector<std::uint8_t> &data,
std::uint32_t startBit,
std::uint32_t length,
std::uint64_t &value) {
/*
* DBC Motorola bit numbering:
* - startBit points to the most significant bit of the signal
* - inside a byte, bit numbering goes 7..0
* - crossing byte boundary moves to the next byte, bit 7
* Example: a signal starting at 7 follows 7..0, 15..8, 23..16.
*/
value = 0U;
std::size_t byteIndex = startBit / 8U;
std::uint32_t bitInByte = startBit % 8U;
for (std::uint32_t bitIndex = 0U; bitIndex < length; ++bitIndex) {
if (byteIndex >= data.size())
return false;
value <<= 1U;
value |= static_cast<std::uint64_t>((data[byteIndex] >> bitInByte) & 0x01U);
if (bitInByte == 0U) {
++byteIndex;
bitInByte = 7U;
} else
--bitInByte;
}
return true;
}
std::int64_t DbcDecoder::SignExtend(std::uint64_t value, std::uint32_t bitLength) {
if ((bitLength == 0U) || (bitLength > 64U))
return 0;
const std::uint64_t signMask = std::uint64_t(1U) << (bitLength - 1U);
const std::uint64_t valueMask = (bitLength == 64U)
? ~std::uint64_t(0U)
: (std::uint64_t(1U) << bitLength) - 1U;
value &= valueMask;
if ((value & signMask) == 0U)
return static_cast<std::int64_t>(value);
// The complemented magnitude fits int64_t, including for INT64_MIN.
return -1 - static_cast<std::int64_t>((~value) & valueMask);
}
+104
View File
@@ -0,0 +1,104 @@
/**
* @file dbc_decoder.h
* @brief
*
* Created: 2026-03-13
* Author: Deeaitch (Dim. Himro)
*
* Licensed under the MIT License.
* See LICENSE file in the project root for full license text.
*/
#ifndef DBC_DECODER_H
#define DBC_DECODER_H
#include <cstdint>
#include <variant>
#include <vector>
#include "decode_database.h"
/**
* @brief Raw CAN frame used for runtime or trace decoding.
*/
struct RawCanFrame {
std::uint32_t canId; /**< Normalized CAN ID. */
bool isExtended; /**< true for extended frame. */
std::vector<std::uint8_t> data; /**< Payload bytes. */
RawCanFrame()
: canId(0U), isExtended(false), data() {
}
};
/**
* @brief One decoded signal value.
*/
struct DecodedSignalValue {
const DecodeSignal *definition; /**< Signal definition. */
std::variant<std::int64_t, std::uint64_t> rawValue; /**< Exact signed or unsigned raw value. */
double physicalValue; /**< Converted physical value. */
bool valid; /**< true if decoding succeeded. */
DecodedSignalValue()
: definition(nullptr), rawValue(std::uint64_t(0U)), physicalValue(0.0), valid(false) {
}
};
/**
* @brief Fully decoded frame.
*/
struct DecodedFrameValue {
const DecodeFrame *definition; /**< Frame definition. */
std::vector<DecodedSignalValue> signals; /**< Decoded signal values. */
bool valid; /**< true if frame was matched. */
DecodedFrameValue()
: definition(nullptr), signals(), valid(false) {
}
};
/**
* @brief Runtime CAN decoder using prebuilt decode database.
*/
class DbcDecoder {
public:
/**
* @brief Find frame definition by CAN ID.
* @param database Runtime decode database.
* @param canId Normalized CAN ID.
* @param isExtended true for extended frame.
* @return Pointer to frame definition or nullptr.
*/
const DecodeFrame *FindFrame(const DecodeDatabase &database,
std::uint32_t canId,
bool isExtended) const;
/**
* @brief Decode one raw CAN frame.
* @param database Runtime decode database.
* @param frame Raw CAN frame.
* @return Decoded frame value.
*/
DecodedFrameValue Decode(const DecodeDatabase &database,
const RawCanFrame &frame) const;
private:
static bool ExtractUnsigned(const std::vector<std::uint8_t> &data,
const DecodeSignal &signal,
std::uint64_t &value);
static bool ExtractIntel(const std::vector<std::uint8_t> &data,
std::uint32_t startBit,
std::uint32_t length,
std::uint64_t &value);
static bool ExtractMotorola(const std::vector<std::uint8_t> &data,
std::uint32_t startBit,
std::uint32_t length,
std::uint64_t &value);
static std::int64_t SignExtend(std::uint64_t value, std::uint32_t bitLength);
};
#endif /* DBC_DECODER_H */
+70
View File
@@ -0,0 +1,70 @@
/**
* @file dbc_parser.cpp
* @brief
*
* Created: 2026-03-13
* Author: Deeaitch (Dim. Himro)
*
* Licensed under the MIT License.
* See LICENSE file in the project root for full license text.
*/
#include "dbc_parser.h"
#include "dbc_statement_handler.h"
#include <cctype>
#include <fstream>
#include <stdexcept>
namespace {
/**
* @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<unsigned char>(text[begin])))
++begin;
std::string::size_type end = text.size();
while ((end > begin) &&
std::isspace(static_cast<unsigned char>(text[end - 1U])))
--end;
return text.substr(begin, end - begin);
}
}// namespace
DbcDatabase DbcParser::ParseFile(const std::string &filePath) const {
std::ifstream input(filePath.c_str());
if (!input.is_open())
throw std::runtime_error("Failed to open DBC file: " + filePath);
DbcDatabase database;
DbcParseContext context{database, nullptr};
const DbcStatementDispatcher dispatcher;
std::size_t lineNumber = 0U;
std::string line;
while (std::getline(input, line)) {
++lineNumber;
line = Trim(line);
if (line.empty())
continue;
try {
dispatcher.Dispatch(line, context);
} catch (const std::exception &error) {
throw std::runtime_error(filePath + ":" + std::to_string(lineNumber) +
": " + error.what());
}
}
return database;
}
std::string DbcParser::Trim(const std::string &text) {
return TrimText(text);
}
+48
View File
@@ -0,0 +1,48 @@
/**
* @file dbc_parser.h
* @brief
*
* Created: 2026-03-13
* Author: Deeaitch (Dim. Himro)
*
* Licensed under the MIT License.
* See LICENSE file in the project root for full license text.
*/
#ifndef DBC_PARSER_H
#define DBC_PARSER_H
#include <string>
#include "dbc_database.h"
/**
* @brief Minimal DBC parser.
*
* Supports:
* - BO_
* - SG_
* - CM_ BO_
* - CM_ SG_
*
* Ignores:
* - attributes
* - multiplexing
* - value tables
*/
class DbcParser {
public:
/**
* @brief Parse DBC file.
* @param filePath Path to input file.
* @return Parsed database.
* @throws std::runtime_error on file or parse errors.
* Line parse errors include the file path and one-based line number.
*/
DbcDatabase ParseFile(const std::string &filePath) const;
private:
static std::string Trim(const std::string &text);
};
#endif /* DBC_PARSER_H */
+324
View File
@@ -0,0 +1,324 @@
// SPDX-License-Identifier: MIT
#include "dbc_statement_handler.h"
#include <cctype>
#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 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;
}
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);
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;
}
/**
* @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<unsigned char>(text[begin])))
++begin;
std::string::size_type end = text.size();
while ((end > begin) &&
std::isspace(static_cast<unsigned char>(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<std::string> SplitReceivers(const std::string &text) {
std::vector<std::string> 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<std::uint32_t>::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<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);
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<std::uint32_t>::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() == "-";
signal.factor = ParsePhysicalValue(fields[6].str(), "signal factor");
signal.offset = ParsePhysicalValue(fields[7].str(), "signal offset");
signal.minimum = ParsePhysicalValue(fields[8].str(), "signal minimum");
signal.maximum = ParsePhysicalValue(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<std::uint64_t>(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<DbcFrameHandler>("BO_");
RegisterType<DbcSignalHandler>("SG_");
RegisterType<DbcCommentHandler>("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;
}
+65
View File
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: MIT
#ifndef DBC_STATEMENT_HANDLER_H
#define DBC_STATEMENT_HANDLER_H
#include "dbc_database.h"
#include "typefactory.h"
#include <string>
#include <unordered_map>
/** Mutable state belonging to one file parse. Handlers do not retain it. */
struct DbcParseContext {
DbcDatabase &database;
FrameInfo *currentFrame = nullptr;
};
class DbcStatementHandler {
public:
virtual ~DbcStatementHandler() = default;
virtual void Handle(const std::string &line, DbcParseContext &context) const = 0;
};
class DbcFrameHandler : public DbcStatementHandler {
public:
void Handle(const std::string &line, DbcParseContext &context) const override;
private:
static FrameInfo ParseFrameLine(const std::string &line);
};
class DbcSignalHandler : public DbcStatementHandler {
public:
void Handle(const std::string &line, DbcParseContext &context) const override;
private:
static SignalInfo ParseSignalLine(const std::string &line);
};
class DbcCommentHandler : public DbcStatementHandler {
public:
void Handle(const std::string &line, DbcParseContext &context) const override;
private:
static void ParseCommentLine(const std::string &line, DbcDatabase &database);
};
/** Exact-key dispatch to handlers created once per registration via TypeFactory. */
class DbcStatementDispatcher {
public:
DbcStatementDispatcher();
template<class Handler>
void RegisterType(const std::string &keyword) {
m_factory.registerType<Handler>(keyword);
m_handlers.insert_or_assign(keyword, m_factory.create(keyword));
}
/** Returns false for an unregistered keyword; input must be trimmed. */
bool Dispatch(const std::string &line, DbcParseContext &context) const;
private:
TypeFactory<std::string, DbcStatementHandler> m_factory;
std::unordered_map<std::string, std::shared_ptr<DbcStatementHandler>> m_handlers;
};
#endif /* DBC_STATEMENT_HANDLER_H */
+31
View File
@@ -0,0 +1,31 @@
/**
* @file dbc_tree_builder.cpp
* @brief
*
* Created: 2026-03-13
* Author: Deeaitch (Dim. Himro)
*
* Licensed under the MIT License.
* See LICENSE file in the project root for full license text.
*/
#include "dbc_tree_builder.h"
std::unique_ptr<TreeNode> DbcTreeBuilder::Build(const DbcDatabase &database) const {
std::unique_ptr<TreeNode> root(new TreeNode());
for (std::size_t frameIndex = 0U; frameIndex < database.frames.size(); ++frameIndex) {
const FrameInfo &frame = database.frames[frameIndex];
std::unique_ptr<TreeNode> frameNode(new TreeNode(frame));
for (std::size_t signalIndex = 0U; signalIndex < frame.signals.size(); ++signalIndex) {
const SignalInfo &signal = frame.signals[signalIndex];
std::unique_ptr<TreeNode> signalNode(new TreeNode(signal));
frameNode->AddChild(std::move(signalNode));
}
root->AddChild(std::move(frameNode));
}
return root;
}
+33
View File
@@ -0,0 +1,33 @@
/**
* @file dbc_tree_builder.h
* @brief
*
* Created: 2026-03-13
* Author: Deeaitch (Dim. Himro)
*
* Licensed under the MIT License.
* See LICENSE file in the project root for full license text.
*/
#ifndef DBC_TREE_BUILDER_H
#define DBC_TREE_BUILDER_H
#include <memory>
#include "dbc_database.h"
#include "tree_node.h"
/**
* @brief Builds a simple tree from parsed DBC database.
*/
class DbcTreeBuilder {
public:
/**
* @brief Build tree representation of parsed DBC data.
* @param database Parsed database.
* @return Root node of the tree.
*/
std::unique_ptr<TreeNode> Build(const DbcDatabase &database) const;
};
#endif /* DBC_TREE_BUILDER_H */
+116
View File
@@ -0,0 +1,116 @@
/**
* @file decode_database.h
* @brief
*
* Created: 2026-03-13
* Author: Deeaitch (Dim. Himro)
*
* Licensed under the MIT License.
* See LICENSE file in the project root for full license text.
*/
#ifndef DECODE_DATABASE_H
#define DECODE_DATABASE_H
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>
/**
* @brief Signal byte order used for runtime decoding.
*/
enum class ByteOrder {
Intel,
Motorola
};
/**
* @brief Signal numeric type.
*/
enum class ValueType {
Unsigned,
Signed
};
/**
* @brief Runtime-ready signal definition.
*/
struct DecodeSignal {
std::string name; /**< Signal name. */
std::uint32_t startBit; /**< DBC start bit. */
std::uint32_t length; /**< Signal length in bits. */
ByteOrder byteOrder; /**< Intel or Motorola. */
ValueType valueType; /**< Signed or unsigned. */
double factor; /**< Scaling factor. */
double offset; /**< Physical offset. */
double minimum; /**< Minimum physical value. */
double maximum; /**< Maximum physical value. */
std::string unit; /**< Physical unit. */
std::vector<std::string> receivers; /**< Receivers. */
std::string comment; /**< Comment. */
DecodeSignal()
: name(), startBit(0U), length(0U), byteOrder(ByteOrder::Intel), valueType(ValueType::Unsigned), factor(1.0), offset(0.0), minimum(0.0), maximum(0.0), unit(), receivers(), comment() {
}
};
/**
* @brief Runtime-ready frame definition.
*/
struct DecodeFrame {
std::string name; /**< Frame name. */
std::uint32_t canId; /**< Normalized CAN ID. */
bool isExtended; /**< true for extended frame. */
std::uint8_t dlc; /**< Payload length. */
std::uint32_t pgn; /**< PGN if available. */
bool hasPgn; /**< true if PGN is valid. */
std::string transmitter; /**< Transmitter ECU. */
std::string comment; /**< Frame comment. */
std::vector<DecodeSignal> signals; /**< Signal definitions. */
DecodeFrame()
: name(), canId(0U), isExtended(false), dlc(0U), pgn(0U), hasPgn(false), transmitter(), comment(), signals() {
}
};
/**
* @brief Key for fast frame lookup.
*/
struct FrameKey {
std::uint32_t canId;
bool isExtended;
FrameKey()
: canId(0U), isExtended(false) {
}
FrameKey(std::uint32_t id, bool extended)
: canId(id), isExtended(extended) {
}
bool operator==(const FrameKey &other) const {
return (canId == other.canId) && (isExtended == other.isExtended);
}
};
/**
* @brief Hasher for frame key.
*/
struct FrameKeyHasher {
std::size_t operator()(const FrameKey &key) const {
const std::size_t a = static_cast<std::size_t>(key.canId);
const std::size_t b = key.isExtended ? 1U : 0U;
return (a * 1315423911U) ^ b;
}
};
/**
* @brief Runtime decode database with fast lookup by CAN ID.
*/
struct DecodeDatabase {
std::vector<DecodeFrame> frames;
std::unordered_map<FrameKey, std::size_t, FrameKeyHasher> frameIndexByKey;
};
#endif /* DECODE_DATABASE_H */
+40
View File
@@ -0,0 +1,40 @@
/**
* @file frame_info.h
* @brief
*
* Created: 2026-03-13
* Author: Deeaitch (Dim. Himro)
*
* Licensed under the MIT License.
* See LICENSE file in the project root for full license text.
*/
#ifndef FRAME_INFO_H
#define FRAME_INFO_H
#include <cstdint>
#include <string>
#include <vector>
#include "signal_info.h"
/**
* @brief Describes one CAN frame from a DBC file.
*/
struct FrameInfo {
std::string name; /**< Frame name. */
std::uint32_t canId; /**< Normalized CAN identifier. */
bool isExtended; /**< true for extended frame. */
std::uint32_t pgn; /**< J1939 PGN if applicable. */
bool hasPgn; /**< true if PGN was derived. */
std::uint8_t dlc; /**< Frame payload length. */
std::string transmitter; /**< Transmitter ECU name. */
std::string comment; /**< Optional frame comment. */
std::vector<SignalInfo> signals; /**< Signals contained in the frame. */
FrameInfo()
: name(), canId(0U), isExtended(false), pgn(0U), hasPgn(false), dlc(0U), transmitter(), comment(), signals() {
}
};
#endif /* FRAME_INFO_H */
+41
View File
@@ -0,0 +1,41 @@
/**
* @file signal_info.h
* @brief
*
* Created: 2026-03-13
* Author: Deeaitch (Dim. Himro)
*
* Licensed under the MIT License.
* See LICENSE file in the project root for full license text.
*/
#ifndef SIGNAL_INFO_H
#define SIGNAL_INFO_H
#include <cstdint>
#include <string>
#include <vector>
/**
* @brief Describes one signal inside a DBC frame.
*/
struct SignalInfo {
std::string name; /**< Signal name. */
std::uint32_t startBit; /**< Start bit in DBC notation. */
std::uint32_t length; /**< Signal length in bits. */
bool isLittleEndian; /**< true for Intel, false for Motorola. */
bool isSigned; /**< true if signal is signed. */
double factor; /**< Scaling factor. */
double offset; /**< Physical offset. */
double minimum; /**< Minimum physical value. */
double maximum; /**< Maximum physical value. */
std::string unit; /**< Physical unit. */
std::vector<std::string> receivers; /**< Signal receivers. */
std::string comment; /**< Optional signal comment. */
SignalInfo()
: name(), startBit(0U), length(0U), isLittleEndian(true), isSigned(false), factor(1.0), offset(0.0), minimum(0.0), maximum(0.0), unit(), receivers(), comment() {
}
};
#endif /* SIGNAL_INFO_H */
+63
View File
@@ -0,0 +1,63 @@
/**
* @file tree_node.cpp
* @brief
*
* Created: 2026-03-13
* Author: Deeaitch (Dim. Himro)
*
* Licensed under the MIT License.
* See LICENSE file in the project root for full license text.
*/
#include "tree_node.h"
TreeNode::TreeNode()
: m_type(NodeType::Root), m_name("dbc"), m_children(), m_frame(), m_signal() {
}
TreeNode::TreeNode(const FrameInfo &frame)
: m_type(NodeType::Frame), m_name(frame.name), m_children(), m_frame(new FrameInfo(frame)), m_signal() {
}
TreeNode::TreeNode(const SignalInfo &signal)
: m_type(NodeType::Signal), m_name(signal.name), m_children(), m_frame(), m_signal(new SignalInfo(signal)) {
}
void TreeNode::AddChild(std::unique_ptr<TreeNode> child) {
if (child)
m_children.push_back(std::move(child));
}
std::size_t TreeNode::GetChildCount() const {
return m_children.size();
}
const TreeNode *TreeNode::GetChild(std::size_t index) const {
if (index >= m_children.size())
return nullptr;
return m_children[index].get();
}
TreeNode *TreeNode::GetChild(std::size_t index) {
if (index >= m_children.size())
return nullptr;
return m_children[index].get();
}
NodeType TreeNode::GetType() const {
return m_type;
}
const std::string &TreeNode::GetName() const {
return m_name;
}
const FrameInfo *TreeNode::GetFrame() const {
return m_frame.get();
}
const SignalInfo *TreeNode::GetSignal() const {
return m_signal.get();
}
+120
View File
@@ -0,0 +1,120 @@
/**
* @file tree_node.h
* @brief
*
* Created: 2026-03-13
* Author: Deeaitch (Dim. Himro)
*
* Licensed under the MIT License.
* See LICENSE file in the project root for full license text.
*/
#ifndef TREE_NODE_H
#define TREE_NODE_H
#include <cstddef>
#include <memory>
#include <string>
#include <vector>
#include "frame_info.h"
#include "signal_info.h"
/**
* @brief Type of a tree node.
*/
enum class NodeType {
Root,
Frame,
Signal
};
/**
* @brief Tree node for later use in model/view or other hierarchy consumers.
*/
class TreeNode {
public:
/**
* @brief Create root node.
*/
TreeNode();
/**
* @brief Create frame node.
* @param frame Frame payload.
*/
explicit TreeNode(const FrameInfo &frame);
/**
* @brief Create signal node.
* @param signal Signal payload.
*/
explicit TreeNode(const SignalInfo &signal);
~TreeNode() = default;
TreeNode(const TreeNode &) = delete;
TreeNode &operator=(const TreeNode &) = delete;
TreeNode(TreeNode &&) = default;
TreeNode &operator=(TreeNode &&) = default;
/**
* @brief Add child node.
* @param child Child node to add.
*/
void AddChild(std::unique_ptr<TreeNode> child);
/**
* @brief Get child count.
* @return Number of children.
*/
std::size_t GetChildCount() const;
/**
* @brief Get child by index.
* @param index Child index.
* @return Child pointer or nullptr if index is invalid.
*/
const TreeNode *GetChild(std::size_t index) const;
/**
* @brief Get mutable child by index.
* @param index Child index.
* @return Child pointer or nullptr if index is invalid.
*/
TreeNode *GetChild(std::size_t index);
/**
* @brief Get node type.
* @return Node type.
*/
NodeType GetType() const;
/**
* @brief Get display name.
* @return Node name.
*/
const std::string &GetName() const;
/**
* @brief Get frame payload if node is frame.
* @return Pointer to frame info or nullptr.
*/
const FrameInfo *GetFrame() const;
/**
* @brief Get signal payload if node is signal.
* @return Pointer to signal info or nullptr.
*/
const SignalInfo *GetSignal() const;
private:
NodeType m_type;
std::string m_name;
std::vector<std::unique_ptr<TreeNode>> m_children;
std::unique_ptr<FrameInfo> m_frame;
std::unique_ptr<SignalInfo> m_signal;
};
#endif /* TREE_NODE_H */
+118
View File
@@ -0,0 +1,118 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Dim Himro
#ifndef TYPEFACTORY_H
#define TYPEFACTORY_H
#include <memory>
#include <type_traits>
#include <unordered_map>
#include <utility>// std::move
/**
* @brief Registry-based factory for creating objects by runtime identifier.
*
* Allows registering derived types and instantiating them using a ClassId key.
*
* Notes:
* - Not thread-safe. Register types during initialization phase only.
* - Re-registering the same id overwrites the previous entry ("last wins").
* - ClassId must be hashable (std::hash<ClassId> specialization must exist).
*
* @tparam ClassId -Unique class identifier type (e.g. std::string, int, enum).
* @tparam BaseClass -Base type for all created objects.
* @tparam Arg - Constructor arguments forwarded to registered derived types (by value).
*
* Usage example:
* @code
* #include <string>
*
* struct Base {
* virtual ~Base() = default;
* virtual int get() = 0;
* };
*
* struct Derived1 : Base {
* explicit Derived1(int start) : m_start(start) {}
* int get() override { return m_start + 1; }
* int m_start = 0;
* };
*
* struct Derived2 : Base {
* explicit Derived2(int start) : m_start(start) {}
* int get() override { return m_start + 2; }
* int m_start = 0;
* };
*
* TypeFactory<std::string, Base, int> factory;
* factory.registerType<Derived1>("one");
* factory.registerType<Derived2>("two");
*
* auto a = factory.create("one", 10);
* auto b = factory.creator("two")(10); // advanced API: get creator function
*
* (void)a->get();
* (void)b->get();
* @endcode
*/
template<class ClassId, class BaseClass, class... Args>
class TypeFactory {
public:
using BasePtr = std::shared_ptr<BaseClass>;
using CreatorFn = BasePtr (*)(Args...);
TypeFactory() = default;
/**
* @brief Registers a Derived type under the given id.
*
* Requirements (checked at compile-time):
* - Derived must inherit from BaseClass
* - Derived must be constructible from Args...
*
* Re-registering the same id overwrites the previous entry ("last wins").
*/
template<class Derived>
void registerType(const ClassId &id) {
static_assert(std::is_base_of_v<BaseClass, Derived>,
"Derived must inherit from BaseClass");
static_assert(std::is_constructible_v<Derived, Args...>,
"Derived must be constructible from Args...");
classes.insert_or_assign(id, &instantiate<Derived>);
}
/**
* @brief Returns creator function for the given id.
* @throws std::out_of_range when id is not registered.
*/
[[nodiscard]] CreatorFn creator(const ClassId &id) const {
return classes.at(id);
}
/**
* @brief Creates an instance for the given id using provided arguments.
* @throws std::out_of_range when id is not registered.
*/
[[nodiscard]] BasePtr create(const ClassId &id, Args... args) const {
return creator(id)(std::move(args)...);
}
/**
* @brief Checks whether an id is registered.
*/
[[nodiscard]] bool contains(const ClassId &id) const {
return classes.find(id) != classes.end();
}
protected:
// Protected by design: derived factories may extend/inspect the registry.
std::unordered_map<ClassId, CreatorFn> classes;
private:
template<class Derived>
static BasePtr instantiate(Args... args) {
return std::make_shared<Derived>(std::move(args)...);
}
};
#endif// TYPEFACTORY_H