1 /*
2 * Copyright (c) 2023, 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 TCAT Agent service.
32 */
33
34 #include "tcat_agent.hpp"
35 #include <openthread/tcat.h>
36 #include "meshcop/network_name.hpp"
37
38 #if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE
39
40 #include "common/array.hpp"
41 #include "common/code_utils.hpp"
42 #include "common/debug.hpp"
43 #include "common/encoding.hpp"
44 #include "common/locator_getters.hpp"
45 #include "common/string.hpp"
46 #include "instance/instance.hpp"
47 #include "radio/radio.hpp"
48 #include "thread/thread_netif.hpp"
49 #include "thread/uri_paths.hpp"
50 #include "utils/otns.hpp"
51
52 namespace ot {
53 namespace MeshCoP {
54
55 RegisterLogModule("TcatAgent");
56
IsValid(void) const57 bool TcatAgent::VendorInfo::IsValid(void) const
58 {
59 return (mProvisioningUrl == nullptr ||
60 (IsValidUtf8String(mProvisioningUrl) &&
61 (static_cast<uint8_t>(StringLength(mProvisioningUrl, kProvisioningUrlMaxLength)) <
62 kProvisioningUrlMaxLength))) &&
63 mPskdString != nullptr;
64 }
65
TcatAgent(Instance & aInstance)66 TcatAgent::TcatAgent(Instance &aInstance)
67 : InstanceLocator(aInstance)
68 , mVendorInfo(nullptr)
69 , mCurrentApplicationProtocol(kApplicationProtocolNone)
70 , mState(kStateDisabled)
71 , mCommissionerHasNetworkName(false)
72 , mCommissionerHasDomainName(false)
73 , mCommissionerHasExtendedPanId(false)
74 {
75 mJoinerPskd.Clear();
76 mCurrentServiceName[0] = 0;
77 }
78
Start(AppDataReceiveCallback aAppDataReceiveCallback,JoinCallback aHandler,void * aContext)79 Error TcatAgent::Start(AppDataReceiveCallback aAppDataReceiveCallback, JoinCallback aHandler, void *aContext)
80 {
81 Error error = kErrorNone;
82
83 LogInfo("Starting");
84 VerifyOrExit(mVendorInfo != nullptr, error = kErrorFailed);
85 mAppDataReceiveCallback.Set(aAppDataReceiveCallback, aContext);
86 mJoinCallback.Set(aHandler, aContext);
87
88 mCurrentApplicationProtocol = kApplicationProtocolNone;
89 mState = kStateEnabled;
90
91 exit:
92 LogWarnOnError(error, "start TCAT agent");
93 return error;
94 }
95
Stop(void)96 void TcatAgent::Stop(void)
97 {
98 mCurrentApplicationProtocol = kApplicationProtocolNone;
99 mState = kStateDisabled;
100 mAppDataReceiveCallback.Clear();
101 mJoinCallback.Clear();
102 LogInfo("TCAT agent stopped");
103 }
104
SetTcatVendorInfo(const VendorInfo & aVendorInfo)105 Error TcatAgent::SetTcatVendorInfo(const VendorInfo &aVendorInfo)
106 {
107 Error error = kErrorNone;
108
109 VerifyOrExit(aVendorInfo.IsValid(), error = kErrorInvalidArgs);
110 SuccessOrExit(error = mJoinerPskd.SetFrom(aVendorInfo.mPskdString));
111 mVendorInfo = &aVendorInfo;
112
113 exit:
114 return error;
115 }
116
Connected(MeshCoP::SecureTransport & aTlsContext)117 Error TcatAgent::Connected(MeshCoP::SecureTransport &aTlsContext)
118 {
119 size_t len;
120 Error error;
121
122 VerifyOrExit(IsEnabled(), error = kErrorInvalidState);
123 len = sizeof(mCommissionerAuthorizationField);
124 SuccessOrExit(
125 error = aTlsContext.GetThreadAttributeFromPeerCertificate(
126 kCertificateAuthorizationField, reinterpret_cast<uint8_t *>(&mCommissionerAuthorizationField), &len));
127 VerifyOrExit(len == sizeof(mCommissionerAuthorizationField), error = kErrorParse);
128 VerifyOrExit((mCommissionerAuthorizationField.mHeader & kCommissionerFlag) == 1, error = kErrorParse);
129
130 len = sizeof(mDeviceAuthorizationField);
131 SuccessOrExit(error = aTlsContext.GetThreadAttributeFromOwnCertificate(
132 kCertificateAuthorizationField, reinterpret_cast<uint8_t *>(&mDeviceAuthorizationField), &len));
133 VerifyOrExit(len == sizeof(mDeviceAuthorizationField), error = kErrorParse);
134 VerifyOrExit((mDeviceAuthorizationField.mHeader & kCommissionerFlag) == 0, error = kErrorParse);
135
136 mCommissionerHasDomainName = false;
137 mCommissionerHasNetworkName = false;
138 mCommissionerHasExtendedPanId = false;
139
140 len = sizeof(mCommissionerDomainName) - 1;
141 if (aTlsContext.GetThreadAttributeFromPeerCertificate(
142 kCertificateDomainName, reinterpret_cast<uint8_t *>(&mCommissionerDomainName), &len) == kErrorNone)
143 {
144 mCommissionerDomainName.m8[len] = '\0';
145 mCommissionerHasDomainName = true;
146 }
147
148 len = sizeof(mCommissionerNetworkName) - 1;
149 if (aTlsContext.GetThreadAttributeFromPeerCertificate(
150 kCertificateNetworkName, reinterpret_cast<uint8_t *>(&mCommissionerNetworkName), &len) == kErrorNone)
151 {
152 mCommissionerNetworkName.m8[len] = '\0';
153 mCommissionerHasNetworkName = true;
154 }
155
156 len = sizeof(mCommissionerExtendedPanId);
157 if (aTlsContext.GetThreadAttributeFromPeerCertificate(
158 kCertificateExtendedPanId, reinterpret_cast<uint8_t *>(&mCommissionerExtendedPanId), &len) == kErrorNone)
159 {
160 if (len == sizeof(mCommissionerExtendedPanId))
161 {
162 mCommissionerHasExtendedPanId = true;
163 }
164 }
165
166 mCurrentApplicationProtocol = kApplicationProtocolNone;
167 mCurrentServiceName[0] = 0;
168 mState = kStateConnected;
169 LogInfo("TCAT agent connected");
170
171 exit:
172 return error;
173 }
174
Disconnected(void)175 void TcatAgent::Disconnected(void)
176 {
177 mCurrentApplicationProtocol = kApplicationProtocolNone;
178
179 if (mState != kStateDisabled)
180 {
181 mState = kStateEnabled;
182 }
183
184 LogInfo("TCAT agent disconnected");
185 }
186
CheckCommandClassAuthorizationFlags(CommandClassFlags aCommissionerCommandClassFlags,CommandClassFlags aDeviceCommandClassFlags,Dataset * aDataset) const187 bool TcatAgent::CheckCommandClassAuthorizationFlags(CommandClassFlags aCommissionerCommandClassFlags,
188 CommandClassFlags aDeviceCommandClassFlags,
189 Dataset *aDataset) const
190 {
191 bool authorized = false;
192 bool additionalDeviceRequirementMet = false;
193 bool domainNamesMatch = false;
194 bool networkNamesMatch = false;
195 bool extendedPanIdsMatch = false;
196
197 VerifyOrExit(IsConnected());
198 VerifyOrExit(aCommissionerCommandClassFlags & kAccessFlag);
199
200 if (aDeviceCommandClassFlags & kAccessFlag)
201 {
202 additionalDeviceRequirementMet = true;
203 }
204
205 if (aDeviceCommandClassFlags & kPskdFlag)
206 {
207 additionalDeviceRequirementMet = true;
208 }
209
210 if (aDeviceCommandClassFlags & kPskcFlag)
211 {
212 additionalDeviceRequirementMet = true;
213 }
214
215 if (mCommissionerHasNetworkName || mCommissionerHasExtendedPanId)
216 {
217 Dataset::Info datasetInfo;
218 Error datasetError = kErrorNone;
219
220 if (aDataset == nullptr)
221 {
222 datasetError = Get<ActiveDatasetManager>().Read(datasetInfo);
223 }
224 else
225 {
226 aDataset->ConvertTo(datasetInfo);
227 }
228
229 if (datasetError == kErrorNone)
230 {
231 if (datasetInfo.IsPresent<Dataset::kNetworkName>() && mCommissionerHasNetworkName &&
232 (datasetInfo.Get<Dataset::kNetworkName>() == mCommissionerNetworkName))
233 {
234 networkNamesMatch = true;
235 }
236
237 if (datasetInfo.IsPresent<Dataset::kExtendedPanId>() && mCommissionerHasExtendedPanId &&
238 (datasetInfo.Get<Dataset::kExtendedPanId>() == mCommissionerExtendedPanId))
239 {
240 extendedPanIdsMatch = true;
241 }
242 }
243 }
244
245 if (!networkNamesMatch)
246 {
247 VerifyOrExit((aCommissionerCommandClassFlags & kNetworkNameFlag) == 0);
248 }
249 else if (aDeviceCommandClassFlags & kNetworkNameFlag)
250 {
251 additionalDeviceRequirementMet = true;
252 }
253
254 if (!extendedPanIdsMatch)
255 {
256 VerifyOrExit((aCommissionerCommandClassFlags & kExtendedPanIdFlag) == 0);
257 }
258 else if (aDeviceCommandClassFlags & kExtendedPanIdFlag)
259 {
260 additionalDeviceRequirementMet = true;
261 }
262
263 #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
264 VerifyOrExit((aCommissionerCommandClassFlags & kThreadDomainFlag) == 0);
265 #endif
266
267 if (!domainNamesMatch)
268 {
269 VerifyOrExit((aCommissionerCommandClassFlags & kThreadDomainFlag) == 0);
270 }
271 else if (aDeviceCommandClassFlags & kThreadDomainFlag)
272 {
273 additionalDeviceRequirementMet = true;
274 }
275
276 if (additionalDeviceRequirementMet)
277 {
278 authorized = true;
279 }
280
281 exit:
282 return authorized;
283 }
284
IsCommandClassAuthorized(CommandClass aCommandClass) const285 bool TcatAgent::IsCommandClassAuthorized(CommandClass aCommandClass) const
286 {
287 bool authorized = false;
288
289 switch (aCommandClass)
290 {
291 case kGeneral:
292 authorized = true;
293 break;
294
295 case kCommissioning:
296 authorized = CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mCommissioningFlags,
297 mDeviceAuthorizationField.mCommissioningFlags, nullptr);
298 break;
299
300 case kExtraction:
301 authorized = CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mExtractionFlags,
302 mDeviceAuthorizationField.mExtractionFlags, nullptr);
303 break;
304
305 case kTlvDecommissioning:
306 authorized = CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mDecommissioningFlags,
307 mDeviceAuthorizationField.mDecommissioningFlags, nullptr);
308 break;
309
310 case kApplication:
311 authorized = CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mApplicationFlags,
312 mDeviceAuthorizationField.mApplicationFlags, nullptr);
313 break;
314
315 case kInvalid:
316 authorized = false;
317 break;
318 }
319
320 return authorized;
321 }
322
GetCommandClass(uint8_t aTlvType) const323 TcatAgent::CommandClass TcatAgent::GetCommandClass(uint8_t aTlvType) const
324 {
325 static constexpr int kGeneralTlvs = 0x1F;
326 static constexpr int kCommissioningTlvs = 0x3F;
327 static constexpr int kExtractionTlvs = 0x5F;
328 static constexpr int kTlvDecommissioningTlvs = 0x7F;
329 static constexpr int kApplicationTlvs = 0x9F;
330
331 if (aTlvType <= kGeneralTlvs)
332 {
333 return kGeneral;
334 }
335 else if (aTlvType <= kCommissioningTlvs)
336 {
337 return kCommissioning;
338 }
339 else if (aTlvType <= kExtractionTlvs)
340 {
341 return kExtraction;
342 }
343 else if (aTlvType <= kTlvDecommissioningTlvs)
344 {
345 return kTlvDecommissioning;
346 }
347 else if (aTlvType <= kApplicationTlvs)
348 {
349 return kApplication;
350 }
351 else
352 {
353 return kInvalid;
354 }
355 }
356
CanProcessTlv(uint8_t aTlvType) const357 bool TcatAgent::CanProcessTlv(uint8_t aTlvType) const
358 {
359 CommandClass tlvCommandClass = GetCommandClass(aTlvType);
360 return IsCommandClassAuthorized(tlvCommandClass);
361 }
362
HandleSingleTlv(const Message & aIncomingMessage,Message & aOutgoingMessage)363 Error TcatAgent::HandleSingleTlv(const Message &aIncomingMessage, Message &aOutgoingMessage)
364 {
365 Error error = kErrorParse;
366 ot::Tlv tlv;
367 uint16_t offset = aIncomingMessage.GetOffset();
368 uint16_t length;
369 bool response = false;
370
371 VerifyOrExit(IsConnected(), error = kErrorInvalidState);
372 SuccessOrExit(error = aIncomingMessage.Read(offset, tlv));
373
374 if (tlv.IsExtended())
375 {
376 ot::ExtendedTlv extTlv;
377 SuccessOrExit(error = aIncomingMessage.Read(offset, extTlv));
378 length = extTlv.GetLength();
379 offset += sizeof(ot::ExtendedTlv);
380 }
381 else
382 {
383 length = tlv.GetLength();
384 offset += sizeof(ot::Tlv);
385 }
386
387 if (!CanProcessTlv(tlv.GetType()))
388 {
389 error = kErrorRejected;
390 }
391 else
392 {
393 switch (tlv.GetType())
394 {
395 case kTlvDisconnect:
396 error = kErrorAbort;
397 response = true; // true - to avoid response-with-status being sent.
398 break;
399
400 case kTlvSetActiveOperationalDataset:
401 error = HandleSetActiveOperationalDataset(aIncomingMessage, offset, length);
402 break;
403
404 case kTlvStartThreadInterface:
405 error = HandleStartThreadInterface();
406 break;
407
408 case kTlvStopThreadInterface:
409 error = otThreadSetEnabled(&GetInstance(), false);
410 break;
411
412 case kTlvSendApplicationData:
413 LogInfo("Application data len:%d, offset:%d", length, offset);
414 mAppDataReceiveCallback.InvokeIfSet(&GetInstance(), &aIncomingMessage, offset,
415 MapEnum(mCurrentApplicationProtocol), mCurrentServiceName);
416 response = true;
417 error = kErrorNone;
418 break;
419
420 case kTlvDecommission:
421 error = HandleDecomission();
422 break;
423
424 case kTlvPing:
425 error = HandlePing(aIncomingMessage, aOutgoingMessage, offset, length, response);
426 break;
427 case kTlvGetNetworkName:
428 error = HandleGetNetworkName(aOutgoingMessage, response);
429 break;
430 case kTlvGetDeviceId:
431 error = HandleGetDeviceId(aOutgoingMessage, response);
432 break;
433 case kTlvGetExtendedPanID:
434 error = HandleGetExtPanId(aOutgoingMessage, response);
435 break;
436 case kTlvGetProvisioningURL:
437 error = HandleGetProvisioningUrl(aOutgoingMessage, response);
438 break;
439
440 default:
441 error = kErrorInvalidCommand;
442 }
443 }
444 if (!response)
445 {
446 StatusCode statusCode;
447
448 switch (error)
449 {
450 case kErrorNone:
451 statusCode = kStatusSuccess;
452 break;
453
454 case kErrorInvalidState:
455 statusCode = kStatusUndefined;
456 break;
457
458 case kErrorParse:
459 statusCode = kStatusParseError;
460 break;
461
462 case kErrorInvalidCommand:
463 statusCode = kStatusUnsupported;
464 break;
465
466 case kErrorRejected:
467 statusCode = kStatusUnauthorized;
468 break;
469
470 case kErrorNotImplemented:
471 statusCode = kStatusUnsupported;
472 break;
473
474 default:
475 statusCode = kStatusGeneralError;
476 break;
477 }
478
479 SuccessOrExit(error = ot::Tlv::Append<ResponseWithStatusTlv>(aOutgoingMessage, statusCode));
480 }
481
482 exit:
483 return error;
484 }
485
HandleSetActiveOperationalDataset(const Message & aIncomingMessage,uint16_t aOffset,uint16_t aLength)486 Error TcatAgent::HandleSetActiveOperationalDataset(const Message &aIncomingMessage, uint16_t aOffset, uint16_t aLength)
487 {
488 Dataset dataset;
489 OffsetRange offsetRange;
490 Error error;
491
492 offsetRange.Init(aOffset, aLength);
493 SuccessOrExit(error = dataset.SetFrom(aIncomingMessage, offsetRange));
494 SuccessOrExit(error = dataset.ValidateTlvs());
495
496 if (!CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mApplicationFlags,
497 mDeviceAuthorizationField.mApplicationFlags, &dataset))
498 {
499 error = kErrorRejected;
500 ExitNow();
501 }
502
503 Get<ActiveDatasetManager>().SaveLocal(dataset);
504
505 exit:
506 return error;
507 }
508
HandleDecomission(void)509 Error TcatAgent::HandleDecomission(void)
510 {
511 Error error = kErrorNone;
512
513 IgnoreReturnValue(otThreadSetEnabled(&GetInstance(), false));
514 Get<ActiveDatasetManager>().Clear();
515 Get<PendingDatasetManager>().Clear();
516
517 error = Get<Instance>().ErasePersistentInfo();
518
519 #if !OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
520 {
521 NetworkKey networkKey;
522 networkKey.Clear();
523 Get<KeyManager>().SetNetworkKey(networkKey);
524 }
525 #endif
526
527 return error;
528 }
529
HandlePing(const Message & aIncomingMessage,Message & aOutgoingMessage,uint16_t aOffset,uint16_t aLength,bool & response)530 Error TcatAgent::HandlePing(const Message &aIncomingMessage,
531 Message &aOutgoingMessage,
532 uint16_t aOffset,
533 uint16_t aLength,
534 bool &response)
535 {
536 Error error = kErrorNone;
537 ot::ExtendedTlv extTlv;
538 ot::Tlv tlv;
539
540 VerifyOrExit(aLength <= kPingPayloadMaxLength, error = kErrorParse);
541 if (aLength > ot::Tlv::kBaseTlvMaxLength)
542 {
543 extTlv.SetType(kTlvResponseWithPayload);
544 extTlv.SetLength(aLength);
545 SuccessOrExit(error = aOutgoingMessage.Append(extTlv));
546 }
547 else
548 {
549 tlv.SetType(kTlvResponseWithPayload);
550 tlv.SetLength(static_cast<uint8_t>(aLength));
551 SuccessOrExit(error = aOutgoingMessage.Append(tlv));
552 }
553
554 SuccessOrExit(error = aOutgoingMessage.AppendBytesFromMessage(aIncomingMessage, aOffset, aLength));
555 response = true;
556
557 exit:
558 return error;
559 }
560
HandleGetNetworkName(Message & aOutgoingMessage,bool & response)561 Error TcatAgent::HandleGetNetworkName(Message &aOutgoingMessage, bool &response)
562 {
563 Error error = kErrorNone;
564 MeshCoP::NameData nameData = Get<MeshCoP::NetworkNameManager>().GetNetworkName().GetAsData();
565
566 VerifyOrExit(Get<ActiveDatasetManager>().IsCommissioned(), error = kErrorInvalidState);
567 #if !OPENTHREAD_CONFIG_ALLOW_EMPTY_NETWORK_NAME
568 VerifyOrExit(nameData.GetLength() > 0, error = kErrorInvalidState);
569 #endif
570
571 SuccessOrExit(
572 error = Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload, nameData.GetBuffer(), nameData.GetLength()));
573 response = true;
574
575 exit:
576 return error;
577 }
578
HandleGetDeviceId(Message & aOutgoingMessage,bool & response)579 Error TcatAgent::HandleGetDeviceId(Message &aOutgoingMessage, bool &response)
580 {
581 const uint8_t *deviceId;
582 uint16_t length = 0;
583 Mac::ExtAddress eui64;
584 Error error = kErrorNone;
585
586 if (mVendorInfo->mGeneralDeviceId != nullptr)
587 {
588 length = mVendorInfo->mGeneralDeviceId->mDeviceIdLen;
589 deviceId = mVendorInfo->mGeneralDeviceId->mDeviceId;
590 }
591
592 if (length == 0)
593 {
594 Get<Radio>().GetIeeeEui64(eui64);
595
596 length = sizeof(Mac::ExtAddress);
597 deviceId = eui64.m8;
598 }
599
600 SuccessOrExit(error = Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload, deviceId, length));
601
602 response = true;
603
604 exit:
605 return error;
606 }
607
HandleGetExtPanId(Message & aOutgoingMessage,bool & response)608 Error TcatAgent::HandleGetExtPanId(Message &aOutgoingMessage, bool &response)
609 {
610 Error error;
611
612 VerifyOrExit(Get<ActiveDatasetManager>().IsCommissioned(), error = kErrorInvalidState);
613
614 SuccessOrExit(error = Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload,
615 &Get<MeshCoP::ExtendedPanIdManager>().GetExtPanId(), sizeof(ExtendedPanId)));
616 response = true;
617
618 exit:
619 return error;
620 }
621
HandleGetProvisioningUrl(Message & aOutgoingMessage,bool & response)622 Error TcatAgent::HandleGetProvisioningUrl(Message &aOutgoingMessage, bool &response)
623 {
624 Error error = kErrorNone;
625 uint16_t length;
626
627 VerifyOrExit(mVendorInfo->mProvisioningUrl != nullptr, error = kErrorInvalidState);
628
629 length = StringLength(mVendorInfo->mProvisioningUrl, kProvisioningUrlMaxLength);
630 VerifyOrExit(length > 0 && length <= Tlv::kBaseTlvMaxLength, error = kErrorInvalidState);
631
632 error = Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload, mVendorInfo->mProvisioningUrl, length);
633 response = true;
634
635 exit:
636 return error;
637 }
638
HandleStartThreadInterface(void)639 Error TcatAgent::HandleStartThreadInterface(void)
640 {
641 Error error;
642 Dataset::Info datasetInfo;
643
644 VerifyOrExit(Get<ActiveDatasetManager>().Read(datasetInfo) == kErrorNone, error = kErrorInvalidState);
645 VerifyOrExit(datasetInfo.IsPresent<Dataset::kNetworkKey>(), error = kErrorInvalidState);
646
647 #if OPENTHREAD_CONFIG_LINK_RAW_ENABLE
648 VerifyOrExit(!Get<Mac::LinkRaw>().IsEnabled(), error = kErrorInvalidState);
649 #endif
650
651 Get<ThreadNetif>().Up();
652 error = Get<Mle::MleRouter>().Start();
653
654 exit:
655 return error;
656 }
657
SeralizeTcatAdvertisementTlv(uint8_t * aBuffer,uint16_t & aOffset,TcatAdvertisementTlvType aType,uint16_t aLength,const uint8_t * aValue)658 void SeralizeTcatAdvertisementTlv(uint8_t *aBuffer,
659 uint16_t &aOffset,
660 TcatAdvertisementTlvType aType,
661 uint16_t aLength,
662 const uint8_t *aValue)
663 {
664 aBuffer[aOffset++] = static_cast<uint8_t>(aType << 4 | (aLength & 0xf));
665 memcpy(aBuffer + aOffset, aValue, aLength);
666 aOffset += aLength;
667 }
668
GetAdvertisementData(uint16_t & aLen,uint8_t * aAdvertisementData)669 Error TcatAgent::GetAdvertisementData(uint16_t &aLen, uint8_t *aAdvertisementData)
670 {
671 Error error = kErrorNone;
672 DeviceTypeAndStatus tas;
673 otBleLinkCapabilities caps;
674
675 VerifyOrExit(mVendorInfo != nullptr && aAdvertisementData != nullptr, error = kErrorInvalidArgs);
676
677 aLen = 0;
678
679 LittleEndian::WriteUint16(OT_TOBLE_SERVICE_UUID, aAdvertisementData);
680 aLen += sizeof(uint16_t);
681 aAdvertisementData[2] = OPENTHREAD_CONFIG_THREAD_VERSION << 4 | OT_TCAT_OPCODE;
682 aLen++;
683
684 if (mVendorInfo->mAdvertisedDeviceIds != nullptr)
685 {
686 for (uint8_t i = 0; mVendorInfo->mAdvertisedDeviceIds[i].mDeviceIdType != OT_TCAT_DEVICE_ID_EMPTY; i++)
687 {
688 switch (MapEnum(mVendorInfo->mAdvertisedDeviceIds[i].mDeviceIdType))
689 {
690 case kTcatDeviceIdOui24:
691 SeralizeTcatAdvertisementTlv(aAdvertisementData, aLen, kTlvVendorOui24,
692 mVendorInfo->mAdvertisedDeviceIds[i].mDeviceIdLen,
693 mVendorInfo->mAdvertisedDeviceIds[i].mDeviceId);
694 break;
695 case kTcatDeviceIdOui36:
696 SeralizeTcatAdvertisementTlv(aAdvertisementData, aLen, kTlvVendorOui36,
697 mVendorInfo->mAdvertisedDeviceIds[i].mDeviceIdLen,
698 mVendorInfo->mAdvertisedDeviceIds[i].mDeviceId);
699 break;
700 case kTcatDeviceIdDiscriminator:
701 SeralizeTcatAdvertisementTlv(aAdvertisementData, aLen, kTlvDeviceDiscriminator,
702 mVendorInfo->mAdvertisedDeviceIds[i].mDeviceIdLen,
703 mVendorInfo->mAdvertisedDeviceIds[i].mDeviceId);
704 break;
705 case kTcatDeviceIdIanaPen:
706 SeralizeTcatAdvertisementTlv(aAdvertisementData, aLen, kTlvVendorIanaPen,
707 mVendorInfo->mAdvertisedDeviceIds[i].mDeviceIdLen,
708 mVendorInfo->mAdvertisedDeviceIds[i].mDeviceId);
709 break;
710 default:
711 break;
712 }
713 }
714 }
715
716 otPlatBleGetLinkCapabilities(&GetInstance(), &caps);
717
718 if (caps.mGattNotifications || caps.mL2CapDirect)
719 {
720 SeralizeTcatAdvertisementTlv(aAdvertisementData, aLen, kTlvBleLinkCapabilities, kTlvBleLinkCapabilitiesLength,
721 reinterpret_cast<uint8_t *>(&caps));
722 }
723
724 tas.mRsv = 0;
725 tas.mMultiradioSupport = otPlatBleSupportsMultiRadio(&GetInstance());
726 tas.mIsCommisionned = Get<ActiveDatasetManager>().IsCommissioned();
727 tas.mThreadNetworkActive = Get<Mle::Mle>().IsAttached();
728 tas.mDeviceType = Get<Mle::Mle>().GetDeviceMode().IsFullThreadDevice();
729 tas.mRxOnWhenIdle = Get<Mle::Mle>().GetDeviceMode().IsRxOnWhenIdle();
730
731 #if OPENTHREAD_FTD && (OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE || OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE || \
732 OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE)
733 tas.mIsBorderRouter = true;
734 #else
735 tas.mIsBorderRouter = false;
736 #endif
737
738 SeralizeTcatAdvertisementTlv(aAdvertisementData, aLen, kTlvDeviceTypeAndStatus, kTlvDeviceTypeAndStatusLength,
739 reinterpret_cast<uint8_t *>(&tas));
740 OT_ASSERT(aLen <= OT_TCAT_ADVERTISEMENT_MAX_LEN);
741
742 exit:
743 return error;
744 }
745
746 } // namespace MeshCoP
747 } // namespace ot
748
749 #endif // OPENTHREAD_CONFIG_BLE_TCAT_ENABLE
750