blob: f0b966daae687a4c178594f2ea19f21c4f140382 [file] [log] [blame]
/*
* 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 "common/array.hpp"
#include "common/as_core_type.hpp"
#include "common/code_utils.hpp"
#include "common/debug.hpp"
#include "common/instance.hpp"
#include "common/locator_getters.hpp"
#include "common/log.hpp"
#include "common/random.hpp"
#include "net/ip6.hpp"
#include "net/udp6.hpp"
#include "thread/thread_netif.hpp"
/**
* @file
* This file contains common code base for CoAP client and server.
*/
namespace ot {
namespace Coap {
RegisterLogModule("Coap");
CoapBase::CoapBase(Instance &aInstance, Sender aSender)
: InstanceLocator(aInstance)
, mMessageId(Random::NonCrypto::GetUint16())
, mRetransmissionTimer(aInstance, Coap::HandleRetransmissionTimer, this)
, mContext(nullptr)
, mInterceptor(nullptr)
, mResponsesQueue(aInstance)
, mDefaultHandler(nullptr)
, mDefaultHandlerContext(nullptr)
, mSender(aSender)
#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
, mLastResponse(nullptr)
#endif
{
}
void CoapBase::ClearRequestsAndResponses(void)
{
ClearRequests(nullptr); // Clear requests matching any address.
mResponsesQueue.DequeueAllResponses();
}
void CoapBase::ClearRequests(const Ip6::Address &aAddress)
{
ClearRequests(&aAddress);
}
void CoapBase::ClearRequests(const Ip6::Address *aAddress)
{
for (Message &message : mPendingRequests)
{
Metadata metadata;
metadata.ReadFrom(message);
if ((aAddress == nullptr) || (metadata.mSourceAddress == *aAddress))
{
FinalizeCoapTransaction(message, metadata, nullptr, nullptr, kErrorAbort);
}
}
}
#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);
}
#endif
void CoapBase::AddResource(Resource &aResource)
{
IgnoreError(mResources.Add(aResource));
}
void CoapBase::RemoveResource(Resource &aResource)
{
IgnoreError(mResources.Remove(aResource));
aResource.SetNext(nullptr);
}
void CoapBase::SetDefaultHandler(RequestHandler aHandler, void *aContext)
{
mDefaultHandler = aHandler;
mDefaultHandlerContext = aContext;
}
void CoapBase::SetInterceptor(Interceptor aInterceptor, void *aContext)
{
mInterceptor = aInterceptor;
mContext = aContext;
}
Message *CoapBase::NewMessage(const Message::Settings &aSettings)
{
Message *message = nullptr;
VerifyOrExit((message = AsCoapMessagePtr(Get<Ip6::Udp>().NewMessage(0, aSettings))) != nullptr);
message->SetOffset(0);
exit:
return message;
}
Message *CoapBase::NewPriorityConfirmablePostMessage(const char *aUriPath)
{
return InitMessage(NewPriorityMessage(), kTypeConfirmable, aUriPath);
}
Message *CoapBase::NewConfirmablePostMessage(const char *aUriPath)
{
return InitMessage(NewMessage(), kTypeConfirmable, aUriPath);
}
Message *CoapBase::NewPriorityNonConfirmablePostMessage(const char *aUriPath)
{
return InitMessage(NewPriorityMessage(), kTypeNonConfirmable, aUriPath);
}
Message *CoapBase::NewNonConfirmablePostMessage(const char *aUriPath)
{
return InitMessage(NewMessage(), kTypeNonConfirmable, aUriPath);
}
Message *CoapBase::NewPriorityResponseMessage(const Message &aRequest)
{
return InitResponse(NewPriorityMessage(), aRequest);
}
Message *CoapBase::NewResponseMessage(const Message &aRequest)
{
return InitResponse(NewMessage(), aRequest);
}
Message *CoapBase::InitMessage(Message *aMessage, Type aType, const char *aUriPath)
{
Error error = kErrorNone;
VerifyOrExit(aMessage != nullptr);
SuccessOrExit(error = aMessage->Init(aType, kCodePost, aUriPath));
SuccessOrExit(error = aMessage->SetPayloadMarker());
exit:
FreeAndNullMessageOnError(aMessage, error);
return aMessage;
}
Message *CoapBase::InitResponse(Message *aMessage, const Message &aResponse)
{
Error error = kErrorNone;
VerifyOrExit(aMessage != nullptr);
SuccessOrExit(error = aMessage->SetDefaultResponseHeader(aResponse));
SuccessOrExit(error = aMessage->SetPayloadMarker());
exit:
FreeAndNullMessageOnError(aMessage, error);
return aMessage;
}
Error CoapBase::Send(ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
Error error;
#if OPENTHREAD_CONFIG_OTNS_ENABLE
Get<Utils::Otns>().EmitCoapSend(AsCoapMessage(&aMessage), aMessageInfo);
#endif
error = mSender(*this, aMessage, aMessageInfo);
#if OPENTHREAD_CONFIG_OTNS_ENABLE
if (error != kErrorNone)
{
Get<Utils::Otns>().EmitCoapSendFailure(error, AsCoapMessage(&aMessage), aMessageInfo);
}
#endif
return error;
}
#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
Error CoapBase::SendMessage(Message & aMessage,
const Ip6::MessageInfo & aMessageInfo,
const TxParameters & aTxParameters,
ResponseHandler aHandler,
void * aContext,
otCoapBlockwiseTransmitHook aTransmitHook,
otCoapBlockwiseReceiveHook aReceiveHook)
#else
Error CoapBase::SendMessage(Message & aMessage,
const Ip6::MessageInfo &aMessageInfo,
const TxParameters & aTxParameters,
ResponseHandler aHandler,
void * aContext)
#endif
{
Error error;
Message *storedCopy = nullptr;
uint16_t copyLength = 0;
#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
uint8_t buf[kMaxBlockLength] = {0};
uint16_t bufLen = kMaxBlockLength;
bool moreBlocks = false;
#endif
switch (aMessage.GetType())
{
case kTypeAck:
#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
// Check for block-wise transfer
if ((aTransmitHook != nullptr) && (aMessage.ReadBlockOptionValues(kOptionBlock2) == kErrorNone) &&
(aMessage.GetBlockWiseBlockNumber() == 0))
{
// Set payload for first block of the transfer
VerifyOrExit((bufLen = otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize())) <= kMaxBlockLength,
error = kErrorNoBufs);
SuccessOrExit(error = aTransmitHook(aContext, buf, aMessage.GetBlockWiseBlockNumber() * bufLen, &bufLen,
&moreBlocks));
SuccessOrExit(error = aMessage.AppendBytes(buf, bufLen));
SuccessOrExit(error = CacheLastBlockResponse(&aMessage));
}
#endif
mResponsesQueue.EnqueueResponse(aMessage, aMessageInfo, aTxParameters);
break;
case kTypeReset:
OT_ASSERT(aMessage.GetCode() == kCodeEmpty);
break;
default:
#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
// Check for block-wise transfer
if ((aTransmitHook != nullptr) && (aMessage.ReadBlockOptionValues(kOptionBlock1) == kErrorNone) &&
(aMessage.GetBlockWiseBlockNumber() == 0))
{
// Set payload for first block of the transfer
VerifyOrExit((bufLen = otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize())) <= kMaxBlockLength,
error = kErrorNoBufs);
SuccessOrExit(error = aTransmitHook(aContext, buf, aMessage.GetBlockWiseBlockNumber() * bufLen, &bufLen,
&moreBlocks));
SuccessOrExit(error = aMessage.AppendBytes(buf, bufLen));
// Block-Wise messages always have to be confirmable
if (aMessage.IsNonConfirmable())
{
aMessage.SetType(kTypeConfirmable);
}
}
#endif
aMessage.SetMessageId(mMessageId++);
break;
}
aMessage.Finish();
if (aMessage.IsConfirmable())
{
copyLength = aMessage.GetLength();
}
else if (aMessage.IsNonConfirmable() && (aHandler != nullptr))
{
// As we do not retransmit non confirmable messages, create a
// copy of header only, for token information.
copyLength = aMessage.GetOptionStart();
}
if (copyLength > 0)
{
Metadata metadata;
#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
// Whether or not to turn on special "Observe" handling.
Option::Iterator iterator;
bool observe;
SuccessOrExit(error = iterator.Init(aMessage, kOptionObserve));
observe = !iterator.IsDone();
// Special case, if we're sending a GET with Observe=1, that is a cancellation.
if (observe && aMessage.IsGetRequest())
{
uint64_t observeVal = 0;
SuccessOrExit(error = iterator.ReadOptionValue(observeVal));
if (observeVal == 1)
{
Metadata handlerMetadata;
// We're cancelling our subscription, so disable special-case handling on this request.
observe = false;
// If we can find the previous handler context, cancel that too. Peer address
// and tokens, etc should all match.
Message *origRequest = FindRelatedRequest(aMessage, aMessageInfo, handlerMetadata);
if (origRequest != nullptr)
{
FinalizeCoapTransaction(*origRequest, handlerMetadata, nullptr, nullptr, kErrorNone);
}
}
}
#endif // OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
metadata.mSourceAddress = aMessageInfo.GetSockAddr();
metadata.mDestinationPort = aMessageInfo.GetPeerPort();
metadata.mDestinationAddress = aMessageInfo.GetPeerAddr();
metadata.mMulticastLoop = aMessageInfo.GetMulticastLoop();
metadata.mResponseHandler = aHandler;
metadata.mResponseContext = aContext;
metadata.mRetransmissionsRemaining = aTxParameters.mMaxRetransmit;
metadata.mRetransmissionTimeout = aTxParameters.CalculateInitialRetransmissionTimeout();
metadata.mAcknowledged = false;
metadata.mConfirmable = aMessage.IsConfirmable();
#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
metadata.mHopLimit = aMessageInfo.GetHopLimit();
metadata.mIsHostInterface = aMessageInfo.IsHostInterface();
#endif
#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
metadata.mBlockwiseReceiveHook = aReceiveHook;
metadata.mBlockwiseTransmitHook = aTransmitHook;
#endif
#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
metadata.mObserve = observe;
#endif
metadata.mNextTimerShot =
TimerMilli::GetNow() +
(metadata.mConfirmable ? metadata.mRetransmissionTimeout : aTxParameters.CalculateMaxTransmitWait());
storedCopy = CopyAndEnqueueMessage(aMessage, copyLength, metadata);
VerifyOrExit(storedCopy != nullptr, error = kErrorNoBufs);
}
SuccessOrExit(error = Send(aMessage, aMessageInfo));
exit:
if (error != kErrorNone && storedCopy != nullptr)
{
DequeueMessage(*storedCopy);
}
return error;
}
Error CoapBase::SendMessage(Message & aMessage,
const Ip6::MessageInfo &aMessageInfo,
ResponseHandler aHandler,
void * aContext)
{
#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
return SendMessage(aMessage, aMessageInfo, TxParameters::GetDefault(), aHandler, aContext, nullptr, nullptr);
#else
return SendMessage(aMessage, aMessageInfo, TxParameters::GetDefault(), aHandler, aContext);
#endif
}
Error CoapBase::SendReset(Message &aRequest, const Ip6::MessageInfo &aMessageInfo)
{
return SendEmptyMessage(kTypeReset, aRequest, aMessageInfo);
}
Error CoapBase::SendAck(const Message &aRequest, const Ip6::MessageInfo &aMessageInfo)
{
return SendEmptyMessage(kTypeAck, aRequest, aMessageInfo);
}
Error CoapBase::SendEmptyAck(const Message &aRequest, const Ip6::MessageInfo &aMessageInfo, Code aCode)
{
return (aRequest.IsConfirmable() ? SendHeaderResponse(aCode, aRequest, aMessageInfo) : kErrorInvalidArgs);
}
Error CoapBase::SendNotFound(const Message &aRequest, const Ip6::MessageInfo &aMessageInfo)
{
return SendHeaderResponse(kCodeNotFound, aRequest, aMessageInfo);
}
Error CoapBase::SendEmptyMessage(Type aType, const Message &aRequest, const Ip6::MessageInfo &aMessageInfo)
{
Error error = kErrorNone;
Message *message = nullptr;
VerifyOrExit(aRequest.IsConfirmable(), error = kErrorInvalidArgs);
VerifyOrExit((message = NewMessage()) != nullptr, error = kErrorNoBufs);
message->Init(aType, kCodeEmpty);
message->SetMessageId(aRequest.GetMessageId());
message->Finish();
SuccessOrExit(error = Send(*message, aMessageInfo));
exit:
FreeMessageOnError(message, error);
return error;
}
Error CoapBase::SendHeaderResponse(Message::Code aCode, const Message &aRequest, const Ip6::MessageInfo &aMessageInfo)
{
Error error = kErrorNone;
Message *message = nullptr;
VerifyOrExit(aRequest.IsRequest(), error = kErrorInvalidArgs);
VerifyOrExit((message = NewMessage()) != nullptr, error = kErrorNoBufs);
switch (aRequest.GetType())
{
case kTypeConfirmable:
message->Init(kTypeAck, aCode);
message->SetMessageId(aRequest.GetMessageId());
break;
case kTypeNonConfirmable:
message->Init(kTypeNonConfirmable, aCode);
break;
default:
ExitNow(error = kErrorInvalidArgs);
OT_UNREACHABLE_CODE(break);
}
SuccessOrExit(error = message->SetTokenFromMessage(aRequest));
SuccessOrExit(error = SendMessage(*message, aMessageInfo));
exit:
FreeMessageOnError(message, error);
return error;
}
void CoapBase::HandleRetransmissionTimer(Timer &aTimer)
{
static_cast<Coap *>(static_cast<TimerMilliContext &>(aTimer).GetContext())->HandleRetransmissionTimer();
}
void CoapBase::HandleRetransmissionTimer(void)
{
TimeMilli now = TimerMilli::GetNow();
TimeMilli nextTime = now.GetDistantFuture();
Metadata metadata;
Ip6::MessageInfo messageInfo;
for (Message &message : mPendingRequests)
{
metadata.ReadFrom(message);
if (now >= metadata.mNextTimerShot)
{
#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
if (message.IsRequest() && metadata.mObserve && metadata.mAcknowledged)
{
// This is a RFC7641 subscription. Do not time out.
continue;
}
#endif
if (!metadata.mConfirmable || (metadata.mRetransmissionsRemaining == 0))
{
// No expected response or acknowledgment.
FinalizeCoapTransaction(message, metadata, nullptr, nullptr, kErrorResponseTimeout);
continue;
}
// Increment retransmission counter and timer.
metadata.mRetransmissionsRemaining--;
metadata.mRetransmissionTimeout *= 2;
metadata.mNextTimerShot = now + metadata.mRetransmissionTimeout;
metadata.UpdateIn(message);
// Retransmit
if (!metadata.mAcknowledged)
{
messageInfo.SetPeerAddr(metadata.mDestinationAddress);
messageInfo.SetPeerPort(metadata.mDestinationPort);
messageInfo.SetSockAddr(metadata.mSourceAddress);
#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
messageInfo.SetHopLimit(metadata.mHopLimit);
messageInfo.SetIsHostInterface(metadata.mIsHostInterface);
#endif
messageInfo.SetMulticastLoop(metadata.mMulticastLoop);
SendCopy(message, messageInfo);
}
}
if (nextTime > metadata.mNextTimerShot)
{
nextTime = metadata.mNextTimerShot;
}
}
if (nextTime < now.GetDistantFuture())
{
mRetransmissionTimer.FireAt(nextTime);
}
}
void CoapBase::FinalizeCoapTransaction(Message & aRequest,
const Metadata & aMetadata,
Message * aResponse,
const Ip6::MessageInfo *aMessageInfo,
Error aResult)
{
DequeueMessage(aRequest);
if (aMetadata.mResponseHandler != nullptr)
{
aMetadata.mResponseHandler(aMetadata.mResponseContext, aResponse, aMessageInfo, aResult);
}
}
Error CoapBase::AbortTransaction(ResponseHandler aHandler, void *aContext)
{
Error error = kErrorNotFound;
Metadata metadata;
for (Message &message : mPendingRequests)
{
metadata.ReadFrom(message);
if (metadata.mResponseHandler == aHandler && metadata.mResponseContext == aContext)
{
FinalizeCoapTransaction(message, metadata, nullptr, nullptr, kErrorAbort);
error = kErrorNone;
}
}
return error;
}
Message *CoapBase::CopyAndEnqueueMessage(const Message &aMessage, uint16_t aCopyLength, const Metadata &aMetadata)
{
Error error = kErrorNone;
Message *messageCopy = nullptr;
VerifyOrExit((messageCopy = aMessage.Clone(aCopyLength)) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = aMetadata.AppendTo(*messageCopy));
mRetransmissionTimer.FireAtIfEarlier(aMetadata.mNextTimerShot);
mPendingRequests.Enqueue(*messageCopy);
exit:
FreeAndNullMessageOnError(messageCopy, error);
return messageCopy;
}
void CoapBase::DequeueMessage(Message &aMessage)
{
mPendingRequests.Dequeue(aMessage);
if (mRetransmissionTimer.IsRunning() && (mPendingRequests.GetHead() == nullptr))
{
mRetransmissionTimer.Stop();
}
aMessage.Free();
// No need to worry that the earliest pending message was removed -
// the timer would just shoot earlier and then it'd be setup again.
}
#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
void CoapBase::FreeLastBlockResponse(void)
{
if (mLastResponse != nullptr)
{
mLastResponse->Free();
mLastResponse = nullptr;
}
}
Error CoapBase::CacheLastBlockResponse(Message *aResponse)
{
Error error = kErrorNone;
// Save last response for block-wise transfer
FreeLastBlockResponse();
if ((mLastResponse = aResponse->Clone()) == nullptr)
{
error = kErrorNoBufs;
}
return error;
}
Error CoapBase::PrepareNextBlockRequest(Message::BlockType aType,
bool aMoreBlocks,
Message & aRequestOld,
Message & aRequest,
Message & aMessage)
{
Error error = kErrorNone;
bool isOptionSet = false;
uint64_t optionBuf = 0;
uint16_t blockOption = 0;
Option::Iterator iterator;
blockOption = (aType == Message::kBlockType1) ? kOptionBlock1 : kOptionBlock2;
aRequest.Init(kTypeConfirmable, static_cast<ot::Coap::Code>(aRequestOld.GetCode()));
SuccessOrExit(error = iterator.Init(aRequestOld));
// Copy options from last response to next message
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 >= blockOption && !isOptionSet)
{
// Write Block1 option to next message
SuccessOrExit(error = aRequest.AppendBlockOption(aType, aMessage.GetBlockWiseBlockNumber() + 1, aMoreBlocks,
aMessage.GetBlockWiseBlockSize()));
aRequest.SetBlockWiseBlockNumber(aMessage.GetBlockWiseBlockNumber() + 1);
aRequest.SetBlockWiseBlockSize(aMessage.GetBlockWiseBlockSize());
aRequest.SetMoreBlocksFlag(aMoreBlocks);
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 = iterator.ReadOptionValue(&optionBuf));
SuccessOrExit(error = aRequest.AppendOption(optionNumber, iterator.GetOption()->GetLength(), &optionBuf));
}
if (!isOptionSet)
{
// Write Block1 option to next message
SuccessOrExit(error = aRequest.AppendBlockOption(aType, aMessage.GetBlockWiseBlockNumber() + 1, aMoreBlocks,
aMessage.GetBlockWiseBlockSize()));
aRequest.SetBlockWiseBlockNumber(aMessage.GetBlockWiseBlockNumber() + 1);
aRequest.SetBlockWiseBlockSize(aMessage.GetBlockWiseBlockSize());
aRequest.SetMoreBlocksFlag(aMoreBlocks);
}
exit:
return error;
}
Error CoapBase::SendNextBlock1Request(Message & aRequest,
Message & aMessage,
const Ip6::MessageInfo &aMessageInfo,
const Metadata & aCoapMetadata)
{
Error error = kErrorNone;
Message *request = nullptr;
bool moreBlocks = false;
uint8_t buf[kMaxBlockLength] = {0};
uint16_t bufLen = kMaxBlockLength;
SuccessOrExit(error = aRequest.ReadBlockOptionValues(kOptionBlock1));
SuccessOrExit(error = aMessage.ReadBlockOptionValues(kOptionBlock1));
// Conclude block-wise transfer if last block has been received
if (!aRequest.IsMoreBlocksFlagSet())
{
FinalizeCoapTransaction(aRequest, aCoapMetadata, &aMessage, &aMessageInfo, kErrorNone);
ExitNow();
}
// Get next block
VerifyOrExit((bufLen = otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize())) <= kMaxBlockLength,
error = kErrorNoBufs);
SuccessOrExit(
error = aCoapMetadata.mBlockwiseTransmitHook(aCoapMetadata.mResponseContext, buf,
otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()) *
(aMessage.GetBlockWiseBlockNumber() + 1),
&bufLen, &moreBlocks));
// Check if block length is valid
VerifyOrExit(bufLen <= otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()), error = kErrorInvalidArgs);
// Init request for next block
VerifyOrExit((request = NewMessage()) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = PrepareNextBlockRequest(Message::kBlockType1, moreBlocks, aRequest, *request, aMessage));
SuccessOrExit(error = request->SetPayloadMarker());
SuccessOrExit(error = request->AppendBytes(buf, bufLen));
DequeueMessage(aRequest);
LogInfo("Send Block1 Nr. %d, Size: %d bytes, More Blocks Flag: %d", request->GetBlockWiseBlockNumber(),
otCoapBlockSizeFromExponent(request->GetBlockWiseBlockSize()), request->IsMoreBlocksFlagSet());
SuccessOrExit(error = SendMessage(*request, aMessageInfo, TxParameters::GetDefault(),
aCoapMetadata.mResponseHandler, aCoapMetadata.mResponseContext,
aCoapMetadata.mBlockwiseTransmitHook, aCoapMetadata.mBlockwiseReceiveHook));
exit:
FreeMessageOnError(request, error);
return error;
}
Error CoapBase::SendNextBlock2Request(Message & aRequest,
Message & aMessage,
const Ip6::MessageInfo &aMessageInfo,
const Metadata & aCoapMetadata,
uint32_t aTotalLength,
bool aBeginBlock1Transfer)
{
Error error = kErrorNone;
Message *request = nullptr;
uint8_t buf[kMaxBlockLength] = {0};
uint16_t bufLen = kMaxBlockLength;
SuccessOrExit(error = aMessage.ReadBlockOptionValues(kOptionBlock2));
// Check payload and block length
VerifyOrExit((aMessage.GetLength() - aMessage.GetOffset()) <=
otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()) &&
(aMessage.GetLength() - aMessage.GetOffset()) <= kMaxBlockLength,
error = kErrorNoBufs);
// Read and then forward payload to receive hook function
bufLen = aMessage.ReadBytes(aMessage.GetOffset(), buf, aMessage.GetLength() - aMessage.GetOffset());
SuccessOrExit(
error = aCoapMetadata.mBlockwiseReceiveHook(aCoapMetadata.mResponseContext, buf,
otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()) *
aMessage.GetBlockWiseBlockNumber(),
bufLen, aMessage.IsMoreBlocksFlagSet(), aTotalLength));
// CoAP Block-Wise Transfer continues
LogInfo("Received Block2 Nr. %d , Size: %d bytes, More Blocks Flag: %d", aMessage.GetBlockWiseBlockNumber(),
otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()), aMessage.IsMoreBlocksFlagSet());
// Conclude block-wise transfer if last block has been received
if (!aMessage.IsMoreBlocksFlagSet())
{
FinalizeCoapTransaction(aRequest, aCoapMetadata, &aMessage, &aMessageInfo, kErrorNone);
ExitNow();
}
// Init request for next block
VerifyOrExit((request = NewMessage()) != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = PrepareNextBlockRequest(Message::kBlockType2, aMessage.IsMoreBlocksFlagSet(), aRequest,
*request, aMessage));
if (!aBeginBlock1Transfer)
{
DequeueMessage(aRequest);
}
LogInfo("Request Block2 Nr. %d, Size: %d bytes", request->GetBlockWiseBlockNumber(),
otCoapBlockSizeFromExponent(request->GetBlockWiseBlockSize()));
SuccessOrExit(error =
SendMessage(*request, aMessageInfo, TxParameters::GetDefault(), aCoapMetadata.mResponseHandler,
aCoapMetadata.mResponseContext, nullptr, aCoapMetadata.mBlockwiseReceiveHook));
exit:
FreeMessageOnError(request, error);
return error;
}
Error CoapBase::ProcessBlock1Request(Message & aMessage,
const Ip6::MessageInfo & aMessageInfo,
const ResourceBlockWise &aResource,
uint32_t aTotalLength)
{
Error error = kErrorNone;
Message *response = nullptr;
uint8_t buf[kMaxBlockLength] = {0};
uint16_t bufLen = kMaxBlockLength;
SuccessOrExit(error = aMessage.ReadBlockOptionValues(kOptionBlock1));
// Read and then forward payload to receive hook function
VerifyOrExit((aMessage.GetLength() - aMessage.GetOffset()) <= kMaxBlockLength, error = kErrorNoBufs);
bufLen = aMessage.ReadBytes(aMessage.GetOffset(), buf, aMessage.GetLength() - aMessage.GetOffset());
SuccessOrExit(error = aResource.HandleBlockReceive(buf,
otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()) *
aMessage.GetBlockWiseBlockNumber(),
bufLen, aMessage.IsMoreBlocksFlagSet(), aTotalLength));
if (aMessage.IsMoreBlocksFlagSet())
{
// Set up next response
VerifyOrExit((response = NewMessage()) != nullptr, error = kErrorFailed);
response->Init(kTypeAck, kCodeContinue);
response->SetMessageId(aMessage.GetMessageId());
IgnoreReturnValue(response->SetToken(AsConst(aMessage).GetToken(), aMessage.GetTokenLength()));
response->SetBlockWiseBlockNumber(aMessage.GetBlockWiseBlockNumber());
response->SetMoreBlocksFlag(aMessage.IsMoreBlocksFlagSet());
response->SetBlockWiseBlockSize(aMessage.GetBlockWiseBlockSize());
SuccessOrExit(error = response->AppendBlockOption(Message::kBlockType1, response->GetBlockWiseBlockNumber(),
response->IsMoreBlocksFlagSet(),
response->GetBlockWiseBlockSize()));
SuccessOrExit(error = CacheLastBlockResponse(response));
LogInfo("Acknowledge Block1 Nr. %d, Size: %d bytes", response->GetBlockWiseBlockNumber(),
otCoapBlockSizeFromExponent(response->GetBlockWiseBlockSize()));
SuccessOrExit(error = SendMessage(*response, aMessageInfo));
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(Message & aMessage,
const Ip6::MessageInfo & aMessageInfo,
const ResourceBlockWise &aResource)
{
Error error = kErrorNone;
Message * response = nullptr;
uint8_t buf[kMaxBlockLength] = {0};
uint16_t bufLen = kMaxBlockLength;
bool moreBlocks = false;
uint64_t optionBuf = 0;
Option::Iterator iterator;
SuccessOrExit(error = aMessage.ReadBlockOptionValues(kOptionBlock2));
LogInfo("Request for Block2 Nr. %d, Size: %d bytes received", aMessage.GetBlockWiseBlockNumber(),
otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()));
if (aMessage.GetBlockWiseBlockNumber() == 0)
{
aResource.HandleRequest(aMessage, aMessageInfo);
ExitNow();
}
// Set up next response
VerifyOrExit((response = NewMessage()) != nullptr, error = kErrorNoBufs);
response->Init(kTypeAck, kCodeContent);
response->SetMessageId(aMessage.GetMessageId());
VerifyOrExit((bufLen = otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize())) <= kMaxBlockLength,
error = kErrorNoBufs);
SuccessOrExit(error = aResource.HandleBlockTransmit(buf,
otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()) *
aMessage.GetBlockWiseBlockNumber(),
&bufLen, &moreBlocks));
response->SetMoreBlocksFlag(moreBlocks);
if (moreBlocks)
{
switch (bufLen)
{
case 1024:
response->SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_1024);
break;
case 512:
response->SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_512);
break;
case 256:
response->SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_256);
break;
case 128:
response->SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_128);
break;
case 64:
response->SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_64);
break;
case 32:
response->SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_32);
break;
case 16:
response->SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_16);
break;
default:
error = kErrorInvalidArgs;
ExitNow();
break;
}
}
else
{
// Verify that buffer length is not larger than requested block size
VerifyOrExit(bufLen <= otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()),
error = kErrorInvalidArgs);
response->SetBlockWiseBlockSize(aMessage.GetBlockWiseBlockSize());
}
response->SetBlockWiseBlockNumber(
(otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()) * aMessage.GetBlockWiseBlockNumber()) /
(otCoapBlockSizeFromExponent(response->GetBlockWiseBlockSize())));
// 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(Message::kBlockType2, response->GetBlockWiseBlockNumber(),
response->IsMoreBlocksFlagSet(),
response->GetBlockWiseBlockSize()));
}
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->SetPayloadMarker());
SuccessOrExit(error = response->AppendBytes(buf, bufLen));
if (response->IsMoreBlocksFlagSet())
{
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", response->GetBlockWiseBlockNumber(),
otCoapBlockSizeFromExponent(response->GetBlockWiseBlockSize()), response->IsMoreBlocksFlagSet());
SuccessOrExit(error = SendMessage(*response, aMessageInfo));
exit:
FreeMessageOnError(response, error);
return error;
}
#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
void CoapBase::SendCopy(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
Error error;
Message *messageCopy = nullptr;
// Create a message copy for lower layers.
messageCopy = aMessage.Clone(aMessage.GetLength() - sizeof(Metadata));
VerifyOrExit(messageCopy != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = Send(*messageCopy, aMessageInfo));
exit:
if (error != kErrorNone)
{
LogWarn("Failed to send copy: %s", ErrorToString(error));
FreeMessage(messageCopy);
}
}
Message *CoapBase::FindRelatedRequest(const Message & aResponse,
const Ip6::MessageInfo &aMessageInfo,
Metadata & aMetadata)
{
Message *request = nullptr;
for (Message &message : mPendingRequests)
{
aMetadata.ReadFrom(message);
if (((aMetadata.mDestinationAddress == aMessageInfo.GetPeerAddr()) ||
aMetadata.mDestinationAddress.IsMulticast() ||
aMetadata.mDestinationAddress.GetIid().IsAnycastLocator()) &&
(aMetadata.mDestinationPort == aMessageInfo.GetPeerPort()))
{
switch (aResponse.GetType())
{
case kTypeReset:
case kTypeAck:
if (aResponse.GetMessageId() == message.GetMessageId())
{
request = &message;
ExitNow();
}
break;
case kTypeConfirmable:
case kTypeNonConfirmable:
if (aResponse.IsTokenEqual(message))
{
request = &message;
ExitNow();
}
break;
}
}
}
exit:
return request;
}
void CoapBase::Receive(ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
Message &message = AsCoapMessage(&aMessage);
if (message.ParseHeader() != kErrorNone)
{
LogDebg("Failed to parse CoAP header");
if (!aMessageInfo.GetSockAddr().IsMulticast() && message.IsConfirmable())
{
IgnoreError(SendReset(message, aMessageInfo));
}
}
else if (message.IsRequest())
{
ProcessReceivedRequest(message, aMessageInfo);
}
else
{
ProcessReceivedResponse(message, aMessageInfo);
}
#if OPENTHREAD_CONFIG_OTNS_ENABLE
Get<Utils::Otns>().EmitCoapReceive(message, aMessageInfo);
#endif
}
void CoapBase::ProcessReceivedResponse(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
Metadata metadata;
Message *request = nullptr;
Error error = kErrorNone;
#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
bool responseObserve = false;
#endif
#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
uint8_t blockOptionType = 0;
uint32_t totalTransfereSize = 0;
#endif
request = FindRelatedRequest(aMessage, aMessageInfo, metadata);
VerifyOrExit(request != nullptr);
#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
if (metadata.mObserve && request->IsRequest())
{
// We sent Observe in our request, see if we received Observe in the response too.
Option::Iterator iterator;
SuccessOrExit(error = iterator.Init(aMessage, kOptionObserve));
responseObserve = !iterator.IsDone();
}
#endif
switch (aMessage.GetType())
{
case kTypeReset:
if (aMessage.IsEmpty())
{
FinalizeCoapTransaction(*request, metadata, nullptr, nullptr, kErrorAbort);
}
// Silently ignore non-empty reset messages (RFC 7252, p. 4.2).
break;
case kTypeAck:
if (aMessage.IsEmpty())
{
// Empty acknowledgment.
#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
if (metadata.mObserve && !request->IsRequest())
{
// This is the ACK to our RFC7641 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.
FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, kErrorNone);
}
else
#endif
{
// This is not related to RFC7641 or the outgoing "request" was not a
// notification.
if (metadata.mConfirmable)
{
metadata.mAcknowledged = true;
metadata.UpdateIn(*request);
}
// Remove the message if response is not expected, otherwise await
// response.
if (metadata.mResponseHandler == nullptr)
{
DequeueMessage(*request);
}
}
}
else if (aMessage.IsResponse() && aMessage.IsTokenEqual(*request))
{
// Piggybacked response. 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 OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
if (metadata.mObserve && responseObserve && (metadata.mResponseHandler != nullptr))
{
// This is a RFC7641 notification. The request is *not* done!
metadata.mResponseHandler(metadata.mResponseContext, &aMessage, &aMessageInfo, kErrorNone);
// Consider the message acknowledged at this point.
metadata.mAcknowledged = true;
metadata.UpdateIn(*request);
}
else
#endif
#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
{
if (metadata.mBlockwiseTransmitHook != nullptr || metadata.mBlockwiseReceiveHook != nullptr)
{
// Search for CoAP Block-Wise Option [RFC7959]
Option::Iterator iterator;
SuccessOrExit(error = iterator.Init(aMessage));
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
totalTransfereSize = 0;
break;
default:
break;
}
SuccessOrExit(error = iterator.Advance());
}
}
switch (blockOptionType)
{
case 0:
// Piggybacked response.
FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, kErrorNone);
break;
case 1: // Block1 option
if (aMessage.GetCode() == kCodeContinue && metadata.mBlockwiseTransmitHook != nullptr)
{
error = SendNextBlock1Request(*request, aMessage, aMessageInfo, metadata);
}
if (aMessage.GetCode() != kCodeContinue || metadata.mBlockwiseTransmitHook == nullptr ||
error != kErrorNone)
{
FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, error);
}
break;
case 2: // Block2 option
if (aMessage.GetCode() < kCodeBadRequest && metadata.mBlockwiseReceiveHook != nullptr)
{
error = SendNextBlock2Request(*request, aMessage, aMessageInfo, metadata, totalTransfereSize,
false);
}
if (aMessage.GetCode() >= kCodeBadRequest || metadata.mBlockwiseReceiveHook == nullptr ||
error != kErrorNone)
{
FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, error);
}
break;
case 3: // Block1 & Block2 option
if (aMessage.GetCode() < kCodeBadRequest && metadata.mBlockwiseReceiveHook != nullptr)
{
error =
SendNextBlock2Request(*request, aMessage, aMessageInfo, metadata, totalTransfereSize, true);
}
FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, error);
break;
default:
error = kErrorAbort;
FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, error);
break;
}
}
#else // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
{
FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, kErrorNone);
}
#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
}
// Silently ignore acknowledgments carrying requests (RFC 7252, p. 4.2)
// or with no token match (RFC 7252, p. 5.3.2)
break;
case kTypeConfirmable:
// Send empty ACK if it is a CON message.
IgnoreError(SendAck(aMessage, aMessageInfo));
OT_FALL_THROUGH;
// Handling of RFC7641 and multicast is below.
case kTypeNonConfirmable:
// Separate response or observation notification. If the request was to a multicast
// address, OR both the request and response carry Observe options, then this is NOT
// the final message, we may see multiples.
if ((metadata.mResponseHandler != nullptr) && (metadata.mDestinationAddress.IsMulticast()
#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
|| (metadata.mObserve && responseObserve)
#endif
))
{
// If multicast non-confirmable request, allow multiple responses
metadata.mResponseHandler(metadata.mResponseContext, &aMessage, &aMessageInfo, kErrorNone);
}
else
{
FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, kErrorNone);
}
break;
}
exit:
if (error == kErrorNone && request == nullptr)
{
if (aMessage.IsConfirmable() || aMessage.IsNonConfirmable())
{
// Successfully parsed a header but no matching request was
// found - reject the message by sending reset.
IgnoreError(SendReset(aMessage, aMessageInfo));
}
}
}
void CoapBase::ProcessReceivedRequest(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
char uriPath[Message::kMaxReceivedUriPath + 1];
Message *cachedResponse = nullptr;
Error error = kErrorNotFound;
#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
Option::Iterator iterator;
char * curUriPath = uriPath;
uint8_t blockOptionType = 0;
uint32_t totalTransfereSize = 0;
#endif
if (mInterceptor != nullptr)
{
SuccessOrExit(error = mInterceptor(aMessage, aMessageInfo, mContext));
}
switch (mResponsesQueue.GetMatchedResponseCopy(aMessage, aMessageInfo, &cachedResponse))
{
case kErrorNone:
cachedResponse->Finish();
error = Send(*cachedResponse, aMessageInfo);
OT_FALL_THROUGH;
case kErrorNoBufs:
ExitNow();
case kErrorNotFound:
default:
break;
}
#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
SuccessOrExit(error = iterator.Init(aMessage));
while (!iterator.IsDone())
{
switch (iterator.GetOption()->GetNumber())
{
case kOptionUriPath:
if (curUriPath != uriPath)
{
*curUriPath++ = '/';
}
VerifyOrExit(curUriPath + iterator.GetOption()->GetLength() < GetArrayEnd(uriPath), error = kErrorParse);
IgnoreError(iterator.ReadOptionValue(curUriPath));
curUriPath += iterator.GetOption()->GetLength();
break;
case kOptionBlock1:
blockOptionType += 1;
break;
case kOptionBlock2:
blockOptionType += 2;
break;
case kOptionSize1:
// ToDo: wait for method to read uint option values
totalTransfereSize = 0;
break;
default:
break;
}
SuccessOrExit(error = iterator.Advance());
}
curUriPath[0] = '\0';
for (const ResourceBlockWise &resource : mBlockWiseResources)
{
if (strcmp(resource.GetUriPath(), uriPath) != 0)
{
continue;
}
if ((resource.mReceiveHook != nullptr || resource.mTransmitHook != nullptr) && blockOptionType != 0)
{
switch (blockOptionType)
{
case 1:
if (resource.mReceiveHook != nullptr)
{
switch (ProcessBlock1Request(aMessage, aMessageInfo, resource, totalTransfereSize))
{
case kErrorNone:
resource.HandleRequest(aMessage, aMessageInfo);
// Fall through
case kErrorBusy:
error = kErrorNone;
break;
case kErrorNoBufs:
IgnoreReturnValue(SendHeaderResponse(kCodeRequestTooLarge, aMessage, aMessageInfo));
error = kErrorDrop;
break;
case kErrorNoFrameReceived:
IgnoreReturnValue(SendHeaderResponse(kCodeRequestIncomplete, aMessage, aMessageInfo));
error = kErrorDrop;
break;
default:
IgnoreReturnValue(SendHeaderResponse(kCodeInternalError, aMessage, aMessageInfo));
error = kErrorDrop;
break;
}
}
break;
case 2:
if (resource.mTransmitHook != nullptr)
{
if ((error = ProcessBlock2Request(aMessage, aMessageInfo, resource)) != kErrorNone)
{
IgnoreReturnValue(SendHeaderResponse(kCodeInternalError, aMessage, aMessageInfo));
error = kErrorDrop;
}
}
break;
}
ExitNow();
}
else
{
resource.HandleRequest(aMessage, aMessageInfo);
error = kErrorNone;
ExitNow();
}
}
#else
SuccessOrExit(error = aMessage.ReadUriPathOptions(uriPath));
#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
for (const Resource &resource : mResources)
{
if (strcmp(resource.mUriPath, uriPath) == 0)
{
resource.HandleRequest(aMessage, aMessageInfo);
error = kErrorNone;
ExitNow();
}
}
if (mDefaultHandler)
{
mDefaultHandler(mDefaultHandlerContext, &aMessage, &aMessageInfo);
error = kErrorNone;
}
exit:
if (error != kErrorNone)
{
LogInfo("Failed to process request: %s", ErrorToString(error));
if (error == kErrorNotFound && !aMessageInfo.GetSockAddr().IsMulticast())
{
IgnoreError(SendNotFound(aMessage, aMessageInfo));
}
FreeMessage(cachedResponse);
}
}
void CoapBase::Metadata::ReadFrom(const Message &aMessage)
{
uint16_t length = aMessage.GetLength();
OT_ASSERT(length >= sizeof(*this));
IgnoreError(aMessage.Read(length - sizeof(*this), *this));
}
void CoapBase::Metadata::UpdateIn(Message &aMessage) const
{
aMessage.Write(aMessage.GetLength() - sizeof(*this), *this);
}
ResponsesQueue::ResponsesQueue(Instance &aInstance)
: mTimer(aInstance, ResponsesQueue::HandleTimer, this)
{
}
Error ResponsesQueue::GetMatchedResponseCopy(const Message & aRequest,
const Ip6::MessageInfo &aMessageInfo,
Message ** aResponse)
{
Error error = kErrorNone;
const Message *cacheResponse;
cacheResponse = FindMatchedResponse(aRequest, aMessageInfo);
VerifyOrExit(cacheResponse != nullptr, error = kErrorNotFound);
*aResponse = cacheResponse->Clone(cacheResponse->GetLength() - sizeof(ResponseMetadata));
VerifyOrExit(*aResponse != nullptr, error = kErrorNoBufs);
exit:
return error;
}
const Message *ResponsesQueue::FindMatchedResponse(const Message &aRequest, const Ip6::MessageInfo &aMessageInfo) const
{
const Message *response = nullptr;
for (const Message &message : mQueue)
{
if (message.GetMessageId() == aRequest.GetMessageId())
{
ResponseMetadata metadata;
metadata.ReadFrom(message);
if ((metadata.mMessageInfo.GetPeerPort() == aMessageInfo.GetPeerPort()) &&
(metadata.mMessageInfo.GetPeerAddr() == aMessageInfo.GetPeerAddr()))
{
response = &message;
break;
}
}
}
return response;
}
void ResponsesQueue::EnqueueResponse(Message & aMessage,
const Ip6::MessageInfo &aMessageInfo,
const TxParameters & aTxParameters)
{
Message * responseCopy;
ResponseMetadata metadata;
metadata.mDequeueTime = TimerMilli::GetNow() + aTxParameters.CalculateExchangeLifetime();
metadata.mMessageInfo = aMessageInfo;
VerifyOrExit(FindMatchedResponse(aMessage, aMessageInfo) == nullptr);
UpdateQueue();
VerifyOrExit((responseCopy = aMessage.Clone()) != nullptr);
VerifyOrExit(metadata.AppendTo(*responseCopy) == kErrorNone, responseCopy->Free());
mQueue.Enqueue(*responseCopy);
mTimer.FireAtIfEarlier(metadata.mDequeueTime);
exit:
return;
}
void ResponsesQueue::UpdateQueue(void)
{
uint16_t msgCount = 0;
Message * earliestMsg = nullptr;
TimeMilli earliestDequeueTime(0);
// Check the number of messages in the queue and if number is at
// `kMaxCachedResponses` remove the one with earliest dequeue
// time.
for (Message &message : mQueue)
{
ResponseMetadata metadata;
metadata.ReadFrom(message);
if ((earliestMsg == nullptr) || (metadata.mDequeueTime < earliestDequeueTime))
{
earliestMsg = &message;
earliestDequeueTime = metadata.mDequeueTime;
}
msgCount++;
}
if (msgCount >= kMaxCachedResponses)
{
DequeueResponse(*earliestMsg);
}
}
void ResponsesQueue::DequeueResponse(Message &aMessage)
{
mQueue.DequeueAndFree(aMessage);
}
void ResponsesQueue::DequeueAllResponses(void)
{
mQueue.DequeueAndFreeAll();
}
void ResponsesQueue::HandleTimer(Timer &aTimer)
{
static_cast<ResponsesQueue *>(static_cast<TimerMilliContext &>(aTimer).GetContext())->HandleTimer();
}
void ResponsesQueue::HandleTimer(void)
{
TimeMilli now = TimerMilli::GetNow();
TimeMilli nextDequeueTime = now.GetDistantFuture();
for (Message &message : mQueue)
{
ResponseMetadata metadata;
metadata.ReadFrom(message);
if (now >= metadata.mDequeueTime)
{
DequeueResponse(message);
continue;
}
if (metadata.mDequeueTime < nextDequeueTime)
{
nextDequeueTime = metadata.mDequeueTime;
}
}
if (nextDequeueTime < now.GetDistantFuture())
{
mTimer.FireAt(nextDequeueTime);
}
}
void ResponsesQueue::ResponseMetadata::ReadFrom(const Message &aMessage)
{
uint16_t length = aMessage.GetLength();
OT_ASSERT(length >= sizeof(*this));
IgnoreError(aMessage.Read(length - sizeof(*this), *this));
}
/// Return product of @p aValueA and @p aValueB if no overflow otherwise 0.
static uint32_t Multiply(uint32_t aValueA, uint32_t aValueB)
{
uint32_t result = 0;
VerifyOrExit(aValueA);
result = aValueA * aValueB;
result = (result / aValueA == aValueB) ? result : 0;
exit:
return result;
}
bool TxParameters::IsValid(void) const
{
bool rval = false;
if ((mAckRandomFactorDenominator > 0) && (mAckRandomFactorNumerator >= mAckRandomFactorDenominator) &&
(mAckTimeout >= OT_COAP_MIN_ACK_TIMEOUT) && (mMaxRetransmit <= OT_COAP_MAX_RETRANSMIT))
{
// Calulate exchange lifetime step by step and verify no overflow.
uint32_t tmp = Multiply(mAckTimeout, (1U << (mMaxRetransmit + 1)) - 1);
tmp = Multiply(tmp, mAckRandomFactorNumerator);
tmp /= mAckRandomFactorDenominator;
rval = (tmp != 0 && (tmp + mAckTimeout + 2 * kDefaultMaxLatency) > tmp);
}
return rval;
}
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);
}
const otCoapTxParameters TxParameters::kDefaultTxParameters = {
kDefaultAckTimeout,
kDefaultAckRandomFactorNumerator,
kDefaultAckRandomFactorDenominator,
kDefaultMaxRetransmit,
};
Coap::Coap(Instance &aInstance)
: CoapBase(aInstance, &Coap::Send)
, mSocket(aInstance)
{
}
Error Coap::Start(uint16_t aPort, otNetifIdentifier aNetifIdentifier)
{
Error error = kErrorNone;
bool socketOpened = false;
VerifyOrExit(!mSocket.IsBound());
SuccessOrExit(error = mSocket.Open(&Coap::HandleUdpReceive, this));
socketOpened = true;
SuccessOrExit(error = mSocket.Bind(aPort, aNetifIdentifier));
exit:
if (error != kErrorNone && socketOpened)
{
IgnoreError(mSocket.Close());
}
return error;
}
Error Coap::Stop(void)
{
Error error = kErrorNone;
VerifyOrExit(mSocket.IsBound());
SuccessOrExit(error = mSocket.Close());
ClearRequestsAndResponses();
exit:
return error;
}
void Coap::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
{
static_cast<Coap *>(aContext)->Receive(AsCoapMessage(aMessage), AsCoreType(aMessageInfo));
}
Error Coap::Send(CoapBase &aCoapBase, ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
return static_cast<Coap &>(aCoapBase).Send(aMessage, aMessageInfo);
}
Error Coap::Send(ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
return mSocket.IsBound() ? mSocket.SendTo(aMessage, aMessageInfo) : kErrorInvalidState;
}
} // namespace Coap
} // namespace ot