Revision control

// 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 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"