| /* |
| * 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 message generation and parsing. |
| */ |
| |
| #include "coap_message.hpp" |
| |
| #include "coap/coap.hpp" |
| #include "common/array.hpp" |
| #include "common/code_utils.hpp" |
| #include "common/debug.hpp" |
| #include "common/encoding.hpp" |
| #include "common/instance.hpp" |
| #include "common/random.hpp" |
| #include "common/string.hpp" |
| |
| namespace ot { |
| namespace Coap { |
| |
| void Message::Init(void) |
| { |
| GetHelpData().Clear(); |
| SetVersion(kVersion1); |
| SetOffset(0); |
| GetHelpData().mHeaderLength = kMinHeaderLength; |
| |
| IgnoreError(SetLength(GetHelpData().mHeaderLength)); |
| #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE |
| SetBlockWiseBlockNumber(0); |
| SetMoreBlocksFlag(false); |
| SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_16); |
| #endif |
| } |
| |
| void Message::Init(Type aType, Code aCode) |
| { |
| Init(); |
| SetType(aType); |
| SetCode(aCode); |
| } |
| |
| Error Message::Init(Type aType, Code aCode, const char *aUriPath) |
| { |
| Error error; |
| |
| Init(aType, aCode); |
| SuccessOrExit(error = GenerateRandomToken(kDefaultTokenLength)); |
| SuccessOrExit(error = AppendUriPathOptions(aUriPath)); |
| |
| exit: |
| return error; |
| } |
| |
| Error Message::InitAsPost(const Ip6::Address &aDestination, const char *aUriPath) |
| { |
| return Init(aDestination.IsMulticast() ? kTypeNonConfirmable : kTypeConfirmable, kCodePost, aUriPath); |
| } |
| |
| bool Message::IsConfirmablePostRequest(void) const |
| { |
| return IsConfirmable() && IsPostRequest(); |
| } |
| |
| bool Message::IsNonConfirmablePostRequest(void) const |
| { |
| return IsNonConfirmable() && IsPostRequest(); |
| } |
| |
| void Message::Finish(void) |
| { |
| // If the payload marker is set but the message contains no |
| // payload, we remove the payload marker from the message. Note |
| // that the presence of a marker followed by a zero-length payload |
| // will be processed as a message format error on the receiver. |
| |
| if (GetHelpData().mPayloadMarkerSet && (GetHelpData().mHeaderLength == GetLength())) |
| { |
| IgnoreError(SetLength(GetLength() - 1)); |
| } |
| |
| WriteBytes(0, &GetHelpData().mHeader, GetOptionStart()); |
| } |
| |
| uint8_t Message::WriteExtendedOptionField(uint16_t aValue, uint8_t *&aBuffer) |
| { |
| /* |
| * This method encodes a CoAP Option header field (Option Delta/Length) per |
| * RFC 7252. The returned value is a 4-bit unsigned integer. Extended fields |
| * (if needed) are written into the given buffer `aBuffer` and the pointer |
| * would also be updated. |
| * |
| * If `aValue < 13 (kOption1ByteExtensionOffset)`, it is returned as is |
| * (no extension). |
| * |
| * If `13 <= aValue < 269 (kOption2ByteExtensionOffset)`, one-byte |
| * extension is used, and the value minus 13 is written in `aBuffer` as an |
| * 8-bit unsigned integer, and `13 (kOption1ByteExtension)` is returned. |
| * |
| * If `269 <= aValue`, two-byte extension is used and the value minis 269 |
| * is written as a 16-bit unsigned integer and `14 (kOption2ByteExtension)` |
| * is returned. |
| * |
| */ |
| |
| uint8_t rval; |
| |
| if (aValue < kOption1ByteExtensionOffset) |
| { |
| rval = static_cast<uint8_t>(aValue); |
| } |
| else if (aValue < kOption2ByteExtensionOffset) |
| { |
| rval = kOption1ByteExtension; |
| *aBuffer = static_cast<uint8_t>(aValue - kOption1ByteExtensionOffset); |
| aBuffer += sizeof(uint8_t); |
| } |
| else |
| { |
| rval = kOption2ByteExtension; |
| Encoding::BigEndian::WriteUint16(aValue - kOption2ByteExtensionOffset, aBuffer); |
| aBuffer += sizeof(uint16_t); |
| } |
| |
| return rval; |
| } |
| |
| Error Message::AppendOption(uint16_t aNumber, uint16_t aLength, const void *aValue) |
| { |
| Error error = kErrorNone; |
| uint16_t delta; |
| uint8_t header[kMaxOptionHeaderSize]; |
| uint16_t headerLength; |
| uint8_t *cur; |
| |
| VerifyOrExit(aNumber >= GetHelpData().mOptionLast, error = kErrorInvalidArgs); |
| delta = aNumber - GetHelpData().mOptionLast; |
| |
| cur = &header[1]; |
| |
| header[0] = static_cast<uint8_t>(WriteExtendedOptionField(delta, cur) << kOptionDeltaOffset); |
| header[0] |= static_cast<uint8_t>(WriteExtendedOptionField(aLength, cur) << kOptionLengthOffset); |
| |
| headerLength = static_cast<uint16_t>(cur - header); |
| |
| VerifyOrExit(static_cast<uint32_t>(GetLength()) + headerLength + aLength < kMaxHeaderLength, error = kErrorNoBufs); |
| |
| SuccessOrExit(error = AppendBytes(header, headerLength)); |
| SuccessOrExit(error = AppendBytes(aValue, aLength)); |
| |
| GetHelpData().mOptionLast = aNumber; |
| |
| GetHelpData().mHeaderLength = GetLength(); |
| |
| exit: |
| return error; |
| } |
| |
| Error Message::AppendUintOption(uint16_t aNumber, uint32_t aValue) |
| { |
| uint8_t buffer[sizeof(uint32_t)]; |
| const uint8_t *value = &buffer[0]; |
| uint16_t length = sizeof(uint32_t); |
| |
| Encoding::BigEndian::WriteUint32(aValue, buffer); |
| |
| while ((length > 0) && (value[0] == 0)) |
| { |
| value++; |
| length--; |
| } |
| |
| return AppendOption(aNumber, length, value); |
| } |
| |
| Error Message::AppendStringOption(uint16_t aNumber, const char *aValue) |
| { |
| return AppendOption(aNumber, static_cast<uint16_t>(strlen(aValue)), aValue); |
| } |
| |
| Error Message::AppendUriPathOptions(const char *aUriPath) |
| { |
| Error error = kErrorNone; |
| const char *cur = aUriPath; |
| const char *end; |
| |
| while ((end = StringFind(cur, '/')) != nullptr) |
| { |
| SuccessOrExit(error = AppendOption(kOptionUriPath, static_cast<uint16_t>(end - cur), cur)); |
| cur = end + 1; |
| } |
| |
| SuccessOrExit(error = AppendStringOption(kOptionUriPath, cur)); |
| |
| exit: |
| return error; |
| } |
| |
| Error Message::ReadUriPathOptions(char (&aUriPath)[kMaxReceivedUriPath + 1]) const |
| { |
| char * curUriPath = aUriPath; |
| Error error = kErrorNone; |
| Option::Iterator iterator; |
| |
| SuccessOrExit(error = iterator.Init(*this, kOptionUriPath)); |
| |
| while (!iterator.IsDone()) |
| { |
| uint16_t optionLength = iterator.GetOption()->GetLength(); |
| |
| if (curUriPath != aUriPath) |
| { |
| *curUriPath++ = '/'; |
| } |
| |
| VerifyOrExit(curUriPath + optionLength < GetArrayEnd(aUriPath), error = kErrorParse); |
| |
| IgnoreError(iterator.ReadOptionValue(curUriPath)); |
| curUriPath += optionLength; |
| |
| SuccessOrExit(error = iterator.Advance(kOptionUriPath)); |
| } |
| |
| *curUriPath = '\0'; |
| |
| exit: |
| return error; |
| } |
| |
| Error Message::AppendBlockOption(Message::BlockType aType, uint32_t aNum, bool aMore, otCoapBlockSzx aSize) |
| { |
| Error error = kErrorNone; |
| uint32_t encoded = aSize; |
| |
| VerifyOrExit(aType == kBlockType1 || aType == kBlockType2, error = kErrorInvalidArgs); |
| VerifyOrExit(aSize <= OT_COAP_OPTION_BLOCK_SZX_1024, error = kErrorInvalidArgs); |
| VerifyOrExit(aNum < kBlockNumMax, error = kErrorInvalidArgs); |
| |
| encoded |= static_cast<uint32_t>(aMore << kBlockMOffset); |
| encoded |= aNum << kBlockNumOffset; |
| |
| error = AppendUintOption((aType == kBlockType1) ? kOptionBlock1 : kOptionBlock2, encoded); |
| |
| exit: |
| return error; |
| } |
| |
| #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE |
| Error Message::ReadBlockOptionValues(uint16_t aBlockType) |
| { |
| Error error = kErrorNone; |
| uint8_t buf[kMaxOptionHeaderSize] = {0}; |
| Option::Iterator iterator; |
| |
| VerifyOrExit((aBlockType == kOptionBlock1) || (aBlockType == kOptionBlock2), error = kErrorInvalidArgs); |
| |
| SuccessOrExit(error = iterator.Init(*this, aBlockType)); |
| SuccessOrExit(error = iterator.ReadOptionValue(buf)); |
| |
| SetBlockWiseBlockNumber(0); |
| SetMoreBlocksFlag(false); |
| |
| switch (iterator.GetOption()->GetLength()) |
| { |
| case 0: |
| case 1: |
| SetBlockWiseBlockNumber(static_cast<uint32_t>((buf[0] & 0xf0) >> 4)); |
| SetMoreBlocksFlag(static_cast<bool>((buf[0] & 0x08) >> 3 == 1)); |
| SetBlockWiseBlockSize(static_cast<otCoapBlockSzx>(buf[0] & 0x07)); |
| break; |
| case 2: |
| SetBlockWiseBlockNumber(static_cast<uint32_t>((buf[0] << 4) + ((buf[1] & 0xf0) >> 4))); |
| SetMoreBlocksFlag(static_cast<bool>((buf[1] & 0x08) >> 3 == 1)); |
| SetBlockWiseBlockSize(static_cast<otCoapBlockSzx>(buf[1] & 0x07)); |
| break; |
| case 3: |
| SetBlockWiseBlockNumber(static_cast<uint32_t>((buf[0] << 12) + (buf[1] << 4) + ((buf[2] & 0xf0) >> 4))); |
| SetMoreBlocksFlag(static_cast<bool>((buf[2] & 0x08) >> 3 == 1)); |
| SetBlockWiseBlockSize(static_cast<otCoapBlockSzx>(buf[2] & 0x07)); |
| break; |
| default: |
| error = kErrorInvalidArgs; |
| break; |
| } |
| |
| exit: |
| return error; |
| } |
| #endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE |
| |
| Error Message::SetPayloadMarker(void) |
| { |
| Error error = kErrorNone; |
| uint8_t marker = kPayloadMarker; |
| |
| VerifyOrExit(GetLength() < kMaxHeaderLength, error = kErrorNoBufs); |
| SuccessOrExit(error = Append(marker)); |
| GetHelpData().mPayloadMarkerSet = true; |
| GetHelpData().mHeaderLength = GetLength(); |
| |
| // Set offset to the start of payload. |
| SetOffset(GetHelpData().mHeaderLength); |
| |
| exit: |
| return error; |
| } |
| |
| Error Message::ParseHeader(void) |
| { |
| Error error = kErrorNone; |
| Option::Iterator iterator; |
| |
| OT_ASSERT(GetReserved() >= |
| sizeof(HelpData) + static_cast<size_t>((reinterpret_cast<uint8_t *>(&GetHelpData()) - GetFirstData()))); |
| |
| GetHelpData().Clear(); |
| |
| GetHelpData().mHeaderOffset = GetOffset(); |
| IgnoreError(Read(GetHelpData().mHeaderOffset, GetHelpData().mHeader)); |
| |
| VerifyOrExit(GetTokenLength() <= kMaxTokenLength, error = kErrorParse); |
| |
| SuccessOrExit(error = iterator.Init(*this)); |
| |
| while (!iterator.IsDone()) |
| { |
| SuccessOrExit(error = iterator.Advance()); |
| } |
| |
| GetHelpData().mHeaderLength = iterator.GetPayloadMessageOffset() - GetHelpData().mHeaderOffset; |
| MoveOffset(GetHelpData().mHeaderLength); |
| |
| exit: |
| return error; |
| } |
| |
| Error Message::SetToken(const uint8_t *aToken, uint8_t aTokenLength) |
| { |
| OT_ASSERT(aTokenLength <= kMaxTokenLength); |
| |
| SetTokenLength(aTokenLength); |
| memcpy(GetToken(), aToken, aTokenLength); |
| GetHelpData().mHeaderLength += aTokenLength; |
| |
| return SetLength(GetHelpData().mHeaderLength); |
| } |
| |
| Error Message::GenerateRandomToken(uint8_t aTokenLength) |
| { |
| uint8_t token[kMaxTokenLength]; |
| |
| OT_ASSERT(aTokenLength <= sizeof(token)); |
| |
| IgnoreError(Random::Crypto::FillBuffer(token, aTokenLength)); |
| |
| return SetToken(token, aTokenLength); |
| } |
| |
| Error Message::SetTokenFromMessage(const Message &aMessage) |
| { |
| return SetToken(aMessage.GetToken(), aMessage.GetTokenLength()); |
| } |
| |
| bool Message::IsTokenEqual(const Message &aMessage) const |
| { |
| uint8_t tokenLength = GetTokenLength(); |
| |
| return ((tokenLength == aMessage.GetTokenLength()) && (memcmp(GetToken(), aMessage.GetToken(), tokenLength) == 0)); |
| } |
| |
| Error Message::SetDefaultResponseHeader(const Message &aRequest) |
| { |
| Init(kTypeAck, kCodeChanged); |
| |
| SetMessageId(aRequest.GetMessageId()); |
| |
| return SetTokenFromMessage(aRequest); |
| } |
| |
| Message *Message::Clone(uint16_t aLength) const |
| { |
| Message *message = static_cast<Message *>(ot::Message::Clone(aLength)); |
| |
| VerifyOrExit(message != nullptr); |
| |
| message->GetHelpData() = GetHelpData(); |
| |
| exit: |
| return message; |
| } |
| |
| #if OPENTHREAD_CONFIG_COAP_API_ENABLE |
| const char *Message::CodeToString(void) const |
| { |
| static constexpr Stringify::Entry kCodeTable[] = { |
| {kCodeEmpty, "Empty"}, |
| {kCodeGet, "Get"}, |
| {kCodePost, "Post"}, |
| {kCodePut, "Put"}, |
| {kCodeDelete, "Delete"}, |
| {kCodeCreated, "Created"}, |
| {kCodeDeleted, "Deleted"}, |
| {kCodeValid, "Valid"}, |
| {kCodeChanged, "Changed"}, |
| {kCodeContent, "Content"}, |
| {kCodeContinue, "Continue"}, |
| {kCodeBadRequest, "BadRequest"}, |
| {kCodeUnauthorized, "Unauthorized"}, |
| {kCodeBadOption, "BadOption"}, |
| {kCodeForbidden, "Forbidden"}, |
| {kCodeNotFound, "NotFound"}, |
| {kCodeMethodNotAllowed, "MethodNotAllowed"}, |
| {kCodeNotAcceptable, "NotAcceptable"}, |
| {kCodeRequestIncomplete, "RequestIncomplete"}, |
| {kCodePreconditionFailed, "PreconditionFailed"}, |
| {kCodeRequestTooLarge, "RequestTooLarge"}, |
| {kCodeUnsupportedFormat, "UnsupportedFormat"}, |
| {kCodeInternalError, "InternalError"}, |
| {kCodeNotImplemented, "NotImplemented"}, |
| {kCodeBadGateway, "BadGateway"}, |
| {kCodeServiceUnavailable, "ServiceUnavailable"}, |
| {kCodeGatewayTimeout, "GatewayTimeout"}, |
| {kCodeProxyNotSupported, "ProxyNotSupported"}, |
| }; |
| |
| static_assert(Stringify::IsSorted(kCodeTable), "kCodeTable is not sorted"); |
| |
| return Stringify::Lookup(GetCode(), kCodeTable, "Unknown"); |
| } |
| #endif // OPENTHREAD_CONFIG_COAP_API_ENABLE |
| |
| Message::Iterator MessageQueue::begin(void) |
| { |
| return Message::Iterator(GetHead()); |
| } |
| |
| Message::ConstIterator MessageQueue::begin(void) const |
| { |
| return Message::ConstIterator(GetHead()); |
| } |
| |
| Error Option::Iterator::Init(const Message &aMessage) |
| { |
| Error error = kErrorParse; |
| uint32_t offset = static_cast<uint32_t>(aMessage.GetHelpData().mHeaderOffset) + aMessage.GetOptionStart(); |
| |
| // Note that the case where `offset == aMessage.GetLength())` is |
| // valid and indicates an empty payload (no CoAP Option and no |
| // Payload Marker). |
| |
| VerifyOrExit(offset <= aMessage.GetLength(), MarkAsParseErrored()); |
| |
| mOption.mNumber = 0; |
| mOption.mLength = 0; |
| mMessage = &aMessage; |
| mNextOptionOffset = static_cast<uint16_t>(offset); |
| |
| error = Advance(); |
| |
| exit: |
| return error; |
| } |
| |
| Error Option::Iterator::Advance(void) |
| { |
| Error error = kErrorNone; |
| uint8_t headerByte; |
| uint16_t optionDelta; |
| uint16_t optionLength; |
| |
| VerifyOrExit(!IsDone()); |
| |
| error = Read(sizeof(uint8_t), &headerByte); |
| |
| if ((error != kErrorNone) || (headerByte == Message::kPayloadMarker)) |
| { |
| // Payload Marker indicates end of options and start of payload. |
| // Absence of a Payload Marker indicates a zero-length payload. |
| |
| MarkAsDone(); |
| |
| if (error == kErrorNone) |
| { |
| // The presence of a marker followed by a zero-length payload |
| // MUST be processed as a message format error. |
| |
| VerifyOrExit(mNextOptionOffset < GetMessage().GetLength(), error = kErrorParse); |
| } |
| |
| ExitNow(error = kErrorNone); |
| } |
| |
| optionDelta = (headerByte & Message::kOptionDeltaMask) >> Message::kOptionDeltaOffset; |
| SuccessOrExit(error = ReadExtendedOptionField(optionDelta)); |
| |
| optionLength = (headerByte & Message::kOptionLengthMask) >> Message::kOptionLengthOffset; |
| SuccessOrExit(error = ReadExtendedOptionField(optionLength)); |
| |
| VerifyOrExit(optionLength <= GetMessage().GetLength() - mNextOptionOffset, error = kErrorParse); |
| mNextOptionOffset += optionLength; |
| |
| mOption.mNumber += optionDelta; |
| mOption.mLength = optionLength; |
| |
| exit: |
| if (error != kErrorNone) |
| { |
| MarkAsParseErrored(); |
| } |
| |
| return error; |
| } |
| |
| Error Option::Iterator::ReadOptionValue(void *aValue) const |
| { |
| Error error = kErrorNone; |
| |
| VerifyOrExit(!IsDone(), error = kErrorNotFound); |
| GetMessage().ReadBytes(mNextOptionOffset - mOption.mLength, aValue, mOption.mLength); |
| |
| exit: |
| return error; |
| } |
| |
| Error Option::Iterator::ReadOptionValue(uint64_t &aUintValue) const |
| { |
| Error error = kErrorNone; |
| uint8_t buffer[sizeof(uint64_t)]; |
| |
| VerifyOrExit(!IsDone(), error = kErrorNotFound); |
| |
| VerifyOrExit(mOption.mLength <= sizeof(uint64_t), error = kErrorNoBufs); |
| IgnoreError(ReadOptionValue(buffer)); |
| |
| aUintValue = 0; |
| |
| for (uint16_t pos = 0; pos < mOption.mLength; pos++) |
| { |
| aUintValue <<= CHAR_BIT; |
| aUintValue |= buffer[pos]; |
| } |
| |
| exit: |
| return error; |
| } |
| |
| Error Option::Iterator::Read(uint16_t aLength, void *aBuffer) |
| { |
| // Reads `aLength` bytes from the message into `aBuffer` at |
| // `mNextOptionOffset` and updates the `mNextOptionOffset` on a |
| // successful read (i.e., when entire `aLength` bytes can be read). |
| |
| Error error = kErrorNone; |
| |
| SuccessOrExit(error = GetMessage().Read(mNextOptionOffset, aBuffer, aLength)); |
| mNextOptionOffset += aLength; |
| |
| exit: |
| return error; |
| } |
| |
| Error Option::Iterator::ReadExtendedOptionField(uint16_t &aValue) |
| { |
| Error error = kErrorNone; |
| |
| VerifyOrExit(aValue >= Message::kOption1ByteExtension); |
| |
| if (aValue == Message::kOption1ByteExtension) |
| { |
| uint8_t value8; |
| |
| SuccessOrExit(error = Read(sizeof(uint8_t), &value8)); |
| aValue = static_cast<uint16_t>(value8) + Message::kOption1ByteExtensionOffset; |
| } |
| else if (aValue == Message::kOption2ByteExtension) |
| { |
| uint16_t value16; |
| |
| SuccessOrExit(error = Read(sizeof(uint16_t), &value16)); |
| value16 = Encoding::BigEndian::HostSwap16(value16); |
| aValue = value16 + Message::kOption2ByteExtensionOffset; |
| } |
| else |
| { |
| error = kErrorParse; |
| } |
| |
| exit: |
| return error; |
| } |
| |
| Error Option::Iterator::InitOrAdvance(const Message *aMessage, uint16_t aNumber) |
| { |
| Error error = (aMessage != nullptr) ? Init(*aMessage) : Advance(); |
| |
| while ((error == kErrorNone) && !IsDone() && (GetOption()->GetNumber() != aNumber)) |
| { |
| error = Advance(); |
| } |
| |
| return error; |
| } |
| |
| } // namespace Coap |
| } // namespace ot |