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