// Copyright (C) 2016 Kurt Pattyn <pattyn.kurt@gmail.com>.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtTest/QtTest>
#include <QtTest/qtestcase.h>
#include <QtTest/QSignalSpy>
#include <QtCore/QBuffer>
#include <QtCore/QByteArray>
#include <QtCore/QDebug>

#include "private/qwebsocketdataprocessor_p.h"
#include "private/qwebsocketprotocol_p.h"
#include "QtWebSockets/qwebsocketprotocol.h"

const quint8 FIN = 0x80;
const quint8 RSV1 = 0x40;
const quint8 RSV2 = 0x30;
const quint8 RSV3 = 0x10;

QT_USE_NAMESPACE

using namespace std::chrono_literals;
constexpr std::chrono::milliseconds DefaultIdleTimeout = 500ms;

Q_DECLARE_METATYPE(QWebSocketProtocol::CloseCode)
Q_DECLARE_METATYPE(QWebSocketProtocol::OpCode)

class tst_DataProcessor : public QObject
{
    Q_OBJECT

public:
    tst_DataProcessor();

private Q_SLOTS:
    void initTestCase();
    void cleanupTestCase();
    void init();
    void cleanup();

    /***************************************************************************
     * Happy Flows
     ***************************************************************************/
    /*!
      Tests all kinds of valid binary frames, including zero length frames
      */
    void goodBinaryFrame();
    void goodBinaryFrame_data();

    /*!
      Tests all kinds of valid text frames, including zero length frames
      */
    void goodTextFrame();
    void goodTextFrame_data();

    /*!
     * Test all kinds of valid control frames.
     */
    void goodControlFrame();

    /*!
     * Test all kinds of valid close frames.
     */
    void goodCloseFrame();
    void goodCloseFrame_data();

    /*!
     * Test all valid opcodes
     */
    void goodOpcodes();
    void goodOpcodes_data();

    /*!
      Tests the QWebSocketDataProcessor for the correct handling of non-charactercodes
      Due to a workaround in QTextCodec, non-characters are treated as illegal.
      This workaround is not necessary anymore, and hence code should be changed in Qt
      to allow non-characters again.
     */
    void nonCharacterCodes();
    void nonCharacterCodes_data();

    /***************************************************************************
     * Rainy Day Flows
     ***************************************************************************/
    /*!
        Tests the QWebSocketDataProcessor for correct handling of frames that don't
        contain the starting 2 bytes.
        This test is a border case, where not enough bytes are received to even start parsing a
        frame.
        This test does not test sequences of frames, only single frames are tested
     */
    void frameTooSmall();

    /*!
        Tests the QWebSocketDataProcessor for correct handling of frames that are oversized.
        This test does not test sequences of frames, only single frames are tested
     */
    void frameTooBig();
    void frameTooBig_data();

    /*!
        Tests the QWebSocketDataProcessor for the correct handling of malformed frame headers.
        This test does not test sequences of frames, only single frames are tested
     */
    void invalidHeader();
    void invalidHeader_data();

    /*!
        Tests the QWebSocketDataProcessor for the correct handling of invalid control frames.
        Invalid means: payload bigger than 125, frame is fragmented, ...
        This test does not test sequences of frames, only single frames are tested
     */
    void invalidControlFrame();
    void invalidControlFrame_data();

    void invalidCloseFrame();
    void invalidCloseFrame_data();

    /*!
        Tests the QWebSocketDataProcessor for the correct handling of incomplete size fields
        for large and big payloads.
     */
    void incompleteSizeField();
    void incompleteSizeField_data();

    /*!
        Tests the QWebSocketDataProcessor for the correct handling of incomplete payloads.
        This includes:
        - incomplete length bytes for large and big payloads (16- and 64-bit values),
        - minimum size representation (see RFC 6455 paragraph 5.2),
        - frames that are too large (larger than MAX_INT in bytes)
        - incomplete payloads (less bytes than indicated in the size field)
        This test does not test sequences of frames, only single frames are tested
     */
    void incompletePayload();
    void incompletePayload_data();

    /*!
        Tests the QWebSocketDataProcessor for the correct handling of invalid UTF-8 payloads.
        This test does not test sequences of frames, only single frames are tested
     */
    void invalidPayload();
    void invalidPayload_data(bool isControlFrame = false);

    void invalidPayloadInCloseFrame();
    void invalidPayloadInCloseFrame_data();

    /*!
      Tests the QWebSocketDataProcessor for the correct handling of the minimum size representation
      requirement of RFC 6455 (see paragraph 5.2)
     */
    void minimumSizeRequirement();
    void minimumSizeRequirement_data();

    void clearDataBuffers(); // qtbug-55506

private:
    //helper function that constructs a new row of test data for invalid UTF8 sequences
    void invalidUTF8(const char *dataTag, const char *utf8Sequence, bool isCloseFrame);
    //helper function that constructs a new row of test data for invalid leading field values
    void invalidField(const char *dataTag, quint8 invalidFieldValue);
    //helper functions that construct a new row of test data for size fields that do not adhere
    //to the minimum size requirement
    void minimumSize16Bit(quint16 sizeInBytes);
    void minimumSize64Bit(quint64 sizeInBytes);
    //helper function to construct a new row of test data containing frames with a payload size
    //smaller than indicated in the header
    void incompleteFrame(quint8 controlCode, quint64 indicatedSize, quint64 actualPayloadSize);
    void insertIncompleteSizeFieldTest(quint8 payloadCode, quint8 numBytesFollowing);

    //helper function to construct a new row of test data containing text frames containing
    //sequences
    void nonCharacterSequence(const char *sequence);

    void doTest(int timeout = 0);
    void doCloseFrameTest();

    QString opCodeToString(quint8 opCode);
};

tst_DataProcessor::tst_DataProcessor()
{
}

void tst_DataProcessor::initTestCase()
{
}

void tst_DataProcessor::cleanupTestCase()
{
}

void tst_DataProcessor::init()
{
    qRegisterMetaType<QWebSocketProtocol::OpCode>("QWebSocketProtocol::OpCode");
    qRegisterMetaType<QWebSocketProtocol::CloseCode>("QWebSocketProtocol::CloseCode");
}

void tst_DataProcessor::cleanup()
{
}

void tst_DataProcessor::goodBinaryFrame_data()
{
    QTest::addColumn<QByteArray>("payload");
    //be sure to get small (< 126 bytes), large (> 125 bytes & < 64K) and big (>64K) frames
    for (int i = 0; i < (65536 + 256); i += 128)
    {
        QTest::newRow(QStringLiteral("Binary frame with %1 bytes").arg(i).toLatin1().constData())
                << QByteArray(i, char(1));
    }
    for (int i = 0; i < 256; ++i)   //test all possible bytes in the payload
    {
        QTest::newRow(QStringLiteral("Binary frame containing byte: '0x%1'")
                      .arg(QByteArray(1, char(i)).toHex().constData()).toLatin1().constData())
                << QByteArray(i, char(1));
    }
}

void tst_DataProcessor::goodBinaryFrame()
{
    QByteArray data;
    QBuffer buffer;
    QWebSocketDataProcessor dataProcessor;
    QFETCH(QByteArray, payload);

    data.append(char(FIN | QWebSocketProtocol::OpCodeBinary));

    if (payload.size() < 126)
    {
        data.append(char(payload.size()));
    }
    else if (payload.size() < 65536)
    {
        quint16 swapped = qToBigEndian<quint16>(payload.size());
        const char *wireRepresentation
                = static_cast<const char *>(static_cast<const void *>(&swapped));
        data.append(char(126)).append(wireRepresentation, 2);
    }
    else
    {
        quint64 swapped = qToBigEndian<quint64>(payload.size());
        const char *wireRepresentation
                = static_cast<const char *>(static_cast<const void *>(&swapped));
        data.append(char(127)).append(wireRepresentation, 8);
    }

    data.append(payload);
    buffer.setData(data);
    buffer.open(QIODevice::ReadOnly);

    QSignalSpy errorReceivedSpy(&dataProcessor,
                                SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode,QString)));
    QSignalSpy closeReceivedSpy(&dataProcessor,
                                SIGNAL(closeReceived(QWebSocketProtocol::CloseCode,QString)));
    QSignalSpy pingReceivedSpy(&dataProcessor, SIGNAL(pingReceived(QByteArray)));
    QSignalSpy pongReceivedSpy(&dataProcessor, SIGNAL(pongReceived(QByteArray)));
    QSignalSpy binaryFrameReceivedSpy(&dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool)));
    QSignalSpy binaryMessageReceivedSpy(&dataProcessor, SIGNAL(binaryMessageReceived(QByteArray)));
    QSignalSpy textFrameReceivedSpy(&dataProcessor, SIGNAL(textFrameReceived(QString,bool)));
    QSignalSpy textMessageReceivedSpy(&dataProcessor, SIGNAL(textMessageReceived(QString)));
    dataProcessor.process(&buffer);
    QCOMPARE(errorReceivedSpy.size(), 0);
    QCOMPARE(pingReceivedSpy.size(), 0);
    QCOMPARE(pongReceivedSpy.size(), 0);
    QCOMPARE(closeReceivedSpy.size(), 0);
    QCOMPARE(binaryFrameReceivedSpy.size(), 1);
    QCOMPARE(binaryMessageReceivedSpy.size(), 1);
    QCOMPARE(textFrameReceivedSpy.size(), 0);
    QCOMPARE(textMessageReceivedSpy.size(), 0);
    QList<QVariant> arguments = binaryFrameReceivedSpy.takeFirst();
    QCOMPARE(arguments.at(0).toByteArray().size(), payload.size());
    arguments = binaryMessageReceivedSpy.takeFirst();
    QCOMPARE(arguments.at(0).toByteArray().size(), payload.size());
    buffer.close();
}

