Revision control

++ // Copyright (C) 2017 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #include <QtSerialBus/qmodbusserver.h>
++ #if QT_CONFIG(modbus_serialport)
++ #include <QtSerialBus/qmodbusrtuserialserver.h>
++ #endif
++ #include <QtSerialBus/qmodbustcpserver.h>
++ #include <QtSerialBus/qmodbusdeviceidentification.h>
++
++ #include <QtCore/qdebug.h>
++ #include <QtCore/qendian.h>
++ #include <QtNetwork/qtcpserver.h>
++ #include <QtNetwork/qtcpsocket.h>
++ #include <QtTest/QtTest>
++
++ class TestServer : public QModbusServer
++ {
++ public:
++ TestServer() {
++ qRegisterMetaType<QModbusDataUnit::RegisterType>();
++ }
++
++ bool open() override {
++ setState(QModbusDevice::ConnectedState);
++ return true;
++ }
++ void close() override {
++ setState(QModbusDevice::UnconnectedState);
++ }
++ QModbusResponse processRequest(const QModbusPdu &request) override
++ {
++ return QModbusServer::processRequest(request);
++ }
++ };
++
++ #define MAP_RANGE 500
++ static QString s_msg;
++ static void myMessageHandler(QtMsgType, const QMessageLogContext &, const QString &msg)
++ {
++ s_msg = msg;
++ }
++
++ class tst_QModbusServer : public QObject
++ {
++ Q_OBJECT
++
++ private:
++ TestServer server;
++
++ private slots:
++ void init()
++ {
++ QModbusDataUnitMap map;
++ map.insert(QModbusDataUnit::DiscreteInputs, { QModbusDataUnit::DiscreteInputs, 0, MAP_RANGE });
++ map.insert(QModbusDataUnit::Coils, { QModbusDataUnit::Coils, 0, MAP_RANGE });
++ map.insert(QModbusDataUnit::InputRegisters, { QModbusDataUnit::InputRegisters, 0, MAP_RANGE });
++ map.insert(QModbusDataUnit::HoldingRegisters, { QModbusDataUnit::HoldingRegisters, 0, MAP_RANGE });
++ server.setMap(map);
++ }
++
++ void testServerAddress()
++ {
++ QCOMPARE(QModbusTcpServer().serverAddress(), 0xff);
++ #if QT_CONFIG(modbus_serialport)
++ QCOMPARE(QModbusRtuSerialServer().serverAddress(), 1);
++ #endif
++ }
++
++ void testProcessRequestReadWriteSingleMultipleCoils()
++ {
++ // request write Coil 173, address: 0x00ac -> 172, value: 0xff00 -> ON
++ QModbusRequest request = QModbusRequest(QModbusRequest::WriteSingleCoil,
++ QByteArray::fromHex("00acff00"));
++ QModbusResponse response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("00acff00"));
++
++ // request read Coil 173, address: 0x00ac -> 172, count: 0x0001 -> 1
++ request = QModbusRequest(QModbusRequest::ReadCoils, QByteArray::fromHex("00ac0001"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, byte count: 0x01 -> 1, status: 0x01 -> 0000 0001
++ QCOMPARE(response.data(), QByteArray::fromHex("0101"));
++
++ // request write 10 coils starting at coil 20, address: 0x0013 -> 19, count: 0x000a -> 10,
++ // payload bytes: 0x02 -> 2, values: 0xcd -> 1100 1101, 0x01 -> 0000 0001
++ request = QModbusRequest(QModbusRequest::WriteMultipleCoils,
++ QByteArray::fromHex("0013000a02cd01"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, equals request's first 4 bytes
++ QCOMPARE(response.data(), QByteArray::fromHex("0013000a"));
++
++ // request read 10 coils starting at coil 20, address: 0x0013 -> 19, count: 0x000a -> 10
++ request = QModbusRequest(QModbusRequest::ReadCoils, QByteArray::fromHex("0013000a"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, byte count: 0x02 -> 1, status: 0xcd -> 1100 1101, 0x01 -> 0000 0001
++ QCOMPARE(response.data(), QByteArray::fromHex("02cd01"));
++
++ // request write 19 coils starting at coil 20, address: 0x0013 -> 19, count: 0x0013 -> 19,
++ // payload bytes: 0x03 -> 3, values: 0xcd -> 1100 1101, 0x6b -> 0110 1011, 0x05 -> 0000 0101
++ request = QModbusRequest(QModbusRequest::WriteMultipleCoils,
++ QByteArray::fromHex("0013001303cd6b05"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, equals request's first 4 bytes
++ QCOMPARE(response.data(), QByteArray::fromHex("00130013"));
++
++ // request read 19 coils starting at coil 20, address: 0x0013 -> 19, count: 0x0013 -> 19
++ request = QModbusRequest(QModbusRequest::ReadCoils, QByteArray::fromHex("00130013"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, byte count: 0x03 -> 3
++ // status: 0xcd -> 1100 1101, 0x6b -> 0110 1011, 0x05 -> 0000 0101
++ QCOMPARE(response.data(), QByteArray::fromHex("03cd6b05"));
++
++ // request write 10 coils, starting at coil 0, address: 0x0000 -> 0, count: 0x000a -> 10
++ // payload bytes: 0x02 -> 2, values: 0xcd -> 1100 1101, 0x02 -> 0000 0010
++ request = QModbusRequest(QModbusRequest::WriteMultipleCoils,
++ QByteArray::fromHex("0000000a02cd02"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, equals request's first 4 bytes
++ QCOMPARE(response.data(), QByteArray::fromHex("0000000a"));
++
++ // request read 10 coils starting at coil 0, address: 0x0000 -> 0, count: 0x000a -> 10
++ request = QModbusRequest(QModbusRequest::ReadCoils, QByteArray::fromHex("0000000a"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, byte count: 0x02 -> 2, status: 0xcd -> 1100 1101, 0x02 -> 0000 0020
++ QCOMPARE(response.data(), QByteArray::fromHex("02cd02"));
++ }
++
++ void testProcessReadDiscreteInputsRequest()
++ {
++ server.setData(QModbusDataUnit::DiscreteInputs, 172, true);
++ // request read DiscreteInput 173, address: 0x00ac -> 172, count: 0x0001 -> 1
++ QModbusRequest request = QModbusRequest(QModbusRequest::ReadDiscreteInputs,
++ QByteArray::fromHex("00ac0001"));
++ QModbusResponse response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, byte count: 0x01 -> 1, status: 0x01 -> 0000 0001
++ QCOMPARE(response.data(), QByteArray::fromHex("0101"));
++
++ server.setData(QModbusDataUnit::DiscreteInputs, 19, quint16(0x0010));
++ // request read 10 inputs starting at input 20, address: 0x0013 -> 19, count: 0x000a -> 10
++ request = QModbusRequest(QModbusRequest::ReadDiscreteInputs, QByteArray::fromHex("0013000a"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, byte count: 0x02 -> 1, status: 0x00 -> 0000 0000, 0x10 -> 0001 0000
++ QCOMPARE(response.data(), QByteArray::fromHex("020100"));
++
++ // request read 10 inputs starting at input 501
++ request = QModbusRequest(QModbusRequest::ReadDiscreteInputs, QByteArray::fromHex("01f5000a"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.data(), QByteArray::fromHex("02"));
++
++ // request read 2001 inputs starting at input 0
++ request = QModbusRequest(QModbusRequest::ReadDiscreteInputs, QByteArray::fromHex("000007d1"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ // request read 0 inputs starting at input 0
++ request = QModbusRequest(QModbusRequest::ReadDiscreteInputs, QByteArray::fromHex("00000000"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++ }
++
++ void testProcessReadHoldingRegistersRequest()
++ {
++ server.setData(QModbusDataUnit::HoldingRegisters, 172, 1234u);
++ server.setData(QModbusDataUnit::HoldingRegisters, 173, 4321u);
++
++ // request read holding registers 173, address: 0x00ac -> 172, count: 0x0001 -> 1
++ QModbusRequest request = QModbusRequest(QModbusRequest::ReadHoldingRegisters,
++ QByteArray::fromHex("00ac0001"));
++ QModbusResponse response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, byte count: 0x02 -> 2, value: 1234u -> 04d2
++ QCOMPARE(response.data(), QByteArray::fromHex("0204d2"));
++
++ // request read 2 registers starting at 172
++ request = QModbusRequest(QModbusRequest::ReadHoldingRegisters,
++ QByteArray::fromHex("00ac0002"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, byte count: 0x04 -> 4, status: 1234u = 04d2, 4321u =10e1
++ QCOMPARE(response.data(), QByteArray::fromHex("0404d210e1"));
++
++ // request read 10 registers starting at offset 501
++ request = QModbusRequest(QModbusRequest::ReadHoldingRegisters,
++ QByteArray::fromHex("01f5000a"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.data(), QByteArray::fromHex("02"));
++
++ // request read 126 registers starting at offset 0
++ request = QModbusRequest(QModbusRequest::ReadHoldingRegisters, QByteArray::fromHex("0000007e"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ // request read 0 registers starting at offset 0
++ request = QModbusRequest(QModbusRequest::ReadHoldingRegisters, QByteArray::fromHex("00000000"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++ }
++
++ void testProcessReadInputRegistersRequest()
++ {
++ server.setData(QModbusDataUnit::InputRegisters, 172, 1234u);
++ server.setData(QModbusDataUnit::InputRegisters, 173, 4321u);
++
++ // request read input registers 173, address: 0x00ac -> 172, count: 0x0001 -> 1
++ QModbusRequest request = QModbusRequest(QModbusRequest::ReadInputRegisters,
++ QByteArray::fromHex("00ac0001"));
++ QModbusResponse response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, byte count: 0x02 -> 2, value: 1234u -> 04d2
++ QCOMPARE(response.data(), QByteArray::fromHex("0204d2"));
++
++ // request read 2 registers starting at 172
++ request = QModbusRequest(QModbusRequest::ReadInputRegisters,
++ QByteArray::fromHex("00ac0002"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, byte count: 0x04 -> 4, status: 1234u = 04d2, 4321u =10e1
++ QCOMPARE(response.data(), QByteArray::fromHex("0404d210e1"));
++
++ // request read 10 registers starting at offset 501
++ request = QModbusRequest(QModbusRequest::ReadInputRegisters,
++ QByteArray::fromHex("01f5000a"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.data(), QByteArray::fromHex("02"));
++
++ // request read 1 register at offset 0 with corrupt message (+1 byte)
++ request = QModbusRequest(QModbusRequest::ReadInputRegisters,
++ QByteArray::fromHex("0000000100"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ // request read 126 registers starting at offset 0
++ request = QModbusRequest(QModbusRequest::ReadInputRegisters, QByteArray::fromHex("0000007e"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ // request read 0 registers starting at offset 0
++ request = QModbusRequest(QModbusRequest::ReadInputRegisters, QByteArray::fromHex("00000000"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++ }
++
++ void testProcessWriteSingleRegisterRequest()
++ {
++ // request write register 173, address: 0x00ac -> 172, value: 0x00ff
++ QModbusRequest request = QModbusRequest(QModbusRequest::WriteSingleRegister,
++ QByteArray::fromHex("00ac00ff"));
++ QModbusResponse response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("00ac00ff"));
++
++ // request write register at offset 501
++ request = QModbusRequest(QModbusRequest::WriteSingleRegister,
++ QByteArray::fromHex("01f500ff"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.data(), QByteArray::fromHex("02"));
++
++ // request write register at offset 0 value only 1 byte
++ request = QModbusRequest(QModbusRequest::WriteSingleRegister,
++ QByteArray::fromHex("00007d"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++ }
++
++ void testProcessReadExceptionStatus()
++ {
++ // simulate Modicon 484 (start at coil 257 up to 264)
++ server.setValue(QModbusServer::ExceptionStatusOffset, 256u);
++ // set the exception status byte to 0000 0011 simulating two bits set
++ // request write Coil 257, address: 0x0100 -> 256, value: 0xff00 -> ON
++ QModbusRequest request = QModbusRequest(QModbusRequest::WriteSingleCoil,
++ QByteArray::fromHex("0100ff00"));
++ QModbusResponse response = server.processRequest(request);
++ // request write Coil 258, address: 0x0101 -> 257, value: 0xff00 -> ON
++ request = QModbusRequest(QModbusRequest::WriteSingleCoil,
++ QByteArray::fromHex("0101ff00"));
++ response = server.processRequest(request);
++
++ request = QModbusRequest(QModbusRequest::ReadExceptionStatus);
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ // invalid request test
++ request = QModbusRequest(QModbusRequest::ReadExceptionStatus,
++ QByteArray::fromHex("007d"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++ }
++
++ void testProcessDiagnosticsRequest()
++ {
++ // subfunction 00
++ QModbusRequest request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("000000ffabcd"));
++ QModbusResponse response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("000000ffabcd"));
++
++ //subfunction 01
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("00010000"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("00010000"));
++
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("0001ff00"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("0001ff00"));
++
++ // invalidate
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("0001ff01"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ // subfunction 02
++ // validate
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("00020000"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("00020000"));
++
++ // invalidate
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("00020001"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("0002000001"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ // subfunction 03
++ // validate
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("00030a00"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("00030a00"));
++
++ // invalidate
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("00030a01"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("00030a0101"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ // subfunction 04
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("00040000"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isValid(), false);
++ QCOMPARE(response.isException(), false);
++ QCOMPARE(response.data(), QByteArray());
++ QCOMPARE(response.functionCode(), QModbusResponse::Invalid);
++
++ // invalidate
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("00040001"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("0004000001"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ // subfunction 10: counter value is 0
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("000a0000"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("000a0000"));
++
++ // invalidate
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("000a0001"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("000a000001"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ // subfunction 11: counter value is 0
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("000b0000"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("000b0000"));
++
++ // invalidate
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("000b0001"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("000b000001"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ // subfunction 12: counter value is 0
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("000c0000"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("000c0000"));
++
++ // invalidate
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("000c0001"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("000c000001"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ // subfunction 13: counter value is 0
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("000d0000"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("000d0000"));
++
++ // invalidate
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("000d0001"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("000d000001"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ // subfunction 14: counter value is 0
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("000e0000"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("000e0000"));
++
++ // invalidate
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("000e0001"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("000e000001"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ // subfunction 15: counter value is 0
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("000f0000"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("000f0000"));
++
++ // invalidate
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("000f0001"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("000f000001"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ // subfunction 16: counter value is 0
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("00100000"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("00100000"));
++
++ // invalidate
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("00100001"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("0010000001"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ // subfunction 17: counter value is 0
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("00110000"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("00110000"));
++
++ // invalidate
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("00110001"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("0011000001"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ // subfunction 18: counter value is 0
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("00120000"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("00120000"));
++
++ // invalidate
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("00120001"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("0012000001"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++
++ // subfunction > 4 < 10
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("0005ff01"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("01"));
++
++ // subfunction > 18
++ request = QModbusRequest(QModbusRequest::Diagnostics,
++ QByteArray::fromHex("00130000"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("01"));
++ }
++
++ void testProcessGetCommEventCounter()
++ {
++ TestServer local; // Used later to control the correct event amount.
++
++ QModbusRequest request = QModbusRequest(QModbusRequest::GetCommEventCounter);
++ QModbusResponse response = local.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ QCOMPARE(response.data(), QByteArray::fromHex("00000000"));
++
++ // TODO: Add more tests once event handling is implemented.
++
++ request = QModbusRequest(QModbusRequest::GetCommEventCounter, quint8(10));
++ response = local.processRequest(request);
++ QCOMPARE(response.isException(), true);
++
++ }
++
++ void testProcessGetCommEventLogRequest()
++ {
++ TestServer local; // Used later to control the correct event amount.
++
++ QModbusRequest request = QModbusRequest(QModbusRequest::GetCommEventLog);
++ QModbusResponse response = local.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ QCOMPARE(response.data(), QByteArray::fromHex("06000000000000"));
++
++ // TODO: Add more tests once event handling is implemented.
++
++ request = QModbusRequest(QModbusRequest::GetCommEventLog, quint8(10));
++ response = local.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ }
++
++ void testProcessWriteMultipleRegistersRequest()
++ {
++ // request write at register 173, address: 0x00ac -> 172, value: 0x00ff 0x1234
++ QModbusRequest request = QModbusRequest(QModbusRequest::WriteMultipleRegisters,
++ QByteArray::fromHex("00ac00020400ff1234"));
++ QModbusResponse response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("00ac0002"));
++
++ // request write register at offset 501
++ request = QModbusRequest(QModbusRequest::WriteMultipleRegisters,
++ QByteArray::fromHex("01f500010200ff"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.data(), QByteArray::fromHex("02"));
++
++ // request write 1 register at offset 0 value only 1 byte
++ request = QModbusRequest(QModbusRequest::WriteMultipleRegisters,
++ QByteArray::fromHex("000000010200"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++ }
++
++ void testReportServerId()
++ {
++ QModbusRequest request = QModbusRequest(QModbusRequest::ReportServerId);
++ QModbusResponse response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ QCOMPARE(response.functionCode(), QModbusRequest::ReportServerId);
++
++ const QByteArray additionalData = "Qt Modbus Server";
++ QCOMPARE(server.value(QModbusServer::ServerIdentifier).value<quint8>(), quint8(0x0a));
++ QCOMPARE(server.value(QModbusServer::RunIndicatorStatus).value<quint8>(), quint8(0xff));
++ QCOMPARE(server.value(QModbusServer::AdditionalData).toByteArray(), additionalData);
++
++ QByteArray data = QByteArray::fromHex("0aff") + additionalData;
++ data.prepend(quint8(data.size()));
++ QCOMPARE(response.data(), data);
++
++ request = QModbusRequest(QModbusRequest::ReportServerId, data);
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.exceptionCode(), QModbusPdu::IllegalDataValue);
++
++ server.setValue(QModbusServer::ServerIdentifier, quint8(0xff));
++ QCOMPARE(server.setValue(QModbusServer::ServerIdentifier, additionalData), false);
++ QCOMPARE(server.value(QModbusServer::ServerIdentifier).value<quint8>(), quint8(0xff));
++
++ server.setValue(QModbusServer::RunIndicatorStatus, quint8(0x00));
++ QCOMPARE(server.setValue(QModbusServer::RunIndicatorStatus, quint8(0xab)), false);
++ QCOMPARE(server.value(QModbusServer::RunIndicatorStatus).value<quint8>(), quint8(0x00));
++
++ server.setValue(QModbusServer::AdditionalData, QByteArray("TestData"));
++ QCOMPARE(server.setValue(QModbusServer::AdditionalData, QStringList()), false);
++ QCOMPARE(server.value(QModbusServer::AdditionalData).toByteArray(), QByteArray("TestData"));
++ }
++
++ void testMaskWriteRegister()
++ {
++ // preset register 172 with value 18 (0x0012h)
++ server.setData(QModbusDataUnit::HoldingRegisters, 172, 18u);
++ //mask request register 172 with andMask: 242 (0x00f2)
++ // orMask: 37 (0x0025)
++ // result: 23 (0x0017)
++ QModbusRequest request = QModbusRequest(QModbusRequest::MaskWriteRegister,
++ QByteArray::fromHex("00ac00f20025"));
++ QModbusResponse response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("00ac00f20025"));
++ // validate contents after masking
++ quint16 data;
++ QVERIFY(server.data(QModbusDataUnit::HoldingRegisters, 172, &data));
++ QCOMPARE(data, quint16(23));
++
++ // invalidate use register 501:
++ request = QModbusRequest(QModbusRequest::MaskWriteRegister,
++ QByteArray::fromHex("01f500f20025"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.data(), QByteArray::fromHex("02"));
++ // invalidate with one bytes less data:
++ request = QModbusRequest(QModbusRequest::MaskWriteRegister,
++ QByteArray::fromHex("00ac00f200"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++ // invalidate with one bytes more data:
++ request = QModbusRequest(QModbusRequest::MaskWriteRegister,
++ QByteArray::fromHex("00ac00f2002500"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++ }
++
++ void testProcessReadWriteMultipleRegistersRequest()
++ {
++ server.setData(QModbusDataUnit::HoldingRegisters, 172, 1234u);
++ server.setData(QModbusDataUnit::HoldingRegisters, 173, 1235u);
++ server.setData(QModbusDataUnit::HoldingRegisters, 174, 1236u);
++
++ // request read 3 registers at register 173, address: 0x00ac -> 172,
++ // write 3 registers at register 173: 1237u, 1238u, 1239u
++ QModbusRequest request = QModbusRequest(QModbusRequest::ReadWriteMultipleRegisters,
++ QByteArray::fromHex("00ac000300ac00030604d504d604d7"));
++ QModbusResponse response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ // response, equals request
++ QCOMPARE(response.data(), QByteArray::fromHex("0604d504d604d7"));
++
++ // request write register at offset 501
++ request = QModbusRequest(QModbusRequest::ReadWriteMultipleRegisters,
++ QByteArray::fromHex("00ac000301f500010200ff"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.data(), QByteArray::fromHex("02"));
++
++ // request write 3 registers at offset 173 value only 5 bytes
++ request = QModbusRequest(QModbusRequest::ReadWriteMultipleRegisters,
++ QByteArray::fromHex("00ac000300ac00030604d504d604"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++ }
++
++ void testProcessReadFifoQueue()
++ {
++ // prepare a fifo with two object values, pointer address is 172 with value 2 items
++ server.setData(QModbusDataUnit::HoldingRegisters, 172, 2u);
++ server.setData(QModbusDataUnit::HoldingRegisters, 173, 1235u);
++ server.setData(QModbusDataUnit::HoldingRegisters, 174, 1236u);
++ // request read fifo queue at fifo pointer address 172
++ QModbusRequest request = QModbusRequest(QModbusRequest::ReadFifoQueue,
++ QByteArray::fromHex("00ac"));
++ QModbusResponse response = server.processRequest(request);
++ QCOMPARE(response.isException(), false);
++ QCOMPARE(response.data(), QByteArray::fromHex("0006000204d304d4"));
++ // invalidate tests
++ // invalid offset address (501)
++ request = QModbusRequest(QModbusRequest::ReadFifoQueue,
++ QByteArray::fromHex("01f5"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.data(), QByteArray::fromHex("02"));
++ // invalid fifo count > 31
++ server.setData(QModbusDataUnit::HoldingRegisters, 172, 32u);
++ request = QModbusRequest(QModbusRequest::ReadFifoQueue,
++ QByteArray::fromHex("00ac"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.data(), QByteArray::fromHex("03"));
++ // invalid fifo data address beyond 500, fifo values 3 (500-502)
++ server.setData(QModbusDataUnit::HoldingRegisters, 499, 3u);
++ request = QModbusRequest(QModbusRequest::ReadFifoQueue,
++ QByteArray::fromHex("01f3"));
++ response = server.processRequest(request);
++ QCOMPARE(response.isException(), true);
++ QCOMPARE(response.data(), QByteArray::fromHex("02"));
++ }
++
++ void tst_dataCalls_data()
++ {
++ QTest::addColumn<QModbusDataUnit::RegisterType>("registerType");
++
++ QTest::newRow("InitCoils") << QModbusDataUnit::Coils;
++ QTest::newRow("InitDiscreteInputs") << QModbusDataUnit::DiscreteInputs;
++ QTest::newRow("InitInputRegisters") << QModbusDataUnit::InputRegisters;
++ QTest::newRow("InitHoldingRegisters") << QModbusDataUnit::HoldingRegisters;
++ QTest::newRow("InitInvalid") << QModbusDataUnit::Invalid;
++ }
++
++ void tst_dataCalls()
++ {
++ QFETCH(QModbusDataUnit::RegisterType, registerType);
++ // basic assumption, all registers have start address 0 and size 500
++
++ const bool validDataUnit = (registerType != QModbusDataUnit::Invalid);
++ //test initialization of 0
++ if (validDataUnit) {
++ for (int i = 0; i < MAP_RANGE; i++) {
++ quint16 data = 123;
++ QVERIFY(server.data(registerType, i, &data));
++ QCOMPARE(data, quint16(0));
++ }
++ } else {
++ quint16 data = 0;
++ QVERIFY(!server.data(registerType, 0 , &data));
++ }
++
++ quint16 data = 0;
++ QSignalSpy writtenSpy(&server, &TestServer::dataWritten);
++ QVERIFY(writtenSpy.isEmpty());
++
++ QVERIFY(!server.data(registerType, MAP_RANGE+1, &data)); // out of range
++ QCOMPARE(data, quint16(0));
++ QVERIFY(!server.data(registerType, 1, 0)); // invalid data pointer
++ QCOMPARE(data, quint16(0));
++
++ QCOMPARE(server.setData(registerType, 1, 444), validDataUnit);
++ QCOMPARE(server.data(registerType, 1, &data), validDataUnit);
++ if (validDataUnit) {
++ QCOMPARE(data, quint16(444));
++ QTRY_COMPARE(writtenSpy.size(), 1);
++ QList<QVariant> signalData = writtenSpy.at(0);
++ QCOMPARE(signalData.size(), 3);
++ QCOMPARE(signalData.at(0).value<QModbusDataUnit::RegisterType>(), registerType);
++ QCOMPARE(signalData.at(1).toInt(), 1);
++ QCOMPARE(signalData.at(2).toInt(), 1);
++ } else {
++ QCOMPARE(data, quint16(0));
++ QTRY_VERIFY(writtenSpy.isEmpty());
++ }
++
++ //write 444 again but this time no dataWritten since no value change
++ data = 0;
++ writtenSpy.clear();
++ QCOMPARE(server.setData(registerType, 1, 444), validDataUnit);
++ QCOMPARE(server.data(registerType, 1, &data), validDataUnit);
++ if (validDataUnit)
++ QCOMPARE(data, quint16(444));
++ else
++ QCOMPARE(data, quint16(0));
++ QTRY_VERIFY(writtenSpy.isEmpty()); //
++
++ QVERIFY(!server.data(registerType, 1, 0)); // out of range although value set
++ QVERIFY(!server.setData(registerType, -1, 1));
++ QVERIFY(!server.setData(registerType, MAP_RANGE+1, 1));
++ QTRY_VERIFY(writtenSpy.isEmpty());
++
++ //testing server.setData(ModbusDataUnit&)
++
++ const QList<quint16> valueVector = { 1, 1, 1, 1, 1 };
++ const QList<quint16> zeroVector = { 0, 0, 0, 0, 0 };
++ QModbusDataUnit rangeUnit(registerType, 7, valueVector);
++ QCOMPARE(rangeUnit.valueCount(), 5);
++ QCOMPARE(rangeUnit.values().size(), 5);
++ QCOMPARE(rangeUnit.startAddress(), 7);
++ QVERIFY(rangeUnit.values() == valueVector);
++ QVERIFY(rangeUnit.registerType() == registerType);
++
++ writtenSpy.clear();
++ QVERIFY(server.setData(rangeUnit) == validDataUnit);
++ if (validDataUnit) {
++ for (qsizetype i = rangeUnit.startAddress();
++ i < rangeUnit.startAddress() + rangeUnit.valueCount(); i++) {
++ quint16 readData = 0;
++ QVERIFY(server.data(registerType, i, &readData));
++ QCOMPARE(readData, valueVector.at(i-rangeUnit.startAddress()));
++ }
++
++ QTRY_COMPARE(writtenSpy.size(), 1);
++ QList<QVariant> signalData = writtenSpy.at(0);
++ QCOMPARE(signalData.size(), 3);
++ QCOMPARE(signalData.at(0).value<QModbusDataUnit::RegisterType>(), registerType);
++ QCOMPARE(signalData.at(1).toInt(), rangeUnit.startAddress());
++ QCOMPARE(signalData.at(2).toInt(), rangeUnit.valueCount());
++ }
++
++ // no writtenData() signal when writing same rangeUnit again
++ writtenSpy.clear();
++ QVERIFY(server.setData(rangeUnit) == validDataUnit);
++ QTRY_VERIFY(writtenSpy.isEmpty());
++
++ //never fits anywhere
++ QModbusDataUnit oversizeUnit(registerType, 0, MAP_RANGE*2);
++ QCOMPARE(oversizeUnit.valueCount(), MAP_RANGE*2);
++ QCOMPARE(oversizeUnit.values().size(), MAP_RANGE*2);
++ QCOMPARE(oversizeUnit.startAddress(), 0);
++ QCOMPARE(oversizeUnit.registerType(), registerType);
++
++ //completely outside of valid range
++ writtenSpy.clear();
++ rangeUnit.setStartAddress(MAP_RANGE + 1 );
++ QVERIFY(!server.setData(rangeUnit));
++
++ //slightly outside of valid range
++ rangeUnit.setStartAddress(MAP_RANGE - 2);
++ QVERIFY(!server.setData(rangeUnit));
++
++ //slightly outside of valid range in the bottom
++ rangeUnit.setStartAddress(-1);
++ QVERIFY(!server.setData(rangeUnit));
++
++ //input data unit doesn't fit
++ QVERIFY(!server.setData(oversizeUnit));
++ oversizeUnit.setStartAddress(-1);
++ QVERIFY(!server.setData(oversizeUnit));
++ oversizeUnit.setStartAddress(MAP_RANGE+1);
++ QVERIFY(!server.setData(oversizeUnit));
++ oversizeUnit.setStartAddress(MAP_RANGE-2);
++ QVERIFY(!server.setData(oversizeUnit));
++ QTRY_VERIFY(writtenSpy.isEmpty());
++
++ //testing server.data(QModbusDataUnit *)
++ QModbusDataUnit requestUnit(registerType, 7, 5);
++ QCOMPARE(requestUnit.valueCount(), 5);
++ QCOMPARE(requestUnit.values().size(), 5);
++ QCOMPARE(requestUnit.startAddress(), 7);
++ QVERIFY(requestUnit.registerType() == registerType);
++ QVERIFY(requestUnit.values() != valueVector);
++
++ QVERIFY(server.data(&requestUnit) == validDataUnit);
++ if (validDataUnit) {
++ QVERIFY(requestUnit.values() == valueVector);
++ QCOMPARE(requestUnit.valueCount(), 5);
++ QCOMPARE(requestUnit.values().size(), 5);
++ QCOMPARE(requestUnit.startAddress(), 7);
++ }
++
++ requestUnit.setValues(zeroVector);
++ QVERIFY(requestUnit.values() != valueVector);
++ QVERIFY(requestUnit.values() == zeroVector);
++
++ requestUnit.setStartAddress(MAP_RANGE + 1);
++ QVERIFY(!server.data(&requestUnit));
++ requestUnit.setStartAddress(MAP_RANGE - 2);
++ QVERIFY(!server.data(&requestUnit));
++
++ // ask for entire map
++ requestUnit.setStartAddress(-1);
++ QVERIFY(server.data(&requestUnit) == validDataUnit);
++ if (validDataUnit) {
++ QCOMPARE(requestUnit.valueCount(), MAP_RANGE);
++ QCOMPARE(requestUnit.values().size(), MAP_RANGE);
++ }
++
++ oversizeUnit.setStartAddress(0);
++ QVERIFY(!server.data(&oversizeUnit));
++ oversizeUnit.setStartAddress(MAP_RANGE+1);
++ QVERIFY(!server.data(&oversizeUnit));
++ oversizeUnit.setStartAddress(MAP_RANGE-2);
++ QVERIFY(!server.data(&oversizeUnit));
++
++ oversizeUnit.setStartAddress(-1);
++ QVERIFY(server.data(&oversizeUnit) == validDataUnit);
++ if (validDataUnit) {
++ QCOMPARE(oversizeUnit.valueCount(), MAP_RANGE);
++ QCOMPARE(oversizeUnit.values().size(), MAP_RANGE);
++ }
++ }
++
++ void tst_dataCallsShiftedIndex()
++ {
++ TestServer overlapIndex;
++ const quint16 dataCount = 4;
++
++ QModbusDataUnitMap map;
++ map.insert(QModbusDataUnit::HoldingRegisters, {QModbusDataUnit::HoldingRegisters, 3, dataCount});
++ server.setMap(map);
++ QCOMPARE(map.value(QModbusDataUnit::HoldingRegisters).valueCount(), dataCount);
++
++ QVERIFY(server.setData(QModbusDataUnit::HoldingRegisters, 3, 0xaaaa));
++ QVERIFY(server.setData(QModbusDataUnit::HoldingRegisters, 4, 0xbbbb));
++ QVERIFY(server.setData(QModbusDataUnit::HoldingRegisters, 5, 0xcccc));
++ QVERIFY(server.setData(QModbusDataUnit::HoldingRegisters, 6, 0xdddd));
++
++ // ********** Test individual access ********** //
++ quint16 data = 0;
++ QVERIFY(server.data(QModbusDataUnit::HoldingRegisters, 3, &data));
++ QCOMPARE(data, 0xaaaa);
++ QVERIFY(server.data(QModbusDataUnit::HoldingRegisters, 4, &data));
++ QCOMPARE(data, 0xbbbb);
++ QVERIFY(server.data(QModbusDataUnit::HoldingRegisters, 5, &data));
++ QCOMPARE(data, 0xcccc);
++ QVERIFY(server.data(QModbusDataUnit::HoldingRegisters, 6, &data));
++ QCOMPARE(data, 0xdddd);
++
++
++ // block write at start
++ QModbusDataUnit unit(QModbusDataUnit::HoldingRegisters, 3, 3);
++ for (qsizetype i = 0; i < 3; i++)
++ unit.setValue(i, quint16(0x1111 + i));
++ QVERIFY(server.setData(unit));
++
++ QModbusDataUnit results(QModbusDataUnit::HoldingRegisters, 3, 3);
++ QVERIFY(server.data(&results));
++ QCOMPARE(results.values(), QList<quint16>({ 0x1111, 0x1112, 0x1113 }));
++
++ //i block write at end
++ unit.setStartAddress(4);
++ results.setStartAddress(4);
++ unit.setValues({0x1, 0x2, 0x3});
++ QVERIFY(server.setData(unit));
++ QVERIFY(server.data(&results));
++ QCOMPARE(results.values(), QList<quint16>({ 0x1, 0x2, 0x3 }));
++
++ unit.setStartAddress(2); // overlap in front
++ QVERIFY(!server.setData(unit));
++ unit.setStartAddress(5); // overlap at end
++ QVERIFY(!server.setData(unit));
++
++ data = 0;
++ QVERIFY(!server.data(QModbusDataUnit::HoldingRegisters, 7, &data));
++ QCOMPARE(data, 0);
++ QVERIFY(!server.data(QModbusDataUnit::HoldingRegisters, 2, &data));
++ QCOMPARE(data, 0);
++
++ QVERIFY(!server.setData(QModbusDataUnit::HoldingRegisters, 7, 0xabcd));
++ QVERIFY(!server.setData(QModbusDataUnit::HoldingRegisters, 2, 0xabcd));
++
++ QVERIFY(!server.data(QModbusDataUnit::HoldingRegisters, 7, &data));
++ QCOMPARE(data, 0);
++ QVERIFY(!server.data(QModbusDataUnit::HoldingRegisters, 2, &data));
++ QCOMPARE(data, 0);
++ }
++
++ void tst_serverAddress()
++ {
++ server.setServerAddress(56);
++ QCOMPARE(server.serverAddress(), 56);
++ server.setServerAddress(1);
++ QCOMPARE(server.serverAddress(), 1);
++ }
++
++ void tst_diagnosticRegister()
++ {
++ server.setValue(QModbusServer::DiagnosticRegister, 56u);
++ QCOMPARE(server.value(QModbusServer::DiagnosticRegister).value<quint16>(), quint16(56));
++ server.setValue(QModbusServer::DiagnosticRegister, 1u);
++ QCOMPARE(server.value(QModbusServer::DiagnosticRegister).value<quint16>(), quint16(1));
++ }
++
++ void tst_exceptionStatusOffset()
++ {
++ server.setValue(QModbusServer::ExceptionStatusOffset, 256u);
++ QCOMPARE(server.value(QModbusServer::ExceptionStatusOffset).value<quint16>(), quint16(256));
++ server.setValue(QModbusServer::ExceptionStatusOffset, 0x0000);
++ QCOMPARE(server.value(QModbusServer::ExceptionStatusOffset).value<quint16>(), quint16(0));
++ }
++
++ void tst_readWriteDataInheritance()
++ {
++ class DebugHandler
++ {
++ public:
++ DebugHandler(QtMessageHandler newMessageHandler)
++ : oldMessageHandler(qInstallMessageHandler(newMessageHandler)) {}
++ ~DebugHandler() {
++ qInstallMessageHandler(oldMessageHandler);
++ }
++ private:
++ QtMessageHandler oldMessageHandler;
++ };
++
++ class InheritanceTestServer : public QModbusServer
++ {
++ public:
++ void close() override {}
++ bool open() override { return true; }
++
++ bool readData(QModbusDataUnit *) const override {
++ qDebug() << "QModbusServer::data() call did end in the expected OVERRIDE.";
++ return false;
++ }
++ bool writeData(const QModbusDataUnit &) override {
++ qDebug() << "QModbusServer::setData() call did end in the expected OVERRIDE.";
++ return false;
++ }
++ };
++
++ InheritanceTestServer s;
++ DebugHandler mhs(myMessageHandler);
++ {
++ QModbusDataUnit unit;
++ s.data(&unit);
++ }
++ QCOMPARE(s_msg, QString("QModbusServer::data() call did end in the expected OVERRIDE."));
++ {
++ s.data(QModbusDataUnit::Coils, 0u, nullptr);
++ }
++ QCOMPARE(s_msg, QString("QModbusServer::data() call did end in the expected OVERRIDE."));
++ {
++ s.setData(QModbusDataUnit());
++ }
++ QCOMPARE(s_msg, QString("QModbusServer::setData() call did end in the expected OVERRIDE."));
++ {
++ s.setData(QModbusDataUnit::Coils, 0u, 0u);
++ }
++ QCOMPARE(s_msg, QString("QModbusServer::setData() call did end in the expected OVERRIDE."));
++ }
++
++ void testReadWriteDataMissingOrInvalidRegister()
++ {
++ TestServer local;
++ local.setMap({ { QModbusDataUnit::Invalid, QModbusDataUnit() },
++ { QModbusDataUnit::Coils, QModbusDataUnit(QModbusDataUnit::Coils) },
++ { QModbusDataUnit::DiscreteInputs, QModbusDataUnit(QModbusDataUnit::DiscreteInputs) }});
++
++ QModbusDataUnit invalid;
++ QCOMPARE(local.data(&invalid), false);
++ QCOMPARE(local.setData(invalid), false);
++
++ QModbusDataUnit missing(QModbusDataUnit::HoldingRegisters);
++ QCOMPARE(local.data(&missing), false);
++ QCOMPARE(local.setData(missing), false);
++ }
++
++ void testIllegalTcpFunctionCodes()
++ {
++ class ModbusTcpServer : public QModbusTcpServer
++ {
++ public:
++ QModbusResponse processRequest(const QModbusPdu &request) override {
++ return QModbusTcpServer::processRequest(request);
++ }
++
++ };
++ ModbusTcpServer local;
++
++ QModbusRequest request(QModbusRequest::ReadExceptionStatus);
++ QCOMPARE(local.processRequest(request).exceptionCode(), QModbusPdu::IllegalFunction);
++
++ request = QModbusRequest(QModbusRequest::Diagnostics);
++ QCOMPARE(local.processRequest(request).exceptionCode(), QModbusPdu::IllegalFunction);
++
++ request = QModbusRequest(QModbusRequest::GetCommEventCounter);
++ QCOMPARE(local.processRequest(request).exceptionCode(), QModbusPdu::IllegalFunction);
++
++ request = QModbusRequest(QModbusRequest::GetCommEventLog);
++ QCOMPARE(local.processRequest(request).exceptionCode(), QModbusPdu::IllegalFunction);
++
++ request = QModbusRequest(QModbusRequest::ReportServerId);
++ QCOMPARE(local.processRequest(request).exceptionCode(), QModbusPdu::IllegalFunction);
++ }
++
++ void testTcpMbapLengthField_data()
++ {
++ QTest::addColumn<QByteArray>("wire");
++ QTest::addColumn<QByteArray>("expected");
++
++ // A valid request, appended to every rejected frame: it must not be
++ // answered out of a desynchronized buffer.
++ const QByteArray request = QByteArray::fromHex("000100000006ff0300000001");
++
++ // 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("000200000000ff") + request << QByteArray();
++ QTest::newRow("length one")
++ << QByteArray::fromHex("000200000001ff") + request << QByteArray();
++ QTest::newRow("one above the maximum")
++ << QByteArray::fromHex("0002000000ffff") + request << QByteArray();
++ QTest::newRow("length huge")
++ << QByteArray::fromHex("00020000ffffff") + request << QByteArray();
++
++ // 254 describes the largest legal frame, a 260 byte ADU. It is answered,
++ // here with an exception because the padding is not a valid PDU.
++ QTest::newRow("maximum length")
++ << QByteArray::fromHex("0002000000feff") + QByteArray(253, 0x03)
++ << QByteArray::fromHex("000200000003ff8303");
++
++ // A legal length of 2 declares a one byte PDU, too short for the function
++ // code it carries. The missing bytes must not be taken from the request
++ // that follows: the short frame is answered with an exception and the
++ // following request with its own correct response.
++ QTest::newRow("declared length bounds the PDU")
++ << QByteArray::fromHex("00bb00000002ff03") + request
++ << QByteArray::fromHex("00bb00000003ff8001")
++ + QByteArray::fromHex("000100000005ff03020000");
++ }
++
++ void testTcpMbapLengthField()
++ {
++ QFETCH(QByteArray, wire);
++ QFETCH(QByteArray, expected);
++
++ QModbusTcpServer modbusServer;
++ modbusServer.setMap({ { QModbusDataUnit::HoldingRegisters,
++ { QModbusDataUnit::HoldingRegisters, 0, MAP_RANGE } } });
++ modbusServer.setConnectionParameter(QModbusDevice::NetworkAddressParameter,
++ QStringLiteral("127.0.0.1"));
++ modbusServer.setConnectionParameter(QModbusDevice::NetworkPortParameter, 0);
++ QVERIFY(modbusServer.connectDevice());
++
++ // The listening socket is a child, so the port the OS picked is reachable
++ // without binding a second time and racing for the same port.
++ auto *listener = modbusServer.findChild<QTcpServer *>();
++ QVERIFY(listener);
++
++ QTcpSocket socket;
++ socket.connectToHost(QHostAddress::LocalHost, listener->serverPort());
++ QTRY_COMPARE(socket.state(), QAbstractSocket::ConnectedState);
++
++ if (expected.isEmpty()) {
++ const QByteArray warning = "(TCP server) Invalid MBAP length field: "
++ + QByteArray::number(qFromBigEndian<quint16>(wire.constData() + 4))
++ + " closing connection.";
++ QTest::ignoreMessage(QtWarningMsg, warning.constData());
++ }
++
++ socket.write(wire);
++ if (expected.isEmpty()) {
++ QTRY_COMPARE(socket.state(), QAbstractSocket::UnconnectedState);
++ QCOMPARE(socket.readAll(), QByteArray());
++ QCOMPARE(modbusServer.error(), QModbusDevice::ProtocolError);
++ return;
++ }
++
++ QTRY_COMPARE(socket.bytesAvailable(), qint64(expected.size()));
++ QCOMPARE(socket.readAll(), expected);
++ QCOMPARE(socket.state(), QAbstractSocket::ConnectedState);
++ }
++
++ void testQModbusServerOptions()
++ {
++ // TODO: Add a local class implementation to test value()/setValue with a different backing
++ // store. That's not only related to AsciiInputDelimiter, rather to all enum values there.
++
++ QCOMPARE(server.value(QModbusServer::AsciiInputDelimiter).toInt(), int('\n'));
++ QCOMPARE(server.setValue(QModbusServer::AsciiInputDelimiter, "Test"), false);
++ QCOMPARE(server.setValue(QModbusServer::AsciiInputDelimiter, '@'), true);
++ QCOMPARE(server.value(QModbusServer::AsciiInputDelimiter).toInt(), int('@'));
++
++ QVERIFY(server.setValue(QModbusServer::AsciiInputDelimiter, 'j'));
++ QCOMPARE(server.value(QModbusServer::AsciiInputDelimiter).toInt(), int('j'));
++ QVERIFY(server.setValue(QModbusServer::AsciiInputDelimiter, 0x6a));
++ QCOMPARE(server.value(QModbusServer::AsciiInputDelimiter).toInt(), int('j'));
++ QVERIFY(server.setValue(QModbusServer::AsciiInputDelimiter, 0x6a));
++ QCOMPARE(server.value(QModbusServer::AsciiInputDelimiter).toInt(), int('j'));
++ QVERIFY(!server.setValue(QModbusServer::AsciiInputDelimiter, 0x100));
++ QCOMPARE(server.value(QModbusServer::AsciiInputDelimiter).toInt(), int('j'));
++ QVERIFY(!server.setValue(QModbusServer::AsciiInputDelimiter, -1));
++
++ TestServer local;
++ QCOMPARE(local.value(QModbusServer::ListenOnlyMode).toBool(), false);
++ QCOMPARE(local.setValue(QModbusServer::ListenOnlyMode, "Test"), false);
++ QCOMPARE(local.setValue(QModbusServer::ListenOnlyMode, true), true);
++ QCOMPARE(local.value(QModbusServer::ListenOnlyMode).toBool(), true);
++ }
++
++ void testClearOverrunCounterAndFlag()
++ {
++ TestServer server;
++ server.setValue(QModbusServer::DiagnosticRegister, 0xffff);
++ server.processRequest(QModbusRequest(QModbusRequest::Diagnostics, quint16(0x0014), quint16(0)));
++ QCOMPARE(server.value(QModbusServer::DiagnosticRegister).value<quint16>(), quint16(0xfffe));
++ }
++
++ void testProcessEncapsulatedInterfaceTransportRequest()
++ {
++ QModbusDeviceIdentification objectPool;
++ QCOMPARE(objectPool.insert(QModbusDeviceIdentification::VendorNameObjectId,
++ "Company identification"), true);
++ QCOMPARE(objectPool.isValid(), false);
++ QCOMPARE(objectPool.insert(QModbusDeviceIdentification::ProductCodeObjectId,
++ "Product code"), true);
++ QCOMPARE(objectPool.isValid(), false);
++ QCOMPARE(objectPool.insert(QModbusDeviceIdentification::MajorMinorRevisionObjectId,
++ "V2.11"), true);
++ QCOMPARE(objectPool.isValid(), true);
++
++ QCOMPARE(server.setValue(QModbusServer::DeviceIdentification,
++ QVariant::fromValue<QModbusDeviceIdentification>(objectPool)), true);
++
++ auto response = server
++ .processRequest(QModbusRequest(QModbusRequest::EncapsulatedInterfaceTransport,
++ QByteArray::fromHex("0e0100")));
++ QCOMPARE(response.data(), QByteArray::fromHex("0e01010000030016")
++ + "Company identification" + QByteArray::fromHex("010c") + "Product code"
++ + QByteArray::fromHex("0205") + "V2.11");
++
++ QCOMPARE(objectPool.insert(QModbusDeviceIdentification::ProductCodeObjectId, QByteArray(
++ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
++ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
++ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")), true);
++ QCOMPARE(objectPool.isValid(), true);
++
++ QCOMPARE(server.setValue(QModbusServer::DeviceIdentification,
++ QVariant::fromValue<QModbusDeviceIdentification>(objectPool)), true);
++
++ response = server.processRequest(QModbusRequest(QModbusPdu::EncapsulatedInterfaceTransport,
++ QByteArray::fromHex("0e0100")));
++ QCOMPARE(response.data(), QByteArray::fromHex("0e0101ff02020016")
++ + "Company identification" + QByteArray::fromHex("01dc") + QByteArray(
++ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
++ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
++ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"));
++
++ response = server.processRequest(QModbusRequest(QModbusPdu::EncapsulatedInterfaceTransport,
++ QByteArray::fromHex("0e0102")));
++ QCOMPARE(response.data(), QByteArray::fromHex("0e01010000010205") + "V2.11");
++ }
++ };
++
++ QTEST_MAIN(tst_QModbusServer)
++
++ #include "tst_qmodbusserver.moc"