1 /*
2 * Copyright (c) 2016, 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 Thread Network Data managed by the Thread Leader.
32 */
33
34 #include "network_data_leader.hpp"
35
36 #include "coap/coap_message.hpp"
37 #include "common/code_utils.hpp"
38 #include "common/debug.hpp"
39 #include "common/encoding.hpp"
40 #include "common/locator_getters.hpp"
41 #include "common/logging.hpp"
42 #include "common/message.hpp"
43 #include "common/random.hpp"
44 #include "common/timer.hpp"
45 #include "instance/instance.hpp"
46 #include "mac/mac_types.hpp"
47 #include "thread/lowpan.hpp"
48 #include "thread/mle_router.hpp"
49 #include "thread/thread_netif.hpp"
50 #include "thread/thread_tlvs.hpp"
51 #include "thread/uri_paths.hpp"
52
53 namespace ot {
54 namespace NetworkData {
55
56 RegisterLogModule("NetworkData");
57
Leader(Instance & aInstance)58 Leader::Leader(Instance &aInstance)
59 : MutableNetworkData(aInstance, mTlvBuffer, 0, sizeof(mTlvBuffer))
60 , mMaxLength(0)
61 #if OPENTHREAD_FTD
62 #if OPENTHREAD_CONFIG_BORDER_ROUTER_SIGNAL_NETWORK_DATA_FULL
63 , mIsClone(false)
64 #endif
65 , mWaitingForNetDataSync(false)
66 , mContextIds(aInstance)
67 , mTimer(aInstance)
68 #endif
69 {
70 Reset();
71 }
72
Reset(void)73 void Leader::Reset(void)
74 {
75 mVersion = Random::NonCrypto::GetUint8();
76 mStableVersion = Random::NonCrypto::GetUint8();
77 SetLength(0);
78 SignalNetDataChanged();
79
80 #if OPENTHREAD_FTD
81 mContextIds.Clear();
82 #endif
83 }
84
GetServiceId(uint32_t aEnterpriseNumber,const ServiceData & aServiceData,bool aServerStable,uint8_t & aServiceId) const85 Error Leader::GetServiceId(uint32_t aEnterpriseNumber,
86 const ServiceData &aServiceData,
87 bool aServerStable,
88 uint8_t &aServiceId) const
89 {
90 Error error = kErrorNotFound;
91 Iterator iterator = kIteratorInit;
92 ServiceConfig serviceConfig;
93 ServiceData serviceData;
94
95 while (GetNextService(iterator, serviceConfig) == kErrorNone)
96 {
97 serviceConfig.GetServiceData(serviceData);
98
99 if (aEnterpriseNumber == serviceConfig.mEnterpriseNumber && aServiceData == serviceData &&
100 aServerStable == serviceConfig.mServerConfig.mStable)
101 {
102 aServiceId = serviceConfig.mServiceId;
103 ExitNow(error = kErrorNone);
104 }
105 }
106
107 exit:
108 return error;
109 }
110
GetPreferredNat64Prefix(ExternalRouteConfig & aConfig) const111 Error Leader::GetPreferredNat64Prefix(ExternalRouteConfig &aConfig) const
112 {
113 Error error = kErrorNotFound;
114 Iterator iterator = kIteratorInit;
115 ExternalRouteConfig config;
116
117 while (GetNextExternalRoute(iterator, config) == kErrorNone)
118 {
119 if (!config.mNat64 || !config.GetPrefix().IsValidNat64())
120 {
121 continue;
122 }
123
124 if ((error == kErrorNotFound) || (config.mPreference > aConfig.mPreference) ||
125 (config.mPreference == aConfig.mPreference && config.GetPrefix() < aConfig.GetPrefix()))
126 {
127 aConfig = config;
128 error = kErrorNone;
129 }
130 }
131
132 return error;
133 }
134
FindNextMatchingPrefixTlv(const Ip6::Address & aAddress,const PrefixTlv * aPrevTlv) const135 const PrefixTlv *Leader::FindNextMatchingPrefixTlv(const Ip6::Address &aAddress, const PrefixTlv *aPrevTlv) const
136 {
137 // This method iterates over Prefix TLVs which match a given IPv6
138 // `aAddress`. If `aPrevTlv` is `nullptr` we start from the
139 // beginning. Otherwise, we search for a match after `aPrevTlv`.
140 // This method returns a pointer to the next matching Prefix TLV
141 // when found, or `nullptr` if no match is found.
142
143 const PrefixTlv *prefixTlv;
144 TlvIterator tlvIterator((aPrevTlv == nullptr) ? GetTlvsStart() : aPrevTlv->GetNext(), GetTlvsEnd());
145
146 while ((prefixTlv = tlvIterator.Iterate<PrefixTlv>()) != nullptr)
147 {
148 if (aAddress.MatchesPrefix(prefixTlv->GetPrefix(), prefixTlv->GetPrefixLength()))
149 {
150 break;
151 }
152 }
153
154 return prefixTlv;
155 }
156
GetContext(const Ip6::Address & aAddress,Lowpan::Context & aContext) const157 Error Leader::GetContext(const Ip6::Address &aAddress, Lowpan::Context &aContext) const
158 {
159 const PrefixTlv *prefixTlv = nullptr;
160 const ContextTlv *contextTlv;
161
162 aContext.mPrefix.SetLength(0);
163
164 if (Get<Mle::MleRouter>().IsMeshLocalAddress(aAddress))
165 {
166 GetContextForMeshLocalPrefix(aContext);
167 }
168
169 while ((prefixTlv = FindNextMatchingPrefixTlv(aAddress, prefixTlv)) != nullptr)
170 {
171 contextTlv = prefixTlv->FindSubTlv<ContextTlv>();
172
173 if (contextTlv == nullptr)
174 {
175 continue;
176 }
177
178 if (prefixTlv->GetPrefixLength() > aContext.mPrefix.GetLength())
179 {
180 prefixTlv->CopyPrefixTo(aContext.mPrefix);
181 aContext.mContextId = contextTlv->GetContextId();
182 aContext.mCompressFlag = contextTlv->IsCompress();
183 aContext.mIsValid = true;
184 }
185 }
186
187 return (aContext.mPrefix.GetLength() > 0) ? kErrorNone : kErrorNotFound;
188 }
189
GetContext(uint8_t aContextId,Lowpan::Context & aContext) const190 Error Leader::GetContext(uint8_t aContextId, Lowpan::Context &aContext) const
191 {
192 Error error = kErrorNotFound;
193 TlvIterator tlvIterator(GetTlvsStart(), GetTlvsEnd());
194 const PrefixTlv *prefixTlv;
195
196 if (aContextId == Mle::kMeshLocalPrefixContextId)
197 {
198 GetContextForMeshLocalPrefix(aContext);
199 ExitNow(error = kErrorNone);
200 }
201
202 while ((prefixTlv = tlvIterator.Iterate<PrefixTlv>()) != nullptr)
203 {
204 const ContextTlv *contextTlv = prefixTlv->FindSubTlv<ContextTlv>();
205
206 if ((contextTlv == nullptr) || (contextTlv->GetContextId() != aContextId))
207 {
208 continue;
209 }
210
211 prefixTlv->CopyPrefixTo(aContext.mPrefix);
212 aContext.mContextId = contextTlv->GetContextId();
213 aContext.mCompressFlag = contextTlv->IsCompress();
214 aContext.mIsValid = true;
215 ExitNow(error = kErrorNone);
216 }
217
218 exit:
219 return error;
220 }
221
GetContextForMeshLocalPrefix(Lowpan::Context & aContext) const222 void Leader::GetContextForMeshLocalPrefix(Lowpan::Context &aContext) const
223 {
224 aContext.mPrefix.Set(Get<Mle::MleRouter>().GetMeshLocalPrefix());
225 aContext.mContextId = Mle::kMeshLocalPrefixContextId;
226 aContext.mCompressFlag = true;
227 aContext.mIsValid = true;
228 }
229
IsOnMesh(const Ip6::Address & aAddress) const230 bool Leader::IsOnMesh(const Ip6::Address &aAddress) const
231 {
232 const PrefixTlv *prefixTlv = nullptr;
233 bool isOnMesh = false;
234
235 VerifyOrExit(!Get<Mle::MleRouter>().IsMeshLocalAddress(aAddress), isOnMesh = true);
236
237 while ((prefixTlv = FindNextMatchingPrefixTlv(aAddress, prefixTlv)) != nullptr)
238 {
239 TlvIterator subTlvIterator(*prefixTlv);
240 const BorderRouterTlv *brTlv;
241
242 while ((brTlv = subTlvIterator.Iterate<BorderRouterTlv>()) != nullptr)
243 {
244 for (const BorderRouterEntry *entry = brTlv->GetFirstEntry(); entry <= brTlv->GetLastEntry();
245 entry = entry->GetNext())
246 {
247 if (entry->IsOnMesh())
248 {
249 ExitNow(isOnMesh = true);
250 }
251 }
252 }
253 }
254
255 exit:
256 return isOnMesh;
257 }
258
RouteLookup(const Ip6::Address & aSource,const Ip6::Address & aDestination,uint16_t & aRloc16) const259 Error Leader::RouteLookup(const Ip6::Address &aSource, const Ip6::Address &aDestination, uint16_t &aRloc16) const
260 {
261 Error error = kErrorNoRoute;
262 const PrefixTlv *prefixTlv = nullptr;
263
264 while ((prefixTlv = FindNextMatchingPrefixTlv(aSource, prefixTlv)) != nullptr)
265 {
266 if (prefixTlv->FindSubTlv<BorderRouterTlv>() == nullptr)
267 {
268 continue;
269 }
270
271 if (ExternalRouteLookup(prefixTlv->GetDomainId(), aDestination, aRloc16) == kErrorNone)
272 {
273 ExitNow(error = kErrorNone);
274 }
275
276 if (DefaultRouteLookup(*prefixTlv, aRloc16) == kErrorNone)
277 {
278 ExitNow(error = kErrorNone);
279 }
280 }
281
282 exit:
283 return error;
284 }
285
CompareRouteEntries(const EntryType & aFirst,const EntryType & aSecond) const286 template <typename EntryType> int Leader::CompareRouteEntries(const EntryType &aFirst, const EntryType &aSecond) const
287 {
288 // `EntryType` can be `HasRouteEntry` or `BorderRouterEntry`.
289
290 return CompareRouteEntries(aFirst.GetPreference(), aFirst.GetRloc(), aSecond.GetPreference(), aSecond.GetRloc());
291 }
292
CompareRouteEntries(int8_t aFirstPreference,uint16_t aFirstRloc,int8_t aSecondPreference,uint16_t aSecondRloc) const293 int Leader::CompareRouteEntries(int8_t aFirstPreference,
294 uint16_t aFirstRloc,
295 int8_t aSecondPreference,
296 uint16_t aSecondRloc) const
297 {
298 // Performs three-way comparison between two BR entries.
299
300 int result;
301
302 // Prefer the entry with higher preference.
303
304 result = ThreeWayCompare(aFirstPreference, aSecondPreference);
305 VerifyOrExit(result == 0);
306
307 #if OPENTHREAD_MTD
308 // On MTD, prefer the BR that is this device itself. This handles
309 // the uncommon case where an MTD itself may be acting as BR.
310
311 result = ThreeWayCompare((aFirstRloc == Get<Mle::Mle>().GetRloc16()), (aSecondRloc == Get<Mle::Mle>().GetRloc16()));
312 #endif
313
314 #if OPENTHREAD_FTD
315 // If all the same, prefer the one with lower mesh path cost.
316 // Lower cost is preferred so we pass the second entry's cost as
317 // the first argument in the call to `ThreeWayCompare()`, i.e.,
318 // if the second entry's cost is larger, we return 1 indicating
319 // that the first entry is preferred over the second one.
320
321 result = ThreeWayCompare(Get<RouterTable>().GetPathCost(aSecondRloc), Get<RouterTable>().GetPathCost(aFirstRloc));
322 VerifyOrExit(result == 0);
323
324 // If all the same, prefer the BR acting as a router over an
325 // end device.
326 result = ThreeWayCompare(Mle::IsActiveRouter(aFirstRloc), Mle::IsActiveRouter(aSecondRloc));
327 #endif
328
329 exit:
330 return result;
331 }
332
ExternalRouteLookup(uint8_t aDomainId,const Ip6::Address & aDestination,uint16_t & aRloc16) const333 Error Leader::ExternalRouteLookup(uint8_t aDomainId, const Ip6::Address &aDestination, uint16_t &aRloc16) const
334 {
335 Error error = kErrorNoRoute;
336 const PrefixTlv *prefixTlv = nullptr;
337 const HasRouteEntry *bestRouteEntry = nullptr;
338 uint8_t bestMatchLength = 0;
339
340 while ((prefixTlv = FindNextMatchingPrefixTlv(aDestination, prefixTlv)) != nullptr)
341 {
342 const HasRouteTlv *hasRoute;
343 uint8_t prefixLength = prefixTlv->GetPrefixLength();
344 TlvIterator subTlvIterator(*prefixTlv);
345
346 if (prefixTlv->GetDomainId() != aDomainId)
347 {
348 continue;
349 }
350
351 if ((bestRouteEntry != nullptr) && (prefixLength <= bestMatchLength))
352 {
353 continue;
354 }
355
356 while ((hasRoute = subTlvIterator.Iterate<HasRouteTlv>()) != nullptr)
357 {
358 for (const HasRouteEntry *entry = hasRoute->GetFirstEntry(); entry <= hasRoute->GetLastEntry();
359 entry = entry->GetNext())
360 {
361 if ((bestRouteEntry == nullptr) || (prefixLength > bestMatchLength) ||
362 CompareRouteEntries(*entry, *bestRouteEntry) > 0)
363 {
364 bestRouteEntry = entry;
365 bestMatchLength = prefixLength;
366 }
367 }
368 }
369 }
370
371 if (bestRouteEntry != nullptr)
372 {
373 aRloc16 = bestRouteEntry->GetRloc();
374 error = kErrorNone;
375 }
376
377 return error;
378 }
379
DefaultRouteLookup(const PrefixTlv & aPrefix,uint16_t & aRloc16) const380 Error Leader::DefaultRouteLookup(const PrefixTlv &aPrefix, uint16_t &aRloc16) const
381 {
382 Error error = kErrorNoRoute;
383 TlvIterator subTlvIterator(aPrefix);
384 const BorderRouterTlv *brTlv;
385 const BorderRouterEntry *route = nullptr;
386
387 while ((brTlv = subTlvIterator.Iterate<BorderRouterTlv>()) != nullptr)
388 {
389 for (const BorderRouterEntry *entry = brTlv->GetFirstEntry(); entry <= brTlv->GetLastEntry();
390 entry = entry->GetNext())
391 {
392 if (!entry->IsDefaultRoute())
393 {
394 continue;
395 }
396
397 if (route == nullptr || CompareRouteEntries(*entry, *route) > 0)
398 {
399 route = entry;
400 }
401 }
402 }
403
404 if (route != nullptr)
405 {
406 aRloc16 = route->GetRloc();
407 error = kErrorNone;
408 }
409
410 return error;
411 }
412
SetNetworkData(uint8_t aVersion,uint8_t aStableVersion,Type aType,const Message & aMessage,uint16_t aOffset,uint16_t aLength)413 Error Leader::SetNetworkData(uint8_t aVersion,
414 uint8_t aStableVersion,
415 Type aType,
416 const Message &aMessage,
417 uint16_t aOffset,
418 uint16_t aLength)
419 {
420 Error error = kErrorNone;
421
422 VerifyOrExit(aLength <= kMaxSize, error = kErrorParse);
423 SuccessOrExit(error = aMessage.Read(aOffset, GetBytes(), aLength));
424
425 SetLength(static_cast<uint8_t>(aLength));
426 mVersion = aVersion;
427 mStableVersion = aStableVersion;
428
429 if (aType == kStableSubset)
430 {
431 RemoveTemporaryData();
432 }
433
434 #if OPENTHREAD_FTD
435 if (Get<Mle::MleRouter>().IsLeader())
436 {
437 Get<Leader>().HandleNetworkDataRestoredAfterReset();
438 }
439 #endif
440
441 DumpDebg("SetNetworkData", GetBytes(), GetLength());
442
443 SignalNetDataChanged();
444
445 exit:
446 return error;
447 }
448
FindCommissioningData(void) const449 const CommissioningDataTlv *Leader::FindCommissioningData(void) const
450 {
451 return NetworkDataTlv::Find<CommissioningDataTlv>(GetTlvsStart(), GetTlvsEnd());
452 }
453
FindCommissioningDataSubTlv(uint8_t aType) const454 const MeshCoP::Tlv *Leader::FindCommissioningDataSubTlv(uint8_t aType) const
455 {
456 const MeshCoP::Tlv *subTlv = nullptr;
457 const NetworkDataTlv *dataTlv = FindCommissioningData();
458
459 VerifyOrExit(dataTlv != nullptr);
460 subTlv = As<MeshCoP::Tlv>(Tlv::FindTlv(dataTlv->GetValue(), dataTlv->GetLength(), aType));
461
462 exit:
463 return subTlv;
464 }
465
ReadCommissioningDataUint16SubTlv(MeshCoP::Tlv::Type aType,uint16_t & aValue) const466 Error Leader::ReadCommissioningDataUint16SubTlv(MeshCoP::Tlv::Type aType, uint16_t &aValue) const
467 {
468 Error error = kErrorNone;
469 const MeshCoP::Tlv *subTlv = FindCommissioningDataSubTlv(aType);
470
471 VerifyOrExit(subTlv != nullptr, error = kErrorNotFound);
472 VerifyOrExit(subTlv->GetLength() >= sizeof(uint16_t), error = kErrorParse);
473 aValue = BigEndian::ReadUint16(subTlv->GetValue());
474
475 exit:
476 return error;
477 }
478
GetCommissioningDataset(MeshCoP::CommissioningDataset & aDataset) const479 void Leader::GetCommissioningDataset(MeshCoP::CommissioningDataset &aDataset) const
480 {
481 const CommissioningDataTlv *dataTlv = FindCommissioningData();
482 const MeshCoP::Tlv *subTlv;
483 const MeshCoP::Tlv *endTlv;
484
485 aDataset.Clear();
486
487 VerifyOrExit(dataTlv != nullptr);
488
489 aDataset.mIsLocatorSet = (FindBorderAgentRloc(aDataset.mLocator) == kErrorNone);
490 aDataset.mIsSessionIdSet = (FindCommissioningSessionId(aDataset.mSessionId) == kErrorNone);
491 aDataset.mIsJoinerUdpPortSet = (FindJoinerUdpPort(aDataset.mJoinerUdpPort) == kErrorNone);
492 aDataset.mIsSteeringDataSet = (FindSteeringData(AsCoreType(&aDataset.mSteeringData)) == kErrorNone);
493
494 // Determine if the Commissioning data has any extra unknown TLVs
495
496 subTlv = reinterpret_cast<const MeshCoP::Tlv *>(dataTlv->GetValue());
497 endTlv = reinterpret_cast<const MeshCoP::Tlv *>(dataTlv->GetValue() + dataTlv->GetLength());
498
499 for (; subTlv < endTlv; subTlv = subTlv->GetNext())
500 {
501 switch (subTlv->GetType())
502 {
503 case MeshCoP::Tlv::kBorderAgentLocator:
504 case MeshCoP::Tlv::kSteeringData:
505 case MeshCoP::Tlv::kJoinerUdpPort:
506 case MeshCoP::Tlv::kCommissionerSessionId:
507 break;
508 default:
509 ExitNow(aDataset.mHasExtraTlv = true);
510 }
511 }
512
513 exit:
514 return;
515 }
516
FindBorderAgentRloc(uint16_t & aRloc16) const517 Error Leader::FindBorderAgentRloc(uint16_t &aRloc16) const
518 {
519 return ReadCommissioningDataUint16SubTlv(MeshCoP::Tlv::kBorderAgentLocator, aRloc16);
520 }
521
FindCommissioningSessionId(uint16_t & aSessionId) const522 Error Leader::FindCommissioningSessionId(uint16_t &aSessionId) const
523 {
524 return ReadCommissioningDataUint16SubTlv(MeshCoP::Tlv::kCommissionerSessionId, aSessionId);
525 }
526
FindJoinerUdpPort(uint16_t & aPort) const527 Error Leader::FindJoinerUdpPort(uint16_t &aPort) const
528 {
529 return ReadCommissioningDataUint16SubTlv(MeshCoP::Tlv::kJoinerUdpPort, aPort);
530 }
531
FindSteeringData(MeshCoP::SteeringData & aSteeringData) const532 Error Leader::FindSteeringData(MeshCoP::SteeringData &aSteeringData) const
533 {
534 Error error = kErrorNone;
535 const MeshCoP::SteeringDataTlv *steeringDataTlv = FindInCommissioningData<MeshCoP::SteeringDataTlv>();
536
537 VerifyOrExit(steeringDataTlv != nullptr, error = kErrorNotFound);
538 steeringDataTlv->CopyTo(aSteeringData);
539
540 exit:
541 return error;
542 }
543
IsJoiningAllowed(void) const544 bool Leader::IsJoiningAllowed(void) const
545 {
546 bool isAllowed = false;
547 MeshCoP::SteeringData steeringData;
548
549 SuccessOrExit(FindSteeringData(steeringData));
550 isAllowed = !steeringData.IsEmpty();
551
552 exit:
553 return isAllowed;
554 }
555
SteeringDataCheck(const FilterIndexes & aFilterIndexes) const556 Error Leader::SteeringDataCheck(const FilterIndexes &aFilterIndexes) const
557 {
558 Error error = kErrorInvalidState;
559 MeshCoP::SteeringData steeringData;
560
561 SuccessOrExit(FindSteeringData(steeringData));
562 error = steeringData.Contains(aFilterIndexes) ? kErrorNone : kErrorNotFound;
563
564 exit:
565 return error;
566 }
567
SteeringDataCheckJoiner(const Mac::ExtAddress & aEui64) const568 Error Leader::SteeringDataCheckJoiner(const Mac::ExtAddress &aEui64) const
569 {
570 FilterIndexes filterIndexes;
571 Mac::ExtAddress joinerId;
572
573 MeshCoP::ComputeJoinerId(aEui64, joinerId);
574 MeshCoP::SteeringData::CalculateHashBitIndexes(joinerId, filterIndexes);
575
576 return SteeringDataCheck(filterIndexes);
577 }
578
SteeringDataCheckJoiner(const MeshCoP::JoinerDiscerner & aDiscerner) const579 Error Leader::SteeringDataCheckJoiner(const MeshCoP::JoinerDiscerner &aDiscerner) const
580 {
581 FilterIndexes filterIndexes;
582
583 MeshCoP::SteeringData::CalculateHashBitIndexes(aDiscerner, filterIndexes);
584
585 return SteeringDataCheck(filterIndexes);
586 }
587
SignalNetDataChanged(void)588 void Leader::SignalNetDataChanged(void)
589 {
590 mMaxLength = Max(mMaxLength, GetLength());
591 Get<ot::Notifier>().Signal(kEventThreadNetdataChanged);
592 }
593
594 } // namespace NetworkData
595 } // namespace ot
596