From bb5585d4127a5d34d62f7b02deef9ce6e8e9a123 Mon Sep 17 00:00:00 2001 From: arnulfrupp <51497546+arnulfrupp@users.noreply.github.com> Date: Wed, 30 Jul 2025 21:27:10 +0200 Subject: [PATCH] [tcat] add tcat implementations and bug fixes (#11402) Commit adds check if commissioning is possible and if the tcat device is already commissioned. Adds advertisement update on disconnected and role change. Fixes key handling for key references. Fixes the authorization processing. Implements recent changes of the application TLVs. --- examples/platforms/simulation/ble.c | 8 + include/openthread/ble_secure.h | 33 +- include/openthread/instance.h | 2 +- include/openthread/platform/ble.h | 16 + include/openthread/tcat.h | 63 +- src/cli/README_TCAT.md | 19 + src/cli/cli_tcat.cpp | 158 +++-- src/cli/cli_tcat.hpp | 2 + src/core/api/ble_secure_api.cpp | 7 +- src/core/meshcop/tcat_agent.cpp | 590 ++++++++++-------- src/core/meshcop/tcat_agent.hpp | 122 ++-- src/core/radio/ble_secure.cpp | 46 +- src/core/radio/ble_secure.hpp | 25 +- src/core/thread/mle.cpp | 5 + src/posix/platform/ble.cpp | 8 + tests/gtest/fake_platform.cpp | 2 + tests/scripts/expect/_common.exp | 4 +- .../scripts/expect/cli-tcat-decommission.exp | 49 +- tests/scripts/expect/cli-tcat-hashes.exp | 12 +- tests/scripts/expect/cli-tcat.exp | 32 +- tests/unit/test_platform.cpp | 8 + tools/tcat_ble_client/cli/base_commands.py | 97 ++- tools/tcat_ble_client/cli/cli.py | 9 +- tools/tcat_ble_client/cli/command.py | 5 +- .../dataset/dataset_entries.py | 35 +- tools/tcat_ble_client/poetry.lock | 97 +-- tools/tcat_ble_client/tlv/dataset_tlv.py | 2 + tools/tcat_ble_client/tlv/tcat_tlv.py | 9 +- 28 files changed, 1003 insertions(+), 462 deletions(-) diff --git a/examples/platforms/simulation/ble.c b/examples/platforms/simulation/ble.c index b757a5eba..2264ea366 100644 --- a/examples/platforms/simulation/ble.c +++ b/examples/platforms/simulation/ble.c @@ -244,6 +244,14 @@ otError otPlatBleGapAdvSetData(otInstance *aInstance, uint8_t *aAdvertisementDat return OT_ERROR_NONE; } +otError otPlatBleGapAdvUpdateData(otInstance *aInstance, uint8_t *aAdvertisementData, uint16_t aAdvertisementLen) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aAdvertisementData); + OT_UNUSED_VARIABLE(aAdvertisementLen); + return OT_ERROR_NONE; +} + bool otPlatBleSupportsMultiRadio(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); diff --git a/include/openthread/ble_secure.h b/include/openthread/ble_secure.h index fb96d4a8c..fe2c056e2 100644 --- a/include/openthread/ble_secure.h +++ b/include/openthread/ble_secure.h @@ -85,6 +85,10 @@ typedef void (*otHandleBleSecureConnect)(otInstance *aInstance, /** * Pointer to call when data was received over a BLE Secure TLS connection. + * + * When TCAT has been started, the TCAT agent automatically responds with status OT_TCAT_STATUS_UNSUPPORTED + * if no response has been generated or no handler is defined. The application may generate a response to + * incoming TCAT application data or vendor-specific data by calling `otBleSecureSendApplicationTlv`. */ typedef otHandleTcatApplicationDataReceive otHandleBleSecureReceive; @@ -409,17 +413,30 @@ otError otBleSecureSendMessage(otInstance *aInstance, otMessage *aMessage); otError otBleSecureSend(otInstance *aInstance, uint8_t *aBuf, uint16_t aLength); /** - * Sends a secure BLE data packet containing a TCAT Send Application Data TLV. + * Sends a secure BLE data packet containing application data directed to the application layer @p aApplicationProtocol + * or a response to the latest received application data packet. * - * @param[in] aInstance A pointer to an OpenThread instance. - * @param[in] aBuf A pointer to the data to send as the Value of the TCAT Send Application Data TLV. - * @param[in] aLength A number indicating the length of the data buffer. + * Only a single response can be sent while executing the `otHandleBleSecureReceive` handler. If no (further) response + * is expected `OT_ERROR_REJECTED` is returned. * - * @retval OT_ERROR_NONE Successfully sent data. - * @retval OT_ERROR_NO_BUFS Failed to allocate buffer memory. - * @retval OT_ERROR_INVALID_STATE TLS connection was not initialized. + * For responses with a payload @p aApplicationProtocol shall be set to `OT_TCAT_APPLICATION_PROTOCOL_PAYLOAD`. + * For responses with a status @p aApplicationProtocol shall be `OT_TCAT_APPLICATION_PROTOCOL_STATUS` and @ aBuf shall + * contain a single byte `otTcatStatusCode` value. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aApplicationProtocol An application protocol the data is directed to. + * @param[in] aBuf A pointer to the data to send as the Value of the TCAT Send Application Data TLV. + * @param[in] aLength A number indicating the length of the data buffer. + * + * @retval OT_ERROR_NONE Successfully sent data. + * @retval OT_ERROR_NO_BUFS Failed to allocate buffer memory. + * @retval OT_ERROR_INVALID_STATE TLS connection was not initialized. + * @retval OT_ERROR_REJECTED Application protocol is response with data or status but no response is pending. */ -otError otBleSecureSendApplicationTlv(otInstance *aInstance, uint8_t *aBuf, uint16_t aLength); +otError otBleSecureSendApplicationTlv(otInstance *aInstance, + otTcatApplicationProtocol aApplicationProtocol, + uint8_t *aBuf, + uint16_t aLength); /** * Flushes the send buffer. diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 0c7effe83..bc24a8b70 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -52,7 +52,7 @@ extern "C" { * * @note This number versions both OpenThread platform and user APIs. */ -#define OPENTHREAD_API_VERSION (520) +#define OPENTHREAD_API_VERSION (521) /** * @addtogroup api-instance diff --git a/include/openthread/platform/ble.h b/include/openthread/platform/ble.h index c75705a75..85c89a90b 100644 --- a/include/openthread/platform/ble.h +++ b/include/openthread/platform/ble.h @@ -189,6 +189,22 @@ otError otPlatBleGetAdvertisementBuffer(otInstance *aInstance, uint8_t **aAdvert */ otError otPlatBleGapAdvSetData(otInstance *aInstance, uint8_t *aAdvertisementData, uint16_t aAdvertisementLen); +/** + * Updates BLE Advertising data. + * + * @note This function shall be used only for BLE Peripheral role. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aAdvertisementData The formatted TCAT advertisement frame. + * @param[in] aAdvertisementLen The TCAT advertisement frame length. + * + * @retval OT_ERROR_NONE Advertising procedure has been started. + * @retval OT_ERROR_FAILED Update of data failed. + * @retval OT_ERROR_INVALID_ARGS Invalid value has been supplied. + * + */ +otError otPlatBleGapAdvUpdateData(otInstance *aInstance, uint8_t *aAdvertisementData, uint16_t aAdvertisementLen); + /** * Starts BLE Advertising procedure. * diff --git a/include/openthread/tcat.h b/include/openthread/tcat.h index 80256d7b8..99c5afd46 100644 --- a/include/openthread/tcat.h +++ b/include/openthread/tcat.h @@ -44,6 +44,7 @@ #ifndef OPENTHREAD_TCAT_H_ #define OPENTHREAD_TCAT_H_ +#include #include #include @@ -66,8 +67,9 @@ extern "C" { * @{ */ -#define OT_TCAT_MAX_SERVICE_NAME_LENGTH \ +#define OT_TCAT_SERVICE_NAME_MAX_LENGTH \ 15 ///< Maximum string length of a UDP or TCP service name (does not include null char). +#define OT_TCAT_APPLICATION_LAYER_MAX_COUNT 4 ///< Maximum number of application layer service names supported #define OT_TCAT_ADVERTISEMENT_MAX_LEN 29 ///< Maximum length of TCAT advertisement. #define OT_TCAT_OPCODE 0x2 ///< TCAT Advertisement Operation Code. @@ -85,9 +87,10 @@ typedef enum otTcatStatusCode OT_TCAT_STATUS_VALUE_ERROR = 3, ///< The value of the transmitted TLV has an error OT_TCAT_STATUS_GENERAL_ERROR = 4, ///< An error not matching any other category occurred OT_TCAT_STATUS_BUSY = 5, ///< Command cannot be executed because the resource is busy - OT_TCAT_STATUS_UNDEFINED = 6, ///< The requested value, data or service is not defined (currently) or not present - OT_TCAT_STATUS_HASH_ERROR = 7, ///< The hash value presented by the commissioner was incorrect - OT_TCAT_STATUS_UNAUTHORIZED = 16, ///< Sender does not have sufficient authorization for the given command + OT_TCAT_STATUS_UNDEFINED = 6, ///< The requested value, data or service is not defined (currently) or not present + OT_TCAT_STATUS_HASH_ERROR = 7, ///< The hash value presented by the commissioner was incorrect + OT_TCAT_STATUS_INVALID_STATE = 8, ///< The TCAT device is not in a correct state for the given command + OT_TCAT_STATUS_UNAUTHORIZED = 16, ///< Sender does not have sufficient authorization for the given command } otTcatStatusCode; @@ -96,9 +99,16 @@ typedef enum otTcatStatusCode */ typedef enum otTcatApplicationProtocol { - OT_TCAT_APPLICATION_PROTOCOL_NONE = 0, ///< Message which has been sent without activating the TCAT agent - OT_TCAT_APPLICATION_PROTOCOL_STATUS = 1, ///< Message directed to a UDP service - OT_TCAT_APPLICATION_PROTOCOL_TCP = 2, ///< Message directed to a TCP service + OT_TCAT_APPLICATION_PROTOCOL_NONE = 0, ///< Message which has been sent without activating the TCAT agent + OT_TCAT_APPLICATION_PROTOCOL_STATUS = 0x01, /** Message directed to any application protocol indicating a + response with status value (one byte otTcatStatusCode) */ + OT_TCAT_APPLICATION_PROTOCOL_RESPONSE = + 0x02, ///< Message directed to any application protocol indicating a response with payload + OT_TCAT_APPLICATION_PROTOCOL_1 = 0x81, ///< Message directed to application protocol 1 + OT_TCAT_APPLICATION_PROTOCOL_2 = 0x82, ///< Message directed to application protocol 2 + OT_TCAT_APPLICATION_PROTOCOL_3 = 0x83, ///< Message directed to application protocol 3 + OT_TCAT_APPLICATION_PROTOCOL_4 = 0x84, ///< Message directed to application protocol 4 + OT_TCAT_APPLICATION_PROTOCOL_VENDOR = 0x9F, ///< Message directed to a vendor specific application protocol } otTcatApplicationProtocol; @@ -151,35 +161,44 @@ typedef struct otTcatGeneralDeviceId */ typedef struct otTcatVendorInfo { - const char *mProvisioningUrl; ///< Provisioning URL path string - const char *mVendorName; ///< Vendor name string - const char *mVendorModel; ///< Vendor model string - const char *mVendorSwVersion; ///< Vendor software version string - const char *mVendorData; ///< Vendor specific data string - const char *mPskdString; ///< Vendor managed pre-shared key for device - const char *mInstallCode; ///< Vendor managed install code string - const otTcatAdvertisedDeviceId *mAdvertisedDeviceIds; /** Vendor managed advertised device ID array. - Array is terminated like C string with OT_TCAT_DEVICE_ID_EMPTY */ - const otTcatGeneralDeviceId *mGeneralDeviceId; /** Vendor managed general device ID array. - (if NULL: device ID is set to EUI-64 in binary format)*/ + const char *mProvisioningUrl; ///< Provisioning URL path string + const char *mVendorName; ///< Vendor name string + const char *mVendorModel; ///< Vendor model string + const char *mVendorSwVersion; ///< Vendor software version string + const char *mVendorData; ///< Vendor specific data string + const char *mPskdString; ///< Vendor managed pre-shared key for device + const char *mInstallCode; ///< Vendor managed install code string + const otTcatAdvertisedDeviceId + *mAdvertisedDeviceIds; /** Vendor managed advertised device ID array. + Array is terminated like C string with OT_TCAT_DEVICE_ID_EMPTY */ + const otTcatGeneralDeviceId *mGeneralDeviceId; /** Vendor managed general device ID array. + (if NULL: device ID is set to EUI-64 in binary format) */ + const char *mApplicationServiceName[OT_TCAT_APPLICATION_LAYER_MAX_COUNT]; /** Array with application service names + as C string with maximum length + OT_TCAT_SERVICE_NAME_MAX_LENGTH or + NULL if not supported */ + bool mApplicationServiceIsTcp[OT_TCAT_APPLICATION_LAYER_MAX_COUNT]; /** Array with boolean values indicating + if the service is of TCP type (otherwise + UDP) */ } otTcatVendorInfo; /** - * Pointer to call when application data was received over a TCAT TLS connection. + * Pointer to call when application data or vendor-specific data was received over a TCAT TLS connection. + * The application may generate a response to an incoming TCAT application data packet. The TCAT agent + * automatically responds with status OT_TCAT_STATUS_UNSUPPORTED if no response has been generated or + * no handler is defined. * * @param[in] aInstance A pointer to an OpenThread instance. * @param[in] aMessage A pointer to the message. * @param[in] aOffset The offset where the application data begins. - * @param[in] aTcatApplicationProtocol The protocol type of the message received. - * @param[in] aServiceName The name of the service the message is direced to. + * @param[in] aTcatApplicationProtocol The application protocol the message is targeted to. * @param[in] aContext A pointer to arbitrary context information. */ typedef void (*otHandleTcatApplicationDataReceive)(otInstance *aInstance, const otMessage *aMessage, int32_t aOffset, otTcatApplicationProtocol aTcatApplicationProtocol, - const char *aServiceName, void *aContext); /** diff --git a/src/cli/README_TCAT.md b/src/cli/README_TCAT.md index b96ebd481..dafa74135 100644 --- a/src/cli/README_TCAT.md +++ b/src/cli/README_TCAT.md @@ -63,6 +63,25 @@ tcat advid clear Done ``` +### certid + +Displays the ID of currently selected TCAT device certificate. A TCAT device supports multiple identities for testing purposes. + +```bash +tcat certid +0 +Done +``` + +### certid \ + +Selects the ID of the TCAT device certificate. A TCAT device supports multiple identities for testing purposes. + +```bash +tcat certid 1 +Done +``` + ### devid Displays currently set TCAT device id. diff --git a/src/cli/cli_tcat.cpp b/src/cli/cli_tcat.cpp index 5a0bfe496..aa3003275 100644 --- a/src/cli/cli_tcat.cpp +++ b/src/cli/cli_tcat.cpp @@ -32,7 +32,9 @@ #include "cli/cli_tcat.hpp" #include "common/code_utils.hpp" +#include "common/debug.hpp" #include "common/error.hpp" +#include "common/string.hpp" #include @@ -43,31 +45,60 @@ #if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE && OPENTHREAD_CONFIG_CLI_BLE_SECURE_ENABLE +#define CERT_SET_COUNT 2 +#define CERT_MAX_SIZE 1024 +#define KEY_MAX_SIZE 512 + // DeviceCert1 default identity for TCAT certification testing. +// DeviceCert2 extra example. // WARNING: storage of private keys in code or program memory MUST NOT be used in production. // The below code is for testing purposes only. For production, secure key storage must be // used to store private keys. -#define OT_CLI_TCAT_X509_CERT \ - "-----BEGIN CERTIFICATE-----\n" \ - "MIIB6TCCAZCgAwIBAgICNekwCgYIKoZIzj0EAwIwcTEmMCQGA1UEAwwdVGhyZWFk\n" \ - "IENlcnRpZmljYXRpb24gRGV2aWNlQ0ExGTAXBgNVBAoMEFRocmVhZCBHcm91cCBJ\n" \ - "bmMxEjAQBgNVBAcMCVNhbiBSYW1vbjELMAkGA1UECAwCQ0ExCzAJBgNVBAYTAlVT\n" \ - "MCAXDTI0MDUwNzA5Mzk0NVoYDzI5OTkxMjMxMDkzOTQ1WjA8MSEwHwYDVQQDDBhU\n" \ - "Q0FUIEV4YW1wbGUgRGV2aWNlQ2VydDExFzAVBgNVBAUTDjQ3MjMtOTgzMy0wMDAx\n" \ - "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE11h/4vKZXVXv+1GDZo066spItloT\n" \ - "dpCi0bux0jvpQSHLdQBIc+40zVCxMDRUvbX//vJKGsSJKOVUlCojQ2wIdqNLMEkw\n" \ - "HwYDVR0jBBgwFoAUX6sbKWiIodS0MaiGYefnZlnt+BkwEAYJKwYBBAGC3yoCBAMC\n" \ - "AQUwFAYJKwYBBAGC3yoDBAcEBSABAQEBMAoGCCqGSM49BAMCA0cAMEQCIHWu+Rd1\n" \ - "VRlzrD8KbuyJcJFTXh2sQ9UIrFIA7+4e/GVcAiAVBdGqTxbt3TGkBBllpafAUB2/\n" \ - "s0GJj7E33oblqy5eHQ==\n" \ - "-----END CERTIFICATE-----\n" +static const char *const OT_CLI_TCAT_X509_CERT[CERT_SET_COUNT] = {R"( +-----BEGIN CERTIFICATE----- +MIIB6TCCAZCgAwIBAgICNekwCgYIKoZIzj0EAwIwcTEmMCQGA1UEAwwdVGhyZWFk +IENlcnRpZmljYXRpb24gRGV2aWNlQ0ExGTAXBgNVBAoMEFRocmVhZCBHcm91cCBJ +bmMxEjAQBgNVBAcMCVNhbiBSYW1vbjELMAkGA1UECAwCQ0ExCzAJBgNVBAYTAlVT +MCAXDTI0MDUwNzA5Mzk0NVoYDzI5OTkxMjMxMDkzOTQ1WjA8MSEwHwYDVQQDDBhU +Q0FUIEV4YW1wbGUgRGV2aWNlQ2VydDExFzAVBgNVBAUTDjQ3MjMtOTgzMy0wMDAx +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE11h/4vKZXVXv+1GDZo066spItloT +dpCi0bux0jvpQSHLdQBIc+40zVCxMDRUvbX//vJKGsSJKOVUlCojQ2wIdqNLMEkw +HwYDVR0jBBgwFoAUX6sbKWiIodS0MaiGYefnZlnt+BkwEAYJKwYBBAGC3yoCBAMC +AQUwFAYJKwYBBAGC3yoDBAcEBSABAQEBMAoGCCqGSM49BAMCA0cAMEQCIHWu+Rd1 +VRlzrD8KbuyJcJFTXh2sQ9UIrFIA7+4e/GVcAiAVBdGqTxbt3TGkBBllpafAUB2/ +s0GJj7E33oblqy5eHQ== +-----END CERTIFICATE----- +)", + R"( +-----BEGIN CERTIFICATE----- +MIIB6TCCAZCgAwIBAgICNeowCgYIKoZIzj0EAwIwcTEmMCQGA1UEAwwdVGhyZWFk +IENlcnRpZmljYXRpb24gRGV2aWNlQ0ExGTAXBgNVBAoMEFRocmVhZCBHcm91cCBJ +bmMxEjAQBgNVBAcMCVNhbiBSYW1vbjELMAkGA1UECAwCQ0ExCzAJBgNVBAYTAlVT +MCAXDTI0MDUwNzA5Mzk0NVoYDzI5OTkxMjMxMDkzOTQ1WjA8MSEwHwYDVQQDDBhU +Q0FUIEV4YW1wbGUgRGV2aWNlQ2VydDIxFzAVBgNVBAUTDjQ3MjMtOTgzMy0wMDAy +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE30GMkqSBj3049NtK6G/MRTqcDxpm +i1LxTpSxFIB7P9HVoVM7Cd9X6bBUp5FrSZI+KHtX2HKtXzmzsdJ3gxAmi6NLMEkw +HwYDVR0jBBgwFoAUX6sbKWiIodS0MaiGYefnZlnt+BkwEAYJKwYBBAGC3yoCBAMC +AQUwFAYJKwYBBAGC3yoDBAcEBSABAQEBMAoGCCqGSM49BAMCA0cAMEQCIAbZzVbC +toNYgSWSgxRGzLRo1YJANqRC7yRtJNKTdQ1ZAiAlgGxEW2lkxCAGPUK1m9Wbb4kl +7AhBhYlK6vZz/omTsQ== +-----END CERTIFICATE----- +)"}; -#define OT_CLI_TCAT_PRIV_KEY \ - "-----BEGIN EC PRIVATE KEY-----\n" \ - "MHcCAQEEIIqKM1QTlNaquV74W6Viz/ggXoLqlPOP6LagSyaFO3oUoAoGCCqGSM49\n" \ - "AwEHoUQDQgAE11h/4vKZXVXv+1GDZo066spItloTdpCi0bux0jvpQSHLdQBIc+40\n" \ - "zVCxMDRUvbX//vJKGsSJKOVUlCojQ2wIdg==\n" \ - "-----END EC PRIVATE KEY-----\n" +static const char *const OT_CLI_TCAT_PRIV_KEY[CERT_SET_COUNT] = {R"( +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIIqKM1QTlNaquV74W6Viz/ggXoLqlPOP6LagSyaFO3oUoAoGCCqGSM49 +AwEHoUQDQgAE11h/4vKZXVXv+1GDZo066spItloTdpCi0bux0jvpQSHLdQBIc+40 +zVCxMDRUvbX//vJKGsSJKOVUlCojQ2wIdg== +-----END EC PRIVATE KEY----- +)", + R"( +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIP7Al8tJA3QgwD3yIuOSEmJkT3GlWmcHQ59JfhZOjSdUoAoGCCqGSM49 +AwEHoUQDQgAE30GMkqSBj3049NtK6G/MRTqcDxpmi1LxTpSxFIB7P9HVoVM7Cd9X +6bBUp5FrSZI+KHtX2HKtXzmzsdJ3gxAmiw== +-----END EC PRIVATE KEY----- +)"}; #define OT_CLI_TCAT_TRUSTED_ROOT_CERTIFICATE \ "-----BEGIN CERTIFICATE-----\n" \ @@ -86,15 +117,16 @@ "-----END CERTIFICATE-----\n" namespace ot { - namespace Cli { otTcatAdvertisedDeviceId sAdvertisedDeviceIds[OT_TCAT_DEVICE_ID_MAX]; otTcatGeneralDeviceId sGeneralDeviceId; -const char kPskdVendor[] = "JJJJJJ"; -const char kInstallVendor[] = "InstallCode"; -const char kUrl[] = "dummy_url"; +const char kPskdVendor[] = "JJJJJJ"; +const char kInstallVendor[] = "InstallCode"; +const char kUrl[] = "dummy_url"; +const char kApplicationServiceName1[] = "echo"; +const char kApplicationServiceName2[] = "discard"; static bool IsDeviceIdSet(void) { @@ -114,28 +146,24 @@ static void HandleBleSecureReceive(otInstance *aInstance, const otMessage *aMessage, int32_t aOffset, otTcatApplicationProtocol aTcatApplicationProtocol, - const char *aServiceName, void *aContext) { OT_UNUSED_VARIABLE(aContext); - OT_UNUSED_VARIABLE(aTcatApplicationProtocol); - OT_UNUSED_VARIABLE(aServiceName); - static constexpr int kTextMaxLen = 100; - static constexpr uint8_t kBufPrefixLen = 5; + static constexpr int kTextMaxLen = 100; - uint16_t nLen; - uint8_t buf[kTextMaxLen]; - - nLen = - otMessageRead(aMessage, static_cast(aOffset), buf + kBufPrefixLen, sizeof(buf) - kBufPrefixLen - 1); - - memcpy(buf, "RECV:", kBufPrefixLen); - - buf[nLen + kBufPrefixLen] = 0; - - IgnoreReturnValue(otBleSecureSendApplicationTlv(aInstance, buf, (uint16_t)strlen((char *)buf))); - IgnoreReturnValue(otBleSecureFlush(aInstance)); + if (aTcatApplicationProtocol == OT_TCAT_APPLICATION_PROTOCOL_1 || + aTcatApplicationProtocol == OT_TCAT_APPLICATION_PROTOCOL_VENDOR) + { + uint8_t buf[kTextMaxLen]; + uint16_t nLen = otMessageRead(aMessage, static_cast(aOffset), buf, sizeof(buf)); + IgnoreReturnValue(otBleSecureSendApplicationTlv(aInstance, OT_TCAT_APPLICATION_PROTOCOL_RESPONSE, buf, nLen)); + } + else if (aTcatApplicationProtocol == OT_TCAT_APPLICATION_PROTOCOL_2) + { + uint8_t status = OT_TCAT_STATUS_SUCCESS; + IgnoreReturnValue(otBleSecureSendApplicationTlv(aInstance, OT_TCAT_APPLICATION_PROTOCOL_STATUS, &status, 1)); + } } /** @@ -230,6 +258,38 @@ exit: return error; } +/** + * @cli tcat certid + * @code + * tcat devid certid 0 + * Done + * @endcode + * @cparam tcat certid [@ca{value}] + * * The `value` int value of the ID. + * @par + * Selects a predefined certificate. + */ +template <> otError Tcat::Process(Arg aArgs[]) +{ + Error error = kErrorNone; + uint8_t certCandidate = 0; + + if (aArgs[0].IsEmpty()) + { + OutputLine("%d", mSelectedCert); + ExitNow(); + } + + SuccessOrExit(error = aArgs[0].ParseAsUint8(certCandidate)); + + VerifyOrExit(certCandidate < CERT_SET_COUNT, error = kErrorInvalidArgs); + + mSelectedCert = certCandidate; + +exit: + return error; +} + /** * @cli tcat devid * @code @@ -292,9 +352,12 @@ template <> otError Tcat::Process(Arg aArgs[]) otError error = OT_ERROR_NONE; ClearAllBytes(mVendorInfo); - mVendorInfo.mPskdString = kPskdVendor; - mVendorInfo.mProvisioningUrl = kUrl; - mVendorInfo.mInstallCode = kInstallVendor; + mVendorInfo.mPskdString = kPskdVendor; + mVendorInfo.mProvisioningUrl = kUrl; + mVendorInfo.mInstallCode = kInstallVendor; + mVendorInfo.mApplicationServiceName[0] = kApplicationServiceName1; + mVendorInfo.mApplicationServiceName[1] = kApplicationServiceName2; + mVendorInfo.mApplicationServiceIsTcp[1] = true; if (IsDeviceIdSet()) { @@ -306,9 +369,10 @@ template <> otError Tcat::Process(Arg aArgs[]) mVendorInfo.mGeneralDeviceId = &sGeneralDeviceId; } - 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)); + otBleSecureSetCertificate(GetInstancePtr(), reinterpret_cast(OT_CLI_TCAT_X509_CERT[mSelectedCert]), + StringLength(OT_CLI_TCAT_X509_CERT[mSelectedCert], CERT_MAX_SIZE) + 1, + reinterpret_cast(OT_CLI_TCAT_PRIV_KEY[mSelectedCert]), + StringLength(OT_CLI_TCAT_PRIV_KEY[mSelectedCert], KEY_MAX_SIZE) + 1); otBleSecureSetCaCertificateChain(GetInstancePtr(), reinterpret_cast(OT_CLI_TCAT_TRUSTED_ROOT_CERTIFICATE), diff --git a/src/cli/cli_tcat.hpp b/src/cli/cli_tcat.hpp index 5a9bc56ae..5523a4257 100644 --- a/src/cli/cli_tcat.hpp +++ b/src/cli/cli_tcat.hpp @@ -55,6 +55,7 @@ public: */ Tcat(otInstance *aInstance, OutputImplementer &aOutputImplementer) : Utils(aInstance, aOutputImplementer) + , mSelectedCert(0) { } @@ -77,6 +78,7 @@ private: template otError Process(Arg aArgs[]); otTcatVendorInfo mVendorInfo; + uint8_t mSelectedCert; }; } // namespace Cli diff --git a/src/core/api/ble_secure_api.cpp b/src/core/api/ble_secure_api.cpp index cc737ffd5..f5d95e33c 100644 --- a/src/core/api/ble_secure_api.cpp +++ b/src/core/api/ble_secure_api.cpp @@ -195,9 +195,12 @@ otError otBleSecureSend(otInstance *aInstance, uint8_t *aBuf, uint16_t aLength) return AsCoreType(aInstance).Get().Send(aBuf, aLength); } -otError otBleSecureSendApplicationTlv(otInstance *aInstance, uint8_t *aBuf, uint16_t aLength) +otError otBleSecureSendApplicationTlv(otInstance *aInstance, + otTcatApplicationProtocol aApplicationProtocol, + uint8_t *aBuf, + uint16_t aLength) { - return AsCoreType(aInstance).Get().SendApplicationTlv(aBuf, aLength); + return AsCoreType(aInstance).Get().SendApplicationTlv(MapEnum(aApplicationProtocol), aBuf, aLength); } otError otBleSecureFlush(otInstance *aInstance) { return AsCoreType(aInstance).Get().Flush(); } diff --git a/src/core/meshcop/tcat_agent.cpp b/src/core/meshcop/tcat_agent.cpp index 805ce212d..f461e1cdd 100644 --- a/src/core/meshcop/tcat_agent.cpp +++ b/src/core/meshcop/tcat_agent.cpp @@ -33,6 +33,8 @@ #include "tcat_agent.hpp" #include "common/code_utils.hpp" +#include "common/error.hpp" +#include "crypto/storage.hpp" #if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE @@ -56,7 +58,6 @@ bool TcatAgent::VendorInfo::IsValid(void) const TcatAgent::TcatAgent(Instance &aInstance) : InstanceLocator(aInstance) , mVendorInfo(nullptr) - , mCurrentApplicationProtocol(kApplicationProtocolNone) , mState(kStateDisabled) , mCommissionerHasNetworkName(false) , mCommissionerHasDomainName(false) @@ -65,9 +66,10 @@ TcatAgent::TcatAgent(Instance &aInstance) , mPskdVerified(false) , mPskcVerified(false) , mInstallCodeVerified(false) + , mIsCommissioned(false) + , mApplicationResponsePending(false) { mJoinerPskd.Clear(); - mCurrentServiceName[0] = 0; } Error TcatAgent::Start(AppDataReceiveCallback aAppDataReceiveCallback, JoinCallback aHandler, void *aContext) @@ -79,9 +81,7 @@ Error TcatAgent::Start(AppDataReceiveCallback aAppDataReceiveCallback, JoinCallb mAppDataReceiveCallback.Set(aAppDataReceiveCallback, aContext); mJoinCallback.Set(aHandler, aContext); mRandomChallenge = 0; - - mCurrentApplicationProtocol = kApplicationProtocolNone; - mState = kStateEnabled; + mState = kStateEnabled; exit: LogWarnOnError(error, "start TCAT agent"); @@ -90,14 +90,19 @@ exit: void TcatAgent::Stop(void) { - mCurrentApplicationProtocol = kApplicationProtocolNone; - mState = kStateDisabled; + mState = kStateDisabled; mAppDataReceiveCallback.Clear(); mJoinCallback.Clear(); - mRandomChallenge = 0; - mPskdVerified = false; - mPskcVerified = false; - mInstallCodeVerified = false; + mCommissionerHasNetworkName = false; + mCommissionerHasDomainName = false; + mCommissionerHasExtendedPanId = false; + mCommissionerNetworkName.m8[0] = '\0'; + mCommissionerDomainName.m8[0] = '\0'; + mRandomChallenge = 0; + mPskdVerified = false; + mPskcVerified = false; + mInstallCodeVerified = false; + mIsCommissioned = false; LogInfo("TCAT agent stopped"); } @@ -162,9 +167,8 @@ Error TcatAgent::Connected(MeshCoP::Tls::Extension &aTls) } } - mCurrentApplicationProtocol = kApplicationProtocolNone; - mCurrentServiceName[0] = 0; - mState = kStateConnected; + mState = kStateConnected; + mIsCommissioned = Get().IsCommissioned(); LogInfo("TCAT agent connected"); exit: @@ -173,8 +177,6 @@ exit: void TcatAgent::Disconnected(void) { - mCurrentApplicationProtocol = kApplicationProtocolNone; - if (mState != kStateDisabled) { mState = kStateEnabled; @@ -188,99 +190,113 @@ void TcatAgent::Disconnected(void) LogInfo("TCAT agent disconnected"); } +uint8_t TcatAgent::CheckAuthorizationRequirements(CommandClassFlags aFlagsRequired, Dataset::Info *aDatasetInfo) const +{ + uint8_t res = kAccessFlag; + + for (uint16_t flag = kPskdFlag; flag < kMaxFlag; flag <<= 1) + { + if (aFlagsRequired & flag) + { + switch (flag) + { + case kPskdFlag: + if (mPskdVerified) + { + res |= flag; + } + break; + + case kNetworkNameFlag: + if (aDatasetInfo != nullptr && mCommissionerHasNetworkName && + aDatasetInfo->IsPresent() && + (aDatasetInfo->Get() == mCommissionerNetworkName)) + { + res |= flag; + } + break; + + case kExtendedPanIdFlag: + if (aDatasetInfo != nullptr && mCommissionerHasExtendedPanId && + aDatasetInfo->IsPresent() && + (aDatasetInfo->Get() == mCommissionerExtendedPanId)) + { + res |= flag; + } + break; + + case kThreadDomainFlag: + + if (mCommissionerHasDomainName) + { +#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_4) + if (Get().GetDomainName() == mCommissionerDomainName) +#else + if (StringMatch(mCommissionerDomainName.GetAsCString(), NetworkName::kDomainNameInit)) +#endif + { + res |= flag; + } + } + break; + + case kPskcFlag: + if (mPskcVerified) + { + res |= flag; + } + break; + + default: + LogCrit("Error while processing access flags. Unexpected flag %d", flag); + OT_ASSERT(false); // Should not get here + } + } + } + + return res; +} + bool TcatAgent::CheckCommandClassAuthorizationFlags(CommandClassFlags aCommissionerCommandClassFlags, CommandClassFlags aDeviceCommandClassFlags, Dataset *aDataset) const { - bool authorized = false; - bool additionalDeviceRequirementMet = false; - bool domainNamesMatch = false; - bool networkNamesMatch = false; - bool extendedPanIdsMatch = false; + bool authorized = false; + uint8_t deviceRequirementMet; + uint8_t commissionerRequirementMet; + Dataset::Info datasetInfo; + Error datasetError = kErrorNone; VerifyOrExit(IsConnected()); - VerifyOrExit(aCommissionerCommandClassFlags & kAccessFlag); - if (aDeviceCommandClassFlags & kAccessFlag) + if (aDataset == nullptr) { - additionalDeviceRequirementMet = true; + datasetError = Get().Read(datasetInfo); + } + else + { + aDataset->ConvertTo(datasetInfo); } - if (!additionalDeviceRequirementMet && (aDeviceCommandClassFlags & kPskdFlag)) + if (datasetError == kErrorNone) { - additionalDeviceRequirementMet = mPskdVerified; + deviceRequirementMet = CheckAuthorizationRequirements(aDeviceCommandClassFlags, &datasetInfo); + commissionerRequirementMet = CheckAuthorizationRequirements(aCommissionerCommandClassFlags, &datasetInfo); + } + else + { + deviceRequirementMet = CheckAuthorizationRequirements(aDeviceCommandClassFlags, nullptr); + commissionerRequirementMet = CheckAuthorizationRequirements(aCommissionerCommandClassFlags, nullptr); } - if (!additionalDeviceRequirementMet && (aDeviceCommandClassFlags & kPskcFlag)) + if (aDataset != nullptr) // For set active operational dataset TLV the PSKc check is always successful { - additionalDeviceRequirementMet = mPskcVerified; + deviceRequirementMet |= kPskcFlag; + commissionerRequirementMet |= (aCommissionerCommandClassFlags & kPskcFlag); } - if (mCommissionerHasNetworkName || mCommissionerHasExtendedPanId) - { - Dataset::Info datasetInfo; - Error datasetError = kErrorNone; - - if (aDataset == nullptr) - { - datasetError = Get().Read(datasetInfo); - } - else - { - aDataset->ConvertTo(datasetInfo); - } - - if (datasetError == kErrorNone) - { - if (datasetInfo.IsPresent() && mCommissionerHasNetworkName && - (datasetInfo.Get() == mCommissionerNetworkName)) - { - networkNamesMatch = true; - } - - if (datasetInfo.IsPresent() && mCommissionerHasExtendedPanId && - (datasetInfo.Get() == mCommissionerExtendedPanId)) - { - extendedPanIdsMatch = true; - } - } - } - - if (!networkNamesMatch) - { - VerifyOrExit((aCommissionerCommandClassFlags & kNetworkNameFlag) == 0); - } - else if (aDeviceCommandClassFlags & kNetworkNameFlag) - { - additionalDeviceRequirementMet = true; - } - - if (!extendedPanIdsMatch) - { - VerifyOrExit((aCommissionerCommandClassFlags & kExtendedPanIdFlag) == 0); - } - else if (aDeviceCommandClassFlags & kExtendedPanIdFlag) - { - additionalDeviceRequirementMet = true; - } - -#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) - VerifyOrExit((aCommissionerCommandClassFlags & kThreadDomainFlag) == 0); -#endif - - if (!domainNamesMatch) - { - VerifyOrExit((aCommissionerCommandClassFlags & kThreadDomainFlag) == 0); - } - else if (aDeviceCommandClassFlags & kThreadDomainFlag) - { - additionalDeviceRequirementMet = true; - } - - if (additionalDeviceRequirementMet) - { - authorized = true; - } + authorized = (commissionerRequirementMet == aCommissionerCommandClassFlags) && + (deviceRequirementMet & aDeviceCommandClassFlags); exit: return authorized; @@ -306,7 +322,7 @@ bool TcatAgent::IsCommandClassAuthorized(CommandClass aCommandClass) const mDeviceAuthorizationField.mExtractionFlags, nullptr); break; - case kTlvDecommissioning: + case kDecommissioning: authorized = CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mDecommissioningFlags, mDeviceAuthorizationField.mDecommissioningFlags, nullptr); break; @@ -324,46 +340,6 @@ bool TcatAgent::IsCommandClassAuthorized(CommandClass aCommandClass) const return authorized; } -TcatAgent::CommandClass TcatAgent::GetCommandClass(uint8_t aTlvType) const -{ - static constexpr int kGeneralTlvs = 0x1F; - static constexpr int kCommissioningTlvs = 0x3F; - static constexpr int kExtractionTlvs = 0x5F; - static constexpr int kTlvDecommissioningTlvs = 0x7F; - static constexpr int kApplicationTlvs = 0x9F; - - if (aTlvType <= kGeneralTlvs) - { - return kGeneral; - } - else if (aTlvType <= kCommissioningTlvs) - { - return kCommissioning; - } - else if (aTlvType <= kExtractionTlvs) - { - return kExtraction; - } - else if (aTlvType <= kTlvDecommissioningTlvs) - { - return kTlvDecommissioning; - } - else if (aTlvType <= kApplicationTlvs) - { - return kApplication; - } - else - { - return kInvalid; - } -} - -bool TcatAgent::CanProcessTlv(uint8_t aTlvType) const -{ - CommandClass tlvCommandClass = GetCommandClass(aTlvType); - return IsCommandClassAuthorized(tlvCommandClass); -} - Error TcatAgent::HandleSingleTlv(const Message &aIncomingMessage, Message &aOutgoingMessage) { Error error = kErrorParse; @@ -388,88 +364,98 @@ Error TcatAgent::HandleSingleTlv(const Message &aIncomingMessage, Message &aOutg offset += sizeof(ot::Tlv); } - if (!CanProcessTlv(tlv.GetType())) + switch (tlv.GetType()) { - error = kErrorRejected; + case kTlvDisconnect: + error = kErrorAbort; + response = true; // true - to avoid response-with-status being sent. + break; + + case kTlvSetActiveOperationalDataset: + error = HandleSetActiveOperationalDataset(aIncomingMessage, offset, length); + break; + + case kTlvGetActiveOperationalDataset: + error = HandleGetActiveOperationalDataset(aOutgoingMessage, response); + break; + + case kTlvGetDiagnosticTlvs: + error = HandleGetDiagnosticTlvs(aIncomingMessage, aOutgoingMessage, offset, length, response); + break; + + case kTlvStartThreadInterface: + error = HandleStartThreadInterface(); + break; + + case kTlvStopThreadInterface: + error = HandleStopThreadInterface(); + break; + + case kTlvGetApplicationLayers: + error = HandleGetApplicationLayers(aOutgoingMessage, response); + break; + + case kTlvSendApplicationData1: + case kTlvSendApplicationData2: + case kTlvSendApplicationData3: + case kTlvSendApplicationData4: + case kTlvSendVendorSpecificData: + error = HandleApplicationData(aIncomingMessage, offset, static_cast(tlv.GetType()), + response); + break; + + case kTlvDecommission: + error = HandleDecomission(); + break; + + case kTlvPing: + 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; + + case kTlvPresentPskdHash: + error = HandlePresentPskdHash(aIncomingMessage, offset, length); + break; + + case kTlvPresentPskcHash: + error = HandlePresentPskcHash(aIncomingMessage, offset, length); + break; + + case kTlvPresentInstallCodeHash: + error = HandlePresentInstallCodeHash(aIncomingMessage, offset, length); + break; + + case kTlvRequestRandomNumChallenge: + error = HandleRequestRandomNumberChallenge(aOutgoingMessage, response); + break; + + case kTlvRequestPskdHash: + error = HandleRequestPskdHash(aIncomingMessage, aOutgoingMessage, offset, length, response); + break; + + case kTlvGetCommissionerCertificate: + error = HandleGetCommissionerCertificate(aOutgoingMessage, response); + break; + + default: + error = kErrorInvalidCommand; } - else - { - switch (tlv.GetType()) - { - case kTlvDisconnect: - error = kErrorAbort; - response = true; // true - to avoid response-with-status being sent. - break; - case kTlvSetActiveOperationalDataset: - error = HandleSetActiveOperationalDataset(aIncomingMessage, offset, length); - break; - - case kTlvGetActiveOperationalDataset: - error = HandleGetActiveOperationalDataset(aOutgoingMessage, response); - break; - - case kTlvGetDiagnosticTlvs: - error = HandleGetDiagnosticTlvs(aIncomingMessage, aOutgoingMessage, offset, length, response); - break; - - case kTlvStartThreadInterface: - error = HandleStartThreadInterface(); - break; - - case kTlvStopThreadInterface: - error = otThreadSetEnabled(&GetInstance(), false); - break; - - case kTlvSendApplicationData: - LogInfo("Application data len:%d, offset:%d", length, offset); - mAppDataReceiveCallback.InvokeIfSet(&GetInstance(), &aIncomingMessage, offset, - MapEnum(mCurrentApplicationProtocol), mCurrentServiceName); - response = true; - error = kErrorNone; - break; - - case kTlvDecommission: - error = HandleDecomission(); - break; - - case kTlvPing: - 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; - case kTlvPresentPskdHash: - error = HandlePresentPskdHash(aIncomingMessage, offset, length); - break; - case kTlvPresentPskcHash: - error = HandlePresentPskcHash(aIncomingMessage, offset, length); - break; - case kTlvPresentInstallCodeHash: - error = HandlePresentInstallCodeHash(aIncomingMessage, offset, length); - break; - case kTlvRequestRandomNumChallenge: - error = HandleRequestRandomNumberChallenge(aOutgoingMessage, response); - break; - case kTlvRequestPskdHash: - error = HandleRequestPskdHash(aIncomingMessage, aOutgoingMessage, offset, length, response); - break; - case kTlvGetCommissionerCertificate: - error = HandleGetCommissionerCertificate(aOutgoingMessage, response); - break; - default: - error = kErrorInvalidCommand; - } - } if (!response) { StatusCode statusCode; @@ -480,30 +466,40 @@ Error TcatAgent::HandleSingleTlv(const Message &aIncomingMessage, Message &aOutg statusCode = kStatusSuccess; break; - case kErrorInvalidState: - statusCode = kStatusUndefined; + case kErrorNotImplemented: + case kErrorInvalidCommand: + statusCode = kStatusUnsupported; break; case kErrorParse: statusCode = kStatusParseError; break; - case kErrorInvalidCommand: - statusCode = kStatusUnsupported; + case kErrorInvalidArgs: + statusCode = kStatusValueError; break; - case kErrorRejected: - statusCode = kStatusUnauthorized; + case kErrorBusy: + statusCode = kStatusBusy; break; - case kErrorNotImplemented: - statusCode = kStatusUnsupported; + case kErrorNotFound: + statusCode = kStatusUndefined; break; case kErrorSecurity: statusCode = kStatusHashError; break; + case kErrorInvalidState: + case kErrorAlready: + statusCode = kStatusInvalidState; + break; + + case kErrorRejected: + statusCode = kStatusUnauthorized; + break; + default: statusCode = kStatusGeneralError; break; @@ -524,9 +520,12 @@ Error TcatAgent::HandleSetActiveOperationalDataset(const Message &aIncomingMessa uint8_t buf[kCommissionerCertMaxLength]; size_t bufLen = sizeof(buf); + VerifyOrExit(!mIsCommissioned, error = kErrorAlready); + offsetRange.Init(aOffset, aLength); SuccessOrExit(error = dataset.SetFrom(aIncomingMessage, offsetRange)); SuccessOrExit(error = dataset.ValidateTlvs()); + VerifyOrExit(dataset.ContainsTlv(Tlv::kNetworkKey), error = kErrorInvalidArgs); if (!CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mCommissioningFlags, mDeviceAuthorizationField.mCommissioningFlags, &dataset)) @@ -550,13 +549,7 @@ Error TcatAgent::HandleGetActiveOperationalDataset(Message &aOutgoingMessage, bo Dataset dataset; Dataset::Tlvs datasetTlvs; - if (!CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mCommissioningFlags, - mDeviceAuthorizationField.mCommissioningFlags, &dataset)) - { - error = kErrorRejected; - ExitNow(); - } - + VerifyOrExit(IsCommandClassAuthorized(kExtraction), error = kErrorRejected); SuccessOrExit(error = Get().Read(datasetTlvs)); SuccessOrExit( error = Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload, datasetTlvs.mTlvs, datasetTlvs.mLength)); @@ -568,20 +561,12 @@ exit: Error TcatAgent::HandleGetCommissionerCertificate(Message &aOutgoingMessage, bool &aResponse) { - Error error = kErrorNone; - Dataset dataset; - uint8_t buf[kCommissionerCertMaxLength]; - uint16_t bufLen = sizeof(buf); + Error error = kErrorNone; + unsigned char buf[kCommissionerCertMaxLength]; + uint16_t bufLen = sizeof(buf); - if (!CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mCommissioningFlags, - mDeviceAuthorizationField.mCommissioningFlags, &dataset)) - { - error = kErrorRejected; - ExitNow(); - } - - VerifyOrExit(kErrorNone == Get().ReadTcatCommissionerCertificate(buf, bufLen), - error = kErrorInvalidState); + VerifyOrExit(IsCommandClassAuthorized(kCommissioning), error = kErrorRejected); + VerifyOrExit(kErrorNone == Get().ReadTcatCommissionerCertificate(buf, bufLen), error = kErrorNotFound); SuccessOrExit(error = Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload, buf, bufLen)); aResponse = true; @@ -653,15 +638,8 @@ Error TcatAgent::HandleDecomission(void) Error error = kErrorNone; unsigned char buf[kCommissionerCertMaxLength]; size_t bufLen = sizeof(buf); - Dataset dataset; - - if (!CheckCommandClassAuthorizationFlags(mCommissionerAuthorizationField.mDecommissioningFlags, - mDeviceAuthorizationField.mDecommissioningFlags, &dataset)) - { - error = kErrorRejected; - ExitNow(); - } + VerifyOrExit(IsCommandClassAuthorized(kDecommissioning), error = kErrorRejected); SuccessOrExit(error = Get().GetPeerCertificateDer(buf, &bufLen, bufLen)); Get().SaveTcatCommissionerCertificate(buf, static_cast(bufLen)); @@ -719,9 +697,9 @@ Error TcatAgent::HandleGetNetworkName(Message &aOutgoingMessage, bool &aResponse Error error = kErrorNone; MeshCoP::NameData nameData = Get().GetNetworkName().GetAsData(); - VerifyOrExit(Get().IsCommissioned(), error = kErrorInvalidState); + VerifyOrExit(Get().IsCommissioned(), error = kErrorNotFound); #if !OPENTHREAD_CONFIG_ALLOW_EMPTY_NETWORK_NAME - VerifyOrExit(nameData.GetLength() > 0, error = kErrorInvalidState); + VerifyOrExit(nameData.GetLength() > 0, error = kErrorNotFound); #endif SuccessOrExit( @@ -739,6 +717,8 @@ Error TcatAgent::HandleGetDeviceId(Message &aOutgoingMessage, bool &aResponse) Mac::ExtAddress eui64; Error error = kErrorNone; + VerifyOrExit(mVendorInfo != nullptr, error = kErrorInvalidState); + if (mVendorInfo->mGeneralDeviceId != nullptr) { length = mVendorInfo->mGeneralDeviceId->mDeviceIdLen; @@ -765,7 +745,7 @@ Error TcatAgent::HandleGetExtPanId(Message &aOutgoingMessage, bool &aResponse) { Error error; - VerifyOrExit(Get().IsCommissioned(), error = kErrorInvalidState); + VerifyOrExit(Get().IsCommissioned(), error = kErrorNotFound); SuccessOrExit(error = Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload, &Get().GetExtPanId(), sizeof(ExtendedPanId))); @@ -780,10 +760,11 @@ Error TcatAgent::HandleGetProvisioningUrl(Message &aOutgoingMessage, bool &aResp Error error = kErrorNone; uint16_t length; + VerifyOrExit(mVendorInfo != nullptr, error = kErrorInvalidState); VerifyOrExit(mVendorInfo->mProvisioningUrl != nullptr, error = kErrorInvalidState); length = StringLength(mVendorInfo->mProvisioningUrl, kProvisioningUrlMaxLength); - VerifyOrExit(length > 0 && length <= Tlv::kBaseTlvMaxLength, error = kErrorInvalidState); + VerifyOrExit(length > 0 && length <= Tlv::kBaseTlvMaxLength, error = kErrorNotFound); SuccessOrExit(error = Tlv::AppendTlv(aOutgoingMessage, kTlvResponseWithPayload, mVendorInfo->mProvisioningUrl, length)); @@ -797,6 +778,7 @@ Error TcatAgent::HandlePresentPskdHash(const Message &aIncomingMessage, uint16_t { Error error = kErrorNone; + VerifyOrExit(mVendorInfo != nullptr, error = kErrorInvalidState); VerifyOrExit(mVendorInfo->mPskdString != nullptr, error = kErrorSecurity); SuccessOrExit(error = VerifyHash(aIncomingMessage, aOffset, aLength, mVendorInfo->mPskdString, @@ -828,6 +810,7 @@ Error TcatAgent::HandlePresentInstallCodeHash(const Message &aIncomingMessage, u { Error error = kErrorNone; + VerifyOrExit(mVendorInfo != nullptr, error = kErrorInvalidState); VerifyOrExit(mVendorInfo->mInstallCode != nullptr, error = kErrorSecurity); SuccessOrExit(error = VerifyHash(aIncomingMessage, aOffset, aLength, mVendorInfo->mInstallCode, @@ -861,6 +844,7 @@ Error TcatAgent::HandleRequestPskdHash(const Message &aIncomingMessage, uint64_t providedChallenge = 0; Crypto::HmacSha256::Hash hash; + VerifyOrExit(mVendorInfo != nullptr, error = kErrorInvalidState); VerifyOrExit(StringLength(mVendorInfo->mPskdString, kMaxPskdLength) != 0, error = kErrorFailed); VerifyOrExit(aLength == sizeof(providedChallenge), error = kErrorParse); @@ -903,12 +887,91 @@ void TcatAgent::CalculateHash(uint64_t aChallenge, const char *aBuf, size_t aBuf Crypto::Key cryptoKey; Crypto::HmacSha256 hmac; +#if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE + Crypto::Storage::KeyRef keyRef; + SuccessOrExit(Crypto::Storage::ImportKey(keyRef, Crypto::Storage::kKeyTypeHmac, + Crypto::Storage::kKeyAlgorithmHmacSha256, Crypto::Storage::kUsageSignHash, + Crypto::Storage::kTypeVolatile, reinterpret_cast(aBuf), + aBufLen)); + cryptoKey.SetAsKeyRef(keyRef); +#else cryptoKey.Set(reinterpret_cast(aBuf), static_cast(aBufLen)); +#endif hmac.Start(cryptoKey); hmac.Update(aChallenge); hmac.Update(rawKey.p, static_cast(rawKey.len)); hmac.Finish(aHash); + +#if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE + Crypto::Storage::DestroyKey(keyRef); +exit: +#endif + return; +} + +Error TcatAgent::HandleGetApplicationLayers(Message &aOutgoingMessage, bool &aResponse) +{ + Error error = kErrorNone; + ot::Tlv tlv; + uint8_t replyLen = 0; + uint8_t count = 0; + + static_assert((kApplicationLayerMaxCount * (kServiceNameMaxLength + 2)) <= 250, + "Unsupported TCAT application layers configuration"); + + VerifyOrExit(mVendorInfo != nullptr, error = kErrorInvalidState); + VerifyOrExit(IsCommandClassAuthorized(kApplication), error = kErrorRejected); + + for (uint8_t i = 0; i < kApplicationLayerMaxCount && mVendorInfo->mApplicationServiceName[i] != nullptr; i++) + { + replyLen += sizeof(tlv); + replyLen += StringLength(mVendorInfo->mApplicationServiceName[i], kServiceNameMaxLength); + count++; + } + + tlv.SetType(kTlvResponseWithPayload); + tlv.SetLength(replyLen); + SuccessOrExit(error = aOutgoingMessage.Append(tlv)); + + for (uint8_t i = 0; i < count; i++) + { + uint16_t length = StringLength(mVendorInfo->mApplicationServiceName[i], kServiceNameMaxLength); + uint8_t type = mVendorInfo->mApplicationServiceIsTcp[i] ? kTlvServiceNameTcp : kTlvServiceNameUdp; + SuccessOrExit(error = Tlv::AppendTlv(aOutgoingMessage, type, mVendorInfo->mApplicationServiceName[i], length)); + } + + aResponse = true; + +exit: + return error; +} + +Error TcatAgent::HandleApplicationData(const Message &aIncomingMessage, + uint16_t aOffset, + TcatApplicationProtocol aApplicationProtocol, + bool &aResponse) +{ + Error error = kErrorNone; + + VerifyOrExit(IsCommandClassAuthorized(kApplication), error = kErrorRejected); + + mApplicationResponsePending = true; + mAppDataReceiveCallback.InvokeIfSet(&GetInstance(), &aIncomingMessage, aOffset, + static_cast(aApplicationProtocol)); + + if (mApplicationResponsePending) + { + mApplicationResponsePending = false; + error = kErrorNotImplemented; // Application unsupported + } + else + { + aResponse = true; + } + +exit: + return error; } Error TcatAgent::HandleStartThreadInterface(void) @@ -916,6 +979,7 @@ Error TcatAgent::HandleStartThreadInterface(void) Error error; Dataset::Info datasetInfo; + VerifyOrExit(IsCommandClassAuthorized(kCommissioning), error = kErrorRejected); VerifyOrExit(Get().Read(datasetInfo) == kErrorNone, error = kErrorInvalidState); VerifyOrExit(datasetInfo.IsPresent(), error = kErrorInvalidState); @@ -930,6 +994,18 @@ exit: return error; } +Error TcatAgent::HandleStopThreadInterface(void) +{ + Error error; + + VerifyOrExit(IsCommandClassAuthorized(kCommissioning), error = kErrorRejected); + + error = otThreadSetEnabled(&GetInstance(), false); + +exit: + return error; +} + void SeralizeTcatAdvertisementTlv(uint8_t *aBuffer, uint16_t &aOffset, TcatAdvertisementTlvType aType, @@ -996,9 +1072,11 @@ Error TcatAgent::GetAdvertisementData(uint16_t &aLen, uint8_t *aAdvertisementDat reinterpret_cast(&caps)); } - tas.mRsv = 0; - tas.mMultiradioSupport = otPlatBleSupportsMultiRadio(&GetInstance()); - tas.mIsCommisionned = Get().IsCommissioned(); + tas.mRsv = 0; + tas.mMultiRadioSupport = otPlatBleSupportsMultiRadio(&GetInstance()); + tas.mStoresActiveOperationalDataset = + Get().IsPartiallyComplete() || Get().IsCommissioned(); + tas.mIsCommissioned = Get().IsCommissioned(); tas.mThreadNetworkActive = Get().IsAttached(); tas.mDeviceType = Get().GetDeviceMode().IsFullThreadDevice(); tas.mRxOnWhenIdle = Get().GetDeviceMode().IsRxOnWhenIdle(); diff --git a/src/core/meshcop/tcat_agent.hpp b/src/core/meshcop/tcat_agent.hpp index 9225d5afe..027990f36 100644 --- a/src/core/meshcop/tcat_agent.hpp +++ b/src/core/meshcop/tcat_agent.hpp @@ -83,11 +83,11 @@ public: */ enum CommandClass { - kGeneral = OT_TCAT_COMMAND_CLASS_GENERAL, ///< TCAT commands related to general operations - kCommissioning = OT_TCAT_COMMAND_CLASS_COMMISSIONING, ///< TCAT commands related to commissioning - kExtraction = OT_TCAT_COMMAND_CLASS_EXTRACTION, ///< TCAT commands related to key extraction - kTlvDecommissioning = OT_TCAT_COMMAND_CLASS_DECOMMISSIONING, ///< TCAT commands related to de-commissioning - kApplication = OT_TCAT_COMMAND_CLASS_APPLICATION, ///< TCAT commands related to application layer + kGeneral = OT_TCAT_COMMAND_CLASS_GENERAL, ///< TCAT commands related to general operations + kCommissioning = OT_TCAT_COMMAND_CLASS_COMMISSIONING, ///< TCAT commands related to commissioning + kExtraction = OT_TCAT_COMMAND_CLASS_EXTRACTION, ///< TCAT commands related to key extraction + kDecommissioning = OT_TCAT_COMMAND_CLASS_DECOMMISSIONING, ///< TCAT commands related to decommissioning + kApplication = OT_TCAT_COMMAND_CLASS_APPLICATION, ///< TCAT commands related to application layer kInvalid ///< TCAT command belongs to reserved pool or is invalid }; @@ -109,8 +109,9 @@ public: kPskdFlag = 1 << 1, ///< Access requires proof-of-possession of the device's PSKd kNetworkNameFlag = 1 << 2, ///< Access requires matching network name kExtendedPanIdFlag = 1 << 3, ///< Access requires matching XPANID - kThreadDomainFlag = 1 << 4, ///< Access requires matching XPANID + kThreadDomainFlag = 1 << 4, ///< Access requires matching Thread Domain Name kPskcFlag = 1 << 5, ///< Access requires proof-of-possession of the device's PSKc + kMaxFlag = 1 << 6, ///< Maximum value of access flags }; /** @@ -151,47 +152,50 @@ public: enum CommandTlvType : uint8_t { // Command Class General - kTlvResponseWithStatus = 1, ///< TCAT response with status value TLV - kTlvResponseWithPayload = 2, ///< TCAT response with payload TLV - kTlvResponseEvent = 3, ///< TCAT response event TLV (reserved) - kTlvGetNetworkName = 8, ///< TCAT network name query TLV - kTlvDisconnect = 9, ///< TCAT disconnect request TLV - 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 - kTlvRequestRandomNumChallenge = 19, ///< TCAT random number challenge query TLV - kTlvRequestPskdHash = 20, ///< TCAT PSKd hash request TLV + 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 + kTlvRequestPskdHash = 0x14, ///< TCAT PSKd hash request TLV // Command Class Commissioning - kTlvSetActiveOperationalDataset = 32, ///< TCAT active operational dataset TLV - kTlvSetActiveOperationalDatasetAlternative = 33, ///< TCAT active operational dataset alternative #1 TLV - kTlvGetProvisioningTlvs = 36, ///< TCAT provisioning TLVs query TLV - kTlvGetCommissionerCertificate = 37, ///< TCAT commissioner certificate query TLV - kTlvGetDiagnosticTlvs = 38, ///< TCAT diagnostics TLVs query TLV - kTlvStartThreadInterface = 39, ///< TCAT start thread interface request TLV - kTlvStopThreadInterface = 40, ///< TCAT stop thread interface request TLV + kTlvSetActiveOperationalDataset = 0x20, ///< TCAT active operational dataset TLV + kTlvSetActiveOperationalDatasetAlternative = 0x21, ///< TCAT active operational dataset alternative #1 TLV + 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 // Command Class Extraction - kTlvGetActiveOperationalDataset = 64, ///< TCAT active oerational dataset query TLV - kTlvGetActiveOperationalDatasetAlternative = 65, ///< TCAT active oerational dataset alternative #1 query TLV + kTlvGetActiveOperationalDataset = 0x40, ///< TCAT active oerational dataset query TLV + kTlvGetActiveOperationalDatasetAlternative = 0x41, ///< TCAT active oerational dataset alternative #1 query TLV // Command Class Decommissioning - kTlvDecommission = 96, ///< TCAT decommission request TLV + kTlvDecommission = 0x60, ///< TCAT decommission request TLV // Command Class Application - kTlvSelectApplicationLayerUdp = 128, ///< TCAT select UDP protocol application layer request TLV - kTlvSelectApplicationLayerTcp = 129, ///< TCAT select TCP protocol application layer request TLV - kTlvSendApplicationData = 130, ///< TCAT send application data TLV - kTlvSendVendorSpecificData = 159, ///< TCAT send vendor specific command or data TLV + 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 = 160, ///< TCAT LDevID operational certificate TLV - kTlvSetLDevIdPrivateKey = 161, ///< TCAT LDevID operational certificate pricate key TLV - kTlvSetDomainCaCert = 162, ///< TCAT domain CA certificate TLV + kTlvSetLDevIdOperationalCert = 0xA0, ///< TCAT LDevID operational certificate TLV + kTlvSetLDevIdPrivateKey = 0xA1, ///< TCAT LDevID operational certificate pricate key TLV + kTlvSetDomainCaCert = 0xA2, ///< TCAT domain CA certificate TLV }; /** @@ -208,6 +212,8 @@ public: kStatusUndefined = OT_TCAT_STATUS_UNDEFINED, ///< The requested value, data or service is not defined ///< (currently) or not present kStatusHashError = OT_TCAT_STATUS_HASH_ERROR, ///< The hash value presented by the commissioner was incorrect + kStatusInvalidState = + OT_TCAT_STATUS_INVALID_STATE, ///< The TCAT device is in invalid state to execute the command kStatusUnauthorized = OT_TCAT_STATUS_UNAUTHORIZED, ///< Sender does not have sufficient authorization for the given command }; @@ -219,8 +225,16 @@ public: { kApplicationProtocolNone = OT_TCAT_APPLICATION_PROTOCOL_NONE, ///< Message which has been sent without activating the TCAT agent - kApplicationProtocolUdp = OT_TCAT_APPLICATION_PROTOCOL_STATUS, ///< Message directed to a UDP service - kApplicationProtocolTcp = OT_TCAT_APPLICATION_PROTOCOL_TCP, ///< Message directed to a TCP service + kApplicationProtocolStatus = OT_TCAT_APPLICATION_PROTOCOL_STATUS, ///< Message directed to any application + ///< indicating a response with status value + kApplicationProtocolResponse = OT_TCAT_APPLICATION_PROTOCOL_RESPONSE, ///< Message directed to any application + ///< indicating a response with payload + kApplicationProtocol1 = OT_TCAT_APPLICATION_PROTOCOL_1, ///< Message directed to application 1 + kApplicationProtocol2 = OT_TCAT_APPLICATION_PROTOCOL_2, ///< Message directed to application 2 + kApplicationProtocol3 = OT_TCAT_APPLICATION_PROTOCOL_3, ///< Message directed to application 3 + kApplicationProtocol4 = OT_TCAT_APPLICATION_PROTOCOL_4, ///< Message directed to application 4 + kApplicationProtocolVendor = + OT_TCAT_APPLICATION_PROTOCOL_VENDOR, ///< Message directed to a vendor specific application }; /** @@ -332,6 +346,8 @@ public: * @retval FALSE The install code was not verified. */ bool GetInstallCodeVerifyStatus(void) const { return mInstallCodeVerified; } + bool GetApplicationResponsePending(void) { return mApplicationResponsePending; } + void NotifyApplicationResponseSent(void) { mApplicationResponsePending = false; } private: Error Connected(MeshCoP::Tls::Extension &aTls); @@ -365,7 +381,13 @@ private: uint16_t aLength, bool &aResponse); Error HandleStartThreadInterface(void); + Error HandleStopThreadInterface(void); Error HandleGetCommissionerCertificate(Message &aOutgoingMessage, bool &aResponse); + Error HandleGetApplicationLayers(Message &aOutgoingMessage, bool &aResponse); + Error HandleApplicationData(const Message &aIncomingMessage, + uint16_t aOffset, + TcatApplicationProtocol aApplicationProtocol, + bool &aResponse); Error VerifyHash(const Message &aIncomingMessage, uint16_t aOffset, @@ -374,12 +396,10 @@ private: size_t aBufLen); void CalculateHash(uint64_t aChallenge, const char *aBuf, size_t aBufLen, Crypto::HmacSha256::Hash &aHash); - bool CheckCommandClassAuthorizationFlags(CommandClassFlags aCommissionerCommandClassFlags, - CommandClassFlags aDeviceCommandClassFlags, - Dataset *aDataset) const; - - bool CanProcessTlv(uint8_t aTlvType) const; - CommandClass GetCommandClass(uint8_t aTlvType) const; + bool CheckCommandClassAuthorizationFlags(CommandClassFlags aCommissionerCommandClassFlags, + CommandClassFlags aDeviceCommandClassFlags, + Dataset *aDataset) const; + uint8_t CheckAuthorizationRequirements(CommandClassFlags aFlagsChecked, Dataset::Info *aDatasetInfo) const; static constexpr uint16_t kJoinerUdpPort = OPENTHREAD_CONFIG_JOINER_UDP_PORT; static constexpr uint16_t kPingPayloadMaxLength = 512; @@ -389,6 +409,8 @@ private: static constexpr uint16_t kInstallCodeMaxSize = 255; static constexpr uint16_t kCommissionerCertMaxLength = 1024; static constexpr uint16_t kBufferReserve = 2048 / (kBufferSize - 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; JoinerPskd mJoinerPskd; const VendorInfo *mVendorInfo; @@ -396,11 +418,9 @@ private: Callback mAppDataReceiveCallback; CertificateAuthorizationField mCommissionerAuthorizationField; CertificateAuthorizationField mDeviceAuthorizationField; - TcatApplicationProtocol mCurrentApplicationProtocol; NetworkName mCommissionerNetworkName; NetworkName mCommissionerDomainName; ExtendedPanId mCommissionerExtendedPanId; - char mCurrentServiceName[OT_TCAT_MAX_SERVICE_NAME_LENGTH + 1]; State mState; bool mCommissionerHasNetworkName : 1; bool mCommissionerHasDomainName : 1; @@ -409,6 +429,8 @@ private: bool mPskdVerified : 1; bool mPskcVerified : 1; bool mInstallCodeVerified : 1; + bool mIsCommissioned : 1; + bool mApplicationResponsePending : 1; friend class Ble::BleSecure; }; @@ -429,9 +451,9 @@ typedef UintTlvInfo Respons struct DeviceTypeAndStatus { uint8_t mRsv : 1; - bool mMultiradioSupport : 1; - bool mStoresActiveOpertonalDataset : 1; - bool mIsCommisionned : 1; + bool mMultiRadioSupport : 1; + bool mStoresActiveOperationalDataset : 1; + bool mIsCommissioned : 1; bool mThreadNetworkActive : 1; bool mIsBorderRouter : 1; bool mRxOnWhenIdle : 1; diff --git a/src/core/radio/ble_secure.cpp b/src/core/radio/ble_secure.cpp index 392b4a2ea..94c03f06d 100644 --- a/src/core/radio/ble_secure.cpp +++ b/src/core/radio/ble_secure.cpp @@ -27,6 +27,7 @@ */ #include "ble_secure.hpp" +#include "common/error.hpp" #if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE @@ -161,9 +162,27 @@ void BleSecure::Disconnect(void) IgnoreError(otPlatBleGapDisconnect(&GetInstance())); } + // Update advertisement + IgnoreError(NotifyAdvertisementChanged()); + mConnectCallback.InvokeIfSet(&GetInstance(), false, false); } +Error BleSecure::NotifyAdvertisementChanged(void) +{ + Error error = kErrorNone; + uint16_t advertisementLen = 0; + uint8_t *advertisementData = nullptr; + + VerifyOrExit(mBleState == kAdvertising); + SuccessOrExit(error = otPlatBleGetAdvertisementBuffer(&GetInstance(), &advertisementData)); + SuccessOrExit(error = mTcatAgent.GetAdvertisementData(advertisementLen, advertisementData)); + SuccessOrExit(error = otPlatBleGapAdvUpdateData(&GetInstance(), advertisementData, advertisementLen)); + +exit: + return error; +} + void BleSecure::SetPsk(const MeshCoP::JoinerPskd &aPskd) { static_assert(static_cast(MeshCoP::JoinerPskd::kMaxLength) <= @@ -207,14 +226,22 @@ exit: return error; } -Error BleSecure::SendApplicationTlv(uint8_t *aBuf, uint16_t aLength) +Error BleSecure::SendApplicationTlv(MeshCoP::TcatAgent::TcatApplicationProtocol aTcatApplicationProtocol, + uint8_t *aBuf, + uint16_t aLength) { Error error = kErrorNone; + + VerifyOrExit((aTcatApplicationProtocol != MeshCoP::TcatAgent::kApplicationProtocolStatus && + aTcatApplicationProtocol != MeshCoP::TcatAgent::kApplicationProtocolResponse) || + mTcatAgent.GetApplicationResponsePending(), + error = kErrorRejected); + if (aLength > Tlv::kBaseTlvMaxLength) { ot::ExtendedTlv tlv; - tlv.SetType(ot::MeshCoP::TcatAgent::kTlvSendApplicationData); + tlv.SetType(static_cast(aTcatApplicationProtocol)); tlv.SetLength(aLength); SuccessOrExit(error = Send(reinterpret_cast(&tlv), sizeof(tlv))); } @@ -222,12 +249,19 @@ Error BleSecure::SendApplicationTlv(uint8_t *aBuf, uint16_t aLength) { ot::Tlv tlv; - tlv.SetType(ot::MeshCoP::TcatAgent::kTlvSendApplicationData); + tlv.SetType(static_cast(aTcatApplicationProtocol)); tlv.SetLength((uint8_t)aLength); SuccessOrExit(error = Send(reinterpret_cast(&tlv), sizeof(tlv))); } - error = Send(aBuf, aLength); + SuccessOrExit(error = Send(aBuf, aLength)); + + if (aTcatApplicationProtocol == MeshCoP::TcatAgent::kApplicationProtocolStatus || + aTcatApplicationProtocol == MeshCoP::TcatAgent::kApplicationProtocolResponse) + { + mTcatAgent.NotifyApplicationResponseSent(); + } + exit: return error; } @@ -390,7 +424,7 @@ void BleSecure::HandleTlsReceive(uint8_t *aBuf, uint16_t aLength) if (!mTlvMode) { SuccessOrExit(mReceivedMessage->AppendBytes(aBuf, aLength)); - mReceiveCallback.InvokeIfSet(&GetInstance(), mReceivedMessage, 0, OT_TCAT_APPLICATION_PROTOCOL_NONE, ""); + mReceiveCallback.InvokeIfSet(&GetInstance(), mReceivedMessage, 0, OT_TCAT_APPLICATION_PROTOCOL_NONE); IgnoreError(mReceivedMessage->SetLength(0)); } else @@ -476,7 +510,7 @@ void BleSecure::HandleTlsReceive(uint8_t *aBuf, uint16_t aLength) { mReceivedMessage->SetOffset((uint16_t)offset); mReceiveCallback.InvokeIfSet(&GetInstance(), mReceivedMessage, (int32_t)offset, - OT_TCAT_APPLICATION_PROTOCOL_NONE, ""); + OT_TCAT_APPLICATION_PROTOCOL_NONE); } SuccessOrExit(mReceivedMessage->SetLength(0)); // also sets the offset to 0 diff --git a/src/core/radio/ble_secure.hpp b/src/core/radio/ble_secure.hpp index 978129f7f..6c46664c3 100644 --- a/src/core/radio/ble_secure.hpp +++ b/src/core/radio/ble_secure.hpp @@ -221,14 +221,19 @@ public: /** * Sends a secure BLE data packet containing a TCAT Send Application Data TLV. * - * @param[in] aBuf A pointer to the data to send as the Value of the TCAT Send Application Data TLV. - * @param[in] aLength A number indicating the length of the data buffer. + * @param[in] aApplicationProtocol An application protocol the data is directed to. + * @param[in] aBuf A pointer to the data to send as the Value of the TCAT application TLV. + * @param[in] aLength A number indicating the length of the data buffer. * - * @retval kErrorNone Successfully sent data. - * @retval kErrorNoBufs Failed to allocate buffer memory. - * @retval kErrorInvalidState TLS connection was not initialized. + * @retval kErrorNone Successfully sent data. + * @retval kErrorNoBufs Failed to allocate buffer memory. + * @retval kErrorInvalidState TLS connection was not initialized. + * @retval kErrorRejected Application protocol is response with data or status but no response is + * pending. */ - Error SendApplicationTlv(uint8_t *aBuf, uint16_t aLength); + Error SendApplicationTlv(MeshCoP::TcatAgent::TcatApplicationProtocol aApplicationProtocol, + uint8_t *aBuf, + uint16_t aLength); /** * Sends all remaining bytes in the send buffer. @@ -276,6 +281,14 @@ public: */ bool GetInstallCodeVerifyStatus(void) const { return mTcatAgent.GetInstallCodeVerifyStatus(); } + /** + * @brief Notifies the BLE layer that the BLE advertisement data should be updated. + * + * @retval kErrorNone Successfully updated. + * @return kErrorFailed Update failed. + */ + Error NotifyAdvertisementChanged(void); + private: enum BleState : uint8_t { diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index 0cc98f991..8d769625a 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -35,6 +35,7 @@ #include "instance/instance.hpp" #include "openthread/platform/toolchain.h" +#include "radio/ble_secure.hpp" #include "utils/static_counter.hpp" namespace ot { @@ -353,6 +354,10 @@ void Mle::SetRole(DeviceRole aRole) break; } +#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE + IgnoreError(Get().NotifyAdvertisementChanged()); +#endif + // If the previous state is disabled, the parent can be in kStateRestored. if (!IsChild() && oldRole != kRoleDisabled) { diff --git a/src/posix/platform/ble.cpp b/src/posix/platform/ble.cpp index 23ebf0d12..a06d6a043 100644 --- a/src/posix/platform/ble.cpp +++ b/src/posix/platform/ble.cpp @@ -102,6 +102,14 @@ otError otPlatBleGapAdvSetData(otInstance *aInstance, uint8_t *aAdvertisementDat return OT_ERROR_NONE; } +otError otPlatBleGapAdvUpdateData(otInstance *aInstance, uint8_t *aAdvertisementData, uint16_t aAdvertisementLen) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aAdvertisementData); + OT_UNUSED_VARIABLE(aAdvertisementLen); + return OT_ERROR_NONE; +} + bool otPlatBleSupportsMultiRadio(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); diff --git a/tests/gtest/fake_platform.cpp b/tests/gtest/fake_platform.cpp index cc568970b..efc9cebbb 100644 --- a/tests/gtest/fake_platform.cpp +++ b/tests/gtest/fake_platform.cpp @@ -536,6 +536,8 @@ bool otPlatBleSupportsMultiRadio(otInstance *) { return false; } otError otPlatBleGapAdvSetData(otInstance *, uint8_t *, uint16_t) { return OT_ERROR_NONE; } +otError otPlatBleGapAdvUpdateData(otInstance *, uint8_t *, uint16_t) { return OT_ERROR_NONE; } + OT_TOOL_WEAK otError otPlatRadioAddCalibratedPower(otInstance *, uint8_t, int16_t, const uint8_t *, uint16_t) { return OT_ERROR_NONE; diff --git a/tests/scripts/expect/_common.exp b/tests/scripts/expect/_common.exp index 8b9968d34..b16f86a52 100644 --- a/tests/scripts/expect/_common.exp +++ b/tests/scripts/expect/_common.exp @@ -266,7 +266,7 @@ proc fail {message} { error $message } -proc spawn_tcat_client_for_node {id} { +proc spawn_tcat_client_for_node {id {cert_path "tools/tcat_ble_client/auth"}} { global tcat_ids global spawn_id @@ -275,7 +275,7 @@ proc spawn_tcat_client_for_node {id} { send "tcat start\n" expect_line "Done" - spawn python "tools/tcat_ble_client/bbtc.py" --simulation $id --cert_path "tools/tcat_ble_client/auth" + spawn python "tools/tcat_ble_client/bbtc.py" --simulation $id --cert_path $cert_path expect_line "Done" set tcat_ids($id) $spawn_id diff --git a/tests/scripts/expect/cli-tcat-decommission.exp b/tests/scripts/expect/cli-tcat-decommission.exp index 302e8db08..cfd5e589c 100755 --- a/tests/scripts/expect/cli-tcat-decommission.exp +++ b/tests/scripts/expect/cli-tcat-decommission.exp @@ -31,7 +31,19 @@ source "tests/scripts/expect/_common.exp" spawn_node 1 "cli" -spawn_tcat_client_for_node 1 +spawn_tcat_client_for_node 1 tools/tcat_ble_client/auth-cert/CommCert2 + +send "commission\n" +expect_line "\tTYPE:\tRESPONSE_W_STATUS" +expect_line "\tVALUE:\t0x10" + +send "random_challenge\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect_line "\tLEN:\t8" + +send "present_hash pskd JJJJJJ\n" +expect_line "\tTYPE:\tRESPONSE_W_STATUS" +expect_line "\tVALUE:\t0x00" send "commission\n" expect_line "\tTYPE:\tRESPONSE_W_STATUS" @@ -41,6 +53,41 @@ send "thread start\n" expect_line "\tTYPE:\tRESPONSE_W_STATUS" expect_line "\tVALUE:\t0x00" +dispose_tcat_client 1 + +switch_node 1 +send "tcat stop\n" +expect_line "Done" + +spawn_tcat_client_for_node 1 + +send "commission\n" +expect_line "\tTYPE:\tRESPONSE_W_STATUS" +expect_line "\tVALUE:\t0x08" + +send "get_dataset\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect_line "\tLEN:\t106" + +send "decommission\n" +expect_line "\tTYPE:\tRESPONSE_W_STATUS" + +dispose_tcat_client 1 + +switch_node 1 +send "tcat stop\n" +expect_line "Done" + +spawn_tcat_client_for_node 1 + +send "commission\n" +expect_line "\tTYPE:\tRESPONSE_W_STATUS" +expect_line "\tVALUE:\t0x00" + +send "get_dataset\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect_line "\tLEN:\t106" + send "decommission\n" expect_line "\tTYPE:\tRESPONSE_W_STATUS" diff --git a/tests/scripts/expect/cli-tcat-hashes.exp b/tests/scripts/expect/cli-tcat-hashes.exp index 90540e44b..631235f37 100755 --- a/tests/scripts/expect/cli-tcat-hashes.exp +++ b/tests/scripts/expect/cli-tcat-hashes.exp @@ -31,11 +31,11 @@ source "tests/scripts/expect/_common.exp" spawn_node 1 "cli" -spawn_tcat_client_for_node 1 +spawn_tcat_client_for_node 1 tools/tcat_ble_client/auth-cert/CommCert2 send "commission\n" expect_line "\tTYPE:\tRESPONSE_W_STATUS" -expect_line "\tVALUE:\t0x00" +expect_line "\tVALUE:\t0x10" send "random_challenge\n" expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" @@ -46,13 +46,17 @@ expect_line "Requested hash is valid." expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" expect_line "\tLEN:\t32" +send "present_hash pskd AAAA\n" +expect_line "\tTYPE:\tRESPONSE_W_STATUS" +expect_line "\tVALUE:\t0x07" + send "present_hash pskd JJJJJJ\n" expect_line "\tTYPE:\tRESPONSE_W_STATUS" expect_line "\tVALUE:\t0x00" -send "present_hash pskd AAAA\n" +send "commission\n" expect_line "\tTYPE:\tRESPONSE_W_STATUS" -expect_line "\tVALUE:\t0x07" +expect_line "\tVALUE:\t0x00" send "present_hash install InstallCode\n" expect_line "\tTYPE:\tRESPONSE_W_STATUS" diff --git a/tests/scripts/expect/cli-tcat.exp b/tests/scripts/expect/cli-tcat.exp index 8296ee809..b483ebebc 100755 --- a/tests/scripts/expect/cli-tcat.exp +++ b/tests/scripts/expect/cli-tcat.exp @@ -39,7 +39,7 @@ expect_line "\tVALUE:\t0x06" send "get_dataset\n" expect_line "\tTYPE:\tRESPONSE_W_STATUS" -expect_line "\tVALUE:\t0x04" +expect_line "\tVALUE:\t0x06" send "get_comm_cert\n" expect_line "\tTYPE:\tRESPONSE_W_STATUS" @@ -94,6 +94,36 @@ expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" expect_line "\tLEN:\t9" expect_line "\tVALUE:\t0x64756d6d795f75726c" +send "get_apps\n" +expect_line "\tApplication 1 is UDP service: echo" +expect_line "\tApplication 2 is TCP service: discard" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect_line "\tLEN:\t15" +expect_line "\tVALUE:\t0x89046563686f8a0764697363617264" + +send "appdata1 1122334455667788\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect_line "\tLEN:\t8" +expect_line "\tVALUE:\t0x1122334455667788" + +send "appdata2 1122334455667788\n" +expect_line "\tTYPE:\tRESPONSE_W_STATUS" +expect_line "\tVALUE:\t0x00" + +send "appdata3 1122334455667788\n" +expect_line "\tTYPE:\tRESPONSE_W_STATUS" +expect_line "\tVALUE:\t0x01" + +send "appdata4 1122334455667788\n" +expect_line "\tTYPE:\tRESPONSE_W_STATUS" +expect_line "\tVALUE:\t0x01" + +send "vendor_data 74657874\n" +expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" +expect_line "\tLEN:\t4" +expect_line "\tVALUE:\t0x74657874" + + dispose_tcat_client 1 switch_node 1 diff --git a/tests/unit/test_platform.cpp b/tests/unit/test_platform.cpp index 4fb5a1f38..6e313dae6 100644 --- a/tests/unit/test_platform.cpp +++ b/tests/unit/test_platform.cpp @@ -858,6 +858,14 @@ otError otPlatBleGapAdvSetData(otInstance *aInstance, uint8_t *aAdvertisementDat return OT_ERROR_NONE; } +otError otPlatBleGapAdvUpdateData(otInstance *aInstance, uint8_t *aAdvertisementData, uint16_t aAdvertisementLen) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aAdvertisementData); + OT_UNUSED_VARIABLE(aAdvertisementLen); + return OT_ERROR_NONE; +} + #endif // OPENTHREAD_CONFIG_BLE_TCAT_ENABLE #if OPENTHREAD_CONFIG_PLATFORM_DNSSD_ENABLE diff --git a/tools/tcat_ble_client/cli/base_commands.py b/tools/tcat_ble_client/cli/base_commands.py index 2305365d5..bf500d158 100644 --- a/tools/tcat_ble_client/cli/base_commands.py +++ b/tools/tcat_ble_client/cli/base_commands.py @@ -107,7 +107,102 @@ class HelloCommand(BleCommand): return 'Send round trip "Hello world!" message.' def prepare_data(self, args, context): - return TLV(TcatTLVType.APPLICATION.value, bytes('Hello world!', 'ascii')).to_bytes() + return TLV(TcatTLVType.VENDOR_APPLICATION.value, bytes('Hello world!', 'ascii')).to_bytes() + + +class GetApplicationLayersCommand(BleCommand): + + def get_log_string(self) -> str: + return 'Getting application layers....' + + def get_help_string(self) -> str: + return 'Get supported application layer service names from device.' + + def prepare_data(self, args, context): + return TLV(TcatTLVType.GET_APPLICATION_LAYERS.value, bytes()).to_bytes() + + def process_response(self, tlv_response, context): + if tlv_response.type == TcatTLVType.RESPONSE_W_PAYLOAD.value: + payload = tlv_response.value + i = 0 + print('Service names:') + while payload: + tlv_application = TLV.from_bytes(payload) + payload = payload[2 + len(tlv_application.value):] + i += 1 + if (tlv_application.type == TcatTLVType.SERVICE_NAME_UDP.value): + print(f"\tApplication {i} is UDP service: {tlv_application.value.decode('ascii')}") + elif (tlv_application.type == TcatTLVType.SERVICE_NAME_TCP.value): + print(f"\tApplication {i} is TCP service: {tlv_application.value.decode('ascii')}") + else: + print('\tUnknown service type.') + else: + print('Dataset extraction error.') + + +class SendApplicationData1(BleCommand): + + def get_log_string(self) -> str: + return 'Sending data to application layer 1....' + + def get_help_string(self) -> str: + return 'Send hex encoded data to application layer 1.' + + def prepare_data(self, args, context): + payload = bytes.fromhex(args[0]) + return TLV(TcatTLVType.APPLICATION_DATA_1.value, payload).to_bytes() + + +class SendApplicationData2(BleCommand): + + def get_log_string(self) -> str: + return 'Sending data to application layer 2....' + + def get_help_string(self) -> str: + return 'Send hex encoded data to application layer 2.' + + def prepare_data(self, args, context): + payload = bytes.fromhex(args[0]) + return TLV(TcatTLVType.APPLICATION_DATA_2.value, payload).to_bytes() + + +class SendApplicationData3(BleCommand): + + def get_log_string(self) -> str: + return 'Sending data to application layer 3....' + + def get_help_string(self) -> str: + return 'Send hex encoded data to application layer 3.' + + def prepare_data(self, args, context): + payload = bytes.fromhex(args[0]) + return TLV(TcatTLVType.APPLICATION_DATA_3.value, payload).to_bytes() + + +class SendApplicationData4(BleCommand): + + def get_log_string(self) -> str: + return 'Sending data to application layer 4....' + + def get_help_string(self) -> str: + return 'Send hex encoded data to application layer 4.' + + def prepare_data(self, args, context): + payload = bytes.fromhex(args[0]) + return TLV(TcatTLVType.APPLICATION_DATA_4.value, payload).to_bytes() + + +class SendVendorData(BleCommand): + + def get_log_string(self) -> str: + return 'Sending data to vendor specific application layer....' + + def get_help_string(self) -> str: + return 'Send hex encoded data to vendor specific application layer.' + + def prepare_data(self, args, context): + payload = bytes.fromhex(args[0]) + return TLV(TcatTLVType.VENDOR_APPLICATION.value, payload).to_bytes() class CommissionCommand(BleCommand): diff --git a/tools/tcat_ble_client/cli/cli.py b/tools/tcat_ble_client/cli/cli.py index 9d73411a5..9c6ad353a 100644 --- a/tools/tcat_ble_client/cli/cli.py +++ b/tools/tcat_ble_client/cli/cli.py @@ -33,7 +33,8 @@ from cli.base_commands import (DisconnectCommand, HelpCommand, HelloCommand, Com ExtractDatasetCommand, GetCommissionerCertificate, GetDeviceIdCommand, GetPskdHash, GetExtPanIDCommand, GetNetworkNameCommand, GetProvisioningUrlCommand, PingCommand, GetRandomNumberChallenge, ThreadStateCommand, ScanCommand, PresentHash, - DiagnosticTlvsCommand) + DiagnosticTlvsCommand, GetApplicationLayersCommand, SendVendorData, + SendApplicationData1, SendApplicationData2, SendApplicationData3, SendApplicationData4) from .tlv_commands import TlvCommand from cli.dataset_commands import (DatasetCommand) from dataset.dataset import ThreadDataset @@ -49,6 +50,12 @@ class CLI: self._commands = { 'help': HelpCommand(), 'hello': HelloCommand(), + 'get_apps': GetApplicationLayersCommand(), + 'appdata1': SendApplicationData1(), + 'appdata2': SendApplicationData2(), + 'appdata3': SendApplicationData3(), + 'appdata4': SendApplicationData4(), + 'vendor_data': SendVendorData(), 'commission': CommissionCommand(), 'decommission': DecommissionCommand(), 'disconnect': DisconnectCommand(), diff --git a/tools/tcat_ble_client/cli/command.py b/tools/tcat_ble_client/cli/command.py index e6b819b10..da858f358 100644 --- a/tools/tcat_ble_client/cli/command.py +++ b/tools/tcat_ble_client/cli/command.py @@ -90,10 +90,7 @@ class CommandResultTLV(CommandResult): else: print(f'\tTYPE:\tunknown: {hex(tlv.type)} ({tlv.type})') print(f'\tLEN:\t{len(tlv.value)}') - if tlv_type == TcatTLVType.APPLICATION: - print(f'\tVALUE:\t{tlv.value.decode("ascii")}') - else: - print(f'\tVALUE:\t0x{tlv.value.hex()}') + print(f'\tVALUE:\t0x{tlv.value.hex()}') class CommandResultNone(CommandResult): diff --git a/tools/tcat_ble_client/dataset/dataset_entries.py b/tools/tcat_ble_client/dataset/dataset_entries.py index 0ffd6ea07..c1671f3db 100644 --- a/tools/tcat_ble_client/dataset/dataset_entries.py +++ b/tools/tcat_ble_client/dataset/dataset_entries.py @@ -22,6 +22,8 @@ from abc import ABC, abstractmethod from tlv.dataset_tlv import MeshcopTlvType from tlv.tlv import TLV +MASK_48_BITS = 0xFFFFFFFFFFFF + class DatasetEntry(ABC): @@ -66,13 +68,13 @@ class ActiveTimestamp(DatasetEntry): def set(self, args: List[str]): if len(args) == 0: raise ValueError('No argument for ActiveTimestamp') - self._seconds = int(args[0]) + self.seconds = int(args[0]) def set_from_tlv(self, tlv: TLV): (value,) = struct.unpack('>Q', tlv.value) self.ubit = value & 0x1 self.ticks = (value >> 1) & 0x7FFF - self.seconds = (value >> 16) & 0xFFFF + self.seconds = (value >> 16) & MASK_48_BITS def to_tlv(self): value = (self.seconds << 16) | (self.ticks << 1) | self.ubit @@ -92,7 +94,7 @@ class PendingTimestamp(DatasetEntry): def set(self, args: List[str]): if len(args) == 0: raise ValueError('No argument for PendingTimestamp') - self._seconds = int(args[0]) + self.seconds = int(args[0]) def set_from_tlv(self, tlv: TLV): (value,) = struct.unpack('>Q', tlv.value) @@ -476,6 +478,30 @@ class ChannelMaskEntry(DatasetEntry): return TLV.from_bytes(tlv) +class WakeupChannel(DatasetEntry): + + def __init__(self): + super().__init__(MeshcopTlvType.WAKEUP_CHANNEL) + self.length = 3 # spec defined + self.channel_page = 0 + self.channel = 0 + + def set(self, args: List[str]): + if len(args) == 0: + raise ValueError('No argument for WakeupChannel') + channel = int(args[0]) + self.channel = channel + + def set_from_tlv(self, tlv: TLV): + self.channel = int.from_bytes(tlv.value[1:3], byteorder='big') + self.channel_page = tlv.value[0] + + def to_tlv(self): + tlv = struct.pack('>BBB', self.type.value, self.length, self.channel_page) + tlv += struct.pack('>H', self.channel) + return TLV.from_bytes(tlv) + + ENTRY_CLASSES = { MeshcopTlvType.ACTIVETIMESTAMP: ActiveTimestamp, MeshcopTlvType.PENDINGTIMESTAMP: PendingTimestamp, @@ -488,7 +514,8 @@ ENTRY_CLASSES = { MeshcopTlvType.CHANNEL: Channel, MeshcopTlvType.PSKC: Pskc, MeshcopTlvType.SECURITYPOLICY: SecurityPolicy, - MeshcopTlvType.CHANNELMASK: ChannelMask + MeshcopTlvType.CHANNELMASK: ChannelMask, + MeshcopTlvType.WAKEUP_CHANNEL: WakeupChannel } diff --git a/tools/tcat_ble_client/poetry.lock b/tools/tcat_ble_client/poetry.lock index 72a06109f..330999350 100644 --- a/tools/tcat_ble_client/poetry.lock +++ b/tools/tcat_ble_client/poetry.lock @@ -1,14 +1,14 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "async-timeout" -version = "4.0.2" +version = "4.0.3" description = "Timeout context manager for asyncio programs" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, - {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, ] [package.dependencies] @@ -67,54 +67,61 @@ files = [ [[package]] name = "dbus-fast" -version = "1.90.1" +version = "1.95.2" description = "A faster version of dbus-next" optional = false python-versions = ">=3.7,<4.0" files = [ - {file = "dbus_fast-1.90.1-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:657f9f292f770b50c755bc9cc3607ec0901a607d6d6e31c67aa953b73b31d66a"}, - {file = "dbus_fast-1.90.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:229cb2dc0942dfe36ce4f5a49cacb7f39ae05527c6ccec66b9a670ca7a02129a"}, - {file = "dbus_fast-1.90.1-cp310-cp310-manylinux_2_31_x86_64.whl", hash = "sha256:4978d6dca49b778c426f279f014554e29ece6bfc7530fa8ab9d258f068e5954e"}, - {file = "dbus_fast-1.90.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2161851a1f90a1c2fe064d1870b04bdca0033b42fdc97cc7d5132637227ac915"}, - {file = "dbus_fast-1.90.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0012799b154fb6b066ff6948f5edd0c6bf8655fca6f3578fc78598334f9f978b"}, - {file = "dbus_fast-1.90.1-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:0b97748928e3e56bc98f292be894d1d3c2a4acc58555795a3aa3b74769b96542"}, - {file = "dbus_fast-1.90.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2baa2e1053ded0a5ccdefc651ee8fcd09d6f4f864b9f301363dbf0545f95a89"}, - {file = "dbus_fast-1.90.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0262ab1d3d07ac892645d4cec54daabd3ba4a096ca4c4b2e9f09abd1819ca663"}, - {file = "dbus_fast-1.90.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c39a352a893923d255031d15fa005ac5f5df2d1195729f206fc79a95b219daed"}, - {file = "dbus_fast-1.90.1-cp37-cp37m-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:bd012a0ed7e6479bcc5b0efe91a45c3abb3e0e4e371a28c0f3c347cb9baffe8a"}, - {file = "dbus_fast-1.90.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3e895adfa89a6c08d23fd22707bd5ea8a301579f3a6ff8bd33df1e94ea16e8a"}, - {file = "dbus_fast-1.90.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a94806e9990b6a7fd4896ee2a979c20af4e5ba76bfddda55a7a79f4da268b51f"}, - {file = "dbus_fast-1.90.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ae0b95a08db0a7e38452926c8d5964d41e5f20d8c89fd00b8913ec4f1908f7bd"}, - {file = "dbus_fast-1.90.1-cp38-cp38-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:3ce5153accbbb7fc2aeab055f46d5611c4c57978d43feba9257fba53c338ebbe"}, - {file = "dbus_fast-1.90.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3b05b68a7be76df3e4833c72d9fadf1b934359ed3fabd69ed205eaa18d132a6"}, - {file = "dbus_fast-1.90.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d407eb9a3581ee5cc047a664d3d15e9846e5a1c0b3565922c72ffe15fea590f2"}, - {file = "dbus_fast-1.90.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:85f06e4cbf560e682930f0d23ea93b1887c40db00b98ee4d6a207aa2a616851e"}, - {file = "dbus_fast-1.90.1-cp39-cp39-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:28a4ee863b4a42351afb38ce9432850922dc2f0d9d9863f98fcb47e23b710572"}, - {file = "dbus_fast-1.90.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fd6090c794ca3404702b59cb4dc92f674e26a15bb79a9d5ae0236658c10cac5"}, - {file = "dbus_fast-1.90.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9e7be16e96bcad521f2c045561799fc486047b6e1ce071c35a5cea36a9ed19f4"}, - {file = "dbus_fast-1.90.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:01f582d9c3f24e1721f3dd9a62c7a558c7c8406752eb042738623b9d89f454e9"}, - {file = "dbus_fast-1.90.1-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:085fd80c7ea3e41a2ac32e419611036a042593778fecb4d92526a22fd2be2c0b"}, - {file = "dbus_fast-1.90.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e0dbffd42875d31d0c7e2407eff685d5cb2dfa7c0448079c96ef2cec0afe8be"}, - {file = "dbus_fast-1.90.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:04eaaea336059909b5cb51c897fc343e038e80f698a049b1bf3002d162d4fec7"}, - {file = "dbus_fast-1.90.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fc215dbccb798df07ee6104b984ae09c159ed3a633df915c3c6dd9df97af753"}, - {file = "dbus_fast-1.90.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:74f7afaf780fbbc7f39cc8167ec585f8e7ce140d214768f5c296c2b03d23e571"}, - {file = "dbus_fast-1.90.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f12c7bcec416e28ba1f741e43ad2eef7eb3c677838208cf6a801ced711b508c"}, - {file = "dbus_fast-1.90.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:c0811b7bdfdce40072fd7c29f0c7e0145982b981bb068efd79a5e43ef14150e5"}, - {file = "dbus_fast-1.90.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1f00197e08c7c5837861624fd1afb8e1d8331d4e28e8d2ff01dc17ac2305ae2"}, - {file = "dbus_fast-1.90.1.tar.gz", hash = "sha256:eff98b45443681bd8876bbb1444b35112d62e8d12157f004d88ebe5f0481d5b7"}, + {file = "dbus_fast-1.95.2-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:69f19fb94ac714b917c51fcc329b51695f085f779841edd6e429170f1f073f47"}, + {file = "dbus_fast-1.95.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c71e84a7ce3b050745dfb2bcd32ade891ecf6f4b06d3baa4dfe30ad09720a0be"}, + {file = "dbus_fast-1.95.2-cp310-cp310-manylinux_2_31_x86_64.whl", hash = "sha256:3ffcd1c999599da7806d7887d5ce1e9f0d814aa35af92daecab542c9b70794c1"}, + {file = "dbus_fast-1.95.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:85739b90e6f983494c3c7187d4daaa50f4f4369aa1e87e876066de97ca1bcd58"}, + {file = "dbus_fast-1.95.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e2c107caefa186919169e07a852edea3d931dfb6112584fa2c6e5653d91c0d2d"}, + {file = "dbus_fast-1.95.2-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:99902ca6c10368492fdf5a28321d86c7e92e50fd742f0163599602c442232d29"}, + {file = "dbus_fast-1.95.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3241252a744b0960e7eb9dcad2892af2bc695b4f47636bab2f17f6012d914ce"}, + {file = "dbus_fast-1.95.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f52ef5edef41a0ed29645d7c6e1ee89b5e8f5e5c1ba9901699dcec9cfaf8d961"}, + {file = "dbus_fast-1.95.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9706e10a6c24d4c03d28b1631e3882009964d5207a86cae2b42466635d7546be"}, + {file = "dbus_fast-1.95.2-cp312-cp312-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:3277944b418063ad051e8e49144151962691188b972f1fbca7af39fdef4f8a47"}, + {file = "dbus_fast-1.95.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cab158bcf86c4b2e03c2f3453738a61de83c82cbed11f23331ccc9a9aba6d5b1"}, + {file = "dbus_fast-1.95.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:17d743d13dbde0691adb98f1a6c87f0cf5617a4c9169b4820972dc8869095c6b"}, + {file = "dbus_fast-1.95.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aefa695088405d313c703790ac503ad0b2ac1e4807393f1f02e410b6984aedd0"}, + {file = "dbus_fast-1.95.2-cp37-cp37m-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:ffd3665a6fbe318aadac998ae117b19214e0782397c86ab47792a120979783a6"}, + {file = "dbus_fast-1.95.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43093d8e42342841c7ac69e236370c31949c35b659032888dae8b3af1fa35e9e"}, + {file = "dbus_fast-1.95.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4f4b26849254fafb11656fc8c10f8bce67deab683694e0fc7b56edd891d4e118"}, + {file = "dbus_fast-1.95.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:331c4a26e010fd8ad3168b9680f39a7d0765507d3aadcbe5f250da0474877f14"}, + {file = "dbus_fast-1.95.2-cp38-cp38-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:7aee30619c79334980a25a1e94299a751b3f276644ec0a69c76bfe57be184e7a"}, + {file = "dbus_fast-1.95.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c5d344e3fe3c4b16593fad70448c66ca3130c8c692c74a921c15f18650581a7"}, + {file = "dbus_fast-1.95.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9180d4d39d8688b9d8d43f427f0f2883354fa9df18a1f46d14f1762b82249d1f"}, + {file = "dbus_fast-1.95.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e19173e65967581186666a47cd0af06111c7e957fb529198e532b35a47171460"}, + {file = "dbus_fast-1.95.2-cp39-cp39-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:fae11a8ed320013decd2bf6a982f010fff011bf935c4f32141e804ba7963719b"}, + {file = "dbus_fast-1.95.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f653085d9f96ed2e48434ba951cf4bf0229cc273a87b9799e7cf3a9a6612a56"}, + {file = "dbus_fast-1.95.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:05d53e0db08de6b3ba43eb110e01b9e6264e7143849ea9772af45b15575c21b2"}, + {file = "dbus_fast-1.95.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4e73b7dcbac5f418bd4b334ad112e40e8b3bc05c53a3fc451926450ce3bb805e"}, + {file = "dbus_fast-1.95.2-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:76fb9d11bd6ebe1f832350ec57ada352f096f24b5731e38bcbad86ab9b27190b"}, + {file = "dbus_fast-1.95.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26045c50bbd8c1674814759332333799b073216b2b2cac9e1cfebf757c7f926d"}, + {file = "dbus_fast-1.95.2-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:bd68b5e7d7c6cd10d804d072062100a4893ad0c19bdac03b53b1809c51a4e3c7"}, + {file = "dbus_fast-1.95.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea2692efe0d6d6d58fb427cd0ed53ab44ced0c2ada642b0c15fd139c60a706d6"}, + {file = "dbus_fast-1.95.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:33cee333a15241e516ee84aaabdd952dbb1a63f9e986028574451475e97a113f"}, + {file = "dbus_fast-1.95.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dfd99cf624f769cf838e62d75ceb737b2866b1783a4f11a928cf38e4c906a5f"}, + {file = "dbus_fast-1.95.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:b842e94140e39a196d4d83e02eddcbc461b906871ba02f1c5374055f2d628c47"}, + {file = "dbus_fast-1.95.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08ca9968dd46b854e5c7b00be32cb273edaf17ddc9647811ff63ae3b9dca822d"}, + {file = "dbus_fast-1.95.2.tar.gz", hash = "sha256:3dd64c5cd362ceead6cc02603b6b4cbda58b2cbb6ec816a2f21b1901dfc3cb61"}, ] [[package]] name = "exceptiongroup" -version = "1.1.2" +version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"}, - {file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"}, + {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, + {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, ] +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + [package.extras] test = ["pytest (>=6)"] @@ -151,13 +158,13 @@ files = [ [[package]] name = "packaging" -version = "23.1" +version = "24.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, - {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] [[package]] @@ -254,13 +261,13 @@ pyobjc-core = ">=9.2" [[package]] name = "pytest" -version = "7.4.0" +version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, - {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] diff --git a/tools/tcat_ble_client/tlv/dataset_tlv.py b/tools/tcat_ble_client/tlv/dataset_tlv.py index 4bd020c8d..ec12b9a0d 100644 --- a/tools/tcat_ble_client/tlv/dataset_tlv.py +++ b/tools/tcat_ble_client/tlv/dataset_tlv.py @@ -67,6 +67,8 @@ class MeshcopTlvType(Enum): PERIOD = 55 SCAN_DURATION = 56 ENERGY_LIST = 57 + THREAD_DOMAIN_NAME = 59 + WAKEUP_CHANNEL = 74 DISCOVERYREQUEST = 128 DISCOVERYRESPONSE = 129 JOINERADVERTISEMENT = 241 diff --git a/tools/tcat_ble_client/tlv/tcat_tlv.py b/tools/tcat_ble_client/tlv/tcat_tlv.py index e78e23c96..ce0604a1f 100644 --- a/tools/tcat_ble_client/tlv/tcat_tlv.py +++ b/tools/tcat_ble_client/tlv/tcat_tlv.py @@ -47,7 +47,14 @@ class TcatTLVType(Enum): GET_ACTIVE_DATASET = 0x40 GET_DIAGNOSTIC_TLVS = 0x26 DECOMMISSION = 0x60 - APPLICATION = 0x82 + GET_APPLICATION_LAYERS = 0x80 + APPLICATION_DATA_1 = 0x81 + APPLICATION_DATA_2 = 0x82 + APPLICATION_DATA_3 = 0x83 + APPLICATION_DATA_4 = 0x84 + SERVICE_NAME_UDP = 0x89 + SERVICE_NAME_TCP = 0x8A + VENDOR_APPLICATION = 0x9F THREAD_START = 0x27 THREAD_STOP = 0x28