void tst_DataProcessor::goodTextFrame_data()
{
    QTest::addColumn<QByteArray>("payload");
    QTest::addColumn<int>("size");

    //test frames with small (< 126), large ( < 65536) and big ( > 65535) payloads
    for (int i = 0; i < (65536 + 256); i += 128)
    {
        QTest::newRow(QStringLiteral("Text frame with %1 ASCII characters")
                      .arg(i).toLatin1().constData()) << QByteArray(i, 'a') << i;
    }
    //test all valid ASCII characters
    for (int i = 0; i < 128; ++i)
    {
        QTest::newRow(QStringLiteral("Text frame with containing ASCII character '0x%1'")
                      .arg(QByteArray(1, char(i)).toHex().constData()).toLatin1().constData())
                << QByteArray(1, char(i)) << 1;
    }

    //the text string reads: Text frame containing Hello-µ@ßöäüàá-UTF-8!!
    //Visual Studio doesn't like UTF-8 in source code, so we use escape codes for the string
    //The number 22 refers to the length of the string;
    //the length was incorrectly calculated on Visual Studio

    //doing extensive QStringLiteral concatenations here, because
    //MSVC 2010 complains when using concatenation literal strings about
    //concatenation of wide and narrow strings:
    //error C2308: concatenating mismatched strings
    QTest::newRow((QStringLiteral("Text frame containing Hello-") +
                  QStringLiteral("\xC2\xB5\x40\xC3\x9F\xC3\xB6\xC3\xA4\xC3\xBC\xC3\xA0") +
                  QStringLiteral("\xC3\xA1-UTF-8!!")).toUtf8().constData())
            << QByteArray::fromHex("48656c6c6f2dc2b540c39fc3b6c3a4c3bcc3a0c3a12d5554462d382121")
            << 22;
}

void tst_DataProcessor::goodTextFrame()
{
    QByteArray data;
    QBuffer buffer;
    QWebSocketDataProcessor dataProcessor;
    QFETCH(QByteArray, payload);
    QFETCH(int, size);

    data.append(char(FIN | QWebSocketProtocol::OpCodeText));

    if (payload.size() < 126)
    {
        data.append(char(payload.size()));
    }
    else if (payload.size() < 65536)
    {
        quint16 swapped = qToBigEndian<quint16>(payload.size());
        const char *wireRepresentation
                = static_cast<const char *>(static_cast<const void *>(&swapped));
        data.append(char(126)).append(wireRepresentation, 2);
    }
    else
    {
        quint64 swapped = qToBigEndian<quint64>(payload.size());
        const char *wireRepresentation
                = static_cast<const char *>(static_cast<const void *>(&swapped));
        data.append(char(127)).append(wireRepresentation, 8);
    }

    data.append(payload);
    buffer.setData(data);
    buffer.open(QIODevice::ReadOnly);

    QSignalSpy errorReceivedSpy(&dataProcessor,
                                SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode,QString)));
    QSignalSpy closeReceivedSpy(&dataProcessor,
                                SIGNAL(closeReceived(QWebSocketProtocol::CloseCode,QString)));
    QSignalSpy pingReceivedSpy(&dataProcessor, SIGNAL(pingReceived(QByteArray)));
    QSignalSpy pongReceivedSpy(&dataProcessor, SIGNAL(pongReceived(QByteArray)));
    QSignalSpy textFrameReceivedSpy(&dataProcessor, SIGNAL(textFrameReceived(QString,bool)));
    QSignalSpy textMessageReceivedSpy(&dataProcessor, SIGNAL(textMessageReceived(QString)));
    QSignalSpy binaryFrameReceivedSpy(&dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool)));
    QSignalSpy binaryMessageReceivedSpy(&dataProcessor, SIGNAL(binaryMessageReceived(QByteArray)));

    dataProcessor.process(&buffer);

    QCOMPARE(errorReceivedSpy.size(), 0);
    QCOMPARE(pingReceivedSpy.size(), 0);
    QCOMPARE(pongReceivedSpy.size(), 0);
    QCOMPARE(closeReceivedSpy.size(), 0);
    QCOMPARE(textFrameReceivedSpy.size(), 1);
    QCOMPARE(textMessageReceivedSpy.size(), 1);
    QCOMPARE(binaryFrameReceivedSpy.size(), 0);
    QCOMPARE(binaryMessageReceivedSpy.size(), 0);
    QList<QVariant> arguments = textFrameReceivedSpy.takeFirst();
    QCOMPARE(arguments.at(0).toString().size(), size);
    arguments = textMessageReceivedSpy.takeFirst();
    QCOMPARE(arguments.at(0).toString().size(), size);
    buffer.close();
}

void tst_DataProcessor::goodControlFrame()
{
    QByteArray data;
    QBuffer buffer;
    QWebSocketDataProcessor dataProcessor;

    QSignalSpy closeFrameReceivedSpy(&dataProcessor,
                                     SIGNAL(closeReceived(QWebSocketProtocol::CloseCode,QString)));
    QSignalSpy errorReceivedSpy(&dataProcessor,
                                SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode,QString)));
    QSignalSpy textFrameReceivedSpy(&dataProcessor, SIGNAL(textFrameReceived(QString,bool)));
    QSignalSpy textMessageReceivedSpy(&dataProcessor, SIGNAL(textMessageReceived(QString)));
    QSignalSpy binaryFrameReceivedSpy(&dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool)));
    QSignalSpy binaryMessageReceivedSpy(&dataProcessor, SIGNAL(binaryMessageReceived(QByteArray)));
    QSignalSpy pingReceivedSpy(&dataProcessor, SIGNAL(pingReceived(QByteArray)));
    QSignalSpy pongReceivedSpy(&dataProcessor, SIGNAL(pongReceived(QByteArray)));

    data.append(char(FIN | QWebSocketProtocol::OpCodePing));
    data.append('\0');
    buffer.setData(data);
    buffer.open(QIODevice::ReadOnly);
    dataProcessor.process(&buffer);
    QCOMPARE(errorReceivedSpy.size(), 0);
    QCOMPARE(textFrameReceivedSpy.size(), 0);
    QCOMPARE(textMessageReceivedSpy.size(), 0);
    QCOMPARE(binaryFrameReceivedSpy.size(), 0);
    QCOMPARE(binaryMessageReceivedSpy.size(), 0);
    QCOMPARE(closeFrameReceivedSpy.size(), 0);
    QCOMPARE(pongReceivedSpy.size(), 0);
    QCOMPARE(pingReceivedSpy.size(), 1);
    buffer.close();

    data.clear();
    pingReceivedSpy.clear();
    pongReceivedSpy.clear();
    data.append(char(FIN | QWebSocketProtocol::OpCodePong));
    data.append('\0');
    buffer.setData(data);
    buffer.open(QIODevice::ReadOnly);
    dataProcessor.process(&buffer);
    QCOMPARE(errorReceivedSpy.size(), 0);
    QCOMPARE(textFrameReceivedSpy.size(), 0);
    QCOMPARE(textMessageReceivedSpy.size(), 0);
    QCOMPARE(binaryFrameReceivedSpy.size(), 0);
    QCOMPARE(binaryMessageReceivedSpy.size(), 0);
    QCOMPARE(closeFrameReceivedSpy.size(), 0);
    QCOMPARE(pingReceivedSpy.size(), 0);
    QCOMPARE(pongReceivedSpy.size(), 1);
    buffer.close();
}

void tst_DataProcessor::goodCloseFrame_data()
{
    QTest::addColumn<QString>("payload");
    QTest::addColumn<QWebSocketProtocol::CloseCode>("closeCode");
    //control frame data cannot exceed 125 bytes; smaller than 124,
    //because we also need a 2 byte close code
    for (int i = 0; i < 124; ++i)
    {
        QTest::newRow(QStringLiteral("Close frame with %1 ASCII characters")
                      .arg(i).toLatin1().constData())
                << QString(i, 'a')
                << QWebSocketProtocol::CloseCodeNormal;
    }
    for (int i = 0; i < 126; ++i)
    {
        QTest::newRow(QStringLiteral("Text frame with containing ASCII character '0x%1'")
                      .arg(QByteArray(1, char(i)).toHex().constData()).toLatin1().constData())
                << QString(1, char(i)) << QWebSocketProtocol::CloseCodeNormal;
    }
    QTest::newRow("Close frame with close code NORMAL")
            << QString(1, 'a') << QWebSocketProtocol::CloseCodeNormal;
    QTest::newRow("Close frame with close code BAD OPERATION")
            << QString(1, 'a') << QWebSocketProtocol::CloseCodeBadOperation;
    QTest::newRow("Close frame with close code DATATYPE NOT SUPPORTED")
            << QString(1, 'a') << QWebSocketProtocol::CloseCodeDatatypeNotSupported;
    QTest::newRow("Close frame with close code GOING AWAY")
            << QString(1, 'a') << QWebSocketProtocol::CloseCodeGoingAway;
    QTest::newRow("Close frame with close code MISSING EXTENSION")
            << QString(1, 'a') << QWebSocketProtocol::CloseCodeMissingExtension;
    QTest::newRow("Close frame with close code POLICY VIOLATED")
            << QString(1, 'a') << QWebSocketProtocol::CloseCodePolicyViolated;
    QTest::newRow("Close frame with close code PROTOCOL ERROR")
            << QString(1, 'a') << QWebSocketProtocol::CloseCodeProtocolError;
    QTest::newRow("Close frame with close code TOO MUCH DATA")
            << QString(1, 'a') << QWebSocketProtocol::CloseCodeTooMuchData;
    QTest::newRow("Close frame with close code WRONG DATATYPE")
            << QString(1, 'a') << QWebSocketProtocol::CloseCodeWrongDatatype;
    QTest::newRow("Close frame with close code 3000")
            << QString(1, 'a') << QWebSocketProtocol::CloseCode(3000);
    QTest::newRow("Close frame with close code 3999")
            << QString(1, 'a') << QWebSocketProtocol::CloseCode(3999);
    QTest::newRow("Close frame with close code 4000")
            << QString(1, 'a') << QWebSocketProtocol::CloseCode(4000);
    QTest::newRow("Close frame with close code 4999")
            << QString(1, 'a') << QWebSocketProtocol::CloseCode(4999);

    //close frames with no close reason
    QTest::newRow("Close frame with close code NORMAL and no reason")
            << QString() << QWebSocketProtocol::CloseCodeNormal;
    QTest::newRow("Close frame with close code BAD OPERATION and no reason")
            << QString() << QWebSocketProtocol::CloseCodeBadOperation;
    QTest::newRow("Close frame with close code DATATYPE NOT SUPPORTED and no reason")
            << QString() << QWebSocketProtocol::CloseCodeDatatypeNotSupported;
    QTest::newRow("Close frame with close code GOING AWAY and no reason")
            << QString() << QWebSocketProtocol::CloseCodeGoingAway;
    QTest::newRow("Close frame with close code MISSING EXTENSION and no reason")
            << QString() << QWebSocketProtocol::CloseCodeMissingExtension;
    QTest::newRow("Close frame with close code POLICY VIOLATED and no reason")
            << QString() << QWebSocketProtocol::CloseCodePolicyViolated;
    QTest::newRow("Close frame with close code PROTOCOL ERROR and no reason")
            << QString() << QWebSocketProtocol::CloseCodeProtocolError;
    QTest::newRow("Close frame with close code TOO MUCH DATA and no reason")
            << QString() << QWebSocketProtocol::CloseCodeTooMuchData;
    QTest::newRow("Close frame with close code WRONG DATATYPE and no reason")
            << QString() << QWebSocketProtocol::CloseCodeWrongDatatype;
    QTest::newRow("Close frame with close code 3000 and no reason")
            << QString() << QWebSocketProtocol::CloseCode(3000);
    QTest::newRow("Close frame with close code 3999 and no reason")
            << QString() << QWebSocketProtocol::CloseCode(3999);
    QTest::newRow("Close frame with close code 4000 and no reason")
            << QString() << QWebSocketProtocol::CloseCode(4000);
    QTest::newRow("Close frame with close code 4999 and no reason")
            << QString() << QWebSocketProtocol::CloseCode(4999);

    QTest::newRow("Close frame with no close code and no reason")
            << QString() << QWebSocketProtocol::CloseCode(0);
}

