From b3d3b5c3c7de62bdcdd6a5cbabfbd0b545f2e91f Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Fri, 16 Jan 2026 10:20:13 -0800 Subject: [PATCH] [netdiag] require 'RD:' prefix for vendor name on reference devices (#12233) When `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is active, this change mandates that the vendor name string MUST begin with the "RD:" prefix. This ensures that reference devices are clearly and consistently identifiable through network diagnostic queries. The enforcement is applied at two levels: - A compile-time `static_assert` is added to validate the default `OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME` at build time. This uses a new `constexpr` helper utility `CheckConstStringPrefix()`. - A runtime check is added to `otThreadSetVendorName()`, which will now return `OT_ERROR_INVALID_ARGS` if an invalid name is provided on a reference device build. All related test configurations (`scan-build`, `toranj`, `nexus`) and CLI tests are updated to reflect this new requirement and validate it. --- include/openthread/instance.h | 2 +- include/openthread/netdiag.h | 7 +++- script/check-scan-build | 2 +- src/core/common/string.hpp | 19 +++++++++++ src/core/config/network_diagnostic.h | 9 ++++++ src/core/thread/network_diagnostic.cpp | 18 ++++++++++- src/core/thread/network_diagnostic.hpp | 7 +++- tests/nexus/openthread-core-nexus-config.h | 2 +- tests/scripts/expect/cli-tcat-diagnostics.exp | 4 +-- .../cli/test-020-net-diag-vendor-info.py | 32 +++++++++++++------ tests/toranj/openthread-core-toranj-config.h | 2 +- tests/unit/test_string.cpp | 9 ++++++ tools/otci/tests/test_otci.py | 4 +-- 13 files changed, 96 insertions(+), 21 deletions(-) diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 711b82a65..66b98ac32 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 (572) +#define OPENTHREAD_API_VERSION (573) /** * @addtogroup api-instance diff --git a/include/openthread/netdiag.h b/include/openthread/netdiag.h index 9770b70fd..19a6ab100 100644 --- a/include/openthread/netdiag.h +++ b/include/openthread/netdiag.h @@ -433,11 +433,16 @@ const char *otThreadGetVendorAppUrl(otInstance *aInstance); * @p aVendorName should be UTF8 with max length of 32 chars (`MAX_VENDOR_NAME_TLV_LENGTH`). Maximum length does not * include the null `\0` character. * + * If `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is enabled, @p aVendorName must start with the "RD:" prefix. + * This is enforced to ensure reference devices are identifiable. If @p aVendorName does not follow this pattern, + * the name is rejected, and `OT_ERROR_INVALID_ARGS` is returned. + * * @param[in] aInstance A pointer to an OpenThread instance. * @param[in] aVendorName The vendor name string. * * @retval OT_ERROR_NONE Successfully set the vendor name. - * @retval OT_ERROR_INVALID_ARGS @p aVendorName is not valid (too long or not UTF8). + * @retval OT_ERROR_INVALID_ARGS @p aVendorName is not valid. It is too long, is not UTF-8, or does not start with + * the "RD:" prefix when `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is enabled. */ otError otThreadSetVendorName(otInstance *aInstance, const char *aVendorName); diff --git a/script/check-scan-build b/script/check-scan-build index a9955f50c..13a753b76 100755 --- a/script/check-scan-build +++ b/script/check-scan-build @@ -82,7 +82,7 @@ OT_BUILD_OPTIONS=( "-DOT_SRP_SERVER=ON" "-DOT_SRP_SERVER_FAST_START_MODE=ON" "-DOT_UPTIME=ON" - "-DOT_VENDOR_NAME=OpenThread" + "-DOT_VENDOR_NAME=RD:OpenThread" "-DOT_VENDOR_MODEL=Scan-build" "-DOT_VENDOR_SW_VERSION=OT" ) diff --git a/src/core/common/string.hpp b/src/core/common/string.hpp index 6c9202bc5..fe919b338 100644 --- a/src/core/common/string.hpp +++ b/src/core/common/string.hpp @@ -396,6 +396,25 @@ inline constexpr bool AreConstStringsEqual(const char *aFirst, const char *aSeco : false; } +/** + * This `constexpr` function checks whether a given C string starts with a given prefix string. + * + * This is intended for use in `static_assert`, e.g., checking the hardcoded vendor name on a reference device. It is + * not recommended to use this function in other situations as it uses recursion so that it can be `constexpr`. + * + * @param[in] aString The string to check. + * @param[in] aPrefix The prefix string. + * + * @retval TRUE If @p aString starts with @p aPrefix. + * @retval FALSE If @p aString does not start with @p aPrefix. + */ +inline constexpr bool CheckConstStringPrefix(const char *aString, const char *aPrefix) +{ + return (*aPrefix == kNullChar) + ? true + : ((*aString == *aPrefix) ? CheckConstStringPrefix(aString + 1, aPrefix + 1) : false); +} + /** * Implements writing to a string buffer. */ diff --git a/src/core/config/network_diagnostic.h b/src/core/config/network_diagnostic.h index b67a2d0ce..0502cfe70 100644 --- a/src/core/config/network_diagnostic.h +++ b/src/core/config/network_diagnostic.h @@ -43,14 +43,23 @@ * @{ */ +#include "config/misc.h" + /** * @def OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME * * Specifies the default Vendor Name string. + * + * If `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is enabled, the Vendor Name string MUST start with the "RD:" prefix + * to ensure reference devices are identifiable. This is checked and enforced at build-time (`static_assert`). */ #ifndef OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE +#define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME "RD:" +#else #define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME "" #endif +#endif /** * @def OPENTHREAD_CONFIG_NET_DIAG_VENDOR_MODEL diff --git a/src/core/thread/network_diagnostic.cpp b/src/core/thread/network_diagnostic.cpp index 97ff2ef34..e44e87521 100644 --- a/src/core/thread/network_diagnostic.cpp +++ b/src/core/thread/network_diagnostic.cpp @@ -46,6 +46,13 @@ const char Server::kVendorModel[] = OPENTHREAD_CONFIG_NET_DIAG_VENDOR_MODEL; const char Server::kVendorSwVersion[] = OPENTHREAD_CONFIG_NET_DIAG_VENDOR_SW_VERSION; const char Server::kVendorAppUrl[] = OPENTHREAD_CONFIG_NET_DIAG_VENDOR_APP_URL; +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE +static constexpr char kVendorNamePrefix[] = "RD:"; + +static_assert(CheckConstStringPrefix(OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME, kVendorNamePrefix), + "VENDOR_NAME MUST start with 'RD:' prefix for a reference device."); +#endif + //--------------------------------------------------------------------------------------------------------------------- // Server @@ -70,7 +77,16 @@ Server::Server(Instance &aInstance) Error Server::SetVendorName(const char *aVendorName) { - return StringCopy(mVendorName, aVendorName, kStringCheckUtf8Encoding); + Error error; + +#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE + VerifyOrExit(aVendorName != nullptr && StringStartsWith(aVendorName, kVendorNamePrefix), error = kErrorInvalidArgs); +#endif + + SuccessOrExit(error = StringCopy(mVendorName, aVendorName, kStringCheckUtf8Encoding)); + +exit: + return error; } Error Server::SetVendorModel(const char *aVendorModel) diff --git a/src/core/thread/network_diagnostic.hpp b/src/core/thread/network_diagnostic.hpp index 3bba00333..bd00606bb 100644 --- a/src/core/thread/network_diagnostic.hpp +++ b/src/core/thread/network_diagnostic.hpp @@ -131,8 +131,13 @@ public: * * @param[in] aVendorName The vendor name string. * + * If `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is enabled, @p aVendorName must start with the "RD:" prefix. + * This is enforced to ensure reference devices are identifiable. If @p aVendorName does not follow this pattern, + * the name is rejected, and `kErrorInvalidArgs` is returned. + * * @retval kErrorNone Successfully set the vendor name. - * @retval kErrorInvalidArgs @p aVendorName is not valid (too long or not UTF8). + * @retval kErrorInvalidArgs @p aVendorName is not valid. It is too long, is not UTF-8, or does not start with + * the "RD:" prefix when `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is enabled. */ Error SetVendorName(const char *aVendorName); diff --git a/tests/nexus/openthread-core-nexus-config.h b/tests/nexus/openthread-core-nexus-config.h index 113fb72a4..df3eec2d9 100644 --- a/tests/nexus/openthread-core-nexus-config.h +++ b/tests/nexus/openthread-core-nexus-config.h @@ -105,7 +105,7 @@ #define OPENTHREAD_CONFIG_NAT64_IDLE_TIMEOUT_SECONDS 600 #define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE OPENTHREAD_FTD #define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_MODEL "Nexus Simulation" -#define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME "OpenThread by Google Nest" +#define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME "RD:OpenThread by Google Nest" #define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_SW_VERSION "OT-simul-nexus" #define OPENTHREAD_CONFIG_NETDATA_PUBLISHER_ENABLE 1 #define OPENTHREAD_CONFIG_NUM_MESSAGE_BUFFERS 256 diff --git a/tests/scripts/expect/cli-tcat-diagnostics.exp b/tests/scripts/expect/cli-tcat-diagnostics.exp index 5b492320a..ca6cb0367 100755 --- a/tests/scripts/expect/cli-tcat-diagnostics.exp +++ b/tests/scripts/expect/cli-tcat-diagnostics.exp @@ -71,8 +71,8 @@ expect -re {\tVALUE:\t0x2242([0-9a-fA-F]{132})} send "diagnostic_tlvs channelpages maxchildtimeout eui64 vendorname vendormodel vendorswversion vendorappurl\n" expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" -expect_line "\tLEN:\t27" -expect_line "\tVALUE:\t0x1101001304000000f0170818b430000000000119001a001b002300" +expect_line "\tLEN:\t30" +expect_line "\tVALUE:\t0x1101001304000000f0170818b4300000000001190352443a1a001b002300" send "diagnostic_tlvs childtable\n" expect_line "\tTYPE:\tRESPONSE_W_PAYLOAD" diff --git a/tests/toranj/cli/test-020-net-diag-vendor-info.py b/tests/toranj/cli/test-020-net-diag-vendor-info.py index 86acd0371..a25e50743 100755 --- a/tests/toranj/cli/test-020-net-diag-vendor-info.py +++ b/tests/toranj/cli/test-020-net-diag-vendor-info.py @@ -75,12 +75,12 @@ NON_PREFERRED_CHANNELS_TLV = 36 #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Check setting vendor name, model, ans sw version -r1.set_vendor_name('nest') +r1.set_vendor_name('RD:nest') r1.set_vendor_model('marble') r1.set_vendor_sw_version('ot-1.4') r1.set_vendor_app_url('https://example.com/vendor-app') -verify(r1.get_vendor_name() == 'nest') +verify(r1.get_vendor_name() == 'RD:nest') verify(r1.get_vendor_model() == 'marble') verify(r1.get_vendor_sw_version() == 'ot-1.4') verify(r1.get_vendor_app_url() == 'https://example.com/vendor-app') @@ -90,12 +90,24 @@ verify(r1.get_vendor_app_url() == 'https://example.com/vendor-app') # Vendor name should accept up to 32 chars -r2.set_vendor_name('01234567890123456789012345678901') # 32 chars +r2.set_vendor_name('RD:01234567890123456789012345678') # 32 chars errored = False try: - r2.set_vendor_name('012345678901234567890123456789012') # 33 chars + r2.set_vendor_name('RD:012345678901234567890123456789') # 33 chars +except cli.CliError as e: + verify(e.message == 'InvalidArgs') + errored = True + +verify(errored) + +# Vendor name under `REFERENCE_DEVICE` build MUST start with "RD:". + +errored = False + +try: + r2.set_vendor_name('name_without_RD_prefix') except cli.CliError as e: verify(e.message == 'InvalidArgs') errored = True @@ -143,21 +155,21 @@ r2_rloc = r2.get_rloc_ip_addr() result = r2.cli('networkdiagnostic get', r1_rloc, VENDOR_NAME_TLV) verify(len(result) == 2) verify(result[1].startswith("Vendor Name:")) -verify(result[1].split(':')[1].strip() == r1.get_vendor_name()) +verify(result[1].split(':', 1)[1].strip() == r1.get_vendor_name()) # Get vendor model (TLV 28) result = r2.cli('networkdiagnostic get', r1_rloc, VENDOR_MODEL_TLV) verify(len(result) == 2) verify(result[1].startswith("Vendor Model:")) -verify(result[1].split(':')[1].strip() == r1.get_vendor_model()) +verify(result[1].split(':', 1)[1].strip() == r1.get_vendor_model()) # Get vendor sw version (TLV 29) result = r2.cli('networkdiagnostic get', r1_rloc, VENDOR_SW_VERSION_TLV) verify(len(result) == 2) verify(result[1].startswith("Vendor SW Version:")) -verify(result[1].split(':')[1].strip() == r1.get_vendor_sw_version()) +verify(result[1].split(':', 1)[1].strip() == r1.get_vendor_sw_version()) # Get vendor app URL (TLV 35) @@ -180,11 +192,11 @@ result = r1.cli('networkdiagnostic get', r2_rloc, VENDOR_NAME_TLV, VENDOR_MODEL_ verify(len(result) == 6) for line in result[1:]: if line.startswith("Vendor Name:"): - verify(line.split(':')[1].strip() == r2.get_vendor_name()) + verify(line.split(':', 1)[1].strip() == r2.get_vendor_name()) elif line.startswith("Vendor Model:"): - verify(line.split(':')[1].strip() == r2.get_vendor_model()) + verify(line.split(':', 1)[1].strip() == r2.get_vendor_model()) elif line.startswith("Vendor SW Version:"): - verify(line.split(':')[1].strip() == r2.get_vendor_sw_version()) + verify(line.split(':', 1)[1].strip() == r2.get_vendor_sw_version()) elif line.startswith("Vendor App URL:"): verify(line.split(':', 1)[1].strip() == r2.get_vendor_app_url()) elif line.startswith("Thread Stack Version:"): diff --git a/tests/toranj/openthread-core-toranj-config.h b/tests/toranj/openthread-core-toranj-config.h index f25f74155..7a5739e5c 100644 --- a/tests/toranj/openthread-core-toranj-config.h +++ b/tests/toranj/openthread-core-toranj-config.h @@ -208,7 +208,7 @@ #define OPENTHREAD_CONFIG_MLE_PARENT_RESPONSE_CALLBACK_API_ENABLE 1 #ifndef OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME -#define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME "OpenThread by Google Nest" +#define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME "RD:OpenThread by Google Nest" #endif #ifndef OPENTHREAD_CONFIG_NET_DIAG_VENDOR_MODEL diff --git a/tests/unit/test_string.cpp b/tests/unit/test_string.cpp index 251fc31fb..716bd66dd 100644 --- a/tests/unit/test_string.cpp +++ b/tests/unit/test_string.cpp @@ -466,6 +466,15 @@ static_assert(!ot::AreStringsInOrder("z", "abcd"), "AreStringsInOrder() failed") static_assert(!ot::AreStringsInOrder("0", ""), "AreStringsInOrder() failed"); #endif +static_assert(ot::CheckConstStringPrefix("abc", "a"), "CheckConstStringPrefix() failed"); +static_assert(ot::CheckConstStringPrefix("abc", "ab"), "CheckConstStringPrefix() failed"); +static_assert(ot::CheckConstStringPrefix("abc", "abc"), "CheckConstStringPrefix() failed"); +static_assert(ot::CheckConstStringPrefix("abc", ""), "CheckConstStringPrefix() failed"); + +static_assert(!ot::CheckConstStringPrefix("abc", "b"), "CheckConstStringPrefix() failed"); +static_assert(!ot::CheckConstStringPrefix("abc", "abcd"), "CheckConstStringPrefix() failed"); +static_assert(!ot::CheckConstStringPrefix("", "a"), "CheckConstStringPrefix() failed"); + } // namespace ot int main(void) diff --git a/tools/otci/tests/test_otci.py b/tools/otci/tests/test_otci.py index 40662fe39..5bdbb9dde 100644 --- a/tools/otci/tests/test_otci.py +++ b/tools/otci/tests/test_otci.py @@ -353,8 +353,8 @@ class TestOTCI(unittest.TestCase): logging.info('dataset active -x: %r', leader.get_dataset_bytes('active')) logging.info('dataset pending -x: %r', leader.get_dataset_bytes('pending')) - leader.set_vendor_name('OpenThread') - self.assertEqual('OpenThread', leader.get_vendor_name()) + leader.set_vendor_name('RD:OpenThread') + self.assertEqual('RD:OpenThread', leader.get_vendor_name()) leader.set_vendor_model('some_model') self.assertEqual('some_model', leader.get_vendor_model()) leader.set_vendor_sw_version('1.0.0')