• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright (c) 2021, The OpenThread Authors.
3  *  All rights reserved.
4  *
5  *  Redistribution and use in source and binary forms, with or without
6  *  modification, are permitted provided that the following conditions are met:
7  *  1. Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  *  2. Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *  3. Neither the name of the copyright holder nor the
13  *     names of its contributors may be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *  POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 /**
30  * @file
31  *   This file implements the History Tracker module.
32  */
33 
34 #include "history_tracker.hpp"
35 
36 #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
37 
38 #include "instance/instance.hpp"
39 
40 namespace ot {
41 namespace Utils {
42 
43 //---------------------------------------------------------------------------------------------------------------------
44 // HistoryTracker
45 
HistoryTracker(Instance & aInstance)46 HistoryTracker::HistoryTracker(Instance &aInstance)
47     : InstanceLocator(aInstance)
48     , mTimer(aInstance)
49 #if OPENTHREAD_CONFIG_HISTORY_TRACKER_NET_DATA
50     , mPreviousNetworkData(aInstance, mNetworkDataTlvBuffer, 0, sizeof(mNetworkDataTlvBuffer))
51 #endif
52 {
53     mTimer.Start(kAgeCheckPeriod);
54 
55 #if OPENTHREAD_FTD && (OPENTHREAD_CONFIG_HISTORY_TRACKER_ROUTER_LIST_SIZE > 0)
56     ClearAllBytes(mRouterEntries);
57 #endif
58 }
59 
RecordNetworkInfo(void)60 void HistoryTracker::RecordNetworkInfo(void)
61 {
62     NetworkInfo    *entry = mNetInfoHistory.AddNewEntry();
63     Mle::DeviceMode mode;
64 
65     VerifyOrExit(entry != nullptr);
66 
67     entry->mRole        = MapEnum(Get<Mle::Mle>().GetRole());
68     entry->mRloc16      = Get<Mle::Mle>().GetRloc16();
69     entry->mPartitionId = Get<Mle::Mle>().GetLeaderData().GetPartitionId();
70     mode                = Get<Mle::Mle>().GetDeviceMode();
71     mode.Get(entry->mMode);
72 
73 exit:
74     return;
75 }
76 
RecordMessage(const Message & aMessage,const Mac::Address & aMacAddress,MessageType aType)77 void HistoryTracker::RecordMessage(const Message &aMessage, const Mac::Address &aMacAddress, MessageType aType)
78 {
79     MessageInfo *entry = nullptr;
80     Ip6::Headers headers;
81 
82     VerifyOrExit(aMessage.GetType() == Message::kTypeIp6);
83 
84     SuccessOrExit(headers.ParseFrom(aMessage));
85 
86 #if OPENTHREAD_CONFIG_HISTORY_TRACKER_EXCLUDE_THREAD_CONTROL_MESSAGES
87     if (headers.IsUdp())
88     {
89         uint16_t port = 0;
90 
91         switch (aType)
92         {
93         case kRxMessage:
94             port = headers.GetDestinationPort();
95             break;
96 
97         case kTxMessage:
98             port = headers.GetSourcePort();
99             break;
100         }
101 
102         VerifyOrExit((port != Mle::kUdpPort) && (port != Tmf::kUdpPort));
103     }
104 #endif
105 
106     switch (aType)
107     {
108     case kRxMessage:
109         entry = mRxHistory.AddNewEntry();
110         break;
111 
112     case kTxMessage:
113         entry = mTxHistory.AddNewEntry();
114         break;
115     }
116 
117     VerifyOrExit(entry != nullptr);
118 
119     entry->mPayloadLength        = headers.GetIp6Header().GetPayloadLength();
120     entry->mNeighborRloc16       = aMacAddress.IsShort() ? aMacAddress.GetShort() : kInvalidRloc16;
121     entry->mSource.mAddress      = headers.GetSourceAddress();
122     entry->mSource.mPort         = headers.GetSourcePort();
123     entry->mDestination.mAddress = headers.GetDestinationAddress();
124     entry->mDestination.mPort    = headers.GetDestinationPort();
125     entry->mChecksum             = headers.GetChecksum();
126     entry->mIpProto              = headers.GetIpProto();
127     entry->mIcmp6Type            = headers.IsIcmp6() ? headers.GetIcmpHeader().GetType() : 0;
128     entry->mAveRxRss             = (aType == kRxMessage) ? aMessage.GetRssAverager().GetAverage() : Radio::kInvalidRssi;
129     entry->mLinkSecurity         = aMessage.IsLinkSecurityEnabled();
130     entry->mTxSuccess            = (aType == kTxMessage) ? aMessage.GetTxSuccess() : true;
131     entry->mPriority             = aMessage.GetPriority();
132 
133     if (aMacAddress.IsExtended())
134     {
135         Neighbor *neighbor = Get<NeighborTable>().FindNeighbor(aMacAddress, Neighbor::kInStateAnyExceptInvalid);
136 
137         if (neighbor != nullptr)
138         {
139             entry->mNeighborRloc16 = neighbor->GetRloc16();
140         }
141     }
142 
143 #if OPENTHREAD_CONFIG_MULTI_RADIO
144     if (aMessage.IsRadioTypeSet())
145     {
146         switch (aMessage.GetRadioType())
147         {
148 #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
149         case Mac::kRadioTypeIeee802154:
150             entry->mRadioIeee802154 = true;
151             break;
152 #endif
153 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
154         case Mac::kRadioTypeTrel:
155             entry->mRadioTrelUdp6 = true;
156             break;
157 #endif
158         }
159 
160         // Radio type may not be set on a tx message indicating that it
161         // was sent over all radio types (e.g., for broadcast frame).
162         // In such a case, we set all supported radios from `else`
163         // block below.
164     }
165     else
166 #endif // OPENTHREAD_CONFIG_MULTI_RADIO
167     {
168 #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
169         entry->mRadioIeee802154 = true;
170 #endif
171 
172 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
173         entry->mRadioTrelUdp6 = true;
174 #endif
175     }
176 
177 exit:
178     return;
179 }
180 
RecordNeighborEvent(NeighborTable::Event aEvent,const NeighborTable::EntryInfo & aInfo)181 void HistoryTracker::RecordNeighborEvent(NeighborTable::Event aEvent, const NeighborTable::EntryInfo &aInfo)
182 {
183     NeighborInfo *entry = mNeighborHistory.AddNewEntry();
184 
185     VerifyOrExit(entry != nullptr);
186 
187     switch (aEvent)
188     {
189     case NeighborTable::kChildAdded:
190     case NeighborTable::kChildRemoved:
191     case NeighborTable::kChildModeChanged:
192         entry->mExtAddress       = aInfo.mInfo.mChild.mExtAddress;
193         entry->mRloc16           = aInfo.mInfo.mChild.mRloc16;
194         entry->mAverageRssi      = aInfo.mInfo.mChild.mAverageRssi;
195         entry->mRxOnWhenIdle     = aInfo.mInfo.mChild.mRxOnWhenIdle;
196         entry->mFullThreadDevice = aInfo.mInfo.mChild.mFullThreadDevice;
197         entry->mFullNetworkData  = aInfo.mInfo.mChild.mFullNetworkData;
198         entry->mIsChild          = true;
199         break;
200 
201     case NeighborTable::kRouterAdded:
202     case NeighborTable::kRouterRemoved:
203         entry->mExtAddress       = aInfo.mInfo.mRouter.mExtAddress;
204         entry->mRloc16           = aInfo.mInfo.mRouter.mRloc16;
205         entry->mAverageRssi      = aInfo.mInfo.mRouter.mAverageRssi;
206         entry->mRxOnWhenIdle     = aInfo.mInfo.mRouter.mRxOnWhenIdle;
207         entry->mFullThreadDevice = aInfo.mInfo.mRouter.mFullThreadDevice;
208         entry->mFullNetworkData  = aInfo.mInfo.mRouter.mFullNetworkData;
209         entry->mIsChild          = false;
210         break;
211     }
212 
213     switch (aEvent)
214     {
215     case NeighborTable::kChildAdded:
216         if (aInfo.mInfo.mChild.mIsStateRestoring)
217         {
218             entry->mEvent = kNeighborRestoring;
219             break;
220         }
221 
222         OT_FALL_THROUGH;
223 
224     case NeighborTable::kRouterAdded:
225         entry->mEvent = kNeighborAdded;
226         break;
227 
228     case NeighborTable::kChildRemoved:
229     case NeighborTable::kRouterRemoved:
230         entry->mEvent = kNeighborRemoved;
231         break;
232 
233     case NeighborTable::kChildModeChanged:
234         entry->mEvent = kNeighborChanged;
235         break;
236     }
237 
238 exit:
239     return;
240 }
241 
RecordAddressEvent(Ip6::Netif::AddressEvent aEvent,const Ip6::Netif::UnicastAddress & aUnicastAddress)242 void HistoryTracker::RecordAddressEvent(Ip6::Netif::AddressEvent          aEvent,
243                                         const Ip6::Netif::UnicastAddress &aUnicastAddress)
244 {
245     UnicastAddressInfo *entry = mUnicastAddressHistory.AddNewEntry();
246 
247     VerifyOrExit(entry != nullptr);
248 
249     entry->mAddress       = aUnicastAddress.GetAddress();
250     entry->mPrefixLength  = aUnicastAddress.GetPrefixLength();
251     entry->mAddressOrigin = aUnicastAddress.GetOrigin();
252     entry->mEvent         = (aEvent == Ip6::Netif::kAddressAdded) ? kAddressAdded : kAddressRemoved;
253     entry->mScope         = (aUnicastAddress.GetScope() & 0xf);
254     entry->mPreferred     = aUnicastAddress.mPreferred;
255     entry->mValid         = aUnicastAddress.mValid;
256     entry->mRloc          = aUnicastAddress.mRloc;
257 
258 exit:
259     return;
260 }
261 
RecordAddressEvent(Ip6::Netif::AddressEvent aEvent,const Ip6::Netif::MulticastAddress & aMulticastAddress,Ip6::Netif::AddressOrigin aAddressOrigin)262 void HistoryTracker::RecordAddressEvent(Ip6::Netif::AddressEvent            aEvent,
263                                         const Ip6::Netif::MulticastAddress &aMulticastAddress,
264                                         Ip6::Netif::AddressOrigin           aAddressOrigin)
265 {
266     MulticastAddressInfo *entry = mMulticastAddressHistory.AddNewEntry();
267 
268     VerifyOrExit(entry != nullptr);
269 
270     entry->mAddress       = aMulticastAddress.GetAddress();
271     entry->mAddressOrigin = aAddressOrigin;
272     entry->mEvent         = (aEvent == Ip6::Netif::kAddressAdded) ? kAddressAdded : kAddressRemoved;
273 
274 exit:
275     return;
276 }
277 
278 #if OPENTHREAD_FTD
RecordRouterTableChange(void)279 void HistoryTracker::RecordRouterTableChange(void)
280 {
281 #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ROUTER_LIST_SIZE > 0
282 
283     for (uint8_t routerId = 0; routerId <= Mle::kMaxRouterId; routerId++)
284     {
285         RouterInfo   entry;
286         RouterEntry &oldEntry = mRouterEntries[routerId];
287 
288         entry.mRouterId = routerId;
289 
290         if (Get<RouterTable>().IsAllocated(routerId))
291         {
292             uint16_t nextHopRloc;
293             uint8_t  pathCost;
294 
295             Get<RouterTable>().GetNextHopAndPathCost(Mle::Rloc16FromRouterId(routerId), nextHopRloc, pathCost);
296 
297             entry.mNextHop  = (nextHopRloc == Mle::kInvalidRloc16) ? kNoNextHop : Mle::RouterIdFromRloc16(nextHopRloc);
298             entry.mPathCost = (pathCost < Mle::kMaxRouteCost) ? pathCost : 0;
299 
300             if (!oldEntry.mIsAllocated)
301             {
302                 entry.mEvent       = kRouterAdded;
303                 entry.mOldPathCost = 0;
304             }
305             else if (oldEntry.mNextHop != entry.mNextHop)
306             {
307                 entry.mEvent       = kRouterNextHopChanged;
308                 entry.mOldPathCost = oldEntry.mPathCost;
309             }
310             else if ((entry.mNextHop != kNoNextHop) && (oldEntry.mPathCost != entry.mPathCost))
311             {
312                 entry.mEvent       = kRouterCostChanged;
313                 entry.mOldPathCost = oldEntry.mPathCost;
314             }
315             else
316             {
317                 continue;
318             }
319 
320             mRouterHistory.AddNewEntry(entry);
321 
322             oldEntry.mIsAllocated = true;
323             oldEntry.mNextHop     = entry.mNextHop;
324             oldEntry.mPathCost    = entry.mPathCost;
325         }
326         else
327         {
328             // `routerId` is not allocated.
329 
330             if (oldEntry.mIsAllocated)
331             {
332                 entry.mEvent       = kRouterRemoved;
333                 entry.mNextHop     = Mle::kInvalidRouterId;
334                 entry.mOldPathCost = 0;
335                 entry.mPathCost    = 0;
336 
337                 mRouterHistory.AddNewEntry(entry);
338 
339                 oldEntry.mIsAllocated = false;
340             }
341         }
342     }
343 
344 #endif // (OPENTHREAD_CONFIG_HISTORY_TRACKER_ROUTER_LIST_SIZE > 0)
345 }
346 #endif // OPENTHREAD_FTD
347 
348 #if OPENTHREAD_CONFIG_HISTORY_TRACKER_NET_DATA
RecordNetworkDataChange(void)349 void HistoryTracker::RecordNetworkDataChange(void)
350 {
351     NetworkData::Iterator            iterator;
352     NetworkData::OnMeshPrefixConfig  prefix;
353     NetworkData::ExternalRouteConfig route;
354 
355     // On mesh prefix entries
356 
357     iterator = NetworkData::kIteratorInit;
358 
359     while (mPreviousNetworkData.GetNextOnMeshPrefix(iterator, prefix) == kErrorNone)
360     {
361         if (!Get<NetworkData::Leader>().ContainsOnMeshPrefix(prefix))
362         {
363             RecordOnMeshPrefixEvent(kNetDataEntryRemoved, prefix);
364         }
365     }
366 
367     iterator = NetworkData::kIteratorInit;
368 
369     while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, prefix) == kErrorNone)
370     {
371         if (!mPreviousNetworkData.ContainsOnMeshPrefix(prefix))
372         {
373             RecordOnMeshPrefixEvent(kNetDataEntryAdded, prefix);
374         }
375     }
376 
377     // External route entries
378 
379     iterator = NetworkData::kIteratorInit;
380 
381     while (mPreviousNetworkData.GetNextExternalRoute(iterator, route) == kErrorNone)
382     {
383         if (!Get<NetworkData::Leader>().ContainsExternalRoute(route))
384         {
385             RecordExternalRouteEvent(kNetDataEntryRemoved, route);
386         }
387     }
388 
389     iterator = NetworkData::kIteratorInit;
390 
391     while (Get<NetworkData::Leader>().GetNextExternalRoute(iterator, route) == kErrorNone)
392     {
393         if (!mPreviousNetworkData.ContainsExternalRoute(route))
394         {
395             RecordExternalRouteEvent(kNetDataEntryAdded, route);
396         }
397     }
398 
399     SuccessOrAssert(Get<NetworkData::Leader>().CopyNetworkData(NetworkData::kFullSet, mPreviousNetworkData));
400 }
401 
RecordOnMeshPrefixEvent(NetDataEvent aEvent,const NetworkData::OnMeshPrefixConfig & aPrefix)402 void HistoryTracker::RecordOnMeshPrefixEvent(NetDataEvent aEvent, const NetworkData::OnMeshPrefixConfig &aPrefix)
403 {
404     OnMeshPrefixInfo *entry = mOnMeshPrefixHistory.AddNewEntry();
405 
406     VerifyOrExit(entry != nullptr);
407     entry->mPrefix = aPrefix;
408     entry->mEvent  = aEvent;
409 
410 exit:
411     return;
412 }
413 
RecordExternalRouteEvent(NetDataEvent aEvent,const NetworkData::ExternalRouteConfig & aRoute)414 void HistoryTracker::RecordExternalRouteEvent(NetDataEvent aEvent, const NetworkData::ExternalRouteConfig &aRoute)
415 {
416     ExternalRouteInfo *entry = mExternalRouteHistory.AddNewEntry();
417 
418     VerifyOrExit(entry != nullptr);
419     entry->mRoute = aRoute;
420     entry->mEvent = aEvent;
421 
422 exit:
423     return;
424 }
425 
426 #endif // OPENTHREAD_CONFIG_HISTORY_TRACKER_NET_DATA
427 
HandleNotifierEvents(Events aEvents)428 void HistoryTracker::HandleNotifierEvents(Events aEvents)
429 {
430     if (aEvents.ContainsAny(kEventThreadRoleChanged | kEventThreadRlocAdded | kEventThreadRlocRemoved |
431                             kEventThreadPartitionIdChanged))
432     {
433         RecordNetworkInfo();
434     }
435 
436 #if OPENTHREAD_CONFIG_HISTORY_TRACKER_NET_DATA
437     if (aEvents.Contains(kEventThreadNetdataChanged))
438     {
439         RecordNetworkDataChange();
440     }
441 #endif
442 }
443 
HandleTimer(void)444 void HistoryTracker::HandleTimer(void)
445 {
446     mNetInfoHistory.UpdateAgedEntries();
447     mUnicastAddressHistory.UpdateAgedEntries();
448     mMulticastAddressHistory.UpdateAgedEntries();
449     mRxHistory.UpdateAgedEntries();
450     mTxHistory.UpdateAgedEntries();
451     mNeighborHistory.UpdateAgedEntries();
452     mOnMeshPrefixHistory.UpdateAgedEntries();
453     mExternalRouteHistory.UpdateAgedEntries();
454 
455     mTimer.Start(kAgeCheckPeriod);
456 }
457 
EntryAgeToString(uint32_t aEntryAge,char * aBuffer,uint16_t aSize)458 void HistoryTracker::EntryAgeToString(uint32_t aEntryAge, char *aBuffer, uint16_t aSize)
459 {
460     StringWriter writer(aBuffer, aSize);
461 
462     if (aEntryAge >= kMaxAge)
463     {
464         writer.Append("more than %u days", static_cast<uint16_t>(kMaxAge / Time::kOneDayInMsec));
465     }
466     else
467     {
468         uint32_t days = aEntryAge / Time::kOneDayInMsec;
469 
470         if (days > 0)
471         {
472             writer.Append("%lu day%s ", ToUlong(days), (days == 1) ? "" : "s");
473             aEntryAge -= days * Time::kOneDayInMsec;
474         }
475 
476         writer.Append("%02u:%02u:%02u.%03u", static_cast<uint16_t>(aEntryAge / Time::kOneHourInMsec),
477                       static_cast<uint16_t>((aEntryAge % Time::kOneHourInMsec) / Time::kOneMinuteInMsec),
478                       static_cast<uint16_t>((aEntryAge % Time::kOneMinuteInMsec) / Time::kOneSecondInMsec),
479                       static_cast<uint16_t>(aEntryAge % Time::kOneSecondInMsec));
480     }
481 }
482 
483 //---------------------------------------------------------------------------------------------------------------------
484 // HistoryTracker::Timestamp
485 
SetToNow(void)486 void HistoryTracker::Timestamp::SetToNow(void)
487 {
488     mTime = TimerMilli::GetNow();
489 
490     // If the current time happens to be the special value which we
491     // use to indicate "distant past", decrement the time by one.
492 
493     if (mTime.GetValue() == kDistantPast)
494     {
495         mTime.SetValue(mTime.GetValue() - 1);
496     }
497 }
498 
GetDurationTill(TimeMilli aTime) const499 uint32_t HistoryTracker::Timestamp::GetDurationTill(TimeMilli aTime) const
500 {
501     return IsDistantPast() ? kMaxAge : Min(aTime - mTime, kMaxAge);
502 }
503 
504 //---------------------------------------------------------------------------------------------------------------------
505 // HistoryTracker::List
506 
List(void)507 HistoryTracker::List::List(void)
508     : mStartIndex(0)
509     , mSize(0)
510 {
511 }
512 
Clear(void)513 void HistoryTracker::List::Clear(void)
514 {
515     mStartIndex = 0;
516     mSize       = 0;
517 }
518 
Add(uint16_t aMaxSize,Timestamp aTimestamps[])519 uint16_t HistoryTracker::List::Add(uint16_t aMaxSize, Timestamp aTimestamps[])
520 {
521     // Add a new entry and return its list index. Overwrites the
522     // oldest entry if list is full.
523     //
524     // Entries are saved in the order they are added such that
525     // `mStartIndex` is the newest entry and the entries after up
526     // to `mSize` are the previously added entries.
527 
528     mStartIndex = (mStartIndex == 0) ? aMaxSize - 1 : mStartIndex - 1;
529     mSize += (mSize == aMaxSize) ? 0 : 1;
530 
531     aTimestamps[mStartIndex].SetToNow();
532 
533     return mStartIndex;
534 }
535 
Iterate(uint16_t aMaxSize,const Timestamp aTimestamps[],Iterator & aIterator,uint16_t & aListIndex,uint32_t & aEntryAge) const536 Error HistoryTracker::List::Iterate(uint16_t        aMaxSize,
537                                     const Timestamp aTimestamps[],
538                                     Iterator       &aIterator,
539                                     uint16_t       &aListIndex,
540                                     uint32_t       &aEntryAge) const
541 {
542     Error error = kErrorNone;
543 
544     VerifyOrExit(aIterator.GetEntryNumber() < mSize, error = kErrorNotFound);
545 
546     aListIndex = MapEntryNumberToListIndex(aIterator.GetEntryNumber(), aMaxSize);
547     aEntryAge  = aTimestamps[aListIndex].GetDurationTill(aIterator.GetInitTime());
548 
549     aIterator.IncrementEntryNumber();
550 
551 exit:
552     return error;
553 }
554 
MapEntryNumberToListIndex(uint16_t aEntryNumber,uint16_t aMaxSize) const555 uint16_t HistoryTracker::List::MapEntryNumberToListIndex(uint16_t aEntryNumber, uint16_t aMaxSize) const
556 {
557     // Map the `aEntryNumber` to the list index. `aEntryNumber` value
558     // of zero corresponds to the newest (the most recently added)
559     // entry and value one to next one and so on. List index
560     // warps at the end of array to start of array. Caller MUST
561     // ensure `aEntryNumber` is smaller than `mSize`.
562 
563     uint32_t index;
564 
565     OT_ASSERT(aEntryNumber < mSize);
566 
567     index = static_cast<uint32_t>(aEntryNumber) + mStartIndex;
568     index -= (index >= aMaxSize) ? aMaxSize : 0;
569 
570     return static_cast<uint16_t>(index);
571 }
572 
UpdateAgedEntries(uint16_t aMaxSize,Timestamp aTimestamps[])573 void HistoryTracker::List::UpdateAgedEntries(uint16_t aMaxSize, Timestamp aTimestamps[])
574 {
575     TimeMilli now = TimerMilli::GetNow();
576 
577     // We go through the entries in reverse (starting with the oldest
578     // entry) and check if the entry's age is larger than `kMaxAge`
579     // and if so mark it as "distant past". We can stop as soon as we
580     // get to an entry with age smaller than max.
581     //
582     // The `for()` loop condition is `(entryNumber < mSize)` which
583     // ensures that we go through the loop body for `entryNumber`
584     // value of zero and then in the next iteration (when the
585     // `entryNumber` rolls over) we stop.
586 
587     for (uint16_t entryNumber = mSize - 1; entryNumber < mSize; entryNumber--)
588     {
589         uint16_t index = MapEntryNumberToListIndex(entryNumber, aMaxSize);
590 
591         if (aTimestamps[index].GetDurationTill(now) < kMaxAge)
592         {
593             break;
594         }
595 
596         aTimestamps[index].MarkAsDistantPast();
597     }
598 }
599 
600 } // namespace Utils
601 } // namespace ot
602 
603 #endif // #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
604