12 std::uint32_t startBit;
15 std::vector<std::uint8_t> data;
17 std::int64_t rawValue;
23Q_DECLARE_METATYPE(DecodeCase)
25class DbcDecoderTest :
public QObject {
31 void payloadPolicy_data();
33 void invalidDefinitions_data();
34 void invalidDefinitions();
35 void physicalConversion_data();
36 void physicalConversion();
37 void selectorConversionFailure();
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}};
62 for (
const DecodeCase &test: cases)
63 QTest::newRow(test.name) << test;
66void DbcDecoderTest::decode() {
67 QFETCH(DecodeCase, test);
69 signal.
name = test.name;
72 signal.
length = test.length;
74 signal.
factor = test.factor;
75 signal.
offset = test.offset;
78 definition.
canId = 0x123U;
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}));
89 frame.data = test.data;
91 QVERIFY2(result.
valid, test.name);
92 QCOMPARE(result.
definition.get(), &database.GetFrames()[0]);
93 QCOMPARE(result.signals.size(), std::size_t(1U));
95 QCOMPARE(decoded.
definition.get(), &database.GetFrames()[0].signals[0]);
96 QCOMPARE(decoded.
valid, test.valid);
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);
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));
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)}) {
122 signal.
startBit = order == ByteOrder::Intel ? 0U : 7U;
128 definition.signals.push_back(signal);
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));
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);
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)) *
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;
154 actualText << std::get<std::uint64_t>(decoded.
rawValue);
155 if (type == ValueType::Unsigned)
156 expectedText << bits;
158 expectedText << signedExpected;
159 QVERIFY(decoded.
valid);
160 QVERIFY(decoded.
rawValue == expected);
162 QCOMPARE(actualText.str(), expectedText.str());
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;
178void DbcDecoderTest::payloadPolicy() {
179 QFETCH(
unsigned, bytes);
181 for (
ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) {
183 definition.
canId = 17U;
186 signal.
name =
"First";
189 signal.
startBit = order == ByteOrder::Intel ? 0U : 7U;
190 definition.signals.push_back(signal);
191 signal.
name =
"Second";
193 definition.signals.push_back(signal);
197 raw.data.assign(bytes, 42U);
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) {
205 QCOMPARE(value.
valid, bytes > index);
207 QCOMPARE(std::get<std::uint64_t>(value.
rawValue), std::uint64_t(42U));
211 raw.isExtended =
true;
213 QVERIFY(!unknown.
valid && !unknown.
definition && unknown.signals.empty());
214 QCOMPARE(unknown.
status, FrameDecodeStatus::UnknownFrame);
218 definition.signals.clear();
221 QVERIFY(empty.
valid && empty.signals.empty());
222 QCOMPARE(empty.
status, status);
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;
238void DbcDecoderTest::invalidDefinitions() {
240 for (
ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) {
247 good.
startBit = order == ByteOrder::Intel ? 0U : 7U;
251 case 0: bad.
length = 0U;
break;
252 case 1: bad.
length = 65U;
break;
254 case 3: bad.
startBit = std::numeric_limits<std::uint32_t>::max();
break;
260 definition.signals = {bad, good};
261 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (
void)
DecodeDatabase({definition}));
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;
284void DbcDecoderTest::physicalConversion() {
285 QFETCH(
double, factor);
286 QFETCH(
double, offset);
287 QFETCH(
bool, finite);
288 for (
ValueType type: {ValueType::Signed, ValueType::Unsigned}) {
292 signal.
name =
"Scaled";
298 definition.signals.push_back(signal);
299 signal.
name =
"Good";
303 definition.signals.push_back(signal);
306 if (!std::isfinite(factor) || !std::isfinite(offset)) {
307 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (
void)
DecodeDatabase({definition}));
311 QVERIFY(result.
valid);
314 QCOMPARE(scaled.
valid, finite);
317 if (type == ValueType::Signed)
318 QCOMPARE(std::get<std::int64_t>(scaled.
rawValue), std::int64_t(2));
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);
332void DbcDecoderTest::selectorConversionFailure() {
336 selector.
name =
"Selector";
339 selector.
factor = std::numeric_limits<double>::max();
341 branch.
name =
"Branch";
346 definition.signals = {branch, selector};
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));
357 definition.signals = {branch, selector};
359 QCOMPARE(inactive.
status, FrameDecodeStatus::Valid);
360 QCOMPARE(inactive.signals[0].
status, SignalDecodeStatus::Inactive);
363void DbcDecoderTest::zeroDlc() {
366 for (
const std::vector<std::uint8_t> &data: {std::vector<std::uint8_t>(), std::vector<std::uint8_t>{42U}}) {
369 QVERIFY(result.
valid && result.signals.empty());
370 QCOMPARE(result.
status, FrameDecodeStatus::Valid);
374 definition.signals.push_back(signal);
375 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (
void)
DecodeDatabase({definition}));
378int RunDecoderTests(
int argc,
char **argv) {
379 DbcDecoderTest tests;
380 return QTest::qExec(&tests, argc, argv);
383#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...
CAN frame decoding API and result types.
SignalDecodeStatus
Outcome of signal extraction and physical conversion.
ByteOrder
Signal byte order used for runtime decoding.
ValueType
Signal numeric type.
Runtime-ready frame definition.
Runtime-ready signal definition.
ValueDescriptions valueDescriptions
Multiplexing multiplexing
std::shared_ptr< const DecodeFrame > definition
One decoded signal value.
std::shared_ptr< const DecodeSignal > definition
SignalDecodeStatus status
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