qpacketprotocol.cpp 14.2 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
Eike Ziller's avatar
Eike Ziller committed
3 4
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
7
**
hjk's avatar
hjk committed
8 9 10 11
** 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
Eike Ziller's avatar
Eike Ziller committed
12 13
** a written agreement between you and The Qt Company.  For licensing terms and
** conditions see http://www.qt.io/terms-conditions.  For further information
Eike Ziller's avatar
Eike Ziller committed
14
** use the contact form at http://www.qt.io/contact-us.
15 16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18 19 20 21 22 23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24
**
Eike Ziller's avatar
Eike Ziller committed
25 26
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company LGPL Exception
27 28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
30

31
#include "qpacketprotocol.h"
32

33 34
#include <qbuffer.h>
#include <qelapsedtimer.h>
35

36
namespace QmlDebug {
37

hjk's avatar
hjk committed
38
static const unsigned int MAX_PACKET_SIZE = 0x7FFFFFFF;
39 40 41 42 43 44 45 46

/*!
  \class QPacketProtocol
  \internal

  \brief The QPacketProtocol class encapsulates communicating discrete packets
  across fragmented IO channels, such as TCP sockets.

47
  QPacketProtocol makes it simple to send arbitrary sized data "packets" across
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
  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 encapsulated by the companion QPacket class.  The
  following example shows two ways to send data using QPacketProtocol.  The
  transmitted data is equivalent in both.

  \code
  QTcpSocket socket;
  // ... connect socket ...

  QPacketProtocol protocol(&socket);

  // Send packet the quick way
  protocol.send() << "Hello world" << 123;

  // Send packet the longer way
  QPacket packet;
  packet << "Hello world" << 123;
  protocol.send(packet);
  \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 the quick way
  protocol.read() >> a >> b;

  // Receive packet the longer way
  QPacket packet = protocol.read();
  p >> a >> b;
  \endcode

  \ingroup io
  \sa QPacket
*/

class QPacketProtocolPrivate : public QObject
{
hjk's avatar
hjk committed
103 104
    Q_OBJECT

105
public:
hjk's avatar
hjk committed
106
    QPacketProtocolPrivate(QPacketProtocol *parent, QIODevice *_dev)
107
        : QObject(parent), inProgressSize(-1), maxPacketSize(MAX_PACKET_SIZE),
108
          waitingForPacket(false), dev(_dev)
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
    {
        Q_ASSERT(4 == sizeof(qint32));

        QObject::connect(this, SIGNAL(readyRead()),
                         parent, SIGNAL(readyRead()));
        QObject::connect(this, SIGNAL(packetWritten()),
                         parent, SIGNAL(packetWritten()));
        QObject::connect(this, SIGNAL(invalidPacket()),
                         parent, SIGNAL(invalidPacket()));
        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)));
    }

Q_SIGNALS:
    void readyRead();
    void packetWritten();
    void invalidPacket();

public Q_SLOTS:
    void aboutToClose()
    {
        inProgress.clear();
        sendingPackets.clear();
        inProgressSize = -1;
    }

    void bytesWritten(qint64 bytes)
    {
        Q_ASSERT(!sendingPackets.isEmpty());

hjk's avatar
hjk committed
143 144
        while (bytes) {
            if (sendingPackets.at(0) > bytes) {
145 146 147 148 149 150 151 152 153 154 155 156
                sendingPackets[0] -= bytes;
                bytes = 0;
            } else {
                bytes -= sendingPackets.at(0);
                sendingPackets.removeFirst();
                emit packetWritten();
            }
        }
    }

    void readyToRead()
    {
hjk's avatar
hjk committed
157
        while (true) {
158
            // Need to get trailing data
hjk's avatar
hjk committed
159
            if (-1 == inProgressSize) {
160
                // We need a size header of sizeof(qint32)
hjk's avatar
hjk committed
161
                if (sizeof(qint32) > (uint)dev->bytesAvailable())
162 163 164 165 166 167 168 169
                    return;

                // Read size header
                int read = dev->read((char *)&inProgressSize, sizeof(qint32));
                Q_ASSERT(read == sizeof(qint32));
                Q_UNUSED(read);

                // Check sizing constraints
hjk's avatar
hjk committed
170
                if (inProgressSize > maxPacketSize) {
171 172 173 174 175 176 177 178 179 180 181 182 183
                    QObject::disconnect(dev, SIGNAL(readyRead()),
                                        this, SLOT(readyToRead()));
                    QObject::disconnect(dev, SIGNAL(aboutToClose()),
                                        this, SLOT(aboutToClose()));
                    QObject::disconnect(dev, SIGNAL(bytesWritten(qint64)),
                                        this, SLOT(bytesWritten(qint64)));
                    emit invalidPacket();
                    return;
                }

                inProgressSize -= sizeof(qint32);
            } else {
                inProgress.append(dev->read(inProgressSize - inProgress.size()));
184

hjk's avatar
hjk committed
185
                if (inProgressSize == inProgress.size()) {
186 187 188 189
                    // Packet has arrived!
                    packets.append(inProgress);
                    inProgressSize = -1;
                    inProgress.clear();
190

191
                    waitingForPacket = false;
192 193 194
                    emit readyRead();
                } else
                    return;
195 196 197 198 199 200 201 202 203 204
            }
        }
    }

