[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.
This commit is contained in:
Abtin Keshavarzian
2025-06-24 09:37:15 -07:00
committed by GitHub
parent 093d2a8427
commit 84295be3f8
7 changed files with 118 additions and 2 deletions
+1 -1
View File
@@ -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
+47
View File
@@ -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.
*
+7
View File
@@ -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);
+26 -1
View File
@@ -250,7 +250,15 @@ exit:
return error;
}
void Message::Free(void) { Get<MessagePool>().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<MessagePool>().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;
+33
View File
@@ -166,6 +166,8 @@ class Buffer : public otMessageBuffer, public LinkedListEntry<Buffer>
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.
*
+2
View File
@@ -524,6 +524,8 @@ void IndirectSender::HandleSentFrameToChild(const Mac::TxFrame &aFrame,
mSourceMatchController.DecrementMessageCount(aChild);
}
message->InvokeTxCallback(txError);
Get<MeshForwarder>().RemoveMessageIfNoPendingTx(*message);
}
+2
View File
@@ -1288,6 +1288,8 @@ void MeshForwarder::FinalizeMessageDirectTx(Message &aMessage, Error aError)
Get<Mle::Mle>().HandleChildIdRequestTxDone(aMessage);
}
aMessage.InvokeTxCallback(aError);
exit:
return;
}