From 184d66caa5f7f93b7383319c5c8985524e0dc824 Mon Sep 17 00:00:00 2001 From: Alex Trotsenko Date: Thu, 5 Mar 2015 10:52:17 +0200 Subject: QDataStream: handle incomplete reads from QIODevice This adds a way to resume reading from a stream after a ReadPastEnd error. This is done by introducing a stream read transaction mechanism that keeps read data in an internal buffer and rolls it back on failure. [ChangeLog][QtCore] Added QDataStream startTransaction(), commitTransaction(), rollbackTransaction(), abortTransaction() functions to support read transactions. Task-number: QTBUG-44418 Change-Id: Ibf946e1939a5573c4182fea7e26608947218c2d9 Reviewed-by: Oswald Buddenhagen --- src/corelib/io/qdatastream.cpp | 213 +++++++++++++++++++++++++++++++++++++---- src/corelib/io/qdatastream.h | 7 ++ src/corelib/io/qdatastream_p.h | 4 +- 3 files changed, 206 insertions(+), 18 deletions(-) (limited to 'src/corelib/io') diff --git a/src/corelib/io/qdatastream.cpp b/src/corelib/io/qdatastream.cpp index c32df4c8a6..528f99df7a 100644 --- a/src/corelib/io/qdatastream.cpp +++ b/src/corelib/io/qdatastream.cpp @@ -180,6 +180,20 @@ QT_BEGIN_NAMESPACE defined, check the \b {Related Non-Members} section of the class's documentation page. + \section1 Using Read Transactions + + When a data stream operates on an asynchronous device, the chunks of data + can arrive at arbitrary points in time. The QDataStream class implements + a transaction mechanism that provides the ability to read the data + atomically with a series of stream operators. As an example, you can + handle incomplete reads from a socket by using a transaction in a slot + connected to the readyRead() signal: + + \snippet code/src_corelib_io_qdatastream.cpp 6 + + If no full packet is received, this code restores the stream to the + initial position, after which you need to wait for more data to arrive. + \sa QTextStream, QVariant */ @@ -223,6 +237,8 @@ QT_BEGIN_NAMESPACE QDataStream member functions *****************************************************************************/ +#define Q_VOID + #undef CHECK_STREAM_PRECOND #ifndef QT_NO_DEBUG #define CHECK_STREAM_PRECOND(retVal) \ @@ -242,6 +258,12 @@ QT_BEGIN_NAMESPACE if (q_status != Ok) \ return retVal; +#define CHECK_STREAM_TRANSACTION_PRECOND(retVal) \ + if (!d || d->transactionDepth == 0) { \ + qWarning("QDataStream: No transaction in progress"); \ + return retVal; \ + } + /*! Constructs a data stream that has no I/O device. @@ -567,10 +589,175 @@ void QDataStream::setByteOrder(ByteOrder bo) \sa version(), Version */ +/*! + \since 5.7 + + Starts a new read transaction on the stream. + + Defines a restorable point within the sequence of read operations. For + sequential devices, read data will be duplicated internally to allow + recovery in case of incomplete reads. For random-access devices, + this function saves the current position of the stream. Call + commitTransaction(), rollbackTransaction(), or abortTransaction() to + finish the current transaction. + + Once a transaction is started, subsequent calls to this function will make + the transaction recursive. Inner transactions act as agents of the + outermost transaction (i.e., report the status of read operations to the + outermost transaction, which can restore the position of the stream). + + \note Restoring to the point of the nested startTransaction() call is not + supported. + + When an error occurs during a transaction (including an inner transaction + failing), reading from the data stream is suspended (all subsequent read + operations return empty/zero values) and subsequent inner transactions are + forced to fail. Starting a new outermost transaction recovers from this + state. This behavior makes it unnecessary to error-check every read + operation separately. + + \sa commitTransaction(), rollbackTransaction(), abortTransaction() +*/ + +void QDataStream::startTransaction() +{ + CHECK_STREAM_PRECOND(Q_VOID) + + if (d == 0) + d.reset(new QDataStreamPrivate()); + + if (++d->transactionDepth == 1) { + dev->startTransaction(); + resetStatus(); + } +} + +/*! + \since 5.7 + + Completes a read transaction. Returns \c true if no read errors have + occurred during the transaction; otherwise returns \c false. + + If called on an inner transaction, committing will be postponed until + the outermost commitTransaction(), rollbackTransaction(), or + abortTransaction() call occurs. + + Otherwise, if the stream status indicates reading past the end of the + data, this function restores the stream data to the point of the + startTransaction() call. When this situation occurs, you need to wait for + more data to arrive, after which you start a new transaction. If the data + stream has read corrupt data or any of the inner transactions was aborted, + this function aborts the transaction. + + \sa startTransaction(), rollbackTransaction(), abortTransaction() +*/ + +bool QDataStream::commitTransaction() +{ + CHECK_STREAM_TRANSACTION_PRECOND(false) + if (--d->transactionDepth == 0) { + CHECK_STREAM_PRECOND(false) + + if (q_status == ReadPastEnd) { + dev->rollbackTransaction(); + return false; + } + dev->commitTransaction(); + } + return q_status == Ok; +} + +/*! + \since 5.7 + + Reverts a read transaction. + + This function is commonly used to rollback the transaction when an + incomplete read was detected prior to committing the transaction. + + If called on an inner transaction, reverting is delegated to the outermost + transaction, and subsequently started inner transactions are forced to + fail. + + For the outermost transaction, restores the stream data to the point of + the startTransaction() call. If the data stream has read corrupt data or + any of the inner transactions was aborted, this function aborts the + transaction. + + If the preceding stream operations were successful, sets the status of the + data stream to \value ReadPastEnd. + + \sa startTransaction(), commitTransaction(), abortTransaction() +*/ + +void QDataStream::rollbackTransaction() +{ + setStatus(ReadPastEnd); + + CHECK_STREAM_TRANSACTION_PRECOND(Q_VOID) + if (--d->transactionDepth != 0) + return; + + CHECK_STREAM_PRECOND(Q_VOID) + if (q_status == ReadPastEnd) + dev->rollbackTransaction(); + else + dev->commitTransaction(); +} + +/*! + \since 5.7 + + Aborts a read transaction. + + This function is commonly used to discard the transaction after + higher-level protocol errors or loss of stream synchronization. + + If called on an inner transaction, aborting is delegated to the outermost + transaction, and subsequently started inner transactions are forced to + fail. + + For the outermost transaction, discards the restoration point and any + internally duplicated data of the stream. Will not affect the current + read position of the stream. + + Sets the status of the data stream to \value ReadCorruptData. + + \sa startTransaction(), commitTransaction(), rollbackTransaction() +*/ + +void QDataStream::abortTransaction() +{ + q_status = ReadCorruptData; + + CHECK_STREAM_TRANSACTION_PRECOND(Q_VOID) + if (--d->transactionDepth != 0) + return; + + CHECK_STREAM_PRECOND(Q_VOID) + dev->commitTransaction(); +} + /***************************************************************************** QDataStream read functions *****************************************************************************/ +/*! + \internal +*/ + +int QDataStream::readBlock(char *data, int len) +{ + // Disable reads on failure in transacted stream + if (q_status != Ok && dev->isTransactionStarted()) + return -1; + + const int readResult = dev->read(data, len); + if (readResult != len) + setStatus(ReadPastEnd); + return readResult; +} + /*! \fn QDataStream &QDataStream::operator>>(quint8 &i) \overload @@ -589,9 +776,7 @@ QDataStream &QDataStream::operator>>(qint8 &i) i = 0; CHECK_STREAM_PRECOND(*this) char c; - if (!dev->getChar(&c)) - setStatus(ReadPastEnd); - else + if (readBlock(&c, 1) == 1) i = qint8(c); return *this; } @@ -616,9 +801,8 @@ QDataStream &QDataStream::operator>>(qint16 &i) { i = 0; CHECK_STREAM_PRECOND(*this) - if (dev->read((char *)&i, 2) != 2) { + if (readBlock(reinterpret_cast(&i), 2) != 2) { i = 0; - setStatus(ReadPastEnd); } else { if (!noswap) { i = qbswap(i); @@ -647,9 +831,8 @@ QDataStream &QDataStream::operator>>(qint32 &i) { i = 0; CHECK_STREAM_PRECOND(*this) - if (dev->read((char *)&i, 4) != 4) { + if (readBlock(reinterpret_cast(&i), 4) != 4) { i = 0; - setStatus(ReadPastEnd); } else { if (!noswap) { i = qbswap(i); @@ -682,9 +865,8 @@ QDataStream &QDataStream::operator>>(qint64 &i) *this >> i2 >> i1; i = ((quint64)i1 << 32) + i2; } else { - if (dev->read((char *)&i, 8) != 8) { + if (readBlock(reinterpret_cast(&i), 8) != 8) { i = qint64(0); - setStatus(ReadPastEnd); } else { if (!noswap) { i = qbswap(i); @@ -728,9 +910,8 @@ QDataStream &QDataStream::operator>>(float &f) f = 0.0f; CHECK_STREAM_PRECOND(*this) - if (dev->read((char *)&f, 4) != 4) { + if (readBlock(reinterpret_cast(&f), 4) != 4) { f = 0.0f; - setStatus(ReadPastEnd); } else { if (!noswap) { union { @@ -766,9 +947,8 @@ QDataStream &QDataStream::operator>>(double &f) f = 0.0; CHECK_STREAM_PRECOND(*this) - if (dev->read((char *)&f, 8) != 8) { + if (readBlock(reinterpret_cast(&f), 8) != 8) { f = 0.0; - setStatus(ReadPastEnd); } else { if (!noswap) { union { @@ -845,9 +1025,8 @@ QDataStream &QDataStream::readBytes(char *&s, uint &l) memcpy(curBuf, prevBuf, allocated); delete [] prevBuf; } - if (dev->read(curBuf + allocated, blockSize) != blockSize) { + if (readBlock(curBuf + allocated, blockSize) != blockSize) { delete [] curBuf; - setStatus(ReadPastEnd); return *this; } allocated += blockSize; @@ -871,7 +1050,7 @@ QDataStream &QDataStream::readBytes(char *&s, uint &l) int QDataStream::readRawData(char *s, int len) { CHECK_STREAM_PRECOND(-1) - return dev->read(s, len); + return readBlock(s, len); } @@ -1154,7 +1333,7 @@ int QDataStream::skipRawData(int len) while (len > 0) { int blockSize = qMin(len, (int)sizeof(buf)); - int n = dev->read(buf, blockSize); + int n = readBlock(buf, blockSize); if (n == -1) return -1; if (n == 0) diff --git a/src/corelib/io/qdatastream.h b/src/corelib/io/qdatastream.h index 744829c659..a72842671a 100644 --- a/src/corelib/io/qdatastream.h +++ b/src/corelib/io/qdatastream.h @@ -168,6 +168,11 @@ public: int skipRawData(int len); + void startTransaction(); + bool commitTransaction(); + void rollbackTransaction(); + void abortTransaction(); + private: Q_DISABLE_COPY(QDataStream) @@ -179,6 +184,8 @@ private: ByteOrder byteorder; int ver; Status q_status; + + int readBlock(char *data, int len); }; diff --git a/src/corelib/io/qdatastream_p.h b/src/corelib/io/qdatastream_p.h index 82c8fe251f..677a53f90a 100644 --- a/src/corelib/io/qdatastream_p.h +++ b/src/corelib/io/qdatastream_p.h @@ -53,9 +53,11 @@ QT_BEGIN_NAMESPACE class QDataStreamPrivate { public: - QDataStreamPrivate() : floatingPointPrecision(QDataStream::DoublePrecision) { } + QDataStreamPrivate() : floatingPointPrecision(QDataStream::DoublePrecision), + transactionDepth(0) { } QDataStream::FloatingPointPrecision floatingPointPrecision; + int transactionDepth; }; #endif -- cgit v1.2.3