// 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 <QtCore/qdebug.h>
#include <QtSerialBus/qmodbuspdu.h>

#include <QtTest/QtTest>

static QString s_msg;
static void myMessageHandler(QtMsgType, const QMessageLogContext &, const QString &msg)
{
    s_msg = msg;
}

class DebugHandler
{
public:
    DebugHandler(QtMessageHandler newMessageHandler)
        : oldMessageHandler(qInstallMessageHandler(newMessageHandler)) {}
    ~DebugHandler() {
        qInstallMessageHandler(oldMessageHandler);
    }
private:
    QtMessageHandler oldMessageHandler;
};

static int minimumDataSize(const QModbusPdu &pdu, bool request)
{
    if (pdu.isException())
        return 1;

    switch (pdu.functionCode()) {
    case QModbusPdu::ReadCoils:
    case QModbusPdu::ReadDiscreteInputs:
        return request ? 4 : 2;
    case QModbusPdu::WriteSingleCoil:
    case QModbusPdu::WriteSingleRegister:
        return 4;
    case QModbusPdu::ReadHoldingRegisters:
    case QModbusPdu::ReadInputRegisters:
        return request ? 4 : 3;
    case QModbusPdu::ReadExceptionStatus:
        return request ? 0 : 1;
    case QModbusPdu::Diagnostics:
        return 4;
    case QModbusPdu::GetCommEventCounter:
        return request ? 0 : 4;
    case QModbusPdu::GetCommEventLog:
        return request ? 0 : 8;
    case QModbusPdu::WriteMultipleCoils:
        return request ? 6 : 4;
    case QModbusPdu::WriteMultipleRegisters:
        return request ? 7 : 4;
    case QModbusPdu::ReportServerId:
        return request ? 0 : 3;
    case QModbusPdu::ReadFileRecord:
        return request ? 8 : 5;
    case QModbusPdu::WriteFileRecord:
        return 10;
    case QModbusPdu::MaskWriteRegister:
        return 6;
    case QModbusPdu::ReadWriteMultipleRegisters:
        return request ? 11 : 3;
    case QModbusPdu::ReadFifoQueue:
        return request ? 2 : 6;
    case QModbusPdu::EncapsulatedInterfaceTransport:
        return 2;
    case QModbusPdu::Invalid:
    case QModbusPdu::UndefinedFunctionCode:
        return -1;
    }
    return -1;
}

class tst_QModbusPdu : public QObject
{
    Q_OBJECT

private slots:
    void testDefaultConstructor()
    {
        QModbusRequest request;
        QCOMPARE(request.isValid(), false);
        QCOMPARE(request.isException(), false);
        QCOMPARE(request.functionCode(), QModbusRequest::FunctionCode(0x00));
        QCOMPARE(request.exceptionCode(), QModbusPdu::ExtendedException);
        QCOMPARE(request.data().toHex(), QByteArray());

        QModbusResponse response;
        QCOMPARE(response.isValid(), false);
        QCOMPARE(response.isException(), false);
        QCOMPARE(response.functionCode(), QModbusResponse::FunctionCode(0x00));
        QCOMPARE(response.exceptionCode(), QModbusPdu::ExtendedException);
        QCOMPARE(response.data().toHex(), QByteArray());

        QModbusExceptionResponse exception;
        QCOMPARE(exception.isValid(), false);
        QCOMPARE(exception.isException(), false);
        QCOMPARE(exception.exceptionCode(), QModbusPdu::ExtendedException);
        QCOMPARE(exception.functionCode(), QModbusExceptionResponse::FunctionCode(0x00));
        QCOMPARE(exception.data().toHex(), QByteArray());
    }

    void testVaridicConstructor()
    {
        QModbusRequest request(QModbusRequest::ReportServerId);
        QCOMPARE(request.isValid(), true);
        QCOMPARE(request.isException(), false);
        QCOMPARE(request.functionCode(), QModbusRequest::ReportServerId);
        QCOMPARE(request.exceptionCode(), QModbusPdu::ExtendedException);
        QCOMPARE(request.data().toHex(), QByteArray());

        // Byte Count, Server ID, Run Indicator Status
        QModbusResponse response(QModbusResponse::ReportServerId, quint8(0x02), quint8(0x01),
            quint8(0xff));
        QCOMPARE(response.isValid(), true);
        QCOMPARE(response.isException(), false);
        QCOMPARE(response.functionCode(), QModbusResponse::ReportServerId);
        QCOMPARE(response.exceptionCode(), QModbusPdu::ExtendedException);
        QCOMPARE(response.data().toHex(), QByteArray("0201ff"));
    }

    void testQByteArrayConstructor()
    {
        QModbusRequest request(QModbusRequest::ReportServerId, QByteArray());
        QCOMPARE(request.isValid(), true);
        QCOMPARE(request.isException(), false);
        QCOMPARE(request.functionCode(), QModbusRequest::ReportServerId);
        QCOMPARE(request.data().toHex(), QByteArray());

        // Byte Count, Server ID, Run Indicator Status
        QModbusResponse response(QModbusResponse::ReportServerId, QByteArray::fromHex("ff0102"));
        QCOMPARE(response.isValid(), true);
        QCOMPARE(response.isException(), false);
        QCOMPARE(response.functionCode(), QModbusResponse::ReportServerId);
        QCOMPARE(response.data().toHex(), QByteArray("ff0102"));
    }

