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/code_utils.hpp"
39 #include "common/debug.hpp"
40 #include "common/instance.hpp"
41 #include "common/locator_getters.hpp"
42 #include "common/string.hpp"
43 #include "common/timer.hpp"
44 
45 namespace ot {
46 namespace Utils {
47 
48 //---------------------------------------------------------------------------------------------------------------------
49 // HistoryTracker
50 
HistoryTracker(Instance & aInstance)51 HistoryTracker::HistoryTracker(Instance &aInstance)
52     : InstanceLocator(aInstance)
53     , mTimer(aInstance, HandleTimer)
54 {
55     mTimer.Start(kAgeCheckPeriod);
56 }
57 
RecordNetworkInfo(void)58 void HistoryTracker::RecordNetworkInfo(void)
59 {
60     NetworkInfo *   entry = mNetInfoHistory.AddNewEntry();
61     Mle::DeviceMode mode;
62 
63     VerifyOrExit(entry != nullptr);
64 
65     entry->mRole        = static_cast<otDeviceRole>(Get<Mle::Mle>().GetRole());
66     entry->mRloc16      = Get<Mle::Mle>().GetRloc16();
67     entry->mPartitionId = Get<Mle::Mle>().GetLeaderData().GetPartitionId();
68     mode                = Get<Mle::Mle>().GetDeviceMode();
69     mode.Get(entry->mMode);
70 
71 exit:
72     return;
73 }
74 
RecordMessage(const Message & aMessage,const Mac::Address & aMacAddresss,MessageType aType)75 void HistoryTracker::RecordMessage(const Message &aMessage, const Mac::Address &aMacAddresss, MessageType aType)
76 {
77     MessageInfo *     entry = nullptr;
78     Ip6::Header       ip6Header;
79     Ip6::Icmp::Header icmp6Header;
80     uint8_t           ip6Proto;
81     uint16_t          checksum;
82     uint16_t          sourcePort;
83     uint16_t          destPort;
84 
85     VerifyOrExit(aMessage.GetType() == Message::kTypeIp6);
86 
87     SuccessOrExit(MeshForwarder::ParseIp6UdpTcpHeader(aMessage, ip6Header, checksum, sourcePort, destPort));
88 
89     ip6Proto = ip6Header.GetNextHeader();
90 
91 #if OPENTHREAD_CONFIG_HISTORY_TRACKER_EXCLUDE_THREAD_CONTROL_MESSAGES
92     if (ip6Proto == Ip6::kProtoUdp)
93     {
94         uint16_t port = 0;
95 
96         switch (aType)
97         {
98         case kRxMessage:
99             port = destPort;
100             break;
101 
102         case kTxMessage:
103             port = sourcePort;
104             break;
105         }
106 
107         VerifyOrExit((port != Mle::kUdpPort) && (port != Tmf::kUdpPort));
108     }
109 #endif
110 
111     if (ip6Proto == Ip6::kProtoIcmp6)
112     {
113         SuccessOrExit(aMessage.Read(sizeof(Ip6::Header), icmp6Header));
114         checksum = icmp6Header.GetChecksum();
115     }
116     else
117     {
118         icmp6Header.Clear();
119     }
120 
121     switch (aType)
122     {
123     case kRxMessage:
124         entry = mRxHistory.AddNewEntry();
125         break;
126 
127     case kTxMessage:
128         entry = mTxHistory.AddNewEntry();
129         break;
130     }
131 
132     VerifyOrExit(entry != nullptr);
133 
134     entry->mPayloadLength        = ip6Header.GetPayloadLength();
135     entry->mNeighborRloc16       = aMacAddresss.IsShort() ? aMacAddresss.GetShort() : kInvalidRloc16;
136     entry->mSource.mAddress      = ip6Header.GetSource();
137     entry->mSource.mPort         = sourcePort;
138     entry->mDestination.mAddress = ip6Header.GetDestination();
139     entry->mDestination.mPort    = destPort;
140     entry->mChecksum             = checksum;
141     entry->mIpProto              = ip6Proto;
142     entry->mIcmp6Type            = icmp6Header.GetType();
143     entry->mAveRxRss             = (aType == kRxMessage) ? aMessage.GetRssAverager().GetAverage() : kInvalidRss;
144     entry->mLinkSecurity         = aMessage.IsLinkSecurityEnabled();
145     entry->mTxSuccess            = (aType == kTxMessage) ? aMessage.GetTxSuccess() : true;
146     entry->mPriority             = aMessage.GetPriority();
147 
148     if (aMacAddresss.IsExtended())
149     {
150         Neighbor *neighbor = Get<NeighborTable>().FindNeighbor(aMacAddresss, Neighbor::kInStateAnyExceptInvalid);
151 
152         if (neighbor != nullptr)
153         {
154             entry->mNeighborRloc16 = neighbor->GetRloc16();
155         }
156     }
157 
158 #if OPENTHREAD_CONFIG_MULTI_RADIO
159     if (aMessage.IsRadioTypeSet())
160     {
161         switch (aMessage.GetRadioType())
162         {
163 #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
164         case Mac::kRadioTypeIeee802154:
165             entry->mRadioIeee802154 = true;
166             break;
167 #endif
168 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
169         case Mac::kRadioTypeTrel:
170             entry->mRadioTrelUdp6 = true;
171             break;
172 #endif
173         }
174 
175         // Radio type may not be set on a tx message indicating that it
176         // was sent over all radio types (e.g., for broadcast frame).
177         // In such a case, we set all supported radios from `else`
178         // block below.
179     }
180     else
181 #endif // OPENTHREAD_CONFIG_MULTI_RADIO
182     {
183 #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
184         entry->mRadioIeee802154 = true;
185 #endif
186 
187 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
188         entry->mRadioTrelUdp6 = true;
189 #endif
190     }
191 
192 exit:
193     return;
194 }
195 
RecordNeighborEvent(NeighborTable::Event aEvent,const NeighborTable::EntryInfo & aInfo)196 void HistoryTracker::RecordNeighborEvent(NeighborTable::Event aEvent, const NeighborTable::EntryInfo &aInfo)
197 {
198     NeighborInfo *entry = mNeighborHistory.AddNewEntry();
199 
200     VerifyOrExit(entry != nullptr);
201 
202     switch (aEvent)
203     {
204     case NeighborTable::kChildAdded:
205     case NeighborTable::kChildRemoved:
206     case NeighborTable::kChildModeChanged:
207         entry->mExtAddress       = aInfo.mInfo.mChild.mExtAddress;
208         entry->mRloc16           = aInfo.mInfo.mChild.mRloc16;
209         entry->mAverageRssi      = aInfo.mInfo.mChild.mAverageRssi;
210         entry->mRxOnWhenIdle     = aInfo.mInfo.mChild.mRxOnWhenIdle;
211         entry->mFullThreadDevice = aInfo.mInfo.mChild.mFullThreadDevice;
212         entry->mFullNetworkData  = aInfo.mInfo.mChild.mFullNetworkData;
213         entry->mIsChild          = true;
214         break;
215 
216     case NeighborTable::kRouterAdded:
217     case NeighborTable::kRouterRemoved:
218         entry->mExtAddress       = aInfo.mInfo.mRouter.mExtAddress;
219         entry->mRloc16           = aInfo.mInfo.mRouter.mRloc16;
220         entry->mAverageRssi      = aInfo.mInfo.mRouter.mAverageRssi;
221         entry->mRxOnWhenIdle     = aInfo.mInfo.mRouter.mRxOnWhenIdle;
222         entry->mFullThreadDevice = aInfo.mInfo.mRouter.mFullThreadDevice;
223         entry->mFullNetworkData  = aInfo.mInfo.mRouter.mFullNetworkData;
224         entry->mIsChild          = false;
225         break;
226     }
227 
228     switch (aEvent)
229     {
230     case NeighborTable::kChildAdded:
231         if (aInfo.mInfo.mChild.mIsStateRestoring)
232         {
233             entry->mEvent = kNeighborRestoring;
234             break;
235         }
236 
237         OT_FALL_THROUGH;
238 
239     case NeighborTable::kRouterAdded:
240         entry->mEvent = kNeighborAdded;
241         break;
242 
243     case NeighborTable::kChildRemoved:
244     case NeighborTable::kRouterRemoved:
245         entry->mEvent = kNeighborRemoved;
246         break;
247 
248     case NeighborTable::kChildModeChanged:
249         entry->mEvent = kNeighborChanged;
250         break;
251     }
252 
253 exit:
254     return;
255 }
256 
HandleNotifierEvents(Events aEvents)257 void HistoryTracker::HandleNotifierEvents(Events aEvents)
258 {
259     if (aEvents.ContainsAny(kEventThreadRoleChanged | kEventThreadRlocAdded | kEventThreadRlocRemoved |
260                             kEventThreadPartitionIdChanged))
261     {
262         RecordNetworkInfo();
263     }
264 }
265 
HandleTimer(Timer & aTimer)266 void HistoryTracker::HandleTimer(Timer &aTimer)
267 {
268     aTimer.Get<HistoryTracker>().HandleTimer();
269 }
270 
HandleTimer(void)271 void HistoryTracker::HandleTimer(void)
272 {
273     mNetInfoHistory.UpdateAgedEntries();
274     mRxHistory.UpdateAgedEntries();
275     mTxHistory.UpdateAgedEntries();
276     mNeighborHistory.UpdateAgedEntries();
277 
278     mTimer.Start(kAgeCheckPeriod);
279 }
280 
EntryAgeToString(uint32_t aEntryAge,char * aBuffer,uint16_t aSize)281 void HistoryTracker::EntryAgeToString(uint32_t aEntryAge, char *aBuffer, uint16_t aSize)
282 {
283     StringWriter writer(aBuffer, aSize);
284 
285     if (aEntryAge >= kMaxAge)
286     {
287         writer.Append("more than %u days", kMaxAge / kOneDayInMsec);
288     }
289     else
290     {
291         uint32_t days = aEntryAge / kOneDayInMsec;
292 
293         if (days > 0)
294         {
295             writer.Append("%u day%s ", days, (days == 1) ? "" : "s");
296             aEntryAge -= days * kOneDayInMsec;
297         }
298 
299         writer.Append("%02u:%02u:%02u.%03u", (aEntryAge / kOneHourInMsec),
300                       (aEntryAge % kOneDayInMsec) / kOneMinuteInMsec, (aEntryAge % kOneMinuteInMsec) / kOneSecondInMsec,
301                       (aEntryAge % kOneSecondInMsec));
302     }
303 }
304 
305 //---------------------------------------------------------------------------------------------------------------------
306 // HistoryTracker::Timestamp
307 
SetToNow(void)308 void HistoryTracker::Timestamp::SetToNow(void)
309 {
310     mTime = TimerMilli::GetNow();
311 
312     // If the current time happens to be the special value which we
313     // use to indicate "distant past", decrement the time by one.
314 
315     if (mTime.GetValue() == kDistantPast)
316     {
317         mTime.SetValue(mTime.GetValue() - 1);
318     }
319 }
320 
GetDurationTill(TimeMilli aTime) const321 uint32_t HistoryTracker::Timestamp::GetDurationTill(TimeMilli aTime) const
322 {
323     return IsDistantPast() ? kMaxAge : OT_MIN(aTime - mTime, kMaxAge);
324 }
325 
326 //---------------------------------------------------------------------------------------------------------------------
327 // HistoryTracker::List
328 
List(void)329 HistoryTracker::List::List(void)
330     : mStartIndex(0)
331     , mSize(0)
332 {
333 }
334 
Clear(void)335 void HistoryTracker::List::Clear(void)
336 {
337     mStartIndex = 0;
338     mSize       = 0;
339 }
340 
Add(uint16_t aMaxSize,Timestamp aTimestamps[])341 uint16_t HistoryTracker::List::Add(uint16_t aMaxSize, Timestamp aTimestamps[])
342 {
343     // Add a new entry and return its list index. Overwrites the
344     // oldest entry if list is full.
345     //
346     // Entries are saved in the order they are added such that
347     // `mStartIndex` is the newest entry and the entries after up
348     // to `mSize` are the previously added entries.
349 
350     mStartIndex = (mStartIndex == 0) ? aMaxSize - 1 : mStartIndex - 1;
351     mSize += (mSize == aMaxSize) ? 0 : 1;
352 
353     aTimestamps[mStartIndex].SetToNow();
354 
355     return mStartIndex;
356 }
357 
Iterate(uint16_t aMaxSize,const Timestamp aTimestamps[],Iterator & aIterator,uint16_t & aListIndex,uint32_t & aEntryAge) const358 Error HistoryTracker::List::Iterate(uint16_t        aMaxSize,
359                                     const Timestamp aTimestamps[],
360                                     Iterator &      aIterator,
361                                     uint16_t &      aListIndex,
362                                     uint32_t &      aEntryAge) const
363 {
364     Error error = kErrorNone;
365 
366     VerifyOrExit(aIterator.GetEntryNumber() < mSize, error = kErrorNotFound);
367 
368     aListIndex = MapEntryNumberToListIndex(aIterator.GetEntryNumber(), aMaxSize);
369     aEntryAge  = aTimestamps[aListIndex].GetDurationTill(aIterator.GetInitTime());
370 
371     aIterator.IncrementEntryNumber();
372 
373 exit:
374     return error;
375 }
376 
MapEntryNumberToListIndex(uint16_t aEntryNumber,uint16_t aMaxSize) const377 uint16_t HistoryTracker::List::MapEntryNumberToListIndex(uint16_t aEntryNumber, uint16_t aMaxSize) const
378 {
379     // Map the `aEntryNumber` to the list index. `aEntryNumber` value
380     // of zero corresponds to the newest (the most recently added)
381     // entry and value one to next one and so on. List index
382     // warps at the end of array to start of array. Caller MUST
383     // ensure `aEntryNumber` is smaller than `mSize`.
384 
385     uint32_t index;
386 
387     OT_ASSERT(aEntryNumber < mSize);
388 
389     index = static_cast<uint32_t>(aEntryNumber) + mStartIndex;
390     index -= (index >= aMaxSize) ? aMaxSize : 0;
391 
392     return static_cast<uint16_t>(index);
393 }
394 
UpdateAgedEntries(uint16_t aMaxSize,Timestamp aTimestamps[])395 void HistoryTracker::List::UpdateAgedEntries(uint16_t aMaxSize, Timestamp aTimestamps[])
396 {
397     TimeMilli now = TimerMilli::GetNow();
398 
399     // We go through the entries in reverse (starting with the oldest
400     // entry) and check if the entry's age is larger than `kMaxAge`
401     // and if so mark it as "distant past". We can stop as soon as we
402     // get to an entry with age smaller than max.
403     //
404     // The `for()` loop condition is `(entryNumber < mSize)` which
405     // ensures that we go through the loop body for `entryNumber`
406     // value of zero and then in the next iteration (when the
407     // `entryNumber` rolls over) we stop.
408 
409     for (uint16_t entryNumber = mSize - 1; entryNumber < mSize; entryNumber--)
410     {
411         uint16_t index = MapEntryNumberToListIndex(entryNumber, aMaxSize);
412 
413         if (aTimestamps[index].GetDurationTill(now) < kMaxAge)
414         {
415             break;
416         }
417 
418         aTimestamps[index].MarkAsDistantPast();
419     }
420 }
421 
422 } // namespace Utils
423 } // namespace ot
424 
425 #endif // #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
426