DBC Framework
DBC parsing and CAN signal decoding framework
Loading...
Searching...
No Matches
dbc_validation_test.cpp
1// SPDX-License-Identifier: MIT
3#include "dbc_decoder.h"
4#include <QtTest>
5#include <limits>
6#include <stdexcept>
7#include <utility>
8
9namespace {
10 DecodeFrame ValidFrame() {
11 DecodeFrame frame;
12 frame.canId = 0x123U;
13 frame.dlc = 8U;
14 DecodeSignal signal;
15 signal.name = "Value";
16 signal.length = 8U;
17 frame.signals.push_back(signal);
18 return frame;
19 }
20
21 DbcDatabase ParsedDefinitions(const std::vector<DecodeFrame> &frames) {
22 DbcDatabase database;
23 for (const DecodeFrame &frame: frames) {
24 FrameInfo target;
25 target.canId = frame.canId;
26 target.isExtended = frame.isExtended;
27 target.dlc = frame.dlc;
28 for (const DecodeSignal &signal: frame.signals) {
29 SignalInfo copy;
30 copy.name = signal.name;
31 copy.startBit = signal.startBit;
32 copy.length = signal.length;
33 copy.isLittleEndian = signal.byteOrder == ByteOrder::Intel;
34 copy.isSigned = signal.valueType == ValueType::Signed;
35 copy.factor = signal.factor;
36 copy.offset = signal.offset;
37 copy.minimum = signal.minimum;
38 copy.maximum = signal.maximum;
39 copy.multiplexing = signal.multiplexing;
40 target.signals.push_back(copy);
41 }
42 database.frames.push_back(target);
43 }
44 return database;
45 }
46}// namespace
47
48class DbcValidationTest : public QObject {
49 Q_OBJECT
50private Q_SLOTS:
51 void invalidMetadata_data();
52 void invalidMetadata();
53 void numericParameters_data();
54 void numericParameters();
55 void validBoundaries();
56 void multiplexingBoundaries();
57 void independentSnapshot();
58 void failedReplacement();
59};
60
61void DbcValidationTest::invalidMetadata_data() {
62 QTest::addColumn<int>("kind");
63 QTest::newRow("standard-id") << 0;
64 QTest::newRow("extended-id") << 1;
65 QTest::newRow("encoded-id") << 2;
66 QTest::newRow("duplicate-frame") << 3;
67 QTest::newRow("duplicate-signal") << 4;
68 QTest::newRow("zero-length") << 5;
69 QTest::newRow("65-bits") << 6;
70 QTest::newRow("length-overflow") << 7;
71 QTest::newRow("intel-start-overflow") << 8;
72 QTest::newRow("motorola-start-overflow") << 9;
73 QTest::newRow("intel-outside-payload") << 10;
74 QTest::newRow("motorola-outside-payload") << 11;
75 QTest::newRow("zero-payload") << 12;
76 QTest::newRow("inverted-range") << 13;
77 QTest::newRow("byte-order") << 14;
78 QTest::newRow("value-type") << 15;
79 QTest::newRow("multiplex-role") << 16;
80 QTest::newRow("missing-selector") << 17;
81 QTest::newRow("signed-selector") << 18;
82 QTest::newRow("two-selectors") << 19;
83 QTest::newRow("branch-range") << 20;
84 QTest::newRow("unconditional-overlap") << 21;
85 QTest::newRow("mixed-order-overlap") << 22;
86 QTest::newRow("same-branch-overlap") << 23;
87 QTest::newRow("selector-branch-overlap") << 24;
88 QTest::newRow("maximum-can-id") << 25;
89}
90
91void DbcValidationTest::invalidMetadata() {
92 QFETCH(int, kind);
93 std::vector<DecodeFrame> frames = {ValidFrame()};
94 DecodeFrame &frame = frames[0];
95 DecodeSignal &signal = frame.signals[0];
96 DecodeSignal other = signal;
97 other.name = "Other";
98 switch (kind) {
99 case 0:
100 frame.canId = 0x800U;
101 break;
102 case 1:
103 frame.isExtended = true;
104 frame.canId = 0x20000000U;
105 break;
106 case 2:
107 frame.isExtended = true;
108 frame.canId = 0x80000123U;
109 break;
110 case 3:
111 frames.push_back(frame);
112 break;
113 case 4:
114 frame.signals.push_back(signal);
115 break;
116 case 5:
117 signal.length = 0U;
118 break;
119 case 6:
120 signal.length = 65U;
121 break;
122 case 7:
123 signal.length = std::numeric_limits<std::uint32_t>::max();
124 break;
125 case 8:
126 signal.startBit = std::numeric_limits<std::uint32_t>::max();
127 break;
128 case 9:
129 signal.byteOrder = ByteOrder::Motorola;
130 signal.startBit = std::numeric_limits<std::uint32_t>::max();
131 break;
132 case 10:
133 signal.startBit = 57U;
134 break;
135 case 11:
136 signal.byteOrder = ByteOrder::Motorola;
137 signal.startBit = 56U;
138 break;
139 case 12:
140 frame.dlc = 0U;
141 break;
142 case 13:
143 signal.minimum = 2.0;
144 signal.maximum = 1.0;
145 break;
146 case 14:
147 signal.byteOrder = static_cast<ByteOrder>(99);
148 break;
149 case 15:
150 signal.valueType = static_cast<ValueType>(99);
151 break;
152 case 16:
153 signal.multiplexing.role = static_cast<MultiplexRole>(99);
154 break;
155 case 17:
156 signal.multiplexing.role = MultiplexRole::Branch;
157 break;
158 case 18:
159 signal.multiplexing.role = MultiplexRole::Selector;
160 signal.valueType = ValueType::Signed;
161 break;
162 case 19:
163 signal.multiplexing.role = MultiplexRole::Selector;
164 other.multiplexing.role = MultiplexRole::Selector;
165 other.startBit = 8U;
166 frame.signals.push_back(other);
167 break;
168 case 20:
169 case 23:
170 case 24:
171 signal.multiplexing.role = MultiplexRole::Selector;
172 other.multiplexing.role = MultiplexRole::Branch;
173 other.startBit = kind == 24 ? 0U : 8U;
174 other.multiplexing.value = kind == 20 ? 256U : 1U;
175 frame.signals.push_back(other);
176 if (kind == 23) {
177 other.name = "Third";
178 frame.signals.push_back(other);
179 }
180 break;
181 case 21:
182 frame.signals.push_back(other);
183 break;
184 case 22:
185 other.byteOrder = ByteOrder::Motorola;
186 other.startBit = 7U;
187 frame.signals.push_back(other);
188 break;
189 case 25:
190 frame.canId = std::numeric_limits<std::uint32_t>::max();
191 break;
192 }
193 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase(frames));
194 // Parsed metadata uses bools for byte order and signedness, so invalid enum
195 // values exist only in direct runtime definitions.
196 if (kind != 14 && kind != 15)
197 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DbcDecodeBuilder().Build(ParsedDefinitions(frames)));
198}
199
200void DbcValidationTest::numericParameters_data() {
201 QTest::addColumn<int>("field");
202 QTest::addColumn<double>("value");
203 const double values[] = {std::numeric_limits<double>::quiet_NaN(),
204 std::numeric_limits<double>::infinity(),
205 -std::numeric_limits<double>::infinity()};
206 for (int field = 0; field < 4; ++field) {
207 for (int index = 0; index < 3; ++index) {
208 const QByteArray name = QByteArray::number(field) + "-" + QByteArray::number(index);
209 QTest::newRow(name.constData()) << field << values[index];
210 }
211 }
212}
213
214void DbcValidationTest::numericParameters() {
215 QFETCH(int, field);
216 QFETCH(double, value);
217 DecodeFrame frame = ValidFrame();
218 DecodeSignal &signal = frame.signals[0];
219 switch (field) {
220 case 0:
221 signal.factor = value;
222 break;
223 case 1:
224 signal.offset = value;
225 break;
226 case 2:
227 signal.minimum = value;
228 break;
229 case 3:
230 signal.maximum = value;
231 break;
232 }
233 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({frame}));
234 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DbcDecodeBuilder().Build(ParsedDefinitions({frame})));
235}
236
237void DbcValidationTest::validBoundaries() {
238 for (ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) {
239 for (std::uint8_t dlc: {std::uint8_t(8U), std::uint8_t(64U), std::uint8_t(255U)}) {
240 DecodeFrame standard = ValidFrame();
241 standard.canId = 0x7FFU;
242 standard.dlc = dlc;
243 DecodeSignal &signal = standard.signals[0];
244 signal.byteOrder = order;
245 signal.startBit = (std::uint32_t(dlc) - 8U) * 8U + (order == ByteOrder::Intel ? 0U : 7U);
246 signal.length = 64U;
247 signal.factor = -2.0;
248 signal.offset = std::numeric_limits<double>::max();
249 signal.minimum = -std::numeric_limits<double>::max();
250 signal.maximum = std::numeric_limits<double>::max();
251 DecodeFrame extended = standard;
252 extended.isExtended = true;
253 DecodeFrame maximum = extended;
254 maximum.canId = 0x1FFFFFFFU;
255 const std::vector<DecodeFrame> frames = {standard, extended, maximum};
256 const DecodeDatabase direct(frames);
257 const DecodeDatabase built = DbcDecodeBuilder().Build(ParsedDefinitions(frames));
258 for (const DecodeFrame &frame: frames) {
259 QVERIFY(direct.FindFrame(frame.GetKey()));
260 QVERIFY(built.FindSignal(SignalKey(frame.GetKey(), "Value")));
261 }
262 }
263 }
264 DecodeFrame empty;
265 empty.canId = 0U;
266 QVERIFY(DecodeDatabase({empty}).FindFrame(empty.GetKey()));
267 QVERIFY(DbcDecodeBuilder().Build(DbcDatabase()).GetFrames().empty());
268}
269
270void DbcValidationTest::multiplexingBoundaries() {
271 DecodeFrame frame = ValidFrame();
272 frame.dlc = 9U;
273 DecodeSignal &selector = frame.signals[0];
274 selector.length = 64U;
275 selector.multiplexing.role = MultiplexRole::Selector;
276 DecodeSignal first;
277 first.name = "First";
278 first.startBit = 64U;
279 first.length = 8U;
280 first.multiplexing.role = MultiplexRole::Branch;
281 first.multiplexing.value = std::numeric_limits<std::uint64_t>::max();
282 DecodeSignal second = first;
283 second.name = "Second";
284 second.byteOrder = ByteOrder::Motorola;
285 second.startBit = 71U;
286 second.multiplexing.value = 0U;
287 frame.signals.push_back(first);
288 frame.signals.push_back(second);
289 const DecodeDatabase direct({frame});
290 const DecodeDatabase built = DbcDecodeBuilder().Build(ParsedDefinitions({frame}));
291 RawCanFrame raw;
292 raw.canId = frame.canId;
293 raw.data.assign(9U, 255U);
294 for (const DecodeDatabase &database: {direct, built}) {
295 const DecodedFrameValue result = DbcDecoder().Decode(database, raw);
296 QCOMPARE(result.signals[1].status, SignalDecodeStatus::Valid);
297 QCOMPARE(result.signals[2].status, SignalDecodeStatus::Inactive);
298 }
299}
300
301void DbcValidationTest::independentSnapshot() {
302 std::vector<DecodeFrame> frames = {ValidFrame()};
303 DecodeFrame &alias = frames[0];
304 DecodeSignal &signalAlias = alias.signals[0];
305 signalAlias.valueDescriptions.emplace(std::uint64_t(1U), "One");
306 std::string &descriptionAlias = signalAlias.valueDescriptions.begin()->second;
307 const FrameKey key = alias.GetKey();
308 const DecodeDatabase database(std::move(frames));
309 alias.canId = 456U;
310 signalAlias.name = "Changed";
311 signalAlias.startBit = std::numeric_limits<std::uint32_t>::max();
312 descriptionAlias = "Changed";
313 const std::shared_ptr<const DecodeFrame> saved = database.FindFrame(key);
314 QVERIFY(saved);
315 QCOMPARE(saved->canId, key.canId);
316 QVERIFY(!database.FindFrame(FrameKey(456U, false)));
317 const std::shared_ptr<const DecodeSignal> signal = database.FindSignal(SignalKey(key, "Value"));
318 QVERIFY(signal);
319 QCOMPARE(signal->startBit, 0U);
320 QCOMPARE(signal->valueDescriptions.begin()->second, std::string("One"));
321}
322
323void DbcValidationTest::failedReplacement() {
324 const DecodeFrame valid = ValidFrame();
325 DecodeDatabase database({valid});
326 const std::shared_ptr<const DecodeFrame> original = database.FindFrame(valid.GetKey());
327 DecodeFrame invalid = valid;
328 invalid.canId = 0x800U;
329 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, database = DecodeDatabase({invalid}));
330 QCOMPARE(database.FindFrame(valid.GetKey()).get(), original.get());
331 invalid = valid;
332 invalid.signals[0].factor = std::numeric_limits<double>::infinity();
333 QVERIFY_THROWS_EXCEPTION(std::invalid_argument,
334 database = DbcDecodeBuilder().Build(ParsedDefinitions({invalid})));
335 QCOMPARE(database.FindFrame(valid.GetKey()).get(), original.get());
336 QCOMPARE(database.GetFrames().size(), std::size_t(1U));
337 database = DecodeDatabase();
338 QVERIFY(database.GetFrames().empty());
339 QVERIFY(!database.FindFrame(valid.GetKey()));
340 QVERIFY(original);
341}
342
343int RunValidationTests(int argc, char **argv) {
344 DbcValidationTest tests;
345 return QTest::qExec(&tests, argc, argv);
346}
347
348#include "dbc_validation_test.moc"
Converts parsed DBC data into runtime decode database.
DecodeDatabase Build(const DbcDatabase &source) const
Build runtime decode database.
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)
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
ByteOrder
Signal byte order used for runtime decoding.
ValueType
Signal numeric type.
Parsed DBC content stored in a simple internal form.
std::vector< FrameInfo > frames
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
FrameDecodeStatus status
Definition dbc_decoder.h:76
Describes one CAN frame from a DBC file.
Definition frame_info.h:24
bool isExtended
Definition frame_info.h:27
std::uint8_t dlc
Definition frame_info.h:30
std::uint32_t canId
Definition frame_info.h:26
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
Describes one signal inside a DBC frame.
Definition signal_info.h:24
std::uint32_t length
Definition signal_info.h:28
std::string name
Definition signal_info.h:26
bool isSigned
Definition signal_info.h:30
double offset
Definition signal_info.h:32
std::uint32_t startBit
Definition signal_info.h:27
double minimum
Definition signal_info.h:33
double maximum
Definition signal_info.h:34
double factor
Definition signal_info.h:31
bool isLittleEndian
Definition signal_info.h:29