diff --git a/src/core/meshcop/tcat_agent.cpp b/src/core/meshcop/tcat_agent.cpp index 11c1859229c..49470828dc0 100644 --- a/src/core/meshcop/tcat_agent.cpp +++ b/src/core/meshcop/tcat_agent.cpp @@ -355,21 +355,21 @@ bool TcatAgent::CanProcessTlv(uint8_t aTlvType) const return IsCommandClassAuthorized(tlvCommandClass); } -Error TcatAgent::HandleSingleTlv(const Message &aIncommingMessage, Message &aOutgoingMessage) +Error TcatAgent::HandleSingleTlv(const Message &aIncomingMessage, Message &aOutgoingMessage) { Error error = kErrorParse; ot::Tlv tlv; - uint16_t offset = aIncommingMessage.GetOffset(); + uint16_t offset = aIncomingMessage.GetOffset(); uint16_t length; bool response = false; VerifyOrExit(IsConnected(), error = kErrorInvalidState); - SuccessOrExit(error = aIncommingMessage.Read(offset, tlv)); + SuccessOrExit(error = aIncomingMessage.Read(offset, tlv)); if (tlv.IsExtended()) { ot::ExtendedTlv extTlv; - SuccessOrExit(error = aIncommingMessage.Read(offset, extTlv)); + SuccessOrExit(error = aIncomingMessage.Read(offset, extTlv)); length = extTlv.GetLength(); offset += sizeof(ot::ExtendedTlv); } @@ -392,7 +392,7 @@ Error TcatAgent::HandleSingleTlv(const Message &aIncommingMessage, Message &aOut break; case kTlvSetActiveOperationalDataset: - error = HandleSetActiveOperationalDataset(aIncommingMessage, offset, length); + error = HandleSetActiveOperationalDataset(aIncomingMessage, offset, length); break; case kTlvStartThreadInterface: @@ -405,7 +405,7 @@ Error TcatAgent::HandleSingleTlv(const Message &aIncommingMessage, Message &aOut case kTlvSendApplicationData: LogInfo("Application data len:%d, offset:%d", length, offset); - mAppDataReceiveCallback.InvokeIfSet(&GetInstance(), &aIncommingMessage, offset, + mAppDataReceiveCallback.InvokeIfSet(&GetInstance(), &aIncomingMessage, offset, MapEnum(mCurrentApplicationProtocol), mCurrentServiceName); response = true; error = kErrorNone; @@ -413,6 +413,13 @@ Error TcatAgent::HandleSingleTlv(const Message &aIncommingMessage, Message &aOut case kTlvDecommission: error = HandleDecomission(); break; + case kTlvPing: + error = HandlePing(aIncomingMessage, aOutgoingMessage, offset, length); + if (error == kErrorNone) + { + response = true; + } + break; default: error = kErrorInvalidCommand; } @@ -460,14 +467,14 @@ Error TcatAgent::HandleSingleTlv(const Message &aIncommingMessage, Message &aOut return error; } -Error TcatAgent::HandleSetActiveOperationalDataset(const Message &aIncommingMessage, uint16_t aOffset, uint16_t aLength) +Error TcatAgent::HandleSetActiveOperationalDataset(const Message &aIncomingMessage, uint16_t aOffset, uint16_t aLength) { Dataset dataset; OffsetRange offsetRange; Error error; offsetRange.Init(aOffset, aLength); - SuccessOrExit(error = dataset.SetFrom(aIncommingMessage, offsetRange)); + SuccessOrExit(error = dataset.SetFrom(aIncomingMessage, offsetRange)); SuccessOrExit(error = dataset.ValidateTlvs()); if (!CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mApplicationFlags, @@ -504,6 +511,35 @@ Error TcatAgent::HandleDecomission(void) return error; } +Error TcatAgent::HandlePing(const Message &aIncomingMessage, + Message &aOutgoingMessage, + uint16_t aOffset, + uint16_t aLength) +{ + Error error = kErrorNone; + ot::ExtendedTlv extTlv; + ot::Tlv tlv; + + VerifyOrExit(aLength <= kPingPayloadMaxLength, error = kErrorParse); + if (aLength > ot::Tlv::kBaseTlvMaxLength) + { + extTlv.SetType(kTlvResponseWithPayload); + extTlv.SetLength(aLength); + SuccessOrExit(error = aOutgoingMessage.Append(extTlv)); + } + else + { + tlv.SetType(kTlvResponseWithPayload); + tlv.SetLength(static_cast(aLength)); + SuccessOrExit(error = aOutgoingMessage.Append(tlv)); + } + + SuccessOrExit(error = aOutgoingMessage.AppendBytesFromMessage(aIncomingMessage, aOffset, aLength)); + +exit: + return error; +} + Error TcatAgent::HandleStartThreadInterface(void) { Error error; diff --git a/src/core/meshcop/tcat_agent.hpp b/src/core/meshcop/tcat_agent.hpp index a95b42cfd06..5d6d0dd9797 100644 --- a/src/core/meshcop/tcat_agent.hpp +++ b/src/core/meshcop/tcat_agent.hpp @@ -350,9 +350,10 @@ class TcatAgent : public InstanceLocator, private NonCopyable Error Connected(MeshCoP::SecureTransport &aTlsContext); void Disconnected(void); - Error HandleSingleTlv(const Message &aIncommingMessage, Message &aOutgoingMessage); - Error HandleSetActiveOperationalDataset(const Message &aIncommingMessage, uint16_t aOffset, uint16_t aLength); + 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 HandleStartThreadInterface(void); bool CheckCommandClassAuthorizationFlags(CommandClassFlags aCommissionerCommandClassFlags, @@ -361,7 +362,8 @@ class TcatAgent : public InstanceLocator, private NonCopyable bool CanProcessTlv(uint8_t aTlvType) const; CommandClass GetCommandClass(uint8_t aTlvType) const; - static constexpr uint16_t kJoinerUdpPort = OPENTHREAD_CONFIG_JOINER_UDP_PORT; + static constexpr uint16_t kJoinerUdpPort = OPENTHREAD_CONFIG_JOINER_UDP_PORT; + static constexpr uint16_t kPingPayloadMaxLength = 512; JoinerPskd mJoinerPskd; const VendorInfo *mVendorInfo; diff --git a/tests/scripts/expect/cli-tcat.exp b/tests/scripts/expect/cli-tcat.exp index 00ce08710e3..9617cb39a3a 100755 --- a/tests/scripts/expect/cli-tcat.exp +++ b/tests/scripts/expect/cli-tcat.exp @@ -46,6 +46,18 @@ send "thread start\n" expect_line "\tTYPE:\tRESPONSE_W_STATUS" expect_line "\tVALUE:\t0x00" +send "ping\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect_line "\tLEN:\t10" + +send "ping 255\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect_line "\tLEN:\t255" + +send "ping 512\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect_line "\tLEN:\t512" + 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 20d48dcb3bb..bb0258ee27e 100644 --- a/tools/tcat_ble_client/cli/base_commands.py +++ b/tools/tcat_ble_client/cli/base_commands.py @@ -37,6 +37,8 @@ from dataset.dataset import ThreadDataset from utils import select_device_by_user_input from os import path +from time import time +from secrets import token_bytes class HelpCommand(Command): @@ -103,6 +105,37 @@ async def execute_default(self, args, context): return CommandResultTLV(tlv_response) +class PingCommand(Command): + + def get_help_string(self) -> str: + return 'Send echo request to TCAT device.' + + async def execute_default(self, args, context): + bless: BleStreamSecure = context['ble_sstream'] + payload_size = 10 + max_payload = 512 + if len(args) > 0: + payload_size = int(args[0]) + if payload_size > max_payload: + print(f'Payload size too large. Maximum supported value is {max_payload}') + return + to_send = token_bytes(payload_size) + data = TLV(TcatTLVType.PING.value, to_send).to_bytes() + elapsed_time = time() + response = await bless.send_with_resp(data) + elapsed_time = time() - elapsed_time + if not response: + return + + tlv_response = TLV.from_bytes(response) + if tlv_response.value != to_send: + print("Received malformed response.") + + print(f"Roundtrip time {elapsed_time} s.") + + return CommandResultTLV(tlv_response) + + class ThreadStartCommand(Command): def get_help_string(self) -> str: diff --git a/tools/tcat_ble_client/cli/cli.py b/tools/tcat_ble_client/cli/cli.py index 83829580561..28c7fb9a176 100644 --- a/tools/tcat_ble_client/cli/cli.py +++ b/tools/tcat_ble_client/cli/cli.py @@ -29,8 +29,8 @@ import readline import shlex from ble.ble_stream_secure import BleStreamSecure -from cli.base_commands import (HelpCommand, HelloCommand, CommissionCommand, DecommissionCommand, ThreadStateCommand, - ScanCommand) +from cli.base_commands import (HelpCommand, HelloCommand, CommissionCommand, DecommissionCommand, PingCommand, + ThreadStateCommand, ScanCommand) from cli.dataset_commands import (DatasetCommand) from dataset.dataset import ThreadDataset from typing import Optional @@ -44,6 +44,7 @@ def __init__(self, dataset: ThreadDataset, ble_sstream: Optional[BleStreamSecure 'hello': HelloCommand(), 'commission': CommissionCommand(), 'decommission': DecommissionCommand(), + 'ping': PingCommand(), 'dataset': DatasetCommand(), 'thread': ThreadStateCommand(), 'scan': ScanCommand(), diff --git a/tools/tcat_ble_client/tlv/tcat_tlv.py b/tools/tcat_ble_client/tlv/tcat_tlv.py index 30ca543bfae..a276cafb200 100644 --- a/tools/tcat_ble_client/tlv/tcat_tlv.py +++ b/tools/tcat_ble_client/tlv/tcat_tlv.py @@ -32,6 +32,7 @@ class TcatTLVType(Enum): RESPONSE_W_STATUS = 0x01 RESPONSE_W_PAYLOAD = 0x02 DISCONNECT = 0x09 + PING = 0x0A ACTIVE_DATASET = 0x20 DECOMMISSION = 0x60 APPLICATION = 0x82 diff --git a/tools/tcat_ble_client/tlv/tlv.py b/tools/tcat_ble_client/tlv/tlv.py index 75657c41a9a..f8904fc4ecd 100644 --- a/tools/tcat_ble_client/tlv/tlv.py +++ b/tools/tcat_ble_client/tlv/tlv.py @@ -58,14 +58,19 @@ def from_bytes(data: bytes) -> TLV: def set_from_bytes(self, data: bytes): self.type = data[0] header_len = 2 + size_offset = 1 if data[1] == 0xFF: header_len = 4 - length = int.from_bytes(data[1:header_len], byteorder='big') + size_offset = 2 + length = int.from_bytes(data[size_offset:header_len], byteorder='big') self.value = data[header_len:header_len + length] def to_bytes(self) -> bytes: - has_long_header = len(self.value) >= 255 + has_long_header = len(self.value) >= 254 header_len = 4 if has_long_header else 2 - len_bytes = len(self.value).to_bytes(header_len - 1, byteorder='big') - header = bytes([self.type]) + len_bytes + len_bytes = len(self.value).to_bytes(header_len // 2, byteorder='big') + type = self.type + if has_long_header: + type = type << 8 | 255 + header = type.to_bytes(header_len // 2, byteorder='big') + len_bytes return header + self.value