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 includes definitions to support History Tracker module. 32 */ 33 34 #ifndef HISTORY_TRACKER_HPP_ 35 #define HISTORY_TRACKER_HPP_ 36 37 #include "openthread-core-config.h" 38 39 #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE 40 41 #include <openthread/history_tracker.h> 42 #include <openthread/platform/radio.h> 43 44 #include "common/as_core_type.hpp" 45 #include "common/clearable.hpp" 46 #include "common/locator.hpp" 47 #include "common/non_copyable.hpp" 48 #include "common/notifier.hpp" 49 #include "common/timer.hpp" 50 #include "net/netif.hpp" 51 #include "net/socket.hpp" 52 #include "thread/mesh_forwarder.hpp" 53 #include "thread/mle.hpp" 54 #include "thread/mle_types.hpp" 55 #include "thread/neighbor_table.hpp" 56 #include "thread/network_data.hpp" 57 #include "thread/router_table.hpp" 58 59 namespace ot { 60 namespace Utils { 61 62 #ifdef OPENTHREAD_CONFIG_HISTORY_TRACKER_NET_DATA 63 #error "OPENTHREAD_CONFIG_HISTORY_TRACKER_NET_DATA should not be defined directly." \ 64 "It is derived from other configs: on-mesh prefix and external route history list sizes" 65 #endif 66 67 #define OPENTHREAD_CONFIG_HISTORY_TRACKER_NET_DATA \ 68 ((OPENTHREAD_CONFIG_HISTORY_TRACKER_ON_MESH_PREFIX_LIST_SIZE > 0) || \ 69 (OPENTHREAD_CONFIG_HISTORY_TRACKER_EXTERNAL_ROUTE_LIST_SIZE > 0)) 70 71 /** 72 * Implements History Tracker. 73 */ 74 class HistoryTracker : public InstanceLocator, private NonCopyable 75 { 76 friend class ot::MeshForwarder; 77 friend class ot::Notifier; 78 friend class ot::Mle::Mle; 79 friend class ot::NeighborTable; 80 friend class ot::Ip6::Netif; 81 #if OPENTHREAD_FTD 82 friend class ot::RouterTable; 83 #endif 84 85 public: 86 /** 87 * This constant specifies the maximum age of entries which is 49 days (value in msec). 88 * 89 * Entries older than the max age will give this value as their age. 90 */ 91 static constexpr uint32_t kMaxAge = OT_HISTORY_TRACKER_MAX_AGE; 92 93 /** 94 * This constant specifies the recommend string size to represent an entry age 95 */ 96 static constexpr uint16_t kEntryAgeStringSize = OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE; 97 98 /** 99 * This constants specified no next hop. 100 * 101 * Used for `mNextHop` in `RouteInfo` structure. 102 */ 103 static constexpr uint8_t kNoNextHop = OT_HISTORY_TRACKER_NO_NEXT_HOP; 104 105 /** 106 * Represents an iterator to iterate through a history list. 107 */ 108 class Iterator : public otHistoryTrackerIterator 109 { 110 friend class HistoryTracker; 111 112 public: 113 /** 114 * Initializes an `Iterator` 115 * 116 * An iterator MUST be initialized before it is used. An iterator can be initialized again to start from 117 * the beginning of the list. 118 */ Init(void)119 void Init(void) { ResetEntryNumber(), SetInitTime(); } 120 121 private: GetEntryNumber(void) const122 uint16_t GetEntryNumber(void) const { return mData16; } ResetEntryNumber(void)123 void ResetEntryNumber(void) { mData16 = 0; } IncrementEntryNumber(void)124 void IncrementEntryNumber(void) { mData16++; } GetInitTime(void) const125 TimeMilli GetInitTime(void) const { return TimeMilli(mData32); } SetInitTime(void)126 void SetInitTime(void) { mData32 = TimerMilli::GetNow().GetValue(); } 127 }; 128 129 typedef otHistoryTrackerNetworkInfo NetworkInfo; ///< Thread network info. 130 typedef otHistoryTrackerUnicastAddressInfo UnicastAddressInfo; ///< Unicast IPv6 address info. 131 typedef otHistoryTrackerMulticastAddressInfo MulticastAddressInfo; ///< Multicast IPv6 address info. 132 typedef otHistoryTrackerMessageInfo MessageInfo; ///< RX/TX IPv6 message info. 133 typedef otHistoryTrackerNeighborInfo NeighborInfo; ///< Neighbor info. 134 typedef otHistoryTrackerRouterInfo RouterInfo; ///< Router info. 135 typedef otHistoryTrackerOnMeshPrefixInfo OnMeshPrefixInfo; ///< Network Data on mesh prefix info. 136 typedef otHistoryTrackerExternalRouteInfo ExternalRouteInfo; ///< Network Data external route info 137 138 /** 139 * Initializes the `HistoryTracker`. 140 * 141 * @param[in] aInstance A reference to the OpenThread instance. 142 */ 143 explicit HistoryTracker(Instance &aInstance); 144 145 /** 146 * Iterates over the entries in the network info history list. 147 * 148 * @param[in,out] aIterator An iterator. MUST be initialized. 149 * @param[out] aEntryAge A reference to a variable to output the entry's age. 150 * Age is provided as the duration (in milliseconds) from when entry was recorded to 151 * @p aIterator initialization time. It is set to `kMaxAge` for entries older than max 152 * age. 153 * 154 * @returns A pointer to `NetworkInfo` entry or `nullptr` if no more entries in the list. 155 */ IterateNetInfoHistory(Iterator & aIterator,uint32_t & aEntryAge) const156 const NetworkInfo *IterateNetInfoHistory(Iterator &aIterator, uint32_t &aEntryAge) const 157 { 158 return mNetInfoHistory.Iterate(aIterator, aEntryAge); 159 } 160 161 /** 162 * Iterates over the entries in the unicast address history list. 163 * 164 * @param[in,out] aIterator An iterator. MUST be initialized. 165 * @param[out] aEntryAge A reference to a variable to output the entry's age. 166 * Age is provided as the duration (in milliseconds) from when entry was recorded to 167 * @p aIterator initialization time. It is set to `kMaxAge` for entries older than max 168 * age. 169 * 170 * @returns A pointer to `UnicastAddress` entry or `nullptr` if no more entries in the list. 171 */ IterateUnicastAddressHistory(Iterator & aIterator,uint32_t & aEntryAge) const172 const UnicastAddressInfo *IterateUnicastAddressHistory(Iterator &aIterator, uint32_t &aEntryAge) const 173 { 174 return mUnicastAddressHistory.Iterate(aIterator, aEntryAge); 175 } 176 177 /** 178 * Iterates over the entries in the multicast address history list. 179 * 180 * @param[in,out] aIterator An iterator. MUST be initialized. 181 * @param[out] aEntryAge A reference to a variable to output the entry's age. 182 * Age is provided as the duration (in milliseconds) from when entry was recorded to 183 * @p aIterator initialization time. It is set to `kMaxAge` for entries older than max 184 * age. 185 * 186 * @returns A pointer to `MulticastAddress` entry or `nullptr` if no more entries in the list. 187 */ IterateMulticastAddressHistory(Iterator & aIterator,uint32_t & aEntryAge) const188 const MulticastAddressInfo *IterateMulticastAddressHistory(Iterator &aIterator, uint32_t &aEntryAge) const 189 { 190 return mMulticastAddressHistory.Iterate(aIterator, aEntryAge); 191 } 192 193 /** 194 * Iterates over the entries in the RX history list. 195 * 196 * @param[in,out] aIterator An iterator. MUST be initialized. 197 * @param[out] aEntryAge A reference to a variable to output the entry's age. 198 * Age is provided as the duration (in milliseconds) from when entry was recorded to 199 * @p aIterator initialization time. It is set to `kMaxAge` for entries older than max 200 * age. 201 * 202 * @returns A pointer to `MessageInfo` entry or `nullptr` if no more entries in the list. 203 */ IterateRxHistory(Iterator & aIterator,uint32_t & aEntryAge) const204 const MessageInfo *IterateRxHistory(Iterator &aIterator, uint32_t &aEntryAge) const 205 { 206 return mRxHistory.Iterate(aIterator, aEntryAge); 207 } 208 209 /** 210 * Iterates over the entries in the TX history list. 211 * 212 * @param[in,out] aIterator An iterator. MUST be initialized. 213 * @param[out] aEntryAge A reference to a variable to output the entry's age. 214 * Age is provided as the duration (in milliseconds) from when entry was recorded to 215 * @p aIterator initialization time. It is set to `kMaxAge` for entries older than max 216 * age. 217 * 218 * @returns A pointer to `MessageInfo` entry or `nullptr` if no more entries in the list. 219 */ IterateTxHistory(Iterator & aIterator,uint32_t & aEntryAge) const220 const MessageInfo *IterateTxHistory(Iterator &aIterator, uint32_t &aEntryAge) const 221 { 222 return mTxHistory.Iterate(aIterator, aEntryAge); 223 } 224 IterateNeighborHistory(Iterator & aIterator,uint32_t & aEntryAge) const225 const NeighborInfo *IterateNeighborHistory(Iterator &aIterator, uint32_t &aEntryAge) const 226 { 227 return mNeighborHistory.Iterate(aIterator, aEntryAge); 228 } 229 IterateRouterHistory(Iterator & aIterator,uint32_t & aEntryAge) const230 const RouterInfo *IterateRouterHistory(Iterator &aIterator, uint32_t &aEntryAge) const 231 { 232 return mRouterHistory.Iterate(aIterator, aEntryAge); 233 } 234 IterateOnMeshPrefixHistory(Iterator & aIterator,uint32_t & aEntryAge) const235 const OnMeshPrefixInfo *IterateOnMeshPrefixHistory(Iterator &aIterator, uint32_t &aEntryAge) const 236 { 237 return mOnMeshPrefixHistory.Iterate(aIterator, aEntryAge); 238 } 239 IterateExternalRouteHistory(Iterator & aIterator,uint32_t & aEntryAge) const240 const ExternalRouteInfo *IterateExternalRouteHistory(Iterator &aIterator, uint32_t &aEntryAge) const 241 { 242 return mExternalRouteHistory.Iterate(aIterator, aEntryAge); 243 } 244 245 /** 246 * Converts a given entry age to a human-readable string. 247 * 248 * The entry age string follows the format "<hh>:<mm>:<ss>.<mmmm>" for hours, minutes, seconds and millisecond 249 * (if shorter than one day) or "<dd> days <hh>:<mm>:<ss>.<mmmm>" (if longer than one day). 250 * 251 * If the resulting string does not fit in @p aBuffer (within its @p aSize characters), the string will be 252 * truncated but the outputted string is always null-terminated. 253 * 254 * @param[in] aEntryAge The entry age (duration in msec). 255 * @param[out] aBuffer A pointer to a char array to output the string (MUST NOT be NULL). 256 * @param[in] aSize The size of @p aBuffer (in bytes). Recommended to use `OT_IP6_ADDRESS_STRING_SIZE`. 257 */ 258 static void EntryAgeToString(uint32_t aEntryAge, char *aBuffer, uint16_t aSize); 259 260 private: 261 // `Timestamp` uses `uint32_t` value. `2^32` msec is 49 days, 17 262 // hours, 2 minutes and 47 seconds and 296 msec. We use 49 days 263 // as `kMaxAge` and check for aged entries every 16 hours. 264 265 static constexpr uint32_t kAgeCheckPeriod = 16 * Time::kOneHourInMsec; 266 267 static constexpr uint16_t kNetInfoListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_NET_INFO_LIST_SIZE; 268 static constexpr uint16_t kUnicastAddrListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_UNICAST_ADDRESS_LIST_SIZE; 269 static constexpr uint16_t kMulticastAddrListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_MULTICAST_ADDRESS_LIST_SIZE; 270 static constexpr uint16_t kRxListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_RX_LIST_SIZE; 271 static constexpr uint16_t kTxListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_TX_LIST_SIZE; 272 static constexpr uint16_t kNeighborListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_NEIGHBOR_LIST_SIZE; 273 static constexpr uint16_t kRouterListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_ROUTER_LIST_SIZE; 274 static constexpr uint16_t kOnMeshPrefixListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_ON_MESH_PREFIX_LIST_SIZE; 275 static constexpr uint16_t kExternalRouteListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_EXTERNAL_ROUTE_LIST_SIZE; 276 277 typedef otHistoryTrackerAddressEvent AddressEvent; 278 279 static constexpr AddressEvent kAddressAdded = OT_HISTORY_TRACKER_ADDRESS_EVENT_ADDED; 280 static constexpr AddressEvent kAddressRemoved = OT_HISTORY_TRACKER_ADDRESS_EVENT_REMOVED; 281 282 static constexpr uint16_t kInvalidRloc16 = Mle::kInvalidRloc16; 283 284 typedef otHistoryTrackerNeighborEvent NeighborEvent; 285 286 static constexpr NeighborEvent kNeighborAdded = OT_HISTORY_TRACKER_NEIGHBOR_EVENT_ADDED; 287 static constexpr NeighborEvent kNeighborRemoved = OT_HISTORY_TRACKER_NEIGHBOR_EVENT_REMOVED; 288 static constexpr NeighborEvent kNeighborChanged = OT_HISTORY_TRACKER_NEIGHBOR_EVENT_CHANGED; 289 static constexpr NeighborEvent kNeighborRestoring = OT_HISTORY_TRACKER_NEIGHBOR_EVENT_RESTORING; 290 291 typedef otHistoryTrackerRouterEvent RouterEvent; 292 293 static constexpr RouterEvent kRouterAdded = OT_HISTORY_TRACKER_ROUTER_EVENT_ADDED; 294 static constexpr RouterEvent kRouterRemoved = OT_HISTORY_TRACKER_ROUTER_EVENT_REMOVED; 295 static constexpr RouterEvent kRouterNextHopChanged = OT_HISTORY_TRACKER_ROUTER_EVENT_NEXT_HOP_CHANGED; 296 static constexpr RouterEvent kRouterCostChanged = OT_HISTORY_TRACKER_ROUTER_EVENT_COST_CHANGED; 297 298 typedef otHistoryTrackerNetDataEvent NetDataEvent; 299 300 static constexpr NetDataEvent kNetDataEntryAdded = OT_HISTORY_TRACKER_NET_DATA_ENTRY_ADDED; 301 static constexpr NetDataEvent kNetDataEntryRemoved = OT_HISTORY_TRACKER_NET_DATA_ENTRY_REMOVED; 302 303 class Timestamp 304 { 305 public: 306 void SetToNow(void); 307 uint32_t GetDurationTill(TimeMilli aTime) const; IsDistantPast(void) const308 bool IsDistantPast(void) const { return (mTime.GetValue() == kDistantPast); } MarkAsDistantPast(void)309 void MarkAsDistantPast(void) { return mTime.SetValue(kDistantPast); } 310 311 private: 312 static constexpr uint32_t kDistantPast = 0; 313 314 TimeMilli mTime; 315 }; 316 317 // An ordered list of timestamped items (base class of `EntryList<Entry, kSize>`). 318 class List : private NonCopyable 319 { 320 public: 321 void Clear(void); GetSize(void) const322 uint16_t GetSize(void) const { return mSize; } 323 324 protected: 325 List(void); 326 uint16_t Add(uint16_t aMaxSize, Timestamp aTimestamps[]); 327 void UpdateAgedEntries(uint16_t aMaxSize, Timestamp aTimestamps[]); 328 uint16_t MapEntryNumberToListIndex(uint16_t aEntryNumber, uint16_t aMaxSize) const; 329 Error Iterate(uint16_t aMaxSize, 330 const Timestamp aTimestamps[], 331 Iterator &aIterator, 332 uint16_t &aListIndex, 333 uint32_t &aEntryAge) const; 334 335 private: 336 uint16_t mStartIndex; 337 uint16_t mSize; 338 }; 339 340 // A history list (with given max size) of timestamped `Entry` items. 341 template <typename Entry, uint16_t kMaxSize> class EntryList : public List 342 { 343 public: 344 // Adds a new entry to the list or overwrites the oldest entry 345 // if list is full. First version returns a pointer to the 346 // new `Entry` (for caller to populate). Second version copies 347 // the given `aEntry`. AddNewEntry(void)348 Entry *AddNewEntry(void) { return &mEntries[Add(kMaxSize, mTimestamps)]; } AddNewEntry(const Entry & aEntry)349 void AddNewEntry(const Entry &aEntry) { mEntries[Add(kMaxSize, mTimestamps)] = aEntry; } 350 UpdateAgedEntries(void)351 void UpdateAgedEntries(void) { List::UpdateAgedEntries(kMaxSize, mTimestamps); } 352 Iterate(Iterator & aIterator,uint32_t & aEntryAge) const353 const Entry *Iterate(Iterator &aIterator, uint32_t &aEntryAge) const 354 { 355 uint16_t index; 356 357 return (List::Iterate(kMaxSize, mTimestamps, aIterator, index, aEntryAge) == kErrorNone) ? &mEntries[index] 358 : nullptr; 359 } 360 361 private: 362 Timestamp mTimestamps[kMaxSize]; 363 Entry mEntries[kMaxSize]; 364 }; 365 366 // Partial specialization for `kMaxSize` zero. 367 template <typename Entry> class EntryList<Entry, 0> : private NonCopyable 368 { 369 public: Clear(void)370 void Clear(void) {} GetSize(void) const371 uint16_t GetSize(void) const { return 0; } AddNewEntry(void)372 Entry *AddNewEntry(void) { return nullptr; } AddNewEntry(const Entry &)373 void AddNewEntry(const Entry &) {} Iterate(Iterator &,uint32_t &) const374 const Entry *Iterate(Iterator &, uint32_t &) const { return nullptr; } RemoveAgedEntries(void)375 void RemoveAgedEntries(void) {} 376 }; 377 378 enum MessageType : uint8_t 379 { 380 kRxMessage, 381 kTxMessage, 382 }; 383 RecordRxMessage(const Message & aMessage,const Mac::Address & aMacSource)384 void RecordRxMessage(const Message &aMessage, const Mac::Address &aMacSource) 385 { 386 RecordMessage(aMessage, aMacSource, kRxMessage); 387 } 388 RecordTxMessage(const Message & aMessage,const Mac::Address & aMacDest)389 void RecordTxMessage(const Message &aMessage, const Mac::Address &aMacDest) 390 { 391 RecordMessage(aMessage, aMacDest, kTxMessage); 392 } 393 394 void RecordNetworkInfo(void); 395 void RecordMessage(const Message &aMessage, const Mac::Address &aMacAddress, MessageType aType); 396 void RecordNeighborEvent(NeighborTable::Event aEvent, const NeighborTable::EntryInfo &aInfo); 397 void RecordAddressEvent(Ip6::Netif::AddressEvent aEvent, const Ip6::Netif::UnicastAddress &aUnicastAddress); 398 void RecordAddressEvent(Ip6::Netif::AddressEvent aEvent, 399 const Ip6::Netif::MulticastAddress &aMulticastAddress, 400 Ip6::Netif::AddressOrigin aAddressOrigin); 401 void HandleNotifierEvents(Events aEvents); 402 void HandleTimer(void); 403 #if OPENTHREAD_FTD 404 void RecordRouterTableChange(void); 405 #endif 406 #if OPENTHREAD_CONFIG_HISTORY_TRACKER_NET_DATA 407 void RecordNetworkDataChange(void); 408 void RecordOnMeshPrefixEvent(NetDataEvent aEvent, const NetworkData::OnMeshPrefixConfig &aPrefix); 409 void RecordExternalRouteEvent(NetDataEvent aEvent, const NetworkData::ExternalRouteConfig &aRoute); 410 #endif 411 412 using TrackerTimer = TimerMilliIn<HistoryTracker, &HistoryTracker::HandleTimer>; 413 414 EntryList<NetworkInfo, kNetInfoListSize> mNetInfoHistory; 415 EntryList<UnicastAddressInfo, kUnicastAddrListSize> mUnicastAddressHistory; 416 EntryList<MulticastAddressInfo, kMulticastAddrListSize> mMulticastAddressHistory; 417 EntryList<MessageInfo, kRxListSize> mRxHistory; 418 EntryList<MessageInfo, kTxListSize> mTxHistory; 419 EntryList<NeighborInfo, kNeighborListSize> mNeighborHistory; 420 EntryList<RouterInfo, kRouterListSize> mRouterHistory; 421 EntryList<OnMeshPrefixInfo, kOnMeshPrefixListSize> mOnMeshPrefixHistory; 422 EntryList<ExternalRouteInfo, kExternalRouteListSize> mExternalRouteHistory; 423 424 TrackerTimer mTimer; 425 426 #if OPENTHREAD_FTD && (OPENTHREAD_CONFIG_HISTORY_TRACKER_ROUTER_LIST_SIZE > 0) 427 struct RouterEntry 428 { 429 bool mIsAllocated : 1; 430 uint8_t mNextHop : 6; 431 uint8_t mPathCost : 4; 432 }; 433 434 RouterEntry mRouterEntries[Mle::kMaxRouterId + 1]; 435 #endif 436 437 #if OPENTHREAD_CONFIG_HISTORY_TRACKER_NET_DATA 438 NetworkData::MutableNetworkData mPreviousNetworkData; 439 440 uint8_t mNetworkDataTlvBuffer[NetworkData::NetworkData::kMaxSize]; 441 #endif 442 }; 443 444 } // namespace Utils 445 446 DefineCoreType(otHistoryTrackerIterator, Utils::HistoryTracker::Iterator); 447 DefineCoreType(otHistoryTrackerNetworkInfo, Utils::HistoryTracker::NetworkInfo); 448 DefineCoreType(otHistoryTrackerMessageInfo, Utils::HistoryTracker::MessageInfo); 449 DefineCoreType(otHistoryTrackerNeighborInfo, Utils::HistoryTracker::NeighborInfo); 450 DefineCoreType(otHistoryTrackerRouterInfo, Utils::HistoryTracker::RouterInfo); 451 DefineCoreType(otHistoryTrackerOnMeshPrefixInfo, Utils::HistoryTracker::OnMeshPrefixInfo); 452 DefineCoreType(otHistoryTrackerExternalRouteInfo, Utils::HistoryTracker::ExternalRouteInfo); 453 454 } // namespace ot 455 456 #endif // OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE 457 458 #endif // HISTORY_TRACKER_HPP_ 459