void tst_DataProcessor::goodOpcodes_data()
{
    QTest::addColumn<QWebSocketProtocol::OpCode>("opCode");

    QTest::newRow("Frame with PING opcode") << QWebSocketProtocol::OpCodePing;
    QTest::newRow("Frame with PONG opcode") << QWebSocketProtocol::OpCodePong;
    QTest::newRow("Frame with TEXT opcode") << QWebSocketProtocol::OpCodeText;
    QTest::newRow("Frame with BINARY opcode") << QWebSocketProtocol::OpCodeBinary;
    QTest::newRow("Frame with CLOSE opcode") << QWebSocketProtocol::OpCodeClose;
}

void tst_DataProcessor::goodOpcodes()
{
    QByteArray data;
    QBuffer buffer;
    QWebSocketDataProcessor dataProcessor;
    QFETCH(QWebSocketProtocol::OpCode, opCode);

    data.append(char(FIN | opCode));
    data.append(char(0));   //zero length

    buffer.setData(data);
    buffer.open(QIODevice::ReadOnly);

    QSignalSpy errorReceivedSpy(&dataProcessor,
                                SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode,QString)));
    QSignalSpy closeReceivedSpy(&dataProcessor,
                                SIGNAL(closeReceived(QWebSocketProtocol::CloseCode,QString)));
    QSignalSpy pingReceivedSpy(&dataProcessor,
                               SIGNAL(pingReceived(QByteArray)));
    QSignalSpy pongReceivedSpy(&dataProcessor,
                               SIGNAL(pongReceived(QByteArray)));
    QSignalSpy textFrameReceivedSpy(&dataProcessor,
                                    SIGNAL(textFrameReceived(QString,bool)));
    QSignalSpy textMessageReceivedSpy(&dataProcessor,
                                      SIGNAL(textMessageReceived(QString)));
    QSignalSpy binaryFrameReceivedSpy(&dataProcessor,
                                      SIGNAL(binaryFrameReceived(QByteArray,bool)));
    QSignalSpy binaryMessageReceivedSpy(&dataProcessor,
                                        SIGNAL(binaryMessageReceived(QByteArray)));

    dataProcessor.process(&buffer);

    QCOMPARE(errorReceivedSpy.size(), 0);
    QCOMPARE(pingReceivedSpy.size(), opCode == QWebSocketProtocol::OpCodePing ? 1 : 0);
    QCOMPARE(pongReceivedSpy.size(), opCode == QWebSocketProtocol::OpCodePong ? 1 : 0);
    QCOMPARE(closeReceivedSpy.size(), opCode == QWebSocketProtocol::OpCodeClose ? 1 : 0);
    QCOMPARE(textFrameReceivedSpy.size(), opCode == QWebSocketProtocol::OpCodeText ? 1 : 0);
    QCOMPARE(textMessageReceivedSpy.size(), opCode == QWebSocketProtocol::OpCodeText ? 1 : 0);
    QCOMPARE(binaryFrameReceivedSpy.size(), opCode == QWebSocketProtocol::OpCodeBinary ? 1 : 0);
    QCOMPARE(binaryMessageReceivedSpy.size(), opCode == QWebSocketProtocol::OpCodeBinary ? 1 : 0);

    buffer.close();
}

void tst_DataProcessor::goodCloseFrame()
{
    QByteArray data;
    QBuffer buffer;
    QWebSocketDataProcessor dataProcessor;
    QFETCH(QString, payload);
    QFETCH(QWebSocketProtocol::CloseCode, closeCode);
    quint16 swapped = qToBigEndian<quint16>(closeCode);
    const char *wireRepresentation = static_cast<const char *>(static_cast<const void *>(&swapped));

    data.append(char(FIN | QWebSocketProtocol::OpCodeClose));
    if (swapped != 0)
    {
        data.append(char(payload.size() + 2)).append(wireRepresentation, 2).append(payload.toUtf8());
    }
    else
    {
        data.append('\0');  //payload length 0;
        //dataprocessor emits a CloseCodeNormal close code when none is present
        closeCode = QWebSocketProtocol::CloseCodeNormal;
    }
    buffer.setData(data);
    buffer.open(QIODevice::ReadOnly);

    QSignalSpy errorReceivedSpy(&dataProcessor,
                                SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode,QString)));
    QSignalSpy pingReceivedSpy(&dataProcessor, SIGNAL(pingReceived(QByteArray)));
    QSignalSpy pongReceivedSpy(&dataProcessor, SIGNAL(pongReceived(QByteArray)));
    QSignalSpy closeFrameReceivedSpy(&dataProcessor,
                                     SIGNAL(closeReceived(QWebSocketProtocol::CloseCode,QString)));
    QSignalSpy textFrameReceivedSpy(&dataProcessor, SIGNAL(textFrameReceived(QString,bool)));
    QSignalSpy textMessageReceivedSpy(&dataProcessor, SIGNAL(textMessageReceived(QString)));
    QSignalSpy binaryFrameReceivedSpy(&dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool)));
    QSignalSpy binaryMessageReceivedSpy(&dataProcessor, SIGNAL(binaryMessageReceived(QByteArray)));

    dataProcessor.process(&buffer);

    QCOMPARE(errorReceivedSpy.size(), 0);
    QCOMPARE(pingReceivedSpy.size(), 0);
    QCOMPARE(pongReceivedSpy.size(), 0);
    QCOMPARE(closeFrameReceivedSpy.size(), 1);
    QCOMPARE(textFrameReceivedSpy.size(), 0);
    QCOMPARE(textMessageReceivedSpy.size(), 0);
    QCOMPARE(binaryFrameReceivedSpy.size(), 0);
    QCOMPARE(binaryMessageReceivedSpy.size(), 0);
    QList<QVariant> arguments = closeFrameReceivedSpy.takeFirst();
    QCOMPARE(arguments.at(0).value<QWebSocketProtocol::CloseCode>(), closeCode);
    QCOMPARE(arguments.at(1).toString().size(), payload.size());
    buffer.close();
}

void tst_DataProcessor::nonCharacterCodes_data()
{
    QTest::addColumn<quint8>("firstByte");
    QTest::addColumn<quint8>("secondByte");
    QTest::addColumn<QByteArray>("payload");
    QTest::addColumn<bool>("isContinuationFrame");

    nonCharacterSequence("efbfbe");
    nonCharacterSequence("efbfbf");
    nonCharacterSequence("f09fbfbe");
    nonCharacterSequence("f09fbfbf");
    nonCharacterSequence("f0afbfbe");
    nonCharacterSequence("f0afbfbf");
    nonCharacterSequence("f0bfbfbe");
    nonCharacterSequence("f0bfbfbf");
    nonCharacterSequence("f18fbfbe");
    nonCharacterSequence("f18fbfbf");
    nonCharacterSequence("f19fbfbe");
    nonCharacterSequence("f19fbfbf");
    nonCharacterSequence("f1afbfbe");
    nonCharacterSequence("f1afbfbf");
    nonCharacterSequence("f1bfbfbe");
    nonCharacterSequence("f1bfbfbf");
    nonCharacterSequence("f28fbfbe");
    nonCharacterSequence("f28fbfbf");
    nonCharacterSequence("f29fbfbe");
    nonCharacterSequence("f29fbfbf");
    nonCharacterSequence("f2afbfbe");
    nonCharacterSequence("f2afbfbf");
    nonCharacterSequence("f2bfbfbe");
    nonCharacterSequence("f2bfbfbf");
    nonCharacterSequence("f38fbfbe");
    nonCharacterSequence("f38fbfbf");
    nonCharacterSequence("f39fbfbe");
    nonCharacterSequence("f39fbfbf");
    nonCharacterSequence("f3afbfbe");
    nonCharacterSequence("f3afbfbf");
    nonCharacterSequence("f3bfbfbe");
    nonCharacterSequence("f3bfbfbf");
    nonCharacterSequence("f48fbfbe");
    nonCharacterSequence("f48fbfbf");
}

void tst_DataProcessor::nonCharacterCodes()
{
    QFETCH(quint8, firstByte);
    QFETCH(quint8, secondByte);
    QFETCH(QByteArray, payload);
    QFETCH(bool, isContinuationFrame);

    if (!isContinuationFrame)
    {
        QByteArray data;
        QBuffer buffer;
        QWebSocketDataProcessor dataProcessor;
        QSignalSpy errorSpy(&dataProcessor,
                            SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode,QString)));
        QSignalSpy closeSpy(&dataProcessor,
                            SIGNAL(closeReceived(QWebSocketProtocol::CloseCode,QString)));
        QSignalSpy pingFrameSpy(&dataProcessor, SIGNAL(pingReceived(QByteArray)));
        QSignalSpy pongFrameSpy(&dataProcessor, SIGNAL(pongReceived(QByteArray)));
        QSignalSpy textFrameSpy(&dataProcessor, SIGNAL(textFrameReceived(QString,bool)));
        QSignalSpy textMessageSpy(&dataProcessor, SIGNAL(textMessageReceived(QString)));
        QSignalSpy binaryFrameSpy(&dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool)));
        QSignalSpy binaryMessageSpy(&dataProcessor, SIGNAL(binaryMessageReceived(QByteArray)));

        data.append(firstByte).append(secondByte);
        data.append(payload);
        buffer.setData(data);
        buffer.open(QIODevice::ReadOnly);
        dataProcessor.process(&buffer);

        QCOMPARE(errorSpy.size(), 0);
        QCOMPARE(closeSpy.size(), 0);
        QCOMPARE(pingFrameSpy.size(), 0);
        QCOMPARE(pongFrameSpy.size(), 0);
        QCOMPARE(textFrameSpy.size(), 1);
        QCOMPARE(textMessageSpy.size(), 1);
        QCOMPARE(binaryFrameSpy.size(), 0);
        QCOMPARE(binaryMessageSpy.size(), 0);

        QVariantList arguments = textFrameSpy.takeFirst();
        QCOMPARE(arguments.at(0).value<QString>().toUtf8(), payload);
        QCOMPARE(arguments.at(1).value<bool>(), !isContinuationFrame);
        arguments = textMessageSpy.takeFirst();
        QCOMPARE(arguments.at(0).value<QString>().toUtf8(), payload);
        buffer.close();
    }
}

