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 break;
398
399 case kTlvSetActiveOperationalDataset:
400 error = HandleSetActiveOperationalDataset(aIncomingMessage, offset, length);
401 break;
402
403 case kTlvStartThreadInterface:
404 error = HandleStartThreadInterface();
405 break;
406
407 case kTlvStopThreadInterface:
408 error = otThreadSetEnabled(&GetInstance(), false);
409 break;
410
411 case kTlvSendApplicationData:
412 LogInfo("Application data len:%d, offset:%d", length, offset);
413 mAppDataReceiveCallback.InvokeIfSet(&GetInstance(), &aIncomingMessage, offset,
414 MapEnum(mCurrentApplicationProtocol), mCurrentServiceName);
415 response = true;
416 error = kErrorNone;
417 break;
418 case kTlvDecommission:
419 error = HandleDecomission();
420 break;
421 case kTlvPing:
422 error = HandlePing(aIncomingMessage, aOutgoingMessage, offset, length, response);
423 break;
424 case kTlvGetNetworkName:
425 error = HandleGetNetworkName(aOutgoingMessage, response);
426 break;
427 case kTlvGetDeviceId:
428 error = HandleGetDeviceId(aOutgoingMessage, response);
429 break;
430 case kTlvGetExtendedPanID:
431 error = HandleGetExtPanId(aOutgoingMessage, response);
432 break;
433 case kTlvGetProvisioningURL:
434 error = HandleGetProvisioningUrl(aOutgoingMessage, response);
435 break;
436 default:
437 error = kErrorInvalidCommand;
438 }
439 }
440 if (!response)
441 {
442 StatusCode statusCode;
443
444 switch (error)
445 {
446 case kErrorNone:
447 statusCode = kStatusSuccess;
448 break;
449
450 case kErrorInvalidState:
451 statusCode = kStatusUndefined;
452 break;
453
454 case kErrorParse:
455 statusCode = kStatusParseError;
456 break;
457
458 case kErrorInvalidCommand:
459 statusCode = kStatusUnsupported;
460 break;
461
462 case kErrorRejected:
463 statusCode = kStatusUnauthorized;
464 break;
465
466 case kErrorNotImplemented:
467 statusCode = kStatusUnsupported;
468 break;
469
470 default:
471 statusCode = kStatusGeneralError;
472 break;
473 }
474
475 SuccessOrExit(error = ot::Tlv::Append<ResponseWithStatusTlv>(aOutgoingMessage, statusCode));
476 }
477
478 exit:
479 return error;
480 }
481
HandleSetActiveOperationalDataset(const Message & aIncomingMessage,uint16_t aOffset,uint16_t aLength)482 Error TcatAgent::HandleSetActiveOperationalDataset(const Message &aIncomingMessage, uint16_t aOffset, uint16_t aLength)
483 {
484 Dataset dataset;
485 OffsetRange offsetRange;
486 Error error;
487
488 offsetRange.Init(aOffset, aLength);
489 SuccessOrExit(error = dataset.SetFrom(aIncomingMessage, offsetRange));
490 SuccessOrExit(error = dataset.ValidateTlvs());
491
492 if (!CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mApplicationFlags,
493 mDeviceAuthorizationField.mApplicationFlags, &dataset))
494 {
495 error = kErrorRejected;
496 ExitNow();
497 }
498
499 Get<ActiveDatasetManager>().SaveLocal(dataset);
500
501 exit:
502 return error;
503 }
504
HandleDecomission(void)505 Error TcatAgent::HandleDecomission(void)
506 {
507 Error error = kErrorNone;
508
509 IgnoreReturnValue(otThreadSetEnabled(&GetInstance(), false));
510 Get<ActiveDatasetManager>().Clear();
511 Get<PendingDatasetManager>().Clear();
512
513 error = Get<Instance>().ErasePersistentInfo();
514
515 #if !OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
516 {
517 NetworkKey networkKey;
518 networkKey.Clear();
519 Get<KeyManager>().SetNetworkKey(networkKey);
520 }
521 #endif
522
523 return error;
524 }
525
HandlePing(const Message & aIncomingMessage,Message & aOutgoingMessage,uint16_t aOffset,uint16_t aLength,bool & response)526 Error TcatAgent::HandlePing(const Message &aIncomingMessage,
527 Message &aOutgoingMessage,
528 uint16_t aOffset,
529 uint16_t aLength,
530 bool &response)
531 {
532 Error error = kErrorNone;
533 ot::ExtendedTlv extTlv;
534 ot::Tlv tlv;
535
536 VerifyOrExit(aLength <= kPingPayloadMaxLength, error = kErrorParse);
537 if (aLength > ot::Tlv::kBaseTlvMaxLength)
538 {
539 extTlv.SetType(kTlvResponseWithPayload);
540 extTlv.SetLength(aLength);
541 SuccessOrExit(error = aOutgoingMessage.Append(extTlv));
542 }
543 else
544 {
545 tlv.SetType(kTlvResponseWithPayload);
546 tlv.SetLength(static_cast<uint8_t>(aLength));
547 SuccessOrExit(error = aOutgoingMessage.Append(tlv));
548 }
549
550 SuccessOrExit(error = aOutgoingMessage.AppendBytesFromMessage(aIncomingMessage, aOffset, aLength));
551 response = true;
552
553 exit:
554 return error;
555 }
556
HandleGetNetworkName(Message & aOutgoingMessage,bool & response)557 Error TcatAgent::HandleGetNetworkName(Message &aOutgoingMessage, bool &response)
558 {
559 Error error = kErrorNone;
560 MeshCoP::NameData nameData = Get<MeshCoP::NetworkNameManager>().GetNetworkName().GetAsData();
561
562 VerifyOrExit(Get<ActiveDatasetManager>().IsCommissioned(), error = kErrorInvalidState);
563 #if !OPENTHREAD_CONFIG_ALLOW_EMPTY_NETWORK_NAME
564 VerifyOrExit(nameData.GetLength() > 0, error = kErrorInvalidState);
565 #endif
566
567 SuccessOrExit(
568 error = Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload, nameData.GetBuffer(), nameData.GetLength()));
569 response = true;
570
571 exit:
572 return error;
573 }
574
HandleGetDeviceId(Message & aOutgoingMessage,bool & response)575 Error TcatAgent::HandleGetDeviceId(Message &aOutgoingMessage, bool &response)
576 {
577 const uint8_t *deviceId;
578 uint16_t length = 0;
579 Mac::ExtAddress eui64;
580 Error error = kErrorNone;
581
582 if (mVendorInfo->mGeneralDeviceId != nullptr)
583 {
584 length = mVendorInfo->mGeneralDeviceId->mDeviceIdLen;
585 deviceId = mVendorInfo->mGeneralDeviceId->mDeviceId;
586 }
587
588 if (length == 0)
589 {
590 Get<Radio>().GetIeeeEui64(eui64);
591
592 length = sizeof(Mac::ExtAddress);
593 deviceId = eui64.m8;
594 }
595
596 SuccessOrExit(error = Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload, deviceId, length));
597
598 response = true;
599
600 exit:
601 return error;
602 }
603
HandleGetExtPanId(Message & aOutgoingMessage,bool & response)604 Error TcatAgent::HandleGetExtPanId(Message &aOutgoingMessage, bool &response)
605 {
606 Error error;
607
608 VerifyOrExit(Get<ActiveDatasetManager>().IsCommissioned(), error = kErrorInvalidState);
609
610 SuccessOrExit(error = Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload,
611 &Get<MeshCoP::ExtendedPanIdManager>().GetExtPanId(), sizeof(ExtendedPanId)));
612 response = true;
613
614 exit:
615 return error;
616 }
617
HandleGetProvisioningUrl(Message & aOutgoingMessage,bool & response)618 Error TcatAgent::HandleGetProvisioningUrl(Message &aOutgoingMessage, bool &response)
619 {
620 Error error = kErrorNone;
621 uint16_t length;
622
623 VerifyOrExit(mVendorInfo->mProvisioningUrl != nullptr, error = kErrorInvalidState);
624
625 length = StringLength(mVendorInfo->mProvisioningUrl, kProvisioningUrlMaxLength);
626 VerifyOrExit(length > 0 && length <= Tlv::kBaseTlvMaxLength, error = kErrorInvalidState);
627
628 error = Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload, mVendorInfo->mProvisioningUrl, length);
629 response = true;
630
631 exit:
632 return error;
633 }
634
HandleStartThreadInterface(void)635 Error TcatAgent::HandleStartThreadInterface(void)
636 {
637 Error error;
638 Dataset::Info datasetInfo;
639
640 VerifyOrExit(Get<ActiveDatasetManager>().Read(datasetInfo) == kErrorNone, error = kErrorInvalidState);
641 VerifyOrExit(datasetInfo.IsPresent<Dataset::kNetworkKey>(), error = kErrorInvalidState);
642
643 #if OPENTHREAD_CONFIG_LINK_RAW_ENABLE
644 VerifyOrExit(!Get<Mac::LinkRaw>().IsEnabled(), error = kErrorInvalidState);
645 #endif
646
647 Get<ThreadNetif>().Up();
648 error = Get<Mle::MleRouter>().Start();
649
650 exit:
651 return error;
652 }
653
SeralizeTcatAdvertisementTlv(uint8_t * aBuffer,uint16_t & aOffset,TcatAdvertisementTlvType aType,uint16_t aLength,const uint8_t * aValue)654 void SeralizeTcatAdvertisementTlv(uint8_t *aBuffer,
655 uint16_t &aOffset,
656 TcatAdvertisementTlvType aType,
657 uint16_t aLength,
658 const uint8_t *aValue)
659 {
660 aBuffer[aOffset++] = static_cast<uint8_t>(aType << 4 | (aLength & 0xf));
661 memcpy(aBuffer + aOffset, aValue, aLength);
662 aOffset += aLength;
663 }
664
GetAdvertisementData(uint16_t & aLen,uint8_t * aAdvertisementData)665 Error TcatAgent::GetAdvertisementData(uint16_t &aLen, uint8_t *aAdvertisementData)
666 {
667 Error error = kErrorNone;
668 DeviceTypeAndStatus tas;
669 otBleLinkCapabilities caps;
670
671 VerifyOrExit(mVendorInfo != nullptr && aAdvertisementData != nullptr, error = kErrorInvalidArgs);
672
673 aLen = 0;
674
675 LittleEndian::WriteUint16(OT_TOBLE_SERVICE_UUID, aAdvertisementData);
676 aLen += sizeof(uint16_t);
677 aAdvertisementData[2] = OPENTHREAD_CONFIG_THREAD_VERSION << 4 | OT_TCAT_OPCODE;
678 aLen++;
679
680 if (mVendorInfo->mAdvertisedDeviceIds != nullptr)
681 {
682 for (uint8_t i = 0; mVendorInfo->mAdvertisedDeviceIds[i].mDeviceIdType != OT_TCAT_DEVICE_ID_EMPTY; i++)
683 {
684 switch (MapEnum(mVendorInfo->mAdvertisedDeviceIds[i].mDeviceIdType))
685 {
686 case kTcatDeviceIdOui24:
687 SeralizeTcatAdvertisementTlv(aAdvertisementData, aLen, kTlvVendorOui24,
688 mVendorInfo->mAdvertisedDeviceIds[i].mDeviceIdLen,
689 mVendorInfo->mAdvertisedDeviceIds[i].mDeviceId);
690 break;
691 case kTcatDeviceIdOui36:
692 SeralizeTcatAdvertisementTlv(aAdvertisementData, aLen, kTlvVendorOui36,
693 mVendorInfo->mAdvertisedDeviceIds[i].mDeviceIdLen,
694 mVendorInfo->mAdvertisedDeviceIds[i].mDeviceId);
695 break;
696 case kTcatDeviceIdDiscriminator:
697 SeralizeTcatAdvertisementTlv(aAdvertisementData, aLen, kTlvDeviceDiscriminator,
698 mVendorInfo->mAdvertisedDeviceIds[i].mDeviceIdLen,
699 mVendorInfo->mAdvertisedDeviceIds[i].mDeviceId);
700 break;
701 case kTcatDeviceIdIanaPen:
702 SeralizeTcatAdvertisementTlv(aAdvertisementData, aLen, kTlvVendorIanaPen,
703 mVendorInfo->mAdvertisedDeviceIds[i].mDeviceIdLen,
704 mVendorInfo->mAdvertisedDeviceIds[i].mDeviceId);
705 break;
706 default:
707 break;
708 }
709 }
710 }
711
712 otPlatBleGetLinkCapabilities(&GetInstance(), &caps);
713
714 if (caps.mGattNotifications || caps.mL2CapDirect)
715 {
716 SeralizeTcatAdvertisementTlv(aAdvertisementData, aLen, kTlvBleLinkCapabilities, kTlvBleLinkCapabilitiesLength,
717 reinterpret_cast<uint8_t *>(&caps));
718 }
719
720 tas.mRsv = 0;
721 tas.mMultiradioSupport = otPlatBleSupportsMultiRadio(&GetInstance());
722 tas.mIsCommisionned = Get<ActiveDatasetManager>().IsCommissioned();
723 tas.mThreadNetworkActive = Get<Mle::Mle>().IsAttached();
724 tas.mDeviceType = Get<Mle::Mle>().GetDeviceMode().IsFullThreadDevice();
725 tas.mRxOnWhenIdle = Get<Mle::Mle>().GetDeviceMode().IsRxOnWhenIdle();
726
727 #if OPENTHREAD_FTD && (OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE || OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE || \
728 OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE)
729 tas.mIsBorderRouter = true;
730 #else
731 tas.mIsBorderRouter = false;
732 #endif
733
734 SeralizeTcatAdvertisementTlv(aAdvertisementData, aLen, kTlvDeviceTypeAndStatus, kTlvDeviceTypeAndStatusLength,
735 reinterpret_cast<uint8_t *>(&tas));
736 OT_ASSERT(aLen <= OT_TCAT_ADVERTISEMENT_MAX_LEN);
737
738 exit:
739 return error;
740 }
741
742 } // namespace MeshCoP
743 } // namespace ot
744
745 #endif // OPENTHREAD_CONFIG_BLE_TCAT_ENABLE
746