    void testCopyConstructorAndAssignment()
    {
        QModbusPdu pdu;
        pdu.setFunctionCode(QModbusPdu::ReadCoils);

        QModbusRequest request(pdu);
        QCOMPARE(request.functionCode(), QModbusResponse::ReadCoils);

        QModbusResponse response(pdu);
        QCOMPARE(response.functionCode(), QModbusResponse::ReadCoils);

        pdu.setFunctionCode(QModbusPdu::WriteMultipleCoils);

        request = pdu;
        QCOMPARE(request.functionCode(), QModbusResponse::WriteMultipleCoils);

        response = pdu;
        QCOMPARE(response.functionCode(), QModbusResponse::WriteMultipleCoils);
    }

    void testEncodeDecode()
    {
        QModbusRequest invalid;
        quint8 data = 0xff;
        invalid.decodeData(&data);
        QCOMPARE(data, quint8(0xff));
        invalid.encodeData(data);
        QCOMPARE(invalid.data().toHex(), QByteArray("ff"));

        data = 0x00;
        QModbusRequest request(QModbusRequest::ReportServerId);
        request.decodeData(&data);
        QCOMPARE(data, quint8(0x00));
        QCOMPARE(request.data().toHex(), QByteArray());

        QModbusResponse response(QModbusResponse::ReportServerId);
        response.encodeData(quint8(0x02), quint8(0x01), quint8(0xff));
        quint8 count = 0, id = 0, run = 0;
        response.decodeData(&count, &id, &run);
        QCOMPARE(count, quint8(0x02));
        QCOMPARE(id, quint8(0x01));
        QCOMPARE(run, quint8(0xff));
        QCOMPARE(response.data().toHex(), QByteArray("0201ff"));

        count = 0, id = 0, run = 0;
        response = QModbusResponse(QModbusResponse::ReportServerId, QByteArray::fromHex("0201ff"));
        QCOMPARE(response.isValid(), true);
        QCOMPARE(response.isException(), false);
        QCOMPARE(response.functionCode(), QModbusResponse::ReportServerId);
        QCOMPARE(response.data().toHex(), QByteArray("0201ff"));
        response.decodeData(&count, &id, &run);
        QCOMPARE(count, quint8(0x02));
        QCOMPARE(id, quint8(0x01));
        QCOMPARE(run, quint8(0xff));

        QModbusRequest readCoils(QModbusRequest::ReadCoils);
        // starting address and quantity of coils
        readCoils.encodeData(quint16(12), quint16(10));
        quint16 address, quantity;
        readCoils.decodeData(&address, &quantity);
        QCOMPARE(address, quint16(0x0c));
        QCOMPARE(quantity, quint16(0x0a));

        request = QModbusRequest(QModbusRequest::WriteMultipleCoils,
            QByteArray::fromHex("0013000a02cd01"));
        QCOMPARE(request.data().toHex(), QByteArray("0013000a02cd01"));
        address = 0xffff, quantity = 0xffff;
        quint8 bytes = 0xff, firstByte = 0xff, secondByte = 0xff;
        request.decodeData(&address, &quantity, &bytes, &firstByte, &secondByte);
        QCOMPARE(address, quint16(19));
        QCOMPARE(quantity, quint16(10));
        QCOMPARE(bytes, quint8(2));
        QCOMPARE(firstByte, quint8(0xcd));
        QCOMPARE(secondByte, quint8(0x01));
    }

    void testQModbusExceptionResponsePdu()
    {
        QModbusExceptionResponse exception(QModbusExceptionResponse::ReportServerId,
            QModbusExceptionResponse::ServerDeviceFailure);
        QCOMPARE(exception.isValid(), true);
        QCOMPARE(exception.isException(), true);
        QCOMPARE(exception.functionCode(), QModbusExceptionResponse::ReportServerId);
        QCOMPARE(exception.data().toHex(), QByteArray("04"));
        QCOMPARE(exception.exceptionCode(), QModbusExceptionResponse::ServerDeviceFailure);

        QModbusExceptionResponse response;
        QCOMPARE(response.isValid(), false);
        QCOMPARE(response.isException(), false);
        QCOMPARE(response.functionCode(), QModbusExceptionResponse::Invalid);

        response.setFunctionCode(QModbusExceptionResponse::ReportServerId);
        response.setExceptionCode(QModbusExceptionResponse::ServerDeviceFailure);
        QCOMPARE(response.isValid(), true);
        QCOMPARE(response.isException(), true);
        QCOMPARE(response.functionCode(), QModbusExceptionResponse::ReportServerId);
        QCOMPARE(response.data().toHex(), QByteArray("04"));
        QCOMPARE(exception.exceptionCode(), QModbusExceptionResponse::ServerDeviceFailure);

        QModbusPdu pdu;
        pdu.setFunctionCode(QModbusExceptionResponse::FunctionCode(QModbusPdu::ReadCoils
            | QModbusPdu::ExceptionByte));
        QCOMPARE(pdu.isException(), true);
        QCOMPARE(pdu.functionCode(), QModbusPdu::ReadCoils);

        QModbusExceptionResponse exception2(pdu);
        QCOMPARE(exception2.isException(), true);
        QCOMPARE(exception2.functionCode(), QModbusPdu::ReadCoils);
        QCOMPARE(exception2.exceptionCode(), QModbusExceptionResponse::ExtendedException);
        exception2.setExceptionCode(QModbusExceptionResponse::IllegalFunction);
        QCOMPARE(exception2.exceptionCode(), QModbusExceptionResponse::IllegalFunction);

        QModbusExceptionResponse exception3 = pdu;
        QCOMPARE(exception3.isException(), true);
        QCOMPARE(exception3.functionCode(), QModbusPdu::ReadCoils);
        QCOMPARE(exception3.exceptionCode(), QModbusExceptionResponse::ExtendedException);
    }

