blob: 737a51097c604f0aa919ff0070853305bb573fe0 [file]
/*
* 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