diff options
Diffstat (limited to 'src/network/socket/qsctpsocket.cpp')
-rw-r--r-- | src/network/socket/qsctpsocket.cpp | 547 |
1 files changed, 547 insertions, 0 deletions
diff --git a/src/network/socket/qsctpsocket.cpp b/src/network/socket/qsctpsocket.cpp new file mode 100644 index 0000000000..cb07e80299 --- /dev/null +++ b/src/network/socket/qsctpsocket.cpp @@ -0,0 +1,547 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Alex Trotsenko <alex1973tr@gmail.com> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QSCTPSOCKET_DEBUG + +/*! + \class QSctpSocket + \since 5.8 + + \brief The QSctpSocket class provides an SCTP socket. + + \ingroup network + \inmodule QtNetwork + + SCTP (Stream Control Transmission Protocol) is a transport layer + protocol serving in a similar role as the popular protocols TCP + and UDP. Like UDP, SCTP is message-oriented, but it ensures reliable, + in-sequence transport of messages with congestion control like + TCP. + + SCTP is connection-oriented protocol, which provides the complete + simultaneous transmission of multiple data streams between + endpoints. This multi-streaming allows data to be delivered by + independent channels, so that if there is data loss in one stream, + delivery will not be affected for the other streams. + + Being message-oriented, SCTP transports a sequence of messages, + rather than transporting an unbroken stream of bytes as does TCP. + Like in UDP, in SCTP a sender sends a message in one operation, + and that exact message is passed to the receiving application + process in one operation. But unlike UDP, the delivery is + guaranteed. + + It also supports multi-homing, meaning that a connected endpoint + can have alternate IP addresses associated with it in order to + route around network failure or changing conditions. + + QSctpSocket is a convenience subclass of QTcpSocket that allows + you to emulate TCP data stream over SCTP or establish an SCTP + connection for reliable datagram service. + + QSctpSocket can operate in one of two possible modes: + + \list + \li Continuous byte stream (TCP emulation). + \li Multi-streamed datagram mode. + \endlist + + To set a continuous byte stream mode, instantiate QSctpSocket and + call setMaxChannelCount() with a negative value. This gives the + ability to use QSctpSocket as a regular buffered QTcpSocket. You + can call connectToHost() to initiate connection with endpoint, + write() to transmit and read() to receive data from the peer, but + you cannot distinguish message boundaries. + + By default, QSctpSocket operates in datagram mode. Before + connecting, call setMaxChannelCount() to set the maximum number of + channels that the application is prepared to support. This number + is a parameter negotiated with the remote endpoint and its value + can be bounded by the operating system. The default value of 0 + indicates to use the peer's value. If both endpoints have default + values, then number of connection channels is system-dependent. + After establishing a connection, you can fetch the actual number + of channels by calling readChannelCount() and writeChannelCount(). + + \snippet code/src_network_socket_qsctpsocket.cpp 0 + + In datagram mode, QSctpSocket performs the buffering of datagrams + independently for each channel. You can queue a datagram to the + buffer of the current channel by calling writeDatagram() and read + a pending datagram by calling readDatagram() respectively. + + Using the standard QIODevice functions read(), readLine(), write(), + etc. is allowed in datagram mode with the same limitations as in + continuous byte stream mode. + + \note This feature is not supported on the Windows platform. + + \sa QSctpServer, QTcpSocket, QAbstractSocket +*/ + +#include "qsctpsocket.h" +#include "qsctpsocket_p.h" + +#include "qabstractsocketengine_p.h" +#include "private/qbytearray_p.h" + +#ifdef QSCTPSOCKET_DEBUG +#include <qdebug.h> +#endif + +QT_BEGIN_NAMESPACE + +/*! \internal +*/ +QSctpSocketPrivate::QSctpSocketPrivate() + : maxChannelCount(0) +{ +} + +/*! \internal +*/ +QSctpSocketPrivate::~QSctpSocketPrivate() +{ +} + +/*! \internal +*/ +bool QSctpSocketPrivate::canReadNotification() +{ + Q_Q(QSctpSocket); +#if defined (QSCTPSOCKET_DEBUG) + qDebug("QSctpSocketPrivate::canReadNotification()"); +#endif + + // Handle TCP emulation mode in the base implementation. + if (!q->inDatagramMode()) + return QTcpSocketPrivate::canReadNotification(); + + const int savedCurrentChannel = currentReadChannel; + bool currentChannelRead = false; + do { + int datagramSize = incomingDatagram.size(); + QIpPacketHeader header; + + do { + // Determine the size of the pending datagram. + qint64 bytesToRead = socketEngine->bytesAvailable(); + if (bytesToRead == 0) { + // As a corner case, if we can't determine the size of the pending datagram, + // try to read 4K of data from the socket. Subsequent ::recvmsg call either + // fails or returns the actual length of the datagram. + bytesToRead = 4096; + } + + Q_ASSERT((datagramSize + int(bytesToRead)) < MaxByteArraySize); + incomingDatagram.resize(datagramSize + int(bytesToRead)); + +#if defined (QSCTPSOCKET_DEBUG) + qDebug("QSctpSocketPrivate::canReadNotification() about to read %lli bytes", + bytesToRead); +#endif + qint64 readBytes = socketEngine->readDatagram( + incomingDatagram.data() + datagramSize, bytesToRead, &header, + QAbstractSocketEngine::WantAll); + if (readBytes <= 0) { + if (readBytes == -2) { // no data available for reading + incomingDatagram.resize(datagramSize); + return currentChannelRead; + } + + socketEngine->close(); + if (readBytes == 0) { + setErrorAndEmit(QAbstractSocket::RemoteHostClosedError, + QSctpSocket::tr("The remote host closed the connection")); + } else { +#if defined (QSCTPSOCKET_DEBUG) + qDebug("QSctpSocketPrivate::canReadNotification() read failed: %s", + socketEngine->errorString().toLatin1().constData()); +#endif + setErrorAndEmit(socketEngine->error(), socketEngine->errorString()); + } + +#if defined (QSCTPSOCKET_DEBUG) + qDebug("QSctpSocketPrivate::canReadNotification() disconnecting socket"); +#endif + q->disconnectFromHost(); + return currentChannelRead; + } + datagramSize += int(readBytes); // update datagram size + } while (!header.endOfRecord); + +#if defined (QSCTPSOCKET_DEBUG) + qDebug("QSctpSocketPrivate::canReadNotification() got datagram from channel %i, size = %i", + header.streamNumber, datagramSize); +#endif + + // Drop the datagram, if opened only for writing + if (!q->isReadable()) { + incomingDatagram.clear(); + continue; + } + + // Store datagram in the channel buffer + Q_ASSERT(header.streamNumber < readBuffers.size()); + incomingDatagram.resize(datagramSize); + readBuffers[header.streamNumber].setChunkSize(0); // set packet mode on channel buffer + readBuffers[header.streamNumber].append(incomingDatagram); + incomingDatagram = QByteArray(); + + if (readHeaders.size() != readBuffers.size()) + readHeaders.resize(readBuffers.size()); + readHeaders[header.streamNumber].push_back(header); + + // Emit notifications. + if (header.streamNumber == savedCurrentChannel) + currentChannelRead = true; + emitReadyRead(header.streamNumber); + + } while (state == QAbstractSocket::ConnectedState); + + return currentChannelRead; +} + +/*! \internal +*/ +bool QSctpSocketPrivate::writeToSocket() +{ + Q_Q(QSctpSocket); +#if defined (QSCTPSOCKET_DEBUG) + qDebug("QSctpSocketPrivate::writeToSocket()"); +#endif + + // Handle TCP emulation mode in the base implementation. + if (!q->inDatagramMode()) + return QTcpSocketPrivate::writeToSocket(); + + if (!socketEngine) + return false; + + QIpPacketHeader defaultHeader; + const int savedCurrentChannel = currentWriteChannel; + bool currentChannelWritten = false; + bool transmitting; + do { + transmitting = false; + + for (int channel = 0; channel < writeBuffers.size(); ++channel) { + QRingBuffer &channelBuffer = writeBuffers[channel]; + + if (channelBuffer.isEmpty()) + continue; + + const bool hasHeader = (channel < writeHeaders.size()) + && !writeHeaders[channel].empty(); + QIpPacketHeader &header = hasHeader ? writeHeaders[channel].front() : defaultHeader; + header.streamNumber = channel; + qint64 sent = socketEngine->writeDatagram(channelBuffer.readPointer(), + channelBuffer.nextDataBlockSize(), + header); + if (sent < 0) { + if (sent == -2) // temporary error in writeDatagram + return currentChannelWritten; + + socketEngine->close(); +#if defined (QSCTPSOCKET_DEBUG) + qDebug("QSctpSocketPrivate::writeToSocket() write error, aborting. %s", + socketEngine->errorString().toLatin1().constData()); +#endif + setErrorAndEmit(socketEngine->error(), socketEngine->errorString()); + // An unexpected error so close the socket. + q->disconnectFromHost(); + return currentChannelWritten; + } + Q_ASSERT(sent == channelBuffer.nextDataBlockSize()); +#if defined (QSCTPSOCKET_DEBUG) + qDebug("QSctpSocketPrivate::writeToSocket() sent datagram of size %lli to channel %i", + sent, channel); +#endif + transmitting = true; + + // Remove datagram from the buffer + channelBuffer.read(); + if (hasHeader) + writeHeaders[channel].pop_front(); + + // Emit notifications. + if (channel == savedCurrentChannel) + currentChannelWritten = true; + emitBytesWritten(sent, channel); + + // If we were closed as a result of the bytesWritten() signal, return. + if (state == QAbstractSocket::UnconnectedState) { +#if defined (QSCTPSOCKET_DEBUG) + qDebug("QSctpSocketPrivate::writeToSocket() socket is closing - returning"); +#endif + return currentChannelWritten; + } + } + } while (transmitting); + + // At this point socket is either in Connected or Closing state, + // write buffers are empty. + if (state == QAbstractSocket::ClosingState) + q->disconnectFromHost(); + else + socketEngine->setWriteNotificationEnabled(false); + + return currentChannelWritten; +} + +/*! \internal +*/ +void QSctpSocketPrivate::configureCreatedSocket() +{ + if (socketEngine) + socketEngine->setOption(QAbstractSocketEngine::MaxStreamsSocketOption, + maxChannelCount < 0 ? 1 : maxChannelCount); +} + +/*! + Creates a QSctpSocket object in state \c UnconnectedState. + + Sets the datagram operation mode. The \a parent argument is passed + to QObject's constructor. + + \sa socketType(), setMaxChannelCount() +*/ +QSctpSocket::QSctpSocket(QObject *parent) + : QTcpSocket(SctpSocket, *new QSctpSocketPrivate, parent) +{ +#if defined(QSCTPSOCKET_DEBUG) + qDebug("QSctpSocket::QSctpSocket()"); +#endif + d_func()->isBuffered = true; +} + +/*! + Destroys the socket, closing the connection if necessary. + + \sa close() +*/ +QSctpSocket::~QSctpSocket() +{ +#if defined(QSCTPSOCKET_DEBUG) + qDebug("QSctpSocket::~QSctpSocket()"); +#endif +} + +/*! \reimp +*/ +qint64 QSctpSocket::readData(char *data, qint64 maxSize) +{ + Q_D(QSctpSocket); + + // Cleanup headers, if the user calls the standard QIODevice functions + if (d->currentReadChannel < d->readHeaders.size()) + d->readHeaders[d->currentReadChannel].clear(); + + return QTcpSocket::readData(data, maxSize); +} + +/*! \reimp +*/ +qint64 QSctpSocket::readLineData(char *data, qint64 maxlen) +{ + Q_D(QSctpSocket); + + // Cleanup headers, if the user calls the standard QIODevice functions + if (d->currentReadChannel < d->readHeaders.size()) + d->readHeaders[d->currentReadChannel].clear(); + + return QTcpSocket::readLineData(data, maxlen); +} + +/*! \reimp +*/ +void QSctpSocket::close() +{ + QTcpSocket::close(); + d_func()->readHeaders.clear(); +} + +/*! \reimp +*/ +void QSctpSocket::disconnectFromHost() +{ + Q_D(QSctpSocket); + + QTcpSocket::disconnectFromHost(); + if (d->state == QAbstractSocket::UnconnectedState) { + d->incomingDatagram.clear(); + d->writeHeaders.clear(); + } +} + +/*! + Sets the maximum number of channels that the application is + prepared to support in datagram mode, to \a count. If \a count + is 0, endpoint's value for maximum number of channels is used. + Negative \a count sets a continuous byte stream mode. + + Call this method only when QSctpSocket is in UnconnectedState. + + \sa maxChannelCount(), readChannelCount(), writeChannelCount() +*/ +void QSctpSocket::setMaxChannelCount(int count) +{ + Q_D(QSctpSocket); + if (d->state != QAbstractSocket::UnconnectedState) { + qWarning("QSctpSocket::setMaxChannelCount() is only allowed in UnconnectedState"); + return; + } +#if defined(QSCTPSOCKET_DEBUG) + qDebug("QSctpSocket::setMaxChannelCount(%i)", count); +#endif + d->maxChannelCount = qMax(count, -1); +} + +/*! + Returns the maximum number of channels that QSctpSocket is able to + support. + + A value of 0 (the default) means that the number of connection + channels would be set by the remote endpoint. + + Returns -1 if QSctpSocket is running in continuous byte stream + mode. + + \sa setMaxChannelCount(), readChannelCount(), writeChannelCount() +*/ +int QSctpSocket::maxChannelCount() const +{ + return d_func()->maxChannelCount; +} + +/*! + Returns \c true if the socket is running in datagram mode. + + \sa setMaxChannelCount() +*/ +bool QSctpSocket::inDatagramMode() const +{ + Q_D(const QSctpSocket); + return d->maxChannelCount != -1 && d->isBuffered; +} + +/*! + Reads a datagram from the buffer of the current read channel, and + returns it as a QNetworkDatagram object, along with the sender's + host address and port. If possible, this function will also try to + determine the datagram's destination address, port, and the number + of hop counts at reception time. + + On failure, returns a QNetworkDatagram that reports \l + {QNetworkDatagram::isValid()}{not valid}. + + \sa writeDatagram(), inDatagramMode(), currentReadChannel() +*/ +QNetworkDatagram QSctpSocket::readDatagram() +{ + Q_D(QSctpSocket); + + if (!isReadable() || !inDatagramMode()) { + qWarning("QSctpSocket::readDatagram(): operation is not permitted"); + return QNetworkDatagram(); + } + + if (d->currentReadChannel >= d->readHeaders.size() + || d->readHeaders[d->currentReadChannel].size() == 0) { + Q_ASSERT(d->buffer.isEmpty()); + return QNetworkDatagram(); + } + + QNetworkDatagram result(*new QNetworkDatagramPrivate(d->buffer.read(), + d->readHeaders[d->currentReadChannel].front())); + d->readHeaders[d->currentReadChannel].pop_front(); + +#if defined (QSCTPSOCKET_DEBUG) + qDebug("QSctpSocket::readDatagram() returning datagram (%p, %i, \"%s\", %i)", + result.d->data.constData(), + result.d->data.size(), + result.senderAddress().toString().toLatin1().constData(), + result.senderPort()); +#endif + + return result; +} + +/*! + Writes a \a datagram to the buffer of the current write channel. + Returns true on success; otherwise returns false. + + \sa readDatagram(), inDatagramMode(), currentWriteChannel() +*/ +bool QSctpSocket::writeDatagram(const QNetworkDatagram &datagram) +{ + Q_D(QSctpSocket); + + if (!isWritable() || d->state != QAbstractSocket::ConnectedState || !d->socketEngine + || !d->socketEngine->isValid() || !inDatagramMode()) { + qWarning("QSctpSocket::writeDatagram(): operation is not permitted"); + return false; + } + + if (datagram.d->data.isEmpty()) { + qWarning("QSctpSocket::writeDatagram() is called with empty datagram"); + return false; + } + + +#if defined QSCTPSOCKET_DEBUG + qDebug("QSctpSocket::writeDatagram(%p, %i, \"%s\", %i)", + datagram.d->data.constData(), + datagram.d->data.size(), + datagram.destinationAddress().toString().toLatin1().constData(), + datagram.destinationPort()); +#endif + + if (d->writeHeaders.size() != d->writeBuffers.size()) + d->writeHeaders.resize(d->writeBuffers.size()); + Q_ASSERT(d->currentWriteChannel < d->writeHeaders.size()); + d->writeHeaders[d->currentWriteChannel].push_back(datagram.d->header); + d->writeBuffer.setChunkSize(0); // set packet mode on channel buffer + d->writeBuffer.append(datagram.d->data); + + d->socketEngine->setWriteNotificationEnabled(true); + return true; +} + +QT_END_NAMESPACE |