[tcat] implement vendor policy for TLV support and automatic advertisement activation/deactivation (#13038)

This commit implements additional vendor application or ecosystem
policy settings for TCAT including:

1) Automatic deactivation of the TCAT agent / TCAT advertisement after
   the thread network has been started over TCAT
2) Automatic activation of the TCAT agent / TCAT advertisement after
   the thread network has been stopped over TCAT
3) Automatic activation of the TCAT agent / TCAT advertisement after
   decommissioning over TCAT
4) Blocking support of certain TCAT TLVs by the application /
   ecosystem

The commit also fixes an issue with certificate storage after
decommissioning.
This commit is contained in:
arnulfrupp
2026-05-28 22:02:29 -07:00
committed by GitHub
parent 2a2d4be953
commit fa3213ec85
7 changed files with 226 additions and 79 deletions
+3
View File
@@ -193,6 +193,9 @@ void Notifier::EmitEvents(void)
#if OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE
Get<Utils::LinkMetricsManager>().HandleNotifierEvents(events);
#endif
#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE
Get<MeshCoP::TcatAgent>().HandleNotifierEvents(events);
#endif
for (ExternalCallback &callback : mExternalCallbacks)
{
+57 -7
View File
@@ -61,6 +61,7 @@ TcatAgent::TcatAgent(Instance &aInstance)
, mActiveOrStandbyTimer(aInstance)
, mTcatActiveDurationMs(0)
, mHashVerificationAttempts(1)
, mLastDeviceRole(Mle::kRoleDisabled)
{
ClearCommissionerState();
mLastHashVerificationTimestamp = Get<UptimeTracker>().GetUptimeInSeconds();
@@ -95,8 +96,16 @@ Error TcatAgent::Start(AppDataReceiveCallback aAppDataReceiveCallback, JoinCallb
mState = kStateActive;
mNextState = kStateActive;
mTcatActiveDurationMs = 0;
mLastDeviceRole = Get<Mle::Mle>().GetRole();
LogInfo("Start");
if (!mVendorInfo->mKeepActiveAfterJoining && mLastDeviceRole != Mle::kRoleDisabled &&
Get<ActiveDatasetManager>().IsCommissioned())
{
IgnoreError(Standby());
}
exit:
LogWarnOnError(error, "Start");
return error;
@@ -400,10 +409,19 @@ Error TcatAgent::HandleSingleTlv(const Message &aIncomingMessage, Message &aOutg
const uint16_t initialOutgoingMsgLength = aOutgoingMessage.GetLength();
bool response = false;
VerifyOrExit(mVendorInfo != nullptr, error = kErrorInvalidState);
VerifyOrExit(IsConnected(), error = kErrorInvalidState);
SuccessOrExit(error = tlvInfo.ParseFrom(aIncomingMessage, aIncomingMessage.GetOffset()));
if (mVendorInfo->mTlvSupportHandler != nullptr &&
!mVendorInfo->mTlvSupportHandler(&GetInstance(), static_cast<otTcatCommandTlvType>(tlvInfo.GetType()),
mJoinCallback.GetContext()))
{
statusCode = kStatusUnsupported;
ExitNow();
}
switch (tlvInfo.GetType())
{
case kTlvDisconnect:
@@ -665,13 +683,19 @@ Error TcatAgent::HandleDecommission(void)
VerifyOrExit(IsCommandClassAuthorized(kDecommissioning), error = kErrorRejected);
SuccessOrExit(error = Get<Ble::BleSecure>().GetPeerCertificateDer(buf, &bufLen, bufLen));
Get<Settings>().SaveTcatCommissionerCertificate(buf, static_cast<uint16_t>(bufLen));
IgnoreReturnValue(otThreadSetEnabled(&GetInstance(), false));
Get<Mle::Mle>().Stop();
if (!mVendorInfo->mDoNotActivateAfterLeaving)
{
IgnoreError(Activate(0, 0));
}
Get<ActiveDatasetManager>().Clear();
Get<PendingDatasetManager>().Clear();
IgnoreReturnValue(Get<Instance>().ErasePersistentInfo());
Get<Settings>().SaveTcatCommissionerCertificate(buf, static_cast<uint16_t>(bufLen));
#if !OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
{
@@ -681,6 +705,7 @@ Error TcatAgent::HandleDecommission(void)
}
#endif
mJoinCallback.InvokeIfSet(&GetInstance(), /* aIsJoin */ false, error);
mIsCommissioned = false; // enable repeated commissioning/decommissioning in a session
exit:
@@ -979,24 +1004,29 @@ Error TcatAgent::HandleStartThreadInterface(void)
#endif
Get<ThreadNetif>().Up();
error = Get<Mle::Mle>().Start();
SuccessOrExit(error = Get<Mle::Mle>().Start());
exit:
// error values for callback MUST be limited to the allowed set, see #JoinCallback
mJoinCallback.InvokeIfSet(error);
mJoinCallback.InvokeIfSet(&GetInstance(), /* aIsJoin */ true, error);
return error;
}
Error TcatAgent::HandleStopThreadInterface(void)
{
Error error;
Error error = kErrorNone;
VerifyOrExit(IsCommandClassAuthorized(kCommissioning), error = kErrorRejected);
error = otThreadSetEnabled(&GetInstance(), false);
Get<Mle::Mle>().Stop();
if (!mVendorInfo->mDoNotActivateAfterLeaving)
{
IgnoreError(Activate(0, 0));
}
exit:
mJoinCallback.InvokeIfSet(error);
mJoinCallback.InvokeIfSet(&GetInstance(), /* aIsJoin */ false, error);
return error;
}
@@ -1043,6 +1073,26 @@ void TcatAgent::NotifyStateChange(void)
mState == kStateConnected);
}
void TcatAgent::HandleNotifierEvents(Events aEvents)
{
VerifyOrExit(IsStarted());
VerifyOrExit(mVendorInfo != nullptr);
if (aEvents.ContainsAny(kEventThreadRoleChanged))
{
if (!mVendorInfo->mKeepActiveAfterJoining && Get<Mle::Mle>().IsAttached() &&
(mLastDeviceRole == Mle::kRoleDisabled || mLastDeviceRole == Mle::kRoleDetached))
{
IgnoreError(Standby());
}
mLastDeviceRole = Get<Mle::Mle>().GetRole();
}
exit:
return;
}
template <> void TcatAgent::HandleTmf<kUriTcatEnable>(Coap::Msg &aMsg)
{
Error error = kErrorNone;
+38 -39
View File
@@ -52,6 +52,7 @@
#include "common/log.hpp"
#include "common/message.hpp"
#include "common/non_copyable.hpp"
#include "common/notifier.hpp"
#include "common/uptime.hpp"
#include "mac/mac_types.hpp"
#include "meshcop/dataset.hpp"
@@ -74,6 +75,7 @@ class TcatAgent : public InstanceLocator, private NonCopyable
{
friend class Ble::BleSecure;
friend class UnitTester;
friend class ot::Notifier;
public:
/**
@@ -164,49 +166,44 @@ public:
enum CommandTlvType : uint8_t
{
// Command Class General
kTlvResponseWithStatus = 0x01, ///< TCAT response with status value TLV
kTlvResponseWithPayload = 0x02, ///< TCAT response with payload TLV
kTlvResponseEvent = 0x03, ///< TCAT response event TLV (reserved)
kTlvGetNetworkName = 0x08, ///< TCAT network name query TLV
kTlvDisconnect = 0x09, ///< TCAT disconnect request TLV
kTlvPing = 0x0A, ///< TCAT ping request TLV
kTlvGetDeviceId = 0x0B, ///< TCAT device ID query TLV
kTlvGetExtendedPanID = 0x0C, ///< TCAT extended PAN ID query TLV
kTlvGetProvisioningURL = 0x0D, ///< TCAT provisioning URL query TLV
kTlvPresentPskdHash = 0x10, ///< TCAT commissioner rights elevation request TLV using PSKd hash
kTlvPresentPskcHash = 0x11, ///< TCAT commissioner rights elevation request TLV using PSKc hash
kTlvPresentInstallCodeHash = 0x12, ///< TCAT commissioner rights elevation request TLV using install code
kTlvRequestRandomNumChallenge = 0x13, ///< TCAT random number challenge query TLV
kTlvResponseWithStatus = OT_TCAT_TLV_RESPONSE_WITH_STATUS,
kTlvResponseWithPayload = OT_TCAT_TLV_RESPONSE_WITH_PAYLOAD,
kTlvResponseEvent = OT_TCAT_TLV_RESPONSE_EVENT,
kTlvGetNetworkName = OT_TCAT_TLV_GET_NETWORK_NAME,
kTlvDisconnect = OT_TCAT_TLV_DISCONNECT,
kTlvPing = OT_TCAT_TLV_PING,
kTlvGetDeviceId = OT_TCAT_TLV_GET_DEVICE_ID,
kTlvGetExtendedPanID = OT_TCAT_TLV_GET_EXTENDED_PAN_ID,
kTlvGetProvisioningURL = OT_TCAT_TLV_GET_PROVISIONING_URL,
kTlvPresentPskdHash = OT_TCAT_TLV_PRESENT_PSKD_HASH,
kTlvPresentPskcHash = OT_TCAT_TLV_PRESENT_PSKC_HASH,
kTlvPresentInstallCodeHash = OT_TCAT_TLV_PRESENT_INSTALL_CODE_HASH,
kTlvRequestRandomNumChallenge = OT_TCAT_TLV_REQUEST_RANDOM_CHALLENGE,
// Command Class Commissioning
kTlvSetActiveOperationalDataset = 0x20, ///< TCAT active operational dataset TLV
kTlvSetActiveOperationalDatasetAlt = 0x21, ///< TCAT active operational dataset alternative #1 TLV (reserved)
kTlvGetCommissionerCertificate = 0x25, ///< TCAT commissioner certificate query TLV
kTlvGetDiagnosticTlvs = 0x26, ///< TCAT diagnostics TLVs query TLV
kTlvStartThreadInterface = 0x27, ///< TCAT start thread interface request TLV
kTlvStopThreadInterface = 0x28, ///< TCAT stop thread interface request TLV
kTlvSetActiveOperationalDataset = OT_TCAT_TLV_SET_ACTIVE_OPERATIONAL_DATASET,
kTlvSetActiveOperationalDatasetAlt = OT_TCAT_TLV_SET_ACTIVE_OPERATIONAL_DATASET_ALT,
kTlvGetCommissionerCertificate = OT_TCAT_TLV_GET_COMMISSIONER_CERTIFICATE,
kTlvGetDiagnosticTlvs = OT_TCAT_TLV_GET_DIAGNOSTIC_TLVS,
kTlvStartThreadInterface = OT_TCAT_TLV_START_THREAD_INTERFACE,
kTlvStopThreadInterface = OT_TCAT_TLV_STOP_THREAD_INTERFACE,
// Command Class Extraction
kTlvGetActiveOperationalDataset = 0x40, ///< TCAT active operational dataset query TLV
kTlvGetActiveOperationalDatasetAlt = 0x41, ///< TCAT active operational dataset alternative #1 query TLV (rsv)
kTlvGetActiveOperationalDataset = OT_TCAT_TLV_GET_ACTIVE_OPERATIONAL_DATASET,
kTlvGetActiveOperationalDatasetAlt = OT_TCAT_TLV_GET_ACTIVE_OPERATIONAL_DATASET_ALT,
// Command Class Decommissioning
kTlvDecommission = 0x60, ///< TCAT decommission request TLV
kTlvDecommission = OT_TCAT_TLV_DECOMMISSION,
// Command Class Application
kTlvGetApplicationLayers = 0x80, ///< TCAT get application layers request TLV
kTlvSendApplicationData1 = 0x81, ///< TCAT send application data 1 TLV
kTlvSendApplicationData2 = 0x82, ///< TCAT send application data 2 TLV
kTlvSendApplicationData3 = 0x83, ///< TCAT send application data 3 TLV
kTlvSendApplicationData4 = 0x84, ///< TCAT send application data 4 TLV
kTlvServiceNameUdp = 0x89, ///< TCAT service name UDP sub-TLV (not used as a command)
kTlvServiceNameTcp = 0x8A, ///< TCAT service name TCP sub-TLV (not used as a command)
kTlvSendVendorSpecificData = 0x9F, ///< TCAT send vendor specific command or data TLV
// Command Class CCM
kTlvSetLDevIdOperationalCert = 0xA0, ///< TCAT set LDevID operational certificate TLV (reserved)
kTlvSetLDevIdPrivateKey = 0xA1, ///< TCAT set LDevID operational certificate private key TLV (reserved)
kTlvSetDomainCaCert = 0xA2, ///< TCAT set domain CA certificate TLV (reserved)
kTlvGetApplicationLayers = OT_TCAT_TLV_GET_APPLICATION_LAYERS,
kTlvSendApplicationData1 = OT_TCAT_TLV_SEND_APPLICATION_DATA_1,
kTlvSendApplicationData2 = OT_TCAT_TLV_SEND_APPLICATION_DATA_2,
kTlvSendApplicationData3 = OT_TCAT_TLV_SEND_APPLICATION_DATA_3,
kTlvSendApplicationData4 = OT_TCAT_TLV_SEND_APPLICATION_DATA_4,
kTlvServiceNameUdp = OT_TCAT_TLV_SERVICE_NAME_UDP,
kTlvServiceNameTcp = OT_TCAT_TLV_SERVICE_NAME_TCP,
kTlvSendVendorSpecificData = OT_TCAT_TLV_SEND_VENDOR_SPECIFIC_DATA,
};
/**
@@ -441,6 +438,7 @@ public:
private:
void NotifyApplicationResponseSent(void) { mApplicationResponsePending = false; }
void NotifyStateChange(void);
void HandleNotifierEvents(Events aEvents);
void ClearCommissionerState();
Error Connected(MeshCoP::Tls::Extension &aTls);
void Disconnected(void);
@@ -520,10 +518,11 @@ private:
bool mIsCommissioned : 1;
bool mApplicationResponsePending : 1;
using ExpireTimer = TimerMilliIn<TcatAgent, &TcatAgent::HandleTimer>;
ExpireTimer mActiveOrStandbyTimer;
uint32_t mTcatActiveDurationMs;
UptimeSec mLastHashVerificationTimestamp;
uint8_t mHashVerificationAttempts;
ExpireTimer mActiveOrStandbyTimer;
uint32_t mTcatActiveDurationMs;
UptimeSec mLastHashVerificationTimestamp;
uint8_t mHashVerificationAttempts;
Mle::DeviceRole mLastDeviceRole;
};
DeclareTmfHandler(TcatAgent, kUriTcatEnable);