diff --git a/include/openthread/border_router.h b/include/openthread/border_router.h index c26fc6bf7..d07febac0 100644 --- a/include/openthread/border_router.h +++ b/include/openthread/border_router.h @@ -168,6 +168,35 @@ otError otBorderRouterGetNextRoute(otInstance *aInstance, */ otError otBorderRouterRegister(otInstance *aInstance); +/** + * Function pointer callback which is invoked when Network Data (local or leader) gets full. + * + * @param[in] aContext A pointer to arbitrary context information. + * + */ +typedef void (*otBorderRouterNetDataFullCallback)(void *aContext); + +/** + * Sets the callback to indicate when Network Data gets full. + * + * Requires `OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL`. + * + * The callback is invoked whenever: + * - The device is acting as a leader and receives a Network Data registration from a Border Router (BR) that it cannot + * add to Network Data (running out of space). + * - The device is acting as a BR and new entries cannot be added to its local Network Data. + * - The device is acting as a BR and tries to register its local Network Data entries with the leader, but determines + * that its local entries will not fit. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aCallback The callback. + * @param[in] aContext A pointer to arbitrary context information used with @p aCallback. + * + */ +void otBorderRouterSetNetDataFullCallback(otInstance *aInstance, + otBorderRouterNetDataFullCallback aCallback, + void *aContext); + /** * @} * diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 95add4889..b835b4b32 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (334) +#define OPENTHREAD_API_VERSION (335) /** * @addtogroup api-instance diff --git a/src/cli/README_NETDATA.md b/src/cli/README_NETDATA.md index 618a92b1b..f41f89983 100644 --- a/src/cli/README_NETDATA.md +++ b/src/cli/README_NETDATA.md @@ -142,6 +142,7 @@ After the device successfully attaches to a Thread network, the device will retr ## Command List - [help](#help) +- [full](#full) - [length](#length) - [maxlength](#maxlength) - [publish](#publish) @@ -160,6 +161,7 @@ Print netdata help menu. ```bash > netdata help +full length maxlength publish @@ -170,6 +172,39 @@ unpublish Done ``` +### full + +Usage: `netdata full` + +Print "yes" or "no" flag tracking whether or not the "net data full" callback has been invoked since start of Thread operation or since the last time `netdata full reset` was used to reset the flag. + +This command requires `OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL`. + +The "net data full" callback is invoked whenever: + +- The device is acting as a leader and receives a Network Data registration from a Border Router (BR) that it cannot add to Network Data (running out of space). +- The device is acting as a BR and new entries cannot be added to its local Network Data. +- The device is acting as a BR and tries to register its local Network Data entries with the leader, but determines that its local entries will not fit. + +``` +> netdata full +no +Done +``` + +### full reset + +Usage: `netdata full reset` + +Reset the flag tracking whether "net data full" callback was invoked. + +This command requires `OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL`. + +``` +> netdata full reset +Done +``` + ### length Usage: `netdata length` diff --git a/src/cli/cli_network_data.cpp b/src/cli/cli_network_data.cpp index 36fcf3de5..8c492e38a 100644 --- a/src/cli/cli_network_data.cpp +++ b/src/cli/cli_network_data.cpp @@ -43,6 +43,15 @@ namespace ot { namespace Cli { +NetworkData::NetworkData(otInstance *aInstance, OutputImplementer &aOutputImplementer) + : Output(aInstance, aOutputImplementer) +{ +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + mFullCallbackWasCalled = false; + otBorderRouterSetNetDataFullCallback(aInstance, HandleNetdataFull, this); +#endif +} + void NetworkData::PrefixFlagsToString(const otBorderRouterConfig &aConfig, FlagsString &aString) { char *flagsPtr = &aString[0]; @@ -802,6 +811,58 @@ exit: return error; } +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL +template <> otError NetworkData::Process(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + + /** + * @cli netdata full + * @code + * netdata full + * no + * Done + * @endcode + * @par + * Print "yes" or "no" indicating whether or not the "net data full" callback has been invoked since start of + * Thread operation or since the last time `netdata full reset` was used to reset the flag. + * This command requires `OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL`. + * The "net data full" callback is invoked whenever: + * - The device is acting as a leader and receives a Network Data registration from a Border Router (BR) that it + * cannot add to Network Data (running out of space). + * - The device is acting as a BR and new entries cannot be added to its local Network Data. + * - The device is acting as a BR and tries to register its local Network Data entries with the leader, but + * determines that its local entries will not fit. + * @sa otBorderRouterSetNetDataFullCallback + */ + if (aArgs[0].IsEmpty()) + { + OutputLine(mFullCallbackWasCalled ? "yes" : "no"); + } + /** + * @cli netdata full reset + * @code + * netdata full reset + * Done + * @endcode + * @par + * Reset the flag tracking whether "net data full" callback was invoked. + */ + else if (aArgs[0] == "reset") + { + VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + mFullCallbackWasCalled = false; + } + else + { + error = OT_ERROR_INVALID_ARGS; + } + +exit: + return error; +} +#endif // OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + otError NetworkData::Process(Arg aArgs[]) { #define CmdEntry(aCommandString) \ @@ -810,6 +871,9 @@ otError NetworkData::Process(Arg aArgs[]) } static constexpr Command kCommands[] = { +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + CmdEntry("full"), +#endif CmdEntry("length"), CmdEntry("maxlength"), #if OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE diff --git a/src/cli/cli_network_data.hpp b/src/cli/cli_network_data.hpp index 93ffb0d2f..635ff383b 100644 --- a/src/cli/cli_network_data.hpp +++ b/src/cli/cli_network_data.hpp @@ -71,10 +71,7 @@ public: * @param[in] aOutputImplementer An `OutputImplementer`. * */ - NetworkData(otInstance *aInstance, OutputImplementer &aOutputImplementer) - : Output(aInstance, aOutputImplementer) - { - } + NetworkData(otInstance *aInstance, OutputImplementer &aOutputImplementer); /** * Processes a CLI sub-command. @@ -146,6 +143,13 @@ private: void OutputRoutes(bool aLocal); void OutputServices(bool aLocal); void OutputLowpanContexts(bool aLocal); + +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + static void HandleNetdataFull(void *aContext) { static_cast(aContext)->HandleNetdataFull(); } + void HandleNetdataFull(void) { mFullCallbackWasCalled = true; } + + bool mFullCallbackWasCalled; +#endif }; } // namespace Cli diff --git a/src/core/api/border_router_api.cpp b/src/core/api/border_router_api.cpp index c3dd9ded2..5694768e9 100644 --- a/src/core/api/border_router_api.cpp +++ b/src/core/api/border_router_api.cpp @@ -118,4 +118,13 @@ otError otBorderRouterRegister(otInstance *aInstance) return kErrorNone; } +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL +void otBorderRouterSetNetDataFullCallback(otInstance *aInstance, + otBorderRouterNetDataFullCallback aCallback, + void *aContext) +{ + AsCoreType(aInstance).Get().SetNetDataFullCallback(aCallback, aContext); +} +#endif + #endif // OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE diff --git a/src/core/config/border_router.h b/src/core/config/border_router.h index 01ddfe9aa..543ea8fed 100644 --- a/src/core/config/border_router.h +++ b/src/core/config/border_router.h @@ -35,6 +35,8 @@ #ifndef CONFIG_BORDER_ROUTER_H_ #define CONFIG_BORDER_ROUTER_H_ +#include + /** * @def OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE * @@ -71,4 +73,14 @@ #define OPENTHREAD_CONFIG_BORDER_ROUTER_REQUEST_ROUTER_ROLE 1 #endif +/** + * @def OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + * + * Define as 1 to enable mechanism to detect and signal when local or leader Network Data gets full. + * + */ +#ifndef OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL +#define OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE +#endif + #endif // CONFIG_BORDER_ROUTER_H_ diff --git a/src/core/thread/network_data_leader_ftd.cpp b/src/core/thread/network_data_leader_ftd.cpp index 39a033ded..4dc1ceb1c 100644 --- a/src/core/thread/network_data_leader_ftd.cpp +++ b/src/core/thread/network_data_leader_ftd.cpp @@ -60,6 +60,9 @@ RegisterLogModule("NetworkData"); Leader::Leader(Instance &aInstance) : LeaderBase(aInstance) +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + , mIsClone(false) +#endif , mWaitingForNetDataSync(false) , mContextIds(aInstance) , mTimer(aInstance) @@ -76,6 +79,10 @@ void Leader::Reset(void) void Leader::Start(Mle::LeaderStartMode aStartMode) { +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + OT_ASSERT(!mIsClone); +#endif + mWaitingForNetDataSync = (aStartMode == Mle::kRestoringLeaderRoleAfterReset); if (mWaitingForNetDataSync) @@ -110,6 +117,10 @@ void Leader::IncrementVersions(const ChangedFlags &aFlags) void Leader::IncrementVersions(bool aIncludeStable) { +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + VerifyOrExit(!mIsClone); +#endif + if (aIncludeStable) { mStableVersion++; @@ -117,6 +128,10 @@ void Leader::IncrementVersions(bool aIncludeStable) mVersion++; SignalNetDataChanged(); + ExitNow(); + +exit: + return; } void Leader::RemoveBorderRouter(uint16_t aRloc16, MatchMode aMatchMode) @@ -124,6 +139,7 @@ void Leader::RemoveBorderRouter(uint16_t aRloc16, MatchMode aMatchMode) ChangedFlags flags; RemoveRloc(aRloc16, aMatchMode, flags); + IncrementVersions(flags); } @@ -672,6 +688,52 @@ exit: return status; } +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + +void Leader::CheckForNetDataGettingFull(const NetworkData &aNetworkData, uint16_t aOldRloc16) +{ + // Determines whether there is still room in Network Data to register + // `aNetworkData` entries. The `aNetworkData` MUST follow the format of + // local Network Data (e.g., all entries associated with the RLOC16 of + // this device). Network data getting full is signaled by invoking the + // `Get().SignalNetworkDataFull()` method. + // + // Input `aOldRloc16` can be used to indicate the old RLOC16 of the + // device. If provided, then entries matching old RLOC16 are first + // removed, before checking if new entries from @p aNetworkData can fit. + + if (!Get().IsLeader()) + { + // Create a clone of the leader's network data, and try to register + // `aNetworkData` into the copy (as if this device itself is the + // leader). `mIsClone` flag is used to mark the clone and ensure + // that the cloned instance does interact with other OT modules, + // e.g., does not start timer, or does not signal version change + // using `Get().Signal()`, or allocate service or + // context ID. + + Leader leaderClone(GetInstance()); + + leaderClone.MarkAsClone(); + SuccessOrAssert(CopyNetworkData(kFullSet, leaderClone)); + + if (aOldRloc16 != Mac::kShortAddrInvalid) + { + leaderClone.RemoveBorderRouter(aOldRloc16, kMatchModeRloc16); + } + + leaderClone.RegisterNetworkData(Get().GetRloc16(), aNetworkData); + } +} + +void Leader::MarkAsClone(void) +{ + mIsClone = true; + mContextIds.MarkAsClone(); +} + +#endif // OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + void Leader::RegisterNetworkData(uint16_t aRloc16, const NetworkData &aNetworkData) { Error error = kErrorNone; @@ -710,9 +772,19 @@ void Leader::RegisterNetworkData(uint16_t aRloc16, const NetworkData &aNetworkDa exit: IncrementVersions(flags); - if (error != kErrorNone) +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + if (error == kErrorNoBufs) { - LogNote("Failed to register network data: %s", ErrorToString(error)); + Get().SignalNetworkDataFull(); + } + + if (!mIsClone) +#endif + { + if (error != kErrorNone) + { + LogNote("Failed to register network data: %s", ErrorToString(error)); + } } } @@ -943,6 +1015,15 @@ Error Leader::AllocateServiceId(uint8_t &aServiceId) const Error error = kErrorNotFound; uint8_t serviceId; +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + if (mIsClone) + { + aServiceId = Mle::kServiceMinId; + error = kErrorNone; + ExitNow(); + } +#endif + for (serviceId = Mle::kServiceMinId; serviceId <= Mle::kServiceMaxId; serviceId++) { if (FindServiceById(serviceId) == nullptr) @@ -950,10 +1031,11 @@ Error Leader::AllocateServiceId(uint8_t &aServiceId) const aServiceId = serviceId; error = kErrorNone; LogInfo("Allocated Service ID = %d", serviceId); - break; + ExitNow(); } } +exit: return error; } @@ -1316,6 +1398,15 @@ exit: //--------------------------------------------------------------------------------------------------------------------- // Leader::ContextIds +Leader::ContextIds::ContextIds(Instance &aInstance) + : InstanceLocator(aInstance) + , mReuseDelay(kReuseDelay) +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + , mIsClone(false) +#endif +{ +} + void Leader::ContextIds::Clear(void) { for (uint8_t id = kMinId; id <= kMaxId; id++) @@ -1328,21 +1419,35 @@ Error Leader::ContextIds::GetUnallocatedId(uint8_t &aId) { Error error = kErrorNotFound; +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + if (mIsClone) + { + aId = kMinId; + error = kErrorNone; + ExitNow(); + } +#endif + for (uint8_t id = kMinId; id <= kMaxId; id++) { if (IsUnallocated(id)) { aId = id; error = kErrorNone; - break; + ExitNow(); } } +exit: return error; } void Leader::ContextIds::ScheduleToRemove(uint8_t aId) { +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + VerifyOrExit(!mIsClone); +#endif + VerifyOrExit(IsInUse(aId)); SetRemoveTime(aId, TimerMilli::GetNow() + Time::SecToMsec(mReuseDelay)); @@ -1369,6 +1474,10 @@ void Leader::ContextIds::HandleTimer(void) TimeMilli now = TimerMilli::GetNow(); TimeMilli nextTime = now.GetDistantFuture(); +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + OT_ASSERT(!mIsClone); +#endif + for (uint8_t id = kMinId; id <= kMaxId; id++) { if (IsUnallocated(id) || IsInUse(id)) diff --git a/src/core/thread/network_data_leader_ftd.hpp b/src/core/thread/network_data_leader_ftd.hpp index 7c29c9478..bae0c7ff4 100644 --- a/src/core/thread/network_data_leader_ftd.hpp +++ b/src/core/thread/network_data_leader_ftd.hpp @@ -52,6 +52,8 @@ namespace ot { namespace NetworkData { +class Notifier; + /** * @addtogroup core-netdata-leader * @@ -69,6 +71,7 @@ namespace NetworkData { class Leader : public LeaderBase, private NonCopyable { friend class Tmf::Agent; + friend class Notifier; public: /** @@ -219,11 +222,7 @@ private: static constexpr uint8_t kInvalidId = NumericLimits::kMax; - explicit ContextIds(Instance &aInstance) - : InstanceLocator(aInstance) - , mReuseDelay(kReuseDelay) - { - } + explicit ContextIds(Instance &aInstance); void Clear(void); Error GetUnallocatedId(uint8_t &aId); @@ -232,6 +231,9 @@ private: uint32_t GetReuseDelay(void) const { return mReuseDelay; } void SetReuseDelay(uint32_t aDelay) { mReuseDelay = aDelay; } void HandleTimer(void); +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + void MarkAsClone(void) { mIsClone = true; } +#endif private: static constexpr uint32_t kReuseDelay = 5 * 60; // 5 minutes (in seconds). @@ -256,6 +258,9 @@ private: TimeMilli mRemoveTimes[kMaxId - kMinId + 1]; uint32_t mReuseDelay; +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + bool mIsClone; +#endif }; template void HandleTmf(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo); @@ -330,8 +335,16 @@ private: void IncrementVersions(bool aIncludeStable); void IncrementVersions(const ChangedFlags &aFlags); +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + void CheckForNetDataGettingFull(const NetworkData &aNetworkData, uint16_t aOldRloc16); + void MarkAsClone(void); +#endif + using UpdateTimer = TimerMilliIn; +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + bool mIsClone; +#endif bool mWaitingForNetDataSync; ContextIds mContextIds; UpdateTimer mTimer; diff --git a/src/core/thread/network_data_local.cpp b/src/core/thread/network_data_local.cpp index a7eb9d9fd..b99900fd5 100644 --- a/src/core/thread/network_data_local.cpp +++ b/src/core/thread/network_data_local.cpp @@ -134,6 +134,13 @@ Error Local::AddPrefix(const Ip6::Prefix &aPrefix, NetworkDataTlv::Type aSubTlvT DumpDebg("AddPrefix", GetBytes(), GetLength()); exit: +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + if (error == kErrorNoBufs) + { + Get().SignalNetworkDataFull(); + } +#endif + return error; } @@ -211,6 +218,13 @@ Error Local::AddService(uint32_t aEnterpriseNumber, DumpDebg("AddService", GetBytes(), GetLength()); exit: +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + if (error == kErrorNoBufs) + { + Get().SignalNetworkDataFull(); + } +#endif + return error; } diff --git a/src/core/thread/network_data_notifier.cpp b/src/core/thread/network_data_notifier.cpp index 65b76e06a..09597cad7 100644 --- a/src/core/thread/network_data_notifier.cpp +++ b/src/core/thread/network_data_notifier.cpp @@ -53,6 +53,9 @@ Notifier::Notifier(Instance &aInstance) : InstanceLocator(aInstance) , mTimer(aInstance) , mSynchronizeDataTask(aInstance) +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + , mNetDataFullTask(aInstance) +#endif , mNextDelay(0) , mOldRloc(Mac::kShortAddrInvalid) , mWaitingForResponse(false) @@ -201,6 +204,10 @@ Error Notifier::SendServerDataNotification(uint16_t aOldRloc16, const NetworkDat tlv.SetLength(aNetworkData->GetLength()); SuccessOrExit(error = message->Append(tlv)); SuccessOrExit(error = message->AppendBytes(aNetworkData->GetBytes(), aNetworkData->GetLength())); + +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + Get().CheckForNetDataGettingFull(*aNetworkData, aOldRloc16); +#endif } if (aOldRloc16 != Mac::kShortAddrInvalid) @@ -273,6 +280,15 @@ void Notifier::HandleCoapResponse(Error aResult) } } +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL +void Notifier::SetNetDataFullCallback(NetDataCallback aCallback, void *aContext) +{ + mNetDataFullCallback.Set(aCallback, aContext); +} + +void Notifier::HandleNetDataFull(void) { mNetDataFullCallback.InvokeIfSet(); } +#endif + #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTER_REQUEST_ROUTER_ROLE bool Notifier::IsEligibleForRouterRoleUpgradeAsBorderRouter(void) const diff --git a/src/core/thread/network_data_notifier.hpp b/src/core/thread/network_data_notifier.hpp index bd60e6490..e12048f09 100644 --- a/src/core/thread/network_data_notifier.hpp +++ b/src/core/thread/network_data_notifier.hpp @@ -38,6 +38,8 @@ #if OPENTHREAD_FTD || OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE || OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE +#include + #include "coap/coap.hpp" #include "common/message.hpp" #include "common/non_copyable.hpp" @@ -78,6 +80,25 @@ public: */ void HandleServerDataUpdated(void); +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + typedef otBorderRouterNetDataFullCallback NetDataCallback; ///< Network Data full callback. + + /** + * Sets the callback to indicate when Network Data gets full. + * + * @param[in] aCallback The callback. + * @param[in] aContext The context to use with @p aCallback. + * + */ + void SetNetDataFullCallback(NetDataCallback aCallback, void *aContext); + + /** + * Signals that network data (local or leader) is getting full. + * + */ + void SignalNetworkDataFull(void) { mNetDataFullTask.Post(); } +#endif + #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTER_REQUEST_ROUTER_ROLE /** * Indicates whether the device as border router is eligible for router role upgrade request using the @@ -123,6 +144,10 @@ private: Error aResult); void HandleCoapResponse(Error aResult); +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + void HandleNetDataFull(void); +#endif + #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTER_REQUEST_ROUTER_ROLE void ScheduleRouterRoleUpgradeIfEligible(void); void HandleTimeTick(void); @@ -130,12 +155,19 @@ private: using SynchronizeDataTask = TaskletIn; using DelayTimer = TimerMilliIn; +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + using NetDataFullTask = TaskletIn; +#endif DelayTimer mTimer; SynchronizeDataTask mSynchronizeDataTask; - uint32_t mNextDelay; - uint16_t mOldRloc; - bool mWaitingForResponse : 1; +#if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL + NetDataFullTask mNetDataFullTask; + Callback mNetDataFullCallback; +#endif + uint32_t mNextDelay; + uint16_t mOldRloc; + bool mWaitingForResponse : 1; #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTER_REQUEST_ROUTER_ROLE bool mDidRequestRouterRoleUpgrade : 1; diff --git a/tests/toranj/cli/cli.py b/tests/toranj/cli/cli.py index 68e780d02..ee496d646 100644 --- a/tests/toranj/cli/cli.py +++ b/tests/toranj/cli/cli.py @@ -411,6 +411,9 @@ class Node(object): leaderdata = Node.parse_list(self.cli('leaderdata')) return (int(leaderdata['Data Version']), int(leaderdata['Stable Data Version'])) + def get_netdata_length(self): + return self._cli_single_output('netdata length') + def add_prefix(self, prefix, flags=None, prf=None): return self._cli_no_output('prefix add', prefix, flags, prf) @@ -423,6 +426,12 @@ class Node(object): def register_netdata(self): self._cli_no_output('netdata register') + def get_netdata_full(self): + return self._cli_single_output('netdata full') + + def reset_netdata_full(self): + self._cli_no_output('netdata full reset') + #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # ping and counters diff --git a/tests/toranj/cli/test-022-netdata-full.py b/tests/toranj/cli/test-022-netdata-full.py new file mode 100755 index 000000000..5111fca43 --- /dev/null +++ b/tests/toranj/cli/test-022-netdata-full.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2023, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from cli import verify +from cli import verify_within +import cli +import time + +# ----------------------------------------------------------------------------------------------------------------------- +# Test description: Validate mechanism to detect when Network Data gets full and related callback. +# + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print('-' * 120) +print('Starting \'{}\''.format(test_name)) + +# ----------------------------------------------------------------------------------------------------------------------- +# Creating `cli.Node` instances + +speedup = 40 +cli.Node.set_time_speedup_factor(speedup) + +leader = cli.Node() +r1 = cli.Node() +r2 = cli.Node() +r3 = cli.Node() + +# ----------------------------------------------------------------------------------------------------------------------- +# Form topology + +leader.form('netdata-full') +r1.join(leader) +r2.join(r1) +r3.join(r1) + +verify(leader.get_state() == 'leader') +verify(r1.get_state() == 'router') +verify(r2.get_state() == 'router') +verify(r2.get_state() == 'router') + +# ----------------------------------------------------------------------------------------------------------------------- +# Test Implementation + +verify(leader.get_netdata_full() == 'no') +verify(r1.get_netdata_full() == 'no') +verify(r2.get_netdata_full() == 'no') +verify(r3.get_netdata_full() == 'no') + +# Add 7 routes from `r1` to netdata and make sure they are all +# successfully registered with leader. + +r1.add_route('fd00:1:0:1::/64', 's', 'med') +r1.add_route('fd00:1:0:2::/64', 's', 'med') +r1.add_route('fd00:1:0:3::/64', 's', 'med') +r1.add_route('fd00:1:0:4::/64', 's', 'med') +r1.add_route('fd00:1:0:5::/64', 's', 'med') +r1.add_route('fd00:1:0:6::/64', 's', 'med') +r1.add_route('fd00:1:0:7::/64', 's', 'med') +r1.register_netdata() + + +def check_netdata_after_r1(): + netdata = leader.get_netdata() + verify(len(netdata['routes']) == 7) + + +verify_within(check_netdata_after_r1, 5) + +verify(leader.get_netdata_full() == 'no') +verify(r1.get_netdata_full() == 'no') +verify(r2.get_netdata_full() == 'no') +verify(r3.get_netdata_full() == 'no') + +prev_len = int(leader.get_netdata_length()) + +# Now add 7 new routes from `r2` to netdata and make sure they are all +# successfully registered with leader. + +r2.add_route('fd00:2:0:1::/64', 's', 'med') +r2.add_route('fd00:2:0:2::/64', 's', 'med') +r2.add_route('fd00:2:0:3::/64', 's', 'med') +r2.add_route('fd00:2:0:4::/64', 's', 'med') +r2.add_route('fd00:2:0:5::/64', 's', 'med') +r2.add_route('fd00:2:0:6::/64', 's', 'med') +r2.add_route('fd00:2:0:7::/64', 's', 'med') +r2.register_netdata() + + +def check_netdata_after_r2(): + netdata = leader.get_netdata() + verify(len(netdata['routes']) == 14) + + +verify_within(check_netdata_after_r2, 5) + +verify(leader.get_netdata_full() == 'no') +verify(r1.get_netdata_full() == 'no') +verify(r2.get_netdata_full() == 'no') +verify(r3.get_netdata_full() == 'no') + +verify(int(leader.get_netdata_length()) > prev_len) + +# Try adding 3 routes from `r3`, this should cause netdata to go +# over its size limit. Make sure that both `r3` and `leader` +# detect that + +r3.add_route('fd00:3:0:1::/64', 's', 'med') +r3.add_route('fd00:3:0:2::/64', 's', 'med') +r3.add_route('fd00:3:0:3::/64', 's', 'med') +r3.register_netdata() + + +def check_netdata_after_r3(): + verify(leader.get_netdata_full() == 'yes') + verify(r1.get_netdata_full() == 'no') + verify(r2.get_netdata_full() == 'no') + verify(r3.get_netdata_full() == 'yes') + + +verify_within(check_netdata_after_r3, 5) + +r3.remove_prefix('fd00:3:0:1::/64') +r3.remove_prefix('fd00:3:0:2::/64') +r3.remove_prefix('fd00:3:0:3::/64') +r3.register_netdata() + +leader.reset_netdata_full() +r3.reset_netdata_full() + + +def check_netdata_after_remove_on_r3(): + verify(leader.get_netdata_full() == 'no') + verify(r1.get_netdata_full() == 'no') + verify(r2.get_netdata_full() == 'no') + verify(r3.get_netdata_full() == 'no') + + +verify_within(check_netdata_after_remove_on_r3, 5) + +r2.add_route('fd00:2:0:8::/64', 's', 'med') +r2.add_route('fd00:2:0:9::/64', 's', 'med') +r2.register_netdata() + + +def check_netdata_after_more_routes_on_r2(): + verify(leader.get_netdata_full() == 'yes') + verify(r1.get_netdata_full() == 'no') + verify(r2.get_netdata_full() == 'yes') + verify(r3.get_netdata_full() == 'no') + + +verify_within(check_netdata_after_more_routes_on_r2, 5) + +# ----------------------------------------------------------------------------------------------------------------------- +# Test finished + +cli.Node.finalize_all_nodes() + +print('\'{}\' passed.'.format(test_name)) diff --git a/tests/toranj/start.sh b/tests/toranj/start.sh index 2bc8fa996..550acd1f3 100755 --- a/tests/toranj/start.sh +++ b/tests/toranj/start.sh @@ -185,6 +185,7 @@ if [ "$TORANJ_CLI" = 1 ]; then run cli/test-018-next-hop-and-path-cost.py run cli/test-019-netdata-context-id.py run cli/test-020-net-diag-vendor-info.py + run cli/test-022-netdata-full.py run cli/test-400-srp-client-server.py run cli/test-601-channel-manager-channel-change.py # Skip the "channel-select" test on a TREL only radio link, since it