moved class to separate file
authorSteven Ceuppens <steven.ceuppens@icloud.com>
Sun, 29 Sep 2013 18:41:29 +0000 (20:41 +0200)
committerSteven Ceuppens <steven.ceuppens@icloud.com>
Sun, 29 Sep 2013 18:50:42 +0000 (20:50 +0200)
As QWebSocketFrame is quite big, i moved it into its own file, for futher implementation:wq

Change-Id: Iadf51323d5e8151c8345057614f4c49f81e626b1
Reviewed-by: Kurt Pattyn <pattyn.kurt@gmail.com>
src/websockets/qwebsocketdataprocessor_p.cpp
src/websockets/qwebsocketframe_p.cpp [new file with mode: 0644]
src/websockets/qwebsocketframe_p.h [new file with mode: 0644]
src/websockets/websockets.pro

index f5c9c61..f3afdee 100644 (file)
@@ -32,7 +32,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
 #include "qwebsocketdataprocessor_p.h"
 #include "qwebsocketprotocol.h"
-#include <QIODevice>
+#include "qwebsocketframe_p.h"
 #include <QtEndian>
 #include <limits.h>
 #include <QTextCodec>
@@ -41,554 +41,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 
 QT_BEGIN_NAMESPACE
 
-const quint64 MAX_FRAME_SIZE_IN_BYTES = INT_MAX - 1;
-const quint64 MAX_MESSAGE_SIZE_IN_BYTES = INT_MAX - 1;
-
-/*!
-    \class Frame
-    The class Frame is responsible for reading, validating and interpreting frames from a websocket.
-    It reads data from a QIODevice, validates it against RFC 6455, and parses it into a frame (data, control).
-    Whenever an error is detected, the isValid() returns false.
-
-    \note The Frame class does not look at valid sequences of frames. It processes frames one at a time.
-    \note It is the DataProcessor that takes the sequence into account.
-
-    \sa DataProcessor()
-    \internal
- */
-class QWebSocketFrame
-{
-public:
-    QWebSocketFrame();
-    QWebSocketFrame(const QWebSocketFrame &other);
-
-    const QWebSocketFrame &operator =(const QWebSocketFrame &other);
-
-    QWebSocketProtocol::CloseCode getCloseCode() const;
-    QString getCloseReason() const;
-    bool isFinalFrame() const;
-    bool isControlFrame() const;
-    bool isDataFrame() const;
-    bool isContinuationFrame() const;
-    bool hasMask() const;
-    quint32 getMask() const;    //returns 0 if no mask
-    int getRsv1() const;
-    int getRsv2() const;
-    int getRsv3() const;
-    QWebSocketProtocol::OpCode getOpCode() const;
-    QByteArray getPayload() const;
-
-    void clear();       //resets all member variables, and invalidates the object
-
-    bool isValid() const;
-
-    static QWebSocketFrame readFrame(QIODevice *pIoDevice);
-
-private:
-    QWebSocketProtocol::CloseCode m_closeCode;
-    QString m_closeReason;
-    bool m_isFinalFrame;
-    quint32 m_mask;
-    int m_rsv1; //reserved field 1
-    int m_rsv2; //reserved field 2
-    int m_rsv3; //reserved field 3
-    QWebSocketProtocol::OpCode m_opCode;
-
-    quint8 m_length;        //length field as read from the header; this is 1 byte, which when 126 or 127, indicates a large payload
-    QByteArray m_payload;
-
-    bool m_isValid;
-
-    enum ProcessingState
-    {
-        PS_READ_HEADER,
-        PS_READ_PAYLOAD_LENGTH,
-        PS_READ_BIG_PAYLOAD_LENGTH,
-        PS_READ_MASK,
-        PS_READ_PAYLOAD,
-        PS_DISPATCH_RESULT,
-        PS_WAIT_FOR_MORE_DATA
-    };
-
-    void setError(QWebSocketProtocol::CloseCode code, QString closeReason);
-    bool checkValidity();
-};
-
-/*!
-    \internal
- */
-QWebSocketFrame::QWebSocketFrame() :
-    m_closeCode(QWebSocketProtocol::CC_NORMAL),
-    m_closeReason(),
-    m_isFinalFrame(true),
-    m_mask(0),
-    m_rsv1(0),
-    m_rsv2(0),
-    m_rsv3(0),
-    m_opCode(QWebSocketProtocol::OC_RESERVED_C),
-    m_length(0),
-    m_payload(),
-    m_isValid(false)
-{
-}
-
-/*!
-    \internal
- */
-QWebSocketFrame::QWebSocketFrame(const QWebSocketFrame &other) :
-    m_closeCode(other.m_closeCode),
-    m_closeReason(other.m_closeReason),
-    m_isFinalFrame(other.m_isFinalFrame),
-    m_mask(other.m_mask),
-    m_rsv1(other.m_rsv1),
-    m_rsv2(other.m_rsv2),
-    m_rsv3(other.m_rsv3),
-    m_opCode(other.m_opCode),
-    m_length(other.m_length),
-    m_payload(other.m_payload),
-    m_isValid(other.m_isValid)
-{
-}
-
-/*!
-    \internal
- */
-const QWebSocketFrame &QWebSocketFrame::operator =(const QWebSocketFrame &other)
-{
-    m_closeCode = other.m_closeCode;
-    m_closeReason = other.m_closeReason;
-    m_isFinalFrame = other.m_isFinalFrame;
-    m_mask = other.m_mask;
-    m_rsv1 = other.m_rsv1;
-    m_rsv2 = other.m_rsv2;
-    m_rsv3 = other.m_rsv2;
-    m_opCode = other.m_opCode;
-    m_length = other.m_length;
-    m_payload = other.m_payload;
-    m_isValid = other.m_isValid;
-
-    return *this;
-}
-
-/*!
-    \internal
- */
-QWebSocketProtocol::CloseCode QWebSocketFrame::getCloseCode() const
-{
-    return m_closeCode;
-}
-
-/*!
-    \internal
- */
-QString QWebSocketFrame::getCloseReason() const
-{
-    return m_closeReason;
-}
-
-/*!
-    \internal
- */
-bool QWebSocketFrame::isFinalFrame() const
-{
-    return m_isFinalFrame;
-}
-
-/*!
-    \internal
- */
-bool QWebSocketFrame::isControlFrame() const
-{
-    return (m_opCode & 0x08) == 0x08;
-}
-
-/*!
-    \internal
- */
-bool QWebSocketFrame::isDataFrame() const
-{
-    return !isControlFrame();
-}
-
-/*!
-    \internal
- */
-bool QWebSocketFrame::isContinuationFrame() const
-{
-    return isDataFrame() && (m_opCode == QWebSocketProtocol::OC_CONTINUE);
-}
-
-/*!
-    \internal
- */
-bool QWebSocketFrame::hasMask() const
-{
-    return m_mask != 0;
-}
-
-/*!
-    \internal
- */
-quint32 QWebSocketFrame::getMask() const
-{
-    return m_mask;
-}
-
-/*!
-    \internal
- */
-int QWebSocketFrame::getRsv1() const
-{
-    return m_rsv1;
-}
-
-/*!
-    \internal
- */
-int QWebSocketFrame::getRsv2() const
-{
-    return m_rsv2;
-}
-
-/*!
-    \internal
- */
-int QWebSocketFrame::getRsv3() const
-{
-    return m_rsv3;
-}
-
-/*!
-    \internal
- */
-QWebSocketProtocol::OpCode QWebSocketFrame::getOpCode() const
-{
-    return m_opCode;
-}
-
-/*!
-    \internal
- */
-QByteArray QWebSocketFrame::getPayload() const
-{
-    return m_payload;
-}
-
-/*!
-    \internal
- */
-void QWebSocketFrame::clear()
-{
-    m_closeCode = QWebSocketProtocol::CC_NORMAL;
-    m_closeReason.clear();
-    m_isFinalFrame = true;
-    m_mask = 0;
-    m_rsv1 = 0;
-    m_rsv2 =0;
-    m_rsv3 = 0;
-    m_opCode = QWebSocketProtocol::OC_RESERVED_C;
-    m_length = 0;
-    m_payload.clear();
-    m_isValid = false;
-}
-
-/*!
-    \internal
- */
-bool QWebSocketFrame::isValid() const
-{
-    return m_isValid;
-}
-
-#define WAIT_FOR_MORE_DATA(dataSizeInBytes)  { returnState = processingState; processingState = PS_WAIT_FOR_MORE_DATA; dataWaitSize = dataSizeInBytes; }
-
-/*!
-    \internal
- */
-QWebSocketFrame QWebSocketFrame::readFrame(QIODevice *pIoDevice)
-{
-    bool isDone = false;
-    qint64 bytesRead = 0;
-    QWebSocketFrame frame;
-    quint64 dataWaitSize = 0;
-    ProcessingState processingState = PS_READ_HEADER;
-    ProcessingState returnState = PS_READ_HEADER;
-    bool hasMask = false;
-    quint64 payloadLength = 0;
-
-    while (!isDone)
-    {
-        switch (processingState)
-        {
-            case PS_WAIT_FOR_MORE_DATA:
-            {
-                bool ok = pIoDevice->waitForReadyRead(5000);
-                if (!ok)
-                {
-                    frame.setError(QWebSocketProtocol::CC_GOING_AWAY, QObject::tr("Timeout when reading data from socket."));
-                    processingState = PS_DISPATCH_RESULT;
-                }
-                else
-                {
-                    processingState = returnState;
-                }
-                break;
-            }
-            case PS_READ_HEADER:
-            {
-                if (pIoDevice->bytesAvailable() >= 2)
-                {
-                    //FIN, RSV1-3, Opcode
-                    char header[2] = {0};
-                    bytesRead = pIoDevice->read(header, 2);
-                    frame.m_isFinalFrame = (header[0] & 0x80) != 0;
-                    frame.m_rsv1 = (header[0] & 0x40);
-                    frame.m_rsv2 = (header[0] & 0x20);
-                    frame.m_rsv3 = (header[0] & 0x10);
-                    frame.m_opCode = static_cast<QWebSocketProtocol::OpCode>(header[0] & 0x0F);
-
-                    //Mask, PayloadLength
-                    hasMask = (header[1] & 0x80) != 0;
-                    frame.m_length = (header[1] & 0x7F);
-
-                    switch (frame.m_length)
-                    {
-                        case 126:
-                        {
-                            processingState = PS_READ_PAYLOAD_LENGTH;
-                            break;
-                        }
-                        case 127:
-                        {
-                            processingState = PS_READ_BIG_PAYLOAD_LENGTH;
-                            break;
-                        }
-                        default:
-                        {
-                            payloadLength = frame.m_length;
-                            processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD;
-                            break;
-                        }
-                    }
-                    if (!frame.checkValidity())
-                    {
-                        processingState = PS_DISPATCH_RESULT;
-                    }
-                }
-                else
-                {
-                    WAIT_FOR_MORE_DATA(2);
-                }
-                break;
-            }
-
-            case PS_READ_PAYLOAD_LENGTH:
-            {
-                if (pIoDevice->bytesAvailable() >= 2)
-                {
-                    uchar length[2] = {0};
-                    bytesRead = pIoDevice->read(reinterpret_cast<char *>(length), 2);
-                    if (bytesRead == -1)
-                    {
-                        frame.setError(QWebSocketProtocol::CC_GOING_AWAY, QObject::tr("Error occurred while reading from the network: %1").arg(pIoDevice->errorString()));
-                        processingState = PS_DISPATCH_RESULT;
-                    }
-                    else
-                    {
-                        payloadLength = qFromBigEndian<quint16>(reinterpret_cast<const uchar *>(length));
-                        if (payloadLength < 126)
-                        {
-                            //see http://tools.ietf.org/html/rfc6455#page-28 paragraph 5.2
-                            //"in all cases, the minimal number of bytes MUST be used to encode
-                            //the length, for example, the length of a 124-byte-long string
-                            //can't be encoded as the sequence 126, 0, 124"
-                            frame.setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Lengths smaller than 126 must be expressed as one byte."));
-                            processingState = PS_DISPATCH_RESULT;
-                        }
-                        else
-                        {
-                            processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD;
-                        }
-                    }
-                }
-                else
-                {
-                    WAIT_FOR_MORE_DATA(2);
-                }
-                break;
-            }
-
-            case PS_READ_BIG_PAYLOAD_LENGTH:
-            {
-                if (pIoDevice->bytesAvailable() >= 8)
-                {
-                    uchar length[8] = {0};
-                    bytesRead = pIoDevice->read(reinterpret_cast<char *>(length), 8);
-                    if (bytesRead < 8)
-                    {
-                        frame.setError(QWebSocketProtocol::CC_ABNORMAL_DISCONNECTION, QObject::tr("Something went wrong during reading from the network."));
-                        processingState = PS_DISPATCH_RESULT;
-                    }
-                    else
-                    {
-                        //Most significant bit must be set to 0 as per http://tools.ietf.org/html/rfc6455#section-5.2
-                        //TODO: Do we check for that? Now we just strip off the highest bit
-                        payloadLength = qFromBigEndian<quint64>(length) & ~(1ULL << 63);
-                        if (payloadLength <= 0xFFFFu)
-                        {
-                            //see http://tools.ietf.org/html/rfc6455#page-28 paragraph 5.2
-                            //"in all cases, the minimal number of bytes MUST be used to encode
-                            //the length, for example, the length of a 124-byte-long string
-                            //can't be encoded as the sequence 126, 0, 124"
-                            frame.setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Lengths smaller than 65536 (2^16) must be expressed as 2 bytes."));
-                            processingState = PS_DISPATCH_RESULT;
-                        }
-                        else
-                        {
-                            processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD;
-                        }
-                    }
-                }
-                else
-                {
-                    WAIT_FOR_MORE_DATA(8);
-                }
-
-                break;
-            }
-
-            case PS_READ_MASK:
-            {
-                if (pIoDevice->bytesAvailable() >= 4)
-                {
-                    bytesRead = pIoDevice->read(reinterpret_cast<char *>(&frame.m_mask), sizeof(frame.m_mask));
-                    if (bytesRead == -1)
-                    {
-                        frame.setError(QWebSocketProtocol::CC_GOING_AWAY, QObject::tr("Error while reading from the network: %1.").arg(pIoDevice->errorString()));
-                        processingState = PS_DISPATCH_RESULT;
-                    }
-                    else
-                    {
-                        processingState = PS_READ_PAYLOAD;
-                    }
-                }
-                else
-                {
-                    WAIT_FOR_MORE_DATA(4);
-                }
-                break;
-            }
-
-            case PS_READ_PAYLOAD:
-            {
-                if (!payloadLength)
-                {
-                    processingState = PS_DISPATCH_RESULT;
-                }
-                else if (payloadLength > MAX_FRAME_SIZE_IN_BYTES)
-                {
-                    frame.setError(QWebSocketProtocol::CC_TOO_MUCH_DATA, QObject::tr("Maximum framesize exceeded."));
-                    processingState = PS_DISPATCH_RESULT;
-                }
-                else
-                {
-                    quint64 bytesAvailable = static_cast<quint64>(pIoDevice->bytesAvailable());
-                    if (bytesAvailable >= payloadLength)
-                    {
-                        frame.m_payload = pIoDevice->read(payloadLength);
-                        //payloadLength can be safely cast to an integer, as the MAX_FRAME_SIZE_IN_BYTES = MAX_INT
-                        if (frame.m_payload.length() != static_cast<int>(payloadLength))  //some error occurred; refer to the Qt documentation
-                        {
-                            frame.setError(QWebSocketProtocol::CC_ABNORMAL_DISCONNECTION, QObject::tr("Some serious error occurred while reading from the network."));
-                            processingState = PS_DISPATCH_RESULT;
-                        }
-                        else
-                        {
-                            if (hasMask)
-                            {
-                                QWebSocketProtocol::mask(&frame.m_payload, frame.m_mask);
-                            }
-                            processingState = PS_DISPATCH_RESULT;
-                        }
-                    }
-                    else
-                    {
-                        WAIT_FOR_MORE_DATA(payloadLength);  //if payload is too big, then this will timeout
-                    }
-                }
-                break;
-            }
-
-            case PS_DISPATCH_RESULT:
-            {
-                processingState = PS_READ_HEADER;
-                isDone = true;
-                break;
-            }
-
-            default:
-            {
-                //should not come here
-                qWarning() << "DataProcessor::process: Found invalid state. This should not happen!";
-                frame.clear();
-                isDone = true;
-                break;
-            }
-        }      //end switch
-    }
-
-    return frame;
-}
-
-/*!
-    \internal
- */
-void QWebSocketFrame::setError(QWebSocketProtocol::CloseCode code, QString closeReason)
-{
-    clear();
-    m_closeCode = code;
-    m_closeReason = closeReason;
-    m_isValid = false;
-}
-
-/*!
-    \internal
- */
-bool QWebSocketFrame::checkValidity()
-{
-    if (!isValid())
-    {
-        if (m_rsv1 || m_rsv2 || m_rsv3)
-        {
-            setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Rsv field is non-zero"));
-        }
-        else if (QWebSocketProtocol::isOpCodeReserved(m_opCode))
-        {
-            setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Used reserved opcode"));
-        }
-        else if (isControlFrame())
-        {
-            if (m_length > 125)
-            {
-                setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Controle frame is larger than 125 bytes"));
-            }
-            else if (!m_isFinalFrame)
-            {
-                setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Controle frames cannot be fragmented"));
-            }
-            else
-            {
-                m_isValid = true;
-            }
-        }
-        else
-        {
-            m_isValid = true;
-        }
-    }
-    return m_isValid;
-}
-
 /*!
     \internal
  */
