From c007599107ec76710d88d655e9c1cc4aaadf3322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Z=C3=BCrcher?= Date: Fri, 29 Aug 2025 23:21:32 +0300 Subject: [PATCH] [coap] add `otCoapSetResponseFallback` to process unmatched responses (#11583) Adding two extensions: 1. Add an api function to configure a response fallback callback with `otCoapSetResponseFallback`. 2. Enable fire and forget for NON requests, supporting requests which do not expect a response. --- include/openthread/coap.h | 22 ++++++++++++++++++++++ include/openthread/instance.h | 2 +- src/core/api/coap_api.cpp | 5 +++++ src/core/coap/coap.cpp | 17 ++++++++++++++--- src/core/coap/coap.hpp | 31 +++++++++++++++++++++++++++++-- src/core/coap/coap_message.hpp | 11 +++++++++++ 6 files changed, 82 insertions(+), 6 deletions(-) diff --git a/include/openthread/coap.h b/include/openthread/coap.h index 2ceffb8e5..c0293a335 100644 --- a/include/openthread/coap.h +++ b/include/openthread/coap.h @@ -352,6 +352,18 @@ typedef void (*otCoapResponseHandler)(void *aContext, */ typedef void (*otCoapRequestHandler)(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo); +/** + * Pointer is called as a fallback if a response did not match a stored CoAP request. + * + * @param[in] aContext A pointer to arbitrary context information. + * @param[in] aMessage A pointer to the message. + * @param[in] aMessageInfo A pointer to the message info for @p aMessage. + * + * @retval TRUE The fallback handled the response. + * @retval FALSE OpenThread takes default actions for response. + */ +typedef bool (*otCoapResponseFallback)(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo); + /** * Pointer is called when a CoAP message with a block-wise transfer option is received. * @@ -1012,6 +1024,16 @@ void otCoapRemoveBlockWiseResource(otInstance *aInstance, otCoapBlockwiseResourc */ void otCoapSetDefaultHandler(otInstance *aInstance, otCoapRequestHandler aHandler, void *aContext); +/** + * Sets a fallback handler for CoAP responses not matching any active/pending request. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aHandler A function pointer that shall be called as a fallback for responses without matching + * active/pending CoAP requests. + * @param[in] aContext A pointer to arbitrary context information. May be NULL if not used. + */ +void otCoapSetResponseFallback(otInstance *aInstance, otCoapResponseFallback aHandler, void *aContext); + /** * Sends a CoAP response from the server with custom transmission parameters. * diff --git a/include/openthread/instance.h b/include/openthread/instance.h index eb59a917c..edf47d572 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -52,7 +52,7 @@ extern "C" { * * @note This number versions both OpenThread platform and user APIs. */ -#define OPENTHREAD_API_VERSION (530) +#define OPENTHREAD_API_VERSION (531) /** * @addtogroup api-instance diff --git a/src/core/api/coap_api.cpp b/src/core/api/coap_api.cpp index f3fad3697..780153be1 100644 --- a/src/core/api/coap_api.cpp +++ b/src/core/api/coap_api.cpp @@ -287,6 +287,11 @@ void otCoapSetDefaultHandler(otInstance *aInstance, otCoapRequestHandler aHandle AsCoreType(aInstance).Get().SetDefaultHandler(aHandler, aContext); } +void otCoapSetResponseFallback(otInstance *aInstance, otCoapResponseFallback aHandler, void *aContext) +{ + AsCoreType(aInstance).Get().SetResponseFallback(aHandler, aContext); +} + #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE otError otCoapSendResponseBlockWiseWithParameters(otInstance *aInstance, otMessage *aMessage, diff --git a/src/core/coap/coap.cpp b/src/core/coap/coap.cpp index 778863d70..5d13b57b7 100644 --- a/src/core/coap/coap.cpp +++ b/src/core/coap/coap.cpp @@ -209,6 +209,12 @@ Error CoapBase::SendMessage(Message &aMessage, bool moreBlocks = false; #endif + // fire and forget (mAckTimeout=0) is only allowed for Non-confirmable (NON) messages + if (aTxParameters.mAckTimeout == 0) + { + VerifyOrExit(aMessage.IsNonConfirmable(), error = kErrorInvalidArgs); + } + switch (aMessage.GetType()) { case kTypeAck: @@ -1287,7 +1293,7 @@ exit: if (error == kErrorNone && request == nullptr) { - if (aMessage.IsConfirmable() || aMessage.IsNonConfirmable()) + if (!InvokeResponseFallback(aMessage, aMessageInfo) && aMessage.RequireResetOnError()) { // Successfully parsed a header but no matching request was // found - reject the message by sending reset. @@ -1626,8 +1632,13 @@ bool TxParameters::IsValid(void) const { bool rval = false; - if ((mAckRandomFactorDenominator > 0) && (mAckRandomFactorNumerator >= mAckRandomFactorDenominator) && - (mAckTimeout >= OT_COAP_MIN_ACK_TIMEOUT) && (mMaxRetransmit <= OT_COAP_MAX_RETRANSMIT)) + // support fire and forget requests + if (mAckTimeout == 0) + { + rval = true; + } + else if ((mAckRandomFactorDenominator > 0) && (mAckRandomFactorNumerator >= mAckRandomFactorDenominator) && + (mAckTimeout >= OT_COAP_MIN_ACK_TIMEOUT) && (mMaxRetransmit <= OT_COAP_MAX_RETRANSMIT)) { // Calculate exchange lifetime step by step and verify no overflow. uint32_t tmp = Multiply(mAckTimeout, (1U << (mMaxRetransmit + 1)) - 1); diff --git a/src/core/coap/coap.hpp b/src/core/coap/coap.hpp index b03ca6cd3..1ee0a3ff5 100644 --- a/src/core/coap/coap.hpp +++ b/src/core/coap/coap.hpp @@ -77,6 +77,13 @@ typedef otCoapResponseHandler ResponseHandler; */ typedef otCoapRequestHandler RequestHandler; +/** + * Represents a function pointer which is called when a CoAP response is not associated with a sent request. + * + * Please see otCoapResponseFallback for details. + */ +typedef otCoapResponseFallback ResponseFallback; + /** * Represents the CoAP transmission parameters. */ @@ -400,13 +407,23 @@ public: */ void RemoveResource(Resource &aResource); - /* Sets the default handler for unhandled CoAP requests. + /** + * Sets the default handler for unhandled CoAP requests. * * @param[in] aHandler A function pointer that shall be called when an unhandled request arrives. * @param[in] aContext A pointer to arbitrary context information. May be `nullptr` if not used. */ void SetDefaultHandler(RequestHandler aHandler, void *aContext) { mDefaultHandler.Set(aHandler, aContext); } + /** + * Sets a fallback handler for CoAP responses not matching any active/pending request. + * + * @param[in] aHandler A function pointer that shall be called as a fallback for responses that do not match any + * pending request. + * @param[in] aContext A pointer to arbitrary context information. May be `nullptr` if not used. + */ + void SetResponseFallback(ResponseFallback aHandler, void *aContext) { mResponseFallback.Set(aHandler, aContext); } + /** * Allocates a new message with a CoAP header. * @@ -831,6 +848,15 @@ private: const Ip6::MessageInfo *aMessageInfo, Error aResult); + inline bool InvokeResponseFallback(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) + { + if (mResponseFallback.IsSet()) + { + return mResponseFallback.Invoke(&aMessage, &aMessageInfo); + } + return false; + } + #if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE void FreeLastBlockResponse(void); Error CacheLastBlockResponse(Message *aResponse); @@ -877,7 +903,8 @@ private: Callback mInterceptor; ResponsesQueue mResponsesQueue; - Callback mDefaultHandler; + Callback mDefaultHandler; + Callback mResponseFallback; ResourceHandler mResourceHandler; diff --git a/src/core/coap/coap_message.hpp b/src/core/coap/coap_message.hpp index 7ed8bdf26..ec3ffbc21 100644 --- a/src/core/coap/coap_message.hpp +++ b/src/core/coap/coap_message.hpp @@ -734,6 +734,17 @@ public: */ bool IsNonConfirmablePostRequest(void) const; + /** + * Checks if the message requires an reset response if an error during low level CoAP processing occurred. + * + * A reset message is expected to be sent for NON and CON messages if the message can not be processed or a + * duplicated message has been received. + * + * @retval TRUE Expect to respond with CoAP reset message on error. + * @retval FALSE No CoAP reset message should be sent on error. + */ + bool RequireResetOnError(void) { return IsConfirmable() || IsNonConfirmable(); } + /** * Creates a copy of this CoAP message. *