void tst_DataProcessor::frameTooSmall()
{
    QByteArray data;
    QBuffer buffer;
    QWebSocketDataProcessor dataProcessor;
    QByteArray firstFrame;

    dataProcessor.setIdleTimeout(DefaultIdleTimeout);

    firstFrame.append(quint8(QWebSocketProtocol::OpCodeText)).append(char(1))
            .append(QByteArray(1, 'a'));

    //with nothing in the buffer, the dataProcessor should time out
    //and the error should be CloseCodeGoingAway meaning the socket will be closed
    buffer.setData(data);
    buffer.open(QIODevice::ReadOnly);
    QSignalSpy errorSpy(&dataProcessor,
                        SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode,QString)));
    QSignalSpy closeSpy(&dataProcessor,
                        SIGNAL(closeReceived(QWebSocketProtocol::CloseCode,QString)));
    QSignalSpy pingMessageSpy(&dataProcessor, SIGNAL(pingReceived(QByteArray)));
    QSignalSpy pongMessageSpy(&dataProcessor, SIGNAL(pongReceived(QByteArray)));
    QSignalSpy textMessageSpy(&dataProcessor, SIGNAL(textMessageReceived(QString)));
    QSignalSpy binaryMessageSpy(&dataProcessor, SIGNAL(binaryMessageReceived(QByteArray)));
    QSignalSpy textFrameSpy(&dataProcessor, SIGNAL(textFrameReceived(QString,bool)));
    QSignalSpy binaryFrameSpy(&dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool)));

    dataProcessor.process(&buffer);

    QTRY_VERIFY_WITH_TIMEOUT(errorSpy.size(), 7000);
    QCOMPARE(errorSpy.size(), 1);
    QCOMPARE(closeSpy.size(), 0);
    QCOMPARE(pingMessageSpy.size(), 0);
    QCOMPARE(pongMessageSpy.size(), 0);
    QCOMPARE(textMessageSpy.size(), 0);
    QCOMPARE(binaryMessageSpy.size(), 0);
    QCOMPARE(textFrameSpy.size(), 0);
    QCOMPARE(binaryFrameSpy.size(), 0);

    QList<QVariant> arguments = errorSpy.takeFirst();
    QCOMPARE(arguments.at(0).value<QWebSocketProtocol::CloseCode>(),
             QWebSocketProtocol::CloseCodeGoingAway);
    errorSpy.clear();
    closeSpy.clear();
    pingMessageSpy.clear();
    pongMessageSpy.clear();
    textMessageSpy.clear();
    binaryMessageSpy.clear();
    textFrameSpy.clear();
    binaryFrameSpy.clear();
    buffer.close();
    data.clear();

    //only one byte; this is far too little;
    //should get a time out as well and the error should be CloseCodeGoingAway
    //meaning the socket will be closed
    data.append(quint8('1'));   //put 1 byte in the buffer; this is too little
    buffer.setData(data);
    buffer.open(QIODevice::ReadOnly);

    dataProcessor.process(&buffer);

    QTRY_VERIFY_WITH_TIMEOUT(errorSpy.size(), 7000);
    QCOMPARE(errorSpy.size(), 1);
    QCOMPARE(closeSpy.size(), 0);
    QCOMPARE(pingMessageSpy.size(), 0);
    QCOMPARE(pongMessageSpy.size(), 0);
    QCOMPARE(textMessageSpy.size(), 0);
    QCOMPARE(binaryMessageSpy.size(), 0);
    QCOMPARE(textFrameSpy.size(), 0);
    QCOMPARE(binaryFrameSpy.size(), 0);

    arguments = errorSpy.takeFirst();
    QCOMPARE(arguments.at(0).value<QWebSocketProtocol::CloseCode>(),
             QWebSocketProtocol::CloseCodeGoingAway);
    buffer.close();
    errorSpy.clear();
    closeSpy.clear();
    pingMessageSpy.clear();
    pongMessageSpy.clear();
    textMessageSpy.clear();
    binaryMessageSpy.clear();
    textFrameSpy.clear();
    binaryFrameSpy.clear();
    data.clear();


    {
        //text frame with final bit not set
        data.append(char(QWebSocketProtocol::OpCodeText)).append(char(0x0));
        buffer.setData(data);
        buffer.open(QIODevice::ReadOnly);

        dataProcessor.process(&buffer);

        QTRY_VERIFY_WITH_TIMEOUT(errorSpy.size(), 7000);
        QCOMPARE(errorSpy.size(), 1);
        QCOMPARE(closeSpy.size(), 0);
        QCOMPARE(pingMessageSpy.size(), 0);
        QCOMPARE(pongMessageSpy.size(), 0);
        QCOMPARE(textMessageSpy.size(), 0);
        QCOMPARE(binaryMessageSpy.size(), 0);
        QCOMPARE(textFrameSpy.size(), 1);
        QCOMPARE(binaryFrameSpy.size(), 0);

        errorSpy.clear();
        closeSpy.clear();
        pingMessageSpy.clear();
        pongMessageSpy.clear();
        textMessageSpy.clear();
        binaryMessageSpy.clear();
        textFrameSpy.clear();
        binaryFrameSpy.clear();
        buffer.close();
        data.clear();

        //with nothing in the buffer,
        //the dataProcessor should time out and the error should be CloseCodeGoingAway
        //meaning the socket will be closed
        buffer.setData(data);
        buffer.open(QIODevice::ReadOnly);
        dataProcessor.process(&buffer);

        QTRY_VERIFY_WITH_TIMEOUT(errorSpy.size(), 7000);
        QCOMPARE(errorSpy.size(), 1);
        QCOMPARE(closeSpy.size(), 0);
        QCOMPARE(pingMessageSpy.size(), 0);
        QCOMPARE(pongMessageSpy.size(), 0);
        QCOMPARE(textMessageSpy.size(), 0);
        QCOMPARE(binaryMessageSpy.size(), 0);
        QCOMPARE(textFrameSpy.size(), 0);
        QCOMPARE(binaryFrameSpy.size(), 0);

        QList<QVariant> arguments = errorSpy.takeFirst();
        QCOMPARE(arguments.at(0).value<QWebSocketProtocol::CloseCode>(),
                 QWebSocketProtocol::CloseCodeGoingAway);
        errorSpy.clear();
        closeSpy.clear();
        pingMessageSpy.clear();
        pongMessageSpy.clear();
        textMessageSpy.clear();
        binaryMessageSpy.clear();
        textFrameSpy.clear();
        binaryFrameSpy.clear();
        buffer.close();
        data.clear();

        //text frame with final bit not set
        data.append(char(QWebSocketProtocol::OpCodeText)).append(char(0x0));
        buffer.setData(data);
        buffer.open(QIODevice::ReadOnly);
        dataProcessor.process(&buffer);

        QTRY_VERIFY_WITH_TIMEOUT(errorSpy.size(), 7000);
        QCOMPARE(errorSpy.size(), 1);
        QCOMPARE(closeSpy.size(), 0);
        QCOMPARE(pingMessageSpy.size(), 0);
        QCOMPARE(pongMessageSpy.size(), 0);
        QCOMPARE(textMessageSpy.size(), 0);
        QCOMPARE(binaryMessageSpy.size(), 0);
        QCOMPARE(textFrameSpy.size(), 1);
        QCOMPARE(binaryFrameSpy.size(), 0);

        buffer.close();
        data.clear();

        errorSpy.clear();
        closeSpy.clear();
        pingMessageSpy.clear();
        pongMessageSpy.clear();
        textMessageSpy.clear();
        binaryMessageSpy.clear();
        textFrameSpy.clear();
        binaryFrameSpy.clear();

        //only 1 byte follows in continuation frame;
        //should time out with close code CloseCodeGoingAway
        data.append('a');
        buffer.setData(data);
        buffer.open(QIODevice::ReadOnly);

        dataProcessor.process(&buffer);
        QTRY_VERIFY_WITH_TIMEOUT(errorSpy.size(), 7000);
        QCOMPARE(errorSpy.size(), 1);
        QCOMPARE(closeSpy.size(), 0);
        QCOMPARE(pingMessageSpy.size(), 0);
        QCOMPARE(pongMessageSpy.size(), 0);
        QCOMPARE(textMessageSpy.size(), 0);
        QCOMPARE(binaryMessageSpy.size(), 0);
        QCOMPARE(textFrameSpy.size(), 0);
        QCOMPARE(binaryFrameSpy.size(), 0);
        arguments = errorSpy.takeFirst();
        QCOMPARE(arguments.at(0).value<QWebSocketProtocol::CloseCode>(),
                 QWebSocketProtocol::CloseCodeGoingAway);
        buffer.close();
    }
}

void tst_DataProcessor::frameTooBig_data()
{
    QTest::addColumn<quint8>("firstByte");
    QTest::addColumn<quint8>("secondByte");
    QTest::addColumn<QByteArray>("payload");
    QTest::addColumn<bool>("isContinuationFrame");
    QTest::addColumn<QWebSocketProtocol::CloseCode>("expectedCloseCode");

    quint64 swapped64 = 0;
    const char *wireRepresentation = nullptr;

    //only data frames are checked for being too big
    //control frames have explicit checking on a maximum payload size of 125,
    //which is tested elsewhere

    swapped64 = qToBigEndian<quint64>(QWebSocketDataProcessor::maxFrameSize() + 1);
    wireRepresentation = static_cast<const char *>(static_cast<const void *>(&swapped64));
    QTest::newRow("Text frame with payload size > INT_MAX")
            << quint8(FIN | QWebSocketProtocol::OpCodeText)
            << quint8(127)
            << QByteArray(wireRepresentation, 8).append(QByteArray(32, 'a'))
            << false
            << QWebSocketProtocol::CloseCodeTooMuchData;

    swapped64 = qToBigEndian<quint64>(QWebSocketDataProcessor::maxFrameSize() + 1);
    wireRepresentation = static_cast<const char *>(static_cast<const void *>(&swapped64));
    QTest::newRow("Binary frame with payload size > INT_MAX")
            << quint8(FIN | QWebSocketProtocol::OpCodeBinary)
            << quint8(127)
            << QByteArray(wireRepresentation, 8).append(QByteArray(32, 'a'))
            << false
            << QWebSocketProtocol::CloseCodeTooMuchData;

    swapped64 = qToBigEndian<quint64>(QWebSocketDataProcessor::maxFrameSize() + 1);
    wireRepresentation = static_cast<const char *>(static_cast<const void *>(&swapped64));
    QTest::newRow("Continuation frame with payload size > INT_MAX")
            << quint8(FIN | QWebSocketProtocol::OpCodeContinue)
            << quint8(127)
            << QByteArray(wireRepresentation, 8).append(QByteArray(32, 'a'))
            << true
            << QWebSocketProtocol::CloseCodeTooMuchData;
}

