diff --git a/README.md b/README.md index edda697..c907b67 100644 --- a/README.md +++ b/README.md @@ -381,6 +381,7 @@ Fields: - `rawValue` - `physicalValue` - `valid` +- `status` --- @@ -444,7 +445,7 @@ Main runtime decoder class. Responsibilities: - find a frame definition by CAN ID -- decode all signals in a frame +- decode unconditional signals and the active multiplexed branch - extract raw values - sign-extend signed values - convert raw values into physical values @@ -465,6 +466,7 @@ The current parser supports the following DBC elements: - `CM_ BO_` - `CM_ SG_` - `VAL_` signal value descriptions +- basic `M` / `mN` multiplexing --- @@ -560,15 +562,64 @@ and scientific notation. The entire numeric token must be valid; hexadecimal numbers, NaN, infinity, conversion overflow, and conversion underflow are rejected. Minimum must not exceed maximum. Zero and negative factors are allowed. -Multiplexing markers (`M`, `mN`, and combinations) are currently rejected rather -than interpreted as part of a signal name. Multiplexing support and duplicate -frame/signal detection remain separate work. +Basic multiplexing markers `M` and `mN` are supported as described below. +Overlapping signals are rejected unless their branch values differ. +Duplicate frame/signal name detection remains separate work. Invalid signal definitions stop parsing with `path:line: description`, including when the signal exceeds the declared payload length. --- +## Basic multiplexing + +```dbc +BO_ 256 Status: 3 ECU + SG_ Mode M : 0|8@1+ (1,0) [0|255] "" ECU + SG_ Temperature m1 : 8|8@1- (1,0) [-128|127] "C" ECU + SG_ Pressure m2 : 8|8@1+ (2,0) [0|510] "kPa" ECU + SG_ Counter : 16|8@1+ (1,0) [0|255] "" ECU +``` + +`SignalInfo` and `DecodeSignal` contain `multiplexing`: a `MultiplexRole` +(`Unconditional`, `Selector`, or `Branch`) and an exact `uint64_t` branch `value`. +Each frame may have one unsigned selector (`M`); a branch (`mN`) references that +selector implicitly. The selector may appear after its branches. `N` must contain +only decimal digits, fit `uint64_t`, and fit the selector's bit width. Leading +zeros are accepted. A selector without branches is allowed. Signed selectors, +missing selectors, multiple selectors, nested/combined markers, and extended +`SG_MUL_VAL_` statements are rejected with the originating `path:line`. +The dispatcher registers the unsupported statement handler through `TypeFactory`. + +Signal bits may overlap only between branches with different selector values. +This validation uses Intel or Motorola bit traversal, including mixed byte orders. +It also rejects overlapping unconditional signals. Metadata survives the runtime +and tree builders; the optional Qt adapter does not add multiplexing roles yet. + +The shared decoder extracts the selector before processing branches, compares its +**raw unsigned value**, and leaves result order equal to definition order. Factor, +offset, and `VAL_` descriptions do not influence branch selection. Unconditional +signals continue to decode. An unmapped selector value leaves all branches inactive. + +Each result has `SignalDecodeStatus status`: + +| Status | Meaning | +| --- | --- | +| `Valid` | Raw and physical values are available; `valid == true` | +| `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 bit length or missing/invalid selector in manually built runtime metadata | + +All statuses except `Valid` have `valid == false`; their numeric fields contain +no decoded data and `GetValueDescription()` returns nullptr. 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. + +--- + ## Comments Frame comment example: @@ -631,7 +682,8 @@ RawCanFrame ↓ Find frame in DecodeDatabase ↓ -For each signal: +Extract multiplexor selector first (when present) +For each unconditional or active signal: extract raw bits apply sign extension if needed convert to physical value @@ -895,7 +947,7 @@ This is still a minimal implementation. Not supported yet: -- multiplexed signals +- extended and nested multiplexing (`SG_MUL_VAL_`, `mNM`) - named `VAL_TABLE_` tables and environment-variable value descriptions - `BA_` attributes - `BA_DEF_` definitions @@ -949,7 +1001,7 @@ Recommended additions: Recommended additions: -- multiplexing support +- basic multiplexing (implemented) - attribute parsing - richer J1939 support - CSV export of decoded traces diff --git a/changelog.md b/changelog.md index 5f8cada..d4729bb 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,24 @@ existing Git tag; subsequent changes are listed as unreleased. ## Unreleased +### 2026-10-07 + +#### Added + +- Basic `M` / `mN` multiplexing metadata in parsed, tree, and runtime definitions. +- Deferred selector and overlap validation with source locations: one unsigned + selector, representable branch values, and overlaps only between exclusive branches. +- Selector-first decoding using exact raw values, preserving definition order and + unconditional signals. Explicit statuses distinguish inactive branches, missing + selector data, insufficient payloads, and malformed runtime metadata. +- Registered `SG_MUL_VAL_` rejection through TypeFactory; extended and nested + multiplexing remain unsupported. Added parser/decoder regression coverage and + documented supported syntax and consumer behavior. + +#### Changed + +- Overlapping unconditional signals are now rejected during parsing. + ### 2026-10-06 #### Added diff --git a/core/core_files.pri b/core/core_files.pri index f660d76..4d03125 100644 --- a/core/core_files.pri +++ b/core/core_files.pri @@ -7,6 +7,7 @@ SOURCES += \ $$PWD/tree_node.cpp HEADERS += \ + $$PWD/multiplexing.h \ $$PWD/dbc_database.h \ $$PWD/dbc_decode_builder.h \ $$PWD/dbc_decoder.h \ diff --git a/core/dbc_decode_builder.cpp b/core/dbc_decode_builder.cpp index d5027e2..2dc98c7 100644 --- a/core/dbc_decode_builder.cpp +++ b/core/dbc_decode_builder.cpp @@ -31,6 +31,7 @@ DecodeDatabase DbcDecodeBuilder::Build(const DbcDatabase &source) const { const SignalInfo &sourceSignal = sourceFrame.signals[signalIndex]; DecodeSignal targetSignal; + targetSignal.multiplexing = sourceSignal.multiplexing; targetSignal.name = sourceSignal.name; targetSignal.startBit = sourceSignal.startBit; targetSignal.length = sourceSignal.length; diff --git a/core/dbc_decoder.cpp b/core/dbc_decoder.cpp index b2bb4f5..2124b3c 100644 --- a/core/dbc_decoder.cpp +++ b/core/dbc_decoder.cpp @@ -39,6 +39,19 @@ DecodedFrameValue DbcDecoder::Decode(const DecodeDatabase &database, result.definition = definition; result.valid = true; + const DecodeSignal *selector = nullptr; + std::size_t selectorCount = 0U; + for (const DecodeSignal &signal : definition->signals) { + if (signal.multiplexing.role == MultiplexRole::Selector) { + selector = &signal; + ++selectorCount; + } + } + const bool selectorDefinitionValid = selectorCount == 1U && + selector->valueType == ValueType::Unsigned && selector->length > 0U && selector->length <= 64U; + std::uint64_t selectorValue = 0U; + const bool selectorAvailable = selectorDefinitionValid && ExtractUnsigned(frame.data, *selector, selectorValue); + for (std::size_t signalIndex = 0U; signalIndex < definition->signals.size(); ++signalIndex) { const DecodeSignal &signal = definition->signals[signalIndex]; DecodedSignalValue decoded; @@ -46,8 +59,29 @@ DecodedFrameValue DbcDecoder::Decode(const DecodeDatabase &database, decoded.definition = &signal; + if (signal.length == 0U || signal.length > 64U || + (signal.multiplexing.role != MultiplexRole::Unconditional && !selectorDefinitionValid)) { + result.signals.push_back(decoded); + continue; + } + if (signal.multiplexing.role == MultiplexRole::Branch) { + if (selector->length < 64U && signal.multiplexing.value >= (std::uint64_t(1U) << selector->length)) { + result.signals.push_back(decoded); + continue; + } + if (!selectorAvailable) { + decoded.status = SignalDecodeStatus::SelectorUnavailable; + result.signals.push_back(decoded); + continue; + } + if (signal.multiplexing.value != selectorValue) { + decoded.status = SignalDecodeStatus::Inactive; + result.signals.push_back(decoded); + continue; + } + } if (!ExtractUnsigned(frame.data, signal, unsignedValue)) { - decoded.valid = false; + decoded.status = SignalDecodeStatus::InsufficientPayload; result.signals.push_back(decoded); continue; } @@ -58,12 +92,13 @@ DecodedFrameValue DbcDecoder::Decode(const DecodeDatabase &database, decoded.rawValue = unsignedValue; double numericValue; - if (const auto *signedValue = std::get_if(&decoded.rawValue)) + if (const std::int64_t *signedValue = std::get_if(&decoded.rawValue)) numericValue = static_cast(*signedValue); else numericValue = static_cast(std::get(decoded.rawValue)); decoded.physicalValue = numericValue * signal.factor + signal.offset; decoded.valid = true; + decoded.status = SignalDecodeStatus::Valid; result.signals.push_back(decoded); } diff --git a/core/dbc_decoder.h b/core/dbc_decoder.h index d2c699d..b6c62ab 100644 --- a/core/dbc_decoder.h +++ b/core/dbc_decoder.h @@ -31,6 +31,14 @@ struct RawCanFrame { } }; +enum class SignalDecodeStatus { + InvalidDefinition, + InsufficientPayload, + SelectorUnavailable, + Inactive, + Valid +}; + /** * @brief One decoded signal value. */ @@ -39,6 +47,7 @@ struct DecodedSignalValue { RawSignalValue rawValue; /**< Exact signed or unsigned raw value. */ double physicalValue; /**< Converted physical value. */ bool valid; /**< true if decoding succeeded. */ + SignalDecodeStatus status = SignalDecodeStatus::InvalidDefinition; /** Lookup using the raw integer; nullptr for invalid or unmapped values. */ const std::string *GetValueDescription() const { diff --git a/core/dbc_parser.cpp b/core/dbc_parser.cpp index aacdb9c..b0ea5a0 100644 --- a/core/dbc_parser.cpp +++ b/core/dbc_parser.cpp @@ -64,6 +64,7 @@ DbcDatabase DbcParser::ParseFile(const std::string &filePath) const { } context.ResolveValueDescriptions(filePath); + context.ValidateMultiplexing(filePath); return database; } diff --git a/core/dbc_statement_handler.cpp b/core/dbc_statement_handler.cpp index 1ff22d0..d69690d 100644 --- a/core/dbc_statement_handler.cpp +++ b/core/dbc_statement_handler.cpp @@ -169,9 +169,24 @@ SignalInfo DbcSignalHandler::ParseSignalLine(const std::string &line) { throw std::runtime_error("Malformed or unsupported SG_ signal definition."); SignalInfo signal; - signal.name = TrimText(fields[1].str()); - if (signal.name.empty() || signal.name.find_first_of(" \t\r\n") != std::string::npos) - throw std::runtime_error("Invalid signal name or unsupported multiplexing syntax."); + std::istringstream nameFields(fields[1].str()); + std::string marker; + std::string extra; + nameFields >> signal.name >> marker; + if (signal.name.empty() || (nameFields >> extra)) + throw std::runtime_error("Invalid signal name or multiplexing marker."); + if (marker == "M") + signal.multiplexing.role = MultiplexRole::Selector; + else if (!marker.empty()) { + if (marker[0] != 'm') + throw std::runtime_error("Unsupported multiplexing marker."); + const std::string value = marker.substr(1U); + const std::from_chars_result parsed = std::from_chars( + value.data(), value.data() + value.size(), signal.multiplexing.value); + if (value.empty() || parsed.ec != std::errc() || parsed.ptr != value.data() + value.size()) + throw std::runtime_error("Invalid or unsupported multiplexing marker."); + signal.multiplexing.role = MultiplexRole::Branch; + } signal.startBit = ParseDecimal(fields[2].str(), std::numeric_limits::max(), "signal start bit"); @@ -281,6 +296,7 @@ namespace { void DbcFrameHandler::Handle(const std::string &line, DbcParseContext &context) const { context.database.frames.push_back(ParseFrameLine(line)); context.currentFrame = &context.database.frames.back(); + context.signalLines.emplace_back(); } void DbcSignalHandler::Handle(const std::string &line, DbcParseContext &context) const { @@ -298,6 +314,7 @@ void DbcSignalHandler::Handle(const std::string &line, DbcParseContext &context) bit = (bit % 8U == 0U) ? bit + 15U : bit - 1U; } context.currentFrame->signals.push_back(std::move(signal)); + context.signalLines.back().push_back(context.lineNumber); } void DbcCommentHandler::Handle(const std::string &line, DbcParseContext &context) const { @@ -306,6 +323,7 @@ void DbcCommentHandler::Handle(const std::string &line, DbcParseContext &context } DbcStatementDispatcher::DbcStatementDispatcher() { + RegisterType("SG_MUL_VAL_"); RegisterType("BO_"); RegisterType("SG_"); RegisterType("CM_"); @@ -408,3 +426,70 @@ void DbcParseContext::ResolveValueDescriptions(const std::string &filePath) { } } } + +void DbcUnsupportedMultiplexingHandler::Handle(const std::string &, DbcParseContext &) const { + throw std::runtime_error("Extended and nested multiplexing are unsupported."); +} + +namespace { + std::vector SignalBits(const SignalInfo &signal) { + std::vector bits; + std::uint64_t bit = signal.startBit; + for (std::uint32_t index = 0U; index < signal.length; ++index) { + bits.push_back(bit); + if (signal.isLittleEndian) + ++bit; + else + bit = bit % 8U == 0U ? bit + 15U : bit - 1U; + } + return bits; + } + + bool SignalsOverlap(const SignalInfo &first, const SignalInfo &second) { + const std::vector firstBits = SignalBits(first); + const std::vector secondBits = SignalBits(second); + for (const std::uint64_t firstBit : firstBits) { + for (const std::uint64_t secondBit : secondBits) { + if (firstBit == secondBit) + return true; + } + } + return false; + } +} + +void DbcParseContext::ValidateMultiplexing(const std::string &filePath) const { + for (std::size_t frameIndex = 0U; frameIndex < database.frames.size(); ++frameIndex) { + const FrameInfo &frame = database.frames[frameIndex]; + const SignalInfo *selector = nullptr; + for (std::size_t index = 0U; index < frame.signals.size(); ++index) { + const SignalInfo &signal = frame.signals[index]; + if (signal.multiplexing.role != MultiplexRole::Selector) + continue; + if (selector != nullptr || signal.isSigned) + throw std::runtime_error(filePath + ":" + std::to_string(signalLines[frameIndex][index]) + + ": Multiplexing requires one unsigned selector per frame."); + selector = &signal; + } + for (std::size_t index = 0U; index < frame.signals.size(); ++index) { + const SignalInfo &signal = frame.signals[index]; + std::string error; + if (signal.multiplexing.role == MultiplexRole::Branch) { + if (selector == nullptr) + error = "Multiplexed branch has no selector."; + else if (selector->length < 64U && signal.multiplexing.value >= (std::uint64_t(1U) << selector->length)) + error = "Branch value exceeds selector raw range."; + } + for (std::size_t previous = 0U; previous < index && error.empty(); ++previous) { + const SignalInfo &other = frame.signals[previous]; + const bool exclusive = signal.multiplexing.role == MultiplexRole::Branch && + other.multiplexing.role == MultiplexRole::Branch && + signal.multiplexing.value != other.multiplexing.value; + if (!exclusive && SignalsOverlap(signal, other)) + error = "Signals overlap without mutually exclusive multiplexing conditions."; + } + if (!error.empty()) + throw std::runtime_error(filePath + ":" + std::to_string(signalLines[frameIndex][index]) + ": " + error); + } + } +} diff --git a/core/dbc_statement_handler.h b/core/dbc_statement_handler.h index 0a63f1b..c0512fa 100644 --- a/core/dbc_statement_handler.h +++ b/core/dbc_statement_handler.h @@ -22,6 +22,9 @@ struct DbcParseContext { std::size_t lineNumber = 0U; std::vector pendingValues = {}; + std::vector> signalLines = {}; + void ValidateMultiplexing(const std::string &filePath) const; + void ResolveValueDescriptions(const std::string &filePath); }; @@ -60,6 +63,11 @@ public: void Handle(const std::string &line, DbcParseContext &context) const override; }; +class DbcUnsupportedMultiplexingHandler : public DbcStatementHandler { +public: + void Handle(const std::string &line, DbcParseContext &context) const override; +}; + /** Exact-key dispatch to handlers created once per registration via TypeFactory. */ class DbcStatementDispatcher { public: diff --git a/core/decode_database.h b/core/decode_database.h index 5ea4d1c..69c60de 100644 --- a/core/decode_database.h +++ b/core/decode_database.h @@ -13,6 +13,7 @@ #define DECODE_DATABASE_H #include "value_descriptions.h" +#include "multiplexing.h" #include #include #include @@ -38,6 +39,7 @@ enum class ValueType { * @brief Runtime-ready signal definition. */ struct DecodeSignal { + Multiplexing multiplexing; std::string name; /**< Signal name. */ std::uint32_t startBit; /**< DBC start bit. */ std::uint32_t length; /**< Signal length in bits. */ diff --git a/core/multiplexing.h b/core/multiplexing.h new file mode 100644 index 0000000..6740070 --- /dev/null +++ b/core/multiplexing.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +#ifndef MULTIPLEXING_H +#define MULTIPLEXING_H + +#include + +enum class MultiplexRole { + Unconditional, + Selector, + Branch +}; + +/** Basic multiplexing: one unsigned selector per frame, one raw value per branch. */ +struct Multiplexing { + MultiplexRole role = MultiplexRole::Unconditional; + std::uint64_t value = 0U; +}; + +#endif diff --git a/core/signal_info.h b/core/signal_info.h index 1e19849..5afb8c0 100644 --- a/core/signal_info.h +++ b/core/signal_info.h @@ -13,6 +13,7 @@ #define SIGNAL_INFO_H #include "value_descriptions.h" +#include "multiplexing.h" #include #include #include @@ -21,6 +22,7 @@ * @brief Describes one signal inside a DBC frame. */ struct SignalInfo { + Multiplexing multiplexing; std::string name; /**< Signal name. */ std::uint32_t startBit; /**< Start bit in DBC notation. */ std::uint32_t length; /**< Signal length in bits. */ diff --git a/tests/README.md b/tests/README.md index 2397728..799c761 100644 --- a/tests/README.md +++ b/tests/README.md @@ -72,3 +72,9 @@ Enable `CONFIG+=dbc_qt` in the root qmake invocation to build `dbc_qt` and QAbstractItemModelTester, verify navigation across columns, invalid/foreign indexes, metadata and exact VAL_ integers, and persistent-index invalidation when reloading or clearing the tree. No GUI/display server is needed. + +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_. diff --git a/tests/dbc_parser_test.cpp b/tests/dbc_parser_test.cpp index eac2d1d..04bb17b 100644 --- a/tests/dbc_parser_test.cpp +++ b/tests/dbc_parser_test.cpp @@ -63,6 +63,8 @@ private: } } private Q_SLOTS: + void multiplexing(); + void multiplexingBoundaries(); void validFrames(); void invalidFrames(); void integration(); @@ -153,6 +155,127 @@ void DbcParserTest::integration() { } } +void DbcParserTest::multiplexing() { + Fixture fixture; + const std::string selector = "SG_ Mode M : 0|8@1+ (10,3) [0|255] \"\" ECU\n"; + const std::string first = "SG_ First m1 : 8|8@1- (2,0) [-128|127] \"V\" ECU\n"; + const std::string second = "SG_ Second m2 : 15|8@0+ (1,0) [0|255] \"\" ECU\n"; + const std::string common = "SG_ Common : 16|8@1+ (1,0) [0|255] \"\" ECU\n"; + fixture.Write("BO_ 1 Frame: 3 ECU\n" + first + second + common + selector + + "VAL_ 1 First -1 \"Negative\";\n"); + const DbcDatabase parsed = DbcParser().ParseFile(fixture.path.string()); + const DecodeDatabase runtime = DbcDecodeBuilder().Build(parsed); + CHECK(parsed.frames[0].signals[3].multiplexing.role == MultiplexRole::Selector, + "Selector declared after branches"); + const std::unique_ptr tree = DbcTreeBuilder().Build(parsed); + CHECK(tree->GetChild(0U)->GetChild(0U)->GetSignal()->multiplexing.value == 1U, + "Tree preserves multiplexing"); + RawCanFrame raw; + raw.canId = 1U; + raw.data = {1U, 255U, 7U}; + DecodedFrameValue result = DbcDecoder().Decode(runtime, raw); + CHECK(result.signals[0].valid && std::get(result.signals[0].rawValue) == -1 && + result.signals[0].physicalValue == -2 && *result.signals[0].GetValueDescription() == "Negative", + "Active signed branch and VAL_ use exact raw selector"); + CHECK(!result.signals[1].valid && result.signals[1].status == SignalDecodeStatus::Inactive && + result.signals[1].GetValueDescription() == nullptr && result.signals[2].valid, + "Inactive branch and unconditional signal"); + raw.data[0] = 2U; + result = DbcDecoder().Decode(runtime, raw); + CHECK(result.signals[0].status == SignalDecodeStatus::Inactive && result.signals[1].valid && + std::get(result.signals[1].rawValue) == 255U, + "Motorola branch becomes active"); + raw.data = {3U}; + result = DbcDecoder().Decode(runtime, raw); + CHECK(result.signals[0].status == SignalDecodeStatus::Inactive && + result.signals[1].status == SignalDecodeStatus::Inactive && + result.signals[2].status == SignalDecodeStatus::InsufficientPayload, + "Unmapped selector leaves branches inactive even with missing bytes"); + raw.data = {1U}; + result = DbcDecoder().Decode(runtime, raw); + CHECK(result.signals[0].status == SignalDecodeStatus::InsufficientPayload, + "Active branch requires payload"); + raw.data.clear(); + result = DbcDecoder().Decode(runtime, raw); + CHECK(result.signals[0].status == SignalDecodeStatus::SelectorUnavailable && + result.signals[3].status == SignalDecodeStatus::InsufficientPayload, + "Unavailable selector does not imply inactive branch"); + + const std::vector invalid = { + first, + selector + "SG_ Other M : 16|8@1+ (1,0) [0|255] \"\" ECU\n", + "SG_ Mode M : 0|8@1- (1,0) [-128|127] \"\" ECU\n", + selector + "SG_ TooLarge m256 : 8|8@1+ (1,0) [0|255] \"\" ECU\n", + selector + first + "SG_ Same m1 : 15|8@0+ (1,0) [0|255] \"\" ECU\n", + selector + "SG_ Collision m1 : 0|8@1+ (1,0) [0|255] \"\" ECU\n", + selector + first + "SG_ Always : 8|8@1+ (1,0) [0|255] \"\" ECU\n", + "SG_MUL_VAL_ 1 First Mode 1-2;\n" + }; + for (const std::string &signals : invalid) { + fixture.Write("BO_ 1 Frame: 3 ECU\n" + signals); + bool rejected = false; + try { + DbcParser().ParseFile(fixture.path.string()); + } catch (const std::runtime_error &error) { + rejected = std::string(error.what()).find(fixture.path.string() + ":") == 0U; + } + CHECK(rejected, "Invalid multiplexing rejected with location"); + } + for (const std::string marker : {"m", "m-1", "m+1", "m1M", "m18446744073709551616", "M m1"}) + RejectSignal(fixture, "SG_ Bad " + marker + " : 8|8@1+ (1,0) [0|255] \"\" ECU"); +} + +void DbcParserTest::multiplexingBoundaries() { + Fixture fixture; + fixture.Write("BO_ 1 Frame: 9 ECU\n" + "SG_ Branch m18446744073709551615 : 64|8@1+ (1,0) [0|255] \"\" ECU\n" + "SG_ Mode M : 7|64@0+ (0,5) [0|1] \"\" ECU\n"); + const DbcDatabase parsed = DbcParser().ParseFile(fixture.path.string()); + DecodeDatabase runtime = DbcDecodeBuilder().Build(parsed); + CHECK(runtime.frames[0].signals[0].multiplexing.value == std::numeric_limits::max(), + "Exact uint64 branch metadata"); + RawCanFrame raw; + raw.canId = 1U; + raw.data = std::vector(9U, 255U); + DecodedFrameValue result = DbcDecoder().Decode(runtime, raw); + CHECK(result.signals[0].valid && result.signals[1].valid && + std::get(result.signals[1].rawValue) == std::numeric_limits::max(), + "Motorola uint64 selector compares raw value before zero scaling"); + raw.data[7] = 254U; + result = DbcDecoder().Decode(runtime, raw); + CHECK(result.signals[0].status == SignalDecodeStatus::Inactive, + "Adjacent large integers remain distinct"); + runtime.frames[0].signals[1].length = 0U; + 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"); + runtime.frames[0].signals.pop_back(); + result = DbcDecoder().Decode(runtime, raw); + CHECK(result.signals[0].status == SignalDecodeStatus::InvalidDefinition, + "Missing runtime selector"); + 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"); + bool rejected = false; + try { + DbcParser().ParseFile(fixture.path.string()); + } catch (const std::runtime_error &error) { + rejected = std::string(error.what()).find(fixture.path.string() + ":3: ") == 0U; + } + CHECK(rejected, "Deferred validation reports branch source line"); + fixture.Write("BO_ 1 Frame: 2 ECU\n" + "SG_ A : 8|8@1+ (1,0) [0|255] \"\" ECU\n" + "SG_ B : 15|8@0+ (1,0) [0|255] \"\" ECU\n"); + rejected = false; + try { + DbcParser().ParseFile(fixture.path.string()); + } catch (const std::runtime_error &) { + rejected = true; + } + CHECK(rejected, "Ordinary overlapping signals rejected"); +} + void DbcParserTest::invalidSignals() { Fixture fixture; const std::string ordinary = SignalLine("0", "8", "1+", "1", "0", "0", "255"); @@ -172,7 +295,7 @@ void DbcParserTest::invalidSignals() { RejectSignal(fixture, SignalLine("0", "8", "1+", "1", "0", "0", value)); } RejectSignal(fixture, SignalLine("0", "8", "1+", "1", "0", "2", "1")); - for (const std::string name: {"", "Two Names", "Value M", "Value m1", "Value m1M"}) { + for (const std::string name: {"", "Two Names", "Value m1M"}) { std::string line = ordinary; line.replace(4U, 5U, name); RejectSignal(fixture, line);