Closes #29
This commit is contained in:
1 parent
a702cffd93
commit
8d06165979
18 files changed
+570
-174
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 \
|
||||
|
||||
+27
-34
@@ -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,57 +105,48 @@ 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];
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
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 (signal.multiplexing.role == MultiplexRole::Branch) {
|
||||
if (selector.definition->length < 64U && signal.multiplexing.value >= (std::uint64_t(1U) << selector.definition->length)) {
|
||||
if (activation != SignalDecodeStatus::Valid) {
|
||||
decoded.status = activation;
|
||||
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;
|
||||
return decoded;
|
||||
|
||||
+6
-9
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
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];
|
||||
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.";
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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)];
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
+200
-7
@@ -103,6 +103,8 @@ private Q_SLOTS:
|
||||
void diagnosticFormatting();
|
||||
void multiplexing();
|
||||
void multiplexingBoundaries();
|
||||
void extendedMultiplexing();
|
||||
void extendedMultiplexingErrors();
|
||||
void validFrames();
|
||||
void invalidFrames();
|
||||
void duplicateFrames();
|
||||
@@ -461,7 +463,7 @@ void DbcParserTest::diagnosticErrors() {
|
||||
{"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},
|
||||
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",
|
||||
@@ -487,12 +489,11 @@ void DbcParserTest::diagnosticErrors() {
|
||||
"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",
|
||||
"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}
|
||||
};
|
||||
DbcDiagnosticCode::InvalidMultiplexing, 3U}};
|
||||
for (const ErrorCase &test : cases) {
|
||||
fixture.Write(test.text);
|
||||
const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
|
||||
@@ -981,14 +982,13 @@ void DbcParserTest::multiplexing() {
|
||||
|
||||
const std::vector<std::string> invalid = {
|
||||
first,
|
||||
selector + "SG_ Other M : 16|8@1+ (1,0) [0|255] \"\" ECU\n",
|
||||
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"
|
||||
};
|
||||
"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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user