void tst_DataProcessor::frameTooBig()
{
    doTest();
}

void tst_DataProcessor::invalidHeader_data()
{
    //The first byte contain the FIN, RSV1, RSV2, RSV3 and the Opcode
    //The second byte contains the MaskFlag and the length of the frame
    QTest::addColumn<quint8>("firstByte");
    QTest::addColumn<quint8>("secondByte");
    //superfluous, but present to be able to call doTest(), which expects a payload field
    QTest::addColumn<QByteArray>("payload");
    QTest::addColumn<bool>("isContinuationFrame");
    QTest::addColumn<QWebSocketProtocol::CloseCode>("expectedCloseCode");

    //invalid bit fields
    invalidField("RSV1 set", RSV1);
    invalidField("RSV2 set", RSV2);
    invalidField("RSV3 set", RSV3);
    invalidField("RSV1 and RSV2 set", RSV1 | RSV2);
    invalidField("RSV1 and RSV3 set", RSV1 | RSV3);
    invalidField("RSV2 and RSV3 set", RSV2 | RSV3);
    invalidField("RSV1, RSV2 and RSV3 set", RSV1 | RSV2 | RSV3);

    //invalid opcodes
    invalidField("Invalid OpCode 3", QWebSocketProtocol::OpCodeReserved3);
    invalidField("Invalid OpCode 4", QWebSocketProtocol::OpCodeReserved4);
    invalidField("Invalid OpCode 5", QWebSocketProtocol::OpCodeReserved5);
    invalidField("Invalid OpCode 6", QWebSocketProtocol::OpCodeReserved6);
    invalidField("Invalid OpCode 7", QWebSocketProtocol::OpCodeReserved7);
    invalidField("Invalid OpCode B", QWebSocketProtocol::OpCodeReservedB);
    invalidField("Invalid OpCode C", QWebSocketProtocol::OpCodeReservedC);
    invalidField("Invalid OpCode D", QWebSocketProtocol::OpCodeReservedD);
    invalidField("Invalid OpCode E", QWebSocketProtocol::OpCodeReservedE);
    invalidField("Invalid OpCode F", QWebSocketProtocol::OpCodeReservedF);
}

void tst_DataProcessor::invalidHeader()
{
    doTest();
}

void tst_DataProcessor::invalidControlFrame_data()
{
    QTest::addColumn<quint8>("firstByte");
    QTest::addColumn<quint8>("secondByte");
    QTest::addColumn<QByteArray>("payload");
    QTest::addColumn<bool>("isContinuationFrame");
    QTest::addColumn<QWebSocketProtocol::CloseCode>("expectedCloseCode");


    QTest::newRow("Close control frame with payload size 126")
            << quint8(FIN | QWebSocketProtocol::OpCodeClose)
            << quint8(126)
            << QByteArray()
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;
    QTest::newRow("Ping control frame with payload size 126")
            << quint8(FIN | QWebSocketProtocol::OpCodePing)
            << quint8(126)
            << QByteArray()
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;
    QTest::newRow("Pong control frame with payload size 126")
            << quint8(FIN | QWebSocketProtocol::OpCodePong)
            << quint8(126)
            << QByteArray()
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;

    QTest::newRow("Non-final close control frame (fragmented)")
            << quint8(QWebSocketProtocol::OpCodeClose)
            << quint8(32)
            << QByteArray()
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;
    QTest::newRow("Non-final ping control frame (fragmented)")
            << quint8(QWebSocketProtocol::OpCodePing)
            << quint8(32) << QByteArray()
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;
    QTest::newRow("Non-final pong control frame (fragmented)")
            << quint8(QWebSocketProtocol::OpCodePong)
            << quint8(32)
            << QByteArray()
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;
}

void tst_DataProcessor::invalidControlFrame()
{
    doTest();
}

void tst_DataProcessor::invalidCloseFrame_data()
{
    QTest::addColumn<quint8>("firstByte");
    QTest::addColumn<quint8>("secondByte");
    QTest::addColumn<QByteArray>("payload");
    QTest::addColumn<bool>("isContinuationFrame");
    QTest::addColumn<QWebSocketProtocol::CloseCode>("expectedCloseCode");

    QTest::newRow("Close control frame with payload size 1")
            << quint8(FIN | QWebSocketProtocol::OpCodeClose)
            << quint8(1)
            << QByteArray(1, 'a')
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;
    quint16 swapped = qToBigEndian<quint16>(QWebSocketProtocol::CloseCodeAbnormalDisconnection);
    const char *wireRepresentation = static_cast<const char *>(static_cast<const void *>(&swapped));

    //Not allowed per RFC 6455 (see para 7.4.1)
    QTest::newRow("Close control frame close code ABNORMAL DISCONNECTION")
            << quint8(FIN | QWebSocketProtocol::OpCodeClose)
            << quint8(2)
            << QByteArray(wireRepresentation, 2)
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;
    swapped = qToBigEndian<quint16>(QWebSocketProtocol::CloseCodeMissingStatusCode);
    wireRepresentation = static_cast<const char *>(static_cast<const void *>(&swapped));
    //Not allowed per RFC 6455 (see para 7.4.1)
    QTest::newRow("Close control frame close code MISSING STATUS CODE")
            << quint8(FIN | QWebSocketProtocol::OpCodeClose)
            << quint8(2)
            << QByteArray(wireRepresentation, 2)
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;
    swapped = qToBigEndian<quint16>(1004);
    wireRepresentation = static_cast<const char *>(static_cast<const void *>(&swapped));
    QTest::newRow("Close control frame close code 1004")
            << quint8(FIN | QWebSocketProtocol::OpCodeClose)
            << quint8(2)
            << QByteArray(wireRepresentation, 2)
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;
    swapped = qToBigEndian<quint16>(QWebSocketProtocol::CloseCodeTlsHandshakeFailed);
    wireRepresentation = static_cast<const char *>(static_cast<const void *>(&swapped));
    //Not allowed per RFC 6455 (see para 7.4.1)
    QTest::newRow("Close control frame close code TLS HANDSHAKE FAILED")
            << quint8(FIN | QWebSocketProtocol::OpCodeClose)
            << quint8(2)
            << QByteArray(wireRepresentation, 2)
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;
    swapped = qToBigEndian<quint16>(0);
    wireRepresentation = static_cast<const char *>(static_cast<const void *>(&swapped));
    QTest::newRow("Close control frame close code 0")
            << quint8(FIN | QWebSocketProtocol::OpCodeClose)
            << quint8(2)
            << QByteArray(wireRepresentation, 2)
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;
    swapped = qToBigEndian<quint16>(999);
    wireRepresentation = static_cast<const char *>(static_cast<const void *>(&swapped));
    QTest::newRow("Close control frame close code 999")
            << quint8(FIN | QWebSocketProtocol::OpCodeClose)
            << quint8(2)
            << QByteArray(wireRepresentation, 2)
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;
    swapped = qToBigEndian<quint16>(1012);
    wireRepresentation = static_cast<const char *>(static_cast<const void *>(&swapped));
    QTest::newRow("Close control frame close code 1012")
            << quint8(FIN | QWebSocketProtocol::OpCodeClose)
            << quint8(2)
            << QByteArray(wireRepresentation, 2)
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;
    swapped = qToBigEndian<quint16>(1013);
    wireRepresentation = static_cast<const char *>(static_cast<const void *>(&swapped));
    QTest::newRow("Close control frame close code 1013")
            << quint8(FIN | QWebSocketProtocol::OpCodeClose)
            << quint8(2)
            << QByteArray(wireRepresentation, 2)
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;
    swapped = qToBigEndian<quint16>(1014);
    wireRepresentation = static_cast<const char *>(static_cast<const void *>(&swapped));
    QTest::newRow("Close control frame close code 1014")
            << quint8(FIN | QWebSocketProtocol::OpCodeClose)
            << quint8(2)
            << QByteArray(wireRepresentation, 2)
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;
    swapped = qToBigEndian<quint16>(1100);
    wireRepresentation = static_cast<const char *>(static_cast<const void *>(&swapped));
    QTest::newRow("Close control frame close code 1100")
            << quint8(FIN | QWebSocketProtocol::OpCodeClose)
            << quint8(2)
            << QByteArray(wireRepresentation, 2)
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;
    swapped = qToBigEndian<quint16>(2000);
    wireRepresentation = static_cast<const char *>(static_cast<const void *>(&swapped));
    QTest::newRow("Close control frame close code 2000")
            << quint8(FIN | QWebSocketProtocol::OpCodeClose)
            << quint8(2)
            << QByteArray(wireRepresentation, 2)
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;
    swapped = qToBigEndian<quint16>(2999);
    wireRepresentation = static_cast<const char *>(static_cast<const void *>(&swapped));
    QTest::newRow("Close control frame close code 2999")
            << quint8(FIN | QWebSocketProtocol::OpCodeClose)
            << quint8(2)
            << QByteArray(wireRepresentation, 2)
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;
    swapped = qToBigEndian<quint16>(5000);
    wireRepresentation = static_cast<const char *>(static_cast<const void *>(&swapped));
    QTest::newRow("Close control frame close code 5000")
            << quint8(FIN | QWebSocketProtocol::OpCodeClose)
            << quint8(2)
            << QByteArray(wireRepresentation, 2)
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;
    swapped = qToBigEndian<quint16>(65535u);
    wireRepresentation = static_cast<const char *>(static_cast<const void *>(&swapped));
    QTest::newRow("Close control frame close code 65535")
            << quint8(FIN | QWebSocketProtocol::OpCodeClose)
            << quint8(2)
            << QByteArray(wireRepresentation, 2)
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;
}

void tst_DataProcessor::invalidCloseFrame()
{
    doCloseFrameTest();
}

