diff --git a/src/core/meshcop/tcat_agent.cpp b/src/core/meshcop/tcat_agent.cpp index c98850aa5..5c39757e9 100644 --- a/src/core/meshcop/tcat_agent.cpp +++ b/src/core/meshcop/tcat_agent.cpp @@ -37,6 +37,7 @@ #if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE #include "instance/instance.hpp" +#include "thread/network_diagnostic.hpp" namespace ot { namespace MeshCoP { @@ -408,6 +409,10 @@ Error TcatAgent::HandleSingleTlv(const Message &aIncomingMessage, Message &aOutg error = HandleGetActiveOperationalDataset(aOutgoingMessage, response); break; + case kTlvGetDiagnosticTlvs: + error = HandleGetDiagnosticTlvs(aIncomingMessage, aOutgoingMessage, offset, length, response); + break; + case kTlvStartThreadInterface: error = HandleStartThreadInterface(); break; @@ -584,6 +589,65 @@ exit: return error; } +Error TcatAgent::HandleGetDiagnosticTlvs(const Message &aIncomingMessage, + Message &aOutgoingMessage, + uint16_t aOffset, + uint16_t aLength, + bool &aResponse) +{ + Error error = kErrorNone; + OffsetRange offsetRange; + ot::ExtendedTlv extTlv; + uint16_t initialLength; + uint16_t length; + + if (!CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mCommissioningFlags, + mDeviceAuthorizationField.mCommissioningFlags, nullptr)) + { + error = kErrorRejected; + ExitNow(); + } + + offsetRange.Init(aOffset, aLength); + initialLength = aOutgoingMessage.GetLength(); + + // Start with extTlv to avoid the need for a temporary message buffer to calucalate reply length + extTlv.SetType(kTlvResponseWithPayload); + extTlv.SetLength(0); + SuccessOrExit(error = aOutgoingMessage.Append(extTlv)); + + error = + Get().AppendRequestedTlvsForTcat(aIncomingMessage, aOutgoingMessage, offsetRange); + + // Ensure enough message buffers are left for transmission of the result. Report error otherwise. + if (Get().GetFreeBufferCount() < kBufferReserve) + { + error = kErrorNoBufs; + } + + if (error != kErrorNone) + { + IgnoreError(aOutgoingMessage.SetLength(initialLength)); + ExitNow(); + } + + length = aOutgoingMessage.GetLength() - initialLength - sizeof(extTlv); + + if (length > 0) + { + extTlv.SetLength(length); + aOutgoingMessage.WriteBytes(initialLength, &extTlv, sizeof(extTlv)); + aResponse = true; + } + else + { + IgnoreError(aOutgoingMessage.SetLength(initialLength)); + } + +exit: + return error; +} + Error TcatAgent::HandleDecomission(void) { Error error = kErrorNone; diff --git a/src/core/meshcop/tcat_agent.hpp b/src/core/meshcop/tcat_agent.hpp index 3781a714d..9225d5afe 100644 --- a/src/core/meshcop/tcat_agent.hpp +++ b/src/core/meshcop/tcat_agent.hpp @@ -340,6 +340,11 @@ private: Error HandleSingleTlv(const Message &aIncomingMessage, Message &aOutgoingMessage); Error HandleSetActiveOperationalDataset(const Message &aIncomingMessage, uint16_t aOffset, uint16_t aLength); Error HandleGetActiveOperationalDataset(Message &aOutgoingMessage, bool &aResponse); + Error HandleGetDiagnosticTlvs(const Message &aIncomingMessage, + Message &aOutgoingMessage, + uint16_t aOffset, + uint16_t aLength, + bool &response); Error HandleDecomission(void); Error HandlePing(const Message &aIncomingMessage, Message &aOutgoingMessage, @@ -383,6 +388,7 @@ private: static constexpr uint16_t kTcatMaxDeviceIdSize = OT_TCAT_MAX_DEVICEID_SIZE; static constexpr uint16_t kInstallCodeMaxSize = 255; static constexpr uint16_t kCommissionerCertMaxLength = 1024; + static constexpr uint16_t kBufferReserve = 2048 / (kBufferSize - sizeof(otMessageBuffer)) + 1; JoinerPskd mJoinerPskd; const VendorInfo *mVendorInfo; diff --git a/src/core/radio/ble_secure.cpp b/src/core/radio/ble_secure.cpp index 719085d7b..392b4a2ea 100644 --- a/src/core/radio/ble_secure.cpp +++ b/src/core/radio/ble_secure.cpp @@ -158,7 +158,7 @@ void BleSecure::Disconnect(void) if (mBleState == kConnected) { mBleState = kAdvertising; - IgnoreReturnValue(otPlatBleGapDisconnect(&GetInstance())); + IgnoreError(otPlatBleGapDisconnect(&GetInstance())); } mConnectCallback.InvokeIfSet(&GetInstance(), false, false); @@ -186,8 +186,8 @@ Error BleSecure::SendMessage(ot::Message &aMessage) SuccessOrExit(error = mSendMessage->AppendBytesFromMessage(aMessage, 0, aMessage.GetLength())); SuccessOrExit(error = Flush()); -exit: aMessage.Free(); +exit: return error; } @@ -234,17 +234,45 @@ exit: Error BleSecure::Flush(void) { - Error error = kErrorNone; + Error error = kErrorNone; + ot::Message *message = nullptr; + uint16_t length; + VerifyOrExit(mSendMessage != nullptr); VerifyOrExit(IsConnected(), error = kErrorInvalidState); - VerifyOrExit(mSendMessage->GetLength() != 0, error = kErrorNone); + length = mSendMessage->GetLength(); + // Split send buffer in chunks which can later be processed by mTls.Send(..) + while (length > kTlsDataMaxSize) + { + VerifyOrExit((message = Get().Allocate(Message::kTypeBle, 0)) != nullptr, error = kErrorNoBufs); + SuccessOrExit(error = message->AppendBytesFromMessage(*mSendMessage, 0, kTlsDataMaxSize)); + + // We accept an expensive copy operation in favor of optimal buffer usage for long messages + mSendMessage->WriteBytesFromMessage(0, *mSendMessage, kTlsDataMaxSize, length - kTlsDataMaxSize); + length -= kTlsDataMaxSize; + + // Should never fail since we are decreasing the length of the message + SuccessOrAssert(error = mSendMessage->SetLength(length)); + mTransmitQueue.Enqueue(*message); + mTransmitTask.Post(); + message = nullptr; + } + + VerifyOrExit(length != 0, error = kErrorNone); mTransmitQueue.Enqueue(*mSendMessage); mTransmitTask.Post(); - mSendMessage = nullptr; exit: + FreeMessage(message); + + if (mSendMessage != nullptr) + { + mSendMessage->Free(); + mSendMessage = nullptr; + } + return error; } @@ -275,7 +303,7 @@ void BleSecure::HandleBleConnected(uint16_t aConnectionId) mBleState = kConnected; - IgnoreReturnValue(otPlatBleGattMtuGet(&GetInstance(), &mMtuSize)); + IgnoreError(otPlatBleGattMtuGet(&GetInstance(), &mMtuSize)); mConnectCallback.InvokeIfSet(&GetInstance(), IsConnected(), true); } @@ -335,6 +363,8 @@ void BleSecure::HandleTlsConnectEvent(MeshCoP::Tls::ConnectEvent aEvent) { FreeMessage(mReceivedMessage); mReceivedMessage = nullptr; + FreeMessage(mSendMessage); + mSendMessage = nullptr; if (mTcatAgent.IsEnabled()) { @@ -361,7 +391,7 @@ void BleSecure::HandleTlsReceive(uint8_t *aBuf, uint16_t aLength) { SuccessOrExit(mReceivedMessage->AppendBytes(aBuf, aLength)); mReceiveCallback.InvokeIfSet(&GetInstance(), mReceivedMessage, 0, OT_TCAT_APPLICATION_PROTOCOL_NONE, ""); - IgnoreReturnValue(mReceivedMessage->SetLength(0)); + IgnoreError(mReceivedMessage->SetLength(0)); } else { @@ -388,7 +418,7 @@ void BleSecure::HandleTlsReceive(uint8_t *aBuf, uint16_t aLength) } } - IgnoreReturnValue(mReceivedMessage->Read(0, tlv)); + IgnoreError(mReceivedMessage->Read(0, tlv)); if (tlv.IsExtended()) { @@ -400,7 +430,7 @@ void BleSecure::HandleTlsReceive(uint8_t *aBuf, uint16_t aLength) continue; } - IgnoreReturnValue(mReceivedMessage->Read(0, extTlv)); + IgnoreError(mReceivedMessage->Read(0, extTlv)); requiredBytes = extTlv.GetSize(); offset = sizeof(extTlv); } @@ -419,18 +449,19 @@ void BleSecure::HandleTlsReceive(uint8_t *aBuf, uint16_t aLength) if (mTcatAgent.IsEnabled()) { - ot::Message *message; - Error error = kErrorNone; + Error error = kErrorNone; - message = Get().Allocate(Message::kTypeBle); - VerifyOrExit(message != nullptr, error = kErrorNoBufs); + IgnoreError(Flush()); - error = mTcatAgent.HandleSingleTlv(*mReceivedMessage, *message); - if (message->GetLength() != 0) + if (mSendMessage == nullptr) { - IgnoreReturnValue(SendMessage(*message)); + mSendMessage = Get().Allocate(Message::kTypeBle); + VerifyOrExit(mSendMessage != nullptr, error = kErrorNoBufs); } + error = mTcatAgent.HandleSingleTlv(*mReceivedMessage, *mSendMessage); + IgnoreError(Flush()); + if (error == kErrorAbort) { LogInfo("Disconnecting TCAT client."); @@ -507,7 +538,7 @@ Error BleSecure::HandleTransport(ot::Message &aMessage) packet.mLength = kPacketBufferSize; } - IgnoreReturnValue(aMessage.Read(offset, mPacketBuffer, packet.mLength)); + IgnoreError(aMessage.Read(offset, mPacketBuffer, packet.mLength)); packet.mValue = mPacketBuffer; packet.mPower = OT_BLE_DEFAULT_POWER; @@ -530,7 +561,7 @@ void otPlatBleGattServerOnWriteRequest(otInstance *aInstance, uint16_t aHandle, OT_UNUSED_VARIABLE(aHandle); // Only a single handle is expected for RX VerifyOrExit(aPacket != nullptr); - IgnoreReturnValue(AsCoreType(aInstance).Get().HandleBleReceive(aPacket->mValue, aPacket->mLength)); + IgnoreError(AsCoreType(aInstance).Get().HandleBleReceive(aPacket->mValue, aPacket->mLength)); exit: return; } @@ -547,7 +578,7 @@ void otPlatBleGapOnDisconnected(otInstance *aInstance, uint16_t aConnectionId) void otPlatBleGattOnMtuUpdate(otInstance *aInstance, uint16_t aMtu) { - IgnoreReturnValue(AsCoreType(aInstance).Get().HandleBleMtuUpdate(aMtu)); + IgnoreError(AsCoreType(aInstance).Get().HandleBleMtuUpdate(aMtu)); } #endif // OPENTHREAD_CONFIG_BLE_TCAT_ENABLE diff --git a/src/core/radio/ble_secure.hpp b/src/core/radio/ble_secure.hpp index 6d48482fb..978129f7f 100644 --- a/src/core/radio/ble_secure.hpp +++ b/src/core/radio/ble_secure.hpp @@ -287,7 +287,8 @@ private: static constexpr uint8_t kInitialMtuSize = 23; // ATT_MTU static constexpr uint8_t kGattOverhead = 3; // BLE GATT payload fits MTU size - 3 bytes static constexpr uint8_t kPacketBufferSize = OT_BLE_ATT_MTU_MAX - kGattOverhead; - static constexpr uint16_t kTxBleHandle = 0; // Characteristics Handle for TX (not used) + static constexpr uint16_t kTxBleHandle = 0; // Characteristics Handle for TX (not used) + static constexpr uint16_t kTlsDataMaxSize = 800; // Maximum size of data chunks sent with mTls.Send(..) static void HandleTlsConnectEvent(MeshCoP::Tls::ConnectEvent aEvent, void *aContext); void HandleTlsConnectEvent(MeshCoP::Tls::ConnectEvent aEvent); diff --git a/src/core/thread/network_diagnostic.cpp b/src/core/thread/network_diagnostic.cpp index ba1c52ca4..93c136add 100644 --- a/src/core/thread/network_diagnostic.cpp +++ b/src/core/thread/network_diagnostic.cpp @@ -238,6 +238,71 @@ exit: return error; } +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE +Error Server::AppendChildTableAsChildTlvs(Message &aMessage) +{ + Error error = kErrorNone; + ChildTlv childTlv; + + for (Child &child : Get().Iterate(Child::kInStateValid)) + { + childTlv.InitFrom(child); + + SuccessOrExit(error = childTlv.AppendTo(aMessage)); + } + + // Add empty TLV to indicate end of the list + + childTlv.InitAsEmpty(); + SuccessOrExit(error = childTlv.AppendTo(aMessage)); + +exit: + return error; +} + +Error Server::AppendRouterNeighborTlvs(Message &aMessage) +{ + Error error = kErrorNone; + RouterNeighborTlv neighborTlv; + + for (Router &router : Get()) + { + if (router.IsStateValid()) + { + neighborTlv.InitFrom(router); + SuccessOrExit(error = neighborTlv.AppendTo(aMessage)); + } + } + + // Add empty TLV to indicate end of the list + + neighborTlv.InitAsEmpty(); + SuccessOrExit(error = neighborTlv.AppendTo(aMessage)); + +exit: + return error; +} + +Error Server::AppendChildTableIp6AddressList(Message &aMessage) +{ + Error error = kErrorNone; + Tlv tlv; + + for (const Child &child : Get().Iterate(Child::kInStateValid)) + { + SuccessOrExit(error = AppendChildIp6AddressListTlv(aMessage, child)); + } + + // Add empty TLV to indicate end of the list + + tlv.SetType(Tlv::kChildIp6AddressList); + tlv.SetLength(0); + SuccessOrExit(error = aMessage.Append(tlv)); + +exit: + return error; +} +#endif // OPENTHREAD_CONFIG_BLE_TCAT_ENABLE #endif // OPENTHREAD_FTD Error Server::AppendMacCounters(Message &aMessage) @@ -282,6 +347,48 @@ exit: return error; } +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE +Error Server::AppendRequestedTlvsForTcat(const Message &aRequest, Message &aResponse, OffsetRange &aOffsetRange) +{ + Error error = kErrorNone; + + while (!aOffsetRange.IsEmpty()) + { + uint8_t tlvType; + + SuccessOrExit(error = aRequest.Read(aOffsetRange, tlvType)); + aOffsetRange.AdvanceOffset(sizeof(uint8_t)); + +#if OPENTHREAD_FTD + switch (tlvType) + { + case ChildTlv::kType: + SuccessOrExit(error = AppendChildTableAsChildTlvs(aResponse)); + break; + + case ChildIp6AddressListTlv::kType: + SuccessOrExit(error = AppendChildTableIp6AddressList(aResponse)); + break; + + case RouterNeighborTlv::kType: + SuccessOrExit(error = AppendRouterNeighborTlvs(aResponse)); + break; + + default: + SuccessOrExit(error = AppendDiagTlv(tlvType, aResponse)); + break; + } + +#elif OPENTHREAD_MTD + SuccessOrExit(error = AppendDiagTlv(tlvType, aResponse)); +#endif + } + +exit: + return error; +} +#endif // OPENTHREAD_CONFIG_BLE_TCAT_ENABLE + Error Server::AppendDiagTlv(uint8_t aTlvType, Message &aMessage) { Error error = kErrorNone; @@ -786,7 +893,7 @@ exit: return error; } -Error Server::AppendChildIp6AddressListTlv(Coap::Message &aAnswer, const Child &aChild) +Error Server::AppendChildIp6AddressListTlv(Message &aAnswer, const Child &aChild) { Error error = kErrorNone; uint16_t numIp6Addr = aChild.GetIp6Addresses().GetLength(); diff --git a/src/core/thread/network_diagnostic.hpp b/src/core/thread/network_diagnostic.hpp index 8d75e9e63..887150b0f 100644 --- a/src/core/thread/network_diagnostic.hpp +++ b/src/core/thread/network_diagnostic.hpp @@ -52,6 +52,10 @@ namespace Utils { class MeshDiag; } +namespace MeshCoP { +class TcatAgent; +} + namespace NetworkDiagnostic { /** @@ -71,6 +75,7 @@ class Client; class Server : public InstanceLocator, private NonCopyable { friend class Tmf::Agent; + friend class MeshCoP::TcatAgent; friend class Client; public: @@ -221,6 +226,10 @@ private: Error AppendRequestedTlvs(const Message &aRequest, Message &aResponse); void PrepareMessageInfoForDest(const Ip6::Address &aDestination, Tmf::MessageInfo &aMessageInfo) const; +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE + Error AppendRequestedTlvsForTcat(const Message &aRequest, Message &aResponse, OffsetRange &aOffsetRange); +#endif + #if OPENTHREAD_MTD void SendAnswer(const Ip6::Address &aDestination, const Message &aRequest); #elif OPENTHREAD_FTD @@ -234,9 +243,15 @@ private: Error AppendChildTableAsChildTlvs(Coap::Message *&aAnswer, AnswerInfo &aInfo); Error AppendRouterNeighborTlvs(Coap::Message *&aAnswer, AnswerInfo &aInfo); Error AppendChildTableIp6AddressList(Coap::Message *&aAnswer, AnswerInfo &aInfo); - Error AppendChildIp6AddressListTlv(Coap::Message &aAnswer, const Child &aChild); + Error AppendChildIp6AddressListTlv(Message &aAnswer, const Child &aChild); Error AppendEnhancedRoute(Message &aMessage); +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE + Error AppendChildTableAsChildTlvs(Message &aMessage); + Error AppendRouterNeighborTlvs(Message &aMessage); + Error AppendChildTableIp6AddressList(Message &aMessage); +#endif + static void HandleAnswerResponse(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo, diff --git a/tests/scripts/expect/cli-tcat-diagnostics.exp b/tests/scripts/expect/cli-tcat-diagnostics.exp new file mode 100755 index 000000000..5b492320a --- /dev/null +++ b/tests/scripts/expect/cli-tcat-diagnostics.exp @@ -0,0 +1,98 @@ +#!/usr/bin/expect -f +# +# Copyright (c) 2025, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +source "tests/scripts/expect/_common.exp" +source "tests/scripts/expect/_multinode.exp" + +spawn_node 2 "cli" +spawn_node 1 "cli" +setup_leader +setup_node 2 + +switch_node 1 + +spawn_tcat_client_for_node 1 + +send "diagnostic_tlvs extaddr\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect_line "\tLEN:\t10" +expect -re {\tVALUE:\t0x0008([0-9a-fA-F]{16})} + +send "diagnostic_tlvs macaddr\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect_line "\tLEN:\t4" +expect -re {\tVALUE:\t0x0102([0-9a-fA-F]{4})} + +send "diagnostic_tlvs mode timeout connectivity\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect_line "\tLEN:\t15" +expect -re {\tVALUE:\t0x0201([0-9a-fA-F]{2})040a([0-9a-fA-F]{20})} + +send "diagnostic_tlvs route64 leaderdata\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect_line "\tLEN:\t22" +expect -re {\tVALUE:\t0x050a([0-9a-fA-F]{20})0608([0-9a-fA-F]{16})} + +send "diagnostic_tlvs ipaddr\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect -re {\tLEN:\t([0-9]{2,3})} +expect -re {\tVALUE:\t0x08([0-9a-fA-F]{33,197})} + +send "diagnostic_tlvs mlecounters\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect_line "\tLEN:\t68" +expect -re {\tVALUE:\t0x2242([0-9a-fA-F]{132})} + +send "diagnostic_tlvs channelpages maxchildtimeout eui64 vendorname vendormodel vendorswversion vendorappurl\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect_line "\tLEN:\t27" +expect_line "\tVALUE:\t0x1101001304000000f0170818b430000000000119001a001b002300" + +send "diagnostic_tlvs childtable\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect_line "\tLEN:\t5" +expect -re {\tVALUE:\t0x1003([0-9a-fA-F]{6})} + +send "diagnostic_tlvs child\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect_line "\tLEN:\t47" +expect -re {\tVALUE:\t0x1d2b([0-9a-fA-F]{86})1d00} + +send "diagnostic_tlvs childipv6list routerneighbor\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect -re {\tLEN:\t([0-9]{2})} +expect -re {\tVALUE:\t0x1e([0-9a-fA-F]{6,69})1e001f00} + +dispose_tcat_client 1 + +switch_node 1 +send "tcat stop\n" +expect_line "Done" + +dispose_all diff --git a/tools/tcat_ble_client/cli/base_commands.py b/tools/tcat_ble_client/cli/base_commands.py index 0286dc522..2305365d5 100644 --- a/tools/tcat_ble_client/cli/base_commands.py +++ b/tools/tcat_ble_client/cli/base_commands.py @@ -33,6 +33,7 @@ from ble.ble_stream import BleStream from ble.ble_stream_secure import BleStreamSecure from ble import ble_scanner from tlv.tlv import TLV +from tlv.diagnostic_tlv import DiagnosticTLVType from tlv.tcat_tlv import TcatTLVType from cli.command import Command, CommandResultNone, CommandResultTLV from dataset.dataset import ThreadDataset @@ -386,6 +387,31 @@ class ScanCommand(Command): return CommandResultNone() +class DiagnosticTlvsCommand(BleCommand): + + def get_log_string(self) -> str: + return 'Retrieving diagnostic information.' + + def get_help_string(self) -> str: + return 'Get diagnostic TLVs from the TCAT device.' + + def prepare_data(self, args, context): + num_args = DiagnosticTLVType.names_to_numbers(args) + try: + if not num_args: + raise ValueError() + vals = [int(x) for x in num_args] + tlvs = bytes(vals) + except ValueError: + print('Please provide a list of diagnostic TLV types as names or numbers') + print('TLV Types:') + for key, value in DiagnosticTLVType.get_dict().items(): + print(f'{key} = {value},') + raise DataNotPrepared() + + return TLV(TcatTLVType.GET_DIAGNOSTIC_TLVS.value, tlvs).to_bytes() + + class ThreadStartCommand(BleCommand): def get_log_string(self) -> str: diff --git a/tools/tcat_ble_client/cli/cli.py b/tools/tcat_ble_client/cli/cli.py index 7137890cf..9d73411a5 100644 --- a/tools/tcat_ble_client/cli/cli.py +++ b/tools/tcat_ble_client/cli/cli.py @@ -32,7 +32,8 @@ from ble.ble_stream_secure import BleStreamSecure from cli.base_commands import (DisconnectCommand, HelpCommand, HelloCommand, CommissionCommand, DecommissionCommand, ExtractDatasetCommand, GetCommissionerCertificate, GetDeviceIdCommand, GetPskdHash, GetExtPanIDCommand, GetNetworkNameCommand, GetProvisioningUrlCommand, PingCommand, - GetRandomNumberChallenge, ThreadStateCommand, ScanCommand, PresentHash) + GetRandomNumberChallenge, ThreadStateCommand, ScanCommand, PresentHash, + DiagnosticTlvsCommand) from .tlv_commands import TlvCommand from cli.dataset_commands import (DatasetCommand) from dataset.dataset import ThreadDataset @@ -65,6 +66,7 @@ class CLI: 'peer_pskd_hash': GetPskdHash(), 'tlv': TlvCommand(), 'get_comm_cert': GetCommissionerCertificate(), + 'diagnostic_tlvs': DiagnosticTlvsCommand() } self._context = { 'ble_sstream': ble_sstream, diff --git a/tools/tcat_ble_client/tlv/diagnostic_tlv.py b/tools/tcat_ble_client/tlv/diagnostic_tlv.py new file mode 100644 index 000000000..0b8ea89be --- /dev/null +++ b/tools/tcat_ble_client/tlv/diagnostic_tlv.py @@ -0,0 +1,71 @@ +""" + Copyright (c) 2025, The OpenThread Authors. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +""" + + +class DiagnosticTLVType: + + def __init__(self): + self._tlv_dict = { + 'extaddr': '0', + 'macaddr': '1', + 'mode': '2', + 'timeout': '3', + 'connectivity': '4', + 'route64': '5', + 'leaderdata': '6', + 'networkdata': '7', + 'ipaddr': '8', + 'maccounters': '9', + 'batterylevel': '14', + 'supplyvoltage': '15', + 'childtable': '16', + 'channelpages': '17', + 'maxchildtimeout': '19', + 'eui64': '23', + 'version': '24', + 'vendorname': '25', + 'vendormodel': '26', + 'vendorswversion': '27', + 'threadstackversion': '28', + 'child': '29', + 'childipv6list': '30', + 'routerneighbor': '31', + 'mlecounters': '34', + 'vendorappurl': '35', + 'channeldenylist': '36' + } + + @staticmethod + def names_to_numbers(args): + res = DiagnosticTLVType() + return [x if x not in res._tlv_dict else res._tlv_dict[x] for x in args] + + @staticmethod + def get_dict(): + res = DiagnosticTLVType() + return res._tlv_dict diff --git a/tools/tcat_ble_client/tlv/tcat_tlv.py b/tools/tcat_ble_client/tlv/tcat_tlv.py index 8a001d51e..e78e23c96 100644 --- a/tools/tcat_ble_client/tlv/tcat_tlv.py +++ b/tools/tcat_ble_client/tlv/tcat_tlv.py @@ -45,6 +45,7 @@ class TcatTLVType(Enum): ACTIVE_DATASET = 0x20 GET_COMMISSIONER_CERTIFICATE = 0x25 GET_ACTIVE_DATASET = 0x40 + GET_DIAGNOSTIC_TLVS = 0x26 DECOMMISSION = 0x60 APPLICATION = 0x82 THREAD_START = 0x27