[child-table] emit child entry inserted/removed from NCP (#2280)

This commit adds new public OpenThread API to set a callback for user
to be notified when a child table entry is being added or removed. This
callback provides info about the child entry and is always invoked before
the `otStateChangedCallback`.

This commit also updates the `NcpBase` implementation to use the new
callback and emit a `VALUE_INSERTED` or `VALUE_REMOVED` async spinel
frame to host whenever child table gets updated. If the async spinel
frame can not be sent (running out of NCP buffer space), an async
`LAST_STATUS(NOMEM)` spinel frame is sent and the entire child table
is sent later (when buffer becomes available) as an async `VALUE_IS`
spinel message.
This commit is contained in:
Abtin Keshavarzian
2017-10-31 14:15:04 -07:00
committed by Jonathan Hui
parent 50942427de
commit 7392200e9e
8 changed files with 225 additions and 23 deletions
+40
View File
@@ -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);
/**
* @}
*
+10
View File
@@ -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
+29 -3
View File
@@ -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<Child &>(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
+25
View File
@@ -38,6 +38,8 @@
#include "utils/wrap_string.h"
#include <openthread/thread_ftd.h>
#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;
+4
View File
@@ -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<void *>(this));
otIcmp6SetEchoEnabled(mInstance, false);
#if OPENTHREAD_FTD
otThreadSetChildTableCallback(mInstance, &NcpBase::HandleChildTableChanged);
#endif
#if OPENTHREAD_ENABLE_LEGACY
mLegacyNodeDidJoin = false;
mLegacyHandlers = NULL;
+14
View File
@@ -40,6 +40,9 @@
#else
#include <openthread/platform/radio.h>
#endif
#if OPENTHREAD_FTD
#include <openthread/thread_ftd.h>
#endif
#include <openthread/message.h>
#include <openthread/ncp.h>
#include <openthread/types.h>
@@ -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;
+83 -19
View File
@@ -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());
}
+20 -1
View File
@@ -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)
{