void tst_DataProcessor::minimumSizeRequirement_data()
{
    QTest::addColumn<quint8>("firstByte");
    QTest::addColumn<quint8>("secondByte");
    QTest::addColumn<QByteArray>("payload");
    QTest::addColumn<bool>("isContinuationFrame");
    QTest::addColumn<QWebSocketProtocol::CloseCode>("expectedCloseCode");

    minimumSize16Bit(0);
    minimumSize16Bit(64);
    minimumSize16Bit(125);

    minimumSize64Bit(0);
    minimumSize64Bit(64);
    minimumSize64Bit(125);
    minimumSize64Bit(126);
    minimumSize64Bit(256);
    minimumSize64Bit(512);
    minimumSize64Bit(1024);
    minimumSize64Bit(2048);
    minimumSize64Bit(4096);
    minimumSize64Bit(8192);
    minimumSize64Bit(16384);
    minimumSize64Bit(32768);
    minimumSize64Bit(0xFFFFu);
}

void tst_DataProcessor::minimumSizeRequirement()
{
    doTest();
}

void tst_DataProcessor::invalidPayload_data(bool isControlFrame)
{
    QTest::addColumn<quint8>("firstByte");
    QTest::addColumn<quint8>("secondByte");
    QTest::addColumn<QByteArray>("payload");
    QTest::addColumn<bool>("isContinuationFrame");
    QTest::addColumn<QWebSocketProtocol::CloseCode>("expectedCloseCode");

    //6.3: invalid UTF-8 sequence
    invalidUTF8("case 6.3.1",   "cebae1bdb9cf83cebcceb5eda080656469746564", isControlFrame);

    //6.4.: fail fast tests; invalid UTF-8 in middle of string
    invalidUTF8("case 6.4.1",   "cebae1bdb9cf83cebcceb5f4908080656469746564", isControlFrame);
    invalidUTF8("case 6.4.4",   "cebae1bdb9cf83cebcceb5eda080656469746564", isControlFrame);

    //6.6: All prefixes of a valid UTF-8 string that contains multi-byte code points
    invalidUTF8("case 6.6.1",   "ce", isControlFrame);
    invalidUTF8("case 6.6.3",   "cebae1", isControlFrame);
    invalidUTF8("case 6.6.4",   "cebae1bd", isControlFrame);
    invalidUTF8("case 6.6.6",   "cebae1bdb9cf", isControlFrame);
    invalidUTF8("case 6.6.8",   "cebae1bdb9cf83ce", isControlFrame);
    invalidUTF8("case 6.6.10",  "cebae1bdb9cf83cebcce", isControlFrame);

    //6.8: First possible sequence length 5/6 (invalid codepoints)
    invalidUTF8("case 6.8.1",   "f888808080", isControlFrame);
    invalidUTF8("case 6.8.2",   "fc8480808080", isControlFrame);

    //6.10: Last possible sequence length 4/5/6 (invalid codepoints)
    invalidUTF8("case 6.10.1",  "f7bfbfbf", isControlFrame);
    invalidUTF8("case 6.10.2",  "fbbfbfbfbf", isControlFrame);
    invalidUTF8("case 6.10.3",  "fdbfbfbfbfbf", isControlFrame);

    //5.11: boundary conditions
    invalidUTF8("case 6.11.5",  "f4908080", isControlFrame);

    //6.12: unexpected continuation bytes
    invalidUTF8("case 6.12.1",  "80", isControlFrame);
    invalidUTF8("case 6.12.2",  "bf", isControlFrame);
    invalidUTF8("case 6.12.3",  "80bf", isControlFrame);
    invalidUTF8("case 6.12.4",  "80bf80", isControlFrame);
    invalidUTF8("case 6.12.5",  "80bf80bf", isControlFrame);
    invalidUTF8("case 6.12.6",  "80bf80bf80", isControlFrame);
    invalidUTF8("case 6.12.7",  "80bf80bf80bf", isControlFrame);
    invalidUTF8("case 6.12.8",
                "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a"
                "7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbe",
                isControlFrame);

    //6.13: lonely start characters
    invalidUTF8("case 6.13.1",
                "c020c120c220c320c420c520c620c720c820c920ca20cb20cc20cd20ce20cf20d020d120d220"
                "d320d420d520d620d720d820d920da20db20dc20dd20de20",
                isControlFrame);
    invalidUTF8("case 6.13.2",  "e020e120e220e320e420e520e620e720e820e920ea20eb20ec20ed20ee20",
                isControlFrame);
    invalidUTF8("case 6.13.3",  "f020f120f220f320f420f520f620", isControlFrame);
    invalidUTF8("case 6.13.4",  "f820f920fa20", isControlFrame);
    invalidUTF8("case 6.13.5",  "fc20", isControlFrame);

    //6.14: sequences with last continuation byte missing
    invalidUTF8("case 6.14.1",  "c0", isControlFrame);
    invalidUTF8("case 6.14.2",  "e080", isControlFrame);
    invalidUTF8("case 6.14.3",  "f08080", isControlFrame);
    invalidUTF8("case 6.14.4",  "f8808080", isControlFrame);
    invalidUTF8("case 6.14.5",  "fc80808080", isControlFrame);
    invalidUTF8("case 6.14.6",  "df", isControlFrame);
    invalidUTF8("case 6.14.7",  "efbf", isControlFrame);
    invalidUTF8("case 6.14.8",  "f7bfbf", isControlFrame);
    invalidUTF8("case 6.14.9",  "fbbfbfbf", isControlFrame);
    invalidUTF8("case 6.14.10", "fdbfbfbfbf", isControlFrame);

    //6.15: concatenation of incomplete sequences
    invalidUTF8("case 6.15.1",
                "c0e080f08080f8808080fc80808080dfefbff7bfbffbbfbfbffdbfbfbfbf", isControlFrame);

    //6.16: impossible bytes
    invalidUTF8("case 6.16.1",  "fe", isControlFrame);
    invalidUTF8("case 6.16.2",  "ff", isControlFrame);
    invalidUTF8("case 6.16.3",  "fefeffff", isControlFrame);

    //6.17: overlong ASCII characters
    invalidUTF8("case 6.17.1",  "c0af", isControlFrame);
    invalidUTF8("case 6.17.2",  "e080af", isControlFrame);
    invalidUTF8("case 6.17.3",  "f08080af", isControlFrame);
    invalidUTF8("case 6.17.4",  "f8808080af", isControlFrame);
    invalidUTF8("case 6.17.5",  "fc80808080af", isControlFrame);

    //6.18: maximum overlong sequences
    invalidUTF8("case 6.18.1",  "c1bf", isControlFrame);
    invalidUTF8("case 6.18.2",  "e09fbf", isControlFrame);
    invalidUTF8("case 6.18.3",  "f08fbfbf", isControlFrame);
    invalidUTF8("case 6.18.4",  "f887bfbfbf", isControlFrame);
    invalidUTF8("case 6.18.5",  "fc83bfbfbfbf", isControlFrame);

    //6.19: overlong presentation of the NUL character
    invalidUTF8("case 6.19.1",  "c080", isControlFrame);
    invalidUTF8("case 6.19.2",  "e08080", isControlFrame);
    invalidUTF8("case 6.19.3",  "f0808080", isControlFrame);
    invalidUTF8("case 6.19.4",  "f880808080", isControlFrame);
    invalidUTF8("case 6.19.5",  "fc8080808080", isControlFrame);

    //6.20: Single UTF-16 surrogates
    invalidUTF8("case 6.20.1",  "eda080", isControlFrame);
    invalidUTF8("case 6.20.2",  "edadbf", isControlFrame);
    invalidUTF8("case 6.20.3",  "edae80", isControlFrame);
    invalidUTF8("case 6.20.4",  "edafbf", isControlFrame);
    invalidUTF8("case 6.20.5",  "edb080", isControlFrame);
    invalidUTF8("case 6.20.6",  "edbe80", isControlFrame);
    invalidUTF8("case 6.20.7",  "edbfbf", isControlFrame);

    //6.21: Paired UTF-16 surrogates
    invalidUTF8("case 6.21.1",  "eda080edb080", isControlFrame);
    invalidUTF8("case 6.21.2",  "eda080edbfbf", isControlFrame);
    invalidUTF8("case 6.21.3",  "edadbfedb080", isControlFrame);
    invalidUTF8("case 6.21.4",  "edadbfedbfbf", isControlFrame);
    invalidUTF8("case 6.21.5",  "edae80edb080", isControlFrame);
    invalidUTF8("case 6.21.6",  "edae80edbfbf", isControlFrame);
    invalidUTF8("case 6.21.7",  "edafbfedb080", isControlFrame);
    invalidUTF8("case 6.21.8",  "edafbfedbfbf", isControlFrame);
}

void tst_DataProcessor::invalidPayload()
{
    doTest();
}

void tst_DataProcessor::invalidPayloadInCloseFrame_data()
{
    invalidPayload_data(true);
}

void tst_DataProcessor::invalidPayloadInCloseFrame()
{
    QFETCH(quint8, firstByte);
    QFETCH(quint8, secondByte);
    QFETCH(QByteArray, payload);
    QFETCH(bool, isContinuationFrame);
    QFETCH(QWebSocketProtocol::CloseCode, expectedCloseCode);

    Q_UNUSED(isContinuationFrame);

    QByteArray data;
    QBuffer buffer;
    QWebSocketDataProcessor dataProcessor;
    QSignalSpy closeSpy(&dataProcessor,
                        SIGNAL(closeReceived(QWebSocketProtocol::CloseCode,QString)));
    QSignalSpy errorSpy(&dataProcessor,
                        SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode,QString)));
    QSignalSpy pingMessageSpy(&dataProcessor, SIGNAL(pingReceived(QByteArray)));
    QSignalSpy pongMessageSpy(&dataProcessor, SIGNAL(pongReceived(QByteArray)));
    QSignalSpy textMessageSpy(&dataProcessor, SIGNAL(textMessageReceived(QString)));
    QSignalSpy binaryMessageSpy(&dataProcessor, SIGNAL(binaryMessageReceived(QByteArray)));
    QSignalSpy textFrameSpy(&dataProcessor, SIGNAL(textFrameReceived(QString,bool)));
    QSignalSpy binaryFrameSpy(&dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool)));

    dataProcessor.setIdleTimeout(DefaultIdleTimeout);

    data.append(firstByte).append(secondByte);
    data.append(payload);
    buffer.setData(data);
    buffer.open(QIODevice::ReadOnly);
    dataProcessor.process(&buffer);
    QCOMPARE(closeSpy.size(), 1);
    QCOMPARE(errorSpy.size(), 0);
    QCOMPARE(pingMessageSpy.size(), 0);
    QCOMPARE(pongMessageSpy.size(), 0);
    QCOMPARE(textMessageSpy.size(), 0);
    QCOMPARE(binaryMessageSpy.size(), 0);
    QCOMPARE(textFrameSpy.size(), 0);
    QCOMPARE(binaryFrameSpy.size(), 0);
    QVariantList arguments = closeSpy.takeFirst();
    QCOMPARE(arguments.at(0).value<QWebSocketProtocol::CloseCode>(), expectedCloseCode);
    buffer.close();
}

