[tcat] add TCAT_ENABLE.req TMF command (#12013)

This adds support for the TMF command to enable TCAT remotely.  A test
is added that uses the 'UDP send' mechanism to send the new TMF
command to a target node.

Some fixes/additions to the test framework are made to support the new
test, including a new argument for udp_send() to send a specific byte
array and udp_rx() to receive data by a UDP client on a node.
This commit is contained in:
Esko Dijk
2025-10-29 08:29:09 -07:00
committed by GitHub
parent 13e7c4e702
commit e4479fb6b1
11 changed files with 301 additions and 34 deletions
+6
View File
@@ -94,6 +94,7 @@ public:
kJoinerIid = OT_MESHCOP_TLV_JOINER_IID, ///< Joiner IID TLV
kJoinerRouterLocator = OT_MESHCOP_TLV_JOINER_RLOC, ///< Joiner Router Locator TLV
kJoinerRouterKek = OT_MESHCOP_TLV_JOINER_ROUTER_KEK, ///< Joiner Router KEK TLV
kDuration = OT_MESHCOP_TLV_DURATION, ///< Duration TLV
kProvisioningUrl = OT_MESHCOP_TLV_PROVISIONING_URL, ///< Provisioning URL TLV
kVendorName = OT_MESHCOP_TLV_VENDOR_NAME_TLV, ///< meshcop Vendor Name TLV
kVendorModel = OT_MESHCOP_TLV_VENDOR_MODEL_TLV, ///< meshcop Vendor Model TLV
@@ -205,6 +206,11 @@ typedef UintTlvInfo<Tlv::kJoinerRouterLocator, uint16_t> JoinerRouterLocatorTlv;
*/
typedef SimpleTlvInfo<Tlv::kJoinerRouterKek, Kek> JoinerRouterKekTlv;
/**
* Defines Duration TLV constants and types.
*/
typedef UintTlvInfo<Tlv::kDuration, uint16_t> DurationTlv;
/**
* Defines Count TLV constants and types.
*/
+98 -19
View File
@@ -60,6 +60,7 @@ TcatAgent::TcatAgent(Instance &aInstance)
, mVendorInfo(nullptr)
, mState(kStateDisabled)
, mNextState(kStateDisabled)
, mTimerSetsToActive(false)
, mActiveOrStandbyTimer(aInstance)
, mTcatActiveDurationMs(0)
{
@@ -95,7 +96,6 @@ Error TcatAgent::Start(AppDataReceiveCallback aAppDataReceiveCallback, JoinCallb
mState = kStateActive;
mNextState = kStateActive;
mTcatActiveDurationMs = 0;
mActiveOrStandbyTimer.Stop();
LogInfo("Start");
exit:
@@ -109,6 +109,7 @@ void TcatAgent::Stop(void)
mAppDataReceiveCallback.Clear();
mJoinCallback.Clear();
mState = kStateDisabled;
mActiveOrStandbyTimer.Stop();
ClearCommissionerState();
LogInfo("Stop");
}
@@ -122,7 +123,7 @@ Error TcatAgent::Standby(void)
mTcatActiveDurationMs = 0;
mActiveOrStandbyTimer.Stop();
mNextState = kStateStandby;
if (!IsConnected())
if (!IsConnected() && mState != kStateStandby)
{
// if already TLS-connected, only move to 'standby' once the connection is done.
// if not yet fully connected, go to 'standby' immediately (ignoring a TLS handshake that may be ongoing)
@@ -141,14 +142,17 @@ Error TcatAgent::Activate(const uint32_t aDelayMs, const uint32_t aDurationMs)
Error error = kErrorNone;
VerifyOrExit(IsStarted(), error = kErrorInvalidState);
VerifyOrExit(mState != kStateActive);
mTcatActiveDurationMs = aDurationMs;
mTimerSetsToActive = true;
if (aDelayMs > 0)
{
mActiveOrStandbyTimer.Start(aDelayMs);
}
else
{
mActiveOrStandbyTimer.Stop();
HandleTimer();
}
@@ -219,8 +223,7 @@ Error TcatAgent::Connected(MeshCoP::Tls::Extension &aTls)
}
// A temporary enablement stops after disconnect: to standby.
// For others, return to prior state, upon disconnect.
mNextState = (mState == kStateActiveTemporary) ? kStateStandby : mState;
mNextState = (mState == kStateActiveTemporary) ? kStateStandby : kStateActive;
mState = kStateConnected;
NotifyStateChange();
LogInfo("Connected");
@@ -237,8 +240,8 @@ void TcatAgent::Disconnected(void)
if (mState != kStateDisabled)
{
mState = mNextState;
NotifyStateChange();
LogInfo("Disconnected");
NotifyStateChange();
ClearCommissionerState();
}
}
@@ -1071,28 +1074,30 @@ void TcatAgent::HandleTimer(void)
{
case kStateStandby:
case kStateStandbyTemporary:
if (mTcatActiveDurationMs > 0)
case kStateActiveTemporary:
if (mTimerSetsToActive)
{
mActiveOrStandbyTimer.Start(mTcatActiveDurationMs);
mState = kStateActiveTemporary;
mTimerSetsToActive = false;
if (mTcatActiveDurationMs > 0)
{
mState = kStateActiveTemporary;
mActiveOrStandbyTimer.Start(mTcatActiveDurationMs);
}
else
{
mState = kStateActive;
}
NotifyStateChange();
LogInfo("Active");
}
else
{
mState = kStateActive;
IgnoreError(Standby());
}
NotifyStateChange();
LogInfo("Active");
break;
case kStateActiveTemporary:
IgnoreError(Standby());
break;
case kStateConnected:
mNextState = (mTcatActiveDurationMs > 0) ? kStateStandby : kStateActive;
break;
// kStateActive: will not go to standby, based on timer. Application has forced it to 'active'.
// kStateConnected: no change here, mNextState already set.
default:
break;
}
@@ -1105,6 +1110,80 @@ void TcatAgent::NotifyStateChange(void)
mState == kStateConnected);
}
template <> void TcatAgent::HandleTmf<kUriTcatEnable>(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
Error error = kErrorNone;
Coap::Message *message = nullptr;
uint32_t delayTimerMs = 0;
uint16_t durationSec = 0;
uint32_t durationMs;
VerifyOrExit(aMessage.IsConfirmablePostRequest());
LogInfo("Received %s from %s", UriToString<kUriTcatEnable>(), aMessageInfo.GetPeerAddr().ToString().AsCString());
message = Get<Tmf::Agent>().NewResponseMessage(aMessage);
VerifyOrExit(message != nullptr, error = kErrorNoBufs);
SuccessOrExit(error = Tlv::Find<DelayTimerTlv>(aMessage, delayTimerMs));
switch (Tlv::Find<DurationTlv>(aMessage, durationSec))
{
case kErrorNone:
break;
case kErrorNotFound:
durationSec = kTcatTmfEnableDefaultSec; // If Duration TLV absent: use default duration
break;
default:
ExitNow(error = kErrorParse);
}
durationMs = Time::SecToMsec(durationSec);
// if an existing activation is ongoing, adapt the requested one to be compatible.
AdaptToExistingActivePeriod(delayTimerMs, durationMs);
error = Activate(delayTimerMs, durationMs);
exit:
if (message != nullptr)
{
error =
Tlv::Append<StateTlv>(*message, error == kErrorNone ? StateTlv::State::kAccept : StateTlv::State::kReject);
if (error == kErrorNone)
{
error = Get<Tmf::Agent>().SendMessage(*message, aMessageInfo);
}
FreeMessageOnError(message, error);
}
LogWarnOnError(error, "send TCAT_ENABLE.rsp");
}
// Adapts delay/duration parameters of the given TCAT temporary activation period to the
// already-ongoing temporary TCAT activation (if any). The goals of the adaptation are:
// - not shorten the duration of an existing (scheduled/ongoing) activation
// - not set the start of activation later than the existing scheduled activation time
void TcatAgent::AdaptToExistingActivePeriod(uint32_t &aPeriodDelayMs, uint32_t &aPeriodDurationMs)
{
VerifyOrExit(mState != kStateActive);
if (mActiveOrStandbyTimer.IsRunning())
{
TimeMilli now = TimerMilli::GetNow();
uint32_t remainingMs;
remainingMs = (mActiveOrStandbyTimer.GetFireTime() > now) ? mActiveOrStandbyTimer.GetFireTime() - now : 0;
if (mTimerSetsToActive)
{
aPeriodDelayMs = Min(aPeriodDelayMs, remainingMs);
aPeriodDurationMs = Max(aPeriodDurationMs, remainingMs + mTcatActiveDurationMs - aPeriodDelayMs);
}
else
{
aPeriodDelayMs = 0;
aPeriodDurationMs = Max(aPeriodDurationMs, remainingMs);
}
}
exit:
return;
}
void SerializeTcatAdvertisementTlv(uint8_t *aBuffer,
uint16_t &aOffset,
TcatAdvertisementTlvType aType,
+18 -11
View File
@@ -52,6 +52,7 @@
#include "meshcop/meshcop.hpp"
#include "meshcop/meshcop_tlvs.hpp"
#include "meshcop/secure_transport.hpp"
#include "thread/tmf.hpp"
namespace ot {
@@ -330,7 +331,7 @@ public:
* Activate TCAT functions of the TCAT agent.
*
* This requires the TCAT agent to be already started.
* The state transitions to kStateActive of kStateActiveTemporary. In these states, TCAT Advertisements
* The state transitions to kStateActive or kStateActiveTemporary. In these states, TCAT Advertisements
* are actively sent and TCAT Commissioners are able to connect. From here, TCAT can be set to standby
* again using Standby().
* If a connection is ongoing and aDurationMs==0, this call will ensure that kStateActive will
@@ -338,7 +339,7 @@ public:
* This function will override any ongoing temporary activation of TCAT, or any
* previously scheduled activation for a future time.
*
* @param[in] aDelayMs Delay in ms before activating. If 0, activate immediately.
* @param[in] aDelayMs Delay in ms before activating. If 0, activate immediately.
* @param[in] aDurationMs Duration in ms of the activation. If 0, activate indefinitely.
*
* @retval kErrorNone Successfully set the TCAT agent to kStateActive now, OR scheduled
@@ -413,6 +414,8 @@ public:
*/
bool GetApplicationResponsePending(void) const { return mApplicationResponsePending; }
template <Uri kUri> void HandleTmf(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
private:
void NotifyApplicationResponseSent(void) { mApplicationResponsePending = false; }
void NotifyStateChange(void);
@@ -456,7 +459,7 @@ private:
TcatApplicationProtocol aApplicationProtocol,
bool &aResponse);
void HandleTimer(void);
void AdaptToExistingActivePeriod(uint32_t &aPeriodDelayMs, uint32_t &aPeriodDurationMs);
Error VerifyHash(const Message &aIncomingMessage,
uint16_t aOffset,
uint16_t aLength,
@@ -478,6 +481,7 @@ private:
static constexpr uint16_t kBufferReserve = 2048 / (Buffer::kSize - sizeof(otMessageBuffer)) + 1;
static constexpr uint8_t kServiceNameMaxLength = OT_TCAT_SERVICE_NAME_MAX_LENGTH;
static constexpr uint8_t kApplicationLayerMaxCount = OT_TCAT_APPLICATION_LAYER_MAX_COUNT;
static constexpr uint16_t kTcatTmfEnableDefaultSec = OT_TCAT_ENABLE_MAX;
const VendorInfo *mVendorInfo;
Callback<JoinCallback> mJoinCallback;
@@ -488,7 +492,8 @@ private:
NetworkName mCommissionerDomainName;
ExtendedPanId mCommissionerExtendedPanId;
State mState;
State mNextState;
State mNextState; //< desired state after client disconnects
bool mTimerSetsToActive : 1;
bool mCommissionerHasNetworkName : 1;
bool mCommissionerHasDomainName : 1;
bool mCommissionerHasExtendedPanId : 1;
@@ -503,15 +508,10 @@ private:
uint32_t mTcatActiveDurationMs;
};
} // namespace MeshCoP
DefineCoreType(otTcatVendorInfo, MeshCoP::TcatAgent::VendorInfo);
DefineMapEnum(otTcatApplicationProtocol, MeshCoP::TcatAgent::TcatApplicationProtocol);
DefineMapEnum(otTcatAdvertisedDeviceIdType, MeshCoP::TcatAgent::TcatDeviceIdType);
DeclareTmfHandler(TcatAgent, kUriTcatEnable);
// Command class TLVs
typedef UintTlvInfo<MeshCoP::TcatAgent::kTlvResponseWithStatus, uint8_t> ResponseWithStatusTlv;
typedef UintTlvInfo<TcatAgent::kTlvResponseWithStatus, uint8_t> ResponseWithStatusTlv;
/**
* Represent TCAT Device Type and Status
@@ -545,6 +545,13 @@ enum TcatAdvertisementTlvType : uint8_t
kTlvVendorIanaPen = 6, ///< TCAT Vendor IANA PEN
};
} // namespace MeshCoP
DefineCoreType(otTcatVendorInfo, MeshCoP::TcatAgent::VendorInfo);
DefineMapEnum(otTcatApplicationProtocol, MeshCoP::TcatAgent::TcatApplicationProtocol);
DefineMapEnum(otTcatAdvertisedDeviceIdType, MeshCoP::TcatAgent::TcatDeviceIdType);
} // namespace ot
#endif // OPENTHREAD_CONFIG_BLE_TCAT_ENABLE
+3
View File
@@ -179,6 +179,9 @@ bool Agent::HandleResource(const char *aUriPath, Message &aMessage, const Ip6::M
#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_DUA_NDPROXYING_ENABLE
Case(kUriDuaRegistrationRequest, BackboneRouter::Manager);
#endif
#endif
#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE
Case(kUriTcatEnable, MeshCoP::TcatAgent);
#endif
default:
+4
View File
@@ -83,6 +83,7 @@ static constexpr Entry kEntries[] = {
{"c/pq"}, // kUriPanIdQuery
{"c/ps"}, // kUriPendingSet
{"c/rx"}, // kUriRelayRx
{"c/te"}, // kUriTcatEnable
{"c/tx"}, // kUriRelayTx
{"c/ur"}, // kUriProxyRx
{"c/ut"}, // kUriProxyTx
@@ -128,6 +129,7 @@ static_assert(AreConstStringsEqual(kEntries[kUriPendingGet].mPath, "c/pg"), "kEn
static_assert(AreConstStringsEqual(kEntries[kUriPanIdQuery].mPath, "c/pq"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriPendingSet].mPath, "c/ps"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriRelayRx].mPath, "c/rx"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriTcatEnable].mPath, "c/te"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriRelayTx].mPath, "c/tx"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriProxyRx].mPath, "c/ur"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriProxyTx].mPath, "c/ut"), "kEntries is invalid");
@@ -172,6 +174,7 @@ struct UriEnumCheck
ValidateNextEnum(kUriPanIdQuery);
ValidateNextEnum(kUriPendingSet);
ValidateNextEnum(kUriRelayRx);
ValidateNextEnum(kUriTcatEnable);
ValidateNextEnum(kUriRelayTx);
ValidateNextEnum(kUriProxyRx);
ValidateNextEnum(kUriProxyTx);
@@ -235,6 +238,7 @@ template <> const char *UriToString<kUriPendingGet>(void) { return "PendingGet";
template <> const char *UriToString<kUriPanIdQuery>(void) { return "PanIdQuery"; }
template <> const char *UriToString<kUriPendingSet>(void) { return "PendingSet"; }
template <> const char *UriToString<kUriRelayRx>(void) { return "RelayRx"; }
template <> const char *UriToString<kUriTcatEnable>(void) { return "TcatEnable"; }
template <> const char *UriToString<kUriRelayTx>(void) { return "RelayTx"; }
template <> const char *UriToString<kUriProxyRx>(void) { return "ProxyRx"; }
template <> const char *UriToString<kUriProxyTx>(void) { return "ProxyTx"; }
+2
View File
@@ -75,6 +75,7 @@ enum Uri : uint8_t
kUriPanIdQuery, ///< PAN ID Query ("c/pq")
kUriPendingSet, ///< MGMT_PENDING_SET ("c/ps")
kUriRelayRx, ///< Relay RX ("c/rx")
kUriTcatEnable, ///< TCAT Enable ("c/te")
kUriRelayTx, ///< Relay TX ("c/tx")
kUriProxyRx, ///< Proxy RX ("c/ur")
kUriProxyTx, ///< Proxy TX ("c/ut")
@@ -146,6 +147,7 @@ template <> const char *UriToString<kUriPendingGet>(void);
template <> const char *UriToString<kUriPanIdQuery>(void);
template <> const char *UriToString<kUriPendingSet>(void);
template <> const char *UriToString<kUriRelayRx>(void);
template <> const char *UriToString<kUriTcatEnable>(void);
template <> const char *UriToString<kUriRelayTx>(void);
template <> const char *UriToString<kUriProxyRx>(void);
template <> const char *UriToString<kUriProxyTx>(void);