1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQml module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qpacketprotocol_p.h"
41
42#include <QtCore/QElapsedTimer>
43#include <QtCore/QtEndian>
44
45#include <private/qiodevice_p.h>
46#include <private/qobject_p.h>
47
48QT_BEGIN_NAMESPACE
49
50/*!
51 \class QPacketProtocol
52 \internal
53
54 \brief The QPacketProtocol class encapsulates communicating discrete packets
55 across fragmented IO channels, such as TCP sockets.
56
57 QPacketProtocol makes it simple to send arbitrary sized data "packets" across
58 fragmented transports such as TCP and UDP.
59
60 As transmission boundaries are not respected, sending packets over protocols
61 like TCP frequently involves "stitching" them back together at the receiver.
62 QPacketProtocol makes this easier by performing this task for you. Packet
63 data sent using QPacketProtocol is prepended with a 4-byte size header
64 allowing the receiving QPacketProtocol to buffer the packet internally until
65 it has all been received. QPacketProtocol does not perform any sanity
66 checking on the size or on the data, so this class should only be used in
67 prototyping or trusted situations where DOS attacks are unlikely.
68
69 QPacketProtocol does not perform any communications itself. Instead it can
70 operate on any QIODevice that supports the QIODevice::readyRead() signal. A
71 logical "packet" is simply a QByteArray. The following example how to send
72 data using QPacketProtocol.
73
74 \code
75 QTcpSocket socket;
76 // ... connect socket ...
77
78 QPacketProtocol protocol(&socket);
79
80 // Send a packet
81 QDataStream packet;
82 packet << "Hello world" << 123;
83 protocol.send(packet.data());
84 \endcode
85
86 Likewise, the following shows how to read data from QPacketProtocol, assuming
87 that the QPacketProtocol::readyRead() signal has been emitted.
88
89 \code
90 // ... QPacketProtocol::readyRead() is emitted ...
91
92 int a;
93 QByteArray b;
94
95 // Receive packet
96 QDataStream packet(protocol.read());
97 p >> a >> b;
98 \endcode
99
100 \ingroup io
101*/
102
103class QPacketProtocolPrivate : public QObjectPrivate
104{
105 Q_DECLARE_PUBLIC(QPacketProtocol)
106public:
107 QPacketProtocolPrivate(QIODevice *dev);
108
109 bool writeToDevice(const char *bytes, qint64 size);
110 bool readFromDevice(char *buffer, qint64 size);
111
112 QList<qint32> sendingPackets;
113 QList<QByteArray> packets;
114 QByteArray inProgress;
115 qint32 inProgressSize;
116 bool waitingForPacket;
117 QIODevice *dev;
118};
119
120/*!
121 Construct a QPacketProtocol instance that works on \a dev with the
122 specified \a parent.
123 */
124QPacketProtocol::QPacketProtocol(QIODevice *dev, QObject *parent)
125 : QObject(*(new QPacketProtocolPrivate(dev)), parent)
126{
127 Q_ASSERT(4 == sizeof(qint32));
128 Q_ASSERT(dev);
129
130 QObject::connect(sender: dev, signal: &QIODevice::readyRead, receiver: this, slot: &QPacketProtocol::readyToRead);
131 QObject::connect(sender: dev, signal: &QIODevice::bytesWritten, receiver: this, slot: &QPacketProtocol::bytesWritten);
132}
133
134/*!
135 \fn void QPacketProtocol::send(const QByteArray &data)
136
137 Transmit the \a packet.
138 */
139void QPacketProtocol::send(const QByteArray &data)
140{
141 Q_D(QPacketProtocol);
142 static const qint32 maxSize = std::numeric_limits<qint32>::max() - sizeof(qint32);
143
144 if (data.isEmpty())
145 return; // We don't send empty packets
146
147 if (data.size() > maxSize) {
148 emit error();
149 return;
150 }
151
152 const qint32 sendSize = data.size() + static_cast<qint32>(sizeof(qint32));
153 d->sendingPackets.append(t: sendSize);
154
155 qint32 sendSizeLE = qToLittleEndian(source: sendSize);
156 if (!d->writeToDevice(bytes: (const char *)&sendSizeLE, size: sizeof(qint32))
157 || !d->writeToDevice(bytes: data.data(), size: data.size())) {
158 emit error();
159 }
160}
161
162/*!
163 Returns the number of received packets yet to be read.
164 */
165qint64 QPacketProtocol::packetsAvailable() const
166{
167 Q_D(const QPacketProtocol);
168 return d->packets.count();
169}
170
171/*!
172 Return the next unread packet, or an empty QByteArray if no packets
173 are available. This method does NOT block.
174 */
175QByteArray QPacketProtocol::read()
176{
177 Q_D(QPacketProtocol);
178 return d->packets.isEmpty() ? QByteArray() : d->packets.takeFirst();
179}
180
181/*!
182 This function locks until a new packet is available for reading and the
183 \l{QIODevice::}{readyRead()} signal has been emitted. The function
184 will timeout after \a msecs milliseconds; the default timeout is
185 30000 milliseconds.
186
187 The function returns true if the readyRead() signal is emitted and
188 there is new data available for reading; otherwise it returns false
189 (if an error occurred or the operation timed out).
190 */
191
192bool QPacketProtocol::waitForReadyRead(int msecs)
193{
194 Q_D(QPacketProtocol);
195 if (!d->packets.isEmpty())
196 return true;
197
198 QElapsedTimer stopWatch;
199 stopWatch.start();
200
201 d->waitingForPacket = true;
202 do {
203 if (!d->dev->waitForReadyRead(msecs))
204 return false;
205 if (!d->waitingForPacket)
206 return true;
207 msecs = qt_subtract_from_timeout(timeout: msecs, elapsed: stopWatch.elapsed());
208 } while (true);
209}
210
211void QPacketProtocol::bytesWritten(qint64 bytes)
212{
213 Q_D(QPacketProtocol);
214 Q_ASSERT(!d->sendingPackets.isEmpty());
215
216 while (bytes) {
217 if (d->sendingPackets.at(i: 0) > bytes) {
218 d->sendingPackets[0] -= bytes;
219 bytes = 0;
220 } else {
221 bytes -= d->sendingPackets.at(i: 0);
222 d->sendingPackets.removeFirst();
223 }
224 }
225}
226
227void QPacketProtocol::readyToRead()
228{
229 Q_D(QPacketProtocol);
230 while (true) {
231 // Need to get trailing data
232 if (-1 == d->inProgressSize) {
233 // We need a size header of sizeof(qint32)
234 if (static_cast<qint64>(sizeof(qint32)) > d->dev->bytesAvailable())
235 return;
236
237 // Read size header
238 qint32 inProgressSizeLE;
239 if (!d->readFromDevice(buffer: (char *)&inProgressSizeLE, size: sizeof(qint32))) {
240 emit error();
241 return;
242 }
243 d->inProgressSize = qFromLittleEndian(source: inProgressSizeLE);
244
245 // Check sizing constraints
246 if (d->inProgressSize < qint32(sizeof(qint32))) {
247 disconnect(sender: d->dev, signal: &QIODevice::readyRead, receiver: this, slot: &QPacketProtocol::readyToRead);
248 disconnect(sender: d->dev, signal: &QIODevice::bytesWritten, receiver: this, slot: &QPacketProtocol::bytesWritten);
249 d->dev = nullptr;
250 emit error();
251 return;
252 }
253
254 d->inProgressSize -= sizeof(qint32);
255 } else {
256
257 const int bytesToRead = static_cast<int>(
258 qMin(a: d->dev->bytesAvailable(),
259 b: static_cast<qint64>(d->inProgressSize - d->inProgress.size())));
260
261 QByteArray toRead(bytesToRead, Qt::Uninitialized);
262 if (!d->readFromDevice(buffer: toRead.data(), size: toRead.length())) {
263 emit error();
264 return;
265 }
266
267 d->inProgress.append(a: toRead);
268 if (d->inProgressSize == d->inProgress.size()) {
269 // Packet has arrived!
270 d->packets.append(t: d->inProgress);
271 d->inProgressSize = -1;
272 d->inProgress.clear();
273
274 d->waitingForPacket = false;
275 emit readyRead();
276 } else
277 return;
278 }
279 }
280}
281
282QPacketProtocolPrivate::QPacketProtocolPrivate(QIODevice *dev) :
283 inProgressSize(-1), waitingForPacket(false), dev(dev)
284{
285}
286
287bool QPacketProtocolPrivate::writeToDevice(const char *bytes, qint64 size)
288{
289 qint64 totalWritten = 0;
290 while (totalWritten < size) {
291 const qint64 chunkSize = dev->write(data: bytes + totalWritten, len: size - totalWritten);
292 if (chunkSize < 0)
293 return false;
294 totalWritten += chunkSize;
295 }
296 return totalWritten == size;
297}
298
299bool QPacketProtocolPrivate::readFromDevice(char *buffer, qint64 size)
300{
301 qint64 totalRead = 0;
302 while (totalRead < size) {
303 const qint64 chunkSize = dev->read(data: buffer + totalRead, maxlen: size - totalRead);
304 if (chunkSize < 0)
305 return false;
306 totalRead += chunkSize;
307 }
308 return totalRead == size;
309}
310
311/*!
312 \fn void QPacketProtocol::readyRead()
313
314 Emitted whenever a new packet is received. Applications may use
315 QPacketProtocol::read() to retrieve this packet.
316 */
317
318/*!
319 \fn void QPacketProtocol::invalidPacket()
320
321 A packet larger than the maximum allowable packet size was received. The
322 packet will be discarded and, as it indicates corruption in the protocol, no
323 further packets will be received.
324 */
325
326QT_END_NAMESPACE
327

source code of qtdeclarative/src/plugins/qmltooling/packetprotocol/qpacketprotocol.cpp