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 | |
48 | QT_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 | |
103 | class QPacketProtocolPrivate : public QObjectPrivate |
104 | { |
105 | Q_DECLARE_PUBLIC(QPacketProtocol) |
106 | public: |
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 | */ |
124 | QPacketProtocol::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 | */ |
139 | void 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 | */ |
165 | qint64 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 | */ |
175 | QByteArray 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 | |
192 | bool 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 | |
211 | void 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 | |
227 | void 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 | |
282 | QPacketProtocolPrivate::QPacketProtocolPrivate(QIODevice *dev) : |
283 | inProgressSize(-1), waitingForPacket(false), dev(dev) |
284 | { |
285 | } |
286 | |
287 | bool 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 | |
299 | bool 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 | |
326 | QT_END_NAMESPACE |
327 | |