diff options
author | Liang Qi <liang.qi@theqtcompany.com> | 2015-06-29 20:24:40 +0200 |
---|---|---|
committer | Liang Qi <liang.qi@theqtcompany.com> | 2015-06-30 16:26:08 +0200 |
commit | c55e385f1f70f11d3ffd0f8372ccb8e7405018c5 (patch) | |
tree | a04c2e84b43830ecf9c8817f830055bfcf64cc0d /tests/auto/websockets | |
parent | a3e0fe474c20eb526af9668c5cc97cf7055c77fa (diff) | |
parent | 086f3722777ee61f3c019ca064d4a844cf0f6fe4 (diff) | |
download | qtwebsockets-c55e385f1f70f11d3ffd0f8372ccb8e7405018c5.tar.gz |
Merge remote-tracking branch 'origin/5.5' into dev
Conflicts:
.qmake.conf
src/websockets/qwebsocket_p.cpp
Change-Id: Ibcc1898263cc8bc80066370f045df6a217e46574
Diffstat (limited to 'tests/auto/websockets')
19 files changed, 4602 insertions, 0 deletions
diff --git a/tests/auto/websockets/dataprocessor/dataprocessor.pro b/tests/auto/websockets/dataprocessor/dataprocessor.pro new file mode 100644 index 0000000..ac5ba5c --- /dev/null +++ b/tests/auto/websockets/dataprocessor/dataprocessor.pro @@ -0,0 +1,14 @@ +CONFIG += console +CONFIG += testcase +CONFIG -= app_bundle + +TEMPLATE = app + +TARGET = tst_dataprocessor + +QT = core testlib websockets websockets-private + +SOURCES += tst_dataprocessor.cpp + +requires(contains(QT_CONFIG, private_tests)) +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 diff --git a/tests/auto/websockets/dataprocessor/tst_dataprocessor.cpp b/tests/auto/websockets/dataprocessor/tst_dataprocessor.cpp new file mode 100644 index 0000000..c1dd9a1 --- /dev/null +++ b/tests/auto/websockets/dataprocessor/tst_dataprocessor.cpp @@ -0,0 +1,1842 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Kurt Pattyn <pattyn.kurt@gmail.com>. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <QtTest/QtTest> +#include <QtTest/qtestcase.h> +#include <QtTest/QSignalSpy> +#include <QtCore/QBuffer> +#include <QtCore/QByteArray> +#include <QtCore/QDebug> + +#include "private/qwebsocketdataprocessor_p.h" +#include "private/qwebsocketprotocol_p.h" +#include "QtWebSockets/qwebsocketprotocol.h" + +const quint8 FIN = 0x80; +const quint8 RSV1 = 0x40; +const quint8 RSV2 = 0x30; +const quint8 RSV3 = 0x10; + +QT_USE_NAMESPACE + +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(); + +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(); + 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.length() < 126) + { + data.append(char(payload.length())); + } + else if (payload.length() < 65536) + { + quint16 swapped = qToBigEndian<quint16>(payload.length()); + 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.length()); + 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.count(), 0); + QCOMPARE(pingReceivedSpy.count(), 0); + QCOMPARE(pongReceivedSpy.count(), 0); + QCOMPARE(closeReceivedSpy.count(), 0); + QCOMPARE(binaryFrameReceivedSpy.count(), 1); + QCOMPARE(binaryMessageReceivedSpy.count(), 1); + QCOMPARE(textFrameReceivedSpy.count(), 0); + QCOMPARE(textMessageReceivedSpy.count(), 0); + QList<QVariant> arguments = binaryFrameReceivedSpy.takeFirst(); + QCOMPARE(arguments.at(0).toByteArray().length(), payload.length()); + arguments = binaryMessageReceivedSpy.takeFirst(); + QCOMPARE(arguments.at(0).toByteArray().length(), payload.length()); + 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.length() < 126) + { + data.append(char(payload.length())); + } + else if (payload.length() < 65536) + { + quint16 swapped = qToBigEndian<quint16>(payload.length()); + 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.length()); + 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.count(), 0); + QCOMPARE(pingReceivedSpy.count(), 0); + QCOMPARE(pongReceivedSpy.count(), 0); + QCOMPARE(closeReceivedSpy.count(), 0); + QCOMPARE(textFrameReceivedSpy.count(), 1); + QCOMPARE(textMessageReceivedSpy.count(), 1); + QCOMPARE(binaryFrameReceivedSpy.count(), 0); + QCOMPARE(binaryMessageReceivedSpy.count(), 0); + QList<QVariant> arguments = textFrameReceivedSpy.takeFirst(); + QCOMPARE(arguments.at(0).toString().length(), size); + arguments = textMessageReceivedSpy.takeFirst(); + QCOMPARE(arguments.at(0).toString().length(), 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(QChar::fromLatin1(0)); + buffer.setData(data); + buffer.open(QIODevice::ReadOnly); + dataProcessor.process(&buffer); + QCOMPARE(errorReceivedSpy.count(), 0); + QCOMPARE(textFrameReceivedSpy.count(), 0); + QCOMPARE(textMessageReceivedSpy.count(), 0); + QCOMPARE(binaryFrameReceivedSpy.count(), 0); + QCOMPARE(binaryMessageReceivedSpy.count(), 0); + QCOMPARE(closeFrameReceivedSpy.count(), 0); + QCOMPARE(pongReceivedSpy.count(), 0); + QCOMPARE(pingReceivedSpy.count(), 1); + buffer.close(); + + data.clear(); + pingReceivedSpy.clear(); + pongReceivedSpy.clear(); + data.append((char)(FIN | QWebSocketProtocol::OpCodePong)); + data.append(QChar::fromLatin1(0)); + buffer.setData(data); + buffer.open(QIODevice::ReadOnly); + dataProcessor.process(&buffer); + QCOMPARE(errorReceivedSpy.count(), 0); + QCOMPARE(textFrameReceivedSpy.count(), 0); + QCOMPARE(textMessageReceivedSpy.count(), 0); + QCOMPARE(binaryFrameReceivedSpy.count(), 0); + QCOMPARE(binaryMessageReceivedSpy.count(), 0); + QCOMPARE(closeFrameReceivedSpy.count(), 0); + QCOMPARE(pingReceivedSpy.count(), 0); + QCOMPARE(pongReceivedSpy.count(), 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.count(), 0); + QCOMPARE(pingReceivedSpy.count(), opCode == QWebSocketProtocol::OpCodePing ? 1 : 0); + QCOMPARE(pongReceivedSpy.count(), opCode == QWebSocketProtocol::OpCodePong ? 1 : 0); + QCOMPARE(closeReceivedSpy.count(), opCode == QWebSocketProtocol::OpCodeClose ? 1 : 0); + QCOMPARE(textFrameReceivedSpy.count(), opCode == QWebSocketProtocol::OpCodeText ? 1 : 0); + QCOMPARE(textMessageReceivedSpy.count(), opCode == QWebSocketProtocol::OpCodeText ? 1 : 0); + QCOMPARE(binaryFrameReceivedSpy.count(), opCode == QWebSocketProtocol::OpCodeBinary ? 1 : 0); + QCOMPARE(binaryMessageReceivedSpy.count(), 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.length() + 2)).append(wireRepresentation, 2).append(payload); + } + else + { + data.append(QChar::fromLatin1(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.count(), 0); + QCOMPARE(pingReceivedSpy.count(), 0); + QCOMPARE(pongReceivedSpy.count(), 0); + QCOMPARE(closeFrameReceivedSpy.count(), 1); + QCOMPARE(textFrameReceivedSpy.count(), 0); + QCOMPARE(textMessageReceivedSpy.count(), 0); + QCOMPARE(binaryFrameReceivedSpy.count(), 0); + QCOMPARE(binaryMessageReceivedSpy.count(), 0); + QList<QVariant> arguments = closeFrameReceivedSpy.takeFirst(); + QCOMPARE(arguments.at(0).value<QWebSocketProtocol::CloseCode>(), closeCode); + QCOMPARE(arguments.at(1).toString().length(), payload.length()); + 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.count(), 0); + QCOMPARE(closeSpy.count(), 0); + QCOMPARE(pingFrameSpy.count(), 0); + QCOMPARE(pongFrameSpy.count(), 0); + QCOMPARE(textFrameSpy.count(), 1); + QCOMPARE(textMessageSpy.count(), 1); + QCOMPARE(binaryFrameSpy.count(), 0); + QCOMPARE(binaryMessageSpy.count(), 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; + + 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); + + QCOMPARE(errorSpy.count(), 1); + QCOMPARE(closeSpy.count(), 0); + QCOMPARE(pingMessageSpy.count(), 0); + QCOMPARE(pongMessageSpy.count(), 0); + QCOMPARE(textMessageSpy.count(), 0); + QCOMPARE(binaryMessageSpy.count(), 0); + QCOMPARE(textFrameSpy.count(), 0); + QCOMPARE(binaryFrameSpy.count(), 0); + + QList<QVariant> arguments = errorSpy.takeFirst(); + QCOMPARE(arguments.at(0).value<QWebSocketProtocol::CloseCode>(), + QWebSocketProtocol::CloseCodeGoingAway); + errorSpy.clear(); + closeSpy.clear(); + pingMessageSpy.clear(); + pongMessageSpy.clear(); + textMessageSpy.clear(); + binaryMessageSpy.clear(); + textFrameSpy.clear(); + binaryFrameSpy.clear(); + buffer.close(); + data.clear(); + + //only one byte; this is far too little; + //should get a time out as well and the error should be CloseCodeGoingAway + //meaning the socket will be closed + data.append(quint8('1')); //put 1 byte in the buffer; this is too little + buffer.setData(data); + buffer.open(QIODevice::ReadOnly); + + dataProcessor.process(&buffer); + + QCOMPARE(errorSpy.count(), 1); + QCOMPARE(closeSpy.count(), 0); + QCOMPARE(pingMessageSpy.count(), 0); + QCOMPARE(pongMessageSpy.count(), 0); + QCOMPARE(textMessageSpy.count(), 0); + QCOMPARE(binaryMessageSpy.count(), 0); + QCOMPARE(textFrameSpy.count(), 0); + QCOMPARE(binaryFrameSpy.count(), 0); + + arguments = errorSpy.takeFirst(); + QCOMPARE(arguments.at(0).value<QWebSocketProtocol::CloseCode>(), + QWebSocketProtocol::CloseCodeGoingAway); + buffer.close(); + errorSpy.clear(); + closeSpy.clear(); + pingMessageSpy.clear(); + pongMessageSpy.clear(); + textMessageSpy.clear(); + binaryMessageSpy.clear(); + textFrameSpy.clear(); + binaryFrameSpy.clear(); + data.clear(); + + + { + //text frame with final bit not set + data.append((char)(QWebSocketProtocol::OpCodeText)).append(char(0x0)); + buffer.setData(data); + buffer.open(QIODevice::ReadOnly); + + dataProcessor.process(&buffer); + + buffer.close(); + data.clear(); + + //with nothing in the buffer, + //the dataProcessor should time out and the error should be CloseCodeGoingAway + //meaning the socket will be closed + buffer.setData(data); + buffer.open(QIODevice::ReadOnly); + QSignalSpy errorSpy(&dataProcessor, + SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode,QString))); + dataProcessor.process(&buffer); + + QCOMPARE(errorSpy.count(), 1); + QCOMPARE(closeSpy.count(), 0); + QCOMPARE(pingMessageSpy.count(), 0); + QCOMPARE(pongMessageSpy.count(), 0); + QCOMPARE(textMessageSpy.count(), 0); + QCOMPARE(binaryMessageSpy.count(), 0); + QCOMPARE(textFrameSpy.count(), 1); + QCOMPARE(binaryFrameSpy.count(), 0); + + QList<QVariant> arguments = errorSpy.takeFirst(); + QCOMPARE(arguments.at(0).value<QWebSocketProtocol::CloseCode>(), + QWebSocketProtocol::CloseCodeGoingAway); + errorSpy.clear(); + closeSpy.clear(); + pingMessageSpy.clear(); + pongMessageSpy.clear(); + textMessageSpy.clear(); + binaryMessageSpy.clear(); + textFrameSpy.clear(); + binaryFrameSpy.clear(); + buffer.close(); + data.clear(); + + //text frame with final bit not set + data.append((char)(QWebSocketProtocol::OpCodeText)).append(char(0x0)); + buffer.setData(data); + buffer.open(QIODevice::ReadOnly); + dataProcessor.process(&buffer); + + QCOMPARE(errorSpy.count(), 1); + QCOMPARE(closeSpy.count(), 0); + QCOMPARE(pingMessageSpy.count(), 0); + QCOMPARE(pongMessageSpy.count(), 0); + QCOMPARE(textMessageSpy.count(), 0); + QCOMPARE(binaryMessageSpy.count(), 0); + QCOMPARE(textFrameSpy.count(), 1); + QCOMPARE(binaryFrameSpy.count(), 0); + + buffer.close(); + data.clear(); + + errorSpy.clear(); + closeSpy.clear(); + pingMessageSpy.clear(); + pongMessageSpy.clear(); + textMessageSpy.clear(); + binaryMessageSpy.clear(); + textFrameSpy.clear(); + binaryFrameSpy.clear(); + + //only 1 byte follows in continuation frame; + //should time out with close code CloseCodeGoingAway + data.append('a'); + buffer.setData(data); + buffer.open(QIODevice::ReadOnly); + + dataProcessor.process(&buffer); + QCOMPARE(errorSpy.count(), 1); + QCOMPARE(closeSpy.count(), 0); + QCOMPARE(pingMessageSpy.count(), 0); + QCOMPARE(pongMessageSpy.count(), 0); + QCOMPARE(textMessageSpy.count(), 0); + QCOMPARE(binaryMessageSpy.count(), 0); + QCOMPARE(textFrameSpy.count(), 0); + QCOMPARE(binaryFrameSpy.count(), 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 = 0; + + //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("Close 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))); + + data.append(firstByte).append(secondByte); + data.append(payload); + buffer.setData(data); + buffer.open(QIODevice::ReadOnly); + dataProcessor.process(&buffer); + QCOMPARE(closeSpy.count(), 1); + QCOMPARE(errorSpy.count(), 0); + QCOMPARE(pingMessageSpy.count(), 0); + QCOMPARE(pongMessageSpy.count(), 0); + QCOMPARE(textMessageSpy.count(), 0); + QCOMPARE(binaryMessageSpy.count(), 0); + QCOMPARE(textFrameSpy.count(), 0); + QCOMPARE(binaryFrameSpy.count(), 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(); +} + +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(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// HELPER FUNCTIONS +////////////////////////////////////////////////////////////////////////////////////////// +void tst_DataProcessor::doTest() +{ + 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))); + + 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); + QCOMPARE(errorSpy.count(), 1); + QCOMPARE(textMessageSpy.count(), 0); + QCOMPARE(binaryMessageSpy.count(), 0); + QCOMPARE(textFrameSpy.count(), isContinuationFrame ? 1 : 0); + QCOMPARE(binaryFrameSpy.count(), 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))); + + data.append(firstByte).append(secondByte); + data.append(payload); + buffer.setData(data); + buffer.open(QIODevice::ReadOnly); + dataProcessor.process(&buffer); + QCOMPARE(closeSpy.count(), 1); + QCOMPARE(errorSpy.count(), 0); + QCOMPARE(textMessageSpy.count(), 0); + QCOMPARE(binaryMessageSpy.count(), 0); + QCOMPARE(textFrameSpy.count(), 0); + QCOMPARE(binaryFrameSpy.count(), 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.length() + 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.length()) + << 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.length()) + << 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; +} + +QTEST_MAIN(tst_DataProcessor) + +#include "tst_dataprocessor.moc" diff --git a/tests/auto/websockets/handshakerequest/handshakerequest.pro b/tests/auto/websockets/handshakerequest/handshakerequest.pro new file mode 100644 index 0000000..d887cd6 --- /dev/null +++ b/tests/auto/websockets/handshakerequest/handshakerequest.pro @@ -0,0 +1,14 @@ +CONFIG += console +CONFIG += testcase +CONFIG -= app_bundle + +TEMPLATE = app + +TARGET = tst_handshakerequest + +QT = core testlib websockets websockets-private + +SOURCES += tst_handshakerequest.cpp + +requires(contains(QT_CONFIG, private_tests)) +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 diff --git a/tests/auto/websockets/handshakerequest/tst_handshakerequest.cpp b/tests/auto/websockets/handshakerequest/tst_handshakerequest.cpp new file mode 100644 index 0000000..9c579bd --- /dev/null +++ b/tests/auto/websockets/handshakerequest/tst_handshakerequest.cpp @@ -0,0 +1,317 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Kurt Pattyn <pattyn.kurt@gmail.com>. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <QtTest/QtTest> +#include <QtTest/qtestcase.h> +#include <QtCore/QDebug> +#include <QtCore/QByteArray> +#include <QtCore/QtEndian> + +#include "private/qwebsockethandshakerequest_p.h" +#include "private/qwebsocketprotocol_p.h" +#include "QtWebSockets/qwebsocketprotocol.h" + +QT_USE_NAMESPACE + +Q_DECLARE_METATYPE(QWebSocketProtocol::CloseCode) +Q_DECLARE_METATYPE(QWebSocketProtocol::OpCode) + +class tst_HandshakeRequest : public QObject +{ + Q_OBJECT + +public: + tst_HandshakeRequest(); + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void tst_initialization(); + + void tst_invalidStream_data(); + void tst_invalidStream(); + + void tst_multipleValuesInConnectionHeader(); + void tst_multipleVersions(); + + void tst_qtbug_39355(); +}; + +tst_HandshakeRequest::tst_HandshakeRequest() +{} + +void tst_HandshakeRequest::initTestCase() +{ +} + +void tst_HandshakeRequest::cleanupTestCase() +{} + +void tst_HandshakeRequest::init() +{ + qRegisterMetaType<QWebSocketProtocol::OpCode>("QWebSocketProtocol::OpCode"); + qRegisterMetaType<QWebSocketProtocol::CloseCode>("QWebSocketProtocol::CloseCode"); +} + +void tst_HandshakeRequest::cleanup() +{ +} + +void tst_HandshakeRequest::tst_initialization() +{ + { + QWebSocketHandshakeRequest request(0, false); + QCOMPARE(request.port(), 0); + QVERIFY(!request.isSecure()); + QVERIFY(!request.isValid()); + QCOMPARE(request.extensions().length(), 0); + QCOMPARE(request.protocols().length(), 0); + QCOMPARE(request.headers().size(), 0); + QCOMPARE(request.key().length(), 0); + QCOMPARE(request.origin().length(), 0); + QCOMPARE(request.host().length(), 0); + QVERIFY(request.requestUrl().isEmpty()); + QCOMPARE(request.resourceName().length(), 0); + QCOMPARE(request.versions().length(), 0); + } + { + QWebSocketHandshakeRequest request(80, true); + QCOMPARE(request.port(), 80); + QVERIFY(request.isSecure()); + QVERIFY(!request.isValid()); + QCOMPARE(request.extensions().length(), 0); + QCOMPARE(request.protocols().length(), 0); + QCOMPARE(request.headers().size(), 0); + QCOMPARE(request.key().length(), 0); + QCOMPARE(request.origin().length(), 0); + QCOMPARE(request.host().length(), 0); + QVERIFY(request.requestUrl().isEmpty()); + QCOMPARE(request.resourceName().length(), 0); + QCOMPARE(request.versions().length(), 0); + } + { + QWebSocketHandshakeRequest request(80, true); + request.clear(); + QCOMPARE(request.port(), 80); + QVERIFY(request.isSecure()); + QVERIFY(!request.isValid()); + QCOMPARE(request.extensions().length(), 0); + QCOMPARE(request.protocols().length(), 0); + QCOMPARE(request.headers().size(), 0); + QCOMPARE(request.key().length(), 0); + QCOMPARE(request.origin().length(), 0); + QCOMPARE(request.host().length(), 0); + QVERIFY(request.requestUrl().isEmpty()); + QCOMPARE(request.resourceName().length(), 0); + QCOMPARE(request.versions().length(), 0); + } +} + +void tst_HandshakeRequest::tst_invalidStream_data() +{ + QTest::addColumn<QString>("dataStream"); + + QTest::newRow("garbage on 2 lines") << QStringLiteral("foofoofoo\r\nfoofoo\r\n\r\n"); + QTest::newRow("garbage on 1 line") << QStringLiteral("foofoofoofoofoo"); + QTest::newRow("Correctly formatted but invalid fields") + << QStringLiteral("VERB RESOURCE PROTOCOL"); + + //internally the fields are parsed and indexes are used to convert + //to a http version for instance + //this test checks if there doesn't occur an out-of-bounds exception + QTest::newRow("Correctly formatted but invalid short fields") << QStringLiteral("V R P"); + QTest::newRow("Invalid \\0 character in header") << QStringLiteral("V R\0 P"); + QTest::newRow("Invalid HTTP version in header") << QStringLiteral("V R HTTP/invalid"); + QTest::newRow("Empty header field") << QStringLiteral("GET . HTTP/1.1\r\nHEADER: "); + QTest::newRow("All zeros") << QString::fromUtf8(QByteArray(10, char(0))); + QTest::newRow("Invalid hostname") << QStringLiteral("GET . HTTP/1.1\r\nHost: \xFF\xFF"); + //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("Complete header - Invalid WebSocket version") + << QStringLiteral("GET . HTTP/1.1\r\nHost: foo\r\nSec-WebSocket-Version: ") + + QStringLiteral("\xFF\xFF\r\n") + + QStringLiteral("Sec-WebSocket-Key: AVDFBDDFF\r\n") + + QStringLiteral("Upgrade: websocket\r\n") + + QStringLiteral("Connection: Upgrade\r\n\r\n"); + QTest::newRow("Complete header - Invalid verb") + << QStringLiteral("XXX . HTTP/1.1\r\nHost: foo\r\nSec-WebSocket-Version: 13\r\n") + + QStringLiteral("Sec-WebSocket-Key: AVDFBDDFF\r\n") + + QStringLiteral("Upgrade: websocket\r\n") + + QStringLiteral("Connection: Upgrade\r\n\r\n"); + QTest::newRow("Complete header - Invalid HTTP version") + << QStringLiteral("GET . HTTP/a.1\r\nHost: foo\r\nSec-WebSocket-Version: 13\r\n") + + QStringLiteral("Sec-WebSocket-Key: AVDFBDDFF\r\n") + + QStringLiteral("Upgrade: websocket\r\n") + + QStringLiteral("Connection: Upgrade\r\n\r\n"); + QTest::newRow("Complete header - Invalid connection") + << QStringLiteral("GET . HTTP/1.1\r\nHost: foo\r\nSec-WebSocket-Version: 13\r\n") + + QStringLiteral("Sec-WebSocket-Key: AVDFBDDFF\r\n") + + QStringLiteral("Upgrade: websocket\r\n") + + QStringLiteral("Connection: xxxxxxx\r\n\r\n"); + QTest::newRow("Complete header - Invalid upgrade") + << QStringLiteral("GET . HTTP/1.1\r\nHost: foo\r\nSec-WebSocket-Version: 13\r\n") + + QStringLiteral("Sec-WebSocket-Key: AVDFBDDFF\r\n") + + QStringLiteral("Upgrade: wabsocket\r\n") + + QStringLiteral("Connection: Upgrade\r\n\r\n"); + QTest::newRow("Complete header - Upgrade contains too many values") + << QStringLiteral("GET . HTTP/1.1\r\nHost: foo\r\nSec-WebSocket-Version: 13\r\n") + + QStringLiteral("Sec-WebSocket-Key: AVDFBDDFF\r\n") + + QStringLiteral("Upgrade: websocket,ftp\r\n") + + QStringLiteral("Connection: Upgrade\r\n\r\n"); +} + +void tst_HandshakeRequest::tst_invalidStream() +{ + QFETCH(QString, dataStream); + + QByteArray data; + QTextStream textStream(&data); + QWebSocketHandshakeRequest request(80, true); + + textStream << dataStream; + textStream.seek(0); + request.readHandshake(textStream); + + QVERIFY(!request.isValid()); + QCOMPARE(request.port(), 80); + QVERIFY(request.isSecure()); + QCOMPARE(request.extensions().length(), 0); + QCOMPARE(request.protocols().length(), 0); + QCOMPARE(request.headers().size(), 0); + QCOMPARE(request.key().length(), 0); + QCOMPARE(request.origin().length(), 0); + QCOMPARE(request.host().length(), 0); + QVERIFY(request.requestUrl().isEmpty()); + QCOMPARE(request.resourceName().length(), 0); + QCOMPARE(request.versions().length(), 0); +} + +/* + * This is a regression test + * Checks for validity when more than one value is present in Connection + */ +void tst_HandshakeRequest::tst_multipleValuesInConnectionHeader() +{ + //doing extensive QStringLiteral concatenations here, because + //MSVC 2010 complains when using concatenation literal strings about + //concatenation of wide and narrow strings (error C2308) + QString header = QStringLiteral("GET /test HTTP/1.1\r\nHost: ") + + QStringLiteral("foo.com\r\nSec-WebSocket-Version: 13\r\n") + + QStringLiteral("Sec-WebSocket-Key: AVDFBDDFF\r\n") + + QStringLiteral("Upgrade: websocket\r\n") + + QStringLiteral("Connection: Upgrade,keepalive\r\n\r\n"); + QByteArray data; + QTextStream textStream(&data); + QWebSocketHandshakeRequest request(80, false); + + textStream << header; + textStream.seek(0); + request.readHandshake(textStream); + + QVERIFY(request.isValid()); + QCOMPARE(request.port(), 80); + QVERIFY(!request.isSecure()); + QCOMPARE(request.extensions().length(), 0); + QCOMPARE(request.protocols().length(), 0); + QCOMPARE(request.headers().size(), 5); + QCOMPARE(request.key().length(), 9); + QCOMPARE(request.origin().length(), 0); + QCOMPARE(request.requestUrl(), QUrl("ws://foo.com/test")); + QCOMPARE(request.host(), QStringLiteral("foo.com")); + QCOMPARE(request.resourceName().length(), 5); + QCOMPARE(request.versions().length(), 1); + QCOMPARE(request.versions().at(0), QWebSocketProtocol::Version13); +} + +void tst_HandshakeRequest::tst_multipleVersions() +{ + QString header = QStringLiteral("GET /test HTTP/1.1\r\nHost: foo.com\r\n") + + QStringLiteral("Sec-WebSocket-Version: 4, 5, 6, 7, 8, 13\r\n") + + QStringLiteral("Sec-WebSocket-Key: AVDFBDDFF\r\n") + + QStringLiteral("Upgrade: websocket\r\n") + + QStringLiteral("Connection: Upgrade,keepalive\r\n\r\n"); + QByteArray data; + QTextStream textStream(&data); + QWebSocketHandshakeRequest request(80, false); + + textStream << header; + textStream.seek(0); + request.readHandshake(textStream); + + QVERIFY(request.isValid()); + QCOMPARE(request.port(), 80); + QVERIFY(!request.isSecure()); + QCOMPARE(request.extensions().length(), 0); + QCOMPARE(request.protocols().length(), 0); + QCOMPARE(request.headers().size(), 5); + QVERIFY(request.headers().contains(QStringLiteral("host"))); + QVERIFY(request.headers().contains(QStringLiteral("sec-websocket-version"))); + QVERIFY(request.headers().contains(QStringLiteral("sec-websocket-key"))); + QVERIFY(request.headers().contains(QStringLiteral("upgrade"))); + QVERIFY(request.headers().contains(QStringLiteral("connection"))); + QCOMPARE(request.key(), QStringLiteral("AVDFBDDFF")); + QCOMPARE(request.origin().length(), 0); + QCOMPARE(request.requestUrl(), QUrl("ws://foo.com/test")); + QCOMPARE(request.host(), QStringLiteral("foo.com")); + QCOMPARE(request.resourceName().length(), 5); + QCOMPARE(request.versions().length(), 6); + //should be 13 since the list is ordered in decreasing order + QCOMPARE(request.versions().at(0), QWebSocketProtocol::Version13); +} + +void tst_HandshakeRequest::tst_qtbug_39355() +{ + QString header = QStringLiteral("GET /ABC/DEF/ HTTP/1.1\r\nHost: localhost:1234\r\n") + + QStringLiteral("Sec-WebSocket-Version: 13\r\n") + + QStringLiteral("Sec-WebSocket-Key: 2Wg20829/4ziWlmsUAD8Dg==\r\n") + + QStringLiteral("Upgrade: websocket\r\n") + + QStringLiteral("Connection: Upgrade\r\n\r\n"); + QByteArray data; + QTextStream textStream(&data); + QWebSocketHandshakeRequest request(8080, false); + + textStream << header; + textStream.seek(0); + request.readHandshake(textStream); + + QVERIFY(request.isValid()); + QCOMPARE(request.port(), 1234); + QCOMPARE(request.host(), QStringLiteral("localhost")); +} + +QTEST_MAIN(tst_HandshakeRequest) + +#include "tst_handshakerequest.moc" diff --git a/tests/auto/websockets/handshakeresponse/handshakeresponse.pro b/tests/auto/websockets/handshakeresponse/handshakeresponse.pro new file mode 100644 index 0000000..4d7cd50 --- /dev/null +++ b/tests/auto/websockets/handshakeresponse/handshakeresponse.pro @@ -0,0 +1,14 @@ +CONFIG += console +CONFIG += testcase +CONFIG -= app_bundle + +TEMPLATE = app + +TARGET = tst_handshakeresponse + +QT = core testlib websockets websockets-private + +SOURCES += tst_handshakeresponse.cpp + +requires(contains(QT_CONFIG, private_tests)) +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 diff --git a/tests/auto/websockets/handshakeresponse/tst_handshakeresponse.cpp b/tests/auto/websockets/handshakeresponse/tst_handshakeresponse.cpp new file mode 100644 index 0000000..b5f103b --- /dev/null +++ b/tests/auto/websockets/handshakeresponse/tst_handshakeresponse.cpp @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Kurt Pattyn <pattyn.kurt@gmail.com>. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <QtTest/QtTest> +#include <QtTest/qtestcase.h> +#include <QtCore/QDebug> +#include <QtCore/QByteArray> +#include <QtCore/QtEndian> + +#include "private/qwebsockethandshakerequest_p.h" +#include "private/qwebsockethandshakeresponse_p.h" +#include "private/qwebsocketprotocol_p.h" +#include "QtWebSockets/qwebsocketprotocol.h" + +QT_USE_NAMESPACE + +Q_DECLARE_METATYPE(QWebSocketProtocol::CloseCode) +Q_DECLARE_METATYPE(QWebSocketProtocol::OpCode) + +class tst_HandshakeResponse : public QObject +{ + Q_OBJECT + +public: + tst_HandshakeResponse(); + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void tst_date_response(); +}; + +tst_HandshakeResponse::tst_HandshakeResponse() +{} + +void tst_HandshakeResponse::initTestCase() +{ +} + +void tst_HandshakeResponse::cleanupTestCase() +{} + +void tst_HandshakeResponse::init() +{ + qRegisterMetaType<QWebSocketProtocol::OpCode>("QWebSocketProtocol::OpCode"); + qRegisterMetaType<QWebSocketProtocol::CloseCode>("QWebSocketProtocol::CloseCode"); +} + +void tst_HandshakeResponse::cleanup() +{ +} + +void tst_HandshakeResponse::tst_date_response() +{ + QWebSocketHandshakeRequest request(80, false); + QString buffer; + QTextStream input(&buffer); + input << QStringLiteral("GET / HTTP/1.1\r\nHost: example.com\r\nSec-WebSocket-Version: 13\r\n") + + QStringLiteral("Sec-WebSocket-Key: AVDFBDDFF\r\n") + + QStringLiteral("Upgrade: websocket\r\n") + + QStringLiteral("Connection: Upgrade\r\n\r\n"); + request.readHandshake(input); + + QWebSocketHandshakeResponse response(request, "example.com", true, + QList<QWebSocketProtocol::Version>() << QWebSocketProtocol::Version13, + QList<QString>(), + QList<QString>()); + QString data; + QTextStream output(&data); + output << response; + + QStringList list = data.split("\r\n"); + int index = list.indexOf(QRegExp("Date:.*")); + QVERIFY(index > -1); + QVERIFY(QLocale::c().toDateTime(list[index], "'Date:' ddd, dd MMM yyyy hh:mm:ss 'GMT'").isValid()); +} + +QTEST_MAIN(tst_HandshakeResponse) + +#include "tst_handshakeresponse.moc" diff --git a/tests/auto/websockets/qdefaultmaskgenerator/qdefaultmaskgenerator.pro b/tests/auto/websockets/qdefaultmaskgenerator/qdefaultmaskgenerator.pro new file mode 100644 index 0000000..3a951a7 --- /dev/null +++ b/tests/auto/websockets/qdefaultmaskgenerator/qdefaultmaskgenerator.pro @@ -0,0 +1,14 @@ +CONFIG += console +CONFIG += testcase +CONFIG -= app_bundle + +TEMPLATE = app + +TARGET = tst_defaultmaskgenerator + +QT = core testlib websockets websockets-private + +SOURCES += tst_defaultmaskgenerator.cpp + +requires(contains(QT_CONFIG, private_tests)) +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 diff --git a/tests/auto/websockets/qdefaultmaskgenerator/tst_defaultmaskgenerator.cpp b/tests/auto/websockets/qdefaultmaskgenerator/tst_defaultmaskgenerator.cpp new file mode 100644 index 0000000..f5fc5c4 --- /dev/null +++ b/tests/auto/websockets/qdefaultmaskgenerator/tst_defaultmaskgenerator.cpp @@ -0,0 +1,150 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Kurt Pattyn <pattyn.kurt@gmail.com>. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <QtTest/QtTest> +#include <QtTest/qtestcase.h> +#include <QtTest/QSignalSpy> +#include <QtCore/QBuffer> +#include <QtCore/QByteArray> +#include <QtCore/QDebug> + +#include "private/qdefaultmaskgenerator_p.h" + +QT_USE_NAMESPACE + +class tst_QDefaultMaskGenerator : public QObject +{ + Q_OBJECT + +public: + tst_QDefaultMaskGenerator(); + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void tst_randomnessWithoutSeed(); + void tst_randomnessWithSeed(); + +private: +}; + + + +tst_QDefaultMaskGenerator::tst_QDefaultMaskGenerator() +{ +} + +void tst_QDefaultMaskGenerator::initTestCase() +{ +} + +void tst_QDefaultMaskGenerator::cleanupTestCase() +{ +} + +void tst_QDefaultMaskGenerator::init() +{ +} + +void tst_QDefaultMaskGenerator::cleanup() +{ +} + +void tst_QDefaultMaskGenerator::tst_randomnessWithoutSeed() +{ + //generate two series of data, and see if they differ + { + QDefaultMaskGenerator generator; + + QVector<quint32> series1, series2; + for (int i = 0; i < 1000; ++i) + series1 << generator.nextMask(); + for (int i = 0; i < 1000; ++i) + series2 << generator.nextMask(); + + QVERIFY(series1 != series2); + } +} + +void tst_QDefaultMaskGenerator::tst_randomnessWithSeed() +{ + //generate two series of data, and see if they differ + //the generator is seeded + { + QDefaultMaskGenerator generator; + generator.seed(); + + QVector<quint32> series1, series2; + for (int i = 0; i < 1000; ++i) + series1 << generator.nextMask(); + for (int i = 0; i < 1000; ++i) + series2 << generator.nextMask(); + + QVERIFY(series1 != series2); + } + //generates two series of data with 2 distinct generators + //both generators are seeded + { + QDefaultMaskGenerator generator1, generator2; + generator1.seed(); + generator2.seed(); + + QVector<quint32> series1, series2; + for (int i = 0; i < 1000; ++i) { + series1 << generator1.nextMask(); + series2 << generator2.nextMask(); + } + + QVERIFY(series1 != series2); + } + //generates two series of data with 2 distinct generators + //only one of the two is seeded + { + QDefaultMaskGenerator generator1, generator2; + generator1.seed(); + + QVector<quint32> series1, series2; + for (int i = 0; i < 1000; ++i) { + series1 << generator1.nextMask(); + series2 << generator2.nextMask(); + } + + QVERIFY(series1 != series2); + } +} + +QTEST_MAIN(tst_QDefaultMaskGenerator) + +#include "tst_defaultmaskgenerator.moc" diff --git a/tests/auto/websockets/qwebsocket/qwebsocket.pro b/tests/auto/websockets/qwebsocket/qwebsocket.pro new file mode 100644 index 0000000..0155d08 --- /dev/null +++ b/tests/auto/websockets/qwebsocket/qwebsocket.pro @@ -0,0 +1,13 @@ +CONFIG += console +CONFIG += testcase +CONFIG -= app_bundle + +QT = core testlib websockets + +TARGET = tst_qwebsocket + +TEMPLATE = app + +SOURCES += tst_qwebsocket.cpp + +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 diff --git a/tests/auto/websockets/qwebsocket/tst_qwebsocket.cpp b/tests/auto/websockets/qwebsocket/tst_qwebsocket.cpp new file mode 100644 index 0000000..f993f84 --- /dev/null +++ b/tests/auto/websockets/qwebsocket/tst_qwebsocket.cpp @@ -0,0 +1,627 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Kurt Pattyn <pattyn.kurt@gmail.com>. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <QString> +#include <QtTest> +#include <QtWebSockets/QWebSocket> +#include <QtWebSockets/QWebSocketServer> +#include <QtWebSockets/qwebsocketprotocol.h> + +QT_USE_NAMESPACE + +Q_DECLARE_METATYPE(QWebSocketProtocol::Version) + +class EchoServer : public QObject +{ + Q_OBJECT +public: + explicit EchoServer(QObject *parent = Q_NULLPTR); + ~EchoServer(); + + QHostAddress hostAddress() const { return m_pWebSocketServer->serverAddress(); } + quint16 port() const { return m_pWebSocketServer->serverPort(); } + +Q_SIGNALS: + void newConnection(QUrl requestUrl); + void newConnection(QNetworkRequest request); + +private Q_SLOTS: + void onNewConnection(); + void processTextMessage(QString message); + void processBinaryMessage(QByteArray message); + void socketDisconnected(); + +private: + QWebSocketServer *m_pWebSocketServer; + QList<QWebSocket *> m_clients; +}; + +EchoServer::EchoServer(QObject *parent) : + QObject(parent), + m_pWebSocketServer(new QWebSocketServer(QStringLiteral("Echo Server"), + QWebSocketServer::NonSecureMode, this)), + m_clients() +{ + if (m_pWebSocketServer->listen(QHostAddress(QStringLiteral("127.0.0.1")))) { + connect(m_pWebSocketServer, SIGNAL(newConnection()), + this, SLOT(onNewConnection())); + } +} + +EchoServer::~EchoServer() +{ + m_pWebSocketServer->close(); + qDeleteAll(m_clients.begin(), m_clients.end()); +} + +void EchoServer::onNewConnection() +{ + QWebSocket *pSocket = m_pWebSocketServer->nextPendingConnection(); + + Q_EMIT newConnection(pSocket->requestUrl()); + Q_EMIT newConnection(pSocket->request()); + + connect(pSocket, SIGNAL(textMessageReceived(QString)), this, SLOT(processTextMessage(QString))); + connect(pSocket, SIGNAL(binaryMessageReceived(QByteArray)), this, SLOT(processBinaryMessage(QByteArray))); + connect(pSocket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); + + m_clients << pSocket; +} + +void EchoServer::processTextMessage(QString message) +{ + QWebSocket *pClient = qobject_cast<QWebSocket *>(sender()); + if (pClient) { + pClient->sendTextMessage(message); + } +} + +void EchoServer::processBinaryMessage(QByteArray message) +{ + QWebSocket *pClient = qobject_cast<QWebSocket *>(sender()); + if (pClient) { + pClient->sendBinaryMessage(message); + } +} + +void EchoServer::socketDisconnected() +{ + QWebSocket *pClient = qobject_cast<QWebSocket *>(sender()); + if (pClient) { + m_clients.removeAll(pClient); + pClient->deleteLater(); + } +} + +class tst_QWebSocket : public QObject +{ + Q_OBJECT + +public: + tst_QWebSocket(); + +private Q_SLOTS: + void init(); + void initTestCase(); + void cleanupTestCase(); + void tst_initialisation_data(); + void tst_initialisation(); + void tst_settersAndGetters(); + void tst_invalidOpen_data(); + void tst_invalidOpen(); + void tst_invalidOrigin(); + void tst_sendTextMessage(); + void tst_sendBinaryMessage(); + void tst_errorString(); + void tst_openRequest(); +#ifndef QT_NO_NETWORKPROXY + void tst_setProxy(); +#endif +}; + +tst_QWebSocket::tst_QWebSocket() +{ +} + +void tst_QWebSocket::init() +{ + qRegisterMetaType<QWebSocketProtocol::Version>("QWebSocketProtocol::Version"); +} + +void tst_QWebSocket::initTestCase() +{ +} + +void tst_QWebSocket::cleanupTestCase() +{ +} + +void tst_QWebSocket::tst_initialisation_data() +{ + QTest::addColumn<QString>("origin"); + QTest::addColumn<QString>("expectedOrigin"); + QTest::addColumn<QWebSocketProtocol::Version>("version"); + QTest::addColumn<QWebSocketProtocol::Version>("expectedVersion"); + + QTest::newRow("Default origin and version") + << QString() << QString() + << QWebSocketProtocol::VersionUnknown << QWebSocketProtocol::VersionLatest; + QTest::newRow("Specific origin and default version") + << QStringLiteral("qt-project.org") << QStringLiteral("qt-project.org") + << QWebSocketProtocol::VersionUnknown << QWebSocketProtocol::VersionLatest; + QTest::newRow("Specific origin and specific version") + << QStringLiteral("qt-project.org") << QStringLiteral("qt-project.org") + << QWebSocketProtocol::Version7 << QWebSocketProtocol::Version7; +} + +void tst_QWebSocket::tst_initialisation() +{ + QFETCH(QString, origin); + QFETCH(QString, expectedOrigin); + QFETCH(QWebSocketProtocol::Version, version); + QFETCH(QWebSocketProtocol::Version, expectedVersion); + + QScopedPointer<QWebSocket> socket; + + if (origin.isEmpty() && (version == QWebSocketProtocol::VersionUnknown)) + socket.reset(new QWebSocket); + else if (!origin.isEmpty() && (version == QWebSocketProtocol::VersionUnknown)) + socket.reset(new QWebSocket(origin)); + else + socket.reset(new QWebSocket(origin, version)); + + QCOMPARE(socket->origin(), expectedOrigin); + QCOMPARE(socket->version(), expectedVersion); + QCOMPARE(socket->error(), QAbstractSocket::UnknownSocketError); + QVERIFY(socket->errorString().isEmpty()); + QVERIFY(!socket->isValid()); + QVERIFY(socket->localAddress().isNull()); + QCOMPARE(socket->localPort(), quint16(0)); + QCOMPARE(socket->pauseMode(), QAbstractSocket::PauseNever); + QVERIFY(socket->peerAddress().isNull()); + QCOMPARE(socket->peerPort(), quint16(0)); + QVERIFY(socket->peerName().isEmpty()); + QCOMPARE(socket->state(), QAbstractSocket::UnconnectedState); + QCOMPARE(socket->readBufferSize(), 0); + QVERIFY(socket->resourceName().isEmpty()); + QVERIFY(!socket->requestUrl().isValid()); + QCOMPARE(socket->closeCode(), QWebSocketProtocol::CloseCodeNormal); + QVERIFY(socket->closeReason().isEmpty()); + QVERIFY(socket->flush()); + QCOMPARE(socket->sendTextMessage(QStringLiteral("A text message")), 0); + QCOMPARE(socket->sendBinaryMessage(QByteArrayLiteral("A binary message")), 0); +} + +void tst_QWebSocket::tst_settersAndGetters() +{ + QWebSocket socket; + + socket.setPauseMode(QAbstractSocket::PauseNever); + QCOMPARE(socket.pauseMode(), QAbstractSocket::PauseNever); + socket.setPauseMode(QAbstractSocket::PauseOnSslErrors); + QCOMPARE(socket.pauseMode(), QAbstractSocket::PauseOnSslErrors); + + socket.setReadBufferSize(0); + QCOMPARE(socket.readBufferSize(), 0); + socket.setReadBufferSize(128); + QCOMPARE(socket.readBufferSize(), 128); + socket.setReadBufferSize(-1); + QCOMPARE(socket.readBufferSize(), -1); +} + +void tst_QWebSocket::tst_invalidOpen_data() +{ + QTest::addColumn<QString>("url"); + QTest::addColumn<QString>("expectedUrl"); + QTest::addColumn<QString>("expectedPeerName"); + QTest::addColumn<QString>("expectedResourceName"); + QTest::addColumn<QAbstractSocket::SocketState>("stateAfterOpenCall"); + QTest::addColumn<int>("disconnectedCount"); + QTest::addColumn<int>("stateChangedCount"); + + QTest::newRow("Illegal local address") + << QStringLiteral("ws://127.0.0.1:1/") << QStringLiteral("ws://127.0.0.1:1/") + << QStringLiteral("127.0.0.1") + << QStringLiteral("/") << QAbstractSocket::ConnectingState + << 1 + << 2; //going from connecting to disconnected + QTest::newRow("URL containing new line in the hostname") + << QStringLiteral("ws://myhacky\r\nserver/") << QString() + << QString() + << QString() << QAbstractSocket::UnconnectedState + << 0 << 0; + QTest::newRow("URL containing new line in the resource name") + << QStringLiteral("ws://127.0.0.1:1/tricky\r\npath") << QString() + << QString() + << QString() + << QAbstractSocket::UnconnectedState + << 0 << 0; +} + +void tst_QWebSocket::tst_invalidOpen() +{ + QFETCH(QString, url); + QFETCH(QString, expectedUrl); + QFETCH(QString, expectedPeerName); + QFETCH(QString, expectedResourceName); + QFETCH(QAbstractSocket::SocketState, stateAfterOpenCall); + QFETCH(int, disconnectedCount); + QFETCH(int, stateChangedCount); + QWebSocket socket; + QSignalSpy errorSpy(&socket, SIGNAL(error(QAbstractSocket::SocketError))); + QSignalSpy aboutToCloseSpy(&socket, SIGNAL(aboutToClose())); + QSignalSpy connectedSpy(&socket, SIGNAL(connected())); + QSignalSpy disconnectedSpy(&socket, SIGNAL(disconnected())); + QSignalSpy stateChangedSpy(&socket, SIGNAL(stateChanged(QAbstractSocket::SocketState))); + QSignalSpy readChannelFinishedSpy(&socket, SIGNAL(readChannelFinished())); + QSignalSpy textFrameReceivedSpy(&socket, SIGNAL(textFrameReceived(QString,bool))); + QSignalSpy binaryFrameReceivedSpy(&socket, SIGNAL(binaryFrameReceived(QByteArray,bool))); + QSignalSpy textMessageReceivedSpy(&socket, SIGNAL(textMessageReceived(QString))); + QSignalSpy binaryMessageReceivedSpy(&socket, SIGNAL(binaryMessageReceived(QByteArray))); + QSignalSpy pongSpy(&socket, SIGNAL(pong(quint64,QByteArray))); + QSignalSpy bytesWrittenSpy(&socket, SIGNAL(bytesWritten(qint64))); + + socket.open(QUrl(url)); + + QVERIFY(socket.origin().isEmpty()); + QCOMPARE(socket.version(), QWebSocketProtocol::VersionLatest); + //at this point the socket is in a connecting state + //so, there should no error at this point + QCOMPARE(socket.error(), QAbstractSocket::UnknownSocketError); + QVERIFY(!socket.errorString().isEmpty()); + QVERIFY(!socket.isValid()); + QVERIFY(socket.localAddress().isNull()); + QCOMPARE(socket.localPort(), quint16(0)); + QCOMPARE(socket.pauseMode(), QAbstractSocket::PauseNever); + QVERIFY(socket.peerAddress().isNull()); + QCOMPARE(socket.peerPort(), quint16(0)); + QCOMPARE(socket.peerName(), expectedPeerName); + QCOMPARE(socket.state(), stateAfterOpenCall); + QCOMPARE(socket.readBufferSize(), 0); + QCOMPARE(socket.resourceName(), expectedResourceName); + QCOMPARE(socket.requestUrl().toString(), expectedUrl); + QCOMPARE(socket.closeCode(), QWebSocketProtocol::CloseCodeNormal); + QVERIFY(socket.closeReason().isEmpty()); + QCOMPARE(socket.sendTextMessage(QStringLiteral("A text message")), 0); + QCOMPARE(socket.sendBinaryMessage(QByteArrayLiteral("A text message")), 0); + + if (errorSpy.count() == 0) + QVERIFY(errorSpy.wait()); + QCOMPARE(errorSpy.count(), 1); + QList<QVariant> arguments = errorSpy.takeFirst(); + QAbstractSocket::SocketError socketError = + qvariant_cast<QAbstractSocket::SocketError>(arguments.at(0)); + QCOMPARE(socketError, QAbstractSocket::ConnectionRefusedError); + QCOMPARE(aboutToCloseSpy.count(), 0); + QCOMPARE(connectedSpy.count(), 0); + QCOMPARE(disconnectedSpy.count(), disconnectedCount); + QCOMPARE(stateChangedSpy.count(), stateChangedCount); + if (stateChangedCount == 2) { + arguments = stateChangedSpy.takeFirst(); + QAbstractSocket::SocketState socketState = + qvariant_cast<QAbstractSocket::SocketState>(arguments.at(0)); + arguments = stateChangedSpy.takeFirst(); + socketState = qvariant_cast<QAbstractSocket::SocketState>(arguments.at(0)); + QCOMPARE(socketState, QAbstractSocket::UnconnectedState); + } + QCOMPARE(readChannelFinishedSpy.count(), 0); + QCOMPARE(textFrameReceivedSpy.count(), 0); + QCOMPARE(binaryFrameReceivedSpy.count(), 0); + QCOMPARE(textMessageReceivedSpy.count(), 0); + QCOMPARE(binaryMessageReceivedSpy.count(), 0); + QCOMPARE(pongSpy.count(), 0); + QCOMPARE(bytesWrittenSpy.count(), 0); +} + +void tst_QWebSocket::tst_invalidOrigin() +{ + QWebSocket socket(QStringLiteral("My server\r\nin the wild.")); + + QSignalSpy errorSpy(&socket, SIGNAL(error(QAbstractSocket::SocketError))); + QSignalSpy aboutToCloseSpy(&socket, SIGNAL(aboutToClose())); + QSignalSpy connectedSpy(&socket, SIGNAL(connected())); + QSignalSpy disconnectedSpy(&socket, SIGNAL(disconnected())); + QSignalSpy stateChangedSpy(&socket, SIGNAL(stateChanged(QAbstractSocket::SocketState))); + QSignalSpy readChannelFinishedSpy(&socket, SIGNAL(readChannelFinished())); + QSignalSpy textFrameReceivedSpy(&socket, SIGNAL(textFrameReceived(QString,bool))); + QSignalSpy binaryFrameReceivedSpy(&socket, SIGNAL(binaryFrameReceived(QByteArray,bool))); + QSignalSpy textMessageReceivedSpy(&socket, SIGNAL(textMessageReceived(QString))); + QSignalSpy binaryMessageReceivedSpy(&socket, SIGNAL(binaryMessageReceived(QByteArray))); + QSignalSpy pongSpy(&socket, SIGNAL(pong(quint64,QByteArray))); + QSignalSpy bytesWrittenSpy(&socket, SIGNAL(bytesWritten(qint64))); + + socket.open(QUrl(QStringLiteral("ws://127.0.0.1:1/"))); + + //at this point the socket is in a connecting state + //so, there should no error at this point + QCOMPARE(socket.error(), QAbstractSocket::UnknownSocketError); + QVERIFY(!socket.errorString().isEmpty()); + QVERIFY(!socket.isValid()); + QVERIFY(socket.localAddress().isNull()); + QCOMPARE(socket.localPort(), quint16(0)); + QCOMPARE(socket.pauseMode(), QAbstractSocket::PauseNever); + QVERIFY(socket.peerAddress().isNull()); + QCOMPARE(socket.peerPort(), quint16(0)); + QCOMPARE(socket.peerName(), QStringLiteral("127.0.0.1")); + QCOMPARE(socket.state(), QAbstractSocket::ConnectingState); + QCOMPARE(socket.readBufferSize(), 0); + QCOMPARE(socket.resourceName(), QStringLiteral("/")); + QCOMPARE(socket.requestUrl(), QUrl(QStringLiteral("ws://127.0.0.1:1/"))); + QCOMPARE(socket.closeCode(), QWebSocketProtocol::CloseCodeNormal); + + QVERIFY(errorSpy.wait()); + + QCOMPARE(errorSpy.count(), 1); + QList<QVariant> arguments = errorSpy.takeFirst(); + QAbstractSocket::SocketError socketError = + qvariant_cast<QAbstractSocket::SocketError>(arguments.at(0)); + QCOMPARE(socketError, QAbstractSocket::ConnectionRefusedError); + QCOMPARE(aboutToCloseSpy.count(), 0); + QCOMPARE(connectedSpy.count(), 0); + QCOMPARE(disconnectedSpy.count(), 1); + QCOMPARE(stateChangedSpy.count(), 2); //connectingstate, unconnectedstate + arguments = stateChangedSpy.takeFirst(); + QAbstractSocket::SocketState socketState = + qvariant_cast<QAbstractSocket::SocketState>(arguments.at(0)); + arguments = stateChangedSpy.takeFirst(); + socketState = qvariant_cast<QAbstractSocket::SocketState>(arguments.at(0)); + QCOMPARE(socketState, QAbstractSocket::UnconnectedState); + QCOMPARE(readChannelFinishedSpy.count(), 0); + QCOMPARE(textFrameReceivedSpy.count(), 0); + QCOMPARE(binaryFrameReceivedSpy.count(), 0); + QCOMPARE(textMessageReceivedSpy.count(), 0); + QCOMPARE(binaryMessageReceivedSpy.count(), 0); + QCOMPARE(pongSpy.count(), 0); + QCOMPARE(bytesWrittenSpy.count(), 0); +} + +void tst_QWebSocket::tst_sendTextMessage() +{ + EchoServer echoServer; + + QWebSocket socket; + + //should return 0 because socket is not open yet + QCOMPARE(socket.sendTextMessage(QStringLiteral("1234")), 0); + + QSignalSpy socketConnectedSpy(&socket, SIGNAL(connected())); + QSignalSpy serverConnectedSpy(&echoServer, SIGNAL(newConnection(QUrl))); + QSignalSpy textMessageReceived(&socket, SIGNAL(textMessageReceived(QString))); + QSignalSpy textFrameReceived(&socket, SIGNAL(textFrameReceived(QString,bool))); + QSignalSpy binaryMessageReceived(&socket, SIGNAL(binaryMessageReceived(QByteArray))); + QSignalSpy binaryFrameReceived(&socket, SIGNAL(binaryFrameReceived(QByteArray,bool))); + QSignalSpy socketError(&socket, SIGNAL(error(QAbstractSocket::SocketError))); + + QUrl url = QUrl(QStringLiteral("ws://") + echoServer.hostAddress().toString() + + QStringLiteral(":") + QString::number(echoServer.port())); + url.setPath("/segment/with spaces"); + url.addQueryItem("queryitem", "with encoded characters"); + + socket.open(url); + + QTRY_COMPARE(socketConnectedSpy.count(), 1); + QCOMPARE(socketError.count(), 0); + QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); + QList<QVariant> arguments = serverConnectedSpy.takeFirst(); + QUrl urlConnected = arguments.at(0).toUrl(); + QCOMPARE(urlConnected, url); + + socket.sendTextMessage(QStringLiteral("Hello world!")); + + QVERIFY(textMessageReceived.wait(500)); + QCOMPARE(textMessageReceived.count(), 1); + QCOMPARE(binaryMessageReceived.count(), 0); + QCOMPARE(binaryFrameReceived.count(), 0); + arguments = textMessageReceived.takeFirst(); + QString messageReceived = arguments.at(0).toString(); + QCOMPARE(messageReceived, QStringLiteral("Hello world!")); + + QCOMPARE(textFrameReceived.count(), 1); + arguments = textFrameReceived.takeFirst(); + QString frameReceived = arguments.at(0).toString(); + bool isLastFrame = arguments.at(1).toBool(); + QCOMPARE(frameReceived, QStringLiteral("Hello world!")); + QVERIFY(isLastFrame); + + socket.close(); + + //QTBUG-36762: QWebSocket emits multiplied signals when socket was reopened + socketConnectedSpy.clear(); + textMessageReceived.clear(); + textFrameReceived.clear(); + + socket.open(QUrl(QStringLiteral("ws://") + echoServer.hostAddress().toString() + + QStringLiteral(":") + QString::number(echoServer.port()))); + + QTRY_COMPARE(socketConnectedSpy.count(), 1); + QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); + + socket.sendTextMessage(QStringLiteral("Hello world!")); + + QVERIFY(textMessageReceived.wait(500)); + QCOMPARE(textMessageReceived.count(), 1); + QCOMPARE(binaryMessageReceived.count(), 0); + QCOMPARE(binaryFrameReceived.count(), 0); + arguments = textMessageReceived.takeFirst(); + messageReceived = arguments.at(0).toString(); + QCOMPARE(messageReceived, QStringLiteral("Hello world!")); + + QCOMPARE(textFrameReceived.count(), 1); + arguments = textFrameReceived.takeFirst(); + frameReceived = arguments.at(0).toString(); + isLastFrame = arguments.at(1).toBool(); + QCOMPARE(frameReceived, QStringLiteral("Hello world!")); + QVERIFY(isLastFrame); + + QString reason = QStringLiteral("going away"); + socket.close(QWebSocketProtocol::CloseCodeGoingAway, reason); + QCOMPARE(socket.closeCode(), QWebSocketProtocol::CloseCodeGoingAway); + QCOMPARE(socket.closeReason(), reason); +} + +void tst_QWebSocket::tst_sendBinaryMessage() +{ + EchoServer echoServer; + + QWebSocket socket; + + //should return 0 because socket is not open yet + QCOMPARE(socket.sendBinaryMessage(QByteArrayLiteral("1234")), 0); + + QSignalSpy socketConnectedSpy(&socket, SIGNAL(connected())); + QSignalSpy textMessageReceived(&socket, SIGNAL(textMessageReceived(QString))); + QSignalSpy textFrameReceived(&socket, SIGNAL(textFrameReceived(QString,bool))); + QSignalSpy binaryMessageReceived(&socket, SIGNAL(binaryMessageReceived(QByteArray))); + QSignalSpy binaryFrameReceived(&socket, SIGNAL(binaryFrameReceived(QByteArray,bool))); + + socket.open(QUrl(QStringLiteral("ws://") + echoServer.hostAddress().toString() + + QStringLiteral(":") + QString::number(echoServer.port()))); + + QTRY_COMPARE(socketConnectedSpy.count(), 1); + QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); + + socket.sendBinaryMessage(QByteArrayLiteral("Hello world!")); + + QVERIFY(binaryMessageReceived.wait(500)); + QCOMPARE(textMessageReceived.count(), 0); + QCOMPARE(textFrameReceived.count(), 0); + QCOMPARE(binaryMessageReceived.count(), 1); + QList<QVariant> arguments = binaryMessageReceived.takeFirst(); + QByteArray messageReceived = arguments.at(0).toByteArray(); + QCOMPARE(messageReceived, QByteArrayLiteral("Hello world!")); + + QCOMPARE(binaryFrameReceived.count(), 1); + arguments = binaryFrameReceived.takeFirst(); + QByteArray frameReceived = arguments.at(0).toByteArray(); + bool isLastFrame = arguments.at(1).toBool(); + QCOMPARE(frameReceived, QByteArrayLiteral("Hello world!")); + QVERIFY(isLastFrame); + + socket.close(); + + //QTBUG-36762: QWebSocket emits multiple signals when socket is reopened + socketConnectedSpy.clear(); + binaryMessageReceived.clear(); + binaryFrameReceived.clear(); + + socket.open(QUrl(QStringLiteral("ws://") + echoServer.hostAddress().toString() + + QStringLiteral(":") + QString::number(echoServer.port()))); + + QTRY_COMPARE(socketConnectedSpy.count(), 1); + QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); + + socket.sendBinaryMessage(QByteArrayLiteral("Hello world!")); + + QVERIFY(binaryMessageReceived.wait(500)); + QCOMPARE(textMessageReceived.count(), 0); + QCOMPARE(textFrameReceived.count(), 0); + QCOMPARE(binaryMessageReceived.count(), 1); + arguments = binaryMessageReceived.takeFirst(); + messageReceived = arguments.at(0).toByteArray(); + QCOMPARE(messageReceived, QByteArrayLiteral("Hello world!")); + + QCOMPARE(binaryFrameReceived.count(), 1); + arguments = binaryFrameReceived.takeFirst(); + frameReceived = arguments.at(0).toByteArray(); + isLastFrame = arguments.at(1).toBool(); + QCOMPARE(frameReceived, QByteArrayLiteral("Hello world!")); + QVERIFY(isLastFrame); +} + +void tst_QWebSocket::tst_errorString() +{ + //Check for QTBUG-37228: QWebSocket returns "Unknown Error" for known errors + QWebSocket socket; + + //check that the default error string is empty + QVERIFY(socket.errorString().isEmpty()); + + QSignalSpy errorSpy(&socket, SIGNAL(error(QAbstractSocket::SocketError))); + + socket.open(QUrl(QStringLiteral("ws://someserver.on.mars:9999"))); + + QTRY_COMPARE(errorSpy.count(), 1); + QList<QVariant> arguments = errorSpy.takeFirst(); + QAbstractSocket::SocketError socketError = + qvariant_cast<QAbstractSocket::SocketError>(arguments.at(0)); + QCOMPARE(socketError, QAbstractSocket::HostNotFoundError); + QCOMPARE(socket.errorString(), QStringLiteral("Host not found")); +} + +void tst_QWebSocket::tst_openRequest() +{ + EchoServer echoServer; + + QWebSocket socket; + + QSignalSpy socketConnectedSpy(&socket, SIGNAL(connected())); + QSignalSpy serverRequestSpy(&echoServer, SIGNAL(newConnection(QNetworkRequest))); + + QUrl url = QUrl(QStringLiteral("ws://") + echoServer.hostAddress().toString() + + QLatin1Char(':') + QString::number(echoServer.port())); + url.addQueryItem("queryitem", "with encoded characters"); + QNetworkRequest req(url); + req.setRawHeader("X-Custom-Header", "A custom header"); + socket.open(req); + + QTRY_COMPARE(socketConnectedSpy.count(), 1); + QTRY_COMPARE(serverRequestSpy.count(), 1); + QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); + QList<QVariant> arguments = serverRequestSpy.takeFirst(); + QNetworkRequest requestConnected = arguments.at(0).value<QNetworkRequest>(); + QCOMPARE(requestConnected.url(), req.url()); + QCOMPARE(requestConnected.rawHeader("X-Custom-Header"), req.rawHeader("X-Custom-Header")); + socket.close(); +} + +#ifndef QT_NO_NETWORKPROXY +void tst_QWebSocket::tst_setProxy() +{ + // check if property assignment works as expected. + QWebSocket socket; + QCOMPARE(socket.proxy(), QNetworkProxy(QNetworkProxy::DefaultProxy)); + + QNetworkProxy proxy; + proxy.setPort(123); + socket.setProxy(proxy); + QCOMPARE(socket.proxy(), proxy); + + proxy.setPort(321); + QCOMPARE(socket.proxy().port(), quint16(123)); + socket.setProxy(proxy); + QCOMPARE(socket.proxy(), proxy); +} +#endif // QT_NO_NETWORKPROXY + +QTEST_MAIN(tst_QWebSocket) + +#include "tst_qwebsocket.moc" diff --git a/tests/auto/websockets/qwebsocketcorsauthenticator/qwebsocketcorsauthenticator.pro b/tests/auto/websockets/qwebsocketcorsauthenticator/qwebsocketcorsauthenticator.pro new file mode 100644 index 0000000..aa485fc --- /dev/null +++ b/tests/auto/websockets/qwebsocketcorsauthenticator/qwebsocketcorsauthenticator.pro @@ -0,0 +1,13 @@ +CONFIG += console +CONFIG += testcase +CONFIG -= app_bundle + +TEMPLATE = app + +TARGET = tst_qwebsocketcorsauthenticator + +QT = core testlib websockets + +SOURCES += tst_qwebsocketcorsauthenticator.cpp + +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 diff --git a/tests/auto/websockets/qwebsocketcorsauthenticator/tst_qwebsocketcorsauthenticator.cpp b/tests/auto/websockets/qwebsocketcorsauthenticator/tst_qwebsocketcorsauthenticator.cpp new file mode 100644 index 0000000..225adc8 --- /dev/null +++ b/tests/auto/websockets/qwebsocketcorsauthenticator/tst_qwebsocketcorsauthenticator.cpp @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Kurt Pattyn <pattyn.kurt@gmail.com>. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <QtTest/QtTest> +#include <QtTest/qtestcase.h> +#include <QtCore/QDebug> +#include <QtCore/QByteArray> +#include <QtCore/QtEndian> + +#include "QtWebSockets/qwebsocketcorsauthenticator.h" + +QT_USE_NAMESPACE + +class tst_QWebSocketCorsAuthenticator : public QObject +{ + Q_OBJECT + +public: + tst_QWebSocketCorsAuthenticator(); + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void tst_initialization(); +}; + +tst_QWebSocketCorsAuthenticator::tst_QWebSocketCorsAuthenticator() +{} + +void tst_QWebSocketCorsAuthenticator::initTestCase() +{ +} + +void tst_QWebSocketCorsAuthenticator::cleanupTestCase() +{} + +void tst_QWebSocketCorsAuthenticator::init() +{ +} + +void tst_QWebSocketCorsAuthenticator::cleanup() +{ +} + +void tst_QWebSocketCorsAuthenticator::tst_initialization() +{ + { + QWebSocketCorsAuthenticator authenticator((QString())); + + QCOMPARE(authenticator.allowed(), true); + QCOMPARE(authenticator.origin(), QString()); + } + { + QWebSocketCorsAuthenticator authenticator(QStringLiteral("com.somesite")); + + QCOMPARE(authenticator.allowed(), true); + QCOMPARE(authenticator.origin(), QStringLiteral("com.somesite")); + + QWebSocketCorsAuthenticator other(authenticator); + QCOMPARE(other.origin(), authenticator.origin()); + QCOMPARE(other.allowed(), authenticator.allowed()); + + authenticator.setAllowed(false); + QVERIFY(!authenticator.allowed()); + QCOMPARE(other.allowed(), true); //make sure other is a real copy + + authenticator.setAllowed(true); + QVERIFY(authenticator.allowed()); + + authenticator.setAllowed(false); + other = authenticator; + QCOMPARE(other.origin(), authenticator.origin()); + QCOMPARE(other.allowed(), authenticator.allowed()); + } +} + +QTEST_MAIN(tst_QWebSocketCorsAuthenticator) + +#include "tst_qwebsocketcorsauthenticator.moc" + diff --git a/tests/auto/websockets/qwebsocketserver/qwebsocketserver.pro b/tests/auto/websockets/qwebsocketserver/qwebsocketserver.pro new file mode 100644 index 0000000..25216d3 --- /dev/null +++ b/tests/auto/websockets/qwebsocketserver/qwebsocketserver.pro @@ -0,0 +1,13 @@ +CONFIG += console +CONFIG += testcase +CONFIG -= app_bundle + +QT = core testlib websockets + +TARGET = tst_qwebsocketserver + +TEMPLATE = app + +SOURCES += tst_qwebsocketserver.cpp + +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 diff --git a/tests/auto/websockets/qwebsocketserver/tst_qwebsocketserver.cpp b/tests/auto/websockets/qwebsocketserver/tst_qwebsocketserver.cpp new file mode 100644 index 0000000..c2ba842 --- /dev/null +++ b/tests/auto/websockets/qwebsocketserver/tst_qwebsocketserver.cpp @@ -0,0 +1,390 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Kurt Pattyn <pattyn.kurt@gmail.com>. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <QString> +#include <QtTest> +#include <QNetworkProxy> +#include <QtWebSockets/QWebSocketServer> +#include <QtWebSockets/QWebSocket> +#include <QtWebSockets/QWebSocketCorsAuthenticator> +#include <QtWebSockets/qwebsocketprotocol.h> + +QT_USE_NAMESPACE + +Q_DECLARE_METATYPE(QWebSocketProtocol::Version) +Q_DECLARE_METATYPE(QWebSocketProtocol::CloseCode) +Q_DECLARE_METATYPE(QWebSocketServer::SslMode) +Q_DECLARE_METATYPE(QWebSocketCorsAuthenticator *) +#ifndef QT_NO_SSL +Q_DECLARE_METATYPE(QSslError) +#endif + +class tst_QWebSocketServer : public QObject +{ + Q_OBJECT + +public: + tst_QWebSocketServer(); + +private Q_SLOTS: + void init(); + void initTestCase(); + void cleanupTestCase(); + void tst_initialisation(); + void tst_settersAndGetters(); + void tst_listening(); + void tst_connectivity(); + void tst_maxPendingConnections(); + void tst_serverDestroyedWhileSocketConnected(); +}; + +tst_QWebSocketServer::tst_QWebSocketServer() +{ +} + +void tst_QWebSocketServer::init() +{ + qRegisterMetaType<QWebSocketProtocol::Version>("QWebSocketProtocol::Version"); + qRegisterMetaType<QWebSocketProtocol::CloseCode>("QWebSocketProtocol::CloseCode"); + qRegisterMetaType<QWebSocketServer::SslMode>("QWebSocketServer::SslMode"); + qRegisterMetaType<QWebSocketCorsAuthenticator *>("QWebSocketCorsAuthenticator *"); +#ifndef QT_NO_SSL + qRegisterMetaType<QSslError>("QSslError"); +#endif +} + +void tst_QWebSocketServer::initTestCase() +{ +} + +void tst_QWebSocketServer::cleanupTestCase() +{ +} + +void tst_QWebSocketServer::tst_initialisation() +{ + { + QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode); + + QVERIFY(server.serverName().isEmpty()); + QCOMPARE(server.secureMode(), QWebSocketServer::NonSecureMode); + QVERIFY(!server.isListening()); + QCOMPARE(server.maxPendingConnections(), 30); + QCOMPARE(server.serverPort(), quint16(0)); + QCOMPARE(server.serverAddress(), QHostAddress()); + QCOMPARE(server.socketDescriptor(), -1); + QVERIFY(!server.hasPendingConnections()); + QVERIFY(!server.nextPendingConnection()); + QCOMPARE(server.error(), QWebSocketProtocol::CloseCodeNormal); + QVERIFY(server.errorString().isEmpty()); + #ifndef QT_NO_NETWORKPROXY + QCOMPARE(server.proxy().type(), QNetworkProxy::DefaultProxy); + #endif + #ifndef QT_NO_SSL + QCOMPARE(server.sslConfiguration(), QSslConfiguration::defaultConfiguration()); + #endif + QCOMPARE(server.supportedVersions().count(), 1); + QCOMPARE(server.supportedVersions().at(0), QWebSocketProtocol::VersionLatest); + QCOMPARE(server.supportedVersions().at(0), QWebSocketProtocol::Version13); + + server.close(); + //closing a server should not affect any of the parameters + //certainly if the server was not opened before + + QVERIFY(server.serverName().isEmpty()); + QCOMPARE(server.secureMode(), QWebSocketServer::NonSecureMode); + QVERIFY(!server.isListening()); + QCOMPARE(server.maxPendingConnections(), 30); + QCOMPARE(server.serverPort(), quint16(0)); + QCOMPARE(server.serverAddress(), QHostAddress()); + QCOMPARE(server.socketDescriptor(), -1); + QVERIFY(!server.hasPendingConnections()); + QVERIFY(!server.nextPendingConnection()); + QCOMPARE(server.error(), QWebSocketProtocol::CloseCodeNormal); + QVERIFY(server.errorString().isEmpty()); + #ifndef QT_NO_NETWORKPROXY + QCOMPARE(server.proxy().type(), QNetworkProxy::DefaultProxy); + #endif + #ifndef QT_NO_SSL + QCOMPARE(server.sslConfiguration(), QSslConfiguration::defaultConfiguration()); + #endif + QCOMPARE(server.supportedVersions().count(), 1); + QCOMPARE(server.supportedVersions().at(0), QWebSocketProtocol::VersionLatest); + QCOMPARE(server.supportedVersions().at(0), QWebSocketProtocol::Version13); + QCOMPARE(server.serverUrl(), QUrl()); + } + + { +#ifndef QT_NO_SSL + QWebSocketServer sslServer(QString(), QWebSocketServer::SecureMode); + QCOMPARE(sslServer.secureMode(), QWebSocketServer::SecureMode); +#endif + } +} + +void tst_QWebSocketServer::tst_settersAndGetters() +{ + QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode); + + server.setMaxPendingConnections(23); + QCOMPARE(server.maxPendingConnections(), 23); + server.setMaxPendingConnections(INT_MIN); + QCOMPARE(server.maxPendingConnections(), INT_MIN); + server.setMaxPendingConnections(INT_MAX); + QCOMPARE(server.maxPendingConnections(), INT_MAX); + + QVERIFY(!server.setSocketDescriptor(-2)); + QCOMPARE(server.socketDescriptor(), -1); + + server.setServerName(QStringLiteral("Qt WebSocketServer")); + QCOMPARE(server.serverName(), QStringLiteral("Qt WebSocketServer")); + +#ifndef QT_NO_NETWORKPROXY + QNetworkProxy proxy(QNetworkProxy::Socks5Proxy); + server.setProxy(proxy); + QCOMPARE(server.proxy(), proxy); +#endif +#ifndef QT_NO_SSL + //cannot set an ssl configuration on a non secure server + QSslConfiguration sslConfiguration = QSslConfiguration::defaultConfiguration(); + sslConfiguration.setPeerVerifyDepth(sslConfiguration.peerVerifyDepth() + 1); + server.setSslConfiguration(sslConfiguration); + QVERIFY(server.sslConfiguration() != sslConfiguration); + QCOMPARE(server.sslConfiguration(), QSslConfiguration::defaultConfiguration()); + + QWebSocketServer sslServer(QString(), QWebSocketServer::SecureMode); + sslServer.setSslConfiguration(sslConfiguration); + QCOMPARE(sslServer.sslConfiguration(), sslConfiguration); + QVERIFY(sslServer.sslConfiguration() != QSslConfiguration::defaultConfiguration()); +#endif +} + +void tst_QWebSocketServer::tst_listening() +{ + //These listening tests are not too extensive, as the implementation of QWebSocketServer + //relies on QTcpServer + + QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode); + + QSignalSpy serverAcceptErrorSpy(&server, SIGNAL(acceptError(QAbstractSocket::SocketError))); + QSignalSpy serverConnectionSpy(&server, SIGNAL(newConnection())); + QSignalSpy serverErrorSpy(&server, + SIGNAL(serverError(QWebSocketProtocol::CloseCode))); + QSignalSpy corsAuthenticationSpy(&server, + SIGNAL(originAuthenticationRequired(QWebSocketCorsAuthenticator*))); + QSignalSpy serverClosedSpy(&server, SIGNAL(closed())); +#ifndef QT_NO_SSL + QSignalSpy peerVerifyErrorSpy(&server, SIGNAL(peerVerifyError(QSslError))); + QSignalSpy sslErrorsSpy(&server, SIGNAL(sslErrors(QList<QSslError>))); +#endif + + QVERIFY(server.listen()); //listen on all network interface, choose an appropriate port + QVERIFY(server.isListening()); + QCOMPARE(serverClosedSpy.count(), 0); + server.close(); + QVERIFY(serverClosedSpy.wait(1000)); + QVERIFY(!server.isListening()); + QCOMPARE(serverErrorSpy.count(), 0); + + QVERIFY(!server.listen(QHostAddress(QStringLiteral("1.2.3.4")), 0)); + QCOMPARE(server.error(), QWebSocketProtocol::CloseCodeAbnormalDisconnection); + QCOMPARE(server.errorString().toLatin1().constData(), "The address is not available"); + QVERIFY(!server.isListening()); + + QCOMPARE(serverAcceptErrorSpy.count(), 0); + QCOMPARE(serverConnectionSpy.count(), 0); + QCOMPARE(corsAuthenticationSpy.count(), 0); +#ifndef QT_NO_SSL + QCOMPARE(peerVerifyErrorSpy.count(), 0); + QCOMPARE(sslErrorsSpy.count(), 0); +#endif + QCOMPARE(serverErrorSpy.count(), 1); + QCOMPARE(serverClosedSpy.count(), 1); +} + +void tst_QWebSocketServer::tst_connectivity() +{ + QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode); + QSignalSpy serverConnectionSpy(&server, SIGNAL(newConnection())); + QSignalSpy serverErrorSpy(&server, + SIGNAL(serverError(QWebSocketProtocol::CloseCode))); + QSignalSpy corsAuthenticationSpy(&server, + SIGNAL(originAuthenticationRequired(QWebSocketCorsAuthenticator*))); + QSignalSpy serverClosedSpy(&server, SIGNAL(closed())); +#ifndef QT_NO_SSL + QSignalSpy peerVerifyErrorSpy(&server, SIGNAL(peerVerifyError(QSslError))); + QSignalSpy sslErrorsSpy(&server, SIGNAL(sslErrors(QList<QSslError>))); +#endif + QWebSocket socket; + QSignalSpy socketConnectedSpy(&socket, SIGNAL(connected())); + + QVERIFY(server.listen()); + QCOMPARE(server.serverAddress(), QHostAddress(QHostAddress::Any)); + QCOMPARE(server.serverUrl(), QUrl(QStringLiteral("ws://") + QHostAddress(QHostAddress::LocalHost).toString() + + QStringLiteral(":").append(QString::number(server.serverPort())))); + + socket.open(server.serverUrl().toString()); + + if (socketConnectedSpy.count() == 0) + QVERIFY(socketConnectedSpy.wait()); + QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); + QCOMPARE(serverConnectionSpy.count(), 1); + QCOMPARE(corsAuthenticationSpy.count(), 1); + + QCOMPARE(serverClosedSpy.count(), 0); + + server.close(); + + QVERIFY(serverClosedSpy.wait()); + QCOMPARE(serverClosedSpy.count(), 1); +#ifndef QT_NO_SSL + QCOMPARE(peerVerifyErrorSpy.count(), 0); + QCOMPARE(sslErrorsSpy.count(), 0); +#endif + QCOMPARE(serverErrorSpy.count(), 0); +} + +void tst_QWebSocketServer::tst_maxPendingConnections() +{ + //tests if maximum connections are respected + //also checks if there are no side-effects like signals that are unexpectedly thrown + QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode); + server.setMaxPendingConnections(2); + QSignalSpy serverConnectionSpy(&server, SIGNAL(newConnection())); + QSignalSpy serverErrorSpy(&server, + SIGNAL(serverError(QWebSocketProtocol::CloseCode))); + QSignalSpy corsAuthenticationSpy(&server, + SIGNAL(originAuthenticationRequired(QWebSocketCorsAuthenticator*))); + QSignalSpy serverClosedSpy(&server, SIGNAL(closed())); +#ifndef QT_NO_SSL + QSignalSpy peerVerifyErrorSpy(&server, SIGNAL(peerVerifyError(QSslError))); + QSignalSpy sslErrorsSpy(&server, SIGNAL(sslErrors(QList<QSslError>))); +#endif + QSignalSpy serverAcceptErrorSpy(&server, SIGNAL(acceptError(QAbstractSocket::SocketError))); + + QWebSocket socket1; + QWebSocket socket2; + QWebSocket socket3; + + QSignalSpy socket1ConnectedSpy(&socket1, SIGNAL(connected())); + QSignalSpy socket2ConnectedSpy(&socket2, SIGNAL(connected())); + QSignalSpy socket3ConnectedSpy(&socket3, SIGNAL(connected())); + + QVERIFY(server.listen()); + + socket1.open(server.serverUrl().toString()); + + if (socket1ConnectedSpy.count() == 0) + QVERIFY(socket1ConnectedSpy.wait()); + QCOMPARE(socket1.state(), QAbstractSocket::ConnectedState); + QCOMPARE(serverConnectionSpy.count(), 1); + QCOMPARE(corsAuthenticationSpy.count(), 1); + socket2.open(server.serverUrl().toString()); + if (socket2ConnectedSpy.count() == 0) + QVERIFY(socket2ConnectedSpy.wait()); + QCOMPARE(socket2.state(), QAbstractSocket::ConnectedState); + QCOMPARE(serverConnectionSpy.count(), 2); + QCOMPARE(corsAuthenticationSpy.count(), 2); + socket3.open(server.serverUrl().toString()); + if (socket3ConnectedSpy.count() == 0) + QVERIFY(!socket3ConnectedSpy.wait(250)); + QCOMPARE(socket3.state(), QAbstractSocket::UnconnectedState); + QCOMPARE(serverConnectionSpy.count(), 2); + QCOMPARE(corsAuthenticationSpy.count(), 2); + + QVERIFY(server.hasPendingConnections()); + QWebSocket *pSocket = server.nextPendingConnection(); + QVERIFY(pSocket); + delete pSocket; + QVERIFY(server.hasPendingConnections()); + pSocket = server.nextPendingConnection(); + QVERIFY(pSocket); + delete pSocket; + QVERIFY(!server.hasPendingConnections()); + QVERIFY(!server.nextPendingConnection()); + +//will resolve in another commit +#ifndef Q_OS_WIN + QCOMPARE(serverErrorSpy.count(), 1); + QCOMPARE(serverErrorSpy.at(0).at(0).value<QWebSocketProtocol::CloseCode>(), + QWebSocketProtocol::CloseCodeAbnormalDisconnection); +#endif + QCOMPARE(serverClosedSpy.count(), 0); + + server.close(); + + QVERIFY(serverClosedSpy.wait()); + QCOMPARE(serverClosedSpy.count(), 1); +#ifndef QT_NO_SSL + QCOMPARE(peerVerifyErrorSpy.count(), 0); + QCOMPARE(sslErrorsSpy.count(), 0); +#endif + QCOMPARE(serverAcceptErrorSpy.count(), 0); +} + +void tst_QWebSocketServer::tst_serverDestroyedWhileSocketConnected() +{ + QWebSocketServer * server = new QWebSocketServer(QString(), QWebSocketServer::NonSecureMode); + QSignalSpy serverConnectionSpy(server, SIGNAL(newConnection())); + QSignalSpy corsAuthenticationSpy(server, + SIGNAL(originAuthenticationRequired(QWebSocketCorsAuthenticator*))); + QSignalSpy serverClosedSpy(server, SIGNAL(closed())); + + QWebSocket socket; + QSignalSpy socketConnectedSpy(&socket, SIGNAL(connected())); + QSignalSpy socketDisconnectedSpy(&socket, SIGNAL(disconnected())); + + QVERIFY(server->listen()); + QCOMPARE(server->serverAddress(), QHostAddress(QHostAddress::Any)); + QCOMPARE(server->serverUrl(), QUrl(QStringLiteral("ws://") + QHostAddress(QHostAddress::LocalHost).toString() + + QStringLiteral(":").append(QString::number(server->serverPort())))); + + socket.open(server->serverUrl().toString()); + + if (socketConnectedSpy.count() == 0) + QVERIFY(socketConnectedSpy.wait()); + QCOMPARE(socket.state(), QAbstractSocket::ConnectedState); + QCOMPARE(serverConnectionSpy.count(), 1); + QCOMPARE(corsAuthenticationSpy.count(), 1); + + QCOMPARE(serverClosedSpy.count(), 0); + + delete server; + + if (socketDisconnectedSpy.count() == 0) + QVERIFY(socketDisconnectedSpy.wait()); + QCOMPARE(socketDisconnectedSpy.count(), 1); +} + +QTEST_MAIN(tst_QWebSocketServer) + +#include "tst_qwebsocketserver.moc" diff --git a/tests/auto/websockets/websocketframe/tst_websocketframe.cpp b/tests/auto/websockets/websocketframe/tst_websocketframe.cpp new file mode 100644 index 0000000..5db82d5 --- /dev/null +++ b/tests/auto/websockets/websocketframe/tst_websocketframe.cpp @@ -0,0 +1,624 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Kurt Pattyn <pattyn.kurt@gmail.com>. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <QtTest/QtTest> +#include <QtTest/qtestcase.h> +#include <QDebug> +#include <QByteArray> +#include <QtEndian> + +#include "private/qwebsocketframe_p.h" +#include "private/qwebsocketprotocol_p.h" +#include "qwebsocketprotocol.h" + +QT_USE_NAMESPACE + +Q_DECLARE_METATYPE(QWebSocketProtocol::CloseCode) +Q_DECLARE_METATYPE(QWebSocketProtocol::OpCode) + +/*! + * \brief class FrameHelper is used to encode a single frame. + * + * \internal + */ +class FrameHelper +{ +public: + FrameHelper(); + + QByteArray wireRepresentation(); + + void setRsv1(int value) { m_rsv1 = value; } + void setRsv2(int value) { m_rsv2 = value; } + void setRsv3(int value) { m_rsv3 = value; } + void setMask(quint32 mask) { m_mask = mask; } + void setOpCode(QWebSocketProtocol::OpCode opCode) { m_opCode = opCode; } + void setPayload(const QByteArray &payload) { m_payload = payload; } + void setFinalFrame(bool isFinal) { m_isFinalFrame = isFinal; } + +private: + int m_rsv1; + int m_rsv2; + int m_rsv3; + quint32 m_mask; + QWebSocketProtocol::OpCode m_opCode; + QByteArray m_payload; + bool m_isFinalFrame; +}; + +FrameHelper::FrameHelper() : + m_rsv1(0), m_rsv2(0), m_rsv3(0), + m_mask(0), m_opCode(QWebSocketProtocol::OpCodeReserved3), + m_payload(), m_isFinalFrame(false) +{} + +QByteArray FrameHelper::wireRepresentation() +{ + quint8 byte = 0x00; + QByteArray wireRep; + quint64 payloadLength = m_payload.length(); + + //FIN, opcode + byte = static_cast<quint8>((m_opCode & 0x0F) | (m_isFinalFrame ? 0x80 : 0x00)); //FIN, opcode + //RSV1-3 + byte |= static_cast<quint8>(((m_rsv1 & 0x01) << 6) | ((m_rsv2 & 0x01) << 5) | + ((m_rsv3 & 0x01) << 4)); + wireRep.append(static_cast<char>(byte)); + + byte = 0x00; + if (m_mask != 0) + { + byte |= 0x80; + } + if (payloadLength <= 125) + { + byte |= static_cast<quint8>(payloadLength); + wireRep.append(static_cast<char>(byte)); + } + else if (payloadLength <= 0xFFFFU) + { + byte |= 126; + wireRep.append(static_cast<char>(byte)); + quint16 swapped = qToBigEndian<quint16>(static_cast<quint16>(payloadLength)); + wireRep.append(static_cast<const char *>(static_cast<const void *>(&swapped)), 2); + } + else + { + byte |= 127; + wireRep.append(static_cast<char>(byte)); + quint64 swapped = qToBigEndian<quint64>(payloadLength); + wireRep.append(static_cast<const char *>(static_cast<const void *>(&swapped)), 8); + } + //Write mask + if (m_mask != 0) + { + wireRep.append(static_cast<const char *>(static_cast<const void *>(&m_mask)), + sizeof(quint32)); + } + QByteArray tmpData = m_payload; + if (m_mask) + { + tmpData.detach(); + QWebSocketProtocol::mask(&tmpData, m_mask); + } + wireRep.append(tmpData); + return wireRep; +} + +class tst_WebSocketFrame : public QObject +{ + Q_OBJECT + +public: + tst_WebSocketFrame(); + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void tst_initialization(); + void tst_copyConstructorAndAssignment(); + + void tst_goodFrames_data(); + void tst_goodFrames(); + + void tst_invalidFrames_data(); + void tst_invalidFrames(); + + void tst_malformedFrames_data(); + void tst_malformedFrames(); +}; + +tst_WebSocketFrame::tst_WebSocketFrame() +{} + +void tst_WebSocketFrame::initTestCase() +{ +} + +void tst_WebSocketFrame::cleanupTestCase() +{} + +void tst_WebSocketFrame::init() +{ + qRegisterMetaType<QWebSocketProtocol::OpCode>("QWebSocketProtocol::OpCode"); + qRegisterMetaType<QWebSocketProtocol::CloseCode>("QWebSocketProtocol::CloseCode"); +} + +void tst_WebSocketFrame::cleanup() +{ +} + +void tst_WebSocketFrame::tst_initialization() +{ + QWebSocketFrame frame; + QVERIFY(!frame.isValid()); + QCOMPARE(frame.payload().length(), 0); +} + +void tst_WebSocketFrame::tst_copyConstructorAndAssignment() +{ + FrameHelper frameHelper; + frameHelper.setRsv1(0); + frameHelper.setRsv2(0); + frameHelper.setRsv3(0); + frameHelper.setFinalFrame(true); + frameHelper.setMask(1234u); + frameHelper.setOpCode(QWebSocketProtocol::OpCodeBinary); + frameHelper.setPayload(QByteArrayLiteral("12345")); + + QByteArray payload = frameHelper.wireRepresentation(); + QBuffer buffer(&payload); + buffer.open(QIODevice::ReadOnly); + + QWebSocketFrame frame = QWebSocketFrame::readFrame(&buffer); + buffer.close(); + + { + QWebSocketFrame other(frame); + QCOMPARE(other.closeCode(), frame.closeCode()); + QCOMPARE(other.closeReason(), frame.closeReason()); + QCOMPARE(other.hasMask(), frame.hasMask()); + QCOMPARE(other.isContinuationFrame(), frame.isContinuationFrame()); + QCOMPARE(other.isControlFrame(), frame.isControlFrame()); + QCOMPARE(other.isDataFrame(), frame.isDataFrame()); + QCOMPARE(other.isFinalFrame(), frame.isFinalFrame()); + QCOMPARE(other.isValid(), frame.isValid()); + QCOMPARE(other.mask(), frame.mask()); + QCOMPARE(other.opCode(), frame.opCode()); + QCOMPARE(other.payload(), frame.payload()); + QCOMPARE(other.rsv1(), frame.rsv1()); + QCOMPARE(other.rsv2(), frame.rsv2()); + QCOMPARE(other.rsv3(), frame.rsv3()); + } + { + QWebSocketFrame other; + other = frame; + QCOMPARE(other.closeCode(), frame.closeCode()); + QCOMPARE(other.closeReason(), frame.closeReason()); + QCOMPARE(other.hasMask(), frame.hasMask()); + QCOMPARE(other.isContinuationFrame(), frame.isContinuationFrame()); + QCOMPARE(other.isControlFrame(), frame.isControlFrame()); + QCOMPARE(other.isDataFrame(), frame.isDataFrame()); + QCOMPARE(other.isFinalFrame(), frame.isFinalFrame()); + QCOMPARE(other.isValid(), frame.isValid()); + QCOMPARE(other.mask(), frame.mask()); + QCOMPARE(other.opCode(), frame.opCode()); + QCOMPARE(other.payload(), frame.payload()); + QCOMPARE(other.rsv1(), frame.rsv1()); + QCOMPARE(other.rsv2(), frame.rsv2()); + QCOMPARE(other.rsv3(), frame.rsv3()); + } +} + +void tst_WebSocketFrame::tst_goodFrames_data() +{ + QTest::addColumn<int>("rsv1"); + QTest::addColumn<int>("rsv2"); + QTest::addColumn<int>("rsv3"); + QTest::addColumn<quint32>("mask"); + QTest::addColumn<QWebSocketProtocol::OpCode>("opCode"); + QTest::addColumn<bool>("isFinal"); + QTest::addColumn<QByteArray>("payload"); + QTest::addColumn<bool>("isControlFrame"); + QTest::addColumn<bool>("isDataFrame"); + QTest::addColumn<bool>("isContinuationFrame"); + + QTest::newRow("Non masked final text frame with small payload") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodeText + << true << QStringLiteral("Hello world!").toUtf8() + << false << true << false; + QTest::newRow("Non masked final binary frame with small payload") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodeBinary + << true << QByteArrayLiteral("\x00\x01\x02\x03\x04") + << false << true << false; + QTest::newRow("Non masked final text frame with no payload") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodeText + << true << QByteArray() + << false << true << false; + QTest::newRow("Non masked final binary frame with no payload") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodeBinary + << true << QByteArray() + << false << true << false; + + QTest::newRow("Non masked final close frame with small payload") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodeClose + << true << QStringLiteral("Hello world!").toUtf8() + << true << false << false; + QTest::newRow("Non masked final close frame with no payload") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodeClose + << true << QByteArray() + << true << false << false; + QTest::newRow("Non masked final ping frame with small payload") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodePing + << true << QStringLiteral("Hello world!").toUtf8() + << true << false << false; + QTest::newRow("Non masked final pong frame with no payload") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodePong + << true << QByteArray() + << true << false << false; + + QTest::newRow("Non masked final continuation frame with small payload") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodeContinue + << true << QStringLiteral("Hello world!").toUtf8() + << false << true << true; + QTest::newRow("Non masked non-final continuation frame with small payload") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodeContinue + << false << QStringLiteral("Hello world!").toUtf8() + << false << true << true; +} + +void tst_WebSocketFrame::tst_goodFrames() +{ + QFETCH(int, rsv1); + QFETCH(int, rsv2); + QFETCH(int, rsv3); + QFETCH(quint32, mask); + QFETCH(QWebSocketProtocol::OpCode, opCode); + QFETCH(bool, isFinal); + QFETCH(QByteArray, payload); + QFETCH(bool, isControlFrame); + QFETCH(bool, isDataFrame); + QFETCH(bool, isContinuationFrame); + + FrameHelper helper; + helper.setRsv1(rsv1); + helper.setRsv2(rsv2); + helper.setRsv3(rsv3); + helper.setMask(mask); + helper.setOpCode(opCode); + helper.setFinalFrame(isFinal); + helper.setPayload(payload); + + QByteArray wireRepresentation = helper.wireRepresentation(); + QBuffer buffer; + buffer.setData(wireRepresentation); + buffer.open(QIODevice::ReadOnly); + QWebSocketFrame frame = QWebSocketFrame::readFrame(&buffer); + buffer.close(); + QVERIFY(frame.isValid()); + QCOMPARE(frame.rsv1(), rsv1); + QCOMPARE(frame.rsv2(), rsv2); + QCOMPARE(frame.rsv3(), rsv3); + QCOMPARE(frame.hasMask(), (mask != 0)); + QCOMPARE(frame.opCode(), opCode); + QCOMPARE(frame.isFinalFrame(), isFinal); + QCOMPARE(frame.isControlFrame(), isControlFrame); + QCOMPARE(frame.isDataFrame(), isDataFrame); + QCOMPARE(frame.isContinuationFrame(), isContinuationFrame); + QCOMPARE(frame.payload().length(), payload.length()); + QCOMPARE(frame.payload(), payload); +} + +void tst_WebSocketFrame::tst_invalidFrames_data() +{ + QTest::addColumn<int>("rsv1"); + QTest::addColumn<int>("rsv2"); + QTest::addColumn<int>("rsv3"); + QTest::addColumn<quint32>("mask"); + QTest::addColumn<QWebSocketProtocol::OpCode>("opCode"); + QTest::addColumn<bool>("isFinal"); + QTest::addColumn<QByteArray>("payload"); + QTest::addColumn<QWebSocketProtocol::CloseCode>("expectedError"); + + QTest::newRow("RSV1 != 0") + << 1 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodeText + << true << QStringLiteral("Hello world!").toUtf8() + << QWebSocketProtocol::CloseCodeProtocolError; + QTest::newRow("RSV2 != 0") + << 0 << 1 << 0 + << 0U << QWebSocketProtocol::OpCodeText + << true << QStringLiteral("Hello world!").toUtf8() + << QWebSocketProtocol::CloseCodeProtocolError; + QTest::newRow("RSV3 != 0") + << 0 << 0 << 1 + << 0U << QWebSocketProtocol::OpCodeText + << true << QStringLiteral("Hello world!").toUtf8() + << QWebSocketProtocol::CloseCodeProtocolError; + QTest::newRow("RSV1 != 0 and RSV2 != 0") + << 1 << 1 << 0 + << 0U << QWebSocketProtocol::OpCodeText + << true << QStringLiteral("Hello world!").toUtf8() + << QWebSocketProtocol::CloseCodeProtocolError; + QTest::newRow("RSV1 != 0 and RSV3 != 0") + << 1 << 0 << 1 + << 0U << QWebSocketProtocol::OpCodeText + << true << QStringLiteral("Hello world!").toUtf8() + << QWebSocketProtocol::CloseCodeProtocolError; + QTest::newRow("RSV2 != 0 and RSV3 != 0") + << 0 << 1 << 1 + << 0U << QWebSocketProtocol::OpCodeText + << true << QStringLiteral("Hello world!").toUtf8() + << QWebSocketProtocol::CloseCodeProtocolError; + + QTest::newRow("Reserved OpCode 3") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodeReserved3 + << true << QStringLiteral("Hello world!").toUtf8() + << QWebSocketProtocol::CloseCodeProtocolError; + QTest::newRow("Reserved OpCode 4") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodeReserved4 + << true << QStringLiteral("Hello world!").toUtf8() + << QWebSocketProtocol::CloseCodeProtocolError; + QTest::newRow("Reserved OpCode 5") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodeReserved5 + << true << QStringLiteral("Hello world!").toUtf8() + << QWebSocketProtocol::CloseCodeProtocolError; + QTest::newRow("Reserved OpCode 6") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodeReserved6 + << true << QStringLiteral("Hello world!").toUtf8() + << QWebSocketProtocol::CloseCodeProtocolError; + QTest::newRow("Reserved OpCode 7") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodeReserved7 + << true << QStringLiteral("Hello world!").toUtf8() + << QWebSocketProtocol::CloseCodeProtocolError; + QTest::newRow("Reserved OpCode B") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodeReservedB + << true << QStringLiteral("Hello world!").toUtf8() + << QWebSocketProtocol::CloseCodeProtocolError; + QTest::newRow("Reserved OpCode C") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodeReservedC + << true << QStringLiteral("Hello world!").toUtf8() + << QWebSocketProtocol::CloseCodeProtocolError; + QTest::newRow("Reserved OpCode D") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodeReservedD + << true << QStringLiteral("Hello world!").toUtf8() + << QWebSocketProtocol::CloseCodeProtocolError; + QTest::newRow("Reserved OpCode E") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodeReservedE + << true << QStringLiteral("Hello world!").toUtf8() + << QWebSocketProtocol::CloseCodeProtocolError; + QTest::newRow("Reserved OpCode F") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodeReservedF + << true << QStringLiteral("Hello world!").toUtf8() + << QWebSocketProtocol::CloseCodeProtocolError; + + QTest::newRow("Close Frame with payload > 125 bytes") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodeClose + << true << QString(126, 'a').toUtf8() + << QWebSocketProtocol::CloseCodeProtocolError; + QTest::newRow("Non-final Close Frame") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodeClose + << false << QString(126, 'a').toUtf8() + << QWebSocketProtocol::CloseCodeProtocolError; + QTest::newRow("Ping Frame with payload > 125 bytes") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodePing + << true << QString(126, 'a').toUtf8() + << QWebSocketProtocol::CloseCodeProtocolError; + QTest::newRow("Non-final Ping Frame") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodePing + << false << QString(126, 'a').toUtf8() + << QWebSocketProtocol::CloseCodeProtocolError; + QTest::newRow("Pong Frame with payload > 125 bytes") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodePong + << true << QString(126, 'a').toUtf8() + << QWebSocketProtocol::CloseCodeProtocolError; + QTest::newRow("Non-final Pong Frame") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodePong + << false << QString(126, 'a').toUtf8() + << QWebSocketProtocol::CloseCodeProtocolError; +} + +void tst_WebSocketFrame::tst_invalidFrames() +{ + QFETCH(int, rsv1); + QFETCH(int, rsv2); + QFETCH(int, rsv3); + QFETCH(quint32, mask); + QFETCH(QWebSocketProtocol::OpCode, opCode); + QFETCH(bool, isFinal); + QFETCH(QByteArray, payload); + QFETCH(QWebSocketProtocol::CloseCode, expectedError); + + FrameHelper helper; + helper.setRsv1(rsv1); + helper.setRsv2(rsv2); + helper.setRsv3(rsv3); + helper.setMask(mask); + helper.setOpCode(opCode); + helper.setFinalFrame(isFinal); + helper.setPayload(payload); + + QByteArray wireRepresentation = helper.wireRepresentation(); + QBuffer buffer; + buffer.setData(wireRepresentation); + buffer.open(QIODevice::ReadOnly); + QWebSocketFrame frame = QWebSocketFrame::readFrame(&buffer); + buffer.close(); + + QVERIFY(!frame.isValid()); + QCOMPARE(frame.closeCode(), expectedError); +} + + +/* + * Incomplete or overly large frames + * Payload must be crafted manually + * + QTest::newRow("Frame Too Big") + << 0 << 0 << 0 + << 0U << QWebSocketProtocol::OpCodeText + << true << QString(MAX_FRAME_SIZE_IN_BYTES + 1, 'a').toUtf8() + << QWebSocketProtocol::CloseCodeTooMuchData; + + */ +void tst_WebSocketFrame::tst_malformedFrames_data() +{ + QTest::addColumn<QByteArray>("payload"); + QTest::addColumn<QWebSocketProtocol::CloseCode>("expectedError"); + + //too little data + QTest::newRow("No data") << QByteArray() << QWebSocketProtocol::CloseCodeGoingAway; + FrameHelper helper; + helper.setRsv1(0); + helper.setRsv2(0); + helper.setRsv3(0); + helper.setMask(0U); + helper.setOpCode(QWebSocketProtocol::OpCodeText); + helper.setFinalFrame(true); + helper.setPayload(QString(10, 'a').toUtf8()); + QByteArray wireRep = helper.wireRepresentation(); + + //too little data + //header + payload should be 12 bytes for non-masked payloads < 126 bytes + for (int i = 1; i < 12; ++i) + { + QTest::newRow(QStringLiteral("Header too small - %1 byte(s)").arg(i).toLatin1().constData()) + << wireRep.left(i) + << QWebSocketProtocol::CloseCodeGoingAway; + } + //too much data + { + const char bigpayloadIndicator = char(127); + const quint64 payloadSize = MAX_FRAME_SIZE_IN_BYTES + 1; + uchar swapped[8] = {0}; + qToBigEndian<quint64>(payloadSize, swapped); + QTest::newRow("Frame too big") + << wireRep.left(1).append(bigpayloadIndicator) + .append(reinterpret_cast<char *>(swapped), 8) + << QWebSocketProtocol::CloseCodeTooMuchData; + } + //invalid size field + { + const char bigpayloadIndicator = char(127); + quint64 payloadSize = quint64(1) << 63; + uchar swapped[8] = {0}; + qToBigEndian<quint64>(payloadSize, swapped); + QTest::newRow("Highest bit of payload length is set") + << wireRep.left(1).append(bigpayloadIndicator) + .append(reinterpret_cast<char *>(swapped), 8) + << QWebSocketProtocol::CloseCodeProtocolError; + + payloadSize = 256; + qToBigEndian<quint64>(payloadSize, swapped); + QTest::newRow("Overlong 64-bit size field; should be 16-bit") + << wireRep.left(1).append(bigpayloadIndicator) + .append(reinterpret_cast<char *>(swapped), 8) + << QWebSocketProtocol::CloseCodeProtocolError; + } + //overlong size field + { + const char largepayloadIndicator = char(126); + const quint16 payloadSize = 120; + uchar swapped[2] = {0}; + qToBigEndian<quint16>(payloadSize, swapped); + QTest::newRow("Overlong 16-bit size field") + << wireRep.left(1).append(largepayloadIndicator) + .append(reinterpret_cast<char *>(swapped), 2) + << QWebSocketProtocol::CloseCodeProtocolError; + } + { + const char bigpayloadIndicator = char(127); + quint64 payloadSize = 120; + uchar swapped[8] = {0}; + qToBigEndian<quint64>(payloadSize, swapped); + QTest::newRow("Overlong 64-bit size field; should be 7-bit") + << wireRep.left(1).append(bigpayloadIndicator) + .append(reinterpret_cast<char *>(swapped), 8) + << QWebSocketProtocol::CloseCodeProtocolError; + + payloadSize = 256; + qToBigEndian<quint64>(payloadSize, swapped); + QTest::newRow("Overlong 64-bit size field; should be 16-bit") + << wireRep.left(1).append(bigpayloadIndicator) + .append(reinterpret_cast<char *>(swapped), 8) + << QWebSocketProtocol::CloseCodeProtocolError; + } +} + +void tst_WebSocketFrame::tst_malformedFrames() +{ + QFETCH(QByteArray, payload); + QFETCH(QWebSocketProtocol::CloseCode, expectedError); + + QBuffer buffer; + buffer.setData(payload); + buffer.open(QIODevice::ReadOnly); + QWebSocketFrame frame = QWebSocketFrame::readFrame(&buffer); + buffer.close(); + + QVERIFY(!frame.isValid()); + QCOMPARE(frame.closeCode(), expectedError); +} + +QTEST_MAIN(tst_WebSocketFrame) + +#include "tst_websocketframe.moc" + diff --git a/tests/auto/websockets/websocketframe/websocketframe.pro b/tests/auto/websockets/websocketframe/websocketframe.pro new file mode 100644 index 0000000..9682348 --- /dev/null +++ b/tests/auto/websockets/websocketframe/websocketframe.pro @@ -0,0 +1,14 @@ +CONFIG += console +CONFIG += testcase +CONFIG -= app_bundle + +TEMPLATE = app + +TARGET = tst_websocketframe + +QT = core testlib websockets websockets-private + +SOURCES += tst_websocketframe.cpp + +requires(contains(QT_CONFIG, private_tests)) +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 diff --git a/tests/auto/websockets/websocketprotocol/tst_websocketprotocol.cpp b/tests/auto/websockets/websocketprotocol/tst_websocketprotocol.cpp new file mode 100644 index 0000000..94816de --- /dev/null +++ b/tests/auto/websockets/websocketprotocol/tst_websocketprotocol.cpp @@ -0,0 +1,289 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Kurt Pattyn <pattyn.kurt@gmail.com>. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <QtTest/QtTest> +#include <QtTest/qtestcase.h> +#include <QtEndian> + +#include <QDebug> + +#include "QtWebSockets/qwebsocketprotocol.h" +#include "private/qwebsocketprotocol_p.h" + +QT_USE_NAMESPACE + +Q_DECLARE_METATYPE(QWebSocketProtocol::CloseCode) +Q_DECLARE_METATYPE(QWebSocketProtocol::OpCode) +Q_DECLARE_METATYPE(QWebSocketProtocol::Version) + +class tst_WebSocketProtocol : public QObject +{ + Q_OBJECT + +public: + tst_WebSocketProtocol(); + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void tst_validMasks_data(); + void tst_validMasks(); + + void tst_opCodes_data(); + void tst_opCodes(); + + void tst_closeCodes_data(); + void tst_closeCodes(); + + void tst_versionFromString_data(); + void tst_versionFromString(); +}; + +tst_WebSocketProtocol::tst_WebSocketProtocol() +{} + +void tst_WebSocketProtocol::initTestCase() +{ +} + +void tst_WebSocketProtocol::cleanupTestCase() +{} + +void tst_WebSocketProtocol::init() +{ + qRegisterMetaType<QWebSocketProtocol::OpCode>("QWebSocketProtocol::OpCode"); + qRegisterMetaType<QWebSocketProtocol::CloseCode>("QWebSocketProtocol::CloseCode"); +} + +void tst_WebSocketProtocol::cleanup() +{ +} + +void tst_WebSocketProtocol::tst_validMasks_data() +{ + QTest::addColumn<quint32>("mask"); + QTest::addColumn<QString>("inputdata"); + QTest::addColumn<QByteArray>("result"); + + QTest::newRow("Empty payload") << 0x12345678u << QString() << QByteArray(); + QTest::newRow("ASCII payload of 8 characters") + << 0x12345678u + << QStringLiteral("abcdefgh") + << QByteArrayLiteral("\x73\x56\x35\x1C\x77\x52\x31\x10"); + QTest::newRow("ASCII payload of 9 characters") + << 0x12345678u + << QStringLiteral("abcdefghi") + << QByteArrayLiteral("\x73\x56\x35\x1C\x77\x52\x31\x10\x7B"); + //MSVC doesn't like UTF-8 in source code; + //the following text is represented in the string below: ∫∂ƒ©øØ + QTest::newRow("UTF-8 payload") + << 0x12345678u + << QString::fromUtf8("\xE2\x88\xAB\xE2\x88\x82\xC6\x92\xC2\xA9\xC3\xB8\xC3\x98") + << QByteArrayLiteral("\x2D\x0B\x69\xD1\xEA\xEC"); +} + +void tst_WebSocketProtocol::tst_validMasks() +{ + QFETCH(quint32, mask); + QFETCH(QString, inputdata); + QFETCH(QByteArray, result); + + //put latin1 into an explicit array + //otherwise, the intermediate object is deleted and the data pointer becomes invalid + QByteArray latin1 = inputdata.toLatin1(); + char *data = latin1.data(); + + QWebSocketProtocol::mask(data, inputdata.size(), mask); + QCOMPARE(QByteArray::fromRawData(data, inputdata.size()), result); +} + +void tst_WebSocketProtocol::tst_opCodes_data() +{ + QTest::addColumn<QWebSocketProtocol::OpCode>("opCode"); + QTest::addColumn<bool>("isReserved"); + + QTest::newRow("OpCodeBinary") << QWebSocketProtocol::OpCodeBinary << false; + QTest::newRow("OpCodeClose") << QWebSocketProtocol::OpCodeClose << false; + QTest::newRow("OpCodeContinue") << QWebSocketProtocol::OpCodeContinue << false; + QTest::newRow("OpCodePing") << QWebSocketProtocol::OpCodePing << false; + QTest::newRow("OpCodePong") << QWebSocketProtocol::OpCodePong << false; + QTest::newRow("OpCodeReserved3") << QWebSocketProtocol::OpCodeReserved3 << true; + QTest::newRow("OpCodeReserved4") << QWebSocketProtocol::OpCodeReserved4 << true; + QTest::newRow("OpCodeReserved5") << QWebSocketProtocol::OpCodeReserved5 << true; + QTest::newRow("OpCodeReserved6") << QWebSocketProtocol::OpCodeReserved6 << true; + QTest::newRow("OpCodeReserved7") << QWebSocketProtocol::OpCodeReserved7 << true; + QTest::newRow("OpCodeReserved8") << QWebSocketProtocol::OpCodeReservedB << true; + QTest::newRow("OpCodeReservedC") << QWebSocketProtocol::OpCodeReservedC << true; + QTest::newRow("OpCodeReservedD") << QWebSocketProtocol::OpCodeReservedD << true; + QTest::newRow("OpCodeReservedE") << QWebSocketProtocol::OpCodeReservedE << true; + QTest::newRow("OpCodeReservedF") << QWebSocketProtocol::OpCodeReservedF << true; + QTest::newRow("OpCodeText") << QWebSocketProtocol::OpCodeText << false; +} + +void tst_WebSocketProtocol::tst_opCodes() +{ + QFETCH(QWebSocketProtocol::OpCode, opCode); + QFETCH(bool, isReserved); + + bool result = QWebSocketProtocol::isOpCodeReserved(opCode); + + QCOMPARE(result, isReserved); +} + +void tst_WebSocketProtocol::tst_closeCodes_data() +{ + QTest::addColumn<int>("closeCode"); + QTest::addColumn<bool>("isValid"); + + for (int i = 0; i < 1000; ++i) + { + QTest::newRow(QStringLiteral("Close code %1").arg(i).toLatin1().constData()) << i << false; + } + + for (int i = 1000; i < 1004; ++i) + { + QTest::newRow(QStringLiteral("Close code %1").arg(i).toLatin1().constData()) << i << true; + } + + QTest::newRow("Close code 1004") << 1004 << false; + QTest::newRow("Close code 1005") << 1005 << false; + QTest::newRow("Close code 1006") << 1006 << false; + + for (int i = 1007; i < 1012; ++i) + { + QTest::newRow(QStringLiteral("Close code %1").arg(i).toLatin1().constData()) << i << true; + } + + for (int i = 1013; i < 3000; ++i) + { + QTest::newRow(QStringLiteral("Close code %1").arg(i).toLatin1().constData()) << i << false; + } + + for (int i = 3000; i < 5000; ++i) + { + QTest::newRow(QStringLiteral("Close code %1").arg(i).toLatin1().constData()) << i << true; + } + + QTest::newRow("Close code 5000") << 1004 << false; + QTest::newRow("Close code 6000") << 1004 << false; + QTest::newRow("Close code 7000") << 1004 << false; +} + +void tst_WebSocketProtocol::tst_closeCodes() +{ + QFETCH(int, closeCode); + QFETCH(bool, isValid); + + bool result = QWebSocketProtocol::isCloseCodeValid(closeCode); + + QCOMPARE(result, isValid); +} + +void tst_WebSocketProtocol::tst_versionFromString_data() +{ + QTest::addColumn<QWebSocketProtocol::Version>("version"); + QTest::addColumn<QString>("versionString"); + + //happy flow; good data + QTest::newRow("Version 0") + << QWebSocketProtocol::Version0 + << QStringLiteral("0"); + QTest::newRow("Version 4") + << QWebSocketProtocol::Version4 + << QStringLiteral("4"); + QTest::newRow("Version 5") + << QWebSocketProtocol::Version5 + << QStringLiteral("5"); + QTest::newRow("Version 6") + << QWebSocketProtocol::Version6 + << QStringLiteral("6"); + QTest::newRow("Version 7") + << QWebSocketProtocol::Version7 + << QStringLiteral("7"); + QTest::newRow("Version 8") + << QWebSocketProtocol::Version8 + << QStringLiteral("8"); + QTest::newRow("Version 13") + << QWebSocketProtocol::Version13 + << QStringLiteral("13"); + + //rainy flow; invalid data + QTest::newRow("Version -1") + << QWebSocketProtocol::VersionUnknown + << QStringLiteral("-1"); + QTest::newRow("Version 1") + << QWebSocketProtocol::VersionUnknown + << QStringLiteral("1"); + QTest::newRow("Version 2") + << QWebSocketProtocol::VersionUnknown + << QStringLiteral("2"); + QTest::newRow("Version 3") + << QWebSocketProtocol::VersionUnknown + << QStringLiteral("3"); + QTest::newRow("Version 9") + << QWebSocketProtocol::VersionUnknown + << QStringLiteral("9"); + QTest::newRow("Version 10") + << QWebSocketProtocol::VersionUnknown + << QStringLiteral("10"); + QTest::newRow("Version 11") + << QWebSocketProtocol::VersionUnknown + << QStringLiteral("11"); + QTest::newRow("Version 12") + << QWebSocketProtocol::VersionUnknown + << QStringLiteral("12"); + QTest::newRow("Version abcd") + << QWebSocketProtocol::VersionUnknown + << QStringLiteral("abcd"); + QTest::newRow("Version 1.6") + << QWebSocketProtocol::VersionUnknown + << QStringLiteral("1.6"); + QTest::newRow("Version empty") + << QWebSocketProtocol::VersionUnknown + << QString(); +} + +void tst_WebSocketProtocol::tst_versionFromString() +{ + QFETCH(QWebSocketProtocol::Version, version); + QFETCH(QString, versionString); + + QCOMPARE(QWebSocketProtocol::versionFromString(versionString), version); +} + +QTEST_MAIN(tst_WebSocketProtocol) + +#include "tst_websocketprotocol.moc" + diff --git a/tests/auto/websockets/websocketprotocol/websocketprotocol.pro b/tests/auto/websockets/websocketprotocol/websocketprotocol.pro new file mode 100644 index 0000000..c21bb64 --- /dev/null +++ b/tests/auto/websockets/websocketprotocol/websocketprotocol.pro @@ -0,0 +1,14 @@ +CONFIG += console +CONFIG += testcase +CONFIG -= app_bundle + +TEMPLATE = app + +TARGET = tst_websocketprotocol + +QT = core testlib websockets websockets-private + +SOURCES += tst_websocketprotocol.cpp + +requires(contains(QT_CONFIG, private_tests)) +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 diff --git a/tests/auto/websockets/websockets.pro b/tests/auto/websockets/websockets.pro new file mode 100644 index 0000000..4698cd0 --- /dev/null +++ b/tests/auto/websockets/websockets.pro @@ -0,0 +1,16 @@ +TEMPLATE = subdirs + +SUBDIRS = \ + qwebsocketcorsauthenticator + +contains(QT_CONFIG, private_tests): SUBDIRS += \ + websocketprotocol \ + dataprocessor \ + websocketframe \ + handshakerequest \ + handshakeresponse \ + qdefaultmaskgenerator + +SUBDIRS += \ + qwebsocket \ + qwebsocketserver |