/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 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 . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /*! \class CoapPdu \brief Represents a CoAP protocol data unit (PDU). \ingroup coap \inmodule libguh */ /*! \enum CoapPdu::MessageType \value Confirmable \value NonConfirmable \value Acknowledgement \value Reset */ /*! \enum CoapPdu::StatusCode The CoAP status codes. Methods: \l{https://tools.ietf.org/html/rfc7252#section-5.8} Status codes: \l{https://tools.ietf.org/html/rfc7252#section-12.1.2} \value Empty 0.00 Empty (i.e. response to ping request) \value Get The GET method \value Post The POST method \value Put The PUT method \value Delete The DELETE method \value Created 2.01 Created \value Deleted 2.02 Deleted \value Valid 2.03 Valid \value Changed 2.04 Changed \value Content 2.05 Content \value Continue 2.31 Continue (from \l{https://tools.ietf.org/html/draft-ietf-core-block-18}{Blockwise V18}) \value BadRequest 4.00 Bad Request \value Unauthorized 4.01 Unauthorized \value BadOption 4.02 Bad Option \value Forbidden 4.03 Forbidden \value NotFound 4.04 Not Found \value MethodNotAllowed 4.05 Method Not Allowed \value NotAcceptable 4.06 Not Acceptable \value RequestEntityIncomplete 4.08 Request Entity Incomplete (from \l{https://tools.ietf.org/html/draft-ietf-core-block-18}{Blockwise V18}) \value PreconditionFailed 4.12 Precondition Failed \value RequestEntityTooLarge 4.13 Request Entity Too Large (from \l{https://tools.ietf.org/html/draft-ietf-core-block-18}{Blockwise V18}) \value UnsupportedContentFormat 4.15 UnsupportedContentFormat \value InternalServerError 5.00 Internal Server Error \value NotImplemented 5.01 Not Implemented \value BadGateway 5.02 Bad Gateway \value ServiceUnavailabl 5.03 Service Unavailabl \value GatewayTimeout 5.04 Gateway Timeout \value ProxyingNotSupported 5.05 Proxying Not Supported */ /*! \enum CoapPdu::ContentType The CoAP content types. \value TextPlain \value ApplicationLink \value ApplicationXml \value ApplicationOctet \value ApplicationExi \value ApplicationJson */ /*! \enum CoapPdu::Error \value NoError \value InvalidTokenError \value InvalidPduSizeError \value InvalidOptionDeltaError \value InvalidOptionLengthError \value UnknownOptionError }; */ #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(); }