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