diff --git a/include/openthread/thread_ftd.h b/include/openthread/thread_ftd.h index b1f59159f..04c517fd5 100644 --- a/include/openthread/thread_ftd.h +++ b/include/openthread/thread_ftd.h @@ -465,6 +465,46 @@ OTAPI int8_t OTCALL otThreadGetParentPriority(otInstance *aInstance); */ OTAPI otError OTCALL otThreadSetParentPriority(otInstance *aInstance, int8_t aParentPriority); +/** + * This enumeration defines the constants used in `otThreadChildTableCallback` to indicate whether a child is added or + * removed. + * + */ +typedef enum otThreadChildTableEvent +{ + OT_THREAD_CHILD_TABLE_EVENT_CHILD_ADDED, ///< A child is being added. + OT_THREAD_CHILD_TABLE_EVENT_CHILD_REMOVED, ///< A child is being removed. +} otThreadChildTableEvent; + +/** + * This function pointer is called to notify that a child is being added to or removed from child table. + * + * @param[in] aEvent A event flag indicating whether a child is being added or removed. + * @param[in] aChildInfo A pointer to child information structure. + * + */ +typedef void (*otThreadChildTableCallback)(otThreadChildTableEvent aEvent, const otChildInfo *aChildInfo); + +/** + * This function gets the child table callback function. + * + * @returns The callback function pointer. + * + */ +otThreadChildTableCallback otThreadGetChildTableCallback(otInstance *aInstance); + +/** + * This function sets the child table callback function. + * + * The provided callback (if non-NULL) will be invoked when a child entry is being added/removed to/from the child + * table. Subsequent calls to this method will overwrite the previous callback. Note that this callback in invoked + * while the child table is being updated and always before the `otStateChangedCallback`. + * + * @param[in] aCallback A pointer to callback handler function. + * + */ +void otThreadSetChildTableCallback(otInstance *aInstance, otThreadChildTableCallback aCallback); + /** * @} * diff --git a/src/core/api/thread_ftd_api.cpp b/src/core/api/thread_ftd_api.cpp index f43bf68c5..761a00bcc 100644 --- a/src/core/api/thread_ftd_api.cpp +++ b/src/core/api/thread_ftd_api.cpp @@ -289,4 +289,14 @@ otError otThreadSetParentPriority(otInstance *aInstance, const int8_t aParentPri return aInstance->mThreadNetif.GetMle().SetAssignParentPriority(aParentPriority); } +otThreadChildTableCallback otThreadGetChildTableCallback(otInstance *aInstance) +{ + return aInstance->mThreadNetif.GetMle().GetChildTableChangedCallback(); +} + +void otThreadSetChildTableCallback(otInstance *aInstance, otThreadChildTableCallback aCallback) +{ + aInstance->mThreadNetif.GetMle().SetChildTableChangedCallback(aCallback); +} + #endif // OPENTHREAD_FTD diff --git a/src/core/thread/mle_router.cpp b/src/core/thread/mle_router.cpp index 69fe6a63d..290008161 100644 --- a/src/core/thread/mle_router.cpp +++ b/src/core/thread/mle_router.cpp @@ -65,6 +65,7 @@ MleRouter::MleRouter(otInstance &aInstance): mRouterIdSequence(0), mRouterIdSequenceLastUpdated(0), mMaxChildrenAllowed(kMaxChildren), + mChildTableChangedCallback(NULL), mChallengeTimeout(0), mNextChildId(kMaxChildId), mNetworkIdTimeout(kNetworkIdTimeout), @@ -3228,9 +3229,9 @@ otError MleRouter::RemoveNeighbor(Neighbor &aNeighbor) case OT_DEVICE_ROLE_LEADER: if (aNeighbor.IsStateValidOrRestoring() && !IsActiveRouter(aNeighbor.GetRloc16())) { + SignalChildUpdated(OT_THREAD_CHILD_TABLE_EVENT_CHILD_REMOVED, static_cast(aNeighbor)); aNeighbor.SetState(Neighbor::kStateInvalid); netif.GetMeshForwarder().UpdateIndirectMessages(); - netif.SetStateChangedFlags(OT_CHANGED_THREAD_CHILD_REMOVED); netif.GetNetworkDataLeader().SendServerDataNotification(aNeighbor.GetRloc16()); RemoveStoredChild(aNeighbor.GetRloc16()); } @@ -4701,8 +4702,8 @@ void MleRouter::SetChildStateToValid(Child &aChild) VerifyOrExit(aChild.GetState() != Neighbor::kStateValid); aChild.SetState(Neighbor::kStateValid); - GetNetif().SetStateChangedFlags(OT_CHANGED_THREAD_CHILD_ADDED); StoreChild(aChild.GetRloc16()); + SignalChildUpdated(OT_THREAD_CHILD_TABLE_EVENT_CHILD_ADDED, aChild); exit: return; @@ -4732,7 +4733,7 @@ void MleRouter::RemoveChildren(void) switch (mChildren[i].GetState()) { case Neighbor::kStateValid: - GetNetif().SetStateChangedFlags(OT_CHANGED_THREAD_CHILD_REMOVED); + SignalChildUpdated(OT_THREAD_CHILD_TABLE_EVENT_CHILD_REMOVED, mChildren[i]); // fall-through @@ -4857,6 +4858,31 @@ exit: return error; } +void MleRouter::SignalChildUpdated(otThreadChildTableEvent aEvent, Child &aChild) +{ + if (mChildTableChangedCallback != NULL) + { + otChildInfo childInfo; + otError error; + + error = GetChildInfo(aChild, childInfo); + assert(error == OT_ERROR_NONE); + + mChildTableChangedCallback(aEvent, &childInfo); + } + + switch (aEvent) + { + case OT_THREAD_CHILD_TABLE_EVENT_CHILD_ADDED: + GetNetif().SetStateChangedFlags(OT_CHANGED_THREAD_CHILD_ADDED); + break; + + case OT_THREAD_CHILD_TABLE_EVENT_CHILD_REMOVED: + GetNetif().SetStateChangedFlags(OT_CHANGED_THREAD_CHILD_REMOVED); + break; + } +} + } // namespace Mle } // namespace ot diff --git a/src/core/thread/mle_router_ftd.hpp b/src/core/thread/mle_router_ftd.hpp index c7b763320..b1551b30a 100644 --- a/src/core/thread/mle_router_ftd.hpp +++ b/src/core/thread/mle_router_ftd.hpp @@ -38,6 +38,8 @@ #include "utils/wrap_string.h" +#include + #include "coap/coap.hpp" #include "coap/coap_header.hpp" #include "common/timer.hpp" @@ -709,6 +711,25 @@ public: */ otError GetMaxChildTimeout(uint32_t &aTimeout) const; + /** + * This method sets the "child table changed" callback function. + * + * The provided callback (if non-NULL) will be invoked when a child entry is being added/remove to/from the child + * table. Subsequent calls to this method will overwrite the previous callback. + * + * @param[in] aCallback A pointer to callback handler function. + * + */ + void SetChildTableChangedCallback(otThreadChildTableCallback aCallback) { mChildTableChangedCallback = aCallback; } + + /** + * This method gets the "child table changed" callback function. + * + * @returns The callback function pointer. + * + */ + otThreadChildTableCallback GetChildTableChangedCallback(void) const { return mChildTableChangedCallback; } + private: enum { @@ -804,6 +825,8 @@ private: static void HandleStateUpdateTimer(Timer &aTimer); void HandleStateUpdateTimer(void); + void SignalChildUpdated(otThreadChildTableEvent aEvent, Child &aChild); + static MleRouter &GetOwner(const Context &aContext); TrickleTimer mAdvertiseTimer; @@ -818,6 +841,8 @@ private: uint8_t mMaxChildrenAllowed; Child mChildren[kMaxChildren]; + otThreadChildTableCallback mChildTableChangedCallback; + uint8_t mChallengeTimeout; uint8_t mChallenge[8]; uint16_t mNextChildId; diff --git a/src/ncp/ncp_base.cpp b/src/ncp/ncp_base.cpp index c9d0b89b4..29f37d8c9 100644 --- a/src/ncp/ncp_base.cpp +++ b/src/ncp/ncp_base.cpp @@ -528,6 +528,7 @@ NcpBase::NcpBase(otInstance *aInstance): mRequireJoinExistingNetwork(false), mIsRawStreamEnabled(false), mDisableStreamWrite(false), + mShouldEmitChildTableUpdate(false), #if OPENTHREAD_ENABLE_RAW_LINK_API mCurTransmitTID(0), mCurReceiveChannel(OPENTHREAD_CONFIG_DEFAULT_CHANNEL), @@ -559,6 +560,9 @@ NcpBase::NcpBase(otInstance *aInstance): otIp6SetReceiveFilterEnabled(mInstance, true); otLinkSetPcapCallback(mInstance, &NcpBase::HandleRawFrame, static_cast(this)); otIcmp6SetEchoEnabled(mInstance, false); +#if OPENTHREAD_FTD + otThreadSetChildTableCallback(mInstance, &NcpBase::HandleChildTableChanged); +#endif #if OPENTHREAD_ENABLE_LEGACY mLegacyNodeDidJoin = false; mLegacyHandlers = NULL; diff --git a/src/ncp/ncp_base.hpp b/src/ncp/ncp_base.hpp index 2beb56829..af785a9e2 100644 --- a/src/ncp/ncp_base.hpp +++ b/src/ncp/ncp_base.hpp @@ -40,6 +40,9 @@ #else #include #endif +#if OPENTHREAD_FTD +#include +#endif #include #include #include @@ -216,6 +219,11 @@ private: static void HandleNetifStateChanged(uint32_t aFlags, void *aContext); void ProcessThreadChangedFlags(void); +#if OPENTHREAD_FTD + static void HandleChildTableChanged(otThreadChildTableEvent aEvent, const otChildInfo *aChildInfo); + void HandleChildTableChanged(otThreadChildTableEvent aEvent, const otChildInfo &aChildInfo); +#endif + static void HandleDatagramFromStack(otMessage *aMessage, void *aContext); void HandleDatagramFromStack(otMessage *aMessage); @@ -235,6 +243,11 @@ private: void SendDoneTask(void); otError GetPropertyHandler_ChannelMaskHelper(uint32_t channel_mask); + +#if OPENTHREAD_FTD + otError EncodeChildInfo(const otChildInfo &aChildInfo); +#endif + #endif // OPENTHREAD_MTD || OPENTHREAD_FTD #if OPENTHREAD_FTD && OPENTHREAD_ENABLE_TMF_PROXY @@ -658,6 +671,7 @@ private: bool mRequireJoinExistingNetwork; bool mIsRawStreamEnabled; bool mDisableStreamWrite; + bool mShouldEmitChildTableUpdate; #if OPENTHREAD_ENABLE_RAW_LINK_API uint8_t mCurTransmitTID; diff --git a/src/ncp/ncp_base_ftd.cpp b/src/ncp/ncp_base_ftd.cpp index c9bded262..54404ba6d 100644 --- a/src/ncp/ncp_base_ftd.cpp +++ b/src/ncp/ncp_base_ftd.cpp @@ -55,6 +55,88 @@ namespace ot { namespace Ncp { +otError NcpBase::EncodeChildInfo(const otChildInfo &aChildInfo) +{ + otError error = OT_ERROR_NONE; + uint8_t modeFlags; + + modeFlags = LinkFlagsToFlagByte( + aChildInfo.mRxOnWhenIdle, + aChildInfo.mSecureDataRequest, + aChildInfo.mFullFunction, + aChildInfo.mFullNetworkData + ); + + SuccessOrExit(error = mEncoder.WriteEui64(aChildInfo.mExtAddress)); + SuccessOrExit(error = mEncoder.WriteUint16(aChildInfo.mRloc16)); + SuccessOrExit(error = mEncoder.WriteUint32(aChildInfo.mTimeout)); + SuccessOrExit(error = mEncoder.WriteUint32(aChildInfo.mAge)); + SuccessOrExit(error = mEncoder.WriteUint8(aChildInfo.mNetworkDataVersion)); + SuccessOrExit(error = mEncoder.WriteUint8(aChildInfo.mLinkQualityIn)); + SuccessOrExit(error = mEncoder.WriteInt8(aChildInfo.mAverageRssi)); + SuccessOrExit(error = mEncoder.WriteUint8(modeFlags)); + SuccessOrExit(error = mEncoder.WriteInt8(aChildInfo.mLastRssi)); + +exit: + return error; +} + +// ---------------------------------------------------------------------------- +// MARK: Property/Status Changed +// ---------------------------------------------------------------------------- + +void NcpBase::HandleChildTableChanged(otThreadChildTableEvent aEvent, const otChildInfo *aChildInfo) +{ + GetNcpInstance()->HandleChildTableChanged(aEvent, *aChildInfo); +} + +void NcpBase::HandleChildTableChanged(otThreadChildTableEvent aEvent, const otChildInfo &aChildInfo) +{ + otError error = OT_ERROR_NONE; + uint8_t header = SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0; + unsigned int command = 0; + + VerifyOrExit(!mChangedPropsSet.IsPropertyFiltered(SPINEL_PROP_THREAD_CHILD_TABLE)); + + switch (aEvent) + { + case OT_THREAD_CHILD_TABLE_EVENT_CHILD_ADDED: + command = SPINEL_CMD_PROP_VALUE_INSERTED; + break; + + case OT_THREAD_CHILD_TABLE_EVENT_CHILD_REMOVED: + command = SPINEL_CMD_PROP_VALUE_REMOVED; + break; + + default: + ExitNow(); + } + + SuccessOrExit(error = mEncoder.BeginFrame(header, command, SPINEL_PROP_THREAD_CHILD_TABLE)); + SuccessOrExit(error = EncodeChildInfo(aChildInfo)); + SuccessOrExit(error = mEncoder.EndFrame()); + +exit: + + // If the frame can not be added (out of NCP buffer space), we remember + // to send an async `LAST_STATUS(NOMEM)` when buffer space becomes + // available. Also `mShouldEmitChildTableUpdate` flag is set to `true` so + // that the entire child table is later emitted as `VALUE_IS` spinel frame + // update from `ProcessThreadChangedFlags()`. + + if (error != OT_ERROR_NONE) + { + mShouldEmitChildTableUpdate = true; + + mChangedPropsSet.AddLastStatus(SPINEL_STATUS_NOMEM); + mUpdateChangedPropsTask.Post(); + } +} + +// ---------------------------------------------------------------------------- +// MARK: Individual Property Handlers +// ---------------------------------------------------------------------------- + otError NcpBase::GetPropertyHandler_THREAD_LOCAL_LEADER_WEIGHT(void) { return mEncoder.WriteUint8(otThreadGetLocalLeaderWeight(mInstance)); @@ -70,7 +152,6 @@ otError NcpBase::GetPropertyHandler_THREAD_CHILD_TABLE(void) otError error = OT_ERROR_NONE; otChildInfo childInfo; uint8_t maxChildren; - uint8_t modeFlags; maxChildren = otThreadGetMaxAllowedChildren(mInstance); @@ -81,25 +162,8 @@ otError NcpBase::GetPropertyHandler_THREAD_CHILD_TABLE(void) continue; } - modeFlags = LinkFlagsToFlagByte( - childInfo.mRxOnWhenIdle, - childInfo.mSecureDataRequest, - childInfo.mFullFunction, - childInfo.mFullNetworkData - ); - SuccessOrExit(error = mEncoder.OpenStruct()); - - SuccessOrExit(error = mEncoder.WriteEui64(childInfo.mExtAddress)); - SuccessOrExit(error = mEncoder.WriteUint16(childInfo.mRloc16)); - SuccessOrExit(error = mEncoder.WriteUint32(childInfo.mTimeout)); - SuccessOrExit(error = mEncoder.WriteUint32(childInfo.mAge)); - SuccessOrExit(error = mEncoder.WriteUint8(childInfo.mNetworkDataVersion)); - SuccessOrExit(error = mEncoder.WriteUint8(childInfo.mLinkQualityIn)); - SuccessOrExit(error = mEncoder.WriteInt8(childInfo.mAverageRssi)); - SuccessOrExit(error = mEncoder.WriteUint8(modeFlags)); - SuccessOrExit(error = mEncoder.WriteInt8(childInfo.mLastRssi)); - + SuccessOrExit(error = EncodeChildInfo(childInfo)); SuccessOrExit(error = mEncoder.CloseStruct()); } diff --git a/src/ncp/ncp_base_mtd.cpp b/src/ncp/ncp_base_mtd.cpp index 34b7e3d33..590ff44db 100644 --- a/src/ncp/ncp_base_mtd.cpp +++ b/src/ncp/ncp_base_mtd.cpp @@ -2707,7 +2707,26 @@ void NcpBase::ProcessThreadChangedFlags(void) if (mThreadChangedFlags & threadFlag) { - mChangedPropsSet.AddProperty(kFlags[i].mPropKey); + spinel_prop_key_t propKey = kFlags[i].mPropKey; + bool shouldAddProperty = true; + + // Child table changes are reported using the `HandleChildAdded()` and + // `HandleChildRemoved()` callbacks emitting spinel `VALUE_INSERTED` and + // `VALUE_REMOVED` async spinel frames. If the spinel frames could not be + // added (e.g., out of NCP buffer) from the above callbacks, the flag + // `mShouldEmitChildTableUpdate` is set to `true` so that the entire + // child table is emitted as an unsolicited `VALUE_IS` update. + + if (propKey == SPINEL_PROP_THREAD_CHILD_TABLE) + { + shouldAddProperty = mShouldEmitChildTableUpdate; + mShouldEmitChildTableUpdate = false; + } + + if (shouldAddProperty) + { + mChangedPropsSet.AddProperty(propKey); + } if (threadFlag == OT_CHANGED_THREAD_NETDATA) {