This commit is contained in:
deeaitch committed 2026-10-23 20:28:55 -04:00
1 parent a702cffd93
commit 8d06165979
18 files changed
+611 -215

No files matched your search

+52 -29
View File
@@ -370,9 +370,9 @@ index or publishing the snapshot, and throws
- 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.
- Multiplexing requires unsigned selectors when branches are present, with
branch ranges inside the parent's raw range. Signals may overlap only when
their ancestor conditions prove mutual exclusion, 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.
@@ -609,7 +609,7 @@ The current parser supports the following DBC elements:
- `CM_ BO_`
- `CM_ SG_`
- `VAL_` signal value descriptions
- basic `M` / `mN` multiplexing
- basic and extended multiplexing (`M`, `mN`, `mNM`, `SG_MUL_VAL_`)
### Structured diagnostics
@@ -639,7 +639,7 @@ if (!result.HasErrors()) {
| `Error` / `InvalidValueDescription` | Invalid `VAL_` syntax, duplicate value, or unresolved reference |
| `Error` / `InvalidMultiplexing` | Selector, branch range, or signal overlap validation failed |
| `Warning` / `UnsupportedStatement` | Unregistered statement skipped; decoding or metadata may be incomplete |
| `Error` / `UnsupportedStatement` | Extended multiplexing (`SG_MUL_VAL_`) rejected |
| `Error` / `InvalidMultiplexing` | Invalid `SG_MUL_VAL_` syntax, references, ranges, or cycles |
Parsing stops at the first error and returns an empty database, together with
any preceding warnings and the error. Deferred comment, `VAL_`, and multiplexing errors
@@ -809,8 +809,8 @@ numbers, NaN, infinity, conversion overflow, and conversion underflow (including
subnormal values) are
rejected. Minimum must not exceed maximum. Zero and negative factors are allowed.
Basic multiplexing markers `M` and `mN` are supported as described below.
Overlapping signals are rejected unless their branch values differ.
Basic and extended multiplexing markers are supported as described below.
Overlapping signals are rejected unless their selector conditions prove mutual exclusion.
Duplicate frame keys and signal names within a frame are rejected.
Invalid signal definitions stop parsing with `path:line: description`, including
@@ -818,7 +818,7 @@ when the signal exceeds the declared payload length.
---
## Basic multiplexing
## Multiplexing
```dbc
BO_ 256 Status: 3 ECU
@@ -829,24 +829,49 @@ BO_ 256 Status: 3 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`.
(`Unconditional`, `Selector`, `Branch`, or `BranchSelector`), an exact `uint64_t`
marker `value`, and optional parent `selector` name with inclusive `ranges`.
`M` declares an unsigned selector; `mN` declares a branch and `mNM` declares a
branch that is itself a selector. Without explicit ranges, `mN`/`mNM` use the
unique unconditional `M` selector and the marker's raw value. If several root
selectors exist, branches require explicit references.
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
Extended conditions use `SG_MUL_VAL_ <message-id> <signal> <selector> <low>-<high>, ...;`:
```dbc
BO_ 1 Nested: 3 ECU
SG_ Root M : 0|8@1+ (1,0) [0|255] "" ECU
SG_ Child m1M : 8|8@1+ (1,0) [0|255] "" ECU
SG_ Value m0 : 16|8@1+ (1,0) [0|255] "" ECU
SG_MUL_VAL_ 1 Child Root 1-2, 4-4;
SG_MUL_VAL_ 1 Value Child 3-5;
```
Here `Value` is active when `Root` is 1, 2, or 4 **and** `Child` is 3 through 5.
Explicit ranges override the marker value. Repeated statements for the same
signal/selector merge their range alternatives. Each signal has at most one
parent selector; conflicting parent references are rejected. Multiple independent
root selectors are supported. Statements and selectors may precede or follow
their targets. Decimal values retain the full `uint64_t` range and must fit the
parent's bit width. Ranges are stored without expanding individual values.
Malformed ranges, unresolved references, signed selectors, ambiguous implicit
selectors, and dependency cycles produce `InvalidMultiplexing` diagnostics.
Statement errors retain the `SG_MUL_VAL_` source line; graph and overlap errors
identify the affected signal declaration.
Signal bits may overlap only when the complete ancestor conditions prove the
signals mutually exclusive: at least one shared selector has disjoint ranges.
This includes nested siblings using the same payload bits and independent
selectors, whose branches may be active simultaneously. Validation uses Intel or
Motorola bit traversal, including mixed byte orders. Metadata survives 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.
The decoder checks ancestors from root to leaf using **raw unsigned values**.
An inactive ancestor leaves descendants inactive even if their selector bytes
are absent. Missing bytes on an otherwise active selector path produce
`SelectorUnavailable`. Selective decoding reads required ancestors internally;
result order remains selection or definition order. Factor, offset, and `VAL_`
descriptions do not influence branch selection.
Each result has `SignalDecodeStatus status`:
@@ -863,9 +888,7 @@ 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.
manually receive the same selector graph and non-overlap validation as parsed files.
---
@@ -1154,7 +1177,7 @@ lines:
- `SG_` → `DbcSignalHandler`
- `CM_` → `DbcCommentHandler`
- `VAL_` → `DbcValueDescriptionHandler`
- `SG_MUL_VAL_` → `DbcUnsupportedMultiplexingHandler`
- `SG_MUL_VAL_` → `DbcMultiplexingHandler`
- `VERSION`, `NS_`, `BS_`, `BU_` → `DbcHeaderHandler` (also accepts attached colons)
Grammar implementations live in `core/dbc_statement_handler.cpp`; their interface,
@@ -1250,7 +1273,7 @@ This is still a minimal implementation.
Not supported yet:
- extended and nested multiplexing (`SG_MUL_VAL_`, `mNM`)
- multiple parent selectors for one signal
- named `VAL_TABLE_` tables and environment-variable value descriptions
- attribute relation forms (`BA_REL_`, `BA_DEF_REL_`)
- `SIG_VALTYPE_` floating-point signal types
@@ -1307,7 +1330,7 @@ Recommended additions:
Recommended additions:
- basic multiplexing (implemented)
- basic, extended, and nested multiplexing (implemented)
- attribute parsing (implemented for definitions, defaults and values)
- richer J1939 support
- CSV export of decoded traces
+15
View File
@@ -8,6 +8,21 @@ existing Git tag; subsequent changes are listed as unreleased.
### 2026-10-23
#### Added
- Extended `SG_MUL_VAL_` range conditions and nested `mNM` selectors, dispatched
through TypeFactory with deferred target resolution and source diagnostics.
- Shared selector graph validation for parsed and runtime metadata, including
ancestor-aware overlap checks, range bounds, reference validation, and cycles.
Preserve selector names and ranges through runtime and tree builders.
- Decode nested paths from raw selectors, including selective decoding and
inactive paths with missing child bytes. Support independent root selectors;
each signal has one parent, with repeated statements merging range alternatives.
- Add parser, decoder, metadata, and malformed runtime regression coverage.
Verify `qmake6 ../dbc.pro`, `make -j4`, and `make check`: 2,398 QtTest checks
passed with no failures; one existing C-locale check skipped because a named
comma-decimal locale is unavailable. Both qmake and standalone tree tests pass.
#### Testing
- Add local Release and ASan/UBSan commands in `tests/run_core_ci.sh` for the
+1
View File
@@ -15,6 +15,7 @@ HEADERS += \
$$PWD/definition_key.h \
$$PWD/dbc_diagnostic.h \
$$PWD/multiplexing.h \
$$PWD/multiplex_graph.h \
$$PWD/dbc_database.h \
$$PWD/dbc_decode_builder.h \
$$PWD/dbc_decoder.h \
+29 -36
View File
@@ -81,12 +81,14 @@ DecodedFrameValue DbcDecoder::DecodeImpl(const DecodeDatabase &database,
? FrameDecodeStatus::InsufficientPayload
: FrameDecodeStatus::Valid;
const SelectorState selector = ReadSelector(*definition, frame);
const dbc_detail::MultiplexGraph &graph = database.GetMultiplexGraph(result.key);
const std::size_t count = selection != nullptr ? selected.size() : definition->signals.size();
result.signals.reserve(count);
for (std::size_t index = 0U; index < count; ++index) {
const DecodeSignal &signal = selection != nullptr ? *selected[index] : definition->signals[index];
DecodedSignalValue decoded = DecodeOneSignal(signal, definition->dlc, frame, selector);
const std::size_t signalIndex = static_cast<std::size_t>(&signal - definition->signals.data());
DecodedSignalValue decoded = DecodeOneSignal(signal, definition->dlc, frame,
ActivationStatus(signalIndex, *definition, frame, graph));
if (!decoded.valid && decoded.status != SignalDecodeStatus::Inactive &&
result.status == FrameDecodeStatus::Valid)
result.status = FrameDecodeStatus::SignalErrors;
@@ -103,56 +105,47 @@ bool DbcDecoder::IsValidDefinition(const DecodeSignal &signal, std::uint8_t dlc)
(signal.valueType != ValueType::Unsigned && signal.valueType != ValueType::Signed) ||
(signal.multiplexing.role != MultiplexRole::Unconditional &&
signal.multiplexing.role != MultiplexRole::Selector &&
signal.multiplexing.role != MultiplexRole::Branch))
signal.multiplexing.role != MultiplexRole::Branch &&
signal.multiplexing.role != MultiplexRole::BranchSelector))
return false;
return IsValidSignalLayout(signal.startBit, signal.length,
signal.byteOrder == ByteOrder::Intel, dlc);
}
DbcDecoder::SelectorState DbcDecoder::ReadSelector(const DecodeFrame &definition,
const RawCanFrame &frame) {
SelectorState selector;
std::size_t count = 0U;
for (const DecodeSignal &signal: definition.signals) {
if (signal.multiplexing.role == MultiplexRole::Selector) {
selector.definition = &signal;
++count;
}
SignalDecodeStatus DbcDecoder::ActivationStatus(std::size_t index,
const DecodeFrame &definition,
const RawCanFrame &frame,
const dbc_detail::MultiplexGraph &graph) {
// Inspect ancestors from the root so inactive paths never require child bytes.
std::vector<std::size_t> path;
while (graph.parents[index] != definition.signals.size()) {
path.push_back(index);
index = graph.parents[index];
}
if (count != 1U)
return selector;
const DecodeSignal &signal = *selector.definition;
selector.valid = signal.valueType == ValueType::Unsigned &&
IsValidDefinition(signal, definition.dlc);
selector.available = selector.valid && ExtractUnsigned(frame.data, signal, selector.value);
return selector;
for (std::vector<std::size_t>::const_reverse_iterator it = path.rbegin(); it != path.rend(); ++it) {
const DecodeSignal &selector = definition.signals[graph.parents[*it]];
std::uint64_t value = 0U;
if (!ExtractUnsigned(frame.data, selector, value))
return SignalDecodeStatus::SelectorUnavailable;
if (!graph.Matches(*it, value))
return SignalDecodeStatus::Inactive;
}
return SignalDecodeStatus::Valid;
}
DecodedSignalValue DbcDecoder::DecodeOneSignal(const DecodeSignal &signal,
std::uint8_t dlc,
const RawCanFrame &frame,
const SelectorState &selector) {
SignalDecodeStatus activation) {
DecodedSignalValue decoded;
std::uint64_t unsignedValue = 0U;
if (!IsValidDefinition(signal, dlc) ||
(signal.multiplexing.role != MultiplexRole::Unconditional && !selector.valid)) {
if (!IsValidDefinition(signal, dlc))
return decoded;
if (activation != SignalDecodeStatus::Valid) {
decoded.status = activation;
return decoded;
}
if (signal.multiplexing.role == MultiplexRole::Branch) {
if (selector.definition->length < 64U && signal.multiplexing.value >= (std::uint64_t(1U) << selector.definition->length)) {
return decoded;
}
if (!selector.available) {
decoded.status = SignalDecodeStatus::SelectorUnavailable;
return decoded;
}
if (signal.multiplexing.value != selector.value) {
decoded.status = SignalDecodeStatus::Inactive;
return decoded;
}
}
if (!ExtractUnsigned(frame.data, signal, unsignedValue)) {
decoded.status = SignalDecodeStatus::InsufficientPayload;
+6 -9
View File
@@ -17,6 +17,7 @@
#include <vector>
#include "decode_database.h"
#include "multiplex_graph.h"
/**
* @brief Raw CAN frame used for runtime or trace decoding.
@@ -128,18 +129,14 @@ private:
static bool IsValidDefinition(const DecodeSignal &signal, std::uint8_t dlc);
struct SelectorState {
const DecodeSignal *definition = nullptr;
bool valid = false;
bool available = false;
std::uint64_t value = 0U;
};
static SelectorState ReadSelector(const DecodeFrame &definition, const RawCanFrame &frame);
static SignalDecodeStatus ActivationStatus(std::size_t index,
const DecodeFrame &definition,
const RawCanFrame &frame,
const dbc_detail::MultiplexGraph &graph);
static DecodedSignalValue DecodeOneSignal(const DecodeSignal &signal,
std::uint8_t dlc,
const RawCanFrame &frame,
const SelectorState &selector);
SignalDecodeStatus activation);
static bool ExtractUnsigned(const std::vector<std::uint8_t> &data,
const DecodeSignal &signal,
+2 -1
View File
@@ -80,7 +80,7 @@ namespace {
DbcDiagnosticCode StatementErrorCode(const std::string &keyword) {
if (keyword == "SG_MUL_VAL_")
return DbcDiagnosticCode::UnsupportedStatement;
return DbcDiagnosticCode::InvalidMultiplexing;
if (keyword == "VAL_")
return DbcDiagnosticCode::InvalidValueDescription;
return DbcDiagnosticCode::InvalidStatement;
@@ -173,6 +173,7 @@ DbcParseResult DbcParser::ParseFileWithDiagnostics(const std::string &filePath)
try {
context.ResolveComments(filePath);
context.ResolveValueDescriptions(filePath);
context.ResolveMultiplexing(filePath);
context.ValidateMultiplexing(filePath);
context.ResolveAttributes(filePath, result.diagnostics);
} catch (const DbcDiagnosticError &error) {
+2 -2
View File
@@ -29,7 +29,7 @@ struct DbcParserOptions {
* - SG_
* - CM_ BO_
* - CM_ SG_
* - basic M / mN multiplexing
* - basic and extended M / mN / mNM / SG_MUL_VAL_ multiplexing
* - VAL_ signal value descriptions
* - BA_DEF_, BA_DEF_DEF_, BA_ typed attributes, frame format and cycle time
*
@@ -37,7 +37,7 @@ struct DbcParserOptions {
* Attribute statements support multiline fields and quoted strings, ending at
* a semicolon outside quoted text. Diagnostics use the first physical line.
*
* Reports unsupported statements as warnings, except SG_MUL_VAL_ which is
* Reports unsupported statements as warnings. Invalid multiplexing is
* an error. VERSION, NS_, BS_, BU_ and NS_ symbol declarations stay quiet.
*/
class DbcParser {
+82 -56
View File
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: MIT
#include "dbc_statement_handler.h"
#include "j1939.h"
#include "text_utils.h"
#include "multiplex_graph.h"
#include "signal_layout.h"
#include "text_utils.h"
#include <charconv>
#include <cmath>
#include <limits>
@@ -59,12 +60,13 @@ namespace {
else if (!marker.empty()) {
if (marker[0] != 'm')
throw std::runtime_error("Unsupported multiplexing marker.");
const std::string value = marker.substr(1U);
const bool nested = marker.back() == 'M';
const std::string value = marker.substr(1U, marker.size() - (nested ? 2U : 1U));
const std::from_chars_result parsed =
std::from_chars(value.data(), value.data() + value.size(), result.value);
if (value.empty() || parsed.ec != std::errc() || parsed.ptr != value.data() + value.size())
throw std::runtime_error("Invalid or unsupported multiplexing marker.");
result.role = MultiplexRole::Branch;
result.role = nested ? MultiplexRole::BranchSelector : MultiplexRole::Branch;
}
return result;
@@ -100,31 +102,31 @@ namespace {
return description;
}
FrameInfo *FindUniqueFrame(DbcDatabase &database, std::uint32_t canId, bool isExtended) {
FrameInfo *FindUniqueFrame(DbcDatabase &database, std::uint32_t canId, bool isExtended, const std::string &statement = "VAL_") {
FrameInfo *frame = nullptr;
for (FrameInfo &candidate : database.frames) {
if (candidate.canId == canId && candidate.isExtended == isExtended) {
if (frame)
throw std::runtime_error("Ambiguous VAL_ frame reference.");
throw std::runtime_error("Ambiguous " + statement + " frame reference.");
frame = &candidate;
}
}
if (!frame)
throw std::runtime_error("Unresolved VAL_ frame reference.");
throw std::runtime_error("Unresolved " + statement + " frame reference.");
return frame;
}
SignalInfo *FindUniqueSignal(FrameInfo &frame, const std::string &signalName) {
SignalInfo *FindUniqueSignal(FrameInfo &frame, const std::string &signalName, const std::string &statement = "VAL_") {
SignalInfo *signal = nullptr;
for (SignalInfo &candidate : frame.signals) {
if (candidate.name == signalName) {
if (signal)
throw std::runtime_error("Ambiguous VAL_ signal reference: " + signalName);
throw std::runtime_error("Ambiguous " + statement + " signal reference: " + signalName);
signal = &candidate;
}
}
if (!signal)
throw std::runtime_error("Unresolved VAL_ signal reference: " + signalName);
throw std::runtime_error("Unresolved " + statement + " signal reference: " + signalName);
return signal;
}
@@ -375,7 +377,7 @@ DbcStatementDispatcher::DbcStatementDispatcher() {
RegisterType<DbcHeaderHandler>("BS_:");
RegisterType<DbcHeaderHandler>("BU_");
RegisterType<DbcHeaderHandler>("BU_:");
RegisterType<DbcUnsupportedMultiplexingHandler>("SG_MUL_VAL_");
RegisterType<DbcMultiplexingHandler>("SG_MUL_VAL_");
RegisterType<DbcFrameHandler>("BO_");
RegisterType<DbcSignalHandler>("SG_");
RegisterType<DbcCommentHandler>("CM_");
@@ -448,8 +450,63 @@ 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.");
void DbcMultiplexingHandler::Handle(const std::string &line, DbcParseContext &context) const {
static const std::regex syntax(R"(^SG_MUL_VAL_[ \t]+([0-9]+)[ \t]+([^ \t;]+)[ \t]+([^ \t;]+)[ \t]+(.+);[ \t]*$)");
static const std::regex rangeSyntax(R"(^[ \t]*([0-9]+)[ \t]*-[ \t]*([0-9]+)[ \t]*$)");
std::smatch fields;
if (!std::regex_match(line, fields, syntax))
throw std::runtime_error("Malformed SG_MUL_VAL_ statement.");
PendingMultiplexing pending;
NormalizeCanId(ParseCanId(fields[1].str()), pending.canId, pending.isExtended);
pending.signalName = fields[2].str();
pending.selectorName = fields[3].str();
pending.lineNumber = context.lineNumber;
const std::string list = fields[4].str();
std::size_t begin = 0U;
for (;;) {
const std::size_t comma = list.find(',', begin);
const std::string item = list.substr(begin, comma == std::string::npos ? comma : comma - begin);
if (!std::regex_match(item, fields, rangeSyntax))
throw std::runtime_error("Malformed multiplex range.");
MultiplexRange range;
const std::string lower = fields[1].str();
const std::string upper = fields[2].str();
const std::from_chars_result low = std::from_chars(lower.data(), lower.data() + lower.size(), range.minimum);
const std::from_chars_result high = std::from_chars(upper.data(), upper.data() + upper.size(), range.maximum);
if (low.ec != std::errc() || high.ec != std::errc() || range.minimum > range.maximum)
throw std::runtime_error("Invalid multiplex range.");
pending.ranges.push_back(range);
if (comma == std::string::npos)
break;
begin = comma + 1U;
}
context.pendingMultiplexing.push_back(std::move(pending));
}
void DbcParseContext::ResolveMultiplexing(const std::string &filePath) {
for (const PendingMultiplexing &pending: pendingMultiplexing) {
try {
FrameInfo *frame = FindUniqueFrame(database, pending.canId, pending.isExtended, "SG_MUL_VAL_");
SignalInfo *signal = FindUniqueSignal(*frame, pending.signalName, "SG_MUL_VAL_");
SignalInfo *selector = FindUniqueSignal(*frame, pending.selectorName, "SG_MUL_VAL_");
if (!IsMultiplexSelector(selector->multiplexing.role) || selector->isSigned)
throw std::runtime_error("Referenced signal must be an unsigned multiplex selector.");
if (!signal->multiplexing.selector.empty() && signal->multiplexing.selector != pending.selectorName)
throw std::runtime_error("Multiple parent selectors for one signal are unsupported.");
for (const MultiplexRange &range: pending.ranges) {
if (selector->length < 64U && range.maximum >= (std::uint64_t(1U) << selector->length))
throw std::runtime_error("Branch value exceeds selector raw range.");
}
signal->multiplexing.selector = pending.selectorName;
signal->multiplexing.ranges.insert(signal->multiplexing.ranges.end(), pending.ranges.begin(), pending.ranges.end());
if (!IsMultiplexSelector(signal->multiplexing.role))
signal->multiplexing.role = MultiplexRole::Branch;
else
signal->multiplexing.role = MultiplexRole::BranchSelector;
} catch (const std::exception &error) {
throw DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing, filePath, pending.lineNumber, error.what());
}
}
}
namespace {
@@ -463,39 +520,6 @@ namespace {
return bits;
}
bool AreMutuallyExclusive(const SignalInfo &first, const SignalInfo &second) {
return first.multiplexing.role == MultiplexRole::Branch &&
second.multiplexing.role == MultiplexRole::Branch &&
first.multiplexing.value != second.multiplexing.value;
}
std::string ValidateBranch(const SignalInfo &signal, const SignalInfo *selector) {
if (signal.multiplexing.role != MultiplexRole::Branch)
return {};
if (selector == nullptr)
return "Multiplexed branch has no selector.";
if (selector->length < 64U && signal.multiplexing.value >= (std::uint64_t(1U) << selector->length))
return "Branch value exceeds selector raw range.";
return {};
}
const SignalInfo *FindSelector(const FrameInfo &frame,
const std::vector<std::size_t> &lines,
const std::string &filePath) {
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 DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing,
filePath, lines[index],
"Multiplexing requires one unsigned selector per frame.");
selector = &signal;
}
return selector;
}
bool SignalsOverlap(const SignalInfo &first, const SignalInfo &second) {
const std::vector<std::uint64_t> firstBits = SignalBits(first);
const std::vector<std::uint64_t> secondBits = SignalBits(second);
@@ -512,18 +536,20 @@ namespace {
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 = FindSelector(frame, signalLines[frameIndex], filePath);
for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
const SignalInfo &signal = frame.signals[index];
std::string error = ValidateBranch(signal, selector);
for (std::size_t previous = 0U; previous < index && error.empty(); ++previous) {
const SignalInfo &other = frame.signals[previous];
if (!AreMutuallyExclusive(signal, other) && SignalsOverlap(signal, other))
error = "Signals overlap without mutually exclusive multiplexing conditions.";
try {
const dbc_detail::MultiplexGraph graph(frame.signals);
for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
const SignalInfo &signal = frame.signals[index];
if (IsMultiplexSelector(signal.multiplexing.role) && signal.isSigned)
throw dbc_detail::MultiplexError(index, "Multiplex selectors must be unsigned.");
for (std::size_t previous = 0U; previous < index; ++previous) {
if (!graph.AreMutuallyExclusive(index, previous) && SignalsOverlap(signal, frame.signals[previous]))
throw dbc_detail::MultiplexError(index, "Signals overlap without mutually exclusive multiplexing conditions.");
}
}
if (!error.empty())
throw DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing,
filePath, signalLines[frameIndex][index], error);
} catch (const dbc_detail::MultiplexError &error) {
throw DbcDiagnosticError(DbcDiagnosticCode::InvalidMultiplexing, filePath,
signalLines[frameIndex][error.index], error.what());
}
}
}
+15 -2
View File
@@ -46,6 +46,16 @@ struct PendingAttribute {
std::size_t lineNumber; /**< One-based physical statement line. */
};
/** @brief Deferred extended multiplexing condition and source location. */
struct PendingMultiplexing {
std::uint32_t canId;
bool isExtended;
std::string signalName;
std::string selectorName;
std::vector<MultiplexRange> ranges;
std::size_t lineNumber;
};
/** Mutable state belonging to one file parse. Handlers do not retain it. */
struct DbcParseContext {
DbcDatabase &database; /**< Parsed metadata under construction. */
@@ -54,6 +64,9 @@ struct DbcParseContext {
std::size_t lineNumber = 0U; /**< Physical line of the current statement. */
std::vector<PendingValueDescriptions> pendingValues = {}; /**< Unresolved raw value descriptions. */
std::vector<PendingMultiplexing> pendingMultiplexing = {};
void ResolveMultiplexing(const std::string &filePath);
std::vector<std::vector<std::size_t>> signalLines = {}; /**< Signal definition lines grouped by frame. */
/** @brief Validate selectors, branch ranges, and signal overlap. */
void ValidateMultiplexing(const std::string &filePath) const;
@@ -114,8 +127,8 @@ public:
void Handle(const std::string &line, DbcParseContext &context) const override;
};
/** @brief Reject unsupported extended multiplexing statements. */
class DbcUnsupportedMultiplexingHandler : public DbcStatementHandler {
/** @brief Parse extended multiplexing conditions for deferred resolution. */
class DbcMultiplexingHandler : public DbcStatementHandler {
public:
void Handle(const std::string &line, DbcParseContext &context) const override;
};
+16 -20
View File
@@ -3,6 +3,7 @@
#include "decode_database.h"
#include "j1939.h"
#include "multiplex_graph.h"
#include "signal_layout.h"
#include <bitset>
#include <cmath>
@@ -21,7 +22,8 @@ namespace {
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)
signal.multiplexing.role != MultiplexRole::Branch &&
signal.multiplexing.role != MultiplexRole::BranchSelector)
throw std::invalid_argument("Invalid multiplexing role: " + signal.name);
if (!IsValidSignalLayout(signal.startBit, signal.length,
signal.byteOrder == ByteOrder::Intel, dlc))
@@ -45,13 +47,7 @@ namespace {
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) {
dbc_detail::MultiplexGraph 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));
@@ -60,33 +56,28 @@ namespace {
throw std::invalid_argument("Invalid J1939 PGN metadata: " + frame.name);
std::unordered_set<std::string> 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: " + 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;
}
if (IsMultiplexSelector(signal.multiplexing.role) && signal.valueType != ValueType::Unsigned)
throw std::invalid_argument("Multiplex selectors must be unsigned.");
}
const dbc_detail::MultiplexGraph graph(frame.signals);
std::vector<SignalBits> 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())
if (!graph.AreMutuallyExclusive(index, previous) && (bits & layouts[previous]).any())
throw std::invalid_argument("Overlapping signals: " + signal.name);
}
layouts.push_back(bits);
}
return graph;
}
}// namespace
@@ -95,8 +86,9 @@ DecodeDatabase::Storage::Storage(const std::vector<DecodeFrame> &definitions)
// Validate the complete snapshot before creating its lookup index.
std::unordered_set<FrameKey, FrameKeyHasher> keys;
keys.reserve(frames.size());
multiplexGraphs.reserve(frames.size());
for (const DecodeFrame &frame: frames) {
ValidateFrame(frame);
multiplexGraphs.push_back(ValidateFrame(frame));
if (!keys.insert(frame.GetKey()).second)
throw std::invalid_argument("Duplicate runtime frame key");
}
@@ -153,3 +145,7 @@ std::shared_ptr<const DecodeSignal> DecodeDatabase::FindSignal(const SignalKey &
}
return nullptr;
}
const dbc_detail::MultiplexGraph &DecodeDatabase::GetMultiplexGraph(const FrameKey &key) const {
return storage->multiplexGraphs[storage->frameIndexByKey.at(key)];
}
+6 -1
View File
@@ -14,6 +14,7 @@
#include "attributes.h"
#include "definition_key.h"
#include "multiplex_graph.h"
#include "multiplexing.h"
#include "value_descriptions.h"
#include <cstdint>
@@ -42,7 +43,7 @@ enum class ValueType {
* @brief Runtime-ready signal definition.
*/
struct DecodeSignal {
Multiplexing multiplexing; /**< Selector role and branch value. */
Multiplexing multiplexing; /**< Selector role, parent, and raw branch ranges. */
std::string name; /**< Signal name. */
std::uint32_t startBit = 0U; /**< DBC start bit. */
std::uint32_t length = 0U; /**< Signal length in bits. */
@@ -118,7 +119,11 @@ public:
std::shared_ptr<const DecodeSignal> FindSignal(const SignalKey &key) const;
private:
friend class DbcDecoder;
const dbc_detail::MultiplexGraph &GetMultiplexGraph(const FrameKey &key) const;
struct Storage {
std::vector<dbc_detail::MultiplexGraph> multiplexGraphs;
std::vector<DecodeFrame> frames;
std::unordered_map<FrameKey, std::size_t, FrameKeyHasher> frameIndexByKey;
+101
View File
@@ -0,0 +1,101 @@
// SPDX-License-Identifier: MIT
#ifndef MULTIPLEX_GRAPH_H
#define MULTIPLEX_GRAPH_H
#include "multiplexing.h"
#include <cstddef>
#include <stdexcept>
namespace dbc_detail {
/** Validation failure associated with a signal in declaration order. */
class MultiplexError : public std::invalid_argument {
public:
MultiplexError(std::size_t signalIndex, const std::string &message)
: std::invalid_argument(message), index(signalIndex) {}
std::size_t index;
};
/** Resolved parent links shared by parser validation and runtime decoding. */
class MultiplexGraph {
public:
template<class Signal>
explicit MultiplexGraph(const std::vector<Signal> &signals)
: parents(signals.size(), signals.size()), ranges(signals.size()) {
const std::size_t count = signals.size();
for (std::size_t index = 0U; index < count; ++index) {
const Multiplexing &mux = signals[index].multiplexing;
if (mux.role != MultiplexRole::Unconditional && mux.role != MultiplexRole::Selector &&
mux.role != MultiplexRole::Branch && mux.role != MultiplexRole::BranchSelector)
throw MultiplexError(index, "Invalid multiplexing role.");
if (mux.selector.empty() != mux.ranges.empty())
throw MultiplexError(index, "Selector name and ranges must be supplied together.");
if (mux.selector.empty() && !IsMultiplexBranch(mux.role))
continue;
std::size_t parent = count;
for (std::size_t candidate = 0U; candidate < count; ++candidate) {
const Multiplexing &other = signals[candidate].multiplexing;
const bool matches = !mux.selector.empty()
? signals[candidate].name == mux.selector
: other.role == MultiplexRole::Selector && other.selector.empty();
if (!matches)
continue;
if (parent != count)
throw MultiplexError(index, "Ambiguous multiplex selector; use SG_MUL_VAL_.");
parent = candidate;
}
if (parent == count)
throw MultiplexError(index, "Multiplexed branch has no selector.");
if (!IsMultiplexSelector(signals[parent].multiplexing.role))
throw MultiplexError(index, "Referenced signal is not a multiplex selector.");
parents[index] = parent;
ranges[index] = mux.selector.empty() ? std::vector<MultiplexRange>{{mux.value, mux.value}} : mux.ranges;
for (const MultiplexRange &range: ranges[index]) {
if (range.minimum > range.maximum)
throw MultiplexError(index, "Reversed multiplex range.");
if (signals[parent].length < 64U && range.maximum >= (std::uint64_t(1U) << signals[parent].length))
throw MultiplexError(index, "Branch value exceeds selector raw range.");
}
}
for (std::size_t index = 0U; index < count; ++index) {
std::size_t current = index;
for (std::size_t depth = 0U; current != count; ++depth) {
if (depth >= count)
throw MultiplexError(index, "Cyclic multiplex selector dependency.");
current = parents[current];
}
}
}
bool Matches(std::size_t index, std::uint64_t value) const {
for (const MultiplexRange &range: ranges[index]) {
if (range.minimum <= value && value <= range.maximum)
return true;
}
return false;
}
bool AreMutuallyExclusive(std::size_t first, std::size_t second) const {
const std::size_t count = parents.size();
for (std::size_t left = first; parents[left] != count; left = parents[left]) {
for (std::size_t right = second; parents[right] != count; right = parents[right]) {
if (parents[left] != parents[right])
continue;
bool intersects = false;
for (const MultiplexRange &a: ranges[left]) {
for (const MultiplexRange &b: ranges[right]) {
if (a.minimum <= b.maximum && b.minimum <= a.maximum)
intersects = true;
}
}
if (!intersects)
return true;
}
}
return false;
}
std::vector<std::size_t> parents;
std::vector<std::vector<MultiplexRange>> ranges;
};
}// namespace dbc_detail
#endif
+25 -6
View File
@@ -3,18 +3,37 @@
#define MULTIPLEXING_H
#include <cstdint>
#include <string>
#include <vector>
/** @brief Unconditional signal, selector, or single-value branch. */
/** @brief Signal participation in multiplexing. */
enum class MultiplexRole {
Unconditional,
Selector,
Branch
Branch,
BranchSelector
};
/** Basic multiplexing: one unsigned selector per frame, one raw value per branch. */
struct Multiplexing {
MultiplexRole role = MultiplexRole::Unconditional; /**< Signal participation in multiplexing. */
std::uint64_t value = 0U; /**< Required raw selector value; used only by branches. */
/** @brief Inclusive raw selector range, without expanding individual values. */
struct MultiplexRange {
std::uint64_t minimum = 0U;
std::uint64_t maximum = 0U;
};
/** @brief Basic markers and optional explicit extended multiplexing condition. */
struct Multiplexing {
MultiplexRole role = MultiplexRole::Unconditional;
std::uint64_t value = 0U; /**< mN fallback when no explicit ranges are supplied. */
std::string selector; /**< Parent selector name within the same frame. */
std::vector<MultiplexRange> ranges; /**< Alternatives for the named selector; override mN. */
};
inline bool IsMultiplexSelector(MultiplexRole role) {
return role == MultiplexRole::Selector || role == MultiplexRole::BranchSelector;
}
inline bool IsMultiplexBranch(MultiplexRole role) {
return role == MultiplexRole::Branch || role == MultiplexRole::BranchSelector;
}
#endif
+1 -1
View File
@@ -23,7 +23,7 @@
* @brief Describes one signal inside a DBC frame.
*/
struct SignalInfo {
Multiplexing multiplexing; /**< Selector role and branch value. */
Multiplexing multiplexing; /**< Selector role, parent, and raw branch ranges. */
std::string name; /**< Signal name. */
std::uint32_t startBit = 0U; /**< Start bit in DBC notation. */
std::uint32_t length = 0U; /**< Signal length in bits. */
+15 -3
View File
@@ -118,7 +118,7 @@ extended ID encodings and repeated signal names within a frame. Standard and
extended frames may share a numeric ID, and separate frames may reuse signal names.
Signal tests cover complete syntax and numeric validation, scientific notation,
finite physical parameters and ordered ranges, spaces and tabs, receiver lists,
unsupported multiplexing, and Intel/Motorola declared-payload boundaries.
invalid multiplexing, and Intel/Motorola declared-payload boundaries.
Physical-number tests apply a custom comma-decimal C++ locale and an available
named comma-decimal C numeric locale. They check all four physical fields,
leading signs, fractions, scientific notation, finite boundaries, zero, malformed
@@ -172,7 +172,19 @@ 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_.
of invalid markers, selector references/ranges, and overlaps.
Extended and nested multiplexing coverage includes deferred `SG_MUL_VAL_`
references, inclusive and separated ranges, repeated range statements, explicit
range precedence over markers, `mNM` fallback, independent selectors, nested
siblings sharing Intel/Motorola payload bits, raw selection with scaling, and
64-bit boundaries. Tests verify metadata through tree/runtime builders, selective
decoding with ancestor dependencies, inactive paths with missing bytes, missing
selectors versus leaf payload, malformed ranges, unknown targets, cycles,
conflicting parents, and invalid manually constructed runtime graphs.
The extended multiplexing change was verified with the root qmake build,
`make -j4`, `make check` (2,398 passed, no failures, one existing named C-locale
check skipped because the locale is unavailable), and the standalone tree test.
## Additional regression coverage
@@ -245,7 +257,7 @@ frame and signal lookups.
`dbc_pipeline_test.cpp` parses temporary DBC files and checks independent expected
metadata against the parser, runtime builder, and tree builder. It exercises
standard and extended frames with the same numeric ID and signal name, Intel
and signed Motorola decoding, scaling, raw value descriptions, basic multiplexing,
and signed Motorola decoding, scaling, raw value descriptions, basic and extended multiplexing,
PGN metadata, receivers, units, ranges, and frame/signal comments. Tree checks
include node types, payloads, parents, rows, names, and child counts.
+5 -5
View File
@@ -813,10 +813,10 @@ void CoverageTest::multiplexingErrorLocations_data() {
const QString selector = "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n";
const QString branch = "SG_ A m1 : 8|8@1+ (1,0) [0|255] \"\" ECU\n";
QTest::newRow("missing-selector") << header + branch << 3U << QString::fromUtf8("Multiplexed branch has no selector.");
QTest::newRow("second-selector") << header + selector + "\nSG_ Other M : 16|1@1+ (1,0) [0|1] \"\" ECU\n"
<< 5U << QString::fromUtf8("Multiplexing requires one unsigned selector per frame.");
QTest::newRow("second-selector") << header + selector + "\nSG_ Other M : 16|1@1- (1,0) [-1|0] \"\" ECU\n"
<< 5U << QString::fromUtf8("Multiplex selectors must be unsigned.");
QTest::newRow("signed-selector") << header + "SG_ Mode M : 0|1@1- (1,0) [-1|0] \"\" ECU\n"
<< 3U << QString::fromUtf8("Multiplexing requires one unsigned selector per frame.");
<< 3U << QString::fromUtf8("Multiplex selectors must be unsigned.");
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.");
QTest::newRow("same-branch-overlap") << header + selector + branch + "SG_ B m1 : 15|8@0+ (1,0) [0|255] \"\" ECU\n"
<< 5U << QString::fromUtf8("Signals overlap without mutually exclusive multiplexing conditions.");
@@ -827,9 +827,9 @@ void CoverageTest::multiplexingErrorLocations_data() {
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"
<< 4U << QString::fromUtf8("Signals overlap without mutually exclusive multiplexing conditions.");
QTest::newRow("extended-multiplexing") << header + "SG_MUL_VAL_ 1 A Mode 1-2;\n"
<< 3U << QString::fromUtf8("Extended and nested multiplexing are unsupported.");
<< 3U << QString::fromUtf8("Unresolved SG_MUL_VAL_ signal reference: A");
QTest::newRow("nested-marker") << header + "SG_ A m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
<< 3U << QString::fromUtf8("Invalid or unsupported multiplexing marker.");
<< 3U << QString::fromUtf8("Multiplexed branch has no selector.");
}
void CoverageTest::multiplexingErrorLocations() {
+237 -44
View File
@@ -103,6 +103,8 @@ private Q_SLOTS:
void diagnosticFormatting();
void multiplexing();
void multiplexingBoundaries();
void extendedMultiplexing();
void extendedMultiplexingErrors();
void validFrames();
void invalidFrames();
void duplicateFrames();
@@ -458,41 +460,40 @@ void DbcParserTest::diagnosticErrors() {
std::size_t line;
};
const ErrorCase cases[] = {
{"UNKNOWN_ data;\n\nBO_ invalid Frame: 1 ECU\n",
DbcDiagnosticCode::InvalidStatement, 3U},
{"BO_ 1 Frame: 1 ECU\nSG_MUL_VAL_ 1 Value Mode 1-2;\n",
DbcDiagnosticCode::UnsupportedStatement, 2U},
{"BO_ 1 Frame: 1 ECU\nVAL_ 1 Missing 0 \"Off\";\n",
DbcDiagnosticCode::InvalidValueDescription, 2U},
{"BO_ 1 Frame: 1 ECU\nSG_ Value m1 : 0|8@1+ (1,0) [0|255] \"\" ECU\n",
DbcDiagnosticCode::InvalidMultiplexing, 2U},
{"BO_ 1 Frame: 1 ECU\nSG_ Mode M : 0|8@1- (1,0) [-128|127] \"\" ECU\n",
DbcDiagnosticCode::InvalidMultiplexing, 2U},
{"VAL_ broken\n", DbcDiagnosticCode::InvalidValueDescription, 1U},
{"SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n",
DbcDiagnosticCode::InvalidStatement, 1U},
{"BO_ 1 Frame: 1 ECU\nBO_ 1 Duplicate: 1 ECU\n",
DbcDiagnosticCode::InvalidStatement, 2U},
{"BO_ 1 Frame: 1 ECU\nSG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n",
DbcDiagnosticCode::InvalidStatement, 3U},
{"BO_ 1 Frame: 1 ECU\nSG_ Value : 8|8@1+ (1,0) [0|255] \"\" ECU\n",
DbcDiagnosticCode::InvalidStatement, 2U},
{"BO_ 1 Frame: 1 ECU\nVAL_ 2 Missing 0 \"Off\";\n",
DbcDiagnosticCode::InvalidValueDescription, 2U},
{"BO_ 1 Frame: 1 ECU\nSG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"VAL_ 1 Value 0 \"Off\";\nVAL_ 1 Value 0 \"Duplicate\";\n",
DbcDiagnosticCode::InvalidValueDescription, 4U},
{"BO_ 1 Frame: 2 ECU\nSG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
"SG_ Value m2 : 8|8@1+ (1,0) [0|255] \"\" ECU\n",
DbcDiagnosticCode::InvalidMultiplexing, 3U},
{"BO_ 1 Frame: 2 ECU\nSG_ Mode M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Other M : 8|8@1+ (1,0) [0|255] \"\" ECU\n",
DbcDiagnosticCode::InvalidMultiplexing, 3U},
{"BO_ 1 Frame: 1 ECU\nSG_ First : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Second : 7|8@0+ (1,0) [0|255] \"\" ECU\n",
DbcDiagnosticCode::InvalidMultiplexing, 3U}
};
{"UNKNOWN_ data;\n\nBO_ invalid Frame: 1 ECU\n",
DbcDiagnosticCode::InvalidStatement, 3U},
{"BO_ 1 Frame: 1 ECU\nSG_MUL_VAL_ 1 Value Mode 1-2;\n",
DbcDiagnosticCode::InvalidMultiplexing, 2U},
{"BO_ 1 Frame: 1 ECU\nVAL_ 1 Missing 0 \"Off\";\n",
DbcDiagnosticCode::InvalidValueDescription, 2U},
{"BO_ 1 Frame: 1 ECU\nSG_ Value m1 : 0|8@1+ (1,0) [0|255] \"\" ECU\n",
DbcDiagnosticCode::InvalidMultiplexing, 2U},
{"BO_ 1 Frame: 1 ECU\nSG_ Mode M : 0|8@1- (1,0) [-128|127] \"\" ECU\n",
DbcDiagnosticCode::InvalidMultiplexing, 2U},
{"VAL_ broken\n", DbcDiagnosticCode::InvalidValueDescription, 1U},
{"SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n",
DbcDiagnosticCode::InvalidStatement, 1U},
{"BO_ 1 Frame: 1 ECU\nBO_ 1 Duplicate: 1 ECU\n",
DbcDiagnosticCode::InvalidStatement, 2U},
{"BO_ 1 Frame: 1 ECU\nSG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n",
DbcDiagnosticCode::InvalidStatement, 3U},
{"BO_ 1 Frame: 1 ECU\nSG_ Value : 8|8@1+ (1,0) [0|255] \"\" ECU\n",
DbcDiagnosticCode::InvalidStatement, 2U},
{"BO_ 1 Frame: 1 ECU\nVAL_ 2 Missing 0 \"Off\";\n",
DbcDiagnosticCode::InvalidValueDescription, 2U},
{"BO_ 1 Frame: 1 ECU\nSG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"VAL_ 1 Value 0 \"Off\";\nVAL_ 1 Value 0 \"Duplicate\";\n",
DbcDiagnosticCode::InvalidValueDescription, 4U},
{"BO_ 1 Frame: 2 ECU\nSG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
"SG_ Value m2 : 8|8@1+ (1,0) [0|255] \"\" ECU\n",
DbcDiagnosticCode::InvalidMultiplexing, 3U},
{"BO_ 1 Frame: 2 ECU\nSG_ Mode M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Other M : 8|8@1- (1,0) [-128|127] \"\" ECU\n",
DbcDiagnosticCode::InvalidMultiplexing, 3U},
{"BO_ 1 Frame: 1 ECU\nSG_ First : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Second : 7|8@0+ (1,0) [0|255] \"\" ECU\n",
DbcDiagnosticCode::InvalidMultiplexing, 3U}};
for (const ErrorCase &test : cases) {
fixture.Write(test.text);
const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
@@ -980,15 +981,14 @@ void DbcParserTest::multiplexing() {
"Unavailable selector does not imply inactive branch");
const std::vector<std::string> 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"
};
first,
selector + "SG_ Other M : 16|8@1- (1,0) [-128|127] \"\" 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;
@@ -1374,6 +1374,199 @@ void DbcParserTest::invalidValueDescriptions() {
}
}
void DbcParserTest::extendedMultiplexing() {
Fixture fixture;
// Forward references, multiple ranges, repeated range statements, and nested siblings.
fixture.Write(
"SG_MUL_VAL_ 1 Child Root 1-2, 4-4;\n"
"SG_MUL_VAL_ 1 Leaf Child 3-5;\n"
"SG_MUL_VAL_ 1 Leaf Child 7-7;\n"
"SG_MUL_VAL_ 1 Sibling Root 6-6;\n"
"SG_MUL_VAL_ 1 Other Sibling 3-5;\n"
"BO_ 1 Frame: 4 ECU\n"
"SG_ Leaf m99 : 16|8@1- (2,1) [-128|127] \"\" ECU\n"
"SG_ Other m0 : 23|8@0+ (1,0) [0|255] \"\" ECU\n"
"SG_ Child m1M : 8|8@1+ (10,3) [0|255] \"\" ECU\n"
"SG_ Sibling m6M : 15|8@0+ (1,0) [0|255] \"\" ECU\n"
"SG_ Root M : 0|8@1+ (10,3) [0|255] \"\" ECU\n"
"SG_ Common : 24|8@1+ (1,0) [0|255] \"\" ECU\n");
const DbcDatabase parsed = DbcParser().ParseFile(fixture.path.string());
const DecodeDatabase database = DbcDecodeBuilder().Build(parsed);
const std::unique_ptr<TreeNode> tree = DbcTreeBuilder().Build(parsed);
const Multiplexing &metadata = tree->GetChild(0U)->GetChild(0U)->GetSignal()->multiplexing;
CHECK(metadata.selector == "Child" && metadata.ranges.size() == 2U &&
metadata.ranges[1].minimum == 7U,
"Tree preserves ranges and selector name");
CHECK(database.GetFrames()[0].signals[2].multiplexing.role == MultiplexRole::BranchSelector,
"Runtime preserves nested selector role");
RawCanFrame raw;
raw.canId = 1U;
raw.data = {2U, 4U, 255U, 8U};
DecodedFrameValue result = DbcDecoder().Decode(database, raw);
CHECK(result.signals[0].valid && result.signals[0].physicalValue == -1.0 &&
result.signals[1].status == SignalDecodeStatus::Inactive && result.signals[2].valid &&
result.signals[3].status == SignalDecodeStatus::Inactive && result.signals[5].valid,
"Nested raw conditions select one path and preserve definition order");
const std::vector<SignalKey> selection = {SignalKey(FrameKey(1U, false), "Leaf")};
result = DbcDecoder().Decode(database, raw, selection);
CHECK(result.signals.size() == 1U && result.signals[0].valid,
"Selection reads all ancestors internally");
for (std::uint8_t root: {1U, 2U, 4U}) {
for (std::uint8_t child: {3U, 5U, 7U}) {
raw.data = {root, child, 9U, 8U};
CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].valid,
"Inclusive endpoints and separated ranges");
}
}
raw.data = {6U, 4U, 9U, 8U};
result = DbcDecoder().Decode(database, raw);
CHECK(result.signals[0].status == SignalDecodeStatus::Inactive && result.signals[1].valid &&
result.signals[3].valid,
"Overlapping sibling selector path becomes active");
raw.data = {0U};
CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].status == SignalDecodeStatus::Inactive,
"Inactive root does not require child selector bytes");
raw.data = {1U};
CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].status == SignalDecodeStatus::SelectorUnavailable,
"Active root with missing child selector");
raw.data = {1U, 3U};
CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].status == SignalDecodeStatus::InsufficientPayload,
"Active nested leaf needs its bytes");
raw.data = {1U, 6U};
CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].status == SignalDecodeStatus::Inactive,
"Gap between explicit ranges does not fall back to mN");
raw.data.clear();
CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].status == SignalDecodeStatus::SelectorUnavailable,
"Missing root selector");
fixture.Write("BO_ 1 Frame: 9 ECU\n"
"SG_ Root M : 0|64@1+ (1,0) [0|1] \"\" ECU\n"
"SG_ Leaf m0 : 64|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_MUL_VAL_ 1 Leaf Root 18446744073709551614-18446744073709551615;\n");
raw.data.assign(9U, 255U);
CHECK(DbcDecoder().Decode(DbcDecodeBuilder().Build(DbcParser().ParseFile(fixture.path.string())), raw).signals[1].valid,
"64-bit ranges retain exact UINT64_MAX without expansion");
fixture.Write("BO_ 1 Frame: 4 ECU\n"
"SG_ Root M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Second M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ A m0 : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ B m0 : 24|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_MUL_VAL_ 1 A Root 1-1;\nSG_MUL_VAL_ 1 B Second 2-2;\n");
raw.data = {1U, 2U, 9U, 10U};
result = DbcDecoder().Decode(DbcDecodeBuilder().Build(DbcParser().ParseFile(fixture.path.string())), raw);
CHECK(result.signals[2].valid && result.signals[3].valid, "Independent explicit selectors");
fixture.Write("BO_ 1 Deep: 5 ECU\n"
"SG_ Root M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Child m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Grandchild m2M : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Leaf : 24|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_MUL_VAL_ 1 Grandchild Child 2-2;\n"
"SG_MUL_VAL_ 1 Leaf Grandchild 3-3;\n");
const DecodeDatabase deep = DbcDecodeBuilder().Build(DbcParser().ParseFile(fixture.path.string()));
raw.data = {1U, 2U, 3U, 9U, 0U};
CHECK(DbcDecoder().Decode(deep, raw).signals[3].valid, "Three selector levels and an unmarked extended branch");
raw.data = {1U, 0U};
CHECK(DbcDecoder().Decode(deep, raw).signals[3].status == SignalDecodeStatus::Inactive,
"Inactive middle ancestor skips missing descendant bytes");
// Marker fallback also works on a nested selector without its own SG_MUL_VAL_.
fixture.Write("BO_ 1 Frame: 3 ECU\n"
"SG_ Root M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Child m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Leaf m2 : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_MUL_VAL_ 1 Leaf Child 2-2;\n");
raw.data = {1U, 2U, 9U};
CHECK(DbcDecoder().Decode(DbcDecodeBuilder().Build(DbcParser().ParseFile(fixture.path.string())), raw).signals[2].valid,
"mNM retains implicit root fallback");
}
void DbcParserTest::extendedMultiplexingErrors() {
Fixture fixture;
const std::string definitions =
"BO_ 1 Frame: 3 ECU\n"
"SG_ Root M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Child m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Leaf m2 : 16|8@1+ (1,0) [0|255] \"\" ECU\n";
for (const std::string statement: {
"SG_MUL_VAL_ 1 Leaf Root 3-2;", "SG_MUL_VAL_ 1 Leaf Root -1-2;",
"SG_MUL_VAL_ 1 Leaf Root 1-256;", "SG_MUL_VAL_ 1 Leaf Root 1-2,;",
"SG_MUL_VAL_ 1 Leaf Root 1-2 trailing;", "SG_MUL_VAL_ 1 Leaf Root 1-2",
"SG_MUL_VAL_ 1 Leaf Root 0-18446744073709551616;",
"SG_MUL_VAL_ 1 Leaf Missing 1-2;", "SG_MUL_VAL_ 1 Missing Root 1-2;",
"SG_MUL_VAL_ 2 Leaf Root 1-2;", "SG_MUL_VAL_ 1 Leaf Leaf 1-2;"}) {
fixture.Write(statement + "\n" + definitions);
const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
CHECK(result.HasErrors() && result.database.frames.empty(), statement);
CHECK(result.diagnostics.back().code == DbcDiagnosticCode::InvalidMultiplexing &&
result.diagnostics.back().lineNumber == 1U,
"Extended error retains source statement location");
}
for (const std::string statements: {
"SG_MUL_VAL_ 1 Root Child 1-1;\nSG_MUL_VAL_ 1 Child Root 1-1;\n",
"SG_MUL_VAL_ 1 Child Child 1-1;\n",
"SG_MUL_VAL_ 1 Leaf Root 1-1;\nSG_MUL_VAL_ 1 Leaf Child 1-1;\n"}) {
fixture.Write(definitions + statements);
CHECK(DbcParser().ParseFileWithDiagnostics(fixture.path.string()).HasErrors(), "Cyclic or conflicting parents rejected");
}
// Range intersection and inherited root conditions must both protect overlaps.
const std::string overlapping =
"BO_ 1 Frame: 4 ECU\n"
"SG_ Root M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Child m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ A m0 : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ B m0 : 23|8@0+ (1,0) [0|255] \"\" ECU\n"
"SG_MUL_VAL_ 1 A Child 2-4;\n"
"SG_MUL_VAL_ 1 Child Root 1-1;\n";
for (const std::string condition: {"SG_MUL_VAL_ 1 B Child 4-6;\n",
"SG_MUL_VAL_ 1 B Root 1-1;\n"}) {
fixture.Write(overlapping + condition);
CHECK(DbcParser().ParseFileWithDiagnostics(fixture.path.string()).HasErrors(),
"Overlapping ranges or compatible ancestors cannot share bits");
}
fixture.Write(overlapping + "SG_MUL_VAL_ 1 B Child 5-6;\n");
CHECK(!DbcParser().ParseFileWithDiagnostics(fixture.path.string()).HasErrors(),
"Disjoint adjacent ranges may share bits");
fixture.Write(overlapping + "SG_ Second M : 24|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_MUL_VAL_ 1 B Second 2-4;\n");
const DbcParseResult independent = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
CHECK(independent.HasErrors() && independent.diagnostics.back().message ==
"Signals overlap without mutually exclusive multiplexing conditions.",
"Independent selector conditions do not imply exclusion");
// Validate manually constructed runtime graphs with the same rules.
fixture.Write(definitions + "SG_MUL_VAL_ 1 Leaf Child 2-3;\n");
const DecodeDatabase valid = DbcDecodeBuilder().Build(DbcParser().ParseFile(fixture.path.string()));
for (unsigned kind = 0U; kind < 6U; ++kind) {
std::vector<DecodeFrame> frames = valid.GetFrames();
Multiplexing &leaf = frames[0].signals[2].multiplexing;
switch (kind) {
case 0:
leaf.selector = "Missing";
break;
case 1:
leaf.ranges[0] = {4U, 3U};
break;
case 2:
leaf.ranges[0] = {0U, 256U};
break;
case 3:
leaf.ranges.clear();
break;
case 4:
frames[0].signals[0].multiplexing.selector = "Child";
frames[0].signals[0].multiplexing.ranges = {{1U, 1U}};
break;
case 5:
frames[0].signals[1].valueType = ValueType::Signed;
break;
}
QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase(frames));
}
}
int RunParserTests(int argc, char **argv) {
DbcParserTest tests;
return QTest::qExec(&tests, argc, argv);
+1
View File
@@ -165,6 +165,7 @@ void DbcValidationTest::invalidMetadata() {
case 19:
signal.multiplexing.role = MultiplexRole::Selector;
other.multiplexing.role = MultiplexRole::Selector;
other.valueType = ValueType::Signed;
other.startBit = 8U;
frame.signals.push_back(other);
break;