Some fixes in Coap

* Fixed an indexOutOfRange warning when creating coap requests
  because the first addOption() call was calling
  m_options.insert(1) on an empty list.

* Old code was appending multiple UDP datagrams to a single big Coap
  message, however, Coap is specified to only send s single datagram
  per message. The datagram length specifies the payload size.

* some boolean member variables weren't initialized which resulted in
  occational wrong flags.

* Parsing had issues with determining the option length in some occations
  and also would crash when receiving coap messages without any options
  or payload. To get rid of the complex and erraneous index calculations,
  the entire package parsing is now using a DataStream.

(This makes it work with Shelly devices)
This commit is contained in:
Michael Zanetti 2022-06-05 02:02:45 +02:00
parent 45081ad64e
commit 62644650c0
4 changed files with 89 additions and 55 deletions

View File

@ -801,16 +801,15 @@ void Coap::hostLookupFinished(const QHostInfo &hostInfo)
void Coap::onReadyRead()
{
QHostAddress hostAddress;
QByteArray data;
quint16 port;
while (m_socket->hasPendingDatagrams()) {
data.resize(m_socket->pendingDatagramSize());
m_socket->readDatagram(data.data(), data.size(), &hostAddress, &port);
QByteArray datagram(m_socket->pendingDatagramSize(), 0);
m_socket->readDatagram(datagram.data(), datagram.size(), &hostAddress, &port);
qCDebug(dcCoap()) << "Datagram received from:" << hostAddress << ":" << datagram;
CoapPdu pdu(datagram);
processResponse(pdu, hostAddress, port);
}
CoapPdu pdu(data);
processResponse(pdu, hostAddress, port);
}
void Coap::onReplyTimeout()

View File

