mirror of https://github.com/nymea/nymea.git
851 lines
29 KiB
C++
851 lines
29 KiB
C++
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
*
|
|
* Copyright 2013 - 2020, nymea GmbH
|
|
* Contact: contact@nymea.io
|
|
*
|
|
* This file is part of nymea.
|
|
* This project including source code and documentation is protected by
|
|
* copyright law, and remains the property of nymea GmbH. All rights, including
|
|
* reproduction, publication, editing and translation, are reserved. The use of
|
|
* this project is subject to the terms of a license agreement to be concluded
|
|
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
|
* under https://nymea.io/license
|
|
*
|
|
* GNU Lesser General Public License Usage
|
|
* Alternatively, this project may be redistributed and/or modified under the
|
|
* terms of the GNU Lesser General Public License as published by the Free
|
|
* Software Foundation; version 3. This project is distributed in the hope that
|
|
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this project. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
* For any further details and any questions please contact us under
|
|
* contact@nymea.io or see our FAQ/Licensing Information on
|
|
* https://nymea.io/license/faq
|
|
*
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
/*!
|
|
\class Coap
|
|
\brief The client connection class to a CoAP server.
|
|
|
|
\ingroup coap-group
|
|
\inmodule libnymea
|
|
|
|
The Coap class provides a signal solt based communication with a \l{https://tools.ietf.org/html/rfc7252}{CoAP (Constrained Application Protocol)}
|
|
server. The API of this class was inspired by the \l{http://doc.qt.io/qt-5/qnetworkaccessmanager.html}{QNetworkAccessManager} and was
|
|
written according to the \l{https://tools.ietf.org/html/rfc7252}{RFC7252}.
|
|
This class supports also blockwise transfere according to the \l{https://tools.ietf.org/html/draft-ietf-core-block-18}{IETF V18} specifications and
|
|
observing resources according to the \l{https://tools.ietf.org/html/rfc7641}{RFC7641}.
|
|
|
|
\sa CoapReply, CoapRequest
|
|
|
|
\section2 Example
|
|
\code
|
|
MyClass::MyClass(QObject *parent) :
|
|
QObject(parent)
|
|
{
|
|
Coap *coap = new Coap(this);
|
|
connect(coap, SIGNAL(replyFinished(CoapReply*)), this, SLOT(onReplyFinished(CoapReply*)));
|
|
|
|
CoapRequest request(QUrl("coap://coap.me/hello"));
|
|
coap->get(request);
|
|
}
|
|
\endcode
|
|
|
|
\code
|
|
void MyClass::onReplyFinished(CoapReply *reply)
|
|
{
|
|
if (reply->error() != CoapReply::NoError) {
|
|
qWarning() << "Reply finished with error" << reply->errorString();
|
|
reply->deleteLater();
|
|
return;
|
|
}
|
|
|
|
qDebug() << "Reply finished" << reply;
|
|
reply->deleteLater();
|
|
}
|
|
\endcode
|
|
*/
|
|
|
|
/*! \fn void Coap::replyFinished(CoapReply *reply);
|
|
This signal is emitted when the given \a reply is finished.
|
|
*/
|
|
|
|
/*! \fn void Coap::notificationReceived(const CoapObserveResource &resource, const int ¬ificationNumber, const QByteArray &payload);
|
|
This signal is emitted when a value of an observed \a resource changed. The \a notificationNumber specifies the count of the notification
|
|
to keep the correct order. The value can be parsed from the \a payload parameter.
|
|
*/
|
|
|
|
#include "coap.h"
|
|
#include "coappdu.h"
|
|
#include "coapoption.h"
|
|
|
|
Q_LOGGING_CATEGORY(dcCoap, "Coap")
|
|
|
|
/*! Constructs a Coap access manager with the given \a parent and \a port. */
|
|
Coap::Coap(QObject *parent, const quint16 &port) :
|
|
QObject(parent),
|
|
m_reply(nullptr)
|
|
{
|
|
m_socket = new QUdpSocket(this);
|
|
|
|
if (!m_socket->bind(QHostAddress::Any, port, QAbstractSocket::ShareAddress))
|
|
qCWarning(dcCoap) << "Could not bind to port" << port << m_socket->errorString();
|
|
|
|
connect(m_socket, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
|
|
}
|
|
|
|
/*! Performs a ping request to the CoAP server specified in the given \a request.
|
|
* Returns a \l{CoapReply} to match the response with the request. */
|
|
CoapReply *Coap::ping(const CoapRequest &request)
|
|
{
|
|
CoapReply *reply = new CoapReply(request, this);
|
|
reply->setRequestMethod(CoapPdu::Empty);
|
|
|
|
connect(reply, &CoapReply::timeout, this, &Coap::onReplyTimeout);
|
|
connect(reply, &CoapReply::finished, this, &Coap::onReplyFinished);
|
|
|
|
if (request.url().scheme() != "coap") {
|
|
reply->setError(CoapReply::InvalidUrlSchemeError);
|
|
reply->m_isFinished = true;
|
|
return reply;
|
|
}
|
|
|
|
// check if there is a request running
|
|
if (m_reply == 0) {
|
|
m_reply = reply;
|
|
lookupHost();
|
|
} else {
|
|
m_replyQueue.enqueue(reply);
|
|
}
|
|
|
|
return reply;
|
|
}
|
|
|
|
/*! Performs a GET request to the CoAP server specified in the given \a request.
|
|
* Returns a \l{CoapReply} to match the response with the request. */
|
|
CoapReply *Coap::get(const CoapRequest &request)
|
|
{
|
|
CoapReply *reply = new CoapReply(request, this);
|
|
reply->setRequestMethod(CoapPdu::Get);
|
|
|
|
connect(reply, &CoapReply::timeout, this, &Coap::onReplyTimeout);
|
|
connect(reply, &CoapReply::finished, this, &Coap::onReplyFinished);
|
|
|
|
if (request.url().scheme() != "coap") {
|
|
reply->setError(CoapReply::InvalidUrlSchemeError);
|
|
reply->m_isFinished = true;
|
|
return reply;
|
|
}
|
|
|
|
// check if there is a request running
|
|
if (m_reply == 0) {
|
|
m_reply = reply;
|
|
lookupHost();
|
|
} else {
|
|
m_replyQueue.enqueue(reply);
|
|
}
|
|
return reply;
|
|
}
|
|
|
|
/*! Performs a PUT request to the CoAP server specified in the given \a request and \a data.
|
|
* Returns a \l{CoapReply} to match the response with the request. */
|
|
CoapReply *Coap::put(const CoapRequest &request, const QByteArray &data)
|
|
{
|
|
CoapReply *reply = new CoapReply(request, this);
|
|
reply->setRequestMethod(CoapPdu::Put);
|
|
reply->setRequestPayload(data);
|
|
|
|
connect(reply, &CoapReply::timeout, this, &Coap::onReplyTimeout);
|
|
connect(reply, &CoapReply::finished, this, &Coap::onReplyFinished);
|
|
|
|
if (request.url().scheme() != "coap") {
|
|
reply->setError(CoapReply::InvalidUrlSchemeError);
|
|
reply->m_isFinished = true;
|
|
return reply;
|
|
}
|
|
|
|
// check if there is a request running
|
|
if (m_reply == 0) {
|
|
m_reply = reply;
|
|
lookupHost();
|
|
} else {
|
|
m_replyQueue.enqueue(reply);
|
|
}
|
|
|
|
return reply;
|
|
}
|
|
|
|
/*! Performs a POST request to the CoAP server specified in the given \a request and \a data.
|
|
* Returns a \l{CoapReply} to match the response with the request. */
|
|
CoapReply *Coap::post(const CoapRequest &request, const QByteArray &data)
|
|
{
|
|
CoapReply *reply = new CoapReply(request, this);
|
|
reply->setRequestMethod(CoapPdu::Post);
|
|
reply->setRequestPayload(data);
|
|
|
|
connect(reply, &CoapReply::timeout, this, &Coap::onReplyTimeout);
|
|
connect(reply, &CoapReply::finished, this, &Coap::onReplyFinished);
|
|
|
|
if (request.url().scheme() != "coap") {
|
|
reply->setError(CoapReply::InvalidUrlSchemeError);
|
|
reply->m_isFinished = true;
|
|
return reply;
|
|
}
|
|
|
|
// check if there is a request running
|
|
if (m_reply == 0) {
|
|
m_reply = reply;
|
|
lookupHost();
|
|
} else {
|
|
m_replyQueue.enqueue(reply);
|
|
}
|
|
|
|
return reply;
|
|
}
|
|
|
|
/*! Performs a DELETE request to the CoAP server specified in the given \a request.
|
|
* Returns a \l{CoapReply} to match the response with the request. */
|
|
CoapReply *Coap::deleteResource(const CoapRequest &request)
|
|
{
|
|
CoapReply *reply = new CoapReply(request, this);
|
|
reply->setRequestMethod(CoapPdu::Delete);
|
|
|
|
connect(reply, &CoapReply::timeout, this, &Coap::onReplyTimeout);
|
|
connect(reply, &CoapReply::finished, this, &Coap::onReplyFinished);
|
|
|
|
if (request.url().scheme() != "coap") {
|
|
reply->setError(CoapReply::InvalidUrlSchemeError);
|
|
reply->m_isFinished = true;
|
|
return reply;
|
|
}
|
|
|
|
// check if there is a request running
|
|
if (m_reply.isNull()) {
|
|
m_reply = reply;
|
|
lookupHost();
|
|
} else {
|
|
m_replyQueue.enqueue(reply);
|
|
}
|
|
|
|
return reply;
|
|
}
|
|
|
|
/*! Enables notifications (observing) on the CoAP server for the resource specified in the
|
|
* given \a request.
|
|
* Returns a \l{CoapReply} to match the response with the request. */
|
|
CoapReply *Coap::enableResourceNotifications(const CoapRequest &request)
|
|
{
|
|
CoapReply *reply = new CoapReply(request, this);
|
|
reply->setRequestMethod(CoapPdu::Get);
|
|
reply->setObservation(true);
|
|
reply->setObservationEnable(true);
|
|
|
|
connect(reply, &CoapReply::timeout, this, &Coap::onReplyTimeout);
|
|
connect(reply, &CoapReply::finished, this, &Coap::onReplyFinished);
|
|
|
|
if (request.url().scheme() != "coap") {
|
|
reply->setError(CoapReply::InvalidUrlSchemeError);
|
|
reply->m_isFinished = true;
|
|
return reply;
|
|
}
|
|
|
|
// check if there is a request running
|
|
if (m_reply.isNull()) {
|
|
m_reply = reply;
|
|
lookupHost();
|
|
} else {
|
|
m_replyQueue.enqueue(reply);
|
|
}
|
|
|
|
return reply;
|
|
}
|
|
|
|
/*! Disables notifications (observing) on the CoAP server for the resource specified in the
|
|
* given \a request.
|
|
* Returns a \l{CoapReply} to match the response with the request. */
|
|
CoapReply *Coap::disableNotifications(const CoapRequest &request)
|
|
{
|
|
CoapReply *reply = new CoapReply(request, this);
|
|
reply->setRequestMethod(CoapPdu::Get);
|
|
reply->setMessageType(CoapPdu::Reset);
|
|
reply->setObservation(true);
|
|
reply->setObservationEnable(false);
|
|
|
|
connect(reply, &CoapReply::timeout, this, &Coap::onReplyTimeout);
|
|
connect(reply, &CoapReply::finished, this, &Coap::onReplyFinished);
|
|
|
|
if (request.url().scheme() != "coap") {
|
|
reply->setError(CoapReply::InvalidUrlSchemeError);
|
|
reply->m_isFinished = true;
|
|
return reply;
|
|
}
|
|
|
|
// check if there is a request running
|
|
if (m_reply == 0) {
|
|
m_reply = reply;
|
|
lookupHost();
|
|
} else {
|
|
m_replyQueue.enqueue(reply);
|
|
}
|
|
|
|
return reply;
|
|
}
|
|
|
|
void Coap::lookupHost()
|
|
{
|
|
int lookupId = QHostInfo::lookupHost(m_reply->request().url().host(), this, SLOT(hostLookupFinished(QHostInfo)));
|
|
m_runningHostLookups.insert(lookupId, m_reply);
|
|
}
|
|
|
|
void Coap::sendRequest(CoapReply *reply, const bool &lookedUp)
|
|
{
|
|
CoapPdu pdu;
|
|
pdu.setMessageType(reply->request().messageType());
|
|
pdu.setStatusCode(reply->requestMethod());
|
|
pdu.createMessageId();
|
|
pdu.createToken();
|
|
|
|
// Add the options in correct order
|
|
// Option number 3
|
|
if (lookedUp)
|
|
pdu.addOption(CoapOption::UriHost, reply->request().url().host().toUtf8());
|
|
|
|
if (reply->observation() && reply->requestMethod() == CoapPdu::Get) {
|
|
if (reply->observationEnable()) {
|
|
// Option number 6
|
|
pdu.addOption(CoapOption::Observe, 0);
|
|
m_observeResources.insert(pdu.token(), CoapObserveResource(reply->request().url(), pdu.token()));
|
|
} else {
|
|
// if disable, we should use the sam token as the notifications
|
|
foreach (const CoapObserveResource &resource, m_observeResources.values()) {
|
|
if (resource.url() == reply->request().url()) {
|
|
pdu.setToken(resource.token());
|
|
}
|
|
}
|
|
// Option number 6
|
|
pdu.addOption(CoapOption::Observe, QByteArray::number(1));
|
|
if (m_observeResources.contains(pdu.token()))
|
|
m_observeResources.remove(pdu.token());
|
|
}
|
|
}
|
|
|
|
// Option number 7
|
|
if (reply->port() != 5683)
|
|
pdu.addOption(CoapOption::UriPort, QByteArray::number(reply->request().url().port()));
|
|
|
|
QStringList urlTokens = reply->request().url().path().split("/");
|
|
urlTokens.removeAll(QString());
|
|
|
|
// Option number 11
|
|
foreach (const QString &token, urlTokens)
|
|
pdu.addOption(CoapOption::UriPath, token.toUtf8());
|
|
|
|
// Option number 12
|
|
if (reply->requestMethod() == CoapPdu::Post || reply->requestMethod() == CoapPdu::Put) {
|
|
pdu.addOption(CoapOption::ContentFormat, QByteArray(1, ((quint8)reply->request().contentType())));
|
|
|
|
// check if we have to block the payload
|
|
if (reply->requestPayload().size() > 64) {
|
|
pdu.addOption(CoapOption::Block1, CoapPduBlock::createBlock(0, 2, true));
|
|
pdu.setPayload(reply->requestPayload().mid(0, 64));
|
|
} else {
|
|
pdu.setPayload(reply->requestPayload());
|
|
}
|
|
}
|
|
|
|
// Option number 15
|
|
if (reply->request().url().hasQuery())
|
|
pdu.addOption(CoapOption::UriQuery, reply->request().url().query().toUtf8());
|
|
|
|
// Option number 23
|
|
if (reply->requestMethod() == CoapPdu::Get)
|
|
pdu.addOption(CoapOption::Block2, CoapPduBlock::createBlock(0));
|
|
|
|
QByteArray pduData = pdu.pack();
|
|
reply->setRequestData(pduData);
|
|
reply->setMessageId(pdu.messageId());
|
|
reply->setMessageToken(pdu.token());
|
|
reply->m_lockedUp = lookedUp;
|
|
reply->m_timer->start();
|
|
|
|
qCDebug(dcCoap) << "--->" << pdu;
|
|
|
|
// send the data
|
|
if (reply->request().messageType() == CoapPdu::NonConfirmable) {
|
|
sendData(reply->hostAddress(), reply->port(), pduData);
|
|
reply->setFinished();
|
|
} else {
|
|
sendData(reply->hostAddress(), reply->port(), pduData);
|
|
}
|
|
}
|
|
|
|
void Coap::sendData(const QHostAddress &hostAddress, const quint16 &port, const QByteArray &data)
|
|
{
|
|
m_socket->writeDatagram(data, hostAddress, port);
|
|
}
|
|
|
|
void Coap::sendCoapPdu(const QHostAddress &hostAddress, const quint16 &port, const CoapPdu &pdu)
|
|
{
|
|
m_socket->writeDatagram(pdu.pack(), hostAddress, port);
|
|
}
|
|
|
|
void Coap::processResponse(const CoapPdu &pdu, const QHostAddress &address, const quint16 &port)
|
|
{
|
|
// if we are waiting for a reply response
|
|
if (m_reply) {
|
|
qCDebug(dcCoap) << "<---" << QString("%1:%2").arg(address.toString()).arg(QString::number(port)) << pdu;
|
|
if (!pdu.isValid()) {
|
|
qCWarning(dcCoap) << "Got invalid PDU";
|
|
m_reply->setError(CoapReply::InvalidPduError);
|
|
m_reply->setFinished();
|
|
return;
|
|
}
|
|
|
|
// check if the message is a response to a reply (message id based check)
|
|
if (m_reply->messageId() == pdu.messageId()) {
|
|
processIdBasedResponse(m_reply, pdu);
|
|
return;
|
|
}
|
|
|
|
// check if we know the message by token (message token based check)
|
|
if (m_reply->messageToken() == pdu.token()) {
|
|
processTokenBasedResponse(m_reply, pdu);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
if (m_observerReply) {
|
|
processBlock2Notification(m_observerReply, pdu);
|
|
return;
|
|
}
|
|
|
|
// check if this is a notification
|
|
if (m_observeResources.keys().contains(pdu.token())) {
|
|
processNotification(pdu, address, port);
|
|
return;
|
|
}
|
|
|
|
qCDebug(dcCoap) << "Got message without request or registered observe resource." << endl << "<---" << pdu;
|
|
CoapPdu responsePdu;
|
|
responsePdu.setMessageType(CoapPdu::Reset);
|
|
responsePdu.setMessageId(pdu.messageId());
|
|
responsePdu.setToken(pdu.token());
|
|
sendCoapPdu(address, port, responsePdu);
|
|
}
|
|
|
|
void Coap::processIdBasedResponse(CoapReply *reply, const CoapPdu &pdu)
|
|
{
|
|
// check if this is an empty ACK response (which indicates a separated response)
|
|
if (pdu.statusCode() == CoapPdu::Empty && pdu.messageType() == CoapPdu::Acknowledgement) {
|
|
reply->m_timer->stop();
|
|
qCDebug(dcCoap) << "Got empty ACK. Data will be sent separated.";
|
|
return;
|
|
}
|
|
|
|
// check if this is a Block1 pdu
|
|
if (pdu.messageType() == CoapPdu::Acknowledgement && pdu.hasOption(CoapOption::Block1)) {
|
|
processBlock1Response(reply, pdu);
|
|
return;
|
|
}
|
|
|
|
// check if this is a Block2 pdu
|
|
if (pdu.messageType() == CoapPdu::Acknowledgement && pdu.hasOption(CoapOption::Block2)) {
|
|
processBlock2Response(reply, pdu);
|
|
return;
|
|
}
|
|
|
|
// Piggybacked response
|
|
reply->setStatusCode(pdu.statusCode());
|
|
reply->setContentType(pdu.contentType());
|
|
reply->appendPayloadData(pdu.payload());
|
|
reply->setFinished();
|
|
}
|
|
|
|
void Coap::processTokenBasedResponse(CoapReply *reply, const CoapPdu &pdu)
|
|
{
|
|
// Separate Response
|
|
CoapPdu responsePdu;
|
|
responsePdu.setMessageType(CoapPdu::Acknowledgement);
|
|
responsePdu.setStatusCode(CoapPdu::Empty);
|
|
responsePdu.setMessageId(pdu.messageId());
|
|
sendCoapPdu(reply->hostAddress(), reply->port(), responsePdu);
|
|
|
|
reply->setStatusCode(pdu.statusCode());
|
|
reply->setContentType(pdu.contentType());
|
|
reply->appendPayloadData(pdu.payload());
|
|
reply->setFinished();
|
|
}
|
|
|
|
void Coap::processNotification(const CoapPdu &pdu, const QHostAddress &address, const quint16 &port)
|
|
{
|
|
CoapObserveResource resource = m_observeResources.value(pdu.token());
|
|
qCDebug(dcCoap) << "<--- Notification" << endl << pdu;
|
|
|
|
// check if it is a blockwise notification
|
|
if (pdu.hasOption(CoapOption::Block2)) {
|
|
if (!m_observeReplyResource.values().contains(resource)) {
|
|
|
|
qCDebug(dcCoap) << "Got first part of blocked notification";
|
|
|
|
// First part of the blocked notification
|
|
// respond with ACK
|
|
CoapPdu responsePdu;
|
|
responsePdu.setMessageType(CoapPdu::Acknowledgement);
|
|
responsePdu.setStatusCode(CoapPdu::Empty);
|
|
responsePdu.setMessageId(pdu.messageId());
|
|
responsePdu.setToken(pdu.token());
|
|
|
|
qCDebug(dcCoap) << "---> Notification" << endl << responsePdu;
|
|
sendCoapPdu(address, port, responsePdu);
|
|
|
|
// create reply for blockwise transfere
|
|
if (!m_observerReply.isNull()) {
|
|
m_observeBlockwise.remove(m_observerReply);
|
|
m_observerReply->deleteLater();
|
|
}
|
|
|
|
m_observerReply = new CoapReply(CoapRequest(resource.url()), this);
|
|
m_observerReply->setRequestMethod(CoapPdu::Get);
|
|
m_observerReply->appendPayloadData(pdu.payload());
|
|
|
|
// Lets store the observation number
|
|
int notificationNumber = 0;
|
|
foreach (const CoapOption &option, pdu.options()) {
|
|
if (option.option() == CoapOption::Observe) {
|
|
notificationNumber = option.data().toHex().toInt(0, 16);
|
|
}
|
|
}
|
|
|
|
m_observeReplyResource.insert(m_observerReply, resource);
|
|
m_observeBlockwise.insert(m_observerReply, notificationNumber);
|
|
|
|
connect(m_observerReply.data(), &CoapReply::timeout, this, &Coap::onReplyTimeout);
|
|
connect(m_observerReply.data(), &CoapReply::finished, this, &Coap::onReplyFinished);
|
|
|
|
CoapPdu pdu;
|
|
pdu.setMessageType(m_observerReply->request().messageType());
|
|
pdu.setStatusCode(m_observerReply->requestMethod());
|
|
pdu.createMessageId();
|
|
pdu.createToken();
|
|
|
|
// Add the options in correct order
|
|
// Option number 3
|
|
pdu.addOption(CoapOption::UriHost, m_observerReply->request().url().host().toUtf8());
|
|
|
|
QStringList urlTokens = m_observerReply->request().url().path().split("/");
|
|
urlTokens.removeAll(QString());
|
|
|
|
// Option number 11
|
|
foreach (const QString &token, urlTokens)
|
|
pdu.addOption(CoapOption::UriPath, token.toUtf8());
|
|
|
|
// Option number 15
|
|
if (m_observerReply->request().url().hasQuery())
|
|
pdu.addOption(CoapOption::UriQuery, m_observerReply->request().url().query().toUtf8());
|
|
|
|
// Option number 23
|
|
pdu.addOption(CoapOption::Block2, CoapPduBlock::createBlock(1, 2, true));
|
|
|
|
QByteArray pduData = pdu.pack();
|
|
m_observerReply->setRequestData(pduData);
|
|
m_observerReply->setHostAddress(address);
|
|
m_observerReply->setPort(port);
|
|
m_observerReply->m_timer->start();
|
|
|
|
qCDebug(dcCoap) << "---> Notification" << endl << pdu;
|
|
sendData(address, port, pduData);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// respond with ACK
|
|
CoapPdu responsePdu;
|
|
responsePdu.setMessageType(CoapPdu::Acknowledgement);
|
|
responsePdu.setStatusCode(CoapPdu::Empty);
|
|
responsePdu.setMessageId(pdu.messageId());
|
|
responsePdu.setToken(pdu.token());
|
|
|
|
qCDebug(dcCoap) << "---> Notification" << endl << responsePdu;
|
|
sendCoapPdu(address, port, responsePdu);
|
|
|
|
int notificationNumber = 0;
|
|
foreach (const CoapOption &option, pdu.options()) {
|
|
if (option.option() == CoapOption::Observe) {
|
|
notificationNumber = option.data().toHex().toInt(0, 16);
|
|
}
|
|
}
|
|
|
|
emit notificationReceived(resource, notificationNumber, pdu.payload());
|
|
}
|
|
|
|
void Coap::processBlock1Response(CoapReply *reply, const CoapPdu &pdu)
|
|
{
|
|
qCDebug(dcCoap) << "Sent successfully block #" << pdu.block().blockNumber();
|
|
|
|
// create next block
|
|
int index = (pdu.block().blockNumber() * 64) + 64;
|
|
QByteArray newBlockData = reply->requestPayload().mid(index, 64);
|
|
bool moreFlag = true;
|
|
|
|
// check if this was the last block
|
|
if (newBlockData.isEmpty()) {
|
|
reply->setStatusCode(pdu.statusCode());
|
|
reply->setContentType(pdu.contentType());
|
|
reply->setFinished();
|
|
return;
|
|
}
|
|
|
|
// check if this is the last block or there will be no next block
|
|
if (newBlockData.size() < 64 || (index + 64) == reply->requestPayload().size())
|
|
moreFlag = false;
|
|
|
|
CoapPdu nextBlockRequest;
|
|
nextBlockRequest.setContentType(reply->request().contentType());
|
|
nextBlockRequest.setMessageType(reply->request().messageType());
|
|
nextBlockRequest.setStatusCode(reply->requestMethod());
|
|
nextBlockRequest.setMessageId(pdu.messageId() + 1);
|
|
nextBlockRequest.setToken(pdu.token());
|
|
|
|
// Add the options in correct order
|
|
// Option number 3
|
|
if (reply->m_lockedUp)
|
|
nextBlockRequest.addOption(CoapOption::UriHost, reply->request().url().host().toUtf8());
|
|
|
|
// Option number 7
|
|
if (reply->port() != 5683)
|
|
nextBlockRequest.addOption(CoapOption::UriPort, QByteArray::number(reply->request().url().port()));
|
|
|
|
QStringList urlTokens = reply->request().url().path().split("/");
|
|
urlTokens.removeAll(QString());
|
|
|
|
// Option number 11
|
|
foreach (const QString &token, urlTokens)
|
|
nextBlockRequest.addOption(CoapOption::UriPath, token.toUtf8());
|
|
|
|
// Option number 15
|
|
if (reply->request().url().hasQuery())
|
|
nextBlockRequest.addOption(CoapOption::UriQuery, reply->request().url().query().toUtf8());
|
|
|
|
// Option number 27
|
|
nextBlockRequest.addOption(CoapOption::Block1, CoapPduBlock::createBlock(pdu.block().blockNumber() + 1, 2, moreFlag));
|
|
|
|
nextBlockRequest.setPayload(newBlockData);
|
|
|
|
QByteArray pduData = nextBlockRequest.pack();
|
|
reply->setRequestData(pduData);
|
|
reply->m_timer->start();
|
|
reply->m_retransmissions = 1;
|
|
|
|
reply->setMessageId(nextBlockRequest.messageId());
|
|
|
|
qCDebug(dcCoap) << "--->" << nextBlockRequest;
|
|
sendData(reply->hostAddress(), reply->port(), pduData);
|
|
}
|
|
|
|
void Coap::processBlock2Response(CoapReply *reply, const CoapPdu &pdu)
|
|
{
|
|
reply->appendPayloadData(pdu.payload());
|
|
|
|
// check if this was the last block
|
|
if (!pdu.block().moreFlag()) {
|
|
reply->setStatusCode(pdu.statusCode());
|
|
reply->setContentType(pdu.contentType());
|
|
reply->setFinished();
|
|
return;
|
|
}
|
|
|
|
CoapPdu nextBlockRequest;
|
|
nextBlockRequest.setContentType(reply->request().contentType());
|
|
nextBlockRequest.setMessageType(reply->request().messageType());
|
|
nextBlockRequest.setStatusCode(reply->requestMethod());
|
|
nextBlockRequest.setMessageId(pdu.messageId() + 1);
|
|
nextBlockRequest.setToken(pdu.token());
|
|
|
|
// Add the options in correct order
|
|
// Option number 3
|
|
if (reply->m_lockedUp)
|
|
nextBlockRequest.addOption(CoapOption::UriHost, reply->request().url().host().toUtf8());
|
|
|
|
// Option number 7
|
|
if (reply->port() != 5683)
|
|
nextBlockRequest.addOption(CoapOption::UriPort, QByteArray::number(reply->request().url().port()));
|
|
|
|
QStringList urlTokens = reply->request().url().path().split("/");
|
|
urlTokens.removeAll(QString());
|
|
|
|
// Option number 11
|
|
foreach (const QString &token, urlTokens)
|
|
nextBlockRequest.addOption(CoapOption::UriPath, token.toUtf8());
|
|
|
|
// Option number 15
|
|
if (reply->request().url().hasQuery())
|
|
nextBlockRequest.addOption(CoapOption::UriQuery, reply->request().url().query().toUtf8());
|
|
|
|
// Option number 23
|
|
nextBlockRequest.addOption(CoapOption::Block2, CoapPduBlock::createBlock(pdu.block().blockNumber() + 1, 2, false));
|
|
|
|
QByteArray pduData = nextBlockRequest.pack();
|
|
reply->setRequestData(pduData);
|
|
reply->m_timer->start();
|
|
|
|
reply->setMessageId(nextBlockRequest.messageId());
|
|
|
|
qCDebug(dcCoap) << "--->" << nextBlockRequest;
|
|
sendData(reply->hostAddress(), reply->port(), pduData);
|
|
}
|
|
|
|
void Coap::processBlock2Notification(CoapReply *reply, const CoapPdu &pdu)
|
|
{
|
|
if (!m_observeReplyResource.contains(reply)) {
|
|
qCWarning(dcCoap) << "Could not find observation resource for" << reply;
|
|
return;
|
|
}
|
|
|
|
CoapObserveResource resource = m_observeReplyResource.value(reply);
|
|
|
|
// respond Block2
|
|
// check if this was the last block
|
|
if (!pdu.block().moreFlag()) {
|
|
// respond with ACK
|
|
CoapPdu responsePdu;
|
|
responsePdu.setMessageType(CoapPdu::Acknowledgement);
|
|
responsePdu.setStatusCode(CoapPdu::Empty);
|
|
responsePdu.setMessageId(pdu.messageId());
|
|
responsePdu.setToken(pdu.token());
|
|
|
|
qCDebug(dcCoap) << "---> Notification" << endl << responsePdu;
|
|
sendCoapPdu(reply->hostAddress(), reply->port(), responsePdu);
|
|
|
|
reply->appendPayloadData(pdu.payload());
|
|
|
|
emit notificationReceived(resource, m_observeBlockwise.take(reply), reply->payload());
|
|
m_observeReplyResource.remove(m_observerReply);
|
|
m_observerReply->deleteLater();
|
|
m_observerReply.clear();
|
|
return;
|
|
}
|
|
|
|
reply->appendPayloadData(pdu.payload());
|
|
|
|
CoapPdu nextBlockRequest;
|
|
nextBlockRequest.setContentType(reply->request().contentType());
|
|
nextBlockRequest.setMessageType(reply->request().messageType());
|
|
nextBlockRequest.setStatusCode(reply->requestMethod());
|
|
nextBlockRequest.setMessageId(pdu.messageId() + 1);
|
|
nextBlockRequest.setToken(pdu.token());
|
|
|
|
// Add the options in correct order
|
|
// Option number 3
|
|
if (reply->m_lockedUp)
|
|
nextBlockRequest.addOption(CoapOption::UriHost, reply->request().url().host().toUtf8());
|
|
|
|
// Option number 7
|
|
if (reply->port() != 5683)
|
|
nextBlockRequest.addOption(CoapOption::UriPort, QByteArray::number(reply->request().url().port()));
|
|
|
|
QStringList urlTokens = reply->request().url().path().split("/");
|
|
urlTokens.removeAll(QString());
|
|
|
|
// Option number 11
|
|
foreach (const QString &token, urlTokens)
|
|
nextBlockRequest.addOption(CoapOption::UriPath, token.toUtf8());
|
|
|
|
// Option number 15
|
|
if (reply->request().url().hasQuery())
|
|
nextBlockRequest.addOption(CoapOption::UriQuery, reply->request().url().query().toUtf8());
|
|
|
|
// Option number 23
|
|
nextBlockRequest.addOption(CoapOption::Block2, CoapPduBlock::createBlock(pdu.block().blockNumber() + 1, 2, false));
|
|
|
|
QByteArray pduData = nextBlockRequest.pack();
|
|
reply->setRequestData(pduData);
|
|
reply->m_timer->start();
|
|
|
|
reply->setMessageId(nextBlockRequest.messageId());
|
|
|
|
qCDebug(dcCoap) << "---> Notification" << endl << nextBlockRequest;
|
|
sendData(reply->hostAddress(), reply->port(), pduData);
|
|
}
|
|
|
|
void Coap::hostLookupFinished(const QHostInfo &hostInfo)
|
|
{
|
|
CoapReply *reply = m_runningHostLookups.take(hostInfo.lookupId());;
|
|
reply->setPort(reply->request().url().port(5683));
|
|
|
|
if (hostInfo.error() != QHostInfo::NoError) {
|
|
qCDebug(dcCoap) << "Host lookup for" << reply->request().url().host() << "failed:" << hostInfo.errorString();
|
|
reply->setError(CoapReply::HostNotFoundError);
|
|
reply->setFinished();
|
|
return;
|
|
}
|
|
|
|
QHostAddress hostAddress = hostInfo.addresses().first();
|
|
reply->setHostAddress(hostAddress);
|
|
|
|
// check if the url had to be looked up
|
|
if (reply->request().url().host() != hostAddress.toString()) {
|
|
qCDebug(dcCoap) << reply->request().url().host() << " -> " << hostAddress.toString();
|
|
sendRequest(reply, true);
|
|
} else {
|
|
sendRequest(reply);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
CoapPdu pdu(data);
|
|
processResponse(pdu, hostAddress, port);
|
|
}
|
|
|
|
void Coap::onReplyTimeout()
|
|
{
|
|
CoapReply *reply = qobject_cast<CoapReply *>(sender());
|
|
if (reply->m_retransmissions < 5) {
|
|
qCDebug(dcCoap) << QString("Reply timeout: resending message %1/4").arg(reply->m_retransmissions);
|
|
}
|
|
reply->resend();
|
|
m_socket->writeDatagram(reply->requestData(), reply->hostAddress(), reply->port());
|
|
}
|
|
|
|
void Coap::onReplyFinished()
|
|
{
|
|
CoapReply *reply = qobject_cast<CoapReply *>(sender());
|
|
|
|
if (reply == m_observerReply) {
|
|
m_observeReplyResource.remove(m_observerReply);
|
|
m_observerReply->deleteLater();
|
|
m_observerReply.clear();
|
|
qCWarning(dcCoap) << "Notification reply finished wirh error" << reply->errorString();
|
|
return;
|
|
}
|
|
|
|
if (reply != m_reply)
|
|
qCWarning(dcCoap) << "This should never happen!! Please report a bug if you get this message!";
|
|
|
|
emit replyFinished(reply);
|
|
m_reply.clear();
|
|
|
|
// check if there is a request in the queue
|
|
if (!m_replyQueue.isEmpty()) {
|
|
m_reply = m_replyQueue.dequeue();
|
|
if (m_reply)
|
|
lookupHost();
|
|
}
|
|
}
|