2026-04-03 16:23:56 +08:00
|
|
|
#include "message_bus/pipeline/stages/1_framers/ModbusRtuFramer.h"
|
2026-04-10 10:52:10 +08:00
|
|
|
#include "plugins/protocols/modbus_rtu/framer/ModbusRtuFraming.h"
|
2026-04-03 16:23:56 +08:00
|
|
|
|
|
|
|
|
#include <QtTest/QtTest>
|
|
|
|
|
|
|
|
|
|
#include <cstring>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
using softbus::core::memory::MemoryPool;
|
|
|
|
|
using softbus::message_bus::pipeline::FrameKind;
|
|
|
|
|
using softbus::message_bus::pipeline::PipelineContext;
|
|
|
|
|
using softbus::message_bus::pipeline::stages::framers::ModbusRtuFramer;
|
|
|
|
|
using softbus::message_bus::pipeline::stages::framers::ModbusRtuFramerConfig;
|
2026-04-07 14:20:04 +08:00
|
|
|
using softbus::message_bus::pipeline::protocol::modbusrtu::crc16;
|
2026-04-03 16:23:56 +08:00
|
|
|
|
|
|
|
|
namespace
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
void appendCrcLe(std::vector<std::uint8_t>& v)
|
|
|
|
|
{
|
2026-04-07 14:20:04 +08:00
|
|
|
const std::uint16_t c = crc16(v.data(), v.size());
|
2026-04-03 16:23:56 +08:00
|
|
|
v.push_back(static_cast<std::uint8_t>(c & 0xFF));
|
|
|
|
|
v.push_back(static_cast<std::uint8_t>(c >> 8));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MemoryPool::BlockPtr makeBlock(MemoryPool& pool, const std::vector<std::uint8_t>& bytes)
|
|
|
|
|
{
|
|
|
|
|
auto blk = pool.allocate(bytes.size());
|
|
|
|
|
if (!blk || !blk->data) {
|
|
|
|
|
QTest::qFail("pool.allocate failed", __FILE__, __LINE__);
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
std::memcpy(blk->data, bytes.data(), bytes.size());
|
|
|
|
|
blk->size = bytes.size();
|
|
|
|
|
return blk;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
class ModbusRtuFramerTest : public QObject
|
|
|
|
|
{
|
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
|
|
private slots:
|
|
|
|
|
void half_then_rest_emits_one_frame();
|
|
|
|
|
void sticky_two_frames_in_one_chunk();
|
|
|
|
|
void crc_error_triggers_resync_and_recovery();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void ModbusRtuFramerTest::half_then_rest_emits_one_frame()
|
|
|
|
|
{
|
|
|
|
|
auto pool = std::make_shared<MemoryPool>(512, 64);
|
|
|
|
|
ModbusRtuFramer framer(pool, ModbusRtuFramerConfig{}, QStringLiteral("/ttyUSB0"));
|
|
|
|
|
std::vector<std::uint8_t> a{0x01, 0x03, 0x04, 0x12, 0x34, 0x56, 0x78};
|
|
|
|
|
appendCrcLe(a);
|
|
|
|
|
|
|
|
|
|
std::vector<std::uint8_t> part1(a.begin(), a.begin() + 4);
|
|
|
|
|
std::vector<std::uint8_t> part2(a.begin() + 4, a.end());
|
|
|
|
|
|
|
|
|
|
PipelineContext c1;
|
|
|
|
|
c1.endpoint = QStringLiteral("/ttyUSB0");
|
|
|
|
|
c1.protocolHint = QStringLiteral("modbus_rtu");
|
|
|
|
|
c1.frameKind = FrameKind::StreamChunk;
|
|
|
|
|
c1.payload.block = makeBlock(*pool, part1);
|
|
|
|
|
c1.payload.offset = 0;
|
|
|
|
|
c1.payload.length = part1.size();
|
|
|
|
|
c1.payloadSize = part1.size();
|
|
|
|
|
|
|
|
|
|
std::vector<PipelineContext> out;
|
|
|
|
|
framer.feed(c1, out);
|
|
|
|
|
QCOMPARE(int(out.size()), 0);
|
|
|
|
|
|
|
|
|
|
PipelineContext c2;
|
|
|
|
|
c2.endpoint = QStringLiteral("/ttyUSB0");
|
|
|
|
|
c2.protocolHint = QStringLiteral("modbus_rtu");
|
|
|
|
|
c2.frameKind = FrameKind::StreamChunk;
|
|
|
|
|
c2.payload.block = makeBlock(*pool, part2);
|
|
|
|
|
c2.payload.offset = 0;
|
|
|
|
|
c2.payload.length = part2.size();
|
|
|
|
|
c2.payloadSize = part2.size();
|
|
|
|
|
framer.feed(c2, out);
|
|
|
|
|
QCOMPARE(int(out.size()), 1);
|
|
|
|
|
QCOMPARE(out[0].frameKind, FrameKind::CompleteFrame);
|
|
|
|
|
QCOMPARE(out[0].payloadSize, a.size());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ModbusRtuFramerTest::sticky_two_frames_in_one_chunk()
|
|
|
|
|
{
|
|
|
|
|
auto pool = std::make_shared<MemoryPool>(512, 64);
|
|
|
|
|
ModbusRtuFramer framer(pool, ModbusRtuFramerConfig{}, QStringLiteral("/ttyUSB0"));
|
|
|
|
|
std::vector<std::uint8_t> f;
|
|
|
|
|
f.insert(f.end(), {0x01, 0x03, 0x02, 0x00, 0x01});
|
|
|
|
|
appendCrcLe(f);
|
|
|
|
|
std::vector<std::uint8_t> g;
|
|
|
|
|
g.insert(g.end(), {0x02, 0x03, 0x04, 0xAA, 0xBB, 0xCC, 0xDD});
|
|
|
|
|
appendCrcLe(g);
|
|
|
|
|
std::vector<std::uint8_t> both;
|
|
|
|
|
both.insert(both.end(), f.begin(), f.end());
|
|
|
|
|
both.insert(both.end(), g.begin(), g.end());
|
|
|
|
|
|
|
|
|
|
PipelineContext c;
|
|
|
|
|
c.endpoint = QStringLiteral("/ttyUSB0");
|
|
|
|
|
c.protocolHint = QStringLiteral("modbus_rtu");
|
|
|
|
|
c.frameKind = FrameKind::StreamChunk;
|
|
|
|
|
c.payload.block = makeBlock(*pool, both);
|
|
|
|
|
c.payload.offset = 0;
|
|
|
|
|
c.payload.length = both.size();
|
|
|
|
|
c.payloadSize = both.size();
|
|
|
|
|
|
|
|
|
|
std::vector<PipelineContext> out;
|
|
|
|
|
framer.feed(c, out);
|
|
|
|
|
QCOMPARE(int(out.size()), 2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ModbusRtuFramerTest::crc_error_triggers_resync_and_recovery()
|
|
|
|
|
{
|
|
|
|
|
auto pool = std::make_shared<MemoryPool>(512, 64);
|
|
|
|
|
ModbusRtuFramer framer(pool, ModbusRtuFramerConfig{}, QStringLiteral("/ttyUSB0"));
|
|
|
|
|
std::vector<std::uint8_t> bad{0x01, 0x03, 0x02, 0x00, 0x00, 0xDE, 0xAD};
|
|
|
|
|
std::vector<std::uint8_t> good;
|
|
|
|
|
good.insert(good.end(), {0x01, 0x03, 0x02, 0x12, 0x34});
|
|
|
|
|
appendCrcLe(good);
|
|
|
|
|
|
|
|
|
|
std::vector<std::uint8_t> stream;
|
|
|
|
|
stream.insert(stream.end(), bad.begin(), bad.end());
|
|
|
|
|
stream.insert(stream.end(), good.begin(), good.end());
|
|
|
|
|
|
|
|
|
|
PipelineContext c;
|
|
|
|
|
c.endpoint = QStringLiteral("/ttyUSB0");
|
|
|
|
|
c.protocolHint = QStringLiteral("modbus_rtu");
|
|
|
|
|
c.frameKind = FrameKind::StreamChunk;
|
|
|
|
|
c.payload.block = makeBlock(*pool, stream);
|
|
|
|
|
c.payload.offset = 0;
|
|
|
|
|
c.payload.length = stream.size();
|
|
|
|
|
c.payloadSize = stream.size();
|
|
|
|
|
|
|
|
|
|
std::vector<PipelineContext> out;
|
|
|
|
|
framer.feed(c, out);
|
|
|
|
|
QVERIFY(!out.empty());
|
|
|
|
|
QCOMPARE(out[0].payloadSize, good.size());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QTEST_MAIN(ModbusRtuFramerTest)
|
|
|
|
|
#include "modbus_rtu_framer_test.moc"
|