@ -163,6 +163,10 @@
#include <QMetaEnum>
#include <QTime>
#include <QLoggingCategory>
#include <QDataStream>
Q_DECLARE_LOGGING_CATEGORY(dcCoap)
/*! Constructs a CoapPdu with the given \a parent. */
CoapPdu::CoapPdu(QObject *parent) :
@ -370,7 +374,7 @@ void CoapPdu::addOption(const CoapOption::Option &option, const QByteArray &data
CoapOption o;
o.setOption(option);
o.setData(data);
m_options.insert(index + 1, o);
m_options.insert(qMin(m_options.length(), index + 1), o);
}
/*! Returns the block of this \l{CoapPdu}. */
@ -504,70 +508,97 @@ QByteArray CoapPdu::pack() const
void CoapPdu::unpack(const QByteArray &data)
{
// create a CoapPDU
if (data.length() < 4) {
m_error = InvalidPduSizeError;
}
quint8 *rawData = (quint8 *)data.data();
setVersion((rawData[0] & 0xc0) >> 6);
setMessageType(static_cast<MessageType>((rawData[0] & 0x30) >> 4));
quint8 tokenLength = (rawData[0] & 0xf);
QDataStream stream(data);
quint8 flags;
stream >> flags;
setVersion((flags & 0xc0) >> 6);
// qCDebug(dcCoap()) << "Version:" << m_version;
setMessageType(static_cast<MessageType>((flags & 0x30) >> 4));
// qCDebug(dcCoap()) << "Message Type:" << messageType();
quint8 tokenLength = flags & 0x0f;
// qCDebug(dcCoap()) << "Token length:" << tokenLength;
if (tokenLength > 8) {
qCWarning(dcCoap()) << "Inavalid token length" << tokenLength;
m_error = InvalidTokenError;
return;
}
setToken(QByteArray((const char *)rawData + 4, tokenLength));
setStatusCode(static_cast<StatusCode>(rawData[1]));
setMessageId((qint16)data.mid(2,2).toHex().toUInt(0,16));
quint8 reqRspCode;
stream >> reqRspCode;
setStatusCode(static_cast<StatusCode>(reqRspCode));
// qCDebug(dcCoap()) << "Req/Rsp code:" << statusCode();
// parse options
int index = 4 + tokenLength;
quint8 optionByte = rawData[index];
quint16 delta = 0;
while (QByteArray::number(optionByte, 16) != "ff" && optionByte != 0) {
quint16 optionNumber = ((optionByte & 0xf0) >> 4);
quint16 messageId;
stream >> messageId;
setMessageId(messageId);
// qCDebug(dcCoap()) << "Message ID:" << messageId;
char tokenData[tokenLength];
if (stream.readRawData(tokenData, tokenLength) != tokenLength) {
qCWarning(dcCoap()) << "Token data not complete.";
m_error = InvalidTokenError;
return;
}
QByteArray token(tokenData, tokenLength);
setToken(token);
// qCDebug(dcCoap()) << "Token:" << token.toHex();
// check option delta
if (optionNumber < 13) {
delta += optionNumber;
} else if (optionNumber == 13) {
// extended 8 bit option delta
delta += (quint8)(rawData[index + 1] + 13);
index += 1;
} else if (optionNumber == 14) {
// extended 16 bit option delta
delta += ((rawData[index + 1] << 8) | rawData[index + 2]) + 269;
index += 2;
} else if (optionNumber == 15) {
m_error = InvalidOptionDeltaError;
while (!stream.atEnd()) {
quint8 optionByte;
stream >> optionByte;
// qCDebug(dcCoap()) << "OptionByte:" << optionByte;
if (optionByte == 0xff) {
char payloadData[65507]; // Max UDP datagram size
int payloadLength = stream.readRawData(payloadData, 65507);
if (payloadLength > 0) {
setPayload(QByteArray(payloadData, payloadLength));
}
return;
}
quint16 optionDelta;
optionDelta = (optionByte & 0xf0) >> 4;
// qCDebug(dcCoap()) << "Option delta:" << optionDelta;
quint16 optionLength = (optionByte & 0x0f);
// qCDebug(dcCoap()) << "Option length:" << optionLength;
if (optionDelta == 13) {
quint8 optionDeltaExtended;
stream >> optionDeltaExtended;
optionDelta = optionDeltaExtended + 13;
// qCDebug(dcCoap()).nospace() << "Extended option delta (8 bit): " << optionDelta << " (" << optionDeltaExtended << " + 13)";
} else if (optionDelta == 14) {
quint16 optionDeltaExtended;
stream >> optionDeltaExtended;
optionDelta = optionDeltaExtended + 269;
// qCDebug(dcCoap()).nospace() << "Extended option delta (16 bit): " << optionDelta << " (" << optionDeltaExtended << " + 269)";
}
// check option length
quint16 optionLength = (optionByte & 0xf);
if (optionLength == 13) {
// extended 8 bit option length
optionLength = (quint8)(rawData[index + 1] - 13);
index += 1;
quint8 optionLengthExtended;
stream >> optionLengthExtended;
optionLength = optionLengthExtended + 13;
// qCDebug(dcCoap()).nospace() << "Extended option length (8 bit): " << optionLength << " (" << optionLengthExtended << " + 13)";
} else if (optionLength == 14) {
// extended 16 bit option delta
optionLength = ((rawData[index + 1] << 8) | rawData[index + 2]) - 269;
index += 2;
} else if (optionLength == 15) {
m_error = InvalidOptionLengthError;
quint16 optionLengthExtended;
stream >> optionLengthExtended;
optionLength = optionLengthExtended + 269;
// qCDebug(dcCoap()).nospace() << "Extended option kength (16 bit): " << optionDelta << " (" << optionLengthExtended << " + 269)";
}
QByteArray optionData = QByteArray((const char *)rawData + index + 1, optionLength);
addOption(static_cast<CoapOption::Option>(delta), optionData);
char optionData[optionLength];
stream.readRawData(optionData, optionLength);
// qCDebug(dcCoap()) << "Option data:" << QByteArray(optionData, optionLength);
index += optionLength + 1;
optionByte = rawData[index];
if (QByteArray::number(optionByte, 16) == "ff") {
setPayload(data.right(data.length() - index - 1));
break;
}
addOption(static_cast<CoapOption::Option>(optionDelta), QByteArray(optionData, optionLength));
}
}

View File

@ -68,6 +68,7 @@ public:
Acknowledgement = 0x02,
Reset = 0x03
};
Q_ENUM(MessageType)
// Methods: https://tools.ietf.org/html/rfc7252#section-5.8
// Respond codes: https://tools.ietf.org/html/rfc7252#section-12.1.2
@ -101,6 +102,7 @@ public:
GatewayTimeout = 0xa4, // 5.04
ProxyingNotSupported = 0xa5 // 5.05
};
Q_ENUM(StatusCode)
// https://tools.ietf.org/html/rfc7252#section-12.3
enum ContentType {
@ -111,6 +113,7 @@ public:
ApplicationExi = 47,
ApplicationJson = 50
};
Q_ENUM(ContentType)
enum Error {
NoError,
@ -120,6 +123,7 @@ public:
InvalidOptionLengthError,
UnknownOptionError
};
Q_ENUM(Error)
CoapPdu(QObject *parent = 0);
CoapPdu(const QByteArray &data, QObject *parent = 0);

View File

@ -132,8 +132,8 @@ private:
int m_messageId;
QByteArray m_messageToken;
bool m_observation;
bool m_observationEnable;
bool m_observation = false;
bool m_observationEnable = false;
signals:
void timeout();