11 std::uint32_t startBit;
14 std::vector<std::uint8_t> data;
16 std::int64_t rawValue;
22Q_DECLARE_METATYPE(DecodeCase)
24class DbcDecoderTest :
public QObject {
30 void payloadPolicy_data();
32 void invalidDefinitions_data();
33 void invalidDefinitions();
34 void physicalConversion_data();
35 void physicalConversion();
36 void selectorConversionFailure();
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}};
61 for (
const DecodeCase &test: cases)
62 QTest::newRow(test.name) << test;
65void DbcDecoderTest::decode() {
66 QFETCH(DecodeCase, test);
68 signal.
name = test.name;
71 signal.
length = test.length;
73 signal.
factor = test.factor;
74 signal.
offset = test.offset;
77 definition.
canId = 0x123U;
79 definition.signals.push_back(signal);
84 frame.data = test.data;
86 QVERIFY2(result.
valid, test.name);
87 QCOMPARE(result.
definition.get(), &database.GetFrames()[0]);
88 QCOMPARE(result.signals.size(), std::size_t(1U));
90 QCOMPARE(decoded.
definition.get(), &database.GetFrames()[0].signals[0]);
91 QCOMPARE(decoded.
valid, test.valid);
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);
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));
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)}) {
117 signal.
startBit = order == ByteOrder::Intel ? 0U : 7U;
123 definition.signals.push_back(signal);
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));
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);
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)) *
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;
149 actualText << std::get<std::uint64_t>(decoded.
rawValue);
150 if (type == ValueType::Unsigned)
151 expectedText << bits;
153 expectedText << signedExpected;
154 QVERIFY(decoded.
valid);
155 QVERIFY(decoded.
rawValue == expected);
157 QCOMPARE(actualText.str(), expectedText.str());
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;
173void DbcDecoderTest::payloadPolicy() {
174 QFETCH(
unsigned, bytes);
176 for (
ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) {
178 definition.
canId = 17U;
181 signal.
name =
"First";
184 signal.
startBit = order == ByteOrder::Intel ? 0U : 7U;
185 definition.signals.push_back(signal);
186 signal.
name =
"Second";
188 definition.signals.push_back(signal);
192 raw.data.assign(bytes, 42U);
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) {
199 QCOMPARE(value.status, bytes > index ? SignalDecodeStatus::Valid : SignalDecodeStatus::InsufficientPayload);
200 QCOMPARE(value.
valid, bytes > index);
202 QCOMPARE(std::get<std::uint64_t>(value.
rawValue), std::uint64_t(42U));
206 raw.isExtended =
true;
208 QVERIFY(!unknown.
valid && !unknown.
definition && unknown.signals.empty());
209 QCOMPARE(unknown.
status, FrameDecodeStatus::UnknownFrame);
213 definition.signals.clear();
216 QVERIFY(empty.
valid && empty.signals.empty());
217 QCOMPARE(empty.
status, status);
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;
233void DbcDecoderTest::invalidDefinitions() {
235 for (
ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) {
242 good.
startBit = order == ByteOrder::Intel ? 0U : 7U;
246 case 0: bad.
length = 0U;
break;
247 case 1: bad.
length = 65U;
break;
249 case 3: bad.
startBit = std::numeric_limits<std::uint32_t>::max();
break;
252 case 6: bad.multiplexing.role =
static_cast<MultiplexRole
>(99);
break;
253 case 7: bad.multiplexing.role = MultiplexRole::Branch;
break;
255 definition.signals = {bad, good};
258 raw.
data = {0xFFU, 0xFFU};
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);
269 QCOMPARE(shortResult.
status, FrameDecodeStatus::InsufficientPayload);
270 QCOMPARE(shortResult.signals[0].
status, SignalDecodeStatus::InvalidDefinition);
271 QCOMPARE(shortResult.signals[1].
status, SignalDecodeStatus::InsufficientPayload);
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;
294void DbcDecoderTest::physicalConversion() {
295 QFETCH(
double, factor);
296 QFETCH(
double, offset);
297 QFETCH(
bool, finite);
298 for (
ValueType type: {ValueType::Signed, ValueType::Unsigned}) {
302 signal.
name =
"Scaled";
308 definition.signals.push_back(signal);
309 signal.
name =
"Good";
313 definition.signals.push_back(signal);
317 QVERIFY(result.
valid);
320 QCOMPARE(scaled.
valid, finite);
321 QCOMPARE(scaled.status, finite ? SignalDecodeStatus::Valid : SignalDecodeStatus::PhysicalConversionFailed);
323 if (type == ValueType::Signed)
324 QCOMPARE(std::get<std::int64_t>(scaled.
rawValue), std::int64_t(2));
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);
338void DbcDecoderTest::selectorConversionFailure() {
342 selector.
name =
"Selector";
344 selector.multiplexing.role = MultiplexRole::Selector;
345 selector.
factor = std::numeric_limits<double>::infinity();
347 branch.
name =
"Branch";
350 branch.multiplexing.role = MultiplexRole::Branch;
351 branch.multiplexing.value = 1U;
352 definition.signals = {branch, selector};
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));
363 definition.signals = {branch, selector};
365 QCOMPARE(inactive.
status, FrameDecodeStatus::Valid);
366 QCOMPARE(inactive.signals[0].
status, SignalDecodeStatus::Inactive);
369void DbcDecoderTest::zeroDlc() {
372 for (
const std::vector<std::uint8_t> &data: {std::vector<std::uint8_t>(), std::vector<std::uint8_t>{42U}}) {
375 QVERIFY(result.
valid && result.signals.empty());
376 QCOMPARE(result.
status, FrameDecodeStatus::Valid);
380 definition.signals.push_back(signal);
382 QCOMPARE(invalid.
status, FrameDecodeStatus::SignalErrors);
383 QCOMPARE(invalid.signals[0].
status, SignalDecodeStatus::InvalidDefinition);
386int RunDecoderTests(
int argc,
char **argv) {
387 DbcDecoderTest tests;
388 return QTest::qExec(&tests, argc, argv);
391#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