Revision control
++ // Copyright (C) 2017 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #include <QtSerialBus/qmodbusclient.h>
++ #include <QtSerialBus/qmodbustcpclient.h>
++ #include <private/qmodbusclient_p.h>
++ #include <private/qmodbus_symbols_p.h>
++
++ #include <QtCore/qendian.h>
++ #include <QtNetwork/qtcpserver.h>
++ #include <QtNetwork/qtcpsocket.h>
++ #include <QtTest/QtTest>
++
++ class TestClient : public QModbusClient
++ {
++ Q_OBJECT
++ class TestClientPrivate : public QModbusClientPrivate
++ {
++ Q_DECLARE_PUBLIC(TestClient)
++
++ public:
++ bool isOpen() const override { return m_open; }
++
++ private:
++ bool m_open = false;
++ };
++
++ public:
++ TestClient()
++ : QModbusClient(*new TestClientPrivate)
++ {}
++ bool open() override {
++ d_func()->m_open = true;
++ setState(QModbusDevice::ConnectedState);
++ return true;
++ }
++ void close() override {
++ d_func()->m_open = true;
++ setState(QModbusDevice::UnconnectedState);
++ }
++ bool processResponse(const QModbusResponse &response, QModbusDataUnit *data) override
++ {
++ return QModbusClient::processResponse(response, data);
++ }
++ Q_DECLARE_PRIVATE(TestClient)
++ };
++
++ class tst_QModbusClient : public QObject
++ {
++ Q_OBJECT
++
++ private slots:
++ void testTimeout()
++ {
++ TestClient client;
++ QCOMPARE(client.timeout(), 1000); //default value test
++
++ QSignalSpy spy(&client, &TestClient::timeoutChanged);
++ client.setTimeout(50);
++ QCOMPARE(client.timeout(), 50);
++ QCOMPARE(spy.isEmpty(), false); // we expect the signal
++
++ spy.clear();
++ client.setTimeout(9); // everything below 10 fails
++ QVERIFY(client.timeout() >= 10);
++ QCOMPARE(spy.isEmpty(), true); // and the signal should not fire
++ }
++
++ void testNumberOfRetries()
++ {
++ TestClient client;
++ QCOMPARE(client.numberOfRetries(), 3);
++
++ client.setNumberOfRetries(-1); // ignore everything below 0
++ QCOMPARE(client.numberOfRetries(), 3);
++
++ client.setNumberOfRetries(1);
++ QCOMPARE(client.numberOfRetries(), 1);
++ }
++
++ void testProcessReadCoilsResponse()
++ {
++ TestClient client;
++ const QList<quint16> values({ 1,0,1,1,0,0,1,1, 1,1,0,1,0,1,1,0, 1,0,1,0,0,0,0,0 });
++
++ QModbusDataUnit unit(QModbusDataUnit::Coils, 100, 24);
++ QModbusResponse response = QModbusResponse(QModbusResponse::ReadCoils,
++ QByteArray::fromHex("03cd6b05"));
++ QCOMPARE(client.processResponse(response, &unit), true);
++
++ QCOMPARE(unit.isValid(), true);
++ QCOMPARE(unit.valueCount(), 24);
++ QCOMPARE(unit.startAddress(), 100);
++ QCOMPARE(unit.values(), values);
++ QCOMPARE(unit.registerType(), QModbusDataUnit::Coils);
++
++ unit = {};
++ response = QModbusResponse(QModbusResponse::ReadCoils,
++ QByteArray::fromHex("03cd6b05"));
++ QCOMPARE(client.processResponse(response, &unit), true);
++
++ QCOMPARE(unit.isValid(), false);
++ QCOMPARE(unit.valueCount(), 24);
++ QCOMPARE(unit.startAddress(), -1);
++ QCOMPARE(unit.values(), values);
++ QCOMPARE(unit.registerType(), QModbusDataUnit::Coils);
++
++ unit = {};
++ unit.setStartAddress(1);
++ response = QModbusResponse(QModbusResponse::ReadCoils,
++ QByteArray::fromHex("03cd6b05"));
++ QCOMPARE(client.processResponse(response, &unit), true);
++
++ QCOMPARE(unit.isValid(), true);
++ QCOMPARE(unit.valueCount(), 24);
++ QCOMPARE(unit.startAddress(), 1);
++ QCOMPARE(unit.values(), values);
++ QCOMPARE(unit.registerType(), QModbusDataUnit::Coils);
++ }
++
++ void testProcessWriteSingleMultipleCoilsResponse()
++ {
++ TestClient client;
++
++ QModbusDataUnit unit;
++ QModbusResponse response = QModbusResponse(QModbusResponse::WriteSingleCoil,
++ QByteArray::fromHex("00acff00"));
++ QCOMPARE(client.processResponse(response, &unit), true);
++
++ QCOMPARE(unit.isValid(), true);
++ QCOMPARE(unit.valueCount(), 1);
++ QCOMPARE(unit.startAddress(), 172);
++ QCOMPARE(unit.values(), QList<quint16>() << Coil::On);
++ QCOMPARE(unit.registerType(), QModbusDataUnit::Coils);
++
++ unit = QModbusDataUnit();
++ response = QModbusResponse(QModbusResponse::WriteMultipleCoils,
++ QByteArray::fromHex("0013000a"));
++ QCOMPARE(client.processResponse(response, &unit), true);
++
++ QCOMPARE(unit.isValid(), true);
++ QCOMPARE(unit.valueCount(), 10);
++ QCOMPARE(unit.startAddress(), 19);
++ QCOMPARE(unit.values(), QList<quint16>());
++ QCOMPARE(unit.registerType(), QModbusDataUnit::Coils);
++ }
++
++ void testProcessReadDiscreteInputsResponse()
++ {
++ TestClient client;
++ const QList<quint16> values({ 0,0,1,1,0,1,0,1, 1,1,0,1,1,0,1,1, 1,0,1,0,1,1,0,0 });
++
++ QModbusDataUnit unit(QModbusDataUnit::DiscreteInputs, 100, 24);
++ QModbusResponse response = QModbusResponse(QModbusResponse::ReadDiscreteInputs,
++ QByteArray::fromHex("03acdb35"));
++ QCOMPARE(client.processResponse(response, &unit), true);
++
++ QCOMPARE(unit.isValid(), true);
++ QCOMPARE(unit.valueCount(), 24);
++ QCOMPARE(unit.startAddress(), 100);
++ QCOMPARE(unit.values(), values);
++ QCOMPARE(unit.registerType(), QModbusDataUnit::DiscreteInputs);
++
++ response.setFunctionCode(QModbusPdu::FunctionCode(0x82));
++ QCOMPARE(client.processResponse(response, &unit), false);
++
++ response.setFunctionCode(QModbusResponse::ReadDiscreteInputs);
++ response.setData(QByteArray::fromHex("35"));
++ QCOMPARE(client.processResponse(response, &unit), false);
++
++ response.setData(QByteArray::fromHex("03acdb"));
++ QCOMPARE(client.processResponse(response, &unit), false);
++
++ response.setData(QByteArray::fromHex("03acdb3517"));
++ QCOMPARE(client.processResponse(response, &unit), false);
++
++ unit = {};
++ response = QModbusResponse(QModbusResponse::ReadDiscreteInputs,
++ QByteArray::fromHex("03acdb35"));
++ QCOMPARE(client.processResponse(response, &unit), true);
++
++ QCOMPARE(unit.isValid(), false);
++ QCOMPARE(unit.valueCount(), 24);
++ QCOMPARE(unit.startAddress(), -1);
++ QCOMPARE(unit.values(), values);
++ QCOMPARE(unit.registerType(), QModbusDataUnit::DiscreteInputs);
++
++ unit = {};
++ unit.setStartAddress(1);
++ response = QModbusResponse(QModbusResponse::ReadDiscreteInputs,
++ QByteArray::fromHex("03acdb35"));
++ QCOMPARE(client.processResponse(response, &unit), true);
++
++ QCOMPARE(unit.isValid(), true);
++ QCOMPARE(unit.valueCount(), 24);
++ QCOMPARE(unit.startAddress(), 1);
++ QCOMPARE(unit.values(), values);
++ QCOMPARE(unit.registerType(), QModbusDataUnit::DiscreteInputs);
++ }
++
++ void testProcessReadHoldingRegistersResponse()
++ {
++ TestClient client;
++ const QList<quint16> values({ 52587, 1407 });
++
++ QModbusDataUnit unit;
++ unit.setStartAddress(100);
++ QModbusResponse response = QModbusResponse(QModbusResponse::ReadHoldingRegisters,
++ QByteArray::fromHex("04cd6b057f"));
++ QCOMPARE(client.processResponse(response, &unit), true);
++
++ QCOMPARE(unit.isValid(), true);
++ QCOMPARE(unit.valueCount(), 2);
++ QCOMPARE(unit.startAddress(), 100);
++ QCOMPARE(unit.values(), values);
++ QCOMPARE(unit.registerType(), QModbusDataUnit::HoldingRegisters);
++
++ response.setFunctionCode(QModbusPdu::FunctionCode(0x83));
++ QCOMPARE(client.processResponse(response, &unit), false);
++
++ response.setFunctionCode(QModbusResponse::ReadHoldingRegisters);
++ response.setData(QByteArray::fromHex("05"));
++ QCOMPARE(client.processResponse(response, &unit), false);
++
++ response.setData(QByteArray::fromHex("04cd6b"));
++ QCOMPARE(client.processResponse(response, &unit), false);
++
++ response.setData(QByteArray::fromHex("04cd6b051755"));
++ QCOMPARE(client.processResponse(response, &unit), false);
++
++ unit = {};
++ response = QModbusResponse(QModbusResponse::ReadHoldingRegisters,
++ QByteArray::fromHex("04cd6b057f"));
++ QCOMPARE(client.processResponse(response, &unit), true);
++
++ QCOMPARE(unit.isValid(), false);
++ QCOMPARE(unit.valueCount(), 2);
++ QCOMPARE(unit.startAddress(), -1);
++ QCOMPARE(unit.values(), values);
++ QCOMPARE(unit.registerType(), QModbusDataUnit::HoldingRegisters);
++
++ unit = {};
++ unit.setStartAddress(1);
++ response = QModbusResponse(QModbusResponse::ReadHoldingRegisters,
++ QByteArray::fromHex("04cd6b057f"));
++ QCOMPARE(client.processResponse(response, &unit), true);
++
++ QCOMPARE(unit.isValid(), true);
++ QCOMPARE(unit.valueCount(), 2);
++ QCOMPARE(unit.startAddress(), 1);
++ QCOMPARE(unit.values(), values);
++ QCOMPARE(unit.registerType(), QModbusDataUnit::HoldingRegisters);
++ }
++
++ void testProcessReadInputRegistersResponse()
++ {
++ TestClient client;
++ const QList<quint16> values({ 52587, 1407 });
++
++ QModbusDataUnit unit;
++ unit.setStartAddress(100);
++ QModbusResponse response = QModbusResponse(QModbusResponse::ReadInputRegisters,
++ QByteArray::fromHex("04cd6b057f"));
++ QCOMPARE(client.processResponse(response, &unit), true);
++
++ QCOMPARE(unit.isValid(), true);
++ QCOMPARE(unit.valueCount(), 2);
++ QCOMPARE(unit.startAddress(), 100);
++ QCOMPARE(unit.values(), values);
++ QCOMPARE(unit.registerType(), QModbusDataUnit::InputRegisters);
++
++ response.setFunctionCode(QModbusPdu::FunctionCode(0x84));
++ QCOMPARE(client.processResponse(response, &unit), false);
++
++ response.setFunctionCode(QModbusResponse::ReadInputRegisters);
++ response.setData(QByteArray::fromHex("05"));
++ QCOMPARE(client.processResponse(response, &unit), false);
++
++ response.setData(QByteArray::fromHex("04cd6b"));
++ QCOMPARE(client.processResponse(response, &unit), false);
++
++ response.setData(QByteArray::fromHex("04cd6b051755"));
++ QCOMPARE(client.processResponse(response, &unit), false);
++
++ unit = {};
++ response = QModbusResponse(QModbusResponse::ReadInputRegisters,
++ QByteArray::fromHex("04cd6b057f"));
++ QCOMPARE(client.processResponse(response, &unit), true);
++
++ QCOMPARE(unit.isValid(), false);
++ QCOMPARE(unit.valueCount(), 2);
++ QCOMPARE(unit.startAddress(), -1);
++ QCOMPARE(unit.values(), values);
++ QCOMPARE(unit.registerType(), QModbusDataUnit::InputRegisters);
++
++ unit = {};
++ unit.setStartAddress(1);
++ response = QModbusResponse(QModbusResponse::ReadInputRegisters,
++ QByteArray::fromHex("04cd6b057f"));
++ QCOMPARE(client.processResponse(response, &unit), true);
++
++ QCOMPARE(unit.isValid(), true);
++ QCOMPARE(unit.valueCount(), 2);
++ QCOMPARE(unit.startAddress(), 1);
++ QCOMPARE(unit.values(), values);
++ QCOMPARE(unit.registerType(), QModbusDataUnit::InputRegisters);
++ }
++
++ void testProcessWriteSingleRegisterResponse()
++ {
++ TestClient client;
++
++ QModbusDataUnit unit;
++ QModbusResponse response = QModbusResponse(QModbusResponse::WriteSingleRegister,
++ QByteArray::fromHex("04cd6b05"));
++ QCOMPARE(client.processResponse(response, &unit), true);
++
++ QCOMPARE(unit.isValid(), true);
++ QCOMPARE(unit.valueCount(), 1);
++ QCOMPARE(unit.startAddress(), 1229);
++ QCOMPARE(unit.values(), QList<quint16>(1, 27397u));
++ QCOMPARE(unit.registerType(), QModbusDataUnit::HoldingRegisters);
++
++ response.setFunctionCode(QModbusPdu::FunctionCode(0x86));
++ QCOMPARE(client.processResponse(response, &unit), false);
++
++ response.setFunctionCode(QModbusResponse::WriteSingleRegister);
++ response.setData(QByteArray::fromHex("05"));
++ QCOMPARE(client.processResponse(response, &unit), false);
++
++ response.setData(QByteArray::fromHex("04cd6b051755"));
++ QCOMPARE(client.processResponse(response, &unit), false);
++ }
++
++ void testProcessWriteMultipleRegistersResponse()
++ {
++ TestClient client;
++
++ QModbusDataUnit unit;
++ QModbusResponse response = QModbusResponse(QModbusResponse::WriteMultipleRegisters,
++ QByteArray::fromHex("03cd007b"));
++ QCOMPARE(client.processResponse(response, &unit), true);
++
++ QCOMPARE(unit.isValid(), true);
++ QCOMPARE(unit.valueCount(), 123);
++ QCOMPARE(unit.startAddress(), 973);
++ QCOMPARE(unit.registerType(), QModbusDataUnit::HoldingRegisters);
++
++ response.setFunctionCode(QModbusPdu::FunctionCode(0x90));
++ QCOMPARE(client.processResponse(response, &unit), false);
++
++ response.setFunctionCode(QModbusResponse::WriteMultipleRegisters);
++ response.setData(QByteArray::fromHex("05"));
++ QCOMPARE(client.processResponse(response, &unit), false);
++
++ response.setData(QByteArray::fromHex("03cd6b"));
++ QCOMPARE(client.processResponse(response, &unit), false);
++
++ response.setData(QByteArray::fromHex("03cd6b0517"));
++ QCOMPARE(client.processResponse(response, &unit), false);
++
++ response.setData(QByteArray::fromHex("03cd0000"));
++ QCOMPARE(client.processResponse(response, &unit), false);
++
++ response.setData(QByteArray::fromHex("03cd007c"));
++ QCOMPARE(client.processResponse(response, &unit), false);
++ }
++
++ void testProcessReadWriteMultipleRegistersResponse()
++ {
++ TestClient client;
++ const QList<quint16> values({ 52587, 1407 });
++
++ QModbusDataUnit unit;
++ unit.setStartAddress(100);
++ QModbusResponse response = QModbusResponse(QModbusResponse::ReadWriteMultipleRegisters,
++ QByteArray::fromHex("04cd6b057f"));
++ QCOMPARE(client.processResponse(response, &unit), true);
++
++ QCOMPARE(unit.isValid(), true);
++ QCOMPARE(unit.valueCount(), 2);
++ QCOMPARE(unit.values(), values);
++ QCOMPARE(unit.registerType(), QModbusDataUnit::HoldingRegisters);
++
++ response.setFunctionCode(QModbusPdu::FunctionCode(0x97));
++ QCOMPARE(client.processResponse(response, &unit), false);
++
++ response.setFunctionCode(QModbusResponse::ReadWriteMultipleRegisters);
++ response.setData(QByteArray::fromHex("05"));
++ QCOMPARE(client.processResponse(response, &unit), false);
++
++ response.setData(QByteArray::fromHex("04cd6b"));
++ QCOMPARE(client.processResponse(response, &unit), false);
++
++ response.setData(QByteArray::fromHex("04cd6b051755"));
++ QCOMPARE(client.processResponse(response, &unit), false);
++
++ unit = {};
++ response = QModbusResponse(QModbusResponse::ReadWriteMultipleRegisters,
++ QByteArray::fromHex("04cd6b057f"));
++ QCOMPARE(client.processResponse(response, &unit), true);
++
++ QCOMPARE(unit.isValid(), false);
++ QCOMPARE(unit.valueCount(), 2);
++ QCOMPARE(unit.startAddress(), -1);
++ QCOMPARE(unit.values(), values);
++ QCOMPARE(unit.registerType(), QModbusDataUnit::HoldingRegisters);
++
++ unit = {};
++ unit.setStartAddress(1);
++ response = QModbusResponse(QModbusResponse::ReadWriteMultipleRegisters,
++ QByteArray::fromHex("04cd6b057f"));
++ QCOMPARE(client.processResponse(response, &unit), true);
++
++ QCOMPARE(unit.isValid(), true);
++ QCOMPARE(unit.valueCount(), 2);
++ QCOMPARE(unit.startAddress(), 1);
++ QCOMPARE(unit.values(), values);
++ QCOMPARE(unit.registerType(), QModbusDataUnit::HoldingRegisters);
++ }
++
++ void testPrivateCreateReadRequest_data()
++ {
++ QTest::addColumn<QModbusDataUnit::RegisterType>("rc");
++ QTest::addColumn<int>("address");
++ QTest::addColumn<int>("count");
++ QTest::addColumn<QModbusPdu::FunctionCode>("fc");
++ QTest::addColumn<QByteArray>("data");
++ QTest::addColumn<bool>("isValid");
++
++ QTest::newRow("QModbusDataUnit::Invalid") << QModbusDataUnit::Invalid << 19 << 19
++ << QModbusRequest::Invalid << QByteArray() << false;
++ QTest::newRow("QModbusDataUnit::Coils") << QModbusDataUnit::Coils << 19 << 19
++ << QModbusRequest::ReadCoils << QByteArray::fromHex("00130013") << true;
++ QTest::newRow("QModbusDataUnit::DiscreteInputs") << QModbusDataUnit::DiscreteInputs << 19
++ << 19 << QModbusRequest::ReadDiscreteInputs << QByteArray::fromHex("00130013") << true;
++ QTest::newRow("QModbusDataUnit::InputRegisters") << QModbusDataUnit::InputRegisters << 19
++ << 19 << QModbusRequest::ReadInputRegisters << QByteArray::fromHex("00130013") << true;
++ QTest::newRow("QModbusDataUnit::HoldingRegisters") << QModbusDataUnit::HoldingRegisters
++ << 19 << 19 << QModbusRequest::ReadHoldingRegisters << QByteArray::fromHex("00130013")
++ << true;
++ }
++
++ void testPrivateCreateReadRequest()
++ {
++ QFETCH(QModbusDataUnit::RegisterType, rc);
++ QFETCH(int, address);
++ QFETCH(int, count);
++
++ TestClient client;
++ QModbusDataUnit read(rc, address, count);
++ QModbusRequest request = client.d_func()->createReadRequest(read);
++ QTEST(request.functionCode(), "fc");
++ QTEST(request.data(), "data");
++ QTEST(request.isValid(), "isValid");
++ }
++
++ void testPrivateCreateWriteRequest_data()
++ {
++ QTest::addColumn<QModbusDataUnit::RegisterType>("rc");
++ QTest::addColumn<int>("address");
++ QTest::addColumn<QList<quint16>>("values");
++ QTest::addColumn<QModbusPdu::FunctionCode>("fc");
++ QTest::addColumn<QByteArray>("data");
++ QTest::addColumn<bool>("isValid");
++
++ QTest::newRow("QModbusDataUnit::Invalid") << QModbusDataUnit::Invalid << 19 << (QList<quint16> { 1 })
++ << QModbusRequest::Invalid << QByteArray() << false;
++ QTest::newRow("QModbusDataUnit::DiscreteInputs")
++ << QModbusDataUnit::DiscreteInputs << 19 << (QList<quint16> { 1 }) << QModbusRequest::Invalid
++ << QByteArray() << false;
++ QTest::newRow("QModbusDataUnit::InputRegisters")
++ << QModbusDataUnit::InputRegisters << 19 << (QList<quint16> { 1 }) << QModbusRequest::Invalid
++ << QByteArray() << false;
++
++ QTest::newRow("QModbusDataUnit::Coils{Single}")
++ << QModbusDataUnit::Coils << 172 << (QList<quint16> { 1 }) << QModbusRequest::WriteSingleCoil
++ << QByteArray::fromHex("00acff00") << true;
++ QTest::newRow("QModbusDataUnit::Coils{Multiple}")
++ << QModbusDataUnit::Coils << 19
++ << (QList<quint16> { 1,0,1,1,0,0,1,1, 1,0 /* 6 times padding */ })
++ << QModbusRequest::WriteMultipleCoils << QByteArray::fromHex("0013000a02cd01") << true;
++ QTest::newRow("QModbusDataUnit::HoldingRegisters{Single}")
++ << QModbusDataUnit::HoldingRegisters << 1229 << (QList<quint16> { 27397u })
++ << QModbusRequest::WriteSingleRegister << QByteArray::fromHex("04cd6b05") << true;
++ QTest::newRow("QModbusDataUnit::HoldingRegisters{Multiple}")
++ << QModbusDataUnit::HoldingRegisters << 1 << (QList<quint16> { 27397u, 27397u })
++ << QModbusRequest::WriteMultipleRegisters << QByteArray::fromHex("00010002046b056b05") << true;
++ }
++
++ void testPrivateCreateWriteRequest()
++ {
++ QFETCH(QModbusDataUnit::RegisterType, rc);
++ QFETCH(int, address);
++ QFETCH(QList<quint16>, values);
++
++ TestClient client;
++ QModbusDataUnit write(rc, address, values);
++ QModbusRequest request = client.d_func()->createWriteRequest(write);
++ QTEST(request.functionCode(), "fc");
++ QTEST(request.data(), "data");
++ QTEST(request.isValid(), "isValid");
++ }
++
++ void testPrivateCreateRWRequest_data()
++ {
++ QTest::addColumn<QModbusDataUnit::RegisterType>("rc");
++ QTest::addColumn<int>("address");
++ QTest::addColumn<QList<quint16>>("values");
++ QTest::addColumn<QModbusPdu::FunctionCode>("fc");
++ QTest::addColumn<QByteArray>("data");
++ QTest::addColumn<bool>("isValid");
++
++ QTest::newRow("QModbusDataUnit::Invalid") << QModbusDataUnit::Invalid << 19 << (QList<quint16> { 1 })
++ << QModbusRequest::Invalid << QByteArray() << false;
++ QTest::newRow("QModbusDataUnit::Coils") << QModbusDataUnit::Invalid << 172 << (QList<quint16> { 1 })
++ << QModbusRequest::Invalid << QByteArray() << false;
++ QTest::newRow("QModbusDataUnit::DiscreteInputs")
++ << QModbusDataUnit::DiscreteInputs << 19 << (QList<quint16> { 1 }) << QModbusRequest::Invalid
++ << QByteArray() << false;
++ QTest::newRow("QModbusDataUnit::InputRegisters")
++ << QModbusDataUnit::InputRegisters << 19 << (QList<quint16> { 1 }) << QModbusRequest::Invalid
++ << QByteArray() << false;
++ QTest::newRow("QModbusDataUnit::HoldingRegisters{Read|Write}")
++ << QModbusDataUnit::HoldingRegisters << 1 << (QList<quint16> { 27397u, 27397u })
++ << QModbusRequest::ReadWriteMultipleRegisters << QByteArray::fromHex("0001000200010002046b056b05")
++ << true;
++ }
++
++ void testPrivateCreateRWRequest()
++ {
++ QFETCH(QModbusDataUnit::RegisterType, rc);
++ QFETCH(int, address);
++ QFETCH(QList<quint16>, values);
++
++ TestClient client;
++ QModbusDataUnit read(rc, address, values.size());
++ QModbusDataUnit write(rc, address, values);
++ QModbusRequest request = client.d_func()->createRWRequest(read, write);
++ QTEST(request.functionCode(), "fc");
++ QTEST(request.data(), "data");
++ QTEST(request.isValid(), "isValid");
++ }
++
++ void testPrivateSendRequest()
++ {
++ TestClient client;
++ QModbusDataUnit unit;
++ QModbusReply *reply = nullptr;
++
++ QTest::ignoreMessage(QtWarningMsg, "(Client) Device is not connected");
++ QCOMPARE(client.d_func()->sendRequest(QModbusRequest(), 1, &unit), reply);
++ QCOMPARE(client.error(), QModbusDevice::ConnectionError);
++ QCOMPARE(client.errorString(), QString("Device not connected."));
++
++ QTest::ignoreMessage(QtWarningMsg, "(Client) Device is not connected");
++ QCOMPARE(client.d_func()->sendRequest(QModbusRequest(), 1, nullptr), reply);
++ QCOMPARE(client.error(), QModbusDevice::ConnectionError);
++ QCOMPARE(client.errorString(), QString("Device not connected."));
++
++ QCOMPARE(client.connectDevice(), true);
++
++ QTest::ignoreMessage(QtWarningMsg, "(Client) Refuse to send invalid request.");
++ QCOMPARE(client.d_func()->sendRequest(QModbusRequest(), 1, &unit), reply);
++ QCOMPARE(client.error(), QModbusDevice::ProtocolError);
++ QCOMPARE(client.errorString(), QString("Invalid Modbus request."));
++
++ QTest::ignoreMessage(QtWarningMsg, "(Client) Refuse to send invalid request.");
++ QCOMPARE(client.d_func()->sendRequest(QModbusRequest(), 1, nullptr), reply);
++ QCOMPARE(client.error(), QModbusDevice::ProtocolError);
++ QCOMPARE(client.errorString(), QString("Invalid Modbus request."));
++
++ QModbusRequest request(QModbusRequest::Diagnostics);
++ // The default implementation is empty and returns a null pointer.
++ QCOMPARE(client.d_func()->sendRequest(request, 1, &unit), reply);
++ QCOMPARE(client.d_func()->sendRequest(request, 1, nullptr), reply);
++ }
++
++ void testTcpMbapLengthField_data()
++ {
++ QTest::addColumn<QByteArray>("frame");
++ QTest::addColumn<bool>("accepted");
++
++ // MBAP header: transaction id (2), protocol id (2), length (2), unit id (1).
++ // The length field counts the unit id, so 2 is its smallest legal value.
++ QTest::newRow("length zero") << QByteArray::fromHex("00000000000001") << false;
++ QTest::newRow("length one") << QByteArray::fromHex("00000000000101") << false;
++ QTest::newRow("one above the maximum") << QByteArray::fromHex("0000000000ff01") << false;
++ QTest::newRow("length huge") << QByteArray::fromHex("00000000ffff01") << false;
++ // 254 describes the largest legal frame, a 260 byte ADU. It is accepted by
++ // the framing, here rejected later because the padding is not a valid PDU.
++ QTest::newRow("maximum length")
++ << (QByteArray::fromHex("0000000000fe01") + QByteArray(253, 0x03)) << true;
++
++ // A legal length of 2 declares a one byte PDU, too short for the read
++ // response it claims to be. The missing bytes must not be taken from what
++ // follows the frame, or the application is handed a fabricated register.
++ QTest::newRow("declared length bounds the PDU")
++ << (QByteArray::fromHex("0000000000020103") + QByteArray::fromHex("02beef")) << true;
++ }
++
++ void testTcpMbapLengthField()
++ {
++ QFETCH(QByteArray, frame);
++ QFETCH(bool, accepted);
++
++ QTcpServer server;
++ QVERIFY(server.listen(QHostAddress::LocalHost, 0));
++
++ QModbusTcpClient client;
++ client.setConnectionParameter(QModbusDevice::NetworkAddressParameter,
++ QStringLiteral("127.0.0.1"));
++ client.setConnectionParameter(QModbusDevice::NetworkPortParameter, server.serverPort());
++ QVERIFY(client.connectDevice());
++ QTRY_COMPARE(client.state(), QModbusDevice::ConnectedState);
++ QTRY_VERIFY(server.hasPendingConnections());
++ QTcpSocket *remote = server.nextPendingConnection();
++
++ QModbusReply *reply = client.sendReadRequest(
++ QModbusDataUnit(QModbusDataUnit::HoldingRegisters, 0, 1), 1);
++ QVERIFY(reply);
++
++ if (!accepted) {
++ const QByteArray warning = "(TCP client) Invalid MBAP length field: "
++ + QByteArray::number(qFromBigEndian<quint16>(frame.constData() + 4))
++ + " closing connection.";
++ QTest::ignoreMessage(QtWarningMsg, warning.constData());
++ }
++
++ remote->write(frame);
++ QTRY_VERIFY(reply->isFinished());
++
++ if (!accepted) {
++ QCOMPARE(reply->error(), QModbusDevice::ReplyAbortedError);
++ QTRY_COMPARE(client.state(), QModbusDevice::UnconnectedState);
++ QCOMPARE(client.error(), QModbusDevice::ProtocolError);
++ return;
++ }
++
++ // Accepted by the framing, so the connection survives and only the
++ // bogus PDU is rejected. No value reaches the application.
++ QCOMPARE(reply->error(), QModbusDevice::InvalidResponseError);
++ QCOMPARE(reply->result().valueCount(), 0);
++ QCOMPARE(client.state(), QModbusDevice::ConnectedState);
++ }
++ };
++
++ QTEST_MAIN(tst_QModbusClient)
++
++ #include "tst_qmodbusclient.moc"