384 lines
17 KiB
C++
384 lines
17 KiB
C++
#include "dbc_decoder.h"
|
|
|
|
#include <QtTest>
|
|
#include <cmath>
|
|
#include <limits>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
|
|
struct DecodeCase {
|
|
const char *name;
|
|
ByteOrder byteOrder;
|
|
std::uint32_t startBit;
|
|
std::uint32_t length;
|
|
ValueType valueType;
|
|
std::vector<std::uint8_t> 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<DecodeCase>("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<std::uint32_t>::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<std::uint8_t>(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<std::int64_t>(&decoded.rawValue);
|
|
QVERIFY(raw != nullptr);
|
|
QCOMPARE(*raw, test.rawValue);
|
|
} else {
|
|
const std::uint64_t *raw = std::get_if<std::uint64_t>(&decoded.rawValue);
|
|
QVERIFY(raw != nullptr);
|
|
QCOMPARE(*raw, static_cast<std::uint64_t>(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<std::uint8_t>(bits >> shift));
|
|
}
|
|
const DecodedFrameValue result = DbcDecoder().Decode(database, frame);
|
|
const std::int64_t signedExpected = bits <= 0x7FFFFFFFFFFFFFFFULL
|
|
? static_cast<std::int64_t>(bits)
|
|
: std::numeric_limits<std::int64_t>::min() +
|
|
static_cast<std::int64_t>(bits - 0x8000000000000000ULL);
|
|
const DecodedSignalValue &decoded = result.signals.at(0U);
|
|
const RawSignalValue expected = type == ValueType::Unsigned
|
|
? std::variant<std::int64_t, std::uint64_t>(bits)
|
|
: std::variant<std::int64_t, std::uint64_t>(signedExpected);
|
|
const double physical = (type == ValueType::Unsigned
|
|
? static_cast<double>(bits)
|
|
: static_cast<double>(signedExpected)) *
|
|
0.5 +
|
|
10.0;
|
|
std::ostringstream actualText;
|
|
std::ostringstream expectedText;
|
|
if (const std::int64_t *signedValue = std::get_if<std::int64_t>(&decoded.rawValue))
|
|
actualText << *signedValue;
|
|
else
|
|
actualText << std::get<std::uint64_t>(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<unsigned>("bytes");
|
|
QTest::addColumn<FrameDecodeStatus>("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<std::uint64_t>(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<int>("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<std::uint32_t>::max(); break;
|
|
case 4: bad.byteOrder = static_cast<ByteOrder>(99); break;
|
|
case 5: bad.valueType = static_cast<ValueType>(99); break;
|
|
case 6: bad.multiplexing.role = static_cast<MultiplexRole>(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<double>("factor");
|
|
QTest::addColumn<double>("offset");
|
|
QTest::addColumn<bool>("finite");
|
|
const double infinity = std::numeric_limits<double>::infinity();
|
|
const double nan = std::numeric_limits<double>::quiet_NaN();
|
|
const double maximum = std::numeric_limits<double>::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<std::int64_t>(scaled.rawValue), std::int64_t(2));
|
|
else
|
|
QCOMPARE(std::get<std::uint64_t>(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<double>::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<std::uint64_t>(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<std::uint8_t> &data: {std::vector<std::uint8_t>(), std::vector<std::uint8_t>{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"
|