diff --git a/README.md b/README.md index 0b34b93..491e5f1 100644 --- a/README.md +++ b/README.md @@ -336,8 +336,28 @@ This matters because the same numeric identifier must not be confused between st Immutable runtime snapshot with a private frame index. Build it with `DbcDecodeBuilder::Build(parsed)` or construct it from `std::vector`. -Construction rejects duplicate frame keys and duplicate signal names within a -frame with `std::invalid_argument`, including manually supplied metadata. +Construction validates all definitions before publishing the snapshot and throws +`std::invalid_argument` for invalid metadata, including manually supplied data: + +- Normalized CAN IDs must fit 11 bits for standard frames or 29 bits for extended + frames. DBC's bit-31 encoding flag must already be removed. +- Frame keys must be unique; signal names must be unique within their frame. +- Signal lengths must be 1–64 bits, with Intel/Motorola layouts entirely inside + the declared payload. Bit coordinates use overflow-safe 64-bit arithmetic. +- Byte order, numeric type, and multiplexing role must be valid enum values. +- Factor, offset, minimum, and maximum must be finite; minimum must not exceed + maximum. Zero and negative factors are allowed. +- Multiplexing requires one unsigned selector when branches are present, with + branch values in its raw range. Signals may overlap only when they are branches + selected by different raw values, including mixed byte orders. + +`dlc` remains a byte count in the storage range 0–255; construction imposes no +additional CAN/CAN FD payload limit. A zero-byte frame may contain no signals. +Both `DbcDecodeBuilder::Build` and the direct constructor apply these rules. +Construction copies the input, including nested metadata, even for rvalue input. +Saved mutable aliases into that input cannot modify definitions or invalidate the +lookup index. Database copies still share the frozen snapshot; a failed +construction or replacement leaves the previous snapshot and its lookups intact. - `GetFrames()` returns a borrowed `const std::vector&`. - `FindFrame(FrameKey)` returns `std::shared_ptr`. @@ -461,9 +481,9 @@ The decoder applies factor and offset; it does not clamp physical values to the declared minimum and maximum. Non-finite conversion results (NaN or positive/ negative infinity), including overflow with finite factor/offset, produce `PhysicalConversionFailed`. The exact raw value and non-finite `physicalValue` -remain available, while signal `valid` is false. Non-finite factor/offset in manually -constructed definitions follow this same conversion-result check. Physical -conversion failure of a selector does not prevent raw branch selection. +remain available, while signal `valid` is false. Non-finite numeric parameters are +rejected during database construction. Finite parameters can still overflow during +conversion; conversion failure of a selector does not prevent raw branch selection. ### Definition ownership and reload @@ -483,8 +503,8 @@ that metadata is no longer needed. `GetFrames()` is a borrowed view. References, iterators, and raw pointers obtained from it or from a handle's `.get()` do not retain ownership. Keep an owning handle -or an unchanged database copy while using them. Do not retain mutable aliases -into a vector moved into the database constructor. The raw string pointer from +or an unchanged database copy while using them. Constructor input is copied and +may be changed independently afterward. The raw string pointer from `GetValueDescription()` follows the same rule: keep the signal definition handle alive while using the string. @@ -763,14 +783,14 @@ Each result has `SignalDecodeStatus status`: | `Inactive` | Selector is available and chooses another branch | | `SelectorUnavailable` | Branch activation is unknown because selector bytes are missing | | `InsufficientPayload` | Selected or unconditional signal bytes are missing | -| `InvalidDefinition` | Invalid length, byte order, numeric type, multiplexing role, layout outside declared DLC, or missing/invalid selector in manually built runtime metadata | +| `InvalidDefinition` | Defensive decoder check failed; invalid definitions are rejected during database construction | | `PhysicalConversionFailed` | Raw value is available, but the converted physical value is NaN or infinity | All statuses except `Valid` have `valid == false` and `GetValueDescription()` returns nullptr. `PhysicalConversionFailed` preserves extracted raw data and the non-finite conversion result; other invalid statuses have no decoded numeric data. Check `status` to distinguish inactive branches from errors. Consumers constructing runtime metadata -manually must obey the same non-overlap rules as the parser. Extended and nested +manually receive the same selector and non-overlap validation as parsed files. Extended and nested multiplexing are outside this stage and are explicitly rejected, rather than silently decoded as unconditional signals. diff --git a/changelog.md b/changelog.md index aa8152d..4a539f3 100644 --- a/changelog.md +++ b/changelog.md @@ -10,6 +10,13 @@ existing Git tag; subsequent changes are listed as unreleased. #### Added +- Issue #16: construction-time validation for both manual runtime definitions and + `DbcDecodeBuilder` input: normalized CAN IDs, duplicate keys, signal lengths and + positions, enums, finite numeric parameters, ordered ranges, selectors, branch + ranges, and overlaps. +- Regression tests for both construction paths, numeric and bit boundaries, + mixed byte orders, exclusive overlaps, mutable input aliases, and failed reloads. + - `FrameDecodeStatus` distinguishes unknown frames, insufficient payloads, signal errors, and successful decoding; frame `valid` still means matched. - `SignalDecodeStatus::PhysicalConversionFailed` reports NaN/Inf results, @@ -20,6 +27,15 @@ existing Git tag; subsequent changes are listed as unreleased. #### Changed +- Runtime construction rejects invalid definitions with `std::invalid_argument` + before publishing a snapshot. Decoder tests now expect construction failures + for malformed definitions; finite arithmetic overflow remains a decode status. +- Shared overflow-safe layout validation across parsing, construction, and + decoding. Intel extraction also widens bit coordinates before arithmetic. +- `DecodeDatabase` now accepts definitions by const reference and copies them, + even for rvalues, preventing retained + mutable aliases from modifying the frozen snapshot or invalidating its index. + - Short payloads decode available signals without padding; longer payloads ignore bytes beyond the declared DLC. Short frame status takes precedence over signal errors, with per-signal causes retained in definition order. diff --git a/core/core_files.pri b/core/core_files.pri index 7a41c26..0ccf7c1 100644 --- a/core/core_files.pri +++ b/core/core_files.pri @@ -8,6 +8,7 @@ SOURCES += \ $$PWD/tree_node.cpp HEADERS += \ + $$PWD/signal_layout.h \ $$PWD/definition_key.h \ $$PWD/dbc_diagnostic.h \ $$PWD/multiplexing.h \ diff --git a/core/dbc_decode_builder.cpp b/core/dbc_decode_builder.cpp index b1c0171..4740a8c 100644 --- a/core/dbc_decode_builder.cpp +++ b/core/dbc_decode_builder.cpp @@ -53,5 +53,5 @@ DecodeDatabase DbcDecodeBuilder::Build(const DbcDatabase &source) const { frames.push_back(std::move(targetFrame)); } - return DecodeDatabase(std::move(frames)); + return DecodeDatabase(frames); } diff --git a/core/dbc_decode_builder.h b/core/dbc_decode_builder.h index 530cb94..a231d6e 100644 --- a/core/dbc_decode_builder.h +++ b/core/dbc_decode_builder.h @@ -24,7 +24,7 @@ public: * @brief Build runtime decode database. * @param source Parsed DBC database. * @return Immutable runtime snapshot independent of source lifetime. - * @throws std::invalid_argument for duplicate frame keys or signal names. + * @throws std::invalid_argument for invalid metadata or duplicate keys. */ DecodeDatabase Build(const DbcDatabase &source) const; }; diff --git a/core/dbc_decoder.cpp b/core/dbc_decoder.cpp index 45ca0e5..e4de87e 100644 --- a/core/dbc_decoder.cpp +++ b/core/dbc_decoder.cpp @@ -10,6 +10,7 @@ */ #include "dbc_decoder.h" +#include "signal_layout.h" #include #include @@ -58,11 +59,8 @@ bool DbcDecoder::IsValidDefinition(const DecodeSignal &signal, std::uint8_t dlc) signal.multiplexing.role != MultiplexRole::Branch)) return false; - // Use widened coordinates to avoid overflow on malformed start bits. - const std::uint64_t start = signal.byteOrder == ByteOrder::Intel - ? signal.startBit - : std::uint64_t(signal.startBit / 8U) * 8U + 7U - signal.startBit % 8U; - return start + signal.length <= std::uint64_t(dlc) * 8U; + return IsValidSignalLayout(signal.startBit, signal.length, + signal.byteOrder == ByteOrder::Intel, dlc); } DbcDecoder::SelectorState DbcDecoder::ReadSelector(const DecodeFrame &definition, @@ -154,8 +152,8 @@ bool DbcDecoder::ExtractIntel(const std::vector &data, value = 0U; for (std::uint32_t bitIndex = 0U; bitIndex < length; ++bitIndex) { - const std::uint32_t absoluteBit = startBit + bitIndex; - const std::uint32_t byteIndex = absoluteBit / 8U; + const std::uint64_t absoluteBit = std::uint64_t(startBit) + bitIndex; + const std::uint64_t byteIndex = absoluteBit / 8U; const std::uint32_t bitInByte = absoluteBit % 8U; if (byteIndex >= data.size()) diff --git a/core/dbc_statement_handler.cpp b/core/dbc_statement_handler.cpp index c72cb7c..a717950 100644 --- a/core/dbc_statement_handler.cpp +++ b/core/dbc_statement_handler.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT #include "dbc_statement_handler.h" #include "text_utils.h" +#include "signal_layout.h" #include #include #include @@ -356,13 +357,9 @@ void DbcSignalHandler::Handle(const std::string &line, DbcParseContext &context) if (FindSignalByName(*context.currentFrame, signal.name) != nullptr) throw std::runtime_error("Duplicate signal name '" + signal.name + "' in frame '" + context.currentFrame->name + "'."); - const std::uint64_t payloadBits = static_cast(context.currentFrame->dlc) * 8U; - std::uint64_t bit = signal.startBit; - for (std::uint32_t index = 0U; index < signal.length; ++index) { - if (bit >= payloadBits) - throw std::runtime_error("Signal exceeds the frame payload length."); - bit = NextSignalBit(bit, signal.isLittleEndian); - } + if (!IsValidSignalLayout(signal.startBit, signal.length, signal.isLittleEndian, + context.currentFrame->dlc)) + throw std::runtime_error("Signal exceeds the frame payload length."); context.currentFrame->signals.push_back(std::move(signal)); context.signalLines.back().push_back(context.lineNumber); } diff --git a/core/decode_database.cpp b/core/decode_database.cpp index 693d229..bb8fc67 100644 --- a/core/decode_database.cpp +++ b/core/decode_database.cpp @@ -2,27 +2,104 @@ // Copyright (c) 2026 Dim Himro #include "decode_database.h" +#include "signal_layout.h" +#include +#include +#include #include #include -#include -DecodeDatabase::Storage::Storage(std::vector definitions) - : frames(std::move(definitions)) { - frameIndexByKey.reserve(frames.size()); - for (std::size_t index = 0U; index < frames.size(); ++index) { - const DecodeFrame &frame = frames[index]; - if (!frameIndexByKey.emplace(frame.GetKey(), index).second) - throw std::invalid_argument("Duplicate runtime frame key"); +namespace { + constexpr std::size_t MaximumPayloadBits = std::numeric_limits::max() * 8U; + using SignalBits = std::bitset; + + void ValidateSignal(const DecodeSignal &signal, std::uint8_t dlc) { + if (signal.byteOrder != ByteOrder::Intel && signal.byteOrder != ByteOrder::Motorola) + throw std::invalid_argument("Invalid signal byte order: " + signal.name); + if (signal.valueType != ValueType::Unsigned && signal.valueType != ValueType::Signed) + throw std::invalid_argument("Invalid signal value type: " + signal.name); + if (signal.multiplexing.role != MultiplexRole::Unconditional && + signal.multiplexing.role != MultiplexRole::Selector && + signal.multiplexing.role != MultiplexRole::Branch) + throw std::invalid_argument("Invalid multiplexing role: " + signal.name); + if (!IsValidSignalLayout(signal.startBit, signal.length, + signal.byteOrder == ByteOrder::Intel, dlc)) + throw std::invalid_argument("Invalid signal length or payload position: " + signal.name); + if (!std::isfinite(signal.factor) || !std::isfinite(signal.offset) || + !std::isfinite(signal.minimum) || !std::isfinite(signal.maximum) || + signal.minimum > signal.maximum) + throw std::invalid_argument("Invalid signal numeric parameters: " + signal.name); + } + + SignalBits GetSignalBits(const DecodeSignal &signal) { + SignalBits bits; + const bool littleEndian = signal.byteOrder == ByteOrder::Intel; + const std::uint64_t start = SignalStartCoordinate(signal.startBit, littleEndian); + for (std::uint32_t index = 0U; index < signal.length; ++index) { + const std::uint64_t coordinate = start + index; + const std::uint64_t bit = littleEndian ? coordinate + : coordinate / 8U * 8U + 7U - coordinate % 8U; + bits.set(static_cast(bit)); + } + return bits; + } + + bool AreMutuallyExclusive(const DecodeSignal &first, const DecodeSignal &second) { + return first.multiplexing.role == MultiplexRole::Branch && + second.multiplexing.role == MultiplexRole::Branch && + first.multiplexing.value != second.multiplexing.value; + } + + void ValidateFrame(const DecodeFrame &frame) { + const std::uint32_t maximumId = frame.isExtended ? 0x1FFFFFFFU : 0x7FFU; + if (frame.canId > maximumId) + throw std::invalid_argument("Invalid normalized CAN ID: " + std::to_string(frame.canId)); + std::unordered_set names; + const DecodeSignal *selector = nullptr; for (const DecodeSignal &signal: frame.signals) { if (!names.insert(signal.name).second) - throw std::invalid_argument("Duplicate runtime signal key"); + throw std::invalid_argument("Duplicate runtime signal key: " + signal.name); + ValidateSignal(signal, frame.dlc); + if (signal.multiplexing.role == MultiplexRole::Selector) { + if (selector != nullptr || signal.valueType != ValueType::Unsigned) + throw std::invalid_argument("Multiplexing requires one unsigned selector per frame"); + selector = &signal; + } } + + std::vector layouts; + layouts.reserve(frame.signals.size()); + for (std::size_t index = 0U; index < frame.signals.size(); ++index) { + const DecodeSignal &signal = frame.signals[index]; + if (signal.multiplexing.role == MultiplexRole::Branch && + (selector == nullptr || + (selector->length < 64U && signal.multiplexing.value >= (std::uint64_t(1U) << selector->length)))) + throw std::invalid_argument("Missing selector or branch value outside selector range: " + signal.name); + const SignalBits bits = GetSignalBits(signal); + for (std::size_t previous = 0U; previous < index; ++previous) { + if (!AreMutuallyExclusive(signal, frame.signals[previous]) && (bits & layouts[previous]).any()) + throw std::invalid_argument("Overlapping signals: " + signal.name); + } + layouts.push_back(bits); + } + } +}// namespace + +DecodeDatabase::Storage::Storage(const std::vector &definitions) + : frames(definitions) { + // Validate the complete snapshot before creating its lookup index. + for (const DecodeFrame &frame: frames) + ValidateFrame(frame); + frameIndexByKey.reserve(frames.size()); + for (std::size_t index = 0U; index < frames.size(); ++index) { + if (!frameIndexByKey.emplace(frames[index].GetKey(), index).second) + throw std::invalid_argument("Duplicate runtime frame key"); } } -DecodeDatabase::DecodeDatabase(std::vector frames) - : storage(std::make_shared(std::move(frames))) { +DecodeDatabase::DecodeDatabase(const std::vector &frames) + : storage(std::make_shared(frames)) { } const std::vector &DecodeDatabase::GetFrames() const { diff --git a/core/decode_database.h b/core/decode_database.h index 4461829..6d8eb82 100644 --- a/core/decode_database.h +++ b/core/decode_database.h @@ -88,11 +88,11 @@ class DecodeDatabase { public: DecodeDatabase() = default; /** - * Freeze definitions and build the frame index. - * Do not retain mutable aliases into moved input vectors. - * @throws std::invalid_argument for duplicate frame keys or signal names. + * Copy and validate definitions before building the frame index. + * Input aliases cannot modify the snapshot, even for rvalue input. + * @throws std::invalid_argument for invalid metadata or duplicate keys. */ - explicit DecodeDatabase(std::vector frames); + explicit DecodeDatabase(const std::vector &frames); /** Borrowed view, valid while this snapshot remains alive and unchanged. */ const std::vector &GetFrames() const; @@ -105,7 +105,7 @@ private: std::vector frames; std::unordered_map frameIndexByKey; - explicit Storage(std::vector definitions); + explicit Storage(const std::vector &definitions); }; std::shared_ptr storage; diff --git a/core/signal_layout.h b/core/signal_layout.h new file mode 100644 index 0000000..6d91f87 --- /dev/null +++ b/core/signal_layout.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +#ifndef SIGNAL_LAYOUT_H +#define SIGNAL_LAYOUT_H + +#include + +/** Linear coordinate in the signal's bit order, widened before arithmetic. */ +inline std::uint64_t SignalStartCoordinate(std::uint32_t startBit, bool littleEndian) { + return littleEndian ? std::uint64_t(startBit) + : std::uint64_t(startBit / 8U) * 8U + 7U - startBit % 8U; +} + +inline bool IsValidSignalLayout(std::uint32_t startBit, std::uint32_t length, + bool littleEndian, std::uint8_t dlc) { + return length >= 1U && length <= 64U && + SignalStartCoordinate(startBit, littleEndian) + length <= std::uint64_t(dlc) * 8U; +} + +#endif diff --git a/docs/dbcframework.tag b/docs/dbcframework.tag index c5696ec..62190e1 100644 --- a/docs/dbcframework.tag +++ b/docs/dbcframework.tag @@ -287,8 +287,8 @@ DecodeDatabase classDecodeDatabase.html - aa935a3a0d334f932d6a8c89a3818286a - (std::vector< DecodeFrame > frames) + a51a44cf7c3f23c98ec8781afe0097c0c + (const std::vector< DecodeFrame > &frames) const std::vector< DecodeFrame > & diff --git a/docs/doxygen_warnings.log b/docs/doxygen_warnings.log index cc72916..df1a64d 100644 --- a/docs/doxygen_warnings.log +++ b/docs/doxygen_warnings.log @@ -10,7 +10,7 @@ /home/deeaitch/work/3.projects/git.himro.ca/automotive/dbc/core/definition_key.h:56: warning: Compound SignalKeyHasher is not documented. /home/deeaitch/work/3.projects/git.himro.ca/automotive/dbc/core/dbc_decoder.h:32: warning: Member SignalDecodeStatus (enumeration) of file dbc_decoder.h is not documented. /home/deeaitch/work/3.projects/git.himro.ca/automotive/dbc/examples/main.cpp:100: warning: Member main(int argc, char *argv[]) (function) of file examples/main.cpp is not documented. -/home/deeaitch/work/3.projects/git.himro.ca/automotive/dbc/README.md:1607: warning: end of comment block while expecting command +/home/deeaitch/work/3.projects/git.himro.ca/automotive/dbc/README.md:1627: warning: end of comment block while expecting command /home/deeaitch/work/3.projects/git.himro.ca/automotive/dbc/core/dbc_statement_handler.h:68: warning: Member Handle(const std::string &line, DbcParseContext &context) const override (function) of class DbcCommentHandler is not documented. /home/deeaitch/work/3.projects/git.himro.ca/automotive/dbc/core/dbc_diagnostic.h:26: warning: Member severity (variable) of struct DbcDiagnostic is not documented. /home/deeaitch/work/3.projects/git.himro.ca/automotive/dbc/core/dbc_diagnostic.h:27: warning: Member code (variable) of struct DbcDiagnostic is not documented. diff --git a/docs/html/classDbcCommentHandler.html b/docs/html/classDbcCommentHandler.html index 0bf77e9..2e81bce 100644 --- a/docs/html/classDbcCommentHandler.html +++ b/docs/html/classDbcCommentHandler.html @@ -149,7 +149,7 @@ Public Member Functions

Implements DbcStatementHandler.

-

Definition at line 370 of file dbc_statement_handler.cpp.

+

Definition at line 367 of file dbc_statement_handler.cpp.

diff --git a/docs/html/classDbcDecodeBuilder.html b/docs/html/classDbcDecodeBuilder.html index fa29228..396cec2 100644 --- a/docs/html/classDbcDecodeBuilder.html +++ b/docs/html/classDbcDecodeBuilder.html @@ -139,7 +139,7 @@ Public Member Functions
Returns
Immutable runtime snapshot independent of source lifetime.
Exceptions
- +
std::invalid_argumentfor duplicate frame keys or signal names.
std::invalid_argumentfor invalid metadata or duplicate keys.
diff --git a/docs/html/classDbcDecoder.html b/docs/html/classDbcDecoder.html index 97cb237..67c364c 100644 --- a/docs/html/classDbcDecoder.html +++ b/docs/html/classDbcDecoder.html @@ -160,11 +160,11 @@ Public Member Functions
Returns
Owning immutable frame handle or nullptr. Safe across reload.
-

Definition at line 16 of file dbc_decoder.cpp.

+

Definition at line 17 of file dbc_decoder.cpp.

-

References DecodeDatabase::FindFrame().

+

References DecodeDatabase::FindFrame().

-

Referenced by Decode().

+

Referenced by Decode().

@@ -204,9 +204,9 @@ Public Member Functions
Returns
Decoded frame value.
-

Definition at line 22 of file dbc_decoder.cpp.

+

Definition at line 23 of file dbc_decoder.cpp.

-

References RawCanFrame::canId, RawCanFrame::data, DecodedSignalValue::definition, DecodedFrameValue::definition, FindFrame(), RawCanFrame::isExtended, DecodedSignalValue::key, DecodedFrameValue::key, DecodedFrameValue::status, DecodedSignalValue::valid, and DecodedFrameValue::valid.

+

References RawCanFrame::canId, RawCanFrame::data, DecodedSignalValue::definition, DecodedFrameValue::definition, FindFrame(), RawCanFrame::isExtended, DecodedSignalValue::key, DecodedFrameValue::key, DecodedFrameValue::status, DecodedSignalValue::valid, and DecodedFrameValue::valid.

diff --git a/docs/html/classDbcFrameHandler.html b/docs/html/classDbcFrameHandler.html index d6f796b..0615bc3 100644 --- a/docs/html/classDbcFrameHandler.html +++ b/docs/html/classDbcFrameHandler.html @@ -149,7 +149,7 @@ Public Member Functions

Implements DbcStatementHandler.

-

Definition at line 342 of file dbc_statement_handler.cpp.

+

Definition at line 343 of file dbc_statement_handler.cpp.

diff --git a/docs/html/classDbcHeaderHandler.html b/docs/html/classDbcHeaderHandler.html index 428ccca..58fa368 100644 --- a/docs/html/classDbcHeaderHandler.html +++ b/docs/html/classDbcHeaderHandler.html @@ -152,7 +152,7 @@ Public Member Functions

Implements DbcStatementHandler.

-

Definition at line 389 of file dbc_statement_handler.cpp.

+

Definition at line 386 of file dbc_statement_handler.cpp.

diff --git a/docs/html/classDbcParser.html b/docs/html/classDbcParser.html index c562041..db610ad 100644 --- a/docs/html/classDbcParser.html +++ b/docs/html/classDbcParser.html @@ -183,7 +183,7 @@ Public Member Functions

Definition at line 44 of file dbc_parser.cpp.

-

References DbcStatementDispatcher::Dispatch().

+

References DbcStatementDispatcher::Dispatch().

Referenced by ParseFile().

diff --git a/docs/html/classDbcSignalHandler.html b/docs/html/classDbcSignalHandler.html index 00a94e2..f071ef5 100644 --- a/docs/html/classDbcSignalHandler.html +++ b/docs/html/classDbcSignalHandler.html @@ -149,7 +149,7 @@ Public Member Functions

Implements DbcStatementHandler.

-

Definition at line 352 of file dbc_statement_handler.cpp.

+

Definition at line 353 of file dbc_statement_handler.cpp.

diff --git a/docs/html/classDbcStatementDispatcher.html b/docs/html/classDbcStatementDispatcher.html index 65672b4..00d2224 100644 --- a/docs/html/classDbcStatementDispatcher.html +++ b/docs/html/classDbcStatementDispatcher.html @@ -127,7 +127,7 @@ Public Member Functions
-

Definition at line 374 of file dbc_statement_handler.cpp.

+

Definition at line 371 of file dbc_statement_handler.cpp.

@@ -189,7 +189,7 @@ template<class Handler >

Returns false for an unregistered keyword; input must be trimmed.

-

Definition at line 391 of file dbc_statement_handler.cpp.

+

Definition at line 388 of file dbc_statement_handler.cpp.

Referenced by DbcParser::ParseFileWithDiagnostics().

diff --git a/docs/html/classDbcUnsupportedMultiplexingHandler.html b/docs/html/classDbcUnsupportedMultiplexingHandler.html index 2ffee48..55053a8 100644 --- a/docs/html/classDbcUnsupportedMultiplexingHandler.html +++ b/docs/html/classDbcUnsupportedMultiplexingHandler.html @@ -149,7 +149,7 @@ Public Member Functions

Implements DbcStatementHandler.

-

Definition at line 452 of file dbc_statement_handler.cpp.

+

Definition at line 449 of file dbc_statement_handler.cpp.

diff --git a/docs/html/classDbcValueDescriptionHandler.html b/docs/html/classDbcValueDescriptionHandler.html index 4ced64b..d9168bb 100644 --- a/docs/html/classDbcValueDescriptionHandler.html +++ b/docs/html/classDbcValueDescriptionHandler.html @@ -149,7 +149,7 @@ Public Member Functions

Implements DbcStatementHandler.

-

Definition at line 403 of file dbc_statement_handler.cpp.

+

Definition at line 400 of file dbc_statement_handler.cpp.

diff --git a/docs/html/classDecodeDatabase-members.html b/docs/html/classDecodeDatabase-members.html index 9e7dc4f..1ae36f9 100644 --- a/docs/html/classDecodeDatabase-members.html +++ b/docs/html/classDecodeDatabase-members.html @@ -97,7 +97,7 @@ $(document).ready(function(){initNavTree('classDecodeDatabase.html',''); initRes

This is the complete list of members for DecodeDatabase, including all inherited members.

- + diff --git a/docs/html/classDecodeDatabase.html b/docs/html/classDecodeDatabase.html index 8fdd0c9..ad643ce 100644 --- a/docs/html/classDecodeDatabase.html +++ b/docs/html/classDecodeDatabase.html @@ -105,8 +105,8 @@ $(document).ready(function(){initNavTree('classDecodeDatabase.html',''); initRes
DecodeDatabase()=default (defined in DecodeDatabase)DecodeDatabase
DecodeDatabase(std::vector< DecodeFrame > frames)DecodeDatabaseexplicit
DecodeDatabase(const std::vector< DecodeFrame > &frames)DecodeDatabaseexplicit
FindFrame(const FrameKey &key) constDecodeDatabase
FindSignal(const SignalKey &key) const (defined in DecodeDatabase)DecodeDatabase
GetFrames() constDecodeDatabase
- - + + @@ -119,8 +119,8 @@ Public Member Functions

Definition at line 87 of file decode_database.h.

Constructor & Destructor Documentation

- -

◆ DecodeDatabase()

+ +

◆ DecodeDatabase()

@@ -131,7 +131,7 @@ Public Member Functions
- + @@ -142,14 +142,14 @@ Public Member Functions

Public Member Functions

 DecodeDatabase (std::vector< DecodeFrame > frames)
 
 DecodeDatabase (const std::vector< DecodeFrame > &frames)
 
const std::vector< DecodeFrame > & GetFrames () const
 
std::shared_ptr< const DecodeFrame > FindFrame (const FrameKey &key) const
DecodeDatabase::DecodeDatabase (std::vector< DecodeFrame > const std::vector< DecodeFrame > &  frames)
-

Freeze definitions and build the frame index. Do not retain mutable aliases into moved input vectors.

Exceptions
+

Copy and validate definitions before building the frame index. Input aliases cannot modify the snapshot, even for rvalue input.

Exceptions
- +
std::invalid_argumentfor duplicate frame keys or signal names.
std::invalid_argumentfor invalid metadata or duplicate keys.
-

Definition at line 24 of file decode_database.cpp.

+

Definition at line 101 of file decode_database.cpp.

@@ -170,7 +170,7 @@ Public Member Functions

Borrowed view, valid while this snapshot remains alive and unchanged.

-

Definition at line 28 of file decode_database.cpp.

+

Definition at line 105 of file decode_database.cpp.

@@ -191,9 +191,9 @@ Public Member Functions

Owning handles retain the entire snapshot across reload or destruction.

-

Definition at line 33 of file decode_database.cpp.

+

Definition at line 110 of file decode_database.cpp.

-

Referenced by DbcDecoder::FindFrame().

+

Referenced by DbcDecoder::FindFrame().

@@ -213,7 +213,7 @@ Public Member Functions
-

Definition at line 43 of file decode_database.cpp.

+

Definition at line 120 of file decode_database.cpp.

diff --git a/docs/html/classDecodeDatabase.js b/docs/html/classDecodeDatabase.js index 33aac06..37b8471 100644 --- a/docs/html/classDecodeDatabase.js +++ b/docs/html/classDecodeDatabase.js @@ -1,6 +1,6 @@ var classDecodeDatabase = [ - [ "DecodeDatabase", "classDecodeDatabase.html#aa935a3a0d334f932d6a8c89a3818286a", null ], + [ "DecodeDatabase", "classDecodeDatabase.html#a51a44cf7c3f23c98ec8781afe0097c0c", null ], [ "GetFrames", "classDecodeDatabase.html#aa034c1510984a85f60ad6c0bd744d6e8", null ], [ "FindFrame", "classDecodeDatabase.html#a780d6e747a48b10a5eb0ab85448d2970", null ] ]; \ No newline at end of file diff --git a/docs/html/dbc__coverage__test_8cpp_source.html b/docs/html/dbc__coverage__test_8cpp_source.html index c9bd1a8..b23af99 100644 --- a/docs/html/dbc__coverage__test_8cpp_source.html +++ b/docs/html/dbc__coverage__test_8cpp_source.html @@ -204,661 +204,654 @@ $(document).ready(function(){initNavTree('dbc__coverage__test_8cpp_source.html',
104 QVERIFY(absent.definition == nullptr);
105 QVERIFY(absent.signals.empty());
106 DecodeSignal signal;
-
107 DecodeDatabase database = SingleSignal(signal);
-
108 QVERIFY(decoder.FindFrame(database, 18U, false) == nullptr);
-
109 QVERIFY(decoder.FindFrame(database, 17U, true) == nullptr);
-
110 raw.isExtended = true;
-
111 QVERIFY(!decoder.Decode(database, raw).valid);
-
112 raw.isExtended = false;
-
113 raw.canId = 18U;
-
114 QVERIFY(!decoder.Decode(database, raw).valid);
-
115 raw.canId = 17U;
-
116 DecodeFrame noSignals;
-
117 noSignals.canId = 17U;
-
118 database = DecodeDatabase({noSignals});
-
119 const DecodedFrameValue matched = decoder.Decode(database, raw);
-
120 QVERIFY(matched.valid);
-
121 QCOMPARE(matched.definition.get(), &database.GetFrames()[0]);
-
122 QVERIFY(matched.signals.empty());
-
123}
-
124
-
125void CoverageTest::signedBoundaries_data() {
-
126 QTest::addColumn<int>("order");
-
127 QTest::addColumn<unsigned>("length");
-
128 QTest::addColumn<qlonglong>("expected");
-
129 for (int order = 0; order < 2; ++order) {
-
130 for (unsigned length: {1U, 2U, 7U, 8U, 31U, 32U, 63U}) {
-
131 const std::int64_t half = std::int64_t(1) << (length - 1U);
-
132 const std::array<std::int64_t, 4> values{{0, -1, -half, half - 1}};
-
133 for (unsigned index = 0; index < values.size(); ++index) {
-
134 const QByteArray name = QByteArray::number(order) + "-" + QByteArray::number(length) + "-" + QByteArray::number(index);
-
135 QTest::newRow(name.constData()) << order << length << qlonglong(values[index]);
-
136 }
-
137 }
-
138 }
-
139}
-
140
-
141void CoverageTest::signedBoundaries() {
-
142 QFETCH(int, order);
-
143 QFETCH(unsigned, length);
-
144 QFETCH(qlonglong, expected);
-
145 DecodeSignal signal;
-
146 signal.byteOrder = order == 0 ? ByteOrder::Intel : ByteOrder::Motorola;
-
147 signal.startBit = order == 0 ? 0U : 7U;
-
148 signal.length = length;
-
149 signal.valueType = ValueType::Signed;
-
150 RawCanFrame raw;
-
151 raw.canId = 17U;
-
152 raw.data.assign(8U, 0U);
-
153 const std::uint64_t bits = static_cast<std::uint64_t>(expected);
-
154 for (unsigned i = 0; i < length; ++i) {
-
155 const unsigned weight = order == 0 ? i : length - 1U - i;
-
156 const unsigned position = order == 0 ? i % 8U : 7U - i % 8U;
-
157 raw.data[i / 8U] |= static_cast<std::uint8_t>(((bits >> weight) & 1U) << position);
-
158 }
-
159 const DecodeDatabase database = SingleSignal(signal);
-
160 const DecodedFrameValue result = DbcDecoder().Decode(database, raw);
-
161 QVERIFY(result.signals[0].valid);
-
162 QVERIFY(std::holds_alternative<std::int64_t>(result.signals[0].rawValue));
-
163 QCOMPARE(std::get<std::int64_t>(result.signals[0].rawValue), static_cast<std::int64_t>(expected));
-
164}
-
165
-
166void CoverageTest::extraction_data() {
-
167 QTest::addColumn<int>("order");
-
168 QTest::addColumn<unsigned>("start");
-
169 QTest::addColumn<unsigned>("length");
-
170 for (int order = 0; order < 2; ++order)
-
171 for (unsigned start = 0; start < 16U; ++start)
-
172 for (unsigned length = 1; length <= 64U; ++length) {
-
173 const QByteArray name = QByteArray::number(order) + "-" + QByteArray::number(start) + "-" + QByteArray::number(length);
-
174 QTest::newRow(name.constData()) << order << start << length;
-
175 }
-
176}
-
177
-
178void CoverageTest::extraction() {
-
179 QFETCH(int, order);
-
180 QFETCH(unsigned, start);
-
181 QFETCH(unsigned, length);
-
182 DecodeSignal signal;
-
183 signal.byteOrder = order == 0 ? ByteOrder::Intel : ByteOrder::Motorola;
-
184 signal.startBit = start;
-
185 signal.length = length;
-
186 const DecodeDatabase database = SingleSignal(signal);
-
187 RawCanFrame raw;
-
188 raw.canId = 17U;
-
189 const std::vector<std::vector<std::uint8_t>> patterns = {
-
190 std::vector<std::uint8_t>(10U, 0U),
-
191 std::vector<std::uint8_t>(10U, 255U),
-
192 {0xA5, 0x3C, 0x81, 0x7E, 0x12, 0xD4, 0x69, 0xB0, 0x5A, 0xC3}};
-
193 for (const std::vector<std::uint8_t> &data: patterns) {
-
194 raw.data = data;
-
195 const unsigned firstCoordinate = order == 0 ? start : (start / 8U) * 8U + 7U - start % 8U;
-
196 raw.data.resize((firstCoordinate + length + 7U) / 8U);
-
197 const DecodedFrameValue decoded = DbcDecoder().Decode(database, raw);
-
198 QVERIFY(decoded.signals[0].valid);
-
199 QCOMPARE(std::get<std::uint64_t>(decoded.signals[0].rawValue), ReferenceExtract(data, signal.byteOrder, start, length));
-
200 }
-
201}
-
202
-
203void CoverageTest::invalidSignalStatus_data() {
-
204 QTest::addColumn<int>("order");
-
205 QTest::addColumn<unsigned>("start");
-
206 QTest::addColumn<unsigned>("length");
-
207 QTest::addColumn<unsigned>("bytes");
-
208 QTest::addColumn<int>("status");
-
209 for (int order = 0; order < 2; ++order) {
-
210 const unsigned start = order == 0 ? 0U : 7U;
-
211 const QByteArray prefix = QByteArray::number(order);
-
212 QTest::newRow((prefix + "-zero").constData()) << order << start << 0U << 1U << int(SignalDecodeStatus::InvalidDefinition);
-
213 QTest::newRow((prefix + "-65").constData()) << order << start << 65U << 9U << int(SignalDecodeStatus::InvalidDefinition);
-
214 QTest::newRow((prefix + "-empty").constData()) << order << start << 8U << 0U << int(SignalDecodeStatus::InsufficientPayload);
-
215 QTest::newRow((prefix + "-short").constData()) << order << start << 16U << 1U << int(SignalDecodeStatus::InsufficientPayload);
-
216 QTest::newRow((prefix + "-large-start").constData()) << order << std::numeric_limits<unsigned>::max() << 8U << 1U << int(SignalDecodeStatus::InvalidDefinition);
-
217 }
-
218}
-
219
-
220void CoverageTest::invalidSignalStatus() {
-
221 QFETCH(int, order);
-
222 QFETCH(unsigned, start);
-
223 QFETCH(unsigned, length);
-
224 QFETCH(unsigned, bytes);
-
225 QFETCH(int, status);
-
226 DecodeSignal signal;
-
227 signal.startBit = start;
-
228 signal.length = length;
-
229 signal.byteOrder = order == 0 ? ByteOrder::Intel : ByteOrder::Motorola;
-
230 const DecodeDatabase database = SingleSignal(signal);
-
231 RawCanFrame raw;
-
232 raw.canId = 17U;
-
233 raw.data.assign(bytes, 255U);
-
234 const DecodedFrameValue decoded = DbcDecoder().Decode(database, raw);
-
235 QVERIFY(decoded.valid);
-
236 QVERIFY(!decoded.signals[0].valid);
-
237 QCOMPARE(int(decoded.signals[0].status), status);
-
238 QVERIFY(decoded.signals[0].GetValueDescription() == nullptr);
-
239}
-
240
-
241void CoverageTest::malformedRuntimeMultiplexing_data() {
-
242 QTest::addColumn<int>("kind");
-
243 QTest::newRow("two-selectors") << 0;
-
244 QTest::newRow("signed-selector") << 1;
-
245 QTest::newRow("65-bit-selector") << 2;
-
246 QTest::newRow("branch-out-of-range") << 3;
-
247}
-
248
-
249void CoverageTest::malformedRuntimeMultiplexing() {
-
250 QFETCH(int, kind);
-
251 DecodeSignal selector;
-
252 selector.length = 1U;
-
253 selector.multiplexing.role = MultiplexRole::Selector;
-
254 selector.name = "Selector";
-
255 std::vector<DecodeFrame> definitions = SingleSignal(selector).GetFrames();
-
256 DecodeSignal branch;
-
257 branch.name = "Branch";
-
258 branch.startBit = 8U;
-
259 branch.length = 8U;
-
260 branch.multiplexing.role = MultiplexRole::Branch;
-
261 branch.multiplexing.value = 1U;
-
262 definitions[0].signals.push_back(branch);
-
263 DecodeSignal ordinary;
-
264 ordinary.name = "Ordinary";
-
265 ordinary.startBit = 16U;
-
266 ordinary.length = 8U;
-
267 definitions[0].signals.push_back(ordinary);
-
268 if (kind == 0) {
-
269 selector.name = "SecondSelector";
-
270 definitions[0].signals.push_back(selector);
-
271 }
-
272 if (kind == 1) definitions[0].signals[0].valueType = ValueType::Signed;
-
273 if (kind == 2) definitions[0].signals[0].length = 65U;
-
274 if (kind == 3) definitions[0].signals[1].multiplexing.value = 2U;
-
275 const DecodeDatabase database(std::move(definitions));
-
276 RawCanFrame raw;
-
277 raw.canId = 17U;
-
278 raw.data = {1U, 42U, 99U};
-
279 const DecodedFrameValue result = DbcDecoder().Decode(database, raw);
-
280 QCOMPARE(int(result.signals[1].status), int(SignalDecodeStatus::InvalidDefinition));
-
281 QVERIFY(!result.signals[1].valid);
-
282 QVERIFY(result.signals[2].valid);
-
283 QCOMPARE(std::get<std::uint64_t>(result.signals[2].rawValue), std::uint64_t(99));
-
284 if (kind != 3) {
-
285 QCOMPARE(int(result.signals[0].status), int(SignalDecodeStatus::InvalidDefinition));
-
286 if (kind == 0) QCOMPARE(int(result.signals[3].status), int(SignalDecodeStatus::InvalidDefinition));
-
287 }
-
288}
-
289
-
290void CoverageTest::pgn_data() {
-
291 QTest::addColumn<unsigned>("id");
-
292 QTest::addColumn<unsigned>("expected");
-
293 QTest::addColumn<bool>("hasPgn");
-
294 QTest::newRow("standard") << 0x7FFU << 0U << false;
-
295 for (unsigned dp: {0U, 1U})
-
296 for (unsigned pf: {239U, 240U})
-
297 for (unsigned ps: {0U, 0xABU}) {
-
298 const unsigned id = 0x80000000U | (dp << 24U) | (pf << 16U) | (ps << 8U) | 0x42U;
-
299 const unsigned expected = (dp << 16U) | (pf == 239U ? 0xEF00U : 0xF000U | ps);
-
300 const QByteArray name = QByteArray::number(dp) + "-" + QByteArray::number(pf) + "-" + QByteArray::number(ps);
-
301 QTest::newRow(name.constData()) << id << expected << true;
-
302 }
-
303}
-
304
-
305void CoverageTest::pgn() {
-
306 QFETCH(unsigned, id);
-
307 QFETCH(unsigned, expected);
-
308 QFETCH(bool, hasPgn);
-
309 const DbcDatabase parsed = ParseText("BO_ " + std::to_string(id) + " Frame: 8 ECU\n");
-
310 QCOMPARE(parsed.frames[0].hasPgn, hasPgn);
-
311 QCOMPARE(parsed.frames[0].pgn, expected);
-
312}
-
313
-
314void CoverageTest::comments_data() {
-
315 QTest::addColumn<QString>("before");
-
316 QTest::addColumn<QString>("after");
-
317 QTest::addColumn<QString>("frameComment");
-
318 QTest::addColumn<QString>("signalComment");
-
319 QTest::newRow("tabs") << QString::fromUtf8("") << QString::fromUtf8("CM_\tBO_ 1 \"Frame\";\nCM_\tSG_ 1 State \"Signal\";\n") << QString::fromUtf8("Frame") << QString::fromUtf8("Signal");
-
320 QTest::newRow("empty-overwrite") << QString::fromUtf8("") << QString::fromUtf8("CM_ BO_ 1 \"Old\";\nCM_ BO_ 1 \"\";\nCM_ SG_ 1 State \"Old\";\nCM_ SG_ 1 State \"\";\n") << QString::fromUtf8("") << QString::fromUtf8("");
-
321 QTest::newRow("last-wins") << QString::fromUtf8("") << QString::fromUtf8("CM_ BO_ 1 \"First\";\nCM_ BO_ 1 \"Last\";\nCM_ SG_ 1 State \"First\";\nCM_ SG_ 1 State \"Last\";\n") << QString::fromUtf8("Last") << QString::fromUtf8("Last");
-
322 QTest::newRow("missing-targets") << QString::fromUtf8("") << QString::fromUtf8("CM_ BO_ 2 \"Unknown\";\nCM_ SG_ 2 State \"Unknown\";\nCM_ SG_ 1 Missing \"Unknown\";\n") << QString::fromUtf8("") << QString::fromUtf8("");
-
323 // Current documented limitation: comments resolve only already parsed targets.
-
324 QTest::newRow("before-definitions") << QString::fromUtf8("CM_ BO_ 1 \"Early\";\nCM_ SG_ 1 State \"Early\";\n") << QString::fromUtf8("") << QString::fromUtf8("") << QString::fromUtf8("");
-
325 QTest::newRow("missing-quotes") << QString::fromUtf8("") << QString::fromUtf8("CM_ BO_ 1 Missing;\nCM_ SG_ 1 State \"Unclosed;\n") << QString::fromUtf8("") << QString::fromUtf8("");
-
326}
-
327
-
328void CoverageTest::comments() {
-
329 QFETCH(QString, before);
-
330 QFETCH(QString, after);
-
331 QFETCH(QString, frameComment);
-
332 QFETCH(QString, signalComment);
-
333 const DbcDatabase parsed = ParseText(before.toStdString() + "BO_ 1 Frame: 1 ECU\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n" + after.toStdString());
-
334 QCOMPARE(parsed.frames[0].comment, frameComment.toStdString());
-
335 QCOMPARE(parsed.frames[0].signals[0].comment, signalComment.toStdString());
-
336}
-
337
-
338void CoverageTest::ignoredCommentStatements_data() {
-
339 QTest::addColumn<QString>("statement");
-
340 QTest::newRow("empty") << QString();
-
341 QTest::newRow("wrong-keyword") << QStringLiteral("OTHER_ BO_ 1 \"Ignored\";");
-
342 QTest::newRow("keyword-prefix") << QStringLiteral("CM_OTHER BO_ 1 \"Ignored\";");
-
343 QTest::newRow("frame-without-cm") << QStringLiteral("BO_ 1 \"Ignored\";");
-
344 QTest::newRow("missing-subtype") << QStringLiteral("CM_");
-
345 QTest::newRow("global-comment") << QStringLiteral("CM_ \"Ignored\";");
-
346 QTest::newRow("node-comment") << QStringLiteral("CM_ BU_ ECU \"Ignored\";");
-
347 QTest::newRow("environment-comment") << QStringLiteral("CM_ EV_ State \"Ignored\";");
-
348 QTest::newRow("unknown-subtype") << QStringLiteral("CM_ OTHER_ 1 \"Ignored\";");
-
349}
-
350
-
351void CoverageTest::ignoredCommentStatements() {
-
352 QFETCH(QString, statement);
-
353 DbcDatabase database;
-
354 FrameInfo frame;
-
355 frame.canId = 1U;
-
356 frame.name = "Frame";
-
357 frame.comment = "Existing frame comment";
-
358 SignalInfo signal;
-
359 signal.name = "State";
-
360 signal.comment = "Existing signal comment";
-
361 frame.signals.push_back(signal);
-
362 database.frames.push_back(frame);
-
363 DbcParseContext context{database, &database.frames[0]};
-
364
-
365 // The handler is public: callers may bypass keyword dispatch. Ignored input
-
366 // must preserve existing metadata, including comments already assigned.
-
367 DbcCommentHandler().Handle(statement.toStdString(), context);
-
368 QCOMPARE(database.frames.size(), std::size_t(1));
-
369 QCOMPARE(database.frames[0].canId, 1U);
-
370 QCOMPARE(database.frames[0].name, frame.name);
-
371 QCOMPARE(database.frames[0].comment, frame.comment);
-
372 QCOMPARE(database.frames[0].signals.size(), std::size_t(1));
-
373 QCOMPARE(database.frames[0].signals[0].name, signal.name);
-
374 QCOMPARE(database.frames[0].signals[0].comment, signal.comment);
-
375 QCOMPARE(context.currentFrame, &database.frames[0]);
-
376}
-
377
-
378void CoverageTest::ambiguousValueReferences_data() {
-
379 QTest::addColumn<bool>("duplicateFrames");
-
380 QTest::addColumn<bool>("extended");
-
381 QTest::newRow("duplicate-standard-frames") << true << false;
-
382 QTest::newRow("duplicate-extended-frames") << true << true;
-
383 QTest::newRow("duplicate-signals-in-standard-frame") << false << false;
-
384 QTest::newRow("duplicate-signals-in-extended-frame") << false << true;
-
385}
-
386
-
387void CoverageTest::ambiguousValueReferences() {
-
388 QFETCH(bool, duplicateFrames);
-
389 QFETCH(bool, extended);
-
390 DbcDatabase database;
-
391 FrameInfo frame;
-
392 frame.canId = 1U;
-
393 frame.name = "Frame";
-
394 frame.isExtended = extended;
-
395 SignalInfo signal;
-
396 signal.name = "State";
-
397 frame.signals.push_back(signal);
-
398 if (!duplicateFrames) frame.signals.push_back(signal);
-
399 database.frames.push_back(frame);
-
400 if (duplicateFrames) database.frames.push_back(frame);
-
401
-
402 // ParseFile rejects duplicate definitions earlier. Construct the database
-
403 // through the public metadata API to exercise the resolver's own checks.
-
404 DbcParseContext context{database, nullptr};
-
405 context.lineNumber = 37U;
-
406 const std::string rawId = extended ? "2147483649" : "1";
-
407 DbcValueDescriptionHandler().Handle("VAL_ " + rawId + " State 0 \"Off\";", context);
-
408 QCOMPARE(context.pendingValues.size(), std::size_t(1));
-
409 QCOMPARE(context.pendingValues[0].lineNumber, std::size_t(37));
-
410 const std::string path = "manual database.dbc";
-
411 try {
-
412 context.ResolveValueDescriptions(path);
-
413 QFAIL("The resolver must reject an ambiguous target");
-
414 } catch (const std::runtime_error &error) {
-
415 const std::string message = duplicateFrames ? "Ambiguous VAL_ frame reference."
-
416 : "Ambiguous VAL_ signal reference: State";
-
417 QCOMPARE(std::string(error.what()), path + ":37: " + message);
-
418 }
-
419 // Ambiguity must be detected before applying any descriptions.
-
420 for (const FrameInfo &candidate: database.frames)
-
421 for (const SignalInfo &candidateSignal: candidate.signals)
-
422 QVERIFY(candidateSignal.valueDescriptions.empty());
-
423}
-
424
-
425void CoverageTest::valueReferenceScope() {
-
426 DbcDatabase database;
-
427 FrameInfo frame;
-
428 frame.canId = 1U;
-
429 SignalInfo signal;
-
430 signal.name = "State";
-
431 frame.signals.push_back(signal);
-
432 database.frames.push_back(frame);
-
433 frame.isExtended = true;
-
434 database.frames.push_back(frame);
-
435 frame.canId = 2U;
-
436 frame.isExtended = false;
-
437 database.frames.push_back(frame);
-
438
-
439 DbcParseContext context{database, nullptr};
-
440 const DbcValueDescriptionHandler handler;
-
441 context.lineNumber = 3U;
-
442 handler.Handle("VAL_ 1 State 0 \"Standard\";", context);
-
443 context.lineNumber = 7U;
-
444 handler.Handle("VAL_ 2147483649 State 0 \"Extended\";", context);
-
445 context.ResolveValueDescriptions("manual database.dbc");
-
446
-
447 const std::string *standard = FindValueDescription(database.frames[0].signals[0].valueDescriptions, std::uint64_t(0));
-
448 const std::string *extended = FindValueDescription(database.frames[1].signals[0].valueDescriptions, std::uint64_t(0));
-
449 QVERIFY(standard != nullptr);
-
450 QVERIFY(extended != nullptr);
-
451 QCOMPARE(*standard, std::string("Standard"));
-
452 QCOMPARE(*extended, std::string("Extended"));
-
453 QVERIFY(database.frames[2].signals[0].valueDescriptions.empty());
-
454}
-
455
-
456void CoverageTest::fileFormatting_data() {
-
457 QTest::addColumn<QString>("text");
-
458 QTest::addColumn<unsigned>("frames");
-
459 QTest::newRow("empty") << QString::fromUtf8("") << 0U;
-
460 QTest::newRow("whitespace") << QString::fromUtf8(" \t\r\n\n\t") << 0U;
-
461 QTest::newRow("unknown-only") << QString::fromUtf8("VERSION \"test\"\nBU_: ECU\nUNKNOWN_ data\n") << 0U;
-
462 QTest::newRow("no-final-newline") << QString::fromUtf8("BO_ 1 Frame: 1 ECU\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU") << 1U;
-
463 QTest::newRow("crlf") << QString::fromUtf8("\r\nBO_ 1 Frame: 1 ECU\r\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\r\nCM_ BO_ 1 \"Comment\";\r\nVAL_ 1 State 0 \"Off\";\r\n") << 1U;
-
464}
-
465
-
466void CoverageTest::fileFormatting() {
-
467 QFETCH(QString, text);
-
468 QFETCH(unsigned, frames);
-
469 const DbcDatabase parsed = ParseText(text.toStdString());
-
470 QCOMPARE(parsed.frames.size(), std::size_t(frames));
-
471 if (frames) {
-
472 QCOMPARE(parsed.frames[0].signals.size(), std::size_t(1));
-
473 if (text.contains("\r\nBO_")) {
-
474 QCOMPARE(parsed.frames[0].comment, std::string("Comment"));
-
475 const std::string *description = FindValueDescription(parsed.frames[0].signals[0].valueDescriptions, std::uint64_t(0));
-
476 QVERIFY(description != nullptr);
-
477 QCOMPARE(*description, std::string("Off"));
-
478 }
-
479 }
-
480}
-
481
-
482void CoverageTest::missingFile() {
-
483 QTemporaryFile file;
-
484 QVERIFY(file.open());
-
485 const QString path = file.fileName();
-
486 QVERIFY(file.remove());
-
487 try {
-
488 DbcParser().ParseFile(path.toStdString());
-
489 QFAIL("Missing file must throw");
-
490 } catch (const std::runtime_error &error) {
-
491 QCOMPARE(std::string(error.what()), "Failed to open DBC file: " + path.toStdString());
-
492 }
-
493}
-
494
-
495void CoverageTest::builders() {
-
496 const DecodeDatabase emptyRuntime = DbcDecodeBuilder().Build(DbcDatabase());
-
497 QVERIFY(emptyRuntime.GetFrames().empty());
-
498 QVERIFY(!emptyRuntime.FindFrame(FrameKey(0U, false)));
-
499 const std::unique_ptr<TreeNode> emptyTree = DbcTreeBuilder().Build(DbcDatabase());
-
500 QCOMPARE(emptyTree->GetType(), NodeType::Root);
-
501 QCOMPARE(emptyTree->GetName(), std::string("dbc"));
-
502 QCOMPARE(emptyTree->GetChildCount(), std::size_t(0));
-
503 QVERIFY(emptyTree->GetFrame() == nullptr && emptyTree->GetSignal() == nullptr);
-
504 const DbcDatabase expected = ParseText(
-
505 "BO_ 256 Standard: 3 ECU1\n"
-
506 "SG_ Mode M : 0|8@1+ (2,3) [0|513] \"mode\" ECU1,ECU2\n"
-
507 "SG_ Branch m1 : 15|8@0- (-2,5) [-249|261] \"rpm\" ECU2,ECU3\n"
-
508 "SG_ Always : 16|8@1+ (0.25,-1) [-1|62.75] \"V\" ECU3\n"
-
509 "CM_ BO_ 256 \"Frame comment\";\nCM_ SG_ 256 Branch \"Branch comment\";\n"
-
510 "VAL_ 256 Branch -1 \"Negative\" 1 \"Positive\";\n"
-
511 "BO_ 2163212866 Extended: 8 ECU4\n");
-
512 DecodeDatabase runtime;
-
513 std::unique_ptr<TreeNode> tree;
-
514 {
-
515 DbcDatabase source = expected;
-
516 runtime = DbcDecodeBuilder().Build(source);
-
517 tree = DbcTreeBuilder().Build(source);
-
518 source.frames[0].name = "Changed";
-
519 source.frames[0].signals[1].receivers.clear();
-
520 source.frames[0].signals[1].valueDescriptions.clear();
-
521 source.frames.clear();
-
522 }
-
523 QCOMPARE(runtime.GetFrames().size(), expected.frames.size());
-
524 QCOMPARE(tree->GetChildCount(), expected.frames.size());
-
525 for (std::size_t i = 0; i < expected.frames.size(); ++i) {
-
526 const FrameInfo &frame = expected.frames[i];
-
527 const DecodeFrame &decodedFrame = runtime.GetFrames()[i];
-
528 const TreeNode *node = tree->GetChild(i);
-
529 QVERIFY(node && node->GetFrame());
-
530 QCOMPARE(node->GetType(), NodeType::Frame);
-
531 QCOMPARE(node->GetName(), frame.name);
-
532 QVERIFY(node->GetSignal() == nullptr);
-
533 QCOMPARE(runtime.FindFrame(FrameKey(frame.canId, frame.isExtended)).get(), &decodedFrame);
-
534 const FrameInfo &copied = *node->GetFrame();
-
535 QCOMPARE(copied.name, frame.name);
-
536 QCOMPARE(decodedFrame.name, frame.name);
-
537 QCOMPARE(copied.canId, frame.canId);
-
538 QCOMPARE(decodedFrame.canId, frame.canId);
-
539 QCOMPARE(copied.isExtended, frame.isExtended);
-
540 QCOMPARE(decodedFrame.isExtended, frame.isExtended);
-
541 QCOMPARE(copied.dlc, frame.dlc);
-
542 QCOMPARE(decodedFrame.dlc, frame.dlc);
-
543 QCOMPARE(copied.pgn, frame.pgn);
-
544 QCOMPARE(decodedFrame.pgn, frame.pgn);
-
545 QCOMPARE(copied.hasPgn, frame.hasPgn);
-
546 QCOMPARE(decodedFrame.hasPgn, frame.hasPgn);
-
547 QCOMPARE(copied.transmitter, frame.transmitter);
-
548 QCOMPARE(decodedFrame.transmitter, frame.transmitter);
-
549 QCOMPARE(copied.comment, frame.comment);
-
550 QCOMPARE(decodedFrame.comment, frame.comment);
-
551 QCOMPARE(node->GetChildCount(), frame.signals.size());
-
552 QCOMPARE(copied.signals.size(), frame.signals.size());
-
553 QCOMPARE(decodedFrame.signals.size(), frame.signals.size());
-
554 for (std::size_t j = 0; j < frame.signals.size(); ++j) {
-
555 const SignalInfo &original = frame.signals[j];
-
556 const DecodeSignal &target = decodedFrame.signals[j];
-
557 const TreeNode *leaf = node->GetChild(j);
-
558 QVERIFY(leaf && leaf->GetSignal());
-
559 QCOMPARE(leaf->GetType(), NodeType::Signal);
-
560 QCOMPARE(leaf->GetName(), original.name);
-
561 QVERIFY(leaf->GetFrame() == nullptr);
-
562 // Check both the frame payload copy and each child payload copy.
-
563 for (const SignalInfo *copy: {&copied.signals[j], leaf->GetSignal()}) {
-
564 QCOMPARE(copy->name, original.name);
-
565 QCOMPARE(copy->startBit, original.startBit);
-
566 QCOMPARE(copy->length, original.length);
-
567 QCOMPARE(copy->isLittleEndian, original.isLittleEndian);
-
568 QCOMPARE(copy->isSigned, original.isSigned);
-
569 QCOMPARE(copy->factor, original.factor);
-
570 QCOMPARE(copy->offset, original.offset);
-
571 QCOMPARE(copy->minimum, original.minimum);
-
572 QCOMPARE(copy->maximum, original.maximum);
-
573 QCOMPARE(copy->unit, original.unit);
-
574 QCOMPARE(copy->comment, original.comment);
-
575 QVERIFY(copy->receivers == original.receivers);
-
576 QVERIFY(copy->valueDescriptions == original.valueDescriptions);
-
577 QCOMPARE(copy->multiplexing.role, original.multiplexing.role);
-
578 QCOMPARE(copy->multiplexing.value, original.multiplexing.value);
-
579 }
-
580 QCOMPARE(target.name, original.name);
-
581 QCOMPARE(target.startBit, original.startBit);
-
582 QCOMPARE(target.length, original.length);
-
583 QCOMPARE(target.byteOrder, original.isLittleEndian ? ByteOrder::Intel : ByteOrder::Motorola);
-
584 QCOMPARE(target.valueType, original.isSigned ? ValueType::Signed : ValueType::Unsigned);
-
585 QCOMPARE(target.factor, original.factor);
-
586 QCOMPARE(target.offset, original.offset);
-
587 QCOMPARE(target.minimum, original.minimum);
-
588 QCOMPARE(target.maximum, original.maximum);
-
589 QCOMPARE(target.unit, original.unit);
-
590 QCOMPARE(target.comment, original.comment);
-
591 QVERIFY(target.receivers == original.receivers);
-
592 QVERIFY(target.valueDescriptions == original.valueDescriptions);
-
593 QCOMPARE(target.multiplexing.role, original.multiplexing.role);
-
594 QCOMPARE(target.multiplexing.value, original.multiplexing.value);
-
595 }
-
596 }
-
597}
-
598
-
599void CoverageTest::physicalConversion_data() {
-
600 QTest::addColumn<double>("factor");
-
601 QTest::addColumn<double>("offset");
-
602 QTest::addColumn<double>("expected");
-
603 QTest::newRow("zero-factor") << 0.0 << 7.0 << 7.0;
-
604 QTest::newRow("negative-factor") << -2.0 << 5.0 << -15.0;
-
605 QTest::newRow("above-declared-range") << 2.0 << 3.0 << 23.0;
-
606}
-
607
-
608void CoverageTest::physicalConversion() {
-
609 QFETCH(double, factor);
-
610 QFETCH(double, offset);
-
611 QFETCH(double, expected);
-
612 DecodeSignal signal;
-
613 signal.length = 8U;
-
614 signal.factor = factor;
-
615 signal.offset = offset;
-
616 signal.minimum = 0.0;
-
617 signal.maximum = 1.0;
-
618 const DecodeDatabase database = SingleSignal(signal);
-
619 RawCanFrame raw;
-
620 raw.canId = 17U;
-
621 raw.data = {10U};
-
622 const DecodedFrameValue decoded = DbcDecoder().Decode(database, raw);
-
623 QVERIFY(decoded.signals[0].valid);
-
624 QCOMPARE(std::get<std::uint64_t>(decoded.signals[0].rawValue), std::uint64_t(10));
-
625 QCOMPARE(decoded.signals[0].physicalValue, expected);
-
626}
-
627
-
628void CoverageTest::multiplexingScenarios() {
-
629 const DecodeDatabase selectorOnly = DbcDecodeBuilder().Build(ParseText(
-
630 "BO_ 1 Frame: 1 ECU\nSG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"));
-
631 RawCanFrame raw;
-
632 raw.canId = 1U;
-
633 raw.data = {1U};
-
634 const DecodedFrameValue only = DbcDecoder().Decode(selectorOnly, raw);
-
635 QCOMPARE(only.signals.size(), std::size_t(1));
-
636 QVERIFY(only.signals[0].valid);
-
637 const DecodeDatabase database = DbcDecodeBuilder().Build(ParseText(
-
638 "BO_ 1 First: 3 ECU\n"
-
639 "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
-
640 "SG_ A m0 : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
-
641 "SG_ B m0 : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
-
642 "BO_ 2 Second: 2 ECU\n"
-
643 "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
-
644 "SG_ A m1 : 8|8@1+ (1,0) [0|255] \"\" ECU\n"));
-
645 raw.data = {0U, 42U, 99U};
-
646 const DecodedFrameValue first = DbcDecoder().Decode(database, raw);
-
647 QVERIFY(first.signals[1].valid && first.signals[2].valid);
-
648 QCOMPARE(std::get<std::uint64_t>(first.signals[1].rawValue), std::uint64_t(42));
-
649 QCOMPARE(std::get<std::uint64_t>(first.signals[2].rawValue), std::uint64_t(99));
-
650 raw.canId = 2U;
-
651 const DecodedFrameValue secondInactive = DbcDecoder().Decode(database, raw);
-
652 QCOMPARE(secondInactive.signals[1].status, SignalDecodeStatus::Inactive);
-
653 raw.data[0] = 1U;
-
654 QVERIFY(DbcDecoder().Decode(database, raw).signals[1].valid);
-
655 raw.canId = 1U;
-
656 const DecodedFrameValue firstInactive = DbcDecoder().Decode(database, raw);
-
657 QCOMPARE(firstInactive.signals[1].status, SignalDecodeStatus::Inactive);
-
658 QCOMPARE(firstInactive.signals[2].status, SignalDecodeStatus::Inactive);
-
659}
-
660
-
661void CoverageTest::multiplexingErrorLocations_data() {
-
662 QTest::addColumn<QString>("text");
-
663 QTest::addColumn<unsigned>("line");
-
664 QTest::addColumn<QString>("message");
-
665 const QString header = "BO_ 1 Frame: 3 ECU\n\n";
-
666 const QString selector = "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n";
-
667 const QString branch = "SG_ A m1 : 8|8@1+ (1,0) [0|255] \"\" ECU\n";
-
668 QTest::newRow("missing-selector") << header + branch << 3U << QString::fromUtf8("Multiplexed branch has no selector.");
-
669 QTest::newRow("second-selector") << header + selector + "\nSG_ Other M : 16|1@1+ (1,0) [0|1] \"\" ECU\n"
-
670 << 5U << QString::fromUtf8("Multiplexing requires one unsigned selector per frame.");
-
671 QTest::newRow("signed-selector") << header + "SG_ Mode M : 0|1@1- (1,0) [-1|0] \"\" ECU\n"
-
672 << 3U << QString::fromUtf8("Multiplexing requires one unsigned selector per frame.");
-
673 QTest::newRow("branch-range") << header + "SG_ A m2 : 8|8@1+ (1,0) [0|255] \"\" ECU\n" + selector << 3U << QString::fromUtf8("Branch value exceeds selector raw range.");
-
674 QTest::newRow("same-branch-overlap") << header + selector + branch + "SG_ B m1 : 15|8@0+ (1,0) [0|255] \"\" ECU\n"
-
675 << 5U << QString::fromUtf8("Signals overlap without mutually exclusive multiplexing conditions.");
-
676 QTest::newRow("selector-overlap") << header + selector + "SG_ A m1 : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
-
677 << 4U << QString::fromUtf8("Signals overlap without mutually exclusive multiplexing conditions.");
-
678 QTest::newRow("unconditional-overlap") << header + selector + branch + "SG_ Always : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
-
679 << 5U << QString::fromUtf8("Signals overlap without mutually exclusive multiplexing conditions.");
-
680 QTest::newRow("ordinary-overlap") << header + "SG_ A : 8|8@1+ (1,0) [0|255] \"\" ECU\nSG_ B : 15|8@0+ (1,0) [0|255] \"\" ECU\n"
-
681 << 4U << QString::fromUtf8("Signals overlap without mutually exclusive multiplexing conditions.");
-
682 QTest::newRow("extended-multiplexing") << header + "SG_MUL_VAL_ 1 A Mode 1-2;\n"
-
683 << 3U << QString::fromUtf8("Extended and nested multiplexing are unsupported.");
-
684 QTest::newRow("nested-marker") << header + "SG_ A m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
-
685 << 3U << QString::fromUtf8("Invalid or unsupported multiplexing marker.");
-
686}
-
687
-
688void CoverageTest::multiplexingErrorLocations() {
-
689 QFETCH(QString, text);
-
690 QFETCH(unsigned, line);
-
691 QFETCH(QString, message);
-
692 QTemporaryFile file;
-
693 QVERIFY(file.open());
-
694 const QByteArray content = text.toUtf8();
-
695 QCOMPARE(file.write(content), qint64(content.size()));
-
696 QVERIFY(file.flush());
-
697 try {
-
698 DbcParser().ParseFile(file.fileName().toStdString());
-
699 QFAIL("Expected multiplexing rejection");
-
700 } catch (const std::runtime_error &error) {
-
701 QCOMPARE(QString::fromStdString(error.what()), file.fileName() + ":" + QString::number(line) + ": " + message);
-
702 }
-
703}
-
704
-
705void CoverageTest::escapedValueDescriptions() {
-
706 const DbcDatabase parsed = ParseText(
-
707 "BO_ 1 Frame: 1 ECU\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
-
708 R"dbc(VAL_ 1 State -0 "Ноль" 1 "C:\\trace" 2 "\\\"quoted\"\\";)dbc"
-
709 "\n");
-
710 const ValueDescriptions &values = parsed.frames[0].signals[0].valueDescriptions;
-
711 QCOMPARE(values.size(), std::size_t(3));
-
712 QVERIFY(values.find(RawSignalValue(std::int64_t(0))) == values.end());
-
713 const std::string *zero = FindValueDescription(values, std::int64_t(0));
-
714 const std::string *path = FindValueDescription(values, std::uint64_t(1));
-
715 const std::string *quoted = FindValueDescription(values, std::uint64_t(2));
-
716 QVERIFY(zero && path && quoted);
-
717 QCOMPARE(*zero, std::string("Ноль"));
-
718 QCOMPARE(*path, std::string("C:\\trace"));
-
719 QCOMPARE(*quoted, std::string("\\\"quoted\"\\"));
-
720 QVERIFY(NormalizeValueKey(RawSignalValue(std::int64_t(0))) == RawSignalValue(std::uint64_t(0)));
-
721 QVERIFY(NormalizeValueKey(RawSignalValue(std::int64_t(-1))) == RawSignalValue(std::int64_t(-1)));
-
722}
-
723
-
724void CoverageTest::factory() {
- -
726 QVERIFY(!factory.contains("value"));
-
727 QVERIFY_THROWS_EXCEPTION(std::out_of_range, static_cast<void>(factory.creator("missing")));
-
728 QVERIFY_THROWS_EXCEPTION(std::out_of_range, static_cast<void>(factory.create("missing", std::make_unique<int>(1))));
-
729 factory.registerType<FactoryFirst>("value");
-
730 QVERIFY(factory.contains("value"));
-
731 std::unique_ptr<int> argument = std::make_unique<int>(42);
-
732 const std::shared_ptr<FactoryBase> first = factory.create("value", std::move(argument));
-
733 QVERIFY(argument == nullptr);
-
734 QCOMPARE(first->Value(), 42);
-
735 const TypeFactory<std::string, FactoryBase, std::unique_ptr<int>>::CreatorFn creator = factory.creator("value");
-
736 QCOMPARE(creator(std::make_unique<int>(7))->Value(), 7);
-
737 factory.registerType<FactorySecond>("value");
-
738 QCOMPARE(factory.create("value", std::make_unique<int>(42))->Value(), 43);
-
739 QCOMPARE(first->Value(), 42);
-
740 QCOMPARE(creator(std::make_unique<int>(7))->Value(), 7);
-
741}
-
742
-
743int RunCoverageTests(int argc, char **argv) {
-
744 CoverageTest tests;
-
745 return QTest::qExec(&tests, argc, argv);
-
746}
-
747#include "dbc_coverage_test.moc"
+
107 signal.length = 8U;
+
108 DecodeDatabase database = SingleSignal(signal);
+
109 QVERIFY(decoder.FindFrame(database, 18U, false) == nullptr);
+
110 QVERIFY(decoder.FindFrame(database, 17U, true) == nullptr);
+
111 raw.isExtended = true;
+
112 QVERIFY(!decoder.Decode(database, raw).valid);
+
113 raw.isExtended = false;
+
114 raw.canId = 18U;
+
115 QVERIFY(!decoder.Decode(database, raw).valid);
+
116 raw.canId = 17U;
+
117 DecodeFrame noSignals;
+
118 noSignals.canId = 17U;
+
119 database = DecodeDatabase({noSignals});
+
120 const DecodedFrameValue matched = decoder.Decode(database, raw);
+
121 QVERIFY(matched.valid);
+
122 QCOMPARE(matched.definition.get(), &database.GetFrames()[0]);
+
123 QVERIFY(matched.signals.empty());
+
124}
+
125
+
126void CoverageTest::signedBoundaries_data() {
+
127 QTest::addColumn<int>("order");
+
128 QTest::addColumn<unsigned>("length");
+
129 QTest::addColumn<qlonglong>("expected");
+
130 for (int order = 0; order < 2; ++order) {
+
131 for (unsigned length: {1U, 2U, 7U, 8U, 31U, 32U, 63U}) {
+
132 const std::int64_t half = std::int64_t(1) << (length - 1U);
+
133 const std::array<std::int64_t, 4> values{{0, -1, -half, half - 1}};
+
134 for (unsigned index = 0; index < values.size(); ++index) {
+
135 const QByteArray name = QByteArray::number(order) + "-" + QByteArray::number(length) + "-" + QByteArray::number(index);
+
136 QTest::newRow(name.constData()) << order << length << qlonglong(values[index]);
+
137 }
+
138 }
+
139 }
+
140}
+
141
+
142void CoverageTest::signedBoundaries() {
+
143 QFETCH(int, order);
+
144 QFETCH(unsigned, length);
+
145 QFETCH(qlonglong, expected);
+
146 DecodeSignal signal;
+
147 signal.byteOrder = order == 0 ? ByteOrder::Intel : ByteOrder::Motorola;
+
148 signal.startBit = order == 0 ? 0U : 7U;
+
149 signal.length = length;
+
150 signal.valueType = ValueType::Signed;
+
151 RawCanFrame raw;
+
152 raw.canId = 17U;
+
153 raw.data.assign(8U, 0U);
+
154 const std::uint64_t bits = static_cast<std::uint64_t>(expected);
+
155 for (unsigned i = 0; i < length; ++i) {
+
156 const unsigned weight = order == 0 ? i : length - 1U - i;
+
157 const unsigned position = order == 0 ? i % 8U : 7U - i % 8U;
+
158 raw.data[i / 8U] |= static_cast<std::uint8_t>(((bits >> weight) & 1U) << position);
+
159 }
+
160 const DecodeDatabase database = SingleSignal(signal);
+
161 const DecodedFrameValue result = DbcDecoder().Decode(database, raw);
+
162 QVERIFY(result.signals[0].valid);
+
163 QVERIFY(std::holds_alternative<std::int64_t>(result.signals[0].rawValue));
+
164 QCOMPARE(std::get<std::int64_t>(result.signals[0].rawValue), static_cast<std::int64_t>(expected));
+
165}
+
166
+
167void CoverageTest::extraction_data() {
+
168 QTest::addColumn<int>("order");
+
169 QTest::addColumn<unsigned>("start");
+
170 QTest::addColumn<unsigned>("length");
+
171 for (int order = 0; order < 2; ++order)
+
172 for (unsigned start = 0; start < 16U; ++start)
+
173 for (unsigned length = 1; length <= 64U; ++length) {
+
174 const QByteArray name = QByteArray::number(order) + "-" + QByteArray::number(start) + "-" + QByteArray::number(length);
+
175 QTest::newRow(name.constData()) << order << start << length;
+
176 }
+
177}
+
178
+
179void CoverageTest::extraction() {
+
180 QFETCH(int, order);
+
181 QFETCH(unsigned, start);
+
182 QFETCH(unsigned, length);
+
183 DecodeSignal signal;
+
184 signal.byteOrder = order == 0 ? ByteOrder::Intel : ByteOrder::Motorola;
+
185 signal.startBit = start;
+
186 signal.length = length;
+
187 const DecodeDatabase database = SingleSignal(signal);
+
188 RawCanFrame raw;
+
189 raw.canId = 17U;
+
190 const std::vector<std::vector<std::uint8_t>> patterns = {
+
191 std::vector<std::uint8_t>(10U, 0U),
+
192 std::vector<std::uint8_t>(10U, 255U),
+
193 {0xA5, 0x3C, 0x81, 0x7E, 0x12, 0xD4, 0x69, 0xB0, 0x5A, 0xC3}};
+
194 for (const std::vector<std::uint8_t> &data: patterns) {
+
195 raw.data = data;
+
196 const unsigned firstCoordinate = order == 0 ? start : (start / 8U) * 8U + 7U - start % 8U;
+
197 raw.data.resize((firstCoordinate + length + 7U) / 8U);
+
198 const DecodedFrameValue decoded = DbcDecoder().Decode(database, raw);
+
199 QVERIFY(decoded.signals[0].valid);
+
200 QCOMPARE(std::get<std::uint64_t>(decoded.signals[0].rawValue), ReferenceExtract(data, signal.byteOrder, start, length));
+
201 }
+
202}
+
203
+
204void CoverageTest::invalidSignalStatus_data() {
+
205 QTest::addColumn<int>("order");
+
206 QTest::addColumn<unsigned>("start");
+
207 QTest::addColumn<unsigned>("length");
+
208 QTest::addColumn<unsigned>("bytes");
+
209 QTest::addColumn<int>("status");
+
210 for (int order = 0; order < 2; ++order) {
+
211 const unsigned start = order == 0 ? 0U : 7U;
+
212 const QByteArray prefix = QByteArray::number(order);
+
213 QTest::newRow((prefix + "-zero").constData()) << order << start << 0U << 1U << int(SignalDecodeStatus::InvalidDefinition);
+
214 QTest::newRow((prefix + "-65").constData()) << order << start << 65U << 9U << int(SignalDecodeStatus::InvalidDefinition);
+
215 QTest::newRow((prefix + "-empty").constData()) << order << start << 8U << 0U << int(SignalDecodeStatus::InsufficientPayload);
+
216 QTest::newRow((prefix + "-short").constData()) << order << start << 16U << 1U << int(SignalDecodeStatus::InsufficientPayload);
+
217 QTest::newRow((prefix + "-large-start").constData()) << order << std::numeric_limits<unsigned>::max() << 8U << 1U << int(SignalDecodeStatus::InvalidDefinition);
+
218 }
+
219}
+
220
+
221void CoverageTest::invalidSignalStatus() {
+
222 QFETCH(int, order);
+
223 QFETCH(unsigned, start);
+
224 QFETCH(unsigned, length);
+
225 QFETCH(unsigned, bytes);
+
226 QFETCH(int, status);
+
227 DecodeSignal signal;
+
228 signal.startBit = start;
+
229 signal.length = length;
+
230 signal.byteOrder = order == 0 ? ByteOrder::Intel : ByteOrder::Motorola;
+
231 if (status == int(SignalDecodeStatus::InvalidDefinition)) {
+
232 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) SingleSignal(signal));
+
233 return;
+
234 }
+
235 const DecodeDatabase database = SingleSignal(signal);
+
236 RawCanFrame raw;
+
237 raw.canId = 17U;
+
238 raw.data.assign(bytes, 255U);
+
239 const DecodedFrameValue decoded = DbcDecoder().Decode(database, raw);
+
240 QVERIFY(decoded.valid);
+
241 QVERIFY(!decoded.signals[0].valid);
+
242 QCOMPARE(int(decoded.signals[0].status), status);
+
243 QVERIFY(decoded.signals[0].GetValueDescription() == nullptr);
+
244}
+
245
+
246void CoverageTest::malformedRuntimeMultiplexing_data() {
+
247 QTest::addColumn<int>("kind");
+
248 QTest::newRow("two-selectors") << 0;
+
249 QTest::newRow("signed-selector") << 1;
+
250 QTest::newRow("65-bit-selector") << 2;
+
251 QTest::newRow("branch-out-of-range") << 3;
+
252}
+
253
+
254void CoverageTest::malformedRuntimeMultiplexing() {
+
255 QFETCH(int, kind);
+
256 DecodeSignal selector;
+
257 selector.length = 1U;
+
258 selector.multiplexing.role = MultiplexRole::Selector;
+
259 selector.name = "Selector";
+
260 std::vector<DecodeFrame> definitions = SingleSignal(selector).GetFrames();
+
261 DecodeSignal branch;
+
262 branch.name = "Branch";
+
263 branch.startBit = 8U;
+
264 branch.length = 8U;
+
265 branch.multiplexing.role = MultiplexRole::Branch;
+
266 branch.multiplexing.value = 1U;
+
267 definitions[0].signals.push_back(branch);
+
268 DecodeSignal ordinary;
+
269 ordinary.name = "Ordinary";
+
270 ordinary.startBit = 16U;
+
271 ordinary.length = 8U;
+
272 definitions[0].signals.push_back(ordinary);
+
273 if (kind == 0) {
+
274 selector.name = "SecondSelector";
+
275 definitions[0].signals.push_back(selector);
+
276 }
+
277 if (kind == 1) definitions[0].signals[0].valueType = ValueType::Signed;
+
278 if (kind == 2) definitions[0].signals[0].length = 65U;
+
279 if (kind == 3) definitions[0].signals[1].multiplexing.value = 2U;
+
280 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase(definitions));
+
281}
+
282
+
283void CoverageTest::pgn_data() {
+
284 QTest::addColumn<unsigned>("id");
+
285 QTest::addColumn<unsigned>("expected");
+
286 QTest::addColumn<bool>("hasPgn");
+
287 QTest::newRow("standard") << 0x7FFU << 0U << false;
+
288 for (unsigned dp: {0U, 1U})
+
289 for (unsigned pf: {239U, 240U})
+
290 for (unsigned ps: {0U, 0xABU}) {
+
291 const unsigned id = 0x80000000U | (dp << 24U) | (pf << 16U) | (ps << 8U) | 0x42U;
+
292 const unsigned expected = (dp << 16U) | (pf == 239U ? 0xEF00U : 0xF000U | ps);
+
293 const QByteArray name = QByteArray::number(dp) + "-" + QByteArray::number(pf) + "-" + QByteArray::number(ps);
+
294 QTest::newRow(name.constData()) << id << expected << true;
+
295 }
+
296}
+
297
+
298void CoverageTest::pgn() {
+
299 QFETCH(unsigned, id);
+
300 QFETCH(unsigned, expected);
+
301 QFETCH(bool, hasPgn);
+
302 const DbcDatabase parsed = ParseText("BO_ " + std::to_string(id) + " Frame: 8 ECU\n");
+
303 QCOMPARE(parsed.frames[0].hasPgn, hasPgn);
+
304 QCOMPARE(parsed.frames[0].pgn, expected);
+
305}
+
306
+
307void CoverageTest::comments_data() {
+
308 QTest::addColumn<QString>("before");
+
309 QTest::addColumn<QString>("after");
+
310 QTest::addColumn<QString>("frameComment");
+
311 QTest::addColumn<QString>("signalComment");
+
312 QTest::newRow("tabs") << QString::fromUtf8("") << QString::fromUtf8("CM_\tBO_ 1 \"Frame\";\nCM_\tSG_ 1 State \"Signal\";\n") << QString::fromUtf8("Frame") << QString::fromUtf8("Signal");
+
313 QTest::newRow("empty-overwrite") << QString::fromUtf8("") << QString::fromUtf8("CM_ BO_ 1 \"Old\";\nCM_ BO_ 1 \"\";\nCM_ SG_ 1 State \"Old\";\nCM_ SG_ 1 State \"\";\n") << QString::fromUtf8("") << QString::fromUtf8("");
+
314 QTest::newRow("last-wins") << QString::fromUtf8("") << QString::fromUtf8("CM_ BO_ 1 \"First\";\nCM_ BO_ 1 \"Last\";\nCM_ SG_ 1 State \"First\";\nCM_ SG_ 1 State \"Last\";\n") << QString::fromUtf8("Last") << QString::fromUtf8("Last");
+
315 QTest::newRow("missing-targets") << QString::fromUtf8("") << QString::fromUtf8("CM_ BO_ 2 \"Unknown\";\nCM_ SG_ 2 State \"Unknown\";\nCM_ SG_ 1 Missing \"Unknown\";\n") << QString::fromUtf8("") << QString::fromUtf8("");
+
316 // Current documented limitation: comments resolve only already parsed targets.
+
317 QTest::newRow("before-definitions") << QString::fromUtf8("CM_ BO_ 1 \"Early\";\nCM_ SG_ 1 State \"Early\";\n") << QString::fromUtf8("") << QString::fromUtf8("") << QString::fromUtf8("");
+
318 QTest::newRow("missing-quotes") << QString::fromUtf8("") << QString::fromUtf8("CM_ BO_ 1 Missing;\nCM_ SG_ 1 State \"Unclosed;\n") << QString::fromUtf8("") << QString::fromUtf8("");
+
319}
+
320
+
321void CoverageTest::comments() {
+
322 QFETCH(QString, before);
+
323 QFETCH(QString, after);
+
324 QFETCH(QString, frameComment);
+
325 QFETCH(QString, signalComment);
+
326 const DbcDatabase parsed = ParseText(before.toStdString() + "BO_ 1 Frame: 1 ECU\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n" + after.toStdString());
+
327 QCOMPARE(parsed.frames[0].comment, frameComment.toStdString());
+
328 QCOMPARE(parsed.frames[0].signals[0].comment, signalComment.toStdString());
+
329}
+
330
+
331void CoverageTest::ignoredCommentStatements_data() {
+
332 QTest::addColumn<QString>("statement");
+
333 QTest::newRow("empty") << QString();
+
334 QTest::newRow("wrong-keyword") << QStringLiteral("OTHER_ BO_ 1 \"Ignored\";");
+
335 QTest::newRow("keyword-prefix") << QStringLiteral("CM_OTHER BO_ 1 \"Ignored\";");
+
336 QTest::newRow("frame-without-cm") << QStringLiteral("BO_ 1 \"Ignored\";");
+
337 QTest::newRow("missing-subtype") << QStringLiteral("CM_");
+
338 QTest::newRow("global-comment") << QStringLiteral("CM_ \"Ignored\";");
+
339 QTest::newRow("node-comment") << QStringLiteral("CM_ BU_ ECU \"Ignored\";");
+
340 QTest::newRow("environment-comment") << QStringLiteral("CM_ EV_ State \"Ignored\";");
+
341 QTest::newRow("unknown-subtype") << QStringLiteral("CM_ OTHER_ 1 \"Ignored\";");
+
342}
+
343
+
344void CoverageTest::ignoredCommentStatements() {
+
345 QFETCH(QString, statement);
+
346 DbcDatabase database;
+
347 FrameInfo frame;
+
348 frame.canId = 1U;
+
349 frame.name = "Frame";
+
350 frame.comment = "Existing frame comment";
+
351 SignalInfo signal;
+
352 signal.name = "State";
+
353 signal.comment = "Existing signal comment";
+
354 frame.signals.push_back(signal);
+
355 database.frames.push_back(frame);
+
356 DbcParseContext context{database, &database.frames[0]};
+
357
+
358 // The handler is public: callers may bypass keyword dispatch. Ignored input
+
359 // must preserve existing metadata, including comments already assigned.
+
360 DbcCommentHandler().Handle(statement.toStdString(), context);
+
361 QCOMPARE(database.frames.size(), std::size_t(1));
+
362 QCOMPARE(database.frames[0].canId, 1U);
+
363 QCOMPARE(database.frames[0].name, frame.name);
+
364 QCOMPARE(database.frames[0].comment, frame.comment);
+
365 QCOMPARE(database.frames[0].signals.size(), std::size_t(1));
+
366 QCOMPARE(database.frames[0].signals[0].name, signal.name);
+
367 QCOMPARE(database.frames[0].signals[0].comment, signal.comment);
+
368 QCOMPARE(context.currentFrame, &database.frames[0]);
+
369}
+
370
+
371void CoverageTest::ambiguousValueReferences_data() {
+
372 QTest::addColumn<bool>("duplicateFrames");
+
373 QTest::addColumn<bool>("extended");
+
374 QTest::newRow("duplicate-standard-frames") << true << false;
+
375 QTest::newRow("duplicate-extended-frames") << true << true;
+
376 QTest::newRow("duplicate-signals-in-standard-frame") << false << false;
+
377 QTest::newRow("duplicate-signals-in-extended-frame") << false << true;
+
378}
+
379
+
380void CoverageTest::ambiguousValueReferences() {
+
381 QFETCH(bool, duplicateFrames);
+
382 QFETCH(bool, extended);
+
383 DbcDatabase database;
+
384 FrameInfo frame;
+
385 frame.canId = 1U;
+
386 frame.name = "Frame";
+
387 frame.isExtended = extended;
+
388 SignalInfo signal;
+
389 signal.name = "State";
+
390 frame.signals.push_back(signal);
+
391 if (!duplicateFrames) frame.signals.push_back(signal);
+
392 database.frames.push_back(frame);
+
393 if (duplicateFrames) database.frames.push_back(frame);
+
394
+
395 // ParseFile rejects duplicate definitions earlier. Construct the database
+
396 // through the public metadata API to exercise the resolver's own checks.
+
397 DbcParseContext context{database, nullptr};
+
398 context.lineNumber = 37U;
+
399 const std::string rawId = extended ? "2147483649" : "1";
+
400 DbcValueDescriptionHandler().Handle("VAL_ " + rawId + " State 0 \"Off\";", context);
+
401 QCOMPARE(context.pendingValues.size(), std::size_t(1));
+
402 QCOMPARE(context.pendingValues[0].lineNumber, std::size_t(37));
+
403 const std::string path = "manual database.dbc";
+
404 try {
+
405 context.ResolveValueDescriptions(path);
+
406 QFAIL("The resolver must reject an ambiguous target");
+
407 } catch (const std::runtime_error &error) {
+
408 const std::string message = duplicateFrames ? "Ambiguous VAL_ frame reference."
+
409 : "Ambiguous VAL_ signal reference: State";
+
410 QCOMPARE(std::string(error.what()), path + ":37: " + message);
+
411 }
+
412 // Ambiguity must be detected before applying any descriptions.
+
413 for (const FrameInfo &candidate: database.frames)
+
414 for (const SignalInfo &candidateSignal: candidate.signals)
+
415 QVERIFY(candidateSignal.valueDescriptions.empty());
+
416}
+
417
+
418void CoverageTest::valueReferenceScope() {
+
419 DbcDatabase database;
+
420 FrameInfo frame;
+
421 frame.canId = 1U;
+
422 SignalInfo signal;
+
423 signal.name = "State";
+
424 frame.signals.push_back(signal);
+
425 database.frames.push_back(frame);
+
426 frame.isExtended = true;
+
427 database.frames.push_back(frame);
+
428 frame.canId = 2U;
+
429 frame.isExtended = false;
+
430 database.frames.push_back(frame);
+
431
+
432 DbcParseContext context{database, nullptr};
+
433 const DbcValueDescriptionHandler handler;
+
434 context.lineNumber = 3U;
+
435 handler.Handle("VAL_ 1 State 0 \"Standard\";", context);
+
436 context.lineNumber = 7U;
+
437 handler.Handle("VAL_ 2147483649 State 0 \"Extended\";", context);
+
438 context.ResolveValueDescriptions("manual database.dbc");
+
439
+
440 const std::string *standard = FindValueDescription(database.frames[0].signals[0].valueDescriptions, std::uint64_t(0));
+
441 const std::string *extended = FindValueDescription(database.frames[1].signals[0].valueDescriptions, std::uint64_t(0));
+
442 QVERIFY(standard != nullptr);
+
443 QVERIFY(extended != nullptr);
+
444 QCOMPARE(*standard, std::string("Standard"));
+
445 QCOMPARE(*extended, std::string("Extended"));
+
446 QVERIFY(database.frames[2].signals[0].valueDescriptions.empty());
+
447}
+
448
+
449void CoverageTest::fileFormatting_data() {
+
450 QTest::addColumn<QString>("text");
+
451 QTest::addColumn<unsigned>("frames");
+
452 QTest::newRow("empty") << QString::fromUtf8("") << 0U;
+
453 QTest::newRow("whitespace") << QString::fromUtf8(" \t\r\n\n\t") << 0U;
+
454 QTest::newRow("unknown-only") << QString::fromUtf8("VERSION \"test\"\nBU_: ECU\nUNKNOWN_ data\n") << 0U;
+
455 QTest::newRow("no-final-newline") << QString::fromUtf8("BO_ 1 Frame: 1 ECU\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU") << 1U;
+
456 QTest::newRow("crlf") << QString::fromUtf8("\r\nBO_ 1 Frame: 1 ECU\r\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\r\nCM_ BO_ 1 \"Comment\";\r\nVAL_ 1 State 0 \"Off\";\r\n") << 1U;
+
457}
+
458
+
459void CoverageTest::fileFormatting() {
+
460 QFETCH(QString, text);
+
461 QFETCH(unsigned, frames);
+
462 const DbcDatabase parsed = ParseText(text.toStdString());
+
463 QCOMPARE(parsed.frames.size(), std::size_t(frames));
+
464 if (frames) {
+
465 QCOMPARE(parsed.frames[0].signals.size(), std::size_t(1));
+
466 if (text.contains("\r\nBO_")) {
+
467 QCOMPARE(parsed.frames[0].comment, std::string("Comment"));
+
468 const std::string *description = FindValueDescription(parsed.frames[0].signals[0].valueDescriptions, std::uint64_t(0));
+
469 QVERIFY(description != nullptr);
+
470 QCOMPARE(*description, std::string("Off"));
+
471 }
+
472 }
+
473}
+
474
+
475void CoverageTest::missingFile() {
+
476 QTemporaryFile file;
+
477 QVERIFY(file.open());
+
478 const QString path = file.fileName();
+
479 QVERIFY(file.remove());
+
480 try {
+
481 DbcParser().ParseFile(path.toStdString());
+
482 QFAIL("Missing file must throw");
+
483 } catch (const std::runtime_error &error) {
+
484 QCOMPARE(std::string(error.what()), "Failed to open DBC file: " + path.toStdString());
+
485 }
+
486}
+
487
+
488void CoverageTest::builders() {
+
489 const DecodeDatabase emptyRuntime = DbcDecodeBuilder().Build(DbcDatabase());
+
490 QVERIFY(emptyRuntime.GetFrames().empty());
+
491 QVERIFY(!emptyRuntime.FindFrame(FrameKey(0U, false)));
+
492 const std::unique_ptr<TreeNode> emptyTree = DbcTreeBuilder().Build(DbcDatabase());
+
493 QCOMPARE(emptyTree->GetType(), NodeType::Root);
+
494 QCOMPARE(emptyTree->GetName(), std::string("dbc"));
+
495 QCOMPARE(emptyTree->GetChildCount(), std::size_t(0));
+
496 QVERIFY(emptyTree->GetFrame() == nullptr && emptyTree->GetSignal() == nullptr);
+
497 const DbcDatabase expected = ParseText(
+
498 "BO_ 256 Standard: 3 ECU1\n"
+
499 "SG_ Mode M : 0|8@1+ (2,3) [0|513] \"mode\" ECU1,ECU2\n"
+
500 "SG_ Branch m1 : 15|8@0- (-2,5) [-249|261] \"rpm\" ECU2,ECU3\n"
+
501 "SG_ Always : 16|8@1+ (0.25,-1) [-1|62.75] \"V\" ECU3\n"
+
502 "CM_ BO_ 256 \"Frame comment\";\nCM_ SG_ 256 Branch \"Branch comment\";\n"
+
503 "VAL_ 256 Branch -1 \"Negative\" 1 \"Positive\";\n"
+
504 "BO_ 2163212866 Extended: 8 ECU4\n");
+
505 DecodeDatabase runtime;
+
506 std::unique_ptr<TreeNode> tree;
+
507 {
+
508 DbcDatabase source = expected;
+
509 runtime = DbcDecodeBuilder().Build(source);
+
510 tree = DbcTreeBuilder().Build(source);
+
511 source.frames[0].name = "Changed";
+
512 source.frames[0].signals[1].receivers.clear();
+
513 source.frames[0].signals[1].valueDescriptions.clear();
+
514 source.frames.clear();
+
515 }
+
516 QCOMPARE(runtime.GetFrames().size(), expected.frames.size());
+
517 QCOMPARE(tree->GetChildCount(), expected.frames.size());
+
518 for (std::size_t i = 0; i < expected.frames.size(); ++i) {
+
519 const FrameInfo &frame = expected.frames[i];
+
520 const DecodeFrame &decodedFrame = runtime.GetFrames()[i];
+
521 const TreeNode *node = tree->GetChild(i);
+
522 QVERIFY(node && node->GetFrame());
+
523 QCOMPARE(node->GetType(), NodeType::Frame);
+
524 QCOMPARE(node->GetName(), frame.name);
+
525 QVERIFY(node->GetSignal() == nullptr);
+
526 QCOMPARE(runtime.FindFrame(FrameKey(frame.canId, frame.isExtended)).get(), &decodedFrame);
+
527 const FrameInfo &copied = *node->GetFrame();
+
528 QCOMPARE(copied.name, frame.name);
+
529 QCOMPARE(decodedFrame.name, frame.name);
+
530 QCOMPARE(copied.canId, frame.canId);
+
531 QCOMPARE(decodedFrame.canId, frame.canId);
+
532 QCOMPARE(copied.isExtended, frame.isExtended);
+
533 QCOMPARE(decodedFrame.isExtended, frame.isExtended);
+
534 QCOMPARE(copied.dlc, frame.dlc);
+
535 QCOMPARE(decodedFrame.dlc, frame.dlc);
+
536 QCOMPARE(copied.pgn, frame.pgn);
+
537 QCOMPARE(decodedFrame.pgn, frame.pgn);
+
538 QCOMPARE(copied.hasPgn, frame.hasPgn);
+
539 QCOMPARE(decodedFrame.hasPgn, frame.hasPgn);
+
540 QCOMPARE(copied.transmitter, frame.transmitter);
+
541 QCOMPARE(decodedFrame.transmitter, frame.transmitter);
+
542 QCOMPARE(copied.comment, frame.comment);
+
543 QCOMPARE(decodedFrame.comment, frame.comment);
+
544 QCOMPARE(node->GetChildCount(), frame.signals.size());
+
545 QCOMPARE(copied.signals.size(), frame.signals.size());
+
546 QCOMPARE(decodedFrame.signals.size(), frame.signals.size());
+
547 for (std::size_t j = 0; j < frame.signals.size(); ++j) {
+
548 const SignalInfo &original = frame.signals[j];
+
549 const DecodeSignal &target = decodedFrame.signals[j];
+
550 const TreeNode *leaf = node->GetChild(j);
+
551 QVERIFY(leaf && leaf->GetSignal());
+
552 QCOMPARE(leaf->GetType(), NodeType::Signal);
+
553 QCOMPARE(leaf->GetName(), original.name);
+
554 QVERIFY(leaf->GetFrame() == nullptr);
+
555 // Check both the frame payload copy and each child payload copy.
+
556 for (const SignalInfo *copy: {&copied.signals[j], leaf->GetSignal()}) {
+
557 QCOMPARE(copy->name, original.name);
+
558 QCOMPARE(copy->startBit, original.startBit);
+
559 QCOMPARE(copy->length, original.length);
+
560 QCOMPARE(copy->isLittleEndian, original.isLittleEndian);
+
561 QCOMPARE(copy->isSigned, original.isSigned);
+
562 QCOMPARE(copy->factor, original.factor);
+
563 QCOMPARE(copy->offset, original.offset);
+
564 QCOMPARE(copy->minimum, original.minimum);
+
565 QCOMPARE(copy->maximum, original.maximum);
+
566 QCOMPARE(copy->unit, original.unit);
+
567 QCOMPARE(copy->comment, original.comment);
+
568 QVERIFY(copy->receivers == original.receivers);
+
569 QVERIFY(copy->valueDescriptions == original.valueDescriptions);
+
570 QCOMPARE(copy->multiplexing.role, original.multiplexing.role);
+
571 QCOMPARE(copy->multiplexing.value, original.multiplexing.value);
+
572 }
+
573 QCOMPARE(target.name, original.name);
+
574 QCOMPARE(target.startBit, original.startBit);
+
575 QCOMPARE(target.length, original.length);
+
576 QCOMPARE(target.byteOrder, original.isLittleEndian ? ByteOrder::Intel : ByteOrder::Motorola);
+
577 QCOMPARE(target.valueType, original.isSigned ? ValueType::Signed : ValueType::Unsigned);
+
578 QCOMPARE(target.factor, original.factor);
+
579 QCOMPARE(target.offset, original.offset);
+
580 QCOMPARE(target.minimum, original.minimum);
+
581 QCOMPARE(target.maximum, original.maximum);
+
582 QCOMPARE(target.unit, original.unit);
+
583 QCOMPARE(target.comment, original.comment);
+
584 QVERIFY(target.receivers == original.receivers);
+
585 QVERIFY(target.valueDescriptions == original.valueDescriptions);
+
586 QCOMPARE(target.multiplexing.role, original.multiplexing.role);
+
587 QCOMPARE(target.multiplexing.value, original.multiplexing.value);
+
588 }
+
589 }
+
590}
+
591
+
592void CoverageTest::physicalConversion_data() {
+
593 QTest::addColumn<double>("factor");
+
594 QTest::addColumn<double>("offset");
+
595 QTest::addColumn<double>("expected");
+
596 QTest::newRow("zero-factor") << 0.0 << 7.0 << 7.0;
+
597 QTest::newRow("negative-factor") << -2.0 << 5.0 << -15.0;
+
598 QTest::newRow("above-declared-range") << 2.0 << 3.0 << 23.0;
+
599}
+
600
+
601void CoverageTest::physicalConversion() {
+
602 QFETCH(double, factor);
+
603 QFETCH(double, offset);
+
604 QFETCH(double, expected);
+
605 DecodeSignal signal;
+
606 signal.length = 8U;
+
607 signal.factor = factor;
+
608 signal.offset = offset;
+
609 signal.minimum = 0.0;
+
610 signal.maximum = 1.0;
+
611 const DecodeDatabase database = SingleSignal(signal);
+
612 RawCanFrame raw;
+
613 raw.canId = 17U;
+
614 raw.data = {10U};
+
615 const DecodedFrameValue decoded = DbcDecoder().Decode(database, raw);
+
616 QVERIFY(decoded.signals[0].valid);
+
617 QCOMPARE(std::get<std::uint64_t>(decoded.signals[0].rawValue), std::uint64_t(10));
+
618 QCOMPARE(decoded.signals[0].physicalValue, expected);
+
619}
+
620
+
621void CoverageTest::multiplexingScenarios() {
+
622 const DecodeDatabase selectorOnly = DbcDecodeBuilder().Build(ParseText(
+
623 "BO_ 1 Frame: 1 ECU\nSG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"));
+
624 RawCanFrame raw;
+
625 raw.canId = 1U;
+
626 raw.data = {1U};
+
627 const DecodedFrameValue only = DbcDecoder().Decode(selectorOnly, raw);
+
628 QCOMPARE(only.signals.size(), std::size_t(1));
+
629 QVERIFY(only.signals[0].valid);
+
630 const DecodeDatabase database = DbcDecodeBuilder().Build(ParseText(
+
631 "BO_ 1 First: 3 ECU\n"
+
632 "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
+
633 "SG_ A m0 : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
+
634 "SG_ B m0 : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
+
635 "BO_ 2 Second: 2 ECU\n"
+
636 "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
+
637 "SG_ A m1 : 8|8@1+ (1,0) [0|255] \"\" ECU\n"));
+
638 raw.data = {0U, 42U, 99U};
+
639 const DecodedFrameValue first = DbcDecoder().Decode(database, raw);
+
640 QVERIFY(first.signals[1].valid && first.signals[2].valid);
+
641 QCOMPARE(std::get<std::uint64_t>(first.signals[1].rawValue), std::uint64_t(42));
+
642 QCOMPARE(std::get<std::uint64_t>(first.signals[2].rawValue), std::uint64_t(99));
+
643 raw.canId = 2U;
+
644 const DecodedFrameValue secondInactive = DbcDecoder().Decode(database, raw);
+
645 QCOMPARE(secondInactive.signals[1].status, SignalDecodeStatus::Inactive);
+
646 raw.data[0] = 1U;
+
647 QVERIFY(DbcDecoder().Decode(database, raw).signals[1].valid);
+
648 raw.canId = 1U;
+
649 const DecodedFrameValue firstInactive = DbcDecoder().Decode(database, raw);
+
650 QCOMPARE(firstInactive.signals[1].status, SignalDecodeStatus::Inactive);
+
651 QCOMPARE(firstInactive.signals[2].status, SignalDecodeStatus::Inactive);
+
652}
+
653
+
654void CoverageTest::multiplexingErrorLocations_data() {
+
655 QTest::addColumn<QString>("text");
+
656 QTest::addColumn<unsigned>("line");
+
657 QTest::addColumn<QString>("message");
+
658 const QString header = "BO_ 1 Frame: 3 ECU\n\n";
+
659 const QString selector = "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n";
+
660 const QString branch = "SG_ A m1 : 8|8@1+ (1,0) [0|255] \"\" ECU\n";
+
661 QTest::newRow("missing-selector") << header + branch << 3U << QString::fromUtf8("Multiplexed branch has no selector.");
+
662 QTest::newRow("second-selector") << header + selector + "\nSG_ Other M : 16|1@1+ (1,0) [0|1] \"\" ECU\n"
+
663 << 5U << QString::fromUtf8("Multiplexing requires one unsigned selector per frame.");
+
664 QTest::newRow("signed-selector") << header + "SG_ Mode M : 0|1@1- (1,0) [-1|0] \"\" ECU\n"
+
665 << 3U << QString::fromUtf8("Multiplexing requires one unsigned selector per frame.");
+
666 QTest::newRow("branch-range") << header + "SG_ A m2 : 8|8@1+ (1,0) [0|255] \"\" ECU\n" + selector << 3U << QString::fromUtf8("Branch value exceeds selector raw range.");
+
667 QTest::newRow("same-branch-overlap") << header + selector + branch + "SG_ B m1 : 15|8@0+ (1,0) [0|255] \"\" ECU\n"
+
668 << 5U << QString::fromUtf8("Signals overlap without mutually exclusive multiplexing conditions.");
+
669 QTest::newRow("selector-overlap") << header + selector + "SG_ A m1 : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
+
670 << 4U << QString::fromUtf8("Signals overlap without mutually exclusive multiplexing conditions.");
+
671 QTest::newRow("unconditional-overlap") << header + selector + branch + "SG_ Always : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
+
672 << 5U << QString::fromUtf8("Signals overlap without mutually exclusive multiplexing conditions.");
+
673 QTest::newRow("ordinary-overlap") << header + "SG_ A : 8|8@1+ (1,0) [0|255] \"\" ECU\nSG_ B : 15|8@0+ (1,0) [0|255] \"\" ECU\n"
+
674 << 4U << QString::fromUtf8("Signals overlap without mutually exclusive multiplexing conditions.");
+
675 QTest::newRow("extended-multiplexing") << header + "SG_MUL_VAL_ 1 A Mode 1-2;\n"
+
676 << 3U << QString::fromUtf8("Extended and nested multiplexing are unsupported.");
+
677 QTest::newRow("nested-marker") << header + "SG_ A m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
+
678 << 3U << QString::fromUtf8("Invalid or unsupported multiplexing marker.");
+
679}
+
680
+
681void CoverageTest::multiplexingErrorLocations() {
+
682 QFETCH(QString, text);
+
683 QFETCH(unsigned, line);
+
684 QFETCH(QString, message);
+
685 QTemporaryFile file;
+
686 QVERIFY(file.open());
+
687 const QByteArray content = text.toUtf8();
+
688 QCOMPARE(file.write(content), qint64(content.size()));
+
689 QVERIFY(file.flush());
+
690 try {
+
691 DbcParser().ParseFile(file.fileName().toStdString());
+
692 QFAIL("Expected multiplexing rejection");
+
693 } catch (const std::runtime_error &error) {
+
694 QCOMPARE(QString::fromStdString(error.what()), file.fileName() + ":" + QString::number(line) + ": " + message);
+
695 }
+
696}
+
697
+
698void CoverageTest::escapedValueDescriptions() {
+
699 const DbcDatabase parsed = ParseText(
+
700 "BO_ 1 Frame: 1 ECU\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
+
701 R"dbc(VAL_ 1 State -0 "Ноль" 1 "C:\\trace" 2 "\\\"quoted\"\\";)dbc"
+
702 "\n");
+
703 const ValueDescriptions &values = parsed.frames[0].signals[0].valueDescriptions;
+
704 QCOMPARE(values.size(), std::size_t(3));
+
705 QVERIFY(values.find(RawSignalValue(std::int64_t(0))) == values.end());
+
706 const std::string *zero = FindValueDescription(values, std::int64_t(0));
+
707 const std::string *path = FindValueDescription(values, std::uint64_t(1));
+
708 const std::string *quoted = FindValueDescription(values, std::uint64_t(2));
+
709 QVERIFY(zero && path && quoted);
+
710 QCOMPARE(*zero, std::string("Ноль"));
+
711 QCOMPARE(*path, std::string("C:\\trace"));
+
712 QCOMPARE(*quoted, std::string("\\\"quoted\"\\"));
+
713 QVERIFY(NormalizeValueKey(RawSignalValue(std::int64_t(0))) == RawSignalValue(std::uint64_t(0)));
+
714 QVERIFY(NormalizeValueKey(RawSignalValue(std::int64_t(-1))) == RawSignalValue(std::int64_t(-1)));
+
715}
+
716
+
717void CoverageTest::factory() {
+ +
719 QVERIFY(!factory.contains("value"));
+
720 QVERIFY_THROWS_EXCEPTION(std::out_of_range, static_cast<void>(factory.creator("missing")));
+
721 QVERIFY_THROWS_EXCEPTION(std::out_of_range, static_cast<void>(factory.create("missing", std::make_unique<int>(1))));
+
722 factory.registerType<FactoryFirst>("value");
+
723 QVERIFY(factory.contains("value"));
+
724 std::unique_ptr<int> argument = std::make_unique<int>(42);
+
725 const std::shared_ptr<FactoryBase> first = factory.create("value", std::move(argument));
+
726 QVERIFY(argument == nullptr);
+
727 QCOMPARE(first->Value(), 42);
+
728 const TypeFactory<std::string, FactoryBase, std::unique_ptr<int>>::CreatorFn creator = factory.creator("value");
+
729 QCOMPARE(creator(std::make_unique<int>(7))->Value(), 7);
+
730 factory.registerType<FactorySecond>("value");
+
731 QCOMPARE(factory.create("value", std::make_unique<int>(42))->Value(), 43);
+
732 QCOMPARE(first->Value(), 42);
+
733 QCOMPARE(creator(std::make_unique<int>(7))->Value(), 7);
+
734}
+
735
+
736int RunCoverageTests(int argc, char **argv) {
+
737 CoverageTest tests;
+
738 return QTest::qExec(&tests, argc, argv);
+
739}
+
740#include "dbc_coverage_test.moc"
Converts parsed DBC data into runtime decode database.
DecodeDatabase Build(const DbcDatabase &source) const
Build runtime decode database.
Runtime CAN decoder using prebuilt decode database.
Definition dbc_decoder.h:84
-
DecodedFrameValue Decode(const DecodeDatabase &database, const RawCanFrame &frame) const
Decode one raw CAN frame.
-
std::shared_ptr< const DecodeFrame > FindFrame(const DecodeDatabase &database, std::uint32_t canId, bool isExtended) const
Find frame definition by CAN ID.
+
DecodedFrameValue Decode(const DecodeDatabase &database, const RawCanFrame &frame) const
Decode one raw CAN frame.
+
std::shared_ptr< const DecodeFrame > FindFrame(const DecodeDatabase &database, std::uint32_t canId, bool isExtended) const
Find frame definition by CAN ID.
Minimal DBC parser.
Definition dbc_parser.h:33
DbcDatabase ParseFile(const std::string &filePath) const
Parse DBC file.
Builds a simple tree from parsed DBC database.
std::unique_ptr< TreeNode > Build(const DbcDatabase &database) const
Build tree representation of parsed DBC data.
Immutable runtime snapshot. Copies share storage; reload replaces a snapshot. Owning lookup handles a...
-
std::shared_ptr< const DecodeFrame > FindFrame(const FrameKey &key) const
-
const std::vector< DecodeFrame > & GetFrames() const
+
std::shared_ptr< const DecodeFrame > FindFrame(const FrameKey &key) const
+
const std::vector< DecodeFrame > & GetFrames() const
Tree node for later use in model/view or other hierarchy consumers.
Definition tree_node.h:35
const SignalInfo * GetSignal() const
Get signal payload if node is signal.
Definition tree_node.cpp:64
const TreeNode * GetChild(std::size_t index) const
Get child by index.
Definition tree_node.cpp:38
diff --git a/docs/html/dbc__decode__builder_8cpp_source.html b/docs/html/dbc__decode__builder_8cpp_source.html index 9392261..beb7ec0 100644 --- a/docs/html/dbc__decode__builder_8cpp_source.html +++ b/docs/html/dbc__decode__builder_8cpp_source.html @@ -144,7 +144,7 @@ $(document).ready(function(){initNavTree('dbc__decode__builder_8cpp_source.html'
53 frames.push_back(std::move(targetFrame));
54 }
55
-
56 return DecodeDatabase(std::move(frames));
+
56 return DecodeDatabase(frames);
57}
DecodeDatabase Build(const DbcDatabase &source) const
Build runtime decode database.
diff --git a/docs/html/dbc__decoder_8cpp.html b/docs/html/dbc__decoder_8cpp.html index 8598b59..f3b5822 100644 --- a/docs/html/dbc__decoder_8cpp.html +++ b/docs/html/dbc__decoder_8cpp.html @@ -97,6 +97,7 @@ $(document).ready(function(){initNavTree('dbc__decoder_8cpp.html',''); initResiz

Created: 2026-03-13 Author: Deeaitch (Dim. Himro) More...

#include "dbc_decoder.h"
+#include "signal_layout.h"
#include <cmath>
#include <utility>
diff --git a/docs/html/dbc__decoder_8cpp_source.html b/docs/html/dbc__decoder_8cpp_source.html index 09aebee..1df1057 100644 --- a/docs/html/dbc__decoder_8cpp_source.html +++ b/docs/html/dbc__decoder_8cpp_source.html @@ -100,224 +100,222 @@ $(document).ready(function(){initNavTree('dbc__decoder_8cpp_source.html',''); in
Go to the documentation of this file.
1
12#include "dbc_decoder.h"
-
13#include <cmath>
-
14#include <utility>
-
15
-
-
16std::shared_ptr<const DecodeFrame> DbcDecoder::FindFrame(const DecodeDatabase &database,
-
17 std::uint32_t canId,
-
18 bool isExtended) const {
-
19 return database.FindFrame(FrameKey(canId, isExtended));
-
20}
+
13#include "signal_layout.h"
+
14#include <cmath>
+
15#include <utility>
+
16
+
+
17std::shared_ptr<const DecodeFrame> DbcDecoder::FindFrame(const DecodeDatabase &database,
+
18 std::uint32_t canId,
+
19 bool isExtended) const {
+
20 return database.FindFrame(FrameKey(canId, isExtended));
+
21}
-
21
-
- -
23 const RawCanFrame &frame) const {
-
24 DecodedFrameValue result;
-
25 result.key = FrameKey(frame.canId, frame.isExtended);
-
26 const std::shared_ptr<const DecodeFrame> definition = FindFrame(database, frame.canId, frame.isExtended);
-
27
-
28 if (definition == nullptr)
-
29 return result;
-
30
-
31 result.definition = definition;
-
32 result.valid = true;
-
33 result.status = frame.data.size() < definition->dlc
-
34 ? FrameDecodeStatus::InsufficientPayload
-
35 : FrameDecodeStatus::Valid;
-
36
-
37 const SelectorState selector = ReadSelector(*definition, frame);
-
38 result.signals.reserve(definition->signals.size());
-
39 for (const DecodeSignal &signal: definition->signals) {
-
40 DecodedSignalValue decoded = DecodeOneSignal(signal, definition->dlc, frame, selector);
-
41 if (!decoded.valid && decoded.status != SignalDecodeStatus::Inactive &&
-
42 result.status == FrameDecodeStatus::Valid)
-
43 result.status = FrameDecodeStatus::SignalErrors;
-
44 decoded.definition = std::shared_ptr<const DecodeSignal>(definition, &signal);
-
45 decoded.key = SignalKey(result.key, signal.name);
-
46 result.signals.push_back(std::move(decoded));
-
47 }
-
48
-
49 return result;
-
50}
+
22
+
+ +
24 const RawCanFrame &frame) const {
+
25 DecodedFrameValue result;
+
26 result.key = FrameKey(frame.canId, frame.isExtended);
+
27 const std::shared_ptr<const DecodeFrame> definition = FindFrame(database, frame.canId, frame.isExtended);
+
28
+
29 if (definition == nullptr)
+
30 return result;
+
31
+
32 result.definition = definition;
+
33 result.valid = true;
+
34 result.status = frame.data.size() < definition->dlc
+
35 ? FrameDecodeStatus::InsufficientPayload
+
36 : FrameDecodeStatus::Valid;
+
37
+
38 const SelectorState selector = ReadSelector(*definition, frame);
+
39 result.signals.reserve(definition->signals.size());
+
40 for (const DecodeSignal &signal: definition->signals) {
+
41 DecodedSignalValue decoded = DecodeOneSignal(signal, definition->dlc, frame, selector);
+
42 if (!decoded.valid && decoded.status != SignalDecodeStatus::Inactive &&
+
43 result.status == FrameDecodeStatus::Valid)
+
44 result.status = FrameDecodeStatus::SignalErrors;
+
45 decoded.definition = std::shared_ptr<const DecodeSignal>(definition, &signal);
+
46 decoded.key = SignalKey(result.key, signal.name);
+
47 result.signals.push_back(std::move(decoded));
+
48 }
+
49
+
50 return result;
+
51}
-
51
-
52bool DbcDecoder::IsValidDefinition(const DecodeSignal &signal, std::uint8_t dlc) {
-
53 if (signal.length == 0U || signal.length > 64U ||
-
54 (signal.byteOrder != ByteOrder::Intel && signal.byteOrder != ByteOrder::Motorola) ||
-
55 (signal.valueType != ValueType::Unsigned && signal.valueType != ValueType::Signed) ||
-
56 (signal.multiplexing.role != MultiplexRole::Unconditional &&
-
57 signal.multiplexing.role != MultiplexRole::Selector &&
-
58 signal.multiplexing.role != MultiplexRole::Branch))
-
59 return false;
-
60
-
61 // Use widened coordinates to avoid overflow on malformed start bits.
-
62 const std::uint64_t start = signal.byteOrder == ByteOrder::Intel
-
63 ? signal.startBit
-
64 : std::uint64_t(signal.startBit / 8U) * 8U + 7U - signal.startBit % 8U;
-
65 return start + signal.length <= std::uint64_t(dlc) * 8U;
-
66}
-
67
-
68DbcDecoder::SelectorState DbcDecoder::ReadSelector(const DecodeFrame &definition,
-
69 const RawCanFrame &frame) {
-
70 SelectorState selector;
-
71 std::size_t count = 0U;
-
72 for (const DecodeSignal &signal: definition.signals) {
-
73 if (signal.multiplexing.role == MultiplexRole::Selector) {
-
74 selector.definition = &signal;
-
75 ++count;
-
76 }
-
77 }
-
78 if (count != 1U)
-
79 return selector;
-
80
-
81 const DecodeSignal &signal = *selector.definition;
-
82 selector.valid = signal.valueType == ValueType::Unsigned &&
-
83 IsValidDefinition(signal, definition.dlc);
-
84 selector.available = selector.valid && ExtractUnsigned(frame.data, signal, selector.value);
-
85 return selector;
-
86}
-
87
-
88DecodedSignalValue DbcDecoder::DecodeOneSignal(const DecodeSignal &signal,
-
89 std::uint8_t dlc,
-
90 const RawCanFrame &frame,
-
91 const SelectorState &selector) {
-
92 DecodedSignalValue decoded;
-
93 std::uint64_t unsignedValue = 0U;
-
94
-
95 if (!IsValidDefinition(signal, dlc) ||
-
96 (signal.multiplexing.role != MultiplexRole::Unconditional && !selector.valid)) {
-
97 return decoded;
-
98 }
-
99 if (signal.multiplexing.role == MultiplexRole::Branch) {
-
100 if (selector.definition->length < 64U && signal.multiplexing.value >= (std::uint64_t(1U) << selector.definition->length)) {
-
101 return decoded;
-
102 }
-
103 if (!selector.available) {
-
104 decoded.status = SignalDecodeStatus::SelectorUnavailable;
-
105 return decoded;
-
106 }
-
107 if (signal.multiplexing.value != selector.value) {
-
108 decoded.status = SignalDecodeStatus::Inactive;
-
109 return decoded;
-
110 }
-
111 }
-
112 if (!ExtractUnsigned(frame.data, signal, unsignedValue)) {
-
113 decoded.status = SignalDecodeStatus::InsufficientPayload;
-
114 return decoded;
-
115 }
-
116
-
117 if (signal.valueType == ValueType::Signed)
-
118 decoded.rawValue = SignExtend(unsignedValue, signal.length);
-
119 else
-
120 decoded.rawValue = unsignedValue;
-
121
-
122 double numericValue;
-
123 if (const std::int64_t *signedValue = std::get_if<std::int64_t>(&decoded.rawValue))
-
124 numericValue = static_cast<double>(*signedValue);
-
125 else
-
126 numericValue = static_cast<double>(std::get<std::uint64_t>(decoded.rawValue));
-
127 decoded.physicalValue = numericValue * signal.factor + signal.offset;
-
128 if (!std::isfinite(decoded.physicalValue)) {
-
129 decoded.status = SignalDecodeStatus::PhysicalConversionFailed;
-
130 return decoded;
-
131 }
-
132 decoded.valid = true;
-
133 decoded.status = SignalDecodeStatus::Valid;
-
134
-
135 return decoded;
-
136}
-
137
-
138bool DbcDecoder::ExtractUnsigned(const std::vector<std::uint8_t> &data,
-
139 const DecodeSignal &signal,
-
140 std::uint64_t &value) {
-
141 if ((signal.length == 0U) || (signal.length > 64U))
-
142 return false;
-
143
-
144 if (signal.byteOrder == ByteOrder::Intel)
-
145 return ExtractIntel(data, signal.startBit, signal.length, value);
-
146
-
147 return ExtractMotorola(data, signal.startBit, signal.length, value);
-
148}
-
149
-
150bool DbcDecoder::ExtractIntel(const std::vector<std::uint8_t> &data,
-
151 std::uint32_t startBit,
-
152 std::uint32_t length,
-
153 std::uint64_t &value) {
-
154 value = 0U;
-
155
-
156 for (std::uint32_t bitIndex = 0U; bitIndex < length; ++bitIndex) {
-
157 const std::uint32_t absoluteBit = startBit + bitIndex;
-
158 const std::uint32_t byteIndex = absoluteBit / 8U;
-
159 const std::uint32_t bitInByte = absoluteBit % 8U;
-
160
-
161 if (byteIndex >= data.size())
-
162 return false;
-
163
-
164 const std::uint64_t bitValue =
-
165 (static_cast<std::uint64_t>((data[byteIndex] >> bitInByte) & 0x01U) << bitIndex);
-
166
-
167 value |= bitValue;
-
168 }
-
169
-
170 return true;
-
171}
-
172
-
173bool DbcDecoder::ExtractMotorola(const std::vector<std::uint8_t> &data,
-
174 std::uint32_t startBit,
-
175 std::uint32_t length,
-
176 std::uint64_t &value) {
-
177 /*
-
178 * DBC Motorola bit numbering:
-
179 * - startBit points to the most significant bit of the signal
-
180 * - inside a byte, bit numbering goes 7..0
-
181 * - crossing byte boundary moves to the next byte, bit 7
-
182 * Example: a signal starting at 7 follows 7..0, 15..8, 23..16.
-
183 */
+
52
+
53bool DbcDecoder::IsValidDefinition(const DecodeSignal &signal, std::uint8_t dlc) {
+
54 if (signal.length == 0U || signal.length > 64U ||
+
55 (signal.byteOrder != ByteOrder::Intel && signal.byteOrder != ByteOrder::Motorola) ||
+
56 (signal.valueType != ValueType::Unsigned && signal.valueType != ValueType::Signed) ||
+
57 (signal.multiplexing.role != MultiplexRole::Unconditional &&
+
58 signal.multiplexing.role != MultiplexRole::Selector &&
+
59 signal.multiplexing.role != MultiplexRole::Branch))
+
60 return false;
+
61
+
62 return IsValidSignalLayout(signal.startBit, signal.length,
+
63 signal.byteOrder == ByteOrder::Intel, dlc);
+
64}
+
65
+
66DbcDecoder::SelectorState DbcDecoder::ReadSelector(const DecodeFrame &definition,
+
67 const RawCanFrame &frame) {
+
68 SelectorState selector;
+
69 std::size_t count = 0U;
+
70 for (const DecodeSignal &signal: definition.signals) {
+
71 if (signal.multiplexing.role == MultiplexRole::Selector) {
+
72 selector.definition = &signal;
+
73 ++count;
+
74 }
+
75 }
+
76 if (count != 1U)
+
77 return selector;
+
78
+
79 const DecodeSignal &signal = *selector.definition;
+
80 selector.valid = signal.valueType == ValueType::Unsigned &&
+
81 IsValidDefinition(signal, definition.dlc);
+
82 selector.available = selector.valid && ExtractUnsigned(frame.data, signal, selector.value);
+
83 return selector;
+
84}
+
85
+
86DecodedSignalValue DbcDecoder::DecodeOneSignal(const DecodeSignal &signal,
+
87 std::uint8_t dlc,
+
88 const RawCanFrame &frame,
+
89 const SelectorState &selector) {
+
90 DecodedSignalValue decoded;
+
91 std::uint64_t unsignedValue = 0U;
+
92
+
93 if (!IsValidDefinition(signal, dlc) ||
+
94 (signal.multiplexing.role != MultiplexRole::Unconditional && !selector.valid)) {
+
95 return decoded;
+
96 }
+
97 if (signal.multiplexing.role == MultiplexRole::Branch) {
+
98 if (selector.definition->length < 64U && signal.multiplexing.value >= (std::uint64_t(1U) << selector.definition->length)) {
+
99 return decoded;
+
100 }
+
101 if (!selector.available) {
+
102 decoded.status = SignalDecodeStatus::SelectorUnavailable;
+
103 return decoded;
+
104 }
+
105 if (signal.multiplexing.value != selector.value) {
+
106 decoded.status = SignalDecodeStatus::Inactive;
+
107 return decoded;
+
108 }
+
109 }
+
110 if (!ExtractUnsigned(frame.data, signal, unsignedValue)) {
+
111 decoded.status = SignalDecodeStatus::InsufficientPayload;
+
112 return decoded;
+
113 }
+
114
+
115 if (signal.valueType == ValueType::Signed)
+
116 decoded.rawValue = SignExtend(unsignedValue, signal.length);
+
117 else
+
118 decoded.rawValue = unsignedValue;
+
119
+
120 double numericValue;
+
121 if (const std::int64_t *signedValue = std::get_if<std::int64_t>(&decoded.rawValue))
+
122 numericValue = static_cast<double>(*signedValue);
+
123 else
+
124 numericValue = static_cast<double>(std::get<std::uint64_t>(decoded.rawValue));
+
125 decoded.physicalValue = numericValue * signal.factor + signal.offset;
+
126 if (!std::isfinite(decoded.physicalValue)) {
+
127 decoded.status = SignalDecodeStatus::PhysicalConversionFailed;
+
128 return decoded;
+
129 }
+
130 decoded.valid = true;
+
131 decoded.status = SignalDecodeStatus::Valid;
+
132
+
133 return decoded;
+
134}
+
135
+
136bool DbcDecoder::ExtractUnsigned(const std::vector<std::uint8_t> &data,
+
137 const DecodeSignal &signal,
+
138 std::uint64_t &value) {
+
139 if ((signal.length == 0U) || (signal.length > 64U))
+
140 return false;
+
141
+
142 if (signal.byteOrder == ByteOrder::Intel)
+
143 return ExtractIntel(data, signal.startBit, signal.length, value);
+
144
+
145 return ExtractMotorola(data, signal.startBit, signal.length, value);
+
146}
+
147
+
148bool DbcDecoder::ExtractIntel(const std::vector<std::uint8_t> &data,
+
149 std::uint32_t startBit,
+
150 std::uint32_t length,
+
151 std::uint64_t &value) {
+
152 value = 0U;
+
153
+
154 for (std::uint32_t bitIndex = 0U; bitIndex < length; ++bitIndex) {
+
155 const std::uint64_t absoluteBit = std::uint64_t(startBit) + bitIndex;
+
156 const std::uint64_t byteIndex = absoluteBit / 8U;
+
157 const std::uint32_t bitInByte = absoluteBit % 8U;
+
158
+
159 if (byteIndex >= data.size())
+
160 return false;
+
161
+
162 const std::uint64_t bitValue =
+
163 (static_cast<std::uint64_t>((data[byteIndex] >> bitInByte) & 0x01U) << bitIndex);
+
164
+
165 value |= bitValue;
+
166 }
+
167
+
168 return true;
+
169}
+
170
+
171bool DbcDecoder::ExtractMotorola(const std::vector<std::uint8_t> &data,
+
172 std::uint32_t startBit,
+
173 std::uint32_t length,
+
174 std::uint64_t &value) {
+
175 /*
+
176 * DBC Motorola bit numbering:
+
177 * - startBit points to the most significant bit of the signal
+
178 * - inside a byte, bit numbering goes 7..0
+
179 * - crossing byte boundary moves to the next byte, bit 7
+
180 * Example: a signal starting at 7 follows 7..0, 15..8, 23..16.
+
181 */
+
182
+
183 value = 0U;
184
-
185 value = 0U;
-
186
-
187 std::size_t byteIndex = startBit / 8U;
-
188 std::uint32_t bitInByte = startBit % 8U;
-
189
-
190 for (std::uint32_t bitIndex = 0U; bitIndex < length; ++bitIndex) {
-
191 if (byteIndex >= data.size())
-
192 return false;
-
193
-
194 value <<= 1U;
-
195 value |= static_cast<std::uint64_t>((data[byteIndex] >> bitInByte) & 0x01U);
-
196
-
197 if (bitInByte == 0U) {
-
198 ++byteIndex;
-
199 bitInByte = 7U;
-
200 } else
-
201 --bitInByte;
-
202 }
-
203
-
204 return true;
-
205}
-
206
-
207std::int64_t DbcDecoder::SignExtend(std::uint64_t value, std::uint32_t bitLength) {
-
208 if ((bitLength == 0U) || (bitLength > 64U))
-
209 return 0;
-
210
-
211 const std::uint64_t signMask = std::uint64_t(1U) << (bitLength - 1U);
-
212 const std::uint64_t valueMask = (bitLength == 64U)
-
213 ? ~std::uint64_t(0U)
-
214 : (std::uint64_t(1U) << bitLength) - 1U;
-
215 value &= valueMask;
-
216
-
217 if ((value & signMask) == 0U)
-
218 return static_cast<std::int64_t>(value);
-
219
-
220 // The complemented magnitude fits int64_t, including for INT64_MIN.
-
221 return -1 - static_cast<std::int64_t>((~value) & valueMask);
-
222}
-
DecodedFrameValue Decode(const DecodeDatabase &database, const RawCanFrame &frame) const
Decode one raw CAN frame.
-
std::shared_ptr< const DecodeFrame > FindFrame(const DecodeDatabase &database, std::uint32_t canId, bool isExtended) const
Find frame definition by CAN ID.
+
185 std::size_t byteIndex = startBit / 8U;
+
186 std::uint32_t bitInByte = startBit % 8U;
+
187
+
188 for (std::uint32_t bitIndex = 0U; bitIndex < length; ++bitIndex) {
+
189 if (byteIndex >= data.size())
+
190 return false;
+
191
+
192 value <<= 1U;
+
193 value |= static_cast<std::uint64_t>((data[byteIndex] >> bitInByte) & 0x01U);
+
194
+
195 if (bitInByte == 0U) {
+
196 ++byteIndex;
+
197 bitInByte = 7U;
+
198 } else
+
199 --bitInByte;
+
200 }
+
201
+
202 return true;
+
203}
+
204
+
205std::int64_t DbcDecoder::SignExtend(std::uint64_t value, std::uint32_t bitLength) {
+
206 if ((bitLength == 0U) || (bitLength > 64U))
+
207 return 0;
+
208
+
209 const std::uint64_t signMask = std::uint64_t(1U) << (bitLength - 1U);
+
210 const std::uint64_t valueMask = (bitLength == 64U)
+
211 ? ~std::uint64_t(0U)
+
212 : (std::uint64_t(1U) << bitLength) - 1U;
+
213 value &= valueMask;
+
214
+
215 if ((value & signMask) == 0U)
+
216 return static_cast<std::int64_t>(value);
+
217
+
218 // The complemented magnitude fits int64_t, including for INT64_MIN.
+
219 return -1 - static_cast<std::int64_t>((~value) & valueMask);
+
220}
+
DecodedFrameValue Decode(const DecodeDatabase &database, const RawCanFrame &frame) const
Decode one raw CAN frame.
+
std::shared_ptr< const DecodeFrame > FindFrame(const DecodeDatabase &database, std::uint32_t canId, bool isExtended) const
Find frame definition by CAN ID.
Immutable runtime snapshot. Copies share storage; reload replaces a snapshot. Owning lookup handles a...
-
std::shared_ptr< const DecodeFrame > FindFrame(const FrameKey &key) const
+
std::shared_ptr< const DecodeFrame > FindFrame(const FrameKey &key) const
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
Runtime-ready frame definition.
std::uint8_t dlc
diff --git a/docs/html/dbc__decoder_8h_source.html b/docs/html/dbc__decoder_8h_source.html index c003037..f7888b1 100644 --- a/docs/html/dbc__decoder_8h_source.html +++ b/docs/html/dbc__decoder_8h_source.html @@ -211,8 +211,8 @@ $(document).ready(function(){initNavTree('dbc__decoder_8h_source.html',''); init
139
140#endif /* DBC_DECODER_H */
Runtime CAN decoder using prebuilt decode database.
Definition dbc_decoder.h:84
-
DecodedFrameValue Decode(const DecodeDatabase &database, const RawCanFrame &frame) const
Decode one raw CAN frame.
-
std::shared_ptr< const DecodeFrame > FindFrame(const DecodeDatabase &database, std::uint32_t canId, bool isExtended) const
Find frame definition by CAN ID.
+
DecodedFrameValue Decode(const DecodeDatabase &database, const RawCanFrame &frame) const
Decode one raw CAN frame.
+
std::shared_ptr< const DecodeFrame > FindFrame(const DecodeDatabase &database, std::uint32_t canId, bool isExtended) const
Find frame definition by CAN ID.
Immutable runtime snapshot. Copies share storage; reload replaces a snapshot. Owning lookup handles a...
FrameDecodeStatus
Definition dbc_decoder.h:42
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
diff --git a/docs/html/dbc__decoder__test_8cpp_source.html b/docs/html/dbc__decoder__test_8cpp_source.html index 67ceeb8..e507fca 100644 --- a/docs/html/dbc__decoder__test_8cpp_source.html +++ b/docs/html/dbc__decoder__test_8cpp_source.html @@ -104,393 +104,385 @@ $(document).ready(function(){initNavTree('dbc__decoder__test_8cpp_source.html','
4#include <cmath>
5#include <limits>
6#include <sstream>
-
7
-
8struct DecodeCase {
-
9 const char *name;
-
10 ByteOrder byteOrder;
-
11 std::uint32_t startBit;
-
12 std::uint32_t length;
-
13 ValueType valueType;
-
14 std::vector<std::uint8_t> data;
-
15 bool valid;
-
16 std::int64_t rawValue;
-
17 double factor;
-
18 double offset;
-
19 double physicalValue;
-
20};
-
21
-
22Q_DECLARE_METATYPE(DecodeCase)
-
23
-
24class DbcDecoderTest : public QObject {
-
25 Q_OBJECT
-
26private Q_SLOTS:
-
27 void decode_data();
-
28 void decode();
-
29 void boundaries();
-
30 void payloadPolicy_data();
-
31 void payloadPolicy();
-
32 void invalidDefinitions_data();
-
33 void invalidDefinitions();
-
34 void physicalConversion_data();
-
35 void physicalConversion();
-
36 void selectorConversionFailure();
-
37 void zeroDlc();
-
38};
-
39
-
40void DbcDecoderTest::decode_data() {
-
41 QTest::addColumn<DecodeCase>("test");
-
42 const DecodeCase cases[] = {
-
43 {"Motorola reproduction", ByteOrder::Motorola, 7U, 16U, ValueType::Unsigned, {0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.0},
-
44 {"Motorola within byte", ByteOrder::Motorola, 5U, 4U, ValueType::Unsigned, {0xAC}, true, 11, 1.0, 0.0, 11.0},
-
45 {"Motorola unaligned crossing", ByteOrder::Motorola, 3U, 12U, ValueType::Unsigned, {0xAB, 0xCD}, true, 0xBCD, 1.0, 0.0, 3021.0},
-
46 {"Motorola starts at bit zero", ByteOrder::Motorola, 0U, 2U, ValueType::Unsigned, {0x01, 0x80}, true, 3, 1.0, 0.0, 3.0},
-
47 {"Motorola second byte", ByteOrder::Motorola, 15U, 16U, ValueType::Unsigned, {0xFF, 0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.0},
-
48 {"Motorola signed scaled", ByteOrder::Motorola, 7U, 16U, ValueType::Signed, {0xFF, 0xFE}, true, -2, 0.5, 10.0, 9.0},
-
49 {"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},
-
50 {"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},
-
51 {"Motorola short payload", ByteOrder::Motorola, 7U, 16U, ValueType::Unsigned, {0x12}, false, 0, 1.0, 0.0, 0.0},
-
52 {"Motorola empty payload", ByteOrder::Motorola, 7U, 8U, ValueType::Unsigned, {}, false, 0, 1.0, 0.0, 0.0},
-
53 {"Motorola large start", ByteOrder::Motorola, std::numeric_limits<std::uint32_t>::max(), 8U, ValueType::Unsigned, {0xFF}, false, 0, 1.0, 0.0, 0.0},
-
54 {"Motorola zero length", ByteOrder::Motorola, 7U, 0U, ValueType::Unsigned, {0xFF}, false, 0, 1.0, 0.0, 0.0},
-
55 {"Motorola excessive length", ByteOrder::Motorola, 7U, 65U, ValueType::Unsigned,
-
56 std::vector<std::uint8_t>(9U, 0xFF), false, 0, 1.0, 0.0, 0.0},
-
57 {"Intel regression", ByteOrder::Intel, 0U, 16U, ValueType::Unsigned, {0x12, 0x34}, true, 0x3412, 1.0, 0.0, 13330.0},
-
58 {"Intel signed regression", ByteOrder::Intel, 0U, 16U, ValueType::Signed, {0xFE, 0xFF}, true, -2, 0.5, 10.0, 9.0},
-
59 {"Intel short payload", ByteOrder::Intel, 0U, 16U, ValueType::Unsigned, {0x12}, false, 0, 1.0, 0.0, 0.0}};
-
60
-
61 for (const DecodeCase &test: cases)
-
62 QTest::newRow(test.name) << test;
-
63}
-
64
-
65void DbcDecoderTest::decode() {
-
66 QFETCH(DecodeCase, test);
-
67 DecodeSignal signal;
-
68 signal.name = test.name;
-
69 signal.byteOrder = test.byteOrder;
-
70 signal.startBit = test.startBit;
-
71 signal.length = test.length;
-
72 signal.valueType = test.valueType;
-
73 signal.factor = test.factor;
-
74 signal.offset = test.offset;
-
75
-
76 DecodeFrame definition;
-
77 definition.canId = 0x123U;
-
78 definition.dlc = 8U;
-
79 definition.signals.push_back(signal);
-
80 const DecodeDatabase database({definition});
-
81
-
82 RawCanFrame frame;
-
83 frame.canId = definition.canId;
-
84 frame.data = test.data;
-
85 const DecodedFrameValue result = DbcDecoder().Decode(database, frame);
-
86 QVERIFY2(result.valid, test.name);
-
87 QCOMPARE(result.definition.get(), &database.GetFrames()[0]);
-
88 QCOMPARE(result.signals.size(), std::size_t(1U));
-
89 const DecodedSignalValue &decoded = result.signals[0];
-
90 QCOMPARE(decoded.definition.get(), &database.GetFrames()[0].signals[0]);
-
91 QCOMPARE(decoded.valid, test.valid);
-
92 if (!test.valid)
-
93 return;
-
94
-
95 if (test.valueType == ValueType::Signed) {
-
96 const std::int64_t *raw = std::get_if<std::int64_t>(&decoded.rawValue);
-
97 QVERIFY(raw != nullptr);
-
98 QCOMPARE(*raw, test.rawValue);
-
99 } else {
-
100 const std::uint64_t *raw = std::get_if<std::uint64_t>(&decoded.rawValue);
-
101 QVERIFY(raw != nullptr);
-
102 QCOMPARE(*raw, static_cast<std::uint64_t>(test.rawValue));
-
103 }
-
104 QCOMPARE(decoded.physicalValue, test.physicalValue);
-
105}
-
106
-
107void DbcDecoderTest::boundaries() {
-
108 for (ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) {
-
109 for (ValueType type: {ValueType::Unsigned, ValueType::Signed}) {
-
110 for (std::uint64_t bits: {std::uint64_t(0U), std::uint64_t(0x7FFFFFFFFFFFFFFFULL),
-
111 std::uint64_t(0x8000000000000000ULL),
-
112 std::uint64_t(0x8000000000000001ULL),
-
113 std::uint64_t(0xFFFFFFFFFFFFFFFFULL)}) {
-
114 DecodeFrame definition;
-
115 definition.dlc = 8U;
-
116 DecodeSignal signal;
-
117 signal.startBit = order == ByteOrder::Intel ? 0U : 7U;
-
118 signal.length = 64U;
-
119 signal.byteOrder = order;
-
120 signal.valueType = type;
-
121 signal.factor = 0.5;
-
122 signal.offset = 10.0;
-
123 definition.signals.push_back(signal);
-
124 const DecodeDatabase database({definition});
-
125 RawCanFrame frame;
-
126 for (unsigned byte = 0U; byte < 8U; ++byte) {
-
127 const unsigned shift = (order == ByteOrder::Intel ? byte : 7U - byte) * 8U;
-
128 frame.data.push_back(static_cast<std::uint8_t>(bits >> shift));
-
129 }
-
130 const DecodedFrameValue result = DbcDecoder().Decode(database, frame);
-
131 const std::int64_t signedExpected = bits <= 0x7FFFFFFFFFFFFFFFULL
-
132 ? static_cast<std::int64_t>(bits)
-
133 : std::numeric_limits<std::int64_t>::min() +
-
134 static_cast<std::int64_t>(bits - 0x8000000000000000ULL);
-
135 const DecodedSignalValue &decoded = result.signals.at(0U);
-
136 const RawSignalValue expected = type == ValueType::Unsigned
-
137 ? std::variant<std::int64_t, std::uint64_t>(bits)
-
138 : std::variant<std::int64_t, std::uint64_t>(signedExpected);
-
139 const double physical = (type == ValueType::Unsigned
-
140 ? static_cast<double>(bits)
-
141 : static_cast<double>(signedExpected)) *
-
142 0.5 +
-
143 10.0;
-
144 std::ostringstream actualText;
-
145 std::ostringstream expectedText;
-
146 if (const std::int64_t *signedValue = std::get_if<std::int64_t>(&decoded.rawValue))
-
147 actualText << *signedValue;
-
148 else
-
149 actualText << std::get<std::uint64_t>(decoded.rawValue);
-
150 if (type == ValueType::Unsigned)
-
151 expectedText << bits;
-
152 else
-
153 expectedText << signedExpected;
-
154 QVERIFY(decoded.valid);
-
155 QVERIFY(decoded.rawValue == expected);
-
156 QCOMPARE(decoded.physicalValue, physical);
-
157 QCOMPARE(actualText.str(), expectedText.str());
-
158 }
-
159 }
-
160 }
-
161}
-
162
-
163void DbcDecoderTest::payloadPolicy_data() {
-
164 QTest::addColumn<unsigned>("bytes");
-
165 QTest::addColumn<FrameDecodeStatus>("status");
-
166 QTest::newRow("empty") << 0U << FrameDecodeStatus::InsufficientPayload;
-
167 QTest::newRow("partial") << 1U << FrameDecodeStatus::InsufficientPayload;
-
168 QTest::newRow("short-with-all-signals") << 2U << FrameDecodeStatus::InsufficientPayload;
-
169 QTest::newRow("exact") << 3U << FrameDecodeStatus::Valid;
-
170 QTest::newRow("long") << 4U << FrameDecodeStatus::Valid;
-
171}
-
172
-
173void DbcDecoderTest::payloadPolicy() {
-
174 QFETCH(unsigned, bytes);
-
175 QFETCH(FrameDecodeStatus, status);
-
176 for (ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) {
-
177 DecodeFrame definition;
-
178 definition.canId = 17U;
-
179 definition.dlc = 3U;
-
180 DecodeSignal signal;
-
181 signal.name = "First";
-
182 signal.length = 8U;
-
183 signal.byteOrder = order;
-
184 signal.startBit = order == ByteOrder::Intel ? 0U : 7U;
-
185 definition.signals.push_back(signal);
-
186 signal.name = "Second";
-
187 signal.startBit += 8U;
-
188 definition.signals.push_back(signal);
-
189 const DecodeDatabase database({definition});
-
190 RawCanFrame raw;
-
191 raw.canId = 17U;
-
192 raw.data.assign(bytes, 42U);
-
193 const DecodedFrameValue result = DbcDecoder().Decode(database, raw);
-
194 QVERIFY(result.valid);
-
195 QCOMPARE(result.status, status);
-
196 QCOMPARE(result.signals.size(), std::size_t(2U));
-
197 for (std::size_t index = 0U; index < result.signals.size(); ++index) {
-
198 const DecodedSignalValue &value = result.signals[index];
-
199 QCOMPARE(value.status, bytes > index ? SignalDecodeStatus::Valid : SignalDecodeStatus::InsufficientPayload);
-
200 QCOMPARE(value.valid, bytes > index);
-
201 if (value.valid) {
-
202 QCOMPARE(std::get<std::uint64_t>(value.rawValue), std::uint64_t(42U));
-
203 QCOMPARE(value.physicalValue, 42.0);
-
204 }
-
205 }
-
206 raw.isExtended = true;
-
207 const DecodedFrameValue unknown = DbcDecoder().Decode(database, raw);
-
208 QVERIFY(!unknown.valid && !unknown.definition && unknown.signals.empty());
-
209 QCOMPARE(unknown.status, FrameDecodeStatus::UnknownFrame);
-
210 QVERIFY(unknown.key == FrameKey(17U, true));
-
211 QCOMPARE(DbcDecoder().Decode(DecodeDatabase(), raw).status, FrameDecodeStatus::UnknownFrame);
-
212
-
213 definition.signals.clear();
-
214 raw.isExtended = false;
-
215 const DecodedFrameValue empty = DbcDecoder().Decode(DecodeDatabase({definition}), raw);
-
216 QVERIFY(empty.valid && empty.signals.empty());
-
217 QCOMPARE(empty.status, status);
-
218 }
-
219}
-
220
-
221void DbcDecoderTest::invalidDefinitions_data() {
-
222 QTest::addColumn<int>("kind");
-
223 QTest::newRow("zero-length") << 0;
-
224 QTest::newRow("65-bits") << 1;
-
225 QTest::newRow("outside-dlc") << 2;
-
226 QTest::newRow("start-overflow") << 3;
-
227 QTest::newRow("invalid-byte-order") << 4;
-
228 QTest::newRow("invalid-value-type") << 5;
-
229 QTest::newRow("invalid-multiplex-role") << 6;
-
230 QTest::newRow("missing-selector") << 7;
-
231}
-
232
-
233void DbcDecoderTest::invalidDefinitions() {
-
234 QFETCH(int, kind);
-
235 for (ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) {
-
236 DecodeFrame definition;
-
237 definition.dlc = 1U;
-
238 DecodeSignal good;
-
239 good.name = "Good";
-
240 good.length = 4U;
-
241 good.byteOrder = order;
-
242 good.startBit = order == ByteOrder::Intel ? 0U : 7U;
-
243 DecodeSignal bad = good;
-
244 bad.name = "Bad";
-
245 switch (kind) {
-
246 case 0: bad.length = 0U; break;
-
247 case 1: bad.length = 65U; break;
-
248 case 2: bad.startBit += 8U; break;
-
249 case 3: bad.startBit = std::numeric_limits<std::uint32_t>::max(); break;
-
250 case 4: bad.byteOrder = static_cast<ByteOrder>(99); break;
-
251 case 5: bad.valueType = static_cast<ValueType>(99); break;
-
252 case 6: bad.multiplexing.role = static_cast<MultiplexRole>(99); break;
-
253 case 7: bad.multiplexing.role = MultiplexRole::Branch; break;
-
254 }
-
255 definition.signals = {bad, good};
-
256 const DecodeDatabase database({definition});
-
257 RawCanFrame raw;
-
258 raw.data = {0xFFU, 0xFFU}; // Extra bytes must not rescue an invalid definition.
-
259 const DecodedFrameValue result = DbcDecoder().Decode(database, raw);
-
260 QVERIFY(result.valid);
-
261 QCOMPARE(result.status, FrameDecodeStatus::SignalErrors);
-
262 QCOMPARE(result.signals[0].status, SignalDecodeStatus::InvalidDefinition);
-
263 QVERIFY(!result.signals[0].valid && !result.signals[0].GetValueDescription());
-
264 QCOMPARE(result.signals[1].status, SignalDecodeStatus::Valid);
-
265 QCOMPARE(std::get<std::uint64_t>(result.signals[1].rawValue), std::uint64_t(15U));
-
266 QCOMPARE(result.signals[1].physicalValue, 15.0);
-
267 raw.data.clear();
-
268 const DecodedFrameValue shortResult = DbcDecoder().Decode(database, raw);
-
269 QCOMPARE(shortResult.status, FrameDecodeStatus::InsufficientPayload);
-
270 QCOMPARE(shortResult.signals[0].status, SignalDecodeStatus::InvalidDefinition);
-
271 QCOMPARE(shortResult.signals[1].status, SignalDecodeStatus::InsufficientPayload);
-
272 }
-
273}
-
274
-
275void DbcDecoderTest::physicalConversion_data() {
-
276 QTest::addColumn<double>("factor");
-
277 QTest::addColumn<double>("offset");
-
278 QTest::addColumn<bool>("finite");
-
279 const double infinity = std::numeric_limits<double>::infinity();
-
280 const double nan = std::numeric_limits<double>::quiet_NaN();
-
281 const double maximum = std::numeric_limits<double>::max();
-
282 QTest::newRow("nan-factor") << nan << 0.0 << false;
-
283 QTest::newRow("nan-offset") << 1.0 << nan << false;
-
284 QTest::newRow("infinite-factor") << infinity << 0.0 << false;
-
285 QTest::newRow("negative-infinite-factor") << -infinity << 0.0 << false;
-
286 QTest::newRow("infinite-offset") << 1.0 << infinity << false;
-
287 QTest::newRow("negative-infinite-offset") << 1.0 << -infinity << false;
-
288 QTest::newRow("multiply-overflow") << maximum << 0.0 << false;
-
289 QTest::newRow("add-overflow") << maximum / 2.0 << maximum << false;
-
290 QTest::newRow("finite-limit") << maximum / 2.0 << 0.0 << true;
-
291 QTest::newRow("zero-factor") << 0.0 << 42.0 << true;
-
292}
-
293
-
294void DbcDecoderTest::physicalConversion() {
-
295 QFETCH(double, factor);
-
296 QFETCH(double, offset);
-
297 QFETCH(bool, finite);
-
298 for (ValueType type: {ValueType::Signed, ValueType::Unsigned}) {
-
299 DecodeFrame definition;
-
300 definition.dlc = 1U;
-
301 DecodeSignal signal;
-
302 signal.name = "Scaled";
-
303 signal.length = 4U;
-
304 signal.valueType = type;
-
305 signal.factor = factor;
-
306 signal.offset = offset;
-
307 signal.valueDescriptions.emplace(std::uint64_t(2U), "Two");
-
308 definition.signals.push_back(signal);
-
309 signal.name = "Good";
-
310 signal.startBit = 4U;
-
311 signal.factor = 1.0;
-
312 signal.offset = 0.0;
-
313 definition.signals.push_back(signal);
-
314 RawCanFrame raw;
-
315 raw.data = {0x32U};
-
316 const DecodedFrameValue result = DbcDecoder().Decode(DecodeDatabase({definition}), raw);
-
317 QVERIFY(result.valid);
-
318 QCOMPARE(result.status, finite ? FrameDecodeStatus::Valid : FrameDecodeStatus::SignalErrors);
-
319 const DecodedSignalValue &scaled = result.signals[0];
-
320 QCOMPARE(scaled.valid, finite);
-
321 QCOMPARE(scaled.status, finite ? SignalDecodeStatus::Valid : SignalDecodeStatus::PhysicalConversionFailed);
-
322 QCOMPARE(std::isfinite(scaled.physicalValue), finite);
-
323 if (type == ValueType::Signed)
-
324 QCOMPARE(std::get<std::int64_t>(scaled.rawValue), std::int64_t(2));
-
325 else
-
326 QCOMPARE(std::get<std::uint64_t>(scaled.rawValue), std::uint64_t(2));
-
327 QVERIFY(result.signals[1].valid);
-
328 QCOMPARE(result.signals[1].physicalValue, 3.0);
-
329 if (finite) {
-
330 QVERIFY(scaled.GetValueDescription());
-
331 QCOMPARE(*scaled.GetValueDescription(), std::string("Two"));
-
332 } else {
-
333 QVERIFY(!scaled.GetValueDescription());
-
334 }
-
335 }
-
336}
-
337
-
338void DbcDecoderTest::selectorConversionFailure() {
-
339 DecodeFrame definition;
-
340 definition.dlc = 1U;
-
341 DecodeSignal selector;
-
342 selector.name = "Selector";
-
343 selector.length = 1U;
-
344 selector.multiplexing.role = MultiplexRole::Selector;
-
345 selector.factor = std::numeric_limits<double>::infinity();
-
346 DecodeSignal branch;
-
347 branch.name = "Branch";
-
348 branch.startBit = 1U;
-
349 branch.length = 7U;
-
350 branch.multiplexing.role = MultiplexRole::Branch;
-
351 branch.multiplexing.value = 1U;
-
352 definition.signals = {branch, selector};
-
353 RawCanFrame raw;
-
354 raw.data = {85U};
-
355 const DecodedFrameValue result = DbcDecoder().Decode(DecodeDatabase({definition}), raw);
-
356 QCOMPARE(result.status, FrameDecodeStatus::SignalErrors);
-
357 QVERIFY(result.signals[0].valid);
-
358 QCOMPARE(result.signals[0].physicalValue, 42.0);
-
359 QCOMPARE(result.signals[1].status, SignalDecodeStatus::PhysicalConversionFailed);
-
360 QCOMPARE(std::get<std::uint64_t>(result.signals[1].rawValue), std::uint64_t(1U));
-
361 raw.data = {84U};
-
362 selector.factor = 1.0;
-
363 definition.signals = {branch, selector};
-
364 const DecodedFrameValue inactive = DbcDecoder().Decode(DecodeDatabase({definition}), raw);
-
365 QCOMPARE(inactive.status, FrameDecodeStatus::Valid);
-
366 QCOMPARE(inactive.signals[0].status, SignalDecodeStatus::Inactive);
-
367}
-
368
-
369void DbcDecoderTest::zeroDlc() {
-
370 DecodeFrame definition;
-
371 RawCanFrame raw;
-
372 for (const std::vector<std::uint8_t> &data: {std::vector<std::uint8_t>(), std::vector<std::uint8_t>{42U}}) {
-
373 raw.data = data;
-
374 const DecodedFrameValue result = DbcDecoder().Decode(DecodeDatabase({definition}), raw);
-
375 QVERIFY(result.valid && result.signals.empty());
-
376 QCOMPARE(result.status, FrameDecodeStatus::Valid);
-
377 }
-
378 DecodeSignal signal;
-
379 signal.length = 8U;
-
380 definition.signals.push_back(signal);
-
381 const DecodedFrameValue invalid = DbcDecoder().Decode(DecodeDatabase({definition}), raw);
-
382 QCOMPARE(invalid.status, FrameDecodeStatus::SignalErrors);
-
383 QCOMPARE(invalid.signals[0].status, SignalDecodeStatus::InvalidDefinition);
-
384}
-
385
-
386int RunDecoderTests(int argc, char **argv) {
-
387 DbcDecoderTest tests;
-
388 return QTest::qExec(&tests, argc, argv);
-
389}
-
390
-
391#include "dbc_decoder_test.moc"
+
7#include <stdexcept>
+
8
+
9struct DecodeCase {
+
10 const char *name;
+
11 ByteOrder byteOrder;
+
12 std::uint32_t startBit;
+
13 std::uint32_t length;
+
14 ValueType valueType;
+
15 std::vector<std::uint8_t> data;
+
16 bool valid;
+
17 std::int64_t rawValue;
+
18 double factor;
+
19 double offset;
+
20 double physicalValue;
+
21};
+
22
+
23Q_DECLARE_METATYPE(DecodeCase)
+
24
+
25class DbcDecoderTest : public QObject {
+
26 Q_OBJECT
+
27private Q_SLOTS:
+
28 void decode_data();
+
29 void decode();
+
30 void boundaries();
+
31 void payloadPolicy_data();
+
32 void payloadPolicy();
+
33 void invalidDefinitions_data();
+
34 void invalidDefinitions();
+
35 void physicalConversion_data();
+
36 void physicalConversion();
+
37 void selectorConversionFailure();
+
38 void zeroDlc();
+
39};
+
40
+
41void DbcDecoderTest::decode_data() {
+
42 QTest::addColumn<DecodeCase>("test");
+
43 const DecodeCase cases[] = {
+
44 {"Motorola reproduction", ByteOrder::Motorola, 7U, 16U, ValueType::Unsigned, {0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.0},
+
45 {"Motorola within byte", ByteOrder::Motorola, 5U, 4U, ValueType::Unsigned, {0xAC}, true, 11, 1.0, 0.0, 11.0},
+
46 {"Motorola unaligned crossing", ByteOrder::Motorola, 3U, 12U, ValueType::Unsigned, {0xAB, 0xCD}, true, 0xBCD, 1.0, 0.0, 3021.0},
+
47 {"Motorola starts at bit zero", ByteOrder::Motorola, 0U, 2U, ValueType::Unsigned, {0x01, 0x80}, true, 3, 1.0, 0.0, 3.0},
+
48 {"Motorola second byte", ByteOrder::Motorola, 15U, 16U, ValueType::Unsigned, {0xFF, 0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.0},
+
49 {"Motorola signed scaled", ByteOrder::Motorola, 7U, 16U, ValueType::Signed, {0xFF, 0xFE}, true, -2, 0.5, 10.0, 9.0},
+
50 {"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},
+
51 {"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},
+
52 {"Motorola short payload", ByteOrder::Motorola, 7U, 16U, ValueType::Unsigned, {0x12}, false, 0, 1.0, 0.0, 0.0},
+
53 {"Motorola empty payload", ByteOrder::Motorola, 7U, 8U, ValueType::Unsigned, {}, false, 0, 1.0, 0.0, 0.0},
+
54 {"Motorola large start", ByteOrder::Motorola, std::numeric_limits<std::uint32_t>::max(), 8U, ValueType::Unsigned, {0xFF}, false, 0, 1.0, 0.0, 0.0},
+
55 {"Motorola zero length", ByteOrder::Motorola, 7U, 0U, ValueType::Unsigned, {0xFF}, false, 0, 1.0, 0.0, 0.0},
+
56 {"Motorola excessive length", ByteOrder::Motorola, 7U, 65U, ValueType::Unsigned,
+
57 std::vector<std::uint8_t>(9U, 0xFF), false, 0, 1.0, 0.0, 0.0},
+
58 {"Intel regression", ByteOrder::Intel, 0U, 16U, ValueType::Unsigned, {0x12, 0x34}, true, 0x3412, 1.0, 0.0, 13330.0},
+
59 {"Intel signed regression", ByteOrder::Intel, 0U, 16U, ValueType::Signed, {0xFE, 0xFF}, true, -2, 0.5, 10.0, 9.0},
+
60 {"Intel short payload", ByteOrder::Intel, 0U, 16U, ValueType::Unsigned, {0x12}, false, 0, 1.0, 0.0, 0.0}};
+
61
+
62 for (const DecodeCase &test: cases)
+
63 QTest::newRow(test.name) << test;
+
64}
+
65
+
66void DbcDecoderTest::decode() {
+
67 QFETCH(DecodeCase, test);
+
68 DecodeSignal signal;
+
69 signal.name = test.name;
+
70 signal.byteOrder = test.byteOrder;
+
71 signal.startBit = test.startBit;
+
72 signal.length = test.length;
+
73 signal.valueType = test.valueType;
+
74 signal.factor = test.factor;
+
75 signal.offset = test.offset;
+
76
+
77 DecodeFrame definition;
+
78 definition.canId = 0x123U;
+
79 definition.dlc = 8U;
+
80 definition.signals.push_back(signal);
+
81 if (test.length == 0U || test.length > 64U || test.startBit > 64U) {
+
82 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({definition}));
+
83 return;
+
84 }
+
85 const DecodeDatabase database({definition});
+
86
+
87 RawCanFrame frame;
+
88 frame.canId = definition.canId;
+
89 frame.data = test.data;
+
90 const DecodedFrameValue result = DbcDecoder().Decode(database, frame);
+
91 QVERIFY2(result.valid, test.name);
+
92 QCOMPARE(result.definition.get(), &database.GetFrames()[0]);
+
93 QCOMPARE(result.signals.size(), std::size_t(1U));
+
94 const DecodedSignalValue &decoded = result.signals[0];
+
95 QCOMPARE(decoded.definition.get(), &database.GetFrames()[0].signals[0]);
+
96 QCOMPARE(decoded.valid, test.valid);
+
97 if (!test.valid)
+
98 return;
+
99
+
100 if (test.valueType == ValueType::Signed) {
+
101 const std::int64_t *raw = std::get_if<std::int64_t>(&decoded.rawValue);
+
102 QVERIFY(raw != nullptr);
+
103 QCOMPARE(*raw, test.rawValue);
+
104 } else {
+
105 const std::uint64_t *raw = std::get_if<std::uint64_t>(&decoded.rawValue);
+
106 QVERIFY(raw != nullptr);
+
107 QCOMPARE(*raw, static_cast<std::uint64_t>(test.rawValue));
+
108 }
+
109 QCOMPARE(decoded.physicalValue, test.physicalValue);
+
110}
+
111
+
112void DbcDecoderTest::boundaries() {
+
113 for (ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) {
+
114 for (ValueType type: {ValueType::Unsigned, ValueType::Signed}) {
+
115 for (std::uint64_t bits: {std::uint64_t(0U), std::uint64_t(0x7FFFFFFFFFFFFFFFULL),
+
116 std::uint64_t(0x8000000000000000ULL),
+
117 std::uint64_t(0x8000000000000001ULL),
+
118 std::uint64_t(0xFFFFFFFFFFFFFFFFULL)}) {
+
119 DecodeFrame definition;
+
120 definition.dlc = 8U;
+
121 DecodeSignal signal;
+
122 signal.startBit = order == ByteOrder::Intel ? 0U : 7U;
+
123 signal.length = 64U;
+
124 signal.byteOrder = order;
+
125 signal.valueType = type;
+
126 signal.factor = 0.5;
+
127 signal.offset = 10.0;
+
128 definition.signals.push_back(signal);
+
129 const DecodeDatabase database({definition});
+
130 RawCanFrame frame;
+
131 for (unsigned byte = 0U; byte < 8U; ++byte) {
+
132 const unsigned shift = (order == ByteOrder::Intel ? byte : 7U - byte) * 8U;
+
133 frame.data.push_back(static_cast<std::uint8_t>(bits >> shift));
+
134 }
+
135 const DecodedFrameValue result = DbcDecoder().Decode(database, frame);
+
136 const std::int64_t signedExpected = bits <= 0x7FFFFFFFFFFFFFFFULL
+
137 ? static_cast<std::int64_t>(bits)
+
138 : std::numeric_limits<std::int64_t>::min() +
+
139 static_cast<std::int64_t>(bits - 0x8000000000000000ULL);
+
140 const DecodedSignalValue &decoded = result.signals.at(0U);
+
141 const RawSignalValue expected = type == ValueType::Unsigned
+
142 ? std::variant<std::int64_t, std::uint64_t>(bits)
+
143 : std::variant<std::int64_t, std::uint64_t>(signedExpected);
+
144 const double physical = (type == ValueType::Unsigned
+
145 ? static_cast<double>(bits)
+
146 : static_cast<double>(signedExpected)) *
+
147 0.5 +
+
148 10.0;
+
149 std::ostringstream actualText;
+
150 std::ostringstream expectedText;
+
151 if (const std::int64_t *signedValue = std::get_if<std::int64_t>(&decoded.rawValue))
+
152 actualText << *signedValue;
+
153 else
+
154 actualText << std::get<std::uint64_t>(decoded.rawValue);
+
155 if (type == ValueType::Unsigned)
+
156 expectedText << bits;
+
157 else
+
158 expectedText << signedExpected;
+
159 QVERIFY(decoded.valid);
+
160 QVERIFY(decoded.rawValue == expected);
+
161 QCOMPARE(decoded.physicalValue, physical);
+
162 QCOMPARE(actualText.str(), expectedText.str());
+
163 }
+
164 }
+
165 }
+
166}
+
167
+
168void DbcDecoderTest::payloadPolicy_data() {
+
169 QTest::addColumn<unsigned>("bytes");
+
170 QTest::addColumn<FrameDecodeStatus>("status");
+
171 QTest::newRow("empty") << 0U << FrameDecodeStatus::InsufficientPayload;
+
172 QTest::newRow("partial") << 1U << FrameDecodeStatus::InsufficientPayload;
+
173 QTest::newRow("short-with-all-signals") << 2U << FrameDecodeStatus::InsufficientPayload;
+
174 QTest::newRow("exact") << 3U << FrameDecodeStatus::Valid;
+
175 QTest::newRow("long") << 4U << FrameDecodeStatus::Valid;
+
176}
+
177
+
178void DbcDecoderTest::payloadPolicy() {
+
179 QFETCH(unsigned, bytes);
+
180 QFETCH(FrameDecodeStatus, status);
+
181 for (ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) {
+
182 DecodeFrame definition;
+
183 definition.canId = 17U;
+
184 definition.dlc = 3U;
+
185 DecodeSignal signal;
+
186 signal.name = "First";
+
187 signal.length = 8U;
+
188 signal.byteOrder = order;
+
189 signal.startBit = order == ByteOrder::Intel ? 0U : 7U;
+
190 definition.signals.push_back(signal);
+
191 signal.name = "Second";
+
192 signal.startBit += 8U;
+
193 definition.signals.push_back(signal);
+
194 const DecodeDatabase database({definition});
+
195 RawCanFrame raw;
+
196 raw.canId = 17U;
+
197 raw.data.assign(bytes, 42U);
+
198 const DecodedFrameValue result = DbcDecoder().Decode(database, raw);
+
199 QVERIFY(result.valid);
+
200 QCOMPARE(result.status, status);
+
201 QCOMPARE(result.signals.size(), std::size_t(2U));
+
202 for (std::size_t index = 0U; index < result.signals.size(); ++index) {
+
203 const DecodedSignalValue &value = result.signals[index];
+
204 QCOMPARE(value.status, bytes > index ? SignalDecodeStatus::Valid : SignalDecodeStatus::InsufficientPayload);
+
205 QCOMPARE(value.valid, bytes > index);
+
206 if (value.valid) {
+
207 QCOMPARE(std::get<std::uint64_t>(value.rawValue), std::uint64_t(42U));
+
208 QCOMPARE(value.physicalValue, 42.0);
+
209 }
+
210 }
+
211 raw.isExtended = true;
+
212 const DecodedFrameValue unknown = DbcDecoder().Decode(database, raw);
+
213 QVERIFY(!unknown.valid && !unknown.definition && unknown.signals.empty());
+
214 QCOMPARE(unknown.status, FrameDecodeStatus::UnknownFrame);
+
215 QVERIFY(unknown.key == FrameKey(17U, true));
+
216 QCOMPARE(DbcDecoder().Decode(DecodeDatabase(), raw).status, FrameDecodeStatus::UnknownFrame);
+
217
+
218 definition.signals.clear();
+
219 raw.isExtended = false;
+
220 const DecodedFrameValue empty = DbcDecoder().Decode(DecodeDatabase({definition}), raw);
+
221 QVERIFY(empty.valid && empty.signals.empty());
+
222 QCOMPARE(empty.status, status);
+
223 }
+
224}
+
225
+
226void DbcDecoderTest::invalidDefinitions_data() {
+
227 QTest::addColumn<int>("kind");
+
228 QTest::newRow("zero-length") << 0;
+
229 QTest::newRow("65-bits") << 1;
+
230 QTest::newRow("outside-dlc") << 2;
+
231 QTest::newRow("start-overflow") << 3;
+
232 QTest::newRow("invalid-byte-order") << 4;
+
233 QTest::newRow("invalid-value-type") << 5;
+
234 QTest::newRow("invalid-multiplex-role") << 6;
+
235 QTest::newRow("missing-selector") << 7;
+
236}
+
237
+
238void DbcDecoderTest::invalidDefinitions() {
+
239 QFETCH(int, kind);
+
240 for (ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) {
+
241 DecodeFrame definition;
+
242 definition.dlc = 1U;
+
243 DecodeSignal good;
+
244 good.name = "Good";
+
245 good.length = 4U;
+
246 good.byteOrder = order;
+
247 good.startBit = order == ByteOrder::Intel ? 0U : 7U;
+
248 DecodeSignal bad = good;
+
249 bad.name = "Bad";
+
250 switch (kind) {
+
251 case 0: bad.length = 0U; break;
+
252 case 1: bad.length = 65U; break;
+
253 case 2: bad.startBit += 8U; break;
+
254 case 3: bad.startBit = std::numeric_limits<std::uint32_t>::max(); break;
+
255 case 4: bad.byteOrder = static_cast<ByteOrder>(99); break;
+
256 case 5: bad.valueType = static_cast<ValueType>(99); break;
+
257 case 6: bad.multiplexing.role = static_cast<MultiplexRole>(99); break;
+
258 case 7: bad.multiplexing.role = MultiplexRole::Branch; break;
+
259 }
+
260 definition.signals = {bad, good};
+
261 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({definition}));
+
262 }
+
263}
+
264
+
265void DbcDecoderTest::physicalConversion_data() {
+
266 QTest::addColumn<double>("factor");
+
267 QTest::addColumn<double>("offset");
+
268 QTest::addColumn<bool>("finite");
+
269 const double infinity = std::numeric_limits<double>::infinity();
+
270 const double nan = std::numeric_limits<double>::quiet_NaN();
+
271 const double maximum = std::numeric_limits<double>::max();
+
272 QTest::newRow("nan-factor") << nan << 0.0 << false;
+
273 QTest::newRow("nan-offset") << 1.0 << nan << false;
+
274 QTest::newRow("infinite-factor") << infinity << 0.0 << false;
+
275 QTest::newRow("negative-infinite-factor") << -infinity << 0.0 << false;
+
276 QTest::newRow("infinite-offset") << 1.0 << infinity << false;
+
277 QTest::newRow("negative-infinite-offset") << 1.0 << -infinity << false;
+
278 QTest::newRow("multiply-overflow") << maximum << 0.0 << false;
+
279 QTest::newRow("add-overflow") << maximum / 2.0 << maximum << false;
+
280 QTest::newRow("finite-limit") << maximum / 2.0 << 0.0 << true;
+
281 QTest::newRow("zero-factor") << 0.0 << 42.0 << true;
+
282}
+
283
+
284void DbcDecoderTest::physicalConversion() {
+
285 QFETCH(double, factor);
+
286 QFETCH(double, offset);
+
287 QFETCH(bool, finite);
+
288 for (ValueType type: {ValueType::Signed, ValueType::Unsigned}) {
+
289 DecodeFrame definition;
+
290 definition.dlc = 1U;
+
291 DecodeSignal signal;
+
292 signal.name = "Scaled";
+
293 signal.length = 4U;
+
294 signal.valueType = type;
+
295 signal.factor = factor;
+
296 signal.offset = offset;
+
297 signal.valueDescriptions.emplace(std::uint64_t(2U), "Two");
+
298 definition.signals.push_back(signal);
+
299 signal.name = "Good";
+
300 signal.startBit = 4U;
+
301 signal.factor = 1.0;
+
302 signal.offset = 0.0;
+
303 definition.signals.push_back(signal);
+
304 RawCanFrame raw;
+
305 raw.data = {0x32U};
+
306 if (!std::isfinite(factor) || !std::isfinite(offset)) {
+
307 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({definition}));
+
308 continue;
+
309 }
+
310 const DecodedFrameValue result = DbcDecoder().Decode(DecodeDatabase({definition}), raw);
+
311 QVERIFY(result.valid);
+
312 QCOMPARE(result.status, finite ? FrameDecodeStatus::Valid : FrameDecodeStatus::SignalErrors);
+
313 const DecodedSignalValue &scaled = result.signals[0];
+
314 QCOMPARE(scaled.valid, finite);
+
315 QCOMPARE(scaled.status, finite ? SignalDecodeStatus::Valid : SignalDecodeStatus::PhysicalConversionFailed);
+
316 QCOMPARE(std::isfinite(scaled.physicalValue), finite);
+
317 if (type == ValueType::Signed)
+
318 QCOMPARE(std::get<std::int64_t>(scaled.rawValue), std::int64_t(2));
+
319 else
+
320 QCOMPARE(std::get<std::uint64_t>(scaled.rawValue), std::uint64_t(2));
+
321 QVERIFY(result.signals[1].valid);
+
322 QCOMPARE(result.signals[1].physicalValue, 3.0);
+
323 if (finite) {
+
324 QVERIFY(scaled.GetValueDescription());
+
325 QCOMPARE(*scaled.GetValueDescription(), std::string("Two"));
+
326 } else {
+
327 QVERIFY(!scaled.GetValueDescription());
+
328 }
+
329 }
+
330}
+
331
+
332void DbcDecoderTest::selectorConversionFailure() {
+
333 DecodeFrame definition;
+
334 definition.dlc = 1U;
+
335 DecodeSignal selector;
+
336 selector.name = "Selector";
+
337 selector.length = 2U;
+
338 selector.multiplexing.role = MultiplexRole::Selector;
+
339 selector.factor = std::numeric_limits<double>::max();
+
340 DecodeSignal branch;
+
341 branch.name = "Branch";
+
342 branch.startBit = 2U;
+
343 branch.length = 6U;
+
344 branch.multiplexing.role = MultiplexRole::Branch;
+
345 branch.multiplexing.value = 2U;
+
346 definition.signals = {branch, selector};
+
347 RawCanFrame raw;
+
348 raw.data = {170U};
+
349 const DecodedFrameValue result = DbcDecoder().Decode(DecodeDatabase({definition}), raw);
+
350 QCOMPARE(result.status, FrameDecodeStatus::SignalErrors);
+
351 QVERIFY(result.signals[0].valid);
+
352 QCOMPARE(result.signals[0].physicalValue, 42.0);
+
353 QCOMPARE(result.signals[1].status, SignalDecodeStatus::PhysicalConversionFailed);
+
354 QCOMPARE(std::get<std::uint64_t>(result.signals[1].rawValue), std::uint64_t(2U));
+
355 raw.data = {168U};
+
356 selector.factor = 1.0;
+
357 definition.signals = {branch, selector};
+
358 const DecodedFrameValue inactive = DbcDecoder().Decode(DecodeDatabase({definition}), raw);
+
359 QCOMPARE(inactive.status, FrameDecodeStatus::Valid);
+
360 QCOMPARE(inactive.signals[0].status, SignalDecodeStatus::Inactive);
+
361}
+
362
+
363void DbcDecoderTest::zeroDlc() {
+
364 DecodeFrame definition;
+
365 RawCanFrame raw;
+
366 for (const std::vector<std::uint8_t> &data: {std::vector<std::uint8_t>(), std::vector<std::uint8_t>{42U}}) {
+
367 raw.data = data;
+
368 const DecodedFrameValue result = DbcDecoder().Decode(DecodeDatabase({definition}), raw);
+
369 QVERIFY(result.valid && result.signals.empty());
+
370 QCOMPARE(result.status, FrameDecodeStatus::Valid);
+
371 }
+
372 DecodeSignal signal;
+
373 signal.length = 8U;
+
374 definition.signals.push_back(signal);
+
375 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({definition}));
+
376}
+
377
+
378int RunDecoderTests(int argc, char **argv) {
+
379 DbcDecoderTest tests;
+
380 return QTest::qExec(&tests, argc, argv);
+
381}
+
382
+
383#include "dbc_decoder_test.moc"
Runtime CAN decoder using prebuilt decode database.
Definition dbc_decoder.h:84
-
DecodedFrameValue Decode(const DecodeDatabase &database, const RawCanFrame &frame) const
Decode one raw CAN frame.
+
DecodedFrameValue Decode(const DecodeDatabase &database, const RawCanFrame &frame) const
Decode one raw CAN frame.
Immutable runtime snapshot. Copies share storage; reload replaces a snapshot. Owning lookup handles a...
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
FrameDecodeStatus
Definition dbc_decoder.h:42
diff --git a/docs/html/dbc__lifetime__test_8cpp_source.html b/docs/html/dbc__lifetime__test_8cpp_source.html index 0e64f3b..ed41a1b 100644 --- a/docs/html/dbc__lifetime__test_8cpp_source.html +++ b/docs/html/dbc__lifetime__test_8cpp_source.html @@ -275,89 +275,93 @@ $(document).ready(function(){initNavTree('dbc__lifetime__test_8cpp_source.html',
175 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({frame, frame}));
176 DecodeSignal signal;
177 signal.name = "State";
-
178 frame.signals = {signal, signal};
-
179 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({frame}));
-
180 DbcDatabase parsed;
-
181 FrameInfo source;
-
182 source.canId = 17U;
-
183 parsed.frames = {source, source};
-
184 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DbcDecodeBuilder().Build(parsed));
-
185}
-
186
-
187void DbcLifetimeTest::failedReload() {
-
188 DecodeDatabase database = BuildText(firstDbc);
-
189 const std::shared_ptr<const DecodeFrame> original = database.FindFrame(FrameKey(17U, false));
-
190 QVERIFY_THROWS_EXCEPTION(std::runtime_error, database = BuildText("BO_ invalid\n"));
-
191 QCOMPARE(database.FindFrame(FrameKey(17U, false)), original);
-
192 database = BuildText("");
-
193 QVERIFY(database.GetFrames().empty());
-
194 QVERIFY(!database.FindFrame(FrameKey(17U, false)));
-
195 QCOMPARE(original->name, std::string("OldFrame"));
-
196}
-
197
-
198void DbcLifetimeTest::invalidResults() {
-
199 RawCanFrame raw;
-
200 raw.canId = 17U;
-
201 DecodedFrameValue result = DbcDecoder().Decode(BuildText(firstDbc), raw);
-
202 QVERIFY(result.valid);
-
203 QVERIFY(!result.signals[0].valid);
-
204 QCOMPARE(result.signals[0].status, SignalDecodeStatus::InsufficientPayload);
-
205 QVERIFY(!result.signals[0].GetValueDescription());
-
206 QCOMPARE(result.signals[0].definition->name, std::string("State"));
-
207 result = DbcDecoder().Decode(DecodeDatabase(), raw);
-
208 QVERIFY(!result.valid && !result.definition && result.signals.empty());
-
209 QVERIFY(result.key == FrameKey(17U, false));
-
210 QVERIFY(!DecodedSignalValue().GetValueDescription());
-
211 QVERIFY(!DecodeDatabase().FindSignal(SignalKey(FrameKey(17U, false), "State")));
-
212}
-
213
-
214void DbcLifetimeTest::retainedMultiplexStatuses() {
-
215 const std::string text =
-
216 "BO_ 17 Frame: 2 ECU\n"
-
217 "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
-
218 "SG_ State m1 : 8|8@1+ (1,0) [0|255] \"\" ECU\n";
-
219 const std::vector<std::vector<std::uint8_t>> payloads = {{0U}, {}, {1U}};
-
220 const SignalDecodeStatus statuses[] = {SignalDecodeStatus::Inactive,
-
221 SignalDecodeStatus::SelectorUnavailable,
-
222 SignalDecodeStatus::InsufficientPayload};
-
223 for (std::size_t index = 0U; index < payloads.size(); ++index) {
-
224 DecodeDatabase database = BuildText(text);
-
225 RawCanFrame raw;
-
226 raw.canId = 17U;
-
227 raw.data = payloads[index];
-
228 const DecodedSignalValue saved = DbcDecoder().Decode(database, raw).signals[1];
-
229 database = BuildText(secondDbc);
-
230 QCOMPARE(saved.status, statuses[index]);
-
231 QVERIFY(!saved.valid && !saved.GetValueDescription());
-
232 QCOMPARE(saved.definition->multiplexing.role, MultiplexRole::Branch);
-
233 QVERIFY(saved.key == SignalKey(FrameKey(17U, false), "State"));
-
234 }
-
235}
-
236
-
237int RunLifetimeTests(int argc, char **argv) {
-
238 DbcLifetimeTest test;
-
239 return QTest::qExec(&test, argc, argv);
-
240}
-
241
-
242#include "dbc_lifetime_test.moc"
+
178 signal.length = 1U;
+
179 frame.dlc = 1U;
+
180 frame.signals = {signal, signal};
+
181 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({frame}));
+
182 DbcDatabase parsed;
+
183 FrameInfo source;
+
184 source.canId = 17U;
+
185 parsed.frames = {source, source};
+
186 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DbcDecodeBuilder().Build(parsed));
+
187}
+
188
+
189void DbcLifetimeTest::failedReload() {
+
190 DecodeDatabase database = BuildText(firstDbc);
+
191 const std::shared_ptr<const DecodeFrame> original = database.FindFrame(FrameKey(17U, false));
+
192 QVERIFY_THROWS_EXCEPTION(std::runtime_error, database = BuildText("BO_ invalid\n"));
+
193 QCOMPARE(database.FindFrame(FrameKey(17U, false)), original);
+
194 database = BuildText("");
+
195 QVERIFY(database.GetFrames().empty());
+
196 QVERIFY(!database.FindFrame(FrameKey(17U, false)));
+
197 QCOMPARE(original->name, std::string("OldFrame"));
+
198}
+
199
+
200void DbcLifetimeTest::invalidResults() {
+
201 RawCanFrame raw;
+
202 raw.canId = 17U;
+
203 DecodedFrameValue result = DbcDecoder().Decode(BuildText(firstDbc), raw);
+
204 QVERIFY(result.valid);
+
205 QVERIFY(!result.signals[0].valid);
+
206 QCOMPARE(result.signals[0].status, SignalDecodeStatus::InsufficientPayload);
+
207 QVERIFY(!result.signals[0].GetValueDescription());
+
208 QCOMPARE(result.signals[0].definition->name, std::string("State"));
+
209 result = DbcDecoder().Decode(DecodeDatabase(), raw);
+
210 QVERIFY(!result.valid && !result.definition && result.signals.empty());
+
211 QVERIFY(result.key == FrameKey(17U, false));
+
212 QVERIFY(!DecodedSignalValue().GetValueDescription());
+
213 QVERIFY(!DecodeDatabase().FindSignal(SignalKey(FrameKey(17U, false), "State")));
+
214}
+
215
+
216void DbcLifetimeTest::retainedMultiplexStatuses() {
+
217 const std::string text =
+
218 "BO_ 17 Frame: 2 ECU\n"
+
219 "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
+
220 "SG_ State m1 : 8|8@1+ (1,0) [0|255] \"\" ECU\n";
+
221 const std::vector<std::vector<std::uint8_t>> payloads = {{0U}, {}, {1U}};
+
222 const SignalDecodeStatus statuses[] = {SignalDecodeStatus::Inactive,
+
223 SignalDecodeStatus::SelectorUnavailable,
+
224 SignalDecodeStatus::InsufficientPayload};
+
225 for (std::size_t index = 0U; index < payloads.size(); ++index) {
+
226 DecodeDatabase database = BuildText(text);
+
227 RawCanFrame raw;
+
228 raw.canId = 17U;
+
229 raw.data = payloads[index];
+
230 const DecodedSignalValue saved = DbcDecoder().Decode(database, raw).signals[1];
+
231 database = BuildText(secondDbc);
+
232 QCOMPARE(saved.status, statuses[index]);
+
233 QVERIFY(!saved.valid && !saved.GetValueDescription());
+
234 QCOMPARE(saved.definition->multiplexing.role, MultiplexRole::Branch);
+
235 QVERIFY(saved.key == SignalKey(FrameKey(17U, false), "State"));
+
236 }
+
237}
+
238
+
239int RunLifetimeTests(int argc, char **argv) {
+
240 DbcLifetimeTest test;
+
241 return QTest::qExec(&test, argc, argv);
+
242}
+
243
+
244#include "dbc_lifetime_test.moc"
Converts parsed DBC data into runtime decode database.
DecodeDatabase Build(const DbcDatabase &source) const
Build runtime decode database.
Runtime CAN decoder using prebuilt decode database.
Definition dbc_decoder.h:84
-
DecodedFrameValue Decode(const DecodeDatabase &database, const RawCanFrame &frame) const
Decode one raw CAN frame.
-
std::shared_ptr< const DecodeFrame > FindFrame(const DecodeDatabase &database, std::uint32_t canId, bool isExtended) const
Find frame definition by CAN ID.
+
DecodedFrameValue Decode(const DecodeDatabase &database, const RawCanFrame &frame) const
Decode one raw CAN frame.
+
std::shared_ptr< const DecodeFrame > FindFrame(const DecodeDatabase &database, std::uint32_t canId, bool isExtended) const
Find frame definition by CAN ID.
Minimal DBC parser.
Definition dbc_parser.h:33
Immutable runtime snapshot. Copies share storage; reload replaces a snapshot. Owning lookup handles a...
-
std::shared_ptr< const DecodeFrame > FindFrame(const FrameKey &key) const
-
const std::vector< DecodeFrame > & GetFrames() const
+
std::shared_ptr< const DecodeFrame > FindFrame(const FrameKey &key) const
+
const std::vector< DecodeFrame > & GetFrames() const
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
Parsed DBC content stored in a simple internal form.
std::vector< FrameInfo > frames
Runtime-ready frame definition.
+
std::uint8_t dlc
std::uint32_t canId
Runtime-ready signal definition.
std::string name
+
std::uint32_t length
Fully decoded frame.
Definition dbc_decoder.h:71
std::shared_ptr< const DecodeFrame > definition
Definition dbc_decoder.h:72
diff --git a/docs/html/dbc__parser_8cpp_source.html b/docs/html/dbc__parser_8cpp_source.html index a1561be..5d591db 100644 --- a/docs/html/dbc__parser_8cpp_source.html +++ b/docs/html/dbc__parser_8cpp_source.html @@ -206,7 +206,7 @@ $(document).ready(function(){initNavTree('dbc__parser_8cpp_source.html',''); ini
DbcParseResult ParseFileWithDiagnostics(const std::string &filePath) const
Parse with warnings and a structured first error instead of throwing.
DbcDatabase ParseFile(const std::string &filePath) const
Parse DBC file.
-
bool Dispatch(const std::string &line, DbcParseContext &context) const
+
bool Dispatch(const std::string &line, DbcParseContext &context) const
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
Parsed DBC content stored in a simple internal form.
diff --git a/docs/html/dbc__parser__test_8cpp_source.html b/docs/html/dbc__parser__test_8cpp_source.html index 6895b51..1d39be9 100644 --- a/docs/html/dbc__parser__test_8cpp_source.html +++ b/docs/html/dbc__parser__test_8cpp_source.html @@ -703,313 +703,306 @@ $(document).ready(function(){initNavTree('dbc__parser__test_8cpp_source.html',''
603 "Adjacent large integers remain distinct");
604 std::vector<DecodeFrame> definitions = runtime.GetFrames();
605 definitions[0].signals[1].length = 0U;
-
606 runtime = DecodeDatabase(definitions);
-
607 result = DbcDecoder().Decode(runtime, raw);
-
608 CHECK(result.signals[0].status == SignalDecodeStatus::InvalidDefinition &&
-
609 result.signals[1].status == SignalDecodeStatus::InvalidDefinition,
-
610 "Malformed runtime selector distinguished from payload errors");
-
611 definitions[0].signals.pop_back();
-
612 runtime = DecodeDatabase(definitions);
-
613 result = DbcDecoder().Decode(runtime, raw);
-
614 CHECK(result.signals[0].status == SignalDecodeStatus::InvalidDefinition,
-
615 "Missing runtime selector");
-
616 fixture.Write("BO_ 1 Frame: 2 ECU\n\n"
-
617 "SG_ Branch m2 : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
-
618 "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n");
-
619 bool rejected = false;
-
620 try {
-
621 DbcParser().ParseFile(fixture.path.string());
-
622 } catch (const std::runtime_error &error) {
-
623 rejected = std::string(error.what()).find(fixture.path.string() + ":3: ") == 0U;
-
624 }
-
625 CHECK(rejected, "Deferred validation reports branch source line");
-
626 fixture.Write("BO_ 1 Frame: 2 ECU\n"
-
627 "SG_ A : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
-
628 "SG_ B : 15|8@0+ (1,0) [0|255] \"\" ECU\n");
-
629 rejected = false;
-
630 try {
-
631 DbcParser().ParseFile(fixture.path.string());
-
632 } catch (const std::runtime_error &) {
-
633 rejected = true;
-
634 }
-
635 CHECK(rejected, "Ordinary overlapping signals rejected");
-
636}
-
637
-
638void DbcParserTest::invalidSignals() {
-
639 Fixture fixture;
-
640 const std::string ordinary = SignalLine("0", "8", "1+", "1", "0", "0", "255");
-
641 for (const std::string value: {"-1", "+1", "0junk", "0x10", "1.5",
-
642 "4294967296", "999999999999999999999"}) {
-
643 RejectSignal(fixture, SignalLine(value, "8", "1+", "1", "0", "0", "255"));
-
644 RejectSignal(fixture, SignalLine("0", value, "1+", "1", "0", "0", "255"));
-
645 }
-
646 for (const std::string length: {"0", "65"})
-
647 RejectSignal(fixture, SignalLine("0", length, "1+", "1", "0", "0", "255"));
-
648 for (const std::string order: {"2+", "9-", "1", "1x", "1++", "01+"})
-
649 RejectSignal(fixture, SignalLine("0", "8", order, "1", "0", "0", "255"));
-
650 for (const std::string value: {"1junk", "nan", "NaN", "inf", "-inf", "1e999", "1e-999", "0x1p2", "", "1e", "1.2.3"}) {
-
651 RejectSignal(fixture, SignalLine("0", "8", "1+", value, "0", "0", "255"));
-
652 RejectSignal(fixture, SignalLine("0", "8", "1+", "1", value, "0", "255"));
-
653 RejectSignal(fixture, SignalLine("0", "8", "1+", "1", "0", value, "255"));
-
654 RejectSignal(fixture, SignalLine("0", "8", "1+", "1", "0", "0", value));
-
655 }
-
656 RejectSignal(fixture, SignalLine("0", "8", "1+", "1", "0", "2", "1"));
-
657 for (const std::string name: {"", "Two Names", "Value m1M"}) {
-
658 std::string line = ordinary;
-
659 line.replace(4U, 5U, name);
-
660 RejectSignal(fixture, line);
-
661 }
-
662 for (const std::string broken: {
-
663 "SG_", "SG_ Value", "SG_ Value : 0|8@1+ (1,0) [0|255] ECU",
-
664 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\"",
-
665 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU,",
-
666 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU extra"})
-
667 RejectSignal(fixture, broken);
-
668 RejectSignal(fixture, SignalLine("64", "1", "1+", "1", "0", "0", "1"));
-
669 RejectSignal(fixture, SignalLine("63", "2", "1+", "1", "0", "0", "3"));
-
670 RejectSignal(fixture, SignalLine("56", "2", "0+", "1", "0", "0", "3"));
-
671 RejectSignal(fixture, SignalLine("4294967295", "64", "1+", "1", "0", "0", "1"));
-
672 RejectSignal(fixture, ordinary, 0U);
-
673 for (const char delimiter: {'|', '@', '(', ')', '[', ']', '"'}) {
-
674 std::string line = ordinary;
-
675 line.erase(line.find(delimiter), 1U);
-
676 RejectSignal(fixture, line);
-
677 }
-
678}
-
679
-
680void DbcParserTest::validSignals() {
-
681 Fixture fixture;
-
682 const DbcParser signalParser;
-
683 fixture.Write("BO_ 1 Frame: 8 ECU\n" +
-
684 SignalLine("0", "8", "1+", "0", "0", "0", "0") + "\n" +
-
685 SignalLine("8", "8", "1-", "-1", "0", "-255", "0", "Negative") + "\n");
-
686 const DbcDatabase factors = signalParser.ParseFile(fixture.path.string());
-
687 CHECK(factors.frames[0].signals.size() == 2U &&
-
688 factors.frames[0].signals[0].factor == 0.0 &&
-
689 factors.frames[0].signals[1].factor == -1.0,
-
690 "Zero and negative factors and equal range boundaries");
-
691
-
692 struct SignalCase {
-
693 const char *start;
-
694 const char *length;
-
695 const char *order;
-
696 unsigned payload;
-
697 };
-
698 const SignalCase validSignals[] = {
-
699 {"0", "1", "1+", 1U},
-
700 {"7", "1", "0-", 1U},
-
701 {"0", "64", "1+", 8U},
-
702 {"7", "64", "0-", 8U},
-
703 {"63", "1", "1+", 8U},
-
704 {"56", "1", "0+", 8U},
-
705 {"3", "12", "0+", 2U},
-
706 {"2039", "1", "1+", 255U}};
-
707 for (const SignalCase &test: validSignals) {
-
708 fixture.Write("BO_ 1 Frame: " + std::to_string(test.payload) + " ECU\n" +
-
709 SignalLine(test.start, test.length, test.order, ".5e+1", "-2.5E-1", "-1.", "+2e2") + "\n");
-
710 try {
-
711 const DbcDatabase parsed = signalParser.ParseFile(fixture.path.string());
-
712 const SignalInfo &signal = parsed.frames.at(0).signals.at(0);
-
713 QCOMPARE(signal.startBit, static_cast<std::uint32_t>(std::stoul(test.start)));
-
714 QCOMPARE(signal.length, static_cast<std::uint32_t>(std::stoul(test.length)));
-
715 QCOMPARE(signal.isLittleEndian, test.order[0] == '1');
-
716 QCOMPARE(signal.isSigned, test.order[1] == '-');
-
717 QCOMPARE(signal.factor, 5.0);
-
718 QCOMPARE(signal.offset, -0.25);
-
719 QCOMPARE(signal.minimum, -1.0);
-
720 QCOMPARE(signal.maximum, 200.0);
-
721 } catch (const std::exception &error) { CHECK(false, error.what()); }
-
722 }
-
723 fixture.Write("BO_ 1 Frame: 8 ECU\n"
-
724 "SG_\tValue\t:\t0 | 8 @1+ ( 1 , 0 ) [ 0 | 255 ] \"rpm\" ECU1,\tECU2\n");
-
725 try {
-
726 const DbcDatabase parsed = signalParser.ParseFile(fixture.path.string());
-
727 const SignalInfo &signal = parsed.frames.at(0).signals.at(0);
-
728 CHECK(signal.name == "Value" && signal.unit == "rpm" &&
-
729 signal.receivers == std::vector<std::string>({"ECU1", "ECU2"}),
-
730 "Signal tabs, spaces, and receiver list");
-
731 } catch (const std::exception &error) { CHECK(false, error.what()); }
-
732}
-
733
-
734void DbcParserTest::dispatch() {
-
735 CountingHandler::instances = 0;
-
736 CountingHandler::calls = 0;
-
737 DbcDatabase dispatchDatabase;
-
738 DbcParseContext context{dispatchDatabase, nullptr};
-
739 DbcStatementDispatcher dispatcher;
-
740 dispatcher.RegisterType<CountingHandler>("TEST_");
-
741 CHECK(dispatcher.Dispatch("TEST_ first", context) &&
-
742 dispatcher.Dispatch("TEST_ second", context) &&
-
743 CountingHandler::instances == 1 && CountingHandler::calls == 2 &&
-
744 dispatchDatabase.frames.size() == 2U,
-
745 "Factory dispatch and handler reuse");
-
746 CHECK(!dispatcher.Dispatch("TEST_OTHER data", context) &&
-
747 !dispatcher.Dispatch("BO_TX_BU_ 1 : ECU;", context) &&
-
748 !dispatcher.Dispatch("VAL_TABLE_ States 0 \"Stopped\";", context) &&
-
749 CountingHandler::calls == 2,
-
750 "Exact and unknown keyword dispatch");
-
751}
-
752
-
753void DbcParserTest::independentSessions() {
-
754 Fixture fixture;
-
755 const DbcParser parser;
-
756 fixture.Write("BO_ 1 First: 8 ECU\n");
-
757 CHECK(parser.ParseFile(fixture.path.string()).frames.size() == 1U, "First parse");
-
758 fixture.Write("SG_ Speed : invalid\n");
-
759 try {
-
760 parser.ParseFile(fixture.path.string());
-
761 CHECK(false, "Independent parse context");
-
762 } catch (const std::runtime_error &error) {
-
763 CHECK(std::string(error.what()).find("Signal found before any frame") !=
-
764 std::string::npos,
-
765 "Independent parse context");
-
766 }
-
767 fixture.Write("BO_ 2 Second: 8 ECU\n");
-
768 const DbcDatabase recovered = parser.ParseFile(fixture.path.string());
-
769 CHECK(recovered.frames.size() == 1U && recovered.frames[0].canId == 2U,
-
770 "Fresh parse after error");
-
771}
-
772
-
773
-
774void DbcParserTest::valueDescriptions() {
-
775 Fixture fixture;
-
776 fixture.Write(
-
777 "VAL_\t256\tState\t0\t\"Off\"\t1 \"On\" 2 \"Error with spaces; and \\\"quotes\\\"\";\n"
-
778 "BO_ 256 Status: 8 ECU\n"
-
779 "SG_ State : 0|8@1+ (2,10) [0|520] \"\" ECU\n"
-
780 "VAL_ 256 State 3 \"\";\n"
-
781 "VAL_ 2147483904 State -9223372036854775808 \"Minimum\" -1 \"Negative\" 9223372036854775807 \"Maximum\";\n"
-
782 "BO_ 2147483904 Extended: 8 ECU\n"
-
783 "SG_ State : 0|64@1- (1,0) [-1e20|1e20] \"\" ECU\n"
-
784 "BO_ 2048 Unsigned: 8 ECU\n"
-
785 "SG_ State : 0|64@1+ (1,0) [0|2e20] \"\" ECU\n"
-
786 "VAL_ 2147485696 State 9223372036854775808 \"High\" 18446744073709551615 \"Full\";\n");
-
787 const DbcDatabase parsed = DbcParser().ParseFile(fixture.path.string());
-
788 const DecodeDatabase runtime = DbcDecodeBuilder().Build(parsed);
-
789 const std::unique_ptr<TreeNode> tree = DbcTreeBuilder().Build(parsed);
-
790 CHECK(tree->GetChild(0)->GetSignal() == nullptr, "Frame node");
-
791 for (std::size_t index = 0; index < parsed.frames.size(); ++index) {
-
792 CHECK(tree->GetChild(index)->GetChild(0)->GetSignal()->valueDescriptions ==
-
793 parsed.frames[index].signals[0].valueDescriptions &&
-
794 runtime.GetFrames()[index].signals[0].valueDescriptions ==
-
795 parsed.frames[index].signals[0].valueDescriptions,
-
796 "All mappings survive both builders");
-
797 }
-
798 CHECK(tree->GetChild(0)->GetChild(0)->GetSignal()->valueDescriptions ==
-
799 runtime.GetFrames()[0].signals[0].valueDescriptions,
-
800 "Both builders preserve descriptions");
-
801 RawCanFrame frame;
-
802 frame.canId = 256;
-
803 frame.data.resize(8);
-
804 const std::string expected[] = {"Off", "On", "Error with spaces; and \"quotes\"", ""};
-
805 for (unsigned value = 0; value < 5; ++value) {
-
806 frame.data[0] = value;
-
807 const DecodedSignalValue decoded = DbcDecoder().Decode(runtime, frame).signals[0];
-
808 CHECK(decoded.valid && decoded.physicalValue == value * 2 + 10, "Numeric scaling unchanged");
-
809 const std::string *description = decoded.GetValueDescription();
-
810 CHECK(value < 4 ? description && *description == expected[value] : !description,
-
811 "Raw lookup and unknown value");
-
812 }
-
813 frame.isExtended = true;
-
814 struct Boundary {
-
815 std::uint64_t bits;
-
816 const char *description;
-
817 };
-
818 for (const Boundary &test: {Boundary{0x8000000000000000ULL, "Minimum"},
-
819 Boundary{0xFFFFFFFFFFFFFFFFULL, "Negative"},
-
820 Boundary{0x7FFFFFFFFFFFFFFFULL, "Maximum"}}) {
-
821 for (unsigned byte = 0; byte < 8; ++byte)
-
822 frame.data[byte] = static_cast<std::uint8_t>(test.bits >> (byte * 8));
-
823 const DecodedSignalValue decoded = DbcDecoder().Decode(runtime, frame).signals[0];
-
824 CHECK(decoded.GetValueDescription() && *decoded.GetValueDescription() == test.description,
-
825 "Signed boundaries and distinct extended frame");
-
826 }
-
827 frame.canId = 2048;
-
828 for (const Boundary &test: {Boundary{0x8000000000000000ULL, "High"},
-
829 Boundary{0xFFFFFFFFFFFFFFFFULL, "Full"}}) {
-
830 for (unsigned byte = 0; byte < 8; ++byte)
-
831 frame.data[byte] = static_cast<std::uint8_t>(test.bits >> (byte * 8));
-
832 const DecodedSignalValue decoded = DbcDecoder().Decode(runtime, frame).signals[0];
-
833 CHECK(decoded.GetValueDescription() && *decoded.GetValueDescription() == test.description,
-
834 "Unsigned boundaries");
-
835 }
-
836 CHECK(FindValueDescription(runtime.GetFrames()[0].signals[0].valueDescriptions, std::int64_t(1)) != nullptr,
-
837 "Nonnegative signed lookup matches unsigned key");
-
838 frame.data.clear();
-
839 CHECK(!DbcDecoder().Decode(runtime, frame).signals[0].GetValueDescription(), "Invalid decode has no description");
-
840}
-
841
-
842void DbcParserTest::invalidValueDescriptions() {
-
843 Fixture fixture;
-
844 const std::string definitions = "BO_ 1 Frame: 8 ECU\nSG_ State : 0|64@1+ (1,0) [0|2e20] \"\" ECU\n";
-
845 for (const std::string statement: {
-
846 "VAL_", "VAL_ 1 State;", "VAL_ 1 State ;", "VAL_ 1 State 0 \"Off\"",
-
847 "VAL_ 1 State 0 \"Off\"; junk", "VAL_ 1 State 0 Off;",
-
848 "VAL_ 1 State 0 \"Unclosed;", "VAL_ 1 State 0 \"Off\"1 \"On\";",
-
849 "VAL_ 1 State 0 \"Off\" 00 \"Duplicate\";",
-
850 "VAL_ 1 State 0 \"Off\" -0 \"Duplicate\";",
-
851 "VAL_ 1 State 0 \"Off\";\nVAL_ 1 State 0 \"Duplicate\";",
-
852 "VAL_ 2 State 0 \"Missing frame\";", "VAL_ 1 Missing 0 \"Missing signal\";",
-
853 "VAL_ -1 State 0 \"Bad ID\";", "VAL_ 536870912 State 0 \"Bad ID\";",
-
854 "VAL_ 4294967296 State 0 \"Bad ID\";", "VAL_ Environment 0 \"Unsupported\";",
-
855 "VAL_ 1 State 0 \"Bad\\q escape\";"}) {
-
856 fixture.Write(definitions + statement + '\n');
-
857 const int line = statement.find('\n') == std::string::npos ? 3 : 4;
-
858 try {
-
859 DbcParser().ParseFile(fixture.path.string());
-
860 CHECK(false, statement);
-
861 } catch (const std::runtime_error &error) {
-
862 CHECK(std::string(error.what()).find(fixture.path.string() + ":" + std::to_string(line) + ": ") == 0,
-
863 statement + " " + error.what());
-
864 }
-
865 }
-
866 for (const std::string suffix: {
-
867 "BO_ 1 Other: 8 ECU\n", // Duplicate frame definition.
-
868 "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n",// Duplicate signal definition.
-
869 ""}) {
-
870 fixture.Write("VAL_ 1 " + std::string(suffix.empty() ? "Missing" : "State") +
-
871 " 0 \"Description\";\n" + definitions + suffix);
-
872 try {
-
873 DbcParser().ParseFile(fixture.path.string());
-
874 CHECK(false, "Missing reference or duplicate definition");
-
875 } catch (const std::runtime_error &error) {
-
876 const std::string line = suffix.empty() ? "1" : "4";
-
877 CHECK(std::string(error.what()).find(fixture.path.string() + ":" + line + ": ") == 0,
-
878 "Error retains the reference or duplicate definition source location");
-
879 }
-
880 }
-
881 for (const std::string number: {"+1", "1x", "0x10", "1.5", "1e2", "--1",
-
882 "-9223372036854775809", "18446744073709551616"}) {
-
883 fixture.Write("VAL_ 1 State " + number + " \"Bad\";\n" + definitions);
-
884 try {
-
885 DbcParser().ParseFile(fixture.path.string());
-
886 CHECK(false, number);
-
887 } catch (const std::runtime_error &error) {
-
888 CHECK(std::string(error.what()).find(fixture.path.string() + ":1: ") == 0, number);
-
889 }
-
890 }
-
891}
-
892
-
893int RunParserTests(int argc, char **argv) {
-
894 DbcParserTest tests;
-
895 return QTest::qExec(&tests, argc, argv);
-
896}
-
897
-
898#include "dbc_parser_test.moc"
+
606 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, runtime = DecodeDatabase(definitions));
+
607 definitions[0].signals.pop_back();
+
608 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, runtime = DecodeDatabase(definitions));
+
609 fixture.Write("BO_ 1 Frame: 2 ECU\n\n"
+
610 "SG_ Branch m2 : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
+
611 "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n");
+
612 bool rejected = false;
+
613 try {
+
614 DbcParser().ParseFile(fixture.path.string());
+
615 } catch (const std::runtime_error &error) {
+
616 rejected = std::string(error.what()).find(fixture.path.string() + ":3: ") == 0U;
+
617 }
+
618 CHECK(rejected, "Deferred validation reports branch source line");
+
619 fixture.Write("BO_ 1 Frame: 2 ECU\n"
+
620 "SG_ A : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
+
621 "SG_ B : 15|8@0+ (1,0) [0|255] \"\" ECU\n");
+
622 rejected = false;
+
623 try {
+
624 DbcParser().ParseFile(fixture.path.string());
+
625 } catch (const std::runtime_error &) {
+
626 rejected = true;
+
627 }
+
628 CHECK(rejected, "Ordinary overlapping signals rejected");
+
629}
+
630
+
631void DbcParserTest::invalidSignals() {
+
632 Fixture fixture;
+
633 const std::string ordinary = SignalLine("0", "8", "1+", "1", "0", "0", "255");
+
634 for (const std::string value: {"-1", "+1", "0junk", "0x10", "1.5",
+
635 "4294967296", "999999999999999999999"}) {
+
636 RejectSignal(fixture, SignalLine(value, "8", "1+", "1", "0", "0", "255"));
+
637 RejectSignal(fixture, SignalLine("0", value, "1+", "1", "0", "0", "255"));
+
638 }
+
639 for (const std::string length: {"0", "65"})
+
640 RejectSignal(fixture, SignalLine("0", length, "1+", "1", "0", "0", "255"));
+
641 for (const std::string order: {"2+", "9-", "1", "1x", "1++", "01+"})
+
642 RejectSignal(fixture, SignalLine("0", "8", order, "1", "0", "0", "255"));
+
643 for (const std::string value: {"1junk", "nan", "NaN", "inf", "-inf", "1e999", "1e-999", "0x1p2", "", "1e", "1.2.3"}) {
+
644 RejectSignal(fixture, SignalLine("0", "8", "1+", value, "0", "0", "255"));
+
645 RejectSignal(fixture, SignalLine("0", "8", "1+", "1", value, "0", "255"));
+
646 RejectSignal(fixture, SignalLine("0", "8", "1+", "1", "0", value, "255"));
+
647 RejectSignal(fixture, SignalLine("0", "8", "1+", "1", "0", "0", value));
+
648 }
+
649 RejectSignal(fixture, SignalLine("0", "8", "1+", "1", "0", "2", "1"));
+
650 for (const std::string name: {"", "Two Names", "Value m1M"}) {
+
651 std::string line = ordinary;
+
652 line.replace(4U, 5U, name);
+
653 RejectSignal(fixture, line);
+
654 }
+
655 for (const std::string broken: {
+
656 "SG_", "SG_ Value", "SG_ Value : 0|8@1+ (1,0) [0|255] ECU",
+
657 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\"",
+
658 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU,",
+
659 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU extra"})
+
660 RejectSignal(fixture, broken);
+
661 RejectSignal(fixture, SignalLine("64", "1", "1+", "1", "0", "0", "1"));
+
662 RejectSignal(fixture, SignalLine("63", "2", "1+", "1", "0", "0", "3"));
+
663 RejectSignal(fixture, SignalLine("56", "2", "0+", "1", "0", "0", "3"));
+
664 RejectSignal(fixture, SignalLine("4294967295", "64", "1+", "1", "0", "0", "1"));
+
665 RejectSignal(fixture, ordinary, 0U);
+
666 for (const char delimiter: {'|', '@', '(', ')', '[', ']', '"'}) {
+
667 std::string line = ordinary;
+
668 line.erase(line.find(delimiter), 1U);
+
669 RejectSignal(fixture, line);
+
670 }
+
671}
+
672
+
673void DbcParserTest::validSignals() {
+
674 Fixture fixture;
+
675 const DbcParser signalParser;
+
676 fixture.Write("BO_ 1 Frame: 8 ECU\n" +
+
677 SignalLine("0", "8", "1+", "0", "0", "0", "0") + "\n" +
+
678 SignalLine("8", "8", "1-", "-1", "0", "-255", "0", "Negative") + "\n");
+
679 const DbcDatabase factors = signalParser.ParseFile(fixture.path.string());
+
680 CHECK(factors.frames[0].signals.size() == 2U &&
+
681 factors.frames[0].signals[0].factor == 0.0 &&
+
682 factors.frames[0].signals[1].factor == -1.0,
+
683 "Zero and negative factors and equal range boundaries");
+
684
+
685 struct SignalCase {
+
686 const char *start;
+
687 const char *length;
+
688 const char *order;
+
689 unsigned payload;
+
690 };
+
691 const SignalCase validSignals[] = {
+
692 {"0", "1", "1+", 1U},
+
693 {"7", "1", "0-", 1U},
+
694 {"0", "64", "1+", 8U},
+
695 {"7", "64", "0-", 8U},
+
696 {"63", "1", "1+", 8U},
+
697 {"56", "1", "0+", 8U},
+
698 {"3", "12", "0+", 2U},
+
699 {"2039", "1", "1+", 255U}};
+
700 for (const SignalCase &test: validSignals) {
+
701 fixture.Write("BO_ 1 Frame: " + std::to_string(test.payload) + " ECU\n" +
+
702 SignalLine(test.start, test.length, test.order, ".5e+1", "-2.5E-1", "-1.", "+2e2") + "\n");
+
703 try {
+
704 const DbcDatabase parsed = signalParser.ParseFile(fixture.path.string());
+
705 const SignalInfo &signal = parsed.frames.at(0).signals.at(0);
+
706 QCOMPARE(signal.startBit, static_cast<std::uint32_t>(std::stoul(test.start)));
+
707 QCOMPARE(signal.length, static_cast<std::uint32_t>(std::stoul(test.length)));
+
708 QCOMPARE(signal.isLittleEndian, test.order[0] == '1');
+
709 QCOMPARE(signal.isSigned, test.order[1] == '-');
+
710 QCOMPARE(signal.factor, 5.0);
+
711 QCOMPARE(signal.offset, -0.25);
+
712 QCOMPARE(signal.minimum, -1.0);
+
713 QCOMPARE(signal.maximum, 200.0);
+
714 } catch (const std::exception &error) { CHECK(false, error.what()); }
+
715 }
+
716 fixture.Write("BO_ 1 Frame: 8 ECU\n"
+
717 "SG_\tValue\t:\t0 | 8 @1+ ( 1 , 0 ) [ 0 | 255 ] \"rpm\" ECU1,\tECU2\n");
+
718 try {
+
719 const DbcDatabase parsed = signalParser.ParseFile(fixture.path.string());
+
720 const SignalInfo &signal = parsed.frames.at(0).signals.at(0);
+
721 CHECK(signal.name == "Value" && signal.unit == "rpm" &&
+
722 signal.receivers == std::vector<std::string>({"ECU1", "ECU2"}),
+
723 "Signal tabs, spaces, and receiver list");
+
724 } catch (const std::exception &error) { CHECK(false, error.what()); }
+
725}
+
726
+
727void DbcParserTest::dispatch() {
+
728 CountingHandler::instances = 0;
+
729 CountingHandler::calls = 0;
+
730 DbcDatabase dispatchDatabase;
+
731 DbcParseContext context{dispatchDatabase, nullptr};
+
732 DbcStatementDispatcher dispatcher;
+
733 dispatcher.RegisterType<CountingHandler>("TEST_");
+
734 CHECK(dispatcher.Dispatch("TEST_ first", context) &&
+
735 dispatcher.Dispatch("TEST_ second", context) &&
+
736 CountingHandler::instances == 1 && CountingHandler::calls == 2 &&
+
737 dispatchDatabase.frames.size() == 2U,
+
738 "Factory dispatch and handler reuse");
+
739 CHECK(!dispatcher.Dispatch("TEST_OTHER data", context) &&
+
740 !dispatcher.Dispatch("BO_TX_BU_ 1 : ECU;", context) &&
+
741 !dispatcher.Dispatch("VAL_TABLE_ States 0 \"Stopped\";", context) &&
+
742 CountingHandler::calls == 2,
+
743 "Exact and unknown keyword dispatch");
+
744}
+
745
+
746void DbcParserTest::independentSessions() {
+
747 Fixture fixture;
+
748 const DbcParser parser;
+
749 fixture.Write("BO_ 1 First: 8 ECU\n");
+
750 CHECK(parser.ParseFile(fixture.path.string()).frames.size() == 1U, "First parse");
+
751 fixture.Write("SG_ Speed : invalid\n");
+
752 try {
+
753 parser.ParseFile(fixture.path.string());
+
754 CHECK(false, "Independent parse context");
+
755 } catch (const std::runtime_error &error) {
+
756 CHECK(std::string(error.what()).find("Signal found before any frame") !=
+
757 std::string::npos,
+
758 "Independent parse context");
+
759 }
+
760 fixture.Write("BO_ 2 Second: 8 ECU\n");
+
761 const DbcDatabase recovered = parser.ParseFile(fixture.path.string());
+
762 CHECK(recovered.frames.size() == 1U && recovered.frames[0].canId == 2U,
+
763 "Fresh parse after error");
+
764}
+
765
+
766
+
767void DbcParserTest::valueDescriptions() {
+
768 Fixture fixture;
+
769 fixture.Write(
+
770 "VAL_\t256\tState\t0\t\"Off\"\t1 \"On\" 2 \"Error with spaces; and \\\"quotes\\\"\";\n"
+
771 "BO_ 256 Status: 8 ECU\n"
+
772 "SG_ State : 0|8@1+ (2,10) [0|520] \"\" ECU\n"
+
773 "VAL_ 256 State 3 \"\";\n"
+
774 "VAL_ 2147483904 State -9223372036854775808 \"Minimum\" -1 \"Negative\" 9223372036854775807 \"Maximum\";\n"
+
775 "BO_ 2147483904 Extended: 8 ECU\n"
+
776 "SG_ State : 0|64@1- (1,0) [-1e20|1e20] \"\" ECU\n"
+
777 "BO_ 2048 Unsigned: 8 ECU\n"
+
778 "SG_ State : 0|64@1+ (1,0) [0|2e20] \"\" ECU\n"
+
779 "VAL_ 2147485696 State 9223372036854775808 \"High\" 18446744073709551615 \"Full\";\n");
+
780 const DbcDatabase parsed = DbcParser().ParseFile(fixture.path.string());
+
781 const DecodeDatabase runtime = DbcDecodeBuilder().Build(parsed);
+
782 const std::unique_ptr<TreeNode> tree = DbcTreeBuilder().Build(parsed);
+
783 CHECK(tree->GetChild(0)->GetSignal() == nullptr, "Frame node");
+
784 for (std::size_t index = 0; index < parsed.frames.size(); ++index) {
+
785 CHECK(tree->GetChild(index)->GetChild(0)->GetSignal()->valueDescriptions ==
+
786 parsed.frames[index].signals[0].valueDescriptions &&
+
787 runtime.GetFrames()[index].signals[0].valueDescriptions ==
+
788 parsed.frames[index].signals[0].valueDescriptions,
+
789 "All mappings survive both builders");
+
790 }
+
791 CHECK(tree->GetChild(0)->GetChild(0)->GetSignal()->valueDescriptions ==
+
792 runtime.GetFrames()[0].signals[0].valueDescriptions,
+
793 "Both builders preserve descriptions");
+
794 RawCanFrame frame;
+
795 frame.canId = 256;
+
796 frame.data.resize(8);
+
797 const std::string expected[] = {"Off", "On", "Error with spaces; and \"quotes\"", ""};
+
798 for (unsigned value = 0; value < 5; ++value) {
+
799 frame.data[0] = value;
+
800 const DecodedSignalValue decoded = DbcDecoder().Decode(runtime, frame).signals[0];
+
801 CHECK(decoded.valid && decoded.physicalValue == value * 2 + 10, "Numeric scaling unchanged");
+
802 const std::string *description = decoded.GetValueDescription();
+
803 CHECK(value < 4 ? description && *description == expected[value] : !description,
+
804 "Raw lookup and unknown value");
+
805 }
+
806 frame.isExtended = true;
+
807 struct Boundary {
+
808 std::uint64_t bits;
+
809 const char *description;
+
810 };
+
811 for (const Boundary &test: {Boundary{0x8000000000000000ULL, "Minimum"},
+
812 Boundary{0xFFFFFFFFFFFFFFFFULL, "Negative"},
+
813 Boundary{0x7FFFFFFFFFFFFFFFULL, "Maximum"}}) {
+
814 for (unsigned byte = 0; byte < 8; ++byte)
+
815 frame.data[byte] = static_cast<std::uint8_t>(test.bits >> (byte * 8));
+
816 const DecodedSignalValue decoded = DbcDecoder().Decode(runtime, frame).signals[0];
+
817 CHECK(decoded.GetValueDescription() && *decoded.GetValueDescription() == test.description,
+
818 "Signed boundaries and distinct extended frame");
+
819 }
+
820 frame.canId = 2048;
+
821 for (const Boundary &test: {Boundary{0x8000000000000000ULL, "High"},
+
822 Boundary{0xFFFFFFFFFFFFFFFFULL, "Full"}}) {
+
823 for (unsigned byte = 0; byte < 8; ++byte)
+
824 frame.data[byte] = static_cast<std::uint8_t>(test.bits >> (byte * 8));
+
825 const DecodedSignalValue decoded = DbcDecoder().Decode(runtime, frame).signals[0];
+
826 CHECK(decoded.GetValueDescription() && *decoded.GetValueDescription() == test.description,
+
827 "Unsigned boundaries");
+
828 }
+
829 CHECK(FindValueDescription(runtime.GetFrames()[0].signals[0].valueDescriptions, std::int64_t(1)) != nullptr,
+
830 "Nonnegative signed lookup matches unsigned key");
+
831 frame.data.clear();
+
832 CHECK(!DbcDecoder().Decode(runtime, frame).signals[0].GetValueDescription(), "Invalid decode has no description");
+
833}
+
834
+
835void DbcParserTest::invalidValueDescriptions() {
+
836 Fixture fixture;
+
837 const std::string definitions = "BO_ 1 Frame: 8 ECU\nSG_ State : 0|64@1+ (1,0) [0|2e20] \"\" ECU\n";
+
838 for (const std::string statement: {
+
839 "VAL_", "VAL_ 1 State;", "VAL_ 1 State ;", "VAL_ 1 State 0 \"Off\"",
+
840 "VAL_ 1 State 0 \"Off\"; junk", "VAL_ 1 State 0 Off;",
+
841 "VAL_ 1 State 0 \"Unclosed;", "VAL_ 1 State 0 \"Off\"1 \"On\";",
+
842 "VAL_ 1 State 0 \"Off\" 00 \"Duplicate\";",
+
843 "VAL_ 1 State 0 \"Off\" -0 \"Duplicate\";",
+
844 "VAL_ 1 State 0 \"Off\";\nVAL_ 1 State 0 \"Duplicate\";",
+
845 "VAL_ 2 State 0 \"Missing frame\";", "VAL_ 1 Missing 0 \"Missing signal\";",
+
846 "VAL_ -1 State 0 \"Bad ID\";", "VAL_ 536870912 State 0 \"Bad ID\";",
+
847 "VAL_ 4294967296 State 0 \"Bad ID\";", "VAL_ Environment 0 \"Unsupported\";",
+
848 "VAL_ 1 State 0 \"Bad\\q escape\";"}) {
+
849 fixture.Write(definitions + statement + '\n');
+
850 const int line = statement.find('\n') == std::string::npos ? 3 : 4;
+
851 try {
+
852 DbcParser().ParseFile(fixture.path.string());
+
853 CHECK(false, statement);
+
854 } catch (const std::runtime_error &error) {
+
855 CHECK(std::string(error.what()).find(fixture.path.string() + ":" + std::to_string(line) + ": ") == 0,
+
856 statement + " " + error.what());
+
857 }
+
858 }
+
859 for (const std::string suffix: {
+
860 "BO_ 1 Other: 8 ECU\n", // Duplicate frame definition.
+
861 "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n",// Duplicate signal definition.
+
862 ""}) {
+
863 fixture.Write("VAL_ 1 " + std::string(suffix.empty() ? "Missing" : "State") +
+
864 " 0 \"Description\";\n" + definitions + suffix);
+
865 try {
+
866 DbcParser().ParseFile(fixture.path.string());
+
867 CHECK(false, "Missing reference or duplicate definition");
+
868 } catch (const std::runtime_error &error) {
+
869 const std::string line = suffix.empty() ? "1" : "4";
+
870 CHECK(std::string(error.what()).find(fixture.path.string() + ":" + line + ": ") == 0,
+
871 "Error retains the reference or duplicate definition source location");
+
872 }
+
873 }
+
874 for (const std::string number: {"+1", "1x", "0x10", "1.5", "1e2", "--1",
+
875 "-9223372036854775809", "18446744073709551616"}) {
+
876 fixture.Write("VAL_ 1 State " + number + " \"Bad\";\n" + definitions);
+
877 try {
+
878 DbcParser().ParseFile(fixture.path.string());
+
879 CHECK(false, number);
+
880 } catch (const std::runtime_error &error) {
+
881 CHECK(std::string(error.what()).find(fixture.path.string() + ":1: ") == 0, number);
+
882 }
+
883 }
+
884}
+
885
+
886int RunParserTests(int argc, char **argv) {
+
887 DbcParserTest tests;
+
888 return QTest::qExec(&tests, argc, argv);
+
889}
+
890
+
891#include "dbc_parser_test.moc"
Converts parsed DBC data into runtime decode database.
DecodeDatabase Build(const DbcDatabase &source) const
Build runtime decode database.
Runtime CAN decoder using prebuilt decode database.
Definition dbc_decoder.h:84
-
DecodedFrameValue Decode(const DecodeDatabase &database, const RawCanFrame &frame) const
Decode one raw CAN frame.
+
DecodedFrameValue Decode(const DecodeDatabase &database, const RawCanFrame &frame) const
Decode one raw CAN frame.
Minimal DBC parser.
Definition dbc_parser.h:33
DbcParseResult ParseFileWithDiagnostics(const std::string &filePath) const
Parse with warnings and a structured first error instead of throwing.
DbcDatabase ParseFile(const std::string &filePath) const
Parse DBC file.
-
bool Dispatch(const std::string &line, DbcParseContext &context) const
+
bool Dispatch(const std::string &line, DbcParseContext &context) const
Builds a simple tree from parsed DBC database.
std::unique_ptr< TreeNode > Build(const DbcDatabase &database) const
Build tree representation of parsed DBC data.
Immutable runtime snapshot. Copies share storage; reload replaces a snapshot. Owning lookup handles a...
-
const std::vector< DecodeFrame > & GetFrames() const
+
const std::vector< DecodeFrame > & GetFrames() const
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
diff --git a/docs/html/dbc__statement__handler_8cpp_source.html b/docs/html/dbc__statement__handler_8cpp_source.html index d3c1516..90324bd 100644 --- a/docs/html/dbc__statement__handler_8cpp_source.html +++ b/docs/html/dbc__statement__handler_8cpp_source.html @@ -101,537 +101,534 @@ $(document).ready(function(){initNavTree('dbc__statement__handler_8cpp_source.ht
1// SPDX-License-Identifier: MIT
2#include "dbc_statement_handler.h"
3#include "text_utils.h"
-
4#include <charconv>
-
5#include <cmath>
-
6#include <limits>
-
7#include <regex>
-
8#include <sstream>
-
9#include <stdexcept>
-
10
-
11namespace {
-
12 std::uint32_t ParseDecimal(const std::string &text,
-
13 std::uint32_t maximum,
-
14 const char *field) {
-
15 std::uint32_t value = 0U;
-
16 const std::from_chars_result parsed = std::from_chars(text.data(), text.data() + text.size(), value);
-
17 if (text.empty() || parsed.ec != std::errc() ||
-
18 parsed.ptr != text.data() + text.size() || value > maximum)
-
19 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
-
20 return value;
-
21 }
-
22
-
23 double ParsePhysicalValue(const std::string &text, const char *field) {
-
24 static const std::regex number(
-
25 R"(^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?$)");
-
26 if (!std::regex_match(text, number))
-
27 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
-
28 std::size_t consumed = 0U;
-
29 double value;
-
30 try {
-
31 value = std::stod(text, &consumed);
-
32 } catch (const std::exception &) {
-
33 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
-
34 }
-
35 if (consumed != text.size() || !std::isfinite(value))
-
36 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
-
37 return value;
-
38 }
-
39
-
40 Multiplexing ParseMultiplexingMarker(const std::string &marker) {
-
41 Multiplexing result;
-
42 if (marker == "M")
-
43 result.role = MultiplexRole::Selector;
-
44 else if (!marker.empty()) {
-
45 if (marker[0] != 'm')
-
46 throw std::runtime_error("Unsupported multiplexing marker.");
-
47 const std::string value = marker.substr(1U);
-
48 const std::from_chars_result parsed =
-
49 std::from_chars(value.data(), value.data() + value.size(), result.value);
-
50 if (value.empty() || parsed.ec != std::errc() || parsed.ptr != value.data() + value.size())
-
51 throw std::runtime_error("Invalid or unsupported multiplexing marker.");
-
52 result.role = MultiplexRole::Branch;
-
53 }
-
54
-
55 return result;
-
56 }
-
57
-
58 RawSignalValue ParseValueKey(const std::string &number) {
-
59 RawSignalValue key;
-
60 if (!number.empty() && number[0] == '-') {
-
61 std::int64_t value = 0;
-
62 const std::from_chars_result parsed =
-
63 std::from_chars(number.data(), number.data() + number.size(), value);
-
64 if (parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
-
65 throw std::runtime_error("Invalid VAL_ integer: " + number);
-
66 key = NormalizeValueKey(RawSignalValue(value));
-
67 } else {
-
68 std::uint64_t value = 0;
-
69 const std::from_chars_result parsed =
-
70 std::from_chars(number.data(), number.data() + number.size(), value);
-
71 if (number.empty() || parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
-
72 throw std::runtime_error("Invalid VAL_ integer: " + number);
-
73 key = value;
-
74 }
-
75 return key;
-
76 }
-
77
-
78 std::string UnescapeDescription(const std::string &quoted) {
-
79 std::string description;
-
80 for (std::size_t i = 0; i < quoted.size(); ++i) {
-
81 if (quoted[i] == '\\')
-
82 ++i;
-
83 description += quoted[i];
-
84 }
-
85 return description;
-
86 }
-
87
-
88 FrameInfo *FindUniqueFrame(DbcDatabase &database, std::uint32_t canId, bool isExtended) {
-
89 FrameInfo *frame = nullptr;
-
90 for (FrameInfo &candidate : database.frames) {
-
91 if (candidate.canId == canId && candidate.isExtended == isExtended) {
-
92 if (frame)
-
93 throw std::runtime_error("Ambiguous VAL_ frame reference.");
-
94 frame = &candidate;
-
95 }
-
96 }
-
97 if (!frame)
-
98 throw std::runtime_error("Unresolved VAL_ frame reference.");
-
99 return frame;
-
100 }
-
101
-
102 SignalInfo *FindUniqueSignal(FrameInfo &frame, const std::string &signalName) {
-
103 SignalInfo *signal = nullptr;
-
104 for (SignalInfo &candidate : frame.signals) {
-
105 if (candidate.name == signalName) {
-
106 if (signal)
-
107 throw std::runtime_error("Ambiguous VAL_ signal reference: " + signalName);
-
108 signal = &candidate;
-
109 }
-
110 }
-
111 if (!signal)
-
112 throw std::runtime_error("Unresolved VAL_ signal reference: " + signalName);
-
113 return signal;
-
114 }
-
115
-
116 std::uint64_t NextSignalBit(std::uint64_t bit, bool isLittleEndian) {
-
117 if (isLittleEndian)
-
118 return bit + 1U;
-
119 // From bit 0, Motorola continues at bit 7 of the next byte (0 -> 15).
-
120 return bit % 8U == 0U ? bit + 15U : bit - 1U;
-
121 }
-
122
-
123 FrameInfo *FindFrameById(DbcDatabase &, std::uint32_t, bool);
-
124 SignalInfo *FindSignalByName(FrameInfo &, const std::string &);
-
125} // namespace
-
126
-
127namespace {
-
128 std::vector<std::string> SplitReceivers(const std::string &text) {
-
129 std::vector<std::string> receivers;
-
130 std::string token;
-
131 std::istringstream stream(text);
-
132
-
133 while (std::getline(stream, token, ',')) {
-
134 token = dbc_detail::TrimText(token);
-
135 if (!token.empty())
-
136 receivers.push_back(token);
-
137 }
-
138
-
139 return receivers;
-
140 }
-
141
-
142 void NormalizeCanId(std::uint32_t rawCanId, std::uint32_t &normalizedCanId, bool &isExtended) {
-
143 /*
-
144 * DBC commonly stores extended identifiers with bit 31 set.
-
145 * Example:
-
146 * raw id = 0x80000000 | actual_29_bit_id
-
147 */
-
148 if ((rawCanId & 0x80000000U) != 0U) {
-
149 isExtended = true;
-
150 normalizedCanId = (rawCanId & 0x1FFFFFFFU);
-
151 } else {
-
152 isExtended = (rawCanId > 0x7FFU);
-
153 normalizedCanId = rawCanId;
-
154 }
-
155 }
-
156
-
157 std::uint32_t TryExtractPgn(std::uint32_t canId, bool isExtended, bool &hasPgn) {
-
158 hasPgn = false;
-
159
-
160 if (!isExtended)
-
161 return 0U;
-
162
-
163 if ((canId & 0x1FFFFFFFU) != canId)
-
164 return 0U;
-
165
-
166 const std::uint32_t pf = (canId >> 16U) & 0xFFU;
-
167 const std::uint32_t ps = (canId >> 8U) & 0xFFU;
-
168 const std::uint32_t dp = (canId >> 24U) & 0x01U;
-
169
-
170 std::uint32_t pgn = 0U;
-
171
-
172 if (pf < 240U)
-
173 pgn = (dp << 16U) | (pf << 8U);
-
174 else
-
175 pgn = (dp << 16U) | (pf << 8U) | ps;
-
176
-
177 hasPgn = true;
-
178 return pgn;
-
179 }
-
180
-
181} // namespace
-
182
-
183FrameInfo DbcFrameHandler::ParseFrameLine(const std::string &line) {
-
184 /*
-
185 * Example:
-
186 * BO_ 256 EngineData: 8 EEC1
-
187 */
-
188
-
189 std::istringstream stream(line);
-
190 std::string keyword;
-
191 std::string idText;
-
192 if (!(stream >> keyword >> idText) || keyword != "BO_")
-
193 throw std::runtime_error("Missing CAN ID in frame definition.");
-
194
-
195 const std::uint32_t rawCanId = ParseDecimal(
-
196 idText, std::numeric_limits<std::uint32_t>::max(), "CAN ID");
-
197 // Only bit 31 may accompany the actual 29-bit identifier.
-
198 if ((rawCanId & 0x60000000U) != 0U)
-
199 throw std::runtime_error("CAN ID exceeds the 29-bit identifier range.");
-
200
-
201 std::string nameText;
-
202 if (!std::getline(stream, nameText, ':') || stream.eof())
-
203 throw std::runtime_error("Missing ':' in frame definition.");
-
204 std::istringstream nameStream(nameText);
-
205 FrameInfo frame;
-
206 std::string extra;
-
207 if (!(nameStream >> frame.name) || (nameStream >> extra))
-
208 throw std::runtime_error("Missing or invalid frame name.");
-
209
-
210 std::string lengthText;
-
211 if (!(stream >> lengthText))
-
212 throw std::runtime_error("Missing payload length in frame definition.");
-
213 frame.dlc = static_cast<std::uint8_t>(ParseDecimal(
-
214 lengthText, std::numeric_limits<std::uint8_t>::max(), "payload length"));
-
215 if (!(stream >> frame.transmitter))
-
216 throw std::runtime_error("Missing transmitter in frame definition.");
-
217 if (stream >> extra)
-
218 throw std::runtime_error("Unexpected trailing field in frame definition: " + extra);
-
219
-
220 NormalizeCanId(rawCanId, frame.canId, frame.isExtended);
-
221 frame.pgn = TryExtractPgn(frame.canId, frame.isExtended, frame.hasPgn);
-
222
-
223 return frame;
-
224}
-
225
-
226SignalInfo DbcSignalHandler::ParseSignalLine(const std::string &line) {
-
227 // Match the complete statement before converting any numeric fields.
-
228 enum Field {
-
229 NameAndMarker = 1, StartBit, BitLength, ByteOrder, Signedness,
-
230 Factor, Offset, Minimum, Maximum, Unit, Receivers
-
231 };
-
232 static const std::regex syntax(
-
233 // Name and optional multiplexing marker.
-
234 R"dbc(^SG_[ \t]+([^:]+):[ \t]*)dbc"
-
235 // Bit position, length, byte order and signedness.
-
236 R"dbc(([^| \t]+)[ \t]*\|[ \t]*([^@ \t]+)[ \t]*@[ \t]*([01])([+-])[ \t]*)dbc"
-
237 // Scaling factor and offset.
-
238 R"dbc(\‍([ \t]*([^, \t]+)[ \t]*,[ \t]*([^ )\t]+)[ \t]*\)[ \t]*)dbc"
-
239 // Physical range.
-
240 R"dbc(\[[ \t]*([^| \t]+)[ \t]*\|[ \t]*([^\] \t]+)[ \t]*\][ \t]*)dbc"
-
241 // Unit and receivers.
-
242 R"dbc("([^"\r\n]*)"[ \t]+([^ \t,]+(?:[ \t]*,[ \t]*[^ \t,]+)*)[ \t]*$)dbc");
-
243 std::smatch fields;
-
244 if (!std::regex_match(line, fields, syntax))
-
245 throw std::runtime_error("Malformed or unsupported SG_ signal definition.");
-
246
-
247 SignalInfo signal;
-
248 std::istringstream nameFields(fields[NameAndMarker].str());
-
249 std::string marker;
-
250 std::string extra;
-
251 nameFields >> signal.name >> marker;
-
252 if (signal.name.empty() || (nameFields >> extra))
-
253 throw std::runtime_error("Invalid signal name or multiplexing marker.");
-
254 signal.multiplexing = ParseMultiplexingMarker(marker);
-
255
-
256 signal.startBit = ParseDecimal(fields[StartBit].str(),
-
257 std::numeric_limits<std::uint32_t>::max(), "signal start bit");
-
258 signal.length = ParseDecimal(fields[BitLength].str(), 64U, "signal length");
-
259 if (signal.length == 0U)
-
260 throw std::runtime_error("Signal length must be within 1..64 bits.");
-
261 signal.isLittleEndian = fields[ByteOrder].str() == "1";
-
262 signal.isSigned = fields[Signedness].str() == "-";
-
263
-
264 signal.factor = ParsePhysicalValue(fields[Factor].str(), "signal factor");
-
265 signal.offset = ParsePhysicalValue(fields[Offset].str(), "signal offset");
-
266 signal.minimum = ParsePhysicalValue(fields[Minimum].str(), "signal minimum");
-
267 signal.maximum = ParsePhysicalValue(fields[Maximum].str(), "signal maximum");
-
268 if (signal.minimum > signal.maximum)
-
269 throw std::runtime_error("Signal minimum exceeds maximum.");
-
270 signal.unit = fields[Unit].str();
-
271 signal.receivers = SplitReceivers(fields[Receivers].str());
-
272 return signal;
-
273}
-
274
-
275void DbcCommentHandler::ParseCommentLine(const std::string &line, DbcDatabase &database) {
-
276 /*
-
277 * Examples:
-
278 * CM_ BO_ 256 "Frame comment";
-
279 * CM_ SG_ 256 EngineSpeed "Signal comment";
-
280 */
-
281
-
282 std::istringstream stream(line);
-
283 std::string token;
-
284 stream >> token;
-
285
-
286 if (token != "CM_")
-
287 return;
-
288
-
289 stream >> token;
-
290 if (token != "BO_" && token != "SG_")
-
291 return;
-
292
-
293 std::uint32_t rawCanId = 0U;
-
294 std::uint32_t canId = 0U;
-
295 bool isExtended = false;
-
296 std::string signalName;
-
297 stream >> rawCanId;
-
298 if (token == "SG_")
-
299 stream >> signalName;
-
300 NormalizeCanId(rawCanId, canId, isExtended);
-
301
-
302 const std::string::size_type quoteBegin = line.find('"');
-
303 const std::string::size_type quoteEnd = line.rfind('"');
-
304 if (quoteBegin == std::string::npos || quoteEnd == std::string::npos || quoteEnd <= quoteBegin)
-
305 return;
-
306
-
307 FrameInfo *frame = FindFrameById(database, canId, isExtended);
-
308 if (frame == nullptr)
-
309 return;
-
310 const std::string comment = line.substr(quoteBegin + 1U, quoteEnd - quoteBegin - 1U);
-
311 if (token == "BO_") {
-
312 frame->comment = comment;
-
313 return;
-
314 }
-
315 SignalInfo *signal = FindSignalByName(*frame, signalName);
-
316 if (signal != nullptr)
-
317 signal->comment = comment;
-
318}
-
319
-
320namespace {
-
321 FrameInfo *FindFrameById(DbcDatabase &database,
-
322 std::uint32_t canId,
-
323 bool isExtended) {
-
324 for (FrameInfo &frame : database.frames) {
-
325 if (frame.canId == canId && frame.isExtended == isExtended)
-
326 return &frame;
-
327 }
-
328
-
329 return nullptr;
-
330 }
-
331
-
332 SignalInfo *FindSignalByName(FrameInfo &frame, const std::string &signalName) {
-
333 for (SignalInfo &signal : frame.signals) {
-
334 if (signal.name == signalName)
-
335 return &signal;
-
336 }
-
337
-
338 return nullptr;
-
339 }
-
340}// namespace
-
341
-
342void DbcFrameHandler::Handle(const std::string &line, DbcParseContext &context) const {
-
343 FrameInfo frame = ParseFrameLine(line);
-
344 if (FindFrameById(context.database, frame.canId, frame.isExtended) != nullptr)
-
345 throw std::runtime_error("Duplicate " + std::string(frame.isExtended ? "extended" : "standard") +
-
346 " frame CAN ID: " + std::to_string(frame.canId));
-
347 context.database.frames.push_back(std::move(frame));
-
348 context.currentFrame = &context.database.frames.back();
-
349 context.signalLines.emplace_back();
-
350}
-
351
-
352void DbcSignalHandler::Handle(const std::string &line, DbcParseContext &context) const {
-
353 if (context.currentFrame == nullptr)
-
354 throw std::runtime_error("Signal found before any frame definition.");
-
355 SignalInfo signal = ParseSignalLine(line);
-
356 if (FindSignalByName(*context.currentFrame, signal.name) != nullptr)
-
357 throw std::runtime_error("Duplicate signal name '" + signal.name +
-
358 "' in frame '" + context.currentFrame->name + "'.");
-
359 const std::uint64_t payloadBits = static_cast<std::uint64_t>(context.currentFrame->dlc) * 8U;
-
360 std::uint64_t bit = signal.startBit;
-
361 for (std::uint32_t index = 0U; index < signal.length; ++index) {
-
362 if (bit >= payloadBits)
-
363 throw std::runtime_error("Signal exceeds the frame payload length.");
-
364 bit = NextSignalBit(bit, signal.isLittleEndian);
-
365 }
-
366 context.currentFrame->signals.push_back(std::move(signal));
-
367 context.signalLines.back().push_back(context.lineNumber);
-
368}
-
369
-
370void DbcCommentHandler::Handle(const std::string &line, DbcParseContext &context) const {
-
371 ParseCommentLine(line, context.database);
-
372}
-
373
-
374DbcStatementDispatcher::DbcStatementDispatcher() {
-
375 RegisterType<DbcHeaderHandler>("VERSION");
-
376 RegisterType<DbcHeaderHandler>("NS_");
-
377 RegisterType<DbcHeaderHandler>("NS_:");
-
378 RegisterType<DbcHeaderHandler>("BS_");
-
379 RegisterType<DbcHeaderHandler>("BS_:");
-
380 RegisterType<DbcHeaderHandler>("BU_");
-
381 RegisterType<DbcHeaderHandler>("BU_:");
-
382 RegisterType<DbcUnsupportedMultiplexingHandler>("SG_MUL_VAL_");
-
383 RegisterType<DbcFrameHandler>("BO_");
-
384 RegisterType<DbcSignalHandler>("SG_");
-
385 RegisterType<DbcCommentHandler>("CM_");
-
386 RegisterType<DbcValueDescriptionHandler>("VAL_");
-
387}
-
388
-
389void DbcHeaderHandler::Handle(const std::string &, DbcParseContext &) const {}
-
390
-
-
391bool DbcStatementDispatcher::Dispatch(const std::string &line,
-
392 DbcParseContext &context) const {
-
393 std::istringstream stream(line);
-
394 std::string keyword;
-
395 stream >> keyword;
-
396 const HandlerMap::const_iterator handler = m_handlers.find(keyword);
-
397 if (handler == m_handlers.end())
-
398 return false;
-
399 handler->second->Handle(line, context);
-
400 return true;
-
401}
+
4#include "signal_layout.h"
+
5#include <charconv>
+
6#include <cmath>
+
7#include <limits>
+
8#include <regex>
+
9#include <sstream>
+
10#include <stdexcept>
+
11
+
12namespace {
+
13 std::uint32_t ParseDecimal(const std::string &text,
+
14 std::uint32_t maximum,
+
15 const char *field) {
+
16 std::uint32_t value = 0U;
+
17 const std::from_chars_result parsed = std::from_chars(text.data(), text.data() + text.size(), value);
+
18 if (text.empty() || parsed.ec != std::errc() ||
+
19 parsed.ptr != text.data() + text.size() || value > maximum)
+
20 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
+
21 return value;
+
22 }
+
23
+
24 double ParsePhysicalValue(const std::string &text, const char *field) {
+
25 static const std::regex number(
+
26 R"(^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?$)");
+
27 if (!std::regex_match(text, number))
+
28 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
+
29 std::size_t consumed = 0U;
+
30 double value;
+
31 try {
+
32 value = std::stod(text, &consumed);
+
33 } catch (const std::exception &) {
+
34 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
+
35 }
+
36 if (consumed != text.size() || !std::isfinite(value))
+
37 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
+
38 return value;
+
39 }
+
40
+
41 Multiplexing ParseMultiplexingMarker(const std::string &marker) {
+
42 Multiplexing result;
+
43 if (marker == "M")
+
44 result.role = MultiplexRole::Selector;
+
45 else if (!marker.empty()) {
+
46 if (marker[0] != 'm')
+
47 throw std::runtime_error("Unsupported multiplexing marker.");
+
48 const std::string value = marker.substr(1U);
+
49 const std::from_chars_result parsed =
+
50 std::from_chars(value.data(), value.data() + value.size(), result.value);
+
51 if (value.empty() || parsed.ec != std::errc() || parsed.ptr != value.data() + value.size())
+
52 throw std::runtime_error("Invalid or unsupported multiplexing marker.");
+
53 result.role = MultiplexRole::Branch;
+
54 }
+
55
+
56 return result;
+
57 }
+
58
+
59 RawSignalValue ParseValueKey(const std::string &number) {
+
60 RawSignalValue key;
+
61 if (!number.empty() && number[0] == '-') {
+
62 std::int64_t value = 0;
+
63 const std::from_chars_result parsed =
+
64 std::from_chars(number.data(), number.data() + number.size(), value);
+
65 if (parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
+
66 throw std::runtime_error("Invalid VAL_ integer: " + number);
+
67 key = NormalizeValueKey(RawSignalValue(value));
+
68 } else {
+
69 std::uint64_t value = 0;
+
70 const std::from_chars_result parsed =
+
71 std::from_chars(number.data(), number.data() + number.size(), value);
+
72 if (number.empty() || parsed.ec != std::errc() || parsed.ptr != number.data() + number.size())
+
73 throw std::runtime_error("Invalid VAL_ integer: " + number);
+
74 key = value;
+
75 }
+
76 return key;
+
77 }
+
78
+
79 std::string UnescapeDescription(const std::string &quoted) {
+
80 std::string description;
+
81 for (std::size_t i = 0; i < quoted.size(); ++i) {
+
82 if (quoted[i] == '\\')
+
83 ++i;
+
84 description += quoted[i];
+
85 }
+
86 return description;
+
87 }
+
88
+
89 FrameInfo *FindUniqueFrame(DbcDatabase &database, std::uint32_t canId, bool isExtended) {
+
90 FrameInfo *frame = nullptr;
+
91 for (FrameInfo &candidate : database.frames) {
+
92 if (candidate.canId == canId && candidate.isExtended == isExtended) {
+
93 if (frame)
+
94 throw std::runtime_error("Ambiguous VAL_ frame reference.");
+
95 frame = &candidate;
+
96 }
+
97 }
+
98 if (!frame)
+
99 throw std::runtime_error("Unresolved VAL_ frame reference.");
+
100 return frame;
+
101 }
+
102
+
103 SignalInfo *FindUniqueSignal(FrameInfo &frame, const std::string &signalName) {
+
104 SignalInfo *signal = nullptr;
+
105 for (SignalInfo &candidate : frame.signals) {
+
106 if (candidate.name == signalName) {
+
107 if (signal)
+
108 throw std::runtime_error("Ambiguous VAL_ signal reference: " + signalName);
+
109 signal = &candidate;
+
110 }
+
111 }
+
112 if (!signal)
+
113 throw std::runtime_error("Unresolved VAL_ signal reference: " + signalName);
+
114 return signal;
+
115 }
+
116
+
117 std::uint64_t NextSignalBit(std::uint64_t bit, bool isLittleEndian) {
+
118 if (isLittleEndian)
+
119 return bit + 1U;
+
120 // From bit 0, Motorola continues at bit 7 of the next byte (0 -> 15).
+
121 return bit % 8U == 0U ? bit + 15U : bit - 1U;
+
122 }
+
123
+
124 FrameInfo *FindFrameById(DbcDatabase &, std::uint32_t, bool);
+
125 SignalInfo *FindSignalByName(FrameInfo &, const std::string &);
+
126} // namespace
+
127
+
128namespace {
+
129 std::vector<std::string> SplitReceivers(const std::string &text) {
+
130 std::vector<std::string> receivers;
+
131 std::string token;
+
132 std::istringstream stream(text);
+
133
+
134 while (std::getline(stream, token, ',')) {
+
135 token = dbc_detail::TrimText(token);
+
136 if (!token.empty())
+
137 receivers.push_back(token);
+
138 }
+
139
+
140 return receivers;
+
141 }
+
142
+
143 void NormalizeCanId(std::uint32_t rawCanId, std::uint32_t &normalizedCanId, bool &isExtended) {
+
144 /*
+
145 * DBC commonly stores extended identifiers with bit 31 set.
+
146 * Example:
+
147 * raw id = 0x80000000 | actual_29_bit_id
+
148 */
+
149 if ((rawCanId & 0x80000000U) != 0U) {
+
150 isExtended = true;
+
151 normalizedCanId = (rawCanId & 0x1FFFFFFFU);
+
152 } else {
+
153 isExtended = (rawCanId > 0x7FFU);
+
154 normalizedCanId = rawCanId;
+
155 }
+
156 }
+
157
+
158 std::uint32_t TryExtractPgn(std::uint32_t canId, bool isExtended, bool &hasPgn) {
+
159 hasPgn = false;
+
160
+
161 if (!isExtended)
+
162 return 0U;
+
163
+
164 if ((canId & 0x1FFFFFFFU) != canId)
+
165 return 0U;
+
166
+
167 const std::uint32_t pf = (canId >> 16U) & 0xFFU;
+
168 const std::uint32_t ps = (canId >> 8U) & 0xFFU;
+
169 const std::uint32_t dp = (canId >> 24U) & 0x01U;
+
170
+
171 std::uint32_t pgn = 0U;
+
172
+
173 if (pf < 240U)
+
174 pgn = (dp << 16U) | (pf << 8U);
+
175 else
+
176 pgn = (dp << 16U) | (pf << 8U) | ps;
+
177
+
178 hasPgn = true;
+
179 return pgn;
+
180 }
+
181
+
182} // namespace
+
183
+
184FrameInfo DbcFrameHandler::ParseFrameLine(const std::string &line) {
+
185 /*
+
186 * Example:
+
187 * BO_ 256 EngineData: 8 EEC1
+
188 */
+
189
+
190 std::istringstream stream(line);
+
191 std::string keyword;
+
192 std::string idText;
+
193 if (!(stream >> keyword >> idText) || keyword != "BO_")
+
194 throw std::runtime_error("Missing CAN ID in frame definition.");
+
195
+
196 const std::uint32_t rawCanId = ParseDecimal(
+
197 idText, std::numeric_limits<std::uint32_t>::max(), "CAN ID");
+
198 // Only bit 31 may accompany the actual 29-bit identifier.
+
199 if ((rawCanId & 0x60000000U) != 0U)
+
200 throw std::runtime_error("CAN ID exceeds the 29-bit identifier range.");
+
201
+
202 std::string nameText;
+
203 if (!std::getline(stream, nameText, ':') || stream.eof())
+
204 throw std::runtime_error("Missing ':' in frame definition.");
+
205 std::istringstream nameStream(nameText);
+
206 FrameInfo frame;
+
207 std::string extra;
+
208 if (!(nameStream >> frame.name) || (nameStream >> extra))
+
209 throw std::runtime_error("Missing or invalid frame name.");
+
210
+
211 std::string lengthText;
+
212 if (!(stream >> lengthText))
+
213 throw std::runtime_error("Missing payload length in frame definition.");
+
214 frame.dlc = static_cast<std::uint8_t>(ParseDecimal(
+
215 lengthText, std::numeric_limits<std::uint8_t>::max(), "payload length"));
+
216 if (!(stream >> frame.transmitter))
+
217 throw std::runtime_error("Missing transmitter in frame definition.");
+
218 if (stream >> extra)
+
219 throw std::runtime_error("Unexpected trailing field in frame definition: " + extra);
+
220
+
221 NormalizeCanId(rawCanId, frame.canId, frame.isExtended);
+
222 frame.pgn = TryExtractPgn(frame.canId, frame.isExtended, frame.hasPgn);
+
223
+
224 return frame;
+
225}
+
226
+
227SignalInfo DbcSignalHandler::ParseSignalLine(const std::string &line) {
+
228 // Match the complete statement before converting any numeric fields.
+
229 enum Field {
+
230 NameAndMarker = 1, StartBit, BitLength, ByteOrder, Signedness,
+
231 Factor, Offset, Minimum, Maximum, Unit, Receivers
+
232 };
+
233 static const std::regex syntax(
+
234 // Name and optional multiplexing marker.
+
235 R"dbc(^SG_[ \t]+([^:]+):[ \t]*)dbc"
+
236 // Bit position, length, byte order and signedness.
+
237 R"dbc(([^| \t]+)[ \t]*\|[ \t]*([^@ \t]+)[ \t]*@[ \t]*([01])([+-])[ \t]*)dbc"
+
238 // Scaling factor and offset.
+
239 R"dbc(\‍([ \t]*([^, \t]+)[ \t]*,[ \t]*([^ )\t]+)[ \t]*\)[ \t]*)dbc"
+
240 // Physical range.
+
241 R"dbc(\[[ \t]*([^| \t]+)[ \t]*\|[ \t]*([^\] \t]+)[ \t]*\][ \t]*)dbc"
+
242 // Unit and receivers.
+
243 R"dbc("([^"\r\n]*)"[ \t]+([^ \t,]+(?:[ \t]*,[ \t]*[^ \t,]+)*)[ \t]*$)dbc");
+
244 std::smatch fields;
+
245 if (!std::regex_match(line, fields, syntax))
+
246 throw std::runtime_error("Malformed or unsupported SG_ signal definition.");
+
247
+
248 SignalInfo signal;
+
249 std::istringstream nameFields(fields[NameAndMarker].str());
+
250 std::string marker;
+
251 std::string extra;
+
252 nameFields >> signal.name >> marker;
+
253 if (signal.name.empty() || (nameFields >> extra))
+
254 throw std::runtime_error("Invalid signal name or multiplexing marker.");
+
255 signal.multiplexing = ParseMultiplexingMarker(marker);
+
256
+
257 signal.startBit = ParseDecimal(fields[StartBit].str(),
+
258 std::numeric_limits<std::uint32_t>::max(), "signal start bit");
+
259 signal.length = ParseDecimal(fields[BitLength].str(), 64U, "signal length");
+
260 if (signal.length == 0U)
+
261 throw std::runtime_error("Signal length must be within 1..64 bits.");
+
262 signal.isLittleEndian = fields[ByteOrder].str() == "1";
+
263 signal.isSigned = fields[Signedness].str() == "-";
+
264
+
265 signal.factor = ParsePhysicalValue(fields[Factor].str(), "signal factor");
+
266 signal.offset = ParsePhysicalValue(fields[Offset].str(), "signal offset");
+
267 signal.minimum = ParsePhysicalValue(fields[Minimum].str(), "signal minimum");
+
268 signal.maximum = ParsePhysicalValue(fields[Maximum].str(), "signal maximum");
+
269 if (signal.minimum > signal.maximum)
+
270 throw std::runtime_error("Signal minimum exceeds maximum.");
+
271 signal.unit = fields[Unit].str();
+
272 signal.receivers = SplitReceivers(fields[Receivers].str());
+
273 return signal;
+
274}
+
275
+
276void DbcCommentHandler::ParseCommentLine(const std::string &line, DbcDatabase &database) {
+
277 /*
+
278 * Examples:
+
279 * CM_ BO_ 256 "Frame comment";
+
280 * CM_ SG_ 256 EngineSpeed "Signal comment";
+
281 */
+
282
+
283 std::istringstream stream(line);
+
284 std::string token;
+
285 stream >> token;
+
286
+
287 if (token != "CM_")
+
288 return;
+
289
+
290 stream >> token;
+
291 if (token != "BO_" && token != "SG_")
+
292 return;
+
293
+
294 std::uint32_t rawCanId = 0U;
+
295 std::uint32_t canId = 0U;
+
296 bool isExtended = false;
+
297 std::string signalName;
+
298 stream >> rawCanId;
+
299 if (token == "SG_")
+
300 stream >> signalName;
+
301 NormalizeCanId(rawCanId, canId, isExtended);
+
302
+
303 const std::string::size_type quoteBegin = line.find('"');
+
304 const std::string::size_type quoteEnd = line.rfind('"');
+
305 if (quoteBegin == std::string::npos || quoteEnd == std::string::npos || quoteEnd <= quoteBegin)
+
306 return;
+
307
+
308 FrameInfo *frame = FindFrameById(database, canId, isExtended);
+
309 if (frame == nullptr)
+
310 return;
+
311 const std::string comment = line.substr(quoteBegin + 1U, quoteEnd - quoteBegin - 1U);
+
312 if (token == "BO_") {
+
313 frame->comment = comment;
+
314 return;
+
315 }
+
316 SignalInfo *signal = FindSignalByName(*frame, signalName);
+
317 if (signal != nullptr)
+
318 signal->comment = comment;
+
319}
+
320
+
321namespace {
+
322 FrameInfo *FindFrameById(DbcDatabase &database,
+
323 std::uint32_t canId,
+
324 bool isExtended) {
+
325 for (FrameInfo &frame : database.frames) {
+
326 if (frame.canId == canId && frame.isExtended == isExtended)
+
327 return &frame;
+
328 }
+
329
+
330 return nullptr;
+
331 }
+
332
+
333 SignalInfo *FindSignalByName(FrameInfo &frame, const std::string &signalName) {
+
334 for (SignalInfo &signal : frame.signals) {
+
335 if (signal.name == signalName)
+
336 return &signal;
+
337 }
+
338
+
339 return nullptr;
+
340 }
+
341}// namespace
+
342
+
343void DbcFrameHandler::Handle(const std::string &line, DbcParseContext &context) const {
+
344 FrameInfo frame = ParseFrameLine(line);
+
345 if (FindFrameById(context.database, frame.canId, frame.isExtended) != nullptr)
+
346 throw std::runtime_error("Duplicate " + std::string(frame.isExtended ? "extended" : "standard") +
+
347 " frame CAN ID: " + std::to_string(frame.canId));
+
348 context.database.frames.push_back(std::move(frame));
+
349 context.currentFrame = &context.database.frames.back();
+
350 context.signalLines.emplace_back();
+
351}
+
352
+
353void DbcSignalHandler::Handle(const std::string &line, DbcParseContext &context) const {
+
354 if (context.currentFrame == nullptr)
+
355 throw std::runtime_error("Signal found before any frame definition.");
+
356 SignalInfo signal = ParseSignalLine(line);
+
357 if (FindSignalByName(*context.currentFrame, signal.name) != nullptr)
+
358 throw std::runtime_error("Duplicate signal name '" + signal.name +
+
359 "' in frame '" + context.currentFrame->name + "'.");
+
360 if (!IsValidSignalLayout(signal.startBit, signal.length, signal.isLittleEndian,
+
361 context.currentFrame->dlc))
+
362 throw std::runtime_error("Signal exceeds the frame payload length.");
+
363 context.currentFrame->signals.push_back(std::move(signal));
+
364 context.signalLines.back().push_back(context.lineNumber);
+
365}
+
366
+
367void DbcCommentHandler::Handle(const std::string &line, DbcParseContext &context) const {
+
368 ParseCommentLine(line, context.database);
+
369}
+
370
+
371DbcStatementDispatcher::DbcStatementDispatcher() {
+
372 RegisterType<DbcHeaderHandler>("VERSION");
+
373 RegisterType<DbcHeaderHandler>("NS_");
+
374 RegisterType<DbcHeaderHandler>("NS_:");
+
375 RegisterType<DbcHeaderHandler>("BS_");
+
376 RegisterType<DbcHeaderHandler>("BS_:");
+
377 RegisterType<DbcHeaderHandler>("BU_");
+
378 RegisterType<DbcHeaderHandler>("BU_:");
+
379 RegisterType<DbcUnsupportedMultiplexingHandler>("SG_MUL_VAL_");
+
380 RegisterType<DbcFrameHandler>("BO_");
+
381 RegisterType<DbcSignalHandler>("SG_");
+
382 RegisterType<DbcCommentHandler>("CM_");
+
383 RegisterType<DbcValueDescriptionHandler>("VAL_");
+
384}
+
385
+
386void DbcHeaderHandler::Handle(const std::string &, DbcParseContext &) const {}
+
387
+
+
388bool DbcStatementDispatcher::Dispatch(const std::string &line,
+
389 DbcParseContext &context) const {
+
390 std::istringstream stream(line);
+
391 std::string keyword;
+
392 stream >> keyword;
+
393 const HandlerMap::const_iterator handler = m_handlers.find(keyword);
+
394 if (handler == m_handlers.end())
+
395 return false;
+
396 handler->second->Handle(line, context);
+
397 return true;
+
398}
-
402
-
403void DbcValueDescriptionHandler::Handle(const std::string &line, DbcParseContext &context) const {
-
404 static const std::regex header(R"(^VAL_[ \t]+([^ \t]+)[ \t]+([^ \t;]+)[ \t]+(.*)$)");
-
405 static const std::regex pair(R"dbc(^([^ \t]+)[ \t]+"((?:[^"\\]|\\["\\])*)")dbc");
-
406 std::smatch fields;
-
407 if (!std::regex_match(line, fields, header))
-
408 throw std::runtime_error("Malformed or unsupported VAL_ statement.");
-
409 const std::uint32_t rawId = ParseDecimal(fields[1].str(), std::numeric_limits<std::uint32_t>::max(), "VAL_ CAN ID");
-
410 if ((rawId & 0x60000000U) != 0U)
-
411 throw std::runtime_error("VAL_ CAN ID exceeds the 29-bit identifier range.");
- -
413 NormalizeCanId(rawId, pending.canId, pending.isExtended);
-
414 pending.signalName = fields[2].str();
-
415 pending.lineNumber = context.lineNumber;
-
416 std::string rest = fields[3].str();
-
417 while (rest != ";") {
-
418 if (!std::regex_search(rest, fields, pair))
-
419 throw std::runtime_error("Malformed VAL_ value/description pair or missing semicolon.");
-
420 const std::string number = fields[1].str();
-
421 const RawSignalValue key = ParseValueKey(number);
-
422 const std::string description = UnescapeDescription(fields[2].str());
-
423 if (!pending.descriptions.emplace(key, description).second)
-
424 throw std::runtime_error("Duplicate VAL_ value key: " + number);
-
425 const std::size_t consumed = static_cast<std::size_t>(fields.length());
-
426 rest.erase(0, consumed);
-
427 if (!rest.empty() && rest[0] != ';' && rest[0] != ' ' && rest[0] != '\t')
-
428 throw std::runtime_error("Missing whitespace between VAL_ pairs.");
-
429 rest = dbc_detail::TrimText(rest);
-
430 }
-
431 if (pending.descriptions.empty())
-
432 throw std::runtime_error("VAL_ requires at least one value/description pair.");
-
433 context.pendingValues.push_back(std::move(pending));
-
434}
-
435
-
436void DbcParseContext::ResolveValueDescriptions(const std::string &filePath) {
-
437 for (const PendingValueDescriptions &pending: pendingValues) {
-
438 try {
-
439 FrameInfo *frame = FindUniqueFrame(database, pending.canId, pending.isExtended);
-
440 SignalInfo *signal = FindUniqueSignal(*frame, pending.signalName);
-
441 for (const ValueDescriptions::value_type &entry: pending.descriptions) {
-
442 if (!signal->valueDescriptions.insert(entry).second)
-
443 throw std::runtime_error("Duplicate VAL_ value key for signal: " + pending.signalName);
-
444 }
-
445 } catch (const std::exception &error) {
-
446 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidValueDescription,
-
447 filePath, pending.lineNumber, error.what());
-
448 }
-
449 }
-
450}
-
451
-
452void DbcUnsupportedMultiplexingHandler::Handle(const std::string &, DbcParseContext &) const {
-
453 throw std::runtime_error("Extended and nested multiplexing are unsupported.");
-
454}
-
455
-
456namespace {
-
457 std::vector<std::uint64_t> SignalBits(const SignalInfo &signal) {
-
458 std::vector<std::uint64_t> bits;
-
459 std::uint64_t bit = signal.startBit;
-
460 for (std::uint32_t index = 0U; index < signal.length; ++index) {
-
461 bits.push_back(bit);
-
462 bit = NextSignalBit(bit, signal.isLittleEndian);
-
463 }
-
464 return bits;
-
465 }
-
466
-
467 bool AreMutuallyExclusive(const SignalInfo &first, const SignalInfo &second) {
-
468 return first.multiplexing.role == MultiplexRole::Branch &&
-
469 second.multiplexing.role == MultiplexRole::Branch &&
-
470 first.multiplexing.value != second.multiplexing.value;
-
471 }
-
472
-
473 std::string ValidateBranch(const SignalInfo &signal, const SignalInfo *selector) {
-
474 if (signal.multiplexing.role != MultiplexRole::Branch)
-
475 return {};
-
476 if (selector == nullptr)
-
477 return "Multiplexed branch has no selector.";
-
478 if (selector->length < 64U && signal.multiplexing.value >= (std::uint64_t(1U) << selector->length))
-
479 return "Branch value exceeds selector raw range.";
-
480 return {};
-
481 }
-
482
-
483 const SignalInfo *FindSelector(const FrameInfo &frame,
-
484 const std::vector<std::size_t> &lines,
-
485 const std::string &filePath) {
-
486 const SignalInfo *selector = nullptr;
-
487 for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
-
488 const SignalInfo &signal = frame.signals[index];
-
489 if (signal.multiplexing.role != MultiplexRole::Selector)
-
490 continue;
-
491 if (selector != nullptr || signal.isSigned)
-
492 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing,
-
493 filePath, lines[index],
-
494 "Multiplexing requires one unsigned selector per frame.");
-
495 selector = &signal;
-
496 }
-
497 return selector;
-
498 }
-
499
-
500 bool SignalsOverlap(const SignalInfo &first, const SignalInfo &second) {
-
501 const std::vector<std::uint64_t> firstBits = SignalBits(first);
-
502 const std::vector<std::uint64_t> secondBits = SignalBits(second);
-
503 for (const std::uint64_t firstBit : firstBits) {
-
504 for (const std::uint64_t secondBit : secondBits) {
-
505 if (firstBit == secondBit)
-
506 return true;
-
507 }
-
508 }
-
509 return false;
-
510 }
-
511}
-
512
-
513void DbcParseContext::ValidateMultiplexing(const std::string &filePath) const {
-
514 for (std::size_t frameIndex = 0U; frameIndex < database.frames.size(); ++frameIndex) {
-
515 const FrameInfo &frame = database.frames[frameIndex];
-
516 const SignalInfo *selector = FindSelector(frame, signalLines[frameIndex], filePath);
-
517 for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
-
518 const SignalInfo &signal = frame.signals[index];
-
519 std::string error = ValidateBranch(signal, selector);
-
520 for (std::size_t previous = 0U; previous < index && error.empty(); ++previous) {
-
521 const SignalInfo &other = frame.signals[previous];
-
522 if (!AreMutuallyExclusive(signal, other) && SignalsOverlap(signal, other))
-
523 error = "Signals overlap without mutually exclusive multiplexing conditions.";
-
524 }
-
525 if (!error.empty())
-
526 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing,
-
527 filePath, signalLines[frameIndex][index], error);
-
528 }
-
529 }
-
530}
+
399
+
400void DbcValueDescriptionHandler::Handle(const std::string &line, DbcParseContext &context) const {
+
401 static const std::regex header(R"(^VAL_[ \t]+([^ \t]+)[ \t]+([^ \t;]+)[ \t]+(.*)$)");
+
402 static const std::regex pair(R"dbc(^([^ \t]+)[ \t]+"((?:[^"\\]|\\["\\])*)")dbc");
+
403 std::smatch fields;
+
404 if (!std::regex_match(line, fields, header))
+
405 throw std::runtime_error("Malformed or unsupported VAL_ statement.");
+
406 const std::uint32_t rawId = ParseDecimal(fields[1].str(), std::numeric_limits<std::uint32_t>::max(), "VAL_ CAN ID");
+
407 if ((rawId & 0x60000000U) != 0U)
+
408 throw std::runtime_error("VAL_ CAN ID exceeds the 29-bit identifier range.");
+ +
410 NormalizeCanId(rawId, pending.canId, pending.isExtended);
+
411 pending.signalName = fields[2].str();
+
412 pending.lineNumber = context.lineNumber;
+
413 std::string rest = fields[3].str();
+
414 while (rest != ";") {
+
415 if (!std::regex_search(rest, fields, pair))
+
416 throw std::runtime_error("Malformed VAL_ value/description pair or missing semicolon.");
+
417 const std::string number = fields[1].str();
+
418 const RawSignalValue key = ParseValueKey(number);
+
419 const std::string description = UnescapeDescription(fields[2].str());
+
420 if (!pending.descriptions.emplace(key, description).second)
+
421 throw std::runtime_error("Duplicate VAL_ value key: " + number);
+
422 const std::size_t consumed = static_cast<std::size_t>(fields.length());
+
423 rest.erase(0, consumed);
+
424 if (!rest.empty() && rest[0] != ';' && rest[0] != ' ' && rest[0] != '\t')
+
425 throw std::runtime_error("Missing whitespace between VAL_ pairs.");
+
426 rest = dbc_detail::TrimText(rest);
+
427 }
+
428 if (pending.descriptions.empty())
+
429 throw std::runtime_error("VAL_ requires at least one value/description pair.");
+
430 context.pendingValues.push_back(std::move(pending));
+
431}
+
432
+
433void DbcParseContext::ResolveValueDescriptions(const std::string &filePath) {
+
434 for (const PendingValueDescriptions &pending: pendingValues) {
+
435 try {
+
436 FrameInfo *frame = FindUniqueFrame(database, pending.canId, pending.isExtended);
+
437 SignalInfo *signal = FindUniqueSignal(*frame, pending.signalName);
+
438 for (const ValueDescriptions::value_type &entry: pending.descriptions) {
+
439 if (!signal->valueDescriptions.insert(entry).second)
+
440 throw std::runtime_error("Duplicate VAL_ value key for signal: " + pending.signalName);
+
441 }
+
442 } catch (const std::exception &error) {
+
443 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidValueDescription,
+
444 filePath, pending.lineNumber, error.what());
+
445 }
+
446 }
+
447}
+
448
+
449void DbcUnsupportedMultiplexingHandler::Handle(const std::string &, DbcParseContext &) const {
+
450 throw std::runtime_error("Extended and nested multiplexing are unsupported.");
+
451}
+
452
+
453namespace {
+
454 std::vector<std::uint64_t> SignalBits(const SignalInfo &signal) {
+
455 std::vector<std::uint64_t> bits;
+
456 std::uint64_t bit = signal.startBit;
+
457 for (std::uint32_t index = 0U; index < signal.length; ++index) {
+
458 bits.push_back(bit);
+
459 bit = NextSignalBit(bit, signal.isLittleEndian);
+
460 }
+
461 return bits;
+
462 }
+
463
+
464 bool AreMutuallyExclusive(const SignalInfo &first, const SignalInfo &second) {
+
465 return first.multiplexing.role == MultiplexRole::Branch &&
+
466 second.multiplexing.role == MultiplexRole::Branch &&
+
467 first.multiplexing.value != second.multiplexing.value;
+
468 }
+
469
+
470 std::string ValidateBranch(const SignalInfo &signal, const SignalInfo *selector) {
+
471 if (signal.multiplexing.role != MultiplexRole::Branch)
+
472 return {};
+
473 if (selector == nullptr)
+
474 return "Multiplexed branch has no selector.";
+
475 if (selector->length < 64U && signal.multiplexing.value >= (std::uint64_t(1U) << selector->length))
+
476 return "Branch value exceeds selector raw range.";
+
477 return {};
+
478 }
+
479
+
480 const SignalInfo *FindSelector(const FrameInfo &frame,
+
481 const std::vector<std::size_t> &lines,
+
482 const std::string &filePath) {
+
483 const SignalInfo *selector = nullptr;
+
484 for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
+
485 const SignalInfo &signal = frame.signals[index];
+
486 if (signal.multiplexing.role != MultiplexRole::Selector)
+
487 continue;
+
488 if (selector != nullptr || signal.isSigned)
+
489 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing,
+
490 filePath, lines[index],
+
491 "Multiplexing requires one unsigned selector per frame.");
+
492 selector = &signal;
+
493 }
+
494 return selector;
+
495 }
+
496
+
497 bool SignalsOverlap(const SignalInfo &first, const SignalInfo &second) {
+
498 const std::vector<std::uint64_t> firstBits = SignalBits(first);
+
499 const std::vector<std::uint64_t> secondBits = SignalBits(second);
+
500 for (const std::uint64_t firstBit : firstBits) {
+
501 for (const std::uint64_t secondBit : secondBits) {
+
502 if (firstBit == secondBit)
+
503 return true;
+
504 }
+
505 }
+
506 return false;
+
507 }
+
508}
+
509
+
510void DbcParseContext::ValidateMultiplexing(const std::string &filePath) const {
+
511 for (std::size_t frameIndex = 0U; frameIndex < database.frames.size(); ++frameIndex) {
+
512 const FrameInfo &frame = database.frames[frameIndex];
+
513 const SignalInfo *selector = FindSelector(frame, signalLines[frameIndex], filePath);
+
514 for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
+
515 const SignalInfo &signal = frame.signals[index];
+
516 std::string error = ValidateBranch(signal, selector);
+
517 for (std::size_t previous = 0U; previous < index && error.empty(); ++previous) {
+
518 const SignalInfo &other = frame.signals[previous];
+
519 if (!AreMutuallyExclusive(signal, other) && SignalsOverlap(signal, other))
+
520 error = "Signals overlap without mutually exclusive multiplexing conditions.";
+
521 }
+
522 if (!error.empty())
+
523 throw DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing,
+
524 filePath, signalLines[frameIndex][index], error);
+
525 }
+
526 }
+
527}
-
bool Dispatch(const std::string &line, DbcParseContext &context) const
+
bool Dispatch(const std::string &line, DbcParseContext &context) const
ByteOrder
Signal byte order used for runtime decoding.
Parsed DBC content stored in a simple internal form.
std::vector< FrameInfo > frames
diff --git a/docs/html/dbc__statement__handler_8h_source.html b/docs/html/dbc__statement__handler_8h_source.html index 139f96a..c7c5f46 100644 --- a/docs/html/dbc__statement__handler_8h_source.html +++ b/docs/html/dbc__statement__handler_8h_source.html @@ -231,7 +231,7 @@ $(document).ready(function(){initNavTree('dbc__statement__handler_8h_source.html -
bool Dispatch(const std::string &line, DbcParseContext &context) const
+
bool Dispatch(const std::string &line, DbcParseContext &context) const
diff --git a/docs/html/dbc__validation__test_8cpp_source.html b/docs/html/dbc__validation__test_8cpp_source.html new file mode 100644 index 0000000..d2c978f --- /dev/null +++ b/docs/html/dbc__validation__test_8cpp_source.html @@ -0,0 +1,506 @@ + + + + + + + +DBC Framework: dbc_validation_test.cpp Source File + + + + + + + + + + + + + +
+
+ + + + + + +
+
DBC Framework +
+
DBC parsing and CAN signal decoding framework
+
+
+ + + + + + + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+
+
+
+
+
Loading...
+
Searching...
+
No Matches
+
+
+
+
+ +
+
dbc_validation_test.cpp
+
+
+
1// SPDX-License-Identifier: MIT
+ +
3#include "dbc_decoder.h"
+
4#include <QtTest>
+
5#include <limits>
+
6#include <stdexcept>
+
7#include <utility>
+
8
+
9namespace {
+
10 DecodeFrame ValidFrame() {
+
11 DecodeFrame frame;
+
12 frame.canId = 0x123U;
+
13 frame.dlc = 8U;
+
14 DecodeSignal signal;
+
15 signal.name = "Value";
+
16 signal.length = 8U;
+
17 frame.signals.push_back(signal);
+
18 return frame;
+
19 }
+
20
+
21 DbcDatabase ParsedDefinitions(const std::vector<DecodeFrame> &frames) {
+
22 DbcDatabase database;
+
23 for (const DecodeFrame &frame: frames) {
+
24 FrameInfo target;
+
25 target.canId = frame.canId;
+
26 target.isExtended = frame.isExtended;
+
27 target.dlc = frame.dlc;
+
28 for (const DecodeSignal &signal: frame.signals) {
+
29 SignalInfo copy;
+
30 copy.name = signal.name;
+
31 copy.startBit = signal.startBit;
+
32 copy.length = signal.length;
+
33 copy.isLittleEndian = signal.byteOrder == ByteOrder::Intel;
+
34 copy.isSigned = signal.valueType == ValueType::Signed;
+
35 copy.factor = signal.factor;
+
36 copy.offset = signal.offset;
+
37 copy.minimum = signal.minimum;
+
38 copy.maximum = signal.maximum;
+
39 copy.multiplexing = signal.multiplexing;
+
40 target.signals.push_back(copy);
+
41 }
+
42 database.frames.push_back(target);
+
43 }
+
44 return database;
+
45 }
+
46}// namespace
+
47
+
48class DbcValidationTest : public QObject {
+
49 Q_OBJECT
+
50private Q_SLOTS:
+
51 void invalidMetadata_data();
+
52 void invalidMetadata();
+
53 void numericParameters_data();
+
54 void numericParameters();
+
55 void validBoundaries();
+
56 void multiplexingBoundaries();
+
57 void independentSnapshot();
+
58 void failedReplacement();
+
59};
+
60
+
61void DbcValidationTest::invalidMetadata_data() {
+
62 QTest::addColumn<int>("kind");
+
63 QTest::newRow("standard-id") << 0;
+
64 QTest::newRow("extended-id") << 1;
+
65 QTest::newRow("encoded-id") << 2;
+
66 QTest::newRow("duplicate-frame") << 3;
+
67 QTest::newRow("duplicate-signal") << 4;
+
68 QTest::newRow("zero-length") << 5;
+
69 QTest::newRow("65-bits") << 6;
+
70 QTest::newRow("length-overflow") << 7;
+
71 QTest::newRow("intel-start-overflow") << 8;
+
72 QTest::newRow("motorola-start-overflow") << 9;
+
73 QTest::newRow("intel-outside-payload") << 10;
+
74 QTest::newRow("motorola-outside-payload") << 11;
+
75 QTest::newRow("zero-payload") << 12;
+
76 QTest::newRow("inverted-range") << 13;
+
77 QTest::newRow("byte-order") << 14;
+
78 QTest::newRow("value-type") << 15;
+
79 QTest::newRow("multiplex-role") << 16;
+
80 QTest::newRow("missing-selector") << 17;
+
81 QTest::newRow("signed-selector") << 18;
+
82 QTest::newRow("two-selectors") << 19;
+
83 QTest::newRow("branch-range") << 20;
+
84 QTest::newRow("unconditional-overlap") << 21;
+
85 QTest::newRow("mixed-order-overlap") << 22;
+
86 QTest::newRow("same-branch-overlap") << 23;
+
87 QTest::newRow("selector-branch-overlap") << 24;
+
88 QTest::newRow("maximum-can-id") << 25;
+
89}
+
90
+
91void DbcValidationTest::invalidMetadata() {
+
92 QFETCH(int, kind);
+
93 std::vector<DecodeFrame> frames = {ValidFrame()};
+
94 DecodeFrame &frame = frames[0];
+
95 DecodeSignal &signal = frame.signals[0];
+
96 DecodeSignal other = signal;
+
97 other.name = "Other";
+
98 switch (kind) {
+
99 case 0:
+
100 frame.canId = 0x800U;
+
101 break;
+
102 case 1:
+
103 frame.isExtended = true;
+
104 frame.canId = 0x20000000U;
+
105 break;
+
106 case 2:
+
107 frame.isExtended = true;
+
108 frame.canId = 0x80000123U;
+
109 break;
+
110 case 3:
+
111 frames.push_back(frame);
+
112 break;
+
113 case 4:
+
114 frame.signals.push_back(signal);
+
115 break;
+
116 case 5:
+
117 signal.length = 0U;
+
118 break;
+
119 case 6:
+
120 signal.length = 65U;
+
121 break;
+
122 case 7:
+
123 signal.length = std::numeric_limits<std::uint32_t>::max();
+
124 break;
+
125 case 8:
+
126 signal.startBit = std::numeric_limits<std::uint32_t>::max();
+
127 break;
+
128 case 9:
+
129 signal.byteOrder = ByteOrder::Motorola;
+
130 signal.startBit = std::numeric_limits<std::uint32_t>::max();
+
131 break;
+
132 case 10:
+
133 signal.startBit = 57U;
+
134 break;
+
135 case 11:
+
136 signal.byteOrder = ByteOrder::Motorola;
+
137 signal.startBit = 56U;
+
138 break;
+
139 case 12:
+
140 frame.dlc = 0U;
+
141 break;
+
142 case 13:
+
143 signal.minimum = 2.0;
+
144 signal.maximum = 1.0;
+
145 break;
+
146 case 14:
+
147 signal.byteOrder = static_cast<ByteOrder>(99);
+
148 break;
+
149 case 15:
+
150 signal.valueType = static_cast<ValueType>(99);
+
151 break;
+
152 case 16:
+
153 signal.multiplexing.role = static_cast<MultiplexRole>(99);
+
154 break;
+
155 case 17:
+
156 signal.multiplexing.role = MultiplexRole::Branch;
+
157 break;
+
158 case 18:
+
159 signal.multiplexing.role = MultiplexRole::Selector;
+
160 signal.valueType = ValueType::Signed;
+
161 break;
+
162 case 19:
+
163 signal.multiplexing.role = MultiplexRole::Selector;
+
164 other.multiplexing.role = MultiplexRole::Selector;
+
165 other.startBit = 8U;
+
166 frame.signals.push_back(other);
+
167 break;
+
168 case 20:
+
169 case 23:
+
170 case 24:
+
171 signal.multiplexing.role = MultiplexRole::Selector;
+
172 other.multiplexing.role = MultiplexRole::Branch;
+
173 other.startBit = kind == 24 ? 0U : 8U;
+
174 other.multiplexing.value = kind == 20 ? 256U : 1U;
+
175 frame.signals.push_back(other);
+
176 if (kind == 23) {
+
177 other.name = "Third";
+
178 frame.signals.push_back(other);
+
179 }
+
180 break;
+
181 case 21:
+
182 frame.signals.push_back(other);
+
183 break;
+
184 case 22:
+
185 other.byteOrder = ByteOrder::Motorola;
+
186 other.startBit = 7U;
+
187 frame.signals.push_back(other);
+
188 break;
+
189 case 25:
+
190 frame.canId = std::numeric_limits<std::uint32_t>::max();
+
191 break;
+
192 }
+
193 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase(frames));
+
194 // Parsed metadata uses bools for byte order and signedness, so invalid enum
+
195 // values exist only in direct runtime definitions.
+
196 if (kind != 14 && kind != 15)
+
197 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DbcDecodeBuilder().Build(ParsedDefinitions(frames)));
+
198}
+
199
+
200void DbcValidationTest::numericParameters_data() {
+
201 QTest::addColumn<int>("field");
+
202 QTest::addColumn<double>("value");
+
203 const double values[] = {std::numeric_limits<double>::quiet_NaN(),
+
204 std::numeric_limits<double>::infinity(),
+
205 -std::numeric_limits<double>::infinity()};
+
206 for (int field = 0; field < 4; ++field) {
+
207 for (int index = 0; index < 3; ++index) {
+
208 const QByteArray name = QByteArray::number(field) + "-" + QByteArray::number(index);
+
209 QTest::newRow(name.constData()) << field << values[index];
+
210 }
+
211 }
+
212}
+
213
+
214void DbcValidationTest::numericParameters() {
+
215 QFETCH(int, field);
+
216 QFETCH(double, value);
+
217 DecodeFrame frame = ValidFrame();
+
218 DecodeSignal &signal = frame.signals[0];
+
219 switch (field) {
+
220 case 0:
+
221 signal.factor = value;
+
222 break;
+
223 case 1:
+
224 signal.offset = value;
+
225 break;
+
226 case 2:
+
227 signal.minimum = value;
+
228 break;
+
229 case 3:
+
230 signal.maximum = value;
+
231 break;
+
232 }
+
233 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({frame}));
+
234 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DbcDecodeBuilder().Build(ParsedDefinitions({frame})));
+
235}
+
236
+
237void DbcValidationTest::validBoundaries() {
+
238 for (ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) {
+
239 for (std::uint8_t dlc: {std::uint8_t(8U), std::uint8_t(64U), std::uint8_t(255U)}) {
+
240 DecodeFrame standard = ValidFrame();
+
241 standard.canId = 0x7FFU;
+
242 standard.dlc = dlc;
+
243 DecodeSignal &signal = standard.signals[0];
+
244 signal.byteOrder = order;
+
245 signal.startBit = (std::uint32_t(dlc) - 8U) * 8U + (order == ByteOrder::Intel ? 0U : 7U);
+
246 signal.length = 64U;
+
247 signal.factor = -2.0;
+
248 signal.offset = std::numeric_limits<double>::max();
+
249 signal.minimum = -std::numeric_limits<double>::max();
+
250 signal.maximum = std::numeric_limits<double>::max();
+
251 DecodeFrame extended = standard;
+
252 extended.isExtended = true;
+
253 DecodeFrame maximum = extended;
+
254 maximum.canId = 0x1FFFFFFFU;
+
255 const std::vector<DecodeFrame> frames = {standard, extended, maximum};
+
256 const DecodeDatabase direct(frames);
+
257 const DecodeDatabase built = DbcDecodeBuilder().Build(ParsedDefinitions(frames));
+
258 for (const DecodeFrame &frame: frames) {
+
259 QVERIFY(direct.FindFrame(frame.GetKey()));
+
260 QVERIFY(built.FindSignal(SignalKey(frame.GetKey(), "Value")));
+
261 }
+
262 }
+
263 }
+
264 DecodeFrame empty;
+
265 empty.canId = 0U;
+
266 QVERIFY(DecodeDatabase({empty}).FindFrame(empty.GetKey()));
+
267 QVERIFY(DbcDecodeBuilder().Build(DbcDatabase()).GetFrames().empty());
+
268}
+
269
+
270void DbcValidationTest::multiplexingBoundaries() {
+
271 DecodeFrame frame = ValidFrame();
+
272 frame.dlc = 9U;
+
273 DecodeSignal &selector = frame.signals[0];
+
274 selector.length = 64U;
+
275 selector.multiplexing.role = MultiplexRole::Selector;
+
276 DecodeSignal first;
+
277 first.name = "First";
+
278 first.startBit = 64U;
+
279 first.length = 8U;
+
280 first.multiplexing.role = MultiplexRole::Branch;
+
281 first.multiplexing.value = std::numeric_limits<std::uint64_t>::max();
+
282 DecodeSignal second = first;
+
283 second.name = "Second";
+
284 second.byteOrder = ByteOrder::Motorola;
+
285 second.startBit = 71U;
+
286 second.multiplexing.value = 0U;
+
287 frame.signals.push_back(first);
+
288 frame.signals.push_back(second);
+
289 const DecodeDatabase direct({frame});
+
290 const DecodeDatabase built = DbcDecodeBuilder().Build(ParsedDefinitions({frame}));
+
291 RawCanFrame raw;
+
292 raw.canId = frame.canId;
+
293 raw.data.assign(9U, 255U);
+
294 for (const DecodeDatabase &database: {direct, built}) {
+
295 const DecodedFrameValue result = DbcDecoder().Decode(database, raw);
+
296 QCOMPARE(result.signals[1].status, SignalDecodeStatus::Valid);
+
297 QCOMPARE(result.signals[2].status, SignalDecodeStatus::Inactive);
+
298 }
+
299}
+
300
+
301void DbcValidationTest::independentSnapshot() {
+
302 std::vector<DecodeFrame> frames = {ValidFrame()};
+
303 DecodeFrame &alias = frames[0];
+
304 DecodeSignal &signalAlias = alias.signals[0];
+
305 signalAlias.valueDescriptions.emplace(std::uint64_t(1U), "One");
+
306 std::string &descriptionAlias = signalAlias.valueDescriptions.begin()->second;
+
307 const FrameKey key = alias.GetKey();
+
308 const DecodeDatabase database(std::move(frames));
+
309 alias.canId = 456U;
+
310 signalAlias.name = "Changed";
+
311 signalAlias.startBit = std::numeric_limits<std::uint32_t>::max();
+
312 descriptionAlias = "Changed";
+
313 const std::shared_ptr<const DecodeFrame> saved = database.FindFrame(key);
+
314 QVERIFY(saved);
+
315 QCOMPARE(saved->canId, key.canId);
+
316 QVERIFY(!database.FindFrame(FrameKey(456U, false)));
+
317 const std::shared_ptr<const DecodeSignal> signal = database.FindSignal(SignalKey(key, "Value"));
+
318 QVERIFY(signal);
+
319 QCOMPARE(signal->startBit, 0U);
+
320 QCOMPARE(signal->valueDescriptions.begin()->second, std::string("One"));
+
321}
+
322
+
323void DbcValidationTest::failedReplacement() {
+
324 const DecodeFrame valid = ValidFrame();
+
325 DecodeDatabase database({valid});
+
326 const std::shared_ptr<const DecodeFrame> original = database.FindFrame(valid.GetKey());
+
327 DecodeFrame invalid = valid;
+
328 invalid.canId = 0x800U;
+
329 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, database = DecodeDatabase({invalid}));
+
330 QCOMPARE(database.FindFrame(valid.GetKey()).get(), original.get());
+
331 invalid = valid;
+
332 invalid.signals[0].factor = std::numeric_limits<double>::infinity();
+
333 QVERIFY_THROWS_EXCEPTION(std::invalid_argument,
+
334 database = DbcDecodeBuilder().Build(ParsedDefinitions({invalid})));
+
335 QCOMPARE(database.FindFrame(valid.GetKey()).get(), original.get());
+
336 QCOMPARE(database.GetFrames().size(), std::size_t(1U));
+
337 database = DecodeDatabase();
+
338 QVERIFY(database.GetFrames().empty());
+
339 QVERIFY(!database.FindFrame(valid.GetKey()));
+
340 QVERIFY(original);
+
341}
+
342
+
343int RunValidationTests(int argc, char **argv) {
+
344 DbcValidationTest tests;
+
345 return QTest::qExec(&tests, argc, argv);
+
346}
+
347
+
348#include "dbc_validation_test.moc"
+
Converts parsed DBC data into runtime decode database.
+
DecodeDatabase Build(const DbcDatabase &source) const
Build runtime decode database.
+
Runtime CAN decoder using prebuilt decode database.
Definition dbc_decoder.h:84
+
DecodedFrameValue Decode(const DecodeDatabase &database, const RawCanFrame &frame) const
Decode one raw CAN frame.
+
Immutable runtime snapshot. Copies share storage; reload replaces a snapshot. Owning lookup handles a...
+
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
+
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
+
ByteOrder
Signal byte order used for runtime decoding.
+
ValueType
Signal numeric type.
+
Parsed DBC content stored in a simple internal form.
+
std::vector< FrameInfo > frames
+
Runtime-ready frame definition.
+ +
std::uint8_t dlc
+
std::uint32_t canId
+
Runtime-ready signal definition.
+ + + +
std::string name
+
ValueDescriptions valueDescriptions
+ +
ValueType valueType
+
std::uint32_t startBit
+
ByteOrder byteOrder
+
std::uint32_t length
+
Fully decoded frame.
Definition dbc_decoder.h:71
+
FrameDecodeStatus status
Definition dbc_decoder.h:76
+
Describes one CAN frame from a DBC file.
Definition frame_info.h:24
+
bool isExtended
Definition frame_info.h:27
+
std::uint8_t dlc
Definition frame_info.h:30
+
std::uint32_t canId
Definition frame_info.h:26
+
Stable frame identifier: normalized CAN ID and frame type.
+
Raw CAN frame used for runtime or trace decoding.
Definition dbc_decoder.h:24
+
std::vector< std::uint8_t > data
Definition dbc_decoder.h:27
+
std::uint32_t canId
Definition dbc_decoder.h:25
+
Describes one signal inside a DBC frame.
Definition signal_info.h:24
+
std::uint32_t length
Definition signal_info.h:28
+
std::string name
Definition signal_info.h:26
+
bool isSigned
Definition signal_info.h:30
+
double offset
Definition signal_info.h:32
+
std::uint32_t startBit
Definition signal_info.h:27
+
double minimum
Definition signal_info.h:33
+
double maximum
Definition signal_info.h:34
+
double factor
Definition signal_info.h:31
+
bool isLittleEndian
Definition signal_info.h:29
+ +
+
+ + + + diff --git a/docs/html/decode__database_8cpp_source.html b/docs/html/decode__database_8cpp_source.html index b145df5..6f4572a 100644 --- a/docs/html/decode__database_8cpp_source.html +++ b/docs/html/decode__database_8cpp_source.html @@ -102,65 +102,154 @@ $(document).ready(function(){initNavTree('decode__database_8cpp_source.html','')
2// Copyright (c) 2026 Dim Himro
3
4#include "decode_database.h"
-
5#include <stdexcept>
-
6#include <unordered_set>
-
7#include <utility>
-
8
-
9DecodeDatabase::Storage::Storage(std::vector<DecodeFrame> definitions)
-
10 : frames(std::move(definitions)) {
-
11 frameIndexByKey.reserve(frames.size());
-
12 for (std::size_t index = 0U; index < frames.size(); ++index) {
-
13 const DecodeFrame &frame = frames[index];
-
14 if (!frameIndexByKey.emplace(frame.GetKey(), index).second)
-
15 throw std::invalid_argument("Duplicate runtime frame key");
-
16 std::unordered_set<std::string> names;
-
17 for (const DecodeSignal &signal: frame.signals) {
-
18 if (!names.insert(signal.name).second)
-
19 throw std::invalid_argument("Duplicate runtime signal key");
-
20 }
-
21 }
-
22}
-
23
-
-
24DecodeDatabase::DecodeDatabase(std::vector<DecodeFrame> frames)
-
25 : storage(std::make_shared<const Storage>(std::move(frames))) {
-
26}
+
5#include "signal_layout.h"
+
6#include <bitset>
+
7#include <cmath>
+
8#include <limits>
+
9#include <stdexcept>
+
10#include <unordered_set>
+
11
+
12namespace {
+
13 constexpr std::size_t MaximumPayloadBits = std::numeric_limits<std::uint8_t>::max() * 8U;
+
14 using SignalBits = std::bitset<MaximumPayloadBits>;
+
15
+
16 void ValidateSignal(const DecodeSignal &signal, std::uint8_t dlc) {
+
17 if (signal.byteOrder != ByteOrder::Intel && signal.byteOrder != ByteOrder::Motorola)
+
18 throw std::invalid_argument("Invalid signal byte order: " + signal.name);
+
19 if (signal.valueType != ValueType::Unsigned && signal.valueType != ValueType::Signed)
+
20 throw std::invalid_argument("Invalid signal value type: " + signal.name);
+
21 if (signal.multiplexing.role != MultiplexRole::Unconditional &&
+
22 signal.multiplexing.role != MultiplexRole::Selector &&
+
23 signal.multiplexing.role != MultiplexRole::Branch)
+
24 throw std::invalid_argument("Invalid multiplexing role: " + signal.name);
+
25 if (!IsValidSignalLayout(signal.startBit, signal.length,
+
26 signal.byteOrder == ByteOrder::Intel, dlc))
+
27 throw std::invalid_argument("Invalid signal length or payload position: " + signal.name);
+
28 if (!std::isfinite(signal.factor) || !std::isfinite(signal.offset) ||
+
29 !std::isfinite(signal.minimum) || !std::isfinite(signal.maximum) ||
+
30 signal.minimum > signal.maximum)
+
31 throw std::invalid_argument("Invalid signal numeric parameters: " + signal.name);
+
32 }
+
33
+
34 SignalBits GetSignalBits(const DecodeSignal &signal) {
+
35 SignalBits bits;
+
36 const bool littleEndian = signal.byteOrder == ByteOrder::Intel;
+
37 const std::uint64_t start = SignalStartCoordinate(signal.startBit, littleEndian);
+
38 for (std::uint32_t index = 0U; index < signal.length; ++index) {
+
39 const std::uint64_t coordinate = start + index;
+
40 const std::uint64_t bit = littleEndian ? coordinate
+
41 : coordinate / 8U * 8U + 7U - coordinate % 8U;
+
42 bits.set(static_cast<std::size_t>(bit));
+
43 }
+
44 return bits;
+
45 }
+
46
+
47 bool AreMutuallyExclusive(const DecodeSignal &first, const DecodeSignal &second) {
+
48 return first.multiplexing.role == MultiplexRole::Branch &&
+
49 second.multiplexing.role == MultiplexRole::Branch &&
+
50 first.multiplexing.value != second.multiplexing.value;
+
51 }
+
52
+
53 void ValidateFrame(const DecodeFrame &frame) {
+
54 const std::uint32_t maximumId = frame.isExtended ? 0x1FFFFFFFU : 0x7FFU;
+
55 if (frame.canId > maximumId)
+
56 throw std::invalid_argument("Invalid normalized CAN ID: " + std::to_string(frame.canId));
+
57
+
58 std::unordered_set<std::string> names;
+
59 const DecodeSignal *selector = nullptr;
+
60 for (const DecodeSignal &signal: frame.signals) {
+
61 if (!names.insert(signal.name).second)
+
62 throw std::invalid_argument("Duplicate runtime signal key: " + signal.name);
+
63 ValidateSignal(signal, frame.dlc);
+
64 if (signal.multiplexing.role == MultiplexRole::Selector) {
+
65 if (selector != nullptr || signal.valueType != ValueType::Unsigned)
+
66 throw std::invalid_argument("Multiplexing requires one unsigned selector per frame");
+
67 selector = &signal;
+
68 }
+
69 }
+
70
+
71 std::vector<SignalBits> layouts;
+
72 layouts.reserve(frame.signals.size());
+
73 for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
+
74 const DecodeSignal &signal = frame.signals[index];
+
75 if (signal.multiplexing.role == MultiplexRole::Branch &&
+
76 (selector == nullptr ||
+
77 (selector->length < 64U && signal.multiplexing.value >= (std::uint64_t(1U) << selector->length))))
+
78 throw std::invalid_argument("Missing selector or branch value outside selector range: " + signal.name);
+
79 const SignalBits bits = GetSignalBits(signal);
+
80 for (std::size_t previous = 0U; previous < index; ++previous) {
+
81 if (!AreMutuallyExclusive(signal, frame.signals[previous]) && (bits & layouts[previous]).any())
+
82 throw std::invalid_argument("Overlapping signals: " + signal.name);
+
83 }
+
84 layouts.push_back(bits);
+
85 }
+
86 }
+
87}// namespace
+
88
+
89DecodeDatabase::Storage::Storage(const std::vector<DecodeFrame> &definitions)
+
90 : frames(definitions) {
+
91 // Validate the complete snapshot before creating its lookup index.
+
92 for (const DecodeFrame &frame: frames)
+
93 ValidateFrame(frame);
+
94 frameIndexByKey.reserve(frames.size());
+
95 for (std::size_t index = 0U; index < frames.size(); ++index) {
+
96 if (!frameIndexByKey.emplace(frames[index].GetKey(), index).second)
+
97 throw std::invalid_argument("Duplicate runtime frame key");
+
98 }
+
99}
+
100
+
+
101DecodeDatabase::DecodeDatabase(const std::vector<DecodeFrame> &frames)
+
102 : storage(std::make_shared<const Storage>(frames)) {
+
103}
-
27
-
-
28const std::vector<DecodeFrame> &DecodeDatabase::GetFrames() const {
-
29 static const std::vector<DecodeFrame> empty;
-
30 return storage ? storage->frames : empty;
-
31}
+
104
+
+
105const std::vector<DecodeFrame> &DecodeDatabase::GetFrames() const {
+
106 static const std::vector<DecodeFrame> empty;
+
107 return storage ? storage->frames : empty;
+
108}
-
32
-
-
33std::shared_ptr<const DecodeFrame> DecodeDatabase::FindFrame(const FrameKey &key) const {
-
34 if (!storage)
-
35 return nullptr;
-
36 const std::unordered_map<FrameKey, std::size_t, FrameKeyHasher>::const_iterator it =
-
37 storage->frameIndexByKey.find(key);
-
38 if (it == storage->frameIndexByKey.end())
-
39 return nullptr;
-
40 return std::shared_ptr<const DecodeFrame>(storage, &storage->frames[it->second]);
-
41}
+
109
+
+
110std::shared_ptr<const DecodeFrame> DecodeDatabase::FindFrame(const FrameKey &key) const {
+
111 if (!storage)
+
112 return nullptr;
+
113 const std::unordered_map<FrameKey, std::size_t, FrameKeyHasher>::const_iterator it =
+
114 storage->frameIndexByKey.find(key);
+
115 if (it == storage->frameIndexByKey.end())
+
116 return nullptr;
+
117 return std::shared_ptr<const DecodeFrame>(storage, &storage->frames[it->second]);
+
118}
-
42
-
43std::shared_ptr<const DecodeSignal> DecodeDatabase::FindSignal(const SignalKey &key) const {
-
44 const std::shared_ptr<const DecodeFrame> frame = FindFrame(key.frame);
-
45 if (!frame)
-
46 return nullptr;
-
47 for (const DecodeSignal &signal: frame->signals) {
-
48 if (signal.name == key.name)
-
49 return std::shared_ptr<const DecodeSignal>(frame, &signal);
-
50 }
-
51 return nullptr;
-
52}
-
std::shared_ptr< const DecodeFrame > FindFrame(const FrameKey &key) const
-
const std::vector< DecodeFrame > & GetFrames() const
+
119
+
120std::shared_ptr<const DecodeSignal> DecodeDatabase::FindSignal(const SignalKey &key) const {
+
121 const std::shared_ptr<const DecodeFrame> frame = FindFrame(key.frame);
+
122 if (!frame)
+
123 return nullptr;
+
124 for (const DecodeSignal &signal: frame->signals) {
+
125 if (signal.name == key.name)
+
126 return std::shared_ptr<const DecodeSignal>(frame, &signal);
+
127 }
+
128 return nullptr;
+
129}
+
std::shared_ptr< const DecodeFrame > FindFrame(const FrameKey &key) const
+
const std::vector< DecodeFrame > & GetFrames() const
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
Runtime-ready frame definition.
+ +
std::uint8_t dlc
+
std::uint32_t canId
Runtime-ready signal definition.
+ + + +
std::string name
+ +
ValueType valueType
+
std::uint32_t startBit
+
ByteOrder byteOrder
+
std::uint32_t length
Stable frame identifier: normalized CAN ID and frame type.
diff --git a/docs/html/decode__database_8h_source.html b/docs/html/decode__database_8h_source.html index 15f26f9..1b57aff 100644 --- a/docs/html/decode__database_8h_source.html +++ b/docs/html/decode__database_8h_source.html @@ -168,7 +168,7 @@ $(document).ready(function(){initNavTree('decode__database_8h_source.html','');
88public:
89 DecodeDatabase() = default;
-
95 explicit DecodeDatabase(std::vector<DecodeFrame> frames);
+
95 explicit DecodeDatabase(const std::vector<DecodeFrame> &frames);
96
98 const std::vector<DecodeFrame> &GetFrames() const;
100 std::shared_ptr<const DecodeFrame> FindFrame(const FrameKey &key) const;
@@ -179,7 +179,7 @@ $(document).ready(function(){initNavTree('decode__database_8h_source.html','');
105 std::vector<DecodeFrame> frames;
106 std::unordered_map<FrameKey, std::size_t, FrameKeyHasher> frameIndexByKey;
107
-
108 explicit Storage(std::vector<DecodeFrame> definitions);
+
108 explicit Storage(const std::vector<DecodeFrame> &definitions);
109 };
110
111 std::shared_ptr<const Storage> storage;
@@ -188,8 +188,8 @@ $(document).ready(function(){initNavTree('decode__database_8h_source.html','');
113
114#endif /* DECODE_DATABASE_H */
Immutable runtime snapshot. Copies share storage; reload replaces a snapshot. Owning lookup handles a...
-
std::shared_ptr< const DecodeFrame > FindFrame(const FrameKey &key) const
-
const std::vector< DecodeFrame > & GetFrames() const
+
std::shared_ptr< const DecodeFrame > FindFrame(const FrameKey &key) const
+
const std::vector< DecodeFrame > & GetFrames() const
ByteOrder
Signal byte order used for runtime decoding.
ValueType
Signal numeric type.
Runtime-ready frame definition.
diff --git a/docs/html/dir_4270bfced15e0e73154b13468c7c9ad9.html b/docs/html/dir_4270bfced15e0e73154b13468c7c9ad9.html index 17724eb..5f1cf25 100644 --- a/docs/html/dir_4270bfced15e0e73154b13468c7c9ad9.html +++ b/docs/html/dir_4270bfced15e0e73154b13468c7c9ad9.html @@ -144,6 +144,8 @@ Files  signal_info.h  Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
  + signal_layout.h +   text_utils.h    tree_node.cpp diff --git a/docs/html/dir_4270bfced15e0e73154b13468c7c9ad9.js b/docs/html/dir_4270bfced15e0e73154b13468c7c9ad9.js index 3a2182e..a13e40a 100644 --- a/docs/html/dir_4270bfced15e0e73154b13468c7c9ad9.js +++ b/docs/html/dir_4270bfced15e0e73154b13468c7c9ad9.js @@ -18,6 +18,7 @@ var dir_4270bfced15e0e73154b13468c7c9ad9 = [ "frame_info.h", "frame__info_8h.html", "frame__info_8h" ], [ "multiplexing.h", "multiplexing_8h_source.html", null ], [ "signal_info.h", "signal__info_8h.html", "signal__info_8h" ], + [ "signal_layout.h", "signal__layout_8h_source.html", null ], [ "text_utils.h", "text__utils_8h_source.html", null ], [ "tree_node.cpp", "tree__node_8cpp.html", null ], [ "tree_node.h", "tree__node_8h.html", "tree__node_8h" ], diff --git a/docs/html/dir_59425e443f801f1f2fd8bbe4959a3ccf.html b/docs/html/dir_59425e443f801f1f2fd8bbe4959a3ccf.html index 060b2ea..a00fa04 100644 --- a/docs/html/dir_59425e443f801f1f2fd8bbe4959a3ccf.html +++ b/docs/html/dir_59425e443f801f1f2fd8bbe4959a3ccf.html @@ -106,6 +106,8 @@ Files    dbc_tree_model_test.cpp   + dbc_validation_test.cpp +   main.cpp    tree_node_test.cpp diff --git a/docs/html/dir_59425e443f801f1f2fd8bbe4959a3ccf.js b/docs/html/dir_59425e443f801f1f2fd8bbe4959a3ccf.js index 74e6d20..946b274 100644 --- a/docs/html/dir_59425e443f801f1f2fd8bbe4959a3ccf.js +++ b/docs/html/dir_59425e443f801f1f2fd8bbe4959a3ccf.js @@ -5,6 +5,7 @@ var dir_59425e443f801f1f2fd8bbe4959a3ccf = [ "dbc_lifetime_test.cpp", "dbc__lifetime__test_8cpp_source.html", null ], [ "dbc_parser_test.cpp", "dbc__parser__test_8cpp_source.html", null ], [ "dbc_tree_model_test.cpp", "dbc__tree__model__test_8cpp_source.html", null ], + [ "dbc_validation_test.cpp", "dbc__validation__test_8cpp_source.html", null ], [ "tests/main.cpp", "tests_2main_8cpp_source.html", null ], [ "tree_node_test.cpp", "tree__node__test_8cpp_source.html", null ] ]; \ No newline at end of file diff --git a/docs/html/examples_2main_8cpp_source.html b/docs/html/examples_2main_8cpp_source.html index 46628c8..250bff9 100644 --- a/docs/html/examples_2main_8cpp_source.html +++ b/docs/html/examples_2main_8cpp_source.html @@ -230,13 +230,13 @@ $(document).ready(function(){initNavTree('examples_2main_8cpp_source.html','');
Converts parsed DBC data into runtime decode database.
DecodeDatabase Build(const DbcDatabase &source) const
Build runtime decode database.
Runtime CAN decoder using prebuilt decode database.
Definition dbc_decoder.h:84
-
DecodedFrameValue Decode(const DecodeDatabase &database, const RawCanFrame &frame) const
Decode one raw CAN frame.
+
DecodedFrameValue Decode(const DecodeDatabase &database, const RawCanFrame &frame) const
Decode one raw CAN frame.
Minimal DBC parser.
Definition dbc_parser.h:33
DbcDatabase ParseFile(const std::string &filePath) const
Parse DBC file.
Builds a simple tree from parsed DBC database.
std::unique_ptr< TreeNode > Build(const DbcDatabase &database) const
Build tree representation of parsed DBC data.
Immutable runtime snapshot. Copies share storage; reload replaces a snapshot. Owning lookup handles a...
-
const std::vector< DecodeFrame > & GetFrames() const
+
const std::vector< DecodeFrame > & GetFrames() const
Tree node for later use in model/view or other hierarchy consumers.
Definition tree_node.h:35
const SignalInfo * GetSignal() const
Get signal payload if node is signal.
Definition tree_node.cpp:64
const TreeNode * GetChild(std::size_t index) const
Get child by index.
Definition tree_node.cpp:38
diff --git a/docs/html/files.html b/docs/html/files.html index 85ba198..528438e 100644 --- a/docs/html/files.html +++ b/docs/html/files.html @@ -114,24 +114,26 @@ $(document).ready(function(){initNavTree('files.html',''); initResizable(); });  frame_info.hCreated: 2026-03-13 Author: Deeaitch (Dim. Himro)  multiplexing.h  signal_info.hCreated: 2026-03-13 Author: Deeaitch (Dim. Himro) - text_utils.h - tree_node.cppCreated: 2026-03-13 Author: Deeaitch (Dim. Himro) - tree_node.hCreated: 2026-03-13 Author: Deeaitch (Dim. Himro) - typefactory.h - value_descriptions.h - ▼ examples - main.cppCreated: 2026-03-13 Author: Deeaitch (Dim. Himro) - ▼ qt - dbc_tree_model.cpp - dbc_tree_model.h - ▼ tests - dbc_coverage_test.cpp - dbc_decoder_test.cpp - dbc_lifetime_test.cpp - dbc_parser_test.cpp - dbc_tree_model_test.cpp - main.cpp - tree_node_test.cpp + signal_layout.h + text_utils.h + tree_node.cppCreated: 2026-03-13 Author: Deeaitch (Dim. Himro) + tree_node.hCreated: 2026-03-13 Author: Deeaitch (Dim. Himro) + typefactory.h + value_descriptions.h + ▼ examples + main.cppCreated: 2026-03-13 Author: Deeaitch (Dim. Himro) + ▼ qt + dbc_tree_model.cpp + dbc_tree_model.h + ▼ tests + dbc_coverage_test.cpp + dbc_decoder_test.cpp + dbc_lifetime_test.cpp + dbc_parser_test.cpp + dbc_tree_model_test.cpp + dbc_validation_test.cpp + main.cpp + tree_node_test.cpp
diff --git a/docs/html/functions.html b/docs/html/functions.html index 676394d..678024f 100644 --- a/docs/html/functions.html +++ b/docs/html/functions.html @@ -115,7 +115,7 @@ $(document).ready(function(){initNavTree('functions.html',''); initResizable();

- d -

  • data : RawCanFrame
  • Decode() : DbcDecoder
  • -
  • DecodeDatabase() : DecodeDatabase
  • +
  • DecodeDatabase() : DecodeDatabase
  • definition : DecodedFrameValue, DecodedSignalValue
  • Dispatch() : DbcStatementDispatcher
  • dlc : DecodeFrame, FrameInfo
  • diff --git a/docs/html/functions_func.html b/docs/html/functions_func.html index 579da61..b780cb9 100644 --- a/docs/html/functions_func.html +++ b/docs/html/functions_func.html @@ -97,7 +97,7 @@ $(document).ready(function(){initNavTree('functions_func.html',''); initResizabl
  • create() : TypeFactory< ClassId, BaseClass, Args >
  • creator() : TypeFactory< ClassId, BaseClass, Args >
  • Decode() : DbcDecoder
  • -
  • DecodeDatabase() : DecodeDatabase
  • +
  • DecodeDatabase() : DecodeDatabase
  • Dispatch() : DbcStatementDispatcher
  • FindFrame() : DbcDecoder, DecodeDatabase
  • GetChild() : TreeNode
  • diff --git a/docs/html/index.qhp b/docs/html/index.qhp index b02409b..fc1be5a 100644 --- a/docs/html/index.qhp +++ b/docs/html/index.qhp @@ -137,6 +137,7 @@
    +
    @@ -185,7 +186,7 @@
    -
    +
    @@ -438,7 +439,7 @@
    -
    +
    @@ -483,6 +484,7 @@
    +
    @@ -524,6 +526,7 @@
    +
    @@ -593,7 +596,7 @@ - + @@ -758,6 +761,7 @@ dbc__tree__model_8cpp_source.html dbc__tree__model_8h_source.html dbc__tree__model__test_8cpp_source.html +dbc__validation__test_8cpp_source.html decode__database_8cpp_source.html decode__database_8h.html decode__database_8h_source.html @@ -915,6 +919,7 @@ search/variables_f.js signal__info_8h.html signal__info_8h_source.html +signal__layout_8h_source.html splitbar.png splitbard.png structDbcDatabase-members.html diff --git a/docs/html/md_README.html b/docs/html/md_README.html index f686d23..034f8ba 100644 --- a/docs/html/md_README.html +++ b/docs/html/md_README.html @@ -369,7 +369,16 @@ Purpose

    <tt>DecodeDatabase</tt>

    -

    Immutable runtime snapshot with a private frame index. Build it with DbcDecodeBuilder::Build(parsed) or construct it from std::vector<DecodeFrame>. Construction rejects duplicate frame keys and duplicate signal names within a frame with std::invalid_argument, including manually supplied metadata.

    +

    Immutable runtime snapshot with a private frame index. Build it with DbcDecodeBuilder::Build(parsed) or construct it from std::vector<DecodeFrame>. Construction validates all definitions before publishing the snapshot and throws std::invalid_argument for invalid metadata, including manually supplied data:

    +
      +
    • Normalized CAN IDs must fit 11 bits for standard frames or 29 bits for extended frames. DBC's bit-31 encoding flag must already be removed.
    • +
    • Frame keys must be unique; signal names must be unique within their frame.
    • +
    • Signal lengths must be 1–64 bits, with Intel/Motorola layouts entirely inside the declared payload. Bit coordinates use overflow-safe 64-bit arithmetic.
    • +
    • Byte order, numeric type, and multiplexing role must be valid enum values.
    • +
    • Factor, offset, minimum, and maximum must be finite; minimum must not exceed maximum. Zero and negative factors are allowed.
    • +
    • Multiplexing requires one unsigned selector when branches are present, with branch values in its raw range. Signals may overlap only when they are branches selected by different raw values, including mixed byte orders.
    • +
    +

    dlc remains a byte count in the storage range 0–255; construction imposes no additional CAN/CAN FD payload limit. A zero-byte frame may contain no signals. Both DbcDecodeBuilder::Build and the direct constructor apply these rules. Construction copies the input, including nested metadata, even for rvalue input. Saved mutable aliases into that input cannot modify definitions or invalidate the lookup index. Database copies still share the frozen snapshot; a failed construction or replacement leaves the previous snapshot and its lookups intact.

    • GetFrames() returns a borrowed const std::vector<DecodeFrame>&.
    • FindFrame(FrameKey) returns std::shared_ptr<const DecodeFrame>.
    • @@ -449,12 +458,12 @@ Raw value representation and precision

      Short-payload status takes precedence over signal errors; inspect each signal's status for the individual cause. valid retains its existing meaning of a matched definition and does not imply status == FrameDecodeStatus::Valid.

      Payload policy uses dlc as a byte count, including for CAN FD; it is not the encoded wire DLC. A shorter payload is never padded: all signals whose bytes are available still decode, even if other signals fail. A longer payload is accepted; bytes beyond dlc are ignored. Signal bit layouts must fit the declared dlc, so extra bytes cannot rescue an invalid definition. Empty payloads and zero-byte frame definitions follow the same rules. Results include every signal in definition order, preserving successful values and definition handles on partial failures.

      -

      The decoder applies factor and offset; it does not clamp physical values to the declared minimum and maximum. Non-finite conversion results (NaN or positive/ negative infinity), including overflow with finite factor/offset, produce PhysicalConversionFailed. The exact raw value and non-finite physicalValue remain available, while signal valid is false. Non-finite factor/offset in manually constructed definitions follow this same conversion-result check. Physical conversion failure of a selector does not prevent raw branch selection.

      +

      The decoder applies factor and offset; it does not clamp physical values to the declared minimum and maximum. Non-finite conversion results (NaN or positive/ negative infinity), including overflow with finite factor/offset, produce PhysicalConversionFailed. The exact raw value and non-finite physicalValue remain available, while signal valid is false. Non-finite numeric parameters are rejected during database construction. Finite parameters can still overflow during conversion; conversion failure of a selector does not prevent raw branch selection.

      Definition ownership and reload

      DecodedFrameValue::definition and DecodedSignalValue::definition are owning std::shared_ptr<const ...> handles. Each handle retains the immutable runtime snapshot. Definitions remain valid until the last database copy, lookup handle, or decoded result owning that snapshot is released. An independently copied signal result retains its definition even after the parent frame result is removed. Copying or moving results preserves ownership.

      Reload by assigning a newly built database. Stored results continue to describe the old DBC, including its units, scaling, comments, and VAL_ descriptions; subsequent decoding uses the new DBC. Removed definitions cannot be found in the new database, but old results safely retain their old definitions. Keeping even one signal handle retains the whole old snapshot; release stored results when that metadata is no longer needed.

      -

      GetFrames() is a borrowed view. References, iterators, and raw pointers obtained from it or from a handle's .get() do not retain ownership. Keep an owning handle or an unchanged database copy while using them. Do not retain mutable aliases into a vector moved into the database constructor. The raw string pointer from GetValueDescription() follows the same rule: keep the signal definition handle alive while using the string.

      +

      GetFrames() is a borrowed view. References, iterators, and raw pointers obtained from it or from a handle's .get() do not retain ownership. Keep an owning handle or an unchanged database copy while using them. Constructor input is copied and may be changed independently afterward. The raw string pointer from GetValueDescription() follows the same rule: keep the signal definition handle alive while using the string.

      Parsed DbcDatabase/FrameInfo/SignalInfo remain ordinary mutable value containers: borrowed pointers follow standard vector invalidation rules. Builders copy their metadata, so parsed input may be changed or destroyed after building. Tree nodes own metadata copies; the Qt model owns its tree and invalidates model indexes on reset. Runtime handles do not depend on the parsed database or tree. Synchronize replacement of a shared database variable with decoding; immutable snapshots do not make concurrent assignment to the same object safe.

      Stable frame and signal identifiers

      @@ -472,7 +481,7 @@ Stable frame and signal identifiers
      Converts parsed DBC data into runtime decode database.
      DecodeDatabase Build(const DbcDatabase &source) const
      Build runtime decode database.
      Runtime CAN decoder using prebuilt decode database.
      Definition dbc_decoder.h:84
      -
      DecodedFrameValue Decode(const DecodeDatabase &database, const RawCanFrame &frame) const
      Decode one raw CAN frame.
      +
      DecodedFrameValue Decode(const DecodeDatabase &database, const RawCanFrame &frame) const
      Decode one raw CAN frame.
      Immutable runtime snapshot. Copies share storage; reload replaces a snapshot. Owning lookup handles a...
      Fully decoded frame.
      Definition dbc_decoder.h:71
      @@ -622,11 +631,11 @@ Basic multiplexing InsufficientPayload Selected or unconditional signal bytes are missing -InvalidDefinition Invalid length, byte order, numeric type, multiplexing role, layout outside declared DLC, or missing/invalid selector in manually built runtime metadata +InvalidDefinition Defensive decoder check failed; invalid definitions are rejected during database construction PhysicalConversionFailed Raw value is available, but the converted physical value is NaN or infinity -

      All statuses except Valid have valid == false and GetValueDescription() returns nullptr. PhysicalConversionFailed preserves extracted raw data and the non-finite conversion result; other invalid statuses have no decoded numeric data. Check status to distinguish inactive branches from errors. Consumers constructing runtime metadata manually must obey the same non-overlap rules as the parser. Extended and nested multiplexing are outside this stage and are explicitly rejected, rather than silently decoded as unconditional signals.

      +

      All statuses except Valid have valid == false and GetValueDescription() returns nullptr. PhysicalConversionFailed preserves extracted raw data and the non-finite conversion result; other invalid statuses have no decoded numeric data. Check status to distinguish inactive branches from errors. Consumers constructing runtime metadata manually receive the same selector and non-overlap validation as parsed files. Extended and nested multiplexing are outside this stage and are explicitly rejected, rather than silently decoded as unconditional signals.


      Comments

      diff --git a/docs/html/md_changelog.html b/docs/html/md_changelog.html index ad2e5ea..3a04722 100644 --- a/docs/html/md_changelog.html +++ b/docs/html/md_changelog.html @@ -101,6 +101,8 @@ Unreleased

      Added

        +
      • Issue #16: construction-time validation for both manual runtime definitions and DbcDecodeBuilder input: normalized CAN IDs, duplicate keys, signal lengths and positions, enums, finite numeric parameters, ordered ranges, selectors, branch ranges, and overlaps.
      • +
      • Regression tests for both construction paths, numeric and bit boundaries, mixed byte orders, exclusive overlaps, mutable input aliases, and failed reloads.
      • FrameDecodeStatus distinguishes unknown frames, insufficient payloads, signal errors, and successful decoding; frame valid still means matched.
      • SignalDecodeStatus::PhysicalConversionFailed reports NaN/Inf results, retaining exact raw values and the non-finite physical result.
      • Regression tests for payload-length policy, partial results, invalid runtime metadata, physical overflow/non-finite parameters, and raw branch selection when selector physical conversion fails.
      • @@ -108,6 +110,9 @@ Added

        Changed

          +
        • Runtime construction rejects invalid definitions with std::invalid_argument before publishing a snapshot. Decoder tests now expect construction failures for malformed definitions; finite arithmetic overflow remains a decode status.
        • +
        • Shared overflow-safe layout validation across parsing, construction, and decoding. Intel extraction also widens bit coordinates before arithmetic.
        • +
        • DecodeDatabase now accepts definitions by const reference and copies them, even for rvalues, preventing retained mutable aliases from modifying the frozen snapshot or invalidating its index.
        • Short payloads decode available signals without padding; longer payloads ignore bytes beyond the declared DLC. Short frame status takes precedence over signal errors, with per-signal causes retained in definition order.
        • Runtime signal validation checks enum values and bit layouts against declared DLC using overflow-safe coordinates. Extra bytes cannot rescue invalid layouts.
        • Documented frame/signal status meanings, value availability, and payload policy.
        • diff --git a/docs/html/md_tests_2README.html b/docs/html/md_tests_2README.html index 2b52317..f065bcc 100644 --- a/docs/html/md_tests_2README.html +++ b/docs/html/md_tests_2README.html @@ -101,7 +101,7 @@ $(document).ready(function(){initNavTree('md_tests_2README.html',''); initResiza
          qmake6 ../dbc.pro
          make -j
          make check
          -

Or run ./tests/dbc_tests directly. Parser, decoder, lifetime, and additional coverage suites run, and the executable returns nonzero when any suite fails. Decoder cases appear as individual QtTest data rows; parser checks are grouped into named test slots. Checks also run in release builds.

+

Or run ./tests/dbc_tests directly. Parser, decoder, lifetime, validation, and additional coverage suites run, and the executable returns nonzero when any suite fails. Decoder cases appear as individual QtTest data rows; parser checks are grouped into named test slots. Checks also run in release builds.

To build tests separately against an existing core build:

mkdir /tmp/dbc-tests-build
cd /tmp/dbc-tests-build
@@ -109,10 +109,10 @@ $(document).ready(function(){initNavTree('md_tests_2README.html',''); initResiza
DBC_CORE_BUILD_DIR=/absolute/path/to/build/core
make
make check
-

parser_tests.pri and decoder_tests.pri include the original test sources; tests.pro also includes dbc_coverage_test.cpp and dbc_lifetime_test.cpp. ../core/core.pri supplies the library include path and linkage.

+

parser_tests.pri and decoder_tests.pri include the original test sources; tests.pro also includes dbc_coverage_test.cpp, dbc_lifetime_test.cpp, and dbc_validation_test.cpp. ../core/core.pri supplies the library include path and linkage.

Decoder coverage

-

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; frame status reports unknown frames, short payloads, and signal errors. Tests exercise empty/short/exact/long payloads, ignored trailing bytes, signals outside declared DLC, invalid enum metadata and overflowing start bits, and preservation of successful signals. Physical conversion checks cover NaN/Inf parameters, multiplication/addition overflow, finite limits, signed/unsigned raw retention, and raw selector activation after physical conversion failure.

+

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; frame status reports unknown frames, short payloads, and signal errors. Tests exercise empty/short/exact/long payloads, ignored trailing bytes, signals outside declared DLC, invalid enum metadata and overflowing start bits rejected at construction, and preservation of successful signals on partial payloads. Physical conversion checks cover NaN/Inf parameters rejected at construction, multiplication/addition overflow, finite limits, signed/unsigned raw retention, and raw selector activation after physical conversion failure.

Parser coverage

Structured diagnostic tests cover severity, code, supplied path, and physical line numbers for skipped syntax and immediate/deferred errors; warnings preceding an error are retained, and failed parses return an empty database. Common headers and namespace symbol lists stay quiet. Tests also check file-open errors, fresh parse sessions, and compatibility with the legacy throwing API. The diagnostic audit also covers 15 unsupported-statement variants, all namespace termination forms, CRLF and missing final newlines, relative paths with spaces, first-error stopping, deferred diagnostic ordering, duplicate definitions/values, selector ranges, overlaps, and preservation of supported decoding and metadata. On Linux, reading a directory exercises the distinct FileReadError path.

@@ -125,12 +125,15 @@ Tree and optional model tests

Basic multiplexing coverage includes selector declarations after branches, Intel/Motorola branch overlap, signed active values and VAL_ lookup, unconditional signals, inactive branches, missing selector/branch payloads, exact UINT64_MAX selector matching, malformed runtime selectors, source locations, and rejection of invalid markers, selector references/ranges, overlaps, and SG_MUL_VAL_.

Additional regression coverage

-

dbc_coverage_test.cpp runs as part of dbc_tests. It covers signed boundaries below 64 bits, deterministic extraction comparisons for both byte orders across lengths 1–64, frame lookup failures, exact decode statuses, malformed runtime multiplexing, PGN boundaries, comment handling, file formatting, metadata copying and source lifetime, physical conversion, and TypeFactory registration/creation. Direct resolver tests construct ambiguous databases through the public metadata API, verify source locations, and check that ambiguous targets receive no values.

+

dbc_coverage_test.cpp runs as part of dbc_tests. It covers signed boundaries below 64 bits, deterministic extraction comparisons for both byte orders across lengths 1–64, frame lookup failures, exact decode statuses, construction rejection of malformed runtime multiplexing, PGN boundaries, comment handling, file formatting, metadata copying and source lifetime, physical conversion, and TypeFactory registration/creation. Direct resolver tests construct ambiguous databases through the public metadata API, verify source locations, and check that ambiguous targets receive no values.

All QtTest targets use Qt 6.8.2 and QVERIFY_THROWS_EXCEPTION. Exception tests explicitly discard nodiscard results when only the exception is being checked. The standalone tree test requires only a C++17 compiler.

Definition lifetime and reload coverage

dbc_lifetime_test.cpp checks retained definitions and value-description strings across reload, clear, and destruction; independently saved signal results; copied/moved results and database copies; owning lookup handles; and release of the old snapshot after its final owner is removed. Compile-time checks enforce const access to runtime metadata. Reload fixtures change ordering, frame names, scaling, units, and descriptions, remove definitions, and rename signals.

-

Key tests check equality and hashing, standard/extended separation, the same signal name in different frames, and missing lookups. Constructor tests reject duplicate runtime keys, including manually supplied parsed metadata. Failed reload leaves the active database intact; empty reload clears it. Invalid/unknown results and insufficient payloads retain their documented ownership and status behavior. Inactive branches and unavailable selectors also retain definitions across reload.

+

Key tests check equality and hashing, standard/extended separation, the same signal name in different frames, and missing lookups. Constructor tests reject duplicate runtime keys, including manually supplied parsed metadata. Failed reload leaves the active database intact; empty reload clears it. Invalid/unknown results and insufficient payloads retain their documented ownership and status behavior. Inactive branches and unavailable selectors also retain definitions across reload.

+

+Database validation coverage

+

dbc_validation_test.cpp checks direct construction and manually assembled DbcDatabase input to DbcDecodeBuilder: standard/extended CAN ID limits, encoded/reserved ID bits, duplicate keys, lengths 0/1–64/65/UINT32_MAX, Intel/Motorola payload boundaries and overflowing start bits, invalid enums, NaN and both infinities in every numeric parameter, and reversed physical ranges. Selector tests include missing, duplicate, and signed selectors, branch ranges, UINT64_MAX selection, mixed-order overlaps, and exclusive branches sharing bits. Valid definitions cover the full byte-count storage range, including zero-byte frames without signals and 255-byte frames. Existing extraction tests cover lengths 1–64. Snapshot tests mutate retained frame, signal, and description aliases after rvalue construction, and verify unchanged lookups and metadata. Failed constructor/builder replacement preserves the active snapshot; clear removes its index while saved handles remain alive.

diff --git a/docs/html/navtreedata.js b/docs/html/navtreedata.js index 7a3d11a..0d7e452 100644 --- a/docs/html/navtreedata.js +++ b/docs/html/navtreedata.js @@ -149,7 +149,8 @@ var NAVTREE = [ "Parser coverage", "md_tests_2README.html#autotoc_md139", null ], [ "Tree and optional model tests", "md_tests_2README.html#autotoc_md140", null ], [ "Additional regression coverage", "md_tests_2README.html#autotoc_md141", null ], - [ "Definition lifetime and reload coverage", "md_tests_2README.html#autotoc_md142", null ] + [ "Definition lifetime and reload coverage", "md_tests_2README.html#autotoc_md142", null ], + [ "Database validation coverage", "md_tests_2README.html#autotoc_md143", null ] ] ], [ "Classes", "annotated.html", [ [ "Class List", "annotated.html", "annotated_dup" ], @@ -174,7 +175,7 @@ var NAVTREE = var NAVTREEINDEX = [ "annotated.html", -"structDecodedSignalValue.html#a6847d05b3cb00d5e0f6c611968f57978" +"structDecodedSignalValue.html" ]; var SYNCONMSG = 'click to disable panel synchronisation'; diff --git a/docs/html/navtreeindex0.js b/docs/html/navtreeindex0.js index 7cbf539..0b0cec4 100644 --- a/docs/html/navtreeindex0.js +++ b/docs/html/navtreeindex0.js @@ -23,9 +23,9 @@ var NAVTREEINDEX0 = "classDbcUnsupportedMultiplexingHandler.html":[3,0,16], "classDbcValueDescriptionHandler.html":[3,0,17], "classDecodeDatabase.html":[3,0,18], +"classDecodeDatabase.html#a51a44cf7c3f23c98ec8781afe0097c0c":[3,0,18,0], "classDecodeDatabase.html#a780d6e747a48b10a5eb0ab85448d2970":[3,0,18,2], "classDecodeDatabase.html#aa034c1510984a85f60ad6c0bd744d6e8":[3,0,18,1], -"classDecodeDatabase.html#aa935a3a0d334f932d6a8c89a3818286a":[3,0,18,0], "classTreeNode.html":[3,0,32], "classTreeNode.html#a226518612d0d00b5988a2c44fc21d3f1":[3,0,32,12], "classTreeNode.html#a4727ff801a2d1b9323c5ae50a35457a6":[3,0,32,7], @@ -79,6 +79,7 @@ var NAVTREEINDEX0 = "dbc__tree__model_8cpp_source.html":[4,0,2,0], "dbc__tree__model_8h_source.html":[4,0,2,1], "dbc__tree__model__test_8cpp_source.html":[4,0,3,4], +"dbc__validation__test_8cpp_source.html":[4,0,3,5], "decode__database_8cpp_source.html":[4,0,0,12], "decode__database_8h.html":[4,0,0,13], "decode__database_8h.html#aaeb92d42f5a6e27b8ba19f18d69d142b":[4,0,0,13,3], @@ -208,10 +209,12 @@ var NAVTREEINDEX0 = "md_tests_2README.html#autotoc_md140":[2,2], "md_tests_2README.html#autotoc_md141":[2,3], "md_tests_2README.html#autotoc_md142":[2,4], +"md_tests_2README.html#autotoc_md143":[2,5], "multiplexing_8h_source.html":[4,0,0,16], "pages.html":[], "signal__info_8h.html":[4,0,0,17], "signal__info_8h_source.html":[4,0,0,17], +"signal__layout_8h_source.html":[4,0,0,18], "structDbcDatabase.html":[3,0,1], "structDbcDatabase.html#a2cade5d0234ee816132127542f8c612b":[3,0,1,0], "structDbcDiagnostic.html":[3,0,4], @@ -246,8 +249,5 @@ var NAVTREEINDEX0 = "structDecodedFrameValue.html#a33e971ba10030506007aa201c89f351d":[3,0,19,0], "structDecodedFrameValue.html#a4f51c31110dba1f73a763c596372beb0":[3,0,19,3], "structDecodedFrameValue.html#a645f433798d48a2787e7f00dfbb7dff9":[3,0,19,4], -"structDecodedFrameValue.html#adae123ac7fbb4a5c314b64b01109f4a8":[3,0,19,2], -"structDecodedSignalValue.html":[3,0,20], -"structDecodedSignalValue.html#a33df0fcd92b23b3e08539906fc6b4c4a":[3,0,20,2], -"structDecodedSignalValue.html#a503c90b4b1a610b8252c45e466367c28":[3,0,20,3] +"structDecodedFrameValue.html#adae123ac7fbb4a5c314b64b01109f4a8":[3,0,19,2] }; diff --git a/docs/html/navtreeindex1.js b/docs/html/navtreeindex1.js index e58ba8f..e0fec52 100644 --- a/docs/html/navtreeindex1.js +++ b/docs/html/navtreeindex1.js @@ -1,5 +1,8 @@ var NAVTREEINDEX1 = { +"structDecodedSignalValue.html":[3,0,20], +"structDecodedSignalValue.html#a33df0fcd92b23b3e08539906fc6b4c4a":[3,0,20,2], +"structDecodedSignalValue.html#a503c90b4b1a610b8252c45e466367c28":[3,0,20,3], "structDecodedSignalValue.html#a6847d05b3cb00d5e0f6c611968f57978":[3,0,20,4], "structDecodedSignalValue.html#a828b5ef7faefc14fbf5dbd46249bcad2":[3,0,20,1], "structDecodedSignalValue.html#aac0ed361af545cde48d0e6f41a859686":[3,0,20,5], @@ -38,17 +41,17 @@ var NAVTREEINDEX1 = "structSignalInfo.html#af363b1563fb4c7cc8973f9a80c06846c":[3,0,29,3], "structSignalKey.html":[3,0,30], "structSignalKeyHasher.html":[3,0,31], -"tests_2main_8cpp_source.html":[4,0,3,5], -"text__utils_8h_source.html":[4,0,0,18], -"tree__node_8cpp.html":[4,0,0,19], -"tree__node_8cpp_source.html":[4,0,0,19], -"tree__node_8h.html":[4,0,0,20], -"tree__node_8h.html#acac9cbaeea226ed297804c012dc12b16":[4,0,0,20,1], -"tree__node_8h.html#acac9cbaeea226ed297804c012dc12b16a085fea7abdc5d904fe69a3081efd7398":[4,0,0,20,1,2], -"tree__node_8h.html#acac9cbaeea226ed297804c012dc12b16a3bb3e8c8a24891ba0f7608bcc96f8b0a":[4,0,0,20,1,1], -"tree__node_8h.html#acac9cbaeea226ed297804c012dc12b16afa03eb688ad8aa1db593d33dabd89bad":[4,0,0,20,1,0], -"tree__node_8h_source.html":[4,0,0,20], -"tree__node__test_8cpp_source.html":[4,0,3,6], -"typefactory_8h_source.html":[4,0,0,21], -"value__descriptions_8h_source.html":[4,0,0,22] +"tests_2main_8cpp_source.html":[4,0,3,6], +"text__utils_8h_source.html":[4,0,0,19], +"tree__node_8cpp.html":[4,0,0,20], +"tree__node_8cpp_source.html":[4,0,0,20], +"tree__node_8h.html":[4,0,0,21], +"tree__node_8h.html#acac9cbaeea226ed297804c012dc12b16":[4,0,0,21,1], +"tree__node_8h.html#acac9cbaeea226ed297804c012dc12b16a085fea7abdc5d904fe69a3081efd7398":[4,0,0,21,1,2], +"tree__node_8h.html#acac9cbaeea226ed297804c012dc12b16a3bb3e8c8a24891ba0f7608bcc96f8b0a":[4,0,0,21,1,1], +"tree__node_8h.html#acac9cbaeea226ed297804c012dc12b16afa03eb688ad8aa1db593d33dabd89bad":[4,0,0,21,1,0], +"tree__node_8h_source.html":[4,0,0,21], +"tree__node__test_8cpp_source.html":[4,0,3,7], +"typefactory_8h_source.html":[4,0,0,22], +"value__descriptions_8h_source.html":[4,0,0,23] }; diff --git a/docs/html/search/all_19.js b/docs/html/search/all_19.js index 85c0826..d1f71fb 100644 --- a/docs/html/search/all_19.js +++ b/docs/html/search/all_19.js @@ -4,10 +4,11 @@ var searchData= ['val_5f_20tt_1',['Signal value descriptions (<tt>VAL_</tt>)',['../md_README.html#autotoc_md115',1,'']]], ['valid_2',['valid',['../structDecodedSignalValue.html#aac0ed361af545cde48d0e6f41a859686',1,'DecodedSignalValue::valid'],['../structDecodedFrameValue.html#a4f51c31110dba1f73a763c596372beb0',1,'DecodedFrameValue::valid']]], ['validation_20and_20parse_20errors_3',['validation and parse errors',['../md_README.html#autotoc_md81',1,'Frame validation and parse errors'],['../md_README.html#autotoc_md85',1,'Signal validation and parse errors']]], - ['value_20descriptions_20tt_20val_5f_20tt_4',['Signal value descriptions (<tt>VAL_</tt>)',['../md_README.html#autotoc_md115',1,'']]], - ['value_20representation_20and_20precision_5',['Raw value representation and precision',['../md_README.html#autotoc_md67',1,'']]], - ['valuedescriptions_6',['valuedescriptions',['../structDecodeSignal.html#a822155d9bf4ed5f3300c9e9befe606ff',1,'DecodeSignal::valueDescriptions'],['../structSignalInfo.html#a43f47d0433d0cc39076bd7278b46af9c',1,'SignalInfo::valueDescriptions']]], - ['valuetype_7',['valuetype',['../structDecodeSignal.html#a9d37638129263b8902ba5be59baf39a8',1,'DecodeSignal::valueType'],['../decode__database_8h.html#ad9971b6ef33e02ba2c75d19c1d2518a1',1,'ValueType: decode_database.h']]], - ['valuetype_20tt_8',['<tt>ValueType</tt>',['../md_README.html#autotoc_md52',1,'']]], - ['variants_20in_20qt_20creator_9',['Configure all build variants in Qt Creator',['../md_README.html#autotoc_md136',1,'']]] + ['validation_20coverage_4',['Database validation coverage',['../md_tests_2README.html#autotoc_md143',1,'']]], + ['value_20descriptions_20tt_20val_5f_20tt_5',['Signal value descriptions (<tt>VAL_</tt>)',['../md_README.html#autotoc_md115',1,'']]], + ['value_20representation_20and_20precision_6',['Raw value representation and precision',['../md_README.html#autotoc_md67',1,'']]], + ['valuedescriptions_7',['valuedescriptions',['../structDecodeSignal.html#a822155d9bf4ed5f3300c9e9befe606ff',1,'DecodeSignal::valueDescriptions'],['../structSignalInfo.html#a43f47d0433d0cc39076bd7278b46af9c',1,'SignalInfo::valueDescriptions']]], + ['valuetype_8',['valuetype',['../structDecodeSignal.html#a9d37638129263b8902ba5be59baf39a8',1,'DecodeSignal::valueType'],['../decode__database_8h.html#ad9971b6ef33e02ba2c75d19c1d2518a1',1,'ValueType: decode_database.h']]], + ['valuetype_20tt_9',['<tt>ValueType</tt>',['../md_README.html#autotoc_md52',1,'']]], + ['variants_20in_20qt_20creator_10',['Configure all build variants in Qt Creator',['../md_README.html#autotoc_md136',1,'']]] ]; diff --git a/docs/html/search/all_7.js b/docs/html/search/all_7.js index 1685e60..c29213b 100644 --- a/docs/html/search/all_7.js +++ b/docs/html/search/all_7.js @@ -2,16 +2,16 @@ var searchData= [ ['can_20id_20normalization_0',['CAN ID Normalization',['../md_README.html#autotoc_md91',1,'']]], ['canid_1',['canid',['../structDecodeFrame.html#a52e10215388db7df8b541b9d9909edcf',1,'DecodeFrame::canId'],['../structFrameInfo.html#af470b0742c05bfbffc31c51a68f5c05e',1,'FrameInfo::canId'],['../structRawCanFrame.html#a2d0e7fa0e3d5c20bbed55ed8f7888ff4',1,'RawCanFrame::canId']]], - ['changed_2',['changed',['../md_changelog.html#autotoc_md19',1,'Changed'],['../md_changelog.html#autotoc_md15',1,'Changed'],['../md_changelog.html#autotoc_md10',1,'Changed'],['../md_changelog.html#autotoc_md7',1,'Changed'],['../md_changelog.html#autotoc_md4',1,'Changed']]], + ['changed_2',['changed',['../md_changelog.html#autotoc_md4',1,'Changed'],['../md_changelog.html#autotoc_md7',1,'Changed'],['../md_changelog.html#autotoc_md10',1,'Changed'],['../md_changelog.html#autotoc_md15',1,'Changed'],['../md_changelog.html#autotoc_md19',1,'Changed']]], ['changelog_3',['Changelog',['../md_changelog.html',1,'']]], ['code_20coverage_4',['Measuring code coverage',['../md_README.html#autotoc_md133',1,'']]], - ['comment_5',['comment',['../structSignalInfo.html#ac99ba7563a5dad01e39c4257216953f5',1,'SignalInfo::comment'],['../structFrameInfo.html#ace75121294f9d89b762080ab5643293c',1,'FrameInfo::comment'],['../structDecodeFrame.html#a25819ef907a32391af5c9f7cae9cdb22',1,'DecodeFrame::comment'],['../structDecodeSignal.html#ae28c905d4a2bc86419c2a8361bfea473',1,'DecodeSignal::comment']]], + ['comment_5',['comment',['../structDecodeSignal.html#ae28c905d4a2bc86419c2a8361bfea473',1,'DecodeSignal::comment'],['../structDecodeFrame.html#a25819ef907a32391af5c9f7cae9cdb22',1,'DecodeFrame::comment'],['../structFrameInfo.html#ace75121294f9d89b762080ab5643293c',1,'FrameInfo::comment'],['../structSignalInfo.html#ac99ba7563a5dad01e39c4257216953f5',1,'SignalInfo::comment']]], ['comments_6',['Comments',['../md_README.html#autotoc_md89',1,'']]], ['configure_20all_20build_20variants_20in_20qt_20creator_7',['Configure all build variants in Qt Creator',['../md_README.html#autotoc_md136',1,'']]], ['configure_20the_20gcc_20coverage_20build_20in_20qt_20creator_8',['Configure the GCC coverage build in Qt Creator',['../md_README.html#autotoc_md135',1,'']]], ['contains_9',['contains',['../classTypeFactory.html#a1a119ad2956abe146213c6ca124d8dd4',1,'TypeFactory']]], ['core_20without_20qt_20libraries_10',['Build core without Qt libraries',['../md_README.html#autotoc_md126',1,'']]], - ['coverage_11',['coverage',['../md_tests_2README.html#autotoc_md141',1,'Additional regression coverage'],['../md_tests_2README.html#autotoc_md138',1,'Decoder coverage'],['../md_tests_2README.html#autotoc_md142',1,'Definition lifetime and reload coverage'],['../md_README.html#autotoc_md133',1,'Measuring code coverage'],['../md_tests_2README.html#autotoc_md139',1,'Parser coverage']]], + ['coverage_11',['coverage',['../md_tests_2README.html#autotoc_md141',1,'Additional regression coverage'],['../md_tests_2README.html#autotoc_md143',1,'Database validation coverage'],['../md_tests_2README.html#autotoc_md138',1,'Decoder coverage'],['../md_tests_2README.html#autotoc_md142',1,'Definition lifetime and reload coverage'],['../md_README.html#autotoc_md133',1,'Measuring code coverage'],['../md_tests_2README.html#autotoc_md139',1,'Parser coverage']]], ['coverage_20build_20in_20qt_20creator_12',['Configure the GCC coverage build in Qt Creator',['../md_README.html#autotoc_md135',1,'']]], ['create_13',['create',['../classTypeFactory.html#a3f43b03d6e3a0fd503fdec01ecf62e9f',1,'TypeFactory']]], ['creator_14',['creator',['../md_README.html#autotoc_md136',1,'Configure all build variants in Qt Creator'],['../md_README.html#autotoc_md135',1,'Configure the GCC coverage build in Qt Creator'],['../classTypeFactory.html#ad82f537569ece3d3a4a376523fa94473',1,'TypeFactory::creator()']]], diff --git a/docs/html/search/all_8.js b/docs/html/search/all_8.js index 785186e..ecb8ff9 100644 --- a/docs/html/search/all_8.js +++ b/docs/html/search/all_8.js @@ -3,69 +3,70 @@ var searchData= ['data_0',['data',['../structRawCanFrame.html#a0acb487612104c99c9907344c035df83',1,'RawCanFrame']]], ['data_20structures_1',['Parsed Data Structures',['../md_README.html#autotoc_md37',1,'']]], ['database_2',['Build runtime decode database',['../md_README.html#autotoc_md102',1,'']]], - ['dbc_3',['Parse DBC',['../md_README.html#autotoc_md100',1,'']]], - ['dbc_20parser_20and_20runtime_20decode_20module_20documentation_4',['DBC Parser and Runtime Decode Module Documentation',['../md_README.html',1,'']]], - ['dbc_20syntax_5',['Supported DBC Syntax',['../md_README.html#autotoc_md78',1,'']]], - ['dbc_5fdatabase_2eh_6',['dbc_database.h',['../dbc__database_8h.html',1,'']]], - ['dbc_5fdecode_5fbuilder_2ecpp_7',['dbc_decode_builder.cpp',['../dbc__decode__builder_8cpp.html',1,'']]], - ['dbc_5fdecode_5fbuilder_2eh_8',['dbc_decode_builder.h',['../dbc__decode__builder_8h.html',1,'']]], - ['dbc_5fdecoder_2ecpp_9',['dbc_decoder.cpp',['../dbc__decoder_8cpp.html',1,'']]], - ['dbc_5fdecoder_2eh_10',['dbc_decoder.h',['../dbc__decoder_8h.html',1,'']]], - ['dbc_5fparser_2ecpp_11',['dbc_parser.cpp',['../dbc__parser_8cpp.html',1,'']]], - ['dbc_5fparser_2eh_12',['dbc_parser.h',['../dbc__parser_8h.html',1,'']]], - ['dbc_5ftree_5fbuilder_2ecpp_13',['dbc_tree_builder.cpp',['../dbc__tree__builder_8cpp.html',1,'']]], - ['dbc_5ftree_5fbuilder_2eh_14',['dbc_tree_builder.h',['../dbc__tree__builder_8h.html',1,'']]], - ['dbccommenthandler_15',['DbcCommentHandler',['../classDbcCommentHandler.html',1,'']]], - ['dbcdatabase_16',['DbcDatabase',['../structDbcDatabase.html',1,'']]], - ['dbcdatabase_20tt_17',['<tt>DbcDatabase</tt>',['../md_README.html#autotoc_md42',1,'']]], - ['dbcdecodebuilder_18',['DbcDecodeBuilder',['../classDbcDecodeBuilder.html',1,'']]], - ['dbcdecoder_19',['DbcDecoder',['../classDbcDecoder.html',1,'']]], - ['dbcdecoder_20tt_20',['<tt>DbcDecoder</tt>',['../md_README.html#autotoc_md73',1,'']]], - ['dbcdiagnostic_21',['DbcDiagnostic',['../structDbcDiagnostic.html',1,'']]], - ['dbcdiagnosticerror_22',['DbcDiagnosticError',['../classDbcDiagnosticError.html',1,'']]], - ['dbcframehandler_23',['DbcFrameHandler',['../classDbcFrameHandler.html',1,'']]], - ['dbcheaderhandler_24',['DbcHeaderHandler',['../classDbcHeaderHandler.html',1,'']]], - ['dbcparsecontext_25',['DbcParseContext',['../structDbcParseContext.html',1,'']]], - ['dbcparser_26',['DbcParser',['../classDbcParser.html',1,'']]], - ['dbcparseresult_27',['DbcParseResult',['../structDbcParseResult.html',1,'']]], - ['dbcsignalhandler_28',['DbcSignalHandler',['../classDbcSignalHandler.html',1,'']]], - ['dbcstatementdispatcher_29',['DbcStatementDispatcher',['../classDbcStatementDispatcher.html',1,'']]], - ['dbcstatementhandler_30',['DbcStatementHandler',['../classDbcStatementHandler.html',1,'']]], - ['dbctreebuilder_31',['DbcTreeBuilder',['../classDbcTreeBuilder.html',1,'']]], - ['dbctreemodel_32',['DbcTreeModel',['../classDbcTreeModel.html',1,'']]], - ['dbcunsupportedmultiplexinghandler_33',['DbcUnsupportedMultiplexingHandler',['../classDbcUnsupportedMultiplexingHandler.html',1,'']]], - ['dbcvaluedescriptionhandler_34',['DbcValueDescriptionHandler',['../classDbcValueDescriptionHandler.html',1,'']]], - ['debian_20ubuntu_35',['GCC, gcov, and LCOV on Debian/Ubuntu',['../md_README.html#autotoc_md134',1,'']]], - ['decode_36',['Decode',['../classDbcDecoder.html#a345951ffe5d943a70d3a1ea1f39e74f7',1,'DbcDecoder']]], - ['decode_20a_20raw_20frame_37',['Decode a raw frame',['../md_README.html#autotoc_md103',1,'']]], - ['decode_20database_38',['Build runtime decode database',['../md_README.html#autotoc_md102',1,'']]], - ['decode_20flow_39',['Decode Flow',['../md_README.html#autotoc_md95',1,'']]], - ['decode_20layer_40',['Runtime Decode Layer',['../md_README.html#autotoc_md47',1,'']]], - ['decode_20layer_20exists_41',['Why the Runtime Decode Layer Exists',['../md_README.html#autotoc_md33',1,'']]], - ['decode_20module_20documentation_42',['DBC Parser and Runtime Decode Module Documentation',['../md_README.html',1,'']]], - ['decode_20path_43',['2. Runtime decode path',['../md_README.html#autotoc_md31',1,'']]], - ['decode_20strategy_44',['Unified Decode Strategy',['../md_README.html#autotoc_md105',1,'']]], - ['decode_5fdatabase_2eh_45',['decode_database.h',['../decode__database_8h.html',1,'']]], - ['decodedatabase_46',['decodedatabase',['../classDecodeDatabase.html',1,'DecodeDatabase'],['../classDecodeDatabase.html#aa935a3a0d334f932d6a8c89a3818286a',1,'DecodeDatabase::DecodeDatabase()']]], - ['decodedatabase_20tt_47',['<tt>DecodeDatabase</tt>',['../md_README.html#autotoc_md60',1,'']]], - ['decodedframevalue_48',['DecodedFrameValue',['../structDecodedFrameValue.html',1,'']]], - ['decodedframevalue_20tt_49',['<tt>DecodedFrameValue</tt>',['../md_README.html#autotoc_md69',1,'']]], - ['decodedsignalvalue_50',['DecodedSignalValue',['../structDecodedSignalValue.html',1,'']]], - ['decodedsignalvalue_20tt_51',['<tt>DecodedSignalValue</tt>',['../md_README.html#autotoc_md65',1,'']]], - ['decodeframe_52',['DecodeFrame',['../structDecodeFrame.html',1,'']]], - ['decodeframe_20tt_53',['<tt>DecodeFrame</tt>',['../md_README.html#autotoc_md56',1,'']]], - ['decoder_20coverage_54',['Decoder coverage',['../md_tests_2README.html#autotoc_md138',1,'']]], - ['decoder_20layer_55',['Decoder Layer',['../md_README.html#autotoc_md62',1,'']]], - ['decodesignal_56',['DecodeSignal',['../structDecodeSignal.html',1,'']]], - ['decodesignal_20tt_57',['<tt>DecodeSignal</tt>',['../md_README.html#autotoc_md54',1,'']]], - ['definition_58',['definition',['../structDecodedFrameValue.html#a33e971ba10030506007aa201c89f351d',1,'DecodedFrameValue::definition'],['../structDecodedSignalValue.html#a828b5ef7faefc14fbf5dbd46249bcad2',1,'DecodedSignalValue::definition'],['../md_README.html#autotoc_md79',1,'Frame definition'],['../md_README.html#autotoc_md83',1,'Signal definition']]], - ['definition_20lifetime_20and_20reload_20coverage_59',['Definition lifetime and reload coverage',['../md_tests_2README.html#autotoc_md142',1,'']]], - ['definition_20ownership_20and_20reload_60',['Definition ownership and reload',['../md_README.html#autotoc_md70',1,'']]], - ['demo_61',['Build and run the demo',['../md_README.html#autotoc_md129',1,'']]], - ['descriptions_20tt_20val_5f_20tt_62',['Signal value descriptions (<tt>VAL_</tt>)',['../md_README.html#autotoc_md115',1,'']]], - ['diagnostics_63',['Structured diagnostics',['../md_README.html#autotoc_md76',1,'']]], - ['dispatch_64',['Dispatch',['../classDbcStatementDispatcher.html#a495c2d5c7ebb8103b1987cdd87c142ed',1,'DbcStatementDispatcher']]], - ['dlc_65',['dlc',['../structFrameInfo.html#aa2b198ea0f55e560c123d35e7b3ecab9',1,'FrameInfo::dlc'],['../structDecodeFrame.html#a51111ac13988be372d5673d12cf3e060',1,'DecodeFrame::dlc']]], - ['documentation_66',['documentation',['../md_README.html',1,'DBC Parser and Runtime Decode Module Documentation'],['../md_changelog.html#autotoc_md22',1,'Documentation'],['../md_README.html#autotoc_md130',1,'Generate API documentation']]], - ['documentation_20and_20maintenance_67',['documentation and maintenance',['../md_changelog.html#autotoc_md25',1,'Documentation and maintenance'],['../md_changelog.html#autotoc_md17',1,'Documentation and maintenance']]] + ['database_20validation_20coverage_3',['Database validation coverage',['../md_tests_2README.html#autotoc_md143',1,'']]], + ['dbc_4',['Parse DBC',['../md_README.html#autotoc_md100',1,'']]], + ['dbc_20parser_20and_20runtime_20decode_20module_20documentation_5',['DBC Parser and Runtime Decode Module Documentation',['../md_README.html',1,'']]], + ['dbc_20syntax_6',['Supported DBC Syntax',['../md_README.html#autotoc_md78',1,'']]], + ['dbc_5fdatabase_2eh_7',['dbc_database.h',['../dbc__database_8h.html',1,'']]], + ['dbc_5fdecode_5fbuilder_2ecpp_8',['dbc_decode_builder.cpp',['../dbc__decode__builder_8cpp.html',1,'']]], + ['dbc_5fdecode_5fbuilder_2eh_9',['dbc_decode_builder.h',['../dbc__decode__builder_8h.html',1,'']]], + ['dbc_5fdecoder_2ecpp_10',['dbc_decoder.cpp',['../dbc__decoder_8cpp.html',1,'']]], + ['dbc_5fdecoder_2eh_11',['dbc_decoder.h',['../dbc__decoder_8h.html',1,'']]], + ['dbc_5fparser_2ecpp_12',['dbc_parser.cpp',['../dbc__parser_8cpp.html',1,'']]], + ['dbc_5fparser_2eh_13',['dbc_parser.h',['../dbc__parser_8h.html',1,'']]], + ['dbc_5ftree_5fbuilder_2ecpp_14',['dbc_tree_builder.cpp',['../dbc__tree__builder_8cpp.html',1,'']]], + ['dbc_5ftree_5fbuilder_2eh_15',['dbc_tree_builder.h',['../dbc__tree__builder_8h.html',1,'']]], + ['dbccommenthandler_16',['DbcCommentHandler',['../classDbcCommentHandler.html',1,'']]], + ['dbcdatabase_17',['DbcDatabase',['../structDbcDatabase.html',1,'']]], + ['dbcdatabase_20tt_18',['<tt>DbcDatabase</tt>',['../md_README.html#autotoc_md42',1,'']]], + ['dbcdecodebuilder_19',['DbcDecodeBuilder',['../classDbcDecodeBuilder.html',1,'']]], + ['dbcdecoder_20',['DbcDecoder',['../classDbcDecoder.html',1,'']]], + ['dbcdecoder_20tt_21',['<tt>DbcDecoder</tt>',['../md_README.html#autotoc_md73',1,'']]], + ['dbcdiagnostic_22',['DbcDiagnostic',['../structDbcDiagnostic.html',1,'']]], + ['dbcdiagnosticerror_23',['DbcDiagnosticError',['../classDbcDiagnosticError.html',1,'']]], + ['dbcframehandler_24',['DbcFrameHandler',['../classDbcFrameHandler.html',1,'']]], + ['dbcheaderhandler_25',['DbcHeaderHandler',['../classDbcHeaderHandler.html',1,'']]], + ['dbcparsecontext_26',['DbcParseContext',['../structDbcParseContext.html',1,'']]], + ['dbcparser_27',['DbcParser',['../classDbcParser.html',1,'']]], + ['dbcparseresult_28',['DbcParseResult',['../structDbcParseResult.html',1,'']]], + ['dbcsignalhandler_29',['DbcSignalHandler',['../classDbcSignalHandler.html',1,'']]], + ['dbcstatementdispatcher_30',['DbcStatementDispatcher',['../classDbcStatementDispatcher.html',1,'']]], + ['dbcstatementhandler_31',['DbcStatementHandler',['../classDbcStatementHandler.html',1,'']]], + ['dbctreebuilder_32',['DbcTreeBuilder',['../classDbcTreeBuilder.html',1,'']]], + ['dbctreemodel_33',['DbcTreeModel',['../classDbcTreeModel.html',1,'']]], + ['dbcunsupportedmultiplexinghandler_34',['DbcUnsupportedMultiplexingHandler',['../classDbcUnsupportedMultiplexingHandler.html',1,'']]], + ['dbcvaluedescriptionhandler_35',['DbcValueDescriptionHandler',['../classDbcValueDescriptionHandler.html',1,'']]], + ['debian_20ubuntu_36',['GCC, gcov, and LCOV on Debian/Ubuntu',['../md_README.html#autotoc_md134',1,'']]], + ['decode_37',['Decode',['../classDbcDecoder.html#a345951ffe5d943a70d3a1ea1f39e74f7',1,'DbcDecoder']]], + ['decode_20a_20raw_20frame_38',['Decode a raw frame',['../md_README.html#autotoc_md103',1,'']]], + ['decode_20database_39',['Build runtime decode database',['../md_README.html#autotoc_md102',1,'']]], + ['decode_20flow_40',['Decode Flow',['../md_README.html#autotoc_md95',1,'']]], + ['decode_20layer_41',['Runtime Decode Layer',['../md_README.html#autotoc_md47',1,'']]], + ['decode_20layer_20exists_42',['Why the Runtime Decode Layer Exists',['../md_README.html#autotoc_md33',1,'']]], + ['decode_20module_20documentation_43',['DBC Parser and Runtime Decode Module Documentation',['../md_README.html',1,'']]], + ['decode_20path_44',['2. Runtime decode path',['../md_README.html#autotoc_md31',1,'']]], + ['decode_20strategy_45',['Unified Decode Strategy',['../md_README.html#autotoc_md105',1,'']]], + ['decode_5fdatabase_2eh_46',['decode_database.h',['../decode__database_8h.html',1,'']]], + ['decodedatabase_47',['decodedatabase',['../classDecodeDatabase.html',1,'DecodeDatabase'],['../classDecodeDatabase.html#a51a44cf7c3f23c98ec8781afe0097c0c',1,'DecodeDatabase::DecodeDatabase()']]], + ['decodedatabase_20tt_48',['<tt>DecodeDatabase</tt>',['../md_README.html#autotoc_md60',1,'']]], + ['decodedframevalue_49',['DecodedFrameValue',['../structDecodedFrameValue.html',1,'']]], + ['decodedframevalue_20tt_50',['<tt>DecodedFrameValue</tt>',['../md_README.html#autotoc_md69',1,'']]], + ['decodedsignalvalue_51',['DecodedSignalValue',['../structDecodedSignalValue.html',1,'']]], + ['decodedsignalvalue_20tt_52',['<tt>DecodedSignalValue</tt>',['../md_README.html#autotoc_md65',1,'']]], + ['decodeframe_53',['DecodeFrame',['../structDecodeFrame.html',1,'']]], + ['decodeframe_20tt_54',['<tt>DecodeFrame</tt>',['../md_README.html#autotoc_md56',1,'']]], + ['decoder_20coverage_55',['Decoder coverage',['../md_tests_2README.html#autotoc_md138',1,'']]], + ['decoder_20layer_56',['Decoder Layer',['../md_README.html#autotoc_md62',1,'']]], + ['decodesignal_57',['DecodeSignal',['../structDecodeSignal.html',1,'']]], + ['decodesignal_20tt_58',['<tt>DecodeSignal</tt>',['../md_README.html#autotoc_md54',1,'']]], + ['definition_59',['definition',['../structDecodedFrameValue.html#a33e971ba10030506007aa201c89f351d',1,'DecodedFrameValue::definition'],['../structDecodedSignalValue.html#a828b5ef7faefc14fbf5dbd46249bcad2',1,'DecodedSignalValue::definition'],['../md_README.html#autotoc_md79',1,'Frame definition'],['../md_README.html#autotoc_md83',1,'Signal definition']]], + ['definition_20lifetime_20and_20reload_20coverage_60',['Definition lifetime and reload coverage',['../md_tests_2README.html#autotoc_md142',1,'']]], + ['definition_20ownership_20and_20reload_61',['Definition ownership and reload',['../md_README.html#autotoc_md70',1,'']]], + ['demo_62',['Build and run the demo',['../md_README.html#autotoc_md129',1,'']]], + ['descriptions_20tt_20val_5f_20tt_63',['Signal value descriptions (<tt>VAL_</tt>)',['../md_README.html#autotoc_md115',1,'']]], + ['diagnostics_64',['Structured diagnostics',['../md_README.html#autotoc_md76',1,'']]], + ['dispatch_65',['Dispatch',['../classDbcStatementDispatcher.html#a495c2d5c7ebb8103b1987cdd87c142ed',1,'DbcStatementDispatcher']]], + ['dlc_66',['dlc',['../structDecodeFrame.html#a51111ac13988be372d5673d12cf3e060',1,'DecodeFrame::dlc'],['../structFrameInfo.html#aa2b198ea0f55e560c123d35e7b3ecab9',1,'FrameInfo::dlc']]], + ['documentation_67',['documentation',['../md_README.html',1,'DBC Parser and Runtime Decode Module Documentation'],['../md_changelog.html#autotoc_md22',1,'Documentation'],['../md_README.html#autotoc_md130',1,'Generate API documentation']]], + ['documentation_20and_20maintenance_68',['documentation and maintenance',['../md_changelog.html#autotoc_md17',1,'Documentation and maintenance'],['../md_changelog.html#autotoc_md25',1,'Documentation and maintenance']]] ]; diff --git a/docs/html/search/functions_3.js b/docs/html/search/functions_3.js index 1d7cf78..6a0b96c 100644 --- a/docs/html/search/functions_3.js +++ b/docs/html/search/functions_3.js @@ -1,6 +1,6 @@ var searchData= [ ['decode_0',['Decode',['../classDbcDecoder.html#a345951ffe5d943a70d3a1ea1f39e74f7',1,'DbcDecoder']]], - ['decodedatabase_1',['DecodeDatabase',['../classDecodeDatabase.html#aa935a3a0d334f932d6a8c89a3818286a',1,'DecodeDatabase']]], + ['decodedatabase_1',['DecodeDatabase',['../classDecodeDatabase.html#a51a44cf7c3f23c98ec8781afe0097c0c',1,'DecodeDatabase']]], ['dispatch_2',['Dispatch',['../classDbcStatementDispatcher.html#a495c2d5c7ebb8103b1987cdd87c142ed',1,'DbcStatementDispatcher']]] ]; diff --git a/docs/html/signal__layout_8h_source.html b/docs/html/signal__layout_8h_source.html new file mode 100644 index 0000000..3b40e95 --- /dev/null +++ b/docs/html/signal__layout_8h_source.html @@ -0,0 +1,129 @@ + + + + + + + +DBC Framework: signal_layout.h Source File + + + + + + + + + + + + + +
+
+ + + + + + +
+
DBC Framework +
+
DBC parsing and CAN signal decoding framework
+
+
+ + + + + + + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+
+
+
+
+
Loading...
+
Searching...
+
No Matches
+
+
+
+
+ +
+
signal_layout.h
+
+
+
1// SPDX-License-Identifier: MIT
+
2#ifndef SIGNAL_LAYOUT_H
+
3#define SIGNAL_LAYOUT_H
+
4
+
5#include <cstdint>
+
6
+
8inline std::uint64_t SignalStartCoordinate(std::uint32_t startBit, bool littleEndian) {
+
9 return littleEndian ? std::uint64_t(startBit)
+
10 : std::uint64_t(startBit / 8U) * 8U + 7U - startBit % 8U;
+
11}
+
12
+
13inline bool IsValidSignalLayout(std::uint32_t startBit, std::uint32_t length,
+
14 bool littleEndian, std::uint8_t dlc) {
+
15 return length >= 1U && length <= 64U &&
+
16 SignalStartCoordinate(startBit, littleEndian) + length <= std::uint64_t(dlc) * 8U;
+
17}
+
18
+
19#endif
+
+
+ + + + diff --git a/docs/html/structDbcParseContext.html b/docs/html/structDbcParseContext.html index e41f72f..ec2e68b 100644 --- a/docs/html/structDbcParseContext.html +++ b/docs/html/structDbcParseContext.html @@ -141,7 +141,7 @@ Public Attributes
-

Definition at line 513 of file dbc_statement_handler.cpp.

+

Definition at line 510 of file dbc_statement_handler.cpp.

@@ -161,7 +161,7 @@ Public Attributes
-

Definition at line 436 of file dbc_statement_handler.cpp.

+

Definition at line 433 of file dbc_statement_handler.cpp.

diff --git a/docs/html/structDecodedFrameValue.html b/docs/html/structDecodedFrameValue.html index 8b72d47..1bf8f23 100644 --- a/docs/html/structDecodedFrameValue.html +++ b/docs/html/structDecodedFrameValue.html @@ -135,7 +135,7 @@ Public Attributes

Definition at line 72 of file dbc_decoder.h.

-

Referenced by DbcDecoder::Decode().

+

Referenced by DbcDecoder::Decode().

@@ -154,7 +154,7 @@ Public Attributes

Definition at line 73 of file dbc_decoder.h.

-

Referenced by DbcDecoder::Decode().

+

Referenced by DbcDecoder::Decode().

@@ -190,7 +190,7 @@ Public Attributes

Definition at line 75 of file dbc_decoder.h.

-

Referenced by DbcDecoder::Decode().

+

Referenced by DbcDecoder::Decode().

@@ -209,7 +209,7 @@ Public Attributes

Definition at line 76 of file dbc_decoder.h.

-

Referenced by DbcDecoder::Decode().

+

Referenced by DbcDecoder::Decode().

diff --git a/docs/html/structDecodedSignalValue.html b/docs/html/structDecodedSignalValue.html index 3f05095..047d310 100644 --- a/docs/html/structDecodedSignalValue.html +++ b/docs/html/structDecodedSignalValue.html @@ -174,7 +174,7 @@ Public Attributes

Definition at line 53 of file dbc_decoder.h.

-

Referenced by DbcDecoder::Decode(), and GetValueDescription().

+

Referenced by DbcDecoder::Decode(), and GetValueDescription().

@@ -193,7 +193,7 @@ Public Attributes

Definition at line 54 of file dbc_decoder.h.

-

Referenced by DbcDecoder::Decode().

+

Referenced by DbcDecoder::Decode().

@@ -248,7 +248,7 @@ Public Attributes

Definition at line 57 of file dbc_decoder.h.

-

Referenced by DbcDecoder::Decode(), and GetValueDescription().

+

Referenced by DbcDecoder::Decode(), and GetValueDescription().

diff --git a/docs/html/structRawCanFrame.html b/docs/html/structRawCanFrame.html index 30941b2..200138c 100644 --- a/docs/html/structRawCanFrame.html +++ b/docs/html/structRawCanFrame.html @@ -131,7 +131,7 @@ Public Attributes

Definition at line 25 of file dbc_decoder.h.

-

Referenced by DbcDecoder::Decode().

+

Referenced by DbcDecoder::Decode().

@@ -150,7 +150,7 @@ Public Attributes

Definition at line 26 of file dbc_decoder.h.

-

Referenced by DbcDecoder::Decode().

+

Referenced by DbcDecoder::Decode().

@@ -169,7 +169,7 @@ Public Attributes

Definition at line 27 of file dbc_decoder.h.

-

Referenced by DbcDecoder::Decode().

+

Referenced by DbcDecoder::Decode().

diff --git a/docs/html/tests_2main_8cpp_source.html b/docs/html/tests_2main_8cpp_source.html index 4a4c0a0..b86748e 100644 --- a/docs/html/tests_2main_8cpp_source.html +++ b/docs/html/tests_2main_8cpp_source.html @@ -107,14 +107,17 @@ $(document).ready(function(){initNavTree('tests_2main_8cpp_source.html',''); ini
7
8int RunLifetimeTests(int argc, char **argv);
9
-
10int main(int argc, char **argv) {
-
11 QCoreApplication application(argc, argv);
-
12 const int parserResult = RunParserTests(argc, argv);
-
13 const int decoderResult = RunDecoderTests(argc, argv);
-
14 const int coverageResult = RunCoverageTests(argc, argv);
-
15 const int lifetimeResult = RunLifetimeTests(argc, argv);
-
16 return parserResult | decoderResult | coverageResult | lifetimeResult;
-
17}
+
10int RunValidationTests(int argc, char **argv);
+
11
+
12int main(int argc, char **argv) {
+
13 QCoreApplication application(argc, argv);
+
14 const int parserResult = RunParserTests(argc, argv);
+
15 const int decoderResult = RunDecoderTests(argc, argv);
+
16 const int coverageResult = RunCoverageTests(argc, argv);
+
17 const int lifetimeResult = RunLifetimeTests(argc, argv);
+
18 const int validationResult = RunValidationTests(argc, argv);
+
19 return validationResult | parserResult | decoderResult | coverageResult | lifetimeResult;
+
20}
diff --git a/tests/README.md b/tests/README.md index f856791..ffdefd8 100644 --- a/tests/README.md +++ b/tests/README.md @@ -24,7 +24,7 @@ make -j make check ``` -Or run `./tests/dbc_tests` directly. Parser, decoder, lifetime, and additional coverage +Or run `./tests/dbc_tests` directly. Parser, decoder, lifetime, validation, and additional coverage suites run, and the executable returns nonzero when any suite fails. Decoder cases appear as individual QtTest data rows; parser checks are grouped into named test slots. Checks also run in release builds. @@ -41,7 +41,8 @@ make check ``` `parser_tests.pri` and `decoder_tests.pri` include the original test sources; -`tests.pro` also includes `dbc_coverage_test.cpp` and `dbc_lifetime_test.cpp`. +`tests.pro` also includes `dbc_coverage_test.cpp`, `dbc_lifetime_test.cpp`, and +`dbc_validation_test.cpp`. `../core/core.pri` supplies the library include path and linkage. ## Decoder coverage @@ -52,10 +53,11 @@ signals, signed values, scaling, 64-bit extraction, signed/unsigned boundary val payloads, and Intel regression cases. Frame validity means the definition was found; frame status reports unknown frames, short payloads, and signal errors. Tests exercise empty/short/exact/long payloads, ignored trailing bytes, signals -outside declared DLC, invalid enum metadata and overflowing start bits, and -preservation of successful signals. Physical conversion checks cover NaN/Inf -parameters, multiplication/addition overflow, finite limits, signed/unsigned raw -retention, and raw selector activation after physical conversion failure. +outside declared DLC, invalid enum metadata and overflowing start bits rejected +at construction, and preservation of successful signals on partial payloads. +Physical conversion checks cover NaN/Inf parameters rejected at construction, +multiplication/addition overflow, finite limits, signed/unsigned raw retention, +and raw selector activation after physical conversion failure. ## Parser coverage @@ -116,8 +118,9 @@ of invalid markers, selector references/ranges, overlaps, and SG_MUL_VAL_. `dbc_coverage_test.cpp` runs as part of `dbc_tests`. It covers signed boundaries below 64 bits, deterministic extraction comparisons for both byte orders across -lengths 1–64, frame lookup failures, exact decode statuses, malformed runtime -multiplexing, PGN boundaries, comment handling, file formatting, metadata copying +lengths 1–64, frame lookup failures, exact decode statuses, construction rejection +of malformed runtime multiplexing, PGN boundaries, comment handling, file +formatting, metadata copying and source lifetime, physical conversion, and TypeFactory registration/creation. Direct resolver tests construct ambiguous databases through the public metadata API, verify source locations, and check that ambiguous targets receive no values. @@ -141,3 +144,19 @@ duplicate runtime keys, including manually supplied parsed metadata. Failed relo leaves the active database intact; empty reload clears it. Invalid/unknown results and insufficient payloads retain their documented ownership and status behavior. Inactive branches and unavailable selectors also retain definitions across reload. + +## Database validation coverage + +`dbc_validation_test.cpp` checks direct construction and manually assembled +`DbcDatabase` input to `DbcDecodeBuilder`: standard/extended CAN ID limits, +encoded/reserved ID bits, duplicate keys, lengths 0/1–64/65/UINT32_MAX, +Intel/Motorola payload boundaries and overflowing start bits, invalid enums, +NaN and both infinities in every numeric parameter, and reversed physical ranges. +Selector tests include missing, duplicate, and signed selectors, branch ranges, +UINT64_MAX selection, mixed-order overlaps, and exclusive branches sharing bits. +Valid definitions cover the full byte-count storage range, including zero-byte +frames without signals and 255-byte frames. Existing extraction tests cover +lengths 1–64. Snapshot tests mutate retained frame, signal, and description aliases +after rvalue construction, and verify unchanged lookups and metadata. Failed +constructor/builder replacement preserves the active snapshot; clear removes its +index while saved handles remain alive. diff --git a/tests/dbc_coverage_test.cpp b/tests/dbc_coverage_test.cpp index 01dddac..701c871 100644 --- a/tests/dbc_coverage_test.cpp +++ b/tests/dbc_coverage_test.cpp @@ -104,6 +104,7 @@ void CoverageTest::frameLookup() { QVERIFY(absent.definition == nullptr); QVERIFY(absent.signals.empty()); DecodeSignal signal; + signal.length = 8U; DecodeDatabase database = SingleSignal(signal); QVERIFY(decoder.FindFrame(database, 18U, false) == nullptr); QVERIFY(decoder.FindFrame(database, 17U, true) == nullptr); @@ -227,6 +228,10 @@ void CoverageTest::invalidSignalStatus() { signal.startBit = start; signal.length = length; signal.byteOrder = order == 0 ? ByteOrder::Intel : ByteOrder::Motorola; + if (status == int(SignalDecodeStatus::InvalidDefinition)) { + QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) SingleSignal(signal)); + return; + } const DecodeDatabase database = SingleSignal(signal); RawCanFrame raw; raw.canId = 17U; @@ -272,19 +277,7 @@ void CoverageTest::malformedRuntimeMultiplexing() { if (kind == 1) definitions[0].signals[0].valueType = ValueType::Signed; if (kind == 2) definitions[0].signals[0].length = 65U; if (kind == 3) definitions[0].signals[1].multiplexing.value = 2U; - const DecodeDatabase database(std::move(definitions)); - RawCanFrame raw; - raw.canId = 17U; - raw.data = {1U, 42U, 99U}; - const DecodedFrameValue result = DbcDecoder().Decode(database, raw); - QCOMPARE(int(result.signals[1].status), int(SignalDecodeStatus::InvalidDefinition)); - QVERIFY(!result.signals[1].valid); - QVERIFY(result.signals[2].valid); - QCOMPARE(std::get(result.signals[2].rawValue), std::uint64_t(99)); - if (kind != 3) { - QCOMPARE(int(result.signals[0].status), int(SignalDecodeStatus::InvalidDefinition)); - if (kind == 0) QCOMPARE(int(result.signals[3].status), int(SignalDecodeStatus::InvalidDefinition)); - } + QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase(definitions)); } void CoverageTest::pgn_data() { diff --git a/tests/dbc_decoder_test.cpp b/tests/dbc_decoder_test.cpp index c47018d..d455bf7 100644 --- a/tests/dbc_decoder_test.cpp +++ b/tests/dbc_decoder_test.cpp @@ -4,6 +4,7 @@ #include #include #include +#include struct DecodeCase { const char *name; @@ -77,6 +78,10 @@ void DbcDecoderTest::decode() { definition.canId = 0x123U; definition.dlc = 8U; definition.signals.push_back(signal); + if (test.length == 0U || test.length > 64U || test.startBit > 64U) { + QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({definition})); + return; + } const DecodeDatabase database({definition}); RawCanFrame frame; @@ -253,22 +258,7 @@ void DbcDecoderTest::invalidDefinitions() { case 7: bad.multiplexing.role = MultiplexRole::Branch; break; } definition.signals = {bad, good}; - const DecodeDatabase database({definition}); - RawCanFrame raw; - raw.data = {0xFFU, 0xFFU}; // Extra bytes must not rescue an invalid definition. - const DecodedFrameValue result = DbcDecoder().Decode(database, raw); - QVERIFY(result.valid); - QCOMPARE(result.status, FrameDecodeStatus::SignalErrors); - QCOMPARE(result.signals[0].status, SignalDecodeStatus::InvalidDefinition); - QVERIFY(!result.signals[0].valid && !result.signals[0].GetValueDescription()); - QCOMPARE(result.signals[1].status, SignalDecodeStatus::Valid); - QCOMPARE(std::get(result.signals[1].rawValue), std::uint64_t(15U)); - QCOMPARE(result.signals[1].physicalValue, 15.0); - raw.data.clear(); - const DecodedFrameValue shortResult = DbcDecoder().Decode(database, raw); - QCOMPARE(shortResult.status, FrameDecodeStatus::InsufficientPayload); - QCOMPARE(shortResult.signals[0].status, SignalDecodeStatus::InvalidDefinition); - QCOMPARE(shortResult.signals[1].status, SignalDecodeStatus::InsufficientPayload); + QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({definition})); } } @@ -313,6 +303,10 @@ void DbcDecoderTest::physicalConversion() { definition.signals.push_back(signal); RawCanFrame raw; raw.data = {0x32U}; + if (!std::isfinite(factor) || !std::isfinite(offset)) { + QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({definition})); + continue; + } const DecodedFrameValue result = DbcDecoder().Decode(DecodeDatabase({definition}), raw); QVERIFY(result.valid); QCOMPARE(result.status, finite ? FrameDecodeStatus::Valid : FrameDecodeStatus::SignalErrors); @@ -340,25 +334,25 @@ void DbcDecoderTest::selectorConversionFailure() { definition.dlc = 1U; DecodeSignal selector; selector.name = "Selector"; - selector.length = 1U; + selector.length = 2U; selector.multiplexing.role = MultiplexRole::Selector; - selector.factor = std::numeric_limits::infinity(); + selector.factor = std::numeric_limits::max(); DecodeSignal branch; branch.name = "Branch"; - branch.startBit = 1U; - branch.length = 7U; + branch.startBit = 2U; + branch.length = 6U; branch.multiplexing.role = MultiplexRole::Branch; - branch.multiplexing.value = 1U; + branch.multiplexing.value = 2U; definition.signals = {branch, selector}; RawCanFrame raw; - raw.data = {85U}; + raw.data = {170U}; const DecodedFrameValue result = DbcDecoder().Decode(DecodeDatabase({definition}), raw); QCOMPARE(result.status, FrameDecodeStatus::SignalErrors); QVERIFY(result.signals[0].valid); QCOMPARE(result.signals[0].physicalValue, 42.0); QCOMPARE(result.signals[1].status, SignalDecodeStatus::PhysicalConversionFailed); - QCOMPARE(std::get(result.signals[1].rawValue), std::uint64_t(1U)); - raw.data = {84U}; + QCOMPARE(std::get(result.signals[1].rawValue), std::uint64_t(2U)); + raw.data = {168U}; selector.factor = 1.0; definition.signals = {branch, selector}; const DecodedFrameValue inactive = DbcDecoder().Decode(DecodeDatabase({definition}), raw); @@ -378,9 +372,7 @@ void DbcDecoderTest::zeroDlc() { DecodeSignal signal; signal.length = 8U; definition.signals.push_back(signal); - const DecodedFrameValue invalid = DbcDecoder().Decode(DecodeDatabase({definition}), raw); - QCOMPARE(invalid.status, FrameDecodeStatus::SignalErrors); - QCOMPARE(invalid.signals[0].status, SignalDecodeStatus::InvalidDefinition); + QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({definition})); } int RunDecoderTests(int argc, char **argv) { diff --git a/tests/dbc_lifetime_test.cpp b/tests/dbc_lifetime_test.cpp index 5cb26d8..05f1078 100644 --- a/tests/dbc_lifetime_test.cpp +++ b/tests/dbc_lifetime_test.cpp @@ -175,6 +175,8 @@ void DbcLifetimeTest::duplicateKeys() { QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({frame, frame})); DecodeSignal signal; signal.name = "State"; + signal.length = 1U; + frame.dlc = 1U; frame.signals = {signal, signal}; QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({frame})); DbcDatabase parsed; diff --git a/tests/dbc_parser_test.cpp b/tests/dbc_parser_test.cpp index 856b430..13e8305 100644 --- a/tests/dbc_parser_test.cpp +++ b/tests/dbc_parser_test.cpp @@ -603,16 +603,9 @@ void DbcParserTest::multiplexingBoundaries() { "Adjacent large integers remain distinct"); std::vector definitions = runtime.GetFrames(); definitions[0].signals[1].length = 0U; - runtime = DecodeDatabase(definitions); - result = DbcDecoder().Decode(runtime, raw); - CHECK(result.signals[0].status == SignalDecodeStatus::InvalidDefinition && - result.signals[1].status == SignalDecodeStatus::InvalidDefinition, - "Malformed runtime selector distinguished from payload errors"); + QVERIFY_THROWS_EXCEPTION(std::invalid_argument, runtime = DecodeDatabase(definitions)); definitions[0].signals.pop_back(); - runtime = DecodeDatabase(definitions); - result = DbcDecoder().Decode(runtime, raw); - CHECK(result.signals[0].status == SignalDecodeStatus::InvalidDefinition, - "Missing runtime selector"); + QVERIFY_THROWS_EXCEPTION(std::invalid_argument, runtime = DecodeDatabase(definitions)); fixture.Write("BO_ 1 Frame: 2 ECU\n\n" "SG_ Branch m2 : 8|8@1+ (1,0) [0|255] \"\" ECU\n" "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"); diff --git a/tests/dbc_validation_test.cpp b/tests/dbc_validation_test.cpp new file mode 100644 index 0000000..6f1c574 --- /dev/null +++ b/tests/dbc_validation_test.cpp @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: MIT +#include "dbc_decode_builder.h" +#include "dbc_decoder.h" +#include +#include +#include +#include + +namespace { + DecodeFrame ValidFrame() { + DecodeFrame frame; + frame.canId = 0x123U; + frame.dlc = 8U; + DecodeSignal signal; + signal.name = "Value"; + signal.length = 8U; + frame.signals.push_back(signal); + return frame; + } + + DbcDatabase ParsedDefinitions(const std::vector &frames) { + DbcDatabase database; + for (const DecodeFrame &frame: frames) { + FrameInfo target; + target.canId = frame.canId; + target.isExtended = frame.isExtended; + target.dlc = frame.dlc; + for (const DecodeSignal &signal: frame.signals) { + SignalInfo copy; + copy.name = signal.name; + copy.startBit = signal.startBit; + copy.length = signal.length; + copy.isLittleEndian = signal.byteOrder == ByteOrder::Intel; + copy.isSigned = signal.valueType == ValueType::Signed; + copy.factor = signal.factor; + copy.offset = signal.offset; + copy.minimum = signal.minimum; + copy.maximum = signal.maximum; + copy.multiplexing = signal.multiplexing; + target.signals.push_back(copy); + } + database.frames.push_back(target); + } + return database; + } +}// namespace + +class DbcValidationTest : public QObject { + Q_OBJECT +private Q_SLOTS: + void invalidMetadata_data(); + void invalidMetadata(); + void numericParameters_data(); + void numericParameters(); + void validBoundaries(); + void multiplexingBoundaries(); + void independentSnapshot(); + void failedReplacement(); +}; + +void DbcValidationTest::invalidMetadata_data() { + QTest::addColumn("kind"); + QTest::newRow("standard-id") << 0; + QTest::newRow("extended-id") << 1; + QTest::newRow("encoded-id") << 2; + QTest::newRow("duplicate-frame") << 3; + QTest::newRow("duplicate-signal") << 4; + QTest::newRow("zero-length") << 5; + QTest::newRow("65-bits") << 6; + QTest::newRow("length-overflow") << 7; + QTest::newRow("intel-start-overflow") << 8; + QTest::newRow("motorola-start-overflow") << 9; + QTest::newRow("intel-outside-payload") << 10; + QTest::newRow("motorola-outside-payload") << 11; + QTest::newRow("zero-payload") << 12; + QTest::newRow("inverted-range") << 13; + QTest::newRow("byte-order") << 14; + QTest::newRow("value-type") << 15; + QTest::newRow("multiplex-role") << 16; + QTest::newRow("missing-selector") << 17; + QTest::newRow("signed-selector") << 18; + QTest::newRow("two-selectors") << 19; + QTest::newRow("branch-range") << 20; + QTest::newRow("unconditional-overlap") << 21; + QTest::newRow("mixed-order-overlap") << 22; + QTest::newRow("same-branch-overlap") << 23; + QTest::newRow("selector-branch-overlap") << 24; + QTest::newRow("maximum-can-id") << 25; +} + +void DbcValidationTest::invalidMetadata() { + QFETCH(int, kind); + std::vector frames = {ValidFrame()}; + DecodeFrame &frame = frames[0]; + DecodeSignal &signal = frame.signals[0]; + DecodeSignal other = signal; + other.name = "Other"; + switch (kind) { + case 0: + frame.canId = 0x800U; + break; + case 1: + frame.isExtended = true; + frame.canId = 0x20000000U; + break; + case 2: + frame.isExtended = true; + frame.canId = 0x80000123U; + break; + case 3: + frames.push_back(frame); + break; + case 4: + frame.signals.push_back(signal); + break; + case 5: + signal.length = 0U; + break; + case 6: + signal.length = 65U; + break; + case 7: + signal.length = std::numeric_limits::max(); + break; + case 8: + signal.startBit = std::numeric_limits::max(); + break; + case 9: + signal.byteOrder = ByteOrder::Motorola; + signal.startBit = std::numeric_limits::max(); + break; + case 10: + signal.startBit = 57U; + break; + case 11: + signal.byteOrder = ByteOrder::Motorola; + signal.startBit = 56U; + break; + case 12: + frame.dlc = 0U; + break; + case 13: + signal.minimum = 2.0; + signal.maximum = 1.0; + break; + case 14: + signal.byteOrder = static_cast(99); + break; + case 15: + signal.valueType = static_cast(99); + break; + case 16: + signal.multiplexing.role = static_cast(99); + break; + case 17: + signal.multiplexing.role = MultiplexRole::Branch; + break; + case 18: + signal.multiplexing.role = MultiplexRole::Selector; + signal.valueType = ValueType::Signed; + break; + case 19: + signal.multiplexing.role = MultiplexRole::Selector; + other.multiplexing.role = MultiplexRole::Selector; + other.startBit = 8U; + frame.signals.push_back(other); + break; + case 20: + case 23: + case 24: + signal.multiplexing.role = MultiplexRole::Selector; + other.multiplexing.role = MultiplexRole::Branch; + other.startBit = kind == 24 ? 0U : 8U; + other.multiplexing.value = kind == 20 ? 256U : 1U; + frame.signals.push_back(other); + if (kind == 23) { + other.name = "Third"; + frame.signals.push_back(other); + } + break; + case 21: + frame.signals.push_back(other); + break; + case 22: + other.byteOrder = ByteOrder::Motorola; + other.startBit = 7U; + frame.signals.push_back(other); + break; + case 25: + frame.canId = std::numeric_limits::max(); + break; + } + QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase(frames)); + // Parsed metadata uses bools for byte order and signedness, so invalid enum + // values exist only in direct runtime definitions. + if (kind != 14 && kind != 15) + QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DbcDecodeBuilder().Build(ParsedDefinitions(frames))); +} + +void DbcValidationTest::numericParameters_data() { + QTest::addColumn("field"); + QTest::addColumn("value"); + const double values[] = {std::numeric_limits::quiet_NaN(), + std::numeric_limits::infinity(), + -std::numeric_limits::infinity()}; + for (int field = 0; field < 4; ++field) { + for (int index = 0; index < 3; ++index) { + const QByteArray name = QByteArray::number(field) + "-" + QByteArray::number(index); + QTest::newRow(name.constData()) << field << values[index]; + } + } +} + +void DbcValidationTest::numericParameters() { + QFETCH(int, field); + QFETCH(double, value); + DecodeFrame frame = ValidFrame(); + DecodeSignal &signal = frame.signals[0]; + switch (field) { + case 0: + signal.factor = value; + break; + case 1: + signal.offset = value; + break; + case 2: + signal.minimum = value; + break; + case 3: + signal.maximum = value; + break; + } + QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({frame})); + QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DbcDecodeBuilder().Build(ParsedDefinitions({frame}))); +} + +void DbcValidationTest::validBoundaries() { + for (ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) { + for (std::uint8_t dlc: {std::uint8_t(8U), std::uint8_t(64U), std::uint8_t(255U)}) { + DecodeFrame standard = ValidFrame(); + standard.canId = 0x7FFU; + standard.dlc = dlc; + DecodeSignal &signal = standard.signals[0]; + signal.byteOrder = order; + signal.startBit = (std::uint32_t(dlc) - 8U) * 8U + (order == ByteOrder::Intel ? 0U : 7U); + signal.length = 64U; + signal.factor = -2.0; + signal.offset = std::numeric_limits::max(); + signal.minimum = -std::numeric_limits::max(); + signal.maximum = std::numeric_limits::max(); + DecodeFrame extended = standard; + extended.isExtended = true; + DecodeFrame maximum = extended; + maximum.canId = 0x1FFFFFFFU; + const std::vector frames = {standard, extended, maximum}; + const DecodeDatabase direct(frames); + const DecodeDatabase built = DbcDecodeBuilder().Build(ParsedDefinitions(frames)); + for (const DecodeFrame &frame: frames) { + QVERIFY(direct.FindFrame(frame.GetKey())); + QVERIFY(built.FindSignal(SignalKey(frame.GetKey(), "Value"))); + } + } + } + DecodeFrame empty; + empty.canId = 0U; + QVERIFY(DecodeDatabase({empty}).FindFrame(empty.GetKey())); + QVERIFY(DbcDecodeBuilder().Build(DbcDatabase()).GetFrames().empty()); +} + +void DbcValidationTest::multiplexingBoundaries() { + DecodeFrame frame = ValidFrame(); + frame.dlc = 9U; + DecodeSignal &selector = frame.signals[0]; + selector.length = 64U; + selector.multiplexing.role = MultiplexRole::Selector; + DecodeSignal first; + first.name = "First"; + first.startBit = 64U; + first.length = 8U; + first.multiplexing.role = MultiplexRole::Branch; + first.multiplexing.value = std::numeric_limits::max(); + DecodeSignal second = first; + second.name = "Second"; + second.byteOrder = ByteOrder::Motorola; + second.startBit = 71U; + second.multiplexing.value = 0U; + frame.signals.push_back(first); + frame.signals.push_back(second); + const DecodeDatabase direct({frame}); + const DecodeDatabase built = DbcDecodeBuilder().Build(ParsedDefinitions({frame})); + RawCanFrame raw; + raw.canId = frame.canId; + raw.data.assign(9U, 255U); + for (const DecodeDatabase &database: {direct, built}) { + const DecodedFrameValue result = DbcDecoder().Decode(database, raw); + QCOMPARE(result.signals[1].status, SignalDecodeStatus::Valid); + QCOMPARE(result.signals[2].status, SignalDecodeStatus::Inactive); + } +} + +void DbcValidationTest::independentSnapshot() { + std::vector frames = {ValidFrame()}; + DecodeFrame &alias = frames[0]; + DecodeSignal &signalAlias = alias.signals[0]; + signalAlias.valueDescriptions.emplace(std::uint64_t(1U), "One"); + std::string &descriptionAlias = signalAlias.valueDescriptions.begin()->second; + const FrameKey key = alias.GetKey(); + const DecodeDatabase database(std::move(frames)); + alias.canId = 456U; + signalAlias.name = "Changed"; + signalAlias.startBit = std::numeric_limits::max(); + descriptionAlias = "Changed"; + const std::shared_ptr saved = database.FindFrame(key); + QVERIFY(saved); + QCOMPARE(saved->canId, key.canId); + QVERIFY(!database.FindFrame(FrameKey(456U, false))); + const std::shared_ptr signal = database.FindSignal(SignalKey(key, "Value")); + QVERIFY(signal); + QCOMPARE(signal->startBit, 0U); + QCOMPARE(signal->valueDescriptions.begin()->second, std::string("One")); +} + +void DbcValidationTest::failedReplacement() { + const DecodeFrame valid = ValidFrame(); + DecodeDatabase database({valid}); + const std::shared_ptr original = database.FindFrame(valid.GetKey()); + DecodeFrame invalid = valid; + invalid.canId = 0x800U; + QVERIFY_THROWS_EXCEPTION(std::invalid_argument, database = DecodeDatabase({invalid})); + QCOMPARE(database.FindFrame(valid.GetKey()).get(), original.get()); + invalid = valid; + invalid.signals[0].factor = std::numeric_limits::infinity(); + QVERIFY_THROWS_EXCEPTION(std::invalid_argument, + database = DbcDecodeBuilder().Build(ParsedDefinitions({invalid}))); + QCOMPARE(database.FindFrame(valid.GetKey()).get(), original.get()); + QCOMPARE(database.GetFrames().size(), std::size_t(1U)); + database = DecodeDatabase(); + QVERIFY(database.GetFrames().empty()); + QVERIFY(!database.FindFrame(valid.GetKey())); + QVERIFY(original); +} + +int RunValidationTests(int argc, char **argv) { + DbcValidationTest tests; + return QTest::qExec(&tests, argc, argv); +} + +#include "dbc_validation_test.moc" diff --git a/tests/main.cpp b/tests/main.cpp index f8ea11d..cdd9681 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -7,11 +7,14 @@ int RunCoverageTests(int argc, char **argv); int RunLifetimeTests(int argc, char **argv); +int RunValidationTests(int argc, char **argv); + int main(int argc, char **argv) { QCoreApplication application(argc, argv); const int parserResult = RunParserTests(argc, argv); const int decoderResult = RunDecoderTests(argc, argv); const int coverageResult = RunCoverageTests(argc, argv); const int lifetimeResult = RunLifetimeTests(argc, argv); - return parserResult | decoderResult | coverageResult | lifetimeResult; + const int validationResult = RunValidationTests(argc, argv); + return validationResult | parserResult | decoderResult | coverageResult | lifetimeResult; } diff --git a/tests/tests.pro b/tests/tests.pro index 69b0255..6fdaf4d 100644 --- a/tests/tests.pro +++ b/tests/tests.pro @@ -8,5 +8,5 @@ CONFIG -= app_bundle include(../core/core.pri) include(parser_tests.pri) include(decoder_tests.pri) -SOURCES += main.cpp dbc_coverage_test.cpp dbc_lifetime_test.cpp +SOURCES += main.cpp dbc_coverage_test.cpp dbc_lifetime_test.cpp dbc_validation_test.cpp CONFIG -= debug_and_release debug_and_release_target