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
8struct DecodeCase {
9 const char *name;
10 ByteOrder byteOrder;
11 std::uint32_t startBit;
12 std::uint32_t length;
13 ValueType valueType;
14 std::vector<std::uint8_t> data;
15 bool valid;
16 std::int64_t rawValue;
17 double factor;
18 double offset;
19 double physicalValue;
20};
21
22Q_DECLARE_METATYPE(DecodeCase)
23
24class DbcDecoderTest : public QObject {
25 Q_OBJECT
26private Q_SLOTS:
27 void decode_data();
28 void decode();
29 void boundaries();
30 void payloadPolicy_data();
31 void payloadPolicy();
32 void invalidDefinitions_data();
33 void invalidDefinitions();
34 void physicalConversion_data();
35 void physicalConversion();
36 void selectorConversionFailure();
37 void zeroDlc();
38};
39
40void DbcDecoderTest::decode_data() {
41 QTest::addColumn<DecodeCase>("test");
42 const DecodeCase cases[] = {
43 {"Motorola reproduction", ByteOrder::Motorola, 7U, 16U, ValueType::Unsigned, {0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.0},
44 {"Motorola within byte", ByteOrder::Motorola, 5U, 4U, ValueType::Unsigned, {0xAC}, true, 11, 1.0, 0.0, 11.0},
45 {"Motorola unaligned crossing", ByteOrder::Motorola, 3U, 12U, ValueType::Unsigned, {0xAB, 0xCD}, true, 0xBCD, 1.0, 0.0, 3021.0},
46 {"Motorola starts at bit zero", ByteOrder::Motorola, 0U, 2U, ValueType::Unsigned, {0x01, 0x80}, true, 3, 1.0, 0.0, 3.0},
47 {"Motorola second byte", ByteOrder::Motorola, 15U, 16U, ValueType::Unsigned, {0xFF, 0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.0},
48 {"Motorola signed scaled", ByteOrder::Motorola, 7U, 16U, ValueType::Signed, {0xFF, 0xFE}, true, -2, 0.5, 10.0, 9.0},
49 {"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},
50 {"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},
51 {"Motorola short payload", ByteOrder::Motorola, 7U, 16U, ValueType::Unsigned, {0x12}, false, 0, 1.0, 0.0, 0.0},
52 {"Motorola empty payload", ByteOrder::Motorola, 7U, 8U, ValueType::Unsigned, {}, false, 0, 1.0, 0.0, 0.0},
53 {"Motorola large start", ByteOrder::Motorola, std::numeric_limits<std::uint32_t>::max(), 8U, ValueType::Unsigned, {0xFF}, false, 0, 1.0, 0.0, 0.0},
54 {"Motorola zero length", ByteOrder::Motorola, 7U, 0U, ValueType::Unsigned, {0xFF}, false, 0, 1.0, 0.0, 0.0},
55 {"Motorola excessive length", ByteOrder::Motorola, 7U, 65U, ValueType::Unsigned,
56 std::vector<std::uint8_t>(9U, 0xFF), false, 0, 1.0, 0.0, 0.0},
57 {"Intel regression", ByteOrder::Intel, 0U, 16U, ValueType::Unsigned, {0x12, 0x34}, true, 0x3412, 1.0, 0.0, 13330.0},
58 {"Intel signed regression", ByteOrder::Intel, 0U, 16U, ValueType::Signed, {0xFE, 0xFF}, true, -2, 0.5, 10.0, 9.0},
59 {"Intel short payload", ByteOrder::Intel, 0U, 16U, ValueType::Unsigned, {0x12}, false, 0, 1.0, 0.0, 0.0}};
60
61 for (const DecodeCase &test: cases)
62 QTest::newRow(test.name) << test;
63}
64
65void DbcDecoderTest::decode() {
66 QFETCH(DecodeCase, test);
67 DecodeSignal signal;
68 signal.name = test.name;
69 signal.byteOrder = test.byteOrder;
70 signal.startBit = test.startBit;
71 signal.length = test.length;
72 signal.valueType = test.valueType;
73 signal.factor = test.factor;
74 signal.offset = test.offset;
75
76 DecodeFrame definition;
77 definition.canId = 0x123U;
78 definition.dlc = 8U;
79 definition.signals.push_back(signal);
80 const DecodeDatabase database({definition});
81
82 RawCanFrame frame;
83 frame.canId = definition.canId;
84 frame.data = test.data;
85 const DecodedFrameValue result = DbcDecoder().Decode(database, frame);
86 QVERIFY2(result.valid, test.name);
87 QCOMPARE(result.definition.get(), &database.GetFrames()[0]);
88 QCOMPARE(result.signals.size(), std::size_t(1U));
89 const DecodedSignalValue &decoded = result.signals[0];
90 QCOMPARE(decoded.definition.get(), &database.GetFrames()[0].signals[0]);
91 QCOMPARE(decoded.valid, test.valid);
92 if (!test.valid)
93 return;
94
95 if (test.valueType == ValueType::Signed) {
96 const std::int64_t *raw = std::get_if<std::int64_t>(&decoded.rawValue);
97 QVERIFY(raw != nullptr);
98 QCOMPARE(*raw, test.rawValue);
99 } else {
100 const std::uint64_t *raw = std::get_if<std::uint64_t>(&decoded.rawValue);
101 QVERIFY(raw != nullptr);
102 QCOMPARE(*raw, static_cast<std::uint64_t>(test.rawValue));
103 }
104 QCOMPARE(decoded.physicalValue, test.physicalValue);
105}
106
107void DbcDecoderTest::boundaries() {
108 for (ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) {
109 for (ValueType type: {ValueType::Unsigned, ValueType::Signed}) {
110 for (std::uint64_t bits: {std::uint64_t(0U), std::uint64_t(0x7FFFFFFFFFFFFFFFULL),
111 std::uint64_t(0x8000000000000000ULL),
112 std::uint64_t(0x8000000000000001ULL),
113 std::uint64_t(0xFFFFFFFFFFFFFFFFULL)}) {
114 DecodeFrame definition;
115 definition.dlc = 8U;
116 DecodeSignal signal;
117 signal.startBit = order == ByteOrder::Intel ? 0U : 7U;
118 signal.length = 64U;
119 signal.byteOrder = order;
120 signal.valueType = type;
121 signal.factor = 0.5;
122 signal.offset = 10.0;
123 definition.signals.push_back(signal);
124 const DecodeDatabase database({definition});
125 RawCanFrame frame;
126 for (unsigned byte = 0U; byte < 8U; ++byte) {
127 const unsigned shift = (order == ByteOrder::Intel ? byte : 7U - byte) * 8U;
128 frame.data.push_back(static_cast<std::uint8_t>(bits >> shift));
129 }
130 const DecodedFrameValue result = DbcDecoder().Decode(database, frame);
131 const std::int64_t signedExpected = bits <= 0x7FFFFFFFFFFFFFFFULL
132 ? static_cast<std::int64_t>(bits)
133 : std::numeric_limits<std::int64_t>::min() +
134 static_cast<std::int64_t>(bits - 0x8000000000000000ULL);
135 const DecodedSignalValue &decoded = result.signals.at(0U);
136 const RawSignalValue expected = type == ValueType::Unsigned
137 ? std::variant<std::int64_t, std::uint64_t>(bits)
138 : std::variant<std::int64_t, std::uint64_t>(signedExpected);
139 const double physical = (type == ValueType::Unsigned
140 ? static_cast<double>(bits)
141 : static_cast<double>(signedExpected)) *
142 0.5 +
143 10.0;
144 std::ostringstream actualText;
145 std::ostringstream expectedText;
146 if (const std::int64_t *signedValue = std::get_if<std::int64_t>(&decoded.rawValue))
147 actualText << *signedValue;
148 else
149 actualText << std::get<std::uint64_t>(decoded.rawValue);
150 if (type == ValueType::Unsigned)
151 expectedText << bits;
152 else
153 expectedText << signedExpected;
154 QVERIFY(decoded.valid);
155 QVERIFY(decoded.rawValue == expected);
156 QCOMPARE(decoded.physicalValue, physical);
157 QCOMPARE(actualText.str(), expectedText.str());
158 }
159 }
160 }
161}
162
163void DbcDecoderTest::payloadPolicy_data() {
164 QTest::addColumn<unsigned>("bytes");
165 QTest::addColumn<FrameDecodeStatus>("status");
166 QTest::newRow("empty") << 0U << FrameDecodeStatus::InsufficientPayload;
167 QTest::newRow("partial") << 1U << FrameDecodeStatus::InsufficientPayload;
168 QTest::newRow("short-with-all-signals") << 2U << FrameDecodeStatus::InsufficientPayload;
169 QTest::newRow("exact") << 3U << FrameDecodeStatus::Valid;
170 QTest::newRow("long") << 4U << FrameDecodeStatus::Valid;
171}
172
173void DbcDecoderTest::payloadPolicy() {
174 QFETCH(unsigned, bytes);
175 QFETCH(FrameDecodeStatus, status);
176 for (ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) {
177 DecodeFrame definition;
178 definition.canId = 17U;
179 definition.dlc = 3U;
180 DecodeSignal signal;
181 signal.name = "First";
182 signal.length = 8U;
183 signal.byteOrder = order;
184 signal.startBit = order == ByteOrder::Intel ? 0U : 7U;
185 definition.signals.push_back(signal);
186 signal.name = "Second";
187 signal.startBit += 8U;
188 definition.signals.push_back(signal);
189 const DecodeDatabase database({definition});
190 RawCanFrame raw;
191 raw.canId = 17U;
192 raw.data.assign(bytes, 42U);
193 const DecodedFrameValue result = DbcDecoder().Decode(database, raw);
194 QVERIFY(result.valid);
195 QCOMPARE(result.status, status);
196 QCOMPARE(result.signals.size(), std::size_t(2U));
197 for (std::size_t index = 0U; index < result.signals.size(); ++index) {
198 const DecodedSignalValue &value = result.signals[index];
199 QCOMPARE(value.status, bytes > index ? SignalDecodeStatus::Valid : SignalDecodeStatus::InsufficientPayload);
200 QCOMPARE(value.valid, bytes > index);
201 if (value.valid) {
202 QCOMPARE(std::get<std::uint64_t>(value.rawValue), std::uint64_t(42U));
203 QCOMPARE(value.physicalValue, 42.0);
204 }
205 }
206 raw.isExtended = true;
207 const DecodedFrameValue unknown = DbcDecoder().Decode(database, raw);
208 QVERIFY(!unknown.valid && !unknown.definition && unknown.signals.empty());
209 QCOMPARE(unknown.status, FrameDecodeStatus::UnknownFrame);
210 QVERIFY(unknown.key == FrameKey(17U, true));
211 QCOMPARE(DbcDecoder().Decode(DecodeDatabase(), raw).status, FrameDecodeStatus::UnknownFrame);
212
213 definition.signals.clear();
214 raw.isExtended = false;
215 const DecodedFrameValue empty = DbcDecoder().Decode(DecodeDatabase({definition}), raw);
216 QVERIFY(empty.valid && empty.signals.empty());
217 QCOMPARE(empty.status, status);
218 }
219}
220
221void DbcDecoderTest::invalidDefinitions_data() {
222 QTest::addColumn<int>("kind");
223 QTest::newRow("zero-length") << 0;
224 QTest::newRow("65-bits") << 1;
225 QTest::newRow("outside-dlc") << 2;
226 QTest::newRow("start-overflow") << 3;
227 QTest::newRow("invalid-byte-order") << 4;
228 QTest::newRow("invalid-value-type") << 5;
229 QTest::newRow("invalid-multiplex-role") << 6;
230 QTest::newRow("missing-selector") << 7;
231}
232
233void DbcDecoderTest::invalidDefinitions() {
234 QFETCH(int, kind);
235 for (ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) {
236 DecodeFrame definition;
237 definition.dlc = 1U;
238 DecodeSignal good;
239 good.name = "Good";
240 good.length = 4U;
241 good.byteOrder = order;
242 good.startBit = order == ByteOrder::Intel ? 0U : 7U;
243 DecodeSignal bad = good;
244 bad.name = "Bad";
245 switch (kind) {
246 case 0: bad.length = 0U; break;
247 case 1: bad.length = 65U; break;
248 case 2: bad.startBit += 8U; break;
249 case 3: bad.startBit = std::numeric_limits<std::uint32_t>::max(); break;
250 case 4: bad.byteOrder = static_cast<ByteOrder>(99); break;
251 case 5: bad.valueType = static_cast<ValueType>(99); break;
252 case 6: bad.multiplexing.role = static_cast<MultiplexRole>(99); break;
253 case 7: bad.multiplexing.role = MultiplexRole::Branch; break;
254 }
255 definition.signals = {bad, good};
256 const DecodeDatabase database({definition});
257 RawCanFrame raw;
258 raw.data = {0xFFU, 0xFFU}; // Extra bytes must not rescue an invalid definition.
259 const DecodedFrameValue result = DbcDecoder().Decode(database, raw);
260 QVERIFY(result.valid);
261 QCOMPARE(result.status, FrameDecodeStatus::SignalErrors);
262 QCOMPARE(result.signals[0].status, SignalDecodeStatus::InvalidDefinition);
263 QVERIFY(!result.signals[0].valid && !result.signals[0].GetValueDescription());
264 QCOMPARE(result.signals[1].status, SignalDecodeStatus::Valid);
265 QCOMPARE(std::get<std::uint64_t>(result.signals[1].rawValue), std::uint64_t(15U));
266 QCOMPARE(result.signals[1].physicalValue, 15.0);
267 raw.data.clear();
268 const DecodedFrameValue shortResult = DbcDecoder().Decode(database, raw);
269 QCOMPARE(shortResult.status, FrameDecodeStatus::InsufficientPayload);
270 QCOMPARE(shortResult.signals[0].status, SignalDecodeStatus::InvalidDefinition);
271 QCOMPARE(shortResult.signals[1].status, SignalDecodeStatus::InsufficientPayload);
272 }
273}
274
275void DbcDecoderTest::physicalConversion_data() {
276 QTest::addColumn<double>("factor");
277 QTest::addColumn<double>("offset");
278 QTest::addColumn<bool>("finite");
279 const double infinity = std::numeric_limits<double>::infinity();
280 const double nan = std::numeric_limits<double>::quiet_NaN();
281 const double maximum = std::numeric_limits<double>::max();
282 QTest::newRow("nan-factor") << nan << 0.0 << false;
283 QTest::newRow("nan-offset") << 1.0 << nan << false;
284 QTest::newRow("infinite-factor") << infinity << 0.0 << false;
285 QTest::newRow("negative-infinite-factor") << -infinity << 0.0 << false;
286 QTest::newRow("infinite-offset") << 1.0 << infinity << false;
287 QTest::newRow("negative-infinite-offset") << 1.0 << -infinity << false;
288 QTest::newRow("multiply-overflow") << maximum << 0.0 << false;
289 QTest::newRow("add-overflow") << maximum / 2.0 << maximum << false;
290 QTest::newRow("finite-limit") << maximum / 2.0 << 0.0 << true;
291 QTest::newRow("zero-factor") << 0.0 << 42.0 << true;
292}
293
294void DbcDecoderTest::physicalConversion() {
295 QFETCH(double, factor);
296 QFETCH(double, offset);
297 QFETCH(bool, finite);
298 for (ValueType type: {ValueType::Signed, ValueType::Unsigned}) {
299 DecodeFrame definition;
300 definition.dlc = 1U;
301 DecodeSignal signal;
302 signal.name = "Scaled";
303 signal.length = 4U;
304 signal.valueType = type;
305 signal.factor = factor;
306 signal.offset = offset;
307 signal.valueDescriptions.emplace(std::uint64_t(2U), "Two");
308 definition.signals.push_back(signal);
309 signal.name = "Good";
310 signal.startBit = 4U;
311 signal.factor = 1.0;
312 signal.offset = 0.0;
313 definition.signals.push_back(signal);
314 RawCanFrame raw;
315 raw.data = {0x32U};
316 const DecodedFrameValue result = DbcDecoder().Decode(DecodeDatabase({definition}), raw);
317 QVERIFY(result.valid);
318 QCOMPARE(result.status, finite ? FrameDecodeStatus::Valid : FrameDecodeStatus::SignalErrors);
319 const DecodedSignalValue &scaled = result.signals[0];
320 QCOMPARE(scaled.valid, finite);
321 QCOMPARE(scaled.status, finite ? SignalDecodeStatus::Valid : SignalDecodeStatus::PhysicalConversionFailed);
322 QCOMPARE(std::isfinite(scaled.physicalValue), finite);
323 if (type == ValueType::Signed)
324 QCOMPARE(std::get<std::int64_t>(scaled.rawValue), std::int64_t(2));
325 else
326 QCOMPARE(std::get<std::uint64_t>(scaled.rawValue), std::uint64_t(2));
327 QVERIFY(result.signals[1].valid);
328 QCOMPARE(result.signals[1].physicalValue, 3.0);
329 if (finite) {
330 QVERIFY(scaled.GetValueDescription());
331 QCOMPARE(*scaled.GetValueDescription(), std::string("Two"));
332 } else {
333 QVERIFY(!scaled.GetValueDescription());
334 }
335 }
336}
337
338void DbcDecoderTest::selectorConversionFailure() {
339 DecodeFrame definition;
340 definition.dlc = 1U;
341 DecodeSignal selector;
342 selector.name = "Selector";
343 selector.length = 1U;
344 selector.multiplexing.role = MultiplexRole::Selector;
345 selector.factor = std::numeric_limits<double>::infinity();
346 DecodeSignal branch;
347 branch.name = "Branch";
348 branch.startBit = 1U;
349 branch.length = 7U;
350 branch.multiplexing.role = MultiplexRole::Branch;
351 branch.multiplexing.value = 1U;
352 definition.signals = {branch, selector};
353 RawCanFrame raw;
354 raw.data = {85U};
355 const DecodedFrameValue result = DbcDecoder().Decode(DecodeDatabase({definition}), raw);
356 QCOMPARE(result.status, FrameDecodeStatus::SignalErrors);
357 QVERIFY(result.signals[0].valid);
358 QCOMPARE(result.signals[0].physicalValue, 42.0);
359 QCOMPARE(result.signals[1].status, SignalDecodeStatus::PhysicalConversionFailed);
360 QCOMPARE(std::get<std::uint64_t>(result.signals[1].rawValue), std::uint64_t(1U));
361 raw.data = {84U};
362 selector.factor = 1.0;
363 definition.signals = {branch, selector};
364 const DecodedFrameValue inactive = DbcDecoder().Decode(DecodeDatabase({definition}), raw);
365 QCOMPARE(inactive.status, FrameDecodeStatus::Valid);
366 QCOMPARE(inactive.signals[0].status, SignalDecodeStatus::Inactive);
367}
368
369void DbcDecoderTest::zeroDlc() {
370 DecodeFrame definition;
371 RawCanFrame raw;
372 for (const std::vector<std::uint8_t> &data: {std::vector<std::uint8_t>(), std::vector<std::uint8_t>{42U}}) {
373 raw.data = data;
374 const DecodedFrameValue result = DbcDecoder().Decode(DecodeDatabase({definition}), raw);
375 QVERIFY(result.valid && result.signals.empty());
376 QCOMPARE(result.status, FrameDecodeStatus::Valid);
377 }
378 DecodeSignal signal;
379 signal.length = 8U;
380 definition.signals.push_back(signal);
381 const DecodedFrameValue invalid = DbcDecoder().Decode(DecodeDatabase({definition}), raw);
382 QCOMPARE(invalid.status, FrameDecodeStatus::SignalErrors);
383 QCOMPARE(invalid.signals[0].status, SignalDecodeStatus::InvalidDefinition);
384}
385
386int RunDecoderTests(int argc, char **argv) {
387 DbcDecoderTest tests;
388 return QTest::qExec(&tests, argc, argv);
389}
390
391#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