From 84295be3f872bbfa6cc47db3e002caa897addf2c Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Tue, 24 Jun 2025 09:37:15 -0700 Subject: [PATCH] [message] add `TxCallback` to track transmission outcome (#11614) This commit introduces new public APIs to register a `TxCallback` on a message to be notified of its transmission outcome. The callback is invoked with an error code indicating the transmission status of the IPv6 message to an immediate neighbor (a one-hop transmission). It does not indicate that the message was received by its final, multi-hop destination. For a unicast IPv6 message, a success (`OT_ERROR_NONE`) indicates that the message, including all its corresponding fragments if applicable, was successfully delivered to the immediate neighbor and a MAC layer acknowledgment was received for all fragments. This is reported regardless of whether the message is sent using direct or indirect transmission (e.g., to a sleepy child via CSL or a data poll). For a multicast message, an `OT_ERROR_NONE` status indicates that the message and all its fragments were successfully broadcast. Note that no MAC-level acknowledgment is required for a broadcast frame transmission. --- include/openthread/instance.h | 2 +- include/openthread/message.h | 47 +++++++++++++++++++++++++++++ src/core/api/message_api.cpp | 7 +++++ src/core/common/message.cpp | 27 ++++++++++++++++- src/core/common/message.hpp | 33 ++++++++++++++++++++ src/core/thread/indirect_sender.cpp | 2 ++ src/core/thread/mesh_forwarder.cpp | 2 ++ 7 files changed, 118 insertions(+), 2 deletions(-) diff --git a/include/openthread/instance.h b/include/openthread/instance.h index c7cdd16dc..ae8d3cb42 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 (513) +#define OPENTHREAD_API_VERSION (514) /** * @addtogroup api-instance diff --git a/include/openthread/message.h b/include/openthread/message.h index 1397915a2..474d65532 100644 --- a/include/openthread/message.h +++ b/include/openthread/message.h @@ -108,6 +108,15 @@ typedef struct otThreadLinkInfo uint8_t mRadioType; ///< Radio link type. } otThreadLinkInfo; +/** + * Gets the `otInstance` associated with a given message. + * + * @param[in] aMessage A message. + * + * @returns The `otInstance` associated with @p aMessage. + */ +otInstance *otMessageGetInstance(const otMessage *aMessage); + /** * Free an allocated message buffer. * @@ -288,6 +297,44 @@ int8_t otMessageGetRss(const otMessage *aMessage); */ otError otMessageGetThreadLinkInfo(const otMessage *aMessage, otThreadLinkInfo *aLinkInfo); +/** + * Represents the callback function pointer to notify the transmission outcome (success or failure) of a message. + * + * The error indicates the transmission status of the IPv6 message from this device to an immediate neighbor (one-hop + * transmission). It doesn't indicate that the message is received by its final intended destination (multi-hop away). + * + * For a unicast IPv6 message, an `OT_ERROR_NONE` error indicates that the message (all its corresponding fragment + * frames if the message is larger and requires fragmentation) was successfully delivered to the immediate neighbor, + * and a MAC layer acknowledgment was received for all fragments. This is reported regardless of whether the message + * is sent using direct TX or indirect TX (to a sleepy child using CSL or data poll triggered TX). + * + * For a multicast message, an `OT_ERROR_NONE` status indicates that the message (all its fragment frames) was + * successfully broadcast. Note that no MAC-level acknowledgment is required for broadcast frame TX. + * + * The OpenThread stack may alter the content of the message as it is prepared for transmission (e.g., IPv6 headers + * may be prepended, or additional metadata appended at the end). So, the content of @p aMessage when this callback + * is invoked may differ from its original content (e.g., when it was given as input in `otIp6Send()` for transmission). + * + * @param[in] aMessage A pointer to the message. + * @param[in] aError The TX error when sending the message. + * @param[in] aContext A pointer to the user-provided context when the callback was registered. + */ +typedef void (*otMessageTxCallback)(const otMessage *aMessage, otError aError, void *aContext); + +/** + * Registers a callback to be notified of a message's transmission outcome. + * + * Calling this function again for the same message will replace any previously registered callback. + * + * If the message is never actually sent (e.g., it's not passed to `otIp6Send()` or other send APIs), the callback + * will still be invoked when the message is freed. In this case, `OT_ERROR_DROP` will be passed as the error. + * + * @param[in] aMessage The message to register the callback with. + * @param[in] aCallback The TX callback. + * @param[in] aContext A pointer to a user-provided arbitrary context for the callback. + */ +void otMessageRegisterTxCallback(otMessage *aMessage, otMessageTxCallback aCallback, void *aContext); + /** * Append bytes to a message. * diff --git a/src/core/api/message_api.cpp b/src/core/api/message_api.cpp index 5404df935..298c3db85 100644 --- a/src/core/api/message_api.cpp +++ b/src/core/api/message_api.cpp @@ -37,6 +37,8 @@ using namespace ot; +otInstance *otMessageGetInstance(const otMessage *aMessage) { return &AsCoreType(aMessage).GetInstance(); } + void otMessageFree(otMessage *aMessage) { AsCoreType(aMessage).Free(); } uint16_t otMessageGetLength(const otMessage *aMessage) { return AsCoreType(aMessage).GetLength(); } @@ -92,6 +94,11 @@ otError otMessageGetThreadLinkInfo(const otMessage *aMessage, otThreadLinkInfo * return AsCoreType(aMessage).GetLinkInfo(AsCoreType(aLinkInfo)); } +void otMessageRegisterTxCallback(otMessage *aMessage, otMessageTxCallback aCallback, void *aContext) +{ + AsCoreType(aMessage).RegisterTxCallback(aCallback, aContext); +} + otError otMessageAppend(otMessage *aMessage, const void *aBuf, uint16_t aLength) { AssertPointerIsNotNull(aBuf); diff --git a/src/core/common/message.cpp b/src/core/common/message.cpp index e2ff22982..406624cc4 100644 --- a/src/core/common/message.cpp +++ b/src/core/common/message.cpp @@ -250,7 +250,15 @@ exit: return error; } -void Message::Free(void) { Get().Free(this); } +void Message::Free(void) +{ + // `TxCallback` is cleared once it is invoked. If the message is + // freed before we know the TX outcome, it's treated as a dropped + // message, signaling `kErrorDrop`. + + InvokeTxCallback(kErrorDrop); + Get().Free(this); +} Message *Message::GetNext(void) const { @@ -364,6 +372,23 @@ const char *Message::PriorityToString(Priority aPriority) return kPriorityStrings[aPriority]; } +void Message::RegisterTxCallback(TxCallback aCallback, void *aContext) +{ + GetMetadata().mTxCallback = aCallback; + GetMetadata().mTxContext = aContext; +} + +void Message::InvokeTxCallback(Error aError) +{ + TxCallback callback = GetMetadata().mTxCallback; + + if (callback != nullptr) + { + GetMetadata().mTxCallback = nullptr; + callback(this, aError, GetMetadata().mTxContext); + } +} + Error Message::AppendBytes(const void *aBuf, uint16_t aLength) { Error error = kErrorNone; diff --git a/src/core/common/message.hpp b/src/core/common/message.hpp index 17679ab9f..838263724 100644 --- a/src/core/common/message.hpp +++ b/src/core/common/message.hpp @@ -166,6 +166,8 @@ class Buffer : public otMessageBuffer, public LinkedListEntry friend class Message; public: + typedef otMessageTxCallback TxCallback; ///< Message TX callback. + /** * Returns a pointer to the next message buffer. * @@ -234,6 +236,8 @@ protected: Message *mNext; // Next message in a doubly linked list. Message *mPrev; // Previous message in a doubly linked list. void *mQueue; // The queue where message is queued (if any). Queue type from `mInPriorityQ`. + TxCallback mTxCallback; // The callback to inform message TX success or failure. + void *mTxContext; // The arbitrary context associated with `mTxCallback`. RssAverager mRssAverager; // The averager maintaining the received signal strength (RSS) average. LqiAverager mLqiAverager; // The averager maintaining the Link quality indicator (LQI) average. #if OPENTHREAD_FTD @@ -654,6 +658,35 @@ public: */ static const char *PriorityToString(Priority aPriority); + /** + * Registers a callback to be notified of a message's transmission outcome. + * + * The registered `TxCallback` provides notification of the transmission status of the message from this device to + * an immediate neighbor (one hop). It doesn't indicate delivery to the final multi-hop destination. + * + * For unicast messages, `kErrorNone` callback error signifies successful delivery and MAC acknowledgment for all + * fragments of the message to an immediate neighbor, irrespective of whether direct or indirect TX is used. For + * multicast messages, `kErrorNone` indicates successful broadcast of all fragments. Note that no MAC-level ack + * is expected for broadcast frame transmissions. + * + * Only one callback can be registered per `Message`. Subsequent calls replace any existing callback. If the + * message is never actually sent, the callback will still be invoked when the message is freed, with `kErrorDrop` + * as the error. + * + * @param[in] aCallback The `TxCallback` function to register with the message. + * @param[in] aContext An arbitrary context that will be passed when @p aCallback is invoked. + */ + void RegisterTxCallback(TxCallback aCallback, void *aContext); + + /** + * Invokes the registered `TxCallback` on the `Message` with the given error status. + * + * The `TxCallback` is a one-time callback, meaning it's automatically cleared once it's invoked. + * + * @param[in] aError The error to report. + */ + void InvokeTxCallback(Error aError); + /** * Prepends bytes to the front of the message. * diff --git a/src/core/thread/indirect_sender.cpp b/src/core/thread/indirect_sender.cpp index cbc56c7c5..762009b51 100644 --- a/src/core/thread/indirect_sender.cpp +++ b/src/core/thread/indirect_sender.cpp @@ -524,6 +524,8 @@ void IndirectSender::HandleSentFrameToChild(const Mac::TxFrame &aFrame, mSourceMatchController.DecrementMessageCount(aChild); } + message->InvokeTxCallback(txError); + Get().RemoveMessageIfNoPendingTx(*message); } diff --git a/src/core/thread/mesh_forwarder.cpp b/src/core/thread/mesh_forwarder.cpp index b4cefc8f4..969d48926 100644 --- a/src/core/thread/mesh_forwarder.cpp +++ b/src/core/thread/mesh_forwarder.cpp @@ -1288,6 +1288,8 @@ void MeshForwarder::FinalizeMessageDirectTx(Message &aMessage, Error aError) Get().HandleChildIdRequestTxDone(aMessage); } + aMessage.InvokeTxCallback(aError); + exit: return; }