public:
    QList<qint64> sendingPackets;
    QList<QByteArray> packets;
    QByteArray inProgress;
    qint32 inProgressSize;
    qint32 maxPacketSize;
205 206
    bool waitingForPacket;
    QIODevice *dev;
207 208 209
};

/*!
Leena Miettinen's avatar
Leena Miettinen committed
210
  Constructs a QPacketProtocol instance that works on \a dev with the
211 212
  specified \a parent.
 */
213
QPacketProtocol::QPacketProtocol(QIODevice *dev, QObject *parent)
214
    : QObject(parent), d(new QPacketProtocolPrivate(this, dev))
215 216 217 218 219 220 221 222 223 224 225 226 227
{
    Q_ASSERT(dev);
}

/*!
  Destroys the QPacketProtocol instance.
 */
QPacketProtocol::~QPacketProtocol()
{
}

/*!
  Returns the maximum packet size allowed.  By default this is
228
  2,147,483,647 bytes.
Orgad Shaneh's avatar
Orgad Shaneh committed
229

230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
  If a packet claiming to be larger than the maximum packet size is received,
  the QPacketProtocol::invalidPacket() signal is emitted.

  \sa QPacketProtocol::setMaximumPacketSize()
 */
qint32 QPacketProtocol::maximumPacketSize() const
{
    return d->maxPacketSize;
}

/*!
  Sets the maximum allowable packet size to \a max.

  \sa QPacketProtocol::maximumPacketSize()
 */
qint32 QPacketProtocol::setMaximumPacketSize(qint32 max)
{
hjk's avatar
hjk committed
247
    if (max > (signed)sizeof(qint32))
248 249 250 251 252 253 254 255 256 257 258
        d->maxPacketSize = max;
    return d->maxPacketSize;
}

/*!
  Returns a streamable object that is transmitted on destruction.  For example

  \code
  protocol.send() << "Hello world" << 123;
  \endcode

259
  will send a packet containing "Hello world" and 123.  To construct more
260 261 262 263 264 265 266 267
  complex packets, explicitly construct a QPacket instance.
 */
QPacketAutoSend QPacketProtocol::send()
{
    return QPacketAutoSend(this);
}

/*!
Leena Miettinen's avatar
Leena Miettinen committed
268
  Transmits the packet \a p.
269 270 271
 */
void QPacketProtocol::send(const QPacket & p)
{
hjk's avatar
hjk committed
272
    if (p.b.isEmpty())
273 274 275 276 277 278 279 280 281 282
        return; // We don't send empty packets

    qint64 sendSize = p.b.size() + sizeof(qint32);

    d->sendingPackets.append(sendSize);
    qint32 sendSize32 = sendSize;
    qint64 writeBytes = d->dev->write((char *)&sendSize32, sizeof(qint32));
    Q_ASSERT(writeBytes == sizeof(qint32));
    writeBytes = d->dev->write(p.b);
    Q_ASSERT(writeBytes == p.b.size());
283
    Q_UNUSED(writeBytes); // For building in release mode.
284 285 286 287 288 289 290 291 292 293 294
}

/*!
  Returns the number of received packets yet to be read.
  */
qint64 QPacketProtocol::packetsAvailable() const
{
    return d->packets.count();
}

/*!
Leena Miettinen's avatar
Leena Miettinen committed
295
  Discards any unread packets.
296 297 298 299 300 301 302
  */
void QPacketProtocol::clear()
{
    d->packets.clear();
}

/*!
Leena Miettinen's avatar
Leena Miettinen committed
303
  Returns the next unread packet, or an invalid QPacket instance if no packets
304
  are available.  This function does NOT block.
305 306 307
  */
QPacket QPacketProtocol::read()
{
hjk's avatar
hjk committed
308
    if (0 == d->packets.count())
309 310 311 312 313 314 315
        return QPacket();

    QPacket rv(d->packets.at(0));
    d->packets.removeFirst();
    return rv;
}

316

Leena Miettinen's avatar
Leena Miettinen committed
317 318
/*!
   Returns the difference between \a msecs and \a elapsed. If msecs is -1,
319 320 321 322 323 324 325 326 327 328 329 330
   however, -1 is returned.
*/
static int qt_timeout_value(int msecs, int elapsed)
{
    if (msecs == -1)
        return -1;

    int timeout = msecs - elapsed;
    return timeout < 0 ? 0 : timeout;
}