    void testQDebugStreamOperator()
    {
        DebugHandler mhs(myMessageHandler);
        {
            QDebug d = qDebug();
            d << QModbusPdu();
            d << QModbusRequest();
            d << QModbusResponse();
            d << QModbusExceptionResponse();
        }
        QCOMPARE(s_msg, QString::fromLatin1("0x00 0x00 0x00 0x00"));

        {
            QDebug d = qDebug();
            d << QModbusRequest(QModbusRequest::ReadCoils, quint16(19), quint16(19));
        }
        QCOMPARE(s_msg, QString::fromLatin1("0x0100130013"));

        {
            QDebug d = qDebug();
            d << QModbusResponse(QModbusResponse::ReadCoils, QByteArray::fromHex("03cd6b05"));
        }
        QCOMPARE(s_msg, QString::fromLatin1("0x0103cd6b05"));

        {
            QDebug d = qDebug();
            d << QModbusExceptionResponse(QModbusExceptionResponse::ReadCoils,
                QModbusExceptionResponse::IllegalDataAddress);
        }
        QCOMPARE(s_msg, QString::fromLatin1("0x8102"));
    }

    void testMinimumDataSize()
    {
        bool request = true;
       QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::ReadCoils), request),
            QModbusRequest::minimumDataSize(QModbusRequest(QModbusRequest::ReadCoils)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::ReadDiscreteInputs), request),
            QModbusRequest::minimumDataSize(QModbusRequest(QModbusRequest::ReadDiscreteInputs)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::WriteSingleCoil), request),
            QModbusRequest::minimumDataSize(QModbusRequest(QModbusRequest::WriteSingleCoil)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::ReadHoldingRegisters), request),
            QModbusRequest::minimumDataSize(QModbusRequest(QModbusRequest::ReadHoldingRegisters)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::ReadInputRegisters), request),
            QModbusRequest::minimumDataSize(QModbusRequest(QModbusRequest::ReadInputRegisters)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::WriteSingleRegister), request),
            QModbusRequest::minimumDataSize(QModbusRequest(QModbusRequest::WriteSingleRegister)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::ReadExceptionStatus), request),
            QModbusRequest::minimumDataSize(QModbusRequest(QModbusRequest::ReadExceptionStatus)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::Diagnostics), request),
            QModbusRequest::minimumDataSize(QModbusRequest(QModbusRequest::Diagnostics)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::GetCommEventCounter), request),
            QModbusRequest::minimumDataSize(QModbusRequest(QModbusRequest::GetCommEventCounter)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::GetCommEventLog), request),
            QModbusRequest::minimumDataSize(QModbusRequest(QModbusRequest::GetCommEventLog)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::WriteMultipleCoils), request),
            QModbusRequest::minimumDataSize(QModbusRequest(QModbusRequest::WriteMultipleCoils)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::WriteMultipleRegisters), request),
            QModbusRequest::minimumDataSize(QModbusRequest(QModbusRequest::WriteMultipleRegisters)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::ReportServerId), request),
            QModbusRequest::minimumDataSize(QModbusRequest(QModbusRequest::ReportServerId)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::ReadFileRecord), request),
            QModbusRequest::minimumDataSize(QModbusRequest(QModbusRequest::ReadFileRecord)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::WriteFileRecord), request),
            QModbusRequest::minimumDataSize(QModbusRequest(QModbusRequest::WriteFileRecord)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::MaskWriteRegister), request),
            QModbusRequest::minimumDataSize(QModbusRequest(QModbusRequest::MaskWriteRegister)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::ReadWriteMultipleRegisters), request),
            QModbusRequest::minimumDataSize(QModbusRequest(QModbusRequest::ReadWriteMultipleRegisters)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::ReadFifoQueue), request),
            QModbusRequest::minimumDataSize(QModbusRequest(QModbusRequest::ReadFifoQueue)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::EncapsulatedInterfaceTransport), request),
            QModbusRequest::minimumDataSize(QModbusRequest(QModbusRequest::EncapsulatedInterfaceTransport)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::Invalid), request),
            QModbusRequest::minimumDataSize(QModbusRequest(QModbusRequest::Invalid)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::UndefinedFunctionCode), request),
            QModbusRequest::minimumDataSize(QModbusRequest(QModbusRequest::Invalid)));

        request = false;
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::ReadCoils), request),
            QModbusResponse::minimumDataSize(QModbusResponse(QModbusResponse::ReadCoils)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::ReadDiscreteInputs), request),
            QModbusResponse::minimumDataSize(QModbusResponse(QModbusResponse::ReadDiscreteInputs)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::WriteSingleCoil), request),
            QModbusResponse::minimumDataSize(QModbusResponse(QModbusResponse::WriteSingleCoil)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::ReadHoldingRegisters), request),
            QModbusResponse::minimumDataSize(QModbusResponse(QModbusResponse::ReadHoldingRegisters)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::ReadInputRegisters), request),
            QModbusResponse::minimumDataSize(QModbusResponse(QModbusResponse::ReadInputRegisters)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::WriteSingleRegister), request),
            QModbusResponse::minimumDataSize(QModbusResponse(QModbusResponse::WriteSingleRegister)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::ReadExceptionStatus), request),
            QModbusResponse::minimumDataSize(QModbusResponse(QModbusResponse::ReadExceptionStatus)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::Diagnostics), request),
            QModbusResponse::minimumDataSize(QModbusResponse(QModbusResponse::Diagnostics)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::GetCommEventCounter), request),
            QModbusResponse::minimumDataSize(QModbusResponse(QModbusResponse::GetCommEventCounter)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::GetCommEventLog), request),
            QModbusResponse::minimumDataSize(QModbusResponse(QModbusResponse::GetCommEventLog)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::WriteMultipleCoils), request),
            QModbusResponse::minimumDataSize(QModbusResponse(QModbusResponse::WriteMultipleCoils)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::WriteMultipleRegisters), request),
            QModbusResponse::minimumDataSize(QModbusResponse(QModbusResponse::WriteMultipleRegisters)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::ReportServerId), request),
            QModbusResponse::minimumDataSize(QModbusResponse(QModbusResponse::ReportServerId)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::ReadFileRecord), request),
            QModbusResponse::minimumDataSize(QModbusResponse(QModbusResponse::ReadFileRecord)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::WriteFileRecord), request),
            QModbusResponse::minimumDataSize(QModbusResponse(QModbusResponse::WriteFileRecord)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::MaskWriteRegister), request),
            QModbusResponse::minimumDataSize(QModbusResponse(QModbusResponse::MaskWriteRegister)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::ReadWriteMultipleRegisters), request),
            QModbusResponse::minimumDataSize(QModbusResponse(QModbusResponse::ReadWriteMultipleRegisters)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::ReadFifoQueue), request),
            QModbusResponse::minimumDataSize(QModbusResponse(QModbusResponse::ReadFifoQueue)));
        QCOMPARE(minimumDataSize(QModbusRequest(QModbusPdu::EncapsulatedInterfaceTransport), request),
            QModbusResponse::minimumDataSize(QModbusRequest(QModbusPdu::EncapsulatedInterfaceTransport)));
        QCOMPARE(minimumDataSize(QModbusResponse(QModbusPdu::Invalid), request),
            QModbusRequest::minimumDataSize(QModbusResponse(QModbusPdu::Invalid)));
        QCOMPARE(minimumDataSize(QModbusResponse(QModbusPdu::UndefinedFunctionCode), request),
            QModbusRequest::minimumDataSize(QModbusResponse(QModbusPdu::Invalid)));

        QModbusExceptionResponse exception(QModbusResponse::ReadCoils, QModbusPdu::IllegalFunction);
        QCOMPARE(minimumDataSize(exception, request), 1);
        QCOMPARE(QModbusResponse::minimumDataSize(exception), 1);
    }

    void testQModbusRequestStreamOperator_data()
    {
        QTest::addColumn<QModbusPdu::FunctionCode>("fc");
        QTest::addColumn<QByteArray>("data");
        QTest::addColumn<QByteArray>("pdu");

        QTest::newRow("ReadCoils") << QModbusRequest::ReadCoils << QByteArray::fromHex("00130013")
            << QByteArray::fromHex("0100130013");
        QTest::newRow("ReadDiscreteInputs") << QModbusRequest::ReadDiscreteInputs
            << QByteArray::fromHex("00c40016") << QByteArray::fromHex("0200c40016");
        QTest::newRow("ReadHoldingRegisters") << QModbusRequest::ReadHoldingRegisters
            << QByteArray::fromHex("006b0003") << QByteArray::fromHex("03006b0003");
        QTest::newRow("ReadInputRegisters") << QModbusRequest::ReadInputRegisters
            << QByteArray::fromHex("00080001") << QByteArray::fromHex("0400080001");
        QTest::newRow("WriteSingleCoil") << QModbusRequest::WriteSingleCoil
            << QByteArray::fromHex("00acff00") << QByteArray::fromHex("0500acff00");
        QTest::newRow("WriteSingleRegister") << QModbusRequest::WriteSingleRegister
            << QByteArray::fromHex("00010003") << QByteArray::fromHex("0600010003");
        QTest::newRow("ReadExceptionStatus") << QModbusRequest::ReadExceptionStatus
            << QByteArray() << QByteArray::fromHex("07");
        QTest::newRow("Diagnostics") << QModbusRequest::Diagnostics
            << QByteArray::fromHex("0000a537") << QByteArray::fromHex("080000a537");
        QTest::newRow("GetCommEventCounter") << QModbusRequest::GetCommEventCounter
            << QByteArray() << QByteArray::fromHex("0b");
        QTest::newRow("GetCommEventLog") << QModbusRequest::GetCommEventLog
            << QByteArray() << QByteArray::fromHex("0c");
        QTest::newRow("WriteMultipleCoils") << QModbusRequest::WriteMultipleCoils
            << QByteArray::fromHex("0013000a02cd01") << QByteArray::fromHex("0f0013000a02cd01");
        QTest::newRow("WriteMultipleRegisters") << QModbusRequest::WriteMultipleRegisters
            << QByteArray::fromHex("0001000204000a0102") << QByteArray::fromHex("100001000204000a0102");
        QTest::newRow("ReportServerId") << QModbusRequest::ReportServerId
            << QByteArray() << QByteArray::fromHex("11");
        QTest::newRow("ReadFileRecord") << QModbusRequest::ReadFileRecord
            << QByteArray::fromHex("0e0600040001000206000300090002")
            << QByteArray::fromHex("140e0600040001000206000300090002");
        QTest::newRow("WriteFileRecord") << QModbusRequest::WriteFileRecord
            << QByteArray::fromHex("0d0600040007000306af04be100d")
            << QByteArray::fromHex("150d0600040007000306af04be100d");
        QTest::newRow("MaskWriteRegister") << QModbusRequest::MaskWriteRegister
            << QByteArray::fromHex("000400f20025") << QByteArray::fromHex("16000400f20025");
        QTest::newRow("ReadWriteMultipleRegisters") << QModbusRequest::ReadWriteMultipleRegisters
            << QByteArray::fromHex("00030006000e00030600ff00ff00ff")
            << QByteArray::fromHex("1700030006000e00030600ff00ff00ff");
        QTest::newRow("ReadFifoQueue") << QModbusRequest::ReadFifoQueue
            << QByteArray::fromHex("04de") << QByteArray::fromHex("1804de");
        QTest::newRow("EncapsulatedInterfaceTransport")
            << QModbusRequest::EncapsulatedInterfaceTransport
            << QByteArray::fromHex("0e0100") << QByteArray::fromHex("2b0e0100");
    }

    void testQModbusRequestStreamOperator()
    {
        QFETCH(QModbusPdu::FunctionCode, fc);
        QFETCH(QByteArray, data);
        QFETCH(QByteArray, pdu);

        QByteArray ba;
        QDataStream output(&ba, QIODevice::WriteOnly);
        output << QModbusRequest(fc, data);
        QCOMPARE(ba, pdu);

        QModbusRequest request;
        QDataStream input(pdu);
        input >> request;
        QCOMPARE(request.functionCode(), fc);
        QCOMPARE(request.data(), data);
    }

    void testQModbusResponseStreamOperator_data()
    {
        QTest::addColumn<QModbusPdu::FunctionCode>("fc");
        QTest::addColumn<QByteArray>("data");
        QTest::addColumn<QByteArray>("pdu");

        QTest::newRow("ReadCoils") << QModbusResponse::ReadCoils << QByteArray::fromHex("03cd6b05")
            << QByteArray::fromHex("0103cd6b05");
        QTest::newRow("ReadDiscreteInputs") << QModbusResponse::ReadDiscreteInputs
            << QByteArray::fromHex("03acdb35") << QByteArray::fromHex("0203acdb35");
        QTest::newRow("ReadHoldingRegisters") << QModbusResponse::ReadHoldingRegisters
            << QByteArray::fromHex("06022b00000064") << QByteArray::fromHex("0306022b00000064");
        QTest::newRow("ReadInputRegisters") << QModbusResponse::ReadInputRegisters
            << QByteArray::fromHex("02000a") << QByteArray::fromHex("0402000a");
        QTest::newRow("WriteSingleCoil") << QModbusResponse::WriteSingleCoil
            << QByteArray::fromHex("00acff00") << QByteArray::fromHex("0500acff00");
        QTest::newRow("WriteSingleRegister") << QModbusResponse::WriteSingleRegister
            << QByteArray::fromHex("00010003") << QByteArray::fromHex("0600010003");
        QTest::newRow("ReadExceptionStatus") << QModbusResponse::ReadExceptionStatus
            << QByteArray::fromHex("6d") << QByteArray::fromHex("076d");
        QTest::newRow("Diagnostics") << QModbusResponse::Diagnostics
            << QByteArray::fromHex("ffff0108") << QByteArray::fromHex("08ffff0108");
        QTest::newRow("GetCommEventCounter") << QModbusResponse::GetCommEventCounter
            << QByteArray::fromHex("ffff0108") << QByteArray::fromHex("0bffff0108");
        QTest::newRow("GetCommEventLog") << QModbusResponse::GetCommEventLog
            << QByteArray::fromHex("080000010801212000") << QByteArray::fromHex("0c080000010801212000");
        QTest::newRow("WriteMultipleCoils") << QModbusResponse::WriteMultipleCoils
            << QByteArray::fromHex("0013000a") << QByteArray::fromHex("0f0013000a");
        QTest::newRow("WriteMultipleRegisters") << QModbusResponse::WriteMultipleRegisters
            << QByteArray::fromHex("00010002") << QByteArray::fromHex("1000010002");
        QTest::newRow("ReportServerId") << QModbusResponse::ReportServerId
            << QByteArray::fromHex("030aff12") << QByteArray::fromHex("11030aff12");
        QTest::newRow("ReadFileRecord") << QModbusResponse::ReadFileRecord
            << QByteArray::fromHex("0c05060dfe0020050633cd0040")
            << QByteArray::fromHex("140c05060dfe0020050633cd0040");
        QTest::newRow("WriteFileRecord") << QModbusResponse::WriteFileRecord
            << QByteArray::fromHex("0d0600040007000306af04be100d")
            << QByteArray::fromHex("150d0600040007000306af04be100d");
        QTest::newRow("MaskWriteRegister") << QModbusResponse::MaskWriteRegister
            << QByteArray::fromHex("000400f20025") << QByteArray::fromHex("16000400f20025");
        QTest::newRow("ReadWriteMultipleRegisters") << QModbusResponse::ReadWriteMultipleRegisters
            << QByteArray::fromHex("0c00fe0acd00010003000d00ff")
            << QByteArray::fromHex("170c00fe0acd00010003000d00ff");
        QTest::newRow("ReadFifoQueue") << QModbusResponse::ReadFifoQueue
            << QByteArray::fromHex("0006000201b81284") << QByteArray::fromHex("180006000201b81284");
        QTest::newRow("EncapsulatedInterfaceTransport")
            << QModbusResponse::EncapsulatedInterfaceTransport
            << QByteArray::fromHex("0e01010000030016") + "Company identification" +
                QByteArray::fromHex("010c") + "Product code" + QByteArray::fromHex("0205") +
                "V2.11"
            << QByteArray::fromHex("2b0e01010000030016") + "Company identification" +
                QByteArray::fromHex("010c") + "Product code" + QByteArray::fromHex("0205") +
                "V2.11";

        QModbusExceptionResponse ex(QModbusPdu::ReadCoils, QModbusPdu::IllegalDataAddress);
        QTest::newRow("StreamExceptionResponse")
            << QModbusPdu::FunctionCode(ex.functionCode() | QModbusPdu::ExceptionByte)
            << QByteArray::fromHex("02") << QByteArray::fromHex("8102");
    }

    void testQModbusResponseStreamOperator()
    {
        QFETCH(QModbusPdu::FunctionCode, fc);
        QFETCH(QByteArray, data);
        QFETCH(QByteArray, pdu);

        QByteArray ba;
        QDataStream output(&ba, QIODevice::WriteOnly);
        output << QModbusResponse(fc, data);
        QCOMPARE(ba, pdu);

        QModbusResponse response;
        QDataStream input(pdu);
        input >> response;
        if (response.isException())
            QCOMPARE(quint8(response.functionCode()), quint8(fc &~ QModbusPdu::ExceptionByte));
        else
            QCOMPARE(response.functionCode(), fc);
        QCOMPARE(response.data(), data);
    }

    void testEncapsulatedInterfaceTransportStreamOperator()
    {
        const QByteArray valid = QByteArray::fromHex("0e01010000030016") + "Company identification"
            + QByteArray::fromHex("010c") + "Product code" + QByteArray::fromHex("0205")
            + "V2.11";
        {
            QByteArray ba;
            QDataStream output(&ba, QIODevice::ReadWrite);
            output << QModbusResponse(QModbusResponse::EncapsulatedInterfaceTransport, valid);

            QModbusResponse response;
            QDataStream input(ba);
            input >> response;
            QCOMPARE(response.functionCode(), QModbusResponse::EncapsulatedInterfaceTransport);
            QCOMPARE(response.data(), valid);
        }

        // valid embedded
        const QByteArray suffix = QByteArray::fromHex("ffffffffff");
        {
            QByteArray ba;
            QDataStream output(&ba, QIODevice::ReadWrite);
            output << QModbusResponse(QModbusResponse::EncapsulatedInterfaceTransport, valid
                + suffix);

            QModbusResponse response;
            QDataStream input(ba);
            input >> response;
            QCOMPARE(response.functionCode(), QModbusResponse::EncapsulatedInterfaceTransport);
            QCOMPARE(response.data(), valid);
        }

        const QByteArray invalid = QByteArray::fromHex("0e01010000030016") + "Company identification"
            + QByteArray::fromHex("01ff") + "Product code" + QByteArray::fromHex("0205")
            + "V2.11";
        {
            QByteArray ba;
            QDataStream output(&ba, QIODevice::ReadWrite);
            output << QModbusResponse(QModbusResponse::EncapsulatedInterfaceTransport, invalid);

            QModbusResponse response;
            QDataStream input(ba);
            input >> response;
            QCOMPARE(response.functionCode(), QModbusResponse::Invalid);
            QCOMPARE(response.data(), QByteArray());
        }

        const QByteArray invalidShort = QByteArray::fromHex("0e01010000030016")
            + "Company identification" + QByteArray::fromHex("01");
        {
            QByteArray ba;
            QDataStream output(&ba, QIODevice::ReadWrite);
            output << QModbusResponse(QModbusResponse::EncapsulatedInterfaceTransport, invalidShort);

            QModbusResponse response;
            QDataStream input(ba);
            input >> response;
            QCOMPARE(response.functionCode(), QModbusResponse::Invalid);
            QCOMPARE(response.data(), QByteArray());
        }
    }

    void testCalculateDataSize()
    {
        // CanOpenGeneralReference
        QModbusResponse cogr(QModbusPdu::EncapsulatedInterfaceTransport, quint8(0x0d));
        QCOMPARE(QModbusResponse::calculateDataSize(cogr), -1);
        cogr.encodeData(quint8(0x0d), quint8(0x00));
        QCOMPARE(QModbusResponse::calculateDataSize(cogr), 2);
        // TODO: fix the following once we can calculate the size for CanOpenGeneralReference
        cogr.encodeData(quint8(0x0d), quint8(0x00), quint8(0x00));
        QCOMPARE(QModbusResponse::calculateDataSize(cogr), 2);

        // ReadDeviceIdentification
        QModbusResponse rdi(QModbusPdu::EncapsulatedInterfaceTransport, quint8(0x0e));
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), -1);
        rdi.encodeData(quint8(0x0e), quint8(0x01));
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 8);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01));
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 8);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00));
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 8);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00));
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 8);

        // single object
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x01)); // object count
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 8);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x01),
            quint8(0x00)); // object id
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 8);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x01),
            quint8(0x00), quint8(0x01)); // object size
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 9);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x01),
            quint8(0x00), quint8(0x01), quint8(0xff)); // object data
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 9);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x01),
            quint8(0x00), quint8(0x01), quint8(0xff),
            quint8(0xff)); // dummy additional data
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 9);

        // multiple objects equal size
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0xff), quint8(0x02),
            quint8(0x02)); // object count
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 8);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0xff), quint8(0x02),
            quint8(0x02),
            quint8(0x00)); // object id
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 8);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x02),
            quint8(0x00), quint8(0x01)); // object size
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 11);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x02),
            quint8(0x00), quint8(0x01), quint8(0xff)); // object data
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 11);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x02),
            quint8(0x00), quint8(0x01), quint8(0xff),
            quint8(0x01)); // second object id
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 11);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x02),
            quint8(0x00), quint8(0x01), quint8(0xff),
            quint8(0x01), quint8(0x01)); // second object size
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 12);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x02),
            quint8(0x00), quint8(0x01), quint8(0xff),
            quint8(0x01), quint8(0x01), quint8(0xff)); // second object data
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 12);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x02),
            quint8(0x00), quint8(0x01), quint8(0xff),
            quint8(0x01), quint8(0x01), quint8(0xff),
            quint8(0xff)); // dummy additional data
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 12);

        // multiple objects different size
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0xff), quint8(0x02),
            quint8(0x03));
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 8);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0xff), quint8(0x02),
            quint8(0x03),
            quint8(0x00));
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 8);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x03),
            quint8(0x00), quint8(0x01));
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 13);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x03),
            quint8(0x00), quint8(0x02), quint8(0xff));
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 14);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x03),
            quint8(0x00), quint8(0x02), quint8(0xff), quint8(0xff));
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 14);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x03),
            quint8(0x00), quint8(0x02), quint8(0xff), quint8(0xff),
            quint8(0x01));
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 14);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x03),
            quint8(0x00), quint8(0x02), quint8(0xff), quint8(0xff),
            quint8(0x01), quint8(0x03));
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 14);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x03),
            quint8(0x00), quint8(0x02), quint8(0xff), quint8(0xff),
            quint8(0x01), quint8(0x03), quint8(0xff));
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 14);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x03),
            quint8(0x00), quint8(0x02), quint8(0xff), quint8(0xff),
            quint8(0x01), quint8(0x03), quint8(0xff), quint8(0xff));
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 17);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x03),
            quint8(0x00), quint8(0x02), quint8(0xff), quint8(0xff),
            quint8(0x01), quint8(0x03), quint8(0xff), quint8(0xff), quint8(0xff));
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 17);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x03),
            quint8(0x00), quint8(0x02), quint8(0xff), quint8(0xff),
            quint8(0x01), quint8(0x03), quint8(0xff), quint8(0xff), quint8(0xff),
            quint8(0x02));
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 17);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x03),
            quint8(0x00), quint8(0x02), quint8(0xff), quint8(0xff),
            quint8(0x01), quint8(0x03), quint8(0xff), quint8(0xff), quint8(0xff),
            quint8(0x02), quint8(0x05));
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 22);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x03),
            quint8(0x00), quint8(0x02), quint8(0xff), quint8(0xff),
            quint8(0x01), quint8(0x03), quint8(0xff), quint8(0xff), quint8(0xff),
            quint8(0x02), quint8(0x05), quint8(0xff));
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 22);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x03),
            quint8(0x00), quint8(0x02), quint8(0xff), quint8(0xff),
            quint8(0x01), quint8(0x03), quint8(0xff), quint8(0xff), quint8(0xff),
            quint8(0x02), quint8(0x05), quint8(0xff), quint8(0xff));
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 22);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x03),
            quint8(0x00), quint8(0x02), quint8(0xff), quint8(0xff),
            quint8(0x01), quint8(0x03), quint8(0xff), quint8(0xff), quint8(0xff),
            quint8(0x02), quint8(0x05), quint8(0xff), quint8(0xff), quint8(0xff));
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 22);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x03),
            quint8(0x00), quint8(0x02), quint8(0xff), quint8(0xff),
            quint8(0x01), quint8(0x03), quint8(0xff), quint8(0xff), quint8(0xff),
            quint8(0x02), quint8(0x05), quint8(0xff), quint8(0xff), quint8(0xff), quint8(0xff));
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 22);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x03),
            quint8(0x00), quint8(0x02), quint8(0xff), quint8(0xff),
            quint8(0x01), quint8(0x03), quint8(0xff), quint8(0xff), quint8(0xff),
            quint8(0x02), quint8(0x05), quint8(0xff), quint8(0xff), quint8(0xff), quint8(0xff), quint8(0xff));
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 22);
        rdi.encodeData(quint8(0x0e), quint8(0x01), quint8(0x01), quint8(0x00), quint8(0x00),
            quint8(0x03),
            quint8(0x00), quint8(0x02), quint8(0xff), quint8(0xff),
            quint8(0x01), quint8(0x03), quint8(0xff), quint8(0xff), quint8(0xff),
            quint8(0x02), quint8(0x05), quint8(0xff), quint8(0xff), quint8(0xff), quint8(0xff), quint8(0xff),
            quint8(0xff)); // dummy additional data
        QCOMPARE(QModbusResponse::calculateDataSize(rdi), 22);
    }

    void testCustomCalculator()
    {
        // request
        QModbusRequest request(QModbusRequest::WriteSingleCoil);
        request.encodeData(quint8(0x00), quint8(0xac), quint8(0xff), quint8(0x00));
        QCOMPARE(QModbusRequest::calculateDataSize(request), 4);

        QModbusRequest::registerDataSizeCalculator(QModbusRequest::WriteSingleCoil,
            [](const QModbusRequest &)->int {
            return 15;
        });
        QCOMPARE(QModbusRequest::calculateDataSize(request), 15);

        QModbusRequest::registerDataSizeCalculator(QModbusRequest::WriteSingleCoil, nullptr);
        QCOMPARE(QModbusRequest::calculateDataSize(request), 4);

        // response
        QModbusResponse response(QModbusRequest::WriteSingleCoil);
        response.encodeData(quint8(0x00), quint8(0xac), quint8(0xff), quint8(0x00));
        QCOMPARE(QModbusResponse::calculateDataSize(request), 4);

        QModbusResponse::registerDataSizeCalculator(QModbusResponse::WriteSingleCoil,
            [](const QModbusResponse &)->int {
            return 15;
        });
        QCOMPARE(QModbusResponse::calculateDataSize(response), 15);

        QModbusResponse::registerDataSizeCalculator(QModbusResponse::WriteSingleCoil, nullptr);
        QCOMPARE(QModbusResponse::calculateDataSize(response), 4);

        // custom
        QModbusPdu::FunctionCode custom = QModbusPdu::FunctionCode(0x30);
        request.setFunctionCode(custom);
        QCOMPARE(QModbusRequest::calculateDataSize(request), -1);

        QModbusRequest::registerDataSizeCalculator(custom, [](const QModbusRequest &)->int {
            return 27;
        });
        QCOMPARE(QModbusRequest::calculateDataSize(request), 27);
    }

    void testCalculateLongDataSize()
    {
        QByteArray longData = QByteArray(128, ' ');
        longData[0] = longData.size();
        const QModbusResponse response(QModbusResponse::ReadCoils, longData);
        QCOMPARE(QModbusResponse::calculateDataSize(response), 1 + longData.size());

        const QModbusRequest wfrRequest(QModbusPdu::WriteFileRecord, longData);
        QCOMPARE(QModbusRequest::calculateDataSize(wfrRequest), 1 + longData.size());

        longData = QByteArray(4 + 128, ' ');
        longData[4] = char(128);
        const QModbusRequest wmcRequest(QModbusPdu::WriteMultipleCoils, longData);
        QCOMPARE(QModbusRequest::calculateDataSize(wmcRequest), 1 + longData.size());

        const QModbusRequest wmrRequest(QModbusPdu::WriteMultipleRegisters, longData);
        QCOMPARE(QModbusRequest::calculateDataSize(wmrRequest), 1 + longData.size());
    }
};

QTEST_MAIN(tst_QModbusPdu)

#include "tst_qmodbuspdu.moc"
