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