diff --git a/src/ncp/ncp_base.cpp b/src/ncp/ncp_base.cpp index 3e8231849..38fca5967 100644 --- a/src/ncp/ncp_base.cpp +++ b/src/ncp/ncp_base.cpp @@ -75,12 +75,6 @@ namespace ot { #define NCP_INVALID_SCAN_CHANNEL (-1) -#define NCP_CHANGED_PLATFORM_RESET (1U << 31) -#define NCP_CHANGED_THREAD_ON_MESH_NETS (1U << 30) -#define NCP_CHANGED_THREAD_OFF_MESH_ROUTES (1U << 29) - -#define IGNORE_RETURN_VALUE(s) do { if (s){} } while (0) - // ---------------------------------------------------------------------------- // MARK: Command/Property Jump Tables // ---------------------------------------------------------------------------- @@ -120,6 +114,8 @@ const NcpBase::GetPropertyHandlerEntry NcpBase::mGetPropertyHandlerTable[] = NCP_GET_PROP_HANDLER_ENTRY(HWADDR), NCP_GET_PROP_HANDLER_ENTRY(LOCK), NCP_GET_PROP_HANDLER_ENTRY(HOST_POWER_STATE), + NCP_GET_PROP_HANDLER_ENTRY(UNSOL_UPDATE_FILTER), + NCP_GET_PROP_HANDLER_ENTRY(UNSOL_UPDATE_LIST), NCP_GET_PROP_HANDLER_ENTRY(PHY_ENABLED), NCP_GET_PROP_HANDLER_ENTRY(PHY_FREQ), NCP_GET_PROP_HANDLER_ENTRY(PHY_CHAN_SUPPORTED), @@ -266,6 +262,7 @@ const NcpBase::GetPropertyHandlerEntry NcpBase::mGetPropertyHandlerTable[] = NCP_GET_PROP_HANDLER_ENTRY(DEBUG_NCP_LOG_LEVEL), #if OPENTHREAD_ENABLE_LEGACY NCP_GET_PROP_HANDLER_ENTRY(NEST_LEGACY_ULA_PREFIX), + NCP_GET_PROP_HANDLER_ENTRY(NEST_LEGACY_LAST_NODE_JOINED), #endif }; @@ -275,6 +272,7 @@ const NcpBase::SetPropertyHandlerEntry NcpBase::mSetPropertyHandlerTable[] = { NCP_SET_PROP_HANDLER_ENTRY(POWER_STATE), NCP_SET_PROP_HANDLER_ENTRY(HOST_POWER_STATE), + NCP_SET_PROP_HANDLER_ENTRY(UNSOL_UPDATE_FILTER), #if OPENTHREAD_ENABLE_RAW_LINK_API NCP_SET_PROP_HANDLER_ENTRY(PHY_ENABLED), NCP_SET_PROP_HANDLER_ENTRY(MAC_15_4_SADDR), @@ -368,6 +366,7 @@ const NcpBase::SetPropertyHandlerEntry NcpBase::mSetPropertyHandlerTable[] = const NcpBase::InsertPropertyHandlerEntry NcpBase::mInsertPropertyHandlerTable[] = { + NCP_INSERT_PROP_HANDLER_ENTRY(UNSOL_UPDATE_FILTER), #if OPENTHREAD_ENABLE_RAW_LINK_API NCP_INSERT_PROP_HANDLER_ENTRY(MAC_SRC_MATCH_SHORT_ADDRESSES), NCP_INSERT_PROP_HANDLER_ENTRY(MAC_SRC_MATCH_EXTENDED_ADDRESSES), @@ -393,6 +392,7 @@ const NcpBase::InsertPropertyHandlerEntry NcpBase::mInsertPropertyHandlerTable[] const NcpBase::RemovePropertyHandlerEntry NcpBase::mRemovePropertyHandlerTable[] = { + NCP_REMOVE_PROP_HANDLER_ENTRY(UNSOL_UPDATE_FILTER), #if OPENTHREAD_ENABLE_RAW_LINK_API NCP_REMOVE_PROP_HANDLER_ENTRY(MAC_SRC_MATCH_SHORT_ADDRESSES), NCP_REMOVE_PROP_HANDLER_ENTRY(MAC_SRC_MATCH_EXTENDED_ADDRESSES), @@ -670,15 +670,12 @@ NcpBase::NcpBase(otInstance *aInstance): mDiscoveryScanEnableFiltering(false), mDiscoveryScanPanId(0xffff), mUpdateChangedPropsTask(aInstance, &NcpBase::UpdateChangedProps, this), - mChangedFlags(NCP_CHANGED_PLATFORM_RESET), - mShouldSignalEndOfScan(false), + mThreadChangedFlags(0), + mChangedPropsSet(), mHostPowerState(SPINEL_HOST_POWER_STATE_ONLINE), mHostPowerStateInProgress(false), mHostPowerReplyFrameTag(NcpFrameBuffer::kInvalidTag), mHostPowerStateHeader(0), -#if OPENTHREAD_ENABLE_JAM_DETECTION - mShouldSignalJamStateChange(false), -#endif #if OPENTHREAD_CONFIG_NCP_ENABLE_PEEK_POKE mAllowPeekDelegate(NULL), mAllowPokeDelegate(NULL), @@ -719,12 +716,14 @@ NcpBase::NcpBase(otInstance *aInstance): otLinkSetPcapCallback(mInstance, &NcpBase::HandleRawFrame, static_cast(this)); otIcmp6SetEchoEnabled(mInstance, false); + mChangedPropsSet.AddLastStatus(SPINEL_STATUS_RESET_UNKNOWN); mUpdateChangedPropsTask.Post(); #if OPENTHREAD_ENABLE_LEGACY mLegacyNodeDidJoin = false; mLegacyHandlers = NULL; memset(mLegacyUlaPrefix, 0, sizeof(mLegacyUlaPrefix)); + memset(&mLegacyLastJoinedNode, 0, sizeof(mLegacyLastJoinedNode)); #endif } @@ -824,7 +823,8 @@ exit: if (error != OT_ERROR_NONE) { - SendLastStatus(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_STATUS_DROPPED); + mChangedPropsSet.AddLastStatus(SPINEL_STATUS_DROPPED); + mUpdateChangedPropsTask.Post(); } } #endif // OPENTHREAD_ENABLE_TMF_PROXY && OPENTHREAD_FTD @@ -878,7 +878,8 @@ exit: if (error != OT_ERROR_NONE) { - SendLastStatus(header, SPINEL_STATUS_DROPPED); + mChangedPropsSet.AddLastStatus(SPINEL_STATUS_DROPPED); + mUpdateChangedPropsTask.Post(); mDroppedOutboundIpFrameCounter++; } else @@ -986,28 +987,34 @@ void NcpBase::HandleActiveScanResult(otActiveScanResult *aResult) flags |= SPINEL_BEACON_THREAD_FLAG_NATIVE; } - SendPropertyUpdate( - SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, - SPINEL_CMD_PROP_VALUE_INSERTED, - SPINEL_PROP_MAC_SCAN_BEACON, - SPINEL_DATATYPE_MAC_SCAN_RESULT_S( - SPINEL_802_15_4_DATATYPE_MAC_SCAN_RESULT_V1_S, - SPINEL_NET_DATATYPE_MAC_SCAN_RESULT_V2_S - ), - aResult->mChannel, // Channel - aResult->mRssi, // RSSI - // "mac-layer data" - aResult->mExtAddress.m8, // laddr - 0xFFFF, // saddr, not given - aResult->mPanId, // panid - aResult->mLqi, // lqi - // "net-layer data" - SPINEL_PROTOCOL_TYPE_THREAD, // type - flags, // flags - aResult->mNetworkName.m8, // network name - aResult->mExtendedPanId.m8, OT_EXT_PAN_ID_SIZE, // xpanid - aResult->mSteeringData.m8, aResult->mSteeringData.mLength // steering data - ); + error = SendPropertyUpdate( + SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, + SPINEL_CMD_PROP_VALUE_INSERTED, + SPINEL_PROP_MAC_SCAN_BEACON, + SPINEL_DATATYPE_MAC_SCAN_RESULT_S( + SPINEL_802_15_4_DATATYPE_MAC_SCAN_RESULT_V1_S, + SPINEL_NET_DATATYPE_MAC_SCAN_RESULT_V2_S + ), + aResult->mChannel, // Channel + aResult->mRssi, // RSSI + // "mac-layer data" + aResult->mExtAddress.m8, // laddr + 0xFFFF, // saddr, not given + aResult->mPanId, // panid + aResult->mLqi, // lqi + // "net-layer data" + SPINEL_PROTOCOL_TYPE_THREAD, // type + flags, // flags + aResult->mNetworkName.m8, // network name + aResult->mExtendedPanId.m8, OT_EXT_PAN_ID_SIZE, // xpanid + aResult->mSteeringData.m8, aResult->mSteeringData.mLength // steering data + ); + + if (error != OT_ERROR_NONE) + { + mChangedPropsSet.AddLastStatus(SPINEL_STATUS_NOMEM); + mUpdateChangedPropsTask.Post(); + } } else { @@ -1022,11 +1029,12 @@ void NcpBase::HandleActiveScanResult(otActiveScanResult *aResult) ); // If we could not send the end of scan indicator message now (no - // buffer space), we set `mShouldSignalEndOfScan` to true to send - // it out when buffer space becomes available. + // buffer space), we add the MAC_SCAN_STATE property to changed + // property set so that the update is sent when buffer becomes + // available. if (error != OT_ERROR_NONE) { - mShouldSignalEndOfScan = true; + mChangedPropsSet.AddProperty(SPINEL_PROP_MAC_SCAN_STATE); } } } @@ -1042,17 +1050,23 @@ void NcpBase::HandleEnergyScanResult(otEnergyScanResult *aResult) if (aResult) { - SendPropertyUpdate( - SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, - SPINEL_CMD_PROP_VALUE_INSERTED, - SPINEL_PROP_MAC_ENERGY_SCAN_RESULT, - ( - SPINEL_DATATYPE_UINT8_S // Channel - SPINEL_DATATYPE_INT8_S // Rssi - ), - aResult->mChannel, - aResult->mMaxRssi - ); + error = SendPropertyUpdate( + SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, + SPINEL_CMD_PROP_VALUE_INSERTED, + SPINEL_PROP_MAC_ENERGY_SCAN_RESULT, + ( + SPINEL_DATATYPE_UINT8_S // Channel + SPINEL_DATATYPE_INT8_S // Rssi + ), + aResult->mChannel, + aResult->mMaxRssi + ); + + if (error != OT_ERROR_NONE) + { + mChangedPropsSet.AddLastStatus(SPINEL_STATUS_NOMEM); + mUpdateChangedPropsTask.Post(); + } } else { @@ -1067,11 +1081,12 @@ void NcpBase::HandleEnergyScanResult(otEnergyScanResult *aResult) ); // If we could not send the end of scan indicator message now (no - // buffer space), we set `mShouldSignalEndOfScan` to true to send - // it out when buffer space becomes available. + // buffer space), we add the MAC_SCAN_STATE property to changed + // property set so that the update is sent when buffer becomes + // available. if (error != OT_ERROR_NONE) { - mShouldSignalEndOfScan = true; + mChangedPropsSet.AddProperty(SPINEL_PROP_MAC_SCAN_STATE); } } } @@ -1202,17 +1217,149 @@ void NcpBase::LinkRawEnergyScanDone(int8_t aEnergyScanMaxRssi) #endif // OPENTHREAD_ENABLE_RAW_LINK_API + // ---------------------------------------------------------------------------- -// MARK: Address Table Changed Glue +// MARK: ChangedPropsSet class +// ---------------------------------------------------------------------------- + +// Defines the list of properties that can support unsolicited update. +// +// Note that {`SPINEL_PROP_LAST_STATUS`, `SPINEL_STATUS_RESET_UNKNOWN`} should be first entry to ensure that RESET is +// reported before any other property update. +// +// Since a `uint32_t` is used as bit-mask to track which entries are in the changed set, we should ensure that the +// number of entries in the list is always less than or equal to 32. +// +const NcpBase::ChangedPropsSet::Entry NcpBase::ChangedPropsSet::mSupportedProps[] = +{ + // Spinel property Status (if prop is `LAST_STATUS`) IsFilterable? + + { SPINEL_PROP_LAST_STATUS, SPINEL_STATUS_RESET_UNKNOWN, false }, // 0 + { SPINEL_PROP_STREAM_DEBUG, SPINEL_STATUS_OK, true }, // 1 + { SPINEL_PROP_IPV6_ADDRESS_TABLE, SPINEL_STATUS_OK, true }, // 2 + { SPINEL_PROP_NET_ROLE, SPINEL_STATUS_OK, true }, // 3 + { SPINEL_PROP_IPV6_LL_ADDR, SPINEL_STATUS_OK, true }, // 4 + { SPINEL_PROP_IPV6_ML_ADDR, SPINEL_STATUS_OK, true }, // 5 + { SPINEL_PROP_NET_PARTITION_ID, SPINEL_STATUS_OK, true }, // 6 + { SPINEL_PROP_NET_KEY_SEQUENCE_COUNTER, SPINEL_STATUS_OK, true }, // 7 + { SPINEL_PROP_THREAD_LEADER_NETWORK_DATA, SPINEL_STATUS_OK, true }, // 8 + { SPINEL_PROP_THREAD_CHILD_TABLE, SPINEL_STATUS_OK, true }, // 9 + { SPINEL_PROP_THREAD_ON_MESH_NETS, SPINEL_STATUS_OK, true }, // 10 + { SPINEL_PROP_THREAD_OFF_MESH_ROUTES, SPINEL_STATUS_OK, true }, // 11 + { SPINEL_PROP_NET_STACK_UP, SPINEL_STATUS_OK, true }, // 12 + { SPINEL_PROP_NET_REQUIRE_JOIN_EXISTING, SPINEL_STATUS_OK, true }, // 13 + { SPINEL_PROP_LAST_STATUS, SPINEL_STATUS_NOMEM, true }, // 14 + { SPINEL_PROP_LAST_STATUS, SPINEL_STATUS_DROPPED, true }, // 15 +#if OPENTHREAD_ENABLE_JAM_DETECTION + { SPINEL_PROP_JAM_DETECTED, SPINEL_STATUS_OK, true }, // 16 +#endif +#if OPENTHREAD_ENABLE_LEGACY + { SPINEL_PROP_NEST_LEGACY_ULA_PREFIX, SPINEL_STATUS_OK, true }, // 17 + { SPINEL_PROP_NEST_LEGACY_LAST_NODE_JOINED, SPINEL_STATUS_OK, true }, // 18 +#endif + { SPINEL_PROP_LAST_STATUS, SPINEL_STATUS_JOIN_FAILURE, false }, // 19 + { SPINEL_PROP_MAC_SCAN_STATE, SPINEL_STATUS_OK, false }, // 20 +}; + +uint8_t NcpBase::ChangedPropsSet::GetNumEntries(void) const +{ + return (sizeof(mSupportedProps) / sizeof(mSupportedProps[0])); +} + +void NcpBase::ChangedPropsSet::Add(spinel_prop_key_t aPropKey, spinel_status_t aStatus) +{ + uint8_t numEntries; + const Entry *entry; + + entry = GetSupportedEntries(numEntries); + + for (uint8_t index = 0; index < numEntries; index++, entry++) + { + if ((entry->mPropKey == aPropKey) && (entry->mStatus == aStatus)) + { + if (!IsEntryFiltered(index)) + { + SetBit(mChangedSet, index); + } + + break; + } + } +} + +otError NcpBase::ChangedPropsSet::EnablePropertyFilter(spinel_prop_key_t aPropKey, bool aEnable) +{ + uint8_t numEntries; + const Entry *entry; + bool didFind = false; + + entry = GetSupportedEntries(numEntries); + + for (uint8_t index = 0; index < numEntries; index++, entry++) + { + if (entry->mFilterable && (entry->mPropKey == aPropKey)) + { + if (aEnable) + { + SetBit(mFilterSet, index); + + // If filter is enabled for a property, the `mChangedSet` is cleared + // for the same property so to ensure a pending update is also filtered. + + ClearBit(mChangedSet, index); + } + else + { + ClearBit(mFilterSet, index); + } + + didFind = true; + + // Continue the search only if the prop key is `LAST_STATUS`, as + // we have multiple filterable `LAST_STATUS` entries in the table + // with different error status (DROPPED and NOMEM). + + if (aPropKey != SPINEL_PROP_LAST_STATUS) + { + break; + } + } + } + + return didFind ? OT_ERROR_NONE : OT_ERROR_INVALID_ARGS; +} + +bool NcpBase::ChangedPropsSet::IsPropertyFiltered(spinel_prop_key_t aPropKey) const +{ + bool isFiltered = false; + uint8_t numEntries; + const Entry *entry; + + entry = GetSupportedEntries(numEntries); + + for (uint8_t index = 0; index < numEntries; index++, entry++) + { + if (entry->mFilterable && (entry->mPropKey == aPropKey)) + { + isFiltered = IsEntryFiltered(index); + + break; + } + } + + return isFiltered; +} + +// ---------------------------------------------------------------------------- +// MARK: Property/Status Changed // ---------------------------------------------------------------------------- void NcpBase::HandleNetifStateChanged(uint32_t aFlags, void *aContext) { - NcpBase *obj = static_cast(aContext); + NcpBase *ncp = static_cast(aContext); - obj->mChangedFlags |= aFlags; - - obj->mUpdateChangedPropsTask.Post(); + ncp->mThreadChangedFlags |= aFlags; + ncp->mUpdateChangedPropsTask.Post(); } void NcpBase::UpdateChangedProps(Tasklet &aTasklet) @@ -1223,190 +1370,133 @@ void NcpBase::UpdateChangedProps(Tasklet &aTasklet) void NcpBase::UpdateChangedProps(void) { - while (mChangedFlags != 0) + uint8_t numEntries; + spinel_prop_key_t propKey; + const ChangedPropsSet::Entry *entry; + + ProcessThreadChangedFlags(); + + VerifyOrExit(!mChangedPropsSet.IsEmpty()); + + entry = mChangedPropsSet.GetSupportedEntries(numEntries); + + for (uint8_t index = 0; index < numEntries; index++, entry++) { - if ((mChangedFlags & NCP_CHANGED_PLATFORM_RESET) != 0) + if (!mChangedPropsSet.IsEntryChanged(index)) { - SuccessOrExit( - SendLastStatus( - SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, - ResetReasonToSpinelStatus(otPlatGetResetReason(mInstance)) - )); - - mChangedFlags &= ~static_cast(NCP_CHANGED_PLATFORM_RESET); + continue; } - else if ((mChangedFlags & OT_CHANGED_THREAD_LL_ADDR) != 0) - { - SuccessOrExit( - HandleCommandPropertyGet( - SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, - SPINEL_PROP_IPV6_LL_ADDR - )); - mChangedFlags &= ~static_cast(OT_CHANGED_THREAD_LL_ADDR); - } - else if ((mChangedFlags & OT_CHANGED_THREAD_ML_ADDR) != 0) - { - SuccessOrExit( - HandleCommandPropertyGet( - SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, - SPINEL_PROP_IPV6_ML_ADDR - )); + propKey = entry->mPropKey; - mChangedFlags &= ~static_cast(OT_CHANGED_THREAD_ML_ADDR); - } - else if ((mChangedFlags & OT_CHANGED_THREAD_ROLE) != 0) + if (propKey == SPINEL_PROP_LAST_STATUS) { - if (mRequireJoinExistingNetwork) + spinel_status_t status = entry->mStatus; + + if (status == SPINEL_STATUS_RESET_UNKNOWN) { - switch (otThreadGetDeviceRole(mInstance)) - { - case OT_DEVICE_ROLE_DETACHED: - case OT_DEVICE_ROLE_DISABLED: - break; - - default: - mRequireJoinExistingNetwork = false; - break; - } - - if ((otThreadGetDeviceRole(mInstance) == OT_DEVICE_ROLE_LEADER) - && otThreadIsSingleton(mInstance) -#if OPENTHREAD_ENABLE_LEGACY - && !mLegacyNodeDidJoin -#endif - ) - { - mChangedFlags &= ~static_cast(OT_CHANGED_THREAD_PARTITION_ID); - otThreadSetEnabled(mInstance, false); - - // TODO: It would be nice to be able to indicate - // something more specific than SPINEL_STATUS_JOIN_FAILURE - // here, but it isn't clear how that would work - // with the current OpenThread API. - - SuccessOrExit( - SendLastStatus( - SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, - SPINEL_STATUS_JOIN_FAILURE - )); - - SuccessOrExit( - HandleCommandPropertyGet( - SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, - SPINEL_PROP_NET_STACK_UP - )); - } - - SuccessOrExit( - HandleCommandPropertyGet( - SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, - SPINEL_PROP_NET_REQUIRE_JOIN_EXISTING - )); + status = ResetReasonToSpinelStatus(otPlatGetResetReason(mInstance)); } - SuccessOrExit( - HandleCommandPropertyGet( - SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, - SPINEL_PROP_NET_ROLE - )); - - mChangedFlags &= ~static_cast(OT_CHANGED_THREAD_ROLE); + SuccessOrExit(SendLastStatus(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, status)); } - else if ((mChangedFlags & OT_CHANGED_THREAD_PARTITION_ID) != 0) + else { - SuccessOrExit( - HandleCommandPropertyGet( - SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, - SPINEL_PROP_NET_PARTITION_ID - )); - - mChangedFlags &= ~static_cast(OT_CHANGED_THREAD_PARTITION_ID); + SuccessOrExit(HandleCommandPropertyGet(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, propKey)); } - else if ((mChangedFlags & OT_CHANGED_THREAD_KEY_SEQUENCE_COUNTER) != 0) + + mChangedPropsSet.RemoveEntry(index); + VerifyOrExit(!mChangedPropsSet.IsEmpty()); + } + +exit: + return; +} + +void NcpBase::ProcessThreadChangedFlags(void) +{ + static const struct + { + uint32_t mThreadFlag; + spinel_prop_key_t mPropKey; + } kFlags[] = + { + { OT_CHANGED_IP6_ADDRESS_ADDED, SPINEL_PROP_IPV6_ADDRESS_TABLE }, + { OT_CHANGED_IP6_ADDRESS_REMOVED, SPINEL_PROP_IPV6_ADDRESS_TABLE }, + { OT_CHANGED_THREAD_ROLE, SPINEL_PROP_NET_ROLE }, + { OT_CHANGED_THREAD_LL_ADDR, SPINEL_PROP_IPV6_LL_ADDR }, + { OT_CHANGED_THREAD_ML_ADDR, SPINEL_PROP_IPV6_ML_ADDR }, + { OT_CHANGED_THREAD_PARTITION_ID, SPINEL_PROP_NET_PARTITION_ID }, + { OT_CHANGED_THREAD_KEY_SEQUENCE_COUNTER, SPINEL_PROP_NET_KEY_SEQUENCE_COUNTER }, + { OT_CHANGED_THREAD_NETDATA, SPINEL_PROP_THREAD_LEADER_NETWORK_DATA }, + { OT_CHANGED_THREAD_CHILD_ADDED, SPINEL_PROP_THREAD_CHILD_TABLE }, + { OT_CHANGED_THREAD_CHILD_REMOVED, SPINEL_PROP_THREAD_CHILD_TABLE }, + { OT_CHANGED_IP6_MULTICAST_SUBSRCRIBED, SPINEL_PROP_IPV6_MULTICAST_ADDRESS_TABLE }, + { OT_CHANGED_IP6_MULTICAST_UNSUBSRCRIBED, SPINEL_PROP_IPV6_MULTICAST_ADDRESS_TABLE }, + }; + + VerifyOrExit(mThreadChangedFlags != 0); + + // If thread role has changed, check for possible "join" error. + + if ((mThreadChangedFlags & OT_CHANGED_THREAD_ROLE) != 0) + { + if (mRequireJoinExistingNetwork) { - SuccessOrExit( - HandleCommandPropertyGet( - SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, - SPINEL_PROP_NET_KEY_SEQUENCE_COUNTER - )); + switch (otThreadGetDeviceRole(mInstance)) + { + case OT_DEVICE_ROLE_DETACHED: + case OT_DEVICE_ROLE_DISABLED: + break; - mChangedFlags &= ~static_cast(OT_CHANGED_THREAD_KEY_SEQUENCE_COUNTER); - } - else if ((mChangedFlags & (OT_CHANGED_IP6_ADDRESS_ADDED | OT_CHANGED_IP6_ADDRESS_REMOVED)) != 0) - { - SuccessOrExit( - HandleCommandPropertyGet( - SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, - SPINEL_PROP_IPV6_ADDRESS_TABLE - )); + default: + mRequireJoinExistingNetwork = false; + mChangedPropsSet.AddProperty(SPINEL_PROP_NET_REQUIRE_JOIN_EXISTING); + break; + } - mChangedFlags &= ~static_cast(OT_CHANGED_IP6_ADDRESS_ADDED | OT_CHANGED_IP6_ADDRESS_REMOVED); - } - else if ((mChangedFlags & (OT_CHANGED_IP6_MULTICAST_SUBSRCRIBED | OT_CHANGED_IP6_MULTICAST_UNSUBSRCRIBED)) != 0) - { - SuccessOrExit( - HandleCommandPropertyGet( - SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, - SPINEL_PROP_IPV6_MULTICAST_ADDRESS_TABLE - )); + if ((otThreadGetDeviceRole(mInstance) == OT_DEVICE_ROLE_LEADER) + && otThreadIsSingleton(mInstance) +#if OPENTHREAD_ENABLE_LEGACY + && !mLegacyNodeDidJoin +#endif + ) + { + mThreadChangedFlags &= ~static_cast(OT_CHANGED_THREAD_PARTITION_ID); + otThreadSetEnabled(mInstance, false); - mChangedFlags &= ~static_cast(OT_CHANGED_IP6_MULTICAST_SUBSRCRIBED | - OT_CHANGED_IP6_MULTICAST_UNSUBSRCRIBED); - } - else if ((mChangedFlags & (OT_CHANGED_THREAD_CHILD_ADDED | OT_CHANGED_THREAD_CHILD_REMOVED)) != 0) - { - SuccessOrExit( - HandleCommandPropertyGet( - SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, - SPINEL_PROP_THREAD_CHILD_TABLE - )); - - mChangedFlags &= ~static_cast(OT_CHANGED_THREAD_CHILD_ADDED | OT_CHANGED_THREAD_CHILD_REMOVED); - } - else if ((mChangedFlags & OT_CHANGED_THREAD_NETDATA) != 0) - { - SuccessOrExit( - HandleCommandPropertyGet( - SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, - SPINEL_PROP_THREAD_LEADER_NETWORK_DATA - )); - - mChangedFlags &= ~static_cast(OT_CHANGED_THREAD_NETDATA); - - // If the network data is updated, after successfully sending (or queuing) the - // network data spinel message, we add `NCP_CHANGED_THREAD_ON_MESH_NETS` and - // `NCP_CHANGED_THREAD_OFF_MESH_ROUTES` to the `mChangedFlags` so that we - // separately send the list of on-mesh prefixes and off-mesh routes. - - mChangedFlags |= NCP_CHANGED_THREAD_ON_MESH_NETS | NCP_CHANGED_THREAD_OFF_MESH_ROUTES; - } - else if ((mChangedFlags & NCP_CHANGED_THREAD_ON_MESH_NETS) != 0) - { - SuccessOrExit( - HandleCommandPropertyGet( - SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, - SPINEL_PROP_THREAD_ON_MESH_NETS - )); - - mChangedFlags &= ~static_cast(NCP_CHANGED_THREAD_ON_MESH_NETS); - } - else if ((mChangedFlags & NCP_CHANGED_THREAD_OFF_MESH_ROUTES) != 0) - { - SuccessOrExit( - HandleCommandPropertyGet( - SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, - SPINEL_PROP_THREAD_OFF_MESH_ROUTES - )); - - mChangedFlags &= ~static_cast(NCP_CHANGED_THREAD_OFF_MESH_ROUTES); - } - else if ((mChangedFlags & (OT_CHANGED_THREAD_RLOC_ADDED | OT_CHANGED_THREAD_RLOC_REMOVED)) != 0) - { - mChangedFlags &= ~static_cast(OT_CHANGED_THREAD_RLOC_ADDED | OT_CHANGED_THREAD_RLOC_REMOVED); + mChangedPropsSet.AddProperty(SPINEL_PROP_NET_STACK_UP); + mChangedPropsSet.AddLastStatus(SPINEL_STATUS_JOIN_FAILURE); + } } } + // Convert OT_CHANGED flags to corresponding NCP property update. + + for (unsigned i = 0; i < sizeof(kFlags) / sizeof(kFlags[0]); i++) + { + uint32_t threadFlag = kFlags[i].mThreadFlag; + + if (mThreadChangedFlags & threadFlag) + { + mChangedPropsSet.AddProperty(kFlags[i].mPropKey); + + if (threadFlag == OT_CHANGED_THREAD_NETDATA) + { + mChangedPropsSet.AddProperty(SPINEL_PROP_THREAD_ON_MESH_NETS); + mChangedPropsSet.AddProperty(SPINEL_PROP_THREAD_OFF_MESH_ROUTES); + } + + mThreadChangedFlags &= ~threadFlag; + VerifyOrExit(mThreadChangedFlags != 0); + } + } + + // Clear any remaining ThreadFlag that has no matching + // NCP property update (e.g., OT_CHANGED_THREAD_RLOC_ADDED) + + mThreadChangedFlags = 0; + exit: return; } @@ -1543,38 +1633,6 @@ void NcpBase::HandleFrameRemovedFromNcpBuffer(NcpFrameBuffer::FrameTag aFrameTag while ((mDroppedReplyTidBitSet & (1 << mDroppedReplyTid)) == 0); } - if (mShouldSignalEndOfScan) - { - SuccessOrExit( - SendPropertyUpdate( - SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, - SPINEL_CMD_PROP_VALUE_IS, - SPINEL_PROP_MAC_SCAN_STATE, - SPINEL_DATATYPE_UINT8_S, - SPINEL_SCAN_STATE_IDLE - )); - - mShouldSignalEndOfScan = false; - } - -#if OPENTHREAD_ENABLE_JAM_DETECTION - - if (mShouldSignalJamStateChange) - { - SuccessOrExit( - SendPropertyUpdate( - SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, - SPINEL_CMD_PROP_VALUE_IS, - SPINEL_PROP_JAM_DETECTED, - SPINEL_DATATYPE_BOOL_S, - otJamDetectionGetState(mInstance) - )); - - mShouldSignalJamStateChange = false; - } - -#endif // OPENTHREAD_ENABLE_JAM_DETECTION - if (mHostPowerStateHeader) { SuccessOrExit( @@ -1902,7 +1960,7 @@ otError NcpBase::CommandHandler_RESET(uint8_t aHeader, unsigned int aCommand, co if (error != OT_ERROR_NONE) { - mChangedFlags |= NCP_CHANGED_PLATFORM_RESET; + mChangedPropsSet.AddLastStatus(SPINEL_STATUS_RESET_UNKNOWN); mUpdateChangedPropsTask.Post(); } @@ -2239,6 +2297,7 @@ otError NcpBase::GetPropertyHandler_CAPS(uint8_t aHeader, spinel_prop_key_t aKey SuccessOrExit(error = OutboundFrameFeedPacked(SPINEL_DATATYPE_UINT_PACKED_S, SPINEL_CAP_NET_THREAD_1_0)); SuccessOrExit(error = OutboundFrameFeedPacked(SPINEL_DATATYPE_UINT_PACKED_S, SPINEL_CAP_COUNTERS)); + SuccessOrExit(error = OutboundFrameFeedPacked(SPINEL_DATATYPE_UINT_PACKED_S, SPINEL_CAP_UNSOL_UPDATE_FILTER)); #if OPENTHREAD_ENABLE_MAC_FILTER SuccessOrExit(error = OutboundFrameFeedPacked(SPINEL_DATATYPE_UINT_PACKED_S, SPINEL_CAP_MAC_WHITELIST)); @@ -2352,6 +2411,68 @@ otError NcpBase::GetPropertyHandler_HOST_POWER_STATE(uint8_t aHeader, spinel_pro ); } +otError NcpBase::GetPropertyHandler_UNSOL_UPDATE_FILTER(uint8_t aHeader, spinel_prop_key_t aKey) +{ + otError error = OT_ERROR_NONE; + uint8_t numEntries; + const ChangedPropsSet::Entry *entry; + + SuccessOrExit(error = OutboundFrameBegin(aHeader)); + SuccessOrExit( + error = OutboundFrameFeedPacked( + SPINEL_DATATYPE_COMMAND_PROP_S, + aHeader, + SPINEL_CMD_PROP_VALUE_IS, + aKey + )); + + entry = mChangedPropsSet.GetSupportedEntries(numEntries); + + for (uint8_t index = 0; index < numEntries; index++, entry++) + { + if (mChangedPropsSet.IsEntryFiltered(index)) + { + SuccessOrExit(error = OutboundFrameFeedPacked(SPINEL_DATATYPE_UINT_PACKED_S, entry->mPropKey)); + } + } + + SuccessOrExit(error = OutboundFrameSend()); + +exit: + return error; +} + +otError NcpBase::GetPropertyHandler_UNSOL_UPDATE_LIST(uint8_t aHeader, spinel_prop_key_t aKey) +{ + otError error = OT_ERROR_NONE; + uint8_t numEntries; + const ChangedPropsSet::Entry *entry; + + SuccessOrExit(error = OutboundFrameBegin(aHeader)); + SuccessOrExit( + error = OutboundFrameFeedPacked( + SPINEL_DATATYPE_COMMAND_PROP_S, + aHeader, + SPINEL_CMD_PROP_VALUE_IS, + aKey + )); + + entry = mChangedPropsSet.GetSupportedEntries(numEntries); + + for (uint8_t index = 0; index < numEntries; index++, entry++) + { + if (entry->mFilterable) + { + SuccessOrExit(error = OutboundFrameFeedPacked(SPINEL_DATATYPE_UINT_PACKED_S, entry->mPropKey)); + } + } + + SuccessOrExit(error = OutboundFrameSend()); + +exit: + return error; +} + otError NcpBase::GetPropertyHandler_PHY_ENABLED(uint8_t aHeader, spinel_prop_key_t aKey) { return SendPropertyUpdate( @@ -4342,6 +4463,23 @@ otError NcpBase::GetPropertyHandler_NEST_LEGACY_ULA_PREFIX(uint8_t aHeader, spin sizeof(mLegacyUlaPrefix) ); } + +otError NcpBase::GetPropertyHandler_NEST_LEGACY_LAST_NODE_JOINED(uint8_t aHeader, spinel_prop_key_t aKey) +{ + if (!mLegacyNodeDidJoin) + { + memset(&mLegacyLastJoinedNode, 0, sizeof(mLegacyLastJoinedNode)); + } + + return SendPropertyUpdate( + aHeader, + SPINEL_CMD_PROP_VALUE_IS, + aKey, + SPINEL_DATATYPE_EUI64_S, + &mLegacyLastJoinedNode, + sizeof(mLegacyLastJoinedNode) + ); +} #endif // OPENTHREAD_ENABLE_LEGACY // ---------------------------------------------------------------------------- @@ -4454,6 +4592,51 @@ otError NcpBase::SetPropertyHandler_HOST_POWER_STATE(uint8_t aHeader, spinel_pro return error; } +otError NcpBase::SetPropertyHandler_UNSOL_UPDATE_FILTER(uint8_t aHeader, spinel_prop_key_t aKey, + const uint8_t *aValuePtr, uint16_t aValueLen) +{ + spinel_ssize_t parsedLength; + unsigned int propKey; + otError error = OT_ERROR_NONE; + bool reportAsync; + + // First clear the current filter. + mChangedPropsSet.ClearFilter(); + + while (aValueLen > 0) + { + parsedLength = spinel_datatype_unpack( + aValuePtr, + aValueLen, + SPINEL_DATATYPE_UINT_PACKED_S, + &propKey + ); + + VerifyOrExit(parsedLength > 0, error = OT_ERROR_PARSE); + + IgnoreReturnValue(mChangedPropsSet.EnablePropertyFilter(static_cast(propKey), true)); + + aValuePtr += parsedLength; + aValueLen -= parsedLength; + } + +exit: + // If we had an error, we may have actually changed + // the state of the filter---so we need to report + // those incomplete changes via an asynchronous + // change event. + reportAsync = (error != OT_ERROR_NONE); + + error = SendSetPropertyResponse(aHeader, aKey, error); + + if (reportAsync) + { + HandleCommandPropertyGet(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, aKey); + } + + return error; +} + #if OPENTHREAD_ENABLE_RAW_LINK_API otError NcpBase::SetPropertyHandler_PHY_ENABLED(uint8_t aHeader, spinel_prop_key_t aKey, const uint8_t *aValuePtr, @@ -4719,7 +4902,6 @@ otError NcpBase::SetPropertyHandler_MAC_SCAN_STATE(uint8_t aHeader, spinel_prop_ } SuccessOrExit(error); - mShouldSignalEndOfScan = false; break; case SPINEL_SCAN_STATE_ENERGY: @@ -4756,7 +4938,6 @@ otError NcpBase::SetPropertyHandler_MAC_SCAN_STATE(uint8_t aHeader, spinel_prop_ } SuccessOrExit(error); - mShouldSignalEndOfScan = false; break; case SPINEL_SCAN_STATE_DISCOVER: @@ -4771,7 +4952,6 @@ otError NcpBase::SetPropertyHandler_MAC_SCAN_STATE(uint8_t aHeader, spinel_prop_ ); SuccessOrExit(error); - mShouldSignalEndOfScan = false; break; default: @@ -6613,23 +6793,10 @@ void NcpBase::HandleJamStateChange_Jump(bool aJamState, void *aContext) void NcpBase::HandleJamStateChange(bool aJamState) { - otError error; + OT_UNUSED_VARIABLE(aJamState); - error = SendPropertyUpdate( - SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, - SPINEL_CMD_PROP_VALUE_IS, - SPINEL_PROP_JAM_DETECTED, - SPINEL_DATATYPE_BOOL_S, - aJamState - ); - - // If we could not send the jam state change indicator (no - // buffer space), we set `mShouldSignalJamStateChange` to true to send - // it out when buffer space becomes available. - if (error != OT_ERROR_NONE) - { - mShouldSignalJamStateChange = true; - } + mChangedPropsSet.AddProperty(SPINEL_PROP_JAM_DETECTED); + mUpdateChangedPropsTask.Post(); } #endif // OPENTHREAD_ENABLE_JAM_DETECTION @@ -6712,6 +6879,44 @@ exit: // MARK: Individual Property Inserters // ---------------------------------------------------------------------------- +otError NcpBase::InsertPropertyHandler_UNSOL_UPDATE_FILTER(uint8_t aHeader, spinel_prop_key_t aKey, + const uint8_t *aValuePtr, uint16_t aValueLen) +{ + spinel_ssize_t parsedLength; + otError error = OT_ERROR_NONE; + spinel_status_t spinelError = SPINEL_STATUS_OK; + unsigned int propKey; + + parsedLength = spinel_datatype_unpack( + aValuePtr, + aValueLen, + SPINEL_DATATYPE_UINT_PACKED_S, + &propKey + ); + + VerifyOrExit(parsedLength > 0, spinelError = SPINEL_STATUS_PARSE_ERROR); + + error = mChangedPropsSet.EnablePropertyFilter(static_cast(propKey), true); + VerifyOrExit(error == OT_ERROR_NONE, spinelError = ThreadErrorToSpinelStatus(error)); + + error = SendPropertyUpdate( + aHeader, + SPINEL_CMD_PROP_VALUE_INSERTED, + aKey, + aValuePtr, + aValueLen + ); + +exit: + + if (spinelError != SPINEL_STATUS_OK) + { + error = SendLastStatus(aHeader, spinelError); + } + + return error; +} + #if OPENTHREAD_ENABLE_RAW_LINK_API otError NcpBase::InsertPropertyHandler_MAC_SRC_MATCH_SHORT_ADDRESSES(uint8_t aHeader, spinel_prop_key_t aKey, @@ -7277,6 +7482,44 @@ exit: // MARK: Individual Property Removers // ---------------------------------------------------------------------------- +otError NcpBase::RemovePropertyHandler_UNSOL_UPDATE_FILTER(uint8_t aHeader, spinel_prop_key_t aKey, + const uint8_t *aValuePtr, uint16_t aValueLen) +{ + spinel_ssize_t parsedLength; + otError error = OT_ERROR_NONE; + spinel_status_t spinelError = SPINEL_STATUS_OK; + unsigned int propKey; + + parsedLength = spinel_datatype_unpack( + aValuePtr, + aValueLen, + SPINEL_DATATYPE_UINT_PACKED_S, + &propKey + ); + + VerifyOrExit(parsedLength > 0, spinelError = SPINEL_STATUS_PARSE_ERROR); + + error = mChangedPropsSet.EnablePropertyFilter(static_cast(propKey), false); + VerifyOrExit(error == OT_ERROR_NONE, spinelError = ThreadErrorToSpinelStatus(error)); + + error = SendPropertyUpdate( + aHeader, + SPINEL_CMD_PROP_VALUE_INSERTED, + aKey, + aValuePtr, + aValueLen + ); + +exit: + + if (spinelError != SPINEL_STATUS_OK) + { + error = SendLastStatus(aHeader, spinelError); + } + + return error; +} + #if OPENTHREAD_ENABLE_RAW_LINK_API otError NcpBase::RemovePropertyHandler_MAC_SRC_MATCH_SHORT_ADDRESSES(uint8_t aHeader, spinel_prop_key_t aKey, const uint8_t *aValuePtr, uint16_t aValueLen) @@ -7771,48 +8014,17 @@ exit: void NcpBase::HandleDidReceiveNewLegacyUlaPrefix(const uint8_t *aUlaPrefix) { - uint8_t header = SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0; - memcpy(mLegacyUlaPrefix, aUlaPrefix, OT_NCP_LEGACY_ULA_PREFIX_LENGTH); - - SuccessOrExit(OutboundFrameBegin(header)); - - SuccessOrExit( - OutboundFrameFeedPacked( - SPINEL_DATATYPE_COMMAND_PROP_S SPINEL_DATATYPE_DATA_S, - header, - SPINEL_CMD_PROP_VALUE_IS, - SPINEL_PROP_NEST_LEGACY_ULA_PREFIX, - aUlaPrefix, OT_NCP_LEGACY_ULA_PREFIX_LENGTH - )); - - SuccessOrExit(OutboundFrameSend()); - -exit: - return; + mChangedPropsSet.AddProperty(SPINEL_PROP_NEST_LEGACY_ULA_PREFIX); + mUpdateChangedPropsTask.Post(); } void NcpBase::HandleLegacyNodeDidJoin(const otExtAddress *aExtAddr) { - uint8_t header = SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0; - mLegacyNodeDidJoin = true; - - SuccessOrExit(OutboundFrameBegin(header)); - - SuccessOrExit( - OutboundFrameFeedPacked( - SPINEL_DATATYPE_COMMAND_PROP_S SPINEL_DATATYPE_EUI64_S, - header, - SPINEL_CMD_PROP_VALUE_IS, - SPINEL_PROP_NEST_LEGACY_JOINED_NODE, - aExtAddr->m8 - )); - - SuccessOrExit(OutboundFrameSend()); - -exit: - return; + mLegacyLastJoinedNode = *aExtAddr; + mChangedPropsSet.AddProperty(SPINEL_PROP_NEST_LEGACY_LAST_NODE_JOINED); + mUpdateChangedPropsTask.Post(); } #endif // OPENTHREAD_ENABLE_LEGACY @@ -7820,22 +8032,34 @@ exit: otError NcpBase::StreamWrite(int aStreamId, const uint8_t *aDataPtr, int aDataLen) { otError error = OT_ERROR_NONE; + spinel_prop_key_t streamPropKey; if (aStreamId == 0) { - aStreamId = SPINEL_PROP_STREAM_DEBUG; + streamPropKey = SPINEL_PROP_STREAM_DEBUG; + } + else + { + streamPropKey = static_cast(aStreamId); } VerifyOrExit(!mDisableStreamWrite, error = OT_ERROR_INVALID_STATE); + VerifyOrExit(!mChangedPropsSet.IsPropertyFiltered(streamPropKey), error = OT_ERROR_INVALID_STATE); error = SendPropertyUpdate( SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_CMD_PROP_VALUE_IS, - static_cast(aStreamId), + streamPropKey, aDataPtr, static_cast(aDataLen) ); + if (error != OT_ERROR_NONE) + { + mChangedPropsSet.AddLastStatus(SPINEL_STATUS_NOMEM); + mUpdateChangedPropsTask.Post(); + } + exit: return error; } diff --git a/src/ncp/ncp_base.hpp b/src/ncp/ncp_base.hpp index fd1971fa4..87e32634e 100644 --- a/src/ncp/ncp_base.hpp +++ b/src/ncp/ncp_base.hpp @@ -214,6 +214,7 @@ private: static void UpdateChangedProps(Tasklet &aTasklet); void UpdateChangedProps(void); + void ProcessThreadChangedFlags(void); static void SendDoneTask(void *aContext); void SendDoneTask(void); @@ -344,6 +345,8 @@ private: NCP_GET_PROP_HANDLER(HWADDR); NCP_GET_PROP_HANDLER(LOCK); NCP_GET_PROP_HANDLER(HOST_POWER_STATE); + NCP_GET_PROP_HANDLER(UNSOL_UPDATE_FILTER); + NCP_GET_PROP_HANDLER(UNSOL_UPDATE_LIST); NCP_GET_PROP_HANDLER(PHY_ENABLED); NCP_GET_PROP_HANDLER(PHY_FREQ); NCP_GET_PROP_HANDLER(PHY_CHAN_SUPPORTED); @@ -444,6 +447,7 @@ private: #endif #if OPENTHREAD_ENABLE_LEGACY NCP_GET_PROP_HANDLER(NEST_LEGACY_ULA_PREFIX); + NCP_GET_PROP_HANDLER(NEST_LEGACY_LAST_NODE_JOINED); #endif // Property Set Handlers @@ -452,6 +456,7 @@ private: NCP_SET_PROP_HANDLER(HOST_POWER_STATE); NCP_SET_PROP_HANDLER(PHY_TX_POWER); NCP_SET_PROP_HANDLER(PHY_CHAN); + NCP_SET_PROP_HANDLER(UNSOL_UPDATE_FILTER); NCP_SET_PROP_HANDLER(MAC_SCAN_MASK); NCP_SET_PROP_HANDLER(MAC_SCAN_STATE); NCP_SET_PROP_HANDLER(MAC_15_4_PANID); @@ -539,6 +544,7 @@ private: // Property Insert Handlers + NCP_INSERT_PROP_HANDLER(UNSOL_UPDATE_FILTER); #if OPENTHREAD_ENABLE_RAW_LINK_API NCP_INSERT_PROP_HANDLER(MAC_SRC_MATCH_SHORT_ADDRESSES); NCP_INSERT_PROP_HANDLER(MAC_SRC_MATCH_EXTENDED_ADDRESSES); @@ -561,6 +567,7 @@ private: // Property Remove Handlers + NCP_REMOVE_PROP_HANDLER(UNSOL_UPDATE_FILTER); #if OPENTHREAD_ENABLE_RAW_LINK_API NCP_REMOVE_PROP_HANDLER(MAC_SRC_MATCH_SHORT_ADDRESSES); NCP_REMOVE_PROP_HANDLER(MAC_SRC_MATCH_EXTENDED_ADDRESSES); @@ -602,6 +609,169 @@ protected: NcpFrameBuffer mTxFrameBuffer; private: + + /** + * Defines a class to track a set of property/status changes that require update to host. The properties that can + * be added to this set must support sending unsolicited updates. This class also provides mechanism for user + * to block certain filterable properties disallowing the unsolicited update from them. + * + */ + class ChangedPropsSet + { + public: + /** + * Defines an entry in the set/list. + * + */ + struct Entry + { + spinel_prop_key_t mPropKey; ///< The spinel property key. + spinel_status_t mStatus; ///< The spinel status (used only if prop key is `LAST_STATUS`). + bool mFilterable; ///< Indicates whether the entry can be filtered + }; + + /** + * This constructor initializes the set. + * + */ + ChangedPropsSet(void): + mChangedSet(0), + mFilterSet(0) + { } + + /** + * This method clears the set. + * + */ + void Clear(void) { mChangedSet = 0; } + + /** + * This method indicates if the set is empty or not. + * + * @returns TRUE if the set if empty, FALSE otherwise. + * + */ + bool IsEmpty(void) const { return (mChangedSet == 0); } + + /** + * This method adds a property to the set. The property added must be in the list of supported properties + * capable of sending unsolicited update, otherwise the input is ignored. + * + * Note that if the property is already in the set, adding it again does not change the set. + * + * @param[in] aPropKey The spinel property key to be added to the set + * + */ + void AddProperty(spinel_prop_key_t aPropKey) { Add(aPropKey, SPINEL_STATUS_OK); } + + /** + * This method adds a `LAST_STATUS` update to the set. The update must be in list of supported entries. + * + * @param[in] aStatus The spinel status update to be added to set. + * + */ + void AddLastStatus(spinel_status_t aStatus) { Add(SPINEL_PROP_LAST_STATUS, aStatus); } + + /** + * This method returns a pointer to array of entries of supported property/status updates. The list includes + * all properties that can generate unsolicited update. + * + * @param[out] aNumEntries A reference to output the number of entries in the list. + * + * @returns A pointer to the supported entries array. + * + */ + const Entry *GetSupportedEntries(uint8_t &aNumEntries) const { + aNumEntries = GetNumEntries(); + return &mSupportedProps[0]; + } + + /** + * This method returns a pointer to the entry associated with a given index. + * + * @param[in] aIndex The index to an entry. + * + * @returns A pointer to the entry associated with @p aIndex, or NULL if the index is beyond end of array. + * + */ + const Entry *GetEntry(uint8_t aIndex) const { + return (aIndex < GetNumEntries()) ? &mSupportedProps[aIndex] : NULL; + } + + /** + * This method indicates if the entry associated with an index is in the set (i.e., it has been changed and + * requires an unsolicited update). + * + * @param[in] aIndex The index to an entry. + * + * @returns TRUE if the entry is in the set, FALSE otherwise. + * + */ + bool IsEntryChanged(uint8_t aIndex) const { return IsBitSet(mChangedSet, aIndex); } + + /** + * This method removes an entry associated with an index in the set. + * + * Note that if the property/entry is not in the set, removing it simply does nothing. + * + * @param[in] aIndex Index of entry to be removed. + * + */ + void RemoveEntry(uint8_t aIndex) { ClearBit(mChangedSet, aIndex); } + + /** + * This method enables/disables filtering of a given property. + * + * @param[in] aPropKey The property key to filter. + * @param[in] aEnable TRUE to enable filtering, FALSE to disable. + * + * @retval OT_ERROR_NONE Filter state for given property updated successfully. + * @retval OT_ERROR_INVALID_ARGS The given property is not valid (i.e., not capable of unsolicited update). + * + */ + otError EnablePropertyFilter(spinel_prop_key_t aPropKey, bool aEnable); + + /** + * This method determines whether filtering is enabled for an entry associated with an index. + * + * @param[in] aIndex Index of entry to be checked. + * + * @returns TRUE if the filter is enabled for the given entry, FALSE otherwise. + * + */ + bool IsEntryFiltered(uint8_t aIndex) const { return IsBitSet(mFilterSet, aIndex); } + + /** + * This method determines whether filtering is enabled for a given property key. + * + * @param[in] aPropKey The property key to check. + * + * @returns TRUE if the filter is enabled for the given property, FALSE if the property is not filtered or if + * it is not filterable. + * + */ + bool IsPropertyFiltered(spinel_prop_key_t aPropKey) const; + + /** + * This method clears the filter. + * + */ + void ClearFilter(void) { mFilterSet = 0; } + + private: + uint8_t GetNumEntries(void) const; + void Add(spinel_prop_key_t aPropKey, spinel_status_t aStatus); + + static void SetBit (uint32_t &aBitset, uint8_t aBitIndex) { aBitset |= (1U << aBitIndex); } + static void ClearBit(uint32_t &aBitset, uint8_t aBitIndex) { aBitset &= ~(1U << aBitIndex); } + static bool IsBitSet(uint32_t aBitset, uint8_t aBitIndex) { return (aBitset & (1U << aBitIndex)) != 0; } + + static const Entry mSupportedProps[]; + + uint32_t mChangedSet; + uint32_t mFilterSet; + }; + enum { kTxBufferSize = OPENTHREAD_CONFIG_NCP_TX_BUFFER_SIZE, // Tx Buffer size (used by mTxFrameBuffer). @@ -614,18 +784,16 @@ private: bool mDiscoveryScanJoinerFlag; bool mDiscoveryScanEnableFiltering; uint16_t mDiscoveryScanPanId; + Tasklet mUpdateChangedPropsTask; - uint32_t mChangedFlags; - bool mShouldSignalEndOfScan; + uint32_t mThreadChangedFlags; + ChangedPropsSet mChangedPropsSet; + spinel_host_power_state_t mHostPowerState; bool mHostPowerStateInProgress; NcpFrameBuffer::FrameTag mHostPowerReplyFrameTag; uint8_t mHostPowerStateHeader; -#if OPENTHREAD_ENABLE_JAM_DETECTION - bool mShouldSignalJamStateChange; -#endif - #if OPENTHREAD_CONFIG_NCP_ENABLE_PEEK_POKE otNcpDelegateAllowPeekPoke mAllowPeekDelegate; otNcpDelegateAllowPeekPoke mAllowPokeDelegate; @@ -661,6 +829,7 @@ private: #if OPENTHREAD_ENABLE_LEGACY const otNcpLegacyHandlers *mLegacyHandlers; uint8_t mLegacyUlaPrefix[OT_NCP_LEGACY_ULA_PREFIX_LENGTH]; + otExtAddress mLegacyLastJoinedNode; bool mLegacyNodeDidJoin; #endif diff --git a/src/ncp/spinel.c b/src/ncp/spinel.c index da688e5eb..823475365 100644 --- a/src/ncp/spinel.c +++ b/src/ncp/spinel.c @@ -972,6 +972,14 @@ spinel_prop_key_to_cstr(spinel_prop_key_t prop_key) ret = "PROP_TRNG_RAW_32"; break; + case SPINEL_PROP_UNSOL_UPDATE_FILTER: + ret = "PROP_UNSOL_UPDATE_FILTER"; + break; + + case SPINEL_PROP_UNSOL_UPDATE_LIST: + ret = "PROP_UNSOL_UPDATE_LIST"; + break; + case SPINEL_PROP_PHY_ENABLED: ret = "PROP_PHY_ENABLED"; break; @@ -1568,8 +1576,8 @@ spinel_prop_key_to_cstr(spinel_prop_key_t prop_key) ret = "PROP_NEST_LEGACY_ULA_PREFIX"; break; - case SPINEL_PROP_NEST_LEGACY_JOINED_NODE: - ret = "PROP_NEST_LEGACY_JOINED_NODE"; + case SPINEL_PROP_NEST_LEGACY_LAST_NODE_JOINED: + ret = "PROP_NEST_LEGACY_LAST_NODE_JOINED"; break; case SPINEL_PROP_DEBUG_TEST_ASSERT: @@ -1819,6 +1827,10 @@ const char *spinel_capability_to_cstr(unsigned int capability) ret = "CAP_CMD_MULTI"; break; + case SPINEL_CAP_UNSOL_UPDATE_FILTER: + ret = "CAP_UNSOL_UPDATE_FILTER"; + break; + case SPINEL_CAP_802_15_4_2003: ret = "CAP_802_15_4_2003"; break; diff --git a/src/ncp/spinel.h b/src/ncp/spinel.h index ec3e09764..e3be9963b 100644 --- a/src/ncp/spinel.h +++ b/src/ncp/spinel.h @@ -365,6 +365,7 @@ enum SPINEL_CAP_GPIO = 9, SPINEL_CAP_TRNG = 10, SPINEL_CAP_CMD_MULTI = 11, + SPINEL_CAP_UNSOL_UPDATE_FILTER = 12, SPINEL_CAP_802_15_4__BEGIN = 16, SPINEL_CAP_802_15_4_2003 = (SPINEL_CAP_802_15_4__BEGIN + 0), @@ -550,6 +551,37 @@ typedef enum /// Raw samples from TRNG entropy source representing 32 bits of entropy. SPINEL_PROP_TRNG_RAW_32 = SPINEL_PROP_BASE_EXT__BEGIN + 7, + + /// NCP Unsolicited update filter + /** Format: `A(I)` + * Type: Read-Write (optional Insert-Remove) + * Required capability: `CAP_UNSOL_UPDATE_FILTER` + * + * Contains a list of properties which are excluded from generating + * unsolicited value updates. This property is empty after reset. + * In other words, the host may opt-out of unsolicited property updates + * for a specific property by adding that property id to this list. + * Hosts SHOULD NOT add properties to this list which are not + * present in `PROP_UNSOL_UPDATE_LIST`. If such properties are added, + * the NCP ignores the unsupported properties. + */ + SPINEL_PROP_UNSOL_UPDATE_FILTER = SPINEL_PROP_BASE_EXT__BEGIN + 8, + + /// List of properties capable of generating unsolicited value update. + /** Format: `A(I)` + * Type: Read-Only + * Required capability: `CAP_UNSOL_UPDATE_FILTER` + * + * Contains a list of properties which are capable of generating + * unsolicited value updates. This list can be used when populating + * `PROP_UNSOL_UPDATE_FILTER` to disable all unsolicited property + * updates. + * + * This property is intended to effectively behave as a constant + * for a given NCP firmware. + */ + SPINEL_PROP_UNSOL_UPDATE_LIST = SPINEL_PROP_BASE_EXT__BEGIN + 9, + SPINEL_PROP_BASE_EXT__END = 0x1100, SPINEL_PROP_PHY__BEGIN = 0x20, @@ -1275,9 +1307,10 @@ typedef enum /** Format: 'D' */ SPINEL_PROP_NEST_LEGACY_ULA_PREFIX = SPINEL_PROP_NEST__BEGIN + 1, - /// A (newly) joined legacy node (this is signaled from NCP) + /// The EUI64 of last node joined using legacy protocol (if none, all zero EUI64 is returned). /** Format: 'E' */ - SPINEL_PROP_NEST_LEGACY_JOINED_NODE = SPINEL_PROP_NEST__BEGIN + 2, + SPINEL_PROP_NEST_LEGACY_LAST_NODE_JOINED + = SPINEL_PROP_NEST__BEGIN + 2, SPINEL_PROP_NEST__END = 15360,