/*!
Leena Miettinen's avatar
Leena Miettinen committed
331
  Locks until a new packet is available for reading and the
332 333 334 335
  \l{QIODevice::}{readyRead()} signal has been emitted. The function
  will timeout after \a msecs milliseconds; the default timeout is
  30000 milliseconds.

Leena Miettinen's avatar
Leena Miettinen committed
336 337
  Returns true if the readyRead() signal is emitted and
  there is new data available for reading; otherwise returns false
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
  (if an error occurred or the operation timed out).
  */

bool QPacketProtocol::waitForReadyRead(int msecs)
{
    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_timeout_value(msecs, stopWatch.elapsed());
    } while (true);
}

359
/*!
Leena Miettinen's avatar
Leena Miettinen committed
360
  Returns the QIODevice passed to the QPacketProtocol constructor.
361
*/
362
QIODevice *QPacketProtocol::device()
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
{
    return d->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.
 */

/*!
  \fn void QPacketProtocol::packetWritten()

Leena Miettinen's avatar
Leena Miettinen committed
385
  Emitted each time a packet is completely written to the device.  This signal
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
  may be used for communications flow control.
 */

/*!
  \class QPacket
  \internal

  \brief The QPacket class encapsulates an unfragmentable packet of data to be
  transmitted by QPacketProtocol.

  The QPacket class works together with QPacketProtocol to make it simple to
  send arbitrary sized data "packets" across fragmented transports such as TCP
  and UDP.

  QPacket provides a QDataStream interface to an unfragmentable packet.
  Applications should construct a QPacket, propagate it with data and then
  transmit it over a QPacketProtocol instance.  For example:
  \code
  QPacketProtocol protocol(...);

  QPacket myPacket;
  myPacket << "Hello world!" << 123;
  protocol.send(myPacket);
  \endcode

  As long as both ends of the connection are using the QPacketProtocol class,
  the data within this packet will be delivered unfragmented at the other end,
  ready for extraction.

  \code
  QByteArray greeting;
  int count;

  QPacket myPacket = protocol.read();

  myPacket >> greeting >> count;
  \endcode

  Only packets returned from QPacketProtocol::read() may be read from.  QPacket
425 426
  instances constructed by directly by applications are for transmission only
  and are considered "write only".  Attempting to read data from them will
427 428 429 430 431 432 433 434 435 436
  result in undefined behavior.

  \ingroup io
  \sa QPacketProtocol
 */

/*!
  Constructs an empty write-only packet.
  */
QPacket::QPacket()
437
    : QDataStream(), buf(0)
438 439 440 441
{
    buf = new QBuffer(&b);
    buf->open(QIODevice::WriteOnly);
    setDevice(buf);
442
    setVersion(QDataStream::Qt_4_7);
443 444 445 446 447 448 449
}

/*!
  Destroys the QPacket instance.
  */
QPacket::~QPacket()
{
hjk's avatar
hjk committed
450
    if (buf) {
451 452 453 454 455 456 457
        delete buf;
        buf = 0;
    }
}

/*!
  Creates a copy of \a other.  The initial stream positions are shared, but the
Tobias Hunger's avatar
Tobias Hunger committed
458
  two packets are otherwise independent.
459 460
 */
QPacket::QPacket(const QPacket & other)
461
    : QDataStream(), b(other.b), buf(0)
462 463 464 465 466 467 468 469 470 471
{
    buf = new QBuffer(&b);
    buf->open(other.buf->openMode());
    setDevice(buf);
}

/*!
  \internal
  */
QPacket::QPacket(const QByteArray & ba)
472
    : QDataStream(), b(ba), buf(0)
473 474 475 476 477 478 479 480 481 482 483 484 485 486
{
    buf = new QBuffer(&b);
    buf->open(QIODevice::ReadOnly);
    setDevice(buf);
}

/*!
  Returns true if this packet is empty - that is, contains no data.
  */
bool QPacket::isEmpty() const
{
    return b.isEmpty();
}

487 488 489 490 491 492 493 494
/*!
  Returns raw packet data.
  */
QByteArray QPacket::data() const
{
    return b;
}

495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524
/*!
  Clears data in the packet.  This is useful for reusing one writable packet.
  For example
  \code
  QPacketProtocol protocol(...);

  QPacket packet;

  packet << "Hello world!" << 123;
  protocol.send(packet);

  packet.clear();
  packet << "Goodbyte world!" << 789;
  protocol.send(packet);
  \endcode
 */
void QPacket::clear()
{
    QBuffer::OpenMode oldMode = buf->openMode();
    buf->close();
    b.clear();
    buf->setBuffer(&b); // reset QBuffer internals with new size of b.
    buf->open(oldMode);
}

/*!
  \class QPacketAutoSend
  \internal

  */
525
QPacketAutoSend::QPacketAutoSend(QPacketProtocol *_p)
526
    : QPacket(), p(_p)
527 528 529 530 531
{
}

QPacketAutoSend::~QPacketAutoSend()
{
hjk's avatar
hjk committed
532
    if (!b.isEmpty())
533 534 535
        p->send(*this);
}

536
} // namespace QmlDebug
537 538

#include <qpacketprotocol.moc>