• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 Joiner role.
32  */
33 
34 #include "joiner.hpp"
35 
36 #if OPENTHREAD_CONFIG_JOINER_ENABLE
37 
38 #include "instance/instance.hpp"
39 
40 namespace ot {
41 namespace MeshCoP {
42 
43 RegisterLogModule("Joiner");
44 
Joiner(Instance & aInstance)45 Joiner::Joiner(Instance &aInstance)
46     : InstanceLocator(aInstance)
47     , mId()
48     , mDiscerner()
49     , mState(kStateIdle)
50     , mJoinerRouterIndex(0)
51     , mFinalizeMessage(nullptr)
52     , mTimer(aInstance)
53 {
54     SetIdFromIeeeEui64();
55     mDiscerner.Clear();
56     ClearAllBytes(mJoinerRouters);
57 }
58 
SetIdFromIeeeEui64(void)59 void Joiner::SetIdFromIeeeEui64(void)
60 {
61     Mac::ExtAddress eui64;
62 
63     Get<Radio>().GetIeeeEui64(eui64);
64     ComputeJoinerId(eui64, mId);
65 }
66 
GetDiscerner(void) const67 const JoinerDiscerner *Joiner::GetDiscerner(void) const { return mDiscerner.IsEmpty() ? nullptr : &mDiscerner; }
68 
SetDiscerner(const JoinerDiscerner & aDiscerner)69 Error Joiner::SetDiscerner(const JoinerDiscerner &aDiscerner)
70 {
71     Error error = kErrorNone;
72 
73     VerifyOrExit(aDiscerner.IsValid(), error = kErrorInvalidArgs);
74     VerifyOrExit(mState == kStateIdle, error = kErrorInvalidState);
75 
76     mDiscerner = aDiscerner;
77     mDiscerner.GenerateJoinerId(mId);
78 
79 exit:
80     return error;
81 }
82 
ClearDiscerner(void)83 Error Joiner::ClearDiscerner(void)
84 {
85     Error error = kErrorNone;
86 
87     VerifyOrExit(mState == kStateIdle, error = kErrorInvalidState);
88     VerifyOrExit(!mDiscerner.IsEmpty());
89 
90     mDiscerner.Clear();
91     SetIdFromIeeeEui64();
92 
93 exit:
94     return error;
95 }
96 
SetState(State aState)97 void Joiner::SetState(State aState)
98 {
99     State oldState = mState;
100     OT_UNUSED_VARIABLE(oldState);
101 
102     SuccessOrExit(Get<Notifier>().Update(mState, aState, kEventJoinerStateChanged));
103 
104     LogInfo("JoinerState: %s -> %s", StateToString(oldState), StateToString(aState));
105 exit:
106     return;
107 }
108 
Start(const char * aPskd,const char * aProvisioningUrl,const char * aVendorName,const char * aVendorModel,const char * aVendorSwVersion,const char * aVendorData,otJoinerCallback aCallback,void * aContext)109 Error Joiner::Start(const char      *aPskd,
110                     const char      *aProvisioningUrl,
111                     const char      *aVendorName,
112                     const char      *aVendorModel,
113                     const char      *aVendorSwVersion,
114                     const char      *aVendorData,
115                     otJoinerCallback aCallback,
116                     void            *aContext)
117 {
118     Error                        error;
119     JoinerPskd                   joinerPskd;
120     Mac::ExtAddress              randomAddress;
121     SteeringData::HashBitIndexes filterIndexes;
122 
123     LogInfo("Joiner starting");
124 
125     VerifyOrExit(aProvisioningUrl == nullptr || IsValidUtf8String(aProvisioningUrl), error = kErrorInvalidArgs);
126     VerifyOrExit(aVendorName == nullptr || IsValidUtf8String(aVendorName), error = kErrorInvalidArgs);
127     VerifyOrExit(aVendorSwVersion == nullptr || IsValidUtf8String(aVendorSwVersion), error = kErrorInvalidArgs);
128 
129     VerifyOrExit(mState == kStateIdle, error = kErrorBusy);
130     VerifyOrExit(Get<ThreadNetif>().IsUp() && Get<Mle::Mle>().GetRole() == Mle::kRoleDisabled,
131                  error = kErrorInvalidState);
132 
133     SuccessOrExit(error = joinerPskd.SetFrom(aPskd));
134 
135     // Use random-generated extended address.
136     randomAddress.GenerateRandom();
137     Get<Mac::Mac>().SetExtAddress(randomAddress);
138     Get<Mle::MleRouter>().UpdateLinkLocalAddress();
139 
140     SuccessOrExit(error = Get<Tmf::SecureAgent>().Open(Ip6::NetifIdentifier::kNetifThreadInternal));
141     SuccessOrExit(error = Get<Tmf::SecureAgent>().Bind(kJoinerUdpPort));
142     Get<Tmf::SecureAgent>().SetConnectCallback(HandleSecureCoapClientConnect, this);
143     Get<Tmf::SecureAgent>().SetPsk(joinerPskd);
144 
145     for (JoinerRouter &router : mJoinerRouters)
146     {
147         router.mPriority = 0; // Priority zero means entry is not in-use.
148     }
149 
150     SuccessOrExit(error = PrepareJoinerFinalizeMessage(aProvisioningUrl, aVendorName, aVendorModel, aVendorSwVersion,
151                                                        aVendorData));
152 
153     if (!mDiscerner.IsEmpty())
154     {
155         SteeringData::CalculateHashBitIndexes(mDiscerner, filterIndexes);
156     }
157     else
158     {
159         SetIdFromIeeeEui64();
160         SteeringData::CalculateHashBitIndexes(mId, filterIndexes);
161     }
162 
163     SuccessOrExit(error = Get<Mle::DiscoverScanner>().Discover(Mac::ChannelMask(0), Get<Mac::Mac>().GetPanId(),
164                                                                /* aJoiner */ true, /* aEnableFiltering */ true,
165                                                                &filterIndexes, HandleDiscoverResult, this));
166 
167     mCallback.Set(aCallback, aContext);
168     SetState(kStateDiscover);
169 
170 exit:
171     if (error != kErrorNone)
172     {
173         FreeJoinerFinalizeMessage();
174     }
175 
176     LogWarnOnError(error, "start joiner");
177     return error;
178 }
179 
Stop(void)180 void Joiner::Stop(void)
181 {
182     LogInfo("Joiner stopped");
183 
184     // Callback is set to `nullptr` to skip calling it from `Finish()`
185     mCallback.Clear();
186     Finish(kErrorAbort);
187 }
188 
Finish(Error aError)189 void Joiner::Finish(Error aError)
190 {
191     switch (mState)
192     {
193     case kStateIdle:
194         ExitNow();
195 
196     case kStateConnect:
197     case kStateConnected:
198     case kStateEntrust:
199     case kStateJoined:
200         Get<Tmf::SecureAgent>().Disconnect();
201         IgnoreError(Get<Ip6::Filter>().RemoveUnsecurePort(kJoinerUdpPort));
202         mTimer.Stop();
203 
204         OT_FALL_THROUGH;
205 
206     case kStateDiscover:
207         Get<Tmf::SecureAgent>().Close();
208         break;
209     }
210 
211     SetState(kStateIdle);
212     FreeJoinerFinalizeMessage();
213 
214     mCallback.InvokeIfSet(aError);
215 
216 exit:
217     return;
218 }
219 
CalculatePriority(int8_t aRssi,bool aSteeringDataAllowsAny)220 uint8_t Joiner::CalculatePriority(int8_t aRssi, bool aSteeringDataAllowsAny)
221 {
222     int16_t priority;
223 
224     if (aRssi == Radio::kInvalidRssi)
225     {
226         aRssi = -127;
227     }
228 
229     // Clamp the RSSI to range [-127, -1]
230     priority = Clamp<int8_t>(aRssi, -127, -1);
231 
232     // Assign higher priority to networks with an exact match of Joiner
233     // ID in the Steering Data (128 < priority < 256) compared to ones
234     // that allow all Joiners (0 < priority < 128). Sub-prioritize
235     // based on signal strength. Priority 0 is reserved for unused
236     // entry.
237 
238     priority += aSteeringDataAllowsAny ? 128 : 256;
239 
240     return static_cast<uint8_t>(priority);
241 }
242 
HandleDiscoverResult(Mle::DiscoverScanner::ScanResult * aResult,void * aContext)243 void Joiner::HandleDiscoverResult(Mle::DiscoverScanner::ScanResult *aResult, void *aContext)
244 {
245     static_cast<Joiner *>(aContext)->HandleDiscoverResult(aResult);
246 }
247 
HandleDiscoverResult(Mle::DiscoverScanner::ScanResult * aResult)248 void Joiner::HandleDiscoverResult(Mle::DiscoverScanner::ScanResult *aResult)
249 {
250     VerifyOrExit(mState == kStateDiscover);
251 
252     if (aResult != nullptr && aResult->mJoinerUdpPort > 0)
253     {
254         SaveDiscoveredJoinerRouter(*aResult);
255     }
256     else
257     {
258         Get<Mac::Mac>().SetExtAddress(mId);
259         Get<Mle::MleRouter>().UpdateLinkLocalAddress();
260 
261         mJoinerRouterIndex = 0;
262         TryNextJoinerRouter(kErrorNone);
263     }
264 
265 exit:
266     return;
267 }
268 
SaveDiscoveredJoinerRouter(const Mle::DiscoverScanner::ScanResult & aResult)269 void Joiner::SaveDiscoveredJoinerRouter(const Mle::DiscoverScanner::ScanResult &aResult)
270 {
271     uint8_t       priority;
272     bool          doesAllowAny;
273     JoinerRouter *end = GetArrayEnd(mJoinerRouters);
274     JoinerRouter *entry;
275 
276     doesAllowAny = AsCoreType(&aResult.mSteeringData).PermitsAllJoiners();
277 
278     LogInfo("Joiner discover network: %s, pan:0x%04x, port:%d, chan:%d, rssi:%d, allow-any:%s",
279             AsCoreType(&aResult.mExtAddress).ToString().AsCString(), aResult.mPanId, aResult.mJoinerUdpPort,
280             aResult.mChannel, aResult.mRssi, ToYesNo(doesAllowAny));
281 
282     priority = CalculatePriority(aResult.mRssi, doesAllowAny);
283 
284     // We keep the list sorted based on priority. Find the place to
285     // add the new result.
286 
287     for (entry = &mJoinerRouters[0]; entry < end; entry++)
288     {
289         if (priority > entry->mPriority)
290         {
291             break;
292         }
293     }
294 
295     VerifyOrExit(entry < end);
296 
297     // Shift elements in array to make room for the new one.
298     memmove(entry + 1, entry,
299             static_cast<size_t>(reinterpret_cast<uint8_t *>(end - 1) - reinterpret_cast<uint8_t *>(entry)));
300 
301     entry->mExtAddr       = AsCoreType(&aResult.mExtAddress);
302     entry->mPanId         = aResult.mPanId;
303     entry->mJoinerUdpPort = aResult.mJoinerUdpPort;
304     entry->mChannel       = aResult.mChannel;
305     entry->mPriority      = priority;
306 
307 exit:
308     return;
309 }
310 
TryNextJoinerRouter(Error aPrevError)311 void Joiner::TryNextJoinerRouter(Error aPrevError)
312 {
313     for (; mJoinerRouterIndex < GetArrayLength(mJoinerRouters); mJoinerRouterIndex++)
314     {
315         JoinerRouter &router = mJoinerRouters[mJoinerRouterIndex];
316         Error         error;
317 
318         if (router.mPriority == 0)
319         {
320             break;
321         }
322 
323         error = Connect(router);
324         VerifyOrExit(error != kErrorNone, mJoinerRouterIndex++);
325 
326         // Save the error from `Connect` only if there is no previous
327         // error from earlier attempts. This ensures that if there has
328         // been a previous Joiner Router connect attempt where
329         // `Connect()` call itself was successful, the error status
330         // emitted from `Finish()` call corresponds to the error from
331         // that attempt.
332 
333         if (aPrevError == kErrorNone)
334         {
335             aPrevError = error;
336         }
337     }
338 
339     if (aPrevError == kErrorNone)
340     {
341         aPrevError = kErrorNotFound;
342     }
343 
344     Finish(aPrevError);
345 
346 exit:
347     return;
348 }
349 
Connect(JoinerRouter & aRouter)350 Error Joiner::Connect(JoinerRouter &aRouter)
351 {
352     Error         error = kErrorNotFound;
353     Ip6::SockAddr sockAddr(aRouter.mJoinerUdpPort);
354 
355     LogInfo("Joiner connecting to %s, pan:0x%04x, chan:%d", aRouter.mExtAddr.ToString().AsCString(), aRouter.mPanId,
356             aRouter.mChannel);
357 
358     Get<Mac::Mac>().SetPanId(aRouter.mPanId);
359     SuccessOrExit(error = Get<Mac::Mac>().SetPanChannel(aRouter.mChannel));
360     SuccessOrExit(error = Get<Ip6::Filter>().AddUnsecurePort(kJoinerUdpPort));
361 
362     sockAddr.GetAddress().SetToLinkLocalAddress(aRouter.mExtAddr);
363 
364     SuccessOrExit(error = Get<Tmf::SecureAgent>().Connect(sockAddr));
365 
366     SetState(kStateConnect);
367 
368 exit:
369     LogWarnOnError(error, "start secure joiner connection");
370     return error;
371 }
372 
HandleSecureCoapClientConnect(Dtls::Session::ConnectEvent aEvent,void * aContext)373 void Joiner::HandleSecureCoapClientConnect(Dtls::Session::ConnectEvent aEvent, void *aContext)
374 {
375     static_cast<Joiner *>(aContext)->HandleSecureCoapClientConnect(aEvent);
376 }
377 
HandleSecureCoapClientConnect(Dtls::Session::ConnectEvent aEvent)378 void Joiner::HandleSecureCoapClientConnect(Dtls::Session::ConnectEvent aEvent)
379 {
380     VerifyOrExit(mState == kStateConnect);
381 
382     if (aEvent == Dtls::Session::kConnected)
383     {
384         SetState(kStateConnected);
385         SendJoinerFinalize();
386         mTimer.Start(kResponseTimeout);
387     }
388     else
389     {
390         TryNextJoinerRouter(kErrorSecurity);
391     }
392 
393 exit:
394     return;
395 }
396 
PrepareJoinerFinalizeMessage(const char * aProvisioningUrl,const char * aVendorName,const char * aVendorModel,const char * aVendorSwVersion,const char * aVendorData)397 Error Joiner::PrepareJoinerFinalizeMessage(const char *aProvisioningUrl,
398                                            const char *aVendorName,
399                                            const char *aVendorModel,
400                                            const char *aVendorSwVersion,
401                                            const char *aVendorData)
402 {
403     Error                 error = kErrorNone;
404     VendorStackVersionTlv vendorStackVersionTlv;
405 
406     mFinalizeMessage = Get<Tmf::SecureAgent>().NewPriorityConfirmablePostMessage(kUriJoinerFinalize);
407     VerifyOrExit(mFinalizeMessage != nullptr, error = kErrorNoBufs);
408 
409     mFinalizeMessage->SetOffset(mFinalizeMessage->GetLength());
410 
411     SuccessOrExit(error = Tlv::Append<StateTlv>(*mFinalizeMessage, StateTlv::kAccept));
412 
413     SuccessOrExit(error = Tlv::Append<VendorNameTlv>(*mFinalizeMessage, aVendorName));
414     SuccessOrExit(error = Tlv::Append<VendorModelTlv>(*mFinalizeMessage, aVendorModel));
415     SuccessOrExit(error = Tlv::Append<VendorSwVersionTlv>(*mFinalizeMessage, aVendorSwVersion));
416 
417     vendorStackVersionTlv.Init();
418     vendorStackVersionTlv.SetOui(OPENTHREAD_CONFIG_STACK_VENDOR_OUI);
419     vendorStackVersionTlv.SetMajor(OPENTHREAD_CONFIG_STACK_VERSION_MAJOR);
420     vendorStackVersionTlv.SetMinor(OPENTHREAD_CONFIG_STACK_VERSION_MINOR);
421     vendorStackVersionTlv.SetRevision(OPENTHREAD_CONFIG_STACK_VERSION_REV);
422     SuccessOrExit(error = vendorStackVersionTlv.AppendTo(*mFinalizeMessage));
423 
424     if (aVendorData != nullptr)
425     {
426         SuccessOrExit(error = Tlv::Append<VendorDataTlv>(*mFinalizeMessage, aVendorData));
427     }
428 
429     if (aProvisioningUrl != nullptr)
430     {
431         SuccessOrExit(error = Tlv::Append<ProvisioningUrlTlv>(*mFinalizeMessage, aProvisioningUrl));
432     }
433 
434 exit:
435     if (error != kErrorNone)
436     {
437         FreeJoinerFinalizeMessage();
438     }
439 
440     return error;
441 }
442 
FreeJoinerFinalizeMessage(void)443 void Joiner::FreeJoinerFinalizeMessage(void)
444 {
445     VerifyOrExit(mState == kStateIdle && mFinalizeMessage != nullptr);
446 
447     mFinalizeMessage->Free();
448     mFinalizeMessage = nullptr;
449 
450 exit:
451     return;
452 }
453 
SendJoinerFinalize(void)454 void Joiner::SendJoinerFinalize(void)
455 {
456     OT_ASSERT(mFinalizeMessage != nullptr);
457 
458 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
459     LogCertMessage("[THCI] direction=send | type=JOIN_FIN.req |", *mFinalizeMessage);
460 #endif
461 
462     SuccessOrExit(Get<Tmf::SecureAgent>().SendMessage(*mFinalizeMessage, Joiner::HandleJoinerFinalizeResponse, this));
463     mFinalizeMessage = nullptr;
464 
465     LogInfo("Sent %s", UriToString<kUriJoinerFinalize>());
466 
467 exit:
468     return;
469 }
470 
HandleJoinerFinalizeResponse(void * aContext,otMessage * aMessage,const otMessageInfo * aMessageInfo,otError aResult)471 void Joiner::HandleJoinerFinalizeResponse(void                *aContext,
472                                           otMessage           *aMessage,
473                                           const otMessageInfo *aMessageInfo,
474                                           otError              aResult)
475 {
476     static_cast<Joiner *>(aContext)->HandleJoinerFinalizeResponse(AsCoapMessagePtr(aMessage), &AsCoreType(aMessageInfo),
477                                                                   aResult);
478 }
479 
HandleJoinerFinalizeResponse(Coap::Message * aMessage,const Ip6::MessageInfo * aMessageInfo,Error aResult)480 void Joiner::HandleJoinerFinalizeResponse(Coap::Message *aMessage, const Ip6::MessageInfo *aMessageInfo, Error aResult)
481 {
482     OT_UNUSED_VARIABLE(aMessageInfo);
483 
484     uint8_t state;
485 
486     VerifyOrExit(mState == kStateConnected && aResult == kErrorNone);
487     OT_ASSERT(aMessage != nullptr);
488 
489     VerifyOrExit(aMessage->IsAck() && aMessage->GetCode() == Coap::kCodeChanged);
490 
491     SuccessOrExit(Tlv::Find<StateTlv>(*aMessage, state));
492 
493     SetState(kStateEntrust);
494     mTimer.Start(kResponseTimeout);
495 
496     LogInfo("Received %s %d", UriToString<kUriJoinerFinalize>(), state);
497 
498 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
499     LogCertMessage("[THCI] direction=recv | type=JOIN_FIN.rsp |", *aMessage);
500 #endif
501 
502 exit:
503     Get<Tmf::SecureAgent>().Disconnect();
504     IgnoreError(Get<Ip6::Filter>().RemoveUnsecurePort(kJoinerUdpPort));
505 }
506 
HandleTmf(Coap::Message & aMessage,const Ip6::MessageInfo & aMessageInfo)507 template <> void Joiner::HandleTmf<kUriJoinerEntrust>(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
508 {
509     Error         error;
510     Dataset::Info datasetInfo;
511 
512     VerifyOrExit(mState == kStateEntrust && aMessage.IsConfirmablePostRequest(), error = kErrorDrop);
513 
514     LogInfo("Received %s", UriToString<kUriJoinerEntrust>());
515     LogCert("[THCI] direction=recv | type=JOIN_ENT.ntf");
516 
517     datasetInfo.Clear();
518 
519     SuccessOrExit(error = Tlv::Find<NetworkKeyTlv>(aMessage, datasetInfo.Update<Dataset::kNetworkKey>()));
520 
521     datasetInfo.Set<Dataset::kChannel>(Get<Mac::Mac>().GetPanChannel());
522     datasetInfo.Set<Dataset::kPanId>(Get<Mac::Mac>().GetPanId());
523 
524     Get<ActiveDatasetManager>().SaveLocal(datasetInfo);
525 
526     LogInfo("Joiner successful!");
527 
528     SendJoinerEntrustResponse(aMessage, aMessageInfo);
529 
530     // Delay extended address configuration to allow DTLS wrap up.
531     mTimer.Start(kConfigExtAddressDelay);
532 
533 exit:
534     LogWarnOnError(error, "process joiner entrust");
535 }
536 
SendJoinerEntrustResponse(const Coap::Message & aRequest,const Ip6::MessageInfo & aRequestInfo)537 void Joiner::SendJoinerEntrustResponse(const Coap::Message &aRequest, const Ip6::MessageInfo &aRequestInfo)
538 {
539     Error            error = kErrorNone;
540     Coap::Message   *message;
541     Ip6::MessageInfo responseInfo(aRequestInfo);
542 
543     message = Get<Tmf::Agent>().NewPriorityResponseMessage(aRequest);
544     VerifyOrExit(message != nullptr, error = kErrorNoBufs);
545 
546     message->SetSubType(Message::kSubTypeJoinerEntrust);
547 
548     responseInfo.GetSockAddr().Clear();
549     SuccessOrExit(error = Get<Tmf::Agent>().SendMessage(*message, responseInfo));
550 
551     SetState(kStateJoined);
552 
553     LogInfo("Sent %s response", UriToString<kUriJoinerEntrust>());
554     LogCert("[THCI] direction=send | type=JOIN_ENT.rsp");
555 
556 exit:
557     FreeMessageOnError(message, error);
558 }
559 
HandleTimer(void)560 void Joiner::HandleTimer(void)
561 {
562     Error error = kErrorNone;
563 
564     switch (mState)
565     {
566     case kStateConnected:
567     case kStateEntrust:
568         error = kErrorResponseTimeout;
569         break;
570 
571     case kStateJoined:
572         Mac::ExtAddress extAddress;
573 
574         extAddress.GenerateRandom();
575         Get<Mac::Mac>().SetExtAddress(extAddress);
576         Get<Mle::MleRouter>().UpdateLinkLocalAddress();
577 
578         error = kErrorNone;
579         break;
580 
581     case kStateIdle:
582     case kStateDiscover:
583     case kStateConnect:
584         OT_ASSERT(false);
585     }
586 
587     Finish(error);
588 }
589 
590 // LCOV_EXCL_START
591 
StateToString(State aState)592 const char *Joiner::StateToString(State aState)
593 {
594     static const char *const kStateStrings[] = {
595         "Idle",       // (0) kStateIdle
596         "Discover",   // (1) kStateDiscover
597         "Connecting", // (2) kStateConnect
598         "Connected",  // (3) kStateConnected
599         "Entrust",    // (4) kStateEntrust
600         "Joined",     // (5) kStateJoined
601     };
602 
603     struct EnumCheck
604     {
605         InitEnumValidatorCounter();
606         ValidateNextEnum(kStateIdle);
607         ValidateNextEnum(kStateDiscover);
608         ValidateNextEnum(kStateConnect);
609         ValidateNextEnum(kStateConnected);
610         ValidateNextEnum(kStateEntrust);
611         ValidateNextEnum(kStateJoined);
612     };
613 
614     return kStateStrings[aState];
615 }
616 
617 // LCOV_EXCL_STOP
618 
619 } // namespace MeshCoP
620 } // namespace ot
621 
622 #endif // OPENTHREAD_CONFIG_JOINER_ENABLE
623