DBC Framework
DBC parsing and CAN signal decoding framework
Loading...
Searching...
No Matches
dbc_decoder_test.cpp
1#include "dbc_decoder.h"
2
3#include <QtTest>
4#include <cmath>
5#include <limits>
6#include <sstream>
7#include <stdexcept>
8
9struct DecodeCase {
10 const char *name;
11 ByteOrder byteOrder;
12 std::uint32_t startBit;
13 std::uint32_t length;
14 ValueType valueType;
15 std::vector<std::uint8_t> data;
16 bool valid;
17 std::int64_t rawValue;
18 double factor;
19 double offset;
20 double physicalValue;
21};
22
23Q_DECLARE_METATYPE(DecodeCase)
24
25class DbcDecoderTest : public QObject {
26 Q_OBJECT
27private Q_SLOTS:
28 void decode_data();
29 void decode();
30 void boundaries();
31 void payloadPolicy_data();
32 void payloadPolicy();
33 void invalidDefinitions_data();
34 void invalidDefinitions();
35 void physicalConversion_data();
36 void physicalConversion();
37 void selectorConversionFailure();
38 void zeroDlc();
39};
40
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}};
61
62 for (const DecodeCase &test: cases)
63 QTest::newRow(test.name) << test;
64}
65
66void DbcDecoderTest::decode() {
67 QFETCH(DecodeCase, test);
68 DecodeSignal signal;
69 signal.name = test.name;
70 signal.byteOrder = test.byteOrder;
71 signal.startBit = test.startBit;
72 signal.length = test.length;
73 signal.valueType = test.valueType;
74 signal.factor = test.factor;
75 signal.offset = test.offset;
76
77 DecodeFrame definition;
78 definition.canId = 0x123U;
79 definition.dlc = 8U;
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}));
83 return;
84 }
85 const DecodeDatabase database({definition});
86
87 RawCanFrame frame;
88 frame.canId = definition.canId;
89 frame.data = test.data;
90 const DecodedFrameValue result = DbcDecoder().Decode(database, frame);
91 QVERIFY2(result.valid, test.name);
92 QCOMPARE(result.definition.get(), &database.GetFrames()[0]);
93 QCOMPARE(result.signals.size(), std::size_t(1U));
94 const DecodedSignalValue &decoded = result.signals[0];
95 QCOMPARE(decoded.definition.get(), &database.GetFrames()[0].signals[0]);
96 QCOMPARE(decoded.valid, test.valid);
97 if (!test.valid)
98 return;
99
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);
104 } else {
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));
108 }
109 QCOMPARE(decoded.physicalValue, test.physicalValue);
110}
111
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)}) {
119 DecodeFrame definition;
120 definition.dlc = 8U;
121 DecodeSignal signal;
122 signal.startBit = order == ByteOrder::Intel ? 0U : 7U;
123 signal.length = 64U;
124 signal.byteOrder = order;
125 signal.valueType = type;
126 signal.factor = 0.5;
127 signal.offset = 10.0;
128 definition.signals.push_back(signal);
129 const DecodeDatabase database({definition});
130 RawCanFrame frame;
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));
134 }
135 const DecodedFrameValue result = DbcDecoder().Decode(database, frame);
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);
140 const DecodedSignalValue &decoded = result.signals.at(0U);
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)) *
147 0.5 +
148 10.0;
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;
153 else
154 actualText << std::get<std::uint64_t>(decoded.rawValue);
155 if (type == ValueType::Unsigned)
156 expectedText << bits;
157 else
158 expectedText << signedExpected;
159 QVERIFY(decoded.valid);
160 QVERIFY(decoded.rawValue == expected);
161 QCOMPARE(decoded.physicalValue, physical);
162 QCOMPARE(actualText.str(), expectedText.str());
163 }
164 }
165 }
166}
167
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;
176}
177
178void DbcDecoderTest::payloadPolicy() {
179 QFETCH(unsigned, bytes);
180 QFETCH(FrameDecodeStatus, status);
181 for (ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) {
182 DecodeFrame definition;
183 definition.canId = 17U;
184 definition.dlc = 3U;
185 DecodeSignal signal;
186 signal.name = "First";
187 signal.length = 8U;
188 signal.byteOrder = order;
189 signal.startBit = order == ByteOrder::Intel ? 0U : 7U;
190 definition.signals.push_back(signal);
191 signal.name = "Second";
192 signal.startBit += 8U;
193 definition.signals.push_back(signal);
194 const DecodeDatabase database({definition});
195 RawCanFrame raw;
196 raw.canId = 17U;
197 raw.data.assign(bytes, 42U);
198 const DecodedFrameValue result = DbcDecoder().Decode(database, raw);
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) {
203 const DecodedSignalValue &value = result.signals[index];
204 QCOMPARE(value.status, bytes > index ? SignalDecodeStatus::Valid : SignalDecodeStatus::InsufficientPayload);
205 QCOMPARE(value.valid, bytes > index);
206 if (value.valid) {
207 QCOMPARE(std::get<std::uint64_t>(value.rawValue), std::uint64_t(42U));
208 QCOMPARE(value.physicalValue, 42.0);
209 }
210 }
211 raw.isExtended = true;
212 const DecodedFrameValue unknown = DbcDecoder().Decode(database, raw);
213 QVERIFY(!unknown.valid && !unknown.definition && unknown.signals.empty());
214 QCOMPARE(unknown.status, FrameDecodeStatus::UnknownFrame);
215 QVERIFY(unknown.key == FrameKey(17U, true));
216 QCOMPARE(DbcDecoder().Decode(DecodeDatabase(), raw).status, FrameDecodeStatus::UnknownFrame);
217
218 definition.signals.clear();
219 raw.isExtended = false;
220 const DecodedFrameValue empty = DbcDecoder().Decode(DecodeDatabase({definition}), raw);
221 QVERIFY(empty.valid && empty.signals.empty());
222 QCOMPARE(empty.status, status);
223 }
224}
225
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;
236}
237
238void DbcDecoderTest::invalidDefinitions() {
239 QFETCH(int, kind);
240 for (ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) {
241 DecodeFrame definition;
242 definition.dlc = 1U;
243 DecodeSignal good;
244 good.name = "Good";
245 good.length = 4U;
246 good.byteOrder = order;
247 good.startBit = order == ByteOrder::Intel ? 0U : 7U;
248 DecodeSignal bad = good;
249 bad.name = "Bad";
250 switch (kind) {
251 case 0: bad.length = 0U; break;
252 case 1: bad.length = 65U; break;
253 case 2: bad.startBit += 8U; break;
254 case 3: bad.startBit = std::numeric_limits<std::uint32_t>::max(); break;
255 case 4: bad.byteOrder = static_cast<ByteOrder>(99); break;
256 case 5: bad.valueType = static_cast<ValueType>(99); break;
257 case 6: bad.multiplexing.role = static_cast<MultiplexRole>(99); break;
258 case 7: bad.multiplexing.role = MultiplexRole::Branch; break;
259 }
260 definition.signals = {bad, good};
261 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({definition}));
262 }
263}
264
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;
282}
283
284void DbcDecoderTest::physicalConversion() {
285 QFETCH(double, factor);
286 QFETCH(double, offset);
287 QFETCH(bool, finite);
288 for (ValueType type: {ValueType::Signed, ValueType::Unsigned}) {
289 DecodeFrame definition;
290 definition.dlc = 1U;
291 DecodeSignal signal;
292 signal.name = "Scaled";
293 signal.length = 4U;
294 signal.valueType = type;
295 signal.factor = factor;
296 signal.offset = offset;
297 signal.valueDescriptions.emplace(std::uint64_t(2U), "Two");
298 definition.signals.push_back(signal);
299 signal.name = "Good";
300 signal.startBit = 4U;
301 signal.factor = 1.0;
302 signal.offset = 0.0;
303 definition.signals.push_back(signal);
304 RawCanFrame raw;
305 raw.data = {0x32U};
306 if (!std::isfinite(factor) || !std::isfinite(offset)) {
307 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({definition}));
308 continue;
309 }
310 const DecodedFrameValue result = DbcDecoder().Decode(DecodeDatabase({definition}), raw);
311 QVERIFY(result.valid);
312 QCOMPARE(result.status, finite ? FrameDecodeStatus::Valid : FrameDecodeStatus::SignalErrors);
313 const DecodedSignalValue &scaled = result.signals[0];
314 QCOMPARE(scaled.valid, finite);
315 QCOMPARE(scaled.status, finite ? SignalDecodeStatus::Valid : SignalDecodeStatus::PhysicalConversionFailed);
316 QCOMPARE(std::isfinite(scaled.physicalValue), finite);
317 if (type == ValueType::Signed)
318 QCOMPARE(std::get<std::int64_t>(scaled.rawValue), std::int64_t(2));
319 else
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);
323 if (finite) {
324 QVERIFY(scaled.GetValueDescription());
325 QCOMPARE(*scaled.GetValueDescription(), std::string("Two"));
326 } else {
327 QVERIFY(!scaled.GetValueDescription());
328 }
329 }
330}
331
332void DbcDecoderTest::selectorConversionFailure() {
333 DecodeFrame definition;
334 definition.dlc = 1U;
335 DecodeSignal selector;
336 selector.name = "Selector";
337 selector.length = 2U;
338 selector.multiplexing.role = MultiplexRole::Selector;
339 selector.factor = std::numeric_limits<double>::max();
340 DecodeSignal branch;
341 branch.name = "Branch";
342 branch.startBit = 2U;
343 branch.length = 6U;
344 branch.multiplexing.role = MultiplexRole::Branch;
345 branch.multiplexing.value = 2U;
346 definition.signals = {branch, selector};
347 RawCanFrame raw;
348 raw.data = {170U};
349 const DecodedFrameValue result = DbcDecoder().Decode(DecodeDatabase({definition}), raw);
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));
355 raw.data = {168U};
356 selector.factor = 1.0;
357 definition.signals = {branch, selector};
358 const DecodedFrameValue inactive = DbcDecoder().Decode(DecodeDatabase({definition}), raw);
359 QCOMPARE(inactive.status, FrameDecodeStatus::Valid);
360 QCOMPARE(inactive.signals[0].status, SignalDecodeStatus::Inactive);
361}
362
363void DbcDecoderTest::zeroDlc() {
364 DecodeFrame definition;
365 RawCanFrame raw;
366 for (const std::vector<std::uint8_t> &data: {std::vector<std::uint8_t>(), std::vector<std::uint8_t>{42U}}) {
367 raw.data = data;
368 const DecodedFrameValue result = DbcDecoder().Decode(DecodeDatabase({definition}), raw);
369 QVERIFY(result.valid && result.signals.empty());
370 QCOMPARE(result.status, FrameDecodeStatus::Valid);
371 }
372 DecodeSignal signal;
373 signal.length = 8U;
374 definition.signals.push_back(signal);
375 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({definition}));
376}
377
378int RunDecoderTests(int argc, char **argv) {
379 DbcDecoderTest tests;
380 return QTest::qExec(&tests, argc, argv);
381}
382
383#include "dbc_decoder_test.moc"
Runtime CAN decoder using prebuilt decode database.
Definition dbc_decoder.h:84
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)
FrameDecodeStatus
Definition dbc_decoder.h:42
ByteOrder
Signal byte order used for runtime decoding.
ValueType
Signal numeric type.
Runtime-ready frame definition.
std::uint8_t dlc
std::uint32_t canId
Runtime-ready signal definition.
std::string name
ValueDescriptions valueDescriptions
ValueType valueType
std::uint32_t startBit
ByteOrder byteOrder
std::uint32_t length
Fully decoded frame.
Definition dbc_decoder.h:71
std::shared_ptr< const DecodeFrame > definition
Definition dbc_decoder.h:72
FrameDecodeStatus status
Definition dbc_decoder.h:76
One decoded signal value.
Definition dbc_decoder.h:52
RawSignalValue rawValue
Definition dbc_decoder.h:55
std::shared_ptr< const DecodeSignal > definition
Definition dbc_decoder.h:53
const std::string * GetValueDescription() const
Definition dbc_decoder.h:61
Stable frame identifier: normalized CAN ID and frame type.
Raw CAN frame used for runtime or trace decoding.
Definition dbc_decoder.h:24
std::vector< std::uint8_t > data
Definition dbc_decoder.h:27
std::uint32_t canId
Definition dbc_decoder.h:25