From a056114da8b96a42c6235d6ff0c7f4d348caa2f0 Mon Sep 17 00:00:00 2001 From: Przemyslaw Bida Date: Fri, 19 Jul 2024 13:32:37 +0200 Subject: [PATCH] [tcat] Implement new tcat General commands. New General TLV's implemented: - Get network name - Get device id - Get ext pan ID - get provisioning URL --- src/cli/cli_tcat.cpp | 18 +++ src/core/meshcop/tcat_agent.cpp | 117 +++++++++++++++-- src/core/meshcop/tcat_agent.hpp | 11 +- tests/scripts/expect/cli-tcat.exp | 25 ++++ tools/tcat_ble_client/cli/base_commands.py | 141 ++++++++++++++------- tools/tcat_ble_client/cli/cli.py | 7 +- tools/tcat_ble_client/cli/command.py | 3 +- tools/tcat_ble_client/tlv/tcat_tlv.py | 4 + 8 files changed, 269 insertions(+), 57 deletions(-) diff --git a/src/cli/cli_tcat.cpp b/src/cli/cli_tcat.cpp index ab29bd0fdeef..1cdfdbf90d8c 100644 --- a/src/cli/cli_tcat.cpp +++ b/src/cli/cli_tcat.cpp @@ -93,6 +93,19 @@ otTcatDeviceId sVendorDeviceIds[OT_TCAT_DEVICE_ID_MAX]; const char kPskdVendor[] = "JJJJJJ"; const char kUrl[] = "dummy_url"; +static bool IsDeviceIdSet(void) +{ + bool ret = false; + + for (otTcatDeviceId &vendorDeviceId : sVendorDeviceIds) + { + VerifyOrExit(vendorDeviceId.mDeviceIdType == OT_TCAT_DEVICE_ID_EMPTY, ret = true); + } + +exit: + return ret; +} + static void HandleBleSecureReceive(otInstance *aInstance, const otMessage *aMessage, int32_t aOffset, @@ -210,6 +223,11 @@ template <> otError Tcat::Process(Arg aArgs[]) mVendorInfo.mPskdString = kPskdVendor; mVendorInfo.mProvisioningUrl = kUrl; + if (IsDeviceIdSet()) + { + mVendorInfo.mDeviceIds = sVendorDeviceIds; + } + otBleSecureSetCertificate(GetInstancePtr(), reinterpret_cast(OT_CLI_TCAT_X509_CERT), sizeof(OT_CLI_TCAT_X509_CERT), reinterpret_cast(OT_CLI_TCAT_PRIV_KEY), sizeof(OT_CLI_TCAT_PRIV_KEY)); diff --git a/src/core/meshcop/tcat_agent.cpp b/src/core/meshcop/tcat_agent.cpp index 49470828dc07..7821fb10d292 100644 --- a/src/core/meshcop/tcat_agent.cpp +++ b/src/core/meshcop/tcat_agent.cpp @@ -33,6 +33,7 @@ #include "tcat_agent.hpp" #include +#include "meshcop/network_name.hpp" #if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE @@ -55,7 +56,10 @@ RegisterLogModule("TcatAgent"); bool TcatAgent::VendorInfo::IsValid(void) const { - return mProvisioningUrl == nullptr || IsValidUtf8String(mProvisioningUrl) || mPskdString != nullptr; + return (mProvisioningUrl == nullptr || + (IsValidUtf8String(mProvisioningUrl) && + (static_cast(strlen(mProvisioningUrl)) < Tlv::kBaseTlvMaxLength))) || + mPskdString != nullptr; } TcatAgent::TcatAgent(Instance &aInstance) @@ -414,17 +418,24 @@ Error TcatAgent::HandleSingleTlv(const Message &aIncomingMessage, Message &aOutg error = HandleDecomission(); break; case kTlvPing: - error = HandlePing(aIncomingMessage, aOutgoingMessage, offset, length); - if (error == kErrorNone) - { - response = true; - } + error = HandlePing(aIncomingMessage, aOutgoingMessage, offset, length, response); + break; + case kTlvGetNetworkName: + error = HandleGetNetworkName(aOutgoingMessage, response); + break; + case kTlvGetDeviceId: + error = HandleGetDeviceId(aOutgoingMessage, response); + break; + case kTlvGetExtendedPanID: + error = HandleGetExtPanId(aOutgoingMessage, response); + break; + case kTlvGetProvisioningURL: + error = HandleGetProvisioningUrl(aOutgoingMessage, response); break; default: error = kErrorInvalidCommand; } } - if (!response) { StatusCode statusCode; @@ -514,7 +525,8 @@ Error TcatAgent::HandleDecomission(void) Error TcatAgent::HandlePing(const Message &aIncomingMessage, Message &aOutgoingMessage, uint16_t aOffset, - uint16_t aLength) + uint16_t aLength, + bool &response) { Error error = kErrorNone; ot::ExtendedTlv extTlv; @@ -535,6 +547,95 @@ Error TcatAgent::HandlePing(const Message &aIncomingMessage, } SuccessOrExit(error = aOutgoingMessage.AppendBytesFromMessage(aIncomingMessage, aOffset, aLength)); + response = true; + +exit: + return error; +} + +Error TcatAgent::HandleGetNetworkName(Message &aOutgoingMessage, bool &response) +{ + Error error = kErrorNone; + MeshCoP::NameData nameData = Get().GetNetworkName().GetAsData(); + + VerifyOrExit(Get().IsCommissioned(), error = kErrorInvalidState); +#if !OPENTHREAD_CONFIG_ALLOW_EMPTY_NETWORK_NAME + VerifyOrExit(nameData.GetLength() > 0, error = kErrorInvalidState); +#endif + + SuccessOrExit( + error = Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload, nameData.GetBuffer(), nameData.GetLength())); + response = true; + +exit: + return error; +} + +Error TcatAgent::HandleGetDeviceId(Message &aOutgoingMessage, bool &response) +{ + uint8_t deviceId[sizeof(Mac::ExtAddress)]; + uint16_t length = 0; + ot::Tlv tlv; + Error error = kErrorNone; + + static_assert(OT_EXT_ADDRESS_SIZE > OT_TCAT_MAX_VENDORID_SIZE, "Size of TCAT vendor id is greater than extaddr."); + + if (mVendorInfo->mDeviceIds != nullptr) + { + // If array is not null return first set device ID. + for (uint8_t i = 0; mVendorInfo->mDeviceIds[i].mDeviceIdType != OT_TCAT_DEVICE_ID_EMPTY; i++) + { + length = mVendorInfo->mDeviceIds->mDeviceIdLen; + memcpy(deviceId, mVendorInfo->mDeviceIds[i].mDeviceId, length); + break; + } + } + + if (length == 0) + { + // If array is null or contains only empty entries return EUI64. + otPlatRadioGetIeeeEui64(&GetInstance(), deviceId); + length = OT_EXT_ADDRESS_SIZE; + } + + tlv.SetType(kTlvResponseWithPayload); + tlv.SetLength(static_cast(length)); + SuccessOrExit(error = aOutgoingMessage.Append(tlv)); + SuccessOrExit(error = aOutgoingMessage.AppendBytes(deviceId, length)); + + response = true; + +exit: + return error; +} + +Error TcatAgent::HandleGetExtPanId(Message &aOutgoingMessage, bool &response) +{ + Error error; + + VerifyOrExit(Get().IsCommissioned(), error = kErrorInvalidState); + + SuccessOrExit(error = Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload, + &Get().GetExtPanId(), sizeof(ExtendedPanId))); + response = true; + +exit: + return error; +} + +Error TcatAgent::HandleGetProvisioningUrl(Message &aOutgoingMessage, bool &response) +{ + Error error = kErrorNone; + uint16_t length; + + VerifyOrExit(mVendorInfo->mProvisioningUrl != nullptr, error = kErrorInvalidState); + + length = StringLength(mVendorInfo->mProvisioningUrl, Tlv::kBaseTlvMaxLength + 1); + VerifyOrExit(length > 0 && length <= Tlv::kBaseTlvMaxLength, error = kErrorInvalidState); + + error = Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload, mVendorInfo->mProvisioningUrl, + static_cast(length)); + response = true; exit: return error; diff --git a/src/core/meshcop/tcat_agent.hpp b/src/core/meshcop/tcat_agent.hpp index 5d6d0dd9797e..66b89d62255a 100644 --- a/src/core/meshcop/tcat_agent.hpp +++ b/src/core/meshcop/tcat_agent.hpp @@ -168,6 +168,7 @@ class TcatAgent : public InstanceLocator, private NonCopyable kTlvPing = 10, ///< TCAT ping request TLV kTlvGetDeviceId = 11, ///< TCAT device ID query TLV kTlvGetExtendedPanID = 12, ///< TCAT extended PAN ID query TLV + kTlvGetProvisioningURL = 13, ///< TCAT provisioning URL query TLV kTlvPresentPskdHash = 16, ///< TCAT commissioner rights elevation request TLV using PSKd hash kTlvPresentPskcHash = 17, ///< TCAT commissioner rights elevation request TLV using PSKc hash kTlvPresentInstallCodeHash = 18, ///< TCAT commissioner rights elevation request TLV using install code @@ -353,7 +354,15 @@ class TcatAgent : public InstanceLocator, private NonCopyable Error HandleSingleTlv(const Message &aIncomingMessage, Message &aOutgoingMessage); Error HandleSetActiveOperationalDataset(const Message &aIncomingMessage, uint16_t aOffset, uint16_t aLength); Error HandleDecomission(void); - Error HandlePing(const Message &aIncomingMessage, Message &aOutgoingMessage, uint16_t aOffset, uint16_t aLength); + Error HandlePing(const Message &aIncomingMessage, + Message &aOutgoingMessage, + uint16_t aOffset, + uint16_t aLength, + bool &response); + Error HandleGetNetworkName(Message &aOutgoingMessage, bool &response); + Error HandleGetDeviceId(Message &aOutgoingMessage, bool &response); + Error HandleGetExtPanId(Message &aOutgoingMessage, bool &response); + Error HandleGetProvisioningUrl(Message &aOutgoingMessage, bool &response); Error HandleStartThreadInterface(void); bool CheckCommandClassAuthorizationFlags(CommandClassFlags aCommissionerCommandClassFlags, diff --git a/tests/scripts/expect/cli-tcat.exp b/tests/scripts/expect/cli-tcat.exp index 9617cb39a3a6..53d344b89847 100755 --- a/tests/scripts/expect/cli-tcat.exp +++ b/tests/scripts/expect/cli-tcat.exp @@ -38,6 +38,11 @@ expect_line "Done" spawn python "tools/tcat_ble_client/bbtc.py" --simulation 1 --cert_path "tools/tcat_ble_client/auth" set py_client "$spawn_id" expect_line "Done" + +send "network_name\n" +expect_line "\tTYPE:\tRESPONSE_W_STATUS" +expect_line "\tVALUE:\t0x06" + send "commission\n" expect_line "\tTYPE:\tRESPONSE_W_STATUS" expect_line "\tVALUE:\t0x00" @@ -58,6 +63,26 @@ send "ping 512\n" expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" expect_line "\tLEN:\t512" +send "network_name\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect_line "\tLEN:\t15" +expect_line "\tVALUE:\t0x4f70656e5468726561642d63363465" + +send "device_id\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect_line "\tLEN:\t8" +expect_line "\tVALUE:\t0x18b4300000000001" + +send "ext_panid\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect_line "\tLEN:\t8" +expect_line "\tVALUE:\t0xef1398c2fd504b67" + +send "provisioning_url\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect_line "\tLEN:\t9" +expect_line "\tVALUE:\t0x64756d6d795f75726c" + send "exit\n" expect eof diff --git a/tools/tcat_ble_client/cli/base_commands.py b/tools/tcat_ble_client/cli/base_commands.py index bb0258ee27e8..e415001d14fd 100644 --- a/tools/tcat_ble_client/cli/base_commands.py +++ b/tools/tcat_ble_client/cli/base_commands.py @@ -26,6 +26,7 @@ POSSIBILITY OF SUCH DAMAGE. """ +from abc import abstractmethod from ble.ble_connection_constants import BBTC_SERVICE_UUID, BBTC_TX_CHAR_UUID, \ BBTC_RX_CHAR_UUID from ble.ble_stream import BleStream @@ -54,15 +55,21 @@ async def execute_default(self, args, context): return CommandResultNone() -class HelloCommand(Command): +class BleCommand(Command): - def get_help_string(self) -> str: - return 'Send round trip "Hello world!" message.' + @abstractmethod + def get_log_string(self) -> str: + pass + + @abstractmethod + def prepare_data(self, context): + pass async def execute_default(self, args, context): bless: BleStreamSecure = context['ble_sstream'] - print('Sending hello world...') - data = TLV(TcatTLVType.APPLICATION.value, bytes('Hello world!', 'ascii')).to_bytes() + + print(self.get_log_string()) + data = self.prepare_data(context) response = await bless.send_with_resp(data) if not response: return @@ -70,39 +77,90 @@ async def execute_default(self, args, context): return CommandResultTLV(tlv_response) -class CommissionCommand(Command): +class HelloCommand(BleCommand): + + def get_log_string(self) -> str: + return 'Sending hello world...' + + def get_help_string(self) -> str: + return 'Send round trip "Hello world!" message.' + + def prepare_data(self, context): + return TLV(TcatTLVType.APPLICATION.value, bytes('Hello world!', 'ascii')).to_bytes() + + +class CommissionCommand(BleCommand): + + def get_log_string(self) -> str: + return 'Commissioning...' def get_help_string(self) -> str: return 'Update the connected device with current dataset.' - async def execute_default(self, args, context): - bless: BleStreamSecure = context['ble_sstream'] + def prepare_data(self, context): dataset: ThreadDataset = context['dataset'] - - print('Commissioning...') dataset_bytes = dataset.to_bytes() - data = TLV(TcatTLVType.ACTIVE_DATASET.value, dataset_bytes).to_bytes() - response = await bless.send_with_resp(data) - if not response: - return - tlv_response = TLV.from_bytes(response) - return CommandResultTLV(tlv_response) + return TLV(TcatTLVType.ACTIVE_DATASET.value, dataset_bytes).to_bytes() -class DecommissionCommand(Command): +class DecommissionCommand(BleCommand): + + def get_log_string(self) -> str: + return 'Disabling Thread and decommissioning device...' def get_help_string(self) -> str: return 'Stop Thread interface and decommission device from current network.' - async def execute_default(self, args, context): - bless: BleStreamSecure = context['ble_sstream'] - print('Disabling Thread and decommissioning device...') - data = (TLV(TcatTLVType.DECOMMISSION.value, bytes()).to_bytes()) - response = await bless.send_with_resp(data) - if not response: - return - tlv_response = TLV.from_bytes(response) - return CommandResultTLV(tlv_response) + def prepare_data(self, context): + return TLV(TcatTLVType.DECOMMISSION.value, bytes()).to_bytes() + + +class GetDeviceIdCommand(BleCommand): + + def get_log_string(self) -> str: + return 'Retrieving device id.' + + def get_help_string(self) -> str: + return 'Get unique identifier for the TCAT device.' + + def prepare_data(self, context): + return TLV(TcatTLVType.GET_DEVICE_ID.value, bytes()).to_bytes() + + +class GetExtPanIDCommand(BleCommand): + + def get_log_string(self) -> str: + return 'Retrieving extended PAN ID.' + + def get_help_string(self) -> str: + return 'Get extended PAN ID that is commissioned in the active dataset.' + + def prepare_data(self, context): + return TLV(TcatTLVType.GET_EXT_PAN_ID.value, bytes()).to_bytes() + + +class GetProvisioningUrlCommand(BleCommand): + + def get_log_string(self) -> str: + return 'Retrieving provisioning url.' + + def get_help_string(self) -> str: + return 'Get a URL for an application suited to commission device.' + + def prepare_data(self, context): + return TLV(TcatTLVType.GET_PROVISIONING_URL.value, bytes()).to_bytes() + + +class GetNetworkNameCommand(BleCommand): + + def get_log_string(self) -> str: + return 'Retrieving network name.' + + def get_help_string(self) -> str: + return 'Get the network name that is commissioned in the active dataset.' + + def prepare_data(self, context): + return TLV(TcatTLVType.GET_NETWORK_NAME.value, bytes()).to_bytes() class PingCommand(Command): @@ -136,37 +194,28 @@ async def execute_default(self, args, context): return CommandResultTLV(tlv_response) -class ThreadStartCommand(Command): +class ThreadStartCommand(BleCommand): + + def get_log_string(self) -> str: + return 'Enabling Thread...' def get_help_string(self) -> str: return 'Enable thread interface.' - async def execute_default(self, args, context): - bless: BleStreamSecure = context['ble_sstream'] + def prepare_data(self, context): + return TLV(TcatTLVType.THREAD_START.value, bytes()).to_bytes() - print('Enabling Thread...') - data = TLV(TcatTLVType.THREAD_START.value, bytes()).to_bytes() - response = await bless.send_with_resp(data) - if not response: - return - tlv_response = TLV.from_bytes(response) - return CommandResultTLV(tlv_response) +class ThreadStopCommand(BleCommand): -class ThreadStopCommand(Command): + def get_log_string(self) -> str: + return 'Disabling Thread...' def get_help_string(self) -> str: return 'Disable thread interface.' - async def execute_default(self, args, context): - bless: BleStreamSecure = context['ble_sstream'] - print('Disabling Thread...') - data = TLV(TcatTLVType.THREAD_STOP.value, bytes()).to_bytes() - response = await bless.send_with_resp(data) - if not response: - return - tlv_response = TLV.from_bytes(response) - return CommandResultTLV(tlv_response) + def prepare_data(self, context): + return TLV(TcatTLVType.THREAD_STOP.value, bytes()).to_bytes() class ThreadStateCommand(Command): diff --git a/tools/tcat_ble_client/cli/cli.py b/tools/tcat_ble_client/cli/cli.py index 28c7fb9a1763..01e33bac31a5 100644 --- a/tools/tcat_ble_client/cli/cli.py +++ b/tools/tcat_ble_client/cli/cli.py @@ -29,7 +29,8 @@ import readline import shlex from ble.ble_stream_secure import BleStreamSecure -from cli.base_commands import (HelpCommand, HelloCommand, CommissionCommand, DecommissionCommand, PingCommand, +from cli.base_commands import (HelpCommand, HelloCommand, CommissionCommand, DecommissionCommand, GetDeviceIdCommand, + GetExtPanIDCommand, GetNetworkNameCommand, GetProvisioningUrlCommand, PingCommand, ThreadStateCommand, ScanCommand) from cli.dataset_commands import (DatasetCommand) from dataset.dataset import ThreadDataset @@ -44,6 +45,10 @@ def __init__(self, dataset: ThreadDataset, ble_sstream: Optional[BleStreamSecure 'hello': HelloCommand(), 'commission': CommissionCommand(), 'decommission': DecommissionCommand(), + 'device_id': GetDeviceIdCommand(), + 'ext_panid': GetExtPanIDCommand(), + 'provisioning_url': GetProvisioningUrlCommand(), + 'network_name': GetNetworkNameCommand(), 'ping': PingCommand(), 'dataset': DatasetCommand(), 'thread': ThreadStateCommand(), diff --git a/tools/tcat_ble_client/cli/command.py b/tools/tcat_ble_client/cli/command.py index f494a3e7d675..e6b819b10ba7 100644 --- a/tools/tcat_ble_client/cli/command.py +++ b/tools/tcat_ble_client/cli/command.py @@ -28,6 +28,7 @@ from tlv.tlv import TLV from tlv.tcat_tlv import TcatTLVType +from ble.ble_stream_secure import BleStreamSecure from abc import ABC, abstractmethod @@ -57,7 +58,7 @@ async def execute_subcommand(self, args, context) -> CommandResult: return await self._subcommands[args[0]].execute(args[1:], context) @abstractmethod - async def execute_default(self, args, context) -> CommandResult: + async def execute_default(self, args, context): pass @abstractmethod diff --git a/tools/tcat_ble_client/tlv/tcat_tlv.py b/tools/tcat_ble_client/tlv/tcat_tlv.py index a276cafb2007..7d26864ae787 100644 --- a/tools/tcat_ble_client/tlv/tcat_tlv.py +++ b/tools/tcat_ble_client/tlv/tcat_tlv.py @@ -31,8 +31,12 @@ class TcatTLVType(Enum): RESPONSE_W_STATUS = 0x01 RESPONSE_W_PAYLOAD = 0x02 + GET_NETWORK_NAME = 0x08 DISCONNECT = 0x09 PING = 0x0A + GET_DEVICE_ID = 0x0B + GET_EXT_PAN_ID = 0x0C + GET_PROVISIONING_URL = 0x0D ACTIVE_DATASET = 0x20 DECOMMISSION = 0x60 APPLICATION = 0x82