From 4f884ef7b5588ee4f639a61a0a812d3efac90e81 Mon Sep 17 00:00:00 2001 From: Kurt Pattyn Date: Sun, 13 Oct 2013 15:30:33 +0200 Subject: [PATCH] Completed websocketframe unit tests Replaced tab with space Change-Id: Ifc47621069870123b5db458a1ba2bb0d66a5419d Reviewed-by: Steven Ceuppens --- tests/auto/websocketframe/tst_websocketframe.cpp | 456 +++++++++++++++++++++++ 1 file changed, 456 insertions(+) diff --git a/tests/auto/websocketframe/tst_websocketframe.cpp b/tests/auto/websocketframe/tst_websocketframe.cpp index bd38c65..c53d02c 100644 --- a/tests/auto/websocketframe/tst_websocketframe.cpp +++ b/tests/auto/websocketframe/tst_websocketframe.cpp @@ -41,11 +41,104 @@ #include #include #include +#include +#include #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((m_opCode & 0x0F) | (m_isFinalFrame ? 0x80 : 0x00)); //FIN, opcode + //RSV1-3 + byte |= static_cast(((m_rsv1 & 0x01) << 6) | ((m_rsv2 & 0x01) << 5) | ((m_rsv3 & 0x01) << 4)); + wireRep.append(static_cast(byte)); + + byte = 0x00; + if (m_mask != 0) + { + byte |= 0x80; + } + if (payloadLength <= 125) + { + byte |= static_cast(payloadLength); + wireRep.append(static_cast(byte)); + } + else if (payloadLength <= 0xFFFFU) + { + byte |= 126; + wireRep.append(static_cast(byte)); + quint16 swapped = qToBigEndian(static_cast(payloadLength)); + wireRep.append(static_cast(static_cast(&swapped)), 2); + } + else if (payloadLength <= 0x7FFFFFFFFFFFFFFFULL) + { + byte |= 127; + wireRep.append(static_cast(byte)); + quint64 swapped = qToBigEndian(payloadLength); + wireRep.append(static_cast(static_cast(&swapped)), 8); + } + //Write mask + if (m_mask != 0) + { + wireRep.append(static_cast(static_cast(&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"); + qRegisterMetaType("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("rsv1"); + QTest::addColumn("rsv2"); + QTest::addColumn("rsv3"); + QTest::addColumn("mask"); + QTest::addColumn("opCode"); + QTest::addColumn("isFinal"); + QTest::addColumn("payload"); + QTest::addColumn("isControlFrame"); + QTest::addColumn("isDataFrame"); + QTest::addColumn("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("rsv1"); + QTest::addColumn("rsv2"); + QTest::addColumn("rsv3"); + QTest::addColumn("mask"); + QTest::addColumn("opCode"); + QTest::addColumn("isFinal"); + QTest::addColumn("payload"); + QTest::addColumn("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("payload"); + QTest::addColumn("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(payloadSize, swapped); + QTest::newRow("Frame too big") + << wireRep.left(1).append(bigpayloadIndicator).append(reinterpret_cast(swapped), 8) + << QWebSocketProtocol::CC_TOO_MUCH_DATA; + } + //overlong size field + { + const char largepayloadIndicator = char(126); + const quint16 payloadSize = 120; + uchar swapped[2] = {0}; + qToBigEndian(payloadSize, swapped); + QTest::newRow("Overlong 16-bit size field") + << wireRep.left(1).append(largepayloadIndicator).append(reinterpret_cast(swapped), 2) + << QWebSocketProtocol::CC_PROTOCOL_ERROR; + } + { + const char bigpayloadIndicator = char(127); + quint64 payloadSize = 120; + uchar swapped[8] = {0}; + qToBigEndian(payloadSize, swapped); + QTest::newRow("Overlong 64-bit size field; should be 7-bit") + << wireRep.left(1).append(bigpayloadIndicator).append(reinterpret_cast(swapped), 8) + << QWebSocketProtocol::CC_PROTOCOL_ERROR; + + payloadSize = 256; + qToBigEndian(payloadSize, swapped); + QTest::newRow("Overlong 64-bit size field; should be 16-bit") + << wireRep.left(1).append(bigpayloadIndicator).append(reinterpret_cast(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" -- 2.7.4