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
qsctpsocket.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 Alex Trotsenko <alex1973tr@gmail.com>
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 reason:default
4
5//#define QSCTPSOCKET_DEBUG
6
7/*!
8 \class QSctpSocket
9 \since 5.8
10
11 \brief The QSctpSocket class provides an SCTP socket.
12
13 \ingroup network
14 \inmodule QtNetwork
15
16 SCTP (Stream Control Transmission Protocol) is a transport layer
17 protocol serving in a similar role as the popular protocols TCP
18 and UDP. Like UDP, SCTP is message-oriented, but it ensures reliable,
19 in-sequence transport of messages with congestion control like
20 TCP.
21
22 SCTP is connection-oriented protocol, which provides the complete
23 simultaneous transmission of multiple data streams between
24 endpoints. This multi-streaming allows data to be delivered by
25 independent channels, so that if there is data loss in one stream,
26 delivery will not be affected for the other streams.
27
28 Being message-oriented, SCTP transports a sequence of messages,
29 rather than transporting an unbroken stream of bytes as does TCP.
30 Like in UDP, in SCTP a sender sends a message in one operation,
31 and that exact message is passed to the receiving application
32 process in one operation. But unlike UDP, the delivery is
33 guaranteed.
34
35 It also supports multi-homing, meaning that a connected endpoint
36 can have alternate IP addresses associated with it in order to
37 route around network failure or changing conditions.
38
39 QSctpSocket is a convenience subclass of QTcpSocket that allows
40 you to emulate TCP data stream over SCTP or establish an SCTP
41 connection for reliable datagram service.
42
43 QSctpSocket can operate in one of two possible modes:
44
45 \list
46 \li Continuous byte stream (TCP emulation).
47 \li Multi-streamed datagram mode.
48 \endlist
49
50 To set a continuous byte stream mode, instantiate QSctpSocket and
51 call setMaximumChannelCount() with a negative value. This gives the
52 ability to use QSctpSocket as a regular buffered QTcpSocket. You
53 can call connectToHost() to initiate connection with endpoint,
54 write() to transmit and read() to receive data from the peer, but
55 you cannot distinguish message boundaries.
56
57 By default, QSctpSocket operates in datagram mode. Before
58 connecting, call setMaximumChannelCount() to set the maximum number of
59 channels that the application is prepared to support. This number
60 is a parameter negotiated with the remote endpoint and its value
61 can be bounded by the operating system. The default value of 0
62 indicates to use the peer's value. If both endpoints have default
63 values, then number of connection channels is system-dependent.
64 After establishing a connection, you can fetch the actual number
65 of channels by calling readChannelCount() and writeChannelCount().
66
67 \snippet code/src_network_socket_qsctpsocket.cpp 0
68
69 In datagram mode, QSctpSocket performs the buffering of datagrams
70 independently for each channel. You can queue a datagram to the
71 buffer of the current channel by calling writeDatagram() and read
72 a pending datagram by calling readDatagram() respectively.
73
74 Using the standard QIODevice functions read(), readLine(), write(),
75 etc. is allowed in datagram mode with the same limitations as in
76 continuous byte stream mode.
77
78 \note This class is not supported on the Windows platform.
79
80 \sa QSctpServer, QTcpSocket, QAbstractSocket
81*/
82
83#include "qsctpsocket.h"
84#include "qsctpsocket_p.h"
85
87
88#ifdef QSCTPSOCKET_DEBUG
89#include <qdebug.h>
90#endif
91
92QT_BEGIN_NAMESPACE
93
94/*! \internal
95*/
96QSctpSocketPrivate::QSctpSocketPrivate()
97 : maximumChannelCount(0)
98{
99}
100
101/*! \internal
102*/
103QSctpSocketPrivate::~QSctpSocketPrivate()
104{
105}
106
107/*! \internal
108*/
109bool QSctpSocketPrivate::canReadNotification()
110{
111 Q_Q(QSctpSocket);
112#if defined (QSCTPSOCKET_DEBUG)
113 qDebug("QSctpSocketPrivate::canReadNotification()");
114#endif
115
116 // Handle TCP emulation mode in the base implementation.
117 if (!q->isInDatagramMode())
118 return QTcpSocketPrivate::canReadNotification();
119
120 const int savedCurrentChannel = currentReadChannel;
121 bool currentChannelRead = false;
122 do {
123 qsizetype datagramSize = incomingDatagram.size();
124 QIpPacketHeader header;
125
126 do {
127 // Determine the size of the pending datagram.
128 qint64 bytesToRead = socketEngine->bytesAvailable();
129 if (bytesToRead == 0) {
130 // As a corner case, if we can't determine the size of the pending datagram,
131 // try to read 4K of data from the socket. Subsequent ::recvmsg call either
132 // fails or returns the actual length of the datagram.
133 bytesToRead = 4096;
134 }
135
136 Q_ASSERT((datagramSize + qsizetype(bytesToRead)) < QByteArray::maxSize());
137 incomingDatagram.resize(datagramSize + int(bytesToRead));
138
139#if defined (QSCTPSOCKET_DEBUG)
140 qDebug("QSctpSocketPrivate::canReadNotification() about to read %lli bytes",
141 bytesToRead);
142#endif
143 qint64 readBytes = socketEngine->readDatagram(
144 incomingDatagram.data() + datagramSize, bytesToRead, &header,
145 QAbstractSocketEngine::WantAll);
146 if (readBytes <= 0) {
147 if (readBytes == -2) { // no data available for reading
148 incomingDatagram.resize(datagramSize);
149 return currentChannelRead;
150 }
151
152 socketEngine->close();
153 if (readBytes == 0) {
154 setErrorAndEmit(QAbstractSocket::RemoteHostClosedError,
155 QSctpSocket::tr("The remote host closed the connection"));
156 } else {
157#if defined (QSCTPSOCKET_DEBUG)
158 qDebug("QSctpSocketPrivate::canReadNotification() read failed: %s",
159 socketEngine->errorString().toLatin1().constData());
160#endif
161 setErrorAndEmit(socketEngine->error(), socketEngine->errorString());
162 }
163
164#if defined (QSCTPSOCKET_DEBUG)
165 qDebug("QSctpSocketPrivate::canReadNotification() disconnecting socket");
166#endif
167 q->disconnectFromHost();
168 return currentChannelRead;
169 }
170 datagramSize += int(readBytes); // update datagram size
171 } while (!header.endOfRecord);
172
173#if defined (QSCTPSOCKET_DEBUG)
174 qDebug("QSctpSocketPrivate::canReadNotification() got datagram from channel %i, size = %i",
175 header.streamNumber, datagramSize);
176#endif
177
178 // Drop the datagram, if opened only for writing
179 if (!q->isReadable()) {
180 incomingDatagram.clear();
181 continue;
182 }
183
184 // Store datagram in the channel buffer
185 Q_ASSERT(header.streamNumber < readBuffers.size());
186 incomingDatagram.resize(datagramSize);
187 readBuffers[header.streamNumber].setChunkSize(0); // set packet mode on channel buffer
188 readBuffers[header.streamNumber].append(incomingDatagram);
189 incomingDatagram = QByteArray();
190
191 if (readHeaders.size() != readBuffers.size())
192 readHeaders.resize(readBuffers.size());
193 readHeaders[header.streamNumber].push_back(header);
194
195 // Emit notifications.
196 if (header.streamNumber == savedCurrentChannel)
197 currentChannelRead = true;
198 emitReadyRead(header.streamNumber);
199
200 } while (state == QAbstractSocket::ConnectedState);
201
202 return currentChannelRead;
203}
204
205/*! \internal
206*/
207bool QSctpSocketPrivate::writeToSocket()
208{
209 Q_Q(QSctpSocket);
210#if defined (QSCTPSOCKET_DEBUG)
211 qDebug("QSctpSocketPrivate::writeToSocket()");
212#endif
213
214 // Handle TCP emulation mode in the base implementation.
215 if (!q->isInDatagramMode())
216 return QTcpSocketPrivate::writeToSocket();
217
218 if (!socketEngine)
219 return false;
220
221 QIpPacketHeader defaultHeader;
222 const int savedCurrentChannel = currentWriteChannel;
223 bool currentChannelWritten = false;
224 bool transmitting;
225 do {
226 transmitting = false;
227
228 for (int channel = 0; channel < writeBuffers.size(); ++channel) {
229 QRingBuffer &channelBuffer = writeBuffers[channel];
230
231 if (channelBuffer.isEmpty())
232 continue;
233
234 const bool hasHeader = (channel < writeHeaders.size())
235 && !writeHeaders[channel].empty();
236 QIpPacketHeader &header = hasHeader ? writeHeaders[channel].front() : defaultHeader;
237 header.streamNumber = channel;
238 qint64 sent = socketEngine->writeDatagram(channelBuffer.readPointer(),
239 channelBuffer.nextDataBlockSize(),
240 header);
241 if (sent < 0) {
242 if (sent == -2) // temporary error in writeDatagram
243 return currentChannelWritten;
244
245 socketEngine->close();
246#if defined (QSCTPSOCKET_DEBUG)
247 qDebug("QSctpSocketPrivate::writeToSocket() write error, aborting. %s",
248 socketEngine->errorString().toLatin1().constData());
249#endif
250 setErrorAndEmit(socketEngine->error(), socketEngine->errorString());
251 // An unexpected error so close the socket.
252 q->disconnectFromHost();
253 return currentChannelWritten;
254 }
255 Q_ASSERT(sent == channelBuffer.nextDataBlockSize());
256#if defined (QSCTPSOCKET_DEBUG)
257 qDebug("QSctpSocketPrivate::writeToSocket() sent datagram of size %lli to channel %i",
258 sent, channel);
259#endif
260 transmitting = true;
261
262 // Remove datagram from the buffer
263 channelBuffer.read();
264 if (hasHeader)
265 writeHeaders[channel].pop_front();
266
267 // Emit notifications.
268 if (channel == savedCurrentChannel)
269 currentChannelWritten = true;
270 emitBytesWritten(sent, channel);
271
272 // If we were closed as a result of the bytesWritten() signal, return.
273 if (state == QAbstractSocket::UnconnectedState) {
274#if defined (QSCTPSOCKET_DEBUG)
275 qDebug("QSctpSocketPrivate::writeToSocket() socket is closing - returning");
276#endif
277 return currentChannelWritten;
278 }
279 }
280 } while (transmitting);
281
282 // At this point socket is either in Connected or Closing state,
283 // write buffers are empty.
284 if (state == QAbstractSocket::ClosingState)
285 q->disconnectFromHost();
286 else
287 socketEngine->setWriteNotificationEnabled(false);
288
289 return currentChannelWritten;
290}
291
292/*! \internal
293*/
294void QSctpSocketPrivate::configureCreatedSocket()
295{
296 if (socketEngine)
297 socketEngine->setOption(QAbstractSocketEngine::MaxStreamsSocketOption,
298 maximumChannelCount < 0 ? 1 : maximumChannelCount);
299}
300
301/*!
302 Creates a QSctpSocket object in state \c UnconnectedState.
303
304 Sets the datagram operation mode. The \a parent argument is passed
305 to QObject's constructor.
306
307 \sa socketType(), setMaximumChannelCount()
308*/
309QSctpSocket::QSctpSocket(QObject *parent)
310 : QTcpSocket(SctpSocket, *new QSctpSocketPrivate, parent)
311{
312#if defined(QSCTPSOCKET_DEBUG)
313 qDebug("QSctpSocket::QSctpSocket()");
314#endif
315 d_func()->isBuffered = true;
316}
317
318/*!
319 Destroys the socket, closing the connection if necessary.
320
321 \sa close()
322*/
323QSctpSocket::~QSctpSocket()
324{
325#if defined(QSCTPSOCKET_DEBUG)
326 qDebug("QSctpSocket::~QSctpSocket()");
327#endif
328}
329
330/*! \reimp
331*/
332qint64 QSctpSocket::readData(char *data, qint64 maxSize)
333{
334 Q_D(QSctpSocket);
335
336 // Cleanup headers, if the user calls the standard QIODevice functions
337 if (d->currentReadChannel < d->readHeaders.size())
338 d->readHeaders[d->currentReadChannel].clear();
339
340 return QTcpSocket::readData(data, maxSize);
341}
342
343/*! \reimp
344*/
345qint64 QSctpSocket::readLineData(char *data, qint64 maxlen)
346{
347 Q_D(QSctpSocket);
348
349 // Cleanup headers, if the user calls the standard QIODevice functions
350 if (d->currentReadChannel < d->readHeaders.size())
351 d->readHeaders[d->currentReadChannel].clear();
352
353 return QTcpSocket::readLineData(data, maxlen);
354}
355
356/*! \reimp
357*/
358void QSctpSocket::close()
359{
360 QTcpSocket::close();
361 d_func()->readHeaders.clear();
362}
363
364/*! \reimp
365*/
366void QSctpSocket::disconnectFromHost()
367{
368 Q_D(QSctpSocket);
369
370 QTcpSocket::disconnectFromHost();
371 if (d->state == QAbstractSocket::UnconnectedState) {
372 d->incomingDatagram.clear();
373 d->writeHeaders.clear();
374 }
375}
376
377/*!
378 Sets the maximum number of channels that the application is
379 prepared to support in datagram mode, to \a count. If \a count
380 is 0, endpoint's value for maximum number of channels is used.
381 Negative \a count sets a continuous byte stream mode.
382
383 Call this method only when QSctpSocket is in UnconnectedState.
384
385 \sa maximumChannelCount(), readChannelCount(), writeChannelCount()
386*/
387void QSctpSocket::setMaximumChannelCount(int count)
388{
389 Q_D(QSctpSocket);
390 if (d->state != QAbstractSocket::UnconnectedState) {
391 qWarning("QSctpSocket::setMaximumChannelCount() is only allowed in UnconnectedState");
392 return;
393 }
394#if defined(QSCTPSOCKET_DEBUG)
395 qDebug("QSctpSocket::setMaximumChannelCount(%i)", count);
396#endif
397 d->maximumChannelCount = qMax(count, -1);
398}
399
400/*!
401 Returns the maximum number of channels that QSctpSocket is able to
402 support.
403
404 A value of 0 (the default) means that the number of connection
405 channels would be set by the remote endpoint.
406
407 Returns -1 if QSctpSocket is running in continuous byte stream
408 mode.
409
410 \sa setMaximumChannelCount(), readChannelCount(), writeChannelCount()
411*/
412int QSctpSocket::maximumChannelCount() const
413{
414 return d_func()->maximumChannelCount;
415}
416
417/*!
418 Returns \c true if the socket is running in datagram mode.
419
420 \sa setMaximumChannelCount()
421*/
422bool QSctpSocket::isInDatagramMode() const
423{
424 Q_D(const QSctpSocket);
425 return d->maximumChannelCount != -1 && d->isBuffered;
426}
427
428/*!
429 Reads a datagram from the buffer of the current read channel, and
430 returns it as a QNetworkDatagram object, along with the sender's
431 host address and port. If possible, this function will also try to
432 determine the datagram's destination address, port, and the number
433 of hop counts at reception time.
434
435 On failure, returns a QNetworkDatagram that reports \l
436 {QNetworkDatagram::isValid()}{not valid}.
437
438 \sa writeDatagram(), isInDatagramMode(), currentReadChannel()
439*/
440QNetworkDatagram QSctpSocket::readDatagram()
441{
442 Q_D(QSctpSocket);
443
444 if (!isReadable() || !isInDatagramMode()) {
445 qWarning("QSctpSocket::readDatagram(): operation is not permitted");
446 return QNetworkDatagram();
447 }
448
449 if (d->currentReadChannel >= d->readHeaders.size()
450 || d->readHeaders[d->currentReadChannel].size() == 0) {
451 Q_ASSERT(d->buffer.isEmpty());
452 return QNetworkDatagram();
453 }
454
455 QNetworkDatagram result(*new QNetworkDatagramPrivate(d->buffer.read(),
456 d->readHeaders[d->currentReadChannel].front()));
457 d->readHeaders[d->currentReadChannel].pop_front();
458
459#if defined (QSCTPSOCKET_DEBUG)
460 qDebug("QSctpSocket::readDatagram() returning datagram (%p, %i, \"%s\", %i)",
461 result.d->data.constData(),
462 result.d->data.size(),
463 result.senderAddress().toString().toLatin1().constData(),
464 result.senderPort());
465#endif
466
467 return result;
468}
469
470/*!
471 Writes a \a datagram to the buffer of the current write channel.
472 Returns true on success; otherwise returns false.
473
474 \sa readDatagram(), isInDatagramMode(), currentWriteChannel()
475*/
476bool QSctpSocket::writeDatagram(const QNetworkDatagram &datagram)
477{
478 Q_D(QSctpSocket);
479
480 if (!isWritable() || d->state != QAbstractSocket::ConnectedState || !d->socketEngine
481 || !d->socketEngine->isValid() || !isInDatagramMode()) {
482 qWarning("QSctpSocket::writeDatagram(): operation is not permitted");
483 return false;
484 }
485
486 if (datagram.d->data.isEmpty()) {
487 qWarning("QSctpSocket::writeDatagram() is called with empty datagram");
488 return false;
489 }
490
491
492#if defined QSCTPSOCKET_DEBUG
493 qDebug("QSctpSocket::writeDatagram(%p, %i, \"%s\", %i)",
494 datagram.d->data.constData(),
495 datagram.d->data.size(),
496 datagram.destinationAddress().toString().toLatin1().constData(),
497 datagram.destinationPort());
498#endif
499
500 if (d->writeHeaders.size() != d->writeBuffers.size())
501 d->writeHeaders.resize(d->writeBuffers.size());
502 Q_ASSERT(d->currentWriteChannel < d->writeHeaders.size());
503 d->writeHeaders[d->currentWriteChannel].push_back(datagram.d->header);
504 d->writeBuffer.setChunkSize(0); // set packet mode on channel buffer
505 d->writeBuffer.append(datagram.d->data);
506
507 d->socketEngine->setWriteNotificationEnabled(true);
508 return true;
509}
510
511QT_END_NAMESPACE
512
513#include "moc_qsctpsocket.cpp"