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