diff --git a/src/websockets/qwebsocketframe_p.cpp b/src/websockets/qwebsocketframe_p.cpp
new file mode 100644 (file)
index 0000000..b4d61e7
--- /dev/null
@@ -0,0 +1,498 @@
+/*
+QWebSockets implements the WebSocket protocol as defined in RFC 6455.
+Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com)
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#include "qwebsocketframe_p.h"
+
+#include <QtEndian>
+#include <QDebug>
+
+/*!
+    \internal
+ */
+QWebSocketFrame::QWebSocketFrame() :
+    m_closeCode(QWebSocketProtocol::CC_NORMAL),
+    m_closeReason(),
+    m_isFinalFrame(true),
+    m_mask(0),
+    m_rsv1(0),
+    m_rsv2(0),
+    m_rsv3(0),
+    m_opCode(QWebSocketProtocol::OC_RESERVED_C),
+    m_length(0),
+    m_payload(),
+    m_isValid(false)
+{
+}
+
+/*!
+    \internal
+ */
+QWebSocketFrame::QWebSocketFrame(const QWebSocketFrame &other) :
+    m_closeCode(other.m_closeCode),
+    m_closeReason(other.m_closeReason),
+    m_isFinalFrame(other.m_isFinalFrame),
+    m_mask(other.m_mask),
+    m_rsv1(other.m_rsv1),
+    m_rsv2(other.m_rsv2),
+    m_rsv3(other.m_rsv3),
+    m_opCode(other.m_opCode),
+    m_length(other.m_length),
+    m_payload(other.m_payload),
+    m_isValid(other.m_isValid)
+{
+}
+
+/*!
+    \internal
+ */
+const QWebSocketFrame &QWebSocketFrame::operator =(const QWebSocketFrame &other)
+{
+    m_closeCode = other.m_closeCode;
+    m_closeReason = other.m_closeReason;
+    m_isFinalFrame = other.m_isFinalFrame;
+    m_mask = other.m_mask;
+    m_rsv1 = other.m_rsv1;
+    m_rsv2 = other.m_rsv2;
+    m_rsv3 = other.m_rsv2;
+    m_opCode = other.m_opCode;
+    m_length = other.m_length;
+    m_payload = other.m_payload;
+    m_isValid = other.m_isValid;
+
+    return *this;
+}
+
+/*!
+    \internal
+ */
+QWebSocketProtocol::CloseCode QWebSocketFrame::getCloseCode() const
+{
+    return m_closeCode;
+}
+
+/*!
+    \internal
+ */
+QString QWebSocketFrame::getCloseReason() const
+{
+    return m_closeReason;
+}
+
+/*!
+    \internal
+ */
+bool QWebSocketFrame::isFinalFrame() const
+{
+    return m_isFinalFrame;
+}
+
+/*!
+    \internal
+ */
+bool QWebSocketFrame::isControlFrame() const
+{
+    return (m_opCode & 0x08) == 0x08;
+}
+
+/*!
+    \internal
+ */
+bool QWebSocketFrame::isDataFrame() const
+{
+    return !isControlFrame();
+}
+
+/*!
+    \internal
+ */
+bool QWebSocketFrame::isContinuationFrame() const
+{
+    return isDataFrame() && (m_opCode == QWebSocketProtocol::OC_CONTINUE);
+}
+
+/*!
+    \internal
+ */
+bool QWebSocketFrame::hasMask() const
+{
+    return m_mask != 0;
+}
+
+/*!
+    \internal
+ */
+quint32 QWebSocketFrame::getMask() const
+{
+    return m_mask;
+}
+
+/*!
+    \internal
+ */
+int QWebSocketFrame::getRsv1() const
+{
+    return m_rsv1;
+}
+
+/*!
+    \internal
+ */
+int QWebSocketFrame::getRsv2() const
+{
+    return m_rsv2;
+}
+
+/*!
+    \internal
+ */
+int QWebSocketFrame::getRsv3() const
+{
+    return m_rsv3;
+}
+
+/*!
+    \internal
+ */
+QWebSocketProtocol::OpCode QWebSocketFrame::getOpCode() const
+{
+    return m_opCode;
+}
+
+/*!
+    \internal
+ */
+QByteArray QWebSocketFrame::getPayload() const
+{
+    return m_payload;
+}
+
+/*!
+    \internal
+ */
+void QWebSocketFrame::clear()
+{
+    m_closeCode = QWebSocketProtocol::CC_NORMAL;
+    m_closeReason.clear();
+    m_isFinalFrame = true;
+    m_mask = 0;
+    m_rsv1 = 0;
+    m_rsv2 =0;
+    m_rsv3 = 0;
+    m_opCode = QWebSocketProtocol::OC_RESERVED_C;
+    m_length = 0;
+    m_payload.clear();
+    m_isValid = false;
+}
+
+/*!
+    \internal
+ */
+bool QWebSocketFrame::isValid() const
+{
+    return m_isValid;
+}
+
+#define WAIT_FOR_MORE_DATA(dataSizeInBytes)  { returnState = processingState; processingState = PS_WAIT_FOR_MORE_DATA; dataWaitSize = dataSizeInBytes; }
+
+/*!
+    \internal
+ */
+QWebSocketFrame QWebSocketFrame::readFrame(QIODevice *pIoDevice)
+{
+    bool isDone = false;
+    qint64 bytesRead = 0;
+    QWebSocketFrame frame;
+    quint64 dataWaitSize = 0;
+    ProcessingState processingState = PS_READ_HEADER;
+    ProcessingState returnState = PS_READ_HEADER;
+    bool hasMask = false;
+    quint64 payloadLength = 0;
+
+    while (!isDone)
+    {
+        switch (processingState)
+        {
+            case PS_WAIT_FOR_MORE_DATA:
+            {
+                bool ok = pIoDevice->waitForReadyRead(5000);
+                if (!ok)
+                {
+                    frame.setError(QWebSocketProtocol::CC_GOING_AWAY, QObject::tr("Timeout when reading data from socket."));
+                    processingState = PS_DISPATCH_RESULT;
+                }
+                else
+                {
+                    processingState = returnState;
+                }
+                break;
+            }
+            case PS_READ_HEADER:
+            {
+                if (pIoDevice->bytesAvailable() >= 2)
+                {
+                    //FIN, RSV1-3, Opcode
+                    char header[2] = {0};
+                    bytesRead = pIoDevice->read(header, 2);
+                    frame.m_isFinalFrame = (header[0] & 0x80) != 0;
+                    frame.m_rsv1 = (header[0] & 0x40);
+                    frame.m_rsv2 = (header[0] & 0x20);
+                    frame.m_rsv3 = (header[0] & 0x10);
+                    frame.m_opCode = static_cast<QWebSocketProtocol::OpCode>(header[0] & 0x0F);
+
+                    //Mask, PayloadLength
+                    hasMask = (header[1] & 0x80) != 0;
+                    frame.m_length = (header[1] & 0x7F);
+
+                    switch (frame.m_length)
+                    {
+                        case 126:
+                        {
+                            processingState = PS_READ_PAYLOAD_LENGTH;
+                            break;
+                        }
+                        case 127:
+                        {
+                            processingState = PS_READ_BIG_PAYLOAD_LENGTH;
+                            break;
+                        }
+                        default:
+                        {
+                            payloadLength = frame.m_length;
+                            processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD;
+                            break;
+                        }
+                    }
+                    if (!frame.checkValidity())
+                    {
+                        processingState = PS_DISPATCH_RESULT;
+                    }
+                }
+                else
+                {
+                    WAIT_FOR_MORE_DATA(2);
+                }
+                break;
+            }
+
+            case PS_READ_PAYLOAD_LENGTH:
+            {
+                if (pIoDevice->bytesAvailable() >= 2)
+                {
+                    uchar length[2] = {0};
+                    bytesRead = pIoDevice->read(reinterpret_cast<char *>(length), 2);
+                    if (bytesRead == -1)
+                    {
+                        frame.setError(QWebSocketProtocol::CC_GOING_AWAY, QObject::tr("Error occurred while reading from the network: %1").arg(pIoDevice->errorString()));
+                        processingState = PS_DISPATCH_RESULT;
+                    }
+                    else
+                    {
+                        payloadLength = qFromBigEndian<quint16>(reinterpret_cast<const uchar *>(length));
+                        if (payloadLength < 126)
+                        {
+                            //see http://tools.ietf.org/html/rfc6455#page-28 paragraph 5.2
+                            //"in all cases, the minimal number of bytes MUST be used to encode
+                            //the length, for example, the length of a 124-byte-long string
+                            //can't be encoded as the sequence 126, 0, 124"
+                            frame.setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Lengths smaller than 126 must be expressed as one byte."));
+                            processingState = PS_DISPATCH_RESULT;
+                        }
+                        else
+                        {
+                            processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD;
+                        }
+                    }
+                }
+                else
+                {
+                    WAIT_FOR_MORE_DATA(2);
+                }
+                break;
+            }
+
+            case PS_READ_BIG_PAYLOAD_LENGTH:
+            {
+                if (pIoDevice->bytesAvailable() >= 8)
+                {
+                    uchar length[8] = {0};
+                    bytesRead = pIoDevice->read(reinterpret_cast<char *>(length), 8);
+                    if (bytesRead < 8)
+                    {
+                        frame.setError(QWebSocketProtocol::CC_ABNORMAL_DISCONNECTION, QObject::tr("Something went wrong during reading from the network."));
+                        processingState = PS_DISPATCH_RESULT;
+                    }
+                    else
+                    {
+                        //Most significant bit must be set to 0 as per http://tools.ietf.org/html/rfc6455#section-5.2
+                        //TODO: Do we check for that? Now we just strip off the highest bit
+                        payloadLength = qFromBigEndian<quint64>(length) & ~(1ULL << 63);
+                        if (payloadLength <= 0xFFFFu)
+                        {
+                            //see http://tools.ietf.org/html/rfc6455#page-28 paragraph 5.2
+                            //"in all cases, the minimal number of bytes MUST be used to encode
+                            //the length, for example, the length of a 124-byte-long string
+                            //can't be encoded as the sequence 126, 0, 124"
+                            frame.setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Lengths smaller than 65536 (2^16) must be expressed as 2 bytes."));
+                            processingState = PS_DISPATCH_RESULT;
+                        }
+                        else
+                        {
+                            processingState = hasMask ? PS_READ_MASK : PS_READ_PAYLOAD;
+                        }
+                    }
+                }
+                else
+                {
+                    WAIT_FOR_MORE_DATA(8);
+                }
+
+                break;
+            }
+
+            case PS_READ_MASK:
+            {
+                if (pIoDevice->bytesAvailable() >= 4)
+                {
+                    bytesRead = pIoDevice->read(reinterpret_cast<char *>(&frame.m_mask), sizeof(frame.m_mask));
+                    if (bytesRead == -1)
+                    {
+                        frame.setError(QWebSocketProtocol::CC_GOING_AWAY, QObject::tr("Error while reading from the network: %1.").arg(pIoDevice->errorString()));
+                        processingState = PS_DISPATCH_RESULT;
+                    }
+                    else
+                    {
+                        processingState = PS_READ_PAYLOAD;
+                    }
+                }
+                else
+                {
+                    WAIT_FOR_MORE_DATA(4);
+                }
+                break;
+            }
+
+            case PS_READ_PAYLOAD:
+            {
+                if (!payloadLength)
+                {
+                    processingState = PS_DISPATCH_RESULT;
+                }
+                else if (payloadLength > MAX_FRAME_SIZE_IN_BYTES)
+                {
+                    frame.setError(QWebSocketProtocol::CC_TOO_MUCH_DATA, QObject::tr("Maximum framesize exceeded."));
+                    processingState = PS_DISPATCH_RESULT;
+                }
+                else
+                {
+                    quint64 bytesAvailable = static_cast<quint64>(pIoDevice->bytesAvailable());
+                    if (bytesAvailable >= payloadLength)
+                    {
+                        frame.m_payload = pIoDevice->read(payloadLength);
+                        //payloadLength can be safely cast to an integer, as the MAX_FRAME_SIZE_IN_BYTES = MAX_INT
+                        if (frame.m_payload.length() != static_cast<int>(payloadLength))  //some error occurred; refer to the Qt documentation
+                        {
+                            frame.setError(QWebSocketProtocol::CC_ABNORMAL_DISCONNECTION, QObject::tr("Some serious error occurred while reading from the network."));
+                            processingState = PS_DISPATCH_RESULT;
+                        }
+                        else
+                        {
+                            if (hasMask)
+                            {
+                                QWebSocketProtocol::mask(&frame.m_payload, frame.m_mask);
+                            }
+                            processingState = PS_DISPATCH_RESULT;
+                        }
+                    }
+                    else
+                    {
+                        WAIT_FOR_MORE_DATA(payloadLength);  //if payload is too big, then this will timeout
+                    }
+                }
+                break;
+            }
+
+            case PS_DISPATCH_RESULT:
+            {
+                processingState = PS_READ_HEADER;
+                isDone = true;
+                break;
+            }
+
+            default:
+            {
+                //should not come here
+                qWarning() << "DataProcessor::process: Found invalid state. This should not happen!";
+                frame.clear();
+                isDone = true;
+                break;
+            }
+        }      //end switch
+    }
+
+    return frame;
+}
+
+/*!
+    \internal
+ */
+void QWebSocketFrame::setError(QWebSocketProtocol::CloseCode code, QString closeReason)
+{
+    clear();
+    m_closeCode = code;
+    m_closeReason = closeReason;
+    m_isValid = false;
+}
+
+/*!
+    \internal
+ */
+bool QWebSocketFrame::checkValidity()
+{
+    if (!isValid())
+    {
+        if (m_rsv1 || m_rsv2 || m_rsv3)
+        {
+            setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Rsv field is non-zero"));
+        }
+        else if (QWebSocketProtocol::isOpCodeReserved(m_opCode))
+        {
+            setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Used reserved opcode"));
+        }
+        else if (isControlFrame())
+        {
+            if (m_length > 125)
+            {
+                setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Controle frame is larger than 125 bytes"));
+            }
+            else if (!m_isFinalFrame)
+            {
+                setError(QWebSocketProtocol::CC_PROTOCOL_ERROR, QObject::tr("Controle frames cannot be fragmented"));
+            }
+            else
+            {
+                m_isValid = true;
+            }
+        }
+        else
+        {
+            m_isValid = true;
+        }
+    }
+    return m_isValid;
+}
diff --git a/src/websockets/qwebsocketframe_p.h b/src/websockets/qwebsocketframe_p.h
new file mode 100644 (file)
index 0000000..434fead
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+QWebSockets implements the WebSocket protocol as defined in RFC 6455.
+Copyright (C) 2013 Kurt Pattyn (pattyn.kurt@gmail.com)
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#ifndef QWEBSOCKETFRAME_P_H
+#define QWEBSOCKETFRAME_P_H
+
+#include "qwebsocketprotocol.h"
+
+#include <QIODevice>
+
+QT_BEGIN_NAMESPACE
+
+const quint64 MAX_FRAME_SIZE_IN_BYTES = INT_MAX - 1;
+const quint64 MAX_MESSAGE_SIZE_IN_BYTES = INT_MAX - 1;
+
+/*!
+    \class Frame
+    The class Frame is responsible for reading, validating and interpreting frames from a websocket.
+    It reads data from a QIODevice, validates it against RFC 6455, and parses it into a frame (data, control).
+    Whenever an error is detected, the isValid() returns false.
+
+    \note The Frame class does not look at valid sequences of frames. It processes frames one at a time.
+    \note It is the DataProcessor that takes the sequence into account.
+
+    \sa DataProcessor()
+    \internal
+ */
+class QWebSocketFrame
+{
+public:
+    QWebSocketFrame();
+    QWebSocketFrame(const QWebSocketFrame &other);
+
+    const QWebSocketFrame &operator =(const QWebSocketFrame &other);
+
+    QWebSocketProtocol::CloseCode getCloseCode() const;
+    QString getCloseReason() const;
+    bool isFinalFrame() const;
+    bool isControlFrame() const;
+    bool isDataFrame() const;
+    bool isContinuationFrame() const;
+    bool hasMask() const;
+    quint32 getMask() const;    //returns 0 if no mask
+    int getRsv1() const;
+    int getRsv2() const;
+    int getRsv3() const;
+    QWebSocketProtocol::OpCode getOpCode() const;
+    QByteArray getPayload() const;
+
+    void clear();       //resets all member variables, and invalidates the object
+
+    bool isValid() const;
+
+    static QWebSocketFrame readFrame(QIODevice *pIoDevice);
+
+private:
+    QWebSocketProtocol::CloseCode m_closeCode;
+    QString m_closeReason;
+    bool m_isFinalFrame;
+    quint32 m_mask;
+    int m_rsv1; //reserved field 1
+    int m_rsv2; //reserved field 2
+    int m_rsv3; //reserved field 3
+    QWebSocketProtocol::OpCode m_opCode;
+
+    quint8 m_length;        //length field as read from the header; this is 1 byte, which when 126 or 127, indicates a large payload
+    QByteArray m_payload;
+
+    bool m_isValid;
+
+    enum ProcessingState
+    {
+        PS_READ_HEADER,
+        PS_READ_PAYLOAD_LENGTH,
+        PS_READ_BIG_PAYLOAD_LENGTH,
+        PS_READ_MASK,
+        PS_READ_PAYLOAD,
+        PS_DISPATCH_RESULT,
+        PS_WAIT_FOR_MORE_DATA
+    };
+
+    void setError(QWebSocketProtocol::CloseCode code, QString closeReason);
+    bool checkValidity();
+};
+
+QT_END_NAMESPACE
+
+#endif // QWEBSOCKETFRAME_P_H
index e7d6bbc..5ba40c1 100644 (file)
@@ -23,7 +23,8 @@ PRIVATE_HEADERS += \
     $$PWD/qwebsockethandshakerequest_p.h \
     $$PWD/qwebsockethandshakeresponse_p.h \
     $$PWD/qwebsocketdataprocessor_p.h \
-    $$PWD/qwebsocketcorsauthenticator_p.h
+    $$PWD/qwebsocketcorsauthenticator_p.h \
+    $$PWD/qwebsocketframe_p.h
 
 SOURCES += \
     $$PWD/qwebsocket.cpp \
@@ -34,6 +35,7 @@ SOURCES += \
     $$PWD/qwebsockethandshakerequest_p.cpp \
     $$PWD/qwebsockethandshakeresponse_p.cpp \
     $$PWD/qwebsocketdataprocessor_p.cpp \
-    $$PWD/qwebsocketcorsauthenticator.cpp
+    $$PWD/qwebsocketcorsauthenticator.cpp \
+    $$PWD/qwebsocketframe_p.cpp
 
 HEADERS += $$PUBLIC_HEADERS $$PRIVATE_HEADERS