diff --git a/include/openthread/coap.h b/include/openthread/coap.h index b7fe938fb..f02cb1ba2 100644 --- a/include/openthread/coap.h +++ b/include/openthread/coap.h @@ -149,6 +149,17 @@ typedef struct otCoapOption uint16_t mLength; ///< Option Length } otCoapOption; +/** + * This structure acts as an iterator for CoAP options + * + */ +typedef struct otCoapOptionIterator +{ + const otMessage *mMessage; ///< CoAP message + otCoapOption mOption; ///< CoAP message option + uint16_t mNextOptionOffset; ///< Byte offset of next option +} otCoapOptionIterator; + /** * CoAP Content Format codes. The full list is documented at * https://www.iana.org/assignments/core-parameters/core-parameters.xhtml#content-formats @@ -566,37 +577,49 @@ uint8_t otCoapMessageGetTokenLength(const otMessage *aMessage); */ const uint8_t *otCoapMessageGetToken(const otMessage *aMessage); +/** + * This function initialises an iterator for the options in the given message. + * + * @param[inout] aIterator A pointer to the CoAP message option iterator. + * @param[in] aMessage A pointer to the CoAP message. + * + * @retval OT_ERROR_NONE Successfully initialised. + * @retval OT_ERROR_PARSE Message state is inconsistent. + * + */ +otError otCoapOptionIteratorInit(otCoapOptionIterator *aIterator, const otMessage *aMessage); + /** * This function returns a pointer to the first option. * - * @param[in] aMessage A pointer to the CoAP message. + * @param[inout] aIterator A pointer to the CoAP message option iterator. * * @returns A pointer to the first option. If no option is present NULL pointer is returned. * */ -const otCoapOption *otCoapMessageGetFirstOption(otMessage *aMessage); +const otCoapOption *otCoapOptionIteratorGetFirstOption(otCoapOptionIterator *aIterator); /** * This function returns a pointer to the next option. * - * @param[in] aMessage A pointer to the CoAP message. + * @param[inout] aIterator A pointer to the CoAP message option iterator. * * @returns A pointer to the next option. If no more options are present NULL pointer is returned. * */ -const otCoapOption *otCoapMessageGetNextOption(otMessage *aMessage); +const otCoapOption *otCoapOptionIteratorGetNextOption(otCoapOptionIterator *aIterator); /** * This function fills current option value into @p aValue. * - * @param[in] aMessage A pointer to the CoAP message. - * @param[out] aValue A pointer to a buffer to receive the option value. + * @param[inout] aIterator A pointer to the CoAP message option iterator. + * @param[out] aValue A pointer to a buffer to receive the option value. * * @retval OT_ERROR_NONE Successfully filled value. * @retval OT_ERROR_NOT_FOUND No current option. * */ -otError otCoapMessageGetOptionValue(otMessage *aMessage, void *aValue); +otError otCoapOptionIteratorGetOptionValue(otCoapOptionIterator *aIterator, void *aValue); /** * This function creates a new CoAP message. diff --git a/src/core/api/coap_api.cpp b/src/core/api/coap_api.cpp index 60ddaca99..3cd99ac0b 100644 --- a/src/core/api/coap_api.cpp +++ b/src/core/api/coap_api.cpp @@ -160,19 +160,24 @@ const uint8_t *otCoapMessageGetToken(const otMessage *aMessage) return static_cast(aMessage)->GetToken(); } -const otCoapOption *otCoapMessageGetFirstOption(otMessage *aMessage) +otError otCoapOptionIteratorInit(otCoapOptionIterator *aIterator, const otMessage *aMessage) { - return static_cast(aMessage)->GetFirstOption(); + return static_cast(aIterator)->Init(static_cast(aMessage)); } -const otCoapOption *otCoapMessageGetNextOption(otMessage *aMessage) +const otCoapOption *otCoapOptionIteratorGetFirstOption(otCoapOptionIterator *aIterator) { - return static_cast(aMessage)->GetNextOption(); + return static_cast(aIterator)->GetFirstOption(); } -otError otCoapMessageGetOptionValue(otMessage *aMessage, void *aValue) +const otCoapOption *otCoapOptionIteratorGetNextOption(otCoapOptionIterator *aIterator) { - return static_cast(aMessage)->GetOptionValue(aValue); + return static_cast(aIterator)->GetNextOption(); +} + +otError otCoapOptionIteratorGetOptionValue(otCoapOptionIterator *aIterator, void *aValue) +{ + return static_cast(aIterator)->GetOptionValue(aValue); } otError otCoapSendRequest(otInstance * aInstance, diff --git a/src/core/coap/coap.cpp b/src/core/coap/coap.cpp index 67a3cf4ab..6bf5df23c 100644 --- a/src/core/coap/coap.cpp +++ b/src/core/coap/coap.cpp @@ -535,10 +535,11 @@ exit: void CoapBase::ProcessReceivedRequest(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { - char uriPath[Resource::kMaxReceivedUriPath]; - char * curUriPath = uriPath; - Message *cachedResponse = NULL; - otError error = OT_ERROR_NOT_FOUND; + char uriPath[Resource::kMaxReceivedUriPath]; + char * curUriPath = uriPath; + Message * cachedResponse = NULL; + otError error = OT_ERROR_NOT_FOUND; + OptionIterator iterator; if (mInterceptor != NULL) { @@ -561,7 +562,8 @@ void CoapBase::ProcessReceivedRequest(Message &aMessage, const Ip6::MessageInfo break; } - for (const otCoapOption *option = aMessage.GetFirstOption(); option != NULL; option = aMessage.GetNextOption()) + SuccessOrExit(error = iterator.Init(&aMessage)); + for (const otCoapOption *option = iterator.GetFirstOption(); option != NULL; option = iterator.GetNextOption()) { switch (option->mNumber) { @@ -573,7 +575,7 @@ void CoapBase::ProcessReceivedRequest(Message &aMessage, const Ip6::MessageInfo VerifyOrExit(option->mLength < sizeof(uriPath) - static_cast(curUriPath + 1 - uriPath)); - aMessage.GetOptionValue(curUriPath); + iterator.GetOptionValue(curUriPath); curUriPath += option->mLength; break; diff --git a/src/core/coap/coap_message.cpp b/src/core/coap/coap_message.cpp index edf4584b6..c1a54ea2d 100644 --- a/src/core/coap/coap_message.cpp +++ b/src/core/coap/coap_message.cpp @@ -207,121 +207,6 @@ otError Message::AppendUriQueryOption(const char *aUriQuery) return AppendStringOption(OT_COAP_OPTION_URI_QUERY, aUriQuery); } -const otCoapOption *Message::GetFirstOption(void) -{ - const otCoapOption *option = NULL; - - GetHelpData().ClearOption(); - - VerifyOrExit(GetLength() - GetHelpData().mHeaderOffset >= GetOptionStart()); - - GetHelpData().mNextOptionOffset = GetHelpData().mHeaderOffset + GetOptionStart(); - - if (GetHelpData().mNextOptionOffset < GetLength()) - { - option = GetNextOption(); - } - -exit: - return option; -} - -const otCoapOption *Message::GetNextOption(void) -{ - otError error = OT_ERROR_NONE; - uint16_t optionDelta; - uint16_t optionLength; - uint8_t buf[kMaxOptionHeaderSize]; - uint8_t * cur = buf + 1; - otCoapOption *rval = NULL; - - VerifyOrExit(GetHelpData().mNextOptionOffset < GetLength(), error = OT_ERROR_NOT_FOUND); - - Read(GetHelpData().mNextOptionOffset, sizeof(buf), buf); - - optionDelta = buf[0] >> 4; - optionLength = buf[0] & 0xf; - GetHelpData().mNextOptionOffset += sizeof(uint8_t); - - if (optionDelta < kOption1ByteExtension) - { - // do nothing - } - else if (optionDelta == kOption1ByteExtension) - { - optionDelta = kOption1ByteExtensionOffset + cur[0]; - GetHelpData().mNextOptionOffset += sizeof(uint8_t); - cur++; - } - else if (optionDelta == kOption2ByteExtension) - { - optionDelta = kOption2ByteExtensionOffset + static_cast((cur[0] << 8) | cur[1]); - GetHelpData().mNextOptionOffset += sizeof(uint16_t); - cur += 2; - } - else - { - // RFC7252 (Section 3): - // Reserved for payload marker. - VerifyOrExit(optionLength == 0xf, error = OT_ERROR_PARSE); - - // The presence of a marker followed by a zero-length payload MUST be processed - // as a message format error. - VerifyOrExit(GetHelpData().mNextOptionOffset < GetLength(), error = OT_ERROR_PARSE); - - ExitNow(error = OT_ERROR_NOT_FOUND); - } - - if (optionLength < kOption1ByteExtension) - { - // do nothing - } - else if (optionLength == kOption1ByteExtension) - { - optionLength = kOption1ByteExtensionOffset + cur[0]; - GetHelpData().mNextOptionOffset += sizeof(uint8_t); - } - else if (optionLength == kOption2ByteExtension) - { - optionLength = kOption2ByteExtensionOffset + static_cast((cur[0] << 8) | cur[1]); - GetHelpData().mNextOptionOffset += sizeof(uint16_t); - } - else - { - ExitNow(error = OT_ERROR_PARSE); - } - - VerifyOrExit(optionLength <= GetLength() - GetHelpData().mNextOptionOffset, error = OT_ERROR_PARSE); - - rval = &GetHelpData().mOption; - rval->mNumber += optionDelta; - rval->mLength = optionLength; - - GetHelpData().mNextOptionOffset += optionLength; - -exit: - if (error == OT_ERROR_PARSE) - { - GetHelpData().mNextOptionOffset = 0; - } - - return rval; -} - -otError Message::GetOptionValue(void *aValue) const -{ - otError error = OT_ERROR_NONE; - const otCoapOption &option = GetHelpData().mOption; - - VerifyOrExit(GetHelpData().mNextOptionOffset > 0, error = OT_ERROR_NOT_FOUND); - - VerifyOrExit(Read(GetHelpData().mNextOptionOffset - option.mLength, option.mLength, aValue) == option.mLength, - error = OT_ERROR_PARSE); - -exit: - return error; -} - otError Message::SetPayloadMarker(void) { otError error = OT_ERROR_NONE; @@ -340,7 +225,8 @@ exit: otError Message::ParseHeader(void) { - otError error = OT_ERROR_NONE; + otError error = OT_ERROR_NONE; + OptionIterator iterator; assert(mBuffer.mHead.mInfo.mReserved >= sizeof(GetHelpData()) + @@ -351,12 +237,13 @@ otError Message::ParseHeader(void) GetHelpData().mHeaderOffset = GetOffset(); Read(GetHelpData().mHeaderOffset, sizeof(GetHelpData().mHeader), &GetHelpData().mHeader); - for (const otCoapOption *option = GetFirstOption(); option != NULL; option = GetNextOption()) + SuccessOrExit(error = iterator.Init(this)); + for (const otCoapOption *option = iterator.GetFirstOption(); option != NULL; option = iterator.GetNextOption()) { } - VerifyOrExit(GetHelpData().mNextOptionOffset > 0, error = OT_ERROR_PARSE); - GetHelpData().mHeaderLength = GetHelpData().mNextOptionOffset - GetHelpData().mHeaderOffset; + VerifyOrExit(iterator.mNextOptionOffset > 0, error = OT_ERROR_PARSE); + GetHelpData().mHeaderLength = iterator.mNextOptionOffset - GetHelpData().mHeaderOffset; MoveOffset(GetHelpData().mHeaderLength); exit: @@ -502,5 +389,141 @@ const char *Message::CodeToString(void) const } #endif // OPENTHREAD_CONFIG_COAP_API_ENABLE +otError OptionIterator::Init(const Message *aMessage) +{ + otError err = OT_ERROR_NONE; + + /* + * Check that: + * Length - Offset: the length of the payload + * is greater than: + * Start position of options + * + * → Check options start before the message ends, or bail ::Init with + * OT_ERROR_PARSE as the reason. + */ + VerifyOrExit(aMessage->GetLength() - aMessage->GetHelpData().mHeaderOffset >= aMessage->GetOptionStart(), + err = OT_ERROR_PARSE); + + mMessage = aMessage; + GetFirstOption(); + +exit: + return err; +} + +const otCoapOption *OptionIterator::GetFirstOption(void) +{ + const otCoapOption *option = NULL; + const Message & message = GetMessage(); + + ClearOption(); + + mNextOptionOffset = message.GetHelpData().mHeaderOffset + message.GetOptionStart(); + + if (mNextOptionOffset < message.GetLength()) + { + option = GetNextOption(); + } + + return option; +} + +const otCoapOption *OptionIterator::GetNextOption(void) +{ + otError error = OT_ERROR_NONE; + uint16_t optionDelta; + uint16_t optionLength; + uint8_t buf[Message::kMaxOptionHeaderSize]; + uint8_t * cur = buf + 1; + otCoapOption * rval = NULL; + const Message &message = GetMessage(); + + VerifyOrExit(mNextOptionOffset < message.GetLength(), error = OT_ERROR_NOT_FOUND); + + message.Read(mNextOptionOffset, sizeof(buf), buf); + + optionDelta = buf[0] >> 4; + optionLength = buf[0] & 0xf; + mNextOptionOffset += sizeof(uint8_t); + + if (optionDelta < Message::kOption1ByteExtension) + { + // do nothing + } + else if (optionDelta == Message::kOption1ByteExtension) + { + optionDelta = Message::kOption1ByteExtensionOffset + cur[0]; + mNextOptionOffset += sizeof(uint8_t); + cur++; + } + else if (optionDelta == Message::kOption2ByteExtension) + { + optionDelta = Message::kOption2ByteExtensionOffset + static_cast((cur[0] << 8) | cur[1]); + mNextOptionOffset += sizeof(uint16_t); + cur += 2; + } + else + { + // RFC7252 (Section 3): + // Reserved for payload marker. + VerifyOrExit(optionLength == 0xf, error = OT_ERROR_PARSE); + + // The presence of a marker followed by a zero-length payload MUST be processed + // as a message format error. + VerifyOrExit(mNextOptionOffset < message.GetLength(), error = OT_ERROR_PARSE); + + ExitNow(error = OT_ERROR_NOT_FOUND); + } + + if (optionLength < Message::kOption1ByteExtension) + { + // do nothing + } + else if (optionLength == Message::kOption1ByteExtension) + { + optionLength = Message::kOption1ByteExtensionOffset + cur[0]; + mNextOptionOffset += sizeof(uint8_t); + } + else if (optionLength == Message::kOption2ByteExtension) + { + optionLength = Message::kOption2ByteExtensionOffset + static_cast((cur[0] << 8) | cur[1]); + mNextOptionOffset += sizeof(uint16_t); + } + else + { + ExitNow(error = OT_ERROR_PARSE); + } + + VerifyOrExit(optionLength <= message.GetLength() - mNextOptionOffset, error = OT_ERROR_PARSE); + + rval = &mOption; + rval->mNumber += optionDelta; + rval->mLength = optionLength; + + mNextOptionOffset += optionLength; + +exit: + if (error == OT_ERROR_PARSE) + { + mNextOptionOffset = 0; + } + + return rval; +} + +otError OptionIterator::GetOptionValue(void *aValue) const +{ + otError error = OT_ERROR_NONE; + + VerifyOrExit(mNextOptionOffset > 0, error = OT_ERROR_NOT_FOUND); + + VerifyOrExit(GetMessage().Read(mNextOptionOffset - mOption.mLength, mOption.mLength, aValue) == mOption.mLength, + error = OT_ERROR_PARSE); + +exit: + return error; +} + } // namespace Coap } // namespace ot diff --git a/src/core/coap/coap_message.hpp b/src/core/coap/coap_message.hpp index de13f5062..7446b2fc3 100644 --- a/src/core/coap/coap_message.hpp +++ b/src/core/coap/coap_message.hpp @@ -67,12 +67,16 @@ using ot::Encoding::BigEndian::HostSwap16; * */ +class OptionIterator; + /** * This class implements CoAP message generation and parsing. * */ class Message : public ot::Message { + friend class OptionIterator; + public: enum { @@ -379,27 +383,6 @@ public: */ otError AppendUriQueryOption(const char *aUriQuery); - /** - * This method returns a pointer to the first option. - * - */ - const otCoapOption *GetFirstOption(void); - - /** - * This method returns a pointer to the next option. - * - */ - const otCoapOption *GetNextOption(void); - - /** - * This function fills current option value into @p aValue. - * - * @retval OT_ERROR_NONE Successfully filled value. - * @retval OT_ERROR_NOT_FOUND No more options, aIterator->mNextOptionOffset is set to offset of payload. - * - */ - otError GetOptionValue(void *aValue) const; - /** * This method adds Payload Marker indicating beginning of the payload to the CoAP header. * @@ -586,14 +569,11 @@ private: struct HelpData { void Clear(void) { memset(this, 0, sizeof(*this)); } - void ClearOption(void) { memset(&mOption, 0, sizeof(mOption)); } - Header mHeader; - otCoapOption mOption; - uint16_t mNextOptionOffset; ///< The byte offset for the next CoAP Option - uint16_t mOptionLast; - uint16_t mHeaderOffset; ///< The byte offset for the CoAP Header - uint16_t mHeaderLength; + Header mHeader; + uint16_t mOptionLast; + uint16_t mHeaderOffset; ///< The byte offset for the CoAP Header + uint16_t mHeaderLength; }; const HelpData &GetHelpData(void) const @@ -607,6 +587,46 @@ private: HelpData &GetHelpData(void) { return const_cast(static_cast(this)->GetHelpData()); } }; +class OptionIterator : public ::otCoapOptionIterator +{ +public: + /** + * Initialise the state of the iterator to iterate over the given message. + * + * @retval OT_ERROR_NONE Successfully initialised + * @retval OT_ERROR_PARSE Message state is inconsistent + * + */ + otError Init(const Message *aMessage); + + /** + * This method returns a pointer to the first option. + * + * @returns A pointer to the first option. If no option is present NULL pointer is returned. + */ + const otCoapOption *GetFirstOption(void); + + /** + * This method returns a pointer to the next option. + * + * @returns A pointer to the next option. If no more options are present NULL pointer is returned. + */ + const otCoapOption *GetNextOption(void); + + /** + * This function fills current option value into @p aValue. + * + * @retval OT_ERROR_NONE Successfully filled value. + * @retval OT_ERROR_NOT_FOUND No more options, mNextOptionOffset is set to offset of payload. + * + */ + otError GetOptionValue(void *aValue) const; + +private: + void ClearOption(void) { memset(&mOption, 0, sizeof(mOption)); } + const Message &GetMessage(void) const { return *static_cast(mMessage); } +}; + /** * @} *