// SPDX-License-Identifier: MIT #include "dbc_decode_builder.h" #include "dbc_decoder.h" #include "signal_layout.h" #include #include #include #include namespace { DecodeFrame ValidFrame() { DecodeFrame frame; frame.canId = 0x123U; frame.dlc = 8U; DecodeSignal signal; signal.name = "Value"; signal.length = 8U; frame.signals.push_back(signal); return frame; } DbcDatabase ParsedDefinitions(const std::vector &frames) { DbcDatabase database; for (const DecodeFrame &frame: frames) { FrameInfo target; target.canId = frame.canId; target.isExtended = frame.isExtended; target.dlc = frame.dlc; for (const DecodeSignal &signal: frame.signals) { SignalInfo copy; copy.name = signal.name; copy.startBit = signal.startBit; copy.length = signal.length; copy.isLittleEndian = signal.byteOrder == ByteOrder::Intel; copy.isSigned = signal.valueType == ValueType::Signed; copy.factor = signal.factor; copy.offset = signal.offset; copy.minimum = signal.minimum; copy.maximum = signal.maximum; copy.multiplexing = signal.multiplexing; target.signals.push_back(copy); } database.frames.push_back(target); } return database; } }// namespace class DbcValidationTest : public QObject { Q_OBJECT private Q_SLOTS: void invalidMetadata_data(); void invalidMetadata(); void numericParameters_data(); void numericParameters(); void validBoundaries(); void layoutBoundaries(); void builderSnapshot(); void multiplexingBoundaries(); void independentSnapshot(); void failedReplacement(); }; void DbcValidationTest::invalidMetadata_data() { QTest::addColumn("kind"); QTest::newRow("standard-id") << 0; QTest::newRow("extended-id") << 1; QTest::newRow("encoded-id") << 2; QTest::newRow("duplicate-frame") << 3; QTest::newRow("duplicate-signal") << 4; QTest::newRow("zero-length") << 5; QTest::newRow("65-bits") << 6; QTest::newRow("length-overflow") << 7; QTest::newRow("intel-start-overflow") << 8; QTest::newRow("motorola-start-overflow") << 9; QTest::newRow("intel-outside-payload") << 10; QTest::newRow("motorola-outside-payload") << 11; QTest::newRow("zero-payload") << 12; QTest::newRow("inverted-range") << 13; QTest::newRow("byte-order") << 14; QTest::newRow("value-type") << 15; QTest::newRow("multiplex-role") << 16; QTest::newRow("missing-selector") << 17; QTest::newRow("signed-selector") << 18; QTest::newRow("two-selectors") << 19; QTest::newRow("branch-range") << 20; QTest::newRow("unconditional-overlap") << 21; QTest::newRow("mixed-order-overlap") << 22; QTest::newRow("same-branch-overlap") << 23; QTest::newRow("selector-branch-overlap") << 24; QTest::newRow("maximum-can-id") << 25; } void DbcValidationTest::invalidMetadata() { QFETCH(int, kind); std::vector frames = {ValidFrame()}; DecodeFrame &frame = frames[0]; DecodeSignal &signal = frame.signals[0]; DecodeSignal other = signal; other.name = "Other"; switch (kind) { case 0: frame.canId = 0x800U; break; case 1: frame.isExtended = true; frame.canId = 0x20000000U; break; case 2: frame.isExtended = true; frame.canId = 0x80000123U; break; case 3: frames.push_back(frame); break; case 4: frame.signals.push_back(signal); break; case 5: signal.length = 0U; break; case 6: signal.length = 65U; break; case 7: signal.length = std::numeric_limits::max(); break; case 8: signal.startBit = std::numeric_limits::max(); break; case 9: signal.byteOrder = ByteOrder::Motorola; signal.startBit = std::numeric_limits::max(); break; case 10: signal.startBit = 57U; break; case 11: signal.byteOrder = ByteOrder::Motorola; signal.startBit = 56U; break; case 12: frame.dlc = 0U; break; case 13: signal.minimum = 2.0; signal.maximum = 1.0; break; case 14: signal.byteOrder = static_cast(99); break; case 15: signal.valueType = static_cast(99); break; case 16: signal.multiplexing.role = static_cast(99); break; case 17: signal.multiplexing.role = MultiplexRole::Branch; break; case 18: signal.multiplexing.role = MultiplexRole::Selector; signal.valueType = ValueType::Signed; break; case 19: signal.multiplexing.role = MultiplexRole::Selector; other.multiplexing.role = MultiplexRole::Selector; other.startBit = 8U; frame.signals.push_back(other); break; case 20: case 23: case 24: signal.multiplexing.role = MultiplexRole::Selector; other.multiplexing.role = MultiplexRole::Branch; other.startBit = kind == 24 ? 0U : 8U; other.multiplexing.value = kind == 20 ? 256U : 1U; frame.signals.push_back(other); if (kind == 23) { other.name = "Third"; frame.signals.push_back(other); } break; case 21: frame.signals.push_back(other); break; case 22: other.byteOrder = ByteOrder::Motorola; other.startBit = 7U; frame.signals.push_back(other); break; case 25: frame.canId = std::numeric_limits::max(); break; } QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase(frames)); // Parsed metadata uses bools for byte order and signedness, so invalid enum // values exist only in direct runtime definitions. if (kind != 14 && kind != 15) QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DbcDecodeBuilder().Build(ParsedDefinitions(frames))); } void DbcValidationTest::numericParameters_data() { QTest::addColumn("field"); QTest::addColumn("value"); const double values[] = {std::numeric_limits::quiet_NaN(), std::numeric_limits::infinity(), -std::numeric_limits::infinity()}; for (int field = 0; field < 4; ++field) { for (int index = 0; index < 3; ++index) { const QByteArray name = QByteArray::number(field) + "-" + QByteArray::number(index); QTest::newRow(name.constData()) << field << values[index]; } } } void DbcValidationTest::numericParameters() { QFETCH(int, field); QFETCH(double, value); DecodeFrame frame = ValidFrame(); DecodeSignal &signal = frame.signals[0]; switch (field) { case 0: signal.factor = value; break; case 1: signal.offset = value; break; case 2: signal.minimum = value; break; case 3: signal.maximum = value; break; } QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({frame})); QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DbcDecodeBuilder().Build(ParsedDefinitions({frame}))); } void DbcValidationTest::validBoundaries() { for (ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) { for (std::uint8_t dlc: {std::uint8_t(8U), std::uint8_t(64U), std::uint8_t(255U)}) { DecodeFrame standard = ValidFrame(); standard.canId = 0x7FFU; standard.dlc = dlc; DecodeSignal &signal = standard.signals[0]; signal.byteOrder = order; signal.startBit = (std::uint32_t(dlc) - 8U) * 8U + (order == ByteOrder::Intel ? 0U : 7U); signal.length = 64U; signal.factor = -2.0; signal.offset = std::numeric_limits::max(); signal.minimum = -std::numeric_limits::max(); signal.maximum = std::numeric_limits::max(); DecodeFrame extended = standard; extended.isExtended = true; DecodeFrame maximum = extended; maximum.canId = 0x1FFFFFFFU; const std::vector frames = {standard, extended, maximum}; const DecodeDatabase direct(frames); const DecodeDatabase built = DbcDecodeBuilder().Build(ParsedDefinitions(frames)); for (const DecodeFrame &frame: frames) { QVERIFY(direct.FindFrame(frame.GetKey())); QVERIFY(built.FindSignal(SignalKey(frame.GetKey(), "Value"))); } } } DecodeFrame empty; empty.canId = 0U; QVERIFY(DecodeDatabase({empty}).FindFrame(empty.GetKey())); QVERIFY(DbcDecodeBuilder().Build(DbcDatabase()).GetFrames().empty()); } void DbcValidationTest::layoutBoundaries() { // Compare the shared coordinate calculation with independent DBC bit traversal. for (const std::uint8_t dlc: {std::uint8_t(0U), std::uint8_t(1U), std::uint8_t(8U), std::uint8_t(64U), std::uint8_t(255U)}) { const std::uint64_t payloadBits = std::uint64_t(dlc) * 8U; for (const bool littleEndian: {true, false}) { for (std::uint32_t start = 0U; start < payloadBits + 8U; ++start) { std::uint64_t bit = start; bool fits = true; QVERIFY(!IsValidSignalLayout(start, 0U, littleEndian, dlc)); for (std::uint32_t length = 1U; length <= 64U; ++length) { fits = fits && bit < payloadBits; QCOMPARE(IsValidSignalLayout(start, length, littleEndian, dlc), fits); if (littleEndian) ++bit; else bit = bit % 8U == 0U ? bit + 15U : bit - 1U; } QVERIFY(!IsValidSignalLayout(start, 65U, littleEndian, dlc)); } const std::uint32_t maximum = std::numeric_limits::max(); QVERIFY(!IsValidSignalLayout(maximum, 1U, littleEndian, dlc)); QVERIFY(!IsValidSignalLayout(maximum, maximum, littleEndian, dlc)); QVERIFY(!IsValidSignalLayout(0U, maximum, littleEndian, dlc)); } } } void DbcValidationTest::builderSnapshot() { DbcDatabase source = ParsedDefinitions({ValidFrame()}); source.frames[0].signals[0].valueDescriptions.emplace(std::uint64_t(1U), "One"); const FrameKey key(source.frames[0].canId, source.frames[0].isExtended); const DecodeDatabase database = DbcDecodeBuilder().Build(source); source.frames[0].canId = 0x800U; source.frames[0].signals[0].name = "Changed"; source.frames[0].signals[0].valueDescriptions.begin()->second = "Changed"; source.frames.clear(); const std::shared_ptr signal = database.FindSignal(SignalKey(key, "Value")); QVERIFY(signal); QCOMPARE(signal->valueDescriptions.begin()->second, std::string("One")); QVERIFY(database.FindFrame(key)); QVERIFY(!database.FindFrame(FrameKey(0x800U, false))); } void DbcValidationTest::multiplexingBoundaries() { DecodeFrame frame = ValidFrame(); frame.dlc = 9U; DecodeSignal &selector = frame.signals[0]; selector.length = 64U; selector.multiplexing.role = MultiplexRole::Selector; DecodeSignal first; first.name = "First"; first.startBit = 64U; first.length = 8U; first.multiplexing.role = MultiplexRole::Branch; first.multiplexing.value = std::numeric_limits::max(); DecodeSignal second = first; second.name = "Second"; second.byteOrder = ByteOrder::Motorola; second.startBit = 71U; second.multiplexing.value = 0U; frame.signals.push_back(first); frame.signals.push_back(second); const DecodeDatabase direct({frame}); const DecodeDatabase built = DbcDecodeBuilder().Build(ParsedDefinitions({frame})); RawCanFrame raw; raw.canId = frame.canId; raw.data.assign(9U, 255U); for (const DecodeDatabase &database: {direct, built}) { const DecodedFrameValue result = DbcDecoder().Decode(database, raw); QCOMPARE(result.signals[1].status, SignalDecodeStatus::Valid); QCOMPARE(result.signals[2].status, SignalDecodeStatus::Inactive); } } void DbcValidationTest::independentSnapshot() { std::vector frames = {ValidFrame()}; DecodeFrame &alias = frames[0]; DecodeSignal &signalAlias = alias.signals[0]; signalAlias.valueDescriptions.emplace(std::uint64_t(1U), "One"); std::string &descriptionAlias = signalAlias.valueDescriptions.begin()->second; const FrameKey key = alias.GetKey(); const DecodeDatabase database(std::move(frames)); alias.canId = 456U; signalAlias.name = "Changed"; signalAlias.startBit = std::numeric_limits::max(); descriptionAlias = "Changed"; const std::shared_ptr saved = database.FindFrame(key); QVERIFY(saved); QCOMPARE(saved->canId, key.canId); QVERIFY(!database.FindFrame(FrameKey(456U, false))); const std::shared_ptr signal = database.FindSignal(SignalKey(key, "Value")); QVERIFY(signal); QCOMPARE(signal->startBit, 0U); QCOMPARE(signal->valueDescriptions.begin()->second, std::string("One")); } void DbcValidationTest::failedReplacement() { const DecodeFrame valid = ValidFrame(); DecodeDatabase database({valid}); const std::shared_ptr original = database.FindFrame(valid.GetKey()); DecodeFrame invalid = valid; invalid.canId = 0x800U; QVERIFY_THROWS_EXCEPTION(std::invalid_argument, database = DecodeDatabase({invalid})); QCOMPARE(database.FindFrame(valid.GetKey()).get(), original.get()); invalid = valid; invalid.signals[0].factor = std::numeric_limits::infinity(); QVERIFY_THROWS_EXCEPTION(std::invalid_argument, database = DbcDecodeBuilder().Build(ParsedDefinitions({invalid}))); QCOMPARE(database.FindFrame(valid.GetKey()).get(), original.get()); QVERIFY_THROWS_EXCEPTION(std::invalid_argument, database = DecodeDatabase({valid, valid})); QCOMPARE(database.FindFrame(valid.GetKey()).get(), original.get()); invalid = valid; invalid.signals.push_back(invalid.signals[0]); QVERIFY_THROWS_EXCEPTION(std::invalid_argument, database = DbcDecodeBuilder().Build(ParsedDefinitions({invalid}))); QCOMPARE(database.FindFrame(valid.GetKey()).get(), original.get()); QVERIFY(database.FindSignal(SignalKey(valid.GetKey(), "Value"))); QCOMPARE(database.GetFrames().size(), std::size_t(1U)); database = DecodeDatabase(); QVERIFY(database.GetFrames().empty()); QVERIFY(!database.FindFrame(valid.GetKey())); QVERIFY(original); } int RunValidationTests(int argc, char **argv) { DbcValidationTest tests; return QTest::qExec(&tests, argc, argv); } #include "dbc_validation_test.moc"