// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only 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 <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 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"
