diff --git a/src/lib/spinel/spinel.c b/src/lib/spinel/spinel.c index b2af6fd04..9181e6e74 100644 --- a/src/lib/spinel/spinel.c +++ b/src/lib/spinel/spinel.c @@ -1350,6 +1350,8 @@ const char *spinel_prop_key_to_cstr(spinel_prop_key_t prop_key) SPINEL_PROP_CSTR(THREAD_CSL_TIMEOUT), SPINEL_PROP_CSTR(THREAD_CSL_CHANNEL), SPINEL_PROP_CSTR(THREAD_DOMAIN_NAME), + SPINEL_PROP_CSTR(THREAD_MLR_REQUEST), + SPINEL_PROP_CSTR(THREAD_MLR_RESPONSE), SPINEL_PROP_CSTR(THREAD_BACKBONE_ROUTER_PRIMARY), SPINEL_PROP_CSTR(THREAD_BACKBONE_ROUTER_LOCAL_STATE), SPINEL_PROP_CSTR(THREAD_BACKBONE_ROUTER_LOCAL_CONFIG), @@ -1539,6 +1541,7 @@ const char *spinel_status_to_cstr(spinel_status_t status) SPINEL_STATUS_CSTR(ALREADY), SPINEL_STATUS_CSTR(ITEM_NOT_FOUND), SPINEL_STATUS_CSTR(INVALID_COMMAND_FOR_PROP), + SPINEL_STATUS_CSTR(RESPONSE_TIMEOUT), SPINEL_STATUS_CSTR(JOIN_FAILURE), SPINEL_STATUS_CSTR(JOIN_SECURITY), SPINEL_STATUS_CSTR(JOIN_NO_PEERS), diff --git a/src/lib/spinel/spinel.h b/src/lib/spinel/spinel.h index 92e379a7a..473256964 100644 --- a/src/lib/spinel/spinel.h +++ b/src/lib/spinel/spinel.h @@ -466,6 +466,7 @@ enum SPINEL_STATUS_ALREADY = 19, ///< The operation is already in progress. SPINEL_STATUS_ITEM_NOT_FOUND = 20, ///< The given item could not be found. SPINEL_STATUS_INVALID_COMMAND_FOR_PROP = 21, ///< The given command cannot be performed on this property. + SPINEL_STATUS_RESPONSE_TIMEOUT = 24, ///< No response received from remote node SPINEL_STATUS_JOIN__BEGIN = 104, @@ -724,6 +725,13 @@ enum SPINEL_RADIO_LINK_TREL_UDP6 = 1, }; +// Parameter ids used for: +// @ref SPINEL_PROP_THREAD_MLR_REQUEST +enum +{ + SPINEL_THREAD_MLR_PARAMID_TIMEOUT = 0 +}; + // Backbone Router states used for: // @ref SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_STATE enum @@ -2955,6 +2963,46 @@ enum */ SPINEL_PROP_THREAD_DOMAIN_NAME = SPINEL_PROP_THREAD_EXT__BEGIN + 44, + /// Multicast Listeners Register Request + /** Format `t(A(6))A(t(CD))` - Write-only + * Required capability: `SPINEL_CAP_NET_THREAD_1_2` + * + * `t(A(6))`: Array of IPv6 multicast addresses + * `A(t(CD))`: Array of structs holding optional parameters as follows + * `C`: Parameter id + * `D`: Parameter value + * + * +----------------------------------------------------------------+ + * | Id: SPINEL_THREAD_MLR_PARAMID_TIMEOUT | + * | Type: `L` | + * | Description: Timeout in seconds. If this optional parameter is | + * | omitted, the default value of the BBR will be used. | + * | Special values: | + * | 0 causes given addresses to be removed | + * | 0xFFFFFFFF is permanent and persistent registration | + * +----------------------------------------------------------------+ + * + * Write to this property initiates update of Multicast Listeners Table on the primary BBR. + * If the write succeeded, the result of network operation will be notified later by the + * SPINEL_PROP_THREAD_MLR_RESPONSE property. If the write fails, no MLR.req is issued and + * notifiaction through the SPINEL_PROP_THREAD_MLR_RESPONSE property will not occur. + * + */ + SPINEL_PROP_THREAD_MLR_REQUEST = SPINEL_PROP_THREAD_EXT__BEGIN + 52, + + /// Multicast Listeners Register Response + /** Format `CCt(A(6))` - Unsolicited notifications only + * Required capability: `SPINEL_CAP_NET_THREAD_1_2` + * + * `C`: Status + * `C`: MlrStatus (The Multicast Listener Registration Status) + * `A(6)`: Array of IPv6 addresses that failed to be updated on the primary BBR + * + * This property is notified asynchronously when the NCP receives MLR.rsp following + * previous write to the SPINEL_PROP_THREAD_MLR_REQUEST property. + */ + SPINEL_PROP_THREAD_MLR_RESPONSE = SPINEL_PROP_THREAD_EXT__BEGIN + 53, + /// Interface Identifier specified for Thread Domain Unicast Address. /** Format: `A(C)` - Read-write * diff --git a/src/ncp/ncp_base.cpp b/src/ncp/ncp_base.cpp index 8268c86ce..4fbb8f6a2 100644 --- a/src/ncp/ncp_base.cpp +++ b/src/ncp/ncp_base.cpp @@ -135,6 +135,10 @@ spinel_status_t NcpBase::ThreadErrorToSpinelStatus(otError aError) ret = SPINEL_STATUS_ITEM_NOT_FOUND; break; + case OT_ERROR_RESPONSE_TIMEOUT: + ret = SPINEL_STATUS_RESPONSE_TIMEOUT; + break; + default: // Unknown error code. Wrap it as a Spinel status and return that. ret = static_cast(SPINEL_STATUS_STACK_NATIVE__BEGIN + static_cast(aError)); diff --git a/src/ncp/ncp_base.hpp b/src/ncp/ncp_base.hpp index 6779dcbee..8a65f3744 100644 --- a/src/ncp/ncp_base.hpp +++ b/src/ncp/ncp_base.hpp @@ -337,6 +337,16 @@ protected: void HandleJoinerCallback(otError aError); #endif + static void HandleMlrRegResult_Jump(void * aContext, + otError aError, + uint8_t aMlrStatus, + const otIp6Address *aFailedAddresses, + uint8_t aFailedAddressNum); + void HandleMlrRegResult(otError aError, + uint8_t aMlrStatus, + const otIp6Address *aFailedAddresses, + uint8_t aFailedAddressNum); + otError EncodeOperationalDataset(const otOperationalDataset &aDataset); otError DecodeOperationalDataset(otOperationalDataset &aDataset, diff --git a/src/ncp/ncp_base_dispatcher.cpp b/src/ncp/ncp_base_dispatcher.cpp index 98b7e0efc..313a6a3bc 100644 --- a/src/ncp/ncp_base_dispatcher.cpp +++ b/src/ncp/ncp_base_dispatcher.cpp @@ -554,6 +554,9 @@ NcpBase::PropertyHandler NcpBase::FindSetPropertyHandler(spinel_prop_key_t aKey) #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_DOMAIN_NAME), #endif +#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE + OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_MLR_REQUEST), +#endif #if OPENTHREAD_CONFIG_DUA_ENABLE OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_DUA_ID), #endif diff --git a/src/ncp/ncp_base_mtd.cpp b/src/ncp/ncp_base_mtd.cpp index eda5b10a7..ff564ab55 100644 --- a/src/ncp/ncp_base_mtd.cpp +++ b/src/ncp/ncp_base_mtd.cpp @@ -262,6 +262,94 @@ template <> otError NcpBase::HandlePropertyGet(v } #endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE +template <> otError NcpBase::HandlePropertySet(void) +{ + otError error = OT_ERROR_NONE; + otIp6Address addresses[kIPv6AddressesNumMax]; + uint8_t addressesCount = 0U; + bool timeoutPresent = false; + uint32_t timeout; + + SuccessOrExit(error = mDecoder.OpenStruct()); + + while (mDecoder.GetRemainingLengthInStruct()) + { + VerifyOrExit(addressesCount < kIPv6AddressesNumMax, error = OT_ERROR_NO_BUFS); + SuccessOrExit(error = mDecoder.ReadIp6Address(addresses[addressesCount])); + ++addressesCount; + } + + SuccessOrExit(error = mDecoder.CloseStruct()); + + while (mDecoder.GetRemainingLengthInStruct()) + { + uint8_t paramId; + + SuccessOrExit(error = mDecoder.OpenStruct()); + + SuccessOrExit(error = mDecoder.ReadUint8(paramId)); + + switch (paramId) + { + case SPINEL_THREAD_MLR_PARAMID_TIMEOUT: + SuccessOrExit(error = mDecoder.ReadUint32(timeout)); + timeoutPresent = true; + break; + + default: + ExitNow(error = OT_ERROR_INVALID_ARGS); + } + + SuccessOrExit(error = mDecoder.CloseStruct()); + } + + SuccessOrExit(error = otIp6RegisterMulticastListeners(mInstance, addresses, addressesCount, + timeoutPresent ? &timeout : nullptr, + &NcpBase::HandleMlrRegResult_Jump, this)); +exit: + return error; +} + +void NcpBase::HandleMlrRegResult_Jump(void * aContext, + otError aError, + uint8_t aMlrStatus, + const otIp6Address *aFailedAddresses, + uint8_t aFailedAddressNum) +{ + static_cast(aContext)->HandleMlrRegResult(aError, aMlrStatus, aFailedAddresses, aFailedAddressNum); +} + +void NcpBase::HandleMlrRegResult(otError aError, + uint8_t aMlrStatus, + const otIp6Address *aFailedAddresses, + uint8_t aFailedAddressNum) +{ + SuccessOrExit(mEncoder.BeginFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_CMD_PROP_VALUE_IS, + SPINEL_PROP_THREAD_MLR_RESPONSE)); + + SuccessOrExit(mEncoder.WriteUint8(static_cast(ThreadErrorToSpinelStatus(aError)))); + SuccessOrExit(mEncoder.WriteUint8(aMlrStatus)); + + SuccessOrExit(mEncoder.OpenStruct()); + + if (aError == OT_ERROR_NONE) + { + for (size_t i = 0U; i < aFailedAddressNum; ++i) + { + SuccessOrExit(mEncoder.WriteIp6Address(aFailedAddresses[i])); + } + } + + SuccessOrExit(mEncoder.CloseStruct()); + + SuccessOrExit(mEncoder.EndFrame()); + +exit: + return; +} +#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE + #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) template <> otError NcpBase::HandlePropertyGet(void) {