From 650e30f81ccfd798409d459a86fb1ed575b5144a Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Wed, 26 Jul 2023 15:50:40 -0700 Subject: [PATCH] [mle] update Avd trickle timer interval based on router neighbor count (#9307) This commit updates the `TrickleTimer` to allow `IntervalMax` and `IntervalMin` to be changed while the timer is running. In particular, when `IntervalMax` is changed to a value that is shorter than the current interval being used by the timer, the timer will adapt the new shorter interval and may fire immediately. A unit test `test_trickle_timer` has been added to validate the behavior of `TrickleTimer` in detail. All different scenarios where `IntervalMax` or `IntervalMin` are changed are covered by the unit test. The new mechanism to change the trickle timer `IntervalMax` is used to update the MLE Advertisement trickle timer. The `IntervalMax` is determined based on the number of router neighbors of the device with link quality 2 or better. If the device has fewer router neighbors, it will use a shorter `IntervalMax`. As new links are established with routers, the `IntervalMax` is recalculated and updated on the Advertisement trickle timer. This commit also adds a new test `test-024-mle-adv-imax-change.py` to validate updates to the `IntervalMax` value based on the number of router neighbors. Co-authored-by: David Smith --- include/openthread/instance.h | 2 +- include/openthread/thread_ftd.h | 10 + src/cli/cli.cpp | 20 + src/core/api/thread_ftd_api.cpp | 11 +- src/core/common/trickle_timer.cpp | 153 ++++++ src/core/common/trickle_timer.hpp | 43 +- src/core/net/ip6_mpl.cpp | 5 +- src/core/thread/mle_router.cpp | 30 +- src/core/thread/mle_router.hpp | 33 +- src/core/thread/mle_types.hpp | 13 +- src/core/thread/neighbor_table.cpp | 7 + src/core/thread/router_table.cpp | 6 +- src/core/thread/router_table.hpp | 8 +- tests/toranj/cli/cli.py | 6 + .../cli/test-024-mle-adv-imax-change.py | 162 ++++++ tests/toranj/start.sh | 1 + tests/unit/CMakeLists.txt | 21 + tests/unit/test_trickle_timer.cpp | 516 ++++++++++++++++++ 18 files changed, 1020 insertions(+), 27 deletions(-) create mode 100755 tests/toranj/cli/test-024-mle-adv-imax-change.py create mode 100644 tests/unit/test_trickle_timer.cpp diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 7b0edb4dd..3be5a11d7 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 (348) +#define OPENTHREAD_API_VERSION (349) /** * @addtogroup api-instance diff --git a/include/openthread/thread_ftd.h b/include/openthread/thread_ftd.h index 24154e45d..930262368 100644 --- a/include/openthread/thread_ftd.h +++ b/include/openthread/thread_ftd.h @@ -881,6 +881,16 @@ void otThreadGetRouterIdRange(otInstance *aInstance, uint8_t *aMinRouterId, uint */ otError otThreadSetRouterIdRange(otInstance *aInstance, uint8_t aMinRouterId, uint8_t aMaxRouterId); +/** + * Gets the current Interval Max value used by Advertisement trickle timer. + * + * This API requires `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE`, and is intended for testing only. + * + * @returns The Interval Max of Advertisement trickle timer in milliseconds. + * + */ +uint32_t otThreadGetAdvertisementTrickleIntervalMax(otInstance *aInstance); + /** * Indicates whether or not a Router ID is currently allocated. * diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 5ab340b2f..fcfee7b9d 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -4135,6 +4135,23 @@ exit: #endif #endif // OPENTHREAD_FTD +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE +/** + * @cli mleadvimax + * @code + * mleadvimax + * 12000 + * Done + * @endcode + * @par api_copy + * #otThreadGetAdvertisementTrickleIntervalMax + */ +template <> otError Interpreter::Process(Arg aArgs[]) +{ + return ProcessGet(aArgs, otThreadGetAdvertisementTrickleIntervalMax); +} +#endif + #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE /** * @cli mliid @@ -8046,6 +8063,9 @@ otError Interpreter::ProcessCommand(Arg aArgs[]) #if OPENTHREAD_CONFIG_MESH_DIAG_ENABLE && OPENTHREAD_FTD CmdEntry("meshdiag"), #endif +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + CmdEntry("mleadvimax"), +#endif #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE CmdEntry("mliid"), #endif diff --git a/src/core/api/thread_ftd_api.cpp b/src/core/api/thread_ftd_api.cpp index 8b8504bb8..814e80a94 100644 --- a/src/core/api/thread_ftd_api.cpp +++ b/src/core/api/thread_ftd_api.cpp @@ -357,6 +357,7 @@ void otThreadSetDiscoveryRequestCallback(otInstance *aInsta } #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + void otThreadSendAddressNotification(otInstance *aInstance, otIp6Address *aDestination, otIp6Address *aTarget, @@ -386,9 +387,7 @@ void otThreadSetThreadVersionCheckEnabled(otInstance *aInstance, bool aEnabled) { AsCoreType(aInstance).Get().SetThreadVersionCheckEnabled(aEnabled); } -#endif -#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE void otThreadGetRouterIdRange(otInstance *aInstance, uint8_t *aMinRouterId, uint8_t *aMaxRouterId) { AssertPointerIsNotNull(aMinRouterId); @@ -401,7 +400,13 @@ otError otThreadSetRouterIdRange(otInstance *aInstance, uint8_t aMinRouterId, ui { return AsCoreType(aInstance).Get().SetRouterIdRange(aMinRouterId, aMaxRouterId); } -#endif + +uint32_t otThreadGetAdvertisementTrickleIntervalMax(otInstance *aInstance) +{ + return AsCoreType(aInstance).Get().GetAdvertisementTrickleIntervalMax(); +} + +#endif // OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE bool otThreadIsRouterIdAllocated(otInstance *aInstance, uint8_t aRouterId) { diff --git a/src/core/common/trickle_timer.cpp b/src/core/common/trickle_timer.cpp index 7258cd27e..2f86f808c 100644 --- a/src/core/common/trickle_timer.cpp +++ b/src/core/common/trickle_timer.cpp @@ -35,6 +35,7 @@ #include "common/code_utils.hpp" #include "common/debug.hpp" +#include "common/num_utils.hpp" #include "common/random.hpp" namespace ot { @@ -53,6 +54,158 @@ TrickleTimer::TrickleTimer(Instance &aInstance, Handler aHandler) { } +TimeMilli TrickleTimer::GetStartTimeOfCurrentInterval(void) const +{ + // Determines and returns the start time of the current + // interval. + + TimeMilli startTime = TimerMilli::GetFireTime(); + + if (mMode == kModePlainTimer) + { + startTime -= mInterval; + ExitNow(); + } + + switch (mPhase) + { + case kBeforeRandomTime: + startTime -= mTimeInInterval; + break; + + case kAfterRandomTime: + startTime -= mInterval; + break; + } + +exit: + return startTime; +} + +void TrickleTimer::SetIntervalMin(uint32_t aIntervalMin) +{ + VerifyOrExit(IsRunning()); + + mIntervalMin = aIntervalMin; + + if (mIntervalMax < mIntervalMin) + { + SetIntervalMax(mIntervalMin); + } + +exit: + return; +} + +void TrickleTimer::SetIntervalMax(uint32_t aIntervalMax) +{ + TimeMilli endOfInterval; + + VerifyOrExit(IsRunning()); + + aIntervalMax = Max(mIntervalMin, aIntervalMax); + VerifyOrExit(aIntervalMax != mIntervalMax); + + mIntervalMax = aIntervalMax; + + // If the new `aIntervalMax` is greater than the current + // `mInterval`, no action is needed. The new `aIntervalMax` will be + // used as the `mInterval` grows. + + VerifyOrExit(mIntervalMax < mInterval); + + // Determine the end of the interval as if the new and shorter + // `mIntervalMax` would have been used. The calculated time may + // be in the past. In this case, `FireAt(endOfInterval)` will + // cause the timer to fire immediately. + + endOfInterval = GetStartTimeOfCurrentInterval() + mIntervalMax; + + if (mMode == kModePlainTimer) + { + TimerMilli::FireAt(endOfInterval); + ExitNow(); + } + + // Trickle mode possible scenarios: + // + // We are in `kBeforeRandomTime` phase. + // + // a) If the new `aIntervalMax < mTimeInInterval`: + // - Reschedule the timer to fire at new `endOfInterval` + // (which may fire immediately). + // - Set `mTimeInInterval = aIntervalMax` + // - Set `mInterval` to use the shorter `aIntervalMax`. + // + // |<---- mInterval ----------------^------------------>| + // |<---- mTimeInInterval ----------^---->| | + // |<---- aIntervalMax -------->| ^ | | + // | | now | | + // + // b) If the new `aIntervalMax >= mTimeInInterval`: + // - Keep timer unchanged to fire at `mTimeInInterval` + // - Keep `mTimeInInterval` unchanged. + // - Set `mInterval` to use the shorter `aIntervalMax`. + // + // |<---- mInterval ----------------^------------------>| + // |<---- mTimeInInterval ----------^---->| | | + // |<---- aIntervalMax -------------^--------->| | + // | now | | + // + // We are in `kAfterRandomTime` phase. + // + // c) If the new `aIntervalMax < mTimeInInterval`: + // - Act as if current interval is already finished. + // - Reschedule the timer to fire at new `endOfInterval` + // (which should fire immediately). + // - Set `mInterval` to use the shorter `aIntervalMax`. + // - The `mTimeInInterval` value does not matter as we + // are in `kAfterRandomTime` phase. Keep it unchanged. + // + // |<---- mInterval ---------------------------^------->| + // |<---- mTimeInInterval --------------->| ^ | + // |<---- aIntervalMax -------->| | ^ | + // | | | now | + // + // d) If the new `aIntervalMax >= mTimeInInterval`: + // - Reschedule the timer to fire at new `endOfInterval` + // - Set `mInterval` to use the shorter `aIntervalMax`. + // - The `mTimeInInterval` value does not matter as we + // are in `kAfterRandomTime` phase. Keep it unchanged. + // + // |<---- mInterval ---------------------------^------->| + // |<---- mTimeInInterval --------------->| ^ | | + // |<---- aIntervalMax ------------------------^-->| | + // | | now | | + + // In all cases we need to set `mInterval` to the new + // shorter `aIntervalMax`. + + mInterval = aIntervalMax; + + switch (mPhase) + { + case kBeforeRandomTime: + if (aIntervalMax < mTimeInInterval) + { + mTimeInInterval = aIntervalMax; + } + else + { + break; + } + + OT_FALL_THROUGH; + + case kAfterRandomTime: + TimerMilli::FireAt(endOfInterval); + break; + } + +exit: + return; +} + void TrickleTimer::Start(Mode aMode, uint32_t aIntervalMin, uint32_t aIntervalMax, uint16_t aRedundancyConstant) { OT_ASSERT((aIntervalMax >= aIntervalMin) && (aIntervalMin > 0)); diff --git a/src/core/common/trickle_timer.hpp b/src/core/common/trickle_timer.hpp index df2a2512c..7c3e0ff4e 100644 --- a/src/core/common/trickle_timer.hpp +++ b/src/core/common/trickle_timer.hpp @@ -57,6 +57,8 @@ namespace ot { */ class TrickleTimer : public TimerMilli { + friend class TrickleTimerTester; + public: /** * Defines the modes of operation for the `TrickleTimer`. @@ -110,7 +112,45 @@ public: Mode GetMode(void) const { return mMode; } /** - * Starts the trickle timer. + * Gets the interval min value of the trickle timer. + * + * @returns The interval min value in milliseconds. + * + */ + uint32_t GetIntervalMin(void) const { return mIntervalMin; } + + /** + * Sets the interval min value of the trickle timer while timer is running. + * + * If @p aIntervalMin is smaller than the current `GetIntervalMax()` the interval max value is also updated to + * the new @p aIntervalMin (as if `SetIntervalMax(aIntervalMin)` was called). + * + * @param[in] aIntervalMin The minimum interval in milliseconds. + * + */ + void SetIntervalMin(uint32_t aIntervalMin); + + /** + * Gets the interval max value of the trickle timer. + * + * @returns The interval max value in milliseconds. + * + */ + uint32_t GetIntervalMax(void) const { return mIntervalMax; } + + /** + * Sets the interval max value of the trickle timer while timer is running. + * + * If the given @p aIntervalMax is smaller than the current `GetIntervalMin()`, the interval min value will be + * used instead. + * + * @param[in] aIntervalMax The maximum interval in milliseconds. + * + */ + void SetIntervalMax(uint32_t aIntervalMax); + + /** + * This method starts the trickle timer. * * @param[in] aMode The operation mode of timer (trickle or plain periodic mode). * @param[in] aIntervalMin The minimum interval for the timer in milliseconds. @@ -162,6 +202,7 @@ private: void HandleTimer(void); void HandleEndOfTimeInInterval(void); void HandleEndOfInterval(void); + TimeMilli GetStartTimeOfCurrentInterval(void) const; // Shadow base class `TimerMilli` methods to ensure they are hidden. void StartAt(void) {} diff --git a/src/core/net/ip6_mpl.cpp b/src/core/net/ip6_mpl.cpp index f39d83300..616b0314a 100644 --- a/src/core/net/ip6_mpl.cpp +++ b/src/core/net/ip6_mpl.cpp @@ -340,12 +340,13 @@ void Mpl::AddBufferedMessage(Message &aMessage, uint16_t aSeedId, uint8_t aSeque Message *messageCopy = nullptr; Metadata metadata; uint8_t hopLimit = 0; + uint8_t interval; #if OPENTHREAD_CONFIG_MPL_DYNAMIC_INTERVAL_ENABLE // adjust the first MPL forward interval dynamically according to the network scale - uint8_t interval = (kDataMessageInterval / Mle::kMaxRouters) * Get().GetNeighborCount(); + interval = (kDataMessageInterval / Mle::kMaxRouters) * Get().GetNeighborCount(kLinkQuality1); #else - uint8_t interval = kDataMessageInterval; + interval = kDataMessageInterval; #endif VerifyOrExit(GetTimerExpirations() > 0); diff --git a/src/core/thread/mle_router.cpp b/src/core/thread/mle_router.cpp index 7b5817537..ddcca0569 100644 --- a/src/core/thread/mle_router.cpp +++ b/src/core/thread/mle_router.cpp @@ -482,14 +482,38 @@ exit: void MleRouter::StopAdvertiseTrickleTimer(void) { mAdvertiseTrickleTimer.Stop(); } +uint32_t MleRouter::DetermineAdvertiseIntervalMax(void) const +{ + uint32_t interval; + +#if OPENTHREAD_CONFIG_MLE_LONG_ROUTES_ENABLE + interval = kAdvIntervalMaxLogRoutes; +#else + // Determine the interval based on the number of router neighbors + // with link quality 2 or higher. + + interval = (Get().GetNeighborCount(kLinkQuality2) + 1) * kAdvIntervalNeighborMultiplier; + interval = Clamp(interval, kAdvIntervalMaxLowerBound, kAdvIntervalMaxUpperBound); +#endif + + return interval; +} + +void MleRouter::UpdateAdvertiseInterval(void) +{ + if (IsRouterOrLeader() && mAdvertiseTrickleTimer.IsRunning()) + { + mAdvertiseTrickleTimer.SetIntervalMax(DetermineAdvertiseIntervalMax()); + } +} + void MleRouter::ResetAdvertiseInterval(void) { VerifyOrExit(IsRouterOrLeader()); if (!mAdvertiseTrickleTimer.IsRunning()) { - mAdvertiseTrickleTimer.Start(TrickleTimer::kModeTrickle, Time::SecToMsec(kAdvertiseIntervalMin), - Time::SecToMsec(kAdvertiseIntervalMax)); + mAdvertiseTrickleTimer.Start(TrickleTimer::kModeTrickle, kAdvIntervalMin, DetermineAdvertiseIntervalMax()); } mAdvertiseTrickleTimer.IndicateInconsistent(); @@ -1312,7 +1336,7 @@ Error MleRouter::HandleAdvertisement(RxInfo &aRxInfo, uint16_t aSourceAddress, c VerifyOrExit(router != nullptr); if (!router->IsStateValid() && !router->IsStateLinkRequest() && - (mRouterTable.GetNeighborCount() < mChildRouterLinks)) + (mRouterTable.GetNeighborCount(kLinkQuality1) < mChildRouterLinks)) { InitNeighbor(*router, aRxInfo); router->SetState(Neighbor::kStateLinkRequest); diff --git a/src/core/thread/mle_router.hpp b/src/core/thread/mle_router.hpp index 801722b8f..2424789a1 100644 --- a/src/core/thread/mle_router.hpp +++ b/src/core/thread/mle_router.hpp @@ -502,6 +502,14 @@ public: */ void ResetAdvertiseInterval(void); + /** + * Updates the MLE Advertisement Trickle timer max interval (if timer is running). + * + * This is called when there is change in router table. + * + */ + void UpdateAdvertiseInterval(void); + #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE /** * Generates an MLE Time Synchronization message. @@ -532,6 +540,7 @@ public: uint8_t GetMaxChildIpAddresses(void) const; #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + /** * Sets/restores the maximum number of IP addresses that each MTD child may register with this * device as parent. @@ -560,9 +569,27 @@ public: * */ void SetThreadVersionCheckEnabled(bool aEnabled) { mThreadVersionCheckEnabled = aEnabled; } -#endif + + /** + * Gets the current Interval Max value used by Advertisement trickle timer. + * + * @returns The Interval Max of Advertisement trickle timer in milliseconds. + * + */ + uint32_t GetAdvertisementTrickleIntervalMax(void) const { return mAdvertiseTrickleTimer.GetIntervalMax(); } + +#endif // OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE private: + // Advertisement trickle timer constants - all times are in milliseconds. + static constexpr uint32_t kAdvIntervalMin = 1000; // I_MIN + static constexpr uint32_t kAdvIntervalNeighborMultiplier = 4000; // Multiplier for I_MAX per router neighbor + static constexpr uint32_t kAdvIntervalMaxLowerBound = 12000; // Lower bound for I_MAX + static constexpr uint32_t kAdvIntervalMaxUpperBound = 32000; // Upper bound for I_MAX +#if OPENTHREAD_CONFIG_MLE_LONG_ROUTES_ENABLE + constexpr uint32_t kAdvIntervalMaxLogRoutes = 5000; +#endif + static constexpr uint16_t kDiscoveryMaxJitter = 250; // Max jitter delay Discovery Responses (in msec). static constexpr uint16_t kChallengeTimeout = 2; // Challenge timeout (in sec). static constexpr uint16_t kUnsolicitedDataResponseJitter = 500; // Max delay for unsol Data Response (in msec). @@ -606,7 +633,9 @@ private: Error ProcessRouteTlv(const RouteTlv &aRouteTlv, RxInfo &aRxInfo); Error ReadAndProcessRouteTlvOnFed(RxInfo &aRxInfo, uint8_t aParentId); - void StopAdvertiseTrickleTimer(void); + void StopAdvertiseTrickleTimer(void); + uint32_t DetermineAdvertiseIntervalMax(void) const; + Error SendAddressSolicit(ThreadStatusTlv::Status aStatus); void SendAddressSolicitResponse(const Coap::Message &aRequest, ThreadStatusTlv::Status aResponseStatus, diff --git a/src/core/thread/mle_types.hpp b/src/core/thread/mle_types.hpp index c959f9a56..32f0ff1f8 100644 --- a/src/core/thread/mle_types.hpp +++ b/src/core/thread/mle_types.hpp @@ -81,7 +81,7 @@ constexpr uint8_t kFailedChildTransmissions = OPENTHREAD_CONFIG_FAILED_CHILD_T // Extra one for core Backbone Router Service. constexpr uint8_t kMaxServiceAlocs = OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_MAX_ALOCS + 1; #else -constexpr uint8_t kMaxServiceAlocs = OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_MAX_ALOCS; +constexpr uint8_t kMaxServiceAlocs = OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_MAX_ALOCS; #endif constexpr uint16_t kUdpPort = 19788; ///< MLE UDP Port @@ -124,7 +124,7 @@ constexpr uint32_t kMinTimeout = OT_MAX(kMinTimeoutKeepAlive, kMinTimeo #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE constexpr uint8_t kLinkAcceptMaxRouters = 3; ///< Max Route TLV entries in a Link Accept message #else -constexpr uint8_t kLinkAcceptMaxRouters = 20; ///< Max Route TLV entries in a Link Accept message +constexpr uint8_t kLinkAcceptMaxRouters = 20; ///< Max Route TLV entries in a Link Accept message #endif constexpr uint8_t kLinkAcceptSequenceRollback = 64; ///< Route Sequence value rollback in a Link Accept message. @@ -141,13 +141,6 @@ constexpr uint8_t kMaxChallengeSize = 8; ///< Maximum Challenge size in bytes. * Routing Protocol Constants * */ -constexpr uint32_t kAdvertiseIntervalMin = 1; ///< Min Advertise interval (in sec) -#if OPENTHREAD_CONFIG_MLE_LONG_ROUTES_ENABLE -constexpr uint32_t kAdvertiseIntervalMax = 5; ///< Max Advertise interval (in sec) -#else -constexpr uint32_t kAdvertiseIntervalMax = 32; ///< Max Advertise interval (in sec) -#endif - constexpr uint8_t kFailedRouterTransmissions = 4; #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE constexpr uint8_t kFailedCslDataPollTransmissions = 15; @@ -160,7 +153,7 @@ constexpr uint32_t kMaxNeighborAge = 100; ///< (in sec) #if OPENTHREAD_CONFIG_MLE_LONG_ROUTES_ENABLE constexpr uint8_t kMaxRouteCost = 127; #else -constexpr uint8_t kMaxRouteCost = 16; +constexpr uint8_t kMaxRouteCost = 16; #endif constexpr uint8_t kMaxRouterId = OT_NETWORK_MAX_ROUTER_ID; ///< Max Router ID diff --git a/src/core/thread/neighbor_table.cpp b/src/core/thread/neighbor_table.cpp index 46ab1c790..d5f679b1f 100644 --- a/src/core/thread/neighbor_table.cpp +++ b/src/core/thread/neighbor_table.cpp @@ -310,6 +310,13 @@ void NeighborTable::Signal(Event aEvent, const Neighbor &aNeighbor) #endif break; +#if OPENTHREAD_FTD + case kRouterAdded: + case kRouterRemoved: + Get().SignalTableChanged(); + break; +#endif + default: break; } diff --git a/src/core/thread/router_table.cpp b/src/core/thread/router_table.cpp index 407f51117..42700009a 100644 --- a/src/core/thread/router_table.cpp +++ b/src/core/thread/router_table.cpp @@ -343,13 +343,13 @@ uint32_t RouterTable::GetLeaderAge(void) const return (!mRouters.IsEmpty()) ? Time::MsecToSec(TimerMilli::GetNow() - mRouterIdSequenceLastUpdated) : 0xffffffff; } -uint8_t RouterTable::GetNeighborCount(void) const +uint8_t RouterTable::GetNeighborCount(LinkQuality aLinkQuality) const { uint8_t count = 0; for (const Router &router : mRouters) { - if (router.IsStateValid()) + if (router.IsStateValid() && (router.GetLinkQualityIn() >= aLinkQuality)) { count++; } @@ -881,6 +881,8 @@ void RouterTable::HandleTableChanged(void) #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE Get().RecordRouterTableChange(); #endif + + Get().UpdateAdvertiseInterval(); } #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) diff --git a/src/core/thread/router_table.hpp b/src/core/thread/router_table.hpp index bed25ab13..7419489d8 100644 --- a/src/core/thread/router_table.hpp +++ b/src/core/thread/router_table.hpp @@ -329,12 +329,14 @@ public: bool IsRouteTlvIdSequenceMoreRecent(const Mle::RouteTlv &aRouteTlv) const; /** - * Returns the number of neighbor links. + * Gets the number of router neighbors with `GetLinkQualityIn()` better than or equal to a given threshold. * - * @returns The number of neighbor links. + * @param[in] aLinkQuality Link quality threshold. + * + * @returns Number of router neighbors with link quality of @o aLinkQuality or better. * */ - uint8_t GetNeighborCount(void) const; + uint8_t GetNeighborCount(LinkQuality aLinkQuality) const; /** * Indicates whether or not a Router ID is allocated. diff --git a/tests/toranj/cli/cli.py b/tests/toranj/cli/cli.py index a086c42ec..39427cd34 100644 --- a/tests/toranj/cli/cli.py +++ b/tests/toranj/cli/cli.py @@ -457,6 +457,12 @@ class Node(object): verify(False) return counter + #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + # Misc + + def get_mle_adv_imax(self): + return self._cli_single_output('mleadvimax') + #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # UDP diff --git a/tests/toranj/cli/test-024-mle-adv-imax-change.py b/tests/toranj/cli/test-024-mle-adv-imax-change.py new file mode 100755 index 000000000..b9816226b --- /dev/null +++ b/tests/toranj/cli/test-024-mle-adv-imax-change.py @@ -0,0 +1,162 @@ +#!/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 changes to `IntervalMax` for MLE Advertisement Trickle Timer based on number of +# router neighbors of the device. +# + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print('-' * 120) +print('Starting \'{}\''.format(test_name)) + +# ----------------------------------------------------------------------------------------------------------------------- +# Creating `cli.Node` instances + +speedup = 20 +cli.Node.set_time_speedup_factor(speedup) + +leader = cli.Node() +routers = [] +for num in range(0, 9): + routers.append(cli.Node()) + +# ----------------------------------------------------------------------------------------------------------------------- +# Test Implementation + +leader.form('mle-adv-imax') + +verify(leader.get_state() == 'leader') + +# The Imax is determined as `Clamp((n + 1) * 4, 12, 32)` with `n` as +# number of router neighbors with link quality 2 or higher + +verify(int(leader.get_mle_adv_imax()) == 12000) + +expected_neighbor_count = 0 + + +def check_leader_has_expected_number_of_neighbors(): + verify(len(leader.get_neighbor_table()) == expected_neighbor_count) + + +# Add two routers one by one and check that Imax +# remains at 12 seconds. + +for num in range(0, 2): + r = routers[num] + + r.join(leader) + verify(r.get_state() == 'router') + + expected_neighbor_count += 1 + verify_within(check_leader_has_expected_number_of_neighbors, 10) + + verify(int(leader.get_mle_adv_imax()) == 12000) + +# Adding the third router, we should see Imax increasing +# to 16 seconds. + +r = routers[2] +r.join(leader) +verify(r.get_state() == 'router') + +expected_neighbor_count += 1 +verify_within(check_leader_has_expected_number_of_neighbors, 10) + +verify(int(leader.get_mle_adv_imax()) == 16000) + +# Adding a neighbor with poor link quality which should not +# count. + +r_poor_lqi = routers[3] +leader.set_macfilter_lqi_to_node(r_poor_lqi, 1) + +r_poor_lqi.join(leader) +verify(r_poor_lqi.get_state() == 'router') + +expected_neighbor_count += 1 +verify_within(check_leader_has_expected_number_of_neighbors, 10) +verify(int(leader.get_mle_adv_imax()) == 16000) + +expected_imax = 16000 + +# Add four new routers one by one and check that Imax is +# increased by 4 second for each new router neighbor up to +# 32 seconds. + +for num in range(4, 8): + r = routers[num] + + r.join(leader) + verify(r.get_state() == 'router') + + expected_neighbor_count += 1 + verify_within(check_leader_has_expected_number_of_neighbors, 10) + expected_imax += 4000 + verify(int(leader.get_mle_adv_imax()) == expected_imax) + +# Check that Imax does not increase beyond 32 seconds. + +r = routers[8] + +r.join(leader) +verify(r.get_state() == 'router') + +expected_neighbor_count += 1 +verify_within(check_leader_has_expected_number_of_neighbors, 10) + +verify(int(leader.get_mle_adv_imax()) == 32000) + +# Check that all routers see each other as neighbor and they are all also +# using 32 seconds as Imax. + + +def check_all_routers_have_expected_number_of_neighbors(): + for r in routers: + verify(len(r.get_neighbor_table()) == expected_neighbor_count) + + +verify_within(check_all_routers_have_expected_number_of_neighbors, 10) + +for r in routers: + verify(int(r.get_mle_adv_imax()) == 32000) + +# ----------------------------------------------------------------------------------------------------------------------- +# 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 33766ae4e..609faf7f8 100755 --- a/tests/toranj/start.sh +++ b/tests/toranj/start.sh @@ -188,6 +188,7 @@ if [ "$TORANJ_CLI" = 1 ]; then run cli/test-021-br-route-prf.py run cli/test-022-netdata-full.py run cli/test-023-mesh-diag.py + run cli/test-024-mle-adv-imax-change.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 diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index ca963d247..51f4b0f6a 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -1082,6 +1082,27 @@ target_link_libraries(ot-test-timer add_test(NAME ot-test-timer COMMAND ot-test-timer) +add_executable(ot-test-trickle-timer + test_trickle_timer.cpp +) + +target_include_directories(ot-test-trickle-timer + PRIVATE + ${COMMON_INCLUDES} +) + +target_compile_options(ot-test-trickle-timer + PRIVATE + ${COMMON_COMPILE_OPTIONS} +) + +target_link_libraries(ot-test-trickle-timer + PRIVATE + ${COMMON_LIBS} +) + +add_test(NAME ot-test-trickle-timer COMMAND ot-test-trickle-timer) + add_executable(ot-test-tlv test_tlv.cpp ) diff --git a/tests/unit/test_trickle_timer.cpp b/tests/unit/test_trickle_timer.cpp new file mode 100644 index 000000000..f5851f261 --- /dev/null +++ b/tests/unit/test_trickle_timer.cpp @@ -0,0 +1,516 @@ +/* + * 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. + */ + +#include "test_platform.h" + +#include "common/code_utils.hpp" +#include "common/debug.hpp" +#include "common/instance.hpp" +#include "common/num_utils.hpp" +#include "common/trickle_timer.hpp" + +static ot::Instance *sInstance; + +static uint32_t sNow = 0; +static uint32_t sAlarmTime; +static bool sAlarmOn = false; + +extern "C" { + +void otPlatAlarmMilliStop(otInstance *) { sAlarmOn = false; } + +void otPlatAlarmMilliStartAt(otInstance *, uint32_t aT0, uint32_t aDt) +{ + sAlarmOn = true; + sAlarmTime = aT0 + aDt; +} + +uint32_t otPlatAlarmMilliGetNow(void) { return sNow; } + +} // extern "C" + +namespace ot { + +void AdvanceTime(uint32_t aDuration) +{ + uint32_t time = sNow + aDuration; + + while (TimeMilli(sAlarmTime) <= TimeMilli(time)) + { + sNow = sAlarmTime; + otPlatAlarmMilliFired(sInstance); + } + + sNow = time; +} + +class TrickleTimerTester : public TrickleTimer +{ +public: + explicit TrickleTimerTester(Instance &aInstance) + : TrickleTimer(aInstance, HandleTimerFired) + , mDidFire(false) + { + } + + Time GetFireTime(void) const { return TimerMilli::GetFireTime(); } + uint32_t GetInterval(void) const { return TrickleTimer::mInterval; } + uint32_t GetTimeInInterval(void) const { return TrickleTimer::mTimeInInterval; } + + void VerifyTimerDidFire(void) + { + VerifyOrQuit(mDidFire); + mDidFire = false; + } + + void VerifyTimerDidNotFire(void) const { VerifyOrQuit(!mDidFire); } + + static void RemoveAll(Instance &aInstance) { TimerMilli::RemoveAll(aInstance); } + +private: + static void HandleTimerFired(TrickleTimer &aTimer) { static_cast(aTimer).HandleTimerFired(); } + void HandleTimerFired(void) { mDidFire = true; } + + bool mDidFire; +}; + +void AlarmFired(otInstance *aInstance) { otPlatAlarmMilliFired(aInstance); } + +void TestTrickleTimerPlainMode(void) +{ + static constexpr uint32_t kMinInterval = 2000; + static constexpr uint32_t kMaxInterval = 5000; + + Instance *instance = testInitInstance(); + TrickleTimerTester timer(*instance); + uint32_t interval; + + sInstance = instance; + TrickleTimerTester::RemoveAll(*instance); + + printf("TestTrickleTimerPlainMode() "); + + // Validate that timer picks a random interval between min and max + // on start. + + sNow = 1000; + timer.Start(TrickleTimer::kModePlainTimer, kMinInterval, kMaxInterval, 0); + + VerifyOrQuit(timer.IsRunning()); + VerifyOrQuit(timer.GetIntervalMax() == kMaxInterval); + VerifyOrQuit(timer.GetIntervalMin() == kMinInterval); + + interval = timer.GetInterval(); + VerifyOrQuit((interval >= kMinInterval) && (interval <= kMaxInterval)); + + for (uint8_t iter = 0; iter <= 10; iter++) + { + AdvanceTime(interval); + + timer.VerifyTimerDidFire(); + + // The plain mode trickle timer restarts with a new random + // interval between min and max. + + VerifyOrQuit(timer.IsRunning()); + interval = timer.GetInterval(); + VerifyOrQuit((interval >= kMinInterval) && (interval <= kMaxInterval)); + } + + printf(" --> PASSED\n"); + + testFreeInstance(instance); +} + +void TestTrickleTimerTrickleMode(uint32_t aRedundancyConstant, uint32_t aConsistentCalls) +{ + static constexpr uint32_t kMinInterval = 1000; + static constexpr uint32_t kMaxInterval = 9000; + + Instance *instance = testInitInstance(); + TrickleTimerTester timer(*instance); + uint32_t interval; + uint32_t t; + + sInstance = instance; + TrickleTimerTester::RemoveAll(*instance); + + printf("TestTrickleTimerTrickleMode(aRedundancyConstant:%u, aConsistentCalls:%u) ", aRedundancyConstant, + aConsistentCalls); + + sNow = 1000; + timer.Start(TrickleTimer::kModeTrickle, kMinInterval, kMaxInterval, aRedundancyConstant); + + // Validate that trickle timer starts with random interval between + // min/max. + + VerifyOrQuit(timer.IsRunning()); + VerifyOrQuit(timer.GetIntervalMax() == kMaxInterval); + VerifyOrQuit(timer.GetIntervalMin() == kMinInterval); + + interval = timer.GetInterval(); + VerifyOrQuit((kMinInterval <= interval) && (interval <= kMaxInterval)); + t = timer.GetTimeInInterval(); + VerifyOrQuit((interval / 2 <= t) && (t <= interval)); + + // After `IndicateInconsistent()` should go back to min + // interval. + + timer.IndicateInconsistent(); + + VerifyOrQuit(timer.IsRunning()); + interval = timer.GetInterval(); + VerifyOrQuit(interval == kMinInterval); + t = timer.GetTimeInInterval(); + VerifyOrQuit((interval / 2 <= t) && (t <= interval)); + + for (uint8_t iter = 0; iter < 10; iter++) + { + for (uint32_t index = 0; index < aConsistentCalls; index++) + { + timer.IndicateConsistent(); + } + + AdvanceTime(t); + + if (aConsistentCalls < aRedundancyConstant) + { + timer.VerifyTimerDidFire(); + } + else + { + timer.VerifyTimerDidNotFire(); + } + + AdvanceTime(interval - t); + + // Verify that interval is doubling each time up + // to max interval. + + VerifyOrQuit(timer.IsRunning()); + VerifyOrQuit(timer.GetInterval() == Min(interval * 2, kMaxInterval)); + + interval = timer.GetInterval(); + t = timer.GetTimeInInterval(); + VerifyOrQuit((interval / 2 <= t) && (t <= interval)); + } + + AdvanceTime(t); + + timer.IndicateInconsistent(); + + VerifyOrQuit(timer.IsRunning()); + interval = timer.GetInterval(); + VerifyOrQuit(interval == kMinInterval); + + printf(" --> PASSED\n"); + + testFreeInstance(instance); +} + +void TestTrickleTimerMinMaxIntervalChange(void) +{ + Instance *instance = testInitInstance(); + TrickleTimerTester timer(*instance); + TimeMilli fireTime; + uint32_t interval; + uint32_t t; + + sInstance = instance; + TrickleTimerTester::RemoveAll(*instance); + + printf("TestTrickleTimerMinMaxIntervalChange()"); + + sNow = 1000; + timer.Start(TrickleTimer::kModeTrickle, 2000, 4000); + + VerifyOrQuit(timer.IsRunning()); + VerifyOrQuit(timer.GetIntervalMin() == 2000); + VerifyOrQuit(timer.GetIntervalMax() == 4000); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Validate that `SetIntervalMin()` to a larger value than + // previously set does not impact the current interval. + + timer.IndicateInconsistent(); + interval = timer.GetInterval(); + t = timer.GetTimeInInterval(); + fireTime = timer.GetFireTime(); + + VerifyOrQuit(interval == 2000); + VerifyOrQuit((interval / 2 <= t) && (t < interval)); + + // Change `IntervalMin` before time `t`. + + timer.SetIntervalMin(3000); + + VerifyOrQuit(timer.IsRunning()); + VerifyOrQuit(timer.GetIntervalMin() == 3000); + VerifyOrQuit(timer.GetIntervalMax() == 4000); + + VerifyOrQuit(interval == timer.GetInterval()); + VerifyOrQuit(t == timer.GetTimeInInterval()); + VerifyOrQuit(fireTime == timer.GetFireTime()); + + AdvanceTime(t); + timer.VerifyTimerDidFire(); + fireTime = timer.GetFireTime(); + + // Change `IntervalMin` after time `t`. + + timer.SetIntervalMin(3500); + + VerifyOrQuit(timer.IsRunning()); + VerifyOrQuit(timer.GetIntervalMin() == 3500); + VerifyOrQuit(timer.GetIntervalMax() == 4000); + + VerifyOrQuit(interval == timer.GetInterval()); + VerifyOrQuit(t == timer.GetTimeInInterval()); + VerifyOrQuit(fireTime == timer.GetFireTime()); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Validate that `SetIntervalMin()` to a smaller value + // also does not impact the current interval. + + timer.IndicateInconsistent(); + + interval = timer.GetInterval(); + t = timer.GetTimeInInterval(); + fireTime = timer.GetFireTime(); + + VerifyOrQuit(interval == 3500); + VerifyOrQuit((interval / 2 <= t) && (t < interval)); + + // Change `IntervalMin` before time `t`. + + timer.SetIntervalMin(3000); + + VerifyOrQuit(timer.IsRunning()); + VerifyOrQuit(timer.GetIntervalMin() == 3000); + VerifyOrQuit(timer.GetIntervalMax() == 4000); + + VerifyOrQuit(interval == timer.GetInterval()); + VerifyOrQuit(t == timer.GetTimeInInterval()); + VerifyOrQuit(fireTime == timer.GetFireTime()); + + AdvanceTime(t); + timer.VerifyTimerDidFire(); + fireTime = timer.GetFireTime(); + + // Change `IntervalMin` after time `t`. + + timer.SetIntervalMin(2000); + + VerifyOrQuit(timer.IsRunning()); + VerifyOrQuit(timer.GetIntervalMin() == 2000); + VerifyOrQuit(timer.GetIntervalMax() == 4000); + + VerifyOrQuit(interval == timer.GetInterval()); + VerifyOrQuit(t == timer.GetTimeInInterval()); + VerifyOrQuit(fireTime == timer.GetFireTime()); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Validate that changing `IntervalMax` to a larger value + // than the current interval being used by timer, does not + // impact the current internal. + + timer.IndicateInconsistent(); + + interval = timer.GetInterval(); + t = timer.GetTimeInInterval(); + fireTime = timer.GetFireTime(); + + VerifyOrQuit(interval == 2000); + VerifyOrQuit((interval / 2 <= t) && (t < interval)); + + // Change `IntervalMax` before time `t`. + + timer.SetIntervalMax(2500); + + VerifyOrQuit(timer.GetIntervalMax() == 2500); + VerifyOrQuit(timer.IsRunning()); + + VerifyOrQuit(interval == timer.GetInterval()); + VerifyOrQuit(t == timer.GetTimeInInterval()); + VerifyOrQuit(fireTime == timer.GetFireTime()); + + AdvanceTime(t); + + timer.VerifyTimerDidFire(); + + fireTime = timer.GetFireTime(); + + // Change `IntervalMax` after time `t`. + + timer.SetIntervalMax(3000); + + VerifyOrQuit(interval == timer.GetInterval()); + VerifyOrQuit(t == timer.GetTimeInInterval()); + VerifyOrQuit(fireTime == timer.GetFireTime()); + + timer.Stop(); + VerifyOrQuit(!timer.IsRunning()); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Check behavior when the new `IntervalMax` is smaller + // than the current interval being used by timer. + + // New `Imax` is smaller than `t` and before now. + // + // |<---- interval --^-------------------------------->| + // |<---- t ---------^------------------>| | + // |<---- new Imax --^--->| | | + // | now | | | + + timer.Start(TrickleTimer::kModeTrickle, 2000, 2000); + interval = timer.GetInterval(); + t = timer.GetTimeInInterval(); + fireTime = timer.GetFireTime(); + + VerifyOrQuit(interval == 2000); + VerifyOrQuit((interval / 2 <= t) && (t < interval)); + timer.SetIntervalMin(500); + + AdvanceTime(100); + timer.VerifyTimerDidNotFire(); + + timer.SetIntervalMax(500); + + VerifyOrQuit(timer.GetInterval() == 500); + VerifyOrQuit(timer.GetTimeInInterval() == 500); + VerifyOrQuit(timer.GetFireTime() != fireTime); + timer.VerifyTimerDidNotFire(); + + AdvanceTime(400); + timer.VerifyTimerDidFire(); + + // New `Imax` is smaller than `t` and after now. + // + // |<---- interval --------------^-------------------->| + // |<---- t ---------------------^------>| | + // |<---- new Imax ------>| ^ | | + // | | now | | + + timer.Start(TrickleTimer::kModeTrickle, 2000, 2000); + interval = timer.GetInterval(); + t = timer.GetTimeInInterval(); + fireTime = timer.GetFireTime(); + + VerifyOrQuit(interval == 2000); + VerifyOrQuit((interval / 2 <= t) && (t < interval)); + timer.SetIntervalMin(500); + + AdvanceTime(800); + timer.VerifyTimerDidNotFire(); + + timer.SetIntervalMax(500); + + VerifyOrQuit(timer.GetInterval() == 500); + VerifyOrQuit(timer.GetTimeInInterval() == 500); + VerifyOrQuit(timer.GetFireTime() != fireTime); + timer.VerifyTimerDidNotFire(); + + AdvanceTime(0); + timer.VerifyTimerDidFire(); + + // New `Imax` is larger than `t` and before now. + // + // |<---- interval --------------------------------^-->| + // |<---- t ---------------------------->| ^ | + // |<---- new Imax --------------------------->| ^ | + // | | | now | + + timer.Start(TrickleTimer::kModeTrickle, 2000, 2000); + + interval = timer.GetInterval(); + t = timer.GetTimeInInterval(); + + VerifyOrQuit(interval == 2000); + VerifyOrQuit((interval / 2 <= t) && (t < interval)); + timer.SetIntervalMin(500); + + AdvanceTime(1999); + timer.VerifyTimerDidFire(); + + timer.SetIntervalMax(t + 1); + + VerifyOrQuit(timer.GetInterval() == t + 1); + fireTime = timer.GetFireTime(); + + // Check that new interval is started immediately. + AdvanceTime(0); + timer.VerifyTimerDidNotFire(); + VerifyOrQuit(fireTime != timer.GetFireTime()); + VerifyOrQuit(timer.GetInterval() == timer.GetIntervalMax()); + + // New `Imax` is larger than `t` and after now. + // + // |<---- interval -------------------------^--------->| + // |<---- t ---------------------------->| ^ | + // |<---- new Imax -------------------------^->| | + // | | now | | + + timer.Start(TrickleTimer::kModeTrickle, 2000, 2000); + + interval = timer.GetInterval(); + t = timer.GetTimeInInterval(); + + VerifyOrQuit(interval == 2000); + VerifyOrQuit((interval / 2 <= t) && (t < interval)); + timer.SetIntervalMin(500); + + AdvanceTime(t); + timer.VerifyTimerDidFire(); + + timer.SetIntervalMax(t + 1); + + VerifyOrQuit(timer.GetInterval() == t + 1); + fireTime = timer.GetFireTime(); + + AdvanceTime(1); + timer.VerifyTimerDidNotFire(); + VerifyOrQuit(fireTime != timer.GetFireTime()); + VerifyOrQuit(timer.GetInterval() == timer.GetIntervalMax()); + + printf(" --> PASSED\n"); + + testFreeInstance(instance); +} + +} // namespace ot + +int main(void) +{ + ot::TestTrickleTimerPlainMode(); + ot::TestTrickleTimerTrickleMode(/* aRedundancyConstant */ 5, /* aConsistentCalls */ 3); + ot::TestTrickleTimerTrickleMode(/* aRedundancyConstant */ 3, /* aConsistentCalls */ 3); + ot::TestTrickleTimerMinMaxIntervalChange(); + + printf("All tests passed\n"); + return 0; +}