diff --git a/src/core/thread/network_data_leader_ftd.cpp b/src/core/thread/network_data_leader_ftd.cpp index 0c079a5b1..6a5534374 100644 --- a/src/core/thread/network_data_leader_ftd.cpp +++ b/src/core/thread/network_data_leader_ftd.cpp @@ -61,6 +61,7 @@ RegisterLogModule("NetworkData"); Leader::Leader(Instance &aInstance) : LeaderBase(aInstance) , mWaitingForNetDataSync(false) + , mContextIds(aInstance) , mTimer(aInstance) { Reset(); @@ -70,9 +71,7 @@ void Leader::Reset(void) { LeaderBase::Reset(); - memset(reinterpret_cast(mContextLastUsed), 0, sizeof(mContextLastUsed)); - mContextUsed = 0; - mContextIdReuseDelay = kContextIdReuseDelay; + mContextIds.Clear(); } void Leader::Start(Mle::LeaderStartMode aStartMode) @@ -852,10 +851,10 @@ Error Leader::AddBorderRouter(const BorderRouterTlv &aBorderRouter, PrefixTlv &a if (dstContext == nullptr) { - // Allocate a Context ID first. This ensure that if we cannot - // allocate, we fail and exit before potentially inserting a - // Border Router sub-TLV. - SuccessOrExit(error = AllocateContextId(contextId)); + // Get a new Context ID first. This ensure that if we cannot + // get new Context ID, we fail and exit before potentially + // inserting a Border Router sub-TLV. + SuccessOrExit(error = mContextIds.GetUnallocatedId(contextId)); } if (dstBorderRouter == nullptr) @@ -894,7 +893,7 @@ Error Leader::AddBorderRouter(const BorderRouterTlv &aBorderRouter, PrefixTlv &a } dstContext->SetCompress(); - StopContextReuseTimer(dstContext->GetContextId()); + mContextIds.MarkAsInUse(dstContext->GetContextId()); VerifyOrExit(!ContainsMatchingEntry(dstBorderRouter, *entry)); @@ -974,58 +973,6 @@ const ServiceTlv *Leader::FindServiceById(uint8_t aServiceId) const return service; } -Error Leader::AllocateContextId(uint8_t &aContextId) -{ - Error error = kErrorNotFound; - - for (uint8_t contextId = kMinContextId; contextId < kMinContextId + kNumContextIds; contextId++) - { - if ((mContextUsed & (1 << contextId)) == 0) - { - mContextUsed |= (1 << contextId); - aContextId = contextId; - error = kErrorNone; - LogInfo("Allocated Context ID = %d", contextId); - break; - } - } - - return error; -} - -void Leader::FreeContextId(uint8_t aContextId) -{ - LogInfo("Free Context Id = %d", aContextId); - RemoveContext(aContextId); - mContextUsed &= ~(1 << aContextId); - IncrementVersions(/* aIncludeStable */ true); -} - -void Leader::StartContextReuseTimer(uint8_t aContextId) -{ - // Start the reuse timer for `aContextId` if it is not already - // scheduled. - - VerifyOrExit(mContextLastUsed[aContextId - kMinContextId].GetValue() == 0); - - mContextLastUsed[aContextId - kMinContextId] = TimerMilli::GetNow(); - - if (mContextLastUsed[aContextId - kMinContextId].GetValue() == 0) - { - mContextLastUsed[aContextId - kMinContextId].SetValue(1); - } - - if (!mTimer.IsRunning()) - { - mTimer.Start(kStateUpdatePeriod); - } - -exit: - return; -} - -void Leader::StopContextReuseTimer(uint8_t aContextId) { mContextLastUsed[aContextId - kMinContextId].SetValue(0); } - void Leader::RemoveRloc(uint16_t aRloc16, MatchMode aMatchMode, ChangedFlags &aChangedFlags) { NetworkData excludeNetworkData(GetInstance()); // Empty network data. @@ -1147,15 +1094,15 @@ void Leader::RemoveRlocInPrefix(PrefixTlv &aPrefix, if ((context = aPrefix.FindSubTlv()) != nullptr) { - if (aPrefix.GetSubTlvsLength() == sizeof(ContextTlv)) + if (aPrefix.FindSubTlv() == nullptr) { context->ClearCompress(); - StartContextReuseTimer(context->GetContextId()); + mContextIds.ScheduleToRemove(context->GetContextId()); } else { context->SetCompress(); - StopContextReuseTimer(context->GetContextId()); + mContextIds.MarkAsInUse(context->GetContextId()); } } } @@ -1263,6 +1210,8 @@ void Leader::RemoveContext(uint8_t aContextId) start = prefix->GetNext(); } + + IncrementVersions(/* aIncludeStable */ true); } void Leader::RemoveContext(PrefixTlv &aPrefix, uint8_t aContextId) @@ -1303,55 +1252,26 @@ void Leader::HandleNetworkDataRestoredAfterReset(void) continue; } - mContextUsed |= 1 << context->GetContextId(); + mContextIds.MarkAsInUse(context->GetContextId()); - if (context->IsCompress()) + if (!context->IsCompress()) { - StopContextReuseTimer(context->GetContextId()); - } - else - { - StartContextReuseTimer(context->GetContextId()); + mContextIds.ScheduleToRemove(context->GetContextId()); } } } void Leader::HandleTimer(void) { - bool contextsWaiting = false; - if (mWaitingForNetDataSync) { LogInfo("Timed out waiting for netdata on restoring leader role after reset"); IgnoreError(Get().BecomeDetached()); - ExitNow(); } - - for (uint8_t i = 0; i < kNumContextIds; i++) + else { - if (mContextLastUsed[i].GetValue() == 0) - { - continue; - } - - if (TimerMilli::GetNow() - mContextLastUsed[i] >= Time::SecToMsec(mContextIdReuseDelay)) - { - mContextLastUsed[i].SetValue(0); - FreeContextId(kMinContextId + i); - } - else - { - contextsWaiting = true; - } + mContextIds.HandleTimer(); } - - if (contextsWaiting) - { - mTimer.Start(kStateUpdatePeriod); - } - -exit: - return; } #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE @@ -1393,6 +1313,86 @@ exit: } #endif +//--------------------------------------------------------------------------------------------------------------------- +// Leader::ContextIds + +void Leader::ContextIds::Clear(void) +{ + for (uint8_t id = kMinId; id <= kMaxId; id++) + { + MarkAsUnallocated(id); + } +} + +Error Leader::ContextIds::GetUnallocatedId(uint8_t &aId) +{ + Error error = kErrorNotFound; + + for (uint8_t id = kMinId; id <= kMaxId; id++) + { + if (IsUnallocated(id)) + { + aId = id; + error = kErrorNone; + break; + } + } + + return error; +} + +void Leader::ContextIds::ScheduleToRemove(uint8_t aId) +{ + VerifyOrExit(IsInUse(aId)); + + SetRemoveTime(aId, TimerMilli::GetNow() + Time::SecToMsec(mReuseDelay)); + Get().mTimer.FireAtIfEarlier(GetRemoveTime(aId)); + +exit: + return; +} + +void Leader::ContextIds::SetRemoveTime(uint8_t aId, TimeMilli aTime) +{ + uint32_t time = aTime.GetValue(); + + while ((time == kUnallocated) || (time == kInUse)) + { + time++; + } + + mRemoveTimes[aId - kMinId].SetValue(time); +} + +void Leader::ContextIds::HandleTimer(void) +{ + TimeMilli now = TimerMilli::GetNow(); + TimeMilli nextTime = now.GetDistantFuture(); + + for (uint8_t id = kMinId; id <= kMaxId; id++) + { + if (IsUnallocated(id) || IsInUse(id)) + { + continue; + } + + if (now >= GetRemoveTime(id)) + { + MarkAsUnallocated(id); + Get().RemoveContext(id); + } + else + { + nextTime = Min(nextTime, GetRemoveTime(id)); + } + } + + if (nextTime != now.GetDistantFuture()) + { + Get().mTimer.FireAt(nextTime); + } +} + } // namespace NetworkData } // namespace ot diff --git a/src/core/thread/network_data_leader_ftd.hpp b/src/core/thread/network_data_leader_ftd.hpp index ce1a08195..c7a3cd152 100644 --- a/src/core/thread/network_data_leader_ftd.hpp +++ b/src/core/thread/network_data_leader_ftd.hpp @@ -41,6 +41,7 @@ #include #include "common/non_copyable.hpp" +#include "common/numeric_limits.hpp" #include "common/timer.hpp" #include "net/ip6_address.hpp" #include "thread/mle_router.hpp" @@ -122,20 +123,20 @@ public: /** * This method returns CONTEXT_ID_RESUSE_DELAY value. * - * @returns The CONTEXT_ID_REUSE_DELAY value. + * @returns The CONTEXT_ID_REUSE_DELAY value (in seconds). * */ - uint32_t GetContextIdReuseDelay(void) const { return mContextIdReuseDelay; } + uint32_t GetContextIdReuseDelay(void) const { return mContextIds.GetReuseDelay(); } /** * This method sets CONTEXT_ID_RESUSE_DELAY value. * * @warning This method should only be used for testing. * - * @param[in] aDelay The CONTEXT_ID_REUSE_DELAY value. + * @param[in] aDelay The CONTEXT_ID_REUSE_DELAY value (in seconds). * */ - void SetContextIdReuseDelay(uint32_t aDelay) { mContextIdReuseDelay = aDelay; } + void SetContextIdReuseDelay(uint32_t aDelay) { mContextIds.SetReuseDelay(aDelay); } /** * This method removes Network Data entries matching with a given RLOC16. @@ -177,6 +178,8 @@ public: #endif private: + static constexpr uint32_t kMaxNetDataSyncWait = 60 * 1000; // Maximum time to wait for netdata sync in msec. + class ChangedFlags { public: @@ -206,6 +209,55 @@ private: kTlvUpdated, // TLV stable flag is updated based on its sub TLVs. }; + class ContextIds : public InstanceLocator + { + public: + // This class tracks Context IDs. A Context ID can be in one + // of the 3 states: It is unallocated, or it is allocated + // and in-use, or it scheduled to be removed (after reuse delay + // interval is passed). + + static constexpr uint8_t kInvalidId = NumericLimits::kMax; + + explicit ContextIds(Instance &aInstance) + : InstanceLocator(aInstance) + , mReuseDelay(kReuseDelay) + { + } + + void Clear(void); + Error GetUnallocatedId(uint8_t &aId); + void MarkAsInUse(uint8_t aId) { mRemoveTimes[aId - kMinId].SetValue(kInUse); } + void ScheduleToRemove(uint8_t aId); + uint32_t GetReuseDelay(void) const { return mReuseDelay; } + void SetReuseDelay(uint32_t aDelay) { mReuseDelay = aDelay; } + void HandleTimer(void); + + private: + static constexpr uint32_t kReuseDelay = 48 * 60 * 60; // in seconds + + static constexpr uint8_t kMinId = 1; + static constexpr uint8_t kMaxId = 15; + + // The `mRemoveTimes[id]` is used to track the state of a + // Context ID and its remove time. Two specific values + // `kUnallocated` and `kInUse` are used to indicate ID is in + // unallocated or in-use states. Other values indicate we + // are in remove state waiting to remove it at `mRemoveTime`. + + static constexpr uint32_t kUnallocated = 0; + static constexpr uint32_t kInUse = 1; + + bool IsUnallocated(uint8_t aId) const { return mRemoveTimes[aId - kMinId].GetValue() == kUnallocated; } + bool IsInUse(uint8_t aId) const { return mRemoveTimes[aId - kMinId].GetValue() == kInUse; } + TimeMilli GetRemoveTime(uint8_t aId) const { return mRemoveTimes[aId - kMinId]; } + void SetRemoveTime(uint8_t aId, TimeMilli aTime); + void MarkAsUnallocated(uint8_t aId) { mRemoveTimes[aId - kMinId].SetValue(kUnallocated); } + + TimeMilli mRemoveTimes[kMaxId - kMinId + 1]; + uint32_t mReuseDelay; + }; + template void HandleTmf(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo); void HandleTimer(void); @@ -220,11 +272,6 @@ private: Error AllocateServiceId(uint8_t &aServiceId) const; - Error AllocateContextId(uint8_t &aContextId); - void FreeContextId(uint8_t aContextId); - void StartContextReuseTimer(uint8_t aContextId); - void StopContextReuseTimer(uint8_t aContextId); - void RemoveContext(uint8_t aContextId); void RemoveContext(PrefixTlv &aPrefix, uint8_t aContextId); @@ -285,16 +332,8 @@ private: using UpdateTimer = TimerMilliIn; - static constexpr uint8_t kMinContextId = 1; // Minimum Context ID (0 is used for Mesh Local) - static constexpr uint8_t kNumContextIds = 15; // Maximum Context ID - static constexpr uint32_t kContextIdReuseDelay = 48 * 60 * 60; // in seconds - static constexpr uint32_t kStateUpdatePeriod = 60 * 1000; // State update period in milliseconds - static constexpr uint32_t kMaxNetDataSyncWait = 60 * 1000; // Maximum time to wait for netdata sync. - bool mWaitingForNetDataSync; - uint16_t mContextUsed; - TimeMilli mContextLastUsed[kNumContextIds]; - uint32_t mContextIdReuseDelay; + ContextIds mContextIds; UpdateTimer mTimer; }; diff --git a/tests/toranj/cli/test-019-netdata-context-id.py b/tests/toranj/cli/test-019-netdata-context-id.py new file mode 100755 index 000000000..dd60cd6b6 --- /dev/null +++ b/tests/toranj/cli/test-019-netdata-context-id.py @@ -0,0 +1,369 @@ +#!/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: Network Data Context ID assignment and reuse delay. +# +# Network topology +# +# r1 ---- r2 ---- r3 +# + +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) + +r1 = cli.Node() +r2 = cli.Node() +r3 = cli.Node() + +# ----------------------------------------------------------------------------------------------------------------------- +# Form topology + +r1.allowlist_node(r2) + +r2.allowlist_node(r1) +r2.allowlist_node(r3) + +r3.allowlist_node(r2) + +r1.form('netdata') +r2.join(r1) +r3.join(r1) + +verify(r1.get_state() == 'leader') +verify(r2.get_state() == 'router') +verify(r3.get_state() == 'router') + +# ----------------------------------------------------------------------------------------------------------------------- +# Test Implementation + +# Change the reuse delay on `r1 (leader) to 5 seconds. + +r1.cli('contextreusedelay 5') +verify(int(r1.cli('contextreusedelay')[0]) == 5) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Add an on-mesh prefix from each router `r1`, r2`, and `r3`. +# Validate that netdata is updated and Context IDs are assigned. + +r1.add_prefix('fd00:1::/64', 'poas') +r1.register_netdata() + +r2.add_prefix('fd00:2::/64', 'poas') +r2.register_netdata() + +r3.add_prefix('fd00:3::/64', 'poas') +r3.add_route('fd00:beef::/64', 's') +r3.register_netdata() + + +def check_netdata_1(): + global netdata + netdata = r1.get_netdata() + verify(len(netdata['prefixes']) == 3) + verify(len(netdata['routes']) == 1) + + +verify_within(check_netdata_1, 5) + +contexts = netdata['contexts'] +verify(len(contexts) == 3) +verify(any([context.startswith('fd00:1:0:0::/64') for context in contexts])) +verify(any([context.startswith('fd00:2:0:0::/64') for context in contexts])) +verify(any([context.startswith('fd00:3:0:0::/64') for context in contexts])) +for context in contexts: + verify(context.endswith('c')) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Remove prefix on `r3`. Validate that Context compress flag +# is cleared for the removed prefix. + +r3.remove_prefix('fd00:3::/64') +r3.register_netdata() + + +def check_netdata_2(): + global netdata, versions + versions = r1.get_netdata_versions() + netdata = r1.get_netdata() + verify(len(netdata['prefixes']) == 2) + verify(len(netdata['routes']) == 1) + + +verify_within(check_netdata_2, 5) + +contexts = netdata['contexts'] +verify(len(contexts) == 3) +verify(any([context.startswith('fd00:1:0:0::/64') for context in contexts])) +verify(any([context.startswith('fd00:2:0:0::/64') for context in contexts])) +verify(any([context.startswith('fd00:3:0:0::/64') for context in contexts])) +for context in contexts: + if context.startswith('fd00:1:0:0::/64') or context.startswith('fd00:2:0:0::/64'): + verify(context.endswith('c')) + else: + verify(context.endswith('-')) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Validate that the prefix context is removed within reuse delay +# time (which is set to 5 seconds on leader). + + +def check_netdata_3(): + global netdata + netdata = r1.get_netdata() + verify(len(netdata['prefixes']) == 2) + verify(len(netdata['routes']) == 1) + verify(len(netdata['contexts']) == 2) + + +verify_within(check_netdata_3, 5) +old_versions = versions +versions = r1.get_netdata_versions() +verify(versions != old_versions) + +# Make sure netdata does not change afterwards + +time.sleep(5 * 3 / speedup) + +verify(versions == r1.get_netdata_versions()) +netdata = r1.get_netdata() +verify(len(netdata['prefixes']) == 2) +verify(len(netdata['routes']) == 1) +verify(len(netdata['contexts']) == 2) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Add two new on-mesh prefixes from `r3`. Validate that they get +# assigned Context IDs. + +r3.add_prefix('fd00:4::/64', 'poas') +r3.register_netdata() + +r3.add_prefix('fd00:3::/64', 'poas') +r3.register_netdata() + + +def check_netdata_4(): + global netdata + netdata = r1.get_netdata() + verify(len(netdata['prefixes']) == 4) + verify(len(netdata['routes']) == 1) + verify(len(netdata['contexts']) == 4) + + +verify_within(check_netdata_4, 5) + +contexts = netdata['contexts'] +verify(len(contexts) == 4) +verify(any([context.startswith('fd00:1:0:0::/64') for context in contexts])) +verify(any([context.startswith('fd00:2:0:0::/64') for context in contexts])) +verify(any([context.startswith('fd00:3:0:0::/64') for context in contexts])) +verify(any([context.startswith('fd00:4:0:0::/64') for context in contexts])) +for context in contexts: + verify(context.endswith('c')) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Remove prefixes on `r1` and `r2` and re-add them back quickly both +# from `r2`. Validate that they get the same context IDs as before. + +for context in contexts: + if context.startswith('fd00:1:0:0::/64'): + cid1 = int(context.split()[1]) + elif context.startswith('fd00:2:0:0::/64'): + cid2 = int(context.split()[1]) + +r1.remove_prefix('fd00:1:0:0::/64') +r1.register_netdata() + +r2.remove_prefix('fd00:2:0:0::/64') +r2.register_netdata() + + +def check_netdata_5(): + global netdata + netdata = r1.get_netdata() + verify(len(netdata['prefixes']) == 2) + verify(len(netdata['routes']) == 1) + + +verify_within(check_netdata_5, 5) + +contexts = netdata['contexts'] +verify(len(contexts) == 4) +verify(any([context.startswith('fd00:1:0:0::/64') for context in contexts])) +verify(any([context.startswith('fd00:2:0:0::/64') for context in contexts])) +verify(any([context.startswith('fd00:3:0:0::/64') for context in contexts])) +verify(any([context.startswith('fd00:4:0:0::/64') for context in contexts])) +for context in contexts: + if context.startswith('fd00:1:0:0::/64') or context.startswith('fd00:2:0:0::/64'): + verify(context.endswith('-')) + else: + verify(context.endswith('c')) + +# Re-add both prefixes (now from `r2`) before CID remove delay time +# is expired. + +r2.add_prefix('fd00:1::/64', 'poas') +r2.add_prefix('fd00:2::/64', 'poas') +r2.register_netdata() + + +def check_netdata_6(): + global netdata + netdata = r1.get_netdata() + verify(len(netdata['prefixes']) == 4) + verify(len(netdata['routes']) == 1) + + +verify_within(check_netdata_6, 5) + +contexts = netdata['contexts'] +verify(len(contexts) == 4) +verify(any([context.startswith('fd00:1:0:0::/64') for context in contexts])) +verify(any([context.startswith('fd00:2:0:0::/64') for context in contexts])) +verify(any([context.startswith('fd00:3:0:0::/64') for context in contexts])) +verify(any([context.startswith('fd00:4:0:0::/64') for context in contexts])) +for context in contexts: + verify(context.endswith('c')) + if context.startswith('fd00:1:0:0::/64'): + verify(int(context.split()[1]) == cid1) + elif context.startswith('fd00:2:0:0::/64'): + verify(int(context.split()[1]) == cid2) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Remove two prefixes on `r3`. Add one back as an external route from +# `r1`. Also add a new prefix from `r1`. Validate that the context IDs +# for the removed on-mesh prefixes are removed after reuse delay time +# and that the new prefix gets a different Context ID. + +# Remember the CID used. +for context in contexts: + if context.startswith('fd00:3:0:0::/64'): + cid3 = int(context.split()[1]) + elif context.startswith('fd00:4:0:0::/64'): + cid4 = int(context.split()[1]) + +r3.remove_prefix('fd00:3:0:0::/64') +r3.remove_prefix('fd00:4:0:0::/64') +r3.register_netdata() + + +def check_netdata_7(): + global netdata + netdata = r1.get_netdata() + verify(len(netdata['prefixes']) == 2) + verify(len(netdata['routes']) == 1) + + +verify_within(check_netdata_7, 5) + +contexts = netdata['contexts'] +verify(len(contexts) == 4) +verify(any([context.startswith('fd00:1:0:0::/64') for context in contexts])) +verify(any([context.startswith('fd00:2:0:0::/64') for context in contexts])) +verify(any([context.startswith('fd00:3:0:0::/64') for context in contexts])) +verify(any([context.startswith('fd00:4:0:0::/64') for context in contexts])) +for context in contexts: + if context.startswith('fd00:3:0:0::/64') or context.startswith('fd00:4:0:0::/64'): + verify(context.endswith('-')) + else: + verify(context.endswith('c')) + +# Add first one removed as route and add a new prefix. + +r1.add_route('fd00:3:0:0::/64', 's') +r1.add_prefix('fd00:5:0:0::/64', 'poas') +r1.register_netdata() + + +def check_netdata_8(): + global netdata + netdata = r1.get_netdata() + verify(len(netdata['prefixes']) == 3) + verify(len(netdata['routes']) == 2) + verify(len(netdata['contexts']) == 3) + + +verify_within(check_netdata_8, 5) + +contexts = netdata['contexts'] +verify(len(contexts) == 3) +verify(any([context.startswith('fd00:1:0:0::/64') for context in contexts])) +verify(any([context.startswith('fd00:2:0:0::/64') for context in contexts])) +verify(any([context.startswith('fd00:5:0:0::/64') for context in contexts])) + +for context in contexts: + verify(context.endswith('c')) + if context.startswith('fd00:5:0:0::/64'): + verify(not int(context.split()[1]) in [cid3, cid4]) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Remove a prefix on `r2`, wait one second and remove a second +# prefix. Validate the Context IDs for both are removed. + +r2.remove_prefix('fd00:1::/64') +r2.register_netdata() + +time.sleep(1 / speedup) + +r2.remove_prefix('fd00:2::/64') +r2.register_netdata() + + +def check_netdata_9(): + global netdata + netdata = r1.get_netdata() + verify(len(netdata['prefixes']) == 1) + verify(len(netdata['routes']) == 2) + verify(len(netdata['contexts']) == 1) + + +verify_within(check_netdata_9, 5) + +contexts = netdata['contexts'] +verify(len(contexts) == 1) +verify(contexts[0].startswith('fd00:5:0:0::/64')) +verify(contexts[0].endswith('c')) + +# ----------------------------------------------------------------------------------------------------------------------- +# 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 01fb3e47d..d2f019786 100755 --- a/tests/toranj/start.sh +++ b/tests/toranj/start.sh @@ -183,6 +183,7 @@ if [ "$TORANJ_CLI" = 1 ]; then run cli/test-016-child-mode-change.py run cli/test-017-network-data-versions.py run cli/test-018-next-hop-and-path-cost.py + run cli/test-019-netdata-context-id.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