1
0
mirror of https://github.com/qt/qtmqtt.git synced 2026-10-25 16:16:26 +00:00

Introduce custom buffer

A custom buffer is required for messages containing more content than
can fit into QTcpSocket's internal buffer. Hence a message cannot be
processed within a single invocation of transportReadReady.

Change-Id: I4eed8ebbc6393569c2266d0523228f24c4925084
Reviewed-by: Maurice Kalinowski <maurice.kalinowski@qt.io>
This commit is contained in:
Maurice Kalinowski 2017-05-11 16:30:43 +02:00
parent 8413dc4ade
commit d96011b080
2 changed files with 323 additions and 214 deletions

View File

@ -442,6 +442,7 @@ void QMqttConnection::setClient(QMqttClient *client)
void QMqttConnection::transportConnectionClosed()
{
m_readBuffer.clear();
m_pingTimer.stop();
m_client->setState(QMqttClient::Disconnected);
}
@ -449,242 +450,329 @@ void QMqttConnection::transportConnectionClosed()
void QMqttConnection::transportReadReady()
{
qCDebug(lcMqttConnectionVerbose) << Q_FUNC_INFO;
m_readBuffer.append(m_transport->readAll());
processData();
}
qint64 available = m_transport->bytesAvailable();
while (available > 0) {
quint8 msg;
m_transport->read((char*)&msg, 1);
switch (msg & 0xF0) {
case QMqttControlPacket::CONNACK: {
qCDebug(lcMqttConnectionVerbose) << "Received CONNACK";
if (m_internalState != BrokerWaitForConnectAck) {
qWarning("Received CONNACK at unexpected time!");
break;
}
void QMqttConnection::readBuffer(char *data, qint64 size)
{
memcpy(data, m_readBuffer.constData(), size);
m_readBuffer = m_readBuffer.mid(size);
}
quint8 payloadSize;
m_transport->read((char*)&payloadSize, 1);
if (payloadSize != 2) {
qWarning("Unexpected FRAME size in CONNACK");
// ## SET SOME ERROR
break;
}
quint8 ackFlags;
m_transport->read((char*)&ackFlags, 1);
if (ackFlags > 1) { // MQTT-3.2.2.1
qWarning("Unexpected CONNACK Flags set");
// ## SET SOME ERROR
break;
}
bool sessionPresent = ackFlags == 1;
QByteArray QMqttConnection::readBuffer(qint64 size)
{
QByteArray res = m_readBuffer.left(size);
m_readBuffer = m_readBuffer.mid(size);
return res;
}
// MQTT-3.2.2-1 & MQTT-3.2.2-2
if (sessionPresent) {
emit m_client->brokerSessionRestored();
if (m_client->cleanSession()) {
qWarning("Connected with a clean session, ack contains session present");
// ### TODO: RABBIT MQ Spec Misalign
// If a clean session is requested by the client, the server has to have an empty
// session. However Rabbit MQ sends a 1 here for unknown reasons.
//break;
}
}
void QMqttConnection::finalize_connack()
{
qCDebug(lcMqttConnectionVerbose) << "Finalize CONNACK";
quint8 ackFlags;
readBuffer((char*)&ackFlags, 1);
if (ackFlags > 1) { // MQTT-3.2.2.1
qWarning("Unexpected CONNACK Flags set");
// ## SET SOME ERROR
return;
}
bool sessionPresent = ackFlags == 1;
quint8 connectResultValue;
m_transport->read((char*)&connectResultValue, 1);
if (connectResultValue != 0) {
qWarning("Connection has been rejected");
// MQTT-3.2.2-5
// ConnectionError
m_transport->close();
}
m_internalState = BrokerConnected;
m_client->setState(QMqttClient::Connected);
m_pingTimer.setInterval(m_client->keepAlive() * 1000);
m_pingTimer.start();
break;
// MQTT-3.2.2-1 & MQTT-3.2.2-2
if (sessionPresent) {
emit m_client->brokerSessionRestored();
if (m_client->cleanSession()) {
qWarning("Connected with a clean session, ack contains session present");
// ### TODO: RABBIT MQ Spec Misalign
// If a clean session is requested by the client, the server has to have an empty
// session. However Rabbit MQ sends a 1 here for unknown reasons.
//break;
}
case QMqttControlPacket::SUBACK: {
char offset;
m_transport->read(&offset, 1);
quint16 id;
m_transport->read((char*)&id, 2);
id = qFromBigEndian<quint16>(id);
if (!m_pendingSubscriptionAck.contains(id)) {
qWarning("Received SUBACK for unknown subscription request");
break;
}
quint8 result;
m_transport->read((char*)&result, 1);
auto sub = m_pendingSubscriptionAck.take(id);
qCDebug(lcMqttConnectionVerbose) << "Received SUBACK: ID:" << id << "QoS:" << result;
if (result <= 2) {
// The broker might have a different support level for QoS than what
// the client requested
if (result != sub->qos()) {
sub->setQos(result);
emit sub->qosChanged(result);
}
sub->setState(QMqttSubscription::Subscribed);
m_activeSubscriptions.insert(sub->topic(), sub);
} else if (result == 0x80) {
qWarning() << "Subscription for id " << id << " failed.";
sub->setState(QMqttSubscription::Error);
} else {
qWarning("Received invalid SUBACK result value");
sub->setState(QMqttSubscription::Error);
}
break;
}
quint8 connectResultValue;
readBuffer((char*)&connectResultValue, 1);
if (connectResultValue != 0) {
qWarning("Connection has been rejected");
// MQTT-3.2.2-5
// ConnectionError
m_readBuffer.clear();
m_transport->close();
}
m_internalState = BrokerConnected;
m_client->setState(QMqttClient::Connected);
m_pingTimer.setInterval(m_client->keepAlive() * 1000);
m_pingTimer.start();
}
void QMqttConnection::finalize_suback()
{
quint16 id;
readBuffer((char*)&id, 2);
id = qFromBigEndian<quint16>(id);
if (!m_pendingSubscriptionAck.contains(id)) {
qWarning("Received SUBACK for unknown subscription request");
return;
}
quint8 result;
readBuffer((char*)&result, 1);
auto sub = m_pendingSubscriptionAck.take(id);
qCDebug(lcMqttConnectionVerbose) << "Finalize SUBACK: id:" << id << "qos:" << result;
if (result <= 2) {
// The broker might have a different support level for QoS than what
// the client requested
if (result != sub->qos()) {
sub->setQos(result);
emit sub->qosChanged(result);
}
case QMqttControlPacket::UNSUBACK: {
char offset;
m_transport->read(&offset, 1);
quint16 id;
m_transport->read((char*)&id, 2);
id = qFromBigEndian<quint16>(id);
qCDebug(lcMqttConnectionVerbose) << "Received UNSUBACK: ID:" << id;
if (!m_pendingUnsubscriptions.contains(id)) {
qWarning("Received UNSUBACK for unknown request");
break;
}
auto sub = m_pendingUnsubscriptions.take(id);
sub->setState(QMqttSubscription::Unsubscribed);
m_activeSubscriptions.remove(sub->topic());
sub->setState(QMqttSubscription::Subscribed);
m_activeSubscriptions.insert(sub->topic(), sub);
} else if (result == 0x80) {
qWarning() << "Subscription for id " << id << " failed.";
sub->setState(QMqttSubscription::Error);
} else {
qWarning("Received invalid SUBACK result value");
sub->setState(QMqttSubscription::Error);
}
}
break;
void QMqttConnection::finalize_unsuback()
{
quint16 id;
readBuffer((char*)&id, 2);
id = qFromBigEndian<quint16>(id);
qCDebug(lcMqttConnectionVerbose) << "Finalize UNSUBACK: " << id;
if (!m_pendingUnsubscriptions.contains(id)) {
qWarning("Received UNSUBACK for unknown request");
return;
}
auto sub = m_pendingUnsubscriptions.take(id);
sub->setState(QMqttSubscription::Unsubscribed);
m_activeSubscriptions.remove(sub->topic());
}
void QMqttConnection::finalize_publish()
{
// String topic
const quint16 topicLength = qFromBigEndian<quint16>(reinterpret_cast<const quint16 *>(readBuffer(2).constData()));
const QString topic = QString::fromUtf8(reinterpret_cast<const char *>(readBuffer(topicLength).constData()));
quint16 id;
if (m_currentPublish.qos > 0) {
id = qFromBigEndian<quint16>(reinterpret_cast<const quint16 *>(readBuffer(2).constData()));
}
// message
qint64 payloadLength = m_missingData - (topicLength + 2) - (m_currentPublish.qos > 0 ? 2 : 0);
const QByteArray message = readBuffer(payloadLength);
qCDebug(lcMqttConnectionVerbose) << "Finalize PUBLISH: topic:" << topic
<< " payloadLength:" << payloadLength;;
emit m_client->messageReceived(message, topic);
for (auto sub = m_activeSubscriptions.constBegin(); sub != m_activeSubscriptions.constEnd(); sub++) {
const QString subTopic = sub.key();
if (subTopic == topic) {
emit sub.value()->messageReceived(message, topic);
continue;
} else if (subTopic.endsWith(QLatin1Char('#')) && topic.startsWith(subTopic.leftRef(subTopic.size() - 1))) {
emit sub.value()->messageReceived(message, topic);
continue;
}
case QMqttControlPacket::PUBLISH: {
quint8 qos = (msg & 0x06) >> 1;
bool retain = msg & 0x01;
Q_UNUSED(retain);
// remaining length
quint32 multiplier = 1;
quint32 msgLength = 0;
quint8 b = 0;
quint8 iteration = 0;
do {
m_transport->read((char*)&b, 1);
msgLength += (b & 127) * multiplier;
multiplier *= 128;
iteration++;
if (iteration > 4)
qFatal("Publish message is too big to handle");
} while ((b & 128) != 0);
// String topic
const quint16 topicLength = qFromBigEndian<quint16>(reinterpret_cast<const quint16 *>(m_transport->read(2).constData()));
const QString topic = QString::fromUtf8(reinterpret_cast<const char *>(m_transport->read(topicLength).constData()));
if (!subTopic.contains(QLatin1Char('+')))
continue;
quint16 id;
if (qos > 0) {
id = qFromBigEndian<quint16>(reinterpret_cast<const quint16 *>(m_transport->read(2).constData()));
}
const QVector<QStringRef> subTopicSplit = subTopic.splitRef(QLatin1Char('/'));
const QVector<QStringRef> topicSplit = topic.splitRef(QLatin1Char('/'));
if (subTopicSplit.size() != topicSplit.size())
continue;
const QVector<QStringRef> subPlusSplit = subTopic.splitRef(QLatin1Char('+'));
// message
qint64 payloadLength = msgLength - (topicLength + 2) - (qos > 0 ? 2 : 0);
const QByteArray message = m_transport->read(payloadLength);
qCDebug(lcMqttConnectionVerbose) << "Received PUBLISH: topic:" << topic
<< " payloadLength:" << payloadLength;;
emit m_client->messageReceived(message, topic);
for (auto sub = m_activeSubscriptions.constBegin(); sub != m_activeSubscriptions.constEnd(); sub++) {
const QString subTopic = sub.key();
if (subTopic == topic) {
emit sub.value()->messageReceived(message, topic);
continue;
} else if (subTopic.endsWith(QLatin1Char('#')) && topic.startsWith(subTopic.leftRef(subTopic.size() - 1))) {
emit sub.value()->messageReceived(message, topic);
continue;
}
if (!subTopic.contains(QLatin1Char('+')))
continue;
const QVector<QStringRef> subTopicSplit = subTopic.splitRef(QLatin1Char('/'));
const QVector<QStringRef> topicSplit = topic.splitRef(QLatin1Char('/'));
if (subTopicSplit.size() != topicSplit.size())
continue;
const QVector<QStringRef> subPlusSplit = subTopic.splitRef(QLatin1Char('+'));
if (topic.startsWith(subPlusSplit.at(0)) && topic.endsWith(subPlusSplit.at(1))) {
emit sub.value()->messageReceived(message, topic);
}
}
if (qos == 1)
sendControlPublishAcknowledge(id);
else if (qos == 2)
sendControlPublishReceive(id);
break;
if (topic.startsWith(subPlusSplit.at(0)) && topic.endsWith(subPlusSplit.at(1))) {
emit sub.value()->messageReceived(message, topic);
}
}
if (m_currentPublish.qos == 1)
sendControlPublishAcknowledge(id);
else if (m_currentPublish.qos == 2)
sendControlPublishReceive(id);
}
void QMqttConnection::finalize_pubAckRecComp()
{
qCDebug(lcMqttConnectionVerbose) << "Finalize PUBACK/REC/COMP";
quint16 id;
readBuffer((char*)&id, 2);
id = qFromBigEndian<quint16>(id);
if ((m_currentPacket & 0xF0) == QMqttControlPacket::PUBCOMP) {
qCDebug(lcMqttConnectionVerbose) << " PUBCOMP:" << id;
auto pendingRelease = m_pendingReleaseMessages.take(id);
if (!pendingRelease)
qWarning("Received PUBCOMP for unknown released message");
emit m_client->messageSent(id);
return;
}
auto pendingMsg = m_pendingMessages.take(id);
if (!pendingMsg) {
qWarning(qPrintable(QString::fromLatin1("Received PUBACK for unknown message: %1").arg(id)));
return;
}
if ((m_currentPacket & 0xF0) == QMqttControlPacket::PUBREC) {
qCDebug(lcMqttConnectionVerbose) << " PUBREC:" << id;
m_pendingReleaseMessages.insert(id, pendingMsg);
sendControlPublishRelease(id);
} else {
qCDebug(lcMqttConnectionVerbose) << " PUBACK:" << id;
emit m_client->messageSent(id);
}
}
void QMqttConnection::finalize_pubrel()
{
quint16 id;
readBuffer((char*)&id, 2);
id = qFromBigEndian<quint16>(id);
qCDebug(lcMqttConnectionVerbose) << "Finalize PUBREL:" << id;
// ### TODO: send to our app now or not???
// See standard Figure 4.3 Method A or B ???
sendControlPublishComp(id);
}
void QMqttConnection::finalize_pingresp()
{
qCDebug(lcMqttConnectionVerbose) << "Finalize PINGRESP";
quint8 v;
readBuffer((char*)&v, 1);
if (v != 0)
qWarning("Received a PINGRESP with payload!");
emit m_client->pingResponse();
}
void QMqttConnection::processData()
{
if (m_missingData > 0) {
if (m_readBuffer.size() < m_missingData)
return;
switch (m_currentPacket & 0xF0) {
case QMqttControlPacket::CONNACK:
finalize_connack();
break;
case QMqttControlPacket::SUBACK:
finalize_suback();
break;
case QMqttControlPacket::UNSUBACK:
finalize_unsuback();
break;
case QMqttControlPacket::PUBLISH:
finalize_publish();
break;
case QMqttControlPacket::PUBACK:
case QMqttControlPacket::PUBREC:
case QMqttControlPacket::PUBCOMP: {
// remaining length
char offset;
m_transport->read(&offset, 1); // ### TODO: Should we care about remaining length???
quint16 id;
m_transport->read((char*)&id, 2);
id = qFromBigEndian<quint16>(id);
if ((msg & 0xF0) == QMqttControlPacket::PUBCOMP) {
qCDebug(lcMqttConnectionVerbose) << "Received PUBCOMP:" << id;
auto pendingRelease = m_pendingReleaseMessages.take(id);
if (!pendingRelease)
qWarning("Received PUBCOMP for unknown released message");
emit m_client->messageSent(id);
break;
}
auto pendingMsg = m_pendingMessages.take(id);
if (!pendingMsg) {
qWarning(qPrintable(QString::fromLatin1("Received PUBACK for unknown message: %1").arg(id)));
break;
}
if ((msg & 0xF0) == QMqttControlPacket::PUBREC) {
qCDebug(lcMqttConnectionVerbose) << "Received PUBREC:" << id;
m_pendingReleaseMessages.insert(id, pendingMsg);
sendControlPublishRelease(id);
} else {
qCDebug(lcMqttConnectionVerbose) << "Received PUBACK:" << id;
emit m_client->messageSent(id);
}
case QMqttControlPacket::PUBCOMP:
finalize_pubAckRecComp();
break;
case QMqttControlPacket::PINGRESP:
finalize_pingresp();
break;
}
case QMqttControlPacket::PUBREL: {
// remaining length
char offset;
m_transport->read(&offset, 1); // ### TODO: Should we care about remaining length???
quint16 id;
m_transport->read((char*)&id, 2);
id = qFromBigEndian<quint16>(id);
qCDebug(lcMqttConnectionVerbose) << "Received PUBREL:" << id;
// ### TODO: send to our app now or not???
// See standard Figure 4.3 Method A or B ???
sendControlPublishComp(id);
finalize_pubrel();
break;
}
case QMqttControlPacket::PINGRESP: {
qCDebug(lcMqttConnectionVerbose) << "Received PINGRESP:";
quint8 v;
m_transport->read((char*)&v, 1);
if (v != 0)
qWarning("Received a PINGRESP with payload!");
emit m_client->pingResponse();
default:
qFatal("Unknown packet to finalize");
break;
}
default: {
qWarning("Transport received unknown data");
m_missingData = 0;
}
if (m_readBuffer.size() == 0)
return;
readBuffer((char*)&m_currentPacket, 1);
switch (m_currentPacket & 0xF0) {
case QMqttControlPacket::CONNACK: {
qCDebug(lcMqttConnectionVerbose) << "Received CONNACK";
if (m_internalState != BrokerWaitForConnectAck) {
qWarning("Received CONNACK at unexpected time!");
break;
}
quint8 payloadSize;
readBuffer((char*)&payloadSize, 1);
if (payloadSize != 2) {
qWarning("Unexpected FRAME size in CONNACK");
// ## SET SOME ERROR
break;
}
available = m_transport->bytesAvailable();
} // available
m_missingData = 2;
break;
}
case QMqttControlPacket::SUBACK: {
qCDebug(lcMqttConnectionVerbose) << "Received SUBACK";
quint8 remaining;
readBuffer((char*)&remaining, 1);
m_missingData = remaining;
break;
}
case QMqttControlPacket::PUBLISH: {
qCDebug(lcMqttConnectionVerbose) << "Received PUBLISH";
m_currentPublish.qos = (m_currentPacket & 0x06) >> 1;
m_currentPublish.retain = m_currentPacket & 0x01;
// remaining length
quint32 multiplier = 1;
quint32 msgLength = 0;
quint8 b = 0;
quint8 iteration = 0;
do {
readBuffer((char*)&b, 1);
msgLength += (b & 127) * multiplier;
multiplier *= 128;
iteration++;
if (iteration > 4)
qFatal("Publish message is too big to handle");
} while ((b & 128) != 0);
m_missingData = msgLength;
break;
}
case QMqttControlPacket::PINGRESP:
qCDebug(lcMqttConnectionVerbose) << "Received PINGRESP";
m_missingData = 1;
break;
case QMqttControlPacket::UNSUBACK:
case QMqttControlPacket::PUBACK:
case QMqttControlPacket::PUBREC:
case QMqttControlPacket::PUBCOMP:
case QMqttControlPacket::PUBREL: {
qCDebug(lcMqttConnectionVerbose) << "Received UNSUBACK/PUBACK/PUBREC/PUBCOMP/PUBREL";
char remaining;
readBuffer(&remaining, 1); // ### TODO: verify this is 2
if (remaining != 0x02)
qWarning("Received 2 byte message with invalid remaining length");
m_missingData = 2;
break;
}
default:
qFatal("Received unknown command");
break;
}
/* set current command CONNACK - PINGRESP */
/* read command size */
/* calculate missing_data */
processData(); // implicitly finishes and enqueues
return;
}
bool QMqttConnection::writePacketToTransport(const QMqttControlPacket &p)

View File

@ -49,6 +49,7 @@
#include <QtCore/QMap>
#include <QtCore/QTimer>
#include <QtCore/QtEndian>
#include <QtCore/QBuffer>
QT_BEGIN_NAMESPACE
@ -105,6 +106,26 @@ public:
QMqttClient *m_client{nullptr};
private:
Q_DISABLE_COPY(QMqttConnection)
void someFuncToBeRemoved();
void finalize_connack();
void finalize_suback();
void finalize_unsuback();
void finalize_publish();
void finalize_pubAckRecComp();
void finalize_pubrel();
void finalize_pingresp();
void processData();
void readBuffer(char *data, qint64 size);
QByteArray readBuffer(qint64 size);
QByteArray m_readBuffer;
qint64 m_missingData{0};
struct PublishData {
quint8 qos;
bool retain;
};
PublishData m_currentPublish;
QMqttControlPacket::PacketType m_currentPacket{QMqttControlPacket::UNKNOWN};
bool writePacketToTransport(const QMqttControlPacket &p);
QMap<quint16, QSharedPointer<QMqttSubscription>> m_pendingSubscriptionAck;
QMap<quint16, QSharedPointer<QMqttSubscription>> m_pendingUnsubscriptions;