Completed websocketframe unit tests
authorKurt Pattyn <pattyn.kurt@gmail.com>
Sun, 13 Oct 2013 13:30:33 +0000 (15:30 +0200)
committerKurt Pattyn <pattyn.kurt@gmail.com>
Sun, 13 Oct 2013 14:58:45 +0000 (16:58 +0200)
Replaced tab with space

Change-Id: Ifc47621069870123b5db458a1ba2bb0d66a5419d
Reviewed-by: Steven Ceuppens <steven.ceuppens@icloud.com>
tests/auto/websocketframe/tst_websocketframe.cpp

index bd38c65..c53d02c 100644 (file)
 #include <QtTest/QtTest>
 #include <QtTest/qtestcase.h>
 #include <QDebug>
+#include <QByteArray>
+#include <QtEndian>
 
 #include "private/qwebsocketframe_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::OC_RESERVED_3),
+    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 if (payloadLength <= 0x7FFFFFFFFFFFFFFFULL)
+    {
+        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
@@ -60,6 +153,15 @@ private Q_SLOTS:
     void cleanup();
 
     void tst_initialization();
+
+    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()
@@ -74,6 +176,8 @@ void tst_WebSocketFrame::cleanupTestCase()
 
 void tst_WebSocketFrame::init()
 {
+    qRegisterMetaType<QWebSocketProtocol::OpCode>("QWebSocketProtocol::OpCode");
+    qRegisterMetaType<QWebSocketProtocol::CloseCode>("QWebSocketProtocol::CloseCode");
 }
 
 void tst_WebSocketFrame::cleanup()
@@ -87,6 +191,358 @@ void tst_WebSocketFrame::tst_initialization()
     QCOMPARE(frame.payload().length(), 0);
 }
 
