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