blob: 294d2cf3aac6400cc69e273122a44f65b538eceb [file] [log] [blame]
/*
* Copyright (c) 2016, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* This file implements the CoAP header generation and parsing.
*/
#include <openthread/config.h>
#include "coap_header.hpp"
#include <openthread/platform/random.h>
#include "coap/coap.hpp"
#include "common/code_utils.hpp"
#include "common/debug.hpp"
#include "common/encoding.hpp"
namespace ot {
namespace Coap {
void Header::Init(void)
{
mHeaderLength = kMinHeaderLength;
mOptionLast = 0;
mFirstOptionOffset = 0;
mNextOptionOffset = 0;
memset(&mOption, 0, sizeof(mOption));
memset(&mHeader, 0, sizeof(mHeader));
SetVersion(kVersion1);
}
void Header::Init(Type aType, Code aCode)
{
Init();
SetType(aType);
SetCode(aCode);
}
otError Header::FromMessage(const Message &aMessage, uint16_t aMetadataSize)
{
otError error = OT_ERROR_PARSE;
uint16_t offset = aMessage.GetOffset();
uint16_t length = aMessage.GetLength() - aMessage.GetOffset();
uint8_t tokenLength;
bool firstOption = true;
uint16_t optionDelta;
uint16_t optionLength;
length -= aMetadataSize;
Init();
VerifyOrExit(length >= kTokenOffset, error = OT_ERROR_PARSE);
aMessage.Read(offset, kTokenOffset, mHeader.mBytes);
mHeaderLength = kTokenOffset;
offset += kTokenOffset;
length -= kTokenOffset;
VerifyOrExit(GetVersion() == 1, error = OT_ERROR_PARSE);
tokenLength = GetTokenLength();
VerifyOrExit(tokenLength <= kMaxTokenLength && tokenLength <= length, error = OT_ERROR_PARSE);
aMessage.Read(offset, tokenLength, mHeader.mBytes + mHeaderLength);
mHeaderLength += tokenLength;
offset += tokenLength;
length -= tokenLength;
while (length > 0)
{
aMessage.Read(offset, kMaxOptionHeaderSize, mHeader.mBytes + mHeaderLength);
if (mHeader.mBytes[mHeaderLength] == 0xff)
{
mHeaderLength += sizeof(uint8_t);
length -= sizeof(uint8_t);
// RFC7252: The presence of a marker followed by a zero-length payload MUST be processed
// as a message format error.
VerifyOrExit(length > 0, error = OT_ERROR_PARSE);
ExitNow(error = OT_ERROR_NONE);
}
if (firstOption)
{
mFirstOptionOffset = mHeaderLength;
}
optionDelta = mHeader.mBytes[mHeaderLength] >> 4;
optionLength = mHeader.mBytes[mHeaderLength] & 0xf;
mHeaderLength += sizeof(uint8_t);
offset += sizeof(uint8_t);
length -= sizeof(uint8_t);
if (optionDelta < kOption1ByteExtension)
{
// do nothing
}
else if (optionDelta == kOption1ByteExtension)
{
optionDelta = kOption1ByteExtensionOffset + mHeader.mBytes[mHeaderLength];
mHeaderLength += sizeof(uint8_t);
offset += sizeof(uint8_t);
length -= sizeof(uint8_t);
}
else if (optionDelta == kOption2ByteExtension)
{
optionDelta = kOption2ByteExtensionOffset +
static_cast<uint16_t>((mHeader.mBytes[mHeaderLength] << 8) | mHeader.mBytes[mHeaderLength + 1]);
mHeaderLength += sizeof(uint16_t);
offset += sizeof(uint16_t);
length -= sizeof(uint16_t);
}
else
{
ExitNow(error = OT_ERROR_PARSE);
}
if (optionLength < kOption1ByteExtension)
{
// do nothing
}
else if (optionLength == kOption1ByteExtension)
{
optionLength = kOption1ByteExtensionOffset + mHeader.mBytes[mHeaderLength];
mHeaderLength += sizeof(uint8_t);
offset += sizeof(uint8_t);
length -= sizeof(uint8_t);
}
else if (optionLength == kOption2ByteExtension)
{
optionLength = kOption2ByteExtensionOffset +
static_cast<uint16_t>((mHeader.mBytes[mHeaderLength] << 8) | mHeader.mBytes[mHeaderLength + 1]);
mHeaderLength += sizeof(uint16_t);
offset += sizeof(uint16_t);
length -= sizeof(uint16_t);
}
else
{
ExitNow(error = OT_ERROR_PARSE);
}
if (firstOption)
{
mOption.mNumber = optionDelta;
mOption.mLength = optionLength;
mOption.mValue = mHeader.mBytes + mHeaderLength;
mNextOptionOffset = mHeaderLength + optionLength;
firstOption = false;
}
VerifyOrExit(optionLength <= length, error = OT_ERROR_PARSE);
aMessage.Read(offset, optionLength, mHeader.mBytes + mHeaderLength);
mHeaderLength += static_cast<uint8_t>(optionLength);
offset += optionLength;
length -= optionLength;
}
if (length == 0)
{
// No payload present - return success.
error = OT_ERROR_NONE;
}
exit:
// In case any step failed, prevent access to corrupt Option
if (error != OT_ERROR_NONE)
{
mFirstOptionOffset = 0;
}
return error;
}
otError Header::AppendOption(const Option &aOption)
{
otError error = OT_ERROR_NONE;
uint8_t *buf = mHeader.mBytes + mHeaderLength;
uint8_t *cur = buf + 1;
uint16_t optionDelta = aOption.mNumber - mOptionLast;
uint16_t optionLength;
// Assure that no option is inserted out of order.
VerifyOrExit(aOption.mNumber >= mOptionLast, error = OT_ERROR_INVALID_ARGS);
// Calculate the total option size and check the buffers.
optionLength = 1 + aOption.mLength;
optionLength += optionDelta < kOption1ByteExtensionOffset ? 0 :
(optionDelta < kOption2ByteExtensionOffset ? 1 : 2);
optionLength += aOption.mLength < kOption1ByteExtensionOffset ? 0 :
(aOption.mLength < kOption2ByteExtensionOffset ? 1 : 2);
VerifyOrExit(mHeaderLength + optionLength < kMaxHeaderLength, error = OT_ERROR_NO_BUFS);
// Insert option delta.
if (optionDelta < kOption1ByteExtensionOffset)
{
*buf = (optionDelta << Option::kOptionDeltaOffset) & Option::kOptionDeltaMask;
}
else if (optionDelta < kOption2ByteExtensionOffset)
{
*buf |= kOption1ByteExtension << Option::kOptionDeltaOffset;
*cur++ = (optionDelta - kOption1ByteExtensionOffset) & 0xff;
}
else
{
*buf |= kOption2ByteExtension << Option::kOptionDeltaOffset;
optionDelta -= kOption2ByteExtensionOffset;
*cur++ = optionDelta >> 8;
*cur++ = optionDelta & 0xff;
}
// Insert option length.
if (aOption.mLength < kOption1ByteExtensionOffset)
{
*buf |= aOption.mLength;
}
else if (aOption.mLength < kOption2ByteExtensionOffset)
{
*buf |= kOption1ByteExtension;
*cur++ = (aOption.mLength - kOption1ByteExtensionOffset) & 0xff;
}
else
{
*buf |= kOption2ByteExtension;
optionLength = aOption.mLength - kOption2ByteExtensionOffset;
*cur++ = optionLength >> 8;
*cur++ = optionLength & 0xff;
}
// Insert option value.
memcpy(cur, aOption.mValue, aOption.mLength);
cur += aOption.mLength;
mHeaderLength += static_cast<uint8_t>(cur - buf);
mOptionLast = aOption.mNumber;
exit:
return error;
}
otError Header::AppendUintOption(uint16_t aNumber, uint32_t aValue)
{
Option coapOption;
aValue = Encoding::BigEndian::HostSwap32(aValue);
coapOption.mNumber = aNumber;
coapOption.mLength = 4;
coapOption.mValue = reinterpret_cast<uint8_t *>(&aValue);
// skip preceding zeros
while (coapOption.mValue[0] == 0 && coapOption.mLength > 0)
{
coapOption.mValue++;
coapOption.mLength--;
}
return AppendOption(coapOption);
}
otError Header::AppendObserveOption(uint32_t aObserve)
{
return AppendUintOption(OT_COAP_OPTION_OBSERVE, aObserve & 0xFFFFFF);
}
otError Header::AppendUriPathOptions(const char *aUriPath)
{
otError error = OT_ERROR_NONE;
const char *cur = aUriPath;
const char *end;
Header::Option coapOption;
coapOption.mNumber = OT_COAP_OPTION_URI_PATH;
while ((end = strchr(cur, '/')) != NULL)
{
coapOption.mLength = static_cast<uint16_t>(end - cur);
coapOption.mValue = reinterpret_cast<const uint8_t *>(cur);
SuccessOrExit(error = AppendOption(coapOption));
cur = end + 1;
}
coapOption.mLength = static_cast<uint16_t>(strlen(cur));
coapOption.mValue = reinterpret_cast<const uint8_t *>(cur);
SuccessOrExit(error = AppendOption(coapOption));
exit:
return error;
}
otError Header::AppendContentFormatOption(otCoapOptionContentFormat aContentFormat)
{
return AppendUintOption(OT_COAP_OPTION_CONTENT_FORMAT, static_cast<uint32_t>(aContentFormat));
}
otError Header::AppendMaxAgeOption(uint32_t aMaxAge)
{
return AppendUintOption(OT_COAP_OPTION_MAX_AGE, aMaxAge);
}
otError Header::AppendUriQueryOption(const char *aUriQuery)
{
Option coapOption;
coapOption.mNumber = OT_COAP_OPTION_URI_QUERY;
coapOption.mLength = static_cast<uint16_t>(strlen(aUriQuery));
coapOption.mValue = reinterpret_cast<const uint8_t *>(aUriQuery);
return AppendOption(coapOption);
}
const Header::Option *Header::GetFirstOption(void)
{
const Option *rval = NULL;
VerifyOrExit(mFirstOptionOffset > 0);
memset(&mOption, 0, sizeof(mOption));
mNextOptionOffset = mFirstOptionOffset;
rval = GetNextOption();
exit:
return rval;
}
const Header::Option *Header::GetNextOption(void)
{
Option *rval = NULL;
uint16_t optionDelta;
uint16_t optionLength;
VerifyOrExit(mNextOptionOffset < mHeaderLength);
optionDelta = mHeader.mBytes[mNextOptionOffset] >> 4;
optionLength = mHeader.mBytes[mNextOptionOffset] & 0xf;
mNextOptionOffset += sizeof(uint8_t);
if (optionDelta < kOption1ByteExtension)
{
// do nothing
}
else if (optionDelta == kOption1ByteExtension)
{
optionDelta = kOption1ByteExtensionOffset + mHeader.mBytes[mNextOptionOffset];
mNextOptionOffset += sizeof(uint8_t);
}
else if (optionDelta == kOption2ByteExtension)
{
optionDelta = kOption2ByteExtensionOffset +
static_cast<uint16_t>((mHeader.mBytes[mNextOptionOffset] << 8) | mHeader.mBytes[mNextOptionOffset + 1]);
mNextOptionOffset += sizeof(uint16_t);
}
else
{
ExitNow();
}
if (optionLength < kOption1ByteExtension)
{
// do nothing
}
else if (optionLength == kOption1ByteExtension)
{
optionLength = kOption1ByteExtensionOffset + mHeader.mBytes[mNextOptionOffset];
mNextOptionOffset += sizeof(uint8_t);
}
else if (optionLength == kOption2ByteExtension)
{
optionLength = kOption2ByteExtensionOffset +
static_cast<uint16_t>((mHeader.mBytes[mNextOptionOffset] << 8) | mHeader.mBytes[mNextOptionOffset + 1]);
mNextOptionOffset += sizeof(uint16_t);
}
else
{
ExitNow();
}
mOption.mNumber += optionDelta;
mOption.mLength = optionLength;
mOption.mValue = mHeader.mBytes + mNextOptionOffset;
mNextOptionOffset += optionLength;
rval = static_cast<Header::Option *>(&mOption);
exit:
return rval;
}
otError Header::SetPayloadMarker(void)
{
otError error = OT_ERROR_NONE;
VerifyOrExit(mHeaderLength < kMaxHeaderLength, error = OT_ERROR_NO_BUFS);
mHeader.mBytes[mHeaderLength++] = 0xff;
exit:
return error;
}
void Header::SetToken(uint8_t aTokenLength)
{
assert(aTokenLength <= kMaxTokenLength);
uint8_t token[kMaxTokenLength] = { 0 };
for (uint8_t i = 0; i < aTokenLength; i++)
{
token[i] = static_cast<uint8_t>(otPlatRandomGet());
}
SetToken(token, aTokenLength);
}
void Header::SetDefaultResponseHeader(const Header &aRequestHeader)
{
Init(OT_COAP_TYPE_ACKNOWLEDGMENT, OT_COAP_CODE_CHANGED);
SetMessageId(aRequestHeader.GetMessageId());
SetToken(aRequestHeader.GetToken(), aRequestHeader.GetTokenLength());
}
} // namespace Coap
} // namespace ot