void tst_DataProcessor::incompletePayload_data()
{
    QTest::addColumn<quint8>("firstByte");
    QTest::addColumn<quint8>("secondByte");
    QTest::addColumn<QByteArray>("payload");
    QTest::addColumn<bool>("isContinuationFrame");
    QTest::addColumn<QWebSocketProtocol::CloseCode>("expectedCloseCode");

    incompleteFrame(QWebSocketProtocol::OpCodeText, 125, 0);
    incompleteFrame(QWebSocketProtocol::OpCodeText, 64, 32);
    incompleteFrame(QWebSocketProtocol::OpCodeText, 256, 32);
    incompleteFrame(QWebSocketProtocol::OpCodeText, 128000, 32);
    incompleteFrame(QWebSocketProtocol::OpCodeBinary, 125, 0);
    incompleteFrame(QWebSocketProtocol::OpCodeBinary, 64, 32);
    incompleteFrame(QWebSocketProtocol::OpCodeBinary, 256, 32);
    incompleteFrame(QWebSocketProtocol::OpCodeBinary, 128000, 32);
    incompleteFrame(QWebSocketProtocol::OpCodeContinue, 125, 0);
    incompleteFrame(QWebSocketProtocol::OpCodeContinue, 64, 32);
    incompleteFrame(QWebSocketProtocol::OpCodeContinue, 256, 32);
    incompleteFrame(QWebSocketProtocol::OpCodeContinue, 128000, 32);

    incompleteFrame(QWebSocketProtocol::OpCodeClose, 64, 32);
    incompleteFrame(QWebSocketProtocol::OpCodePing, 64, 32);
    incompleteFrame(QWebSocketProtocol::OpCodePong, 64, 32);
}

void tst_DataProcessor::incompletePayload()
{
    doTest(7000);
}

void tst_DataProcessor::incompleteSizeField_data()
{
    QTest::addColumn<quint8>("firstByte");
    QTest::addColumn<quint8>("secondByte");
    QTest::addColumn<QByteArray>("payload");
    QTest::addColumn<bool>("isContinuationFrame");
    QTest::addColumn<QWebSocketProtocol::CloseCode>("expectedCloseCode");

    //for a frame length value of 126
    //there should be 2 bytes following to form a 16-bit frame length
    insertIncompleteSizeFieldTest(126, 0);
    insertIncompleteSizeFieldTest(126, 1);

    //for a frame length value of 127
    //there should be 8 bytes following to form a 64-bit frame length
    insertIncompleteSizeFieldTest(127, 0);
    insertIncompleteSizeFieldTest(127, 1);
    insertIncompleteSizeFieldTest(127, 2);
    insertIncompleteSizeFieldTest(127, 3);
    insertIncompleteSizeFieldTest(127, 4);
    insertIncompleteSizeFieldTest(127, 5);
    insertIncompleteSizeFieldTest(127, 6);
    insertIncompleteSizeFieldTest(127, 7);
}

void tst_DataProcessor::incompleteSizeField()
{
    doTest(7000);
}

//////////////////////////////////////////////////////////////////////////////////////////
/// HELPER FUNCTIONS
//////////////////////////////////////////////////////////////////////////////////////////
void tst_DataProcessor::doTest(int timeout)
{
    QFETCH(quint8, firstByte);
    QFETCH(quint8, secondByte);
    QFETCH(QByteArray, payload);
    QFETCH(bool, isContinuationFrame);
    QFETCH(QWebSocketProtocol::CloseCode, expectedCloseCode);

    QByteArray data;
    QBuffer buffer;
    QWebSocketDataProcessor dataProcessor;
    QSignalSpy errorSpy(&dataProcessor,
                        SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode,QString)));
    QSignalSpy textMessageSpy(&dataProcessor, SIGNAL(textMessageReceived(QString)));
    QSignalSpy binaryMessageSpy(&dataProcessor, SIGNAL(binaryMessageReceived(QByteArray)));
    QSignalSpy textFrameSpy(&dataProcessor, SIGNAL(textFrameReceived(QString,bool)));
    QSignalSpy binaryFrameSpy(&dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool)));

    dataProcessor.setIdleTimeout(DefaultIdleTimeout);

    if (isContinuationFrame)
    {
        data.append(quint8(QWebSocketProtocol::OpCodeText))
            .append(char(1))
            .append(QByteArray(1, 'a'));
    }
    data.append(firstByte).append(secondByte);
    data.append(payload);
    buffer.setData(data);
    buffer.open(QIODevice::ReadOnly);
    dataProcessor.process(&buffer);
    QTRY_VERIFY_WITH_TIMEOUT(errorSpy.size(), timeout);
    QCOMPARE(errorSpy.size(), 1);
    QCOMPARE(textMessageSpy.size(), 0);
    QCOMPARE(binaryMessageSpy.size(), 0);
    QCOMPARE(textFrameSpy.size(), isContinuationFrame ? 1 : 0);
    QCOMPARE(binaryFrameSpy.size(), 0);
    QVariantList arguments = errorSpy.takeFirst();
    QCOMPARE(arguments.at(0).value<QWebSocketProtocol::CloseCode>(), expectedCloseCode);
    buffer.close();
    errorSpy.clear();
    data.clear();
}

void tst_DataProcessor::doCloseFrameTest()
{
    QFETCH(quint8, firstByte);
    QFETCH(quint8, secondByte);
    QFETCH(QByteArray, payload);
    QFETCH(bool, isContinuationFrame);
    QFETCH(QWebSocketProtocol::CloseCode, expectedCloseCode);

    Q_UNUSED(isContinuationFrame);

    QByteArray data;
    QBuffer buffer;
    QWebSocketDataProcessor dataProcessor;
    QSignalSpy closeSpy(&dataProcessor,
                        SIGNAL(closeReceived(QWebSocketProtocol::CloseCode,QString)));
    QSignalSpy errorSpy(&dataProcessor,
                        SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode,QString)));
    QSignalSpy textMessageSpy(&dataProcessor, SIGNAL(textMessageReceived(QString)));
    QSignalSpy binaryMessageSpy(&dataProcessor, SIGNAL(binaryMessageReceived(QByteArray)));
    QSignalSpy textFrameSpy(&dataProcessor, SIGNAL(textFrameReceived(QString,bool)));
    QSignalSpy binaryFrameSpy(&dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool)));

    dataProcessor.setIdleTimeout(DefaultIdleTimeout);

    data.append(firstByte).append(secondByte);
    data.append(payload);
    buffer.setData(data);
    buffer.open(QIODevice::ReadOnly);
    dataProcessor.process(&buffer);
    QCOMPARE(closeSpy.size(), 1);
    QCOMPARE(errorSpy.size(), 0);
    QCOMPARE(textMessageSpy.size(), 0);
    QCOMPARE(binaryMessageSpy.size(), 0);
    QCOMPARE(textFrameSpy.size(), 0);
    QCOMPARE(binaryFrameSpy.size(), 0);
    QVariantList arguments = closeSpy.takeFirst();
    QCOMPARE(arguments.at(0).value<QWebSocketProtocol::CloseCode>(), expectedCloseCode);
    buffer.close();
}

QString tst_DataProcessor::opCodeToString(quint8 opCode)
{
    QString frameType;
    switch (opCode)
    {
        case QWebSocketProtocol::OpCodeBinary:
        {
            frameType = QStringLiteral("Binary");
            break;
        }
        case QWebSocketProtocol::OpCodeText:
        {
            frameType = QStringLiteral("Text");
            break;
        }
        case QWebSocketProtocol::OpCodePing:
        {
            frameType = QStringLiteral("Ping");
            break;
        }
        case QWebSocketProtocol::OpCodePong:
        {
            frameType = QStringLiteral("Pong");
            break;
        }
        case QWebSocketProtocol::OpCodeClose:
        {
            frameType = QStringLiteral("Close");
            break;
        }
        case QWebSocketProtocol::OpCodeContinue:
        {
            frameType = QStringLiteral("Continuation");
            break;
        }
        case QWebSocketProtocol::OpCodeReserved3:
        {
            frameType = QStringLiteral("Reserved3");
            break;
        }
        case QWebSocketProtocol::OpCodeReserved4:
        {
            frameType = QStringLiteral("Reserved5");
            break;
        }
        case QWebSocketProtocol::OpCodeReserved5:
        {
            frameType = QStringLiteral("Reserved5");
            break;
        }
        case QWebSocketProtocol::OpCodeReserved6:
        {
            frameType = QStringLiteral("Reserved6");
            break;
        }
        case QWebSocketProtocol::OpCodeReserved7:
        {
            frameType = QStringLiteral("Reserved7");
            break;
        }
        case QWebSocketProtocol::OpCodeReservedB:
        {
            frameType = QStringLiteral("ReservedB");
            break;
        }
        case QWebSocketProtocol::OpCodeReservedC:
        {
            frameType = QStringLiteral("ReservedC");
            break;
        }
        case QWebSocketProtocol::OpCodeReservedD:
        {
            frameType = QStringLiteral("ReservedD");
            break;
        }
        case QWebSocketProtocol::OpCodeReservedE:
        {
            frameType = QStringLiteral("ReservedE");
            break;
        }
        case QWebSocketProtocol::OpCodeReservedF:
        {
            frameType = QStringLiteral("ReservedF");
            break;
        }
        default:
        {
            //should never come here
            Q_ASSERT(false);
        }
    }
    return frameType;
}

void tst_DataProcessor::minimumSize16Bit(quint16 sizeInBytes)
{
    quint16 swapped16 = qToBigEndian<quint16>(sizeInBytes);
    const char *wireRepresentation
            = static_cast<const char *>(static_cast<const void *>(&swapped16));
    QTest::newRow(QStringLiteral("Text frame with payload size %1, represented in 2 bytes")
                  .arg(sizeInBytes).toLatin1().constData())
            << quint8(FIN | QWebSocketProtocol::OpCodeText)
            << quint8(126)
            << QByteArray(wireRepresentation, 2)
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;
    QTest::newRow(QStringLiteral("Binary frame with payload size %1, represented in 2 bytes")
                  .arg(sizeInBytes).toLatin1().constBegin())
            << quint8(FIN | QWebSocketProtocol::OpCodeBinary)
            << quint8(126)
            << QByteArray(wireRepresentation, 2)
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;
    QTest::newRow(QStringLiteral("Continuation frame with payload size %1, represented in 2 bytes")
                  .arg(sizeInBytes).toLatin1().constData())
            << quint8(FIN | QWebSocketProtocol::OpCodeContinue)
            << quint8(126)
            << QByteArray(wireRepresentation, 2)
            << true
            << QWebSocketProtocol::CloseCodeProtocolError;
}

