12 std::uint32_t startBit;
15 std::vector<std::uint8_t> data;
17 std::int64_t rawValue;
23Q_DECLARE_METATYPE(DecodeCase)
25class DbcDecoderTest :
public QObject {
31 void payloadPolicy_data();
33 void invalidDefinitions_data();
34 void invalidDefinitions();
35 void physicalConversion_data();
36 void physicalConversion();
37 void selectorConversionFailure();
41void DbcDecoderTest::decode_data() {
42 QTest::addColumn<DecodeCase>(
"test");
43 const DecodeCase cases[] = {
44 {
"Motorola reproduction", ByteOrder::Motorola, 7U, 16U, ValueType::Unsigned, {0x12, 0x34},
true, 0x1234, 1.0, 0.0, 4660.0},
45 {
"Motorola within byte", ByteOrder::Motorola, 5U, 4U, ValueType::Unsigned, {0xAC},
true, 11, 1.0, 0.0, 11.0},
46 {
"Motorola unaligned crossing", ByteOrder::Motorola, 3U, 12U, ValueType::Unsigned, {0xAB, 0xCD},
true, 0xBCD, 1.0, 0.0, 3021.0},
47 {
"Motorola starts at bit zero", ByteOrder::Motorola, 0U, 2U, ValueType::Unsigned, {0x01, 0x80},
true, 3, 1.0, 0.0, 3.0},
48 {
"Motorola second byte", ByteOrder::Motorola, 15U, 16U, ValueType::Unsigned, {0xFF, 0x12, 0x34},
true, 0x1234, 1.0, 0.0, 4660.0},
49 {
"Motorola signed scaled", ByteOrder::Motorola, 7U, 16U, ValueType::Signed, {0xFF, 0xFE},
true, -2, 0.5, 10.0, 9.0},
50 {
"Motorola 64 bits", ByteOrder::Motorola, 7U, 64U, ValueType::Unsigned, {0, 0, 0, 0, 0, 0, 0x12, 0x34},
true, 0x1234, 1.0, 0.0, 4660.0},
51 {
"Motorola 64 bits high bit", ByteOrder::Motorola, 7U, 64U, ValueType::Unsigned, {0x40, 0, 0, 0, 0, 0, 0, 0},
true, 0x4000000000000000LL, 1.0, 0.0, 4611686018427387904.0},
52 {
"Motorola short payload", ByteOrder::Motorola, 7U, 16U, ValueType::Unsigned, {0x12},
false, 0, 1.0, 0.0, 0.0},
53 {
"Motorola empty payload", ByteOrder::Motorola, 7U, 8U, ValueType::Unsigned, {},
false, 0, 1.0, 0.0, 0.0},
54 {
"Motorola large start", ByteOrder::Motorola, std::numeric_limits<std::uint32_t>::max(), 8U, ValueType::Unsigned, {0xFF},
false, 0, 1.0, 0.0, 0.0},
55 {
"Motorola zero length", ByteOrder::Motorola, 7U, 0U, ValueType::Unsigned, {0xFF},
false, 0, 1.0, 0.0, 0.0},
56 {
"Motorola excessive length", ByteOrder::Motorola, 7U, 65U, ValueType::Unsigned,
57 std::vector<std::uint8_t>(9U, 0xFF),
false, 0, 1.0, 0.0, 0.0},
58 {
"Intel regression", ByteOrder::Intel, 0U, 16U, ValueType::Unsigned, {0x12, 0x34},
true, 0x3412, 1.0, 0.0, 13330.0},
59 {
"Intel signed regression", ByteOrder::Intel, 0U, 16U, ValueType::Signed, {0xFE, 0xFF},
true, -2, 0.5, 10.0, 9.0},
60 {
"Intel short payload", ByteOrder::Intel, 0U, 16U, ValueType::Unsigned, {0x12},
false, 0, 1.0, 0.0, 0.0}};
62 for (
const DecodeCase &test: cases)
63 QTest::newRow(test.name) << test;
66void DbcDecoderTest::decode() {
67 QFETCH(DecodeCase, test);
69 signal.
name = test.name;
72 signal.
length = test.length;
74 signal.
factor = test.factor;
75 signal.
offset = test.offset;
78 definition.
canId = 0x123U;
80 definition.signals.push_back(signal);
81 if (test.length == 0U || test.length > 64U || test.startBit > 64U) {
82 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (
void)
DecodeDatabase({definition}));
89 frame.data = test.data;
91 QVERIFY2(result.
valid, test.name);
92 QCOMPARE(result.
definition.get(), &database.GetFrames()[0]);
93 QCOMPARE(result.signals.size(), std::size_t(1U));
95 QCOMPARE(decoded.
definition.get(), &database.GetFrames()[0].signals[0]);
96 QCOMPARE(decoded.
valid, test.valid);
100 if (test.valueType == ValueType::Signed) {
101 const std::int64_t *raw = std::get_if<std::int64_t>(&decoded.
rawValue);
102 QVERIFY(raw !=
nullptr);
103 QCOMPARE(*raw, test.rawValue);
105 const std::uint64_t *raw = std::get_if<std::uint64_t>(&decoded.
rawValue);
106 QVERIFY(raw !=
nullptr);
107 QCOMPARE(*raw,
static_cast<std::uint64_t
>(test.rawValue));
112void DbcDecoderTest::boundaries() {
113 for (
ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) {
114 for (
ValueType type: {ValueType::Unsigned, ValueType::Signed}) {
115 for (std::uint64_t bits: {std::uint64_t(0U), std::uint64_t(0x7FFFFFFFFFFFFFFFULL),
116 std::uint64_t(0x8000000000000000ULL),
117 std::uint64_t(0x8000000000000001ULL),
118 std::uint64_t(0xFFFFFFFFFFFFFFFFULL)}) {
122 signal.
startBit = order == ByteOrder::Intel ? 0U : 7U;
128 definition.signals.push_back(signal);
131 for (
unsigned byte = 0U;
byte < 8U; ++byte) {
132 const unsigned shift = (order == ByteOrder::Intel ? byte : 7U - byte) * 8U;
133 frame.
data.push_back(
static_cast<std::uint8_t
>(bits >> shift));
136 const std::int64_t signedExpected = bits <= 0x7FFFFFFFFFFFFFFFULL
137 ?
static_cast<std::int64_t
>(bits)
138 : std::numeric_limits<std::int64_t>::min() +
139 static_cast<std::int64_t>(bits - 0x8000000000000000ULL);
141 const RawSignalValue expected = type == ValueType::Unsigned
142 ? std::variant<std::int64_t, std::uint64_t>(bits)
143 : std::variant<std::int64_t, std::uint64_t>(signedExpected);
144 const double physical = (type == ValueType::Unsigned
145 ?
static_cast<double>(bits)
146 : static_cast<double>(signedExpected)) *
149 std::ostringstream actualText;
150 std::ostringstream expectedText;
151 if (
const std::int64_t *signedValue = std::get_if<std::int64_t>(&decoded.
rawValue))
152 actualText << *signedValue;
154 actualText << std::get<std::uint64_t>(decoded.
rawValue);
155 if (type == ValueType::Unsigned)
156 expectedText << bits;
158 expectedText << signedExpected;
159 QVERIFY(decoded.
valid);
160 QVERIFY(decoded.
rawValue == expected);
162 QCOMPARE(actualText.str(), expectedText.str());
168void DbcDecoderTest::payloadPolicy_data() {
169 QTest::addColumn<unsigned>(
"bytes");
170 QTest::addColumn<FrameDecodeStatus>(
"status");
171 QTest::newRow(
"empty") << 0U << FrameDecodeStatus::InsufficientPayload;
172 QTest::newRow(
"partial") << 1U << FrameDecodeStatus::InsufficientPayload;
173 QTest::newRow(
"short-with-all-signals") << 2U << FrameDecodeStatus::InsufficientPayload;
174 QTest::newRow(
"exact") << 3U << FrameDecodeStatus::Valid;
175 QTest::newRow(
"long") << 4U << FrameDecodeStatus::Valid;
178void DbcDecoderTest::payloadPolicy() {
179 QFETCH(
unsigned, bytes);
181 for (
ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) {
183 definition.
canId = 17U;
186 signal.
name =
"First";
189 signal.
startBit = order == ByteOrder::Intel ? 0U : 7U;
190 definition.signals.push_back(signal);
191 signal.
name =
"Second";
193 definition.signals.push_back(signal);
197 raw.data.assign(bytes, 42U);
199 QVERIFY(result.
valid);
200 QCOMPARE(result.
status, status);
201 QCOMPARE(result.signals.size(), std::size_t(2U));
202 for (std::size_t index = 0U; index < result.signals.size(); ++index) {
204 QCOMPARE(value.status, bytes > index ? SignalDecodeStatus::Valid : SignalDecodeStatus::InsufficientPayload);
205 QCOMPARE(value.
valid, bytes > index);
207 QCOMPARE(std::get<std::uint64_t>(value.
rawValue), std::uint64_t(42U));
211 raw.isExtended =
true;
213 QVERIFY(!unknown.
valid && !unknown.
definition && unknown.signals.empty());
214 QCOMPARE(unknown.
status, FrameDecodeStatus::UnknownFrame);
218 definition.signals.clear();
221 QVERIFY(empty.
valid && empty.signals.empty());
222 QCOMPARE(empty.
status, status);
226void DbcDecoderTest::invalidDefinitions_data() {
227 QTest::addColumn<int>(
"kind");
228 QTest::newRow(
"zero-length") << 0;
229 QTest::newRow(
"65-bits") << 1;
230 QTest::newRow(
"outside-dlc") << 2;
231 QTest::newRow(
"start-overflow") << 3;
232 QTest::newRow(
"invalid-byte-order") << 4;
233 QTest::newRow(
"invalid-value-type") << 5;
234 QTest::newRow(
"invalid-multiplex-role") << 6;
235 QTest::newRow(
"missing-selector") << 7;
238void DbcDecoderTest::invalidDefinitions() {
240 for (
ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) {
247 good.
startBit = order == ByteOrder::Intel ? 0U : 7U;
251 case 0: bad.
length = 0U;
break;
252 case 1: bad.
length = 65U;
break;
254 case 3: bad.
startBit = std::numeric_limits<std::uint32_t>::max();
break;
257 case 6: bad.multiplexing.role =
static_cast<MultiplexRole
>(99);
break;
258 case 7: bad.multiplexing.role = MultiplexRole::Branch;
break;
260 definition.signals = {bad, good};
261 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (
void)
DecodeDatabase({definition}));
265void DbcDecoderTest::physicalConversion_data() {
266 QTest::addColumn<double>(
"factor");
267 QTest::addColumn<double>(
"offset");
268 QTest::addColumn<bool>(
"finite");
269 const double infinity = std::numeric_limits<double>::infinity();
270 const double nan = std::numeric_limits<double>::quiet_NaN();
271 const double maximum = std::numeric_limits<double>::max();
272 QTest::newRow(
"nan-factor") << nan << 0.0 <<
false;
273 QTest::newRow(
"nan-offset") << 1.0 << nan <<
false;
274 QTest::newRow(
"infinite-factor") << infinity << 0.0 <<
false;
275 QTest::newRow(
"negative-infinite-factor") << -infinity << 0.0 <<
false;
276 QTest::newRow(
"infinite-offset") << 1.0 << infinity <<
false;
277 QTest::newRow(
"negative-infinite-offset") << 1.0 << -infinity <<
false;
278 QTest::newRow(
"multiply-overflow") << maximum << 0.0 <<
false;
279 QTest::newRow(
"add-overflow") << maximum / 2.0 << maximum <<
false;
280 QTest::newRow(
"finite-limit") << maximum / 2.0 << 0.0 <<
true;
281 QTest::newRow(
"zero-factor") << 0.0 << 42.0 <<
true;
284void DbcDecoderTest::physicalConversion() {
285 QFETCH(
double, factor);
286 QFETCH(
double, offset);
287 QFETCH(
bool, finite);
288 for (
ValueType type: {ValueType::Signed, ValueType::Unsigned}) {
292 signal.
name =
"Scaled";
298 definition.signals.push_back(signal);
299 signal.
name =
"Good";
303 definition.signals.push_back(signal);
306 if (!std::isfinite(factor) || !std::isfinite(offset)) {
307 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (
void)
DecodeDatabase({definition}));
311 QVERIFY(result.
valid);
314 QCOMPARE(scaled.
valid, finite);
315 QCOMPARE(scaled.status, finite ? SignalDecodeStatus::Valid : SignalDecodeStatus::PhysicalConversionFailed);
317 if (type == ValueType::Signed)
318 QCOMPARE(std::get<std::int64_t>(scaled.
rawValue), std::int64_t(2));
320 QCOMPARE(std::get<std::uint64_t>(scaled.
rawValue), std::uint64_t(2));
321 QVERIFY(result.signals[1].
valid);
322 QCOMPARE(result.signals[1].physicalValue, 3.0);
332void DbcDecoderTest::selectorConversionFailure() {
336 selector.
name =
"Selector";
338 selector.multiplexing.role = MultiplexRole::Selector;
339 selector.
factor = std::numeric_limits<double>::max();
341 branch.
name =
"Branch";
344 branch.multiplexing.role = MultiplexRole::Branch;
345 branch.multiplexing.value = 2U;
346 definition.signals = {branch, selector};
350 QCOMPARE(result.
status, FrameDecodeStatus::SignalErrors);
351 QVERIFY(result.signals[0].
valid);
352 QCOMPARE(result.signals[0].physicalValue, 42.0);
353 QCOMPARE(result.signals[1].
status, SignalDecodeStatus::PhysicalConversionFailed);
354 QCOMPARE(std::get<std::uint64_t>(result.signals[1].rawValue), std::uint64_t(2U));
357 definition.signals = {branch, selector};
359 QCOMPARE(inactive.
status, FrameDecodeStatus::Valid);
360 QCOMPARE(inactive.signals[0].
status, SignalDecodeStatus::Inactive);
363void DbcDecoderTest::zeroDlc() {
366 for (
const std::vector<std::uint8_t> &data: {std::vector<std::uint8_t>(), std::vector<std::uint8_t>{42U}}) {
369 QVERIFY(result.
valid && result.signals.empty());
370 QCOMPARE(result.
status, FrameDecodeStatus::Valid);
374 definition.signals.push_back(signal);
375 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (
void)
DecodeDatabase({definition}));
378int RunDecoderTests(
int argc,
char **argv) {
379 DbcDecoderTest tests;
380 return QTest::qExec(&tests, argc, argv);
383#include "dbc_decoder_test.moc"
Runtime CAN decoder using prebuilt decode database.
DecodedFrameValue Decode(const DecodeDatabase &database, const RawCanFrame &frame) const
Decode one raw CAN frame.
Immutable runtime snapshot. Copies share storage; reload replaces a snapshot. Owning lookup handles a...
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
ByteOrder
Signal byte order used for runtime decoding.
ValueType
Signal numeric type.
Runtime-ready frame definition.
Runtime-ready signal definition.
ValueDescriptions valueDescriptions
std::shared_ptr< const DecodeFrame > definition
One decoded signal value.
std::shared_ptr< const DecodeSignal > definition
const std::string * GetValueDescription() const
Stable frame identifier: normalized CAN ID and frame type.
Raw CAN frame used for runtime or trace decoding.
std::vector< std::uint8_t > data