| /* |
| * 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 |