void tst_DataProcessor::minimumSize64Bit(quint64 sizeInBytes)
{
    quint64 swapped64 = qToBigEndian<quint64>(sizeInBytes);
    const char *wireRepresentation
            = static_cast<const char *>(static_cast<const void *>(&swapped64));

    QTest::newRow(QStringLiteral("Text frame with payload size %1, represented in 8 bytes")
                  .arg(sizeInBytes).toLatin1().constData())
            << quint8(FIN | QWebSocketProtocol::OpCodeText)
            << quint8(127)
            << QByteArray(wireRepresentation, 8)
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;

    QTest::newRow(QStringLiteral("Binary frame with payload size %1, represented in 8 bytes")
                  .arg(sizeInBytes).toLatin1().constData())
            << quint8(FIN | QWebSocketProtocol::OpCodeBinary)
            << quint8(127)
            << QByteArray(wireRepresentation, 8)
            << false
            << QWebSocketProtocol::CloseCodeProtocolError;

    QTest::newRow(QStringLiteral("Continuation frame with payload size %1, represented in 8 bytes")
                  .arg(sizeInBytes).toLatin1().constData())
            << quint8(FIN | QWebSocketProtocol::OpCodeContinue)
            << quint8(127)
            << QByteArray(wireRepresentation, 8)
            << true
            << QWebSocketProtocol::CloseCodeProtocolError;
}

void tst_DataProcessor::invalidUTF8(const char *dataTag, const char *utf8Sequence,
                                    bool isCloseFrame)
{
    QByteArray payload = QByteArray::fromHex(utf8Sequence);

    if (isCloseFrame)
    {
        quint16 closeCode = qToBigEndian<quint16>(QWebSocketProtocol::CloseCodeNormal);
        const char *wireRepresentation
                = static_cast<const char *>(static_cast<const void *>(&closeCode));
        QTest::newRow(QStringLiteral("Close frame with invalid UTF8-sequence: %1")
                      .arg(dataTag).toLatin1().constData())
                << quint8(FIN | QWebSocketProtocol::OpCodeClose)
                << quint8(payload.size() + 2)
                << QByteArray(wireRepresentation, 2).append(payload)
                << false
                << QWebSocketProtocol::CloseCodeWrongDatatype;
    }
    else
    {
        QTest::newRow(QStringLiteral("Text frame with invalid UTF8-sequence: %1")
                      .arg(dataTag).toLatin1().constData())
                << quint8(FIN | QWebSocketProtocol::OpCodeText)
                << quint8(payload.size())
                << payload
                << false
                << QWebSocketProtocol::CloseCodeWrongDatatype;

        QTest::newRow(QStringLiteral("Continuation text frame with invalid UTF8-sequence: %1")
                      .arg(dataTag).toLatin1().constData())
                << quint8(FIN | QWebSocketProtocol::OpCodeContinue)
                << quint8(payload.size())
                << payload
                << true
                << QWebSocketProtocol::CloseCodeWrongDatatype;
    }
}

void tst_DataProcessor::invalidField(const char *dataTag, quint8 invalidFieldValue)
{
    QTest::newRow(dataTag) << quint8(FIN | invalidFieldValue)
                           << quint8(0x00)
                           << QByteArray()
                           << false
                           << QWebSocketProtocol::CloseCodeProtocolError;
    QTest::newRow(QString::fromLatin1(dataTag).append(QStringLiteral(" with continuation frame"))
                  .toLatin1().constData())
                            << quint8(FIN | invalidFieldValue)
                            << quint8(0x00)
                            << QByteArray()
                            << true
                            << QWebSocketProtocol::CloseCodeProtocolError;
}

void tst_DataProcessor::incompleteFrame(quint8 controlCode, quint64 indicatedSize,
                                        quint64 actualPayloadSize)
{
    QVERIFY(!QWebSocketProtocol::isOpCodeReserved(QWebSocketProtocol::OpCode(controlCode)));
    QVERIFY(indicatedSize > actualPayloadSize);

    QString frameType = opCodeToString(controlCode);
    QByteArray firstFrame;

    if (indicatedSize < 126)
    {
        //doing extensive QStringLiteral concatenations here, because
        //MSVC 2010 complains when using concatenation literal strings about
        //concatenation of wide and narrow strings (error C2308)
        QTest::newRow(frameType
                      .append(QStringLiteral(" frame with payload size %1, but only %2 bytes") +
                              QStringLiteral(" of data")
                      .arg(indicatedSize).arg(actualPayloadSize)).toLatin1().constData())
                << quint8(FIN | controlCode)
                << quint8(indicatedSize)
                << firstFrame.append(QByteArray(actualPayloadSize, 'a'))
                << (controlCode == QWebSocketProtocol::OpCodeContinue)
                << QWebSocketProtocol::CloseCodeGoingAway;
    }
    else if (indicatedSize <= 0xFFFFu)
    {
        quint16 swapped16 = qToBigEndian<quint16>(static_cast<quint16>(indicatedSize));
        const char *wireRepresentation
                = static_cast<const char *>(static_cast<const void *>(&swapped16));
        QTest::newRow(
                    frameType.append(QStringLiteral(" frame with payload size %1, but only ") +
                                     QStringLiteral("%2 bytes of data")
                                     .arg(indicatedSize)
                                     .arg(actualPayloadSize)).toLatin1().constData())
                << quint8(FIN | controlCode)
                << quint8(126)
                << firstFrame.append(QByteArray(wireRepresentation, 2)
                                     .append(QByteArray(actualPayloadSize, 'a')))
                << (controlCode == QWebSocketProtocol::OpCodeContinue)
                << QWebSocketProtocol::CloseCodeGoingAway;
    }
    else
    {
        quint64 swapped64 = qToBigEndian<quint64>(indicatedSize);
        const char *wireRepresentation
                = static_cast<const char *>(static_cast<const void *>(&swapped64));
        QTest::newRow(frameType
                      .append(QStringLiteral(" frame with payload size %1, but only %2 bytes ") +
                              QStringLiteral("of data")
                                .arg(indicatedSize).arg(actualPayloadSize)).toLatin1().constData())
                << quint8(FIN | controlCode)
                << quint8(127)
                << firstFrame.append(QByteArray(wireRepresentation, 8)
                                     .append(QByteArray(actualPayloadSize, 'a')))
                << (controlCode == QWebSocketProtocol::OpCodeContinue)
                << QWebSocketProtocol::CloseCodeGoingAway;
    }
}

void tst_DataProcessor::nonCharacterSequence(const char *sequence)
{
    QByteArray utf8Sequence = QByteArray::fromHex(sequence);

    //doing extensive QStringLiteral concatenations here, because
    //MSVC 2010 complains when using concatenation literal strings about
    //concatenation of wide and narrow strings (error C2308)
    QTest::newRow((QStringLiteral("Text frame with payload containing the non-control character ") +
                  QStringLiteral("sequence 0x%1"))
                  .arg(QString::fromLocal8Bit(sequence)).toLatin1().constData())
            << quint8(FIN | QWebSocketProtocol::OpCodeText)
            << quint8(utf8Sequence.size())
            << utf8Sequence
            << false;

    QTest::newRow((QStringLiteral("Continuation frame with payload containing the non-control ") +
                  QStringLiteral("character sequence 0x%1"))
                  .arg(QString::fromLocal8Bit(sequence)).toLatin1().constData())
            << quint8(FIN | QWebSocketProtocol::OpCodeContinue)
            << quint8(utf8Sequence.size())
            << utf8Sequence
            << true;
}

void tst_DataProcessor::insertIncompleteSizeFieldTest(quint8 payloadCode, quint8 numBytesFollowing)
{
    //doing extensive QStringLiteral concatenations here, because
    //MSVC 2010 complains when using concatenation literal strings about
    //concatenation of wide and narrow strings (error C2308)
    QTest::newRow(QStringLiteral("Text frame with payload size %1, with %2 bytes following.")
                  .arg(payloadCode).arg(numBytesFollowing).toLatin1().constData())
            << quint8(FIN | QWebSocketProtocol::OpCodeText)
            << quint8(payloadCode)
            << QByteArray(numBytesFollowing, quint8(1))
            << false
            << QWebSocketProtocol::CloseCodeGoingAway;
    QTest::newRow(QStringLiteral("Binary frame with payload size %1, with %2 bytes following.")
                  .arg(payloadCode).arg(numBytesFollowing).toLatin1().constData())
            << quint8(FIN | QWebSocketProtocol::OpCodeBinary)
            << quint8(payloadCode)
            << QByteArray(numBytesFollowing, quint8(1))
            << false
            << QWebSocketProtocol::CloseCodeGoingAway;
    QTest::newRow((QStringLiteral("Continuation frame with payload size %1, with %2 bytes ") +
                  QStringLiteral("following."))
                  .arg(payloadCode).arg(numBytesFollowing).toLatin1().constData())
            << quint8(FIN | QWebSocketProtocol::OpCodeContinue)
            << quint8(payloadCode)
            << QByteArray(numBytesFollowing, quint8(1))
            << true
            << QWebSocketProtocol::CloseCodeGoingAway;
}

void tst_DataProcessor::clearDataBuffers()
{
    const QByteArray binaryData("Hello!");
    QByteArray data;
    data.append(char(FIN | QWebSocketProtocol::OpCodeBinary));
    data.append(char(binaryData.size()));
    data.append(binaryData);

    QWebSocketDataProcessor dataProcessor;
    dataProcessor.setIdleTimeout(DefaultIdleTimeout);
    connect(&dataProcessor, &QWebSocketDataProcessor::binaryMessageReceived,
            [&binaryData](const QByteArray &message)
    {
        QCOMPARE(message, binaryData);
        QEventLoop loop;
        QTimer::singleShot(2000, &loop, SLOT(quit()));
        loop.exec();
    });

    QBuffer buffer;
    buffer.setData(data);
    auto processData = [&dataProcessor, &buffer]()
    {
        buffer.open(QIODevice::ReadOnly);
        dataProcessor.process(&buffer);
        buffer.close();
    };

    QTimer timer;
    timer.setSingleShot(true);
    connect(&timer, &QTimer::timeout, processData);

    timer.start(1000);
    processData();
    QTest::qWait(2000);
}

QTEST_MAIN(tst_DataProcessor)

#include "tst_dataprocessor.moc"
