From b86f2949f63e0ae904c19fb6d39dc6793b164db9 Mon Sep 17 00:00:00 2001 From: Kurt Pattyn Date: Thu, 10 Oct 2013 21:12:27 +0200 Subject: [PATCH] Added checks for valid control frames Also added extra checks on signals Added extra comments Cleaned up superfluous TODOs Change-Id: I8ec760f0984bee4bd4cc757ce3ef6d7ed1c01102 Reviewed-by: Steven Ceuppens --- src/websockets/qwebsocketserver.cpp | 3 - tests/auto/dataprocessor/tst_dataprocessor.cpp | 1805 +++++++++++++----------- 2 files changed, 951 insertions(+), 857 deletions(-) diff --git a/src/websockets/qwebsocketserver.cpp b/src/websockets/qwebsocketserver.cpp index 1ea0f99..f267001 100644 --- a/src/websockets/qwebsocketserver.cpp +++ b/src/websockets/qwebsocketserver.cpp @@ -107,9 +107,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #include "qwebsocketserver.h" #include "qwebsocketserver_p.h" -//TODO: CorsCheck: give list in constructor or use CorsAuthenticator object -//in QNetworkAccessManager the signal cannot be connected to a queued signal, because it waits for the signal to return - QT_BEGIN_NAMESPACE /*! diff --git a/tests/auto/dataprocessor/tst_dataprocessor.cpp b/tests/auto/dataprocessor/tst_dataprocessor.cpp index fac155d..4fd14fe 100644 --- a/tests/auto/dataprocessor/tst_dataprocessor.cpp +++ b/tests/auto/dataprocessor/tst_dataprocessor.cpp @@ -14,29 +14,16 @@ Q_DECLARE_METATYPE(QWebSocketProtocol::OpCode) //TODO: test valid UTF8 sequences (see UC 6.2) //TODO: test on masking correctness //TODO: test for valid fields -//TODO: test for valid control frames -//DONE: test for valid close codes -//DONE: test close frame with no close code and reason //TODO: test if opcode is correct after processing of a continuation frame (text and binary frames) //TODO: test valid frame sequences //rainy-day flow -//DONE: test invalid UTF8 sequences -//DONE: test invalid UTF8 sequences in control/close frames //TODO: test invalid masks //TODO: test for AutoBahn testcase 5 //TODO: test for AutoBahn testcase 6.1 //TODO: test for AutoBahn testcase 6.3 (fragmentation test) //TODO: test for AutoBahn testcase 6.4 (fragmentation test) -//DONE: test for invalid fields -//DONE: test for invalid opcodes -//DONE: test continuation frames for too big payload -//DONE: test continuation frames for too small frame -//DONE: test continuation frames for bad rsv fields, etc. -//DONE: test continuation frames for incomplete payload -//DONE: test close frame with payload length 1 (is either 0, if no close code, or at least 2, close code + optional reason) -//DONE: besides spying on errors, we should also check if the frame and message signals are not emitted (or partially emitted) //TODO: test invalid frame sequences @@ -68,20 +55,24 @@ private Q_SLOTS: \brief Tests all kinds of valid binary frames, including zero length frames */ void goodBinaryFrame(); + void goodBinaryFrame_data(); /*! \brief Tests all kinds of valid text frames, including zero length frames */ void goodTextFrame(); + void goodTextFrame_data(); /*! * \brief Test all kinds of valid control frames. */ - //void goodControlFrame(); + void goodControlFrame(); + /*! * \brief Test all kinds of valid close frames. */ void goodCloseFrame(); + void goodCloseFrame_data(); //void goodHeaders(); //test all valid opcodes @@ -92,9 +83,10 @@ private Q_SLOTS: to allow non-characters again. */ void nonCharacterCodes(); + void nonCharacterCodes_data(); /*************************************************************************** - * Unhappy Flows + * Rainy Day Flows ***************************************************************************/ /*! \brief Tests the QWebSocketDataProcessor for correct handling of frames that don't contain the starting 2 bytes. @@ -108,12 +100,14 @@ private Q_SLOTS: This test does not test sequences of frames, only single frames are tested */ void frameTooBig(); + void frameTooBig_data(); /*! \brief 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(); /*! \brief Tests the QWebSocketDataProcessor for the correct handling of invalid control frames. @@ -121,12 +115,16 @@ private Q_SLOTS: This test does not test sequences of frames, only single frames are tested */ void invalidControlFrame(); + void invalidControlFrame_data(); + void invalidCloseFrame(); + void invalidCloseFrame_data(); /*! \brief Tests the QWebSocketDataProcessor for the correct handling of incomplete size fields for large and big payloads. */ void incompleteSizeField(); + void incompleteSizeField_data(); /*! \brief Tests the QWebSocketDataProcessor for the correct handling of incomplete payloads. @@ -138,34 +136,23 @@ private Q_SLOTS: This test does not test sequences of frames, only single frames are tested */ void incompletePayload(); + void incompletePayload_data(); /*! \brief 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 invalidHeader_data(); - void incompleteSizeField_data(); - void incompletePayload_data(); - void invalidPayload_data(bool isControlFrame = false); - void invalidPayloadInCloseFrame_data(); void minimumSizeRequirement_data(); - void invalidControlFrame_data(); - void invalidCloseFrame_data(); - void frameTooBig_data(); - - void nonCharacterCodes_data(); - void goodTextFrame_data(); - void goodBinaryFrame_data(); - void goodCloseFrame_data(); private: //helper function that constructs a new row of test data for invalid UTF8 sequences @@ -210,80 +197,19 @@ void tst_DataProcessor::cleanup() { } -void tst_DataProcessor::goodTextFrame_data() -{ - QTest::addColumn("payload"); - - //test frames with small (< 126), large ( < 65536) and big ( > 65535) payloads - for (int i = 0; i < (65536 + 256); i += 128) - { - QTest::newRow(QString("Text frame with %1 ASCII characters").arg(i).toStdString().data()) << QByteArray(i, 'a'); - } - //test all valid ASCII characters - for (int i = 0; i < 128; ++i) - { - QTest::newRow(QString("Text frame with containing ASCII character '0x%1'").arg(QByteArray(1, char(i)).toHex().constData()).toStdString().data()) << QByteArray(1, char(i)); - } -} - void tst_DataProcessor::goodBinaryFrame_data() { QTest::addColumn("payload"); - for (int i = 0; i < (65536 + 256); i += 128) + for (int i = 0; i < (65536 + 256); i += 128) //be sure to get small (< 126 bytes), large (> 125 bytes & < 64K) and big (>64K) frames { QTest::newRow(QString("Binary frame with %1 bytes").arg(i).toStdString().data()) << QByteArray(i, char(1)); } - for (int i = 0; i < 256; ++i) + for (int i = 0; i < 256; ++i) //test all possible bytes in the payload { QTest::newRow(QString("Binary frame containing byte: '0x%1'").arg(QByteArray(1, char(i)).toHex().constData()).toStdString().data()) << QByteArray(i, char(1)); } } -void tst_DataProcessor::goodCloseFrame_data() -{ - QTest::addColumn("payload"); - QTest::addColumn("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(QString("Close frame with %1 ASCII characters").arg(i).toStdString().data()) << QString(i, 'a') << QWebSocketProtocol::CC_NORMAL; - } - for (int i = 0; i < 126; ++i) - { - QTest::newRow(QString("Text frame with containing ASCII character '0x%1'").arg(QByteArray(1, char(i)).toHex().constData()).toStdString().data()) << QString(1, char(i)) << QWebSocketProtocol::CC_NORMAL; - } - QTest::newRow("Close frame with close code NORMAL") << QString(1, 'a') << QWebSocketProtocol::CC_NORMAL; - QTest::newRow("Close frame with close code BAD OPERATION") << QString(1, 'a') << QWebSocketProtocol::CC_BAD_OPERATION; - QTest::newRow("Close frame with close code DATATYPE NOT SUPPORTED") << QString(1, 'a') << QWebSocketProtocol::CC_DATATYPE_NOT_SUPPORTED; - QTest::newRow("Close frame with close code GOING AWAY") << QString(1, 'a') << QWebSocketProtocol::CC_GOING_AWAY; - QTest::newRow("Close frame with close code MISSING EXTENSION") << QString(1, 'a') << QWebSocketProtocol::CC_MISSING_EXTENSION; - QTest::newRow("Close frame with close code POLICY VIOLATED") << QString(1, 'a') << QWebSocketProtocol::CC_POLICY_VIOLATED; - QTest::newRow("Close frame with close code PROTOCOL ERROR") << QString(1, 'a') << QWebSocketProtocol::CC_PROTOCOL_ERROR; - QTest::newRow("Close frame with close code TOO MUCH DATA") << QString(1, 'a') << QWebSocketProtocol::CC_TOO_MUCH_DATA; - QTest::newRow("Close frame with close code WRONG DATATYPE") << QString(1, 'a') << QWebSocketProtocol::CC_WRONG_DATATYPE; - 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::CC_NORMAL; - QTest::newRow("Close frame with close code BAD OPERATION and no reason") << QString() << QWebSocketProtocol::CC_BAD_OPERATION; - QTest::newRow("Close frame with close code DATATYPE NOT SUPPORTED and no reason") << QString() << QWebSocketProtocol::CC_DATATYPE_NOT_SUPPORTED; - QTest::newRow("Close frame with close code GOING AWAY and no reason") << QString() << QWebSocketProtocol::CC_GOING_AWAY; - QTest::newRow("Close frame with close code MISSING EXTENSION and no reason") << QString() << QWebSocketProtocol::CC_MISSING_EXTENSION; - QTest::newRow("Close frame with close code POLICY VIOLATED and no reason") << QString() << QWebSocketProtocol::CC_POLICY_VIOLATED; - QTest::newRow("Close frame with close code PROTOCOL ERROR and no reason") << QString() << QWebSocketProtocol::CC_PROTOCOL_ERROR; - QTest::newRow("Close frame with close code TOO MUCH DATA and no reason") << QString() << QWebSocketProtocol::CC_TOO_MUCH_DATA; - QTest::newRow("Close frame with close code WRONG DATATYPE and no reason") << QString() << QWebSocketProtocol::CC_WRONG_DATATYPE; - 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::goodBinaryFrame() { QByteArray data; @@ -314,23 +240,44 @@ void tst_DataProcessor::goodBinaryFrame() buffer.setData(data); buffer.open(QIODevice::ReadOnly); - QSignalSpy spyFrameReceived(&dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool))); - QSignalSpy spyMessageReceived(&dataProcessor, SIGNAL(binaryMessageReceived(QByteArray))); - QSignalSpy spyTextFrameReceived(&dataProcessor, SIGNAL(textFrameReceived(QString,bool))); - QSignalSpy spyTextMessageReceived(&dataProcessor, SIGNAL(textMessageReceived(QString))); + 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(spyFrameReceived.count(), 1); - QCOMPARE(spyMessageReceived.count(), 1); - QCOMPARE(spyTextFrameReceived.count(), 0); - QCOMPARE(spyTextMessageReceived.count(), 0); - QList arguments = spyFrameReceived.takeFirst(); + 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 arguments = binaryFrameReceivedSpy.takeFirst(); QCOMPARE(arguments.at(0).toByteArray().length(), payload.length()); - arguments = spyMessageReceived.takeFirst(); + arguments = binaryMessageReceivedSpy.takeFirst(); QCOMPARE(arguments.at(0).toByteArray().length(), payload.length()); buffer.close(); - spyFrameReceived.clear(); - spyMessageReceived.clear(); - data.clear(); +} + +void tst_DataProcessor::goodTextFrame_data() +{ + QTest::addColumn("payload"); + + //test frames with small (< 126), large ( < 65536) and big ( > 65535) payloads + for (int i = 0; i < (65536 + 256); i += 128) + { + QTest::newRow(QString("Text frame with %1 ASCII characters").arg(i).toStdString().data()) << QByteArray(i, 'a'); + } + //test all valid ASCII characters + for (int i = 0; i < 128; ++i) + { + QTest::newRow(QString("Text frame with containing ASCII character '0x%1'").arg(QByteArray(1, char(i)).toHex().constData()).toStdString().data()) << QByteArray(1, char(i)); + } } void tst_DataProcessor::goodTextFrame() @@ -363,23 +310,124 @@ void tst_DataProcessor::goodTextFrame() buffer.setData(data); buffer.open(QIODevice::ReadOnly); - QSignalSpy spyFrameReceived(&dataProcessor, SIGNAL(textFrameReceived(QString,bool))); - QSignalSpy spyMessageReceived(&dataProcessor, SIGNAL(textMessageReceived(QString))); - QSignalSpy spyBinaryFrameReceived(&dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool))); - QSignalSpy spyBinaryMessageReceived(&dataProcessor, SIGNAL(binaryMessageReceived(QByteArray))); + 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(spyFrameReceived.count(), 1); - QCOMPARE(spyMessageReceived.count(), 1); - QCOMPARE(spyBinaryFrameReceived.count(), 0); - QCOMPARE(spyBinaryMessageReceived.count(), 0); - QList arguments = spyFrameReceived.takeFirst(); + + 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 arguments = textFrameReceivedSpy.takeFirst(); QCOMPARE(arguments.at(0).toString().length(), payload.length()); - arguments = spyMessageReceived.takeFirst(); + arguments = textMessageReceivedSpy.takeFirst(); QCOMPARE(arguments.at(0).toString().length(), payload.length()); buffer.close(); - spyFrameReceived.clear(); - spyMessageReceived.clear(); +} + +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::OC_PING)); + 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::OC_PONG)); + 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("payload"); + QTest::addColumn("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(QString("Close frame with %1 ASCII characters").arg(i).toStdString().data()) << QString(i, 'a') << QWebSocketProtocol::CC_NORMAL; + } + for (int i = 0; i < 126; ++i) + { + QTest::newRow(QString("Text frame with containing ASCII character '0x%1'").arg(QByteArray(1, char(i)).toHex().constData()).toStdString().data()) << QString(1, char(i)) << QWebSocketProtocol::CC_NORMAL; + } + QTest::newRow("Close frame with close code NORMAL") << QString(1, 'a') << QWebSocketProtocol::CC_NORMAL; + QTest::newRow("Close frame with close code BAD OPERATION") << QString(1, 'a') << QWebSocketProtocol::CC_BAD_OPERATION; + QTest::newRow("Close frame with close code DATATYPE NOT SUPPORTED") << QString(1, 'a') << QWebSocketProtocol::CC_DATATYPE_NOT_SUPPORTED; + QTest::newRow("Close frame with close code GOING AWAY") << QString(1, 'a') << QWebSocketProtocol::CC_GOING_AWAY; + QTest::newRow("Close frame with close code MISSING EXTENSION") << QString(1, 'a') << QWebSocketProtocol::CC_MISSING_EXTENSION; + QTest::newRow("Close frame with close code POLICY VIOLATED") << QString(1, 'a') << QWebSocketProtocol::CC_POLICY_VIOLATED; + QTest::newRow("Close frame with close code PROTOCOL ERROR") << QString(1, 'a') << QWebSocketProtocol::CC_PROTOCOL_ERROR; + QTest::newRow("Close frame with close code TOO MUCH DATA") << QString(1, 'a') << QWebSocketProtocol::CC_TOO_MUCH_DATA; + QTest::newRow("Close frame with close code WRONG DATATYPE") << QString(1, 'a') << QWebSocketProtocol::CC_WRONG_DATATYPE; + 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::CC_NORMAL; + QTest::newRow("Close frame with close code BAD OPERATION and no reason") << QString() << QWebSocketProtocol::CC_BAD_OPERATION; + QTest::newRow("Close frame with close code DATATYPE NOT SUPPORTED and no reason") << QString() << QWebSocketProtocol::CC_DATATYPE_NOT_SUPPORTED; + QTest::newRow("Close frame with close code GOING AWAY and no reason") << QString() << QWebSocketProtocol::CC_GOING_AWAY; + QTest::newRow("Close frame with close code MISSING EXTENSION and no reason") << QString() << QWebSocketProtocol::CC_MISSING_EXTENSION; + QTest::newRow("Close frame with close code POLICY VIOLATED and no reason") << QString() << QWebSocketProtocol::CC_POLICY_VIOLATED; + QTest::newRow("Close frame with close code PROTOCOL ERROR and no reason") << QString() << QWebSocketProtocol::CC_PROTOCOL_ERROR; + QTest::newRow("Close frame with close code TOO MUCH DATA and no reason") << QString() << QWebSocketProtocol::CC_TOO_MUCH_DATA; + QTest::newRow("Close frame with close code WRONG DATATYPE and no reason") << QString() << QWebSocketProtocol::CC_WRONG_DATATYPE; + 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::goodCloseFrame() @@ -406,24 +454,72 @@ void tst_DataProcessor::goodCloseFrame() buffer.setData(data); buffer.open(QIODevice::ReadOnly); - QSignalSpy spyCloseFrameReceived(&dataProcessor, SIGNAL(closeReceived(QWebSocketProtocol::CloseCode,QString))); - QSignalSpy spyTextFrameReceived(&dataProcessor, SIGNAL(textFrameReceived(QString,bool))); - QSignalSpy spyTextMessageReceived(&dataProcessor, SIGNAL(textMessageReceived(QString))); - QSignalSpy spyBinaryFrameReceived(&dataProcessor, SIGNAL(binaryFrameReceived(QByteArray,bool))); - QSignalSpy spyBinaryMessageReceived(&dataProcessor, SIGNAL(binaryMessageReceived(QByteArray))); + 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(spyCloseFrameReceived.count(), 1); - QCOMPARE(spyTextFrameReceived.count(), 0); - QCOMPARE(spyTextMessageReceived.count(), 0); - QCOMPARE(spyBinaryFrameReceived.count(), 0); - QCOMPARE(spyBinaryMessageReceived.count(), 0); - QList arguments = spyCloseFrameReceived.takeFirst(); + + 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 arguments = closeFrameReceivedSpy.takeFirst(); QCOMPARE(arguments.at(0).value(), closeCode); QCOMPARE(arguments.at(1).toString().length(), payload.length()); buffer.close(); - spyCloseFrameReceived.clear(); - data.clear(); +} + +void tst_DataProcessor::nonCharacterCodes_data() +{ + QTest::addColumn("firstByte"); + QTest::addColumn("secondByte"); + QTest::addColumn("payload"); + QTest::addColumn("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() @@ -438,8 +534,12 @@ void tst_DataProcessor::nonCharacterCodes() QByteArray data; QBuffer buffer; QWebSocketDataProcessor dataProcessor; - QSignalSpy frameSpy(&dataProcessor, SIGNAL(textFrameReceived(QString,bool))); - QSignalSpy messageSpy(&dataProcessor, SIGNAL(textMessageReceived(QString))); + 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))); @@ -448,21 +548,22 @@ void tst_DataProcessor::nonCharacterCodes() buffer.setData(data); buffer.open(QIODevice::ReadOnly); dataProcessor.process(&buffer); - //QEXPECT_FAIL(QTest::currentDataTag(), "Due to QTextCode interpeting non-characters unicode points as invalid (QTBUG-33229).", Abort); - QCOMPARE(frameSpy.count(), 1); - QCOMPARE(messageSpy.count(), 1); + 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 = frameSpy.takeFirst(); + QVariantList arguments = textFrameSpy.takeFirst(); QCOMPARE(arguments.at(0).value().toUtf8(), payload); - arguments = messageSpy.takeFirst(); + QCOMPARE(arguments.at(1).value(), !isContinuationFrame); + arguments = textMessageSpy.takeFirst(); QCOMPARE(arguments.at(0).value().toUtf8(), payload); buffer.close(); - frameSpy.clear(); - messageSpy.clear(); - data.clear(); } } @@ -479,20 +580,32 @@ void tst_DataProcessor::frameTooSmall() //meaning the socket will be closed buffer.setData(data); buffer.open(QIODevice::ReadOnly); - QSignalSpy spy(&dataProcessor, SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode,QString))); + 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(spy.count(), 1); + + 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 arguments = spy.takeFirst(); + + QList arguments = errorSpy.takeFirst(); QCOMPARE(arguments.at(0).value(), QWebSocketProtocol::CC_GOING_AWAY); - spy.clear(); + errorSpy.clear(); + closeSpy.clear(); + pingMessageSpy.clear(); + pongMessageSpy.clear(); textMessageSpy.clear(); binaryMessageSpy.clear(); textFrameSpy.clear(); @@ -505,16 +618,25 @@ void tst_DataProcessor::frameTooSmall() 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(spy.count(), 1); + + 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 = spy.takeFirst(); + + arguments = errorSpy.takeFirst(); QCOMPARE(arguments.at(0).value(), QWebSocketProtocol::CC_GOING_AWAY); buffer.close(); - spy.clear(); + errorSpy.clear(); + closeSpy.clear(); + pingMessageSpy.clear(); + pongMessageSpy.clear(); textMessageSpy.clear(); binaryMessageSpy.clear(); textFrameSpy.clear(); @@ -527,6 +649,7 @@ void tst_DataProcessor::frameTooSmall() data.append((char)(QWebSocketProtocol::OC_TEXT)).append(char(0x0)); buffer.setData(data); buffer.open(QIODevice::ReadOnly); + dataProcessor.process(&buffer); buffer.close(); @@ -536,16 +659,24 @@ void tst_DataProcessor::frameTooSmall() //meaning the socket will be closed buffer.setData(data); buffer.open(QIODevice::ReadOnly); - QSignalSpy spy(&dataProcessor, SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode,QString))); + QSignalSpy errorSpy(&dataProcessor, SIGNAL(errorEncountered(QWebSocketProtocol::CloseCode,QString))); dataProcessor.process(&buffer); - QCOMPARE(spy.count(), 1); + + 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 arguments = spy.takeFirst(); + + QList arguments = errorSpy.takeFirst(); QCOMPARE(arguments.at(0).value(), QWebSocketProtocol::CC_GOING_AWAY); - spy.clear(); + errorSpy.clear(); + closeSpy.clear(); + pingMessageSpy.clear(); + pongMessageSpy.clear(); textMessageSpy.clear(); binaryMessageSpy.clear(); textFrameSpy.clear(); @@ -559,6 +690,10 @@ void tst_DataProcessor::frameTooSmall() 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); @@ -566,7 +701,11 @@ void tst_DataProcessor::frameTooSmall() buffer.close(); data.clear(); - spy.clear(); + + errorSpy.clear(); + closeSpy.clear(); + pingMessageSpy.clear(); + pongMessageSpy.clear(); textMessageSpy.clear(); binaryMessageSpy.clear(); textFrameSpy.clear(); @@ -578,496 +717,305 @@ void tst_DataProcessor::frameTooSmall() buffer.open(QIODevice::ReadOnly); dataProcessor.process(&buffer); - QCOMPARE(spy.count(), 1); + 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 = spy.takeFirst(); + arguments = errorSpy.takeFirst(); QCOMPARE(arguments.at(0).value(), QWebSocketProtocol::CC_GOING_AWAY); - spy.clear(); buffer.close(); } } -void tst_DataProcessor::frameTooBig() -{ - doTest(); -} - -void tst_DataProcessor::invalidHeader() +void tst_DataProcessor::frameTooBig_data() { - doTest(); -} + QTest::addColumn("firstByte"); + QTest::addColumn("secondByte"); + QTest::addColumn("payload"); + QTest::addColumn("isContinuationFrame"); + QTest::addColumn("expectedCloseCode"); -void tst_DataProcessor::invalidControlFrame() -{ - doTest(); -} + quint64 swapped64 = 0; + const char *wireRepresentation = 0; -void tst_DataProcessor::invalidCloseFrame() -{ - doCloseFrameTest(); -} + //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 -void tst_DataProcessor::minimumSizeRequirement() -{ - doTest(); + swapped64 = qToBigEndian(QWebSocketDataProcessor::maxFrameSize() + 1); + wireRepresentation = static_cast(static_cast(&swapped64)); + QTest::newRow("Text frame with payload size > INT_MAX") + << quint8(FIN | QWebSocketProtocol::OC_TEXT) + << quint8(127) + << QByteArray(wireRepresentation, 8).append(QByteArray(32, 'a')) + << false + << QWebSocketProtocol::CC_TOO_MUCH_DATA; + + swapped64 = qToBigEndian(QWebSocketDataProcessor::maxFrameSize() + 1); + wireRepresentation = static_cast(static_cast(&swapped64)); + QTest::newRow("Binary frame with payload size > INT_MAX") + << quint8(FIN | QWebSocketProtocol::OC_BINARY) + << quint8(127) + << QByteArray(wireRepresentation, 8).append(QByteArray(32, 'a')) + << false + << QWebSocketProtocol::CC_TOO_MUCH_DATA; + + swapped64 = qToBigEndian(QWebSocketDataProcessor::maxFrameSize() + 1); + wireRepresentation = static_cast(static_cast(&swapped64)); + QTest::newRow("Continuation frame with payload size > INT_MAX") + << quint8(FIN | QWebSocketProtocol::OC_CONTINUE) + << quint8(127) + << QByteArray(wireRepresentation, 8).append(QByteArray(32, 'a')) + << true + << QWebSocketProtocol::CC_TOO_MUCH_DATA; } -void tst_DataProcessor::invalidPayload() +void tst_DataProcessor::frameTooBig() { doTest(); } -void tst_DataProcessor::invalidPayloadInCloseFrame() +void tst_DataProcessor::invalidHeader_data() { - QFETCH(quint8, firstByte); - QFETCH(quint8, secondByte); - QFETCH(QByteArray, payload); - QFETCH(bool, isContinuationFrame); - QFETCH(QWebSocketProtocol::CloseCode, expectedCloseCode); - - Q_UNUSED(isContinuationFrame) + //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("firstByte"); + QTest::addColumn("secondByte"); + QTest::addColumn("payload"); //superfluous, but present to be able to call doTest(), which expects a payload field + QTest::addColumn("isContinuationFrame"); + QTest::addColumn("expectedCloseCode"); - QByteArray data; - QBuffer buffer; - QWebSocketDataProcessor dataProcessor; - QSignalSpy spy(&dataProcessor, SIGNAL(closeReceived(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))); + //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); - data.append(firstByte).append(secondByte); - data.append(payload); - buffer.setData(data); - buffer.open(QIODevice::ReadOnly); - dataProcessor.process(&buffer); - QCOMPARE(spy.count(), 1); - QCOMPARE(textMessageSpy.count(), 0); - QCOMPARE(binaryMessageSpy.count(), 0); - QCOMPARE(textFrameSpy.count(), 0); - QCOMPARE(binaryFrameSpy.count(), 0); - QVariantList arguments = spy.takeFirst(); - QCOMPARE(arguments.at(0).value(), expectedCloseCode); - buffer.close(); - spy.clear(); - data.clear(); + //invalid opcodes + invalidField("Invalid OpCode 3", QWebSocketProtocol::OC_RESERVED_3); + invalidField("Invalid OpCode 4", QWebSocketProtocol::OC_RESERVED_4); + invalidField("Invalid OpCode 5", QWebSocketProtocol::OC_RESERVED_5); + invalidField("Invalid OpCode 6", QWebSocketProtocol::OC_RESERVED_6); + invalidField("Invalid OpCode 7", QWebSocketProtocol::OC_RESERVED_7); + invalidField("Invalid OpCode B", QWebSocketProtocol::OC_RESERVED_B); + invalidField("Invalid OpCode C", QWebSocketProtocol::OC_RESERVED_C); + invalidField("Invalid OpCode D", QWebSocketProtocol::OC_RESERVED_D); + invalidField("Invalid OpCode E", QWebSocketProtocol::OC_RESERVED_E); + invalidField("Invalid OpCode F", QWebSocketProtocol::OC_RESERVED_F); } -void tst_DataProcessor::incompletePayload() +void tst_DataProcessor::invalidHeader() { doTest(); } -void tst_DataProcessor::incompleteSizeField() +void tst_DataProcessor::invalidControlFrame_data() { - doTest(); -} + QTest::addColumn("firstByte"); + QTest::addColumn("secondByte"); + QTest::addColumn("payload"); + QTest::addColumn("isContinuationFrame"); + QTest::addColumn("expectedCloseCode"); -////////////////////////////////////////////////////////////////////////////////////////// -/// 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 spy(&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))); + QTest::newRow("Close control frame with payload size 126") + << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << quint8(126) << QByteArray() << false << QWebSocketProtocol::CC_PROTOCOL_ERROR; + QTest::newRow("Ping control frame with payload size 126") + << quint8(FIN | QWebSocketProtocol::OC_PING) << quint8(126) << QByteArray() << false << QWebSocketProtocol::CC_PROTOCOL_ERROR; + QTest::newRow("Close control frame with payload size 126") + << quint8(FIN | QWebSocketProtocol::OC_PONG) << quint8(126) << QByteArray() << false << QWebSocketProtocol::CC_PROTOCOL_ERROR; - if (isContinuationFrame) - { - data.append(quint8(QWebSocketProtocol::OC_TEXT)).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(spy.count(), 1); - QCOMPARE(textMessageSpy.count(), 0); - QCOMPARE(binaryMessageSpy.count(), 0); - QCOMPARE(textFrameSpy.count(), isContinuationFrame ? 1 : 0); - QCOMPARE(binaryFrameSpy.count(), 0); - QVariantList arguments = spy.takeFirst(); - QCOMPARE(arguments.at(0).value(), expectedCloseCode); - buffer.close(); - spy.clear(); - data.clear(); + QTest::newRow("Non-final close control frame (fragmented)") + << quint8(QWebSocketProtocol::OC_CLOSE) << quint8(32) << QByteArray() << false << QWebSocketProtocol::CC_PROTOCOL_ERROR; + QTest::newRow("Non-final ping control frame (fragmented)") + << quint8(QWebSocketProtocol::OC_PING) << quint8(32) << QByteArray() << false << QWebSocketProtocol::CC_PROTOCOL_ERROR; + QTest::newRow("Non-final pong control frame (fragmented)") + << quint8(QWebSocketProtocol::OC_PONG) << quint8(32) << QByteArray() << false << QWebSocketProtocol::CC_PROTOCOL_ERROR; } -void tst_DataProcessor::doCloseFrameTest() +void tst_DataProcessor::invalidControlFrame() { - 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 spy(&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(spy.count(), 1); - QCOMPARE(errorSpy.count(), 0); - QCOMPARE(textMessageSpy.count(), 0); - QCOMPARE(binaryMessageSpy.count(), 0); - QCOMPARE(textFrameSpy.count(), 0); - QCOMPARE(binaryFrameSpy.count(), 0); - QVariantList arguments = spy.takeFirst(); - QCOMPARE(arguments.at(0).value(), expectedCloseCode); - buffer.close(); - spy.clear(); - data.clear(); + doTest(); } -QString tst_DataProcessor::opCodeToString(quint8 opCode) +void tst_DataProcessor::invalidCloseFrame_data() { - QString frameType; - switch (opCode) - { - case QWebSocketProtocol::OC_BINARY: - { - frameType = "Binary"; - break; - } - case QWebSocketProtocol::OC_TEXT: - { - frameType = "Text"; - break; - } - case QWebSocketProtocol::OC_PING: - { - frameType = "Ping"; - break; - } - case QWebSocketProtocol::OC_PONG: - { - frameType = "Pong"; - break; - } - case QWebSocketProtocol::OC_CLOSE: - { - frameType = "Close"; - break; - } - case QWebSocketProtocol::OC_CONTINUE: - { - frameType = "Continuation"; - break; - } - case QWebSocketProtocol::OC_RESERVED_3: - { - frameType = "Reserved3"; - break; - } - case QWebSocketProtocol::OC_RESERVED_4: - { - frameType = "Reserved5"; - break; - } - case QWebSocketProtocol::OC_RESERVED_5: - { - frameType = "Reserved5"; - break; - } - case QWebSocketProtocol::OC_RESERVED_6: - { - frameType = "Reserved6"; - break; - } - case QWebSocketProtocol::OC_RESERVED_7: - { - frameType = "Reserved7"; - break; - } - case QWebSocketProtocol::OC_RESERVED_B: - { - frameType = "ReservedB"; - break; - } - case QWebSocketProtocol::OC_RESERVED_C: - { - frameType = "ReservedC"; - break; - } - case QWebSocketProtocol::OC_RESERVED_D: - { - frameType = "ReservedD"; - break; - } - case QWebSocketProtocol::OC_RESERVED_E: - { - frameType = "ReservedE"; - break; - } - case QWebSocketProtocol::OC_RESERVED_F: - { - frameType = "ReservedF"; - break; - } - default: - { - //should never come here - Q_ASSERT(false); - } - } - return frameType; -} + QTest::addColumn("firstByte"); + QTest::addColumn("secondByte"); + QTest::addColumn("payload"); + QTest::addColumn("isContinuationFrame"); + QTest::addColumn("expectedCloseCode"); -void tst_DataProcessor::minimumSize16Bit(quint16 sizeInBytes) -{ - quint16 swapped16 = qToBigEndian(sizeInBytes); - const char *wireRepresentation = static_cast(static_cast(&swapped16)); - QTest::newRow(QString("Text frame with payload size %1, represented in 2 bytes").arg(sizeInBytes).toStdString().data()) - << quint8(FIN | QWebSocketProtocol::OC_TEXT) - << quint8(126) - << QByteArray(wireRepresentation, 2) - << false - << QWebSocketProtocol::CC_PROTOCOL_ERROR; - QTest::newRow(QString("Binary frame with payload size %1, represented in 2 bytes").arg(sizeInBytes).toStdString().data()) - << quint8(FIN | QWebSocketProtocol::OC_BINARY) - << quint8(126) - << QByteArray(wireRepresentation, 2) - << false - << QWebSocketProtocol::CC_PROTOCOL_ERROR; - QTest::newRow(QString("Continuation frame with payload size %1, represented in 2 bytes").arg(sizeInBytes).toStdString().data()) - << quint8(FIN | QWebSocketProtocol::OC_CONTINUE) - << quint8(126) - << QByteArray(wireRepresentation, 2) - << true - << QWebSocketProtocol::CC_PROTOCOL_ERROR; + QTest::newRow("Close control frame with payload size 1") + << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << quint8(1) << QByteArray(1, 'a') << false << QWebSocketProtocol::CC_PROTOCOL_ERROR; + quint16 swapped = qToBigEndian(QWebSocketProtocol::CC_ABNORMAL_DISCONNECTION); + const char *wireRepresentation = static_cast(static_cast(&swapped)); + + //Not allowed per RFC 6455 (see para 7.4.1) + QTest::newRow("Close control frame close code ABNORMAL DISCONNECTION") + << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << + quint8(2) << + QByteArray(wireRepresentation, 2) << + false << + QWebSocketProtocol::CC_PROTOCOL_ERROR; + swapped = qToBigEndian(QWebSocketProtocol::CC_MISSING_STATUS_CODE); + wireRepresentation = static_cast(static_cast(&swapped)); + //Not allowed per RFC 6455 (see para 7.4.1) + QTest::newRow("Close control frame close code MISSING STATUS CODE") + << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << + quint8(2) << + QByteArray(wireRepresentation, 2) << + false << + QWebSocketProtocol::CC_PROTOCOL_ERROR; + swapped = qToBigEndian(1004); + wireRepresentation = static_cast(static_cast(&swapped)); + QTest::newRow("Close control frame close code 1004") + << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << + quint8(2) << + QByteArray(wireRepresentation, 2) << + false << + QWebSocketProtocol::CC_PROTOCOL_ERROR; + swapped = qToBigEndian(QWebSocketProtocol::CC_TLS_HANDSHAKE_FAILED); + wireRepresentation = static_cast(static_cast(&swapped)); + //Not allowed per RFC 6455 (see para 7.4.1) + QTest::newRow("Close control frame close code TLS HANDSHAKE FAILED") + << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << + quint8(2) << + QByteArray(wireRepresentation, 2) << + false << + QWebSocketProtocol::CC_PROTOCOL_ERROR; + swapped = qToBigEndian(0); + wireRepresentation = static_cast(static_cast(&swapped)); + QTest::newRow("Close control frame close code 0") + << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << + quint8(2) << + QByteArray(wireRepresentation, 2) << + false << + QWebSocketProtocol::CC_PROTOCOL_ERROR; + swapped = qToBigEndian(999); + wireRepresentation = static_cast(static_cast(&swapped)); + QTest::newRow("Close control frame close code 999") + << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << + quint8(2) << + QByteArray(wireRepresentation, 2) << + false << + QWebSocketProtocol::CC_PROTOCOL_ERROR; + swapped = qToBigEndian(1012); + wireRepresentation = static_cast(static_cast(&swapped)); + QTest::newRow("Close control frame close code 1012") + << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << + quint8(2) << + QByteArray(wireRepresentation, 2) << + false << + QWebSocketProtocol::CC_PROTOCOL_ERROR; + swapped = qToBigEndian(1013); + wireRepresentation = static_cast(static_cast(&swapped)); + QTest::newRow("Close control frame close code 1013") + << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << + quint8(2) << + QByteArray(wireRepresentation, 2) << + false << + QWebSocketProtocol::CC_PROTOCOL_ERROR; + swapped = qToBigEndian(1014); + wireRepresentation = static_cast(static_cast(&swapped)); + QTest::newRow("Close control frame close code 1014") + << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << + quint8(2) << + QByteArray(wireRepresentation, 2) << + false << + QWebSocketProtocol::CC_PROTOCOL_ERROR; + swapped = qToBigEndian(1100); + wireRepresentation = static_cast(static_cast(&swapped)); + QTest::newRow("Close control frame close code 1100") + << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << + quint8(2) << + QByteArray(wireRepresentation, 2) << + false << + QWebSocketProtocol::CC_PROTOCOL_ERROR; + swapped = qToBigEndian(2000); + wireRepresentation = static_cast(static_cast(&swapped)); + QTest::newRow("Close control frame close code 2000") + << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << + quint8(2) << + QByteArray(wireRepresentation, 2) << + false << + QWebSocketProtocol::CC_PROTOCOL_ERROR; + swapped = qToBigEndian(2999); + wireRepresentation = static_cast(static_cast(&swapped)); + QTest::newRow("Close control frame close code 2999") + << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << + quint8(2) << + QByteArray(wireRepresentation, 2) << + false << + QWebSocketProtocol::CC_PROTOCOL_ERROR; + swapped = qToBigEndian(5000); + wireRepresentation = static_cast(static_cast(&swapped)); + QTest::newRow("Close control frame close code 5000") + << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << + quint8(2) << + QByteArray(wireRepresentation, 2) << + false << + QWebSocketProtocol::CC_PROTOCOL_ERROR; + swapped = qToBigEndian(65535u); + wireRepresentation = static_cast(static_cast(&swapped)); + QTest::newRow("Close control frame close code 65535") + << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << + quint8(2) << + QByteArray(wireRepresentation, 2) << + false << + QWebSocketProtocol::CC_PROTOCOL_ERROR; } -void tst_DataProcessor::minimumSize64Bit(quint64 sizeInBytes) +void tst_DataProcessor::invalidCloseFrame() { - quint64 swapped64 = qToBigEndian(sizeInBytes); - const char *wireRepresentation = static_cast(static_cast(&swapped64)); - - QTest::newRow(QString("Text frame with payload size %1, represented in 8 bytes").arg(sizeInBytes).toStdString().data()) - << quint8(FIN | QWebSocketProtocol::OC_TEXT) - << quint8(127) - << QByteArray(wireRepresentation, 8) - << false - << QWebSocketProtocol::CC_PROTOCOL_ERROR; - - QTest::newRow(QString("Binary frame with payload size %1, represented in 8 bytes").arg(sizeInBytes).toStdString().data()) - << quint8(FIN | QWebSocketProtocol::OC_BINARY) - << quint8(127) - << QByteArray(wireRepresentation, 8) - << false - << QWebSocketProtocol::CC_PROTOCOL_ERROR; - - QTest::newRow(QString("Continuation frame with payload size %1, represented in 8 bytes").arg(sizeInBytes).toStdString().data()) - << quint8(FIN | QWebSocketProtocol::OC_CONTINUE) - << quint8(127) - << QByteArray(wireRepresentation, 8) - << true - << QWebSocketProtocol::CC_PROTOCOL_ERROR; + doCloseFrameTest(); } -void tst_DataProcessor::invalidUTF8(const char *dataTag, const char *utf8Sequence, bool isCloseFrame) +void tst_DataProcessor::minimumSizeRequirement_data() { - QByteArray payload = QByteArray::fromHex(utf8Sequence); + QTest::addColumn("firstByte"); + QTest::addColumn("secondByte"); + QTest::addColumn("payload"); + QTest::addColumn("isContinuationFrame"); + QTest::addColumn("expectedCloseCode"); - if (isCloseFrame) - { - quint16 closeCode = qToBigEndian(QWebSocketProtocol::CC_NORMAL); - const char *wireRepresentation = static_cast(static_cast(&closeCode)); - QTest::newRow(QString("Close frame with invalid UTF8-sequence: %1").arg(dataTag).toStdString().data()) - << quint8(FIN | QWebSocketProtocol::OC_CLOSE) - << quint8(payload.length() + 2) - << QByteArray(wireRepresentation, 2).append(payload) - << false - << QWebSocketProtocol::CC_WRONG_DATATYPE; - } - else - { - QTest::newRow(QString("Text frame with invalid UTF8-sequence: %1").arg(dataTag).toStdString().data()) - << quint8(FIN | QWebSocketProtocol::OC_TEXT) - << quint8(payload.length()) - << payload - << false - << QWebSocketProtocol::CC_WRONG_DATATYPE; + minimumSize16Bit(0); + minimumSize16Bit(64); + minimumSize16Bit(125); - QTest::newRow(QString("Continuation text frame with invalid UTF8-sequence: %1").arg(dataTag).toStdString().data()) - << quint8(FIN | QWebSocketProtocol::OC_CONTINUE) - << quint8(payload.length()) - << payload - << true - << QWebSocketProtocol::CC_WRONG_DATATYPE; - } + 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::invalidField(const char *dataTag, quint8 invalidFieldValue) +void tst_DataProcessor::minimumSizeRequirement() { - QTest::newRow(dataTag) << quint8(FIN | invalidFieldValue) - << quint8(0x00) - << QByteArray() - << false - << QWebSocketProtocol::CC_PROTOCOL_ERROR; - QTest::newRow(QString(dataTag).append(" with continuation frame").toStdString().data()) - << quint8(FIN | invalidFieldValue) - << quint8(0x00) - << QByteArray() - << true - << QWebSocketProtocol::CC_PROTOCOL_ERROR; + doTest(); } -void tst_DataProcessor::incompleteFrame(quint8 controlCode, quint64 indicatedSize, quint64 actualPayloadSize) +void tst_DataProcessor::invalidPayload_data(bool isControlFrame) { - QVERIFY(!QWebSocketProtocol::isOpCodeReserved(QWebSocketProtocol::OpCode(controlCode))); - QVERIFY(indicatedSize > actualPayloadSize); - - QString frameType = opCodeToString(controlCode); - QByteArray firstFrame; - - if (indicatedSize < 126) - { - QTest::newRow(frameType.append(QString(" frame with payload size %1, but only %2 bytes of data").arg(indicatedSize).arg(actualPayloadSize)).toStdString().data()) - << quint8(FIN | controlCode) - << quint8(indicatedSize) - << firstFrame.append(QByteArray(actualPayloadSize, 'a')) - << (controlCode == QWebSocketProtocol::OC_CONTINUE) - << QWebSocketProtocol::CC_GOING_AWAY; - } - else if (indicatedSize <= 0xFFFFu) - { - quint16 swapped16 = qToBigEndian(static_cast(indicatedSize)); - const char *wireRepresentation = static_cast(static_cast(&swapped16)); - QTest::newRow(frameType.append(QString(" frame with payload size %1, but only %2 bytes of data").arg(indicatedSize).arg(actualPayloadSize)).toStdString().data()) - << quint8(FIN | controlCode) - << quint8(126) - << firstFrame.append(QByteArray(wireRepresentation, 2).append(QByteArray(actualPayloadSize, 'a'))) - << (controlCode == QWebSocketProtocol::OC_CONTINUE) - << QWebSocketProtocol::CC_GOING_AWAY; - } - else - { - quint64 swapped64 = qToBigEndian(indicatedSize); - const char *wireRepresentation = static_cast(static_cast(&swapped64)); - QTest::newRow(frameType.append(QString(" frame with payload size %1, but only %2 bytes of data").arg(indicatedSize).arg(actualPayloadSize)).toStdString().data()) - << quint8(FIN | controlCode) - << quint8(127) - << firstFrame.append(QByteArray(wireRepresentation, 8).append(QByteArray(actualPayloadSize, 'a'))) - << (controlCode == QWebSocketProtocol::OC_CONTINUE) - << QWebSocketProtocol::CC_GOING_AWAY; - } -} - -void tst_DataProcessor::nonCharacterSequence(const char *sequence) -{ - QByteArray utf8Sequence = QByteArray::fromHex(sequence); - //TODO: qstrdup - memory leak! qstrdup is necessary because once QString goes out of scope we have garbage - const char *tagName = qstrdup(qPrintable(QString("Text frame with payload containing the non-control character sequence 0x%1").arg(QString(sequence)))); - const char *tagName2 = qstrdup(qPrintable(QString("Continuation frame with payload containing the non-control character sequence 0x%1").arg(QString(sequence)))); - - QTest::newRow(tagName) - << quint8(FIN | QWebSocketProtocol::OC_TEXT) - << quint8(utf8Sequence.size()) - << utf8Sequence - << false; - - QTest::newRow(tagName2) - << quint8(FIN | QWebSocketProtocol::OC_CONTINUE) - << quint8(utf8Sequence.size()) - << utf8Sequence - << true; -} - -void tst_DataProcessor::insertIncompleteSizeFieldTest(quint8 payloadCode, quint8 numBytesFollowing) -{ - QTest::newRow(QString("Text frame with payload size %1, with %2 bytes following.").arg(payloadCode).arg(numBytesFollowing).toStdString().data()) - << quint8(FIN | QWebSocketProtocol::OC_TEXT) - << quint8(payloadCode) - << QByteArray(numBytesFollowing, quint8(1)) - << false - << QWebSocketProtocol::CC_GOING_AWAY; - QTest::newRow(QString("Binary frame with payload size %1, with %2 bytes following.").arg(payloadCode).arg(numBytesFollowing).toStdString().data()) - << quint8(FIN | QWebSocketProtocol::OC_BINARY) - << quint8(payloadCode) - << QByteArray(numBytesFollowing, quint8(1)) - << false - << QWebSocketProtocol::CC_GOING_AWAY; - QTest::newRow(QString("Continuation frame with payload size %1, with %2 bytes following.").arg(payloadCode).arg(numBytesFollowing).toStdString().data()) - << quint8(FIN | QWebSocketProtocol::OC_CONTINUE) - << quint8(payloadCode) - << QByteArray(numBytesFollowing, quint8(1)) - << true - << QWebSocketProtocol::CC_GOING_AWAY; -} - -////////////////////////////////////////////////////////////////////////////////// -//// TEST DATA -////////////////////////////////////////////////////////////////////////////////// -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("firstByte"); - QTest::addColumn("secondByte"); - QTest::addColumn("payload"); //superfluous, but present to be able to call doTest(), which expects a payload field - QTest::addColumn("isContinuationFrame"); - QTest::addColumn("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::OC_RESERVED_3); - invalidField("Invalid OpCode 4", QWebSocketProtocol::OC_RESERVED_4); - invalidField("Invalid OpCode 5", QWebSocketProtocol::OC_RESERVED_5); - invalidField("Invalid OpCode 6", QWebSocketProtocol::OC_RESERVED_6); - invalidField("Invalid OpCode 7", QWebSocketProtocol::OC_RESERVED_7); - invalidField("Invalid OpCode B", QWebSocketProtocol::OC_RESERVED_B); - invalidField("Invalid OpCode C", QWebSocketProtocol::OC_RESERVED_C); - invalidField("Invalid OpCode D", QWebSocketProtocol::OC_RESERVED_D); - invalidField("Invalid OpCode E", QWebSocketProtocol::OC_RESERVED_E); - invalidField("Invalid OpCode F", QWebSocketProtocol::OC_RESERVED_F); -} - -void tst_DataProcessor::invalidPayloadInCloseFrame_data() -{ - invalidPayload_data(true); -} - -void tst_DataProcessor::invalidPayload_data(bool isControlFrame) -{ - QTest::addColumn("firstByte"); - QTest::addColumn("secondByte"); - QTest::addColumn("payload"); - QTest::addColumn("isContinuationFrame"); - QTest::addColumn("expectedCloseCode"); + QTest::addColumn("firstByte"); + QTest::addColumn("secondByte"); + QTest::addColumn("payload"); + QTest::addColumn("isContinuationFrame"); + QTest::addColumn("expectedCloseCode"); //6.3: invalid UTF-8 sequence invalidUTF8("case 6.3.1", "cebae1bdb9cf83cebcceb5eda080656469746564", isControlFrame); @@ -1174,34 +1122,57 @@ void tst_DataProcessor::invalidPayload_data(bool isControlFrame) invalidUTF8("case 6.21.8", "edafbfedbfbf", isControlFrame); } -void tst_DataProcessor::minimumSizeRequirement_data() +void tst_DataProcessor::invalidPayload() { - QTest::addColumn("firstByte"); - QTest::addColumn("secondByte"); - QTest::addColumn("payload"); - QTest::addColumn("isContinuationFrame"); - QTest::addColumn("expectedCloseCode"); + doTest(); +} - minimumSize16Bit(0); - minimumSize16Bit(64); - minimumSize16Bit(125); +void tst_DataProcessor::invalidPayloadInCloseFrame_data() +{ + invalidPayload_data(true); +} - 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::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(), expectedCloseCode); + buffer.close(); } -void tst_DataProcessor::invalidControlFrame_data() +void tst_DataProcessor::incompletePayload_data() { QTest::addColumn("firstByte"); QTest::addColumn("secondByte"); @@ -1209,23 +1180,30 @@ void tst_DataProcessor::invalidControlFrame_data() QTest::addColumn("isContinuationFrame"); QTest::addColumn("expectedCloseCode"); + incompleteFrame(QWebSocketProtocol::OC_TEXT, 125, 0); + incompleteFrame(QWebSocketProtocol::OC_TEXT, 64, 32); + incompleteFrame(QWebSocketProtocol::OC_TEXT, 256, 32); + incompleteFrame(QWebSocketProtocol::OC_TEXT, 128000, 32); + incompleteFrame(QWebSocketProtocol::OC_BINARY, 125, 0); + incompleteFrame(QWebSocketProtocol::OC_BINARY, 64, 32); + incompleteFrame(QWebSocketProtocol::OC_BINARY, 256, 32); + incompleteFrame(QWebSocketProtocol::OC_BINARY, 128000, 32); + incompleteFrame(QWebSocketProtocol::OC_CONTINUE, 125, 0); + incompleteFrame(QWebSocketProtocol::OC_CONTINUE, 64, 32); + incompleteFrame(QWebSocketProtocol::OC_CONTINUE, 256, 32); + incompleteFrame(QWebSocketProtocol::OC_CONTINUE, 128000, 32); - QTest::newRow("Close control frame with payload size 126") - << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << quint8(126) << QByteArray() << false << QWebSocketProtocol::CC_PROTOCOL_ERROR; - QTest::newRow("Ping control frame with payload size 126") - << quint8(FIN | QWebSocketProtocol::OC_PING) << quint8(126) << QByteArray() << false << QWebSocketProtocol::CC_PROTOCOL_ERROR; - QTest::newRow("Close control frame with payload size 126") - << quint8(FIN | QWebSocketProtocol::OC_PONG) << quint8(126) << QByteArray() << false << QWebSocketProtocol::CC_PROTOCOL_ERROR; + incompleteFrame(QWebSocketProtocol::OC_CLOSE, 64, 32); + incompleteFrame(QWebSocketProtocol::OC_PING, 64, 32); + incompleteFrame(QWebSocketProtocol::OC_PONG, 64, 32); +} - QTest::newRow("Non-final close control frame (fragmented)") - << quint8(QWebSocketProtocol::OC_CLOSE) << quint8(32) << QByteArray() << false << QWebSocketProtocol::CC_PROTOCOL_ERROR; - QTest::newRow("Non-final ping control frame (fragmented)") - << quint8(QWebSocketProtocol::OC_PING) << quint8(32) << QByteArray() << false << QWebSocketProtocol::CC_PROTOCOL_ERROR; - QTest::newRow("Non-final pong control frame (fragmented)") - << quint8(QWebSocketProtocol::OC_PONG) << quint8(32) << QByteArray() << false << QWebSocketProtocol::CC_PROTOCOL_ERROR; +void tst_DataProcessor::incompletePayload() +{ + doTest(); } -void tst_DataProcessor::invalidCloseFrame_data() +void tst_DataProcessor::incompleteSizeField_data() { QTest::addColumn("firstByte"); QTest::addColumn("secondByte"); @@ -1233,262 +1211,381 @@ void tst_DataProcessor::invalidCloseFrame_data() QTest::addColumn("isContinuationFrame"); QTest::addColumn("expectedCloseCode"); - QTest::newRow("Close control frame with payload size 1") - << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << quint8(1) << QByteArray(1, 'a') << false << QWebSocketProtocol::CC_PROTOCOL_ERROR; - quint16 swapped = qToBigEndian(QWebSocketProtocol::CC_ABNORMAL_DISCONNECTION); - const char *wireRepresentation = static_cast(static_cast(&swapped)); + ////////////////////////////////////////////////////////////////////////////////////////////////// + //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); - //Not allowed per RFC 6455 (see para 7.4.1) - QTest::newRow("Close control frame close code ABNORMAL DISCONNECTION") - << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << - quint8(2) << - QByteArray(wireRepresentation, 2) << - false << - QWebSocketProtocol::CC_PROTOCOL_ERROR; - swapped = qToBigEndian(QWebSocketProtocol::CC_MISSING_STATUS_CODE); - wireRepresentation = static_cast(static_cast(&swapped)); - //Not allowed per RFC 6455 (see para 7.4.1) - QTest::newRow("Close control frame close code MISSING STATUS CODE") - << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << - quint8(2) << - QByteArray(wireRepresentation, 2) << - false << - QWebSocketProtocol::CC_PROTOCOL_ERROR; - swapped = qToBigEndian(1004); - wireRepresentation = static_cast(static_cast(&swapped)); - QTest::newRow("Close control frame close code 1004") - << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << - quint8(2) << - QByteArray(wireRepresentation, 2) << - false << - QWebSocketProtocol::CC_PROTOCOL_ERROR; - swapped = qToBigEndian(QWebSocketProtocol::CC_TLS_HANDSHAKE_FAILED); - wireRepresentation = static_cast(static_cast(&swapped)); - //Not allowed per RFC 6455 (see para 7.4.1) - QTest::newRow("Close control frame close code TLS HANDSHAKE FAILED") - << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << - quint8(2) << - QByteArray(wireRepresentation, 2) << - false << - QWebSocketProtocol::CC_PROTOCOL_ERROR; - swapped = qToBigEndian(0); - wireRepresentation = static_cast(static_cast(&swapped)); - QTest::newRow("Close control frame close code 0") - << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << - quint8(2) << - QByteArray(wireRepresentation, 2) << - false << - QWebSocketProtocol::CC_PROTOCOL_ERROR; - swapped = qToBigEndian(999); - wireRepresentation = static_cast(static_cast(&swapped)); - QTest::newRow("Close control frame close code 999") - << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << - quint8(2) << - QByteArray(wireRepresentation, 2) << - false << - QWebSocketProtocol::CC_PROTOCOL_ERROR; - swapped = qToBigEndian(1012); - wireRepresentation = static_cast(static_cast(&swapped)); - QTest::newRow("Close control frame close code 1012") - << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << - quint8(2) << - QByteArray(wireRepresentation, 2) << - false << - QWebSocketProtocol::CC_PROTOCOL_ERROR; - swapped = qToBigEndian(1013); - wireRepresentation = static_cast(static_cast(&swapped)); - QTest::newRow("Close control frame close code 1013") - << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << - quint8(2) << - QByteArray(wireRepresentation, 2) << - false << - QWebSocketProtocol::CC_PROTOCOL_ERROR; - swapped = qToBigEndian(1014); - wireRepresentation = static_cast(static_cast(&swapped)); - QTest::newRow("Close control frame close code 1014") - << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << - quint8(2) << - QByteArray(wireRepresentation, 2) << - false << - QWebSocketProtocol::CC_PROTOCOL_ERROR; - swapped = qToBigEndian(1100); - wireRepresentation = static_cast(static_cast(&swapped)); - QTest::newRow("Close control frame close code 1100") - << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << - quint8(2) << - QByteArray(wireRepresentation, 2) << - false << - QWebSocketProtocol::CC_PROTOCOL_ERROR; - swapped = qToBigEndian(2000); - wireRepresentation = static_cast(static_cast(&swapped)); - QTest::newRow("Close control frame close code 2000") - << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << - quint8(2) << - QByteArray(wireRepresentation, 2) << - false << - QWebSocketProtocol::CC_PROTOCOL_ERROR; - swapped = qToBigEndian(2999); - wireRepresentation = static_cast(static_cast(&swapped)); - QTest::newRow("Close control frame close code 2999") - << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << - quint8(2) << - QByteArray(wireRepresentation, 2) << - false << - QWebSocketProtocol::CC_PROTOCOL_ERROR; - swapped = qToBigEndian(5000); - wireRepresentation = static_cast(static_cast(&swapped)); - QTest::newRow("Close control frame close code 5000") - << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << - quint8(2) << - QByteArray(wireRepresentation, 2) << - false << - QWebSocketProtocol::CC_PROTOCOL_ERROR; - swapped = qToBigEndian(65535u); - wireRepresentation = static_cast(static_cast(&swapped)); - QTest::newRow("Close control frame close code 65535") - << quint8(FIN | QWebSocketProtocol::OC_CLOSE) << - quint8(2) << - QByteArray(wireRepresentation, 2) << - false << - QWebSocketProtocol::CC_PROTOCOL_ERROR; + ////////////////////////////////////////////////////////////////////////////////////////////////// + //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::OC_TEXT)).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(), 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(), expectedCloseCode); + buffer.close(); +} + +QString tst_DataProcessor::opCodeToString(quint8 opCode) +{ + QString frameType; + switch (opCode) + { + case QWebSocketProtocol::OC_BINARY: + { + frameType = "Binary"; + break; + } + case QWebSocketProtocol::OC_TEXT: + { + frameType = "Text"; + break; + } + case QWebSocketProtocol::OC_PING: + { + frameType = "Ping"; + break; + } + case QWebSocketProtocol::OC_PONG: + { + frameType = "Pong"; + break; + } + case QWebSocketProtocol::OC_CLOSE: + { + frameType = "Close"; + break; + } + case QWebSocketProtocol::OC_CONTINUE: + { + frameType = "Continuation"; + break; + } + case QWebSocketProtocol::OC_RESERVED_3: + { + frameType = "Reserved3"; + break; + } + case QWebSocketProtocol::OC_RESERVED_4: + { + frameType = "Reserved5"; + break; + } + case QWebSocketProtocol::OC_RESERVED_5: + { + frameType = "Reserved5"; + break; + } + case QWebSocketProtocol::OC_RESERVED_6: + { + frameType = "Reserved6"; + break; + } + case QWebSocketProtocol::OC_RESERVED_7: + { + frameType = "Reserved7"; + break; + } + case QWebSocketProtocol::OC_RESERVED_B: + { + frameType = "ReservedB"; + break; + } + case QWebSocketProtocol::OC_RESERVED_C: + { + frameType = "ReservedC"; + break; + } + case QWebSocketProtocol::OC_RESERVED_D: + { + frameType = "ReservedD"; + break; + } + case QWebSocketProtocol::OC_RESERVED_E: + { + frameType = "ReservedE"; + break; + } + case QWebSocketProtocol::OC_RESERVED_F: + { + frameType = "ReservedF"; + break; + } + default: + { + //should never come here + Q_ASSERT(false); + } + } + return frameType; +} + +void tst_DataProcessor::minimumSize16Bit(quint16 sizeInBytes) +{ + quint16 swapped16 = qToBigEndian(sizeInBytes); + const char *wireRepresentation = static_cast(static_cast(&swapped16)); + QTest::newRow(QString("Text frame with payload size %1, represented in 2 bytes").arg(sizeInBytes).toStdString().data()) + << quint8(FIN | QWebSocketProtocol::OC_TEXT) + << quint8(126) + << QByteArray(wireRepresentation, 2) + << false + << QWebSocketProtocol::CC_PROTOCOL_ERROR; + QTest::newRow(QString("Binary frame with payload size %1, represented in 2 bytes").arg(sizeInBytes).toStdString().data()) + << quint8(FIN | QWebSocketProtocol::OC_BINARY) + << quint8(126) + << QByteArray(wireRepresentation, 2) + << false + << QWebSocketProtocol::CC_PROTOCOL_ERROR; + QTest::newRow(QString("Continuation frame with payload size %1, represented in 2 bytes").arg(sizeInBytes).toStdString().data()) + << quint8(FIN | QWebSocketProtocol::OC_CONTINUE) + << quint8(126) + << QByteArray(wireRepresentation, 2) + << true + << QWebSocketProtocol::CC_PROTOCOL_ERROR; } -void tst_DataProcessor::incompleteSizeField_data() +void tst_DataProcessor::minimumSize64Bit(quint64 sizeInBytes) { - QTest::addColumn("firstByte"); - QTest::addColumn("secondByte"); - QTest::addColumn("payload"); - QTest::addColumn("isContinuationFrame"); - QTest::addColumn("expectedCloseCode"); + quint64 swapped64 = qToBigEndian(sizeInBytes); + const char *wireRepresentation = static_cast(static_cast(&swapped64)); - ////////////////////////////////////////////////////////////////////////////////////////////////// - //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); + QTest::newRow(QString("Text frame with payload size %1, represented in 8 bytes").arg(sizeInBytes).toStdString().data()) + << quint8(FIN | QWebSocketProtocol::OC_TEXT) + << quint8(127) + << QByteArray(wireRepresentation, 8) + << false + << QWebSocketProtocol::CC_PROTOCOL_ERROR; - ////////////////////////////////////////////////////////////////////////////////////////////////// - //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); + QTest::newRow(QString("Binary frame with payload size %1, represented in 8 bytes").arg(sizeInBytes).toStdString().data()) + << quint8(FIN | QWebSocketProtocol::OC_BINARY) + << quint8(127) + << QByteArray(wireRepresentation, 8) + << false + << QWebSocketProtocol::CC_PROTOCOL_ERROR; + + QTest::newRow(QString("Continuation frame with payload size %1, represented in 8 bytes").arg(sizeInBytes).toStdString().data()) + << quint8(FIN | QWebSocketProtocol::OC_CONTINUE) + << quint8(127) + << QByteArray(wireRepresentation, 8) + << true + << QWebSocketProtocol::CC_PROTOCOL_ERROR; } -void tst_DataProcessor::nonCharacterCodes_data() +void tst_DataProcessor::invalidUTF8(const char *dataTag, const char *utf8Sequence, bool isCloseFrame) { - QTest::addColumn("firstByte"); - QTest::addColumn("secondByte"); - QTest::addColumn("payload"); - QTest::addColumn("isContinuationFrame"); + QByteArray payload = QByteArray::fromHex(utf8Sequence); - 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"); + if (isCloseFrame) + { + quint16 closeCode = qToBigEndian(QWebSocketProtocol::CC_NORMAL); + const char *wireRepresentation = static_cast(static_cast(&closeCode)); + QTest::newRow(QString("Close frame with invalid UTF8-sequence: %1").arg(dataTag).toStdString().data()) + << quint8(FIN | QWebSocketProtocol::OC_CLOSE) + << quint8(payload.length() + 2) + << QByteArray(wireRepresentation, 2).append(payload) + << false + << QWebSocketProtocol::CC_WRONG_DATATYPE; + } + else + { + QTest::newRow(QString("Text frame with invalid UTF8-sequence: %1").arg(dataTag).toStdString().data()) + << quint8(FIN | QWebSocketProtocol::OC_TEXT) + << quint8(payload.length()) + << payload + << false + << QWebSocketProtocol::CC_WRONG_DATATYPE; + + QTest::newRow(QString("Continuation text frame with invalid UTF8-sequence: %1").arg(dataTag).toStdString().data()) + << quint8(FIN | QWebSocketProtocol::OC_CONTINUE) + << quint8(payload.length()) + << payload + << true + << QWebSocketProtocol::CC_WRONG_DATATYPE; + } } -void tst_DataProcessor::incompletePayload_data() +void tst_DataProcessor::invalidField(const char *dataTag, quint8 invalidFieldValue) { - QTest::addColumn("firstByte"); - QTest::addColumn("secondByte"); - QTest::addColumn("payload"); - QTest::addColumn("isContinuationFrame"); - QTest::addColumn("expectedCloseCode"); + QTest::newRow(dataTag) << quint8(FIN | invalidFieldValue) + << quint8(0x00) + << QByteArray() + << false + << QWebSocketProtocol::CC_PROTOCOL_ERROR; + QTest::newRow(QString(dataTag).append(" with continuation frame").toStdString().data()) + << quint8(FIN | invalidFieldValue) + << quint8(0x00) + << QByteArray() + << true + << QWebSocketProtocol::CC_PROTOCOL_ERROR; +} - incompleteFrame(QWebSocketProtocol::OC_TEXT, 125, 0); - incompleteFrame(QWebSocketProtocol::OC_TEXT, 64, 32); - incompleteFrame(QWebSocketProtocol::OC_TEXT, 256, 32); - incompleteFrame(QWebSocketProtocol::OC_TEXT, 128000, 32); - incompleteFrame(QWebSocketProtocol::OC_BINARY, 125, 0); - incompleteFrame(QWebSocketProtocol::OC_BINARY, 64, 32); - incompleteFrame(QWebSocketProtocol::OC_BINARY, 256, 32); - incompleteFrame(QWebSocketProtocol::OC_BINARY, 128000, 32); - incompleteFrame(QWebSocketProtocol::OC_CONTINUE, 125, 0); - incompleteFrame(QWebSocketProtocol::OC_CONTINUE, 64, 32); - incompleteFrame(QWebSocketProtocol::OC_CONTINUE, 256, 32); - incompleteFrame(QWebSocketProtocol::OC_CONTINUE, 128000, 32); +void tst_DataProcessor::incompleteFrame(quint8 controlCode, quint64 indicatedSize, quint64 actualPayloadSize) +{ + QVERIFY(!QWebSocketProtocol::isOpCodeReserved(QWebSocketProtocol::OpCode(controlCode))); + QVERIFY(indicatedSize > actualPayloadSize); - incompleteFrame(QWebSocketProtocol::OC_CLOSE, 64, 32); - incompleteFrame(QWebSocketProtocol::OC_PING, 64, 32); - incompleteFrame(QWebSocketProtocol::OC_PONG, 64, 32); + QString frameType = opCodeToString(controlCode); + QByteArray firstFrame; + + if (indicatedSize < 126) + { + QTest::newRow(frameType.append(QString(" frame with payload size %1, but only %2 bytes of data").arg(indicatedSize).arg(actualPayloadSize)).toStdString().data()) + << quint8(FIN | controlCode) + << quint8(indicatedSize) + << firstFrame.append(QByteArray(actualPayloadSize, 'a')) + << (controlCode == QWebSocketProtocol::OC_CONTINUE) + << QWebSocketProtocol::CC_GOING_AWAY; + } + else if (indicatedSize <= 0xFFFFu) + { + quint16 swapped16 = qToBigEndian(static_cast(indicatedSize)); + const char *wireRepresentation = static_cast(static_cast(&swapped16)); + QTest::newRow(frameType.append(QString(" frame with payload size %1, but only %2 bytes of data").arg(indicatedSize).arg(actualPayloadSize)).toStdString().data()) + << quint8(FIN | controlCode) + << quint8(126) + << firstFrame.append(QByteArray(wireRepresentation, 2).append(QByteArray(actualPayloadSize, 'a'))) + << (controlCode == QWebSocketProtocol::OC_CONTINUE) + << QWebSocketProtocol::CC_GOING_AWAY; + } + else + { + quint64 swapped64 = qToBigEndian(indicatedSize); + const char *wireRepresentation = static_cast(static_cast(&swapped64)); + QTest::newRow(frameType.append(QString(" frame with payload size %1, but only %2 bytes of data").arg(indicatedSize).arg(actualPayloadSize)).toStdString().data()) + << quint8(FIN | controlCode) + << quint8(127) + << firstFrame.append(QByteArray(wireRepresentation, 8).append(QByteArray(actualPayloadSize, 'a'))) + << (controlCode == QWebSocketProtocol::OC_CONTINUE) + << QWebSocketProtocol::CC_GOING_AWAY; + } } -void tst_DataProcessor::frameTooBig_data() +void tst_DataProcessor::nonCharacterSequence(const char *sequence) { - QTest::addColumn("firstByte"); - QTest::addColumn("secondByte"); - QTest::addColumn("payload"); - QTest::addColumn("isContinuationFrame"); - QTest::addColumn("expectedCloseCode"); + QByteArray utf8Sequence = QByteArray::fromHex(sequence); + //TODO: qstrdup - memory leak! qstrdup is necessary because once QString goes out of scope we have garbage + const char *tagName = qstrdup(qPrintable(QString("Text frame with payload containing the non-control character sequence 0x%1").arg(QString(sequence)))); + const char *tagName2 = qstrdup(qPrintable(QString("Continuation frame with payload containing the non-control character sequence 0x%1").arg(QString(sequence)))); - quint64 swapped64 = 0; - const char *wireRepresentation = 0; + QTest::newRow(tagName) + << quint8(FIN | QWebSocketProtocol::OC_TEXT) + << quint8(utf8Sequence.size()) + << utf8Sequence + << false; - //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 + QTest::newRow(tagName2) + << quint8(FIN | QWebSocketProtocol::OC_CONTINUE) + << quint8(utf8Sequence.size()) + << utf8Sequence + << true; +} - swapped64 = qToBigEndian(QWebSocketDataProcessor::maxFrameSize() + 1); - wireRepresentation = static_cast(static_cast(&swapped64)); - QTest::newRow("Text frame with payload size > INT_MAX") +void tst_DataProcessor::insertIncompleteSizeFieldTest(quint8 payloadCode, quint8 numBytesFollowing) +{ + QTest::newRow(QString("Text frame with payload size %1, with %2 bytes following.").arg(payloadCode).arg(numBytesFollowing).toStdString().data()) << quint8(FIN | QWebSocketProtocol::OC_TEXT) - << quint8(127) - << QByteArray(wireRepresentation, 8).append(QByteArray(32, 'a')) + << quint8(payloadCode) + << QByteArray(numBytesFollowing, quint8(1)) << false - << QWebSocketProtocol::CC_TOO_MUCH_DATA; - - swapped64 = qToBigEndian(QWebSocketDataProcessor::maxFrameSize() + 1); - wireRepresentation = static_cast(static_cast(&swapped64)); - QTest::newRow("Binary frame with payload size > INT_MAX") + << QWebSocketProtocol::CC_GOING_AWAY; + QTest::newRow(QString("Binary frame with payload size %1, with %2 bytes following.").arg(payloadCode).arg(numBytesFollowing).toStdString().data()) << quint8(FIN | QWebSocketProtocol::OC_BINARY) - << quint8(127) - << QByteArray(wireRepresentation, 8).append(QByteArray(32, 'a')) + << quint8(payloadCode) + << QByteArray(numBytesFollowing, quint8(1)) << false - << QWebSocketProtocol::CC_TOO_MUCH_DATA; - - swapped64 = qToBigEndian(QWebSocketDataProcessor::maxFrameSize() + 1); - wireRepresentation = static_cast(static_cast(&swapped64)); - QTest::newRow("Continuation frame with payload size > INT_MAX") + << QWebSocketProtocol::CC_GOING_AWAY; + QTest::newRow(QString("Continuation frame with payload size %1, with %2 bytes following.").arg(payloadCode).arg(numBytesFollowing).toStdString().data()) << quint8(FIN | QWebSocketProtocol::OC_CONTINUE) - << quint8(127) - << QByteArray(wireRepresentation, 8).append(QByteArray(32, 'a')) + << quint8(payloadCode) + << QByteArray(numBytesFollowing, quint8(1)) << true - << QWebSocketProtocol::CC_TOO_MUCH_DATA; + << QWebSocketProtocol::CC_GOING_AWAY; } QTEST_MAIN(tst_DataProcessor) -- 2.7.4