| /* |
| * 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. |
| */ |
| |
| #include "coap.hpp" |
| |
| #include "instance/instance.hpp" |
| |
| /** |
| * @file |
| * This file contains common code base for CoAP client and server. |
| */ |
| |
| namespace ot { |
| namespace Coap { |
| |
| RegisterLogModule("Coap"); |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // Msg |
| |
| Error Msg::ParseHeaderAndOptions(PayloadMarkerMode aPayloadMarkerMode) |
| { |
| // Parses and validates CoAP headers and options. Assumes |
| // `GetHeaderOffset()` points to the header start. |
| // `aPayloadMarkerMode` determines behavior regarding "payload |
| // marker". Either rejects the message if payload marker is present |
| // with no payload (used on a received message), or removes the |
| // payload marker in such a situation (used on a message to be |
| // sent). Upon completion, `GetOffset()` is updated to the start of |
| // the payload. |
| |
| Error error; |
| Option::Iterator iterator; |
| uint16_t payloadOffset; |
| bool emptyPayload; |
| |
| SuccessOrExit(error = mMessage.ParseHeaderInfo(*this)); |
| |
| SuccessOrExit(error = iterator.Init(mMessage)); |
| |
| while (!iterator.IsDone()) |
| { |
| SuccessOrExit(error = iterator.Advance()); |
| } |
| |
| payloadOffset = iterator.GetPayloadMessageOffset(); |
| emptyPayload = (payloadOffset == mMessage.GetLength()); |
| |
| if (iterator.HasPayloadMarker()) |
| { |
| switch (aPayloadMarkerMode) |
| { |
| case kRejectIfNoPayloadWithPayloadMarker: |
| VerifyOrExit(!emptyPayload, error = kErrorParse); |
| break; |
| |
| case kRemovePayloadMarkerIfNoPayload: |
| if (emptyPayload) |
| { |
| mMessage.RemoveFooter(sizeof(uint8_t)); |
| payloadOffset--; |
| } |
| break; |
| } |
| } |
| |
| mMessage.SetOffset(payloadOffset); |
| |
| exit: |
| return error; |
| } |
| |
| uint16_t Msg::GetHeaderSize(void) const |
| { |
| // Determines the size of the CoAP header including the token |
| // but excluding any appended Options. |
| |
| return sizeof(Message::Header) + GetToken().GetLength(); |
| } |
| |
| void Msg::UpdateType(Type aType) |
| { |
| mType = aType; |
| mMessage.WriteType(aType); |
| } |
| |
| void Msg::UpdateMessageId(uint16_t aMessageId) |
| { |
| mMessageId = aMessageId; |
| mMessage.WriteMessageId(aMessageId); |
| } |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // CoapBase |
| |
| CoapBase::CoapBase(Instance &aInstance, Transmitter aTransmitter) |
| : InstanceLocator(aInstance) |
| , mPendingRequests(aInstance, *this) |
| , mResponseCache(aInstance) |
| , mResourceHandler(nullptr) |
| , mTransmitter(aTransmitter) |
| , mMessageId(Random::NonCrypto::GetUint16()) |
| #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE |
| , mLastResponse(nullptr) |
| #endif |
| { |
| } |
| |
| void CoapBase::ClearAllRequestsAndResponses(void) |
| { |
| mPendingRequests.AbortAllRequests(); |
| mResponseCache.RemoveAll(); |
| } |
| |
| void CoapBase::AddResource(Resource &aResource) { IgnoreError(mResources.Add(aResource)); } |
| |
| void CoapBase::RemoveResource(Resource &aResource) |
| { |
| IgnoreError(mResources.Remove(aResource)); |
| aResource.SetNext(nullptr); |
| } |
| |
| Message *CoapBase::AllocateAndInitPriorityConfirmablePostMessage(Uri aUri) |
| { |
| return InitMessage(NewNetPriorityMessage(), kTypeConfirmable, aUri); |
| } |
| |
| Message *CoapBase::AllocateAndInitConfirmablePostMessage(Uri aUri) |
| { |
| return InitMessage(NewMessage(), kTypeConfirmable, aUri); |
| } |
| |
| Message *CoapBase::AllocateAndInitPriorityNonConfirmablePostMessage(Uri aUri) |
| { |
| return InitMessage(NewNetPriorityMessage(), kTypeNonConfirmable, aUri); |
| } |
| |
| Message *CoapBase::AllocateAndInitNonConfirmablePostMessage(Uri aUri) |
| { |
| return InitMessage(NewMessage(), kTypeNonConfirmable, aUri); |
| } |
| |
| Message *CoapBase::AllocateAndInitPostMessageTo(Uri aUri, const Ip6::Address &aDestination) |
| { |
| return InitMessage(NewMessage(), aDestination.IsMulticast() ? kTypeNonConfirmable : kTypeConfirmable, aUri); |
| } |
| |
| Message *CoapBase::AllocateAndInitPriorityPostMessageTo(Uri aUri, const Ip6::Address &aDestination) |
| { |
| return InitMessage(NewNetPriorityMessage(), aDestination.IsMulticast() ? kTypeNonConfirmable : kTypeConfirmable, |
| aUri); |
| } |
| |
| Message *CoapBase::AllocateAndInitPriorityResponseFor(const Message &aRequest) |
| { |
| return InitResponse(NewNetPriorityMessage(), aRequest); |
| } |
| |
| Message *CoapBase::AllocateAndInitResponseFor(const Message &aRequest) { return InitResponse(NewMessage(), aRequest); } |
| |
| Message *CoapBase::InitMessage(Message *aMessage, Type aType, Uri aUri) |
| { |
| Error error = kErrorNone; |
| |
| VerifyOrExit(aMessage != nullptr); |
| |
| SuccessOrExit(error = aMessage->Init(aType, kCodePost, aUri)); |
| SuccessOrExit(error = aMessage->AppendPayloadMarker()); |
| |
| exit: |
| FreeAndNullMessageOnError(aMessage, error); |
| return aMessage; |
| } |
| |
| Message *CoapBase::InitResponse(Message *aMessage, const Message &aRequest) |
| { |
| Error error = kErrorNone; |
| |
| VerifyOrExit(aMessage != nullptr); |
| |
| SuccessOrExit(error = aMessage->InitAsResponse(kTypeAck, kCodeChanged, aRequest)); |
| SuccessOrExit(error = aMessage->AppendPayloadMarker()); |
| |
| exit: |
| FreeAndNullMessageOnError(aMessage, error); |
| return aMessage; |
| } |
| |
| Error CoapBase::Transmit(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) |
| { |
| Error error; |
| |
| #if OPENTHREAD_CONFIG_OTNS_ENABLE |
| Get<Utils::Otns>().EmitCoapSend(aMessage, aMessageInfo); |
| #endif |
| |
| error = mTransmitter(*this, aMessage, aMessageInfo); |
| |
| #if OPENTHREAD_CONFIG_OTNS_ENABLE |
| if (error != kErrorNone) |
| { |
| Get<Utils::Otns>().EmitCoapSendFailure(error, aMessage, aMessageInfo); |
| } |
| #endif |
| return error; |
| } |
| |
| Error CoapBase::SendMessage(Message &aMessage, |
| const Ip6::MessageInfo &aMessageInfo, |
| const TxParameters *aTxParameters, |
| const SendCallbacks &aCallbacks) |
| { |
| Error error; |
| Request request; |
| Msg txMsg(aMessage, aMessageInfo); |
| |
| request.Clear(); |
| |
| SuccessOrExit(error = txMsg.ParseHeaderAndOptions(Msg::kRemovePayloadMarkerIfNoPayload)); |
| |
| if (aTxParameters == nullptr) |
| { |
| aTxParameters = &TxParameters::GetDefault(); |
| } |
| else |
| { |
| SuccessOrExit(error = aTxParameters->ValidateFor(txMsg)); |
| } |
| |
| #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE |
| SuccessOrExit(error = ProcessBlockwiseSend(txMsg, aCallbacks)); |
| #endif |
| |
| switch (txMsg.GetType()) |
| { |
| case kTypeAck: |
| mResponseCache.Add(txMsg, aTxParameters->CalculateExchangeLifetime()); |
| break; |
| case kTypeReset: |
| break; |
| case kTypeConfirmable: |
| case kTypeNonConfirmable: |
| txMsg.UpdateMessageId(mMessageId++); |
| break; |
| } |
| |
| switch (txMsg.GetType()) |
| { |
| case kTypeAck: |
| case kTypeReset: |
| break; |
| |
| case kTypeNonConfirmable: |
| if (!aCallbacks.HasResponseHandler()) |
| { |
| // Since a non-confirmable request is not retransmitted, |
| // we only save it when a response handler is provided. |
| break; |
| } |
| |
| OT_FALL_THROUGH; |
| |
| case kTypeConfirmable: |
| SuccessOrExit(error = mPendingRequests.Add(txMsg, *aTxParameters, aCallbacks, request)); |
| break; |
| } |
| |
| SuccessOrExit(error = Transmit(txMsg.mMessage, txMsg.mMessageInfo)); |
| |
| exit: |
| if (error != kErrorNone) |
| { |
| mPendingRequests.Remove(request); |
| } |
| |
| return error; |
| } |
| |
| Error CoapBase::SendMessage(Message &aMessage, |
| const Ip6::MessageInfo &aMessageInfo, |
| const TxParameters *aTxParameters, |
| ResponseHandler aHandler, |
| void *aContext) |
| { |
| SendCallbacks callbacks; |
| |
| callbacks.Clear(); |
| callbacks.mResponseHandler = aHandler; |
| callbacks.mContext = aContext; |
| |
| return SendMessage(aMessage, aMessageInfo, aTxParameters, callbacks); |
| } |
| |
| Error CoapBase::SendMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo, const TxParameters &aTxParameters) |
| { |
| SendCallbacks callbacks; |
| |
| callbacks.Clear(); |
| |
| return SendMessage(aMessage, aMessageInfo, &aTxParameters, callbacks); |
| } |
| |
| Error CoapBase::SendMessage(Message &aMessage, |
| const Ip6::MessageInfo &aMessageInfo, |
| const ResponseHandler aHandler, |
| void *aContext) |
| { |
| SendCallbacks callbacks; |
| |
| callbacks.Clear(); |
| callbacks.mContext = aContext; |
| callbacks.mResponseHandler = aHandler; |
| |
| return SendMessage(aMessage, aMessageInfo, /* aTxParameters */ nullptr, callbacks); |
| } |
| |
| Error CoapBase::SendMessage(OwnedPtr<Message> aMessage, |
| const Ip6::MessageInfo &aMessageInfo, |
| const ResponseHandler aHandler, |
| void *aContext) |
| { |
| Error error; |
| |
| OT_ASSERT(aMessage != nullptr); |
| |
| SuccessOrExit(error = SendMessage(*aMessage, aMessageInfo, aHandler, aContext)); |
| aMessage.Release(); |
| |
| exit: |
| return error; |
| } |
| |
| Error CoapBase::SendMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) |
| { |
| return SendMessage(aMessage, aMessageInfo, nullptr, nullptr); |
| } |
| |
| Error CoapBase::SendMessage(OwnedPtr<Message> aMessage, const Ip6::MessageInfo &aMessageInfo) |
| { |
| Error error; |
| |
| OT_ASSERT(aMessage != nullptr); |
| |
| SuccessOrExit(error = SendMessage(*aMessage, aMessageInfo)); |
| aMessage.Release(); |
| |
| exit: |
| return error; |
| } |
| |
| Error CoapBase::SendMessageWithResponseHandlerSeparateParams(Message &aMessage, |
| const Ip6::MessageInfo &aMessageInfo, |
| const TxParameters *aTxParameters, |
| ResponseHandlerSeparateParams aHandler, |
| #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE |
| BlockwiseTransmitHook aTransmitHook, |
| BlockwiseReceiveHook aReceiveHook, |
| #endif |
| void *aContext) |
| { |
| SendCallbacks callbacks; |
| |
| callbacks.Clear(); |
| callbacks.mResponseHandlerSeparateParams = aHandler; |
| callbacks.mContext = aContext; |
| #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE |
| callbacks.mBlockwiseTransmitHook = aTransmitHook; |
| callbacks.mBlockwiseReceiveHook = aReceiveHook; |
| #endif |
| |
| return SendMessage(aMessage, aMessageInfo, aTxParameters, callbacks); |
| } |
| |
| Error CoapBase::SendAckResponse(const Msg &aRxMsg, Code aCode) |
| { |
| return (aRxMsg.IsConfirmable() ? SendResponse(aCode, aRxMsg) : kErrorInvalidArgs); |
| } |
| |
| Error CoapBase::SendAckResponse(const Msg &aRxMsg) { return SendAckResponse(aRxMsg, kCodeChanged); } |
| |
| Error CoapBase::SendAckResponseIfUnicastRequest(const Msg &aRxMsg, Error aError) |
| { |
| Error error; |
| |
| VerifyOrExit(aRxMsg.IsConfirmable(), error = kErrorInvalidArgs); |
| VerifyOrExit(!aRxMsg.mMessageInfo.GetSockAddr().IsMulticast(), error = kErrorInvalidArgs); |
| |
| error = SendResponse(Message::MapErrorToCoapCode(aError), aRxMsg); |
| |
| exit: |
| return error; |
| } |
| |
| Error CoapBase::SendEmptyMessage(Type aType, const Msg &aRxMsg) |
| { |
| Error error = kErrorNone; |
| Message *message = nullptr; |
| |
| switch (aType) |
| { |
| case kTypeConfirmable: |
| // An empty confirmable message is not used in normal |
| // operation but only to elicit a Reset response. This is |
| // used as "CoAP ping" (RFC 7573 section 4.3). |
| break; |
| |
| case kTypeAck: |
| VerifyOrExit(aRxMsg.IsConfirmable(), error = kErrorInvalidArgs); |
| break; |
| |
| case kTypeReset: |
| break; |
| |
| case kTypeNonConfirmable: |
| ExitNow(error = kErrorInvalidArgs); |
| } |
| |
| VerifyOrExit((message = NewMessage()) != nullptr, error = kErrorNoBufs); |
| |
| SuccessOrExit(error = message->Init(aType, kCodeEmpty, aRxMsg.GetMessageId())); |
| SuccessOrExit(error = Transmit(*message, aRxMsg.mMessageInfo)); |
| |
| exit: |
| FreeMessageOnError(message, error); |
| return error; |
| } |
| |
| Error CoapBase::SendResponse(Message::Code aCode, const Msg &aRxMsg) |
| { |
| Error error = kErrorNone; |
| Message *message = nullptr; |
| |
| VerifyOrExit(aRxMsg.IsRequest(), error = kErrorInvalidArgs); |
| VerifyOrExit((message = NewMessage()) != nullptr, error = kErrorNoBufs); |
| |
| switch (aRxMsg.GetType()) |
| { |
| case kTypeConfirmable: |
| SuccessOrExit(error = message->Init(kTypeAck, aCode, aRxMsg.GetMessageId())); |
| break; |
| |
| case kTypeNonConfirmable: |
| SuccessOrExit(error = message->Init(kTypeNonConfirmable, aCode)); |
| break; |
| |
| default: |
| ExitNow(error = kErrorInvalidArgs); |
| } |
| |
| SuccessOrExit(error = message->WriteTokenFromMessage(aRxMsg.mMessage)); |
| |
| SuccessOrExit(error = SendMessage(*message, aRxMsg.mMessageInfo)); |
| |
| exit: |
| FreeMessageOnError(message, error); |
| return error; |
| } |
| |
| Error CoapBase::AbortTransaction(ResponseHandler aHandler, void *aContext) |
| { |
| return mPendingRequests.AbortRequestsMatching(aHandler, aContext); |
| } |
| |
| void CoapBase::GetRequestAndCachedResponsesQueueInfo(MessageQueue::Info &aQueueInfo) const |
| { |
| MessageQueue::Info info; |
| |
| mPendingRequests.GetInfo(aQueueInfo); |
| mResponseCache.GetInfo(info); |
| MessageQueue::AddQueueInfos(aQueueInfo, info); |
| } |
| |
| void CoapBase::Receive(ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) |
| { |
| Msg rxMsg(AsCoapMessage(&aMessage), aMessageInfo); |
| |
| rxMsg.mMessage.SetHeaderOffset(rxMsg.mMessage.GetOffset()); |
| |
| if (rxMsg.ParseHeaderAndOptions(Msg::kRejectIfNoPayloadWithPayloadMarker) != kErrorNone) |
| { |
| LogDebg("Failed to parse CoAP header"); |
| |
| if (!aMessageInfo.GetSockAddr().IsMulticast() && rxMsg.IsConfirmable()) |
| { |
| IgnoreError(SendEmptyMessage(kTypeReset, rxMsg)); |
| } |
| |
| ExitNow(); |
| } |
| |
| if (rxMsg.IsRequest()) |
| { |
| ProcessReceivedRequest(rxMsg); |
| } |
| else |
| { |
| ProcessReceivedResponse(rxMsg); |
| } |
| |
| #if OPENTHREAD_CONFIG_OTNS_ENABLE |
| Get<Utils::Otns>().EmitCoapReceive(rxMsg.mMessage, aMessageInfo); |
| #endif |
| |
| exit: |
| return; |
| } |
| |
| void CoapBase::ProcessReceivedResponse(Msg &aRxMsg) |
| { |
| Error error; |
| Request request; |
| #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE |
| bool shouldObserve = false; |
| #endif |
| |
| error = mPendingRequests.FindRelatedRequest(aRxMsg, request); |
| |
| if (error != kErrorNone) |
| { |
| bool didHandle = InvokeResponseFallback(aRxMsg); |
| |
| if (!didHandle && aRxMsg.RequireResetOnError()) |
| { |
| // Successfully parsed a header but no matching request was |
| // found - reject the message by sending reset. |
| |
| IgnoreError(SendEmptyMessage(kTypeReset, aRxMsg)); |
| } |
| |
| ExitNow(); |
| } |
| |
| #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE |
| // If there's an Observe option present in both request and |
| // response, and we have a response handler; then we're dealing |
| // with RFC7641 rules here. If there is no response handler, then |
| // we're wasting our time! |
| if (request.IsObserve() && request.IsRequest() && request.HasResponseHandler()) |
| { |
| Option::Iterator iterator; |
| |
| SuccessOrExit(error = iterator.Init(aRxMsg.mMessage, kOptionObserve)); |
| shouldObserve = !iterator.IsDone(); |
| } |
| #endif |
| |
| switch (aRxMsg.GetType()) |
| { |
| case kTypeReset: |
| // Silently ignore non-empty reset messages (RFC 7252, Section 4.2). |
| VerifyOrExit(aRxMsg.IsEmpty()); |
| mPendingRequests.FinalizeRequest(request, kErrorAbort); |
| break; |
| |
| case kTypeAck: |
| if (aRxMsg.IsEmpty()) |
| { |
| // Empty acknowledgment. |
| |
| #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE |
| if (request.IsObserve() && !request.IsRequest()) |
| { |
| // This is the ACK to our RFC7641 CON notification. |
| // There will be no "separate" response so pass it back |
| // as if it were a piggy-backed response so we can stop |
| // re-sending and the application can move on. |
| |
| mPendingRequests.FinalizeRequest(request, kErrorNone, &aRxMsg); |
| ExitNow(); |
| } |
| #endif |
| |
| if (request.IsConfirmable()) |
| { |
| request.MarkAsAcknowledged(); |
| } |
| |
| // Remove the message if response is not expected, otherwise await |
| // response. |
| if (!request.HasResponseHandler()) |
| { |
| mPendingRequests.Remove(request); |
| } |
| |
| ExitNow(); |
| } |
| |
| if (aRxMsg.IsResponse() && aRxMsg.mMessage.HasSameTokenAs(request.GetMessage())) |
| { |
| // Piggybacked response. |
| |
| #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE |
| if (shouldObserve) |
| { |
| // This is a RFC7641 notification. The request is *not* done! |
| request.InvokeResponseHandler(&aRxMsg, kErrorNone); |
| |
| request.MarkAsAcknowledged(); |
| ExitNow(); |
| } |
| #endif |
| |
| #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE |
| SuccessOrExit(error = ProcessBlockwiseResponse(aRxMsg, request)); |
| #else |
| mPendingRequests.FinalizeRequest(request, kErrorNone, &aRxMsg); |
| #endif |
| } |
| |
| // Silently ignore acknowledgments carrying requests (RFC 7252, p. 4.2) |
| // or with no token match (RFC 7252, p. 5.3.2) |
| |
| break; |
| |
| case kTypeConfirmable: |
| // Received a confirmable response, send an Empty Ack message. |
| IgnoreError(SendEmptyMessage(kTypeAck, aRxMsg)); |
| |
| OT_FALL_THROUGH; |
| |
| case kTypeNonConfirmable: |
| |
| #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE |
| if (shouldObserve) |
| { |
| request.InvokeResponseHandler(&aRxMsg, kErrorNone); |
| |
| // When any Observe response is seen, consider a NON observe |
| // request "acknowledged" at this point. This will keep the |
| // Observe request active indefinitely until it is |
| // canceled. |
| |
| if (!request.IsConfirmable()) |
| { |
| request.MarkAsAcknowledged(); |
| } |
| |
| ExitNow(); |
| } |
| #endif |
| |
| // If the request was to a multicast address, then this is NOT |
| // the final message, we may see more. |
| |
| if (request.HasResponseHandler() && request.GetDestinationAddress().IsMulticast()) |
| { |
| request.InvokeResponseHandler(&aRxMsg, kErrorNone); |
| } |
| else |
| { |
| mPendingRequests.FinalizeRequest(request, kErrorNone, &aRxMsg); |
| } |
| |
| break; |
| } |
| |
| exit: |
| return; |
| } |
| |
| bool CoapBase::InvokeResponseFallback(Msg &aRxMsg) const |
| { |
| bool didHandle = false; |
| |
| VerifyOrExit(mResponseFallback.IsSet()); |
| didHandle = mResponseFallback.Invoke(&aRxMsg.mMessage, &aRxMsg.mMessageInfo); |
| |
| exit: |
| return didHandle; |
| } |
| |
| void CoapBase::ProcessReceivedRequest(Msg &aRxMsg) |
| { |
| Message::UriPathStringBuffer uriPath; |
| Error error = kErrorNone; |
| |
| if (mInterceptor.IsSet()) |
| { |
| SuccessOrExit(error = mInterceptor.Invoke(aRxMsg)); |
| } |
| |
| // Check if `mResponseCache` has a matching cached response for this |
| // request and send it. Only if not found (`kErrorNotFound`), we |
| // continue to process the `aRxMsg.mMessage` further. |
| |
| error = mResponseCache.SendCachedResponse(aRxMsg, *this); |
| |
| switch (error) |
| { |
| case kErrorNotFound: |
| break; |
| case kErrorNone: |
| default: |
| ExitNow(); |
| } |
| |
| SuccessOrExit(error = aRxMsg.mMessage.ReadUriPathOptions(uriPath)); |
| |
| #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE |
| { |
| bool didHandle = false; |
| |
| SuccessOrExit(error = ProcessBlockwiseRequest(aRxMsg, uriPath, didHandle)); |
| VerifyOrExit(!didHandle); |
| } |
| #endif |
| |
| if ((mResourceHandler != nullptr) && mResourceHandler(*this, uriPath, aRxMsg)) |
| { |
| error = kErrorNone; |
| ExitNow(); |
| } |
| |
| for (const Resource &resource : mResources) |
| { |
| if (StringMatch(resource.mUriPath, uriPath)) |
| { |
| resource.HandleRequest(aRxMsg); |
| error = kErrorNone; |
| ExitNow(); |
| } |
| } |
| |
| if (mDefaultHandler.IsSet()) |
| { |
| mDefaultHandler.Invoke(&aRxMsg.mMessage, &aRxMsg.mMessageInfo); |
| error = kErrorNone; |
| ExitNow(); |
| } |
| |
| error = kErrorNotFound; |
| |
| exit: |
| LogInfoOnError(error, "process request"); |
| |
| if (error == kErrorNotFound && !aRxMsg.mMessageInfo.GetSockAddr().IsMulticast()) |
| { |
| IgnoreError(SendResponse(kCodeNotFound, aRxMsg)); |
| } |
| } |
| |
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| // `CoapBase` - BLockwise transfer methods |
| |
| #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE |
| |
| void CoapBase::AddBlockWiseResource(ResourceBlockWise &aResource) { IgnoreError(mBlockWiseResources.Add(aResource)); } |
| |
| void CoapBase::RemoveBlockWiseResource(ResourceBlockWise &aResource) |
| { |
| IgnoreError(mBlockWiseResources.Remove(aResource)); |
| aResource.SetNext(nullptr); |
| } |
| |
| Error CoapBase::ProcessBlockwiseSend(Msg &aMsg, const SendCallbacks &aCallbacks) |
| { |
| Error error = kErrorNone; |
| uint8_t type = aMsg.GetType(); |
| bool moreBlocks = false; |
| uint16_t blockSize; |
| uint8_t buf[kMaxBlockSize]; |
| BlockInfo blockInfo; |
| |
| VerifyOrExit(type != kTypeReset); |
| |
| VerifyOrExit(aCallbacks.HasBlockwiseTransmitHook()); |
| |
| SuccessOrExit(aMsg.mMessage.ReadBlockOptionValues(type == kTypeAck ? kOptionBlock2 : kOptionBlock1, blockInfo)); |
| |
| VerifyOrExit(blockInfo.mBlockNumber == 0); |
| |
| blockSize = blockInfo.GetBlockSize(); |
| VerifyOrExit(blockSize <= kMaxBlockSize, error = kErrorNoBufs); |
| |
| SuccessOrExit(error = aCallbacks.mBlockwiseTransmitHook(aCallbacks.mContext, buf, 0, &blockSize, &moreBlocks)); |
| |
| SuccessOrExit(error = aMsg.mMessage.AppendPayloadMarker()); |
| SuccessOrExit(error = aMsg.mMessage.AppendBytes(buf, blockSize)); |
| |
| switch (type) |
| { |
| case kTypeAck: |
| SuccessOrExit(error = CacheLastBlockResponse(&aMsg.mMessage)); |
| break; |
| |
| case kTypeNonConfirmable: |
| // Block-Wise messages always have to be confirmable |
| aMsg.UpdateType(kTypeConfirmable); |
| break; |
| |
| default: |
| break; |
| } |
| |
| exit: |
| return error; |
| } |
| |
| Error CoapBase::ProcessBlockwiseResponse(Msg &aRxMsg, Request &aRequest) |
| { |
| Error error = kErrorNone; |
| uint8_t blockOptionType = 0; |
| uint32_t totalTransferSize = 0; |
| |
| if (aRequest.HasBlockwiseTransmitHook() || aRequest.HasBlockwiseReceiveHook()) |
| { |
| // Search for CoAP Block-Wise Option [RFC7959] |
| Option::Iterator iterator; |
| |
| SuccessOrExit(error = iterator.Init(aRxMsg.mMessage)); |
| |
| while (!iterator.IsDone()) |
| { |
| switch (iterator.GetOption()->GetNumber()) |
| { |
| case kOptionBlock1: |
| blockOptionType += 1; |
| break; |
| |
| case kOptionBlock2: |
| blockOptionType += 2; |
| break; |
| |
| case kOptionSize2: |
| // ToDo: wait for method to read uint option values |
| totalTransferSize = 0; |
| break; |
| |
| default: |
| break; |
| } |
| |
| SuccessOrExit(error = iterator.Advance()); |
| } |
| } |
| |
| switch (blockOptionType) |
| { |
| case 0: |
| // Piggybacked response. |
| mPendingRequests.FinalizeRequest(aRequest, kErrorNone, &aRxMsg); |
| break; |
| case 1: // Block1 option |
| if (aRxMsg.GetCode() == kCodeContinue && aRequest.HasBlockwiseTransmitHook()) |
| { |
| error = SendNextBlock1Request(aRequest, aRxMsg); |
| } |
| |
| if (aRxMsg.GetCode() != kCodeContinue || !aRequest.HasBlockwiseTransmitHook() || error != kErrorNone) |
| { |
| mPendingRequests.FinalizeRequest(aRequest, error, &aRxMsg); |
| } |
| break; |
| case 2: // Block2 option |
| if (aRxMsg.GetCode() < kCodeBadRequest && aRequest.HasBlockwiseReceiveHook()) |
| { |
| error = SendNextBlock2Request(aRequest, aRxMsg, totalTransferSize, false); |
| } |
| |
| if (aRxMsg.GetCode() >= kCodeBadRequest || !aRequest.HasBlockwiseReceiveHook() || error != kErrorNone) |
| { |
| mPendingRequests.FinalizeRequest(aRequest, error, &aRxMsg); |
| } |
| break; |
| case 3: // Block1 & Block2 option |
| if (aRxMsg.GetCode() < kCodeBadRequest && aRequest.HasBlockwiseReceiveHook()) |
| { |
| error = SendNextBlock2Request(aRequest, aRxMsg, totalTransferSize, true); |
| } |
| |
| mPendingRequests.FinalizeRequest(aRequest, error, &aRxMsg); |
| break; |
| default: |
| error = kErrorAbort; |
| mPendingRequests.FinalizeRequest(aRequest, error, &aRxMsg); |
| break; |
| } |
| |
| exit: |
| return error; |
| } |
| |
| Error CoapBase::ProcessBlockwiseRequest(Msg &aRxMsg, const Message::UriPathStringBuffer &aUriPath, bool &aDidHandle) |
| { |
| Error error = kErrorNone; |
| Option::Iterator iterator; |
| uint8_t blockOptionType = 0; |
| uint32_t totalTransferSize = 0; |
| |
| SuccessOrExit(error = iterator.Init(aRxMsg.mMessage)); |
| |
| while (!iterator.IsDone()) |
| { |
| switch (iterator.GetOption()->GetNumber()) |
| { |
| case kOptionBlock1: |
| blockOptionType += 1; |
| break; |
| |
| case kOptionBlock2: |
| blockOptionType += 2; |
| break; |
| |
| case kOptionSize1: |
| // ToDo: wait for method to read uint option values |
| totalTransferSize = 0; |
| break; |
| |
| default: |
| break; |
| } |
| |
| SuccessOrExit(error = iterator.Advance()); |
| } |
| |
| for (const ResourceBlockWise &resource : mBlockWiseResources) |
| { |
| if (!StringMatch(resource.GetUriPath(), aUriPath)) |
| { |
| continue; |
| } |
| |
| if ((resource.mReceiveHook != nullptr || resource.mTransmitHook != nullptr) && blockOptionType != 0) |
| { |
| switch (blockOptionType) |
| { |
| case 1: |
| if (resource.mReceiveHook != nullptr) |
| { |
| switch (ProcessBlock1Request(aRxMsg, resource, totalTransferSize)) |
| { |
| case kErrorNone: |
| resource.HandleRequest(aRxMsg); |
| OT_FALL_THROUGH; |
| case kErrorBusy: |
| error = kErrorNone; |
| break; |
| case kErrorNoBufs: |
| IgnoreError(SendResponse(kCodeRequestTooLarge, aRxMsg)); |
| error = kErrorDrop; |
| break; |
| case kErrorNoFrameReceived: |
| IgnoreError(SendResponse(kCodeRequestIncomplete, aRxMsg)); |
| error = kErrorDrop; |
| break; |
| default: |
| IgnoreError(SendResponse(kCodeInternalError, aRxMsg)); |
| error = kErrorDrop; |
| break; |
| } |
| } |
| break; |
| case 2: |
| if (resource.mTransmitHook != nullptr) |
| { |
| if ((error = ProcessBlock2Request(aRxMsg, resource)) != kErrorNone) |
| { |
| IgnoreError(SendResponse(kCodeInternalError, aRxMsg)); |
| error = kErrorDrop; |
| } |
| } |
| break; |
| } |
| |
| aDidHandle = true; |
| ExitNow(); |
| } |
| else |
| { |
| resource.HandleRequest(aRxMsg); |
| error = kErrorNone; |
| aDidHandle = true; |
| ExitNow(); |
| } |
| } |
| |
| exit: |
| return error; |
| } |
| |
| void CoapBase::FreeLastBlockResponse(void) |
| { |
| if (mLastResponse != nullptr) |
| { |
| mLastResponse->Free(); |
| mLastResponse = nullptr; |
| } |
| } |
| |
| Error CoapBase::CacheLastBlockResponse(Message *aResponse) |
| { |
| Error error = kErrorNone; |
| |
| FreeLastBlockResponse(); |
| |
| mLastResponse = AsCoapMessagePtr(aResponse->Clone<kNoReservedHeader>()); |
| VerifyOrExit(mLastResponse != nullptr, error = kErrorNoBufs); |
| |
| exit: |
| return error; |
| } |
| |
| Error CoapBase::PrepareNextBlockRequest(uint16_t aBlockOptionNumber, |
| Request &aRequestOld, |
| Message &aRequest, |
| const BlockInfo &aBlockInfo) |
| { |
| Error error; |
| bool isOptionSet = false; |
| Option::Iterator iterator; |
| |
| SuccessOrExit( |
| error = aRequest.Init(kTypeConfirmable, static_cast<ot::Coap::Code>(aRequestOld.GetMessage().ReadCode()))); |
| |
| aRequestOld.RemoveMetadataFromMessage(); |
| |
| // Per RFC 7959, all requests in a block-wise transfer MUST use the |
| // same token. |
| IgnoreError(aRequest.WriteTokenFromMessage(aRequestOld.GetMessage())); |
| |
| // Copy options from last response to next message |
| |
| SuccessOrExit(error = iterator.Init(aRequestOld.GetMessage())); |
| |
| for (; !iterator.IsDone() && iterator.GetOption()->GetLength() != 0; error = iterator.Advance()) |
| { |
| uint16_t optionNumber = iterator.GetOption()->GetNumber(); |
| |
| SuccessOrExit(error); |
| |
| // Check if option to copy next is higher than or equal to Block1 option |
| if (optionNumber >= aBlockOptionNumber && !isOptionSet) |
| { |
| SuccessOrExit(error = aRequest.AppendBlockOption(aBlockOptionNumber, aBlockInfo)); |
| |
| isOptionSet = true; |
| |
| // If option to copy next is Block1 or Block2 option, option is not copied |
| if (optionNumber == kOptionBlock1 || optionNumber == kOptionBlock2) |
| { |
| continue; |
| } |
| } |
| |
| // Copy option |
| SuccessOrExit(error = aRequest.AppendOptionFromMessage(optionNumber, iterator.GetOption()->GetLength(), |
| iterator.GetMessage(), |
| iterator.GetOptionValueMessageOffset())); |
| } |
| |
| if (!isOptionSet) |
| { |
| SuccessOrExit(error = aRequest.AppendBlockOption(aBlockOptionNumber, aBlockInfo)); |
| } |
| |
| error = aRequestOld.AppendMetadataToMessage(); |
| |
| exit: |
| return error; |
| } |
| |
| Error CoapBase::SendNextBlock1Request(Request &aRequest, Msg &aRxMsg) |
| { |
| Error error = kErrorNone; |
| Message *request = nullptr; |
| uint8_t buf[kMaxBlockSize] = {0}; |
| uint16_t blockSize; |
| BlockInfo msgBlockInfo; |
| BlockInfo requestBlockInfo; |
| |
| SuccessOrExit(error = aRequest.GetMessage().ReadBlockOptionValues(kOptionBlock1, requestBlockInfo)); |
| SuccessOrExit(error = aRxMsg.mMessage.ReadBlockOptionValues(kOptionBlock1, msgBlockInfo)); |
| |
| // Conclude block-wise transfer if last block has been received |
| if (!requestBlockInfo.mMoreBlocks) |
| { |
| mPendingRequests.FinalizeRequest(aRequest, kErrorNone, &aRxMsg); |
| ExitNow(); |
| } |
| |
| blockSize = msgBlockInfo.GetBlockSize(); |
| VerifyOrExit(blockSize <= kMaxBlockSize, error = kErrorNoBufs); |
| |
| requestBlockInfo.mBlockNumber = msgBlockInfo.mBlockNumber + 1; |
| requestBlockInfo.mBlockSzx = msgBlockInfo.mBlockSzx; |
| requestBlockInfo.mMoreBlocks = false; |
| |
| SuccessOrExit(error = aRequest.GetCallbacks().mBlockwiseTransmitHook(aRequest.GetCallbacks().mContext, buf, |
| requestBlockInfo.GetBlockOffsetPosition(), |
| &blockSize, &requestBlockInfo.mMoreBlocks)); |
| |
| VerifyOrExit(blockSize <= msgBlockInfo.GetBlockSize(), error = kErrorInvalidArgs); |
| |
| VerifyOrExit((request = NewMessage()) != nullptr, error = kErrorNoBufs); |
| |
| SuccessOrExit(error = PrepareNextBlockRequest(kOptionBlock1, aRequest, *request, requestBlockInfo)); |
| |
| SuccessOrExit(error = request->AppendPayloadMarker()); |
| |
| SuccessOrExit(error = request->AppendBytes(buf, blockSize)); |
| |
| mPendingRequests.Remove(aRequest); |
| |
| LogInfo("Send Block1 Nr. %d, Size: %d bytes, More Blocks Flag: %d", requestBlockInfo.mBlockNumber, |
| requestBlockInfo.GetBlockSize(), requestBlockInfo.mMoreBlocks); |
| |
| SuccessOrExit(error = |
| SendMessage(*request, aRxMsg.mMessageInfo, /* aTxParamters */ nullptr, aRequest.GetCallbacks())); |
| |
| exit: |
| FreeMessageOnError(request, error); |
| |
| return error; |
| } |
| |
| Error CoapBase::SendNextBlock2Request(Request &aRequest, Msg &aRxMsg, uint32_t aTotalLength, bool aBeginBlock1Transfer) |
| { |
| Error error = kErrorNone; |
| Message *request = nullptr; |
| uint8_t buf[kMaxBlockSize]; |
| OffsetRange offsetRange; |
| BlockInfo msgBlockInfo; |
| BlockInfo requestBlockInfo; |
| SendCallbacks callbacks; |
| |
| SuccessOrExit(error = aRxMsg.mMessage.ReadBlockOptionValues(kOptionBlock2, msgBlockInfo)); |
| |
| VerifyOrExit(msgBlockInfo.GetBlockSize() <= kMaxBlockSize, error = kErrorNoBufs); |
| |
| offsetRange.InitFromMessageOffsetToEnd(aRxMsg.mMessage); |
| VerifyOrExit(offsetRange.GetLength() <= msgBlockInfo.GetBlockSize(), error = kErrorNoBufs); |
| |
| aRxMsg.mMessage.ReadBytes(offsetRange, buf); |
| SuccessOrExit(error = aRequest.GetCallbacks().mBlockwiseReceiveHook( |
| aRequest.GetCallbacks().mContext, buf, msgBlockInfo.GetBlockOffsetPosition(), |
| offsetRange.GetLength(), msgBlockInfo.mMoreBlocks, aTotalLength)); |
| |
| LogInfo("Received Block2 Nr. %d , Size: %d bytes, More Blocks Flag: %d", msgBlockInfo.mBlockNumber, |
| msgBlockInfo.GetBlockSize(), msgBlockInfo.mMoreBlocks); |
| |
| if (!msgBlockInfo.mMoreBlocks) |
| { |
| mPendingRequests.FinalizeRequest(aRequest, kErrorNone, &aRxMsg); |
| ExitNow(); |
| } |
| |
| VerifyOrExit((request = NewMessage()) != nullptr, error = kErrorNoBufs); |
| |
| requestBlockInfo = msgBlockInfo; |
| requestBlockInfo.mBlockNumber++; |
| requestBlockInfo.mMoreBlocks = false; // RFC 7959 Section 2.3 second bullet: MUST be 0 in request. |
| |
| SuccessOrExit(error = PrepareNextBlockRequest(kOptionBlock2, aRequest, *request, requestBlockInfo)); |
| |
| if (!aBeginBlock1Transfer) |
| { |
| mPendingRequests.Remove(aRequest); |
| } |
| |
| LogInfo("Request Block2 Nr. %d, Size: %d bytes", requestBlockInfo.mBlockNumber, requestBlockInfo.GetBlockSize()); |
| |
| callbacks = aRequest.GetCallbacks(); |
| callbacks.mBlockwiseTransmitHook = nullptr; |
| |
| SuccessOrExit(error = SendMessage(*request, aRxMsg.mMessageInfo, /* aTxParameters */ nullptr, callbacks)); |
| |
| exit: |
| FreeMessageOnError(request, error); |
| |
| return error; |
| } |
| |
| Error CoapBase::ProcessBlock1Request(Msg &aRxMsg, const ResourceBlockWise &aResource, uint32_t aTotalLength) |
| { |
| Error error = kErrorNone; |
| Message *response = nullptr; |
| uint8_t buf[kMaxBlockSize]; |
| OffsetRange offsetRange; |
| BlockInfo msgBlockInfo; |
| |
| SuccessOrExit(error = aRxMsg.mMessage.ReadBlockOptionValues(kOptionBlock1, msgBlockInfo)); |
| |
| offsetRange.InitFromMessageOffsetToEnd(aRxMsg.mMessage); |
| VerifyOrExit(offsetRange.GetLength() <= kMaxBlockSize, error = kErrorNoBufs); |
| |
| aRxMsg.mMessage.ReadBytes(offsetRange, buf); |
| SuccessOrExit(error = |
| aResource.HandleBlockReceive(buf, msgBlockInfo.GetBlockOffsetPosition(), offsetRange.GetLength(), |
| msgBlockInfo.mMoreBlocks, aTotalLength)); |
| |
| if (msgBlockInfo.mMoreBlocks) |
| { |
| // Set up next response |
| VerifyOrExit((response = NewMessage()) != nullptr, error = kErrorFailed); |
| SuccessOrExit(error = response->Init(kTypeAck, kCodeContinue, aRxMsg.GetMessageId())); |
| SuccessOrExit(error = response->WriteTokenFromMessage(aRxMsg.mMessage)); |
| |
| SuccessOrExit(error = response->AppendBlockOption(kOptionBlock1, msgBlockInfo)); |
| |
| SuccessOrExit(error = CacheLastBlockResponse(response)); |
| |
| LogInfo("Acknowledge Block1 Nr. %d, Size: %d bytes", msgBlockInfo.mBlockNumber, msgBlockInfo.GetBlockSize()); |
| |
| SuccessOrExit(error = SendMessage(*response, aRxMsg.mMessageInfo)); |
| |
| error = kErrorBusy; |
| } |
| else |
| { |
| // Conclude block-wise transfer if last block has been received |
| FreeLastBlockResponse(); |
| error = kErrorNone; |
| } |
| |
| exit: |
| if (error != kErrorNone && error != kErrorBusy && response != nullptr) |
| { |
| response->Free(); |
| } |
| |
| return error; |
| } |
| |
| Error CoapBase::ProcessBlock2Request(Msg &aRxMsg, const ResourceBlockWise &aResource) |
| { |
| Error error = kErrorNone; |
| Message *response = nullptr; |
| uint64_t optionBuf = 0; |
| uint8_t buf[kMaxBlockSize] = {0}; |
| uint16_t blockSize; |
| Option::Iterator iterator; |
| BlockInfo msgBlockInfo; |
| BlockInfo responseBlockInfo; |
| |
| SuccessOrExit(error = aRxMsg.mMessage.ReadBlockOptionValues(kOptionBlock2, msgBlockInfo)); |
| |
| LogInfo("Request for Block2 Nr. %d, Size: %d bytes received", msgBlockInfo.mBlockNumber, |
| msgBlockInfo.GetBlockSize()); |
| |
| if (msgBlockInfo.mBlockNumber == 0) |
| { |
| aResource.HandleRequest(aRxMsg); |
| ExitNow(); |
| } |
| |
| VerifyOrExit((response = NewMessage()) != nullptr, error = kErrorNoBufs); |
| |
| SuccessOrExit(error = response->Init(kTypeAck, kCodeContent, aRxMsg.GetMessageId())); |
| SuccessOrExit(error = response->WriteTokenFromMessage(aRxMsg.mMessage)); |
| |
| responseBlockInfo.mMoreBlocks = false; |
| |
| VerifyOrExit((blockSize = msgBlockInfo.GetBlockSize()) <= kMaxBlockSize, error = kErrorNoBufs); |
| SuccessOrExit(error = aResource.HandleBlockTransmit(buf, msgBlockInfo.GetBlockOffsetPosition(), &blockSize, |
| &responseBlockInfo.mMoreBlocks)); |
| |
| if (responseBlockInfo.mMoreBlocks) |
| { |
| SuccessOrExit(error = DetermineBlockSzxFromSize(blockSize, responseBlockInfo.mBlockSzx)); |
| } |
| else |
| { |
| VerifyOrExit(blockSize <= msgBlockInfo.GetBlockSize(), error = kErrorInvalidArgs); |
| responseBlockInfo.mBlockSzx = msgBlockInfo.mBlockSzx; |
| } |
| |
| responseBlockInfo.mBlockNumber = msgBlockInfo.GetBlockOffsetPosition() / responseBlockInfo.GetBlockSize(); |
| |
| // Copy options from last response |
| SuccessOrExit(error = iterator.Init(*mLastResponse)); |
| |
| while (!iterator.IsDone()) |
| { |
| uint16_t optionNumber = iterator.GetOption()->GetNumber(); |
| |
| if (optionNumber == kOptionBlock2) |
| { |
| SuccessOrExit(error = response->AppendBlockOption(kOptionBlock2, responseBlockInfo)); |
| } |
| else if (optionNumber == kOptionBlock1) |
| { |
| SuccessOrExit(error = iterator.ReadOptionValue(&optionBuf)); |
| SuccessOrExit(error = response->AppendOption(optionNumber, iterator.GetOption()->GetLength(), &optionBuf)); |
| } |
| |
| SuccessOrExit(error = iterator.Advance()); |
| } |
| |
| SuccessOrExit(error = response->AppendPayloadMarker()); |
| SuccessOrExit(error = response->AppendBytes(buf, blockSize)); |
| |
| if (responseBlockInfo.mMoreBlocks) |
| { |
| SuccessOrExit(error = CacheLastBlockResponse(response)); |
| } |
| else |
| { |
| // Conclude block-wise transfer if last block has been received |
| FreeLastBlockResponse(); |
| } |
| |
| LogInfo("Send Block2 Nr. %d, Size: %d bytes, More Blocks Flag %d", responseBlockInfo.mBlockNumber, |
| responseBlockInfo.GetBlockSize(), responseBlockInfo.mMoreBlocks); |
| |
| SuccessOrExit(error = SendMessage(*response, aRxMsg.mMessageInfo)); |
| |
| exit: |
| FreeMessageOnError(response, error); |
| |
| return error; |
| } |
| |
| Error CoapBase::DetermineBlockSzxFromSize(uint16_t aSize, BlockSzx &aBlockSzx) |
| { |
| Error error = kErrorNone; |
| |
| for (uint8_t szx = kBlockSzx16; szx <= kBlockSzx1024; szx++) |
| { |
| aBlockSzx = static_cast<BlockSzx>(szx); |
| |
| if (BlockSizeFromExponent(aBlockSzx) == aSize) |
| { |
| ExitNow(); |
| } |
| } |
| |
| error = kErrorInvalidArgs; |
| |
| exit: |
| return error; |
| } |
| |
| #endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE |
| |
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| // `CoapBase` - Observe methods |
| |
| #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE |
| |
| Error CoapBase::PendingRequests::ProcessObserveSend(const Msg &aTxMsg, Request &aRequest) |
| { |
| Error error; |
| Option::Iterator iterator; |
| |
| aRequest.mMetadata.mObserve = false; |
| aRequest.mMetadata.mIsRequest = aTxMsg.IsRequest(); |
| |
| SuccessOrExit(error = iterator.Init(aTxMsg.mMessage, kOptionObserve)); |
| aRequest.mMetadata.mObserve = !iterator.IsDone(); |
| |
| // Special case, if we're sending a GET with Observe=1, that is a |
| // cancellation. |
| |
| if (aRequest.mMetadata.mObserve && aTxMsg.IsGetRequest()) |
| { |
| uint64_t value = 0; |
| |
| SuccessOrExit(error = iterator.ReadOptionValue(value)); |
| |
| if (value == 1) |
| { |
| Request request; |
| |
| aRequest.mMetadata.mObserve = false; |
| |
| // If we can find the previous matching request, cancel that too. |
| |
| if (FindRelatedRequest(aTxMsg, request) == kErrorNone) |
| { |
| FinalizeRequest(request, kErrorNone); |
| } |
| } |
| } |
| |
| exit: |
| return error; |
| } |
| |
| #endif // OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // CoapBase::SendCallbacks |
| |
| void CoapBase::SendCallbacks::Clear(void) |
| { |
| // We avoid using `ClearAllBytes()` or `Clearable` because they |
| // zero out all object memory. Unlike standard data pointers, the |
| // C++ standard does not strictly guarantee that a `nullptr` |
| // function pointer is represented by an "all-bits-zero" memory |
| // pattern. |
| |
| mContext = nullptr; |
| mResponseHandler = nullptr; |
| mResponseHandlerSeparateParams = nullptr; |
| #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE |
| mBlockwiseReceiveHook = nullptr; |
| mBlockwiseTransmitHook = nullptr; |
| #endif |
| } |
| |
| bool CoapBase::SendCallbacks::HasResponseHandler(void) const |
| { |
| return (mResponseHandler != nullptr) || (mResponseHandlerSeparateParams != nullptr); |
| } |
| |
| bool CoapBase::SendCallbacks::Matches(ResponseHandler aHandler, void *aContext) const |
| { |
| return (mResponseHandler == aHandler) && (mContext == aContext); |
| } |
| |
| void CoapBase::SendCallbacks::InvokeResponseHandler(Msg *aMsg, Error aResult) const |
| { |
| if (mResponseHandler != nullptr) |
| { |
| mResponseHandler(mContext, aMsg, aResult); |
| } |
| else if (mResponseHandlerSeparateParams != nullptr) |
| { |
| Message *message = (aMsg != nullptr) ? &aMsg->mMessage : nullptr; |
| const Ip6::MessageInfo *messageInfo = (aMsg != nullptr) ? &aMsg->mMessageInfo : nullptr; |
| |
| mResponseHandlerSeparateParams(mContext, message, messageInfo, aResult); |
| } |
| } |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // CoapBase::Request::Metadata |
| |
| void CoapBase::Request::Metadata::Init(const Msg &aTxMsg, |
| const TxParameters &aTxParams, |
| const SendCallbacks &aCallbacks) |
| { |
| mSourceAddress = aTxMsg.mMessageInfo.GetSockAddr(); |
| mDestinationPort = aTxMsg.mMessageInfo.GetPeerPort(); |
| mDestinationAddress = aTxMsg.mMessageInfo.GetPeerAddr(); |
| mMulticastLoop = aTxMsg.mMessageInfo.GetMulticastLoop(); |
| mCallbacks = aCallbacks; |
| mRetxRemaining = aTxParams.mMaxRetransmit; |
| mRetxTimeout = aTxParams.CalculateInitialRetransmissionTimeout(); |
| mAcknowledged = false; |
| mConfirmable = aTxMsg.IsConfirmable(); |
| #if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE |
| mHopLimit = aTxMsg.mMessageInfo.GetHopLimit(); |
| mIsHostInterface = aTxMsg.mMessageInfo.IsHostInterface(); |
| #endif |
| |
| mTimerFireTime = TimerMilli::GetNow() + (mConfirmable ? mRetxTimeout : aTxParams.CalculateMaxTransmitWait()); |
| } |
| |
| void CoapBase::Request::Metadata::CopyInfoTo(Ip6::MessageInfo &aMessageInfo) const |
| { |
| aMessageInfo.SetPeerAddr(mDestinationAddress); |
| aMessageInfo.SetPeerPort(mDestinationPort); |
| aMessageInfo.SetSockAddr(mSourceAddress); |
| aMessageInfo.SetMulticastLoop(mMulticastLoop); |
| #if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE |
| aMessageInfo.SetHopLimit(mHopLimit); |
| aMessageInfo.SetIsHostInterface(mIsHostInterface); |
| #endif |
| } |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // CoapBase::Request |
| |
| void CoapBase::Request::MarkAsAcknowledged(void) |
| { |
| mMetadata.mAcknowledged = true; |
| WriteMetadataInMessage(); |
| } |
| |
| bool CoapBase::Request::HasSamePeerAddrAndPort(const Ip6::MessageInfo &aMessageInfo) const |
| { |
| return (mMetadata.mDestinationPort == aMessageInfo.GetPeerPort()) && |
| (mMetadata.mDestinationAddress == aMessageInfo.GetPeerAddr()); |
| } |
| |
| bool CoapBase::Request::ShouldRetransmit(void) const { return IsConfirmable() && (mMetadata.mRetxRemaining > 0); } |
| |
| void CoapBase::Request::UpdateRetxCounterAndTimeout(TimeMilli aNow) |
| { |
| mMetadata.mRetxRemaining--; |
| mMetadata.mRetxTimeout *= 2; |
| |
| mMetadata.mTimerFireTime = aNow + mMetadata.mRetxTimeout; |
| WriteMetadataInMessage(); |
| } |
| |
| #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE |
| bool CoapBase::Request::IsObserveSubscription(void) const |
| { |
| // Indicate whether the message is an RFC7641 subscription which |
| // is already acknowledged. |
| |
| return IsRequest() && IsObserve() && IsAcknowledged(); |
| } |
| #endif |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // CoapBase::PendingRequests |
| |
| CoapBase::PendingRequests::PendingRequests(Instance &aInstance, CoapBase &aCoapBase) |
| : mCoapBase(aCoapBase) |
| , mTimer(aInstance, HandleTimer, this) |
| { |
| } |
| |
| Error CoapBase::PendingRequests::Add(const Msg &aTxMsg, |
| const TxParameters &aTxParams, |
| const SendCallbacks &aCallbacks, |
| Request &aRequest) |
| { |
| Error error = kErrorNone; |
| uint16_t cloneLength; |
| |
| aRequest.mMetadata.Init(aTxMsg, aTxParams, aCallbacks); |
| |
| #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE |
| SuccessOrExit(error = ProcessObserveSend(aTxMsg, aRequest)); |
| #endif |
| |
| // We clone the full message for confirmable requests to allow for |
| // retransmits, but only the header for non-confirmable requests |
| // to preserve the token. |
| |
| cloneLength = aTxMsg.IsConfirmable() ? aTxMsg.mMessage.GetLength() : aTxMsg.GetHeaderSize(); |
| |
| aRequest.mMessage = AsCoapMessagePtr(aTxMsg.mMessage.Clone<kNoReservedHeader>(cloneLength)); |
| VerifyOrExit(aRequest.HasMessage(), error = kErrorNoBufs); |
| |
| SuccessOrExit(error = aRequest.AppendMetadataToMessage()); |
| |
| mRequestMessages.Enqueue(*aRequest.mMessage); |
| |
| mTimer.FireAtIfEarlier(aRequest.GetTimerFireTime()); |
| |
| exit: |
| FreeAndNullMessageOnError(aRequest.mMessage, error); |
| return error; |
| } |
| |
| void CoapBase::PendingRequests::Remove(Request &aRequest) |
| { |
| VerifyOrExit(aRequest.HasMessage()); |
| mRequestMessages.DequeueAndFree(*aRequest.mMessage); |
| aRequest.Clear(); |
| |
| exit: |
| return; |
| } |
| |
| Error CoapBase::PendingRequests::FindRelatedRequest(const Msg &aMsg, Request &aRequest) |
| { |
| Error error = kErrorNotFound; |
| |
| for (Message &message : mRequestMessages) |
| { |
| aRequest.InitFrom(message); |
| |
| if (aRequest.HasSamePeerAddrAndPort(aMsg.mMessageInfo) || aRequest.GetDestinationAddress().IsMulticast() || |
| aRequest.GetDestinationAddress().GetIid().IsAnycastLocator()) |
| { |
| switch (aMsg.GetType()) |
| { |
| case kTypeReset: |
| case kTypeAck: |
| if (aMsg.GetMessageId() == message.ReadMessageId()) |
| { |
| ExitNow(error = kErrorNone); |
| } |
| |
| break; |
| |
| case kTypeConfirmable: |
| case kTypeNonConfirmable: |
| if (aMsg.mMessage.HasSameTokenAs(message)) |
| { |
| ExitNow(error = kErrorNone); |
| } |
| |
| break; |
| } |
| } |
| } |
| |
| aRequest.Clear(); |
| |
| exit: |
| return error; |
| } |
| |
| void CoapBase::PendingRequests::FinalizeRequest(Request &aRequest, Error aResult) |
| { |
| FinalizeRequest(aRequest, aResult, /* aResponse */ nullptr); |
| } |
| |
| void CoapBase::PendingRequests::FinalizeRequest(Request &aRequest, Error aResult, Msg *aResponse) |
| { |
| VerifyOrExit(aRequest.HasMessage()); |
| |
| Remove(aRequest); |
| aRequest.InvokeResponseHandler(aResponse, aResult); |
| |
| exit: |
| return; |
| } |
| |
| void CoapBase::PendingRequests::AbortAllRequests(void) |
| { |
| IgnoreError(AbortAllMatching(Matcher())); |
| mTimer.Stop(); |
| } |
| |
| void CoapBase::PendingRequests::AbortRequestsMatching(const Ip6::Address &aAddress) |
| { |
| IgnoreError(AbortAllMatching(Matcher(aAddress))); |
| } |
| |
| Error CoapBase::PendingRequests::AbortRequestsMatching(ResponseHandler aHandler, void *aContext) |
| { |
| return AbortAllMatching(Matcher(aHandler, aContext)); |
| } |
| |
| Error CoapBase::PendingRequests::AbortAllMatching(const Matcher &aMatcher) |
| { |
| Error error = kErrorNotFound; |
| MessageQueue abortedMessages; |
| |
| for (Message &message : mRequestMessages) |
| { |
| Request request; |
| |
| request.InitFrom(message); |
| |
| if (aMatcher.Matches(request)) |
| { |
| mRequestMessages.Dequeue(message); |
| abortedMessages.Enqueue(message); |
| error = kErrorNone; |
| } |
| } |
| |
| FinalizeRemovedRequestsIn(abortedMessages, kErrorAbort); |
| |
| return error; |
| } |
| |
| void CoapBase::PendingRequests::FinalizeRemovedRequestsIn(MessageQueue &aQueue, Error aResult) |
| { |
| for (Message &message : aQueue) |
| { |
| Request request; |
| |
| request.InitFrom(message); |
| request.InvokeResponseHandler(/* aResponse */ nullptr, aResult); |
| } |
| |
| aQueue.DequeueAndFreeAll(); |
| } |
| |
| void CoapBase::PendingRequests::RetransmitRequest(const Request &aRequest) |
| { |
| Error error; |
| Message *clone; |
| Ip6::MessageInfo messageInfo; |
| |
| clone = mCoapBase.CloneMessageWithout<Request::Metadata>(aRequest.GetMessage()); |
| VerifyOrExit(clone != nullptr, error = kErrorNoBufs); |
| |
| aRequest.mMetadata.CopyInfoTo(messageInfo); |
| |
| SuccessOrExit(error = mCoapBase.Transmit(*clone, messageInfo)); |
| |
| exit: |
| FreeMessageOnError(clone, error); |
| } |
| |
| void CoapBase::PendingRequests::HandleTimer(Timer &aTimer) |
| { |
| static_cast<PendingRequests *>(static_cast<TimerMilliContext &>(aTimer).GetContext())->HandleTimer(); |
| } |
| |
| void CoapBase::PendingRequests::HandleTimer(void) |
| { |
| NextFireTime nextTime; |
| MessageQueue expiredMessages; |
| |
| for (Message &message : mRequestMessages) |
| { |
| Request request; |
| |
| request.InitFrom(message); |
| |
| #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE |
| if (request.IsObserveSubscription()) |
| { |
| // This is an RFC7641 subscription which is already |
| // acknowledged. We do not time it out, so skip it when |
| // determining the next fire time. |
| continue; |
| } |
| #endif |
| |
| if (nextTime.GetNow() >= request.GetTimerFireTime()) |
| { |
| if (!request.ShouldRetransmit()) |
| { |
| // We move the expired request to a separate queue to |
| // finalize it after the loop. This ensures that the |
| // iterator over `mRequestMessages` remains valid |
| // even if the user callback (invoked during |
| // finalization) modifies any pending requests |
| |
| mRequestMessages.Dequeue(message); |
| expiredMessages.Enqueue(message); |
| continue; |
| } |
| |
| request.UpdateRetxCounterAndTimeout(nextTime.GetNow()); |
| |
| if (!request.IsAcknowledged()) |
| { |
| RetransmitRequest(request); |
| } |
| } |
| |
| nextTime.UpdateIfEarlier(request.GetTimerFireTime()); |
| } |
| |
| mTimer.FireAt(nextTime); |
| |
| FinalizeRemovedRequestsIn(expiredMessages, kErrorResponseTimeout); |
| } |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // CoapBase::PendingRequests::Matcher |
| |
| bool CoapBase::PendingRequests::Matcher::Matches(const Request &aRequest) const |
| { |
| bool matches = false; |
| |
| switch (mMode) |
| { |
| case kAny: |
| break; |
| case kAddress: |
| VerifyOrExit(aRequest.GetSourceAddress() == *mAddress); |
| break; |
| case kHandler: |
| VerifyOrExit(aRequest.GetCallbacks().Matches(mHandler, mContext)); |
| break; |
| } |
| |
| matches = true; |
| |
| exit: |
| return matches; |
| } |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // CoapBase::ResponseCache |
| |
| CoapBase::ResponseCache::ResponseCache(Instance &aInstance) |
| : mTimer(aInstance, ResponseCache::HandleTimer, this) |
| { |
| } |
| |
| Error CoapBase::ResponseCache::SendCachedResponse(const Msg &aRxMsg, CoapBase &aCoapBase) |
| { |
| // Search `ResponseCache` for a cached response matching the given |
| // request `aRxMsg`. If found, clone the response and send it. Returns |
| // `kErrorNotFound` if no match is found, `kErrorNone` on success, |
| // or other errors if send fails. |
| |
| Error error = kErrorNone; |
| const Message *match = FindMatching(aRxMsg); |
| Message *response = nullptr; |
| |
| VerifyOrExit(match != nullptr, error = kErrorNotFound); |
| |
| response = aCoapBase.CloneMessageWithout<ResponseMetadata>(*match); |
| VerifyOrExit(response != nullptr, error = kErrorNoBufs); |
| |
| error = aCoapBase.Transmit(*response, aRxMsg.mMessageInfo); |
| |
| exit: |
| FreeMessageOnError(response, error); |
| return error; |
| } |
| |
| const Message *CoapBase::ResponseCache::FindMatching(const Msg &aRxMsg) const |
| { |
| const Message *match = nullptr; |
| uint16_t requestMsgId = aRxMsg.GetMessageId(); |
| |
| for (const Message &response : mResponses) |
| { |
| if (response.ReadMessageId() == requestMsgId) |
| { |
| ResponseMetadata metadata; |
| |
| metadata.ReadFrom(response); |
| |
| if (metadata.mMessageInfo.HasSamePeerAddrAndPort(aRxMsg.mMessageInfo)) |
| { |
| match = &response; |
| break; |
| } |
| } |
| } |
| |
| return match; |
| } |
| |
| void CoapBase::ResponseCache::Add(const Msg &aTxMsg, uint32_t aExchangeLifetime) |
| { |
| // Adds a clone of the `aTxMsg` to the cache if a matching |
| // entry does not already exist. |
| |
| Message *responseClone = nullptr; |
| ResponseMetadata metadata; |
| |
| VerifyOrExit(FindMatching(aTxMsg) == nullptr); |
| |
| MaintainCacheSize(); |
| |
| responseClone = AsCoapMessagePtr(aTxMsg.mMessage.Clone<kNoReservedHeader>()); |
| VerifyOrExit(responseClone != nullptr); |
| |
| metadata.mExpireTime = TimerMilli::GetNow() + aExchangeLifetime; |
| metadata.mMessageInfo = aTxMsg.mMessageInfo; |
| |
| SuccessOrExit(metadata.AppendTo(*responseClone)); |
| |
| mResponses.Enqueue(*responseClone); |
| responseClone = nullptr; |
| |
| mTimer.FireAtIfEarlier(metadata.mExpireTime); |
| |
| exit: |
| FreeMessage(responseClone); |
| } |
| |
| void CoapBase::ResponseCache::MaintainCacheSize(void) |
| { |
| // Checks the cache size. If the limit (`kMaxCacheSize`) is |
| // reached, removes the entry with the earliest expire time. |
| |
| uint16_t count = 0; |
| Message *msgToRemove = nullptr; |
| TimeMilli earliestExpireTime; |
| |
| for (Message &response : mResponses) |
| { |
| ResponseMetadata metadata; |
| |
| metadata.ReadFrom(response); |
| |
| if ((msgToRemove == nullptr) || (metadata.mExpireTime < earliestExpireTime)) |
| { |
| msgToRemove = &response; |
| earliestExpireTime = metadata.mExpireTime; |
| } |
| |
| count++; |
| } |
| |
| if (count >= kMaxCacheSize) |
| { |
| mResponses.DequeueAndFree(*msgToRemove); |
| } |
| } |
| |
| void CoapBase::ResponseCache::RemoveAll(void) |
| { |
| mResponses.DequeueAndFreeAll(); |
| mTimer.Stop(); |
| } |
| |
| void CoapBase::ResponseCache::HandleTimer(Timer &aTimer) |
| { |
| static_cast<ResponseCache *>(static_cast<TimerMilliContext &>(aTimer).GetContext())->HandleTimer(); |
| } |
| |
| void CoapBase::ResponseCache::HandleTimer(void) |
| { |
| NextFireTime expireTime; |
| |
| for (Message &response : mResponses) |
| { |
| ResponseMetadata metadata; |
| |
| metadata.ReadFrom(response); |
| |
| if (expireTime.GetNow() >= metadata.mExpireTime) |
| { |
| mResponses.DequeueAndFree(response); |
| } |
| else |
| { |
| expireTime.UpdateIfEarlier(metadata.mExpireTime); |
| } |
| } |
| |
| mTimer.FireAt(expireTime); |
| } |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // TxParameters |
| |
| const otCoapTxParameters TxParameters::kDefaultTxParameters = { |
| kDefaultAckTimeout, |
| kDefaultAckRandomFactorNumerator, |
| kDefaultAckRandomFactorDenominator, |
| kDefaultMaxRetransmit, |
| }; |
| |
| const TxParameters &TxParameters::GetDefault(void) |
| { |
| // Validate the default `TxParameters` at compile-time |
| |
| static constexpr uint64_t kMaxDuration = static_cast<uint64_t>(kDefaultAckTimeout) * |
| kDefaultAckRandomFactorNumerator * |
| (1UL << (kDefaultMaxRetransmit + 1)) + |
| 2 * kDefaultMaxLatency; |
| |
| static_assert(kDefaultAckRandomFactorDenominator > 0, "kDefaultAckRandomFactorDenominator MUST be non-zero"); |
| static_assert(kDefaultAckRandomFactorNumerator >= kDefaultAckRandomFactorDenominator, "Numerator is invalid"); |
| static_assert(kMinAckTimeout > 0, "kMinAckTimeout MUST be non-zero"); |
| static_assert(kDefaultAckTimeout >= kMinAckTimeout, "kDefaultAckTimeout is invalid"); |
| static_assert(kMaxRetransmit > 0, "kMaxRetransmit MUST be non-zero"); |
| static_assert(kMaxRetransmit < 31, "kMaxRetransmit is not valid"); |
| static_assert(kDefaultMaxRetransmit <= kMaxRetransmit, "kDefaultMaxRetransmit is invalid"); |
| static_assert(kMaxDuration < NumericLimits<uint32_t>::kMax, "Default `TxParameters` is invalid"); |
| |
| return AsCoreType(&kDefaultTxParameters); |
| } |
| |
| Error TxParameters::ValidateFor(const Msg &aMsg) const |
| { |
| Error error = kErrorInvalidArgs; |
| uint32_t duration; |
| uint32_t retryFactor; |
| |
| if (mAckTimeout == 0) |
| { |
| // Fire and forget is only allowed for non-confirmable messages. |
| VerifyOrExit(aMsg.IsNonConfirmable()); |
| error = kErrorNone; |
| ExitNow(); |
| } |
| |
| VerifyOrExit(mAckRandomFactorDenominator > 0); |
| VerifyOrExit(mAckRandomFactorNumerator >= mAckRandomFactorDenominator); |
| VerifyOrExit(mAckTimeout >= kMinAckTimeout); |
| VerifyOrExit(mMaxRetransmit <= kMaxRetransmit); |
| |
| // Calculate exchange lifetime max duration step by step and verify no overflow. |
| |
| retryFactor = static_cast<uint32_t>((1U << (mMaxRetransmit + 1)) - 1); |
| SuccessOrExit(SafeMultiply<uint32_t>(mAckTimeout, retryFactor, duration)); |
| |
| SuccessOrExit(SafeMultiply<uint32_t>(duration, mAckRandomFactorNumerator, duration)); |
| duration /= mAckRandomFactorDenominator; |
| |
| VerifyOrExit(duration > 0); |
| VerifyOrExit(CanAddSafely<uint32_t>(mAckTimeout, 2 * kDefaultMaxLatency)); |
| VerifyOrExit(CanAddSafely<uint32_t>(duration, mAckTimeout + 2 * kDefaultMaxLatency)); |
| |
| error = kErrorNone; |
| |
| exit: |
| return error; |
| } |
| |
| uint32_t TxParameters::CalculateInitialRetransmissionTimeout(void) const |
| { |
| return Random::NonCrypto::GetUint32InRange( |
| mAckTimeout, mAckTimeout * mAckRandomFactorNumerator / mAckRandomFactorDenominator + 1); |
| } |
| |
| uint32_t TxParameters::CalculateExchangeLifetime(void) const |
| { |
| // Final `mAckTimeout` is to account for processing delay. |
| return CalculateSpan(mMaxRetransmit) + 2 * kDefaultMaxLatency + mAckTimeout; |
| } |
| |
| uint32_t TxParameters::CalculateMaxTransmitWait(void) const { return CalculateSpan(mMaxRetransmit + 1); } |
| |
| uint32_t TxParameters::CalculateSpan(uint8_t aMaxRetx) const |
| { |
| return static_cast<uint32_t>(mAckTimeout * ((1U << aMaxRetx) - 1) / mAckRandomFactorDenominator * |
| mAckRandomFactorNumerator); |
| } |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // Resource |
| |
| Resource::Resource(const char *aUriPath, RequestHandler aHandler, void *aContext) |
| { |
| mUriPath = aUriPath; |
| mHandler = aHandler; |
| mContext = aContext; |
| mNext = nullptr; |
| } |
| |
| Resource::Resource(Uri aUri, RequestHandler aHandler, void *aContext) |
| : Resource(PathForUri(aUri), aHandler, aContext) |
| { |
| } |
| |
| //--------------------------------------------------------------------------------------------------------------------- |
| // Coap |
| |
| Coap::Coap(Instance &aInstance) |
| : CoapBase(aInstance, Coap::Transmit) |
| , mSocket(aInstance, *this) |
| { |
| } |
| |
| Error Coap::Start(uint16_t aPort, Ip6::NetifIdentifier aNetifIdentifier) |
| { |
| Error error = kErrorNone; |
| bool socketOpened = false; |
| |
| VerifyOrExit(!mSocket.IsBound()); |
| |
| SuccessOrExit(error = mSocket.Open(aNetifIdentifier)); |
| socketOpened = true; |
| |
| SuccessOrExit(error = mSocket.Bind(aPort)); |
| |
| exit: |
| if (error != kErrorNone && socketOpened) |
| { |
| IgnoreError(mSocket.Close()); |
| } |
| |
| return error; |
| } |
| |
| Error Coap::Stop(void) |
| { |
| Error error = kErrorNone; |
| |
| VerifyOrExit(mSocket.IsBound()); |
| |
| SuccessOrExit(error = mSocket.Close()); |
| ClearAllRequestsAndResponses(); |
| |
| exit: |
| return error; |
| } |
| |
| void Coap::HandleUdpReceive(ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) |
| { |
| Receive(AsCoapMessage(&aMessage), aMessageInfo); |
| } |
| |
| Error Coap::Transmit(CoapBase &aCoapBase, ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) |
| { |
| return static_cast<Coap &>(aCoapBase).Transmit(aMessage, aMessageInfo); |
| } |
| |
| Error Coap::Transmit(ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) |
| { |
| return mSocket.IsBound() ? mSocket.SendTo(aMessage, aMessageInfo) : kErrorInvalidState; |
| } |
| |
| } // namespace Coap |
| } // namespace ot |