From db7f037f73cf0ef1dc03a0e21ac6ecfb28261e9f Mon Sep 17 00:00:00 2001 From: Zhanglong Xia Date: Tue, 9 Sep 2025 05:15:09 +0800 Subject: [PATCH] [p2p] add unlink API to tear down the P2P link (#11904) --- include/openthread/instance.h | 2 +- include/openthread/provisional/p2p.h | 24 +++++ src/cli/README.md | 11 ++ src/cli/cli.cpp | 49 ++++++--- src/cli/cli.hpp | 11 +- src/core/api/p2p_api.cpp | 8 ++ src/core/thread/mle.cpp | 6 ++ src/core/thread/mle.hpp | 48 +++++++-- src/core/thread/mle_p2p.cpp | 114 ++++++++++++++++++++- src/core/thread/mle_types.hpp | 1 + src/core/thread/peer.hpp | 28 ++++- tests/scripts/expect/v1_5-cli-p2p-link.exp | 20 ++++ 12 files changed, 291 insertions(+), 31 deletions(-) diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 0c4bbb8f5..53b195b43 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 (534) +#define OPENTHREAD_API_VERSION (535) /** * @addtogroup api-instance diff --git a/include/openthread/provisional/p2p.h b/include/openthread/provisional/p2p.h index 8c9afca7e..53a5bbc14 100644 --- a/include/openthread/provisional/p2p.h +++ b/include/openthread/provisional/p2p.h @@ -91,6 +91,30 @@ otError otP2pWakeupAndLink(otInstance *aInstance, otP2pLinkDoneCallback aCallback, void *aContext); +/** + * Notifies the caller that the P2P link tear down process has ended. + * + * @param[in] aContext A pointer to application-specific context. + */ +typedef void (*otP2pUnlinkDoneCallback)(void *aContext); + +/** + * Tears down the P2P link specified by the Extended Address. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aExtAddress A pointer to the P2P peer's Extended Address. + * @param[in] aCallback A pointer to function that is called when the P2P link tear down process has ended. + * @param[in] aContext A pointer to callback application-specific context. + * + * @retval OT_ERROR_NONE Successfully started to tear down the P2P link. + * @retval OT_ERROR_BUSY Tearing down or establishing a P2P link process is in progress. + * @retval OT_ERROR_NOT_FOUND The P2P link identified by the @p aExtAddress was not found. + */ +otError otP2pUnlink(otInstance *aInstance, + const otExtAddress *aExtAddress, + otP2pUnlinkDoneCallback aCallback, + void *aContext); + /** * Defines events of the P2P link. */ diff --git a/src/cli/README.md b/src/cli/README.md index 5518a608c..f85887008 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -3125,6 +3125,17 @@ Wakes up the peer identified by the extended address and establishes a peer-to-p Done ``` +### p2p unlink \ + +Tears down the P2P link identified by the extended address. + +`OPENTHREAD_CONFIG_P2P_ENABLE` is required. + +```bash +> p2p unlink dead00beef00cafe +Done +``` + ### panid Get the IEEE 802.15.4 PAN ID value. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 28eb69d40..83dc12e4a 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -8088,22 +8088,43 @@ exit: #endif // OPENTHREAD_CONFIG_VERHOEFF_CHECKSUM_ENABLE -#if OPENTHREAD_CONFIG_P2P_ENABLE && OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE +#if OPENTHREAD_CONFIG_P2P_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; - if (aArgs[0] == "link") + if (aArgs[0] == "unlink") + { + otExtAddress extAddress; + + /** + * @cli p2p unlink + * @code + * p2p unlink dead00beef00cafe + * Done + * @endcode + * @cparam p2p unlink @ca{extended-address} + * @par + * `OPENTHREAD_CONFIG_P2P_ENABLE` is required. + * @par + * Tears down the P2P link identified by the extended address. + */ + SuccessOrExit(error = aArgs[1].ParseAsHexString(extAddress.m8)); + SuccessOrExit(error = otP2pUnlink(GetInstancePtr(), &extAddress, HandleP2pUnlinkDone, this)); + error = OT_ERROR_PENDING; + } +#if OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE + else if (aArgs[0] == "link") { otP2pRequest p2pRequest; /** - * @cli link + * @cli p2p link * @code - * link extaddr dead00beef00cafe + * p2p link extaddr dead00beef00cafe * Done * @endcode - * @cparam link extaddr @ca{extended-address} + * @cparam p2p link extaddr @ca{extended-address} * @par * `OPENTHREAD_CONFIG_P2P_ENABLE` and `OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE` are required. * @par @@ -8120,9 +8141,10 @@ template <> otError Interpreter::Process(Arg aArgs[]) ExitNow(error = OT_ERROR_INVALID_ARGS); } - SuccessOrExit(error = otP2pWakeupAndLink(GetInstancePtr(), &p2pRequest, HandleP2pLinkedResult, this)); + SuccessOrExit(error = otP2pWakeupAndLink(GetInstancePtr(), &p2pRequest, HandleP2pLinkDone, this)); error = OT_ERROR_PENDING; } +#endif else { error = OT_ERROR_INVALID_ARGS; @@ -8132,13 +8154,16 @@ exit: return error; } -void Interpreter::HandleP2pLinkedResult(void *aContext) -{ - static_cast(aContext)->HandleP2pLinkedResult(); -} +#if OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE +void Interpreter::HandleP2pLinkDone(void *aContext) { static_cast(aContext)->HandleP2pLinkDone(); } -void Interpreter::HandleP2pLinkedResult(void) { OutputResult(OT_ERROR_NONE); } -#endif // OPENTHREAD_CONFIG_P2P_ENABLE && OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE +void Interpreter::HandleP2pLinkDone(void) { OutputResult(OT_ERROR_NONE); } +#endif + +void Interpreter::HandleP2pUnlinkDone(void *aContext) { static_cast(aContext)->HandleP2pUnlinkDone(); } + +void Interpreter::HandleP2pUnlinkDone(void) { OutputResult(OT_ERROR_NONE); } +#endif // OPENTHREAD_CONFIG_P2P_ENABLE #if OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE || OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index 6e80d6c66..9b7c2de0d 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -310,9 +310,14 @@ private: static void HandleIp6Receive(otMessage *aMessage, void *aContext); #endif -#if OPENTHREAD_CONFIG_P2P_ENABLE && OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE - static void HandleP2pLinkedResult(void *aContext); - void HandleP2pLinkedResult(void); +#if OPENTHREAD_CONFIG_P2P_ENABLE +#if OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE + static void HandleP2pLinkDone(void *aContext); + void HandleP2pLinkDone(void); +#endif + + static void HandleP2pUnlinkDone(void *aContext); + void HandleP2pUnlinkDone(void); #endif #if OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE diff --git a/src/core/api/p2p_api.cpp b/src/core/api/p2p_api.cpp index 3d48d5e89..c6dde7b7a 100644 --- a/src/core/api/p2p_api.cpp +++ b/src/core/api/p2p_api.cpp @@ -53,4 +53,12 @@ void otP2pSetEventCallback(otInstance *aInstance, otP2pEventCallback aCallback, { AsCoreType(aInstance).Get().P2pSetEventCallback(aCallback, aContext); } + +otError otP2pUnlink(otInstance *aInstance, + const otExtAddress *aExtAddress, + otP2pUnlinkDoneCallback aCallback, + void *aContext) +{ + return AsCoreType(aInstance).Get().P2pUnlink(AsCoreType(aExtAddress), aCallback, aContext); +} #endif diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index 823180462..5b8c5660b 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -1780,6 +1780,10 @@ void Mle::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageIn mP2p.HandleP2pLinkAcceptAndRequest(rxInfo); break; #endif + + case kCommandP2pLinkTearDown: + mP2p.HandleP2pLinkTearDown(rxInfo); + break; #endif // OPENTHREAD_CONFIG_P2P_ENABLE default: @@ -2895,6 +2899,7 @@ const char *Mle::MessageTypeToString(MessageType aType) "P2P Link Request", // (32) kTypeP2pLinkRequest "P2P Link Accept and Request", // (33) kTypeP2pLinkAcceptAndRequest "P2P Link Accept", // (34) kTypeP2pLinkAccept + "P2P Link Tear Down", // (35) kTypeP2pLinkTearDown #endif }; @@ -2943,6 +2948,7 @@ const char *Mle::MessageTypeToString(MessageType aType) ValidateNextEnum(kTypeP2pLinkRequest); ValidateNextEnum(kTypeP2pLinkAcceptAndRequest); ValidateNextEnum(kTypeP2pLinkAccept); + ValidateNextEnum(kTypeP2pLinkTearDown); #endif }; diff --git a/src/core/thread/mle.hpp b/src/core/thread/mle.hpp index b1eaf0980..fb8c0da0b 100644 --- a/src/core/thread/mle.hpp +++ b/src/core/thread/mle.hpp @@ -135,8 +135,10 @@ class Mle : public InstanceLocator, private NonCopyable public: typedef otDetachGracefullyCallback DetachCallback; ///< Callback to signal end of graceful detach. typedef otP2pLinkDoneCallback P2pLinkDoneCallback; ///< Callback to inform the result of establishing P2P links. - typedef otP2pEventCallback P2pEventCallback; ///< Callback to signal events of the P2P link. - typedef otWakeupCallback WakeupCallback; ///< Callback to communicate the result of waking a Wake-up End Device + typedef otP2pUnlinkDoneCallback + P2pUnlinkDoneCallback; ///< Callback to inform the result of tearing down the P2P link. + typedef otP2pEventCallback P2pEventCallback; ///< Callback to signal events of the P2P link. + typedef otWakeupCallback WakeupCallback; ///< Callback to communicate the result of waking a Wake-up End Device. /** * Initializes the MLE object. @@ -1186,6 +1188,22 @@ public: } #endif + /** + * Tears down the P2P link specified by the Extended Address. + * + * @param[in] aExtAddress A constant reference to the P2P peer's Extended Address. + * @param[in] aCallback A pointer to function that is called when the P2P link tear down process has ended. + * @param[in] aContext A pointer to callback application-specific context. + * + * @retval OT_ERROR_NONE Successfully started to tear down the P2P link. + * @retval OT_ERROR_BUSY Tearing down or establishing a P2P link process is in progress. + * @retval OT_ERROR_NOT_FOUND The P2P link identified by the @p aExtAddress was not found. + */ + Error P2pUnlink(const Mac::ExtAddress &aExtAddress, P2pUnlinkDoneCallback aCallback, void *aContext) + { + return mP2p.Unlink(aExtAddress, aCallback, aContext); + } + /** * Sets the callback function to notify event changes of P2P links. * @@ -1473,6 +1491,7 @@ private: kTypeP2pLinkRequest, kTypeP2pLinkAcceptAndRequest, kTypeP2pLinkAccept, + kTypeP2pLinkTearDown, #endif }; @@ -2170,8 +2189,10 @@ private: void HandleP2pLinkAcceptAndRequest(RxInfo &aRxInfo); #endif - void SetEventCallback(P2pEventCallback aCallback, void *aContext); - void HandleLinkTimer(void); + Error Unlink(const Mac::ExtAddress &aExtAddress, P2pUnlinkDoneCallback aCallback, void *aContext); + void HandleP2pLinkTearDown(RxInfo &aRxInfo); + void SetEventCallback(P2pEventCallback aCallback, void *aContext); + void HandleLinkTimer(void); private: static constexpr uint16_t kWakeupMaxDuration = OPENTHREAD_CONFIG_WAKEUP_MAX_DURATION; @@ -2185,6 +2206,7 @@ private: kStateWaitingLinkAccept, kStateAttachDelay, kStateWaitingLinkAcceptAndRequest, + kStateTearingDown, }; #if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE @@ -2196,19 +2218,25 @@ private: Error SendP2pLinkAcceptAndRequest(const LinkAcceptInfo &aInfo); #endif + static void HandleLinkTearDownTxDone(const otMessage *aMessage, otError aError, void *aContext); + void HandleLinkTearDownTxDone(const Message &aMessage); + Error SendP2pLinkAcceptVariant(const LinkAcceptInfo &aInfo, bool aIsLinkAcceptorRequest); void HandleP2pLinkAcceptVariant(RxInfo &aRxInfo, MessageType aMessageType); void SetWakeupListenerEnabled(void); void ClearPeersInLinkRequestState(void); + Error SendLinkTearDown(Peer &aPeer); + void PeerUnlinked(Peer &aPeer); using P2pLinkTimer = TimerMicroIn; - State mState; - PeerTable mPeerTable; - P2pLinkTimer mTimer; - Callback mLinkedCallback; - Callback mEventCallback; - Peer *mPeer; + State mState; + PeerTable mPeerTable; + P2pLinkTimer mTimer; + Callback mLinkDoneCallback; + Callback mUnlinkDoneCallback; + Callback mEventCallback; + Peer *mPeer; }; #endif diff --git a/src/core/thread/mle_p2p.cpp b/src/core/thread/mle_p2p.cpp index 5dd5c56ef..7aa28447d 100644 --- a/src/core/thread/mle_p2p.cpp +++ b/src/core/thread/mle_p2p.cpp @@ -53,7 +53,8 @@ Mle::P2p::P2p(Instance &aInstance) , mState(kStateIdle) , mPeerTable(aInstance) , mTimer(aInstance) - , mLinkedCallback() + , mLinkDoneCallback() + , mUnlinkDoneCallback() , mEventCallback() , mPeer(nullptr) { @@ -77,7 +78,7 @@ Error Mle::P2p::WakeupAndLink(const P2pRequest &aP2pRequest, P2pLinkDoneCallback error = Get().WakeUp(aP2pRequest.GetWakeupRequest(), kWakeupTxInterval, kWakeupMaxDuration)); mState = kStateWakingUp; - mLinkedCallback.Set(aCallback, aContext); + mLinkDoneCallback.Set(aCallback, aContext); mTimer.FireAt(Get().GetTxEndTime() + Get().GetConnectionWindowUs()); exit: @@ -333,7 +334,7 @@ void Mle::P2p::HandleP2pLinkAcceptVariant(RxInfo &aRxInfo, MessageType aMessageT // All P2P links have been established. mState = kStateIdle; mTimer.Stop(); - mLinkedCallback.InvokeAndClearIfSet(); + mLinkDoneCallback.InvokeAndClearIfSet(); } } @@ -341,6 +342,111 @@ exit: LogProcessError(aMessageType, error); } +Error Mle::P2p::Unlink(const Mac::ExtAddress &aExtAddress, P2pUnlinkDoneCallback aCallback, void *aContext) +{ + Error error = kErrorNone; + Peer *peer; + + VerifyOrExit(mState == kStateIdle, error = kErrorBusy); + VerifyOrExit((peer = mPeerTable.FindPeer(aExtAddress, Peer::kInStateValid)) != nullptr, error = kErrorNotFound); + SuccessOrExit(error = SendLinkTearDown(*peer)); + + mState = kStateTearingDown; + mUnlinkDoneCallback.Set(aCallback, aContext); + +exit: + return error; +} + +Error Mle::P2p::SendLinkTearDown(Peer &aPeer) +{ + Error error = kErrorNone; + TxMessage *message; + Ip6::Address destination; + + aPeer.GetLinkLocalIp6Address(destination); + VerifyOrExit((message = Get().NewMleMessage(kCommandP2pLinkTearDown)) != nullptr, error = kErrorNoBufs); + message->RegisterTxCallback(HandleLinkTearDownTxDone, this); + SuccessOrExit(error = message->SendTo(destination)); + + Log(kMessageSend, kTypeP2pLinkTearDown, destination); + +exit: + FreeMessageOnError(message, error); + return error; +} + +void Mle::P2p::HandleLinkTearDownTxDone(const otMessage *aMessage, otError aError, void *aContext) +{ + OT_UNUSED_VARIABLE(aError); + static_cast(aContext)->HandleLinkTearDownTxDone(AsCoreType(aMessage)); +} + +void Mle::P2p::HandleLinkTearDownTxDone(const Message &aMessage) +{ + Ip6::Header ip6Header; + Mac::ExtAddress extAddress; + Peer *peer; + + SuccessOrExit(aMessage.Read(0, ip6Header)); + VerifyOrExit(ip6Header.GetDestination().IsLinkLocalUnicast()); + extAddress.SetFromIid(ip6Header.GetDestination().GetIid()); + + peer = mPeerTable.FindPeer(extAddress, Peer::kInStateValid); + if (peer == nullptr) + { + // Peer may have been removed if we received a tear down from it. In this case, we can consider the unlink done. + mState = kStateIdle; + mUnlinkDoneCallback.InvokeAndClearIfSet(); + ExitNow(); + } + + if (!aMessage.GetTxSuccess() && (peer->GetTearDownCount() < Peer::kMaxRetransmitLinkTearDowns)) + { + peer->IncrementTearDownCount(); + + if (SendLinkTearDown(*peer) == kErrorNone) + { + ExitNow(); + } + } + + PeerUnlinked(*peer); + mUnlinkDoneCallback.InvokeAndClearIfSet(); + +exit: + return; +} + +void Mle::P2p::PeerUnlinked(Peer &aPeer) +{ + aPeer.SetState(Neighbor::kStateInvalid); + + if (mState == kStateTearingDown) + { + mState = kStateIdle; + } + + mEventCallback.InvokeIfSet(OT_P2P_EVENT_UNLINKED, &aPeer.GetExtAddress()); +} + +void Mle::P2p::HandleP2pLinkTearDown(RxInfo &aRxInfo) +{ + Peer *peer; + Mac::ExtAddress extAddress; + + Log(kMessageReceive, kTypeP2pLinkTearDown, aRxInfo.mMessageInfo.GetPeerAddr()); + VerifyOrExit(aRxInfo.mMessageInfo.GetPeerAddr().IsLinkLocalUnicast()); + extAddress.SetFromIid(aRxInfo.mMessageInfo.GetPeerAddr().GetIid()); + VerifyOrExit((peer = mPeerTable.FindPeer(extAddress, Peer::kInStateValid)) != nullptr); + + Get().ProcessKeySequence(aRxInfo); + PeerUnlinked(*peer); + +exit: + return; +} + void Mle::P2p::HandleLinkTimer(void) { switch (mState) @@ -356,7 +462,7 @@ void Mle::P2p::HandleLinkTimer(void) mState = kStateIdle; ClearPeersInLinkRequestState(); - mLinkedCallback.InvokeAndClearIfSet(); + mLinkDoneCallback.InvokeAndClearIfSet(); } break; #endif diff --git a/src/core/thread/mle_types.hpp b/src/core/thread/mle_types.hpp index ea5baee36..da2868d11 100644 --- a/src/core/thread/mle_types.hpp +++ b/src/core/thread/mle_types.hpp @@ -160,6 +160,7 @@ enum Command : uint8_t kCommandP2pLinkRequest = 100, ///< P2P Link Request command kCommandP2pLinkAccept = 101, ///< P2P Link Accept command kCommandP2pLinkAcceptAndRequest = 102, ///< P2P Link Accept And Request command + kCommandP2pLinkTearDown = 103, ///< P2P Link Tear Down command }; /** diff --git a/src/core/thread/peer.hpp b/src/core/thread/peer.hpp index e7e5e5e15..04032aaad 100644 --- a/src/core/thread/peer.hpp +++ b/src/core/thread/peer.hpp @@ -48,12 +48,21 @@ namespace ot { class Peer : public CslNeighbor { public: + /** + * Max number of re-transmitted the P2P link tear down messages. + */ + static constexpr uint8_t kMaxRetransmitLinkTearDowns = 4; + /** * Initializes the `Peer` object. * * @param[in] aInstance The OpenThread instance. */ - void Init(Instance &aInstance) { Neighbor::Init(aInstance); } + void Init(Instance &aInstance) + { + Neighbor::Init(aInstance); + mTearDownCount = 0; + } /** * Clears the peer entry. @@ -86,8 +95,25 @@ public: */ const Mle::TxChallenge &GetChallenge(void) const { return mAttachChallenge; } + /** + * Increments the count of re-transmitted link tear down messages. + */ + void IncrementTearDownCount(void) { mTearDownCount++; } + + /** + * Resets the count of re-transmitted link tear down messages to zero. + */ + void ResetTearDownCount(void) { mTearDownCount = 0; } + + /** + * Returns the count of re-transmitted link tear down messages. + */ + uint8_t GetTearDownCount(void) const { return mTearDownCount; } + private: Mle::TxChallenge mAttachChallenge; + + uint8_t mTearDownCount : 3; // The count of re-transmitted link tear down messages. }; } // namespace ot diff --git a/tests/scripts/expect/v1_5-cli-p2p-link.exp b/tests/scripts/expect/v1_5-cli-p2p-link.exp index b8050fc55..b8b0b3ba2 100755 --- a/tests/scripts/expect/v1_5-cli-p2p-link.exp +++ b/tests/scripts/expect/v1_5-cli-p2p-link.exp @@ -214,4 +214,24 @@ for {set i 1} {$i <= 5} {incr i} { expect "28 bytes from $wc1_link_local_addr: icmp_seq=$i" } + +send_user "\n\n>>> WC1 tears down the P2P link with WL1\n" +switch_node $WC1 +send "p2p unlink $WL1_EXT_ADDRESS\n" +expect_line "Done" + + +send_user "\n\n>>> WC2 tears down the P2P link with WL1\n" +switch_node $WC2 +send "p2p unlink $WL1_EXT_ADDRESS\n" +expect_line "Done" + + +send_user "\n\n>>> WL2 tears down the P2P link with WC1\n" +switch_node $WL2 +send "p2p unlink $WC1_EXT_ADDRESS\n" +expect_line "Done" + +sleep 1 + dispose_all