Compare commits

..
6 Commits
27 changed files with 1663 additions and 831 deletions

No files matched your search

+65
View File
@@ -0,0 +1,65 @@
BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: None
AlignOperands: Align
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Always
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: true
BreakBeforeBinaryOperators: None
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
ColumnLimit: 0
CompactNamespaces: false
ContinuationIndentWidth: 8
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: true
MaxEmptyLinesToKeep: 2
NamespaceIndentation: All
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PointerAlignment: Right
ReflowComments: false
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 0
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
TabWidth: 4
UseTab: Never
+132 -9
View File
@@ -151,8 +151,11 @@ Files:
- `dbc_parser.h` - `dbc_parser.h`
- `dbc_parser.cpp` - `dbc_parser.cpp`
- `dbc_statement_handler.h`
- `dbc_statement_handler.cpp`
- `typefactory.h`
These parse the DBC file itself. The parser reads files and reports errors; registered statement handlers parse DBC constructs.
### Demo ### Demo
@@ -414,6 +417,34 @@ Fields:
--- ---
### Raw value representation and precision
`DecodedSignalValue::rawValue` is a C++17
`std::variant<std::int64_t, std::uint64_t>`. For a valid signal, signed definitions
produce the `int64_t` alternative and unsigned definitions produce the `uint64_t`
alternative. Raw values remain exact across their full 64-bit ranges. Check
`valid` before using the value; an invalid result's default value is not decoded data.
Use `std::get` when the numeric type is known, or `std::visit` to handle both:
```cpp
std::visit([] (auto raw) { std::cout << raw; }, decodedSignal.rawValue);
```
This replaces the previous `int64_t` field: consumers that read or print
`rawValue` directly must handle the variant.
Physical conversion uses the active numeric type:
`physicalValue = static_cast<double>(raw) * factor + offset`.
`physicalValue` remains a `double` and is approximate for large integers.
On usual IEEE 754 binary64 implementations, integers are exactly representable
through 2^53; above that, some integers round to the same floating-point value.
For example, unsigned `UINT64_MAX` remains exact in `rawValue`, but converts to
approximately 18446744073709551616 in binary64 before scaling.
Use `rawValue` whenever exact integer values are required.
---
## `DecodedFrameValue` ## `DecodedFrameValue`
Represents one decoded frame result. Represents one decoded frame result.
@@ -477,6 +508,35 @@ Parsed fields:
--- ---
### Frame validation and parse errors
`BO_` definitions require a decimal CAN ID, a nonempty single-token frame name,
a colon, a decimal payload length, and a transmitter token. Spaces or tabs may
separate fields, and whitespace before the colon is accepted. Additional trailing
fields are rejected.
Numeric fields must contain decimal digits only; signs, hexadecimal notation,
fractional values, suffixes, and overflow are rejected. CAN IDs may contain the
29-bit identifier plus the optional DBC extended flag (bit 31). Bits 29 and 30
are rejected rather than silently discarded. For compatibility, unflagged IDs
above `0x7FF` are still treated as extended.
The payload length must fit `uint8_t` (`0..255`). This is a storage-range check,
not CAN or CAN FD protocol validation; no new 8-byte or 64-byte limit is imposed.
Parsing stops at the first detected error. Exceptions raised while parsing a
line are reported as `std::runtime_error` with the supplied file path and the
one-based physical line number, including blank lines:
```text
example.dbc:3: Invalid CAN ID: invalid
```
This change validates frame definitions. Signal definitions are also validated as described below; comprehensive
DBC grammar coverage is still planned. File-open errors contain the path but have no line number.
---
## Signal definition ## Signal definition
Example: Example:
@@ -501,6 +561,35 @@ Parsed fields:
--- ---
### Signal validation and parse errors
`SG_` statements are matched completely. Signal names must be nonempty and
contain no whitespace. Spaces and tabs are accepted between syntax elements;
byte order and signedness remain adjacent (`@0+`, `@0-`, `@1+`, or `@1-`).
A quoted unit (which may be empty) and a nonempty comma-separated receiver
list are required. Unexpected trailing fields are rejected.
Start bits must be unsigned decimal integers fitting `uint32_t`; lengths must
be within `1..64` bits. Signs, fractional values, numeric suffixes, and overflow
are rejected for these fields. Every signal bit must fit within the frame's
declared payload length, using Intel increasing bit positions or Motorola
sawtooth traversal. This uses the stored payload length without introducing
an additional CAN or CAN FD payload limit.
Factor, offset, minimum, and maximum accept signed decimal numbers, fractions,
and scientific notation. The entire numeric token must be valid; hexadecimal
numbers, NaN, infinity, conversion overflow, and conversion underflow are
rejected. Minimum must not exceed maximum. Zero and negative factors are allowed.
Multiplexing markers (`M`, `mN`, and combinations) are currently rejected rather
than interpreted as part of a signal name. Multiplexing support and duplicate
frame/signal detection remain separate work.
Invalid signal definitions stop parsing with `path:line: description`, including
when the signal exceeds the declared payload length.
---
## Comments ## Comments
Frame comment example: Frame comment example:
@@ -709,20 +798,47 @@ That is why `DecodeDatabase` is a separate layer.
--- ---
## Why No Abstract Factory Is Used ## Statement Handler Factory
At the current stage, abstract factory is intentionally avoided. `DbcParser` owns file reading, trimming, line numbers, and error reporting.
Each `ParseFile()` call creates a fresh `DbcParseContext` and a
`DbcStatementDispatcher`. The context contains the parsed database and current
frame; handlers receive it by reference and retain no parsing state.
The current design is already clean: The dispatcher uses `TypeFactory<std::string, DbcStatementHandler>` to create
registered handlers once per parsing session, then reuses them for all matching
lines:
```text - `BO_` → `DbcFrameHandler`
parser → parsed database → decode database - `SG_` → `DbcSignalHandler`
↘ tree builder → UI tree - `CM_` → `DbcCommentHandler`
Grammar implementations live in `dbc_statement_handler.cpp`; their interface,
context, and dispatcher live in `dbc_statement_handler.h`. `typefactory.h`
provides the generic registry and object creation.
To add a construct, implement a default-constructible handler deriving from
`DbcStatementHandler` with:
```cpp
void Handle(const std::string &line, DbcParseContext &context) const override;
``` ```
Introducing factory layers now would increase complexity without solving an immediate problem. Register it in the dispatcher constructor:
If later the project requires multiple output representations or multiple build strategies, that can be added then. ```cpp
RegisterType<DbcValueHandler>("VAL_"); // after implementing DbcValueHandler
```
Registration creates one handler; replacing a registration replaces that instance.
Register during initialization before dispatching. A line is routed using its
full first whitespace-delimited keyword, so `BO_TX_BU_` cannot match `BO_`.
Unregistered keywords remain ignored. `SG_` accepts spaces and tabs after its keyword; `CM_` retains its existing
policy requiring a literal space.
This factory handles statements inside DBC files. A separate format-level
factory can be introduced when another file format is implemented. The decoder
and database builders remain independent of statement dispatch.
--- ---
@@ -839,3 +955,10 @@ DBC parser → DbcDatabase → DecodeDatabase → DbcDecoder
``` ```
This gives FrameTap a much better foundation for real use, because both browsing and decoding are supported without forcing one representation to do the other's job. This gives FrameTap a much better foundation for real use, because both browsing and decoding are supported without forcing one representation to do the other's job.
## Regression tests
See [tests/README.md](tests/README.md) for compiler and qmake commands.
Tests cover frame validation and error locations, Intel and Motorola extraction,
signed and unsigned 64-bit boundaries,
physical conversion, numeric output, and invalid signal payloads.
+3
View File
@@ -7,6 +7,7 @@ SOURCES += \
dbc_decode_builder.cpp \ dbc_decode_builder.cpp \
dbc_decoder.cpp \ dbc_decoder.cpp \
dbc_parser.cpp \ dbc_parser.cpp \
dbc_statement_handler.cpp \
dbc_tree_builder.cpp \ dbc_tree_builder.cpp \
main.cpp \ main.cpp \
tree_node.cpp tree_node.cpp
@@ -16,6 +17,8 @@ HEADERS += \
dbc_decode_builder.h \ dbc_decode_builder.h \
dbc_decoder.h \ dbc_decoder.h \
dbc_parser.h \ dbc_parser.h \
dbc_statement_handler.h \
typefactory.h \
dbc_tree_builder.h \ dbc_tree_builder.h \
decode_database.h \ decode_database.h \
frame_info.h \ frame_info.h \
+1 -1
View File
@@ -20,7 +20,7 @@
* @brief Parsed DBC content stored in a simple internal form. * @brief Parsed DBC content stored in a simple internal form.
*/ */
struct DbcDatabase { struct DbcDatabase {
std::vector<FrameInfo> frames; /**< All frames found in the DBC file. */ std::vector<FrameInfo> frames; /**< All frames found in the DBC file. */
}; };
#endif /* DBC_DATABASE_H */ #endif /* DBC_DATABASE_H */
+35 -35
View File
@@ -11,46 +11,46 @@
#include "dbc_decode_builder.h" #include "dbc_decode_builder.h"
DecodeDatabase DbcDecodeBuilder::Build (const DbcDatabase &source) const { DecodeDatabase DbcDecodeBuilder::Build(const DbcDatabase &source) const {
DecodeDatabase result; DecodeDatabase result;
for (std::size_t frameIndex = 0U; frameIndex < source.frames.size(); ++frameIndex) { for (std::size_t frameIndex = 0U; frameIndex < source.frames.size(); ++frameIndex) {
const FrameInfo &sourceFrame = source.frames[frameIndex]; const FrameInfo &sourceFrame = source.frames[frameIndex];
DecodeFrame targetFrame; DecodeFrame targetFrame;
targetFrame.name = sourceFrame.name; targetFrame.name = sourceFrame.name;
targetFrame.canId = sourceFrame.canId; targetFrame.canId = sourceFrame.canId;
targetFrame.isExtended = sourceFrame.isExtended; targetFrame.isExtended = sourceFrame.isExtended;
targetFrame.dlc = sourceFrame.dlc; targetFrame.dlc = sourceFrame.dlc;
targetFrame.pgn = sourceFrame.pgn; targetFrame.pgn = sourceFrame.pgn;
targetFrame.hasPgn = sourceFrame.hasPgn; targetFrame.hasPgn = sourceFrame.hasPgn;
targetFrame.transmitter = sourceFrame.transmitter; targetFrame.transmitter = sourceFrame.transmitter;
targetFrame.comment = sourceFrame.comment; targetFrame.comment = sourceFrame.comment;
for (std::size_t signalIndex = 0U; signalIndex < sourceFrame.signals.size(); ++signalIndex) { for (std::size_t signalIndex = 0U; signalIndex < sourceFrame.signals.size(); ++signalIndex) {
const SignalInfo &sourceSignal = sourceFrame.signals[signalIndex]; const SignalInfo &sourceSignal = sourceFrame.signals[signalIndex];
DecodeSignal targetSignal; DecodeSignal targetSignal;
targetSignal.name = sourceSignal.name; targetSignal.name = sourceSignal.name;
targetSignal.startBit = sourceSignal.startBit; targetSignal.startBit = sourceSignal.startBit;
targetSignal.length = sourceSignal.length; targetSignal.length = sourceSignal.length;
targetSignal.byteOrder = sourceSignal.isLittleEndian ? ByteOrder::Intel : ByteOrder::Motorola; targetSignal.byteOrder = sourceSignal.isLittleEndian ? ByteOrder::Intel : ByteOrder::Motorola;
targetSignal.valueType = sourceSignal.isSigned ? ValueType::Signed : ValueType::Unsigned; targetSignal.valueType = sourceSignal.isSigned ? ValueType::Signed : ValueType::Unsigned;
targetSignal.factor = sourceSignal.factor; targetSignal.factor = sourceSignal.factor;
targetSignal.offset = sourceSignal.offset; targetSignal.offset = sourceSignal.offset;
targetSignal.minimum = sourceSignal.minimum; targetSignal.minimum = sourceSignal.minimum;
targetSignal.maximum = sourceSignal.maximum; targetSignal.maximum = sourceSignal.maximum;
targetSignal.unit = sourceSignal.unit; targetSignal.unit = sourceSignal.unit;
targetSignal.receivers = sourceSignal.receivers; targetSignal.receivers = sourceSignal.receivers;
targetSignal.comment = sourceSignal.comment; targetSignal.comment = sourceSignal.comment;
targetFrame.signals.push_back (targetSignal); targetFrame.signals.push_back(targetSignal);
}
result.frames.push_back(targetFrame);
result.frameIndexByKey[FrameKey(targetFrame.canId, targetFrame.isExtended)] =
result.frames.size() - 1U;
} }
result.frames.push_back (targetFrame); return result;
result.frameIndexByKey[FrameKey (targetFrame.canId, targetFrame.isExtended)] =
result.frames.size() - 1U;
}
return result;
} }
+2 -2
View File
@@ -19,13 +19,13 @@
* @brief Converts parsed DBC data into runtime decode database. * @brief Converts parsed DBC data into runtime decode database.
*/ */
class DbcDecodeBuilder { class DbcDecodeBuilder {
public: public:
/** /**
* @brief Build runtime decode database. * @brief Build runtime decode database.
* @param source Parsed DBC database. * @param source Parsed DBC database.
* @return Runtime-ready decode database. * @return Runtime-ready decode database.
*/ */
DecodeDatabase Build (const DbcDatabase &source) const; DecodeDatabase Build(const DbcDatabase &source) const;
}; };
#endif /* DBC_DECODE_BUILDER_H */ #endif /* DBC_DECODE_BUILDER_H */
+102 -102
View File
@@ -11,147 +11,147 @@
#include "dbc_decoder.h" #include "dbc_decoder.h"
const DecodeFrame *DbcDecoder::FindFrame (const DecodeDatabase &database, const DecodeFrame *DbcDecoder::FindFrame(const DecodeDatabase &database,
std::uint32_t canId, std::uint32_t canId,
bool isExtended) const { bool isExtended) const {
const FrameKey key (canId, isExtended); const FrameKey key(canId, isExtended);
const std::unordered_map<FrameKey, std::size_t, FrameKeyHasher>::const_iterator it = const std::unordered_map<FrameKey, std::size_t, FrameKeyHasher>::const_iterator it =
database.frameIndexByKey.find (key); database.frameIndexByKey.find(key);
if (it == database.frameIndexByKey.end()) if (it == database.frameIndexByKey.end())
return nullptr; return nullptr;
const std::size_t index = it->second; const std::size_t index = it->second;
if (index >= database.frames.size()) if (index >= database.frames.size())
return nullptr; return nullptr;
return &database.frames[index]; return &database.frames[index];
} }
DecodedFrameValue DbcDecoder::Decode (const DecodeDatabase &database, DecodedFrameValue DbcDecoder::Decode(const DecodeDatabase &database,
const RawCanFrame &frame) const { const RawCanFrame &frame) const {
DecodedFrameValue result; DecodedFrameValue result;
const DecodeFrame *definition = FindFrame (database, frame.canId, frame.isExtended); const DecodeFrame *definition = FindFrame(database, frame.canId, frame.isExtended);
if (definition == nullptr) if (definition == nullptr)
return result; return result;
result.definition = definition; result.definition = definition;
result.valid = true; result.valid = true;
for (std::size_t signalIndex = 0U; signalIndex < definition->signals.size(); ++signalIndex) { for (std::size_t signalIndex = 0U; signalIndex < definition->signals.size(); ++signalIndex) {
const DecodeSignal &signal = definition->signals[signalIndex]; const DecodeSignal &signal = definition->signals[signalIndex];
DecodedSignalValue decoded; DecodedSignalValue decoded;
std::uint64_t unsignedValue = 0U; std::uint64_t unsignedValue = 0U;
decoded.definition = &signal; decoded.definition = &signal;
if (!ExtractUnsigned (frame.data, signal, unsignedValue)) { if (!ExtractUnsigned(frame.data, signal, unsignedValue)) {
decoded.valid = false; decoded.valid = false;
result.signals.push_back (decoded); result.signals.push_back(decoded);
continue; continue;
}
if (signal.valueType == ValueType::Signed)
decoded.rawValue = SignExtend(unsignedValue, signal.length);
else
decoded.rawValue = unsignedValue;
decoded.physicalValue =
std::visit([&signal](auto rawValue) {
return (static_cast<double>(rawValue) * signal.factor) + signal.offset;
},
decoded.rawValue);
decoded.valid = true;
result.signals.push_back(decoded);
} }
if (signal.valueType == ValueType::Signed) return result;
decoded.rawValue = SignExtend (unsignedValue, signal.length);
else
decoded.rawValue = static_cast<std::int64_t> (unsignedValue);
decoded.physicalValue =
(static_cast<double> (decoded.rawValue) * signal.factor) + signal.offset;
decoded.valid = true;
result.signals.push_back (decoded);
}
return result;
} }
bool DbcDecoder::ExtractUnsigned (const std::vector<std::uint8_t> &data, bool DbcDecoder::ExtractUnsigned(const std::vector<std::uint8_t> &data,
const DecodeSignal &signal, const DecodeSignal &signal,
std::uint64_t &value) { std::uint64_t &value) {
if ((signal.length == 0U) || (signal.length > 64U)) if ((signal.length == 0U) || (signal.length > 64U))
return false; return false;
if (signal.byteOrder == ByteOrder::Intel) if (signal.byteOrder == ByteOrder::Intel)
return ExtractIntel (data, signal.startBit, signal.length, value); return ExtractIntel(data, signal.startBit, signal.length, value);
return ExtractMotorola (data, signal.startBit, signal.length, value); return ExtractMotorola(data, signal.startBit, signal.length, value);
} }
bool DbcDecoder::ExtractIntel (const std::vector<std::uint8_t> &data, bool DbcDecoder::ExtractIntel(const std::vector<std::uint8_t> &data,
std::uint32_t startBit, std::uint32_t startBit,
std::uint32_t length, std::uint32_t length,
std::uint64_t &value) { std::uint64_t &value) {
value = 0U; value = 0U;
for (std::uint32_t bitIndex = 0U; bitIndex < length; ++bitIndex) { for (std::uint32_t bitIndex = 0U; bitIndex < length; ++bitIndex) {
const std::uint32_t absoluteBit = startBit + bitIndex; const std::uint32_t absoluteBit = startBit + bitIndex;
const std::uint32_t byteIndex = absoluteBit / 8U; const std::uint32_t byteIndex = absoluteBit / 8U;
const std::uint32_t bitInByte = absoluteBit % 8U; const std::uint32_t bitInByte = absoluteBit % 8U;
if (byteIndex >= data.size()) if (byteIndex >= data.size())
return false; return false;
const std::uint64_t bitValue = const std::uint64_t bitValue =
(static_cast<std::uint64_t> ((data[byteIndex] >> bitInByte) & 0x01U) << bitIndex); (static_cast<std::uint64_t>((data[byteIndex] >> bitInByte) & 0x01U) << bitIndex);
value |= bitValue; value |= bitValue;
} }
return true; return true;
} }
bool DbcDecoder::ExtractMotorola (const std::vector<std::uint8_t> &data, bool DbcDecoder::ExtractMotorola(const std::vector<std::uint8_t> &data,
std::uint32_t startBit, std::uint32_t startBit,
std::uint32_t length, std::uint32_t length,
std::uint64_t &value) { std::uint64_t &value) {
/* /*
* DBC Motorola bit numbering: * DBC Motorola bit numbering:
* - startBit points to the most significant bit of the signal * - startBit points to the most significant bit of the signal
* - inside a byte, bit numbering goes 7..0 * - inside a byte, bit numbering goes 7..0
* - crossing byte boundary moves to the next byte, bit 7 * - 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; value = 0U;
std::int32_t currentBit = static_cast<std::int32_t> (startBit); std::size_t byteIndex = startBit / 8U;
std::uint32_t bitInByte = startBit % 8U;
for (std::uint32_t bitIndex = 0U; bitIndex < length; ++bitIndex) { for (std::uint32_t bitIndex = 0U; bitIndex < length; ++bitIndex) {
if (currentBit < 0) if (byteIndex >= data.size())
return false; return false;
const std::uint32_t absoluteBit = static_cast<std::uint32_t> (currentBit); value <<= 1U;
const std::uint32_t byteIndex = absoluteBit / 8U; value |= static_cast<std::uint64_t>((data[byteIndex] >> bitInByte) & 0x01U);
const std::uint32_t bitFromMsb = absoluteBit % 8U;
const std::uint32_t bitInByte = 7U - bitFromMsb;
if (byteIndex >= data.size()) if (bitInByte == 0U) {
return false; ++byteIndex;
bitInByte = 7U;
} else
--bitInByte;
}
value <<= 1U; return true;
value |= static_cast<std::uint64_t> ((data[byteIndex] >> bitInByte) & 0x01U);
if ((absoluteBit % 8U) == 7U)
currentBit = static_cast<std::int32_t> ((byteIndex + 1U) * 8U);
else
--currentBit;
}
return true;
} }
std::int64_t DbcDecoder::SignExtend (std::uint64_t value, std::uint32_t bitLength) { std::int64_t DbcDecoder::SignExtend(std::uint64_t value, std::uint32_t bitLength) {
if ((bitLength == 0U) || (bitLength >= 64U)) if ((bitLength == 0U) || (bitLength > 64U))
return static_cast<std::int64_t> (value); return 0;
const std::uint64_t signMask = (static_cast<std::uint64_t> (1U) << (bitLength - 1U)); const std::uint64_t signMask = std::uint64_t(1U) << (bitLength - 1U);
const std::uint64_t valueMask = (static_cast<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;
value &= valueMask; if ((value & signMask) == 0U)
return static_cast<std::int64_t>(value);
if ((value & signMask) == 0U) // The complemented magnitude fits int64_t, including for INT64_MIN.
return static_cast<std::int64_t> (value); return -1 - static_cast<std::int64_t>((~value) & valueMask);
return static_cast<std::int64_t> (value | (~valueMask));
} }
+40 -46
View File
@@ -12,8 +12,9 @@
#ifndef DBC_DECODER_H #ifndef DBC_DECODER_H
#define DBC_DECODER_H #define DBC_DECODER_H
#include <vector>
#include <cstdint> #include <cstdint>
#include <variant>
#include <vector>
#include "decode_database.h" #include "decode_database.h"
@@ -21,54 +22,47 @@
* @brief Raw CAN frame used for runtime or trace decoding. * @brief Raw CAN frame used for runtime or trace decoding.
*/ */
struct RawCanFrame { struct RawCanFrame {
std::uint32_t canId; /**< Normalized CAN ID. */ std::uint32_t canId; /**< Normalized CAN ID. */
bool isExtended; /**< true for extended frame. */ bool isExtended; /**< true for extended frame. */
std::vector<std::uint8_t> data; /**< Payload bytes. */ std::vector<std::uint8_t> data; /**< Payload bytes. */
RawCanFrame() RawCanFrame()
: canId (0U) : canId(0U), isExtended(false), data() {
, isExtended (false) }
, data() {
}
}; };
/** /**
* @brief One decoded signal value. * @brief One decoded signal value.
*/ */
struct DecodedSignalValue { struct DecodedSignalValue {
const DecodeSignal *definition; /**< Signal definition. */ const DecodeSignal *definition; /**< Signal definition. */
std::int64_t rawValue; /**< Extracted raw integer value. */ std::variant<std::int64_t, std::uint64_t> rawValue; /**< Exact signed or unsigned raw value. */
double physicalValue; /**< Converted physical value. */ double physicalValue; /**< Converted physical value. */
bool valid; /**< true if decoding succeeded. */ bool valid; /**< true if decoding succeeded. */
DecodedSignalValue() DecodedSignalValue()
: definition (nullptr) : definition(nullptr), rawValue(std::uint64_t(0U)), physicalValue(0.0), valid(false) {
, rawValue (0) }
, physicalValue (0.0)
, valid (false) {
}
}; };
/** /**
* @brief Fully decoded frame. * @brief Fully decoded frame.
*/ */
struct DecodedFrameValue { struct DecodedFrameValue {
const DecodeFrame *definition; /**< Frame definition. */ const DecodeFrame *definition; /**< Frame definition. */
std::vector<DecodedSignalValue> signals; /**< Decoded signal values. */ std::vector<DecodedSignalValue> signals; /**< Decoded signal values. */
bool valid; /**< true if frame was matched. */ bool valid; /**< true if frame was matched. */
DecodedFrameValue() DecodedFrameValue()
: definition (nullptr) : definition(nullptr), signals(), valid(false) {
, signals() }
, valid (false) {
}
}; };
/** /**
* @brief Runtime CAN decoder using prebuilt decode database. * @brief Runtime CAN decoder using prebuilt decode database.
*/ */
class DbcDecoder { class DbcDecoder {
public: public:
/** /**
* @brief Find frame definition by CAN ID. * @brief Find frame definition by CAN ID.
* @param database Runtime decode database. * @param database Runtime decode database.
@@ -76,9 +70,9 @@ class DbcDecoder {
* @param isExtended true for extended frame. * @param isExtended true for extended frame.
* @return Pointer to frame definition or nullptr. * @return Pointer to frame definition or nullptr.
*/ */
const DecodeFrame *FindFrame (const DecodeDatabase &database, const DecodeFrame *FindFrame(const DecodeDatabase &database,
std::uint32_t canId, std::uint32_t canId,
bool isExtended) const; bool isExtended) const;
/** /**
* @brief Decode one raw CAN frame. * @brief Decode one raw CAN frame.
@@ -86,25 +80,25 @@ class DbcDecoder {
* @param frame Raw CAN frame. * @param frame Raw CAN frame.
* @return Decoded frame value. * @return Decoded frame value.
*/ */
DecodedFrameValue Decode (const DecodeDatabase &database, DecodedFrameValue Decode(const DecodeDatabase &database,
const RawCanFrame &frame) const; const RawCanFrame &frame) const;
private: private:
static bool ExtractUnsigned (const std::vector<std::uint8_t> &data, static bool ExtractUnsigned(const std::vector<std::uint8_t> &data,
const DecodeSignal &signal, const DecodeSignal &signal,
std::uint64_t &value); std::uint64_t &value);
static bool ExtractIntel (const std::vector<std::uint8_t> &data, static bool ExtractIntel(const std::vector<std::uint8_t> &data,
std::uint32_t startBit, std::uint32_t startBit,
std::uint32_t length, std::uint32_t length,
std::uint64_t &value); std::uint64_t &value);
static bool ExtractMotorola (const std::vector<std::uint8_t> &data, static bool ExtractMotorola(const std::vector<std::uint8_t> &data,
std::uint32_t startBit, std::uint32_t startBit,
std::uint32_t length, std::uint32_t length,
std::uint64_t &value); std::uint64_t &value);
static std::int64_t SignExtend (std::uint64_t value, std::uint32_t bitLength); static std::int64_t SignExtend(std::uint64_t value, std::uint32_t bitLength);
}; };
#endif /* DBC_DECODER_H */ #endif /* DBC_DECODER_H */
+46 -332
View File
@@ -10,347 +10,61 @@
*/ */
#include "dbc_parser.h" #include "dbc_parser.h"
#include "dbc_statement_handler.h"
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <cctype> #include <cctype>
#include <fstream>
#include <stdexcept>
namespace { namespace {
/** /**
* @brief Remove leading and trailing spaces. * @brief Remove leading and trailing spaces.
* @param text Input text. * @param text Input text.
* @return Trimmed text. * @return Trimmed text.
*/ */
std::string TrimText (const std::string &text) { std::string TrimText(const std::string &text) {
std::string::size_type begin = 0U; std::string::size_type begin = 0U;
while ((begin < text.size()) && while ((begin < text.size()) &&
std::isspace (static_cast<unsigned char> (text[begin]))) std::isspace(static_cast<unsigned char>(text[begin])))
++begin; ++begin;
std::string::size_type end = text.size(); std::string::size_type end = text.size();
while ((end > begin) && while ((end > begin) &&
std::isspace (static_cast<unsigned char> (text[end - 1U]))) std::isspace(static_cast<unsigned char>(text[end - 1U])))
--end; --end;
return text.substr (begin, end - begin); return text.substr(begin, end - begin);
}
}
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;
FrameInfo *currentFrame = nullptr;
std::string line;
while (std::getline (input, line)) {
line = Trim (line);
if (line.empty())
continue;
if (IsFrameLine (line)) {
FrameInfo frame = ParseFrameLine (line);
database.frames.push_back (frame);
currentFrame = &database.frames.back();
} else if (IsSignalLine (line)) {
if (currentFrame == nullptr)
throw std::runtime_error ("Signal found before any frame definition.");
SignalInfo signal = ParseSignalLine (line);
currentFrame->signals.push_back (signal);
} else if (IsCommentLine (line))
ParseCommentLine (line, database);
}
return database;
}
bool DbcParser::IsFrameLine (const std::string &line) {
return (line.size() >= 4U) && (line.compare (0U, 4U, "BO_ ") == 0);
}
bool DbcParser::IsSignalLine (const std::string &line) {
return (line.size() >= 4U) && (line.compare (0U, 4U, "SG_ ") == 0);
}
bool DbcParser::IsCommentLine (const std::string &line) {
return (line.size() >= 4U) && (line.compare (0U, 4U, "CM_ ") == 0);
}
std::string DbcParser::Trim (const std::string &text) {
return TrimText (text);
}
std::vector<std::string> DbcParser::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 DbcParser::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 DbcParser::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;
}
FrameInfo DbcParser::ParseFrameLine (const std::string &line) {
/*
* Example:
* BO_ 256 EngineData: 8 EEC1
*/
std::istringstream stream (line);
std::string token;
FrameInfo frame;
std::uint32_t rawCanId = 0U;
stream >> token;
if (token != "BO_")
throw std::runtime_error ("Invalid frame line: " + line);
stream >> rawCanId;
NormalizeCanId (rawCanId, frame.canId, frame.isExtended);
stream >> token;
if (token.empty())
throw std::runtime_error ("Missing frame name: " + line);
if (token[token.size() - 1U] == ':')
token.erase (token.size() - 1U, 1U);
frame.name = token;
{
unsigned int dlcValue = 0U;
stream >> dlcValue;
frame.dlc = static_cast<std::uint8_t> (dlcValue);
}
stream >> frame.transmitter;
frame.pgn = TryExtractPgn (frame.canId, frame.isExtended, frame.hasPgn);
return frame;
}
SignalInfo DbcParser::ParseSignalLine (const std::string &line) {
/*
* Example:
* SG_ EngineSpeed : 0|16@1+ (0.125,0) [0|8000] "rpm" ECU1,ECU2
*/
SignalInfo signal;
std::string work = TrimText (line);
if (work.compare (0U, 4U, "SG_ ") != 0)
throw std::runtime_error ("Invalid signal line: " + line);
work.erase (0U, 4U);
const std::string::size_type colonPos = work.find (':');
if (colonPos == std::string::npos)
throw std::runtime_error ("Signal line missing ':' : " + line);
signal.name = TrimText (work.substr (0U, colonPos));
std::string rest = TrimText (work.substr (colonPos + 1U));
const std::string::size_type pipePos = rest.find ('|');
const std::string::size_type atPos = rest.find ('@');
const std::string::size_type signPos = rest.find_first_of ("+-", atPos);
const std::string::size_type factorBegin = rest.find ('(');
const std::string::size_type factorComma = rest.find (',', factorBegin);
const std::string::size_type factorEnd = rest.find (')', factorComma);
const std::string::size_type rangeBegin = rest.find ('[');
const std::string::size_type rangeSep = rest.find ('|', rangeBegin);
const std::string::size_type rangeEnd = rest.find (']', rangeSep);
const std::string::size_type unitBegin = rest.find ('"', rangeEnd);
const std::string::size_type unitEnd = rest.find ('"', unitBegin + 1U);
if ((pipePos == std::string::npos) ||
(atPos == std::string::npos) ||
(signPos == std::string::npos) ||
(factorBegin == std::string::npos) ||
(factorComma == std::string::npos) ||
(factorEnd == std::string::npos) ||
(rangeBegin == std::string::npos) ||
(rangeSep == std::string::npos) ||
(rangeEnd == std::string::npos) ||
(unitBegin == std::string::npos) ||
(unitEnd == std::string::npos))
throw std::runtime_error ("Unsupported signal syntax: " + line);
signal.startBit = static_cast<std::uint32_t> (
std::stoul (TrimText (rest.substr (0U, pipePos)))
);
signal.length = static_cast<std::uint32_t> (
std::stoul (TrimText (rest.substr (pipePos + 1U, atPos - pipePos - 1U)))
);
{
if ((atPos + 1U) >= rest.size())
throw std::runtime_error ("Invalid endianness in signal: " + line);
const char endianChar = rest[atPos + 1U];
signal.isLittleEndian = (endianChar == '1');
}
{
const char signChar = rest[signPos];
signal.isSigned = (signChar == '-');
}
signal.factor = std::stod (
TrimText (rest.substr (factorBegin + 1U, factorComma - factorBegin - 1U))
);
signal.offset = std::stod (
TrimText (rest.substr (factorComma + 1U, factorEnd - factorComma - 1U))
);
signal.minimum = std::stod (
TrimText (rest.substr (rangeBegin + 1U, rangeSep - rangeBegin - 1U))
);
signal.maximum = std::stod (
TrimText (rest.substr (rangeSep + 1U, rangeEnd - rangeSep - 1U))
);
signal.unit = rest.substr (unitBegin + 1U, unitEnd - unitBegin - 1U);
{
const std::string receiversText = TrimText (rest.substr (unitEnd + 1U));
signal.receivers = SplitReceivers (receiversText);
}
return signal;
}
void DbcParser::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
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;
} }
FrameInfo *DbcParser::FindFrameById (DbcDatabase &database, std::string DbcParser::Trim(const std::string &text) {
std::uint32_t canId, return TrimText(text);
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 *DbcParser::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;
} }
+5 -26
View File
@@ -13,8 +13,6 @@
#define DBC_PARSER_H #define DBC_PARSER_H
#include <string> #include <string>
#include <vector>
#include <cstdint>
#include "dbc_database.h" #include "dbc_database.h"
@@ -33,37 +31,18 @@
* - value tables * - value tables
*/ */
class DbcParser { class DbcParser {
public: public:
/** /**
* @brief Parse DBC file. * @brief Parse DBC file.
* @param filePath Path to input file. * @param filePath Path to input file.
* @return Parsed database. * @return Parsed database.
* @throws std::runtime_error on file or parse errors. * @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; DbcDatabase ParseFile(const std::string &filePath) const;
private: private:
static bool IsFrameLine (const std::string &line); static std::string Trim(const std::string &text);
static bool IsSignalLine (const std::string &line);
static bool IsCommentLine (const std::string &line);
static std::string Trim (const std::string &text);
static std::vector<std::string> SplitReceivers (const std::string &text);
static std::uint32_t TryExtractPgn (std::uint32_t canId, bool isExtended, bool &hasPgn);
static void NormalizeCanId (std::uint32_t rawCanId,
std::uint32_t &normalizedCanId,
bool &isExtended);
static FrameInfo ParseFrameLine (const std::string &line);
static SignalInfo ParseSignalLine (const std::string &line);
static void ParseCommentLine (const std::string &line, DbcDatabase &database);
static FrameInfo *FindFrameById (DbcDatabase &database,
std::uint32_t canId,
bool isExtended);
static SignalInfo *FindSignalByName (FrameInfo &frame, const std::string &signalName);
}; };
#endif /* DBC_PARSER_H */ #endif /* DBC_PARSER_H */
+323
View File
@@ -0,0 +1,323 @@
// 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;
}
/**
* @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() == "-";
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<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 */
+13 -13
View File
@@ -11,21 +11,21 @@
#include "dbc_tree_builder.h" #include "dbc_tree_builder.h"
std::unique_ptr<TreeNode> DbcTreeBuilder::Build (const DbcDatabase &database) const { std::unique_ptr<TreeNode> DbcTreeBuilder::Build(const DbcDatabase &database) const {
std::unique_ptr<TreeNode> root (new TreeNode()); std::unique_ptr<TreeNode> root(new TreeNode());
for (std::size_t frameIndex = 0U; frameIndex < database.frames.size(); ++frameIndex) { for (std::size_t frameIndex = 0U; frameIndex < database.frames.size(); ++frameIndex) {
const FrameInfo &frame = database.frames[frameIndex]; const FrameInfo &frame = database.frames[frameIndex];
std::unique_ptr<TreeNode> frameNode (new TreeNode (frame)); std::unique_ptr<TreeNode> frameNode(new TreeNode(frame));
for (std::size_t signalIndex = 0U; signalIndex < frame.signals.size(); ++signalIndex) { for (std::size_t signalIndex = 0U; signalIndex < frame.signals.size(); ++signalIndex) {
const SignalInfo &signal = frame.signals[signalIndex]; const SignalInfo &signal = frame.signals[signalIndex];
std::unique_ptr<TreeNode> signalNode (new TreeNode (signal)); std::unique_ptr<TreeNode> signalNode(new TreeNode(signal));
frameNode->AddChild (std::move (signalNode)); frameNode->AddChild(std::move(signalNode));
}
root->AddChild(std::move(frameNode));
} }
root->AddChild (std::move (frameNode)); return root;
}
return root;
} }
+2 -2
View File
@@ -21,13 +21,13 @@
* @brief Builds a simple tree from parsed DBC database. * @brief Builds a simple tree from parsed DBC database.
*/ */
class DbcTreeBuilder { class DbcTreeBuilder {
public: public:
/** /**
* @brief Build tree representation of parsed DBC data. * @brief Build tree representation of parsed DBC data.
* @param database Parsed database. * @param database Parsed database.
* @return Root node of the tree. * @return Root node of the tree.
*/ */
std::unique_ptr<TreeNode> Build (const DbcDatabase &database) const; std::unique_ptr<TreeNode> Build(const DbcDatabase &database) const;
}; };
#endif /* DBC_TREE_BUILDER_H */ #endif /* DBC_TREE_BUILDER_H */
+52 -73
View File
@@ -12,126 +12,105 @@
#ifndef DECODE_DATABASE_H #ifndef DECODE_DATABASE_H
#define DECODE_DATABASE_H #define DECODE_DATABASE_H
#include <string>
#include <vector>
#include <unordered_map>
#include <cstdint> #include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>
/** /**
* @brief Signal byte order used for runtime decoding. * @brief Signal byte order used for runtime decoding.
*/ */
enum class ByteOrder { enum class ByteOrder {
Intel, Intel,
Motorola Motorola
}; };
/** /**
* @brief Signal numeric type. * @brief Signal numeric type.
*/ */
enum class ValueType { enum class ValueType {
Unsigned, Unsigned,
Signed Signed
}; };
/** /**
* @brief Runtime-ready signal definition. * @brief Runtime-ready signal definition.
*/ */
struct DecodeSignal { struct DecodeSignal {
std::string name; /**< Signal name. */ std::string name; /**< Signal name. */
std::uint32_t startBit; /**< DBC start bit. */ std::uint32_t startBit; /**< DBC start bit. */
std::uint32_t length; /**< Signal length in bits. */ std::uint32_t length; /**< Signal length in bits. */
ByteOrder byteOrder; /**< Intel or Motorola. */ ByteOrder byteOrder; /**< Intel or Motorola. */
ValueType valueType; /**< Signed or unsigned. */ ValueType valueType; /**< Signed or unsigned. */
double factor; /**< Scaling factor. */ double factor; /**< Scaling factor. */
double offset; /**< Physical offset. */ double offset; /**< Physical offset. */
double minimum; /**< Minimum physical value. */ double minimum; /**< Minimum physical value. */
double maximum; /**< Maximum physical value. */ double maximum; /**< Maximum physical value. */
std::string unit; /**< Physical unit. */ std::string unit; /**< Physical unit. */
std::vector<std::string> receivers; /**< Receivers. */ std::vector<std::string> receivers; /**< Receivers. */
std::string comment; /**< Comment. */ std::string comment; /**< Comment. */
DecodeSignal() DecodeSignal()
: name() : 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() {
, 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. * @brief Runtime-ready frame definition.
*/ */
struct DecodeFrame { struct DecodeFrame {
std::string name; /**< Frame name. */ std::string name; /**< Frame name. */
std::uint32_t canId; /**< Normalized CAN ID. */ std::uint32_t canId; /**< Normalized CAN ID. */
bool isExtended; /**< true for extended frame. */ bool isExtended; /**< true for extended frame. */
std::uint8_t dlc; /**< Payload length. */ std::uint8_t dlc; /**< Payload length. */
std::uint32_t pgn; /**< PGN if available. */ std::uint32_t pgn; /**< PGN if available. */
bool hasPgn; /**< true if PGN is valid. */ bool hasPgn; /**< true if PGN is valid. */
std::string transmitter; /**< Transmitter ECU. */ std::string transmitter; /**< Transmitter ECU. */
std::string comment; /**< Frame comment. */ std::string comment; /**< Frame comment. */
std::vector<DecodeSignal> signals; /**< Signal definitions. */ std::vector<DecodeSignal> signals; /**< Signal definitions. */
DecodeFrame() DecodeFrame()
: name() : name(), canId(0U), isExtended(false), dlc(0U), pgn(0U), hasPgn(false), transmitter(), comment(), signals() {
, canId (0U) }
, isExtended (false)
, dlc (0U)
, pgn (0U)
, hasPgn (false)
, transmitter()
, comment()
, signals() {
}
}; };
/** /**
* @brief Key for fast frame lookup. * @brief Key for fast frame lookup.
*/ */
struct FrameKey { struct FrameKey {
std::uint32_t canId; std::uint32_t canId;
bool isExtended; bool isExtended;
FrameKey() FrameKey()
: canId (0U) : canId(0U), isExtended(false) {
, isExtended (false) { }
}
FrameKey (std::uint32_t id, bool extended) FrameKey(std::uint32_t id, bool extended)
: canId (id) : canId(id), isExtended(extended) {
, isExtended (extended) { }
}
bool operator== (const FrameKey &other) const { bool operator==(const FrameKey &other) const {
return (canId == other.canId) && (isExtended == other.isExtended); return (canId == other.canId) && (isExtended == other.isExtended);
} }
}; };
/** /**
* @brief Hasher for frame key. * @brief Hasher for frame key.
*/ */
struct FrameKeyHasher { struct FrameKeyHasher {
std::size_t operator() (const FrameKey &key) const { std::size_t operator()(const FrameKey &key) const {
const std::size_t a = static_cast<std::size_t> (key.canId); const std::size_t a = static_cast<std::size_t>(key.canId);
const std::size_t b = key.isExtended ? 1U : 0U; const std::size_t b = key.isExtended ? 1U : 0U;
return (a * 1315423911U) ^ b; return (a * 1315423911U) ^ b;
} }
}; };
/** /**
* @brief Runtime decode database with fast lookup by CAN ID. * @brief Runtime decode database with fast lookup by CAN ID.
*/ */
struct DecodeDatabase { struct DecodeDatabase {
std::vector<DecodeFrame> frames; std::vector<DecodeFrame> frames;
std::unordered_map<FrameKey, std::size_t, FrameKeyHasher> frameIndexByKey; std::unordered_map<FrameKey, std::size_t, FrameKeyHasher> frameIndexByKey;
}; };
#endif /* DECODE_DATABASE_H */ #endif /* DECODE_DATABASE_H */
+13 -21
View File
@@ -12,9 +12,9 @@
#ifndef FRAME_INFO_H #ifndef FRAME_INFO_H
#define FRAME_INFO_H #define FRAME_INFO_H
#include <cstdint>
#include <string> #include <string>
#include <vector> #include <vector>
#include <cstdint>
#include "signal_info.h" #include "signal_info.h"
@@ -22,27 +22,19 @@
* @brief Describes one CAN frame from a DBC file. * @brief Describes one CAN frame from a DBC file.
*/ */
struct FrameInfo { struct FrameInfo {
std::string name; /**< Frame name. */ std::string name; /**< Frame name. */
std::uint32_t canId; /**< Normalized CAN identifier. */ std::uint32_t canId; /**< Normalized CAN identifier. */
bool isExtended; /**< true for extended frame. */ bool isExtended; /**< true for extended frame. */
std::uint32_t pgn; /**< J1939 PGN if applicable. */ std::uint32_t pgn; /**< J1939 PGN if applicable. */
bool hasPgn; /**< true if PGN was derived. */ bool hasPgn; /**< true if PGN was derived. */
std::uint8_t dlc; /**< Frame payload length. */ std::uint8_t dlc; /**< Frame payload length. */
std::string transmitter; /**< Transmitter ECU name. */ std::string transmitter; /**< Transmitter ECU name. */
std::string comment; /**< Optional frame comment. */ std::string comment; /**< Optional frame comment. */
std::vector<SignalInfo> signals; /**< Signals contained in the frame. */ std::vector<SignalInfo> signals; /**< Signals contained in the frame. */
FrameInfo() FrameInfo()
: name() : name(), canId(0U), isExtended(false), pgn(0U), hasPgn(false), dlc(0U), transmitter(), comment(), signals() {
, canId (0U) }
, isExtended (false)
, pgn (0U)
, hasPgn (false)
, dlc (0U)
, transmitter()
, comment()
, signals() {
}
}; };
#endif /* FRAME_INFO_H */ #endif /* FRAME_INFO_H */
+40
View File
@@ -0,0 +1,40 @@
# Validate `SG_` signal definitions and report malformed or unsupported syntax
## Description
Following strict validation of `BO_` frame definitions, add equivalent validation for `SG_` signal definitions. Currently, some malformed statements are accepted silently, and unsupported syntax can produce incorrect decoding.
Examples of current behavior:
- `@2+` is interpreted as Motorola instead of being rejected.
- Numeric fields containing suffixes, such as `0junk` or `1junk`, are partially parsed and accepted.
- `SG_ Value m1 : ...` is treated as an ordinary signal named `Value m1`, without multiplexing conditions.
- A statement containing a tab after `SG_` is silently skipped.
## Scope
- Validate statement structure and require a nonempty signal name without whitespace.
- Accept spaces and tabs between syntax elements.
- Parse numeric fields completely, rejecting overflow and trailing characters; preserve support for fractional values and scientific notation in physical parameters.
- Accept only byte order `@0` or `@1` and signedness `+` or `-`.
- Require signal lengths within `1..64` bits.
- Validate that the signal fits within the frame's declared payload length, using the appropriate Intel or Motorola bit traversal.
- Reject NaN and infinite values for factor, offset, minimum, and maximum; require `minimum <= maximum`.
- Explicitly reject multiplexing markers `M`, `mN`, and their combinations until multiplexing is implemented.
- Preserve the error format `path:line: description`.
## Acceptance criteria
- [ ] Existing valid signal definitions continue to parse successfully.
- [ ] Malformed and unsupported definitions stop parsing with a clear error.
- [ ] Regression tests cover the cases above, numeric boundaries, and Intel/Motorola payload boundaries.
- [ ] Existing parser and decoder tests pass.
- [ ] README documents the new validation rules and limitations.
## Out of scope
Duplicate detection, multiplexing support, and `VAL_` support remain separate tasks.
## Suggested branch
`codex/validate-sg-definitions`
+93 -92
View File
@@ -9,128 +9,129 @@
* See LICENSE file in the project root for full license text. * See LICENSE file in the project root for full license text.
*/ */
#include <iostream> #include <cstddef>
#include <iomanip> #include <iomanip>
#include <iostream>
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <cstddef>
#include "dbc_parser.h"
#include "dbc_tree_builder.h"
#include "dbc_decode_builder.h" #include "dbc_decode_builder.h"
#include "dbc_decoder.h" #include "dbc_decoder.h"
#include "dbc_parser.h"
#include "dbc_tree_builder.h"
static void PrintTree (const TreeNode *node, int indent) { static void PrintTree(const TreeNode *node, int indent) {
if (node == nullptr) if (node == nullptr)
return; return;
for (int i = 0; i < indent; ++i) for (int i = 0; i < indent; ++i)
std::cout << " "; std::cout << " ";
switch (node->GetType()) { switch (node->GetType()) {
case NodeType::Root: case NodeType::Root:
std::cout << "[root] " << node->GetName() << "\n"; std::cout << "[root] " << node->GetName() << "\n";
break; break;
case NodeType::Frame: { case NodeType::Frame: {
const FrameInfo *frame = node->GetFrame(); const FrameInfo *frame = node->GetFrame();
std::cout << "[frame] " << node->GetName(); std::cout << "[frame] " << node->GetName();
if (frame != nullptr) { if (frame != nullptr) {
std::cout << " id=0x" << std::hex << frame->canId << std::dec std::cout << " id=0x" << std::hex << frame->canId << std::dec
<< " ext=" << (frame->isExtended ? "yes" : "no") << " ext=" << (frame->isExtended ? "yes" : "no")
<< " dlc=" << static_cast<unsigned int> (frame->dlc); << " dlc=" << static_cast<unsigned int>(frame->dlc);
if (frame->hasPgn) if (frame->hasPgn)
std::cout << " pgn=" << frame->pgn; std::cout << " pgn=" << frame->pgn;
} }
std::cout << "\n"; std::cout << "\n";
break; break;
}
case NodeType::Signal: {
const SignalInfo *signal = node->GetSignal();
std::cout << "[signal] " << node->GetName();
if (signal != nullptr) {
std::cout << " start=" << signal->startBit
<< " len=" << signal->length
<< " unit=" << signal->unit;
}
std::cout << "\n";
break;
}
default:
std::cout << "[unknown]\n";
break;
} }
case NodeType::Signal: { for (std::size_t i = 0U; i < node->GetChildCount(); ++i)
const SignalInfo *signal = node->GetSignal(); PrintTree(node->GetChild(i), indent + 1);
std::cout << "[signal] " << node->GetName(); }
if (signal != nullptr) { static void PrintDecodedFrame(const DecodedFrameValue &decoded) {
std::cout << " start=" << signal->startBit if (!decoded.valid || (decoded.definition == nullptr)) {
<< " len=" << signal->length std::cout << "No frame definition found.\n";
<< " unit=" << signal->unit; return;
}
std::cout << "\n";
break;
} }
default: std::cout << "Decoded frame: " << decoded.definition->name << "\n";
std::cout << "[unknown]\n";
break;
}
for (std::size_t i = 0U; i < node->GetChildCount(); ++i) for (std::size_t index = 0U; index < decoded.signals.size(); ++index) {
PrintTree (node->GetChild (i), indent + 1); const DecodedSignalValue &signal = decoded.signals[index];
if ((signal.definition == nullptr) || !signal.valid)
continue;
std::cout << " " << signal.definition->name
<< " raw=";
std::visit([](auto rawValue) { std::cout << rawValue; }, signal.rawValue);
std::cout << " physical=" << signal.physicalValue
<< " " << signal.definition->unit
<< "\n";
}
} }
static void PrintDecodedFrame (const DecodedFrameValue &decoded) { int main(int argc, char *argv[]) {
if (!decoded.valid || (decoded.definition == nullptr)) { if (argc < 2) {
std::cout << "No frame definition found.\n"; std::cerr << "Usage: dbc_demo <file.dbc>\n";
return; return 1;
} }
std::cout << "Decoded frame: " << decoded.definition->name << "\n"; try {
DbcParser parser;
DbcDatabase database = parser.ParseFile(argv[1]);
for (std::size_t index = 0U; index < decoded.signals.size(); ++index) { DbcTreeBuilder treeBuilder;
const DecodedSignalValue &signal = decoded.signals[index]; std::unique_ptr<TreeNode> root = treeBuilder.Build(database);
if ((signal.definition == nullptr) || !signal.valid) std::cout << "=== Parsed tree ===\n";
continue; PrintTree(root.get(), 0);
std::cout << " " << signal.definition->name DbcDecodeBuilder decodeBuilder;
<< " raw=" << signal.rawValue DecodeDatabase decodeDatabase = decodeBuilder.Build(database);
<< " physical=" << signal.physicalValue
<< " " << signal.definition->unit
<< "\n";
}
}
int main (int argc, char *argv[]) { /*
if (argc < 2) {
std::cerr << "Usage: dbc_demo <file.dbc>\n";
return 1;
}
try {
DbcParser parser;
DbcDatabase database = parser.ParseFile (argv[1]);
DbcTreeBuilder treeBuilder;
std::unique_ptr<TreeNode> root = treeBuilder.Build (database);
std::cout << "=== Parsed tree ===\n";
PrintTree (root.get(), 0);
DbcDecodeBuilder decodeBuilder;
DecodeDatabase decodeDatabase = decodeBuilder.Build (database);
/*
* Example raw frame. * Example raw frame.
* Replace with live CAN frame or trace record later. * Replace with live CAN frame or trace record later.
*/ */
RawCanFrame rawFrame; RawCanFrame rawFrame;
rawFrame.canId = decodeDatabase.frames.empty() ? 0U : decodeDatabase.frames[0].canId; rawFrame.canId = decodeDatabase.frames.empty() ? 0U : decodeDatabase.frames[0].canId;
rawFrame.isExtended = decodeDatabase.frames.empty() ? false : decodeDatabase.frames[0].isExtended; rawFrame.isExtended = decodeDatabase.frames.empty() ? false : decodeDatabase.frames[0].isExtended;
rawFrame.data.resize (8U, 0U); rawFrame.data.resize(8U, 0U);
DbcDecoder decoder; DbcDecoder decoder;
DecodedFrameValue decoded = decoder.Decode (decodeDatabase, rawFrame); DecodedFrameValue decoded = decoder.Decode(decodeDatabase, rawFrame);
std::cout << "\n=== Decoded frame ===\n"; std::cout << "\n=== Decoded frame ===\n";
PrintDecodedFrame (decoded); PrintDecodedFrame(decoded);
} catch (const std::exception &ex) { } catch (const std::exception &ex) {
std::cerr << "Error: " << ex.what() << "\n"; std::cerr << "Error: " << ex.what() << "\n";
return 2; return 2;
} }
return 0; return 0;
} }
+16 -27
View File
@@ -12,41 +12,30 @@
#ifndef SIGNAL_INFO_H #ifndef SIGNAL_INFO_H
#define SIGNAL_INFO_H #define SIGNAL_INFO_H
#include <cstdint>
#include <string> #include <string>
#include <vector> #include <vector>
#include <cstdint>
/** /**
* @brief Describes one signal inside a DBC frame. * @brief Describes one signal inside a DBC frame.
*/ */
struct SignalInfo { struct SignalInfo {
std::string name; /**< Signal name. */ std::string name; /**< Signal name. */
std::uint32_t startBit; /**< Start bit in DBC notation. */ std::uint32_t startBit; /**< Start bit in DBC notation. */
std::uint32_t length; /**< Signal length in bits. */ std::uint32_t length; /**< Signal length in bits. */
bool isLittleEndian; /**< true for Intel, false for Motorola. */ bool isLittleEndian; /**< true for Intel, false for Motorola. */
bool isSigned; /**< true if signal is signed. */ bool isSigned; /**< true if signal is signed. */
double factor; /**< Scaling factor. */ double factor; /**< Scaling factor. */
double offset; /**< Physical offset. */ double offset; /**< Physical offset. */
double minimum; /**< Minimum physical value. */ double minimum; /**< Minimum physical value. */
double maximum; /**< Maximum physical value. */ double maximum; /**< Maximum physical value. */
std::string unit; /**< Physical unit. */ std::string unit; /**< Physical unit. */
std::vector<std::string> receivers; /**< Signal receivers. */ std::vector<std::string> receivers; /**< Signal receivers. */
std::string comment; /**< Optional signal comment. */ std::string comment; /**< Optional signal comment. */
SignalInfo() SignalInfo()
: name() : name(), startBit(0U), length(0U), isLittleEndian(true), isSigned(false), factor(1.0), offset(0.0), minimum(0.0), maximum(0.0), unit(), receivers(), comment() {
, 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 */ #endif /* SIGNAL_INFO_H */
+53
View File
@@ -0,0 +1,53 @@
# Decoder regression tests
Run from the repository root with a C++17 compiler:
```sh
g++ -std=c++17 -Wall -Wextra -Wpedantic -I. \
tests/dbc_decoder_test.cpp dbc_decoder.cpp -o /tmp/dbc_decoder_test
/tmp/dbc_decoder_test
```
Alternatively, build the standalone qmake project out of source:
```sh
mkdir -p /tmp/dbc_decoder_test_build
cd /tmp/dbc_decoder_test_build
qmake /absolute/path/to/dbc/tests/dbc_decoder_test.pro
make
./dbc_decoder_test
```
The executable returns a nonzero exit code if any case fails. Checks remain
active in release builds and do not depend on `assert` or an external test framework.
Coverage includes Motorola bit numbering within and across bytes, unaligned
signals, signed values, scaling, 64-bit extraction, signed/unsigned boundary values
(including `INT64_MIN`, `INT64_MAX`, and `UINT64_MAX`), numeric output, invalid lengths, insufficient
payloads, and Intel regression cases. Frame validity means the definition was
found; insufficient payloads are checked through signal validity.
## Parser regression tests
Run from the repository root:
```sh
g++ -std=c++17 -Wall -Wextra -Wpedantic -Werror -I. \
tests/dbc_parser_test.cpp dbc_parser.cpp dbc_statement_handler.cpp -o /tmp/dbc_parser_test
/tmp/dbc_parser_test
```
For qmake, use `tests/dbc_parser_test.pro` in a separate build directory,
following the decoder instructions above, then run `./dbc_parser_test`.
Parser tests cover standard and extended CAN IDs, whitespace, payload storage
boundaries, missing fields, malformed and overflowing numbers, reserved ID bits,
error paths and line numbers, and integration with signals and comments.
Signal tests cover complete syntax and numeric validation, scientific notation,
finite physical parameters and ordered ranges, spaces and tabs, receiver lists,
unsupported multiplexing, and Intel/Motorola declared-payload boundaries.
Temporary DBC fixtures are created in the system temporary directory and removed
automatically.
Dispatch tests also verify exact keyword matching, handler construction and reuse,
unknown statements, and independent parsing sessions, including recovery after errors.
+139
View File
@@ -0,0 +1,139 @@
#include "dbc_decoder.h"
#include <iostream>
#include <limits>
#include <sstream>
struct DecodeCase {
const char *name;
ByteOrder byteOrder;
std::uint32_t startBit;
std::uint32_t length;
ValueType valueType;
std::vector<std::uint8_t> data;
bool valid;
std::int64_t rawValue;
double factor;
double offset;
double physicalValue;
};
int main() {
const DecodeCase cases[] = {
{"Motorola reproduction", ByteOrder::Motorola, 7U, 16U, ValueType::Unsigned, {0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.0},
{"Motorola within byte", ByteOrder::Motorola, 5U, 4U, ValueType::Unsigned, {0xAC}, true, 11, 1.0, 0.0, 11.0},
{"Motorola unaligned crossing", ByteOrder::Motorola, 3U, 12U, ValueType::Unsigned, {0xAB, 0xCD}, true, 0xBCD, 1.0, 0.0, 3021.0},
{"Motorola starts at bit zero", ByteOrder::Motorola, 0U, 2U, ValueType::Unsigned, {0x01, 0x80}, true, 3, 1.0, 0.0, 3.0},
{"Motorola second byte", ByteOrder::Motorola, 15U, 16U, ValueType::Unsigned, {0xFF, 0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.0},
{"Motorola signed scaled", ByteOrder::Motorola, 7U, 16U, ValueType::Signed, {0xFF, 0xFE}, true, -2, 0.5, 10.0, 9.0},
{"Motorola 64 bits", ByteOrder::Motorola, 7U, 64U, ValueType::Unsigned, {0, 0, 0, 0, 0, 0, 0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.0},
{"Motorola 64 bits high bit", ByteOrder::Motorola, 7U, 64U, ValueType::Unsigned, {0x40, 0, 0, 0, 0, 0, 0, 0}, true, 0x4000000000000000LL, 1.0, 0.0, 4611686018427387904.0},
{"Motorola short payload", ByteOrder::Motorola, 7U, 16U, ValueType::Unsigned, {0x12}, false, 0, 1.0, 0.0, 0.0},
{"Motorola empty payload", ByteOrder::Motorola, 7U, 8U, ValueType::Unsigned, {}, false, 0, 1.0, 0.0, 0.0},
{"Motorola large start", ByteOrder::Motorola, std::numeric_limits<std::uint32_t>::max(), 8U, ValueType::Unsigned, {0xFF}, false, 0, 1.0, 0.0, 0.0},
{"Motorola zero length", ByteOrder::Motorola, 7U, 0U, ValueType::Unsigned, {0xFF}, false, 0, 1.0, 0.0, 0.0},
{"Motorola excessive length", ByteOrder::Motorola, 7U, 65U, ValueType::Unsigned,
std::vector<std::uint8_t>(9U, 0xFF), false, 0, 1.0, 0.0, 0.0},
{"Intel regression", ByteOrder::Intel, 0U, 16U, ValueType::Unsigned, {0x12, 0x34}, true, 0x3412, 1.0, 0.0, 13330.0},
{"Intel signed regression", ByteOrder::Intel, 0U, 16U, ValueType::Signed, {0xFE, 0xFF}, true, -2, 0.5, 10.0, 9.0},
{"Intel short payload", ByteOrder::Intel, 0U, 16U, ValueType::Unsigned, {0x12}, false, 0, 1.0, 0.0, 0.0}};
int failures = 0;
for (const DecodeCase &test: cases) {
DecodeSignal signal;
signal.name = test.name;
signal.byteOrder = test.byteOrder;
signal.startBit = test.startBit;
signal.length = test.length;
signal.valueType = test.valueType;
signal.factor = test.factor;
signal.offset = test.offset;
DecodeFrame definition;
definition.canId = 0x123U;
definition.dlc = static_cast<std::uint8_t>(test.data.size());
definition.signals.push_back(signal);
DecodeDatabase database;
database.frames.push_back(definition);
database.frameIndexByKey.emplace(FrameKey(definition.canId, false), 0U);
RawCanFrame frame;
frame.canId = definition.canId;
frame.data = test.data;
const DecodedFrameValue result = DbcDecoder().Decode(database, frame);
const bool passed = result.valid &&
(result.definition == &database.frames[0]) &&
(result.signals.size() == 1U) &&
(result.signals[0].definition == &database.frames[0].signals[0]) &&
(result.signals[0].valid == test.valid) &&
(!test.valid || (((test.valueType == ValueType::Signed)
? (std::get_if<std::int64_t>(&result.signals[0].rawValue) &&
std::get<std::int64_t>(result.signals[0].rawValue) == test.rawValue)
: (std::get_if<std::uint64_t>(&result.signals[0].rawValue) &&
std::get<std::uint64_t>(result.signals[0].rawValue) ==
static_cast<std::uint64_t>(test.rawValue))) &&
(result.signals[0].physicalValue == test.physicalValue)));
if (!passed) {
std::cerr << "FAIL: " << test.name << '\n';
++failures;
}
}
std::size_t boundaryCases = 0U;
for (ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) {
for (ValueType type: {ValueType::Unsigned, ValueType::Signed}) {
for (std::uint64_t bits: {std::uint64_t(0U), std::uint64_t(0x7FFFFFFFFFFFFFFFULL),
std::uint64_t(0x8000000000000000ULL),
std::uint64_t(0x8000000000000001ULL),
std::uint64_t(0xFFFFFFFFFFFFFFFFULL)}) {
DecodeDatabase database;
DecodeFrame definition;
DecodeSignal signal;
signal.startBit = order == ByteOrder::Intel ? 0U : 7U;
signal.length = 64U;
signal.byteOrder = order;
signal.valueType = type;
signal.factor = 0.5;
signal.offset = 10.0;
definition.signals.push_back(signal);
database.frames.push_back(definition);
database.frameIndexByKey.emplace(FrameKey(0U, false), 0U);
RawCanFrame frame;
for (unsigned byte = 0U; byte < 8U; ++byte) {
const unsigned shift = (order == ByteOrder::Intel ? byte : 7U - byte) * 8U;
frame.data.push_back(static_cast<std::uint8_t>(bits >> shift));
}
const auto result = DbcDecoder().Decode(database, frame);
const std::int64_t signedExpected = bits <= 0x7FFFFFFFFFFFFFFFULL
? static_cast<std::int64_t>(bits)
: std::numeric_limits<std::int64_t>::min() +
static_cast<std::int64_t>(bits - 0x8000000000000000ULL);
const auto &decoded = result.signals.at(0U);
const auto expected = type == ValueType::Unsigned
? std::variant<std::int64_t, std::uint64_t>(bits)
: std::variant<std::int64_t, std::uint64_t>(signedExpected);
const double physical = (type == ValueType::Unsigned
? static_cast<double>(bits)
: static_cast<double>(signedExpected)) *
0.5 +
10.0;
std::ostringstream actualText;
std::ostringstream expectedText;
std::visit([&actualText](auto raw) { actualText << raw; }, decoded.rawValue);
if (type == ValueType::Unsigned)
expectedText << bits;
else
expectedText << signedExpected;
if (!decoded.valid || decoded.rawValue != expected ||
decoded.physicalValue != physical || actualText.str() != expectedText.str()) {
std::cerr << "FAIL: 64-bit boundary " << bits << '\n';
++failures;
}
++boundaryCases;
}
}
}
if (failures != 0)
return 1;
std::cout << "All " << sizeof(cases) / sizeof(cases[0]) + boundaryCases << " decoder cases passed.\n";
return 0;
}
+7
View File
@@ -0,0 +1,7 @@
TEMPLATE = app
TARGET = dbc_decoder_test
CONFIG += console c++17
CONFIG -= app_bundle qt
INCLUDEPATH += ..
SOURCES += dbc_decoder_test.cpp ../dbc_decoder.cpp
HEADERS += ../dbc_decoder.h ../decode_database.h
+253
View File
@@ -0,0 +1,253 @@
#include "dbc_parser.h"
#include "dbc_statement_handler.h"
#include <chrono>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <stdexcept>
struct Fixture {
std::filesystem::path path = std::filesystem::temp_directory_path() /
("dbc_parser_test_" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".dbc");
~Fixture() {
std::error_code error;
std::filesystem::remove(path, error);
}
void Write(const std::string &text) const {
std::ofstream output(path);
output << text;
if (!output)
throw std::runtime_error("Cannot write test fixture.");
}
};
class CountingHandler : public DbcStatementHandler {
public:
inline static int instances = 0;
inline static int calls = 0;
CountingHandler() { ++instances; }
void Handle(const std::string &, DbcParseContext &context) const override {
++calls;
context.database.frames.emplace_back();
}
};
int main() {
Fixture fixture;
int failures = 0;
std::size_t count = 0U;
const auto check = [&](bool passed, const std::string &name) {
++count;
if (!passed) {
++failures;
std::cerr << "FAIL: " << name << '\n';
}
};
struct ValidCase {
const char *line;
std::uint32_t id;
bool extended;
unsigned length;
};
const ValidCase valid[] = {
{"BO_ 0 Frame: 0 ECU", 0U, false, 0U},
{"BO_ 2047 Frame: 8 ECU", 2047U, false, 8U},
{"BO_ 2048 Frame: 64 ECU", 2048U, true, 64U},
{"BO_ 536870911 Frame: 255 ECU", 0x1FFFFFFFU, true, 255U},
{"BO_ 2147483649 Frame: 8 ECU", 1U, true, 8U},
{"BO_ 2684354559 Frame: 8 ECU", 0x1FFFFFFFU, true, 8U},
{" BO_\t256\tFrame :\t8\tECU \r", 256U, false, 8U}};
for (const auto &test: valid) {
fixture.Write(std::string(test.line) + '\n');
try {
const auto database = DbcParser().ParseFile(fixture.path.string());
check(database.frames.size() == 1U && database.frames[0].name == "Frame" &&
database.frames[0].canId == test.id &&
database.frames[0].isExtended == test.extended &&
database.frames[0].dlc == test.length &&
database.frames[0].transmitter == "ECU",
test.line);
} catch (const std::exception &error) { check(false, error.what()); }
}
const char *invalid[] = {
"BO_ invalid", "BO_", "BO_ 1", "BO_ 1 : 8 ECU", "BO_ 1 Frame 8 ECU",
"BO_ 1 Frame:", "BO_ 1 Frame: 8", "BO_ 1 Frame: 8 ECU extra",
"BO_ 1 Two Names: 8 ECU", "BO_ -1 Frame: 8 ECU", "BO_ +1 Frame: 8 ECU",
"BO_ 1x Frame: 8 ECU", "BO_ 0x123 Frame: 8 ECU", "BO_ 1.5 Frame: 8 ECU",
"BO_ 4294967296 Frame: 8 ECU", "BO_ 999999999999999999999 Frame: 8 ECU",
"BO_ 536870912 Frame: 8 ECU", "BO_ 1073741824 Frame: 8 ECU",
"BO_ 2684354560 Frame: 8 ECU", "BO_ 4294967295 Frame: 8 ECU",
"BO_ 1 Frame: -1 ECU", "BO_ 1 Frame: +8 ECU", "BO_ 1 Frame: 8x ECU",
"BO_ 1 Frame: 8.5 ECU", "BO_ 1 Frame: 256 ECU",
"BO_ 1 Frame: 4294967296 ECU", "BO_\tinvalid"};
for (const auto *line: invalid) {
fixture.Write(std::string("VERSION \"test\"\n\n") + line + '\n');
try {
DbcParser().ParseFile(fixture.path.string());
check(false, line);
} catch (const std::runtime_error &error) {
const std::string prefix = fixture.path.string() + ":3: ";
check(std::string(error.what()).find(prefix) == 0U &&
std::string(error.what()).size() > prefix.size(),
line);
}
}
fixture.Write(
"BO_TX_BU_ 256 : ECU;\nBO_ 256 Frame: 8 ECU\n"
" SG_ Speed : 0|16@1+ (0.5,1) [0|100] \"rpm\" ECU\n"
"CM_ BO_ 256 \"Frame comment\";\nCM_ SG_ 256 Speed \"Signal comment\";\n"
"BO_ 257 Second: 8 ECU\n");
const auto database = DbcParser().ParseFile(fixture.path.string());
check(database.frames.size() == 2U && database.frames[0].signals.size() == 1U &&
database.frames[0].comment == "Frame comment" &&
database.frames[0].signals[0].comment == "Signal comment",
"Integration");
fixture.Write("\nSG_ Speed : invalid\n");
try {
DbcParser().ParseFile(fixture.path.string());
check(false, "Signal error location");
} catch (const std::runtime_error &error) {
check(std::string(error.what()).find(fixture.path.string() + ":2: ") == 0U,
"Signal error location");
}
const DbcParser signalParser;
const auto signalLine = [](const std::string &start, const std::string &length,
const std::string &order, const std::string &factor,
const std::string &offset, const std::string &minimum,
const std::string &maximum) {
return "SG_ Value : " + start + "|" + length + "@" + order +
" (" + factor + "," + offset + ") [" + minimum + "|" + maximum + "] \"\" ECU";
};
const auto rejectSignal = [&](const std::string &line, unsigned payload = 8U) {
fixture.Write("BO_ 1 Frame: " + std::to_string(payload) + " ECU\n\n" + line + "\n");
try {
signalParser.ParseFile(fixture.path.string());
check(false, "Reject " + line);
} catch (const std::runtime_error &error) {
const std::string prefix = fixture.path.string() + ":3: ";
check(std::string(error.what()).find(prefix) == 0U &&
std::string(error.what()).size() > prefix.size(),
"Reject " + line);
}
};
const std::string ordinary = signalLine("0", "8", "1+", "1", "0", "0", "255");
for (const std::string value: {"-1", "+1", "0junk", "0x10", "1.5",
"4294967296", "999999999999999999999"}) {
rejectSignal(signalLine(value, "8", "1+", "1", "0", "0", "255"));
rejectSignal(signalLine("0", value, "1+", "1", "0", "0", "255"));
}
for (const std::string length: {"0", "65"})
rejectSignal(signalLine("0", length, "1+", "1", "0", "0", "255"));
for (const std::string order: {"2+", "9-", "1", "1x", "1++", "01+"})
rejectSignal(signalLine("0", "8", order, "1", "0", "0", "255"));
for (const std::string value: {"1junk", "nan", "NaN", "inf", "-inf", "1e999", "1e-999", "0x1p2", "", "1e", "1.2.3"}) {
rejectSignal(signalLine("0", "8", "1+", value, "0", "0", "255"));
rejectSignal(signalLine("0", "8", "1+", "1", value, "0", "255"));
rejectSignal(signalLine("0", "8", "1+", "1", "0", value, "255"));
rejectSignal(signalLine("0", "8", "1+", "1", "0", "0", value));
}
rejectSignal(signalLine("0", "8", "1+", "1", "0", "2", "1"));
for (const std::string name: {"", "Two Names", "Value M", "Value m1", "Value m1M"}) {
std::string line = ordinary;
line.replace(4U, 5U, name);
rejectSignal(line);
}
for (const std::string broken: {
"SG_", "SG_ Value", "SG_ Value : 0|8@1+ (1,0) [0|255] ECU",
"SG_ Value : 0|8@1+ (1,0) [0|255] \"\"",
"SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU,",
"SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU extra"})
rejectSignal(broken);
rejectSignal(signalLine("64", "1", "1+", "1", "0", "0", "1"));
rejectSignal(signalLine("63", "2", "1+", "1", "0", "0", "3"));
rejectSignal(signalLine("56", "2", "0+", "1", "0", "0", "3"));
rejectSignal(signalLine("4294967295", "64", "1+", "1", "0", "0", "1"));
rejectSignal(ordinary, 0U);
for (const char delimiter: {'|', '@', '(', ')', '[', ']', '"'}) {
std::string line = ordinary;
line.erase(line.find(delimiter), 1U);
rejectSignal(line);
}
fixture.Write("BO_ 1 Frame: 8 ECU\n" +
signalLine("0", "8", "1+", "0", "0", "0", "0") + "\n" +
signalLine("8", "8", "1-", "-1", "0", "-255", "0") + "\n");
const auto factors = signalParser.ParseFile(fixture.path.string());
check(factors.frames[0].signals.size() == 2U &&
factors.frames[0].signals[0].factor == 0.0 &&
factors.frames[0].signals[1].factor == -1.0,
"Zero and negative factors and equal range boundaries");
struct SignalCase {
const char *start;
const char *length;
const char *order;
unsigned payload;
};
const SignalCase validSignals[] = {
{"0", "1", "1+", 1U},
{"7", "1", "0-", 1U},
{"0", "64", "1+", 8U},
{"7", "64", "0-", 8U},
{"63", "1", "1+", 8U},
{"56", "1", "0+", 8U},
{"3", "12", "0+", 2U},
{"2039", "1", "1+", 255U}};
for (const auto &test: validSignals) {
fixture.Write("BO_ 1 Frame: " + std::to_string(test.payload) + " ECU\n" +
signalLine(test.start, test.length, test.order, ".5e+1", "-2.5E-1", "-1.", "+2e2") + "\n");
try {
const auto parsed = signalParser.ParseFile(fixture.path.string());
const auto &signal = parsed.frames.at(0).signals.at(0);
check(signal.startBit == std::stoul(test.start) &&
signal.length == std::stoul(test.length) &&
signal.isLittleEndian == (test.order[0] == '1') &&
signal.isSigned == (test.order[1] == '-') &&
signal.factor == 5.0 && signal.offset == -0.25 &&
signal.minimum == -1.0 && signal.maximum == 200.0,
"Valid signal boundary and numeric metadata");
} catch (const std::exception &error) { check(false, error.what()); }
}
fixture.Write("BO_ 1 Frame: 8 ECU\n"
"SG_\tValue\t:\t0 | 8 @1+ ( 1 , 0 ) [ 0 | 255 ] \"rpm\" ECU1,\tECU2\n");
try {
const auto parsed = signalParser.ParseFile(fixture.path.string());
const auto &signal = parsed.frames.at(0).signals.at(0);
check(signal.name == "Value" && signal.unit == "rpm" &&
signal.receivers == std::vector<std::string>({"ECU1", "ECU2"}),
"Signal tabs, spaces, and receiver list");
} catch (const std::exception &error) { check(false, error.what()); }
DbcDatabase dispatchDatabase;
DbcParseContext context{dispatchDatabase, nullptr};
DbcStatementDispatcher dispatcher;
dispatcher.RegisterType<CountingHandler>("TEST_");
check(dispatcher.Dispatch("TEST_ first", context) &&
dispatcher.Dispatch("TEST_ second", context) &&
CountingHandler::instances == 1 && CountingHandler::calls == 2 &&
dispatchDatabase.frames.size() == 2U,
"Factory dispatch and handler reuse");
check(!dispatcher.Dispatch("TEST_OTHER data", context) &&
!dispatcher.Dispatch("BO_TX_BU_ 1 : ECU;", context) &&
!dispatcher.Dispatch("VAL_ 1 Speed 0 \"Stopped\";", context) &&
CountingHandler::calls == 2,
"Exact and unknown keyword dispatch");
const DbcParser parser;
fixture.Write("BO_ 1 First: 8 ECU\n");
check(parser.ParseFile(fixture.path.string()).frames.size() == 1U, "First parse");
fixture.Write("SG_ Speed : invalid\n");
try {
parser.ParseFile(fixture.path.string());
check(false, "Independent parse context");
} catch (const std::runtime_error &error) {
check(std::string(error.what()).find("Signal found before any frame") !=
std::string::npos,
"Independent parse context");
}
fixture.Write("BO_ 2 Second: 8 ECU\n");
const auto recovered = parser.ParseFile(fixture.path.string());
check(recovered.frames.size() == 1U && recovered.frames[0].canId == 2U,
"Fresh parse after error");
if (failures != 0) return 1;
std::cout << "All " << count << " parser cases passed.\n";
return 0;
}
+7
View File
@@ -0,0 +1,7 @@
TEMPLATE = app
TARGET = dbc_parser_test
CONFIG += console c++17
CONFIG -= app_bundle qt
INCLUDEPATH += ..
SOURCES += dbc_parser_test.cpp ../dbc_parser.cpp ../dbc_statement_handler.cpp
HEADERS += ../dbc_statement_handler.h ../typefactory.h ../dbc_parser.h ../dbc_database.h ../frame_info.h ../signal_info.h
+21 -33
View File
@@ -12,64 +12,52 @@
#include "tree_node.h" #include "tree_node.h"
TreeNode::TreeNode() TreeNode::TreeNode()
: m_type (NodeType::Root) : m_type(NodeType::Root), m_name("dbc"), m_children(), m_frame(), m_signal() {
, m_name ("dbc")
, m_children()
, m_frame()
, m_signal() {
} }
TreeNode::TreeNode (const FrameInfo &frame) TreeNode::TreeNode(const FrameInfo &frame)
: m_type (NodeType::Frame) : m_type(NodeType::Frame), m_name(frame.name), m_children(), m_frame(new FrameInfo(frame)), m_signal() {
, m_name (frame.name)
, m_children()
, m_frame (new FrameInfo (frame))
, m_signal() {
} }
TreeNode::TreeNode (const SignalInfo &signal) TreeNode::TreeNode(const SignalInfo &signal)
: m_type (NodeType::Signal) : m_type(NodeType::Signal), m_name(signal.name), m_children(), m_frame(), m_signal(new SignalInfo(signal)) {
, m_name (signal.name)
, m_children()
, m_frame()
, m_signal (new SignalInfo (signal)) {
} }
void TreeNode::AddChild (std::unique_ptr<TreeNode> child) { void TreeNode::AddChild(std::unique_ptr<TreeNode> child) {
if (child) if (child)
m_children.push_back (std::move (child)); m_children.push_back(std::move(child));
} }
std::size_t TreeNode::GetChildCount() const { std::size_t TreeNode::GetChildCount() const {
return m_children.size(); return m_children.size();
} }
const TreeNode *TreeNode::GetChild (std::size_t index) const { const TreeNode *TreeNode::GetChild(std::size_t index) const {
if (index >= m_children.size()) if (index >= m_children.size())
return nullptr; return nullptr;
return m_children[index].get(); return m_children[index].get();
} }
TreeNode *TreeNode::GetChild (std::size_t index) { TreeNode *TreeNode::GetChild(std::size_t index) {
if (index >= m_children.size()) if (index >= m_children.size())
return nullptr; return nullptr;
return m_children[index].get(); return m_children[index].get();
} }
NodeType TreeNode::GetType() const { NodeType TreeNode::GetType() const {
return m_type; return m_type;
} }
const std::string &TreeNode::GetName() const { const std::string &TreeNode::GetName() const {
return m_name; return m_name;
} }
const FrameInfo *TreeNode::GetFrame() const { const FrameInfo *TreeNode::GetFrame() const {
return m_frame.get(); return m_frame.get();
} }
const SignalInfo *TreeNode::GetSignal() const { const SignalInfo *TreeNode::GetSignal() const {
return m_signal.get(); return m_signal.get();
} }
+17 -17
View File
@@ -12,10 +12,10 @@
#ifndef TREE_NODE_H #ifndef TREE_NODE_H
#define TREE_NODE_H #define TREE_NODE_H
#include <cstddef>
#include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include <memory>
#include <cstddef>
#include "frame_info.h" #include "frame_info.h"
#include "signal_info.h" #include "signal_info.h"
@@ -24,16 +24,16 @@
* @brief Type of a tree node. * @brief Type of a tree node.
*/ */
enum class NodeType { enum class NodeType {
Root, Root,
Frame, Frame,
Signal Signal
}; };
/** /**
* @brief Tree node for later use in model/view or other hierarchy consumers. * @brief Tree node for later use in model/view or other hierarchy consumers.
*/ */
class TreeNode { class TreeNode {
public: public:
/** /**
* @brief Create root node. * @brief Create root node.
*/ */
@@ -43,27 +43,27 @@ class TreeNode {
* @brief Create frame node. * @brief Create frame node.
* @param frame Frame payload. * @param frame Frame payload.
*/ */
explicit TreeNode (const FrameInfo &frame); explicit TreeNode(const FrameInfo &frame);
/** /**
* @brief Create signal node. * @brief Create signal node.
* @param signal Signal payload. * @param signal Signal payload.
*/ */
explicit TreeNode (const SignalInfo &signal); explicit TreeNode(const SignalInfo &signal);
~TreeNode() = default; ~TreeNode() = default;
TreeNode (const TreeNode &) = delete; TreeNode(const TreeNode &) = delete;
TreeNode &operator= (const TreeNode &) = delete; TreeNode &operator=(const TreeNode &) = delete;
TreeNode (TreeNode &&) = default; TreeNode(TreeNode &&) = default;
TreeNode &operator= (TreeNode &&) = default; TreeNode &operator=(TreeNode &&) = default;
/** /**
* @brief Add child node. * @brief Add child node.
* @param child Child node to add. * @param child Child node to add.
*/ */
void AddChild (std::unique_ptr<TreeNode> child); void AddChild(std::unique_ptr<TreeNode> child);
/** /**
* @brief Get child count. * @brief Get child count.
@@ -76,14 +76,14 @@ class TreeNode {
* @param index Child index. * @param index Child index.
* @return Child pointer or nullptr if index is invalid. * @return Child pointer or nullptr if index is invalid.
*/ */
const TreeNode *GetChild (std::size_t index) const; const TreeNode *GetChild(std::size_t index) const;
/** /**
* @brief Get mutable child by index. * @brief Get mutable child by index.
* @param index Child index. * @param index Child index.
* @return Child pointer or nullptr if index is invalid. * @return Child pointer or nullptr if index is invalid.
*/ */
TreeNode *GetChild (std::size_t index); TreeNode *GetChild(std::size_t index);
/** /**
* @brief Get node type. * @brief Get node type.
@@ -109,10 +109,10 @@ class TreeNode {
*/ */
const SignalInfo *GetSignal() const; const SignalInfo *GetSignal() const;
private: private:
NodeType m_type; NodeType m_type;
std::string m_name; std::string m_name;
std::vector<std::unique_ptr<TreeNode> > m_children; std::vector<std::unique_ptr<TreeNode>> m_children;
std::unique_ptr<FrameInfo> m_frame; std::unique_ptr<FrameInfo> m_frame;
std::unique_ptr<SignalInfo> m_signal; std::unique_ptr<SignalInfo> m_signal;
}; };
+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