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