#include "dbc_decoder.h" #include #include #include #include #include struct DecodeCase { const char *name; ByteOrder byteOrder; std::uint32_t startBit; std::uint32_t length; ValueType valueType; std::vector data; bool valid; std::int64_t rawValue; double factor; double offset; double physicalValue; }; Q_DECLARE_METATYPE(DecodeCase) class DbcDecoderTest : public QObject { Q_OBJECT private Q_SLOTS: void decode_data(); void decode(); void boundaries(); void payloadPolicy_data(); void payloadPolicy(); void invalidDefinitions_data(); void invalidDefinitions(); void physicalConversion_data(); void physicalConversion(); void selectorConversionFailure(); void zeroDlc(); }; void DbcDecoderTest::decode_data() { QTest::addColumn("test"); const DecodeCase cases[] = { {"Motorola reproduction", ByteOrder::Motorola, 7U, 16U, ValueType::Unsigned, {0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.0}, {"Motorola within byte", ByteOrder::Motorola, 5U, 4U, ValueType::Unsigned, {0xAC}, true, 11, 1.0, 0.0, 11.0}, {"Motorola unaligned crossing", ByteOrder::Motorola, 3U, 12U, ValueType::Unsigned, {0xAB, 0xCD}, true, 0xBCD, 1.0, 0.0, 3021.0}, {"Motorola starts at bit zero", ByteOrder::Motorola, 0U, 2U, ValueType::Unsigned, {0x01, 0x80}, true, 3, 1.0, 0.0, 3.0}, {"Motorola second byte", ByteOrder::Motorola, 15U, 16U, ValueType::Unsigned, {0xFF, 0x12, 0x34}, true, 0x1234, 1.0, 0.0, 4660.0}, {"Motorola signed scaled", ByteOrder::Motorola, 7U, 16U, ValueType::Signed, {0xFF, 0xFE}, true, -2, 0.5, 10.0, 9.0}, {"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}, {"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}, {"Motorola short payload", ByteOrder::Motorola, 7U, 16U, ValueType::Unsigned, {0x12}, false, 0, 1.0, 0.0, 0.0}, {"Motorola empty payload", ByteOrder::Motorola, 7U, 8U, ValueType::Unsigned, {}, false, 0, 1.0, 0.0, 0.0}, {"Motorola large start", ByteOrder::Motorola, std::numeric_limits::max(), 8U, ValueType::Unsigned, {0xFF}, false, 0, 1.0, 0.0, 0.0}, {"Motorola zero length", ByteOrder::Motorola, 7U, 0U, ValueType::Unsigned, {0xFF}, false, 0, 1.0, 0.0, 0.0}, {"Motorola excessive length", ByteOrder::Motorola, 7U, 65U, ValueType::Unsigned, std::vector(9U, 0xFF), false, 0, 1.0, 0.0, 0.0}, {"Intel regression", ByteOrder::Intel, 0U, 16U, ValueType::Unsigned, {0x12, 0x34}, true, 0x3412, 1.0, 0.0, 13330.0}, {"Intel signed regression", ByteOrder::Intel, 0U, 16U, ValueType::Signed, {0xFE, 0xFF}, true, -2, 0.5, 10.0, 9.0}, {"Intel short payload", ByteOrder::Intel, 0U, 16U, ValueType::Unsigned, {0x12}, false, 0, 1.0, 0.0, 0.0}}; for (const DecodeCase &test: cases) QTest::newRow(test.name) << test; } void DbcDecoderTest::decode() { QFETCH(DecodeCase, test); DecodeSignal signal; signal.name = test.name; signal.byteOrder = test.byteOrder; signal.startBit = test.startBit; signal.length = test.length; signal.valueType = test.valueType; signal.factor = test.factor; signal.offset = test.offset; DecodeFrame definition; definition.canId = 0x123U; definition.dlc = 8U; definition.signals.push_back(signal); if (test.length == 0U || test.length > 64U || test.startBit > 64U) { QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({definition})); return; } const DecodeDatabase database({definition}); RawCanFrame frame; frame.canId = definition.canId; frame.data = test.data; const DecodedFrameValue result = DbcDecoder().Decode(database, frame); QVERIFY2(result.valid, test.name); QCOMPARE(result.definition.get(), &database.GetFrames()[0]); QCOMPARE(result.signals.size(), std::size_t(1U)); const DecodedSignalValue &decoded = result.signals[0]; QCOMPARE(decoded.definition.get(), &database.GetFrames()[0].signals[0]); QCOMPARE(decoded.valid, test.valid); if (!test.valid) return; if (test.valueType == ValueType::Signed) { const std::int64_t *raw = std::get_if(&decoded.rawValue); QVERIFY(raw != nullptr); QCOMPARE(*raw, test.rawValue); } else { const std::uint64_t *raw = std::get_if(&decoded.rawValue); QVERIFY(raw != nullptr); QCOMPARE(*raw, static_cast(test.rawValue)); } QCOMPARE(decoded.physicalValue, test.physicalValue); } void DbcDecoderTest::boundaries() { for (ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) { for (ValueType type: {ValueType::Unsigned, ValueType::Signed}) { for (std::uint64_t bits: {std::uint64_t(0U), std::uint64_t(0x7FFFFFFFFFFFFFFFULL), std::uint64_t(0x8000000000000000ULL), std::uint64_t(0x8000000000000001ULL), std::uint64_t(0xFFFFFFFFFFFFFFFFULL)}) { DecodeFrame definition; definition.dlc = 8U; DecodeSignal signal; signal.startBit = order == ByteOrder::Intel ? 0U : 7U; signal.length = 64U; signal.byteOrder = order; signal.valueType = type; signal.factor = 0.5; signal.offset = 10.0; definition.signals.push_back(signal); const DecodeDatabase database({definition}); RawCanFrame frame; for (unsigned byte = 0U; byte < 8U; ++byte) { const unsigned shift = (order == ByteOrder::Intel ? byte : 7U - byte) * 8U; frame.data.push_back(static_cast(bits >> shift)); } const DecodedFrameValue result = DbcDecoder().Decode(database, frame); const std::int64_t signedExpected = bits <= 0x7FFFFFFFFFFFFFFFULL ? static_cast(bits) : std::numeric_limits::min() + static_cast(bits - 0x8000000000000000ULL); const DecodedSignalValue &decoded = result.signals.at(0U); const RawSignalValue expected = type == ValueType::Unsigned ? std::variant(bits) : std::variant(signedExpected); const double physical = (type == ValueType::Unsigned ? static_cast(bits) : static_cast(signedExpected)) * 0.5 + 10.0; std::ostringstream actualText; std::ostringstream expectedText; if (const std::int64_t *signedValue = std::get_if(&decoded.rawValue)) actualText << *signedValue; else actualText << std::get(decoded.rawValue); if (type == ValueType::Unsigned) expectedText << bits; else expectedText << signedExpected; QVERIFY(decoded.valid); QVERIFY(decoded.rawValue == expected); QCOMPARE(decoded.physicalValue, physical); QCOMPARE(actualText.str(), expectedText.str()); } } } } void DbcDecoderTest::payloadPolicy_data() { QTest::addColumn("bytes"); QTest::addColumn("status"); QTest::newRow("empty") << 0U << FrameDecodeStatus::InsufficientPayload; QTest::newRow("partial") << 1U << FrameDecodeStatus::InsufficientPayload; QTest::newRow("short-with-all-signals") << 2U << FrameDecodeStatus::InsufficientPayload; QTest::newRow("exact") << 3U << FrameDecodeStatus::Valid; QTest::newRow("long") << 4U << FrameDecodeStatus::Valid; } void DbcDecoderTest::payloadPolicy() { QFETCH(unsigned, bytes); QFETCH(FrameDecodeStatus, status); for (ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) { DecodeFrame definition; definition.canId = 17U; definition.dlc = 3U; DecodeSignal signal; signal.name = "First"; signal.length = 8U; signal.byteOrder = order; signal.startBit = order == ByteOrder::Intel ? 0U : 7U; definition.signals.push_back(signal); signal.name = "Second"; signal.startBit += 8U; definition.signals.push_back(signal); const DecodeDatabase database({definition}); RawCanFrame raw; raw.canId = 17U; raw.data.assign(bytes, 42U); const DecodedFrameValue result = DbcDecoder().Decode(database, raw); QVERIFY(result.valid); QCOMPARE(result.status, status); QCOMPARE(result.signals.size(), std::size_t(2U)); for (std::size_t index = 0U; index < result.signals.size(); ++index) { const DecodedSignalValue &value = result.signals[index]; QCOMPARE(value.status, bytes > index ? SignalDecodeStatus::Valid : SignalDecodeStatus::InsufficientPayload); QCOMPARE(value.valid, bytes > index); if (value.valid) { QCOMPARE(std::get(value.rawValue), std::uint64_t(42U)); QCOMPARE(value.physicalValue, 42.0); } } raw.isExtended = true; const DecodedFrameValue unknown = DbcDecoder().Decode(database, raw); QVERIFY(!unknown.valid && !unknown.definition && unknown.signals.empty()); QCOMPARE(unknown.status, FrameDecodeStatus::UnknownFrame); QVERIFY(unknown.key == FrameKey(17U, true)); QCOMPARE(DbcDecoder().Decode(DecodeDatabase(), raw).status, FrameDecodeStatus::UnknownFrame); definition.signals.clear(); raw.isExtended = false; const DecodedFrameValue empty = DbcDecoder().Decode(DecodeDatabase({definition}), raw); QVERIFY(empty.valid && empty.signals.empty()); QCOMPARE(empty.status, status); } } void DbcDecoderTest::invalidDefinitions_data() { QTest::addColumn("kind"); QTest::newRow("zero-length") << 0; QTest::newRow("65-bits") << 1; QTest::newRow("outside-dlc") << 2; QTest::newRow("start-overflow") << 3; QTest::newRow("invalid-byte-order") << 4; QTest::newRow("invalid-value-type") << 5; QTest::newRow("invalid-multiplex-role") << 6; QTest::newRow("missing-selector") << 7; } void DbcDecoderTest::invalidDefinitions() { QFETCH(int, kind); for (ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) { DecodeFrame definition; definition.dlc = 1U; DecodeSignal good; good.name = "Good"; good.length = 4U; good.byteOrder = order; good.startBit = order == ByteOrder::Intel ? 0U : 7U; DecodeSignal bad = good; bad.name = "Bad"; switch (kind) { case 0: bad.length = 0U; break; case 1: bad.length = 65U; break; case 2: bad.startBit += 8U; break; case 3: bad.startBit = std::numeric_limits::max(); break; case 4: bad.byteOrder = static_cast(99); break; case 5: bad.valueType = static_cast(99); break; case 6: bad.multiplexing.role = static_cast(99); break; case 7: bad.multiplexing.role = MultiplexRole::Branch; break; } definition.signals = {bad, good}; QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({definition})); } } void DbcDecoderTest::physicalConversion_data() { QTest::addColumn("factor"); QTest::addColumn("offset"); QTest::addColumn("finite"); const double infinity = std::numeric_limits::infinity(); const double nan = std::numeric_limits::quiet_NaN(); const double maximum = std::numeric_limits::max(); QTest::newRow("nan-factor") << nan << 0.0 << false; QTest::newRow("nan-offset") << 1.0 << nan << false; QTest::newRow("infinite-factor") << infinity << 0.0 << false; QTest::newRow("negative-infinite-factor") << -infinity << 0.0 << false; QTest::newRow("infinite-offset") << 1.0 << infinity << false; QTest::newRow("negative-infinite-offset") << 1.0 << -infinity << false; QTest::newRow("multiply-overflow") << maximum << 0.0 << false; QTest::newRow("add-overflow") << maximum / 2.0 << maximum << false; QTest::newRow("finite-limit") << maximum / 2.0 << 0.0 << true; QTest::newRow("zero-factor") << 0.0 << 42.0 << true; } void DbcDecoderTest::physicalConversion() { QFETCH(double, factor); QFETCH(double, offset); QFETCH(bool, finite); for (ValueType type: {ValueType::Signed, ValueType::Unsigned}) { DecodeFrame definition; definition.dlc = 1U; DecodeSignal signal; signal.name = "Scaled"; signal.length = 4U; signal.valueType = type; signal.factor = factor; signal.offset = offset; signal.valueDescriptions.emplace(std::uint64_t(2U), "Two"); definition.signals.push_back(signal); signal.name = "Good"; signal.startBit = 4U; signal.factor = 1.0; signal.offset = 0.0; definition.signals.push_back(signal); RawCanFrame raw; raw.data = {0x32U}; if (!std::isfinite(factor) || !std::isfinite(offset)) { QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({definition})); continue; } const DecodedFrameValue result = DbcDecoder().Decode(DecodeDatabase({definition}), raw); QVERIFY(result.valid); QCOMPARE(result.status, finite ? FrameDecodeStatus::Valid : FrameDecodeStatus::SignalErrors); const DecodedSignalValue &scaled = result.signals[0]; QCOMPARE(scaled.valid, finite); QCOMPARE(scaled.status, finite ? SignalDecodeStatus::Valid : SignalDecodeStatus::PhysicalConversionFailed); QCOMPARE(std::isfinite(scaled.physicalValue), finite); if (type == ValueType::Signed) QCOMPARE(std::get(scaled.rawValue), std::int64_t(2)); else QCOMPARE(std::get(scaled.rawValue), std::uint64_t(2)); QVERIFY(result.signals[1].valid); QCOMPARE(result.signals[1].physicalValue, 3.0); if (finite) { QVERIFY(scaled.GetValueDescription()); QCOMPARE(*scaled.GetValueDescription(), std::string("Two")); } else { QVERIFY(!scaled.GetValueDescription()); } } } void DbcDecoderTest::selectorConversionFailure() { DecodeFrame definition; definition.dlc = 1U; DecodeSignal selector; selector.name = "Selector"; selector.length = 2U; selector.multiplexing.role = MultiplexRole::Selector; selector.factor = std::numeric_limits::max(); DecodeSignal branch; branch.name = "Branch"; branch.startBit = 2U; branch.length = 6U; branch.multiplexing.role = MultiplexRole::Branch; branch.multiplexing.value = 2U; definition.signals = {branch, selector}; RawCanFrame raw; raw.data = {170U}; const DecodedFrameValue result = DbcDecoder().Decode(DecodeDatabase({definition}), raw); QCOMPARE(result.status, FrameDecodeStatus::SignalErrors); QVERIFY(result.signals[0].valid); QCOMPARE(result.signals[0].physicalValue, 42.0); QCOMPARE(result.signals[1].status, SignalDecodeStatus::PhysicalConversionFailed); QCOMPARE(std::get(result.signals[1].rawValue), std::uint64_t(2U)); raw.data = {168U}; selector.factor = 1.0; definition.signals = {branch, selector}; const DecodedFrameValue inactive = DbcDecoder().Decode(DecodeDatabase({definition}), raw); QCOMPARE(inactive.status, FrameDecodeStatus::Valid); QCOMPARE(inactive.signals[0].status, SignalDecodeStatus::Inactive); } void DbcDecoderTest::zeroDlc() { DecodeFrame definition; RawCanFrame raw; for (const std::vector &data: {std::vector(), std::vector{42U}}) { raw.data = data; const DecodedFrameValue result = DbcDecoder().Decode(DecodeDatabase({definition}), raw); QVERIFY(result.valid && result.signals.empty()); QCOMPARE(result.status, FrameDecodeStatus::Valid); } DecodeSignal signal; signal.length = 8U; definition.signals.push_back(signal); QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({definition})); } int RunDecoderTests(int argc, char **argv) { DbcDecoderTest tests; return QTest::qExec(&tests, argc, argv); } #include "dbc_decoder_test.moc"