+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::OC_TEXT
+            << true << QString("Hello world!").toUtf8()
+            << false << true << false;
+    QTest::newRow("Non masked final binary frame with small payload")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_BINARY
+            << true << QByteArray("\x00\x01\x02\x03\x04")
+            << false << true << false;
+    QTest::newRow("Non masked final text frame with no payload")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_TEXT
+            << true << QByteArray()
+            << false << true << false;
+    QTest::newRow("Non masked final binary frame with no payload")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_BINARY
+            << true << QByteArray()
+            << false << true << false;
+
+    QTest::newRow("Non masked final close frame with small payload")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_CLOSE
+            << true << QString("Hello world!").toUtf8()
+            << true << false << false;
+    QTest::newRow("Non masked final close frame with no payload")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_CLOSE
+            << true << QByteArray()
+            << true << false << false;
+    QTest::newRow("Non masked final ping frame with small payload")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_PING
+            << true << QString("Hello world!").toUtf8()
+            << true << false << false;
+    QTest::newRow("Non masked final pong frame with no payload")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_PONG
+            << true << QByteArray()
+            << true << false << false;
+
+    QTest::newRow("Non masked final continuation frame with small payload")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_CONTINUE
+            << true << QString("Hello world!").toUtf8()
+            << false << true << true;
+    QTest::newRow("Non masked non-final continuation frame with small payload")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_CONTINUE
+            << false << QString("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();
+    QCOMPARE(frame.isValid(), true);
+    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::OC_TEXT
+            << true << QString("Hello world!").toUtf8()
+            << QWebSocketProtocol::CC_PROTOCOL_ERROR;
+    QTest::newRow("RSV2 != 0")
+            << 0 << 1 << 0
+            << 0U << QWebSocketProtocol::OC_TEXT
+            << true << QString("Hello world!").toUtf8()
+            << QWebSocketProtocol::CC_PROTOCOL_ERROR;
+    QTest::newRow("RSV3 != 0")
+            << 0 << 0 << 1
+            << 0U << QWebSocketProtocol::OC_TEXT
+            << true << QString("Hello world!").toUtf8()
+            << QWebSocketProtocol::CC_PROTOCOL_ERROR;
+    QTest::newRow("RSV1 != 0 and RSV2 != 0")
+            << 1 << 1 << 0
+            << 0U << QWebSocketProtocol::OC_TEXT
+            << true << QString("Hello world!").toUtf8()
+            << QWebSocketProtocol::CC_PROTOCOL_ERROR;
+    QTest::newRow("RSV1 != 0 and RSV3 != 0")
+            << 1 << 0 << 1
+            << 0U << QWebSocketProtocol::OC_TEXT
+            << true << QString("Hello world!").toUtf8()
+            << QWebSocketProtocol::CC_PROTOCOL_ERROR;
+    QTest::newRow("RSV2 != 0 and RSV3 != 0")
+            << 0 << 1 << 1
+            << 0U << QWebSocketProtocol::OC_TEXT
+            << true << QString("Hello world!").toUtf8()
+            << QWebSocketProtocol::CC_PROTOCOL_ERROR;
+
+    QTest::newRow("Reserved OpCode 3")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_RESERVED_3
+            << true << QString("Hello world!").toUtf8()
+            << QWebSocketProtocol::CC_PROTOCOL_ERROR;
+    QTest::newRow("Reserved OpCode 4")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_RESERVED_4
+            << true << QString("Hello world!").toUtf8()
+            << QWebSocketProtocol::CC_PROTOCOL_ERROR;
+    QTest::newRow("Reserved OpCode 5")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_RESERVED_5
+            << true << QString("Hello world!").toUtf8()
+            << QWebSocketProtocol::CC_PROTOCOL_ERROR;
+    QTest::newRow("Reserved OpCode 6")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_RESERVED_6
+            << true << QString("Hello world!").toUtf8()
+            << QWebSocketProtocol::CC_PROTOCOL_ERROR;
+    QTest::newRow("Reserved OpCode 7")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_RESERVED_7
+            << true << QString("Hello world!").toUtf8()
+            << QWebSocketProtocol::CC_PROTOCOL_ERROR;
+    QTest::newRow("Reserved OpCode B")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_RESERVED_B
+            << true << QString("Hello world!").toUtf8()
+            << QWebSocketProtocol::CC_PROTOCOL_ERROR;
+    QTest::newRow("Reserved OpCode C")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_RESERVED_C
+            << true << QString("Hello world!").toUtf8()
+            << QWebSocketProtocol::CC_PROTOCOL_ERROR;
+    QTest::newRow("Reserved OpCode D")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_RESERVED_D
+            << true << QString("Hello world!").toUtf8()
+            << QWebSocketProtocol::CC_PROTOCOL_ERROR;
+    QTest::newRow("Reserved OpCode E")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_RESERVED_E
+            << true << QString("Hello world!").toUtf8()
+            << QWebSocketProtocol::CC_PROTOCOL_ERROR;
+    QTest::newRow("Reserved OpCode F")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_RESERVED_F
+            << true << QString("Hello world!").toUtf8()
+            << QWebSocketProtocol::CC_PROTOCOL_ERROR;
+
+    QTest::newRow("Close Frame with payload > 125 bytes")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_CLOSE
+            << true << QString(126, 'a').toUtf8()
+            << QWebSocketProtocol::CC_PROTOCOL_ERROR;
+    QTest::newRow("Non-final Close Frame")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_CLOSE
+            << false << QString(126, 'a').toUtf8()
+            << QWebSocketProtocol::CC_PROTOCOL_ERROR;
+    QTest::newRow("Ping Frame with payload > 125 bytes")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_PING
+            << true << QString(126, 'a').toUtf8()
+            << QWebSocketProtocol::CC_PROTOCOL_ERROR;
+    QTest::newRow("Non-final Ping Frame")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_PING
+            << false << QString(126, 'a').toUtf8()
+            << QWebSocketProtocol::CC_PROTOCOL_ERROR;
+    QTest::newRow("Pong Frame with payload > 125 bytes")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_PONG
+            << true << QString(126, 'a').toUtf8()
+            << QWebSocketProtocol::CC_PROTOCOL_ERROR;
+    QTest::newRow("Non-final Pong Frame")
+            << 0 << 0 << 0
+            << 0U << QWebSocketProtocol::OC_PONG
+            << false << QString(126, 'a').toUtf8()
+            << QWebSocketProtocol::CC_PROTOCOL_ERROR;
+}
+
+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::OC_TEXT
+            << true << QString(MAX_FRAME_SIZE_IN_BYTES + 1, 'a').toUtf8()
+            << QWebSocketProtocol::CC_TOO_MUCH_DATA;
+
+ */
+void tst_WebSocketFrame::tst_malformedFrames_data()
+{
+    QTest::addColumn<QByteArray>("payload");
+    QTest::addColumn<QWebSocketProtocol::CloseCode>("expectedError");
+
+    //too little data
+    QTest::newRow("No data") << QByteArray() << QWebSocketProtocol::CC_GOING_AWAY;
+    FrameHelper helper;
+    helper.setRsv1(0);
+    helper.setRsv2(0);
+    helper.setRsv3(0);
+    helper.setMask(0U);
+    helper.setOpCode(QWebSocketProtocol::OC_TEXT);
+    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(QString("Header too small - %1 byte(s)").arg(i).toLatin1().constData()) << wireRep.left(i) << QWebSocketProtocol::CC_GOING_AWAY;
+    }
+    //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::CC_TOO_MUCH_DATA;
+    }
+    //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::CC_PROTOCOL_ERROR;
+    }
+    {
+        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::CC_PROTOCOL_ERROR;
+
+        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::CC_PROTOCOL_ERROR;
+    }
+}
+
+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"