diff --git a/libguh/coap/coap.cpp b/libguh/coap/coap.cpp new file mode 100644 index 00000000..9d4cef4b --- /dev/null +++ b/libguh/coap/coap.cpp @@ -0,0 +1,473 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of QtCoap. * + * * + * QtCoap is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 3 of the License. * + * * + * QtCoap 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with QtCoap. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "coap.h" +#include "coappdu.h" +#include "coapoption.h" + +Coap::Coap(QObject *parent, const quint16 &port) : + QObject(parent), + m_reply(0) +{ + m_socket = new QUdpSocket(this); + + if (!m_socket->bind(QHostAddress::Any, port)) + qWarning() << "Could not bind to port" << port << m_socket->errorString(); + + connect(m_socket, SIGNAL(readyRead()), this, SLOT(onReadyRead())); +} + +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; +} + +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; +} + +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; +} + +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; +} + +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 == 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(); + + if (lookedUp) + pdu.addOption(CoapOption::UriHost, reply->request().url().host().toUtf8()); + + QStringList urlTokens = reply->request().url().path().split("/"); + urlTokens.removeAll(QString()); + + foreach (const QString &token, urlTokens) + pdu.addOption(CoapOption::UriPath, token.toUtf8()); + + if (reply->request().url().hasQuery()) + pdu.addOption(CoapOption::UriQuery, reply->request().url().query().toUtf8()); + + if (reply->requestMethod() == CoapPdu::Get) + pdu.addOption(CoapOption::Block2, CoapPduBlock::createBlock(0)); + + 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()); + } + } + + QByteArray pduData = pdu.pack(); + reply->setRequestData(pduData); + reply->setMessageId(pdu.messageId()); + reply->setMessageToken(pdu.token()); + reply->m_lockedUp = lookedUp; + reply->m_timer->start(); + + qDebug() << "--->" << 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) +{ + qDebug() << "--->" << pdu; + m_socket->writeDatagram(pdu.pack(), hostAddress, port); +} + +void Coap::processResponse(const CoapPdu &pdu) +{ + qDebug() << "<---" << pdu; + + if (!pdu.isValid()) { + qWarning() << "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; + } + + qDebug() << "Got message without request"; +} + +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(); + qDebug() << "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::processBlock1Response(CoapReply *reply, const CoapPdu &pdu) +{ + qDebug() << "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()); + + if (reply->m_lockedUp) + nextBlockRequest.addOption(CoapOption::UriHost, reply->request().url().host().toUtf8()); + + if (reply->port() != 5683) + nextBlockRequest.addOption(CoapOption::UriPort, QByteArray::number(reply->request().url().port())); + + QStringList urlTokens = reply->request().url().path().split("/"); + urlTokens.removeAll(QString()); + + foreach (const QString &token, urlTokens) + nextBlockRequest.addOption(CoapOption::UriPath, token.toUtf8()); + + if (reply->request().url().hasQuery()) + nextBlockRequest.addOption(CoapOption::UriQuery, reply->request().url().query().toUtf8()); + + 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()); + + qDebug() << "--->" << 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()); + + if (reply->m_lockedUp) + nextBlockRequest.addOption(CoapOption::UriHost, reply->request().url().host().toUtf8()); + + if (reply->port() != 5683) + nextBlockRequest.addOption(CoapOption::UriPort, QByteArray::number(reply->request().url().port())); + + + QStringList urlTokens = reply->request().url().path().split("/"); + urlTokens.removeAll(QString()); + + foreach (const QString &token, urlTokens) + nextBlockRequest.addOption(CoapOption::UriPath, token.toUtf8()); + + if (reply->request().url().hasQuery()) + nextBlockRequest.addOption(CoapOption::UriQuery, reply->request().url().query().toUtf8()); + + 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()); + + qDebug() << "--->" << 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) { + qDebug() << "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()) { + qDebug() << 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); +} + +void Coap::onReplyTimeout() +{ + CoapReply *reply = qobject_cast(sender()); + if (reply->m_retransmissions < 5) { + qDebug() << 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(sender()); + + if (reply != m_reply) + qWarning() << "This should never happen!! Please report a bug if you get this message!"; + + emit replyFinished(reply); + + m_reply = 0; + // check if there is a request in the queue + if (!m_replyQueue.isEmpty()) { + m_reply = m_replyQueue.dequeue(); + if (m_reply) + lookupHost(); + } +} diff --git a/libguh/coap/coap.h b/libguh/coap/coap.h new file mode 100644 index 00000000..3135635b --- /dev/null +++ b/libguh/coap/coap.h @@ -0,0 +1,84 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of QtCoap. * + * * + * QtCoap is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 3 of the License. * + * * + * QtCoap 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with QtCoap. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef COAP_H +#define COAP_H + +#include +#include +#include +#include +#include + +#include "coaprequest.h" +#include "coapreply.h" + +/* Information about CoAP + * + * The Constrained Application Protocol (CoAP) : https://tools.ietf.org/html/rfc7252 + * Blockwise transfers in CoAP : https://tools.ietf.org/html/draft-ietf-core-block-18 + * Constrained RESTful Environments (CoRE) Link Format : http://tools.ietf.org/html/rfc6690 + * Observing Resources in CoAP : https://tools.ietf.org/html/rfc7641 + */ + +class Coap : public QObject +{ + Q_OBJECT + +public: + explicit Coap(QObject *parent = 0, const quint16 &port = 5683); + + CoapReply *ping(const CoapRequest &request); + CoapReply *get(const CoapRequest &request); + CoapReply *put(const CoapRequest &request, const QByteArray &data = QByteArray()); + CoapReply *post(const CoapRequest &request, const QByteArray &data = QByteArray()); + CoapReply *deleteResource(const CoapRequest &request); + +private: + QUdpSocket *m_socket; + + CoapReply *m_reply; + QHash m_runningHostLookups; + + QQueue m_replyQueue; + + void lookupHost(); + void sendRequest(CoapReply *reply, const bool &lookedUp = false); + void sendData(const QHostAddress &hostAddress, const quint16 &port, const QByteArray &data); + void sendCoapPdu(const QHostAddress &hostAddress, const quint16 &port, const CoapPdu &pdu); + + void processResponse(const CoapPdu &pdu); + void processIdBasedResponse(CoapReply *reply, const CoapPdu &pdu); + void processTokenBasedResponse(CoapReply *reply, const CoapPdu &pdu); + + void processBlock1Response(CoapReply *reply, const CoapPdu &pdu); + void processBlock2Response(CoapReply *reply, const CoapPdu &pdu); + +signals: + void replyFinished(CoapReply *reply); + +private slots: + void hostLookupFinished(const QHostInfo &hostInfo); + void onReadyRead(); + void onReplyTimeout(); + void onReplyFinished(); +}; + +#endif // COAP_H diff --git a/libguh/coap/coap.pri b/libguh/coap/coap.pri new file mode 100644 index 00000000..1372e905 --- /dev/null +++ b/libguh/coap/coap.pri @@ -0,0 +1,24 @@ +QT += network + +QMAKE_CXXFLAGS += -Werror +CONFIG += c++11 + +SOURCES += \ + $$PWD/coap.cpp \ + $$PWD/coappdu.cpp \ + $$PWD/coapoption.cpp \ + $$PWD/coaprequest.cpp \ + $$PWD/coapreply.cpp \ + $$PWD/coappdublock.cpp \ + $$PWD/corelinkparser.cpp \ + $$PWD/corelink.cpp + +HEADERS += \ + $$PWD/coap.h \ + $$PWD/coappdu.h \ + $$PWD/coapoption.h \ + $$PWD/coaprequest.h \ + $$PWD/coapreply.h \ + $$PWD/coappdublock.h \ + $$PWD/corelinkparser.h \ + $$PWD/corelink.h diff --git a/libguh/coap/coapoption.cpp b/libguh/coap/coapoption.cpp new file mode 100644 index 00000000..cca94f4f --- /dev/null +++ b/libguh/coap/coapoption.cpp @@ -0,0 +1,105 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of QtCoap. * + * * + * QtCoap is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 3 of the License. * + * * + * QtCoap 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with QtCoap. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "coapoption.h" + +#include + +CoapOption::CoapOption() +{ +} + +CoapOption::CoapOption(const CoapOption::Option &option, const QByteArray &data) : + m_option(option), + m_data(data) +{ +} + +void CoapOption::setOption(const CoapOption::Option &option) +{ + m_option = option; +} + +CoapOption::Option CoapOption::option() const +{ + return m_option; +} + +void CoapOption::setData(const QByteArray &data) +{ + m_data = data; +} + +QByteArray CoapOption::data() const +{ + return m_data; +} + +#include "coappdu.h" + +QDebug operator<<(QDebug debug, const CoapOption &coapOption) +{ + const QMetaObject &metaObject = CoapOption::staticMetaObject; + QMetaEnum optionEnum = metaObject.enumerator(metaObject.indexOfEnumerator("Option")); + + switch (coapOption.option()) { + case CoapOption::ETag: + debug.nospace() << "CoapOption(" << optionEnum.valueToKey(coapOption.option()) << "): " << "0x" + coapOption.data().toHex() << endl; + break; + case CoapOption::UriHost: + debug.nospace() << "CoapOption(" << optionEnum.valueToKey(coapOption.option()) << "): " << coapOption.data() << endl; + break; + case CoapOption::UriPath: + debug.nospace() << "CoapOption(" << optionEnum.valueToKey(coapOption.option()) << "): " << coapOption.data() << endl; + break; + case CoapOption::UriQuery: + debug.nospace() << "CoapOption(" << optionEnum.valueToKey(coapOption.option()) << "): " << coapOption.data() << endl; + break; + case CoapOption::ContentFormat: { + const QMetaObject &pduMetaObject = CoapPdu::staticMetaObject; + QMetaEnum contentEnum = pduMetaObject.enumerator(pduMetaObject.indexOfEnumerator("ContentType")); + debug.nospace() << "CoapOption(" << optionEnum.valueToKey(coapOption.option()) << "): " << contentEnum.valueToKey(static_cast(coapOption.data().toHex().toInt(0, 16))) << endl; + break; + } + case CoapOption::Block1: { + // SZX = size exponent + CoapPduBlock block(coapOption.data()); + debug.nospace() << "CoapOption(" << optionEnum.valueToKey(coapOption.option()) << "): " << coapOption.data().toHex() << " Block #" << block.blockNumber() << ", More flag = " << block.moreFlag() << ", SZX:" << block.blockSize() << endl; + break; + } + case CoapOption::Block2: { + // SZX = size exponent + CoapPduBlock block(coapOption.data()); + debug.nospace() << "CoapOption(" << optionEnum.valueToKey(coapOption.option()) << "): " << coapOption.data().toHex() << " Block #" << block.blockNumber() << ", More flag = " << block.moreFlag() << ", SZX:" << block.blockSize() << endl; + break; + } + + default: + QString optionName = optionEnum.valueToKey(coapOption.option()); + if (optionName.isNull()) { + debug.nospace() << "CoapOption(" << "Unknown" << "): " << "value = " << coapOption.option() << " -> " << coapOption.data() << " = " << "0x" + coapOption.data().toHex() << endl; + } else { + debug.nospace() << "CoapOption(" << optionName << "): " << coapOption.data() << " = " << "0x" + coapOption.data().toHex() << endl; + } + break; + } + + return debug.space(); +} diff --git a/libguh/coap/coapoption.h b/libguh/coap/coapoption.h new file mode 100644 index 00000000..830bb0b4 --- /dev/null +++ b/libguh/coap/coapoption.h @@ -0,0 +1,73 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of QtCoap. * + * * + * QtCoap is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 3 of the License. * + * * + * QtCoap 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with QtCoap. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef COAPOPTION_H +#define COAPOPTION_H + +#include +#include +#include + +class CoapOption +{ + Q_GADGET + Q_ENUMS(Option) + +public: + // Options format: https://tools.ietf.org/html/rfc7252#section-3.1 + enum Option { + IfMatch = 1, + UriHost = 3, + ETag = 4, + IfNoneMatch = 5, + UriPort = 7, + LocationPath = 8, + UriPath = 11, + ContentFormat = 12, + MaxAge = 14, + UriQuery = 15, + Accept = 17, + LocationQuery = 20, + Block2 = 23, // (Block) + Block1 = 27, // (Block) + ProxyUri = 35, + ProxyScheme = 39, + Size1 = 60 + }; + + CoapOption(); + CoapOption(const Option &option, const QByteArray &data); + + void setOption(const Option &option); + Option option() const; + + void setData(const QByteArray &data); + QByteArray data() const; + +private: + Option m_option; + QByteArray m_data; +}; + +Q_DECLARE_METATYPE(CoapOption) + +QDebug operator<<(QDebug debug, const CoapOption &coapOption); + +#endif // COAPOPTION_H diff --git a/libguh/coap/coappdu.cpp b/libguh/coap/coappdu.cpp new file mode 100644 index 00000000..86e46ef3 --- /dev/null +++ b/libguh/coap/coappdu.cpp @@ -0,0 +1,412 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of QtCoap. * + * * + * QtCoap is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 3 of the License. * + * * + * QtCoap 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with QtCoap. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "coappdu.h" +#include "coapoption.h" + +#include +#include + +CoapPdu::CoapPdu(QObject *parent) : + QObject(parent), + m_version(1), + m_messageType(Confirmable), + m_statusCode(Empty), + m_messageId(0), + m_contentType(TextPlain), + m_payload(QByteArray()), + m_error(NoError) +{ + qsrand(QDateTime::currentMSecsSinceEpoch()); +} + +CoapPdu::CoapPdu(const QByteArray &data, QObject *parent) : + QObject(parent), + m_version(1), + m_messageType(Confirmable), + m_statusCode(Empty), + m_messageId(0), + m_contentType(TextPlain), + m_payload(QByteArray()), + m_error(NoError) +{ + qsrand(QDateTime::currentMSecsSinceEpoch()); + unpack(data); +} + +QString CoapPdu::getStatusCodeString(const CoapPdu::StatusCode &statusCode) +{ + QString statusCodeString; + const QMetaObject &metaObject = CoapPdu::staticMetaObject; + QMetaEnum statusCodeEnum = metaObject.enumerator(metaObject.indexOfEnumerator("StatusCode")); + int classNumber = (statusCode & 0xE0) >> 5; + int detailNumber = statusCode & 0x1F; + statusCodeString.append(QString::number(classNumber) + "."); + if (detailNumber < 10) + statusCodeString.append("0"); + + statusCodeString.append(QString::number(detailNumber) + " "); + statusCodeString.append(statusCodeEnum.valueToKey(statusCode)); + return statusCodeString; +} + +quint8 CoapPdu::version() const +{ + return m_version; +} + +void CoapPdu::setVersion(const quint8 &version) +{ + m_version = version; +} + +CoapPdu::MessageType CoapPdu::messageType() const +{ + return m_messageType; +} + +void CoapPdu::setMessageType(const CoapPdu::MessageType &messageType) +{ + m_messageType = messageType; +} + +CoapPdu::StatusCode CoapPdu::statusCode() const +{ + return m_statusCode; +} + +void CoapPdu::setStatusCode(const CoapPdu::StatusCode &statusCode) +{ + m_statusCode = statusCode; +} + +quint16 CoapPdu::messageId() const +{ + return m_messageId; +} + +void CoapPdu::createMessageId() +{ + setMessageId((quint16)qrand() % 65536); +} + +void CoapPdu::setMessageId(const quint16 &messageId) +{ + m_messageId = messageId; +} + +CoapPdu::ContentType CoapPdu::contentType() const +{ + return m_contentType; +} + +void CoapPdu::setContentType(const CoapPdu::ContentType &contentType) +{ + // TODO: add the contentFormat option + + m_contentType = contentType; +} + +QByteArray CoapPdu::token() const +{ + return m_token; +} + +void CoapPdu::createToken() +{ + m_token.clear(); + // make sure that the toke has a minimum size of 1 + quint8 length = (quint8)(qrand() % 7) + 1; + for (int i = 0; i < length; i++) { + m_token.append((char)qrand() % 256); + } +} + +void CoapPdu::setToken(const QByteArray &token) +{ + m_token = token; +} + +QByteArray CoapPdu::payload() const +{ + return m_payload; +} + +void CoapPdu::setPayload(const QByteArray &payload) +{ + m_payload = payload; +} + +QList CoapPdu::options() const +{ + return m_options; +} + +void CoapPdu::addOption(const CoapOption::Option &option, const QByteArray &data) +{ + // set pdu data from the option + switch (option) { + case CoapOption::ContentFormat: { + if (data.isEmpty()) { + setContentType(TextPlain); + } else { + setContentType(static_cast(data.toHex().toInt(0, 16))); + } + break; + } + case CoapOption::Block1: { + m_block = CoapPduBlock(data); + break; + } + case CoapOption::Block2: { + m_block = CoapPduBlock(data); + break; + } + default: + break; + } + + // insert option (keep the list sorted to ensure a positiv option delta) + int index = 0; + for (int i = 0; i < m_options.length(); i ++) { + index = i; + if (m_options.at(i).option() <= option) { + continue; + } else { + break; + } + } + m_options.insert(index + 1, CoapOption(option, data)); +} + +CoapPduBlock CoapPdu::block() const +{ + return m_block; +} + +bool CoapPdu::hasOption(const CoapOption::Option &option) const +{ + foreach (const CoapOption &o, m_options) { + if (o.option() == option) + return true; + } + return false; +} + +void CoapPdu::clear() +{ + m_version = 1; + m_messageType = Confirmable; + m_statusCode = Empty; + m_messageId = 0; + m_contentType = TextPlain; + m_token.clear(); + m_payload.clear(); + m_options.clear(); + m_error = NoError; +} + +bool CoapPdu::isValid() const +{ + return (m_error == NoError); +} + +QByteArray CoapPdu::pack() const +{ + QByteArray pduData; + + // header + QByteArray header; + header.resize(4); + header.fill('0'); + quint8 *rawHeader = (quint8 *)header.data(); + rawHeader[0] = m_version << 6; + rawHeader[0] |= (quint8)m_messageType << 4; + rawHeader[0] |= (quint8)m_token.size(); + rawHeader[1] = (quint8)m_statusCode; + rawHeader[2] = (quint8)(m_messageId >> 8); + rawHeader[3] = (quint8)(m_messageId & 0xff); + pduData = QByteArray::fromRawData((char *)rawHeader, 4); + + // token + pduData.append(m_token); + + // options + QByteArray optionsData; + quint8 prevOption = 0; + foreach (const CoapOption &option, m_options) { + quint8 optionByte = 0; + + // encode option delta + quint16 optionDelta = (quint8)option.option() - prevOption; + prevOption = (quint8)option.option(); + + quint8 extendedOptionDeltaByte = 0; + quint16 bigExtendedOptionDeltaByte = 0; + if (optionDelta < 13) { + optionByte = optionDelta << 4; + } else if (optionDelta < 270) { + // extended 8 bit option delta + optionByte = 13 << 4; + extendedOptionDeltaByte = optionDelta - 13; + } else { + // extended 16 bit option delta + optionByte = 14 << 4; + bigExtendedOptionDeltaByte = ((optionDelta - 269) >> 8) & 0xff; + bigExtendedOptionDeltaByte = (optionDelta - 269) & 0xff; + } + + // encode option length + int optionLength = option.data().length(); + quint8 extendedOptionLengthByte = 0; + quint16 bigExtendedOptionLengthByte = 0; + if (optionLength < 13) { + optionByte |= optionLength; + } else if (optionLength < 270) { + // extended 8 bit option length + optionByte |= 13; + extendedOptionLengthByte = optionLength - 13; + } else { + // extended 16 bit option length + optionByte |= 14; + bigExtendedOptionLengthByte = ((optionLength - 269) >> 8) & 0xff; + bigExtendedOptionLengthByte = (optionLength - 269) & 0xff; + } + + // add obligatory option byte + pduData.append((char)optionByte); + + // check extended option delta bytes + if (extendedOptionDeltaByte != 0) + pduData.append((char)extendedOptionDeltaByte); + + if (bigExtendedOptionDeltaByte != 0) + pduData.append((char)bigExtendedOptionDeltaByte); + + // check extended option length bytes + if (extendedOptionLengthByte != 0) + pduData.append((char)extendedOptionLengthByte); + + if (bigExtendedOptionLengthByte != 0) + pduData.append((char)extendedOptionLengthByte); + + // add the option data + pduData.append(option.data()); + } + pduData.append(optionsData); + + if (!m_payload.isEmpty()) { + pduData.append((char)255); + pduData.append(m_payload.data()); + } + + return pduData; +} + +void CoapPdu::unpack(const QByteArray &data) +{ + // create a CoapPDU + if (data.length() < 4) { + m_error = InvalidPduSizeError; + qWarning() << "pdu to small" << data.length(); + } + + quint8 *rawData = (quint8 *)data.data(); + setVersion((rawData[0] & 0xc0) >> 6); + setMessageType(static_cast((rawData[0] & 0x30) >> 4)); + quint8 tokenLength = (rawData[0] & 0xf); + + if (tokenLength > 8) { + m_error = InvalidTokenError; + qWarning() << "PDU token to long"; + } + + setToken(QByteArray((const char *)rawData + 4, tokenLength)); + setStatusCode(static_cast(rawData[1])); + setMessageId((qint16)data.mid(2,2).toHex().toUInt(0,16)); + + // 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); + + // 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; + } + + // check option length + quint16 optionLength = (optionByte & 0xf); + if (optionLength == 13) { + // extended 8 bit option length + optionLength = (quint8)(rawData[index + 1] - 13); + index += 1; + } 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; + } + + QByteArray optionData = QByteArray((const char *)rawData + index + 1, optionLength); + addOption(static_cast(delta), optionData); + + index += optionLength + 1; + optionByte = rawData[index]; + + if (QByteArray::number(optionByte, 16) == "ff") { + setPayload(data.right(data.length() - index - 1)); + break; + } + } +} + +QDebug operator<<(QDebug debug, const CoapPdu &coapPdu) +{ + const QMetaObject &metaObject = CoapPdu::staticMetaObject; + QMetaEnum messageTypeEnum = metaObject.enumerator(metaObject.indexOfEnumerator("MessageType")); + debug.nospace() << "CoapPdu(" << messageTypeEnum.valueToKey(coapPdu.messageType()) << ")" << endl; + debug.nospace() << " Code: " << CoapPdu::getStatusCodeString(coapPdu.statusCode()) << endl; + debug.nospace() << " Ver: " << coapPdu.version() << endl; + debug.nospace() << " Token: " << coapPdu.token().length() << " " << "0x"+ coapPdu.token().toHex() << endl; + debug.nospace() << " Message ID: " << coapPdu.messageId() << endl; + debug.nospace() << " Payload size: " << coapPdu.payload().size() << endl; + foreach (const CoapOption &option, coapPdu.options()) { + debug.nospace() << " " << option; + } + + if (!coapPdu.payload().isEmpty()) + debug.nospace() << endl << coapPdu.payload() << endl; + + return debug.space(); +} diff --git a/libguh/coap/coappdu.h b/libguh/coap/coappdu.h new file mode 100644 index 00000000..864d4fd4 --- /dev/null +++ b/libguh/coap/coappdu.h @@ -0,0 +1,175 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of QtCoap. * + * * + * QtCoap is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 3 of the License. * + * * + * QtCoap 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with QtCoap. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef COAPPDU_H +#define COAPPDU_H + +#include +#include + +#include "coapoption.h" +#include "coappdublock.h" + +// PDU = Protocol Data Unit + +/* 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |Ver| T | TKL | Code | Message ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Token (if any, TKL bytes) ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Options (if any) ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |1 1 1 1 1 1 1 1| Payload (if any) ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + +class CoapPdu : public QObject +{ + Q_OBJECT + Q_ENUMS(MessageType) + Q_ENUMS(StatusCode) + Q_ENUMS(ContentType) + +public: + + enum MessageType { + Confirmable = 0x00, + NonConfirmable = 0x01, + Acknowledgement = 0x02, + Reset = 0x03 + }; + + // Methods: https://tools.ietf.org/html/rfc7252#section-5.8 + // Respond codes: https://tools.ietf.org/html/rfc7252#section-12.1.2 + enum StatusCode { + Empty = 0x00, // Empty mesage (ping) + Get = 0x01, // Method GET + Post = 0x02, // Method POST + Put = 0x03, // Method PUT + Delete = 0x04, // Method DELETE + Created = 0x41, // 2.01 + Deleted = 0x42, // 2.02 + Valid = 0x43, // 2.03 + Changed = 0x44, // 2.04 + Content = 0x45, // 2.05 + Continue = 0x5f, // 2.31 (Block) + BadRequest = 0x80, // 4.00 + Unauthorized = 0x81, // 4.01 + BadOption = 0x82, // 4.02 + Forbidden = 0x83, // 4.03 + NotFound = 0x84, // 4.04 + MethodNotAllowed = 0x85, // 4.05 + NotAcceptable = 0x86, // 4.06 + RequestEntityIncomplete = 0x88, // 4.08 (Block) + PreconditionFailed = 0x8c, // 4.12 + RequestEntityTooLarge = 0x8d, // 4.13 (Block) + UnsupportedContentFormat = 0x8f, // 4.15 + InternalServerError = 0xa0, // 5.00 + NotImplemented = 0xa1, // 5.01 + BadGateway = 0xa2, // 5.02 + ServiceUnavailabl = 0xa3, // 5.03 + GatewayTimeout = 0xa4, // 5.04 + ProxyingNotSupported = 0xa5 // 5.05 + }; + + // https://tools.ietf.org/html/rfc7252#section-12.3 + enum ContentType { + TextPlain = 0, + ApplicationLink = 40, + ApplicationXml = 41, + ApplicationOctet = 42, + ApplicationExi = 47, + ApplicationJson = 50 + }; + + enum Error { + NoError, + InvalidTokenError, + InvalidPduSizeError, + InvalidOptionDeltaError, + InvalidOptionLengthError, + UnknownOptionError + }; + + CoapPdu(QObject *parent = 0); + CoapPdu(const QByteArray &data, QObject *parent = 0); + + static QString getStatusCodeString(const StatusCode &statusCode); + + // header fields + quint8 version() const; + void setVersion(const quint8 &version); + + MessageType messageType() const; + void setMessageType(const MessageType &messageType); + + StatusCode statusCode() const; + void setStatusCode(const StatusCode &statusCode); + + quint16 messageId() const; + void createMessageId(); + void setMessageId(const quint16 &messageId); + + ContentType contentType() const; + void setContentType(const ContentType &contentType); + + QByteArray token() const; + void createToken(); + void setToken(const QByteArray &token); + + // payload + QByteArray payload() const; + void setPayload(const QByteArray &payload); + + QList options() const; + void addOption(const CoapOption::Option &option, const QByteArray &data); + + CoapPduBlock block() const; + + bool hasOption(const CoapOption::Option &option) const; + + void clear(); + bool isValid() const; + bool isNull() const; + + QByteArray pack() const; + +private: + quint8 m_version; + MessageType m_messageType; + StatusCode m_statusCode; + quint16 m_messageId; + ContentType m_contentType; + QByteArray m_token; + QByteArray m_payload; + QList m_options; + + CoapPduBlock m_block; + + Error m_error; + + void unpack(const QByteArray &data); +}; + +QDebug operator<<(QDebug debug, const CoapPdu &coapPdu); + +#endif // COAPPDU_H diff --git a/libguh/coap/coappdublock.cpp b/libguh/coap/coappdublock.cpp new file mode 100644 index 00000000..8517c6eb --- /dev/null +++ b/libguh/coap/coappdublock.cpp @@ -0,0 +1,76 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of QtCoap. * + * * + * QtCoap is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 3 of the License. * + * * + * QtCoap 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with QtCoap. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "coappdublock.h" + +CoapPduBlock::CoapPduBlock() +{ +} + +CoapPduBlock::CoapPduBlock(const QByteArray &blockData) +{ + if (blockData.size() == 1) { + quint8 block = (quint8)blockData.at(0); + m_blockNumber = (block & 0xf0) >> 4; + m_blockSize = (quint8) pow(2, (block & 0x07) + 4); + m_moreFlag = (bool)((block & 0x8) >> 3); + } else if (blockData.size() == 2) { + quint16 block = (quint16)blockData.toHex().toUInt(0, 16); + m_blockNumber = (int)(block & 0xff0) >> 4; + m_blockSize = (int) pow(2, (block & 0x07) + 4); + m_moreFlag = (bool)((block & 0x08) >> 3); + } +} + +QByteArray CoapPduBlock::createBlock(const int &blockNumber, const int &blockSize, const bool &moreFlag) +{ + QByteArray blockData; + if (blockNumber < 16) { + quint8 block = (quint8)blockSize; + block |= (quint8)moreFlag << 3; + block |= (quint8)blockNumber << 4; + blockData = QByteArray(1, (char)block); + } else { + quint16 block = (quint16)blockSize; + block |= (quint16)moreFlag << 3; + block |= (quint8)blockNumber << 4; + blockData.resize(2); + blockData.fill(0); + blockData[0] = (quint8)(block >> 8); + blockData[1] = (quint8)(block & 0xff); + } + return blockData; +} + +int CoapPduBlock::blockNumber() const +{ + return m_blockNumber; +} + +int CoapPduBlock::blockSize() const +{ + return m_blockSize; +} + +bool CoapPduBlock::moreFlag() const +{ + return m_moreFlag; +} + diff --git a/libguh/coap/coappdublock.h b/libguh/coap/coappdublock.h new file mode 100644 index 00000000..bbc3f289 --- /dev/null +++ b/libguh/coap/coappdublock.h @@ -0,0 +1,45 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of QtCoap. * + * * + * QtCoap is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 3 of the License. * + * * + * QtCoap 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with QtCoap. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef COAPPDUBLOCK_H +#define COAPPDUBLOCK_H + +#include + +class CoapPduBlock +{ +public: + CoapPduBlock(); + CoapPduBlock(const QByteArray &blockData); + + static QByteArray createBlock(const int &blockNumber, const int &blockSize = 2, const bool &moreFlag = false); + + int blockNumber() const; + int blockSize() const; + bool moreFlag() const; + +private: + int m_blockNumber; + int m_blockSize; + bool m_moreFlag; + +}; + +#endif // COAPPDUBLOCK_H diff --git a/libguh/coap/coapreply.cpp b/libguh/coap/coapreply.cpp new file mode 100644 index 00000000..97847d14 --- /dev/null +++ b/libguh/coap/coapreply.cpp @@ -0,0 +1,236 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of QtCoap. * + * * + * QtCoap is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 3 of the License. * + * * + * QtCoap 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with QtCoap. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "coapreply.h" +#include "coappdu.h" + +#include + +CoapRequest CoapReply::request() const +{ + return m_request; +} + +QByteArray CoapReply::payload() const +{ + return m_payload; +} + +bool CoapReply::isFinished() const +{ + return m_isFinished; +} + +bool CoapReply::isRunning() const +{ + return m_timer->isActive(); +} + +CoapReply::Error CoapReply::error() const +{ + return m_error; +} + +QString CoapReply::errorString() const +{ + QString errorString; + switch (m_error) { + case NoError: + break; + case HostNotFoundError: + errorString = "The remote host name was not found (invalid hostname)."; + break; + case TimeoutError: + errorString = "The server did not respond after 4 retransmissions."; + break; + case InvalidUrlSchemeError: + errorString = "The given URL does not have a valid scheme."; + break; + case InvalidPduError: + errorString = "The package data unit (PDU) could not be parsed successfully."; + break; + default: + break; + } + return errorString; +} + +CoapPdu::ContentType CoapReply::contentType() const +{ + return m_contentType; +} + +CoapPdu::MessageType CoapReply::messageType() const +{ + return m_messageType; +} + +CoapPdu::StatusCode CoapReply::statusCode() const +{ + return m_statusCode; +} + +CoapReply::CoapReply(const CoapRequest &request, QObject *parent) : + QObject(parent), + m_request(request), + m_error(NoError), + m_isFinished(false), + m_retransmissions(1), + m_contentType(CoapPdu::TextPlain), + m_messageType(CoapPdu::Acknowledgement), + m_statusCode(CoapPdu::Empty), + m_lockedUp(false) +{ + m_timer = new QTimer(this); + m_timer->setSingleShot(false); + m_timer->setInterval(2000); + + connect(m_timer, &QTimer::timeout, this, &CoapReply::timeout); +} + +QByteArray CoapReply::requestData() const +{ + return m_requestData; +} + +int CoapReply::messageId() const +{ + return m_messageId; +} + +void CoapReply::setMessageId(const int &messageId) +{ + m_messageId = messageId; +} + +QByteArray CoapReply::messageToken() const +{ + return m_messageToken; +} + +void CoapReply::setMessageToken(const QByteArray &messageToken) +{ + m_messageToken = messageToken; +} + +void CoapReply::setFinished() +{ + m_isFinished = true; + m_timer->stop(); + emit finished(); +} + +void CoapReply::setError(const CoapReply::Error &code) +{ + m_error = code; + emit error(m_error); +} + +void CoapReply::resend() +{ + m_retransmissions++; + if (m_retransmissions > 5) { + setError(CoapReply::TimeoutError); + setFinished(); + } +} + +void CoapReply::setContentType(const CoapPdu::ContentType contentType) +{ + m_contentType = contentType; +} + +void CoapReply::setMessageType(const CoapPdu::MessageType &messageType) +{ + m_messageType = messageType; +} + +void CoapReply::setStatusCode(const CoapPdu::StatusCode &statusCode) +{ + m_statusCode = statusCode; +} + +void CoapReply::setHostAddress(const QHostAddress &address) +{ + m_hostAddress = address; +} + +QHostAddress CoapReply::hostAddress() const +{ + return m_hostAddress; +} + +void CoapReply::setPort(const int &port) +{ + m_port = port; +} + +int CoapReply::port() const +{ + return m_port; +} + +void CoapReply::setRequestPayload(const QByteArray &requestPayload) +{ + m_requestPayload = requestPayload; +} + +QByteArray CoapReply::requestPayload() const +{ + return m_requestPayload; +} + +void CoapReply::setRequestMethod(const CoapPdu::StatusCode &method) +{ + m_requestMethod = method; +} + +CoapPdu::StatusCode CoapReply::requestMethod() const +{ + return m_requestMethod; +} + +void CoapReply::appendPayloadData(const QByteArray &data) +{ + m_payload.append(data); + m_timer->start(); + m_retransmissions = 1; +} + +void CoapReply::setRequestData(const QByteArray &requestData) +{ + m_requestData = requestData; +} + +QDebug operator<<(QDebug debug, CoapReply *reply) +{ + const QMetaObject &metaObject = CoapPdu::staticMetaObject; + QMetaEnum messageTypeEnum = metaObject.enumerator(metaObject.indexOfEnumerator("MessageType")); + QMetaEnum contentTypeEnum = metaObject.enumerator(metaObject.indexOfEnumerator("ContentType")); + debug.nospace() << "CoapReply(" << messageTypeEnum.valueToKey(reply->messageType()) << ")" << endl; + debug.nospace() << " Status code: " << CoapPdu::getStatusCodeString(reply->statusCode()) << endl; + debug.nospace() << " Content type: " << contentTypeEnum.valueToKey(reply->contentType()) << endl; + debug.nospace() << " Payload size: " << reply->payload().size() << endl; + + if (!reply->payload().isEmpty()) + debug.nospace() << endl << reply->payload() << endl; + + return debug.space(); +} diff --git a/libguh/coap/coapreply.h b/libguh/coap/coapreply.h new file mode 100644 index 00000000..7b21161d --- /dev/null +++ b/libguh/coap/coapreply.h @@ -0,0 +1,126 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of QtCoap. * + * * + * QtCoap is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 3 of the License. * + * * + * QtCoap 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with QtCoap. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef COAPREPLY_H +#define COAPREPLY_H + +#include +#include + +#include "coappdu.h" +#include "coapoption.h" +#include "coaprequest.h" + +class CoapReply : public QObject +{ + friend class Coap; + + Q_OBJECT + Q_ENUMS(Error) + +public: + enum Error { + NoError, + HostNotFoundError, + TimeoutError, + InvalidUrlSchemeError, + InvalidPduError + }; + + CoapRequest request() const; + QByteArray payload() const; + + bool isFinished() const; + bool isRunning() const; + + Error error() const; + QString errorString() const; + + CoapPdu::ContentType contentType() const; + CoapPdu::MessageType messageType() const; + CoapPdu::StatusCode statusCode() const; + +private: + CoapReply(const CoapRequest &request, QObject *parent = 0); + + void appendPayloadData(const QByteArray &data); + + void setFinished(); + void setError(const Error &error); + + void resend(); + + void setContentType(const CoapPdu::ContentType contentType = CoapPdu::TextPlain); + void setMessageType(const CoapPdu::MessageType &messageType); + void setStatusCode(const CoapPdu::StatusCode &statusCode); + + QTimer *m_timer; + CoapRequest m_request; + QByteArray m_payload; + + Error m_error; + + bool m_isFinished; + int m_retransmissions; + + CoapPdu::ContentType m_contentType; + CoapPdu::MessageType m_messageType; + CoapPdu::StatusCode m_statusCode; + + // data for the request + void setHostAddress(const QHostAddress &address); + QHostAddress hostAddress() const; + + void setPort(const int &port); + int port() const; + + void setRequestPayload(const QByteArray &requestPayload); + QByteArray requestPayload() const; + + void setRequestMethod(const CoapPdu::StatusCode &method); + CoapPdu::StatusCode requestMethod() const; + + void setRequestData(const QByteArray &requestData); + QByteArray requestData() const; + + int messageId() const; + void setMessageId(const int &messageId); + + QByteArray messageToken() const; + void setMessageToken(const QByteArray &messageToken); + + QHostAddress m_hostAddress; + int m_port; + CoapPdu::StatusCode m_requestMethod; + QByteArray m_requestPayload; + QByteArray m_requestData; + bool m_lockedUp; + int m_messageId; + QByteArray m_messageToken; + +signals: + void timeout(); + void finished(); + void error(const Error &code); +}; + +QDebug operator<<(QDebug debug, CoapReply *reply); + +#endif // COAPREPLY_H diff --git a/libguh/coap/coaprequest.cpp b/libguh/coap/coaprequest.cpp new file mode 100644 index 00000000..e89434d7 --- /dev/null +++ b/libguh/coap/coaprequest.cpp @@ -0,0 +1,72 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of QtCoap. * + * * + * QtCoap is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 3 of the License. * + * * + * QtCoap 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with QtCoap. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "coaprequest.h" + +CoapRequest::CoapRequest(const QUrl &url) : + m_url(url), + m_contentType(CoapPdu::TextPlain), + m_messageType(CoapPdu::Confirmable), + m_statusCode(CoapPdu::Empty) +{ +} + +void CoapRequest::setUrl(const QUrl &url) +{ + m_url = url; +} + +QUrl CoapRequest::url() const +{ + return m_url; +} + +void CoapRequest::setContentType(const CoapPdu::ContentType contentType) +{ + m_contentType = contentType; +} + +CoapPdu::ContentType CoapRequest::contentType() const +{ + return m_contentType; +} + +void CoapRequest::setMessageType(const CoapPdu::MessageType &messageType) +{ + m_messageType = messageType; +} + +CoapPdu::MessageType CoapRequest::messageType() const +{ + return m_messageType; +} + +void CoapRequest::setStatusCode(const CoapPdu::StatusCode &statusCode) +{ + m_statusCode = statusCode; +} + +CoapPdu::StatusCode CoapRequest::statusCode() +{ + return m_statusCode; +} + + + diff --git a/libguh/coap/coaprequest.h b/libguh/coap/coaprequest.h new file mode 100644 index 00000000..4934a64f --- /dev/null +++ b/libguh/coap/coaprequest.h @@ -0,0 +1,58 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of QtCoap. * + * * + * QtCoap is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 3 of the License. * + * * + * QtCoap 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with QtCoap. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef COAPREQUEST_H +#define COAPREQUEST_H + +#include +#include + +#include "coappdu.h" +#include "coapoption.h" + +//class Coap; + +class CoapRequest +{ +// friend class Coap; +public: + CoapRequest(const QUrl &url = QUrl()); + + void setUrl(const QUrl &url); + QUrl url() const; + + void setContentType(const CoapPdu::ContentType contentType = CoapPdu::TextPlain); + CoapPdu::ContentType contentType() const; + + void setMessageType(const CoapPdu::MessageType &messageType); + CoapPdu::MessageType messageType() const; + +private: + QUrl m_url; + CoapPdu::ContentType m_contentType; + CoapPdu::MessageType m_messageType; + CoapPdu::StatusCode m_statusCode; + + void setStatusCode(const CoapPdu::StatusCode &statusCode); + CoapPdu::StatusCode statusCode(); + +}; + +#endif // COAPREQUEST_H diff --git a/libguh/coap/corelink.cpp b/libguh/coap/corelink.cpp new file mode 100644 index 00000000..a4225963 --- /dev/null +++ b/libguh/coap/corelink.cpp @@ -0,0 +1,124 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of QtCoap. * + * * + * QtCoap is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 3 of the License. * + * * + * QtCoap 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with QtCoap. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "corelink.h" + +#include + +CoreLink::CoreLink() : + m_contentType(CoapPdu::TextPlain), + m_maximumSize(-1), + m_observable(false) +{ +} + +QString CoreLink::path() const +{ + return m_path; +} + +void CoreLink::setPath(const QString &path) +{ + m_path = path; +} + +QString CoreLink::title() const +{ + return m_title; +} + +void CoreLink::setTitle(const QString &title) +{ + m_title = title; +} + +QString CoreLink::resourceType() const +{ + return m_resourceType; +} + +void CoreLink::setResourceType(const QString &resourceType) +{ + m_resourceType = resourceType; +} + +QString CoreLink::interfaceDescription() const +{ + return m_interfaceDescription; +} + +void CoreLink::setInterfaceDescription(const QString &interfaceDescription) +{ + m_interfaceDescription = interfaceDescription; +} + +CoapPdu::ContentType CoreLink::contentType() const +{ + return m_contentType; +} + +void CoreLink::setContentType(const CoapPdu::ContentType &contentType) +{ + m_contentType = contentType; +} + +int CoreLink::maximumSize() const +{ + return m_maximumSize; +} + +void CoreLink::setMaximumSize(const int &maximumSize) +{ + m_maximumSize = maximumSize; +} + +bool CoreLink::observable() const +{ + return m_observable; +} + +void CoreLink::setObservable(const bool &observable) +{ + m_observable = observable; +} + +QDebug operator<<(QDebug debug, const CoreLink &link) +{ + const QMetaObject &metaObject = CoapPdu::staticMetaObject; + QMetaEnum contentTypeEnum = metaObject.enumerator(metaObject.indexOfEnumerator("ContentType")); + debug.nospace() << "CoapLink(" << link.path() << ")" << endl; + + if (!link.title().isEmpty()) + debug.nospace() << " Title: " << link.title() << endl; + + debug.nospace() << " Resource type: " << link.resourceType() << endl; + debug.nospace() << " Content type: " << contentTypeEnum.valueToKey(link.contentType()) << endl; + + if (link.observable()) + debug.nospace() << " Observable: " << link.observable() << endl; + + if (!link.interfaceDescription().isEmpty()) + debug.nospace() << " Interface description: " << link.interfaceDescription() << endl; + + if (link.maximumSize() >= 0) + debug.nospace() << " Maximum size: " << link.maximumSize() << endl; + + return debug.space(); +} diff --git a/libguh/coap/corelink.h b/libguh/coap/corelink.h new file mode 100644 index 00000000..18fab0b7 --- /dev/null +++ b/libguh/coap/corelink.h @@ -0,0 +1,70 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of QtCoap. * + * * + * QtCoap is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 3 of the License. * + * * + * QtCoap 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with QtCoap. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef CORELINK_H +#define CORELINK_H + +#include +#include + +#include "coappdu.h" + +class CoreLink +{ +public: + CoreLink(); + + QString path() const; + void setPath(const QString &path); + + // link params + QString title() const; + void setTitle(const QString &title); + + QString resourceType() const; + void setResourceType(const QString &resourceType); + + QString interfaceDescription() const; + void setInterfaceDescription(const QString &interfaceDescription); + + CoapPdu::ContentType contentType() const; + void setContentType(const CoapPdu::ContentType &contentType); + + int maximumSize() const; + void setMaximumSize(const int &maximumSize); + + bool observable() const; + void setObservable(const bool &observable); + +private: + QString m_path; + + QString m_title; + QString m_resourceType; + QString m_interfaceDescription; + CoapPdu::ContentType m_contentType; + int m_maximumSize; + bool m_observable; + +}; + +QDebug operator<<(QDebug debug, const CoreLink &link); + +#endif // CORELINK_H diff --git a/libguh/coap/corelinkparser.cpp b/libguh/coap/corelinkparser.cpp new file mode 100644 index 00000000..d7d4d292 --- /dev/null +++ b/libguh/coap/corelinkparser.cpp @@ -0,0 +1,63 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of QtCoap. * + * * + * QtCoap is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 3 of the License. * + * * + * QtCoap 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with QtCoap. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "corelinkparser.h" + +#include + +CoreLinkParser::CoreLinkParser(const QByteArray &data, QObject *parent) : + QObject(parent), + m_data(data) +{ + QList linkList = data.split(','); + foreach (const QByteArray &linkLine, linkList) { + qDebug() << "-------------------------------------"; + qDebug() << linkLine; + QList valueList = linkLine.split(';'); + CoreLink link; + foreach (const QByteArray &value, valueList) { + qDebug() << value; + if (value.startsWith("<")) { + link.setPath(QString(value.mid(1, value.length() - 2))); + } else if (value.startsWith("rt=")) { + link.setResourceType(QString(value.right(value.length() - 3)).remove('"')); + } else if (value.startsWith("if=")) { + link.setInterfaceDescription(QString(value.right(value.length() - 3)).remove('"')); + } else if (value.startsWith("sz=")) { + link.setMaximumSize(value.right(value.length() - 3).toInt()); + } else if (value.startsWith("ct=")) { + link.setContentType(static_cast(value.right(value.length() - 3).toUInt())); + } else if (value.startsWith("title=")) { + link.setTitle(QString(value.right(value.length() - 6)).remove('"')); + } else if (value == "obs") { + link.setObservable(true); + } + } + qDebug() << endl << link; + m_links.append(link); + } +} + +QList CoreLinkParser::links() const +{ + return m_links; +} + + diff --git a/libguh/coap/corelinkparser.h b/libguh/coap/corelinkparser.h new file mode 100644 index 00000000..160f817f --- /dev/null +++ b/libguh/coap/corelinkparser.h @@ -0,0 +1,43 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of QtCoap. * + * * + * QtCoap is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 3 of the License. * + * * + * QtCoap 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with QtCoap. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef COREPARSER_H +#define COREPARSER_H + +#include + +#include "corelink.h" + +// Constrained RESTful Environments (CoRE) Link Format : http://tools.ietf.org/html/rfc6690 + +class CoreLinkParser : public QObject +{ + Q_OBJECT +public: + explicit CoreLinkParser(const QByteArray &data, QObject *parent = 0); + + QList links() const; + +private: + QByteArray m_data; + QList m_links; +}; + +#endif // COREPARSER_H diff --git a/libguh/libguh.pro b/libguh/libguh.pro index 0acd8f18..3643c6b8 100644 --- a/libguh/libguh.pro +++ b/libguh/libguh.pro @@ -16,6 +16,8 @@ contains(DEFINES, BLUETOOTH_LE) { bluetooth/bluetoothlowenergydevice.h \ } +include(coap/coap.pri) + SOURCES += devicemanager.cpp \ loggingcategories.cpp \ guhsettings.cpp \ diff --git a/plugins/deviceplugins/deviceplugins.pro b/plugins/deviceplugins/deviceplugins.pro index e1cae99e..1b981d88 100644 --- a/plugins/deviceplugins/deviceplugins.pro +++ b/plugins/deviceplugins/deviceplugins.pro @@ -21,6 +21,7 @@ SUBDIRS += elro \ elgato \ awattar \ netatmo \ + osdomotics \ diff --git a/plugins/deviceplugins/osdomotics/devicepluginosdomotics.cpp b/plugins/deviceplugins/osdomotics/devicepluginosdomotics.cpp new file mode 100644 index 00000000..a7a72775 --- /dev/null +++ b/plugins/deviceplugins/osdomotics/devicepluginosdomotics.cpp @@ -0,0 +1,312 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of guh. * + * * + * Guh is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 2 of the License. * + * * + * Guh 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with guh. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/*! + \page osdomotics.html + \title OSDomotics + + \ingroup plugins + \ingroup network + + This plugin allows you to connect guh to a 6LoWPAN network by adding a Mercury Board from OSDomotics + as a RPL router to your devices \l{OSDomotics Tutorial}{http://osdwiki.open-entry.com/doku.php/de:tutorials:contiki:merkur_board_rpl_usb_router}. + All nodes in the 6LoWPAN network of the added RPL router will appear automatically in the system. + + \note Currently the plugin recognizes only one node. That node has to be flashed like the Node in this \l{OSDomotics tutorial}{http://osdwiki.open-entry.com/doku.php/de:tutorials:contiki:use_example_firmware}. + + \chapter Plugin properties + Following JSON file contains the definition and the description of all available \l{DeviceClass}{DeviceClasses} + and \l{Vendor}{Vendors} of this \l{DevicePlugin}. + + Each \l{DeviceClass} has a list of \l{ParamType}{paramTypes}, \l{ActionType}{actionTypes}, \l{StateType}{stateTypes} + and \l{EventType}{eventTypes}. The \l{DeviceClass::CreateMethod}{createMethods} parameter describes how the \l{Device} + will be created in the system. A device can have more than one \l{DeviceClass::CreateMethod}{CreateMethod}. + The \l{DeviceClass::SetupMethod}{setupMethod} describes the setup method of the \l{Device}. + The detailed implementation of each \l{DeviceClass} can be found in the source code. + + \note If a \l{StateType} has the parameter \tt{"writable": {...}}, an \l{ActionType} with the same uuid and \l{ParamType}{ParamTypes} + will be created automatically. + + \quotefile plugins/deviceplugins/osdomotics/devicepluginosdomotics.json +*/ + +#include "devicepluginosdomotics.h" +#include "plugin/device.h" +#include "plugininfo.h" + +DevicePluginOsdomotics::DevicePluginOsdomotics() +{ + m_coap = new Coap(this); + connect(m_coap, SIGNAL(replyFinished(CoapReply*)), this, SLOT(coapReplyFinished(CoapReply*))); +} + +DeviceManager::HardwareResources DevicePluginOsdomotics::requiredHardware() const +{ + return DeviceManager::HardwareResourceNetworkManager | DeviceManager::HardwareResourceTimer; +} + +DeviceManager::DeviceSetupStatus DevicePluginOsdomotics::setupDevice(Device *device) +{ + + if (device->deviceClassId() == rplRouterDeviceClassId) { + qCDebug(dcOsdomotics) << "setup RPL router" << device->paramValue("host").toString(); + QHostAddress address(device->paramValue("host").toString()); + + if (address.isNull()) { + qCWarning(dcOsdomotics) << "Got invalid address" << device->paramValue("host").toString(); + return DeviceManager::DeviceSetupStatusFailure; + } + + QUrl url; + url.setScheme("http"); + url.setHost(address.toString()); + + QNetworkReply *reply = networkManagerGet(QNetworkRequest(url)); + m_asyncSetup.insert(reply, device); + + return DeviceManager::DeviceSetupStatusAsync; + } else if (device->deviceClassId() == merkurNodeDeviceClassId) { + qCDebug(dcOsdomotics) << "setup Merkur node" << device->paramValue("host").toString(); + device->setParentId(DeviceId(device->paramValue("router id").toString())); + return DeviceManager::DeviceSetupStatusSuccess; + } + return DeviceManager::DeviceSetupStatusFailure; +} + +void DevicePluginOsdomotics::deviceRemoved(Device *device) +{ + Q_UNUSED(device) +} + +void DevicePluginOsdomotics::networkManagerReplyReady(QNetworkReply *reply) +{ + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // create user finished + if (m_asyncSetup.contains(reply)) { + Device *device = m_asyncSetup.take(reply); + + // check HTTP status code + if (status != 200) { + qCWarning(dcOsdomotics) << "Setup reply HTTP error:" << reply->errorString(); + emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusFailure); + reply->deleteLater(); + return; + } + + QByteArray data = reply->readAll(); + parseNodes(device, data); + + emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusSuccess); + } else if (m_asyncNodeRescans.contains(reply)) { + Device *device = m_asyncSetup.take(reply); + + // check HTTP status code + if (status != 200) { + qCWarning(dcOsdomotics) << "Setup reply HTTP error:" << reply->errorString(); + emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusFailure); + reply->deleteLater(); + return; + } + + QByteArray data = reply->readAll(); + parseNodes(device, data); + } + reply->deleteLater(); +} + +void DevicePluginOsdomotics::postSetupDevice(Device *device) +{ + updateNode(device); +} + +void DevicePluginOsdomotics::guhTimer() +{ + foreach (Device *device, myDevices()) { + if (device->deviceClassId() == merkurNodeDeviceClassId) { + updateNode(device); + } else if(device->deviceClassId() == rplRouterDeviceClassId) { + scanNodes(device); + } + } +} + +DeviceManager::DeviceError DevicePluginOsdomotics::executeAction(Device *device, const Action &action) +{ + if (device->deviceClassId() == merkurNodeDeviceClassId) { + if (action.actionTypeId() == toggleLedActionTypeId) { + QUrl url; + url.setScheme("coap"); + url.setHost(device->paramValue("host").toString()); + url.setPath("/actuators/toggle"); + + qCDebug(dcOsdomotics) << "Toggle light"; + + CoapReply *reply = m_coap->post(CoapRequest(url)); + + if (reply->isFinished()) { + if (reply->error() != CoapReply::NoError) { + qCWarning(dcOsdomotics) << "CoAP reply finished with error" << reply->errorString(); + reply->deleteLater(); + return DeviceManager::DeviceErrorHardwareNotAvailable; + } + } + + m_toggleLightRequests.insert(reply, action); + + return DeviceManager::DeviceErrorAsync; + } + return DeviceManager::DeviceErrorActionTypeNotFound; + } + return DeviceManager::DeviceErrorDeviceClassNotFound; +} + +void DevicePluginOsdomotics::scanNodes(Device *device) +{ + QHostAddress address(device->paramValue("host").toString()); + qCDebug(dcOsdomotics) << "Scan for new nodes" << address.toString(); + + QUrl url; + url.setScheme("http"); + url.setHost(address.toString()); + + QNetworkReply *reply = networkManagerGet(QNetworkRequest(url)); + m_asyncNodeRescans.insert(reply, device); +} + +void DevicePluginOsdomotics::parseNodes(Device *device, const QByteArray &data) +{ + //qCDebug(dcOsdomotics) << data; + + // TODO: get all nodes + // find better method to get nodes + + int index = data.indexOf("Routes
") + 11;
+    int delta = data.indexOf("/128",index);
+
+    QHostAddress nodeAddress(QString(data.mid(index, delta - index)));
+
+    // check if we allready have found this node
+    foreach (Device *device, myDevices()) {
+        if (device->paramValue("host").toString() == nodeAddress.toString()) {
+            return;
+        }
+    }
+
+    QUrl url;
+    url.setScheme("coap");
+    url.setHost(nodeAddress.toString());
+    url.setPath("/.well-known/core");
+
+    qCDebug(dcOsdomotics) << "discover node on" << url.toString();
+
+    CoapReply *reply = m_coap->get(CoapRequest(url));
+    if (reply->isFinished()) {
+        if (reply->error() != CoapReply::NoError) {
+            qCWarning(dcOsdomotics) << "Reply finished with error" << reply->errorString();
+        } else {
+            qCDebug(dcOsdomotics) << "Reply finished" << reply;
+        }
+
+        // Note: please don't forget to delete the reply
+        reply->deleteLater();
+        return;
+    }
+    m_discoveryRequests.insert(reply, device);
+}
+
+void DevicePluginOsdomotics::updateNode(Device *device)
+{
+    qCDebug(dcOsdomotics) << "Update node" << device->paramValue("host").toString() << "battery value";
+
+    QUrl url;
+    url.setScheme("coap");
+    url.setHost(device->paramValue("host").toString());
+    url.setPath("/sensors/battery");
+
+    CoapReply *reply = m_coap->get(CoapRequest(url));
+
+    if (reply->isFinished()) {
+        if (reply->error() != CoapReply::NoError) {
+            qCWarning(dcOsdomotics) << "CoAP reply finished with error" << reply->errorString();
+            reply->deleteLater();
+        }
+    }
+    m_updateRequests.insert(reply, device);
+}
+
+Device *DevicePluginOsdomotics::findDevice(const QHostAddress &address)
+{
+    foreach (Device *device, myDevices()) {
+        if (device->paramValue("host").toString() == address.toString()) {
+            return device;
+        }
+    }
+    return 0;
+}
+
+void DevicePluginOsdomotics::coapReplyFinished(CoapReply *reply)
+{
+    qCDebug(dcOsdomotics) << "coap reply finished" << reply;
+
+    if (m_discoveryRequests.contains(reply)) {
+        Device *device = m_discoveryRequests.take(reply);
+        if (reply->error() != CoapReply::NoError) {
+            qCWarning(dcOsdomotics) << "CoAP discover reply finished with error" << reply->errorString();
+            reply->deleteLater();
+            return;
+        }
+
+        // TODO: parse CoRE links and get the type of the node
+
+        DeviceDescriptor descriptor(merkurNodeDeviceClassId, "Merkur Node", reply->request().url().host());
+        ParamList params;
+        params.append(Param("name", "Merkur Node"));
+        params.append(Param("host",  reply->request().url().host()));
+        params.append(Param("router id", device->id()));
+        descriptor.setParams(params);
+        emit autoDevicesAppeared(merkurNodeDeviceClassId, QList() << descriptor);
+
+    } else if (m_toggleLightRequests.contains(reply)) {
+        Action action = m_toggleLightRequests.take(reply);
+        if (reply->error() != CoapReply::NoError) {
+            qCWarning(dcOsdomotics) << "CoAP toggle reply finished with error" << reply->errorString();
+            reply->deleteLater();
+            emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
+            return;
+        }
+
+        emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorNoError);
+    } else if (m_updateRequests.contains(reply)) {
+        Device *device = m_updateRequests.take(reply);
+        if (reply->error() != CoapReply::NoError) {
+            qCWarning(dcOsdomotics) << "CoAP update reply finished with error" << reply->errorString();
+            reply->deleteLater();
+            return;
+        }
+        int batteryValue = reply->payload().toInt();
+        qCDebug(dcOsdomotics) << "Node updated" << batteryValue;
+        device->setStateValue(batteryStateTypeId, batteryValue);
+    }
+
+    reply->deleteLater();
+}
+
+
diff --git a/plugins/deviceplugins/osdomotics/devicepluginosdomotics.h b/plugins/deviceplugins/osdomotics/devicepluginosdomotics.h
new file mode 100644
index 00000000..47109df9
--- /dev/null
+++ b/plugins/deviceplugins/osdomotics/devicepluginosdomotics.h
@@ -0,0 +1,70 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ *                                                                         *
+ *  Copyright (C) 2015 Simon Stuerz                 *
+ *                                                                         *
+ *  This file is part of guh.                                              *
+ *                                                                         *
+ *  Guh is free software: you can redistribute it and/or modify            *
+ *  it under the terms of the GNU General Public License as published by   *
+ *  the Free Software Foundation, version 2 of the License.                *
+ *                                                                         *
+ *  Guh 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 General Public License for more details.                           *
+ *                                                                         *
+ *  You should have received a copy of the GNU General Public License      *
+ *  along with guh. If not, see .            *
+ *                                                                         *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef DEVICEPLUGINOSDOMOTICS_H
+#define DEVICEPLUGINOSDOMOTICS_H
+
+#include "plugin/deviceplugin.h"
+
+#include 
+#include 
+
+#include "coap/coap.h"
+
+class DevicePluginOsdomotics : public DevicePlugin
+{
+    Q_OBJECT
+    Q_PLUGIN_METADATA(IID "guru.guh.DevicePlugin" FILE "devicepluginosdomotics.json")
+    Q_INTERFACES(DevicePlugin)
+
+public:
+    explicit DevicePluginOsdomotics();
+
+    DeviceManager::HardwareResources requiredHardware() const override;
+    DeviceManager::DeviceSetupStatus setupDevice(Device *device) override;
+    void deviceRemoved(Device *device) override;
+    void networkManagerReplyReady(QNetworkReply *reply) override;
+
+    void postSetupDevice(Device *device) override;
+
+    void guhTimer() override;
+    DeviceManager::DeviceError executeAction(Device *device, const Action &action) override;
+
+private:
+    Coap *m_coap;
+    QHash m_asyncSetup;
+    QHash m_asyncNodeRescans;
+
+    QHash m_discoveryRequests;
+    QHash m_updateRequests;
+    QHash m_toggleLightRequests;
+
+    void scanNodes(Device *device);
+    void parseNodes(Device *device, const QByteArray &data);
+    void updateNode(Device *device);
+
+    Device *findDevice(const QHostAddress &address);
+
+private slots:
+    void coapReplyFinished(CoapReply *reply);
+
+};
+
+#endif // DEVICEPLUGINOSDOMOTICS_H
diff --git a/plugins/deviceplugins/osdomotics/devicepluginosdomotics.json b/plugins/deviceplugins/osdomotics/devicepluginosdomotics.json
new file mode 100644
index 00000000..d8f71a02
--- /dev/null
+++ b/plugins/deviceplugins/osdomotics/devicepluginosdomotics.json
@@ -0,0 +1,74 @@
+{
+    "name": "OSDomotics",
+    "idName": "Osdomotics",
+    "id": "78927596-e266-4a23-8634-7311b2c5fe32",
+    "vendors": [
+        {
+            "name": "OSDomotics",
+            "idName": "osdomotics",
+            "id": "e2912117-3b2f-4888-950e-6d259b699102",
+            "deviceClasses": [
+                {
+                    "deviceClassId": "57d1b080-36a0-46af-a676-78c6b78d08ae",
+                    "idName": "rplRouter",
+                    "name": "RPL Router",
+                    "createMethods": ["user"],
+                    "paramTypes": [
+                        {
+                            "name": "name",
+                            "type": "QString",
+                            "inputType": "TextLine",
+                            "defaultValue": "Merkur RPL Router"
+                        },
+                        {
+                            "name": "host",
+                            "type": "QString",
+                            "inputType": "IPv6Address"
+                        }
+                    ]
+                },
+                {
+                    "deviceClassId": "4454e05e-ac0a-4b10-b9dd-56a1475895d7",
+                    "idName": "merkurNode",
+                    "name": "Merkur Node",
+                    "createMethods": ["auto"],
+                    "paramTypes": [
+                        {
+                            "name": "name",
+                            "type": "QString",
+                            "inputType": "TextLine",
+                            "defaultValue": "Merkur Node"
+                        },
+                        {
+                            "name": "host",
+                            "type": "QString",
+                            "inputType": "IPv6Address",
+                            "readOnly": true
+                        },
+                        {
+                            "name": "router id",
+                            "type": "QString",
+                            "readOnly": true
+                        }
+                    ],
+                    "stateTypes": [
+                        {
+                            "id": "06cf4e66-f102-4a8e-ae76-fac250a07753",
+                            "idName": "battery",
+                            "name": "battery voltage",
+                            "type": "double",
+                            "defaultValue": 0
+                        }
+                    ],
+                    "actionTypes": [
+                        {
+                            "id": "a91db0e7-9d0f-4071-9a8f-2bda45ed4c9d",
+                            "idName": "toggleLed",
+                            "name": "toggle led"
+                        }
+                    ]
+                }
+            ]
+        }
+    ]
+}
diff --git a/plugins/deviceplugins/osdomotics/osdomotics.pro b/plugins/deviceplugins/osdomotics/osdomotics.pro
new file mode 100644
index 00000000..53d561eb
--- /dev/null
+++ b/plugins/deviceplugins/osdomotics/osdomotics.pro
@@ -0,0 +1,11 @@
+include(../../plugins.pri)
+
+TARGET = $$qtLibraryTarget(guh_devicepluginosdomotics)
+
+SOURCES += \
+    devicepluginosdomotics.cpp
+
+HEADERS += \
+    devicepluginosdomotics.h
+
+