1 /*
2 * Copyright (c) 2016-2017, 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 for maintaining Thread network topologies.
32 */
33
34 #include "topology.hpp"
35
36 #include "common/array.hpp"
37 #include "common/code_utils.hpp"
38 #include "common/debug.hpp"
39 #include "common/instance.hpp"
40 #include "common/locator_getters.hpp"
41 #include "common/num_utils.hpp"
42
43 namespace ot {
44
SetState(State aState)45 void Neighbor::SetState(State aState)
46 {
47 VerifyOrExit(mState != aState);
48 mState = static_cast<uint8_t>(aState);
49
50 #if OPENTHREAD_CONFIG_UPTIME_ENABLE
51 if (mState == kStateValid)
52 {
53 mConnectionStart = Uptime::MsecToSec(Get<Uptime>().GetUptime());
54 }
55 #endif
56
57 exit:
58 return;
59 }
60
61 #if OPENTHREAD_CONFIG_UPTIME_ENABLE
GetConnectionTime(void) const62 uint32_t Neighbor::GetConnectionTime(void) const
63 {
64 return IsStateValid() ? Uptime::MsecToSec(Get<Uptime>().GetUptime()) - mConnectionStart : 0;
65 }
66 #endif
67
Matches(const Neighbor & aNeighbor) const68 bool Neighbor::AddressMatcher::Matches(const Neighbor &aNeighbor) const
69 {
70 bool matches = false;
71
72 VerifyOrExit(aNeighbor.MatchesFilter(mStateFilter));
73
74 if (mShortAddress != Mac::kShortAddrInvalid)
75 {
76 VerifyOrExit(mShortAddress == aNeighbor.GetRloc16());
77 }
78
79 if (mExtAddress != nullptr)
80 {
81 VerifyOrExit(*mExtAddress == aNeighbor.GetExtAddress());
82 }
83
84 matches = true;
85
86 exit:
87 return matches;
88 }
89
SetFrom(const Neighbor & aNeighbor)90 void Neighbor::Info::SetFrom(const Neighbor &aNeighbor)
91 {
92 Clear();
93
94 mExtAddress = aNeighbor.GetExtAddress();
95 mAge = Time::MsecToSec(TimerMilli::GetNow() - aNeighbor.GetLastHeard());
96 mRloc16 = aNeighbor.GetRloc16();
97 mLinkFrameCounter = aNeighbor.GetLinkFrameCounters().GetMaximum();
98 mMleFrameCounter = aNeighbor.GetMleFrameCounter();
99 mLinkQualityIn = aNeighbor.GetLinkQualityIn();
100 mAverageRssi = aNeighbor.GetLinkInfo().GetAverageRss();
101 mLastRssi = aNeighbor.GetLinkInfo().GetLastRss();
102 mLinkMargin = aNeighbor.GetLinkInfo().GetLinkMargin();
103 mFrameErrorRate = aNeighbor.GetLinkInfo().GetFrameErrorRate();
104 mMessageErrorRate = aNeighbor.GetLinkInfo().GetMessageErrorRate();
105 mRxOnWhenIdle = aNeighbor.IsRxOnWhenIdle();
106 mFullThreadDevice = aNeighbor.IsFullThreadDevice();
107 mFullNetworkData = (aNeighbor.GetNetworkDataType() == NetworkData::kFullSet);
108 mVersion = aNeighbor.GetVersion();
109 #if OPENTHREAD_CONFIG_UPTIME_ENABLE
110 mConnectionTime = aNeighbor.GetConnectionTime();
111 #endif
112 }
113
Init(Instance & aInstance)114 void Neighbor::Init(Instance &aInstance)
115 {
116 InstanceLocatorInit::Init(aInstance);
117 mLinkInfo.Init(aInstance);
118 SetState(kStateInvalid);
119 }
120
IsStateValidOrAttaching(void) const121 bool Neighbor::IsStateValidOrAttaching(void) const
122 {
123 bool rval = false;
124
125 switch (GetState())
126 {
127 case kStateInvalid:
128 case kStateParentRequest:
129 case kStateParentResponse:
130 break;
131
132 case kStateRestored:
133 case kStateChildIdRequest:
134 case kStateLinkRequest:
135 case kStateChildUpdateRequest:
136 case kStateValid:
137 rval = true;
138 break;
139 }
140
141 return rval;
142 }
143
MatchesFilter(StateFilter aFilter) const144 bool Neighbor::MatchesFilter(StateFilter aFilter) const
145 {
146 bool matches = false;
147
148 switch (aFilter)
149 {
150 case kInStateValid:
151 matches = IsStateValid();
152 break;
153
154 case kInStateValidOrRestoring:
155 matches = IsStateValidOrRestoring();
156 break;
157
158 case kInStateChildIdRequest:
159 matches = IsStateChildIdRequest();
160 break;
161
162 case kInStateValidOrAttaching:
163 matches = IsStateValidOrAttaching();
164 break;
165
166 case kInStateInvalid:
167 matches = IsStateInvalid();
168 break;
169
170 case kInStateAnyExceptInvalid:
171 matches = !IsStateInvalid();
172 break;
173
174 case kInStateAnyExceptValidOrRestoring:
175 matches = !IsStateValidOrRestoring();
176 break;
177
178 case kInStateAny:
179 matches = true;
180 break;
181 }
182
183 return matches;
184 }
185
186 #if OPENTHREAD_CONFIG_MULTI_RADIO
SetLastRxFragmentTag(uint16_t aTag)187 void Neighbor::SetLastRxFragmentTag(uint16_t aTag)
188 {
189 mLastRxFragmentTag = (aTag == 0) ? 0xffff : aTag;
190 mLastRxFragmentTagTime = TimerMilli::GetNow();
191 }
192
IsLastRxFragmentTagSet(void) const193 bool Neighbor::IsLastRxFragmentTagSet(void) const
194 {
195 return (mLastRxFragmentTag != 0) && (TimerMilli::GetNow() <= mLastRxFragmentTagTime + kLastRxFragmentTagTimeout);
196 }
197 #endif
198
GenerateChallenge(void)199 void Neighbor::GenerateChallenge(void)
200 {
201 IgnoreError(
202 Random::Crypto::FillBuffer(mValidPending.mPending.mChallenge, sizeof(mValidPending.mPending.mChallenge)));
203 }
204
205 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
AggregateLinkMetrics(uint8_t aSeriesId,uint8_t aFrameType,uint8_t aLqi,int8_t aRss)206 void Neighbor::AggregateLinkMetrics(uint8_t aSeriesId, uint8_t aFrameType, uint8_t aLqi, int8_t aRss)
207 {
208 for (LinkMetrics::SeriesInfo &entry : mLinkMetricsSeriesInfoList)
209 {
210 if (aSeriesId == 0 || aSeriesId == entry.GetSeriesId())
211 {
212 entry.AggregateLinkMetrics(aFrameType, aLqi, aRss);
213 }
214 }
215 }
216
GetForwardTrackingSeriesInfo(const uint8_t & aSeriesId)217 LinkMetrics::SeriesInfo *Neighbor::GetForwardTrackingSeriesInfo(const uint8_t &aSeriesId)
218 {
219 return mLinkMetricsSeriesInfoList.FindMatching(aSeriesId);
220 }
221
AddForwardTrackingSeriesInfo(LinkMetrics::SeriesInfo & aSeriesInfo)222 void Neighbor::AddForwardTrackingSeriesInfo(LinkMetrics::SeriesInfo &aSeriesInfo)
223 {
224 mLinkMetricsSeriesInfoList.Push(aSeriesInfo);
225 }
226
RemoveForwardTrackingSeriesInfo(const uint8_t & aSeriesId)227 LinkMetrics::SeriesInfo *Neighbor::RemoveForwardTrackingSeriesInfo(const uint8_t &aSeriesId)
228 {
229 return mLinkMetricsSeriesInfoList.RemoveMatching(aSeriesId);
230 }
231
RemoveAllForwardTrackingSeriesInfo(void)232 void Neighbor::RemoveAllForwardTrackingSeriesInfo(void)
233 {
234 while (!mLinkMetricsSeriesInfoList.IsEmpty())
235 {
236 LinkMetrics::SeriesInfo *seriesInfo = mLinkMetricsSeriesInfoList.Pop();
237 Get<LinkMetrics::Subject>().Free(*seriesInfo);
238 }
239 }
240 #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
241
StateToString(State aState)242 const char *Neighbor::StateToString(State aState)
243 {
244 static const char *const kStateStrings[] = {
245 "Invalid", // (0) kStateInvalid
246 "Restored", // (1) kStateRestored
247 "ParentReq", // (2) kStateParentRequest
248 "ParentRes", // (3) kStateParentResponse
249 "ChildIdReq", // (4) kStateChildIdRequest
250 "LinkReq", // (5) kStateLinkRequest
251 "ChildUpdateReq", // (6) kStateChildUpdateRequest
252 "Valid", // (7) kStateValid
253 };
254
255 static_assert(0 == kStateInvalid, "kStateInvalid value is incorrect");
256 static_assert(1 == kStateRestored, "kStateRestored value is incorrect");
257 static_assert(2 == kStateParentRequest, "kStateParentRequest value is incorrect");
258 static_assert(3 == kStateParentResponse, "kStateParentResponse value is incorrect");
259 static_assert(4 == kStateChildIdRequest, "kStateChildIdRequest value is incorrect");
260 static_assert(5 == kStateLinkRequest, "kStateLinkRequest value is incorrect");
261 static_assert(6 == kStateChildUpdateRequest, "kStateChildUpdateRequest value is incorrect");
262 static_assert(7 == kStateValid, "kStateValid value is incorrect");
263
264 return kStateStrings[aState];
265 }
266
267 #if OPENTHREAD_FTD
268
SetFrom(const Child & aChild)269 void Child::Info::SetFrom(const Child &aChild)
270 {
271 Clear();
272 mExtAddress = aChild.GetExtAddress();
273 mTimeout = aChild.GetTimeout();
274 mRloc16 = aChild.GetRloc16();
275 mChildId = Mle::ChildIdFromRloc16(aChild.GetRloc16());
276 mNetworkDataVersion = aChild.GetNetworkDataVersion();
277 mAge = Time::MsecToSec(TimerMilli::GetNow() - aChild.GetLastHeard());
278 mLinkQualityIn = aChild.GetLinkQualityIn();
279 mAverageRssi = aChild.GetLinkInfo().GetAverageRss();
280 mLastRssi = aChild.GetLinkInfo().GetLastRss();
281 mFrameErrorRate = aChild.GetLinkInfo().GetFrameErrorRate();
282 mMessageErrorRate = aChild.GetLinkInfo().GetMessageErrorRate();
283 mQueuedMessageCnt = aChild.GetIndirectMessageCount();
284 mVersion = ClampToUint8(aChild.GetVersion());
285 mRxOnWhenIdle = aChild.IsRxOnWhenIdle();
286 mFullThreadDevice = aChild.IsFullThreadDevice();
287 mFullNetworkData = (aChild.GetNetworkDataType() == NetworkData::kFullSet);
288 mIsStateRestoring = aChild.IsStateRestoring();
289 mSupervisionInterval = aChild.GetSupervisionInterval();
290 #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
291 mIsCslSynced = aChild.IsCslSynchronized();
292 #else
293 mIsCslSynced = false;
294 #endif
295 #if OPENTHREAD_CONFIG_UPTIME_ENABLE
296 mConnectionTime = aChild.GetConnectionTime();
297 #endif
298 }
299
GetAddress(void) const300 const Ip6::Address *Child::AddressIterator::GetAddress(void) const
301 {
302 // `mIndex` value of zero indicates mesh-local IPv6 address.
303 // Non-zero value specifies the index into address array starting
304 // from one for first element (i.e, `mIndex - 1` gives the array
305 // index).
306
307 return (mIndex == 0) ? &mMeshLocalAddress : ((mIndex < kMaxIndex) ? &mChild.mIp6Address[mIndex - 1] : nullptr);
308 }
309
Update(void)310 void Child::AddressIterator::Update(void)
311 {
312 const Ip6::Address *address;
313
314 if ((mIndex == 0) && (mChild.GetMeshLocalIp6Address(mMeshLocalAddress) != kErrorNone))
315 {
316 mIndex++;
317 }
318
319 while (true)
320 {
321 address = GetAddress();
322
323 VerifyOrExit((address != nullptr) && !address->IsUnspecified(), mIndex = kMaxIndex);
324
325 VerifyOrExit(!address->MatchesFilter(mFilter));
326 mIndex++;
327 }
328
329 exit:
330 return;
331 }
332
Clear(void)333 void Child::Clear(void)
334 {
335 Instance &instance = GetInstance();
336
337 memset(reinterpret_cast<void *>(this), 0, sizeof(Child));
338 Init(instance);
339 }
340
ClearIp6Addresses(void)341 void Child::ClearIp6Addresses(void)
342 {
343 mMeshLocalIid.Clear();
344 memset(mIp6Address, 0, sizeof(mIp6Address));
345 #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
346 mMlrToRegisterMask.Clear();
347 mMlrRegisteredMask.Clear();
348 #endif
349 }
350
SetDeviceMode(Mle::DeviceMode aMode)351 void Child::SetDeviceMode(Mle::DeviceMode aMode)
352 {
353 VerifyOrExit(aMode != GetDeviceMode());
354
355 Neighbor::SetDeviceMode(aMode);
356
357 VerifyOrExit(IsStateValid());
358 Get<NeighborTable>().Signal(NeighborTable::kChildModeChanged, *this);
359
360 exit:
361 return;
362 }
363
GetMeshLocalIp6Address(Ip6::Address & aAddress) const364 Error Child::GetMeshLocalIp6Address(Ip6::Address &aAddress) const
365 {
366 Error error = kErrorNone;
367
368 VerifyOrExit(!mMeshLocalIid.IsUnspecified(), error = kErrorNotFound);
369
370 aAddress.SetPrefix(Get<Mle::MleRouter>().GetMeshLocalPrefix());
371 aAddress.SetIid(mMeshLocalIid);
372
373 exit:
374 return error;
375 }
376
AddIp6Address(const Ip6::Address & aAddress)377 Error Child::AddIp6Address(const Ip6::Address &aAddress)
378 {
379 Error error = kErrorNone;
380
381 VerifyOrExit(!aAddress.IsUnspecified(), error = kErrorInvalidArgs);
382
383 if (Get<Mle::MleRouter>().IsMeshLocalAddress(aAddress))
384 {
385 VerifyOrExit(mMeshLocalIid.IsUnspecified(), error = kErrorAlready);
386 mMeshLocalIid = aAddress.GetIid();
387 ExitNow();
388 }
389
390 for (Ip6::Address &ip6Address : mIp6Address)
391 {
392 if (ip6Address.IsUnspecified())
393 {
394 ip6Address = aAddress;
395 ExitNow();
396 }
397
398 VerifyOrExit(ip6Address != aAddress, error = kErrorAlready);
399 }
400
401 error = kErrorNoBufs;
402
403 exit:
404 return error;
405 }
406
RemoveIp6Address(const Ip6::Address & aAddress)407 Error Child::RemoveIp6Address(const Ip6::Address &aAddress)
408 {
409 Error error = kErrorNotFound;
410 uint16_t index;
411
412 VerifyOrExit(!aAddress.IsUnspecified(), error = kErrorInvalidArgs);
413
414 if (Get<Mle::MleRouter>().IsMeshLocalAddress(aAddress))
415 {
416 if (aAddress.GetIid() == mMeshLocalIid)
417 {
418 mMeshLocalIid.Clear();
419 error = kErrorNone;
420 }
421
422 ExitNow();
423 }
424
425 for (index = 0; index < kNumIp6Addresses; index++)
426 {
427 VerifyOrExit(!mIp6Address[index].IsUnspecified());
428
429 if (mIp6Address[index] == aAddress)
430 {
431 error = kErrorNone;
432 break;
433 }
434 }
435
436 SuccessOrExit(error);
437
438 for (; index < kNumIp6Addresses - 1; index++)
439 {
440 mIp6Address[index] = mIp6Address[index + 1];
441 }
442
443 mIp6Address[kNumIp6Addresses - 1].Clear();
444
445 exit:
446 return error;
447 }
448
HasIp6Address(const Ip6::Address & aAddress) const449 bool Child::HasIp6Address(const Ip6::Address &aAddress) const
450 {
451 bool retval = false;
452
453 VerifyOrExit(!aAddress.IsUnspecified());
454
455 if (Get<Mle::MleRouter>().IsMeshLocalAddress(aAddress))
456 {
457 retval = (aAddress.GetIid() == mMeshLocalIid);
458 ExitNow();
459 }
460
461 for (const Ip6::Address &ip6Address : mIp6Address)
462 {
463 VerifyOrExit(!ip6Address.IsUnspecified());
464
465 if (ip6Address == aAddress)
466 {
467 ExitNow(retval = true);
468 }
469 }
470
471 exit:
472 return retval;
473 }
474
475 #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE
GetDomainUnicastAddress(void) const476 const Ip6::Address *Child::GetDomainUnicastAddress(void) const
477 {
478 const Ip6::Address *addr = nullptr;
479
480 for (const Ip6::Address &ip6Address : mIp6Address)
481 {
482 VerifyOrExit(!ip6Address.IsUnspecified());
483
484 if (Get<BackboneRouter::Leader>().IsDomainUnicast(ip6Address))
485 {
486 ExitNow(addr = &ip6Address);
487 }
488 }
489
490 exit:
491 return addr;
492 }
493 #endif
494
GenerateChallenge(void)495 void Child::GenerateChallenge(void)
496 {
497 IgnoreError(Random::Crypto::FillBuffer(mAttachChallenge, sizeof(mAttachChallenge)));
498 }
499
500 #if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
HasMlrRegisteredAddress(const Ip6::Address & aAddress) const501 bool Child::HasMlrRegisteredAddress(const Ip6::Address &aAddress) const
502 {
503 bool has = false;
504
505 VerifyOrExit(mMlrRegisteredMask.HasAny());
506
507 for (const Ip6::Address &address : IterateIp6Addresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal))
508 {
509 if (GetAddressMlrState(address) == kMlrStateRegistered && address == aAddress)
510 {
511 ExitNow(has = true);
512 }
513 }
514
515 exit:
516 return has;
517 }
518
GetAddressMlrState(const Ip6::Address & aAddress) const519 MlrState Child::GetAddressMlrState(const Ip6::Address &aAddress) const
520 {
521 uint16_t addressIndex;
522
523 OT_ASSERT(&mIp6Address[0] <= &aAddress && &aAddress < GetArrayEnd(mIp6Address));
524
525 addressIndex = static_cast<uint16_t>(&aAddress - mIp6Address);
526
527 return mMlrToRegisterMask.Get(addressIndex)
528 ? kMlrStateToRegister
529 : (mMlrRegisteredMask.Get(addressIndex) ? kMlrStateRegistered : kMlrStateRegistering);
530 }
531
SetAddressMlrState(const Ip6::Address & aAddress,MlrState aState)532 void Child::SetAddressMlrState(const Ip6::Address &aAddress, MlrState aState)
533 {
534 uint16_t addressIndex;
535
536 OT_ASSERT(&mIp6Address[0] <= &aAddress && &aAddress < GetArrayEnd(mIp6Address));
537
538 addressIndex = static_cast<uint16_t>(&aAddress - mIp6Address);
539
540 mMlrToRegisterMask.Set(addressIndex, aState == kMlrStateToRegister);
541 mMlrRegisteredMask.Set(addressIndex, aState == kMlrStateRegistered);
542 }
543 #endif // OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
544
545 #endif // OPENTHREAD_FTD
546
SetFrom(const Router & aRouter)547 void Router::Info::SetFrom(const Router &aRouter)
548 {
549 Clear();
550 mRloc16 = aRouter.GetRloc16();
551 mRouterId = Mle::RouterIdFromRloc16(mRloc16);
552 mExtAddress = aRouter.GetExtAddress();
553 mAllocated = true;
554 mNextHop = aRouter.GetNextHop();
555 mLinkEstablished = aRouter.IsStateValid();
556 mPathCost = aRouter.GetCost();
557 mLinkQualityIn = aRouter.GetLinkQualityIn();
558 mLinkQualityOut = aRouter.GetLinkQualityOut();
559 mAge = static_cast<uint8_t>(Time::MsecToSec(TimerMilli::GetNow() - aRouter.GetLastHeard()));
560 mVersion = ClampToUint8(aRouter.GetVersion());
561 }
562
SetFrom(const Parent & aParent)563 void Router::Info::SetFrom(const Parent &aParent)
564 {
565 SetFrom(static_cast<const Router &>(aParent));
566 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
567 mCslClockAccuracy = aParent.GetCslAccuracy().GetClockAccuracy();
568 mCslUncertainty = aParent.GetCslAccuracy().GetUncertainty();
569 #endif
570 }
571
Clear(void)572 void Router::Clear(void)
573 {
574 Instance &instance = GetInstance();
575
576 memset(reinterpret_cast<void *>(this), 0, sizeof(Router));
577 Init(instance);
578 }
579
GetTwoWayLinkQuality(void) const580 LinkQuality Router::GetTwoWayLinkQuality(void) const { return Min(GetLinkQualityIn(), GetLinkQualityOut()); }
581
SetFrom(const Parent & aParent)582 void Router::SetFrom(const Parent &aParent)
583 {
584 // We use an intermediate pointer to copy `aParent` to silence
585 // code checkers warning about object slicing (assigning a
586 // sub-class to base class instance).
587
588 const Router *parentAsRouter = &aParent;
589
590 *this = *parentAsRouter;
591 }
592
Clear(void)593 void Parent::Clear(void)
594 {
595 Instance &instance = GetInstance();
596
597 memset(reinterpret_cast<void *>(this), 0, sizeof(Parent));
598 Init(instance);
599 }
600
SetNextHopAndCost(uint8_t aNextHop,uint8_t aCost)601 bool Router::SetNextHopAndCost(uint8_t aNextHop, uint8_t aCost)
602 {
603 bool changed = false;
604
605 if (mNextHop != aNextHop)
606 {
607 mNextHop = aNextHop;
608 changed = true;
609 }
610
611 if (mCost != aCost)
612 {
613 mCost = aCost;
614 changed = true;
615 }
616
617 return changed;
618 }
619
SetNextHopToInvalid(void)620 bool Router::SetNextHopToInvalid(void) { return SetNextHopAndCost(Mle::kInvalidRouterId, 0); }
621
622 } // namespace ot
623