Revision control

Copy as Markdown

// 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 = 0x20;
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
/*!
Tests that QWebSocketDataProcessor stops handling a connection once it has
reported an error. RFC 6455 requires the connection to be failed on a
protocol violation (paragraphs 5.2 and 8.1) and forbids processing any
further data from the peer afterwards (paragraph 7.1.7), including bytes
already received (paragraph 7.1.1). The same handling is applied to frame
sequences that do not match the fragmentation rules of paragraph 5.4 and
to messages exceeding the configured limit.
*/
void stopProcessingOnError();
void stopProcessingOnError_data();
void stopProcessingOnTimeout();
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();
dataProcessor.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();
dataProcessor.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();
dataProcessor.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();
dataProcessor.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();
dataProcessor.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,
this, [&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, this, processData);
timer.start(1000);
processData();
QTest::qWait(2000);
}
void tst_DataProcessor::stopProcessingOnError_data()
{
QTest::addColumn<QByteArray>("data");
QTest::addColumn<QWebSocketProtocol::CloseCode>("expectedCloseCode");
// frames legitimately delivered before the violation is detected
QTest::addColumn<int>("expectedTextFrames");
// zero leaves the default limit in place
QTest::addColumn<quint64>("maxMessageSize");
// the well formed frame trailing most rows; it must never be delivered
QByteArray textFrame;
textFrame.append(char(FIN | QWebSocketProtocol::OpCodeText));
textFrame.append(char(5));
textFrame.append("hello");
// Ensure a continuation frame that has no message to continue is rejected,
// and that the text frame behind it is not delivered.
QByteArray strayContinuation;
strayContinuation.append(char(FIN | QWebSocketProtocol::OpCodeContinue));
strayContinuation.append(char(0));
QTest::newRow("stray continuation frame")
<< strayContinuation + textFrame << QWebSocketProtocol::CloseCodeProtocolError
<< 0 << quint64(0);
// Ensure a frame rejected on its header does not leak the payload it
// announced.
QByteArray rsv1Frame;
rsv1Frame.append(char(FIN | RSV1 | QWebSocketProtocol::OpCodeText));
rsv1Frame.append(char(textFrame.size()));
QTest::newRow("rsv1 set, announced payload is a frame")
<< rsv1Frame + textFrame << QWebSocketProtocol::CloseCodeProtocolError
<< 0 << quint64(0);
// Ensure a payload that fails UTF-8 validation stops processing.
QByteArray invalidUtf8Frame;
invalidUtf8Frame.append(char(FIN | QWebSocketProtocol::OpCodeText));
invalidUtf8Frame.append(char(1));
invalidUtf8Frame.append(char(0xFF));
QTest::newRow("invalid utf-8")
<< invalidUtf8Frame + textFrame << QWebSocketProtocol::CloseCodeWrongDatatype
<< 0 << quint64(0);
// Ensure a data frame arriving mid-fragmentation is rejected: once a message
// is fragmented, the frames that follow must use opcode 0.
QByteArray firstFragment;
firstFragment.append(char(QWebSocketProtocol::OpCodeText));
firstFragment.append(char(1));
firstFragment.append("a");
QByteArray secondDataFrame;
secondDataFrame.append(char(FIN | QWebSocketProtocol::OpCodeText));
secondDataFrame.append(char(1));
secondDataFrame.append("b");
QTest::newRow("data frame during a fragmented message")
<< firstFragment + secondDataFrame + textFrame
<< QWebSocketProtocol::CloseCodeProtocolError << 1 << quint64(0);
// Ensure even the peer's Close frame is ignored after a failure.
QByteArray closeFrame;
closeFrame.append(char(FIN | QWebSocketProtocol::OpCodeClose));
closeFrame.append(char(2));
closeFrame.append(char(0x03));
closeFrame.append(char(0xE8));
QTest::newRow("close frame after the violation")
<< strayContinuation + closeFrame << QWebSocketProtocol::CloseCodeProtocolError
<< 0 << quint64(0);
// Ensure exceeding the configured message limit stops processing.
QTest::newRow("message too big")
<< textFrame + textFrame << QWebSocketProtocol::CloseCodeTooMuchData
<< 0 << quint64(4);
}
void tst_DataProcessor::stopProcessingOnError()
{
QFETCH(QByteArray, data);
QFETCH(QWebSocketProtocol::CloseCode, expectedCloseCode);
QFETCH(int, expectedTextFrames);
QFETCH(quint64, maxMessageSize);
QWebSocketDataProcessor dataProcessor;
dataProcessor.setIdleTimeout(DefaultIdleTimeout);
if (maxMessageSize)
dataProcessor.setMaxAllowedMessageSize(maxMessageSize);
QSignalSpy errorSpy(&dataProcessor, &QWebSocketDataProcessor::errorEncountered);
QSignalSpy textFrameSpy(&dataProcessor, &QWebSocketDataProcessor::textFrameReceived);
QSignalSpy textMessageSpy(&dataProcessor, &QWebSocketDataProcessor::textMessageReceived);
QSignalSpy closeSpy(&dataProcessor, &QWebSocketDataProcessor::closeReceived);
QBuffer buffer;
buffer.setData(data);
buffer.open(QIODevice::ReadOnly);
// the violating frame is followed by a well-formed text frame in the same
// buffer; processing must stop at the violation
QVERIFY(!dataProcessor.process(&buffer));
QCOMPARE(errorSpy.size(), 1);
QCOMPARE(errorSpy.first().at(0).value<QWebSocketProtocol::CloseCode>(), expectedCloseCode);
QCOMPARE(textFrameSpy.size(), expectedTextFrames);
QCOMPARE(textMessageSpy.size(), 0);
// no further data is parsed for a connection that has failed
QVERIFY(!dataProcessor.process(&buffer));
QCOMPARE(errorSpy.size(), 1);
QCOMPARE(textFrameSpy.size(), expectedTextFrames);
QCOMPARE(textMessageSpy.size(), 0);
QCOMPARE(closeSpy.size(), 0);
buffer.close();
// ensure processor is usable again after clear()
dataProcessor.clear();
QByteArray goodFrame;
goodFrame.append(char(FIN | QWebSocketProtocol::OpCodeText));
goodFrame.append(char(2));
goodFrame.append("ok");
QBuffer reopened;
reopened.setData(goodFrame);
reopened.open(QIODevice::ReadOnly);
QVERIFY(dataProcessor.process(&reopened));
QCOMPARE(textMessageSpy.size(), 1);
QCOMPARE(textMessageSpy.first().at(0).toString(), QStringLiteral("ok"));
}
void tst_DataProcessor::stopProcessingOnTimeout()
{
QWebSocketDataProcessor dataProcessor;
dataProcessor.setIdleTimeout(DefaultIdleTimeout);
QSignalSpy errorSpy(&dataProcessor, &QWebSocketDataProcessor::errorEncountered);
QSignalSpy textMessageSpy(&dataProcessor, &QWebSocketDataProcessor::textMessageReceived);
// a header announcing a 7 byte payload, with no payload yet
QByteArray header;
header.append(char(FIN | QWebSocketProtocol::OpCodeText));
header.append(char(7));
QBuffer buffer;
buffer.setData(header);
buffer.open(QIODevice::ReadOnly);
QVERIFY(!dataProcessor.process(&buffer));
buffer.close();
QTRY_VERIFY_WITH_TIMEOUT(errorSpy.size(), 7000);
QCOMPARE(errorSpy.size(), 1);
QCOMPARE(errorSpy.first().at(0).value<QWebSocketProtocol::CloseCode>(),
QWebSocketProtocol::CloseCodeGoingAway);
// the announced payload finally arrives; it must not be parsed as a frame
QByteArray payload;
payload.append(char(FIN | QWebSocketProtocol::OpCodeText));
payload.append(char(5));
payload.append("hello");
QBuffer resumed;
resumed.setData(payload);
resumed.open(QIODevice::ReadOnly);
QVERIFY(!dataProcessor.process(&resumed));
QCOMPARE(textMessageSpy.size(), 0);
}
QTEST_MAIN(tst_DataProcessor)
#include "tst_dataprocessor.moc"