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

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