aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/qmltooling/packetprotocol/qpacketprotocol.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/qmltooling/packetprotocol/qpacketprotocol.cpp')
-rw-r--r--src/plugins/qmltooling/packetprotocol/qpacketprotocol.cpp299
1 files changed, 299 insertions, 0 deletions
diff --git a/src/plugins/qmltooling/packetprotocol/qpacketprotocol.cpp b/src/plugins/qmltooling/packetprotocol/qpacketprotocol.cpp
new file mode 100644
index 0000000000..d20ddf9dc0
--- /dev/null
+++ b/src/plugins/qmltooling/packetprotocol/qpacketprotocol.cpp
@@ -0,0 +1,299 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qpacketprotocol_p.h"
+
+#include <QtCore/QElapsedTimer>
+#include <private/qiodevice_p.h>
+#include <private/qobject_p.h>
+
+QT_BEGIN_NAMESPACE
+
+static const int MAX_PACKET_SIZE = 0x7FFFFFFF;
+
+/*!
+ \class QPacketProtocol
+ \internal
+
+ \brief The QPacketProtocol class encapsulates communicating discrete packets
+ across fragmented IO channels, such as TCP sockets.
+
+ QPacketProtocol makes it simple to send arbitrary sized data "packets" across
+ fragmented transports such as TCP and UDP.
+
+ As transmission boundaries are not respected, sending packets over protocols
+ like TCP frequently involves "stitching" them back together at the receiver.
+ QPacketProtocol makes this easier by performing this task for you. Packet
+ data sent using QPacketProtocol is prepended with a 4-byte size header
+ allowing the receiving QPacketProtocol to buffer the packet internally until
+ it has all been received. QPacketProtocol does not perform any sanity
+ checking on the size or on the data, so this class should only be used in
+ prototyping or trusted situations where DOS attacks are unlikely.
+
+ QPacketProtocol does not perform any communications itself. Instead it can
+ operate on any QIODevice that supports the QIODevice::readyRead() signal. A
+ logical "packet" is simply a QByteArray. The following example how to send
+ data using QPacketProtocol.
+
+ \code
+ QTcpSocket socket;
+ // ... connect socket ...
+
+ QPacketProtocol protocol(&socket);
+
+ // Send a packet
+ QDataStream packet;
+ packet << "Hello world" << 123;
+ protocol.send(packet.data());
+ \endcode
+
+ Likewise, the following shows how to read data from QPacketProtocol, assuming
+ that the QPacketProtocol::readyRead() signal has been emitted.
+
+ \code
+ // ... QPacketProtocol::readyRead() is emitted ...
+
+ int a;
+ QByteArray b;
+
+ // Receive packet
+ QDataStream packet(protocol.read());
+ p >> a >> b;
+ \endcode
+
+ \ingroup io
+*/
+
+class QPacketProtocolPrivate : public QObjectPrivate
+{
+ Q_DECLARE_PUBLIC(QPacketProtocol)
+public:
+ QPacketProtocolPrivate(QIODevice *dev);
+
+ QList<qint64> sendingPackets;
+ QList<QByteArray> packets;
+ QByteArray inProgress;
+ qint32 inProgressSize;
+ bool waitingForPacket;
+ QIODevice *dev;
+};
+
+/*!
+ Construct a QPacketProtocol instance that works on \a dev with the
+ specified \a parent.
+ */
+QPacketProtocol::QPacketProtocol(QIODevice *dev, QObject *parent)
+ : QObject(*(new QPacketProtocolPrivate(dev)), parent)
+{
+ Q_ASSERT(4 == sizeof(qint32));
+ Q_ASSERT(dev);
+
+ QObject::connect(dev, SIGNAL(readyRead()),
+ this, SLOT(readyToRead()));
+ QObject::connect(dev, SIGNAL(aboutToClose()),
+ this, SLOT(aboutToClose()));
+ QObject::connect(dev, SIGNAL(bytesWritten(qint64)),
+ this, SLOT(bytesWritten(qint64)));
+}
+
+/*!
+ \fn void QPacketProtocol::send(const QByteArray &data)
+
+ Transmit the \a packet.
+ */
+void QPacketProtocol::send(const QByteArray &data)
+{
+ Q_D(QPacketProtocol);
+
+ if (data.isEmpty())
+ return; // We don't send empty packets
+ qint64 sendSize = data.size() + sizeof(qint32);
+
+ d->sendingPackets.append(sendSize);
+ qint32 sendSize32 = sendSize;
+ qint64 writeBytes = d->dev->write((char *)&sendSize32, sizeof(qint32));
+ Q_UNUSED(writeBytes);
+ Q_ASSERT(writeBytes == sizeof(qint32));
+ writeBytes = d->dev->write(data);
+ Q_ASSERT(writeBytes == data.size());
+}
+
+/*!
+ Returns the number of received packets yet to be read.
+ */
+qint64 QPacketProtocol::packetsAvailable() const
+{
+ Q_D(const QPacketProtocol);
+ return d->packets.count();
+}
+
+/*!
+ Return the next unread packet, or an empty QByteArray if no packets
+ are available. This method does NOT block.
+ */
+QByteArray QPacketProtocol::read()
+{
+ Q_D(QPacketProtocol);
+ return d->packets.isEmpty() ? QByteArray() : d->packets.takeFirst();
+}
+
+/*!
+ This function locks until a new packet is available for reading and the
+ \l{QIODevice::}{readyRead()} signal has been emitted. The function
+ will timeout after \a msecs milliseconds; the default timeout is
+ 30000 milliseconds.
+
+ The function returns true if the readyRead() signal is emitted and
+ there is new data available for reading; otherwise it returns false
+ (if an error occurred or the operation timed out).
+ */
+
+bool QPacketProtocol::waitForReadyRead(int msecs)
+{
+ Q_D(QPacketProtocol);
+ if (!d->packets.isEmpty())
+ return true;
+
+ QElapsedTimer stopWatch;
+ stopWatch.start();
+
+ d->waitingForPacket = true;
+ do {
+ if (!d->dev->waitForReadyRead(msecs))
+ return false;
+ if (!d->waitingForPacket)
+ return true;
+ msecs = qt_subtract_from_timeout(msecs, stopWatch.elapsed());
+ } while (true);
+}
+
+/*!
+ Return the QIODevice passed to the QPacketProtocol constructor.
+*/
+void QPacketProtocol::aboutToClose()
+{
+ Q_D(QPacketProtocol);
+ d->inProgress.clear();
+ d->sendingPackets.clear();
+ d->inProgressSize = -1;
+}
+
+void QPacketProtocol::bytesWritten(qint64 bytes)
+{
+ Q_D(QPacketProtocol);
+ Q_ASSERT(!d->sendingPackets.isEmpty());
+
+ while (bytes) {
+ if (d->sendingPackets.at(0) > bytes) {
+ d->sendingPackets[0] -= bytes;
+ bytes = 0;
+ } else {
+ bytes -= d->sendingPackets.at(0);
+ d->sendingPackets.removeFirst();
+ }
+ }
+}
+
+void QPacketProtocol::readyToRead()
+{
+ Q_D(QPacketProtocol);
+ while (true) {
+ // Need to get trailing data
+ if (-1 == d->inProgressSize) {
+ // We need a size header of sizeof(qint32)
+ if (sizeof(qint32) > (uint)d->dev->bytesAvailable())
+ return;
+
+ // Read size header
+ int read = d->dev->read((char *)&d->inProgressSize, sizeof(qint32));
+ Q_ASSERT(read == sizeof(qint32));
+ Q_UNUSED(read);
+
+ // Check sizing constraints
+ if (d->inProgressSize > MAX_PACKET_SIZE) {
+ QObject::disconnect(d->dev, SIGNAL(readyRead()),
+ this, SLOT(readyToRead()));
+ QObject::disconnect(d->dev, SIGNAL(aboutToClose()),
+ this, SLOT(aboutToClose()));
+ QObject::disconnect(d->dev, SIGNAL(bytesWritten(qint64)),
+ this, SLOT(bytesWritten(qint64)));
+ d->dev = 0;
+ emit invalidPacket();
+ return;
+ }
+
+ d->inProgressSize -= sizeof(qint32);
+ } else {
+ d->inProgress.append(d->dev->read(d->inProgressSize - d->inProgress.size()));
+
+ if (d->inProgressSize == d->inProgress.size()) {
+ // Packet has arrived!
+ d->packets.append(d->inProgress);
+ d->inProgressSize = -1;
+ d->inProgress.clear();
+
+ d->waitingForPacket = false;
+ emit readyRead();
+ } else
+ return;
+ }
+ }
+}
+
+QPacketProtocolPrivate::QPacketProtocolPrivate(QIODevice *dev) :
+ inProgressSize(-1), waitingForPacket(false), dev(dev)
+{
+}
+
+/*!
+ \fn void QPacketProtocol::readyRead()
+
+ Emitted whenever a new packet is received. Applications may use
+ QPacketProtocol::read() to retrieve this packet.
+ */
+
+/*!
+ \fn void QPacketProtocol::invalidPacket()
+
+ A packet larger than the maximum allowable packet size was received. The
+ packet will be discarded and, as it indicates corruption in the protocol, no
+ further packets will be received.
+ */
+
+QT_END_NAMESPACE